flamegraph 0.0.5 → 0.0.6

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: 00a8e007306576db9b6d761785ef83a17dafaf42
4
- data.tar.gz: 82efe147b7295c832b85ee2ae74ae8e351f1a701
3
+ metadata.gz: f613760265ad137dae994ad575e5d90397506e1f
4
+ data.tar.gz: b0b4968018697e4308746c72a7f0b9ac88fce97a
5
5
  SHA512:
6
- metadata.gz: 76a760121a2ebec486d6d1f76b4d5414f8d6bf2d1ff7eb82276fdd375c9324ac546d414f353b6d89f0aebfbe5307b6bb49cba4ba38216aa5dddc8065bd2140c0
7
- data.tar.gz: 20e0dee3f38a4860a3cedb1ff0cb836e40519079c85321e5e693c0810467f9b72990e074dd5ef46d05d478b7acb1e43aaf5f5ff94db0d50a336c740b8094c1f7
6
+ metadata.gz: 046ab66a7a510a2f9bd69da94e0b6c610d00e53a73ee3d98e38298075507fc73511fe92d4736c50253e957c8e453da5f9ea174d2577a21bb324a2466db030d03
7
+ data.tar.gz: b8346b54a13bf842a3a584a1f6149a0467b904b1a53d2c72720031c63c42b40268d1b38c4e2c9746c47276fcd5045c98e013c93eae3a27d977f60a7a19155bc4
data/CHANGELOG CHANGED
@@ -17,3 +17,9 @@
17
17
 
18
18
  - 0.0.5
19
19
  - Correct embed
20
+
21
+ 12-Nov-2014
22
+
23
+ - 0.0.6
24
+ - Better analysis with stackprof on ruby 2.1
25
+ - New window displaying the backtrace when clicking on element
data/Gemfile CHANGED
@@ -2,3 +2,7 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in flamegraph.gemspec
4
4
  gemspec
5
+
6
+ # both are optional, depending on platform
7
+ gem 'fast_stack'
8
+ gem 'stackprof', platform: :mri_21
data/Guardfile CHANGED
@@ -1,6 +1,6 @@
1
1
  guard :minitest do
2
2
  # with Minitest::Unit
3
- watch(%r{^test/(.*)\/?test_(.*)\.rb$})
3
+ watch(%r{^test/(.*)\/?test_(.*)\.rb$}) { |s| s }
4
4
  watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/test_#{m[2]}.rb" }
5
5
  watch(%r{^test/test_helper\.rb$}) { 'test' }
6
6
 
@@ -1,5 +1,21 @@
1
1
  require "json"
2
- require "fast_stack"
2
+
3
+ if RUBY_VERSION >= "2.1.0".freeze
4
+ begin
5
+ require "stackprof"
6
+ require "flamegraph/stackprof_sampler"
7
+ rescue
8
+ STDERR.puts "Please require the stackprof gem falling back to fast_stack"
9
+ require "fast_stack"
10
+ end
11
+ else
12
+ begin
13
+ require "fast_stack"
14
+ rescue
15
+ STDERR.puts "Please require the fast_stack gem, note flamegraph is only supported on Ruby 2.0 and above"
16
+ end
17
+ end
18
+
3
19
  require "flamegraph/version"
4
20
  require "flamegraph/renderer"
5
21
 
@@ -7,9 +23,16 @@ module Flamegraph
7
23
  def self.generate(filename=nil, opts = {})
8
24
  fidelity = opts[:fidelity] || 0.5
9
25
 
10
- backtraces = FastStack.profile(fidelity) do
11
- yield
12
- end
26
+ backtraces =
27
+ if defined? StackProf
28
+ StackProfSampler.collect(fidelity) do
29
+ yield
30
+ end
31
+ else
32
+ FastStack.profile(fidelity) do # , opts[:mode] || :ruby) do
33
+ yield
34
+ end
35
+ end
13
36
 
14
37
  embed_resources = (filename && !opts.key?(:embed_resources)) || opts[:embed_resources]
15
38
 
@@ -7,7 +7,7 @@
7
7
 
8
8
  <title>Flame Graph of Page</title>
9
9
  <style>
10
- .info {height: 40px;}
10
+ .info {min-height: 50px; margin: 10px; }
11
11
  .legend div {
12
12
  display: block;
13
13
  float: left;
@@ -16,13 +16,62 @@
16
16
  padding: 4px;
17
17
  height: 50px;
18
18
  }
