Skip to content

Commit

Permalink
tech: create benchmark
Browse files Browse the repository at this point in the history
  • Loading branch information
inomdzhon committed Jul 4, 2024
1 parent a69ed80 commit 0b29075
Show file tree
Hide file tree
Showing 29 changed files with 1,075 additions and 5 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ storybook-static/
playwright-report/
blob-report/
all-blob-reports/
tmp/
5 changes: 5 additions & 0 deletions benchmark/.env.default
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
WEB_SERVER_HOST="127.0.0.1"

WEB_SERVER_PORT=8888

STATIC_BUILD_DIR=/tmp/static/
59 changes: 59 additions & 0 deletions benchmark/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"root": true,
"env": {
"node": true,
"browser": true
},
"extends": ["plugin:react-hooks/recommended", "prettier"],
"plugins": ["import", "unicorn"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2018, // Allows for the parsing of modern ECMAScript features
"sourceType": "module", // Allows for the use of imports
"ecmaFeatures": {
"jsx": true, // Allows for the parsing of JSX
"restParams": true,
"spread": true
}
},
"rules": {
"react/prop-types": [0],
"no-shadow": "off",
"import/order": [
"error",
{
"groups": ["builtin", "external", "internal", "parent", "sibling", "index"],
"newlines-between": "never",
"alphabetize": {
"order": "asc"
},
"pathGroupsExcludedImportTypes": ["**/*.css", "react", "react-dom", "react-dom/**"],
"pathGroups": [
{
"pattern": "{react,react-dom,react-dom/**}",
"group": "external",
"position": "before"
},
{
"pattern": "{@vkui/**,@vkui}",
"group": "external",
"position": "after"
},
{ "pattern": "{.,..}/**/*.css", "group": "index", "position": "after" }
]
}
],
"sort-imports": [
"error",
{
"ignoreCase": true,
"ignoreDeclarationSort": true,
"ignoreMemberSort": false,
"allowSeparatedGroups": true,
"memberSyntaxSortOrder": ["none", "single", "all", "multiple"]
}
],
"curly": "error", // Enforce consistent brace style
"eqeqeq": "error" // Only type-safe equality operators
}
}
88 changes: 88 additions & 0 deletions benchmark/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Benchmark

## Runtime

