From 4a770d37e256addade7391ff94a2f1ba2a6eb3e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bas=20du=20Pr=C3=A9?= Date: Wed, 26 Nov 2025 09:55:46 +0100 Subject: [PATCH 1/2] Updates example to build and support Windows --- example/src/main.zig | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/example/src/main.zig b/example/src/main.zig index fae5f4a..e4f75f9 100644 --- a/example/src/main.zig +++ b/example/src/main.zig @@ -1,5 +1,7 @@ const std = @import("std"); +const builtin = @import("builtin"); const tracy = @import("tracy"); +const windows = std.os.windows; var finalise_threads: std.Thread.ResetEvent = .{}; @@ -7,15 +9,24 @@ fn handleSigInt(_: c_int) callconv(.C) void { finalise_threads.set(); } +fn handleCtrlEvent(_: windows.DWORD) callconv(windows.WINAPI) windows.BOOL { + finalise_threads.set(); + return windows.TRUE; +} + pub fn main() !void { tracy.setThreadName("Main"); defer tracy.message("Graceful main thread exit"); - std.posix.sigaction(std.posix.SIG.INT, &.{ - .handler = .{ .handler = handleSigInt }, - .mask = std.posix.empty_sigset, - .flags = 0, - }, null); + if (builtin.os.tag == .windows) { + _ = windows.kernel32.SetConsoleCtrlHandler(handleCtrlEvent, windows.TRUE); + } else { + std.posix.sigaction(std.posix.SIG.INT, &.{ + .handler = .{ .handler = handleSigInt }, + .mask = std.posix.empty_sigset, + .flags = 0, + }, null); + } const other_thread = try std.Thread.spawn(.{}, otherThread, .{}); defer other_thread.join(); From 26260e60e009473c3e8057aedc3f06273e88cd22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bas=20du=20Pr=C3=A9?= Date: Wed, 26 Nov 2025 10:58:18 +0100 Subject: [PATCH 2/2] Implemented TracingMutex to follow locks in Tracy. --- build.zig.zon | 2 +- {example => examples/basic}/build.zig | 0 {example => examples/basic}/build.zig.zon | 2 +- {example => examples/basic}/src/main.zig | 0 examples/mutex/build.zig | 36 ++++++++ examples/mutex/build.zig.zon | 14 +++ examples/mutex/src/main.zig | 93 +++++++++++++++++++ src/tracy.zig | 106 +++++++++++++++++++++- 8 files changed, 248 insertions(+), 5 deletions(-) rename {example => examples/basic}/build.zig (100%) rename {example => examples/basic}/build.zig.zon (86%) rename {example => examples/basic}/src/main.zig (100%) create mode 100644 examples/mutex/build.zig create mode 100644 examples/mutex/build.zig.zon create mode 100644 examples/mutex/src/main.zig diff --git a/build.zig.zon b/build.zig.zon index 837e8d3..985d756 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -7,7 +7,7 @@ "build.zig", "build.zig.zon", "src", - "example", + "examples", "README.md", "LICENSE", }, diff --git a/example/build.zig b/examples/basic/build.zig similarity index 100% rename from example/build.zig rename to examples/basic/build.zig diff --git a/example/build.zig.zon b/examples/basic/build.zig.zon similarity index 86% rename from example/build.zig.zon rename to examples/basic/build.zig.zon index c5e0c54..1087a1a 100644 --- a/example/build.zig.zon +++ b/examples/basic/build.zig.zon @@ -9,6 +9,6 @@ "src", }, .dependencies = .{ - .tracy = .{ .path = ".." }, + .tracy = .{ .path = "../.." }, }, } diff --git a/example/src/main.zig b/examples/basic/src/main.zig similarity index 100% rename from example/src/main.zig rename to examples/basic/src/main.zig diff --git a/examples/mutex/build.zig b/examples/mutex/build.zig new file mode 100644 index 0000000..4233989 --- /dev/null +++ b/examples/mutex/build.zig @@ -0,0 +1,36 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + // Keep tracy enabled by default for the demo; real projects should opt-in explicitly. + const tracy_enable = b.option(bool, "tracy_enable", "Enable profiling") orelse true; + + const tracy = b.dependency("tracy", .{ + .target = target, + .optimize = optimize, + .tracy_enable = tracy_enable, + }); + + const exe = b.addExecutable(.{ + .name = "tracy-mutex-demo", + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + exe.root_module.addImport("tracy", tracy.module("tracy")); + exe.linkLibrary(tracy.artifact("tracy")); + exe.linkLibCpp(); + b.installArtifact(exe); + + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step("run", "Run the mutex demo"); + run_step.dependOn(&run_cmd.step); +} diff --git a/examples/mutex/build.zig.zon b/examples/mutex/build.zig.zon new file mode 100644 index 0000000..915f731 --- /dev/null +++ b/examples/mutex/build.zig.zon @@ -0,0 +1,14 @@ +.{ + .name = .zig_tracy_mutex_example, + .fingerprint = 0xd6986380c2c18822, + .version = "0.0.1", + .minimum_zig_version = "0.14.1", + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, + .dependencies = .{ + .tracy = .{ .path = "../.." }, + }, +} diff --git a/examples/mutex/src/main.zig b/examples/mutex/src/main.zig new file mode 100644 index 0000000..19a16e9 --- /dev/null +++ b/examples/mutex/src/main.zig @@ -0,0 +1,93 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const tracy = @import("tracy"); +const windows = std.os.windows; + +var finalise_threads: std.Thread.ResetEvent = .{}; +const worker_count = 6; + +fn handleSigInt(_: c_int) callconv(.C) void { + finalise_threads.set(); +} + +fn handleCtrlEvent(_: windows.DWORD) callconv(windows.WINAPI) windows.BOOL { + finalise_threads.set(); + return windows.TRUE; +} + +pub fn main() !void { + tracy.setThreadName("Main"); + defer tracy.message("Graceful main thread exit"); + + if (builtin.os.tag == .windows) { + _ = windows.kernel32.SetConsoleCtrlHandler(handleCtrlEvent, windows.TRUE); + } else { + std.posix.sigaction(std.posix.SIG.INT, &.{ + .handler = .{ .handler = handleSigInt }, + .mask = std.posix.empty_sigset, + .flags = 0, + }, null); + } + + var shared = SharedState.init(); + defer shared.deinit(); + + var threads: [worker_count]std.Thread = undefined; + var started: usize = 0; + errdefer { + finalise_threads.set(); + for (threads[0..started]) |thread| thread.join(); + } + + for (&threads, 0..) |*thread, idx| { + thread.* = try std.Thread.spawn(.{}, worker, .{ &finalise_threads, &shared, idx }); + started += 1; + } + + finalise_threads.wait(); + + for (threads[0..started]) |thread| thread.join(); +} + +const SharedState = struct { + mutex: tracy.TracingMutex, + counter: u64 = 0, + + fn init() SharedState { + return .{ + .mutex = tracy.TracingMutex.init(@src(), .{ + .name = "Traced mutex", + .color = 0xFF8844, + }), + }; + } + + fn deinit(self: *SharedState) void { + self.mutex.deinit(); + } + + fn blockingIncrement(self: *SharedState) u64 { + const zone = tracy.initZone(@src(), .{ .name = "Mutex critical section" }); + defer zone.deinit(); + + self.mutex.lock(@src()); + defer self.mutex.unlock(); + + self.counter += 1; + zone.value(self.counter); + // Hold the lock briefly to force contention to show up in Tracy. + std.time.sleep(50 * std.time.ns_per_ms); + return self.counter; + } +}; + +fn worker(finalise: *std.Thread.ResetEvent, shared: *SharedState, idx: usize) void { + tracy.setThreadName("Mutex worker"); + + while (!finalise.isSet()) { + _ = shared.blockingIncrement(); + + const pause_ns: u64 = (@as(u64, @intCast(idx)) + 1) * 200 * std.time.ns_per_ms; + std.time.sleep(pause_ns); + } +} diff --git a/src/tracy.zig b/src/tracy.zig index 227867c..08e919f 100644 --- a/src/tracy.zig +++ b/src/tracy.zig @@ -130,10 +130,12 @@ const ZoneContext = if (options.tracy_enable) extern struct { pub inline fn value(_: *const ZoneContext, _: u64) void {} }; -pub inline fn initZone(comptime src: std.builtin.SourceLocation, comptime opts: ZoneOptions) ZoneContext { - if (!options.tracy_enable) return .{}; - const active: c_int = @intFromBool(opts.active); +pub const SrcLocOptions = struct { + name: ?[]const u8 = null, + color: ?u32 = null, +}; +inline fn getSrcLoc(comptime src: std.builtin.SourceLocation, comptime opts: SrcLocOptions) type { const static = struct { var src_loc = c.___tracy_source_location_data{ .name = if (opts.name) |name| name.ptr else null, @@ -147,6 +149,18 @@ pub inline fn initZone(comptime src: std.builtin.SourceLocation, comptime opts: // src.line magically is not comptime https://github.com/ziglang/zig/pull/12016#issuecomment-1178092847 static.src_loc.line = src.line; + return static; +} + +pub inline fn initZone(comptime src: std.builtin.SourceLocation, comptime opts: ZoneOptions) ZoneContext { + if (!options.tracy_enable) return .{}; + const active: c_int = @intFromBool(opts.active); + + const static = getSrcLoc(src, .{ + .name = opts.name, + .color = opts.color, + }); + if (!options.tracy_no_callstack) { if (options.tracy_callstack) |depth| { return .{ @@ -374,3 +388,89 @@ pub const TracingAllocator = struct { self.parent_allocator.rawFree(buf, buf_align, ret_addr); } }; + +const TracingMutexImpl = struct { + mutex: std.Thread.Mutex, + tracy_lock_ctx: c.TracyCLockCtx, + + pub const TracingMutexOptions = struct { + name: ?[]const u8 = null, + color: ?u32 = null, + }; + + pub fn init(comptime src: std.builtin.SourceLocation, comptime opts: TracingMutexOptions) TracingMutexImpl { + const static = getSrcLoc(src, .{ + .name = opts.name, + .color = opts.color, + }); + + const m: TracingMutexImpl = .{ + .mutex = .{}, + .tracy_lock_ctx = c.___tracy_announce_lockable_ctx(&static.src_loc), + }; + + if (opts.name) |name| { + c.___tracy_custom_name_lockable_ctx(m.tracy_lock_ctx, name.ptr, name.len); + } + + return m; + } + + pub fn deinit(self: *TracingMutex) void { + c.___tracy_terminate_lockable_ctx(self.tracy_lock_ctx); + } + + pub fn lock(self: *TracingMutex, comptime src: std.builtin.SourceLocation) void { + _ = c.___tracy_before_lock_lockable_ctx(self.tracy_lock_ctx); + self.mutex.lock(); + c.___tracy_after_lock_lockable_ctx(self.tracy_lock_ctx); + + const static = getSrcLoc(src, .{}); + c.___tracy_mark_lockable_ctx(self.tracy_lock_ctx, &static.src_loc); + } + + pub fn tryLock(self: *TracingMutex, comptime src: std.builtin.SourceLocation) bool { + _ = c.___tracy_before_lock_lockable_ctx(self.tracy_lock_ctx); + const result = self.mutex.tryLock(); + c.___tracy_after_try_lock_lockable_ctx(self.tracy_lock_ctx, if (result) 1 else 0); + + const static = getSrcLoc(src, .{}); + c.___tracy_mark_lockable_ctx(self.tracy_lock_ctx, &static.src_loc); + + return result; + } + + pub fn unlock(self: *TracingMutex) void { + self.mutex.unlock(); + c.___tracy_after_unlock_lockable_ctx(self.tracy_lock_ctx); + } +}; + +pub const TracingMutex = if (options.tracy_enable) TracingMutexImpl else struct { + mutex: std.Thread.Mutex, + + pub inline fn init(comptime src: std.builtin.SourceLocation, comptime opts: TracingMutexImpl.TracingMutexOptions) TracingMutex { + _ = src; + _ = opts; + + return .{ + .mutex = .{}, + }; + } + + pub inline fn deinit(_: *TracingMutex) void {} + + pub inline fn lock(self: *TracingMutex, comptime src: std.builtin.SourceLocation) void { + _ = src; + self.mutex.lock(); + } + + pub inline fn tryLock(self: *TracingMutex, comptime src: std.builtin.SourceLocation) bool { + _ = src; + return self.mutex.tryLock(); + } + + pub inline fn unlock(self: *TracingMutex) void { + self.mutex.unlock(); + } +};