hecks 2.7.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/exe/hecks +13 -0
- data/lib/hecks/adapters/driven/memory.rb +1 -1
- data/lib/hecks/adapters/driven/postgres.rb +1 -1
- data/lib/hecks/adapters/driven/prism.rb +1 -1
- data/lib/hecks/adapters/driven/sqlite/projection.rb +1 -1
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +31 -10
- data/lib/hecks/behaviors/expectations.rb +2 -2
- data/lib/hecks/bluebook/assembly/contract.rb +1 -1
- data/lib/hecks/bluebook/assembly/contracts.rb +5 -5
- data/lib/hecks/bluebook/assembly/specializer.rb +2 -2
- data/lib/hecks/bluebook/assembly.rb +1 -1
- data/lib/hecks/bluebook/behaviour/chapter.rb +5 -2
- data/lib/hecks/bluebook/behaviour/read_model.rb +18 -0
- data/lib/hecks/bluebook/capabilities.rb +2 -2
- data/lib/hecks/bluebook/chapter.rb +1 -1
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +2 -2
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +6 -1
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +2 -2
- data/lib/hecks/bluebook/dsl/entity_builder.rb +1 -1
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +1 -1
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +14 -9
- data/lib/hecks/bluebook/dsl/rule_reference.rb +3 -3
- data/lib/hecks/bluebook/dsl/translation_builder.rb +1 -1
- data/lib/hecks/bluebook/dsl/world_builder.rb +5 -0
- data/lib/hecks/bluebook/meta_validator/judge.rb +3 -3
- data/lib/hecks/bluebook/meta_validator/plan.rb +4 -4
- data/lib/hecks/bluebook/meta_validator/readings.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +2 -2
- data/lib/hecks/bluebook/meta_validator/shapes.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +11 -10
- data/lib/hecks/bluebook/meta_validator.rb +1 -1
- data/lib/hecks/bluebook/model_check/client_profile.rb +4 -70
- data/lib/hecks/bluebook/model_check.rb +12 -13
- data/lib/hecks/cache_dir.rb +103 -0
- data/lib/hecks/cli/document.rb +104 -0
- data/lib/hecks/cli/ir.rb +47 -0
- data/lib/hecks/cli/mcp.rb +32 -0
- data/lib/hecks/cli/mcp_door.rb +461 -0
- data/lib/hecks/cli/model_check.rb +270 -0
- data/lib/hecks/cli/project_cli.rb +123 -0
- data/lib/hecks/cli/project_diagrams.rb +43 -0
- data/lib/hecks/cli/run.rb +219 -0
- data/lib/hecks/cli/smoke_test.rb +59 -0
- data/lib/hecks/cli/stores.rb +54 -0
- data/lib/hecks/cli.rb +166 -0
- data/lib/hecks/facade/handle.rb +2 -2
- data/lib/hecks/facade/surface/aggregate_door.rb +1 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +16 -0
- data/lib/hecks/mcp_door_scope.rb +176 -0
- data/lib/hecks/ports/persistence/append_only.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +12 -6
- data/lib/hecks/ports/persistence/state_codec.rb +1 -1
- data/lib/hecks/projections/deploy/fargate.rb +1 -1
- data/lib/hecks/projections/deploy/lambda.rb +3 -3
- data/lib/hecks/projections/reference.rb +7 -1
- data/lib/hecks/projector/exporter.rb +4 -4
- data/lib/hecks/projector/target.rb +1 -1
- data/lib/hecks/release/runner/ci_publisher.rb +96 -0
- data/lib/hecks/release/runner/commands.rb +68 -0
- data/lib/hecks/release/runner/console.rb +52 -0
- data/lib/hecks/release/runner/gem_publisher.rb +44 -0
- data/lib/hecks/release/runner/git.rb +48 -0
- data/lib/hecks/release/runner/npm_publisher.rb +89 -0
- data/lib/hecks/release/runner/preflight.rb +119 -0
- data/lib/hecks/release/runner/published.rb +55 -0
- data/lib/hecks/release/runner/tagger.rb +87 -0
- data/lib/hecks/release/runner.rb +244 -0
- data/lib/hecks/router/namespace_installer.rb +1 -1
- data/lib/hecks/runtime/command_interpreter.rb +3 -3
- data/lib/hecks/runtime/command_rules/admissibility.rb +2 -2
- data/lib/hecks/runtime/entity_element.rb +1 -1
- data/lib/hecks/runtime/entity_interpreter.rb +1 -1
- data/lib/hecks/runtime/invocation.rb +1 -1
- data/lib/hecks/runtime/loader.rb +1 -1
- data/lib/hecks/runtime/outbox.rb +1 -1
- data/lib/hecks/runtime/reaction_invocation.rb +3 -3
- data/lib/hecks/runtime/read_model_interpreter.rb +37 -15
- data/lib/hecks/runtime/reference_hop.rb +4 -4
- data/lib/hecks/runtime/registry/verification.rb +11 -11
- data/lib/hecks/runtime/registry.rb +8 -4
- data/lib/hecks/runtime/saga_interpreter.rb +1 -1
- data/lib/hecks/runtime/value/coercion.rb +3 -3
- data/lib/hecks/runtime/value/entity_list_coercion.rb +1 -1
- data/lib/hecks/storehouse.rb +18 -12
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +5 -0
- data/lib/hecks.rb +6 -1
- metadata +28 -58
- data/lib/hecks/bench/cli.rb +0 -74
- data/lib/hecks/bench/environment.rb +0 -100
- data/lib/hecks/bench/postgres_probe.rb +0 -41
- data/lib/hecks/bench/report.rb +0 -116
- data/lib/hecks/bench/ruby_runner.rb +0 -151
- data/lib/hecks/bench/run.rb +0 -55
- data/lib/hecks/bench/rust_runner.rb +0 -165
- data/lib/hecks/bench/stats.rb +0 -75
- data/lib/hecks/bench/suite.rb +0 -172
- data/lib/hecks/bench/workload.rb +0 -137
- data/lib/hecks/bench.rb +0 -28
- data/lib/hecks/codemod.rb +0 -443
- data/lib/hecks/corpus.rb +0 -479
- data/lib/hecks/doc/reference.rb +0 -585
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
- data/lib/hecks/fuzzing/combination_miner.rb +0 -178
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
- data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
- data/lib/hecks/fuzzing/differential.rb +0 -192
- data/lib/hecks/fuzzing/domain_generator.rb +0 -871
- data/lib/hecks/fuzzing/era_boundary.rb +0 -154
- data/lib/hecks/fuzzing/form_census.rb +0 -285
- data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
- data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
- data/lib/hecks/fuzzing/isolated_boot.rb +0 -658
- data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
- data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
- data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
- data/lib/hecks/fuzzing/properties/guards.rb +0 -398
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
- data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
- data/lib/hecks/fuzzing/properties/querying.rb +0 -260
- data/lib/hecks/fuzzing/properties.rb +0 -267
- data/lib/hecks/fuzzing/qa_settings.rb +0 -164
- data/lib/hecks/fuzzing/replay.rb +0 -930
- data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
- data/lib/hecks/fuzzing/self_consistency.rb +0 -825
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
- data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
- data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
- data/lib/hecks/fuzzing/shrinker.rb +0 -309
- data/lib/hecks/fuzzing/structural_skips.rb +0 -53
- data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
- data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
- data/lib/hecks/fuzzing/value_generator.rb +0 -340
- data/lib/hecks/fuzzing.rb +0 -23
- data/lib/hecks/grammar/evolve.rb +0 -524
- data/lib/hecks/query_ir.rb +0 -482
data/lib/hecks/bench/suite.rb
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
module Hecks
|
|
2
|
-
module Bench
|
|
3
|
-
# Runs every requested target against every requested domain and aggregates the runs.
|
|
4
|
-
#
|
|
5
|
-
# Each domain's runs are interleaved across targets (memory, sqlite, ..., memory,
|
|
6
|
-
# sqlite, ...) rather than finished one target at a time, so a burst of unrelated load on
|
|
7
|
-
# the machine lands on every target instead of flattering one of them. Each figure in the
|
|
8
|
-
# result is the median across runs, with the fastest and slowest throughput kept so a noisy
|
|
9
|
-
# measurement shows.
|
|
10
|
-
module Suite
|
|
11
|
-
# Every target `bin/bench` can measure.
|
|
12
|
-
TARGETS = %w[ruby:memory ruby:sqlite ruby:postgres ruby:postgres_era rust].freeze
|
|
13
|
-
|
|
14
|
-
# What to measure and how hard.
|
|
15
|
-
#
|
|
16
|
-
# @!attribute domains
|
|
17
|
-
# @return [Array<String>] workload names, keys of `Workload.all`
|
|
18
|
-
# @!attribute targets
|
|
19
|
-
# @return [Array<String>] members of `TARGETS`
|
|
20
|
-
# @!attribute warmup
|
|
21
|
-
# @return [Integer] cycles discarded before each run is timed
|
|
22
|
-
# @!attribute iterations
|
|
23
|
-
# @return [Integer] cycles timed in each run
|
|
24
|
-
# @!attribute runs
|
|
25
|
-
# @return [Integer] how many fresh boots of each target to measure per domain
|
|
26
|
-
# @!attribute rust_binary
|
|
27
|
-
# @return [String, nil] a pre-built binary to use instead of building one; only valid
|
|
28
|
-
# with a single domain, since a binary embeds exactly one
|
|
29
|
-
Config = Struct.new(:domains, :targets, :warmup, :iterations, :runs, :rust_binary, keyword_init: true)
|
|
30
|
-
|
|
31
|
-
module_function
|
|
32
|
-
|
|
33
|
-
# Measures everything `config` asks for.
|
|
34
|
-
#
|
|
35
|
-
# @param config [Config] what to measure
|
|
36
|
-
# @param log [#puts] where progress lines go; one line per run
|
|
37
|
-
# @return [Hash{Symbol => Object}] `:environment`, `:config`, `:results` (one entry per
|
|
38
|
-
# domain and target, see `.aggregate`) and `:skipped` (each skipped target and why)
|
|
39
|
-
# @raise [ArgumentError] if a target or domain is unknown, a count is not positive, or
|
|
40
|
-
# `rust_binary` is combined with more than one domain
|
|
41
|
-
def call(config, log: $stderr)
|
|
42
|
-
validate(config)
|
|
43
|
-
load_before = Environment.load_average
|
|
44
|
-
skipped = unavailable_targets(config)
|
|
45
|
-
active = config.targets - skipped.map { |entry| entry[:target] }
|
|
46
|
-
skipped.each { |entry| log.puts "skip #{entry[:target]}: #{entry[:reason]}" }
|
|
47
|
-
results = config.domains.flat_map { |domain| measure_domain(Workload.fetch(domain), active, config, log) }
|
|
48
|
-
environment = Environment.describe
|
|
49
|
-
environment[:load_average] = { before: load_before, after: environment[:load_average] }
|
|
50
|
-
{ environment: environment.merge(postgres: postgres_version(active)),
|
|
51
|
-
config: config.to_h, results: results, skipped: skipped }
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# Checks a configuration before anything is booted.
|
|
55
|
-
#
|
|
56
|
-
# @param config [Config] the configuration to check
|
|
57
|
-
# @return [void]
|
|
58
|
-
# @raise [ArgumentError] on the first problem found
|
|
59
|
-
def validate(config)
|
|
60
|
-
unknown = config.targets - TARGETS
|
|
61
|
-
raise ArgumentError, "unknown target #{unknown.first.inspect} — one of #{TARGETS.join(', ')}" if unknown.any?
|
|
62
|
-
|
|
63
|
-
config.domains.each { |domain| Workload.fetch(domain) }
|
|
64
|
-
if config.iterations < 1 || config.runs < 1 || config.warmup.negative?
|
|
65
|
-
raise ArgumentError, "iterations and runs must be at least 1, and warmup at least 0"
|
|
66
|
-
end
|
|
67
|
-
return unless config.rust_binary && config.domains.size > 1
|
|
68
|
-
|
|
69
|
-
raise ArgumentError, "--rust-binary embeds one domain, so it needs exactly one --domain"
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# Finds the targets this machine cannot run.
|
|
73
|
-
#
|
|
74
|
-
# @param config [Config] the configuration whose targets to check
|
|
75
|
-
# @return [Array<Hash{Symbol => String}>] `{target:, reason:}` for each unavailable target
|
|
76
|
-
def unavailable_targets(config)
|
|
77
|
-
postgres = PostgresProbe.unavailable_reason if config.targets.any? { |t| t.start_with?("ruby:postgres") }
|
|
78
|
-
rust = RustRunner.unavailable_reason(binary: config.rust_binary) if config.targets.include?("rust")
|
|
79
|
-
config.targets.filter_map do |target|
|
|
80
|
-
reason = target == "rust" ? rust : (postgres if target.start_with?("ruby:postgres"))
|
|
81
|
-
{ target: target, reason: reason } if reason
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
# Measures one domain across every active target, `config.runs` times each.
|
|
86
|
-
#
|
|
87
|
-
# @param workload [Workload] the domain's commands
|
|
88
|
-
# @param targets [Array<String>] the targets that can run here
|
|
89
|
-
# @param config [Config] warmup, iterations, runs and any binary override
|
|
90
|
-
# @param log [#puts] where progress lines go
|
|
91
|
-
# @return [Array<Hash>] one aggregated entry per target
|
|
92
|
-
def measure_domain(workload, targets, config, log)
|
|
93
|
-
binaries = {}
|
|
94
|
-
summaries = Hash.new { |hash, target| hash[target] = [] }
|
|
95
|
-
config.runs.times do |index|
|
|
96
|
-
targets.each do |target|
|
|
97
|
-
run = measure_once(workload, target, config, binaries)
|
|
98
|
-
summaries[target] << run.summary
|
|
99
|
-
log.puts progress_line("#{workload.name} #{target}", "run #{index + 1}/#{config.runs}", summaries[target].last)
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
targets.map { |target| aggregate(workload.name, target, summaries[target]) }
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# Formats the one line printed as each run finishes.
|
|
106
|
-
#
|
|
107
|
-
# @param label [String] the domain and target
|
|
108
|
-
# @param position [String] which run of how many, e.g. `"2/3"`
|
|
109
|
-
# @param summary [Hash] the run's `Run#summary`
|
|
110
|
-
# @return [String] the progress line
|
|
111
|
-
def progress_line(label, position, summary)
|
|
112
|
-
"#{label.ljust(27)} #{position} #{summary[:throughput_per_s].round.to_s.rjust(8)} cmds/s " \
|
|
113
|
-
"p50 #{summary[:p50_us].to_s.rjust(9)} us p99 #{summary[:p99_us].to_s.rjust(9)} us"
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
# Runs one target once.
|
|
117
|
-
#
|
|
118
|
-
# @param workload [Workload] the domain's commands
|
|
119
|
-
# @param target [String] a member of `TARGETS`
|
|
120
|
-
# @param config [Config] warmup, iterations and any binary override
|
|
121
|
-
# @param binaries [Hash{String => String}] Rust binaries already built, by domain name;
|
|
122
|
-
# filled in as they are built
|
|
123
|
-
# @return [Run] the run's timings
|
|
124
|
-
def measure_once(workload, target, config, binaries)
|
|
125
|
-
if target == "rust"
|
|
126
|
-
binary = config.rust_binary || (binaries[workload.name] ||= RustRunner.build(workload.name))
|
|
127
|
-
RustRunner.call(workload, binary: binary, warmup: config.warmup, iterations: config.iterations)
|
|
128
|
-
else
|
|
129
|
-
RubyRunner.call(workload, adapter: target.delete_prefix("ruby:").to_sym,
|
|
130
|
-
warmup: config.warmup, iterations: config.iterations)
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
# Reduces one target's runs to a single entry.
|
|
135
|
-
#
|
|
136
|
-
# @param domain [String] the workload name
|
|
137
|
-
# @param target [String] the target name
|
|
138
|
-
# @param summaries [Array<Hash>] each run's `Run#summary`
|
|
139
|
-
# @return [Hash{Symbol => Object}] `:domain`, `:target`, `:runs` (the raw summaries),
|
|
140
|
-
# `:median` (the median of each run's throughput, p50, p99 and drift, plus
|
|
141
|
-
# `:by_verb`), and `:throughput_range` (`[slowest, fastest]` across runs)
|
|
142
|
-
def aggregate(domain, target, summaries)
|
|
143
|
-
rates = summaries.map { |summary| summary[:throughput_per_s] }
|
|
144
|
-
median = %i[throughput_per_s p50_us p99_us mean_us max_us drift].to_h do |key|
|
|
145
|
-
[key, Stats.median(summaries.filter_map { |summary| summary[key] })]
|
|
146
|
-
end
|
|
147
|
-
median[:roundtrip_floor_p50_us] = Stats.median(summaries.filter_map { |s| s[:roundtrip_floor_p50_us] })
|
|
148
|
-
median[:by_verb] = median_by_verb(summaries)
|
|
149
|
-
{ domain: domain, target: target, runs: summaries, median: median.compact,
|
|
150
|
-
throughput_range: rates.minmax }
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
# Takes each verb's median p50 and p99 across runs.
|
|
154
|
-
#
|
|
155
|
-
# @param summaries [Array<Hash>] each run's `Run#summary`
|
|
156
|
-
# @return [Hash{String => Hash{Symbol => Float}}] `:p50_us` and `:p99_us` per verb
|
|
157
|
-
def median_by_verb(summaries)
|
|
158
|
-
summaries.first[:by_verb].keys.to_h do |verb|
|
|
159
|
-
[verb, %i[p50_us p99_us].to_h { |key| [key, Stats.median(summaries.map { |s| s[:by_verb][verb][key] })] }]
|
|
160
|
-
end
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
# Reads the Postgres server version if a Postgres target ran.
|
|
164
|
-
#
|
|
165
|
-
# @param active [Array<String>] the targets that ran
|
|
166
|
-
# @return [String, nil] the server version, or nil when no Postgres target ran
|
|
167
|
-
def postgres_version(active)
|
|
168
|
-
PostgresProbe.server_version if active.any? { |target| target.start_with?("ruby:postgres") }
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
end
|
data/lib/hecks/bench/workload.rb
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
require "json"
|
|
2
|
-
|
|
3
|
-
module Hecks
|
|
4
|
-
module Bench
|
|
5
|
-
# A fixed command sequence for one example domain, identical for every runtime.
|
|
6
|
-
#
|
|
7
|
-
# `setup` runs once, untimed, before a run starts. `cycle(n)` answers the timed
|
|
8
|
-
# commands for the n-th unit of work. Every cycle names its own records by `n`, so
|
|
9
|
-
# cycles never collide and no command is ever refused: a refusal would mean the
|
|
10
|
-
# benchmark measured a rejection path rather than the work it claims.
|
|
11
|
-
#
|
|
12
|
-
# Steps are the same `{"verb", "args"}` shape `bin/run` and the corpus scripts use.
|
|
13
|
-
# Ruby dispatches them through `dispatch_flat` and the Rust binary reads them as
|
|
14
|
-
# JSON lines, so both runtimes see byte-identical input.
|
|
15
|
-
class Workload
|
|
16
|
-
# One command to dispatch.
|
|
17
|
-
#
|
|
18
|
-
# @!attribute verb
|
|
19
|
-
# @return [String] the fully qualified command, e.g. `"Pizzas::Order.CreatePizza"`
|
|
20
|
-
# @!attribute args
|
|
21
|
-
# @return [Hash{String => Object}] the command's arguments, string-keyed throughout
|
|
22
|
-
Step = Struct.new(:verb, :args) do
|
|
23
|
-
# Renders the step as one line of `rust --serve` input.
|
|
24
|
-
#
|
|
25
|
-
# @return [String] the step as compact JSON with no trailing newline
|
|
26
|
-
def to_json_line
|
|
27
|
-
JSON.generate("verb" => verb, "args" => args)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# Gives the arguments as `dispatch_flat` takes them.
|
|
31
|
-
#
|
|
32
|
-
# @return [Hash{Symbol => Object}] `args` with only its top-level keys symbolized,
|
|
33
|
-
# the way the corpus replay hands them over
|
|
34
|
-
def ruby_args
|
|
35
|
-
args.transform_keys(&:to_sym)
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# @return [String] the example domain's directory name, also its Cargo feature
|
|
40
|
-
attr_reader :name
|
|
41
|
-
|
|
42
|
-
# @return [String] the absolute path of the example domain directory
|
|
43
|
-
attr_reader :domain_path
|
|
44
|
-
|
|
45
|
-
# @return [Array<Step>] the commands run once, untimed, before any cycle
|
|
46
|
-
attr_reader :setup
|
|
47
|
-
|
|
48
|
-
# Builds a workload.
|
|
49
|
-
#
|
|
50
|
-
# @param name [String] the example domain's directory name, e.g. `"pizzas"`
|
|
51
|
-
# @param setup [Array<Step>] the untimed preparation commands
|
|
52
|
-
# @param cycle [#call] takes a cycle number and returns that cycle's `Array<Step>`
|
|
53
|
-
def initialize(name:, setup:, cycle:)
|
|
54
|
-
@name = name
|
|
55
|
-
@domain_path = File.expand_path("../../../examples/#{name}", __dir__)
|
|
56
|
-
@setup = setup
|
|
57
|
-
@cycle = cycle
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# Gives the timed commands for one unit of work.
|
|
61
|
-
#
|
|
62
|
-
# @param number [Integer] the zero-based cycle number, unique across warmup and
|
|
63
|
-
# measurement so record names never repeat
|
|
64
|
-
# @return [Array<Step>] the commands in dispatch order
|
|
65
|
-
def cycle(number) = @cycle.call(number)
|
|
66
|
-
|
|
67
|
-
# Gives how many commands each cycle dispatches.
|
|
68
|
-
#
|
|
69
|
-
# @return [Integer] the length of every cycle
|
|
70
|
-
def commands_per_cycle = cycle(0).size
|
|
71
|
-
|
|
72
|
-
# Lists the workloads `bin/bench` knows.
|
|
73
|
-
#
|
|
74
|
-
# @return [Hash{String => Workload}] each workload keyed by its domain name
|
|
75
|
-
def self.all
|
|
76
|
-
{ "pizzas" => pizzas, "banking" => banking }
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Looks up one workload.
|
|
80
|
-
#
|
|
81
|
-
# @param name [String] a key of `.all`
|
|
82
|
-
# @return [Workload] the workload for `name`
|
|
83
|
-
# @raise [ArgumentError] if there is no workload with that name
|
|
84
|
-
def self.fetch(name)
|
|
85
|
-
all.fetch(name) do
|
|
86
|
-
raise ArgumentError, "unknown domain #{name.inspect} — one of #{all.keys.join(', ')}"
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# A pizza is created, topped twice and bought: four commands and two aggregate updates.
|
|
91
|
-
#
|
|
92
|
-
# @return [Workload] the pizzas workload
|
|
93
|
-
def self.pizzas
|
|
94
|
-
new(name: "pizzas", setup: [], cycle: lambda { |n|
|
|
95
|
-
pizza = "pizza-#{n}"
|
|
96
|
-
[
|
|
97
|
-
Step.new("Pizzas::Order.CreatePizza",
|
|
98
|
-
{ "name" => { "value" => pizza },
|
|
99
|
-
"pizza" => { "price_cents" => { "cents" => 1200 }, "size" => { "value" => "large" } } }),
|
|
100
|
-
Step.new("Pizzas::Order.AddTopping",
|
|
101
|
-
{ "name" => pizza, "topping" => { "value" => "Basil" }, "amount" => { "value" => 3 } }),
|
|
102
|
-
Step.new("Pizzas::Order.AddTopping",
|
|
103
|
-
{ "name" => pizza, "topping" => { "value" => "Olive" }, "amount" => { "value" => 2 } }),
|
|
104
|
-
Step.new("Pizzas::Order.Purchase",
|
|
105
|
-
{ "name" => pizza, "customer_name" => { "value" => "Chris" },
|
|
106
|
-
"amount" => { "cents" => 1200 } })
|
|
107
|
-
]
|
|
108
|
-
})
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
# An account is opened, credited twice and debited once, all for one registered customer.
|
|
112
|
-
#
|
|
113
|
-
# @return [Workload] the banking workload
|
|
114
|
-
def self.banking
|
|
115
|
-
register = Step.new("Banking::Customer.Register",
|
|
116
|
-
{ "reference" => { "value" => "CUST-0001" },
|
|
117
|
-
"name" => { "given" => "Ada", "family" => "Lovelace" },
|
|
118
|
-
"email" => { "address" => "ada@example.com" } })
|
|
119
|
-
new(name: "banking", setup: [register], cycle: lambda { |n|
|
|
120
|
-
number = { "value" => "acct-#{n}" }
|
|
121
|
-
money = ->(cents) { { "cents" => cents, "currency" => "USD" } }
|
|
122
|
-
[
|
|
123
|
-
Step.new("Banking::Account.Open",
|
|
124
|
-
{ "number" => number, "kind" => { "name" => "current" },
|
|
125
|
-
"daily_limit" => { "cents" => 50_000 }, "customer" => "CUST-0001" }),
|
|
126
|
-
Step.new("Banking::Account.Credit",
|
|
127
|
-
{ "number" => number, "amount" => money.call(10_000), "narrative" => { "text" => "Deposit" } }),
|
|
128
|
-
Step.new("Banking::Account.Credit",
|
|
129
|
-
{ "number" => number, "amount" => money.call(5000), "narrative" => { "text" => "Deposit" } }),
|
|
130
|
-
Step.new("Banking::Account.Debit",
|
|
131
|
-
{ "number" => number, "amount" => money.call(2500), "narrative" => { "text" => "Groceries" } })
|
|
132
|
-
]
|
|
133
|
-
})
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
end
|
data/lib/hecks/bench.rb
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
module Hecks
|
|
2
|
-
# The throughput and latency harness `bin/bench` drives.
|
|
3
|
-
#
|
|
4
|
-
# It dispatches a fixed, valid command workload against the Ruby runtime on each
|
|
5
|
-
# persistence adapter and against the native Rust binary, and reports
|
|
6
|
-
# commands per second plus p50 and p99 latency for each. Not required by
|
|
7
|
-
# `lib/hecks.rb` on purpose: a booted domain never needs it.
|
|
8
|
-
#
|
|
9
|
-
# ## What is measured
|
|
10
|
-
#
|
|
11
|
-
# Every runtime is driven the same way: one caller, one command at a time, each
|
|
12
|
-
# waiting for the previous answer. The Ruby side times `dispatch_flat` in-process.
|
|
13
|
-
# The Rust side times a full round trip over the pipes of `rust --serve`. See
|
|
14
|
-
# `docs/benchmarks.md` for what that does and does not compare.
|
|
15
|
-
module Bench
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
require_relative "bench/stats"
|
|
20
|
-
require_relative "bench/workload"
|
|
21
|
-
require_relative "bench/environment"
|
|
22
|
-
require_relative "bench/postgres_probe"
|
|
23
|
-
require_relative "bench/run"
|
|
24
|
-
require_relative "bench/ruby_runner"
|
|
25
|
-
require_relative "bench/rust_runner"
|
|
26
|
-
require_relative "bench/suite"
|
|
27
|
-
require_relative "bench/report"
|
|
28
|
-
require_relative "bench/cli"
|