This repository has been archived by the owner on Feb 9, 2020. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
1 parent
56ad9c1
commit f8359a9
Showing
7 changed files
with
219 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); } | ||
}); | ||
} |