thag_profiler 1.1.0

A lightweight, cross-platform Rust code profiling toolkit with zero overhead when disabled
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
#![warn(missing_docs)]
#![allow(unused_variables)]
//! # `thag_profiler`
//!
//! A performance profiling library for Rust applications.
//!
//! ## Feature flags
//!
#![cfg_attr(
    feature = "document-features",
    cfg_attr(doc, doc = ::document_features::document_features!())
)]
//!
//! ## Examples
//!
//! ```toml
//! # Time profiling only (default)
//! thag_profiler = { version = "1" }
//!
//! # Full profiling with memory tracking
//! thag_profiler = { version = "1", features = ["full_profiling"] }
//! ```
//!
//! ## Basic Usage
//!
//! ```rust
//! // Enable profiling for the program.
//! // To disable it while keeping the instrumentation, you can either
//! // disable the profiling features in the `thag_profiler` dependency
//! // or simply specify `#[enable_profiling(no)]`.
//! use thag_profiler::{enable_profiling, profiled};
//! #[enable_profiling]
//! fn main() {
//!     // Function code...
//!     println!("some_calculation()={}", some_calculation());
//! }
//!
//! // Instrument a function
//! #[profiled]
//! fn some_calculation() -> u64 {
//!     // Function code...
//!     42
//! }
//! ```
//!
//! ## Documentation Levels
//!
//! This crate provides two levels of documentation:
//!
//! - **Public API** (default): Clean, user-focused documentation
//! - **Internal API** (with `internal_docs` feature): Includes implementation details
//!
//! Enable the `internal_docs` feature when building documentation to see internal
//! utilities, debug functions, and implementation details that are normally hidden.
//!
//! For details including attribute arguments see the main `thag_profiler` README.md.

/// Specialized errors for the `thag_profiler` crate.
mod errors;

/// Logging module for the `thag_profiler` crate.
mod logging;

/// Core profiling module for profile definition and handling.
pub mod profiling;

/// Memory tracking module for use with the `full_profiling` feature.
#[cfg_attr(not(feature = "internal_docs"), doc(hidden))]
#[cfg(feature = "full_profiling")]
pub mod mem_tracking;

/// Memory attribution module manages a profile registry for use with the `full_profiling` feature.
#[cfg_attr(not(feature = "internal_docs"), doc(hidden))]
#[cfg(feature = "full_profiling")]
pub mod mem_attribution;

/// Demo visualization utilities for analyzing profiling data.
#[cfg(feature = "demo")]
pub mod visualization;

use std::{fmt::Display, fs, path::Path};

#[cfg(feature = "time_profiling")]
use std::sync::OnceLock;

// ============================================================================
// Public API Re-exports
// ============================================================================

/// Core profiling functionality and configuration.
pub use {
    errors::{ProfileError, ProfileResult},
    profiling::{
        Profile, ProfileConfiguration, ProfileType, disable_profiling, extract_filename_timestamp,
        is_profiling_enabled,
    },
    thag_common::{
        V, init_verbosity, lazy_static_var, re, set_global_verbosity, set_verbosity, static_lazy,
        warn_once,
    },
    thag_proc_macros::{fn_name, internal_doc},
};

/// Advanced profiling configuration and utilities.
///
/// These are typically used by advanced users or the profiling macros themselves.
#[internal_doc]
pub use profiling::{
    clear_profile_config_cache, get_config_profile_type, get_global_profile_type,
    is_detailed_memory, parse_env_profile_config, strip_hex_suffix_slice,
};

/// Debug logging utilities.
///
/// These are primarily for internal debugging and development.
#[internal_doc]
pub use logging::{DebugLogger, flush_debug_log, get_debug_log_path};

/// Private module for internal macro implementations.
///
/// This module contains re-exports of procedural macros that are used internally
/// by the public macros in this crate. Users should not directly use items from
/// this module as they are implementation details and may change without notice.
#[internal_doc]
pub mod __private {
    pub use thag_proc_macros::safe_alloc_private;
}

