An eBPF-based profiler for process-lifecycle events.
zig ebpf profilers
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: record when observing a clone

We need to know when we've seen a process fork so that we know which
clone_exit tracepoint triggers we can ignore.

I've also noticed that the exec args are broken again.

+20 -29
+1 -13
build.zig
··· 239 239 return run_btf_sanitizer.addPrefixedOutputFileArg("-o", b.fmt("{s}_sanitized.o", .{name})); 240 240 } 241 241 242 - fn create_test_step(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, config: *std.Build.Step.Options, test_filter: ?[]const u8) !*std.Build.Step { 242 + fn create_test_step(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, _: *std.Build.Step.Options, test_filter: ?[]const u8) !*std.Build.Step { 243 243 const test_step = b.step("test", "Build and run all unit tests"); 244 244 245 245 if (target.result.os.tag != .linux) { ··· 263 263 // Create bpf programs for test 264 264 const build_options = b.addOptions(); 265 265 build_options.addOption(bool, "debug", debugging); 266 - var sample_dir = try fs.cwd().openDir("samples", .{ .iterate = true }); 267 - defer sample_dir.close(); 268 - var it = sample_dir.iterate(); 269 - while (try it.next()) |entry| { 270 - if (test_filter) |f| { 271 - if (!std.mem.containsAtLeast(u8, entry.name, 1, f)) { 272 - continue; 273 - } 274 - } 275 - const prog = create_bpf_prog(b, optimize, target.result.cpu.arch.endian(), try fs.path.join(b.allocator, &.{ "samples", entry.name }), null, config); 276 - build_options.addOptionPath(b.fmt("prog_{s}_path", .{fs.path.stem(entry.name)}), prog); 277 - } 278 266 bpf_exe_tests.root_module.addOptions("@build_options", build_options); 279 267 280 268 const run_unit_test = b.addRunArtifact(bpf_exe_tests);
+19 -16
src/bpf_collector.zig
··· 12 12 var events_ringbuf = bpf.Map.RingBuffer("events", 10, 0).init(); 13 13 var diagnostics_ringbuf = bpf.Map.RingBuffer("diagnostics", 10, 0).init(); 14 14 15 + // This one isn't needed by userspace, we use it to keep track of which 16 + // processes we've seen a `clone_enter`/`clone3_enter` from so that we can know 17 + // which `clone_exit`/`clone3_exit` calls to record. 18 + // 19 + // Remember that you can see a `clone`/`clone3` when a *thread* is created, 20 + // and you can only tell apart a thread from a process when you have access 21 + // to the clone flags that are passed in as an argument. Once you're on the 22 + // exit side of the tracepoint you no longer have access to those flags, so 23 + // you need some other way to tell whether or not to record the event. 24 + var clone_seen_map = bpf.Map.HashMap("seen", bridge.Pid, bool, 10, 0).init(); 25 + 15 26 const raw_tp_enter = bpf.RawTracepoint{ .side = .enter }; 16 27 const raw_tp_exit = bpf.RawTracepoint{ .side = .exit }; 17 28 ··· 146 157 // unsigned long, tls) 147 158 148 159 const clone_flags = args[0]; 149 - if (is_new_process(clone_flags)) { 150 - const meta = new_event_meta(); 151 - if (events_ringbuf.reserve(bridge.Event)) |reserved| { 152 - reserved.ptr.* = .{ .fork_start = .{ .meta = meta } }; 153 - reserved.submit(); 154 - } else { 155 - if (diagnostics_ringbuf.reserve(bridge.Diagnostic)) |r| { 156 - var buf: [64]u8 = [_]u8{0} ** 64; 157 - const str = "failed to reserve space in ringbuf"; 158 - @memcpy(buf[0..str.len], str); 159 - r.ptr.* = .{ .meta = meta, .tp = .clone_enter, .err = bridge.CollectorError.RingbufReserve, .msg = buf }; 160 - r.submit(); 161 - } 162 - } 163 - } 160 + clone_enter_common(clone_flags, .clone_enter); 164 161 } 165 162 166 163 fn handle_clone3_enter(args: *const [6]u64) void { ··· 182 179 } 183 180 return; 184 181 } 182 + clone_enter_common(flags, .clone3_enter); 183 + } 184 + 185 + fn clone_enter_common(flags: u64, tp_kind: bridge.TracepointKind) void { 185 186 if (is_new_process(flags)) { 186 187 const meta = new_event_meta(); 187 188 if (events_ringbuf.reserve(bridge.Event)) |reserved| { ··· 192 193 var buf: [64]u8 = [_]u8{0} ** 64; 193 194 const str = "failed to reserve space in ringbuf"; 194 195 @memcpy(buf[0..str.len], str); 195 - r.ptr.* = .{ .meta = meta, .tp = .clone3_enter, .err = bridge.CollectorError.RingbufReserve, .msg = buf }; 196 + r.ptr.* = .{ .meta = meta, .tp = tp_kind, .err = bridge.CollectorError.RingbufReserve, .msg = buf }; 196 197 r.submit(); 198 + return; 197 199 } 198 200 } 201 + clone_seen_map.update(.any, meta.pid, true); 199 202 } 200 203 } 201 204