Files

387 lines
14 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Prometheus 指标导出模块
提供 /metrics 端点暴露监控数据
"""
import os
import sys
import time
from datetime import datetime
from typing import Dict, Optional
# 添加项目根目录到路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
class PrometheusMetrics:
"""Prometheus 指标收集器"""
def __init__(self, config: dict = None):
self.config = config or {}
self._metrics: Dict[str, dict] = {}
# 初始化指标
self._init_metrics()
def _init_metrics(self):
"""初始化指标定义"""
self._metrics = {
# 服务器指标
'hyc_server_uptime_seconds': {
'type': 'gauge',
'description': 'Server uptime in seconds',
'value': 0
},
'hyc_server_start_time': {
'type': 'gauge',
'description': 'Server start timestamp',
'value': time.time()
},
# 文件指标
'hyc_files_total': {
'type': 'gauge',
'description': 'Total number of files in the system',
'value': 0
},
'hyc_files_size_bytes': {
'type': 'gauge',
'description': 'Total size of all files in bytes',
'value': 0
},
'hyc_downloads_total': {
'type': 'counter',
'description': 'Total number of downloads',
'value': 0
},
'hyc_downloads_today': {
'type': 'counter',
'description': 'Number of downloads today',
'value': 0
},
# 缓存指标
'hyc_cache_size_bytes': {
'type': 'gauge',
'description': 'Current cache size in bytes',
'value': 0
},
'hyc_cache_entries': {
'type': 'gauge',
'description': 'Number of cache entries',
'value': 0
},
'hyc_cache_hits_total': {
'type': 'counter',
'description': 'Total number of cache hits',
'value': 0
},
'hyc_cache_misses_total': {
'type': 'counter',
'description': 'Total number of cache misses',
'value': 0
},
'hyc_cache_hit_ratio': {
'type': 'gauge',
'description': 'Cache hit ratio (0-1)',
'value': 0
},
# 同步指标
'hyc_sync_sources_total': {
'type': 'gauge',
'description': 'Total number of sync sources',
'value': 0
},
'hyc_sync_running': {
'type': 'gauge',
'description': 'Number of currently running sync operations',
'value': 0
},
'hyc_sync_files_total': {
'type': 'counter',
'description': 'Total number of synced files',
'value': 0
},
'hyc_sync_last_timestamp': {
'type': 'gauge',
'description': 'Timestamp of last successful sync',
'value': 0
},
# 数据库指标
'hyc_db_files_total': {
'type': 'gauge',
'description': 'Number of files in database',
'value': 0
},
'hyc_db_sync_records': {
'type': 'gauge',
'description': 'Number of sync records in database',
'value': 0
},
'hyc_db_cache_records': {
'type': 'gauge',
'description': 'Number of cache records in database',
'value': 0
},
# 系统资源指标 (从监控模块获取)
'hyc_cpu_percent': {
'type': 'gauge',
'description': 'CPU usage percentage',
'value': 0
},
'hyc_memory_percent': {
'type': 'gauge',
'description': 'Memory usage percentage',
'value': 0
},
'hyc_disk_percent': {
'type': 'gauge',
'description': 'Disk usage percentage',
'value': 0
},
'hyc_disk_free_bytes': {
'type': 'gauge',
'description': 'Free disk space in bytes',
'value': 0
},
'hyc_disk_total_bytes': {
'type': 'gauge',
'description': 'Total disk space in bytes',
'value': 0
},
'hyc_network_rx_bytes': {
'type': 'counter',
'description': 'Network receive bytes',
'value': 0
},
'hyc_network_tx_bytes': {
'type': 'counter',
'description': 'Network transmit bytes',
'value': 0
},
'hyc_active_connections': {
'type': 'gauge',
'description': 'Number of active connections',
'value': 0
},
# 镜像源指标
'hyc_mirror_enabled': {
'type': 'gauge',
'description': 'Whether a mirror is enabled (1=enabled, 0=disabled)',
'labels': ['mirror_type'],
'value': {}
},
'hyc_mirror_last_sync': {
'type': 'gauge',
'description': 'Timestamp of last mirror sync',
'labels': ['mirror_type'],
'value': {}
},
}
def set_uptime(self, seconds: float):
"""设置运行时间"""
self._metrics['hyc_server_uptime_seconds']['value'] = seconds
def set_files(self, count: int, size_bytes: int):
"""设置文件统计"""
self._metrics['hyc_files_total']['value'] = count
self._metrics['hyc_files_size_bytes']['value'] = size_bytes
def set_downloads(self, total: int, today: int):
"""设置下载统计"""
self._metrics['hyc_downloads_total']['value'] = total
self._metrics['hyc_downloads_today']['value'] = today
def set_cache(self, size_bytes: int, entries: int, hits: int, misses: int):
"""设置缓存统计"""
self._metrics['hyc_cache_size_bytes']['value'] = size_bytes
self._metrics['hyc_cache_entries']['value'] = entries
self._metrics['hyc_cache_hits_total']['value'] = hits
self._metrics['hyc_cache_misses_total']['value'] = misses
# 计算命中率
total = hits + misses
if total > 0:
self._metrics['hyc_cache_hit_ratio']['value'] = hits / total
else:
self._metrics['hyc_cache_hit_ratio']['value'] = 0
def set_sync(self, running: int, files_total: int, last_timestamp: float):
"""设置同步统计"""
self._metrics['hyc_sync_running']['value'] = running
self._metrics['hyc_sync_files_total']['value'] = files_total
self._metrics['hyc_sync_last_timestamp']['value'] = last_timestamp
def set_db_stats(self, files: int, sync_records: int, cache_records: int):
"""设置数据库统计"""
self._metrics['hyc_db_files_total']['value'] = files
self._metrics['hyc_db_sync_records']['value'] = sync_records
self._metrics['hyc_db_cache_records']['value'] = cache_records
def set_system(self, cpu: float, memory: float, disk: float,
disk_free: int, disk_total: int,
network_rx: int, network_tx: int):
"""设置系统资源统计"""
self._metrics['hyc_cpu_percent']['value'] = cpu
self._metrics['hyc_memory_percent']['value'] = memory
self._metrics['hyc_disk_percent']['value'] = disk
self._metrics['hyc_disk_free_bytes']['value'] = disk_free
self._metrics['hyc_disk_total_bytes']['value'] = disk_total
self._metrics['hyc_network_rx_bytes']['value'] = network_rx
self._metrics['hyc_network_tx_bytes']['value'] = network_tx
def set_connections(self, count: int):
"""设置连接数"""
self._metrics['hyc_active_connections']['value'] = count
def set_mirror_status(self, mirror_type: str, enabled: bool, last_sync: float):
"""设置镜像源状态"""
key = 'hyc_mirror_enabled'
if 'labels' not in self._metrics[key]:
self._metrics[key]['labels'] = ['mirror_type']
if 'value' not in self._metrics[key]:
self._metrics[key]['value'] = {}
self._metrics[key]['value'][mirror_type] = 1 if enabled else 0
key = 'hyc_mirror_last_sync'
if 'labels' not in self._metrics[key]:
self._metrics[key]['labels'] = ['mirror_type']
if 'value' not in self._metrics[key]:
self._metrics[key]['value'] = {}
self._metrics[key]['value'][mirror_type] = last_sync
def increment_downloads(self, count: int = 1):
"""增加下载计数"""
self._metrics['hyc_downloads_total']['value'] += count
def increment_cache_hits(self, count: int = 1):
"""增加缓存命中计数"""
self._metrics['hyc_cache_hits_total']['value'] += count
def increment_cache_misses(self, count: int = 1):
"""增加缓存未命中计数"""
self._metrics['hyc_cache_misses_total']['value'] += count
def increment_sync_files(self, count: int = 1):
"""增加同步文件计数"""
self._metrics['hyc_sync_files_total']['value'] += count
def generate_metrics(self) -> str:
"""生成 Prometheus 格式的指标输出"""
output = []
output.append("# Prometheus metrics for HYC Mirror Server")
output.append(f"# Generated at: {datetime.now().isoformat()}")
output.append("")
for name, metric in self._metrics.items():
desc = metric.get('description', '')
mtype = metric.get('type', 'gauge')
output.append(f"# TYPE {name} {mtype}")
output.append(f"# HELP {name} {desc}")
value = metric.get('value')
# 处理带标签的指标
if 'labels' in metric and isinstance(value, dict):
labels = metric['labels']
for label_values, v in value.items():
if isinstance(label_values, str):
label_str = f'{",".join([f"{labels[0]}={label_values}"])}'
else:
label_str = ','.join([f"{l}={v}" for l, v in zip(labels, label_values)])
output.append(f"{name}{{{label_str}}} {v}")
# 处理普通指标
elif isinstance(value, dict):
# 旧格式,可能是直接存储
for k, v in value.items():
output.append(f"{name}{{type=\"{k}\"}} {v}")
else:
output.append(f"{name} {value}")
output.append("")
return '\n'.join(output)
# ==================== 指标中间件 ====================
class MetricsMiddleware:
"""HTTP 请求指标中间件"""
def __init__(self, metrics: PrometheusMetrics = None):
self.metrics = metrics or PrometheusMetrics()
self._request_count = 0
self._request_duration_total = 0
self._errors_4xx = 0
self._errors_5xx = 0
def record_request(self, duration: float, status_code: int):
"""记录请求"""
self._request_count += 1
self._request_duration_total += duration
if 400 <= status_code < 500:
self._errors_4xx += 1
elif status_code >= 500:
self._errors_5xx += 1
def get_request_count(self) -> int:
return self._request_count
def get_request_duration_total(self) -> float:
return self._request_duration_total
def get_errors_4xx(self) -> int:
return self._errors_4xx
def get_errors_5xx(self) -> int:
return self._errors_5xx
def update_metrics(self):
"""更新 Prometheus 指标"""
# 添加请求相关指标
if 'hyc_http_requests_total' not in self.metrics._metrics:
self.metrics._metrics['hyc_http_requests_total'] = {
'type': 'counter',
'description': 'Total HTTP requests',
'value': self._request_count
}
else:
self.metrics._metrics['hyc_http_requests_total']['value'] = self._request_count
if 'hyc_http_request_duration_seconds_total' not in self.metrics._metrics:
self.metrics._metrics['hyc_http_request_duration_seconds_total'] = {
'type': 'counter',
'description': 'Total HTTP request duration in seconds',
'value': self._request_duration_total
}
else:
self.metrics._metrics['hyc_http_request_duration_seconds_total']['value'] = self._request_duration_total
if 'hyc_http_requests_4xx_total' not in self.metrics._metrics:
self.metrics._metrics['hyc_http_requests_4xx_total'] = {
'type': 'counter',
'description': 'Total HTTP 4xx errors',
'value': self._errors_4xx
}
else:
self.metrics._metrics['hyc_http_requests_4xx_total']['value'] = self._errors_4xx
if 'hyc_http_requests_5xx_total' not in self.metrics._metrics:
self.metrics._metrics['hyc_http_requests_5xx_total'] = {
'type': 'counter',
'description': 'Total HTTP 5xx errors',
'value': self._errors_5xx
}
else:
self.metrics._metrics['hyc_http_requests_5xx_total']['value'] = self._errors_5xx