/// Memory profiling functionality (available with `full_profiling` feature).
#[cfg(feature = "full_profiling")]
pub use {
    mem_attribution::{ProfileRef, find_profile, register_profile},
    mem_tracking::{
        Allocator, Dispatcher, TaskGuard, TaskMemoryContext, TrackingAllocator, create_memory_task,
        current_allocator, get_last_active_task, record_allocation,
    },
};

/// Advanced memory profiling utilities.
#[cfg(feature = "full_profiling")]
#[internal_doc]
pub use profiling::extract_path;

/// Demo utilities.
#[cfg(feature = "demo")]
pub use {
    thag_proc_macros::timing,
    visualization::{AnalysisType, prompted_analysis},
};

// ============================================================================
// Internal Utilities (hidden from public API docs unless internal_docs feature is enabled)
// ============================================================================

// Export individual TLS/global variants for advanced usage
#[cfg(feature = "full_profiling")]
pub use mem_tracking::{
    compare_exchange_using_system, get_using_system, reset_allocator_state, set_using_system,
};

pub use thag_proc_macros::{enable_profiling, end, profile, profiled};

#[cfg(feature = "time_profiling")]
pub use profiling::PROFILING_MUTEX;

// Removed use of function-based enable_profiling as it's being deprecated
// in favor of the attribute macro #[enable_profiling]

/// Static variable to manage base location for profile backtrace cutoff (typically `lib.rs::init_profiling`)
#[cfg(feature = "time_profiling")]
static PROFILER: OnceLock<Profiler> = OnceLock::new();

/// Struct to manage base location for profile backtrace cutoff (typically `lib.rs::init_profiling`)
#[cfg(feature = "time_profiling")]
#[derive(Debug)]
struct Profiler {
    base_location: &'static str,
}

#[cfg(feature = "time_profiling")]
impl Profiler {
    const fn new(base_location: &'static str) -> Self {
        Self { base_location }
    }
}

/// Retrieve preset base location for profile backtrace cutoff (typically `lib.rs::init_profiling`)
#[cfg(feature = "time_profiling")]
/// Get the base location set during profiling initialization.
///
/// This is primarily used internally by the profiling macros.
#[internal_doc]
pub fn get_base_location() -> Option<&'static str> {
    PROFILER.get().map(|profiler| profiler.base_location)
}

#[cfg(feature = "time_profiling")]
static PROFILEE: OnceLock<Profilee> = OnceLock::new();

#[cfg(feature = "time_profiling")]
#[derive(Debug)]
struct Profilee {
    root_module: &'static str,
}

#[cfg(feature = "time_profiling")]
impl Profilee {
    const fn new(root_module: &'static str) -> Self {
        Self { root_module }
    }
}

// /// Retrieve profiled root module holder
// #[cfg(feature = "time_profiling")]
// pub fn get_profilee() -> Option<&'static Profilee> {
//     PROFILEE.get()
// }

/// Retrieve profiled root module
#[cfg(feature = "time_profiling")]
/// Get the root module name set during profiling initialization.
///
/// This is primarily used internally by the profiling macros.
#[internal_doc]
pub fn get_root_module() -> Option<&'static str> {
    PROFILEE.get().map(|profilee| profilee.root_module)
}

/// Extract the file stem from a path string.
///
/// # Panics
///
/// Panics if the input path does not contain at least a slash and a dot.
#[internal_doc]
#[must_use]
#[cfg(not(target_os = "windows"))]
pub fn file_stem_from_path_str(file_name: &str) -> String {
    let fname_start = file_name.rfind('/').map_or_else(|| 0, |pos| pos + 1);
    let fname_dot = file_name.rfind('.').unwrap_or(file_name.len());
    file_name[fname_start..fname_dot].to_string()
}

/// Extract the file stem from a path string (Windows version).
///
/// # Panics
///
/// Panics if the input path does not contain at least a backslash and a dot.
#[internal_doc]
#[must_use]
#[cfg(target_os = "windows")]
pub fn file_stem_from_path_str(file_name: &str) -> String {
    // On Windows, paths use backslashes and may be escaped (double backslashes)
    // Look for both single and double backslashes
    let fname_start = file_name
        .rfind("\\\\")
        .map(|pos| pos + 2)
        .or_else(|| file_name.rfind('\\').map(|pos| pos + 1))
        .unwrap_or(0);
    let fname_dot = file_name.rfind('.').unwrap_or_else(|| file_name.len());
    file_name[fname_start..fname_dot].to_string()
}

