Commit e3f58bd551

Andrew Kelley <andrew@ziglang.org>
2024-09-12 04:53:14
add runs per second to fuzzing ui
closes #21025
1 parent 9dc75f0
Changed files (6)
lib/fuzzer/web/index.html
@@ -146,6 +146,7 @@
       <ul>
         <li>Total Runs: <span id="statTotalRuns"></span></li>
         <li>Unique Runs: <span id="statUniqueRuns"></span></li>
+        <li>Speed (Runs/Second): <span id="statSpeed"></span></li>
         <li>Coverage: <span id="statCoverage"></span></li>
         <li>Entry Points: <ul id="entryPointsList"></ul></li>
       </ul>
lib/fuzzer/web/main.js
@@ -5,6 +5,7 @@
   const domSourceText = document.getElementById("sourceText");
   const domStatTotalRuns = document.getElementById("statTotalRuns");
   const domStatUniqueRuns = document.getElementById("statUniqueRuns");
+  const domStatSpeed = document.getElementById("statSpeed");
   const domStatCoverage = document.getElementById("statCoverage");
   const domEntryPointsList = document.getElementById("entryPointsList");
 
@@ -31,6 +32,9 @@
           const msg = decodeString(ptr, len);
           throw new Error("panic: " + msg);
       },
+      timestamp: function () {
+        return BigInt(new Date());
+      },
       emitSourceIndexChange: onSourceIndexChange,
       emitCoverageUpdate: onCoverageUpdate,
       emitEntryPointsUpdate: renderStats,
@@ -157,6 +161,7 @@
     domStatTotalRuns.innerText = totalRuns;
     domStatUniqueRuns.innerText = uniqueRuns + " (" + percent(uniqueRuns, totalRuns) + "%)";
     domStatCoverage.innerText = coveredSourceLocations + " / " + totalSourceLocations + " (" + percent(coveredSourceLocations, totalSourceLocations) + "%)";
+    domStatSpeed.innerText = wasm_exports.totalRunsPerSecond().toFixed(0);
 
     const entryPoints = unwrapInt32Array(wasm_exports.entryPoints());
     resizeDomList(domEntryPointsList, entryPoints.length, "<li></li>");
lib/fuzzer/web/main.zig
@@ -10,9 +10,17 @@ const Walk = @import("Walk");
 const Decl = Walk.Decl;
 const html_render = @import("html_render");
 
