diff options
author | 2021-10-05 02:27:49 -0700 | |
---|---|---|
committer | 2021-10-05 02:27:49 -0700 | |
commit | 00e7b7c3d53e41ff3df264bfe382a8fa70bb0b9d (patch) | |
tree | 1cb6bdd5eb389c934a799f94a1fa3985f8ec160c /src/analytics/analytics.zig | |
parent | d2be50bf4d87de13a6010e93e3f100412d6290d2 (diff) | |
download | bun-00e7b7c3d53e41ff3df264bfe382a8fa70bb0b9d.tar.gz bun-00e7b7c3d53e41ff3df264bfe382a8fa70bb0b9d.tar.zst bun-00e7b7c3d53e41ff3df264bfe382a8fa70bb0b9d.zip |
Simple analytics
Diffstat (limited to 'src/analytics/analytics.zig')
-rw-r--r-- | src/analytics/analytics.zig | 643 |
1 files changed, 643 insertions, 0 deletions
diff --git a/src/analytics/analytics.zig b/src/analytics/analytics.zig new file mode 100644 index 000000000..042f9040d --- /dev/null +++ b/src/analytics/analytics.zig @@ -0,0 +1,643 @@ +const std = @import("std"); + +pub const Reader = struct { + const Self = @This(); + pub const ReadError = error{EOF}; + + buf: []u8, + remain: []u8, + allocator: *std.mem.Allocator, + + pub fn init(buf: []u8, allocator: *std.mem.Allocator) Reader { + return Reader{ + .buf = buf, + .remain = buf, + .allocator = allocator, + }; + } + + pub fn read(this: *Self, count: usize) ![]u8 { + const read_count = std.math.min(count, this.remain.len); + if (read_count < count) { + return error.EOF; + } + + var slice = this.remain[0..read_count]; + + this.remain = this.remain[read_count..]; + + return slice; + } + + pub fn readAs(this: *Self, comptime T: type) !T { + if (!std.meta.trait.hasUniqueRepresentation(T)) { + @compileError(@typeName(T) ++ " must have unique representation."); + } + + return std.mem.bytesAsValue(T, try this.read(@sizeOf(T))); + } + + pub fn readByte(this: *Self) !u8 { + return (try this.read(1))[0]; + } + + pub fn readEnum(this: *Self, comptime Enum: type) !Enum { + const E = error{ + /// An integer was read, but it did not match any of the tags in the supplied enum. + InvalidValue, + }; + const type_info = @typeInfo(Enum).Enum; + const tag = try this.readInt(type_info.tag_type); + + inline for (std.meta.fields(Enum)) |field| { + if (tag == field.value) { + return @field(Enum, field.name); + } + } + + return E.InvalidValue; + } + + pub fn readArray(this: *Self, comptime T: type) ![]const T { + const length = try this.readInt(u32); + if (length == 0) { + return &([_]T{}); + } + + switch (T) { + u8 => { + return try this.read(length); + }, + u16, u32, i8, i16, i32 => { + return std.mem.readIntSliceNative(T, this.read(length * @sizeOf(T))); + }, + []const u8 => { + var i: u32 = 0; + var array = try this.allocator.alloc([]const u8, length); + while (i < length) : (i += 1) { + array[i] = try this.readArray(u8); + } + return array; + }, + else => { + switch (@typeInfo(T)) { + .Struct => |Struct| { + switch (Struct.layout) { + .Packed => { + const sizeof = @sizeOf(T); + var slice = try this.read(sizeof * length); + return std.mem.bytesAsSlice(T, slice); + }, + else => {}, + } + }, + .Enum => |type_info| { + const enum_values = try this.read(length * @sizeOf(type_info.tag_type)); + return @ptrCast([*]T, enum_values.ptr)[0..length]; + }, + else => {}, + } + + var i: u32 = 0; + var array = try this.allocator.alloc(T, length); + while (i < length) : (i += 1) { + array[i] = try this.readValue(T); + } + + return array; + }, + } + } + + pub fn readByteArray(this: *Self) ![]u8 { + const length = try this.readInt(u32); + if (length == 0) { + return &([_]u8{}); + } + + return try this.read(@intCast(usize, length)); + } + + pub fn readInt(this: *Self, comptime T: type) !T { + var slice = try this.read(@sizeOf(T)); + + return std.mem.readIntSliceNative(T, slice); + } + + pub fn readBool(this: *Self) !bool { + return (try this.readByte()) > 0; + } + + pub fn readValue(this: *Self, comptime T: type) !T { + switch (T) { + bool => { + return try this.readBool(); + }, + u8 => { + return try this.readByte(); + }, + []const u8 => { + return try this.readArray(u8); + }, + + []const []const u8 => { + return try this.readArray([]const u8); + }, + []u8 => { + return try this.readArray([]u8); + }, + u16, u32, i8, i16, i32 => { + return std.mem.readIntSliceNative(T, try this.read(@sizeOf(T))); + }, + else => { + switch (@typeInfo(T)) { + .Struct => |Struct| { + switch (Struct.layout) { + .Packed => { + const sizeof = @sizeOf(T); + var slice = try this.read(sizeof); + return @ptrCast(*T, slice[0..sizeof]).*; + }, + else => {}, + } + }, + .Enum => |type_info| { + return try this.readEnum(T); + }, + else => {}, + } + + return try T.decode(this); + }, + } + + @compileError("Invalid type passed to readValue"); + } +}; + +pub fn Writer(comptime WritableStream: type) type { + return struct { + const Self = @This(); + writable: WritableStream, + + pub fn init(writable: WritableStream) Self { + return Self{ .writable = writable }; + } + + pub fn write(this: *Self, bytes: anytype) !void { + _ = try this.writable.write(bytes); + } + + pub fn writeByte(this: *Self, byte: u8) !void { + _ = try this.writable.write(&[1]u8{byte}); + } + + pub fn writeInt(this: *Self, int: anytype) !void { + try this.write(std.mem.asBytes(&int)); + } + + pub fn writeFieldID(this: *Self, comptime id: comptime_int) !void { + try this.writeByte(id); + } + + pub fn writeEnum(this: *Self, val: anytype) !void { + try this.writeInt(@enumToInt(val)); + } + + pub fn writeValue(this: *Self, slice: anytype) !void { + switch (@TypeOf(slice)) { + []u16, + []u32, + []i16, + []i32, + []i8, + []const u16, + []const u32, + []const i16, + []const i32, + []const i8, + => { + try this.writeArray(@TypeOf(slice), slice); + }, + + []u8, []const u8 => { + try this.writeArray(u8, slice); + }, + + u8 => { + try this.write(slice); + }, + u16, u32, i16, i32, i8 => { + try this.write(std.mem.asBytes(slice)); + }, + + else => { + try slice.encode(this); + }, + } + } + + pub fn writeArray(this: *Self, comptime T: type, slice: anytype) !void { + try this.writeInt(@truncate(u32, slice.len)); + + switch (T) { + u8 => { + try this.write(slice); + }, + u16, u32, i16, i32, i8 => { + try this.write(std.mem.asBytes(slice)); + }, + []u8, + []u16, + []u32, + []i16, + []i32, + []i8, + []const u8, + []const u16, + []const u32, + []const i16, + []const i32, + []const i8, + => { + for (slice) |num_slice| { + try this.writeArray(std.meta.Child(@TypeOf(num_slice)), num_slice); + } + }, + else => { + for (slice) |val| { + try val.encode(this); + } + }, + } + } + + pub fn endMessage(this: *Self) !void { + try this.writeByte(0); + } + }; +} + +pub const ByteWriter = Writer(*std.io.FixedBufferStream([]u8)); +pub const FileWriter = Writer(std.fs.File); + +pub const Analytics = struct { + pub const OperatingSystem = enum(u8) { + _none, + /// linux + linux, + + /// macos + macos, + + /// windows + windows, + + /// wsl + wsl, + + _, + + pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void { + return try std.json.stringify(@tagName(self), opts, o); + } + }; + + pub const Architecture = enum(u8) { + _none, + /// x64 + x64, + + /// arm + arm, + + _, + + pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void { + return try std.json.stringify(@tagName(self), opts, o); + } + }; + + pub const Platform = struct { + /// os + os: OperatingSystem, + + /// arch + arch: Architecture, + + /// version + version: []const u8, + + pub fn decode(reader: anytype) anyerror!Platform { + var this = std.mem.zeroes(Platform); + + this.os = try reader.readValue(OperatingSystem); + this.arch = try reader.readValue(Architecture); + this.version = try reader.readValue([]const u8); + return this; + } + + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeEnum(this.os); + try writer.writeEnum(this.arch); + try writer.writeValue(this.version); + } + }; + + pub const EventKind = enum(u32) { + _none, + /// bundle_success + bundle_success, + + /// bundle_fail + bundle_fail, + + /// http_start + http_start, + + /// http_build + http_build, + + /// bundle_start + bundle_start, + + _, + + pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void { + return try std.json.stringify(@tagName(self), opts, o); + } + }; + + pub const Uint64 = packed struct { + /// first + first: u32 = 0, + + /// second + second: u32 = 0, + + pub fn decode(reader: anytype) anyerror!Uint64 { + var this = std.mem.zeroes(Uint64); + + this.first = try reader.readValue(u32); + this.second = try reader.readValue(u32); + return this; + } + + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeInt(this.first); + try writer.writeInt(this.second); + } + }; + + pub const EventListHeader = struct { + /// machine_id + machine_id: Uint64, + + /// platform + platform: Platform, + + /// build_id + build_id: u32 = 0, + + /// session_length + session_length: u32 = 0, + + pub fn decode(reader: anytype) anyerror!EventListHeader { + var this = std.mem.zeroes(EventListHeader); + + this.machine_id = try reader.readValue(Uint64); + this.platform = try reader.readValue(Platform); + this.build_id = try reader.readValue(u32); + this.session_length = try reader.readValue(u32); + return this; + } + + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeValue(this.machine_id); + try writer.writeValue(this.platform); + try writer.writeInt(this.build_id); + try writer.writeInt(this.session_length); + } + }; + + pub const EventHeader = struct { + /// timestamp + timestamp: Uint64, + + /// kind + kind: EventKind, + + pub fn decode(reader: anytype) anyerror!EventHeader { + var this = std.mem.zeroes(EventHeader); + + this.timestamp = try reader.readValue(Uint64); + this.kind = try reader.readValue(EventKind); + return this; + } + + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeValue(this.timestamp); + try writer.writeEnum(this.kind); + } + }; + + pub const EventList = struct { + /// header + header: EventListHeader, + + /// event_count + event_count: u32 = 0, + + pub fn decode(reader: anytype) anyerror!EventList { + var this = std.mem.zeroes(EventList); + + this.header = try reader.readValue(EventListHeader); + this.event_count = try reader.readValue(u32); + return this; + } + + pub fn encode(this: *const @This(), writer: anytype) anyerror!void { + try writer.writeValue(this.header); + try writer.writeInt(this.event_count); + } + }; +}; + +const ExamplePackedStruct = packed struct { + len: u32 = 0, + offset: u32 = 0, + + pub fn encode(this: *const ExamplePackedStruct, writer: anytype) !void { + try writer.write(std.mem.asBytes(this)); + } + + pub fn decode(reader: anytype) !ExamplePackedStruct { + return try reader.readAs(ExamplePackedStruct); + } +}; + +const ExampleStruct = struct { + name: []const u8 = "", + age: u32 = 0, + + pub fn encode(this: *const ExampleStruct, writer: anytype) !void { + try writer.writeArray(u8, this.name); + try writer.writeInt(this.age); + } + + pub fn decode(reader: anytype) !ExampleStruct { + var this = std.mem.zeroes(ExampleStruct); + this.name = try reader.readArray(u8); + this.age = try reader.readInt(u32); + + return this; + } +}; + +const EnumValue = enum(u8) { hey, hi, heyopoo }; + +const ExampleMessage = struct { + examples: ?[]ExampleStruct = &([_]ExampleStruct{}), + pack: ?[]ExamplePackedStruct = &([_]ExamplePackedStruct{}), + hey: ?u8 = 0, + hey16: ?u16 = 0, + hey32: ?u16 = 0, + heyi32: ?i32 = 0, + heyi16: ?i16 = 0, + heyi8: ?i8 = 0, + boolean: ?bool = null, + heyooo: ?EnumValue = null, + + pub fn encode(this: *const ExampleMessage, writer: anytype) !void { + if (this.examples) |examples| { + try writer.writeFieldID(1); + try writer.writeArray(ExampleStruct, examples); + } + + if (this.pack) |pack| { + try writer.writeFieldID(2); + try writer.writeArray(ExamplePackedStruct, pack); + } + + if (this.hey) |hey| { + try writer.writeFieldID(3); + try writer.writeInt(hey); + } + if (this.hey16) |hey16| { + try writer.writeFieldID(4); + try writer.writeInt(hey16); + } + if (this.hey32) |hey32| { + try writer.writeFieldID(5); + try writer.writeInt(hey32); + } + if (this.heyi32) |heyi32| { + try writer.writeFieldID(6); + try writer.writeInt(heyi32); + } + if (this.heyi16) |heyi16| { + try writer.writeFieldID(7); + try writer.writeInt(heyi16); + } + if (this.heyi8) |heyi8| { + try writer.writeFieldID(8); + try writer.writeInt(heyi8); + } + if (this.boolean) |boolean| { + try writer.writeFieldID(9); + try writer.writeInt(boolean); + } + + if (this.heyooo) |heyoo| { + try writer.writeFieldID(10); + try writer.writeEnum(heyoo); + } + + try writer.endMessage(); + } + + pub fn decode(reader: anytype) !ExampleMessage { + var this = std.mem.zeroes(ExampleMessage); + while (true) { + switch (try reader.readByte()) { + 0 => { + return this; + }, + + 1 => { + this.examples = try reader.readArray(std.meta.Child(@TypeOf(this.examples.?))); + }, + 2 => { + this.pack = try reader.readArray(std.meta.Child(@TypeOf(this.pack.?))); + }, + 3 => { + this.hey = try reader.readValue(@TypeOf(this.hey.?)); + }, + 4 => { + this.hey16 = try reader.readValue(@TypeOf(this.hey16.?)); + }, + 5 => { + this.hey32 = try reader.readValue(@TypeOf(this.hey32.?)); + }, + 6 => { + this.heyi32 = try reader.readValue(@TypeOf(this.heyi32.?)); + }, + 7 => { + this.heyi16 = try reader.readValue(@TypeOf(this.heyi16.?)); + }, + 8 => { + this.heyi8 = try reader.readValue(@TypeOf(this.heyi8.?)); + }, + 9 => { + this.boolean = try reader.readValue(@TypeOf(this.boolean.?)); + }, + 10 => { + this.heyooo = try reader.readValue(@TypeOf(this.heyooo.?)); + }, + else => { + return error.InvalidValue; + }, + } + } + + return this; + } +}; + +test "ExampleMessage" { + var base = std.mem.zeroes(ExampleMessage); + base.hey = 1; + var buf: [4096]u8 = undefined; + var writable = std.io.fixedBufferStream(&buf); + var writer = ByteWriter.init(writable); + var examples = [_]ExamplePackedStruct{ + .{ .len = 2, .offset = 5 }, + .{ .len = 0, .offset = 10 }, + }; + + var more_examples = [_]ExampleStruct{ + .{ .name = "bacon", .age = 10 }, + .{ .name = "slime", .age = 300 }, + }; + base.examples = &more_examples; + base.pack = &examples; + base.heyooo = EnumValue.hey; + try base.encode(&writer); + var reader = Reader.init(&buf, std.heap.c_allocator); + var compare = try ExampleMessage.decode(&reader); + try std.testing.expectEqual(base.hey orelse 255, 1); + + const cmp_pack = compare.pack.?; + for (cmp_pack) |item, id| { + try std.testing.expectEqual(item, examples[id]); + } + + const cmp_ex = compare.examples.?; + for (cmp_ex) |item, id| { + try std.testing.expectEqualStrings(item.name, more_examples[id].name); + try std.testing.expectEqual(item.age, more_examples[id].age); + } + + try std.testing.expectEqual(cmp_pack[0].len, examples[0].len); + try std.testing.expectEqual(base.heyooo, compare.heyooo); +} |