Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
e94699d
Show line number
genya0407 Sep 20, 2021
02b44b3
Switch hash algorithm from MD5 to SHA256
stanhu May 3, 2022
6911b61
Add Ruby 3.1 to CI
byroot Jul 12, 2022
179c5ac
Use postponed jobs if YJIT is enabled.
byroot Jul 12, 2022
ec245ef
Merge pull request #180 from Shopify/yjit-head-compat
tenderlove Jul 18, 2022
ac14b1f
Merge pull request #181 from Shopify/update-rubies
tenderlove Jul 18, 2022
6bbabf1
bumping version
tenderlove Jul 26, 2022
36a4711
Fix mixed declarations and code warnings on Ruby <= 2.6
eregon Aug 13, 2022
8245c33
Support installing the gem on TruffleRuby
eregon Aug 13, 2022
9f78db5
Use postponed jobs on Ruby 2.x
byroot Aug 22, 2022
c98d61a
Merge pull request #186 from Shopify/fix-2.x-compat
tenderlove Aug 22, 2022
00128bb
Merge pull request #184 from eregon/truffleruby
tenderlove Aug 22, 2022
d5f2f2b
Merge pull request #177 from stanhu/sh-use-sha256
tenderlove Aug 22, 2022
f692dbc
Merge pull request #168 from genya0407/feature/show-linenum-in-d3-fla…
tenderlove Aug 22, 2022
8679467
bump version
tenderlove Aug 22, 2022
408d04e
`stackprof run` CLI
byroot Sep 14, 2022
1784bed
Merge pull request #187 from Shopify/cli-run
tenderlove Sep 14, 2022
9115838
Correct parsing of opts for bin/rubocop
nate-at-gusto Sep 19, 2022
bbf6541
bump version
tenderlove Oct 13, 2022
998374a
Merge pull request #188 from nate-at-gusto/correct-bin-rubocop-opts
tmm1 Oct 24, 2022
67ec8fe
Forward SIGALRM to original thread
jhawthorn May 23, 2022
1b50bc1
Merge pull request #192 from jhawthorn/thread_hacks
tenderlove Nov 29, 2022
86b9cc6
bumping version
tenderlove Nov 29, 2022
cfe2a6e
Use postponed jobs on Ruby 3.2.0
byroot Jan 20, 2023
f7ba37b
Fix printing CLI banner
fatkodima Feb 21, 2023
58312fb
Merge pull request #197 from fatkodima/fix-cli-banner
tenderlove Feb 22, 2023
52d1df6
Merge pull request #196 from Shopify/3.2.0-postponed-jobs
tenderlove Feb 22, 2023
455d76a
Check that VM is running in sigaction
ianks Mar 16, 2023
eb1db9f
Merge pull request #200 from ianks/ruby-signal-safety
tenderlove Mar 20, 2023
f27ee5b
bump version
tenderlove Mar 20, 2023
c1f12ca
Mark frames_buffer
peterzhu2118 Apr 5, 2023
846cd22
Merge pull request #202 from Shopify/pz-frames-buffer-mark
tenderlove Apr 6, 2023
e263746
bump version
tenderlove Apr 6, 2023
0cee272
Add Ruby 3.2 to CI
stanhu May 3, 2023
4f4e783
Restore pre-C99 compatibility.
byroot May 9, 2023
97ed54c
Merge pull request #211 from Shopify/pre-c99-compat
tenderlove Jul 6, 2023
a995dba
Merge pull request #209 from stanhu/patch-1
tenderlove Jul 6, 2023
bb92978
Remove mocha / clean up assertions
tenderlove Jul 7, 2023
ef8b134
Merge pull request #212 from tmm1/remove-mocha
tenderlove Jul 7, 2023
03369b9
Add raw line numbers for raw mode
tenderlove Jul 8, 2023
51b71bf
Merge pull request #213 from tmm1/raw-line-numbers
tenderlove Jul 9, 2023
329e57b
Fix GC profiling timing
tenderlove Jul 11, 2023
726a172
Merge pull request #214 from tmm1/fix-gc-times
tenderlove Jul 12, 2023
c771fd4
Don't use postponed jobs on Ruby 3.3+YJIT
byroot Nov 8, 2023
c1e71bd
Fix compatibility with newer minitest
byroot Nov 8, 2023
91d12e7
Merge pull request #219 from casperisfine/yjit-3.3-postponed-jbos
tenderlove Nov 9, 2023
aaeef71
Migrate to the TypedData API
byroot Nov 30, 2023
bd885f9
Merge pull request #222 from casperisfine/typed-data
tenderlove Nov 30, 2023
ebdd3af
bumping version
tenderlove Jan 15, 2024
08b5127
don't set `running` until all relevant state is initialized
froydnj Feb 5, 2024
4e504d3
be more diligent about atomic operations
froydnj Feb 5, 2024
02b866a
Add Ruby 3.3 to CI
s4na Jun 14, 2024
078f365
Merge pull request #230 from s4na/ruby3.3
tmm1 Jun 19, 2024
d90ad35
Merge pull request #226 from froydnj/froydnj-fix-timing-crash
tenderlove Dec 18, 2024
a4d23d1
bumping version
tenderlove Jan 15, 2024
93ab15f
Add Ruby 3.4 to CI
boimw Feb 21, 2025
5d83283
Merge pull request #234 from boimw/master
tenderlove Feb 28, 2025
fae0152
Read only first two bytes to check signature
aidenfoxivey Oct 1, 2025
8085169
Merge pull request #238 from aidenfoxivey/master
tmm1 Oct 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
strategy:
fail-fast: false
matrix:
ruby: [ ruby-head, '3.0', '2.7', '2.6', '2.5', '2.4', '2.3', '2.2' ]
ruby: [ ruby-head, '3.4', '3.3','3.2', '3.1', '3.0', truffleruby ]
steps:
- name: Checkout
uses: actions/checkout@v2
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 0.2.25

