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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 47479f224980721d4b7b42338c36b0c6231bbef6ee46e7196f8092578ad58535
4
+ data.tar.gz: 5e9223bb6124b75df1a0b1e2da1cafefe925d70e9df81550f1c882a076f3396c
5
+ SHA512:
6
+ metadata.gz: 81baabd850c9ac7902a999e11fc50c0c8dd8a569aad063542459d89d093d435c3c340c94da76737bde9ab4083b917afd42b420e8fd3bb65eed081052faca903d
7
+ data.tar.gz: '085b8ee57acfd988940f8c8db093bf93f55140f46001dd0278c01b5499ac16b5b9597652e3682e5d8434d5201959ea12d05b2bfffda1b1a5ad186ae22cef0f50'
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yudai Takada
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # Corkscrews
2
+
3
+ Corkscrews is a Ruby causal-profiling toolkit for bottleneck experiments. It
4
+ records progress points, line samples, latency spans, Ruby-level wait targets,
5
+ and validation benchmark results, then reports virtual speedup curves for likely
6
+ bottlenecks.
7
+
8
+ The gem includes a portable Ruby engine and a native CRuby extension for thread
9
+ hooks, GC hooks, delay accounting, sampling handoff, stamp inheritance, native
10
+ monitor ticks, native sample ring flushes, thread-state gauges, adaptive
11
+ line/wait experiment rounds, and runtime snapshots.
12
+
13
+ ## Installation
14
+
15
+ Install the gem and add it to the application's Gemfile by executing:
16
+
17
+ ```sh
18
+ bundle add corkscrews
19
+ ```
20
+
21
+ If Bundler is not being used to manage dependencies, install the gem by
22
+ executing:
23
+
24
+ ```sh
25
+ gem install corkscrews
26
+ ```
27
+
28
+ Corkscrews requires CRuby 3.3 or newer. The native extension is compiled during
29
+ installation when the required CRuby APIs are available; otherwise the Ruby
30
+ engine remains the portable fallback.
31
+
32
+ ## Usage
33
+
34
+ Profile an existing Ruby command with the CLI:
35
+
36
+ ```sh
37
+ corkscrews run --repeat 3 --targets both --output run.corkscrews.ndjson -- ruby app.rb
38
+ ```
39
+
40
+ Generate reports from the recorded NDJSON file:
41
+
42
+ ```sh
43
+ corkscrews report --html report.html --firefox profile.json run.corkscrews.ndjson
44
+ ```
45
+
46
+ `--firefox` writes an aggregate Firefox Profiler JSON with thread, sample,
47
+ stack, frame, function, marker, and string tables. It includes target summary
48
+ markers and round timeline markers derived from Corkscrews' NDJSON records.
49
+
50
+ ### Progress Points
51
+
52
+ Application code can provide throughput and latency points:
53
+
54
+ ```ruby
55
+ require "corkscrews"
56
+
57
+ Corkscrews.latency_begin(:request)
58
+ do_work
59
+ Corkscrews.progress(:request)
60
+ Corkscrews.latency_end(:request)
61
+ ```
62
+
63
+ `--targets lines` records line targets. `--targets waits` or `--targets both`
64
+ also enables Ruby-level wrappers for `Mutex`, `Thread::Queue`,
65
+ `Thread::SizedQueue`, `Thread::ConditionVariable`, `Kernel.sleep`, and `IO`.
66
+
67
+ ### Native Signal Source
68
+
69
+ The default signal source is Ruby's `setitimer` path. The native monitor can
70
+ deliver `SIGPROF` to the registered profiling thread by setting
71
+ `CORKSCREWS_NATIVE_SIGNALS=1`; this path is recorded in native counters and kept
72
+ opt-in because direct pthread signal delivery can conflict with platform signal
73
+ handling in embedded Ruby processes.
74
+
75
+ ## Validation
76
+
77
+ Run the bundled validation harness:
78
+
79
+ ```sh
80
+ corkscrews validate --quick
81
+ corkscrews validate
82
+ ```
83
+
84
+ The validation harness includes benchmark manifests for serial CPU hotspots,
85
+ false hotspots under concurrency, I/O wait, lock contention, GVL contention, GC
86
+ pressure, queue pipeline behavior, and native-call attribution smoke coverage.
87
+
88
+ ## Development
89
+
90
+ After checking out the repo, install dependencies:
91
+
92
+ ```sh
93
+ bundle install
94
+ ```
95
+
96
+ Compile the native extension and run the test suite:
97
+
98
+ ```sh
99
+ bundle exec rake compile
100
+ bundle exec rake
101
+ ```
102
+
103
+ Install the gem locally from the checkout:
104
+
105
+ ```sh
106
+ bundle exec rake install
107
+ ```
108
+
109
+ Run validation locally:
110
+
111
+ ```sh
112
+ ruby -Ilib exe/corkscrews validate --quick
113
+ ruby -Ilib exe/corkscrews validate
114
+ python3 validate/harness/stats_check.py
115
+ ```
116
+
117
+ For local CLI testing without installing the gem, use:
118
+
119
+ ```sh
120
+ ruby -Ilib exe/corkscrews run --repeat 1 --output run.corkscrews.ndjson -- ruby app.rb
121
+ ```
122
+
123
+ ## Contributing
124
+
125
+ Bug reports and pull requests are welcome. Please include a focused reproduction
126
+ or validation benchmark when changing profiling behavior.
127
+
128
+ ## License
129
+
130
+ The gem is available as open source under the terms of the MIT License.
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :compile do
9
+ sh "cd ext/corkscrews && ruby extconf.rb && make"
10
+ end
11
+
12
+ task :validate do
13
+ sh "ruby -Ilib exe/corkscrews validate --quick"
14
+ end
15
+
16
+ task :validate_full do
17
+ sh "ruby -Ilib exe/corkscrews validate"
18
+ end
19
+
20
+ task default: :spec
data/exe/corkscrews ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ lib = File.expand_path("../lib", __dir__)
5
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
+
7
+ require "corkscrews/cli"
8
+
9
+ exit Corkscrews::CLI.new(ARGV).run
@@ -0,0 +1,24 @@
1
+ #include "ruby.h"
2
+ #include "corkscrews_native.h"
3
+
4
+ cs_global_t cs_global;
5
+
6
+ void Init_corkscrews_hooks(VALUE rb_mCorkscrews);
7
+ void Init_corkscrews_sampler(VALUE rb_mCorkscrews);
8
+ void Init_corkscrews_delay(VALUE rb_mCorkscrews);
9
+ void Init_corkscrews_record(VALUE rb_mCorkscrews);
10
+ void Init_corkscrews_monitor(VALUE rb_mCorkscrews);
11
+
12
+ void
13
+ Init_corkscrews(void)
14
+ {
15
+ VALUE rb_mCorkscrews = rb_define_module("Corkscrews");
16
+ VALUE rb_mNativeExtension = rb_define_module_under(rb_mCorkscrews, "NativeExtension");
17
+ atomic_store(&cs_global.sample_period_ns, 1000000ULL);
18
+ rb_define_const(rb_mNativeExtension, "AVAILABLE", Qtrue);
19
+ Init_corkscrews_hooks(rb_mCorkscrews);
20
+ Init_corkscrews_sampler(rb_mCorkscrews);
21
+ Init_corkscrews_delay(rb_mCorkscrews);
22
+ Init_corkscrews_record(rb_mCorkscrews);
23
+ Init_corkscrews_monitor(rb_mCorkscrews);
24
+ }
@@ -0,0 +1,85 @@
1
+ #ifndef CORKSCREWS_NATIVE_H
2
+ #define CORKSCREWS_NATIVE_H
3
+
4
+ #include "ruby.h"
5
+ #include "ruby/debug.h"
6
+ #include "ruby/thread.h"
7
+ #include "ruby/internal/event.h"
8
+
9
+ #include <stdatomic.h>
10
+ #include <stdint.h>
11
+
12
+ #define CS_RECORD_RING_SIZE 8192
13
+
14
+ typedef struct {
15
+ _Atomic uint64_t global_delay_ns;
16
+ _Atomic uint64_t experiment_id;
17
+ _Atomic uintptr_t target_iseq;
18
+ _Atomic uint32_t target_line;
19
+ _Atomic uint32_t target_kind;
20
+ _Atomic uint32_t speedup_pct;
21
+ _Atomic uint64_t sample_period_ns;
22
+ _Atomic uint64_t vclock_credit_ns;
23
+ _Atomic uint64_t samples;
24
+ _Atomic uint64_t target_hits;
25
+ _Atomic uint64_t thread_events_started;
26
+ _Atomic uint64_t thread_events_ready;
27
+ _Atomic uint64_t thread_events_resumed;
28
+ _Atomic uint64_t thread_events_suspended;
29
+ _Atomic uint64_t thread_events_exited;
30
+ _Atomic uint64_t thread_state_running;
31
+ _Atomic uint64_t thread_state_ready;
32
+ _Atomic uint64_t thread_state_suspended;
33
+ _Atomic uint64_t thread_state_dead;
34
+ _Atomic uint64_t thread_live_count;
35
+ _Atomic uint64_t thread_max_live_count;
36
+ _Atomic uint64_t gc_enter_count;
37
+ _Atomic uint64_t gc_exit_count;
38
+ _Atomic uint64_t gc_pause_ns;
39
+ _Atomic uint64_t debt_settlements;
40
+ _Atomic uint64_t debt_settled_ns;
41
+ _Atomic uint64_t monitor_ticks;
42
+ _Atomic uint64_t monitor_signals;
43
+ _Atomic uint64_t monitor_signal_failures;
44
+ _Atomic uint64_t monitor_start_count;
45
+ _Atomic uint64_t monitor_stop_count;
46
+ _Atomic uint8_t monitor_running;
47
+ _Atomic uint8_t monitor_target_registered;
48
+ } cs_global_t;
49
+
50
+ typedef struct {
51
+ _Atomic uint64_t local_delay_ns;
52
+ _Atomic uint64_t inherited_stamp;
53
+ _Atomic uint8_t state;
54
+ _Atomic uint8_t state_initialized;
55
+ _Atomic uint8_t settle_requested;
56
+ _Atomic uint64_t suspended_since_id;
57
+ } cs_thread_t;
58
+
59
+ typedef struct {
60
+ uint64_t sequence;
61
+ uintptr_t site_key;
62
+ uint32_t line;
63
+ uint32_t target_kind;
64
+ uint32_t speedup_pct;
65
+ uint64_t experiment_id;
66
+ uint64_t delay_ns;
67
+ uint8_t target_hit;
68
+ } cs_record_entry_t;
69
+
70
+ extern cs_global_t cs_global;
71
+
72
+ cs_thread_t *cs_thread_data_for(VALUE thread, int create);
73
+ cs_thread_t *cs_current_thread_data(void);
74
+ uint64_t cs_settled_now_for_current_thread(void);
75
+ void cs_stamp_write(VALUE object);
76
+ void cs_stamp_inherit(VALUE object);
77
+ void cs_delay_set_experiment(uint32_t target_kind, uintptr_t target_iseq, uint32_t target_line, uint32_t speedup_pct);
78
+ void cs_delay_clear_experiment(void);
79
+ void cs_delay_issue_for_current(uint64_t delay_ns);
80
+ uint64_t cs_delay_settle_current(void);
81
+ void cs_delay_request_settle(void);
82
+ VALUE cs_record_snapshot(VALUE self);
83
+ void cs_record_line_sample(uintptr_t site_key, uint32_t line, uint8_t target_hit, uint64_t delay_ns);
84
+
85
+ #endif
@@ -0,0 +1,259 @@
1
+ #include "corkscrews_native.h"
2
+
3
+ #include <stdlib.h>
4
+ #include <time.h>
5
+
6
+ static VALUE stamp_table;
7
+ static ID id_aref;
8
+ static ID id_aset;
9
+ static rb_postponed_job_handle_t settle_job_handle = POSTPONED_JOB_HANDLE_INVALID;
10
+ static rb_internal_thread_specific_key_t thread_key = -1;
11
+
12
+ static void *
13
+ nogvl_sleep(void *ptr)
14
+ {
15
+ uint64_t ns = *((uint64_t *)ptr);
16
+ struct timespec ts;
17
+ ts.tv_sec = (time_t)(ns / 1000000000ULL);
18
+ ts.tv_nsec = (long)(ns % 1000000000ULL);
19
+ nanosleep(&ts, NULL);
20
+ return NULL;
21
+ }
22
+
23
+ cs_thread_t *
24
+ cs_thread_data_for(VALUE thread, int create)
25
+ {
26
+ if (thread_key == -1) {
27
+ rb_raise(rb_eRuntimeError, "corkscrews delay storage is not initialized");
28
+ }
29
+
30
+ cs_thread_t *data = rb_internal_thread_specific_get(thread, thread_key);
31
+ if (!data && create) {
32
+ data = calloc(1, sizeof(cs_thread_t));
33
+ if (!data) rb_memerror();
34
+ atomic_store(&data->local_delay_ns, atomic_load(&cs_global.global_delay_ns));
35
+ atomic_store(&data->inherited_stamp, 0);
36
+ atomic_store(&data->state, 3);
37
+ atomic_store(&data->state_initialized, 0);
38
+ atomic_store(&data->settle_requested, 0);
39
+ atomic_store(&data->suspended_since_id, 0);
40
+ rb_internal_thread_specific_set(thread, thread_key, data);
41
+ }
42
+ return data;
43
+ }
44
+
45
+ cs_thread_t *
46
+ cs_current_thread_data(void)
47
+ {
48
+ return cs_thread_data_for(rb_thread_current(), 1);
49
+ }
50
+
51
+ uint64_t
52
+ cs_settled_now_for_current_thread(void)
53
+ {
54
+ cs_thread_t *data = cs_current_thread_data();
55
+ uint64_t local = atomic_load(&data->local_delay_ns);
56
+ uint64_t inherited = atomic_load(&data->inherited_stamp);
57
+ return local > inherited ? local : inherited;
58
+ }
59
+
60
+ void
61
+ cs_delay_set_experiment(uint32_t target_kind, uintptr_t target_iseq, uint32_t target_line, uint32_t speedup_pct)
62
+ {
63
+ atomic_fetch_add(&cs_global.experiment_id, 1);
64
+ atomic_store(&cs_global.target_kind, target_kind);
65
+ atomic_store(&cs_global.target_iseq, target_iseq);
66
+ atomic_store(&cs_global.target_line, target_line);
67
+ atomic_store(&cs_global.speedup_pct, speedup_pct);
68
+ }
69
+
70
+ void
71
+ cs_delay_clear_experiment(void)
72
+ {
73
+ atomic_store(&cs_global.target_kind, 0);
74
+ }
75
+
76
+ void
77
+ cs_delay_issue_for_current(uint64_t delay_ns)
78
+ {
79
+ /* Paper basis: Coz SOSP'15 Section 3.4 / Figure 3: a target hit
80
+ * virtually speeds that code by charging equivalent delay to other
81
+ * execution while crediting the thread that hit the target.
82
+ * Paper URL: https://arxiv.org/abs/1608.03676 */
83
+ cs_thread_t *self = cs_current_thread_data();
84
+ atomic_fetch_add(&cs_global.global_delay_ns, delay_ns);
85
+ atomic_fetch_add(&self->local_delay_ns, delay_ns);
86
+ }
87
+
88
+ uint64_t
89
+ cs_delay_settle_current(void)
90
+ {
91
+ /* Paper basis: Coz SOSP'15 Section 3.4 inserts pauses for virtual
92
+ * speedup. The CRuby adaptation pays that debt without holding GVL.
93
+ * Paper URL: https://arxiv.org/abs/1608.03676 */
94
+ cs_thread_t *self = cs_current_thread_data();
95
+ uint64_t global = atomic_load(&cs_global.global_delay_ns);
96
+ uint64_t local = atomic_load(&self->local_delay_ns);
97
+ uint64_t inherited = atomic_load(&self->inherited_stamp);
98
+ uint64_t settled = local > inherited ? local : inherited;
99
+ if (global <= settled) {
100
+ atomic_store(&self->settle_requested, 0);
101
+ return 0;
102
+ }
103
+
104
+ uint64_t debt = global - settled;
105
+ rb_thread_call_without_gvl(nogvl_sleep, &debt, RUBY_UBF_IO, NULL);
106
+ atomic_store(&self->local_delay_ns, global);
107
+ atomic_store(&self->settle_requested, 0);
108
+ atomic_fetch_add(&cs_global.debt_settlements, 1);
109
+ atomic_fetch_add(&cs_global.debt_settled_ns, debt);
110
+ return debt;
111
+ }
112
+
113
+ static void
114
+ settle_postponed_job(void *arg)
115
+ {
116
+ (void)arg;
117
+ cs_delay_settle_current();
118
+ }
119
+
120
+ void
121
+ cs_delay_request_settle(void)
122
+ {
123
+ cs_thread_t *self = cs_current_thread_data();
124
+ if (atomic_exchange(&self->settle_requested, 1)) return;
125
+ if (settle_job_handle != POSTPONED_JOB_HANDLE_INVALID) {
126
+ rb_postponed_job_trigger(settle_job_handle);
127
+ }
128
+ }
129
+
130
+ void
131
+ cs_stamp_write(VALUE object)
132
+ {
133
+ /* Paper basis: Coz SOSP'15 Section 3.4 uses delayed execution as
134
+ * the virtual-speedup mechanism. Stamp inheritance is the CRuby
135
+ * synchronization adaptation that prevents double-paying delay.
136
+ * Paper URL: https://arxiv.org/abs/1608.03676 */
137
+ rb_funcall(stamp_table, id_aset, 2, object, ULL2NUM(cs_settled_now_for_current_thread()));
138
+ }
139
+
140
+ void
141
+ cs_stamp_inherit(VALUE object)
142
+ {
143
+ VALUE stored = rb_funcall(stamp_table, id_aref, 1, object);
144
+ if (NIL_P(stored)) return;
145
+
146
+ cs_thread_t *self = cs_current_thread_data();
147
+ uint64_t stamp = NUM2ULL(stored);
148
+ uint64_t inherited = atomic_load(&self->inherited_stamp);
149
+ if (stamp > inherited) {
150
+ atomic_store(&self->inherited_stamp, stamp);
151
+ }
152
+ }
153
+
154
+ static VALUE
155
+ delay_available_p(VALUE self)
156
+ {
157
+ (void)self;
158
+ return Qtrue;
159
+ }
160
+
161
+ static VALUE
162
+ delay_set_experiment(VALUE self, VALUE kind, VALUE iseq, VALUE line, VALUE speedup)
163
+ {
164
+ (void)self;
165
+ cs_delay_set_experiment(NUM2UINT(kind), (uintptr_t)NUM2ULL(iseq), NUM2UINT(line), NUM2UINT(speedup));
166
+ return Qnil;
167
+ }
168
+
169
+ static VALUE
170
+ delay_set_sample_period(VALUE self, VALUE period_ns)
171
+ {
172
+ (void)self;
173
+ atomic_store(&cs_global.sample_period_ns, NUM2ULL(period_ns));
174
+ return Qnil;
175
+ }
176
+
177
+ static VALUE
178
+ delay_clear_experiment(VALUE self)
179
+ {
180
+ (void)self;
181
+ cs_delay_clear_experiment();
182
+ return Qnil;
183
+ }
184
+
185
+ static VALUE
186
+ delay_settle_current(VALUE self)
187
+ {
188
+ (void)self;
189
+ return ULL2NUM(cs_delay_settle_current());
190
+ }
191
+
192
+ static VALUE
193
+ delay_request_settle(VALUE self)
194
+ {
195
+ (void)self;
196
+ cs_delay_request_settle();
197
+ return Qnil;
198
+ }
199
+
200
+ static VALUE
201
+ delay_debt_current(VALUE self)
202
+ {
203
+ (void)self;
204
+ uint64_t global = atomic_load(&cs_global.global_delay_ns);
205
+ uint64_t settled = cs_settled_now_for_current_thread();
206
+ return ULL2NUM(global > settled ? global - settled : 0);
207
+ }
208
+
209
+ static VALUE
210
+ delay_settled_now(VALUE self)
211
+ {
212
+ (void)self;
213
+ return ULL2NUM(cs_settled_now_for_current_thread());
214
+ }
215
+
216
+ static VALUE
217
+ delay_stamp_write(VALUE self, VALUE object)
218
+ {
219
+ (void)self;
220
+ cs_stamp_write(object);
221
+ return Qnil;
222
+ }
223
+
224
+ static VALUE
225
+ delay_stamp_inherit(VALUE self, VALUE object)
226
+ {
227
+ (void)self;
228
+ cs_stamp_inherit(object);
229
+ return Qnil;
230
+ }
231
+
232
+ void
233
+ Init_corkscrews_delay(VALUE rb_mCorkscrews)
234
+ {
235
+ VALUE rb_mDelay = rb_define_module_under(rb_mCorkscrews, "Delay");
236
+ VALUE rb_mObjectSpace = rb_const_get(rb_cObject, rb_intern("ObjectSpace"));
237
+ VALUE rb_cWeakMap = rb_const_get(rb_mObjectSpace, rb_intern("WeakMap"));
238
+
239
+ if (thread_key == -1) {
240
+ thread_key = rb_internal_thread_specific_key_create();
241
+ }
242
+
243
+ id_aref = rb_intern("[]");
244
+ id_aset = rb_intern("[]=");
245
+ stamp_table = rb_funcall(rb_cWeakMap, rb_intern("new"), 0);
246
+ rb_global_variable(&stamp_table);
247
+ settle_job_handle = rb_postponed_job_preregister(0, settle_postponed_job, NULL);
248
+
249
+ rb_define_singleton_method(rb_mDelay, "available?", delay_available_p, 0);
250
+ rb_define_singleton_method(rb_mDelay, "set_experiment", delay_set_experiment, 4);
251
+ rb_define_singleton_method(rb_mDelay, "set_sample_period", delay_set_sample_period, 1);
252
+ rb_define_singleton_method(rb_mDelay, "clear_experiment", delay_clear_experiment, 0);
253
+ rb_define_singleton_method(rb_mDelay, "settle_current", delay_settle_current, 0);
254
+ rb_define_singleton_method(rb_mDelay, "request_settle", delay_request_settle, 0);
255
+ rb_define_singleton_method(rb_mDelay, "debt_current", delay_debt_current, 0);
256
+ rb_define_singleton_method(rb_mDelay, "settled_now", delay_settled_now, 0);
257
+ rb_define_singleton_method(rb_mDelay, "stamp_write", delay_stamp_write, 1);
258
+ rb_define_singleton_method(rb_mDelay, "stamp_inherit", delay_stamp_inherit, 1);
259
+ }
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mkmf"
4
+ require "rubygems"
5
+
6
+ required_version = Gem::Version.new("3.3")
7
+ current_version = Gem::Version.new(RUBY_VERSION)
8
+ abort "corkscrews native extension requires Ruby >= #{required_version}" if current_version < required_version
9
+
10
+ required = %w[
11
+ rb_profile_frames
12
+ rb_thread_call_without_gvl
13
+ rb_add_event_hook
14
+ rb_internal_thread_add_event_hook
15
+ rb_internal_thread_specific_key_create
16
+ rb_internal_thread_specific_get
17
+ rb_internal_thread_specific_set
18
+ rb_postponed_job_preregister
19
+ rb_postponed_job_trigger
20
+ ]
21
+
22
+ missing_required = required.reject { |name| have_func(name) }
23
+ abort "missing required Ruby C APIs: #{missing_required.join(", ")}" unless missing_required.empty?
24
+
25
+ create_makefile("corkscrews/corkscrews")