19
+ .code {
20
+ font-family: Consolas, Menlo, Monaco, "Lucida Console", "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Courier New", monospace;
21
+ }
22
+ #frameinfo table .sample-info {
23
+ width: 200px;
24
+ color: #333;
25
+ font-size: 12px;
26
+ }
27
+ #frameinfo table {
28
+ border-collapse: collapse;
29
+ }
30
+ #frameinfo table tr {
31
+ border-bottom: 1px solid #eee;
32
+ margin-top: 5px;
33
+ }
34
+ #frameinfo td {
35
+ padding: 5px;
36
+ }
37
+ #frameinfo-wrapper {
38
+ position: fixed;
39
+ z-index: 1;
40
+ opacity: 0.7;
41
+ background-color: black;
42
+ width: 100%;
43
+ height: 100%;
44
+ top: 0;
45
+ left: 0;
46
+ display: none;
47
+ }
48
+ #frameinfo {
49
+ position: fixed;
50
+ padding: 10px;
51
+ z-index: 2;
52
+ opacity: 1.0;
53
+ top: 0;
54
+ margin-top: 50px;
55
+ margin-left: 40px;
56
+ width: 80%;
57
+ height: 80%;
58
+ background-color: white;
59
+ border: 2px solid #666;
60
+ display: none;
61
+ overflow: auto;
62
+ }
19
63
  </style>
20
64
  </head>
21
65
  <body>
22
66
  <div class="graph"></div>
23
- <div class="info"></div>
67
+ <div class="info code"></div>
24
68
  <div class="legend"></div>
25
-
69
+ <div id="frameinfo-wrapper"></div>
70
+ <div id="frameinfo">
71
+ <h3>Frame Info</h3>
72
+ <table>
73
+ </table>
74
+ </div>
26
75
  <script>
27
76
 
28
77
  var data = /**DATA**/;
@@ -84,7 +133,12 @@ var guessGem = function(frame)
84
133
  var guessMethod = function(frame) {
85
134
  var split = frame.split('`');
86
135
  if(split.length == 2) {
87
- return split[1].split("'")[0];
136
+ var fullMethod = split[1].split("'")[0];
137
+ split = fullMethod.split("#");
138
+ if(split.length == 2) {
139
+ return split[1];
140
+ }
141
+ return split[0];
88
142
  }
89
143
  return '?';
90
144
  }
