Skip to content

Commit 8dd6c46

Browse files
committed
feat(perf): change detection profiler
Closes angular#4000
1 parent 28a29f5 commit 8dd6c46

File tree

12 files changed

+202
-2
lines changed

12 files changed

+202
-2
lines changed

modules/angular2/core.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export 'package:angular2/src/core/util.dart';
55
export 'package:angular2/src/core/di.dart';
66
export 'package:angular2/src/core/pipes.dart';
77
export 'package:angular2/src/core/facade.dart';
8+
export 'package:angular2/src/core/application_ref.dart';
89
// Do not export application for dart. Must import from angular2/bootstrap
910
//export 'package:angular2/src/core/application.dart';
1011
export 'package:angular2/src/core/services.dart';

modules/angular2/src/core/facade/browser.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export {win as window};
77
export var document = window.document;
88
export var location = window.location;
99
export var gc = window['gc'] ? () => window['gc']() : () => null;
10+
export var performance = window['performance'] ? window['performance'] : null;
1011
export const Event = Event;
1112
export const MouseEvent = MouseEvent;
1213
export const KeyboardEvent = KeyboardEvent;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import {ApplicationRef, LifeCycle} from 'angular2/angular2';
2+
import {isPresent, NumberWrapper} from 'angular2/src/core/facade/lang';
3+
import {performance, window} from 'angular2/src/core/facade/browser';
4+
5+
/**
6+
* Entry point for all Angular debug tools. This object corresponds to the `ng`
7+
* global variable accessible in the dev console.
8+
*/
9+
export class AngularTools {
10+
profiler: AngularProfiler;
11+
12+
constructor(appRef: ApplicationRef) { this.profiler = new AngularProfiler(appRef); }
13+
}
14+
15+
/**
16+
* Entry point for all Angular profiling-related debug tools. This object
17+
* corresponds to the `ng.profiler` in the dev console.
18+
*/
19+
export class AngularProfiler {
20+
lifeCycle: LifeCycle;
21+
22+
constructor(appRef: ApplicationRef) { this.lifeCycle = appRef.injector.get(LifeCycle); }
23+
24+
/**
25+
* Exercises change detection in a loop and then prints the average amount of
26+
* time in milliseconds how long a single round of change detection takes for
27+
* the current state of the UI. It runs a minimum of 5 rounds for a minimum
28+
* of 500 milliseconds.
29+
*
30+
* Optionally, a user may pass a `config` parameter containing a map of
31+
* options. Supported options are:
32+
*
33+
* `record` (boolean) - causes the profiler to record a CPU profile while
34+
* it exercises the change detector. Example:
35+
*
36+
* ```
37+
* ng.profiler.timeChangeDetection({record: true})
38+
* ```
39+
*/
40+
timeChangeDetection(config: any) {
41+
var record = isPresent(config) && config['record'];
42+
var profileName = 'Change Detection';
43+
if (record) {
44+
window.console.profile(profileName);
45+
}
46+
var start = window.performance.now();
47+
var numTicks = 0;
48+
while (numTicks < 5 || (window.performance.now() - start) < 500) {
49+
this.lifeCycle.tick();
50+
numTicks++;
51+
}
52+
var end = window.performance.now();
53+
if (record) {
54+
// need to cast to <any> because type checker thinks there's no argument
55+
// while in fact there is:
56+
//
57+
// https://developer.mozilla.org/en-US/docs/Web/API/Console/profileEnd
58+
(<any>window.console.profileEnd)(profileName);
59+
}
60+
var msPerTick = (end - start) / numTicks;
61+
window.console.log(`ran ${numTicks} change detection cycles`);
62+
window.console.log(`${NumberWrapper.toFixed(msPerTick, 2)} ms per check`);
63+
}
64+
}

modules/angular2/src/tools/tools.dart

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
library angular2.src.tools.tools;
2+
3+
import 'dart:js';
4+
import 'package:angular2/angular2.dart' show ApplicationRef;
5+
import 'common_tools.dart' show AngularTools;
6+
7+
/**
8+
* Enabled Angular 2 debug tools that are accessible via your browser's
9+
* developer console.
10+
*
11+
* Usage:
12+
*
13+
* 1. Open developer console (e.g. in Chrome Ctrl + Shift + j)
14+
* 1. Type `ng.` (usually the console will show auto-complete suggestion)
15+
* 1. Try the change detection profiler `ng.profiler.timeChangeDetection()`
16+
* then hit Enter.
17+
*/
18+
void enableDebugTools(ApplicationRef appRef) {
19+
final tools = new AngularTools(appRef);
20+
context['ng'] = new JsObject.jsify({
21+
'profiler': {
22+
'timeChangeDetection': ([config]) {
23+
tools.profiler.timeChangeDetection(config);
24+
}
25+
}
26+
});
27+
}
28+
29+
/**
30+
* Disables Angular 2 tools.
31+
*/
32+
void disableDebugTools() {
33+
context.deleteProperty('ng');
34+
}

