Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion host/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ type Trace struct {
PID libpf.PID
TID libpf.PID
Origin libpf.Origin
OffTime int64 // Time a task was off-cpu in nanoseconds.
ContextValue int64 // Context value: off-cpu time (ns) or custom context ID.
APMTraceID libpf.APMTraceID
APMTransactionID libpf.APMTransactionID
CPU int
Expand Down
2 changes: 1 addition & 1 deletion processmanager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ func (pm *ProcessManager) HandleTrace(bpfTrace *host.Trace) {
ExecutablePath: bpfTrace.ExecutablePath,
ContainerID: bpfTrace.ContainerID,
Origin: bpfTrace.Origin,
OffTime: bpfTrace.OffTime,
ContextValue: bpfTrace.ContextValue,
EnvVars: bpfTrace.EnvVars,
}

Expand Down
8 changes: 4 additions & 4 deletions reporter/base_reporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,14 @@ func (b *baseReporter) ReportTraceEvent(trace *libpf.Trace, meta *samples.TraceE

if events, exists := (*eventsTree)[samples.ContainerID(containerID)][meta.Origin][key]; exists {
events.Timestamps = append(events.Timestamps, uint64(meta.Timestamp))
events.OffTimes = append(events.OffTimes, meta.OffTime)
events.ContextValues = append(events.ContextValues, meta.ContextValue)
(*eventsTree)[samples.ContainerID(containerID)][meta.Origin][key] = events
return nil
}
(*eventsTree)[samples.ContainerID(containerID)][meta.Origin][key] = &samples.TraceEvents{
Frames: slices.Clone(trace.Frames),
Timestamps: []uint64{uint64(meta.Timestamp)},
OffTimes: []int64{meta.OffTime},
Frames: slices.Clone(trace.Frames),
Timestamps: []uint64{uint64(meta.Timestamp)},
ContextValues: []int64{meta.ContextValue},
EnvVars: meta.EnvVars,
Labels: trace.CustomLabels,
}
Expand Down
2 changes: 1 addition & 1 deletion reporter/internal/pdata/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func (p *Pdata) setProfile(

sample.TimestampsUnixNano().FromRaw(traceInfo.Timestamps)
if origin == support.TraceOriginOffCPU {
sample.Values().Append(traceInfo.OffTimes...)
sample.Values().Append(traceInfo.ContextValues...)
}

locationIndices := make([]int32, 0, len(traceInfo.Frames))
Expand Down
6 changes: 3 additions & 3 deletions reporter/internal/pdata/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,9 +363,9 @@ func TestGenerate_MultipleOriginsAndContainers(t *testing.T) {
},
support.TraceOriginOffCPU: {
traceKey: &samples.TraceEvents{
Frames: frames,
Timestamps: []uint64{3, 4},
OffTimes: []int64{10, 20},
Frames: frames,
Timestamps: []uint64{3, 4},
ContextValues: []int64{10, 20},
},
},
}
Expand Down
10 changes: 5 additions & 5 deletions reporter/samples/samples.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@ type TraceEventMeta struct {
PID, TID libpf.PID
CPU int
Origin libpf.Origin
OffTime int64
ContextValue int64 // Context value: off-cpu time (ns) or custom context ID
EnvVars map[libpf.String]libpf.String
}

// TraceEvents holds known information about a trace.
type TraceEvents struct {
Frames libpf.Frames
Timestamps []uint64 // in nanoseconds
OffTimes []int64 // in nanoseconds
EnvVars map[libpf.String]libpf.String
Frames libpf.Frames
Timestamps []uint64 // in nanoseconds
ContextValues []int64 // in nanoseconds
EnvVars map[libpf.String]libpf.String
Labels map[libpf.String]libpf.String
}

Expand Down
8 changes: 4 additions & 4 deletions support/ebpf/Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
SHELL ?= bash
BPF_CLANG ?= clang-17
BPF_LINK ?= llvm-link-17
STRIP ?= llvm-strip-17
LLC ?= llc-17
BPF_CLANG ?= clang-18
BPF_LINK ?= llvm-link-18
STRIP ?= llvm-strip-18
LLC ?= llc-18
CLANG_FORMAT ?= clang-format-17

# Detect native architecture and translate to GOARCH.
Expand Down
48 changes: 48 additions & 0 deletions support/ebpf/custom_trace.ebpf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include "bpfdefs.h"
#include "tracemgmt.h"
#include "types.h"

// Per-CPU array to pass context_value from external programs to custom__generic.
// External programs should:
// 1. Get a reference to this map (via map reuse or pinning)
// 2. Store context_value: bpf_map_update_elem(&custom_context_map, &key0, &context_value, BPF_ANY)
// 3. Tail call to custom__generic
//
// This map can be reused by loading the same eBPF object or via BPF filesystem pinning.
struct custom_context_map_t {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__type(key, u32);
__type(value, u64);
__uint(max_entries, 1);
} custom_context_map SEC(".maps");

// custom__generic serves as entry point for custom trace profiling with context_value.
// This can be called as a tail call from external eBPF programs.
// Not meant to be attached directly - just loaded for tail calling.
SEC("uprobe/custom__generic")
int custom__generic(struct pt_regs *ctx)
{
u64 pid_tgid = bpf_get_current_pid_tgid();
u32 pid = pid_tgid >> 32;
u32 tid = pid_tgid & 0xFFFFFFFF;

if (pid == 0 || tid == 0) {
return 0;
}

u64 ts = bpf_ktime_get_ns();

// Retrieve context_value from the shared per-CPU map
u32 key0 = 0;
u64 *context_value_ptr = bpf_map_lookup_elem(&custom_context_map, &key0);
u64 context_value = context_value_ptr ? *context_value_ptr : 0;

PerCPURecord *record = get_per_cpu_record();
if (record) {
record->tailCalls += 1;
}

// Collect trace with TRACE_CUSTOM origin
// Pass context_value as the last parameter (similar to off_cpu_time for TRACE_OFF_CPU)
return collect_trace(ctx, TRACE_CUSTOM, pid, tid, ts, context_value);
}
3 changes: 1 addition & 2 deletions support/ebpf/errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,7 @@ typedef enum ErrorCode {
// Native: Unable to read the IRQ stack link
ERR_NATIVE_CHASE_IRQ_STACK_LINK = 4010,

// Native: Unexpectedly encountered a kernel mode pointer while attempting to unwind user-mode
// stack
// Native: Unexpectedly encountered a kernel mode pointer while attempting to unwind user-mode stack
ERR_NATIVE_UNEXPECTED_KERNEL_ADDRESS = 4011,

// Native: Unable to locate the PID page mapping for the current instruction pointer
Expand Down
2 changes: 1 addition & 1 deletion support/ebpf/tracemgmt.h
Original file line number Diff line number Diff line change
Expand Up @@ -738,7 +738,7 @@ static inline EBPF_INLINE int collect_trace(
trace->pid = pid;
trace->tid = tid;
trace->ktime = trace_timestamp;
trace->offtime = off_cpu_time;
trace->context_value = off_cpu_time;
if (bpf_get_current_comm(&(trace->comm), sizeof(trace->comm)) < 0) {
increment_metric(metricID_ErrBPFCurrentComm);
}
Expand Down
Binary file modified support/ebpf/tracer.ebpf.amd64
Binary file not shown.
8 changes: 6 additions & 2 deletions support/ebpf/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ typedef enum TraceOrigin {
TRACE_SAMPLING,
TRACE_OFF_CPU,
TRACE_UPROBE,
TRACE_CUSTOM,
} TraceOrigin;

// MAX_FRAME_UNWINDS defines the maximum number of frames per
Expand Down Expand Up @@ -582,8 +583,11 @@ typedef struct Trace {
// origin indicates the source of the trace.
TraceOrigin origin;

// offtime stores the nanoseconds that the trace was off-cpu for.
u64 offtime;
// context_value stores contextual data for the trace.
// For TRACE_OFF_CPU: nanoseconds the trace was off-cpu.
// For TRACE_CUSTOM: custom context identifier for trace correlation.
// For other origins: typically 0.
u64 context_value;

// The frames of the stack trace.
Frame frames[MAX_FRAME_UNWINDS];
Expand Down
3 changes: 2 additions & 1 deletion support/types.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions support/types_def.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ const (
TraceOriginSampling = C.TRACE_SAMPLING
TraceOriginOffCPU = C.TRACE_OFF_CPU
TraceOriginUProbe = C.TRACE_UPROBE
TraceOriginCustom = C.TRACE_CUSTOM
)

type ApmSpanID C.ApmSpanID
Expand Down
43 changes: 42 additions & 1 deletion tracer/tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,25 @@ func initializeMapsAndPrograms(kmod *kallsyms.Module, cfg *Config) (
}
}

// Load custom trace program using loadProbeUnwinders (same as uprobe__generic)
// This ensures proper map rewrites and program loading for tail call support
customProgs := []progLoaderHelper{
{
name: "custom__generic",
noTailCallTarget: true, // Not added to perf_progs, users manage their own prog_array
enable: true,
},
}
if err = loadProbeUnwinders(coll, ebpfProgs, ebpfMaps["kprobe_progs"], customProgs,
cfg.BPFVerifierLogLevel, ebpfMaps["perf_progs"].FD()); err != nil {
// Don't fail if custom__generic is not present - it's optional
log.Debugf("custom trace program not loaded (optional): %v", err)
} else {
if prog, ok := ebpfProgs["custom__generic"]; ok {
log.Infof("Loaded custom__generic program (FD: %d)", prog.FD())
}
}

if err = removeTemporaryMaps(ebpfMaps); err != nil {
return nil, nil, fmt.Errorf("failed to remove temporary maps: %v", err)
}
Expand Down Expand Up @@ -888,7 +907,7 @@ func (t *Tracer) loadBpfTrace(raw []byte, cpu int) *host.Trace {
PID: pid,
TID: libpf.PID(ptr.Tid),
Origin: libpf.Origin(ptr.Origin),
OffTime: int64(ptr.Offtime),
ContextValue: int64(ptr.Context_value),
KTime: times.KTime(ptr.Ktime),
CPU: cpu,
EnvVars: procMeta.EnvVariables,
Expand All @@ -898,6 +917,7 @@ func (t *Tracer) loadBpfTrace(raw []byte, cpu int) *host.Trace {
case support.TraceOriginSampling:
case support.TraceOriginOffCPU:
case support.TraceOriginUProbe:
case support.TraceOriginCustom:
default:
log.Warnf("Skip handling trace from unexpected %d origin", trace.Origin)
return nil
Expand Down Expand Up @@ -1160,3 +1180,24 @@ func (t *Tracer) AttachUProbes(uprobes []string) error {
func (t *Tracer) HandleTrace(bpfTrace *host.Trace) {
t.processManager.HandleTrace(bpfTrace)
}

// GetCustomTraceProgramFD returns the file descriptor of the custom__generic eBPF program.
// Returns -1 if the program is not loaded.
// Users can use this FD to add the program to their own prog_array maps for tail call support.
func (t *Tracer) GetCustomTraceProgramFD() int {
if prog, ok := t.ebpfProgs["custom__generic"]; ok {
return prog.FD()
}
return -1
}

// GetCustomContextMapFD returns the file descriptor of the custom_context_map.
// Returns -1 if the map is not loaded.
// External programs must store context_value in this map before tail calling to custom__generic.
// Usage: bpf_map_update_elem(map_fd, &key0, &context_value, BPF_ANY) where key0=0.
func (t *Tracer) GetCustomContextMapFD() int {
if m, ok := t.ebpfMaps["custom_context_map"]; ok {
return m.FD()
}
return -1
}