import asyncio import time from app.collect import cpu, disks, gpu, mem, power from app.sample import Sample from app.state import HistoryStore def _collect() -> Sample: """Fill a fresh Sample with one synchronous collector pass. Runs in a worker thread (see sampler_loop) because the collectors hit sysfs and psutil. Disk read/write rates are intentionally not set here: they need the delta between two samples, which sampler_loop keeps. Returns: A Sample with cpu, load, memory, swap, GPU, and power fields filled. """ sample = Sample() cpu.fill(sample) mem.fill(sample) gpu.fill(sample) power.fill(sample) return sample async def sampler_loop(store: HistoryStore, sample_interval: float) -> None: """Sample the system into the store every `sample_interval` seconds, forever. Before the first sample it primes `psutil.cpu_percent` (its first call always returns 0) and takes a baseline disk-counter reading, so the first stored sample already carries valid CPU and disk rates. Each loop collects in a worker thread, then computes per-disk byte deltas divided by the elapsed time as the aggregate io_read / io_write rates. Args: store: ring buffer that receives each sample. sample_interval: seconds between samples. """ cpu.prime() prev_disk = disks.counters() prev_t = time.monotonic() while True: await asyncio.sleep(sample_interval) sample = await asyncio.to_thread(_collect) now = time.monotonic() dt = now - prev_t sample.io_read, sample.io_write = disks.rates(prev_disk, dt) prev_disk = disks.counters() prev_t = now store.record(sample)