authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-09-11 19:53:14-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-09-11 19:53:29-07:00
loge3f58bd5515ffd0039c7f5afde8b9d74dc5a24b5
tree5dd003acf4f9d1f181a0cbb9d5ddc4f345f32a2f
parent9dc75f03e26146cb81fc992baf172202fcd19b17

add runs per second to fuzzing ui

closes #21025

6 files changed, 70 insertions(+), 2 deletions(-)

lib/fuzzer/web/index.html+1
...@@ -146,6 +146,7 @@...@@ -146,6 +146,7 @@
146 <ul>146 <ul>
147 <li>Total Runs: <span id="statTotalRuns"></span></li>147 <li>Total Runs: <span id="statTotalRuns"></span></li>
148 <li>Unique Runs: <span id="statUniqueRuns"></span></li>148 <li>Unique Runs: <span id="statUniqueRuns"></span></li>
149 <li>Speed (Runs/Second): <span id="statSpeed"></span></li>
149 <li>Coverage: <span id="statCoverage"></span></li>150 <li>Coverage: <span id="statCoverage"></span></li>
150 <li>Entry Points: <ul id="entryPointsList"></ul></li>151 <li>Entry Points: <ul id="entryPointsList"></ul></li>
151 </ul>152 </ul>
lib/fuzzer/web/main.js+5
...@@ -5,6 +5,7 @@...@@ -5,6 +5,7 @@
5 const domSourceText = document.getElementById("sourceText");5 const domSourceText = document.getElementById("sourceText");
6 const domStatTotalRuns = document.getElementById("statTotalRuns");6 const domStatTotalRuns = document.getElementById("statTotalRuns");
7 const domStatUniqueRuns = document.getElementById("statUniqueRuns");7 const domStatUniqueRuns = document.getElementById("statUniqueRuns");
8 const domStatSpeed = document.getElementById("statSpeed");
8 const domStatCoverage = document.getElementById("statCoverage");9 const domStatCoverage = document.getElementById("statCoverage");
9 const domEntryPointsList = document.getElementById("entryPointsList");10 const domEntryPointsList = document.getElementById("entryPointsList");
1011
...@@ -31,6 +32,9 @@...@@ -31,6 +32,9 @@
31 const msg = decodeString(ptr, len);32 const msg = decodeString(ptr, len);
32 throw new Error("panic: " + msg);33 throw new Error("panic: " + msg);
33 },34 },
35 timestamp: function () {
36 return BigInt(new Date());
37 },
34 emitSourceIndexChange: onSourceIndexChange,38 emitSourceIndexChange: onSourceIndexChange,
35 emitCoverageUpdate: onCoverageUpdate,39 emitCoverageUpdate: onCoverageUpdate,
36 emitEntryPointsUpdate: renderStats,40 emitEntryPointsUpdate: renderStats,
...@@ -157,6 +161,7 @@...@@ -157,6 +161,7 @@
157 domStatTotalRuns.innerText = totalRuns;161 domStatTotalRuns.innerText = totalRuns;
158 domStatUniqueRuns.innerText = uniqueRuns + " (" + percent(uniqueRuns, totalRuns) + "%)";162 domStatUniqueRuns.innerText = uniqueRuns + " (" + percent(uniqueRuns, totalRuns) + "%)";
159 domStatCoverage.innerText = coveredSourceLocations + " / " + totalSourceLocations + " (" + percent(coveredSourceLocations, totalSourceLocations) + "%)";163 domStatCoverage.innerText = coveredSourceLocations + " / " + totalSourceLocations + " (" + percent(coveredSourceLocations, totalSourceLocations) + "%)";
164 domStatSpeed.innerText = wasm_exports.totalRunsPerSecond().toFixed(0);
160165
161 const entryPoints = unwrapInt32Array(wasm_exports.entryPoints());166 const entryPoints = unwrapInt32Array(wasm_exports.entryPoints());
162 resizeDomList(domEntryPointsList, entryPoints.length, "<li></li>");167 resizeDomList(domEntryPointsList, entryPoints.length, "<li></li>");
lib/fuzzer/web/main.zig+36-2
...@@ -10,9 +10,17 @@ const Walk = @import("Walk");...@@ -10,9 +10,17 @@ const Walk = @import("Walk");
10const Decl = Walk.Decl;10const Decl = Walk.Decl;
11const html_render = @import("html_render");11const html_render = @import("html_render");
1212
13/// Nanoseconds.
14var server_base_timestamp: i64 = 0;
15/// Milliseconds.
16var client_base_timestamp: i64 = 0;
17/// Relative to `server_base_timestamp`.
18var start_fuzzing_timestamp: i64 = undefined;
19
13const js = struct {20const js = struct {
14 extern "js" fn log(ptr: [*]const u8, len: usize) void;21 extern "js" fn log(ptr: [*]const u8, len: usize) void;
15 extern "js" fn panic(ptr: [*]const u8, len: usize) noreturn;22 extern "js" fn panic(ptr: [*]const u8, len: usize) noreturn;
23 extern "js" fn timestamp() i64;
16 extern "js" fn emitSourceIndexChange() void;24 extern "js" fn emitSourceIndexChange() void;
17 extern "js" fn emitCoverageUpdate() void;25 extern "js" fn emitCoverageUpdate() void;
18 extern "js" fn emitEntryPointsUpdate() void;26 extern "js" fn emitEntryPointsUpdate() void;
...@@ -64,6 +72,7 @@ export fn message_end() void {...@@ -64,6 +72,7 @@ export fn message_end() void {
6472
65 const tag: abi.ToClientTag = @enumFromInt(msg_bytes[0]);73 const tag: abi.ToClientTag = @enumFromInt(msg_bytes[0]);
66 switch (tag) {74 switch (tag) {
75 .current_time => return currentTimeMessage(msg_bytes),
67 .source_index => return sourceIndexMessage(msg_bytes) catch @panic("OOM"),76 .source_index => return sourceIndexMessage(msg_bytes) catch @panic("OOM"),
68 .coverage_update => return coverageUpdateMessage(msg_bytes) catch @panic("OOM"),77 .coverage_update => return coverageUpdateMessage(msg_bytes) catch @panic("OOM"),
69 .entry_points => return entryPointsMessage(msg_bytes) catch @panic("OOM"),78 .entry_points => return entryPointsMessage(msg_bytes) catch @panic("OOM"),
...@@ -117,16 +126,28 @@ export fn coveredSourceLocations() usize {...@@ -117,16 +126,28 @@ export fn coveredSourceLocations() usize {
117 return count;126 return count;
118}127}
119128
129fn getCoverageUpdateHeader() *abi.CoverageUpdateHeader {
130 return @alignCast(@ptrCast(recent_coverage_update.items[0..@sizeOf(abi.CoverageUpdateHeader)]));
131}
132
120export fn totalRuns() u64 {133export fn totalRuns() u64 {
121 const header: *abi.CoverageUpdateHeader = @alignCast(@ptrCast(recent_coverage_update.items[0..@sizeOf(abi.CoverageUpdateHeader)]));134 const header = getCoverageUpdateHeader();
122 return header.n_runs;135 return header.n_runs;
123}136}
124137
125export fn uniqueRuns() u64 {138export fn uniqueRuns() u64 {
126 const header: *abi.CoverageUpdateHeader = @alignCast(@ptrCast(recent_coverage_update.items[0..@sizeOf(abi.CoverageUpdateHeader)]));139 const header = getCoverageUpdateHeader();
127 return header.unique_runs;140 return header.unique_runs;
128}141}
129142
143export fn totalRunsPerSecond() f64 {
144 @setFloatMode(.optimized);
145 const header = getCoverageUpdateHeader();
146 const ns_elapsed: f64 = @floatFromInt(nsSince(start_fuzzing_timestamp));
147 const n_runs: f64 = @floatFromInt(header.n_runs);
148 return n_runs / (ns_elapsed / std.time.ns_per_s);
149}
150
130const String = Slice(u8);151const String = Slice(u8);
131152
132fn Slice(T: type) type {153fn Slice(T: type) type {
...@@ -189,6 +210,18 @@ fn fatal(comptime format: []const u8, args: anytype) noreturn {...@@ -189,6 +210,18 @@ fn fatal(comptime format: []const u8, args: anytype) noreturn {
189 js.panic(line.ptr, line.len);210 js.panic(line.ptr, line.len);
190}211}
191212
213fn currentTimeMessage(msg_bytes: []u8) void {
214 client_base_timestamp = js.timestamp();
215 server_base_timestamp = @bitCast(msg_bytes[1..][0..8].*);
216}
217
218/// Nanoseconds passed since a server timestamp.
219fn nsSince(server_timestamp: i64) i64 {
220 const ms_passed = js.timestamp() - client_base_timestamp;
221 const ns_passed = server_base_timestamp - server_timestamp;
222 return ns_passed + ms_passed * std.time.ns_per_ms;
223}
224
192fn sourceIndexMessage(msg_bytes: []u8) error{OutOfMemory}!void {225fn sourceIndexMessage(msg_bytes: []u8) error{OutOfMemory}!void {
193 const Header = abi.SourceIndexHeader;226 const Header = abi.SourceIndexHeader;
194 const header: Header = @bitCast(msg_bytes[0..@sizeOf(Header)].*);227 const header: Header = @bitCast(msg_bytes[0..@sizeOf(Header)].*);
...@@ -205,6 +238,7 @@ fn sourceIndexMessage(msg_bytes: []u8) error{OutOfMemory}!void {...@@ -205,6 +238,7 @@ fn sourceIndexMessage(msg_bytes: []u8) error{OutOfMemory}!void {
205 const files: []const Coverage.File = @alignCast(std.mem.bytesAsSlice(Coverage.File, msg_bytes[files_start..files_end]));238 const files: []const Coverage.File = @alignCast(std.mem.bytesAsSlice(Coverage.File, msg_bytes[files_start..files_end]));
206 const source_locations: []const Coverage.SourceLocation = @alignCast(std.mem.bytesAsSlice(Coverage.SourceLocation, msg_bytes[source_locations_start..source_locations_end]));239 const source_locations: []const Coverage.SourceLocation = @alignCast(std.mem.bytesAsSlice(Coverage.SourceLocation, msg_bytes[source_locations_start..source_locations_end]));
207240
241 start_fuzzing_timestamp = header.start_timestamp;
208 try updateCoverage(directories, files, source_locations, string_bytes);242 try updateCoverage(directories, files, source_locations, string_bytes);
209 js.emitSourceIndexChange();243 js.emitSourceIndexChange();
210}244}
lib/std/Build/Fuzz.zig+2
...@@ -66,6 +66,8 @@ pub fn start(...@@ -66,6 +66,8 @@ pub fn start(
66 .coverage_files = .{},66 .coverage_files = .{},
67 .coverage_mutex = .{},67 .coverage_mutex = .{},
68 .coverage_condition = .{},68 .coverage_condition = .{},
69
70 .base_timestamp = std.time.nanoTimestamp(),
69 };71 };
7072
71 // For accepting HTTP connections.73 // For accepting HTTP connections.
lib/std/Build/Fuzz/WebServer.zig+17
...@@ -33,6 +33,9 @@ coverage_mutex: std.Thread.Mutex,...@@ -33,6 +33,9 @@ coverage_mutex: std.Thread.Mutex,
33/// Signaled when `coverage_files` changes.33/// Signaled when `coverage_files` changes.
34coverage_condition: std.Thread.Condition,34coverage_condition: std.Thread.Condition,
3535
36/// Time at initialization of WebServer.
37base_timestamp: i128,
38
36const fuzzer_bin_name = "fuzzer";39const fuzzer_bin_name = "fuzzer";
37const fuzzer_arch_os_abi = "wasm32-freestanding";40const fuzzer_arch_os_abi = "wasm32-freestanding";
38const fuzzer_cpu_features = "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext";41const fuzzer_cpu_features = "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext";
...@@ -43,6 +46,7 @@ const CoverageMap = struct {...@@ -43,6 +46,7 @@ const CoverageMap = struct {
43 source_locations: []Coverage.SourceLocation,46 source_locations: []Coverage.SourceLocation,
44 /// Elements are indexes into `source_locations` pointing to the unit tests that are being fuzz tested.47 /// Elements are indexes into `source_locations` pointing to the unit tests that are being fuzz tested.
45 entry_points: std.ArrayListUnmanaged(u32),48 entry_points: std.ArrayListUnmanaged(u32),
49 start_timestamp: i64,
4650
47 fn deinit(cm: *CoverageMap, gpa: Allocator) void {51 fn deinit(cm: *CoverageMap, gpa: Allocator) void {
48 std.posix.munmap(cm.mapped_memory);52 std.posix.munmap(cm.mapped_memory);
...@@ -87,6 +91,10 @@ pub fn run(ws: *WebServer) void {...@@ -87,6 +91,10 @@ pub fn run(ws: *WebServer) void {
87 }91 }
88}92}
8993
94fn now(s: *const WebServer) i64 {
95 return @intCast(std.time.nanoTimestamp() - s.base_timestamp);
96}
97
90fn accept(ws: *WebServer, connection: std.net.Server.Connection) void {98fn accept(ws: *WebServer, connection: std.net.Server.Connection) void {
91 defer connection.stream.close();99 defer connection.stream.close();
92100
...@@ -381,6 +389,13 @@ fn serveWebSocket(ws: *WebServer, web_socket: *std.http.WebSocket) !void {...@@ -381,6 +389,13 @@ fn serveWebSocket(ws: *WebServer, web_socket: *std.http.WebSocket) !void {
381 ws.coverage_mutex.lock();389 ws.coverage_mutex.lock();
382 defer ws.coverage_mutex.unlock();390 defer ws.coverage_mutex.unlock();
383391
392 // On first connection, the client needs to know what time the server
393 // thinks it is to rebase timestamps.
394 {
395 const timestamp_message: abi.CurrentTime = .{ .base = ws.now() };
396 try web_socket.writeMessage(std.mem.asBytes(&timestamp_message), .binary);
397 }
398
384 // On first connection, the client needs all the coverage information399 // On first connection, the client needs all the coverage information
385 // so that subsequent updates can contain only the updated bits.400 // so that subsequent updates can contain only the updated bits.
386 var prev_unique_runs: usize = 0;401 var prev_unique_runs: usize = 0;
...@@ -416,6 +431,7 @@ fn sendCoverageContext(...@@ -416,6 +431,7 @@ fn sendCoverageContext(
416 .files_len = @intCast(coverage_map.coverage.files.entries.len),431 .files_len = @intCast(coverage_map.coverage.files.entries.len),
417 .source_locations_len = @intCast(coverage_map.source_locations.len),432 .source_locations_len = @intCast(coverage_map.source_locations.len),
418 .string_bytes_len = @intCast(coverage_map.coverage.string_bytes.items.len),433 .string_bytes_len = @intCast(coverage_map.coverage.string_bytes.items.len),
434 .start_timestamp = coverage_map.start_timestamp,
419 };435 };
420 const iovecs: [5]std.posix.iovec_const = .{436 const iovecs: [5]std.posix.iovec_const = .{
421 makeIov(std.mem.asBytes(&header)),437 makeIov(std.mem.asBytes(&header)),
...@@ -582,6 +598,7 @@ fn prepareTables(...@@ -582,6 +598,7 @@ fn prepareTables(
582 .mapped_memory = undefined, // populated below598 .mapped_memory = undefined, // populated below
583 .source_locations = undefined, // populated below599 .source_locations = undefined, // populated below
584 .entry_points = .{},600 .entry_points = .{},
601 .start_timestamp = ws.now(),
585 };602 };
586 errdefer gop.value_ptr.coverage.deinit(gpa);603 errdefer gop.value_ptr.coverage.deinit(gpa);
587604
lib/std/Build/Fuzz/abi.zig+9
...@@ -43,12 +43,19 @@ pub const SeenPcsHeader = extern struct {...@@ -43,12 +43,19 @@ pub const SeenPcsHeader = extern struct {
43};43};
4444
45pub const ToClientTag = enum(u8) {45pub const ToClientTag = enum(u8) {
46 current_time,
46 source_index,47 source_index,
47 coverage_update,48 coverage_update,
48 entry_points,49 entry_points,
49 _,50 _,
50};51};
5152
53pub const CurrentTime = extern struct {
54 tag: ToClientTag = .current_time,
55 /// Number of nanoseconds that all other timestamps are in reference to.
56 base: i64 align(1),
57};
58
52/// Sent to the fuzzer web client on first connection to the websocket URL.59/// Sent to the fuzzer web client on first connection to the websocket URL.
53///60///
54/// Trailing:61/// Trailing:
...@@ -62,6 +69,8 @@ pub const SourceIndexHeader = extern struct {...@@ -62,6 +69,8 @@ pub const SourceIndexHeader = extern struct {
62 files_len: u32,69 files_len: u32,
63 source_locations_len: u32,70 source_locations_len: u32,
64 string_bytes_len: u32,71 string_bytes_len: u32,
72 /// When, according to the server, fuzzing started.
73 start_timestamp: i64 align(4),
6574
66 pub const Flags = packed struct(u32) {75 pub const Flags = packed struct(u32) {
67 tag: ToClientTag = .source_index,76 tag: ToClientTag = .source_index,