Skip to content

Commit b7b95b7

Browse files
committed
feat: add PID namespace translation support for sidecar deployments
This change introduces the ability for the eBPF profiler to translate host-level PIDs and TGIDs into their corresponding values within a target PID namespace. In sidecar scenarios, the profiler often runs in a different PID namespace than the target application, leading to a mismatch between the PIDs reported in profiles (host view) and the PIDs seen by operators inside the container (typically PID 1). Changes: - Integrated the `bpf_get_ns_current_pid_tgid` BPF helper to resolve namespaced IDs. - Added `EnableNamespacePID` configuration options to control the translation logic. - Implemented automatic PID namespace metadata retrieval in the Go component to feed the BPF RODATA variables. - Added safety checks to fallback to host PIDs if the namespace translation fails (e.g., on older kernels or invalid namespace).
1 parent c8a3299 commit b7b95b7

File tree

7 files changed

+86
-5
lines changed

7 files changed

+86
-5
lines changed

collector/config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ type Config struct {
4040
NoKernelVersionCheck bool `mapstructure:"no_kernel_version_check"`
4141
MaxGRPCRetries uint32 `mapstructure:"max_grpc_retries"`
4242
MaxRPCMsgSize int `mapstructure:"max_rpc_msg_size"`
43+
EnableNamespacePID bool `mapstructure:"enable_namespace_pid"`
4344
}
4445

4546
// Validate validates the config.

internal/controller/controller.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ func (c *Controller) Start(ctx context.Context) error {
9494
ProbeLinks: c.config.ProbeLinks,
9595
LoadProbe: c.config.LoadProbe,
9696
ExecutableReporter: c.config.ExecutableReporter,
97+
EnableNamespacePID: c.config.EnableNamespacePID,
9798
})
9899
if err != nil {
99100
return fmt.Errorf("failed to load eBPF tracer: %w", err)

support/ebpf/bpfdefs.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ static inline u64 bpf_get_current_pid_tgid(void)
5959
return __cgo_ctx->id;
6060
}
6161

