1use atomic_refcell::AtomicRefCell;
2use std::collections::HashMap;
3use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
4use std::time::{Duration, Instant};
5
6pub struct GlobalState {
16 pub(crate) gc_status: GcStatusWord,
18 pub(crate) pause_requested_time: AtomicRefCell<Option<Instant>>,
20 pub(crate) pause_start_time: AtomicRefCell<Option<Instant>>,
24 pub(crate) emergency_collection: AtomicBool,
27 pub(crate) user_triggered_collection: AtomicBool,
29 pub(crate) internal_triggered_collection: AtomicBool,
32 pub(crate) last_internal_triggered_collection: AtomicBool,
34 pub(crate) allocation_success: AtomicBool,
36 pub(crate) max_collection_attempts: AtomicUsize,
38 pub(crate) cur_collection_attempts: AtomicUsize,
40 pub(crate) scanned_stacks: AtomicUsize,
42 pub(crate) stacks_prepared: AtomicBool,
44 pub(crate) allocation_bytes: AtomicUsize,
46 pub(crate) inside_harness: AtomicBool,
48 #[cfg(feature = "malloc_counted_size")]
50 pub(crate) malloc_bytes: AtomicUsize,
51 pub(crate) live_bytes_in_last_gc: AtomicRefCell<HashMap<&'static str, LiveBytesStats>>,
53 pub(crate) used_pages_after_last_gc: AtomicUsize,
55}
56
57impl GlobalState {
58 pub fn is_initialized(&self) -> bool {
60 self.gc_status.is_initialized()
61 }
62
63 pub fn set_collection_kind(
66 &self,
67 last_collection_was_exhaustive: bool,
68 heap_can_grow: bool,
69 ) -> bool {
70 self.cur_collection_attempts.store(
71 if self.user_triggered_collection.load(Ordering::Relaxed) {
72 1
73 } else {
74 self.determine_collection_attempts()
75 },
76 Ordering::Relaxed,
77 );
78
79 let emergency_collection = !self.is_internal_triggered_collection()
80 && last_collection_was_exhaustive
81 && self.cur_collection_attempts.load(Ordering::Relaxed) > 1
82 && !heap_can_grow;
83 self.emergency_collection
84 .store(emergency_collection, Ordering::Relaxed);
85
86 emergency_collection
87 }
88
89 fn determine_collection_attempts(&self) -> usize {
90 if !self.allocation_success.load(Ordering::Relaxed) {
91 self.max_collection_attempts.fetch_add(1, Ordering::Relaxed);
92 } else {
93 self.allocation_success.store(false, Ordering::Relaxed);
94 self.max_collection_attempts.store(1, Ordering::Relaxed);
95 }
96
97 self.max_collection_attempts.load(Ordering::Relaxed)
98 }
99
100 fn is_internal_triggered_collection(&self) -> bool {
101 let is_internal_triggered = self
102 .last_internal_triggered_collection
103 .load(Ordering::SeqCst);
104 assert!(
106 !is_internal_triggered,
107 "We have no concurrent GC implemented. We should not have internally triggered GC"
108 );
109 is_internal_triggered
110 }
111
112 pub fn is_emergency_collection(&self) -> bool {
113 self.emergency_collection.load(Ordering::Relaxed)
114 }
115
116 pub fn is_user_triggered_collection(&self) -> bool {
118 self.user_triggered_collection.load(Ordering::Relaxed)
119 }
120
121 pub fn reset_collection_trigger(&self) {
123 self.last_internal_triggered_collection.store(
124 self.internal_triggered_collection.load(Ordering::SeqCst),
125 Ordering::Relaxed,
126 );
127 self.internal_triggered_collection
128 .store(false, Ordering::SeqCst);
129 self.user_triggered_collection
130 .store(false, Ordering::Relaxed);
131 }
132
133 pub fn stacks_prepared(&self) -> bool {
135 self.stacks_prepared.load(Ordering::SeqCst)
136 }
137
138 pub fn prepare_for_stack_scanning(&self) {
141 self.scanned_stacks.store(0, Ordering::SeqCst);
142 self.stacks_prepared.store(false, Ordering::SeqCst);
143 }
144
145 pub fn inform_stack_scanned(&self, n_mutators: usize) -> bool {
150 let old = self.scanned_stacks.fetch_add(1, Ordering::SeqCst);
151 debug_assert!(
152 old < n_mutators,
153 "The number of scanned stacks ({}) is more than the number of mutators ({})",
154 old,
155 n_mutators
156 );
157 let scanning_done = old + 1 == n_mutators;
158 if scanning_done {
159 self.stacks_prepared.store(true, Ordering::SeqCst);
160 }
161 scanning_done
162 }
163
164 pub fn increase_allocation_bytes_by(&self, size: usize) -> usize {
166 let old_allocation_bytes = self.allocation_bytes.fetch_add(size, Ordering::SeqCst);
167 trace!(
168 "Stress GC: old_allocation_bytes = {}, size = {}, allocation_bytes = {}",
169 old_allocation_bytes,
170 size,
171 self.allocation_bytes.load(Ordering::Relaxed),
172 );
173 old_allocation_bytes + size
174 }
175
176 #[cfg(feature = "malloc_counted_size")]
177 pub fn get_malloc_bytes_in_pages(&self) -> usize {
178 crate::util::conversions::bytes_to_pages_up(self.malloc_bytes.load(Ordering::Relaxed))
179 }
180
181 #[cfg(feature = "malloc_counted_size")]
182 pub(crate) fn increase_malloc_bytes_by(&self, size: usize) {
183 self.malloc_bytes.fetch_add(size, Ordering::SeqCst);
184 }
185
186 #[cfg(feature = "malloc_counted_size")]
187 pub(crate) fn decrease_malloc_bytes_by(&self, size: usize) {
188 self.malloc_bytes.fetch_sub(size, Ordering::SeqCst);
189 }
190
191 pub(crate) fn set_used_pages_after_last_gc(&self, pages: usize) {
192 self.used_pages_after_last_gc
193 .store(pages, Ordering::Relaxed);
194 }
195
196 pub(crate) fn get_used_pages_after_last_gc(&self) -> usize {
197 self.used_pages_after_last_gc.load(Ordering::Relaxed)
198 }
199
200 pub(crate) fn record_pause_requested_time(&self) {
203 let mut pause_requested_time = self.pause_requested_time.borrow_mut();
204 assert!(
205 pause_requested_time.is_none(),
206 "A pause was requested while a previous pause request time is still pending"
207 );
208 *pause_requested_time = Some(Instant::now());
209 }
210
211 pub(crate) fn take_time_to_yield(&self) -> Duration {
215 self.pause_requested_time
216 .borrow_mut()
217 .take()
218 .expect("Pause requested time was not recorded")
219 .elapsed()
220 }
221
222 pub(crate) fn record_pause_start_time(&self) {
224 let mut pause_start_time = self.pause_start_time.borrow_mut();
225 assert!(
226 pause_start_time.is_none(),
227 "A pause was started while a previous pause start time is still pending"
228 );
229 *pause_start_time = Some(Instant::now());
230 }
231
232 pub(crate) fn take_pause_time(&self) -> Duration {
236 self.pause_start_time
237 .borrow_mut()
238 .take()
239 .expect("Pause start time was not recorded")
240 .elapsed()
241 }
242}
243
244impl Default for GlobalState {
245 fn default() -> Self {
246 Self {
247 gc_status: GcStatusWord::new(GcStatus::Uninitialized),
248 pause_requested_time: AtomicRefCell::new(None),
249 pause_start_time: AtomicRefCell::new(None),
250 stacks_prepared: AtomicBool::new(false),
251 emergency_collection: AtomicBool::new(false),
252 user_triggered_collection: AtomicBool::new(false),
253 internal_triggered_collection: AtomicBool::new(false),
254 last_internal_triggered_collection: AtomicBool::new(false),
255 allocation_success: AtomicBool::new(false),
256 max_collection_attempts: AtomicUsize::new(0),
257 cur_collection_attempts: AtomicUsize::new(0),
258 scanned_stacks: AtomicUsize::new(0),
259 allocation_bytes: AtomicUsize::new(0),
260 inside_harness: AtomicBool::new(false),
261 #[cfg(feature = "malloc_counted_size")]
262 malloc_bytes: AtomicUsize::new(0),
263 live_bytes_in_last_gc: AtomicRefCell::new(HashMap::new()),
264 used_pages_after_last_gc: AtomicUsize::new(0),
265 }
266 }
267}
268
269#[derive(PartialEq, Copy, Clone, Debug)]
274pub enum GcStatus {
275 Uninitialized,
278 NotInGC,
280 InConcurrentGC,
283 InPause,
286 PauseRequested,
289 Disabled(usize),
294}
295
296pub(crate) struct GcStatusWord(AtomicUsize);
307
308impl GcStatusWord {
309 const TAG_BITS: u32 = 3;
312 const TAG_MASK: usize = (1 << Self::TAG_BITS) - 1;
313
314 fn encode(status: GcStatus) -> usize {
315 match status {
316 GcStatus::Uninitialized => 0,
317 GcStatus::NotInGC => 1,
318 GcStatus::InConcurrentGC => 2,
319 GcStatus::InPause => 3,
320 GcStatus::PauseRequested => 4,
321 GcStatus::Disabled(depth) => {
322 debug_assert!(
323 depth < (1 << (usize::BITS - Self::TAG_BITS)),
324 "GC-disable nesting depth overflows the bits reserved for it"
325 );
326 5 | (depth << Self::TAG_BITS)
327 }
328 }
329 }
330
331 fn decode(bits: usize) -> GcStatus {
332 match bits & Self::TAG_MASK {
333 0 => GcStatus::Uninitialized,
334 1 => GcStatus::NotInGC,
335 2 => GcStatus::InConcurrentGC,
336 3 => GcStatus::InPause,
337 4 => GcStatus::PauseRequested,
338 5 => GcStatus::Disabled(bits >> Self::TAG_BITS),
339 _ => unreachable!("invalid encoded GcStatus tag"),
340 }
341 }
342
343 pub(crate) fn new(status: GcStatus) -> Self {
344 GcStatusWord(AtomicUsize::new(Self::encode(status)))
345 }
346
347 pub(crate) fn load(&self) -> GcStatus {
349 Self::decode(self.0.load(Ordering::SeqCst))
350 }
351
352 fn transition_inner<F: FnMut(GcStatus) -> GcStatus>(&self, mut f: F) -> GcStatus {
355 let old_bits = self
356 .0
357 .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |bits| {
358 Some(Self::encode(f(Self::decode(bits))))
359 })
360 .unwrap(); Self::decode(old_bits)
362 }
363
364 fn transition<F: FnMut(GcStatus) -> GcStatus>(&self, mut f: F) -> GcStatus {
376 let mut maybe_new_state = None;
377 let old_state = self.transition_inner(|old_state| {
378 let new_state = f(old_state);
379 maybe_new_state = Some(new_state);
380 new_state
381 });
382 let new_state = maybe_new_state.unwrap();
383 log::trace!("GC status transitioned from {old_state:?} to {new_state:?}");
384 old_state
385 }
386
387 fn try_transition_inner<F: FnMut(GcStatus) -> Option<GcStatus>>(
390 &self,
391 mut f: F,
392 ) -> Result<GcStatus, GcStatus> {
393 self.0
394 .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |bits| {
395 f(Self::decode(bits)).map(Self::encode)
396 })
397 .map(Self::decode)
398 .map_err(Self::decode)
399 }
400
401 fn try_transition<F: FnMut(GcStatus) -> Option<GcStatus>>(
407 &self,
408 mut f: F,
409 ) -> Result<GcStatus, GcStatus> {
410 let mut maybe_new_state = None;
411 let result = self.try_transition_inner(|old_state| {
412 maybe_new_state = f(old_state);
413 maybe_new_state
414 });
415
416 match result {
417 Ok(old_state) => {
418 let new_state = maybe_new_state.unwrap();
419 log::trace!("GC status transitioned from {old_state:?} to {new_state:?}")
420 }
421 Err(old_state) => {
422 log::trace!("GC status transition attempted, but remains {old_state:?}")
423 }
424 }
425
426 result
427 }
428
429 pub(crate) fn is_initialized(&self) -> bool {
430 self.load() != GcStatus::Uninitialized
431 }
432
433 pub(crate) fn is_disabled(&self) -> bool {
434 matches!(self.load(), GcStatus::Disabled(_))
435 }
436
437 pub(crate) fn set_initialized(&self) {
439 self.transition(|status| {
440 assert!(
441 status == GcStatus::Uninitialized,
442 "Trying to set initialized GC status when it is not uninitialized"
443 );
444 GcStatus::NotInGC
445 });
446 }
447
448 pub(crate) fn set_uninitialized(&self) {
450 self.transition(|status| {
451 assert!(
452 status != GcStatus::Uninitialized,
453 "Trying to set uninitialized GC status when it is already uninitialized"
454 );
455 GcStatus::Uninitialized
456 });
457 }
458
459 pub(crate) fn set_in_pause(&self) {
461 self.transition(|status| {
462 assert!(
463 status == GcStatus::PauseRequested,
464 "Trying to set in-pause GC status in invalid status: {:?}",
465 status
466 );
467 GcStatus::InPause
468 });
469 }
470
471 pub(crate) fn set_in_concurrent_gc(&self) {
474 self.transition(|status| {
475 assert!(
476 status == GcStatus::InPause,
477 "Trying to set in-concurrent-gc GC status in invalid status: {:?}",
478 status
479 );
480 GcStatus::InConcurrentGC
481 });
482 }
483
484 pub(crate) fn set_concurrent_gc_finished(&self) -> bool {
487 self.try_transition(|status| match status {
488 GcStatus::InConcurrentGC => Some(GcStatus::NotInGC),
489 _ => None,
490 })
491 .is_ok()
492 }
493
494 pub(crate) fn set_not_in_gc(&self) {
496 self.transition(|status| {
497 assert!(
498 status == GcStatus::InPause,
499 "Trying to set not-in-gc GC status in invalid status: {:?}",
500 status
501 );
502 GcStatus::NotInGC
503 });
504 }
505
506 pub(crate) fn set_disabled(&self) -> Result<bool, GcStatus> {
515 self.try_transition(|status| match status {
516 GcStatus::Disabled(depth) => Some(GcStatus::Disabled(depth + 1)),
517 GcStatus::NotInGC => Some(GcStatus::Disabled(1)),
518 _ => None,
519 })
520 .map(|old_status| old_status == GcStatus::NotInGC)
521 }
522
523 pub(crate) fn set_enabled(&self) -> bool {
529 let old = self.transition(|status| match status {
530 GcStatus::Disabled(1) => GcStatus::NotInGC,
531 GcStatus::Disabled(depth) => GcStatus::Disabled(depth - 1),
532 other => other,
533 });
534 old == GcStatus::Disabled(1)
535 }
536
537 pub(crate) fn try_request_pause(&self) -> Result<GcStatus, GcStatus> {
542 self.try_transition(|status| match status {
543 GcStatus::Disabled(_) | GcStatus::Uninitialized | GcStatus::PauseRequested => None,
544 GcStatus::NotInGC | GcStatus::InConcurrentGC => Some(GcStatus::PauseRequested),
545 _ => panic!("Trying to request a GC pause in invalid status: {status:?}"),
546 })
547 }
548}
549
550#[cfg(test)]
551mod gc_status_tests {
552 use super::{GcStatus, GcStatusWord};
553
554 #[test]
555 fn encode_decode_roundtrip() {
556 let statuses = [
557 GcStatus::Uninitialized,
558 GcStatus::NotInGC,
559 GcStatus::InConcurrentGC,
560 GcStatus::InPause,
561 GcStatus::PauseRequested,
562 GcStatus::Disabled(1),
563 GcStatus::Disabled(42),
564 ];
565 for status in statuses {
566 assert_eq!(GcStatusWord::decode(GcStatusWord::encode(status)), status);
567 }
568 }
569
570 #[test]
571 fn new_and_load_roundtrip() {
572 let statuses = [
573 GcStatus::Uninitialized,
574 GcStatus::NotInGC,
575 GcStatus::InConcurrentGC,
576 GcStatus::InPause,
577 GcStatus::PauseRequested,
578 GcStatus::Disabled(1),
579 GcStatus::Disabled(42),
580 ];
581 for status in statuses {
582 assert_eq!(GcStatusWord::new(status).load(), status);
583 }
584 }
585
586 #[test]
587 fn set_initialized_from_uninitialized() {
588 let word = GcStatusWord::new(GcStatus::Uninitialized);
589 assert!(!word.is_initialized());
590 word.set_initialized();
591 assert_eq!(word.load(), GcStatus::NotInGC);
592 assert!(word.is_initialized());
593 }
594
595 #[test]
596 #[should_panic(expected = "not uninitialized")]
597 fn set_initialized_panics_if_already_initialized() {
598 GcStatusWord::new(GcStatus::NotInGC).set_initialized();
599 }
600
601 #[test]
602 fn set_uninitialized_from_not_in_gc() {
603 let word = GcStatusWord::new(GcStatus::NotInGC);
604 word.set_uninitialized();
605 assert_eq!(word.load(), GcStatus::Uninitialized);
606 }
607
608 #[test]
609 #[should_panic(expected = "already uninitialized")]
610 fn set_uninitialized_panics_if_already_uninitialized() {
611 GcStatusWord::new(GcStatus::Uninitialized).set_uninitialized();
612 }
613
614 #[test]
615 fn try_request_pause_from_not_in_gc() {
616 let word = GcStatusWord::new(GcStatus::NotInGC);
617 assert_eq!(word.try_request_pause(), Ok(GcStatus::NotInGC));
618 assert_eq!(word.load(), GcStatus::PauseRequested);
619 }
620
621 #[test]
622 fn try_request_pause_from_in_concurrent_gc() {
623 let word = GcStatusWord::new(GcStatus::InConcurrentGC);
624 assert_eq!(word.try_request_pause(), Ok(GcStatus::InConcurrentGC));
625 assert_eq!(word.load(), GcStatus::PauseRequested);
626 }
627
628 #[test]
629 fn try_request_pause_when_already_requested() {
630 let word = GcStatusWord::new(GcStatus::PauseRequested);
631 assert_eq!(word.try_request_pause(), Err(GcStatus::PauseRequested));
632 assert_eq!(word.load(), GcStatus::PauseRequested);
634 }
635
636 #[test]
641 #[should_panic(expected = "invalid status")]
642 fn try_request_pause_panics_when_already_in_pause() {
643 let _ = GcStatusWord::new(GcStatus::InPause).try_request_pause();
644 }
645
646 #[test]
647 fn try_request_pause_when_disabled() {
648 let word = GcStatusWord::new(GcStatus::Disabled(1));
649 assert_eq!(word.try_request_pause(), Err(GcStatus::Disabled(1)));
650 assert_eq!(word.load(), GcStatus::Disabled(1));
652 }
653
654 #[test]
660 fn try_request_pause_when_uninitialized() {
661 let word = GcStatusWord::new(GcStatus::Uninitialized);
662 assert_eq!(word.try_request_pause(), Err(GcStatus::Uninitialized));
663 assert_eq!(word.load(), GcStatus::Uninitialized);
664 }
665
666 #[test]
667 fn set_in_pause_from_pause_requested() {
668 let word = GcStatusWord::new(GcStatus::PauseRequested);
669 word.set_in_pause();
670 assert_eq!(word.load(), GcStatus::InPause);
671 }
672
673 #[test]
674 #[should_panic(expected = "invalid status")]
675 fn set_in_pause_panics_if_not_requested() {
676 GcStatusWord::new(GcStatus::NotInGC).set_in_pause();
677 }
678
679 #[test]
680 fn set_disabled_from_not_in_gc() {
681 let word = GcStatusWord::new(GcStatus::NotInGC);
682 assert_eq!(word.set_disabled(), Ok(true));
683 assert_eq!(word.load(), GcStatus::Disabled(1));
684 }
685
686 #[test]
687 fn set_disabled_nests() {
688 let word = GcStatusWord::new(GcStatus::Disabled(1));
689 assert_eq!(word.set_disabled(), Ok(false));
690 assert_eq!(word.load(), GcStatus::Disabled(2));
691
692 assert_eq!(word.set_disabled(), Ok(false));
693 assert_eq!(word.load(), GcStatus::Disabled(3));
694 }
695
696 #[test]
697 fn set_disabled_fails_without_changing_status() {
698 for status in [
699 GcStatus::Uninitialized,
700 GcStatus::InConcurrentGC,
701 GcStatus::PauseRequested,
702 GcStatus::InPause,
703 ] {
704 let word = GcStatusWord::new(status);
705 assert_eq!(word.set_disabled(), Err(status));
706 assert_eq!(word.load(), status);
707 }
708 }
709
710 #[test]
711 fn set_enabled_decrements_nesting() {
712 let word = GcStatusWord::new(GcStatus::Disabled(3));
713 assert!(!word.set_enabled());
714 assert_eq!(word.load(), GcStatus::Disabled(2));
715 }
716
717 #[test]
718 fn set_enabled_to_not_in_gc_at_zero_depth() {
719 let word = GcStatusWord::new(GcStatus::Disabled(1));
720 assert!(word.set_enabled());
721 assert_eq!(word.load(), GcStatus::NotInGC);
722 }
723
724 #[test]
725 fn set_disabled_and_set_enabled_nest_round_trip() {
726 let word = GcStatusWord::new(GcStatus::NotInGC);
727 assert!(word.set_disabled().is_ok());
728 assert!(word.set_disabled().is_ok());
729 assert!(word.set_disabled().is_ok());
730 assert_eq!(word.load(), GcStatus::Disabled(3));
731
732 assert!(!word.set_enabled());
735 assert_eq!(word.load(), GcStatus::Disabled(2));
736 assert!(!word.set_enabled());
737 assert_eq!(word.load(), GcStatus::Disabled(1));
738 assert!(word.set_enabled());
739 assert_eq!(word.load(), GcStatus::NotInGC);
740 }
741
742 #[test]
743 fn set_enabled_is_noop_if_not_disabled() {
744 for status in [
745 GcStatus::Uninitialized,
746 GcStatus::NotInGC,
747 GcStatus::InConcurrentGC,
748 GcStatus::InPause,
749 GcStatus::PauseRequested,
750 ] {
751 let word = GcStatusWord::new(status);
752 assert!(!word.set_enabled());
753 assert_eq!(word.load(), status);
754 }
755 }
756
757 #[test]
758 fn set_in_concurrent_gc_from_in_pause() {
759 let word = GcStatusWord::new(GcStatus::InPause);
760 word.set_in_concurrent_gc();
761 assert_eq!(word.load(), GcStatus::InConcurrentGC);
762 }
763
764 #[test]
765 #[should_panic(expected = "invalid status")]
766 fn set_in_concurrent_gc_panics_if_not_in_pause() {
767 GcStatusWord::new(GcStatus::NotInGC).set_in_concurrent_gc();
768 }
769
770 #[test]
771 fn set_concurrent_gc_finished_from_in_concurrent_gc() {
772 let word = GcStatusWord::new(GcStatus::InConcurrentGC);
773 assert!(word.set_concurrent_gc_finished());
774 assert_eq!(word.load(), GcStatus::NotInGC);
775 }
776
777 #[test]
778 fn set_concurrent_gc_finished_does_not_overwrite_a_requested_pause() {
779 let word = GcStatusWord::new(GcStatus::PauseRequested);
783 assert!(!word.set_concurrent_gc_finished());
784 assert_eq!(word.load(), GcStatus::PauseRequested);
785 }
786
787 #[test]
788 fn set_concurrent_gc_finished_is_a_no_op_outside_a_concurrent_gc() {
789 for status in [
790 GcStatus::NotInGC,
791 GcStatus::InPause,
792 GcStatus::Disabled(1),
793 GcStatus::Uninitialized,
794 ] {
795 let word = GcStatusWord::new(status);
796 assert!(!word.set_concurrent_gc_finished());
797 assert_eq!(word.load(), status);
798 }
799 }
800
801 #[test]
802 fn set_not_in_gc_from_in_pause() {
803 let word = GcStatusWord::new(GcStatus::InPause);
804 word.set_not_in_gc();
805 assert_eq!(word.load(), GcStatus::NotInGC);
806 }
807
808 #[test]
809 #[should_panic(expected = "invalid status")]
810 fn set_not_in_gc_panics_if_not_in_pause() {
811 GcStatusWord::new(GcStatus::InConcurrentGC).set_not_in_gc();
812 }
813
814 #[test]
815 fn is_disabled_reflects_status() {
816 assert!(GcStatusWord::new(GcStatus::Disabled(1)).is_disabled());
817 assert!(!GcStatusWord::new(GcStatus::NotInGC).is_disabled());
818 }
819}
820
821#[derive(Copy, Clone, Debug)]
823pub struct LiveBytesStats {
824 pub live_bytes: usize,
826 pub used_pages: usize,
828 pub used_bytes: usize,
831}