stackprofiler 0.0.1 → 0.0.2
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 +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
|

|
|
22
|
+

|
|
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
|