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