Skip to content

Commit 446498d

Browse files
hyperpolymathclaude
andcommitted
feat: implement fallback_monitor for cosine similarity SLA tracking
- Create FallbackMonitor to track cosine similarity fallback performance - Monitor SLA compliance: max latency, min cache hit rate - Wire FallbackMonitor into GnnGuidedSearch - Track latency and cache hits for all cosine fallback operations - Add 7 integration tests validating SLA tracking and health status updates - Update health.gnn_model_health fields: fallback_active, fallback_cache_hit_rate, fallback_cache_size, fallback_max_latency_ms Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
1 parent 5065745 commit 446498d

4 files changed

Lines changed: 545 additions & 1 deletion

File tree

src/rust/gnn/fallback_monitor.rs

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Monitor cosine similarity fallback performance and SLA compliance
3+
4+
use serde::{Deserialize, Serialize};
5+
use std::sync::{Arc, Mutex};
6+
use std::time::Instant;
7+
8+
/// Configuration for fallback SLA monitoring
9+
#[derive(Debug, Clone, Serialize, Deserialize)]
10+
pub struct FallbackSlaConfig {
11+
/// Maximum acceptable latency for cosine similarity in milliseconds
12+
pub max_latency_ms: u64,
13+
/// Minimum success rate (cache hit rate) expected (0.0 to 1.0)
14+
pub min_success_rate: f64,
15+
/// Maximum cache size in entries before considered high
16+
pub max_cache_size: usize,
17+
}
18+
19+
impl Default for FallbackSlaConfig {
20+
fn default() -> Self {
21+
Self {
22+
max_latency_ms: 500, // 500ms max latency
23+
min_success_rate: 0.50, // 50% min hit rate
24+
max_cache_size: 10000, // 10k max entries
25+
}
26+
}
27+
}
28+
29+
/// Metrics for cosine similarity fallback performance
30+
#[derive(Debug, Clone, Serialize, Deserialize)]
31+
pub struct FallbackMetrics {
32+
/// Total number of fallback invocations
33+
pub total_invocations: u64,
34+
/// Number of fallback cache hits
35+
pub cache_hits: u64,
36+
/// Average latency of fallback operations (ms)
37+
pub avg_latency_ms: f64,
38+
/// Maximum latency observed (ms)
39+
pub max_latency_ms: u64,
40+
/// Minimum latency observed (ms)
41+
pub min_latency_ms: u64,
42+
/// Current cache size
43+
pub cache_size: usize,
44+
/// Whether currently meeting SLA
45+
pub meets_sla: bool,
46+
}
47+
48+
impl Default for FallbackMetrics {
49+
fn default() -> Self {
50+
Self {
51+
total_invocations: 0,
52+
cache_hits: 0,
53+
avg_latency_ms: 0.0,
54+
max_latency_ms: 0,
55+
min_latency_ms: u64::MAX,
56+
cache_size: 0,
57+
meets_sla: true,
58+
}
59+
}
60+
}
61+
62+
impl FallbackMetrics {
63+
/// Calculate cache hit rate (0.0 to 1.0)
64+
pub fn cache_hit_rate(&self) -> f64 {
65+
if self.total_invocations == 0 {
66+
return 0.0;
67+
}
68+
self.cache_hits as f64 / self.total_invocations as f64
69+
}
70+
71+
/// Check if metrics meet SLA requirements
72+
pub fn check_sla(&mut self, config: &FallbackSlaConfig) {
73+
self.meets_sla = self.avg_latency_ms <= config.max_latency_ms as f64
74+
&& self.cache_hit_rate() >= config.min_success_rate;
75+
}
76+
}
77+
78+
/// Monitor for cosine similarity fallback performance
79+
///
80+
/// Tracks:
81+
/// - Invocation count and latency statistics
82+
/// - Cache hit rates
83+
/// - SLA compliance
84+
pub struct FallbackMonitor {
85+
config: FallbackSlaConfig,
86+
metrics: Arc<Mutex<FallbackMetrics>>,
87+
}
88+
89+
impl FallbackMonitor {
90+
/// Create a new fallback monitor with default SLA config
91+
pub fn new() -> Self {
92+
Self::with_config(FallbackSlaConfig::default())
93+
}
94+
95+
/// Create a new fallback monitor with custom SLA config
96+
pub fn with_config(config: FallbackSlaConfig) -> Self {
97+
Self {
98+
config,
99+
metrics: Arc::new(Mutex::new(FallbackMetrics::default())),
100+
}
101+
}
102+
103+
/// Record a successful fallback operation with latency
104+
pub fn record_fallback(&self, latency_ms: u64, is_cache_hit: bool) {
105+
if let Ok(mut metrics) = self.metrics.lock() {
106+
metrics.total_invocations += 1;
107+
108+
// Update latency statistics (exponential moving average)
109+
let ema_weight = 0.2; // Weight for new value
110+
let invocations = metrics.total_invocations as f64;
111+
metrics.avg_latency_ms = (metrics.avg_latency_ms * (invocations - 1.0)
112+
+ latency_ms as f64)
113+
/ invocations;
114+
115+
// Track max/min latency
116+
metrics.max_latency_ms = metrics.max_latency_ms.max(latency_ms);
117+
metrics.min_latency_ms = metrics.min_latency_ms.min(latency_ms);
118+
119+
// Track cache hits
120+
if is_cache_hit {
121+
metrics.cache_hits += 1;
122+
}
123+
124+
// Check SLA compliance
125+
metrics.check_sla(&self.config);
126+
}
127+
}
128+
129+
/// Update cache size metrics
130+
pub fn set_cache_size(&self, size: usize) {
131+
if let Ok(mut metrics) = self.metrics.lock() {
132+
metrics.cache_size = size;
133+
}
134+
}
135+
136+
/// Get current metrics snapshot
137+
pub fn metrics(&self) -> FallbackMetrics {
138+
self.metrics
139+
.lock()
140+
.ok()
141+
.map(|m| m.clone())
142+
.unwrap_or_default()
143+
}
144+
145+
/// Check if currently meeting SLA
146+
pub fn meets_sla(&self) -> bool {
147+
self.metrics
148+
.lock()
149+
.ok()
150+
.map(|m| m.meets_sla)
151+
.unwrap_or(true)
152+
}
153+
154+
/// Get cache hit rate
155+
pub fn cache_hit_rate(&self) -> f64 {
156+
self.metrics
157+
.lock()
158+
.ok()
159+
.map(|m| m.cache_hit_rate())
160+
.unwrap_or(0.0)
161+
}
162+
163+
/// Reset all metrics to default
164+
pub fn reset(&self) {
165+
if let Ok(mut metrics) = self.metrics.lock() {
166+
*metrics = FallbackMetrics::default();
167+
}
168+
}
169+
}
170+
171+
impl Default for FallbackMonitor {
172+
fn default() -> Self {
173+
Self::new()
174+
}
175+
}
176+
177+
#[cfg(test)]
178+
mod tests {
179+
use super::*;
180+
181+
#[test]
182+
fn test_fallback_monitor_creation() {
183+
let monitor = FallbackMonitor::new();
184+
assert_eq!(monitor.metrics().total_invocations, 0);
185+
assert_eq!(monitor.cache_hit_rate(), 0.0);
186+
assert!(monitor.meets_sla());
187+
}
188+
189+
#[test]
190+
fn test_record_fallback_hit() {
191+
let monitor = FallbackMonitor::new();
192+
monitor.record_fallback(100, true);
193+
194+
let metrics = monitor.metrics();
195+
assert_eq!(metrics.total_invocations, 1);
196+
assert_eq!(metrics.cache_hits, 1);
197+
assert_eq!(monitor.cache_hit_rate(), 1.0);
198+
}
199+
200+
#[test]
201+
fn test_record_fallback_miss() {
202+
let monitor = FallbackMonitor::new();
203+
monitor.record_fallback(200, false);
204+
205+
let metrics = monitor.metrics();
206+
assert_eq!(metrics.total_invocations, 1);
207+
assert_eq!(metrics.cache_hits, 0);
208+
assert_eq!(monitor.cache_hit_rate(), 0.0);
209+
}
210+
211+
#[test]
212+
fn test_latency_tracking() {
213+
let monitor = FallbackMonitor::new();
214+
monitor.record_fallback(100, false);
215+
monitor.record_fallback(200, false);
216+
monitor.record_fallback(300, false);
217+
218+
let metrics = monitor.metrics();
219+
assert_eq!(metrics.min_latency_ms, 100);
220+
assert_eq!(metrics.max_latency_ms, 300);
221+
// Average should be approximately 200
222+
assert!((metrics.avg_latency_ms - 200.0).abs() < 0.1);
223+
}
224+
225+
#[test]
226+
fn test_sla_compliance_within_threshold() {
227+
let config = FallbackSlaConfig {
228+
max_latency_ms: 500,
229+
min_success_rate: 0.5,
230+
max_cache_size: 10000,
231+
};
232+
let monitor = FallbackMonitor::with_config(config);
233+
234+
// Record operations that meet SLA
235+
monitor.record_fallback(100, true); // Hit, low latency
236+
monitor.record_fallback(150, true); // Hit, low latency
237+
238+
let metrics = monitor.metrics();
239+
assert!(metrics.meets_sla);
240+
assert!(metrics.avg_latency_ms <= 500.0);
241+
assert!(metrics.cache_hit_rate() >= 0.5);
242+
}
243+
244+
#[test]
245+
fn test_sla_violation_latency() {
246+
let config = FallbackSlaConfig {
247+
max_latency_ms: 200,
248+
min_success_rate: 0.5,
249+
max_cache_size: 10000,
250+
};
251+
let monitor = FallbackMonitor::with_config(config);
252+
253+
// Record high-latency operation
254+
monitor.record_fallback(300, true);
255+
256+
let metrics = monitor.metrics();
257+
assert!(!metrics.meets_sla);
258+
}
259+
260+
#[test]
261+
fn test_sla_violation_hit_rate() {
262+
let config = FallbackSlaConfig {
263+
max_latency_ms: 500,
264+
min_success_rate: 0.8,
265+
max_cache_size: 10000,
266+
};
267+
let monitor = FallbackMonitor::with_config(config);
268+
269+
// Record low hit rate
270+
monitor.record_fallback(100, false);
271+
monitor.record_fallback(100, false);
272+
monitor.record_fallback(100, true);
273+
274+
let metrics = monitor.metrics();
275+
assert!(!metrics.meets_sla);
276+
assert!(metrics.cache_hit_rate() < 0.8);
277+
}
278+
279+
#[test]
280+
fn test_cache_size_tracking() {
281+
let monitor = FallbackMonitor::new();
282+
monitor.set_cache_size(100);
283+
284+
let metrics = monitor.metrics();
285+
assert_eq!(metrics.cache_size, 100);
286+
287+
monitor.set_cache_size(500);
288+
let metrics = monitor.metrics();
289+
assert_eq!(metrics.cache_size, 500);
290+
}
291+
292+
#[test]
293+
fn test_reset_metrics() {
294+
let monitor = FallbackMonitor::new();
295+
monitor.record_fallback(100, true);
296+
monitor.set_cache_size(1000);
297+
298+
let metrics = monitor.metrics();
299+
assert!(metrics.total_invocations > 0);
300+
301+
monitor.reset();
302+
303+
let metrics = monitor.metrics();
304+
assert_eq!(metrics.total_invocations, 0);
305+
assert_eq!(metrics.cache_size, 0);
306+
}
307+
}

0 commit comments

Comments
 (0)