Bump to 0.2.9 by shivnagarajan · Pull Request #5 · Shopify/stackprof · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
c50ad78
Don't escape method filter regexp
csfrancis Feb 12, 2014
c2e8f4d
v0.2.7
tmm1 Jun 17, 2014
b7e7f55
Explicitly specify minitest dependency
rick Sep 11, 2014
18005c5
Update minitest includes
rick Sep 11, 2014
2e3f127
update assertion
rick Sep 11, 2014
164b9fd
yes, we can drop the require
rick Sep 11, 2014
ac022a0
Merge pull request #32 from rick/fix-test-suite
tmm1 Sep 12, 2014
97c7983
use StackProf::Report#print_method instead of #print_source in readme…
exAspArk Oct 3, 2014
4d8bd5b
Don`t modify memoized files list
Oct 11, 2014
aacc0df
Merge pull request #35 from Strech/patch-1
tmm1 Oct 11, 2014
e9835f0
Add raw option for StackProf::Middleware
SpringMT Nov 4, 2014
ec0ac75
Add option to drop less important nodes for big graphviz output
pushrax Nov 19, 2014
93e69d4
Add legend and method to show unlimited nodes
pushrax Nov 19, 2014
bac8931
Merge branch 'master' of https://github.com/tmm1/stackprof
tmm1 Dec 2, 2014
9bddace
Add test for dumping data to STDOUT
Jan 6, 2015
c247c73
Pass optional io to Report#print_dump
Jan 6, 2015
f070108
Merge pull request #44 from splattael/file-dump
tmm1 Jan 6, 2015
70c1b84
Fix filtering for Graphviz report
Mar 25, 2015
8139129
Merge pull request #47 from splattael/graphviz_filter
tmm1 Apr 4, 2015
8a5627e
Merge pull request #41 from pushrax/graphviz-drop-nodes
tmm1 May 9, 2015
e6ba27b
Merge pull request #39 from SpringMT/feature/add-raw-option-for-middl…
tmm1 May 15, 2015
6c64e93
middleware: pass raw to start
sirupsen May 15, 2015
58e2961
Merge pull request #48 from Sirupsen/master
tmm1 May 15, 2015
604bc53
Create LICENSE
bkeepers Sep 16, 2015
ef4088f
Merge pull request #50 from bkeepers/patch-1
tmm1 Sep 16, 2015
933352c
Merge pull request #34 from exAspArk/readme
tmm1 Sep 24, 2015
1f58d09
Added error messages and docs re raw: true
nyarly Oct 20, 2015
6e82d7f
Clarification
nyarly Oct 20, 2015
e252254
Merge pull request #53 from nyarly/master
tmm1 Oct 20, 2015
5f12b47
Better method examples
mperham Dec 15, 2015
bb933cc
Merge pull request #55 from mperham/patch-1
tmm1 Dec 15, 2015
a73eec7
Describe where `__LINE__` points
yui-knk Jan 5, 2016
f080a08
Merge pull request #57 from yui-knk/better_test_line_name
tmm1 Jan 13, 2016
1da228d
v0.2.8
tmm1 Jan 14, 2016
fee9a63
Avoid "stack level too deep" error
yuroyoro Jan 25, 2016
28699a2
Merge pull request #59 from yuroyoro/fix_system_stack_error_on_reporting
tmm1 Jan 25, 2016
e20fa5d
Revert "Avoid "stack level too deep" error"
tmm1 Jan 26, 2016
8c85961
Merge pull request #60 from tmm1/revert-59-fix_system_stack_error_on_…
tmm1 Jan 26, 2016
81b8d51
Avoid "stack level too deep" error
yuroyoro Jan 25, 2016
04330f9
prevent frames from being GC'd
tenderlove Feb 8, 2016
21a7c8c
Merge pull request #62 from tenderlove/prevent_frame_gc
tmm1 Feb 8, 2016
088b6ea
Merge pull request #18 from csfrancis/no_escape_method_regexp
tmm1 Feb 10, 2016
a93dc90
Merge pull request #61 from yuroyoro/fix_system_stack_error_on_reporting
tmm1 Feb 10, 2016
c69600f
v0.2.9
tmm1 Apr 13, 2016
26d2db1
Merge tag 'tags/v0.2.9' into bump_to_0.2.9
shivnagarajan May 17, 2016
435df56
bump to 0.29
shivnagarajan May 17, 2016
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
1 change: 1 addition & 0 deletions .gitignore
13 changes: 9 additions & 4 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
PATH
remote: .
specs:
stackprof (0.2.7)
stackprof (0.2.9)

GEM
remote: https://rubygems.org/
specs:
metaclass (0.0.1)
metaclass (0.0.4)
minitest (5.4.1)
mocha (0.14.0)
metaclass (~> 0.0.1)
rake (10.1.0)
rake-compiler (0.9.1)
rake (10.3.2)
rake-compiler (0.9.3)
rake

PLATFORMS
ruby

DEPENDENCIES
minitest (~> 5.0)
mocha (~> 0.14)
rake-compiler (~> 0.9)
stackprof!

BUNDLED WITH
1.11.2
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2013-2015 Aman Gupta

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ digraph profile {
}
```

