litellm/tests/e2e/perf/setup/series.py
Kerry Lu b99d2cc06e
test(perf): ramp the load cases until the pod stops keeping up and floor sustained_rps (#47758)
* test(perf): add small_bodies_30rps and mixed_size_requests_4rps load cases

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(perf): run the load cases at 50 rps small and 5 rps mixed, the pod's knee

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(perf): ramp the load cases until the pod stops keeping up and report sustained_rps

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(perf): hand the ramp to the observation and widen the mixed ramp to 14 rps

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(perf): floor the small ramp at 20 rps and emit the per-step readings

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(perf): emit the ramp's hold bound and each step's unexpected statuses

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(perf): read each ramp step's cpu, redis latency and connections from the samplers' windows

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(perf): floor mixed_size_requests_ramp at 8 rps from the lane's 10 rps sustained step

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(perf): compare step readings with math.isclose so the e2e basedpyright gate stays clean

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: kerry <kerry@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-20 22:31:08 -07:00

48 lines
1.5 KiB
Python

"""Readings a background sampler takes while the traffic runs, each stamped with the wall clock
(`time.time()`, the clock the Locust records carry in `started_at`), so a ramped run can cut them into its
steps' windows after the fact."""
from __future__ import annotations
import time
from dataclasses import dataclass
from typing import Final, Generic, TypeVar
T = TypeVar("T")
@dataclass(frozen=True, slots=True)
class Sample(Generic[T]):
at: float
value: T
@dataclass(frozen=True, slots=True)
class Series(Generic[T]):
samples: tuple[Sample[T], ...]
def between(self, start: float, end: float) -> tuple[T, ...]:
"""The values stamped inside `[start, end)`, in time order."""
return tuple(sample.value for sample in self.samples if start <= sample.at < end)
def last_before(self, at: float) -> T | None:
"""The newest value stamped before `at`, None when the series starts later."""
earlier: Final = tuple(sample.value for sample in self.samples if sample.at < at)
return earlier[-1] if earlier else None
class Recorder(Generic[T]):
"""What a sampler thread appends to; `series` is a frozen copy for the observation."""
def __init__(self) -> None:
self._samples: list[Sample[T]] = []
def record(self, value: T) -> None:
self._samples.append(Sample(at=time.time(), value=value))
@property
def series(self) -> Series[T]:
return Series(samples=tuple(self._samples))
def __len__(self) -> int:
return len(self._samples)