duel 0.1.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 +7 -0
- data/AGENT_PROMPT.md +211 -0
- data/CHANGELOG.md +281 -0
- data/LICENSE +21 -0
- data/README.md +354 -0
- data/docs/README.md +23 -0
- data/docs/benchmarks.md +289 -0
- data/docs/ci.md +141 -0
- data/docs/configuration.md +429 -0
- data/docs/how-it-works.md +207 -0
- data/docs/json.md +306 -0
- data/docs/plugins.md +408 -0
- data/docs/troubleshooting.md +105 -0
- data/exe/duel +6 -0
- data/lib/duel/across.rb +48 -0
- data/lib/duel/baseline.rb +166 -0
- data/lib/duel/cli.rb +264 -0
- data/lib/duel/config.rb +343 -0
- data/lib/duel/context.rb +630 -0
- data/lib/duel/init.rb +211 -0
- data/lib/duel/json_report.rb +107 -0
- data/lib/duel/lock.rb +38 -0
- data/lib/duel/metric.rb +496 -0
- data/lib/duel/native.rb +162 -0
- data/lib/duel/process_sample.rb +144 -0
- data/lib/duel/profile.rb +116 -0
- data/lib/duel/rake_task.rb +25 -0
- data/lib/duel/resource.rb +209 -0
- data/lib/duel/rubies.rb +149 -0
- data/lib/duel/runner.rb +1114 -0
- data/lib/duel/stats.rb +73 -0
- data/lib/duel/summary.rb +237 -0
- data/lib/duel/templates/benchmark_duel.rb.erb +34 -0
- data/lib/duel/templates/duel.rb.erb +35 -0
- data/lib/duel/templates/perf.yml.erb +97 -0
- data/lib/duel/testing.rb +241 -0
- data/lib/duel/version.rb +5 -0
- data/lib/duel/worker.rb +211 -0
- data/lib/duel.rb +413 -0
- data/sig/duel/across.rbs +21 -0
- data/sig/duel/baseline.rbs +61 -0
- data/sig/duel/cli.rbs +95 -0
- data/sig/duel/config.rbs +181 -0
- data/sig/duel/context.rbs +387 -0
- data/sig/duel/init.rbs +97 -0
- data/sig/duel/json_report.rbs +47 -0
- data/sig/duel/lock.rbs +12 -0
- data/sig/duel/metric.rbs +351 -0
- data/sig/duel/native.rbs +98 -0
- data/sig/duel/process_sample.rbs +54 -0
- data/sig/duel/profile.rbs +64 -0
- data/sig/duel/rake_task.rbs +9 -0
- data/sig/duel/resource.rbs +106 -0
- data/sig/duel/rubies.rbs +45 -0
- data/sig/duel/runner.rbs +530 -0
- data/sig/duel/stats.rbs +26 -0
- data/sig/duel/summary.rbs +87 -0
- data/sig/duel/testing.rbs +91 -0
- data/sig/duel/version.rbs +3 -0
- data/sig/duel/worker.rbs +74 -0
- data/sig/duel.rbs +276 -0
- data/sig/manifest.yaml +4 -0
- metadata +124 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
module Duel
|
|
2
|
+
# What a plugin's tests need: its prepare blocks run as a worker's would
|
|
3
|
+
# be, its metrics measuring a block, and whole comparisons of two small
|
|
4
|
+
# projects.
|
|
5
|
+
module Testing
|
|
6
|
+
extend Testing
|
|
7
|
+
|
|
8
|
+
# What Testing.compare found.
|
|
9
|
+
class Comparison < Data
|
|
10
|
+
# A Result for each benchmark.
|
|
11
|
+
attr_reader results: Array[Result]
|
|
12
|
+
|
|
13
|
+
# What the run printed.
|
|
14
|
+
attr_reader output: String
|
|
15
|
+
|
|
16
|
+
# As `duel` would have it: no benchmark worse, nothing across the board
|
|
17
|
+
# (see #passed?).
|
|
18
|
+
# @api private
|
|
19
|
+
attr_reader passed: bool
|
|
20
|
+
|
|
21
|
+
# @api private
|
|
22
|
+
def self.new: (Array[Result] results, String output, bool passed) -> Comparison
|
|
23
|
+
|
|
24
|
+
# Whether it passed, as `duel` would have it.
|
|
25
|
+
def passed?: () -> bool
|
|
26
|
+
|
|
27
|
+
# The benchmark's Result.
|
|
28
|
+
def []: (String name) -> Result?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# What #measure measured, by name, and what metrics said of it.
|
|
32
|
+
class Measurement < Hash[String, Metric::value]
|
|
33
|
+
# What metrics said of it, by measure.
|
|
34
|
+
attr_reader details: Hash[String, String]
|
|
35
|
+
|
|
36
|
+
# @api private
|
|
37
|
+
attr_writer details: Hash[String, String]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Where prepare blocks' builds are kept, for every test of this process.
|
|
41
|
+
# @api private
|
|
42
|
+
CACHE: String
|
|
43
|
+
|
|
44
|
+
# Each config's metrics.
|
|
45
|
+
# @api private
|
|
46
|
+
MADE: Hash[Config, [Hash[String, Hash[Symbol, untyped]], Array[Metric]]]
|
|
47
|
+
|
|
48
|
+
# Runs +config+'s prepare blocks as they'd prepare a worker, sets the
|
|
49
|
+
# environment they give while the block runs, yields it, and stops
|
|
50
|
+
# what they started after.
|
|
51
|
+
def prepare: [T] (Config config, ?name: String, ?root: String, ?cache: String) { (Hash[String, String?] env) -> T } -> T
|
|
52
|
+
|
|
53
|
+
# What +config+'s metrics measure of the block, as one counted sample
|
|
54
|
+
# of +n+ iterations of a benchmark would, with its hooks (each
|
|
55
|
+
# iteration inside around_iteration hooks, if it has them). The
|
|
56
|
+
# block gets the benchmark's Context.
|
|
57
|
+
def measure: (Config config, ?n: Integer, ?only: (:count | :timing)?, **untyped hooks) { (Context context) -> untyped } -> Measurement
|
|
58
|
+
|
|
59
|
+
# Runs the block with +env+ set, and the environment as it was after.
|
|
60
|
+
# @api private
|
|
61
|
+
def with_env: [T] (Hash[String, String?] env) { () -> T } -> T
|
|
62
|
+
|
|
63
|
+
# Each config's worker's Stops.
|
|
64
|
+
# @api private
|
|
65
|
+
STOPS: Hash[Config, Stops]
|
|
66
|
+
|
|
67
|
+
# Runs the block as a worker of +config+ would, then ends it.
|
|
68
|
+
def in_worker: [T] (Config config) { () -> T } -> T
|
|
69
|
+
|
|
70
|
+
# +config+'s metrics, made once (its in_worker blocks run first).
|
|
71
|
+
def metrics: (Config config) -> Array[Metric]
|
|
72
|
+
|
|
73
|
+
# Compares the project at +base+ with the one at +candidate+, in
|
|
74
|
+
# worker processes, as `duel` does, with +settings+ (as `duel`'s
|
|
75
|
+
# options name them: processes:, timeout:, filter:, limit:...).
|
|
76
|
+
def compare: (String base, String candidate, **untyped settings) -> Comparison
|
|
77
|
+
|
|
78
|
+
# A stand-in Sample of +n+ iterations of a benchmark, measuring
|
|
79
|
+
# nothing.
|
|
80
|
+
def sample: (?n: Integer, ?name: String, ?only: only?, ?peak_memory: bool) -> Sample
|
|
81
|
+
|
|
82
|
+
# A stand-in reading of running totals, and what was read with them.
|
|
83
|
+
def reading: [T, C] (?Snapshot::totals[T] totals, ?C? context) -> Snapshot::Reading[T, C?]
|
|
84
|
+
|
|
85
|
+
# What running totals' details block says of +stretches+ of +sample+.
|
|
86
|
+
def details: (singleton(Snapshot) metric, Snapshot::stretches[Metric::value, untyped] stretches, ?Sample sample) -> Snapshot::explained
|
|
87
|
+
|
|
88
|
+
# Writes a project into +dir+: +files+, by path, with their contents.
|
|
89
|
+
def project: (String dir, Hash[String, String] files) -> String
|
|
90
|
+
end
|
|
91
|
+
end
|
data/sig/duel/worker.rbs
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module Duel
|
|
2
|
+
# A worker process: loads one version of the code and the candidate's
|
|
3
|
+
# benchmarks, and answers the runner's requests, one JSON object a line.
|
|
4
|
+
# @api private
|
|
5
|
+
class Worker
|
|
6
|
+
type hello = { "benchmarks" => Array[Definition::summary], "metrics" => Array[Metric::spec], "ruby" => String,
|
|
7
|
+
"memory" => bool, "profiler" => bool, "yjit" => bool, "extensions" => Hash[String, String],
|
|
8
|
+
"gem_dirs" => Array[String] }
|
|
9
|
+
|
|
10
|
+
type ok = { "ok" => true }
|
|
11
|
+
|
|
12
|
+
type sampled = { "timed" => Hash[String, Metric::value], "counted" => Hash[String, Metric::value], "heap" => Integer,
|
|
13
|
+
?"details" => Hash[String, String] }
|
|
14
|
+
|
|
15
|
+
type memory = { "peak" => Integer }
|
|
16
|
+
|
|
17
|
+
type error = { "error" => String, "backtrace" => Array[String] }
|
|
18
|
+
|
|
19
|
+
type reply = ok | sampled | memory | Profile::profile | error
|
|
20
|
+
|
|
21
|
+
BUILT_IN: Array[singleton(Metric)]
|
|
22
|
+
|
|
23
|
+
@metrics: Array[Metric]
|
|
24
|
+
@specs: Array[Metric::spec]
|
|
25
|
+
@benchmarks: Hash[String, Definition]
|
|
26
|
+
@hooks: Hash[Symbol, Array[hook]]
|
|
27
|
+
@harnesses: Hash[String, Harness]
|
|
28
|
+
|
|
29
|
+
def self.main: (String version_root, String candidate_root, String control, ?String? config_path) -> void
|
|
30
|
+
|
|
31
|
+
# The native extensions loaded, by feature, at their real paths.
|
|
32
|
+
def self.extensions: (?Array[String] features, ?Array[String] load_path) -> Hash[String, String]
|
|
33
|
+
|
|
34
|
+
# The directories gems are installed in, as real paths ending in "/".
|
|
35
|
+
def self.gem_dirs: (?Array[String] dirs) -> Array[String]
|
|
36
|
+
|
|
37
|
+
# Whether StackProf is installed, for profiles: on CRuby only.
|
|
38
|
+
def self.profiler?: (?String engine) -> bool
|
|
39
|
+
|
|
40
|
+
# Whether YJIT is on, by Ruby.
|
|
41
|
+
YJIT: ^() -> bool
|
|
42
|
+
|
|
43
|
+
# Whether YJIT is on, for a longer warm-up.
|
|
44
|
+
def self.yjit?: () -> bool
|
|
45
|
+
|
|
46
|
+
def initialize: (?Array[singleton(Metric)] plugin_metrics, ?Hash[String, Hash[Symbol, untyped]] measures,
|
|
47
|
+
?hooks: Hash[Symbol, Array[hook]]) -> void
|
|
48
|
+
|
|
49
|
+
# Closes its metrics, last first, as it ends.
|
|
50
|
+
def close: () -> void
|
|
51
|
+
|
|
52
|
+
# +metrics+ made, and their measures' specs, with +measures+' changes.
|
|
53
|
+
def self.made: (Array[singleton(Metric)] metrics, Hash[String, Hash[Symbol, untyped]] measures) -> [Array[Metric], Array[Metric::spec]]
|
|
54
|
+
|
|
55
|
+
# What the runner learns first.
|
|
56
|
+
def hello: () -> hello
|
|
57
|
+
|
|
58
|
+
# Answers a request (a parsed JSON object). Errors are answered, not
|
|
59
|
+
# raised.
|
|
60
|
+
def handle: (Hash[String, untyped] request) -> reply
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def setup: (String name, bool trial) -> ok
|
|
65
|
+
|
|
66
|
+
def sample: (String name, Integer n, bool count, Integer runs) -> sampled
|
|
67
|
+
|
|
68
|
+
def memory: (String name, Integer n) -> memory
|
|
69
|
+
|
|
70
|
+
def profile: (String name, Integer n, Integer runs) -> Profile::profile
|
|
71
|
+
|
|
72
|
+
def teardown: (String name) -> ok
|
|
73
|
+
end
|
|
74
|
+
end
|
data/sig/duel.rbs
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# Performance regression guards: every benchmark runs on two versions of
|
|
2
|
+
# a codebase side by side, the baseline and the candidate, and the
|
|
3
|
+
# candidate fails if it's slower at any of them.
|
|
4
|
+
module Duel
|
|
5
|
+
# What Duel raises when something is used wrong, or fails (see Failed).
|
|
6
|
+
class Error < StandardError
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# What a benchmark is judged on, if not both its timings and its
|
|
10
|
+
# counts (see DSL#duel).
|
|
11
|
+
type only = :count | :timing
|
|
12
|
+
|
|
13
|
+
# A benchmark's body, run in its Context: given the iterations to do
|
|
14
|
+
# when it takes a parameter, else run once an iteration.
|
|
15
|
+
# @api private
|
|
16
|
+
type body = ^() [self: Context] -> void | ^(Integer n) [self: Context] -> void
|
|
17
|
+
|
|
18
|
+
# A hook (see HOOKS): a block, or a lambda, given the benchmark (setup,
|
|
19
|
+
# teardown and before_sample, if it takes it), or the Iteration to run
|
|
20
|
+
# (around_iteration), or nothing (after_iterations). A file's, or a
|
|
21
|
+
# benchmark's own, runs in each benchmark's Context.
|
|
22
|
+
type hook = ^(?untyped) [self: Context] -> untyped
|
|
23
|
+
|
|
24
|
+
# A benchmark's own hooks, by name (see DSL#duel).
|
|
25
|
+
# @api private
|
|
26
|
+
type hooks = Hash[Symbol, hook]
|
|
27
|
+
|
|
28
|
+
# A benchmark (see DSL#duel), defined in +file+, whose hooks (its
|
|
29
|
+
# file's, and its own) it gets. Or one of a whole process (see
|
|
30
|
+
# DSL#duel_process). What a metric sees of it (Sample#benchmark): its
|
|
31
|
+
# name, only and peak_memory.
|
|
32
|
+
class Definition < Struct[String | only | bool | body | hooks | Definition::process | nil]
|
|
33
|
+
# A process benchmark's process: its command, or Ruby code.
|
|
34
|
+
# @api private
|
|
35
|
+
type process = { "command" => Array[String]?, "ruby" => String?, "runs" => Integer? }
|
|
36
|
+
|
|
37
|
+
# What the runner learns of it (see #to_h).
|
|
38
|
+
# @api private
|
|
39
|
+
type summary = { "name" => String, "only" => String?, "peak_memory" => bool, "process" => process? }
|
|
40
|
+
|
|
41
|
+
# Its name, as DSL#duel was given it.
|
|
42
|
+
attr_reader name: String
|
|
43
|
+
|
|
44
|
+
# @api private
|
|
45
|
+
attr_writer name: String
|
|
46
|
+
|
|
47
|
+
# What it's judged on (DSL#duel's only:): nil for both its timings
|
|
48
|
+
# and its counts.
|
|
49
|
+
attr_reader only: only?
|
|
50
|
+
|
|
51
|
+
# @api private
|
|
52
|
+
attr_writer only: only?
|
|
53
|
+
|
|
54
|
+
# Whether its peak memory is judged too.
|
|
55
|
+
attr_reader peak_memory: bool
|
|
56
|
+
|
|
57
|
+
# @api private
|
|
58
|
+
attr_writer peak_memory: bool
|
|
59
|
+
|
|
60
|
+
# @api private
|
|
61
|
+
attr_accessor file: String
|
|
62
|
+
|
|
63
|
+
# (None, for a process benchmark, which is never run in a worker.)
|
|
64
|
+
# @api private
|
|
65
|
+
attr_accessor body: body
|
|
66
|
+
|
|
67
|
+
# Its own hooks, by name (see DSL#duel).
|
|
68
|
+
# @api private
|
|
69
|
+
attr_accessor hooks: hooks
|
|
70
|
+
|
|
71
|
+
# Its process, for a process benchmark.
|
|
72
|
+
# @api private
|
|
73
|
+
attr_accessor process: process?
|
|
74
|
+
|
|
75
|
+
# @api private
|
|
76
|
+
def self.new: (String name, only? only, bool peak_memory, String file, body? body, ?hooks? hooks, ?process? process) -> instance
|
|
77
|
+
|
|
78
|
+
# Whose CPU time it takes: the benchmark thread's, or the process's.
|
|
79
|
+
# @api private
|
|
80
|
+
def cpu: () -> (:thread | :process)
|
|
81
|
+
|
|
82
|
+
# What the runner learns of it.
|
|
83
|
+
# @api private
|
|
84
|
+
def to_h: () -> summary
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# The iterations of a benchmark judged on its counts alone.
|
|
88
|
+
# @api private
|
|
89
|
+
COUNTS_ONLY: Integer
|
|
90
|
+
|
|
91
|
+
# What a benchmark may be judged on: nil for both.
|
|
92
|
+
# @api private
|
|
93
|
+
ONLY: Array[only?]
|
|
94
|
+
|
|
95
|
+
# The hooks, by name, in the order a benchmark's life runs them.
|
|
96
|
+
HOOKS: Array[Symbol]
|
|
97
|
+
|
|
98
|
+
# No hooks, by name.
|
|
99
|
+
# @api private
|
|
100
|
+
NO_HOOKS: Hash[Symbol, Array[hook]]
|
|
101
|
+
|
|
102
|
+
# What a worker's blocks (Config#in_worker) are given: blocks to run
|
|
103
|
+
# when the worker ends.
|
|
104
|
+
class Stops
|
|
105
|
+
@blocks: Array[^() -> void]
|
|
106
|
+
|
|
107
|
+
# @api private
|
|
108
|
+
def initialize: () -> void
|
|
109
|
+
|
|
110
|
+
# Runs the block when the worker ends.
|
|
111
|
+
def at_stop: () { () -> void } -> void
|
|
112
|
+
|
|
113
|
+
# Runs them, last first.
|
|
114
|
+
# @api private
|
|
115
|
+
def run: () -> void
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# What Config#in_worker returns: what its block returned, in the
|
|
119
|
+
# worker.
|
|
120
|
+
class InWorker[T]
|
|
121
|
+
# The value, boxed (so a nil or false one is made too), or nil outside
|
|
122
|
+
# a worker.
|
|
123
|
+
@made: [T]?
|
|
124
|
+
|
|
125
|
+
# The worker's. Outside a worker, it fails.
|
|
126
|
+
def value: () -> T
|
|
127
|
+
|
|
128
|
+
# @api private
|
|
129
|
+
def made: (T value) -> void
|
|
130
|
+
|
|
131
|
+
# @api private
|
|
132
|
+
def forget: () -> void
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
self.@config: Config?
|
|
136
|
+
self.@benchmarks: Hash[String, Definition]?
|
|
137
|
+
self.@file_hooks: Hash[String, Hash[Symbol, Array[hook]]]?
|
|
138
|
+
self.@plugins: Hash[Symbol, Module & _Plugin]?
|
|
139
|
+
|
|
140
|
+
# A plugin (see Duel.plugin): what configures Duel for it, given the
|
|
141
|
+
# configuration and the options it's used with.
|
|
142
|
+
interface _Plugin
|
|
143
|
+
def configure: (Config config, **untyped options) -> untyped
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# The configuration: the project's .duel.rb, or the conventions.
|
|
147
|
+
def self.config: () -> Config
|
|
148
|
+
|
|
149
|
+
# @api private
|
|
150
|
+
def self.config=: (Config config) -> Config
|
|
151
|
+
|
|
152
|
+
# Yields the configuration, as .duel.rb does.
|
|
153
|
+
def self.configure: [T] () { (Config config) -> T } -> T
|
|
154
|
+
|
|
155
|
+
self.@measuring: bool?
|
|
156
|
+
|
|
157
|
+
# Whether a benchmark is being measured right now: in a worker, inside a
|
|
158
|
+
# sample, but not in its untimed blocks (or in setup).
|
|
159
|
+
def self.measuring?: () -> bool
|
|
160
|
+
|
|
161
|
+
# Fails while a benchmark is being measured: for a helper, +name+d,
|
|
162
|
+
# meant for setting up.
|
|
163
|
+
def self.unmeasured!: (String name) -> void
|
|
164
|
+
|
|
165
|
+
# Runs the block with measuring? as +measuring+.
|
|
166
|
+
# @api private
|
|
167
|
+
def self.measured: [T] (bool measuring) { () -> T } -> T
|
|
168
|
+
|
|
169
|
+
# Loads a config file (.duel.rb): a fresh configuration, rooted where
|
|
170
|
+
# the file is.
|
|
171
|
+
# @api private
|
|
172
|
+
def self.load_config: (String path) -> Config
|
|
173
|
+
|
|
174
|
+
# Where +method+ is defined ("file:line"), or whose it is.
|
|
175
|
+
# @api private
|
|
176
|
+
def self.where: (Method | UnboundMethod method) -> String
|
|
177
|
+
|
|
178
|
+
# Every benchmark defined, by name.
|
|
179
|
+
# @api private
|
|
180
|
+
def self.benchmarks: () -> Hash[String, Definition]
|
|
181
|
+
|
|
182
|
+
# Each file's hooks, by its path, then by name.
|
|
183
|
+
# @api private
|
|
184
|
+
def self.file_hooks: () -> Hash[String, Hash[Symbol, Array[hook]]]
|
|
185
|
+
|
|
186
|
+
# Adds +block+ as a hook of the file of benchmarks running, or of
|
|
187
|
+
# +file+, the one that gave it.
|
|
188
|
+
# @api private
|
|
189
|
+
def self.file_hook: (Symbol name, Proc? block, String file) -> void
|
|
190
|
+
|
|
191
|
+
# +block+, a hook +name+d, or a failure if it isn't one.
|
|
192
|
+
# @api private
|
|
193
|
+
def self.hook: (Symbol name, untyped block) -> untyped
|
|
194
|
+
|
|
195
|
+
# Registers a plugin, +name+d as its file is required (see
|
|
196
|
+
# Config#use): a module whose configure Config#use calls, and whose
|
|
197
|
+
# DSL module, if it has one, has its DSL methods. Returns its name.
|
|
198
|
+
def self.plugin: (Symbol name, Module & _Plugin plugin) -> Symbol
|
|
199
|
+
|
|
200
|
+
# Every plugin registered, by name.
|
|
201
|
+
# @api private
|
|
202
|
+
def self.plugins: () -> Hash[Symbol, Module & _Plugin]
|
|
203
|
+
|
|
204
|
+
self.@defining_file: String?
|
|
205
|
+
|
|
206
|
+
# The file of benchmarks that called the DSL method running, if one is.
|
|
207
|
+
# @api private
|
|
208
|
+
def self.defining_file: () -> String?
|
|
209
|
+
|
|
210
|
+
# Runs the block with +file+ as the defining file, unless one is already.
|
|
211
|
+
# @api private
|
|
212
|
+
def self.defining: [T] (String? file) { () -> T } -> T
|
|
213
|
+
|
|
214
|
+
# What files of benchmarks call (the worker gives it to its main
|
|
215
|
+
# object).
|
|
216
|
+
module DSL
|
|
217
|
+
# Adds the public methods of a plugin's DSL module to the DSL (a name
|
|
218
|
+
# it has, or every object has, fails), as .duel.rb uses the plugin.
|
|
219
|
+
# @api private
|
|
220
|
+
def self.add: (Module methods) -> Array[Symbol]
|
|
221
|
+
|
|
222
|
+
# Adds one of a plugin's DSL module's methods to the DSL, unless it's
|
|
223
|
+
# there already.
|
|
224
|
+
# @api private
|
|
225
|
+
def self.define: (Symbol name, Module methods) -> void
|
|
226
|
+
|
|
227
|
+
# The plugin's DSL module each method DSL.add added is from.
|
|
228
|
+
# @api private
|
|
229
|
+
DEFINED: Hash[Symbol, Module]
|
|
230
|
+
|
|
231
|
+
# Defines a benchmark: its block is timed, run as many times as a
|
|
232
|
+
# sample needs or, with a parameter, once a sample, given how many
|
|
233
|
+
# iterations to do. Its own hooks (see HOOKS) run after its file's:
|
|
234
|
+
# an around_iteration one runs around each iteration, given it to
|
|
235
|
+
# run, and only the iteration is measured.
|
|
236
|
+
def duel: (String name, ?only: only?, ?peak_memory: bool, **hook hooks) { (?Integer n) [self: Context] -> void } -> void
|
|
237
|
+
|
|
238
|
+
# A benchmark's own hooks, checked.
|
|
239
|
+
# @api private
|
|
240
|
+
def self.hooks: (String name, Hash[Symbol, untyped] hooks, ^(?) [self: Context] -> untyped body) -> hooks
|
|
241
|
+
|
|
242
|
+
# Defines a benchmark of a whole process: a command (as Process.spawn
|
|
243
|
+
# takes it), or Ruby code, run fresh for each version in every sample
|
|
244
|
+
# (the fastest of +runs+).
|
|
245
|
+
def duel_process: (String name, *String command, ?ruby: String?, ?runs: Integer?, **untyped options) -> void
|
|
246
|
+
|
|
247
|
+
# En garde.
|
|
248
|
+
# @api private
|
|
249
|
+
alias `⚔️` duel
|
|
250
|
+
|
|
251
|
+
# @api private
|
|
252
|
+
alias ⚔ duel
|
|
253
|
+
|
|
254
|
+
# The file's hooks (see HOOKS), for every benchmark in it, run in its
|
|
255
|
+
# context: setup once in each worker before a benchmark's samples,
|
|
256
|
+
# teardown after them, and before_sample before every sample, not
|
|
257
|
+
# measured, each given the benchmark if it takes it.
|
|
258
|
+
def setup: () { (?Definition benchmark) [self: Context] -> void } -> void
|
|
259
|
+
|
|
260
|
+
def teardown: () { (?Definition benchmark) [self: Context] -> void } -> void
|
|
261
|
+
|
|
262
|
+
def before_sample: () { (?Definition benchmark) [self: Context] -> void } -> void
|
|
263
|
+
|
|
264
|
+
# Runs the block around each iteration of every benchmark in the
|
|
265
|
+
# file, given the iteration to run: only the iteration is measured.
|
|
266
|
+
def around_iteration: () { (Iteration iteration) [self: Context] -> void } -> void
|
|
267
|
+
|
|
268
|
+
# Runs the block after each sample's iterations of every benchmark in
|
|
269
|
+
# the file, measured with them.
|
|
270
|
+
def after_iterations: () { () [self: Context] -> void } -> void
|
|
271
|
+
|
|
272
|
+
# The file that called the DSL method calling this.
|
|
273
|
+
# @api private
|
|
274
|
+
def self.caller_file: () -> String
|
|
275
|
+
end
|
|
276
|
+
end
|
data/sig/manifest.yaml
ADDED
metadata
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: duel
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Erik Berlin
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: fiddle
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.1'
|
|
26
|
+
description: Duel runs your benchmarks on two versions of your code side by side,
|
|
27
|
+
the baseline (where your branch left main, say) and the candidate, and fails if
|
|
28
|
+
the candidate is slower at any of them. Timings are judged by a distribution-free
|
|
29
|
+
confidence interval for paired CPU-time ratios and confirmed in fresh processes,
|
|
30
|
+
so it fails on real regressions, not noise.
|
|
31
|
+
email:
|
|
32
|
+
- sferik@gmail.com
|
|
33
|
+
executables:
|
|
34
|
+
- duel
|
|
35
|
+
extensions: []
|
|
36
|
+
extra_rdoc_files: []
|
|
37
|
+
files:
|
|
38
|
+
- AGENT_PROMPT.md
|
|
39
|
+
- CHANGELOG.md
|
|
40
|
+
- LICENSE
|
|
41
|
+
- README.md
|
|
42
|
+
- docs/README.md
|
|
43
|
+
- docs/benchmarks.md
|
|
44
|
+
- docs/ci.md
|
|
45
|
+
- docs/configuration.md
|
|
46
|
+
- docs/how-it-works.md
|
|
47
|
+
- docs/json.md
|
|
48
|
+
- docs/plugins.md
|
|
49
|
+
- docs/troubleshooting.md
|
|
50
|
+
- exe/duel
|
|
51
|
+
- lib/duel.rb
|
|
52
|
+
- lib/duel/across.rb
|
|
53
|
+
- lib/duel/baseline.rb
|
|
54
|
+
- lib/duel/cli.rb
|
|
55
|
+
- lib/duel/config.rb
|
|
56
|
+
- lib/duel/context.rb
|
|
57
|
+
- lib/duel/init.rb
|
|
58
|
+
- lib/duel/json_report.rb
|
|
59
|
+
- lib/duel/lock.rb
|
|
60
|
+
- lib/duel/metric.rb
|
|
61
|
+
- lib/duel/native.rb
|
|
62
|
+
- lib/duel/process_sample.rb
|
|
63
|
+
- lib/duel/profile.rb
|
|
64
|
+
- lib/duel/rake_task.rb
|
|
65
|
+
- lib/duel/resource.rb
|
|
66
|
+
- lib/duel/rubies.rb
|
|
67
|
+
- lib/duel/runner.rb
|
|
68
|
+
- lib/duel/stats.rb
|
|
69
|
+
- lib/duel/summary.rb
|
|
70
|
+
- lib/duel/templates/benchmark_duel.rb.erb
|
|
71
|
+
- lib/duel/templates/duel.rb.erb
|
|
72
|
+
- lib/duel/templates/perf.yml.erb
|
|
73
|
+
- lib/duel/testing.rb
|
|
74
|
+
- lib/duel/version.rb
|
|
75
|
+
- lib/duel/worker.rb
|
|
76
|
+
- sig/duel.rbs
|
|
77
|
+
- sig/duel/across.rbs
|
|
78
|
+
- sig/duel/baseline.rbs
|
|
79
|
+
- sig/duel/cli.rbs
|
|
80
|
+
- sig/duel/config.rbs
|
|
81
|
+
- sig/duel/context.rbs
|
|
82
|
+
- sig/duel/init.rbs
|
|
83
|
+
- sig/duel/json_report.rbs
|
|
84
|
+
- sig/duel/lock.rbs
|
|
85
|
+
- sig/duel/metric.rbs
|
|
86
|
+
- sig/duel/native.rbs
|
|
87
|
+
- sig/duel/process_sample.rbs
|
|
88
|
+
- sig/duel/profile.rbs
|
|
89
|
+
- sig/duel/rake_task.rbs
|
|
90
|
+
- sig/duel/resource.rbs
|
|
91
|
+
- sig/duel/rubies.rbs
|
|
92
|
+
- sig/duel/runner.rbs
|
|
93
|
+
- sig/duel/stats.rbs
|
|
94
|
+
- sig/duel/summary.rbs
|
|
95
|
+
- sig/duel/testing.rbs
|
|
96
|
+
- sig/duel/version.rbs
|
|
97
|
+
- sig/duel/worker.rbs
|
|
98
|
+
- sig/manifest.yaml
|
|
99
|
+
homepage: https://github.com/sferik/duel-ruby
|
|
100
|
+
licenses:
|
|
101
|
+
- MIT
|
|
102
|
+
metadata:
|
|
103
|
+
source_code_uri: https://github.com/sferik/duel-ruby
|
|
104
|
+
changelog_uri: https://github.com/sferik/duel-ruby/blob/main/CHANGELOG.md
|
|
105
|
+
bug_tracker_uri: https://github.com/sferik/duel-ruby/issues
|
|
106
|
+
rubygems_mfa_required: 'true'
|
|
107
|
+
rdoc_options: []
|
|
108
|
+
require_paths:
|
|
109
|
+
- lib
|
|
110
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
111
|
+
requirements:
|
|
112
|
+
- - ">="
|
|
113
|
+
- !ruby/object:Gem::Version
|
|
114
|
+
version: '3.4'
|
|
115
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
116
|
+
requirements:
|
|
117
|
+
- - ">="
|
|
118
|
+
- !ruby/object:Gem::Version
|
|
119
|
+
version: '0'
|
|
120
|
+
requirements: []
|
|
121
|
+
rubygems_version: 4.0.23
|
|
122
|
+
specification_version: 4
|
|
123
|
+
summary: 'Performance regression guards: fail when a change makes your code slower'
|
|
124
|
+
test_files: []
|