stackprofiler 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -0
- data/README.md +41 -62
- data/bin/stackprofiler +2 -2
- data/config.ru +4 -2
- data/lib/stackprofiler/filters/build_tree.rb +7 -7
- data/lib/stackprofiler/filters/compress_tree.rb +1 -1
- data/lib/stackprofiler/filters/frame_regex_removal.rb +2 -2
- data/lib/stackprofiler/filters/gem_removal.rb +2 -2
- data/lib/stackprofiler/filters/js_tree.rb +9 -8
- data/lib/stackprofiler/filters/quick_method_removal.rb +2 -2
- data/lib/stackprofiler/filters/rebase_stack.rb +15 -11
- data/lib/stackprofiler/run_code_cache.rb +21 -0
- data/lib/stackprofiler/run_data_source.rb +11 -5
- data/lib/stackprofiler/utils.rb +2 -14
- data/lib/stackprofiler/web_ui/public/js/stackprofiler.js +20 -19
- data/lib/stackprofiler/web_ui/views/index.erb +3 -3
- data/lib/stackprofiler/web_ui/views/layout.erb +17 -17
- data/lib/stackprofiler/web_ui.rb +25 -22
- data/lib/stackprofiler.rb +1 -4
- data/stackprofiler.gemspec +1 -5
- metadata +3 -19
- data/lib/stackprofiler/data_collector.rb +0 -37
- data/lib/stackprofiler/middleware.rb +0 -18
- data/lib/stackprofiler/version.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cef292287c198e673dec3311397b8a0f3124bdf5
|
4
|
+
data.tar.gz: cc122485fbbca1605e100a0ca5e80e45aa979273
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e5dc5a401932e640d444d32bee79d2ef70a5f01bb017b014192e21fcab25db82e1b6877f3fd34918a3c01ee1b19684c1b228cf1bd4908df32a1a76d25e69b22e
|
7
|
+
data.tar.gz: 93f5d521e61ab24d5ac34ade6be06cc22a6497eacb977a361e1e8009b7ec8ad4b84dd861214592c364ec79d275babb125ebc01ab40f6d93ac8c74383780bae83
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -23,86 +23,61 @@ rough edges :)
|
|
23
23
|
|
24
24
|
## Installation
|
25
25
|
|
26
|
-
Add this line to your application's Gemfile:
|
27
|
-
|
28
|
-
```ruby
|
29
|
-
gem 'stackprofiler'
|
30
|
-
```
|
31
|
-
|
32
|
-
And then execute:
|
33
|
-
|
34
|
-
$ bundle
|
35
|
-
|
36
|
-
Or install it yourself as:
|
37
|
-
|
38
26
|
$ gem install stackprofiler
|
39
27
|
|
40
|
-
|
28
|
+
Stackprofiler is a stand-alone Ruby app that can be installed using Rubygems.
|
29
|
+
The client gems (see below) should probably be installed as part of a Bundler
|
30
|
+
Gemfile.
|
41
31
|
|
42
|
-
|
43
|
-
not yet being much flexibility in the way of configuration. This will be
|
44
|
-
fixed later - hopefully the simplicity can remain.
|
32
|
+
## Usage
|
45
33
|
|
46
|
-
Once installed,
|
47
|
-
|
34
|
+
Once installed, the Stackprofiler web UI can be started from the terminal by
|
35
|
+
running `stackprofiler`. By itself, it won't do much. It will wait for profile
|
36
|
+
data to come in from client gems. There are a handful of these gems and they
|
37
|
+
are listed below.
|
48
38
|
|
49
|
-
|
39
|
+
## Other gems
|
50
40
|
|
51
|
-
|
52
|
-
append `profile=true` to the query string. This will inform Stackprofiler
|
53
|
-
that it should do its thing. Take note that the response will remain
|
54
|
-
unchanged - Stackprofiler will record its statistics for your later perusal
|
55
|
-
elsewhere. This is to make it easier to profile requests that don't return
|
56
|
-
visible results, e.g. XHR.
|
41
|
+
### Rack Middleware
|
57
42
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
the link might be [`http://localhost:5000/__stackprofiler`](http://localhost:5000/__stackprofiler).
|
43
|
+
Stackprofiler can be used to measure the performance of Ruby-powered websites
|
44
|
+
by using a drop-in Rack middleware. This middleware is provided by a separate
|
45
|
+
gem; [`stackprofiler-middleware`][3].
|
62
46
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
later.
|
47
|
+
The reason for a separate gem is so that Stackprofiler can be used in as many
|
48
|
+
circumstances as possible. Your app may have dependencies that conflict with
|
49
|
+
those powering the Stackprofiler web UI and it would be a shame to miss out
|
50
|
+
on using this tool on account of that.
|
68
51
|
|
69
|
-
|
52
|
+
Head on over to the README for that gem to learn how to use it.
|
70
53
|
|
71
|
-
|
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
|
54
|
+
### Pry Plugin
|
86
55
|
|
87
56
|
Sometimes you want to test some code that isn't part of a Rack app - or is
|
88
57
|
just cumbersome to run outside of an IRB console. You can test this code
|
89
|
-
directly
|
90
|
-
|
58
|
+
directly very easily using the [`pry-stackprofiler`][4] gem in the [Pry][5]
|
59
|
+
REPL.
|
91
60
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
Next, in an IRB console (or, even better, [Pry][3]!)
|
61
|
+
Pry is an alternative to IRB with handy support for plugins. `pry-stackprofiler`
|
62
|
+
is such a plugin and works well with the Stackprofiler server. Once installed,
|
63
|
+
you can type code into the REPL like:
|
96
64
|
|
97
65
|
```ruby
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
66
|
+
Pry.profile do
|
67
|
+
sleep 0.3
|
68
|
+
sleep 0.4
|
69
|
+
sleep 0.1
|
102
70
|
end
|
103
71
|
```
|
104
72
|
|
105
|
-
|
73
|
+
And the profile results will appear in the Stackprofiler web UI. For running
|
74
|
+
instructions, refer to the gem's README.
|
75
|
+
|
76
|
+
### Sidekiq Middleware
|
77
|
+
|
78
|
+
Stackprofiler can also be used to measure the performance of background jobs
|
79
|
+
powered by [Sidekiq][6]. It does this using the [`stackprofiler-sidekiq`][7] middleware
|
80
|
+
gem. Have a look at the README for that gem to learn more.
|
106
81
|
|
107
82
|
## Contributing
|
108
83
|
|
@@ -118,4 +93,8 @@ So much. First todo: write a todo list.
|
|
118
93
|
|
119
94
|
[1]: https://github.com/tmm1/stackprof
|
120
95
|
[2]: https://github.com/ruby-prof/ruby-prof
|
121
|
-
[3]: https://github.com/
|
96
|
+
[3]: https://github.com/glassechidna/stackprofiler-middleware
|
97
|
+
[4]: https://github.com/glassechidna/pry-stackprofiler
|
98
|
+
[5]: https://github.com/pry/pry
|
99
|
+
[6]: https://github.com/mperham/sidekiq
|
100
|
+
[7]: https://github.com/glassechidna/stackprofiler-sidekiq
|
data/bin/stackprofiler
CHANGED
data/config.ru
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'stackprofiler'
|
2
|
+
require 'stackprofiler/middleware'
|
2
3
|
|
3
|
-
|
4
|
-
# run
|
4
|
+
use Stackprofiler::Middleware, ui_url: 'http://localhost:9260/receive', predicate: /json/
|
5
|
+
# run Stackprofiler::WebUI.new file: 'portal.json'
|
6
|
+
run Stackprofiler::WebUI.new file: 'threads.json'
|
5
7
|
|
@@ -5,20 +5,20 @@ module Stackprofiler
|
|
5
5
|
@options = options
|
6
6
|
end
|
7
7
|
|
8
|
-
def filter run,
|
8
|
+
def filter run, run2
|
9
9
|
stacks = run.stacks
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
root = StandardWarning.disable { Tree::TreeNode.new '(Root)', {addrs: [], open: true} }
|
12
|
+
all = {root_addr: root}
|
13
13
|
|
14
14
|
stacks.each do |stack|
|
15
15
|
prev = root
|
16
|
-
iterate stack do |addr|
|
17
|
-
|
18
|
-
node = prev[addr]
|
16
|
+
iterate stack[1..-1] do |addr|
|
17
|
+
node = all[addr]
|
19
18
|
if node.nil?
|
20
19
|
hash = {count: 0, addrs: [addr]}
|
21
|
-
node = Tree::TreeNode.new(addr, hash)
|
20
|
+
node = StandardWarning.disable { Tree::TreeNode.new(addr, hash) }
|
21
|
+
all[addr] = node
|
22
22
|
prev << node
|
23
23
|
end
|
24
24
|
node.content[:count] +=1
|
@@ -12,8 +12,8 @@ module Stackprofiler
|
|
12
12
|
@regexes ||= ary.reject(&:blank?).map {|r| /#{r}/ }.compact
|
13
13
|
end
|
14
14
|
|
15
|
-
def filter root,
|
16
|
-
remove_frames root,
|
15
|
+
def filter root, run
|
16
|
+
remove_frames root, run do |node, frame|
|
17
17
|
regexes.any? {|r| frame[:name] =~ r }
|
18
18
|
end
|
19
19
|
end
|
@@ -5,25 +5,26 @@ module Stackprofiler
|
|
5
5
|
|
6
6
|
end
|
7
7
|
|
8
|
-
def filter root,
|
8
|
+
def filter root, run
|
9
9
|
addrs = root.content[:addrs]
|
10
|
-
name = addrs.first
|
10
|
+
name = addrs.first
|
11
|
+
frames = run.profile[:frames]
|
11
12
|
frame = frames[name]
|
12
13
|
|
13
14
|
escaped = addrs.map do |addr|
|
14
|
-
this_frame = frames[addr
|
15
|
+
this_frame = frames[addr]
|
15
16
|
this_name = CGI::escapeHTML(this_frame[:name])
|
16
17
|
"#{this_name} (<b>#{this_frame[:samples]}</b>)"
|
17
18
|
end
|
18
|
-
text = escaped.join("<br> ↳ ")
|
19
|
+
text = escaped.join("<br> ↳ ").presence || root.name
|
19
20
|
|
20
21
|
sorted_children = root.children.sort_by do |child|
|
21
|
-
addr = child.content[:addrs].first
|
22
|
-
|
23
|
-
|
22
|
+
addr = child.content[:addrs].first
|
23
|
+
cframe = frames[addr]
|
24
|
+
cframe[:samples]
|
24
25
|
end.reverse
|
25
26
|
|
26
|
-
children = sorted_children.map { |n| filter(n,
|
27
|
+
children = sorted_children.map { |n| filter(n, run) }
|
27
28
|
open = root.content.has_key?(:open) ? root.content[:open] : frame[:total_samples] > 100
|
28
29
|
{text: text, state: {opened: open}, children: children, icon: false, data: {addrs: addrs}}
|
29
30
|
end
|
@@ -8,8 +8,8 @@ module Stackprofiler
|
|
8
8
|
self.limit = options[:limit].try(:to_i) || 0
|
9
9
|
end
|
10
10
|
|
11
|
-
def filter root,
|
12
|
-
remove_frames root,
|
11
|
+
def filter root, run
|
12
|
+
remove_frames root, run do |node, frame|
|
13
13
|
frame[:total_samples] < limit
|
14
14
|
end
|
15
15
|
end
|
@@ -1,24 +1,28 @@
|
|
1
1
|
module Stackprofiler
|
2
2
|
module Filter
|
3
3
|
class RebaseStack
|
4
|
-
attr_accessor :
|
4
|
+
attr_accessor :manual
|
5
5
|
|
6
6
|
def initialize(options={})
|
7
|
-
self.
|
7
|
+
self.manual = options[:name].presence
|
8
8
|
end
|
9
9
|
|
10
|
-
def filter root,
|
10
|
+
def filter root, run
|
11
|
+
suggested = run.profile[:suggested_rebase]
|
12
|
+
|
11
13
|
root.find do |node|
|
14
|
+
next if node == root
|
12
15
|
addr = node.content[:addrs].first.to_i
|
13
|
-
|
14
|
-
# frames[addr][:name] == top_names
|
15
|
-
end || root
|
16
|
-
end
|
16
|
+
frame = run.profile[:frames][addr]
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
if manual
|
19
|
+
frame[:name].include? manual
|
20
|
+
elsif suggested.is_a? String
|
21
|
+
suggested == frame[:name]
|
22
|
+
else
|
23
|
+
suggested == addr
|
24
|
+
end
|
25
|
+
end || root
|
22
26
|
end
|
23
27
|
end
|
24
28
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Stackprofiler
|
2
|
+
class RunCodeCache
|
3
|
+
extend MethodSource::CodeHelpers
|
4
|
+
|
5
|
+
def initialize profile
|
6
|
+
@profile = profile
|
7
|
+
end
|
8
|
+
|
9
|
+
def source_helper(source_location, name=nil)
|
10
|
+
file, line = *source_location
|
11
|
+
file_cache = @profile[:files] || []
|
12
|
+
|
13
|
+
if file_cache.include? file
|
14
|
+
file_data = @profile[:files][file]
|
15
|
+
self.class.expression_at(file_data, line)
|
16
|
+
else
|
17
|
+
MethodSource::source_helper(source_location, name)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -10,7 +10,11 @@ module Stackprofiler
|
|
10
10
|
@timestamp = timestamp
|
11
11
|
end
|
12
12
|
|
13
|
-
def
|
13
|
+
def code_cache
|
14
|
+
@code_cache ||= RunCodeCache.new @profile
|
15
|
+
end
|
16
|
+
|
17
|
+
def stacks use_weights=false
|
14
18
|
@stacks ||= begin
|
15
19
|
off = 0
|
16
20
|
stacks = []
|
@@ -18,9 +22,11 @@ module Stackprofiler
|
|
18
22
|
while off < raw.length
|
19
23
|
len = raw[off]
|
20
24
|
these_frames = raw[off + 1..off + len]
|
25
|
+
weight = raw[off + len + 1]
|
21
26
|
off += len + 2
|
22
27
|
|
23
|
-
|
28
|
+
times = use_weights ? weight : 1
|
29
|
+
times.times { stacks.push these_frames }
|
24
30
|
end
|
25
31
|
stacks
|
26
32
|
end
|
@@ -32,10 +38,10 @@ module Stackprofiler
|
|
32
38
|
|
33
39
|
def gem_breakdown
|
34
40
|
bottom_frames = stacks.map &:last
|
35
|
-
|
41
|
+
frames = bottom_frames.map {|addr| profile[:frames][addr] }
|
36
42
|
|
37
|
-
gems =
|
38
|
-
case
|
43
|
+
gems = frames.map do |frame|
|
44
|
+
case frame[:file]
|
39
45
|
when %r{gems/(\D\w+)}
|
40
46
|
$1
|
41
47
|
when %r{#{RbConfig::CONFIG['rubylibdir']}}
|
data/lib/stackprofiler/utils.rb
CHANGED
@@ -15,23 +15,11 @@ module Tree
|
|
15
15
|
end
|
16
16
|
|
17
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
18
|
module Filter
|
31
19
|
module RemoveFramesHelper
|
32
|
-
def remove_frames root,
|
20
|
+
def remove_frames root, run
|
33
21
|
root.reverse_depth_first do |node|
|
34
|
-
frame = frames[node.name
|
22
|
+
frame = run.profile[:frames][node.name]
|
35
23
|
|
36
24
|
if yield node, frame
|
37
25
|
parent = node.parent
|
@@ -16,7 +16,7 @@ $(function() {
|
|
16
16
|
});
|
17
17
|
|
18
18
|
$('#rebase_stack').autocomplete({
|
19
|
-
source: "/
|
19
|
+
source: "/frame_names?run_id=" + $tree.data('runId'),
|
20
20
|
minLength: 2
|
21
21
|
});
|
22
22
|
|
@@ -39,7 +39,7 @@ $(function() {
|
|
39
39
|
};
|
40
40
|
|
41
41
|
var getNodes = function(node, cb) {
|
42
|
-
$.post("/
|
42
|
+
$.post("/json", JSON.stringify(getParams()), function(data, status, xhr) {
|
43
43
|
cb.call(this, data);
|
44
44
|
});
|
45
45
|
};
|
@@ -54,7 +54,7 @@ $(function() {
|
|
54
54
|
if (!data.node) return;
|
55
55
|
|
56
56
|
var addrs = data.node.data.addrs;
|
57
|
-
var url = "/
|
57
|
+
var url = "/code/" + addrs[0] + "?run_id=" + $tree.data('runId');
|
58
58
|
$("#source-display").load(url);
|
59
59
|
});
|
60
60
|
|
@@ -62,30 +62,31 @@ $(function() {
|
|
62
62
|
$tree.jstree().refresh();
|
63
63
|
});
|
64
64
|
|
65
|
-
$.getJSON('/
|
66
|
-
var
|
65
|
+
$.getJSON('/gem_breakdown?run_id=' + $tree.data('runId'), function(data, status, xhr) {
|
66
|
+
var objs = _(data).map(function(count, gem) {
|
67
|
+
return {label: gem, value: count};
|
68
|
+
}).sortBy('value').reverse().value();
|
67
69
|
|
68
|
-
var
|
69
|
-
return memo +
|
70
|
-
}
|
70
|
+
var summer = function(memo, item) {
|
71
|
+
return memo + item.value;
|
72
|
+
};
|
71
73
|
|
72
|
-
var
|
73
|
-
var key = count/sum > threshold ? gem : '(other)';
|
74
|
-
var accum = memo[key] || 0;
|
75
|
-
memo[key] = accum + count;
|
76
|
-
return memo;
|
77
|
-
}, {});
|
74
|
+
var sum = _.reduce(objs, summer, 0);
|
78
75
|
|
79
|
-
var
|
80
|
-
|
76
|
+
var threshold = 0.1;
|
77
|
+
var thresholder = function(item) {
|
78
|
+
return item.value/sum > threshold;
|
81
79
|
};
|
82
80
|
|
83
|
-
var
|
84
|
-
var
|
81
|
+
var below_threshold = _.reject(objs, thresholder);
|
82
|
+
var other_count = _.reduce(below_threshold, summer, 0);
|
83
|
+
|
84
|
+
var graph_data = _.filter(objs, thresholder);
|
85
|
+
graph_data.push({label: '(other)', value: other_count});
|
85
86
|
|
86
87
|
var colors = d3.scale.category10().range();
|
87
88
|
|
88
|
-
var rows = _(
|
89
|
+
var rows = _(objs).zip(colors).first(objs.length).map(function(item) {
|
89
90
|
var gem = item[0];
|
90
91
|
var percent = (gem.value / sum * 100).toFixed(1);
|
91
92
|
var has_color = gem.value/sum > threshold;
|
@@ -3,7 +3,7 @@
|
|
3
3
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Profiles <b class="caret"></b></a>
|
4
4
|
<ul class="dropdown-menu">
|
5
5
|
<% @runs.each_with_index do |run, run_id| %>
|
6
|
-
<li><a href="
|
6
|
+
<li><a href="/?run_id=<%= run_id %>"><%= run.url %></a> (<b><%= run.duration %>s</b>)</li>
|
7
7
|
<% end %>
|
8
8
|
</ul>
|
9
9
|
</li>
|
@@ -13,7 +13,7 @@
|
|
13
13
|
<% end %>
|
14
14
|
|
15
15
|
<% content_for :head do %>
|
16
|
-
<script src="/
|
16
|
+
<script src="/js/stackprofiler.js"></script>
|
17
17
|
<% end %>
|
18
18
|
|
19
19
|
<div id="foo">
|
@@ -84,7 +84,7 @@
|
|
84
84
|
<input type="checkbox" id="rebase_stack_enabled" name="rebase_stack_enabled" checked="true">
|
85
85
|
Enabled
|
86
86
|
</label>
|
87
|
-
<input type="text" class="form-control" disabled="true" id="rebase_stack" name="rebase_stack" placeholder="e.g.
|
87
|
+
<input type="text" class="form-control" disabled="true" id="rebase_stack" name="rebase_stack" placeholder="e.g. MyController#index">
|
88
88
|
<span class="help-block">Enter name of frame above which frames will be hidden.</span>
|
89
89
|
</div>
|
90
90
|
</div>
|
@@ -1,22 +1,22 @@
|
|
1
1
|
<html>
|
2
2
|
<head>
|
3
|
-
<link rel="stylesheet" href="/
|
4
|
-
<link rel="stylesheet" href="/
|
5
|
-
<link rel="stylesheet" href="/
|
6
|
-
<link rel="stylesheet" href="/
|
7
|
-
<link rel="stylesheet" href="/
|
8
|
-
<link rel="stylesheet" href="/
|
9
|
-
<link rel="stylesheet" href="/
|
10
|
-
<link rel="stylesheet" href="/
|
11
|
-
<script src="/
|
12
|
-
<script src="/
|
13
|
-
<script src="/
|
14
|
-
<script src="/
|
15
|
-
<script src="/
|
16
|
-
<script src="/
|
17
|
-
<script src="/
|
18
|
-
<script src="/
|
19
|
-
<script src="/
|
3
|
+
<link rel="stylesheet" href="/vendor/css/bootstrap.min.css" />
|
4
|
+
<link rel="stylesheet" href="/vendor/css/splitter.css" />
|
5
|
+
<link rel="stylesheet" href="/vendor/jstree/themes/default/style.min.css" />
|
6
|
+
<link rel="stylesheet" href="/vendor/css/coderay.css" />
|
7
|
+
<link rel="stylesheet" href="/vendor/octicons/octicons.css" />
|
8
|
+
<link rel="stylesheet" href="/vendor/tokenfield/bootstrap-tokenfield.min.css" />
|
9
|
+
<link rel="stylesheet" href="/vendor/jquery-ui/jquery-ui.min.css" />
|
10
|
+
<link rel="stylesheet" href="/vendor/epoch/epoch.min.css" />
|
11
|
+
<script src="/vendor/js/jquery.min.js"></script>
|
12
|
+
<script src="/vendor/js/bootstrap.min.js"></script>
|
13
|
+
<script src="/vendor/jstree/jstree.js"></script>
|
14
|
+
<script src="/vendor/js/splitter.js"></script>
|
15
|
+
<script src="/vendor/tokenfield/bootstrap-tokenfield.min.js"></script>
|
16
|
+
<script src="/vendor/jquery-ui/jquery-ui.min.js"></script>
|
17
|
+
<script src="/vendor/d3/d3.min.js"></script>
|
18
|
+
<script src="/vendor/epoch/epoch.min.js"></script>
|
19
|
+
<script src="/vendor/lodash/lodash.min.js"></script>
|
20
20
|
<%= yield_content :head %>
|
21
21
|
<style>
|
22
22
|
body, html {
|
data/lib/stackprofiler/web_ui.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module Stackprofiler
|
2
2
|
class WebUI < Sinatra::Base
|
3
|
+
use Rack::Deflater
|
4
|
+
|
3
5
|
helpers Sinatra::ContentFor
|
4
6
|
set :views, proc { File.join(root, 'web_ui', 'views') }
|
5
7
|
set :public_folder, proc { File.join(root, 'web_ui', 'public') }
|
@@ -19,19 +21,9 @@ module Stackprofiler
|
|
19
21
|
end
|
20
22
|
|
21
23
|
configure :development do
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
26
|
-
|
27
|
-
before do
|
28
|
-
puts "starting #{request.path}"
|
29
|
-
@start_req = Time.now
|
30
|
-
end
|
31
|
-
|
32
|
-
after do
|
33
|
-
duration = Time.now - @start_req
|
34
|
-
puts "finished #{request.path} (#{duration})"
|
24
|
+
require 'better_errors'
|
25
|
+
use BetterErrors::Middleware
|
26
|
+
BetterErrors.application_root = __dir__
|
35
27
|
end
|
36
28
|
|
37
29
|
get '/' do
|
@@ -48,6 +40,13 @@ module Stackprofiler
|
|
48
40
|
end.to_json
|
49
41
|
end
|
50
42
|
|
43
|
+
get '/dump' do
|
44
|
+
content_type 'application/json'
|
45
|
+
run_id = params[:run_id].to_i
|
46
|
+
run = RunDataSource.runs[run_id]
|
47
|
+
Oj.dump run
|
48
|
+
end
|
49
|
+
|
51
50
|
get '/frames' do
|
52
51
|
content_type 'application/json'
|
53
52
|
run_id = params[:run_id].to_i
|
@@ -70,7 +69,7 @@ module Stackprofiler
|
|
70
69
|
@file, @first_line = frame.values_at :file, :line
|
71
70
|
@first_line ||= 1
|
72
71
|
|
73
|
-
@source =
|
72
|
+
@source = run.code_cache.source_helper([@file, @first_line]).strip_heredoc
|
74
73
|
@output = CodeRay.scan(@source, :ruby).div(wrap: nil).lines.map.with_index do |code, idx|
|
75
74
|
line_index = idx + @first_line
|
76
75
|
samples = frame[:lines][line_index] || []
|
@@ -85,18 +84,18 @@ module Stackprofiler
|
|
85
84
|
run = RunDataSource.runs[run_id]
|
86
85
|
|
87
86
|
content_type 'application/octet-stream'
|
88
|
-
Marshal.dump(run.profile)
|
89
|
-
|
87
|
+
# Marshal.dump(run.profile)
|
88
|
+
Oj.dump(run)
|
90
89
|
end
|
91
90
|
|
92
91
|
get '/frame_names' do
|
93
|
-
name = params[:term]
|
92
|
+
name = params[:term].downcase
|
94
93
|
run_id = params[:run_id].to_i
|
95
94
|
|
96
95
|
run = RunDataSource.runs[run_id]
|
97
96
|
frames = run.profile[:frames]
|
98
97
|
|
99
|
-
matching = frames.select {|addr, f| f[:name].include? name }
|
98
|
+
matching = frames.select {|addr, f| f[:name].downcase.include? name }
|
100
99
|
results = matching.map {|addr, f| f[:name] }
|
101
100
|
|
102
101
|
content_type 'application/json'
|
@@ -105,8 +104,9 @@ module Stackprofiler
|
|
105
104
|
|
106
105
|
post '/receive' do
|
107
106
|
data = request.body.read
|
108
|
-
json =
|
109
|
-
|
107
|
+
json = Marshal.load(data)
|
108
|
+
name = json[:name] || 'unknown'
|
109
|
+
run = Run.new name, json, Time.now
|
110
110
|
RunDataSource.runs << run
|
111
111
|
|
112
112
|
# if they sent us a profile, they probably changed something and want that reflected
|
@@ -121,8 +121,11 @@ module Stackprofiler
|
|
121
121
|
run_id = params[:run_id].to_i
|
122
122
|
run = RunDataSource.runs[run_id]
|
123
123
|
|
124
|
+
breakdown = run.gem_breakdown
|
125
|
+
breakdown['(gc)'] = run.profile[:gc_samples]
|
126
|
+
|
124
127
|
content_type 'application/json'
|
125
|
-
|
128
|
+
breakdown.to_json
|
126
129
|
end
|
127
130
|
|
128
131
|
post '/json' do
|
@@ -155,7 +158,7 @@ module Stackprofiler
|
|
155
158
|
end
|
156
159
|
end.compact
|
157
160
|
|
158
|
-
filtered = filters.reduce(run) {|memo, filter| filter.filter(memo,
|
161
|
+
filtered = filters.reduce(run) {|memo, filter| filter.filter(memo, run) }
|
159
162
|
|
160
163
|
content_type 'application/json'
|
161
164
|
Oj.dump(filtered, mode: :compat)
|
data/lib/stackprofiler.rb
CHANGED
@@ -2,18 +2,15 @@ require 'oj'
|
|
2
2
|
require 'tree'
|
3
3
|
require 'coderay'
|
4
4
|
require 'objspace'
|
5
|
-
require 'stackprof'
|
6
5
|
require 'method_source'
|
7
6
|
require 'sinatra/base'
|
8
7
|
require 'active_support/all'
|
9
8
|
require 'sinatra/content_for'
|
10
9
|
require 'net/http'
|
11
10
|
|
12
|
-
require 'stackprofiler/version'
|
13
11
|
require 'stackprofiler/web_ui'
|
14
|
-
require 'stackprofiler/middleware'
|
15
|
-
require 'stackprofiler/data_collector'
|
16
12
|
require 'stackprofiler/run_data_source'
|
13
|
+
require 'stackprofiler/run_code_cache'
|
17
14
|
require 'stackprofiler/utils'
|
18
15
|
|
19
16
|
require 'stackprofiler/filters/js_tree'
|
data/stackprofiler.gemspec
CHANGED
@@ -1,11 +1,8 @@
|
|
1
1
|
# coding: utf-8
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require 'stackprofiler/version'
|
5
2
|
|
6
3
|
Gem::Specification.new do |spec|
|
7
4
|
spec.name = 'stackprofiler'
|
8
|
-
spec.version =
|
5
|
+
spec.version = '0.0.3'
|
9
6
|
spec.authors = ['Aidan Steele']
|
10
7
|
spec.email = ['aidan.steele@glassechidna.com.au']
|
11
8
|
spec.summary = %q{Web UI wrapper for the awesome stackprof profiler.}
|
@@ -30,7 +27,6 @@ Gem::Specification.new do |spec|
|
|
30
27
|
spec.add_development_dependency 'pry-rescue'
|
31
28
|
spec.add_development_dependency 'pry-stack_explorer'
|
32
29
|
|
33
|
-
spec.add_dependency 'stackprof'
|
34
30
|
spec.add_dependency 'method_source'
|
35
31
|
spec.add_dependency 'activesupport'
|
36
32
|
spec.add_dependency 'rubytree', '>= 0.9.5pre4'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stackprofiler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aidan Steele
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-01-
|
11
|
+
date: 2015-01-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -164,20 +164,6 @@ dependencies:
|
|
164
164
|
- - ">="
|
165
165
|
- !ruby/object:Gem::Version
|
166
166
|
version: '0'
|
167
|
-
- !ruby/object:Gem::Dependency
|
168
|
-
name: stackprof
|
169
|
-
requirement: !ruby/object:Gem::Requirement
|
170
|
-
requirements:
|
171
|
-
- - ">="
|
172
|
-
- !ruby/object:Gem::Version
|
173
|
-
version: '0'
|
174
|
-
type: :runtime
|
175
|
-
prerelease: false
|
176
|
-
version_requirements: !ruby/object:Gem::Requirement
|
177
|
-
requirements:
|
178
|
-
- - ">="
|
179
|
-
- !ruby/object:Gem::Version
|
180
|
-
version: '0'
|
181
167
|
- !ruby/object:Gem::Dependency
|
182
168
|
name: method_source
|
183
169
|
requirement: !ruby/object:Gem::Requirement
|
@@ -293,7 +279,6 @@ files:
|
|
293
279
|
- bin/stackprofiler
|
294
280
|
- config.ru
|
295
281
|
- lib/stackprofiler.rb
|
296
|
-
- lib/stackprofiler/data_collector.rb
|
297
282
|
- lib/stackprofiler/filters/build_tree.rb
|
298
283
|
- lib/stackprofiler/filters/compress_tree.rb
|
299
284
|
- lib/stackprofiler/filters/frame_regex_removal.rb
|
@@ -301,10 +286,9 @@ files:
|
|
301
286
|
- lib/stackprofiler/filters/js_tree.rb
|
302
287
|
- lib/stackprofiler/filters/quick_method_removal.rb
|
303
288
|
- lib/stackprofiler/filters/rebase_stack.rb
|
304
|
-
- lib/stackprofiler/
|
289
|
+
- lib/stackprofiler/run_code_cache.rb
|
305
290
|
- lib/stackprofiler/run_data_source.rb
|
306
291
|
- lib/stackprofiler/utils.rb
|
307
|
-
- lib/stackprofiler/version.rb
|
308
292
|
- lib/stackprofiler/web_ui.rb
|
309
293
|
- lib/stackprofiler/web_ui/public/js/stackprofiler.js
|
310
294
|
- lib/stackprofiler/web_ui/public/vendor/css/bootstrap-theme.css
|
@@ -1,37 +0,0 @@
|
|
1
|
-
module Stackprofiler
|
2
|
-
class DataCollector
|
3
|
-
def initialize(app, options)
|
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] || {})
|
19
|
-
end
|
20
|
-
|
21
|
-
def call(env)
|
22
|
-
if @predicate.call(env)
|
23
|
-
out = nil
|
24
|
-
profile = StackProf.run(@stackprof_opts) { out = @app.call env }
|
25
|
-
|
26
|
-
req = Rack::Request.new env
|
27
|
-
run = Run.new req.fullpath, profile, Time.now
|
28
|
-
RunDataSource.runs << run
|
29
|
-
|
30
|
-
out
|
31
|
-
else
|
32
|
-
@app.call env
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
module Stackprofiler
|
2
|
-
class Middleware
|
3
|
-
def initialize(app, options = {})
|
4
|
-
mid = Rack::Builder.new do
|
5
|
-
use Rack::Deflater
|
6
|
-
run WebUI
|
7
|
-
end
|
8
|
-
|
9
|
-
@app = Rack::URLMap.new({'/__stackprofiler' => mid, '/' => DataCollector.new(app, options)})
|
10
|
-
@options = options
|
11
|
-
end
|
12
|
-
|
13
|
-
def call(env)
|
14
|
-
@app.call env
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|