mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 22:27:01 +01:00
Lifts the atop subprocess machinery into _usage_report_atop.py: running atop, streaming its parseable output, building/caching the atop_agg C helper, and reading its TSV. What stays is record parsing, epoch windows and per-day merging. Verified by the 100%-coverage tests from the previous commit. An AST comparison of all 16 functions against HEAD reports 16 identical, 0 mismatches. _usage_report_render.py imported _run from the parsing module; it now imports from the module that owns it. That is the same re-export barrel the last split shipped and had to fix in a follow-up - caught in the same commit this time. usage_report.py needed no edit at all. Both modules hold 100% line+branch (94/28 and 130/34, 0 missed). 179 tests before and after; the five new test files collect 8+6+4+14+5 = 37, which is last commit's 36 plus the one re-homed out of test_usage_report_modes.py, where it referenced a constant that had moved out from under it. The two test files added last commit were themselves over cap (373, 272) so they are split five ways here - the DropZone lesson from earlier today, where a lint fix silently added an over-cap file. Ran the program: a real report from today's logs, through the native atop_agg path, which is precisely the code this commit moved. Over-cap 39 -> 38. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
112 lines
3.6 KiB
Python
112 lines
3.6 KiB
Python
"""Tests for aggregate_atop, usage_report's pure-Python fallback parser used
|
|
when the native atop_agg helper is unavailable.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING
|
|
|
|
import _usage_report_parsing as parsing
|
|
from _usage_report_types import _Progress
|
|
|
|
if TYPE_CHECKING:
|
|
import pytest
|
|
|
|
|
|
def _progress() -> _Progress:
|
|
"""A progress reporter that never draws, so stderr stays clean."""
|
|
return _Progress(enabled=False, total_stages=1)
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# aggregate_atop (pure-Python fallback)
|
|
# --------------------------------------------------------------------------- #
|
|
def test_aggregate_atop_parses_prc_and_prm(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
"""With no native helper, the Python parser folds PRC and PRM records."""
|
|
log = tmp_path / "atop.log"
|
|
log.write_bytes(b"x" * 100)
|
|
lines = [
|
|
"# a comment",
|
|
"RESET",
|
|
"SEP",
|
|
"",
|
|
"PRC host 1000 d t 600 42 (python) S 100 500 20 a b c",
|
|
"PRC host 1600 d t 600 42 (python) S 100 900 40 a b c",
|
|
"PRM host 1000 d t 600 42 (python) S 4096 9999 2048 a b c",
|
|
"PRC short row",
|
|
]
|
|
monkeypatch.setattr(parsing, "_atop_agg_binary", lambda: None)
|
|
monkeypatch.setattr(parsing, "_iter_atop_lines", lambda *_a, **_k: iter(lines))
|
|
|
|
agg, window = parsing.aggregate_atop(log, _progress())
|
|
|
|
# utime+stime deltas: (900+40) - (500+20) = 420.
|
|
assert agg["python"].cpu_ticks == 420
|
|
assert agg["python"].peak_rss_kb == 2048
|
|
assert window.distinct_samples == 2
|
|
assert window.interval_s == 600
|
|
|
|
|
|
def test_aggregate_atop_ignores_unparseable_epoch(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
"""A PRC row with a non-numeric epoch still parses, minus the timestamp."""
|
|
log = tmp_path / "atop.log"
|
|
log.write_bytes(b"x" * 10)
|
|
lines = ["PRC host notanepoch d t 600 42 (python) S 100 500 20 a b c"]
|
|
monkeypatch.setattr(parsing, "_atop_agg_binary", lambda: None)
|
|
monkeypatch.setattr(parsing, "_iter_atop_lines", lambda *_a, **_k: iter(lines))
|
|
|
|
agg, window = parsing.aggregate_atop(log, _progress())
|
|
|
|
assert agg["python"].pid_count == 1
|
|
assert window.distinct_samples == 0
|
|
|
|
|
|
def test_aggregate_atop_delegates_to_native_when_available(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
"""When the C helper builds, the native path is used instead."""
|
|
log = tmp_path / "atop.log"
|
|
log.write_bytes(b"x")
|
|
monkeypatch.setattr(parsing, "_atop_agg_binary", lambda: Path("/bin/atop_agg"))
|
|
called: list[Path] = []
|
|
|
|
def fake_native(
|
|
log_arg: Path,
|
|
_progress_arg: _Progress,
|
|
binary: Path,
|
|
_begin: str | None = None,
|
|
_end: str | None = None,
|
|
) -> tuple[dict[str, object], object]:
|
|
called.append(binary)
|
|
return {}, parsing._Window()
|
|
|
|
monkeypatch.setattr(parsing, "_aggregate_atop_native", fake_native)
|
|
|
|
parsing.aggregate_atop(log, _progress())
|
|
|
|
assert called == [Path("/bin/atop_agg")]
|
|
|
|
|
|
def test_aggregate_atop_skips_whitespace_only_rows(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
"""A row of only whitespace splits to nothing and is skipped."""
|
|
log = tmp_path / "atop.log"
|
|
log.write_bytes(b"x")
|
|
monkeypatch.setattr(parsing, "_atop_agg_binary", lambda: None)
|
|
monkeypatch.setattr(parsing, "_iter_atop_lines", lambda *_a, **_k: iter([" "]))
|
|
|
|
agg, window = parsing.aggregate_atop(log, _progress())
|
|
|
|
assert agg == {}
|
|
assert window.distinct_samples == 0
|