151 lines
4.7 KiB
Python
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) |