Skip to content

Commit baf420b

Browse files
committed
add e2e tests
Signed-off-by: Vincent Gimenes <vincent.gimenes@gmail.com>
1 parent df2c8c3 commit baf420b

2 files changed

Lines changed: 194 additions & 6 deletions

File tree

tests/e2e/test_successful_benchmark.py

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,191 @@ def test_max_requests_benchmark(server: VllmSimServer, tmp_path: Path):
120120
f"Expected {max_requests} successful requests, got {len(successful_requests)}"
121121
)
122122
assert_successful_requests_fields(successful_requests)
123+
124+
125+
@pytest.mark.timeout(30)
126+
@pytest.mark.sanity
127+
def test_replay_profile_benchmark(server: VllmSimServer, tmp_path: Path):
128+
"""
129+
Test trace replay profile with a simple trace file.
130+
Validates that requests are replayed with correct timing from trace.
131+
Also tests time_scale (rate) functionality.
132+
"""
133+
report_name = "replay_benchmarks.json"
134+
report_path = tmp_path / report_name
135+
136+
# Create trace file with 5 requests at 0.05s intervals
137+
trace_file = _create_trace_file(tmp_path, num_requests=5, interval=0.05)
138+
139+
# Create and configure the guidellm client with replay profile
140+
client = GuidellmClient(
141+
target=server.get_url(),
142+
output_dir=tmp_path,
143+
outputs=report_name,
144+
)
145+
146+
# Start the benchmark with replay profile
147+
# rate=2.0 means time_scale=2.0 (timestamps multiplied by 2)
148+
client.start_benchmark(
149+
profile="replay",
150+
rate=2.0,
151+
max_requests=5,
152+
data=str(trace_file),
153+
processor="gpt2",
154+
)
155+
156+
# Wait for the benchmark to complete
157+
client.wait_for_completion(timeout=30)
158+
159+
# Assert no Python exceptions occurred
160+
assert_no_python_exceptions(client.stderr)
161+
162+
# Load and validate the report
163+
report = load_benchmark_report(report_path)
164+
assert len(report["benchmarks"]) == 1
165+
166+
benchmark = report["benchmarks"][0]
167+
168+
# Validate successful requests have all expected fields
169+
successful_requests = benchmark["requests"]["successful"]
170+
assert len(successful_requests) == 5, (
171+
f"Expected 5 successful requests, got {len(successful_requests)}"
172+
)
173+
assert_successful_requests_fields(successful_requests)
174+
175+
# Verify scheduler state shows correct request count
176+
assert "scheduler_state" in benchmark
177+
scheduler_state = benchmark["scheduler_state"]
178+
assert scheduler_state["processed_requests"] == 5
179+
180+
181+
@pytest.mark.timeout(30)
182+
@pytest.mark.sanity
183+
def test_replay_profile_max_requests_stronger_than_max_seconds(
184+
server: VllmSimServer, tmp_path: Path
185+
):
186+
"""
187+
Test replay profile where max_requests is the limiting constraint.
188+
Trace has 20 requests over 2 seconds, but max_requests=5 limits to 5.
189+
max_seconds=10 is not reached because max_requests triggers first.
190+
"""
191+
report_name = "replay_max_requests_stronger.json"
192+
report_path = tmp_path / report_name
193+
194+
# Create trace with 20 requests at 0.1s intervals (total 1.9s)
195+
trace_file = _create_trace_file(tmp_path, num_requests=20, interval=0.1)
196+
197+
client = GuidellmClient(
198+
target=server.get_url(),
199+
output_dir=tmp_path,
200+
outputs=report_name,
201+
)
202+
203+
# max_requests=5 should be the limiting constraint
204+
# max_seconds=10 should NOT be reached
205+
client.start_benchmark(
206+
profile="replay",
207+
rate=1.0,
208+
max_requests=5,
209+
max_seconds=10, # Very high, won't be reached
210+
data=str(trace_file),
211+
processor="gpt2",
212+
)
213+
214+
client.wait_for_completion(timeout=30)
215+
assert_no_python_exceptions(client.stderr)
216+
217+
report = load_benchmark_report(report_path)
218+
benchmark = report["benchmarks"][0]
219+
220+
# Should only have 5 requests (max_requests won)
221+
successful_requests = benchmark["requests"]["successful"]
222+
assert len(successful_requests) == 5, (
223+
f"Expected 5 requests (max_requests limit), got {len(successful_requests)}"
224+
)
225+
226+
# Verify max_requests constraint was triggered
227+
assert_constraint_triggered(benchmark, "max_requests", {"processed_exceeded": True})
228+
229+
230+
@pytest.mark.timeout(30)
231+
@pytest.mark.sanity
232+
def test_replay_profile_max_seconds_stronger_than_max_requests(
233+
server: VllmSimServer, tmp_path: Path
234+
):
235+
"""
236+
Test replay profile where max_seconds is the limiting constraint.
237+
Trace has 20 requests over 2 seconds, but max_seconds=0.3 limits to ~3 requests.
238+
max_requests=10 is not reached because max_seconds triggers first.
239+
"""
240+
report_name = "replay_max_seconds_stronger.json"
241+
report_path = tmp_path / report_name
242+
243+
# Create trace with 20 requests at 0.1s intervals
244+
# With time_scale=1.0, timestamps are: 0.0, 0.1, 0.2, 0.3, 0.4, ...
245+
# max_seconds=0.25 should include: 0.0, 0.1, 0.2 (3 requests, 0.3 > 0.25)
246+
trace_file = _create_trace_file(tmp_path, num_requests=20, interval=0.1)
247+
248+
client = GuidellmClient(
249+
target=server.get_url(),
250+
output_dir=tmp_path,
251+
outputs=report_name,
252+
)
253+
254+
# max_seconds=0.25 should be the limiting constraint
255+
# Only timestamps <= 0.25 should be kept: 0.0, 0.1, 0.2
256+
client.start_benchmark(
257+
profile="replay",
258+
rate=1.0,
259+
max_requests=10, # High, won't be reached
260+
max_seconds=0.25,
261+
data=str(trace_file),
262+
processor="gpt2",
263+
)
264+
265+
client.wait_for_completion(timeout=30)
266+
assert_no_python_exceptions(client.stderr)
267+
268+
report = load_benchmark_report(report_path)
269+
benchmark = report["benchmarks"][0]
270+
271+
# Should have 3 requests (0.0, 0.1, 0.2 where 0.2 <= 0.25)
272+
successful_requests = benchmark["requests"]["successful"]
273+
assert len(successful_requests) == 3, (
274+
f"Expected 3 requests (max_seconds=0.25 filter), got {len(successful_requests)}"
275+
)
276+
277+
# Verify max_requests constraint was triggered
278+
# (max_seconds is converted to max_requests internally)
279+
assert_constraint_triggered(benchmark, "max_requests", {"processed_exceeded": True})
280+
281+
282+
# Helper functions for trace file creation
283+
284+
285+
def _create_trace_file(
286+
tmp_path: Path, num_requests: int = 5, interval: float = 0.1
287+
) -> Path:
288+
"""Create a trace file with evenly spaced timestamps for testing."""
289+
trace_file = tmp_path / "trace.jsonl"
290+
lines = [
291+
f'{{"timestamp": {i * interval}, '
292+
f'"input_length": {10 * (i + 1)}, '
293+
f'"output_length": {5 * (i + 1)}}}'
294+
for i in range(num_requests)
295+
]
296+
trace_file.write_text("\n".join(lines))
297+
return trace_file
298+
299+
300+
def _create_burst_trace_file(tmp_path: Path, num_requests: int = 10) -> Path:
301+
"""Create a trace file with all requests at the same timestamp."""
302+
trace_file = tmp_path / "trace_burst.jsonl"
303+
lines = [
304+
f'{{"timestamp": 0.0, '
305+
f'"input_length": {20 * (i + 1)}, '
306+
f'"output_length": {10 * (i + 1)}}}'
307+
for i in range(num_requests)
308+
]
309+
trace_file.write_text("\n".join(lines))
310+
return trace_file

