fix monitor error

This commit is contained in:
larssand
2026-07-06 13:02:10 +02:00
parent 35282bca7f
commit d314c94dee
6 changed files with 30 additions and 24 deletions

View File

@@ -556,6 +556,7 @@ class BaselineStore:
value_retention_days: int = DEFAULT_VALUE_RETENTION_DAYS, value_retention_days: int = DEFAULT_VALUE_RETENTION_DAYS,
max_values_per_field: int = DEFAULT_MAX_VALUES_PER_FIELD, max_values_per_field: int = DEFAULT_MAX_VALUES_PER_FIELD,
vacuum: bool = False, vacuum: bool = False,
include_rows: bool = False,
) -> dict[str, object]: ) -> dict[str, object]:
now = int(time.time()) now = int(time.time())
bucket_cutoff = now - max(1, retention_days) * 86400 bucket_cutoff = now - max(1, retention_days) * 86400
@@ -597,35 +598,39 @@ class BaselineStore:
if vacuum: if vacuum:
with self._connect() as connection: with self._connect() as connection:
connection.execute("vacuum") connection.execute("vacuum")
stats = self.stats() stats = self.stats(include_rows=include_rows)
return {"retention_days": retention_days, "value_retention_days": value_retention_days, "max_values_per_field": max_values_per_field, "vacuum": vacuum, "deleted": deleted, **stats} return {"retention_days": retention_days, "value_retention_days": value_retention_days, "max_values_per_field": max_values_per_field, "vacuum": vacuum, "deleted": deleted, **stats}
def stats(self) -> dict[str, object]: def stats(self, *, include_rows: bool = False) -> dict[str, object]:
with self._connect() as connection: with self._connect() as connection:
tables = (
"seen_events",
"source_buckets",
"source_values",
"profile_seen_events",
"profile_buckets",
"profile_temporal_buckets",
"profile_detector_buckets",
"profile_detector_temporal_buckets",
"profile_values",
)
rows = {table: int(connection.execute(f"select count(*) from {table}").fetchone()[0]) for table in tables}
page_size = int(connection.execute("pragma page_size").fetchone()[0]) page_size = int(connection.execute("pragma page_size").fetchone()[0])
page_count = int(connection.execute("pragma page_count").fetchone()[0]) page_count = int(connection.execute("pragma page_count").fetchone()[0])
freelist_count = int(connection.execute("pragma freelist_count").fetchone()[0]) freelist_count = int(connection.execute("pragma freelist_count").fetchone()[0])
rows = {}
if include_rows:
tables = (
"seen_events",
"source_buckets",
"source_values",
"profile_seen_events",
"profile_buckets",
"profile_temporal_buckets",
"profile_detector_buckets",
"profile_detector_temporal_buckets",
"profile_values",
)
rows = {table: int(connection.execute(f"select count(*) from {table}").fetchone()[0]) for table in tables}
size_bytes = self.path.stat().st_size if self.path.exists() else 0 size_bytes = self.path.stat().st_size if self.path.exists() else 0
return { stats: dict[str, object] = {
"size_bytes": size_bytes, "size_bytes": size_bytes,
"page_size": page_size, "page_size": page_size,
"page_count": page_count, "page_count": page_count,
"freelist_count": freelist_count, "freelist_count": freelist_count,
"reclaimable_bytes": freelist_count * page_size, "reclaimable_bytes": freelist_count * page_size,
"rows": rows,
} }
if include_rows:
stats["rows"] = rows
return stats
def profile_readiness(self, profiles: dict[str, object], *, min_training_days: int = 0) -> list[dict[str, object]]: def profile_readiness(self, profiles: dict[str, object], *, min_training_days: int = 0) -> list[dict[str, object]]:
rows: list[dict[str, object]] = [] rows: list[dict[str, object]] = []

View File

@@ -245,6 +245,7 @@ def baseline_maintenance(args: argparse.Namespace) -> int:
value_retention_days=args.value_retention_days, value_retention_days=args.value_retention_days,
max_values_per_field=args.max_values_per_field, max_values_per_field=args.max_values_per_field,
vacuum=args.vacuum, vacuum=args.vacuum,
include_rows=True,
) )
_print_json(result) _print_json(result)
return 0 return 0

View File