/// Extract the file stem from a Path.
///
/// # Panics
///
/// Panics if `Path::file_stem()`    does not return a valid file stem.
#[internal_doc]
#[cfg(not(feature = "full_profiling"))]
#[must_use]
pub fn file_stem_from_path(path: &Path) -> String {
    path.file_stem().unwrap().to_string_lossy().to_string()
}

/// Extract the file stem from a Path.
///
/// # Panics
///
/// Panics if `Path::file_stem()`    does not return a valid file stem.
#[internal_doc]
#[cfg(feature = "full_profiling")]
#[must_use]
pub fn file_stem_from_path(path: &Path) -> String {
    safe_alloc!(path.file_stem().unwrap().to_string_lossy().to_string())
}

/// Rewrite the svg file with the `Search` link rendered more visible.
///
/// # Errors
///
/// This function will bubble up any i/o error encountered reading or writing the file.
pub fn enhance_svg_accessibility(svg_path: &str) -> ProfileResult<()> {
    let content = fs::read_to_string(svg_path)?;

    // Make the inactive search link more visible
    let enhanced = content.replace(
        "opacity:0.1",
        "opacity:0.5", // Darker grey for better visibility
    );

    fs::write(svg_path, enhanced)?;
    Ok(())
}

/// Constant reflecting whether any profiling feature is enabled.
#[cfg(feature = "time_profiling")]
pub const PROFILING_FEATURE_ENABLED: bool = true;

/// Constant reflecting whether any profiling feature is enabled.
#[cfg(not(feature = "time_profiling"))]
pub const PROFILING_FEATURE_ENABLED: bool = false;

/// Safe allocation macro that passes expressions through when full profiling is disabled.
///
/// When the `full_profiling` feature is not enabled, this macro simply evaluates
/// the provided expression without any memory tracking overhead.
///
/// # Examples
///
/// ```rust
/// use thag_profiler::safe_alloc;
/// let result = safe_alloc!(vec![1, 2, 3]);
/// ```
#[cfg(feature = "full_profiling")]
#[macro_export]
macro_rules! safe_alloc {
    ($($tt:tt)*) => {
        {
            use $crate::mem_tracking::{compare_exchange_using_system, set_using_system};
            $crate::__private::safe_alloc_private!($($tt)*)
        }
    };
}

/// Safe allocation macro that passes expressions through when full profiling is disabled.
///
/// When the `full_profiling` feature is not enabled, this macro simply evaluates
/// the provided expression without any memory tracking overhead.
///
/// # Examples
///
/// ```rust
/// use thag_profiler::safe_alloc;
/// let result = safe_alloc!(vec![1, 2, 3]);
/// ```
#[cfg(not(feature = "full_profiling"))]
#[macro_export]
macro_rules! safe_alloc {
    ($($tt:tt)*) => {
        { $($tt)* }
    };
}

/// Formats a given positive integer with thousands separators (commas).
///
/// This function takes any unsigned integer type (`u8`, `u16`, `u32`, `u64`, `u128`, `usize`)
/// and returns a `String` representation where groups of three digits are separated by commas.
///
/// # Examples
///
/// ```
/// use thag_profiler::thousands;
/// assert_eq!(thousands(1234567u32), "1,234,567");
/// assert_eq!(thousands(9876u16), "9,876");
/// assert_eq!(thousands(42u8), "42");
/// assert_eq!(thousands(12345678901234567890u128), "12,345,678,901,234,567,890");
/// ```
///
/// # Panics
///
/// This function panics if `std::str::from_utf8()` fails,
/// which is highly unlikely since the input is always a valid ASCII digit string.
///
/// # Complexity
///
/// Runs in **O(d)** time complexity, where `d` is the number of digits in the input number.
///
/// # Note
///
/// If you need to format signed integers, you'll need a modified version
/// that correctly handles negative numbers.
/// Format a number with thousands separators.
///
/// This utility function formats numbers with comma separators for better readability
/// in profiling output.
#[internal_doc]
pub fn thousands<T: Display>(n: T) -> String {
    n.to_string()
        .as_bytes()
        .rchunks(3)
        .rev()
        .map(std::str::from_utf8)
        .collect::<Result<Vec<&str>, _>>()
        .unwrap()
        .join(",")
}

