tigerbeetle 0.0.34 → 0.0.37

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 (249) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -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 +1084 -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 +11 -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 +362 -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 +1036 -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 +105 -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 +557 -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 +2910 -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/client.rb +1 -1
  245. data/lib/tigerbeetle/platforms.rb +9 -0
  246. data/lib/tigerbeetle/version.rb +2 -2
  247. data/tigerbeetle.gemspec +22 -5
  248. metadata +242 -3
  249. data/ext/tb_client/pkg.tar.gz +0 -0
@@ -0,0 +1,766 @@
1
+ //! The Vortex _supervisor_ is a program that runs:
2
+ //!
3
+ //! * a set of TigerBeetle replicas, forming a cluster
4
+ //! * a workload that runs commands and queries against the cluster, verifying its correctness
5
+ //! (whatever that means is up to the workload)
6
+ //!
7
+ //! The replicas and workload run as child processes, while the supervisor restarts terminated
8
+ //! replicas and injects crashes and network faults. After some configurable amount of time, the
9
+ //! supervisor terminates the workload and replicas, unless the workload exits on its own or if
10
+ //! any of the replicas exit unexpectedly.
11
+ //!
12
+ //! If the workload exits successfully, or is actively terminated, the whole vortex exits
13
+ //! successfully.
14
+ //!
15
+ //! To launch a one-second smoke test, run this command from the repository root:
16
+ //!
17
+ //! $ zig build test:integration -- "vortex smoke"
18
+ //!
19
+ //! If you need more control, you can run this program directly.
20
+ //!
21
+ //! $ zig build vortex -- supervisor
22
+ //!
23
+ //! Other options:
24
+ //!
25
+ //! * Set the test duration by adding the `--test-duration=XmYs` option (it's 1 minute by default).
26
+ //! * Enable replica debug logging with `--log-debug`.
27
+ //!
28
+ //! If you have permissions troubles with unshare and Ubuntu, see:
29
+ //! https://github.com/YoYoGames/GameMaker-Bugs/issues/6015#issuecomment-2135552784
30
+ //!
31
+ //! Further possible work:
32
+ //!
33
+ //! * full partitioning
34
+ //! * filesystem faults
35
+ //! * clock faults
36
+ //! * upgrades (replicas and clients)
37
+ //! * multiple drivers? could use a special multiplexer driver that delegates to others
38
+
39
+ const std = @import("std");
40
+ const stdx = @import("stdx");
41
+ const builtin = @import("builtin");
42
+ const IO = @import("../../io.zig").IO;
43
+ const RingBufferType = stdx.RingBufferType;
44
+ const LoggedProcess = @import("./logged_process.zig");
45
+ const faulty_network = @import("./faulty_network.zig");
46
+ const constants = @import("constants.zig");
47
+ const Progress = @import("./workload.zig").Progress;
48
+ const ratio = stdx.PRNG.ratio;
49
+ const Shell = @import("../../shell.zig");
50
+
51
+ const log = std.log.scoped(.supervisor);
52
+ const tigerbeetle_exe_default: []const u8 = @import("vortex_options").tigerbeetle_exe;
53
+ const vortex_driver_exe_default: []const u8 = @import("vortex_options").driver_exe;
54
+
55
+ const assert = std.debug.assert;
56
+ const maybe = stdx.maybe;
57
+
58
+ pub const CLIArgs = struct {
59
+ tigerbeetle_executable: ?[]const u8 = null,
60
+ test_duration: stdx.Duration = .minutes(1),
61
+ driver_command: ?[]const u8 = null,
62
+ replica_count: u8 = 1,
63
+ disable_faults: bool = false,
64
+ output_directory: ?[]const u8 = null,
65
+ log_debug: bool = false,
66
+ /// Log file path.
67
+ log: ?[]const u8 = null,
68
+
69
+ @"--": void,
70
+ /// Vortex is non-deterministic, but providing a seed can still help constrain the scenario.
71
+ seed: ?u64 = null,
72
+ };
73
+
74
+ pub fn main(allocator: std.mem.Allocator, args: CLIArgs) !void {
75
+ if (builtin.os.tag == .windows) {
76
+ log.err("vortex is not supported on Windows", .{});
77
+ return error.NotSupported;
78
+ }
79
+
80
+ if (args.log) |log_path| {
81
+ const log_file = try std.fs.cwd().createFile(log_path, .{});
82
+ defer log_file.close();
83
+
84
+ // Redirect stderr to the file.
85
+ try std.posix.dup2(log_file.handle, std.posix.STDERR_FILENO);
86
+ }
87
+
88
+ if (builtin.os.tag == .linux) {
89
+ // Relaunch in fresh pid / network namespaces.
90
+ try stdx.unshare.maybe_unshare_and_relaunch(allocator, .{
91
+ .pid = true,
92
+ .network = true,
93
+ });
94
+ } else {
95
+ log.warn("vortex may spawn runaway processes when run on a non-Linux OS", .{});
96
+ log.warn("vortex may encounter port collisions non-Linux OS", .{});
97
+ }
98
+
99
+ const shell = try Shell.create(allocator);
100
+ defer shell.destroy();
101
+
102
+ // By default, the shell uses project root as cwd, but we want to use the actual process cwd.
103
+ try shell.pushd_dir(std.fs.cwd());
104
+ defer shell.popd();
105
+
106
+ var io = try IO.init(128, 0);
107
+
108
+ const tigerbeetle_executable = args.tigerbeetle_executable orelse tigerbeetle_exe_default;
109
+ const output_directory = args.output_directory orelse try shell.create_tmp_dir();
110
+ defer {
111
+ if (args.output_directory == null) {
112
+ shell.cwd.deleteTree(output_directory) catch |err| {
113
+ log.err("error deleting tree: {}", .{err});
114
+ };
115
+ }
116
+ }
117
+
118
+ log.info("output directory: {s}", .{output_directory});
119
+ log.info("starting test with target runtime of {}", .{args.test_duration});
120
+
121
+ const seed = args.seed orelse std.crypto.random.int(u64);
122
+ var prng = stdx.PRNG.from_seed(seed);
123
+
124
+ var network = try faulty_network.Network.listen(
125
+ allocator,
126
+ &prng,
127
+ &io,
128
+ constants.vortex.replica_ports_actual[0..args.replica_count],
129
+ );
130
+ defer network.destroy(allocator);
131
+
132
+ var replicas: [constants.vsr.replicas_max]*Replica = undefined;
133
+ var replicas_initialized: usize = 0;
134
+ defer {
135
+ for (replicas[0..replicas_initialized]) |replica| {
136
+ // We might have terminated the replica and never restarted it,
137
+ // so we need to check its state.
138
+ if (replica.state() != .terminated) {
139
+ _ = replica.terminate() catch {};
140
+ }
141
+ replica.destroy();
142
+ }
143
+ }
144
+
145
+ var datafile_buffers: [constants.vsr.replicas_max][std.fs.max_path_bytes]u8 = undefined;
146
+ for (0..args.replica_count) |replica_index| {
147
+ const datafile = try std.fmt.bufPrint(
148
+ datafile_buffers[replica_index][0..],
149
+ "{s}/{d}_{d}.tigerbeetle",
150
+ .{ output_directory, constants.vortex.cluster_id, replica_index },
151
+ );
152
+
153
+ shell.exec(
154
+ \\{tigerbeetle_executable} format
155
+ \\ --cluster={cluster}
156
+ \\ --replica={replica_index}
157
+ \\ --replica-count={replica_count}
158
+ \\ {datafile}
159
+ , .{
160
+ .tigerbeetle_executable = tigerbeetle_executable,
161
+ .cluster = constants.vortex.cluster_id,
162
+ .replica_index = replica_index,
163
+ .replica_count = args.replica_count,
164
+ .datafile = datafile,
165
+ }) catch |err| {
166
+ log.err("failed formatting datafile: {}", .{err});
167
+ return error.SetupFailed;
168
+ };
169
+
170
+ var replica_ports: [constants.vsr.replicas_max]u16 = undefined;
171
+ for (replica_ports[0..args.replica_count], 0..) |*replica_port, i| {
172
+ if (replica_index == i) {
173
+ replica_port.* = network.proxies[i].remote_address.getPort();
174
+ } else {
175
+ replica_port.* = network.proxies[i].origin_address.getPort();
176
+ }
177
+ }
178
+
179
+ var replica = try Replica.create(
180
+ allocator,
181
+ tigerbeetle_executable,
182
+ args.replica_count,
183
+ @intCast(replica_index),
184
+ replica_ports,
185
+ datafile,
186
+ args.log_debug,
187
+ );
188
+
189
+ replicas[replica_index] = replica;
190
+ replicas_initialized += 1;
191
+
192
+ try replica.start();
193
+ }
194
+
195
+ var proxy_ports: [constants.vsr.replicas_max]u16 = undefined;
196
+ for (proxy_ports[0..args.replica_count], 0..) |*port, i| {
197
+ port.* = network.proxies[i].origin_address.getPort();
198
+ }
199
+
200
+ const workload = try Workload.spawn(allocator, &io, proxy_ports[0..args.replica_count], args);
201
+ defer {
202
+ if (workload.process.state == .running) {
203
+ _ = workload.process.terminate() catch {};
204
+ }
205
+ workload.destroy(allocator);
206
+ }
207
+
208
+ const supervisor = try Supervisor.create(allocator, .{
209
+ .io = &io,
210
+ .network = network,
211
+ .replicas = replicas[0..args.replica_count],
212
+ .workload = workload,
213
+ .prng = &prng,
214
+ .test_duration = args.test_duration,
215
+ .faulty = !args.disable_faults,
216
+ });
217
+ defer supervisor.destroy(allocator);
218
+
219
+ try supervisor.run();
220
+ }
221
+
222
+ const Supervisor = struct {
223
+ io: *IO,
224
+ network: *faulty_network.Network,
225
+ replicas: []*Replica,
226
+ workload: *Workload,
227
+ prng: *stdx.PRNG,
228
+ test_deadline: i128,
229
+ faulty: bool,
230
+
231
+ fn create(allocator: std.mem.Allocator, options: struct {
232
+ io: *IO,
233
+ network: *faulty_network.Network,
234
+ replicas: []*Replica,
235
+ workload: *Workload,
236
+ prng: *stdx.PRNG,
237
+ test_duration: stdx.Duration,
238
+ faulty: bool,
239
+ }) !*Supervisor {
240
+ const supervisor = try allocator.create(Supervisor);
241
+ errdefer allocator.destroy(supervisor);
242
+
243
+ supervisor.* = .{
244
+ .io = options.io,
245
+ .network = options.network,
246
+ .replicas = options.replicas,
247
+ .workload = options.workload,
248
+ .prng = options.prng,
249
+ .test_deadline = std.time.nanoTimestamp() + options.test_duration.ns,
250
+ .faulty = options.faulty,
251
+ };
252
+ return supervisor;
253
+ }
254
+
255
+ fn destroy(supervisor: *Supervisor, allocator: std.mem.Allocator) void {
256
+ allocator.destroy(supervisor);
257
+ }
258
+
259
+ fn run(supervisor: *Supervisor) !void {
260
+ var running_replicas_buffer: [constants.vsr.replicas_max]ReplicaWithIndex = undefined;
261
+ var terminated_replicas_buffer: [constants.vsr.replicas_max]ReplicaWithIndex = undefined;
262
+ var paused_replicas_buffer: [constants.vsr.replicas_max]ReplicaWithIndex = undefined;
263
+
264
+ var sleep_deadline: u64 = 0;
265
+ // This represents the start timestamp of a period where we have an acceptable number of
266
+ // process faults, such that we require liveness (that requests are finished within a
267
+ // certain time period). If null, it means we're in a period of too many faults, thus
268
+ // enforcing no such requirement.
269
+ var acceptable_faults_start_ns: ?u64 = null;
270
+ // How many replicas can be faulty while still expecting the cluster to
271
+ // make progress (based on 2f+1).
272
+ const liveness_faulty_replicas_max = @divFloor(supervisor.replicas.len - 1, 2);
273
+ const workload_result = while (std.time.nanoTimestamp() < supervisor.test_deadline) {
274
+ supervisor.network.tick();
275
+ try supervisor.io.run_for_ns(constants.vsr.tick_ms * std.time.ns_per_ms);
276
+ const now: u64 = @intCast(std.time.nanoTimestamp());
277
+
278
+ const running_replicas =
279
+ replicas_in_state(supervisor.replicas, &running_replicas_buffer, .running);
280
+ const terminated_replicas =
281
+ replicas_in_state(supervisor.replicas, &terminated_replicas_buffer, .terminated);
282
+ const paused_replicas =
283
+ replicas_in_state(supervisor.replicas, &paused_replicas_buffer, .paused);
284
+
285
+ const faulty_replica_count = terminated_replicas.len + paused_replicas.len;
286
+
287
+ if (acceptable_faults_start_ns) |start_ns| {
288
+ const deadline = start_ns + constants.vortex.liveness_requirement_seconds *
289
+ std.time.ns_per_s;
290
+ // If we've been in a state with an acceptable number of faults for the required
291
+ // amount of time, we should have seen finished requests.
292
+ const no_finished_requests =
293
+ now > deadline and supervisor.workload.requests_finished.empty();
294
+ // Also, those that do finish should not have too long durations, counting from the
295
+ // start of the acceptably-faulty period.
296
+ const too_slow_request = supervisor.workload.find_slow_request_since(start_ns);
297
+
298
+ if (no_finished_requests) {
299
+ log.err("liveness check: no finished requests after {d} seconds", .{
300
+ constants.vortex.liveness_requirement_seconds,
301
+ });
302
+ return error.TestFailed;
303
+ }
304
+
305
+ if (too_slow_request) |_| {
306
+ log.err("liveness check: too slow request", .{});
307
+ return error.TestFailed;
308
+ }
309
+ }
310
+
311
+ // Check if `acceptable_faults_start_ns` should change state. If so, we reset the max
312
+ // request duration too.
313
+ // NOTE: Network faults are currently global, so we relax the requirement in such cases.
314
+ if (faulty_replica_count <= liveness_faulty_replicas_max and
315
+ supervisor.network.faults.is_healed())
316
+ {
317
+ // We have an acceptable number of faults, so we require liveness (after some time).
318
+ if (acceptable_faults_start_ns == null) {
319
+ acceptable_faults_start_ns = @intCast(std.time.nanoTimestamp());
320
+ supervisor.workload.requests_finished.clear();
321
+ }
322
+ } else {
323
+ // We have too many faults to require liveness.
324
+ if (acceptable_faults_start_ns) |_| {
325
+ acceptable_faults_start_ns = null;
326
+ supervisor.workload.requests_finished.clear();
327
+ }
328
+ }
329
+
330
+ if (sleep_deadline < now and supervisor.faulty) {
331
+ const Action = enum {
332
+ sleep,
333
+ replica_terminate,
334
+ replica_restart,
335
+ replica_pause,
336
+ replica_resume,
337
+ network_delay,
338
+ network_lose,
339
+ network_corrupt,
340
+ network_heal,
341
+ quiesce,
342
+ };
343
+
344
+ switch (supervisor.prng.enum_weighted(Action, .{
345
+ .sleep = 10,
346
+ .replica_terminate = if (running_replicas.len > 0) 4 else 0,
347
+ .replica_restart = if (terminated_replicas.len > 0) 3 else 0,
348
+ .replica_pause = if (running_replicas.len > 0) 3 else 0,
349
+ .replica_resume = if (paused_replicas.len > 0) 10 else 0,
350
+ .network_delay = if (supervisor.network.faults.delay == null) 3 else 0,
351
+ .network_lose = if (supervisor.network.faults.lose == null) 3 else 0,
352
+ .network_corrupt = if (supervisor.network.faults.corrupt == null) 3 else 0,
353
+ .network_heal = if (!supervisor.network.faults.is_healed()) 10 else 0,
354
+ .quiesce = if (faulty_replica_count > 0 or
355
+ !supervisor.network.faults.is_healed()) 1 else 0,
356
+ })) {
357
+ .sleep => {
358
+ const duration =
359
+ supervisor.prng.int_inclusive(u64, 10 * std.time.ns_per_s);
360
+ log.info("sleeping for {}", .{std.fmt.fmtDuration(duration)});
361
+ sleep_deadline = now + duration;
362
+ },
363
+ .replica_terminate => {
364
+ const pick =
365
+ running_replicas[supervisor.prng.index(running_replicas)];
366
+ _ = try pick.replica.terminate();
367
+ },
368
+ .replica_restart => {
369
+ const pick =
370
+ terminated_replicas[supervisor.prng.index(terminated_replicas)];
371
+ try pick.replica.start();
372
+ },
373
+ .replica_pause => {
374
+ const pick =
375
+ running_replicas[supervisor.prng.index(running_replicas)];
376
+ try pick.replica.pause();
377
+ },
378
+ .replica_resume => {
379
+ const pick =
380
+ paused_replicas[supervisor.prng.index(paused_replicas)];
381
+ try pick.replica.unpause();
382
+ },
383
+ .network_delay => {
384
+ const time_ms = supervisor.prng.range_inclusive(u32, 10, 500);
385
+ supervisor.network.faults.delay = .{
386
+ .time_ms = time_ms,
387
+ .jitter_ms = @min(time_ms, 50),
388
+ };
389
+ log.info("injecting network delays: {any}", .{supervisor.network.faults});
390
+ },
391
+ .network_lose => {
392
+ supervisor.network.faults.lose =
393
+ ratio(supervisor.prng.range_inclusive(u8, 1, 10), 100);
394
+ log.info("injecting network loss: {any}", .{supervisor.network.faults});
395
+ },
396
+ .network_corrupt => {
397
+ supervisor.network.faults.corrupt =
398
+ ratio(supervisor.prng.range_inclusive(u8, 1, 10), 100);
399
+ log.info("injecting network corruption: {any}", .{
400
+ supervisor.network.faults,
401
+ });
402
+ },
403
+ .network_heal => {
404
+ log.info("healing network", .{});
405
+ supervisor.network.faults.heal();
406
+ },
407
+ .quiesce => {
408
+ const duration = supervisor.prng.range_inclusive(
409
+ u64,
410
+ constants.vortex.liveness_requirement_seconds,
411
+ constants.vortex.liveness_requirement_seconds * 2,
412
+ ) * std.time.ns_per_s;
413
+ sleep_deadline = now + duration;
414
+
415
+ supervisor.network.faults.heal();
416
+ for (paused_replicas) |paused| try paused.replica.unpause();
417
+ for (terminated_replicas) |terminated| try terminated.replica.start();
418
+
419
+ log.info("going into {} quiescence (no faults)", .{
420
+ std.fmt.fmtDuration(duration),
421
+ });
422
+ },
423
+ }
424
+ }
425
+
426
+ // Check for replicas that have exited.
427
+ for (supervisor.replicas, 0..) |replica, replica_index| {
428
+ if (replica.state() != .terminated) {
429
+ if (replica.process.?.wait_nonblocking()) |term| {
430
+ // Replicas shouldn't exit on their own, even with code=0.
431
+ maybe(std.meta.eql(term, .{ .Exited = 0 }));
432
+
433
+ log.err(
434
+ "{}: replica terminated unexpectedly with {}",
435
+ .{ replica_index, term },
436
+ );
437
+ if (std.meta.eql(term, .{ .Signal = std.posix.SIG.KILL })) {
438
+ // If one of the replica dies to SIGKILL, it is likely an OOM.
439
+ // Bubble that up to CFO so that this Vortex run is counted as neither a
440
+ // success or failure.
441
+ std.posix.exit(@intCast(128 + term.Signal));
442
+ } else {
443
+ return error.TestFailed;
444
+ }
445
+ }
446
+ }
447
+ }
448
+
449
+ if (supervisor.workload.process.wait_nonblocking()) |code| {
450
+ log.err("workload terminated by itself: code={}", .{code});
451
+ return error.TestFailed;
452
+ }
453
+ } else blk: {
454
+ log.info("terminating workload due to max duration", .{});
455
+ break :blk try supervisor.workload.process.terminate();
456
+ };
457
+
458
+ switch (workload_result) {
459
+ .Signal => |signal| {
460
+ switch (signal) {
461
+ std.posix.SIG.KILL => log.info("workload terminated as requested", .{}),
462
+ else => {
463
+ log.err("workload exited unexpectedly with signal {d}", .{signal});
464
+ return error.TestFailed;
465
+ },
466
+ }
467
+ },
468
+ else => {
469
+ log.err("unexpected workload result: {any}", .{workload_result});
470
+ return error.TestFailed;
471
+ },
472
+ }
473
+ }
474
+ };
475
+
476
+ const ReplicaWithIndex = struct { replica: *Replica, index: u8 };
477
+
478
+ fn replicas_in_state(
479
+ replicas: []*Replica,
480
+ buffer: []ReplicaWithIndex,
481
+ state: Replica.State,
482
+ ) []ReplicaWithIndex {
483
+ var count: u8 = 0;
484
+
485
+ for (replicas, 0..) |replica, index| {
486
+ if (replica.state() == state) {
487
+ buffer[count] = .{ .replica = replica, .index = @intCast(index) };
488
+ count += 1;
489
+ }
490
+ }
491
+ return buffer[0..count];
492
+ }
493
+
494
+ fn comma_separate_ports(allocator: std.mem.Allocator, ports: []const u16) ![]const u8 {
495
+ assert(ports.len > 0);
496
+
497
+ var out = std.ArrayList(u8).init(allocator);
498
+ errdefer out.deinit();
499
+
500
+ const writer = out.writer();
501
+ try writer.print("{d}", .{ports[0]});
502
+ for (ports[1..]) |port| try writer.print(",{d}", .{port});
503
+
504
+ return out.toOwnedSlice();
505
+ }
506
+
507
+ test comma_separate_ports {
508
+ const formatted = try comma_separate_ports(std.testing.allocator, &.{ 3000, 3001, 3002 });
509
+ defer std.testing.allocator.free(formatted);
510
+
511
+ try std.testing.expectEqualStrings("3000,3001,3002", formatted);
512
+ }
513
+
514
+ const Replica = struct {
515
+ pub const State = enum(u8) { initial, running, paused, terminated };
516
+
517
+ allocator: std.mem.Allocator,
518
+ executable_path: []const u8,
519
+ replica_count: u8,
520
+ replica_index: u8,
521
+ replica_ports: [constants.vsr.replicas_max]u16,
522
+ datafile: []const u8,
523
+ log_debug: bool,
524
+ process: ?*LoggedProcess,
525
+
526
+ pub fn create(
527
+ allocator: std.mem.Allocator,
528
+ executable_path: []const u8,
529
+ replica_count: u8,
530
+ replica_index: u8,
531
+ replica_ports: [constants.vsr.replicas_max]u16,
532
+ datafile: []const u8,
533
+ log_debug: bool,
534
+ ) !*Replica {
535
+ assert(replica_index < replica_count);
536
+
537
+ const self = try allocator.create(Replica);
538
+ errdefer allocator.destroy(self);
539
+
540
+ self.* = .{
541
+ .allocator = allocator,
542
+ .executable_path = executable_path,
543
+ .replica_count = replica_count,
544
+ .replica_index = replica_index,
545
+ .replica_ports = replica_ports,
546
+ .datafile = datafile,
547
+ .log_debug = log_debug,
548
+ .process = null,
549
+ };
550
+ return self;
551
+ }
552
+
553
+ pub fn destroy(self: *Replica) void {
554
+ assert(self.state() == .initial or self.state() == .terminated);
555
+ const allocator = self.allocator;
556
+ if (self.process) |process| {
557
+ process.destroy(allocator);
558
+ }
559
+ allocator.destroy(self);
560
+ }
561
+
562
+ pub fn state(self: *Replica) State {
563
+ if (self.process) |process| {
564
+ switch (process.state) {
565
+ .running => return .running,
566
+ .paused => return .paused,
567
+ .terminated => return .terminated,
568
+ }
569
+ } else return .initial;
570
+ }
571
+
572
+ pub fn start(self: *Replica) !void {
573
+ assert(self.state() != .running);
574
+ defer assert(self.state() == .running);
575
+
576
+ if (self.process) |process| {
577
+ process.destroy(self.allocator);
578
+ }
579
+
580
+ const replica_addresses =
581
+ try comma_separate_ports(self.allocator, self.replica_ports[0..self.replica_count]);
582
+ defer self.allocator.free(replica_addresses);
583
+
584
+ var addresses_buffer: [128]u8 = undefined;
585
+ const addresses_arg = try std.fmt.bufPrint(
586
+ addresses_buffer[0..],
587
+ "--addresses={s}",
588
+ .{replica_addresses},
589
+ );
590
+
591
+ var argv: stdx.BoundedArrayType([]const u8, 16) = .{};
592
+ argv.push_slice(&.{ self.executable_path, "start" });
593
+ if (self.log_debug) {
594
+ argv.push_slice(&.{ "--log-debug", "--experimental" });
595
+ }
596
+ argv.push_slice(&.{ addresses_arg, self.datafile });
597
+
598
+ log.info("{}: starting replica", .{self.replica_index});
599
+ self.process = try LoggedProcess.spawn(self.allocator, argv.const_slice(), .{});
600
+ }
601
+
602
+ pub fn terminate(self: *Replica) !std.process.Child.Term {
603
+ assert(self.state() == .running or self.state() == .paused);
604
+ defer assert(self.state() == .terminated);
605
+
606
+ log.info("{}: terminating replica", .{self.replica_index});
607
+ return try self.process.?.terminate();
608
+ }
609
+
610
+ pub fn pause(self: *Replica) !void {
611
+ assert(self.state() == .running);
612
+ defer assert(self.state() == .paused);
613
+
614
+ log.info("{}: pausing replica", .{self.replica_index});
615
+ try self.process.?.pause();
616
+ }
617
+
618
+ pub fn unpause(self: *Replica) !void {
619
+ assert(self.state() == .paused);
620
+ defer assert(self.state() == .running);
621
+
622
+ log.info("{}: unpausing replica", .{self.replica_index});
623
+ try self.process.?.unpause();
624
+ }
625
+ };
626
+
627
+ const Workload = struct {
628
+ pub const State = enum(u8) { running, terminated };
629
+
630
+ const RequestInfo = struct {
631
+ timestamp_start_micros: u64,
632
+ timestamp_end_micros: u64,
633
+ };
634
+
635
+ const Requests = RingBufferType(RequestInfo, .{ .array = 1024 * 16 });
636
+
637
+ io: *IO,
638
+ process: *LoggedProcess,
639
+
640
+ read_buffer: [@sizeOf(Progress)]u8 = undefined,
641
+ read_completion: IO.Completion = undefined,
642
+ read_progress: usize = 0,
643
+
644
+ requests_finished: Requests = Requests.init(),
645
+
646
+ pub fn spawn(
647
+ allocator: std.mem.Allocator,
648
+ io: *IO,
649
+ proxy_ports: []u16,
650
+ args: CLIArgs,
651
+ ) !*Workload {
652
+ var vortex_path_buffer: [std.fs.max_path_bytes]u8 = undefined;
653
+ const vortex_path = try std.fs.selfExePath(&vortex_path_buffer);
654
+
655
+ const driver_command_selected = args.driver_command orelse vortex_driver_exe_default;
656
+ log.info("launching workload with driver: {s}", .{driver_command_selected});
657
+
658
+ var driver_command_arg_buffer: [std.fs.max_path_bytes]u8 = undefined;
659
+ const driver_command_arg = try std.fmt.bufPrint(
660
+ &driver_command_arg_buffer,
661
+ "--driver-command={s}",
662
+ .{driver_command_selected},
663
+ );
664
+
665
+ const proxy_addresses = try comma_separate_ports(allocator, proxy_ports);
666
+ defer allocator.free(proxy_addresses);
667
+
668
+ const arg_addresses =
669
+ try std.fmt.allocPrint(allocator, "--addresses={s}", .{proxy_addresses});
670
+ defer allocator.free(arg_addresses);
671
+
672
+ const argv = &.{
673
+ vortex_path,
674
+ "workload",
675
+ std.fmt.comptimePrint("--cluster-id={d}", .{constants.vortex.cluster_id}),
676
+ arg_addresses,
677
+ driver_command_arg,
678
+ };
679
+
680
+ const workload = try allocator.create(Workload);
681
+ errdefer allocator.destroy(workload);
682
+
683
+ const process = try LoggedProcess.spawn(allocator, argv, .{
684
+ .stdout_behavior = .Pipe,
685
+ });
686
+ errdefer process.destroy(allocator);
687
+
688
+ workload.* = .{
689
+ .io = io,
690
+ .process = process,
691
+ };
692
+
693
+ // Kick off read loop.
694
+ workload.read();
695
+
696
+ return workload;
697
+ }
698
+
699
+ pub fn destroy(workload: *Workload, allocator: std.mem.Allocator) void {
700
+ assert(workload.process.state == .terminated);
701
+ workload.process.destroy(allocator);
702
+ allocator.destroy(workload);
703
+ }
704
+
705
+ fn read(workload: *Workload) void {
706
+ assert(workload.process.state == .running);
707
+
708
+ workload.io.read(
709
+ *Workload,
710
+ workload,
711
+ on_read,
712
+ &workload.read_completion,
713
+ workload.process.child.stdout.?.handle,
714
+ workload.read_buffer[workload.read_progress..workload.read_buffer.len],
715
+ 0,
716
+ );
717
+ }
718
+
719
+ fn on_read(
720
+ workload: *Workload,
721
+ _: *IO.Completion,
722
+ result: IO.ReadError!usize,
723
+ ) void {
724
+ if (workload.process.state != .running) return;
725
+
726
+ const count = result catch |err| {
727
+ log.err("couldn't read from workload stdout: {}", .{err});
728
+ return;
729
+ };
730
+
731
+ workload.read_progress += count;
732
+
733
+ if (workload.read_progress >= workload.read_buffer.len) {
734
+ const progress = std.mem.bytesAsValue(Progress, workload.read_buffer[0..]);
735
+ const request_info: RequestInfo = .{
736
+ .timestamp_start_micros = progress.timestamp_start_micros,
737
+ .timestamp_end_micros = progress.timestamp_end_micros,
738
+ };
739
+ workload.read_progress = 0;
740
+ workload.requests_finished.push(request_info) catch
741
+ log.warn("requests_finished is full", .{});
742
+
743
+ log.debug("workload: request done duration={}us events={}", .{
744
+ progress.timestamp_end_micros - progress.timestamp_start_micros,
745
+ progress.event_count,
746
+ });
747
+ }
748
+
749
+ workload.read();
750
+ }
751
+
752
+ fn find_slow_request_since(workload: *const Workload, start_ns: u64) ?RequestInfo {
753
+ var it = workload.requests_finished.iterator();
754
+ while (it.next()) |request| {
755
+ assert(request.timestamp_start_micros < request.timestamp_end_micros);
756
+ // If a request started before the acceptably-faulty period,
757
+ // we ignore that part of its duration.
758
+ const duration_adjusted_micros = request.timestamp_end_micros -|
759
+ @max(request.timestamp_start_micros, @divFloor(start_ns, 1000));
760
+ if (duration_adjusted_micros > constants.vortex.liveness_requirement_micros) {
761
+ return request;
762
+ }
763
+ }
764
+ return null;
765
+ }
766
+ };