diff --git a/host/host.go b/host/host.go index 1adc80f07..fe1c4f5b6 100644 --- a/host/host.go +++ b/host/host.go @@ -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 diff --git a/processmanager/manager.go b/processmanager/manager.go index a824cdb1c..ac03ab423 100644 --- a/processmanager/manager.go +++ b/processmanager/manager.go @@ -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, } diff --git a/reporter/base_reporter.go b/reporter/base_reporter.go index 20fed8c79..31dd3b889 100644 --- a/reporter/base_reporter.go +++ b/reporter/base_reporter.go @@ -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, } diff --git a/reporter/internal/pdata/generate.go b/reporter/internal/pdata/generate.go index 97919722b..315b07a6e 100644 --- a/reporter/internal/pdata/generate.go +++ b/reporter/internal/pdata/generate.go @@ -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)) diff --git a/reporter/internal/pdata/generate_test.go b/reporter/internal/pdata/generate_test.go index 448bf430c..0b47faa14 100644 --- a/reporter/internal/pdata/generate_test.go +++ b/reporter/internal/pdata/generate_test.go @@ -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}, }, }, } diff --git a/reporter/samples/samples.go b/reporter/samples/samples.go index 4e1559a87..10638abc1 100644 --- a/reporter/samples/samples.go +++ b/reporter/samples/samples.go @@ -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 } diff --git a/support/ebpf/Makefile b/support/ebpf/Makefile index 4aff96d7b..fdfaf54f1 100644 --- a/support/ebpf/Makefile +++ b/support/ebpf/Makefile @@ -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. diff --git a/support/ebpf/custom_trace.ebpf.c b/support/ebpf/custom_trace.ebpf.c new file mode 100644 index 000000000..efc43bdaa --- /dev/null +++ b/support/ebpf/custom_trace.ebpf.c @@ -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); +} diff --git a/support/ebpf/errors.h b/support/ebpf/errors.h index 16837d2fd..4dd60ba8e 100644 --- a/support/ebpf/errors.h +++ b/support/ebpf/errors.h @@ -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 diff --git a/support/ebpf/tracemgmt.h b/support/ebpf/tracemgmt.h index ce212de7e..7a3012225 100644 --- a/support/ebpf/tracemgmt.h +++ b/support/ebpf/tracemgmt.h @@ -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); } diff --git a/support/ebpf/tracer.ebpf.amd64 b/support/ebpf/tracer.ebpf.amd64 index 904f01500..e0b89a36c 100644 Binary files a/support/ebpf/tracer.ebpf.amd64 and b/support/ebpf/tracer.ebpf.amd64 differ diff --git a/support/ebpf/types.h b/support/ebpf/types.h index 3317ff17d..7a17a7c87 100644 --- a/support/ebpf/types.h +++ b/support/ebpf/types.h @@ -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 @@ -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]; diff --git a/support/types.go b/support/types.go index 7541b5b89..ed3510954 100644 --- a/support/types.go +++ b/support/types.go @@ -90,6 +90,7 @@ const ( TraceOriginSampling = 0x1 TraceOriginOffCPU = 0x2 TraceOriginUProbe = 0x3 + TraceOriginCustom = 0x4 ) type ApmSpanID [8]byte @@ -164,7 +165,7 @@ type Trace struct { Kernel_stack_id int32 Stack_len uint32 Origin uint32 - Offtime uint64 + Context_value uint64 Frames [128]Frame } type UnwindInfo struct { diff --git a/support/types_def.go b/support/types_def.go index 70d16eaa8..3fdca6f65 100644 --- a/support/types_def.go +++ b/support/types_def.go @@ -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 diff --git a/tracer/tracer.go b/tracer/tracer.go index c1eb5b884..9af0c750c 100644 --- a/tracer/tracer.go +++ b/tracer/tracer.go @@ -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) } @@ -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, @@ -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 @@ -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 +}