/// Initialize the profiling system.
/// Called by the `#[enable_profiling]` attribute macro to set up profiling.
///
/// # Panics
///
/// This function panics if profiling cannot be enabled.
#[internal_doc]
#[fn_name]
pub fn init_profiling(root_module: &'static str, profile_type: Option<ProfileType>) {
    #[cfg(feature = "full_profiling")]
    safe_alloc! {
        // eprintln!("root_module={root_module}, profile_type={profile_type:#?});

        // Only set PROFILEE if it hasn't been set already
        // This allows multiple test functions to call init_profiling
        if PROFILEE.get().is_none() {
            PROFILEE.set(Profilee::new(root_module)).unwrap();
        } else if PROFILEE.get().unwrap().root_module != root_module {
            // If already set but with a different root_module, just log it and continue
            eprintln!(
                "Warning: PROFILEE already set with root_module={}, not changing to {root_module}",
                PROFILEE.get().unwrap().root_module,
            );
        }

        set_base_location(file!(), fn_name, line!());

        profiling::enable_profiling(true, profile_type)
            .expect("Failed to enable profiling");

        let global_profile_type = get_global_profile_type();

        // eprintln!("In init_profiling with global_profile_type={global_profile_type:?}");

        if global_profile_type == ProfileType::Time {
            // eprintln!(
            //     "Skipping memory profiling initialization because global_profile_type={global_profile_type:?}"
            // );
        } else {
            // eprintln!("Initializing memory profiling");
            mem_tracking::initialize_memory_profiling();
        }
    };
    // eprintln!("Exiting init_profiling");

    #[cfg(all(feature = "time_profiling", not(feature = "full_profiling")))]
    {
        // Only set PROFILEE if it hasn't been set already
        // This allows multiple test functions to call init_profiling
        if PROFILEE.get().is_none() {
            PROFILEE.set(Profilee::new(root_module)).unwrap();
        } else if PROFILEE.get().unwrap().root_module != root_module {
            // If already set but with a different root_module, just log it and continue
            eprintln!(
                "Warning: PROFILEE already set with root_module={}, not changing to {}",
                PROFILEE.get().unwrap().root_module,
                root_module
            );
        }

        set_base_location(file!(), fn_name, line!());
        profiling::enable_profiling(true, profile_type).expect("Failed to enable profiling");
        eprintln!("Exiting init_profiling");
    }

    // Register finalize_profiling as an atexit handler so it runs even when
    // a GUI framework (e.g. eframe/winit on macOS) terminates the process via
    // std::process::exit() rather than returning from the event-loop function.
    // finalize_profiling itself is idempotent (PROFILING_FINALIZED flag) so
    // the explicit call in the #[enable_profiling] generated code is harmless.
    #[cfg(feature = "time_profiling")]
    {
        unsafe extern "C" fn finalize_atexit() {
            crate::finalize_profiling();
        }
        unsafe extern "C" {
            fn atexit(func: unsafe extern "C" fn()) -> i32;
        }
        unsafe {
            atexit(finalize_atexit);
        }
    }

    #[cfg(not(feature = "time_profiling"))]
    {}
}

