feat: add WTF tracing

- $digest
- $digest#asyncQueue
- $digest#postDigestQueue
- $eval
- $watcher
- $compile#compile
- $compile#link
- $timeout

Show events (ng-click, etc.) expressions.
Show watched expressions.
Show $timeout callbacks.
Show reslving/rejecting promises details.
This commit is contained in:
Vojta Jina
2014-08-21 15:43:06 -07:00
parent 550ba01b32
commit 2a62cd0002
7 changed files with 164 additions and 1 deletions

View File

@@ -103,6 +103,8 @@
* <div doc-module-components="ng"></div>
*/
var WTF_ENABLED = WTF && WTF.PRESENT;
var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/;
// The name of a form control's ValidityState property.

View File

@@ -1024,12 +1024,23 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
*/
function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective,
previousCompileContext) {
var wtfScope, wtfHtml;
if (WTF_ENABLED) {
wtfScope = WTF.trace.enterScope('$compile#compile');
wtfHtml = '';
}
var linkFns = [],
attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound;
for (var i = 0; i < nodeList.length; i++) {
attrs = new Attributes();
if (WTF_ENABLED) {
wtfHtml += (nodeList[i].outerHTML || '');
}
// we must always refer to nodeList[i] since the nodes can be replaced underneath us.
directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined,
ignoreDirective);
@@ -1062,12 +1073,18 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
previousCompileContext = null;
}
if (WTF_ENABLED) {
WTF.trace.appendScopeData('html', wtfHtml);
WTF.trace.leaveScope(wtfScope);
}
// return a linking function if we have found anything, null otherwise
return linkFnFound ? compositeLinkFn : null;
function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) {
var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn;
var stableNodeList;
var wtfLinkScope;
if (nodeLinkFnFound) {
@@ -1090,6 +1107,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
nodeLinkFn = linkFns[i++];
childLinkFn = linkFns[i++];
if (WTF_ENABLED) {
wtfLinkScope = WTF.trace.enterScope('$compile#link');
WTF.trace.appendScopeData('html', node.outerHTML || node.textContent);
}
if (nodeLinkFn) {
if (nodeLinkFn.scope) {
childScope = scope.$new();
@@ -1118,6 +1140,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
} else if (childLinkFn) {
childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
}
if (WTF_ENABLED) {
WTF.trace.leaveScope(wtfLinkScope);
}
}
}
}

View File

