diff --git a/lib/fuzzer/index.html b/lib/fuzzer/index.html
index 16fa87991377..2dbfa85d2d3a 100644
--- a/lib/fuzzer/index.html
+++ b/lib/fuzzer/index.html
@@ -145,6 +145,7 @@
- Total Runs:
+ - Rate:
- Unique Runs:
- Coverage:
- Lowest Stack:
diff --git a/lib/fuzzer/main.js b/lib/fuzzer/main.js
index ce02276f9819..e1931b17b2ac 100644
--- a/lib/fuzzer/main.js
+++ b/lib/fuzzer/main.js
@@ -4,6 +4,7 @@
const domSectStats = document.getElementById("sectStats");
const domSourceText = document.getElementById("sourceText");
const domStatTotalRuns = document.getElementById("statTotalRuns");
+ const domStatRate = document.getElementById("statRate");
const domStatUniqueRuns = document.getElementById("statUniqueRuns");
const domStatCoverage = document.getElementById("statCoverage");
const domStatLowestStack = document.getElementById("statLowestStack");
@@ -34,6 +35,7 @@
},
emitSourceIndexChange: onSourceIndexChange,
emitCoverageUpdate: onCoverageUpdate,
+ emitPeriodicUpdate: onPeriodicUpdate,
emitEntryPointsUpdate: renderStats,
},
}).then(function(obj) {
@@ -146,16 +148,22 @@
renderCoverage();
}
+ function onPeriodicUpdate() {
+ renderStats();
+ }
+
function render() {
domStatus.classList.add("hidden");
}
function renderStats() {
const totalRuns = wasm_exports.totalRuns();
+ const cyclesPerSecond = wasm_exports.cyclesPerSecond();
const uniqueRuns = wasm_exports.uniqueRuns();
const totalSourceLocations = wasm_exports.totalSourceLocations();
const coveredSourceLocations = wasm_exports.coveredSourceLocations();
domStatTotalRuns.innerText = totalRuns;
+ domStatRate.innerText = cyclesPerSecond + " cycles / second";
domStatUniqueRuns.innerText = uniqueRuns + " (" + percent(uniqueRuns, totalRuns) + "%)";
domStatCoverage.innerText = coveredSourceLocations + " / " + totalSourceLocations + " (" + percent(coveredSourceLocations, totalSourceLocations) + "%)";
domStatLowestStack.innerText = unwrapString(wasm_exports.lowestStack());
diff --git a/lib/fuzzer/wasm/main.zig b/lib/fuzzer/wasm/main.zig
index 342adc3b5608..ba7e8821ffd7 100644
--- a/lib/fuzzer/wasm/main.zig
+++ b/lib/fuzzer/wasm/main.zig
@@ -15,6 +15,7 @@ const js = struct {
extern "js" fn panic(ptr: [*]const u8, len: usize) noreturn;
extern "js" fn emitSourceIndexChange() void;
extern "js" fn emitCoverageUpdate() void;
+ extern "js" fn emitPeriodicUpdate() void;
extern "js" fn emitEntryPointsUpdate() void;
};
@@ -67,6 +68,7 @@ export fn message_end() void {
.source_index => return sourceIndexMessage(msg_bytes) catch @panic("OOM"),
.coverage_update => return coverageUpdateMessage(msg_bytes) catch @panic("OOM"),
.entry_points => return entryPointsMessage(msg_bytes) catch @panic("OOM"),
+ .periodic => return periodicMessage(msg_bytes),
_ => unreachable,
}
}
@@ -129,6 +131,10 @@ export fn totalRuns() u64 {
return header.n_runs;
}
+export fn cyclesPerSecond() u64 {
+ return recent_periodic_update.cycles_per_second;
+}
+
export fn uniqueRuns() u64 {
const header: *abi.CoverageUpdateHeader = @alignCast(@ptrCast(recent_coverage_update.items[0..@sizeOf(abi.CoverageUpdateHeader)]));
return header.unique_runs;
@@ -235,6 +241,11 @@ export fn entryPoints() Slice(u32) {
return Slice(u32).init(entry_points.items);
}
+fn periodicMessage(msg_bytes: []u8) void {
+ recent_periodic_update = std.mem.bytesToValue(abi.PeriodicUpdateHeader, msg_bytes);
+ js.emitPeriodicUpdate();
+}
+
/// Index into `coverage_source_locations`.
const SourceLocationIndex = enum(u32) {
_,
@@ -350,6 +361,8 @@ var coverage = Coverage.init;
var coverage_source_locations: std.ArrayListUnmanaged(Coverage.SourceLocation) = .{};
/// Contains the most recent coverage update message, unmodified.
var recent_coverage_update: std.ArrayListAlignedUnmanaged(u8, @alignOf(u64)) = .{};
+/// Contains the most recent periodic update message, unmodified.
+var recent_periodic_update: abi.PeriodicUpdateHeader = .{ .cycles_per_second = 0 };
fn updateCoverage(
directories: []const Coverage.String,
diff --git a/lib/std/Build/Fuzz.zig b/lib/std/Build/Fuzz.zig
index 23f8a0269283..3d1ce4bcf8c0 100644
--- a/lib/std/Build/Fuzz.zig
+++ b/lib/std/Build/Fuzz.zig
@@ -13,6 +13,8 @@ const build_runner = @import("root");
pub const WebServer = @import("Fuzz/WebServer.zig");
pub const abi = @import("Fuzz/abi.zig");
+pub const StartError = Allocator.Error || std.time.Timer.Error;
+
pub fn start(
gpa: Allocator,
arena: Allocator,
@@ -24,7 +26,7 @@ pub fn start(
ttyconf: std.io.tty.Config,
listen_address: std.net.Address,
prog_node: std.Progress.Node,
-) Allocator.Error!void {
+) StartError!void {
const fuzz_run_steps = block: {
const rebuild_node = prog_node.start("Rebuilding Unit Tests", 0);
defer rebuild_node.end();
diff --git a/lib/std/Build/Fuzz/WebServer.zig b/lib/std/Build/Fuzz/WebServer.zig
index a0ab018cf57c..7ab45b9de939 100644
--- a/lib/std/Build/Fuzz/WebServer.zig
+++ b/lib/std/Build/Fuzz/WebServer.zig
@@ -247,6 +247,8 @@ fn buildWasmBinary(
try std.fmt.allocPrint(arena, "-MWalk={}", .{walk_src_path}), //
"--dep", "Walk", //
try std.fmt.allocPrint(arena, "-Mhtml_render={}", .{html_render_src_path}), //
+ "--zig-lib-dir",
+ ws.zig_lib_directory.path orelse ".",
"--listen=-",
});
@@ -385,10 +387,12 @@ fn serveWebSocket(ws: *WebServer, web_socket: *std.http.WebSocket) !void {
// so that subsequent updates can contain only the updated bits.
var prev_unique_runs: usize = 0;
var prev_entry_points: usize = 0;
- try sendCoverageContext(ws, web_socket, &prev_unique_runs, &prev_entry_points);
+ var timer = try std.time.Timer.start();
+ var last_cycle_timed: usize = 0;
+ try sendCoverageContext(ws, web_socket, &prev_unique_runs, &prev_entry_points, &timer, &last_cycle_timed);
while (true) {
ws.coverage_condition.timedWait(&ws.coverage_mutex, std.time.ns_per_ms * 500) catch {};
- try sendCoverageContext(ws, web_socket, &prev_unique_runs, &prev_entry_points);
+ try sendCoverageContext(ws, web_socket, &prev_unique_runs, &prev_entry_points, &timer, &last_cycle_timed);
}
}
@@ -397,6 +401,8 @@ fn sendCoverageContext(
web_socket: *std.http.WebSocket,
prev_unique_runs: *usize,
prev_entry_points: *usize,
+ timer: *std.time.Timer,
+ last_cycle_timed: *usize,
) !void {
const coverage_maps = ws.coverage_files.values();
if (coverage_maps.len == 0) return;
@@ -407,6 +413,7 @@ fn sendCoverageContext(
const n_runs = @atomicLoad(usize, &cov_header.n_runs, .monotonic);
const unique_runs = @atomicLoad(usize, &cov_header.unique_runs, .monotonic);
const lowest_stack = @atomicLoad(usize, &cov_header.lowest_stack, .monotonic);
+ const duration = timer.read();
if (prev_unique_runs.* != unique_runs) {
// There has been an update.
if (prev_unique_runs.* == 0) {
@@ -456,6 +463,19 @@ fn sendCoverageContext(
prev_entry_points.* = coverage_map.entry_points.items.len;
}
+
+ if (duration >= std.time.ns_per_s) {
+ const time_s: usize = @intCast(duration / std.time.ns_per_s);
+ const window_size = n_runs -% last_cycle_timed.*;
+ const cycles_per_second = window_size / time_s;
+ last_cycle_timed.* = n_runs;
+
+ try web_socket.writeMessage(std.mem.asBytes(&abi.PeriodicUpdateHeader{
+ .cycles_per_second = cycles_per_second,
+ }), .binary);
+
+ timer.reset();
+ }
}
fn serveSourcesTar(ws: *WebServer, request: *std.http.Server.Request) !void {
diff --git a/lib/std/Build/Fuzz/abi.zig b/lib/std/Build/Fuzz/abi.zig
index 0e16f0d5fa3f..8d368547db81 100644
--- a/lib/std/Build/Fuzz/abi.zig
+++ b/lib/std/Build/Fuzz/abi.zig
@@ -47,6 +47,7 @@ pub const ToClientTag = enum(u8) {
source_index,
coverage_update,
entry_points,
+ periodic,
_,
};
@@ -103,3 +104,14 @@ pub const EntryPointHeader = extern struct {
locs_len: u24,
};
};
+
+/// Sent to the fuzzer web client on a periodic basis.
+pub const PeriodicUpdateHeader = extern struct {
+ flags: Flags = .{},
+ cycles_per_second: u64,
+
+ pub const Flags = packed struct(u64) {
+ tag: ToClientTag = .periodic,
+ _: u56 = 0,
+ };
+};