#[cfg(feature = "time_profiling")]
fn set_base_location(file_name: &'static str, fn_name: &str, _line_no: u32) {
    let base_loc = format!("{file_name}::{fn_name}");

    // Only set PROFILER if it hasn't been set already
    // This allows multiple test functions to call set_base_location
    if PROFILER.get().is_none() {
        let base_location = Box::leak(base_loc.into_boxed_str());
        PROFILER.set(Profiler::new(base_location)).unwrap();
        // eprintln!("Base location set to {base_location}");
    } else if PROFILER.get().unwrap().base_location != base_loc {
        // If already set but with a different base_location, just log it and continue
        eprintln!(
            "Warning: PROFILER already set with base_location={}, not changing to {}",
            PROFILER.get().unwrap().base_location,
            base_loc
        );
    }
    // eprintln!("base_location={base_location}");
}

/// Finalize profiling and write out data files.
/// Called by the `#[enable_profiling]` attribute macro at program termination.
///
/// # Panics
///
/// This function panics if profiling cannot be disabled.
#[internal_doc]
#[allow(clippy::missing_const_for_fn)]
pub fn finalize_profiling() {
    // Idempotency guard: prevent double-processing when both the explicit call
    // (from #[enable_profiling] generated code) and the atexit handler fire.
    #[cfg(feature = "time_profiling")]
    if profiling::PROFILING_FINALIZED.swap(true, std::sync::atomic::Ordering::SeqCst) {
        return;
    }

    #[cfg(all(feature = "time_profiling", not(feature = "full_profiling")))]
    {
        // Ensure debug log is flushed before we disable profiling

        flush_debug_log();

        // Disable profiling
        disable_profiling();

        let global_profile_type = get_global_profile_type();

        // Process any recorded profiles
        if matches!(global_profile_type, ProfileType::Time | ProfileType::Both) {
            if let Err(e) = profiling::process_time_profile() {
                debug_log!("Error converting time profile to exclusive time: {e:?}");
            }
        }

        // Final flush to ensure all data is written
        flush_debug_log();

        // Add a delay to ensure flush completes before program exit
        std::thread::sleep(std::time::Duration::from_millis(10));
    }

    #[cfg(feature = "full_profiling")]
    safe_alloc! {
        // Ensure debug log is flushed before we disable profiling
        // flush_debug_log();

        // Disable profiling
        disable_profiling();

        let global_profile_type = get_global_profile_type();

        // Process any recorded profiles
        if matches!(global_profile_type, ProfileType::Time | ProfileType::Both) {
            // Convert inclusive time profile to exclusive time if enabled
            if let Err(e) = profiling::process_time_profile() {
                debug_log!("Error converting time profile to exclusive time: {e:?}");
            }
        }

        if matches!(global_profile_type, ProfileType::Memory | ProfileType::Both) {
            mem_tracking::finalize_memory_profiling();
        }

        // Final flush to ensure all data is written
        // flush_debug_log();

        // Add a delay to ensure flush completes before program exit
        std::thread::sleep(std::time::Duration::from_millis(10));
    };

    #[cfg(not(feature = "time_profiling"))]
    {}
}

#[cfg(test)]
mod feature_tests {
    use crate::profiling::is_profiling_enabled;

    #[test]
    fn test_profiling_feature_flag_behavior() {
        // This test verifies the behavior of profiling features

        #[cfg(feature = "time_profiling")]
        {
            // When compiled with the "time_profiling" feature but profiling is disabled at runtime,
            // is_profiling_enabled() should return false in test mode due to our special handling
            assert!(
                !is_profiling_enabled(),
                "With profiling feature enabled but disabled at runtime, is_profiling_enabled() should return false in test mode"
            );

            // We can enable profiling and it should work
            // Force set the state directly rather than using enable_profiling which might have side effects
            crate::profiling::force_set_profiling_state(true);
            assert!(
                is_profiling_enabled(),
                "After enabling profiling, is_profiling_enabled() should return true"
            );

            // Clean up
            crate::profiling::force_set_profiling_state(false);
        }

        #[cfg(not(feature = "time_profiling"))]
        {
            // When compiled without the "time_profiling" feature, is_profiling_enabled() should always return false
            assert!(
                !is_profiling_enabled(),
                "Without profiling feature, is_profiling_enabled() should always return false"
            );
        }
    }
}

#[cfg(test)]
mod config_tests {
    #[cfg(feature = "time_profiling")]
    use super::*;
    #[cfg(feature = "time_profiling")]
    use std::env;

