tigerbeetle-node 0.9.0 → 0.10.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.
Files changed (83) hide show
  1. package/README.md +3 -2
  2. package/dist/index.d.ts +66 -61
  3. package/dist/index.js +66 -61
  4. package/dist/index.js.map +1 -1
  5. package/package.json +1 -1
  6. package/src/index.ts +5 -0
  7. package/src/node.zig +17 -18
  8. package/src/tigerbeetle/scripts/benchmark.bat +4 -3
  9. package/src/tigerbeetle/scripts/benchmark.sh +25 -10
  10. package/src/tigerbeetle/scripts/install.sh +2 -1
  11. package/src/tigerbeetle/scripts/install_zig.sh +14 -18
  12. package/src/tigerbeetle/scripts/upgrade_ubuntu_kernel.sh +12 -3
  13. package/src/tigerbeetle/scripts/vopr.sh +5 -5
  14. package/src/tigerbeetle/src/benchmark.zig +17 -9
  15. package/src/tigerbeetle/src/benchmark_array_search.zig +317 -0
  16. package/src/tigerbeetle/src/benchmarks/perf.zig +299 -0
  17. package/src/tigerbeetle/src/c/tb_client/context.zig +103 -0
  18. package/src/tigerbeetle/src/c/tb_client/packet.zig +80 -0
  19. package/src/tigerbeetle/src/c/tb_client/signal.zig +288 -0
  20. package/src/tigerbeetle/src/c/tb_client/thread.zig +329 -0
  21. package/src/tigerbeetle/src/c/tb_client.h +201 -0
  22. package/src/tigerbeetle/src/c/tb_client.zig +101 -0
  23. package/src/tigerbeetle/src/c/test.zig +1 -0
  24. package/src/tigerbeetle/src/cli.zig +142 -83
  25. package/src/tigerbeetle/src/config.zig +119 -10
  26. package/src/tigerbeetle/src/demo.zig +12 -8
  27. package/src/tigerbeetle/src/demo_05_post_pending_transfers.zig +2 -2
  28. package/src/tigerbeetle/src/ewah.zig +318 -0
  29. package/src/tigerbeetle/src/ewah_benchmark.zig +121 -0
  30. package/src/tigerbeetle/src/eytzinger_benchmark.zig +317 -0
  31. package/src/tigerbeetle/src/fifo.zig +17 -1
  32. package/src/tigerbeetle/src/io/darwin.zig +12 -10
  33. package/src/tigerbeetle/src/io/linux.zig +25 -9
  34. package/src/tigerbeetle/src/io/windows.zig +13 -9
  35. package/src/tigerbeetle/src/iops.zig +101 -0
  36. package/src/tigerbeetle/src/lsm/binary_search.zig +214 -0
  37. package/src/tigerbeetle/src/lsm/bloom_filter.zig +82 -0
  38. package/src/tigerbeetle/src/lsm/compaction.zig +603 -0
  39. package/src/tigerbeetle/src/lsm/composite_key.zig +75 -0
  40. package/src/tigerbeetle/src/lsm/direction.zig +11 -0
  41. package/src/tigerbeetle/src/lsm/eytzinger.zig +587 -0
  42. package/src/tigerbeetle/src/lsm/forest.zig +630 -0
  43. package/src/tigerbeetle/src/lsm/grid.zig +473 -0
  44. package/src/tigerbeetle/src/lsm/groove.zig +939 -0
  45. package/src/tigerbeetle/src/lsm/k_way_merge.zig +452 -0
  46. package/src/tigerbeetle/src/lsm/level_iterator.zig +296 -0
  47. package/src/tigerbeetle/src/lsm/manifest.zig +680 -0
  48. package/src/tigerbeetle/src/lsm/manifest_level.zig +1169 -0
  49. package/src/tigerbeetle/src/lsm/manifest_log.zig +904 -0
  50. package/src/tigerbeetle/src/lsm/node_pool.zig +231 -0
  51. package/src/tigerbeetle/src/lsm/posted_groove.zig +399 -0
  52. package/src/tigerbeetle/src/lsm/segmented_array.zig +998 -0
  53. package/src/tigerbeetle/src/lsm/set_associative_cache.zig +844 -0
  54. package/src/tigerbeetle/src/lsm/table.zig +932 -0
  55. package/src/tigerbeetle/src/lsm/table_immutable.zig +196 -0
  56. package/src/tigerbeetle/src/lsm/table_iterator.zig +295 -0
  57. package/src/tigerbeetle/src/lsm/table_mutable.zig +123 -0
  58. package/src/tigerbeetle/src/lsm/test.zig +429 -0
  59. package/src/tigerbeetle/src/lsm/tree.zig +1085 -0
  60. package/src/tigerbeetle/src/main.zig +119 -109
  61. package/src/tigerbeetle/src/message_bus.zig +49 -48
  62. package/src/tigerbeetle/src/message_pool.zig +15 -2
  63. package/src/tigerbeetle/src/ring_buffer.zig +126 -30
  64. package/src/tigerbeetle/src/simulator.zig +76 -44
  65. package/src/tigerbeetle/src/state_machine.zig +1022 -585
  66. package/src/tigerbeetle/src/storage.zig +46 -16
  67. package/src/tigerbeetle/src/test/cluster.zig +109 -63
  68. package/src/tigerbeetle/src/test/message_bus.zig +15 -24
  69. package/src/tigerbeetle/src/test/network.zig +26 -17
  70. package/src/tigerbeetle/src/test/state_checker.zig +7 -5
  71. package/src/tigerbeetle/src/test/state_machine.zig +159 -69
  72. package/src/tigerbeetle/src/test/storage.zig +57 -28
  73. package/src/tigerbeetle/src/tigerbeetle.zig +5 -0
  74. package/src/tigerbeetle/src/unit_tests.zig +8 -0
  75. package/src/tigerbeetle/src/util.zig +51 -0
  76. package/src/tigerbeetle/src/vsr/client.zig +21 -7
  77. package/src/tigerbeetle/src/vsr/journal.zig +154 -167
  78. package/src/tigerbeetle/src/vsr/replica.zig +744 -226
  79. package/src/tigerbeetle/src/vsr/superblock.zig +1743 -0
  80. package/src/tigerbeetle/src/vsr/superblock_client_table.zig +258 -0
  81. package/src/tigerbeetle/src/vsr/superblock_free_set.zig +644 -0
  82. package/src/tigerbeetle/src/vsr/superblock_manifest.zig +546 -0
  83. package/src/tigerbeetle/src/vsr.zig +43 -115
