Skip to content
This repository was archived by the owner on Apr 4, 2025. It is now read-only.

Commit 3827a76

Browse files
SomeKittensbtford
authored andcommitted
feat(profiling) hint emits data about each digest cycle
This fixes a previous performance issue and emits an event after each digest with details on the length of the digest as well as the length of each watcher. fix tests
1 parent a27ea9d commit 3827a76

File tree

3 files changed

+129
-67
lines changed

3 files changed

+129
-67
lines changed

examples/profile.html

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<!doctype html>
2+
<html ng-app="profileApp" ng-hint>
3+
<!-- The intent of this example file is to show how an Angular app might consume data produced by ng-hint (in this case profiling data) -->
4+
<head>
5+
<meta charset="utf-8">
6+
<title>Consuming ng-hint data</title>
7+
<link rel="icon" href="https://angularjs.org/favicon.ico" type="image/x-icon">
8+
<link rel="stylesheet" href="app.css">
9+
</head>
10+
<body>
11+
<header>
12+
<h1> Consuming ng-hint data </h1>
13+
</header>
14+
<div id="subtitle1">
15+
Here's where you generate lots of numbers to stare at while "hmmmmm"ing loudly
16+
</div>
17+
<div ng-controller="ProfileController">
18+
<button ng-click="clickButton()">Trigger a digest</button>
19+
<ul>
20+
<li ng-repeat="digest in digests">
21+
Digest #{{::$index}}: {{::digest.time }}
22+
<!-- There's a bug in scopes.js
23+
where we break one-time bindings
24+
25+
Without one-time bindings, the following code
26+
leads to a rather large number of watchers rather quickly
27+
<table>
28+
<tr ng-repeat="watcher in ::digest.events">
29+
<td>{{::watcher.watch }}</td>
30+
<td>{{::watcher.time }}</td>
31+
</tr>
32+
</table> -->
33+
</li>
34+
</ul>
35+
</div>
36+
37+
<script src="../node_modules/angular/angular.js"></script>
38+
<script src="../dist/hint.js"></script>
39+
<script>
40+
angular.module('profileApp', [])
41+
/**
42+
* Best practice: declare controllers on modules rather than on global scope.
43+
**/
44+
.controller('ProfileController', ['$scope', 'HintService', function($scope, HintService) {
45+
$scope.buttonClick = function () {
46+
// A digest is triggered, even for a noop!
47+
};
48+
49+
$scope.digests = HintService;
50+
}])
51+
.service('HintService', function () {
52+
var digests = [];
53+
angular.hint.on('scope:digest', function (event) {
54+
console.log(event);
55+
digests.push(event);
56+
});
57+
return digests;
58+
});
59+
</script>
60+
</body>
61+
62+
</html>

src/modules/scopes.js

Lines changed: 65 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -72,63 +72,74 @@ function decorateRootScope($delegate, $parse) {
7272
});
7373
};
7474

75-
var debouncedEmit = debounceOn(hint.emit, 10, function (params) {
76-
return params.id + params.path;
77-
});
78-
79-
8075
var scopePrototype = ('getPrototypeOf' in Object) ?
8176
Object.getPrototypeOf($delegate) : $delegate.__proto__;
8277

