Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

Commit c066923

Browse files
committed
feat(scope): add scope digest stat collection
Closes #609
1 parent 7f36a8e commit c066923

File tree

7 files changed

+125
-20
lines changed

7 files changed

+125
-20
lines changed

demo/bouncing_balls/web/bouncy_balls.dart

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,14 @@ class BounceController {
6666
balls.removeAt(0);
6767
count++;
6868
}
69-
tick();
69+
//tick();
7070
}
7171

7272
void timeDigest() {
7373
var start = window.performance.now();
7474
digestTime = currentDigestTime;
7575
scope.rootScope.domRead(() {
76-
currentDigestTime = (window.performance.now() - start).round();
76+
currentDigestTime = window.performance.now() - start;
7777
});
7878
}
7979

@@ -100,7 +100,7 @@ class BounceController {
100100
@NgDirective(
101101
selector: '[ball-position]',
102102
map: const {
103-
"ballPosition": '=>position'})
103+
"ball-position": '=>position'})
104104
class BallPositionDirective {
105105
final Element element;
106106
final Scope scope;
@@ -118,6 +118,17 @@ class MyModule extends Module {
118118
MyModule() {
119119
type(BounceController);
120120
type(BallPositionDirective);
121+
value(GetterCache, new GetterCache({
122+
'x': (o) => o.x,
123+
'y': (o) => o.y,
124+
'bounce': (o) => o.bounce,
125+
'fps': (o) => o.fps,
126+
'balls': (o) => o.balls,
127+
'length': (o) => o.length,
128+
'digestTime': (o) => o.digestTime,
129+
'ballClassName': (o) => o.ballClassName
130+
}));
131+
value(ScopeStats, new ScopeStats(report: true));
121132
}
122133
}
123134

demo/bouncing_balls/web/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
</div>
5151
</div>
5252

53-
{{bounce.fps}} fps. ({{bounce.balls.length}} balls) [{{(1000/bounce.fps).round()}} ms] <br>
53+
{{bounce.fps}} fps. ({{bounce.balls.length}} balls) [{{1000/bounce.fps}} ms] <br>
5454
Digest: {{bounce.digestTime}} ms<br>
5555
<a href ng-click="bounce.changeCount(1)">+1</a>
5656
<a href ng-click="bounce.changeCount(10)">+10</a>

lib/change_detection/change_detection.dart

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ abstract class ChangeDetector<H> extends ChangeDetectorGroup<H> {
5454
* linked list of [ChangeRecord]s. The [ChangeRecord]s are returned in the
5555
* same order as they were registered.
5656
*/
57-
ChangeRecord<H> collectChanges([EvalExceptionHandler exceptionHandler]);
57+
ChangeRecord<H> collectChanges({ EvalExceptionHandler exceptionHandler,
58+
AvgStopwatch stopwatch });
5859
}
5960

6061
abstract class Record<H> {
@@ -238,3 +239,20 @@ abstract class MovedItem<V> extends CollectionChangeItem<V> {
238239
abstract class RemovedItem<V> extends CollectionChangeItem<V> {
239240
RemovedItem<V> get nextRemovedItem;
240241
}
242+
243+
class AvgStopwatch extends Stopwatch {
244+
int _count = 0;
245+
246+
int get count => _count;
247+
248+
void reset() {
249+
_count = 0;
250+
super.reset();
251+
}
252+
253+
int increment(int count) => _count += count;
254+
255+
double get ratePerMs => elapsedMicroseconds == 0
256+
? 0.0
257+
: _count / elapsedMicroseconds * 1000;
258+
}

lib/change_detection/dirty_checking_change_detector.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,11 +281,14 @@ class DirtyCheckingChangeDetector<H> extends DirtyCheckingChangeDetectorGroup<H>
281281
return true;
282282
}
283283

284-
DirtyCheckingRecord<H> collectChanges([EvalExceptionHandler exceptionHandler]) {
284+
DirtyCheckingRecord<H> collectChanges({ EvalExceptionHandler exceptionHandler,
285+
AvgStopwatch stopwatch}) {
286+
if (stopwatch != null) stopwatch.start();
285287
DirtyCheckingRecord changeHead = null;
286288
DirtyCheckingRecord changeTail = null;
287289
DirtyCheckingRecord current = _recordHead; // current index
288290

291+
int count = 0;
289292
while (current != null) {
290293
try {
291294
if (current.check() != null) {
@@ -295,6 +298,7 @@ class DirtyCheckingChangeDetector<H> extends DirtyCheckingChangeDetectorGroup<H>
295298
changeTail = changeTail.nextChange = current;
296299
}
297300
}
301+
if (stopwatch != null) count++;
298302
} catch (e, s) {
299303
if (exceptionHandler == null) {
300304
rethrow;
@@ -305,6 +309,7 @@ class DirtyCheckingChangeDetector<H> extends DirtyCheckingChangeDetectorGroup<H>
305309
current = current._nextRecord;
306310
}
307311
if (changeTail != null) changeTail.nextChange = null;
312+
if (stopwatch != null) stopwatch..stop()..increment(count);
308313
return changeHead;
309314
}
310315

lib/change_detection/watch_group.dart

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -358,25 +358,33 @@ class RootWatchGroup extends WatchGroup {
358358
* Each step is called in sequence. ([ReactionFn]s are not called until all
359359
* previous steps are completed).
360360
*/
361-
int detectChanges({EvalExceptionHandler exceptionHandler,
362-
ChangeLog changeLog}) {
361+
int detectChanges({ EvalExceptionHandler exceptionHandler,
362+
ChangeLog changeLog,
363+
AvgStopwatch fieldStopwatch,
364+
AvgStopwatch evalStopwatch,
365+
AvgStopwatch processStopwatch}) {
363366
// Process the ChangeRecords from the change detector
364367
ChangeRecord<_Handler> changeRecord =
365-
(_changeDetector as ChangeDetector<_Handler>)
366-
.collectChanges(exceptionHandler);
368+
(_changeDetector as ChangeDetector<_Handler>).collectChanges(
369+
exceptionHandler:exceptionHandler,
370+
stopwatch: fieldStopwatch);
371+
if (processStopwatch != null) processStopwatch.start();
367372
while (changeRecord != null) {
368373
if (changeLog != null) changeLog(changeRecord.handler.expression,
369374
changeRecord.currentValue,
370375
changeRecord.previousValue);
371376
changeRecord.handler.onChange(changeRecord);
372377
changeRecord = changeRecord.nextChange;
373378
}
379+
if (processStopwatch != null) processStopwatch.stop();
374380

375-
int count = 0;
381+
if (evalStopwatch != null) evalStopwatch.start();
376382
// Process our own function evaluations
377383
_EvalWatchRecord evalRecord = _evalWatchHead;
384+
int evalCount = 0;
378385
while (evalRecord != null) {
379386
try {
387+
if (evalStopwatch != null) evalCount++;
380388
var change = evalRecord.check();
381389
if (change != null && changeLog != null) {
382390
changeLog(evalRecord.handler.expression,
@@ -388,10 +396,13 @@ class RootWatchGroup extends WatchGroup {
388396
}
389397
evalRecord = evalRecord._nextEvalWatch;
390398
}
399+
if (evalStopwatch != null) evalStopwatch..stop()..increment(evalCount);
391400

392401
// Because the handler can forward changes between each other synchronously
393402
// We need to call reaction functions asynchronously. This processes the
394403
// asynchronous reaction function queue.
404+
int count = 0;
405+
if (processStopwatch != null) processStopwatch.stop();
395406
Watch dirtyWatch = _dirtyWatchHead;
396407
RootWatchGroup root = _rootGroup;
397408
root._removeCount = 0;
@@ -407,6 +418,7 @@ class RootWatchGroup extends WatchGroup {
407418
dirtyWatch = dirtyWatch._nextDirtyWatch;
408419
}
409420
_dirtyWatchHead = _dirtyWatchTail = null;
421+
if (processStopwatch != null) processStopwatch..stop()..increment(count);
410422
return count;
411423
}
412424

lib/core/module.dart

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ library angular.core;
33
import 'dart:async' as async;
44
import 'dart:collection';
55
import 'dart:mirrors';
6+
import 'package:intl/intl.dart';
67

78
import 'package:di/di.dart';
89

@@ -40,15 +41,10 @@ class NgCoreModule extends Module {
4041
type(FilterMap);
4142
type(Interpolate);
4243
type(RootScope);
44+
factory(Scope, (injector) => injector.get(RootScope));
45+
value(ScopeStats, new ScopeStats());
4346
value(GetterCache, new GetterCache({}));
4447
value(Object, {}); // RootScope context
45-
factory(Scope, (injector) {
46-
// try { throw null; }
47-
// catch (e, s) {
48-
// print('DEPRECATED reference to Scope:\n$s');
49-
// }
50-
return injector.get(RootScope);
51-
});
5248
type(AstParser);
5349
type(NgZone);
5450

lib/core/scope.dart

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,59 @@ class Scope {
344344
_mapEqual(Map a, Map b) => a.length == b.length &&
345345
a.keys.every((k) => b.containsKey(k) && a[k] == b[k]);
346346

347+
class ScopeStats {
348+
bool report = true;
349+
final nf = new NumberFormat.decimalPattern();
350+
351+
final digestFieldStopwatch = new AvgStopwatch();
352+
final digestEvalStopwatch = new AvgStopwatch();
353+
final digestProcessStopwatch = new AvgStopwatch();
354+
int _digestLoopNo = 0;
355+
356+
final flushFieldStopwatch = new AvgStopwatch();
357+
final flushEvalStopwatch = new AvgStopwatch();
358+
final flushProcessStopwatch = new AvgStopwatch();
359+
360+
ScopeStats({this.report: false}) {
361+
nf.maximumFractionDigits = 0;
362+
}
363+
364+
void digestStart() {
365+
_digestStopwatchReset();
366+
_digestLoopNo = 0;
367+
}
368+
369+
_digestStopwatchReset() {
370+
digestFieldStopwatch.reset();
371+
digestEvalStopwatch.reset();
372+
digestProcessStopwatch.reset();
373+
}
374+
375+
void digestLoop(int changeCount) {
376+
_digestLoopNo++;
377+
if (report) {
378+
print(this);
379+
}
380+
_digestStopwatchReset();
381+
}
382+
383+
String _stat(AvgStopwatch s) {
384+
return '${nf.format(s.count)}'
385+
' / ${nf.format(s.elapsedMicroseconds)} us'
386+
' = ${nf.format(s.ratePerMs)} #/ms';
387+
}
388+
389+
void digestEnd() {
390+
}
391+
392+
toString() =>
393+
'digest #$_digestLoopNo:'
394+
'Field: ${_stat(digestFieldStopwatch)} '
395+
'Eval: ${_stat(digestEvalStopwatch)} '
396+
'Process: ${_stat(digestProcessStopwatch)}';
397+
}
398+
399+
347400
class RootScope extends Scope {
348401
static final STATE_APPLY = 'apply';
349402
static final STATE_DIGEST = 'digest';
@@ -360,11 +413,14 @@ class RootScope extends Scope {
360413
_FunctionChain _domWriteHead, _domWriteTail;
361414
_FunctionChain _domReadHead, _domReadTail;
362415

416+
final ScopeStats _scopeStats;
417+
363418
String _state;
364419

365420
RootScope(Object context, this._astParser, this._parser,
366421
GetterCache cacheGetter, FilterMap filterMap,
367-
this._exceptionHandler, this._ttl, this._zone)
422+
this._exceptionHandler, this._ttl, this._zone,
423+
this._scopeStats)
368424
: super(context, null, null,
369425
new RootWatchGroup(new DirtyCheckingChangeDetector(cacheGetter), context),
370426
new RootWatchGroup(new DirtyCheckingChangeDetector(cacheGetter), context))
@@ -387,6 +443,7 @@ class RootScope extends Scope {
387443
List digestLog;
388444
var count;
389445
ChangeLog changeLog;
446+
_scopeStats.digestStart();
390447
do {
391448
while(_runAsyncHead != null) {
392449
try {
@@ -399,7 +456,11 @@ class RootScope extends Scope {
399456

400457
digestTTL--;
401458
count = rootWatchGroup.detectChanges(
402-
exceptionHandler: _exceptionHandler, changeLog: changeLog);
459+
exceptionHandler: _exceptionHandler,
460+
changeLog: changeLog,
461+
fieldStopwatch: _scopeStats.digestFieldStopwatch,
462+
evalStopwatch: _scopeStats.digestEvalStopwatch,
463+
processStopwatch: _scopeStats.digestProcessStopwatch);
403464

404465
if (digestTTL <= LOG_COUNT) {
405466
if (changeLog == null) {
@@ -415,8 +476,10 @@ class RootScope extends Scope {
415476
throw 'Model did not stabilize in ${_ttl.ttl} digests. '
416477
'Last $LOG_COUNT iterations:\n${log.join('\n')}';
417478
}
479+
_scopeStats.digestLoop(count);
418480
} while (count > 0);
419481
} finally {
482+
_scopeStats.digestEnd();
420483
_transitionState(STATE_DIGEST, null);
421484
}
422485
}

0 commit comments

Comments
 (0)