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 }