108 lines
4.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import unittest
from detector.engine import DetectionEngine, aggregate_value, compare
from detector.models import MetricRule, MetricSample, RuleScope
from detector.rule_loader import RuleLoader
from detector.storage import InMemoryCache, InMemoryRuleStore
def make_rule(rule_id="r-cpu", metric="cpu_usage", operator="gt", threshold=90.0, for_duration="60s"):
return MetricRule(
rule_id=rule_id,
name="cpu high",
metric=metric,
aggregation="avg",
operator=operator,
threshold=threshold,
for_duration=for_duration,
severity="critical",
scope=RuleScope(scope_type="all"),
)
def make_engine(rule):
store = InMemoryRuleStore()
store.create_rule(rule)
loader = RuleLoader(store, InMemoryCache())
loader.load()
return DetectionEngine(loader, evaluation_interval="15s", recover_duration="15s")
class TestAggregateCompare(unittest.TestCase):
def test_aggregate(self):
self.assertEqual(aggregate_value([1, 2, 3], "avg"), 2)
self.assertEqual(aggregate_value([1, 2, 3], "min"), 1)
self.assertEqual(aggregate_value([1, 2, 3], "max"), 3)
self.assertEqual(aggregate_value([1, 2, 3], "sum"), 6)
self.assertEqual(aggregate_value([1, 2, 3], "last"), 3)
def test_compare(self):
self.assertTrue(compare(91, "gt", 90))
self.assertFalse(compare(90, "gt", 90))
self.assertTrue(compare(90, "gte", 90))
self.assertTrue(compare(89, "lt", 90))
self.assertTrue(compare(95, "between", 90, 100))
self.assertFalse(compare(105, "between", 90, 100))
class TestStateMachine(unittest.TestCase):
def test_firing_after_sustained(self):
rule = make_rule(for_duration="60s")
engine = make_engine(rule)
events = []
for i in range(5):
ts = float(i * 15)
sample = MetricSample(name="cpu_usage", value=95.0, timestamp=ts)
events.extend(engine.handle(sample, "h-1"))
self.assertEqual(len(events), 1)
self.assertEqual(events[0].status, "firing")
self.assertEqual(engine.state_of("h-1", rule.rule_id)["status"], "firing")
def test_no_fire_before_for_duration(self):
rule = make_rule(for_duration="60s")
engine = make_engine(rule)
events = []
for i in range(3):
ts = float(i * 15)
events.extend(engine.handle(MetricSample(name="cpu_usage", value=95.0, timestamp=ts), "h-1"))
self.assertEqual(events, [])
self.assertEqual(engine.state_of("h-1", rule.rule_id)["status"], "ok")
def test_resolved(self):
rule = make_rule(for_duration="60s")
engine = make_engine(rule)
events = []
for i in range(5):
events.extend(engine.handle(MetricSample(name="cpu_usage", value=95.0, timestamp=float(i * 15)), "h-1"))
self.assertEqual(engine.state_of("h-1", rule.rule_id)["status"], "firing")
# 一次回落进入 pending
events.clear()
events.extend(engine.handle(MetricSample(name="cpu_usage", value=50.0, timestamp=75.0), "h-1"))
self.assertEqual(engine.state_of("h-1", rule.rule_id)["status"], "pending")
# 观察期后仍回落 -> resolved
events.clear()
events.extend(engine.handle(MetricSample(name="cpu_usage", value=50.0, timestamp=90.0), "h-1"))
self.assertEqual(len(events), 1)
self.assertEqual(events[0].status, "resolved")
self.assertEqual(engine.state_of("h-1", rule.rule_id)["status"], "ok")
def test_pending_back_to_firing_no_duplicate_event(self):
rule = make_rule(for_duration="60s")
engine = make_engine(rule)
for i in range(5):
engine.handle(MetricSample(name="cpu_usage", value=95.0, timestamp=float(i * 15)), "h-1")
engine.handle(MetricSample(name="cpu_usage", value=50.0, timestamp=75.0), "h-1")
self.assertEqual(engine.state_of("h-1", rule.rule_id)["status"], "pending")
# 观察期内再次满足(低值样本滑出窗口后聚合值回升)-> 回到 firing不重复触发
events = engine.handle(MetricSample(name="cpu_usage", value=96.0, timestamp=150.0), "h-1")
self.assertEqual(events, [])
self.assertEqual(engine.state_of("h-1", rule.rule_id)["status"], "firing")
if __name__ == "__main__":
unittest.main()