Skip to content

Commit afc09e2

Browse files
committed
Initial commit
0 parents  commit afc09e2

64 files changed

Lines changed: 5050 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
workflow_dispatch:
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
test:
14+
name: Ruby ${{ matrix.ruby }}
15+
runs-on: macos-latest
16+
strategy:
17+
fail-fast: false
18+
matrix:
19+
ruby:
20+
- "3.3"
21+
- "3.4"
22+
- "4.0"
23+
- "head"
24+
steps:
25+
- uses: actions/checkout@v4
26+
with:
27+
persist-credentials: false
28+
- uses: ruby/setup-ruby@v1
29+
with:
30+
ruby-version: ${{ matrix.ruby }}
31+
bundler-cache: true
32+
- name: Build native extension
33+
run: bundle exec rake compile
34+
- name: Run specs
35+
run: bundle exec rake
36+
37+
stats-smoke:
38+
name: Stats smoke
39+
runs-on: macos-latest
40+
steps:
41+
- uses: actions/checkout@v4
42+
with:
43+
persist-credentials: false
44+
- name: Run independent stats smoke
45+
run: python3 validate/harness/stats_check.py
46+
47+
full-validation:
48+
if: github.event_name == 'workflow_dispatch'
49+
name: Full validation Ruby ${{ matrix.ruby }}
50+
runs-on: macos-latest
51+
strategy:
52+
fail-fast: false
53+
matrix:
54+
ruby:
55+
- "3.3"
56+
- "3.4"
57+
- "4.0"
58+
- "head"
59+
steps:
60+
- uses: actions/checkout@v4
61+
with:
62+
persist-credentials: false
63+
- uses: ruby/setup-ruby@v1
64+
with:
65+
ruby-version: ${{ matrix.ruby }}
66+
bundler-cache: true
67+
- name: Build native extension
68+
run: bundle exec rake compile
69+
- name: Run quick validation
70+
run: ruby -Ilib exe/corkscrew validate --quick
71+
- name: Run full validation
72+
run: ruby -Ilib exe/corkscrew validate

.gitignore

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/.bundle/
2+
/.yardoc
3+
/_yardoc/
4+
/coverage/
5+
/doc/
6+
/pkg/
7+
/spec/reports/
8+
/tmp/
9+
/ext/corkscrew/Makefile
10+
/ext/corkscrew/*.o
11+
/ext/corkscrew/*.bundle
12+
/ext/corkscrew/*.bundle.dSYM/
13+
/ext/corkscrew/mkmf.log
14+
15+
# rspec failure tracking
16+
.rspec_status
17+
Gemfile.lock

.rspec

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
--format documentation
2+
--color
3+
--require spec_helper

Gemfile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# frozen_string_literal: true
2+
3+
source "https://rubygems.org"
4+
5+
gemspec
6+
7+
group :development, :test do
8+
gem "rake"
9+
gem "rspec"
10+
end

LICENSE.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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.

README.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Corkscrew
2+
3+
Corkscrew 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 corkscrew
19+
```
20+
21+
If Bundler is not being used to manage dependencies, install the gem by
22+
executing:
23+
24+
```sh
25+
gem install corkscrew
26+
```
27+
28+
Corkscrew 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+
corkscrew run --repeat 3 --targets both --output run.cork.ndjson -- ruby app.rb
38+
```
39+
40+
Generate reports from the recorded NDJSON file:
41+
42+
```sh
43+
corkscrew report --html report.html --firefox profile.json run.cork.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 Corkscrew's NDJSON records.
49+
50+
### Progress Points
51+
52+
Application code can provide throughput and latency points:
53+
54+
```ruby
55+
require "corkscrew"
56+
57+
Corkscrew.latency_begin(:request)
58+
do_work
59+
Corkscrew.progress(:request)
60+
Corkscrew.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+
`CORKSCREW_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+
corkscrew validate --quick
81+
corkscrew 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/corkscrew validate --quick
113+
ruby -Ilib exe/corkscrew 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/corkscrew run --repeat 1 --output run.cork.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.

Rakefile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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/corkscrew && ruby extconf.rb && make"
10+
end
11+
12+
task :validate do
13+
sh "ruby -Ilib exe/corkscrew validate --quick"
14+
end
15+
16+
task :validate_full do
17+
sh "ruby -Ilib exe/corkscrew validate"
18+
end
19+
20+
task default: :spec

corkscrew.gemspec

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "lib/corkscrew/version"
4+
5+
Gem::Specification.new do |spec|
6+
spec.name = "corkscrew"
7+
spec.version = Corkscrew::VERSION
8+
spec.authors = ["Yudai Takada"]
9+
spec.email = ["t.yudai92@gmail.com"]
10+
11+
spec.summary = "Causal profiling tools for Ruby bottleneck experiments."
12+
spec.description = "corkscrew records progress points, line samples, wait targets, and validation benchmarks for Ruby causal profiling experiments."
13+
spec.homepage = "https://github.com/ydah/corkscrew"
14+
spec.license = "MIT"
15+
spec.required_ruby_version = ">= 3.3.0"
16+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
17+
spec.metadata["homepage_uri"] = spec.homepage
18+
spec.metadata["source_code_uri"] = spec.homepage
19+
spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/issues"
20+
21+
gemspec = File.basename(__FILE__)
22+
spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls|
23+
ls.readlines("\x0", chomp: true).reject do |f|
24+
(f == gemspec) ||
25+
f.start_with?(*%w[bin/ Gemfile .gitignore .rspec spec/ .github/])
26+
end
27+
end
28+
spec.bindir = "exe"
29+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30+
spec.extensions = ["ext/corkscrew/extconf.rb"]
31+
spec.require_paths = ["lib"]
32+
33+
spec.add_dependency "fiddle"
34+
end

exe/corkscrew

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 "corkscrew/cli"
8+
9+
exit Corkscrew::CLI.new(ARGV).run

ext/corkscrew/corkscrew.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#include "ruby.h"
2+
#include "corkscrew_native.h"
3+
4+
cs_global_t cs_global;
5+
6+
void Init_corkscrew_hooks(VALUE rb_mCorkscrew);
7+
void Init_corkscrew_sampler(VALUE rb_mCorkscrew);
8+
void Init_corkscrew_delay(VALUE rb_mCorkscrew);
9+
void Init_corkscrew_record(VALUE rb_mCorkscrew);
10+
void Init_corkscrew_monitor(VALUE rb_mCorkscrew);
11+
12+
void
13+
Init_corkscrew(void)
14+
{
15+
VALUE rb_mCorkscrew = rb_define_module("Corkscrew");
16+
VALUE rb_mNativeExtension = rb_define_module_under(rb_mCorkscrew, "NativeExtension");
17+
atomic_store(&cs_global.sample_period_ns, 1000000ULL);
18+
rb_define_const(rb_mNativeExtension, "AVAILABLE", Qtrue);
19+
Init_corkscrew_hooks(rb_mCorkscrew);
20+
Init_corkscrew_sampler(rb_mCorkscrew);
21+
Init_corkscrew_delay(rb_mCorkscrew);
22+
Init_corkscrew_record(rb_mCorkscrew);
23+
Init_corkscrew_monitor(rb_mCorkscrew);
24+
}

0 commit comments

Comments
 (0)