83-
// var _watch = scopePrototype.$watch;
84-
// scopePrototype.$watch = function (watchExpression, reactionFunction) {
85-
// var watchStr = humanReadableWatchExpression(watchExpression);
86-
// var scopeId = this.$id;
87-
// if (typeof watchExpression === 'function') {
88-
// arguments[0] = function () {
89-
// var start = perf.now();
90-
// var ret = watchExpression.apply(this, arguments);
91-
// var end = perf.now();
92-
// hint.emit('scope:watch', {
93-
// id: scopeId,
94-
// watch: watchStr,
95-
// time: end - start
96-
// });
97-
// return ret;
98-
// };
99-
// } else {
100-
// var thatScope = this;
101-
// arguments[0] = function () {
102-
// var start = perf.now();
103-
// var ret = thatScope.$eval(watchExpression);
104-
// var end = perf.now();
105-
// hint.emit('scope:watch', {
106-
// id: scopeId,
107-
// watch: watchStr,
108-
// time: end - start
109-
// });
110-
// return ret;
111-
// };
112-
// }
113-
114-
// if (typeof reactionFunction === 'function') {
115-
// var applyStr = reactionFunction.toString();
116-
// arguments[1] = function () {
117-
// var start = perf.now();
118-
// var ret = reactionFunction.apply(this, arguments);
119-
// var end = perf.now();
120-
// hint.emit('scope:reaction', {
121-
// id: this.$id,
122-
// watch: watchStr,
123-
// time: end - start
124-
// });
125-
// return ret;
126-
// };
127-
// }
128-
129-
// return _watch.apply(this, arguments);
130-
// };
78+
var _watch = scopePrototype.$watch;
79+
var _digestEvents = [];
80+
scopePrototype.$watch = function (watchExpression, reactionFunction) {
81+
var watchStr = humanReadableWatchExpression(watchExpression);
82+
var scopeId = this.$id;
83+
if (typeof watchExpression === 'function') {
84+
arguments[0] = function () {
85+
var start = perf.now();
86+
var ret = watchExpression.apply(this, arguments);
87+
var end = perf.now();
88+
_digestEvents.push({
89+
eventType: 'scope:watch',
90+
id: scopeId,
91+
watch: watchStr,
92+
time: end - start
93+
});
94+
return ret;
95+
};
96+
} else {
97+
var thatScope = this;
98+
arguments[0] = function () {
99+
var start = perf.now();
100+
var ret = thatScope.$eval(watchExpression);
101+
var end = perf.now();
102+
_digestEvents.push({
103+
eventType: 'scope:watch',
104+
id: scopeId,
105+
watch: watchStr,
106+
time: end - start
107+
});
108+
return ret;
109+
};
110+
}
111+
112+
if (typeof reactionFunction === 'function') {
113+
arguments[1] = function () {
114+
var start = perf.now();
115+
var ret = reactionFunction.apply(this, arguments);
116+
var end = perf.now();
117+
_digestEvents.push({
118+
eventType: 'scope:reaction',
119+
id: this.$id,
120+
watch: watchStr,
121+
time: end - start
122+
});
123+
return ret;
124+
};
125+
}
131126

127+
return _watch.apply(this, arguments);
128+
};
129+
130+
var _digest = scopePrototype.$digest;
131+
scopePrototype.$digest = function (fn) {
132+
_digestEvents = [];
133+
var start = perf.now();
134+
var ret = _digest.apply(this, arguments);
135+
var end = perf.now();
136+
hint.emit('scope:digest', {
137+
id: this.$id,
138+
time: end - start,
139+
events: _digestEvents
140+
});
141+
return ret;
142+
};
132143

133144
var _destroy = scopePrototype.$destroy;
134145
scopePrototype.$destroy = function () {
@@ -180,17 +191,6 @@ function decorateRootScope($delegate, $parse) {
180191
}
181192
}
182193

183-
184-
// var _digest = scopePrototype.$digest;
185-
// scopePrototype.$digest = function (fn) {
186-
// var start = perf.now();
187-
// var ret = _digest.apply(this, arguments);
188-
// var end = perf.now();
189-
// hint.emit('scope:digest', { id: this.$id, time: end - start });
190-
// return ret;
191-
// };
192-
193-
194194
var _apply = scopePrototype.$apply;
195195
scopePrototype.$apply = function (fn) {
196196
// var start = perf.now();
@@ -255,7 +255,7 @@ function decorateDollaCompile ($delegate) {
255255
descriptor: descriptor
256256
});
257257
return elt;
258-
}
258+
};
259259
};
260260

261261
// TODO: test this

test/scopes.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ describe('ngHintScopes', function() {
139139
jasmine.clock().tick(10);
140140

141141
expect(hint.emit).toHaveBeenCalled();
142-
var args = getArgsOfNthCall(4);
142+
var args = getArgsOfNthCall(5);
143143
expect(args[0]).toBe('model:change');
144144

145145
expect(args[1]).toEqual({
@@ -149,7 +149,7 @@ describe('ngHintScopes', function() {
149149
value : '{"c":2}'
150150
});
151151

152-
args = getArgsOfNthCall(5);
152+
args = getArgsOfNthCall(6);
153153
expect(args[0]).toBe('model:change');
154154

155155
expect(args[1]).toEqual({

0 commit comments

Comments
 (0)