trace: write traced events to disk

This commit is contained in:
2026-06-08 11:28:57 +02:00
parent a8ac2773f9
commit aa9c3b7274
3 changed files with 200 additions and 31 deletions
+3 -1
View File
@@ -65,6 +65,7 @@ const Descriptor = struct {
gpa: Allocator, gpa: Allocator,
trace: *Trace, trace: *Trace,
prng: *std.Random.DefaultPrng, prng: *std.Random.DefaultPrng,
id: u32,
local_time: u64, local_time: u64,
arena: std.heap.ArenaAllocator, arena: std.heap.ArenaAllocator,
argv: [][*:0]const u8, argv: [][*:0]const u8,
@@ -114,10 +115,11 @@ fn splitCommandArguments(command: []const u8, arena: Allocator) Allocator.Error!
return result; return result;
} }
pub fn init(self: *Node, real_io: std.Io, trace: *Trace, prng: *std.Random.DefaultPrng, scheduler: *Scheduler, network: *Network, command: []const u8, addresses: []const u32, gpa: Allocator) !void { pub fn init(self: *Node, real_io: std.Io, trace: *Trace, prng: *std.Random.DefaultPrng, scheduler: *Scheduler, network: *Network, node_id: u32, command: []const u8, addresses: []const u32, gpa: Allocator) !void {
self.gpa = gpa; self.gpa = gpa;
self.trace = trace; self.trace = trace;
self.prng = prng; self.prng = prng;
self.id = node_id;
self.local_time = 0; self.local_time = 0;
self.arena = .init(gpa); self.arena = .init(gpa);
self.scheduler = scheduler; self.scheduler = scheduler;
+7 -2
View File
@@ -33,16 +33,18 @@ network: Network,
nodes: std.ArrayList(*Node), nodes: std.ArrayList(*Node),
executables: std.ArrayList(ExecutableName), executables: std.ArrayList(ExecutableName),
real_io: std.Io, real_io: std.Io,
next_node_id: u32,
pub fn init(self: *Simulator, gpa: Allocator, real_io: std.Io, seed: u64) void { pub fn init(self: *Simulator, gpa: Allocator, real_io: std.Io, seed: u64) void {
self.gpa = gpa; self.gpa = gpa;
self.trace.init(real_io); self.trace.init(&self.scheduler, real_io);
self.prng = std.Random.DefaultPrng.init(seed); self.prng = std.Random.DefaultPrng.init(seed);
self.scheduler.init(gpa, &self.trace, &self.prng); self.scheduler.init(gpa, &self.trace, &self.prng);
self.network.init(gpa); self.network.init(gpa);
self.nodes = .empty; self.nodes = .empty;
self.executables = .empty; self.executables = .empty;
self.real_io = real_io; self.real_io = real_io;
self.next_node_id = 0;
} }
pub fn deinit(self: *Simulator) void { pub fn deinit(self: *Simulator) void {
@@ -72,10 +74,13 @@ pub fn spawn(self: *Simulator, command: []const u8, options: SpawnOptions) Spawn
const name = extractProgramNameFromCommand(command) orelse return SpawnError.InvalidCommand; const name = extractProgramNameFromCommand(command) orelse return SpawnError.InvalidCommand;
const entry = self.getExecutableEntryPoint(name) orelse return SpawnError.NoSuchProgram; const entry = self.getExecutableEntryPoint(name) orelse return SpawnError.NoSuchProgram;
const node_id = self.next_node_id;
self.next_node_id += 1;
const node = try self.gpa.create(Node); const node = try self.gpa.create(Node);
errdefer self.gpa.destroy(node); errdefer self.gpa.destroy(node);
try node.init(self.real_io, &self.trace, &self.prng, &self.scheduler, &self.network, command, options.addresses, self.gpa); try node.init(self.real_io, &self.trace, &self.prng, &self.scheduler, &self.network, node_id, command, options.addresses, self.gpa);
try self.nodes.append(self.gpa, node); try self.nodes.append(self.gpa, node);
errdefer _ = self.nodes.swapRemove(self.nodes.items.len-1); errdefer _ = self.nodes.swapRemove(self.nodes.items.len-1);
+190 -28
View File
@@ -1,9 +1,8 @@
const std = @import("std"); const std = @import("std");
const Io = std.Io; const Io = std.Io;
const Node = @import("node.zig"); const Node = @import("node.zig");
const TaskID = @import("scheduler.zig").TaskID; const Scheduler = @import("scheduler.zig");
const TaskID = Scheduler.TaskID;
const PendingTrace = struct {};
pub const Trace = struct { pub const Trace = struct {
@@ -15,14 +14,43 @@ pub const Trace = struct {
waiting_task, waiting_task,
failed, failed,
returned, returned,
pub fn toString(self: TaskState) []const u8 {
return switch (self) {
.ready => "ready",
.running => "running",
.sleeping => "sleeping",
.waiting_futex => "waiting-futex",
.waiting_task => "waiting-task",
.failed => "failed",
.returned => "returned",
};
}
};
const Pending = struct {
trace_id: u64,
local_time: u64,
global_time: u64,
node_id: u32,
task_id: TaskID,
disk: bool,
op: []const u8,
}; };
io: Io, io: Io,
file: ?Io.File, file: ?Io.File,
scheduler: *Scheduler,
task: ?TaskID,
node: ?*Node,
next_trace_id: u64,
pub fn init(self: *Trace, io: Io) void { pub fn init(self: *Trace, scheduler: *Scheduler, io: Io) void {
self.io = io; self.io = io;
self.file = null; self.file = null;
self.scheduler = scheduler;
self.task = null;
self.node = null;
} }
pub fn deinit(self: *Trace) void { pub fn deinit(self: *Trace) void {
@@ -38,65 +66,199 @@ pub const Trace = struct {
pub fn enterTask(self: *Trace, task_id: TaskID, node: *Node) void { pub fn enterTask(self: *Trace, task_id: TaskID, node: *Node) void {
if (self.file == null) return; if (self.file == null) return;
_ = task_id; self.task = task_id;
_ = node; self.node = node;
} }
pub fn leaveTask(self: *Trace) void { pub fn leaveTask(self: *Trace) void {
if (self.file == null) return; if (self.file == null) return;
self.task = null;
self.node = null;
} }
pub fn taskSpawned(self: *Trace, task_id: TaskID, node: *Node, parent_id: ?TaskID) void { pub fn taskSpawned(self: *Trace, task_id: TaskID, node: *Node, parent_id: ?TaskID) void {
if (self.file == null) return; if (self.file == null) return;
_ = task_id; const global_time = self.scheduler.current_time;
_ = node; const local_time = node.local_time;
_ = parent_id;
var line_buffer: [256]u8 = undefined;
const prefix = std.fmt.bufPrint(&line_buffer,
\\{{"time":{},"global_time":{},"event":"task_spawned","node":{},"task":{}
, .{ local_time, global_time, node.id, task_id }) catch return;
self.write(prefix);
if (parent_id) |it| {
const parent = std.fmt.bufPrint(&line_buffer, ",\"parent\":{}", .{it}) catch return;
self.write(parent);
}
self.write("}\n");
} }
pub fn taskRemoved(self: *Trace, task_id: TaskID, node: *Node) void { pub fn taskRemoved(self: *Trace, task_id: TaskID, node: *Node) void {
if (self.file == null) return; if (self.file == null) return;
_ = task_id; const global_time = self.scheduler.current_time;
_ = node; const local_time = node.local_time;
var line_buffer: [256]u8 = undefined;
const line = std.fmt.bufPrint(&line_buffer,
\\{{"time":{},"global_time":{},"event":"task_removed","node":{},"task":{}}}
\\
, .{ local_time, global_time, node.id, task_id }) catch return;
self.write(line);
} }
pub fn taskState(self: *Trace, task_id: TaskID, node: *Node, state: Trace.TaskState, reason: []const u8) void { pub fn taskState(self: *Trace, task_id: TaskID, node: *Node, state: Trace.TaskState, reason: []const u8) void {
if (self.file == null) return; if (self.file == null) return;
_ = task_id; const global_time = self.scheduler.current_time;
_ = node; const local_time = node.local_time;
_ = state;
_ = reason; var line_buffer: [256]u8 = undefined;
const prefix = std.fmt.bufPrint(&line_buffer,
\\{{"time":{},"global_time":{},"event":"state","node":{},"task":{},"state":
, .{ local_time, global_time, node.id, task_id }) catch return;
self.write(prefix);
self.writeJsonString(state.toString());
self.write(",\"reason\":");
self.writeJsonString(reason);
self.write("}\n");
} }
pub fn timeAdvanced(self: *Trace, from: u64, to: u64) void { pub fn timeAdvanced(self: *Trace, from: u64, to: u64) void {
if (self.file == null) return; if (self.file == null) return;
_ = from; var line_buffer: [256]u8 = undefined;
_ = to; const line = std.fmt.bufPrint(&line_buffer,
\\{{"time":{},"global_time":{},"event":"time_advanced","from":{},"to":{}}}
\\
, .{ to, to, from, to }) catch return;
self.write(line);
} }
pub fn beginIO(self: *Trace, disk: bool, source: std.builtin.SourceLocation) PendingTrace { pub fn beginIO(self: *Trace, disk: bool, source: std.builtin.SourceLocation) Pending {
if (self.file == null) return .{}; if (self.file == null)
return .{
.trace_id = 0,
.local_time = 0,
.global_time = 0,
.task_id = 0,
.node_id = 0,
.disk = false,
.op = &.{},
};
_ = disk; std.debug.assert(self.task != null);
_ = source; std.debug.assert(self.node != null);
return .{}; const node = self.node.?;
const task = self.task.?;
const global_time = self.scheduler.current_time;
const local_time = node.local_time;
const trace_id = self.next_trace_id;
self.next_trace_id += 1;
var line_buffer: [256]u8 = undefined;
const prefix = std.fmt.bufPrint(&line_buffer,
\\{{"time":{},"global_time":{},"event":"io_start","id":{},"node":{},"task":{},"op":
, .{ local_time, global_time, trace_id, node.id, task }) catch @panic("TODO");
self.write(prefix);
self.writeJsonString(source.fn_name);
self.write(",\"resource\":");
self.writeJsonString(if (disk) "disk" else "io");
self.write("}\n");
return .{
.trace_id = trace_id,
.local_time = local_time,
.global_time = global_time,
.node_id = node.id,
.task_id = task,
.disk = disk,
.op = source.fn_name,
};
} }
pub fn failIO(self: *Trace, pending_trace: PendingTrace, e: anyerror) void { pub fn failIO(self: *Trace, pending_trace: Pending, e: anyerror) void {
if (self.file == null) return; if (self.file == null) return;
_ = pending_trace; var result_buffer: [96]u8 = undefined;
_ = e catch {}; const result = std.fmt.bufPrint(&result_buffer, "error.{s}", .{@errorName(e)}) catch "error";
self.completeIO(pending_trace, result);
} }
pub fn completeIO(self: *Trace, pending_trace: PendingTrace, result: anytype) void { pub fn completeIO(self: *Trace, pending_trace: Pending, result: anytype) void {
if (self.file == null) return; if (self.file == null) return;
_ = pending_trace; const global_time = self.scheduler.current_time;
_ = result; const node = self.node orelse return;
const local_time = node.local_time;
var line_buffer: [256]u8 = undefined;
const prefix = std.fmt.bufPrint(&line_buffer,
\\{{"time":{},"global_time":{},"event":"io_complete","id":{},"node":{},"task":{},"op":
, .{ local_time, global_time, pending_trace.trace_id, pending_trace.node_id, pending_trace.task_id }) catch return;
self.write(prefix);
self.writeJsonString(pending_trace.op);
self.write(",\"resource\":");
self.writeJsonString(if (pending_trace.disk) "disk" else "io");
const suffix = std.fmt.bufPrint(&line_buffer,
\\,"start":{},"end":{},"result":
, .{ pending_trace.local_time, local_time }) catch return;
self.write(suffix);
var result_buffer: [96]u8 = undefined;
self.writeJsonString(resultText(&result_buffer, result));
self.write("}\n");
if (pending_trace.disk) {
var disk_line_buffer: [256]u8 = undefined;
const disk_prefix = std.fmt.bufPrint(&disk_line_buffer,
\\{{"time":{},"global_time":{},"event":"disk","node":{},"op":
, .{ pending_trace.local_time, global_time, pending_trace.node_id }) catch return;
self.write(disk_prefix);
self.writeJsonString(pending_trace.op);
const middle = std.fmt.bufPrint(&disk_line_buffer,
\\,"start":{},"end":{},"detail":
, .{ pending_trace.local_time, local_time }) catch return;
self.write(middle);
self.writeJsonString(resultText(&result_buffer, result));
self.write("}\n");
}
}
fn writeJsonString(self: *Trace, text: []const u8) void {
self.write("\"");
for (text) |byte| {
switch (byte) {
'"' => self.write("\\\""),
'\\' => self.write("\\\\"),
'\n' => self.write("\\n"),
'\r' => self.write("\\r"),
'\t' => self.write("\\t"),
else => self.write((&byte)[0..1]),
}
}
self.write("\"");
}
fn write(self: *Trace, bytes: []const u8) void {
const file = self.file orelse return;
file.writeStreamingAll(self.io, bytes) catch {};
} }
}; };
fn resultText(buffer: []u8, result: anytype) []const u8 {
const T = @TypeOf(result);
return switch (@typeInfo(T)) {
.void => "ok",
.@"struct" => "ok",
.pointer => |pointer| if (pointer.size == .slice and pointer.child == u8)
result
else
std.fmt.bufPrint(buffer, "{any}", .{result}) catch "result",
else => std.fmt.bufPrint(buffer, "{}", .{result}) catch "result",
};
}