finished refactor of instrumentation

test-unit-sauce
Brian Ford 12 years ago
parent d8aa313eba
commit bd61693243

@ -39,32 +39,8 @@ panelApp.controller('PerfCtrl', function PerfCtrl($scope, appContext, filesystem
appContext.inspect(this.val.id);
};
var updateHistogram = function () {
var info = appContext.getHistogram();
if (!info) {
return;
}
var total = 0;
info.forEach(function (elt) {
total += elt.time;
});
var i, elt, his;
for (i = 0; (i < $scope.histogram.length && i < info.length); i++) {
elt = info[i];
his = $scope.histogram[i];
his.time = elt.time.toPrecision(3);
his.percent = (100 * elt.time / total).toPrecision(3);
}
for ( ; i < info.length; i++) {
elt = info[i];
elt.time = elt.time.toPrecision(3);
elt.percent = (100 * elt.time / total).toPrecision(3);
$scope.histogram.push(elt);
}
$scope.histogram.length = info.length;
};
var updateTree = function () {
$scope.histogram = appContext.getHistogram();
var roots = appContext.getListOfRoots();
if (!roots) {
return;
@ -86,5 +62,4 @@ panelApp.controller('PerfCtrl', function PerfCtrl($scope, appContext, filesystem
}
};
appContext.watchPoll(updateTree);
appContext.watchPoll(updateHistogram);
});

@ -0,0 +1,6 @@
// returns the number's first 4 decimals
panelApp.filter('precision', function () {
return function (input, output) {
return input.toPrecision(4);
};
});

@ -49,16 +49,15 @@ var inject = function () {
return;
}
// Helpers
// =======
// polyfill for performance.now on older webkit
if (!performance.now) {
performance.now = performance.webkitNow;
}
// Helpers
// =======
// Based on cycle.js
// 2011-08-24
// https://github.com/douglascrockford/JSON-js/blob/master/cycle.js
// Make a deep copy of an object or array, assuring that there is at most
@ -66,7 +65,7 @@ var inject = function () {
// duplicate references (which might be forming cycles) are replaced with
// an object of the form
// {$ref: PATH}
// where the PATH is a JSONPath string that locates the first occurance.
// where the PATH is a JSONPath string that locates the first occurrence.
var decycle = function (object) {
var objects = [], // Keep a reference to each unique object or array
paths = []; // Keep the path to each unique object or array
@ -112,9 +111,8 @@ var inject = function () {
// End
// ===
// Instrumentation
// ---------------
// given a scope object, return an object with deep clones
// of the models exposed on that scope
var getScopeLocals = function (scope) {
var scopeLocals = {}, prop;
for (prop in scope) {
@ -125,16 +123,73 @@ var inject = function () {
return scopeLocals;
};
//var bootstrap = window.angular.bootstrap;
var debug = window.__ngDebug = {
watchers: {}, // map of scopes --> watchers
// helper to extract dependencies from function arguments
// not all versions of AngularJS expose annotate
var annotate = angular.injector().annotate;
if (!annotate) {
annotate = (function () {
watchPerf: {}, // maps of watch/apply exp/fns to perf data
applyPerf: {},
var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(.+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
scopes: {}, // map of scope.$ids --> model objects
rootScopes: {}, // map of $ids --> refs to root scopes
rootScopeDirty: {},
// TODO: should I keep these assertions?
function assertArg(arg, name, reason) {
if (!arg) {
throw new Error("Argument '" + (name || '?') + "' is " + (reason || "required"));
}
return arg;
}
function assertArgFn(arg, name, acceptArrayAnnotation) {
if (acceptArrayAnnotation && angular.isArray(arg)) {
arg = arg[arg.length - 1];
}
assertArg(angular.isFunction(arg), name, 'not a function, got ' +
(arg && typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg));
return arg;
}
return function (fn) {
var $inject,
fnText,
argDecl,
last;
if (typeof fn == 'function') {
if (!($inject = fn.$inject)) {
$inject = [];
fnText = fn.toString().replace(STRIP_COMMENTS, '');
argDecl = fnText.match(FN_ARGS);
argDecl[1].split(FN_ARG_SPLIT).forEach(function(arg) {
arg.replace(FN_ARG, function(all, underscore, name) {
$inject.push(name);
});
});
fn.$inject = $inject;
}
} else if (angular.isArray(fn)) {
last = fn.length - 1;
assertArgFn(fn[last], 'fn');
$inject = fn.slice(0, last);
} else {
assertArgFn(fn, 'fn', true);
}
return $inject;
};
}());
}
// Public API
// ==========
var api = window.__ngDebug = {
getDeps: function () {
return debug.deps;
},
getRootScopeIds: function () {
var ids = [];
@ -143,6 +198,7 @@ var inject = function () {
});
return ids;
},
getScopeTree: function (id) {
if (debug.rootScopeDirty[id] === false) {
return;
@ -174,6 +230,20 @@ var inject = function () {
return tree;
},
getWatchPerf: function () {
var changes = [];
angular.forEach(debug.watchPerf, function (info, name) {
if (info.time > 0) {
changes.push({
name: name,
time: info.time
});
info.time = 0;
}
});
return changes;
},
getWatchTree: function (id) {
var traverse = function (sc) {
var tree = {
@ -196,68 +266,37 @@ var inject = function () {
var tree = traverse(root);
return tree;
},
deps: []
}
};
var annotate = angular.injector().annotate;
// not all versions of AngularJS expose annotate
if (!annotate) {
annotate = (function () {
// Private state
// =============
var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(.+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
//var bootstrap = window.angular.bootstrap;
var debug = {
// map of scopes --> watcher function name strings
watchers: {},
// TODO: should I keep these assertions?
function assertArg(arg, name, reason) {
if (!arg) {
throw new Error("Argument '" + (name || '?') + "' is " + (reason || "required"));
}
return arg;
}
function assertArgFn(arg, name, acceptArrayAnnotation) {
if (acceptArrayAnnotation && angular.isArray(arg)) {
arg = arg[arg.length - 1];
}
// maps of watch/apply exp/fns to perf data
watchPerf: {},
applyPerf: {},
assertArg(angular.isFunction(arg), name, 'not a function, got ' +
(arg && typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg));
return arg;
}
// map of scope.$ids --> model objects
scopes: {},
return function (fn) {
var $inject,
fnText,
argDecl,
last;
// map of $ids --> refs to root scopes
rootScopes: {},
if (typeof fn == 'function') {
if (!($inject = fn.$inject)) {
$inject = [];
fnText = fn.toString().replace(STRIP_COMMENTS, '');
argDecl = fnText.match(FN_ARGS);
argDecl[1].split(FN_ARG_SPLIT).forEach(function(arg) {
arg.replace(FN_ARG, function(all, underscore, name) {
$inject.push(name);
});
});
fn.$inject = $inject;
}
} else if (angular.isArray(fn)) {
last = fn.length - 1;
assertArgFn(fn[last], 'fn');
$inject = fn.slice(0, last);
} else {
assertArgFn(fn, 'fn', true);
}
return $inject;
};
}());
}
// map of $ids --> bools
rootScopeDirty: {},
deps: []
};
// Instrumentation
// ===============
var ng = angular.module('ng');
ng.config(function ($provide) {
@ -327,7 +366,6 @@ var inject = function () {
// patch watchExpression
// ---------------------
var w = watchExpression;
if (typeof w === 'function') {
watchExpression = function () {
@ -378,9 +416,8 @@ var inject = function () {
};
// patch destroy
// -------------
// patch $destroy
// --------------
var _destroy = $delegate.__proto__.$destroy;
$delegate.__proto__.$destroy = function () {
if (debug.watchers[this.$id]) {
@ -392,6 +429,8 @@ var inject = function () {
return _destroy.apply(this, arguments);
};
// patch $new
// ----------
var _new = $delegate.__proto__.$new;
$delegate.__proto__.$new = function () {
@ -410,8 +449,8 @@ var inject = function () {
return ret;
};
// patch apply
// -----------
// patch $apply
// ------------
var _apply = $delegate.__proto__.$apply;
$delegate.__proto__.$apply = function (fn) {
var start = performance.now();

@ -1,4 +1,4 @@
// Service for doing stuff in the context of the application being debugged
// Service for running code in the context of the application being debugged
panelApp.factory('appContext', function (chromeExtension) {
// Private vars
@ -10,6 +10,7 @@ panelApp.factory('appContext', function (chromeExtension) {
_pollListeners = [],
_pollInterval = 500;
// TODO: make this private and have it automatically poll?
var getDebugData = function (callback) {
chromeExtension.eval(function (window) {
@ -17,15 +18,15 @@ panelApp.factory('appContext', function (chromeExtension) {
return {};
}
return {
deps: window.__ngDebug.deps,
applyPerf: window.__ngDebug.applyPerf,
watchPerf: window.__ngDebug.watchPerf,
deps: window.__ngDebug.getDeps(),
watchPerf: window.__ngDebug.getWatchPerf(),
roots: window.__ngDebug.getRootScopeIds()
};
},
function (data) {
if (data) {
_debugCache = data;
_incomingHistogramData = data.watchPerf;
}
_pollListeners.forEach(function (fn) {
fn();
@ -37,10 +38,41 @@ panelApp.factory('appContext', function (chromeExtension) {
};
getDebugData();
var _histogramCache = [];
var _incomingHistogramData = [];
var _watchNameToPerf = {};
var _totalCache = 0;
var processHistogram = function () {
if (_incomingHistogramData.length === 0) {
return;
}
_incomingHistogramData.forEach(function (info) {
_totalCache += info.time;
if (_watchNameToPerf[info.name]) {
_watchNameToPerf[info.name].time += info.time;
} else {
_watchNameToPerf[info.name] = info;
_histogramCache.push(info);
}
});
// recalculate all percentages
_histogramCache.forEach(function (item) {
item.percent = (100 * item.time / _totalCache).toPrecision(3);
});
// clear the incoming queue
_incomingHistogramData = [];
};
// Public API
// ==========
return {
// Fix selection of scope
// TODO: Fix selection of scope
// https://github.com/angular/angularjs-batarang/issues/6
executeOnScope: function(scopeId, fn, args, cb) {
if (typeof args === 'function') {
@ -69,7 +101,8 @@ panelApp.factory('appContext', function (chromeExtension) {
// -------
getHistogram: function () {
return _debugCache.watchPerf;
processHistogram();
return _histogramCache;
},
getListOfRoots: function () {
@ -139,7 +172,7 @@ panelApp.factory('appContext', function (chromeExtension) {
clearHistogram: function (cb) {
chromeExtension.eval(function (window) {
window.__ngDebug.watchExp = {};
window.__ngDebug.watchPerf = {};
}, cb);
},

@ -23,6 +23,7 @@
<script src="js/directives/watcherTree.js"></script>
<script src="js/filters/first.js"></script>
<script src="js/filters/precision.js"></script>
<script src="js/filters/sortByTime.js"></script>
<script src="js/services/appContext.js"></script>

@ -29,7 +29,7 @@
<div class="well well-top" style="height: 400px; overflow-y: auto;">
<div ng-repeat="watch in histogram|sortByTime:min:max">
<span style="font-family: monospace;">{{watch.name | first}} </span>
<span> | {{watch.percent}}% | {{watch.time}}ms</span>
<span> | {{watch.percent}}% | {{watch.time | precision}}ms</span>
<div class="progress">
<div ng-style="{width: (watch.percent) + '%'}" class= "bar">
</div>
@ -48,4 +48,4 @@
</div>
</div>
</div>
</div>