lib/tracy/src/cli/usage.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const pretty = @import("pretty");
  3 const pretty_usage = @import("pretty_usage");
  4 
  5 const usage_commands = [_][]const u8{
  6     "tracy-agent summary <events.jsonl> [--format text|jsonl] [--top N]",
  7     "tracy-agent tree <events.jsonl> [--format text|jsonl] [--top N] [--max-depth N] [--min-duration-ns N] [--thread ID]",
  8     "tracy-agent stats <events.jsonl> [--format text|jsonl] [--top N] [--sort self|total|mean|count|threads|name] [--min-total-ns N] [--min-self-ns N] [--thread ID]",
  9     "tracy-agent frames <events.jsonl> [--format text|jsonl] [--top N]" ++
 10         " [--sort max|total|mean|count|name] [--min-frame-ns N]" ++
 11         " [--budget-ns N] [--set NAME]",
 12     "tracy-agent memory <events.jsonl> [--format text|jsonl] [--top N]" ++
 13         " [--occurrences N] [--sort live|high-water|allocated|lifetime-tail|allocs" ++
 14         "|frees|name] [--min-live-bytes N] [--name NAME]",
 15     "tracy-agent locks <events.jsonl> [--format text|jsonl] [--top N]" ++
 16         " [--occurrences N] [--sort wait|wait-tail|hold|hold-tail|contention" ++
 17         "|count|name|last] [--kind all|wait|hold] [--mode all|lock|shared]" ++
 18         " [--min-wait-ns N] [--min-hold-ns N] [--lock ID] [--thread ID]" ++
 19         " [--since-ns N] [--until-ns N] [--around-ns N] [--window-ns N]" ++
 20         " [--match TEXT] [--ignore-case]",
 21     "tracy-agent messages <events.jsonl> [--format text|jsonl] [--top N] [--occurrences N] [--kind all|message|app-info] [--group thread|text|kind|none] [--sort time|count|last|thread|text|kind] [--thread ID] [--since-ns N] [--until-ns N] [--around-ns N] [--window-ns N] [--match TEXT] [--ignore-case]",
 22     "tracy-agent samples <events.jsonl> [--format text|jsonl] [--top N] [--occurrences N] [--group source|symbol|stack|thread|kind|none] [--sort samples|weight|last|first|thread|name|kind] [--kind TEXT] [--min-weight N] [--thread ID] [--since-ns N] [--until-ns N] [--around-ns N] [--window-ns N] [--match TEXT] [--ignore-case]",
 23     "tracy-agent fibers <events.jsonl> [--format text|jsonl] [--top N] [--occurrences N] [--group fiber|thread|transition|none] [--sort running|enters|leaves|migrations|unmatched|last|fiber|thread|label] [--fiber ID] [--thread ID] [--since-ns N] [--until-ns N] [--around-ns N] [--window-ns N] [--match TEXT] [--ignore-case]",
 24     "tracy-agent gpu <events.jsonl> [--format text|jsonl] [--top N]" ++
 25         " [--occurrences N] [--group context|name|thread|annotation|none]" ++
 26         " [--sort gpu|gpu-tail|cpu|cpu-tail|count|annotations|last|context|thread|label]" ++
 27         " [--context ID] [--thread ID] [--since-ns N] [--until-ns N]" ++
 28         " [--around-ns N] [--window-ns N] [--min-gpu-ns N] [--match TEXT]" ++
 29         " [--ignore-case]",
 30     "tracy-agent context <events.jsonl> [--format text|jsonl] [--top N]" ++
 31         " [--occurrences N] [--group thread|cpu|reason|state|transition|none]" ++
 32         " [--sort running|blocked|ready|latency|switches|wakeups|migrations|last" ++
 33         "|thread|cpu|label] [--thread ID] [--cpu ID] [--since-ns N]" ++
 34         " [--until-ns N] [--around-ns N] [--window-ns N] [--match TEXT]" ++
 35         " [--ignore-case]",
 36     "tracy-agent system <events.jsonl> [--format text|jsonl] [--top N] [--occurrences N] [--group kind|thread|cpu|address|topology|name|none] [--sort count|value|last|first|thread|cpu|address|label] [--thread ID] [--cpu ID] [--address ADDR] [--kind TEXT] [--since-ns N] [--until-ns N] [--around-ns N] [--window-ns N] [--match TEXT] [--ignore-case]",
 37     "tracy-agent plots <events.jsonl> [--format text|jsonl] [--top N] [--sort samples|max|min|mean|range|last|name] [--min-samples N] [--name NAME]",
 38     "tracy-agent source <events.jsonl> [--format text|jsonl] [--top N] [--sort self|total|mean|count|file|name] [--min-total-ns N] [--min-self-ns N] [--thread ID] [--match TEXT] [--root PATH] [--context N] [--context-back N] [--ignore-case]",
 39     "tracy-agent find <events.jsonl> PATTERN [--format text|jsonl] [--top N] [--occurrences N] [--group source|name|thread|parent|text|none] [--sort total|self|count|mean|max|name|time] [--thread ID] [--since-ns N] [--until-ns N] [--around-ns N] [--window-ns N] [--min-duration-ns N] [--ignore-case]",
 40     "tracy-agent flame <events.jsonl> [--format text|jsonl|folded] [--top N] [--sort time|name] [--max-depth N] [--min-ns N] [--thread ID] [--since-ns N] [--until-ns N] [--around-ns N] [--window-ns N] [--match TEXT]",
 41     "tracy-agent timeline <events.jsonl> [--format text|jsonl] [--top N] [--kind all|zone|message|app-info|frame|plot|alloc|free|thread|fiber|gpu|sample|context|lock|metadata] [--sort time|duration] [--thread ID] [--since-ns N] [--until-ns N] [--around-ns N] [--window-ns N] [--min-duration-ns N] [--match TEXT]",
 42     "tracy-agent compare BASELINE.jsonl RUN.jsonl [--format text|jsonl] [--top N]" ++
 43         " [--frame-budget-ns N] [--min-frame-delta-ns N]" ++
 44         " [--min-zone-delta-ns N] [--min-gpu-delta-ns N]" ++
 45         " [--min-memory-delta-bytes N] [--min-memory-lifetime-delta-ns N]" ++
 46         " [--include-stable]",
 47     "tracy-agent export zones|messages|plots|allocs <events.jsonl> [--output PATH]",
 48 };
 49 
 50 pub fn printError(allocator: std.mem.Allocator, file: std.Io.File, text: []const u8) !void {
 51     try pretty_usage.Terminal.init(allocator, file, .{}).writeErrorText("tracy-agent", text);
 52 }
 53 
 54 pub fn write(allocator: std.mem.Allocator, file: std.Io.File) !void {
 55     var arena_state = std.heap.ArenaAllocator.init(allocator);
 56     defer arena_state.deinit();
 57     const builder = pretty.Builder.init(arena_state.allocator());
 58     try pretty_usage.Terminal.init(allocator, file, .{}).writeDoc(try docAlloc(builder));
 59 }
 60 
 61 fn renderAlloc(allocator: std.mem.Allocator, options: pretty.LayoutOptions) ![]u8 {
 62     var arena_state = std.heap.ArenaAllocator.init(allocator);
 63     defer arena_state.deinit();
 64     const builder = pretty.Builder.init(arena_state.allocator());
 65     const doc = try docAlloc(builder);
 66     return try pretty.renderAlloc(allocator, doc, options);
 67 }
 68 
 69 fn docAlloc(builder: pretty.Builder) !pretty.Doc {
 70     var parts: std.ArrayListUnmanaged(pretty.Doc) = .empty;
 71     try parts.append(builder.allocator, try builder.styledText(.keyword, "usage"));
 72     try parts.append(builder.allocator, try builder.punct(":"));
 73     try parts.append(builder.allocator, pretty.hardline);
 74     for (usage_commands) |command| {
 75         try parts.append(builder.allocator, try builder.spaces(2));
 76         try parts.append(builder.allocator, try builder.nest(4, try builder.styledWords(.name, command)));
 77         try parts.append(builder.allocator, pretty.hardline);
 78     }
 79     return try builder.concat(parts.items);
 80 }
 81 
 82 test "usage renders through pretty plain and colored output" {
 83     const plain = try renderAlloc(std.testing.allocator, .{ .width = 88 });
 84     defer std.testing.allocator.free(plain);
 85     try std.testing.expect(std.mem.indexOf(u8, plain, "\x1b[") == null);
 86     try std.testing.expect(std.mem.indexOf(u8, plain, "usage:\n") != null);
 87     try std.testing.expect(std.mem.indexOf(u8, plain, "tracy-agent summary <events.jsonl>") != null);
 88     try std.testing.expect(std.mem.indexOf(u8, plain, "--budget-ns") != null);
 89     try std.testing.expect(std.mem.indexOf(u8, plain, "--min-gpu-delta-ns") != null);
 90     try std.testing.expect(std.mem.indexOf(u8, plain, "--frame-budget-ns") != null);
 91     try std.testing.expect(
 92         std.mem.indexOf(u8, plain, "--min-memory-lifetime-delta-ns") != null,
 93     );
 94     try std.testing.expect(std.mem.indexOf(u8, plain, "gpu|gpu-tail|cpu|cpu-tail") != null);
 95 
 96     const colored = try renderAlloc(std.testing.allocator, .{ .width = 88, .color = .ansi });
 97     defer std.testing.allocator.free(colored);
 98     try std.testing.expect(std.mem.indexOf(u8, colored, "\x1b[") != null);
 99 }
100 
101 test "usage wraps long tracy commands at terminal width" {
102     const rendered = try renderAlloc(std.testing.allocator, .{ .width = 48 });
103     defer std.testing.allocator.free(rendered);
104     try std.testing.expect(std.mem.indexOf(u8, rendered, "tracy-agent locks <events.jsonl> [--format\n") != null);
105 }
106 
107 test "errors render through pretty plain and colored output" {
108     const plain = try pretty_usage.renderErrorTextAlloc(
109         std.testing.allocator,
110         "tracy-agent",
111         "UnknownCommand",
112         .{ .layout = .{ .width = 88 } },
113     );
114     defer std.testing.allocator.free(plain);
115     try std.testing.expectEqualStrings("tracy-agent: UnknownCommand\n", plain);
116 
117     const colored = try pretty_usage.renderErrorTextAlloc(
118         std.testing.allocator,
119         "tracy-agent",
120         "UnknownCommand",
121         .{ .layout = .{ .width = 88, .color = .ansi } },
122     );
123     defer std.testing.allocator.free(colored);
124     try std.testing.expect(std.mem.indexOf(u8, colored, "\x1b[") != null);
125     try std.testing.expect(std.mem.indexOf(u8, colored, "UnknownCommand") != null);
126 }