WIP autoscaler agent
This commit is contained in:
parent
c610a3e284
commit
28059dcedf
34 changed files with 2409 additions and 35 deletions
103
agent/nix_builder_autoscaler/metrics.py
Normal file
103
agent/nix_builder_autoscaler/metrics.py
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
"""In-memory Prometheus metrics registry.
|
||||
|
||||
No prometheus_client dependency — formats text manually.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
from typing import Any
|
||||
|
||||
|
||||
def _labels_key(labels: dict[str, str]) -> tuple[tuple[str, str], ...]:
|
||||
return tuple(sorted(labels.items()))
|
||||
|
||||
|
||||
def _format_labels(labels: dict[str, str]) -> str:
|
||||
if not labels:
|
||||
return ""
|
||||
parts = ",".join(f'{k}="{v}"' for k, v in sorted(labels.items()))
|
||||
return "{" + parts + "}"
|
||||
|
||||
|
||||
class MetricsRegistry:
|
||||
"""Thread-safe in-memory metrics store with Prometheus text output."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._lock = threading.Lock()
|
||||
self._gauges: dict[str, dict[tuple[tuple[str, str], ...], float]] = {}
|
||||
self._counters: dict[str, dict[tuple[tuple[str, str], ...], float]] = {}
|
||||
self._histograms: dict[str, dict[tuple[tuple[str, str], ...], Any]] = {}
|
||||
|
||||
def gauge(self, name: str, labels: dict[str, str], value: float) -> None:
|
||||
"""Set a gauge value."""
|
||||
key = _labels_key(labels)
|
||||
with self._lock:
|
||||
if name not in self._gauges:
|
||||
self._gauges[name] = {}
|
||||
self._gauges[name][key] = value
|
||||
|
||||
def counter(self, name: str, labels: dict[str, str], increment: float = 1.0) -> None:
|
||||
"""Increment a counter."""
|
||||
key = _labels_key(labels)
|
||||
with self._lock:
|
||||
if name not in self._counters:
|
||||
self._counters[name] = {}
|
||||
self._counters[name][key] = self._counters[name].get(key, 0.0) + increment
|
||||
|
||||
def histogram_observe(self, name: str, labels: dict[str, str], value: float) -> None:
|
||||
"""Record a histogram observation.
|
||||
|
||||
Uses fixed buckets: 0.01, 0.05, 0.1, 0.5, 1, 5, 10, 30, 60, 120, +Inf.
|
||||
"""
|
||||
key = _labels_key(labels)
|
||||
buckets = (0.01, 0.05, 0.1, 0.5, 1.0, 5.0, 10.0, 30.0, 60.0, 120.0)
|
||||
with self._lock:
|
||||
if name not in self._histograms:
|
||||
self._histograms[name] = {}
|
||||
if key not in self._histograms[name]:
|
||||
self._histograms[name][key] = {
|
||||
"labels": labels,
|
||||
"buckets": {b: 0 for b in buckets},
|
||||
"sum": 0.0,
|
||||
"count": 0,
|
||||
}
|
||||
entry = self._histograms[name][key]
|
||||
entry["sum"] += value
|
||||
entry["count"] += 1
|
||||
for b in buckets:
|
||||
if value <= b:
|
||||
entry["buckets"][b] += 1
|
||||
|
||||
def render(self) -> str:
|
||||
"""Render all metrics in Prometheus text exposition format."""
|
||||
lines: list[str] = []
|
||||
with self._lock:
|
||||
for name, series in sorted(self._gauges.items()):
|
||||
lines.append(f"# TYPE {name} gauge")
|
||||
for key, val in sorted(series.items()):
|
||||
labels = dict(key)
|
||||
lines.append(f"{name}{_format_labels(labels)} {val}")
|
||||
|
||||
for name, series in sorted(self._counters.items()):
|
||||
lines.append(f"# TYPE {name} counter")
|
||||
for key, val in sorted(series.items()):
|
||||
labels = dict(key)
|
||||
lines.append(f"{name}{_format_labels(labels)} {val}")
|
||||
|
||||
for name, series in sorted(self._histograms.items()):
|
||||
lines.append(f"# TYPE {name} histogram")
|
||||
for _key, entry in sorted(series.items()):
|
||||
labels = entry["labels"]
|
||||
cumulative = 0
|
||||
for b, count in sorted(entry["buckets"].items()):
|
||||
cumulative += count
|
||||
le_labels = {**labels, "le": str(b)}
|
||||
lines.append(f"{name}_bucket{_format_labels(le_labels)} {cumulative}")
|
||||
inf_labels = {**labels, "le": "+Inf"}
|
||||
lines.append(f"{name}_bucket{_format_labels(inf_labels)} {entry['count']}")
|
||||
lines.append(f"{name}_sum{_format_labels(labels)} {entry['sum']}")
|
||||
lines.append(f"{name}_count{_format_labels(labels)} {entry['count']}")
|
||||
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
Loading…
Add table
Add a link
Reference in a new issue