tiny.trace.event
Defined in tiny.trace.
API (9)
Types and contracts
Public types and contracts.
Values and defaults
Public values and defaults.
Source
Source: lib/trace/src/event.zig
zig
const std = @import("std");const pretty_json = @import("pretty").json;const Allocator = std.mem.Allocator;pub const trace_format_version: u32 = 3;pub const ThreadId = u32;pub const Mode = enum { off, record, replay,};pub const Timepoint = struct { epoch: u64 = 0, thread_id: ThreadId = 0, seq: u64 = 0, pub fn beforeOrEqual(self: Timepoint, other: Timepoint) bool { if (self.epoch != other.epoch) return self.epoch < other.epoch; if (self.seq != other.seq) return self.seq <= other.seq; return self.thread_id <= other.thread_id; } pub fn eql(left: Timepoint, right: Timepoint) bool { return left.epoch == right.epoch and left.thread_id == right.thread_id and left.seq == right.seq; } pub fn write(self: Timepoint, writer: *std.Io.Writer) !void { try writer.print("{d}:{d}:{d}", .{ self.epoch, self.thread_id, self.seq }); }};pub const Safepoint = struct { function_id: u64, site_id: u64, stack_map_id: u64 = 0,};pub const EventKind = enum { session_start, session_end, function_enter, function_exit, safepoint, boundary, allocation, free, checkpoint, checkpoint_restore, user, pub fn tag(self: EventKind) []const u8 { return switch (self) { .session_start => "session.start", .session_end => "session.end", .function_enter => "function.enter", .function_exit => "function.exit", .safepoint => "safepoint", .boundary => "boundary", .allocation => "allocation", .free => "free", .checkpoint => "checkpoint", .checkpoint_restore => "checkpoint.restore", .user => "user", }; } pub fn fromTag(text: []const u8) ?EventKind { inline for ( @typeInfo(EventKind).@"enum".field_names, @typeInfo(EventKind).@"enum".field_values, ) |field_name, field_name_value| { const field = .{ .name = field_name, .value = field_name_value }; const kind: EventKind = @fromBackingInt(@intCast(field.value)); if (std.mem.eql(u8, text, kind.tag())) return kind; } return null; }};pub const Event = struct { kind: EventKind, timepoint: Timepoint, function_id: u64 = 0, site_id: u64 = 0, stack_map_id: u64 = 0, object_id: u64 = 0, size: u64 = 0, alignment: u32 = 0, status: i64 = 0, operation: ?[]const u8 = null, label: ?[]const u8 = null, data: ?[]const u8 = null, pub fn sessionStart(timepoint: Timepoint, label: []const u8) Event { return .{ .kind = .session_start, .timepoint = timepoint, .label = label }; } pub fn sessionEnd(timepoint: Timepoint, status: i64) Event { return .{ .kind = .session_end, .timepoint = timepoint, .status = status }; } pub fn functionEnter(timepoint: Timepoint, safepoint: Safepoint) Event { return functionEvent(.function_enter, timepoint, safepoint); } pub fn functionExit(timepoint: Timepoint, safepoint: Safepoint) Event { return functionEvent(.function_exit, timepoint, safepoint); } pub fn safepointReached(timepoint: Timepoint, safepoint: Safepoint) Event { return functionEvent(.safepoint, timepoint, safepoint); } pub fn boundaryBytes(timepoint: Timepoint, operation: []const u8, bytes: []const u8) Event { return .{ .kind = .boundary, .timepoint = timepoint, .operation = operation, .data = bytes, }; } pub fn allocation( timepoint: Timepoint, object_id: u64, size: u64, alignment: u32, label: ?[]const u8, ) Event { return .{ .kind = .allocation, .timepoint = timepoint, .object_id = object_id, .size = size, .alignment = alignment, .label = label, }; } pub fn free(timepoint: Timepoint, object_id: u64) Event { return .{ .kind = .free, .timepoint = timepoint, .object_id = object_id }; } pub fn checkpoint(timepoint: Timepoint, label: []const u8, bytes: []const u8) Event { return .{ .kind = .checkpoint, .timepoint = timepoint, .label = label, .data = bytes, }; } pub fn checkpointRestore(timepoint: Timepoint, label: []const u8) Event { return .{ .kind = .checkpoint_restore, .timepoint = timepoint, .label = label }; } pub fn user(timepoint: Timepoint, label: []const u8, bytes: []const u8) Event { return .{ .kind = .user, .timepoint = timepoint, .label = label, .data = bytes, }; } pub fn cloneAlloc(self: Event, allocator: Allocator) !Event { var cloned = self; cloned.operation = if (self.operation) |operation| try allocator.dupe(u8, operation) else null; errdefer if (cloned.operation) |operation| allocator.free(operation); cloned.label = if (self.label) |label| try allocator.dupe(u8, label) else null; errdefer if (cloned.label) |label| allocator.free(label); cloned.data = if (self.data) |data| try allocator.dupe(u8, data) else null; errdefer if (cloned.data) |data| allocator.free(data); return cloned; } pub fn deinit(self: *Event, allocator: Allocator) void { if (self.operation) |operation| allocator.free(operation); if (self.label) |label| allocator.free(label); if (self.data) |data| allocator.free(data); self.* = undefined; } pub fn eqlForReplay(left: Event, right: Event) bool { return left.kind == right.kind and Timepoint.eql(left.timepoint, right.timepoint) and left.function_id == right.function_id and left.site_id == right.site_id and left.stack_map_id == right.stack_map_id and left.object_id == right.object_id and left.size == right.size and left.alignment == right.alignment and left.status == right.status and optionalBytesEql(left.operation, right.operation) and optionalBytesEql(left.label, right.label) and optionalBytesEql(left.data, right.data); } pub fn writeJsonLine(self: Event, writer: *std.Io.Writer) !void { var stream = pretty_json.Writer.init(writer, .minified); const object = try stream.object(); try object.field("v", trace_format_version); const timepoint = try object.object("timepoint"); try timepoint.field("epoch", self.timepoint.epoch); try timepoint.field("thread", self.timepoint.thread_id); try timepoint.field("seq", self.timepoint.seq); try timepoint.end(); try object.field("kind", self.kind.tag()); try object.field("function_id", self.function_id); try object.field("site_id", self.site_id); try object.field("stack_map_id", self.stack_map_id); try object.field("object_id", self.object_id); try object.field("size", self.size); try object.field("alignment", self.alignment); try object.field("status", self.status); if (self.operation) |operation| try object.field("operation", operation); if (self.label) |label| try object.field("label", label); if (self.data) |data| try object.hexString("data_hex", data); try object.endLine(); } pub fn fromJsonLine(allocator: Allocator, line: []const u8) !Event { const parsed = std.json.parseFromSlice(std.json.Value, allocator, line, .{}) catch return error.InvalidEventJson; defer parsed.deinit(); const object = switch (parsed.value) { .object => |object| object, else => return error.InvalidEventJson, }; const version = try jsonU64(object.get("v") orelse return error.InvalidEventJson); if (version != trace_format_version) return error.UnsupportedTraceVersion; const timepoint_object = switch (object.get("timepoint") orelse return error.InvalidEventJson) { .object => |value| value, else => return error.InvalidEventJson, }; const kind_text = try jsonString(object.get("kind") orelse return error.InvalidEventJson); const kind = EventKind.fromTag(kind_text) orelse return error.InvalidEventJson; var event = Event{ .kind = kind, .timepoint = .{ .epoch = try jsonU64(timepoint_object.get("epoch") orelse return error.InvalidEventJson), .thread_id = @intCast(try jsonU64(timepoint_object.get("thread") orelse return error.InvalidEventJson)), .seq = try jsonU64(timepoint_object.get("seq") orelse return error.InvalidEventJson), }, .function_id = try jsonU64(object.get("function_id") orelse return error.InvalidEventJson), .site_id = try jsonU64(object.get("site_id") orelse return error.InvalidEventJson), .stack_map_id = try jsonU64(object.get("stack_map_id") orelse return error.InvalidEventJson), .object_id = try jsonU64(object.get("object_id") orelse return error.InvalidEventJson), .size = try jsonU64(object.get("size") orelse return error.InvalidEventJson), .alignment = @intCast(try jsonU64(object.get("alignment") orelse return error.InvalidEventJson)), .status = try jsonI64(object.get("status") orelse return error.InvalidEventJson), }; errdefer event.deinit(allocator); if (object.get("operation")) |operation| { event.operation = try allocator.dupe(u8, try jsonString(operation)); } if (object.get("label")) |label| { event.label = try allocator.dupe(u8, try jsonString(label)); } if (object.get("data_hex")) |data_hex| { event.data = try decodeHexAlloc(allocator, try jsonString(data_hex)); } return event; }};pub const Sink = struct { context: *anyopaque, appendFn: *const fn (context: *anyopaque, item: Event) anyerror!void, pub fn append(self: Sink, item: Event) !void { try self.appendFn(self.context, item); }};pub const Source = struct { context: *anyopaque, peekFn: *const fn (context: *anyopaque) anyerror!?*const Event, advanceFn: *const fn (context: *anyopaque) void, countFn: *const fn (context: *anyopaque) u64, pub fn peek(self: Source) !?*const Event { return try self.peekFn(self.context); } pub fn advance(self: Source) void { self.advanceFn(self.context); } pub fn count(self: Source) u64 { return self.countFn(self.context); }};fn functionEvent(kind: EventKind, timepoint: Timepoint, safepoint: Safepoint) Event { return .{ .kind = kind, .timepoint = timepoint, .function_id = safepoint.function_id, .site_id = safepoint.site_id, .stack_map_id = safepoint.stack_map_id, };}fn optionalBytesEql(left: ?[]const u8, right: ?[]const u8) bool { if (left == null and right == null) return true; if (left == null or right == null) return false; return std.mem.eql(u8, left.?, right.?);}fn jsonString(value: std.json.Value) ![]const u8 { return switch (value) { .string => |text| text, else => error.InvalidEventJson, };}fn jsonU64(value: std.json.Value) !u64 { return switch (value) { .integer => |integer| if (integer < 0) error.InvalidEventJson else @intCast(integer), .number_string => |text| std.fmt.parseInt(u64, text, 10) catch error.InvalidEventJson, else => error.InvalidEventJson, };}fn jsonI64(value: std.json.Value) !i64 { return switch (value) { .integer => |integer| @intCast(integer), else => error.InvalidEventJson, };}fn decodeHexAlloc(allocator: Allocator, text: []const u8) ![]u8 { if (text.len % 2 != 0) return error.InvalidEventJson; const bytes = try allocator.alloc(u8, text.len / 2); errdefer allocator.free(bytes); for (bytes, 0..) |*byte, index| { const high = try hexNibble(text[index * 2]); const low = try hexNibble(text[index * 2 + 1]); byte.* = (high << 4) | low; } return bytes;}fn hexNibble(byte: u8) !u8 { return switch (byte) { '0'...'9' => byte - '0', 'a'...'f' => byte - 'a' + 10, 'A'...'F' => byte - 'A' + 10, else => error.InvalidEventJson, };}test "event json roundtrip preserves replay identity" { const allocator = std.testing.allocator; const original = Event.boundaryBytes( .{ .epoch = 7, .thread_id = 1, .seq = 42 }, "env.TEST", "value\nwith bytes", ); var out = std.Io.Writer.Allocating.init(allocator); defer out.deinit(); try original.writeJsonLine(&out.writer); const bytes = try out.toOwnedSlice(); defer allocator.free(bytes); var parsed = try Event.fromJsonLine(allocator, std.mem.trimEnd(u8, bytes, "\n")); defer parsed.deinit(allocator); try std.testing.expect(original.eqlForReplay(parsed));}test "timepoints sort by epoch then sequence" { try std.testing.expect((Timepoint{ .epoch = 0, .thread_id = 9, .seq = 10 }).beforeOrEqual(.{ .epoch = 0, .thread_id = 1, .seq = 11, })); try std.testing.expect(!(Timepoint{ .epoch = 1, .thread_id = 0, .seq = 0 }).beforeOrEqual(.{ .epoch = 0, .thread_id = 0, .seq = 100, }));}Source: lib/trace/src/root.zig:32
zig
pub const event = @import("event.zig");Audit
| Definitions | 2 |
|---|---|
| Public names | 2 |
| Members | 0 |
| Version | 26.7.0 |
| Revision | daab053ee433 |