@@ -101,7 +155,7 @@ var guessFile = function(frame) {
101
155
  $.each(data, function(){
102
156
  maxX = Math.max(maxX, this.x + this.width);
103
157
  maxY = Math.max(maxY, this.y);
104
- this.shortName = /* guessGem(this.frame) + " " + guessFile(this.frame) + " " */ guessMethod(this.frame);
158
+ this.shortName = guessMethod(this.frame);
105
159
  });
106
160
 
107
161
  var width = $(window).width();
@@ -194,6 +248,87 @@ var mouseout = function(d) {
194
248
  .attr('opacity',1);
195
249
  };
196
250
 
251
+ var backtrace = function(frame){
252
+ for(var i=0; i<data.length; i++){
253
+ if(frame === data[i]){ break; }
254
+ }
255
+
256
+ frames = [frame];
257
+ var depth = frame.y;
258
+
259
+ while(i > 0){
260
+ if(depth == -1) break;
261
+
262
+ if(data[i].y === depth-1) {
263
+ frames.push(data[i]);
264
+ depth--;
265
+ }
266
+
267
+ i--;
268
+ }
269
+
270
+ return frames;
271
+ }
272
+
273
+ $('#frameinfo-wrapper').click(function(d){
274
+ $(this).hide();
275
+ $('#frameinfo').hide();
276
+ });
277
+
278
+ var click = function(d){
279
+ var trace = backtrace(d);
280
+
281
+ var link = function(path, dest){
282
+ return path.replace(/[^\/]+:\d+/, function(x){ return "<a target='_blank' href='"+ dest +"'>" + x + "</a>"})
283
+ };
284
+
285
+ var linkify = function(path){
286
+ var split = path.split("/")[0].split("-");
287
+ if(["activerecord","actionpack","railties","activesupport", "rails"].indexOf(split[0]) > -1) {
288
+ var github = "https://github.com/rails/rails/blob/";
289
+ var file = path.split(":")[0].split("/");
290
+ if(split[0] === "rails") {
291
+ file.shift();
292
+ } else {
293
+ file[0] = split[0];
294
+ }
295
+
296
+ github += (split[1].length < 6 ? "v" : "") + split[1] + "/";
297
+ github += file.join("/");
298
+ github += "#L" + parseInt(path.split(":")[1]);
299
+
300
+ return link(path, github);
301
+ }
302
+ return path;
303
+ }
304
+
305
+ var simplify = function(frame){
306
+ var split = frame.split('/gems/');
307
+ if(split.length > 1){
308
+ var path = linkify(split.pop());
309
+ return "<span class='full-location'>" + split.join('/gems/') + "/</span>" + path;
310
+ } else {
311
+ return frame;
312
+ }
313
+ }
314
+
315
+ var table = trace.map(function(f){
316
+ var i = info[f.frame];
317
+ return "<tr><td class='code'>" + simplify(f.frame) + "</td><td class='sample-info'>" +
318
+ samplePercent(i.samples.length, f.topFrame ? f.topFrame.exclusiveCount : 0) +
319
+ "</td></tr>";
320
+ }).join("\n");
321
+
322
+ var table = $('#frameinfo table').html(table);
323
+ table.find(".full-location").hide().after("<span class='expand'>&hellip; </span>");
324
+ table.find(".expand").css({cursor: "pointer"}).click(function(){
325
+ $(this).hide().parent().find(".full-location").show();
326
+ });
327
+
328
+ $('#frameinfo-wrapper').show();
329
+ $('#frameinfo').show();
330
+ };
331
+
197
332
  // http://stackoverflow.com/a/7419630
198
333
  var rainbow = function(numOfSteps, step) {
199
334
  // This function generates vibrant, "evenly spaced" colours (i.e. no clustering). This is ideal for creating easily distiguishable vibrant markers in Google Maps and other apps.
@@ -291,7 +426,7 @@ function fontSize(d,i) {
291
426
  var height = yScale(1);
292
427
  var length = 0;
293
428
  d3.select(this).style("font-size", size + "px").text(word);
294
- while(((this.getBBox().width >= width) || (this.getBBox().height >= height)) && (size > 12))
429
+ while((size > 12.1) && ((this.getBBox().width >= width) || (this.getBBox().height >= height)))
295
430
  {
296
431
  size -= 0.1;
297
432
  d3.select(this).style("font-size", size + "px");
@@ -323,7 +458,9 @@ svg.selectAll("g")
323
458
  return i.color;
324
459
  })
325
460
  .on("mouseover", mouseover)
326
- .on("mouseout", mouseout);
461
+ .on("mouseout", mouseout)
462
+ .on("click", click)
463
+ .attr("cursor", "pointer");
327
464
 
328
465
  d3.select(this)
329
466
  .append("text")
@@ -331,7 +468,9 @@ svg.selectAll("g")
331
468
  .attr("y",function(d) { return yScale(maxY - d.y);})
332
469
  .on("mouseover", mouseover)
333
470
  .on("mouseout", mouseout)
471
+ .on("click", click)
334
472
  .each(fontSize)
473
+ .attr("cursor", "pointer")
335
474
  .attr("display", "none");
336
475
 
337
476
  });
@@ -22,13 +22,14 @@ class Flamegraph::Renderer
22
22
  end
23
23
 
24
24
  def graph_data
25
- height = 0
26
-
27
25
  table = []
28
26
  prev = []
29
27
 
30
28
  # a 2d array makes collapsing easy
31
29
  @stacks.each_with_index do |stack, pos|
30
+
31
+ next unless stack
32
+
32
33
  col = []
33
34
 
34
35
  stack.reverse.map{|r| r.to_s}.each_with_index do |frame, i|
@@ -79,7 +80,7 @@ class Flamegraph::Renderer
79
80
  end
80
81
 
81
82
  def read(file)
82
- body = IO.read(::File.expand_path(file, ::File.dirname(__FILE__)))
83
+ IO.read(::File.expand_path(file, ::File.dirname(__FILE__)))
83
84
  end
84
85
 
85
86
  end