@@ -0,0 +1,201 @@
1
+ #ifndef TB_CLIENT_C
2
+ #define TB_CLIENT_C
3
+
4
+ #include <stddef.h>
5
+ #include <stdint.h>
6
+ #include <stdbool.h>
7
+
8
+ typedef __uint128_t tb_uint128_t;
9
+
10
+
11
+ typedef enum TB_ACCOUNT_FLAGS {
12
+ TB_ACCOUNT_LINKED = 1 << 0,
13
+ TB_ACCOUNT_DEBITS_MUST_NOT_EXCEED_CREDITS = 1 << 1,
14
+ TB_ACCOUNT_CREDITS_MUST_NOT_EXCEED_DEBITS = 1 << 2,
15
+ } TB_ACCOUNT_FLAGS;
16
+
17
+ typedef struct tb_account_t {
18
+ tb_uint128_t id;
19
+ tb_uint128_t user_data;
20
+ uint8_t reserved[48];
21
+ uint16_t unit;
22
+ uint16_t code;
23
+ uint32_t flags;
24
+ uint64_t debits_reserved;
25
+ uint64_t debits_accepted;
26
+ uint64_t credits_reserved;
27
+ uint64_t credits_accepted;
28
+ uint64_t timestamp;
29
+ } tb_account_t;
30
+
31
+ typedef enum TB_TRANSFER_FLAGS {
32
+ TB_TRANSFER_LINKED = 1 << 0,
33
+ TB_TRANSFER_TWO_PHASE_COMMIT = 1 << 1,
34
+ TB_TRANSFER_CONDITION = 1 << 2,
35
+ } TB_TRANSFER_FLAGS;
36
+
37
+ typedef struct tb_transfer_t {
38
+ tb_uint128_t id;
39
+ tb_uint128_t debit_account_id;
40
+ tb_uint128_t credit_account_id;
41
+ tb_uint128_t user_data;
42
+ uint8_t reserved[32];
43
+ uint64_t timeout;
44
+ uint32_t code;
45
+ uint32_t flags;
46
+ uint64_t amount;
47
+ uint64_t timestamp;
48
+ } tb_transfer_t;
49
+
50
+ typedef enum TB_COMMIT_FLAGS {
51
+ TB_COMMIT_LINKED = 1 << 0,
52
+ TB_COMMIT_REJECT = 1 << 1,
53
+ TB_COMMIT_PREIMAGE = 1 << 2,
54
+ } TB_COMMIT_FLAGS;
55
+
56
+ typedef struct tb_commit_t {
57
+ tb_uint128_t id;
58
+ uint8_t reserved[32];
59
+ uint32_t code;
60
+ uint32_t flags;
61
+ uint64_t timestamp;
62
+ } tb_commit_t;
63
+
64
+ typedef enum TB_CREATE_ACCOUNT_RESULT {
65
+ TB_CREATE_ACCOUNT_OK,
66
+ TB_CREATE_ACCOUNT_LINKED_EVENT_FAILED,
67
+ TB_CREATE_ACCOUNT_EXISTS,
68
+ TB_CREATE_ACCOUNT_EXISTS_WITH_DIFFERENT_USER_DATA,
69
+ TB_CREATE_ACCOUNT_EXISTS_WITH_DIFFERENT_RESERVED_FIELD,
70
+ TB_CREATE_ACCOUNT_EXISTS_WITH_DIFFERENT_UNIT,
71
+ TB_CREATE_ACCOUNT_EXISTS_WITH_DIFFERENT_CODE,
72
+ TB_CREATE_ACCOUNT_EXISTS_WITH_DIFFERENT_FLAGS,
73
+ TB_CREATE_ACCOUNT_EXCEEDS_CREDITS,
74
+ TB_CREATE_ACCOUNT_EXCEEDS_DEBITS,
75
+ TB_CREATE_ACCOUNT_RESERVE_FAILED,
76
+ TB_CREATE_ACCOUNT_RESERVE_FLAG_PADDING
77
+ } TB_CREATE_ACCOUNT_RESULT;
78
+
79
+ typedef enum TB_CREATE_TRANSFER_RESULT {
80
+ TB_CREATE_TRANSFER_OK,
81
+ TB_CREATE_TRANSFER_LINKED_EVENT_FAILED,
82
+ TB_CREATE_TRANSFER_EXISTS,
83
+ TB_CREATE_TRANSFER_EXISTS_WITH_DIFFERENT_DEBIT_ACCOUNT_ID,
84
+ TB_CREATE_TRANSFER_EXISTS_WITH_DIFFERENT_CREDIT_ACCOUNT_ID,
85
+ TB_CREATE_TRANSFER_EXISTS_WITH_DIFFERENT_USER_DATA,
86
+ TB_CREATE_TRANSFER_EXISTS_WITH_DIFFERENT_RESERVED_FIELD,
87
+ TB_CREATE_TRANSFER_EXISTS_WITH_DIFFERENT_CODE,
88
+ TB_CREATE_TRANSFER_EXISTS_WITH_DIFFERENT_AMOUNT,
89
+ TB_CREATE_TRANSFER_EXISTS_WITH_DIFFERENT_TIMEOUT,
90
+ TB_CREATE_TRANSFER_EXISTS_WITH_DIFFERENT_FLAGS,
91
+ TB_CREATE_TRANSFER_EXISTS_AND_ALREADY_COMMITTED_AND_ACCEPTED,
92
+ TB_CREATE_TRANSFER_EXISTS_AND_ALREADY_COMMITTED_AND_REJECTED,
93
+ TB_CREATE_TRANSFER_RESERVED_FIELD,
94
+ TB_CREATE_TRANSFER_RESERVED_FLAG_PADDING,
95
+ TB_CREATE_TRANSFER_DEBIT_ACCOUNT_NOT_FOUND,
96
+ TB_CREATE_TRANSFER_CREDIT_ACCOUNT_NOT_FOUND,
97
+ TB_CREATE_TRANSFER_ACCOUNTS_ARE_THE_SAME,
98
+ TB_CREATE_TRANSFER_ACCOUNTS_HAVE_DIFFERENT_UNITS,
99
+ TB_CREATE_TRANSFER_AMOUNT_IS_ZERO,
100
+ TB_CREATE_TRANSFER_EXCEEDS_CREDITS,
101
+ TB_CREATE_TRANSFER_EXCEEDS_DEBITS,
102
+ TB_CREATE_TRANSFER_TWO_PHASE_COMMIT_MUST_TIMEOUT,
103
+ TB_CREATE_TRANSFER_TIMEOUT_RESERVED_FOR_TWO_PHASE_COMMIT
104
+ } TB_CREATE_TRANSFER_RESULT;
105
+
106
+ typedef enum TB_COMMIT_TRANSFER_RESULT {
107
+ TB_COMMIT_TRANSFER_OK,
108
+ TB_COMMIT_TRANSFER_LINKED_EVENT_FAILED,
109
+ TB_COMMIT_TRANSFER_RESERVE_FAILED,
110
+ TB_COMMIT_TRANSFER_RESERVE_FLAG_PADDING,
111
+ TB_COMMIT_TRANSFER_NOT_FOUND,
112
+ TB_COMMIT_TRANSFER_NOT_TWO_PHASE_COMMIT,
113
+ TB_COMMIT_TRANSFER_EXPIRED,
114
+ TB_COMMIT_TRANSFER_ALREADY_COMMITTED,
115
+ TB_COMMIT_TRANSFER_ALREADY_COMMITTED_BUT_ACCEPTED,
116
+ TB_COMMIT_TRANSFER_ALREADY_COMMITTED_BUT_REJECTED,
117
+ TB_COMMIT_TRANSFER_DEBIT_ACCOUNT_NOT_FOUND,
118
+ TB_COMMIT_TRANSFER_CREDIT_ACCOUNT_NOT_FOUND,
119
+ TB_COMMIT_TRANSFER_EXCEEDS_CREDITS,
120
+ TB_COMMIT_TRANSFER_EXCEEDS_DEBITS,
121
+ TB_COMMIT_TRANSFER_CONDITION_REQUIRES_PREIMAGE,
122
+ TB_COMMIT_TRANSFER_PREIMAGE_REQUIRES_CONDITION,
123
+ TB_COMMIT_TRANSFER_PREIMAGE_INVALID
124
+ } TB_COMMIT_TRANSFER_RESULT;
125
+
126
+ typedef struct tb_create_accounts_result_t {
127
+ uint32_t index;
128
+ TB_CREATE_ACCOUNT_RESULT result;
129
+ } tb_create_accounts_result_t;
130
+
131
+ typedef struct tb_create_transfers_result_t {
132
+ uint32_t index;
133
+ TB_CREATE_TRANSFER_RESULT result;
134
+ } tb_create_transfers_result_t;
135
+
136
+ typedef struct tb_commit_transfers_result_t {
137
+ uint32_t index;
138
+ TB_COMMIT_TRANSFER_RESULT result;
139
+ } tb_commit_transfers_result_t;
140
+
141
+ typedef enum TB_OPERATION {
142
+ TB_OP_CREATE_ACCOUNTS = 3,
143
+ TB_OP_CREATE_TRANSFERS = 4,
144
+ TB_OP_COMMIT_TRANSFERS = 5,
145
+ TB_OP_LOOKUP_ACCOUNTS = 6,
146
+ TB_OP_LOOKUP_TRANSFERS = 7
147
+ } TB_OPERATION;
148
+
149
+ typedef enum TB_PACKET_STATUS {
150
+ TB_PACKET_OK,
151
+ TB_PACKET_TOO_MUCH_DATA,
152
+ TB_PACKET_INVALID_OPERATION,
153
+ TB_PACKET_INVALID_DATA_SIZE
154
+ } TB_PACKET_STATUS;
155
+
156
+ typedef struct tb_packet_t {
157
+ struct tb_packet_t* next;
158
+ void* user_data;
159
+ uint8_t operation;
160
+ uint8_t status;
161
+ uint32_t data_size;
162
+ void* data;
163
+ } tb_packet_t;
164
+
165
+ typedef struct tb_packet_list_t {
166
+ struct tb_packet_t* head;
167
+ struct tb_packet_t* tail;
168
+ } tb_packet_list_t;
169
+
170
+ typedef void* tb_client_t;
171
+
172
+ typedef enum TB_STATUS {
173
+ TB_STATUS_SUCCESS = 0,
174
+ TB_STATUS_UNEXPECTED = 1,
175
+ TB_STATUS_OUT_OF_MEMORY = 2,
176
+ TB_STATUS_INVALID_ADDRESS = 3,
177
+ TB_STATUS_SYSTEM_RESOURCES = 4,
178
+ TB_STATUS_NETWORK_SUBSYSTEM = 5,
179
+ } TB_STATUS;
180
+
181
+ TB_STATUS tb_client_init(
182
+ tb_client_t* out_client,
183
+ tb_packet_list_t* out_packets,
184
+ uint32_t cluster_id,
185
+ const char* address_ptr,
186
+ uint32_t address_len,
187
+ uint32_t num_packets,
188
+ uintptr_t on_completion_ctx,
189
+ void (*on_completion_fn)(uintptr_t, tb_client_t, tb_packet_t*, const uint8_t*, uint32_t)
190
+ );
191
+
192
+ void tb_client_submit(
193
+ tb_client_t client,
194
+ tb_packet_list_t* packets
195
+ );
196
+
197
+ void tb_client_deinit(
198
+ tb_client_t client
199
+ );
200
+
201
+ #endif // TB_CLIENT_C
@@ -0,0 +1,101 @@
1
+ const std = @import("std");
2
+ const builtin = @import("builtin");
3
+
4
+ pub const tb_packet_t = @import("tb_client/packet.zig").Packet;
5
+ pub const tb_packet_list_t = tb_packet_t.List;
6
+ pub const tb_packet_status_t = tb_packet_t.Status;
7
+
8
+ pub const tb_client_t = *anyopaque;
9
+ pub const tb_status_t = enum(c_int) {
10
+ success = 0,
11
+ unexpected,
12
+ out_of_memory,
13
+ invalid_address,
14
+ system_resources,
15
+ network_subsystem,
16
+ };
17
+
18
+ pub const tb_completion_t = fn (
19
+ context: usize,
20
+ client: tb_client_t,
21
+ packet: *tb_packet_t,
22
+ result_ptr: ?[*]const u8,
23
+ result_len: u32,
24
+ ) callconv(.C) void;
25
+
26
+ const ContextType = @import("tb_client/context.zig").ContextType;
27
+ const ContextImplementation = @import("tb_client/context.zig").ContextImplementation;
28
+
29
+ pub fn context_to_client(implementation: *ContextImplementation) tb_client_t {
30
+ return @ptrCast(tb_client_t, implementation);
31
+ }
32
+
33
+ fn client_to_context(tb_client: tb_client_t) *ContextImplementation {
34
+ return @ptrCast(*ContextImplementation, @alignCast(@alignOf(ContextImplementation), tb_client));
35
+ }
36
+
37
+ const DefaultContext = blk: {
38
+ const Storage = @import("../storage.zig").Storage;
39
+ const MessageBus = @import("../message_bus.zig").MessageBusClient;
40
+ const StateMachine = @import("../state_machine.zig").StateMachineType(Storage);
41
+ break :blk ContextType(StateMachine, MessageBus);
42
+ };
43
+
44
+ // const TestingContext = blk: {
45
+ // const MessageBus = @import("test_message_bus.zig").MessageBusClient;
46
+ // const StateMachine = @import("../state_machine.zig").StateMachine;
47
+ // break :blk ContextType(StateMachine, MessageBus);
48
+ // };
49
+
50
+ // Pick the most suitable allocator
51
+ const global_allocator = if (builtin.link_libc)
52
+ std.heap.c_allocator
53
+ else if (builtin.target.os.tag == .windows)
54
+ (struct {
55
+ var gpa = std.heap.HeapAllocator.init();
56
+ }).gpa.allocator()
57
+ else
58
+ @compileError("tb_client must be built with libc");
59
+
60
+ pub export fn tb_client_init(
61
+ out_client: *tb_client_t,
62
+ out_packets: *tb_packet_list_t,
63
+ cluster_id: u32,
64
+ addresses_ptr: [*:0]const u8,
65
+ addresses_len: u32,
66
+ num_packets: u32,
67
+ on_completion_ctx: usize,
68
+ on_completion_fn: tb_completion_t,
69
+ ) tb_status_t {
70
+ var init_fn = DefaultContext.init;
71
+ // if (addresses_len == 0) {
72
+ // init_fn = TestingContext.init;
73
+ // }
74
+
75
+ return (init_fn)(
76
+ global_allocator,
77
+ out_client,
78
+ out_packets,
79
+ cluster_id,
80
+ addresses_ptr,
81
+ addresses_len,
82
+ num_packets,
83
+ on_completion_ctx,
84
+ on_completion_fn,
85
+ );
86
+ }
87
+
88
+ pub export fn tb_client_submit(
89
+ client: tb_client_t,
90
+ packets: *tb_packet_list_t,
91
+ ) void {
92
+ const context = client_to_context(client);
93
+ (context.submit_fn)(context, packets);
94
+ }
95
+
96
+ pub export fn tb_client_deinit(
97
+ client: tb_client_t,
98
+ ) void {
99
+ const context = client_to_context(client);
100
+ (context.deinit_fn)(context);
101
+ }
@@ -0,0 +1 @@
1
+ // TODO
@@ -10,89 +10,93 @@ const config = @import("config.zig");
10
10
  const vsr = @import("vsr.zig");
