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
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,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")
|