Add typed jevdecide HTTP client (circuit, metrics) and coordinator DecideAssist wiring behind JEV_DECIDE_MODE (default off). Offline eval and synthetic soak evidence gate realtime/profile before assist rollout.
61 lines
1.3 KiB
Go
61 lines
1.3 KiB
Go
package jevdecide
|
|
|
|
import "sync/atomic"
|
|
|
|
// RolloutMetrics tracks shadow/assist outcomes (process-local, no prometheus).
|
|
type RolloutMetrics struct {
|
|
Agree atomic.Int64
|
|
Disagree atomic.Int64
|
|
JevFail atomic.Int64
|
|
AssistHit atomic.Int64
|
|
LatencyMs atomic.Int64 // cumulative ms for latency / (agree+disagree+fail)
|
|
Calls atomic.Int64
|
|
}
|
|
|
|
// Global metrics singleton for process-wide soak observation.
|
|
var Global = &RolloutMetrics{}
|
|
|
|
// Snapshot returns a copy of current counters.
|
|
func (m *RolloutMetrics) Snapshot() map[string]int64 {
|
|
if m == nil {
|
|
return map[string]int64{}
|
|
}
|
|
calls := m.Calls.Load()
|
|
lat := m.LatencyMs.Load()
|
|
avg := int64(0)
|
|
if calls > 0 {
|
|
avg = lat / calls
|
|
}
|
|
return map[string]int64{
|
|
"agree": m.Agree.Load(),
|
|
"disagree": m.Disagree.Load(),
|
|
"jev_fail": m.JevFail.Load(),
|
|
"assist_hit": m.AssistHit.Load(),
|
|
"calls": calls,
|
|
"latency_ms_sum": lat,
|
|
"latency_ms_avg": avg,
|
|
}
|
|
}
|
|
|
|
// Reset clears counters (tests / soak windows).
|
|
func (m *RolloutMetrics) Reset() {
|
|
if m == nil {
|
|
return
|
|
}
|
|
m.Agree.Store(0)
|
|
m.Disagree.Store(0)
|
|
m.JevFail.Store(0)
|
|
m.AssistHit.Store(0)
|
|
m.LatencyMs.Store(0)
|
|
m.Calls.Store(0)
|
|
}
|
|
|
|
// RecordCall accumulates one decide latency sample.
|
|
func (m *RolloutMetrics) RecordCall(latencyMs int64) {
|
|
if m == nil {
|
|
return
|
|
}
|
|
m.Calls.Add(1)
|
|
m.LatencyMs.Add(latencyMs)
|
|
}
|