forked from angular/angular
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test(core): add benchmark for hydration runtime logic
This commit adds a benchmark for hydration runtime logic, which contains 2 parts: a baseline (create DOM nodes from scratch) and a main scenario (DOM matching instead of re-creating nodes).
- Loading branch information
1 parent
20e7e21
commit 735ce9f
Showing
13 changed files
with
622 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
load("//tools:defaults.bzl", "ng_module", "ts_library") | ||
|
||
package(default_visibility = ["//visibility:public"]) | ||
|
||
ng_module( | ||
name = "shared_lib", | ||
srcs = [ | ||
"init.ts", | ||
"table.ts", | ||
"util.ts", | ||
], | ||
tsconfig = "//modules/benchmarks:tsconfig-build.json", | ||
deps = [ | ||
"//modules/benchmarks/src:util_lib", | ||
"//packages/common", | ||
"//packages/core", | ||
"//packages/platform-browser", | ||
], | ||
) | ||
|
||
ts_library( | ||
name = "perf_tests_lib", | ||
testonly = 1, | ||
srcs = ["hydration.perf-spec.ts"], | ||
tsconfig = "//modules/benchmarks:tsconfig-e2e.json", | ||
deps = [ | ||
"@npm//@angular/build-tooling/bazel/benchmark/driver-utilities", | ||
"@npm//protractor", | ||
], | ||
) | ||
|
||
ts_library( | ||
name = "e2e_tests_lib", | ||
testonly = 1, | ||
srcs = ["hydration.e2e-spec.ts"], | ||
tsconfig = "//modules/benchmarks:tsconfig-e2e.json", | ||
deps = [ | ||
"@npm//@angular/build-tooling/bazel/benchmark/driver-utilities", | ||
"@npm//protractor", | ||
], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Hydration benchmark | ||
|
||
This folder contains hydration benchmark that tests the process of matching DOM nodes at runtime. | ||
|
||
There are 2 folders in this benchmark: | ||
|
||
* `baseline` - renders a component without hydration, we use it as a baseline | ||
* `main` - the same code as the `baseline`, but Angular uses hydration and matches existing DOM nodes instead of creating new ones | ||
|
||
The benchmarks are based on `largetable` benchmarks. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
load("//tools:defaults.bzl", "app_bundle", "http_server", "ng_module") | ||
load("@npm//@angular/build-tooling/bazel/benchmark/component_benchmark:benchmark_test.bzl", "benchmark_test") | ||
load("//modules/benchmarks:e2e_test.bzl", "e2e_test") | ||
|
||
package(default_visibility = ["//modules/benchmarks:__subpackages__"]) | ||
|
||
ng_module( | ||
name = "main", | ||
srcs = glob(["*.ts"]), | ||
tsconfig = "//modules/benchmarks:tsconfig-build.json", | ||
deps = [ | ||
"//modules/benchmarks/src:util_lib", | ||
"//modules/benchmarks/src/hydration:shared_lib", | ||
"//packages/core", | ||
"//packages/platform-browser", | ||
], | ||
) | ||
|
||
app_bundle( | ||
name = "bundle", | ||
entry_point = ":index.ts", | ||
deps = [ | ||
":main", | ||
"@npm//rxjs", | ||
], | ||
) | ||
|
||
# The script needs to be called `app_bundle` for easier syncing into g3. | ||
genrule( | ||
name = "app_bundle", | ||
srcs = [":bundle.debug.min.js"], | ||
outs = ["app_bundle.js"], | ||
cmd = "cp $< $@", | ||
) | ||
|
||
http_server( | ||
name = "prodserver", | ||
srcs = ["index.html"], | ||
deps = [ | ||
":app_bundle", | ||
"//packages/zone.js/bundles:zone.umd.js", | ||
], | ||
) | ||
|
||
benchmark_test( | ||
name = "perf", | ||
server = ":prodserver", | ||
deps = ["//modules/benchmarks/src/hydration:perf_tests_lib"], | ||
) | ||
|
||
e2e_test( | ||
name = "e2e", | ||
server = ":prodserver", | ||
deps = ["//modules/benchmarks/src/hydration:e2e_tests_lib"], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="utf-8" /> | ||
<!-- Prevent the browser from requesting any favicon. --> | ||
<link rel="icon" href="data:," /> | ||
</head> | ||
<body> | ||
<!--nghm--> | ||
<h2>Params</h2> | ||
<form> | ||
Cols: | ||
<input type="number" id="cols" name="cols" value="" /> | ||
<br /> | ||
Rows: | ||
<input type="number" id="rows" name="rows" value="" /> | ||
<br /> | ||
<button>Apply</button> | ||
</form> | ||
|
||
<h2>Hydration Benchmark (baseline)</h2> | ||
<p> | ||
<button id="prepare">prepare</button> | ||
<button id="createDom">createDom</button> | ||
<button id="updateDom">updateDom</button> | ||
<button id="createDomProfile">profile createDom</button> | ||
<button id="updateDomProfile">profile updateDom</button> | ||
</p> | ||
|
||
<div> | ||
<app id="root"></app> | ||
</div> | ||
|
||
<!-- BEGIN-EXTERNAL --> | ||
<script src="/angular/packages/zone.js/bundles/zone.umd.js"></script> | ||
<!-- END-EXTERNAL --> | ||
|
||
<div id="table"></div> | ||
|
||
<!-- Needs to be named `app_bundle` for sync into Google. --> | ||
<script src="/app_bundle.js"></script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC 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 {bootstrapApplication, provideProtractorTestingSupport} from '@angular/platform-browser'; | ||
|
||
import {init, syncUrlParamsToForm} from '../init'; | ||
import {AppComponent} from '../table'; | ||
|
||
syncUrlParamsToForm(); | ||
|
||
bootstrapApplication(AppComponent, { | ||
providers: [ | ||
provideProtractorTestingSupport(), | ||
], | ||
}).then(appRef => init(appRef, false /* insertSsrContent */)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC 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 {openBrowser, verifyNoBrowserErrors} from '@angular/build-tooling/bazel/benchmark/driver-utilities'; | ||
import {$} from 'protractor'; | ||
|
||
describe('hydration benchmark', () => { | ||
afterEach(verifyNoBrowserErrors); | ||
|
||
it(`should render the table`, async () => { | ||
openBrowser({ | ||
url: '', | ||
ignoreBrowserSynchronization: true, | ||
params: [{name: 'cols', value: 5}, {name: 'rows', value: 5}], | ||
}); | ||
await $('#prepare').click(); | ||
await $('#createDom').click(); | ||
expect($('#table').getText()).toContain('0/0'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC 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 {runBenchmark, verifyNoBrowserErrors} from '@angular/build-tooling/bazel/benchmark/driver-utilities'; | ||
import {$} from 'protractor'; | ||
|
||
interface Worker { | ||
id: string; | ||
prepare?(): void; | ||
work(): void; | ||
} | ||
|
||
const CreateWorker: Worker = { | ||
id: 'create', | ||
prepare: () => $('#prepare').click(), | ||
work: () => $('#createDom').click() | ||
}; | ||
|
||
const UpdateWorker: Worker = { | ||
id: 'update', | ||
prepare: () => { | ||
$('#prepare').click(); | ||
$('#createDom').click(); | ||
}, | ||
work: () => $('#updateDom').click() | ||
}; | ||
|
||
// In order to make sure that we don't change the ids of the benchmarks, we need to | ||
// determine the current test package name from the Bazel target. This is necessary | ||
// because previous to the Bazel conversion, the benchmark test ids contained the test | ||
// name. e.g. "largeTable.ng2_switch.createDestroy". We determine the name of the | ||
// Bazel package where this test runs from the current test target. The Bazel target | ||
// looks like: "//modules/benchmarks/src/largetable/{pkg_name}:{target_name}". | ||
const testPackageName = process.env['BAZEL_TARGET']!.split(':')[0].split('/').pop(); | ||
|
||
describe('hydration benchmark perf', () => { | ||
afterEach(verifyNoBrowserErrors); | ||
|
||
[CreateWorker, UpdateWorker].forEach((worker) => { | ||
describe(worker.id, () => { | ||
it(`should run benchmark for ${testPackageName}`, async () => { | ||
await runTableBenchmark({ | ||
id: `hydration.${testPackageName}.${worker.id}`, | ||
url: '/', | ||
ignoreBrowserSynchronization: true, | ||
worker, | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
function runTableBenchmark( | ||
config: {id: string, url: string, ignoreBrowserSynchronization?: boolean, worker: Worker}) { | ||
return runBenchmark({ | ||
id: config.id, | ||
url: config.url, | ||
ignoreBrowserSynchronization: config.ignoreBrowserSynchronization, | ||
params: [{name: 'cols', value: 40}, {name: 'rows', value: 200}], | ||
prepare: config.worker.prepare, | ||
work: config.worker.work | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC 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 {ApplicationRef, ComponentRef, createComponent, EnvironmentInjector} from '@angular/core'; | ||
|
||
import {bindAction, profile} from '../util'; | ||
|
||
import {TableComponent} from './table'; | ||
import {buildTable, emptyTable, initTableUtils, TableCell} from './util'; | ||
|
||
const DEFAULT_COLS_COUNT = '40'; | ||
const DEFAULT_ROWS_COUNT = '200'; | ||
|
||
function getUrlParamValue(name: string): string|null { | ||
const url = new URL(document.location.href); | ||
return url.searchParams.get(name); | ||
} | ||
|
||
export function syncUrlParamsToForm(): {cols: string, rows: string} { | ||
let cols = getUrlParamValue('cols') ?? DEFAULT_COLS_COUNT; | ||
let rows = getUrlParamValue('rows') ?? DEFAULT_ROWS_COUNT; | ||
(document.getElementById('cols') as HTMLInputElement).value = cols; | ||
(document.getElementById('rows') as HTMLInputElement).value = rows; | ||
return {cols, rows}; | ||
} | ||
|
||
export function init(appRef: ApplicationRef, insertSsrContent = true) { | ||
let tableComponentRef: ComponentRef<TableComponent>; | ||
const injector = appRef.injector; | ||
const environmentInjector = injector.get(EnvironmentInjector); | ||
|
||
let data: TableCell[][] = []; | ||
|
||
const setInput = (data: TableCell[][]) => { | ||
if (tableComponentRef) { | ||
tableComponentRef.setInput('data', data); | ||
tableComponentRef.changeDetectorRef.detectChanges(); | ||
} | ||
}; | ||
|
||
function destroyDom() { | ||
setInput(emptyTable); | ||
} | ||
|
||
function updateDom() { | ||
data = buildTable(); | ||
setInput(data); | ||
} | ||
|
||
function createDom() { | ||
const hostElement = document.getElementById('table'); | ||
tableComponentRef = createComponent(TableComponent, {environmentInjector, hostElement}); | ||
setInput(data); | ||
} | ||
|
||
function prepare() { | ||
destroyDom(); | ||
data = buildTable(); | ||
|
||
if (insertSsrContent) { | ||
// Prepare DOM structure, similar to what SSR would produce. | ||
const hostElement = document.getElementById('table'); | ||
hostElement.setAttribute('ngh', '0'); | ||
hostElement.textContent = ''; // clear existing DOM contents | ||
hostElement.appendChild(createTableDom(data)); | ||
} | ||
} | ||
|
||
function noop() {} | ||
|
||
initTableUtils(); | ||
|
||
bindAction('#prepare', prepare); | ||
bindAction('#createDom', createDom); | ||
bindAction('#updateDom', updateDom); | ||
bindAction('#createDomProfile', profile(createDom, prepare, 'create')); | ||
bindAction('#updateDomProfile', profile(updateDom, noop, 'update')); | ||
} | ||
|
||
/** | ||
* Creates DOM to represent a table, similar to what'd be generated | ||
* during the SSR. | ||
*/ | ||
function createTableDom(data: TableCell[][]) { | ||
const table = document.createElement('table'); | ||
const tbody = document.createElement('tbody'); | ||
table.appendChild(tbody); | ||
this._renderCells = []; | ||
for (let r = 0; r < data.length; r++) { | ||
const dataRow = data[r]; | ||
const tr = document.createElement('tr'); | ||
// Mark created DOM nodes, so that we can verify that | ||
// they were *not* re-created during hydration. | ||
(tr as any).__existing = true; | ||
tbody.appendChild(tr); | ||
const renderRow = []; | ||
for (let c = 0; c < dataRow.length; c++) { | ||
const dataCell = dataRow[c]; | ||
const renderCell = document.createElement('td'); | ||
// Mark created DOM nodes, so that we can verify that | ||
// they were *not* re-created during hydration. | ||
(renderCell as any).__existing = true; | ||
if (r % 2 === 0) { | ||
renderCell.style.backgroundColor = 'grey'; | ||
} | ||
tr.appendChild(renderCell); | ||
renderRow[c] = renderCell; | ||
renderCell.textContent = dataCell.value; | ||
} | ||
// View container anchor | ||
const comment = document.createComment(''); | ||
tr.appendChild(comment); | ||
} | ||
// View container anchor | ||
const comment = document.createComment(''); | ||
tbody.appendChild(comment); | ||
return table; | ||
} |
Oops, something went wrong.