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

Commit

Permalink
feat(performance tab): Add performance tab
Browse files Browse the repository at this point in the history
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
  • Loading branch information
SomeKittens committed Sep 17, 2015
1 parent 56ad9c1 commit f8359a9
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 2 deletions.
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -17,7 +17,8 @@
},
"dependencies": {
"angular": "^1.3.6",
"angular-hint": "0.3.4"
"angular-hint": "0.3.4",
"browserify-eep": "^0.3.1"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
Expand Down
49 changes: 49 additions & 0 deletions panel/app.css
Expand Up @@ -15,6 +15,10 @@ body {
background-color: rgba(0,0,0,0.06);
}

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

.well-top {
border-radius: 4px 4px 0 0;
Expand Down Expand Up @@ -307,3 +311,48 @@ li .status:empty {
left: -28px;
margin-top: 3px;
}

/* Performance tab */
.perf {
overflow: scroll;
margin-left: 15px;
margin-right: 15px;
}

.perf.disabled {
opacity: 0.5;
}

.perf .stats {
height: 100px;
}

.perf .averages {
vertical-align: top;
}

.perf .left {
display: inline-block;
width: 45%;
}
.perf .right {
display: inline-block;
width: 45%;
}
.perf pre {
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
}

/* for the canvas */
.graph {
background: black;
border-bottom: 1px solid #666;
border-right: 1px solid #666;
color: #666;
font-family: 'Courier';
width: 80%;
z-index: 9999;
text-align: right;
}
3 changes: 3 additions & 0 deletions panel/app.html
Expand Up @@ -7,6 +7,7 @@
<link rel="stylesheet" href="components/json-tree/json-tree.css">
<link rel="stylesheet" href="components/scope-tree/scope-tree.css">

<script src="../node_modules/browserify-eep/out.js"></script>
<script src="../node_modules/angular/angular.js"></script>

<!-- components -->
Expand All @@ -20,13 +21,15 @@
<!-- panes -->
<script src="hints/hints.js"></script>
<script src="scopes/scopes.js"></script>
<script src="perf/perf.js"></script>

<script src="app.js"></script>
</head>
<body>
<bat-tabs>
<bat-pane title="Scopes" src="scopes/scopes.html"></bat-pane>
<bat-pane title="Hints" src="hints/hints.html"></bat-pane>
<bat-pane title="Performance (Alpha)" src="perf/perf.html"></bat-pane>
</bat-tabs>
</body>
</html>
1 change: 1 addition & 0 deletions panel/app.js
Expand Up @@ -3,6 +3,7 @@
angular.module('batarang.app', [
'batarang.app.hint',
'batarang.app.scopes',
'batarang.app.perf',

'batarang.scope-tree',
'batarang.code',
Expand Down
6 changes: 5 additions & 1 deletion panel/components/inspected-app/inspected-app.js
Expand Up @@ -6,7 +6,8 @@ angular.module('batarang.inspected-app', []).
function inspectedAppService($rootScope, $q) {

var scopes = this.scopes = {},
hints = this.hints = [];
hints = this.hints = [],
perf = this.perf = [];

this.watch = function (scopeId, path) {
return invokeAngularHintMethod('watch', scopeId, path);
Expand Down Expand Up @@ -102,6 +103,9 @@ function inspectedAppService($rootScope, $q) {
scope.models[message.data.path] = message.data.value;
} else if (message.event === 'scope:link') {
scope.descriptor = message.data.descriptor;
} else if (message.event === 'scope:digest') {
// Hack to avoid reference shenanigans
perf[0] = message.data;
}
}
$rootScope.$broadcast(message.event, message.data);
Expand Down
31 changes: 31 additions & 0 deletions panel/perf/perf.html
@@ -0,0 +1,31 @@
<div bat-vertical-split ng-controller="PerfController" class="perf" ng-class="{disabled: !enabled}">

<h1>Performance</h1>

<h3 ng-hide="::lastDigestTime">Waiting for first digest cycle...</h3>

<div class="stats">
<div class="left">
<div class="graph">
<canvas></canvas>
<div>
<span>{{ numWatchers }} watchers</span> in <span>{{ lastDigestTime | number }}ms</span>
</div>
</div>
</div>

<div class="right averages">
<div>The last digest cycle took {{ lastDigestTime | number }} ms and had {{ numWatchers || 0 }} watchers</div>
<div ng-show="::last30Digests.watchers">The last 30 digest cycles averaged {{ last30Digests.time | number }} ms and averaged {{ last30Digests.watchers | number }} watchers.</div>
</div>
</div>
<hr>
<h3>Digest timings</h3>
<div ng-repeat="watcher in watchTimings">
<pre class="left">{{ watcher.text }}</pre>
<pre class="right">{{ watcher.time | number }} ms</pre>
</div>
<hr>
<h3>Raw data</h3>
<pre>{{ perf | json }}</pre>
</div>
128 changes: 128 additions & 0 deletions panel/perf/perf.js
@@ -0,0 +1,128 @@
'use strict';

/* globals angular,document,console */

// Awesome canvas graph built with help from Kent Dodds
// https://github.com/kentcdodds/ng-stats
// Stats generated by eep.js
// https://github.com/darach/eep-js

angular.module('batarang.app.perf', [])
.controller('PerfController', ['$scope', 'inspectedApp', '$timeout', '$window', PerfController]);

function PerfController($scope, inspectedApp, $timeout, $window) {

$scope.watchTimings = [];
$scope.numWatchers = 0;
$scope.last30Digests = {};

// used for graph
var noDigestSteps = 0;
var opts = {
digestTimeThreshold: 16,
watchCountThreshold: 2000
};
var graphSz = {width: 130, height: 40};
var canvasEl = document.querySelector('canvas');
var cvs = angular.element(canvasEl).attr(graphSz)[0];
// </graph vars>

// eep
var eep = $window.eep;
// TODO implement rolling windows over time:
// var last30Seconds = new eep.EventWorld.make().windows().monotonic(eep.Stats.count, 30);
var last30Digests = {
watchers: new eep.EventWorld.make().windows().sliding(eep.Stats.mean, 30),
time: new eep.EventWorld.make().windows().sliding(eep.Stats.mean, 30)
};

last30Digests.watchers.on('emit', function (avg) {
$scope.last30Digests.watchers = avg;
});
last30Digests.time.on('emit', function (avg) {
$scope.last30Digests.time = avg;
});
// last30Seconds.on('emit', function (count) {
// $scope.last30Seconds.time = count;
// });


$scope.$on('scope:digest', function (e, digestData) {

last30Digests.watchers.enqueue(digestData.events.length);
last30Digests.time.enqueue(digestData.time);
// last30Seconds.enqueue();

$scope.lastDigestTime = digestData.time;

var reducedWatches = digestData.events.reduce(function (prev, next) {
if (!prev[next.watch]) {
prev[next.watch] = next.time;
} else {
prev[next.watch] += next.time;
}
return prev;
}, {});

$scope.watchTimings = Object.keys(reducedWatches)
.filter(function (key) { return reducedWatches[key]; })
.map(function (key) {
return {
text: key.trim().replace(/\s{2,}/g, ' '),
time: reducedWatches[key]
};
})
.sort(function (a, b) {
return b.time - a.time;
});

$scope.numWatchers = digestData.events.length;
addDataToCanvas(digestData.events.length, digestData.time);
});

function getColor(metric, threshold) {
if (metric > threshold) {
return 'red';
} else if (metric > 0.7 * threshold) {
return 'orange';
}
return 'green';
}

function addDataToCanvas(watchCount, digestLength) {
var digestColor = getColor(digestLength, opts.digestTimeThreshold);
var watchColor = getColor(watchCount, opts.watchCountThreshold);

// color the sliver if this is the first step
var ctx = cvs.getContext('2d');
if (noDigestSteps > 0) {
noDigestSteps = 0;
ctx.fillStyle = '#333';
ctx.fillRect(graphSz.width - 1, 0, 1, graphSz.height);
}

// mark the point on the graph
ctx.fillStyle = digestColor;
ctx.fillRect(graphSz.width - 1, Math.max(0, graphSz.height - digestLength), 2, 2);
}

var shiftTimeout;

// Shift the canvas to the left.
function shiftLeft() {
if ($scope.enabled) {
shiftTimeout = $timeout(shiftLeft, 250);
var ctx = cvs.getContext('2d');
var imageData = ctx.getImageData(1, 0, graphSz.width - 1, graphSz.height);
ctx.putImageData(imageData, 0, 0);
ctx.fillStyle = ((noDigestSteps++) > 2) ? 'black' : '#333';
ctx.fillRect(graphSz.width - 1, 0, 1, graphSz.height);
}
}

// TODO: clear canvas when enable is toggled
$scope.$watch('enabled', function (n) {
if (n) { shiftLeft(); }
else { $timeout.cancel(shiftTimeout); }
});
}

0 comments on commit f8359a9

Please sign in to comment.