2026-07-16 01:19:47 +08:00
|
|
|
|
"""Prometheus 指标定义:发送计数/耗时/失败/图像匹配/DB校验/队列/运行态/熔断/缓存/自适应等待。
|
|
|
|
|
|
|
|
|
|
|
|
所有指标用 prometheus_client 定义,通过 /metrics 端点暴露(app.py 挂载)。
|
2026-07-18 03:43:28 +08:00
|
|
|
|
|
|
|
|
|
|
prometheus_client 为可选依赖:缺失时本模块降级为 NoOp 桩件,所有指标调用
|
|
|
|
|
|
(.labels()/.inc()/.set()/.observe() 等)变为 no-op,bridge 核心业务不受影响。
|
|
|
|
|
|
app.py 的 /metrics 端点会同步降级(make_asgi_app 仍 ImportError → 禁用并 warning)。
|
2026-07-16 01:19:47 +08:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
import logging
|
|
|
|
|
|
import time
|
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger("woc-bridge")
|
|
|
|
|
|
|
2026-07-18 03:43:28 +08:00
|
|
|
|
|
|
|
|
|
|
class _NoOpMetric:
|
|
|
|
|
|
"""prometheus_client 缺失时的 NoOp 桩件。
|
|
|
|
|
|
|
|
|
|
|
|
覆盖上层所有调用模式:实例化(接收任意参数)+
|
|
|
|
|
|
.labels(*args, **kwargs) → 返回 self,支持链式 .inc()/.dec()/.set()/.observe()。
|
|
|
|
|
|
所有操作 no-op,不持有任何状态。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
__slots__ = ()
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs) -> None:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def labels(self, *args, **kwargs) -> "_NoOpMetric":
|
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
def inc(self, amount: float = 1.0) -> None:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def dec(self, amount: float = 1.0) -> None:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def set(self, value: float) -> None:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def observe(self, amount: float) -> None:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def info(self, val) -> None:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
from prometheus_client import (
|
|
|
|
|
|
Counter,
|
|
|
|
|
|
Gauge,
|
|
|
|
|
|
Histogram,
|
|
|
|
|
|
Info,
|
|
|
|
|
|
)
|
|
|
|
|
|
_PROMETHEUS_AVAILABLE = True
|
|
|
|
|
|
except ImportError:
|
|
|
|
|
|
logger.warning(
|
|
|
|
|
|
"[metrics] prometheus_client 不可用,指标降级为 NoOp;"
|
|
|
|
|
|
"/metrics 端点将自动禁用,核心业务不受影响"
|
|
|
|
|
|
)
|
|
|
|
|
|
_PROMETHEUS_AVAILABLE = False
|
|
|
|
|
|
# 类型忽略:故意将多个指标类名重绑定为同一个 NoOp 类,
|
|
|
|
|
|
# 使所有 Counter/Gauge/Histogram/Info(...) 实例化返回 _NoOpMetric 实例
|
|
|
|
|
|
Counter = Gauge = Histogram = Info = _NoOpMetric # type: ignore[assignment,misc]
|
|
|
|
|
|
|
2026-07-16 01:19:47 +08:00
|
|
|
|
# 发送总数(按 flow_name / result 标签)
|
|
|
|
|
|
send_total = Counter(
|
|
|
|
|
|
"woc_send_total",
|
|
|
|
|
|
"Total send operations",
|
|
|
|
|
|
["flow_name", "result"], # result: success / failed / skipped
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 发送耗时分布
|
|
|
|
|
|
send_duration = Histogram(
|
|
|
|
|
|
"woc_send_duration_seconds",
|
|
|
|
|
|
"Send operation duration in seconds",
|
|
|
|
|
|
["flow_name"],
|
|
|
|
|
|
buckets=(0.5, 1.0, 2.0, 5.0, 10.0, 30.0, 60.0),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 按失败状态分类的失败计数
|
|
|
|
|
|
send_failed_by_state = Counter(
|
|
|
|
|
|
"woc_send_failed_by_state_total",
|
|
|
|
|
|
"Send failures by flow state",
|
|
|
|
|
|
["flow_name", "state"],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 图像匹配置信度分布
|
|
|
|
|
|
image_match_confidence = Histogram(
|
|
|
|
|
|
"woc_image_match_confidence",
|
|
|
|
|
|
"OpenCV template match confidence",
|
|
|
|
|
|
["kind"],
|
|
|
|
|
|
buckets=(0.5, 0.7, 0.8, 0.85, 0.9, 0.95, 1.0),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# DB 校验耗时
|
|
|
|
|
|
db_verify_duration = Histogram(
|
|
|
|
|
|
"woc_db_verify_duration_seconds",
|
|
|
|
|
|
"DB verify duration in seconds",
|
|
|
|
|
|
buckets=(0.1, 0.2, 0.5, 1.0, 2.0, 3.0, 5.0),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 发送队列待处理数
|
|
|
|
|
|
send_queue_pending = Gauge(
|
|
|
|
|
|
"woc_send_queue_pending",
|
|
|
|
|
|
"Send queue pending count",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# WeChat 运行态(1=running, 0=not running)
|
|
|
|
|
|
wechat_running = Gauge(
|
|
|
|
|
|
"woc_wechat_running",
|
|
|
|
|
|
"WeChat process running state (1=running, 0=not)",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 熔断器状态(0=closed, 1=open, 2=half_open)
|
|
|
|
|
|
circuit_state = Gauge(
|
|
|
|
|
|
"woc_circuit_state",
|
|
|
|
|
|
"Circuit breaker state (0=closed, 1=open, 2=half_open)",
|
|
|
|
|
|
["name"],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 会话缓存命中
|
|
|
|
|
|
session_cache_hit = Counter(
|
|
|
|
|
|
"woc_session_cache_hit_total",
|
|
|
|
|
|
"Session cache hit count",
|
|
|
|
|
|
["result"], # hit / miss
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 自适应等待超时计数
|
|
|
|
|
|
adaptive_wait_timeout = Counter(
|
|
|
|
|
|
"woc_adaptive_wait_timeout_total",
|
|
|
|
|
|
"Adaptive wait timeout count",
|
|
|
|
|
|
["kind"],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 首次发送耗时(无缓存)
|
|
|
|
|
|
send_duration_first = Histogram(
|
|
|
|
|
|
"woc_send_duration_first_seconds",
|
|
|
|
|
|
"First send duration (no session cache) in seconds",
|
|
|
|
|
|
buckets=(1.0, 3.0, 5.0, 10.0, 30.0, 60.0),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 缓存命中后的发送耗时
|
|
|
|
|
|
send_duration_cached = Histogram(
|
|
|
|
|
|
"woc_send_duration_cached_seconds",
|
|
|
|
|
|
"Cached send duration (session cache hit) in seconds",
|
|
|
|
|
|
buckets=(0.5, 1.0, 2.0, 5.0, 10.0, 30.0),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-18 03:43:28 +08:00
|
|
|
|
# UIActionScheduler 调度器指标
|
|
|
|
|
|
ui_action_executed = Counter(
|
|
|
|
|
|
"woc_ui_action_executed_total",
|
|
|
|
|
|
"UI actions executed total",
|
|
|
|
|
|
["priority"], # CRITICAL / HIGH / NORMAL / LOW
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
ui_action_wait_duration = Histogram(
|
|
|
|
|
|
"woc_ui_action_wait_duration_seconds",
|
|
|
|
|
|
"UI action wait duration (queue wait time)",
|
|
|
|
|
|
buckets=(0.1, 0.5, 1.0, 2.0, 5.0, 10.0, 30.0),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
ui_action_exec_duration = Histogram(
|
|
|
|
|
|
"woc_ui_action_exec_duration_seconds",
|
|
|
|
|
|
"UI action execution duration",
|
|
|
|
|
|
["priority"],
|
|
|
|
|
|
buckets=(0.5, 1.0, 2.0, 5.0, 10.0, 30.0, 60.0),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
ui_action_timeout = Counter(
|
|
|
|
|
|
"woc_ui_action_timeout_total",
|
|
|
|
|
|
"UI action wait timeout count",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
ui_action_rate_limited = Counter(
|
|
|
|
|
|
"woc_ui_action_rate_limited_total",
|
|
|
|
|
|
"UI action rate limited (queue full) count",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
ui_action_fast_fail = Counter(
|
|
|
|
|
|
"woc_ui_action_fast_fail_total",
|
|
|
|
|
|
"UI action fast-fail count (wechat dead window)",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-16 01:19:47 +08:00
|
|
|
|
def set_circuit_state(name: str, state_int: int) -> None:
|
|
|
|
|
|
"""更新熔断器状态指标。
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
name: 熔断器名称
|
|
|
|
|
|
state_int: 0=closed, 1=open, 2=half_open
|
|
|
|
|
|
"""
|
|
|
|
|
|
circuit_state.labels(name=name).set(state_int)
|