198 lines
6.5 KiB
Python
198 lines
6.5 KiB
Python
"""多渠道网关关键指标(纯内存计数器实现,不依赖 prometheus_client)。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import threading
|
||
from collections import defaultdict
|
||
from typing import Any
|
||
|
||
|
||
class Counter:
|
||
"""按标签维度累加的计数器。"""
|
||
|
||
def __init__(self, name: str, description: str = "") -> None:
|
||
self.name = name
|
||
self.description = description
|
||
self._values: dict[tuple[tuple[str, str], ...], int | float] = defaultdict(int)
|
||
self._lock = threading.Lock()
|
||
|
||
def _key(self, labels: dict[str, str] | None) -> tuple[tuple[str, str], ...]:
|
||
labels = labels or {}
|
||
return tuple(sorted(labels.items()))
|
||
|
||
def inc(self, labels: dict[str, str] | None = None, amount: int | float = 1) -> None:
|
||
if amount < 0:
|
||
raise ValueError("Counter increment amount must be non-negative")
|
||
key = self._key(labels)
|
||
with self._lock:
|
||
self._values[key] += amount
|
||
|
||
def get(self, labels: dict[str, str] | None = None) -> int | float:
|
||
key = self._key(labels)
|
||
with self._lock:
|
||
return self._values[key]
|
||
|
||
|
||
class Gauge:
|
||
"""按标签维度可增可减的仪表盘。"""
|
||
|
||
def __init__(self, name: str, description: str = "") -> None:
|
||
self.name = name
|
||
self.description = description
|
||
self._values: dict[tuple[tuple[str, str], ...], int | float] = defaultdict(int)
|
||
self._lock = threading.Lock()
|
||
|
||
def _key(self, labels: dict[str, str] | None) -> tuple[tuple[str, str], ...]:
|
||
labels = labels or {}
|
||
return tuple(sorted(labels.items()))
|
||
|
||
def inc(self, labels: dict[str, str] | None = None, amount: int | float = 1) -> None:
|
||
key = self._key(labels)
|
||
with self._lock:
|
||
self._values[key] += amount
|
||
|
||
def dec(self, labels: dict[str, str] | None = None, amount: int | float = 1) -> None:
|
||
key = self._key(labels)
|
||
with self._lock:
|
||
self._values[key] -= amount
|
||
|
||
def set(self, labels: dict[str, str] | None = None, value: int | float = 0) -> None:
|
||
key = self._key(labels)
|
||
with self._lock:
|
||
self._values[key] = value
|
||
|
||
def get(self, labels: dict[str, str] | None = None) -> int | float:
|
||
key = self._key(labels)
|
||
with self._lock:
|
||
return self._values[key]
|
||
|
||
|
||
class Histogram:
|
||
"""按标签维度记录数值分布的直方图。"""
|
||
|
||
def __init__(
|
||
self,
|
||
name: str,
|
||
description: str = "",
|
||
buckets: tuple[float, ...] = (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
|
||
) -> None:
|
||
self.name = name
|
||
self.description = description
|
||
self.buckets = buckets
|
||
self._counts: dict[tuple[tuple[str, str], ...], int] = defaultdict(int)
|
||
self._sums: dict[tuple[tuple[str, str], ...], float] = defaultdict(float)
|
||
self._buckets: dict[tuple[tuple[str, str], ...], dict[int, int]] = defaultdict(
|
||
lambda: {i: 0 for i in range(len(buckets) + 1)}
|
||
)
|
||
self._lock = threading.Lock()
|
||
|
||
def _key(self, labels: dict[str, str] | None) -> tuple[tuple[str, str], ...]:
|
||
labels = labels or {}
|
||
return tuple(sorted(labels.items()))
|
||
|
||
def observe(self, labels: dict[str, str] | None = None, value: float = 0) -> None:
|
||
key = self._key(labels)
|
||
with self._lock:
|
||
self._counts[key] += 1
|
||
self._sums[key] += value
|
||
for idx, bucket in enumerate(self.buckets):
|
||
if value <= bucket:
|
||
self._buckets[key][idx] += 1
|
||
break
|
||
else:
|
||
self._buckets[key][len(self.buckets)] += 1
|
||
|
||
def get(self, labels: dict[str, str] | None = None) -> dict[str, Any]:
|
||
key = self._key(labels)
|
||
with self._lock:
|
||
return {
|
||
"count": self._counts[key],
|
||
"sum": self._sums[key],
|
||
"buckets": dict(self._buckets[key]),
|
||
}
|
||
|
||
|
||
channel_messages_received_total = Counter(
|
||
"channel_messages_received_total",
|
||
"接收的渠道消息总数(按 channel_type/account_id 计数)",
|
||
)
|
||
channel_messages_delivered_total = Counter(
|
||
"channel_messages_delivered_total",
|
||
"成功投递的渠道消息总数",
|
||
)
|
||
channel_messages_failed_total = Counter(
|
||
"channel_messages_failed_total",
|
||
"投递失败的渠道消息总数",
|
||
)
|
||
channel_delivery_duration_seconds = Histogram(
|
||
"channel_delivery_duration_seconds",
|
||
"从 Worker 生成消息到渠道投递完成的耗时(秒)",
|
||
)
|
||
channel_reconnect_total = Counter(
|
||
"channel_reconnect_total",
|
||
"渠道重连次数",
|
||
)
|
||
channel_active_connections = Gauge(
|
||
"channel_active_connections",
|
||
"当前活跃 WS/Polling 连接数",
|
||
)
|
||
channel_rate_limited_total = Counter(
|
||
"channel_rate_limited_total",
|
||
"被限流的消息数",
|
||
)
|
||
|
||
_COUNTERS = [
|
||
channel_messages_received_total,
|
||
channel_messages_delivered_total,
|
||
channel_messages_failed_total,
|
||
channel_reconnect_total,
|
||
channel_rate_limited_total,
|
||
]
|
||
_GAUGES = [channel_active_connections]
|
||
_HISTOGRAMS = [channel_delivery_duration_seconds]
|
||
|
||
|
||
def _format_labels(key: tuple[tuple[str, str], ...]) -> str:
|
||
return ",".join(f'{k}="{v}"' for k, v in key)
|
||
|
||
|
||
def dump() -> dict[str, Any]:
|
||
"""导出所有指标的当前快照。"""
|
||
counters: dict[str, dict[str, Any]] = {}
|
||
for counter in _COUNTERS:
|
||
with counter._lock:
|
||
counters[counter.name] = {
|
||
"description": counter.description,
|
||
"values": {_format_labels(key): value for key, value in counter._values.items()},
|
||
}
|
||
|
||
gauges: dict[str, dict[str, Any]] = {}
|
||
for gauge in _GAUGES:
|
||
with gauge._lock:
|
||
gauges[gauge.name] = {
|
||
"description": gauge.description,
|
||
"values": {_format_labels(key): value for key, value in gauge._values.items()},
|
||
}
|
||
|
||
histograms: dict[str, dict[str, Any]] = {}
|
||
for histogram in _HISTOGRAMS:
|
||
with histogram._lock:
|
||
histograms[histogram.name] = {
|
||
"description": histogram.description,
|
||
"buckets": list(histogram.buckets),
|
||
"values": {
|
||
_format_labels(key): {
|
||
"count": histogram._counts[key],
|
||
"sum": histogram._sums[key],
|
||
"buckets": dict(histogram._buckets[key]),
|
||
}
|
||
for key in histogram._counts
|
||
},
|
||
}
|
||
|
||
return {
|
||
"counters": counters,
|
||
"gauges": gauges,
|
||
"histograms": histograms,
|
||
}
|