11
11
  const IO = @import("io.zig").IO;
12
12
 
13
+ // TODO Document --memory
13
14
  const usage = fmt.comptimePrint(
14
15
  \\Usage:
15
16
  \\
16
17
  \\ tigerbeetle [-h | --help]
17
18
  \\
18
- \\ tigerbeetle init [--directory=<path>] --cluster=<integer> --replica=<index>
19
+ \\ tigerbeetle format --cluster=<integer> --replica=<index> <path>
19
20
  \\
20
- \\ tigerbeetle start [--directory=<path>] --cluster=<integer> --replica=<index> --addresses=<addresses>
21
+ \\ tigerbeetle start --addresses=<addresses> <path>
21
22
  \\
22
23
  \\Commands:
23
24
  \\
24
- \\ init Create a new .tigerbeetle data file. Requires the --cluster and
25
- \\ --replica options. The file will be created in the path set by
26
- \\ the --directory option if provided. Otherwise, it will be created in
27
- \\ the default {[default_directory]s}.
25
+ \\ format Create a TigerBeetle replica data file at <path>.
26
+ \\ The --cluster and --replica arguments are required.
27
+ \\ Each TigerBeetle replica must have its own data file.
28
28
  \\
29
- \\ start Run a TigerBeetle replica as part of the cluster specified by the
30
- \\ --cluster, --replica, and --addresses options. This requires an
31
- \\ existing .tigerbeetle data file, either in the default
32
- \\ {[default_directory]s} or the path set with --directory.
29
+ \\ start Run a TigerBeetle replica from the data file at <path>.
33
30
  \\
34
31
  \\Options:
35
32
  \\
36
33
  \\ -h, --help
37
34
  \\ Print this help message and exit.
38
35
  \\
39
- \\ --directory=<path>
40
- \\ Set the directory used to store .tigerbeetle data files. If this option is
41
- \\ omitted, the default {[default_directory]s} will be used.
42
- \\
43
36
  \\ --cluster=<integer>
44
37
  \\ Set the cluster ID to the provided 32-bit unsigned integer.
45
38
  \\
46
39
  \\ --replica=<index>
47
- \\ Set the zero-based index that will be used for this replica process.
48
- \\ The value of this option will be interpreted as an index into the --addresses array.
40
+ \\ Set the zero-based index that will be used for the replica process.
41
+ \\ The value of this argument will be interpreted as an index into the --addresses array.
49
42
  \\
50
43
  \\ --addresses=<addresses>
51
- \\ Set the addresses of all replicas in the cluster. Accepts a
52
- \\ comma-separated list of IPv4 addresses with port numbers.
53
- \\ Either the IPv4 address or port number, but not both, may be
54
- \\ ommited in which case a default of {[default_address]s} or {[default_port]d}
44
+ \\ Set the addresses of all replicas in the cluster.
45
+ \\ Accepts a comma-separated list of IPv4 addresses with port numbers.
46
+ \\ Either the IPv4 address or port number (but not both) may be omitted,
47
+ \\ in which case a default of {[default_address]s} or {[default_port]d}
55
48
  \\ will be used.
56
49
  \\
57
50
  \\Examples:
58
51
  \\
59
- \\ tigerbeetle init --cluster=0 --replica=0 --directory=/var/lib/tigerbeetle
60
- \\ tigerbeetle init --cluster=0 --replica=1 --directory=/var/lib/tigerbeetle
61
- \\ tigerbeetle init --cluster=0 --replica=2 --directory=/var/lib/tigerbeetle
52
+ \\ tigerbeetle format --cluster=7 --replica=0 7_0.tigerbeetle
53
+ \\ tigerbeetle format --cluster=7 --replica=1 7_1.tigerbeetle
54
+ \\ tigerbeetle format --cluster=7 --replica=2 7_2.tigerbeetle
62
55
  \\
63
- \\ tigerbeetle start --cluster=0 --replica=0 --addresses=127.0.0.1:3003,127.0.0.1:3001,127.0.0.1:3002
64
- \\ tigerbeetle start --cluster=0 --replica=1 --addresses=3003,3001,3002
65
- \\ tigerbeetle start --cluster=0 --replica=2 --addresses=3003,3001,3002
56
+ \\ tigerbeetle start --addresses=127.0.0.1:3003,127.0.0.1:3001,127.0.0.1:3002 7_0.tigerbeetle
57
+ \\ tigerbeetle start --addresses=3003,3001,3002 7_1.tigerbeetle
58
+ \\ tigerbeetle start --addresses=3003,3001,3002 7_2.tigerbeetle
66
59
  \\
67
- \\ tigerbeetle start --cluster=1 --replica=0 --addresses=192.168.0.1,192.168.0.2,192.168.0.3
60
+ \\ tigerbeetle start --addresses=192.168.0.1,192.168.0.2,192.168.0.3 7_0.tigerbeetle
68
61
  \\
69
62
  , .{
70
- .default_directory = config.directory,
71
63
  .default_address = config.address,
72
64
  .default_port = config.port,
73
65
  });
