core: services: commander: Delete gz logs first - #4356
Conversation
Streamed unlink of every rotated .gz is too slow. Fix bluerobotics#3588 Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>
Automated PR Review0. Summary
Adds a fast-path bulk delete of every 1. Correctness & Implementation Bugs
5. UI / UX
6. Code Quality & Style
8. Documentation
Generated by PR Review Bot. This is advisory, a human reviewer must still approve. |
|
Since #4123, the current speed is around 1 GB/s; did you measure what we are gaining here? Looking at the code, it seems the wrong abstraction: we have a stream, and this patch moves the heavy work outside of it, removing feedback. |
|
Just to be sure how did you test ? how many files and what are the size of the files ? |
|
I previously measured on a single system that had a lot of non-gzipped extension logs from my debug builds, so it was a bit above 1GB/s because of that skew. I now replicated a per-service averaged distribution from 6 system logs from different clients, then scaled its gz distribution to fill ~16GB. I remeasured it, and deletion is at 0.35GB/s on 1.4-dev. Master barely has logs, though. The averaged distributions: service,n_files,n_log,n_gz,total_bytes,file_median,file_p90
linux2rest,67,1,66,73778733,662006,1409084
mavlink-camera-manager,67,1,66,5940545,25255,263037
beacon,38,1,37,2761092,26908,321340
wifi-manager,28,1,27,2590191,32855,204937
bag-of-holding,29,1,28,1711641,14234,157940
version-chooser,16,1,15,1508972,12786,156380
ping,46,1,45,1241154,27820,148025
cable-guy,35,1,34,1046160,4752,108954
helper,27,1,26,691286,9527,33219
ardupilot-manager,30,1,29,431237,5295,88333
major_tom,6,1,5,112401,5922,53842
blueos_startup_update,24,1,23,109652,3435,4160
commander,66,1,65,92852,1166,3184
kraken,28,1,27,86463,1663,7430
bridget,72,1,71,63833,709,804
nmea-injector,72,1,71,59706,714,911
log-zipper,18,1,17,55071,1202,2801
telemetry,6,1,5,28518,1271,5646
bootstrap,32,1,31,23087,670,745
pardal,72,1,71,23044,330,486 |
|
@joaoantoniocardoso yeah, this deletes 32GB in less than 1 second. |
Awesome! Can that step run inside the stream? Since gz won't ever be open, we may just skip the lsof forgas in the stream task? |
the delete stream is generic, the code is specific for the logs. |
Yes, and we should keep it generic. Perhaps: diff --git a/core/services/commander/main.py b/core/services/commander/main.py
index 0c157992c..636696849 100755
--- a/core/services/commander/main.py
+++ b/core/services/commander/main.py
@@ -12,7 +12,12 @@ from typing import Any, AsyncGenerator, Dict
import appdirs
from commonwealth.utils.apis import GenericErrorHandlingRoute
from commonwealth.utils.commands import run_command
-from commonwealth.utils.general import delete_everything, delete_everything_stream
+from commonwealth.utils.general import (
+ bulk_find_delete_stream,
+ delete_everything,
+ delete_everything_stream,
+ open_files_under,
+)
from commonwealth.utils.logs import InterceptHandler, init_logger
from commonwealth.utils.sentry_config import init_sentry_async
from commonwealth.utils.streaming import streamer
@@ -56,13 +61,13 @@ def check_what_i_am_doing(i_know_what_i_am_doing: bool = False) -> None:
)
-def deletion_stream_response(path: Path) -> StreamingResponse:
+def deletion_stream_response(stream: AsyncGenerator[dict[str, Any], None]) -> StreamingResponse:
async def generate() -> AsyncGenerator[str, None]:
try:
- async for info in delete_everything_stream(path):
+ async for info in stream:
yield json.dumps(info)
except Exception as error:
- logger.error(f"Error during deletion stream of {path}: {error}")
+ logger.error(f"Error during deletion stream: {error}")
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(error)) from error
return StreamingResponse(
@@ -77,6 +82,14 @@ def deletion_stream_response(path: Path) -> StreamingResponse:
)
+async def service_log_deletion_stream(root: Path) -> AsyncGenerator[dict[str, Any], None]:
+ open_files = await asyncio.to_thread(open_files_under, root)
+ async for info in bulk_find_delete_stream(root, "*.gz"):
+ yield info
+ async for info in delete_everything_stream(root, open_files=open_files):
+ yield info
+
+
@app.post("/command/host", status_code=status.HTTP_200_OK)
@version(1, 0)
async def command_host(command: str, i_know_what_i_am_doing: bool = False) -> Any:
@@ -213,7 +226,7 @@ async def remove_log_services(i_know_what_i_am_doing: bool = False) -> Any:
async def remove_log_services_stream(i_know_what_i_am_doing: bool = False) -> StreamingResponse:
"""Stream the deletion of log files, providing real-time updates about each file being deleted."""
check_what_i_am_doing(i_know_what_i_am_doing)
- return deletion_stream_response(Path(LOG_FOLDER_PATH))
+ return deletion_stream_response(service_log_deletion_stream(Path(LOG_FOLDER_PATH)))
@app.post("/services/remove_mavlink_log", status_code=status.HTTP_200_OK)
@@ -228,7 +241,7 @@ async def remove_mavlink_log_services(i_know_what_i_am_doing: bool = False) -> A
async def remove_mavlink_log_services_stream(i_know_what_i_am_doing: bool = False) -> StreamingResponse:
"""Stream the deletion of MAVLink log files, providing real-time updates about each file being deleted."""
check_what_i_am_doing(i_know_what_i_am_doing)
- return deletion_stream_response(Path(MAVLINK_LOG_FOLDER_PATH))
+ return deletion_stream_response(delete_everything_stream(Path(MAVLINK_LOG_FOLDER_PATH)))
@app.get("/services/check_log_folder_size", status_code=status.HTTP_200_OK)
|
Streamed unlink of every rotated .gz is too slow.
Fix #3588