fix(alerts): a rule's page opens at once; its pickers read the latest values (#5473)
#5473 "Opening an alert rule takes up to 15 s: the Resource picker scans every raw host-agent sample". The access log showed the edit page at 6-15 s (one load gave up) while /alerts/ took 0.2 s. resources_for ran SELECT DISTINCT over plugin_metrics, and Postgres has no skip scan, so it read every host-agent sample; every rule page, source swap, rule save and MCP rule call paid it. resources_for and metrics_for now read plugin_metric_latest, one row per series, which lists the same names. A test fails if either reads plugin_metrics again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@ from sqlalchemy import func, or_, select
|
||||
from steward.models.alerts import (
|
||||
AlertEvent, AlertOperator, AlertRule, AlertState, AlertStateEnum, MaintenanceWindow,
|
||||
)
|
||||
from steward.models.metrics import PluginMetric, PluginMetricLatest
|
||||
from steward.models.metrics import PluginMetricLatest
|
||||
from steward.models.monitors import MONITOR_TYPE_VALUES, Monitor
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -338,14 +338,17 @@ async def list_maintenance_windows(db) -> list[MaintenanceWindow]:
|
||||
async def resources_for(db, source_module: str) -> list[str]:
|
||||
"""Sorted distinct resource names for a source module.
|
||||
|
||||
Read from the latest-value table, one row per series: a DISTINCT over
|
||||
plugin_metrics reads every raw sample (Postgres has no skip scan), which
|
||||
held the rule page for up to 15 s on host_agent (#5208's trap again).
|
||||
|
||||
For monitor types (icmp/tcp/dns/http), supplements with monitor names so
|
||||
the list is populated even before any metrics have been recorded.
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(PluginMetric.resource_name)
|
||||
.where(PluginMetric.source_module == source_module)
|
||||
select(PluginMetricLatest.resource_name)
|
||||
.where(PluginMetricLatest.source_module == source_module)
|
||||
.distinct()
|
||||
.order_by(PluginMetric.resource_name)
|
||||
)
|
||||
resources: set[str] = {r for (r,) in result}
|
||||
if source_module in MONITOR_TYPE_VALUES:
|
||||
@@ -361,10 +364,10 @@ async def metrics_for(db, source_module: str) -> list[str]:
|
||||
metrics = metric_catalog().get(source_module, [])
|
||||
if source_module and not metrics:
|
||||
result = await db.execute(
|
||||
select(PluginMetric.metric_name)
|
||||
.where(PluginMetric.source_module == source_module)
|
||||
select(PluginMetricLatest.metric_name)
|
||||
.where(PluginMetricLatest.source_module == source_module)
|
||||
.distinct()
|
||||
.order_by(PluginMetric.metric_name))
|
||||
.order_by(PluginMetricLatest.metric_name))
|
||||
metrics = [r for (r,) in result]
|
||||
return metrics
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
"""The rule form's Resource and Metric pickers read the latest-value table,
|
||||
one row per series, never the raw history: a DISTINCT over plugin_metrics
|
||||
reads every sample and held the rule page for up to 15 s on host_agent."""
|
||||
from types import SimpleNamespace
|
||||
|
||||
from steward.alerts import service as alert_svc
|
||||
|
||||
|
||||
async def _sql(fn, source) -> list[str]:
|
||||
"""The SQL `fn` sends for `source`, against a session that returns no rows."""
|
||||
sent: list[str] = []
|
||||
|
||||
async def execute(stmt):
|
||||
sent.append(str(stmt.compile(compile_kwargs={"literal_binds": True})))
|
||||
return []
|
||||
await fn(SimpleNamespace(execute=execute), source)
|
||||
return sent
|
||||
|
||||
|
||||
async def test_resources_come_from_the_latest_value_table():
|
||||
(sql,) = await _sql(alert_svc.resources_for, "host_agent")
|
||||
assert "FROM plugin_metric_latest" in sql and "plugin_metrics" not in sql
|
||||
|
||||
|
||||
async def test_a_dynamic_source_s_metrics_come_from_the_latest_value_table():
|
||||
(sql,) = await _sql(alert_svc.metrics_for, "snmp")
|
||||
assert "FROM plugin_metric_latest" in sql and "plugin_metrics" not in sql
|
||||
Reference in New Issue
Block a user