Skip to content

Commit 5ec9865

Browse files
committed
refactor: update output handler to support summary and full
1 parent 6ec30ae commit 5ec9865

1 file changed

Lines changed: 101 additions & 24 deletions

File tree

benchmark_runner/output_summary_json.py

Lines changed: 101 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
"""
2-
Output handler for serializing generative benchmark reports to JSON.
2+
Output handler for serializing generative benchmark reports to JSON (both summary and full).
33
4-
This module implements a file-based output for saving benchmark results in JSON format.
5-
It extends GenerativeBenchmarkerOutput and supports both directory and explicit file path
6-
output, automatically creating parent directories as needed.
4+
This module implements a dual-output JSON handler that saves both:
5+
1. Summary JSON - Excludes large fields like individual requests and detailed metrics
6+
2. Full JSON - Contains complete benchmark data including all requests
7+
8+
Both files are saved to the same directory with clear naming conventions.
79
"""
810

911
from __future__ import annotations
@@ -18,20 +20,79 @@
1820
from guidellm.benchmark.outputs.output import GenerativeBenchmarkerOutput
1921
from guidellm.benchmark.schemas import GenerativeBenchmarksReport
2022

21-
__all__ = ["GenerativeBenchmarkerSummaryJson"]
23+
__all__ = ["GenerativeBenchmarkerDualJson", "AutoMarshalJSONEncoder"]
24+
2225

26+
class AutoMarshalJSONEncoder(json.JSONEncoder):
27+
"""
28+
Custom JSON encoder with auto-marshal support (similar to Golang's MarshalJSON).
2329
24-
@GenerativeBenchmarkerOutput.register("summary_json")
25-
class GenerativeBenchmarkerSummaryJson(GenerativeBenchmarkerOutput):
30+
This encoder automatically checks if objects have __class_json__() or __json__()
31+
methods and calls them for serialization, providing a Golang-like interface for
32+
custom JSON marshaling in Python.
2633
"""
27-
Output handler for serializing benchmark reports to JSON files.
2834

29-
This class saves generative benchmark reports to a specified file or directory in JSON format.
30-
If a directory is provided, a default filename is used. Certain fields can be excluded from the output.
35+
def default(self, o):
36+
"""
37+
Override default serialization for non-serializable objects.
38+
39+
Args:
40+
o: Object to serialize.
41+
42+
Returns:
43+
Serializable representation of the object.
44+
"""
45+
# Check if the object has a __class_json__ method (for class objects)
46+
if isinstance(o, type) and hasattr(o, "__class_json__"):
47+
return o.__class_json__()
48+
49+
# Check if the object has a __json__ method (for instances)
50+
if hasattr(o, "__json__") and callable(getattr(o, "__json__")):
51+
return o.__json__()
52+
53+
# Handle class/type objects (like response handler classes)
54+
if isinstance(o, type):
55+
# Try to find the registered name for this handler class
56+
from guidellm.backends.response_handlers import (
57+
GenerationResponseHandlerFactory,
58+
)
59+
60+
registry = GenerationResponseHandlerFactory.registry or {}
61+
class_to_name = {v: k for k, v in registry.items()}
62+
63+
handler_name = class_to_name.get(o)
64+
if handler_name:
65+
return handler_name
66+
else:
67+
# Fallback: use the full class name
68+
return f"{o.__module__}.{o.__name__}"
69+
70+
# Let the base class handle other types or raise TypeError
71+
return super().default(o)
72+
73+
74+
@GenerativeBenchmarkerOutput.register("dual_json")
75+
class GenerativeBenchmarkerDualJson(GenerativeBenchmarkerOutput):
76+
"""
77+
Output handler for serializing benchmark reports to both summary and full JSON files.
78+
79+
This class saves two JSON files:
80+
1. Summary JSON - Excludes large fields (requests, detailed metrics) for quick overview
81+
2. Full JSON - Contains complete benchmark data including all requests and metrics
82+
83+
If a directory is provided, default filenames are used. If a file path is provided,
84+
the summary uses that path and the full version adds a suffix.
3185
3286
Example:
33-
output = GenerativeBenchmarkerSummaryJson(output_path="/path/to/output.json")
34-
result_path = await output.finalize(report)
87+
# Using directory
88+
output = GenerativeBenchmarkerDualJson(output_path="/path/to/dir")
89+
# Creates: /path/to/dir/benchmarks.json (summary)
90+
# /path/to/dir/benchmarks.full.json (full)
91+
92+
# Using file path
93+
output = GenerativeBenchmarkerDualJson(output_path="/path/to/results.json")
94+
# Creates: /path/to/results.json (summary)
95+
# /path/to/results.full.json (full)
3596
"""
3697

3798
DEFAULT_FILE: ClassVar[str] = "benchmarks.json"
@@ -79,7 +140,7 @@ def validated_kwargs(
79140
output_path = (
80141
output_path if isinstance(output_path, Path) else Path(output_path)
81142
)
82-
if output_path.suffix.lower() == ".summary_json":
143+
if output_path.suffix.lower() == ".dual_json":
83144
output_path = output_path.with_suffix(".json")
84145
validated["output_path"] = output_path
85146

@@ -91,29 +152,45 @@ def validated_kwargs(
91152

92153
async def finalize(self, report: GenerativeBenchmarksReport) -> Path:
93154
"""
94-
Serialize and save the benchmark report to the configured output path in JSON format.
155+
Serialize and save the benchmark report to both summary and full JSON files.
95156
96157
Args:
97158
report: The generative benchmarks report to serialize.
98159
Returns:
99-
Path to the saved report file.
160+
Path to the saved summary report file.
100161
"""
101-
output_path = self.output_path
102-
if output_path.is_dir():
103-
output_path = output_path / self.DEFAULT_FILE
162+
# Determine output paths
163+
summary_path = self.output_path
164+
if summary_path.is_dir():
165+
summary_path = summary_path / self.DEFAULT_FILE
166+
167+
# Create full path by inserting ".full" before the extension
168+
full_path = (
169+
summary_path.parent / f"{summary_path.stem}.full{summary_path.suffix}"
170+
)
104171

105-
output_path.parent.mkdir(parents=True, exist_ok=True)
172+
# Ensure parent directory exists
173+
summary_path.parent.mkdir(parents=True, exist_ok=True)
106174

107-
# Exclude specified fields from the report, but keep a small error sample
175+
# Prepare data
108176
full_dict = report.model_dump()
109177
summary_dict = report.model_dump(exclude=self.EXCLUDE_FIELDS)
110178
self._attach_error_samples(summary_dict, full_dict)
111-
save_str = json.dumps(summary_dict, indent=4)
112179

113-
with output_path.open("w", encoding="utf-8") as file:
114-
file.write(save_str)
180+
# Use custom encoder to handle response handler classes
181+
encoder_cls = AutoMarshalJSONEncoder
182+
183+
# Save summary JSON
184+
summary_str = json.dumps(summary_dict, indent=4, cls=encoder_cls)
185+
with summary_path.open("w", encoding="utf-8") as file:
186+
file.write(summary_str)
187+
188+
# Save full JSON
189+
full_str = json.dumps(full_dict, indent=4, cls=encoder_cls)
190+
with full_path.open("w", encoding="utf-8") as file:
191+
file.write(full_str)
115192

116-
return output_path
193+
return summary_path
117194

118195
def _attach_error_samples(
119196
self, summary_dict: dict[str, Any], full_dict: dict[str, Any]

0 commit comments

Comments
 (0)