uploader-bot/app/core/models/stats/metrics_models.py

151 lines
4.7 KiB
Python

from __future__ import annotations
import time
import hashlib
import json
from dataclasses import dataclass, field, asdict
from typing import Dict, Any, Optional, List
def _now_ts() -> int:
return int(time.time())
def _gen_nonce(prefix: str = "stats") -> str:
base = f"{prefix}:{_now_ts()}:{time.time_ns()}"
return hashlib.sha256(base.encode("utf-8")).hexdigest()[:16]
@dataclass
class SystemMetrics:
cpu_percent: Optional[float] = None
cpu_load_avg_1m: Optional[float] = None
cpu_load_avg_5m: Optional[float] = None
cpu_load_avg_15m: Optional[float] = None
mem_total_mb: Optional[float] = None
mem_used_mb: Optional[float] = None
mem_available_mb: Optional[float] = None
mem_percent: Optional[float] = None
disk_total_mb: Optional[float] = None
disk_used_mb: Optional[float] = None
disk_free_mb: Optional[float] = None
disk_percent: Optional[float] = None
io_read_mb_s: Optional[float] = None
io_write_mb_s: Optional[float] = None
net_sent_kb_s: Optional[float] = None
net_recv_kb_s: Optional[float] = None
uptime_seconds: Optional[int] = None
timestamp: int = field(default_factory=_now_ts)
def to_dict(self) -> Dict[str, Any]:
return asdict(self)
@staticmethod
def from_dict(data: Dict[str, Any]) -> "SystemMetrics":
return SystemMetrics(**data)
@dataclass
class AppMetrics:
total_conversions: int = 0
total_requests: int = 0
total_errors: int = 0
slow_ops_count: int = 0
avg_response_ms: Optional[float] = None
p95_response_ms: Optional[float] = None
p99_response_ms: Optional[float] = None
details: Dict[str, Any] = field(default_factory=dict)
timestamp: int = field(default_factory=_now_ts)
def to_dict(self) -> Dict[str, Any]:
return asdict(self)
@staticmethod
def from_dict(data: Dict[str, Any]) -> "AppMetrics":
return AppMetrics(**data)
@dataclass
class NodeStats:
node_id: str
public_key: str
system: SystemMetrics
app: AppMetrics
known_content_items: Optional[int] = None
available_content_items: Optional[int] = None
protocol_version: str = "stats-gossip-v1"
timestamp: int = field(default_factory=_now_ts)
nonce: str = field(default_factory=_gen_nonce)
signature: Optional[str] = None # ed25519
def to_dict(self, include_signature: bool = True) -> Dict[str, Any]:
data = {
"node_id": self.node_id,
"public_key": self.public_key,
"system": self.system.to_dict(),
"app": self.app.to_dict(),
"known_content_items": self.known_content_items,
"available_content_items": self.available_content_items,
"protocol_version": self.protocol_version,
"timestamp": self.timestamp,
"nonce": self.nonce,
}
if include_signature:
data["signature"] = self.signature
return data
@staticmethod
def canonical_payload(data: Dict[str, Any]) -> Dict[str, Any]:
# Для подписи удаляем signature и сортируем
payload = dict(data)
payload.pop("signature", None)
return payload
@staticmethod
def to_signable_json(data: Dict[str, Any]) -> str:
payload = NodeStats.canonical_payload(data)
return json.dumps(payload, sort_keys=True, ensure_ascii=False)
@staticmethod
def from_dict(data: Dict[str, Any]) -> "NodeStats":
return NodeStats(
node_id=data["node_id"],
public_key=data["public_key"],
system=SystemMetrics.from_dict(data["system"]),
app=AppMetrics.from_dict(data["app"]),
known_content_items=data.get("known_content_items"),
available_content_items=data.get("available_content_items"),
protocol_version=data.get("protocol_version", "stats-gossip-v1"),
timestamp=data.get("timestamp", _now_ts()),
nonce=data.get("nonce", _gen_nonce()),
signature=data.get("signature"),
)
@dataclass
class NetworkStats:
# Сводная статистика по сети
node_count: int
active_nodes: int
avg_uptime_seconds: Optional[float] = None
avg_cpu_percent: Optional[float] = None
avg_mem_percent: Optional[float] = None
avg_latency_ms: Optional[float] = None
total_available_content: Optional[int] = None
health_score: Optional[float] = None # 0..100
timestamp: int = field(default_factory=_now_ts)
nodes: List[Dict[str, Any]] = field(default_factory=list) # список упрощенных NodeStats резюме
def to_dict(self) -> Dict[str, Any]:
return asdict(self)
@staticmethod
def from_dict(data: Dict[str, Any]) -> "NetworkStats":
return NetworkStats(**data)