lib/gpalloc/src/profiling/external/runner.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const bench = @import("bench");
3 const coz = bench.coz;
4 const sys = @import("sys");
5
6 const catalog = @import("catalog.zig");
7 const options = @import("options.zig");
8 const output = @import("output.zig");
9
10 const wait_poll_ms: u64 = 2;
11 const max_stdout_bytes: usize = 1024 * 1024;
12
13 pub const Execution = struct {
14 wall_ns: u64,
15 max_rss_kib: u64,
16 };
17
18 pub fn writeBenchmarks(writer: *std.Io.Writer) !void {
19 var line_buffer: [256]u8 = undefined;
20 for (catalog.all) |benchmark| {
21 try bench.pretty.write(
22 writer,
23 .{ .text = try std.fmt.bufPrint(&line_buffer, "{s}\t{s}\n", .{
24 benchmark.id,
25 benchmark.name,
26 }) },
27 .{},
28 );
29 }
30 }
31
32 pub fn runSelectedBenchmarks(
33 arena: std.mem.Allocator,
34 process_io: std.Io,
35 environ_map: *const sys.process.Environ.Map,
36 writer: *std.Io.Writer,
37 parsed: options.Options,
38 ) !struct { benches: u32, errors: u32 } {
39 var benches: u32 = 0;
40 var errors: u32 = 0;
41 if (parsed.benchmark_id) |id| {
42 const benchmark = catalog.byId(id) orelse return error.UnknownBenchmark;
43 runOneBenchmark(arena, process_io, environ_map, writer, parsed, benchmark) catch |err| {
44 try output.writeBenchmarkError(writer, parsed, benchmark, @errorName(err));
45 errors += 1;
46 };
47 benches += 1;
48 return .{ .benches = benches, .errors = errors };
49 }
50 for (catalog.all) |benchmark| {
51 runOneBenchmark(arena, process_io, environ_map, writer, parsed, benchmark) catch |err| {
52 try output.writeBenchmarkError(writer, parsed, benchmark, @errorName(err));
53 errors += 1;
54 };
55 benches += 1;
56 }
57 return .{ .benches = benches, .errors = errors };
58 }
59
60 fn runOneBenchmark(
61 arena: std.mem.Allocator,
62 process_io: std.Io,
63 environ_map: *const sys.process.Environ.Map,
64 writer: *std.Io.Writer,
65 parsed: options.Options,
66 benchmark: catalog.Benchmark,
67 ) !void {
68 const benchmark_latency = bench.phaseAt("gpalloc.external.benchmark", @src());
69 benchmark_latency.setName(benchmark.id);
70 defer benchmark_latency.end();
71
72 const procs = if (parsed.procs != 0) parsed.procs else sys.thread.cpuCount();
73 const binary = try catalog.binaryPath(arena, parsed.suite_dir, benchmark);
74 const args = try catalog.arguments(arena, parsed.suite_dir, benchmark, parsed.profile, procs);
75 const stdin_path = try catalog.stdinPath(arena, parsed.suite_dir, benchmark);
76 const stdout_path: ?[]const u8 = if (benchmark.metric.parsesStdout())
77 try std.fs.path.join(arena, &.{ parsed.suite_dir, "out", "gpalloc-external-stdout.log" })
78 else
79 null;
80 const child_cwd = try std.fs.path.join(arena, &.{ parsed.suite_dir, "out", "bench" });
81
82 var argv: std.ArrayListUnmanaged([]const u8) = .empty;
83 try argv.append(arena, binary);
84 try argv.appendSlice(arena, args);
85
86 var env = try environ_map.clone(arena);
87 if (try options.preloadPath(arena, parsed)) |preload| {
88 try env.put("LD_PRELOAD", try sys.fs.realPathAlloc(arena, preload));
89 } else if (env.get("LD_PRELOAD") != null) {
90 try env.put("LD_PRELOAD", "");
91 }
92
93 var warmup_index: u32 = 0;
94 while (warmup_index < parsed.warmup) : (warmup_index += 1) {
95 _ = try runExecution(process_io, argv.items, &env, stdin_path, stdout_path, child_cwd);
96 coz.progressNamed("gpalloc.external.warmup");
97 }
98
99 const metric_samples = try arena.alloc(u64, parsed.samples);
100 const rss_samples = try arena.alloc(u64, parsed.samples);
101 var sample_index: u32 = 0;
102 while (sample_index < parsed.samples) : (sample_index += 1) {
103 const sample_latency = bench.phaseAt("gpalloc.external.sample", @src());
104 sample_latency.setName(benchmark.id);
105 defer sample_latency.end();
106
107 const execution = try runExecution(process_io, argv.items, &env, stdin_path, stdout_path, child_cwd);
108 const stdout_text: []const u8 = if (stdout_path) |path|
109 try sys.fs.readFileAlloc(arena, path, max_stdout_bytes)
110 else
111 "";
112 const metric_ns = try catalog.deriveNs(benchmark.metric, stdout_text, execution.wall_ns);
113 metric_samples[sample_index] = metric_ns;
114 rss_samples[sample_index] = execution.max_rss_kib;
115 try output.writeSample(
116 writer,
117 parsed,
118 benchmark,
119 sample_index,
120 metric_ns,
121 execution.wall_ns,
122 execution.max_rss_kib,
123 );
124 coz.progressNamed("gpalloc.external.sample");
125 }
126
127 var statistics = try bench.StatisticsStorage.init(arena, .{ .samples = metric_samples.len });
128 defer statistics.deinit(arena);
129 statistics.activate();
130 const metric_stats = try bench.computeSampleStats(&statistics, metric_samples);
131 try output.writeSummary(writer, parsed, benchmark, metric_stats, medianInPlace(rss_samples));
132 coz.progressNamed("gpalloc.external.benchmark.complete");
133 }
134
135 fn runExecution(
136 process_io: std.Io,
137 argv: []const []const u8,
138 env: *sys.process.Environ.Map,
139 stdin_path: ?[]const u8,
140 stdout_path: ?[]const u8,
141 child_cwd: []const u8,
142 ) !Execution {
143 const fs_io = sys.fs.debugIo();
144
145 const stdin_file: ?std.Io.File = if (stdin_path) |path|
146 if (std.mem.startsWith(u8, path, "/"))
147 try sys.fs.openAbsoluteFile(path, .{})
148 else
149 try sys.fs.cwd().openFile(fs_io, path, .{})
150 else
151 null;
152 defer if (stdin_file) |file| file.close(fs_io);
153
154 const stdout_file: ?std.Io.File = if (stdout_path) |path|
155 try sys.fs.createFile(path, .{})
156 else
157 null;
158 defer if (stdout_file) |file| file.close(fs_io);
159
160 const start = bench.nowNs();
161 var child = try sys.process.spawn(process_io, .{
162 .argv = argv,
163 .cwd = .{ .path = child_cwd },
164 .environ_map = env,
165 .stdin = if (stdin_file) |file| .{ .file = file } else .ignore,
166 .stdout = if (stdout_file) |file| .{ .file = file } else .ignore,
167 .stderr = .ignore,
168 .request_resource_usage_statistics = false,
169 });
170 errdefer child.kill(process_io);
171
172 const pid = child.id.?;
173 while (true) {
174 if (try sys.process.waitNoHang(pid)) |outcome| {
175 child.id = null;
176 const wall_ns = bench.elapsedNs(start);
177 if (sys.process.exitCode(outcome.term) != 0) return error.BenchmarkFailed;
178 const max_rss_kib: u64 = if (outcome.usage.maxrss_kib) |kib|
179 @intCast(@max(kib, 0))
180 else
181 0;
182 return .{ .wall_ns = wall_ns, .max_rss_kib = max_rss_kib };
183 }
184 sys.time.sleepMilliseconds(wait_poll_ms);
185 }
186 }
187
188 fn medianInPlace(values: []u64) u64 {
189 std.mem.sort(u64, values, {}, std.sort.asc(u64));
190 return values[values.len / 2];
191 }
192
193 test "median in place selects the middle observation" {
194 var values = [_]u64{ 5, 1, 9, 3, 7 };
195 try std.testing.expectEqual(@as(u64, 5), medianInPlace(&values));
196 var pair = [_]u64{ 4, 2 };
197 try std.testing.expectEqual(@as(u64, 4), medianInPlace(&pair));
198 }