corkscrews 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/LICENSE.txt +21 -0
- data/README.md +130 -0
- data/Rakefile +20 -0
- data/exe/corkscrews +9 -0
- data/ext/corkscrews/corkscrews.c +24 -0
- data/ext/corkscrews/corkscrews_native.h +85 -0
- data/ext/corkscrews/delay.c +259 -0
- data/ext/corkscrews/extconf.rb +25 -0
- data/ext/corkscrews/hooks.c +156 -0
- data/ext/corkscrews/monitor.c +117 -0
- data/ext/corkscrews/record.c +109 -0
- data/ext/corkscrews/sampler.c +99 -0
- data/lib/corkscrews/analysis.rb +452 -0
- data/lib/corkscrews/boot.rb +25 -0
- data/lib/corkscrews/cli.rb +159 -0
- data/lib/corkscrews/controller.rb +137 -0
- data/lib/corkscrews/firefox_profile.rb +290 -0
- data/lib/corkscrews/native.rb +108 -0
- data/lib/corkscrews/primitives.rb +127 -0
- data/lib/corkscrews/rack.rb +39 -0
- data/lib/corkscrews/recorder.rb +903 -0
- data/lib/corkscrews/report.rb +241 -0
- data/lib/corkscrews/statistics.rb +112 -0
- data/lib/corkscrews/time_source.rb +11 -0
- data/lib/corkscrews/validate.rb +348 -0
- data/lib/corkscrews/version.rb +5 -0
- data/lib/corkscrews.rb +43 -0
- data/validate/benchmarks/b01_serial_hotspot/base.rb +28 -0
- data/validate/benchmarks/b01_serial_hotspot/manifest.yml +23 -0
- data/validate/benchmarks/b01_serial_hotspot/optimized.rb +28 -0
- data/validate/benchmarks/b02_false_hotspot/base.rb +43 -0
- data/validate/benchmarks/b02_false_hotspot/manifest.yml +19 -0
- data/validate/benchmarks/b02_false_hotspot/optimized.rb +43 -0
- data/validate/benchmarks/b03_io_wait/base.rb +29 -0
- data/validate/benchmarks/b03_io_wait/manifest.yml +16 -0
- data/validate/benchmarks/b03_io_wait/optimized.rb +29 -0
- data/validate/benchmarks/b04_lock_contention/base.rb +30 -0
- data/validate/benchmarks/b04_lock_contention/manifest.yml +19 -0
- data/validate/benchmarks/b04_lock_contention/optimized.rb +30 -0
- data/validate/benchmarks/b05_gvl_contention/base.rb +24 -0
- data/validate/benchmarks/b05_gvl_contention/manifest.yml +17 -0
- data/validate/benchmarks/b05_gvl_contention/optimized.rb +24 -0
- data/validate/benchmarks/b06_gc_pressure/base.rb +16 -0
- data/validate/benchmarks/b06_gc_pressure/manifest.yml +16 -0
- data/validate/benchmarks/b06_gc_pressure/optimized.rb +17 -0
- data/validate/benchmarks/b07_pipeline/base.rb +34 -0
- data/validate/benchmarks/b07_pipeline/manifest.yml +17 -0
- data/validate/benchmarks/b07_pipeline/optimized.rb +34 -0
- data/validate/benchmarks/b08_native_attribution/base.rb +18 -0
- data/validate/benchmarks/b08_native_attribution/manifest.yml +18 -0
- data/validate/benchmarks/b08_native_attribution/optimized.rb +18 -0
- data/validate/benchmarks/support.rb +29 -0
- data/validate/harness/run_all.rb +16 -0
- data/validate/harness/stats_check.py +27 -0
- data/validate/harness/t1.rb +15 -0
- data/validate/harness/t4.rb +15 -0
- data/validate/harness/t5.rb +15 -0
- metadata +118 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cgi"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
require_relative "analysis"
|
|
7
|
+
require_relative "firefox_profile"
|
|
8
|
+
|
|
9
|
+
module Corkscrews
|
|
10
|
+
class Report
|
|
11
|
+
def initialize(path)
|
|
12
|
+
@path = path
|
|
13
|
+
@analysis = Analysis.load(path)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_text(limit: 10)
|
|
17
|
+
aggregate = @analysis.aggregate
|
|
18
|
+
lines = []
|
|
19
|
+
lines << "corkscrews report: #{@path}"
|
|
20
|
+
lines << "runs: #{aggregate[:run_count]} samples: #{aggregate[:total_samples]} duration: #{format_seconds(aggregate[:duration_ns])}"
|
|
21
|
+
unless aggregate[:native].empty?
|
|
22
|
+
lines << "native: samples=#{aggregate[:native][:samples].to_i} target_hits=#{aggregate[:native][:target_hits].to_i} ring_events=#{aggregate[:native][:ring_events].to_i} monitor_ticks=#{aggregate[:native][:monitor_ticks].to_i} monitor_signals=#{aggregate[:native][:monitor_signals].to_i} monitor_signal_failures=#{aggregate[:native][:monitor_signal_failures].to_i} live_threads=#{aggregate[:native][:thread_live_count].to_i} max_live_threads=#{aggregate[:native][:thread_max_live_count].to_i} debt_settled_ns=#{aggregate[:native][:debt_settled_ns].to_i} gc_pause_ns=#{aggregate[:native][:gc_pause_ns].to_i}"
|
|
23
|
+
end
|
|
24
|
+
lines << "runtime: fiber_switches=#{aggregate[:runtime][:fiber_switches]} max_fibers=#{aggregate[:runtime][:max_fiber_count]} fiber_threads=#{aggregate[:runtime][:fiber_thread_count]} fiber_scheduler=#{aggregate[:runtime][:fiber_scheduler]} max_ractors=#{aggregate[:runtime][:max_ractor_count]}"
|
|
25
|
+
lines << ""
|
|
26
|
+
|
|
27
|
+
if aggregate[:progress].empty?
|
|
28
|
+
lines << "progress: none"
|
|
29
|
+
else
|
|
30
|
+
lines << "progress:"
|
|
31
|
+
aggregate[:progress].each do |name, stats|
|
|
32
|
+
ci = stats[:rate_ci]
|
|
33
|
+
lines << format(
|
|
34
|
+
" %-16s rate=%10.2f/s ci=[%10.2f,%10.2f] count=%d",
|
|
35
|
+
name,
|
|
36
|
+
stats[:mean_rate],
|
|
37
|
+
ci[0],
|
|
38
|
+
ci[1],
|
|
39
|
+
stats[:count]
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
unless aggregate[:latency].empty?
|
|
45
|
+
lines << ""
|
|
46
|
+
lines << "latency:"
|
|
47
|
+
aggregate[:latency].each do |name, stats|
|
|
48
|
+
ci = stats[:mean_ms_ci]
|
|
49
|
+
little_ci = stats[:little_mean_ms_ci]
|
|
50
|
+
lines << format(
|
|
51
|
+
" %-16s mean=%8.3fms little=%8.3fms ci=[%8.3f,%8.3f] little_ci=[%8.3f,%8.3f] completed=%d",
|
|
52
|
+
name,
|
|
53
|
+
stats[:mean_ms],
|
|
54
|
+
stats[:little_mean_ms],
|
|
55
|
+
ci[0],
|
|
56
|
+
ci[1],
|
|
57
|
+
little_ci[0],
|
|
58
|
+
little_ci[1],
|
|
59
|
+
stats[:completed_count]
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
lines << ""
|
|
65
|
+
lines << "top targets:"
|
|
66
|
+
lines << " share causal verdict samples target predicted improvement at 25% / 50% / 90%"
|
|
67
|
+
aggregate[:targets].first(limit).each do |target|
|
|
68
|
+
curve = target[:curve]
|
|
69
|
+
i25 = curve.find { |point| point[:speedup_pct] == 25 }[:improvement_pct]
|
|
70
|
+
i50 = curve.find { |point| point[:speedup_pct] == 50 }[:improvement_pct]
|
|
71
|
+
i90 = curve.find { |point| point[:speedup_pct] == 90 }[:improvement_pct]
|
|
72
|
+
lines << format(
|
|
73
|
+
" %5.1f%% %5.1f%% %-10s %7d %-62s %6.2f%% / %6.2f%% / %6.2f%%",
|
|
74
|
+
target[:sample_share] * 100.0,
|
|
75
|
+
target[:causal_share].to_f * 100.0,
|
|
76
|
+
target[:verdict],
|
|
77
|
+
target[:samples],
|
|
78
|
+
target_label(target),
|
|
79
|
+
i25,
|
|
80
|
+
i50,
|
|
81
|
+
i90
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
disagreements = disagreement_targets(aggregate[:targets]).first(5)
|
|
86
|
+
unless disagreements.empty?
|
|
87
|
+
lines << ""
|
|
88
|
+
lines << "disagreement highlights:"
|
|
89
|
+
disagreements.each do |target|
|
|
90
|
+
lines << format(
|
|
91
|
+
" %-70s observed=%5.1f%% causal=%5.1f%% verdict=%s",
|
|
92
|
+
target_label(target),
|
|
93
|
+
target[:sample_share] * 100.0,
|
|
94
|
+
target[:causal_share].to_f * 100.0,
|
|
95
|
+
target[:verdict]
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
lines.join("\n")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def write_firefox(path)
|
|
104
|
+
aggregate = @analysis.aggregate
|
|
105
|
+
File.write(path, JSON.pretty_generate(FirefoxProfile.build(aggregate)))
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def write_html(path, limit: 20)
|
|
109
|
+
aggregate = @analysis.aggregate
|
|
110
|
+
File.write(path, html_document(aggregate, limit))
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
def format_seconds(ns)
|
|
116
|
+
format("%.3fs", ns.to_f / 1_000_000_000)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def html_document(aggregate, limit)
|
|
120
|
+
rows = aggregate[:targets].first(limit).map do |target|
|
|
121
|
+
curve = target[:curve]
|
|
122
|
+
i25 = curve.find { |point| point[:speedup_pct] == 25 }[:improvement_pct]
|
|
123
|
+
i50 = curve.find { |point| point[:speedup_pct] == 50 }[:improvement_pct]
|
|
124
|
+
i90 = curve.find { |point| point[:speedup_pct] == 90 }[:improvement_pct]
|
|
125
|
+
|
|
126
|
+
<<~HTML
|
|
127
|
+
<tr>
|
|
128
|
+
<td>#{escape(target_label(target))}</td>
|
|
129
|
+
<td>#{format("%.1f%%", target[:sample_share] * 100.0)}</td>
|
|
130
|
+
<td>#{format("%.1f%%", target[:causal_share].to_f * 100.0)}</td>
|
|
131
|
+
<td>#{escape(target[:verdict])}</td>
|
|
132
|
+
<td>#{target[:samples]}</td>
|
|
133
|
+
<td>#{format("%.2f%%", i25)}</td>
|
|
134
|
+
<td>#{format("%.2f%%", i50)}</td>
|
|
135
|
+
<td>#{format("%.2f%%", i90)}</td>
|
|
136
|
+
<td>#{curve_svg(curve)}</td>
|
|
137
|
+
</tr>
|
|
138
|
+
HTML
|
|
139
|
+
end.join
|
|
140
|
+
|
|
141
|
+
latency_rows = aggregate[:latency].map do |name, stats|
|
|
142
|
+
<<~HTML
|
|
143
|
+
<tr>
|
|
144
|
+
<td>#{escape(name)}</td>
|
|
145
|
+
<td>#{format("%.3fms", stats[:mean_ms])}</td>
|
|
146
|
+
<td>#{format("%.3fms", stats[:little_mean_ms])}</td>
|
|
147
|
+
<td>#{format("[%.3f, %.3f]", *stats[:mean_ms_ci])}</td>
|
|
148
|
+
<td>#{format("[%.3f, %.3f]", *stats[:little_mean_ms_ci])}</td>
|
|
149
|
+
<td>#{stats[:completed_count]}</td>
|
|
150
|
+
</tr>
|
|
151
|
+
HTML
|
|
152
|
+
end.join
|
|
153
|
+
|
|
154
|
+
<<~HTML
|
|
155
|
+
<!doctype html>
|
|
156
|
+
<html lang="en">
|
|
157
|
+
<head>
|
|
158
|
+
<meta charset="utf-8">
|
|
159
|
+
<title>corkscrews report</title>
|
|
160
|
+
<style>
|
|
161
|
+
body { font-family: system-ui, sans-serif; margin: 2rem; color: #1f2933; }
|
|
162
|
+
table { border-collapse: collapse; width: 100%; }
|
|
163
|
+
th, td { border-bottom: 1px solid #d8dee9; padding: 0.5rem; text-align: left; }
|
|
164
|
+
th { background: #eef2f7; }
|
|
165
|
+
.meta { margin-bottom: 1rem; color: #52606d; }
|
|
166
|
+
</style>
|
|
167
|
+
</head>
|
|
168
|
+
<body>
|
|
169
|
+
<h1>corkscrews report</h1>
|
|
170
|
+
<p class="meta">runs: #{aggregate[:run_count]} samples: #{aggregate[:total_samples]} duration: #{format_seconds(aggregate[:duration_ns])}</p>
|
|
171
|
+
<p class="meta">fiber switches: #{aggregate[:runtime][:fiber_switches]} max fibers: #{aggregate[:runtime][:max_fiber_count]} fiber threads: #{aggregate[:runtime][:fiber_thread_count]} scheduler: #{aggregate[:runtime][:fiber_scheduler]} max ractors: #{aggregate[:runtime][:max_ractor_count]}</p>
|
|
172
|
+
<table>
|
|
173
|
+
<thead>
|
|
174
|
+
<tr>
|
|
175
|
+
<th>Target</th>
|
|
176
|
+
<th>Sample share</th>
|
|
177
|
+
<th>Causal share</th>
|
|
178
|
+
<th>Verdict</th>
|
|
179
|
+
<th>Samples</th>
|
|
180
|
+
<th>25% virtual speedup</th>
|
|
181
|
+
<th>50% virtual speedup</th>
|
|
182
|
+
<th>90% virtual speedup</th>
|
|
183
|
+
<th>Curve</th>
|
|
184
|
+
</tr>
|
|
185
|
+
</thead>
|
|
186
|
+
<tbody>
|
|
187
|
+
#{rows}
|
|
188
|
+
</tbody>
|
|
189
|
+
</table>
|
|
190
|
+
<h2>Latency</h2>
|
|
191
|
+
<table>
|
|
192
|
+
<thead>
|
|
193
|
+
<tr>
|
|
194
|
+
<th>Name</th>
|
|
195
|
+
<th>Mean</th>
|
|
196
|
+
<th>Little mean</th>
|
|
197
|
+
<th>CI</th>
|
|
198
|
+
<th>Little CI</th>
|
|
199
|
+
<th>Completed</th>
|
|
200
|
+
</tr>
|
|
201
|
+
</thead>
|
|
202
|
+
<tbody>
|
|
203
|
+
#{latency_rows}
|
|
204
|
+
</tbody>
|
|
205
|
+
</table>
|
|
206
|
+
</body>
|
|
207
|
+
</html>
|
|
208
|
+
HTML
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def escape(value)
|
|
212
|
+
CGI.escapeHTML(value.to_s)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def target_label(target)
|
|
216
|
+
if target[:kind] == "wait"
|
|
217
|
+
"wait:#{target[:name]}"
|
|
218
|
+
else
|
|
219
|
+
"#{target[:file]}:#{target[:line]}"
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def disagreement_targets(targets)
|
|
224
|
+
targets.select do |target|
|
|
225
|
+
target[:sample_share].to_f >= 0.05 && target[:causal_share].to_f <= target[:sample_share].to_f / 3.0
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def curve_svg(curve)
|
|
230
|
+
max_y = [curve.map { |point| point[:improvement_pct].to_f }.max.to_f, 1.0].max
|
|
231
|
+
points = curve.map do |point|
|
|
232
|
+
x = (point[:speedup_pct].to_f / 95.0) * 160.0
|
|
233
|
+
y = 40.0 - ((point[:improvement_pct].to_f / max_y) * 36.0)
|
|
234
|
+
"#{format("%.1f", x)},#{format("%.1f", y)}"
|
|
235
|
+
end.join(" ")
|
|
236
|
+
|
|
237
|
+
%(<svg width="170" height="44" viewBox="0 0 170 44" role="img" aria-label="virtual speedup curve"><polyline points="#{points}" fill="none" stroke="#1f6feb" stroke-width="2"/></svg>)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
end
|
|
241
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Corkscrews
|
|
4
|
+
module Statistics
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def mean(values)
|
|
8
|
+
return 0.0 if values.empty?
|
|
9
|
+
|
|
10
|
+
values.sum.to_f / values.length
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def percentile(values, pct)
|
|
14
|
+
return 0.0 if values.empty?
|
|
15
|
+
|
|
16
|
+
sorted = values.sort
|
|
17
|
+
index = (pct * (sorted.length - 1)).round
|
|
18
|
+
sorted.fetch(index)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def percentile_ci(values, lower: 0.025, upper: 0.975)
|
|
22
|
+
[percentile(values, lower), percentile(values, upper)]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def median(values)
|
|
26
|
+
return 0.0 if values.empty?
|
|
27
|
+
|
|
28
|
+
sorted = values.sort
|
|
29
|
+
middle = sorted.length / 2
|
|
30
|
+
return sorted.fetch(middle).to_f if sorted.length.odd?
|
|
31
|
+
|
|
32
|
+
(sorted.fetch(middle - 1) + sorted.fetch(middle)).to_f / 2.0
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def bootstrap_mean_ci(values, iterations: 1_000, random: Random.new(12_345))
|
|
36
|
+
return [0.0, 0.0] if values.empty?
|
|
37
|
+
return [values.first.to_f, values.first.to_f] if values.length == 1
|
|
38
|
+
|
|
39
|
+
# Paper basis: Kalibera & Jones, "Quantifying Performance Changes
|
|
40
|
+
# with Effect Size Confidence Intervals" (arXiv:2007.10899),
|
|
41
|
+
# motivates reporting uncertainty for performance effect sizes.
|
|
42
|
+
# Paper URL: https://arxiv.org/abs/2007.10899
|
|
43
|
+
means = Array.new(iterations) do
|
|
44
|
+
sample = Array.new(values.length) { values.fetch(random.rand(values.length)) }
|
|
45
|
+
mean(sample)
|
|
46
|
+
end
|
|
47
|
+
percentile_ci(means)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def monotonic_regression(points)
|
|
51
|
+
ordered = points.sort_by { |point| point[:speedup_pct] }
|
|
52
|
+
blocks = []
|
|
53
|
+
|
|
54
|
+
# Paper basis: Coz SOSP'15 Section 2 plots increasing virtual
|
|
55
|
+
# speedup on the x-axis; this pool-adjacent-violators pass enforces
|
|
56
|
+
# the physical monotonicity expected from that curve.
|
|
57
|
+
# Paper URL: https://arxiv.org/abs/1608.03676
|
|
58
|
+
ordered.each do |point|
|
|
59
|
+
blocks << {
|
|
60
|
+
start: point[:speedup_pct],
|
|
61
|
+
finish: point[:speedup_pct],
|
|
62
|
+
weight: 1.0,
|
|
63
|
+
value: point[:improvement_pct].to_f
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
while blocks.length >= 2 && blocks[-2][:value] > blocks[-1][:value]
|
|
67
|
+
right = blocks.pop
|
|
68
|
+
left = blocks.pop
|
|
69
|
+
weight = left[:weight] + right[:weight]
|
|
70
|
+
blocks << {
|
|
71
|
+
start: left[:start],
|
|
72
|
+
finish: right[:finish],
|
|
73
|
+
weight: weight,
|
|
74
|
+
value: ((left[:value] * left[:weight]) + (right[:value] * right[:weight])) / weight
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
blocks.flat_map do |block|
|
|
80
|
+
ordered
|
|
81
|
+
.select { |point| point[:speedup_pct] >= block[:start] && point[:speedup_pct] <= block[:finish] }
|
|
82
|
+
.map { |point| point.merge(improvement_pct: block[:value]) }
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def kendall_tau(left, right)
|
|
87
|
+
return 0.0 unless left.length == right.length
|
|
88
|
+
return 1.0 if left.length < 2
|
|
89
|
+
|
|
90
|
+
concordant = 0
|
|
91
|
+
discordant = 0
|
|
92
|
+
(0...(left.length - 1)).each do |i|
|
|
93
|
+
((i + 1)...left.length).each do |j|
|
|
94
|
+
left_delta = left[i] <=> left[j]
|
|
95
|
+
right_delta = right[i] <=> right[j]
|
|
96
|
+
next if left_delta.zero? || right_delta.zero?
|
|
97
|
+
|
|
98
|
+
if left_delta == right_delta
|
|
99
|
+
concordant += 1
|
|
100
|
+
else
|
|
101
|
+
discordant += 1
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
total = concordant + discordant
|
|
107
|
+
return 0.0 if total.zero?
|
|
108
|
+
|
|
109
|
+
(concordant - discordant).to_f / total
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|