tests/e2e/utils.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@ def __init__(
4545
def start_benchmark(
4646
self,
4747
profile: str = "constant",
48-
rate: int = 10,
49-
max_seconds: int | None = None,
48+
rate: int | float = 10,
49+
max_seconds: int | float | None = None,
5050
max_requests: int | None = None,
5151
max_error_rate: float | None = None,
5252
over_saturation: dict[str, Any] | None = None,
53-
data: str = "prompt_tokens=256,output_tokens=128",
53+
data: str | Path = "prompt_tokens=256,output_tokens=128",
5454
processor: str = "gpt2",
5555
additional_args: str = "",
5656
extra_env: dict[str, str] | None = None,
@@ -59,13 +59,13 @@ def start_benchmark(
5959
Start a guidellm benchmark command.
6060
6161
:param profile: Type of rate control (constant, etc.)
62-
:param rate: Request rate
62+
:param rate: Request rate (or time_scale for replay profile)
6363
:param max_seconds: Maximum duration in seconds
6464
:param max_requests: Maximum number of requests
6565
:param max_error_rate: Maximum error rate before stopping
6666
:param over_saturation: Over-saturation detection configuration (dict).
6767
Passed as JSON string to --over-saturation CLI argument.
68-
:param data: Data configuration string
68+
:param data: Data configuration string or Path to trace file for replay profile
6969
:param processor: Processor/tokenizer to use
7070
:param additional_args: Additional command line arguments
7171
:param extra_env: Additional environment variables to set
@@ -109,7 +109,7 @@ def start_benchmark(
109109

110110
cmd_parts.extend(
111111
[
112-
f'--data "{data}"',
112+
f'--data "{str(data)}"',
113113
f'--processor "{processor}"',
114114
f"--output-dir {self.output_dir}",
115115
f"--outputs {self.outputs}",

0 commit comments

Comments
 (0)