Skip to content

Commit

Permalink
test(core): add benchmark for defer runtime logic
Browse files Browse the repository at this point in the history
This commit adds a benchmark for `@defer` runtime logic and uses `@if` as a baseline.
  • Loading branch information
AndrewKushnir committed Oct 16, 2023
1 parent 403e0f2 commit 6b7c454
Show file tree
Hide file tree
Showing 14 changed files with 580 additions and 0 deletions.
39 changes: 39 additions & 0 deletions modules/benchmarks/src/defer/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
load("//tools:defaults.bzl", "ng_module", "ts_library")

package(default_visibility = ["//visibility:public"])

ng_module(
name = "shared_lib",
srcs = [
"init.ts",
"util.ts",
],
tsconfig = "//modules/benchmarks:tsconfig-build.json",
deps = [
"//modules/benchmarks/src:util_lib",
"//packages/core",
"//packages/platform-browser",
],
)

ts_library(
name = "perf_tests_lib",
testonly = 1,
srcs = ["defer.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 = ["defer.e2e-spec.ts"],
tsconfig = "//modules/benchmarks:tsconfig-e2e.json",
deps = [
"@npm//@angular/build-tooling/bazel/benchmark/driver-utilities",
"@npm//protractor",
],
)
10 changes: 10 additions & 0 deletions modules/benchmarks/src/defer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Defer benchmark

This folder contains defer benchmark that tests the process of `@defer` block creation.

There are 2 folders in this benchmark:

* `baseline` - renders a component using an `@if` condition, we use it as a baseline
* `main` - the same code as the `baseline`, but instead of the `@if`, we use `@defer` to compare defer blocks against conditionals

The benchmarks are based on `largetable` benchmarks.
55 changes: 55 additions & 0 deletions modules/benchmarks/src/defer/baseline/BUILD.bazel
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/defer: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/defer:perf_tests_lib"],
)

e2e_test(
name = "e2e",
server = ":prodserver",
deps = ["//modules/benchmarks/src/defer:e2e_tests_lib"],
)
55 changes: 55 additions & 0 deletions modules/benchmarks/src/defer/baseline/app.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* @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 {Component, Input} from '@angular/core';
import {DomSanitizer, SafeStyle} from '@angular/platform-browser';

import {TableCell} from '../util';

let trustedEmptyColor: SafeStyle;
let trustedGreyColor: SafeStyle;

@Component({
standalone: true,
selector: 'app',
template: `
<table>
<tbody>
@for (row of data; track $index) {
<tr>
@for (cell of row; track $index) {
<td [style.backgroundColor]="getColor(cell.row)">
@if (condition) {
<!--
Use static text in cells to avoid the need
to run a new change detection cycle.
-->
Cell
}
</td>
}
</tr>
}
</tbody>
</table>
`,
})
export class AppComponent {
@Input() data: TableCell[][] = [];

condition = true;

constructor(sanitizer: DomSanitizer) {
trustedEmptyColor = sanitizer.bypassSecurityTrustStyle('white');
trustedGreyColor = sanitizer.bypassSecurityTrustStyle('grey');
}

getColor(row: number) {
return row % 2 ? trustedEmptyColor : trustedGreyColor;
}
}
39 changes: 39 additions & 0 deletions modules/benchmarks/src/defer/baseline/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<!-- Prevent the browser from requesting any favicon. -->
<link rel="icon" href="data:," />
</head>
<body>
<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>Defer Benchmark (baseline)</h2>
<p>
<button id="destroyDom">destroyDom</button>
<button id="createDom">createDom</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 -->

<!-- Needs to be named `app_bundle` for sync into Google. -->
<script src="/app_bundle.js"></script>
</body>
</html>
21 changes: 21 additions & 0 deletions modules/benchmarks/src/defer/baseline/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* @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 './app.component';

syncUrlParamsToForm();

bootstrapApplication(AppComponent, {
providers: [
provideProtractorTestingSupport(),
],
}).then(init);
24 changes: 24 additions & 0 deletions modules/benchmarks/src/defer/defer.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* @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('defer benchmark', () => {
afterEach(verifyNoBrowserErrors);

it(`should render the table`, async () => {
openBrowser({
url: '',
ignoreBrowserSynchronization: true,
params: [{name: 'cols', value: 5}, {name: 'rows', value: 5}],
});
await $('#createDom').click();
expect($('#root').getText()).toContain('Cell');
});
});
67 changes: 67 additions & 0 deletions modules/benchmarks/src/defer/defer.perf-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* @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: () => $('#destroyDom').click(),
work: () => $('#createDom').click()
};

const UpdateWorker: Worker = {
id: 'update',
prepare: () => {
$('#createDom').click();
},
work: () => $('#createDom').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('defer benchmark perf', () => {
afterEach(verifyNoBrowserErrors);

[CreateWorker, UpdateWorker].forEach((worker) => {
describe(worker.id, () => {
it(`should run benchmark for ${testPackageName}`, async () => {
await runTableBenchmark({
id: `defer.${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
});
}
52 changes: 52 additions & 0 deletions modules/benchmarks/src/defer/init.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* @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} from '@angular/core';

import {bindAction, profile} from '../util';

import {buildTable, emptyTable, initTableUtils} 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) {
const table = appRef.components[0].instance;

function destroyDom() {
table.data = emptyTable;
appRef.tick();
}

function createDom() {
table.data = buildTable();
appRef.tick();
}

function noop() {}

initTableUtils();

bindAction('#destroyDom', destroyDom);
bindAction('#createDom', createDom);
bindAction('#createDomProfile', profile(createDom, destroyDom, 'create'));
bindAction('#updateDomProfile', profile(createDom, noop, 'update'));
}
Loading

0 comments on commit 6b7c454

Please sign in to comment.