tigerbeetle 0.0.36 → 0.0.38

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 (248) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/ext/tb_client/extconf.rb +13 -13
  4. data/ext/tb_client/tigerbeetle/LICENSE +177 -0
  5. data/ext/tb_client/tigerbeetle/build.zig +2327 -0
  6. data/ext/tb_client/tigerbeetle/src/aof.zig +1000 -0
  7. data/ext/tb_client/tigerbeetle/src/build_multiversion.zig +808 -0
  8. data/ext/tb_client/tigerbeetle/src/cdc/amqp/protocol.zig +1283 -0
  9. data/ext/tb_client/tigerbeetle/src/cdc/amqp/spec.zig +1704 -0
  10. data/ext/tb_client/tigerbeetle/src/cdc/amqp/types.zig +341 -0
  11. data/ext/tb_client/tigerbeetle/src/cdc/amqp.zig +1450 -0
  12. data/ext/tb_client/tigerbeetle/src/cdc/runner.zig +1659 -0
  13. data/ext/tb_client/tigerbeetle/src/clients/c/samples/main.c +406 -0
  14. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/context.zig +1092 -0
  15. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/echo_client.zig +286 -0
  16. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/packet.zig +158 -0
  17. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal.zig +229 -0
  18. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal_fuzz.zig +110 -0
  19. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.h +386 -0
  20. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.zig +34 -0
  21. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_exports.zig +281 -0
  22. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header.zig +312 -0
  23. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header_test.zig +138 -0
  24. data/ext/tb_client/tigerbeetle/src/clients/c/test.zig +466 -0
  25. data/ext/tb_client/tigerbeetle/src/clients/docs_samples.zig +157 -0
  26. data/ext/tb_client/tigerbeetle/src/clients/docs_types.zig +90 -0
  27. data/ext/tb_client/tigerbeetle/src/clients/dotnet/ci.zig +203 -0
  28. data/ext/tb_client/tigerbeetle/src/clients/dotnet/docs.zig +79 -0
  29. data/ext/tb_client/tigerbeetle/src/clients/dotnet/dotnet_bindings.zig +542 -0
  30. data/ext/tb_client/tigerbeetle/src/clients/go/ci.zig +109 -0
  31. data/ext/tb_client/tigerbeetle/src/clients/go/docs.zig +86 -0
  32. data/ext/tb_client/tigerbeetle/src/clients/go/go_bindings.zig +370 -0
  33. data/ext/tb_client/tigerbeetle/src/clients/go/pkg/native/tb_client.h +386 -0
  34. data/ext/tb_client/tigerbeetle/src/clients/java/ci.zig +167 -0
  35. data/ext/tb_client/tigerbeetle/src/clients/java/docs.zig +126 -0
  36. data/ext/tb_client/tigerbeetle/src/clients/java/java_bindings.zig +996 -0
  37. data/ext/tb_client/tigerbeetle/src/clients/java/src/client.zig +748 -0
  38. data/ext/tb_client/tigerbeetle/src/clients/java/src/jni.zig +3238 -0
  39. data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_tests.zig +1718 -0
  40. data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_thread_cleaner.zig +190 -0
  41. data/ext/tb_client/tigerbeetle/src/clients/node/ci.zig +104 -0
  42. data/ext/tb_client/tigerbeetle/src/clients/node/docs.zig +75 -0
  43. data/ext/tb_client/tigerbeetle/src/clients/node/node.zig +522 -0
  44. data/ext/tb_client/tigerbeetle/src/clients/node/node_bindings.zig +267 -0
  45. data/ext/tb_client/tigerbeetle/src/clients/node/src/c.zig +3 -0
  46. data/ext/tb_client/tigerbeetle/src/clients/node/src/translate.zig +379 -0
  47. data/ext/tb_client/tigerbeetle/src/clients/python/ci.zig +131 -0
  48. data/ext/tb_client/tigerbeetle/src/clients/python/docs.zig +63 -0
  49. data/ext/tb_client/tigerbeetle/src/clients/python/python_bindings.zig +588 -0
  50. data/ext/tb_client/tigerbeetle/src/clients/rust/assets/tb_client.h +386 -0
  51. data/ext/tb_client/tigerbeetle/src/clients/rust/ci.zig +73 -0
  52. data/ext/tb_client/tigerbeetle/src/clients/rust/docs.zig +106 -0
  53. data/ext/tb_client/tigerbeetle/src/clients/rust/rust_bindings.zig +305 -0
  54. data/ext/tb_client/tigerbeetle/src/config.zig +296 -0
  55. data/ext/tb_client/tigerbeetle/src/constants.zig +790 -0
  56. data/ext/tb_client/tigerbeetle/src/copyhound.zig +202 -0
  57. data/ext/tb_client/tigerbeetle/src/counting_allocator.zig +72 -0
  58. data/ext/tb_client/tigerbeetle/src/direction.zig +120 -0
  59. data/ext/tb_client/tigerbeetle/src/docs_website/build.zig +158 -0
  60. data/ext/tb_client/tigerbeetle/src/docs_website/src/content.zig +156 -0
  61. data/ext/tb_client/tigerbeetle/src/docs_website/src/docs.zig +252 -0
  62. data/ext/tb_client/tigerbeetle/src/docs_website/src/file_checker.zig +313 -0
  63. data/ext/tb_client/tigerbeetle/src/docs_website/src/html.zig +87 -0
  64. data/ext/tb_client/tigerbeetle/src/docs_website/src/page_writer.zig +63 -0
  65. data/ext/tb_client/tigerbeetle/src/docs_website/src/redirects.zig +47 -0
  66. data/ext/tb_client/tigerbeetle/src/docs_website/src/search_index_writer.zig +28 -0
  67. data/ext/tb_client/tigerbeetle/src/docs_website/src/service_worker_writer.zig +61 -0
  68. data/ext/tb_client/tigerbeetle/src/docs_website/src/single_page_writer.zig +169 -0
  69. data/ext/tb_client/tigerbeetle/src/docs_website/src/website.zig +46 -0
  70. data/ext/tb_client/tigerbeetle/src/ewah.zig +445 -0
  71. data/ext/tb_client/tigerbeetle/src/ewah_benchmark.zig +128 -0
  72. data/ext/tb_client/tigerbeetle/src/ewah_fuzz.zig +171 -0
  73. data/ext/tb_client/tigerbeetle/src/fuzz_tests.zig +179 -0
  74. data/ext/tb_client/tigerbeetle/src/integration_tests.zig +662 -0
  75. data/ext/tb_client/tigerbeetle/src/io/common.zig +155 -0
  76. data/ext/tb_client/tigerbeetle/src/io/darwin.zig +1093 -0
  77. data/ext/tb_client/tigerbeetle/src/io/linux.zig +1880 -0
  78. data/ext/tb_client/tigerbeetle/src/io/test.zig +1005 -0
  79. data/ext/tb_client/tigerbeetle/src/io/windows.zig +1598 -0
  80. data/ext/tb_client/tigerbeetle/src/io.zig +34 -0
  81. data/ext/tb_client/tigerbeetle/src/iops.zig +134 -0
  82. data/ext/tb_client/tigerbeetle/src/list.zig +236 -0
  83. data/ext/tb_client/tigerbeetle/src/lsm/binary_search.zig +848 -0
  84. data/ext/tb_client/tigerbeetle/src/lsm/binary_search_benchmark.zig +179 -0
  85. data/ext/tb_client/tigerbeetle/src/lsm/cache_map.zig +424 -0
  86. data/ext/tb_client/tigerbeetle/src/lsm/cache_map_fuzz.zig +420 -0
  87. data/ext/tb_client/tigerbeetle/src/lsm/compaction.zig +2117 -0
  88. data/ext/tb_client/tigerbeetle/src/lsm/composite_key.zig +182 -0
  89. data/ext/tb_client/tigerbeetle/src/lsm/forest.zig +1119 -0
  90. data/ext/tb_client/tigerbeetle/src/lsm/forest_fuzz.zig +1102 -0
  91. data/ext/tb_client/tigerbeetle/src/lsm/forest_table_iterator.zig +200 -0
  92. data/ext/tb_client/tigerbeetle/src/lsm/groove.zig +1495 -0
  93. data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge.zig +739 -0
  94. data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge_benchmark.zig +166 -0
  95. data/ext/tb_client/tigerbeetle/src/lsm/manifest.zig +754 -0
  96. data/ext/tb_client/tigerbeetle/src/lsm/manifest_level.zig +1294 -0
  97. data/ext/tb_client/tigerbeetle/src/lsm/manifest_level_fuzz.zig +510 -0
  98. data/ext/tb_client/tigerbeetle/src/lsm/manifest_log.zig +1263 -0
  99. data/ext/tb_client/tigerbeetle/src/lsm/manifest_log_fuzz.zig +628 -0
  100. data/ext/tb_client/tigerbeetle/src/lsm/node_pool.zig +247 -0
  101. data/ext/tb_client/tigerbeetle/src/lsm/scan_buffer.zig +116 -0
  102. data/ext/tb_client/tigerbeetle/src/lsm/scan_builder.zig +543 -0
  103. data/ext/tb_client/tigerbeetle/src/lsm/scan_fuzz.zig +938 -0
  104. data/ext/tb_client/tigerbeetle/src/lsm/scan_lookup.zig +293 -0
  105. data/ext/tb_client/tigerbeetle/src/lsm/scan_merge.zig +359 -0
  106. data/ext/tb_client/tigerbeetle/src/lsm/scan_range.zig +99 -0
  107. data/ext/tb_client/tigerbeetle/src/lsm/scan_state.zig +17 -0
  108. data/ext/tb_client/tigerbeetle/src/lsm/scan_tree.zig +962 -0
  109. data/ext/tb_client/tigerbeetle/src/lsm/schema.zig +617 -0
  110. data/ext/tb_client/tigerbeetle/src/lsm/scratch_memory.zig +84 -0
  111. data/ext/tb_client/tigerbeetle/src/lsm/segmented_array.zig +1500 -0
  112. data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_benchmark.zig +149 -0
  113. data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_fuzz.zig +7 -0
  114. data/ext/tb_client/tigerbeetle/src/lsm/set_associative_cache.zig +865 -0
  115. data/ext/tb_client/tigerbeetle/src/lsm/table.zig +607 -0
  116. data/ext/tb_client/tigerbeetle/src/lsm/table_memory.zig +843 -0
  117. data/ext/tb_client/tigerbeetle/src/lsm/table_value_iterator.zig +90 -0
  118. data/ext/tb_client/tigerbeetle/src/lsm/timestamp_range.zig +40 -0
  119. data/ext/tb_client/tigerbeetle/src/lsm/tree.zig +630 -0
  120. data/ext/tb_client/tigerbeetle/src/lsm/tree_fuzz.zig +933 -0
  121. data/ext/tb_client/tigerbeetle/src/lsm/zig_zag_merge.zig +534 -0
  122. data/ext/tb_client/tigerbeetle/src/message_buffer.zig +469 -0
  123. data/ext/tb_client/tigerbeetle/src/message_bus.zig +1214 -0
  124. data/ext/tb_client/tigerbeetle/src/message_bus_fuzz.zig +936 -0
  125. data/ext/tb_client/tigerbeetle/src/message_pool.zig +343 -0
  126. data/ext/tb_client/tigerbeetle/src/multiversion.zig +2195 -0
  127. data/ext/tb_client/tigerbeetle/src/queue.zig +390 -0
  128. data/ext/tb_client/tigerbeetle/src/repl/completion.zig +201 -0
  129. data/ext/tb_client/tigerbeetle/src/repl/parser.zig +1356 -0
  130. data/ext/tb_client/tigerbeetle/src/repl/terminal.zig +496 -0
  131. data/ext/tb_client/tigerbeetle/src/repl.zig +1034 -0
  132. data/ext/tb_client/tigerbeetle/src/scripts/amqp.zig +973 -0
  133. data/ext/tb_client/tigerbeetle/src/scripts/cfo.zig +1866 -0
  134. data/ext/tb_client/tigerbeetle/src/scripts/changelog.zig +304 -0
  135. data/ext/tb_client/tigerbeetle/src/scripts/ci.zig +227 -0
  136. data/ext/tb_client/tigerbeetle/src/scripts/client_readmes.zig +658 -0
  137. data/ext/tb_client/tigerbeetle/src/scripts/devhub.zig +466 -0
  138. data/ext/tb_client/tigerbeetle/src/scripts/release.zig +1058 -0
  139. data/ext/tb_client/tigerbeetle/src/scripts.zig +105 -0
  140. data/ext/tb_client/tigerbeetle/src/shell.zig +1195 -0
  141. data/ext/tb_client/tigerbeetle/src/stack.zig +260 -0
  142. data/ext/tb_client/tigerbeetle/src/state_machine/auditor.zig +911 -0
  143. data/ext/tb_client/tigerbeetle/src/state_machine/workload.zig +2079 -0
  144. data/ext/tb_client/tigerbeetle/src/state_machine.zig +4872 -0
  145. data/ext/tb_client/tigerbeetle/src/state_machine_fuzz.zig +288 -0
  146. data/ext/tb_client/tigerbeetle/src/state_machine_tests.zig +3128 -0
  147. data/ext/tb_client/tigerbeetle/src/static_allocator.zig +82 -0
  148. data/ext/tb_client/tigerbeetle/src/stdx/bit_set.zig +157 -0
  149. data/ext/tb_client/tigerbeetle/src/stdx/bounded_array.zig +292 -0
  150. data/ext/tb_client/tigerbeetle/src/stdx/debug.zig +65 -0
  151. data/ext/tb_client/tigerbeetle/src/stdx/flags.zig +1414 -0
  152. data/ext/tb_client/tigerbeetle/src/stdx/mlock.zig +92 -0
  153. data/ext/tb_client/tigerbeetle/src/stdx/prng.zig +677 -0
  154. data/ext/tb_client/tigerbeetle/src/stdx/radix.zig +336 -0
  155. data/ext/tb_client/tigerbeetle/src/stdx/ring_buffer.zig +511 -0
  156. data/ext/tb_client/tigerbeetle/src/stdx/sort_test.zig +112 -0
  157. data/ext/tb_client/tigerbeetle/src/stdx/stdx.zig +1160 -0
  158. data/ext/tb_client/tigerbeetle/src/stdx/testing/low_level_hash_vectors.zig +142 -0
  159. data/ext/tb_client/tigerbeetle/src/stdx/testing/snaptest.zig +361 -0
  160. data/ext/tb_client/tigerbeetle/src/stdx/time_units.zig +275 -0
  161. data/ext/tb_client/tigerbeetle/src/stdx/unshare.zig +295 -0
  162. data/ext/tb_client/tigerbeetle/src/stdx/vendored/aegis.zig +436 -0
  163. data/ext/tb_client/tigerbeetle/src/stdx/windows.zig +48 -0
  164. data/ext/tb_client/tigerbeetle/src/stdx/zipfian.zig +402 -0
  165. data/ext/tb_client/tigerbeetle/src/storage.zig +489 -0
  166. data/ext/tb_client/tigerbeetle/src/storage_fuzz.zig +180 -0
  167. data/ext/tb_client/tigerbeetle/src/testing/bench.zig +146 -0
  168. data/ext/tb_client/tigerbeetle/src/testing/cluster/grid_checker.zig +53 -0
  169. data/ext/tb_client/tigerbeetle/src/testing/cluster/journal_checker.zig +61 -0
  170. data/ext/tb_client/tigerbeetle/src/testing/cluster/manifest_checker.zig +76 -0
  171. data/ext/tb_client/tigerbeetle/src/testing/cluster/message_bus.zig +110 -0
  172. data/ext/tb_client/tigerbeetle/src/testing/cluster/network.zig +412 -0
  173. data/ext/tb_client/tigerbeetle/src/testing/cluster/state_checker.zig +331 -0
  174. data/ext/tb_client/tigerbeetle/src/testing/cluster/storage_checker.zig +458 -0
  175. data/ext/tb_client/tigerbeetle/src/testing/cluster.zig +1198 -0
  176. data/ext/tb_client/tigerbeetle/src/testing/exhaustigen.zig +128 -0
  177. data/ext/tb_client/tigerbeetle/src/testing/fixtures.zig +181 -0
  178. data/ext/tb_client/tigerbeetle/src/testing/fuzz.zig +144 -0
  179. data/ext/tb_client/tigerbeetle/src/testing/id.zig +97 -0
  180. data/ext/tb_client/tigerbeetle/src/testing/io.zig +317 -0
  181. data/ext/tb_client/tigerbeetle/src/testing/marks.zig +126 -0
  182. data/ext/tb_client/tigerbeetle/src/testing/packet_simulator.zig +533 -0
  183. data/ext/tb_client/tigerbeetle/src/testing/reply_sequence.zig +154 -0
  184. data/ext/tb_client/tigerbeetle/src/testing/state_machine.zig +389 -0
  185. data/ext/tb_client/tigerbeetle/src/testing/storage.zig +1247 -0
  186. data/ext/tb_client/tigerbeetle/src/testing/table.zig +249 -0
  187. data/ext/tb_client/tigerbeetle/src/testing/time.zig +98 -0
  188. data/ext/tb_client/tigerbeetle/src/testing/tmp_tigerbeetle.zig +212 -0
  189. data/ext/tb_client/tigerbeetle/src/testing/vortex/constants.zig +26 -0
  190. data/ext/tb_client/tigerbeetle/src/testing/vortex/faulty_network.zig +580 -0
  191. data/ext/tb_client/tigerbeetle/src/testing/vortex/java_driver/ci.zig +39 -0
  192. data/ext/tb_client/tigerbeetle/src/testing/vortex/logged_process.zig +214 -0
  193. data/ext/tb_client/tigerbeetle/src/testing/vortex/rust_driver/ci.zig +34 -0
  194. data/ext/tb_client/tigerbeetle/src/testing/vortex/supervisor.zig +766 -0
  195. data/ext/tb_client/tigerbeetle/src/testing/vortex/workload.zig +543 -0
  196. data/ext/tb_client/tigerbeetle/src/testing/vortex/zig_driver.zig +181 -0
  197. data/ext/tb_client/tigerbeetle/src/tidy.zig +1448 -0
  198. data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_driver.zig +227 -0
  199. data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_load.zig +1069 -0
  200. data/ext/tb_client/tigerbeetle/src/tigerbeetle/cli.zig +1422 -0
  201. data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect.zig +1658 -0
  202. data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect_integrity.zig +518 -0
  203. data/ext/tb_client/tigerbeetle/src/tigerbeetle/libtb_client.zig +36 -0
  204. data/ext/tb_client/tigerbeetle/src/tigerbeetle/main.zig +646 -0
  205. data/ext/tb_client/tigerbeetle/src/tigerbeetle.zig +958 -0
  206. data/ext/tb_client/tigerbeetle/src/time.zig +236 -0
  207. data/ext/tb_client/tigerbeetle/src/trace/event.zig +745 -0
  208. data/ext/tb_client/tigerbeetle/src/trace/statsd.zig +462 -0
  209. data/ext/tb_client/tigerbeetle/src/trace.zig +556 -0
  210. data/ext/tb_client/tigerbeetle/src/unit_tests.zig +321 -0
  211. data/ext/tb_client/tigerbeetle/src/vopr.zig +1785 -0
  212. data/ext/tb_client/tigerbeetle/src/vortex.zig +101 -0
  213. data/ext/tb_client/tigerbeetle/src/vsr/checkpoint_trailer.zig +473 -0
  214. data/ext/tb_client/tigerbeetle/src/vsr/checksum.zig +208 -0
  215. data/ext/tb_client/tigerbeetle/src/vsr/checksum_benchmark.zig +43 -0
  216. data/ext/tb_client/tigerbeetle/src/vsr/client.zig +768 -0
  217. data/ext/tb_client/tigerbeetle/src/vsr/client_replies.zig +532 -0
  218. data/ext/tb_client/tigerbeetle/src/vsr/client_sessions.zig +338 -0
  219. data/ext/tb_client/tigerbeetle/src/vsr/clock.zig +1019 -0
  220. data/ext/tb_client/tigerbeetle/src/vsr/fault_detector.zig +279 -0
  221. data/ext/tb_client/tigerbeetle/src/vsr/free_set.zig +1381 -0
  222. data/ext/tb_client/tigerbeetle/src/vsr/free_set_fuzz.zig +315 -0
  223. data/ext/tb_client/tigerbeetle/src/vsr/grid.zig +1460 -0
  224. data/ext/tb_client/tigerbeetle/src/vsr/grid_blocks_missing.zig +757 -0
  225. data/ext/tb_client/tigerbeetle/src/vsr/grid_scrubber.zig +797 -0
  226. data/ext/tb_client/tigerbeetle/src/vsr/journal.zig +2586 -0
  227. data/ext/tb_client/tigerbeetle/src/vsr/marzullo.zig +308 -0
  228. data/ext/tb_client/tigerbeetle/src/vsr/message_header.zig +1777 -0
  229. data/ext/tb_client/tigerbeetle/src/vsr/multi_batch.zig +715 -0
  230. data/ext/tb_client/tigerbeetle/src/vsr/multi_batch_fuzz.zig +185 -0
  231. data/ext/tb_client/tigerbeetle/src/vsr/repair_budget.zig +333 -0
  232. data/ext/tb_client/tigerbeetle/src/vsr/replica.zig +12355 -0
  233. data/ext/tb_client/tigerbeetle/src/vsr/replica_format.zig +416 -0
  234. data/ext/tb_client/tigerbeetle/src/vsr/replica_reformat.zig +165 -0
  235. data/ext/tb_client/tigerbeetle/src/vsr/replica_test.zig +2928 -0
  236. data/ext/tb_client/tigerbeetle/src/vsr/routing.zig +1075 -0
  237. data/ext/tb_client/tigerbeetle/src/vsr/superblock.zig +1603 -0
  238. data/ext/tb_client/tigerbeetle/src/vsr/superblock_fuzz.zig +484 -0
  239. data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums.zig +405 -0
  240. data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +355 -0
  241. data/ext/tb_client/tigerbeetle/src/vsr/sync.zig +29 -0
  242. data/ext/tb_client/tigerbeetle/src/vsr.zig +1727 -0
  243. data/lib/tb_client/shared_lib.rb +12 -5
  244. data/lib/tigerbeetle/platforms.rb +9 -0
  245. data/lib/tigerbeetle/version.rb +2 -2
  246. data/tigerbeetle.gemspec +22 -5
  247. metadata +242 -3
  248. data/ext/tb_client/pkg.tar.gz +0 -0
