lib/sys/src/profiling/font.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const bench = @import("bench");
3 const sys = @import("sys");
4
5 const font = sys.font;
6
7 const debug_io = std.Options.debug_io;
8 const corpus_directories: u16 = 64;
9 const corpus_files: u16 = 16;
10
11 const limits: font.Catalog.Limits = .{
12 .entries = 2048,
13 .directories = 256,
14 .path_bytes = 512 * 1024,
15 };
16
17 const Fixture = struct {
18 tmp: std.testing.TmpDir,
19 set: font.RootSet,
20 storage: []align(font.Catalog.storage_alignment) u8,
21 catalog: font.Catalog,
22 allocator: std.mem.Allocator,
23 expected: u32,
24
25 fn init(self: *Fixture, backing_allocator: std.mem.Allocator) !void {
26 self.allocator = backing_allocator;
27 self.tmp = std.testing.tmpDir(.{});
28 errdefer self.tmp.cleanup();
29 self.expected = try font.fixture.createCorpus(
30 debug_io,
31 self.tmp.dir,
32 corpus_directories,
33 corpus_files,
34 );
35 var path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
36 const used = try self.tmp.dir.realPath(debug_io, &path_buffer);
37 self.set = .{};
38 if (!self.set.append(path_buffer[0..used])) return error.RootRejected;
39 const capacity = try font.Catalog.Capacity.derive(limits);
40 self.storage = try backing_allocator.alignedAlloc(
41 u8,
42 .fromByteUnits(font.Catalog.storage_alignment),
43 capacity.storage_bytes,
44 );
45 errdefer backing_allocator.free(self.storage);
46 std.debug.assert(self.expected == @as(u32, corpus_directories) * corpus_files);
47 std.debug.assert(self.storage.len == capacity.storage_bytes);
48 self.catalog = try font.Catalog.init(self.storage, limits);
49 self.catalog.activate();
50 }
51
52 fn deinit(self: *Fixture) void {
53 _ = self.catalog.deinit();
54 self.allocator.free(self.storage);
55 self.tmp.cleanup();
56 self.* = undefined;
57 }
58 };
59
60 var active_fixture: ?*Fixture = null;
61
62 fn enumerateWorkload(_: std.mem.Allocator) void {
63 const fixture = active_fixture orelse unreachable;
64 std.debug.assert(fixture.set.count == 1);
65 fixture.catalog.reset();
66 std.debug.assert(fixture.catalog.entryCount() == 0);
67 var stats: font.Stats = .{};
68 const phase = bench.phaseAt("sys.font.enumerate_1024", @src());
69 defer phase.end();
70 font.enumerate(
71 debug_io,
72 &fixture.catalog,
73 &fixture.set,
74 .{ .depth_max = 2 },
75 &stats,
76 ) catch @panic("sys font enumeration benchmark rejected an admission");
77 std.debug.assert(stats.directories_visited == corpus_directories + 1);
78 std.mem.doNotOptimizeAway(stats.directories_visited);
79 if (fixture.catalog.entryCount() != fixture.expected) {
80 @panic("sys font enumeration benchmark count failed");
81 }
82 bench.coz.progressNamed("sys.font.enumerate.complete");
83 }
84
85 fn minTimeNs(allocator: std.mem.Allocator) !u64 {
86 const value = try sys.env.getOwned(allocator, "BENCH_MIN_TIME_NS");
87 defer if (value) |text| allocator.free(text);
88 return if (value) |text| try std.fmt.parseUnsigned(u64, text, 10) else 1_000_000;
89 }
90
91 test "bench: sys font enumeration over a fixture corpus" {
92 const allocator = sys.allocator.benchmarkAllocator();
93 var fixture: Fixture = undefined;
94 try fixture.init(allocator);
95 defer fixture.deinit();
96 active_fixture = &fixture;
97 defer active_fixture = null;
98
99 const filter = try sys.env.getOwned(allocator, "BENCH_FILTER");
100 defer if (filter) |value| allocator.free(value);
101 var suite = bench.Suite.init(allocator, .{
102 .min_time_ns = try minTimeNs(allocator),
103 .min_iterations = 5,
104 .max_iterations = 20,
105 .warmup_iterations = 1,
106 .allocation_attribution = .sample_call,
107 });
108 defer suite.deinit();
109 suite.setFilter(filter);
110 try suite.add("sys font enumerate", enumerateWorkload, .{});
111 try suite.run();
112 }