* Fix GC marking

# 0.2.16

* [flamegraph.pl] Update to latest version
Expand Down
18 changes: 14 additions & 4 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,21 @@ Rake::TestTask.new(:test) do |t|
t.test_files = FileList["test/**/test_*.rb"]
end

require "rake/extensiontask"
if RUBY_ENGINE == "truffleruby"
task :compile do
# noop
end

Rake::ExtensionTask.new("stackprof") do |ext|
ext.ext_dir = "ext/stackprof"
ext.lib_dir = "lib/stackprof"
task :clean do
# noop
end
else
require "rake/extensiontask"

Rake::ExtensionTask.new("stackprof") do |ext|
ext.ext_dir = "ext/stackprof"
ext.lib_dir = "lib/stackprof"
end
end

task default: %i(compile test)
196 changes: 115 additions & 81 deletions bin/stackprof
Original file line number Diff line number Diff line change
Expand Up @@ -2,94 +2,128 @@
require 'optparse'
require 'stackprof'

options = {}
banner = <<-END
Usage: stackprof run [--mode=MODE|--out=FILE|--interval=INTERVAL|--format=FORMAT] -- COMMAND
Usage: stackprof [file.dump]+ [--text|--method=NAME|--callgrind|--graphviz]
END

parser = OptionParser.new(ARGV) do |o|
o.banner = "Usage: stackprof [file.dump]+ [--text|--method=NAME|--callgrind|--graphviz]"
if ARGV.first == "run"
ARGV.shift
env = {}
parser = OptionParser.new(banner) do |o|
o.on('--mode [MODE]', String, 'Mode of sampling: cpu, wall, object, default to wall') do |mode|
env["STACKPROF_MODE"] = mode
end

