├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── build.zig ├── licenses.txt ├── src ├── chrome.zig ├── log.zig ├── main.zig ├── mod.zig ├── none.zig └── spall.zig └── zigmod.yml /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.zig text eol=lf 3 | zigmod.* text eol=lf 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .zig-cache 2 | zig-out 3 | .zigmod 4 | deps.zig 5 | files.zig 6 | zigmod.lock 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Meghan Denny 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zig-tracer 2 | 3 | ![loc](https://sloc.xyz/github/nektro/zig-tracer) 4 | [![license](https://img.shields.io/github/license/nektro/zig-tracer.svg)](https://github.com/nektro/zig-tracer/blob/master/LICENSE) 5 | [![nektro @ github sponsors](https://img.shields.io/badge/sponsors-nektro-purple?logo=github)](https://github.com/sponsors/nektro) 6 | [![Zig](https://img.shields.io/badge/Zig-0.14-f7a41d)](https://ziglang.org/) 7 | [![Zigmod](https://img.shields.io/badge/Zigmod-latest-f7a41d)](https://github.com/nektro/zigmod) 8 | 9 | Generic tracing library for Zig, supports multiple backends. 10 | 11 | ## Usage 12 | 13 | in your program: 14 | 15 | ```zig 16 | const std = @import("std"); 17 | const tracer = @import("tracer"); 18 | pub const build_options = @import("build_options"); 19 | 20 | pub const tracer_impl = tracer.spall; // see 'Backends' section below 21 | 22 | pub fn main() !void { 23 | try tracer.init(); 24 | defer tracer.deinit(); 25 | 26 | // main loop 27 | while (true) { 28 | try tracer.init_thread(); 29 | defer tracer.deinit_thread(); 30 | 31 | handler(); 32 | } 33 | } 34 | 35 | fn handler() void { 36 | const t = tracer.trace(@src()); 37 | defer t.end(); 38 | } 39 | ``` 40 | 41 | `@src()` values are sometimes absolute paths so backends may use this value to trim it to only log relative paths 42 | 43 | ```zig 44 | exe_options.addOption(usize, "src_file_trimlen", std.fs.path.dirname(std.fs.path.dirname(@src().file).?).?.len); 45 | ``` 46 | 47 | ## Backends 48 | 49 | - `none` this is the default and causes tracing calls to become a no-op so that `tracer` can be added to libraries transparently 50 | - `log` uses `std.log` to print on function entrance. 51 | - `chrome` writes a json file in the `chrome://tracing` format described [here](https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview) and [here](https://www.chromium.org/developers/how-tos/trace-event-profiling-tool/). 52 | - `spall` writes a binary file compatible with the [Spall](https://gravitymoth.com/spall/) profiler. 53 | - more? feel free to open an issue with requests! 54 | 55 | Any custom backend may also be used that defines the following functions: 56 | 57 | - `pub fn init() !void` 58 | - `pub fn deinit() void` 59 | - `pub fn init_thread(dir: std.fs.Dir) !void` 60 | - `pub fn deinit_thread() void` 61 | - `pub inline fn trace_begin(ctx: tracer.Ctx, comptime ifmt: []const u8, iargs: anytype) void` 62 | - `pub inline fn trace_end(ctx: tracer.Ctx) void` 63 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const deps = @import("./deps.zig"); 3 | 4 | pub fn build(b: *std.Build) void { 5 | const target = b.standardTargetOptions(.{}); 6 | const mode = b.option(std.builtin.Mode, "mode", "") orelse .Debug; 7 | const disable_llvm = b.option(bool, "disable_llvm", "use the non-llvm zig codegen") orelse false; 8 | 9 | const mod = b.addModule("tracer", .{ .root_source_file = b.path("src/mod.zig") }); 10 | 11 | addTest(b, target, mode, disable_llvm, mod, 0); 12 | addTest(b, target, mode, disable_llvm, mod, 1); 13 | addTest(b, target, mode, disable_llvm, mod, 2); 14 | addTest(b, target, mode, disable_llvm, mod, 3); 15 | 16 | const test_step = b.step("test", "Run all library tests"); 17 | test_step.dependOn(b.getInstallStep()); 18 | } 19 | 20 | fn addTest(b: *std.Build, target: std.Build.ResolvedTarget, mode: std.builtin.Mode, disable_llvm: bool, mod: *std.Build.Module, comptime backend: u8) void { 21 | _ = mod; 22 | const options = b.addOptions(); 23 | options.addOption(u8, "backend", backend); 24 | 25 | const exe = b.addExecutable(.{ 26 | .name = "test" ++ std.fmt.comptimePrint("{d}", .{backend}), 27 | .root_source_file = b.path("src/main.zig"), 28 | .target = target, 29 | .optimize = mode, 30 | }); 31 | deps.addAllTo(exe); 32 | exe.linkLibC(); 33 | exe.root_module.addImport("build_options", options.createModule()); 34 | exe.use_llvm = !disable_llvm; 35 | exe.use_lld = !disable_llvm; 36 | b.installArtifact(exe); 37 | } 38 | -------------------------------------------------------------------------------- /licenses.txt: -------------------------------------------------------------------------------- 1 | MIT: 2 | = https://spdx.org/licenses/MIT 3 | - This 4 | - git https://github.com/nektro/zig-extras 5 | -------------------------------------------------------------------------------- /src/chrome.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const tracer = @import("./mod.zig"); 3 | const alloc = std.heap.c_allocator; 4 | const log = std.log.scoped(.tracer); 5 | const root = @import("root"); 6 | 7 | var pid: std.os.linux.pid_t = undefined; 8 | threadlocal var tid: std.os.linux.pid_t = undefined; 9 | threadlocal var path: []const u8 = undefined; 10 | threadlocal var file: std.fs.File = undefined; 11 | threadlocal var buffered_writer: std.io.BufferedWriter(4096, std.fs.File.Writer) = undefined; 12 | 13 | pub fn init() !void { 14 | pid = std.os.linux.getpid(); 15 | } 16 | 17 | pub fn deinit() void { 18 | // 19 | } 20 | 21 | pub fn init_thread(dir: std.fs.Dir) !void { 22 | tid = std.os.linux.gettid(); 23 | 24 | path = try std.fmt.allocPrint(alloc, "trace.{d}.{d}.chrome.json", .{ pid, tid }); 25 | file = try dir.createFile(path, .{}); 26 | buffered_writer = std.io.bufferedWriter(file.writer()); 27 | 28 | try buffered_writer.writer().writeAll("[\n"); 29 | } 30 | 31 | pub fn deinit_thread() void { 32 | defer alloc.free(path); 33 | defer file.close(); 34 | 35 | buffered_writer.writer().writeAll("]\n") catch {}; 36 | buffered_writer.flush() catch {}; 37 | } 38 | 39 | pub inline fn trace_begin(ctx: tracer.Ctx, comptime ifmt: []const u8, iargs: anytype) void { 40 | buffered_writer.writer().print( 41 | \\{{"cat":"function", "name":"{s}:{d}:{d} ({s}) 42 | ++ ifmt ++ 43 | \\", "ph": "B", "pid": {d}, "tid": {d}, "ts": {d}}}, 44 | \\ 45 | , 46 | .{ 47 | ctx.src.file, 48 | ctx.src.line, 49 | ctx.src.column, 50 | ctx.src.fn_name, 51 | } ++ iargs ++ .{ 52 | pid, 53 | tid, 54 | std.time.microTimestamp(), 55 | }, 56 | ) catch {}; 57 | } 58 | 59 | pub inline fn trace_end(ctx: tracer.Ctx) void { 60 | _ = ctx; 61 | buffered_writer.writer().print( 62 | \\{{"cat":"function", "ph": "E", "pid": {d}, "tid": {d}, "ts": {d}}}, 63 | \\ 64 | , 65 | .{ 66 | pid, 67 | tid, 68 | std.time.microTimestamp(), 69 | }, 70 | ) catch {}; 71 | } 72 | -------------------------------------------------------------------------------- /src/log.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const tracer = @import("./mod.zig"); 3 | const log = std.log.scoped(.tracer); 4 | 5 | pub fn init() !void {} 6 | 7 | pub fn deinit() void {} 8 | 9 | pub fn init_thread(dir: std.fs.Dir) !void { 10 | _ = dir; 11 | } 12 | 13 | pub fn deinit_thread() void {} 14 | 15 | pub inline fn trace_begin(ctx: tracer.Ctx, comptime ifmt: []const u8, iargs: anytype) void { 16 | log.debug("{s}:{d}:{d} ({s})" ++ ifmt, .{ ctx.src.file, ctx.src.line, ctx.src.column, ctx.src.fn_name } ++ iargs); 17 | } 18 | 19 | pub inline fn trace_end(ctx: tracer.Ctx) void { 20 | _ = ctx; 21 | } 22 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const tracer = @import("tracer"); 3 | pub const build_options = @import("build_options"); 4 | 5 | pub const tracer_impl = switch (build_options.backend) { 6 | 0 => tracer.none, 7 | 1 => tracer.log, 8 | 2 => tracer.spall, 9 | 3 => tracer.chrome, 10 | else => unreachable, 11 | }; 12 | 13 | pub fn main() !void { 14 | try tracer.init(); 15 | defer tracer.deinit(); 16 | 17 | // main loop 18 | var go = false; 19 | _ = &go; 20 | while (go) { 21 | try tracer.init_thread(std.fs.cwd()); 22 | defer tracer.deinit_thread(); 23 | 24 | handler(); 25 | } 26 | } 27 | 28 | fn handler() void { 29 | const t = tracer.trace(@src(), "", .{}); 30 | defer t.end(); 31 | } 32 | -------------------------------------------------------------------------------- /src/mod.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const root = @import("root"); 3 | const extras = @import("extras"); 4 | const impl = extras.globalOption("tracer_impl", type) orelse none; 5 | 6 | threadlocal var started = false; 7 | 8 | pub const none = @import("./none.zig"); 9 | pub const log = @import("./log.zig"); 10 | pub const chrome = @import("./chrome.zig"); 11 | pub const spall = @import("./spall.zig"); 12 | 13 | pub fn init() !void { 14 | try impl.init(); 15 | } 16 | 17 | pub fn deinit() void { 18 | impl.deinit(); 19 | } 20 | 21 | pub fn init_thread(dir: ?std.fs.Dir) !void { 22 | try impl.init_thread(dir orelse std.fs.cwd()); 23 | started = true; 24 | } 25 | 26 | pub fn deinit_thread() void { 27 | impl.deinit_thread(); 28 | } 29 | 30 | pub inline fn trace(src: std.builtin.SourceLocation, comptime fmt: []const u8, args: anytype) Ctx { 31 | const ctx = Ctx{ 32 | .src = src, 33 | }; 34 | if (started) impl.trace_begin(ctx, fmt, args); 35 | return ctx; 36 | } 37 | 38 | pub const Ctx = struct { 39 | src: std.builtin.SourceLocation, 40 | 41 | pub inline fn end(self: Ctx) void { 42 | if (started) impl.trace_end(self); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /src/none.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const tracer = @import("./mod.zig"); 3 | const log = std.log.scoped(.tracer); 4 | 5 | pub fn init() !void {} 6 | 7 | pub fn deinit() void {} 8 | 9 | pub fn init_thread(dir: std.fs.Dir) !void { 10 | _ = dir; 11 | } 12 | 13 | pub fn deinit_thread() void {} 14 | 15 | pub inline fn trace_begin(ctx: tracer.Ctx, comptime ifmt: []const u8, iargs: anytype) void { 16 | _ = ctx; 17 | _ = ifmt; 18 | _ = iargs; 19 | } 20 | 21 | pub inline fn trace_end(ctx: tracer.Ctx) void { 22 | _ = ctx; 23 | } 24 | -------------------------------------------------------------------------------- /src/spall.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const tracer = @import("./mod.zig"); 3 | const alloc = std.heap.c_allocator; 4 | const log = std.log.scoped(.tracer); 5 | const root = @import("root"); 6 | 7 | var pid: std.os.linux.pid_t = undefined; 8 | threadlocal var tid: std.os.linux.pid_t = undefined; 9 | threadlocal var path: []const u8 = undefined; 10 | threadlocal var file: std.fs.File = undefined; 11 | threadlocal var buffered_writer: std.io.BufferedWriter(4096, std.fs.File.Writer) = undefined; 12 | 13 | pub fn init() !void { 14 | pid = std.os.linux.getpid(); 15 | } 16 | 17 | pub fn deinit() void { 18 | // 19 | } 20 | 21 | pub fn init_thread(dir: std.fs.Dir) !void { 22 | tid = std.os.linux.gettid(); 23 | 24 | path = try std.fmt.allocPrint(alloc, "{d}.{d}.spall", .{ pid, tid }); 25 | file = try dir.createFile(path, .{}); 26 | buffered_writer = std.io.bufferedWriter(file.writer()); 27 | 28 | try buffered_writer.writer().writeStruct(Header{}); 29 | } 30 | 31 | pub fn deinit_thread() void { 32 | defer alloc.free(path); 33 | defer file.close(); 34 | 35 | buffered_writer.flush() catch {}; 36 | } 37 | 38 | pub inline fn trace_begin(ctx: tracer.Ctx, comptime ifmt: []const u8, iargs: anytype) void { 39 | const fmt = "{s}:{d}:{d} ({s})" ++ ifmt; 40 | const args = .{ ctx.src.file, ctx.src.line, ctx.src.column, ctx.src.fn_name }; 41 | buffered_writer.writer().writeStruct(BeginEvent{ 42 | .pid = @intCast(pid), 43 | .tid = @intCast(tid), 44 | .time = @floatFromInt(std.time.microTimestamp()), 45 | .name_len = @truncate(std.fmt.count(fmt, args ++ iargs)), 46 | .args_len = 0, 47 | }) catch return; 48 | buffered_writer.writer().print(fmt, args ++ iargs) catch return; 49 | } 50 | 51 | pub inline fn trace_end(ctx: tracer.Ctx) void { 52 | _ = ctx; 53 | buffered_writer.writer().writeStruct(EndEvent{ 54 | .pid = @intCast(pid), 55 | .tid = @intCast(tid), 56 | .time = @floatFromInt(std.time.microTimestamp()), 57 | }) catch return; 58 | } 59 | 60 | // https://github.com/colrdavidson/spall-web/blob/1d4610a1fe9aaaf2e071327a1142a498f3436bdc/formats/spall/spall.odin 61 | // https://github.com/colrdavidson/spall-web/blob/1d4610a1fe9aaaf2e071327a1142a498f3436bdc/tools/json2bin/json2bin.odin 62 | // https://github.com/colrdavidson/spall-web/blob/1d4610a1fe9aaaf2e071327a1142a498f3436bdc/tools/upconvert/main.odin 63 | 64 | // package spall 65 | 66 | // MAGIC :: u64(0x0BADF00D) 67 | const magic: u64 = 0x0BADF00D; 68 | 69 | // V1_Header :: struct #packed { 70 | // magic: u64, 71 | // version: u64, 72 | // timestamp_unit: f64, 73 | // must_be_0: u64, 74 | // } 75 | const Header = extern struct { 76 | magic: u64 align(1) = magic, 77 | version: u64 align(1) = 1, 78 | timestamp_unit: f64 align(1) = 1.0, 79 | must_be_0: u64 align(1) = 0, 80 | }; 81 | 82 | // V1_Event_Type :: enum u8 { 83 | // Invalid = 0, 84 | // Custom_Data = 1, // Basic readers can skip this. 85 | // StreamOver = 2, 86 | // Begin = 3, 87 | // End = 4, 88 | // Instant = 5, 89 | // Overwrite_Timestamp = 6, // Retroactively change timestamp units - useful for incrementally improving RDTSC frequency. 90 | // } 91 | const EventType = enum(u8) { 92 | invalid = 0, 93 | custom_data = 1, 94 | stream_over = 2, 95 | begin = 3, 96 | end = 4, 97 | instant = 5, 98 | overwrite_timestamp = 6, 99 | }; 100 | 101 | // V1_Begin_Event :: struct #packed { 102 | // type: V1_Event_Type, 103 | // category: u8, 104 | // pid: u32, 105 | // tid: u32, 106 | // time: f64, 107 | // name_len: u8, 108 | // args_len: u8, 109 | // } 110 | const BeginEvent = extern struct { 111 | type: EventType align(1) = .begin, 112 | category: u8 align(1) = 0, 113 | pid: u32 align(1), 114 | tid: u32 align(1), 115 | time: f64 align(1), 116 | name_len: u8 align(1), 117 | args_len: u8 align(1), 118 | }; 119 | 120 | // V1_End_Event :: struct #packed { 121 | // type: V1_Event_Type, 122 | // pid: u32, 123 | // tid: u32, 124 | // time: f64, 125 | // } 126 | const EndEvent = extern struct { 127 | type: EventType align(1) = .end, 128 | pid: u32 align(1), 129 | tid: u32 align(1), 130 | time: f64 align(1), 131 | }; 132 | -------------------------------------------------------------------------------- /zigmod.yml: -------------------------------------------------------------------------------- 1 | id: ede2wygpe1iycfhdm8wzmbce7zxye49zo8xjzt09g132home 2 | name: tracer 3 | main: src/mod.zig 4 | license: MIT 5 | description: Generic tracing library for Zig, supports multiple backends. 6 | dependencies: 7 | - src: git https://github.com/nektro/zig-extras 8 | --------------------------------------------------------------------------------