@@ -12,7 +12,7 @@ const ID_FILE = "lightpanda.id";
1212
1313pub const Telemetry = TelemetryT (blk : {
1414 if (builtin .mode == .Debug or builtin .is_test ) break :blk NoopProvider ;
15- break :blk @import ("ligtpanda .zig" ).Lightpanda ;
15+ break :blk @import ("lightpanda .zig" ).Lightpanda ;
1616});
1717
1818fn TelemetryT (comptime P : type ) type {
@@ -36,11 +36,13 @@ fn TelemetryT(comptime P: type) type {
3636 const disabled = std .process .hasEnvVarConstant ("LIGHTPANDA_DISABLE_TELEMETRY" );
3737
3838 var eid : [36 ]u8 = undefined ;
39- uuidv4 (& eid )
39+ uuidv4 (& eid );
4040
4141 return .{
42- .eid = eid ,
4342 .iid = if (disabled ) null else getOrCreateId (),
43+ .eid = eid ,
44+ .count = 0 ,
45+ .pending = undefined ,
4446 .disabled = disabled ,
4547 .provider = try P .init (allocator ),
4648 };
@@ -62,7 +64,7 @@ fn TelemetryT(comptime P: type) type {
6264 return ;
6365 }
6466
65- const iid = if (self .iid ) | * iid | * iid else null ;
67+ const iid : ? [] const u8 = if (self .iid ) | * iid | iid else null ;
6668 self .provider .send (iid , & self .eid , & self .pending ) catch | err | {
6769 log .warn ("failed to record event: {}" , .{err });
6870 };
@@ -73,22 +75,22 @@ fn TelemetryT(comptime P: type) type {
7375
7476fn getOrCreateId () ? [36 ]u8 {
7577 var buf : [37 ]u8 = undefined ;
76- const data = std .fs .cwd ().readFile (ID_FILE , & buf ) catch | err | switch (err ) blk : {
77- error .FileNotFound = > break : bkl &.{},
78+ const data = std .fs .cwd ().readFile (ID_FILE , & buf ) catch | err | switch (err ) {
79+ error .FileNotFound = > &.{},
7880 else = > {
7981 log .warn ("failed to open id file: {}" , .{err });
80- return null ,
82+ return null ;
8183 },
82- }
84+ };
8385
8486 var id : [36 ]u8 = undefined ;
8587 if (data .len == 36 ) {
86- @memcpy (id [0.. 36], data )
88+ @memcpy (id [0.. 36], data );
8789 return id ;
8890 }
8991
9092 uuidv4 (& id );
91- std .fs .cwd ().writeFile (.{.sub_path = ID_FILE , .data = buf [0 .. 36] }) catch | err | {
93+ std .fs .cwd ().writeFile (.{ .sub_path = ID_FILE , .data = & id }) catch | err | {
9294 log .warn ("failed to write to id file: {}" , .{err });
9395 return null ;
9496 };
@@ -97,6 +99,7 @@ fn getOrCreateId() ?[36]u8 {
9799
98100pub const Event = union (enum ) {
99101 run : Run ,
102+ flag : []const u8 , // used for testing
100103
101104 const Run = struct {
102105 version : []const u8 ,
@@ -114,7 +117,7 @@ const NoopProvider = struct {
114117 return .{};
115118 }
116119 fn deinit (_ : NoopProvider ) void {}
117- pub fn record (_ : NoopProvider , _ : Event ) ! void {}
120+ pub fn send (_ : NoopProvider , _ : ? [] const u8 , _ : [] const u8 , _ : [] Event ) ! void {}
118121};
119122
120123extern fn setenv (name : [* :0 ]u8 , value : [* :0 ]u8 , override : c_int ) c_int ;
@@ -129,7 +132,7 @@ test "telemetry: disabled by environment" {
129132 return .{};
130133 }
131134 fn deinit (_ : @This ()) void {}
132- pub fn record (_ : @This (), _ : Event ) ! void {
135+ pub fn send (_ : @This (), _ : ? [] const u8 , _ : [] const u8 , _ : [] Event ) ! void {
133136 unreachable ;
134137 }
135138 };
@@ -138,3 +141,76 @@ test "telemetry: disabled by environment" {
138141 defer telemetry .deinit ();
139142 telemetry .record (.{ .run = .{ .mode = .serve , .version = "123" } });
140143}
144+
145+ test "telemetry: getOrCreateId" {
146+ defer std .fs .cwd ().deleteFile (ID_FILE ) catch {};
147+
148+ std .fs .cwd ().deleteFile (ID_FILE ) catch {};
149+
150+ const id1 = getOrCreateId ().? ;
151+ const id2 = getOrCreateId ().? ;
152+ try testing .expectEqualStrings (& id1 , & id2 );
153+
154+ std .fs .cwd ().deleteFile (ID_FILE ) catch {};
155+ const id3 = getOrCreateId ().? ;
156+ try testing .expectEqual (false , std .mem .eql (u8 , & id1 , & id3 ));
157+ }
158+
159+ test "telemetry: sends batch" {
160+ defer std .fs .cwd ().deleteFile (ID_FILE ) catch {};
161+ std .fs .cwd ().deleteFile (ID_FILE ) catch {};
162+
163+ var telemetry = TelemetryT (MockProvider ).init (testing .allocator );
164+ defer telemetry .deinit ();
165+ const mock = & telemetry .provider ;
166+
167+ telemetry .record (.{ .flag = "1" });
168+ telemetry .record (.{ .flag = "2" });
169+ telemetry .record (.{ .flag = "3" });
170+ telemetry .record (.{ .flag = "4" });
171+ try testing .expectEqual (0 , mock .events .items .len );
172+ telemetry .record (.{ .flag = "5" });
173+ try testing .expectEqual (5 , mock .events .items .len );
174+
175+ telemetry .record (.{ .flag = "6" });
176+ telemetry .record (.{ .flag = "7" });
177+ telemetry .record (.{ .flag = "8" });
178+ telemetry .record (.{ .flag = "9" });
179+ try testing .expectEqual (5 , mock .events .items .len );
180+ telemetry .record (.{ .flag = "a" });
181+ try testing .expectEqual (10 , mock .events .items .len );
182+
183+ for (mock .events .items , 0.. ) | event , i | {
184+ try testing .expectEqual (i + 1 , std .fmt .parseInt (usize , event .flag , 16 ));
185+ }
186+ }
187+
188+ const MockProvider = struct {
189+ iid : ? []const u8 ,
190+ eid : ? []const u8 ,
191+ allocator : Allocator ,
192+ events : std .ArrayListUnmanaged (Event ),
193+
194+ fn init (allocator : Allocator ) ! @This () {
195+ return .{
196+ .iid = null ,
197+ .eid = null ,
198+ .events = .{},
199+ .allocator = allocator ,
200+ };
201+ }
202+ fn deinit (self : * MockProvider ) void {
203+ self .events .deinit (self .allocator );
204+ }
205+ pub fn send (self : * MockProvider , iid : ? []const u8 , eid : []const u8 , events : []Event ) ! void {
206+ if (self .iid == null ) {
207+ try testing .expectEqual (null , self .eid );
208+ self .iid = iid .? ;
209+ self .eid = eid ;
210+ } else {
211+ try testing .expectEqualStrings (self .iid .? , iid .? );
212+ try testing .expectEqualStrings (self .eid .? , eid );
213+ }
214+ try self .events .appendSlice (self .allocator , events );
215+ }
216+ };
0 commit comments