fix monitor error
This commit is contained in:
@@ -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]] = []
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)' : ''}`,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user