stackprofiler 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/Guardfile +3 -0
  3. data/README.md +42 -2
  4. data/bin/stackprofiler +5 -0
  5. data/config.ru +5 -0
  6. data/lib/stackprofiler/data_collector.rb +17 -4
  7. data/lib/stackprofiler/filters/build_tree.rb +49 -0
  8. data/lib/stackprofiler/filters/compress_tree.rb +11 -14
  9. data/lib/stackprofiler/filters/frame_regex_removal.rb +22 -0
  10. data/lib/stackprofiler/filters/gem_removal.rb +16 -0
  11. data/lib/stackprofiler/filters/js_tree.rb +13 -3
  12. data/lib/stackprofiler/filters/quick_method_removal.rb +18 -0
  13. data/lib/stackprofiler/filters/rebase_stack.rb +25 -0
  14. data/lib/stackprofiler/middleware.rb +1 -1
  15. data/lib/stackprofiler/run_data_source.rb +18 -0
  16. data/lib/stackprofiler/utils.rb +50 -0
  17. data/lib/stackprofiler/version.rb +1 -1
  18. data/lib/stackprofiler/web_ui/public/js/stackprofiler.js +103 -0
  19. data/lib/stackprofiler/web_ui/public/{css → vendor/css}/bootstrap-theme.css +0 -0
  20. data/lib/stackprofiler/web_ui/public/{css → vendor/css}/bootstrap-theme.css.map +0 -0
  21. data/lib/stackprofiler/web_ui/public/{css → vendor/css}/bootstrap-theme.min.css +0 -0
  22. data/lib/stackprofiler/web_ui/public/{css → vendor/css}/bootstrap.css +0 -0
  23. data/lib/stackprofiler/web_ui/public/{css → vendor/css}/bootstrap.css.map +0 -0
  24. data/lib/stackprofiler/web_ui/public/{css → vendor/css}/bootstrap.min.css +0 -0
  25. data/lib/stackprofiler/web_ui/public/{css → vendor/css}/coderay.css +0 -0
  26. data/lib/stackprofiler/web_ui/public/{css → vendor/css}/jquery-ui.css +0 -0
  27. data/lib/stackprofiler/web_ui/public/{css → vendor/css}/splitter.css +0 -0
  28. data/lib/stackprofiler/web_ui/public/vendor/d3/d3.min.js +5 -0
  29. data/lib/stackprofiler/web_ui/public/vendor/epoch/epoch.min.css +1 -0
  30. data/lib/stackprofiler/web_ui/public/vendor/epoch/epoch.min.js +114 -0
  31. data/lib/stackprofiler/web_ui/public/{fonts → vendor/fonts}/glyphicons-halflings-regular.eot +0 -0
  32. data/lib/stackprofiler/web_ui/public/{fonts → vendor/fonts}/glyphicons-halflings-regular.svg +0 -0
  33. data/lib/stackprofiler/web_ui/public/{fonts → vendor/fonts}/glyphicons-halflings-regular.ttf +0 -0
  34. data/lib/stackprofiler/web_ui/public/{fonts → vendor/fonts}/glyphicons-halflings-regular.woff +0 -0
  35. data/lib/stackprofiler/web_ui/public/vendor/jquery-ui/.DS_Store +0 -0
  36. data/lib/stackprofiler/web_ui/public/vendor/jquery-ui/jquery-ui.min.css +7 -0
  37. data/lib/stackprofiler/web_ui/public/vendor/jquery-ui/jquery-ui.min.js +13 -0
  38. data/lib/stackprofiler/web_ui/public/{js → vendor/js}/bootstrap.js +0 -0
  39. data/lib/stackprofiler/web_ui/public/{js → vendor/js}/bootstrap.min.js +0 -0
  40. data/lib/stackprofiler/web_ui/public/{js → vendor/js}/jquery.min.js +0 -0
  41. data/lib/stackprofiler/web_ui/public/{js → vendor/js}/splitter.js +0 -0
  42. data/lib/stackprofiler/web_ui/public/{jstree → vendor/jstree}/jstree.js +0 -0
  43. data/lib/stackprofiler/web_ui/public/{jstree → vendor/jstree}/jstree.min.js +0 -0
  44. data/lib/stackprofiler/web_ui/public/{jstree → vendor/jstree}/themes/default/32px.png +0 -0
  45. data/lib/stackprofiler/web_ui/public/{jstree → vendor/jstree}/themes/default/40px.png +0 -0
  46. data/lib/stackprofiler/web_ui/public/{jstree → vendor/jstree}/themes/default/style.css +0 -0
  47. data/lib/stackprofiler/web_ui/public/{jstree → vendor/jstree}/themes/default/style.min.css +0 -0
  48. data/lib/stackprofiler/web_ui/public/{jstree → vendor/jstree}/themes/default/throbber.gif +0 -0
  49. data/lib/stackprofiler/web_ui/public/{jstree → vendor/jstree}/themes/default-dark/32px.png +0 -0
  50. data/lib/stackprofiler/web_ui/public/{jstree → vendor/jstree}/themes/default-dark/40px.png +0 -0
  51. data/lib/stackprofiler/web_ui/public/{jstree → vendor/jstree}/themes/default-dark/style.css +0 -0
  52. data/lib/stackprofiler/web_ui/public/{jstree → vendor/jstree}/themes/default-dark/style.min.css +0 -0
  53. data/lib/stackprofiler/web_ui/public/{jstree → vendor/jstree}/themes/default-dark/throbber.gif +0 -0
  54. data/lib/stackprofiler/web_ui/public/vendor/lodash/lodash.min.js +56 -0
  55. data/lib/stackprofiler/web_ui/public/{octicons → vendor/octicons}/LICENSE.txt +0 -0
  56. data/lib/stackprofiler/web_ui/public/{octicons → vendor/octicons}/README.md +0 -0
  57. data/lib/stackprofiler/web_ui/public/{octicons → vendor/octicons}/octicons-local.ttf +0 -0
  58. data/lib/stackprofiler/web_ui/public/{octicons → vendor/octicons}/octicons.css +0 -0
  59. data/lib/stackprofiler/web_ui/public/{octicons → vendor/octicons}/octicons.eot +0 -0
  60. data/lib/stackprofiler/web_ui/public/{octicons → vendor/octicons}/octicons.less +0 -0
  61. data/lib/stackprofiler/web_ui/public/{octicons → vendor/octicons}/octicons.svg +0 -0
  62. data/lib/stackprofiler/web_ui/public/{octicons → vendor/octicons}/octicons.ttf +0 -0
  63. data/lib/stackprofiler/web_ui/public/{octicons → vendor/octicons}/octicons.woff +0 -0
  64. data/lib/stackprofiler/web_ui/public/{octicons → vendor/octicons}/sprockets-octicons.scss +0 -0
  65. data/lib/stackprofiler/web_ui/public/vendor/tokenfield/.DS_Store +0 -0
  66. data/lib/stackprofiler/web_ui/public/vendor/tokenfield/bootstrap-tokenfield.min.css +5 -0
  67. data/lib/stackprofiler/web_ui/public/vendor/tokenfield/bootstrap-tokenfield.min.js +7 -0
  68. data/lib/stackprofiler/web_ui/views/code.erb +6 -7
  69. data/lib/stackprofiler/web_ui/views/index.erb +106 -26
  70. data/lib/stackprofiler/web_ui/views/layout.erb +33 -9
  71. data/lib/stackprofiler/web_ui.rb +107 -54
  72. data/lib/stackprofiler.rb +9 -2
  73. data/stackprofiler.gemspec +12 -1
  74. metadata +219 -44
  75. data/lib/stackprofiler/filters/stackprofiler_elision.rb +0 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ec396ab3126b59c0e52b0b0e35abe6a67bfbc39c