@@ -18,7 +18,7 @@ DEFAULT_CONFIG: dict[str, object] = {
"graylog_max_events_per_stream": 5000, "graylog_max_events_per_stream": 5000,
"graylog_raw_sample_events": 5000, "graylog_raw_sample_events": 5000,
"graylog_mcp_call_timeout_seconds": 8, "graylog_mcp_call_timeout_seconds": 8,
"graylog_mcp_poll_timeout_seconds": 120, "graylog_mcp_poll_timeout_seconds": 240,
"baseline_retention_days": 7, "baseline_retention_days": 7,
"baseline_value_retention_days": 3, "baseline_value_retention_days": 3,
"baseline_max_values_per_field": 500, "baseline_max_values_per_field": 500,

View File

@@ -234,7 +234,7 @@ function mcpCapability(mcp, configuration, pollRunningSeconds=0) {
if (status === 'connected') return {state: 'on', detail: 'connected'}; if (status === 'connected') return {state: 'on', detail: 'connected'};
if (status === 'partial') return {state: 'warn', detail: 'partial'}; if (status === 'partial') return {state: 'warn', detail: 'partial'};
if (status === 'refreshing') { if (status === 'refreshing') {
const pollBudget = Number(mcp.poll_timeout_seconds || configuration.graylog_mcp_poll_timeout_seconds || 120); const pollBudget = Number(mcp.poll_timeout_seconds || configuration.graylog_mcp_poll_timeout_seconds || 240);
if (pollRunningSeconds > pollBudget) return {state: 'warn', detail: `poll over budget ${pollRunningSeconds}s`}; if (pollRunningSeconds > pollBudget) return {state: 'warn', detail: `poll over budget ${pollRunningSeconds}s`};
const previous = String(mcp.previous_status || ''); const previous = String(mcp.previous_status || '');
if (['connected', 'partial'].includes(previous) || Number(mcp.previous_events_fetched || mcp.events_fetched || 0) > 0 || Number(mcp.previous_aggregate_events || mcp.aggregate_events || 0) > 0) { if (['connected', 'partial'].includes(previous) || Number(mcp.previous_events_fetched || mcp.events_fetched || 0) > 0 || Number(mcp.previous_aggregate_events || mcp.aggregate_events || 0) > 0) {
@@ -626,7 +626,7 @@ async function refresh() {
`MCP status: ${esc(mcp.status || 'unknown')}`, `MCP status: ${esc(mcp.status || 'unknown')}`,
mcp.status === 'refreshing' && pollStartedAt ? `MCP poll running: ${esc(pollRunningSeconds)}s` : '', mcp.status === 'refreshing' && pollStartedAt ? `MCP poll running: ${esc(pollRunningSeconds)}s` : '',
pollCompletedAt ? `Last completed MCP poll: ${esc(pollCompletedAge)}s ago` : '', pollCompletedAt ? `Last completed MCP poll: ${esc(pollCompletedAge)}s ago` : '',
mcp.status === 'refreshing' && pollRunningSeconds > Number(mcp.poll_timeout_seconds || configuration.graylog_mcp_poll_timeout_seconds || 120) ? `<span class="sev-high">MCP poll has been refreshing for ${esc(pollRunningSeconds)}s, which is over the configured poll budget. Restart monitor or lower enabled streams/sample size if this keeps happening.</span>` : '', mcp.status === 'refreshing' && pollRunningSeconds > Number(mcp.poll_timeout_seconds || configuration.graylog_mcp_poll_timeout_seconds || 240) ? `<span class="sev-high">MCP poll has been refreshing for ${esc(pollRunningSeconds)}s, which is over the configured poll budget. Restart monitor or lower enabled streams/sample size if this keeps happening.</span>` : '',
mcp.error ? `<span class="sev-high">MCP error: ${esc(mcp.error)}</span>` : '', mcp.error ? `<span class="sev-high">MCP error: ${esc(mcp.error)}</span>` : '',
`Enabled streams: ${esc(enabledStreams.length)}`, `Enabled streams: ${esc(enabledStreams.length)}`,
`MCP fetch mode: ${esc(displayedFetchMode)}${mcp.status === 'refreshing' ? ' (refreshing, showing previous counters)' : ''}`, `MCP fetch mode: ${esc(displayedFetchMode)}${mcp.status === 'refreshing' ? ' (refreshing, showing previous counters)' : ''}`,

View File

@@ -254,7 +254,7 @@ def build_status(
max_events_per_stream = max(1, int(runtime_values.get("graylog_max_events_per_stream", 5000) or 5000)) max_events_per_stream = max(1, int(runtime_values.get("graylog_max_events_per_stream", 5000) or 5000))
raw_sample_events = max(1, int(runtime_values.get("graylog_raw_sample_events", 5000) or 5000)) raw_sample_events = max(1, int(runtime_values.get("graylog_raw_sample_events", 5000) or 5000))
mcp_call_timeout = max(1, int(runtime_values.get("graylog_mcp_call_timeout_seconds", 8) or 8)) mcp_call_timeout = max(1, int(runtime_values.get("graylog_mcp_call_timeout_seconds", 8) or 8))
mcp_poll_timeout = max(60, int(runtime_values.get("graylog_mcp_poll_timeout_seconds", 120) or 120)) mcp_poll_timeout = max(60, int(runtime_values.get("graylog_mcp_poll_timeout_seconds", 240) or 240))
fetch_mode = str(runtime_values.get("graylog_fetch_mode", "auto") or "auto") fetch_mode = str(runtime_values.get("graylog_fetch_mode", "auto") or "auto")
use_aggregate = fetch_mode == "aggregate" or (fetch_mode == "auto" and max_events_per_stream > raw_sample_events) use_aggregate = fetch_mode == "aggregate" or (fetch_mode == "auto" and max_events_per_stream > raw_sample_events)
aggregate_events_total = 0 aggregate_events_total = 0
@@ -739,11 +739,11 @@ def monitor_loop(
effective_llm = bool(runtime.get("llm_enabled")) if runtime else llm effective_llm = bool(runtime.get("llm_enabled")) if runtime else llm
effective_model = str(runtime.get("llm_model") or llm_model or "") effective_model = str(runtime.get("llm_model") or llm_model or "")
mcp_call_timeout = max(1, int(runtime.get("graylog_mcp_call_timeout_seconds", 8) or 8)) mcp_call_timeout = max(1, int(runtime.get("graylog_mcp_call_timeout_seconds", 8) or 8))
mcp_poll_timeout = max(60, int(runtime.get("graylog_mcp_poll_timeout_seconds", 120) or 120)) mcp_poll_timeout = max(60, int(runtime.get("graylog_mcp_poll_timeout_seconds", 240) or 240))
if runtime.get("log_source") == "graylog_mcp": if runtime.get("log_source") == "graylog_mcp":
write_refreshing_status(output, cache_path=status_cache_path, call_timeout_seconds=mcp_call_timeout, poll_timeout_seconds=mcp_poll_timeout, runtime_values=runtime) write_refreshing_status(output, cache_path=status_cache_path, call_timeout_seconds=mcp_call_timeout, poll_timeout_seconds=mcp_poll_timeout, runtime_values=runtime)
try: try:
status_timeout = mcp_poll_timeout + max(30, mcp_call_timeout * 2) status_timeout = mcp_poll_timeout + max(90, mcp_call_timeout * 4)
with _cycle_timeout(status_timeout if runtime.get("log_source") == "graylog_mcp" else 0): with _cycle_timeout(status_timeout if runtime.get("log_source") == "graylog_mcp" else 0):
status = build_status( status = build_status(
log_path, policy_path=policy_path, anomaly_limit=anomaly_limit, log_path, policy_path=policy_path, anomaly_limit=anomaly_limit,

View File

@@ -103,7 +103,7 @@ class BaselineTests(unittest.TestCase):
store.ingest([parse_log_line("srcip=10.0.0.2 dstport=443 recent=1")], observed_at=recent) store.ingest([parse_log_line("srcip=10.0.0.2 dstport=443 recent=1")], observed_at=recent)
store.ingest_profile_fields([parse_log_line("fgai_stream_id=windows username=bob action=login recent=1")], profiles, observed_at=recent) store.ingest_profile_fields([parse_log_line("fgai_stream_id=windows username=bob action=login recent=1")], profiles, observed_at=recent)
result = store.maintenance(retention_days=7, value_retention_days=7, max_values_per_field=2000) result = store.maintenance(retention_days=7, value_retention_days=7, max_values_per_field=2000, include_rows=True)
self.assertGreaterEqual(result["deleted"]["source_buckets"], 1) self.assertGreaterEqual(result["deleted"]["source_buckets"], 1)
self.assertGreaterEqual(result["deleted"]["profile_buckets"], 1) self.assertGreaterEqual(result["deleted"]["profile_buckets"], 1)
@@ -122,7 +122,7 @@ class BaselineTests(unittest.TestCase):
] ]
store.ingest_profile_fields(events, profiles, observed_at=1_700_000_000) store.ingest_profile_fields(events, profiles, observed_at=1_700_000_000)
stats = store.stats() stats = store.stats(include_rows=True)
self.assertEqual(stats["rows"]["profile_buckets"], 2) self.assertEqual(stats["rows"]["profile_buckets"], 2)
with store._connect() as connection: with store._connect() as connection: