1use crate::NetworkConfig;
35use parking_lot::RwLock;
36use serde::{Deserialize, Serialize};
37use std::sync::Arc;
38use std::time::{Duration, Instant};
39use thiserror::Error;
40
41#[derive(Debug, Error)]
43pub enum AutoTunerError {
44 #[error("Failed to detect system resources: {0}")]
45 ResourceDetectionFailed(String),
46
47 #[error("Invalid configuration: {0}")]
48 InvalidConfig(String),
49
50 #[error("Tuning not initialized")]
51 NotInitialized,
52
53 #[error("Monitoring already running")]
54 MonitoringActive,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct SystemResources {
60 pub total_memory: u64,
62
63 pub available_memory: u64,
65
66 pub cpu_cores: usize,
68
69 pub network_bandwidth: u64,
71
72 pub is_battery_powered: bool,
74}
75
76impl SystemResources {
77 pub fn detect() -> Result<Self, AutoTunerError> {
79 Ok(Self {
82 total_memory: 4 * 1024 * 1024 * 1024, available_memory: 2 * 1024 * 1024 * 1024, cpu_cores: num_cpus::get(),
85 network_bandwidth: 0, is_battery_powered: false,
87 })
88 }
89
90 pub fn memory_category(&self) -> &'static str {
92 match self.total_memory {
93 0..134_217_728 => "very_low", 134_217_728..536_870_912 => "low", 536_870_912..2_147_483_648 => "medium", 2_147_483_648..8_589_934_592 => "high", _ => "very_high", }
99 }
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct WorkloadProfile {
105 pub avg_connections: f64,
107
108 pub avg_query_rate: f64,
110
111 pub avg_bandwidth_usage: f64,
113
114 pub peak_memory_usage: u64,
116
117 pub cpu_bound: bool,
119
120 pub bandwidth_bound: bool,
122
123 pub memory_bound: bool,
125}
126
127impl Default for WorkloadProfile {
128 fn default() -> Self {
129 Self {
130 avg_connections: 0.0,
131 avg_query_rate: 0.0,
132 avg_bandwidth_usage: 0.0,
133 peak_memory_usage: 0,
134 cpu_bound: false,
135 bandwidth_bound: false,
136 memory_bound: false,
137 }
138 }
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct AutoTunerConfig {
144 pub enable_auto_adjust: bool,
146
147 pub adjustment_interval: Duration,
149
150 pub stabilization_period: Duration,
152
153 pub safety_margin: f64,
156
157 pub aggressive_mode: bool,
159}
160
161impl Default for AutoTunerConfig {
162 fn default() -> Self {
163 Self {
164 enable_auto_adjust: true,
165 adjustment_interval: Duration::from_secs(300), stabilization_period: Duration::from_secs(60), safety_margin: 0.2,
168 aggressive_mode: false,
169 }
170 }
171}
172
173impl AutoTunerConfig {
174 pub fn conservative() -> Self {
176 Self {
177 enable_auto_adjust: true,
178 adjustment_interval: Duration::from_secs(600), stabilization_period: Duration::from_secs(120), safety_margin: 0.3,
181 aggressive_mode: false,
182 }
183 }
184
185 pub fn aggressive() -> Self {
187 Self {
188 enable_auto_adjust: true,
189 adjustment_interval: Duration::from_secs(60), stabilization_period: Duration::from_secs(30), safety_margin: 0.1,
192 aggressive_mode: true,
193 }
194 }
195}
196
197#[derive(Debug, Clone, Default)]
199pub struct AutoTunerStats {
200 pub adjustments_made: u64,
202
203 pub resource_checks: u64,
205
206 pub workload_checks: u64,
208
209 pub last_adjustment: Option<Instant>,
211
212 pub optimization_score: f64,
214}
215
216pub struct AutoTuner {
218 config: AutoTunerConfig,
219 system_resources: Option<SystemResources>,
220 workload_profile: WorkloadProfile,
221 stats: Arc<RwLock<AutoTunerStats>>,
222 monitoring_active: Arc<RwLock<bool>>,
223}
224
225impl AutoTuner {
226 pub fn new() -> Self {
228 Self::with_config(AutoTunerConfig::default())
229 }
230
231 pub fn with_config(config: AutoTunerConfig) -> Self {
233 Self {
234 config,
235 system_resources: None,
236 workload_profile: WorkloadProfile::default(),
237 stats: Arc::new(RwLock::new(AutoTunerStats::default())),
238 monitoring_active: Arc::new(RwLock::new(false)),
239 }
240 }
241
242 pub async fn analyze_system(&mut self) -> Result<SystemResources, AutoTunerError> {
244 let resources = SystemResources::detect()?;
245 self.system_resources = Some(resources.clone());
246
247 let mut stats = self.stats.write();
248 stats.resource_checks += 1;
249
250 Ok(resources)
251 }
252
253 pub async fn generate_config(&mut self) -> Result<NetworkConfig, AutoTunerError> {
255 if self.system_resources.is_none() {
257 self.analyze_system().await?;
258 }
259
260 let resources = self
261 .system_resources
262 .as_ref()
263 .expect("just populated by analyze_system() if was None");
264 let usable_factor = 1.0 - self.config.safety_margin;
265
266 let mut config = match resources.memory_category() {
268 "very_low" => NetworkConfig::low_memory(),
269 "low" => NetworkConfig::iot(),
270 "medium" => NetworkConfig::mobile(),
271 "high" | "very_high" => {
272 if resources.is_battery_powered {
273 NetworkConfig::mobile()
274 } else {
275 NetworkConfig::high_performance()
276 }
277 }
278 _ => NetworkConfig::default(),
279 };
280
281 let base_connections = resources.cpu_cores * 50;
283 config.max_connections = Some((base_connections as f64 * usable_factor) as usize);
284
285 let memory_mb = resources.available_memory / (1024 * 1024);
287 if memory_mb < 256 {
288 config.connection_buffer_size = 8 * 1024; config.max_connections = Some(16);
290 } else if memory_mb < 512 {
291 config.connection_buffer_size = 16 * 1024; config.max_connections = Some(32);
293 }
294
295 config.enable_nat_traversal =
297 resources.memory_category() != "very_high" || resources.is_battery_powered;
298
299 let mut stats = self.stats.write();
300 stats.adjustments_made += 1;
301 stats.last_adjustment = Some(Instant::now());
302 stats.optimization_score = self.calculate_optimization_score();
303
304 Ok(config)
305 }
306
307 pub fn update_workload(
309 &mut self,
310 connections: usize,
311 query_rate: f64,
312 bandwidth_usage: f64,
313 memory_usage: u64,
314 ) {
315 let alpha = 0.3; let profile = &mut self.workload_profile;
319 profile.avg_connections =
320 profile.avg_connections * (1.0 - alpha) + (connections as f64) * alpha;
321 profile.avg_query_rate = profile.avg_query_rate * (1.0 - alpha) + query_rate * alpha;
322 profile.avg_bandwidth_usage =
323 profile.avg_bandwidth_usage * (1.0 - alpha) + bandwidth_usage * alpha;
324 profile.peak_memory_usage = profile.peak_memory_usage.max(memory_usage);
325
326 if let Some(resources) = &self.system_resources {
328 let memory_usage_ratio = memory_usage as f64 / resources.available_memory as f64;
329 profile.memory_bound = memory_usage_ratio > 0.8;
330
331 profile.cpu_bound = connections > resources.cpu_cores * 100;
333 profile.bandwidth_bound = resources.network_bandwidth > 0
334 && bandwidth_usage > (resources.network_bandwidth as f64 * 0.8);
335 }
336
337 let mut stats = self.stats.write();
338 stats.workload_checks += 1;
339 }
340
341 pub async fn start_monitoring(&mut self) -> Result<(), AutoTunerError> {
343 let mut active = self.monitoring_active.write();
344 if *active {
345 return Err(AutoTunerError::MonitoringActive);
346 }
347 *active = true;
348
349 Ok(())
350 }
351
352 pub fn stop_monitoring(&mut self) {
354 let mut active = self.monitoring_active.write();
355 *active = false;
356 }
357
358 pub fn is_monitoring(&self) -> bool {
360 *self.monitoring_active.read()
361 }
362
363 pub fn workload_profile(&self) -> &WorkloadProfile {
365 &self.workload_profile
366 }
367
368 pub fn stats(&self) -> AutoTunerStats {
370 self.stats.read().clone()
371 }
372
373 fn calculate_optimization_score(&self) -> f64 {
375 if self.system_resources.is_none() {
376 return 0.0;
377 }
378
379 let resources = self
380 .system_resources
381 .as_ref()
382 .expect("just checked is_some above");
383 let profile = &self.workload_profile;
384
385 let memory_score = if profile.peak_memory_usage > 0 {
387 1.0 - (profile.peak_memory_usage as f64 / resources.available_memory as f64).min(1.0)
388 } else {
389 0.5
390 };
391
392 let cpu_score = if profile.cpu_bound { 0.3 } else { 0.8 };
393 let bandwidth_score = if profile.bandwidth_bound { 0.3 } else { 0.8 };
394
395 (memory_score * 0.4 + cpu_score * 0.3 + bandwidth_score * 0.3).clamp(0.0, 1.0)
397 }
398
399 pub fn recommendations(&self) -> Vec<String> {
401 let mut recommendations = Vec::new();
402
403 if let Some(resources) = &self.system_resources {
404 let profile = &self.workload_profile;
405
406 if profile.memory_bound {
407 recommendations.push(
408 "Memory usage is high. Consider reducing max_connections or enabling low_memory_mode.".to_string()
409 );
410 }
411
412 if profile.cpu_bound {
413 recommendations.push(
414 format!("CPU usage is high with {} cores. Consider distributing load across more nodes.",
415 resources.cpu_cores)
416 );
417 }
418
419 if profile.bandwidth_bound {
420 recommendations.push(
421 "Bandwidth is saturated. Consider enabling bandwidth throttling or upgrading network capacity.".to_string()
422 );
423 }
424
425 if resources.is_battery_powered && profile.avg_query_rate > 10.0 {
426 recommendations.push(
427 "High DHT query rate on battery power. Consider enabling query batching."
428 .to_string(),
429 );
430 }
431
432 if resources.memory_category() == "very_low" && !profile.memory_bound {
433 recommendations.push(
434 "System resources are underutilized. You can increase max_connections for better performance.".to_string()
435 );
436 }
437 } else {
438 recommendations.push("Run analyze_system() first to get recommendations.".to_string());
439 }
440
441 recommendations
442 }
443}
444
445impl Default for AutoTuner {
446 fn default() -> Self {
447 Self::new()
448 }
449}
450
451mod num_cpus {
453 pub fn get() -> usize {
454 std::thread::available_parallelism()
455 .map(|n| n.get())
456 .unwrap_or(4) }
458}
459
460#[cfg(test)]
461mod tests {
462 use super::*;
463
464 #[tokio::test]
465 async fn test_auto_tuner_creation() {
466 let tuner = AutoTuner::new();
467 assert!(!tuner.is_monitoring());
468 }
469
470 #[tokio::test]
471 async fn test_system_resource_detection() {
472 let mut tuner = AutoTuner::new();
473 let resources = tuner
474 .analyze_system()
475 .await
476 .expect("test: analyze_system should succeed");
477 assert!(resources.cpu_cores > 0);
478 assert!(resources.total_memory > 0);
479 }
480
481 #[tokio::test]
482 async fn test_config_generation() {
483 let mut tuner = AutoTuner::new();
484 let config = tuner
485 .generate_config()
486 .await
487 .expect("test: generate_config should succeed");
488 assert!(config.max_connections.is_some());
489 }
490
491 #[tokio::test]
492 async fn test_workload_update() {
493 let mut tuner = AutoTuner::new();
494 tuner
495 .analyze_system()
496 .await
497 .expect("test: analyze_system should succeed");
498
499 tuner.update_workload(10, 5.0, 100_000.0, 50_000_000);
500 let profile = tuner.workload_profile();
501 assert!(profile.avg_connections > 0.0);
502 }
503
504 #[tokio::test]
505 async fn test_monitoring_lifecycle() {
506 let mut tuner = AutoTuner::new();
507 assert!(!tuner.is_monitoring());
508
509 tuner
510 .start_monitoring()
511 .await
512 .expect("test: start_monitoring should succeed when not yet active");
513 assert!(tuner.is_monitoring());
514
515 tuner.stop_monitoring();
516 assert!(!tuner.is_monitoring());
517 }
518
519 #[test]
520 fn test_memory_categories() {
521 let low = SystemResources {
522 total_memory: 100 * 1024 * 1024, available_memory: 50 * 1024 * 1024,
524 cpu_cores: 2,
525 network_bandwidth: 0,
526 is_battery_powered: true,
527 };
528 assert_eq!(low.memory_category(), "very_low");
529
530 let high = SystemResources {
531 total_memory: 16 * 1024 * 1024 * 1024, available_memory: 8 * 1024 * 1024 * 1024,
533 cpu_cores: 8,
534 network_bandwidth: 0,
535 is_battery_powered: false,
536 };
537 assert_eq!(high.memory_category(), "very_high");
538 }
539
540 #[tokio::test]
541 async fn test_statistics_tracking() {
542 let mut tuner = AutoTuner::new();
543
544 let stats_before = tuner.stats();
545 assert_eq!(stats_before.adjustments_made, 0);
546
547 tuner
548 .generate_config()
549 .await
550 .expect("test: generate_config should succeed");
551
552 let stats_after = tuner.stats();
553 assert_eq!(stats_after.adjustments_made, 1);
554 assert!(stats_after.last_adjustment.is_some());
555 }
556
557 #[tokio::test]
558 async fn test_recommendations() {
559 let mut tuner = AutoTuner::new();
560 tuner
561 .analyze_system()
562 .await
563 .expect("test: analyze_system should succeed");
564
565 if let Some(resources) = &tuner.system_resources {
567 let high_memory = (resources.available_memory as f64 * 0.85) as u64;
568 tuner.update_workload(50, 20.0, 1_000_000.0, high_memory);
569 }
570
571 let recommendations = tuner.recommendations();
572 assert!(!recommendations.is_empty());
573 }
574
575 #[tokio::test]
576 async fn test_config_presets() {
577 let conservative = AutoTunerConfig::conservative();
578 assert!(!conservative.aggressive_mode);
579 assert!(conservative.safety_margin > 0.2);
580
581 let aggressive = AutoTunerConfig::aggressive();
582 assert!(aggressive.aggressive_mode);
583 assert!(aggressive.safety_margin < 0.2);
584 }
585
586 #[tokio::test]
587 async fn test_optimization_score() {
588 let mut tuner = AutoTuner::new();
589 tuner
590 .analyze_system()
591 .await
592 .expect("test: analyze_system should succeed");
593
594 let stats = tuner.stats();
595 assert!(stats.optimization_score >= 0.0 && stats.optimization_score <= 1.0);
596 }
597}