Skip to content

Commit dc93187

Browse files
patrickelectricjoaomariolago
authored andcommitted
core: services: disk_usage: Add dynamic disk speed test
Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>
1 parent 1e61dc6 commit dc93187

File tree

1 file changed

+64
-14
lines changed

1 file changed

+64
-14
lines changed

core/services/disk_usage/main.py

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#! /usr/bin/env python3
22

33
import asyncio
4+
import json
45
import logging
56
import os
67
import re
@@ -9,12 +10,14 @@
910
import time
1011
from functools import wraps
1112
from pathlib import Path
12-
from typing import Any, Dict, List, Optional
13+
from typing import Any, AsyncGenerator, Dict, List, Optional
1314

1415
from commonwealth.utils.apis import GenericErrorHandlingRoute, PrettyJSONResponse
1516
from commonwealth.utils.logs import InterceptHandler, init_logger
1617
from commonwealth.utils.sentry_config import init_sentry_async
18+
from commonwealth.utils.streaming import streamer
1719
from fastapi import APIRouter, FastAPI, HTTPException, Query, status
20+
from fastapi.responses import StreamingResponse
1821
from fastapi_versioning import VersionedFastAPI, versioned_api_route
1922
from loguru import logger
2023
from pydantic import BaseModel, Field
@@ -56,6 +59,13 @@ class DiskSpeedResult(BaseModel):
5659
error: Optional[str] = Field(None, description="Error message if test failed")
5760

5861

62+
class DiskSpeedTestPoint(BaseModel):
63+
size_mb: int = Field(..., description="Test size in MB")
64+
write_speed: Optional[float] = Field(None, description="Write speed in MiB/s")
65+
read_speed: Optional[float] = Field(None, description="Read speed in MiB/s")
66+
total_tests: Optional[int] = Field(None, description="Total number of tests in the sequence")
67+
68+
5969
DiskNode.update_forward_refs()
6070

6171

@@ -304,19 +314,8 @@ def parse_disktest_speed(output: str) -> tuple[Optional[float], Optional[float],
304314

305315

306316
# pylint: disable=too-many-locals
307-
@disk_router.get(
308-
"/speed",
309-
response_model=DiskSpeedResult,
310-
summary="Run disk speed test using disktest binary.",
311-
)
312-
@to_http_exception
313-
async def disk_speed(
314-
size_bytes: int = Query(
315-
1024 * 1024 * 1024,
316-
ge=1024 * 1024,
317-
description="Number of bytes to test (default 1 GiB).",
318-
),
319-
) -> DiskSpeedResult:
317+
async def run_single_speed_test(size_bytes: int) -> DiskSpeedResult:
318+
"""Run a single disk speed test and return the result."""
320319
disktest_binary = "disktest"
321320
temp_file_path: Optional[Path] = None
322321

@@ -408,6 +407,57 @@ async def disk_speed(
408407
logger.warning(f"Failed to clean up temporary file {temp_file_path}: {e}")
409408

410409

410+
@disk_router.get(
411+
"/speed",
412+
response_model=DiskSpeedResult,
413+
summary="Run disk speed test using disktest binary.",
414+
)
415+
@to_http_exception
416+
async def disk_speed(
417+
size_bytes: int = Query(
418+
1024 * 1024 * 1024,
419+
ge=1024 * 1024,
420+
description="Number of bytes to test (default 1 GiB).",
421+
),
422+
) -> DiskSpeedResult:
423+
return await run_single_speed_test(size_bytes)
424+
425+
426+
async def multi_size_speed_test_generator() -> AsyncGenerator[str, None]:
427+
"""Generator that runs speed tests at multiple sizes and yields JSON results."""
428+
test_sizes_mb = [10, 50, 100, 200]
429+
total_tests = len(test_sizes_mb)
430+
431+
for size_mb in test_sizes_mb:
432+
size_bytes = size_mb * 1024 * 1024
433+
result = await run_single_speed_test(size_bytes)
434+
435+
point = DiskSpeedTestPoint(
436+
size_mb=size_mb,
437+
write_speed=result.write_speed_mbps,
438+
read_speed=result.read_speed_mbps,
439+
total_tests=total_tests,
440+
)
441+
yield json.dumps(point.dict())
442+
443+
444+
@disk_router.get(
445+
"/speed/stream",
446+
summary="Run multi-size disk speed test with streaming results.",
447+
)
448+
async def disk_speed_stream() -> StreamingResponse:
449+
return StreamingResponse(
450+
streamer(multi_size_speed_test_generator(), heartbeats=1.0),
451+
media_type="application/x-ndjson",
452+
headers={
453+
"Content-Type": "application/x-ndjson",
454+
"Cache-Control": "no-cache",
455+
"Connection": "keep-alive",
456+
"X-Accel-Buffering": "no",
457+
},
458+
)
459+
460+
411461
fast_api_app = FastAPI(
412462
title="Disk Usage API",
413463
description="Inspect disk usage and delete files using du.",

0 commit comments

Comments
 (0)