A Python port of the Invisible Internet Project (I2P)
at main 140 lines 4.1 kB view raw
1"""StatManager — central statistics repository. 2 3Ported from net.i2p.stat.StatManager. 4""" 5 6import threading 7from typing import Dict, List, Optional, Set 8 9from i2p_stat.rate_stat import RateStat 10from i2p_stat.frequency_stat import FrequencyStat 11 12 13class StatManager: 14 """Central registry for all RateStats and FrequencyStats. 15 16 By default, only "required" stats are created. Set stat_full=True 17 to allow all stats (used in router context for full monitoring). 18 """ 19 20 _FREQ_COALESCE_RATE = 9 21 22 def __init__(self, stat_full: bool = False) -> None: 23 self._stat_full = stat_full 24 self._lock = threading.Lock() 25 self._rate_stats: Dict[str, RateStat] = {} 26 self._frequency_stats: Dict[str, FrequencyStat] = {} 27 self._coalesce_counter = 0 28 29 def shutdown(self) -> None: 30 with self._lock: 31 self._rate_stats.clear() 32 self._frequency_stats.clear() 33 34 # --- Rate Stats --- 35 36 def create_rate_stat( 37 self, 38 name: str, 39 description: str, 40 group: str, 41 periods: List[int], 42 ) -> None: 43 """Create a rate stat if stat_full is True.""" 44 if not self._stat_full: 45 return 46 self.create_required_rate_stat(name, description, group, periods) 47 48 def create_required_rate_stat( 49 self, 50 name: str, 51 description: str, 52 group: str, 53 periods: List[int], 54 ) -> None: 55 """Always create the rate stat.""" 56 if name not in self._rate_stats: 57 self._rate_stats[name] = RateStat(name, description, group, periods) 58 59 def remove_rate_stat(self, name: str) -> None: 60 self._rate_stats.pop(name, None) 61 62 def add_rate_data( 63 self, name: str, data: int, event_duration: int = 0 64 ) -> None: 65 stat = self._rate_stats.get(name) 66 if stat is not None: 67 stat.add_data(data, event_duration) 68 69 def get_rate(self, name: str) -> Optional[RateStat]: 70 return self._rate_stats.get(name) 71 72 def get_rate_names(self) -> Set[str]: 73 return set(self._rate_stats.keys()) 74 75 def is_rate(self, name: str) -> bool: 76 return name in self._rate_stats 77 78 # --- Frequency Stats --- 79 80 def create_frequency_stat( 81 self, 82 name: str, 83 description: str, 84 group: str, 85 periods: List[int], 86 ) -> None: 87 if not self._stat_full: 88 return 89 self.create_required_frequency_stat(name, description, group, periods) 90 91 def create_required_frequency_stat( 92 self, 93 name: str, 94 description: str, 95 group: str, 96 periods: List[int], 97 ) -> None: 98 if name not in self._frequency_stats: 99 self._frequency_stats[name] = FrequencyStat( 100 name, description, group, periods 101 ) 102 103 def update_frequency(self, name: str) -> None: 104 stat = self._frequency_stats.get(name) 105 if stat is not None: 106 stat.event_occurred() 107 108 def get_frequency(self, name: str) -> Optional[FrequencyStat]: 109 return self._frequency_stats.get(name) 110 111 def get_frequency_names(self) -> Set[str]: 112 return set(self._frequency_stats.keys()) 113 114 def is_frequency(self, name: str) -> bool: 115 return name in self._frequency_stats 116 117 # --- Coalesce --- 118 119 def coalesce_stats(self) -> None: 120 """Coalesce all rate stats, and frequency stats every Nth call.""" 121 with self._lock: 122 self._coalesce_counter += 1 123 do_freq = self._coalesce_counter % self._FREQ_COALESCE_RATE == 0 124 125 for rs in list(self._rate_stats.values()): 126 rs.coalesce_stats() 127 128 if do_freq: 129 for fs in list(self._frequency_stats.values()): 130 fs.coalesce_stats() 131 132 # --- Grouping --- 133 134 def get_stats_by_group(self) -> Dict[str, Set[str]]: 135 result: Dict[str, Set[str]] = {} 136 for name, rs in self._rate_stats.items(): 137 result.setdefault(rs.group_name, set()).add(name) 138 for name, fs in self._frequency_stats.items(): 139 result.setdefault(fs.group_name, set()).add(name) 140 return result