o.on('--text', 'Text summary per method (default)'){ options[:format] = :text }
o.on('--json', 'JSON output (use with web viewers)'){ options[:format] = :json }
o.on('--files', 'List of files'){ |f| options[:format] = :files }
o.on('--limit [num]', Integer, 'Limit --text, --files, or --graphviz output to N entries'){ |n| options[:limit] = n }
o.on('--sort-total', "Sort --text or --files output on total samples\n\n"){ options[:sort] = true }
o.on('--method [grep]', 'Zoom into specified method'){ |f| options[:format] = :method; options[:filter] = f }
o.on('--file [grep]', "Show annotated code for specified file"){ |f| options[:format] = :file; options[:filter] = f }
o.on('--walk', "Walk the stacktrace interactively\n\n"){ |f| options[:walk] = true }
o.on('--callgrind', 'Callgrind output (use with kcachegrind, stackprof-gprof2dot.py)'){ options[:format] = :callgrind }
o.on('--graphviz', "Graphviz output (use with dot)"){ options[:format] = :graphviz }
o.on('--node-fraction [frac]', OptionParser::DecimalNumeric, 'Drop nodes representing less than [frac] fraction of samples'){ |n| options[:node_fraction] = n }
o.on('--stackcollapse', 'stackcollapse.pl compatible output (use with stackprof-flamegraph.pl)'){ options[:format] = :stackcollapse }
o.on('--timeline-flamegraph', "timeline-flamegraph output (js)"){ options[:format] = :timeline_flamegraph }
o.on('--alphabetical-flamegraph', "alphabetical-flamegraph output (js)"){ options[:format] = :alphabetical_flamegraph }
o.on('--flamegraph', "alias to --timeline-flamegraph"){ options[:format] = :timeline_flamegraph }
o.on('--flamegraph-viewer [f.js]', String, "open html viewer for flamegraph output"){ |file|
puts("open file://#{File.expand_path('../../lib/stackprof/flamegraph/viewer.html', __FILE__)}?data=#{File.expand_path(file)}")
exit
}
o.on('--d3-flamegraph', "flamegraph output (html using d3-flame-graph)\n\n"){ options[:format] = :d3_flamegraph }
o.on('--select-files []', String, 'Show results of matching files'){ |path| (options[:select_files] ||= []) << File.expand_path(path) }
o.on('--reject-files []', String, 'Exclude results of matching files'){ |path| (options[:reject_files] ||= []) << File.expand_path(path) }
o.on('--select-names []', Regexp, 'Show results of matching method names'){ |regexp| (options[:select_names] ||= []) << regexp }
o.on('--reject-names []', Regexp, 'Exclude results of matching method names'){ |regexp| (options[:reject_names] ||= []) << regexp }
o.on('--dump', 'Print marshaled profile dump (combine multiple profiles)'){ options[:format] = :dump }
o.on('--debug', 'Pretty print raw profile data'){ options[:format] = :debug }
end
o.on('--out [FILENAME]', String, 'The target file, which will be overwritten. Defaults to a random temporary file') do |out|
env['STACKPROF_OUT'] = out
end

parser.parse!
parser.abort(parser.help) if ARGV.empty?
o.on('--interval [MILLISECONDS]', Integer, 'Mode-relative sample rate') do |interval|
env['STACKPROF_INTERVAL'] = interval.to_s
end

reports = []
while ARGV.size > 0
begin
file = ARGV.pop
reports << StackProf::Report.from_file(file)
rescue TypeError => e
STDERR.puts "** error parsing #{file}: #{e.inspect}"
o.on('--raw', 'collects the extra data required by the --flamegraph and --stackcollapse report types') do |raw|
env['STACKPROF_RAW'] = raw.to_s
end

o.on('--ignore-gc', 'Ignore garbage collection frames') do |gc|
env['STACKPROF_IGNORE_GC'] = gc.to_s
end
end
end
report = reports.inject(:+)
parser.parse!
parser.abort(parser.help) if ARGV.empty?
stackprof_path = File.expand_path('../lib', __dir__)
env['RUBYOPT'] = "-I #{stackprof_path} -r stackprof/autorun #{ENV['RUBYOPT']}"
Kernel.exec(env, *ARGV)
else
options = {}