#### `StackProf::Report.new(data).print_source(/pow|newobj|math/)`
#### `StackProf::Report.new(data).print_method(/pow|newobj|math/)`

```
A#pow (/Users/tmm1/code/stackprof/sample.rb:11)
Expand Down Expand Up @@ -265,11 +265,23 @@ multiple start/stop invocations.

``` ruby
StackProf.running?
StackProf.start
StackProf.start(mode: :cpu)
StackProf.stop
StackProf.results
StackProf.results('/tmp/some.file')
```

### all options

`StackProf.run` accepts an options hash. Currently, the following options are recognized:

Option | Meaning
------- | ---------
`mode` | mode of sampling: `:cpu`, `:wall`, `:object`, or `:custom` [c.f.](#sampling)
`out` | the target file, which will be overwritten
`interval` | mode-relative sample rate [c.f.](#sampling)
`aggregate` | defaults: `true` - if `false` disables [aggregation](#aggregation)
`raw` | defaults `false` - if `true` collects the extra data required by the `--flamegraph` and `--stackcollapse` report types

### todo

* file/iseq blacklist
Expand Down
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ task :default => :test
# Packaging
# ==========================================================

GEMSPEC = eval(File.read('stackprof.gemspec'))
GEMSPEC = Gem::Specification::load('stackprof.gemspec')

require 'rubygems/package_task'
Gem::PackageTask.new(GEMSPEC) do |pkg|
Expand Down
25 changes: 18 additions & 7 deletions bin/stackprof
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,20 @@
require 'optparse'
require 'stackprof'

options = {
:format => :text,
:sort => false,
:limit => 30
}
options = {}

parser = OptionParser.new(ARGV) do |o|
o.banner = "Usage: stackprof [file.dump]+ [--text|--method=NAME|--callgrind|--graphviz]"

o.on('--text', 'Text summary per method (default)'){ options[:format] = :text }
o.on('--files', 'List of files'){ |f| options[:format] = :files }
o.on('--limit [num]', Integer, 'Limit --text or --files output to N lines'){ |n| options[:limit] = n }
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\n\n"){ |f| options[:format] = :file; options[:filter] = f }
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('--flamegraph', "timeline-flamegraph output (js)"){ options[:format] = :flamegraph }
o.on('--flamegraph-viewer [f.js]', String, "open html viewer for flamegraph output\n\n"){ |file|
Expand All @@ -43,6 +40,20 @@ while ARGV.size > 0
end
report = reports.inject(:+)

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])
Expand All @@ -53,7 +64,7 @@ when :dump
when :callgrind
report.print_callgrind
when :graphviz
report.print_graphviz
report.print_graphviz(options)
when :stackcollapse
report.print_stackcollapse
when :flamegraph
Expand Down
2 changes: 1 addition & 1 deletion ext/stackprof.c
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ Init_stackprof(void)
S(aggregate);
#undef S

gc_hook = Data_Wrap_Struct(rb_cObject, stackprof_gc_mark, NULL, NULL);
gc_hook = Data_Wrap_Struct(rb_cObject, stackprof_gc_mark, NULL, &_stackprof);
rb_global_variable(&gc_hook);

rb_mStackProf = rb_define_module("StackProf");
Expand Down
12 changes: 6 additions & 6 deletions lib/stackprof/middleware.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ def initialize(app, options = {})

Middleware.mode = options[:mode] || :cpu
Middleware.interval = options[:interval] || 1000
Middleware.raw = options[:raw] || false
Middleware.enabled = options[:enabled]
Middleware.path = options[:path] || 'tmp'
Middleware.raw = options[:raw] || false
at_exit{ Middleware.save } if options[:save_at_exit]
end

def call(env)
enabled, mode = Middleware.enabled?
StackProf.start(mode: mode || Middleware.mode, raw: Middleware.raw, interval: Middleware.interval) if enabled
enabled, mode = Middleware.enabled?(env)
StackProf.start(mode: mode || Middleware.mode, interval: Middleware.interval, raw: Middleware.raw) if enabled
@app.call(env)
ensure
if enabled
Expand All @@ -30,11 +30,11 @@ def call(env)
end

class << self
attr_accessor :enabled, :mode, :interval, :path, :raw
attr_accessor :enabled, :mode, :interval, :raw, :path

def enabled?
def enabled?(env)
if enabled.respond_to?(:call)
enabled.call
enabled.call(env)
else
enabled
end
Expand Down
53 changes: 37 additions & 16 deletions lib/stackprof/report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def initialize(data)
attr_reader :data

def frames(sort_by_total=false)
Hash[ *@data[:frames].sort_by{ |iseq, stats| -stats[sort_by_total ? :total_samples : :samples] }.flatten(1) ]
@data[:frames].sort_by{ |iseq, stats| -stats[sort_by_total ? :total_samples : :samples] }.inject({}){|h, (k, v)| h[k] = v; h}
end

# normalized frames is used when we want to combine multiple
Expand Down Expand Up @@ -81,12 +81,12 @@ def print_debug
pp @data
end

def print_dump
puts Marshal.dump(@data.reject{|k,v| k == :files })
def print_dump(f=STDOUT)
f.puts Marshal.dump(@data.reject{|k,v| k == :files })
end

def print_stackcollapse
raise "profile does not include raw samples" unless raw = data[:raw]
raise "profile does not include raw samples (add `raw: true` to collecting StackProf.run)" unless raw = data[:raw]

while len = raw.shift
frames = raw.slice!(0, len)
Expand All @@ -98,7 +98,7 @@ def print_stackcollapse
end

def print_flamegraph(f=STDOUT, skip_common=true)
raise "profile does not include raw samples" unless raw = data[:raw]
raise "profile does not include raw samples (add `raw: true` to collecting StackProf.run)" unless raw = data[:raw]

stacks = []
max_x = 0
Expand Down Expand Up @@ -165,38 +165,60 @@ def flamegraph_row(f, x, y, weight, addr)
f.puts %{{"x":#{x},"y":#{y},"width":#{weight},"frame_id":#{addr},"frame":#{frame[:name].dump},"file":#{frame[:file].dump}}}
end

def print_graphviz(filter = nil, f = STDOUT)
if filter
def print_graphviz(options = {}, f = STDOUT)
if filter = options[:filter]
mark_stack = []
list = frames
list = frames(true)
list.each{ |addr, frame| mark_stack << addr if frame[:name] =~ filter }
while addr = mark_stack.pop
frame = list[addr]
unless frame[:marked]
$stderr.puts frame[:edges].inspect
mark_stack += frame[:edges].map{ |addr, weight| addr.to_s if list[addr.to_s][:total_samples] <= weight*1.2 }.compact if frame[:edges]
mark_stack += frame[:edges].map{ |addr, weight| addr if list[addr][:total_samples] <= weight*1.2 }.compact if frame[:edges]
frame[:marked] = true
end
end
list = list.select{ |addr, frame| frame[:marked] }
list.each{ |addr, frame| frame[:edges] && frame[:edges].delete_if{ |k,v| list[k.to_s].nil? } }
list.each{ |addr, frame| frame[:edges] && frame[:edges].delete_if{ |k,v| list[k].nil? } }
list
else
list = frames
list = frames(true)
end


limit = options[:limit]
fraction = options[:node_fraction]

included_nodes = {}
node_minimum = fraction ? (fraction * overall_samples).ceil : 0

f.puts "digraph profile {"
list.each do |frame, info|
f.puts "Legend [shape=box,fontsize=24,shape=plaintext,label=\""
f.print "Total samples: #{overall_samples}\\l"
f.print "Showing top #{limit} nodes\\l" if limit
f.print "Dropped nodes with < #{node_minimum} samples\\l" if fraction
f.puts "\"];"

list.each_with_index do |(frame, info), index|
call, total = info.values_at(:samples, :total_samples)
break if total < node_minimum || (limit && index >= limit)

sample = ''
sample << "#{call} (%2.1f%%)\\rof " % (call*100.0/overall_samples) if call < total
sample << "#{total} (%2.1f%%)\\r" % (total*100.0/overall_samples)
fontsize = (1.0 * call / max_samples) * 28 + 10
size = (1.0 * total / overall_samples) * 2.0 + 0.5

f.puts " \"#{frame}\" [size=#{size}] [fontsize=#{fontsize}] [penwidth=\"#{size}\"] [shape=box] [label=\"#{info[:name]}\\n#{sample}\"];"
included_nodes[frame] = true
end

list.each do |frame, info|
next unless included_nodes[frame]

if edges = info[:edges]
edges.each do |edge, weight|
next unless included_nodes[edge]

size = (1.0 * weight / overall_samples) * 2.0 + 0.5
f.puts " \"#{frame}\" -> \"#{edge}\" [label=\"#{weight}\"] [weight=\"#{weight}\"] [penwidth=\"#{size}\"];"
end
Expand Down Expand Up @@ -252,7 +274,7 @@ def print_callgrind(f = STDOUT)
end

def print_method(name, f = STDOUT)
name = /#{Regexp.escape name}/ unless Regexp === name
name = /#{name}/ unless Regexp === name
frames.each do |frame, info|
next unless info[:name] =~ name
file, line = info.values_at(:file, :line)
Expand Down Expand Up @@ -296,8 +318,7 @@ def print_files(sort_by_total=false, limit=nil, f = STDOUT)

def print_file(filter, f = STDOUT)
filter = /#{Regexp.escape filter}/ unless Regexp === filter
list = files
list.select!{ |name, lines| name =~ filter }
list = files.select{ |name, lines| name =~ filter }
list.sort_by{ |file, vals| -vals.values.inject(0){ |sum, n| sum + (n.is_a?(Array) ? n[1] : n) } }.each do |file, lines|
source_display(f, file, lines)
end
Expand Down
2 changes: 1 addition & 1 deletion sample.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def math

result = StackProf::Report.new(profile)
puts
result.print_source(/pow|newobj|math/)
result.print_method(/pow|newobj|math/)
puts
result.print_text
puts
Expand Down
3 changes: 2 additions & 1 deletion stackprof.gemspec
Loading