modules/angular2/src/tools/tools.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import {global} from 'angular2/src/core/facade/lang';
2+
import {ApplicationRef} from 'angular2/angular2';
3+
import {AngularTools} from './common_tools';
4+
5+
var context = <any>global;
6+
7+
/**
8+
* Enabled Angular 2 debug tools that are accessible via your browser's
9+
* developer console.
10+
*
11+
* Usage:
12+
*
13+
* 1. Open developer console (e.g. in Chrome Ctrl + Shift + j)
14+
* 1. Type `ng.` (usually the console will show auto-complete suggestion)
15+
* 1. Try the change detection profiler `ng.profiler.timeChangeDetection()`
16+
* then hit Enter.
17+
*/
18+
export function enableDebugTools(appRef: ApplicationRef): void {
19+
context.ng = new AngularTools(appRef);
20+
}
21+
22+
/**
23+
* Disables Angular 2 tools.
24+
*/
25+
export function disableDebugTools(): void {
26+
context.ng = undefined;
27+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import 'package:angular2/test_lib.dart' show SpyObject;
2+
import 'package:angular2/core.dart'
3+
show ApplicationRef, LifeCycle, Injector, bind;
4+
import 'dart:js';
5+
6+
@proxy
7+
class SpyLifeCycle extends SpyObject implements LifeCycle {
8+
noSuchMethod(m) => super.noSuchMethod(m);
9+
}
10+
11+
@proxy
12+
class SpyApplicationRef extends SpyObject implements ApplicationRef {
13+
Injector injector;
14+
15+
SpyApplicationRef() {
16+
this.injector = Injector.resolveAndCreate([
17+
bind(LifeCycle).toClass(SpyLifeCycle)
18+
]);
19+
}
20+
21+
noSuchMethod(m) => super.noSuchMethod(m);
22+
}
23+
24+
void callNgProfilerTimeChangeDetection([config]) {
25+
context['ng']['profiler'].callMethod('timeChangeDetection',
26+
config != null ? [config] : []);
27+
}

modules/angular2/test/tools/spies.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import {SpyObject} from 'angular2/test_lib';
2+
import {ApplicationRef, LifeCycle, Injector, bind} from 'angular2/angular2';
3+
import {global} from 'angular2/src/core/facade/lang';
4+
5+
export class SpyApplicationRef extends SpyObject {
6+
injector;
7+
constructor() {
8+
super();
9+
this.injector = Injector.resolveAndCreate([bind(LifeCycle).toValue({tick: () => {}})]);
10+
}
11+
}
12+
13+
export function callNgProfilerTimeChangeDetection(config?): void {
14+
(<any>global).ng.profiler.timeChangeDetection(config);
15+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import {
2+
afterEach,
3+
beforeEach,
4+
ddescribe,
5+
describe,
6+
expect,
7+
iit,
8+
inject,
9+
it,
10+
xit
11+
} from 'angular2/test_lib';
12+
13+
import {enableDebugTools, disableDebugTools} from 'angular2/tools';
14+
import {SpyApplicationRef, callNgProfilerTimeChangeDetection} from './spies';
15+
16+
export function main() {
17+
describe('profiler', () => {
18+
beforeEach(() => { enableDebugTools((<any>new SpyApplicationRef())); });
19+
20+
afterEach(() => { disableDebugTools(); });
21+
22+
it('should time change detection', () => { callNgProfilerTimeChangeDetection(); });
23+
24+
it('should time change detection with recording',
25+
() => { callNgProfilerTimeChangeDetection({'record': true}); });
26+
});
27+
}

modules/angular2/tools.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/*
2+
* Debugging and profiling tools for Angular 2
3+
*/
4+
export {enableDebugTools, disableDebugTools} from 'angular2/src/tools/tools';

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: angular
22
environment:
3-
sdk: '>=1.12.0 <2.0.0'
3+
sdk: '>=1.12.0-dev.5.10 <2.0.0'
44
dependencies:
55
observe: '0.13.1'
66
dev_dependencies:

tools/broccoli/trees/node_tree.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ module.exports = function makeNodeTree(destinationPath) {
2424
'angular2/test/test_lib/fake_async_spec.ts',
2525
'angular2/test/core/render/xhr_impl_spec.ts',
2626
'angular2/test/core/forms/**',
27+
'angular2/test/tools/tools_spec.ts',
2728
'angular1_router/**'
2829
]
2930
});

tools/build/file2modulename.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ function file2moduleName(filePath) {
22
return filePath.replace(/\\/g, '/')
33
// module name should be relative to `modules` and `tools` folder
44
.replace(/.*\/modules\//, '')
5-
.replace(/.*\/tools\//, '')
65
// and 'dist' folder
76
.replace(/.*\/dist\/js\/dev\/es5\//, '')
87
// module name should not include `lib`, `web` folders

0 commit comments

Comments
 (0)