test_satellite_metrics.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. # -*- coding: utf-8 -*-
  2. """
  3. Tests für Satellite Metrics System.
  4. """
  5. import pytest
  6. from datetime import datetime, timedelta
  7. from trixy_core.satellite.metrics import (
  8. SatelliteMetricsCollector,
  9. MetricType,
  10. MetricValue,
  11. SatelliteMetrics,
  12. MetricsAggregator,
  13. AggregationType,
  14. AggregatedMetric,
  15. TimeWindow,
  16. )
  17. class TestMetricType:
  18. """Tests für MetricType."""
  19. def test_metric_types(self):
  20. """Testet Metrik-Typen."""
  21. assert MetricType.COUNTER.name == "COUNTER"
  22. assert MetricType.GAUGE.name == "GAUGE"
  23. assert MetricType.HISTOGRAM.name == "HISTOGRAM"
  24. assert MetricType.TIMING.name == "TIMING"
  25. class TestMetricValue:
  26. """Tests für MetricValue."""
  27. def test_value_creation(self):
  28. """Testet Value-Erstellung."""
  29. value = MetricValue(
  30. name="latency",
  31. value=15.5,
  32. metric_type=MetricType.TIMING,
  33. unit="ms",
  34. )
  35. assert value.name == "latency"
  36. assert value.value == 15.5
  37. assert value.metric_type == MetricType.TIMING
  38. assert value.unit == "ms"
  39. def test_value_with_labels(self):
  40. """Testet Value mit Labels."""
  41. value = MetricValue(
  42. name="requests",
  43. value=100,
  44. labels={"endpoint": "/api/data"},
  45. )
  46. assert value.labels["endpoint"] == "/api/data"
  47. def test_to_dict(self):
  48. """Testet Dictionary-Konvertierung."""
  49. value = MetricValue(
  50. name="test",
  51. value=42.0,
  52. metric_type=MetricType.GAUGE,
  53. )
  54. d = value.to_dict()
  55. assert d["name"] == "test"
  56. assert d["value"] == 42.0
  57. assert d["type"] == "GAUGE"
  58. class TestSatelliteMetrics:
  59. """Tests für SatelliteMetrics."""
  60. @pytest.fixture
  61. def metrics(self):
  62. """Erstellt SatelliteMetrics."""
  63. return SatelliteMetrics(satellite_id="sat-001")
  64. def test_initial_metrics(self, metrics):
  65. """Testet initiale Metriken."""
  66. assert metrics.satellite_id == "sat-001"
  67. assert metrics.latency_ms == 0.0
  68. assert metrics.messages_sent == 0
  69. assert metrics.error_count == 0
  70. def test_record_latency(self, metrics):
  71. """Testet Latenz-Aufzeichnung."""
  72. metrics.record_latency(15.5)
  73. assert metrics.latency_ms == 15.5
  74. assert len(metrics.latency_history) == 1
  75. def test_record_error(self, metrics):
  76. """Testet Fehler-Aufzeichnung."""
  77. metrics.record_error("Connection timeout")
  78. assert metrics.error_count == 1
  79. assert metrics.last_error == "Connection timeout"
  80. assert metrics.last_error_time is not None
  81. def test_get_avg_latency(self, metrics):
  82. """Testet Durchschnitts-Latenz."""
  83. metrics.record_latency(10.0)
  84. metrics.record_latency(20.0)
  85. metrics.record_latency(30.0)
  86. avg = metrics.get_avg_latency(300)
  87. assert avg == 20.0
  88. def test_to_dict(self, metrics):
  89. """Testet Dictionary-Konvertierung."""
  90. metrics.latency_ms = 15.0
  91. metrics.messages_sent = 100
  92. d = metrics.to_dict()
  93. assert d["satellite_id"] == "sat-001"
  94. assert d["network"]["latency_ms"] == 15.0
  95. assert d["network"]["messages_sent"] == 100
  96. class TestSatelliteMetricsCollector:
  97. """Tests für SatelliteMetricsCollector."""
  98. @pytest.fixture
  99. def collector(self):
  100. """Erstellt MetricsCollector."""
  101. return SatelliteMetricsCollector()
  102. def test_record_latency(self, collector):
  103. """Testet Latenz-Aufzeichnung."""
  104. collector.record_latency("sat-001", 15.5)
  105. metrics = collector.get_metrics("sat-001")
  106. assert metrics is not None
  107. assert metrics.latency_ms == 15.5
  108. def test_record_message_sent(self, collector):
  109. """Testet gesendete Nachricht."""
  110. collector.record_message_sent("sat-001", 256)
  111. metrics = collector.get_metrics("sat-001")
  112. assert metrics.messages_sent == 1
  113. assert metrics.bytes_sent == 256
  114. def test_record_message_received(self, collector):
  115. """Testet empfangene Nachricht."""
  116. collector.record_message_received("sat-001", 512)
  117. metrics = collector.get_metrics("sat-001")
  118. assert metrics.messages_received == 1
  119. assert metrics.bytes_received == 512
  120. def test_record_heartbeat(self, collector):
  121. """Testet Heartbeat-Aufzeichnung."""
  122. collector.record_heartbeat("sat-001")
  123. metrics = collector.get_metrics("sat-001")
  124. assert metrics.last_heartbeat is not None
  125. def test_record_reconnect(self, collector):
  126. """Testet Reconnect-Aufzeichnung."""
  127. collector.record_reconnect("sat-001")
  128. metrics = collector.get_metrics("sat-001")
  129. assert metrics.reconnect_count == 1
  130. def test_record_error(self, collector):
  131. """Testet Fehler-Aufzeichnung."""
  132. collector.record_error("sat-001", "Connection lost")
  133. metrics = collector.get_metrics("sat-001")
  134. assert metrics.error_count == 1
  135. def test_record_packet_loss(self, collector):
  136. """Testet Paketverlust-Aufzeichnung."""
  137. collector.record_packet_loss("sat-001", 2.5)
  138. metrics = collector.get_metrics("sat-001")
  139. assert metrics.packet_loss_percent == 2.5
  140. def test_record_bandwidth(self, collector):
  141. """Testet Bandbreiten-Aufzeichnung."""
  142. collector.record_bandwidth("sat-001", 1000.0)
  143. metrics = collector.get_metrics("sat-001")
  144. assert metrics.bandwidth_kbps == 1000.0
  145. def test_record_custom(self, collector):
  146. """Testet benutzerdefinierte Metrik."""
  147. collector.record_custom(
  148. "sat-001",
  149. "cpu_usage",
  150. 45.5,
  151. metric_type=MetricType.GAUGE,
  152. unit="%",
  153. )
  154. history = collector.get_custom_metric("sat-001", "cpu_usage")
  155. assert len(history) == 1
  156. assert history[0].value == 45.5
  157. def test_get_all_metrics(self, collector):
  158. """Testet Abruf aller Metriken."""
  159. collector.record_latency("sat-001", 10.0)
  160. collector.record_latency("sat-002", 20.0)
  161. all_metrics = collector.get_all_metrics()
  162. assert len(all_metrics) == 2
  163. assert "sat-001" in all_metrics
  164. assert "sat-002" in all_metrics
  165. def test_remove_satellite(self, collector):
  166. """Testet Satellite-Entfernung."""
  167. collector.record_latency("sat-001", 10.0)
  168. assert collector.remove_satellite("sat-001")
  169. assert collector.get_metrics("sat-001") is None
  170. def test_get_summary(self, collector):
  171. """Testet Zusammenfassung."""
  172. collector.record_latency("sat-001", 10.0)
  173. collector.record_latency("sat-002", 20.0)
  174. collector.record_error("sat-001", "Error")
  175. summary = collector.get_summary()
  176. assert summary["satellite_count"] == 2
  177. assert summary["avg_latency_ms"] == 15.0
  178. assert summary["total_errors"] == 1
  179. def test_on_metric_callback(self, collector):
  180. """Testet Metrik-Callback."""
  181. received = []
  182. def on_metric(sat_id, value):
  183. received.append((sat_id, value.name))
  184. collector.on_metric(on_metric)
  185. collector.record_latency("sat-001", 10.0)
  186. assert ("sat-001", "latency") in received
  187. def test_clear_all(self, collector):
  188. """Testet Löschen aller Metriken."""
  189. collector.record_latency("sat-001", 10.0)
  190. collector.record_latency("sat-002", 20.0)
  191. collector.clear_all()
  192. assert collector.satellite_count == 0
  193. class TestTimeWindow:
  194. """Tests für TimeWindow."""
  195. def test_window_values(self):
  196. """Testet Window-Werte."""
  197. assert TimeWindow.MINUTE.value == 60
  198. assert TimeWindow.FIVE_MINUTES.value == 300
  199. assert TimeWindow.HOUR.value == 3600
  200. assert TimeWindow.DAY.value == 86400
  201. class TestAggregationType:
  202. """Tests für AggregationType."""
  203. def test_aggregation_types(self):
  204. """Testet Aggregations-Typen."""
  205. assert AggregationType.SUM.name == "SUM"
  206. assert AggregationType.AVG.name == "AVG"
  207. assert AggregationType.MIN.name == "MIN"
  208. assert AggregationType.MAX.name == "MAX"
  209. assert AggregationType.P95.name == "P95"
  210. class TestAggregatedMetric:
  211. """Tests für AggregatedMetric."""
  212. def test_metric_creation(self):
  213. """Testet Metrik-Erstellung."""
  214. metric = AggregatedMetric(
  215. name="latency",
  216. aggregation=AggregationType.AVG,
  217. value=15.5,
  218. window=TimeWindow.FIVE_MINUTES,
  219. sample_count=100,
  220. start_time=datetime.now() - timedelta(minutes=5),
  221. end_time=datetime.now(),
  222. )
  223. assert metric.name == "latency"
  224. assert metric.value == 15.5
  225. assert metric.sample_count == 100
  226. def test_to_dict(self):
  227. """Testet Dictionary-Konvertierung."""
  228. metric = AggregatedMetric(
  229. name="test",
  230. aggregation=AggregationType.AVG,
  231. value=42.0,
  232. window=TimeWindow.MINUTE,
  233. sample_count=10,
  234. start_time=datetime.now(),
  235. end_time=datetime.now(),
  236. )
  237. d = metric.to_dict()
  238. assert d["name"] == "test"
  239. assert d["aggregation"] == "AVG"
  240. assert d["value"] == 42.0
  241. class TestMetricsAggregator:
  242. """Tests für MetricsAggregator."""
  243. @pytest.fixture
  244. def collector(self):
  245. """Erstellt Collector mit Testdaten."""
  246. collector = SatelliteMetricsCollector()
  247. # Testdaten hinzufügen
  248. collector.record_latency("sat-001", 10.0)
  249. collector.record_latency("sat-002", 20.0)
  250. collector.record_latency("sat-003", 30.0)
  251. collector.record_error("sat-001", "Error 1")
  252. collector.record_error("sat-002", "Error 2")
  253. return collector
  254. @pytest.fixture
  255. def aggregator(self, collector):
  256. """Erstellt Aggregator."""
  257. return MetricsAggregator(collector)
  258. def test_aggregate_avg(self, aggregator):
  259. """Testet Durchschnitts-Aggregation."""
  260. result = aggregator.aggregate(
  261. "latency",
  262. AggregationType.AVG,
  263. TimeWindow.HOUR,
  264. )
  265. assert result.value == 20.0
  266. assert result.sample_count == 3
  267. def test_aggregate_sum(self, aggregator):
  268. """Testet Summen-Aggregation."""
  269. result = aggregator.aggregate(
  270. "latency",
  271. AggregationType.SUM,
  272. TimeWindow.HOUR,
  273. )
  274. assert result.value == 60.0
  275. def test_aggregate_min(self, aggregator):
  276. """Testet Minimum-Aggregation."""
  277. result = aggregator.aggregate(
  278. "latency",
  279. AggregationType.MIN,
  280. TimeWindow.HOUR,
  281. )
  282. assert result.value == 10.0
  283. def test_aggregate_max(self, aggregator):
  284. """Testet Maximum-Aggregation."""
  285. result = aggregator.aggregate(
  286. "latency",
  287. AggregationType.MAX,
  288. TimeWindow.HOUR,
  289. )
  290. assert result.value == 30.0
  291. def test_aggregate_count(self, aggregator):
  292. """Testet Zähl-Aggregation."""
  293. result = aggregator.aggregate(
  294. "latency",
  295. AggregationType.COUNT,
  296. TimeWindow.HOUR,
  297. )
  298. assert result.value == 3.0
  299. def test_aggregate_median(self, aggregator):
  300. """Testet Median-Aggregation."""
  301. result = aggregator.aggregate(
  302. "latency",
  303. AggregationType.MEDIAN,
  304. TimeWindow.HOUR,
  305. )
  306. assert result.value == 20.0
  307. def test_aggregate_multiple(self, aggregator):
  308. """Testet mehrfache Aggregation."""
  309. results = aggregator.aggregate_multiple(
  310. ["latency", "error_count"],
  311. [AggregationType.AVG, AggregationType.SUM],
  312. )
  313. assert "latency" in results
  314. assert "error_count" in results
  315. assert "AVG" in results["latency"]
  316. assert "SUM" in results["latency"]
  317. def test_get_top_satellites(self, aggregator):
  318. """Testet Top-Satellites."""
  319. top = aggregator.get_top_satellites("latency", limit=2)
  320. assert len(top) == 2
  321. # Höchste Latenz zuerst
  322. assert top[0][1] == 30.0
  323. def test_get_top_satellites_ascending(self, aggregator):
  324. """Testet Top-Satellites aufsteigend."""
  325. top = aggregator.get_top_satellites(
  326. "latency",
  327. limit=2,
  328. ascending=True,
  329. )
  330. # Niedrigste Latenz zuerst
  331. assert top[0][1] == 10.0
  332. def test_get_metric_distribution(self, aggregator):
  333. """Testet Metrik-Verteilung."""
  334. dist = aggregator.get_metric_distribution(
  335. "latency",
  336. buckets=3,
  337. )
  338. assert "buckets" in dist
  339. assert dist["min"] == 10.0
  340. assert dist["max"] == 30.0
  341. assert dist["count"] == 3
  342. def test_get_satellite_comparison(self, aggregator):
  343. """Testet Satellite-Vergleich."""
  344. comparison = aggregator.get_satellite_comparison(
  345. "latency",
  346. ["sat-001", "sat-002"],
  347. )
  348. assert "sat-001" in comparison
  349. assert "sat-002" in comparison
  350. assert comparison["sat-001"]["current"] == 10.0
  351. assert comparison["sat-002"]["current"] == 20.0
  352. def test_get_health_summary(self, aggregator):
  353. """Testet Gesundheits-Übersicht."""
  354. summary = aggregator.get_health_summary()
  355. assert "status" in summary
  356. assert "metrics" in summary
  357. assert "satellite_count" in summary
  358. def test_cache_functionality(self, aggregator):
  359. """Testet Cache-Funktionalität."""
  360. # Erste Abfrage
  361. result1 = aggregator.aggregate("latency", AggregationType.AVG)
  362. # Zweite Abfrage (sollte aus Cache kommen)
  363. result2 = aggregator.aggregate("latency", AggregationType.AVG)
  364. assert result1.value == result2.value
  365. def test_clear_cache(self, aggregator):
  366. """Testet Cache-Löschen."""
  367. aggregator.aggregate("latency", AggregationType.AVG)
  368. assert len(aggregator._cache) > 0
  369. aggregator.clear_cache()
  370. assert len(aggregator._cache) == 0