Выдаёт таблицу с результатами скорости первого рендера (см. [Performance: measure() method](https://developer.mozilla.org/en-US/docs/Web/API/Performance/measure)),
а также некоторые браузерные метрики (см. [Chrome DevTools Protocol – Performance](https://chromedevtools.github.io/devtools-protocol/tot/Performance/)).

```sh
yarn workspace benchmark runtime:start
```

Результат выводится в консоль, а также записывается во временный файл `benchmark.md` в папке `<project>/benchmark/runtime/tmp/`.

Так как цифры не абсолютные, мы вынуждены следить за изменениями производительности вручную.

Ниже представлены результаты последнего запуска.

> Docker запускался локально на ПК со следующими характеристиками:
>
> - Чип Apple M1 Pro
> - Память 16Gb
> - macOS 14.5 (23F79)
> [!NOTE]
>
> 1. Используется `@vkontakte/vkui/dist/cssm` версия библиотеки.
> 2. `noop` задаёт базовые время и метрики, которые дают понять как выглядит результаты до применения библиотеки.
> 3. `noop with providers` как и **п.2** задаёт базу, но с учётом бойлерплейта библиотеки.
### noop

| sampleCount | mean | stdDev | min | median | max |
| ----------- | ----- | -------- | ---- | ------ | ---- |
| 15 | 06.05 | ±00.93ms | 04.3 | 06 | 07.8 |

| JSEventListeners | JSHeapTotalSize | JSHeapUsedSize | LayoutCount | LayoutDuration | RecalcStyleCount | RecalcStyleDuration | ScriptDuration | TaskDuration |
| ---------------- | --------------- | -------------- | ----------- | -------------- | ---------------- | ------------------- | -------------- | ------------ |
| 140 | 3.4 MiB | 1.8 MiB | 2 | 0.000208 | 2 | 0.00121 | 0.012348 | 0.027074 |

### noop with providers

| sampleCount | mean | stdDev | min | median | max |
| ----------- | ----- | -------- | ---- | ------ | ---- |
| 15 | 07.61 | ±01.08ms | 06.2 | 07.2 | 10.1 |

| JSEventListeners | JSHeapTotalSize | JSHeapUsedSize | LayoutCount | LayoutDuration | RecalcStyleCount | RecalcStyleDuration | ScriptDuration | TaskDuration |
| ---------------- | --------------- | -------------- | ----------- | -------------- | ---------------- | ------------------- | -------------- | ------------ |
| 146 | 3.6 MiB | 1.9 MiB | 2 | 0.000242 | 2 | 0.001816 | 0.013222 | 0.028641 |

### touch (single)

| sampleCount | mean | stdDev | min | median | max |
| ----------- | ----- | -------- | ---- | ------ | ---- |
| 15 | 07.98 | ±02.09ms | 06.6 | 07.2 | 15.4 |

| JSEventListeners | JSHeapTotalSize | JSHeapUsedSize | LayoutCount | LayoutDuration | RecalcStyleCount | RecalcStyleDuration | ScriptDuration | TaskDuration |
| ---------------- | --------------- | -------------- | ----------- | -------------- | ---------------- | ------------------- | -------------- | ------------ |
| 143 | 3.4 MiB | 1.8 MiB | 2 | 0.001632 | 2 | 0.0012 | 0.012209 | 0.028316 |

### touch width providers (single)

| sampleCount | mean | stdDev | min | median | max |
| ----------- | ----- | -------- | ---- | ------ | ---- |
| 15 | 10.55 | ±02.38ms | 07.9 | 07.9 | 17.4 |

| JSEventListeners | JSHeapTotalSize | JSHeapUsedSize | LayoutCount | LayoutDuration | RecalcStyleCount | RecalcStyleDuration | ScriptDuration | TaskDuration |
| ---------------- | --------------- | -------------- | ----------- | -------------- | ---------------- | ------------------- | -------------- | ------------ |
| 149 | 3.6 MiB | 1.9 MiB | 2 | 0.001637 | 2 | 0.001821 | 0.013611 | 0.03002 |

### touch (multiple)

| sampleCount | mean | stdDev | min | median | max |
| ----------- | ----- | -------- | ---- | ------ | ---- |
| 15 | 42.65 | ±06.64ms | 36.8 | 41.4 | 65.4 |

| JSEventListeners | JSHeapTotalSize | JSHeapUsedSize | LayoutCount | LayoutDuration | RecalcStyleCount | RecalcStyleDuration | ScriptDuration | TaskDuration |
| ---------------- | --------------- | -------------- | ----------- | -------------- | ---------------- | ------------------- | -------------- | ------------ |
| 3140 | 27.0 MiB | 9.2 MiB | 2 | 0.011171 | 2 | 0.002869 | 0.035676 | 0.07151 |

### touch with providers (multiple)

| sampleCount | mean | stdDev | min | median | max |
| ----------- | ----- | -------- | ---- | ------ | ---- |
| 15 | 43.33 | ±04.62ms | 38.1 | 42 | 57.8 |

| JSEventListeners | JSHeapTotalSize | JSHeapUsedSize | LayoutCount | LayoutDuration | RecalcStyleCount | RecalcStyleDuration | ScriptDuration | TaskDuration |
| ---------------- | --------------- | -------------- | ----------- | -------------- | ---------------- | ------------------- | -------------- | ------------ |
| 3146 | 26.5 MiB | 9.3 MiB | 3 | 0.010126 | 3 | 0.003575 | 0.037185 | 0.071412 |
30 changes: 30 additions & 0 deletions benchmark/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
services:
benchmark:
image: ${IMAGE}
ipc: host
user: root
working_dir: /repo/benchmark
command: sh -c "
YARN_ENABLE_SCRIPTS=false yarn install --immutable &&
yarn run runtime:start:ci ${UPDATE_SNAPSHOTS_FLAG:-}
"
volumes:
- ../:/repo
# Исключаем node_modules.
- /repo/node_modules
# Кешируем установленные внутри контейнера node_modules и кэш директории.
- benchmark_yarn_cache:/yarn
- benchmark_root_node_modules_cache:/repo/node_modules
# Исключаем всё то, что не потребуется в контейнере.
- /repo/benchmark/.swc
- /repo/benchmark/runtime/tmp/static
- /repo/packages/vkui/.swc
- /repo/packages/vkui/dist
- /repo/.git
- /repo/.husky
- /repo/.cache
- /repo/.swc
volumes:
benchmark_yarn_cache:
benchmark_root_node_modules_cache:
32 changes: 32 additions & 0 deletions benchmark/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"private": true,
"version": "1.0.0",
"name": "benchmark",
"packageManager": "[email protected]",
"scripts": {
"runtime:build": "yarn workspace @vkontakte/vkui build && webpack --config runtime/webpack.config.mjs",
"runtime:server": "node runtime/http-server.mjs",
"runtime:start": "../scripts/generate_env_docker.sh && docker compose --env-file=./.env.docker up --abort-on-container-exit",
"runtime:start:ci": "yarn run -T playwright test --config runtime/playwright.config.ts"
},
"dependencies": {
"@playwright/test": "1.45.0",
"@swc/core": "^1.5.25",
"@vkontakte/vkui": "workspace:packages/vkui",
"cli-table3": "^0.6.1",
"css-loader": "^6.10.0",
"css-minimizer-webpack-plugin": "^7.0.0",
"dotenv": "^16.4.5",
"html-webpack-plugin": "^5.5.0",
"mini-css-extract-plugin": "^2.9.0",
"playwright": "1.45.0",
"postcss": "^8.4.38",
"postcss-modules": "^6.0.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"serve-handler": "^6.1.5",
"swc-loader": "^0.2.6",
"terser-webpack-plugin": "^5.3.10",
"webpack": "^5.91.0"
}
}
40 changes: 40 additions & 0 deletions benchmark/runtime/http-server.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import http from 'node:http';
import path from 'node:path';
import dotenv from 'dotenv';
import handler from 'serve-handler';

dotenv.config({
path: [
path.resolve(import.meta.dirname, '../.env.default'),
path.resolve(import.meta.dirname, '../.env'),
],
override: true,
});

function createServer({ host, port }) {
const server = http.createServer((request, response) => {
return handler(request, response, {
public: path.join(import.meta.dirname, process.env.STATIC_BUILD_DIR),
});
});

function close() {
return new Promise((resolve, reject) => {
server.close((error) => {
if (error !== undefined) {
reject(error);
} else {
resolve();
}
});
});
}

return new Promise((resolve) => {
server.listen(port, host, () => {
resolve({ close });
});
});
}

void createServer({ host: process.env.WEB_SERVER_HOST, port: process.env.WEB_SERVER_PORT });
18 changes: 18 additions & 0 deletions benchmark/runtime/index.template.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>VKUI App</title>
<meta name="description" content="Test web site for benchmark" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no, viewport-fit=cover"
/>
<meta content="#ffffff" name="theme-color" />
<link rel="shortcut icon" href="https://vk.com/images/icons/favicons/fav_logo_2x.ico?6" />
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
45 changes: 45 additions & 0 deletions benchmark/runtime/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import path from 'path';
import process from 'process';
import { defineConfig, devices } from '@playwright/test';
import dotenv from 'dotenv';

dotenv.config({
path: [path.resolve(__dirname, '../.env.default'), path.resolve(__dirname, '../.env')],
override: true,
});

const webServerUrl = `http://${process.env.WEB_SERVER_HOST}:${process.env.WEB_SERVER_PORT}/`;

export default defineConfig({
timeout: 30 * 1000,

expect: { timeout: 5000 },

fullyParallel: false,

workers: 1,

reporter: './testing/reporter/index.ts',

use: {
baseURL: webServerUrl,
trace: 'retain-on-failure',
},

webServer: {
command: 'yarn run runtime:build && yarn run runtime:server',
url: webServerUrl,
reuseExistingServer: false,
},

projects: [
{
name: 'Benchmark',
use: devices['Desktop Chrome'],
repeatEach: 15,
testDir: __dirname,
testMatch: '**/*.benchmark.ts',
outputDir: './tmp/playwright-test-results',
},
],
});
9 changes: 9 additions & 0 deletions benchmark/runtime/shared/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const PERF_MARK_START = 'Perf:Start';

export const PERF_MARK_END = 'Perf:End';

export const PERF_MEASURE = 'Perf:Measure';

export const ATTACHMENT_SAMPLE_NAME = 'sample';

export const ATTACHMENT_METRICS_NAME = 'metrics';
5 changes: 5 additions & 0 deletions benchmark/runtime/shared/guards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const isPerformanceMeasure = (data: any): data is PerformanceMeasure =>
data !== null &&
typeof data === 'object' &&
data.hasOwnProperty('startTime') &&
data.hasOwnProperty('duration');
32 changes: 32 additions & 0 deletions benchmark/runtime/shared/humanFleSize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* @file см. https://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable-string
*
* Format bytes as human-readable text.
*
* @param bytes Number of bytes.
* @param si True to use metric (SI) units, aka powers of 1000. False to use
* binary (IEC), aka powers of 1024.
* @param dp Number of decimal places to display.
*
* @return Formatted string.
*/
export function humanFileSize(bytes: number, si = false, dp = 1) {
const thresh = si ? 1000 : 1024;

if (Math.abs(bytes) < thresh) {
return bytes + ' B';
}

const units = si
? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
let u = -1;
const r = 10 ** dp;

do {
bytes /= thresh;
++u;
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);

return bytes.toFixed(dp) + ' ' + units[u];
}
Loading

0 comments on commit 0b29075

Please sign in to comment.