@@ -62,6 +62,13 @@ forEach(
var callback = function() {
fn(scope, {$event:event});
};
if (WTF_ENABLED) {
callback.toString = function() {
return 'ng-' + name + '="' + attr[directiveName] + '"';
};
}
if (forceAsyncEvents[eventName] && scope.$$phase) {
scope.$evalAsync(callback);
} else {

View File

@@ -1117,6 +1117,11 @@ function $ParseProvider() {
// initial value is defined (for bind-once)
return isDefined(value) ? result : value;
};
if (WTF_ENABLED) {
fn.exp = parsedExpression;
}
fn.$$watchDelegate = parsedExpression.$$watchDelegate;
return fn;
}

View File

@@ -325,7 +325,28 @@ function qFactory(nextTick, exceptionHandler) {
function scheduleProcessQueue(state) {
if (state.processScheduled || !state.pending) return;
state.processScheduled = true;
nextTick(function() { processQueue(state); });
var fn = function() { processQueue(state); };
if (WTF_ENABLED) {
var pending = state.pending;
fn.toString = function() {
var callbacks = state.pending.map(function(p) {
return p[state.status];
}).filter(function(callback) {
return !!callback;
}).map(function(callback) {
return callback.name || callback.toString();
});
return (state.status === 1 ? 'resolving' : 'rejecting') + ' promise\n' +
'callbacks (' + callbacks.length + '):\n' + callbacks.join('\n\n') + '\n\n' +
'value: ' + toJson(state.value, true);
};
}
nextTick(fn);
}
function Deferred() {

View File

@@ -622,6 +622,11 @@ function $RootScopeProvider(){
}
}
if (WTF_ENABLED) {
$watchCollectionAction.exp = listener;
changeDetector.exp = obj;
}
return this.$watch(changeDetector, $watchCollectionAction);
},
@@ -687,6 +692,15 @@ function $RootScopeProvider(){
watchLog = [],
logIdx, logMsg, asyncTask;
var wtfDigestScope, wtfDigestCycleScope, wtfDigestCycleCount, wtfAsyncQueueScope,
wtfAsyncQueueCount, wtfWatcherScope;
if (WTF_ENABLED) {
wtfDigestScope = WTF.trace.enterScope('$digest');
wtfDigestCycleCount = 0;
wtfAsyncQueueCount = 0;
}
beginPhase('$digest');
// Check for changes to browser url that happened in sync before the call to $digest
$browser.$$checkUrlChange();
@@ -704,8 +718,17 @@ function $RootScopeProvider(){
dirty = false;
current = target;
if (WTF_ENABLED) {
wtfDigestCycleCount++;
wtfDigestCycleScope = WTF.trace.enterScope('$digest#' + wtfDigestCycleCount);
wtfAsyncQueueScope = WTF.trace.enterScope('$digest#asyncQueue');
}
while(asyncQueue.length) {
try {
if (WTF_ENABLED) {
wtfAsyncQueueCount++
}
asyncTask = asyncQueue.shift();
asyncTask.scope.$eval(asyncTask.expression);
} catch (e) {
@@ -714,6 +737,11 @@ function $RootScopeProvider(){
lastDirtyWatch = null;
}
if (WTF_ENABLED) {
WTF.trace.appendScopeData('length', wtfAsyncQueueCount);
WTF.trace.leaveScope(wtfAsyncQueueScope);
}
traverseScopesLoop:
do { // "traverse the scopes" loop
if ((watchers = current.$$watchers)) {
@@ -733,7 +761,33 @@ function $RootScopeProvider(){
dirty = true;
lastDirtyWatch = watch;
watch.last = watch.eq ? copy(value, null) : value;
if (WTF_ENABLED) {
wtfWatcherScope = WTF.trace.enterScope('watcher');
if (watch.exp) {
// interpolation watch.exp.exp
// parse watch.exp
if (watch.exp.exp) {
WTF.trace.appendScopeData('exp', watch.exp.exp.toString());
} else if (Object.hasOwnProperty.call(watch.exp, 'toString')) {
WTF.trace.appendScopeData('exp', watch.exp.toString());
} else {
WTF.trace.appendScopeData('exp', watch.exp.name || watch.exp.toString());
}
}
WTF.trace.appendScopeData('get', (watch.get.name || watch.get.toString()));
WTF.trace.appendScopeData('fn', (watch.fn.name || watch.fn.toString()));
WTF.trace.appendScopeData('previous', toJson(last));
WTF.trace.appendScopeData('current', toJson(value));
}
watch.fn(value, ((last === initWatchVal) ? value : last), current);
if (WTF_ENABLED) {
WTF.trace.leaveScope(wtfWatcherScope);
}
if (ttl < 5) {
logIdx = 4 - ttl;
if (!watchLog[logIdx]) watchLog[logIdx] = [];
@@ -777,17 +831,41 @@ function $RootScopeProvider(){
TTL, toJson(watchLog));
}
if (WTF_ENABLED) {
WTF.trace.leaveScope(wtfDigestCycleScope);
}
} while (dirty || asyncQueue.length);
if (WTF_ENABLED) {
WTF.trace.appendScopeData('cycles', wtfDigestCycleCount);
WTF.trace.leaveScope(wtfDigestScope);
}
clearPhase();
var wtfPostDigestQueueScope, wtfPostDigestQueueCount;
if (WTF_ENABLED) {
wtfPostDigestQueueScope = WTF.trace.enterScope('$digest#postDigestQueue');
wtfPostDigestQueueCount = 0;
}
while(postDigestQueue.length) {
try {
if (WTF_ENABLED) {
wtfPostDigestQueueCount++;
}
postDigestQueue.shift()();
} catch (e) {
$exceptionHandler(e);
}
}
if (WTF_ENABLED) {
WTF.trace.appendScopeData('length', wtfPostDigestQueueCount);
WTF.trace.leaveScope(wtfPostDigestQueueScope);
}
},
@@ -895,6 +973,20 @@ function $RootScopeProvider(){
* @returns {*} The result of evaluating the expression.
*/
$eval: function(expr, locals) {
if (WTF_ENABLED) {
var wtfScope = WTF.trace.enterScope('$eval');
if (expr) {
WTF.trace.appendScopeData('expression', Object.hasOwnProperty.call(expr, 'toString') ? expr.toString() : (expr.name || expr.toString()));
}
var result = $parse(expr)(this, locals);
WTF.trace.leaveScope(wtfScope);
return result;
}
return $parse(expr)(this, locals);
},

View File

@@ -39,6 +39,13 @@ function $TimeoutProvider() {
timeoutId;
timeoutId = $browser.defer(function() {
var wtfScope;
if (WTF_ENABLED) {
wtfScope = WTF.trace.enterScope('$timeout');
WTF.trace.appendScopeData('fn', fn.name || fn.toString());
}
try {
deferred.resolve(fn());
} catch(e) {
@@ -50,6 +57,9 @@ function $TimeoutProvider() {
}
if (!skipApply) $rootScope.$apply();
if (WTF_ENABLED) {
WTF.trace.leaveScope(wtfScope);
}
}, delay);
promise.$$timeoutId = timeoutId;