4
- data.tar.gz: 035f170274d6bfe42cc4ca507a53069658fe1592
3
+ metadata.gz: 53d49ea799e330d848bf167e2f5fbec763e136cc
4
+ data.tar.gz: 572be41c382be167f0b61bdca9aad96d44d55c30
5
5
  SHA512:
6
- metadata.gz: a3f64d23fd792ae533c17df0b31a21ae6125d1df0b1c7d1f4c890e5609b66bf03280031d370c5613c385d27a0459b6456844b6416531ac365cb63351ace2a999
7
- data.tar.gz: 4e303ce9b29293ea6340bffbc0a0a22950251a47a5f9c765d39757f0441a7c9cc74c55d0d1b850d2bb0577ed4dbe66eb685393ffdf997be20a1b64932e93a617
6
+ metadata.gz: 1ef14a9045d59fdfe64b6d5ad0e0a7e0d33cf641e2b01f2cf4557ba921b042156d752b0ce8e9e0b08140c320ab2e4ad58bf76f39a599bd57ff4fe1d867ffc848
7
+ data.tar.gz: c3ef5a43a3707f168b3026d6bd44763733824df9b533d0879c8f8daa04fbe512a52962f7d1793d7bcbb52fe5bf7889a21a40f680a7a560ce51a31d5cc3ded425
data/Guardfile ADDED
@@ -0,0 +1,3 @@
1
+ guard 'puma', port: 9292 do
2
+ watch(/.+/)
3
+ end
data/README.md CHANGED
@@ -19,6 +19,7 @@ for a job application! Please keep that in mind when you find all the
19
19
  rough edges :)
