"""An SNMP poll collects on schedule while the database hangs, and what it held is stored oldest first, each at its own time, once it answers (#5995).""" import asyncio from contextlib import asynccontextmanager from types import SimpleNamespace as NS import pytest import plugins.snmp.scheduler as scheduler import plugins.snmp.service as service from plugins.snmp.poller import Credentials, PollResult def _device(presets=("system",)): return NS(id="d1", name="nas", address="10.0.0.5", port=161, version="2c", presets=list(presets), interfaces="", custom=None, tables={}, tables_at=None, last_error=None, texts={}) @asynccontextmanager async def _hung_session(): await asyncio.Event().wait() # never answers: the device list comes from memory yield None def test_polls_go_on_while_the_database_hangs_and_are_stored_in_order_after(monkeypatch): async def main(): stalled = asyncio.Event() @asynccontextmanager async def hung_session(): await stalled.wait() # the storage under Postgres has stalled yield None app = NS(db_sessionmaker=hung_session) gate, stored = asyncio.Event(), [] async def store(app_, rounds): await gate.wait() stored.extend(rounds) uptimes = iter([100.0, 200.0]) async def poll(address, port, creds, oids): return PollResult(readings={"uptime": next(uptimes)}) monkeypatch.setattr(scheduler, "DEVICES", [_device()]) monkeypatch.setattr(scheduler, "DEVICES_READ_S", 0.05) monkeypatch.setattr(scheduler, "STORE_WAIT_S", 0.05) monkeypatch.setattr(scheduler, "_store", store) monkeypatch.setattr(service, "credentials", lambda d: Credentials(community="public")) scheduler.RATES = None await scheduler._do_poll(app, poll=poll) # the device list is the last one read await asyncio.sleep(0.01) await scheduler._do_poll(app, poll=poll) # the first store hasn't returned: polled anyway gate.set() await app._holdings["SNMP poll"].flush(1.0) return stored rounds = asyncio.run(main()) assert [[(p.name, p.values) for p in r] for r in rounds] == [ [("nas", [("uptime", 100.0)])], [("nas", [("uptime", 200.0)])]] assert rounds[0][0].at < rounds[1][0].at # each stored at the time it was collected def test_a_reading_worked_out_from_others_is_asked_for_by_its_inputs_and_kept_alone(monkeypatch): """#5997: CPU waiting on disk from the tick counters' rates, and available memory from three gauges; the inputs are neither stored nor named missing.""" mem = {"memory_free_kb": 1000.0, "memory_buffers_kb": 200.0, "memory_cached_kb": 3000.0} answers = iter([ {"cpu_ticks_user": 1000.0, "cpu_ticks_nice": 0.0, "cpu_ticks_system": 500.0, "cpu_ticks_idle": 8000.0, "cpu_ticks_wait": 500.0} | mem, {"cpu_ticks_user": 1100.0, "cpu_ticks_nice": 0.0, "cpu_ticks_system": 550.0, "cpu_ticks_idle": 8800.0, "cpu_ticks_wait": 550.0} | mem, {"cpu_ticks_user": 1200.0, "cpu_ticks_nice": 0.0, "cpu_ticks_system": 600.0, "cpu_ticks_idle": 9600.0} | mem]) # this time it doesn't answer the wait counter asked, stored = [], [] async def poll(address, port, creds, oids): asked.append(dict((k, o) for o, k in oids)) got = next(answers) return PollResult(readings=got, missing=[k for _, k in oids if k not in got]) async def store(app_, rounds): stored.extend(rounds) async def main(): monkeypatch.setattr(scheduler, "DEVICES", [_device(["linux"])]) monkeypatch.setattr(scheduler, "DEVICES_READ_S", 0.05) monkeypatch.setattr(scheduler, "_store", store) monkeypatch.setattr(service, "credentials", lambda d: Credentials(community="public")) scheduler.RATES = None app = NS(db_sessionmaker=_hung_session) for _ in range(3): await scheduler._do_poll(app, poll=poll, walk=_no_tables) await asyncio.sleep(0.01) asyncio.run(main()) assert asked[0]["cpu_ticks_wait"] == "1.3.6.1.4.1.2021.11.54.0" and "cpu_iowait" not in asked[0] assert "" not in asked[0].values() first, second, third = (dict(r[0].values) for r in stored) assert first == {"memory_available": 4200.0} # a counter needs two samples assert second == {"cpu_iowait": pytest.approx(5.0), "memory_available": 4200.0} # 50 of 1,000 ticks waited assert third == {"memory_available": 4200.0} assert "cpu_iowait" in stored[2][0].missing and not any(k.startswith("cpu_ticks") for k in stored[2][0].missing) async def _no_tables(address, port, creds, tables): from plugins.snmp.poller import TableScan return TableScan(rows={})