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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +130 -0
  4. data/Rakefile +20 -0
  5. data/exe/corkscrews +9 -0
  6. data/ext/corkscrews/corkscrews.c +24 -0
  7. data/ext/corkscrews/corkscrews_native.h +85 -0
  8. data/ext/corkscrews/delay.c +259 -0
  9. data/ext/corkscrews/extconf.rb +25 -0
  10. data/ext/corkscrews/hooks.c +156 -0
  11. data/ext/corkscrews/monitor.c +117 -0
  12. data/ext/corkscrews/record.c +109 -0
  13. data/ext/corkscrews/sampler.c +99 -0
  14. data/lib/corkscrews/analysis.rb +452 -0
  15. data/lib/corkscrews/boot.rb +25 -0
  16. data/lib/corkscrews/cli.rb +159 -0
  17. data/lib/corkscrews/controller.rb +137 -0
  18. data/lib/corkscrews/firefox_profile.rb +290 -0
  19. data/lib/corkscrews/native.rb +108 -0
  20. data/lib/corkscrews/primitives.rb +127 -0
  21. data/lib/corkscrews/rack.rb +39 -0
  22. data/lib/corkscrews/recorder.rb +903 -0
  23. data/lib/corkscrews/report.rb +241 -0
  24. data/lib/corkscrews/statistics.rb +112 -0
  25. data/lib/corkscrews/time_source.rb +11 -0
  26. data/lib/corkscrews/validate.rb +348 -0
  27. data/lib/corkscrews/version.rb +5 -0
  28. data/lib/corkscrews.rb +43 -0
  29. data/validate/benchmarks/b01_serial_hotspot/base.rb +28 -0
  30. data/validate/benchmarks/b01_serial_hotspot/manifest.yml +23 -0
  31. data/validate/benchmarks/b01_serial_hotspot/optimized.rb +28 -0
  32. data/validate/benchmarks/b02_false_hotspot/base.rb +43 -0
  33. data/validate/benchmarks/b02_false_hotspot/manifest.yml +19 -0
  34. data/validate/benchmarks/b02_false_hotspot/optimized.rb +43 -0
  35. data/validate/benchmarks/b03_io_wait/base.rb +29 -0
  36. data/validate/benchmarks/b03_io_wait/manifest.yml +16 -0
  37. data/validate/benchmarks/b03_io_wait/optimized.rb +29 -0
  38. data/validate/benchmarks/b04_lock_contention/base.rb +30 -0
  39. data/validate/benchmarks/b04_lock_contention/manifest.yml +19 -0
  40. data/validate/benchmarks/b04_lock_contention/optimized.rb +30 -0
  41. data/validate/benchmarks/b05_gvl_contention/base.rb +24 -0
  42. data/validate/benchmarks/b05_gvl_contention/manifest.yml +17 -0
  43. data/validate/benchmarks/b05_gvl_contention/optimized.rb +24 -0
  44. data/validate/benchmarks/b06_gc_pressure/base.rb +16 -0
  45. data/validate/benchmarks/b06_gc_pressure/manifest.yml +16 -0
  46. data/validate/benchmarks/b06_gc_pressure/optimized.rb +17 -0
  47. data/validate/benchmarks/b07_pipeline/base.rb +34 -0
  48. data/validate/benchmarks/b07_pipeline/manifest.yml +17 -0
  49. data/validate/benchmarks/b07_pipeline/optimized.rb +34 -0
  50. data/validate/benchmarks/b08_native_attribution/base.rb +18 -0
  51. data/validate/benchmarks/b08_native_attribution/manifest.yml +18 -0
  52. data/validate/benchmarks/b08_native_attribution/optimized.rb +18 -0
  53. data/validate/benchmarks/support.rb +29 -0
  54. data/validate/harness/run_all.rb +16 -0
  55. data/validate/harness/stats_check.py +27 -0
  56. data/validate/harness/t1.rb +15 -0
  57. data/validate/harness/t4.rb +15 -0
  58. data/validate/harness/t5.rb +15 -0
  59. 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