20
20
 
21
21
  ![Screenshot](http://i.imgur.com/8UJV9Oo.png)
22
+ ![Screenshot](http://i.imgur.com/CsZMXLu.png)
22
23
 
23
24
  ## Installation
24
25
 
@@ -36,7 +37,7 @@ Or install it yourself as:
36
37
 
37
38
  $ gem install stackprofiler
38
39
 
39
- ## Usage
40
+ ## Middleware
40
41
 
41
42
  Using Stackprofiler at this stage is pretty simple on account of there
42
43
  not yet being much flexibility in the way of configuration. This will be
@@ -57,7 +58,7 @@ visible results, e.g. XHR.
57
58
  Once you have profiled a request or two, you can head over to Stackprofiler's
58
59
  GUI. This is located on the same port as your website, albeit at the
59
60
  path `/__stackprofiler`. If your server is running on port 5000 in development,
60
- the link might be [`http://localhost:5000/__stackprofiler`]([http://localhost:5000/__stackprofiler]).
61
+ the link might be [`http://localhost:5000/__stackprofiler`](http://localhost:5000/__stackprofiler).
61
62
 
62
63
  You should now see a page that looks like the screenshot above. Click on any
63
64
  line in the stack trace to see the code for that method annotated with the
@@ -65,6 +66,44 @@ performance characteristics for each line. I would document those here but
65
66
  they are going to change in the next few dev hours, so I'll come back to that
66
67
  later.
67
68
 
69
+ ### Data collection configuration
70
+
71
+ Stackprofiler's operation can be configured by passing in parameters to the
72
+ middleware specified above. While the defaults should suit most applications,
73
+ changing them is easy enough:
74
+
75
+ ```ruby
76
+ config.middleware.use Stackprofiler::Middleware {
77
+ predicate: /profile=true/, # regex form for urls to be profiled
78
+ predicate: proc {|env| true }, # callable form for greater flexibility than regex
79
+ stackprof: { # options to be passed directly through to stackprof, e.g.:
80
+ interval: 1000 # sample every n micro-seconds
81
+ }
82
+ }
83
+ ```
84
+
85
+ ## Ad hoc
86
+
87
+ Sometimes you want to test some code that isn't part of a Rack app - or is
88
+ just cumbersome to run outside of an IRB console. You can test this code
89
+ directly using code very similar to the `stackprof` interface. It works like
90
+ this:
91
+
92
+ Run `$ stackprofiler` from the command line. This will start a Stackprofiler
93
+ server that will listen for incoming profile runs and display them on
94
+ [`http://localhost:9292/__stackprofiler`](http://localhost:9292/__stackprofiler).
95
+ Next, in an IRB console (or, even better, [Pry][3]!)
96
+
97
+ ```ruby
98
+ require 'stackprofiler'
99
+
100
+ Stackprofiler.profile do
101
+ SomeSlowTaskThatNeedsInvestigation.run
102
+ end
103
+ ```
104
+
105
+ Now visit the above URL and a visual breakdown of the code flow will be visible.
106
+
68
107
  ## Contributing
69
108
 
70
109
  1. Fork it ( https://github.com/glassechidna/stackprofiler/fork )
@@ -79,3 +118,4 @@ So much. First todo: write a todo list.
79
118
 
80
119
  [1]: https://github.com/tmm1/stackprof
81
120
  [2]: https://github.com/ruby-prof/ruby-prof
121
+ [3]: https://github.com/pry/pry
data/bin/stackprofiler ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ require 'stackprofiler'
3
+
4
+ app = Rack::URLMap.new '/__stackprofiler' => Stackprofiler::WebUI.new
5
+ Rack::Server.start app: app, Port: 9292
data/config.ru ADDED
@@ -0,0 +1,5 @@
1
+ require 'stackprofiler'
2
+
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
5
+
@@ -1,14 +1,27 @@
1
1
  module Stackprofiler
2
2
  class DataCollector
3
- def initialize(app, options = {})
3
+ def initialize(app, options)
4
4
  @app = app
5
+
6
+ pred = options[:predicate] || /profile=true/
7
+ if pred.respond_to? :call
8
+ @predicate = pred
9
+ else
10
+ regex = Regexp.new pred
11
+
12
+ @predicate = proc do |env|
13
+ req = Rack::Request.new env
14
+ req.fullpath =~ regex
15
+ end
16
+ end
17
+
18
+ @stackprof_opts = {mode: :wall, interval: 1000, raw: true}.merge(options[:stackprof] || {})
5
19
  end
6
20
 
7
21
  def call(env)
8
- puts env['PATH_INFO']
9
- if env['QUERY_STRING'] =~ /profile=true/
22
+ if @predicate.call(env)
10
23
  out = nil
11
- profile = StackProf.run(mode: :wall, interval: 1000, raw: true) { out = @app.call env }
24
+ profile = StackProf.run(@stackprof_opts) { out = @app.call env }
12
25
 
13
26
  req = Rack::Request.new env
14
27
  run = Run.new req.fullpath, profile, Time.now
@@ -0,0 +1,49 @@
1
+ module Stackprofiler
2
+ module Filter
3
+ class BuildTree
4
+ def initialize(options={})
5
+ @options = options
6
+ end
7
+
8
+ def filter run, frames
9
+ stacks = run.stacks
10
+
11
+ root_addr = stacks[0][0].to_s
12
+ root = Tree::TreeNode.new root_addr, {addrs: [root_addr]}
13
+
14
+ stacks.each do |stack|
15
+ prev = root
16
+ iterate stack do |addr|
17
+ addr = addr.to_s
18
+ node = prev[addr]
19
+ if node.nil?
20
+ hash = {count: 0, addrs: [addr]}
21
+ node = Tree::TreeNode.new(addr, hash)
22
+ prev << node
23
+ end
24
+ node.content[:count] +=1
25
+ prev = node
26
+ end
27
+ end
28
+
29
+ if inverted?
30
+ root.children.each {|n| n.content[:open] = false }
31
+ end
32
+
33
+ root
34
+ end
35
+
36
+ def inverted?
37
+ @options[:invert]
38
+ end
39
+
40
+ def iterate stack, &blk
41
+ if inverted?
42
+ stack.reverse_each &blk
43
+ else
44
+ stack.each &blk
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,21 +1,18 @@
1
1
  module Stackprofiler
2
2
  module Filter
3
3
  class CompressTree
4
- def filter root, frames
5
- depths = Hash.new {|h, k| h[k] = [] }
6
- root.each {|n| depths[n.node_depth].push n }
7
- depths.delete 0
4
+ def initialize(options={})
5
+
6
+ end
8
7
 
9
- depths.keys.sort.reverse.each do |depth|
10
- nodes = depths[depth]
11
- nodes.each do |node|
12
- if node.out_degree == 1
13
- hash = node.content.dup
14
- hash[:addrs] = hash[:addrs] + node.first_child.content[:addrs]
15
- repl = Tree::TreeNode.new(node.name, hash)
16
- node.first_child.children.each {|n| repl << n }
17
- node.replace_with repl
18
- end
8
+ def filter root, frames
9
+ root.reverse_depth_first do |node|
10
+ if node.out_degree == 1
11
+ hash = node.content
12
+ hash[:addrs] += node.first_child.content[:addrs]
13
+ repl = Tree::TreeNode.new(node.name, hash)
14
+ node.first_child.children.each {|n| repl << n }
15
+ node.replace_with repl
19
16
  end
20
17
  end
21
18
  root
@@ -0,0 +1,22 @@
1
+ module Stackprofiler
2
+ module Filter
3
+ class FrameRegexRemoval
4
+ include RemoveFramesHelper
5
+
6
+ def initialize(options={})
7
+ @options = options
8
+ end
9
+
10
+ def regexes
11
+ ary = @options[:regexes] || []
12
+ @regexes ||= ary.reject(&:blank?).map {|r| /#{r}/ }.compact
13
+ end
14
+
15
+ def filter root, frames
16
+ remove_frames root, frames do |node, frame|
17
+ regexes.any? {|r| frame[:name] =~ r }
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,16 @@
1
+ module Stackprofiler
2
+ module Filter
3
+ class GemRemoval
4
+ include RemoveFramesHelper
5
+
6
+ def initialize(options={})
7
+ end
8
+
9
+ def filter root, frames
10
+ remove_frames root, frames do |node, frame|
11
+ Gem.path.any? {|p| frame[:file].include?(p) }
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,6 +1,10 @@
1
1
  module Stackprofiler
2
2
  module Filter
3
3
  class JsTree
4
+ def initialize(options={})
5
+
6
+ end
7
+
4
8
  def filter root, frames
5
9
  addrs = root.content[:addrs]
6
10
  name = addrs.first.to_i
@@ -13,9 +17,15 @@ module Stackprofiler
13
17
  end
14
18
  text = escaped.join("<br> ↳ ")
15
19
 
16
- children = root.children.map { |n| filter(n, frames) }
17
- open = frame[:total_samples] > 100
18
- {text: text, children: children, state: {opened: open}, icon: false, data: {addrs: addrs}}
20
+ sorted_children = root.children.sort_by do |child|
21
+ addr = child.content[:addrs].first.to_i
22
+ frame = frames[addr]
23
+ frame[:samples]
24
+ end.reverse
25
+
26
+ children = sorted_children.map { |n| filter(n, frames) }
27
+ open = root.content.has_key?(:open) ? root.content[:open] : frame[:total_samples] > 100
28
+ {text: text, state: {opened: open}, children: children, icon: false, data: {addrs: addrs}}
19
29
  end
20
30
  end
21
31
 
@@ -0,0 +1,18 @@
1
+ module Stackprofiler
2
+ module Filter
3
+ class QuickMethodRemoval
4
+ include RemoveFramesHelper
5
+ attr_accessor :limit
6
+
7
+ def initialize(options={})
8
+ self.limit = options[:limit].try(:to_i) || 0
9
+ end
10
+
11
+ def filter root, frames
12
+ remove_frames root, frames do |node, frame|
13
+ frame[:total_samples] < limit
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,25 @@
1
+ module Stackprofiler
2
+ module Filter
3
+ class RebaseStack
4
+ attr_accessor :top_names
5
+
6
+ def initialize(options={})
7
+ self.top_names = options[:name].presence || RebaseStack.default
8
+ end
9
+
10
+ def filter root, frames
11
+ root.find do |node|
12
+ addr = node.content[:addrs].first.to_i
13
+ top_names.include? frames[addr][:name]
14
+ # frames[addr][:name] == top_names
15
+ end || root
16
+ end
17
+
18
+ class << self
19
+ def default
20
+ ['Stackprofiler::DataCollector#call', 'block in Stackprofiler#profile']
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -6,7 +6,7 @@ module Stackprofiler
6
6
  run WebUI
7
7
  end
8
8
 
9
- @app = Rack::URLMap.new({'/__stackprofiler' => mid, '/' => DataCollector.new(app)})
9
+ @app = Rack::URLMap.new({'/__stackprofiler' => mid, '/' => DataCollector.new(app, options)})
10
10
  @options = options
11
11
  end
12
12
 
@@ -29,6 +29,24 @@ module Stackprofiler
29
29
  def duration
30
30
  profile[:samples] * profile[:interval] / 1e6
31
31
  end
32
+
33
+ def gem_breakdown
34
+ bottom_frames = stacks.map &:last
35
+ paths = bottom_frames.map {|addr| profile[:frames][addr][:file] }
36
+
37
+ gems = paths.map do |p|
38
+ case p
39
+ when %r{gems/(\D\w+)}
40
+ $1
41
+ when %r{#{RbConfig::CONFIG['rubylibdir']}}
42
+ 'stdlib'
43
+ else
44
+ '(app)'
45
+ end
46
+ end
47
+
48
+ gems.group_by {|g| g }.map {|k, v| [k, v.count] }.sort_by(&:last).reverse.to_h
49
+ end
32
50
  end
33
51
 
34
52
  class RunDataSource
@@ -0,0 +1,50 @@
1
+ module Tree
2
+ class TreeNode
3
+ def reverse_depth_first &blk
4
+ depths = Hash.new {|h, k| h[k] = [] }
5
+ root.each {|n| depths[n.node_depth].push n }
6
+ depths.delete 0
7
+ keys = depths.keys.sort.reverse
8
+
9
+ keys.each do |depth|
10
+ nodes = depths[depth]
11
+ nodes.each &blk
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ module Stackprofiler
18
+ def profile
19
+ # todo: pass through options perhaps?
20
+ profile = StackProf.run(mode: :wall, raw: true) { yield }
21
+ # todo: remove terrible hard-coded url
22
+ url = URI::parse 'http://localhost:9292/__stackprofiler/receive'
23
+ headers = {'Content-Type' => 'application/json'}
24
+ req = Net::HTTP::Post.new(url.to_s, headers)
25
+ req.body = Oj.dump profile
26
+ response = Net::HTTP.new(url.host, url.port).start {|http| http.request(req) }
27
+ end
28
+ module_function :profile
29
+
30
+ module Filter
31
+ module RemoveFramesHelper
32
+ def remove_frames root, frames
33
+ root.reverse_depth_first do |node|
34
+ frame = frames[node.name.to_i]
35
+
36
+ if yield node, frame
37
+ parent = node.parent
38
+ node.remove_from_parent!
39
+
40
+ node.children.each do |n|
41
+ n.remove_from_parent!
42
+ parent << n if parent[n.name].nil? # todo: have a think about if/why this "if" is necessary
43
+ end
44
+ end
45
+ end
46
+ root
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,3 +1,3 @@
1
1
  module Stackprofiler
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.2'
3
3
  end
@@ -0,0 +1,103 @@
1
+ $(function() {
2
+ $('#foo').split({
3
+ orientation: 'horizontal',
4
+ limit: 10,
5
+ position: '40%'
6
+ });
7
+
8
+ var $filteredFrames = $('#filtered_frames');
9
+ $filteredFrames.tokenfield();
10
+
11
+ var $tree = $('#jstree_demo');
12
+
13
+ $('#rebase_stack_enabled').change(function(e) {
14
+ var enabled = $(e.target).is(':checked');
15
+ $('#rebase_stack').prop('disabled', !enabled);
16
+ });
17
+
18
+ $('#rebase_stack').autocomplete({
19
+ source: "/__stackprofiler/frame_names?run_id=" + $tree.data('runId'),
20
+ minLength: 2
21
+ });
22
+
23
+ var getParams = function()
24
+ {
25
+ var params = {
26
+ run_id: $tree.data('runId'),
27
+ filters: {}
28
+ };
29
+
30
+ params.filters.filtered_frames = {regexes: $filteredFrames.val().split(', ')};
31
+ params.filters.quick_method_removal = {limit: $('form#options input[name="quick_method_removal"]').val()};
32
+
33
+ if ($('#rebase_stack_enabled').is(':checked')) params.filters.rebase_stack = {name: $('#rebase_stack').val() };
34
+ if ($('form#options input[name="invert"]').is(':checked')) params.filters.build_tree = {invert: true};
35
+ if ($('form#options input[name="remove_gems"]').is(':checked')) params.filters.remove_gems = {};
36
+ if ($('form#options input[name="compress_tree"]').is(':checked')) params.filters.compress_tree = {};
37
+
38
+ return params;
39
+ };
40
+
41
+ var getNodes = function(node, cb) {
42
+ $.post("/__stackprofiler/json", JSON.stringify(getParams()), function(data, status, xhr) {
43
+ cb.call(this, data);
44
+ });
45
+ };
46
+
47
+ $tree.jstree({
48
+ core: {
49
+ data: getNodes
50
+ }
51
+ });
52
+
53
+ $tree.on("changed.jstree", function (e, data) {
54
+ if (!data.node) return;
55
+
56
+ var addrs = data.node.data.addrs;
57
+ var url = "/__stackprofiler/code/" + addrs[0] + "?run_id=" + $tree.data('runId');
58
+ $("#source-display").load(url);
59
+ });
60
+
61
+ $('button#save-options').click(function() {
62
+ $tree.jstree().refresh();
63
+ });
64
+
65
+ $.getJSON('/__stackprofiler/gem_breakdown?run_id=' + $tree.data('runId'), function(data, status, xhr) {
66
+ var threshold = 0.1;
67
+
68
+ var sum = _.reduce(data, function(memo, count, gem) {
69
+ return memo + count;
70
+ }, 0);
71
+
72
+ var reduced = _(data).reduce(function(memo, count, gem) {
73
+ var key = count/sum > threshold ? gem : '(other)';
74
+ var accum = memo[key] || 0;
75
+ memo[key] = accum + count;
76
+ return memo;
77
+ }, {});
78
+
79
+ var f = function(count, gem) {
80
+ return {label: gem, value: count};
81
+ };
82
+
83
+ var ary = _.map(data, f);
84
+ var graph_data = _.map(reduced, f);
85
+
86
+ var colors = d3.scale.category10().range();
87
+
88
+ var rows = _(ary).zip(colors).first(ary.length).map(function(item) {
89
+ var gem = item[0];
90
+ var percent = (gem.value / sum * 100).toFixed(1);
91
+ var has_color = gem.value/sum > threshold;
92
+ var style = has_color ? 'background-color: ' + item[1] + ';' : '';
93
+ return $('<tr><td><div class="color-box" style="' + style + '"></div>' + gem.label + '</td><td>' + gem.value + '</td><td>' + percent + '%</td></tr>');
94
+ }).value();
95
+
96
+ $('#gem-pie').epoch({
97
+ type: 'pie',
98
+ data: graph_data
99
+ });
100
+
101
+ $('#gem-table').append(rows);
102
+ });
103
+ });