Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 84b6896

Browse files
JeanMecheAndrewKushnir
authored andcommitted
refactor(platform-server): Add an ssr benchmark setup. (#57647)
In order to investigate the performances of SSR, this commit introduces a benchmark suite which will measure several step of the rendering. PR Close #57647
1 parent 58bfb4a commit 84b6896

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+7262
-88
lines changed

.pullapprove.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,8 @@ groups:
384384
conditions:
385385
- >
386386
contains_any_globs(files, [
387-
'modules/benchmarks/**/{*,.*}'
387+
'modules/benchmarks/**/{*,.*}',
388+
'modules/ssr-benchmarks/**/{*,.*}'
388389
])
389390
reviewers:
390391
users:

BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ nodejs_binary(
6868
entry_point = ".yarn/releases/yarn-1.22.19.cjs",
6969
visibility = [
7070
"//integration:__subpackages__",
71+
"//modules/ssr-benchmarks:__subpackages__",
7172
],
7273
)
7374

integration/npm_package_archives.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ NPM_PACKAGE_ARCHIVES = [
77
"@rollup/plugin-commonjs",
88
"check-side-effects",
99
"jasmine",
10+
"http-server",
1011
"typescript",
1112
"rxjs",
1213
"systemjs",
1314
"tslib",
15+
"patch-package",
1416
"protractor",
1517
"terser",
1618
"rollup",

modules/ssr-benchmarks/BUILD.bazel

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
load("//integration:index.bzl", "ng_integration_test")
2+
3+
ng_integration_test(
4+
name = "run",
5+
commands = [
6+
"yarn install --cache-folder ./.yarn_local_cache",
7+
"yarn ng build",
8+
"node ./dist/ssr-benchmarks/server/server.mjs",
9+
],
10+
)
11+
12+
## This target is intended to run the benchmark in a browser
13+
## For this the benchmark script is loaded by an html page serve the http-server script
14+
## Note: This target doesn't end until the http-server is stopped. It should not run as part of our testset.
15+
ng_integration_test(
16+
name = "run_browser",
17+
commands = [
18+
"yarn install --cache-folder ./.yarn_local_cache",
19+
20+
# The patch only applies for the browser target (to remove the DOM Emulation polyfills and browser incompatible code)
21+
"yarn patch",
22+
#
23+
"NG_BUILD_MANGLE=0 yarn ng build --configuration production,browser",
24+
"yarn http-server ./dist",
25+
],
26+
)
27+
28+
# This target is mostly intended for investigating via the devTools using the flamechart
29+
ng_integration_test(
30+
name = "run_browser_emulated_dom",
31+
commands = [
32+
"yarn install --cache-folder ./.yarn_local_cache",
33+
# We keep the emulated dom in this target
34+
# But still need to drop the node import which doesn't work in browsers.
35+
"git apply patches/@angular-devkit+build-angular++@angular+build+19.0.0-next.6+require.patch",
36+
37+
# We keep the symbols with the NG_BUILD_MANGLE flag
38+
"NG_BUILD_MANGLE=0 yarn ng build",
39+
"yarn http-server ./dist",
40+
],
41+
)
42+
43+
# This is a target to investigate with deopt explorer (https://github.com/microsoft/deoptexplorer-vscode)
44+
# The v8 log file will be generated in the test directory
45+
ng_integration_test(
46+
name = "run_deopt",
47+
commands = [
48+
"yarn install --cache-folder ./.yarn_local_cache",
49+
"NG_BUILD_MANGLE=0 yarn ng build",
50+
"yarn node --prof \
51+
--log-deopt \
52+
--log-ic \
53+
--log-maps \
54+
--log-maps-details \
55+
--log-internal-timer-events \
56+
--log-code \
57+
--log-source-code \
58+
--detailed-line-info \
59+
./dist/ssr-benchmarks/server/server.mjs narrowRun",
60+
],
61+
)

modules/ssr-benchmarks/README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
## Intro
2+
3+
This small benchmark suite is dedicated to mesure & describe how compute time is spent when rendering an application like in SSR.
4+
5+
## Struture
6+
7+
* `./main.ts` is the entry point to run the benchmark
8+
* `./src` contains a sample app that exports a `render` function.
9+
* This app render a table of variable size, which depends on data (`initData()`)
10+
* This app is then rendered X numbers of times
11+
12+
* Individual function calls are measured with `startMeasuring()`/`stopMeasuring()` from the core package.
13+
* If you add a new measure, make sure to add it also to the `levels` map for it to be represented correctly in the result
14+
15+
## Build & run
16+
17+
`yarn bazel run //modules/ssr-benchmarks:run`
18+
19+
20+
### Running the benchmark in a browser environment
21+
22+
`yarn bazel run //modules/ssr-benchmarks:run_browser`
23+
24+
This bazel target will build the benchmark, start a http-server with a html that will load the benckmark script.
25+
The benchmark script with this target will have DOM Emulation disabled.
26+
The result will be visible in the devtools console.
27+
28+
Note: Due to the CLI adding some polyfills, @angular/build is patched to disable DOM emulation and running server code inside a browser:
29+
1. removing an import from `node:module` in `polyfills.server.mjs` (with `tail ...`)
30+
2. removing the import of `platform-server/init`.
31+
32+
To run create a usable flame chart, prepare a narrowed run (like `benchmarkRun(10000, 20);`).
33+
Then in the performance tab of the devtools, trigger "Record & Reload" to generate a profile.
34+
35+
### Deopt Explorer
36+
37+
A target is dedicated to generate a v8 log that can be fed to the [Deopt Explorer extension](https://github.com/microsoft/deoptexplorer-vscode).
38+
39+
1. Run `yarn bazel run //modules/ssr-benchmarks:run_deopt`,
40+
2. open the project generated at the path after `Successfully ran all commands in test directory:`,
41+
3. open the logfile in the extension
42+
43+
## Result example
44+
45+
=== table with 10000 rows, with 1000 renders ===
46+
┌─────────┬──────────────────────────────────────┬──────────┬──────────┬────────────┬───────────┐
47+
│ (index) │ name │ min │ average │ percentage │ max │
48+
├─────────┼──────────────────────────────────────┼──────────┼──────────┼────────────┼───────────┤
49+
│ 0 │ ' renderApplication ' │ '77.0ms' │ '86.4ms' │ '100.0%' │ '259.2ms' │
50+
│ 1 │ ' └ createServerPlatform ' │ '0.0ms' │ '0.1ms' │ '0.1%' │ '3.7ms' │
51+
│ 2 │ ' └ bootstrap ' │ '35.9ms' │ '42.6ms' │ '49.3%' │ '138.4ms' │
52+
│ 3 │ ' └ _render ' │ '39.7ms' │ '43.8ms' │ '50.7%' │ '124.9ms' │
53+
│ 4 │ ' └ whenStable ' │ '0.0ms' │ '0.0ms' │ '0.0%' │ '0.0ms' │
54+
│ 5 │ ' └ prepareForHydration ' │ '13.1ms' │ '14.8ms' │ '17.1%' │ '53.4ms' │
55+
│ 6 │ ' └ insertEventRecordScript ' │ '0.0ms' │ '0.0ms' │ '0.0%' │ '0.0ms' │
56+
│ 7 │ ' └ serializeTransferStateFactory' │ '0.0ms' │ '0.0ms' │ '0.0%' │ '0.1ms' │
57+
│ 8 │ ' └ renderToString ' │ '7.3ms' │ '8.9ms' │ '10.3%' │ '41.8ms' │
58+
└─────────┴──────────────────────────────────────┴──────────┴──────────┴────────────┴───────────┘
59+
60+
Note: The max measure is often an outlier of the first few measures, probably before the JIT optimisation happens

modules/ssr-benchmarks/angular.json

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
{
2+
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3+
"version": 1,
4+
"newProjectRoot": "projects",
5+
"projects": {
6+
"ssr-benchmarks": {
7+
"projectType": "application",
8+
"schematics": {},
9+
"root": "",
10+
"sourceRoot": "src",
11+
"prefix": "app",
12+
"architect": {
13+
"build": {
14+
"builder": "@angular-devkit/build-angular:application",
15+
"options": {
16+
"outputPath": "dist/ssr-benchmarks",
17+
"index": "src/index.html",
18+
"browser": "src/main.ts",
19+
"polyfills": [],
20+
"tsConfig": "tsconfig.app.json",
21+
"assets": [
22+
{
23+
"glob": "**/*",
24+
"input": "public"
25+
}
26+
],
27+
"styles": [],
28+
"scripts": [],
29+
"server": "src/main.server.ts",
30+
"prerender": false,
31+
"ssr": {
32+
"entry": "run-benchmark.ts"
33+
}
34+
},
35+
"configurations": {
36+
"production": {
37+
"outputHashing": "none",
38+
"define": {
39+
"DISABLE_DOM_EMULATION": "false"
40+
}
41+
},
42+
"development": {
43+
"optimization": false,
44+
"extractLicenses": false,
45+
"sourceMap": true
46+
},
47+
"browser": {
48+
"define": {
49+
"DISABLE_DOM_EMULATION": "true"
50+
}
51+
}
52+
},
53+
"defaultConfiguration": "production"
54+
},
55+
"serve": {
56+
"builder": "@angular-devkit/build-angular:dev-server",
57+
"configurations": {
58+
"production": {
59+
"buildTarget": "ssr-new:build:production"
60+
},
61+
"development": {
62+
"buildTarget": "ssr-new:build:development"
63+
}
64+
},
65+
"defaultConfiguration": "development"
66+
}
67+
}
68+
}
69+
},
70+
"cli": {
71+
"analytics": "8b89251b-0b82-4190-a832-e19ddedba643",
72+
"cache": {
73+
"enabled": false
74+
}
75+
}
76+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<html>
2+
<script type="module" src="./ssr-benchmarks/server/server.mjs"></script>
3+
<body>
4+
Check the console.
5+
<app-root></app-root>
6+
</body>
7+
8+
</html>

modules/ssr-benchmarks/package.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "ssr-benchmark",
3+
"version": "0.0.0",
4+
"scripts": {
5+
"patch": "yarn patch-package",
6+
"ng": "ng",
7+
"http-server": "http-server"
8+
},
9+
"license": "MIT",
10+
"dependencies": {
11+
"@angular-devkit/build-angular": "file:../../node_modules/@angular-devkit/build-angular",
12+
"@angular/cli": "file:../../node_modules/@angular/cli",
13+
"@angular/animations": "file:../../dist/packages-dist/animations",
14+
"@angular/common": "file:../../dist/packages-dist/common",
15+
"@angular/compiler": "file:../../dist/packages-dist/compiler",
16+
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
17+
"@angular/core": "file:../../dist/packages-dist/core",
18+
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
19+
"@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic",
20+
"@angular/platform-server": "file:../../dist/packages-dist/platform-server",
21+
"@angular/router": "file:../../dist/packages-dist/router",
22+
"@angular/ssr": "file:../../dist/node_modules/@angular/ssr",
23+
"rxjs": "file:../../node_modules/rxjs",
24+
"typescript": "file:../../node_modules/typescript",
25+
"http-server": "file:../../node_modules/http-server",
26+
"patch-package": "file:../../node_modules/patch-package"
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
diff --git a/node_modules/@angular-devkit/build-angular/node_modules/@angular/build/src/tools/esbuild/application-code-bundle.js b/node_modules/@angular-devkit/build-angular/node_modules/@angular/build/src/tools/esbuild/application-code-bundle.js
2+
index f6fb661..5e5aabb 100755
3+
--- a/node_modules/@angular-devkit/build-angular/node_modules/@angular/build/src/tools/esbuild/application-code-bundle.js
4+
+++ b/node_modules/@angular-devkit/build-angular/node_modules/@angular/build/src/tools/esbuild/application-code-bundle.js
5+
@@ -144,8 +144,8 @@ function createServerPolyfillBundleOptions(options, target, sourceFileCache) {
6+
js: [
7+
// Note: Needed as esbuild does not provide require shims / proxy from ESModules.
8+
// See: https://github.com/evanw/esbuild/issues/1921.
9+
- `import { createRequire } from 'node:module';`,
10+
- `globalThis['require'] ??= createRequire(import.meta.url);`,
11+
+ // `import { createRequire } from 'node:module';`,
12+
+ // `globalThis['require'] ??= createRequire(import.meta.url);`,
13+
].join('\n'),
14+
},
15+
target,
16+
@@ -197,15 +197,17 @@ function createServerMainCodeBundleOptions(options, target, sourceFileCache) {
17+
}
18+
// Mark manifest and polyfills file as external as these are generated by a different bundle step.
19+
(buildOptions.external ??= []).push(...utils_1.SERVER_GENERATED_EXTERNALS);
20+
- buildOptions.plugins.push((0, virtual_module_plugin_1.createVirtualModulePlugin)({
21+
- namespace: mainServerInjectPolyfillsNamespace,
22+
- cache: sourceFileCache?.loadResultCache,
23+
- loadContent: () => ({
24+
- contents: `import './polyfills.server.mjs';`,
25+
- loader: 'js',
26+
- resolveDir: workspaceRoot,
27+
- }),
28+
- }), (0, virtual_module_plugin_1.createVirtualModulePlugin)({
29+
+ buildOptions.plugins.push(
30+
+ // (0, virtual_module_plugin_1.createVirtualModulePlugin)({
31+
+ // namespace: mainServerInjectPolyfillsNamespace,
32+
+ // cache: sourceFileCache?.loadResultCache,
33+
+ // loadContent: () => ({
34+
+ // contents: `import './polyfills.server.mjs';`,
35+
+ // loader: 'js',
36+
+ // resolveDir: workspaceRoot,
37+
+ // }),
38+
+ // }),
39+
+ (0, virtual_module_plugin_1.createVirtualModulePlugin)({
40+
namespace: mainServerInjectManifestNamespace,
41+
cache: sourceFileCache?.loadResultCache,
42+
loadContent: async () => {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
diff --git a/node_modules/@angular-devkit/build-angular/node_modules/@angular/build/src/tools/esbuild/application-code-bundle.js b/node_modules/@angular-devkit/build-angular/node_modules/@angular/build/src/tools/esbuild/application-code-bundle.js
2+
index f6fb661..5e5aabb 100755
3+
--- a/node_modules/@angular-devkit/build-angular/node_modules/@angular/build/src/tools/esbuild/application-code-bundle.js
4+
+++ b/node_modules/@angular-devkit/build-angular/node_modules/@angular/build/src/tools/esbuild/application-code-bundle.js
5+
@@ -121,7 +121,7 @@ function createServerPolyfillBundleOptions(options, target, sourceFileCache) {
6+
polyfillsFromConfig.has('@angular/localize/init')) {
7+
serverPolyfills.push('@angular/localize/init');
8+
}
9+
- serverPolyfills.push('@angular/platform-server/init');
10+
+ // serverPolyfills.push('@angular/platform-server/init');
11+
const namespace = 'angular:polyfills-server';
12+
const polyfillBundleOptions = getEsBuildCommonPolyfillsOptions({
13+
...options,
14+
@@ -144,8 +144,8 @@ function createServerPolyfillBundleOptions(options, target, sourceFileCache) {
15+
js: [
16+
// Note: Needed as esbuild does not provide require shims / proxy from ESModules.
17+
// See: https://github.com/evanw/esbuild/issues/1921.
18+
- `import { createRequire } from 'node:module';`,
19+
- `globalThis['require'] ??= createRequire(import.meta.url);`,
20+
+ // `import { createRequire } from 'node:module';`,
21+
+ // `globalThis['require'] ??= createRequire(import.meta.url);`,
22+
].join('\n'),
23+
},
24+
target,
25+
@@ -197,15 +197,17 @@ function createServerMainCodeBundleOptions(options, target, sourceFileCache) {
26+
}
27+
// Mark manifest and polyfills file as external as these are generated by a different bundle step.
28+
(buildOptions.external ??= []).push(...utils_1.SERVER_GENERATED_EXTERNALS);
29+
- buildOptions.plugins.push((0, virtual_module_plugin_1.createVirtualModulePlugin)({
30+
- namespace: mainServerInjectPolyfillsNamespace,
31+
- cache: sourceFileCache?.loadResultCache,
32+
- loadContent: () => ({
33+
- contents: `import './polyfills.server.mjs';`,
34+
- loader: 'js',
35+
- resolveDir: workspaceRoot,
36+
- }),
37+
- }), (0, virtual_module_plugin_1.createVirtualModulePlugin)({
38+
+ buildOptions.plugins.push(
39+
+ // (0, virtual_module_plugin_1.createVirtualModulePlugin)({
40+
+ // namespace: mainServerInjectPolyfillsNamespace,
41+
+ // cache: sourceFileCache?.loadResultCache,
42+
+ // loadContent: () => ({
43+
+ // contents: `import './polyfills.server.mjs';`,
44+
+ // loader: 'js',
45+
+ // resolveDir: workspaceRoot,
46+
+ // }),
47+
+ // }),
48+
+ (0, virtual_module_plugin_1.createVirtualModulePlugin)({
49+
namespace: mainServerInjectManifestNamespace,
50+
cache: sourceFileCache?.loadResultCache,
51+
loadContent: async () => {

0 commit comments

Comments
 (0)