lib/tracy/src/runtime.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

   1 const std = @import("std");
   2 const sys = @import("sys");
   3 const build_options = @import("build_options");
   4 const clock = @import("clock.zig");
   5 const event = @import("event.zig");
   6 
   7 pub const enabled = build_options.enabled;
   8 
   9 pub const StartOptions = struct {
  10     name: []const u8 = "tiny",
  11     detail: ?[]const u8 = null,
  12 };
  13 
  14 pub const PlotUnit = union(enum) {
  15     number,
  16     nanoseconds,
  17     bytes,
  18     count,
  19     percent,
  20     hertz,
  21     watts,
  22     custom: []const u8,
  23 
  24     pub fn tag(self: PlotUnit) []const u8 {
  25         return switch (self) {
  26             .number => "number",
  27             .nanoseconds => "nanoseconds",
  28             .bytes => "bytes",
  29             .count => "count",
  30             .percent => "percent",
  31             .hertz => "hertz",
  32             .watts => "watts",
  33             .custom => |text| text,
  34         };
  35     }
  36 };
  37 
  38 pub const PlotConfig = struct {
  39     unit: PlotUnit = .number,
  40     step: bool = false,
  41     fill: bool = false,
  42     color: ?u32 = null,
  43 };
  44 
  45 pub const Zone = struct {
  46     id: u64 = 0,
  47     active: bool = false,
  48 
  49     pub inline fn end(self: Zone) void {
  50         if (enabled and self.active) global.endZone(self.id);
  51     }
  52 
  53     pub inline fn setText(self: Zone, text: []const u8) void {
  54         if (enabled and self.active) global.zoneText(self.id, text);
  55     }
  56 
  57     pub inline fn setName(self: Zone, name: []const u8) void {
  58         if (enabled and self.active) global.zoneName(self.id, name);
  59     }
  60 
  61     pub inline fn setColor(self: Zone, color: u32) void {
  62         if (enabled and self.active) global.zoneColor(self.id, color);
  63     }
  64 
  65     pub inline fn setValue(self: Zone, value: u64) void {
  66         if (enabled and self.active) global.zoneValue(self.id, value);
  67     }
  68 };
  69 
  70 pub const LockKind = enum {
  71     lock,
  72     shared,
  73 
  74     fn tag(self: LockKind) []const u8 {
  75         return switch (self) {
  76             .lock => "lock",
  77             .shared => "shared",
  78         };
  79     }
  80 };
  81 
  82 pub const SampleKind = enum {
  83     manual,
  84     wall,
  85     cpu,
  86     context_switch,
  87     hardware,
  88 
  89     pub fn tag(self: SampleKind) []const u8 {
  90         return switch (self) {
  91             .manual => "manual",
  92             .wall => "wall",
  93             .cpu => "cpu",
  94             .context_switch => "context-switch",
  95             .hardware => "hardware",
  96         };
  97     }
  98 };
  99 
 100 pub const SampleFrame = struct {
 101     name: ?[]const u8 = null,
 102     function: ?[]const u8 = null,
 103     file: ?[]const u8 = null,
 104     line: u32 = 0,
 105     column: u32 = 0,
 106     address: u64 = 0,
 107 };
 108 
 109 pub const Fiber = struct {
 110     id: u64 = 0,
 111     name: ?[]const u8 = null,
 112     group_hint: i32 = 0,
 113     active: bool = false,
 114 
 115     pub inline fn enter(self: Fiber) void {
 116         if (enabled and self.active) global.enterFiber(self);
 117     }
 118 
 119     pub inline fn leave(self: Fiber) void {
 120         if (enabled and self.active) global.leaveFiber(self.id);
 121     }
 122 };
 123 
 124 pub const GpuContextType = enum {
 125     unknown,
 126     opengl,
 127     vulkan,
 128     direct3d11,
 129     direct3d12,
 130     metal,
 131     custom,
 132 
 133     pub fn tag(self: GpuContextType) ?[]const u8 {
 134         return switch (self) {
 135             .unknown => null,
 136             .opengl => "opengl",
 137             .vulkan => "vulkan",
 138             .direct3d11 => "direct3d11",
 139             .direct3d12 => "direct3d12",
 140             .metal => "metal",
 141             .custom => "custom",
 142         };
 143     }
 144 };
 145 
 146 pub const GpuContextOptions = struct {
 147     name: ?[]const u8 = null,
 148     context_type: GpuContextType = .unknown,
 149     period: f64 = 1.0,
 150     gpu_time: ?i64 = null,
 151     has_calibration: bool = false,
 152     serial: bool = false,
 153 };
 154 
 155 pub const GpuContext = struct {
 156     id: u32 = 0,
 157     active: bool = false,
 158 
 159     pub inline fn zone(self: GpuContext, comptime name: [:0]const u8) GpuZone {
 160         return self.zoneAt(name, @src());
 161     }
 162 
 163     pub inline fn zoneAt(self: GpuContext, comptime name: [:0]const u8, comptime src: std.builtin.SourceLocation) GpuZone {
 164         if (enabled and self.active) return global.beginGpuZone(self.id, name, src, null, 0);
 165         return .{};
 166     }
 167 
 168     pub inline fn zoneColor(self: GpuContext, comptime name: [:0]const u8, comptime color: u32) GpuZone {
 169         return self.zoneColorAt(name, color, @src());
 170     }
 171 
 172     pub inline fn zoneColorAt(self: GpuContext, comptime name: [:0]const u8, comptime color: u32, comptime src: std.builtin.SourceLocation) GpuZone {
 173         if (enabled and self.active) return global.beginGpuZone(self.id, name, src, color, 0);
 174         return .{};
 175     }
 176 
 177     pub inline fn zoneQuery(self: GpuContext, comptime name: [:0]const u8, query: u32) GpuZone {
 178         return self.zoneQueryAt(name, query, @src());
 179     }
 180 
 181     pub inline fn zoneQueryAt(self: GpuContext, comptime name: [:0]const u8, query: u32, comptime src: std.builtin.SourceLocation) GpuZone {
 182         if (enabled and self.active) return global.beginGpuZone(self.id, name, src, null, query);
 183         return .{};
 184     }
 185 
 186     pub inline fn zoneDynamic(self: GpuContext, name: []const u8) GpuZone {
 187         if (enabled and self.active) return global.beginGpuZoneDynamic(self.id, name, 0);
 188         return .{};
 189     }
 190 
 191     pub inline fn setName(self: GpuContext, name: []const u8) void {
 192         if (enabled and self.active) global.gpuContextName(self.id, name);
 193     }
 194 
 195     pub inline fn nextQuery(self: GpuContext) u32 {
 196         if (enabled and self.active) return global.nextGpuQuery();
 197         return 0;
 198     }
 199 
 200     pub inline fn time(self: GpuContext, query: u32, gpu_time: i64) void {
 201         if (enabled and self.active) global.gpuTime(self.id, query, gpu_time);
 202     }
 203 
 204     pub inline fn sync(self: GpuContext, gpu_time: i64) void {
 205         if (enabled and self.active) global.gpuTimeSync(self.id, gpu_time);
 206     }
 207 
 208     pub inline fn calibrate(self: GpuContext, gpu_time: i64, cpu_delta_ns: i64) void {
 209         if (enabled and self.active) global.gpuCalibration(self.id, gpu_time, cpu_delta_ns);
 210     }
 211 
 212     pub inline fn annotationName(self: GpuContext, id: i64, name: []const u8) void {
 213         if (enabled and self.active) global.gpuAnnotationName(self.id, id, name);
 214     }
 215 
 216     pub inline fn annotate(self: GpuContext, id: i64, value: f64) void {
 217         if (enabled and self.active) global.gpuAnnotation(self.id, 0, id, value);
 218     }
 219 };
 220 
 221 pub const GpuZone = struct {
 222     context: u32 = 0,
 223     begin_query: u32 = 0,
 224     active: bool = false,
 225 
 226     pub inline fn end(self: GpuZone) void {
 227         if (enabled and self.active) global.endGpuZone(self.context, 0, false);
 228     }
 229 
 230     pub inline fn endQuery(self: GpuZone, query: u32) void {
 231         if (enabled and self.active) global.endGpuZone(self.context, query, false);
 232     }
 233 
 234     pub inline fn endSerial(self: GpuZone) void {
 235         if (enabled and self.active) global.endGpuZone(self.context, 0, true);
 236     }
 237 
 238     pub inline fn endWithTimes(self: GpuZone, gpu_start: i64, gpu_end: i64) void {
 239         if (enabled and self.active) global.endGpuZoneWithTimes(self.context, self.begin_query, 0, gpu_start, gpu_end, false);
 240     }
 241 
 242     pub inline fn endQueryWithTimes(self: GpuZone, query: u32, gpu_start: i64, gpu_end: i64) void {
 243         if (enabled and self.active) global.endGpuZoneWithTimes(self.context, self.begin_query, query, gpu_start, gpu_end, false);
 244     }
 245 
 246     pub inline fn annotate(self: GpuZone, id: i64, value: f64) void {
 247         if (enabled and self.active) global.gpuAnnotation(self.context, self.begin_query, id, value);
 248     }
 249 };
 250 
 251 pub const HardwareSampleKind = enum {
 252     cpu_cycle,
 253     instruction_retired,
 254     cache_reference,
 255     cache_miss,
 256     branch_retired,
 257     branch_miss,
 258 
 259     pub fn tag(self: HardwareSampleKind) []const u8 {
 260         return switch (self) {
 261             .cpu_cycle => "cpu-cycle",
 262             .instruction_retired => "instruction-retired",
 263             .cache_reference => "cache-reference",
 264             .cache_miss => "cache-miss",
 265             .branch_retired => "branch-retired",
 266             .branch_miss => "branch-miss",
 267         };
 268     }
 269 };
 270 
 271 pub const CpuTopology = struct {
 272     cpu: u32,
 273     package: u32 = 0,
 274     die: u32 = 0,
 275     core: u32 = 0,
 276 };
 277 
 278 pub const ContextSwitchOptions = struct {
 279     cpu: ?u32 = null,
 280     prev_thread: u64 = 0,
 281     next_thread: u64 = 0,
 282     reason: ?[]const u8 = null,
 283     state: ?[]const u8 = null,
 284     previous_cstate: ?u32 = null,
 285     prev_priority: ?i64 = null,
 286     next_priority: ?i64 = null,
 287 };
 288 
 289 pub const ThreadWakeupOptions = struct {
 290     thread: u64,
 291     cpu: ?u32 = null,
 292     reason: ?[]const u8 = null,
 293     priority_increment: ?i64 = null,
 294 };
 295 
 296 pub const Lock = struct {
 297     id: u64 = 0,
 298     active: bool = false,
 299 
 300     pub inline fn terminate(self: Lock) void {
 301         if (enabled and self.active) global.lockEvent(self.id, .lock_terminate);
 302     }
 303 
 304     pub inline fn wait(self: Lock) void {
 305         if (enabled and self.active) global.lockEvent(self.id, .lock_wait);
 306     }
 307 
 308     pub inline fn obtain(self: Lock) void {
 309         if (enabled and self.active) global.lockEvent(self.id, .lock_obtain);
 310     }
 311 
 312     pub inline fn release(self: Lock) void {
 313         if (enabled and self.active) global.lockEvent(self.id, .lock_release);
 314     }
 315 
 316     pub inline fn sharedWait(self: Lock) void {
 317         if (enabled and self.active) global.lockEvent(self.id, .lock_shared_wait);
 318     }
 319 
 320     pub inline fn sharedObtain(self: Lock) void {
 321         if (enabled and self.active) global.lockEvent(self.id, .lock_shared_obtain);
 322     }
 323 
 324     pub inline fn sharedRelease(self: Lock) void {
 325         if (enabled and self.active) global.lockEvent(self.id, .lock_shared_release);
 326     }
 327 
 328     pub inline fn markAt(self: Lock, comptime src: std.builtin.SourceLocation) void {
 329         if (enabled and self.active) global.lockMark(self.id, src);
 330     }
 331 
 332     pub inline fn setName(self: Lock, name: []const u8) void {
 333         if (enabled and self.active) global.lockName(self.id, name);
 334     }
 335 };
 336 
 337 const State = struct {
 338     mutex: sys.thread.Mutex = .{},
 339     writer: ?*std.Io.Writer = null,
 340     active: bool = false,
 341     failed: bool = false,
 342     next_seq: u64 = 1,
 343     next_zone_id: u64 = 1,
 344     next_lock_id: u64 = 1,
 345     next_sample_id: u64 = 1,
 346     next_fiber_id: u64 = 1,
 347     next_gpu_context_id: u32 = 1,
 348     next_gpu_query_id: u32 = 1,
 349 
 350     fn start(self: *State, writer: *std.Io.Writer, options: StartOptions) !bool {
 351         self.lock();
 352         defer self.unlock();
 353         if (self.active) return error.TraceAlreadyStarted;
 354         current_fiber_id = 0;
 355         self.writer = writer;
 356         self.active = true;
 357         self.failed = false;
 358         self.next_seq = 1;
 359         self.next_zone_id = 1;
 360         self.next_lock_id = 1;
 361         self.next_sample_id = 1;
 362         self.next_fiber_id = 1;
 363         self.next_gpu_context_id = 1;
 364         self.next_gpu_query_id = 1;
 365         self.writePhysicalLocked(.{
 366             .kind = .start,
 367             .name = options.name,
 368             .text = options.detail,
 369         }) catch |err| {
 370             self.disableLocked();
 371             return err;
 372         };
 373         return true;
 374     }
 375 
 376     fn stop(self: *State) void {
 377         self.lock();
 378         defer self.unlock();
 379         if (!self.active) return;
 380         self.writePhysicalLocked(.{ .kind = .stop }) catch {};
 381         if (self.writer) |writer| writer.flush() catch {};
 382         self.writer = null;
 383         self.active = false;
 384         self.failed = false;
 385         current_fiber_id = 0;
 386     }
 387 
 388     fn isActive(self: *State) bool {
 389         self.lock();
 390         defer self.unlock();
 391         return self.active and !self.failed;
 392     }
 393 
 394     fn beginZone(
 395         self: *State,
 396         comptime name: [:0]const u8,
 397         comptime src: std.builtin.SourceLocation,
 398         color: ?u32,
 399     ) Zone {
 400         self.lock();
 401         defer self.unlock();
 402         if (!self.active or self.failed) return .{};
 403         const id = self.next_zone_id;
 404         self.next_zone_id +|= 1;
 405         self.writeLocked(.{
 406             .kind = .zone_begin,
 407             .id = id,
 408             .name = name,
 409             .function = src.fn_name,
 410             .file = src.file,
 411             .line = src.line,
 412             .column = src.column,
 413             .color = color,
 414         }) catch {
 415             self.disableLocked();
 416             return .{};
 417         };
 418         return .{ .id = id, .active = true };
 419     }
 420 
 421     fn announceLock(
 422         self: *State,
 423         comptime name: [:0]const u8,
 424         comptime src: std.builtin.SourceLocation,
 425         kind: LockKind,
 426     ) Lock {
 427         self.lock();
 428         defer self.unlock();
 429         if (!self.active or self.failed) return .{};
 430         const id = self.next_lock_id;
 431         self.next_lock_id +|= 1;
 432         self.writeLocked(.{
 433             .kind = .lock_announce,
 434             .id = id,
 435             .name = name,
 436             .function = src.fn_name,
 437             .file = src.file,
 438             .line = src.line,
 439             .column = src.column,
 440             .lock_kind = kind.tag(),
 441         }) catch {
 442             self.disableLocked();
 443             return .{};
 444         };
 445         return .{ .id = id, .active = true };
 446     }
 447 
 448     fn endZone(self: *State, id: u64) void {
 449         self.emit(.{ .kind = .zone_end, .id = id });
 450     }
 451 
 452     fn zoneText(self: *State, id: u64, text: []const u8) void {
 453         self.emit(.{ .kind = .zone_text, .id = id, .text = text });
 454     }
 455 
 456     fn zoneName(self: *State, id: u64, name: []const u8) void {
 457         self.emit(.{ .kind = .zone_name, .id = id, .name = name });
 458     }
 459 
 460     fn zoneColor(self: *State, id: u64, color: u32) void {
 461         self.emit(.{ .kind = .zone_color, .id = id, .color = color });
 462     }
 463 
 464     fn zoneValue(self: *State, id: u64, value: u64) void {
 465         self.emit(.{ .kind = .zone_value, .id = id, .value_u64 = value });
 466     }
 467 
 468     fn lockEvent(self: *State, id: u64, kind: event.Kind) void {
 469         self.emit(.{ .kind = kind, .id = id });
 470     }
 471 
 472     fn lockMark(self: *State, id: u64, comptime src: std.builtin.SourceLocation) void {
 473         self.emit(.{
 474             .kind = .lock_mark,
 475             .id = id,
 476             .function = src.fn_name,
 477             .file = src.file,
 478             .line = src.line,
 479             .column = src.column,
 480         });
 481     }
 482 
 483     fn lockName(self: *State, id: u64, name: []const u8) void {
 484         self.emit(.{ .kind = .lock_name, .id = id, .name = name });
 485     }
 486 
 487     fn createFiber(self: *State, name: []const u8, group_hint: i32) Fiber {
 488         self.lock();
 489         defer self.unlock();
 490         if (!self.active or self.failed) return .{};
 491         const id = self.next_fiber_id;
 492         self.next_fiber_id +|= 1;
 493         const fiber_handle = Fiber{
 494             .id = id,
 495             .name = name,
 496             .group_hint = group_hint,
 497             .active = true,
 498         };
 499         self.writePhysicalLocked(.{
 500             .kind = .fiber_name,
 501             .id = id,
 502             .name = name,
 503             .group_hint = group_hint,
 504         }) catch {
 505             self.disableLocked();
 506             return .{};
 507         };
 508         return fiber_handle;
 509     }
 510 
 511     fn enterFiber(self: *State, fiber_handle: Fiber) void {
 512         self.lock();
 513         defer self.unlock();
 514         if (!self.active or self.failed) return;
 515         self.writePhysicalLocked(.{
 516             .kind = .fiber_enter,
 517             .id = fiber_handle.id,
 518             .name = fiber_handle.name,
 519             .group_hint = fiber_handle.group_hint,
 520         }) catch {
 521             self.disableLocked();
 522             return;
 523         };
 524         current_fiber_id = fiber_handle.id;
 525     }
 526 
 527     fn leaveFiber(self: *State, id: u64) void {
 528         self.lock();
 529         defer self.unlock();
 530         if (!self.active or self.failed) return;
 531         const actual_id = if (id != 0) id else current_fiber_id;
 532         self.writePhysicalLocked(.{
 533             .kind = .fiber_leave,
 534             .id = actual_id,
 535         }) catch {
 536             self.disableLocked();
 537             return;
 538         };
 539         if (current_fiber_id == actual_id) current_fiber_id = 0;
 540     }
 541 
 542     fn createGpuContext(self: *State, options: GpuContextOptions) GpuContext {
 543         self.lock();
 544         defer self.unlock();
 545         if (!self.active or self.failed) return .{};
 546         const id = self.next_gpu_context_id;
 547         self.next_gpu_context_id +|= 1;
 548         self.writePhysicalLocked(.{
 549             .kind = .gpu_context,
 550             .name = options.name,
 551             .gpu_context = id,
 552             .gpu_time = options.gpu_time,
 553             .gpu_period = options.period,
 554             .gpu_context_type = options.context_type.tag(),
 555             .gpu_has_calibration = options.has_calibration,
 556             .gpu_serial = options.serial,
 557         }) catch {
 558             self.disableLocked();
 559             return .{};
 560         };
 561         return .{ .id = id, .active = true };
 562     }
 563 
 564     fn beginGpuZone(
 565         self: *State,
 566         context: u32,
 567         comptime name: [:0]const u8,
 568         comptime src: std.builtin.SourceLocation,
 569         color: ?u32,
 570         query: u32,
 571     ) GpuZone {
 572         self.lock();
 573         defer self.unlock();
 574         if (!self.active or self.failed) return .{};
 575         const actual_query = self.gpuQueryLocked(query);
 576         self.writePhysicalLocked(.{
 577             .kind = .gpu_zone_begin,
 578             .gpu_context = context,
 579             .gpu_query = actual_query,
 580             .name = name,
 581             .function = src.fn_name,
 582             .file = src.file,
 583             .line = src.line,
 584             .column = src.column,
 585             .color = color,
 586         }) catch {
 587             self.disableLocked();
 588             return .{};
 589         };
 590         return .{ .context = context, .begin_query = actual_query, .active = true };
 591     }
 592 
 593     fn beginGpuZoneDynamic(self: *State, context: u32, name: []const u8, query: u32) GpuZone {
 594         self.lock();
 595         defer self.unlock();
 596         if (!self.active or self.failed) return .{};
 597         const actual_query = self.gpuQueryLocked(query);
 598         self.writePhysicalLocked(.{
 599             .kind = .gpu_zone_begin,
 600             .gpu_context = context,
 601             .gpu_query = actual_query,
 602             .name = name,
 603         }) catch {
 604             self.disableLocked();
 605             return .{};
 606         };
 607         return .{ .context = context, .begin_query = actual_query, .active = true };
 608     }
 609 
 610     fn endGpuZone(self: *State, context: u32, query: u32, serial: bool) void {
 611         self.lock();
 612         defer self.unlock();
 613         if (!self.active or self.failed) return;
 614         const actual_query = self.gpuQueryLocked(query);
 615         self.writePhysicalLocked(.{
 616             .kind = .gpu_zone_end,
 617             .gpu_context = context,
 618             .gpu_query = actual_query,
 619             .gpu_serial = serial,
 620         }) catch self.disableLocked();
 621     }
 622 
 623     fn endGpuZoneWithTimes(self: *State, context: u32, begin_query: u32, end_query: u32, gpu_start: i64, gpu_end: i64, serial: bool) void {
 624         self.lock();
 625         defer self.unlock();
 626         if (!self.active or self.failed) return;
 627         const actual_end_query = self.gpuQueryLocked(end_query);
 628         const end_time = clock.nowNs();
 629         const thread = clock.threadId();
 630         self.writeAtLocked(.{
 631             .kind = .gpu_zone_end,
 632             .gpu_context = context,
 633             .gpu_query = actual_end_query,
 634             .gpu_serial = serial,
 635         }, end_time, thread) catch {
 636             self.disableLocked();
 637             return;
 638         };
 639         self.writeAtLocked(.{
 640             .kind = .gpu_time,
 641             .gpu_context = context,
 642             .gpu_query = begin_query,
 643             .gpu_time = gpu_start,
 644         }, end_time, thread) catch {
 645             self.disableLocked();
 646             return;
 647         };
 648         self.writeAtLocked(.{
 649             .kind = .gpu_time,
 650             .gpu_context = context,
 651             .gpu_query = actual_end_query,
 652             .gpu_time = gpu_end,
 653         }, end_time, thread) catch {
 654             self.disableLocked();
 655             return;
 656         };
 657     }
 658 
 659     fn gpuContextName(self: *State, context: u32, name: []const u8) void {
 660         self.emitPhysical(.{ .kind = .gpu_context_name, .gpu_context = context, .name = name });
 661     }
 662 
 663     fn gpuTime(self: *State, context: u32, query: u32, gpu_time: i64) void {
 664         self.emitPhysical(.{ .kind = .gpu_time, .gpu_context = context, .gpu_query = query, .gpu_time = gpu_time });
 665     }
 666 
 667     fn gpuTimeSync(self: *State, context: u32, gpu_time: i64) void {
 668         self.emitPhysical(.{ .kind = .gpu_time_sync, .gpu_context = context, .gpu_time = gpu_time });
 669     }
 670 
 671     fn gpuCalibration(self: *State, context: u32, gpu_time: i64, cpu_delta_ns: i64) void {
 672         self.emitPhysical(.{
 673             .kind = .gpu_calibration,
 674             .gpu_context = context,
 675             .gpu_time = gpu_time,
 676             .gpu_cpu_delta_ns = cpu_delta_ns,
 677         });
 678     }
 679 
 680     fn gpuAnnotationName(self: *State, context: u32, id: i64, name: []const u8) void {
 681         self.emitPhysical(.{
 682             .kind = .gpu_annotation_name,
 683             .gpu_context = context,
 684             .gpu_annotation_id = id,
 685             .name = name,
 686         });
 687     }
 688 
 689     fn gpuAnnotation(self: *State, context: u32, query: u32, id: i64, value: f64) void {
 690         if (!std.math.isFinite(value)) return;
 691         self.emitPhysical(.{
 692             .kind = .gpu_annotation,
 693             .gpu_context = context,
 694             .gpu_query = if (query == 0) null else query,
 695             .gpu_annotation_id = id,
 696             .value_f64 = value,
 697         });
 698     }
 699 
 700     fn nextGpuQuery(self: *State) u32 {
 701         self.lock();
 702         defer self.unlock();
 703         if (!self.active or self.failed) return 0;
 704         return self.gpuQueryLocked(0);
 705     }
 706 
 707     fn recordThreadContext(self: *State, thread: u64, name: ?[]const u8) void {
 708         self.emitForThread(thread, .{
 709             .kind = .thread_context,
 710             .name = name,
 711         });
 712     }
 713 
 714     fn recordThreadPid(self: *State, thread: u64, pid: u64) void {
 715         self.emitForThread(thread, .{
 716             .kind = .thread_pid,
 717             .pid = pid,
 718         });
 719     }
 720 
 721     fn recordCpuTopology(self: *State, topology: CpuTopology) void {
 722         self.emitPhysical(.{
 723             .kind = .cpu_topology,
 724             .cpu = topology.cpu,
 725             .cpu_package = topology.package,
 726             .cpu_die = topology.die,
 727             .cpu_core = topology.core,
 728         });
 729     }
 730 
 731     fn recordSystemTime(self: *State, value: f64) void {
 732         if (!std.math.isFinite(value)) return;
 733         self.emitPhysical(.{
 734             .kind = .sys_time,
 735             .value_f64 = value,
 736         });
 737     }
 738 
 739     fn recordSystemPower(self: *State, name: []const u8, delta_uj: u64) void {
 740         self.emitPhysical(.{
 741             .kind = .sys_power,
 742             .name = name,
 743             .power_delta_uj = delta_uj,
 744         });
 745     }
 746 
 747     fn recordHardwareSample(self: *State, kind: HardwareSampleKind, address: u64) void {
 748         self.emitPhysical(.{
 749             .kind = .hw_sample,
 750             .address = address,
 751             .hw_sample_kind = kind.tag(),
 752         });
 753     }
 754 
 755     fn recordSampleAt(
 756         self: *State,
 757         comptime name: [:0]const u8,
 758         kind: SampleKind,
 759         comptime src: std.builtin.SourceLocation,
 760         weight: u64,
 761     ) void {
 762         self.lock();
 763         defer self.unlock();
 764         if (!self.active or self.failed) return;
 765         const id = self.next_sample_id;
 766         self.next_sample_id +|= 1;
 767         const time_ns = clock.nowNs();
 768         const thread = clock.threadId();
 769         self.writeAtLocked(.{
 770             .kind = .sample,
 771             .id = id,
 772             .name = name,
 773             .function = src.fn_name,
 774             .file = src.file,
 775             .line = src.line,
 776             .column = src.column,
 777             .value_u64 = sampleWeight(weight),
 778             .sample_kind = kind.tag(),
 779         }, time_ns, thread) catch {
 780             self.disableLocked();
 781             return;
 782         };
 783         self.writeAtLocked(.{
 784             .kind = .sample_frame,
 785             .id = id,
 786             .name = name,
 787             .function = src.fn_name,
 788             .file = src.file,
 789             .line = src.line,
 790             .column = src.column,
 791             .sample_kind = kind.tag(),
 792         }, time_ns, thread) catch {
 793             self.disableLocked();
 794             return;
 795         };
 796     }
 797 
 798     fn recordSampleStack(self: *State, name: []const u8, kind: SampleKind, frames: []const SampleFrame, weight: u64) void {
 799         self.lock();
 800         defer self.unlock();
 801         if (!self.active or self.failed) return;
 802         const id = self.next_sample_id;
 803         self.next_sample_id +|= 1;
 804         const time_ns = clock.nowNs();
 805         const thread = clock.threadId();
 806         const leaf: ?SampleFrame = if (frames.len == 0) null else frames[frames.len - 1];
 807         self.writeAtLocked(.{
 808             .kind = .sample,
 809             .id = id,
 810             .name = name,
 811             .function = if (leaf) |frame_info| frame_info.function else null,
 812             .file = if (leaf) |frame_info| frame_info.file else null,
 813             .line = if (leaf) |frame_info| frame_info.line else 0,
 814             .column = if (leaf) |frame_info| frame_info.column else 0,
 815             .address = if (leaf) |frame_info| frame_info.address else 0,
 816             .value_u64 = sampleWeight(weight),
 817             .sample_kind = kind.tag(),
 818         }, time_ns, thread) catch {
 819             self.disableLocked();
 820             return;
 821         };
 822         for (frames, 0..) |frame_info, index| {
 823             self.writeAtLocked(.{
 824                 .kind = .sample_frame,
 825                 .id = id,
 826                 .depth = @intCast(index),
 827                 .name = frame_info.name,
 828                 .function = frame_info.function,
 829                 .file = frame_info.file,
 830                 .line = frame_info.line,
 831                 .column = frame_info.column,
 832                 .address = frame_info.address,
 833                 .sample_kind = kind.tag(),
 834             }, time_ns, thread) catch {
 835                 self.disableLocked();
 836                 return;
 837             };
 838         }
 839     }
 840 
 841     fn recordContextSwitch(self: *State, options: ContextSwitchOptions) void {
 842         self.emit(.{
 843             .kind = .context_switch,
 844             .cpu = options.cpu,
 845             .prev_thread = options.prev_thread,
 846             .next_thread = options.next_thread,
 847             .context_reason = options.reason,
 848             .context_state = options.state,
 849             .previous_cstate = options.previous_cstate,
 850             .prev_priority = options.prev_priority,
 851             .next_priority = options.next_priority,
 852         });
 853     }
 854 
 855     fn recordThreadWakeup(self: *State, options: ThreadWakeupOptions) void {
 856         self.emit(.{
 857             .kind = .thread_wakeup,
 858             .target_thread = options.thread,
 859             .cpu = options.cpu,
 860             .context_reason = options.reason,
 861             .value_i64 = options.priority_increment,
 862         });
 863     }
 864 
 865     fn emit(self: *State, trace_event: event.TraceEvent) void {
 866         self.lock();
 867         defer self.unlock();
 868         if (!self.active or self.failed) return;
 869         self.writeLocked(trace_event) catch self.disableLocked();
 870     }
 871 
 872     fn emitPhysical(self: *State, trace_event: event.TraceEvent) void {
 873         self.lock();
 874         defer self.unlock();
 875         if (!self.active or self.failed) return;
 876         self.writePhysicalLocked(trace_event) catch self.disableLocked();
 877     }
 878 
 879     fn emitForThread(self: *State, thread: u64, trace_event: event.TraceEvent) void {
 880         self.lock();
 881         defer self.unlock();
 882         if (!self.active or self.failed) return;
 883         self.writeAtLocked(trace_event, clock.nowNs(), thread) catch self.disableLocked();
 884     }
 885 
 886     fn gpuQueryLocked(self: *State, query: u32) u32 {
 887         if (query != 0) return query;
 888         const actual = self.next_gpu_query_id;
 889         self.next_gpu_query_id +|= 1;
 890         return actual;
 891     }
 892 
 893     fn writeLocked(self: *State, trace_event: event.TraceEvent) !void {
 894         try self.writeAtLocked(trace_event, clock.nowNs(), logicalThreadId());
 895     }
 896 
 897     fn writePhysicalLocked(self: *State, trace_event: event.TraceEvent) !void {
 898         try self.writeAtLocked(trace_event, clock.nowNs(), clock.threadId());
 899     }
 900 
 901     fn writeAtLocked(self: *State, trace_event: event.TraceEvent, time_ns: u64, thread: u64) !void {
 902         const writer = self.writer orelse return error.TraceNotStarted;
 903         var output = trace_event;
 904         output.seq = self.next_seq;
 905         output.time_ns = time_ns;
 906         output.thread = thread;
 907         self.next_seq +|= 1;
 908         try output.writeJsonLine(writer);
 909     }
 910 
 911     fn disableLocked(self: *State) void {
 912         self.failed = true;
 913         self.active = false;
 914         self.writer = null;
 915     }
 916 
 917     fn lock(self: *State) void {
 918         self.mutex.lock();
 919     }
 920 
 921     fn unlock(self: *State) void {
 922         self.mutex.unlock();
 923     }
 924 };
 925 
 926 var global: State = .{};
 927 threadlocal var current_fiber_id: u64 = 0;
 928 
 929 pub fn start(writer: *std.Io.Writer, options: StartOptions) !bool {
 930     if (!enabled) return false;
 931     return try global.start(writer, options);
 932 }
 933 
 934 pub fn stop() void {
 935     if (enabled) global.stop();
 936 }
 937 
 938 pub fn isActive() bool {
 939     if (!enabled) return false;
 940     return global.isActive();
 941 }
 942 
 943 pub inline fn zoneAt(
 944     comptime name: [:0]const u8,
 945     comptime src: std.builtin.SourceLocation,
 946     color: ?u32,
 947 ) Zone {
 948     if (!enabled) return .{};
 949     return global.beginZone(name, src, color);
 950 }
 951 
 952 pub inline fn lockableAt(
 953     comptime name: [:0]const u8,
 954     comptime src: std.builtin.SourceLocation,
 955 ) Lock {
 956     if (!enabled) return .{};
 957     return global.announceLock(name, src, .lock);
 958 }
 959 
 960 pub inline fn sharedLockableAt(
 961     comptime name: [:0]const u8,
 962     comptime src: std.builtin.SourceLocation,
 963 ) Lock {
 964     if (!enabled) return .{};
 965     return global.announceLock(name, src, .shared);
 966 }
 967 
 968 pub fn frame(name: ?[]const u8) void {
 969     if (!enabled) return;
 970     global.emit(.{ .kind = .frame, .name = name });
 971 }
 972 
 973 pub fn message(text: []const u8) void {
 974     if (!enabled) return;
 975     global.emit(.{ .kind = .message, .text = text });
 976 }
 977 
 978 pub fn appInfo(text: []const u8) void {
 979     if (!enabled) return;
 980     global.emit(.{ .kind = .app_info, .text = text });
 981 }
 982 
 983 pub fn plotConfig(name: []const u8, config: PlotConfig) void {
 984     if (!enabled) return;
 985     const unit = config.unit.tag();
 986     std.debug.assert(unit.len > 0);
 987     global.emit(.{
 988         .kind = .plot_config,
 989         .name = name,
 990         .color = config.color,
 991         .plot_unit = unit,
 992         .plot_step = config.step,
 993         .plot_fill = config.fill,
 994     });
 995 }
 996 
 997 pub fn plotFloat(name: []const u8, value: f64) void {
 998     if (!enabled or !std.math.isFinite(value)) return;
 999     global.emit(.{
1000         .kind = .plot,
1001         .name = name,
1002         .value_f64 = value,
1003         .plot_kind = "float",
1004     });
1005 }
1006 
1007 pub fn plotInt(name: []const u8, value: i64) void {
1008     if (!enabled) return;
1009     global.emit(.{
1010         .kind = .plot,
1011         .name = name,
1012         .value_i64 = value,
1013         .plot_kind = "int",
1014     });
1015 }
1016 
1017 pub fn alloc(ptr: ?*const anyopaque, size: usize) void {
1018     if (!enabled) return;
1019     allocNamed(ptr, size, null);
1020 }
1021 
1022 pub fn allocNamed(ptr: ?*const anyopaque, size: usize, name: ?[]const u8) void {
1023     if (!enabled) return;
1024     allocAddress(ptrAddress(ptr), size, name);
1025 }
1026 
1027 pub fn allocAddress(address: u64, size: usize, name: ?[]const u8) void {
1028     if (!enabled) return;
1029     global.emit(.{
1030         .kind = .alloc,
1031         .name = name,
1032         .address = address,
1033         .size = @intCast(size),
1034     });
1035 }
1036 
1037 pub fn free(ptr: ?*const anyopaque) void {
1038     if (!enabled) return;
1039     freeSizedNamed(ptr, 0, null);
1040 }
1041 
1042 pub fn freeNamed(ptr: ?*const anyopaque, name: ?[]const u8) void {
1043     if (!enabled) return;
1044     freeSizedNamed(ptr, 0, name);
1045 }
1046 
1047 pub fn freeSized(ptr: ?*const anyopaque, size: usize) void {
1048     if (!enabled) return;
1049     freeSizedNamed(ptr, size, null);
1050 }
1051 
1052 pub fn freeSizedNamed(ptr: ?*const anyopaque, size: usize, name: ?[]const u8) void {
1053     if (!enabled) return;
1054     freeAddress(ptrAddress(ptr), size, name);
1055 }
1056 
1057 pub fn freeAddress(address: u64, size: usize, name: ?[]const u8) void {
1058     if (!enabled) return;
1059     global.emit(.{
1060         .kind = .free,
1061         .name = name,
1062         .address = address,
1063         .size = @intCast(size),
1064     });
1065 }
1066 
1067 pub fn threadName(name: []const u8) void {
1068     if (!enabled) return;
1069     global.lock();
1070     defer global.unlock();
1071     if (!global.active or global.failed) return;
1072     global.writePhysicalLocked(.{ .kind = .thread_name, .name = name }) catch global.disableLocked();
1073 }
1074 
1075 pub fn fiber(name: []const u8, group_hint: i32) Fiber {
1076     if (!enabled) return .{};
1077     return global.createFiber(name, group_hint);
1078 }
1079 
1080 pub fn gpuContext(options: GpuContextOptions) GpuContext {
1081     if (!enabled) return .{};
1082     return global.createGpuContext(options);
1083 }
1084 
1085 fn ptrAddress(ptr: ?*const anyopaque) u64 {
1086     const actual = ptr orelse return 0;
1087     return @intCast(@intFromPtr(actual));
1088 }
1089 
1090 fn sampleWeight(weight: u64) ?u64 {
1091     if (weight == 1) return null;
1092     return weight;
1093 }
1094 
1095 pub inline fn sampleAt(
1096     comptime name: [:0]const u8,
1097     kind: SampleKind,
1098     comptime src: std.builtin.SourceLocation,
1099 ) void {
1100     if (!enabled) return;
1101     global.recordSampleAt(name, kind, src, 1);
1102 }
1103 
1104 pub inline fn sampleWeightedAt(
1105     comptime name: [:0]const u8,
1106     kind: SampleKind,
1107     weight: u64,
1108     comptime src: std.builtin.SourceLocation,
1109 ) void {
1110     if (!enabled) return;
1111     global.recordSampleAt(name, kind, src, weight);
1112 }
1113 
1114 pub fn sampleStack(name: []const u8, kind: SampleKind, frames: []const SampleFrame) void {
1115     if (!enabled) return;
1116     global.recordSampleStack(name, kind, frames, 1);
1117 }
1118 
1119 pub fn sampleStackWeighted(name: []const u8, kind: SampleKind, frames: []const SampleFrame, weight: u64) void {
1120     if (!enabled) return;
1121     global.recordSampleStack(name, kind, frames, weight);
1122 }
1123 
1124 pub fn contextSwitch(options: ContextSwitchOptions) void {
1125     if (!enabled) return;
1126     global.recordContextSwitch(options);
1127 }
1128 
1129 pub fn threadWakeup(options: ThreadWakeupOptions) void {
1130     if (!enabled) return;
1131     global.recordThreadWakeup(options);
1132 }
1133 
1134 pub fn threadContext(thread: u64, name: ?[]const u8) void {
1135     if (!enabled) return;
1136     global.recordThreadContext(thread, name);
1137 }
1138 
1139 pub fn threadPid(thread: u64, pid: u64) void {
1140     if (!enabled) return;
1141     global.recordThreadPid(thread, pid);
1142 }
1143 
1144 pub fn cpuTopology(topology: CpuTopology) void {
1145     if (!enabled) return;
1146     global.recordCpuTopology(topology);
1147 }
1148 
1149 pub fn systemTime(value: f64) void {
1150     if (!enabled) return;
1151     global.recordSystemTime(value);
1152 }
1153 
1154 pub fn systemPower(name: []const u8, delta_uj: u64) void {
1155     if (!enabled) return;
1156     global.recordSystemPower(name, delta_uj);
1157 }
1158 
1159 pub fn hardwareSample(kind: HardwareSampleKind, address: u64) void {
1160     if (!enabled) return;
1161     global.recordHardwareSample(kind, address);
1162 }
1163 
1164 fn logicalThreadId() u64 {
1165     if (current_fiber_id != 0) return current_fiber_id;
1166     return clock.threadId();
1167 }
1168 
1169 test "disabled runtime start is inert" {
1170     if (enabled) return;
1171     var out = std.Io.Writer.Allocating.init(std.testing.allocator);
1172     defer out.deinit();
1173     try std.testing.expect(!(try start(&out.writer, .{})));
1174     try std.testing.expectEqualStrings("", out.written());
1175     try std.testing.expect(!isActive());
1176 }