lib/coz/src/point.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const abi = @import("abi.zig");
3
4 pub const ThroughputPoint = struct {
5 name: []const u8,
6 counter: abi.Counter = .{},
7
8 pub fn init(name: []const u8) ThroughputPoint {
9 return .{ .name = name };
10 }
11
12 pub fn save(self: *const ThroughputPoint) ThroughputSnapshot {
13 return .{
14 .origin = self,
15 .start_count = self.getCount(),
16 };
17 }
18
19 pub fn visit(self: *ThroughputPoint, visits: usize) void {
20 _ = @atomicRmw(usize, &self.counter.count, .Add, visits, .monotonic);
21 }
22
23 pub fn getCount(self: *const ThroughputPoint) usize {
24 return @atomicLoad(usize, &self.counter.count, .monotonic);
25 }
26
27 pub fn counterStruct(self: *ThroughputPoint) *abi.Counter {
28 return &self.counter;
29 }
30 };
31
32 pub const ThroughputSnapshot = struct {
33 origin: *const ThroughputPoint,
34 start_count: usize,
35
36 pub fn log(self: ThroughputSnapshot, writer: *std.Io.Writer) !void {
37 try writer.print("throughput-point\tname={s}\tdelta={d}\n", .{
38 self.getName(),
39 self.getDelta(),
40 });
41 }
42
43 pub fn getDelta(self: ThroughputSnapshot) usize {
44 return self.origin.getCount() -% self.start_count;
45 }
46
47 pub fn getName(self: ThroughputSnapshot) []const u8 {
48 return self.origin.name;
49 }
50 };
51
52 pub const LatencyPoint = struct {
53 name: []const u8,
54 begin_counter: abi.Counter = .{},
55 end_counter: abi.Counter = .{},
56
57 pub fn init(name: []const u8) LatencyPoint {
58 return .{ .name = name };
59 }
60
61 pub fn save(self: *const LatencyPoint) LatencySnapshot {
62 return .{
63 .origin = self,
64 .begin_start_count = self.getBeginCount(),
65 .end_start_count = self.getEndCount(),
66 };
67 }
68
69 pub fn visitBegin(self: *LatencyPoint, visits: usize) void {
70 _ = @atomicRmw(usize, &self.begin_counter.count, .Add, visits, .monotonic);
71 }
72
73 pub fn visitEnd(self: *LatencyPoint, visits: usize) void {
74 _ = @atomicRmw(usize, &self.end_counter.count, .Add, visits, .monotonic);
75 }
76
77 pub fn getBeginCount(self: *const LatencyPoint) usize {
78 return @atomicLoad(usize, &self.begin_counter.count, .monotonic);
79 }
80
81 pub fn getEndCount(self: *const LatencyPoint) usize {
82 return @atomicLoad(usize, &self.end_counter.count, .monotonic);
83 }
84
85 pub fn beginCounterStruct(self: *LatencyPoint) *abi.Counter {
86 return &self.begin_counter;
87 }
88
89 pub fn endCounterStruct(self: *LatencyPoint) *abi.Counter {
90 return &self.end_counter;
91 }
92 };
93
94 pub const LatencySnapshot = struct {
95 origin: *const LatencyPoint,
96 begin_start_count: usize,
97 end_start_count: usize,
98
99 pub fn log(self: LatencySnapshot, writer: *std.Io.Writer) !void {
100 try writer.print("latency-point\tname={s}\tarrivals={d}\tdepartures={d}\tdifference={d}\n", .{
101 self.getName(),
102 self.getBeginDelta(),
103 self.getEndDelta(),
104 self.getDifference(),
105 });
106 }
107
108 pub fn getBeginDelta(self: LatencySnapshot) usize {
109 return self.origin.getBeginCount() -% self.begin_start_count;
110 }
111
112 pub fn getEndDelta(self: LatencySnapshot) usize {
113 return self.origin.getEndCount() -% self.end_start_count;
114 }
115
116 pub fn getDifference(self: LatencySnapshot) usize {
117 return self.origin.getBeginCount() -% self.origin.getEndCount();
118 }
119
120 pub fn getName(self: LatencySnapshot) []const u8 {
121 return self.origin.name;
122 }
123 };
124
125 test "throughput point accumulates visits and exposes counter storage" {
126 var point = ThroughputPoint.init("items");
127
128 point.visit(1);
129 point.visit(4);
130
131 try std.testing.expectEqual(@as(usize, 5), point.getCount());
132 try std.testing.expectEqualStrings("items", point.name);
133 try std.testing.expectEqual(@as(usize, 0), point.counterStruct().backoff);
134 }
135
136 test "throughput snapshot reports delta since save" {
137 var point = ThroughputPoint.init("items");
138 point.visit(2);
139 const snapshot = point.save();
140
141 point.visit(3);
142
143 try std.testing.expectEqual(@as(usize, 3), snapshot.getDelta());
144 try std.testing.expectEqualStrings("items", snapshot.getName());
145 }
146
147 test "throughput snapshot log matches upstream format" {
148 var point = ThroughputPoint.init("items");
149 const snapshot = point.save();
150 point.visit(7);
151
152 var buffer: [128]u8 = undefined;
153 var writer = std.Io.Writer.fixed(&buffer);
154 try snapshot.log(&writer);
155
156 try std.testing.expectEqualStrings("throughput-point\tname=items\tdelta=7\n", writer.buffered());
157 }
158
159 test "latency point accumulates begin and end visits" {
160 var point = LatencyPoint.init("request");
161
162 point.visitBegin(5);
163 point.visitEnd(2);
164
165 try std.testing.expectEqual(@as(usize, 5), point.getBeginCount());
166 try std.testing.expectEqual(@as(usize, 2), point.getEndCount());
167 try std.testing.expectEqual(@as(usize, 0), point.beginCounterStruct().backoff);
168 try std.testing.expectEqual(@as(usize, 0), point.endCounterStruct().backoff);
169 }
170
171 test "latency snapshot reports arrivals departures and outstanding difference" {
172 var point = LatencyPoint.init("request");
173 point.visitBegin(2);
174 point.visitEnd(1);
175 const snapshot = point.save();
176
177 point.visitBegin(5);
178 point.visitEnd(3);
179
180 try std.testing.expectEqual(@as(usize, 5), snapshot.getBeginDelta());
181 try std.testing.expectEqual(@as(usize, 3), snapshot.getEndDelta());
182 try std.testing.expectEqual(@as(usize, 3), snapshot.getDifference());
183 try std.testing.expectEqualStrings("request", snapshot.getName());
184 }
185
186 test "latency snapshot log matches upstream format" {
187 var point = LatencyPoint.init("request");
188 const snapshot = point.save();
189
190 point.visitBegin(4);
191 point.visitEnd(1);
192
193 var buffer: [160]u8 = undefined;
194 var writer = std.Io.Writer.fixed(&buffer);
195 try snapshot.log(&writer);
196
197 try std.testing.expectEqualStrings(
198 "latency-point\tname=request\tarrivals=4\tdepartures=1\tdifference=3\n",
199 writer.buffered(),
200 );
201 }