litellm/tests/unit/accounting/test_metrics.py
joshua-berri 82e43cd55a
fix(accounting): stop delivery metrics from scanning retained receipts on every flush (#50565)
* fix(accounting): stop delivery metrics from scanning retained receipts on every flush

The backlog query now reads only pending and parked deliveries, so its cost follows the backlog instead
of retained receipt history. Batch count and table size are measured once per check interval per pod,
and the storage and delayed-delivery alerts repeat at most once per interval instead of every flush

Fixes #50475

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(accounting): prove the storage query reruns only when its interval is due

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(accounting): read the check interval on each flush so YAML environment_variables apply

The writer is built at proxy import, before the config's environment_variables load, so reading
the interval there ignored values set in config.yaml

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(router): wait for router accounting instead of sleeping in user-config limit tests

Each request ended with a 0.3s sleep and hoped the router's usage writes had landed, so under CI load a
concurrent request could read counters mid-update. The tests now flush the logging worker and wait for
every task the requests scheduled, and a retired router's counter flush lands before the next router reads

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Joshua Valluru <326636767+joshua-berri@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 18:21:32 -08:00

62 lines
2.2 KiB
Python

import importlib.util
import sys
import pytest
from prometheus_client.metrics import MetricWrapperBase
from litellm.accounting import metrics
from litellm.accounting.metrics import Cadence, record_backlog, record_storage
from litellm.repositories.accounting_delivery_repository import DeliveryBacklog, DeliveryStorage
_BACKLOG = DeliveryBacklog(pending_count=4, parked_count=1, oldest_pending_seconds=-3.0)
_STORAGE = DeliveryStorage(batch_count=7, table_bytes=8192)
def _sample(metric: MetricWrapperBase, suffix: str = "") -> float:
family = metric.collect()[0]
return next(sample.value for sample in family.samples if sample.name == family.name + suffix)
def test_delivery_metrics_publish_backlog_and_clamp_clock_skew() -> None:
instruments = metrics._INSTRUMENTS
assert instruments is not None
batches_before = _sample(instruments.batch_time, "_count")
record_backlog(_BACKLOG, 0.5)
record_storage(_STORAGE)
assert (
_sample(instruments.pending),
_sample(instruments.parked),
_sample(instruments.oldest),
_sample(instruments.batch_count),
_sample(instruments.table_bytes),
) == (4.0, 1.0, 0.0, 7.0, 8192.0)
assert _sample(instruments.batch_time, "_count") == batches_before + 1
def test_delivery_metrics_are_skipped_when_prometheus_client_is_not_installed(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setitem(sys.modules, "prometheus_client", None)
spec = importlib.util.spec_from_file_location("accounting_metrics_without_prometheus", metrics.__file__)
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
monkeypatch.setitem(sys.modules, spec.name, module)
spec.loader.exec_module(module)
assert module.record_backlog(_BACKLOG, 0.5) is None
assert module.record_storage(_STORAGE) is None
def test_cadence_is_due_once_per_interval() -> None:
now = [100.0]
cadence = Cadence(lambda: now[0])
observed = []
for moment in (100.0, 101.0, 999.9, 1000.0, 1000.0, 1899.0, 1900.0):
now[0] = moment
observed.append(cadence.due(900))
assert observed == [True, False, False, True, False, False, True]