diff --git a/modules/benchmarks/e2e_test/tree_data.ts b/modules/benchmarks/e2e_test/tree_data.ts new file mode 100644 index 0000000000..8aff156795 --- /dev/null +++ b/modules/benchmarks/e2e_test/tree_data.ts @@ -0,0 +1,81 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {$} from 'protractor'; + +export const CreateBtn = '#createDom'; +export const DestroyBtn = '#destroyDom'; +export const DetectChangesBtn = '#detectChanges'; +export const RootEl = '#root'; +export const NumberOfChecksEl = '#numberOfChecks'; + +export interface Benchmark { + id: string; + url: string; + buttons: string[]; + ignoreBrowserSynchronization?: boolean; + extraParams?: {name: string, value: any}[]; +} + +const CreateDestroyButtons: string[] = [CreateBtn, DestroyBtn]; +const CreateDestroyDetectChangesButtons: string[] = [...CreateDestroyButtons, DetectChangesBtn]; + +export const Benchmarks: Benchmark[] = [ + { + id: `deepTree.ng2`, + url: 'all/benchmarks/src/tree/ng2/index.html', + buttons: CreateDestroyDetectChangesButtons, + }, + { + id: `deepTree.ng2.next`, + url: 'all/benchmarks/src/tree/ng2_next/index.html', + buttons: CreateDestroyDetectChangesButtons, + ignoreBrowserSynchronization: true, + // Can't use bundles as we use non exported code + extraParams: [{name: 'bundles', value: false}] + }, + { + id: `deepTree.ng2.static`, + url: 'all/benchmarks/src/tree/ng2_static/index.html', + buttons: CreateDestroyButtons, + }, + { + id: `deepTree.ng2_switch`, + url: 'all/benchmarks/src/tree/ng2_switch/index.html', + buttons: CreateDestroyButtons, + }, + { + id: `deepTree.baseline`, + url: 'all/benchmarks/src/tree/baseline/index.html', + buttons: CreateDestroyButtons, + ignoreBrowserSynchronization: true, + }, + { + id: `deepTree.incremental_dom`, + url: 'all/benchmarks/src/tree/incremental_dom/index.html', + buttons: CreateDestroyButtons, + ignoreBrowserSynchronization: true, + }, + { + id: `deepTree.polymer`, + url: 'all/benchmarks/src/tree/polymer/index.html', + buttons: CreateDestroyButtons, + ignoreBrowserSynchronization: true, + }, + { + id: `deepTree.polymer_leaves`, + url: 'all/benchmarks/src/tree/polymer_leaves/index.html', + buttons: CreateDestroyButtons, + ignoreBrowserSynchronization: true, + }, + { + id: `deepTree.ng1`, + url: 'all/benchmarks/src/tree/ng1/index.html', + buttons: CreateDestroyDetectChangesButtons, + } +]; diff --git a/modules/benchmarks/e2e_test/tree_perf.ts b/modules/benchmarks/e2e_test/tree_perf.ts index b27e08c678..6040106382 100644 --- a/modules/benchmarks/e2e_test/tree_perf.ts +++ b/modules/benchmarks/e2e_test/tree_perf.ts @@ -7,162 +7,77 @@ */ import {runBenchmark, verifyNoBrowserErrors} from 'e2e_util/perf_util'; -import {$} from 'protractor'; +import {$, browser} from 'protractor'; -interface Worker { - id: string; - prepare?(): void; - work(): void; -} - -const CreateOnlyWorker: Worker = { - id: 'createOnly', - prepare: () => $('#destroyDom').click(), - work: () => $('#createDom').click() -}; - -const CreateAndDestroyWorker: Worker = { - id: 'createDestroy', - work: () => { - $('#createDom').click(); - $('#destroyDom').click(); - } -}; - -const UpdateWorker: Worker = { - id: 'update', - work: () => $('#createDom').click() -}; +import {Benchmark, Benchmarks, CreateBtn, DestroyBtn, DetectChangesBtn, RootEl} from './tree_data'; describe('tree benchmark perf', () => { - afterEach(verifyNoBrowserErrors); + let _oldRootEl: any; + beforeEach(() => _oldRootEl = browser.rootEl); - [CreateOnlyWorker, CreateAndDestroyWorker, UpdateWorker].forEach((worker) => { - describe(worker.id, () => { + afterEach(() => { + browser.rootEl = _oldRootEl; + verifyNoBrowserErrors(); + }); - it('should run for ng2', (done) => { + Benchmarks.forEach(benchmark => { + describe(benchmark.id, () => { + it('should work for createOnly', (done) => { runTreeBenchmark({ - id: `deepTree.ng2.${worker.id}`, - url: 'all/benchmarks/src/tree/ng2/index.html', - work: worker.work, - prepare: worker.prepare, + id: 'createOnly', + benchmark, + prepare: () => $(CreateBtn).click(), + work: () => $(DestroyBtn).click() }).then(done, done.fail); }); - it('should run for ng2 next', (done) => { + it('should work for createDestroy', (done) => { runTreeBenchmark({ - id: `deepTree.ng2.next.${worker.id}`, - url: 'all/benchmarks/src/tree/ng2_next/index.html', - ignoreBrowserSynchronization: true, - work: worker.work, - prepare: worker.prepare, - // Can't use bundles as we use non exported code - extraParams: [{name: 'bundles', value: false}] + id: 'createDestroy', + benchmark, + work: () => { + $(DestroyBtn).click(); + $(CreateBtn).click(); + } }).then(done, done.fail); }); - it('should run for ng2 static', (done) => { - runTreeBenchmark({ - id: `deepTree.ng2.static.${worker.id}`, - url: 'all/benchmarks/src/tree/ng2_static/index.html', - work: worker.work, - prepare: worker.prepare, - }).then(done, done.fail); + it('should work for update', (done) => { + runTreeBenchmark({id: 'update', benchmark, work: () => $(CreateBtn).click()}) + .then(done, done.fail); }); - it('should run for ng2 switch', (done) => { - runTreeBenchmark({ - id: `deepTree.ng2_switch.${worker.id}`, - url: 'all/benchmarks/src/tree/ng2_switch/index.html', - work: worker.work, - prepare: worker.prepare, - }).then(done, done.fail); - }); + if (benchmark.buttons.indexOf(DetectChangesBtn) !== -1) { + it('should work for detectChanges', (done) => { + runTreeBenchmark({ + id: 'detectChanges', + benchmark, + work: () => $(DetectChangesBtn).click(), + setup: () => $(DestroyBtn).click() + }).then(done, done.fail); + }); + } - it('should run for the baseline', (done) => { - runTreeBenchmark({ - id: `deepTree.baseline.${worker.id}`, - url: 'all/benchmarks/src/tree/baseline/index.html', - ignoreBrowserSynchronization: true, - work: worker.work, - prepare: worker.prepare, - }).then(done, done.fail); - }); - - it('should run for incremental-dom', (done) => { - runTreeBenchmark({ - id: `deepTree.incremental_dom.${worker.id}`, - url: 'all/benchmarks/src/tree/incremental_dom/index.html', - ignoreBrowserSynchronization: true, - work: worker.work, - prepare: worker.prepare, - }).then(done, done.fail); - }); - - it('should run for polymer binary tree', (done) => { - runTreeBenchmark({ - id: `deepTree.polymer.${worker.id}`, - url: 'all/benchmarks/src/tree/polymer/index.html', - ignoreBrowserSynchronization: true, - work: worker.work, - prepare: worker.prepare, - }).then(done, done.fail); - }); - - it('should run for polymer leaves', (done) => { - runTreeBenchmark({ - id: `deepTree.polymer_leaves.${worker.id}`, - url: 'all/benchmarks/src/tree/polymer_leaves/index.html', - ignoreBrowserSynchronization: true, - work: worker.work, - prepare: worker.prepare, - }).then(done, done.fail); - }); }); }); - - it('should run ng2 changedetection', (done) => { - runTreeBenchmark({ - id: `deepTree.ng2.changedetection`, - url: 'all/benchmarks/src/tree/ng2/index.html', - work: () => $('#detectChanges').click(), - setup: () => $('#createDom').click(), - }).then(done, done.fail); - }); - - it('should run ng2 next changedetection', (done) => { - runTreeBenchmark({ - id: `deepTree.ng2.next.changedetection`, - url: 'all/benchmarks/src/tree/ng2_next/index.html', - work: () => $('#detectChanges').click(), - setup: () => $('#createDom').click(), - ignoreBrowserSynchronization: true, - // Can't use bundles as we use non exported code - extraParams: [{name: 'bundles', value: false}] - }).then(done, done.fail); - }); - - function runTreeBenchmark(config: { - id: string, - url: string, ignoreBrowserSynchronization?: boolean, - work: () => any, - prepare?: () => any, - extraParams?: {name: string, value: any}[], - setup?: () => any - }) { - let params = [{name: 'depth', value: 11}]; - if (config.extraParams) { - params = params.concat(config.extraParams); - } - return runBenchmark({ - id: config.id, - url: config.url, - ignoreBrowserSynchronization: config.ignoreBrowserSynchronization, - params: params, - work: config.work, - prepare: config.prepare, - setup: config.setup - }); - } }); + +function runTreeBenchmark({id, benchmark, prepare, setup, work}: { + id: string; benchmark: Benchmark, prepare ? () : void; setup ? () : void; work(): void; +}) { + let params = [{name: 'depth', value: 11}]; + if (benchmark.extraParams) { + params = params.concat(benchmark.extraParams); + } + browser.rootEl = RootEl; + return runBenchmark({ + id: `${benchmark.id}.${id}`, + url: benchmark.url, + ignoreBrowserSynchronization: benchmark.ignoreBrowserSynchronization, + params: params, + work: work, + prepare: prepare, + setup: setup + }); +} diff --git a/modules/benchmarks/e2e_test/tree_spec.ts b/modules/benchmarks/e2e_test/tree_spec.ts index 9b6f9e4963..6c58fd4b84 100644 --- a/modules/benchmarks/e2e_test/tree_spec.ts +++ b/modules/benchmarks/e2e_test/tree_spec.ts @@ -7,107 +7,57 @@ */ import {openBrowser, verifyNoBrowserErrors} from 'e2e_util/e2e_util'; -import {$} from 'protractor'; +import {$, browser} from 'protractor'; + +import {Benchmark, Benchmarks, CreateBtn, DestroyBtn, DetectChangesBtn, NumberOfChecksEl, RootEl} from './tree_data'; describe('tree benchmark spec', () => { - afterEach(verifyNoBrowserErrors); + let _oldRootEl: any; + beforeEach(() => _oldRootEl = browser.rootEl); - it('should work for ng2', () => { - testTreeBenchmark({ - url: 'all/benchmarks/src/tree/ng2/index.html', + afterEach(() => { + browser.rootEl = _oldRootEl; + verifyNoBrowserErrors(); + }); + + Benchmarks.forEach(benchmark => { + describe(benchmark.id, () => { + it('should work for createDestroy', () => { + openTreeBenchmark(benchmark); + $(CreateBtn).click(); + expect($(RootEl).getText()).toContain('0'); + $(DestroyBtn).click(); + expect($(RootEl).getText()).toEqual(''); + }); + + it('should work for update', () => { + openTreeBenchmark(benchmark); + $(CreateBtn).click(); + $(CreateBtn).click(); + expect($(RootEl).getText()).toContain('A'); + }); + + if (benchmark.buttons.indexOf(DetectChangesBtn) !== -1) { + it('should work for detectChanges', () => { + openTreeBenchmark(benchmark); + $(DetectChangesBtn).click(); + expect($(NumberOfChecksEl).getText()).toContain('10'); + }); + } }); }); - it('should work for ng2 detect changes', () => { + function openTreeBenchmark(benchmark: Benchmark) { let params = [{name: 'depth', value: 4}]; - openBrowser({url: 'all/benchmarks/src/tree/ng2/index.html', params}); - $('#detectChanges').click(); - expect($('#numberOfChecks').getText()).toContain('10'); - }); - - it('should work for ng2 next', () => { - testTreeBenchmark({ - url: 'all/benchmarks/src/tree/ng2_next/index.html', - ignoreBrowserSynchronization: true, - // Can't use bundles as we use non exported code - extraParams: [{name: 'bundles', value: false}] - }); - }); - - it('should work for ng2 next detect changes', () => { - let params = [ - {name: 'depth', value: 4}, - // Can't use bundles as we use non exported code - {name: 'bundles', value: false} - ]; - openBrowser({ - url: 'all/benchmarks/src/tree/ng2_next/index.html', - ignoreBrowserSynchronization: true, params - }); - $('#detectChanges').click(); - expect($('#numberOfChecks').getText()).toContain('10'); - }); - - it('should work for ng2 static', () => { - testTreeBenchmark({ - url: 'all/benchmarks/src/tree/ng2_static/index.html', - }); - }); - - it('should work for ng2 switch', () => { - testTreeBenchmark({ - url: 'all/benchmarks/src/tree/ng2_switch/index.html', - }); - }); - - it('should work for the baseline', () => { - testTreeBenchmark({ - url: 'all/benchmarks/src/tree/baseline/index.html', - ignoreBrowserSynchronization: true, - }); - }); - - it('should work for incremental dom', () => { - testTreeBenchmark({ - url: 'all/benchmarks/src/tree/incremental_dom/index.html', - ignoreBrowserSynchronization: true, - }); - }); - - it('should work for polymer binary tree', () => { - testTreeBenchmark({ - url: 'all/benchmarks/src/tree/polymer/index.html', - ignoreBrowserSynchronization: true, - }); - }); - - it('should work for polymer leaves', () => { - testTreeBenchmark({ - url: 'all/benchmarks/src/tree/polymer_leaves/index.html', - ignoreBrowserSynchronization: true, - }); - }); - - function testTreeBenchmark(openConfig: { - url: string, - ignoreBrowserSynchronization?: boolean, - extraParams?: {name: string, value: any}[] - }) { - let params = [{name: 'depth', value: 4}]; - if (openConfig.extraParams) { - params = params.concat(openConfig.extraParams); + if (benchmark.extraParams) { + params = params.concat(benchmark.extraParams); } + browser.rootEl = RootEl; openBrowser({ - url: openConfig.url, - ignoreBrowserSynchronization: openConfig.ignoreBrowserSynchronization, + url: benchmark.url, + ignoreBrowserSynchronization: benchmark.ignoreBrowserSynchronization, params: params, }); - $('#createDom').click(); - expect($('#root').getText()).toContain('0'); - $('#createDom').click(); - expect($('#root').getText()).toContain('A'); - $('#destroyDom').click(); - expect($('#root').getText()).toEqual(''); } }); diff --git a/modules/benchmarks/src/tree/ng1/index.html b/modules/benchmarks/src/tree/ng1/index.html new file mode 100644 index 0000000000..ee7c5b37e6 --- /dev/null +++ b/modules/benchmarks/src/tree/ng1/index.html @@ -0,0 +1,44 @@ + + + + +

Params

+
+ Depth: + +
+ +
+ +

Ng1 Tree Benchmark

+

+ + + + + + +

+ +
+ Change detection runs: +
+
+ Loading... +
+ + + + \ No newline at end of file diff --git a/modules/benchmarks/src/tree/ng1/index.ts b/modules/benchmarks/src/tree/ng1/index.ts new file mode 100644 index 0000000000..d4dd3c80ae --- /dev/null +++ b/modules/benchmarks/src/tree/ng1/index.ts @@ -0,0 +1,53 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {bindAction, profile} from '../../util'; +import {buildTree, emptyTree} from '../util'; + +import {addTreeToModule} from './tree'; + +declare var angular: any; + +function init() { + let detectChangesRuns = 0; + const numberOfChecksEl = document.getElementById('numberOfChecks') !; + + addTreeToModule(angular.module('app', [])).run([ + '$rootScope', + ($rootScope: any) => { + function detectChanges() { + for (let i = 0; i < 10; i++) { + $rootScope.$digest(); + } + detectChangesRuns += 10; + numberOfChecksEl.textContent = `${detectChangesRuns}`; + } + + function noop() {} + + function destroyDom() { + $rootScope.$apply(() => { $rootScope.initData = emptyTree; }); + } + + function createDom() { + $rootScope.$apply(() => { $rootScope.initData = buildTree(); }); + } + + bindAction('#destroyDom', destroyDom); + bindAction('#createDom', createDom); + bindAction('#detectChanges', detectChanges); + bindAction('#detectChangesProfile', profile(detectChanges, noop, 'detectChanges')); + bindAction('#updateDomProfile', profile(createDom, noop, 'update')); + bindAction('#createDomProfile', profile(createDom, destroyDom, 'create')); + } + ]); + + angular.bootstrap(document.querySelector('tree'), ['app']); +} + +init(); diff --git a/modules/benchmarks/src/tree/ng1/tree.ts b/modules/benchmarks/src/tree/ng1/tree.ts new file mode 100644 index 0000000000..d914fa9b7b --- /dev/null +++ b/modules/benchmarks/src/tree/ng1/tree.ts @@ -0,0 +1,72 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import {TreeNode} from '../util'; + +declare var angular: any; + +export function addTreeToModule(mod: any): any { + return mod + .directive( + 'tree', + function() { + return { + scope: {data: '='}, + template: + ` {{data.value}} ` + }; + }) + // special directive for "if" as angular 1.3 does not support + // recursive components. + // Cloned from real ngIf directive, but using a lazily created transclude function. + .directive( + 'treeIf', + [ + '$compile', '$animate', + function($compile: any, $animate: any) { + let transcludeFn: any; + return { + transclude: 'element', + priority: 600, + terminal: true, + $$tlb: true, + link: function($scope: any, $element: any, $attr: any, ctrl: any) { + if (!transcludeFn) { + const template = ''; + transcludeFn = $compile(template); + } + let childElement: any, childScope: any; + $scope.$watch($attr.data, function ngIfWatchAction(value: any) { + + if (value) { + if (!childScope) { + childScope = $scope.$new(); + transcludeFn(childScope, function(clone: any) { + childElement = clone; + $animate.enter(clone, $element.parent(), $element); + }); + } + } else { + if (childScope) { + childScope.$destroy(); + childScope = null; + } + if (childElement) { + $animate.leave(childElement); + childElement = null; + } + } + }); + } + }; + } + ]) + .config([ + '$compileProvider', + function($compileProvider: any) { $compileProvider.debugInfoEnabled(false); } + ]); +}