    /// Tests that resetting profile config picks up environment variable changes
    /// This test is isolated to avoid interfering with other tests
    #[test]
    #[cfg(feature = "time_profiling")]
    fn test_profile_config_picks_up_env_changes() {
        // Wait for exclusive use
        let _guard = PROFILING_MUTEX.lock();

        // Save original env var if it exists
        let original = env::var("THAG_PROFILER").ok();

        let orig_global_profile_type = profiling::get_global_profile_type();
        eprintln!("orig_global_profile_type={orig_global_profile_type:?}");

        // First set to "time"
        unsafe { env::set_var("THAG_PROFILER", "time,.,none,false") };

        // Clear the cache to force reloading from environment
        profiling::clear_profile_config_cache();

        // Check that it's set to Time
        assert_eq!(
            profiling::get_config_profile_type(),
            profiling::ProfileType::Time
        );
        assert_eq!(get_config_profile_type(), profiling::ProfileType::Time);

        // Now change to "both"
        unsafe { env::set_var("THAG_PROFILER", "both,.,none,false") };

        // Clear the cache again to force reloading
        profiling::clear_profile_config_cache();

        // Check that it picked up the change
        assert_eq!(get_config_profile_type(), profiling::ProfileType::Both);

        // Restore original env var or remove it
        if let Some(val) = original {
            unsafe { env::set_var("THAG_PROFILER", val) };
        } else {
            unsafe { env::remove_var("THAG_PROFILER") };
        }

        // Clear the cache once more to restore state
        profiling::clear_profile_config_cache();
    }
}

/// Public API test module
/// This test module for `lib.rs` provides comprehensive coverage of the public API:
///
/// ### Key Areas Tested:
///
/// 1. **Utility Functions**:
///    - `file_stem_from_path_str`
///    - `thousands` formatter
///
/// 2. **Macros**:
///    - `lazy_static_var!` for different types
///    - `re!` for pattern matching
///    - `static_lazy!` for static initialization
///
/// 3. **Profiling API**:
///    - `Profiler` and `Profilee` types and their accessors
///    - Profiling feature constants and runtime states
///    - Initialization and finalization
///
/// 4. **Error Handling**:
///    - `ProfileError` variants
///    - `ProfileResult` usage
///
/// 5. **Profile Type Enum**:
///    - Variants, display, and parsing
///
/// 6. **Memory Tracking Integration**:
///    - Basic allocator operations
///    - Task creation and management
///
/// ### Design Considerations:
///
/// 1. **Feature Compatibility**: Tests are conditionally compiled based on the same feature flags as the code they're testing
/// 2. **Independence**: Each test function tests a specific aspect without relying on global state
/// 3. **Thoroughness**: All public APIs exposed by `lib.rs` are covered
/// 4. **Safety**: The tests avoid modifying global state in ways that could affect other tests
///
/// Since this module tests the highest-level API of the library, it serves as an excellent integration test, ensuring that all the individual components work together correctly.
#[cfg(test)]
mod lib_tests {
    use super::*;

    #[test]
    fn test_file_stem_functions() {
        // Test file_stem_from_path_str with Unix paths
        #[cfg(not(target_os = "windows"))]
        {
            let file_name = "path/to/my_file.rs";
            assert_eq!(file_stem_from_path_str(file_name), "my_file");
        }

        // Test file_stem_from_path_str with Windows paths
        #[cfg(target_os = "windows")]
        {
            // Test with double backslashes (escaped)
            let win_file = "C:\\\\Users\\\\donforbes\\\\Documents\\\\GitHub\\\\thag_rs\\\\demo\\\\document_pipeline_profile_minimal.rs";
            assert_eq!(
                file_stem_from_path_str(win_file),
                "document_pipeline_profile_minimal"
            );

            // Test with single backslashes
            let win_file_single = "C:\\Users\\Documents\\test_file.rs";
            assert_eq!(file_stem_from_path_str(win_file_single), "test_file");

            // Test with just filename (no directory)
            let simple_file = "simple.rs";
            assert_eq!(file_stem_from_path_str(simple_file), "simple");
        }

        // // Test file_stem_from_path (should work on all platforms)
        // let path = std::path::Path::new("path/to/another_file.rs");
        // assert_eq!(file_stem_from_path(path), "another_file");

        // Test with just filename (no directory) - cross-platform
        let simple_file = "simple.rs";
        assert_eq!(file_stem_from_path_str(simple_file), "simple");

        // Test with file having multiple extensions - cross-platform
        #[cfg(not(target_os = "windows"))]
        {
            let multi_ext = "test.data.rs";
            assert_eq!(file_stem_from_path_str(multi_ext), "test.data");
        }

        #[cfg(target_os = "windows")]
        {
            let multi_ext = "C:\\\\temp\\\\test.data.rs";
            assert_eq!(file_stem_from_path_str(multi_ext), "test.data");
        }

        // Test with just stem (no directory or extension) - cross-platform
        let bare_stem = "bare";
        assert_eq!(file_stem_from_path_str(bare_stem), "bare");
    }

