tigerbeetle-node 0.11.1 → 0.11.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ cd58e7f1a46beef96d550cfb8423c01542980ff7f9832127e73ad2e5cd0e3a63 dist/client.node
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tigerbeetle-node",
3
- "version": "0.11.1",
3
+ "version": "0.11.3",
4
4
  "description": "TigerBeetle Node.js client",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -13,6 +13,7 @@
13
13
  "LICENSE",
14
14
  "README.md",
15
15
  "dist",
16
+ "dist/.client.node.sha256",
16
17
  "!dist/client.node",
17
18
  "package.json",
18
19
  "package-lock.json",
@@ -1,16 +1,18 @@
1
1
  const std = @import("std");
2
2
  const assert = std.debug.assert;
3
3
  const fmt = std.fmt;
4
+ const math = std.math;
4
5
  const mem = std.mem;
5
6
  const meta = std.meta;
6
7
  const net = std.net;
7
8
  const os = std.os;
8
9
 
9
10
  const config = @import("config.zig");
11
+ const tigerbeetle = @import("tigerbeetle.zig");
10
12
  const vsr = @import("vsr.zig");
11
13
  const IO = @import("io.zig").IO;
12
14
 
13
- // TODO Document --memory
15
+ // TODO Document --cache-accounts, --cache-transfers, --cache-transfers-posted
14
16
  const usage = fmt.comptimePrint(
15
17
  \\Usage:
16
18
  \\
@@ -84,7 +86,9 @@ pub const Command = union(enum) {
84
86
  start: struct {
85
87
  args_allocated: std.ArrayList([:0]const u8),
86
88
  addresses: []net.Address,
87
- memory: u64,
89
+ cache_accounts: u32,
90
+ cache_transfers: u32,
91
+ cache_transfers_posted: u32,
88
92
  path: [:0]const u8,
89
93
  },
90
94
  version: struct {
@@ -110,7 +114,9 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
110
114
  var cluster: ?[]const u8 = null;
111
115
  var replica: ?[]const u8 = null;
112
116
  var addresses: ?[]const u8 = null;
113
- var memory: ?[]const u8 = null;
117
+ var cache_accounts: ?[]const u8 = null;
118
+ var cache_transfers: ?[]const u8 = null;
119
+ var cache_transfers_posted: ?[]const u8 = null;
114
120
  var verbose: ?bool = null;
115
121
 
116
122
  var args = try std.process.argsWithAllocator(allocator);
@@ -140,14 +146,25 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
140
146
  try args_allocated.append(arg);
141
147
 
142
148
  if (mem.startsWith(u8, arg, "--cluster")) {
149
+ if (command != .format) fatal("--cluster: supported only by 'format' command", .{});
143
150
  cluster = parse_flag("--cluster", arg);
144
151
  } else if (mem.startsWith(u8, arg, "--replica")) {
152
+ if (command != .format) fatal("--replica: supported only by 'format' command", .{});
145
153
  replica = parse_flag("--replica", arg);
146
154
  } else if (mem.startsWith(u8, arg, "--addresses")) {
155
+ if (command != .start) fatal("--addresses: supported only by 'start' command", .{});
147
156
  addresses = parse_flag("--addresses", arg);
148
- } else if (mem.startsWith(u8, arg, "--memory")) {
149
- memory = parse_flag("--memory", arg);
157
+ } else if (mem.startsWith(u8, arg, "--cache-accounts")) {
158
+ if (command != .start) fatal("--cache-accounts: supported only by 'start' command", .{});
159
+ cache_accounts = parse_flag("--cache-accounts", arg);
160
+ } else if (mem.startsWith(u8, arg, "--cache-transfers")) {
161
+ if (command != .start) fatal("--cache-transfers: supported only by 'start' command", .{});
162
+ cache_transfers = parse_flag("--cache-transfers", arg);
163
+ } else if (mem.startsWith(u8, arg, "--cache-transfers-posted")) {
164
+ if (command != .start) fatal("--cache-transfers-posted: supported only by 'start' command", .{});
165
+ cache_transfers_posted = parse_flag("--cache-transfers-posted", arg);
150
166
  } else if (mem.eql(u8, arg, "--verbose")) {
167
+ if (command != .version) fatal("--verbose: supported only by 'version' command", .{});
151
168
  verbose = true;
152
169
  } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
153
170
  std.io.getStdOut().writeAll(usage) catch os.exit(1);
@@ -155,6 +172,7 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
155
172
  } else if (mem.startsWith(u8, arg, "-")) {
156
173
  fatal("unexpected argument: '{s}'", .{arg});
157
174
  } else if (path == null) {
175
+ if (!(command == .format or command == .start)) fatal("unexpected path", .{});
158
176
  path = arg;
159
177
  } else {
160
178
  fatal("unexpected argument: '{s}' (must start with '--')", .{arg});
@@ -163,21 +181,11 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
163
181
 
164
182
  switch (command) {
165
183
  .version => {
166
- if (addresses != null) fatal("--addresses: supported only by 'start' command", .{});
167
- if (memory != null) fatal("--memory: supported only by 'start' command", .{});
168
- if (cluster != null) fatal("--cluster: supported only by 'format' command", .{});
169
- if (replica != null) fatal("--replica: supported only by 'format' command", .{});
170
- if (path != null) fatal("unexpected path", .{});
171
-
172
184
  return Command{
173
185
  .version = .{ .verbose = verbose orelse false },
174
186
  };
175
187
  },
176
188
  .format => {
177
- if (addresses != null) fatal("--addresses: supported only by 'start' command", .{});
178
- if (memory != null) fatal("--memory: supported only by 'start' command", .{});
179
- if (verbose != null) fatal("--verbose: supported only by 'version' command", .{});
180
-
181
189
  return Command{
182
190
  .format = .{
183
191
  .args_allocated = args_allocated,
@@ -188,10 +196,6 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
188
196
  };
189
197
  },
190
198
  .start => {
191
- if (cluster != null) fatal("--cluster: supported only by 'format' command", .{});
192
- if (replica != null) fatal("--replica: supported only by 'format' command", .{});
193
- if (verbose != null) fatal("--verbose: supported only by 'version' command", .{});
194
-
195
199
  return Command{
196
200
  .start = .{
197
201
  .args_allocated = args_allocated,
@@ -199,7 +203,21 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
199
203
  allocator,
200
204
  addresses orelse fatal("required: --addresses", .{}),
201
205
  ),
202
- .memory = if (memory) |m| parse_size(m) else config.memory_size_max_default,
206
+ .cache_accounts = parse_size_to_count(
207
+ tigerbeetle.Account,
208
+ cache_accounts,
209
+ config.cache_accounts_max,
210
+ ),
211
+ .cache_transfers = parse_size_to_count(
212
+ tigerbeetle.Transfer,
213
+ cache_transfers,
214
+ config.cache_transfers_max,
215
+ ),
216
+ .cache_transfers_posted = parse_size_to_count(
217
+ u256, // TODO(#264): Use actual type here, once exposed.
218
+ cache_transfers_posted,
219
+ config.cache_transfers_posted_max,
220
+ ),
203
221
  .path = path orelse fatal("required: <path>", .{}),
204
222
  },
205
223
  };
@@ -323,6 +341,30 @@ test "parse_size" {
323
341
  try expectEqual(@as(u64, 1000 * kib), parse_size(" 1000 kb "));
324
342
  }
325
343
 
344
+ /// Given a limit like `10GiB`, return the maximum power-of-two count of `T`s
345
+ /// that can fit in the limit.
346
+ fn parse_size_to_count(comptime T: type, string_opt: ?[]const u8, comptime default: u32) u32 {
347
+ var result: u32 = default;
348
+ if (string_opt) |string| {
349
+ const byte_size = parse_size(string);
350
+ const count_u64 = math.floorPowerOfTwo(u64, @divFloor(byte_size, @sizeOf(T)));
351
+ const count = math.cast(u32, count_u64) catch |err| switch (err) {
352
+ error.Overflow => fatal("size value is too large: '{s}'", .{string}),
353
+ };
354
+ if (count < 2048) fatal("size value is too small: '{s}'", .{string});
355
+ assert(count * @sizeOf(T) <= byte_size);
356
+
357
+ result = count;
358
+ }
359
+
360
+ // SetAssociativeCache requires a power-of-two cardinality and a minimal
361
+ // size.
362
+ assert(result >= 2048);
363
+ assert(math.isPowerOfTwo(result));
364
+
365
+ return result;
366
+ }
367
+
326
368
  fn parse_replica(raw_replica: []const u8) u8 {
327
369
  comptime assert(config.replicas_max <= std.math.maxInt(u8));
328
370
  const replica = fmt.parseUnsigned(u8, raw_replica, 10) catch |err| switch (err) {
@@ -1,8 +1,9 @@
1
1
  const std = @import("std");
2
2
  const assert = std.debug.assert;
3
- const tigerbeetle = @import("tigerbeetle.zig");
4
3
  const vsr = @import("vsr.zig");
5
4
 
5
+ const build_options = @import("tigerbeetle_build_options");
6
+
6
7
  const Environment = enum {
7
8
  development,
8
9
  production,
@@ -64,7 +65,7 @@ pub const cache_transfers_max = switch (deployment_environment) {
64
65
 
65
66
  /// The maximum number of two-phase transfers to store in memory:
66
67
  /// This impacts the amount of memory allocated at initialization by the server.
67
- pub const cache_transfers_pending_max = cache_transfers_max;
68
+ pub const cache_transfers_posted_max = cache_transfers_max;
68
69
 
69
70
  /// The maximum number of batch entries in the journal file:
70
71
  /// A batch entry may contain many transfers, so this is not a limit on the number of transfers.
@@ -331,6 +332,10 @@ pub const verify = true;
331
332
  pub const journal_size_headers = journal_slot_count * @sizeOf(vsr.Header);
332
333
  pub const journal_size_prepares = journal_slot_count * message_size_max;
333
334
 
335
+ // Which backend to use for ./tracer.zig.
336
+ // Default is `.none`.
337
+ pub const tracer_backend = build_options.tracer_backend;
338
+
334
339
  // TODO Move these into a separate `config_valid.zig` which we import here:
335
340
  comptime {
336
341
  // vsr.parse_address assumes that config.address/config.port are valid.
@@ -383,7 +388,7 @@ comptime {
383
388
  // SetAssociativeCache requires a power-of-two cardinality.
384
389
  assert(std.math.isPowerOfTwo(cache_accounts_max));
385
390
  assert(std.math.isPowerOfTwo(cache_transfers_max));
386
- assert(std.math.isPowerOfTwo(cache_transfers_pending_max));
391
+ assert(std.math.isPowerOfTwo(cache_transfers_posted_max));
387
392
  }
388
393
 
389
394
  pub const is_32_bit = @sizeOf(usize) == 4; // TODO Return a compile error if we are not 32-bit.
@@ -160,6 +160,24 @@ pub fn ewah(comptime Word: type) type {
160
160
  };
161
161
  }
162
162
 
163
+ test "ewah encode→decode cycle" {
164
+ const fuzz = @import("./ewah_fuzz.zig");
165
+ var prng = std.rand.DefaultPrng.init(123);
166
+
167
+ inline for (.{ u8, u16, u32, u64, usize }) |Word| {
168
+ var decoded: [4096]Word = undefined;
169
+
170
+ std.mem.set(Word, &decoded, 0);
171
+ try fuzz.fuzz_encode_decode(Word, std.testing.allocator, &decoded);
172
+
173
+ std.mem.set(Word, &decoded, std.math.maxInt(Word));
174
+ try fuzz.fuzz_encode_decode(Word, std.testing.allocator, &decoded);
175
+
176
+ prng.random().bytes(std.mem.asBytes(&decoded));
177
+ try fuzz.fuzz_encode_decode(Word, std.testing.allocator, &decoded);
178
+ }
179
+ }
180
+
163
181
  test "ewah Word=u8 decode→encode→decode" {
164
182
  try test_decode_with_word(u8);
165
183
 
@@ -179,35 +197,6 @@ test "ewah Word=u8 decode→encode→decode" {
179
197
  }
180
198
  }
181
199
 
182
- test "ewah Word=u8 encode→decode→encode" {
183
- const codec = ewah(u8);
184
-
185
- var seed: u64 = undefined;
186
- try std.os.getrandom(mem.asBytes(&seed));
187
-
188
- var prng = std.rand.DefaultPrng.init(seed);
189
- const random = prng.random();
190
-
191
- var decoded_expect: [4096]u8 = undefined;
192
- var decoded_actual: [4096]u8 = undefined;
193
-
194
- const encoded_actual = try std.testing.allocator.alignedAlloc(
195
- u8,
196
- @alignOf(u8),
197
- codec.encode_size_max(decoded_expect.len),
198
- );
199
- defer std.testing.allocator.free(encoded_actual);
200
-
201
- var t: usize = 0;
202
- while (t < 100) : (t += 1) {
203
- random.bytes(decoded_expect[0..]);
204
- _ = codec.encode(decoded_expect[0..], encoded_actual);
205
- const decoded_actual_length = codec.decode(encoded_actual[0..], decoded_actual[0..]);
206
- try std.testing.expectEqual(decoded_expect.len, decoded_actual_length);
207
- try std.testing.expectEqual(decoded_expect, decoded_actual);
208
- }
209
- }
210
-
211
200
  test "ewah Word=u16" {
212
201
  try test_decode_with_word(u16);
213
202
  }
@@ -0,0 +1,128 @@
1
+ //! Fuzz EWAH encode/decode cycle.
2
+ const std = @import("std");
3
+ const assert = std.debug.assert;
4
+ const log = std.log.scoped(.fuzz_ewah);
5
+
6
+ const ewah = @import("./ewah.zig");
7
+ const fuzz = @import("./test/fuzz.zig");
8
+
9
+ pub fn main() !void {
10
+ const allocator = std.testing.allocator;
11
+ const args = try fuzz.parse_fuzz_args(allocator);
12
+
13
+ inline for (.{ u8, u16, u32, u64, usize }) |Word| {
14
+ var prng = std.rand.DefaultPrng.init(args.seed);
15
+ const random = prng.random();
16
+
17
+ const decoded_size_max = @divExact(1024 * 1024, @sizeOf(Word));
18
+ const decoded_size = 1 + random.uintLessThan(usize, decoded_size_max);
19
+ const decoded = try allocator.alloc(Word, decoded_size);
20
+ defer allocator.free(decoded);
21
+
22
+ const decoded_bits_total = decoded_size * @bitSizeOf(Word);
23
+ const decoded_bits = random.uintLessThan(usize, decoded_bits_total);
24
+ generate_bits(random, std.mem.sliceAsBytes(decoded[0..decoded_size]), decoded_bits);
25
+
26
+ var context = try ContextType(Word).init(allocator, decoded.len);
27
+ defer context.deinit(allocator);
28
+
29
+ const encoded_size = try context.test_encode_decode(decoded);
30
+
31
+ log.info("word={} decoded={} encoded={} compression_ratio={d:.2} set={d:.2}", .{
32
+ Word,
33
+ decoded_size,
34
+ encoded_size,
35
+ @intToFloat(f64, decoded_size) / @intToFloat(f64, encoded_size),
36
+ @intToFloat(f64, decoded_bits) / @intToFloat(f64, decoded_bits_total),
37
+ });
38
+ }
39
+ }
40
+
41
+ pub fn fuzz_encode_decode(
42
+ comptime Word: type,
43
+ allocator: std.mem.Allocator,
44
+ decoded: []const Word,
45
+ ) !void {
46
+ var context = try ContextType(Word).init(allocator, decoded.len);
47
+ defer context.deinit(allocator);
48
+
49
+ _ = try context.test_encode_decode(decoded);
50
+ }
51
+
52
+ /// Modify `data` such that it has exactly `bits_set_total` randomly-chosen bits set,
53
+ /// with the remaining bits unset.
54
+ fn generate_bits(random: std.rand.Random, data: []u8, bits_set_total: usize) void {
55
+ const bits_total = data.len * @bitSizeOf(u8);
56
+ assert(bits_set_total <= bits_total);
57
+
58
+ // Start off full or empty to save some work.
59
+ const init_empty = bits_set_total < @divExact(bits_total, 2);
60
+ std.mem.set(u8, data, if (init_empty) @as(u8, 0) else std.math.maxInt(u8));
61
+
62
+ var bits_set = if (init_empty) 0 else bits_total;
63
+ while (bits_set != bits_set_total) {
64
+ const bit = random.uintLessThan(usize, bits_total);
65
+ const word = @divFloor(bit, @bitSizeOf(u8));
66
+ const mask = @as(u8, 1) << @intCast(std.math.Log2Int(u8), bit % @bitSizeOf(u8));
67
+
68
+ if (init_empty) {
69
+ if (data[word] & mask != 0) continue;
70
+ data[word] |= mask;
71
+ bits_set += 1;
72
+ } else {
73
+ if (data[word] & mask == 0) continue;
74
+ data[word] &= ~mask;
75
+ bits_set -= 1;
76
+ }
77
+ }
78
+ }
79
+
80
+ fn ContextType(comptime Word: type) type {
81
+ return struct {
82
+ const Self = @This();
83
+ const Codec = ewah.ewah(Word);
84
+
85
+ decoded_actual: []Word,
86
+ encoded_actual: []align(@alignOf(Word)) u8,
87
+
88
+ fn init(allocator: std.mem.Allocator, size_max: usize) !Self {
89
+ const decoded_actual = try allocator.alloc(Word, size_max);
90
+ errdefer allocator.free(decoded_actual);
91
+
92
+ const encoded_actual = try allocator.alignedAlloc(
93
+ u8,
94
+ @alignOf(Word),
95
+ Codec.encode_size_max(size_max),
96
+ );
97
+ errdefer allocator.free(encoded_actual);
98
+
99
+ return Self{
100
+ .decoded_actual = decoded_actual,
101
+ .encoded_actual = encoded_actual,
102
+ };
103
+ }
104
+
105
+ fn deinit(context: *Self, allocator: std.mem.Allocator) void {
106
+ allocator.free(context.decoded_actual);
107
+ allocator.free(context.encoded_actual);
108
+ }
109
+
110
+ fn test_encode_decode(context: Self, decoded_expect: []const Word) !usize {
111
+ assert(decoded_expect.len > 0);
112
+
113
+ const encoded_size = Codec.encode(decoded_expect, context.encoded_actual);
114
+ const decoded_actual_size = Codec.decode(
115
+ context.encoded_actual[0..encoded_size],
116
+ context.decoded_actual[0..],
117
+ );
118
+
119
+ try std.testing.expectEqual(decoded_expect.len, decoded_actual_size);
120
+ try std.testing.expectEqualSlices(
121
+ Word,
122
+ decoded_expect,
123
+ context.decoded_actual[0..decoded_actual_size],
124
+ );
125
+ return encoded_size;
126
+ }
127
+ };
128
+ }
@@ -36,6 +36,8 @@ const math = std.math;
36
36
  const assert = std.debug.assert;
37
37
 
38
38
  const log = std.log.scoped(.compaction);
39
+ const tracer = @import("../tracer.zig");
40
+
39
41
  const config = @import("../config.zig");
40
42
 
41
43
  const GridType = @import("grid.zig").GridType;
@@ -120,6 +122,8 @@ pub fn CompactionType(
120
122
  done,
121
123
  };
122
124
 
125
+ tree_name: []const u8,
126
+
123
127
  grid: *Grid,
124
128
  grid_reservation: Grid.Reservation,
125
129
  range: Manifest.CompactionRange,
@@ -155,7 +159,9 @@ pub fn CompactionType(
155
159
 
156
160
  tables_output_count: usize = 0,
157
161
 
158
- pub fn init(allocator: mem.Allocator) !Compaction {
162
+ tracer_slot: ?tracer.SpanStart = null,
163
+
164
+ pub fn init(allocator: mem.Allocator, tree_name: []const u8) !Compaction {
159
165
  var iterator_a = try IteratorA.init(allocator);
160
166
  errdefer iterator_a.deinit(allocator);
161
167
 
@@ -166,6 +172,8 @@ pub fn CompactionType(
166
172
  errdefer table_builder.deinit(allocator);
167
173
 
168
174
  return Compaction{
175
+ .tree_name = tree_name,
176
+
169
177
  // Assigned by start()
170
178
  .grid = undefined,
171
179
  .grid_reservation = undefined,
@@ -216,6 +224,7 @@ pub fn CompactionType(
216
224
  assert(compaction.callback == null);
217
225
  assert(compaction.io_pending == 0);
218
226
  assert(!compaction.merge_done and compaction.merge_iterator == null);
227
+ assert(compaction.tracer_slot == null);
219
228
 
220
229
  assert(op_min % @divExact(config.lsm_batch_multiple, 2) == 0);
221
230
  assert(range.table_count > 0);
@@ -230,6 +239,8 @@ pub fn CompactionType(
230
239
  assert(drop_tombstones or level_b < config.lsm_levels - 1);
231
240
 
232
241
  compaction.* = .{
242
+ .tree_name = compaction.tree_name,
243
+
233
244
  .grid = grid,
234
245
  // Reserve enough blocks to write our output tables in the worst case, where:
235
246
  // - no tombstones are dropped,
@@ -337,6 +348,15 @@ pub fn CompactionType(
337
348
 
338
349
  compaction.callback = callback;
339
350
 
351
+ tracer.start(
352
+ &compaction.tracer_slot,
353
+ .{ .tree = .{ .tree_name = compaction.tree_name } },
354
+ .{ .tree_compaction_tick = .{
355
+ .tree_name = compaction.tree_name,
356
+ .level_b = compaction.level_b,
357
+ } },
358
+ );
359
+
340
360
  // Generate fake IO to make sure io_pending doesn't reach zero multiple times from
341
361
  // IO being completed inline down below.
342
362
  // The fake IO is immediately resolved and triggers the cpu_merge_start if all
@@ -405,6 +425,16 @@ pub fn CompactionType(
405
425
  assert(compaction.io_pending == 0);
406
426
  assert(!compaction.merge_done);
407
427
 
428
+ var tracer_slot: ?tracer.SpanStart = null;
429
+ tracer.start(
430
+ &tracer_slot,
431
+ .{ .tree = .{ .tree_name = compaction.tree_name } },
432
+ .{ .tree_compaction_merge = .{
433
+ .tree_name = compaction.tree_name,
434
+ .level_b = compaction.level_b,
435
+ } },
436
+ );
437
+
408
438
  // Create the merge iterator only when we can peek() from the read iterators.
409
439
  // This happens after IO for the first reads complete.
410
440
  if (compaction.merge_iterator == null) {
@@ -422,6 +452,23 @@ pub fn CompactionType(
422
452
  compaction.cpu_merge_finish();
423
453
  }
424
454
 
455
+ tracer.end(
456
+ &tracer_slot,
457
+ .{ .tree = .{ .tree_name = compaction.tree_name } },
458
+ .{ .tree_compaction_merge = .{
459
+ .tree_name = compaction.tree_name,
460
+ .level_b = compaction.level_b,
461
+ } },
462
+ );
463
+ tracer.end(
464
+ &compaction.tracer_slot,
465
+ .{ .tree = .{ .tree_name = compaction.tree_name } },
466
+ .{ .tree_compaction_tick = .{
467
+ .tree_name = compaction.tree_name,
468
+ .level_b = compaction.level_b,
469
+ } },
470
+ );
471
+
425
472
  // TODO Implement pacing here by deciding if we should do another compact_tick()
426
473
  // instead of invoking the callback, using compaction.range.table_count as the heuristic.
427
474
 
@@ -560,6 +607,7 @@ pub fn CompactionType(
560
607
  assert(compaction.callback == null);
561
608
  assert(compaction.io_pending == 0);
562
609
  assert(compaction.merge_done);
610
+ assert(compaction.tracer_slot == null);
563
611
 
564
612
  // TODO(Beat Pacing) This should really be where the compaction callback is invoked,
565
613
  // but currently that can occur multiple times per beat.
@@ -6,7 +6,9 @@ const assert = std.debug.assert;
6
6
  const config = @import("../config.zig");
7
7
  const fuzz = @import("../test/fuzz.zig");
8
8
  const vsr = @import("../vsr.zig");
9
+
9
10
  const log = std.log.scoped(.lsm_forest_fuzz);
11
+ const tracer = @import("../tracer.zig");
10
12
 
11
13
  const MessagePool = @import("../message_pool.zig").MessagePool;
12
14
  const Transfer = @import("../tigerbeetle.zig").Transfer;
@@ -395,6 +397,9 @@ pub fn generate_fuzz_ops(random: std.rand.Random) ![]const FuzzOp {
395
397
  }
396
398
 
397
399
  pub fn main() !void {
400
+ try tracer.init(allocator);
401
+ defer tracer.deinit(allocator);
402
+
398
403
  const fuzz_args = try fuzz.parse_fuzz_args(allocator);
399
404
  var rng = std.rand.DefaultPrng.init(fuzz_args.seed);
400
405
  const random = rng.random();
@@ -172,9 +172,9 @@ pub fn GridType(comptime Storage: type) type {
172
172
  // Resolve reads that were seen in the cache during start_read()
173
173
  // but deferred to be asynchronously resolved on the next tick.
174
174
  //
175
- // Drain directly from the queue so that new cache reads (added upon completion of old
176
- // cache reads) that can be serviced immediately aren't deferred until the next tick
177
- // (which may be milliseconds later due to IO.run_for_ns). This is necessary to ensure
175
+ // Drain directly from the queue so that new cache reads (added upon completion of old
176
+ // cache reads) that can be serviced immediately aren't deferred until the next tick
177
+ // (which may be milliseconds later due to IO.run_for_ns). This is necessary to ensure
178
178
  // that groove prefetch completes promptly.
179
179
  //
180
180
  // Even still, we cap the reads processed to prevent going over
@@ -7,6 +7,7 @@ const mem = std.mem;
7
7
  const os = std.os;
8
8
 
9
9
  const log = std.log.scoped(.tree);
10
+ const tracer = @import("../tracer.zig");
10
11
 
11
12
  const config = @import("../config.zig");
12
13
  const div_ceil = @import("../util.zig").div_ceil;
@@ -144,6 +145,8 @@ pub fn TreeType(comptime TreeTable: type, comptime Storage: type, comptime tree_
144
145
  checkpoint_callback: ?fn (*Tree) void,
145
146
  open_callback: ?fn (*Tree) void,
146
147
 
148
+ tracer_slot: ?tracer.SpanStart = null,
149
+
147
150
  pub const Options = struct {
148
151
  /// The maximum number of keys that may be committed per batch.
149
152
  ///
@@ -188,13 +191,13 @@ pub fn TreeType(comptime TreeTable: type, comptime Storage: type, comptime tree_
188
191
  var manifest = try Manifest.init(allocator, node_pool, grid, tree_hash);
189
192
  errdefer manifest.deinit(allocator);
190
193
 
191
- var compaction_table_immutable = try CompactionTableImmutable.init(allocator);
194
+ var compaction_table_immutable = try CompactionTableImmutable.init(allocator, tree_name);
192
195
  errdefer compaction_table_immutable.deinit(allocator);
193
196
 
194
197
  var compaction_table: [@divFloor(config.lsm_levels, 2)]CompactionTable = undefined;
195
198
  for (compaction_table) |*compaction, i| {
196
199
  errdefer for (compaction_table[0..i]) |*c| c.deinit(allocator);
197
- compaction.* = try CompactionTable.init(allocator);
200
+ compaction.* = try CompactionTable.init(allocator, tree_name);
198
201
  }
199
202
  errdefer for (compaction_table) |*c| c.deinit(allocator);
200
203
 
@@ -221,6 +224,8 @@ pub fn TreeType(comptime TreeTable: type, comptime Storage: type, comptime tree_
221
224
  }
222
225
 
223
226
  pub fn deinit(tree: *Tree, allocator: mem.Allocator) void {
227
+ assert(tree.tracer_slot == null);
228
+
224
229
  tree.compaction_table_immutable.deinit(allocator);
225
230
  for (tree.compaction_table) |*compaction| compaction.deinit(allocator);
226
231
 
@@ -548,6 +553,12 @@ pub fn TreeType(comptime TreeTable: type, comptime Storage: type, comptime tree_
548
553
  assert(tree.compaction_io_pending == 0);
549
554
  assert(tree.compaction_callback == null);
550
555
 
556
+ tracer.start(
557
+ &tree.tracer_slot,
558
+ .{ .tree = .{ .tree_name = tree_name } },
559
+ .{ .tree_compaction_beat = .{ .tree_name = tree_name } },
560
+ );
561
+
551
562
  tree.compaction_callback = callback;
552
563
 
553
564
  const compaction_beat = tree.compaction_op % config.lsm_batch_multiple;
@@ -917,6 +928,12 @@ pub fn TreeType(comptime TreeTable: type, comptime Storage: type, comptime tree_
917
928
  assert(tree.compaction_io_pending == 0);
918
929
  assert(tree.table_mutable.can_commit_batch(tree.options.commit_entries_max));
919
930
 
931
+ tracer.end(
932
+ &tree.tracer_slot,
933
+ .{ .tree = .{ .tree_name = tree_name } },
934
+ .{ .tree_compaction_beat = .{ .tree_name = tree_name } },
935
+ );
936
+
920
937
  // Invoke the compact() callback after the manifest compacts at the end of the beat.
921
938
  const callback = tree.compaction_callback.?;
922
939
  tree.compaction_callback = null;
@@ -6,7 +6,9 @@ const assert = std.debug.assert;
6
6
  const config = @import("../config.zig");
7
7
  const fuzz = @import("../test/fuzz.zig");
8
8
  const vsr = @import("../vsr.zig");
9
+
9
10
  const log = std.log.scoped(.lsm_tree_fuzz);
11
+ const tracer = @import("../tracer.zig");
10
12
 
11
13
  const MessagePool = @import("../message_pool.zig").MessagePool;
12
14
  const Transfer = @import("../tigerbeetle.zig").Transfer;
@@ -27,7 +29,7 @@ const Table = @import("table.zig").TableType(
27
29
  Key.tombstone,
28
30
  Key.tombstone_from_key,
29
31
  );
30
- const Tree = @import("tree.zig").TreeType(Table, Storage, @typeName(Table) ++ "_test");
32
+ const Tree = @import("tree.zig").TreeType(Table, Storage, "Key.Value");
31
33
 
32
34
  const Grid = GridType(Storage);
33
35
  const SuperBlock = vsr.SuperBlockType(Storage);
@@ -440,6 +442,9 @@ pub fn generate_fuzz_ops(random: std.rand.Random) ![]const FuzzOp {
440
442
  }
441
443
 
442
444
  pub fn main() !void {
445
+ try tracer.init(allocator);
446
+ defer tracer.deinit(allocator);
447
+
443
448
  const fuzz_args = try fuzz.parse_fuzz_args(allocator);
444
449
  var rng = std.rand.DefaultPrng.init(fuzz_args.seed);
445
450
  const random = rng.random();