From ff9cde7f9c51aa3eb8b8aed4a7b24648a33dd97a Mon Sep 17 00:00:00 2001 From: stumpylog <797416+stumpylog@users.noreply.github.com> Date: Thu, 30 Jul 2026 07:49:05 -0700 Subject: [PATCH] feat(benchmark): add timed API endpoint scenarios --- src/paperless_benchmark/endpoints.py | 80 ++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 src/paperless_benchmark/endpoints.py diff --git a/src/paperless_benchmark/endpoints.py b/src/paperless_benchmark/endpoints.py new file mode 100644 index 000000000..d6644f11d --- /dev/null +++ b/src/paperless_benchmark/endpoints.py @@ -0,0 +1,80 @@ +# src/paperless_benchmark/endpoints.py +from __future__ import annotations + +import statistics +import time +from dataclasses import dataclass +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from django.contrib.auth.models import User + +ENDPOINTS: tuple[tuple[str, str], ...] = ( + ("documents_default", "/api/documents/"), + ("documents_page50", "/api/documents/?page_size=50"), + ("tags_all", "/api/tags/?page_size=100000"), +) + + +@dataclass(frozen=True, slots=True) +class EndpointTiming: + user_label: str + endpoint_name: str + query_count: int + min_ms: float + median_ms: float + max_ms: float + + +def _timed_requests(client, url: str, n: int) -> list[float]: + times = [] + for _ in range(n): + t0 = time.perf_counter() + resp = client.get(url) + t1 = time.perf_counter() + if resp.status_code != 200: + raise RuntimeError( + f"GET {url} -> {resp.status_code}: {resp.content[:300]!r}", + ) + times.append(t1 - t0) + return times + + +def _query_count(client, url: str) -> int: + from django.db import connection + from django.test.utils import CaptureQueriesContext + + with CaptureQueriesContext(connection) as ctx: + resp = client.get(url) + if resp.status_code != 200: + raise RuntimeError(f"GET {url} -> {resp.status_code}: {resp.content[:300]!r}") + return len(ctx.captured_queries) + + +def run_endpoint_benchmarks( + *, + perf_target: User, + perf_admin: User, + repeat: int, +) -> list[EndpointTiming]: + from rest_framework.test import APIClient + + results: list[EndpointTiming] = [] + for user_label, user in (("target", perf_target), ("admin", perf_admin)): + client = APIClient() + client.force_authenticate(user=user) + for name, url in ENDPOINTS: + client.get(url) # warm-up request, not counted + qcount = _query_count(client, url) + times_ms = [t * 1000 for t in _timed_requests(client, url, repeat)] + results.append( + EndpointTiming( + user_label=user_label, + endpoint_name=name, + query_count=qcount, + min_ms=min(times_ms), + median_ms=statistics.median(times_ms), + max_ms=max(times_ms), + ), + ) + return results