tigerbeetle-node 0.10.0 → 0.11.0
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.
- package/README.md +302 -101
- package/dist/index.d.ts +70 -72
- package/dist/index.js +70 -72
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/scripts/download_node_headers.sh +14 -7
- package/src/index.ts +6 -10
- package/src/node.zig +6 -3
- package/src/tigerbeetle/scripts/benchmark.sh +4 -4
- package/src/tigerbeetle/scripts/confirm_image.sh +44 -0
- package/src/tigerbeetle/scripts/fuzz_loop.sh +15 -0
- package/src/tigerbeetle/scripts/fuzz_unique_errors.sh +7 -0
- package/src/tigerbeetle/scripts/install.sh +19 -4
- package/src/tigerbeetle/scripts/install_zig.bat +5 -1
- package/src/tigerbeetle/scripts/install_zig.sh +24 -14
- package/src/tigerbeetle/scripts/pre-commit.sh +9 -0
- package/src/tigerbeetle/scripts/shellcheck.sh +5 -0
- package/src/tigerbeetle/scripts/tests_on_alpine.sh +10 -0
- package/src/tigerbeetle/scripts/tests_on_ubuntu.sh +14 -0
- package/src/tigerbeetle/src/benchmark.zig +4 -2
- package/src/tigerbeetle/src/benchmark_array_search.zig +3 -3
- package/src/tigerbeetle/src/c/tb_client/thread.zig +8 -9
- package/src/tigerbeetle/src/c/tb_client.h +100 -80
- package/src/tigerbeetle/src/c/tb_client.zig +4 -1
- package/src/tigerbeetle/src/cli.zig +1 -1
- package/src/tigerbeetle/src/config.zig +48 -16
- package/src/tigerbeetle/src/demo.zig +3 -1
- package/src/tigerbeetle/src/eytzinger_benchmark.zig +3 -3
- package/src/tigerbeetle/src/io/linux.zig +1 -1
- package/src/tigerbeetle/src/lsm/README.md +214 -0
- package/src/tigerbeetle/src/lsm/binary_search.zig +137 -10
- package/src/tigerbeetle/src/lsm/bloom_filter.zig +43 -0
- package/src/tigerbeetle/src/lsm/compaction.zig +352 -398
- package/src/tigerbeetle/src/lsm/composite_key.zig +2 -0
- package/src/tigerbeetle/src/lsm/eytzinger.zig +1 -1
- package/src/tigerbeetle/src/lsm/forest.zig +21 -447
- package/src/tigerbeetle/src/lsm/forest_fuzz.zig +412 -0
- package/src/tigerbeetle/src/lsm/grid.zig +145 -69
- package/src/tigerbeetle/src/lsm/groove.zig +196 -133
- package/src/tigerbeetle/src/lsm/k_way_merge.zig +40 -18
- package/src/tigerbeetle/src/lsm/level_iterator.zig +28 -9
- package/src/tigerbeetle/src/lsm/manifest.zig +81 -181
- package/src/tigerbeetle/src/lsm/manifest_level.zig +210 -454
- package/src/tigerbeetle/src/lsm/manifest_log.zig +77 -28
- package/src/tigerbeetle/src/lsm/posted_groove.zig +64 -76
- package/src/tigerbeetle/src/lsm/segmented_array.zig +561 -241
- package/src/tigerbeetle/src/lsm/segmented_array_benchmark.zig +148 -0
- package/src/tigerbeetle/src/lsm/segmented_array_fuzz.zig +9 -0
- package/src/tigerbeetle/src/lsm/set_associative_cache.zig +62 -12
- package/src/tigerbeetle/src/lsm/table.zig +83 -48
- package/src/tigerbeetle/src/lsm/table_immutable.zig +30 -23
- package/src/tigerbeetle/src/lsm/table_iterator.zig +25 -14
- package/src/tigerbeetle/src/lsm/table_mutable.zig +63 -12
- package/src/tigerbeetle/src/lsm/test.zig +49 -55
- package/src/tigerbeetle/src/lsm/tree.zig +407 -402
- package/src/tigerbeetle/src/lsm/tree_fuzz.zig +457 -0
- package/src/tigerbeetle/src/main.zig +28 -6
- package/src/tigerbeetle/src/message_bus.zig +2 -2
- package/src/tigerbeetle/src/message_pool.zig +14 -17
- package/src/tigerbeetle/src/simulator.zig +145 -112
- package/src/tigerbeetle/src/state_machine.zig +338 -228
- package/src/tigerbeetle/src/static_allocator.zig +65 -0
- package/src/tigerbeetle/src/storage.zig +3 -7
- package/src/tigerbeetle/src/test/accounting/auditor.zig +577 -0
- package/src/tigerbeetle/src/test/accounting/workload.zig +819 -0
- package/src/tigerbeetle/src/test/cluster.zig +18 -48
- package/src/tigerbeetle/src/test/conductor.zig +365 -0
- package/src/tigerbeetle/src/test/fuzz.zig +121 -0
- package/src/tigerbeetle/src/test/id.zig +89 -0
- package/src/tigerbeetle/src/test/priority_queue.zig +645 -0
- package/src/tigerbeetle/src/test/state_checker.zig +93 -69
- package/src/tigerbeetle/src/test/state_machine.zig +11 -35
- package/src/tigerbeetle/src/test/storage.zig +29 -8
- package/src/tigerbeetle/src/tigerbeetle.zig +14 -16
- package/src/tigerbeetle/src/unit_tests.zig +7 -0
- package/src/tigerbeetle/src/vopr.zig +494 -0
- package/src/tigerbeetle/src/vopr_hub/README.md +58 -0
- package/src/tigerbeetle/src/vopr_hub/SETUP.md +199 -0
- package/src/tigerbeetle/src/vopr_hub/go.mod +3 -0
- package/src/tigerbeetle/src/vopr_hub/main.go +1022 -0
- package/src/tigerbeetle/src/vopr_hub/scheduler/go.mod +3 -0
- package/src/tigerbeetle/src/vopr_hub/scheduler/main.go +403 -0
- package/src/tigerbeetle/src/vsr/client.zig +13 -0
- package/src/tigerbeetle/src/vsr/journal.zig +16 -13
- package/src/tigerbeetle/src/vsr/replica.zig +924 -491
- package/src/tigerbeetle/src/vsr/superblock.zig +55 -37
- package/src/tigerbeetle/src/vsr/superblock_client_table.zig +7 -10
- package/src/tigerbeetle/src/vsr/superblock_free_set.zig +2 -2
- package/src/tigerbeetle/src/vsr/superblock_manifest.zig +18 -3
- package/src/tigerbeetle/src/vsr.zig +75 -55
- package/src/tigerbeetle/scripts/vopr.bat +0 -48
- package/src/tigerbeetle/scripts/vopr.sh +0 -33
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
const std = @import("std");
|
|
2
|
+
const testing = std.testing;
|
|
3
|
+
const allocator = testing.allocator;
|
|
4
|
+
const assert = std.debug.assert;
|
|
5
|
+
|
|
6
|
+
const config = @import("../config.zig");
|
|
7
|
+
const fuzz = @import("../test/fuzz.zig");
|
|
8
|
+
const vsr = @import("../vsr.zig");
|
|
9
|
+
const log = std.log.scoped(.lsm_forest_fuzz);
|
|
10
|
+
|
|
11
|
+
const MessagePool = @import("../message_pool.zig").MessagePool;
|
|
12
|
+
const Transfer = @import("../tigerbeetle.zig").Transfer;
|
|
13
|
+
const Account = @import("../tigerbeetle.zig").Account;
|
|
14
|
+
const Storage = @import("../test/storage.zig").Storage;
|
|
15
|
+
const StateMachine = @import("../state_machine.zig").StateMachineType(Storage, .{
|
|
16
|
+
.message_body_size_max = config.message_body_size_max,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const GridType = @import("grid.zig").GridType;
|
|
20
|
+
const GrooveType = @import("groove.zig").GrooveType;
|
|
21
|
+
const Forest = StateMachine.Forest;
|
|
22
|
+
|
|
23
|
+
const Grid = GridType(Storage);
|
|
24
|
+
const SuperBlock = vsr.SuperBlockType(Storage);
|
|
25
|
+
|
|
26
|
+
const FuzzOp = union(enum) {
|
|
27
|
+
// TODO Test secondary index lookups and range queries.
|
|
28
|
+
compact: struct {
|
|
29
|
+
op: u64,
|
|
30
|
+
checkpoint: bool,
|
|
31
|
+
},
|
|
32
|
+
put_account: Account,
|
|
33
|
+
get_account: u128,
|
|
34
|
+
};
|
|
35
|
+
const FuzzOpTag = std.meta.Tag(FuzzOp);
|
|
36
|
+
|
|
37
|
+
const Environment = struct {
|
|
38
|
+
const cluster = 32;
|
|
39
|
+
const replica = 4;
|
|
40
|
+
// TODO Is this appropriate for the number of fuzz_ops we want to run?
|
|
41
|
+
const size_max = vsr.Zone.superblock.size().? + vsr.Zone.wal.size().? + 1024 * 1024 * 1024;
|
|
42
|
+
|
|
43
|
+
const node_count = 1024;
|
|
44
|
+
// This is the smallest size that set_associative_cache will allow us.
|
|
45
|
+
const cache_entries_max = 2048;
|
|
46
|
+
const forest_options = StateMachine.forest_options(.{
|
|
47
|
+
// Ignored by StateMachine.forest_options().
|
|
48
|
+
.lsm_forest_node_count = undefined,
|
|
49
|
+
.cache_entries_accounts = cache_entries_max,
|
|
50
|
+
.cache_entries_transfers = cache_entries_max,
|
|
51
|
+
.cache_entries_posted = cache_entries_max,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Each account put can generate a put and a tombstone in each index.
|
|
55
|
+
const puts_since_compact_max = @divTrunc(forest_options.accounts.tree_options_object.commit_entries_max, 2);
|
|
56
|
+
|
|
57
|
+
const compacts_per_checkpoint = std.math.divCeil(
|
|
58
|
+
usize,
|
|
59
|
+
config.journal_slot_count,
|
|
60
|
+
config.lsm_batch_multiple,
|
|
61
|
+
) catch unreachable;
|
|
62
|
+
|
|
63
|
+
const State = enum {
|
|
64
|
+
uninit,
|
|
65
|
+
init,
|
|
66
|
+
formatted,
|
|
67
|
+
superblock_open,
|
|
68
|
+
forest_open,
|
|
69
|
+
forest_compacting,
|
|
70
|
+
forest_checkpointing,
|
|
71
|
+
superblock_checkpointing,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
state: State,
|
|
75
|
+
storage: *Storage,
|
|
76
|
+
message_pool: MessagePool,
|
|
77
|
+
superblock: SuperBlock,
|
|
78
|
+
superblock_context: SuperBlock.Context = undefined,
|
|
79
|
+
grid: Grid,
|
|
80
|
+
forest: Forest,
|
|
81
|
+
// We need @fieldParentPtr() of forest, so we can't use an optional Forest.
|
|
82
|
+
forest_exists: bool,
|
|
83
|
+
|
|
84
|
+
fn init(env: *Environment, storage: *Storage) !void {
|
|
85
|
+
env.state = .uninit;
|
|
86
|
+
|
|
87
|
+
env.storage = storage;
|
|
88
|
+
errdefer env.storage.deinit(allocator);
|
|
89
|
+
|
|
90
|
+
env.message_pool = try MessagePool.init(allocator, .replica);
|
|
91
|
+
errdefer env.message_pool.deinit(allocator);
|
|
92
|
+
|
|
93
|
+
env.superblock = try SuperBlock.init(allocator, env.storage, &env.message_pool);
|
|
94
|
+
errdefer env.superblock.deinit(allocator);
|
|
95
|
+
|
|
96
|
+
env.grid = try Grid.init(allocator, &env.superblock);
|
|
97
|
+
errdefer env.grid.deinit(allocator);
|
|
98
|
+
|
|
99
|
+
// Forest must be initialized with an open superblock.
|
|
100
|
+
env.forest = undefined;
|
|
101
|
+
env.forest_exists = false;
|
|
102
|
+
|
|
103
|
+
env.state = .init;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fn deinit(env: *Environment) void {
|
|
107
|
+
assert(env.state != .uninit);
|
|
108
|
+
|
|
109
|
+
if (env.forest_exists) {
|
|
110
|
+
env.forest.deinit(allocator);
|
|
111
|
+
env.forest_exists = false;
|
|
112
|
+
}
|
|
113
|
+
env.grid.deinit(allocator);
|
|
114
|
+
env.superblock.deinit(allocator);
|
|
115
|
+
env.message_pool.deinit(allocator);
|
|
116
|
+
|
|
117
|
+
env.state = .uninit;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
fn tick(env: *Environment) void {
|
|
121
|
+
env.grid.tick();
|
|
122
|
+
env.storage.tick();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
fn tick_until_state_change(env: *Environment, current_state: State, next_state: State) void {
|
|
126
|
+
// Sometimes IO completes synchronously (eg if cached), so we might already be in next_state before ticking.
|
|
127
|
+
assert(env.state == current_state or env.state == next_state);
|
|
128
|
+
while (env.state == current_state) env.tick();
|
|
129
|
+
assert(env.state == next_state);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
fn change_state(env: *Environment, current_state: State, next_state: State) void {
|
|
133
|
+
assert(env.state == current_state);
|
|
134
|
+
env.state = next_state;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
pub fn format(storage: *Storage) !void {
|
|
138
|
+
var env: Environment = undefined;
|
|
139
|
+
|
|
140
|
+
try env.init(storage);
|
|
141
|
+
defer env.deinit();
|
|
142
|
+
|
|
143
|
+
assert(env.state == .init);
|
|
144
|
+
env.superblock.format(superblock_format_callback, &env.superblock_context, .{
|
|
145
|
+
.cluster = cluster,
|
|
146
|
+
.replica = replica,
|
|
147
|
+
.size_max = size_max,
|
|
148
|
+
});
|
|
149
|
+
env.tick_until_state_change(.init, .formatted);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
fn superblock_format_callback(superblock_context: *SuperBlock.Context) void {
|
|
153
|
+
const env = @fieldParentPtr(@This(), "superblock_context", superblock_context);
|
|
154
|
+
env.change_state(.init, .formatted);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
pub fn open(env: *Environment) void {
|
|
158
|
+
assert(env.state == .init);
|
|
159
|
+
env.superblock.open(superblock_open_callback, &env.superblock_context);
|
|
160
|
+
env.tick_until_state_change(.init, .forest_open);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
fn superblock_open_callback(superblock_context: *SuperBlock.Context) void {
|
|
164
|
+
const env = @fieldParentPtr(@This(), "superblock_context", superblock_context);
|
|
165
|
+
env.change_state(.init, .superblock_open);
|
|
166
|
+
env.forest = Forest.init(allocator, &env.grid, node_count, forest_options) catch unreachable;
|
|
167
|
+
env.forest_exists = true;
|
|
168
|
+
env.forest.open(forest_open_callback);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
fn forest_open_callback(forest: *Forest) void {
|
|
172
|
+
const env = @fieldParentPtr(@This(), "forest", forest);
|
|
173
|
+
env.change_state(.superblock_open, .forest_open);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
pub fn compact(env: *Environment, op: u64) void {
|
|
177
|
+
env.change_state(.forest_open, .forest_compacting);
|
|
178
|
+
env.forest.compact(forest_compact_callback, op);
|
|
179
|
+
env.tick_until_state_change(.forest_compacting, .forest_open);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
fn forest_compact_callback(forest: *Forest) void {
|
|
183
|
+
const env = @fieldParentPtr(@This(), "forest", forest);
|
|
184
|
+
env.change_state(.forest_compacting, .forest_open);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
pub fn checkpoint(env: *Environment) void {
|
|
188
|
+
env.change_state(.forest_open, .forest_checkpointing);
|
|
189
|
+
env.forest.checkpoint(forest_checkpoint_callback);
|
|
190
|
+
env.tick_until_state_change(.forest_checkpointing, .superblock_checkpointing);
|
|
191
|
+
env.tick_until_state_change(.superblock_checkpointing, .forest_open);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
fn forest_checkpoint_callback(forest: *Forest) void {
|
|
195
|
+
const env = @fieldParentPtr(@This(), "forest", forest);
|
|
196
|
+
env.change_state(.forest_checkpointing, .superblock_checkpointing);
|
|
197
|
+
env.superblock.checkpoint(superblock_checkpoint_callback, &env.superblock_context);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
fn superblock_checkpoint_callback(superblock_context: *SuperBlock.Context) void {
|
|
201
|
+
const env = @fieldParentPtr(@This(), "superblock_context", superblock_context);
|
|
202
|
+
env.change_state(.superblock_checkpointing, .forest_open);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
fn prefetch_account(env: *Environment, id: u128) void {
|
|
206
|
+
const groove = &env.forest.grooves.accounts;
|
|
207
|
+
const Groove = @TypeOf(groove.*);
|
|
208
|
+
const Getter = struct {
|
|
209
|
+
finished: bool = false,
|
|
210
|
+
prefetch_context: Groove.PrefetchContext = undefined,
|
|
211
|
+
fn prefetch_callback(prefetch_context: *Groove.PrefetchContext) void {
|
|
212
|
+
const getter = @fieldParentPtr(@This(), "prefetch_context", prefetch_context);
|
|
213
|
+
getter.finished = true;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
var getter = Getter{};
|
|
217
|
+
groove.prefetch_setup(null);
|
|
218
|
+
groove.prefetch_enqueue(id);
|
|
219
|
+
groove.prefetch(Getter.prefetch_callback, &getter.prefetch_context);
|
|
220
|
+
while (!getter.finished) env.tick();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
fn run(storage: *Storage, fuzz_ops: []const FuzzOp) !void {
|
|
224
|
+
var env: Environment = undefined;
|
|
225
|
+
|
|
226
|
+
try env.init(storage);
|
|
227
|
+
defer env.deinit();
|
|
228
|
+
|
|
229
|
+
// Open the superblock then forest.
|
|
230
|
+
env.open();
|
|
231
|
+
|
|
232
|
+
// The forest should behave like a simple key-value data-structure.
|
|
233
|
+
// We'll compare it to a hash map.
|
|
234
|
+
var model = std.hash_map.AutoHashMap(u128, Account).init(allocator);
|
|
235
|
+
defer model.deinit();
|
|
236
|
+
|
|
237
|
+
for (fuzz_ops) |fuzz_op, fuzz_op_index| {
|
|
238
|
+
log.debug("Running fuzz_ops[{}/{}] == {}", .{ fuzz_op_index, fuzz_ops.len, fuzz_op });
|
|
239
|
+
//TODO(@djg) Restore these when dj-vopr-workload merges.
|
|
240
|
+
//const storage_size_used = storage.size_used();
|
|
241
|
+
//log.debug("storage.size_used = {}/{}", .{ storage_size_used, storage.size });
|
|
242
|
+
//const model_size = model.count() * @sizeOf(Account);
|
|
243
|
+
//log.debug("space_amplification = {d:.2}", .{@intToFloat(f64, storage_size_used) / @intToFloat(f64, model_size)});
|
|
244
|
+
// Apply fuzz_op to the forest and the model.
|
|
245
|
+
switch (fuzz_op) {
|
|
246
|
+
.compact => |compact| {
|
|
247
|
+
env.compact(compact.op);
|
|
248
|
+
if (compact.checkpoint)
|
|
249
|
+
env.checkpoint();
|
|
250
|
+
},
|
|
251
|
+
.put_account => |account| {
|
|
252
|
+
env.forest.grooves.accounts.put(&account);
|
|
253
|
+
try model.put(account.id, account);
|
|
254
|
+
},
|
|
255
|
+
.get_account => |id| {
|
|
256
|
+
// Get account from lsm.
|
|
257
|
+
env.prefetch_account(id);
|
|
258
|
+
const lsm_account = env.forest.grooves.accounts.get(id);
|
|
259
|
+
|
|
260
|
+
// Compare result to model.
|
|
261
|
+
const model_account = model.get(id);
|
|
262
|
+
if (model_account == null) {
|
|
263
|
+
assert(lsm_account == null);
|
|
264
|
+
} else {
|
|
265
|
+
assert(std.mem.eql(
|
|
266
|
+
u8,
|
|
267
|
+
std.mem.asBytes(&model_account.?),
|
|
268
|
+
std.mem.asBytes(lsm_account.?),
|
|
269
|
+
));
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
pub fn run_fuzz_ops(fuzz_ops: []const FuzzOp) !void {
|
|
278
|
+
// Init mocked storage.
|
|
279
|
+
var storage = try Storage.init(
|
|
280
|
+
allocator,
|
|
281
|
+
Environment.size_max,
|
|
282
|
+
Storage.Options{
|
|
283
|
+
// We don't apply storage faults yet, so this seed doesn't matter.
|
|
284
|
+
.seed = 0xdeadbeef,
|
|
285
|
+
.read_latency_min = 0,
|
|
286
|
+
.read_latency_mean = 0,
|
|
287
|
+
.write_latency_min = 0,
|
|
288
|
+
.write_latency_mean = 0,
|
|
289
|
+
.read_fault_probability = 0,
|
|
290
|
+
.write_fault_probability = 0,
|
|
291
|
+
},
|
|
292
|
+
0,
|
|
293
|
+
.{
|
|
294
|
+
.first_offset = 0,
|
|
295
|
+
.period = 0,
|
|
296
|
+
},
|
|
297
|
+
);
|
|
298
|
+
defer storage.deinit(allocator);
|
|
299
|
+
|
|
300
|
+
try Environment.format(&storage);
|
|
301
|
+
try Environment.run(&storage, fuzz_ops);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
fn random_id(random: std.rand.Random, comptime Int: type) Int {
|
|
305
|
+
// We have two opposing desires for random ids:
|
|
306
|
+
const avg_int: Int = if (random.boolean())
|
|
307
|
+
// 1. We want to cause many collisions.
|
|
308
|
+
8
|
|
309
|
+
else
|
|
310
|
+
// 2. We want to generate enough ids that the cache can't hold them all.
|
|
311
|
+
Environment.cache_entries_max;
|
|
312
|
+
return fuzz.random_int_exponential(random, Int, avg_int);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
pub fn generate_fuzz_ops(random: std.rand.Random) ![]const FuzzOp {
|
|
316
|
+
const fuzz_op_count = @minimum(
|
|
317
|
+
@as(usize, 1E7),
|
|
318
|
+
fuzz.random_int_exponential(random, usize, 1E6),
|
|
319
|
+
);
|
|
320
|
+
log.info("fuzz_op_count = {}", .{fuzz_op_count});
|
|
321
|
+
|
|
322
|
+
const fuzz_ops = try allocator.alloc(FuzzOp, fuzz_op_count);
|
|
323
|
+
errdefer allocator.free(fuzz_ops);
|
|
324
|
+
|
|
325
|
+
var fuzz_op_distribution = fuzz.Distribution(FuzzOpTag){
|
|
326
|
+
// Maybe compact more often than forced to by `puts_since_compact`.
|
|
327
|
+
.compact = if (random.boolean()) 0 else 1,
|
|
328
|
+
// Always do puts, and always more puts than removes.
|
|
329
|
+
.put_account = config.lsm_batch_multiple * 2,
|
|
330
|
+
// Maybe do some gets.
|
|
331
|
+
.get_account = if (random.boolean()) 0 else config.lsm_batch_multiple,
|
|
332
|
+
};
|
|
333
|
+
log.info("fuzz_op_distribution = {d:.2}", .{fuzz_op_distribution});
|
|
334
|
+
|
|
335
|
+
log.info("puts_since_compact_max = {}", .{Environment.puts_since_compact_max});
|
|
336
|
+
log.info("compacts_per_checkpoint = {}", .{Environment.compacts_per_checkpoint});
|
|
337
|
+
|
|
338
|
+
var id_to_timestamp = std.hash_map.AutoHashMap(u128, u64).init(allocator);
|
|
339
|
+
defer id_to_timestamp.deinit();
|
|
340
|
+
|
|
341
|
+
var op: u64 = 1;
|
|
342
|
+
var puts_since_compact: usize = 0;
|
|
343
|
+
for (fuzz_ops) |*fuzz_op, fuzz_op_index| {
|
|
344
|
+
const fuzz_op_tag: FuzzOpTag = if (puts_since_compact >= Environment.puts_since_compact_max)
|
|
345
|
+
// We have to compact before doing any other operations.
|
|
346
|
+
.compact
|
|
347
|
+
else
|
|
348
|
+
// Otherwise pick a random FuzzOp.
|
|
349
|
+
fuzz.random_enum(random, FuzzOpTag, fuzz_op_distribution);
|
|
350
|
+
fuzz_op.* = switch (fuzz_op_tag) {
|
|
351
|
+
.compact => compact: {
|
|
352
|
+
const compact_op = op;
|
|
353
|
+
op += 1;
|
|
354
|
+
const checkpoint =
|
|
355
|
+
// Can only checkpoint on the last beat of the bar.
|
|
356
|
+
compact_op % config.lsm_batch_multiple == config.lsm_batch_multiple - 1 and
|
|
357
|
+
// Checkpoint at roughly the same rate as log wraparound.
|
|
358
|
+
random.uintLessThan(usize, Environment.compacts_per_checkpoint) == 0;
|
|
359
|
+
break :compact FuzzOp{
|
|
360
|
+
.compact = .{
|
|
361
|
+
.op = compact_op,
|
|
362
|
+
.checkpoint = checkpoint,
|
|
363
|
+
},
|
|
364
|
+
};
|
|
365
|
+
},
|
|
366
|
+
.put_account => put_account: {
|
|
367
|
+
const id = random_id(random, u128);
|
|
368
|
+
// `timestamp` just needs to be unique, but we're not allowed to change the timestamp of an existing account.
|
|
369
|
+
const timestamp = id_to_timestamp.get(id) orelse fuzz_op_index;
|
|
370
|
+
try id_to_timestamp.put(id, timestamp);
|
|
371
|
+
break :put_account FuzzOp{ .put_account = Account{
|
|
372
|
+
.id = id,
|
|
373
|
+
.timestamp = timestamp,
|
|
374
|
+
.user_data = random_id(random, u128),
|
|
375
|
+
.reserved = [_]u8{0} ** 48,
|
|
376
|
+
.ledger = random_id(random, u32),
|
|
377
|
+
.code = random_id(random, u16),
|
|
378
|
+
.flags = .{
|
|
379
|
+
.debits_must_not_exceed_credits = random.boolean(),
|
|
380
|
+
.credits_must_not_exceed_debits = random.boolean(),
|
|
381
|
+
},
|
|
382
|
+
.debits_pending = random.int(u64),
|
|
383
|
+
.debits_posted = random.int(u64),
|
|
384
|
+
.credits_pending = random.int(u64),
|
|
385
|
+
.credits_posted = random.int(u64),
|
|
386
|
+
} };
|
|
387
|
+
},
|
|
388
|
+
.get_account => FuzzOp{
|
|
389
|
+
.get_account = random_id(random, u128),
|
|
390
|
+
},
|
|
391
|
+
};
|
|
392
|
+
switch (fuzz_op.*) {
|
|
393
|
+
.compact => puts_since_compact = 0,
|
|
394
|
+
.put_account => puts_since_compact += 1,
|
|
395
|
+
.get_account => {},
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return fuzz_ops;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
pub fn main() !void {
|
|
403
|
+
const fuzz_args = try fuzz.parse_fuzz_args(allocator);
|
|
404
|
+
var rng = std.rand.DefaultPrng.init(fuzz_args.seed);
|
|
405
|
+
|
|
406
|
+
const fuzz_ops = try generate_fuzz_ops(rng.random());
|
|
407
|
+
defer allocator.free(fuzz_ops);
|
|
408
|
+
|
|
409
|
+
try run_fuzz_ops(fuzz_ops);
|
|
410
|
+
|
|
411
|
+
log.info("Passed!", .{});
|
|
412
|
+
}
|