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,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
require_relative "analysis"
|
|
6
|
+
|
|
7
|
+
module Corkscrews
|
|
8
|
+
class Controller
|
|
9
|
+
SPEEDUPS = Analysis::SPEEDUPS.freeze
|
|
10
|
+
DEFAULT_RANDOM = Random.new(12_345)
|
|
11
|
+
|
|
12
|
+
Round = Struct.new(
|
|
13
|
+
:id,
|
|
14
|
+
:target,
|
|
15
|
+
:speedup_pct,
|
|
16
|
+
:baseline,
|
|
17
|
+
:visits,
|
|
18
|
+
:physical_duration_ns,
|
|
19
|
+
:virtual_duration_ns,
|
|
20
|
+
keyword_init: true
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
def initialize(targets:, random: DEFAULT_RANDOM)
|
|
24
|
+
@targets = targets
|
|
25
|
+
@random = random
|
|
26
|
+
@round_index = 0
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def next_round(progress_visits:, duration_ns:, targets: nil, history: [])
|
|
30
|
+
active_targets = targets || @targets
|
|
31
|
+
target = pick_target(active_targets, history)
|
|
32
|
+
speedup_pct = pick_speedup(target, history)
|
|
33
|
+
share = target_share(target)
|
|
34
|
+
reduced = share * (speedup_pct.to_f / 100.0)
|
|
35
|
+
|
|
36
|
+
Round.new(
|
|
37
|
+
id: next_round_id,
|
|
38
|
+
target: target,
|
|
39
|
+
speedup_pct: speedup_pct,
|
|
40
|
+
baseline: speedup_pct.zero?,
|
|
41
|
+
visits: progress_visits,
|
|
42
|
+
physical_duration_ns: duration_ns,
|
|
43
|
+
virtual_duration_ns: (duration_ns * (1.0 - reduced)).round
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.plan_for(analysis, rounds: 32, random: DEFAULT_RANDOM)
|
|
48
|
+
aggregate = analysis.aggregate
|
|
49
|
+
controller = new(targets: aggregate[:targets].first(32), random: random)
|
|
50
|
+
progress_count = aggregate[:progress].values.sum { |entry| entry[:count].to_i }
|
|
51
|
+
duration_ns = aggregate[:duration_ns]
|
|
52
|
+
|
|
53
|
+
Array.new(rounds) do
|
|
54
|
+
controller.next_round(progress_visits: progress_count, duration_ns: duration_ns)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
# Paper basis: Coz SOSP'15 Section 2 "Experiment initialization"
|
|
61
|
+
# and Section 3.2 select the speedup target/amount during execution;
|
|
62
|
+
# Stabilizer ASPLOS'13 Section 2 motivates randomized exploration to
|
|
63
|
+
# avoid systematic measurement bias.
|
|
64
|
+
# Paper URLs: https://arxiv.org/abs/1608.03676
|
|
65
|
+
# https://people.cs.umass.edu/~emery/pubs/stabilizer-asplos13.pdf
|
|
66
|
+
def pick_target(targets, history)
|
|
67
|
+
return { kind: "none", sample_share: 0.0, causal_share: 0.0 } if targets.empty?
|
|
68
|
+
|
|
69
|
+
total = targets.sum { |target| target_weight(target, history) }
|
|
70
|
+
cursor = @random.rand * total
|
|
71
|
+
targets.each do |target|
|
|
72
|
+
cursor -= target_weight(target, history)
|
|
73
|
+
return target if cursor <= 0.0
|
|
74
|
+
end
|
|
75
|
+
targets.last
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Paper basis: Coz SOSP'15 Section 3.2 gives 0% virtual speedup
|
|
79
|
+
# 50% probability because every target needs a local baseline.
|
|
80
|
+
# Paper URL: https://arxiv.org/abs/1608.03676
|
|
81
|
+
def pick_speedup(target, history)
|
|
82
|
+
target_history = history_for_target(target, history)
|
|
83
|
+
baseline_count = target_history.count { |round| round.fetch(:speedup_pct, round["speedup_pct"]).to_i.zero? }
|
|
84
|
+
nonbaseline_count = target_history.length - baseline_count
|
|
85
|
+
return 0 if baseline_count <= nonbaseline_count / 2
|
|
86
|
+
|
|
87
|
+
uncovered = priority_speedups.reject do |speedup|
|
|
88
|
+
target_history.any? { |round| round.fetch(:speedup_pct, round["speedup_pct"]).to_i == speedup }
|
|
89
|
+
end
|
|
90
|
+
return uncovered.first if uncovered.any?
|
|
91
|
+
|
|
92
|
+
SPEEDUPS.reject(&:zero?).fetch(@random.rand(SPEEDUPS.length - 1))
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def next_round_id
|
|
96
|
+
@round_index += 1
|
|
97
|
+
"round-#{@round_index}-#{SecureRandom.hex(3)}"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def target_weight(target, history)
|
|
101
|
+
count = history_for_target(target, history).length
|
|
102
|
+
share = [target_share(target), 0.0001].max
|
|
103
|
+
exploration = 1.0 / Math.sqrt(count + 1)
|
|
104
|
+
coverage = missing_priority_speedups(target, history) * 0.05
|
|
105
|
+
share * exploration + coverage
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def target_share(target)
|
|
109
|
+
target.fetch(:causal_share, target.fetch(:sample_share, target.fetch(:share, 0.0))).to_f
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def missing_priority_speedups(target, history)
|
|
113
|
+
seen = history_for_target(target, history).map { |round| round.fetch(:speedup_pct, round["speedup_pct"]).to_i }
|
|
114
|
+
priority_speedups.count { |speedup| !seen.include?(speedup) }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def priority_speedups
|
|
118
|
+
[0, 25, 50, 90]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def history_for_target(target, history)
|
|
122
|
+
key = target_key(target)
|
|
123
|
+
history.select { |round| target_key(round.fetch(:target, round["target"])) == key }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def target_key(target)
|
|
127
|
+
return ["none"] unless target
|
|
128
|
+
|
|
129
|
+
kind = target.fetch(:kind, target["kind"]).to_s
|
|
130
|
+
if kind == "wait"
|
|
131
|
+
[kind, target.fetch(:name, target["name"]).to_s]
|
|
132
|
+
else
|
|
133
|
+
[kind, File.expand_path(target.fetch(:file, target["file"]).to_s), target.fetch(:line, target["line"]).to_i]
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Corkscrews
|
|
4
|
+
class FirefoxProfile
|
|
5
|
+
MAX_SYNTHETIC_SAMPLES = 10_000
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
def build(aggregate)
|
|
9
|
+
new(aggregate).build
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize(aggregate)
|
|
14
|
+
@aggregate = aggregate
|
|
15
|
+
@strings = []
|
|
16
|
+
@string_indexes = {}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def build
|
|
20
|
+
thread = build_thread
|
|
21
|
+
|
|
22
|
+
{
|
|
23
|
+
"meta" => meta,
|
|
24
|
+
"libs" => [],
|
|
25
|
+
"pages" => [],
|
|
26
|
+
"profileGatheringLog" => [],
|
|
27
|
+
"threads" => [thread]
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def meta
|
|
34
|
+
{
|
|
35
|
+
"version" => 34,
|
|
36
|
+
"product" => "corkscrews",
|
|
37
|
+
"interval" => sample_interval_ms,
|
|
38
|
+
"processType" => 0,
|
|
39
|
+
"platform" => RUBY_PLATFORM,
|
|
40
|
+
"misc" => "Ruby #{RUBY_VERSION}"
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def build_thread
|
|
45
|
+
targets = @aggregate.fetch(:targets, [])
|
|
46
|
+
funcs = build_funcs(targets)
|
|
47
|
+
frames = build_frames(targets)
|
|
48
|
+
stacks = build_stacks(targets)
|
|
49
|
+
samples = build_samples(targets)
|
|
50
|
+
markers = build_markers(targets)
|
|
51
|
+
|
|
52
|
+
{
|
|
53
|
+
"name" => "corkscrews aggregate",
|
|
54
|
+
"processType" => "default",
|
|
55
|
+
"processName" => "corkscrews",
|
|
56
|
+
"isMainThread" => true,
|
|
57
|
+
"pid" => 0,
|
|
58
|
+
"tid" => 0,
|
|
59
|
+
"registerTime" => 0,
|
|
60
|
+
"unregisterTime" => duration_ms,
|
|
61
|
+
"stringArray" => @strings,
|
|
62
|
+
"stringTable" => @strings,
|
|
63
|
+
"resourceTable" => empty_resource_table,
|
|
64
|
+
"funcTable" => funcs,
|
|
65
|
+
"frameTable" => frames,
|
|
66
|
+
"stackTable" => stacks,
|
|
67
|
+
"samples" => samples,
|
|
68
|
+
"markers" => markers,
|
|
69
|
+
"pausedRanges" => []
|
|
70
|
+
}
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def build_funcs(targets)
|
|
74
|
+
{
|
|
75
|
+
"schema" => {
|
|
76
|
+
"name" => 0,
|
|
77
|
+
"isJS" => 1,
|
|
78
|
+
"resource" => 2,
|
|
79
|
+
"relevantForJS" => 3
|
|
80
|
+
},
|
|
81
|
+
"data" => targets.map { |target| [string_index(target_label(target)), false, -1, false] }
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def build_frames(targets)
|
|
86
|
+
{
|
|
87
|
+
"schema" => {
|
|
88
|
+
"address" => 0,
|
|
89
|
+
"category" => 1,
|
|
90
|
+
"subcategory" => 2,
|
|
91
|
+
"func" => 3,
|
|
92
|
+
"innerWindowID" => 4,
|
|
93
|
+
"implementation" => 5,
|
|
94
|
+
"line" => 6,
|
|
95
|
+
"column" => 7
|
|
96
|
+
},
|
|
97
|
+
"data" => targets.each_with_index.map do |target, index|
|
|
98
|
+
[-1, 0, 0, index, 0, nil, frame_line(target), nil]
|
|
99
|
+
end
|
|
100
|
+
}
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def build_stacks(targets)
|
|
104
|
+
{
|
|
105
|
+
"schema" => {
|
|
106
|
+
"prefix" => 0,
|
|
107
|
+
"frame" => 1,
|
|
108
|
+
"category" => 2,
|
|
109
|
+
"subcategory" => 3
|
|
110
|
+
},
|
|
111
|
+
"data" => targets.each_index.map { |index| [nil, index, 0, 0] }
|
|
112
|
+
}
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def build_samples(targets)
|
|
116
|
+
rows = synthetic_sample_rows(targets)
|
|
117
|
+
|
|
118
|
+
{
|
|
119
|
+
"schema" => {
|
|
120
|
+
"stack" => 0,
|
|
121
|
+
"time" => 1,
|
|
122
|
+
"responsiveness" => 2
|
|
123
|
+
},
|
|
124
|
+
"data" => rows
|
|
125
|
+
}
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def build_markers(targets)
|
|
129
|
+
{
|
|
130
|
+
"schema" => {
|
|
131
|
+
"name" => 0,
|
|
132
|
+
"startTime" => 1,
|
|
133
|
+
"endTime" => 2,
|
|
134
|
+
"phase" => 3,
|
|
135
|
+
"category" => 4,
|
|
136
|
+
"data" => 5
|
|
137
|
+
},
|
|
138
|
+
"data" => target_markers(targets) + round_markers
|
|
139
|
+
}
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def target_markers(targets)
|
|
143
|
+
targets.map do |target|
|
|
144
|
+
[
|
|
145
|
+
string_index("#{target[:verdict]}: #{target_label(target)}"),
|
|
146
|
+
0,
|
|
147
|
+
duration_ms,
|
|
148
|
+
1,
|
|
149
|
+
0,
|
|
150
|
+
marker_payload(target)
|
|
151
|
+
]
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def round_markers
|
|
156
|
+
cursor_ms = 0.0
|
|
157
|
+
@aggregate.fetch(:rounds, []).map do |round|
|
|
158
|
+
duration = round_duration_ms(round)
|
|
159
|
+
start_time = cursor_ms
|
|
160
|
+
cursor_ms += duration
|
|
161
|
+
|
|
162
|
+
[
|
|
163
|
+
string_index("round #{round_value(round, :speedup_pct)}% #{target_label(round_target(round))}"),
|
|
164
|
+
rounded_time(start_time),
|
|
165
|
+
rounded_time(cursor_ms),
|
|
166
|
+
1,
|
|
167
|
+
0,
|
|
168
|
+
round_marker_payload(round)
|
|
169
|
+
]
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def synthetic_sample_rows(targets)
|
|
174
|
+
sample_units = sample_units_for(targets)
|
|
175
|
+
return [] if sample_units.empty?
|
|
176
|
+
|
|
177
|
+
total_rows = sample_units.sum
|
|
178
|
+
spacing_ms = total_rows.positive? ? duration_ms / total_rows : sample_interval_ms
|
|
179
|
+
time_ms = 0.0
|
|
180
|
+
|
|
181
|
+
sample_units.each_with_index.flat_map do |count, stack_index|
|
|
182
|
+
Array.new(count) do
|
|
183
|
+
row = [stack_index, rounded_time(time_ms), 0]
|
|
184
|
+
time_ms += spacing_ms
|
|
185
|
+
row
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def sample_units_for(targets)
|
|
191
|
+
counts = targets.map { |target| target[:samples].to_i }
|
|
192
|
+
total = counts.sum
|
|
193
|
+
return [] unless total.positive?
|
|
194
|
+
|
|
195
|
+
if total <= MAX_SYNTHETIC_SAMPLES
|
|
196
|
+
counts
|
|
197
|
+
else
|
|
198
|
+
counts.map do |count|
|
|
199
|
+
count.positive? ? [((count.to_f / total) * MAX_SYNTHETIC_SAMPLES).round, 1].max : 0
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def marker_payload(target)
|
|
205
|
+
{
|
|
206
|
+
"type" => "CorkscrewsTarget",
|
|
207
|
+
"target" => target_label(target),
|
|
208
|
+
"sampleShare" => target[:sample_share].to_f,
|
|
209
|
+
"causalShare" => target[:causal_share].to_f,
|
|
210
|
+
"samples" => target[:samples].to_i,
|
|
211
|
+
"verdict" => target[:verdict].to_s
|
|
212
|
+
}
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def round_marker_payload(round)
|
|
216
|
+
{
|
|
217
|
+
"type" => "CorkscrewsRound",
|
|
218
|
+
"target" => target_label(round_target(round)),
|
|
219
|
+
"speedupPct" => round_value(round, :speedup_pct).to_i,
|
|
220
|
+
"baseline" => !!round_value(round, :baseline),
|
|
221
|
+
"visits" => round_value(round, :visits).to_i,
|
|
222
|
+
"physicalDurationMs" => round_value(round, :physical_duration_ns).to_f / 1_000_000.0,
|
|
223
|
+
"virtualDurationMs" => round_value(round, :virtual_duration_ns).to_f / 1_000_000.0
|
|
224
|
+
}
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def round_duration_ms(round)
|
|
228
|
+
duration = round_value(round, :physical_duration_ns).to_f / 1_000_000.0
|
|
229
|
+
duration.positive? ? duration : sample_interval_ms
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def round_target(round)
|
|
233
|
+
round_value(round, :target) || { kind: "none" }
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def round_value(round, key)
|
|
237
|
+
round[key] || round[key.to_s]
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def empty_resource_table
|
|
241
|
+
{
|
|
242
|
+
"schema" => {
|
|
243
|
+
"type" => 0,
|
|
244
|
+
"name" => 1,
|
|
245
|
+
"host" => 2
|
|
246
|
+
},
|
|
247
|
+
"data" => []
|
|
248
|
+
}
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def target_label(target)
|
|
252
|
+
kind = target[:kind] || target["kind"]
|
|
253
|
+
if kind == "wait"
|
|
254
|
+
"wait:#{target[:name] || target["name"]}"
|
|
255
|
+
elsif kind == "none"
|
|
256
|
+
"none"
|
|
257
|
+
else
|
|
258
|
+
"#{target[:file] || target["file"]}:#{target[:line] || target["line"]}"
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def frame_line(target)
|
|
263
|
+
return nil if (target[:kind] || target["kind"]) == "wait"
|
|
264
|
+
|
|
265
|
+
(target[:line] || target["line"]).to_i
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def sample_interval_ms
|
|
269
|
+
[duration_ms / [@aggregate.fetch(:total_samples, 0).to_i, 1].max, 0.001].max
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def duration_ms
|
|
273
|
+
duration = @aggregate.fetch(:duration_ns, 0).to_f / 1_000_000.0
|
|
274
|
+
duration.positive? ? duration : 1.0
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def rounded_time(value)
|
|
278
|
+
value.round(6)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def string_index(value)
|
|
282
|
+
key = value.to_s
|
|
283
|
+
@string_indexes.fetch(key) do
|
|
284
|
+
@string_indexes[key] = @strings.length
|
|
285
|
+
@strings << key
|
|
286
|
+
@string_indexes[key]
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require "corkscrews/corkscrews"
|
|
5
|
+
rescue LoadError
|
|
6
|
+
begin
|
|
7
|
+
require_relative "../../ext/corkscrews/corkscrews"
|
|
8
|
+
rescue LoadError
|
|
9
|
+
nil
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module Corkscrews
|
|
14
|
+
module Native
|
|
15
|
+
@stamps = ObjectSpace::WeakMap.new
|
|
16
|
+
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
def available?
|
|
20
|
+
defined?(Corkscrews::NativeExtension::AVAILABLE) && Corkscrews::NativeExtension::AVAILABLE
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def settled_now
|
|
24
|
+
return Corkscrews::Delay.settled_now if defined?(Corkscrews::Delay)
|
|
25
|
+
|
|
26
|
+
Thread.current[:corkscrews_settled_stamp_ns] ||= 0
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def stamp_write(object)
|
|
30
|
+
if defined?(Corkscrews::Delay)
|
|
31
|
+
Corkscrews::Delay.stamp_write(object)
|
|
32
|
+
return nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
@stamps[object] = settled_now
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def stamp_inherit(object)
|
|
39
|
+
if defined?(Corkscrews::Delay)
|
|
40
|
+
Corkscrews::Delay.stamp_inherit(object)
|
|
41
|
+
return nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
stamp = @stamps[object].to_i
|
|
45
|
+
current = settled_now
|
|
46
|
+
Thread.current[:corkscrews_settled_stamp_ns] = [current, stamp].max
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def install_hooks!
|
|
50
|
+
Corkscrews::Hooks.install! if defined?(Corkscrews::Hooks)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def start_monitor
|
|
54
|
+
Corkscrews::Monitor.start! if defined?(Corkscrews::Monitor)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def stop_monitor
|
|
58
|
+
Corkscrews::Monitor.stop! if defined?(Corkscrews::Monitor)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def monitor_available?
|
|
62
|
+
defined?(Corkscrews::Monitor) &&
|
|
63
|
+
Corkscrews::Monitor.respond_to?(:available?) &&
|
|
64
|
+
Corkscrews::Monitor.available?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def register_monitor_thread
|
|
68
|
+
Corkscrews::Monitor.register_current_thread! if defined?(Corkscrews::Monitor) && Corkscrews::Monitor.respond_to?(:register_current_thread!)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def clear_monitor_thread
|
|
72
|
+
Corkscrews::Monitor.clear_current_thread! if defined?(Corkscrews::Monitor) && Corkscrews::Monitor.respond_to?(:clear_current_thread!)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def native_snapshot
|
|
76
|
+
return {} unless defined?(Corkscrews::Record)
|
|
77
|
+
|
|
78
|
+
Corkscrews::Record.snapshot
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def native_events
|
|
82
|
+
return [] unless defined?(Corkscrews::Record) && Corkscrews::Record.respond_to?(:events)
|
|
83
|
+
|
|
84
|
+
Corkscrews::Record.events
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def set_experiment(kind:, iseq: 0, line: 0, speedup_pct: 0)
|
|
88
|
+
return unless defined?(Corkscrews::Delay)
|
|
89
|
+
|
|
90
|
+
kind_id = { none: 0, line: 1, wait: 2 }.fetch(kind.to_sym, 0)
|
|
91
|
+
Corkscrews::Delay.set_experiment(kind_id, iseq.to_i, line.to_i, speedup_pct.to_i)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def set_sample_period(ns)
|
|
95
|
+
Corkscrews::Delay.set_sample_period(ns.to_i) if defined?(Corkscrews::Delay)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def clear_experiment
|
|
99
|
+
Corkscrews::Delay.clear_experiment if defined?(Corkscrews::Delay)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def settle_current
|
|
103
|
+
return Corkscrews::Delay.settle_current if defined?(Corkscrews::Delay)
|
|
104
|
+
|
|
105
|
+
0
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "native"
|
|
4
|
+
require_relative "time_source"
|
|
5
|
+
|
|
6
|
+
module Corkscrews
|
|
7
|
+
module Primitives
|
|
8
|
+
# Paper basis: Ousterhout et al. NSDI'15 Section 2.3.1 instruments
|
|
9
|
+
# time spent blocked from the compute thread's perspective; these
|
|
10
|
+
# prepends adapt that blocked-time accounting to Ruby waits.
|
|
11
|
+
# Paper URL: https://www.usenix.org/system/files/conference/nsdi15/nsdi15-paper-ousterhout.pdf
|
|
12
|
+
module MutexStamp
|
|
13
|
+
def unlock
|
|
14
|
+
Corkscrews::Native.stamp_write(self)
|
|
15
|
+
super
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def lock
|
|
19
|
+
started_ns = Corkscrews::TimeSource.monotonic_ns
|
|
20
|
+
result = super
|
|
21
|
+
Corkscrews.record_wait(:lock_wait, Corkscrews::TimeSource.monotonic_ns - started_ns)
|
|
22
|
+
Corkscrews::Native.stamp_inherit(self)
|
|
23
|
+
result
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def synchronize(&block)
|
|
27
|
+
lock
|
|
28
|
+
begin
|
|
29
|
+
block.call
|
|
30
|
+
ensure
|
|
31
|
+
unlock
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
module QueueStamp
|
|
37
|
+
def <<(...)
|
|
38
|
+
push(...)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def push(...)
|
|
42
|
+
Corkscrews::Native.stamp_write(self)
|
|
43
|
+
started_ns = Corkscrews::TimeSource.monotonic_ns
|
|
44
|
+
result = super
|
|
45
|
+
Corkscrews.record_wait(:queue_wait, Corkscrews::TimeSource.monotonic_ns - started_ns)
|
|
46
|
+
Corkscrews::Native.stamp_inherit(self)
|
|
47
|
+
result
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def pop(...)
|
|
51
|
+
Corkscrews::Native.stamp_write(self)
|
|
52
|
+
started_ns = Corkscrews::TimeSource.monotonic_ns
|
|
53
|
+
result = super
|
|
54
|
+
Corkscrews.record_wait(:queue_wait, Corkscrews::TimeSource.monotonic_ns - started_ns)
|
|
55
|
+
Corkscrews::Native.stamp_inherit(self)
|
|
56
|
+
result
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def deq(...)
|
|
60
|
+
pop(...)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
module CondVarStamp
|
|
65
|
+
def signal
|
|
66
|
+
Corkscrews::Native.stamp_write(self)
|
|
67
|
+
super
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def broadcast
|
|
71
|
+
Corkscrews::Native.stamp_write(self)
|
|
72
|
+
super
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def wait(mutex, timeout = nil)
|
|
76
|
+
started_ns = Corkscrews::TimeSource.monotonic_ns
|
|
77
|
+
result = super
|
|
78
|
+
Corkscrews.record_wait(:condition_wait, Corkscrews::TimeSource.monotonic_ns - started_ns)
|
|
79
|
+
Corkscrews::Native.stamp_inherit(self)
|
|
80
|
+
result
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
module KernelWait
|
|
85
|
+
def sleep(...)
|
|
86
|
+
started_ns = Corkscrews::TimeSource.monotonic_ns
|
|
87
|
+
result = super
|
|
88
|
+
Corkscrews.record_wait(:sleep_wait, Corkscrews::TimeSource.monotonic_ns - started_ns)
|
|
89
|
+
result
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
module IOWait
|
|
94
|
+
def read(...)
|
|
95
|
+
measure_io_wait { super }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def readpartial(...)
|
|
99
|
+
measure_io_wait { super }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def write(...)
|
|
103
|
+
measure_io_wait { super }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
def measure_io_wait
|
|
109
|
+
started_ns = Corkscrews::TimeSource.monotonic_ns
|
|
110
|
+
result = yield
|
|
111
|
+
Corkscrews.record_wait(:io_wait, Corkscrews::TimeSource.monotonic_ns - started_ns)
|
|
112
|
+
result
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def self.install!
|
|
117
|
+
Mutex.prepend MutexStamp unless Mutex.ancestors.include?(MutexStamp)
|
|
118
|
+
Thread::Queue.prepend QueueStamp unless Thread::Queue.ancestors.include?(QueueStamp)
|
|
119
|
+
Thread::SizedQueue.prepend QueueStamp unless Thread::SizedQueue.ancestors.include?(QueueStamp)
|
|
120
|
+
Queue.prepend QueueStamp if defined?(Queue) && !Queue.ancestors.include?(QueueStamp)
|
|
121
|
+
SizedQueue.prepend QueueStamp if defined?(SizedQueue) && !SizedQueue.ancestors.include?(QueueStamp)
|
|
122
|
+
Thread::ConditionVariable.prepend CondVarStamp unless Thread::ConditionVariable.ancestors.include?(CondVarStamp)
|
|
123
|
+
Kernel.prepend KernelWait unless Kernel.ancestors.include?(KernelWait)
|
|
124
|
+
IO.prepend IOWait unless IO.ancestors.include?(IOWait)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../corkscrews"
|
|
4
|
+
|
|
5
|
+
module Corkscrews
|
|
6
|
+
class Rack
|
|
7
|
+
def initialize(app, progress_name: :rack_request)
|
|
8
|
+
@app = app
|
|
9
|
+
@progress_name = progress_name
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call(env)
|
|
13
|
+
Corkscrews.latency_begin(@progress_name)
|
|
14
|
+
status, headers, body = @app.call(env)
|
|
15
|
+
Corkscrews.progress(@progress_name)
|
|
16
|
+
[status, headers, ResponseBody.new(body, @progress_name)]
|
|
17
|
+
rescue Exception
|
|
18
|
+
Corkscrews.latency_end(@progress_name)
|
|
19
|
+
raise
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class ResponseBody
|
|
23
|
+
def initialize(body, progress_name)
|
|
24
|
+
@body = body
|
|
25
|
+
@progress_name = progress_name
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def each(&block)
|
|
29
|
+
@body.each(&block)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def close
|
|
33
|
+
@body.close if @body.respond_to?(:close)
|
|
34
|
+
ensure
|
|
35
|
+
Corkscrews.latency_end(@progress_name)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|