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