diff --git a/docs/OFFLOADING_DECISION_EVENTS.md b/docs/OFFLOADING_DECISION_EVENTS.md index e53ed14..af2669b 100644 --- a/docs/OFFLOADING_DECISION_EVENTS.md +++ b/docs/OFFLOADING_DECISION_EVENTS.md @@ -111,6 +111,7 @@ python scripts/analysis/offloading_decision_summary.py \ The summary includes selected layer, strategy, estimated total milliseconds, observed total milliseconds, and error milliseconds. -> Note: with segment splitting enabled the base `offloading_decisions.csv` is not -> written. Point the script at a specific segment, or disable splitting for a -> single-file run. +The script reads a whole run via `iter_run_offloading_decisions`, so pointing +it at the base name (e.g. `offloading_decisions.csv`) covers every split +segment for that run, even though the base file itself is never written once +splitting is enabled. diff --git a/scripts/analysis/offloading_decision_summary.py b/scripts/analysis/offloading_decision_summary.py index d19ee8b..48f534f 100644 --- a/scripts/analysis/offloading_decision_summary.py +++ b/scripts/analysis/offloading_decision_summary.py @@ -7,7 +7,7 @@ from server.commons import EvaluationFiles from server.telemetry.offloading_decisions import ( - iter_offloading_decisions, + iter_run_offloading_decisions, summarize_prediction_error, ) @@ -27,7 +27,16 @@ def summarize_file(path: str | Path) -> list[dict]: - return [summarize_prediction_error(event) for event in iter_offloading_decisions(path)] + """Summarize one run's offloading decisions, across all split segments. + + ``path`` may point at a single legacy file or at any segment's base name + (e.g. ``offloading_decisions.csv``); ``iter_run_offloading_decisions`` + discovers and reads every ``.segNNNN_*`` segment for that run, in order. + """ + return [ + summarize_prediction_error(event) + for event in iter_run_offloading_decisions(path) + ] def main(argv: list[str] | None = None) -> int: @@ -38,7 +47,10 @@ def main(argv: list[str] | None = None) -> int: "input", nargs="?", default=EvaluationFiles.offloading_decisions_file_path, - help="Path to offloading_decisions.csv or a legacy JSONL file.", + help=( + "Path to offloading_decisions.csv (or a legacy JSONL file). " + "All split segments for the run are read automatically." + ), ) parser.add_argument( "--output", diff --git a/tests/unit/test_offloading_decision_summary.py b/tests/unit/test_offloading_decision_summary.py index ef6c3df..1fab42a 100644 --- a/tests/unit/test_offloading_decision_summary.py +++ b/tests/unit/test_offloading_decision_summary.py @@ -42,3 +42,24 @@ def test_offloading_decision_summary_reads_csv(tmp_path): "error_ms": 5.0, } ] + + +def test_offloading_decision_summary_reads_all_split_segments(tmp_path): + base = tmp_path / "offloading_decisions.csv" + for i in range(3): + event = build_offloading_decision_event( + device_id="device-1", + model_dir="model", + request_id=f"REQ{i}", + selected_layer=i, + selection_reason="lowest_estimated_cost", + avg_speed_bytes_per_second=1, + ) + append_offloading_decision(base, event, max_rows=1) + + # Splitting keeps segments and never writes the base file directly. + assert not base.exists() + + rows = summarize_file(base) + + assert [row["request_id"] for row in rows] == ["REQ0", "REQ1", "REQ2"]