@@ -0,0 +1,1658 @@
1
+ //! Decode a TigerBeetle data file without running a replica or modifying the data file.
2
+ //!
3
+ //! This tool is intended for TigerBeetle developers, for debugging and understanding data files.
4
+ //!
5
+ //! Principles:
6
+ //! - Never modify the data file.
7
+ //! - Adhere to the "be liberal in what you accept" side of Postel's Law.
8
+ //! When the data file is corrupt, decode as much as possible. (This is somewhat aspirational).
9
+ //! - Outside of the "summary" commands, don't discard potentially useful information.
10
+
11
+ const std = @import("std");
12
+ const assert = std.debug.assert;
13
+ const log = std.log.scoped(.inspect);
14
+
15
+ const cli = @import("cli.zig");
16
+ const vsr = @import("vsr");
17
+ const stdx = vsr.stdx;
18
+ const schema = vsr.lsm.schema;
19
+ const constants = vsr.constants;
20
+ const IO = vsr.io.IO;
21
+ const Tracer = vsr.trace.Tracer;
22
+ const Storage = @import("main.zig").Storage;
23
+ const SuperBlockHeader = vsr.superblock.SuperBlockHeader;
24
+ const SuperBlockVersion = vsr.superblock.SuperBlockVersion;
25
+ const SuperBlockQuorums = vsr.superblock.Quorums;
26
+ const StateMachine = @import("main.zig").StateMachine;
27
+ const BlockPtr = vsr.grid.BlockPtr;
28
+ const BlockPtrConst = vsr.grid.BlockPtrConst;
29
+ const allocate_block = vsr.grid.allocate_block;
30
+ const is_composite_key = vsr.lsm.composite_key.is_composite_key;
31
+
32
+ const EventMetric = vsr.trace.EventMetric;
33
+ const EventMetricAggregate = vsr.trace.EventMetricAggregate;
34
+ const EventTiming = vsr.trace.EventTiming;
35
+ const EventTimingAggregate = vsr.trace.EventTimingAggregate;
36
+ const command_inspect_integrity = @import("inspect_integrity.zig").command_inspect_integrity;
37
+
38
+ pub fn command_inspect(
39
+ allocator: std.mem.Allocator,
40
+ io: *IO,
41
+ tracer: *Tracer,
42
+ cli_args: *const cli.Command.Inspect,
43
+ ) !void {
44
+ var stdout_buffer = std.io.bufferedWriter(std.io.getStdOut().writer());
45
+ var stdout_writer = stdout_buffer.writer();
46
+
47
+ const inspect_result = run_inspect(allocator, io, tracer, cli_args, stdout_writer.any());
48
+ const flush_result = stdout_buffer.flush();
49
+
50
+ inline for (.{ inspect_result, flush_result }) |result| {
51
+ result catch |err| switch (err) {
52
+ // Ignore BrokenPipe so that e.g. "tigerbeetle inspect ... | head -n12" succeeds.
53
+ error.BrokenPipe => {},
54
+ else => return err,
55
+ };
56
+ }
57
+ }
58
+
59
+ fn run_inspect(
60
+ allocator: std.mem.Allocator,
61
+ io: *IO,
62
+ tracer: *Tracer,
63
+ cli_args: *const cli.Command.Inspect,
64
+ stdout: std.io.AnyWriter,
65
+ ) !void {
66
+ const data_file = switch (cli_args.*) {
67
+ .constants => return try inspect_constants(stdout),
68
+ .metrics => return try inspect_metrics(stdout),
69
+ .op => |op| return try inspect_op(stdout, op),
70
+ .integrity => |*args| return try command_inspect_integrity(allocator, io, tracer, args),
71
+ .data_file => |data_file| data_file,
72
+ };
73
+
74
+ const inspector = try Inspector.create(allocator, io, tracer, data_file.path);
75
+ defer inspector.destroy();
76
+
77
+ switch (data_file.query) {
78
+ .superblock => try inspector.inspect_superblock(stdout),
79
+ .wal => |args| {
80
+ if (args.slot) |slot| {
81
+ if (slot >= constants.journal_slot_count) {
82
+ return vsr.fatal(
83
+ .cli,
84
+ "--slot: slot exceeds {}",
85
+ .{constants.journal_slot_count - 1},
86
+ );
87
+ }
88
+ try inspector.inspect_wal_slot(stdout, slot);
89
+ } else {
90
+ try inspector.inspect_wal(stdout);
91
+ }
92
+ },
93
+ .replies => |args| {
94
+ if (args.slot) |slot| {
95
+ if (slot >= constants.clients_max) {
96
+ return vsr.fatal(.cli, "--slot: slot exceeds {}", .{constants.clients_max - 1});
97
+ }
98
+ try inspector.inspect_replies_slot(stdout, args.superblock_copy, slot);
99
+ } else {
100
+ try inspector.inspect_replies(stdout, args.superblock_copy);
101
+ }
102
+ },
103
+ .grid => |args| {
104
+ if (args.superblock_copy != null and
105
+ args.superblock_copy.? >= constants.superblock_copies)
106
+ {
107
+ return vsr.fatal(
108
+ .cli,
109
+ "--superblock-copy: copy exceeds {}",
110
+ .{constants.superblock_copies - 1},
111
+ );
112
+ }
113
+
114
+ if (args.block) |address| {
115
+ try inspector.inspect_grid_block(stdout, address);
116
+ } else {
117
+ try inspector.inspect_grid(stdout, args.superblock_copy);
118
+ }
119
+ },
120
+ .manifest => |args| {
121
+ if (args.superblock_copy != null and
122
+ args.superblock_copy.? >= constants.superblock_copies)
123
+ {
124
+ return vsr.fatal(
125
+ .cli,
126
+ "--superblock-copy: copy exceeds {}",
127
+ .{constants.superblock_copies - 1},
128
+ );
129
+ }
130
+
131
+ try inspector.inspect_manifest(stdout, args.superblock_copy);
132
+ },
133
+ .tables => |args| {
134
+ if (args.superblock_copy != null and
135
+ args.superblock_copy.? >= constants.superblock_copies)
136
+ {
137
+ return vsr.fatal(
138
+ .cli,
139
+ "--superblock-copy: copy exceeds {}",
140
+ .{constants.superblock_copies - 1},
141
+ );
142
+ }
143
+
144
+ const tree_id = parse_tree_id(args.tree) orelse {
145
+ return vsr.fatal(.cli, "--tree: invalid tree name/id: {s}", .{args.tree});
146
+ };
147
+ try inspector.inspect_tables(stdout, args.superblock_copy, .{
148
+ .tree_id = tree_id,
149
+ .level = args.level,
150
+ });
151
+ },
152
+ }
153
+ }
154
+
155
+ fn inspect_constants(output: std.io.AnyWriter) !void {
156
+ try output.print("VSR:\n", .{});
157
+ try print_header(output, 0, "prepare_queue");
158
+ try output.print("{}\n", .{constants.pipeline_prepare_queue_max});
159
+ try print_header(output, 0, "request_queue");
160
+ try output.print("{}\n", .{constants.pipeline_request_queue_max});
161
+ try print_header(output, 0, "prepare_cache");
162
+ try output.print("{}\n", .{
163
+ constants.pipeline_prepare_queue_max + constants.pipeline_request_queue_max,
164
+ });
165
+ try output.print("\n", .{});
166
+
167
+ try output.print("LSM:\n", .{});
168
+ try print_header(output, 0, "compaction_ops");
169
+ try output.print("{}\n", .{constants.lsm_compaction_ops});
170
+ try print_header(output, 0, "checkpoint_ops");
171
+ try output.print("{}\n", .{constants.vsr_checkpoint_ops});
172
+ try print_header(output, 0, "journal_slot_count");
173
+ try output.print("{}\n", .{constants.journal_slot_count});
174
+ try output.print("\n", .{});
175
+
176
+ try output.print("Data File Layout:\n", .{});
177
+ inline for (comptime std.enums.values(vsr.Zone)) |zone| {
178
+ try print_header(output, 0, @tagName(zone));
179
+ switch (zone) {
180
+ inline else => |zone_sized| {
181
+ try print_size_count(
182
+ output,
183
+ zone_sized.size().?,
184
+ 1,
185
+ );
186
+ },
187
+ .grid => {
188
+ try output.print("elastic\n", .{});
189
+ },
190
+ }
191
+ switch (zone) {
192
+ .superblock => {
193
+ try print_header(output, 1, "copy");
194
+ try print_size_count(
195
+ output,
196
+ vsr.superblock.superblock_copy_size,
197
+ constants.superblock_copies,
198
+ );
199
+ },
200
+ .wal_headers => {
201
+ try print_header(output, 1, "sector");
202
+ try print_size_count(
203
+ output,
204
+ constants.sector_size,
205
+ @divExact(vsr.Zone.wal_headers.size().?, constants.sector_size),
206
+ );
207
+
208
+ try print_header(output, 2, "header");
209
+ try print_size_count(
210
+ output,
211
+ @sizeOf(vsr.Header),
212
+ @divExact(constants.sector_size, @sizeOf(vsr.Header)),
213
+ );
214
+ },
215
+ .wal_prepares => {
216
+ try print_header(output, 1, "prepare");
217
+ try print_size_count(
218
+ output,
219
+ constants.message_size_max,
220
+ constants.journal_slot_count,
221
+ );
222
+ },
223
+ .client_replies => {
224
+ try print_header(output, 1, "reply");
225
+ try print_size_count(
226
+ output,
227
+ constants.message_size_max,
228
+ constants.clients_max,
229
+ );
230
+ },
231
+ .grid => {
232
+ try print_header(output, 1, "block");
233
+ try print_size_count(output, constants.block_size, 1);
234
+ },
235
+ else => {},
236
+ }
237
+ try output.print("\n", .{});
238
+ }
239
+
240
+ // Print the size required to store each object + indexes.
241
+ try output.print("StateMachine:\n", .{});
242
+ try print_objects(output);
243
+
244
+ // Memory usage is intentionally estimated from constants, rather than measured, to sanity
245
+ // check that our observed memory usage is reasonable.
246
+ try output.print("Memory (approximate):\n", .{});
247
+ const datafile_size = constants.storage_size_limit_max;
248
+ try print_header(output, 0, "datafile (on disk)");
249
+ try output.print("{}\n", .{
250
+ stdx.fmt_int_size_bin_exact(datafile_size),
251
+ });
252
+
253
+ {
254
+ const grid_size_limit = datafile_size - vsr.superblock.data_file_size_min;
255
+ const blocks_count = vsr.FreeSet.block_count_max(grid_size_limit);
256
+ const ewah = vsr.ewah(vsr.FreeSet.Word);
257
+
258
+ // 2x since both `blocks_acquired` and `blocks_released` are encoded.
259
+ const free_set_encoded_blocks_max = 2 *
260
+ vsr.checkpoint_trailer.block_count_for_trailer_size(ewah.encode_size_max(blocks_count));
261
+
262
+ const client_sessions_encoded_blocks_max =
263
+ vsr.checkpoint_trailer.block_count_for_trailer_size(vsr.ClientSessions.encode_size);
264
+
265
+ try print_header(output, 0, "free_set");
266
+ const hashmap_entries = stdx.div_ceil(
267
+ 100 * (StateMachine.Forest.compaction_blocks_released_per_pipeline_max() +
268
+ free_set_encoded_blocks_max + client_sessions_encoded_blocks_max),
269
+ std.hash_map.default_max_load_percentage,
270
+ );
271
+
272
+ try output.print("{:.2}\n", .{std.fmt.fmtIntSizeBin(
273
+ // HashMap of block addresses plus two bitsets with bit per block.
274
+ hashmap_entries * @sizeOf(u64) + 2 * stdx.div_ceil(blocks_count, 8),
275
+ )});
276
+ }
277
+ }
278
+
279
+ fn inspect_metrics(output: std.io.AnyWriter) !void {
280
+ const EventMetricTag = std.meta.Tag(EventMetric);
281
+ const EventTimingTag = std.meta.Tag(EventTiming);
282
+
283
+ const stats_per_gauge = std.meta.fields(EventMetricAggregate).len - 1; // -1 to ignore `event`.
284
+ const stats_per_timing = std.meta.fields(std.meta.FieldType(EventTimingAggregate, .values)).len;
285
+ var stats_total: usize = 0;
286
+
287
+ log.info("Format: [metric type]: [metric name]([metric tags])=[metric cardinality]", .{});
288
+
289
+ inline for (std.meta.fields(EventMetric)) |field| {
290
+ const metric_tag = std.meta.stringToEnum(EventMetricTag, field.name).?;
291
+ try output.print("gauge: {s}(", .{field.name});
292
+ if (field.type != void) {
293
+ inline for (std.meta.fields(field.type), 0..) |data_field, i| {
294
+ if (i != 0) try output.print(", ");
295
+ try output.print("{s}", .{data_field.name});
296
+ }
297
+ }
298
+ const metric_stats = EventMetric.slot_limits.get(metric_tag) * stats_per_gauge;
299
+ try output.print(")={}\n", .{metric_stats});
300
+ stats_total += metric_stats;
301
+ }
302
+ inline for (std.meta.fields(EventTiming)) |field| {
303
+ const timing_tag = std.meta.stringToEnum(EventTimingTag, field.name).?;
304
+ try output.print("timing: {s}(", .{field.name});
305
+ if (field.type != void) {
306
+ inline for (std.meta.fields(field.type), 0..) |data_field, i| {
307
+ if (i != 0) try output.print(", ");
308
+ try output.print("{s}", .{data_field.name});
309
+ }
310
+ }
311
+ const timing_stats = EventTiming.slot_limits.get(timing_tag) * stats_per_timing;
312
+ try output.print(")={}\n", .{timing_stats});
313
+ stats_total += timing_stats;
314
+ }
315
+ log.info("Total stats per replica: {}", .{stats_total});
316
+ log.info(
317
+ "(All stats are tagged with the replica, so the cluster has 6x as many stats.)",
318
+ .{},
319
+ );
320
+ }
321
+
322
+ // Example output:
323
+ // checkpoint op trigger prepare_max checkpoint_next
324
+ // 624894719 +20 624894739 +12 624894751 +16 624894767 +912 624895679
325
+ fn inspect_op(output: std.io.AnyWriter, op: u64) !void {
326
+ const checkpoint = if (op < constants.vsr_checkpoint_ops - 1) 0 else checkpoint: {
327
+ // op = q * checkpoints_ops - 1 + r
328
+ const r = (op + 1) % constants.vsr_checkpoint_ops;
329
+ const q = @divExact(op + 1 - r, constants.vsr_checkpoint_ops);
330
+ break :checkpoint q * constants.vsr_checkpoint_ops - 1;
331
+ };
332
+ const checkpoint_next = vsr.Checkpoint.checkpoint_after(checkpoint);
333
+
334
+ const points = .{
335
+ .checkpoint = checkpoint,
336
+ .trigger = vsr.Checkpoint.trigger_for_checkpoint(checkpoint) orelse 0,
337
+ .prepare_max = vsr.Checkpoint.prepare_max_for_checkpoint(checkpoint) orelse 0,
338
+ .checkpoint_next = checkpoint_next,
339
+ .op = op,
340
+ };
341
+ const Points = @TypeOf(points);
342
+
343
+ const Entry = struct {
344
+ label: []const u8,
345
+ op: u64,
346
+ fn less_than(_: void, a: @This(), b: @This()) bool {
347
+ return a.op < b.op;
348
+ }
349
+ };
350
+
351
+ var entries: [std.meta.fields(Points).len]Entry = undefined;
352
+ inline for (std.meta.fields(Points), 0..) |field, index| {
353
+ entries[index] = .{
354
+ .label = field.name,
355
+ .op = @field(points, field.name),
356
+ };
357
+ }
358
+ std.sort.insertion(Entry, &entries, {}, Entry.less_than);
359
+ for (entries) |entry| {
360
+ try output.print("{s: <20}", .{entry.label});
361
+ }
362
+ try output.print("\n", .{});
363
+ for (entries[0 .. entries.len - 1], entries[1..]) |entry, entry_next| {
364
+ try output.print("{d: <15}", .{entry.op});
365
+ try output.print("+{d: <4}", .{entry_next.op - entry.op});
366
+ }
367
+ try output.print("{d: <20}", .{entries[entries.len - 1].op});
368
+ try output.print("\n", .{});
369
+ }
370
+
371
+ fn print_header(output: std.io.AnyWriter, comptime level: u8, comptime header: []const u8) !void {
372
+ const width_total = 32;
373
+ const pad_left = " " ** level;
374
+ const pad_right = " " ** (width_total -| level * 2 -| header.len);
375
+ try output.print(pad_left ++ header ++ pad_right, .{});
376
+ }
377
+
378
+ fn print_size_count(output: std.io.AnyWriter, comptime size: u64, comptime count: u64) !void {
379
+ if (count == 1) {
380
+ try output.print("{}\n", .{stdx.fmt_int_size_bin_exact(size)});
381
+ } else {
382
+ const size_formatted = comptime if (size < 1024)
383
+ std.fmt.comptimePrint("{}B", .{size})
384
+ else
385
+ std.fmt.comptimePrint("{}", .{stdx.fmt_int_size_bin_exact(size)});
386
+ try output.print("{s<8} x{}\n", .{ size_formatted, count });
387
+ }
388
+ }
389
+
390
+ fn print_objects(output: std.io.AnyWriter) !void {
391
+ const Grooves = StateMachine.Forest.Grooves;
392
+ inline for (std.meta.fields(Grooves)) |groove_field| {
393
+ const Groove = groove_field.type;
394
+ const ObjectTree = Groove.ObjectTree;
395
+
396
+ comptime var size_total: usize = 0;
397
+
398
+ const object_size = @sizeOf(ObjectTree.Table.Value);
399
+ size_total += object_size;
400
+
401
+ const id_size = if (Groove.IdTree == void) 0 else @sizeOf(Groove.IdTree.Table.Value);
402
+ size_total += id_size;
403
+
404
+ comptime {
405
+ for (std.meta.fields(Groove.IndexTrees)) |index_field| {
406
+ const IndexTree = index_field.type;
407
+ const index_size = @sizeOf(IndexTree.Table.Value);
408
+ size_total += index_size;
409
+ }
410
+ }
411
+
412
+ try print_header(output, 0, ObjectTree.tree_name());
413
+ try print_size_count(output, size_total, 1);
414
+
415
+ try print_header(output, 1, "object");
416
+ try print_size_count(output, object_size, 1);
417
+
418
+ try print_header(output, 1, "id");
419
+ try print_size_count(output, id_size, 1);
420
+
421
+ inline for (std.meta.fields(Groove.IndexTrees)) |index_field| {
422
+ const IndexTree = index_field.type;
423
+ const index_size = @sizeOf(IndexTree.Table.Value);
424
+
425
+ try print_header(output, 1, index_field.name);
426
+ try print_size_count(output, index_size, 1);
427
+ }
428
+
429
+ try output.print("\n", .{});
430
+ }
431
+ }
432
+
433
+ const Inspector = struct {
434
+ allocator: std.mem.Allocator,
435
+ io: *IO,
436
+ storage: Storage,
437
+
438
+ superblock_buffer: []align(constants.sector_size) u8,
439
+ superblock_headers: [constants.superblock_copies]*const SuperBlockHeader,
440
+
441
+ busy: bool = false,
442
+ read: Storage.Read = undefined,
443
+
444
+ fn create(
445
+ allocator: std.mem.Allocator,
446
+ io: *IO,
447
+ tracer: *Tracer,
448
+ path: []const u8,
449
+ ) !*Inspector {
450
+ var inspector = try allocator.create(Inspector);
451
+ errdefer allocator.destroy(inspector);
452
+
453
+ inspector.* = .{
454
+ .allocator = allocator,
455
+ .io = io,
456
+ .storage = undefined,
457
+ .superblock_buffer = undefined,
458
+ .superblock_headers = undefined,
459
+ };
460
+
461
+ inspector.storage = try Storage.init(io, tracer, .{
462
+ .path = path,
463
+ .size_min = vsr.superblock.data_file_size_min,
464
+ .purpose = .inspect,
465
+ .direct_io = .direct_io_optional,
466
+ });
467
+ errdefer inspector.storage.deinit();
468
+
469
+ inspector.superblock_buffer = try allocator.alignedAlloc(
470
+ u8,
471
+ constants.sector_size,
472
+ vsr.superblock.superblock_zone_size,
473
+ );
474
+ errdefer allocator.free(inspector.superblock_buffer);
475
+
476
+ try inspector.read_buffer(inspector.superblock_buffer, .superblock, 0);
477
+
478
+ for (&inspector.superblock_headers, 0..) |*superblock_header, copy| {
479
+ const offset = @as(u64, copy) * vsr.superblock.superblock_copy_size;
480
+ superblock_header.* = @alignCast(std.mem.bytesAsValue(
481
+ SuperBlockHeader,
482
+ inspector.superblock_buffer[offset..][0..@sizeOf(SuperBlockHeader)],
483
+ ));
484
+ }
485
+
486
+ const superblock = try inspector.read_superblock(null);
487
+ if (superblock.version != SuperBlockVersion) {
488
+ return vsr.fatal(
489
+ .cli,
490
+ "invalid superblock version; inspector supports version={}, version in {s}={}",
491
+ .{
492
+ SuperBlockVersion,
493
+ path,
494
+ superblock.version,
495
+ },
496
+ );
497
+ }
498
+ return inspector;
499
+ }
500
+
501
+ fn destroy(inspector: *Inspector) void {
502
+ inspector.allocator.free(inspector.superblock_buffer);
503
+ inspector.storage.deinit();
504
+ inspector.allocator.destroy(inspector);
505
+ }
506
+
507
+ fn work(inspector: *Inspector) !void {
508
+ assert(!inspector.busy);
509
+ inspector.busy = true;
510
+
511
+ while (inspector.busy) {
512
+ try inspector.io.run_for_ns(constants.tick_ms * std.time.ns_per_ms);
513
+ }
514
+ }
515
+
516
+ fn inspector_read_callback(read: *Storage.Read) void {
517
+ const inspector: *Inspector = @alignCast(@fieldParentPtr("read", read));
518
+ assert(inspector.busy);
519
+
520
+ inspector.busy = false;
521
+ }
522
+
523
+ fn inspect_superblock(inspector: *Inspector, output: std.io.AnyWriter) !void {
524
+ log.info("In the left column of the output, \"|\" denotes which copies have a " ++
525
+ "particular value.", .{});
526
+ log.info("\"||||\" means that all four superblock copies are in agreement.", .{});
527
+ log.info("\"|_|_\" means that the value matches in copies 0/2, but differs from copies " ++
528
+ "1/3.", .{});
529
+
530
+ var header_valid: [constants.superblock_copies]bool = undefined;
531
+ for (&inspector.superblock_headers, 0..) |header, i| {
532
+ header_valid[i] = header.valid_checksum();
533
+ }
534
+
535
+ inline for (std.meta.fields(SuperBlockHeader)) |field| {
536
+ var group_by = GroupByType(constants.superblock_copies){};
537
+ for (inspector.superblock_headers) |header| {
538
+ group_by.compare(std.mem.asBytes(&@field(header, field.name)));
539
+ }
540
+
541
+ var label_buffer: [128]u8 = undefined;
542
+ for (group_by.groups()) |group| {
543
+ const header_index = group.first_set().?;
544
+ const header = &inspector.superblock_headers[header_index];
545
+ const header_mark: u8 = if (header_valid[header_index]) '|' else 'X';
546
+
547
+ var label_stream = std.io.fixedBufferStream(&label_buffer);
548
+ for (0..constants.superblock_copies) |j| {
549
+ try label_stream.writer().writeByte(if (group.is_set(j)) header_mark else '_');
550
+ }
551
+ try label_stream.writer().writeByte(' ');
552
+ try label_stream.writer().writeAll(field.name);
553
+
554
+ try print_struct(output, label_stream.getWritten(), &@field(header.*, field.name));
555
+ }
556
+ }
557
+ }
558
+
559
+ fn inspect_wal(inspector: *Inspector, output: std.io.AnyWriter) !void {
560
+ log.info("In the left column of the output, \"|\" denotes which set of headers has " ++
561
+ "each value.", .{});
562
+ log.info("\"||\" denotes that the prepare and the redundant header match.", .{});
563
+ log.info("\"|_\" is the redundant header.", .{});
564
+ log.info("\"_|\" is the prepare's header.", .{});
565
+
566
+ const headers_buffer = try inspector.allocator.alignedAlloc(
567
+ u8,
568
+ constants.sector_size,
569
+ constants.journal_size_headers,
570
+ );
571
+ defer inspector.allocator.free(headers_buffer);
572
+
573
+ const prepare_buffer = try inspector.allocator.alignedAlloc(
574
+ u8,
575
+ constants.sector_size,
576
+ constants.message_size_max,
577
+ );
578
+ defer inspector.allocator.free(prepare_buffer);
579
+
580
+ try inspector.read_buffer(headers_buffer, .wal_headers, 0);
581
+
582
+ for (std.mem.bytesAsSlice(vsr.Header.Prepare, headers_buffer), 0..) |*wal_header, slot| {
583
+ const offset = slot * constants.message_size_max;
584
+ try inspector.read_buffer(prepare_buffer, .wal_prepares, offset);
585
+
586
+ const wal_prepare = std.mem.bytesAsValue(
587
+ vsr.Header.Prepare,
588
+ prepare_buffer[0..@sizeOf(vsr.Header)],
589
+ );
590
+
591
+ const wal_prepare_body_valid =
592
+ wal_prepare.valid_checksum() and
593
+ wal_prepare.valid_checksum_body(
594
+ prepare_buffer[@sizeOf(vsr.Header)..wal_prepare.size],
595
+ );
596
+
597
+ const header_pair = [_]*const vsr.Header.Prepare{ wal_header, wal_prepare };
598
+
599
+ var group_by = GroupByType(2){};
600
+ group_by.compare(std.mem.asBytes(wal_header));
601
+ group_by.compare(std.mem.asBytes(wal_prepare));
602
+
603
+ var label_buffer: [64]u8 = undefined;
604
+ for (group_by.groups()) |group| {
605
+ const header = header_pair[group.first_set().?];
606
+ const header_valid = header.valid_checksum() and
607
+ (!group.is_set(1) or wal_prepare_body_valid);
608
+
609
+ const mark: u8 = if (header_valid) '|' else 'X';
610
+ var label_stream = std.io.fixedBufferStream(&label_buffer);
611
+ try label_stream.writer().writeByte(if (group.is_set(0)) mark else '_');
612
+ try label_stream.writer().writeByte(if (group.is_set(1)) mark else '_');
613
+ try label_stream.writer().print("{:_>4}: ", .{slot});
614
+
615
+ try print_struct(output, label_stream.getWritten(), &.{
616
+ "checksum=", header.checksum,
617
+ "release=", header.release,
618
+ "view=", header.view,
619
+ "op=", header.op,
620
+ "size=", header.size,
621
+ "operation=", header.operation,
622
+ });
623
+ }
624
+ }
625
+ }
626
+
627
+ fn inspect_wal_slot(inspector: *Inspector, output: std.io.AnyWriter, slot: usize) !void {
628
+ assert(slot <= constants.journal_slot_count);
629
+
630
+ const headers_buffer = try inspector.allocator.alignedAlloc(
631
+ u8,
632
+ constants.sector_size,
633
+ constants.journal_size_headers,
634
+ );
635
+ defer inspector.allocator.free(headers_buffer);
636
+
637
+ const prepare_buffer = try inspector.allocator.alignedAlloc(
638
+ u8,
639
+ constants.sector_size,
640
+ constants.message_size_max,
641
+ );
642
+ defer inspector.allocator.free(prepare_buffer);
643
+
644
+ try inspector.read_buffer(headers_buffer, .wal_headers, 0);
645
+ try inspector.read_buffer(prepare_buffer, .wal_prepares, slot * constants.message_size_max);
646
+
647
+ const headers = std.mem.bytesAsSlice(vsr.Header.Prepare, headers_buffer);
648
+ const prepare_header =
649
+ std.mem.bytesAsValue(vsr.Header.Prepare, prepare_buffer[0..@sizeOf(vsr.Header)]);
650
+
651
+ const prepare_body_valid =
652
+ prepare_header.valid_checksum() and
653
+ prepare_header.valid_checksum_body(
654
+ prepare_buffer[@sizeOf(vsr.Header)..prepare_header.size],
655
+ );
656
+
657
+ const copies: [2]*const vsr.Header.Prepare = .{ &headers[slot], prepare_header };
658
+
659
+ var group_by = GroupByType(2){};
660
+ for (copies) |h| group_by.compare(std.mem.asBytes(h));
661
+
662
+ var label_buffer: [2]u8 = undefined;
663
+ for (group_by.groups()) |group| {
664
+ const header = copies[group.first_set().?];
665
+ const header_mark: u8 = if (header.valid_checksum()) '|' else 'X';
666
+ label_buffer[0] = if (group.is_set(0)) header_mark else '_';
667
+ label_buffer[1] = if (group.is_set(1)) header_mark else '_';
668
+
669
+ try print_struct(output, &label_buffer, header);
670
+ }
671
+ try print_prepare_body(output, prepare_buffer);
672
+
673
+ if (!prepare_body_valid) {
674
+ try output.writeAll("error: invalid prepare body!\n");
675
+ }
676
+ }
677
+
678
+ fn inspect_replies(
679
+ inspector: *Inspector,
680
+ output: std.io.AnyWriter,
681
+ superblock_copy: ?u8,
682
+ ) !void {
683
+ const entries_block = try allocate_block(inspector.allocator);
684
+ defer inspector.allocator.free(entries_block);
685
+
686
+ const reply_sector =
687
+ try inspector.allocator.alignedAlloc(u8, constants.sector_size, constants.sector_size);
688
+ defer inspector.allocator.free(reply_sector);
689
+
690
+ const entries =
691
+ try inspector.read_client_sessions(entries_block, superblock_copy) orelse return;
692
+
693
+ var label_buffer: [64]u8 = undefined;
694
+ for (&entries.headers, &entries.sessions, 0..) |*session_header, session, slot| {
695
+ try inspector.read_buffer(
696
+ reply_sector,
697
+ .client_replies,
698
+ constants.message_size_max * slot,
699
+ );
700
+
701
+ const reply_header =
702
+ std.mem.bytesAsValue(vsr.Header.Reply, reply_sector[0..@sizeOf(vsr.Header)]);
703
+ const copies: [2]*const vsr.Header.Reply = .{ session_header, reply_header };
704
+ var group_by = GroupByType(2){};
705
+ for (copies) |h| group_by.compare(std.mem.asBytes(h));
706
+
707
+ // The session doesn't include the group diff labels since it is only stored in the
708
+ // client sessions, not the replies.
709
+ try output.print("{:_>2} session={}\n", .{ slot, session });
710
+
711
+ for (group_by.groups()) |group| {
712
+ const header_index = group.first_set().?;
713
+ const header = copies[header_index];
714
+ const header_mark: u8 = if (header.valid_checksum()) '|' else 'X';
715
+
716
+ var label_stream = std.io.fixedBufferStream(&label_buffer);
717
+ try label_stream.writer().print("{:_>2}: ", .{slot});
718
+ try label_stream.writer().writeByte(if (group.is_set(0)) header_mark else '_');
719
+ try label_stream.writer().writeByte(if (group.is_set(1)) header_mark else '_');
720
+ try label_stream.writer().writeAll(" header");
721
+ try print_struct(output, label_stream.getWritten(), header);
722
+ }
723
+ }
724
+ }
725
+
726
+ fn inspect_replies_slot(
727
+ inspector: *Inspector,
728
+ output: std.io.AnyWriter,
729
+ superblock_copy: ?u8,
730
+ slot: usize,
731
+ ) !void {
732
+ assert(slot < constants.clients_max);
733
+
734
+ const block = try allocate_block(inspector.allocator);
735
+ defer inspector.allocator.free(block);
736
+
737
+ const reply = try inspector.allocator.alignedAlloc(
738
+ u8,
739
+ constants.sector_size,
740
+ constants.message_size_max,
741
+ );
742
+ defer inspector.allocator.free(reply);
743
+
744
+ log.info("\"||\" denotes that the client session header and reply header match.", .{});
745
+ log.info("\"|_\" is the client session header.", .{});
746
+ log.info("\"_|\" is the client reply's header.", .{});
747
+
748
+ const entries = try inspector.read_client_sessions(block, superblock_copy) orelse {
749
+ try output.writeAll("error: no client sessions\n");
750
+ return;
751
+ };
752
+
753
+ try inspector.read_buffer(
754
+ reply,
755
+ .client_replies,
756
+ constants.message_size_max * slot,
757
+ );
758
+
759
+ const reply_header = std.mem.bytesAsValue(vsr.Header.Reply, reply[0..@sizeOf(vsr.Header)]);
760
+ const copies: [2]*const vsr.Header.Reply = .{ &entries.headers[slot], reply_header };
761
+ var group_by = GroupByType(2){};
762
+ for (copies) |h| group_by.compare(std.mem.asBytes(h));
763
+
764
+ var label_buffer: [2]u8 = undefined;
765
+ for (group_by.groups()) |group| {
766
+ const header = copies[group.first_set().?];
767
+ const header_mark: u8 = if (header.valid_checksum()) '|' else 'X';
768
+ label_buffer[0] = if (group.is_set(0)) header_mark else '_';
769
+ label_buffer[1] = if (group.is_set(1)) header_mark else '_';
770
+
771
+ try print_struct(output, &label_buffer, header);
772
+ }
773
+ try print_reply_body(output, reply);
774
+ }
775
+
776
+ fn inspect_grid(inspector: *Inspector, output: std.io.AnyWriter, superblock_copy: ?u8) !void {
777
+ const superblock = try inspector.read_superblock(superblock_copy);
778
+
779
+ const free_set_blocks_acquired_size =
780
+ superblock.vsr_state.checkpoint.free_set_blocks_acquired_size;
781
+ const free_set_blocks_released_size =
782
+ superblock.vsr_state.checkpoint.free_set_blocks_released_size;
783
+
784
+ const free_set_blocks_acquired_buffer =
785
+ try inspector.allocator.alignedAlloc(
786
+ u8,
787
+ @alignOf(vsr.FreeSet.Word),
788
+ free_set_blocks_acquired_size,
789
+ );
790
+ defer inspector.allocator.free(free_set_blocks_acquired_buffer);
791
+
792
+ var free_set_blocks_acquired_addresses =
793
+ try std.ArrayList(u64).initCapacity(
794
+ inspector.allocator,
795
+ stdx.div_ceil(
796
+ free_set_blocks_acquired_size,
797
+ constants.block_size - @sizeOf(vsr.Header),
798
+ ),
799
+ );
800
+ defer free_set_blocks_acquired_addresses.deinit();
801
+
802
+ const free_set_blocks_released_buffer =
803
+ try inspector.allocator.alignedAlloc(
804
+ u8,
805
+ @alignOf(vsr.FreeSet.Word),
806
+ free_set_blocks_released_size,
807
+ );
808
+ defer inspector.allocator.free(free_set_blocks_released_buffer);
809
+
810
+ var free_set_blocks_released_addresses =
811
+ try std.ArrayList(u64).initCapacity(
812
+ inspector.allocator,
813
+ stdx.div_ceil(
814
+ free_set_blocks_released_size,
815
+ constants.block_size - @sizeOf(vsr.Header),
816
+ ),
817
+ );
818
+ defer free_set_blocks_released_addresses.deinit();
819
+
820
+ try inspector.read_free_set_bitset(
821
+ output,
822
+ superblock,
823
+ .blocks_acquired,
824
+ free_set_blocks_acquired_buffer,
825
+ &free_set_blocks_acquired_addresses,
826
+ );
827
+ try inspector.read_free_set_bitset(
828
+ output,
829
+ superblock,
830
+ .blocks_released,
831
+ free_set_blocks_released_buffer,
832
+ &free_set_blocks_released_addresses,
833
+ );
834
+
835
+ // This is not exact, but is an overestimate:
836
+ const grid_blocks_max =
837
+ @divFloor(constants.storage_size_limit_max, constants.block_size);
838
+
839
+ var free_set = try vsr.FreeSet.init(
840
+ inspector.allocator,
841
+ .{
842
+ .grid_size_limit = grid_blocks_max * constants.block_size,
843
+ .blocks_released_prior_checkpoint_durability_max = 0,
844
+ },
845
+ );
846
+ defer free_set.deinit(inspector.allocator);
847
+
848
+ const SliceOfAlignedWordSlice = []const []align(@alignOf(vsr.FreeSet.Word)) const u8;
849
+ const encoded_free_set_blocks_acquired: SliceOfAlignedWordSlice =
850
+ if (free_set_blocks_acquired_buffer.len != 0)
851
+ &.{free_set_blocks_acquired_buffer}
852
+ else
853
+ &.{};
854
+ const encoded_free_set_blocks_released: SliceOfAlignedWordSlice =
855
+ if (free_set_blocks_released_buffer.len != 0)
856
+ &.{free_set_blocks_released_buffer}
857
+ else
858
+ &.{};
859
+
860
+ free_set.open(.{
861
+ .encoded = .{
862
+ .blocks_acquired = encoded_free_set_blocks_acquired,
863
+ .blocks_released = encoded_free_set_blocks_released,
864
+ },
865
+ .free_set_block_addresses = .{
866
+ .blocks_acquired = free_set_blocks_acquired_addresses.items,
867
+ .blocks_released = free_set_blocks_released_addresses.items,
868
+ },
869
+ });
870
+
871
+ const free_set_acquired_address_max = free_set.highest_address_acquired() orelse 0;
872
+ const free_set_blocks_acquired_compression_ratio =
873
+ @as(f64, @floatFromInt(stdx.div_ceil(free_set_acquired_address_max, 8))) /
874
+ @as(f64, @floatFromInt(superblock.vsr_state.checkpoint.free_set_blocks_acquired_size));
875
+
876
+ const free_set_released_address_max = free_set.highest_address_released() orelse 0;
877
+ const free_set_blocks_released_compression_ratio =
878
+ @as(f64, @floatFromInt(stdx.div_ceil(free_set_released_address_max, 8))) /
879
+ @as(f64, @floatFromInt(superblock.vsr_state.checkpoint.free_set_blocks_released_size));
880
+
881
+ try output.print(
882
+ \\free_set.blocks_free={}
883
+ \\free_set.blocks_acquired={}
884
+ \\free_set.blocks_released={}
885
+ \\free_set.highest_address_acquired={?}
886
+ \\free_set.acquired_size={}
887
+ \\free_set.acquired_compression_ratio={d:0.4}
888
+ \\free_set.highest_address_released={?}
889
+ \\free_set.released_size={}
890
+ \\free_set.released_compression_ratio={d:0.4}
891
+ \\
892
+ ,
893
+ .{
894
+ free_set.count_free(),
895
+ free_set.count_acquired(),
896
+ free_set.count_released(),
897
+ free_set.highest_address_acquired(),
898
+ std.fmt.fmtIntSizeBin(superblock.vsr_state.checkpoint
899
+ .free_set_blocks_acquired_size),
900
+ free_set_blocks_acquired_compression_ratio,
901
+ free_set.highest_address_released(),
902
+ std.fmt.fmtIntSizeBin(superblock.vsr_state.checkpoint
903
+ .free_set_blocks_released_size),
904
+ free_set_blocks_released_compression_ratio,
905
+ },
906
+ );
907
+ }
908
+
909
+ fn inspect_grid_block(inspector: *Inspector, output: std.io.AnyWriter, address: u64) !void {
910
+ const block = try allocate_block(inspector.allocator);
911
+ defer inspector.allocator.free(block);
912
+
913
+ try inspector.read_block(block, address, null);
914
+
915
+ // If this is an unexpected (but valid) block, log an error but keep going.
916
+ const header = schema.header_from_block(block);
917
+ if (header.address != address) log.err("misdirected block", .{});
918
+
919
+ try print_block(output, block);
920
+ }
921
+
922
+ fn inspect_manifest(
923
+ inspector: *Inspector,
924
+ output: std.io.AnyWriter,
925
+ superblock_copy: ?u8,
926
+ ) !void {
927
+ const superblock = try inspector.read_superblock(superblock_copy);
928
+
929
+ const block = try allocate_block(inspector.allocator);
930
+ defer inspector.allocator.free(block);
931
+
932
+ var manifest_block_address = superblock.vsr_state.checkpoint.manifest_newest_address;
933
+ var manifest_block_checksum = superblock.vsr_state.checkpoint.manifest_newest_checksum;
934
+ for (0..superblock.vsr_state.checkpoint.manifest_block_count) |i| {
935
+ try output.print(
936
+ "manifest_log.blocks[{}]: address={} checksum={x:0>32} ",
937
+ .{ i, manifest_block_address, manifest_block_checksum },
938
+ );
939
+
940
+ inspector.read_block(
941
+ block,
942
+ manifest_block_address,
943
+ manifest_block_checksum,
944
+ ) catch {
945
+ try output.writeAll("error: manifest block not found\n");
946
+ break;
947
+ };
948
+
949
+ var entry_counts = std.enums.EnumArray(
950
+ schema.ManifestNode.Event,
951
+ [constants.lsm_levels]usize,
952
+ ).initDefault([_]usize{0} ** constants.lsm_levels, .{});
953
+
954
+ const manifest_node = schema.ManifestNode.from(block);
955
+ for (manifest_node.tables_const(block)) |*table_info| {
956
+ entry_counts.getPtr(table_info.label.event)[table_info.label.level] += 1;
957
+ }
958
+
959
+ try output.print(
960
+ "entries={}/{}",
961
+ .{ manifest_node.entry_count, schema.ManifestNode.entry_count_max },
962
+ );
963
+
964
+ for (std.enums.values(schema.ManifestNode.Event)) |event| {
965
+ if (event == .reserved) continue;
966
+ try output.print(" {s}=", .{@tagName(event)});
967
+ for (0..constants.lsm_levels) |level| {
968
+ if (level != 0) try output.writeAll(",");
969
+ try output.print("{}", .{entry_counts.get(event)[level]});
970
+ }
971
+ }
972
+ try output.writeAll("\n");
973
+
974
+ const manifest_metadata = schema.ManifestNode.metadata(block);
975
+ manifest_block_address = manifest_metadata.previous_manifest_block_address;
976
+ manifest_block_checksum = manifest_metadata.previous_manifest_block_checksum;
977
+ }
978
+ }
979
+
980
+ fn inspect_tables(
981
+ inspector: *Inspector,
982
+ output: std.io.AnyWriter,
983
+ superblock_copy: ?u8,
984
+ filter: struct { tree_id: u16, level: ?u6 },
985
+ ) !void {
986
+ var tables_latest =
987
+ std.AutoHashMap(u128, ?schema.ManifestNode.TableInfo).init(inspector.allocator);
988
+ defer tables_latest.deinit();
989
+
990
+ const block = try allocate_block(inspector.allocator);
991
+ defer inspector.allocator.free(block);
992
+
993
+ // Construct a set of all active tables.
994
+ const superblock = try inspector.read_superblock(superblock_copy);
995
+ var manifest_block_address = superblock.vsr_state.checkpoint.manifest_newest_address;
996
+ var manifest_block_checksum = superblock.vsr_state.checkpoint.manifest_newest_checksum;
997
+ for (0..superblock.vsr_state.checkpoint.manifest_block_count) |_| {
998
+ try inspector.read_block(block, manifest_block_address, manifest_block_checksum);
999
+
1000
+ const manifest_node = schema.ManifestNode.from(block);
1001
+ const tables = manifest_node.tables_const(block);
1002
+ for (0..tables.len) |i| {
1003
+ const table_info = &tables[tables.len - i - 1];
1004
+ const table_latest = try tables_latest.getOrPut(table_info.checksum);
1005
+ if (!table_latest.found_existing) {
1006
+ if (table_info.label.event == .remove) {
1007
+ table_latest.value_ptr.* = null;
1008
+ } else {
1009
+ table_latest.value_ptr.* = table_info.*;
1010
+ }
1011
+ }
1012
+ }
1013
+
1014
+ const manifest_metadata = schema.ManifestNode.metadata(block);
1015
+ manifest_block_address = manifest_metadata.previous_manifest_block_address;
1016
+ manifest_block_checksum = manifest_metadata.previous_manifest_block_checksum;
1017
+ }
1018
+
1019
+ var tables_filtered =
1020
+ std.ArrayList(schema.ManifestNode.TableInfo).init(inspector.allocator);
1021
+ defer tables_filtered.deinit();
1022
+
1023
+ // Construct a list of only the tables matching the `filter`.
1024
+ var tables_latest_iterator = tables_latest.iterator();
1025
+ while (tables_latest_iterator.next()) |table_or_null| {
1026
+ const table = table_or_null.value_ptr.* orelse continue;
1027
+ if (table.tree_id != filter.tree_id) continue;
1028
+ if (filter.level) |level| {
1029
+ if (table.label.level != level) continue;
1030
+ }
1031
+ try tables_filtered.append(table);
1032
+ }
1033
+
1034
+ // Order the tables in a predictable way, since the manifest log can shuffle them around.
1035
+ std.mem.sortUnstable(schema.ManifestNode.TableInfo, tables_filtered.items, {}, struct {
1036
+ fn less_than(
1037
+ _: void,
1038
+ table_a: schema.ManifestNode.TableInfo,
1039
+ table_b: schema.ManifestNode.TableInfo,
1040
+ ) bool {
1041
+ for ([_]std.math.Order{
1042
+ std.math.order(table_a.tree_id, table_b.tree_id),
1043
+ std.math.order(table_a.label.level, table_b.label.level),
1044
+ std.math.order(
1045
+ std.mem.bytesAsValue(u256, &table_a.key_min).*,
1046
+ std.mem.bytesAsValue(u256, &table_b.key_min).*,
1047
+ ),
1048
+ std.math.order(
1049
+ std.mem.bytesAsValue(u256, &table_a.key_max).*,
1050
+ std.mem.bytesAsValue(u256, &table_b.key_max).*,
1051
+ ),
1052
+ std.math.order(table_a.snapshot_min, table_b.snapshot_min),
1053
+ std.math.order(table_a.snapshot_max, table_b.snapshot_max),
1054
+ std.math.order(table_a.checksum, table_b.checksum),
1055
+ }) |order| {
1056
+ if (order != .eq) return order == .lt;
1057
+ }
1058
+ // This *should* be unreachable, especially given the checksum comparison.
1059
+ return false;
1060
+ }
1061
+ }.less_than);
1062
+
1063
+ inline for (StateMachine.Forest.tree_infos) |tree_info| {
1064
+ if (tree_info.tree_id == filter.tree_id) {
1065
+ for (tables_filtered.items) |*table| {
1066
+ try print_table_info(output, tree_info, table);
1067
+ }
1068
+ break;
1069
+ }
1070
+ } else {
1071
+ try output.print("error: unknown tree_id={}\n", .{filter.tree_id});
1072
+ }
1073
+ }
1074
+
1075
+ fn read_buffer(
1076
+ inspector: *Inspector,
1077
+ buffer: []align(constants.sector_size) u8,
1078
+ zone: vsr.Zone,
1079
+ offset_in_zone: u64,
1080
+ ) !void {
1081
+ inspector.storage.read_sectors(
1082
+ inspector_read_callback,
1083
+ &inspector.read,
1084
+ buffer,
1085
+ zone,
1086
+ offset_in_zone,
1087
+ );
1088
+ try inspector.work();
1089
+ }
1090
+
1091
+ fn read_superblock(inspector: *const Inspector, superblock_copy: ?u8) !*const SuperBlockHeader {
1092
+ if (superblock_copy) |copy| {
1093
+ return inspector.superblock_headers[copy];
1094
+ } else {
1095
+ var copies: [constants.superblock_copies]SuperBlockHeader = undefined;
1096
+ for (&copies, inspector.superblock_headers) |*copy, header| copy.* = header.*;
1097
+
1098
+ var quorums = SuperBlockQuorums{};
1099
+ const quorum = try quorums.working(&copies, .open);
1100
+ if (!quorum.valid) return error.SuperBlockQuorumInvalid;
1101
+ return inspector.superblock_headers[quorum.copies.first_set().?];
1102
+ }
1103
+ }
1104
+
1105
+ fn read_block(
1106
+ inspector: *Inspector,
1107
+ buffer: BlockPtr,
1108
+ address: u64,
1109
+ checksum: ?u128,
1110
+ ) !void {
1111
+ try inspector.read_buffer(buffer, .grid, (address - 1) * constants.block_size);
1112
+
1113
+ const header = std.mem.bytesAsValue(vsr.Header.Block, buffer[0..@sizeOf(vsr.Header)]);
1114
+ if (!header.valid_checksum()) {
1115
+ log.err(
1116
+ "read_block: invalid block address={} checksum_expect={?x:0>32} " ++
1117
+ "checksum_actual={x:0>32} (bad checksum)",
1118
+ .{ address, checksum, header.checksum },
1119
+ );
1120
+ return error.InvalidChecksum;
1121
+ }
1122
+
1123
+ if (!header.valid_checksum_body(buffer[@sizeOf(vsr.Header)..header.size])) {
1124
+ log.err(
1125
+ "read_block: invalid block address={} checksum_expect={?x:0>32} " ++
1126
+ "checksum_actual={x:0>32} (bad checksum_body)",
1127
+ .{ address, checksum, header.checksum },
1128
+ );
1129
+ return error.InvalidChecksumBody;
1130
+ }
1131
+
1132
+ if (checksum) |checksum_| {
1133
+ if (header.checksum != checksum_) {
1134
+ log.err(
1135
+ "read_block: invalid block address={} checksum_expect={?x:0>32} " ++
1136
+ "checksum_actual={x:0>32} (wrong block)",
1137
+ .{ address, checksum, header.checksum },
1138
+ );
1139
+ return error.WrongBlock;
1140
+ }
1141
+ }
1142
+ }
1143
+
1144
+ fn read_free_set_bitset(
1145
+ inspector: *Inspector,
1146
+ output: std.io.AnyWriter,
1147
+ superblock: *const SuperBlockHeader,
1148
+ bitset: vsr.FreeSet.BitsetKind,
1149
+ free_set_buffer: []align(@alignOf(vsr.FreeSet.Word)) u8,
1150
+ free_set_addresses: *std.ArrayList(u64),
1151
+ ) !void {
1152
+ const block = try allocate_block(inspector.allocator);
1153
+ defer inspector.allocator.free(block);
1154
+
1155
+ const free_set_reference = superblock.free_set_reference(bitset);
1156
+ const free_set_size = free_set_reference.trailer_size;
1157
+ const free_set_checksum = free_set_reference.checksum;
1158
+
1159
+ const free_set_block_count = stdx.div_ceil(
1160
+ free_set_size,
1161
+ constants.block_size - @sizeOf(vsr.Header),
1162
+ );
1163
+
1164
+ var free_set_block_references = try std.ArrayList(vsr.BlockReference).initCapacity(
1165
+ inspector.allocator,
1166
+ free_set_block_count,
1167
+ );
1168
+ defer free_set_block_references.deinit();
1169
+
1170
+ if (free_set_size > 0) {
1171
+ // Read free set from the grid by manually following the linked list of blocks.
1172
+ // Note that free set is written in direct order, and must be read backwards.
1173
+ var free_set_block_reference: ?vsr.BlockReference = .{
1174
+ .address = free_set_reference.last_block_address,
1175
+ .checksum = free_set_reference.last_block_checksum,
1176
+ };
1177
+
1178
+ var free_set_cursor: usize = free_set_size;
1179
+ while (free_set_block_reference) |block_reference| {
1180
+ try inspector.read_block(
1181
+ block,
1182
+ block_reference.address,
1183
+ block_reference.checksum,
1184
+ );
1185
+
1186
+ assert(schema.header_from_block(block).checksum == block_reference.checksum);
1187
+
1188
+ const encoded_words = schema.TrailerNode.body(block);
1189
+ free_set_cursor -= encoded_words.len;
1190
+ stdx.copy_disjoint(
1191
+ .inexact,
1192
+ u8,
1193
+ free_set_buffer[free_set_cursor..],
1194
+ encoded_words,
1195
+ );
1196
+ free_set_block_references.appendAssumeCapacity(block_reference);
1197
+ free_set_addresses.appendAssumeCapacity(block_reference.address);
1198
+ free_set_block_reference = schema.TrailerNode.previous(block);
1199
+ }
1200
+ assert(free_set_block_reference == null);
1201
+ assert(free_set_cursor == 0);
1202
+ } else {
1203
+ assert(free_set_reference.last_block_address == 0);
1204
+ assert(free_set_reference.last_block_checksum == 0);
1205
+ }
1206
+
1207
+ assert(free_set_block_references.items.len == free_set_block_count);
1208
+ assert(free_set_addresses.items.len == free_set_block_count);
1209
+ assert(vsr.checksum(free_set_buffer[0..free_set_size]) == free_set_checksum);
1210
+
1211
+ for (free_set_block_references.items, 0..) |reference, i| {
1212
+ try output.print(
1213
+ "free_set_trailer.blocks[{}]: address={} checksum={x:0>32}\n",
1214
+ .{ i, reference.address, reference.checksum },
1215
+ );
1216
+ }
1217
+ }
1218
+
1219
+ const ClientSessions = extern struct {
1220
+ headers: [constants.clients_max]vsr.Header.Reply,
1221
+ sessions: [constants.clients_max]u64,
1222
+ };
1223
+
1224
+ fn read_client_sessions(
1225
+ inspector: *Inspector,
1226
+ block: BlockPtr,
1227
+ superblock_copy: ?u8,
1228
+ ) !?*ClientSessions {
1229
+ const superblock = try inspector.read_superblock(superblock_copy);
1230
+
1231
+ if (superblock.vsr_state.checkpoint.client_sessions_size == 0) {
1232
+ assert(superblock.vsr_state.checkpoint.client_sessions_last_block_address == 0);
1233
+ assert(superblock.vsr_state.checkpoint.client_sessions_last_block_checksum == 0);
1234
+ return null;
1235
+ }
1236
+ assert(superblock.vsr_state.checkpoint.client_sessions_size == @sizeOf(ClientSessions));
1237
+
1238
+ try inspector.read_block(
1239
+ block,
1240
+ superblock.vsr_state.checkpoint.client_sessions_last_block_address,
1241
+ superblock.vsr_state.checkpoint.client_sessions_last_block_checksum,
1242
+ );
1243
+
1244
+ const block_header = schema.header_from_block(block);
1245
+ assert(block_header.size == @sizeOf(vsr.Header) + @sizeOf(ClientSessions));
1246
+ assert(vsr.checksum(block[@sizeOf(vsr.Header)..block_header.size]) ==
1247
+ superblock.vsr_state.checkpoint.client_sessions_checksum);
1248
+
1249
+ return std.mem.bytesAsValue(
1250
+ ClientSessions,
1251
+ block[@sizeOf(vsr.Header)..][0..@sizeOf(ClientSessions)],
1252
+ );
1253
+ }
1254
+ };
1255
+
1256
+ fn print_struct(
1257
+ output: std.io.AnyWriter,
1258
+ label: []const u8,
1259
+ value: anytype,
1260
+ ) !void {
1261
+ comptime assert(@typeInfo(@TypeOf(value)) == .pointer);
1262
+ comptime assert(@typeInfo(@TypeOf(value)).pointer.size == .one);
1263
+
1264
+ const Type = @typeInfo(@TypeOf(value)).pointer.child;
1265
+ // Print structs *without* a custom format() function.
1266
+ if (comptime @typeInfo(Type) == .@"struct" and !std.meta.hasFn(Type, "format")) {
1267
+ if (@typeInfo(Type).@"struct".is_tuple) {
1268
+ try output.writeAll(label);
1269
+ // Print tuples as a single line.
1270
+ inline for (std.meta.fields(Type), 0..) |field, i| {
1271
+ if (@typeInfo(field.type) == .pointer and
1272
+ @typeInfo(@typeInfo(field.type).pointer.child) == .array)
1273
+ {
1274
+ // Allow inline labels.
1275
+ try output.writeAll(@field(value, field.name));
1276
+ } else {
1277
+ try print_value(output, @field(value, field.name));
1278
+ if (i != std.meta.fields(Type).len) try output.writeAll(" ");
1279
+ }
1280
+ }
1281
+ try output.writeAll("\n");
1282
+ return;
1283
+ } else {
1284
+ var label_buffer: [1024]u8 = undefined;
1285
+ inline for (std.meta.fields(Type)) |field| {
1286
+ var label_stream = std.io.fixedBufferStream(&label_buffer);
1287
+ try label_stream.writer().print("{s}.{s}", .{ label, field.name });
1288
+ try print_struct(output, label_stream.getWritten(), &@field(value, field.name));
1289
+ }
1290
+ return;
1291
+ }
1292
+ }
1293
+
1294
+ if (Element: {
1295
+ const type_info = @typeInfo(Type);
1296
+ if (type_info == .array) {
1297
+ break :Element @as(?type, type_info.array.child);
1298
+ }
1299
+ break :Element null;
1300
+ }) |Element| {
1301
+ if (Element == u8) {
1302
+ if (stdx.zeroed(value)) {
1303
+ return output.print("{s}=[{}]u8{{0}}\n", .{ label, value.len });
1304
+ } else {
1305
+ return output.print("{s}=[{}]u8{{nonzero}}\n", .{ label, value.len });
1306
+ }
1307
+ } else {
1308
+ var label_buffer: [1024]u8 = undefined;
1309
+ for (value[0..], 0..) |*item, index| {
1310
+ var label_stream = std.io.fixedBufferStream(&label_buffer);
1311
+ try label_stream.writer().print("{s}[{}]", .{ label, index });
1312
+ try print_struct(output, label_stream.getWritten(), item);
1313
+ }
1314
+ return;
1315
+ }
1316
+ }
1317
+
1318
+ try output.print("{s}=", .{label});
1319
+ try print_value(output, value.*);
1320
+ try output.writeAll("\n");
1321
+ }
1322
+
1323
+ fn print_value(output: std.io.AnyWriter, value: anytype) !void {
1324
+ const Type = @TypeOf(value);
1325
+ if (@typeInfo(Type) == .@"struct") assert(std.meta.hasFn(Type, "format"));
1326
+ assert(@typeInfo(Type) != .array);
1327
+
1328
+ if (Type == u128) return output.print("0x{x:0>32}", .{value});
1329
+
1330
+ if (Type == vsr.Operation) {
1331
+ if (value.valid(StateMachine.Operation)) {
1332
+ return output.writeAll(value.tag_name(StateMachine.Operation));
1333
+ } else {
1334
+ return output.print("{}!", .{@intFromEnum(value)});
1335
+ }
1336
+ }
1337
+
1338
+ if (@typeInfo(Type) == .@"enum") {
1339
+ if (std.enums.tagName(Type, value)) |value_string| {
1340
+ return output.print("{s}", .{value_string});
1341
+ } else {
1342
+ return output.print("{}!", .{@intFromEnum(value)});
1343
+ }
1344
+ }
1345
+ try output.print("{}", .{value});
1346
+ }
1347
+
1348
+ fn print_block(writer: std.io.AnyWriter, block: BlockPtrConst) !void {
1349
+ const header = schema.header_from_block(block);
1350
+ try print_struct(writer, "header", header);
1351
+
1352
+ inline for (.{
1353
+ .{ .block_type = .free_set, .Schema = schema.TrailerNode },
1354
+ .{ .block_type = .client_sessions, .Schema = schema.TrailerNode },
1355
+ .{ .block_type = .manifest, .Schema = schema.ManifestNode },
1356
+ .{ .block_type = .index, .Schema = schema.TableIndex },
1357
+ .{ .block_type = .value, .Schema = schema.TableValue },
1358
+ }) |pair| {
1359
+ if (header.block_type == pair.block_type) {
1360
+ try print_struct(writer, "header.metadata", pair.Schema.metadata(block));
1361
+ break;
1362
+ }
1363
+ } else {
1364
+ try writer.print("header.metadata: unknown block type\n", .{});
1365
+ }
1366
+
1367
+ switch (header.block_type) {
1368
+ .manifest => {
1369
+ const manifest_node = schema.ManifestNode.from(block);
1370
+ for (manifest_node.tables_const(block), 0..) |*table_info, entry_index| {
1371
+ try writer.print(
1372
+ "entry[{:_>4}]: {s} level={} address={} checksum={x:0>32} " ++
1373
+ "tree_id={s} key={:0>64}..{:0>64} snapshot={}..{} values={}\n",
1374
+ .{
1375
+ entry_index,
1376
+ @tagName(table_info.label.event),
1377
+ table_info.label.level,
1378
+ table_info.address,
1379
+ table_info.checksum,
1380
+ format_tree_id(table_info.tree_id),
1381
+ std.fmt.fmtSliceHexLower(&table_info.key_min),
1382
+ std.fmt.fmtSliceHexLower(&table_info.key_max),
1383
+ table_info.snapshot_min,
1384
+ table_info.snapshot_max,
1385
+ table_info.value_count,
1386
+ },
1387
+ );
1388
+ }
1389
+ },
1390
+ .index => {
1391
+ const index = schema.TableIndex.from(block);
1392
+ for (
1393
+ index.value_addresses_used(block),
1394
+ index.value_checksums_used(block),
1395
+ 0..,
1396
+ ) |value_address, value_checksum, i| {
1397
+ try writer.print(
1398
+ "value_blocks[{:_>3}]: address={} checksum={x:0>32}\n",
1399
+ .{ i, value_address, value_checksum.value },
1400
+ );
1401
+ }
1402
+ },
1403
+ .value => {
1404
+ const value_block = schema.TableValue.from(block);
1405
+ const metadata = value_block.block_metadata(block);
1406
+ const value_bytes = value_block.block_values_used_bytes(block);
1407
+
1408
+ var label_buffer: [256]u8 = undefined;
1409
+ inline for (StateMachine.Forest.tree_infos) |tree_info| {
1410
+ if (metadata.tree_id == tree_info.tree_id) {
1411
+ for (
1412
+ std.mem.bytesAsSlice(tree_info.Tree.Table.Value, value_bytes),
1413
+ 0..,
1414
+ ) |*value, i| {
1415
+ var label_stream = std.io.fixedBufferStream(&label_buffer);
1416
+ try label_stream.writer().print("{s}[{}]", .{ tree_info.tree_name, i });
1417
+ if (comptime is_composite_key(tree_info.Tree.Table.Value)) {
1418
+ try label_stream.writer().writeAll(": ");
1419
+ try print_struct(
1420
+ writer,
1421
+ label_stream.getWritten(),
1422
+ &.{ value.field, value.timestamp },
1423
+ );
1424
+ } else {
1425
+ try print_struct(writer, label_stream.getWritten(), value);
1426
+ }
1427
+ }
1428
+ break;
1429
+ }
1430
+ } else {
1431
+ try writer.print("body: unknown tree id\n", .{});
1432
+ }
1433
+ },
1434
+ else => {
1435
+ try writer.print(
1436
+ "body: unimplemented for block_type={s}\n",
1437
+ .{@tagName(header.block_type)},
1438
+ );
1439
+ },
1440
+ }
1441
+ }
1442
+
1443
+ fn format_tree_id(tree_id: u16) []const u8 {
1444
+ inline for (StateMachine.Forest.tree_infos) |tree_info| {
1445
+ if (tree_info.tree_id == tree_id) {
1446
+ return tree_info.tree_name;
1447
+ }
1448
+ } else {
1449
+ return "(unknown)";
1450
+ }
1451
+ }
1452
+
1453
+ fn parse_tree_id(tree_label: []const u8) ?u16 {
1454
+ const tree_label_integer = std.fmt.parseInt(u16, tree_label, 10) catch null;
1455
+ inline for (StateMachine.Forest.tree_infos) |tree_info| {
1456
+ if (std.mem.eql(u8, tree_info.tree_name, tree_label)) {
1457
+ return tree_info.tree_id;
1458
+ }
1459
+
1460
+ if (tree_label_integer) |tree_id| {
1461
+ if (tree_info.tree_id == tree_id) {
1462
+ return tree_id;
1463
+ }
1464
+ }
1465
+ }
1466
+ return null;
1467
+ }
1468
+
1469
+ const operation_schemas = list: {
1470
+ const OperationSchema = struct {
1471
+ operation: vsr.Operation,
1472
+ Event: type,
1473
+ Result: type,
1474
+ };
1475
+
1476
+ var list: []const OperationSchema = &[_]OperationSchema{};
1477
+
1478
+ for (&[_]struct { vsr.Operation, type, type }{
1479
+ .{ .reserved, extern struct {}, extern struct {} },
1480
+ .{ .root, extern struct {}, extern struct {} },
1481
+ // TODO vsr.RegisterRequest once that is merged.
1482
+ .{ .register, extern struct {}, vsr.RegisterResult },
1483
+ .{ .reconfigure, vsr.ReconfigurationRequest, vsr.ReconfigurationResult },
1484
+ .{ .pulse, extern struct {}, extern struct {} },
1485
+ .{ .upgrade, vsr.UpgradeRequest, extern struct {} },
1486
+ }) |operation_schema| {
1487
+ list = list ++ [_]OperationSchema{.{
1488
+ .operation = operation_schema[0],
1489
+ .Event = operation_schema[1],
1490
+ .Result = operation_schema[2],
1491
+ }};
1492
+ }
1493
+
1494
+ for (std.enums.values(StateMachine.Operation)) |operation| {
1495
+ if (operation == .pulse) continue;
1496
+ list = list ++ [_]OperationSchema{.{
1497
+ .operation = operation.to_vsr(),
1498
+ .Event = operation.EventType(),
1499
+ .Result = operation.ResultType(),
1500
+ }};
1501
+ }
1502
+ break :list list;
1503
+ };
1504
+
1505
+ fn print_prepare_body(output: std.io.AnyWriter, prepare: []const u8) !void {
1506
+ const header = std.mem.bytesAsValue(vsr.Header.Prepare, prepare[0..@sizeOf(vsr.Header)]);
1507
+ inline for (operation_schemas) |operation_schema| {
1508
+ if (operation_schema.operation == header.operation) {
1509
+ const event_size = @sizeOf(operation_schema.Event);
1510
+ const body_size = header.size - @sizeOf(vsr.Header);
1511
+ if (body_size == 0) {
1512
+ try output.print("(no body)\n", .{});
1513
+ } else if (event_size != 0 and body_size % event_size == 0) {
1514
+ var label_buffer: [128]u8 = undefined;
1515
+ for (std.mem.bytesAsSlice(
1516
+ operation_schema.Event,
1517
+ prepare[@sizeOf(vsr.Header)..header.size],
1518
+ ), 0..) |*event, i| {
1519
+ var label_stream = std.io.fixedBufferStream(&label_buffer);
1520
+ try label_stream.writer().print("events[{}]: ", .{i});
1521
+ try print_struct(output, label_stream.getWritten(), event);
1522
+ }
1523
+ } else {
1524
+ try output.print(
1525
+ "error: unexpected body size={}, @sizeOf(Event)={}\n",
1526
+ .{ header.size, event_size },
1527
+ );
1528
+ }
1529
+ return;
1530
+ }
1531
+ } else {
1532
+ try output.print("error: unimplemented operation={s}\n", .{@tagName(header.operation)});
1533
+ }
1534
+ }
1535
+
1536
+ fn print_reply_body(output: std.io.AnyWriter, reply: []const u8) !void {
1537
+ const header = std.mem.bytesAsValue(vsr.Header.Reply, reply[0..@sizeOf(vsr.Header)]);
1538
+ inline for (operation_schemas) |operation_schema| {
1539
+ if (operation_schema.operation == header.operation) {
1540
+ const result_size = @sizeOf(operation_schema.Result);
1541
+ const body_size = header.size - @sizeOf(vsr.Header);
1542
+ if (body_size == 0) {
1543
+ try output.print("(no body)\n", .{});
1544
+ } else if (result_size != 0 and body_size % result_size == 0) {
1545
+ var label_buffer: [128]u8 = undefined;
1546
+ for (std.mem.bytesAsSlice(
1547
+ operation_schema.Result,
1548
+ reply[@sizeOf(vsr.Header)..header.size],
1549
+ ), 0..) |*result, i| {
1550
+ var label_stream = std.io.fixedBufferStream(&label_buffer);
1551
+ try label_stream.writer().print("results[{}]: ", .{i});
1552
+ try print_struct(output, label_stream.getWritten(), result);
1553
+ }
1554
+ } else {
1555
+ try output.print(
1556
+ "error: unexpected body size={}, @sizeOf(Result)={}\n",
1557
+ .{ header.size, result_size },
1558
+ );
1559
+ }
1560
+ return;
1561
+ }
1562
+ } else {
1563
+ try output.print("error: unimplemented operation={s}\n", .{@tagName(header.operation)});
1564
+ }
1565
+ }
1566
+
1567
+ fn print_table_info(
1568
+ output: std.io.AnyWriter,
1569
+ comptime tree_info: anytype,
1570
+ table: *const schema.ManifestNode.TableInfo,
1571
+ ) !void {
1572
+ try output.print("{c} T={s} L={}", .{
1573
+ @as(u8, switch (table.label.event) {
1574
+ .insert => 'I',
1575
+ .update => 'U',
1576
+ // These shouldn't be hit, but included just for completeness' sake:
1577
+ .remove => 'R',
1578
+ else => '?',
1579
+ }),
1580
+ format_tree_id(table.tree_id),
1581
+ table.label.level,
1582
+ });
1583
+
1584
+ const Key = tree_info.Tree.Table.Key;
1585
+ const Value = tree_info.Tree.Table.Value;
1586
+ const key_min = std.mem.bytesAsValue(Key, table.key_min[0..@sizeOf(Key)]).*;
1587
+ const key_max = std.mem.bytesAsValue(Key, table.key_max[0..@sizeOf(Key)]).*;
1588
+
1589
+ if (comptime is_composite_key(Value)) {
1590
+ const f: Value = undefined;
1591
+ const Field = @TypeOf(f.field);
1592
+ const key_min_timestamp: u64 = @truncate(key_min & std.math.maxInt(u64));
1593
+ const key_max_timestamp: u64 = @truncate(key_max & std.math.maxInt(u64));
1594
+ const key_min_field: Field = Value.key_prefix(key_min);
1595
+ const key_max_field: Field = Value.key_prefix(key_max);
1596
+
1597
+ try output.print(" K={:_>6}:{}..{:_>6}:{}", .{
1598
+ key_min_field,
1599
+ key_min_timestamp,
1600
+ key_max_field,
1601
+ key_max_timestamp,
1602
+ });
1603
+ } else {
1604
+ try output.print(" K={}..{}", .{ key_min, key_max });
1605
+ }
1606
+
1607
+ if (table.snapshot_max == std.math.maxInt(u64)) {
1608
+ try output.print(" S={}..max", .{table.snapshot_min});
1609
+ } else {
1610
+ try output.print(" S={}..{}", .{ table.snapshot_min, table.snapshot_max });
1611
+ }
1612
+
1613
+ try output.print(" V={:_>6}/{} C={x:0>32} A={} O={}\n", .{
1614
+ table.value_count,
1615
+ tree_info.Tree.Table.value_count_max,
1616
+ table.checksum,
1617
+ table.address,
1618
+ vsr.Zone.offset(.grid, (table.address - 1) * constants.block_size),
1619
+ });
1620
+ }
1621
+
1622
+ fn GroupByType(comptime count_max: usize) type {
1623
+ return struct {
1624
+ const GroupBy = @This();
1625
+ const BitSet = stdx.BitSetType(count_max);
1626
+
1627
+ count: usize = 0,
1628
+ checksums: [count_max]?u128 = @splat(null),
1629
+ matches: [count_max]BitSet = undefined,
1630
+
1631
+ pub fn compare(group_by: *GroupBy, bytes: []const u8) void {
1632
+ assert(group_by.count < count_max);
1633
+ defer group_by.count += 1;
1634
+
1635
+ assert(group_by.checksums[group_by.count] == null);
1636
+ group_by.checksums[group_by.count] = vsr.checksum(bytes);
1637
+ }
1638
+
1639
+ pub fn groups(group_by: *GroupBy) []const BitSet {
1640
+ assert(group_by.count == count_max);
1641
+
1642
+ var distinct: usize = 0;
1643
+ for (&group_by.checksums, 0..) |checksum_a, a| {
1644
+ var matches: BitSet = .{};
1645
+ for (&group_by.checksums, 0..) |checksum_b, b| {
1646
+ matches.set_value(b, checksum_a.? == checksum_b.?);
1647
+ }
1648
+ if (matches.first_set().? == a) {
1649
+ group_by.matches[distinct] = matches;
1650
+ distinct += 1;
1651
+ }
1652
+ }
1653
+ assert(distinct > 0);
1654
+ assert(distinct <= count_max);
1655
+ return group_by.matches[0..distinct];
1656
+ }
1657
+ };
1658
+ }