#!/usr/bin/env python3 """Publish Atlas request availability from deduplicated daily rollups.""" from __future__ import annotations import json import os import time from datetime import datetime, timedelta, timezone from typing import Iterable from urllib.parse import urlencode from urllib.request import Request, urlopen VM_URL = os.environ.get( "VM_URL", "http://victoria-metrics-single-server:8428" ).rstrip("/") SCOPE = "atlas" DEFINITION = "request-v4" REQUESTS_METRIC = "atlas:availability:requests_1d" FAILURES_METRIC = "atlas:availability:failures_1d" OUTPUT_METRIC = "atlas:availability:ratio_365d" WINDOW_DAYS = 365 def parse_export(lines: Iterable[bytes]) -> dict[int, float]: """Return the last exported value for each timestamp.""" points: dict[int, float] = {} for raw_line in lines: if not raw_line.strip(): continue series = json.loads(raw_line) for timestamp, value in zip(series["timestamps"], series["values"], strict=True): points[int(timestamp)] = float(value) return points def fetch_rollup(metric: str, start: datetime, end: datetime) -> dict[int, float]: """Stream one compact rollup series from VictoriaMetrics.""" matcher = ( f'{{__name__="{metric}",scope="{SCOPE}",definition="{DEFINITION}"}}' ) query = urlencode( { "match[]": matcher, "start": start.isoformat().replace("+00:00", "Z"), "end": end.isoformat().replace("+00:00", "Z"), } ) with urlopen(f"{VM_URL}/api/v1/export?{query}", timeout=600) as response: return parse_export(response) def calculate_availability(requests: float, failures: float) -> float: """Calculate the bounded successful-request ratio.""" if requests <= 0: raise ValueError("availability requires at least one observed request") if failures < 0: raise ValueError("failed request count cannot be negative") return max(0.0, min(1.0, 1.0 - (failures / requests))) def render_metric(value: float, timestamp_ms: int) -> str: """Render one VictoriaMetrics Prometheus-import sample.""" return ( f'{OUTPUT_METRIC}{{definition="{DEFINITION}",scope="{SCOPE}",' f'rollup="yearly"}} {value:.12f} {timestamp_ms}\n' ) def publish(value: float, timestamp_ms: int) -> None: """Write the calculated annual ratio to VictoriaMetrics.""" request = Request( f"{VM_URL}/api/v1/import/prometheus", data=render_metric(value, timestamp_ms).encode(), headers={"Content-Type": "text/plain"}, method="POST", ) with urlopen(request, timeout=30) as response: if response.status not in {200, 204}: raise RuntimeError(f"VictoriaMetrics import returned HTTP {response.status}") def main() -> None: """Rebuild and publish the rolling request-availability sample.""" end = datetime.now(timezone.utc) start = end - timedelta(days=WINDOW_DAYS) requests = sum(fetch_rollup(REQUESTS_METRIC, start, end).values()) failures = sum(fetch_rollup(FAILURES_METRIC, start, end).values()) availability = calculate_availability(requests, failures) timestamp_ms = time.time_ns() // 1_000_000 publish(availability, timestamp_ms) print( json.dumps( { "requests": requests, "failures": failures, "availability_percent": availability * 100, "timestamp_ms": timestamp_ms, }, sort_keys=True, ) ) if __name__ == "__main__": main()