These benchmarks run automatically with pytest-benchmark in tests/performance/.
They establish a baseline for detecting regressions.
| Function | Input | Median | P95 | Notes |
|---|---|---|---|---|
validate_image_content |
JPEG (1KB) | 0.6 µs | 1.0 µs | Magic-byte scan, O(1) |
validate_image_content |
PNG (1KB) | 0.8 µs | 1.3 µs | Magic-byte scan |
validate_image |
.jpg + 1KB | 5.1 µs | 7.5 µs | Extension + size check |
sanitize_filename |
"normal.jpg" | 6.1 µs | 9.2 µs | Regex + truncation |
sanitize_filename |
"../special!@#.txt" | 8.1 µs | 12 µs | More replacements |
sanitize_filename |
200 chars | 8.0 µs | 13 µs | Truncation to 128 |
frame_to_base64 |
100×100 RGB | 74 µs | 85 µs | JPEG encode + base64 |
frame_to_base64 |
640×480→320px | 535 µs | 610 µs | Downscale + encode |
draw_boxes |
0 detections | 0.5 µs | 1.2 µs | Early return |
draw_boxes |
10 detections | 316 µs | 370 µs | Box drawing + labels |
Total suite: 10 benchmarks in ~9.5s.
- No automatic benchmark because it requires the actual model (ultralytics + PyTorch)
- Estimated: 50–200ms per image (CPU, YOLOv8n)
- Variable: depends on image size, model (n/s/m/l/x), CPU vs GPU
- How to profile:
python -c " from src.detector import Detector from src.profiling import timed_context d = Detector() with timed_context('yolo_inference', warn_threshold=1.0): r = d.detect('test.jpg') print(f'Detections: {len(r)}') "
- Per frame: extraction → YOLO inference → drawing → base64
- Dominant: YOLO inference (50-200ms/frame)
frame_to_base64at 640×480: ~535 µs (negligible vs YOLO)draw_boxeswith 10 detections: ~316 µs (negligible)- Scaling: linear with frame count (1 FPS by default)
- Sequential iteration over N images
- Dominant: N × YOLO inference
- No current parallelization — could benefit from
asyncio.gatherorThreadPoolExecutor
| Endpoint | No load | Under load | Bottleneck |
|---|---|---|---|
GET /health |
< 5ms | < 10ms | None |
GET / |
< 5ms | < 10ms | Static file read |
POST /v1/upload |
100-500ms | 500ms-2s | YOLO inference (dominant) |
POST /v1/upload/batch |
N × 100-500ms | variable | Sequential pipeline |
POST /v1/upload/video |
F × 100-500ms | variable | YOLO per frame |
POST /v1/detect/frame |
100-500ms | 500ms-2s | YOLO + decode |
GET /v1/results/{id} |
< 10ms | < 20ms | Dictionary lookup |
@timed(warn_threshold=5.0)
def detect(self, image_path: str) -> list[dict]:
...The decorator automatically logs to logging.INFO (or WARNING if it exceeds the threshold):
Profile [Detector.detect]: 0.3452s
Profile [BatchPipeline.process]: 2.1341s
It is applied on:
detector.py:Detector.detect,Detector.detect_arraypipeline.py:BatchPipeline.processutils.py:process_video_frames
from src.profiling import timed_context
with timed_context("custom_operation"):
# code to measure
passThe MetricsMiddleware collects per-route latency and exposes it at /metrics:
cortex_vision_request_latency_seconds{route="/v1/upload",quantile="avg"} 0.3452
cortex_vision_request_latency_seconds{route="/v1/upload",quantile="p99"} 1.2345
- Parallelize batch processing: Use
asyncio.gatherorThreadPoolExecutorto process images in parallel - Cache more aggressively:
DetectionCachecovers repeated detections, but there's no cache for video frames - Compress/resize before YOLO: Reduce input resolution to speed up inference
- Video streaming: For long videos, consider async processing with WebSockets
- GPU acceleration: YOLOv8 with CUDA reduces inference from 50-200ms to 5-20ms
# Full benchmarks
API_KEY=test-key pytest tests/performance/ --benchmark-only --benchmark-json=.benchmarks/latest.json
# Compare with baseline (detects regressions)
API_KEY=test-key pytest tests/performance/ --benchmark-compare=.benchmarks/latest.json
# Manual endpoint profiling
curl -w "\n⏱️ Time: %{time_total}s\n" -X POST \
-H "X-API-Key: $API_KEY" \
-F "file=@test.jpg" \
http://localhost:8000/v1/upload