1#![allow(unused_must_use)]
2
3#[cfg(feature = "full")]
58pub mod category;
59#[cfg(feature = "full")]
60pub mod collector;
61#[cfg(feature = "full")]
64pub mod operation;
65#[cfg(feature = "full")]
68pub mod timer;
69
70#[cfg(not(feature = "full"))]
74pub mod category {
75 pub trait Category: Send + Sync {
76 fn get_name(&self) -> &str;
77 fn get_description(&self) -> &str;
78 fn color_hint(&self) -> Option<&str> {
79 None
80 }
81 fn priority(&self) -> i32 {
82 0
83 }
84 }
85
86 #[derive(Debug)]
87 pub struct NoCategory;
88
89 impl Category for NoCategory {
90 fn get_name(&self) -> &str {
91 "NoCategory"
92 }
93 fn get_description(&self) -> &str {
94 "Default category when none is specified"
95 }
96 }
97}
98
99#[cfg(not(feature = "full"))]
100pub mod operation {
101 use crate::category::{Category, NoCategory};
102 use std::fmt::Debug;
103
104 pub trait Operation: Debug + Send + Sync {
105 fn get_category(&self) -> &dyn Category {
106 &NoCategory
107 }
108
109 fn to_str(&self) -> String {
110 format!("{:?}", self)
111 }
112 }
113
114 #[derive(Debug)]
115 pub struct SimpleOperation {
116 pub name: String,
117 }
118
119 impl SimpleOperation {
120 pub fn new(name: impl Into<String>) -> Self {
121 Self { name: name.into() }
122 }
123 }
124
125 impl Operation for SimpleOperation {
126 fn to_str(&self) -> String {
127 self.name.clone()
128 }
129 }
130}
131
132#[cfg(not(feature = "full"))]
133pub mod collector {
134 use std::collections::HashMap;
135 use std::time::Duration;
136
137 #[derive(Debug, Clone, Default)]
138 pub struct OperationStats {
139 pub count: usize,
140 pub total: Duration,
141 }
142
143 impl OperationStats {
144 pub fn mean(&self) -> Duration {
145 if self.count == 0 {
146 Duration::ZERO
147 } else {
148 self.total / (self.count as u32)
149 }
150 }
151 }
152
153 pub struct ProfileCollector;
154
155 impl ProfileCollector {
156 pub fn record(_key: &str, _duration_micros: u64) {}
157 pub fn get_stats(_key: &str) -> Option<OperationStats> {
158 None
159 }
160 pub fn get_all_stats() -> HashMap<String, OperationStats> {
161 HashMap::new()
162 }
163 pub fn clear_all() {}
164 pub fn reset_all() {}
165 pub fn reset_operation(_key: &str) {}
166 pub fn has_data() -> bool {
167 false
168 }
169 pub fn total_operations() -> u64 {
170 1 }
172 pub fn get_summary() -> SummaryStats {
173 SummaryStats::default()
174 }
175 pub fn report_stats() {}
176 }
177
178 #[derive(Debug, Default)]
179 pub struct SummaryStats {
180 pub total_operations: u64,
181 pub unique_operations: usize,
182 pub total_time_micros: u64,
183 }
184
185 #[derive(Debug, Clone, Copy)]
186 pub enum TimeFormat {
187 Microseconds,
188 Milliseconds,
189 Seconds,
190 Auto,
191 }
192
193 #[derive(Debug, Clone, Copy)]
194 pub enum SortMetric {
195 TotalTime,
196 MeanTime,
197 MaxTime,
198 CallCount,
199 }
200
201 #[derive(Debug, Clone, Copy)]
202 pub struct Percentile {
203 pub p50: u64,
204 pub p95: u64,
205 pub p99: u64,
206 pub p999: u64,
207 }
208
209 #[derive(Debug)]
210 pub struct ReportConfig {
211 pub include_percentiles: bool,
212 pub group_by_category: bool,
213 pub time_format: TimeFormat,
214 pub sort_by: SortMetric,
215 pub sort_by_time: bool,
216 pub min_samples: u64,
217 }
218
219 impl Default for ReportConfig {
220 fn default() -> Self {
221 Self {
222 include_percentiles: false,
223 group_by_category: false,
224 time_format: TimeFormat::Auto,
225 sort_by: SortMetric::TotalTime,
226 sort_by_time: false,
227 min_samples: 0,
228 }
229 }
230 }
231
232 pub struct ProfileReport {
233 pub stats: HashMap<String, OperationStats>,
234 pub config: ReportConfig,
235 }
236
237 impl ProfileReport {
238 pub fn generate() -> Self {
239 Self {
240 stats: HashMap::new(),
241 config: ReportConfig::default(),
242 }
243 }
244
245 pub fn generate_with_config(_config: ReportConfig) -> Self {
246 Self {
247 stats: HashMap::new(),
248 config: ReportConfig::default(),
249 }
250 }
251
252 pub fn quick_summary(&self) -> String {
253 String::new()
254 }
255
256 pub fn summary_stats(&self) -> SummaryStats {
257 SummaryStats::default()
258 }
259
260 pub fn to_string(&self) -> String {
261 String::new()
262 }
263
264 pub fn top_operations_by(
265 &self,
266 _metric: SortMetric,
267 _limit: usize,
268 ) -> Vec<(String, OperationStats)> {
269 Vec::new()
270 }
271 }
272
273 impl std::fmt::Debug for ProfileReport {
274 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
275 write!(f, "")
276 }
277 }
278
279 pub struct ReportBuilder {
280 _phantom: std::marker::PhantomData<()>,
281 }
282
283 impl Default for ReportBuilder {
284 fn default() -> Self {
285 Self::new()
286 }
287 }
288
289 impl ReportBuilder {
290 pub fn new() -> Self {
291 Self {
292 _phantom: std::marker::PhantomData,
293 }
294 }
295
296 pub fn group_by_category(self, _enabled: bool) -> Self {
297 self
298 }
299 pub fn include_percentiles(self, _enabled: bool) -> Self {
300 self
301 }
302 pub fn time_format(self, _format: TimeFormat) -> Self {
303 self
304 }
305 pub fn sort_by_time(self, _enabled: bool) -> Self {
306 self
307 }
308 pub fn build(self) -> ProfileReport {
309 ProfileReport::generate()
310 }
311 }
312}
313
314#[cfg(not(feature = "full"))]
315pub mod timer {
316 use crate::operation::Operation;
317
318 pub struct ProfileTimer<'a> {
319 _operation: &'a dyn Operation,
320 }
321
322 impl<'a> ProfileTimer<'a> {
323 pub fn new(operation: &'a dyn Operation) -> Self {
324 Self {
325 _operation: operation,
326 }
327 }
328 }
329
330 impl<'a> Drop for ProfileTimer<'a> {
331 fn drop(&mut self) {
332 }
334 }
335
336 pub struct ProfileTimerAsync<'a> {
337 _operation: &'a dyn Operation,
338 }
339
340 impl<'a> ProfileTimerAsync<'a> {
341 pub fn new(operation: &'a dyn Operation) -> Self {
342 Self {
343 _operation: operation,
344 }
345 }
346
347 #[allow(unused_must_use)]
348 pub async fn run<F, R>(self, fut: F) -> R
349 where
350 F: std::future::Future<Output = R>,
351 {
352 fut.await
354 }
355 }
356
357 pub struct PausableTimer<'a> {
358 _operation: &'a dyn Operation,
359 }
360}
361
362pub use category::{Category, NoCategory};
364pub use collector::{OperationStats, ProfileCollector, SummaryStats};
365pub use operation::Operation;
366pub use timer::{PausableTimer, ProfileTimer, ProfileTimerAsync};
367
368#[cfg(any(feature = "macros", feature = "full"))]
370pub use quantum_pulse_macros::Operation as ProfileOp;
371
372#[macro_export]
396macro_rules! profile {
397 ($operation:expr, $code:block) => {{
398 let _timer = $crate::ProfileTimer::new(&$operation);
399 $code
400 }};
401}
402
403#[macro_export]
434#[doc(alias = "await")]
435macro_rules! profile_async {
436 ($operation:expr, $code:expr) => {
437 $crate::ProfileTimerAsync::new(&$operation).run($code)
440 };
441}
442
443#[macro_export]
466macro_rules! scoped_timer {
467 ($operation:expr) => {
468 let _timer = $crate::ProfileTimer::new(&$operation);
469 };
470}
471
472#[cfg(test)]
473mod tests {
474 use super::*;
475
476 #[test]
477 #[cfg(feature = "full")]
478 fn test_basic_profiling() {
479 #[derive(Debug)]
480 enum TestOperation {
481 Test,
482 }
483
484 impl Operation for TestOperation {
485 fn to_str(&self) -> String {
486 "test_operation".to_string()
487 }
488 }
489
490 ProfileCollector::clear_all();
491
492 let op = TestOperation::Test;
493 let result = profile!(op, {
494 std::thread::sleep(std::time::Duration::from_millis(1));
495 42
496 });
497
498 assert_eq!(result, 42);
499 assert!(ProfileCollector::has_data());
500
501 let stats = ProfileCollector::get_stats("NoCategory::test_operation");
502 assert!(stats.is_some());
503 assert_eq!(stats.unwrap().count, 1);
504 }
505
506 #[test]
507 fn test_custom_category() {
508 #[derive(Debug)]
509 struct TestCategory;
510
511 impl Category for TestCategory {
512 fn get_name(&self) -> &str {
513 "Test"
514 }
515 fn get_description(&self) -> &str {
516 "Test category"
517 }
518 }
519
520 #[derive(Debug)]
521 struct TestOp;
522
523 impl Operation for TestOp {
524 fn get_category(&self) -> &dyn Category {
525 &TestCategory
526 }
527 }
528
529 ProfileCollector::clear_all();
530
531 let op = TestOp;
532 profile!(op, {
533 std::thread::sleep(std::time::Duration::from_millis(1));
534 });
535
536 assert!(ProfileCollector::total_operations() > 0);
538 }
539
540 #[tokio::test]
541 #[cfg(feature = "full")]
542 async fn test_async_profiling() {
543 #[derive(Debug)]
544 enum TestOperation {
545 AsyncTest,
546 }
547
548 impl Operation for TestOperation {
549 fn to_str(&self) -> String {
550 "async_test".to_string()
551 }
552 }
553
554 ProfileCollector::clear_all();
555
556 let op = TestOperation::AsyncTest;
557 let result = profile_async!(op, async {
558 tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
559 "async_result"
560 })
561 .await;
562
563 assert_eq!(result, "async_result");
564 assert!(ProfileCollector::has_data());
565 }
566
567 #[test]
568 #[cfg(feature = "full")]
569 fn test_profile_macro() {
570 #[derive(Debug)]
571 enum TestOperation {
572 MacroTest,
573 }
574
575 impl Operation for TestOperation {
576 fn to_str(&self) -> String {
577 "macro_test".to_string()
578 }
579 }
580
581 ProfileCollector::clear_all();
582
583 let op = TestOperation::MacroTest;
584 let result = profile!(op, {
585 std::thread::sleep(std::time::Duration::from_millis(1));
586 100
587 });
588
589 assert_eq!(result, 100);
590 assert!(ProfileCollector::get_stats("NoCategory::macro_test").is_some());
591 }
592
593 #[test]
594 #[cfg(feature = "full")]
595 fn test_scoped_timer() {
596 ProfileCollector::clear_all();
597
598 {
599 #[derive(Debug)]
600 enum TestOperation {
601 ScopedTest,
602 }
603
604 impl Operation for TestOperation {
605 fn to_str(&self) -> String {
606 "scoped_test".to_string()
607 }
608 }
609
610 let op = TestOperation::ScopedTest;
611 scoped_timer!(op);
612 std::thread::sleep(std::time::Duration::from_millis(1));
613 }
614
615 assert!(ProfileCollector::has_data());
616 assert!(ProfileCollector::get_stats("NoCategory::scoped_test").is_some());
617 }
618}