74
66
 
75
67
  pub const Command = union(enum) {
76
- init: struct {
68
+ format: struct {
69
+ args_allocated: std.ArrayList([:0]const u8),
77
70
  cluster: u32,
78
71
  replica: u8,
79
- dir_fd: os.fd_t,
72
+ path: [:0]const u8,
80
73
  },
81
74
  start: struct {
82
- cluster: u32,
83
- replica: u8,
75
+ args_allocated: std.ArrayList([:0]const u8),
84
76
  addresses: []net.Address,
85
- dir_fd: os.fd_t,
77
+ memory: u64,
78
+ path: [:0]const u8,
86
79
  },
80
+
81
+ pub fn deinit(command: Command, allocator: std.mem.Allocator) void {
82
+ var args_allocated = switch (command) {
83
+ .format => |cmd| cmd.args_allocated,
84
+ .start => |cmd| cmd.args_allocated,
85
+ };
86
+
87
+ for (args_allocated.items) |arg| allocator.free(arg);
88
+ args_allocated.deinit();
89
+ }
87
90
  };
88
91
 
89
- /// Parse the command line arguments passed to the tigerbeetle binary.
92
+ /// Parse the command line arguments passed to the `tigerbeetle` binary.
90
93
  /// Exits the program with a non-zero exit code if an error is found.
91
94
  pub fn parse_args(allocator: std.mem.Allocator) !Command {
92
- var maybe_cluster: ?[]const u8 = null;
93
- var maybe_replica: ?[]const u8 = null;
94
- var maybe_addresses: ?[]const u8 = null;
95
- var maybe_directory: ?[:0]const u8 = null;
95
+ var path: ?[:0]const u8 = null;
96
+ var cluster: ?[]const u8 = null;
97
+ var replica: ?[]const u8 = null;
98
+ var addresses: ?[]const u8 = null;
99
+ var memory: ?[]const u8 = null;
96
100
 
97
101
  var args = try std.process.argsWithAllocator(allocator);
98
102
  defer args.deinit();
@@ -100,17 +104,13 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
100
104
  // Keep track of the args from the ArgIterator above that were allocated
101
105
  // then free them all at the end of the scope.
102
106
  var args_allocated = std.ArrayList([:0]const u8).init(allocator);
103
- defer {
104
- for (args_allocated.items) |arg| allocator.free(arg);
105
- args_allocated.deinit();
106
- }
107
107
 
108
108
  // Skip argv[0] which is the name of this executable
109
109
  const did_skip = args.skip();
110
110
  assert(did_skip);
111
111
 
112
112
  const raw_command = try (args.next(allocator) orelse
113
- fatal("no command provided, expected 'start' or 'init'", .{}));
113
+ fatal("no command provided, expected 'start' or 'format'", .{}));
114
114
  defer allocator.free(raw_command);
115
115
 
116
116
  if (mem.eql(u8, raw_command, "-h") or mem.eql(u8, raw_command, "--help")) {
@@ -118,69 +118,59 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
118
118
  os.exit(0);
119
119
  }
120
120
  const command = meta.stringToEnum(meta.Tag(Command), raw_command) orelse
121
- fatal("unknown command '{s}', expected 'start' or 'init'", .{raw_command});
121
+ fatal("unknown command '{s}', expected 'start' or 'format'", .{raw_command});
122
122
 
123
123
  while (args.next(allocator)) |parsed_arg| {
124
124
  const arg = try parsed_arg;
125
125
  try args_allocated.append(arg);
126
126
 
127
127
  if (mem.startsWith(u8, arg, "--cluster")) {
128
- maybe_cluster = parse_flag("--cluster", arg);
128
+ cluster = parse_flag("--cluster", arg);
129
129
  } else if (mem.startsWith(u8, arg, "--replica")) {
130
- maybe_replica = parse_flag("--replica", arg);
130
+ replica = parse_flag("--replica", arg);
131
131
  } else if (mem.startsWith(u8, arg, "--addresses")) {
132
- maybe_addresses = parse_flag("--addresses", arg);
133
- } else if (mem.startsWith(u8, arg, "--directory")) {
134
- maybe_directory = parse_flag("--directory", arg);
132
+ addresses = parse_flag("--addresses", arg);
133
+ } else if (mem.startsWith(u8, arg, "--memory")) {
134
+ memory = parse_flag("--memory", arg);
135
135
  } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
136
136
  std.io.getStdOut().writeAll(usage) catch os.exit(1);
137
137
  os.exit(0);
138
- } else if (mem.startsWith(u8, arg, "--")) {
138
+ } else if (mem.startsWith(u8, arg, "-")) {
139
139
  fatal("unexpected argument: '{s}'", .{arg});
140
+ } else if (path == null) {
141
+ path = arg;
140
142
  } else {
141
143
  fatal("unexpected argument: '{s}' (must start with '--')", .{arg});
142
144
  }
143
145
  }