    #[test]
    fn test_thousands_formatter() {
        // Test with various integer sizes
        assert_eq!(thousands(0), "0");
        assert_eq!(thousands(42), "42");
        assert_eq!(thousands(1000), "1,000");
        assert_eq!(thousands(1234), "1,234");
        assert_eq!(thousands(1_234_567), "1,234,567");
        assert_eq!(thousands(1_234_567_890u32), "1,234,567,890");
        assert_eq!(thousands(123_456_789_012_345u64), "123,456,789,012,345");

        // Test with small numbers
        assert_eq!(thousands(1), "1");
        assert_eq!(thousands(12), "12");
        assert_eq!(thousands(123), "123");

        // Test with string representations
        assert_eq!(thousands("1234567"), "1,234,567");
    }

    #[test]
    fn test_lazy_static_var_macro() {
        // Test with reference type
        let vec_ref = lazy_static_var!(Vec<i32>, vec![1, 2, 3, 4]);
        assert_eq!(vec_ref.len(), 4);
        assert_eq!(vec_ref[0], 1);

        // Test with dereferenced type
        let bool_val = lazy_static_var!(bool, deref, true);
        assert!(bool_val);

        // Test with complex type
        let map_ref = lazy_static_var!(std::collections::HashMap<&str, i32>, {
            let mut map = std::collections::HashMap::new();
            map.insert("one", 1);
            map.insert("two", 2);
            map
        });
        assert_eq!(map_ref.len(), 2);
        assert_eq!(map_ref.get("one"), Some(&1));
    }

    #[test]
    fn test_regex_macro() {
        let re = re!(r"\d+");
        assert!(re.is_match("123"));
        assert!(!re.is_match("abc"));

        // Test capturing
        let cap_re = re!(r"(\w+):(\d+)");
        let caps = cap_re.captures("name:42").unwrap();
        assert_eq!(caps.get(1).unwrap().as_str(), "name");
        assert_eq!(caps.get(2).unwrap().as_str(), "42");

        // Test special characters
        let special_re = re!(r"\s+");
        assert!(special_re.is_match(" \t\n"));
        assert!(!special_re.is_match("abc"));
    }

    #[test]
    fn test_static_lazy_macro() {
        // Define a static lazy instance for testing
        static_lazy! {
            TestLazy: Vec<i32> = vec![1, 2, 3]
        }

        // Test access
        let test_lazy = TestLazy::get();
        assert_eq!(test_lazy.len(), 3);
        assert_eq!(test_lazy[0], 1);

        // Test optional version
        static_lazy! {
            TestOptional: Option<String> = Some("test".to_string())
        }

        let test_opt = TestOptional::get();
        assert!(test_opt.is_some());
        assert_eq!(test_opt.unwrap(), "test");

        // Initialize explicitly (just for coverage)
        TestLazy::init();
        TestOptional::init();
    }

