stackprofiler 0.0.2 → 0.0.3
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/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
|
-
|