Skip to content

Commit 077ca55

Browse files
committed
implement html output, fix log scaling in charts
Signed-off-by: dalthecow <[email protected]>
1 parent 49c70aa commit 077ca55

39 files changed

+2554
-1284
lines changed

.github/workflows/development.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,11 @@ jobs:
271271
with:
272272
fetch-depth: 0
273273

274+
- name: Set up Node.js 22
275+
uses: actions/setup-node@v4
276+
with:
277+
node-version: '22'
278+
274279
- name: Check if UI-related files changed
275280
id: check-changes
276281
run: |

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ src/guidellm/version.py
66
benchmarks.json
77
benchmarks.yaml
88
benchmarks.csv
9+
benchmarks.html
910

1011
# Byte-compiled / optimized / DLL files
1112
__pycache__/
@@ -227,4 +228,5 @@ src/ui/next-env.d.ts
227228
!tsconfig.*.json
228229
!src/ui/lib
229230
!src/ui/public/manifest.json
231+
!src/ui/serve.json
230232
.eslintcache

DEVELOPING.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,16 @@ Reference [https://www.npmjs.com/package/jest-runner-groups](jest-runner-groups)
267267
*/
268268
```
269269

270+
### 🧪 UI Development Notes
271+
272+
During development, it can be helpful to view sample data. We include a sample benchmark run wired into the Redux store under:
273+
274+
```
275+
src/ui/lib/store/[runInfo/workloadDetails/benchmarks]WindowData.ts
276+
```
277+
278+
In the future this will be replaced by a configurable untracked file for dev use.
279+
270280
### Logging
271281

272282
Logging is useful for learning how GuideLLM works and finding problems.

README.md

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,41 @@ The `guidellm benchmark` command is used to run benchmarks against a generative
157157

158158
GuideLLM UI is a companion frontend for visualizing the results of a GuideLLM benchmark run.
159159

160-
### 🛠 Running the UI
160+
### 🛠 Generating an HTML report with a benchmark run
161161

162-
The UI is a WIP, check more recent PRs for the latest updates
162+
Set the output to benchmarks.html for your run:
163+
164+
```base
165+
--output-path=benchmarks.html
166+
```
167+
168+
1. Use the Hosted Build (Recommended for Most Users)
169+
170+
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.
171+
172+
Open benchmarks.html in your browser and you're done—no setup required.
173+
174+
2. Build and Serve the UI Locally (For Development) This option is useful if:
175+
176+
- You are actively developing the UI
177+
178+
- You want to test changes to the UI before publishing
179+
180+
- You want full control over how the report is displayed
181+
182+
```bash
183+
npm install
184+
npm run build
185+
npm run serve
186+
```
187+
188+
This will start a local server (e.g., at http://localhost:3000). Then set the Environment to LOCAL before running your benchmarks.
189+
190+
```bash
191+
export GUIDELLM__ENV=local
192+
193+
Alternatively, in config.py update the ENV_REPORT_MAPPING used as the asset base for report generation to the LOCAL option.
194+
```
163195

164196
## Resources
165197

package-lock.json

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"scripts": {
66
"dev": "next dev src/ui",
77
"build": "next build src/ui",
8+
"serve": "serve src/ui/out -c ../serve.json --cors",
89
"lint": "next lint --fix src/ui",
910
"type-check": "tsc -p src/ui/tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit && tsc -p tsconfig.cypress.json --noEmit",
1011
"format": "prettier --write .",
@@ -71,6 +72,7 @@
7172
"jest-runner-groups": "^2.2.0",
7273
"jest-transform-stub": "^2.0.0",
7374
"prettier": "^3.5.3",
75+
"serve": "^14.2.4",
7476
"sharp": "^0.32.0",
7577
"typescript": "^5",
7678
"typescript-eslint": "^8.34.0"

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ dependencies = [
5353
"protobuf",
5454
"pydantic>=2.11.7",
5555
"pydantic-settings>=2.0.0",
56+
"pyhumps>=3.8.0",
5657
"pyyaml>=6.0.0",
5758
"rich",
5859
"transformers",

src/guidellm/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ def benchmark():
220220
help=(
221221
"The path to save the output to. If it is a directory, "
222222
"it will save benchmarks.json under it. "
223-
"Otherwise, json, yaml, or csv files are supported for output types "
223+
"Otherwise, json, yaml, csv, or html files are supported for output types "
224224
"which will be read from the extension for the file path."
225225
),
226226
)

src/guidellm/benchmark/output.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from pathlib import Path
77
from typing import Any, Literal, Optional, Union
88

9+
import humps # type: ignore[import-not-found]
910
import yaml
1011
from pydantic import Field
1112
from rich.console import Console
@@ -25,6 +26,8 @@
2526
StandardBaseModel,
2627
StatusDistributionSummary,
2728
)
29+
from guidellm.presentation import UIDataBuilder
30+
from guidellm.presentation.injector import create_report
2831
from guidellm.scheduler import strategy_display_str
2932
from guidellm.utils import Colors, split_text_list_by_length
3033

@@ -68,6 +71,9 @@ def load_file(path: Union[str, Path]) -> "GenerativeBenchmarksReport":
6871
if type_ == "csv":
6972
raise ValueError(f"CSV file type is not supported for loading: {path}.")
7073

74+
if type_ == "html":
75+
raise ValueError(f"HTML file type is not supported for loading: {path}.")
76+
7177
raise ValueError(f"Unsupported file type: {type_} for {path}.")
7278

7379
benchmarks: list[GenerativeBenchmark] = Field(
@@ -114,6 +120,9 @@ def save_file(self, path: Union[str, Path]) -> Path:
114120
if type_ == "csv":
115121
return self.save_csv(path)
116122

123+
if type_ == "html":
124+
return self.save_html(path)
125+
117126
raise ValueError(f"Unsupported file type: {type_} for {path}.")
118127

119128
def save_json(self, path: Union[str, Path]) -> Path:
@@ -220,11 +229,29 @@ def save_csv(self, path: Union[str, Path]) -> Path:
220229

221230
return path
222231

232+
def save_html(self, path: Union[str, Path]) -> Path:
233+
"""
234+
Download html, inject report data and save to a file.
235+
236+
:param path: The path to create the report at.
237+
:return: The path to the report.
238+
"""
239+
240+
data_builder = UIDataBuilder(self.benchmarks)
241+
data = data_builder.to_dict()
242+
camel_data = humps.camelize(data)
243+
ui_api_data = {}
244+
for k, v in camel_data.items():
245+
key = f"window.{humps.decamelize(k)} = {{}};"
246+
value = f"window.{humps.decamelize(k)} = {json.dumps(v, indent=2)};\n"
247+
ui_api_data[key] = value
248+
return create_report(ui_api_data, path)
249+
223250
@staticmethod
224251
def _file_setup(
225252
path: Union[str, Path],
226-
default_file_type: Literal["json", "yaml", "csv"] = "json",
227-
) -> tuple[Path, Literal["json", "yaml", "csv"]]:
253+
default_file_type: Literal["json", "yaml", "csv", "html"] = "json",
254+
) -> tuple[Path, Literal["json", "yaml", "csv", "html"]]:
228255
path = Path(path) if not isinstance(path, Path) else path
229256

230257
if path.is_dir():
@@ -242,9 +269,12 @@ def _file_setup(
242269
if path_suffix in [".csv"]:
243270
return path, "csv"
244271

272+
if path_suffix in [".html"]:
273+
return path, "html"
274+
245275
raise ValueError(
246276
f"Unsupported file extension: {path_suffix} for {path}; "
247-
"expected json, yaml, or csv."
277+
"expected json, yaml, csv, or html."
248278
)
249279

250280
@staticmethod

src/guidellm/config.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ class Environment(str, Enum):
3030

3131

3232
ENV_REPORT_MAPPING = {
33-
Environment.PROD: "https://guidellm.neuralmagic.com/local-report/index.html",
34-
Environment.STAGING: "https://staging.guidellm.neuralmagic.com/local-report/index.html",
35-
Environment.DEV: "https://dev.guidellm.neuralmagic.com/local-report/index.html",
36-
Environment.LOCAL: "tests/dummy/report.html",
33+
Environment.PROD: "https://neuralmagic.github.io/guidellm/ui/latest/index.html",
34+
Environment.STAGING: "https://neuralmagic.github.io/guidellm/ui/release/latest/index.html",
35+
Environment.DEV: "https://neuralmagic.github.io/guidellm/ui/dev/index.html",
36+
Environment.LOCAL: "http://localhost:3000/index.html",
3737
}
3838

3939

@@ -87,6 +87,14 @@ class OpenAISettings(BaseModel):
8787
max_output_tokens: int = 16384
8888

8989

90+
class ReportGenerationSettings(BaseModel):
91+
"""
92+
Report generation settings for the application
93+
"""
94+
95+
source: str = ""
96+
97+
9098
class Settings(BaseSettings):
9199
"""
92100
All the settings are powered by pydantic_settings and could be
@@ -140,6 +148,9 @@ class Settings(BaseSettings):
140148
)
141149
openai: OpenAISettings = OpenAISettings()
142150

151+
# Report settings
152+
report_generation: ReportGenerationSettings = ReportGenerationSettings()
153+
143154
# Output settings
144155
table_border_char: str = "="
145156
table_headers_border_char: str = "-"
@@ -148,6 +159,8 @@ class Settings(BaseSettings):
148159
@model_validator(mode="after")
149160
@classmethod
150161
def set_default_source(cls, values):
162+
if not values.report_generation.source:
163+
values.report_generation.source = ENV_REPORT_MAPPING.get(values.env)
151164
return values
152165

153166
def generate_env_file(self) -> str:

0 commit comments

Comments
 (0)