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#[macro_export]
392macro_rules! profile {
393 ($operation:expr, $code:block) => {{
394 let _timer = $crate::ProfileTimer::new(&$operation);
395 $code
396 }};
397}
398
399#[macro_export]
430#[doc(alias = "await")]
431macro_rules! profile_async {
432 ($operation:expr, $code:expr) => {
433 $crate::ProfileTimerAsync::new(&$operation).run($code)
436 };
437}
438
439#[macro_export]
462macro_rules! scoped_timer {
463 ($operation:expr) => {
464 let _timer = $crate::ProfileTimer::new(&$operation);
465 };
466}
467
468#[cfg(test)]
469mod tests {
470 use super::*;
471
472 #[test]
473 #[cfg(feature = "full")]
474 fn test_basic_profiling() {
475 #[derive(Debug)]
476 enum TestOperation {
477 Test,
478 }
479
480 impl Operation for TestOperation {
481 fn to_str(&self) -> String {
482 "test_operation".to_string()
483 }
484 }
485
486 ProfileCollector::clear_all();
487
488 let op = TestOperation::Test;
489 let result = profile!(op, {
490 std::thread::sleep(std::time::Duration::from_millis(1));
491 42
492 });
493
494 assert_eq!(result, 42);
495 assert!(ProfileCollector::has_data());
496
497 let stats = ProfileCollector::get_stats("NoCategory::test_operation");
498 assert!(stats.is_some());
499 assert_eq!(stats.unwrap().count, 1);
500 }
501
502 #[test]
503 fn test_custom_category() {
504 #[derive(Debug)]
505 struct TestCategory;
506
507 impl Category for TestCategory {
508 fn get_name(&self) -> &str {
509 "Test"
510 }
511 fn get_description(&self) -> &str {
512 "Test category"
513 }
514 }
515
516 #[derive(Debug)]
517 struct TestOp;
518
519 impl Operation for TestOp {
520 fn get_category(&self) -> &dyn Category {
521 &TestCategory
522 }
523 }
524
525 ProfileCollector::clear_all();
526
527 let op = TestOp;
528 profile!(op, {
529 std::thread::sleep(std::time::Duration::from_millis(1));
530 });
531
532 assert!(ProfileCollector::total_operations() > 0);
534 }
535
536 #[tokio::test]
537 #[cfg(feature = "full")]
538 async fn test_async_profiling() {
539 #[derive(Debug)]
540 enum TestOperation {
541 AsyncTest,
542 }
543
544 impl Operation for TestOperation {
545 fn to_str(&self) -> String {
546 "async_test".to_string()
547 }
548 }
549
550 ProfileCollector::clear_all();
551
552 let op = TestOperation::AsyncTest;
553 let result = profile_async!(op, async {
554 tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
555 "async_result"
556 })
557 .await;
558
559 assert_eq!(result, "async_result");
560 assert!(ProfileCollector::has_data());
561 }
562
563 #[test]
564 #[cfg(feature = "full")]
565 fn test_profile_macro() {
566 #[derive(Debug)]
567 enum TestOperation {
568 MacroTest,
569 }
570
571 impl Operation for TestOperation {
572 fn to_str(&self) -> String {
573 "macro_test".to_string()
574 }
575 }
576
577 ProfileCollector::clear_all();
578
579 let op = TestOperation::MacroTest;
580 let result = profile!(op, {
581 std::thread::sleep(std::time::Duration::from_millis(1));
582 100
583 });
584
585 assert_eq!(result, 100);
586 assert!(ProfileCollector::get_stats("NoCategory::macro_test").is_some());
587 }
588
589 #[test]
590 #[cfg(feature = "full")]
591 fn test_scoped_timer() {
592 ProfileCollector::clear_all();
593
594 {
595 #[derive(Debug)]
596 enum TestOperation {
597 ScopedTest,
598 }
599
600 impl Operation for TestOperation {
601 fn to_str(&self) -> String {
602 "scoped_test".to_string()
603 }
604 }
605
606 let op = TestOperation::ScopedTest;
607 scoped_timer!(op);
608 std::thread::sleep(std::time::Duration::from_millis(1));
609 }
610
611 assert!(ProfileCollector::has_data());
612 assert!(ProfileCollector::get_stats("NoCategory::scoped_test").is_some());
613 }
614}