@@ -0,0 +1,42 @@
1
+ class Flamegraph::StackProfSampler
2
+ def self.collect(fidelity=0.5)
3
+
4
+ result = StackProf.run(mode: :wall,
5
+ raw: true,
6
+ aggregate: false,
7
+ interval: (fidelity * 1000).to_i) do
8
+ yield
9
+ end
10
+
11
+
12
+ stacks = []
13
+ stack = []
14
+
15
+ return [] unless result[:raw]
16
+
17
+ length = nil
18
+ result[:raw].each do |i|
19
+ if length.nil?
20
+ length = i
21
+ next
22
+ end
23
+
24
+ if length > 0
25
+ frame = result[:frames][i]
26
+ frame = "#{frame[:file]}:#{frame[:line]}:in `#{frame[:name]}'"
27
+ stack << frame.to_s
28
+ length -= 1
29
+ next
30
+ end
31
+
32
+ i.times do
33
+ stacks << stack.reverse
34
+ end
35
+
36
+ stack = []
37
+ length = nil
38
+ end
39
+
40
+ stacks
41
+ end
42
+ end
@@ -1,3 +1,3 @@
1
1
  module Flamegraph
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.6"
3
3
  end
@@ -0,0 +1,36 @@
1
+ if RUBY_VERSION >= "2.1.0"
2
+
3
+ require 'stackprof'
4
+ require 'test_helper'
5
+ require 'flamegraph/stackprof_sampler'
6
+
7
+ class TestStackprofSampler < Minitest::Test
8
+
9
+ def idle(duration)
10
+ r, w = IO.pipe
11
+ IO.select([r], nil, nil, duration)
12
+ ensure
13
+ r.close
14
+ w.close
15
+ end
16
+
17
+ def test_sample_collection
18
+
19
+ samples = Flamegraph::StackProfSampler.collect do
20
+ idle 0.005
21
+ end
22
+
23
+ assert(samples.count > 3, "Should get more than 3 samples in 5 millisecs")
24
+ end
25
+
26
+ def test_fidelity
27
+ samples = Flamegraph::StackProfSampler.collect(10) do
28
+ idle 0.005
29
+ end
30
+
31
+ assert(samples.count <= 1, "Should get a max of 1 sample got #{samples.count}")
32
+ end
33
+
34
+ end
35
+
36
+ end
metadata CHANGED
@@ -1,97 +1,97 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flamegraph
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-12-05 00:00:00.000000000 Z
11
+ date: 2014-11-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fast_stack
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '1.3'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.3'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: minitest
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - '>='
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - '>='
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: guard
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - '>='
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
75
  version: '0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - '>='
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: guard-minitest
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - '>='
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
89
  version: '0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - '>='
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  description: Flamegraph support for arbitrary ruby apps
@@ -101,7 +101,7 @@ executables: []
101
101
  extensions: []
102
102
  extra_rdoc_files: []
103
103
  files:
104
- - .gitignore
104
+ - ".gitignore"
105
105
  - CHANGELOG
106
106
  - Gemfile
107
107
  - Guardfile
@@ -119,11 +119,13 @@ files:
119
119
  - lib/flamegraph/lodash.min.js
120
120
  - lib/flamegraph/renderer.rb
121
121
  - lib/flamegraph/sampler.rb
122
+ - lib/flamegraph/stackprof_sampler.rb
122
123
  - lib/flamegraph/version.rb
123
124
  - test/test_fast_stack_sampler.rb
124
125
  - test/test_helper.rb
125
126
  - test/test_renderer.rb
126
127
  - test/test_sampler.rb
128
+ - test/test_stackprof_sampler.rb
127
129
  homepage: ''
128
130
  licenses:
129
131
  - MIT
@@ -134,17 +136,17 @@ require_paths:
134
136
  - lib
135
137
  required_ruby_version: !ruby/object:Gem::Requirement
136
138
  requirements:
137
- - - '>='
139
+ - - ">="
138
140
  - !ruby/object:Gem::Version
139
141
  version: '0'
140
142
  required_rubygems_version: !ruby/object:Gem::Requirement
141
143
  requirements:
142
- - - '>='
144
+ - - ">="
143
145
  - !ruby/object:Gem::Version
144
146
  version: '0'
145
147
  requirements: []
146
148
  rubyforge_project:
147
- rubygems_version: 2.0.14
149
+ rubygems_version: 2.2.2
148
150
  signing_key:
149
151
  specification_version: 4
150
152
  summary: Flamegraph support for arbitrary ruby apps
@@ -153,3 +155,4 @@ test_files:
153
155
  - test/test_helper.rb
154
156
  - test/test_renderer.rb
155
157
  - test/test_sampler.rb
158
+ - test/test_stackprof_sampler.rb