Skip to main content

memscope_rs/capture/backends/
unified_tracker.rs

1//! Unified tracker types and environment detection.
2//!
3//! This module contains type definitions for unified tracking strategy.
4
5use crate::core::{MemScopeError, MemScopeResult};
6use std::sync::Arc;
7use tracing::{debug, info, warn};
8
9/// Runtime environment type.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum RuntimeEnvironment {
12    /// Single-threaded environment
13    SingleThreaded,
14    /// Multi-threaded environment with thread count
15    MultiThreaded { thread_count: usize },
16    /// Async environment with runtime type
17    AsyncRuntime { runtime_type: AsyncRuntimeType },
18    /// Hybrid environment
19    Hybrid {
20        thread_count: usize,
21        async_task_count: usize,
22    },
23}
24
25/// Supported async runtime types.
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub enum AsyncRuntimeType {
28    /// Tokio runtime
29    Tokio,
30    /// async-std runtime
31    AsyncStd,
32    /// Custom or unknown runtime
33    Custom,
34}
35
36/// Tracking strategy type.
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum TrackingStrategy {
39    /// Direct global tracking for single-threaded apps
40    GlobalDirect,
41    /// Thread-local storage for multi-threaded apps
42    ThreadLocal,
43    /// Task-local storage for async applications
44    TaskLocal,
45    /// Combined strategy for hybrid applications
46    HybridTracking,
47}
48
49/// Configuration for backend behavior.
50#[derive(Debug, Clone)]
51pub struct BackendConfig {
52    /// Enable automatic environment detection
53    pub auto_detect: bool,
54    /// Forced strategy override
55    pub force_strategy: Option<TrackingStrategy>,
56    /// Performance sampling rate (0.0 to 1.0)
57    pub sample_rate: f64,
58    /// Maximum memory overhead percentage
59    pub max_overhead_percent: f64,
60}
61
62impl Default for BackendConfig {
63    fn default() -> Self {
64        Self {
65            auto_detect: true,
66            force_strategy: None,
67            sample_rate: 1.0,
68            max_overhead_percent: 5.0,
69        }
70    }
71}
72
73/// Environment detection configuration.
74#[derive(Debug, Clone)]
75pub struct DetectionConfig {
76    /// Enable deep async runtime analysis
77    pub deep_async_detection: bool,
78    /// Sampling period for runtime analysis (milliseconds)
79    pub analysis_period_ms: u64,
80    /// Threshold for considering environment as multi-threaded
81    pub multi_thread_threshold: usize,
82    /// Maximum time to spend on detection
83    pub max_detection_time_ms: u64,
84    /// Confidence level for detection results (0.0 to 1.0)
85    pub confidence_level: f64,
86}
87
88impl Default for DetectionConfig {
89    fn default() -> Self {
90        Self {
91            deep_async_detection: true,
92            analysis_period_ms: 100,
93            multi_thread_threshold: 2,
94            max_detection_time_ms: 500,
95            confidence_level: 1.0,
96        }
97    }
98}
99
100/// Environment detection result.
101#[derive(Debug, Clone)]
102pub struct EnvironmentDetection {
103    /// Detected environment type
104    pub environment: RuntimeEnvironment,
105    /// Recommended tracking strategy
106    pub recommended_strategy: TrackingStrategy,
107    /// Number of detected threads
108    pub thread_count: usize,
109    /// Memory usage percentage
110    pub memory_usage: f64,
111    /// Detection confidence score
112    pub confidence: f64,
113}
114
115/// Tracking session handle.
116pub struct TrackingSession {
117    /// Session identifier
118    session_id: String,
119    /// Backend reference
120    backend: Arc<UnifiedBackend>,
121    /// Session start timestamp
122    start_time: std::time::Instant,
123}
124
125/// Comprehensive memory analysis data.
126#[derive(Debug)]
127pub struct MemoryAnalysisData {
128    /// Raw tracking data
129    pub raw_data: Vec<u8>,
130    /// Aggregated statistics
131    pub statistics: MemoryStatistics,
132    /// Environment context
133    pub environment: RuntimeEnvironment,
134    /// Session metadata
135    pub session_metadata: SessionMetadata,
136}
137
138/// Statistical summary of memory tracking session.
139#[derive(Debug)]
140pub struct MemoryStatistics {
141    /// Total allocations tracked
142    pub total_allocations: usize,
143    /// Peak memory usage
144    pub peak_memory_bytes: usize,
145    /// Average allocation size
146    pub avg_allocation_size: f64,
147    /// Tracking session duration
148    pub session_duration_ms: u64,
149}
150
151/// Session metadata for analysis context.
152#[derive(Debug)]
153pub struct SessionMetadata {
154    /// Session unique identifier
155    pub session_id: String,
156    /// Environment detection results
157    pub detected_environment: RuntimeEnvironment,
158    /// Strategy used for tracking
159    pub strategy_used: TrackingStrategy,
160    /// Performance overhead measured
161    pub overhead_percent: f64,
162}
163
164/// Unified tracking backend.
165#[derive(Debug)]
166pub struct UnifiedBackend {
167    /// Current environment
168    environment: RuntimeEnvironment,
169    /// Active tracking strategy
170    active_strategy: TrackingStrategy,
171    /// Configuration
172    config: BackendConfig,
173}
174
175impl Clone for UnifiedBackend {
176    fn clone(&self) -> Self {
177        Self {
178            environment: self.environment.clone(),
179            active_strategy: self.active_strategy,
180            config: self.config.clone(),
181        }
182    }
183}
184
185impl UnifiedBackend {
186    /// Initialize unified backend with configuration.
187    pub fn initialize(config: BackendConfig) -> MemScopeResult<Self> {
188        if config.sample_rate < 0.0 || config.sample_rate > 1.0 {
189            return Err(MemScopeError::error(
190                "unified_tracker",
191                "initialize",
192                "Sample rate must be between 0.0 and 1.0",
193            ));
194        }
195
196        if config.max_overhead_percent < 0.0 || config.max_overhead_percent > 100.0 {
197            return Err(MemScopeError::error(
198                "unified_tracker",
199                "initialize",
200                "Max overhead percent must be between 0.0 and 100.0",
201            ));
202        }
203
204        info!("Initializing unified backend");
205
206        let environment = if config.auto_detect {
207            Self::detect_environment()?
208        } else {
209            RuntimeEnvironment::SingleThreaded
210        };
211
212        let active_strategy = if let Some(forced) = config.force_strategy {
213            warn!("Using forced strategy: {:?}", forced);
214            forced
215        } else {
216            Self::select_strategy(&environment)?
217        };
218
219        info!("Selected tracking strategy: {:?}", active_strategy);
220
221        Ok(Self {
222            environment,
223            active_strategy,
224            config,
225        })
226    }
227
228    /// Create a new unified backend with auto-detection.
229    pub fn new() -> Self {
230        Self::initialize(BackendConfig::default()).unwrap_or_else(|_| Self {
231            environment: RuntimeEnvironment::SingleThreaded,
232            active_strategy: TrackingStrategy::GlobalDirect,
233            config: BackendConfig::default(),
234        })
235    }
236
237    /// Create with specified configuration.
238    pub fn with_config(config: BackendConfig) -> MemScopeResult<Self> {
239        Self::initialize(config)
240    }
241
242    /// Get the current tracking strategy.
243    pub fn strategy(&self) -> &TrackingStrategy {
244        &self.active_strategy
245    }
246
247    /// Get the current environment.
248    pub fn environment(&self) -> &RuntimeEnvironment {
249        &self.environment
250    }
251
252    /// Get the configuration.
253    pub fn config(&self) -> &BackendConfig {
254        &self.config
255    }
256
257    /// Detect current runtime environment.
258    pub fn detect_environment() -> MemScopeResult<RuntimeEnvironment> {
259        debug!("Starting environment detection");
260
261        let async_runtime = Self::detect_async_runtime();
262        let thread_count = std::thread::available_parallelism()
263            .map(|p| p.get())
264            .unwrap_or(1);
265
266        let environment = match (async_runtime, thread_count) {
267            (Some(runtime_type), 1) => RuntimeEnvironment::AsyncRuntime { runtime_type },
268            (Some(_runtime_type), threads) => RuntimeEnvironment::Hybrid {
269                thread_count: threads,
270                async_task_count: 0,
271            },
272            (None, 1) => RuntimeEnvironment::SingleThreaded,
273            (None, threads) => RuntimeEnvironment::MultiThreaded {
274                thread_count: threads,
275            },
276        };
277
278        debug!("Environment detection completed: {:?}", environment);
279        Ok(environment)
280    }
281
282    /// Detect presence and type of async runtime.
283    fn detect_async_runtime() -> Option<AsyncRuntimeType> {
284        if Self::is_tokio_present() {
285            debug!("Tokio runtime detected");
286            return Some(AsyncRuntimeType::Tokio);
287        }
288
289        if Self::is_async_std_present() {
290            debug!("async-std runtime detected");
291            return Some(AsyncRuntimeType::AsyncStd);
292        }
293
294        None
295    }
296
297    /// Check if Tokio runtime is active.
298    /// Note: This is a heuristic check using environment variables.
299    /// For more reliable detection, consider using `tokio::runtime::Handle::try_current()`
300    /// when tokio is available as a dependency.
301    fn is_tokio_present() -> bool {
302        std::env::var("TOKIO_WORKER_THREADS").is_ok()
303    }
304
305    /// Check if async-std runtime is active.
306    /// Note: This is a heuristic check using environment variables.
307    fn is_async_std_present() -> bool {
308        std::env::var("ASYNC_STD_THREAD_COUNT").is_ok()
309    }
310
311    /// Select optimal tracking strategy.
312    fn select_strategy(environment: &RuntimeEnvironment) -> MemScopeResult<TrackingStrategy> {
313        let strategy = match environment {
314            RuntimeEnvironment::SingleThreaded => TrackingStrategy::GlobalDirect,
315            RuntimeEnvironment::MultiThreaded { .. } => TrackingStrategy::ThreadLocal,
316            RuntimeEnvironment::AsyncRuntime { .. } => TrackingStrategy::TaskLocal,
317            RuntimeEnvironment::Hybrid { .. } => TrackingStrategy::HybridTracking,
318        };
319
320        debug!(
321            "Selected strategy {:?} for environment {:?}",
322            strategy, environment
323        );
324        Ok(strategy)
325    }
326
327    /// Start active memory tracking session.
328    pub fn start_tracking(&mut self) -> MemScopeResult<TrackingSession> {
329        let session_id = format!(
330            "session_{}",
331            std::time::SystemTime::now()
332                .duration_since(std::time::UNIX_EPOCH)
333                .map_err(|e| MemScopeError::error(
334                    "unified_tracker",
335                    "start_tracking",
336                    format!("Failed to generate session ID: {}", e)
337                ))?
338                .as_millis()
339        );
340
341        info!("Starting tracking session: {}", session_id);
342
343        let session = TrackingSession {
344            session_id: session_id.clone(),
345            backend: Arc::new(self.clone()),
346            start_time: std::time::Instant::now(),
347        };
348
349        debug!("Tracking session {} started", session_id);
350        Ok(session)
351    }
352
353    /// Collect all tracking data.
354    pub fn collect_data(&self) -> MemScopeResult<MemoryAnalysisData> {
355        debug!("Collecting tracking data");
356
357        let statistics = MemoryStatistics {
358            total_allocations: 0,
359            peak_memory_bytes: 0,
360            avg_allocation_size: 0.0,
361            session_duration_ms: 0,
362        };
363
364        let session_metadata = SessionMetadata {
365            session_id: "current_session".to_string(),
366            detected_environment: self.environment.clone(),
367            strategy_used: self.active_strategy,
368            overhead_percent: self.config.max_overhead_percent,
369        };
370
371        Ok(MemoryAnalysisData {
372            raw_data: vec![],
373            statistics,
374            environment: self.environment.clone(),
375            session_metadata,
376        })
377    }
378
379    /// Shutdown backend.
380    pub fn shutdown(self) -> MemScopeResult<MemoryAnalysisData> {
381        info!("Shutting down unified backend");
382        self.collect_data()
383    }
384}
385
386impl Default for UnifiedBackend {
387    fn default() -> Self {
388        Self::new()
389    }
390}
391
392impl TrackingSession {
393    /// Get session identifier.
394    pub fn session_id(&self) -> &str {
395        &self.session_id
396    }
397
398    /// Get elapsed time since session start.
399    pub fn elapsed_time(&self) -> std::time::Duration {
400        self.start_time.elapsed()
401    }
402
403    /// Collect current tracking data.
404    pub fn collect_data(&self) -> MemScopeResult<MemoryAnalysisData> {
405        self.backend.collect_data()
406    }
407
408    /// End tracking session.
409    pub fn end_session(self) -> MemScopeResult<MemoryAnalysisData> {
410        info!("Ending tracking session: {}", self.session_id);
411        self.backend.collect_data()
412    }
413}
414
415/// Environment detector for runtime analysis.
416#[derive(Debug)]
417pub struct EnvironmentDetector {
418    config: DetectionConfig,
419}
420
421impl EnvironmentDetector {
422    /// Create new environment detector.
423    pub fn new(config: DetectionConfig) -> Self {
424        Self { config }
425    }
426
427    /// Perform environment detection.
428    pub fn detect(&self) -> MemScopeResult<EnvironmentDetection> {
429        let environment = UnifiedBackend::detect_environment()?;
430        let recommended_strategy = UnifiedBackend::select_strategy(&environment)?;
431
432        let thread_count = match &environment {
433            RuntimeEnvironment::SingleThreaded => 1,
434            RuntimeEnvironment::MultiThreaded { thread_count } => *thread_count,
435            RuntimeEnvironment::AsyncRuntime { .. } => 1,
436            RuntimeEnvironment::Hybrid { thread_count, .. } => *thread_count,
437        };
438
439        let confidence = self.config.confidence_level;
440
441        Ok(EnvironmentDetection {
442            environment,
443            recommended_strategy,
444            thread_count,
445            memory_usage: 0.0,
446            confidence,
447        })
448    }
449
450    /// Get the detection configuration.
451    pub fn config(&self) -> &DetectionConfig {
452        &self.config
453    }
454}
455
456impl Default for EnvironmentDetector {
457    fn default() -> Self {
458        Self::new(DetectionConfig::default())
459    }
460}
461
462/// Quick initialization function.
463pub fn initialize() -> MemScopeResult<UnifiedBackend> {
464    UnifiedBackend::initialize(BackendConfig::default())
465}
466
467/// Get a backend with default configuration.
468pub fn get_backend() -> UnifiedBackend {
469    UnifiedBackend::new()
470}
471
472/// Detect environment with default configuration.
473pub fn detect_environment() -> MemScopeResult<RuntimeEnvironment> {
474    UnifiedBackend::detect_environment()
475}
476
477/// Configuration for dispatcher behavior.
478#[derive(Debug, Clone)]
479pub struct DispatcherConfig {
480    /// Enable automatic strategy switching
481    pub auto_switch_strategies: bool,
482    /// Maximum number of concurrent trackers
483    pub max_concurrent_trackers: usize,
484    /// Performance monitoring interval (milliseconds)
485    pub metrics_interval_ms: u64,
486    /// Memory usage threshold for strategy switching (MB)
487    pub memory_threshold_mb: usize,
488}
489
490impl Default for DispatcherConfig {
491    fn default() -> Self {
492        Self {
493            auto_switch_strategies: true,
494            max_concurrent_trackers: 4,
495            metrics_interval_ms: 1000,
496            memory_threshold_mb: 100,
497        }
498    }
499}
500
501/// Performance metrics for dispatcher operations.
502#[derive(Debug, Clone, Default)]
503pub struct DispatcherMetrics {
504    /// Total tracking operations dispatched
505    pub total_dispatches: u64,
506    /// Strategy switch count
507    pub strategy_switches: u64,
508    /// Average dispatch latency (microseconds)
509    pub avg_dispatch_latency_us: f64,
510    /// Current memory overhead percentage
511    pub memory_overhead_percent: f64,
512    /// Active tracker count
513    pub active_trackers: usize,
514}
515
516/// Tracking operations that can be dispatched.
517#[derive(Debug, Clone)]
518pub enum TrackingOperation {
519    /// Start tracking operation
520    StartTracking,
521    /// Stop tracking operation
522    StopTracking,
523    /// Collect current data
524    CollectData,
525}
526
527/// Tracker type identifiers.
528#[derive(Debug, Clone, Copy, PartialEq, Eq)]
529pub enum TrackerType {
530    /// Single-threaded global tracker
531    SingleThread,
532    /// Multi-threaded with thread-local storage
533    MultiThread,
534    /// Async-aware task-local tracker
535    AsyncTracker,
536    /// Hybrid multi-thread + async tracker
537    HybridTracker,
538}
539
540/// Configuration for individual tracker instances.
541#[derive(Debug, Clone)]
542pub struct TrackerConfig {
543    /// Sampling rate (0.0 to 1.0)
544    pub sample_rate: f64,
545    /// Maximum memory overhead allowed (MB)
546    pub max_overhead_mb: usize,
547}
548
549impl Default for TrackerConfig {
550    fn default() -> Self {
551        Self {
552            sample_rate: 1.0,
553            max_overhead_mb: 50,
554        }
555    }
556}
557
558/// Statistics from individual tracker.
559#[derive(Debug, Clone, Default)]
560pub struct TrackerStatistics {
561    /// Allocations tracked by this tracker
562    pub allocations_tracked: u64,
563    /// Memory currently tracked (bytes)
564    pub memory_tracked_bytes: u64,
565    /// Tracker overhead (bytes)
566    pub overhead_bytes: u64,
567    /// Tracking duration (milliseconds)
568    pub tracking_duration_ms: u64,
569}
570
571/// Errors specific to tracker operations.
572#[derive(Debug)]
573pub enum TrackerError {
574    /// Tracker initialization failed
575    InitializationFailed { reason: String },
576    /// Tracking start failed
577    StartFailed { reason: String },
578    /// Data collection failed
579    DataCollectionFailed { reason: String },
580    /// Tracker configuration invalid
581    InvalidConfiguration { reason: String },
582}
583
584impl std::fmt::Display for TrackerError {
585    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
586        match self {
587            TrackerError::InitializationFailed { reason } => {
588                write!(f, "Tracker initialization failed: {}", reason)
589            }
590            TrackerError::StartFailed { reason } => {
591                write!(f, "Failed to start tracking: {}", reason)
592            }
593            TrackerError::DataCollectionFailed { reason } => {
594                write!(f, "Failed to collect tracking data: {}", reason)
595            }
596            TrackerError::InvalidConfiguration { reason } => {
597                write!(f, "Invalid tracker configuration: {}", reason)
598            }
599        }
600    }
601}
602
603impl std::error::Error for TrackerError {}
604
605/// Unified memory tracker interface.
606pub trait MemoryTracker: Send + Sync {
607    /// Initialize tracker with given configuration
608    fn initialize(&mut self, config: TrackerConfig) -> Result<(), TrackerError>;
609    /// Start tracking memory operations
610    fn start_tracking(&mut self) -> Result<(), TrackerError>;
611    /// Stop tracking and collect data
612    fn stop_tracking(&mut self) -> Result<Vec<u8>, TrackerError>;
613    /// Get current tracking statistics
614    fn get_statistics(&self) -> TrackerStatistics;
615    /// Check if tracker is currently active
616    fn is_active(&self) -> bool;
617    /// Get tracker type identifier
618    fn tracker_type(&self) -> TrackerType;
619}
620
621/// Central dispatcher for tracking operations.
622///
623/// **Note**: This struct is currently unused but reserved for future versions
624/// where it will handle automatic strategy selection based on runtime environment.
625pub struct TrackingDispatcher {
626    active_strategy: Option<TrackingStrategy>,
627    config: DispatcherConfig,
628    metrics: DispatcherMetrics,
629}
630
631impl TrackingDispatcher {
632    /// Create new tracking dispatcher.
633    pub fn new(config: DispatcherConfig) -> Self {
634        Self {
635            active_strategy: None,
636            config,
637            metrics: DispatcherMetrics::default(),
638        }
639    }
640
641    /// Select strategy for environment.
642    pub fn select_strategy(&mut self, environment: &RuntimeEnvironment) -> TrackingStrategy {
643        let strategy = match environment {
644            RuntimeEnvironment::SingleThreaded => TrackingStrategy::GlobalDirect,
645            RuntimeEnvironment::MultiThreaded { thread_count } => {
646                if *thread_count <= 2 {
647                    TrackingStrategy::GlobalDirect
648                } else {
649                    TrackingStrategy::ThreadLocal
650                }
651            }
652            RuntimeEnvironment::AsyncRuntime { .. } => TrackingStrategy::TaskLocal,
653            RuntimeEnvironment::Hybrid {
654                thread_count,
655                async_task_count,
656            } => {
657                if *thread_count > 1 && *async_task_count > 0 {
658                    TrackingStrategy::HybridTracking
659                } else if *async_task_count > 0 {
660                    TrackingStrategy::TaskLocal
661                } else {
662                    TrackingStrategy::ThreadLocal
663                }
664            }
665        };
666
667        self.active_strategy = Some(strategy);
668        strategy
669    }
670
671    /// Get active strategy.
672    pub fn active_strategy(&self) -> Option<&TrackingStrategy> {
673        self.active_strategy.as_ref()
674    }
675
676    /// Get current metrics.
677    pub fn metrics(&self) -> &DispatcherMetrics {
678        &self.metrics
679    }
680
681    /// Get configuration.
682    pub fn config(&self) -> &DispatcherConfig {
683        &self.config
684    }
685}
686
687impl Default for TrackingDispatcher {
688    fn default() -> Self {
689        Self::new(DispatcherConfig::default())
690    }
691}
692
693#[cfg(test)]
694mod tests {
695    use super::*;
696
697    #[test]
698    fn test_unified_backend_creation() {
699        let backend = UnifiedBackend::new();
700        assert!(matches!(
701            backend.environment(),
702            RuntimeEnvironment::SingleThreaded | RuntimeEnvironment::MultiThreaded { .. }
703        ));
704    }
705
706    #[test]
707    fn test_backend_initialization() {
708        let config = BackendConfig::default();
709        let backend = UnifiedBackend::initialize(config);
710        assert!(backend.is_ok());
711    }
712
713    #[test]
714    fn test_environment_detection() {
715        let env = UnifiedBackend::detect_environment();
716        assert!(env.is_ok());
717    }
718
719    #[test]
720    fn test_invalid_config_sample_rate() {
721        let config = BackendConfig {
722            sample_rate: 1.5,
723            ..Default::default()
724        };
725        let result = UnifiedBackend::initialize(config);
726        assert!(result.is_err());
727    }
728
729    #[test]
730    fn test_strategy_selection() {
731        let env = RuntimeEnvironment::SingleThreaded;
732        let strategy = UnifiedBackend::select_strategy(&env);
733        assert!(matches!(strategy, Ok(TrackingStrategy::GlobalDirect)));
734    }
735
736    #[test]
737    fn test_runtime_environment_variants() {
738        let single = RuntimeEnvironment::SingleThreaded;
739        let multi = RuntimeEnvironment::MultiThreaded { thread_count: 4 };
740        let async_env = RuntimeEnvironment::AsyncRuntime {
741            runtime_type: AsyncRuntimeType::Tokio,
742        };
743        let hybrid = RuntimeEnvironment::Hybrid {
744            thread_count: 2,
745            async_task_count: 4,
746        };
747
748        assert_ne!(single, multi);
749        assert_ne!(multi, async_env);
750        assert_ne!(async_env, hybrid);
751    }
752
753    #[test]
754    fn test_tracking_strategy_variants() {
755        let global = TrackingStrategy::GlobalDirect;
756        let thread_local = TrackingStrategy::ThreadLocal;
757        let task_local = TrackingStrategy::TaskLocal;
758        let hybrid = TrackingStrategy::HybridTracking;
759
760        assert_ne!(global, thread_local);
761        assert_ne!(thread_local, task_local);
762        assert_ne!(task_local, hybrid);
763    }
764
765    #[test]
766    fn test_tracking_session() {
767        let mut backend = UnifiedBackend::new();
768        let session = backend.start_tracking();
769        assert!(session.is_ok());
770
771        let session = session.unwrap();
772        assert!(!session.session_id().is_empty());
773        let _elapsed = session.elapsed_time();
774    }
775
776    #[test]
777    fn test_environment_detector() {
778        let detector = EnvironmentDetector::default();
779        let result = detector.detect();
780        assert!(result.is_ok());
781    }
782
783    #[test]
784    fn test_initialize_function() {
785        let result = initialize();
786        assert!(result.is_ok());
787    }
788
789    #[test]
790    fn test_get_backend_function() {
791        let backend = get_backend();
792        assert!(matches!(
793            backend.environment(),
794            RuntimeEnvironment::SingleThreaded | RuntimeEnvironment::MultiThreaded { .. }
795        ));
796    }
797}