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)