default_options = {
:format => :text,
:sort => false,
:limit => 30
}
parser = OptionParser.new(banner) do |o|
o.on('--text', 'Text summary per method (default)'){ options[:format] = :text }
o.on('--json', 'JSON output (use with web viewers)'){ options[:format] = :json }
o.on('--files', 'List of files'){ |f| options[:format] = :files }
o.on('--limit [num]', Integer, 'Limit --text, --files, or --graphviz output to N entries'){ |n| options[:limit] = n }
o.on('--sort-total', "Sort --text or --files output on total samples\n\n"){ options[:sort] = true }
o.on('--method [grep]', 'Zoom into specified method'){ |f| options[:format] = :method; options[:filter] = f }
o.on('--file [grep]', "Show annotated code for specified file"){ |f| options[:format] = :file; options[:filter] = f }
o.on('--walk', "Walk the stacktrace interactively\n\n"){ |f| options[:walk] = true }
o.on('--callgrind', 'Callgrind output (use with kcachegrind, stackprof-gprof2dot.py)'){ options[:format] = :callgrind }
o.on('--graphviz', "Graphviz output (use with dot)"){ options[:format] = :graphviz }
o.on('--node-fraction [frac]', OptionParser::DecimalNumeric, 'Drop nodes representing less than [frac] fraction of samples'){ |n| options[:node_fraction] = n }
o.on('--stackcollapse', 'stackcollapse.pl compatible output (use with stackprof-flamegraph.pl)'){ options[:format] = :stackcollapse }
o.on('--timeline-flamegraph', "timeline-flamegraph output (js)"){ options[:format] = :timeline_flamegraph }
o.on('--alphabetical-flamegraph', "alphabetical-flamegraph output (js)"){ options[:format] = :alphabetical_flamegraph }
o.on('--flamegraph', "alias to --timeline-flamegraph"){ options[:format] = :timeline_flamegraph }
o.on('--flamegraph-viewer [f.js]', String, "open html viewer for flamegraph output"){ |file|
puts("open file://#{File.expand_path('../../lib/stackprof/flamegraph/viewer.html', __FILE__)}?data=#{File.expand_path(file)}")
exit
}
o.on('--d3-flamegraph', "flamegraph output (html using d3-flame-graph)\n\n"){ options[:format] = :d3_flamegraph }
o.on('--select-files []', String, 'Show results of matching files'){ |path| (options[:select_files] ||= []) << File.expand_path(path) }
o.on('--reject-files []', String, 'Exclude results of matching files'){ |path| (options[:reject_files] ||= []) << File.expand_path(path) }
o.on('--select-names []', Regexp, 'Show results of matching method names'){ |regexp| (options[:select_names] ||= []) << regexp }
o.on('--reject-names []', Regexp, 'Exclude results of matching method names'){ |regexp| (options[:reject_names] ||= []) << regexp }
o.on('--dump', 'Print marshaled profile dump (combine multiple profiles)'){ options[:format] = :dump }
o.on('--debug', 'Pretty print raw profile data'){ options[:format] = :debug }
end

if options[:format] == :graphviz
default_options[:limit] = 120
default_options[:node_fraction] = 0.005
end
parser.parse!
parser.abort(parser.help) if ARGV.empty?

options = default_options.merge(options)
options.delete(:limit) if options[:limit] == 0
reports = []
while ARGV.size > 0
begin
file = ARGV.pop
reports << StackProf::Report.from_file(file)
rescue TypeError => e
STDERR.puts "** error parsing #{file}: #{e.inspect}"
end
end
report = reports.inject(:+)

case options[:format]
when :text
report.print_text(options[:sort], options[:limit], options[:select_files], options[:reject_files], options[:select_names], options[:reject_names])
when :json
report.print_json
when :debug
report.print_debug
when :dump
report.print_dump
when :callgrind
report.print_callgrind
when :graphviz
report.print_graphviz(options)
when :stackcollapse
report.print_stackcollapse
when :timeline_flamegraph
report.print_timeline_flamegraph
when :alphabetical_flamegraph
report.print_alphabetical_flamegraph
when :d3_flamegraph
report.print_d3_flamegraph
when :method
options[:walk] ? report.walk_method(options[:filter]) : report.print_method(options[:filter])
when :file
report.print_file(options[:filter])
when :files
report.print_files(options[:sort], options[:limit])
else
raise ArgumentError, "unknown format: #{options[:format]}"
default_options = {
:format => :text,
:sort => false,
:limit => 30
}

if options[:format] == :graphviz
default_options[:limit] = 120
default_options[:node_fraction] = 0.005
end

options = default_options.merge(options)
options.delete(:limit) if options[:limit] == 0

case options[:format]
when :text
report.print_text(options[:sort], options[:limit], options[:select_files], options[:reject_files], options[:select_names], options[:reject_names])
when :json
report.print_json
when :debug
report.print_debug
when :dump
report.print_dump
when :callgrind
report.print_callgrind
when :graphviz
report.print_graphviz(options)
when :stackcollapse
report.print_stackcollapse
when :timeline_flamegraph
report.print_timeline_flamegraph
when :alphabetical_flamegraph
report.print_alphabetical_flamegraph
when :d3_flamegraph
report.print_d3_flamegraph
when :method
options[:walk] ? report.walk_method(options[:filter]) : report.print_method(options[:filter])
when :file
report.print_file(options[:filter])
when :files
report.print_files(options[:sort], options[:limit])
else
raise ArgumentError, "unknown format: #{options[:format]}"
end
end
6 changes: 6 additions & 0 deletions ext/stackprof/extconf.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
require 'mkmf'

if RUBY_ENGINE == 'truffleruby'
File.write('Makefile', dummy_makefile($srcdir).join(""))
return
end

if have_func('rb_postponed_job_register_one') &&
have_func('rb_profile_frames') &&
have_func('rb_tracepoint_new') &&
Expand Down
Loading