add runs per second to fuzzing ui

closes #21025
This commit is contained in:
Andrew Kelley 2024-09-11 19:53:14 -07:00
parent 9dc75f03e2
commit e3f58bd551
6 changed files with 70 additions and 2 deletions

View File

@ -146,6 +146,7 @@
<ul> <ul>
<li>Total Runs: <span id="statTotalRuns"></span></li> <li>Total Runs: <span id="statTotalRuns"></span></li>
<li>Unique Runs: <span id="statUniqueRuns"></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>Coverage: <span id="statCoverage"></span></li>
<li>Entry Points: <ul id="entryPointsList"></ul></li> <li>Entry Points: <ul id="entryPointsList"></ul></li>
</ul> </ul>

View File

@ -5,6 +5,7 @@
const domSourceText = document.getElementById("sourceText"); const domSourceText = document.getElementById("sourceText");
const domStatTotalRuns = document.getElementById("statTotalRuns"); const domStatTotalRuns = document.getElementById("statTotalRuns");
const domStatUniqueRuns = document.getElementById("statUniqueRuns"); const domStatUniqueRuns = document.getElementById("statUniqueRuns");
const domStatSpeed = document.getElementById("statSpeed");
const domStatCoverage = document.getElementById("statCoverage"); const domStatCoverage = document.getElementById("statCoverage");
const domEntryPointsList = document.getElementById("entryPointsList"); const domEntryPointsList = document.getElementById("entryPointsList");
@ -31,6 +32,9 @@
const msg = decodeString(ptr, len); const msg = decodeString(ptr, len);
throw new Error("panic: " + msg); throw new Error("panic: " + msg);
}, },
timestamp: function () {
return BigInt(new Date());
},
emitSourceIndexChange: onSourceIndexChange, emitSourceIndexChange: onSourceIndexChange,
emitCoverageUpdate: onCoverageUpdate, emitCoverageUpdate: onCoverageUpdate,
emitEntryPointsUpdate: renderStats, emitEntryPointsUpdate: renderStats,
@ -157,6 +161,7 @@
domStatTotalRuns.innerText = totalRuns; domStatTotalRuns.innerText = totalRuns;
domStatUniqueRuns.innerText = uniqueRuns + " (" + percent(uniqueRuns, totalRuns) + "%)"; domStatUniqueRuns.innerText = uniqueRuns + " (" + percent(uniqueRuns, totalRuns) + "%)";
domStatCoverage.innerText = coveredSourceLocations + " / " + totalSourceLocations + " (" + percent(coveredSourceLocations, totalSourceLocations) + "%)"; domStatCoverage.innerText = coveredSourceLocations + " / " + totalSourceLocations + " (" + percent(coveredSourceLocations, totalSourceLocations) + "%)";
domStatSpeed.innerText = wasm_exports.totalRunsPerSecond().toFixed(0);
const entryPoints = unwrapInt32Array(wasm_exports.entryPoints()); const entryPoints = unwrapInt32Array(wasm_exports.entryPoints());
resizeDomList(domEntryPointsList, entryPoints.length, "<li></li>"); resizeDomList(domEntryPointsList, entryPoints.length, "<li></li>");

View File