144
146
 
145
- const raw_cluster = maybe_cluster orelse fatal("required argument: --cluster", .{});
146
- const raw_replica = maybe_replica orelse fatal("required argument: --replica", .{});
147
-
148
- const cluster = parse_cluster(raw_cluster);
149
- const replica = parse_replica(raw_replica);
150
-
151
- const dir_path = maybe_directory orelse config.directory;
152
- const dir_fd = IO.open_dir(dir_path) catch |err|
153
- fatal("failed to open directory '{s}': {}", .{ dir_path, err });
154
-
155
147
  switch (command) {
156
- .init => {
157
- if (maybe_addresses != null) {
158
- fatal("--addresses: supported only by 'start' command", .{});
159
- }
148
+ .format => {
149
+ if (addresses != null) fatal("--addresses: supported only by 'start' command", .{});
150
+ if (memory != null) fatal("--memory: supported only by 'start' command", .{});
160
151
 
161
152
  return Command{
162
- .init = .{
163
- .cluster = cluster,
164
- .replica = replica,
165
- .dir_fd = dir_fd,
153
+ .format = .{
154
+ .args_allocated = args_allocated,
155
+ .cluster = parse_cluster(cluster orelse fatal("required: --cluster", .{})),
156
+ .replica = parse_replica(replica orelse fatal("required: --replica", .{})),
157
+ .path = path orelse fatal("required: <path>", .{}),
166
158
  },
167
159
  };
168
160
  },
169
161
  .start => {
170
- const raw_addresses = maybe_addresses orelse
171
- fatal("required argument: --addresses", .{});
172
- const addresses = parse_addresses(allocator, raw_addresses);
173
-
174
- if (replica >= addresses.len) {
175
- fatal("--replica: value greater than length of --addresses array", .{});
176
- }
162
+ if (cluster != null) fatal("--cluster: supported only by 'format' command", .{});
163
+ if (replica != null) fatal("--replica: supported only by 'format' command", .{});
177
164
 
178
165
  return Command{
179
166
  .start = .{
180
- .cluster = cluster,
181
- .replica = replica,
182
- .addresses = addresses,
183
- .dir_fd = dir_fd,
167
+ .args_allocated = args_allocated,
168
+ .addresses = parse_addresses(
169
+ allocator,
170
+ addresses orelse fatal("required: --addresses", .{}),
171
+ ),
172
+ .memory = if (memory) |m| parse_size(m) else config.memory_size_max_default,
173
+ .path = path orelse fatal("required: <path>", .{}),
184
174
  },
185
175
  };
186
176
  },
@@ -189,20 +179,20 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
189
179
 
190
180
  /// Format and print an error message followed by the usage string to stderr,
191
181
  /// then exit with an exit code of 1.
192
- fn fatal(comptime fmt_string: []const u8, args: anytype) noreturn {
182
+ pub fn fatal(comptime fmt_string: []const u8, args: anytype) noreturn {
193
183
  const stderr = std.io.getStdErr().writer();
194
184
  stderr.print("error: " ++ fmt_string ++ "\n", args) catch {};
195
185
  os.exit(1);
196
186
  }
197
187
 
198
- /// Parse e.g. `--cluster=1a2b3c` into `1a2b3c` with error handling.
188
+ /// Parse e.g. `--cluster=123` into `123` with error handling.
199
189
  fn parse_flag(comptime flag: []const u8, arg: [:0]const u8) [:0]const u8 {
200
190
  const value = arg[flag.len..];
201
191
  if (value.len < 2) {
202
192
  fatal("{s} argument requires a value", .{flag});
203
193
  }
204
194
  if (value[0] != '=') {
205
- fatal("expected '=' after {s} but found '{c}'", .{ flag, value[0] });
195
+ fatal("expected '=' after '{s}' but found '{c}'", .{ flag, value[0] });
206
196
  }
207
197
  return value[1..];
208
198
  }
@@ -230,8 +220,77 @@ fn parse_addresses(allocator: std.mem.Allocator, raw_addresses: []const u8) []ne
230
220
  error.PortOverflow => fatal("--addresses: port exceeds 65535", .{}),
231
221
  error.PortInvalid => fatal("--addresses: invalid port", .{}),
232
222
  error.AddressInvalid => fatal("--addresses: invalid IPv4 address", .{}),
233
- error.OutOfMemory => fatal("--addresses: out of memory", .{}),
223
+ error.OutOfMemory => fatal("out of memory", .{}),
224
+ };
225
+ }
226
+
227
+ fn parse_size(string: []const u8) u64 {
228
+ var value = mem.trim(u8, string, " ");
229
+
230
+ const unit: u64 = blk: {
231
+ if (parse_size_unit(&value, &[_][]const u8{ "TiB", "tib", "TB", "tb" })) {
232
+ break :blk 1024 * 1024 * 1024 * 1024;
233
+ } else if (parse_size_unit(&value, &[_][]const u8{ "GiB", "gib", "GB", "gb" })) {
234
+ break :blk 1024 * 1024 * 1024;
235
+ } else if (parse_size_unit(&value, &[_][]const u8{ "MiB", "mib", "MB", "mb" })) {
236
+ break :blk 1024 * 1024;
237
+ } else if (parse_size_unit(&value, &[_][]const u8{ "KiB", "kib", "KB", "kb" })) {
238
+ break :blk 1024;
239
+ } else {
240
+ break :blk 1;
241
+ }
242
+ };
243
+
244
+ const size = fmt.parseUnsigned(u64, value, 10) catch |err| switch (err) {
245
+ error.Overflow => fatal("size value exceeds a 64-bit unsigned integer", .{}),
246
+ error.InvalidCharacter => fatal("size value contains an invalid character", .{}),
234
247
  };
248
+
249
+ return size * unit;
250
+ }
251
+
252
+ fn parse_size_unit(value: *[]const u8, suffixes: []const []const u8) bool {
253
+ for (suffixes) |suffix| {
254
+ if (mem.endsWith(u8, value.*, suffix)) {
255
+ value.* = mem.trim(u8, value.*[0 .. value.*.len - suffix.len], " ");
256
+ return true;
257
+ }
258
+ }
259
+ return false;
260
+ }
261
+
262
+ test "parse_size" {
263
+ const expectEqual = std.testing.expectEqual;
264
+
265
+ const tib = 1024 * 1024 * 1024 * 1024;
266
+ const gib = 1024 * 1024 * 1024;
267
+ const mib = 1024 * 1024;
268
+ const kib = 1024;
269
+
270
+ try expectEqual(@as(u64, 0), parse_size("0"));
271
+ try expectEqual(@as(u64, 1), parse_size(" 1 "));
272
+ try expectEqual(@as(u64, 140737488355328), parse_size(" 140737488355328 "));
273
+ try expectEqual(@as(u64, 140737488355328), parse_size(" 128TiB "));
274
+
275
+ try expectEqual(@as(u64, 1 * tib), parse_size(" 1TiB "));
276
+ try expectEqual(@as(u64, 10 * tib), parse_size(" 10 tib "));
277
+ try expectEqual(@as(u64, 100 * tib), parse_size(" 100 TB "));
278
+ try expectEqual(@as(u64, 1000 * tib), parse_size(" 1000 tb "));
279
+
280
+ try expectEqual(@as(u64, 1 * gib), parse_size(" 1GiB "));
281
+ try expectEqual(@as(u64, 10 * gib), parse_size(" 10 gib "));
282
+ try expectEqual(@as(u64, 100 * gib), parse_size(" 100 GB "));
283
+ try expectEqual(@as(u64, 1000 * gib), parse_size(" 1000 gb "));
284
+
285
+ try expectEqual(@as(u64, 1 * mib), parse_size(" 1MiB "));
286
+ try expectEqual(@as(u64, 10 * mib), parse_size(" 10 mib "));
287
+ try expectEqual(@as(u64, 100 * mib), parse_size(" 100 MB "));
288
+ try expectEqual(@as(u64, 1000 * mib), parse_size(" 1000 mb "));
289
+
290
+ try expectEqual(@as(u64, 1 * kib), parse_size(" 1KiB "));
291
+ try expectEqual(@as(u64, 10 * kib), parse_size(" 10 kib "));
292
+ try expectEqual(@as(u64, 100 * kib), parse_size(" 100 KB "));
293
+ try expectEqual(@as(u64, 1000 * kib), parse_size(" 1000 kb "));
235
294
  }
236
295
 
237
296
  fn parse_replica(raw_replica: []const u8) u8 {