1use std::collections::HashMap;
7use std::sync::{Arc, Mutex};
8use std::time::Instant;
9
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone)]
14pub struct OperationProfiler {
15 entries: Arc<Mutex<Vec<ProfileEntry>>>,
16 active_operations: Arc<Mutex<HashMap<String, Instant>>>,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct ProfileEntry {
22 pub name: String,
24 pub duration_ms: u64,
26 pub start_time: std::time::SystemTime,
28 pub memory_delta: i64,
30 pub metadata: HashMap<String, String>,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct ProfileStats {
37 pub name: String,
39 pub count: usize,
41 pub total_duration_ms: u64,
43 pub avg_duration_ms: f64,
45 pub min_duration_ms: u64,
47 pub max_duration_ms: u64,
49 pub std_dev_ms: f64,
51 pub p50_ms: u64,
53 pub p95_ms: u64,
55 pub p99_ms: u64,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
61pub enum PackageOperation {
62 Create,
64 Load,
66 Save,
68 AddResource,
70 GetResource,
72 Compress,
74 Decompress,
76 Sign,
78 Verify,
80 Encrypt,
82 Decrypt,
84 ResolveDependencies,
86 Install,
88 Custom(String),
90}
91
92impl PackageOperation {
93 pub fn as_str(&self) -> &str {
95 match self {
96 Self::Create => "create",
97 Self::Load => "load",
98 Self::Save => "save",
99 Self::AddResource => "add_resource",
100 Self::GetResource => "get_resource",
101 Self::Compress => "compress",
102 Self::Decompress => "decompress",
103 Self::Sign => "sign",
104 Self::Verify => "verify",
105 Self::Encrypt => "encrypt",
106 Self::Decrypt => "decrypt",
107 Self::ResolveDependencies => "resolve_dependencies",
108 Self::Install => "install",
109 Self::Custom(name) => name,
110 }
111 }
112}
113
114impl OperationProfiler {
115 pub fn new() -> Self {
117 Self {
118 entries: Arc::new(Mutex::new(Vec::new())),
119 active_operations: Arc::new(Mutex::new(HashMap::new())),
120 }
121 }
122
123 pub fn start(&self, operation: &str) {
125 let mut active = self
126 .active_operations
127 .lock()
128 .expect("lock should not be poisoned");
129 active.insert(operation.to_string(), Instant::now());
130 }
131
132 pub fn end(&self, operation: &str) {
134 self.end_with_metadata(operation, HashMap::new());
135 }
136
137 pub fn end_with_metadata(&self, operation: &str, metadata: HashMap<String, String>) {
139 let mut active = self
140 .active_operations
141 .lock()
142 .expect("lock should not be poisoned");
143 if let Some(start) = active.remove(operation) {
144 let duration = start.elapsed();
145 let entry = ProfileEntry {
146 name: operation.to_string(),
147 duration_ms: duration.as_millis() as u64,
148 start_time: std::time::SystemTime::now() - duration,
149 memory_delta: 0, metadata,
151 };
152
153 let mut entries = self.entries.lock().expect("lock should not be poisoned");
154 entries.push(entry);
155 }
156 }
157
158 pub fn entries(&self) -> Vec<ProfileEntry> {
160 self.entries
161 .lock()
162 .expect("lock should not be poisoned")
163 .clone()
164 }
165
166 pub fn clear(&self) {
168 self.entries
169 .lock()
170 .expect("lock should not be poisoned")
171 .clear();
172 self.active_operations
173 .lock()
174 .expect("lock should not be poisoned")
175 .clear();
176 }
177
178 pub fn stats_for(&self, operation: &str) -> Option<ProfileStats> {
180 let entries = self.entries.lock().expect("lock should not be poisoned");
181 let operation_entries: Vec<_> = entries.iter().filter(|e| e.name == operation).collect();
182
183 if operation_entries.is_empty() {
184 return None;
185 }
186
187 let count = operation_entries.len();
188 let durations: Vec<u64> = operation_entries.iter().map(|e| e.duration_ms).collect();
189
190 let total: u64 = durations.iter().sum();
191 let avg = total as f64 / count as f64;
192 let min = *durations.iter().min().expect("reduction should succeed");
193 let max = *durations.iter().max().expect("reduction should succeed");
194
195 let variance = durations
197 .iter()
198 .map(|d| {
199 let diff = *d as f64 - avg;
200 diff * diff
201 })
202 .sum::<f64>()
203 / count as f64;
204 let std_dev = variance.sqrt();
205
206 let mut sorted_durations = durations.clone();
208 sorted_durations.sort_unstable();
209 let p50 = percentile(&sorted_durations, 50.0);
210 let p95 = percentile(&sorted_durations, 95.0);
211 let p99 = percentile(&sorted_durations, 99.0);
212
213 Some(ProfileStats {
214 name: operation.to_string(),
215 count,
216 total_duration_ms: total,
217 avg_duration_ms: avg,
218 min_duration_ms: min,
219 max_duration_ms: max,
220 std_dev_ms: std_dev,
221 p50_ms: p50,
222 p95_ms: p95,
223 p99_ms: p99,
224 })
225 }
226
227 pub fn all_stats(&self) -> Vec<ProfileStats> {
229 let entries = self.entries.lock().expect("lock should not be poisoned");
230 let mut operation_names: Vec<String> = entries.iter().map(|e| e.name.clone()).collect();
231 operation_names.sort();
232 operation_names.dedup();
233
234 drop(entries); operation_names
237 .into_iter()
238 .filter_map(|name| self.stats_for(&name))
239 .collect()
240 }
241
242 pub fn export_json(&self) -> Result<String, serde_json::Error> {
244 let stats = self.all_stats();
245 serde_json::to_string_pretty(&stats)
246 }
247
248 pub fn total_time_ms(&self) -> u64 {
250 self.entries
251 .lock()
252 .expect("lock should not be poisoned")
253 .iter()
254 .map(|e| e.duration_ms)
255 .sum()
256 }
257
258 pub fn operation_count(&self) -> usize {
260 self.entries
261 .lock()
262 .expect("lock should not be poisoned")
263 .len()
264 }
265}
266
267impl Default for OperationProfiler {
268 fn default() -> Self {
269 Self::new()
270 }
271}
272
273fn percentile(sorted_data: &[u64], p: f64) -> u64 {
275 if sorted_data.is_empty() {
276 return 0;
277 }
278 let index = ((p / 100.0) * (sorted_data.len() - 1) as f64).round() as usize;
279 sorted_data[index.min(sorted_data.len() - 1)]
280}
281
282pub struct ProfileGuard<'a> {
284 profiler: &'a OperationProfiler,
285 operation: String,
286 metadata: HashMap<String, String>,
287}
288
289impl<'a> ProfileGuard<'a> {
290 pub fn new(profiler: &'a OperationProfiler, operation: impl Into<String>) -> Self {
292 let operation = operation.into();
293 profiler.start(&operation);
294 Self {
295 profiler,
296 operation,
297 metadata: HashMap::new(),
298 }
299 }
300
301 pub fn add_metadata(&mut self, key: impl Into<String>, value: impl Into<String>) {
303 self.metadata.insert(key.into(), value.into());
304 }
305}
306
307impl<'a> Drop for ProfileGuard<'a> {
308 fn drop(&mut self) {
309 self.profiler
310 .end_with_metadata(&self.operation, self.metadata.clone());
311 }
312}
313
314static GLOBAL_PROFILER: once_cell::sync::Lazy<OperationProfiler> =
316 once_cell::sync::Lazy::new(OperationProfiler::new);
317
318pub fn global_profiler() -> &'static OperationProfiler {
320 &GLOBAL_PROFILER
321}
322
323pub fn profile<F, R>(operation: &str, f: F) -> R
325where
326 F: FnOnce() -> R,
327{
328 let _guard = ProfileGuard::new(&GLOBAL_PROFILER, operation);
329 f()
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335 use std::time::Duration;
336
337 #[test]
338 fn test_profiler_basic() {
339 let profiler = OperationProfiler::new();
340
341 profiler.start("test_op");
342 std::thread::sleep(Duration::from_millis(10));
343 profiler.end("test_op");
344
345 let entries = profiler.entries();
346 assert_eq!(entries.len(), 1);
347 assert_eq!(entries[0].name, "test_op");
348 assert!(entries[0].duration_ms >= 10);
349 }
350
351 #[test]
352 fn test_profiler_multiple_operations() {
353 let profiler = OperationProfiler::new();
354
355 for _ in 0..5 {
356 profiler.start("op");
357 std::thread::sleep(Duration::from_millis(5));
358 profiler.end("op");
359 }
360
361 let stats = profiler.stats_for("op").unwrap();
362 assert_eq!(stats.count, 5);
363 assert!(stats.avg_duration_ms >= 5.0);
364 assert!(stats.total_duration_ms >= 25);
365 }
366
367 #[test]
368 fn test_profiler_stats() {
369 let profiler = OperationProfiler::new();
370
371 profiler.start("test");
373 std::thread::sleep(Duration::from_millis(10));
374 profiler.end("test");
375
376 profiler.start("test");
377 std::thread::sleep(Duration::from_millis(20));
378 profiler.end("test");
379
380 let stats = profiler.stats_for("test").unwrap();
381 assert_eq!(stats.count, 2);
382 assert!(stats.min_duration_ms >= 10);
383 assert!(stats.max_duration_ms >= 20);
384 assert!(stats.avg_duration_ms >= 15.0);
385 }
386
387 #[test]
388 fn test_profile_guard() {
389 let profiler = OperationProfiler::new();
390
391 {
392 let _guard = ProfileGuard::new(&profiler, "guarded_op");
393 std::thread::sleep(Duration::from_millis(10));
394 } let entries = profiler.entries();
397 assert_eq!(entries.len(), 1);
398 assert_eq!(entries[0].name, "guarded_op");
399 }
400
401 #[test]
402 fn test_profile_guard_with_metadata() {
403 let profiler = OperationProfiler::new();
404
405 {
406 let mut guard = ProfileGuard::new(&profiler, "meta_op");
407 guard.add_metadata("key", "value");
408 std::thread::sleep(Duration::from_millis(5));
409 }
410
411 let entries = profiler.entries();
412 assert_eq!(entries.len(), 1);
413 assert_eq!(entries[0].metadata.get("key").unwrap(), "value");
414 }
415
416 #[test]
417 fn test_profiler_clear() {
418 let profiler = OperationProfiler::new();
419
420 profiler.start("op");
421 profiler.end("op");
422 assert_eq!(profiler.entries().len(), 1);
423
424 profiler.clear();
425 assert_eq!(profiler.entries().len(), 0);
426 }
427
428 #[test]
429 fn test_profiler_export_json() {
430 let profiler = OperationProfiler::new();
431
432 profiler.start("test");
433 std::thread::sleep(Duration::from_millis(5));
434 profiler.end("test");
435
436 let json = profiler.export_json().unwrap();
437 assert!(json.contains("test"));
438 assert!(json.contains("count"));
439 let _parsed: Vec<ProfileStats> = serde_json::from_str(&json).unwrap();
441 }
442
443 #[test]
444 fn test_package_operation_names() {
445 assert_eq!(PackageOperation::Create.as_str(), "create");
446 assert_eq!(PackageOperation::Load.as_str(), "load");
447 assert_eq!(PackageOperation::Compress.as_str(), "compress");
448 assert_eq!(
449 PackageOperation::Custom("custom_op".to_string()).as_str(),
450 "custom_op"
451 );
452 }
453
454 #[test]
455 fn test_global_profiler() {
456 global_profiler().clear(); let result = profile("global_test", || {
459 std::thread::sleep(Duration::from_millis(5));
460 42
461 });
462
463 assert_eq!(result, 42);
464 let entries = global_profiler().entries();
465 assert!(!entries.is_empty());
466 }
467
468 #[test]
469 fn test_percentile_calculation() {
470 let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
471 assert_eq!(percentile(&data, 0.0), 1);
472 assert_eq!(percentile(&data, 50.0), 6);
474 assert_eq!(percentile(&data, 100.0), 10);
475
476 let small = vec![1, 2, 3];
478 assert_eq!(percentile(&small, 0.0), 1);
479 assert_eq!(percentile(&small, 50.0), 2);
480 assert_eq!(percentile(&small, 100.0), 3);
481 }
482}