完成py_plan.md
This commit is contained in:
156
sim/core/metrics.py
Normal file
156
sim/core/metrics.py
Normal file
@@ -0,0 +1,156 @@
|
||||
"""
|
||||
Metrics system for simulation evaluation.
|
||||
|
||||
Collects and reports:
|
||||
- sent_packets, received_packets
|
||||
- delivery_ratio
|
||||
- avg_delay
|
||||
- avg_hop
|
||||
- retransmissions
|
||||
- collisions
|
||||
- convergence_time
|
||||
"""
|
||||
|
||||
from typing import Dict, List, Set
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from sim import config
|
||||
|
||||
|
||||
@dataclass
|
||||
class SimulationMetrics:
|
||||
"""Metrics for the entire simulation."""
|
||||
|
||||
# Packet counts
|
||||
total_sent: int = 0 # Data packets generated (all nodes)
|
||||
total_received: int = 0 # Data packets received at sink
|
||||
total_forwarded: int = 0 # Data packets forwarded by nodes
|
||||
total_dropped: int = 0 # Packets dropped due to collision
|
||||
|
||||
# Routing
|
||||
convergence_time: float = 0.0
|
||||
route_updates: int = 0
|
||||
|
||||
# MAC
|
||||
retries: int = 0
|
||||
acks_received: int = 0
|
||||
|
||||
# Channel
|
||||
collisions: int = 0
|
||||
|
||||
# Hop statistics
|
||||
hop_counts: List[int] = field(default_factory=list)
|
||||
|
||||
# Per-node stats
|
||||
node_stats: Dict[int, dict] = field(default_factory=dict)
|
||||
|
||||
# Track unique packets received at sink
|
||||
received_packet_ids: Set[tuple] = field(default_factory=set)
|
||||
|
||||
def calculate_pdr(self) -> float:
|
||||
"""Calculate Packet Delivery Ratio (unique packets at sink / sent)."""
|
||||
unique_received = len(self.received_packet_ids)
|
||||
if self.total_sent == 0:
|
||||
return 0.0
|
||||
return unique_received / self.total_sent
|
||||
|
||||
def calculate_avg_hop(self) -> float:
|
||||
"""Calculate average hop count."""
|
||||
if not self.hop_counts:
|
||||
return 0.0
|
||||
return sum(self.hop_counts) / len(self.hop_counts)
|
||||
|
||||
def calculate_avg_retries(self) -> float:
|
||||
"""Calculate average retries per packet."""
|
||||
if self.total_sent == 0:
|
||||
return 0.0
|
||||
return self.retries / self.total_sent
|
||||
|
||||
def get_summary(self) -> dict:
|
||||
"""Get metrics summary."""
|
||||
unique_received = len(self.received_packet_ids)
|
||||
return {
|
||||
"total_sent": self.total_sent,
|
||||
"total_received": unique_received,
|
||||
"total_forwarded": self.total_forwarded,
|
||||
"total_dropped": self.total_dropped,
|
||||
"pdr": round(self.calculate_pdr() * 100, 2),
|
||||
"avg_hop": round(self.calculate_avg_hop(), 2),
|
||||
"avg_retries": round(self.calculate_avg_retries(), 2),
|
||||
"convergence_time": round(self.convergence_time, 2),
|
||||
"collisions": self.collisions,
|
||||
"route_updates": self.route_updates,
|
||||
}
|
||||
|
||||
|
||||
class MetricsCollector:
|
||||
"""Collects metrics from simulation."""
|
||||
|
||||
def __init__(self):
|
||||
self.metrics = SimulationMetrics()
|
||||
self.start_time = 0.0
|
||||
|
||||
def set_start_time(self, time: float):
|
||||
"""Set simulation start time."""
|
||||
self.start_time = time
|
||||
|
||||
def set_convergence_time(self, time: float):
|
||||
"""Set convergence time."""
|
||||
self.metrics.convergence_time = time - self.start_time
|
||||
|
||||
def add_node_stats(self, node_id: int, stats: dict, is_sink: bool = False):
|
||||
"""Add per-node statistics."""
|
||||
self.metrics.node_stats[node_id] = stats
|
||||
|
||||
# Aggregate
|
||||
node_stats = stats.get("stats", {})
|
||||
|
||||
if is_sink:
|
||||
# For sink, data_received is actual unique packets received
|
||||
# Track unique (src, seq) pairs
|
||||
pass # Will handle sink specially below
|
||||
else:
|
||||
self.metrics.total_sent += node_stats.get("data_sent", 0)
|
||||
|
||||
self.metrics.total_forwarded += node_stats.get("data_forwarded", 0)
|
||||
self.metrics.total_dropped += node_stats.get("packets_dropped", 0)
|
||||
self.metrics.route_updates += node_stats.get("route_updates", 0)
|
||||
|
||||
# MAC stats
|
||||
mac_stats = stats.get("mac", {})
|
||||
self.metrics.retries += mac_stats.get("retries", 0)
|
||||
self.metrics.acks_received += mac_stats.get("received_acks", 0)
|
||||
|
||||
def add_sink_stats(self, node_id: int, stats: dict):
|
||||
"""Add sink-specific statistics (unique packet delivery tracking)."""
|
||||
self.metrics.node_stats[node_id] = stats
|
||||
|
||||
node_stats = stats.get("stats", {})
|
||||
# Count how many unique packets the sink received
|
||||
# This is the actual delivery count for PDR
|
||||
received = node_stats.get("data_received", 0)
|
||||
self.metrics.total_received = received
|
||||
|
||||
# Also track all packets sent
|
||||
for nid, nstats in self.metrics.node_stats.items():
|
||||
if nid != node_id:
|
||||
self.metrics.total_sent += nstats.get("stats", {}).get("data_sent", 0)
|
||||
|
||||
# Update rest of stats
|
||||
self.metrics.total_dropped += node_stats.get("packets_dropped", 0)
|
||||
|
||||
mac_stats = stats.get("mac", {})
|
||||
self.metrics.retries += mac_stats.get("retries", 0)
|
||||
self.metrics.acks_received += mac_stats.get("received_acks", 0)
|
||||
|
||||
def add_collision(self, count: int = 1):
|
||||
"""Add collision count."""
|
||||
self.metrics.collisions += count
|
||||
|
||||
def add_hop_count(self, hops: int):
|
||||
"""Add hop count for a received packet."""
|
||||
self.metrics.hop_counts.append(hops)
|
||||
|
||||
def get_metrics(self) -> SimulationMetrics:
|
||||
"""Get collected metrics."""
|
||||
return self.metrics
|
||||
Reference in New Issue
Block a user