/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 | | } |