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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 53d49ea799e330d848bf167e2f5fbec763e136cc
4
- data.tar.gz: 572be41c382be167f0b61bdca9aad96d44d55c30
3
+ metadata.gz: cef292287c198e673dec3311397b8a0f3124bdf5
4
+ data.tar.gz: cc122485fbbca1605e100a0ca5e80e45aa979273
5
5
  SHA512:
6
- metadata.gz: 1ef14a9045d59fdfe64b6d5ad0e0a7e0d33cf641e2b01f2cf4557ba921b042156d752b0ce8e9e0b08140c320ab2e4ad58bf76f39a599bd57ff4fe1d867ffc848
7
- data.tar.gz: c3ef5a43a3707f168b3026d6bd44763733824df9b533d0879c8f8daa04fbe512a52962f7d1793d7bcbb52fe5bf7889a21a40f680a7a560ce51a31d5cc3ded425
6
+ metadata.gz: e5dc5a401932e640d444d32bee79d2ef70a5f01bb017b014192e21fcab25db82e1b6877f3fd34918a3c01ee1b19684c1b228cf1bd4908df32a1a76d25e69b22e
7
+ data.tar.gz: 93f5d521e61ab24d5ac34ade6be06cc22a6497eacb977a361e1e8009b7ec8ad4b84dd861214592c364ec79d275babb125ebc01ab40f6d93ac8c74383780bae83
data/Gemfile CHANGED
@@ -2,3 +2,5 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in stackprofiler.gemspec
4
4
  gemspec
5
+ gem 'stackprofx', path: '/Users/aidan/dev/stackprofx'
6
+ gem 'stackprofiler-middleware', path: '/Users/aidan/dev/stackprofiler-middleware'
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
- ## Middleware
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
- Using Stackprofiler at this stage is pretty simple on account of there
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, add Stackprofiler's middleware somewhere in your Rack
47
- chain, e.g.:
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
- config.middleware.use Stackprofiler::Middleware
39
+ ## Other gems
50
40
 
51
- Now start your server like normal. If you wish to profile a request,
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
- Once you have profiled a request or two, you can head over to Stackprofiler's
59
- GUI. This is located on the same port as your website, albeit at the
60
- path `/__stackprofiler`. If your server is running on port 5000 in development,
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
- You should now see a page that looks like the screenshot above. Click on any
64
- line in the stack trace to see the code for that method annotated with the
65
- performance characteristics for each line. I would document those here but
66
- they are going to change in the next few dev hours, so I'll come back to that
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
- ### Data collection configuration
52
+ Head on over to the README for that gem to learn how to use it.
70
53
 
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
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 using code very similar to the `stackprof` interface. It works like
90
- this:
58
+ directly very easily using the [`pry-stackprofiler`][4] gem in the [Pry][5]
59
+ REPL.
91
60
 
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]!)
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
- require 'stackprofiler'
99
-
100
- Stackprofiler.profile do
101
- SomeSlowTaskThatNeedsInvestigation.run
66
+ Pry.profile do
67
+ sleep 0.3
68
+ sleep 0.4
69
+ sleep 0.1
102
70
  end