62+
static inline u64 bpf_get_ns_current_pid_tgid(u64 dev, u64 ino, void *info, u32 size)
63+
{
64+
return __cgo_ctx->id;
65+
}
66+
6267
static inline void *bpf_map_lookup_elem(void *map, const void *key)
6368
{
6469
void *__bpf_map_lookup_elem(u64, void *, const void *);
@@ -106,7 +111,9 @@ static int (*bpf_probe_read)(void *dst, int size, const void *unsafe_ptr) = (voi
106111
BPF_FUNC_probe_read;
107112
static unsigned long long (*bpf_ktime_get_ns)(void) = (void *)BPF_FUNC_ktime_get_ns;
108113
static unsigned long long (*bpf_get_current_pid_tgid)(void) = (void *)BPF_FUNC_get_current_pid_tgid;
109-
static int (*bpf_get_current_comm)(void *buf, int buf_size) = (void *)BPF_FUNC_get_current_comm;
114+
static unsigned long long (*bpf_get_ns_current_pid_tgid)(u64 dev, u64 ino, void *info, u32 size) =
115+
(void *)BPF_FUNC_get_ns_current_pid_tgid;
116+
static int (*bpf_get_current_comm)(void *buf, int buf_size) = (void *)BPF_FUNC_get_current_comm;
110117
static void (*bpf_tail_call)(void *ctx, void *map, int index) = (void *)BPF_FUNC_tail_call;
111118
static unsigned long long (*bpf_get_current_task)(void) = (void *)BPF_FUNC_get_current_task;
112119
static int (*bpf_perf_event_output)(

support/ebpf/native_stack_trace.ebpf.c

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,32 @@ BPF_RODATA_VAR(u32, task_stack_offset, 0)
2525
// The offset of struct pt_regs within the kernel entry stack.
2626
BPF_RODATA_VAR(u32, stack_ptregs_offset, 0)
2727

28+
// If enabled, the profiler translates host-level PIDs/TGIDs into the
29+
// corresponding IDs within a specific PID namespace. This is essential
30+
// for sidecar deployments to report PIDs consistent with the container's
31+
// internal view (e.g., reporting PID 1 instead of the host PID).
32+
BPF_RODATA_VAR(bool, pid_ns_translation_enabled, false)
33+
34+
// The inode number of the target PID namespace.
35+
// Obtained by calling stat() on /proc/[pid]/ns/pid.
36+
BPF_RODATA_VAR(u64, target_pid_ns_inode, 0)
37+
38+
// The device ID (st_dev) of the target PID namespace inode.
39+
// Required by the bpf_get_ns_current_pid_tgid helper to uniquely
40+
// identify the namespace filesystem (nsfs) instance.
41+
BPF_RODATA_VAR(u64, target_pid_ns_dev, 0)
42+
43+
// Target PID namespace process identifiers.
44+
// pid: The process ID as seen within the target PID namespace.
45+
// tgid: The thread group ID (effectively the process ID in userspace)
46+
// within the target PID namespace.
47+
// This structure must match the kernel's definition for use with the
48+
// bpf_get_ns_current_pid_tgid() helper.
49+
struct bpf_pidns_info {
50+
u32 pid;
51+
u32 tgid;
52+
};
53+
2854
// Macro to create a map named exe_id_to_X_stack_deltas that is a nested maps with a fileID for the
2955
// outer map and an array as inner map that holds up to 2^X stack delta entries for the given
3056
// fileID.
@@ -610,10 +636,24 @@ static EBPF_INLINE int unwind_native(struct pt_regs *ctx)
610636
SEC("perf_event/native_tracer_entry")
611637
int native_tracer_entry(struct bpf_perf_event_data *ctx)
612638
{
613-
// Get the PID and TGID register.
614-
u64 id = bpf_get_current_pid_tgid();
615-
u32 pid = id >> 32;
616-
u32 tid = id & 0xFFFFFFFF;
639+
u32 pid = 0;
640+
u32 tid = 0;
641+
if (pid_ns_translation_enabled) {
642+
struct bpf_pidns_info ns_info = {0};
643+
long ret = bpf_get_ns_current_pid_tgid(
644+
target_pid_ns_dev, target_pid_ns_inode, &ns_info, sizeof(ns_info));
645+
if (ret < 0) {
646+
DEBUG_PRINT(
647+
"failed to get namespace PID/TGID (%llu, %llu)", target_pid_ns_dev, target_pid_ns_inode);
648+
return 0;
649+
}
650+
pid = ns_info.pid;
651+
tid = ns_info.tgid;
652+
} else {
653+
u64 id = bpf_get_current_pid_tgid();
654+
pid = id >> 32;
655+
tid = id & 0xFFFFFFFF;
656+
}
617657

618658
if (pid == 0 && filter_idle_frames) {
619659
return 0;

support/ebpf/tracer.ebpf.amd64

-33.4 KB
Binary file not shown.

tracer/systemconfig.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"os"
1111
"runtime"
1212
"strings"
13+
"syscall"
1314
"unsafe"
1415

1516
"go.opentelemetry.io/ebpf-profiler/kallsyms"
@@ -259,6 +260,19 @@ func prepareAnalysis(orig *cebpf.CollectionSpec) (*cebpf.CollectionSpec, map[str
259260
return new, maps, nil
260261
}
261262

263+
func getCurrentNS(filename string) (uint64, uint64, error) {
264+
info, err := os.Stat(filename)
265+
if err != nil {
266+
return 0, 0, err
267+
}
268+
269+
stat, ok := info.Sys().(*syscall.Stat_t)
270+
if !ok {
271+
return 0, 0, fmt.Errorf("not a syscall.Stat_t")
272+
}
273+
return uint64(stat.Dev), uint64(stat.Ino), nil
274+
}
275+
262276
func determineSysConfig(coll *cebpf.CollectionSpec, maps map[string]*cebpf.Map,
263277
kmod *kallsyms.Module, includeTracers types.IncludedTracers, vars *sysConfigVars,
264278
) error {
@@ -303,6 +317,21 @@ func loadRodataVars(coll *cebpf.CollectionSpec, kmod *kallsyms.Module, cfg *Conf
303317
return fmt.Errorf("failed to set debug output: %v", err)
304318
}
305319
}
320+
if cfg.EnableNamespacePID {
321+
if err := coll.Variables["pid_ns_translation_enabled"].Set(uint8(1)); err != nil {
322+
return fmt.Errorf("failed to set pid_ns_translation_enabled: %v", err)
323+
}
324+
dev, ns, err := getCurrentNS("/proc/self/ns/pid")
325+
if err != nil {
326+
return fmt.Errorf("failed to read my namespace: %v", err)
327+
}
328+
if err := coll.Variables["target_pid_ns_inode"].Set(ns); err != nil {
329+
return fmt.Errorf("failed to set target_pid_ns_inode: %v", err)
330+
}
331+
if err := coll.Variables["target_pid_ns_dev"].Set(dev); err != nil {
332+
return fmt.Errorf("failed to set target_pid_ns_dev: %v", err)
333+
}
334+
}
306335

307336
if err := coll.Variables["off_cpu_threshold"].Set(cfg.OffCPUThreshold); err != nil {
308337
return fmt.Errorf("failed to set off_cpu_threshold: %v", err)

tracer/tracer.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,9 @@ type Config struct {
164164
// LoadProbe indicates whether the generic eBPF program should be loaded
165165
// without being attached to something.
166166
LoadProbe bool
167+
// EnableNamespacePID toggles the translation of host-level PIDs into their
168+
// container-specific equivalents using the PID namespace of the profiler.
169+
EnableNamespacePID bool
167170
}
168171

169172
// hookPoint specifies the group and name of the hooked point in the kernel.

0 commit comments

Comments
 (0)