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
data/sig/duel/config.rbs
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
module Duel
|
|
2
|
+
# What Duel knows about a project (.duel.rb changes what doesn't follow
|
|
3
|
+
# from the conventions).
|
|
4
|
+
class Config
|
|
5
|
+
# A prepare block (see #prepare): given a Resource for a version or a
|
|
6
|
+
# worker, it returns the environment variables it gets.
|
|
7
|
+
type prepare = ^(Resource resource) -> Hash[String, String?]
|
|
8
|
+
|
|
9
|
+
# The Ruby each version runs on (see #ruby): nil for the runner's, its
|
|
10
|
+
# version file's, a path, or what a block makes of the version's tree.
|
|
11
|
+
type ruby = :version_file | String | ^(String root) -> String | nil
|
|
12
|
+
|
|
13
|
+
@root: String
|
|
14
|
+
@benchmarks: String
|
|
15
|
+
@watch: Array[String]
|
|
16
|
+
@library: String | :conventional | nil
|
|
17
|
+
@build: String | :conventional | nil
|
|
18
|
+
@main_branch: String?
|
|
19
|
+
@release_tags: String
|
|
20
|
+
@prepares: Hash[Symbol, Array[prepare]]
|
|
21
|
+
@ruby: ruby
|
|
22
|
+
@metrics: Array[singleton(Metric)]
|
|
23
|
+
@in_workers: Array[^(Stops stops) -> void]
|
|
24
|
+
@hooks: Hash[Symbol, Array[hook]]
|
|
25
|
+
@used: Hash[Symbol, Used]
|
|
26
|
+
@measures: Hash[String, Hash[Symbol, untyped]]
|
|
27
|
+
@loader: (^(String version_root) -> void)?
|
|
28
|
+
|
|
29
|
+
# Where the project is (and its config file).
|
|
30
|
+
attr_reader root: String
|
|
31
|
+
|
|
32
|
+
# A glob, from the root, of the files defining benchmarks.
|
|
33
|
+
attr_accessor benchmarks: String
|
|
34
|
+
|
|
35
|
+
# Paths, from the root, whose changes a comparison is about.
|
|
36
|
+
attr_accessor watch: Array[String]
|
|
37
|
+
|
|
38
|
+
# The library to require from each version, or nil for none.
|
|
39
|
+
attr_writer library (): String?
|
|
40
|
+
|
|
41
|
+
# A shell command that builds a checked-out tree, or nil for none.
|
|
42
|
+
attr_writer build (): String?
|
|
43
|
+
|
|
44
|
+
# The branch a comparison defaults to (nil: the one origin/HEAD names).
|
|
45
|
+
attr_accessor main_branch: String?
|
|
46
|
+
|
|
47
|
+
# The glob release tags match (for `--base last-release`).
|
|
48
|
+
attr_accessor release_tags: String
|
|
49
|
+
|
|
50
|
+
# The Ruby each version runs on: nil for the runner's own,
|
|
51
|
+
# :version_file for the one its version file names, a path, or a block
|
|
52
|
+
# given the version's tree.
|
|
53
|
+
attr_accessor ruby: ruby
|
|
54
|
+
|
|
55
|
+
# What #prepare was given, by scope.
|
|
56
|
+
# @api private
|
|
57
|
+
attr_reader prepares: Hash[Symbol, Array[prepare]]
|
|
58
|
+
|
|
59
|
+
# The metrics #metric added, in order.
|
|
60
|
+
attr_reader metrics: Array[singleton(Metric)]
|
|
61
|
+
|
|
62
|
+
# What #in_worker was given.
|
|
63
|
+
# @api private
|
|
64
|
+
attr_reader in_workers: Array[^(Stops stops) -> void]
|
|
65
|
+
|
|
66
|
+
# What the hooks were given, by name.
|
|
67
|
+
# @api private
|
|
68
|
+
attr_reader hooks: Hash[Symbol, Array[hook]]
|
|
69
|
+
|
|
70
|
+
# Each measure's settings changed, by name.
|
|
71
|
+
# @api private
|
|
72
|
+
attr_reader measures: Hash[String, Hash[Symbol, untyped]]
|
|
73
|
+
|
|
74
|
+
# A configuration of the conventions, for the project at +root+.
|
|
75
|
+
def initialize: (?String root) -> void
|
|
76
|
+
|
|
77
|
+
# What #prepare prepares: each version, or each worker.
|
|
78
|
+
# @api private
|
|
79
|
+
SCOPES: Array[Symbol]
|
|
80
|
+
|
|
81
|
+
# The library: the gem the project's only .gemspec names, unless set.
|
|
82
|
+
%a{pure} def library: () -> String?
|
|
83
|
+
|
|
84
|
+
# The build command: `bundle exec rake compile` for a project with
|
|
85
|
+
# ext/ and a Rakefile, unless set.
|
|
86
|
+
%a{pure} def build: () -> String?
|
|
87
|
+
|
|
88
|
+
# How a worker loads one version of the code, from the tree at the
|
|
89
|
+
# root it's given, instead of the convention.
|
|
90
|
+
def load: () { (String version_root) -> void } -> void
|
|
91
|
+
|
|
92
|
+
# Loads the version at +version_root+.
|
|
93
|
+
# @api private
|
|
94
|
+
def load_into: (String version_root) -> void
|
|
95
|
+
|
|
96
|
+
# Uses a plugin, by its name: requires duel/+name+, and configures Duel
|
|
97
|
+
# for it with +options+, once. Returns what that returned.
|
|
98
|
+
def use: (Symbol name, **untyped options) -> untyped
|
|
99
|
+
|
|
100
|
+
# The plugins used, by name, in the order they were first used.
|
|
101
|
+
def plugins: () -> Array[Symbol]
|
|
102
|
+
|
|
103
|
+
# A plugin as #use installed it.
|
|
104
|
+
# @api private
|
|
105
|
+
class Used
|
|
106
|
+
@options: Hash[Symbol, untyped]
|
|
107
|
+
@value: untyped
|
|
108
|
+
|
|
109
|
+
attr_reader options: Hash[Symbol, untyped]
|
|
110
|
+
|
|
111
|
+
attr_accessor value: untyped
|
|
112
|
+
|
|
113
|
+
def initialize: (Hash[Symbol, untyped] options) -> void
|
|
114
|
+
|
|
115
|
+
# What using it again with +given+ options returns, or fails.
|
|
116
|
+
def again: (Symbol name, Hash[Symbol, untyped] given) -> untyped
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Prepares each version (once for a run, in the runner, before any of
|
|
120
|
+
# its workers start), or each worker (as it starts). Returns the
|
|
121
|
+
# environment the version's workers, or the worker, get.
|
|
122
|
+
def prepare: (Symbol scope) { (Resource resource) -> Hash[String, String?] } -> void
|
|
123
|
+
|
|
124
|
+
# Adds a metric to every benchmark: a Duel::Metric subclass, or
|
|
125
|
+
# running totals the block reads (see Snapshot), one by name (with its
|
|
126
|
+
# settings) or several by their settings. Returns the metric: running
|
|
127
|
+
# totals' takes what explains them (.details), typed by what the block
|
|
128
|
+
# read with them, C.
|
|
129
|
+
def metric: (singleton(Metric) klass) -> singleton(Metric)
|
|
130
|
+
# (Totals alone, each a T: no context.)
|
|
131
|
+
| [T < Metric::value] (String name, **untyped settings) { () -> Snapshot::read_totals[T] } -> Snapshot::_Explained[T, nil]
|
|
132
|
+
| [T < Metric::value] (Hash[String, Metric::settings] measures) { () -> Snapshot::read_totals[T] } -> Snapshot::_Explained[T, nil]
|
|
133
|
+
| [T < Metric::value] (**Metric::settings measures) { () -> Snapshot::read_totals[T] } -> Snapshot::_Explained[T, nil]
|
|
134
|
+
# (Totals and their context, C.)
|
|
135
|
+
| [T < Metric::value, C] (String name, **untyped settings) { () -> [Snapshot::read_totals[T], C] } -> Snapshot::_Explained[T, C]
|
|
136
|
+
| [T < Metric::value, C] (Hash[String, Metric::settings] measures) { () -> [Snapshot::read_totals[T], C] } -> Snapshot::_Explained[T, C]
|
|
137
|
+
| [T < Metric::value, C] (**Metric::settings measures) { () -> [Snapshot::read_totals[T], C] } -> Snapshot::_Explained[T, C]
|
|
138
|
+
|
|
139
|
+
# Runs the block in each worker, once its version and the benchmarks
|
|
140
|
+
# have loaded and before its metrics are made. Returns what it
|
|
141
|
+
# returns, in the worker.
|
|
142
|
+
def in_worker: [T] () { (Stops worker) -> T } -> InWorker[T]
|
|
143
|
+
|
|
144
|
+
# The configuration's hooks (see Duel::HOOKS), for every benchmark,
|
|
145
|
+
# run where they were written, before its file's and its own.
|
|
146
|
+
def setup: () { (?Definition benchmark) -> void } -> void
|
|
147
|
+
|
|
148
|
+
def teardown: () { (?Definition benchmark) -> void } -> void
|
|
149
|
+
|
|
150
|
+
def before_sample: () { (?Definition benchmark) -> void } -> void
|
|
151
|
+
|
|
152
|
+
def around_iteration: () { (Iteration iteration) -> void } -> void
|
|
153
|
+
|
|
154
|
+
def after_iterations: () { () -> void } -> void
|
|
155
|
+
|
|
156
|
+
# Changes a measure's settings, by its name (skip: true leaves it out).
|
|
157
|
+
def measure: (String name, **untyped settings) -> void
|
|
158
|
+
|
|
159
|
+
# Whether a measure is skipped (see #measure).
|
|
160
|
+
def skip?: (String name) -> bool
|
|
161
|
+
|
|
162
|
+
# The files that define benchmarks, in order.
|
|
163
|
+
# @api private
|
|
164
|
+
def benchmark_files: () -> Array[String]
|
|
165
|
+
|
|
166
|
+
private
|
|
167
|
+
|
|
168
|
+
def add_hook: (Symbol name, Proc? block) -> void
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Silences warnings about redefined constants, while loading a version
|
|
172
|
+
# over files of another's (see Config#load_into).
|
|
173
|
+
# @api private
|
|
174
|
+
module QuietRedefinition : Warning
|
|
175
|
+
REDEFINITION: Regexp
|
|
176
|
+
|
|
177
|
+
def self.during: [T] () { () -> T } -> T
|
|
178
|
+
|
|
179
|
+
def warn: (String message, ?category: Symbol?) -> nil
|
|
180
|
+
end
|
|
181
|
+
end
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
module Duel
|
|
2
|
+
# What a benchmark's run is measured by: a Sample, or a Trial,
|
|
3
|
+
# MemorySample or ProfileSample.
|
|
4
|
+
# @api private
|
|
5
|
+
interface _Sample
|
|
6
|
+
def start: () -> void
|
|
7
|
+
|
|
8
|
+
def stop: () -> void
|
|
9
|
+
|
|
10
|
+
# Runs the block with measuring paused, and returns what it does.
|
|
11
|
+
def pause: [T] () { () -> T } -> T
|
|
12
|
+
|
|
13
|
+
# Runs the block (around_iteration hooks, around each iteration)
|
|
14
|
+
# paused, and returns what it does.
|
|
15
|
+
def between: [T] () { () -> T } -> T
|
|
16
|
+
|
|
17
|
+
# Runs the block measured, inside #between's, and returns what it
|
|
18
|
+
# does.
|
|
19
|
+
def unpause: [T] () { () -> T } -> T
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# What a benchmark's blocks run in (its file's hooks, and its own,
|
|
23
|
+
# too): its state lives in instance variables. Its
|
|
24
|
+
# methods are the two a benchmark calls, and the helpers that files of
|
|
25
|
+
# benchmarks define.
|
|
26
|
+
class Context
|
|
27
|
+
@__duel_harness: Harness
|
|
28
|
+
|
|
29
|
+
# @api private
|
|
30
|
+
def initialize: (Harness harness) -> void
|
|
31
|
+
|
|
32
|
+
# Runs the block the first time the benchmark runs: its trial, or its
|
|
33
|
+
# first peak memory sample in a worker that ran none.
|
|
34
|
+
def once: () { () -> void } -> void
|
|
35
|
+
|
|
36
|
+
# Runs the block with measuring paused, and returns what it does.
|
|
37
|
+
def untimed: [T] () { () -> T } -> T
|
|
38
|
+
|
|
39
|
+
# Fails if a helper has the name of a method Duel gives benchmarks.
|
|
40
|
+
# @api private
|
|
41
|
+
def self.check: () -> void
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# A benchmark as a worker runs it: its Context, its hooks, its trial
|
|
45
|
+
# and its samples.
|
|
46
|
+
# @api private
|
|
47
|
+
class Harness
|
|
48
|
+
# Nothing left by pausing and resuming.
|
|
49
|
+
NO_LEAK: Hash[String, Float]
|
|
50
|
+
|
|
51
|
+
# The iterations each calibration sample pauses and resumes.
|
|
52
|
+
STRETCHES: Integer
|
|
53
|
+
|
|
54
|
+
# The iterations of each sample that learns what around_iteration
|
|
55
|
+
# hooks add to the metrics costly to read.
|
|
56
|
+
HOOKED: Integer
|
|
57
|
+
|
|
58
|
+
# The benchmark's body (or a hook), as a method of the context's, or
|
|
59
|
+
# a hook of the configuration's.
|
|
60
|
+
interface _Bound
|
|
61
|
+
def call: (?) -> untyped
|
|
62
|
+
|
|
63
|
+
def arity: () -> Integer
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
@definition: Definition
|
|
67
|
+
@context: Context
|
|
68
|
+
@setup: Array[_Bound]
|
|
69
|
+
@teardown: Array[_Bound]
|
|
70
|
+
@before_sample: Array[_Bound]
|
|
71
|
+
@after: Array[_Bound]
|
|
72
|
+
@body: _Bound
|
|
73
|
+
# Whether its block takes the number of iterations.
|
|
74
|
+
@given_n: bool
|
|
75
|
+
@iteration: Iteration?
|
|
76
|
+
@trial: bool
|
|
77
|
+
@sample: _Sample?
|
|
78
|
+
# Whether its once blocks have run (false: they run in the sample
|
|
79
|
+
# running, a worker's first for peak memory, and nil: no trial yet).
|
|
80
|
+
@once_done: bool?
|
|
81
|
+
@leak: Hash[String, Float]?
|
|
82
|
+
|
|
83
|
+
attr_reader definition: Definition
|
|
84
|
+
|
|
85
|
+
attr_reader context: Context
|
|
86
|
+
|
|
87
|
+
def initialize: (Definition definition, ?Hash[Symbol, Array[hook]] hooks) -> void
|
|
88
|
+
|
|
89
|
+
def once: () { () -> void } -> void
|
|
90
|
+
|
|
91
|
+
def untimed: [T] () { () -> T } -> T
|
|
92
|
+
|
|
93
|
+
# Runs its setup hooks.
|
|
94
|
+
def prepare: () -> void
|
|
95
|
+
|
|
96
|
+
# Runs its teardown hooks.
|
|
97
|
+
def finish: () -> void
|
|
98
|
+
|
|
99
|
+
# Runs the benchmark once, measuring nothing, with its once blocks.
|
|
100
|
+
def trial: () -> void
|
|
101
|
+
|
|
102
|
+
# Runs one sample for its peak memory (its once blocks too, if there
|
|
103
|
+
# was no trial).
|
|
104
|
+
def run_memory: (Integer n, MemorySample sample) -> void
|
|
105
|
+
|
|
106
|
+
# Runs one sample of +n+ iterations, measured by +sample+.
|
|
107
|
+
def run: (Integer n, _Sample sample) -> void
|
|
108
|
+
|
|
109
|
+
# Whether it runs inside around_iteration hooks.
|
|
110
|
+
def around?: () -> bool
|
|
111
|
+
|
|
112
|
+
# A Sample of +n+ iterations of it, measured by +metrics+.
|
|
113
|
+
def sample: (Array[Metric] metrics, n: Integer, ?counting: bool) -> Sample
|
|
114
|
+
|
|
115
|
+
# What a benchmark with around_iteration hooks leaves in each
|
|
116
|
+
# measure, an iteration, that isn't the iteration's own.
|
|
117
|
+
def leak: (Array[Metric] metrics) -> Hash[String, Float]
|
|
118
|
+
|
|
119
|
+
# Runs one iteration, measured, then its after_iterations hooks.
|
|
120
|
+
def iteration: () -> untyped
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
# The hooks +name+d: the configuration's, then its file's and its own.
|
|
125
|
+
def hooks: (Symbol name, Hash[Symbol, Array[hook]] hooks) -> Array[_Bound]
|
|
126
|
+
|
|
127
|
+
# +hook+, given the benchmark, if it takes it.
|
|
128
|
+
def given: (_Bound hook) -> _Bound
|
|
129
|
+
|
|
130
|
+
# What pausing and resuming leaves in each measure, an iteration.
|
|
131
|
+
def paused: (Array[Metric] metrics) -> Hash[String, Float]
|
|
132
|
+
|
|
133
|
+
# What the around_iteration hooks add to each measure (of metrics
|
|
134
|
+
# costly to read), an iteration.
|
|
135
|
+
def hooked: (Array[Metric] metrics) -> Hash[String, Float]
|
|
136
|
+
|
|
137
|
+
# Each measure's least in +runs+, over +stretches+.
|
|
138
|
+
def least: (Array[Hash[String, Metric::value]] runs, Integer stretches) -> Hash[String, Float]
|
|
139
|
+
|
|
140
|
+
def iterate: (Integer n, _Sample sample) -> void
|
|
141
|
+
|
|
142
|
+
def bound: (^(?) [self: Context] -> untyped block) -> _Bound
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# An iteration of a benchmark with around_iteration hooks, which a hook
|
|
146
|
+
# is given to run.
|
|
147
|
+
class Iteration
|
|
148
|
+
@harness: Harness
|
|
149
|
+
@hook: Harness::_Bound
|
|
150
|
+
@inner: Iteration?
|
|
151
|
+
@ran: bool
|
|
152
|
+
# What its iteration did, the last time it ran.
|
|
153
|
+
@done: untyped
|
|
154
|
+
|
|
155
|
+
# @api private
|
|
156
|
+
def initialize: (Harness harness, Harness::_Bound hook, Iteration? inner) -> void
|
|
157
|
+
|
|
158
|
+
# Runs the iteration, measured, and returns what it does.
|
|
159
|
+
def call: () -> untyped
|
|
160
|
+
|
|
161
|
+
# Runs its hook, given it, and returns what its iteration did.
|
|
162
|
+
# @api private
|
|
163
|
+
def run: () -> untyped
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# One measured stretch of a benchmark: every metric's measurement of it,
|
|
167
|
+
# and (CRuby) about how many bytes it allocated. A metric's #start_sample gets
|
|
168
|
+
# it: its #benchmark and #n are the metric's to read, and the rest is the
|
|
169
|
+
# worker's.
|
|
170
|
+
class Sample
|
|
171
|
+
# Zero bytes allocated: what JRuby tells.
|
|
172
|
+
# @api private
|
|
173
|
+
NO_HEAP: ^() -> Integer
|
|
174
|
+
|
|
175
|
+
# Bytes allocated so far, roughly (zero on JRuby).
|
|
176
|
+
# @api private
|
|
177
|
+
HEAP: ^() -> Integer
|
|
178
|
+
|
|
179
|
+
# Collects the garbage left so far, then holds off collecting.
|
|
180
|
+
# @api private
|
|
181
|
+
QUIET_GC: ^() -> void
|
|
182
|
+
|
|
183
|
+
@benchmark: Definition
|
|
184
|
+
@n: Integer
|
|
185
|
+
@counting: bool
|
|
186
|
+
@metrics: Array[Metric]
|
|
187
|
+
# Those paused around each iteration: not those costly to read.
|
|
188
|
+
@paused: Array[Metric]
|
|
189
|
+
@timed: Hash[String, Metric::value]
|
|
190
|
+
@counted: Hash[String, Metric::value]
|
|
191
|
+
@details: Hash[String, String]
|
|
192
|
+
@started: true?
|
|
193
|
+
@stopped: true?
|
|
194
|
+
@heap: Integer
|
|
195
|
+
|
|
196
|
+
# What a metric learns of the sample: its benchmark and its iterations.
|
|
197
|
+
attr_reader benchmark: Definition
|
|
198
|
+
|
|
199
|
+
attr_reader n: Integer
|
|
200
|
+
|
|
201
|
+
# @api private
|
|
202
|
+
attr_reader started: true?
|
|
203
|
+
|
|
204
|
+
# @api private
|
|
205
|
+
attr_reader stopped: true?
|
|
206
|
+
|
|
207
|
+
# Each timed measure's total, by name.
|
|
208
|
+
# @api private
|
|
209
|
+
attr_reader timed: Hash[String, Metric::value]
|
|
210
|
+
|
|
211
|
+
# Each counted measure's total, by name (when counting).
|
|
212
|
+
# @api private
|
|
213
|
+
attr_reader counted: Hash[String, Metric::value]
|
|
214
|
+
|
|
215
|
+
# Bytes allocated (once it's stopped).
|
|
216
|
+
# @api private
|
|
217
|
+
attr_reader heap: Integer
|
|
218
|
+
|
|
219
|
+
# What metrics said of it (Metric#details), by measure.
|
|
220
|
+
# @api private
|
|
221
|
+
attr_reader details: Hash[String, String]
|
|
222
|
+
|
|
223
|
+
# @api private
|
|
224
|
+
def initialize: (Definition benchmark, Array[Metric] metrics, n: Integer, ?counting: bool, ?around: bool) -> void
|
|
225
|
+
|
|
226
|
+
# @api private
|
|
227
|
+
def start: () -> void
|
|
228
|
+
|
|
229
|
+
# @api private
|
|
230
|
+
def stop: () -> void
|
|
231
|
+
|
|
232
|
+
# Runs the block with the metrics paused, and returns what it does.
|
|
233
|
+
# @api private
|
|
234
|
+
def pause: [T] () { () -> T } -> T
|
|
235
|
+
|
|
236
|
+
# Runs the block (around_iteration hooks) with the metrics paused, but those
|
|
237
|
+
# costly to read, and returns what it does.
|
|
238
|
+
# @api private
|
|
239
|
+
def between: [T] () { () -> T } -> T
|
|
240
|
+
|
|
241
|
+
# Runs the block measured, inside #between's, and returns what it
|
|
242
|
+
# does.
|
|
243
|
+
# @api private
|
|
244
|
+
def unpause: [T] () { () -> T } -> T
|
|
245
|
+
|
|
246
|
+
# Takes +leak+ away from each measure, once an iteration.
|
|
247
|
+
# @api private
|
|
248
|
+
def less: (Hash[String, Float] leak) -> void
|
|
249
|
+
|
|
250
|
+
private
|
|
251
|
+
|
|
252
|
+
# Keeps what a metric says of the sample, by measure.
|
|
253
|
+
def explain: (Metric metric) -> void
|
|
254
|
+
|
|
255
|
+
# Files a metric's measurement (or several, by name) by kind.
|
|
256
|
+
def record: (Metric metric, Metric::measurement value) -> void
|
|
257
|
+
|
|
258
|
+
# Adds +measured+ to +name+'s total, of its kind.
|
|
259
|
+
def file: (Hash[String, Metric::kind] kinds, String name, Metric::value? measured) -> void
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# A sample of what around_iteration hooks add to the metrics costly to
|
|
263
|
+
# read: all but the iterations measured.
|
|
264
|
+
# @api private
|
|
265
|
+
class Hooked
|
|
266
|
+
@sample: Sample
|
|
267
|
+
|
|
268
|
+
def initialize: (Sample sample) -> void
|
|
269
|
+
|
|
270
|
+
def start: () -> void
|
|
271
|
+
|
|
272
|
+
def stop: () -> void
|
|
273
|
+
|
|
274
|
+
# Runs the block (an untimed one, inside an iteration): paused already.
|
|
275
|
+
def pause: [T] () { () -> T } -> T
|
|
276
|
+
|
|
277
|
+
# Runs the block (the around_iteration hooks): measured.
|
|
278
|
+
def between: [T] () { () -> T } -> T
|
|
279
|
+
|
|
280
|
+
# Runs the block (an iteration) paused.
|
|
281
|
+
def unpause: [T] () { () -> T } -> T
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# A run of a benchmark that measures nothing: to see that it runs.
|
|
285
|
+
# @api private
|
|
286
|
+
class Trial
|
|
287
|
+
@started: true?
|
|
288
|
+
@stopped: true?
|
|
289
|
+
|
|
290
|
+
attr_reader started: true?
|
|
291
|
+
|
|
292
|
+
attr_reader stopped: true?
|
|
293
|
+
|
|
294
|
+
def start: () -> true
|
|
295
|
+
|
|
296
|
+
def stop: () -> true
|
|
297
|
+
|
|
298
|
+
def pause: [T] () { () -> T } -> T
|
|
299
|
+
|
|
300
|
+
def between: [T] () { () -> T } -> T
|
|
301
|
+
|
|
302
|
+
def unpause: [T] () { () -> T } -> T
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# A stretch of a benchmark run for its peak memory.
|
|
306
|
+
# @api private
|
|
307
|
+
class MemorySample
|
|
308
|
+
@started: true?
|
|
309
|
+
@stopped: true?
|
|
310
|
+
# Resident memory where it started, plus what paused blocks left.
|
|
311
|
+
@base: Integer
|
|
312
|
+
# The most above it the watcher saw, outside paused blocks.
|
|
313
|
+
@peak: Integer
|
|
314
|
+
@paused: bool?
|
|
315
|
+
# Resident memory when it last stopped watching.
|
|
316
|
+
@unwatched: Integer
|
|
317
|
+
@watcher: Thread
|
|
318
|
+
|
|
319
|
+
attr_reader started: true?
|
|
320
|
+
|
|
321
|
+
attr_reader stopped: true?
|
|
322
|
+
|
|
323
|
+
# Bytes (once it's stopped).
|
|
324
|
+
attr_reader peak: Integer
|
|
325
|
+
|
|
326
|
+
def start: () -> void
|
|
327
|
+
|
|
328
|
+
def stop: () -> void
|
|
329
|
+
|
|
330
|
+
# Runs the block unwatched: what it leaves resident isn't counted.
|
|
331
|
+
def pause: [T] () { () -> T } -> T
|
|
332
|
+
|
|
333
|
+
# Runs the block (around_iteration hooks) unwatched, as #pause does.
|
|
334
|
+
def between: [T] () { () -> T } -> T
|
|
335
|
+
|
|
336
|
+
# Runs the block watched, inside #between's.
|
|
337
|
+
def unpause: [T] () { () -> T } -> T
|
|
338
|
+
|
|
339
|
+
private
|
|
340
|
+
|
|
341
|
+
def unwatched: () -> void
|
|
342
|
+
|
|
343
|
+
def watched: () -> void
|
|
344
|
+
|
|
345
|
+
def resident: () -> Integer
|
|
346
|
+
|
|
347
|
+
def watch: () -> void
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
# A stretch of a benchmark profiled with StackProf, the garbage
|
|
351
|
+
# collector held off, and untimed blocks and around_iteration hooks'
|
|
352
|
+
# own work left out.
|
|
353
|
+
# @api private
|
|
354
|
+
class ProfileSample
|
|
355
|
+
# Microseconds between samples.
|
|
356
|
+
INTERVAL: Integer
|
|
357
|
+
|
|
358
|
+
# What StackProf samples by here (see .mode).
|
|
359
|
+
MODE: :cpu | :wall
|
|
360
|
+
|
|
361
|
+
@started: true?
|
|
362
|
+
@stopped: true?
|
|
363
|
+
|
|
364
|
+
# What StackProf samples by on +platform+: CPU time, but elapsed time
|
|
365
|
+
# on macOS.
|
|
366
|
+
def self.mode: (?String platform) -> (:cpu | :wall)
|
|
367
|
+
|
|
368
|
+
attr_reader started: true?
|
|
369
|
+
|
|
370
|
+
attr_reader stopped: true?
|
|
371
|
+
|
|
372
|
+
def start: () -> void
|
|
373
|
+
|
|
374
|
+
def stop: () -> void
|
|
375
|
+
|
|
376
|
+
# Runs the block (an untimed one, or around_iteration hooks' work) left
|
|
377
|
+
# out of the profile.
|
|
378
|
+
def pause: [T] () { () -> T } -> T
|
|
379
|
+
|
|
380
|
+
# Runs the block (around_iteration hooks) in #pause.
|
|
381
|
+
def between: [T] () { () -> T } -> T
|
|
382
|
+
|
|
383
|
+
# Runs the block (an iteration, inside its hooks) profiled,
|
|
384
|
+
# inside #pause's.
|
|
385
|
+
def unpause: [T] () { () -> T } -> T
|
|
386
|
+
end
|
|
387
|
+
end
|
data/sig/duel/init.rbs
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
module Duel
|
|
2
|
+
# `duel init`: sets Duel up in a gem or an app that isn't a Rails app.
|
|
3
|
+
# @api private
|
|
4
|
+
class Init
|
|
5
|
+
USAGE: String
|
|
6
|
+
|
|
7
|
+
TEMPLATES: String
|
|
8
|
+
|
|
9
|
+
# What's added to a Rakefile.
|
|
10
|
+
RAKE_TASK: String
|
|
11
|
+
|
|
12
|
+
# What's added to a Gemfile, for profiles of regressions.
|
|
13
|
+
STACKPROF: String
|
|
14
|
+
|
|
15
|
+
# A Gemfile's development group.
|
|
16
|
+
DEVELOPMENT: Regexp
|
|
17
|
+
|
|
18
|
+
@argv: Array[String]
|
|
19
|
+
@out: _Output
|
|
20
|
+
@err: _Output
|
|
21
|
+
@root: String
|
|
22
|
+
@force: bool?
|
|
23
|
+
@dry_run: bool?
|
|
24
|
+
@stackprof: bool?
|
|
25
|
+
|
|
26
|
+
def initialize: (Array[String] argv, ?out: _Output, ?err: _Output, ?root: String) -> void
|
|
27
|
+
|
|
28
|
+
# Returns the exit status: 0 set up, 1 a Rails app, 2 a bad option.
|
|
29
|
+
def run: () -> Integer
|
|
30
|
+
|
|
31
|
+
# The files it writes, from the project's root, to what's in them.
|
|
32
|
+
def files: () -> Hash[String, String]
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def parser: () -> OptionParser
|
|
37
|
+
|
|
38
|
+
# Says to use duel-rails's generator instead.
|
|
39
|
+
def rails: () -> Integer
|
|
40
|
+
|
|
41
|
+
# Writes +content+ to +path+, unless it exists, and says what it did.
|
|
42
|
+
def write: (String path, String content) -> void
|
|
43
|
+
|
|
44
|
+
# Adds Duel's Rake task to the Rakefile.
|
|
45
|
+
def add_rake_task: () -> void
|
|
46
|
+
|
|
47
|
+
# Adds StackProf to the Gemfile: in its development group, or at its end.
|
|
48
|
+
def add_stackprof: () -> void
|
|
49
|
+
|
|
50
|
+
# The Gemfile with StackProf last in its development group, or nil.
|
|
51
|
+
def in_development_group: (String text) -> String?
|
|
52
|
+
|
|
53
|
+
# What goes between a file's text and what's added at its end.
|
|
54
|
+
def gap: (String text) -> String
|
|
55
|
+
|
|
56
|
+
# The Gemfile with StackProf at its end.
|
|
57
|
+
def at_the_end: (String text) -> String
|
|
58
|
+
|
|
59
|
+
def said: (String what, String path) -> void
|
|
60
|
+
|
|
61
|
+
# What to do next, numbered, after a blank line.
|
|
62
|
+
def next_steps: () -> String
|
|
63
|
+
|
|
64
|
+
def render: (String name) -> String
|
|
65
|
+
|
|
66
|
+
# The library the gemspec names, or nil.
|
|
67
|
+
def library: () -> String?
|
|
68
|
+
|
|
69
|
+
# The library, without a gemspec: lib/'s only Ruby file, or nil.
|
|
70
|
+
def guess: () -> String?
|
|
71
|
+
|
|
72
|
+
# What each version's workers require.
|
|
73
|
+
def required: () -> String?
|
|
74
|
+
|
|
75
|
+
def benchmark_file: () -> String
|
|
76
|
+
|
|
77
|
+
def gemfile?: () -> bool
|
|
78
|
+
|
|
79
|
+
def ext: () -> bool
|
|
80
|
+
|
|
81
|
+
def version_file: () -> bool
|
|
82
|
+
|
|
83
|
+
# The Ruby the workflow sets up, or none.
|
|
84
|
+
def ruby_version: () -> String?
|
|
85
|
+
|
|
86
|
+
# The root of the project's git repository.
|
|
87
|
+
def repository: () -> String
|
|
88
|
+
|
|
89
|
+
# The project's directory, from its repository's root, or nil.
|
|
90
|
+
def working_directory: () -> String?
|
|
91
|
+
|
|
92
|
+
def workflow_name: () -> String
|
|
93
|
+
|
|
94
|
+
# What marks the workflow's comment on a pull request.
|
|
95
|
+
def marker: () -> String
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module Duel
|
|
2
|
+
# A run's report as JSON, for a program to read.
|
|
3
|
+
# @api private
|
|
4
|
+
class JSONReport
|
|
5
|
+
# A value in the report: plain JSON types.
|
|
6
|
+
type value = String | Integer | Float | bool | nil | Array[value] | Hash[String, value]
|
|
7
|
+
|
|
8
|
+
# The report, by key.
|
|
9
|
+
type report = Hash[String, value]
|
|
10
|
+
|
|
11
|
+
# Its schema's version.
|
|
12
|
+
SCHEMA: Integer
|
|
13
|
+
|
|
14
|
+
@runner: Runner
|
|
15
|
+
@results: Array[Result]
|
|
16
|
+
@settings: Settings
|
|
17
|
+
@exit: Integer
|
|
18
|
+
@versions: Hash[String, Hash[String, String | bool | nil]]
|
|
19
|
+
|
|
20
|
+
# A run that didn't compare: +status+, its +exit+ status, and what it
|
|
21
|
+
# said.
|
|
22
|
+
def self.note: (String status, Integer exit, String message, Settings settings) -> report
|
|
23
|
+
|
|
24
|
+
# What every report starts with.
|
|
25
|
+
def self.head: (String status, Integer exit, String? message, Settings settings) -> report
|
|
26
|
+
|
|
27
|
+
def initialize: (Runner runner, Array[Result] results, settings: Settings, exit: Integer, baseline: Hash[String, String?],
|
|
28
|
+
candidate: Hash[String, String | bool | nil]) -> void
|
|
29
|
+
|
|
30
|
+
def to_h: () -> report
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def benchmark: (Result result) -> report
|
|
35
|
+
|
|
36
|
+
# Each timed measure, by metric.
|
|
37
|
+
def timed: (Result result) -> Hash[String, report]
|
|
38
|
+
|
|
39
|
+
# Each counted measure, by metric.
|
|
40
|
+
def counted: (Result result) -> Hash[String, report]
|
|
41
|
+
|
|
42
|
+
def peak_memory: (Result result) -> report?
|
|
43
|
+
|
|
44
|
+
# The verdicts across the board, by metric.
|
|
45
|
+
def across: () -> Hash[String, report]
|
|
46
|
+
end
|
|
47
|
+
end
|