stackflame 0.0.1

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 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: []