Skip to content
This repository was archived by the owner on Feb 9, 2020. It is now read-only.

Commit f8359a9

Browse files
committed
feat(performance tab): Add performance tab
Add a perf item for inspected app, create a performance tab, add some basic stylings. Calculate total time taken per-watcher add total number of watchers to perf pane do not display watches with a digest time of zero added graph from ng-stats add tracking of last 30 digest cycles
1 parent 56ad9c1 commit f8359a9

File tree

7 files changed

+219
-2
lines changed

7 files changed

+219
-2
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
},
1818
"dependencies": {
1919
"angular": "^1.3.6",
20-
"angular-hint": "0.3.4"
20+
"angular-hint": "0.3.4",
21+
"browserify-eep": "^0.3.1"
2122
},
2223
"scripts": {
2324
"test": "echo \"Error: no test specified\" && exit 1",

panel/app.css

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ body {
1515
background-color: rgba(0,0,0,0.06);
1616
}
1717

18+
/* TODO: Figure out why Angular's CSS isn't loading */
19+
.ng-hide {
20+
display: none!important;
21+
}
1822

1923
.well-top {
2024
border-radius: 4px 4px 0 0;
@@ -307,3 +311,48 @@ li .status:empty {
307311
left: -28px;
308312
margin-top: 3px;
309313
}
314+
315+
/* Performance tab */
316+
.perf {
317+
overflow: scroll;
318+
margin-left: 15px;
319+
margin-right: 15px;
320+
}
321+
322+
.perf.disabled {
323+
opacity: 0.5;
324+
}
325+
326+
.perf .stats {
327+
height: 100px;
328+
}
329+
330+
.perf .averages {
331+
vertical-align: top;
332+
}
333+
334+
.perf .left {
335+
display: inline-block;
336+
width: 45%;
337+
}
338+
.perf .right {
339+
display: inline-block;
340+
width: 45%;
341+
}
342+
.perf pre {
343+
display: inline-block;
344+
overflow: hidden;
345+
text-overflow: ellipsis;
346+
}
347+
348+
/* for the canvas */
349+
.graph {
350+
background: black;
351+
border-bottom: 1px solid #666;
352+
border-right: 1px solid #666;
353+
color: #666;
354+
font-family: 'Courier';
355+
width: 80%;
356+
z-index: 9999;
357+
text-align: right;
358+
}

panel/app.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<link rel="stylesheet" href="components/json-tree/json-tree.css">
88
<link rel="stylesheet" href="components/scope-tree/scope-tree.css">
99

10+
<script src="../node_modules/browserify-eep/out.js"></script>
1011
<script src="../node_modules/angular/angular.js"></script>
1112

1213
<!-- components -->
@@ -20,13 +21,15 @@
2021
<!-- panes -->
2122
<script src="hints/hints.js"></script>
2223
<script src="scopes/scopes.js"></script>
24+
<script src="perf/perf.js"></script>
2325

2426
<script src="app.js"></script>
2527
</head>
2628
<body>
2729
<bat-tabs>
2830
<bat-pane title="Scopes" src="scopes/scopes.html"></bat-pane>
2931
<bat-pane title="Hints" src="hints/hints.html"></bat-pane>
32+
<bat-pane title="Performance (Alpha)" src="perf/perf.html"></bat-pane>
3033
</bat-tabs>
3134
</body>
3235
</html>

panel/app.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
angular.module('batarang.app', [
44
'batarang.app.hint',
55
'batarang.app.scopes',
6+
'batarang.app.perf',
67

78
'batarang.scope-tree',
89
'batarang.code',

panel/components/inspected-app/inspected-app.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ angular.module('batarang.inspected-app', []).
66
function inspectedAppService($rootScope, $q) {
77

88
var scopes = this.scopes = {},
9-
hints = this.hints = [];
9+
hints = this.hints = [],
10+
perf = this.perf = [];
1011

1112
this.watch = function (scopeId, path) {
1213
return invokeAngularHintMethod('watch', scopeId, path);
@@ -102,6 +103,9 @@ function inspectedAppService($rootScope, $q) {
102103
scope.models[message.data.path] = message.data.value;
103104
} else if (message.event === 'scope:link') {
104105
scope.descriptor = message.data.descriptor;
106+
} else if (message.event === 'scope:digest') {
107+
// Hack to avoid reference shenanigans
108+
perf[0] = message.data;
105109
}
106110
}
107111
$rootScope.$broadcast(message.event, message.data);

panel/perf/perf.html

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<div bat-vertical-split ng-controller="PerfController" class="perf" ng-class="{disabled: !enabled}">
2+
3+
<h1>Performance</h1>
4+
5+
<h3 ng-hide="::lastDigestTime">Waiting for first digest cycle...</h3>
6+
7+
<div class="stats">
8+
<div class="left">
9+
<div class="graph">
10+
<canvas></canvas>
11+
<div>
12+
<span>{{ numWatchers }} watchers</span> in <span>{{ lastDigestTime | number }}ms</span>
13+
</div>
14+
</div>
15+
</div>
16+
17+
<div class="right averages">
18+
<div>The last digest cycle took {{ lastDigestTime | number }} ms and had {{ numWatchers || 0 }} watchers</div>
19+
<div ng-show="::last30Digests.watchers">The last 30 digest cycles averaged {{ last30Digests.time | number }} ms and averaged {{ last30Digests.watchers | number }} watchers.</div>
20+
</div>
21+
</div>
22+
<hr>
23+
<h3>Digest timings</h3>
24+
<div ng-repeat="watcher in watchTimings">
25+
<pre class="left">{{ watcher.text }}</pre>
26+
<pre class="right">{{ watcher.time | number }} ms</pre>
27+
</div>
28+
<hr>
29+
<h3>Raw data</h3>
30+
<pre>{{ perf | json }}</pre>
31+
</div>

panel/perf/perf.js

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
'use strict';
2+
3+
/* globals angular,document,console */
4+
5+
// Awesome canvas graph built with help from Kent Dodds
6+
// https://github.com/kentcdodds/ng-stats
7+
// Stats generated by eep.js
8+
// https://github.com/darach/eep-js
9+
10+
angular.module('batarang.app.perf', [])
11+
.controller('PerfController', ['$scope', 'inspectedApp', '$timeout', '$window', PerfController]);
12+
13+
function PerfController($scope, inspectedApp, $timeout, $window) {
14+
15+
$scope.watchTimings = [];
16+
$scope.numWatchers = 0;
17+
$scope.last30Digests = {};
18+
19+
// used for graph
20+
var noDigestSteps = 0;
21+
var opts = {
22+
digestTimeThreshold: 16,
23+
watchCountThreshold: 2000
24+
};
25+
var graphSz = {width: 130, height: 40};
26+
var canvasEl = document.querySelector('canvas');
27+
var cvs = angular.element(canvasEl).attr(graphSz)[0];
28+
// </graph vars>
29+
30+
// eep
31+
var eep = $window.eep;
32+
// TODO implement rolling windows over time:
33+
// var last30Seconds = new eep.EventWorld.make().windows().monotonic(eep.Stats.count, 30);
34+
var last30Digests = {
35+
watchers: new eep.EventWorld.make().windows().sliding(eep.Stats.mean, 30),
36+
time: new eep.EventWorld.make().windows().sliding(eep.Stats.mean, 30)
37+
};
38+
39+
last30Digests.watchers.on('emit', function (avg) {
40+
$scope.last30Digests.watchers = avg;
41+
});
42+
last30Digests.time.on('emit', function (avg) {
43+
$scope.last30Digests.time = avg;
44+
});
45+
// last30Seconds.on('emit', function (count) {
46+
// $scope.last30Seconds.time = count;
47+
// });
48+
49+
50+
$scope.$on('scope:digest', function (e, digestData) {
51+
52+
last30Digests.watchers.enqueue(digestData.events.length);
53+
last30Digests.time.enqueue(digestData.time);
54+
// last30Seconds.enqueue();
55+
56+
$scope.lastDigestTime = digestData.time;
57+
58+
var reducedWatches = digestData.events.reduce(function (prev, next) {
59+
if (!prev[next.watch]) {
60+
prev[next.watch] = next.time;
61+
} else {
62+
prev[next.watch] += next.time;
63+
}
64+
return prev;
65+
}, {});
66+
67+
$scope.watchTimings = Object.keys(reducedWatches)
68+
.filter(function (key) { return reducedWatches[key]; })
69+
.map(function (key) {
70+
return {
71+
text: key.trim().replace(/\s{2,}/g, ' '),
72+
time: reducedWatches[key]
73+
};
74+
})
75+
.sort(function (a, b) {
76+
return b.time - a.time;
77+
});
78+
79+
$scope.numWatchers = digestData.events.length;
80+
addDataToCanvas(digestData.events.length, digestData.time);
81+
});
82+
83+
function getColor(metric, threshold) {
84+
if (metric > threshold) {
85+
return 'red';
86+
} else if (metric > 0.7 * threshold) {
87+
return 'orange';
88+
}
89+
return 'green';
90+
}
91+
92+
function addDataToCanvas(watchCount, digestLength) {
93+
var digestColor = getColor(digestLength, opts.digestTimeThreshold);
94+
var watchColor = getColor(watchCount, opts.watchCountThreshold);
95+
96+
// color the sliver if this is the first step
97+
var ctx = cvs.getContext('2d');
98+
if (noDigestSteps > 0) {
99+
noDigestSteps = 0;
100+
ctx.fillStyle = '#333';
101+
ctx.fillRect(graphSz.width - 1, 0, 1, graphSz.height);
102+
}
103+
104+
// mark the point on the graph
105+
ctx.fillStyle = digestColor;
106+
ctx.fillRect(graphSz.width - 1, Math.max(0, graphSz.height - digestLength), 2, 2);
107+
}
108+
109+
var shiftTimeout;
110+
111+
// Shift the canvas to the left.
112+
function shiftLeft() {
113+
if ($scope.enabled) {
114+
shiftTimeout = $timeout(shiftLeft, 250);
115+
var ctx = cvs.getContext('2d');
116+
var imageData = ctx.getImageData(1, 0, graphSz.width - 1, graphSz.height);
117+
ctx.putImageData(imageData, 0, 0);
118+
ctx.fillStyle = ((noDigestSteps++) > 2) ? 'black' : '#333';
119+
ctx.fillRect(graphSz.width - 1, 0, 1, graphSz.height);
120+
}
121+
}
122+
123+
// TODO: clear canvas when enable is toggled
124+
$scope.$watch('enabled', function (n) {
125+
if (n) { shiftLeft(); }
126+
else { $timeout.cancel(shiftTimeout); }
127+
});
128+
}

0 commit comments

Comments
 (0)