scirs2_vision/streaming_modules/
performance.rs1use crate::error::Result;
8use std::time::{Duration, Instant};
9
10pub struct AdaptivePerformanceMonitor {
26 stage_metrics: std::collections::HashMap<String, StagePerformanceMetrics>,
28 resource_monitor: SystemResourceMonitor,
30 thread_pool_manager: AutoScalingThreadPoolManager,
32 config: AdaptiveConfig,
34 performance_history: std::collections::VecDeque<PerformanceSnapshot>,
36 last_adaptation: Instant,
38}
39
40#[derive(Debug, Clone)]
42pub struct StagePerformanceMetrics {
43 pub stagename: String,
45 pub processing_times: std::collections::VecDeque<Duration>,
47 pub avg_processing_time: Duration,
49 pub peak_processing_time: Duration,
51 pub frames_processed: usize,
53 pub dropped_frames: usize,
55 pub queue_depth: usize,
57 pub thread_utilization: f32,
59 pub memory_usage: usize,
61 pub throughput: f32,
63 pub bottleneck_score: f32,
65}
66
67#[derive(Debug, Clone)]
69pub struct SystemResourceMonitor {
70 pub cpu_usage: f32,
72 pub memory_usage: usize,
74 pub available_memory: usize,
76 pub total_threads: usize,
78 pub load_average: f32,
80}
81
82pub struct AutoScalingThreadPoolManager {
84 thread_pools: std::collections::HashMap<String, ThreadPoolConfig>,
86 min_threads: usize,
88 maxthreads: usize,
90 scale_up_threshold: f32,
92 scale_down_threshold: f32,
94}
95
96#[derive(Debug, Clone)]
98pub struct ThreadPoolConfig {
99 pub stagename: String,
101 pub current_threads: usize,
103 pub target_threads: usize,
105 pub last_scaled: Instant,
107 pub cooldown_period: Duration,
109}
110
111#[derive(Debug, Clone)]
113pub struct AdaptiveConfig {
114 pub monitoring_window: usize,
116 pub adaptation_interval: Duration,
118 pub bottleneck_threshold: f32,
120 pub memory_warning_threshold: usize,
122 pub cpu_warning_threshold: f32,
124 pub enable_predictive_scaling: bool,
126}
127
128#[derive(Debug, Clone)]
130pub struct PerformanceSnapshot {
131 pub timestamp: Instant,
133 pub pipeline_throughput: f32,
135 pub pipeline_latency: Duration,
137 pub resource_usage: SystemResourceMonitor,
139 pub bottlenecks: Vec<String>,
141}
142
143impl Default for AdaptiveConfig {
144 fn default() -> Self {
145 Self {
146 monitoring_window: 100,
147 adaptation_interval: Duration::from_secs(2),
148 bottleneck_threshold: 0.8,
149 memory_warning_threshold: 1_073_741_824, cpu_warning_threshold: 80.0,
151 enable_predictive_scaling: true,
152 }
153 }
154}
155
156impl Default for SystemResourceMonitor {
157 fn default() -> Self {
158 Self {
159 cpu_usage: 0.0,
160 memory_usage: 0,
161 available_memory: 1_073_741_824, total_threads: 1,
163 load_average: 0.0,
164 }
165 }
166}
167
168impl AutoScalingThreadPoolManager {
169 pub fn new(min_threads: usize, maxthreads: usize) -> Self {
180 Self {
181 thread_pools: std::collections::HashMap::new(),
182 min_threads,
183 maxthreads,
184 scale_up_threshold: 75.0, scale_down_threshold: 25.0, }
187 }
188
189 pub fn register_stage(&mut self, stagename: &str, initialthreads: usize) -> Result<()> {
200 let config = ThreadPoolConfig {
201 stagename: stagename.to_string(),
202 current_threads: initialthreads.clamp(self.min_threads, self.maxthreads),
203 target_threads: initialthreads.clamp(self.min_threads, self.maxthreads),
204 last_scaled: Instant::now(),
205 cooldown_period: Duration::from_secs(5),
206 };
207
208 self.thread_pools.insert(stagename.to_string(), config);
209 Ok(())
210 }
211
212 pub fn adapt_thread_count(
223 &mut self,
224 stagename: &str,
225 metrics: &StagePerformanceMetrics,
226 ) -> usize {
227 if let Some(config) = self.thread_pools.get_mut(stagename) {
228 let now = Instant::now();
229
230 if now.duration_since(config.last_scaled) < config.cooldown_period {
232 return config.current_threads;
233 }
234
235 let utilization = metrics.thread_utilization;
236 let bottleneck_score = metrics.bottleneck_score;
237
238 let scale_factor = if utilization > self.scale_up_threshold || bottleneck_score > 0.7 {
240 if config.current_threads < self.maxthreads {
242 let scale_amount =
243 ((utilization - self.scale_up_threshold) / 25.0).ceil() as i32;
244 scale_amount.max(1)
245 } else {
246 0
247 }
248 } else if utilization < self.scale_down_threshold && bottleneck_score < 0.3 {
249 if config.current_threads > self.min_threads {
251 let scale_amount =
252 ((self.scale_down_threshold - utilization) / 25.0).ceil() as i32;
253 -(scale_amount.max(1))
254 } else {
255 0
256 }
257 } else {
258 0
259 };
260
261 if scale_factor != 0 {
262 let new_thread_count = if scale_factor > 0 {
263 (config.current_threads + scale_factor as usize).min(self.maxthreads)
264 } else {
265 ((config.current_threads as i32 + scale_factor).max(self.min_threads as i32))
266 as usize
267 };
268
269 config.target_threads = new_thread_count;
270 config.current_threads = new_thread_count;
271 config.last_scaled = now;
272
273 let old_thread_count = if scale_factor > 0 {
274 config.current_threads - scale_factor as usize
275 } else {
276 config.current_threads + (-scale_factor) as usize
277 };
278
279 eprintln!(
280 "Scaled {stagename} from {old_thread_count} to {new_thread_count} threads (utilization: {utilization:.1}%, bottleneck: {bottleneck_score:.2})"
281 );
282 }
283
284 config.current_threads
285 } else {
286 self.min_threads
288 }
289 }
290
291 pub fn get_stage_config(&self, stagename: &str) -> Option<&ThreadPoolConfig> {
301 self.thread_pools.get(stagename)
302 }
303
304 pub fn get_registered_stages(&self) -> Vec<String> {
310 self.thread_pools.keys().cloned().collect()
311 }
312}
313
314impl AdaptivePerformanceMonitor {
315 pub fn new(config: AdaptiveConfig) -> Self {
325 Self {
326 stage_metrics: std::collections::HashMap::new(),
327 resource_monitor: SystemResourceMonitor::default(),
328 thread_pool_manager: AutoScalingThreadPoolManager::new(1, 8),
329 config,
330 performance_history: std::collections::VecDeque::with_capacity(100),
331 last_adaptation: Instant::now(),
332 }
333 }
334
335 pub fn record_stage_metrics(
344 &mut self,
345 stagename: &str,
346 processing_time: Duration,
347 queue_depth: usize,
348 memory_usage: usize,
349 ) {
350 let metrics = self
351 .stage_metrics
352 .entry(stagename.to_string())
353 .or_insert_with(|| StagePerformanceMetrics {
354 stagename: stagename.to_string(),
355 processing_times: std::collections::VecDeque::with_capacity(
356 self.config.monitoring_window,
357 ),
358 avg_processing_time: Duration::ZERO,
359 peak_processing_time: Duration::ZERO,
360 frames_processed: 0,
361 dropped_frames: 0,
362 queue_depth: 0,
363 thread_utilization: 0.0,
364 memory_usage: 0,
365 throughput: 0.0,
366 bottleneck_score: 0.0,
367 });
368
369 metrics.processing_times.push_back(processing_time);
371 if metrics.processing_times.len() > self.config.monitoring_window {
372 metrics.processing_times.pop_front();
373 }
374
375 metrics.frames_processed += 1;
377 metrics.queue_depth = queue_depth;
378 metrics.memory_usage = memory_usage;
379
380 if processing_time > metrics.peak_processing_time {
381 metrics.peak_processing_time = processing_time;
382 }
383
384 if !metrics.processing_times.is_empty() {
386 let total_time: Duration = metrics.processing_times.iter().sum();
387 metrics.avg_processing_time = total_time / metrics.processing_times.len() as u32;
388 }
389
390 if !metrics.avg_processing_time.is_zero() {
392 metrics.throughput = 1.0 / metrics.avg_processing_time.as_secs_f32();
393 }
394
395 let time_variance = Self::calculate_processing_time_variance(&metrics.processing_times);
397 metrics.bottleneck_score = (queue_depth as f32 / 10.0 + time_variance / 100.0).min(1.0);
398
399 if let Some(config) = self.thread_pool_manager.get_stage_config(stagename) {
401 let target_processing_time = Duration::from_millis(16); let utilization_factor =
403 processing_time.as_secs_f32() / target_processing_time.as_secs_f32();
404 metrics.thread_utilization =
405 (utilization_factor * 100.0 / config.current_threads as f32).min(100.0);
406 }
407 }
408
409 fn calculate_processing_time_variance(times: &std::collections::VecDeque<Duration>) -> f32 {
411 if times.len() < 2 {
412 return 0.0;
413 }
414
415 let mean = times.iter().sum::<Duration>().as_secs_f32() / times.len() as f32;
416 let variance: f32 = times
417 .iter()
418 .map(|t| {
419 let diff = t.as_secs_f32() - mean;
420 diff * diff
421 })
422 .sum::<f32>()
423 / times.len() as f32;
424
425 variance.sqrt() * 1000.0 }
427
428 pub fn update_system_resources(
436 &mut self,
437 cpu_usage: f32,
438 memory_usage: usize,
439 available_memory: usize,
440 ) {
441 self.resource_monitor.cpu_usage = cpu_usage;
442 self.resource_monitor.memory_usage = memory_usage;
443 self.resource_monitor.available_memory = available_memory;
444 self.resource_monitor.total_threads = self
445 .thread_pool_manager
446 .get_registered_stages()
447 .iter()
448 .map(|stage| {
449 self.thread_pool_manager
450 .get_stage_config(stage)
451 .map(|config| config.current_threads)
452 .unwrap_or(1)
453 })
454 .sum();
455 }
456
457 pub fn adapt(&mut self) -> Vec<String> {
463 let now = Instant::now();
464 if now.duration_since(self.last_adaptation) < self.config.adaptation_interval {
465 return Vec::new();
466 }
467
468 let mut actions = Vec::new();
469
470 for (stagename, metrics) in &self.stage_metrics {
472 if metrics.bottleneck_score > self.config.bottleneck_threshold {
473 let old_threads = self
474 .thread_pool_manager
475 .get_stage_config(stagename)
476 .map(|config| config.current_threads)
477 .unwrap_or(1);
478
479 let new_threads = self
480 .thread_pool_manager
481 .adapt_thread_count(stagename, metrics);
482
483 if new_threads != old_threads {
484 actions.push(format!(
485 "Scaled {} from {} to {} threads (bottleneck: {:.2})",
486 stagename, old_threads, new_threads, metrics.bottleneck_score
487 ));
488 }
489 }
490 }
491
492 if self.resource_monitor.cpu_usage > self.config.cpu_warning_threshold {
494 actions.push(format!(
495 "High CPU usage detected: {:.1}%",
496 self.resource_monitor.cpu_usage
497 ));
498 }
499
500 if self.resource_monitor.memory_usage > self.config.memory_warning_threshold {
501 actions.push(format!(
502 "High memory usage detected: {} MB",
503 self.resource_monitor.memory_usage / 1_048_576
504 ));
505 }
506
507 let snapshot = PerformanceSnapshot {
509 timestamp: now,
510 pipeline_throughput: self.calculate_overall_throughput(),
511 pipeline_latency: self.calculate_overall_latency(),
512 resource_usage: self.resource_monitor.clone(),
513 bottlenecks: self
514 .stage_metrics
515 .iter()
516 .filter(|(_, metrics)| metrics.bottleneck_score > self.config.bottleneck_threshold)
517 .map(|(name, _)| name.clone())
518 .collect(),
519 };
520
521 self.performance_history.push_back(snapshot);
522 if self.performance_history.len() > 100 {
523 self.performance_history.pop_front();
524 }
525
526 self.last_adaptation = now;
527 actions
528 }
529
530 fn calculate_overall_throughput(&self) -> f32 {
532 if self.stage_metrics.is_empty() {
533 return 0.0;
534 }
535
536 self.stage_metrics
538 .values()
539 .map(|metrics| metrics.throughput)
540 .min_by(|a, b| a.partial_cmp(b).expect("Operation failed"))
541 .unwrap_or(0.0)
542 }
543
544 fn calculate_overall_latency(&self) -> Duration {
546 self.stage_metrics
547 .values()
548 .map(|metrics| metrics.avg_processing_time)
549 .sum()
550 }
551
552 pub fn get_performance_summary(&self) -> PerformanceSnapshot {
558 PerformanceSnapshot {
559 timestamp: Instant::now(),
560 pipeline_throughput: self.calculate_overall_throughput(),
561 pipeline_latency: self.calculate_overall_latency(),
562 resource_usage: self.resource_monitor.clone(),
563 bottlenecks: self
564 .stage_metrics
565 .iter()
566 .filter(|(_, metrics)| metrics.bottleneck_score > self.config.bottleneck_threshold)
567 .map(|(name, _)| name.clone())
568 .collect(),
569 }
570 }
571
572 pub fn get_stage_metrics(&self, stagename: &str) -> Option<&StagePerformanceMetrics> {
582 self.stage_metrics.get(stagename)
583 }
584
585 pub fn get_all_stage_metrics(
591 &self,
592 ) -> &std::collections::HashMap<String, StagePerformanceMetrics> {
593 &self.stage_metrics
594 }
595}