guidellm 0.3.1__py3-none-any.whl → 0.6.0a5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- guidellm/__init__.py +5 -2
- guidellm/__main__.py +524 -255
- guidellm/backends/__init__.py +33 -0
- guidellm/backends/backend.py +109 -0
- guidellm/backends/openai.py +340 -0
- guidellm/backends/response_handlers.py +428 -0
- guidellm/benchmark/__init__.py +69 -39
- guidellm/benchmark/benchmarker.py +160 -316
- guidellm/benchmark/entrypoints.py +560 -127
- guidellm/benchmark/outputs/__init__.py +24 -0
- guidellm/benchmark/outputs/console.py +633 -0
- guidellm/benchmark/outputs/csv.py +721 -0
- guidellm/benchmark/outputs/html.py +473 -0
- guidellm/benchmark/outputs/output.py +169 -0
- guidellm/benchmark/outputs/serialized.py +69 -0
- guidellm/benchmark/profiles.py +718 -0
- guidellm/benchmark/progress.py +553 -556
- guidellm/benchmark/scenarios/__init__.py +40 -0
- guidellm/benchmark/scenarios/chat.json +6 -0
- guidellm/benchmark/scenarios/rag.json +6 -0
- guidellm/benchmark/schemas/__init__.py +66 -0
- guidellm/benchmark/schemas/base.py +402 -0
- guidellm/benchmark/schemas/generative/__init__.py +55 -0
- guidellm/benchmark/schemas/generative/accumulator.py +841 -0
- guidellm/benchmark/schemas/generative/benchmark.py +163 -0
- guidellm/benchmark/schemas/generative/entrypoints.py +381 -0
- guidellm/benchmark/schemas/generative/metrics.py +927 -0
- guidellm/benchmark/schemas/generative/report.py +158 -0
- guidellm/data/__init__.py +34 -4
- guidellm/data/builders.py +541 -0
- guidellm/data/collators.py +16 -0
- guidellm/data/config.py +120 -0
- guidellm/data/deserializers/__init__.py +49 -0
- guidellm/data/deserializers/deserializer.py +141 -0
- guidellm/data/deserializers/file.py +223 -0
- guidellm/data/deserializers/huggingface.py +94 -0
- guidellm/data/deserializers/memory.py +194 -0
- guidellm/data/deserializers/synthetic.py +246 -0
- guidellm/data/entrypoints.py +52 -0
- guidellm/data/loaders.py +190 -0
- guidellm/data/preprocessors/__init__.py +27 -0
- guidellm/data/preprocessors/formatters.py +410 -0
- guidellm/data/preprocessors/mappers.py +196 -0
- guidellm/data/preprocessors/preprocessor.py +30 -0
- guidellm/data/processor.py +29 -0
- guidellm/data/schemas.py +175 -0
- guidellm/data/utils/__init__.py +6 -0
- guidellm/data/utils/dataset.py +94 -0
- guidellm/extras/__init__.py +4 -0
- guidellm/extras/audio.py +220 -0
- guidellm/extras/vision.py +242 -0
- guidellm/logger.py +2 -2
- guidellm/mock_server/__init__.py +8 -0
- guidellm/mock_server/config.py +84 -0
- guidellm/mock_server/handlers/__init__.py +17 -0
- guidellm/mock_server/handlers/chat_completions.py +280 -0
- guidellm/mock_server/handlers/completions.py +280 -0
- guidellm/mock_server/handlers/tokenizer.py +142 -0
- guidellm/mock_server/models.py +510 -0
- guidellm/mock_server/server.py +238 -0
- guidellm/mock_server/utils.py +302 -0
- guidellm/scheduler/__init__.py +69 -26
- guidellm/scheduler/constraints/__init__.py +49 -0
- guidellm/scheduler/constraints/constraint.py +325 -0
- guidellm/scheduler/constraints/error.py +411 -0
- guidellm/scheduler/constraints/factory.py +182 -0
- guidellm/scheduler/constraints/request.py +312 -0
- guidellm/scheduler/constraints/saturation.py +722 -0
- guidellm/scheduler/environments.py +252 -0
- guidellm/scheduler/scheduler.py +137 -368
- guidellm/scheduler/schemas.py +358 -0
- guidellm/scheduler/strategies.py +617 -0
- guidellm/scheduler/worker.py +413 -419
- guidellm/scheduler/worker_group.py +712 -0
- guidellm/schemas/__init__.py +65 -0
- guidellm/schemas/base.py +417 -0
- guidellm/schemas/info.py +188 -0
- guidellm/schemas/request.py +235 -0
- guidellm/schemas/request_stats.py +349 -0
- guidellm/schemas/response.py +124 -0
- guidellm/schemas/statistics.py +1018 -0
- guidellm/{config.py → settings.py} +31 -24
- guidellm/utils/__init__.py +71 -8
- guidellm/utils/auto_importer.py +98 -0
- guidellm/utils/cli.py +132 -5
- guidellm/utils/console.py +566 -0
- guidellm/utils/encoding.py +778 -0
- guidellm/utils/functions.py +159 -0
- guidellm/utils/hf_datasets.py +1 -2
- guidellm/utils/hf_transformers.py +4 -4
- guidellm/utils/imports.py +9 -0
- guidellm/utils/messaging.py +1118 -0
- guidellm/utils/mixins.py +115 -0
- guidellm/utils/random.py +3 -4
- guidellm/utils/registry.py +220 -0
- guidellm/utils/singleton.py +133 -0
- guidellm/utils/synchronous.py +159 -0
- guidellm/utils/text.py +163 -50
- guidellm/utils/typing.py +41 -0
- guidellm/version.py +2 -2
- guidellm-0.6.0a5.dist-info/METADATA +364 -0
- guidellm-0.6.0a5.dist-info/RECORD +109 -0
- guidellm/backend/__init__.py +0 -23
- guidellm/backend/backend.py +0 -259
- guidellm/backend/openai.py +0 -708
- guidellm/backend/response.py +0 -136
- guidellm/benchmark/aggregator.py +0 -760
- guidellm/benchmark/benchmark.py +0 -837
- guidellm/benchmark/output.py +0 -997
- guidellm/benchmark/profile.py +0 -409
- guidellm/benchmark/scenario.py +0 -104
- guidellm/data/prideandprejudice.txt.gz +0 -0
- guidellm/dataset/__init__.py +0 -22
- guidellm/dataset/creator.py +0 -213
- guidellm/dataset/entrypoints.py +0 -42
- guidellm/dataset/file.py +0 -92
- guidellm/dataset/hf_datasets.py +0 -62
- guidellm/dataset/in_memory.py +0 -132
- guidellm/dataset/synthetic.py +0 -287
- guidellm/objects/__init__.py +0 -18
- guidellm/objects/pydantic.py +0 -89
- guidellm/objects/statistics.py +0 -953
- guidellm/preprocess/__init__.py +0 -3
- guidellm/preprocess/dataset.py +0 -374
- guidellm/presentation/__init__.py +0 -28
- guidellm/presentation/builder.py +0 -27
- guidellm/presentation/data_models.py +0 -232
- guidellm/presentation/injector.py +0 -66
- guidellm/request/__init__.py +0 -18
- guidellm/request/loader.py +0 -284
- guidellm/request/request.py +0 -79
- guidellm/request/types.py +0 -10
- guidellm/scheduler/queues.py +0 -25
- guidellm/scheduler/result.py +0 -155
- guidellm/scheduler/strategy.py +0 -495
- guidellm-0.3.1.dist-info/METADATA +0 -329
- guidellm-0.3.1.dist-info/RECORD +0 -62
- {guidellm-0.3.1.dist-info → guidellm-0.6.0a5.dist-info}/WHEEL +0 -0
- {guidellm-0.3.1.dist-info → guidellm-0.6.0a5.dist-info}/entry_points.txt +0 -0
- {guidellm-0.3.1.dist-info → guidellm-0.6.0a5.dist-info}/licenses/LICENSE +0 -0
- {guidellm-0.3.1.dist-info → guidellm-0.6.0a5.dist-info}/top_level.txt +0 -0
guidellm/benchmark/output.py
DELETED
|
@@ -1,997 +0,0 @@
|
|
|
1
|
-
import csv
|
|
2
|
-
import json
|
|
3
|
-
import math
|
|
4
|
-
from collections import OrderedDict
|
|
5
|
-
from copy import deepcopy
|
|
6
|
-
from datetime import datetime
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from typing import Any, Literal, Optional, Union
|
|
9
|
-
|
|
10
|
-
import yaml
|
|
11
|
-
from pydantic import Field
|
|
12
|
-
from rich.console import Console
|
|
13
|
-
from rich.padding import Padding
|
|
14
|
-
from rich.text import Text
|
|
15
|
-
|
|
16
|
-
from guidellm.benchmark.benchmark import GenerativeBenchmark, GenerativeMetrics
|
|
17
|
-
from guidellm.benchmark.profile import (
|
|
18
|
-
AsyncProfile,
|
|
19
|
-
ConcurrentProfile,
|
|
20
|
-
SweepProfile,
|
|
21
|
-
ThroughputProfile,
|
|
22
|
-
)
|
|
23
|
-
from guidellm.config import settings
|
|
24
|
-
from guidellm.objects import (
|
|
25
|
-
DistributionSummary,
|
|
26
|
-
StandardBaseModel,
|
|
27
|
-
StatusDistributionSummary,
|
|
28
|
-
)
|
|
29
|
-
from guidellm.presentation import UIDataBuilder
|
|
30
|
-
from guidellm.presentation.injector import create_report
|
|
31
|
-
from guidellm.scheduler import strategy_display_str
|
|
32
|
-
from guidellm.utils import Colors, split_text_list_by_length
|
|
33
|
-
from guidellm.utils.dict import recursive_key_update
|
|
34
|
-
from guidellm.utils.text import camelize_str
|
|
35
|
-
|
|
36
|
-
__all__ = [
|
|
37
|
-
"GenerativeBenchmarksConsole",
|
|
38
|
-
"GenerativeBenchmarksReport",
|
|
39
|
-
]
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class GenerativeBenchmarksReport(StandardBaseModel):
|
|
43
|
-
"""
|
|
44
|
-
A pydantic model representing a completed benchmark report.
|
|
45
|
-
Contains a list of benchmarks along with convenience methods for finalizing
|
|
46
|
-
and saving the report.
|
|
47
|
-
"""
|
|
48
|
-
|
|
49
|
-
@staticmethod
|
|
50
|
-
def load_file(path: Union[str, Path]) -> "GenerativeBenchmarksReport":
|
|
51
|
-
"""
|
|
52
|
-
Load a report from a file. The file type is determined by the file extension.
|
|
53
|
-
If the file is a directory, it expects a file named benchmarks.json under the
|
|
54
|
-
directory.
|
|
55
|
-
|
|
56
|
-
:param path: The path to load the report from.
|
|
57
|
-
:return: The loaded report.
|
|
58
|
-
"""
|
|
59
|
-
path, type_ = GenerativeBenchmarksReport._file_setup(path)
|
|
60
|
-
|
|
61
|
-
if type_ == "json":
|
|
62
|
-
with path.open("r") as file:
|
|
63
|
-
model_dict = json.load(file)
|
|
64
|
-
|
|
65
|
-
return GenerativeBenchmarksReport.model_validate(model_dict)
|
|
66
|
-
|
|
67
|
-
if type_ == "yaml":
|
|
68
|
-
with path.open("r") as file:
|
|
69
|
-
model_dict = yaml.safe_load(file)
|
|
70
|
-
|
|
71
|
-
return GenerativeBenchmarksReport.model_validate(model_dict)
|
|
72
|
-
|
|
73
|
-
if type_ == "csv":
|
|
74
|
-
raise ValueError(f"CSV file type is not supported for loading: {path}.")
|
|
75
|
-
|
|
76
|
-
if type_ == "html":
|
|
77
|
-
raise ValueError(f"HTML file type is not supported for loading: {path}.")
|
|
78
|
-
|
|
79
|
-
raise ValueError(f"Unsupported file type: {type_} for {path}.")
|
|
80
|
-
|
|
81
|
-
benchmarks: list[GenerativeBenchmark] = Field(
|
|
82
|
-
description="The list of completed benchmarks contained within the report.",
|
|
83
|
-
default_factory=list,
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
def set_sample_size(
|
|
87
|
-
self, sample_size: Optional[int]
|
|
88
|
-
) -> "GenerativeBenchmarksReport":
|
|
89
|
-
"""
|
|
90
|
-
Set the sample size for each benchmark in the report. In doing this, it will
|
|
91
|
-
reduce the contained requests of each benchmark to the sample size.
|
|
92
|
-
If sample size is None, it will return the report as is.
|
|
93
|
-
|
|
94
|
-
:param sample_size: The sample size to set for each benchmark.
|
|
95
|
-
If None, the report will be returned as is.
|
|
96
|
-
:return: The report with the sample size set for each benchmark.
|
|
97
|
-
"""
|
|
98
|
-
|
|
99
|
-
if sample_size is not None:
|
|
100
|
-
for benchmark in self.benchmarks:
|
|
101
|
-
benchmark.set_sample_size(sample_size)
|
|
102
|
-
|
|
103
|
-
return self
|
|
104
|
-
|
|
105
|
-
def save_file(self, path: Union[str, Path]) -> Path:
|
|
106
|
-
"""
|
|
107
|
-
Save the report to a file. The file type is determined by the file extension.
|
|
108
|
-
If the file is a directory, it will save the report to a file named
|
|
109
|
-
benchmarks.json under the directory.
|
|
110
|
-
|
|
111
|
-
:param path: The path to save the report to.
|
|
112
|
-
:return: The path to the saved report.
|
|
113
|
-
"""
|
|
114
|
-
path, type_ = GenerativeBenchmarksReport._file_setup(path)
|
|
115
|
-
|
|
116
|
-
if type_ == "json":
|
|
117
|
-
return self.save_json(path)
|
|
118
|
-
|
|
119
|
-
if type_ == "yaml":
|
|
120
|
-
return self.save_yaml(path)
|
|
121
|
-
|
|
122
|
-
if type_ == "csv":
|
|
123
|
-
return self.save_csv(path)
|
|
124
|
-
|
|
125
|
-
if type_ == "html":
|
|
126
|
-
return self.save_html(path)
|
|
127
|
-
|
|
128
|
-
raise ValueError(f"Unsupported file type: {type_} for {path}.")
|
|
129
|
-
|
|
130
|
-
def save_json(self, path: Union[str, Path]) -> Path:
|
|
131
|
-
"""
|
|
132
|
-
Save the report to a JSON file containing all of the report data which is
|
|
133
|
-
reloadable using the pydantic model. If the file is a directory, it will save
|
|
134
|
-
the report to a file named benchmarks.json under the directory.
|
|
135
|
-
|
|
136
|
-
:param path: The path to save the report to.
|
|
137
|
-
:return: The path to the saved report.
|
|
138
|
-
"""
|
|
139
|
-
path, type_ = GenerativeBenchmarksReport._file_setup(path, "json")
|
|
140
|
-
|
|
141
|
-
if type_ != "json":
|
|
142
|
-
raise ValueError(
|
|
143
|
-
f"Unsupported file type for saving a JSON: {type_} for {path}."
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
model_dict = self.model_dump()
|
|
147
|
-
|
|
148
|
-
with path.open("w", encoding="utf-8") as file:
|
|
149
|
-
json.dump(model_dict, file, ensure_ascii=False, indent=4)
|
|
150
|
-
|
|
151
|
-
return path
|
|
152
|
-
|
|
153
|
-
def save_yaml(self, path: Union[str, Path]) -> Path:
|
|
154
|
-
"""
|
|
155
|
-
Save the report to a YAML file containing all of the report data which is
|
|
156
|
-
reloadable using the pydantic model. If the file is a directory, it will save
|
|
157
|
-
the report to a file named benchmarks.yaml under the directory.
|
|
158
|
-
|
|
159
|
-
:param path: The path to save the report to.
|
|
160
|
-
:return: The path to the saved report.
|
|
161
|
-
"""
|
|
162
|
-
|
|
163
|
-
path, type_ = GenerativeBenchmarksReport._file_setup(path, "yaml")
|
|
164
|
-
|
|
165
|
-
if type_ != "yaml":
|
|
166
|
-
raise ValueError(
|
|
167
|
-
f"Unsupported file type for saving a YAML: {type_} for {path}."
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
model_dict = self.model_dump()
|
|
171
|
-
model_yaml = yaml.dump(model_dict)
|
|
172
|
-
|
|
173
|
-
with path.open("w") as file:
|
|
174
|
-
file.write(model_yaml)
|
|
175
|
-
|
|
176
|
-
return path
|
|
177
|
-
|
|
178
|
-
def save_csv(self, path: Union[str, Path]) -> Path:
|
|
179
|
-
"""
|
|
180
|
-
Save the report to a CSV file containing the summarized statistics and values
|
|
181
|
-
for each report. Note, this data is not reloadable using the pydantic model.
|
|
182
|
-
If the file is a directory, it will save the report to a file named
|
|
183
|
-
benchmarks.csv under the directory.
|
|
184
|
-
|
|
185
|
-
:param path: The path to save the report to.
|
|
186
|
-
:return: The path to the saved report.
|
|
187
|
-
"""
|
|
188
|
-
path, type_ = GenerativeBenchmarksReport._file_setup(path, "csv")
|
|
189
|
-
|
|
190
|
-
if type_ != "csv":
|
|
191
|
-
raise ValueError(
|
|
192
|
-
f"Unsupported file type for saving a CSV: {type_} for {path}."
|
|
193
|
-
)
|
|
194
|
-
|
|
195
|
-
with path.open("w", newline="") as file:
|
|
196
|
-
writer = csv.writer(file)
|
|
197
|
-
headers: list[str] = []
|
|
198
|
-
rows: list[list[Union[str, float, list[float]]]] = []
|
|
199
|
-
|
|
200
|
-
for benchmark in self.benchmarks:
|
|
201
|
-
benchmark_headers: list[str] = []
|
|
202
|
-
benchmark_values: list[Union[str, float, list[float]]] = []
|
|
203
|
-
|
|
204
|
-
desc_headers, desc_values = self._benchmark_desc_headers_and_values(
|
|
205
|
-
benchmark
|
|
206
|
-
)
|
|
207
|
-
benchmark_headers += desc_headers
|
|
208
|
-
benchmark_values += desc_values
|
|
209
|
-
|
|
210
|
-
for status in StatusDistributionSummary.model_fields:
|
|
211
|
-
status_headers, status_values = (
|
|
212
|
-
self._benchmark_status_headers_and_values(benchmark, status)
|
|
213
|
-
)
|
|
214
|
-
benchmark_headers += status_headers
|
|
215
|
-
benchmark_values += status_values
|
|
216
|
-
|
|
217
|
-
benchmark_extra_headers, benchmark_extra_values = (
|
|
218
|
-
self._benchmark_extras_headers_and_values(benchmark)
|
|
219
|
-
)
|
|
220
|
-
benchmark_headers += benchmark_extra_headers
|
|
221
|
-
benchmark_values += benchmark_extra_values
|
|
222
|
-
|
|
223
|
-
if not headers:
|
|
224
|
-
headers = benchmark_headers
|
|
225
|
-
rows.append(benchmark_values)
|
|
226
|
-
|
|
227
|
-
writer.writerow(headers)
|
|
228
|
-
for row in rows:
|
|
229
|
-
writer.writerow(row)
|
|
230
|
-
|
|
231
|
-
return path
|
|
232
|
-
|
|
233
|
-
def save_html(self, path: Union[str, Path]) -> Path:
|
|
234
|
-
"""
|
|
235
|
-
Download html, inject report data and save to a file.
|
|
236
|
-
|
|
237
|
-
:param path: The path to create the report at.
|
|
238
|
-
:return: The path to the report.
|
|
239
|
-
"""
|
|
240
|
-
|
|
241
|
-
data_builder = UIDataBuilder(self.benchmarks)
|
|
242
|
-
data = data_builder.to_dict()
|
|
243
|
-
camel_data = recursive_key_update(deepcopy(data), camelize_str)
|
|
244
|
-
ui_api_data = {}
|
|
245
|
-
for k, v in camel_data.items():
|
|
246
|
-
key = f"window.{k} = {{}};"
|
|
247
|
-
value = f"window.{k} = {json.dumps(v, indent=2)};\n"
|
|
248
|
-
ui_api_data[key] = value
|
|
249
|
-
return create_report(ui_api_data, path)
|
|
250
|
-
|
|
251
|
-
@staticmethod
|
|
252
|
-
def _file_setup(
|
|
253
|
-
path: Union[str, Path],
|
|
254
|
-
default_file_type: Literal["json", "yaml", "csv", "html"] = "json",
|
|
255
|
-
) -> tuple[Path, Literal["json", "yaml", "csv", "html"]]:
|
|
256
|
-
path = Path(path) if not isinstance(path, Path) else path
|
|
257
|
-
|
|
258
|
-
if path.is_dir():
|
|
259
|
-
path = path / f"benchmarks.{default_file_type}"
|
|
260
|
-
|
|
261
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
262
|
-
path_suffix = path.suffix.lower()
|
|
263
|
-
|
|
264
|
-
if path_suffix == ".json":
|
|
265
|
-
return path, "json"
|
|
266
|
-
|
|
267
|
-
if path_suffix in [".yaml", ".yml"]:
|
|
268
|
-
return path, "yaml"
|
|
269
|
-
|
|
270
|
-
if path_suffix in [".csv"]:
|
|
271
|
-
return path, "csv"
|
|
272
|
-
|
|
273
|
-
if path_suffix in [".html"]:
|
|
274
|
-
return path, "html"
|
|
275
|
-
|
|
276
|
-
raise ValueError(
|
|
277
|
-
f"Unsupported file extension: {path_suffix} for {path}; "
|
|
278
|
-
"expected json, yaml, csv, or html."
|
|
279
|
-
)
|
|
280
|
-
|
|
281
|
-
@staticmethod
|
|
282
|
-
def _benchmark_desc_headers_and_values(
|
|
283
|
-
benchmark: GenerativeBenchmark,
|
|
284
|
-
) -> tuple[list[str], list[Union[str, float]]]:
|
|
285
|
-
headers = [
|
|
286
|
-
"Type",
|
|
287
|
-
"Run Id",
|
|
288
|
-
"Id",
|
|
289
|
-
"Name",
|
|
290
|
-
"Start Time",
|
|
291
|
-
"End Time",
|
|
292
|
-
"Duration",
|
|
293
|
-
]
|
|
294
|
-
values: list[Union[str, float]] = [
|
|
295
|
-
benchmark.type_,
|
|
296
|
-
benchmark.run_id,
|
|
297
|
-
benchmark.id_,
|
|
298
|
-
strategy_display_str(benchmark.args.strategy),
|
|
299
|
-
datetime.fromtimestamp(benchmark.start_time).strftime("%Y-%m-%d %H:%M:%S"),
|
|
300
|
-
datetime.fromtimestamp(benchmark.end_time).strftime("%Y-%m-%d %H:%M:%S"),
|
|
301
|
-
benchmark.duration,
|
|
302
|
-
]
|
|
303
|
-
|
|
304
|
-
if len(headers) != len(values):
|
|
305
|
-
raise ValueError("Headers and values length mismatch.")
|
|
306
|
-
|
|
307
|
-
return headers, values
|
|
308
|
-
|
|
309
|
-
@staticmethod
|
|
310
|
-
def _benchmark_extras_headers_and_values(
|
|
311
|
-
benchmark: GenerativeBenchmark,
|
|
312
|
-
) -> tuple[list[str], list[str]]:
|
|
313
|
-
headers = ["Args", "Worker", "Request Loader", "Extras"]
|
|
314
|
-
values: list[str] = [
|
|
315
|
-
json.dumps(benchmark.args.model_dump()),
|
|
316
|
-
json.dumps(benchmark.worker.model_dump()),
|
|
317
|
-
json.dumps(benchmark.request_loader.model_dump()),
|
|
318
|
-
json.dumps(benchmark.extras),
|
|
319
|
-
]
|
|
320
|
-
|
|
321
|
-
if len(headers) != len(values):
|
|
322
|
-
raise ValueError("Headers and values length mismatch.")
|
|
323
|
-
|
|
324
|
-
return headers, values
|
|
325
|
-
|
|
326
|
-
@staticmethod
|
|
327
|
-
def _benchmark_status_headers_and_values(
|
|
328
|
-
benchmark: GenerativeBenchmark, status: str
|
|
329
|
-
) -> tuple[list[str], list[Union[float, list[float]]]]:
|
|
330
|
-
headers = [
|
|
331
|
-
f"{status.capitalize()} Requests",
|
|
332
|
-
]
|
|
333
|
-
values = [
|
|
334
|
-
getattr(benchmark.request_totals, status),
|
|
335
|
-
]
|
|
336
|
-
|
|
337
|
-
for metric in GenerativeMetrics.model_fields:
|
|
338
|
-
metric_headers, metric_values = (
|
|
339
|
-
GenerativeBenchmarksReport._benchmark_status_metrics_stats(
|
|
340
|
-
benchmark, status, metric
|
|
341
|
-
)
|
|
342
|
-
)
|
|
343
|
-
headers += metric_headers
|
|
344
|
-
values += metric_values
|
|
345
|
-
|
|
346
|
-
if len(headers) != len(values):
|
|
347
|
-
raise ValueError("Headers and values length mismatch.")
|
|
348
|
-
|
|
349
|
-
return headers, values
|
|
350
|
-
|
|
351
|
-
@staticmethod
|
|
352
|
-
def _benchmark_status_metrics_stats(
|
|
353
|
-
benchmark: GenerativeBenchmark,
|
|
354
|
-
status: str,
|
|
355
|
-
metric: str,
|
|
356
|
-
) -> tuple[list[str], list[Union[float, list[float]]]]:
|
|
357
|
-
status_display = status.capitalize()
|
|
358
|
-
metric_display = metric.replace("_", " ").capitalize()
|
|
359
|
-
status_dist_summary: StatusDistributionSummary = getattr(
|
|
360
|
-
benchmark.metrics, metric
|
|
361
|
-
)
|
|
362
|
-
dist_summary: DistributionSummary = getattr(status_dist_summary, status)
|
|
363
|
-
headers = [
|
|
364
|
-
f"{status_display} {metric_display} mean",
|
|
365
|
-
f"{status_display} {metric_display} median",
|
|
366
|
-
f"{status_display} {metric_display} std dev",
|
|
367
|
-
(
|
|
368
|
-
f"{status_display} {metric_display} "
|
|
369
|
-
"[min, 0.1, 1, 5, 10, 25, 75, 90, 95, 99, max]"
|
|
370
|
-
),
|
|
371
|
-
]
|
|
372
|
-
values: list[Union[float, list[float]]] = [
|
|
373
|
-
dist_summary.mean,
|
|
374
|
-
dist_summary.median,
|
|
375
|
-
dist_summary.std_dev,
|
|
376
|
-
[
|
|
377
|
-
dist_summary.min,
|
|
378
|
-
dist_summary.percentiles.p001,
|
|
379
|
-
dist_summary.percentiles.p01,
|
|
380
|
-
dist_summary.percentiles.p05,
|
|
381
|
-
dist_summary.percentiles.p10,
|
|
382
|
-
dist_summary.percentiles.p25,
|
|
383
|
-
dist_summary.percentiles.p75,
|
|
384
|
-
dist_summary.percentiles.p90,
|
|
385
|
-
dist_summary.percentiles.p95,
|
|
386
|
-
dist_summary.percentiles.p99,
|
|
387
|
-
dist_summary.max,
|
|
388
|
-
],
|
|
389
|
-
]
|
|
390
|
-
|
|
391
|
-
if len(headers) != len(values):
|
|
392
|
-
raise ValueError("Headers and values length mismatch.")
|
|
393
|
-
|
|
394
|
-
return headers, values
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
class GenerativeBenchmarksConsole:
|
|
398
|
-
"""
|
|
399
|
-
A class for outputting progress and benchmark results to the console.
|
|
400
|
-
Utilizes the rich library for formatting, enabling colored and styled output.
|
|
401
|
-
"""
|
|
402
|
-
|
|
403
|
-
def __init__(self, enabled: bool = True):
|
|
404
|
-
"""
|
|
405
|
-
:param enabled: Whether to enable console output. Defaults to True.
|
|
406
|
-
If False, all console output will be suppressed.
|
|
407
|
-
"""
|
|
408
|
-
self.enabled = enabled
|
|
409
|
-
self.benchmarks: Optional[list[GenerativeBenchmark]] = None
|
|
410
|
-
self.console = Console()
|
|
411
|
-
|
|
412
|
-
@property
|
|
413
|
-
def benchmarks_profile_str(self) -> str:
|
|
414
|
-
"""
|
|
415
|
-
:return: A string representation of the profile used for the benchmarks.
|
|
416
|
-
"""
|
|
417
|
-
profile = self.benchmarks[0].args.profile if self.benchmarks else None
|
|
418
|
-
|
|
419
|
-
if profile is None:
|
|
420
|
-
return "None"
|
|
421
|
-
|
|
422
|
-
profile_args = OrderedDict(
|
|
423
|
-
{
|
|
424
|
-
"type": profile.type_,
|
|
425
|
-
"strategies": profile.strategy_types,
|
|
426
|
-
}
|
|
427
|
-
)
|
|
428
|
-
|
|
429
|
-
if isinstance(profile, ConcurrentProfile):
|
|
430
|
-
profile_args["streams"] = str(profile.streams)
|
|
431
|
-
elif isinstance(profile, ThroughputProfile):
|
|
432
|
-
profile_args["max_concurrency"] = str(profile.max_concurrency)
|
|
433
|
-
elif isinstance(profile, AsyncProfile):
|
|
434
|
-
profile_args["max_concurrency"] = str(profile.max_concurrency)
|
|
435
|
-
profile_args["rate"] = str(profile.rate)
|
|
436
|
-
profile_args["initial_burst"] = str(profile.initial_burst)
|
|
437
|
-
elif isinstance(profile, SweepProfile):
|
|
438
|
-
profile_args["sweep_size"] = str(profile.sweep_size)
|
|
439
|
-
|
|
440
|
-
return ", ".join(f"{key}={value}" for key, value in profile_args.items())
|
|
441
|
-
|
|
442
|
-
@property
|
|
443
|
-
def benchmarks_args_str(self) -> str:
|
|
444
|
-
"""
|
|
445
|
-
:return: A string representation of the arguments used for the benchmarks.
|
|
446
|
-
"""
|
|
447
|
-
args = self.benchmarks[0].args if self.benchmarks else None
|
|
448
|
-
|
|
449
|
-
if args is None:
|
|
450
|
-
return "None"
|
|
451
|
-
|
|
452
|
-
args_dict = OrderedDict(
|
|
453
|
-
{
|
|
454
|
-
"max_number": args.max_number,
|
|
455
|
-
"max_duration": args.max_duration,
|
|
456
|
-
"warmup_number": args.warmup_number,
|
|
457
|
-
"warmup_duration": args.warmup_duration,
|
|
458
|
-
"cooldown_number": args.cooldown_number,
|
|
459
|
-
"cooldown_duration": args.cooldown_duration,
|
|
460
|
-
}
|
|
461
|
-
)
|
|
462
|
-
|
|
463
|
-
return ", ".join(f"{key}={value}" for key, value in args_dict.items())
|
|
464
|
-
|
|
465
|
-
@property
|
|
466
|
-
def benchmarks_worker_desc_str(self) -> str:
|
|
467
|
-
"""
|
|
468
|
-
:return: A string representation of the worker used for the benchmarks.
|
|
469
|
-
"""
|
|
470
|
-
return str(self.benchmarks[0].worker) if self.benchmarks else "None"
|
|
471
|
-
|
|
472
|
-
@property
|
|
473
|
-
def benchmarks_request_loader_desc_str(self) -> str:
|
|
474
|
-
"""
|
|
475
|
-
:return: A string representation of the request loader used for the benchmarks.
|
|
476
|
-
"""
|
|
477
|
-
return str(self.benchmarks[0].request_loader) if self.benchmarks else "None"
|
|
478
|
-
|
|
479
|
-
@property
|
|
480
|
-
def benchmarks_extras_str(self) -> str:
|
|
481
|
-
"""
|
|
482
|
-
:return: A string representation of the extras used for the benchmarks.
|
|
483
|
-
"""
|
|
484
|
-
extras = self.benchmarks[0].extras if self.benchmarks else None
|
|
485
|
-
|
|
486
|
-
if not extras:
|
|
487
|
-
return "None"
|
|
488
|
-
|
|
489
|
-
return ", ".join(f"{key}={value}" for key, value in extras.items())
|
|
490
|
-
|
|
491
|
-
def print_section_header(self, title: str, indent: int = 0, new_lines: int = 2):
|
|
492
|
-
"""
|
|
493
|
-
Print out a styled section header to the console.
|
|
494
|
-
The title is underlined, bolded, and colored with the INFO color.
|
|
495
|
-
|
|
496
|
-
:param title: The title of the section.
|
|
497
|
-
:param indent: The number of spaces to indent the title.
|
|
498
|
-
Defaults to 0.
|
|
499
|
-
:param new_lines: The number of new lines to print before the title.
|
|
500
|
-
Defaults to 2.
|
|
501
|
-
"""
|
|
502
|
-
self.print_line(
|
|
503
|
-
value=f"{title}:",
|
|
504
|
-
style=f"bold underline {Colors.INFO}",
|
|
505
|
-
indent=indent,
|
|
506
|
-
new_lines=new_lines,
|
|
507
|
-
)
|
|
508
|
-
|
|
509
|
-
def print_labeled_line(
|
|
510
|
-
self, label: str, value: str, indent: int = 4, new_lines: int = 0
|
|
511
|
-
):
|
|
512
|
-
"""
|
|
513
|
-
Print out a styled, labeled line (label: value) to the console.
|
|
514
|
-
The label is bolded and colored with the INFO color,
|
|
515
|
-
and the value is italicized.
|
|
516
|
-
|
|
517
|
-
:param label: The label of the line.
|
|
518
|
-
:param value: The value of the line.
|
|
519
|
-
:param indent: The number of spaces to indent the line.
|
|
520
|
-
Defaults to 4.
|
|
521
|
-
:param new_lines: The number of new lines to print before the line.
|
|
522
|
-
Defaults to 0.
|
|
523
|
-
"""
|
|
524
|
-
self.print_line(
|
|
525
|
-
value=[label + ":", value],
|
|
526
|
-
style=["bold " + Colors.INFO, "italic"],
|
|
527
|
-
new_lines=new_lines,
|
|
528
|
-
indent=indent,
|
|
529
|
-
)
|
|
530
|
-
|
|
531
|
-
def print_line(
|
|
532
|
-
self,
|
|
533
|
-
value: Union[str, list[str]],
|
|
534
|
-
style: Union[str, list[str]] = "",
|
|
535
|
-
indent: int = 0,
|
|
536
|
-
new_lines: int = 0,
|
|
537
|
-
):
|
|
538
|
-
"""
|
|
539
|
-
Print out a a value to the console as a line with optional indentation.
|
|
540
|
-
|
|
541
|
-
:param value: The value to print.
|
|
542
|
-
:param style: The style to apply to the value.
|
|
543
|
-
Defaults to none.
|
|
544
|
-
:param indent: The number of spaces to indent the line.
|
|
545
|
-
Defaults to 0.
|
|
546
|
-
:param new_lines: The number of new lines to print before the value.
|
|
547
|
-
Defaults to 0.
|
|
548
|
-
"""
|
|
549
|
-
if not self.enabled:
|
|
550
|
-
return
|
|
551
|
-
|
|
552
|
-
text = Text()
|
|
553
|
-
|
|
554
|
-
for _ in range(new_lines):
|
|
555
|
-
text.append("\n")
|
|
556
|
-
|
|
557
|
-
if not isinstance(value, list):
|
|
558
|
-
value = [value]
|
|
559
|
-
|
|
560
|
-
if not isinstance(style, list):
|
|
561
|
-
style = [style for _ in range(len(value))]
|
|
562
|
-
|
|
563
|
-
if len(value) != len(style):
|
|
564
|
-
raise ValueError(
|
|
565
|
-
f"Value and style length mismatch. Value length: {len(value)}, "
|
|
566
|
-
f"Style length: {len(style)}."
|
|
567
|
-
)
|
|
568
|
-
|
|
569
|
-
for val, sty in zip(value, style):
|
|
570
|
-
text.append(val, style=sty)
|
|
571
|
-
|
|
572
|
-
self.console.print(Padding.indent(text, indent))
|
|
573
|
-
|
|
574
|
-
def print_table(
|
|
575
|
-
self,
|
|
576
|
-
headers: list[str],
|
|
577
|
-
rows: list[list[Any]],
|
|
578
|
-
title: str,
|
|
579
|
-
sections: Optional[dict[str, tuple[int, int]]] = None,
|
|
580
|
-
max_char_per_col: int = 2**10,
|
|
581
|
-
indent: int = 0,
|
|
582
|
-
new_lines: int = 2,
|
|
583
|
-
):
|
|
584
|
-
"""
|
|
585
|
-
Print a table to the console with the given headers and rows.
|
|
586
|
-
|
|
587
|
-
:param headers: The headers of the table.
|
|
588
|
-
:param rows: The rows of the table.
|
|
589
|
-
:param title: The title of the table.
|
|
590
|
-
:param sections: The sections of the table grouping columns together.
|
|
591
|
-
This is a mapping of the section display name to a tuple of the start and
|
|
592
|
-
end column indices. If None, no sections are added (default).
|
|
593
|
-
:param max_char_per_col: The maximum number of characters per column.
|
|
594
|
-
:param indent: The number of spaces to indent the table.
|
|
595
|
-
Defaults to 0.
|
|
596
|
-
:param new_lines: The number of new lines to print before the table.
|
|
597
|
-
Defaults to 0.
|
|
598
|
-
"""
|
|
599
|
-
|
|
600
|
-
if rows and any(len(row) != len(headers) for row in rows):
|
|
601
|
-
raise ValueError(
|
|
602
|
-
f"Headers and rows length mismatch. Headers length: {len(headers)}, "
|
|
603
|
-
f"Row length: {len(rows[0]) if rows else 'N/A'}."
|
|
604
|
-
)
|
|
605
|
-
|
|
606
|
-
max_characters_per_column = self.calculate_max_chars_per_column(
|
|
607
|
-
headers, rows, sections, max_char_per_col
|
|
608
|
-
)
|
|
609
|
-
|
|
610
|
-
self.print_section_header(title, indent=indent, new_lines=new_lines)
|
|
611
|
-
self.print_table_divider(
|
|
612
|
-
max_characters_per_column, include_separators=False, indent=indent
|
|
613
|
-
)
|
|
614
|
-
if sections:
|
|
615
|
-
self.print_table_sections(
|
|
616
|
-
sections, max_characters_per_column, indent=indent
|
|
617
|
-
)
|
|
618
|
-
self.print_table_row(
|
|
619
|
-
split_text_list_by_length(headers, max_characters_per_column),
|
|
620
|
-
style=f"bold {Colors.INFO}",
|
|
621
|
-
indent=indent,
|
|
622
|
-
)
|
|
623
|
-
self.print_table_divider(
|
|
624
|
-
max_characters_per_column, include_separators=True, indent=indent
|
|
625
|
-
)
|
|
626
|
-
for row in rows:
|
|
627
|
-
self.print_table_row(
|
|
628
|
-
split_text_list_by_length(row, max_characters_per_column),
|
|
629
|
-
style="italic",
|
|
630
|
-
indent=indent,
|
|
631
|
-
)
|
|
632
|
-
self.print_table_divider(
|
|
633
|
-
max_characters_per_column, include_separators=False, indent=indent
|
|
634
|
-
)
|
|
635
|
-
|
|
636
|
-
def calculate_max_chars_per_column(
|
|
637
|
-
self,
|
|
638
|
-
headers: list[str],
|
|
639
|
-
rows: list[list[Any]],
|
|
640
|
-
sections: Optional[dict[str, tuple[int, int]]],
|
|
641
|
-
max_char_per_col: int,
|
|
642
|
-
) -> list[int]:
|
|
643
|
-
"""
|
|
644
|
-
Calculate the maximum number of characters per column in the table.
|
|
645
|
-
This is done by checking the length of the headers, rows, and optional sections
|
|
646
|
-
to ensure all columns are accounted for and spaced correctly.
|
|
647
|
-
|
|
648
|
-
:param headers: The headers of the table.
|
|
649
|
-
:param rows: The rows of the table.
|
|
650
|
-
:param sections: The sections of the table grouping columns together.
|
|
651
|
-
This is a mapping of the section display name to a tuple of the start and
|
|
652
|
-
end column indices. If None, no sections are added (default).
|
|
653
|
-
:param max_char_per_col: The maximum number of characters per column.
|
|
654
|
-
:return: A list of the maximum number of characters per column.
|
|
655
|
-
"""
|
|
656
|
-
max_characters_per_column = []
|
|
657
|
-
for ind in range(len(headers)):
|
|
658
|
-
max_characters_per_column.append(min(len(headers[ind]), max_char_per_col))
|
|
659
|
-
|
|
660
|
-
for row in rows:
|
|
661
|
-
max_characters_per_column[ind] = max(
|
|
662
|
-
max_characters_per_column[ind], len(str(row[ind]))
|
|
663
|
-
)
|
|
664
|
-
|
|
665
|
-
if not sections:
|
|
666
|
-
return max_characters_per_column
|
|
667
|
-
|
|
668
|
-
for section in sections:
|
|
669
|
-
start_col, end_col = sections[section]
|
|
670
|
-
min_section_len = len(section) + (
|
|
671
|
-
end_col - start_col
|
|
672
|
-
) # ensure we have enough space for separators
|
|
673
|
-
chars_in_columns = sum(
|
|
674
|
-
max_characters_per_column[start_col : end_col + 1]
|
|
675
|
-
) + 2 * (end_col - start_col)
|
|
676
|
-
if min_section_len > chars_in_columns:
|
|
677
|
-
add_chars_per_col = math.ceil(
|
|
678
|
-
(min_section_len - chars_in_columns) / (end_col - start_col + 1)
|
|
679
|
-
)
|
|
680
|
-
for col in range(start_col, end_col + 1):
|
|
681
|
-
max_characters_per_column[col] += add_chars_per_col
|
|
682
|
-
|
|
683
|
-
return max_characters_per_column
|
|
684
|
-
|
|
685
|
-
def print_table_divider(
|
|
686
|
-
self, max_chars_per_column: list[int], include_separators: bool, indent: int = 0
|
|
687
|
-
):
|
|
688
|
-
"""
|
|
689
|
-
Print a divider line for the table (top and bottom of table with '=' characters)
|
|
690
|
-
|
|
691
|
-
:param max_chars_per_column: The maximum number of characters per column.
|
|
692
|
-
:param include_separators: Whether to include separators between columns.
|
|
693
|
-
:param indent: The number of spaces to indent the line.
|
|
694
|
-
Defaults to 0.
|
|
695
|
-
"""
|
|
696
|
-
if include_separators:
|
|
697
|
-
columns = [
|
|
698
|
-
settings.table_headers_border_char * max_chars
|
|
699
|
-
+ settings.table_column_separator_char
|
|
700
|
-
+ settings.table_headers_border_char
|
|
701
|
-
for max_chars in max_chars_per_column
|
|
702
|
-
]
|
|
703
|
-
else:
|
|
704
|
-
columns = [
|
|
705
|
-
settings.table_border_char * (max_chars + 2)
|
|
706
|
-
for max_chars in max_chars_per_column
|
|
707
|
-
]
|
|
708
|
-
|
|
709
|
-
columns[-1] = columns[-1][:-2]
|
|
710
|
-
self.print_line(value=columns, style=Colors.INFO, indent=indent)
|
|
711
|
-
|
|
712
|
-
def print_table_sections(
|
|
713
|
-
self,
|
|
714
|
-
sections: dict[str, tuple[int, int]],
|
|
715
|
-
max_chars_per_column: list[int],
|
|
716
|
-
indent: int = 0,
|
|
717
|
-
):
|
|
718
|
-
"""
|
|
719
|
-
Print the sections of the table with corresponding separators to the columns
|
|
720
|
-
the sections are mapped to to ensure it is compliant with a CSV format.
|
|
721
|
-
For example, a section named "Metadata" with columns 0-3 will print this:
|
|
722
|
-
Metadata ,,,,
|
|
723
|
-
Where the spaces plus the separators at the end will span the columns 0-3.
|
|
724
|
-
All columns must be accounted for in the sections.
|
|
725
|
-
|
|
726
|
-
:param sections: The sections of the table.
|
|
727
|
-
:param max_chars_per_column: The maximum number of characters per column.
|
|
728
|
-
:param indent: The number of spaces to indent the line.
|
|
729
|
-
Defaults to 0.
|
|
730
|
-
"""
|
|
731
|
-
section_tuples = [(start, end, name) for name, (start, end) in sections.items()]
|
|
732
|
-
section_tuples.sort(key=lambda x: x[0])
|
|
733
|
-
|
|
734
|
-
if any(start > end for start, end, _ in section_tuples):
|
|
735
|
-
raise ValueError(f"Invalid section ranges: {section_tuples}")
|
|
736
|
-
|
|
737
|
-
if (
|
|
738
|
-
any(
|
|
739
|
-
section_tuples[ind][1] + 1 != section_tuples[ind + 1][0]
|
|
740
|
-
for ind in range(len(section_tuples) - 1)
|
|
741
|
-
)
|
|
742
|
-
or section_tuples[0][0] != 0
|
|
743
|
-
or section_tuples[-1][1] != len(max_chars_per_column) - 1
|
|
744
|
-
):
|
|
745
|
-
raise ValueError(f"Invalid section ranges: {section_tuples}")
|
|
746
|
-
|
|
747
|
-
line_values = []
|
|
748
|
-
line_styles = []
|
|
749
|
-
for section, (start_col, end_col) in sections.items():
|
|
750
|
-
section_length = sum(max_chars_per_column[start_col : end_col + 1]) + 2 * (
|
|
751
|
-
end_col - start_col + 1
|
|
752
|
-
)
|
|
753
|
-
num_separators = end_col - start_col
|
|
754
|
-
line_values.append(section)
|
|
755
|
-
line_styles.append("bold " + Colors.INFO)
|
|
756
|
-
line_values.append(
|
|
757
|
-
" " * (section_length - len(section) - num_separators - 2)
|
|
758
|
-
)
|
|
759
|
-
line_styles.append("")
|
|
760
|
-
line_values.append(settings.table_column_separator_char * num_separators)
|
|
761
|
-
line_styles.append("")
|
|
762
|
-
line_values.append(settings.table_column_separator_char + " ")
|
|
763
|
-
line_styles.append(Colors.INFO)
|
|
764
|
-
line_values = line_values[:-1]
|
|
765
|
-
line_styles = line_styles[:-1]
|
|
766
|
-
self.print_line(value=line_values, style=line_styles, indent=indent)
|
|
767
|
-
|
|
768
|
-
def print_table_row(
|
|
769
|
-
self, column_lines: list[list[str]], style: str, indent: int = 0
|
|
770
|
-
):
|
|
771
|
-
"""
|
|
772
|
-
Print a single row of a table to the console.
|
|
773
|
-
|
|
774
|
-
:param column_lines: The lines of text to print for each column.
|
|
775
|
-
:param indent: The number of spaces to indent the line.
|
|
776
|
-
Defaults to 0.
|
|
777
|
-
"""
|
|
778
|
-
for row in range(len(column_lines[0])):
|
|
779
|
-
print_line = []
|
|
780
|
-
print_styles = []
|
|
781
|
-
for column in range(len(column_lines)):
|
|
782
|
-
print_line.extend(
|
|
783
|
-
[
|
|
784
|
-
column_lines[column][row],
|
|
785
|
-
settings.table_column_separator_char,
|
|
786
|
-
" ",
|
|
787
|
-
]
|
|
788
|
-
)
|
|
789
|
-
print_styles.extend([style, Colors.INFO, ""])
|
|
790
|
-
print_line = print_line[:-2]
|
|
791
|
-
print_styles = print_styles[:-2]
|
|
792
|
-
self.print_line(value=print_line, style=print_styles, indent=indent)
|
|
793
|
-
|
|
794
|
-
def print_benchmarks_metadata(self):
|
|
795
|
-
"""
|
|
796
|
-
Print out the metadata of the benchmarks to the console including the run id,
|
|
797
|
-
duration, profile, args, worker, request loader, and extras.
|
|
798
|
-
"""
|
|
799
|
-
|
|
800
|
-
if not self.benchmarks:
|
|
801
|
-
raise ValueError(
|
|
802
|
-
"No benchmarks to print metadata for. Please set benchmarks first."
|
|
803
|
-
)
|
|
804
|
-
|
|
805
|
-
start_time = self.benchmarks[0].run_stats.start_time
|
|
806
|
-
end_time = self.benchmarks[-1].run_stats.end_time
|
|
807
|
-
duration = end_time - start_time
|
|
808
|
-
|
|
809
|
-
self.print_section_header(title="Benchmarks Metadata")
|
|
810
|
-
self.print_labeled_line(
|
|
811
|
-
label="Run id",
|
|
812
|
-
value=str(self.benchmarks[0].run_id),
|
|
813
|
-
)
|
|
814
|
-
self.print_labeled_line(
|
|
815
|
-
label="Duration",
|
|
816
|
-
value=f"{duration:.1f} seconds",
|
|
817
|
-
)
|
|
818
|
-
self.print_labeled_line(
|
|
819
|
-
label="Profile",
|
|
820
|
-
value=self.benchmarks_profile_str,
|
|
821
|
-
)
|
|
822
|
-
self.print_labeled_line(
|
|
823
|
-
label="Args",
|
|
824
|
-
value=self.benchmarks_args_str,
|
|
825
|
-
)
|
|
826
|
-
self.print_labeled_line(
|
|
827
|
-
label="Worker",
|
|
828
|
-
value=self.benchmarks_worker_desc_str,
|
|
829
|
-
)
|
|
830
|
-
self.print_labeled_line(
|
|
831
|
-
label="Request Loader",
|
|
832
|
-
value=self.benchmarks_request_loader_desc_str,
|
|
833
|
-
)
|
|
834
|
-
self.print_labeled_line(
|
|
835
|
-
label="Extras",
|
|
836
|
-
value=self.benchmarks_extras_str,
|
|
837
|
-
)
|
|
838
|
-
|
|
839
|
-
def print_benchmarks_info(self):
|
|
840
|
-
"""
|
|
841
|
-
Print out the benchmark information to the console including the start time,
|
|
842
|
-
end time, duration, request totals, and token totals for each benchmark.
|
|
843
|
-
"""
|
|
844
|
-
if not self.benchmarks:
|
|
845
|
-
raise ValueError(
|
|
846
|
-
"No benchmarks to print info for. Please set benchmarks first."
|
|
847
|
-
)
|
|
848
|
-
|
|
849
|
-
sections = {
|
|
850
|
-
"Metadata": (0, 3),
|
|
851
|
-
"Requests Made": (4, 6),
|
|
852
|
-
"Prompt Tok/Req": (7, 9),
|
|
853
|
-
"Output Tok/Req": (10, 12),
|
|
854
|
-
"Prompt Tok Total": (13, 15),
|
|
855
|
-
"Output Tok Total": (16, 18),
|
|
856
|
-
}
|
|
857
|
-
headers = [
|
|
858
|
-
"Benchmark",
|
|
859
|
-
"Start Time",
|
|
860
|
-
"End Time",
|
|
861
|
-
"Duration (s)",
|
|
862
|
-
"Comp",
|
|
863
|
-
"Inc",
|
|
864
|
-
"Err",
|
|
865
|
-
"Comp",
|
|
866
|
-
"Inc",
|
|
867
|
-
"Err",
|
|
868
|
-
"Comp",
|
|
869
|
-
"Inc",
|
|
870
|
-
"Err",
|
|
871
|
-
"Comp",
|
|
872
|
-
"Inc",
|
|
873
|
-
"Err",
|
|
874
|
-
"Comp",
|
|
875
|
-
"Inc",
|
|
876
|
-
"Err",
|
|
877
|
-
]
|
|
878
|
-
rows = []
|
|
879
|
-
|
|
880
|
-
for benchmark in self.benchmarks:
|
|
881
|
-
rows.append(
|
|
882
|
-
[
|
|
883
|
-
strategy_display_str(benchmark.args.strategy),
|
|
884
|
-
f"{datetime.fromtimestamp(benchmark.start_time).strftime('%H:%M:%S')}",
|
|
885
|
-
f"{datetime.fromtimestamp(benchmark.end_time).strftime('%H:%M:%S')}",
|
|
886
|
-
f"{(benchmark.end_time - benchmark.start_time):.1f}",
|
|
887
|
-
f"{benchmark.request_totals.successful:.0f}",
|
|
888
|
-
f"{benchmark.request_totals.incomplete:.0f}",
|
|
889
|
-
f"{benchmark.request_totals.errored:.0f}",
|
|
890
|
-
f"{benchmark.metrics.prompt_token_count.successful.mean:.1f}",
|
|
891
|
-
f"{benchmark.metrics.prompt_token_count.incomplete.mean:.1f}",
|
|
892
|
-
f"{benchmark.metrics.prompt_token_count.errored.mean:.1f}",
|
|
893
|
-
f"{benchmark.metrics.output_token_count.successful.mean:.1f}",
|
|
894
|
-
f"{benchmark.metrics.output_token_count.incomplete.mean:.1f}",
|
|
895
|
-
f"{benchmark.metrics.output_token_count.errored.mean:.1f}",
|
|
896
|
-
f"{benchmark.metrics.prompt_token_count.successful.total_sum:.0f}",
|
|
897
|
-
f"{benchmark.metrics.prompt_token_count.incomplete.total_sum:.0f}",
|
|
898
|
-
f"{benchmark.metrics.prompt_token_count.errored.total_sum:.0f}",
|
|
899
|
-
f"{benchmark.metrics.output_token_count.successful.total_sum:.0f}",
|
|
900
|
-
f"{benchmark.metrics.output_token_count.incomplete.total_sum:.0f}",
|
|
901
|
-
f"{benchmark.metrics.output_token_count.errored.total_sum:.0f}",
|
|
902
|
-
]
|
|
903
|
-
)
|
|
904
|
-
|
|
905
|
-
self.print_table(
|
|
906
|
-
headers=headers, rows=rows, title="Benchmarks Info", sections=sections
|
|
907
|
-
)
|
|
908
|
-
|
|
909
|
-
def print_benchmarks_stats(self):
|
|
910
|
-
"""
|
|
911
|
-
Print out the benchmark statistics to the console including the requests per
|
|
912
|
-
second, request concurrency, output tokens per second, total tokens per second,
|
|
913
|
-
request latency, time to first token, inter token latency, and time per output
|
|
914
|
-
token for each benchmark.
|
|
915
|
-
"""
|
|
916
|
-
if not self.benchmarks:
|
|
917
|
-
raise ValueError(
|
|
918
|
-
"No benchmarks to print stats for. Please set benchmarks first."
|
|
919
|
-
)
|
|
920
|
-
|
|
921
|
-
sections = {
|
|
922
|
-
"Metadata": (0, 0),
|
|
923
|
-
"Request Stats": (1, 2),
|
|
924
|
-
"Out Tok/sec": (3, 3),
|
|
925
|
-
"Tot Tok/sec": (4, 4),
|
|
926
|
-
"Req Latency (sec)": (5, 7),
|
|
927
|
-
"TTFT (ms)": (8, 10),
|
|
928
|
-
"ITL (ms)": (11, 13),
|
|
929
|
-
"TPOT (ms)": (14, 16),
|
|
930
|
-
}
|
|
931
|
-
headers = [
|
|
932
|
-
"Benchmark",
|
|
933
|
-
"Per Second",
|
|
934
|
-
"Concurrency",
|
|
935
|
-
"mean",
|
|
936
|
-
"mean",
|
|
937
|
-
"mean",
|
|
938
|
-
"median",
|
|
939
|
-
"p99",
|
|
940
|
-
"mean",
|
|
941
|
-
"median",
|
|
942
|
-
"p99",
|
|
943
|
-
"mean",
|
|
944
|
-
"median",
|
|
945
|
-
"p99",
|
|
946
|
-
"mean",
|
|
947
|
-
"median",
|
|
948
|
-
"p99",
|
|
949
|
-
]
|
|
950
|
-
rows = []
|
|
951
|
-
|
|
952
|
-
for benchmark in self.benchmarks:
|
|
953
|
-
rows.append(
|
|
954
|
-
[
|
|
955
|
-
strategy_display_str(benchmark.args.strategy),
|
|
956
|
-
f"{benchmark.metrics.requests_per_second.successful.mean:.2f}",
|
|
957
|
-
f"{benchmark.metrics.request_concurrency.successful.mean:.2f}",
|
|
958
|
-
f"{benchmark.metrics.output_tokens_per_second.successful.mean:.1f}",
|
|
959
|
-
f"{benchmark.metrics.tokens_per_second.successful.mean:.1f}",
|
|
960
|
-
f"{benchmark.metrics.request_latency.successful.mean:.2f}",
|
|
961
|
-
f"{benchmark.metrics.request_latency.successful.median:.2f}",
|
|
962
|
-
f"{benchmark.metrics.request_latency.successful.percentiles.p99:.2f}",
|
|
963
|
-
f"{benchmark.metrics.time_to_first_token_ms.successful.mean:.1f}",
|
|
964
|
-
f"{benchmark.metrics.time_to_first_token_ms.successful.median:.1f}",
|
|
965
|
-
f"{benchmark.metrics.time_to_first_token_ms.successful.percentiles.p99:.1f}",
|
|
966
|
-
f"{benchmark.metrics.inter_token_latency_ms.successful.mean:.1f}",
|
|
967
|
-
f"{benchmark.metrics.inter_token_latency_ms.successful.median:.1f}",
|
|
968
|
-
f"{benchmark.metrics.inter_token_latency_ms.successful.percentiles.p99:.1f}",
|
|
969
|
-
f"{benchmark.metrics.time_per_output_token_ms.successful.mean:.1f}",
|
|
970
|
-
f"{benchmark.metrics.time_per_output_token_ms.successful.median:.1f}",
|
|
971
|
-
f"{benchmark.metrics.time_per_output_token_ms.successful.percentiles.p99:.1f}",
|
|
972
|
-
]
|
|
973
|
-
)
|
|
974
|
-
|
|
975
|
-
self.print_table(
|
|
976
|
-
headers=headers,
|
|
977
|
-
rows=rows,
|
|
978
|
-
title="Benchmarks Stats",
|
|
979
|
-
sections=sections,
|
|
980
|
-
)
|
|
981
|
-
|
|
982
|
-
def print_full_report(self):
|
|
983
|
-
"""
|
|
984
|
-
Print out the benchmark statistics to the console.
|
|
985
|
-
Temporarily enables the console if it's disabled.
|
|
986
|
-
|
|
987
|
-
Format:
|
|
988
|
-
- Metadata
|
|
989
|
-
- Info
|
|
990
|
-
- Stats
|
|
991
|
-
"""
|
|
992
|
-
orig_enabled = self.enabled
|
|
993
|
-
self.enabled = True
|
|
994
|
-
self.print_benchmarks_metadata()
|
|
995
|
-
self.print_benchmarks_info()
|
|
996
|
-
self.print_benchmarks_stats()
|
|
997
|
-
self.enabled = orig_enabled
|