memscope_rs/unified/
environment_detector.rs

1// Runtime Environment Detection System
2// Intelligently detects execution context to optimize memory tracking strategy
3// Supports single-thread, multi-thread, async, and hybrid runtime detection
4
5use crate::unified::backend::{AsyncRuntimeType, BackendError, RuntimeEnvironment};
6use std::sync::atomic::AtomicUsize;
7use std::sync::Arc;
8use tracing::{debug, info, warn};
9
10/// Advanced environment detector with runtime analysis capabilities
11/// Provides deep inspection of execution context for optimal tracking strategy selection
12pub struct EnvironmentDetector {
13    /// Detection configuration parameters
14    config: DetectionConfig,
15    /// Runtime statistics collector
16    runtime_stats: RuntimeStatistics,
17}
18
19/// Configuration for environment detection behavior
20#[derive(Debug, Clone)]
21pub struct DetectionConfig {
22    /// Enable deep async runtime analysis
23    pub deep_async_detection: bool,
24    /// Sampling period for runtime analysis (milliseconds)
25    pub analysis_period_ms: u64,
26    /// Threshold for considering environment as multi-threaded
27    pub multi_thread_threshold: usize,
28    /// Maximum time to spend on detection
29    pub max_detection_time_ms: u64,
30}
31
32/// Runtime statistics collected during detection
33#[derive(Debug, Clone)]
34pub struct RuntimeStatistics {
35    /// Active thread count observed
36    pub active_threads: Arc<AtomicUsize>,
37    /// Async task count (if detectable)
38    pub async_tasks: Arc<AtomicUsize>,
39    /// Peak thread utilization
40    pub peak_thread_count: usize,
41    /// Detection duration
42    pub detection_duration_ms: u64,
43}
44
45/// Detailed environment analysis result
46#[derive(Debug, Clone)]
47pub struct EnvironmentAnalysis {
48    /// Primary detected environment
49    pub environment: RuntimeEnvironment,
50    /// Confidence level (0.0 to 1.0)
51    pub confidence: f64,
52    /// Alternative environments considered
53    pub alternatives: Vec<RuntimeEnvironment>,
54    /// Detection metadata
55    pub detection_metadata: DetectionMetadata,
56}
57
58/// Metadata about detection process
59#[derive(Debug, Clone)]
60pub struct DetectionMetadata {
61    /// Time spent on detection
62    pub detection_time_ms: u64,
63    /// Number of samples taken
64    pub sample_count: usize,
65    /// Detection method used
66    pub method: DetectionMethod,
67    /// Warnings or issues during detection
68    pub warnings: Vec<String>,
69}
70
71/// Method used for environment detection
72#[derive(Debug, Clone, PartialEq)]
73pub enum DetectionMethod {
74    /// Static analysis only
75    Static,
76    /// Runtime sampling
77    Dynamic,
78    /// Combined static and dynamic
79    Hybrid,
80    /// Forced by configuration
81    Manual,
82}
83
84impl Default for DetectionConfig {
85    /// Default detection configuration optimized for accuracy and performance
86    fn default() -> Self {
87        Self {
88            deep_async_detection: true,
89            analysis_period_ms: 100,
90            multi_thread_threshold: 2,
91            max_detection_time_ms: 500,
92        }
93    }
94}
95
96impl Default for RuntimeStatistics {
97    /// Initialize runtime statistics with zero values
98    fn default() -> Self {
99        Self {
100            active_threads: Arc::new(AtomicUsize::new(1)),
101            async_tasks: Arc::new(AtomicUsize::new(0)),
102            peak_thread_count: 1,
103            detection_duration_ms: 0,
104        }
105    }
106}
107
108impl EnvironmentDetector {
109    /// Create new environment detector with configuration
110    pub fn new(config: DetectionConfig) -> Self {
111        debug!("Creating environment detector with config: {:?}", config);
112
113        Self {
114            config,
115            runtime_stats: RuntimeStatistics::default(),
116        }
117    }
118
119    /// Perform comprehensive environment detection and analysis
120    /// Returns detailed analysis with confidence levels and alternatives
121    pub fn analyze_environment(&mut self) -> Result<EnvironmentAnalysis, BackendError> {
122        let start_time = std::time::Instant::now();
123        info!("Starting comprehensive environment analysis");
124
125        let mut warnings = Vec::new();
126
127        // Phase 1: Static analysis
128        let static_env = self.perform_static_analysis(&mut warnings)?;
129        debug!("Static analysis result: {:?}", static_env);
130
131        // Phase 2: Dynamic runtime analysis (if enabled)
132        let dynamic_env = if self.config.deep_async_detection {
133            Some(self.perform_dynamic_analysis(&mut warnings)?)
134        } else {
135            None
136        };
137
138        // Phase 3: Combine results and calculate confidence
139        let has_dynamic = dynamic_env.is_some();
140        let (final_env, confidence, alternatives) =
141            self.synthesize_results(static_env, dynamic_env, &warnings)?;
142
143        let detection_time = start_time.elapsed().as_millis() as u64;
144        self.runtime_stats.detection_duration_ms = detection_time;
145
146        let analysis = EnvironmentAnalysis {
147            environment: final_env,
148            confidence,
149            alternatives,
150            detection_metadata: DetectionMetadata {
151                detection_time_ms: detection_time,
152                sample_count: self.calculate_sample_count(),
153                method: self.determine_detection_method(has_dynamic),
154                warnings,
155            },
156        };
157
158        info!(
159            "Environment analysis completed: {:?} (confidence: {:.2})",
160            analysis.environment, analysis.confidence
161        );
162
163        Ok(analysis)
164    }
165
166    /// Perform static environment analysis based on available system information
167    fn perform_static_analysis(
168        &self,
169        warnings: &mut Vec<String>,
170    ) -> Result<RuntimeEnvironment, BackendError> {
171        debug!("Performing static environment analysis");
172
173        // Detect available parallelism
174        let logical_cores = std::thread::available_parallelism()
175            .map(|p| p.get())
176            .unwrap_or_else(|e| {
177                warnings.push(format!("Could not detect CPU cores: {}", e));
178                1
179            });
180
181        debug!("Detected {} logical CPU cores", logical_cores);
182
183        // Check for async runtime indicators
184        let async_runtime = self.detect_async_runtime_static(warnings);
185
186        // Determine base environment from static analysis
187        let environment = match (async_runtime, logical_cores) {
188            (Some(runtime_type), 0) => {
189                warnings.push("Zero cores detected with async runtime".to_string());
190                RuntimeEnvironment::AsyncRuntime { runtime_type }
191            }
192            (Some(runtime_type), 1) => RuntimeEnvironment::AsyncRuntime { runtime_type },
193            (Some(_runtime_type), cores) if cores >= self.config.multi_thread_threshold => {
194                RuntimeEnvironment::Hybrid {
195                    thread_count: cores,
196                    async_task_count: 0, // Will be determined dynamically
197                }
198            }
199            (Some(runtime_type), _cores) => {
200                // Low core count with async runtime
201                RuntimeEnvironment::AsyncRuntime { runtime_type }
202            }
203            (None, 1) => RuntimeEnvironment::SingleThreaded,
204            (None, cores) if cores >= self.config.multi_thread_threshold => {
205                RuntimeEnvironment::MultiThreaded {
206                    thread_count: cores,
207                }
208            }
209            (None, cores) => {
210                warnings.push(format!("Low core count {} but above single-thread", cores));
211                RuntimeEnvironment::SingleThreaded
212            }
213        };
214
215        debug!("Static analysis determined environment: {:?}", environment);
216        Ok(environment)
217    }
218
219    /// Detect async runtime presence using static indicators
220    fn detect_async_runtime_static(&self, warnings: &mut Vec<String>) -> Option<AsyncRuntimeType> {
221        debug!("Detecting async runtime using static analysis");
222
223        // Check for Tokio runtime
224        if self.is_tokio_runtime_present() {
225            debug!("Tokio runtime detected via static analysis");
226            return Some(AsyncRuntimeType::Tokio);
227        }
228
229        // Check for async-std runtime
230        if self.is_async_std_runtime_present() {
231            debug!("async-std runtime detected via static analysis");
232            return Some(AsyncRuntimeType::AsyncStd);
233        }
234
235        // Check environment variables for async indicators
236        if let Ok(async_env) = std::env::var("ASYNC_RUNTIME") {
237            match async_env.to_lowercase().as_str() {
238                "tokio" => {
239                    debug!("Tokio runtime detected via environment variable");
240                    return Some(AsyncRuntimeType::Tokio);
241                }
242                "async-std" => {
243                    debug!("async-std runtime detected via environment variable");
244                    return Some(AsyncRuntimeType::AsyncStd);
245                }
246                other => {
247                    warnings.push(format!("Unknown async runtime specified: {}", other));
248                    return Some(AsyncRuntimeType::Custom);
249                }
250            }
251        }
252
253        debug!("No async runtime detected in static analysis");
254        None
255    }
256
257    /// Check for Tokio runtime presence using available detection methods
258    fn is_tokio_runtime_present(&self) -> bool {
259        // Method 1: Check for Tokio environment variables
260        if std::env::var("TOKIO_WORKER_THREADS").is_ok() {
261            return true;
262        }
263
264        // Note: tokio::runtime::Handle::try_current() requires tokio dependency
265        // Would be enabled when tokio feature is available
266
267        false
268    }
269
270    /// Check for async-std runtime presence
271    fn is_async_std_runtime_present(&self) -> bool {
272        // async-std detection is more challenging as it has fewer runtime introspection APIs
273        // Check for async-std specific environment variables or patterns
274
275        // Method 1: Check for async-std environment indicators
276        if std::env::var("ASYNC_STD_THREAD_COUNT").is_ok() {
277            return true;
278        }
279
280        // Method 2: Check if we're running inside async-std executor
281        // This would require async-std specific detection logic
282
283        false
284    }
285
286    /// Perform dynamic runtime analysis through sampling and observation
287    fn perform_dynamic_analysis(
288        &mut self,
289        warnings: &mut Vec<String>,
290    ) -> Result<RuntimeEnvironment, BackendError> {
291        debug!("Performing dynamic runtime analysis");
292
293        let analysis_start = std::time::Instant::now();
294        let max_duration = std::time::Duration::from_millis(self.config.max_detection_time_ms);
295
296        // Sample runtime characteristics over time
297        let mut sample_count = 0;
298        let mut thread_samples = Vec::new();
299        let mut async_indicators = Vec::new();
300
301        while analysis_start.elapsed() < max_duration {
302            // Sample current thread activity
303            let current_threads = self.sample_thread_activity();
304            thread_samples.push(current_threads);
305
306            // Sample async task activity (if possible)
307            let async_activity = self.sample_async_activity();
308            async_indicators.push(async_activity);
309
310            sample_count += 1;
311
312            // Sleep for sampling interval
313            std::thread::sleep(std::time::Duration::from_millis(
314                self.config.analysis_period_ms / 10,
315            ));
316        }
317
318        // Analyze collected samples
319        let avg_threads = if thread_samples.is_empty() {
320            1
321        } else {
322            thread_samples.iter().sum::<usize>() / thread_samples.len()
323        };
324
325        let peak_threads = thread_samples.into_iter().max().unwrap_or(1);
326        self.runtime_stats.peak_thread_count = peak_threads;
327
328        let has_async_activity = async_indicators.iter().any(|&active| active);
329
330        debug!(
331            "Dynamic analysis: avg_threads={}, peak_threads={}, async_activity={}",
332            avg_threads, peak_threads, has_async_activity
333        );
334
335        // Determine environment from dynamic analysis
336        let environment = match (has_async_activity, peak_threads) {
337            (true, 0) => {
338                warnings.push("Async activity detected with zero threads".to_string());
339                let runtime_type = self
340                    .detect_async_runtime_static(warnings)
341                    .unwrap_or(AsyncRuntimeType::Custom);
342                RuntimeEnvironment::AsyncRuntime { runtime_type }
343            }
344            (true, 1) => {
345                // Async activity on single thread suggests async runtime
346                let runtime_type = self
347                    .detect_async_runtime_static(warnings)
348                    .unwrap_or(AsyncRuntimeType::Custom);
349                RuntimeEnvironment::AsyncRuntime { runtime_type }
350            }
351            (true, threads) => {
352                // Both async and multi-thread activity
353                RuntimeEnvironment::Hybrid {
354                    thread_count: threads,
355                    async_task_count: sample_count, // Use sample count as proxy
356                }
357            }
358            (false, 1) => RuntimeEnvironment::SingleThreaded,
359            (false, threads) => RuntimeEnvironment::MultiThreaded {
360                thread_count: threads,
361            },
362        };
363
364        debug!("Dynamic analysis determined environment: {:?}", environment);
365        Ok(environment)
366    }
367
368    /// Sample current thread activity level
369    fn sample_thread_activity(&self) -> usize {
370        // This is a simplified implementation
371        // Real implementation would use platform-specific APIs to count active threads
372
373        // For now, use available parallelism as baseline
374        std::thread::available_parallelism()
375            .map(|p| p.get())
376            .unwrap_or(1)
377    }
378
379    /// Sample async task activity
380    fn sample_async_activity(&self) -> bool {
381        // This would use runtime-specific APIs to detect async task activity
382        // For now, use simple heuristics
383
384        // Check if we can detect any async runtime activity
385        self.is_tokio_runtime_present() || self.is_async_std_runtime_present()
386    }
387
388    /// Synthesize static and dynamic analysis results
389    fn synthesize_results(
390        &self,
391        static_result: RuntimeEnvironment,
392        dynamic_result: Option<RuntimeEnvironment>,
393        warnings: &[String],
394    ) -> Result<(RuntimeEnvironment, f64, Vec<RuntimeEnvironment>), BackendError> {
395        debug!("Synthesizing analysis results");
396
397        let mut alternatives = Vec::new();
398        let _base_confidence = 0.7; // Base confidence
399
400        let (final_environment, mut confidence) = match dynamic_result {
401            Some(dynamic_env) => {
402                // Compare static and dynamic results
403                if std::mem::discriminant(&static_result) == std::mem::discriminant(&dynamic_env) {
404                    // Results agree - high confidence
405                    let confidence = 0.95;
406                    alternatives.push(static_result);
407                    (dynamic_env, confidence)
408                } else {
409                    // Results disagree - medium confidence, prefer dynamic
410                    let confidence = 0.75;
411                    alternatives.push(static_result);
412                    (dynamic_env, confidence)
413                }
414            }
415            None => {
416                // Only static analysis available
417                let confidence = 0.80;
418                (static_result, confidence)
419            }
420        };
421
422        // Adjust confidence based on warnings
423        if !warnings.is_empty() {
424            confidence -= 0.1 * warnings.len() as f64;
425            confidence = confidence.max(0.3); // Minimum confidence threshold
426        }
427
428        debug!(
429            "Final synthesis: {:?} with confidence {:.2}",
430            final_environment, confidence
431        );
432        Ok((final_environment, confidence, alternatives))
433    }
434
435    /// Calculate total number of samples taken during analysis
436    fn calculate_sample_count(&self) -> usize {
437        // This would be tracked during dynamic analysis
438        // For now, estimate based on detection duration
439        let samples = self.runtime_stats.detection_duration_ms / self.config.analysis_period_ms;
440        samples.max(1) as usize
441    }
442
443    /// Determine which detection method was primarily used
444    fn determine_detection_method(&self, used_dynamic: bool) -> DetectionMethod {
445        if used_dynamic {
446            DetectionMethod::Hybrid
447        } else {
448            DetectionMethod::Static
449        }
450    }
451
452    /// Get current runtime statistics
453    pub fn runtime_statistics(&self) -> &RuntimeStatistics {
454        &self.runtime_stats
455    }
456}
457
458/// Convenience function for quick environment detection
459/// Uses default configuration for most common use cases
460pub fn detect_environment() -> Result<RuntimeEnvironment, BackendError> {
461    let mut detector = EnvironmentDetector::new(DetectionConfig::default());
462    let analysis = detector.analyze_environment()?;
463
464    if analysis.confidence < 0.5 {
465        warn!(
466            "Low confidence environment detection: {:.2}",
467            analysis.confidence
468        );
469    }
470
471    Ok(analysis.environment)
472}
473
474/// Advanced environment detection with custom configuration
475/// Provides detailed analysis results for advanced use cases
476pub fn detect_environment_detailed(
477    config: DetectionConfig,
478) -> Result<EnvironmentAnalysis, BackendError> {
479    let mut detector = EnvironmentDetector::new(config);
480    detector.analyze_environment()
481}
482
483#[cfg(test)]
484mod tests {
485    use super::*;
486
487    #[test]
488    fn test_detector_creation() {
489        let config = DetectionConfig::default();
490        let detector = EnvironmentDetector::new(config);
491        assert_eq!(detector.runtime_stats.peak_thread_count, 1);
492    }
493
494    #[test]
495    fn test_static_analysis() {
496        let config = DetectionConfig::default();
497        let detector = EnvironmentDetector::new(config);
498        let mut warnings = Vec::new();
499
500        let result = detector.perform_static_analysis(&mut warnings);
501        assert!(result.is_ok());
502    }
503
504    #[test]
505    fn test_tokio_detection() {
506        let config = DetectionConfig::default();
507        let detector = EnvironmentDetector::new(config);
508
509        // This test will pass regardless of Tokio presence
510        let _has_tokio = detector.is_tokio_runtime_present();
511        // Just ensure the method doesn't panic
512    }
513
514    #[test]
515    fn test_environment_analysis_confidence() {
516        let mut detector = EnvironmentDetector::new(DetectionConfig::default());
517        let analysis = detector.analyze_environment();
518
519        assert!(analysis.is_ok());
520        let analysis = analysis.unwrap();
521        assert!(analysis.confidence >= 0.0 && analysis.confidence <= 1.0);
522    }
523
524    #[test]
525    fn test_quick_detection() {
526        let result = detect_environment();
527        assert!(result.is_ok());
528    }
529
530    #[test]
531    fn test_detailed_detection() {
532        let config = DetectionConfig {
533            deep_async_detection: false, // Disable for faster test
534            max_detection_time_ms: 50,   // Short timeout for test
535            ..Default::default()
536        };
537
538        let result = detect_environment_detailed(config);
539        assert!(result.is_ok());
540
541        let analysis = result.unwrap();
542        assert!(analysis.detection_metadata.detection_time_ms <= 100); // Should be quick
543    }
544}