Files
FabledSteward/tests/integration/test_host_senders.py
T
bvandeusenandClaude Opus 5.5 403025e201
CI / lint (push) Successful in 3s
CI / unit (push) Successful in 1m30s
CI / integration (push) Successful in 1m40s
CI / publish (push) Successful in 20s
fix(traffic): find a host's senders in the latest-value table, not by a prefix scan of every metric (#6115)
get_host_senders on VDNT-Docker02 ran past 120 s: a prefix LIKE on
plugin_metrics.resource_name can't use its index (the column's collation
isn't C), so each read went through the whole table, and the host page's
Who's sending panel repeats it on every refresh. The containers and NFS
mounts under a host are now named from plugin_metric_latest, and their
series read by resource IN (...) on the series index.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:59:50 -04:00

65 lines
3.0 KiB
Python

"""Integration (#6115): a host's traffic put down to its containers and NFS
mounts, read from plugin_metrics in shared time buckets on Postgres."""
from __future__ import annotations
import asyncio
import os
import uuid
from datetime import datetime, timedelta, timezone
import pytest
pytestmark = pytest.mark.integration
@pytest.fixture
def app():
if not os.environ.get("STEWARD_DATABASE_URL"):
pytest.skip("needs Postgres")
from steward.app import create_app
return create_app(testing=False)
def test_the_host_s_peak_is_put_down_to_its_senders(app):
from steward.models.metrics import PluginMetric, PluginMetricLatest
from plugins.host_agent.senders import senders
now = datetime.now(timezone.utc)
host = "senders-host-" + uuid.uuid4().hex[:8]
peak, later = now - timedelta(minutes=30), now - timedelta(minutes=10)
task = "a" * 25
def m(source, resource, metric, value, at):
return PluginMetric(source_module=source, resource_name=resource, metric_name=metric,
value=value, recorded_at=at)
async def _go():
async with app.db_sessionmaker() as s:
async with s.begin():
s.add_all([
m("host_agent", host, "net_tx_bps", 1000.0, peak),
m("host_agent", host, "net_tx_bps", 100.0, later),
# one service's two tasks add up; another host's don't count
m("docker", f"{host}/web.1.{task}", "net_tx_bps", 600.0, peak),
m("docker", f"{host}/web.2.{task}", "net_tx_bps", 200.0, peak),
m("docker", f"{host}/web.1.{task}", "net_tx_bps", 50.0, later),
m("docker", f"{host}x/web.1.{task}", "net_tx_bps", 9999.0, peak),
m("host_agent", f"{host}:/nfs/data", "nfs_tx_bps", 100.0, peak),
m("host_agent", f"{host}:net:eno1", "net_tx_bps", 1000.0, peak),
])
# the latest-value table names the resources under the host
for src, res, metric in (("docker", f"{host}/web.1.{task}", "net_tx_bps"),
("docker", f"{host}/web.2.{task}", "net_tx_bps"),
("docker", f"{host}x/web.1.{task}", "net_tx_bps"),
("host_agent", f"{host}:/nfs/data", "nfs_tx_bps")):
await s.merge(PluginMetricLatest(source_module=src, resource_name=res,
metric_name=metric, value=1.0, recorded_at=peak))
async with app.db_sessionmaker() as s:
return await senders(s, host, now - timedelta(hours=1))
got = asyncio.run(_go())
assert got["host"]["peak_bps"] == 1000.0 and got["bucket_s"] == 60
assert [(r["kind"], r["name"], r["at_host_peak_bps"]) for r in got["senders"]] == [
("container", "web", 800.0), ("nfs", "/nfs/data", 100.0)]
assert got["unaccounted_at_peak_bps"] == 100.0