1use core::marker::PhantomData;
16
17#[cfg(all(
33 feature = "probe",
34 not(target_family = "wasm"),
35 not(feature = "web_lift")
36))]
37mod imp {
38 use std::cell::RefCell;
39 use std::time::Instant;
40
41 thread_local! {
42 static EVENTS: RefCell<Vec<super::Event>> = const { RefCell::new(Vec::new()) };
43 }
44
45 pub struct Span {
47 pub(crate) name: &'static str,
48 pub(crate) start: Instant,
49 }
50
51 impl Drop for Span {
52 fn drop(&mut self) {
53 let dur_ns = self.start.elapsed().as_nanos() as u64;
54 let _ = EVENTS.try_with(|cell| {
59 cell.borrow_mut().push(super::Event {
60 name: self.name,
61 kind: super::EventKind::Span { dur_ns },
62 });
63 });
64 }
65 }
66
67 pub(super) fn open(name: &'static str) -> Span {
68 Span { name, start: Instant::now() }
69 }
70
71 pub(super) fn sample_rss(label: &'static str, bytes: u64) {
72 let _ = EVENTS.try_with(|cell| {
74 cell.borrow_mut().push(super::Event {
75 name: label,
76 kind: super::EventKind::Rss { bytes },
77 });
78 });
79 }
80
81 pub(super) fn drain() -> Vec<super::Event> {
82 EVENTS
83 .try_with(|cell| core::mem::take(&mut *cell.borrow_mut()))
84 .unwrap_or_default()
85 }
86
87 pub(super) fn drop_events() {
88 let _ = EVENTS.try_with(|cell| cell.borrow_mut().clear());
89 }
90
91 pub(super) fn peek_len() -> usize {
92 EVENTS.try_with(|cell| cell.borrow().len()).unwrap_or(0)
93 }
94
95 pub(super) fn enabled() -> bool {
96 true
97 }
98}
99
100#[cfg(any(
101 not(feature = "probe"),
102 target_family = "wasm",
103 feature = "web_lift"
104))]
105mod imp {
106 #[derive(Debug)]
107 pub struct Span;
108
109 impl Drop for Span {
110 #[inline]
111 fn drop(&mut self) {}
112 }
113
114 #[inline]
115 pub(super) const fn open(_name: &'static str) -> Span {
116 Span
117 }
118
119 #[inline]
120 pub(super) const fn sample_rss(_label: &'static str, _bytes: u64) {}
121
122 #[inline]
123 pub(super) const fn drain() -> Vec<super::Event> {
124 Vec::new()
125 }
126
127 #[inline]
128 pub(super) const fn drop_events() {}
129
130 #[inline]
131 pub(super) const fn peek_len() -> usize { 0 }
132
133 #[inline]
134 pub(super) const fn enabled() -> bool {
135 false
136 }
137}
138
139#[derive(Copy, Debug, Clone)]
142pub struct Event {
143 pub name: &'static str,
144 pub kind: EventKind,
145}
146
147#[derive(Copy, Debug, Clone)]
148pub enum EventKind {
149 Span { dur_ns: u64 },
151 Rss { bytes: u64 },
153}
154
155pub use imp::Span;
157
158#[derive(Copy, Clone, Debug)]
160pub struct Probe {
161 _no_construct: PhantomData<()>,
162}
163
164impl Probe {
165 #[inline]
168 #[allow(clippy::missing_const_for_fn)]
170 #[must_use] pub fn span(name: &'static str) -> Span {
171 imp::open(name)
172 }
173
174 #[inline]
179 #[allow(clippy::missing_const_for_fn)]
181 pub fn sample_rss(label: &'static str, bytes: u64) {
182 imp::sample_rss(label, bytes);
183 }
184
185 #[inline]
187 #[allow(clippy::missing_const_for_fn)]
189 #[must_use] pub fn drain() -> Vec<Event> {
190 imp::drain()
191 }
192
193 #[inline]
198 #[allow(clippy::missing_const_for_fn)]
200 pub fn drop_events() {
201 imp::drop_events();
202 }
203
204 #[inline]
206 #[allow(clippy::missing_const_for_fn)]
208 #[must_use] pub fn peek_len() -> usize {
209 imp::peek_len()
210 }
211
212 #[inline]
214 #[allow(clippy::missing_const_for_fn)]
216 #[must_use] pub fn enabled() -> bool {
217 imp::enabled()
218 }
219}
220
221#[inline]
225#[allow(clippy::cast_possible_truncation)] pub fn monotonic_now_nanos() -> u64 {
227 use std::sync::OnceLock;
228 use std::time::Instant;
229 static LAUNCH: OnceLock<Instant> = OnceLock::new();
230 let start = LAUNCH.get_or_init(Instant::now);
231 start.elapsed().as_nanos() as u64
232}
233
234#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)] pub fn print_drained_events(label: &str, events: &[Event]) {
250 use std::collections::BTreeMap;
251
252 if events.is_empty() {
253 if Probe::enabled() {
254 eprintln!("[CPU] {label}: no events recorded this pass");
255 } else {
256 eprintln!(
259 "[CPU] {label}: probe unavailable on this target (timings = ???)"
260 );
261 }
262 return;
263 }
264
265 let mut spans: BTreeMap<&'static str, Vec<u64>> = BTreeMap::new();
266 let mut rss_marks: Vec<(&'static str, u64)> = Vec::new();
267 for ev in events {
268 match ev.kind {
269 EventKind::Span { dur_ns } => spans.entry(ev.name).or_default().push(dur_ns),
270 EventKind::Rss { bytes } => rss_marks.push((ev.name, bytes)),
271 }
272 }
273
274 let mut rows: Vec<(&'static str, usize, u64, u64, u64, u64)> = spans
275 .into_iter()
276 .map(|(name, mut ns)| {
277 ns.sort_unstable();
278 let n = ns.len();
279 let total: u128 = ns.iter().map(|&x| u128::from(x)).sum();
280 let avg = (total / n.max(1) as u128) as u64;
281 let p99 = ns[(n.saturating_sub(1) * 99) / 100];
282 let max = *ns.last().unwrap();
283 (name, n, total as u64, avg, p99, max)
284 })
285 .collect();
286 rows.sort_by(|a, b| b.2.cmp(&a.2));
287
288 eprintln!("[CPU] === {label} ({} phases) ===", rows.len());
289 eprintln!(
290 "[CPU] {:<28} {:>5} {:>10} {:>9} {:>9} {:>9}",
291 "phase", "n", "total(µs)", "avg(µs)", "p99(µs)", "max(µs)"
292 );
293 for (name, n, total, avg, p99, max) in &rows {
294 eprintln!(
295 "[CPU] {:<28} {:>5} {:>10.1} {:>9.2} {:>9.2} {:>9.2}",
296 name,
297 n,
298 (*total as f64) / 1_000.0,
299 (*avg as f64) / 1_000.0,
300 (*p99 as f64) / 1_000.0,
301 (*max as f64) / 1_000.0,
302 );
303 }
304 if !rss_marks.is_empty() {
305 eprintln!("[CPU] -- RSS checkpoints (wall-clock order) --");
306 let mut prev: Option<u64> = None;
307 for (lbl, bytes) in &rss_marks {
308 let delta = prev
309 .map(|p| {
310 let diff = i128::from(*bytes) - i128::from(p);
311 if diff >= 0 {
312 format!(" (Δ +{:.2} MiB)", diff as f64 / 1_048_576.0)
313 } else {
314 format!(" (Δ -{:.2} MiB)", -diff as f64 / 1_048_576.0)
315 }
316 })
317 .unwrap_or_default();
318 eprintln!(
319 "[CPU] {:<28} {:.2} MiB{}",
320 lbl,
321 *bytes as f64 / 1_048_576.0,
322 delta
323 );
324 prev = Some(*bytes);
325 }
326 }
327}
328
329#[inline]
338#[allow(clippy::missing_const_for_fn)]
340pub fn sample_peak_rss(label: &'static str) {
341 #[cfg(all(feature = "probe", not(feature = "web_lift")))]
345 {
346 let (current, _virt) = current_rss_bytes();
347 let bytes = if current != 0 { current } else { peak_rss_bytes_self() };
348 Probe::sample_rss(label, bytes);
349 }
350 #[cfg(any(not(feature = "probe"), feature = "web_lift"))]
351 let _ = label;
352}
353
354#[cfg(feature = "probe")]
355pub fn peak_rss_bytes_pub() -> u64 { peak_rss_bytes_self() }
356
357#[cfg(feature = "probe")]
358fn peak_rss_bytes_self() -> u64 {
359 #[cfg(unix)]
360 unsafe {
361 let mut ru: libc::rusage = core::mem::zeroed();
362 if libc::getrusage(libc::RUSAGE_SELF, &mut ru) != 0 {
363 return 0;
364 }
365 let raw = ru.ru_maxrss as u64;
366 if cfg!(target_os = "macos") { raw } else { raw.saturating_mul(1024) }
367 }
368 #[cfg(not(unix))]
369 {
370 0
371 }
372}
373
374#[inline]
386#[allow(clippy::missing_const_for_fn)]
389pub fn hint_purge_allocator() {
390 #[cfg(feature = "allocator_mimalloc")]
391 {
392 unsafe {
394 libmimalloc_sys::mi_collect(true);
395 }
396 static PURGE_TRACE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
397 if *PURGE_TRACE.get_or_init(azul_core::profile::memory_enabled) {
398 let (rss, _) = current_rss_bytes();
399 eprintln!("[PURGE] mi_collect(true) called — current rss={:.2} MiB", rss as f64 / 1048576.0);
400 }
401 return;
402 }
403 #[cfg(feature = "allocator_jemalloc")]
404 {
405 unsafe {
407 let _ = tikv_jemalloc_sys::mallctl(
408 b"arena.4096.purge\0".as_ptr() as *const _,
409 core::ptr::null_mut(),
410 core::ptr::null_mut(),
411 core::ptr::null_mut(),
412 0,
413 );
414 }
415 return;
416 }
417 #[cfg(all(target_os = "macos", not(miri), not(any(feature = "allocator_mimalloc", feature = "allocator_jemalloc"))))]
418 {
419 extern "C" {
420 fn malloc_zone_pressure_relief(zone: *mut core::ffi::c_void, goal: usize) -> usize;
421 }
422 unsafe {
423 malloc_zone_pressure_relief(core::ptr::null_mut(), 0);
424 }
425 }
426}
427
428#[cfg(feature = "probe")]
438pub fn current_rss_bytes() -> (u64, u64) {
439 #[cfg(miri)]
442 return (0, 0);
443 #[cfg(all(target_os = "macos", not(miri)))]
444 {
445 let pf = phys_footprint_bytes();
449 #[repr(C)]
450 struct MachTaskBasicInfo {
451 virtual_size: u64,
452 resident_size: u64,
453 resident_size_max: u64,
454 user_time: [u32; 2],
455 system_time: [u32; 2],
456 policy: i32,
457 suspend_count: i32,
458 }
459 const MACH_TASK_BASIC_INFO: u32 = 20;
460 extern "C" {
461 fn mach_task_self() -> u32;
462 fn task_info(
463 target: u32, flavor: u32,
464 info: *mut core::ffi::c_void, count: *mut u32,
465 ) -> i32;
466 }
467 unsafe {
468 let mut info: MachTaskBasicInfo = core::mem::zeroed();
469 let mut count = (core::mem::size_of::<MachTaskBasicInfo>() / 4) as u32;
470 let kr = task_info(
471 mach_task_self(),
472 MACH_TASK_BASIC_INFO,
473 &mut info as *mut _ as *mut core::ffi::c_void,
474 &mut count,
475 );
476 if kr == 0 {
477 let rss = if pf != 0 { pf } else { info.resident_size };
478 (rss, info.virtual_size)
479 } else {
480 (pf, 0)
481 }
482 }
483 }
484 #[cfg(not(target_os = "macos"))]
485 { (0, 0) }
486}
487
488#[cfg(feature = "probe")]
497pub fn malloc_heap_bytes() -> u64 {
498 #[cfg(target_os = "macos")]
499 {
500 #[repr(C)]
501 struct Mstats {
502 bytes_total: usize,
503 chunks_used: usize,
504 bytes_used: usize,
505 chunks_free: usize,
506 bytes_free: usize,
507 }
508 extern "C" {
509 fn mstats() -> Mstats;
510 }
511 unsafe { mstats().bytes_used as u64 }
512 }
513 #[cfg(not(target_os = "macos"))]
514 { 0 }
515}
516
517#[cfg(feature = "probe")]
529pub fn phys_footprint_bytes() -> u64 {
530 #[cfg(miri)]
532 return 0;
533 #[cfg(all(target_os = "macos", not(miri)))]
534 {
535 #[repr(C)]
539 struct TaskVmInfo {
540 virtual_size: u64,
541 region_count: u32,
542 page_size: u32,
543 resident_size: u64,
544 resident_size_peak: u64,
545 device: u64,
546 device_peak: u64,
547 internal: u64,
548 internal_peak: u64,
549 external: u64,
550 external_peak: u64,
551 reusable: u64,
552 reusable_peak: u64,
553 purgeable_volatile_pmap: u64,
554 purgeable_volatile_resident: u64,
555 purgeable_volatile_virtual: u64,
556 compressed: u64,
557 compressed_peak: u64,
558 compressed_lifetime: u64,
559 phys_footprint: u64,
560 _rest: [u64; 12],
562 }
563 const TASK_VM_INFO: u32 = 22;
564 extern "C" {
565 fn mach_task_self() -> u32;
566 fn task_info(
567 target: u32, flavor: u32,
568 info: *mut core::ffi::c_void, count: *mut u32,
569 ) -> i32;
570 }
571 unsafe {
572 let mut info: TaskVmInfo = core::mem::zeroed();
573 let mut count = (core::mem::size_of::<TaskVmInfo>() / 4) as u32;
574 let kr = task_info(
575 mach_task_self(),
576 TASK_VM_INFO,
577 &mut info as *mut _ as *mut core::ffi::c_void,
578 &mut count,
579 );
580 if kr == 0 { info.phys_footprint } else { 0 }
581 }
582 }
583 #[cfg(not(target_os = "macos"))]
584 { 0 }
585}
586
587#[cfg(feature = "probe")]
599pub fn start_peak_sampler() {
600 #[cfg(target_os = "macos")]
601 {
602 use std::sync::atomic::Ordering;
603 static STARTED: std::sync::atomic::AtomicBool =
605 std::sync::atomic::AtomicBool::new(false);
606 if STARTED.swap(true, Ordering::AcqRel) {
607 return;
608 }
609 std::thread::Builder::new()
610 .name("azul-peak-sampler".to_string())
611 .spawn(|| loop {
612 let now = phys_footprint_bytes();
613 let prev = PEAK_PHYS_FOOTPRINT.load(Ordering::Relaxed);
614 if now > prev {
615 PEAK_PHYS_FOOTPRINT.store(now, Ordering::Relaxed);
616 }
617 std::thread::sleep(std::time::Duration::from_micros(250));
618 })
619 .ok();
620 }
621}
622
623#[cfg(feature = "probe")]
624static PEAK_PHYS_FOOTPRINT: std::sync::atomic::AtomicU64 =
625 std::sync::atomic::AtomicU64::new(0);
626
627#[cfg(feature = "probe")]
630pub fn peak_phys_footprint_seen() -> u64 {
631 PEAK_PHYS_FOOTPRINT.load(std::sync::atomic::Ordering::Relaxed)
632}
633
634#[cfg(feature = "probe")]
640pub fn reset_peak() {
641 let now = phys_footprint_bytes();
642 PEAK_PHYS_FOOTPRINT.store(now, std::sync::atomic::Ordering::Relaxed);
643}
644
645#[cfg(feature = "probe")]
649#[inline]
650pub fn sample_phase_peak(label: &'static str) {
651 let peak = PEAK_PHYS_FOOTPRINT.load(std::sync::atomic::Ordering::Relaxed);
652 Probe::sample_rss(label, peak);
653}
654
655#[cfg(not(feature = "probe"))]
656#[inline]
657pub const fn reset_peak() {}
658
659#[cfg(not(feature = "probe"))]
660#[inline]
661pub const fn sample_phase_peak(_label: &'static str) {}
662
663#[cfg(not(feature = "probe"))]
664#[inline]
665#[must_use] pub const fn malloc_heap_bytes() -> u64 { 0 }
666
667#[cfg(feature = "probe")]
682pub fn emit_phase_heap(label: &str) {
683 use std::io::Write;
684 if !heap_jsonl_enabled() { return; }
685 let Some(p) = azul_core::profile::out_path() else { return };
686 static CALL_ID: std::sync::atomic::AtomicU64 =
687 std::sync::atomic::AtomicU64::new(0);
688 static CURRENT_CALL: std::sync::atomic::AtomicU64 =
692 std::sync::atomic::AtomicU64::new(0);
693 let call_id = if label == "start" {
694 let next = CALL_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1;
695 CURRENT_CALL.store(next, std::sync::atomic::Ordering::Relaxed);
696 next
697 } else {
698 CURRENT_CALL.load(std::sync::atomic::Ordering::Relaxed)
699 };
700 let heap = malloc_heap_bytes();
701 if let Ok(mut f) = std::fs::OpenOptions::new()
702 .create(true)
703 .append(true)
704 .open(p)
705 {
706 let _ = writeln!(
707 f,
708 r#"{{"ev":"phase","call":{},"label":"{}","heap":{}}}"#,
709 call_id, label, heap
710 );
711 }
712}
713
714#[cfg(not(feature = "probe"))]
715#[inline]
716pub const fn emit_phase_heap(_label: &str) {}
717
718#[cfg(feature = "probe")]
726pub fn emit_phase_heap_extra(label: &str, extra: u64) {
727 use std::io::Write;
728 if !heap_jsonl_enabled() { return; }
729 if !azul_core::profile::detail_enabled() { return; }
730 let Some(p) = azul_core::profile::out_path() else { return };
731 let heap = malloc_heap_bytes();
732 if let Ok(mut f) = std::fs::OpenOptions::new()
733 .create(true)
734 .append(true)
735 .open(p)
736 {
737 let _ = writeln!(
738 f,
739 r#"{{"ev":"phase","call":0,"label":"{}","heap":{},"extra":{}}}"#,
740 label, heap, extra
741 );
742 }
743}
744
745#[cfg(not(feature = "probe"))]
746#[inline]
747pub const fn emit_phase_heap_extra(_label: &str, _extra: u64) {}
748
749#[cfg(feature = "probe")]
752#[inline]
753fn heap_jsonl_enabled() -> bool {
754 let f = azul_core::profile::flags();
755 f.heap && f.jsonl
756}
757
758#[cfg(feature = "probe")]
762#[inline]
763pub fn detail_enabled() -> bool {
764 azul_core::profile::detail_enabled()
765}
766
767#[cfg(not(feature = "probe"))]
768#[inline]
769#[must_use] pub const fn detail_enabled() -> bool { false }
770
771#[cfg(test)]
772#[allow(let_underscore_drop, clippy::too_many_lines)]
773mod autotest_generated {
774 use super::*;
775
776 fn leak(s: String) -> &'static str {
780 Box::leak(s.into_boxed_str())
781 }
782
783 fn reset() {
787 Probe::drop_events();
788 assert_eq!(Probe::peek_len(), 0, "drop_events must leave an empty buffer");
789 }
790
791 fn span_ns(ev: &Event) -> Option<u64> {
792 match ev.kind {
793 EventKind::Span { dur_ns } => Some(dur_ns),
794 EventKind::Rss { .. } => None,
795 }
796 }
797
798 fn rss_bytes(ev: &Event) -> Option<u64> {
799 match ev.kind {
800 EventKind::Rss { bytes } => Some(bytes),
801 EventKind::Span { .. } => None,
802 }
803 }
804
805 #[test]
810 fn enabled_matches_the_compiled_imp() {
811 let expected = cfg!(all(
815 feature = "probe",
816 not(target_family = "wasm"),
817 not(feature = "web_lift")
818 ));
819 assert_eq!(Probe::enabled(), expected);
820 assert_eq!(imp::enabled(), expected);
821 }
822
823 #[test]
824 fn enabled_is_pure_and_idempotent() {
825 let first = Probe::enabled();
826 for _ in 0..1000 {
827 assert_eq!(Probe::enabled(), first);
828 }
829 }
830
831 #[test]
836 fn span_round_trips_name_through_drain() {
837 reset();
838 {
839 let _g = Probe::span("autotest_span_round_trip");
840 }
841 let events = Probe::drain();
842 if Probe::enabled() {
843 assert_eq!(events.len(), 1);
844 assert_eq!(events[0].name, "autotest_span_round_trip");
845 assert!(span_ns(&events[0]).is_some(), "span guard must emit EventKind::Span");
846 } else {
847 assert!(events.is_empty(), "no-op imp must never buffer events");
848 }
849 assert_eq!(Probe::peek_len(), 0, "drain must empty the buffer");
850 }
851
852 #[test]
853 fn nested_spans_drop_inner_first_and_outer_duration_is_the_larger() {
854 reset();
855 {
856 let _outer = Probe::span("outer");
857 {
858 let _inner = Probe::span("inner");
859 }
860 }
861 let events = Probe::drain();
862 if !Probe::enabled() {
863 assert!(events.is_empty());
864 return;
865 }
866 assert_eq!(events.len(), 2);
867 assert_eq!(events[0].name, "inner");
869 assert_eq!(events[1].name, "outer");
870 let inner = span_ns(&events[0]).expect("inner is a span");
871 let outer = span_ns(&events[1]).expect("outer is a span");
872 assert!(
874 outer >= inner,
875 "outer span ({outer} ns) must cover the inner one ({inner} ns)"
876 );
877 }
878
879 #[test]
880 fn forgotten_span_guard_records_nothing() {
881 reset();
882 core::mem::forget(Probe::span("forgotten"));
883 let events = Probe::drain();
884 assert!(
885 events.is_empty(),
886 "a leaked guard never runs Drop, so it must not emit an event"
887 );
888 }
889
890 #[test]
891 fn many_spans_do_not_lose_or_reorder_events() {
892 reset();
893 const N: usize = 10_000;
894 let names: Vec<&'static str> = (0..N).map(|i| leak(format!("phase_{i}"))).collect();
895 for &name in &names {
896 drop(Probe::span(name));
897 }
898 if Probe::enabled() {
899 assert_eq!(Probe::peek_len(), N);
900 } else {
901 assert_eq!(Probe::peek_len(), 0);
902 }
903 let events = Probe::drain();
904 if Probe::enabled() {
905 assert_eq!(events.len(), N);
906 for (i, ev) in events.iter().enumerate() {
907 assert_eq!(ev.name, names[i], "event order must be emission order");
908 }
909 } else {
910 assert!(events.is_empty());
911 }
912 assert_eq!(Probe::peek_len(), 0);
913 }
914
915 #[test]
916 fn span_survives_hostile_unicode_and_huge_names() {
917 reset();
918 let hostile: Vec<&'static str> = vec![
919 "",
920 "\0embedded\0nul\0",
921 "\n\r\t",
922 "{}{:?}{0}%s%n", "🦀👨👩👧👦🇩🇪", "مرحبا بالعالم", "e\u{0301}\u{0301}\u{0301}", leak("A".repeat(100_000)), leak("\u{1F4A9}".repeat(10_000)),
928 ];
929 for &name in &hostile {
930 drop(Probe::span(name));
931 }
932 let events = Probe::drain();
933 if Probe::enabled() {
934 assert_eq!(events.len(), hostile.len());
935 for (ev, name) in events.iter().zip(hostile.iter()) {
936 assert_eq!(ev.name, *name, "name must round-trip byte-for-byte");
937 }
938 print_drained_events("hostile-names", &events);
940 } else {
941 assert!(events.is_empty());
942 }
943 }
944
945 #[test]
946 fn drain_is_empty_the_second_time() {
947 reset();
948 drop(Probe::span("once"));
949 let first = Probe::drain();
950 let second = Probe::drain();
951 if Probe::enabled() {
952 assert_eq!(first.len(), 1);
953 }
954 assert!(second.is_empty(), "a drained buffer must stay drained");
955 }
956
957 #[test]
962 fn sample_rss_round_trips_every_numeric_boundary() {
963 reset();
964 let boundaries: [u64; 8] = [
965 0,
966 1,
967 u64::from(u32::MAX),
968 u64::from(u32::MAX) + 1,
969 1 << 63,
970 u64::MAX - 1,
971 u64::MAX,
972 0xDEAD_BEEF_DEAD_BEEF,
973 ];
974 for b in boundaries {
975 Probe::sample_rss("bytes", b);
976 }
977 let events = Probe::drain();
978 if !Probe::enabled() {
979 assert!(events.is_empty());
980 return;
981 }
982 assert_eq!(events.len(), boundaries.len());
983 for (ev, expected) in events.iter().zip(boundaries.iter()) {
984 assert_eq!(
985 rss_bytes(ev),
986 Some(*expected),
987 "RSS byte counts must survive the buffer unchanged (no saturation)"
988 );
989 }
990 }
991
992 #[test]
993 fn sample_rss_zero_is_recorded_not_skipped() {
994 reset();
995 Probe::sample_rss("zero", 0);
996 let events = Probe::drain();
997 if Probe::enabled() {
998 assert_eq!(events.len(), 1, "a 0-byte checkpoint is still a checkpoint");
999 assert_eq!(rss_bytes(&events[0]), Some(0));
1000 assert_eq!(events[0].name, "zero");
1001 } else {
1002 assert!(events.is_empty());
1003 }
1004 }
1005
1006 #[test]
1011 fn peek_len_tracks_pushes_and_drop_events_clears() {
1012 reset();
1013 assert_eq!(Probe::peek_len(), 0);
1014 for i in 0..64u64 {
1015 Probe::sample_rss("tick", i);
1016 }
1017 if Probe::enabled() {
1018 assert_eq!(Probe::peek_len(), 64);
1019 } else {
1020 assert_eq!(Probe::peek_len(), 0);
1021 }
1022 Probe::drop_events();
1023 assert_eq!(Probe::peek_len(), 0, "drop_events must clear the buffer");
1024 assert!(
1025 Probe::drain().is_empty(),
1026 "drop_events must discard, not stash, the events"
1027 );
1028 }
1029
1030 #[test]
1031 fn drop_events_on_an_empty_buffer_is_a_no_op() {
1032 reset();
1033 for _ in 0..100 {
1034 Probe::drop_events();
1035 assert_eq!(Probe::peek_len(), 0);
1036 }
1037 }
1038
1039 #[test]
1040 fn peek_len_is_side_effect_free() {
1041 reset();
1042 Probe::sample_rss("keep", 7);
1043 let expected = if Probe::enabled() { 1 } else { 0 };
1044 for _ in 0..100 {
1045 assert_eq!(Probe::peek_len(), expected, "peek must not consume events");
1046 }
1047 let events = Probe::drain();
1048 assert_eq!(events.len(), expected);
1049 }
1050
1051 #[test]
1056 fn event_buffer_is_per_thread() {
1057 reset();
1058 Probe::sample_rss("main_thread", 1);
1059
1060 let child_len = std::thread::spawn(|| {
1061 assert_eq!(Probe::peek_len(), 0, "buffers must not be shared across threads");
1064 Probe::sample_rss("child_thread", 2);
1065 let drained = Probe::drain();
1066 for ev in &drained {
1067 assert_eq!(ev.name, "child_thread", "child must only see its own events");
1068 }
1069 drained.len()
1070 })
1071 .join()
1072 .expect("probe calls must not panic on a spawned thread");
1073
1074 let events = Probe::drain();
1075 if Probe::enabled() {
1076 assert_eq!(child_len, 1);
1077 assert_eq!(events.len(), 1, "the child's drain must not touch our buffer");
1078 assert_eq!(events[0].name, "main_thread");
1079 } else {
1080 assert_eq!(child_len, 0);
1081 assert!(events.is_empty());
1082 }
1083 }
1084
1085 #[test]
1090 fn imp_facade_parity() {
1091 reset();
1092 {
1093 let _g = imp::open("imp_open");
1094 }
1095 imp::sample_rss("imp_rss", u64::MAX);
1096 let len = imp::peek_len();
1097 assert_eq!(len, Probe::peek_len());
1098 let events = imp::drain();
1099 assert_eq!(events.len(), len);
1100 assert_eq!(imp::peek_len(), 0);
1101 if Probe::enabled() {
1102 assert_eq!(events[0].name, "imp_open");
1103 assert_eq!(rss_bytes(&events[1]), Some(u64::MAX));
1104 } else {
1105 assert!(events.is_empty());
1106 }
1107 imp::drop_events();
1108 assert_eq!(imp::peek_len(), 0);
1109 }
1110
1111 #[test]
1116 fn print_drained_events_empty_slice_does_not_panic() {
1117 print_drained_events("empty", &[]);
1121 print_drained_events("", &[]);
1122 }
1123
1124 #[test]
1125 fn print_drained_events_rss_only_has_no_span_rows() {
1126 let events = [
1129 Event { name: "a", kind: EventKind::Rss { bytes: 0 } },
1130 Event { name: "b", kind: EventKind::Rss { bytes: u64::MAX } },
1131 Event { name: "c", kind: EventKind::Rss { bytes: 1 } },
1132 ];
1133 print_drained_events("rss-only", &events);
1134 }
1135
1136 #[test]
1137 fn print_drained_events_p99_index_is_in_bounds_for_every_sample_count() {
1138 for n in [1usize, 2, 3, 99, 100, 101, 199, 200, 201, 1000] {
1141 let events: Vec<Event> = (0..n)
1142 .map(|i| Event {
1143 name: "phase",
1144 kind: EventKind::Span { dur_ns: i as u64 },
1145 })
1146 .collect();
1147 print_drained_events("p99", &events);
1148 }
1149 }
1150
1151 #[test]
1152 fn print_drained_events_saturating_totals_do_not_panic() {
1153 let events = [
1157 Event { name: "huge", kind: EventKind::Span { dur_ns: u64::MAX } },
1158 Event { name: "huge", kind: EventKind::Span { dur_ns: u64::MAX } },
1159 Event { name: "huge", kind: EventKind::Span { dur_ns: u64::MAX } },
1160 Event { name: "zero", kind: EventKind::Span { dur_ns: 0 } },
1161 ];
1162 print_drained_events("overflowing-total", &events);
1163 }
1164
1165 #[test]
1166 fn print_drained_events_rss_delta_handles_full_u64_swing() {
1167 let events = [
1170 Event { name: "peak", kind: EventKind::Rss { bytes: u64::MAX } },
1171 Event { name: "trough", kind: EventKind::Rss { bytes: 0 } },
1172 Event { name: "peak_again", kind: EventKind::Rss { bytes: u64::MAX } },
1173 ];
1174 print_drained_events("delta-swing", &events);
1175 }
1176
1177 #[test]
1178 fn print_drained_events_hostile_labels_and_names() {
1179 let big = leak("x".repeat(65_536));
1180 let events = [
1181 Event { name: "", kind: EventKind::Span { dur_ns: 1 } },
1182 Event { name: "{}{:?}", kind: EventKind::Span { dur_ns: 2 } },
1183 Event { name: big, kind: EventKind::Span { dur_ns: u64::MAX } },
1184 Event { name: "🦀\u{0301}\0", kind: EventKind::Rss { bytes: 1 } },
1185 ];
1186 print_drained_events(big, &events);
1187 print_drained_events("\0\n{}", &events);
1188 }
1189
1190 #[test]
1191 fn print_drained_events_accepts_a_real_drain() {
1192 reset();
1193 {
1194 let _a = Probe::span("layout");
1195 let _b = Probe::span("layout");
1196 }
1197 Probe::sample_rss("after", 4096);
1198 let events = Probe::drain();
1199 print_drained_events("real-drain", &events);
1200 }
1201
1202 #[test]
1207 fn monotonic_now_nanos_never_goes_backwards() {
1208 let mut prev = monotonic_now_nanos();
1209 for _ in 0..10_000 {
1210 let now = monotonic_now_nanos();
1211 assert!(now >= prev, "clock went backwards: {prev} -> {now}");
1212 prev = now;
1213 }
1214 }
1215
1216 #[test]
1217 fn monotonic_now_nanos_is_monotonic_across_threads() {
1218 let before = monotonic_now_nanos();
1221 let mid = std::thread::spawn(monotonic_now_nanos)
1222 .join()
1223 .expect("monotonic_now_nanos must not panic off the main thread");
1224 let after = monotonic_now_nanos();
1225 assert!(before <= mid && mid <= after, "{before} <= {mid} <= {after}");
1226 }
1227
1228 #[test]
1233 fn sample_peak_rss_emits_exactly_one_labelled_event() {
1234 reset();
1235 sample_peak_rss("autotest_peak_rss");
1236 let events = Probe::drain();
1237 if Probe::enabled() {
1238 assert_eq!(events.len(), 1);
1239 assert_eq!(events[0].name, "autotest_peak_rss");
1240 assert!(
1241 rss_bytes(&events[0]).is_some(),
1242 "sample_peak_rss must emit an Rss-kind event"
1243 );
1244 } else {
1245 assert!(events.is_empty());
1246 }
1247 }
1248
1249 #[test]
1250 fn sample_phase_peak_emits_exactly_one_labelled_event() {
1251 reset();
1252 sample_phase_peak("autotest_phase_peak");
1253 let events = Probe::drain();
1254 if Probe::enabled() {
1255 assert_eq!(events.len(), 1);
1256 assert_eq!(events[0].name, "autotest_phase_peak");
1257 assert!(rss_bytes(&events[0]).is_some());
1258 } else {
1259 assert!(events.is_empty());
1260 }
1261 }
1262
1263 #[test]
1264 fn reset_peak_is_repeatable_and_side_effect_free_on_the_event_buffer() {
1265 reset();
1266 for _ in 0..100 {
1267 reset_peak();
1268 }
1269 assert_eq!(
1270 Probe::peek_len(),
1271 0,
1272 "reset_peak touches an atomic, it must not push events"
1273 );
1274 }
1275
1276 #[test]
1277 fn hint_purge_allocator_is_repeatable_and_emits_nothing() {
1278 reset();
1279 for _ in 0..50 {
1280 hint_purge_allocator();
1281 }
1282 assert_eq!(Probe::peek_len(), 0, "purging must not push probe events");
1283 }
1284
1285 #[test]
1290 fn malloc_heap_bytes_is_zero_off_macos_and_deterministic() {
1291 let a = malloc_heap_bytes();
1292 let b = malloc_heap_bytes();
1293 if cfg!(all(feature = "probe", target_os = "macos")) {
1294 let _ = (a, b);
1297 } else {
1298 assert_eq!(a, 0);
1301 assert_eq!(b, 0);
1302 }
1303 }
1304
1305 #[test]
1306 fn detail_enabled_is_deterministic() {
1307 let first = detail_enabled();
1308 for _ in 0..100 {
1309 assert_eq!(detail_enabled(), first, "flag reads are cached, must not flap");
1310 }
1311 if !cfg!(feature = "probe") {
1312 assert!(!first, "the no-probe stub is a const `false`");
1313 }
1314 }
1315
1316 #[test]
1322 fn emit_phase_heap_survives_hostile_labels() {
1323 reset();
1324 let huge = "L".repeat(65_536);
1325 let labels: Vec<&str> = vec![
1326 "",
1327 "start",
1328 "start", "end",
1330 "\"quote\"", "back\\slash",
1332 "new\nline",
1333 "\0nul",
1334 "🦀 unicode",
1335 &huge,
1336 ];
1337 for l in &labels {
1338 emit_phase_heap(l);
1339 }
1340 assert_eq!(Probe::peek_len(), 0, "JSONL emission must not touch the span buffer");
1341 }
1342
1343 #[test]
1344 fn emit_phase_heap_extra_survives_numeric_boundaries() {
1345 reset();
1346 for extra in [0u64, 1, u64::MAX / 2, u64::MAX - 1, u64::MAX] {
1347 emit_phase_heap_extra("autotest_extra", extra);
1348 emit_phase_heap_extra("", extra);
1349 }
1350 assert_eq!(Probe::peek_len(), 0);
1351 }
1352
1353 #[test]
1358 fn event_is_copy_and_clone_preserving_payload() {
1359 let span = Event { name: "n", kind: EventKind::Span { dur_ns: u64::MAX } };
1360 let rss = Event { name: "n", kind: EventKind::Rss { bytes: u64::MAX } };
1361 let span_copy = span; #[allow(clippy::clone_on_copy)]
1363 let rss_clone = rss.clone();
1364 assert_eq!(span_ns(&span_copy), Some(u64::MAX));
1365 assert_eq!(rss_bytes(&rss_clone), Some(u64::MAX));
1366 assert!(span_ns(&rss_clone).is_none());
1368 assert!(rss_bytes(&span_copy).is_none());
1369 let _ = format!("{span:?}{rss:?}");
1371 }
1372
1373 #[cfg(feature = "probe")]
1378 #[test]
1379 fn peak_rss_bytes_is_monotonic_and_agrees_with_the_pub_wrapper() {
1380 let first = peak_rss_bytes_self();
1382 let pubbed = peak_rss_bytes_pub();
1383 let second = peak_rss_bytes_self();
1384 assert!(pubbed >= first, "peak RSS must never decrease: {first} -> {pubbed}");
1385 assert!(second >= pubbed, "peak RSS must never decrease: {pubbed} -> {second}");
1386 if cfg!(unix) && !cfg!(miri) {
1387 assert!(first > 0, "getrusage on a live unix process must report some RSS");
1388 }
1389 }
1390
1391 #[cfg(feature = "probe")]
1392 #[test]
1393 fn current_rss_bytes_does_not_panic_and_is_self_consistent() {
1394 let (footprint, virt) = current_rss_bytes();
1395 if cfg!(all(target_os = "macos", not(miri))) {
1396 assert!(footprint > 0, "macOS must report a non-zero footprint");
1397 assert!(virt >= footprint || virt == 0);
1398 }
1399 for _ in 0..100 {
1401 let _ = current_rss_bytes();
1402 }
1403 }
1404
1405 #[cfg(feature = "probe")]
1406 #[test]
1407 fn phys_footprint_bytes_is_zero_off_macos() {
1408 let v = phys_footprint_bytes();
1409 if cfg!(all(target_os = "macos", not(miri))) {
1410 assert!(v > 0);
1411 } else {
1412 assert_eq!(v, 0, "documented: returns 0 on non-macOS / under miri");
1413 }
1414 }
1415
1416 #[cfg(feature = "probe")]
1417 #[test]
1418 fn start_peak_sampler_is_idempotent() {
1419 for _ in 0..200 {
1422 start_peak_sampler();
1423 }
1424 let _ = peak_phys_footprint_seen();
1425 }
1426
1427 #[cfg(feature = "probe")]
1428 #[test]
1429 fn peak_phys_footprint_seen_is_readable_without_a_sampler() {
1430 let seen = peak_phys_footprint_seen();
1434 if !cfg!(target_os = "macos") {
1435 assert_eq!(seen, 0, "no phys_footprint source off macOS => peak stays 0");
1436 }
1437 }
1438
1439 #[cfg(feature = "probe")]
1440 #[test]
1441 fn heap_jsonl_enabled_matches_the_profile_flags() {
1442 let f = azul_core::profile::flags();
1443 assert_eq!(
1444 heap_jsonl_enabled(),
1445 f.heap && f.jsonl,
1446 "either token alone must be a no-op"
1447 );
1448 let first = heap_jsonl_enabled();
1449 for _ in 0..100 {
1450 assert_eq!(heap_jsonl_enabled(), first, "flags are cached, must not flap");
1451 }
1452 }
1453}