Skip to content

Commit d626ffe

Browse files
authored
Merge pull request #8 from vlascik/add-flame-graph-clean
[WIP] Add flame graph
2 parents f55e031 + 2e135ec commit d626ffe

File tree

14 files changed

+906
-50
lines changed

14 files changed

+906
-50
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@
1515
/libpeerconnection.log
1616
npm-debug.log*
1717
testem.log
18+
/.idea
19+
/*.iml

app/components/flame-graph.js

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import Ember from 'ember';
2+
import FlameGraph from '../utils/d3-flame-graphs-v4/d3-flame-graph';
3+
4+
const { run, get, inject } = Ember;
5+
6+
export default Ember.Component.extend({
7+
classNames: ['flame-graph'],
8+
graph: inject.service(),
9+
flameGraph: null,
10+
totalTime: Ember.computed.alias('graph.data.summary.totalTime'),
11+
12+
init() {
13+
this._super(...arguments);
14+
15+
this._graphData = null;
16+
},
17+
18+
didReceiveAttrs() {
19+
this._scheduleDraw();
20+
},
21+
22+
_scheduleDraw() {
23+
let graphData = get(this, 'graph.graph');
24+
25+
if (this._lastGraphData !== graphData && graphData) {
26+
run.schedule('render', this, this.drawFlame, graphData);
27+
28+
this._lastGraphData = graphData;
29+
}
30+
},
31+
32+
formatTime(ms) {
33+
if (ms > 1000000000) {
34+
return (Math.round(ms / 1000000000 * 100) / 100).toFixed(1) + 's';
35+
} else if (ms > 1000000) {
36+
return (Math.round(ms / 1000000 * 100) / 100).toFixed(0) + 'ms';
37+
} else {
38+
return (Math.round(ms / 1000000 * 100) / 100).toFixed(1) + 'ms';
39+
}
40+
},
41+
42+
convert(rawData) {
43+
let child, node, subTree, _i, _len, _ref;
44+
45+
node = {
46+
value: rawData._stats.time.self,
47+
treeValue: rawData._stats.time.self,
48+
name: rawData._label.name + (rawData._label.broccoliPluginName ? ' (' + rawData._label.broccoliPluginName + ')' : ''),
49+
stats: rawData._stats,
50+
children: []
51+
};
52+
if (!rawData._children) {
53+
return node;
54+
}
55+
56+
let treeValue = node.treeValue;
57+
_ref = rawData._children;
58+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
59+
child = _ref[_i];
60+
subTree = this.convert(child);
61+
if (subTree) {
62+
node.children.push(subTree);
63+
treeValue += subTree.treeValue;
64+
}
65+
}
66+
node.treeValue = treeValue;
67+
node.time = this.formatTime(node.treeValue);
68+
node.percent = ((node.treeValue / this.get('totalTime')) * 100).toFixed(1) + "%";
69+
return node;
70+
},
71+
72+
drawFlame(data) {
73+
let _this = this;
74+
let profile = this.convert(data);
75+
76+
let indent = -1;
77+
let objToString = function(obj) {
78+
indent++;
79+
let str = '';
80+
let pad = "&nbsp;";
81+
for (let p in obj) {
82+
if (obj.hasOwnProperty(p) && p !== 'own') {
83+
if (typeof obj[p] === 'object') {
84+
if (p !== 'time' || (p === 'time' && Object.keys(obj[p]).length > 1)) {
85+
let padded = p + pad.repeat(13).substring(0, pad.length * 13 - p.length * 6);
86+
str += '&nbsp;'.repeat(indent) + padded + (indent <= 0 ? '<br/>' : '') + objToString(obj[p]);
87+
}
88+
} else {
89+
if (p === 'count') {
90+
let padded = pad.repeat(5).substring(0, pad.length * 5 - obj[p].toString().length * 6) + obj[p];
91+
str += padded;
92+
} else if (p === 'time') {
93+
let time = _this.formatTime(obj[p]);
94+
let padded = ' ' + pad.repeat(8).substring(0, pad.length * 8 - time.length * 6) + time + '<br/>';
95+
str += padded;
96+
}
97+
}
98+
}
99+
}
100+
indent--;
101+
return str;
102+
};
103+
104+
let tooltip = function(d) {
105+
let time = _this.formatTime(d.data.treeValue);
106+
let percent = " [" + (((d.data.treeValue / _this.get('totalTime')) * 100).toFixed(1)) + "%]";
107+
let self = " (self: " + _this.formatTime(d.data.stats.time.self) + ")";
108+
return d.data.name + "<br/>" + time + percent + self + "<br/>" + objToString(d.data.stats);
109+
};
110+
111+
let clientHeight = document.getElementsByClassName('flame-graph')[0].clientHeight;
112+
clientHeight -= clientHeight % 20;
113+
let clientWidth = document.getElementsByClassName('flame-graph')[0].clientWidth;
114+
115+
this.flameGraph = new FlameGraph('#d3-flame-graph', profile, true)
116+
.size([clientWidth, clientHeight])
117+
.cellHeight(20)
118+
.zoomEnabled(true)
119+
.zoomAction((node, event) => console.log('Zoom: ', node, event))
120+
.labelFunction(d => d.data.name + ' [' + d.data.time + ' / ' + d.data.percent + ']')
121+
.tooltip(tooltip).render();
122+
}
123+
});

app/controllers/flame.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import Ember from 'ember';
2+
3+
const {
4+
Controller,
5+
inject
6+
} = Ember;
7+
8+
export default Controller.extend({
9+
graph: inject.service(),
10+
});

app/index.html

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
11
<!DOCTYPE html>
22
<html>
3-
<head>
4-
<meta charset="utf-8">
5-
<meta http-equiv="X-UA-Compatible" content="IE=edge">
6-
<title>HeimdalljsVisualizer</title>
7-
<meta name="description" content="">
8-
<meta name="viewport" content="width=device-width, initial-scale=1">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
6+
<title>HeimdalljsVisualizer</title>
7+
<meta name="description" content="">
8+
<meta name="viewport" content="width=device-width, initial-scale=1">
99

10-
{{content-for "head"}}
10+
{{content-for "head"}}
1111

12-
<link rel="stylesheet" href="{{rootURL}}assets/vendor.css">
13-
<link rel="stylesheet" href="{{rootURL}}assets/heimdalljs-visualizer.css">
12+
<link rel="stylesheet" href="{{rootURL}}assets/vendor.css">
13+
<link rel="stylesheet" href="{{rootURL}}assets/heimdalljs-visualizer.css">
1414

15-
{{content-for "head-footer"}}
16-
</head>
17-
<body>
18-
{{content-for "body"}}
15+
{{content-for "head-footer"}}
16+
</head>
17+
<body>
18+
{{content-for "body"}}
1919

20-
<script src="{{rootURL}}assets/vendor.js"></script>
21-
<script src="{{rootURL}}assets/heimdalljs-visualizer.js"></script>
20+
<script src="{{rootURL}}assets/vendor.js"></script>
21+
<script src="{{rootURL}}assets/heimdalljs-visualizer.js"></script>
2222

23-
{{content-for "body-footer"}}
24-
</body>
23+
24+
{{content-for "body-footer"}}
25+
</body>
2526
</html>

app/router.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Router.map(function() {
1212
});
1313

1414
this.route('slow-nodes');
15+
this.route('flame');
1516
});
1617

1718
export default Router;

app/styles/app.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
body {
22
margin-top: 3.5rem;
3+
height: calc(100% - 3.55rem);
4+
}
5+
6+
html,
7+
body > [class="ember-view"],
8+
.flame-graph {
9+
height: 100%;
10+
}
11+
12+
.d3-tip {
13+
font-family: "Lucida Console", Monaco, monospace;
14+
font-size: 13px;
315
}
416

517
.global-nav {
@@ -66,3 +78,4 @@ body {
6678
.table-row:hover {
6779
cursor: pointer;
6880
}
81+

app/templates/application.hbs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<div class="nav-left">
44
{{link-to 'Graph' 'graph' class="nav-item is-tab" activeClass="is-active"}}
55
{{link-to 'Nodes' 'slow-nodes' class="nav-item is-tab" activeClass="is-active"}}
6+
{{link-to 'Flame' 'flame' class="nav-item is-tab" activeClass="is-active"}}
67
</div>
78

89
<div class="nav-center">
@@ -35,26 +36,26 @@
3536
<div class="modal-background"></div>
3637
<div class="modal-content">
3738
<div class="box">
38-
<form>
39-
<div class="control is-grouped">
40-
<label class="label">Upload the output of <code>BROCCOLI_VIZ=1 ember build</code>:</label>
41-
<p class="control">
42-
<input name="file-upload" type="file" onchange={{action 'parseFile'}}>
43-
</p>
44-
</div>
45-
<div class="control is-grouped">
46-
<label class="label">Sample File:</label>
47-
<p class="control">
39+
<form>
40+
<div class="control is-grouped">
41+
<label class="label">Upload the output of <code>BROCCOLI_VIZ=1 ember build</code>:</label>
42+
<p class="control">
43+
<input name="file-upload" type="file" onchange={{action 'parseFile'}}>
44+
</p>
45+
</div>
46+
<div class="control is-grouped">
47+
<label class="label">Sample File:</label>
48+
<p class="control">
4849
<span class="select is-small">
4950
<select onchange={{action 'useSample' value="target.value"}}>
5051
<option selected disabled>Choose sample file</option>
5152
<option value="./broccoli-viz-files/initial-build-canary-ember-cli-20170206.json">Empty Project - 2017-02-06</option>
5253
<option value="./broccoli-viz-files/ghost-initial-build-canary-ember-cli-20170206.json">Ghost Admin Client - 2017-02-06</option>
5354
</select>
5455
</span>
55-
</p>
56-
</div>
57-
</form>
56+
</p>
57+
</div>
58+
</form>
5859
</div>
5960
</div>
6061
<button class="modal-close" {{action (action (mut showUploadModal) false)}}></button>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<div id="d3-flame-graph">
2+
</div>

app/templates/flame.hbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{{flame-graph graphData=graph.graph}}

0 commit comments

Comments
 (0)