stackprofiler 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Guardfile +3 -0
- data/README.md +42 -2
- data/bin/stackprofiler +5 -0
- data/config.ru +5 -0
- data/lib/stackprofiler/data_collector.rb +17 -4
- data/lib/stackprofiler/filters/build_tree.rb +49 -0
- data/lib/stackprofiler/filters/compress_tree.rb +11 -14
- data/lib/stackprofiler/filters/frame_regex_removal.rb +22 -0
- data/lib/stackprofiler/filters/gem_removal.rb +16 -0
- data/lib/stackprofiler/filters/js_tree.rb +13 -3
- data/lib/stackprofiler/filters/quick_method_removal.rb +18 -0
- data/lib/stackprofiler/filters/rebase_stack.rb +25 -0
- data/lib/stackprofiler/middleware.rb +1 -1
- data/lib/stackprofiler/run_data_source.rb +18 -0
- data/lib/stackprofiler/utils.rb +50 -0
- data/lib/stackprofiler/version.rb +1 -1
- data/lib/stackprofiler/web_ui/public/js/stackprofiler.js +103 -0
- data/lib/stackprofiler/web_ui/public/{css → vendor/css}/bootstrap-theme.css +0 -0
- data/lib/stackprofiler/web_ui/public/{css → vendor/css}/bootstrap-theme.css.map +0 -0
- data/lib/stackprofiler/web_ui/public/{css → vendor/css}/bootstrap-theme.min.css +0 -0
- data/lib/stackprofiler/web_ui/public/{css → vendor/css}/bootstrap.css +0 -0
- data/lib/stackprofiler/web_ui/public/{css → vendor/css}/bootstrap.css.map +0 -0
- data/lib/stackprofiler/web_ui/public/{css → vendor/css}/bootstrap.min.css +0 -0
- data/lib/stackprofiler/web_ui/public/{css → vendor/css}/coderay.css +0 -0
- data/lib/stackprofiler/web_ui/public/{css → vendor/css}/jquery-ui.css +0 -0
- data/lib/stackprofiler/web_ui/public/{css → vendor/css}/splitter.css +0 -0
- data/lib/stackprofiler/web_ui/public/vendor/d3/d3.min.js +5 -0
- data/lib/stackprofiler/web_ui/public/vendor/epoch/epoch.min.css +1 -0
- data/lib/stackprofiler/web_ui/public/vendor/epoch/epoch.min.js +114 -0
- data/lib/stackprofiler/web_ui/public/{fonts → vendor/fonts}/glyphicons-halflings-regular.eot +0 -0
- data/lib/stackprofiler/web_ui/public/{fonts → vendor/fonts}/glyphicons-halflings-regular.svg +0 -0
- data/lib/stackprofiler/web_ui/public/{fonts → vendor/fonts}/glyphicons-halflings-regular.ttf +0 -0
- data/lib/stackprofiler/web_ui/public/{fonts → vendor/fonts}/glyphicons-halflings-regular.woff +0 -0
- data/lib/stackprofiler/web_ui/public/vendor/jquery-ui/.DS_Store +0 -0
- data/lib/stackprofiler/web_ui/public/vendor/jquery-ui/jquery-ui.min.css +7 -0
- data/lib/stackprofiler/web_ui/public/vendor/jquery-ui/jquery-ui.min.js +13 -0
- data/lib/stackprofiler/web_ui/public/{js → vendor/js}/bootstrap.js +0 -0
- data/lib/stackprofiler/web_ui/public/{js → vendor/js}/bootstrap.min.js +0 -0
- data/lib/stackprofiler/web_ui/public/{js → vendor/js}/jquery.min.js +0 -0
- data/lib/stackprofiler/web_ui/public/{js → vendor/js}/splitter.js +0 -0
- data/lib/stackprofiler/web_ui/public/{jstree → vendor/jstree}/jstree.js +0 -0
- data/lib/stackprofiler/web_ui/public/{jstree → vendor/jstree}/jstree.min.js +0 -0
- data/lib/stackprofiler/web_ui/public/{jstree → vendor/jstree}/themes/default/32px.png +0 -0
- data/lib/stackprofiler/web_ui/public/{jstree → vendor/jstree}/themes/default/40px.png +0 -0
- data/lib/stackprofiler/web_ui/public/{jstree → vendor/jstree}/themes/default/style.css +0 -0
- data/lib/stackprofiler/web_ui/public/{jstree → vendor/jstree}/themes/default/style.min.css +0 -0
- data/lib/stackprofiler/web_ui/public/{jstree → vendor/jstree}/themes/default/throbber.gif +0 -0
- data/lib/stackprofiler/web_ui/public/{jstree → vendor/jstree}/themes/default-dark/32px.png +0 -0
- data/lib/stackprofiler/web_ui/public/{jstree → vendor/jstree}/themes/default-dark/40px.png +0 -0
- data/lib/stackprofiler/web_ui/public/{jstree → vendor/jstree}/themes/default-dark/style.css +0 -0
- data/lib/stackprofiler/web_ui/public/{jstree → vendor/jstree}/themes/default-dark/style.min.css +0 -0
- data/lib/stackprofiler/web_ui/public/{jstree → vendor/jstree}/themes/default-dark/throbber.gif +0 -0
- data/lib/stackprofiler/web_ui/public/vendor/lodash/lodash.min.js +56 -0
- data/lib/stackprofiler/web_ui/public/{octicons → vendor/octicons}/LICENSE.txt +0 -0
- data/lib/stackprofiler/web_ui/public/{octicons → vendor/octicons}/README.md +0 -0
- data/lib/stackprofiler/web_ui/public/{octicons → vendor/octicons}/octicons-local.ttf +0 -0
- data/lib/stackprofiler/web_ui/public/{octicons → vendor/octicons}/octicons.css +0 -0
- data/lib/stackprofiler/web_ui/public/{octicons → vendor/octicons}/octicons.eot +0 -0
- data/lib/stackprofiler/web_ui/public/{octicons → vendor/octicons}/octicons.less +0 -0
- data/lib/stackprofiler/web_ui/public/{octicons → vendor/octicons}/octicons.svg +0 -0
- data/lib/stackprofiler/web_ui/public/{octicons → vendor/octicons}/octicons.ttf +0 -0
- data/lib/stackprofiler/web_ui/public/{octicons → vendor/octicons}/octicons.woff +0 -0
- data/lib/stackprofiler/web_ui/public/{octicons → vendor/octicons}/sprockets-octicons.scss +0 -0
- data/lib/stackprofiler/web_ui/public/vendor/tokenfield/.DS_Store +0 -0
- data/lib/stackprofiler/web_ui/public/vendor/tokenfield/bootstrap-tokenfield.min.css +5 -0
- data/lib/stackprofiler/web_ui/public/vendor/tokenfield/bootstrap-tokenfield.min.js +7 -0
- data/lib/stackprofiler/web_ui/views/code.erb +6 -7
- data/lib/stackprofiler/web_ui/views/index.erb +106 -26
- data/lib/stackprofiler/web_ui/views/layout.erb +33 -9
- data/lib/stackprofiler/web_ui.rb +107 -54
- data/lib/stackprofiler.rb +9 -2
- data/stackprofiler.gemspec +12 -1
- metadata +219 -44
- data/lib/stackprofiler/filters/stackprofiler_elision.rb +0 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53d49ea799e330d848bf167e2f5fbec763e136cc
|
4
|
+
data.tar.gz: 572be41c382be167f0b61bdca9aad96d44d55c30
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1ef14a9045d59fdfe64b6d5ad0e0a7e0d33cf641e2b01f2cf4557ba921b042156d752b0ce8e9e0b08140c320ab2e4ad58bf76f39a599bd57ff4fe1d867ffc848
|
7
|
+
data.tar.gz: c3ef5a43a3707f168b3026d6bd44763733824df9b533d0879c8f8daa04fbe512a52962f7d1793d7bcbb52fe5bf7889a21a40f680a7a560ce51a31d5cc3ded425
|
data/Guardfile
ADDED
data/README.md
CHANGED
@@ -19,6 +19,7 @@ for a job application! Please keep that in mind when you find all the
|
|
19
19
|
rough edges :)
|
20
20
|
|
21
21
|
![Screenshot](http://i.imgur.com/8UJV9Oo.png)
|
22
|
+
![Screenshot](http://i.imgur.com/CsZMXLu.png)
|
22
23
|
|
23
24
|
## Installation
|
24
25
|
|
@@ -36,7 +37,7 @@ Or install it yourself as:
|
|
36
37
|
|
37
38
|
$ gem install stackprofiler
|
38
39
|
|
39
|
-
##
|
40
|
+
## Middleware
|
40
41
|
|
41
42
|
Using Stackprofiler at this stage is pretty simple on account of there
|
42
43
|
not yet being much flexibility in the way of configuration. This will be
|
@@ -57,7 +58,7 @@ visible results, e.g. XHR.
|
|
57
58
|
Once you have profiled a request or two, you can head over to Stackprofiler's
|
58
59
|
GUI. This is located on the same port as your website, albeit at the
|
59
60
|
path `/__stackprofiler`. If your server is running on port 5000 in development,
|
60
|
-
the link might be [`http://localhost:5000/__stackprofiler`](
|
61
|
+
the link might be [`http://localhost:5000/__stackprofiler`](http://localhost:5000/__stackprofiler).
|
61
62
|
|
62
63
|
You should now see a page that looks like the screenshot above. Click on any
|
63
64
|
line in the stack trace to see the code for that method annotated with the
|
@@ -65,6 +66,44 @@ performance characteristics for each line. I would document those here but
|
|
65
66
|
they are going to change in the next few dev hours, so I'll come back to that
|
66
67
|
later.
|
67
68
|
|
69
|
+
### Data collection configuration
|
70
|
+
|
71
|
+
Stackprofiler's operation can be configured by passing in parameters to the
|
72
|
+
middleware specified above. While the defaults should suit most applications,
|
73
|
+
changing them is easy enough:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
config.middleware.use Stackprofiler::Middleware {
|
77
|
+
predicate: /profile=true/, # regex form for urls to be profiled
|
78
|
+
predicate: proc {|env| true }, # callable form for greater flexibility than regex
|
79
|
+
stackprof: { # options to be passed directly through to stackprof, e.g.:
|
80
|
+
interval: 1000 # sample every n micro-seconds
|
81
|
+
}
|
82
|
+
}
|
83
|
+
```
|
84
|
+
|
85
|
+
## Ad hoc
|
86
|
+
|
87
|
+
Sometimes you want to test some code that isn't part of a Rack app - or is
|
88
|
+
just cumbersome to run outside of an IRB console. You can test this code
|
89
|
+
directly using code very similar to the `stackprof` interface. It works like
|
90
|
+
this:
|
91
|
+
|
92
|
+
Run `$ stackprofiler` from the command line. This will start a Stackprofiler
|
93
|
+
server that will listen for incoming profile runs and display them on
|
94
|
+
[`http://localhost:9292/__stackprofiler`](http://localhost:9292/__stackprofiler).
|
95
|
+
Next, in an IRB console (or, even better, [Pry][3]!)
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
require 'stackprofiler'
|
99
|
+
|
100
|
+
Stackprofiler.profile do
|
101
|
+
SomeSlowTaskThatNeedsInvestigation.run
|
102
|
+
end
|
103
|
+
```
|
104
|
+
|
105
|
+
Now visit the above URL and a visual breakdown of the code flow will be visible.
|
106
|
+
|
68
107
|
## Contributing
|
69
108
|
|
70
109
|
1. Fork it ( https://github.com/glassechidna/stackprofiler/fork )
|
@@ -79,3 +118,4 @@ So much. First todo: write a todo list.
|
|
79
118
|
|
80
119
|
[1]: https://github.com/tmm1/stackprof
|
81
120
|
[2]: https://github.com/ruby-prof/ruby-prof
|
121
|
+
[3]: https://github.com/pry/pry
|
data/bin/stackprofiler
ADDED
data/config.ru
ADDED
@@ -1,14 +1,27 @@
|
|
1
1
|
module Stackprofiler
|
2
2
|
class DataCollector
|
3
|
-
def initialize(app, options
|
3
|
+
def initialize(app, options)
|
4
4
|
@app = app
|
5
|
+
|
6
|
+
pred = options[:predicate] || /profile=true/
|
7
|
+
if pred.respond_to? :call
|
8
|
+
@predicate = pred
|
9
|
+
else
|
10
|
+
regex = Regexp.new pred
|
11
|
+
|
12
|
+
@predicate = proc do |env|
|
13
|
+
req = Rack::Request.new env
|
14
|
+
req.fullpath =~ regex
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
@stackprof_opts = {mode: :wall, interval: 1000, raw: true}.merge(options[:stackprof] || {})
|
5
19
|
end
|
6
20
|
|
7
21
|
def call(env)
|
8
|
-
|
9
|
-
if env['QUERY_STRING'] =~ /profile=true/
|
22
|
+
if @predicate.call(env)
|
10
23
|
out = nil
|
11
|
-
profile = StackProf.run(
|
24
|
+
profile = StackProf.run(@stackprof_opts) { out = @app.call env }
|
12
25
|
|
13
26
|
req = Rack::Request.new env
|
14
27
|
run = Run.new req.fullpath, profile, Time.now
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Stackprofiler
|
2
|
+
module Filter
|
3
|
+
class BuildTree
|
4
|
+
def initialize(options={})
|
5
|
+
@options = options
|
6
|
+
end
|
7
|
+
|
8
|
+
def filter run, frames
|
9
|
+
stacks = run.stacks
|
10
|
+
|
11
|
+
root_addr = stacks[0][0].to_s
|
12
|
+
root = Tree::TreeNode.new root_addr, {addrs: [root_addr]}
|
13
|
+
|
14
|
+
stacks.each do |stack|
|
15
|
+
prev = root
|
16
|
+
iterate stack do |addr|
|
17
|
+
addr = addr.to_s
|
18
|
+
node = prev[addr]
|
19
|
+
if node.nil?
|
20
|
+
hash = {count: 0, addrs: [addr]}
|
21
|
+
node = Tree::TreeNode.new(addr, hash)
|
22
|
+
prev << node
|
23
|
+
end
|
24
|
+
node.content[:count] +=1
|
25
|
+
prev = node
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
if inverted?
|
30
|
+
root.children.each {|n| n.content[:open] = false }
|
31
|
+
end
|
32
|
+
|
33
|
+
root
|
34
|
+
end
|
35
|
+
|
36
|
+
def inverted?
|
37
|
+
@options[:invert]
|
38
|
+
end
|
39
|
+
|
40
|
+
def iterate stack, &blk
|
41
|
+
if inverted?
|
42
|
+
stack.reverse_each &blk
|
43
|
+
else
|
44
|
+
stack.each &blk
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -1,21 +1,18 @@
|
|
1
1
|
module Stackprofiler
|
2
2
|
module Filter
|
3
3
|
class CompressTree
|
4
|
-
def
|
5
|
-
|
6
|
-
|
7
|
-
depths.delete 0
|
4
|
+
def initialize(options={})
|
5
|
+
|
6
|
+
end
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
node.replace_with repl
|
18
|
-
end
|
8
|
+
def filter root, frames
|
9
|
+
root.reverse_depth_first do |node|
|
10
|
+
if node.out_degree == 1
|
11
|
+
hash = node.content
|
12
|
+
hash[:addrs] += node.first_child.content[:addrs]
|
13
|
+
repl = Tree::TreeNode.new(node.name, hash)
|
14
|
+
node.first_child.children.each {|n| repl << n }
|
15
|
+
node.replace_with repl
|
19
16
|
end
|
20
17
|
end
|
21
18
|
root
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Stackprofiler
|
2
|
+
module Filter
|
3
|
+
class FrameRegexRemoval
|
4
|
+
include RemoveFramesHelper
|
5
|
+
|
6
|
+
def initialize(options={})
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
def regexes
|
11
|
+
ary = @options[:regexes] || []
|
12
|
+
@regexes ||= ary.reject(&:blank?).map {|r| /#{r}/ }.compact
|
13
|
+
end
|
14
|
+
|
15
|
+
def filter root, frames
|
16
|
+
remove_frames root, frames do |node, frame|
|
17
|
+
regexes.any? {|r| frame[:name] =~ r }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Stackprofiler
|
2
|
+
module Filter
|
3
|
+
class GemRemoval
|
4
|
+
include RemoveFramesHelper
|
5
|
+
|
6
|
+
def initialize(options={})
|
7
|
+
end
|
8
|
+
|
9
|
+
def filter root, frames
|
10
|
+
remove_frames root, frames do |node, frame|
|
11
|
+
Gem.path.any? {|p| frame[:file].include?(p) }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -1,6 +1,10 @@
|
|
1
1
|
module Stackprofiler
|
2
2
|
module Filter
|
3
3
|
class JsTree
|
4
|
+
def initialize(options={})
|
5
|
+
|
6
|
+
end
|
7
|
+
|
4
8
|
def filter root, frames
|
5
9
|
addrs = root.content[:addrs]
|
6
10
|
name = addrs.first.to_i
|
@@ -13,9 +17,15 @@ module Stackprofiler
|
|
13
17
|
end
|
14
18
|
text = escaped.join("<br> ↳ ")
|
15
19
|
|
16
|
-
|
17
|
-
|
18
|
-
|
20
|
+
sorted_children = root.children.sort_by do |child|
|
21
|
+
addr = child.content[:addrs].first.to_i
|
22
|
+
frame = frames[addr]
|
23
|
+
frame[:samples]
|
24
|
+
end.reverse
|
25
|
+
|
26
|
+
children = sorted_children.map { |n| filter(n, frames) }
|
27
|
+
open = root.content.has_key?(:open) ? root.content[:open] : frame[:total_samples] > 100
|
28
|
+
{text: text, state: {opened: open}, children: children, icon: false, data: {addrs: addrs}}
|
19
29
|
end
|
20
30
|
end
|
21
31
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Stackprofiler
|
2
|
+
module Filter
|
3
|
+
class QuickMethodRemoval
|
4
|
+
include RemoveFramesHelper
|
5
|
+
attr_accessor :limit
|
6
|
+
|
7
|
+
def initialize(options={})
|
8
|
+
self.limit = options[:limit].try(:to_i) || 0
|
9
|
+
end
|
10
|
+
|
11
|
+
def filter root, frames
|
12
|
+
remove_frames root, frames do |node, frame|
|
13
|
+
frame[:total_samples] < limit
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Stackprofiler
|
2
|
+
module Filter
|
3
|
+
class RebaseStack
|
4
|
+
attr_accessor :top_names
|
5
|
+
|
6
|
+
def initialize(options={})
|
7
|
+
self.top_names = options[:name].presence || RebaseStack.default
|
8
|
+
end
|
9
|
+
|
10
|
+
def filter root, frames
|
11
|
+
root.find do |node|
|
12
|
+
addr = node.content[:addrs].first.to_i
|
13
|
+
top_names.include? frames[addr][:name]
|
14
|
+
# frames[addr][:name] == top_names
|
15
|
+
end || root
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
def default
|
20
|
+
['Stackprofiler::DataCollector#call', 'block in Stackprofiler#profile']
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -29,6 +29,24 @@ module Stackprofiler
|
|
29
29
|
def duration
|
30
30
|
profile[:samples] * profile[:interval] / 1e6
|
31
31
|
end
|
32
|
+
|
33
|
+
def gem_breakdown
|
34
|
+
bottom_frames = stacks.map &:last
|
35
|
+
paths = bottom_frames.map {|addr| profile[:frames][addr][:file] }
|
36
|
+
|
37
|
+
gems = paths.map do |p|
|
38
|
+
case p
|
39
|
+
when %r{gems/(\D\w+)}
|
40
|
+
$1
|
41
|
+
when %r{#{RbConfig::CONFIG['rubylibdir']}}
|
42
|
+
'stdlib'
|
43
|
+
else
|
44
|
+
'(app)'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
gems.group_by {|g| g }.map {|k, v| [k, v.count] }.sort_by(&:last).reverse.to_h
|
49
|
+
end
|
32
50
|
end
|
33
51
|
|
34
52
|
class RunDataSource
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Tree
|
2
|
+
class TreeNode
|
3
|
+
def reverse_depth_first &blk
|
4
|
+
depths = Hash.new {|h, k| h[k] = [] }
|
5
|
+
root.each {|n| depths[n.node_depth].push n }
|
6
|
+
depths.delete 0
|
7
|
+
keys = depths.keys.sort.reverse
|
8
|
+
|
9
|
+
keys.each do |depth|
|
10
|
+
nodes = depths[depth]
|
11
|
+
nodes.each &blk
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module Stackprofiler
|
18
|
+
def profile
|
19
|
+
# todo: pass through options perhaps?
|
20
|
+
profile = StackProf.run(mode: :wall, raw: true) { yield }
|
21
|
+
# todo: remove terrible hard-coded url
|
22
|
+
url = URI::parse 'http://localhost:9292/__stackprofiler/receive'
|
23
|
+
headers = {'Content-Type' => 'application/json'}
|
24
|
+
req = Net::HTTP::Post.new(url.to_s, headers)
|
25
|
+
req.body = Oj.dump profile
|
26
|
+
response = Net::HTTP.new(url.host, url.port).start {|http| http.request(req) }
|
27
|
+
end
|
28
|
+
module_function :profile
|
29
|
+
|
30
|
+
module Filter
|
31
|
+
module RemoveFramesHelper
|
32
|
+
def remove_frames root, frames
|
33
|
+
root.reverse_depth_first do |node|
|
34
|
+
frame = frames[node.name.to_i]
|
35
|
+
|
36
|
+
if yield node, frame
|
37
|
+
parent = node.parent
|
38
|
+
node.remove_from_parent!
|
39
|
+
|
40
|
+
node.children.each do |n|
|
41
|
+
n.remove_from_parent!
|
42
|
+
parent << n if parent[n.name].nil? # todo: have a think about if/why this "if" is necessary
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
root
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
$(function() {
|
2
|
+
$('#foo').split({
|
3
|
+
orientation: 'horizontal',
|
4
|
+
limit: 10,
|
5
|
+
position: '40%'
|
6
|
+
});
|
7
|
+
|
8
|
+
var $filteredFrames = $('#filtered_frames');
|
9
|
+
$filteredFrames.tokenfield();
|
10
|
+
|
11
|
+
var $tree = $('#jstree_demo');
|
12
|
+
|
13
|
+
$('#rebase_stack_enabled').change(function(e) {
|
14
|
+
var enabled = $(e.target).is(':checked');
|
15
|
+
$('#rebase_stack').prop('disabled', !enabled);
|
16
|
+
});
|
17
|
+
|
18
|
+
$('#rebase_stack').autocomplete({
|
19
|
+
source: "/__stackprofiler/frame_names?run_id=" + $tree.data('runId'),
|
20
|
+
minLength: 2
|
21
|
+
});
|
22
|
+
|
23
|
+
var getParams = function()
|
24
|
+
{
|
25
|
+
var params = {
|
26
|
+
run_id: $tree.data('runId'),
|
27
|
+
filters: {}
|
28
|
+
};
|
29
|
+
|
30
|
+
params.filters.filtered_frames = {regexes: $filteredFrames.val().split(', ')};
|
31
|
+
params.filters.quick_method_removal = {limit: $('form#options input[name="quick_method_removal"]').val()};
|
32
|
+
|
33
|
+
if ($('#rebase_stack_enabled').is(':checked')) params.filters.rebase_stack = {name: $('#rebase_stack').val() };
|
34
|
+
if ($('form#options input[name="invert"]').is(':checked')) params.filters.build_tree = {invert: true};
|
35
|
+
if ($('form#options input[name="remove_gems"]').is(':checked')) params.filters.remove_gems = {};
|
36
|
+
if ($('form#options input[name="compress_tree"]').is(':checked')) params.filters.compress_tree = {};
|
37
|
+
|
38
|
+
return params;
|
39
|
+
};
|
40
|
+
|
41
|
+
var getNodes = function(node, cb) {
|
42
|
+
$.post("/__stackprofiler/json", JSON.stringify(getParams()), function(data, status, xhr) {
|
43
|
+
cb.call(this, data);
|
44
|
+
});
|
45
|
+
};
|
46
|
+
|
47
|
+
$tree.jstree({
|
48
|
+
core: {
|
49
|
+
data: getNodes
|
50
|
+
}
|
51
|
+
});
|
52
|
+
|
53
|
+
$tree.on("changed.jstree", function (e, data) {
|
54
|
+
if (!data.node) return;
|
55
|
+
|
56
|
+
var addrs = data.node.data.addrs;
|
57
|
+
var url = "/__stackprofiler/code/" + addrs[0] + "?run_id=" + $tree.data('runId');
|
58
|
+
$("#source-display").load(url);
|
59
|
+
});
|
60
|
+
|
61
|
+
$('button#save-options').click(function() {
|
62
|
+
$tree.jstree().refresh();
|
63
|
+
});
|
64
|
+
|
65
|
+
$.getJSON('/__stackprofiler/gem_breakdown?run_id=' + $tree.data('runId'), function(data, status, xhr) {
|
66
|
+
var threshold = 0.1;
|
67
|
+
|
68
|
+
var sum = _.reduce(data, function(memo, count, gem) {
|
69
|
+
return memo + count;
|
70
|
+
}, 0);
|
71
|
+
|
72
|
+
var reduced = _(data).reduce(function(memo, count, gem) {
|
73
|
+
var key = count/sum > threshold ? gem : '(other)';
|
74
|
+
var accum = memo[key] || 0;
|
75
|
+
memo[key] = accum + count;
|
76
|
+
return memo;
|
77
|
+
}, {});
|
78
|
+
|
79
|
+
var f = function(count, gem) {
|
80
|
+
return {label: gem, value: count};
|
81
|
+
};
|
82
|
+
|
83
|
+
var ary = _.map(data, f);
|
84
|
+
var graph_data = _.map(reduced, f);
|
85
|
+
|
86
|
+
var colors = d3.scale.category10().range();
|
87
|
+
|
88
|
+
var rows = _(ary).zip(colors).first(ary.length).map(function(item) {
|
89
|
+
var gem = item[0];
|
90
|
+
var percent = (gem.value / sum * 100).toFixed(1);
|
91
|
+
var has_color = gem.value/sum > threshold;
|
92
|
+
var style = has_color ? 'background-color: ' + item[1] + ';' : '';
|
93
|
+
return $('<tr><td><div class="color-box" style="' + style + '"></div>' + gem.label + '</td><td>' + gem.value + '</td><td>' + percent + '%</td></tr>');
|
94
|
+
}).value();
|
95
|
+
|
96
|
+
$('#gem-pie').epoch({
|
97
|
+
type: 'pie',
|
98
|
+
data: graph_data
|
99
|
+
});
|
100
|
+
|
101
|
+
$('#gem-table').append(rows);
|
102
|
+
});
|
103
|
+
});
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|