Coverage Report

Created: 2025-06-23 14:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/b/lab/denet/src/lib.rs
Line
Count
Source
1
//! Denet: A high-performance process monitoring library
2
//!
3
//! Denet provides accurate measurement of process resource usage, including
4
//! CPU, memory, disk I/O, and network I/O. It's designed to be lightweight,
5
//! accurate, and cross-platform.
6
//!
7
//! # Architecture
8
//!
9
//! The library is organized into focused modules:
10
//! - `core`: Pure Rust monitoring functionality
11
//! - `monitor`: Metrics types and summary generation
12
//! - `config`: Configuration structures and builders
13
//! - `error`: Comprehensive error handling
14
//! - `cpu_sampler`: Platform-specific CPU measurement
15
//! - `python`: PyO3 bindings (when feature is enabled)
16
//!
17
//! # Platform Support
18
//!
19
//! CPU measurement strategies:
20
//! - Linux: Direct procfs reading - matches top/htop measurements
21
//! - macOS: Will use host_processor_info API and libproc (planned)
22
//! - Windows: Will use GetProcessTimes and Performance Counters (planned)
23
24
// Core modules
25
pub mod config;
26
pub mod core;
27
pub mod error;
28
pub mod monitor;
29
30
// Platform-specific modules
31
#[cfg(target_os = "linux")]
32
pub mod cpu_sampler;
33
34
// eBPF profiling (optional feature)
35
#[cfg(feature = "ebpf")]
36
pub mod ebpf;
37
38
// Python bindings
39
#[cfg(feature = "python")]
40
mod python;
41
42
// Re-export main types
43
pub use core::{ProcessMonitor, ProcessResult};
44
pub use monitor::*;
45
46
// Re-export for convenience
47
pub use config::{DenetConfig, MonitorConfig, OutputConfig, OutputFormat};
48
pub use error::{DenetError, Result};
49
50
// Python-specific code is completely isolated here
51
#[cfg(feature = "python")]
52
mod python_bindings {
53
    use super::python;
54
    use pyo3::prelude::*;
55
56
    #[pymodule]
57
    pub fn _denet(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
58
        python::register_python_module(m)
59
    }
60
}
61
62
/// Run a simple monitoring loop with better error handling and configuration
63
5
pub fn run_monitor(
64
5
    cmd: Vec<String>,
65
5
    base_interval_ms: u64,
66
5
    max_interval_ms: u64,
67
5
    since_process_start: bool,
68
5
) -> Result<()> {
69
5
    let 
monitor2
= create_monitor(cmd, base_interval_ms, max_interval_ms, since_process_start)
?3
;
70
2
    execute_monitoring_loop(monitor, base_interval_ms)
71
5
}
72
73
/// Create a ProcessMonitor with the given configuration
74
7
fn create_monitor(
75
7
    cmd: Vec<String>,
76
7
    base_interval_ms: u64,
77
7
    max_interval_ms: u64,
78
7
    since_process_start: bool,
79
7
) -> Result<ProcessMonitor> {
80
    use std::time::Duration;
81
7
    ProcessMonitor::new_with_options(
82
7
        cmd,
83
7
        Duration::from_millis(base_interval_ms),
84
7
        Duration::from_millis(max_interval_ms),
85
7
        since_process_start,
86
7
    )
87
7
    .map_err(DenetError::Io)
88
7
}
89
90
/// Execute the monitoring loop until the process completes
91
2
fn execute_monitoring_loop(monitor: ProcessMonitor, interval_ms: u64) -> Result<()> {
92
    use crate::core::constants::timeouts;
93
    use crate::core::monitoring_utils::monitor_until_completion;
94
    use std::time::Duration;
95
96
    // For testing purposes, use a very short timeout to avoid long-running tests
97
2
    let timeout = if cfg!(test) {
98
2
        Some(timeouts::SHORT)
99
    } else {
100
0
        None
101
    };
102
103
2
    let _result = monitor_until_completion(monitor, Duration::from_millis(interval_ms), timeout);
104
2
105
2
    Ok(())
106
2
}
107
108
#[cfg(test)]
109
mod tests {
110
    use super::*;
111
    use std::time::Duration;
112
113
    #[test]
114
1
    fn test_run_monitor_with_invalid_command() {
115
        use crate::core::constants::sampling;
116
        // Test with non-existent command
117
1
        let result = run_monitor(
118
1
            vec!["non_existent_command_12345".to_string()],
119
1
            sampling::STANDARD.as_millis() as u64,
120
1
            sampling::MAX_ADAPTIVE.as_millis() as u64,
121
1
            false,
122
1
        );
123
1
        assert!(result.is_err());
124
1
    }
125
126
    #[test]
127
1
    fn test_run_monitor_with_valid_command() {
128
        use crate::core::constants::sampling;
129
        // Test with a command that should succeed quickly
130
        #[cfg(target_family = "unix")]
131
        {
132
1
            let result = run_monitor(
133
1
                vec!["true".to_string()],
134
1
                sampling::STANDARD.as_millis() as u64,
135
1
                sampling::MAX_ADAPTIVE.as_millis() as u64,
136
1
                false,
137
1
            );
138
1
            assert!(result.is_ok());
139
        }
140
141
        #[cfg(target_family = "windows")]
142
        {
143
            let result = run_monitor(
144
                vec!["cmd".to_string(), "/c".to_string(), "exit".to_string()],
145
                sampling::STANDARD.as_millis() as u64,
146
                sampling::MAX_ADAPTIVE.as_millis() as u64,
147
                false,
148
            );
149
            assert!(result.is_ok());
150
        }
151
1
    }
152
153
    #[test]
154
1
    fn test_run_monitor_since_process_start() {
155
        use crate::core::constants::sampling;
156
        #[cfg(target_family = "unix")]
157
        {
158
1
            let result = run_monitor(
159
1
                vec!["true".to_string()],
160
1
                sampling::STANDARD.as_millis() as u64,
161
1
                sampling::MAX_ADAPTIVE.as_millis() as u64,
162
1
                true,
163
1
            );
164
1
            assert!(result.is_ok());
165
        }
166
1
    }
167
168
    #[test]
169
1
    fn test_create_monitor_success() {
170
        use crate::core::constants::sampling;
171
        #[cfg(target_family = "unix")]
172
        {
173
1
            let monitor = create_monitor(
174
1
                vec!["true".to_string()],
175
1
                sampling::STANDARD.as_millis() as u64,
176
1
                sampling::MAX_ADAPTIVE.as_millis() as u64,
177
1
                false,
178
1
            );
179
1
            assert!(monitor.is_ok());
180
        }
181
1
    }
182
183
    #[test]
184
1
    fn test_create_monitor_failure() {
185
        use crate::core::constants::sampling;
186
1
        let monitor = create_monitor(
187
1
            vec!["non_existent_command_12345".to_string()],
188
1
            sampling::STANDARD.as_millis() as u64,
189
1
            sampling::MAX_ADAPTIVE.as_millis() as u64,
190
1
            false,
191
1
        );
192
1
        assert!(monitor.is_err());
193
1
    }
194
195
    #[test]
196
1
    fn test_execute_monitoring_loop() {
197
        use crate::core::constants::{delays, sampling};
198
199
        // Test with a non-existent PID to ensure the function handles errors gracefully
200
        // This will fail quickly without long monitoring loops
201
1
        let monitor = ProcessMonitor::from_pid_with_options(
202
1
            999999, // Non-existent PID
203
1
            delays::MINIMAL,
204
1
            sampling::FAST,
205
1
            false,
206
1
        );
207
208
        // If monitor creation fails, that's expected and fine for this test
209
1
        if let Ok(
monitor0
) = monitor {
210
            // Test the function exists and can be called - it will timeout quickly
211
0
            let result = execute_monitoring_loop(monitor, delays::MINIMAL.as_millis() as u64);
212
0
            assert!(result.is_ok());
213
1
        }
214
1
    }
215
216
    #[test]
217
1
    fn test_re_exports() {
218
        // Test that all re-exports are accessible
219
        use crate::core::constants::sampling;
220
        use crate::ProcessMonitor;
221
        use crate::{DenetConfig, MonitorConfig, OutputConfig, OutputFormat};
222
        use crate::{DenetError, Result};
223
224
        // Test ProcessMonitor re-export
225
1
        let pid = std::process::id() as usize;
226
1
        let monitor = ProcessMonitor::from_pid_with_options(
227
1
            pid,
228
1
            sampling::STANDARD,
229
1
            sampling::MAX_ADAPTIVE,
230
1
            false,
231
1
        );
232
1
        assert!(monitor.is_ok());
233
234
        // Test config re-exports
235
1
        let _config = DenetConfig::default();
236
1
        let _monitor_config = MonitorConfig::default();
237
1
        let _output_config = OutputConfig::default();
238
1
        let _format = OutputFormat::default();
239
1
240
1
        // Test error re-exports
241
1
        let _error = DenetError::Other("test".to_string());
242
1
        let _result: Result<()> = Ok(());
243
1
    }
244
245
    #[test]
246
1
    fn test_different_intervals() {
247
        use crate::core::constants::sampling;
248
1
        let result = run_monitor(
249
1
            vec![],
250
1
            sampling::FAST.as_millis() as u64,
251
1
            sampling::SLOW.as_millis() as u64,
252
1
            false,
253
1
        );
254
1
        assert!(result.is_err()); // Empty command should fail
255
256
1
        let result = run_monitor(
257
1
            vec![],
258
1
            sampling::SLOW.as_millis() as u64,
259
1
            (sampling::SLOW.as_millis() * 10) as u64,
260
1
            true,
261
1
        );
262
1
        assert!(result.is_err()); // Empty command should fail
263
1
    }
264
265
    #[cfg(feature = "python")]
266
    #[test]
267
    fn test_python_module_exists() {
268
        // This test just ensures the python module code compiles
269
        // when the python feature is enabled
270
        use crate::python_bindings::_denet;
271
272
        // We can't actually test the module initialization without a Python runtime,
273
        // but we can ensure the function exists and is callable
274
        assert!(std::mem::size_of_val(&_denet) > 0);
275
    }
276
}