4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
115 lines
3.7 KiB
Python
115 lines
3.7 KiB
Python
"""Datadog-backed upstream-evidence provider factory."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Mapping
|
|
from datetime import UTC, datetime
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
from pydantic import ValidationError
|
|
|
|
from integrations.datadog.correlation.adapter import DatadogCorrelationAdapter
|
|
from integrations.datadog.correlation.provider import (
|
|
DatadogCorrelationQueries,
|
|
DatadogUpstreamEvidenceProvider,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from core.domain.types.upstream import (
|
|
UpstreamEvidenceProvider,
|
|
)
|
|
|
|
|
|
def _parse_iso8601(value: str) -> datetime:
|
|
return datetime.fromisoformat(value.replace("Z", "+00:00")).astimezone(UTC)
|
|
|
|
|
|
def _window_minutes(start: str, end: str) -> int:
|
|
try:
|
|
delta = _parse_iso8601(end) - _parse_iso8601(start)
|
|
return max(1, int(delta.total_seconds() // 60))
|
|
except Exception:
|
|
return 60
|
|
|
|
|
|
def datadog_avg_query(metric_name: str) -> str:
|
|
metric = metric_name.strip()
|
|
if metric.startswith(("avg:", "sum:", "min:", "max:", "count:")):
|
|
return metric
|
|
if "{" in metric and "}" in metric:
|
|
return f"avg:{metric}"
|
|
return f"avg:{metric}{{*}}"
|
|
|
|
|
|
def build_datadog_provider(
|
|
*,
|
|
integration_config: Mapping[str, Any] | None,
|
|
target_resource: str = "unknown-rds",
|
|
candidate_services: tuple[str, ...] = (),
|
|
) -> UpstreamEvidenceProvider | None:
|
|
"""Return a Datadog-backed upstream-evidence provider, or ``None``.
|
|
|
|
Callers pass the integration config and the alert-derived knobs
|
|
directly; the factory does **not** know about agent state shape.
|
|
"""
|
|
from integrations.config_models import DatadogIntegrationConfig
|
|
from integrations.datadog.client import DatadogClient
|
|
|
|
if not integration_config:
|
|
return None
|
|
|
|
try:
|
|
datadog_cfg = DatadogIntegrationConfig.model_validate(integration_config)
|
|
except ValidationError:
|
|
return None
|
|
|
|
client = DatadogClient(datadog_cfg)
|
|
|
|
def metric_query(metric_name: str, window: dict[str, Any]) -> dict[str, Any]:
|
|
start = str(window.get("from") or "")
|
|
end = str(window.get("to") or "")
|
|
if not start or not end:
|
|
return {"timestamps": [], "values": []}
|
|
query = datadog_avg_query(metric_name)
|
|
result = client.query_metrics(query, start=_parse_iso8601(start), end=_parse_iso8601(end))
|
|
if not result.get("success"):
|
|
return {"timestamps": [], "values": []}
|
|
return {
|
|
"timestamps": result.get("timestamps") or [],
|
|
"values": result.get("values") or [],
|
|
}
|
|
|
|
def log_query(query: str, window: dict[str, Any]) -> dict[str, Any]:
|
|
start = str(window.get("from") or "")
|
|
end = str(window.get("to") or "")
|
|
start_dt = _parse_iso8601(start) if start else None
|
|
end_dt = _parse_iso8601(end) if end else None
|
|
minutes = _window_minutes(start, end)
|
|
result = client.search_logs(
|
|
query,
|
|
time_range_minutes=minutes,
|
|
limit=100,
|
|
start=start_dt,
|
|
end=end_dt,
|
|
)
|
|
logs = result.get("logs") if isinstance(result, dict) else []
|
|
if not isinstance(logs, list):
|
|
logs = []
|
|
return {
|
|
"timestamps": [
|
|
str(item.get("timestamp", "")) for item in logs if isinstance(item, dict)
|
|
],
|
|
"messages": [str(item.get("message", "")) for item in logs if isinstance(item, dict)],
|
|
}
|
|
|
|
return DatadogUpstreamEvidenceProvider(
|
|
adapter=DatadogCorrelationAdapter(
|
|
metric_query_fn=metric_query,
|
|
log_query_fn=log_query,
|
|
),
|
|
queries=DatadogCorrelationQueries(
|
|
upstream_service_names=candidate_services,
|
|
),
|
|
target_resource=target_resource,
|
|
)
|