stackflame 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1b3079b183df5c34aa2346423f760c4f46447cd7
4
+ data.tar.gz: fd0ff8bde7e03b081eb2436c7465928a5b39e084
5
+ SHA512:
6
+ metadata.gz: cc074f970f4423ce0d9cbc1f59832836e70b4c51b4740c8abb7c99d010f4eb0b52bd192fb772bcd33780a89ab530ec8cd58c915b5ae96a2c8e1c6b65ee2d95f7
7
+ data.tar.gz: b85c35082cb6ef953ca518d2033199ddb12979e82b63d81d5e0ebdbbbf51abe3e9050d14dac08f635c68cee2b5d4233ebaf8a847c7e434d93647c7177a4eb05f
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ *.sqlite3
15
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in stackflame.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Takashi Kokubun
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # Stackflame
2
+
3
+ Stackflame provides a simple API to deal with Flamegraph of [stackprof](https://github.com/tmm1/stackprof).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ $ gem install stackflame
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ruby
14
+ require "stackflame"
15
+
16
+ Stackflame.profile do
17
+ # some code to profile
18
+ 100.times { User.create }
19
+ end
20
+ ```
21
+
22
+ If you use OSX, flamegraph will be opened with your default browser.
23
+
24
+ ![](http://i.gyazo.com/47871c2de985298c61d0fcca041a34d0.png)
25
+
26
+ ## License
27
+
28
+ MIT License
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,23 @@
1
+ require "active_record"
2
+ require "yaml"
3
+ require "stackflame"
4
+
5
+ Stackflame.profile do
6
+ ActiveRecord::Base.configurations["profile"] = {
7
+ adapter: "sqlite3",
8
+ database: "test.sqlite3",
9
+ pool: 5,
10
+ timeout: 5000,
11
+ }
12
+ ActiveRecord::Base.establish_connection :profile
13
+
14
+ ActiveRecord::Schema.define do
15
+ create_table :users, force: true do |t|
16
+ t.column :created_at, :datetime
17
+ t.column :updated_at, :datetime
18
+ end
19
+ end
20
+
21
+ class User < ActiveRecord::Base; end
22
+ 100.times { User.create }
23
+ end
@@ -0,0 +1,3 @@
1
+ class Stackflame
2
+ VERSION = "0.0.1"
3
+ end
data/lib/stackflame.rb ADDED
@@ -0,0 +1,43 @@
1
+ require "stackflame/version"
2
+ require "stackprof"
3
+
4
+ class Stackflame
5
+ def self.profile(mode: :cpu, &block)
6
+ stackflame = self.new
7
+ stackflame.run(mode, &block)
8
+ stackflame.open_flamegraph
9
+ end
10
+
11
+ def run(mode, &block)
12
+ result = StackProf.run(mode: mode, raw: true, &block)
13
+
14
+ File.open(temp_js_path, 'w') do |f|
15
+ StackProf::Report.new(result).print_flamegraph(f)
16
+ end
17
+ end
18
+
19
+ def open_flamegraph
20
+ if system("which osascript > /dev/null")
21
+ # NOTE: `open` can't open path with query string
22
+ `osascript -e 'open location "#{flamegraph_path}"'`
23
+ else
24
+ puts "This OS is not supported. Please open: #{flamegraph_path}"
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def flamegraph_path
31
+ viewer_path = File.expand_path('../../vendor/viewer.html', __FILE__)
32
+ "file://#{viewer_path}?data=#{temp_js_path}"
33
+ end
34
+
35
+ def temp_js_path
36
+ return @js_path if @js_path
37
+
38
+ temp = `mktemp /tmp/stackflame-XXXXXXXX`.strip
39
+ @js_path = "#{temp}.js"
40
+ `mv #{temp} #{@js_path}`
41
+ @js_path
42
+ end
43
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'stackflame/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "stackflame"
8
+ spec.version = Stackflame::VERSION
9
+ spec.authors = ["Takashi Kokubun"]
10
+ spec.email = ["takashikkbn@gmail.com"]
11
+ spec.summary = %q{Stackflame provides a simple API to deal with Flamegraph of stackprof.}
12
+ spec.description = %q{Stackflame provides a simple API to deal with Flamegraph of stackprof.}
13
+ spec.homepage = "https://github.com/k0kubun/stackflame"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "stackprof", "~> 0.2.7"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.7"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "activerecord", "~> 4.1"
26
+ spec.add_development_dependency "sqlite3"
27
+ spec.add_development_dependency "pry"
28
+ end
@@ -0,0 +1,357 @@
1
+ var guessGem = function(frame) {
2
+ var split = frame.split('/gems/');
3
+ if(split.length == 1) {
4
+ split = frame.split('/app/');
5
+ if(split.length == 1) {
6
+ split = frame.split('/lib/');
7
+ } else {
8
+ return split[split.length-1].split('/')[0]
9
+ }
10
+
11
+ split = split[Math.max(split.length-2,0)].split('/');
12
+ return split[split.length-1].split(':')[0];
13
+ }
14
+ else
15
+ {
16
+ return split[split.length -1].split('/')[0].split('-', 2)[0];
17
+ }
18
+ }
19
+
20
+ var color = function() {
21
+ var r = parseInt(205 + Math.random() * 50);
22
+ var g = parseInt(Math.random() * 230);
23
+ var b = parseInt(Math.random() * 55);
24
+ return "rgb(" + r + "," + g + "," + b + ")";
25
+ }
26
+
27
+ // http://stackoverflow.com/a/7419630
28
+ var rainbow = function(numOfSteps, step) {
29
+ // 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.
30
+ // Adam Cole, 2011-Sept-14
31
+ // HSV to RBG adapted from: http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
32
+ var r, g, b;
33
+ var h = step / numOfSteps;
34
+ var i = ~~(h * 6);
35
+ var f = h * 6 - i;
36
+ var q = 1 - f;
37
+ switch(i % 6){
38
+ case 0: r = 1, g = f, b = 0; break;
39
+ case 1: r = q, g = 1, b = 0; break;
40
+ case 2: r = 0, g = 1, b = f; break;
41
+ case 3: r = 0, g = q, b = 1; break;
42
+ case 4: r = f, g = 0, b = 1; break;
43
+ case 5: r = 1, g = 0, b = q; break;
44
+ }
45
+ var c = "#" + ("00" + (~ ~(r * 255)).toString(16)).slice(-2) + ("00" + (~ ~(g * 255)).toString(16)).slice(-2) + ("00" + (~ ~(b * 255)).toString(16)).slice(-2);
46
+ return (c);
47
+ }
48
+
49
+ // http://stackoverflow.com/questions/1960473/unique-values-in-an-array
50
+ var getUnique = function(orig) {
51
+ var o = {}, a = []
52
+ for (var i = 0; i < orig.length; i++) o[orig[i]] = 1
53
+ for (var e in o) a.push(e)
54
+ return a
55
+ }
56
+
57
+ function flamegraph(data) {
58
+ var maxX = 0;
59
+ var maxY = 0;
60
+ var minY = 10000;
61
+ $.each(data, function(){
62
+ maxX = Math.max(maxX, this.x + this.width);
63
+ maxY = Math.max(maxY, this.y);
64
+ minY = Math.min(minY, this.y);
65
+ });
66
+
67
+ // normalize Y
68
+ if (minY > 0) {
69
+ $.each(data, function(){
70
+ this.y -= minY
71
+ })
72
+ maxY -= minY
73
+ minY = 0
74
+ }
75
+
76
+ var margin = {top: 10, right: 10, bottom: 10, left: 10}
77
+ var width = $(window).width() - 200 - margin.left - margin.right;
78
+ var height = $(window).height() * 0.70 - margin.top - margin.bottom;
79
+ var height2 = $(window).height() * 0.30 - 60 - margin.top - margin.bottom;
80
+
81
+ $('.flamegraph').width(width + margin.left + margin.right).height(height + margin.top + margin.bottom);
82
+ $('.zoom').width(width + margin.left + margin.right).height(height2 + margin.top + margin.bottom);
83
+
84
+ var xScale = d3.scale.linear()
85
+ .domain([0, maxX])
86
+ .range([0, width]);
87
+
88
+ var xScale2 = d3.scale.linear()
89
+ .domain([0, maxX])
90
+ .range([0, width])
91
+
92
+ var yScale = d3.scale.linear()
93
+ .domain([0, maxY])
94
+ .range([0,height]);
95
+
96
+ var yScale2 = d3.scale.linear()
97
+ .domain([0, maxY])
98
+ .range([0,height2]);
99
+
100
+ var zoomXRatio = 1
101
+ var zoomed = function() {
102
+ svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + (zoomXRatio*d3.event.scale) + "," + d3.event.scale + ")");
103
+
104
+ var x = xScale.domain(), y = yScale.domain()
105
+ brush.extent([ [x[0]/zoomXRatio, y[0]], [x[1]/zoomXRatio, y[1]] ])
106
+ if (x[1] == maxX && y[1] == maxY)
107
+ brush.clear()
108
+ svg2.select('g.brush').call(brush)
109
+ }
110
+
111
+ var zoom = d3.behavior.zoom().x(xScale).y(yScale).scaleExtent([1, 14]).on('zoom', zoomed)
112
+
113
+ var svg2 = d3.select('.zoom').append('svg').attr('width', '100%').attr('height', '100%').append('svg:g')
114
+ .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
115
+ .append('g').attr('class', 'graph')
116
+
117
+ var svg = d3.select(".flamegraph")
118
+ .append("svg")
119
+ .attr("width", "100%")
120
+ .attr("height", "100%")
121
+ .attr("pointer-events", "all")
122
+ .append('svg:g')
123
+ .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
124
+ .call(zoom)
125
+ .append('svg:g').attr('class', 'graph');
126
+
127
+ // so zoom works everywhere
128
+ svg.append("rect")
129
+ .attr("x",function(d) { return xScale(0); })
130
+ .attr("y",function(d) { return yScale(0);})
131
+ .attr("width", function(d){return xScale(maxX);})
132
+ .attr("height", yScale(maxY))
133
+ .attr("fill", "white");
134
+
135
+ var samplePercentRaw = function(samples, exclusive) {
136
+ var ret = [samples, ((samples / maxX) * 100).toFixed(2)]
137
+ if (exclusive)
138
+ ret = ret.concat([exclusive, ((exclusive / maxX) * 100).toFixed(2)])
139
+ return ret;
140
+ }
141
+
142
+ var samplePercent = function(samples, exclusive) {
143
+ var info = samplePercentRaw(samples, exclusive)
144
+ var samplesPct = info[1], exclusivePct = info[3]
145
+ var ret = " (" + samples + " sample" + (samples == 1 ? "" : "s") + " - " + samplesPct + "%) ";
146
+ if (exclusive)
147
+ ret += " (" + exclusive + " exclusive - " + exclusivePct + "%) ";
148
+ return ret;
149
+ }
150
+
151
+ var info = {};
152
+
153
+ var mouseover = function(d) {
154
+ var i = info[d.frame_id];
155
+ var shortFile = d.file.replace(/^.+\/(gems|app|lib|config|jobs)/, '$1')
156
+ var data = samplePercentRaw(i.samples.length, d.topFrame ? d.topFrame.exclusiveCount : 0)
157
+
158
+ $('.info')
159
+ .css('background-color', i.color)
160
+ .find('.frame').text(d.frame).end()
161
+ .find('.file').text(shortFile).end()
162
+ .find('.samples').text(data[0] + ' samples ('+data[1]+'%)').end()
163
+ .find('.exclusive').text('')
164
+
165
+ if (data[3])
166
+ $('.info .exclusive').text(data[2] + ' exclusive ('+data[3]+'%)')
167
+
168
+ d3.selectAll(i.nodes)
169
+ .attr('opacity',0.5);
170
+ };
171
+
172
+ var mouseout = function(d) {
173
+ var i = info[d.frame_id];
174
+ $('.info').css('background-color', 'none').find('.frame, .file, .samples, .exclusive').text('')
175
+
176
+ d3.selectAll(i.nodes)
177
+ .attr('opacity',1);
178
+ };
179
+
180
+ // assign some colors, analyze samples per gem
181
+ var gemStats = {}
182
+ var topFrames = {}
183
+ var lastFrame = {frame: 'd52e04d-df28-41ed-a215-b6ec840a8ea5', x: -1}
184
+
185
+ $.each(data, function(){
186
+ var gem = guessGem(this.file);
187
+ var stat = gemStats[gem];
188
+ this.gemName = gem
189
+
190
+ if(!stat) {
191
+ gemStats[gem] = stat = {name: gem, samples: [], frames: [], nodes:[]};
192
+ }
193
+
194
+ stat.frames.push(this.frame_id);
195
+ for(var j=0; j < this.width; j++){
196
+ stat.samples.push(this.x + j);
197
+ }
198
+ // This assumes the traversal is in order
199
+ if (lastFrame.x != this.x) {
200
+ var topFrame = topFrames[lastFrame.frame_id]
201
+ if (!topFrame) {
202
+ topFrames[lastFrame.frame_id] = topFrame = {exclusiveCount: 0}
203
+ }
204
+ topFrame.exclusiveCount += 1;
205
+ lastFrame.topFrame = topFrame;
206
+ }
207
+ lastFrame = this;
208
+
209
+ });
210
+
211
+ var topFrame = topFrames[lastFrame.frame_id]
212
+ if (!topFrame) {
213
+ topFrames[lastFrame.frame_id] = topFrame = {exclusiveCount: 0}
214
+ }
215
+ topFrame.exclusiveCount += 1;
216
+ lastFrame.topFrame = topFrame;
217
+
218
+ var totalGems = 0;
219
+ $.each(gemStats, function(k,stat){
220
+ totalGems++;
221
+ stat.samples = getUnique(stat.samples);
222
+ });
223
+
224
+ var gemsSorted = $.map(gemStats, function(v, k){ return v })
225
+ gemsSorted.sort(function(a, b){ return b.samples.length - a.samples.length })
226
+
227
+ var currentIndex = 0;
228
+ $.each(gemsSorted, function(k,stat){
229
+ stat.color = rainbow(totalGems, currentIndex);
230
+ currentIndex += 1;
231
+
232
+ for(var x=0; x < stat.frames.length; x++) {
233
+ info[stat.frames[x]] = {nodes: [], samples: [], color: stat.color};
234
+ }
235
+ });
236
+
237
+ function drawData(svg, data, xScale, yScale, mini) {
238
+ svg.selectAll("g.flames")
239
+ .data(data)
240
+ .enter()
241
+ .append("g")
242
+ .attr('class', 'flames')
243
+ .each(function(d){
244
+ gemStats[d.gemName].nodes.push(this)
245
+
246
+ var r = d3.select(this)
247
+ .append("rect")
248
+ .attr("x",function(d) { return xScale(d.x); })
249
+ .attr("y",function(d) { return yScale(maxY - d.y);})
250
+ .attr("width", function(d){return xScale(d.width);})
251
+ .attr("height", yScale(1))
252
+ .attr("fill", function(d){
253
+ var i = info[d.frame_id];
254
+ if(!i) {
255
+ info[d.frame_id] = i = {nodes: [], samples: [], color: color()};
256
+ }
257
+ i.nodes.push(this);
258
+ if (!mini)
259
+ for(var j=0; j < d.width; j++){
260
+ i.samples.push(d.x + j);
261
+ }
262
+ return i.color;
263
+ })
264
+
265
+ if (!mini)
266
+ r
267
+ .on("mouseover", mouseover)
268
+ .on("mouseout", mouseout);
269
+
270
+ if (!mini)
271
+ d3.select(this)
272
+ .append('foreignObject')
273
+ .classed('label-body', true)
274
+ .attr("x",function(d) { return xScale(d.x); })
275
+ .attr("y",function(d) { return yScale(maxY - d.y);})
276
+ .attr("width", function(d){return xScale(d.width);})
277
+ .attr("height", yScale(1))
278
+ .attr("line-height", yScale(1))
279
+ .attr("font-size", yScale(0.42) + 'px')
280
+ .attr('pointer-events', 'none')
281
+ .append('xhtml:span')
282
+ .style("height", yScale(1))
283
+ .classed('label', true)
284
+ .text(function(d){ return d.frame })
285
+ });
286
+ }
287
+
288
+ drawData(svg, data, xScale, yScale, 0)
289
+ drawData(svg2, data, xScale2, yScale2, 1)
290
+
291
+ var brushed = function(){
292
+ if (brush.empty()) {
293
+ svg.attr('transform', '')
294
+ zoomXRatio = 1
295
+ zoom.scale(1).translate([0,0])
296
+ svg.selectAll('.label-body')
297
+ .attr('transform', 'scale(1,1)')
298
+ .attr("x",function(d) { return xScale(d.x)*zoomXRatio; })
299
+ .attr("width", function(d){return xScale(d.width)*zoomXRatio;})
300
+ } else {
301
+ var e = brush.extent()
302
+ var x = [e[0][0],e[1][0]], y = [e[0][1],e[1][1]]
303
+
304
+ xScale.domain([0, maxX])
305
+ yScale.domain([0, maxY])
306
+
307
+ var w = width, h = height2
308
+ var dx = xScale2(1.0*x[1]-x[0]), dy = yScale2(1.0*y[1]-y[0])
309
+ var sx = w/dx, sy = h/dy
310
+ var trlx = -xScale(x[0])*sx, trly = -yScale(y[0])*sy
311
+ var transform = "translate(" + trlx + ',' + trly + ")" + " scale(" + sx + ',' + sy + ")"
312
+
313
+ zoomXRatio = sx/sy
314
+
315
+ svg.selectAll('.label-body')
316
+ .attr("x",function(d) { return xScale(d.x)*zoomXRatio; })
317
+ .attr("width", function(d){return xScale(d.width)*zoomXRatio;})
318
+ .attr('transform', function(d){
319
+ var x = xScale(d.x)
320
+ return "scale("+(1.0/zoomXRatio)+",1)"
321
+ })
322
+
323
+ svg.attr("transform", transform)
324
+ zoom.translate([trlx, trly]).scale(sy)
325
+ }
326
+ }
327
+
328
+ var brush = d3.svg.brush()
329
+ .x(xScale2)
330
+ .y(yScale2)
331
+ .on("brush", brushed);
332
+
333
+ svg2.append("g")
334
+ .attr("class", "brush")
335
+ .call(brush)
336
+
337
+ // Samples may overlap on the same line
338
+ for (var r in info) {
339
+ if (info[r].samples) {
340
+ info[r].samples = getUnique(info[r].samples);
341
+ }
342
+ };
343
+
344
+ // render the legend
345
+ $.each(gemsSorted, function(k,gem){
346
+ var data = samplePercentRaw(gem.samples.length)
347
+ var node = $("<div class='"+gem.name+"'></div>")
348
+ .css("background-color", gem.color)
349
+ .html("<span style='float: right'>" + data[0] + 'x<br>' + data[1] + '%' + '</span>' + '<div class="name">'+gem.name+'<br>&nbsp;</div>');
350
+
351
+ node.on('mouseenter mouseleave', function(e){
352
+ d3.selectAll(gemStats[gem.name].nodes).classed('highlighted', e.type == 'mouseenter')
353
+ })
354
+
355
+ $('.legend').append(node);
356
+ });
357
+ }
@@ -0,0 +1,85 @@
1
+ <html>
2
+ <head>
3
+ <title>flamegraph</title>
4
+ <style>
5
+ body {
6
+ margin: 0;
7
+ padding: 0;
8
+ font-family: Monaco, "Liberation Mono", Courier, monospace;
9
+ font-size: 10pt;
10
+ }
11
+ .info {
12
+ display: block;
13
+ height: 40px;
14
+ margin: 3px 6px;
15
+ margin-right: 206px;
16
+ padding: 3px 6px;
17
+ line-height: 18px;
18
+ }
19
+ .legend {
20
+ display: block;
21
+ float: right;
22
+ width: 195px;
23
+ max-height: 100%;
24
+ overflow-y: scroll;
25
+ }
26
+ .legend > div {
27
+ padding: 6px;
28
+ clear: right;
29
+ }
30
+ .legend > div span {
31
+ opacity: 0.75;
32
+ display: block;
33
+ text-align: right;
34
+ }
35
+ .legend > div .name {
36
+ max-width: 70%;
37
+ word-wrap: break-word;
38
+ }
39
+ .legend:hover + .flamegraph .flames:not(.highlighted) {
40
+ opacity: 0.25;
41
+ }
42
+ .legend:hover ~ .zoom .flames:not(.highlighted) {
43
+ opacity: 0.25;
44
+ }
45
+ .brush .extent {
46
+ stroke: #999;
47
+ fill-opacity: .125;
48
+ shape-rendering: crispEdges;
49
+ }
50
+ .label {
51
+ white-space: nowrap;
52
+ display: inline-flex;
53
+ align-items: center;
54
+ vertical-align: middle;
55
+ padding-left: 1px;
56
+ }
57
+ </style>
58
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
59
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.0.8/d3.min.js"></script>
60
+ <script src="flamegraph.js"></script>
61
+ </head>
62
+ <body>
63
+ <div class="legend"></div>
64
+ <div class="flamegraph"></div>
65
+ <div class="info">
66
+ <div style="float: right; text-align: right">
67
+ <div class="samples"></div>
68
+ <div class="exclusive"></div>
69
+ </div>
70
+ <div class="frame"></div>
71
+ <div class="file"></div>
72
+ </div>
73
+ <div class="zoom"></div>
74
+ <script type="text/javascript">
75
+ var queryDict = {}
76
+ location.search.substr(1).split("&").forEach(function(item) {queryDict[item.split("=")[0]] = decodeURIComponent(item.split("=")[1])})
77
+
78
+ if (queryDict.data) {
79
+ s = document.createElement('script')
80
+ s.setAttribute('src', queryDict.data)
81
+ document.body.appendChild(s)
82
+ }
83
+ </script>
84
+ </body>
85
+ </html>
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stackflame
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Takashi Kokubun
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: stackprof
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.2.7
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.2.7
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.7'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: activerecord
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '4.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '4.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sqlite3
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Stackflame provides a simple API to deal with Flamegraph of stackprof.
98
+ email:
99
+ - takashikkbn@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - Gemfile
106
+ - LICENSE.txt
107
+ - README.md
108
+ - Rakefile
109
+ - example/ar_profile.rb
110
+ - lib/stackflame.rb
111
+ - lib/stackflame/version.rb
112
+ - stackflame.gemspec
113
+ - vendor/flamegraph.js
114
+ - vendor/viewer.html
115
+ homepage: https://github.com/k0kubun/stackflame
116
+ licenses:
117
+ - MIT
118
+ metadata: {}
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ requirements: []
134
+ rubyforge_project:
135
+ rubygems_version: 2.2.2
136
+ signing_key:
137
+ specification_version: 4
138
+ summary: Stackflame provides a simple API to deal with Flamegraph of stackprof.
139
+ test_files: []