diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml
index c426c077..81e75c22 100644
--- a/.github/workflows/development.yml
+++ b/.github/workflows/development.yml
@@ -263,6 +263,11 @@ jobs:
with:
fetch-depth: 0
+ - name: Set up Node.js 22
+ uses: actions/setup-node@v4
+ with:
+ node-version: '22'
+
- name: Check if UI-related files changed
id: check-changes
run: |
diff --git a/.gitignore b/.gitignore
index ebbf9b09..dccc8bd8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@ src/guidellm/version.py
benchmarks.json
benchmarks.yaml
benchmarks.csv
+benchmarks.html
# Byte-compiled / optimized / DLL files
__pycache__/
@@ -227,4 +228,5 @@ src/ui/next-env.d.ts
!tsconfig.*.json
!src/ui/lib
!src/ui/public/manifest.json
+!src/ui/serve.json
.eslintcache
diff --git a/DEVELOPING.md b/DEVELOPING.md
index dde51744..517ce689 100644
--- a/DEVELOPING.md
+++ b/DEVELOPING.md
@@ -267,6 +267,16 @@ Reference [https://www.npmjs.com/package/jest-runner-groups](jest-runner-groups)
*/
```
+### 🧪 UI Development Notes
+
+During development, it can be helpful to view sample data. We include a sample benchmark run wired into the Redux store under:
+
+```
+src/ui/lib/store/[runInfo/workloadDetails/benchmarks]WindowData.ts
+```
+
+In the future this will be replaced by a configurable untracked file for dev use.
+
## Additional Resources
- [CONTRIBUTING.md](https://github.com/neuralmagic/guidellm/blob/main/CONTRIBUTING.md): Guidelines for contributing to the project.
diff --git a/README.md b/README.md
index 1e489bb5..a895bcd5 100644
--- a/README.md
+++ b/README.md
@@ -157,17 +157,19 @@ The `guidellm benchmark` command is used to run benchmarks against a generative
GuideLLM UI is a companion frontend for visualizing the results of a GuideLLM benchmark run.
-### 🛠Running the UI
+### 🛠Generating an HTML report with a benchmark run
-1. Use the Hosted Build (Recommended for Most Users)
-
-After running a benchmark with GuideLLM, a report.html file will be generated (by default at guidellm_report/report.html). This file references the latest stable version of the UI hosted at:
+Set the output to benchmarks.html for your run:
+```base
+--output-path=benchmarks.html
```
-https://neuralmagic.github.io/guidellm/ui/dev/
-```
-Open the file in your browser and you're done—no setup required.
+1. Use the Hosted Build (Recommended for Most Users)
+
+This is preconfigured. The latest stable version of the hosted UI (https://neuralmagic.github.io/guidellm/ui/latest) will be used to build the local html file.
+
+Open benchmarks.html in your browser and you're done—no setup required.
2. Build and Serve the UI Locally (For Development) This option is useful if:
@@ -180,20 +182,16 @@ Open the file in your browser and you're done—no setup required.
```bash
npm install
npm run build
-npx serve out
+npm run serve
```
-This will start a local server (e.g., at http://localhost:3000). Then, in your GuideLLM config or CLI flags, point to this local server as the asset base for report generation.
-
-### 🧪 Development Notes
+This will start a local server (e.g., at http://localhost:3000). Then set the Environment to LOCAL before running your benchmarks.
-During UI development, it can be helpful to view sample data. We include a sample benchmark run wired into the Redux store under:
+```bash
+export GUIDELLM__ENV=local
+Alternatively, in config.py update the ENV_REPORT_MAPPING used as the asset base for report generation to the LOCAL option.
```
-src/lib/store/[runInfo/workloadDetails/benchmarks]WindowData.ts
-```
-
-In the future this will be replaced by a configurable untracked file for dev use.
## Resources
diff --git a/package-lock.json b/package-lock.json
index 5ac94871..a210be66 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -61,6 +61,7 @@
"jest-runner-groups": "^2.2.0",
"jest-transform-stub": "^2.0.0",
"prettier": "^3.5.3",
+ "serve": "^14.2.4",
"sharp": "^0.32.0",
"typescript": "^5",
"typescript-eslint": "^8.34.0"
@@ -2038,6 +2039,18 @@
"ms": "^2.1.1"
}
},
+ "node_modules/@emnapi/core": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz",
+ "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.0.2",
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/@emnapi/runtime": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz",
@@ -2048,6 +2061,17 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz",
+ "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/@emotion/babel-plugin": {
"version": "11.13.5",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
@@ -3922,6 +3946,19 @@
}
}
},
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "0.2.11",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz",
+ "integrity": "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.4.3",
+ "@emnapi/runtime": "^1.4.3",
+ "@tybys/wasm-util": "^0.9.0"
+ }
+ },
"node_modules/@next/env": {
"version": "15.3.2",
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.2.tgz",
@@ -4864,6 +4901,17 @@
"node": ">=10.13.0"
}
},
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz",
+ "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/@types/aria-query": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
@@ -5503,6 +5551,240 @@
"darwin"
]
},
+ "node_modules/@unrs/resolver-binding-darwin-x64": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.9.0.tgz",
+ "integrity": "sha512-TK+UA1TTa0qS53rjWn7cVlEKVGz2B6JYe0C++TdQjvWYIyx83ruwh0wd4LRxYBM5HeuAzXcylA9BH2trARXJTw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-freebsd-x64": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.9.0.tgz",
+ "integrity": "sha512-6uZwzMRFcD7CcCd0vz3Hp+9qIL2jseE/bx3ZjaLwn8t714nYGwiE84WpaMCYjU+IQET8Vu/+BNAGtYD7BG/0yA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.9.0.tgz",
+ "integrity": "sha512-bPUBksQfrgcfv2+mm+AZinaKq8LCFvt5PThYqRotqSuuZK1TVKkhbVMS/jvSRfYl7jr3AoZLYbDkItxgqMKRkg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.9.0.tgz",
+ "integrity": "sha512-uT6E7UBIrTdCsFQ+y0tQd3g5oudmrS/hds5pbU3h4s2t/1vsGWbbSKhBSCD9mcqaqkBwoqlECpUrRJCmldl8PA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-gnu": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.9.0.tgz",
+ "integrity": "sha512-vdqBh911wc5awE2bX2zx3eflbyv8U9xbE/jVKAm425eRoOVv/VseGZsqi3A3SykckSpF4wSROkbQPvbQFn8EsA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-musl": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.9.0.tgz",
+ "integrity": "sha512-/8JFZ/SnuDr1lLEVsxsuVwrsGquTvT51RZGvyDB/dOK3oYK2UqeXzgeyq6Otp8FZXQcEYqJwxb9v+gtdXn03eQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.9.0.tgz",
+ "integrity": "sha512-FkJjybtrl+rajTw4loI3L6YqSOpeZfDls4SstL/5lsP2bka9TiHUjgMBjygeZEis1oC8LfJTS8FSgpKPaQx2tQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.9.0.tgz",
+ "integrity": "sha512-w/NZfHNeDusbqSZ8r/hp8iL4S39h4+vQMc9/vvzuIKMWKppyUGKm3IST0Qv0aOZ1rzIbl9SrDeIqK86ZpUK37w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-musl": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.9.0.tgz",
+ "integrity": "sha512-bEPBosut8/8KQbUixPry8zg/fOzVOWyvwzOfz0C0Rw6dp+wIBseyiHKjkcSyZKv/98edrbMknBaMNJfA/UEdqw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-s390x-gnu": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.9.0.tgz",
+ "integrity": "sha512-LDtMT7moE3gK753gG4pc31AAqGUC86j3AplaFusc717EUGF9ZFJ356sdQzzZzkBk1XzMdxFyZ4f/i35NKM/lFA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-gnu": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.9.0.tgz",
+ "integrity": "sha512-WmFd5KINHIXj8o1mPaT8QRjA9HgSXhN1gl9Da4IZihARihEnOylu4co7i/yeaIpcfsI6sYs33cNZKyHYDh0lrA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-musl": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.9.0.tgz",
+ "integrity": "sha512-CYuXbANW+WgzVRIl8/QvZmDaZxrqvOldOwlbUjIM4pQ46FJ0W5cinJ/Ghwa/Ng1ZPMJMk1VFdsD/XwmCGIXBWg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-wasm32-wasi": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.9.0.tgz",
+ "integrity": "sha512-6Rp2WH0OoitMYR57Z6VE8Y6corX8C6QEMWLgOV6qXiJIeZ1F9WGXY/yQ8yDC4iTraotyLOeJ2Asea0urWj2fKQ==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@napi-rs/wasm-runtime": "^0.2.11"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@unrs/resolver-binding-win32-arm64-msvc": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.9.0.tgz",
+ "integrity": "sha512-rknkrTRuvujprrbPmGeHi8wYWxmNVlBoNW8+4XF2hXUnASOjmuC9FNF1tGbDiRQWn264q9U/oGtixyO3BT8adQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-ia32-msvc": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.9.0.tgz",
+ "integrity": "sha512-Ceymm+iBl+bgAICtgiHyMLz6hjxmLJKqBim8tDzpX61wpZOx2bPK6Gjuor7I2RiUynVjvvkoRIkrPyMwzBzF3A==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-x64-msvc": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.9.0.tgz",
+ "integrity": "sha512-k59o9ZyeyS0hAlcaKFezYSH2agQeRFEB7KoQLXl3Nb3rgkqT1NY9Vwy+SqODiLmYnEjxWJVRE/yq2jFVqdIxZw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@zeit/schemas": {
+ "version": "2.36.0",
+ "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz",
+ "integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/abab": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
@@ -5511,6 +5793,20 @@
"dev": true,
"license": "BSD-3-Clause"
},
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
@@ -5602,6 +5898,16 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
+ "node_modules/ansi-align": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
+ "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.1.0"
+ }
+ },
"node_modules/ansi-colors": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
@@ -5689,6 +5995,13 @@
],
"license": "MIT"
},
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -6334,6 +6647,146 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/boxen": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz",
+ "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-align": "^3.0.1",
+ "camelcase": "^7.0.0",
+ "chalk": "^5.0.1",
+ "cli-boxes": "^3.0.0",
+ "string-width": "^5.1.2",
+ "type-fest": "^2.13.0",
+ "widest-line": "^4.0.1",
+ "wrap-ansi": "^8.0.1"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/boxen/node_modules/ansi-regex": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/boxen/node_modules/ansi-styles": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/boxen/node_modules/camelcase": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz",
+ "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/boxen/node_modules/chalk": {
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
+ "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/boxen/node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/boxen/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/boxen/node_modules/type-fest": {
+ "version": "2.19.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
+ "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/boxen/node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
@@ -6454,6 +6907,16 @@
"node": ">=10.16.0"
}
},
+ "node_modules/bytes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
+ "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/cachedir": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz",
@@ -6580,6 +7043,22 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/chalk-template": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz",
+ "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.1.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk-template?sponsor=1"
+ }
+ },
"node_modules/char-regex": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
@@ -6640,6 +7119,19 @@
"node": ">=6"
}
},
+ "node_modules/cli-boxes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
+ "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/cli-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
@@ -6692,6 +7184,71 @@
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT"
},
+ "node_modules/clipboardy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz",
+ "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "arch": "^2.2.0",
+ "execa": "^5.1.1",
+ "is-wsl": "^2.2.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/clipboardy/node_modules/execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/clipboardy/node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/clipboardy/node_modules/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=10.17.0"
+ }
+ },
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
@@ -6806,19 +7363,75 @@
"dev": true,
"license": "MIT",
"engines": {
- "node": ">= 6"
+ "node": ">= 6"
+ }
+ },
+ "node_modules/common-tags": {
+ "version": "1.8.2",
+ "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz",
+ "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/compressible": {
+ "version": "2.0.18",
+ "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
+ "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": ">= 1.43.0 < 2"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/compression": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz",
+ "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.5",
+ "bytes": "3.0.0",
+ "compressible": "~2.0.16",
+ "debug": "2.6.9",
+ "on-headers": "~1.0.2",
+ "safe-buffer": "5.1.2",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
}
},
- "node_modules/common-tags": {
- "version": "1.8.2",
- "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz",
- "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==",
+ "node_modules/compression/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=4.0.0"
+ "dependencies": {
+ "ms": "2.0.0"
}
},
+ "node_modules/compression/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/compression/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -6826,6 +7439,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/content-disposition": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
+ "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/convert-source-map": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
@@ -7670,8 +8293,7 @@
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true,
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/ecc-jsbn": {
"version": "0.1.2",
@@ -9979,6 +10601,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-docker": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -10137,6 +10775,19 @@
"node": ">=8"
}
},
+ "node_modules/is-port-reachable": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz",
+ "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/is-potential-custom-element-name": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
@@ -10322,6 +10973,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-wsl": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
+ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-docker": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/isarray": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
@@ -13095,6 +13759,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/next": {
"version": "15.3.2",
"resolved": "https://registry.npmjs.org/next/-/next-15.3.2.tgz",
@@ -13505,6 +14179,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/on-headers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
+ "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -13716,6 +14400,13 @@
"node": ">=0.10.0"
}
},
+ "node_modules/path-is-inside": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+ "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==",
+ "dev": true,
+ "license": "(WTFPL OR MIT)"
+ },
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -13758,6 +14449,13 @@
"license": "ISC",
"peer": true
},
+ "node_modules/path-to-regexp": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz",
+ "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
@@ -14226,6 +14924,16 @@
],
"license": "MIT"
},
+ "node_modules/range-parser": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
+ "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
@@ -14478,6 +15186,30 @@
"node": ">=4"
}
},
+ "node_modules/registry-auth-token": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz",
+ "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "rc": "^1.1.6",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/registry-url": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz",
+ "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "rc": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/regjsgen": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz",
@@ -14531,6 +15263,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
@@ -14803,6 +15545,108 @@
"semver": "bin/semver.js"
}
},
+ "node_modules/serve": {
+ "version": "14.2.4",
+ "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.4.tgz",
+ "integrity": "sha512-qy1S34PJ/fcY8gjVGszDB3EXiPSk5FKhUa7tQe0UPRddxRidc2V6cNHPNewbE1D7MAkgLuWEt3Vw56vYy73tzQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@zeit/schemas": "2.36.0",
+ "ajv": "8.12.0",
+ "arg": "5.0.2",
+ "boxen": "7.0.0",
+ "chalk": "5.0.1",
+ "chalk-template": "0.4.0",
+ "clipboardy": "3.0.0",
+ "compression": "1.7.4",
+ "is-port-reachable": "4.0.0",
+ "serve-handler": "6.1.6",
+ "update-check": "1.5.4"
+ },
+ "bin": {
+ "serve": "build/main.js"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/serve-handler": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz",
+ "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.0.0",
+ "content-disposition": "0.5.2",
+ "mime-types": "2.1.18",
+ "minimatch": "3.1.2",
+ "path-is-inside": "1.0.2",
+ "path-to-regexp": "3.3.0",
+ "range-parser": "1.2.0"
+ }
+ },
+ "node_modules/serve-handler/node_modules/mime-db": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
+ "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/serve-handler/node_modules/mime-types": {
+ "version": "2.1.18",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
+ "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "~1.33.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/serve/node_modules/ajv": {
+ "version": "8.12.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
+ "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/serve/node_modules/chalk": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz",
+ "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/serve/node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -16220,6 +17064,17 @@
"browserslist": ">= 4.21.0"
}
},
+ "node_modules/update-check": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz",
+ "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "registry-auth-token": "3.3.2",
+ "registry-url": "3.1.0"
+ }
+ },
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -16289,6 +17144,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/verror": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
@@ -16479,6 +17344,69 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/widest-line": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz",
+ "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "string-width": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/widest-line/node_modules/ansi-regex": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/widest-line/node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/widest-line/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
"node_modules/word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
@@ -16665,6 +17593,36 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/@next/swc-linux-x64-gnu": {
+ "version": "15.3.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.2.tgz",
+ "integrity": "sha512-uRBo6THWei0chz+Y5j37qzx+BtoDRFIkDzZjlpCItBRXyMPIg079eIkOCl3aqr2tkxL4HFyJ4GHDes7W8HuAUg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-musl": {
+ "version": "15.3.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.2.tgz",
+ "integrity": "sha512-+uxFlPuCNx/T9PdMClOqeE8USKzj8tVz37KflT3Kdbx/LOlZBRI2yxuIcmx1mPNK8DwSOMNCr4ureSet7eyC0w==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
}
}
}
diff --git a/package.json b/package.json
index 3abaf7b3..87974d43 100644
--- a/package.json
+++ b/package.json
@@ -5,6 +5,7 @@
"scripts": {
"dev": "next dev src/ui",
"build": "next build src/ui",
+ "serve": "serve src/ui/out -c ../serve.json --cors",
"lint": "next lint --fix src/ui",
"type-check": "tsc -p src/ui/tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit && tsc -p tsconfig.cypress.json --noEmit",
"format": "prettier --write .",
@@ -71,6 +72,7 @@
"jest-runner-groups": "^2.2.0",
"jest-transform-stub": "^2.0.0",
"prettier": "^3.5.3",
+ "serve": "^14.2.4",
"sharp": "^0.32.0",
"typescript": "^5",
"typescript-eslint": "^8.34.0"
diff --git a/pyproject.toml b/pyproject.toml
index a78b1fc5..aef3f44a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -53,6 +53,7 @@ dependencies = [
"protobuf",
"pydantic>=2.0.0",
"pydantic-settings>=2.0.0",
+ "pyhumps>=3.8.0",
"pyyaml>=6.0.0",
"rich",
"transformers",
diff --git a/src/guidellm/__main__.py b/src/guidellm/__main__.py
index 9e8a12fb..e8dee36d 100644
--- a/src/guidellm/__main__.py
+++ b/src/guidellm/__main__.py
@@ -206,7 +206,7 @@ def cli():
help=(
"The path to save the output to. If it is a directory, "
"it will save benchmarks.json under it. "
- "Otherwise, json, yaml, or csv files are supported for output types "
+ "Otherwise, json, yaml, csv, or html files are supported for output types "
"which will be read from the extension for the file path."
),
)
diff --git a/src/guidellm/benchmark/output.py b/src/guidellm/benchmark/output.py
index 4847160d..792097cb 100644
--- a/src/guidellm/benchmark/output.py
+++ b/src/guidellm/benchmark/output.py
@@ -6,6 +6,7 @@
from pathlib import Path
from typing import Any, Literal, Optional, Union
+import humps # type: ignore[import-not-found]
import yaml
from pydantic import Field
from rich.console import Console
@@ -25,6 +26,8 @@
StandardBaseModel,
StatusDistributionSummary,
)
+from guidellm.presentation import UIDataBuilder
+from guidellm.presentation.injector import create_report
from guidellm.scheduler import strategy_display_str
from guidellm.utils import Colors, split_text_list_by_length
@@ -68,6 +71,9 @@ def load_file(path: Union[str, Path]) -> "GenerativeBenchmarksReport":
if type_ == "csv":
raise ValueError(f"CSV file type is not supported for loading: {path}.")
+ if type_ == "html":
+ raise ValueError(f"HTML file type is not supported for loading: {path}.")
+
raise ValueError(f"Unsupported file type: {type_} for {path}.")
benchmarks: list[GenerativeBenchmark] = Field(
@@ -114,6 +120,9 @@ def save_file(self, path: Union[str, Path]) -> Path:
if type_ == "csv":
return self.save_csv(path)
+ if type_ == "html":
+ return self.save_html(path)
+
raise ValueError(f"Unsupported file type: {type_} for {path}.")
def save_json(self, path: Union[str, Path]) -> Path:
@@ -220,11 +229,29 @@ def save_csv(self, path: Union[str, Path]) -> Path:
return path
+ def save_html(self, path: Union[str, Path]) -> Path:
+ """
+ Download html, inject report data and save to a file.
+
+ :param path: The path to create the report at.
+ :return: The path to the report.
+ """
+
+ data_builder = UIDataBuilder(self.benchmarks)
+ data = data_builder.to_dict()
+ camel_data = humps.camelize(data)
+ ui_api_data = {}
+ for k, v in camel_data.items():
+ key = f"window.{humps.decamelize(k)} = {{}};"
+ value = f"window.{humps.decamelize(k)} = {json.dumps(v, indent=2)};\n"
+ ui_api_data[key] = value
+ return create_report(ui_api_data, path)
+
@staticmethod
def _file_setup(
path: Union[str, Path],
- default_file_type: Literal["json", "yaml", "csv"] = "json",
- ) -> tuple[Path, Literal["json", "yaml", "csv"]]:
+ default_file_type: Literal["json", "yaml", "csv", "html"] = "json",
+ ) -> tuple[Path, Literal["json", "yaml", "csv", "html"]]:
path = Path(path) if not isinstance(path, Path) else path
if path.is_dir():
@@ -242,6 +269,9 @@ def _file_setup(
if path_suffix in [".csv"]:
return path, "csv"
+ if path_suffix in [".html"]:
+ return path, "html"
+
raise ValueError(f"Unsupported file extension: {path_suffix} for {path}.")
@staticmethod
diff --git a/src/guidellm/config.py b/src/guidellm/config.py
index ed7e782b..134d3208 100644
--- a/src/guidellm/config.py
+++ b/src/guidellm/config.py
@@ -30,10 +30,10 @@ class Environment(str, Enum):
ENV_REPORT_MAPPING = {
- Environment.PROD: "https://guidellm.neuralmagic.com/local-report/index.html",
- Environment.STAGING: "https://staging.guidellm.neuralmagic.com/local-report/index.html",
- Environment.DEV: "https://dev.guidellm.neuralmagic.com/local-report/index.html",
- Environment.LOCAL: "tests/dummy/report.html",
+ Environment.PROD: "https://neuralmagic.github.io/guidellm/ui/latest/index.html",
+ Environment.STAGING: "https://neuralmagic.github.io/guidellm/ui/release/latest/index.html",
+ Environment.DEV: "https://neuralmagic.github.io/guidellm/ui/dev/index.html",
+ Environment.LOCAL: "http://localhost:3000/index.html",
}
@@ -87,6 +87,14 @@ class OpenAISettings(BaseModel):
max_output_tokens: int = 16384
+class ReportGenerationSettings(BaseModel):
+ """
+ Report generation settings for the application
+ """
+
+ source: str = ""
+
+
class Settings(BaseSettings):
"""
All the settings are powered by pydantic_settings and could be
@@ -140,6 +148,9 @@ class Settings(BaseSettings):
)
openai: OpenAISettings = OpenAISettings()
+ # Report settings
+ report_generation: ReportGenerationSettings = ReportGenerationSettings()
+
# Output settings
table_border_char: str = "="
table_headers_border_char: str = "-"
@@ -148,6 +159,8 @@ class Settings(BaseSettings):
@model_validator(mode="after")
@classmethod
def set_default_source(cls, values):
+ if not values.report_generation.source:
+ values.report_generation.source = ENV_REPORT_MAPPING.get(values.env)
return values
def generate_env_file(self) -> str:
diff --git a/src/guidellm/objects/statistics.py b/src/guidellm/objects/statistics.py
index 552b5c20..7831b2cf 100644
--- a/src/guidellm/objects/statistics.py
+++ b/src/guidellm/objects/statistics.py
@@ -37,6 +37,9 @@ class Percentiles(StandardBaseModel):
p25: float = Field(
description="The 25th percentile of the distribution.",
)
+ p50: float = Field(
+ description="The 50th percentile of the distribution.",
+ )
p75: float = Field(
description="The 75th percentile of the distribution.",
)
@@ -159,6 +162,7 @@ def from_distribution_function(
p05=cdf[np.argmax(cdf[:, 1] >= 0.05), 0].item(), # noqa: PLR2004
p10=cdf[np.argmax(cdf[:, 1] >= 0.1), 0].item(), # noqa: PLR2004
p25=cdf[np.argmax(cdf[:, 1] >= 0.25), 0].item(), # noqa: PLR2004
+ p50=cdf[np.argmax(cdf[:, 1] >= 0.50), 0].item(), # noqa: PLR2004
p75=cdf[np.argmax(cdf[:, 1] >= 0.75), 0].item(), # noqa: PLR2004
p90=cdf[np.argmax(cdf[:, 1] >= 0.9), 0].item(), # noqa: PLR2004
p95=cdf[np.argmax(cdf[:, 1] >= 0.95), 0].item(), # noqa: PLR2004
@@ -172,6 +176,7 @@ def from_distribution_function(
p05=0,
p10=0,
p25=0,
+ p50=0,
p75=0,
p90=0,
p95=0,
diff --git a/src/guidellm/presentation/__init__.py b/src/guidellm/presentation/__init__.py
new file mode 100644
index 00000000..872188db
--- /dev/null
+++ b/src/guidellm/presentation/__init__.py
@@ -0,0 +1,28 @@
+from .builder import UIDataBuilder
+from .data_models import (
+ BenchmarkDatum,
+ Bucket,
+ Dataset,
+ Distribution,
+ Model,
+ RunInfo,
+ Server,
+ TokenDetails,
+ WorkloadDetails,
+)
+from .injector import create_report, inject_data
+
+__all__ = [
+ "BenchmarkDatum",
+ "Bucket",
+ "Dataset",
+ "Distribution",
+ "Model",
+ "RunInfo",
+ "Server",
+ "TokenDetails",
+ "UIDataBuilder",
+ "WorkloadDetails",
+ "create_report",
+ "inject_data",
+]
diff --git a/src/guidellm/presentation/builder.py b/src/guidellm/presentation/builder.py
new file mode 100644
index 00000000..a27d7cec
--- /dev/null
+++ b/src/guidellm/presentation/builder.py
@@ -0,0 +1,27 @@
+from typing import TYPE_CHECKING, Any
+
+if TYPE_CHECKING:
+ from guidellm.benchmark.benchmark import GenerativeBenchmark
+
+from .data_models import BenchmarkDatum, RunInfo, WorkloadDetails
+
+
+class UIDataBuilder:
+ def __init__(self, benchmarks: list["GenerativeBenchmark"]):
+ self.benchmarks = benchmarks
+
+ def build_run_info(self):
+ return RunInfo.from_benchmarks(self.benchmarks)
+
+ def build_workload_details(self):
+ return WorkloadDetails.from_benchmarks(self.benchmarks)
+
+ def build_benchmarks(self):
+ return [BenchmarkDatum.from_benchmark(b) for b in self.benchmarks]
+
+ def to_dict(self) -> dict[str, Any]:
+ return {
+ "run_info": self.build_run_info().model_dump(),
+ "workload_details": self.build_workload_details().model_dump(),
+ "benchmarks": [b.model_dump() for b in self.build_benchmarks()],
+ }
diff --git a/src/guidellm/presentation/data_models.py b/src/guidellm/presentation/data_models.py
new file mode 100644
index 00000000..ff5221e3
--- /dev/null
+++ b/src/guidellm/presentation/data_models.py
@@ -0,0 +1,232 @@
+import random
+from collections import defaultdict
+from math import ceil
+from typing import TYPE_CHECKING, Optional, Union
+
+from pydantic import BaseModel, computed_field
+
+if TYPE_CHECKING:
+ from guidellm.benchmark.benchmark import GenerativeBenchmark
+
+from guidellm.objects.statistics import DistributionSummary
+
+
+class Bucket(BaseModel):
+ value: Union[float, int]
+ count: int
+
+ @staticmethod
+ def from_data(
+ data: Union[list[float], list[int]],
+ bucket_width: Optional[float] = None,
+ n_buckets: Optional[int] = None,
+ ) -> tuple[list["Bucket"], float]:
+ if not data:
+ return [], 1.0
+
+ min_v = min(data)
+ max_v = max(data)
+ range_v = (1 + max_v) - min_v
+
+ if bucket_width is None:
+ if n_buckets is None:
+ n_buckets = 10
+ bucket_width = range_v / n_buckets
+ else:
+ n_buckets = ceil(range_v / bucket_width)
+
+ bucket_counts: defaultdict[Union[float, int], int] = defaultdict(int)
+ for val in data:
+ idx = int((val - min_v) // bucket_width)
+ if idx >= n_buckets:
+ idx = n_buckets - 1
+ bucket_start = min_v + idx * bucket_width
+ bucket_counts[bucket_start] += 1
+
+ buckets = [
+ Bucket(value=start, count=count)
+ for start, count in sorted(bucket_counts.items())
+ ]
+ return buckets, bucket_width
+
+
+class Model(BaseModel):
+ name: str
+ size: int
+
+
+class Dataset(BaseModel):
+ name: str
+
+
+class RunInfo(BaseModel):
+ model: Model
+ task: str
+ timestamp: float
+ dataset: Dataset
+
+ @classmethod
+ def from_benchmarks(cls, benchmarks: list["GenerativeBenchmark"]):
+ model = benchmarks[0].worker.backend_model or "N/A"
+ timestamp = max(
+ bm.run_stats.start_time for bm in benchmarks if bm.start_time is not None
+ )
+ return cls(
+ model=Model(name=model, size=0),
+ task="N/A",
+ timestamp=timestamp,
+ dataset=Dataset(name="N/A"),
+ )
+
+
+class Distribution(BaseModel):
+ statistics: Optional[DistributionSummary] = None
+ buckets: list[Bucket]
+ bucket_width: float
+
+
+class TokenDetails(BaseModel):
+ samples: list[str]
+ token_distributions: Distribution
+
+
+class Server(BaseModel):
+ target: str
+
+
+class RequestOverTime(BaseModel):
+ num_benchmarks: int
+ requests_over_time: Distribution
+
+
+class WorkloadDetails(BaseModel):
+ prompts: TokenDetails
+ generations: TokenDetails
+ requests_over_time: RequestOverTime
+ rate_type: str
+ server: Server
+
+ @classmethod
+ def from_benchmarks(cls, benchmarks: list["GenerativeBenchmark"]):
+ target = benchmarks[0].worker.backend_target
+ rate_type = benchmarks[0].args.profile.type_
+ successful_requests = [
+ req for bm in benchmarks for req in bm.requests.successful
+ ]
+ sample_indices = random.sample(
+ range(len(successful_requests)), min(5, len(successful_requests))
+ )
+ sample_prompts = [
+ successful_requests[i].prompt.replace("\n", " ").replace('"', "'")
+ for i in sample_indices
+ ]
+ sample_outputs = [
+ successful_requests[i].output.replace("\n", " ").replace('"', "'")
+ for i in sample_indices
+ ]
+
+ prompt_tokens = [
+ float(req.prompt_tokens)
+ for bm in benchmarks
+ for req in bm.requests.successful
+ ]
+ output_tokens = [
+ float(req.output_tokens)
+ for bm in benchmarks
+ for req in bm.requests.successful
+ ]
+
+ prompt_token_buckets, _prompt_token_bucket_width = Bucket.from_data(
+ prompt_tokens, 1
+ )
+ output_token_buckets, _output_token_bucket_width = Bucket.from_data(
+ output_tokens, 1
+ )
+
+ prompt_token_stats = DistributionSummary.from_values(prompt_tokens)
+ output_token_stats = DistributionSummary.from_values(output_tokens)
+ prompt_token_distributions = Distribution(
+ statistics=prompt_token_stats, buckets=prompt_token_buckets, bucket_width=1
+ )
+ output_token_distributions = Distribution(
+ statistics=output_token_stats, buckets=output_token_buckets, bucket_width=1
+ )
+
+ min_start_time = benchmarks[0].run_stats.start_time
+
+ all_req_times = [
+ req.start_time - min_start_time
+ for bm in benchmarks
+ for req in bm.requests.successful
+ if req.start_time is not None
+ ]
+ number_of_buckets = len(benchmarks)
+ request_over_time_buckets, bucket_width = Bucket.from_data(
+ all_req_times, None, number_of_buckets
+ )
+ request_over_time_distribution = Distribution(
+ buckets=request_over_time_buckets, bucket_width=bucket_width
+ )
+ return cls(
+ prompts=TokenDetails(
+ samples=sample_prompts, token_distributions=prompt_token_distributions
+ ),
+ generations=TokenDetails(
+ samples=sample_outputs, token_distributions=output_token_distributions
+ ),
+ requests_over_time=RequestOverTime(
+ requests_over_time=request_over_time_distribution,
+ num_benchmarks=number_of_buckets,
+ ),
+ rate_type=rate_type,
+ server=Server(target=target),
+ )
+
+
+class TabularDistributionSummary(DistributionSummary):
+ """
+ Same fields as `DistributionSummary`, but adds a ready-to-serialize/iterate
+ `percentile_rows` helper.
+ """
+
+ @computed_field
+ def percentile_rows(self) -> list[dict[str, float]]:
+ rows = [
+ {"percentile": name, "value": value}
+ for name, value in self.percentiles.model_dump().items()
+ ]
+ return list(
+ filter(lambda row: row["percentile"] in ["p50", "p90", "p95", "p99"], rows)
+ )
+
+ @classmethod
+ def from_distribution_summary(
+ cls, distribution: DistributionSummary
+ ) -> "TabularDistributionSummary":
+ return cls(**distribution.model_dump())
+
+
+class BenchmarkDatum(BaseModel):
+ requests_per_second: float
+ tpot: TabularDistributionSummary
+ ttft: TabularDistributionSummary
+ throughput: TabularDistributionSummary
+ time_per_request: TabularDistributionSummary
+
+ @classmethod
+ def from_benchmark(cls, bm: "GenerativeBenchmark"):
+ return cls(
+ requests_per_second=bm.metrics.requests_per_second.successful.mean,
+ tpot=TabularDistributionSummary.from_distribution_summary(
+ bm.metrics.inter_token_latency_ms.successful
+ ),
+ ttft=TabularDistributionSummary.from_distribution_summary(
+ bm.metrics.time_to_first_token_ms.successful
+ ),
+ throughput=TabularDistributionSummary.from_distribution_summary(
+ bm.metrics.output_tokens_per_second.successful
+ ),
+ time_per_request=TabularDistributionSummary.from_distribution_summary(
+ bm.metrics.request_latency.successful
+ ),
+ )
diff --git a/src/guidellm/presentation/injector.py b/src/guidellm/presentation/injector.py
new file mode 100644
index 00000000..a2a4855a
--- /dev/null
+++ b/src/guidellm/presentation/injector.py
@@ -0,0 +1,62 @@
+import re
+from pathlib import Path
+from typing import Union
+
+from guidellm.config import settings
+from guidellm.utils.text import load_text
+
+
+def create_report(js_data: dict, output_path: Union[str, Path]) -> Path:
+ """
+ Creates a report from the dictionary and saves it to the output path.
+
+ :param js_data: dict with match str and json data to inject
+ :type js_data: dict
+ :param output_path: the file to save the report to.
+ :type output_path: str
+ :return: the path to the saved report
+ :rtype: str
+ """
+
+ if not isinstance(output_path, Path):
+ output_path = Path(output_path)
+
+ html_content = load_text(settings.report_generation.source)
+ report_content = inject_data(
+ js_data,
+ html_content,
+ )
+
+ output_path.parent.mkdir(parents=True, exist_ok=True)
+ output_path.write_text(report_content)
+ return output_path
+
+
+def inject_data(
+ js_data: dict,
+ html: str,
+) -> str:
+ """
+ Injects the json data into the HTML,
+ replacing placeholders only within the
section.
+
+ :param js_data: the json data to inject
+ :type js_data: dict
+ :param html: the html to inject the data into
+ :type html: str
+ :return: the html with the json data injected
+ :rtype: str
+ """
+ head_match = re.search(r"]*>(.*?)", html, re.DOTALL | re.IGNORECASE)
+ if not head_match:
+ return html # or raise error?
+
+ head_content = head_match.group(1)
+
+ # Replace placeholders only inside the content
+ for placeholder, script in js_data.items():
+ head_content = head_content.replace(placeholder, script)
+
+ # Rebuild the HTML
+ new_head = f"{head_content}"
+ return html[: head_match.start()] + new_head + html[head_match.end() :]
diff --git a/src/ui/.env.development b/src/ui/.env.development
index 938fd8bd..2d03b789 100644
--- a/src/ui/.env.development
+++ b/src/ui/.env.development
@@ -1,3 +1,3 @@
-ASSET_PREFIX=https://neuralmagic.github.io/ui/dev
+ASSET_PREFIX=https://neuralmagic.github.io/guidellm/ui/dev
BASE_PATH=/ui/dev
NEXT_PUBLIC_USE_MOCK_API=true
diff --git a/src/ui/.env.example b/src/ui/.env.example
index 06812a30..b9d5ff2b 100644
--- a/src/ui/.env.example
+++ b/src/ui/.env.example
@@ -1,3 +1,4 @@
ASSET_PREFIX=http://localhost:3000
BASE_PATH=http://localhost:3000
NEXT_PUBLIC_USE_MOCK_API=true
+USE_MOCK_DATA=false
diff --git a/src/ui/.env.local b/src/ui/.env.local
index 44ab168b..60b459a6 100644
--- a/src/ui/.env.local
+++ b/src/ui/.env.local
@@ -1,4 +1,4 @@
-ASSET_PREFIX=http://localhost:3000
-BASE_PATH=http://localhost:3000
+ASSET_PREFIX=http://localhost:3001
+BASE_PATH=http://localhost:3001
NEXT_PUBLIC_USE_MOCK_API=true
-USE_MOCK_DATA=true
+USE_MOCK_DATA=false
diff --git a/src/ui/.env.production b/src/ui/.env.production
index 2aa8deb6..981e7d86 100644
--- a/src/ui/.env.production
+++ b/src/ui/.env.production
@@ -1,3 +1,3 @@
-ASSET_PREFIX=https://neuralmagic.github.io/ui/latest
+ASSET_PREFIX=https://neuralmagic.github.io/guidellm/ui/latest
BASE_PATH=/ui/latest
NEXT_PUBLIC_USE_MOCK_API=true
diff --git a/src/ui/.env.staging b/src/ui/.env.staging
index a2280817..416e04c3 100644
--- a/src/ui/.env.staging
+++ b/src/ui/.env.staging
@@ -1,3 +1,3 @@
-ASSET_PREFIX=https://neuralmagic.github.io/ui/release/latest
+ASSET_PREFIX=https://neuralmagic.github.io/guidellm/ui/release/latest
BASE_PATH=/ui/release/latest
NEXT_PUBLIC_USE_MOCK_API=true
diff --git a/src/ui/lib/components/Charts/DashedLine/DashedLine.component.tsx b/src/ui/lib/components/Charts/DashedLine/DashedLine.component.tsx
index a3b9ab87..650c62ee 100644
--- a/src/ui/lib/components/Charts/DashedLine/DashedLine.component.tsx
+++ b/src/ui/lib/components/Charts/DashedLine/DashedLine.component.tsx
@@ -7,7 +7,7 @@ import { DashedLineProps, ScaleType } from './DashedLine.interfaces';
import { spacedLogValues } from './helpers';
export const getMinTick = (data: readonly Serie[]) => {
- return Math.max(
+ return Math.min(
...data.map((lineData) =>
Math.min(...lineData.data.map((point) => point.y as number))
)
@@ -80,11 +80,16 @@ export const Component = ({
let extraLeftAxisOptions = {};
let extraYScaleOptions = {};
if (yScaleType === ScaleType.log) {
- const ticks = spacedLogValues(getMinTick(data), getMaxTick(data), 6);
+ const ticks = spacedLogValues(
+ Math.floor(getMinTick(data)),
+ Math.ceil(getMaxTick(data)),
+ 6
+ );
extraLeftAxisOptions = {
tickValues: ticks,
};
extraYScaleOptions = {
+ min: ticks[0],
max: ticks[ticks.length - 1],
};
}
diff --git a/src/ui/lib/components/Charts/DashedLine/helpers.ts b/src/ui/lib/components/Charts/DashedLine/helpers.ts
index c73405ed..0c5a6bd0 100644
--- a/src/ui/lib/components/Charts/DashedLine/helpers.ts
+++ b/src/ui/lib/components/Charts/DashedLine/helpers.ts
@@ -13,6 +13,21 @@ const allowedMultipliers = [
1, 1.2, 1.4, 1.5, 1.6, 1.8, 2, 2.5, 3, 3.5, 4, 5, 6, 7, 7.5, 8, 9, 10,
];
+export function roundDownNice(x: number) {
+ if (x <= 0) {
+ return x;
+ }
+ const exponent = Math.floor(Math.log10(x));
+ const base = Math.pow(10, exponent);
+ const fraction = x / base;
+ for (const m of allowedMultipliers.slice().reverse()) {
+ if (m <= fraction) {
+ return Math.floor(m * base);
+ }
+ }
+ return Math.floor(10 * base);
+}
+
export function roundUpNice(x: number) {
if (x <= 0) {
return x;
@@ -22,10 +37,10 @@ export function roundUpNice(x: number) {
const fraction = x / base;
for (const m of allowedMultipliers) {
if (m >= fraction) {
- return Math.round(m * base);
+ return Math.ceil(m * base);
}
}
- return Math.round(10 * base);
+ return Math.ceil(10 * base);
}
export function roundNearestNice(x: number) {
@@ -51,11 +66,14 @@ export function spacedLogValues(min: number, max: number, steps: number) {
if (steps < 2) {
return [];
}
+ if (steps > max - min) {
+ steps = max - min + 1;
+ }
if (min === 0) {
const nonzeroCount = steps - 1;
- const exponent = Math.floor(Math.log10(max)) - (nonzeroCount - 1);
- const lowerNonZero = roundNearestNice(Math.pow(10, exponent));
+ const exponent = Math.log10(max) / (nonzeroCount - 1);
+ const lowerNonZero = roundDownNice(Math.pow(10, exponent));
const upperTick = roundUpNice(max);
const r = Math.pow(upperTick / lowerNonZero, 1 / (nonzeroCount - 1));
const ticks = [0];
@@ -65,7 +83,7 @@ export function spacedLogValues(min: number, max: number, steps: number) {
}
return ticks;
} else {
- const lowerTick = roundUpNice(min);
+ const lowerTick = roundNearestNice(min);
const upperTick = roundUpNice(max);
const r = Math.pow(upperTick / lowerTick, 1 / (steps - 1));
const ticks = [];
diff --git a/src/ui/lib/components/Charts/MetricLine/MetricLine.component.tsx b/src/ui/lib/components/Charts/MetricLine/MetricLine.component.tsx
index 06bb386e..8b1b4df2 100644
--- a/src/ui/lib/components/Charts/MetricLine/MetricLine.component.tsx
+++ b/src/ui/lib/components/Charts/MetricLine/MetricLine.component.tsx
@@ -59,8 +59,8 @@ export const Component: FC = ({
xScale={{ type: 'linear', min: minX }}
yScale={{
type: yScaleType,
- min: 'auto',
- max: 'auto',
+ min: minY,
+ max: maxY,
...extraYScaleOptions,
}}
axisBottom={null}
diff --git a/src/ui/lib/components/Charts/MiniCombined/components/CustomTick/index.tsx b/src/ui/lib/components/Charts/MiniCombined/components/CustomTick/index.tsx
index c7941c97..8aac9fd4 100644
--- a/src/ui/lib/components/Charts/MiniCombined/components/CustomTick/index.tsx
+++ b/src/ui/lib/components/Charts/MiniCombined/components/CustomTick/index.tsx
@@ -1,5 +1,7 @@
import { useTheme } from '@mui/material';
+import { formatNumber } from '@/lib/utils/helpers';
+
import { CustomTickProps } from './CustomTick.interfaces';
const CustomTick = ({
@@ -45,7 +47,7 @@ const CustomTick = ({
fontSize={theme.typography.axisLabel.fontSize}
fill={theme.palette.surface.onSurface}
>
- {tick}
+ {formatNumber(tick)}
>
);
diff --git a/src/ui/lib/components/MetricsSummary/MetricsSummary.component.tsx b/src/ui/lib/components/MetricsSummary/MetricsSummary.component.tsx
index ae9a428b..7a84e581 100644
--- a/src/ui/lib/components/MetricsSummary/MetricsSummary.component.tsx
+++ b/src/ui/lib/components/MetricsSummary/MetricsSummary.component.tsx
@@ -35,6 +35,7 @@ import {
StyledFormControl,
} from './MetricsSummary.styles';
import { useSummary } from './useSummary';
+import { ScaleType } from '../Charts/DashedLine/DashedLine.interfaces';
const percentileOptions = ['p50', 'p90', 'p95', 'p99'];
@@ -95,7 +96,7 @@ export const Component = () => {
},
];
- if ((data?.benchmarks?.length ?? 0) <= 1) {
+ if ((data?.length ?? 0) <= 1) {
return <>>;
}
@@ -207,6 +208,7 @@ export const Component = () => {
data={[{ id: 'ttft', data: lineDataByRps.ttft || [] }]}
threshold={ttftSLO}
lineColor={LineColor.Primary}
+ yScaleType={ScaleType.linear}
/>
@@ -225,6 +227,7 @@ export const Component = () => {
data={[{ id: 'tpot', data: lineDataByRps.tpot || [] }]}
threshold={tpotSLO}
lineColor={LineColor.Secondary}
+ yScaleType={ScaleType.linear}
/>
@@ -245,6 +248,7 @@ export const Component = () => {
]}
threshold={timePerRequestSLO}
lineColor={LineColor.Tertiary}
+ yScaleType={ScaleType.linear}
/>
@@ -265,6 +269,7 @@ export const Component = () => {
data={[{ id: 'throughput', data: lineDataByRps.throughput || [] }]}
threshold={throughputSLO}
lineColor={LineColor.Quarternary}
+ yScaleType={ScaleType.linear}
/>
diff --git a/src/ui/lib/components/PageHeader/PageHeader.component.tsx b/src/ui/lib/components/PageHeader/PageHeader.component.tsx
index 48af2ffc..a443fb7c 100644
--- a/src/ui/lib/components/PageHeader/PageHeader.component.tsx
+++ b/src/ui/lib/components/PageHeader/PageHeader.component.tsx
@@ -32,11 +32,11 @@ const Component = () => {
variant="metric2"
withTooltip
/>
-
+ />*/}
{/**/}
{/* {
{/* key="Version"*/}
{/* />*/}
{/**/}
-
+ {/*
-
-
+ */}
+ {/*
{
}
/>
-
+ */}
= ({
lines={lines}
width={312}
height={85}
- xLegend="time"
+ xLegend="time (sec)"
margins={{ bottom: 30 }}
containerSize={containerSize}
/>
diff --git a/src/ui/lib/components/WorkloadMetrics/WorkloadMetrics.component.tsx b/src/ui/lib/components/WorkloadMetrics/WorkloadMetrics.component.tsx
index b717bb11..7be48983 100644
--- a/src/ui/lib/components/WorkloadMetrics/WorkloadMetrics.component.tsx
+++ b/src/ui/lib/components/WorkloadMetrics/WorkloadMetrics.component.tsx
@@ -48,10 +48,8 @@ export const Component = () => {
throughput: throughputAtRPS,
} = useSelector(selectInterpolatedMetrics);
- const minX = Math.floor(
- Math.min(...(data?.benchmarks?.map((bm) => bm.requestsPerSecond) || []))
- );
- if ((data?.benchmarks?.length ?? 0) <= 1) {
+ const minX = Math.floor(Math.min(...(data?.map((bm) => bm.requestsPerSecond) || [])));
+ if ((data?.length ?? 0) <= 1) {
return <>>;
}
return (
diff --git a/src/ui/lib/store/benchmarksWindowData.ts b/src/ui/lib/store/benchmarksWindowData.ts
index e8a5cc40..eb198179 100644
--- a/src/ui/lib/store/benchmarksWindowData.ts
+++ b/src/ui/lib/store/benchmarksWindowData.ts
@@ -1,1154 +1,1752 @@
-export const benchmarksScript = `window.benchmarks = {
- "benchmarks": [
- {
- "requestsPerSecond": 0.6668550387660497,
- "tpot": {
- "statistics": {
- "total": 80,
- "mean": 23.00635663936911,
- "median": 22.959455611213805,
- "min": 22.880917503720237,
- "max": 24.14080301920573,
- "std": 0.18918760384209338
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 22.959455611213805
- },
- {
- "percentile": "p90",
- "value": 23.01789086962503
- },
- {
- "percentile": "p95",
- "value": 23.30297423947242
- },
- {
- "percentile": "p99",
- "value": 24.14080301920573
- }
- ]
- },
- "ttft": {
- "statistics": {
- "total": 80,
- "mean": 49.64659512042999,
- "median": 49.23129081726074,
- "min": 44.538259506225586,
- "max": 55.47308921813965,
- "std": 1.7735485090634995
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 49.23129081726074
- },
- {
- "percentile": "p90",
- "value": 50.16160011291504
- },
- {
- "percentile": "p95",
- "value": 54.918766021728516
- },
- {
- "percentile": "p99",
- "value": 55.47308921813965
- }
- ]
- },
- "throughput": {
- "statistics": {
- "total": 210,
- "mean": 42.58702991319684,
- "median": 43.536023084668,
- "min": 0.0,
- "max": 43.68247620237872,
- "std": 4.559764488536857
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 43.536023084668
- },
- {
- "percentile": "p90",
- "value": 43.62613633999709
- },
- {
- "percentile": "p95",
- "value": 43.64020767654067
- },
- {
- "percentile": "p99",
- "value": 43.68202126662431
- }
- ]
- },
- "timePerRequest": {
- "statistics": {
- "total": 80,
- "mean": 1496.706646680832,
- "median": 1496.1087703704834,
- "min": 1490.584135055542,
- "max": 1505.8784484863281,
- "std": 3.4553340533022667
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 1496.1087703704834
- },
- {
- "percentile": "p90",
- "value": 1500.9305477142334
- },
- {
- "percentile": "p95",
- "value": 1505.3200721740723
- },
- {
- "percentile": "p99",
- "value": 1505.8784484863281
- }
- ]
- }
- },
- {
- "requestsPerSecond": 28.075330129628725,
- "tpot": {
- "statistics": {
- "total": 3416,
- "mean": 126.08707076148656,
- "median": 125.30853256346687,
- "min": 23.034303907364134,
- "max": 138.08223756693178,
- "std": 3.508992115582193
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 125.30853256346687
- },
- {
- "percentile": "p90",
- "value": 129.21135009281218
- },
- {
- "percentile": "p95",
- "value": 129.52291770059554
- },
- {
- "percentile": "p99",
- "value": 132.21229490686636
- }
- ]
- },
- "ttft": {
- "statistics": {
- "total": 3416,
- "mean": 8585.486161415694,
- "median": 8965.316534042358,
- "min": 110.53991317749023,
- "max": 12575.379610061646,
- "std": 1929.5632525234505
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 8965.316534042358
- },
- {
- "percentile": "p90",
- "value": 9231.79316520691
- },
- {
- "percentile": "p95",
- "value": 9485.00108718872
- },
- {
- "percentile": "p99",
- "value": 12096.465587615967
- }
- ]
- },
- "throughput": {
- "statistics": {
- "total": 15981,
- "mean": 1795.4403743554367,
- "median": 670.1236619268253,
- "min": 0.0,
- "max": 838860.8,
- "std": 5196.545581836957
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 670.1236619268253
- },
- {
- "percentile": "p90",
- "value": 4068.1901066925316
- },
- {
- "percentile": "p95",
- "value": 6374.322188449848
- },
- {
- "percentile": "p99",
- "value": 16194.223938223939
- }
- ]
- },
- "timePerRequest": {
- "statistics": {
- "total": 3416,
- "mean": 16526.811318389147,
- "median": 17058.441638946533,
- "min": 1711.3444805145264,
- "max": 20646.55351638794,
- "std": 2054.9553770234484
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 17058.441638946533
- },
- {
- "percentile": "p90",
- "value": 17143.84412765503
- },
- {
- "percentile": "p95",
- "value": 17248.060703277588
- },
- {
- "percentile": "p99",
- "value": 20116.52660369873
- }
- ]
- }
- },
- {
- "requestsPerSecond": 4.071681142252993,
- "tpot": {
- "statistics": {
- "total": 488,
- "mean": 24.898151556004148,
- "median": 24.889995181371294,
- "min": 24.822999560643755,
- "max": 26.217273871103924,
- "std": 0.11227504505081555
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 24.889995181371294
- },
- {
- "percentile": "p90",
- "value": 24.90483389960395
- },
- {
- "percentile": "p95",
- "value": 24.965975019666885
- },
- {
- "percentile": "p99",
- "value": 25.306613214554325
- }
- ]
- },
- "ttft": {
- "statistics": {
- "total": 488,
- "mean": 58.341102033364976,
- "median": 58.38632583618164,
- "min": 44.857025146484375,
- "max": 111.23061180114746,
- "std": 8.190008649880411
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 58.38632583618164
- },
- {
- "percentile": "p90",
- "value": 67.66843795776367
- },
- {
- "percentile": "p95",
- "value": 68.76754760742188
- },
- {
- "percentile": "p99",
- "value": 71.46525382995605
- }
- ]
- },
- "throughput": {
- "statistics": {
- "total": 11338,
- "mean": 260.42072092623033,
- "median": 47.630070406540995,
- "min": 0.0,
- "max": 838860.8,
- "std": 886.8274389295076
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 47.630070406540995
- },
- {
- "percentile": "p90",
- "value": 604.8895298528987
- },
- {
- "percentile": "p95",
- "value": 1621.9273008507348
- },
- {
- "percentile": "p99",
- "value": 3054.846321922797
- }
- ]
- },
- "timePerRequest": {
- "statistics": {
- "total": 488,
- "mean": 1626.5668087318297,
- "median": 1626.236915588379,
- "min": 1611.9341850280762,
- "max": 1690.2406215667725,
- "std": 8.871477705542668
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 1626.236915588379
- },
- {
- "percentile": "p90",
- "value": 1635.761022567749
- },
- {
- "percentile": "p95",
- "value": 1637.390375137329
- },
- {
- "percentile": "p99",
- "value": 1643.500804901123
- }
- ]
- }
- },
- {
- "requestsPerSecond": 7.466101414346809,
- "tpot": {
- "statistics": {
- "total": 895,
- "mean": 27.56459906601014,
- "median": 27.525402250744047,
- "min": 26.69054911686824,
- "max": 29.5785041082473,
- "std": 0.18545649185329754
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 27.525402250744047
- },
- {
- "percentile": "p90",
- "value": 27.62497795952691
- },
- {
- "percentile": "p95",
- "value": 27.947206345815506
- },
- {
- "percentile": "p99",
- "value": 28.41202157442687
- }
- ]
- },
- "ttft": {
- "statistics": {
- "total": 895,
- "mean": 64.73036744741088,
- "median": 62.484025955200195,
- "min": 48.038482666015625,
- "max": 256.4809322357178,
- "std": 21.677914089867077
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 62.484025955200195
- },
- {
- "percentile": "p90",
- "value": 72.04723358154297
- },
- {
- "percentile": "p95",
- "value": 72.50738143920898
- },
- {
- "percentile": "p99",
- "value": 229.35032844543457
- }
- ]
- },
- "throughput": {
- "statistics": {
- "total": 12465,
- "mean": 477.5134940335642,
- "median": 49.76925541382379,
- "min": 0.0,
- "max": 1677721.6,
- "std": 2472.852317203968
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 49.76925541382379
- },
- {
- "percentile": "p90",
- "value": 1191.5636363636363
- },
- {
- "percentile": "p95",
- "value": 2501.075730471079
- },
- {
- "percentile": "p99",
- "value": 7025.634840871022
- }
- ]
- },
- "timePerRequest": {
- "statistics": {
- "total": 895,
- "mean": 1800.9132816804852,
- "median": 1797.5835800170898,
- "min": 1756.2305927276611,
- "max": 1994.28129196167,
- "std": 24.24935353039552
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 1797.5835800170898
- },
- {
- "percentile": "p90",
- "value": 1808.2549571990967
- },
- {
- "percentile": "p95",
- "value": 1813.141107559204
- },
- {
- "percentile": "p99",
- "value": 1967.8056240081787
- }
- ]
- }
- },
- {
- "requestsPerSecond": 10.83989165148388,
- "tpot": {
- "statistics": {
- "total": 1300,
- "mean": 31.6048062981453,
- "median": 31.577579558841766,
- "min": 30.171105355927438,
- "max": 33.10690323511759,
- "std": 0.15146862300990216
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 31.577579558841766
- },
- {
- "percentile": "p90",
- "value": 31.63230986822219
- },
- {
- "percentile": "p95",
- "value": 31.682415614052424
- },
- {
- "percentile": "p99",
- "value": 32.138043834317116
- }
- ]
- },
- "ttft": {
- "statistics": {
- "total": 1300,
- "mean": 66.61205951984113,
- "median": 65.78803062438965,
- "min": 51.81550979614258,
- "max": 244.69709396362305,
- "std": 14.858653160342651
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 65.78803062438965
- },
- {
- "percentile": "p90",
- "value": 76.70044898986816
+export const benchmarksScript = `window.benchmarks = [
+ {
+ requestsPerSecond: 11.411616848282272,
+ tpot: {
+ mean: 8.758024845683707,
+ median: 8.788176945277623,
+ mode: 7.119315011160714,
+ variance: 0.3289263782653722,
+ stdDev: 0.5735210355909992,
+ min: 7.119315011160714,
+ max: 10.9208311353411,
+ count: 141,
+ totalSum: 1234.8815032414027,
+ percentiles: {
+ p001: 7.119315011160714,
+ p01: 7.164444242204938,
+ p05: 7.513999938964844,
+ p10: 8.169140134538923,
+ p25: 8.586951664515905,
+ p50: 8.788176945277623,
+ p75: 9.003571101597377,
+ p90: 9.308576583862305,
+ p95: 9.504761014665876,
+ p99: 10.393142700195312,
+ p999: 10.9208311353411,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 8.788176945277623,
+ },
+ {
+ percentile: 'p90',
+ value: 9.308576583862305,
+ },
+ {
+ percentile: 'p95',
+ value: 9.504761014665876,
+ },
+ {
+ percentile: 'p99',
+ value: 10.393142700195312,
+ },
+ ],
+ },
+ ttft: {
+ mean: 24.680864726398006,
+ median: 23.874998092651367,
+ mode: 22.01700210571289,
+ variance: 6.960879030702799,
+ stdDev: 2.6383477842587015,
+ min: 22.01700210571289,
+ max: 38.763999938964844,
+ count: 141,
+ totalSum: 3480.001926422119,
+ percentiles: {
+ p001: 22.01700210571289,
+ p01: 22.218942642211914,
+ p05: 22.49598503112793,
+ p10: 22.723674774169922,
+ p25: 23.18596839904785,
+ p50: 23.874998092651367,
+ p75: 24.981975555419922,
+ p90: 27.83489227294922,
+ p95: 30.942916870117188,
+ p99: 34.74783897399902,
+ p999: 38.763999938964844,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 23.874998092651367,
+ },
+ {
+ percentile: 'p90',
+ value: 27.83489227294922,
+ },
+ {
+ percentile: 'p95',
+ value: 30.942916870117188,
+ },
+ {
+ percentile: 'p99',
+ value: 34.74783897399902,
+ },
+ ],
+ },
+ throughput: {
+ mean: 91.21200133343346,
+ median: 111.06031880527459,
+ mode: 112.25822337606724,
+ variance: 1131.7836977561178,
+ stdDev: 33.64199307050814,
+ min: 0.0,
+ max: 132.49633560778366,
+ count: 388,
+ totalSum: 33659.178494722335,
+ percentiles: {
+ p001: 0.0,
+ p01: 29.332438178359627,
+ p05: 37.242645687749174,
+ p10: 39.52416132679985,
+ p25: 42.258712582994974,
+ p50: 111.06031880527459,
+ p75: 114.19908516663037,
+ p90: 116.31136130445634,
+ p95: 117.65228611500702,
+ p99: 122.08714888662495,
+ p999: 132.49633560778366,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 111.06031880527459,
+ },
+ {
+ percentile: 'p90',
+ value: 116.31136130445634,
+ },
+ {
+ percentile: 'p95',
+ value: 117.65228611500702,
+ },
+ {
+ percentile: 'p99',
+ value: 122.08714888662495,
+ },
+ ],
+ },
+ timePerRequest: {
+ mean: 0.0869351363351159,
+ median: 0.08605790138244629,
+ mode: 0.08000683784484863,
+ variance: 1.7069091574769884e-5,
+ stdDev: 0.004131475713927153,
+ min: 0.08000683784484863,
+ max: 0.10561299324035645,
+ count: 141,
+ totalSum: 12.257854223251343,
+ percentiles: {
+ p001: 0.08000683784484863,
+ p01: 0.08143115043640137,
+ p05: 0.08298420906066895,
+ p10: 0.08342385292053223,
+ p25: 0.08434486389160156,
+ p50: 0.08605790138244629,
+ p75: 0.08783197402954102,
+ p90: 0.09138989448547363,
+ p95: 0.09475302696228027,
+ p99: 0.10389590263366699,
+ p999: 0.10561299324035645,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 0.08605790138244629,
+ },
+ {
+ percentile: 'p90',
+ value: 0.09138989448547363,
+ },
+ {
+ percentile: 'p95',
+ value: 0.09475302696228027,
+ },
+ {
+ percentile: 'p99',
+ value: 0.10389590263366699,
+ },
+ ],
},
- {
- "percentile": "p95",
- "value": 77.78120040893555
- },
- {
- "percentile": "p99",
- "value": 88.29903602600098
- }
- ]
- },
- "throughput": {
- "statistics": {
- "total": 12708,
- "mean": 693.3695002980695,
- "median": 55.59272071785492,
- "min": 0.0,
- "max": 838860.8,
- "std": 2454.288991845712
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 55.59272071785492
- },
- {
- "percentile": "p90",
- "value": 1897.875113122172
- },
- {
- "percentile": "p95",
- "value": 2931.030048916841
- },
- {
- "percentile": "p99",
- "value": 7108.989830508474
- }
- ]
- },
- "timePerRequest": {
- "statistics": {
- "total": 1300,
- "mean": 2057.3723330864545,
- "median": 2056.5311908721924,
- "min": 2027.0307064056396,
- "max": 2233.853578567505,
- "std": 16.334707021033957
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 2056.5311908721924
- },
- {
- "percentile": "p90",
- "value": 2065.953254699707
- },
- {
- "percentile": "p95",
- "value": 2067.810297012329
- },
- {
- "percentile": "p99",
- "value": 2087.8031253814697
- }
- ]
- }
- },
- {
- "requestsPerSecond": 14.211845819540324,
- "tpot": {
- "statistics": {
- "total": 1704,
- "mean": 35.695500394825224,
- "median": 35.60370869106717,
- "min": 34.798149078611345,
- "max": 38.94662857055664,
- "std": 0.24967658675392423
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 35.60370869106717
- },
- {
- "percentile": "p90",
- "value": 35.84100708128914
- },
- {
- "percentile": "p95",
- "value": 36.09923778041716
- },
- {
- "percentile": "p99",
- "value": 36.71476489207784
- }
- ]
- },
- "ttft": {
- "statistics": {
- "total": 1704,
- "mean": 74.19940031750102,
- "median": 71.50626182556152,
- "min": 53.643226623535156,
- "max": 322.6609230041504,
- "std": 23.98415146629138
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 71.50626182556152
- },
- {
- "percentile": "p90",
- "value": 83.71734619140625
- },
- {
- "percentile": "p95",
- "value": 98.2356071472168
- },
- {
- "percentile": "p99",
- "value": 113.44718933105469
- }
- ]
- },
- "throughput": {
- "statistics": {
- "total": 15532,
- "mean": 908.715763654939,
- "median": 98.84067397195712,
- "min": 0.0,
- "max": 838860.8,
- "std": 3628.67537220603
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 98.84067397195712
- },
- {
- "percentile": "p90",
- "value": 2205.2071503680336
- },
- {
- "percentile": "p95",
- "value": 3775.251125112511
- },
- {
- "percentile": "p99",
- "value": 10512.040100250626
- }
- ]
- },
- "timePerRequest": {
- "statistics": {
- "total": 1704,
- "mean": 2321.92987861208,
- "median": 2313.3785724639893,
- "min": 2290.93074798584,
- "max": 2594.4881439208984,
- "std": 29.46118583560937
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 2313.3785724639893
- },
- {
- "percentile": "p90",
- "value": 2339.4439220428467
- },
- {
- "percentile": "p95",
- "value": 2341.9249057769775
- },
- {
- "percentile": "p99",
- "value": 2370.450496673584
- }
- ]
- }
- },
- {
- "requestsPerSecond": 17.5623040970073,
- "tpot": {
- "statistics": {
- "total": 2106,
- "mean": 39.546438065771135,
- "median": 39.47442675393725,
- "min": 38.74176740646362,
- "max": 43.32651032341851,
- "std": 0.3121106751660994
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 39.47442675393725
- },
- {
- "percentile": "p90",
- "value": 39.722594003828746
- },
- {
- "percentile": "p95",
- "value": 40.083578654697966
- },
- {
- "percentile": "p99",
- "value": 40.73049983040231
- }
- ]
- },
- "ttft": {
- "statistics": {
- "total": 2106,
- "mean": 85.68002797259905,
- "median": 89.88213539123535,
- "min": 57.360172271728516,
- "max": 362.8504276275635,
- "std": 27.802786177158218
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 89.88213539123535
- },
- {
- "percentile": "p90",
- "value": 101.7305850982666
- },
- {
- "percentile": "p95",
- "value": 103.26790809631348
- },
- {
- "percentile": "p99",
- "value": 138.88931274414062
- }
- ]
- },
- "throughput": {
- "statistics": {
- "total": 15121,
- "mean": 1123.0284569989917,
- "median": 99.91909855397003,
- "min": 0.0,
- "max": 932067.5555555555,
- "std": 4358.833642800455
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 99.91909855397003
- },
- {
- "percentile": "p90",
- "value": 2868.8809849521203
- },
- {
- "percentile": "p95",
- "value": 4848.906358381503
- },
- {
- "percentile": "p99",
- "value": 12905.55076923077
- }
- ]
- },
- "timePerRequest": {
- "statistics": {
- "total": 2106,
- "mean": 2575.916517267653,
- "median": 2573.6281871795654,
- "min": 2533.904790878296,
- "max": 2894.4458961486816,
- "std": 33.18594265783404
},
- "percentiles": [
- {
- "percentile": "p50",
- "value": 2573.6281871795654
- },
- {
- "percentile": "p90",
- "value": 2588.9015197753906
+ {
+ requestsPerSecond: 36.289181300710815,
+ tpot: {
+ mean: 588.0161376137819,
+ median: 461.7137227739607,
+ mode: 323.1611592429025,
+ variance: 19560.214456505688,
+ stdDev: 139.85783659311224,
+ min: 323.1611592429025,
+ max: 988.006728036063,
+ count: 256,
+ totalSum: 150532.13122912816,
+ percentiles: {
+ p001: 323.1611592429025,
+ p01: 388.2312774658203,
+ p05: 394.99473571777344,
+ p10: 460.00800813947404,
+ p25: 460.8048711504255,
+ p50: 461.7137227739607,
+ p75: 726.0744231087821,
+ p90: 726.3181550162179,
+ p95: 726.4585835593087,
+ p99: 726.9112723214286,
+ p999: 988.006728036063,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 461.7137227739607,
+ },
+ {
+ percentile: 'p90',
+ value: 726.3181550162179,
+ },
+ {
+ percentile: 'p95',
+ value: 726.4585835593087,
+ },
+ {
+ percentile: 'p99',
+ value: 726.9112723214286,
+ },
+ ],
+ },
+ ttft: {
+ mean: 2801.9311213865876,
+ median: 3645.9569931030273,
+ mode: 120.23282051086426,
+ variance: 876438.9633642528,
+ stdDev: 936.1831889989548,
+ min: 120.23282051086426,
+ max: 4289.892911911011,
+ count: 256,
+ totalSum: 717294.3670749664,
+ percentiles: {
+ p001: 120.23282051086426,
+ p01: 1860.9709739685059,
+ p05: 1867.4709796905518,
+ p10: 1874.9330043792725,
+ p25: 1898.1659412384033,
+ p50: 3645.9569931030273,
+ p75: 3683.4659576416016,
+ p90: 3707.062005996704,
+ p95: 3714.3311500549316,
+ p99: 4193.973064422607,
+ p999: 4289.892911911011,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 3645.9569931030273,
+ },
+ {
+ percentile: 'p90',
+ value: 3707.062005996704,
+ },
+ {
+ percentile: 'p95',
+ value: 3714.3311500549316,
+ },
+ {
+ percentile: 'p99',
+ value: 4193.973064422607,
+ },
+ ],
+ },
+ throughput: {
+ mean: 290.17169579123066,
+ median: 2.574805445619618,
+ mode: 1.0106780980195134,
+ variance: 32683153.434954323,
+ stdDev: 5716.91817633892,
+ min: 0.0,
+ max: 1677721.6,
+ count: 541,
+ totalSum: 26954108.473821327,
+ percentiles: {
+ p001: 0.0,
+ p01: 0.0,
+ p05: 1.0106780980195134,
+ p10: 1.0106780980195134,
+ p25: 1.1875075558681243,
+ p50: 2.574805445619618,
+ p75: 4.724922214536926,
+ p90: 7.90451942158339,
+ p95: 16.513072886113726,
+ p99: 1795.5068493150684,
+ p999: 62601.55223880597,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 2.574805445619618,
+ },
+ {
+ percentile: 'p90',
+ value: 7.90451942158339,
+ },
+ {
+ percentile: 'p95',
+ value: 16.513072886113726,
+ },
+ {
+ percentile: 'p99',
+ value: 1795.5068493150684,
+ },
+ ],
+ },
+ timePerRequest: {
+ mean: 6.962754532694817,
+ median: 6.958127975463867,
+ mode: 6.888041973114014,
+ variance: 0.002266230397153679,
+ stdDev: 0.04760494089013954,
+ min: 6.888041973114014,
+ max: 7.051568031311035,
+ count: 256,
+ totalSum: 1782.465160369873,
+ percentiles: {
+ p001: 6.888041973114014,
+ p01: 6.888926029205322,
+ p05: 6.892949104309082,
+ p10: 6.902682065963745,
+ p25: 6.92248797416687,
+ p50: 6.958127975463867,
+ p75: 6.9972498416900635,
+ p90: 7.035952091217041,
+ p95: 7.041622877120972,
+ p99: 7.051159858703613,
+ p999: 7.051568031311035,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 6.958127975463867,
+ },
+ {
+ percentile: 'p90',
+ value: 7.035952091217041,
+ },
+ {
+ percentile: 'p95',
+ value: 7.041622877120972,
+ },
+ {
+ percentile: 'p99',
+ value: 7.051159858703613,
+ },
+ ],
},
- {
- "percentile": "p95",
- "value": 2591.136932373047
- },
- {
- "percentile": "p99",
- "value": 2700.568437576294
- }
- ]
- }
- },
- {
- "requestsPerSecond": 20.885632360055222,
- "tpot": {
- "statistics": {
- "total": 2505,
- "mean": 44.20494748431818,
- "median": 44.02147020612444,
- "min": 42.981475591659546,
- "max": 52.62617986710345,
- "std": 1.0422073399474652
},
- "percentiles": [
- {
- "percentile": "p50",
- "value": 44.02147020612444
- },
- {
- "percentile": "p90",
- "value": 44.47330747331892
+ {
+ requestsPerSecond: 20.752070927855794,
+ tpot: {
+ mean: 116.28360712595156,
+ median: 26.769569941929408,
+ mode: 10.624987738473076,
+ variance: 12697.514443947253,
+ stdDev: 112.68324828450436,
+ min: 10.624987738473076,
+ max: 378.4891196659633,
+ count: 229,
+ totalSum: 26628.946031842912,
+ percentiles: {
+ p001: 10.624987738473076,
+ p01: 11.029584067208427,
+ p05: 12.239864894321986,
+ p10: 12.511866433279854,
+ p25: 13.14084870474679,
+ p50: 26.769569941929408,
+ p75: 254.47828429085868,
+ p90: 254.67797688075476,
+ p95: 254.72869191850936,
+ p99: 340.54568835667203,
+ p999: 378.4891196659633,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 26.769569941929408,
+ },
+ {
+ percentile: 'p90',
+ value: 254.67797688075476,
+ },
+ {
+ percentile: 'p95',
+ value: 254.72869191850936,
+ },
+ {
+ percentile: 'p99',
+ value: 340.54568835667203,
+ },
+ ],
+ },
+ ttft: {
+ mean: 350.13929725213853,
+ median: 48.3551025390625,
+ mode: 23.19192886352539,
+ variance: 171533.22641308085,
+ stdDev: 414.1656992232467,
+ min: 23.19192886352539,
+ max: 974.7560024261475,
+ count: 229,
+ totalSum: 80181.89907073975,
+ percentiles: {
+ p001: 23.19192886352539,
+ p01: 23.784875869750977,
+ p05: 25.762081146240234,
+ p10: 27.454376220703125,
+ p25: 30.905961990356445,
+ p50: 48.3551025390625,
+ p75: 924.9362945556641,
+ p90: 960.2079391479492,
+ p95: 966.1390781402588,
+ p99: 974.3149280548096,
+ p999: 974.7560024261475,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 48.3551025390625,
+ },
+ {
+ percentile: 'p90',
+ value: 960.2079391479492,
+ },
+ {
+ percentile: 'p95',
+ value: 966.1390781402588,
+ },
+ {
+ percentile: 'p99',
+ value: 974.3149280548096,
+ },
+ ],
+ },
+ throughput: {
+ mean: 165.92594702578148,
+ median: 78.71010358804985,
+ mode: 2.640067325020095,
+ variance: 6402491.209634123,
+ stdDev: 2530.314448766027,
+ min: 0.0,
+ max: 1048576.0,
+ count: 1287,
+ totalSum: 19225083.512811106,
+ percentiles: {
+ p001: 0.0,
+ p01: 2.640067325020095,
+ p05: 2.6408868303973385,
+ p10: 8.261512885765386,
+ p25: 63.60692133877254,
+ p50: 78.71010358804985,
+ p75: 113.60827758064953,
+ p90: 186.8536552768744,
+ p95: 244.65142323845077,
+ p99: 554.5087255420412,
+ p999: 19878.218009478675,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 78.71010358804985,
+ },
+ {
+ percentile: 'p90',
+ value: 186.8536552768744,
+ },
+ {
+ percentile: 'p95',
+ value: 244.65142323845077,
+ },
+ {
+ percentile: 'p99',
+ value: 554.5087255420412,
+ },
+ ],
+ },
+ timePerRequest: {
+ mean: 1.1796869305023459,
+ median: 0.22142529487609863,
+ mode: 0.10968017578125,
+ variance: 1.3985689524884573,
+ stdDev: 1.1826110740596238,
+ min: 0.10968017578125,
+ max: 2.7613768577575684,
+ count: 229,
+ totalSum: 270.14830708503723,
+ percentiles: {
+ p001: 0.10968017578125,
+ p01: 0.11065411567687988,
+ p05: 0.11456704139709473,
+ p10: 0.11728477478027344,
+ p25: 0.12422728538513184,
+ p50: 0.22142529487609863,
+ p75: 2.7111761569976807,
+ p90: 2.748539924621582,
+ p95: 2.7520828247070312,
+ p99: 2.7607710361480713,
+ p999: 2.7613768577575684,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 0.22142529487609863,
+ },
+ {
+ percentile: 'p90',
+ value: 2.748539924621582,
+ },
+ {
+ percentile: 'p95',
+ value: 2.7520828247070312,
+ },
+ {
+ percentile: 'p99',
+ value: 2.7607710361480713,
+ },
+ ],
},
- {
- "percentile": "p95",
- "value": 45.131300316482296
- },
- {
- "percentile": "p99",
- "value": 50.400745301019576
- }
- ]
- },
- "ttft": {
- "statistics": {
- "total": 2505,
- "mean": 98.4621736103903,
- "median": 95.84355354309082,
- "min": 61.09285354614258,
- "max": 524.099588394165,
- "std": 34.20521833421915
},
- "percentiles": [
- {
- "percentile": "p50",
- "value": 95.84355354309082
- },
- {
- "percentile": "p90",
- "value": 109.4822883605957
+ {
+ requestsPerSecond: 26.81917480361788,
+ tpot: {
+ mean: 299.7306064613554,
+ median: 372.7384294782366,
+ mode: 13.360295976911273,
+ variance: 43881.168589896006,
+ stdDev: 209.47832486893722,
+ min: 13.360295976911273,
+ max: 646.5137345450265,
+ count: 278,
+ totalSum: 83325.1085962568,
+ percentiles: {
+ p001: 13.360295976911273,
+ p01: 14.045170375279017,
+ p05: 17.420836857387,
+ p10: 18.262999398367747,
+ p25: 35.025290080479216,
+ p50: 372.7384294782366,
+ p75: 484.5614092690604,
+ p90: 484.67516899108887,
+ p95: 646.2434019361224,
+ p99: 646.4788573128836,
+ p999: 646.5137345450265,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 372.7384294782366,
+ },
+ {
+ percentile: 'p90',
+ value: 484.67516899108887,
+ },
+ {
+ percentile: 'p95',
+ value: 646.2434019361224,
+ },
+ {
+ percentile: 'p99',
+ value: 646.4788573128836,
+ },
+ ],
+ },
+ ttft: {
+ mean: 603.4488720859555,
+ median: 323.30799102783203,
+ mode: 23.794889450073242,
+ variance: 385351.6075362678,
+ stdDev: 620.7669510663948,
+ min: 23.794889450073242,
+ max: 2183.549165725708,
+ count: 278,
+ totalSum: 167758.78643989563,
+ percentiles: {
+ p001: 23.794889450073242,
+ p01: 25.097131729125977,
+ p05: 28.53107452392578,
+ p10: 31.769990921020508,
+ p25: 42.52219200134277,
+ p50: 323.30799102783203,
+ p75: 1421.4229583740234,
+ p90: 1454.333782196045,
+ p95: 1465.749979019165,
+ p99: 1481.3649654388428,
+ p999: 2183.549165725708,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 323.30799102783203,
+ },
+ {
+ percentile: 'p90',
+ value: 1454.333782196045,
+ },
+ {
+ percentile: 'p95',
+ value: 1465.749979019165,
+ },
+ {
+ percentile: 'p99',
+ value: 1481.3649654388428,
+ },
+ ],
+ },
+ throughput: {
+ mean: 214.45692657713144,
+ median: 59.739410340407346,
+ mode: 1.5484621717509202,
+ variance: 10330273.607661681,
+ stdDev: 3214.0743002708696,
+ min: 0.0,
+ max: 1677721.6,
+ count: 1408,
+ totalSum: 23048554.08119256,
+ percentiles: {
+ p001: 0.0,
+ p01: 0.0,
+ p05: 1.5484621717509202,
+ p10: 1.5484621717509202,
+ p25: 11.441965016422422,
+ p50: 59.739410340407346,
+ p75: 104.92580177115124,
+ p90: 204.92007035372288,
+ p95: 328.06445052796244,
+ p99: 1077.6731757451182,
+ p999: 27776.847682119205,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 59.739410340407346,
+ },
+ {
+ percentile: 'p90',
+ value: 204.92007035372288,
+ },
+ {
+ percentile: 'p95',
+ value: 328.06445052796244,
+ },
+ {
+ percentile: 'p99',
+ value: 1077.6731757451182,
+ },
+ ],
+ },
+ timePerRequest: {
+ mean: 2.727286984594606,
+ median: 2.9081506729125977,
+ mode: 0.12541699409484863,
+ variance: 3.887494791582503,
+ stdDev: 1.9716730945018504,
+ min: 0.12541699409484863,
+ max: 4.927099943161011,
+ count: 278,
+ totalSum: 758.1857817173004,
+ percentiles: {
+ p001: 0.12541699409484863,
+ p01: 0.12821292877197266,
+ p05: 0.15644598007202148,
+ p10: 0.1615278720855713,
+ p25: 0.2925078868865967,
+ p50: 2.9081506729125977,
+ p75: 4.833041191101074,
+ p90: 4.866931915283203,
+ p95: 4.889725923538208,
+ p99: 4.91511082649231,
+ p999: 4.927099943161011,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 2.9081506729125977,
+ },
+ {
+ percentile: 'p90',
+ value: 4.866931915283203,
+ },
+ {
+ percentile: 'p95',
+ value: 4.889725923538208,
+ },
+ {
+ percentile: 'p99',
+ value: 4.91511082649231,
+ },
+ ],
},
- {
- "percentile": "p95",
- "value": 111.46354675292969
- },
- {
- "percentile": "p99",
- "value": 334.31243896484375
- }
- ]
- },
- "throughput": {
- "statistics": {
- "total": 14779,
- "mean": 1335.7133120200747,
- "median": 104.45284522475407,
- "min": 0.0,
- "max": 1677721.6,
- "std": 5200.1934248077005
},
- "percentiles": [
- {
- "percentile": "p50",
- "value": 104.45284522475407
- },
- {
- "percentile": "p90",
- "value": 3472.1059602649007
+ {
+ requestsPerSecond: 26.823988819498975,
+ tpot: {
+ mean: 683.8011571339198,
+ median: 742.2689029148647,
+ mode: 317.1694278717041,
+ variance: 28604.497606927893,
+ stdDev: 169.12864218377646,
+ min: 317.1694278717041,
+ max: 1093.381404876709,
+ count: 282,
+ totalSum: 192831.9263117654,
+ percentiles: {
+ p001: 317.1694278717041,
+ p01: 321.53899329049244,
+ p05: 339.3098626817976,
+ p10: 382.89002009800504,
+ p25: 576.0242598397391,
+ p50: 742.2689029148647,
+ p75: 835.7884543282645,
+ p90: 835.8725479670933,
+ p95: 835.9301771436419,
+ p99: 835.9494209289551,
+ p999: 1093.381404876709,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 742.2689029148647,
+ },
+ {
+ percentile: 'p90',
+ value: 835.8725479670933,
+ },
+ {
+ percentile: 'p95',
+ value: 835.9301771436419,
+ },
+ {
+ percentile: 'p99',
+ value: 835.9494209289551,
+ },
+ ],
+ },
+ ttft: {
+ mean: 1175.8291366252492,
+ median: 1099.5359420776367,
+ mode: 31.527996063232422,
+ variance: 656345.7486101,
+ stdDev: 810.1516824706963,
+ min: 31.527996063232422,
+ max: 3620.6698417663574,
+ count: 282,
+ totalSum: 331583.8165283203,
+ percentiles: {
+ p001: 31.527996063232422,
+ p01: 45.86386680603027,
+ p05: 83.20093154907227,
+ p10: 158.12087059020996,
+ p25: 363.6949062347412,
+ p50: 1099.5359420776367,
+ p75: 1955.5552005767822,
+ p90: 2000.9040832519531,
+ p95: 2015.0668621063232,
+ p99: 2435.93692779541,
+ p999: 3620.6698417663574,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 1099.5359420776367,
+ },
+ {
+ percentile: 'p90',
+ value: 2000.9040832519531,
+ },
+ {
+ percentile: 'p95',
+ value: 2015.0668621063232,
+ },
+ {
+ percentile: 'p99',
+ value: 2435.93692779541,
+ },
+ ],
+ },
+ throughput: {
+ mean: 214.4967900282631,
+ median: 7.5584893487808,
+ mode: 0.9142165629474438,
+ variance: 14926330.042152546,
+ stdDev: 3863.4608891708153,
+ min: 0.0,
+ max: 838860.8,
+ count: 888,
+ totalSum: 21536027.789829955,
+ percentiles: {
+ p001: 0.0,
+ p01: 0.0,
+ p05: 0.9142165629474438,
+ p10: 0.9142165629474438,
+ p25: 3.153194765204003,
+ p50: 7.5584893487808,
+ p75: 23.973067975925787,
+ p90: 67.82180683343304,
+ p95: 126.88864014521252,
+ p99: 1646.116169544741,
+ p999: 39568.90566037736,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 7.5584893487808,
+ },
+ {
+ percentile: 'p90',
+ value: 67.82180683343304,
+ },
+ {
+ percentile: 'p95',
+ value: 126.88864014521252,
+ },
+ {
+ percentile: 'p99',
+ value: 1646.116169544741,
+ },
+ ],
+ },
+ timePerRequest: {
+ mean: 5.992494348938584,
+ median: 6.383468866348267,
+ mode: 2.272388219833374,
+ variance: 3.362285611082358,
+ stdDev: 1.8336536235293617,
+ min: 2.272388219833374,
+ max: 7.87853217124939,
+ count: 282,
+ totalSum: 1689.8834064006805,
+ percentiles: {
+ p001: 2.272388219833374,
+ p01: 2.36775279045105,
+ p05: 2.9363210201263428,
+ p10: 3.3975088596343994,
+ p25: 4.363567113876343,
+ p50: 6.383468866348267,
+ p75: 7.805588245391846,
+ p90: 7.848597049713135,
+ p95: 7.86446475982666,
+ p99: 7.874046802520752,
+ p999: 7.87853217124939,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 6.383468866348267,
+ },
+ {
+ percentile: 'p90',
+ value: 7.848597049713135,
+ },
+ {
+ percentile: 'p95',
+ value: 7.86446475982666,
+ },
+ {
+ percentile: 'p99',
+ value: 7.874046802520752,
+ },
+ ],
},
- {
- "percentile": "p95",
- "value": 5882.6143057503505
- },
- {
- "percentile": "p99",
- "value": 15768.060150375939
- }
- ]
- },
- "timePerRequest": {
- "statistics": {
- "total": 2505,
- "mean": 2882.6246785070603,
- "median": 2869.71378326416,
- "min": 2826.8485069274902,
- "max": 3324.9876499176025,
- "std": 78.07038363701177
},
- "percentiles": [
- {
- "percentile": "p50",
- "value": 2869.71378326416
- },
- {
- "percentile": "p90",
- "value": 2888.715982437134
- },
- {
- "percentile": "p95",
- "value": 2937.7262592315674
+ {
+ requestsPerSecond: 24.50047903792646,
+ tpot: {
+ mean: 742.9258901891964,
+ median: 773.0941431862967,
+ mode: 538.750410079956,
+ variance: 5888.534815943889,
+ stdDev: 76.73678919490891,
+ min: 538.750410079956,
+ max: 1112.7384049551827,
+ count: 256,
+ totalSum: 190189.02788843427,
+ percentiles: {
+ p001: 538.750410079956,
+ p01: 559.9275997706821,
+ p05: 622.4285534449986,
+ p10: 651.3757365090506,
+ p25: 691.4628573826382,
+ p50: 773.0941431862967,
+ p75: 803.8818495614188,
+ p90: 804.060867854527,
+ p95: 804.1924408503941,
+ p99: 804.2235374450684,
+ p999: 1112.7384049551827,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 773.0941431862967,
+ },
+ {
+ percentile: 'p90',
+ value: 804.060867854527,
+ },
+ {
+ percentile: 'p95',
+ value: 804.1924408503941,
+ },
+ {
+ percentile: 'p99',
+ value: 804.2235374450684,
+ },
+ ],
+ },
+ ttft: {
+ mean: 1639.041354879737,
+ median: 2199.6800899505615,
+ mode: 40.383100509643555,
+ variance: 769961.5680314268,
+ stdDev: 877.4745398194906,
+ min: 40.383100509643555,
+ max: 3934.627056121826,
+ count: 256,
+ totalSum: 419594.58684921265,
+ percentiles: {
+ p001: 40.383100509643555,
+ p01: 46.3411808013916,
+ p05: 128.04388999938965,
+ p10: 259.2899799346924,
+ p25: 663.4221076965332,
+ p50: 2199.6800899505615,
+ p75: 2240.969181060791,
+ p90: 2276.355028152466,
+ p95: 2569.640874862671,
+ p99: 2960.084915161133,
+ p999: 3934.627056121826,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 2199.6800899505615,
+ },
+ {
+ percentile: 'p90',
+ value: 2276.355028152466,
+ },
+ {
+ percentile: 'p95',
+ value: 2569.640874862671,
+ },
+ {
+ percentile: 'p99',
+ value: 2960.084915161133,
+ },
+ ],
+ },
+ throughput: {
+ mean: 195.90812730716976,
+ median: 5.651687770251424,
+ mode: 0.8974143971723011,
+ variance: 16305893.758862041,
+ stdDev: 4038.0556904111713,
+ min: 0.0,
+ max: 1048576.0,
+ count: 718,
+ totalSum: 25179910.958649173,
+ percentiles: {
+ p001: 0.0,
+ p01: 0.0,
+ p05: 0.8974143971723011,
+ p10: 0.8974143971723011,
+ p25: 1.3957095523970422,
+ p50: 5.651687770251424,
+ p75: 13.66877299553858,
+ p90: 32.61435580818488,
+ p95: 59.516467299533154,
+ p99: 990.1567516525024,
+ p999: 40136.88038277512,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 5.651687770251424,
+ },
+ {
+ percentile: 'p90',
+ value: 32.61435580818488,
+ },
+ {
+ percentile: 'p95',
+ value: 59.516467299533154,
+ },
+ {
+ percentile: 'p99',
+ value: 990.1567516525024,
+ },
+ ],
+ },
+ timePerRequest: {
+ mean: 6.883434834890068,
+ median: 7.825762987136841,
+ mode: 4.416188955307007,
+ variance: 1.3703848800474456,
+ stdDev: 1.1706343921342162,
+ min: 4.416188955307007,
+ max: 7.9228410720825195,
+ count: 256,
+ totalSum: 1762.1593177318573,
+ percentiles: {
+ p001: 4.416188955307007,
+ p01: 4.504012584686279,
+ p05: 4.920926094055176,
+ p10: 5.141816139221191,
+ p25: 5.672410011291504,
+ p50: 7.825762987136841,
+ p75: 7.86903715133667,
+ p90: 7.897527694702148,
+ p95: 7.910752058029175,
+ p99: 7.920928716659546,
+ p999: 7.9228410720825195,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 7.825762987136841,
+ },
+ {
+ percentile: 'p90',
+ value: 7.897527694702148,
+ },
+ {
+ percentile: 'p95',
+ value: 7.910752058029175,
+ },
+ {
+ percentile: 'p99',
+ value: 7.920928716659546,
+ },
+ ],
},
- {
- "percentile": "p99",
- "value": 3282.898426055908
- }
- ]
- }
- },
- {
- "requestsPerSecond": 24.179871480414207,
- "tpot": {
- "statistics": {
- "total": 2900,
- "mean": 51.023722283946924,
- "median": 50.24327550615583,
- "min": 47.58137645143451,
- "max": 60.63385087935651,
- "std": 2.0749227872708285
},
- "percentiles": [
- {
- "percentile": "p50",
- "value": 50.24327550615583
- },
- {
- "percentile": "p90",
- "value": 52.928451507810564
- },
- {
- "percentile": "p95",
- "value": 57.28437408568367
+ {
+ requestsPerSecond: 25.617829792196602,
+ tpot: {
+ mean: 663.3098317044122,
+ median: 613.7458937508719,
+ mode: 440.9824098859514,
+ variance: 10479.9469011006,
+ stdDev: 102.37161179301907,
+ min: 440.9824098859514,
+ max: 1060.6839997427803,
+ count: 256,
+ totalSum: 169807.31691632952,
+ percentiles: {
+ p001: 440.9824098859514,
+ p01: 440.9982817513602,
+ p05: 442.1650000980922,
+ p10: 534.6532889774868,
+ p25: 612.1257373264858,
+ p50: 613.7458937508719,
+ p75: 755.2382605416434,
+ p90: 755.9503146580288,
+ p95: 756.0351576123919,
+ p99: 786.0629899161203,
+ p999: 1060.6839997427803,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 613.7458937508719,
+ },
+ {
+ percentile: 'p90',
+ value: 755.9503146580288,
+ },
+ {
+ percentile: 'p95',
+ value: 756.0351576123919,
+ },
+ {
+ percentile: 'p99',
+ value: 786.0629899161203,
+ },
+ ],
+ },
+ ttft: {
+ mean: 1987.0930286124349,
+ median: 2171.497106552124,
+ mode: 26.77607536315918,
+ variance: 755024.7838922634,
+ stdDev: 868.9216212595146,
+ min: 26.77607536315918,
+ max: 4371.719121932983,
+ count: 256,
+ totalSum: 508695.8153247833,
+ percentiles: {
+ p001: 26.77607536315918,
+ p01: 55.07302284240723,
+ p05: 291.4888858795166,
+ p10: 515.1617527008057,
+ p25: 1566.1020278930664,
+ p50: 2171.497106552124,
+ p75: 2225.597858428955,
+ p90: 3119.4918155670166,
+ p95: 3129.302978515625,
+ p99: 4363.926887512207,
+ p999: 4371.719121932983,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 2171.497106552124,
+ },
+ {
+ percentile: 'p90',
+ value: 3119.4918155670166,
+ },
+ {
+ percentile: 'p95',
+ value: 3129.302978515625,
+ },
+ {
+ percentile: 'p99',
+ value: 4363.926887512207,
+ },
+ ],
+ },
+ throughput: {
+ mean: 204.84256868994706,
+ median: 1.3254838186715343,
+ mode: 0.9418142869653279,
+ variance: 13137897.553754935,
+ stdDev: 3624.623780995061,
+ min: 0.0,
+ max: 1677721.6,
+ count: 808,
+ totalSum: 26909147.73473306,
+ percentiles: {
+ p001: 0.0,
+ p01: 0.0,
+ p05: 0.9418142869653279,
+ p10: 0.9418142869653279,
+ p25: 1.2725896353660826,
+ p50: 1.3254838186715343,
+ p75: 8.018996417141132,
+ p90: 20.67656874682652,
+ p95: 52.4720894738159,
+ p99: 2868.8809849521203,
+ p999: 36157.793103448275,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 1.3254838186715343,
+ },
+ {
+ percentile: 'p90',
+ value: 20.67656874682652,
+ },
+ {
+ percentile: 'p95',
+ value: 52.4720894738159,
+ },
+ {
+ percentile: 'p99',
+ value: 2868.8809849521203,
+ },
+ ],
+ },
+ timePerRequest: {
+ mean: 6.655263062566519,
+ median: 7.432342052459717,
+ mode: 3.603327989578247,
+ variance: 1.427610769055824,
+ stdDev: 1.194826669042763,
+ min: 3.603327989578247,
+ max: 7.537046670913696,
+ count: 256,
+ totalSum: 1703.7473440170288,
+ percentiles: {
+ p001: 3.603327989578247,
+ p01: 3.6770501136779785,
+ p05: 4.052419900894165,
+ p10: 4.532166004180908,
+ p25: 5.912662982940674,
+ p50: 7.432342052459717,
+ p75: 7.480893135070801,
+ p90: 7.51776123046875,
+ p95: 7.526960849761963,
+ p99: 7.536363124847412,
+ p999: 7.537046670913696,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 7.432342052459717,
+ },
+ {
+ percentile: 'p90',
+ value: 7.51776123046875,
+ },
+ {
+ percentile: 'p95',
+ value: 7.526960849761963,
+ },
+ {
+ percentile: 'p99',
+ value: 7.536363124847412,
+ },
+ ],
},
- {
- "percentile": "p99",
- "value": 58.51330454387362
- }
- ]
- },
- "ttft": {
- "statistics": {
- "total": 2900,
- "mean": 123.56691516678907,
- "median": 115.33927917480469,
- "min": 88.05131912231445,
- "max": 594.1901206970215,
- "std": 44.50765227271787
},
- "percentiles": [
- {
- "percentile": "p50",
- "value": 115.33927917480469
+ {
+ requestsPerSecond: 37.02892550982192,
+ tpot: {
+ mean: 606.4144710877113,
+ median: 543.5235500335693,
+ mode: 331.6155501774379,
+ variance: 9907.596850846778,
+ stdDev: 99.53691200176334,
+ min: 331.6155501774379,
+ max: 970.1211452484131,
+ count: 256,
+ totalSum: 155242.10459845408,
+ percentiles: {
+ p001: 331.6155501774379,
+ p01: 401.9838741847447,
+ p05: 471.85257502964566,
+ p10: 482.9780033656529,
+ p25: 542.1572753361294,
+ p50: 543.5235500335693,
+ p75: 707.3319980076382,
+ p90: 708.0604348863874,
+ p95: 708.2712990897043,
+ p99: 708.6352961403983,
+ p999: 970.1211452484131,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 543.5235500335693,
+ },
+ {
+ percentile: 'p90',
+ value: 708.0604348863874,
+ },
+ {
+ percentile: 'p95',
+ value: 708.2712990897043,
+ },
+ {
+ percentile: 'p99',
+ value: 708.6352961403983,
+ },
+ ],
+ },
+ ttft: {
+ mean: 1941.031264141202,
+ median: 1882.4608325958252,
+ mode: 95.6277847290039,
+ variance: 475070.5414439769,
+ stdDev: 689.2536118468854,
+ min: 95.6277847290039,
+ max: 4049.8838424682617,
+ count: 256,
+ totalSum: 496904.0036201477,
+ percentiles: {
+ p001: 95.6277847290039,
+ p01: 381.1471462249756,
+ p05: 627.6748180389404,
+ p10: 1059.0367317199707,
+ p25: 1838.1130695343018,
+ p50: 1882.4608325958252,
+ p75: 2040.8010482788086,
+ p90: 2977.8239727020264,
+ p95: 2986.7701530456543,
+ p99: 3983.0429553985596,
+ p999: 4049.8838424682617,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 1882.4608325958252,
+ },
+ {
+ percentile: 'p90',
+ value: 2977.8239727020264,
+ },
+ {
+ percentile: 'p95',
+ value: 2986.7701530456543,
+ },
+ {
+ percentile: 'p99',
+ value: 3983.0429553985596,
+ },
+ ],
+ },
+ throughput: {
+ mean: 296.0867598383026,
+ median: 2.8321597670693794,
+ mode: 1.0269062248433556,
+ variance: 26077653.461617615,
+ stdDev: 5106.628384914808,
+ min: 0.0,
+ max: 838860.8,
+ count: 659,
+ totalSum: 24515718.391140904,
+ percentiles: {
+ p001: 0.0,
+ p01: 0.0,
+ p05: 1.0269062248433556,
+ p10: 1.0269062248433556,
+ p25: 1.155029909085852,
+ p50: 2.8321597670693794,
+ p75: 6.789239657033897,
+ p90: 12.755506761996577,
+ p95: 21.059231700030626,
+ p99: 4969.554502369669,
+ p999: 56679.78378378379,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 2.8321597670693794,
+ },
+ {
+ percentile: 'p90',
+ value: 12.755506761996577,
+ },
+ {
+ percentile: 'p95',
+ value: 21.059231700030626,
+ },
+ {
+ percentile: 'p99',
+ value: 4969.554502369669,
+ },
+ ],
+ },
+ timePerRequest: {
+ mean: 6.242897774092853,
+ median: 6.808126211166382,
+ mode: 3.6642260551452637,
+ variance: 0.919577384180231,
+ stdDev: 0.958945975631699,
+ min: 3.6642260551452637,
+ max: 6.912218809127808,
+ count: 256,
+ totalSum: 1598.1818301677704,
+ percentiles: {
+ p001: 3.6642260551452637,
+ p01: 3.728823661804199,
+ p05: 4.065090894699097,
+ p10: 4.494028091430664,
+ p25: 5.758455991744995,
+ p50: 6.808126211166382,
+ p75: 6.852805137634277,
+ p90: 6.882004976272583,
+ p95: 6.897234916687012,
+ p99: 6.907586097717285,
+ p999: 6.912218809127808,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 6.808126211166382,
+ },
+ {
+ percentile: 'p90',
+ value: 6.882004976272583,
+ },
+ {
+ percentile: 'p95',
+ value: 6.897234916687012,
+ },
+ {
+ percentile: 'p99',
+ value: 6.907586097717285,
+ },
+ ],
},
- {
- "percentile": "p90",
- "value": 141.8297290802002
- },
- {
- "percentile": "p95",
- "value": 144.49095726013184
- },
- {
- "percentile": "p99",
- "value": 375.5221366882324
- }
- ]
- },
- "throughput": {
- "statistics": {
- "total": 14925,
- "mean": 1546.3194569459229,
- "median": 138.59511614843208,
- "min": 0.0,
- "max": 1677721.6,
- "std": 5844.302138842639
},
- "percentiles": [
- {
- "percentile": "p50",
- "value": 138.59511614843208
- },
- {
- "percentile": "p90",
- "value": 3916.250233426704
- },
- {
- "percentile": "p95",
- "value": 6678.828025477707
+ {
+ requestsPerSecond: 37.29183354201869,
+ tpot: {
+ mean: 603.3237551205925,
+ median: 528.1183038439069,
+ mode: 400.96027510506764,
+ variance: 12393.495352536762,
+ stdDev: 111.32607669605878,
+ min: 400.96027510506764,
+ max: 963.4451525551932,
+ count: 256,
+ totalSum: 154450.8813108717,
+ percentiles: {
+ p001: 400.96027510506764,
+ p01: 409.7368376595633,
+ p05: 410.0832939147949,
+ p10: 477.33085496085033,
+ p25: 527.9027053288052,
+ p50: 528.1183038439069,
+ p75: 722.0331260136196,
+ p90: 722.1321378435407,
+ p95: 722.210134778704,
+ p99: 722.3572731018066,
+ p999: 963.4451525551932,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 528.1183038439069,
+ },
+ {
+ percentile: 'p90',
+ value: 722.1321378435407,
+ },
+ {
+ percentile: 'p95',
+ value: 722.210134778704,
+ },
+ {
+ percentile: 'p99',
+ value: 722.3572731018066,
+ },
+ ],
+ },
+ ttft: {
+ mean: 2091.1083230748773,
+ median: 1747.6298809051514,
+ mode: 90.54684638977051,
+ variance: 479250.36269232794,
+ stdDev: 692.2791075081841,
+ min: 90.54684638977051,
+ max: 3954.521894454956,
+ count: 256,
+ totalSum: 535323.7307071686,
+ percentiles: {
+ p001: 90.54684638977051,
+ p01: 905.7919979095459,
+ p05: 1236.3860607147217,
+ p10: 1478.6958694458008,
+ p25: 1703.301191329956,
+ p50: 1747.6298809051514,
+ p75: 2842.387914657593,
+ p90: 3039.8709774017334,
+ p95: 3047.684907913208,
+ p99: 3951.2219429016113,
+ p999: 3954.521894454956,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 1747.6298809051514,
+ },
+ {
+ percentile: 'p90',
+ value: 3039.8709774017334,
+ },
+ {
+ percentile: 'p95',
+ value: 3047.684907913208,
+ },
+ {
+ percentile: 'p99',
+ value: 3951.2219429016113,
+ },
+ ],
+ },
+ throughput: {
+ mean: 298.188997111376,
+ median: 3.797001003045347,
+ mode: 1.0358592736273142,
+ variance: 19142664.16642712,
+ stdDev: 4375.2330413850095,
+ min: 0.0,
+ max: 1398101.3333333333,
+ count: 783,
+ totalSum: 26051541.418045178,
+ percentiles: {
+ p001: 0.0,
+ p01: 0.0,
+ p05: 1.0358592736273142,
+ p10: 1.0358592736273142,
+ p25: 1.3815590063114291,
+ p50: 3.797001003045347,
+ p75: 5.743851552603649,
+ p90: 11.806858966960643,
+ p95: 24.753772699641765,
+ p99: 7781.640074211503,
+ p999: 43240.24742268041,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 3.797001003045347,
+ },
+ {
+ percentile: 'p90',
+ value: 11.806858966960643,
+ },
+ {
+ percentile: 'p95',
+ value: 24.753772699641765,
+ },
+ {
+ percentile: 'p99',
+ value: 7781.640074211503,
+ },
+ ],
+ },
+ timePerRequest: {
+ mean: 6.360964580439031,
+ median: 6.761261701583862,
+ mode: 4.238183259963989,
+ variance: 0.5511043357306581,
+ stdDev: 0.7423640183431967,
+ min: 4.238183259963989,
+ max: 6.863919734954834,
+ count: 256,
+ totalSum: 1628.406932592392,
+ percentiles: {
+ p001: 4.238183259963989,
+ p01: 4.295440912246704,
+ p05: 4.5983030796051025,
+ p10: 4.984205961227417,
+ p25: 6.1305251121521,
+ p50: 6.761261701583862,
+ p75: 6.79938006401062,
+ p90: 6.837599039077759,
+ p95: 6.842914819717407,
+ p99: 6.856215000152588,
+ p999: 6.863919734954834,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 6.761261701583862,
+ },
+ {
+ percentile: 'p90',
+ value: 6.837599039077759,
+ },
+ {
+ percentile: 'p95',
+ value: 6.842914819717407,
+ },
+ {
+ percentile: 'p99',
+ value: 6.856215000152588,
+ },
+ ],
},
- {
- "percentile": "p99",
- "value": 17924.37606837607
- }
- ]
- },
- "timePerRequest": {
- "statistics": {
- "total": 2900,
- "mean": 3336.9750574539444,
- "median": 3282.672882080078,
- "min": 3228.010654449463,
- "max": 3863.8863563537598,
- "std": 141.37106520368962
},
- "percentiles": [
- {
- "percentile": "p50",
- "value": 3282.672882080078
+ {
+ requestsPerSecond: 37.45318312972309,
+ tpot: {
+ mean: 600.7204526769262,
+ median: 626.2100083487375,
+ mode: 398.7384523664202,
+ variance: 19496.451141682686,
+ stdDev: 139.62969290835917,
+ min: 398.7384523664202,
+ max: 876.9458702632359,
+ count: 256,
+ totalSum: 153784.43588529312,
+ percentiles: {
+ p001: 398.7384523664202,
+ p01: 398.79986218043734,
+ p05: 465.77743121555875,
+ p10: 465.8282824925014,
+ p25: 465.9903049468994,
+ p50: 626.2100083487375,
+ p75: 626.3504368918283,
+ p90: 876.4010156903948,
+ p95: 876.5457017081125,
+ p99: 876.6791820526123,
+ p999: 876.9458702632359,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 626.2100083487375,
+ },
+ {
+ percentile: 'p90',
+ value: 876.4010156903948,
+ },
+ {
+ percentile: 'p95',
+ value: 876.5457017081125,
+ },
+ {
+ percentile: 'p99',
+ value: 876.6791820526123,
+ },
+ ],
+ },
+ ttft: {
+ mean: 2270.3185863792896,
+ median: 2333.8708877563477,
+ mode: 624.4189739227295,
+ variance: 689884.3929942232,
+ stdDev: 830.5927961367249,
+ min: 624.4189739227295,
+ max: 4022.5632190704346,
+ count: 256,
+ totalSum: 581201.5581130981,
+ percentiles: {
+ p001: 624.4189739227295,
+ p01: 627.3941993713379,
+ p05: 636.2800598144531,
+ p10: 646.9879150390625,
+ p25: 2297.8010177612305,
+ p50: 2333.8708877563477,
+ p75: 2491.302967071533,
+ p90: 3417.022943496704,
+ p95: 3426.0239601135254,
+ p99: 3947.2179412841797,
+ p999: 4022.5632190704346,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 2333.8708877563477,
+ },
+ {
+ percentile: 'p90',
+ value: 3417.022943496704,
+ },
+ {
+ percentile: 'p95',
+ value: 3426.0239601135254,
+ },
+ {
+ percentile: 'p99',
+ value: 3947.2179412841797,
+ },
+ ],
+ },
+ throughput: {
+ mean: 299.4791635411842,
+ median: 3.030949722688924,
+ mode: 1.1391714581369894,
+ variance: 22450634.582333777,
+ stdDev: 4738.210061018167,
+ min: 0.0,
+ max: 1258291.2,
+ count: 644,
+ totalSum: 24922318.936492577,
+ percentiles: {
+ p001: 0.0,
+ p01: 0.0,
+ p05: 0.0,
+ p10: 1.1391714581369894,
+ p25: 1.1490267949845356,
+ p50: 3.030949722688924,
+ p75: 5.887742339400797,
+ p90: 8.667779853522244,
+ p95: 16.13454481108487,
+ p99: 7358.428070175439,
+ p999: 49932.19047619047,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 3.030949722688924,
+ },
+ {
+ percentile: 'p90',
+ value: 8.667779853522244,
+ },
+ {
+ percentile: 'p95',
+ value: 16.13454481108487,
+ },
+ {
+ percentile: 'p99',
+ value: 7358.428070175439,
+ },
+ ],
+ },
+ timePerRequest: {
+ mean: 6.510815089568496,
+ median: 6.725250959396362,
+ mode: 4.9165239334106445,
+ variance: 0.2361784686553011,
+ stdDev: 0.48598196330244714,
+ min: 4.9165239334106445,
+ max: 6.835154294967651,
+ count: 256,
+ totalSum: 1666.768662929535,
+ percentiles: {
+ p001: 4.9165239334106445,
+ p01: 4.9701738357543945,
+ p05: 5.246534109115601,
+ p10: 5.63304591178894,
+ p25: 6.683944940567017,
+ p50: 6.725250959396362,
+ p75: 6.763767957687378,
+ p90: 6.793000221252441,
+ p95: 6.804349184036255,
+ p99: 6.820380926132202,
+ p999: 6.835154294967651,
+ },
+ cumulativeDistributionFunction: null,
+ percentileRows: [
+ {
+ percentile: 'p50',
+ value: 6.725250959396362,
+ },
+ {
+ percentile: 'p90',
+ value: 6.793000221252441,
+ },
+ {
+ percentile: 'p95',
+ value: 6.804349184036255,
+ },
+ {
+ percentile: 'p99',
+ value: 6.820380926132202,
+ },
+ ],
},
- {
- "percentile": "p90",
- "value": 3561.7692470550537
- },
- {
- "percentile": "p95",
- "value": 3737.921953201294
- },
- {
- "percentile": "p99",
- "value": 3811.5434646606445
- }
- ]
- }
- },
- {
- "requestsPerSecond": 27.382251189847466,
- "tpot": {
- "statistics": {
- "total": 3285,
- "mean": 62.44881585866599,
- "median": 60.908238093058266,
- "min": 58.94644298250713,
- "max": 72.59870383699061,
- "std": 2.9764436606898887
},
- "percentiles": [
- {
- "percentile": "p50",
- "value": 60.908238093058266
- },
- {
- "percentile": "p90",
- "value": 68.3861043718126
- },
- {
- "percentile": "p95",
- "value": 69.21934324597555
- },
- {
- "percentile": "p99",
- "value": 70.13290269034249
- }
- ]
- },
- "ttft": {
- "statistics": {
- "total": 3285,
- "mean": 142.7834399758953,
- "median": 129.18686866760254,
- "min": 92.2248363494873,
- "max": 802.5562763214111,
- "std": 54.896961282893
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 129.18686866760254
- },
- {
- "percentile": "p90",
- "value": 158.26964378356934
- },
- {
- "percentile": "p95",
- "value": 166.79859161376953
- },
- {
- "percentile": "p99",
- "value": 422.8503704071045
- }
- ]
- },
- "throughput": {
- "statistics": {
- "total": 15706,
- "mean": 1751.1720673421933,
- "median": 318.5950626661603,
- "min": 0.0,
- "max": 1677721.6,
- "std": 6434.120608249914
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 318.5950626661603
- },
- {
- "percentile": "p90",
- "value": 4165.147964250248
- },
- {
- "percentile": "p95",
- "value": 7194.346483704974
- },
- {
- "percentile": "p99",
- "value": 19878.218009478675
- }
- ]
- },
- "timePerRequest": {
- "statistics": {
- "total": 3285,
- "mean": 4076.002237894764,
- "median": 3972.564697265625,
- "min": 3890.990972518921,
- "max": 4623.138666152954,
- "std": 197.81266460135544
- },
- "percentiles": [
- {
- "percentile": "p50",
- "value": 3972.564697265625
- },
- {
- "percentile": "p90",
- "value": 4444.445371627808
- },
- {
- "percentile": "p95",
- "value": 4506.659030914307
- },
- {
- "percentile": "p99",
- "value": 4553.745985031128
- }
- ]
- }
- }
- ]
-};`;
+ ];`;
diff --git a/src/ui/lib/store/slices/benchmarks/benchmarks.api.ts b/src/ui/lib/store/slices/benchmarks/benchmarks.api.ts
index 67d867d7..838dbc7a 100644
--- a/src/ui/lib/store/slices/benchmarks/benchmarks.api.ts
+++ b/src/ui/lib/store/slices/benchmarks/benchmarks.api.ts
@@ -1,26 +1,31 @@
import { ThunkDispatch, UnknownAction } from '@reduxjs/toolkit';
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
-import { Benchmarks, MetricData } from './benchmarks.interfaces';
+import { Benchmarks, Statistics } from './benchmarks.interfaces';
import { formatNumber } from '../../../utils/helpers';
import { defaultPercentile } from '../slo/slo.constants';
import { setSloData } from '../slo/slo.slice';
const USE_MOCK_API = process.env.NEXT_PUBLIC_USE_MOCK_API === 'true';
+// currently the injector requires 'window.benchmarks = {};' to be present in the html, but benchmarks is expected to be null or an array
const fetchBenchmarks = () => {
- return { data: window.benchmarks as Benchmarks };
+ let benchmarks = window.benchmarks;
+ if (!Array.isArray(benchmarks)) {
+ benchmarks = [];
+ }
+ return { data: benchmarks as Benchmarks };
};
const getAverageValueForPercentile = (
- firstMetric: MetricData,
- lastMetric: MetricData,
+ firstMetric: Statistics,
+ lastMetric: Statistics,
percentile: string
) => {
- const firstPercentile = firstMetric.percentiles.find(
+ const firstPercentile = firstMetric?.percentileRows.find(
(p) => p.percentile === percentile
);
- const lastPercentile = lastMetric.percentiles.find(
+ const lastPercentile = lastMetric?.percentileRows.find(
(p) => p.percentile === percentile
);
return ((firstPercentile?.value ?? 0) + (lastPercentile?.value ?? 0)) / 2;
@@ -32,33 +37,33 @@ const setDefaultSLOs = (
dispatch: ThunkDispatch
) => {
// temporarily set default slo values, long term the backend should set default slos that will not just be the avg at the default percentile
- const firstBM = data.benchmarks[0];
- const lastBM = data.benchmarks[data.benchmarks.length - 1];
+ const firstBM = data[0];
+ const lastBM = data[data.length - 1];
const ttftAvg = getAverageValueForPercentile(
- firstBM.ttft,
- lastBM.ttft,
+ firstBM?.ttft,
+ lastBM?.ttft,
defaultPercentile
);
const tpotAvg = getAverageValueForPercentile(
- firstBM.tpot,
- lastBM.tpot,
+ firstBM?.tpot,
+ lastBM?.tpot,
defaultPercentile
);
const timePerRequestAvg = getAverageValueForPercentile(
- firstBM.timePerRequest,
- lastBM.timePerRequest,
+ firstBM?.timePerRequest,
+ lastBM?.timePerRequest,
defaultPercentile
);
const throughputAvg = getAverageValueForPercentile(
- firstBM.throughput,
- lastBM.throughput,
+ firstBM?.throughput,
+ lastBM?.throughput,
defaultPercentile
);
dispatch(
setSloData({
- currentRequestRate: firstBM.requestsPerSecond,
+ currentRequestRate: firstBM?.requestsPerSecond,
current: {
ttft: formatNumber(ttftAvg, 0),
tpot: formatNumber(tpotAvg, 0),
diff --git a/src/ui/lib/store/slices/benchmarks/benchmarks.constants.ts b/src/ui/lib/store/slices/benchmarks/benchmarks.constants.ts
index deb444b2..38bddb74 100644
--- a/src/ui/lib/store/slices/benchmarks/benchmarks.constants.ts
+++ b/src/ui/lib/store/slices/benchmarks/benchmarks.constants.ts
@@ -2,6 +2,4 @@ import { Benchmarks, Name } from './benchmarks.interfaces';
export const name: Readonly = 'benchmarks';
-export const initialState: Benchmarks = {
- benchmarks: [],
-};
+export const initialState: Benchmarks = [];
diff --git a/src/ui/lib/store/slices/benchmarks/benchmarks.interfaces.ts b/src/ui/lib/store/slices/benchmarks/benchmarks.interfaces.ts
index 4dc755b2..602ae17e 100644
--- a/src/ui/lib/store/slices/benchmarks/benchmarks.interfaces.ts
+++ b/src/ui/lib/store/slices/benchmarks/benchmarks.interfaces.ts
@@ -1,44 +1,32 @@
export type Name = 'benchmarks';
-interface Statistics {
+export interface Statistics {
total: number;
mean: number;
std: number;
median: number;
min: number;
max: number;
+ percentileRows: Percentile[];
+ percentiles: Record;
}
export type PercentileValues = 'p50' | 'p90' | 'p95' | 'p99';
interface Percentile {
- percentile: string;
+ percentile: PercentileValues;
value: number;
}
-interface Bucket {
- value: number;
- count: number;
-}
-
-export interface MetricData {
- statistics: Statistics;
- percentiles: Percentile[];
- buckets: Bucket[];
- bucketWidth: number;
-}
-
export interface BenchmarkMetrics {
- ttft: MetricData;
- tpot: MetricData;
- timePerRequest: MetricData;
- throughput: MetricData;
+ ttft: Statistics;
+ tpot: Statistics;
+ timePerRequest: Statistics;
+ throughput: Statistics;
}
export interface Benchmark extends BenchmarkMetrics {
requestsPerSecond: number;
}
-export type Benchmarks = {
- benchmarks: Benchmark[];
-};
+export type Benchmarks = Benchmark[];
diff --git a/src/ui/lib/store/slices/benchmarks/benchmarks.selectors.ts b/src/ui/lib/store/slices/benchmarks/benchmarks.selectors.ts
index 9453f772..53d54f40 100644
--- a/src/ui/lib/store/slices/benchmarks/benchmarks.selectors.ts
+++ b/src/ui/lib/store/slices/benchmarks/benchmarks.selectors.ts
@@ -14,7 +14,7 @@ export const selectBenchmarks = (state: RootState) => state.benchmarks.data;
export const selectMetricsSummaryLineData = createSelector(
[selectBenchmarks, selectSloState],
(benchmarks, sloState) => {
- const sortedByRPS = benchmarks?.benchmarks
+ const sortedByRPS = benchmarks
?.slice()
?.sort((bm1, bm2) => (bm1.requestsPerSecond > bm2.requestsPerSecond ? 1 : -1));
const selectedPercentile = sloState.enforcedPercentile;
@@ -34,7 +34,7 @@ export const selectMetricsSummaryLineData = createSelector(
metrics.forEach((metric) => {
const data: Point[] = [];
sortedByRPS?.forEach((benchmark) => {
- const percentile = benchmark[metric].percentiles.find(
+ const percentile = benchmark[metric].percentileRows.find(
(p) => p.percentile === selectedPercentile
);
data.push({
@@ -58,11 +58,6 @@ const getDefaultMetricValues = () => ({
export const selectInterpolatedMetrics = createSelector(
[selectBenchmarks, selectSloState],
(benchmarks, sloState) => {
- const sortedByRPS = benchmarks?.benchmarks
- ?.slice()
- ?.sort((bm1, bm2) => (bm1.requestsPerSecond > bm2.requestsPerSecond ? 1 : -1));
- const requestRates = sortedByRPS?.map((bm) => bm.requestsPerSecond) || [];
- const { enforcedPercentile, currentRequestRate } = sloState;
const metricData: {
[K in keyof BenchmarkMetrics | 'mean']: {
enforcedPercentileValue: number;
@@ -76,6 +71,14 @@ export const selectInterpolatedMetrics = createSelector(
throughput: getDefaultMetricValues(),
mean: getDefaultMetricValues(),
};
+ if ((benchmarks?.length || 0) < 2) {
+ return metricData;
+ }
+ const sortedByRPS = benchmarks
+ ?.slice()
+ ?.sort((bm1, bm2) => (bm1.requestsPerSecond > bm2.requestsPerSecond ? 1 : -1));
+ const requestRates = sortedByRPS?.map((bm) => bm.requestsPerSecond) || [];
+ const { enforcedPercentile, currentRequestRate } = sloState;
const metrics: (keyof BenchmarkMetrics)[] = [
'ttft',
'tpot',
@@ -92,15 +95,13 @@ export const selectInterpolatedMetrics = createSelector(
return metricData;
}
metrics.forEach((metric) => {
- const meanValues = sortedByRPS.map((bm) => bm[metric].statistics.mean);
+ const meanValues = sortedByRPS.map((bm) => bm[metric].mean);
const interpolateMeanAt = createMonotoneSpline(requestRates, meanValues);
const interpolatedMeanValue: number = interpolateMeanAt(currentRequestRate) || 0;
const percentiles: PercentileValues[] = ['p50', 'p90', 'p95', 'p99'];
const valuesByPercentile = percentiles.map((p) => {
const bmValuesAtP = sortedByRPS.map((bm) => {
- const result =
- bm[metric].percentiles.find((percentile) => percentile.percentile === p)
- ?.value || 0;
+ const result = bm[metric].percentiles[p] || 0;
return result;
});
const interpolateValueAtP = createMonotoneSpline(requestRates, bmValuesAtP);
@@ -126,7 +127,7 @@ export const selectMetricsDetailsLineData = createSelector(
[selectBenchmarks],
(benchmarks) => {
const sortedByRPS =
- benchmarks?.benchmarks
+ benchmarks
?.slice()
?.sort((bm1, bm2) =>
bm1.requestsPerSecond > bm2.requestsPerSecond ? 1 : -1
@@ -152,16 +153,16 @@ export const selectMetricsDetailsLineData = createSelector(
}
const data: { [key: string]: { data: Point[]; id: string; solid?: boolean } } =
{};
- sortedByRPS[0].ttft.percentiles.forEach((p) => {
+ sortedByRPS[0].ttft.percentileRows.forEach((p) => {
data[p.percentile] = { data: [], id: p.percentile };
});
data.mean = { data: [], id: 'mean', solid: true };
sortedByRPS?.forEach((benchmark) => {
const rps = benchmark.requestsPerSecond;
- benchmark[prop].percentiles.forEach((p) => {
+ benchmark[prop].percentileRows.forEach((p) => {
data[p.percentile].data.push({ x: rps, y: p.value });
});
- const mean = benchmark[prop].statistics.mean;
+ const mean = benchmark[prop].mean;
data.mean.data.push({ x: rps, y: mean });
});
lineData[prop] = Object.keys(data).map((key) => {
diff --git a/src/ui/lib/store/slices/workloadDetails/workloadDetails.constants.ts b/src/ui/lib/store/slices/workloadDetails/workloadDetails.constants.ts
index c45efa76..e6604add 100644
--- a/src/ui/lib/store/slices/workloadDetails/workloadDetails.constants.ts
+++ b/src/ui/lib/store/slices/workloadDetails/workloadDetails.constants.ts
@@ -1,5 +1,5 @@
import { Name, WorkloadDetails } from './workloadDetails.interfaces';
-
+import { PercentileValues } from '../benchmarks/benchmarks.interfaces';
export const name: Readonly = 'workloadDetails';
export const initialState: WorkloadDetails = {
@@ -13,8 +13,9 @@ export const initialState: WorkloadDetails = {
median: 0,
min: 0,
max: 0,
+ percentiles: {} as Record,
+ percentileRows: [],
},
- percentiles: [],
buckets: [],
bucketWidth: 0,
},
@@ -29,8 +30,9 @@ export const initialState: WorkloadDetails = {
median: 0,
min: 0,
max: 0,
+ percentiles: {} as Record,
+ percentileRows: [],
},
- percentiles: [],
buckets: [],
bucketWidth: 0,
},
@@ -45,8 +47,9 @@ export const initialState: WorkloadDetails = {
median: 0,
min: 0,
max: 0,
+ percentiles: {} as Record,
+ percentileRows: [],
},
- percentiles: [],
buckets: [],
bucketWidth: 0,
},
diff --git a/src/ui/lib/store/slices/workloadDetails/workloadDetails.interfaces.ts b/src/ui/lib/store/slices/workloadDetails/workloadDetails.interfaces.ts
index 2aa7619f..bbe5d7df 100644
--- a/src/ui/lib/store/slices/workloadDetails/workloadDetails.interfaces.ts
+++ b/src/ui/lib/store/slices/workloadDetails/workloadDetails.interfaces.ts
@@ -1,18 +1,6 @@
-export type Name = 'workloadDetails';
-
-interface Statistics {
- total: number;
- mean: number;
- std: number;
- median: number;
- min: number;
- max: number;
-}
+import { Statistics } from '../benchmarks';
-interface Percentile {
- percentile: string;
- value: number;
-}
+export type Name = 'workloadDetails';
interface Bucket {
value: number;
@@ -21,7 +9,6 @@ interface Bucket {
interface Distribution {
statistics: Statistics;
- percentiles: Percentile[];
buckets: Bucket[];
bucketWidth: number;
}
diff --git a/src/ui/serve.json b/src/ui/serve.json
new file mode 100644
index 00000000..b308df80
--- /dev/null
+++ b/src/ui/serve.json
@@ -0,0 +1,3 @@
+{
+ "cleanUrls": false
+}
diff --git a/tests/ui/integration/page.test.tsx b/tests/ui/integration/page.test.tsx
index 85c4bee8..cbd8f324 100644
--- a/tests/ui/integration/page.test.tsx
+++ b/tests/ui/integration/page.test.tsx
@@ -17,10 +17,7 @@ const route = (input: RequestInfo) => {
if (url.endsWith('/run-info')) return jsonResponse({});
if (url.endsWith('/workload-details')) return jsonResponse({});
- if (url.endsWith('/benchmarks'))
- return jsonResponse({
- benchmarks: mockBenchmarks,
- });
+ if (url.endsWith('/benchmarks')) return jsonResponse(mockBenchmarks);
/* fall-through → 404 */
return { ok: false, status: 404, json: () => Promise.resolve({}) };
diff --git a/tests/ui/unit/components/Charts/DashedLine/helpers.test.ts b/tests/ui/unit/components/Charts/DashedLine/helpers.test.ts
index e8a75732..1e455159 100644
--- a/tests/ui/unit/components/Charts/DashedLine/helpers.test.ts
+++ b/tests/ui/unit/components/Charts/DashedLine/helpers.test.ts
@@ -1,4 +1,5 @@
import {
+ roundDownNice,
roundNearestNice,
roundUpNice,
spacedLogValues,
@@ -51,12 +52,57 @@ describe('roundUpNice', () => {
expect(roundUpNice(1000)).toBe(1000);
expect(roundUpNice(1200)).toBe(1200);
});
+ it("doesn't round down", () => {
+ expect(roundUpNice(1.3)).toBeGreaterThanOrEqual(1.5);
+ expect(roundUpNice(3)).toBeGreaterThanOrEqual(3);
+ expect(roundUpNice(3.3)).toBeGreaterThanOrEqual(3.5);
+ expect(roundUpNice(7.3)).toBeGreaterThanOrEqual(7.5);
+ expect(roundUpNice(11)).toBeGreaterThanOrEqual(11);
+ expect(roundUpNice(19)).toBeGreaterThanOrEqual(19);
+ });
+});
+
+describe('roundDownNice', () => {
+ it('rounds down to a nearby nice number', () => {
+ expect([10]).toContain(roundDownNice(11));
+ expect([20, 25]).toContain(roundDownNice(27));
+ expect([40, 45, 48]).toContain(roundDownNice(49));
+ expect([70, 75]).toContain(roundDownNice(79));
+ expect([75, 80]).toContain(roundDownNice(81));
+ expect([700, 750, 800]).toContain(roundDownNice(810));
+ expect([1200, 1250, 1300]).toContain(roundDownNice(1342));
+ });
+ it("doesn't round some nice numbers", () => {
+ expect(roundDownNice(15)).toBe(15);
+ expect(roundDownNice(20)).toBe(20);
+ expect(roundDownNice(30)).toBe(30);
+ expect(roundDownNice(40)).toBe(40);
+ expect(roundDownNice(75)).toBe(75);
+ expect(roundDownNice(100)).toBe(100);
+ expect(roundDownNice(150)).toBe(150);
+ expect(roundDownNice(200)).toBe(200);
+ expect(roundDownNice(400)).toBe(400);
+ expect(roundDownNice(1000)).toBe(1000);
+ expect(roundDownNice(1200)).toBe(1200);
+ });
+ it("doesn't round up", () => {
+ expect(roundDownNice(1.6)).toBeLessThanOrEqual(1.5);
+ expect(roundDownNice(3)).toBeLessThanOrEqual(3);
+ expect(roundDownNice(3.6)).toBeLessThanOrEqual(3.5);
+ expect(roundDownNice(7.6)).toBeLessThanOrEqual(7.5);
+ expect(roundDownNice(11)).toBeLessThanOrEqual(11);
+ expect(roundDownNice(19)).toBeLessThanOrEqual(19);
+ });
});
describe('spacedLogValues', () => {
const checkValuesRoughlyLogSpaced = (values: number[]) => {
+ let i = 1;
+ if (values[0] === 0) {
+ i++;
+ }
const valuesRatios = [];
- for (let i = 1; i < values.length; i++) {
+ for (i; i < values.length; i++) {
valuesRatios.push(values[i] / values[i - 1]);
}
const valuesRatiosAvg = valuesRatios.reduce((a, b) => a + b) / valuesRatios.length;
@@ -72,6 +118,10 @@ describe('spacedLogValues', () => {
checkValuesRoughlyLogSpaced(spacedLogValues(1, 122, 6));
checkValuesRoughlyLogSpaced(spacedLogValues(1, 122, 9));
});
+ it('can handle ticks for small numbers', () => {
+ checkValuesRoughlyLogSpaced(spacedLogValues(0, 8, 6));
+ });
+
it('generates an array of nice round numbers', () => {
for (const value of spacedLogValues(1, 1000, 4)) {
expect([roundUpNice(value), roundNearestNice(value)]).toContain(value);
diff --git a/tests/ui/unit/mocks/mockBenchmarks.ts b/tests/ui/unit/mocks/mockBenchmarks.ts
index 5acd7d12..884e8b89 100644
--- a/tests/ui/unit/mocks/mockBenchmarks.ts
+++ b/tests/ui/unit/mocks/mockBenchmarks.ts
@@ -2,15 +2,19 @@ export const mockBenchmarks = [
{
requestsPerSecond: 0.6668550387660497,
tpot: {
- statistics: {
- total: 80,
- mean: 23.00635663936911,
- median: 22.959455611213805,
- min: 22.880917503720237,
- max: 24.14080301920573,
- std: 0.18918760384209338,
+ total: 80,
+ mean: 23.00635663936911,
+ median: 22.959455611213805,
+ min: 22.880917503720237,
+ max: 24.14080301920573,
+ std: 0.18918760384209338,
+ percentiles: {
+ p50: 22.959455611213805,
+ p90: 23.01789086962503,
+ p95: 23.30297423947242,
+ p99: 24.14080301920573,
},
- percentiles: [
+ percentileRows: [
{
percentile: 'p50',
value: 22.959455611213805,
@@ -30,15 +34,19 @@ export const mockBenchmarks = [
],
},
ttft: {
- statistics: {
- total: 80,
- mean: 49.64659512042999,
- median: 49.23129081726074,
- min: 44.538259506225586,
- max: 55.47308921813965,
- std: 1.7735485090634995,
+ total: 80,
+ mean: 49.64659512042999,
+ median: 49.23129081726074,
+ min: 44.538259506225586,
+ max: 55.47308921813965,
+ std: 1.7735485090634995,
+ percentiles: {
+ p50: 49.23129081726074,
+ p90: 50.16160011291504,
+ p95: 54.918766021728516,
+ p99: 55.47308921813965,
},
- percentiles: [
+ percentileRows: [
{
percentile: 'p50',
value: 49.23129081726074,
@@ -58,15 +66,19 @@ export const mockBenchmarks = [
],
},
throughput: {
- statistics: {
- total: 210,
- mean: 42.58702991319684,
- median: 43.536023084668,
- min: 0.0,
- max: 43.68247620237872,
- std: 4.559764488536857,
+ total: 210,
+ mean: 42.58702991319684,
+ median: 43.536023084668,
+ min: 0.0,
+ max: 43.68247620237872,
+ std: 4.559764488536857,
+ percentiles: {
+ p50: 43.536023084668,
+ p90: 43.62613633999709,
+ p95: 43.64020767654067,
+ p99: 43.68202126662431,
},
- percentiles: [
+ percentileRows: [
{
percentile: 'p50',
value: 43.536023084668,
@@ -86,15 +98,19 @@ export const mockBenchmarks = [
],
},
timePerRequest: {
- statistics: {
- total: 80,
- mean: 1496.706646680832,
- median: 1496.1087703704834,
- min: 1490.584135055542,
- max: 1505.8784484863281,
- std: 3.4553340533022667,
+ total: 80,
+ mean: 1496.706646680832,
+ median: 1496.1087703704834,
+ min: 1490.584135055542,
+ max: 1505.8784484863281,
+ std: 3.4553340533022667,
+ percentiles: {
+ p50: 1496.1087703704834,
+ p90: 1500.9305477142334,
+ p95: 1505.3200721740723,
+ p99: 1505.8784484863281,
},
- percentiles: [
+ percentileRows: [
{
percentile: 'p50',
value: 1496.1087703704834,
@@ -117,15 +133,19 @@ export const mockBenchmarks = [
{
requestsPerSecond: 28.075330129628725,
tpot: {
- statistics: {
- total: 3416,
- mean: 126.08707076148656,
- median: 125.30853256346687,
- min: 23.034303907364134,
- max: 138.08223756693178,
- std: 3.508992115582193,
+ total: 3416,
+ mean: 126.08707076148656,
+ median: 125.30853256346687,
+ min: 23.034303907364134,
+ max: 138.08223756693178,
+ std: 3.508992115582193,
+ percentiles: {
+ p50: 125.30853256346687,
+ p90: 129.21135009281218,
+ p95: 129.52291770059554,
+ p99: 132.21229490686636,
},
- percentiles: [
+ percentileRows: [
{
percentile: 'p50',
value: 125.30853256346687,
@@ -145,15 +165,19 @@ export const mockBenchmarks = [
],
},
ttft: {
- statistics: {
- total: 3416,
- mean: 8585.486161415694,
- median: 8965.316534042358,
- min: 110.53991317749023,
- max: 12575.379610061646,
- std: 1929.5632525234505,
+ total: 3416,
+ mean: 8585.486161415694,
+ median: 8965.316534042358,
+ min: 110.53991317749023,
+ max: 12575.379610061646,
+ std: 1929.5632525234505,
+ percentiles: {
+ p50: 8965.316534042358,
+ p90: 9231.79316520691,
+ p95: 9485.00108718872,
+ p99: 12096.465587615967,
},
- percentiles: [
+ percentileRows: [
{
percentile: 'p50',
value: 8965.316534042358,
@@ -181,7 +205,13 @@ export const mockBenchmarks = [
max: 838860.8,
std: 5196.545581836957,
},
- percentiles: [
+ percentiles: {
+ p50: 670.1236619268253,
+ p90: 4068.1901066925316,
+ p95: 6374.322188449848,
+ p99: 16194.223938223939,
+ },
+ percentileRows: [
{
percentile: 'p50',
value: 670.1236619268253,
@@ -201,15 +231,19 @@ export const mockBenchmarks = [
],
},
timePerRequest: {
- statistics: {
- total: 3416,
- mean: 16526.811318389147,
- median: 17058.441638946533,
- min: 1711.3444805145264,
- max: 20646.55351638794,
- std: 2054.9553770234484,
+ total: 3416,
+ mean: 16526.811318389147,
+ median: 17058.441638946533,
+ min: 1711.3444805145264,
+ max: 20646.55351638794,
+ std: 2054.9553770234484,
+ percentiles: {
+ p50: 17058.441638946533,
+ p90: 17143.84412765503,
+ p95: 17248.060703277588,
+ p99: 20116.52660369873,
},
- percentiles: [
+ percentileRows: [
{
percentile: 'p50',
value: 17058.441638946533,
diff --git a/tests/unit/objects/test_statistics.py b/tests/unit/objects/test_statistics.py
index f3332758..fa8cccd0 100644
--- a/tests/unit/objects/test_statistics.py
+++ b/tests/unit/objects/test_statistics.py
@@ -21,6 +21,7 @@ def create_default_percentiles() -> Percentiles:
p05=5.0,
p10=10.0,
p25=25.0,
+ p50=50.0,
p75=75.0,
p90=90.0,
p95=95.0,
@@ -52,6 +53,7 @@ def test_percentiles_initialization():
assert percentiles.p05 == 5.0
assert percentiles.p10 == 10.0
assert percentiles.p25 == 25.0
+ assert percentiles.p50 == 50.0
assert percentiles.p75 == 75.0
assert percentiles.p90 == 90.0
assert percentiles.p95 == 95.0
@@ -67,6 +69,7 @@ def test_percentiles_invalid_initialization():
"p05": 5.0,
"p10": 10.0,
"p25": 25.0,
+ "p50": 50.0,
"p75": 75.0,
"p90": 90.0,
"p95": 95.0,
@@ -108,6 +111,7 @@ def test_distribution_summary_initilaization():
assert distribution_summary.percentiles.p05 == 5.0
assert distribution_summary.percentiles.p10 == 10.0
assert distribution_summary.percentiles.p25 == 25.0
+ assert distribution_summary.percentiles.p50 == 50.0
assert distribution_summary.percentiles.p75 == 75.0
assert distribution_summary.percentiles.p90 == 90.0
assert distribution_summary.percentiles.p95 == 95.0
@@ -175,6 +179,9 @@ def test_distribution_summary_from_distribution_function():
assert distribution_summary.percentiles.p25 == pytest.approx(
np.percentile(values, 25.0)
)
+ assert distribution_summary.percentiles.p50 == pytest.approx(
+ np.percentile(values, 50.0)
+ )
assert distribution_summary.percentiles.p75 == pytest.approx(
np.percentile(values, 75.0)
)
@@ -226,6 +233,9 @@ def test_distribution_summary_from_values():
assert distribution_summary.percentiles.p25 == pytest.approx(
np.percentile(values, 25.0)
)
+ assert distribution_summary.percentiles.p50 == pytest.approx(
+ np.percentile(values, 50.0)
+ )
assert distribution_summary.percentiles.p75 == pytest.approx(
np.percentile(values, 75.0)
)
@@ -284,6 +294,7 @@ def test_distribution_summary_from_request_times_concurrency():
assert distribution_summary.percentiles.p05 == pytest.approx(10)
assert distribution_summary.percentiles.p10 == pytest.approx(10)
assert distribution_summary.percentiles.p25 == pytest.approx(10)
+ assert distribution_summary.percentiles.p50 == pytest.approx(10)
assert distribution_summary.percentiles.p75 == pytest.approx(10)
assert distribution_summary.percentiles.p90 == pytest.approx(10)
assert distribution_summary.percentiles.p95 == pytest.approx(10)
@@ -318,6 +329,7 @@ def test_distribution_summary_from_request_times_rate():
assert distribution_summary.percentiles.p05 == pytest.approx(10.0)
assert distribution_summary.percentiles.p10 == pytest.approx(10.0)
assert distribution_summary.percentiles.p25 == pytest.approx(10.0)
+ assert distribution_summary.percentiles.p50 == pytest.approx(10.0)
assert distribution_summary.percentiles.p75 == pytest.approx(10.0)
assert distribution_summary.percentiles.p90 == pytest.approx(10.0)
assert distribution_summary.percentiles.p95 == pytest.approx(10.0)
@@ -358,6 +370,7 @@ def test_distribution_summary_from_iterable_request_times():
assert distribution_summary.percentiles.p05 == pytest.approx(80.0)
assert distribution_summary.percentiles.p10 == pytest.approx(80.0)
assert distribution_summary.percentiles.p25 == pytest.approx(80.0)
+ assert distribution_summary.percentiles.p50 == pytest.approx(80.0)
assert distribution_summary.percentiles.p75 == pytest.approx(80.0)
assert distribution_summary.percentiles.p90 == pytest.approx(160.0)
assert distribution_summary.percentiles.p95 == pytest.approx(160.0)
diff --git a/tests/unit/presentation/__init__.py b/tests/unit/presentation/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/unit/presentation/test_data_models.py b/tests/unit/presentation/test_data_models.py
new file mode 100644
index 00000000..c1663c43
--- /dev/null
+++ b/tests/unit/presentation/test_data_models.py
@@ -0,0 +1,20 @@
+import pytest
+
+from guidellm.presentation.data_models import Bucket
+
+
+@pytest.mark.smoke
+def test_bucket_from_data():
+ buckets, bucket_width = Bucket.from_data([8, 8, 8, 8, 8, 8], 1)
+ assert len(buckets) == 1
+ assert buckets[0].value == 8.0
+ assert buckets[0].count == 6
+ assert bucket_width == 1
+
+ buckets, bucket_width = Bucket.from_data([8, 8, 8, 8, 8, 7], 1)
+ assert len(buckets) == 2
+ assert buckets[0].value == 7.0
+ assert buckets[0].count == 1
+ assert buckets[1].value == 8.0
+ assert buckets[1].count == 5
+ assert bucket_width == 1
diff --git a/tests/unit/presentation/test_injector.py b/tests/unit/presentation/test_injector.py
new file mode 100644
index 00000000..cdaa7619
--- /dev/null
+++ b/tests/unit/presentation/test_injector.py
@@ -0,0 +1,87 @@
+from pathlib import Path
+
+import pytest
+from pydantic import BaseModel
+
+from guidellm.config import settings
+from guidellm.presentation.injector import create_report, inject_data
+
+
+class ExampleModel(BaseModel):
+ name: str
+ version: str
+
+
+@pytest.mark.smoke
+def test_inject_data():
+ html = ""
+ expected_html = (
+ ""
+ )
+ js_data = {
+ "window.run_info = {};": "window.run_info ="
+ '{ "model": { "name": "neuralmagic/Qwen2.5-7B-quantized.w8a8" } };'
+ }
+ result = inject_data(
+ js_data,
+ html,
+ )
+ assert result == expected_html
+
+
+@pytest.mark.smoke
+def test_create_report_to_file(tmpdir):
+ js_data = {
+ "window.run_info = {};": "window.run_info ="
+ '{ "model": { "name": "neuralmagic/Qwen2.5-7B-quantized.w8a8" } };'
+ }
+ html_content = ""
+ expected_html_content = (
+ ""
+ )
+
+ mock_html_path = tmpdir.join("template.html")
+ mock_html_path.write(html_content)
+ settings.report_generation.source = str(mock_html_path)
+
+ output_path = tmpdir.join("output.html")
+ result_path = create_report(js_data, str(output_path))
+ result_content = result_path.read_text()
+
+ assert result_path == output_path
+ assert result_content == expected_html_content
+
+
+@pytest.mark.smoke
+def test_create_report_with_file_nested_in_dir(tmpdir):
+ js_data = {
+ "window.run_info = {};": "window.run_info ="
+ '{ "model": { "name": "neuralmagic/Qwen2.5-7B-quantized.w8a8" } };'
+ }
+ html_content = ""
+ expected_html_content = (
+ ""
+ )
+
+ output_dir = tmpdir.mkdir("output_dir")
+ mock_html_path = tmpdir.join("template.html")
+ mock_html_path.write(html_content)
+ settings.report_generation.source = str(mock_html_path)
+
+ output_path = Path(output_dir) / "report.html"
+ result_path = create_report(js_data, str(output_path))
+
+ with Path(result_path).open("r") as file:
+ result_content = file.read()
+
+ assert result_path == output_path
+ assert result_content == expected_html_content
diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py
index 316f13e4..d9ccd885 100644
--- a/tests/unit/test_config.py
+++ b/tests/unit/test_config.py
@@ -5,6 +5,7 @@
Environment,
LoggingSettings,
OpenAISettings,
+ ReportGenerationSettings,
Settings,
print_config,
reload_settings,
@@ -18,6 +19,10 @@ def test_default_settings():
assert settings.env == Environment.PROD
assert settings.logging == LoggingSettings()
assert settings.openai == OpenAISettings()
+ assert (
+ settings.report_generation.source
+ == "https://neuralmagic.github.io/guidellm/ui/latest/index.html"
+ )
@pytest.mark.smoke
@@ -29,6 +34,7 @@ def test_settings_from_env_variables(mocker):
"GUIDELLM__logging__disabled": "true",
"GUIDELLM__OPENAI__API_KEY": "test_key",
"GUIDELLM__OPENAI__BASE_URL": "http://test.url",
+ "GUIDELLM__REPORT_GENERATION__SOURCE": "http://custom.url",
},
)
@@ -37,6 +43,31 @@ def test_settings_from_env_variables(mocker):
assert settings.logging.disabled is True
assert settings.openai.api_key == "test_key"
assert settings.openai.base_url == "http://test.url"
+ assert settings.report_generation.source == "http://custom.url"
+
+
+@pytest.mark.smoke
+def test_report_generation_default_source():
+ settings = Settings(env=Environment.LOCAL)
+ assert settings.report_generation.source == "http://localhost:3000/index.html"
+
+ settings = Settings(env=Environment.DEV)
+ assert (
+ settings.report_generation.source
+ == "https://neuralmagic.github.io/guidellm/ui/dev/index.html"
+ )
+
+ settings = Settings(env=Environment.STAGING)
+ assert (
+ settings.report_generation.source
+ == "https://neuralmagic.github.io/guidellm/ui/release/latest/index.html"
+ )
+
+ settings = Settings(env=Environment.PROD)
+ assert (
+ settings.report_generation.source
+ == "https://neuralmagic.github.io/guidellm/ui/latest/index.html"
+ )
@pytest.mark.sanity
@@ -60,6 +91,11 @@ def test_openai_settings():
assert openai_settings.base_url == "http://test.api"
+def test_report_generation_settings():
+ report_settings = ReportGenerationSettings(source="http://custom.report")
+ assert report_settings.source == "http://custom.report"
+
+
@pytest.mark.sanity
def test_generate_env_file():
settings = Settings()