+/// Nanoseconds.
+var server_base_timestamp: i64 = 0;
+/// Milliseconds.
+var client_base_timestamp: i64 = 0;
+/// Relative to `server_base_timestamp`.
+var start_fuzzing_timestamp: i64 = undefined;
+
 const js = struct {
     extern "js" fn log(ptr: [*]const u8, len: usize) void;
     extern "js" fn panic(ptr: [*]const u8, len: usize) noreturn;
+    extern "js" fn timestamp() i64;
     extern "js" fn emitSourceIndexChange() void;
     extern "js" fn emitCoverageUpdate() void;
     extern "js" fn emitEntryPointsUpdate() void;
@@ -64,6 +72,7 @@ export fn message_end() void {
 
     const tag: abi.ToClientTag = @enumFromInt(msg_bytes[0]);
     switch (tag) {
+        .current_time => return currentTimeMessage(msg_bytes),
         .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"),
@@ -117,16 +126,28 @@ export fn coveredSourceLocations() usize {
     return count;
 }
 
+fn getCoverageUpdateHeader() *abi.CoverageUpdateHeader {
+    return @alignCast(@ptrCast(recent_coverage_update.items[0..@sizeOf(abi.CoverageUpdateHeader)]));
+}
+
 export fn totalRuns() u64 {
-    const header: *abi.CoverageUpdateHeader = @alignCast(@ptrCast(recent_coverage_update.items[0..@sizeOf(abi.CoverageUpdateHeader)]));
+    const header = getCoverageUpdateHeader();
     return header.n_runs;
 }
 
 export fn uniqueRuns() u64 {
-    const header: *abi.CoverageUpdateHeader = @alignCast(@ptrCast(recent_coverage_update.items[0..@sizeOf(abi.CoverageUpdateHeader)]));
+    const header = getCoverageUpdateHeader();
     return header.unique_runs;
 }
 
+export fn totalRunsPerSecond() f64 {
+    @setFloatMode(.optimized);
+    const header = getCoverageUpdateHeader();
+    const ns_elapsed: f64 = @floatFromInt(nsSince(start_fuzzing_timestamp));
+    const n_runs: f64 = @floatFromInt(header.n_runs);
+    return n_runs / (ns_elapsed / std.time.ns_per_s);
+}
+
 const String = Slice(u8);
 
 fn Slice(T: type) type {
@@ -189,6 +210,18 @@ fn fatal(comptime format: []const u8, args: anytype) noreturn {
     js.panic(line.ptr, line.len);
 }
 
+fn currentTimeMessage(msg_bytes: []u8) void {
+    client_base_timestamp = js.timestamp();
+    server_base_timestamp = @bitCast(msg_bytes[1..][0..8].*);
+}
+
+/// Nanoseconds passed since a server timestamp.
+fn nsSince(server_timestamp: i64) i64 {
+    const ms_passed = js.timestamp() - client_base_timestamp;
+    const ns_passed = server_base_timestamp - server_timestamp;
+    return ns_passed + ms_passed * std.time.ns_per_ms;
+}
+
 fn sourceIndexMessage(msg_bytes: []u8) error{OutOfMemory}!void {
     const Header = abi.SourceIndexHeader;
     const header: Header = @bitCast(msg_bytes[0..@sizeOf(Header)].*);
@@ -205,6 +238,7 @@ fn sourceIndexMessage(msg_bytes: []u8) error{OutOfMemory}!void {
     const files: []const Coverage.File = @alignCast(std.mem.bytesAsSlice(Coverage.File, msg_bytes[files_start..files_end]));
     const source_locations: []const Coverage.SourceLocation = @alignCast(std.mem.bytesAsSlice(Coverage.SourceLocation, msg_bytes[source_locations_start..source_locations_end]));
 
+    start_fuzzing_timestamp = header.start_timestamp;
     try updateCoverage(directories, files, source_locations, string_bytes);
     js.emitSourceIndexChange();
 }
lib/std/Build/Fuzz/abi.zig
@@ -43,12 +43,19 @@ pub const SeenPcsHeader = extern struct {
 };
 
 pub const ToClientTag = enum(u8) {
+    current_time,
     source_index,
     coverage_update,
     entry_points,
     _,
 };
 
+pub const CurrentTime = extern struct {
+    tag: ToClientTag = .current_time,
+    /// Number of nanoseconds that all other timestamps are in reference to.
+    base: i64 align(1),
+};
+
 /// Sent to the fuzzer web client on first connection to the websocket URL.
 ///
 /// Trailing:
@@ -62,6 +69,8 @@ pub const SourceIndexHeader = extern struct {
     files_len: u32,
     source_locations_len: u32,
     string_bytes_len: u32,
+    /// When, according to the server, fuzzing started.
+    start_timestamp: i64 align(4),
 
     pub const Flags = packed struct(u32) {
         tag: ToClientTag = .source_index,
lib/std/Build/Fuzz/WebServer.zig
@@ -33,6 +33,9 @@ coverage_mutex: std.Thread.Mutex,
 /// Signaled when `coverage_files` changes.
 coverage_condition: std.Thread.Condition,
 
+/// Time at initialization of WebServer.
+base_timestamp: i128,
+
 const fuzzer_bin_name = "fuzzer";
 const fuzzer_arch_os_abi = "wasm32-freestanding";
 const fuzzer_cpu_features = "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext";
@@ -43,6 +46,7 @@ const CoverageMap = struct {
     source_locations: []Coverage.SourceLocation,
     /// Elements are indexes into `source_locations` pointing to the unit tests that are being fuzz tested.
     entry_points: std.ArrayListUnmanaged(u32),
+    start_timestamp: i64,
 
     fn deinit(cm: *CoverageMap, gpa: Allocator) void {
         std.posix.munmap(cm.mapped_memory);
@@ -87,6 +91,10 @@ pub fn run(ws: *WebServer) void {
     }
 }
 
+fn now(s: *const WebServer) i64 {
+    return @intCast(std.time.nanoTimestamp() - s.base_timestamp);
+}
+
 fn accept(ws: *WebServer, connection: std.net.Server.Connection) void {
     defer connection.stream.close();
 
@@ -381,6 +389,13 @@ fn serveWebSocket(ws: *WebServer, web_socket: *std.http.WebSocket) !void {
     ws.coverage_mutex.lock();
     defer ws.coverage_mutex.unlock();
 
+    // On first connection, the client needs to know what time the server
+    // thinks it is to rebase timestamps.
+    {
+        const timestamp_message: abi.CurrentTime = .{ .base = ws.now() };
+        try web_socket.writeMessage(std.mem.asBytes(&timestamp_message), .binary);
+    }
+
     // On first connection, the client needs all the coverage information
     // so that subsequent updates can contain only the updated bits.
     var prev_unique_runs: usize = 0;
@@ -416,6 +431,7 @@ fn sendCoverageContext(
                 .files_len = @intCast(coverage_map.coverage.files.entries.len),
                 .source_locations_len = @intCast(coverage_map.source_locations.len),
                 .string_bytes_len = @intCast(coverage_map.coverage.string_bytes.items.len),
+                .start_timestamp = coverage_map.start_timestamp,
             };
             const iovecs: [5]std.posix.iovec_const = .{
                 makeIov(std.mem.asBytes(&header)),
@@ -582,6 +598,7 @@ fn prepareTables(
         .mapped_memory = undefined, // populated below
         .source_locations = undefined, // populated below
         .entry_points = .{},
+        .start_timestamp = ws.now(),
     };
     errdefer gop.value_ptr.coverage.deinit(gpa);
 
lib/std/Build/Fuzz.zig
@@ -66,6 +66,8 @@ pub fn start(
         .coverage_files = .{},
         .coverage_mutex = .{},
         .coverage_condition = .{},
+
+        .base_timestamp = std.time.nanoTimestamp(),
     };
 
     // For accepting HTTP connections.