Files
wehub-resource-sync 4b6817381b
Benchmark image — build + push to ECR (any adapter) / build + push (push) Waiting to run
CI / quality (ubuntu-latest) (push) Waiting to run
CI / test (tools-runtime) (push) Waiting to run
CI / test (e2e-general) (push) Waiting to run
CI / test (cli-runtime) (push) Waiting to run
CI / test (e2e-provider-and-openclaw) (push) Waiting to run
CI / test (integrations-and-misc) (push) Waiting to run
CI / coverage-report (push) Blocked by required conditions
CI / test-kubernetes (push) Waiting to run
CI / should-run-thorough (push) Waiting to run
CI / test-thorough (cloudwatch-demo) (push) Blocked by required conditions
CI / test-thorough (flink-ecs) (push) Blocked by required conditions
CI / test-thorough (upstream-lambda) (push) Blocked by required conditions
CI / test-thorough (prefect-ecs-fargate) (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Blocked by required conditions
Release / publish-release (push) Blocked by required conditions
Release / publish-main-release (push) Blocked by required conditions
Release / prepare (push) Waiting to run
Release / verify (push) Blocked by required conditions
Release / build-python-dist (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Blocked by required conditions
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Waiting to run
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

181 lines
6.7 KiB
Python

"""Deterministic performance-fault localization from CloudOpsBench metric alerts.
Performance cases often alert on MULTIPLE services (latency on victims, CPU
throttling on a different pod). The 2026-06-07 Anthropic pilot showed opensre
investigations cluster on the loudest CPU-throttling service while the injected
fault is ``pod_network_delay`` on the service with the extreme relative latency
spike. This module reads ``raw_data/alert.json`` (the same data ``GetAlerts``
returns) and infers rank-1 ``fault_object`` + ``root_cause`` before the
paper-format predictor runs.
Heuristics (audited against boutique/trainticket performance metadata):
- ``pod_cpu_overload``: exactly one service shows RESOURCE_SATURATION /
cpu_cfs throttling and no competing latency leader.
- ``pod_network_delay``: the service whose alert evidence contains the largest
relative latency increase (``+NNNN%`` in LATENCY_DEGRADATION lines) wins,
even when another service shows CPU throttling.
"""
from __future__ import annotations
import json
import re
from pathlib import Path
from typing import Any
_LATENCY_PCT_RE = re.compile(r"\+(\d+(?:\.\d+)?)%")
_CPU_THROTTLE_MARKERS = ("cpu_cfs", "Throttled")
def load_alert_json(case_dir: Path) -> dict[str, Any] | None:
path = case_dir / "raw_data" / "alert.json"
if not path.is_file():
return None
try:
data = json.loads(path.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
return None
return data if isinstance(data, dict) else None
def format_metric_alerts(alert_data: dict[str, Any] | None) -> str:
"""Compact per-service anomaly lines for the paper-format predictor."""
if not alert_data:
return ""
alerts = alert_data.get("alerts")
if not isinstance(alerts, list) or not alerts:
return ""
lines: list[str] = []
for entry in alerts:
if not isinstance(entry, dict):
continue
name = entry.get("entity_name") or entry.get("name")
if not isinstance(name, str) or not name.strip():
continue
category = entry.get("metric_category")
cat = category if isinstance(category, str) and category.strip() else "METRIC"
evidence = entry.get("evidence")
if isinstance(evidence, list):
ev_text = " | ".join(str(x) for x in evidence if x)
elif isinstance(evidence, str):
ev_text = evidence
else:
ev_text = ""
if not ev_text.strip():
continue
lines.append(f" - {name}: [{cat}] {ev_text}")
if not lines:
return ""
return "Metric anomalies (from GetAlerts):\n" + "\n".join(lines)
def _service_name(entry: dict[str, Any]) -> str | None:
name = entry.get("entity_name") or entry.get("name")
return name.strip() if isinstance(name, str) and name.strip() else None
def _evidence_text(entry: dict[str, Any]) -> str:
evidence = entry.get("evidence")
if isinstance(evidence, list):
return " | ".join(str(x) for x in evidence if x)
if isinstance(evidence, str):
return evidence
return ""
def _has_cpu_throttling(evidence: str, category: str) -> bool:
return any(marker in evidence for marker in _CPU_THROTTLE_MARKERS) or (
"RESOURCE_SATURATION" in category and "cpu" in evidence.lower()
)
def _max_latency_pct_increase(evidence: str) -> float:
return max((float(m) for m in _LATENCY_PCT_RE.findall(evidence)), default=0.0)
def infer_performance_localization(
alert_data: dict[str, Any] | None,
*,
namespace: str,
) -> dict[str, str] | None:
"""Infer ``fault_object`` + ``root_cause`` for a performance case from alerts.
Returns ``None`` when the alert payload is missing or ambiguous. The
``namespace`` argument is only used to reject node-level entities.
"""
if not alert_data:
return None
alerts = alert_data.get("alerts")
if not isinstance(alerts, list) or not alerts:
return None
cpu_throttled: set[str] = set()
latency_peak: dict[str, float] = {}
for entry in alerts:
if not isinstance(entry, dict):
continue
service = _service_name(entry)
if service is None or service in {"master", "worker-01", "worker-02", "worker-03"}:
continue
category = entry.get("metric_category")
cat = category if isinstance(category, str) else ""
evidence = _evidence_text(entry)
if _has_cpu_throttling(evidence, cat):
cpu_throttled.add(service)
peak = _max_latency_pct_increase(evidence)
if peak > 0:
latency_peak[service] = max(latency_peak.get(service, 0.0), peak)
# One service with cpu_cfs: default pod_cpu_overload UNLESS another service
# shows a much larger latency spike (pod_network_delay on the latency leader).
if len(cpu_throttled) == 1:
cpu_service = next(iter(cpu_throttled))
cpu_latency = latency_peak.get(cpu_service, 0.0)
other_latency = {
svc: pct for svc, pct in latency_peak.items() if svc != cpu_service and pct >= 500.0
}
if other_latency:
best_other = max(other_latency, key=other_latency.get)
best_other_pct = other_latency[best_other]
if best_other_pct > max(cpu_latency, 1.0) * 2:
return {
"fault_object": f"app/{best_other}",
"root_cause": "pod_network_delay",
"rationale": (
f"largest relative latency spike (+{best_other_pct:.0f}%) "
f"on a non-CPU-throttled service"
),
}
return {
"fault_object": f"app/{cpu_service}",
"root_cause": "pod_cpu_overload",
"rationale": "cpu_cfs throttling on alerted service",
}
# No cpu throttling: latency leader with a large relative spike.
if latency_peak:
best_service = max(latency_peak, key=latency_peak.get)
best_pct = latency_peak[best_service]
if best_pct >= 500.0:
return {
"fault_object": f"app/{best_service}",
"root_cause": "pod_network_delay",
"rationale": (
f"largest relative latency spike (+{best_pct:.0f}%) among alerted services"
),
}
return None
def performance_context_for_case_dir(
case_dir: Path, *, namespace: str
) -> tuple[str, dict[str, str] | None]:
"""Return ``(formatted_alerts, localization_hint)`` for a case directory."""
alert_data = load_alert_json(case_dir)
return (
format_metric_alerts(alert_data),
infer_performance_localization(alert_data, namespace=namespace),
)