@@ -16,33 +16,65 @@ import (
1616// burst 丢包突发 —— 单轮丢包数相对历史分布的 robust z 超阈,30 分钟内 N 次升级为告警
1717// degrade P99 劣化 —— 近 15 分钟 P99 相对 1 小时前基线超倍数,连续 3 次确认
1818type Detector struct {
19- mu sync.Mutex
20- cfg AlertCfg
21- store * Store
22- notify * Notifier
23- states map [string ]* tstate
19+ mu sync.Mutex
20+ cfg AlertCfg
21+ store * Store
22+ notify * Notifier
23+ states map [string ]* tstate
24+ targets map [string ]TargetCfg // 告警事件需要 host/extra/敏感度
25+ extra map [string ]string // 全局自定义字段
26+ }
27+
28+ // sensParams 敏感度三档:对基准阈值的整体缩放。
29+ // strict 给核心链路(早叫),relaxed 给天生就抖的公网链路(少叫)。
30+ type sensParams struct {
31+ ratio float64 // P99 劣化倍数
32+ burstN int // 30 分钟内突发次数升级线
33+ streak int // 劣化连续确认次数
34+ }
35+
36+ func sensOf (t TargetCfg , base AlertCfg ) sensParams {
37+ switch t .Sensitivity {
38+ case "strict" :
39+ return sensParams {1.3 , 2 , 2 }
40+ case "relaxed" :
41+ return sensParams {2.0 , 5 , 5 }
42+ default :
43+ return sensParams {base .DegradeRatio , base .BurstAlertN , 3 }
44+ }
2445}
2546
2647type tstate struct {
2748 degradeStreak int // 连续劣化确认次数
2849 alertActive bool
2950 alertKind string
51+ alertSince int64 // 本次告警的开始时间
3052 lastNotify int64
3153 clearSince int64 // 条件消失的起点(恢复确认用)
32- // 告警证据(推送和 UI 共用)
33- evP99Base , evP99Cur float64
34- evBursts int
35- evWorstLoss float64
3654}
3755
3856func NewDetector (cfg * Config , store * Store , n * Notifier ) * Detector {
39- d := & Detector {cfg : cfg .Alerts , store : store , notify : n , states : map [string ]* tstate {}}
57+ d := & Detector {cfg : cfg .Alerts , store : store , notify : n ,
58+ states : map [string ]* tstate {}, targets : map [string ]TargetCfg {}, extra : cfg .Extra }
4059 for _ , t := range cfg .Targets {
4160 d .states [t .Name ] = & tstate {}
61+ d .targets [t .Name ] = t
4262 }
4363 return d
4464}
4565
66+ // mergedExtra 全局字段 + 目标字段,目标覆盖同名键。
67+ func (d * Detector ) mergedExtra (t TargetCfg ) map [string ]string {
68+ out := map [string ]string {}
69+ for k , v := range d .extra {
70+ out [k ] = v
71+ }
72+ for k , v := range t .Extra {
73+ out [k ] = v
74+ }
75+ return out
76+ }
77+
4678// CheckBurst 在落盘前判定当前轮是否丢包突发(基线不含当前轮)。
4779func (d * Detector ) CheckBurst (name string , r Round ) bool {
4880 loss := r .S - r .R
@@ -98,6 +130,11 @@ func median(s []float64) float64 {
98130
99131// AfterAppend 是每轮落盘后的完整评估:劣化检查 + 突发计数 + 状态机。
100132func (d * Detector ) AfterAppend (name string ) {
133+ tc := d .targets [name ]
134+ if tc .Alerts != nil && ! * tc .Alerts {
135+ return // 纯观测目标:烟雾图和突发标记照常,但永远不叫
136+ }
137+ sp := sensOf (tc , d .cfg )
101138 now := time .Now ()
102139 ring24 := d .store .Recent (name , now .Add (- 24 * time .Hour ).Unix ())
103140
@@ -121,7 +158,7 @@ func (d *Detector) AfterAppend(name string) {
121158 sort .Float64s (curPool )
122159 sort .Float64s (basePool )
123160 p99c , p99b = pct (curPool , 99 ), pct (basePool , 99 )
124- degrade = p99c > p99b * d . cfg . DegradeRatio && p99c - p99b > d .cfg .DegradeMinMs
161+ degrade = p99c > p99b * sp . ratio && p99c - p99b > d .cfg .DegradeMinMs
125162 }
126163
127164 // --- 突发计数(30 分钟窗) ---
@@ -143,12 +180,13 @@ func (d *Detector) AfterAppend(name string) {
143180 } else {
144181 st .degradeStreak = 0
145182 }
146- degradeAlert := st .degradeStreak >= 3
147- burstAlert := bursts30 >= d . cfg . BurstAlertN
183+ degradeAlert := st .degradeStreak >= sp . streak
184+ burstAlert := bursts30 >= sp . burstN
148185 condition := degradeAlert || burstAlert
149186
150187 var fire , recovered bool
151188 var kind string
189+ var since int64
152190 if condition {
153191 st .clearSince = 0
154192 switch {
@@ -161,11 +199,13 @@ func (d *Detector) AfterAppend(name string) {
161199 }
162200 cooldown := int64 (d .cfg .CooldownMin ) * 60
163201 if ! st .alertActive || now .Unix ()- st .lastNotify >= cooldown {
202+ if ! st .alertActive {
203+ st .alertSince = now .Unix ()
204+ }
164205 fire = true
165206 st .alertActive = true
166207 st .alertKind = kind
167208 st .lastNotify = now .Unix ()
168- st .evP99Base , st .evP99Cur , st .evBursts , st .evWorstLoss = p99b , p99c , bursts30 , worstLoss
169209 }
170210 } else if st .alertActive {
171211 if st .clearSince == 0 {
@@ -177,17 +217,31 @@ func (d *Detector) AfterAppend(name string) {
177217 kind = st .alertKind
178218 }
179219 }
180- ev := * st
220+ since = st . alertSince
181221 d .mu .Unlock ()
182222
183223 if fire {
184224 log .Printf ("[%s] 告警: %s" , name , kind )
185- d .notify .SendAlert (name , kind , ev .evP99Base , ev .evP99Cur , ev .evBursts , ev .evWorstLoss )
225+ d .notify .SendAlert (AlertEvent {
226+ Target : name , Host : targetAddr (tc ), Kind : kind ,
227+ P99Base : p99b , P99Cur : p99c , Bursts : bursts30 , WorstLoss : worstLoss ,
228+ Since : since , Extra : d .mergedExtra (tc ),
229+ })
186230 }
187231 if recovered {
188232 log .Printf ("[%s] 恢复: %s" , name , kind )
189- d .notify .SendRecovery (name , kind )
233+ d .notify .SendRecovery (AlertEvent {
234+ Target : name , Host : targetAddr (tc ), Kind : kind ,
235+ Since : since , Extra : d .mergedExtra (tc ),
236+ })
237+ }
238+ }
239+
240+ func targetAddr (t TargetCfg ) string {
241+ if t .Type == "tcp" {
242+ return fmt .Sprintf ("%s:%d" , t .Host , t .Port )
190243 }
244+ return t .Host
191245}
192246
193247// Status 给 Web 和报告用的目标状态快照。
0 commit comments