@ -10,9 +10,17 @@ const Walk = @import("Walk");
const Decl = Walk.Decl; const Decl = Walk.Decl;
const html_render = @import("html_render"); 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 { const js = struct {
extern "js" fn log(ptr: [*]const u8, len: usize) void; extern "js" fn log(ptr: [*]const u8, len: usize) void;
extern "js" fn panic(ptr: [*]const u8, len: usize) noreturn; extern "js" fn panic(ptr: [*]const u8, len: usize) noreturn;
extern "js" fn timestamp() i64;
extern "js" fn emitSourceIndexChange() void; extern "js" fn emitSourceIndexChange() void;
extern "js" fn emitCoverageUpdate() void; extern "js" fn emitCoverageUpdate() void;
extern "js" fn emitEntryPointsUpdate() void; extern "js" fn emitEntryPointsUpdate() void;
@ -64,6 +72,7 @@ export fn message_end() void {
const tag: abi.ToClientTag = @enumFromInt(msg_bytes[0]); const tag: abi.ToClientTag = @enumFromInt(msg_bytes[0]);
switch (tag) { switch (tag) {
.current_time => return currentTimeMessage(msg_bytes),
.source_index => return sourceIndexMessage(msg_bytes) catch @panic("OOM"), .source_index => return sourceIndexMessage(msg_bytes) catch @panic("OOM"),
.coverage_update => return coverageUpdateMessage(msg_bytes) catch @panic("OOM"), .coverage_update => return coverageUpdateMessage(msg_bytes) catch @panic("OOM"),
.entry_points => return entryPointsMessage(msg_bytes) catch @panic("OOM"), .entry_points => return entryPointsMessage(msg_bytes) catch @panic("OOM"),
@ -117,16 +126,28 @@ export fn coveredSourceLocations() usize {
return count; return count;
} }
fn getCoverageUpdateHeader() *abi.CoverageUpdateHeader {
return @alignCast(@ptrCast(recent_coverage_update.items[0..@sizeOf(abi.CoverageUpdateHeader)]));
}
export fn totalRuns() u64 { 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; return header.n_runs;
} }
export fn uniqueRuns() u64 { 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; 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); const String = Slice(u8);
fn Slice(T: type) type { fn Slice(T: type) type {
@ -189,6 +210,18 @@ fn fatal(comptime format: []const u8, args: anytype) noreturn {
js.panic(line.ptr, line.len); 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 { fn sourceIndexMessage(msg_bytes: []u8) error{OutOfMemory}!void {
const Header = abi.SourceIndexHeader; const Header = abi.SourceIndexHeader;
const header: Header = @bitCast(msg_bytes[0..@sizeOf(Header)].*); 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 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])); 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); try updateCoverage(directories, files, source_locations, string_bytes);
js.emitSourceIndexChange(); js.emitSourceIndexChange();
} }

View File

@ -66,6 +66,8 @@ pub fn start(
.coverage_files = .{}, .coverage_files = .{},
.coverage_mutex = .{}, .coverage_mutex = .{},
.coverage_condition = .{}, .coverage_condition = .{},
.base_timestamp = std.time.nanoTimestamp(),
}; };
// For accepting HTTP connections. // For accepting HTTP connections.

View File

@ -33,6 +33,9 @@ coverage_mutex: std.Thread.Mutex,
/// Signaled when `coverage_files` changes. /// Signaled when `coverage_files` changes.
coverage_condition: std.Thread.Condition, coverage_condition: std.Thread.Condition,
/// Time at initialization of WebServer.
base_timestamp: i128,
const fuzzer_bin_name = "fuzzer"; const fuzzer_bin_name = "fuzzer";
const fuzzer_arch_os_abi = "wasm32-freestanding"; const fuzzer_arch_os_abi = "wasm32-freestanding";
const fuzzer_cpu_features = "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext"; 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, source_locations: []Coverage.SourceLocation,
/// Elements are indexes into `source_locations` pointing to the unit tests that are being fuzz tested. /// Elements are indexes into `source_locations` pointing to the unit tests that are being fuzz tested.
entry_points: std.ArrayListUnmanaged(u32), entry_points: std.ArrayListUnmanaged(u32),
start_timestamp: i64,
fn deinit(cm: *CoverageMap, gpa: Allocator) void { fn deinit(cm: *CoverageMap, gpa: Allocator) void {
std.posix.munmap(cm.mapped_memory); 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 { fn accept(ws: *WebServer, connection: std.net.Server.Connection) void {
defer connection.stream.close(); defer connection.stream.close();
@ -381,6 +389,13 @@ fn serveWebSocket(ws: *WebServer, web_socket: *std.http.WebSocket) !void {
ws.coverage_mutex.lock(); ws.coverage_mutex.lock();
defer ws.coverage_mutex.unlock(); 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 // On first connection, the client needs all the coverage information
// so that subsequent updates can contain only the updated bits. // so that subsequent updates can contain only the updated bits.
var prev_unique_runs: usize = 0; var prev_unique_runs: usize = 0;
@ -416,6 +431,7 @@ fn sendCoverageContext(
.files_len = @intCast(coverage_map.coverage.files.entries.len), .files_len = @intCast(coverage_map.coverage.files.entries.len),
.source_locations_len = @intCast(coverage_map.source_locations.len), .source_locations_len = @intCast(coverage_map.source_locations.len),
.string_bytes_len = @intCast(coverage_map.coverage.string_bytes.items.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 = .{ const iovecs: [5]std.posix.iovec_const = .{
makeIov(std.mem.asBytes(&header)), makeIov(std.mem.asBytes(&header)),
@ -582,6 +598,7 @@ fn prepareTables(
.mapped_memory = undefined, // populated below .mapped_memory = undefined, // populated below
.source_locations = undefined, // populated below .source_locations = undefined, // populated below
.entry_points = .{}, .entry_points = .{},
.start_timestamp = ws.now(),
}; };
errdefer gop.value_ptr.coverage.deinit(gpa); errdefer gop.value_ptr.coverage.deinit(gpa);

View File

@ -43,12 +43,19 @@ pub const SeenPcsHeader = extern struct {
}; };
pub const ToClientTag = enum(u8) { pub const ToClientTag = enum(u8) {
current_time,
source_index, source_index,
coverage_update, coverage_update,
entry_points, 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. /// Sent to the fuzzer web client on first connection to the websocket URL.
/// ///
/// Trailing: /// Trailing:
@ -62,6 +69,8 @@ pub const SourceIndexHeader = extern struct {
files_len: u32, files_len: u32,
source_locations_len: u32, source_locations_len: u32,
string_bytes_len: u32, string_bytes_len: u32,
/// When, according to the server, fuzzing started.
start_timestamp: i64 align(4),
pub const Flags = packed struct(u32) { pub const Flags = packed struct(u32) {
tag: ToClientTag = .source_index, tag: ToClientTag = .source_index,