    #[cfg(feature = "time_profiling")]
    #[test]
    fn test_profiler_and_profilee() {
        // Create a temporary instance to test the API
        let test_module = "test_module";
        let leaked_module = Box::leak(test_module.to_string().into_boxed_str());

        // Create a profilee instance
        let profilee = Profilee::new(leaked_module);
        assert_eq!(profilee.root_module, leaked_module);

        // Test base location setting (creates a profiler)
        set_base_location(file!(), "test_function", line!());

        // Test base location getter
        let location = get_base_location();
        eprintln!("location={location:#?}");
        assert!(location.is_some());
        let loc_str = location.unwrap();
        assert!(loc_str.contains(file!()));
        // assert!(loc_str.contains("test_function"));

        // Manual setting of PROFILEE for testing
        if PROFILEE.get().is_none() {
            let _ = PROFILEE.set(profilee);
        }

        // Test root module getter
        let root = get_root_module();
        assert!(root.is_some());
        assert_eq!(root.unwrap(), leaked_module);
    }

    // Test initialization and finalization with mocked objects
    #[cfg(feature = "time_profiling")]
    #[test]
    fn test_init_and_finalize() {
        let _guard = PROFILING_MUTEX.lock();
        // Setup: Make sure profiling is disabled
        disable_profiling();

        // Create a profile configuration for testing
        // let config = ProfileConfiguration {
        //     enabled: true,
        //     profile_type: Some(ProfileType::Time),
        //     output_dir: Some(std::path::PathBuf::from(".")),
        //     debug_level: Some(profiling::DebugLevel::None),
        //     detailed_memory: false,
        // };
        let config = ProfileConfiguration::try_from(vec!["time", "", "none"].as_slice()).unwrap();

        // Initialize profiling
        init_profiling("test_module", config.profile_type());

        // Verify profiling is enabled
        assert!(is_profiling_enabled());

        // Finalize profiling
        finalize_profiling();

        // Verify profiling is disabled
        assert!(!is_profiling_enabled());
    }

    // Test public API error types
    #[test]
    #[allow(clippy::unnecessary_literal_unwrap)]
    fn test_error_types() {
        // Test ProfileError creation and conversion
        let error = ProfileError::General("test error".to_string());
        let error_string = error.to_string();
        assert!(error_string.contains("test error"));

        // Test io error conversion
        let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
        let profile_error = ProfileError::from(io_error);
        assert!(matches!(profile_error, ProfileError::Io(_)));

        // Test ProfileResult usage
        let result: ProfileResult<i32> = Ok(42);
        assert_eq!(result.unwrap(), 42);

        let err_result: ProfileResult<()> = Err(ProfileError::General("test".into()));
        assert!(err_result.is_err());
    }

    // Test profile type enum
    #[test]
    fn test_profile_type_enum() {
        // Test variants exist and can be created
        let time = ProfileType::Time;
        let memory = ProfileType::Memory;
        let both = ProfileType::Both;
        let none = ProfileType::None;

        // Test Display implementation
        assert_eq!(time.to_string(), "time");
        assert_eq!(memory.to_string(), "memory");
        assert_eq!(both.to_string(), "both");
        assert_eq!(none.to_string(), "none");

        // Test FromStr implementation
        assert_eq!("time".parse::<ProfileType>().unwrap(), ProfileType::Time);
        assert_eq!(
            "memory".parse::<ProfileType>().unwrap(),
            ProfileType::Memory
        );
        assert_eq!("both".parse::<ProfileType>().unwrap(), ProfileType::Both);

        // Test error case
        let invalid = "invalid".parse::<ProfileType>();
        assert!(invalid.is_err());
    }

    #[cfg(feature = "full_profiling")]
    #[test]
    fn test_mem_tracking_integration() {
        // Test basic allocator operations

        // use crate::safe_alloc;
        let current = mem_tracking::current_allocator();
        assert!(matches!(current, Allocator::Tracking) || matches!(current, Allocator::System));

        // Test with_sys_alloc function
        let result = safe_alloc!(42);
        assert_eq!(result, 42);

        // Test creating a memory task
        let task = create_memory_task();
        assert!(task.id() > 0);

        // Test TaskGuard creation
        let guard = TaskGuard::new(task.id());
        drop(guard); // Explicit drop
    }
}