103
71
  ```
104
72
 
105
- Now visit the above URL and a visual breakdown of the code flow will be visible.
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/pry/pry
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
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'stackprofiler'
3
3
 
4
- app = Rack::URLMap.new '/__stackprofiler' => Stackprofiler::WebUI.new
5
- Rack::Server.start app: app, Port: 9292
4
+ app = Rack::URLMap.new '/' => Stackprofiler::WebUI.new
5
+ Rack::Server.start app: app, Port: 9260
data/config.ru CHANGED
@@ -1,5 +1,7 @@
1
1
  require 'stackprofiler'
2
+ require 'stackprofiler/middleware'
2
3
 
3
- run Rack::URLMap.new '/__stackprofiler' => Stackprofiler::WebUI.new(file: '/Users/aidan/dev/better-data/yy.json')
4
- # run Rack::URLMap.new '/__stackprofiler' => Stackprofiler::WebUI.new
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, frames
8
+ def filter run, run2
9
9
  stacks = run.stacks
10
10
 
11
- root_addr = stacks[0][0].to_s
12
- root = Tree::TreeNode.new root_addr, {addrs: [root_addr]}
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
- addr = addr.to_s
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
@@ -5,7 +5,7 @@ module Stackprofiler
5
5
 
6
6
  end
7
7
 
8
- def filter root, frames
8
+ def filter root, run
9
9
  root.reverse_depth_first do |node|
10
10
  if node.out_degree == 1
11
11
  hash = node.content
@@ -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, frames
16
- remove_frames root, frames do |node, frame|
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
@@ -6,8 +6,8 @@ module Stackprofiler
6
6
  def initialize(options={})
7
7
  end
8
8
 
9
- def filter root, frames
10
- remove_frames root, frames do |node, frame|
9
+ def filter root, run
10
+ remove_frames root, run do |node, frame|
11
11
  Gem.path.any? {|p| frame[:file].include?(p) }
12
12
  end
13
13
  end
@@ -5,25 +5,26 @@ module Stackprofiler
5
5
 
6
6
  end
7
7
 
8
- def filter root, frames
8
+ def filter root, run
9
9
  addrs = root.content[:addrs]
10
- name = addrs.first.to_i
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.to_i]
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.to_i
22
- frame = frames[addr]
23
- frame[:samples]
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, frames) }
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, frames
12
- remove_frames root, frames do |node, frame|
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 :top_names
4
+ attr_accessor :manual
5
5
 
6
6
  def initialize(options={})
7
- self.top_names = options[:name].presence || RebaseStack.default
7
+ self.manual = options[:name].presence
8
8
  end
9
9
 
10
- def filter root, frames
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
- top_names.include? frames[addr][:name]
14
- # frames[addr][:name] == top_names
15
- end || root
16
- end
16
+ frame = run.profile[:frames][addr]
17
17
 
18
- class << self
19
- def default
20
- ['Stackprofiler::DataCollector#call', 'block in Stackprofiler#profile']
21
- end
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 stacks
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
- stacks.push these_frames
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
- paths = bottom_frames.map {|addr| profile[:frames][addr][:file] }
41
+ frames = bottom_frames.map {|addr| profile[:frames][addr] }
36
42
 
37
- gems = paths.map do |p|
38
- case p
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']}}
@@ -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, frames
20
+ def remove_frames root, run
33
21
  root.reverse_depth_first do |node|
34
- frame = frames[node.name.to_i]
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: "/__stackprofiler/frame_names?run_id=" + $tree.data('runId'),
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("/__stackprofiler/json", JSON.stringify(getParams()), function(data, status, xhr) {
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 = "/__stackprofiler/code/" + addrs[0] + "?run_id=" + $tree.data('runId');
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('/__stackprofiler/gem_breakdown?run_id=' + $tree.data('runId'), function(data, status, xhr) {
66
- var threshold = 0.1;
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 sum = _.reduce(data, function(memo, count, gem) {
69
- return memo + count;
70
- }, 0);
70
+ var summer = function(memo, item) {
71
+ return memo + item.value;
72
+ };
71
73
 
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
- }, {});
74
+ var sum = _.reduce(objs, summer, 0);
78
75
 
79
- var f = function(count, gem) {
80
- return {label: gem, value: count};
76
+ var threshold = 0.1;
77
+ var thresholder = function(item) {
78
+ return item.value/sum > threshold;
81
79
  };
82
80
 
83
- var ary = _.map(data, f);
84
- var graph_data = _.map(reduced, f);
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 = _(ary).zip(colors).first(ary.length).map(function(item) {
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="/__stackprofiler/?run_id=<%= run_id %>"><%= run.url %></a>&nbsp;(<b><%= run.duration %>s</b>)</li>
6
+ <li><a href="/?run_id=<%= run_id %>"><%= run.url %></a>&nbsp;(<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="/__stackprofiler/js/stackprofiler.js"></script>
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. <%= Stackprofiler::Filter::RebaseStack.default.first %>">
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="/__stackprofiler/vendor/css/bootstrap.min.css" />
4
- <link rel="stylesheet" href="/__stackprofiler/vendor/css/splitter.css" />
5
- <link rel="stylesheet" href="/__stackprofiler/vendor/jstree/themes/default/style.min.css" />
6
- <link rel="stylesheet" href="/__stackprofiler/vendor/css/coderay.css" />
7
- <link rel="stylesheet" href="/__stackprofiler/vendor/octicons/octicons.css" />
8
- <link rel="stylesheet" href="/__stackprofiler/vendor/tokenfield/bootstrap-tokenfield.min.css" />
9
- <link rel="stylesheet" href="/__stackprofiler/vendor/jquery-ui/jquery-ui.min.css" />
10
- <link rel="stylesheet" href="/__stackprofiler/vendor/epoch/epoch.min.css" />
11
- <script src="/__stackprofiler/vendor/js/jquery.min.js"></script>
12
- <script src="/__stackprofiler/vendor/js/bootstrap.min.js"></script>
13
- <script src="/__stackprofiler/vendor/jstree/jstree.js"></script>
14
- <script src="/__stackprofiler/vendor/js/splitter.js"></script>
15
- <script src="/__stackprofiler/vendor/tokenfield/bootstrap-tokenfield.min.js"></script>
16
- <script src="/__stackprofiler/vendor/jquery-ui/jquery-ui.min.js"></script>
17
- <script src="/__stackprofiler/vendor/d3/d3.min.js"></script>
18
- <script src="/__stackprofiler/vendor/epoch/epoch.min.js"></script>
19
- <script src="/__stackprofiler/vendor/lodash/lodash.min.js"></script>
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 {
@@ -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
- # require 'better_errors'
23
- # use BetterErrors::Middleware
24
- # BetterErrors.application_root = __dir__
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 = MethodSource::source_helper([@file, @first_line]).strip_heredoc
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
- # Oj.dump(run)
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 = Oj.load(data)
109
- run = Run.new 'unknown', json, Time.now
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
- run.gem_breakdown.to_json
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, frames) }
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'
@@ -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 = Stackprofiler::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.2
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-04 00:00:00.000000000 Z
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/middleware.rb
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
-
@@ -1,3 +0,0 @@
1
- module Stackprofiler
2
- VERSION = '0.0.2'
3
- end