1use std::time::{Duration, Instant};
51
52pub const PLANK_TIME_NS: u64 = 1024;
58
59pub const PLANK_SPACE_BYTES: usize = 16;
61
62pub trait ZombieOps {
86 #[inline]
90 fn stack_size() -> usize
91 where
92 Self: Sized,
93 {
94 std::mem::size_of::<Self>()
95 }
96
97 #[inline]
103 fn heap_size(&self) -> usize {
104 0
105 }
106
107 #[inline]
111 fn get_size(&self) -> usize
112 where
113 Self: Sized,
114 {
115 Self::stack_size() + self.heap_size()
116 }
117}
118
119#[cfg(feature = "derive")]
121pub use zombie_derive::ZombieOps;
122
123macro_rules! impl_zombie_ops_primitive {
128 ($($t:ty),*) => {
129 $(
130 impl ZombieOps for $t {}
131 )*
132 };
133}
134
135impl_zombie_ops_primitive!(
136 u8, u16, u32, u64, u128, usize,
137 i8, i16, i32, i64, i128, isize,
138 f32, f64, bool, char, ()
139);
140
141impl<T: ?Sized> ZombieOps for &T {}
143
144impl<T> ZombieOps for Vec<T> {
150 #[inline]
151 fn heap_size(&self) -> usize {
152 self.capacity() * std::mem::size_of::<T>()
153 }
154}
155
156impl ZombieOps for String {
158 #[inline]
159 fn heap_size(&self) -> usize {
160 self.capacity()
161 }
162}
163
164impl<T: ZombieOps> ZombieOps for Box<T> {
166 #[inline]
167 fn heap_size(&self) -> usize {
168 T::stack_size() + self.as_ref().heap_size()
169 }
170}
171
172impl<T: ?Sized> ZombieOps for std::rc::Rc<T> {}
174
175impl<T: ?Sized> ZombieOps for std::sync::Arc<T> {}
177
178impl<T: ZombieOps> ZombieOps for Option<T> {
180 #[inline]
181 fn heap_size(&self) -> usize {
182 match self {
183 Some(v) => v.heap_size(),
184 None => 0,
185 }
186 }
187}
188
189impl<T: ZombieOps, E: ZombieOps> ZombieOps for Result<T, E> {
191 #[inline]
192 fn heap_size(&self) -> usize {
193 match self {
194 Ok(v) => v.heap_size(),
195 Err(e) => e.heap_size(),
196 }
197 }
198}
199
200impl<A: ZombieOps, B: ZombieOps> ZombieOps for (A, B) {
205 #[inline]
206 fn heap_size(&self) -> usize {
207 self.0.heap_size() + self.1.heap_size()
208 }
209}
210
211impl<A: ZombieOps, B: ZombieOps, C: ZombieOps> ZombieOps for (A, B, C) {
212 #[inline]
213 fn heap_size(&self) -> usize {
214 self.0.heap_size() + self.1.heap_size() + self.2.heap_size()
215 }
216}
217
218impl<A: ZombieOps, B: ZombieOps, C: ZombieOps, D: ZombieOps> ZombieOps for (A, B, C, D) {
219 #[inline]
220 fn heap_size(&self) -> usize {
221 self.0.heap_size() + self.1.heap_size() + self.2.heap_size() + self.3.heap_size()
222 }
223}
224
225impl<T: ZombieOps, const N: usize> ZombieOps for [T; N] {
230 #[inline]
231 fn heap_size(&self) -> usize {
232 self.iter().map(|e| e.heap_size()).sum()
234 }
235}
236
237#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
248pub struct Time(u64);
249
250impl Time {
251 pub const ZERO: Self = Time(0);
252
253 #[inline]
254 pub fn from_duration(d: Duration) -> Self {
255 Time(d.as_nanos() as u64)
256 }
257
258 #[inline]
259 pub const fn from_nanos(nanos: u64) -> Self {
260 Time(nanos)
261 }
262
263 #[inline]
265 pub fn plank(&self) -> u64 {
266 if self.0 == 0 {
267 0
268 } else {
269 (self.0 / PLANK_TIME_NS).max(1)
270 }
271 }
272
273 #[inline]
274 pub fn as_duration(&self) -> Duration {
275 Duration::from_nanos(self.0)
276 }
277
278 #[inline]
279 pub const fn as_nanos(&self) -> u64 {
280 self.0
281 }
282}
283
284impl std::ops::Add for Time {
285 type Output = Self;
286 #[inline]
287 fn add(self, rhs: Self) -> Self {
288 Time(self.0.saturating_add(rhs.0))
289 }
290}
291
292impl std::ops::Sub for Time {
293 type Output = Self;
294 #[inline]
295 fn sub(self, rhs: Self) -> Self {
296 Time(self.0.saturating_sub(rhs.0))
297 }
298}
299
300#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
302pub struct Space(usize);
303
304impl Space {
305 pub const ZERO: Self = Space(0);
306
307 #[inline]
308 pub const fn from_bytes(bytes: usize) -> Self {
309 Space(bytes)
310 }
311
312 #[inline]
314 pub fn plank(&self) -> u64 {
315 ((self.0 / PLANK_SPACE_BYTES).max(1)) as u64
316 }
317
318 #[inline]
319 pub const fn as_bytes(&self) -> usize {
320 self.0
321 }
322}
323
324impl std::ops::Add for Space {
325 type Output = Self;
326 #[inline]
327 fn add(self, rhs: Self) -> Self {
328 Space(self.0 + rhs.0)
329 }
330}
331
332impl std::ops::AddAssign for Space {
333 #[inline]
334 fn add_assign(&mut self, rhs: Self) {
335 self.0 += rhs.0;
336 }
337}
338
339impl std::ops::Sub for Space {
340 type Output = Self;
341 #[inline]
342 fn sub(self, rhs: Self) -> Self {
343 Space(self.0.saturating_sub(rhs.0))
344 }
345}
346
347impl std::ops::SubAssign for Space {
348 #[inline]
349 fn sub_assign(&mut self, rhs: Self) {
350 self.0 = self.0.saturating_sub(rhs.0);
351 }
352}
353
354pub struct ZombieMeter {
363 start: Instant,
364 skipped: Duration,
365 skip_stack: Vec<Duration>,
366}
367
368impl ZombieMeter {
369 pub fn new() -> Self {
370 Self {
371 start: Instant::now(),
372 skipped: Duration::ZERO,
373 skip_stack: Vec::new(),
374 }
375 }
376
377 #[inline]
379 pub fn raw_time(&self) -> Duration {
380 self.start.elapsed()
381 }
382
383 #[inline]
385 pub fn time(&self) -> Time {
386 Time::from_duration(self.raw_time().saturating_sub(self.skipped))
387 }
388
389 #[inline]
391 pub fn enter_skip_block(&mut self) {
392 self.skip_stack.push(self.raw_time());
393 }
394
395 #[inline]
397 pub fn exit_skip_block(&mut self) {
398 if let Some(start) = self.skip_stack.pop() {
399 let elapsed = self.raw_time().saturating_sub(start);
400 self.skipped += elapsed;
401 }
402 }
403
404 #[inline]
406 pub fn in_skip_block<T, F: FnOnce() -> T>(&mut self, f: F) -> T {
407 self.enter_skip_block();
408 let result = f();
409 self.exit_skip_block();
410 result
411 }
412}
413
414impl Default for ZombieMeter {
415 fn default() -> Self {
416 Self::new()
417 }
418}
419
420#[cfg(test)]
425mod tests {
426 use super::*;
427
428 #[test]
429 fn test_time_plank() {
430 assert_eq!(Time::ZERO.plank(), 0);
431 assert_eq!(Time::from_nanos(1).plank(), 1);
432 assert_eq!(Time::from_nanos(1024).plank(), 1);
433 assert_eq!(Time::from_nanos(2048).plank(), 2);
434 }
435
436 #[test]
437 fn test_space_plank() {
438 assert_eq!(Space::from_bytes(0).plank(), 1);
439 assert_eq!(Space::from_bytes(1).plank(), 1);
440 assert_eq!(Space::from_bytes(16).plank(), 1);
441 assert_eq!(Space::from_bytes(32).plank(), 2);
442 }
443
444 #[test]
445 fn test_zombie_ops_primitives() {
446 assert_eq!(42i32.get_size(), 4);
447 assert_eq!(1.5f64.get_size(), 8);
448 assert_eq!(true.get_size(), 1);
449 assert_eq!(42i32.heap_size(), 0);
451 }
452
453 #[test]
454 fn test_zombie_ops_vec() {
455 let v: Vec<u8> = vec![1, 2, 3];
456 assert_eq!(v.get_size(), Vec::<u8>::stack_size() + v.capacity());
458 assert_eq!(v.heap_size(), v.capacity());
459 }
460
461 #[test]
462 fn test_zombie_ops_string() {
463 let s = String::from("hello");
464 assert!(s.get_size() >= String::stack_size() + 5);
465 assert_eq!(s.heap_size(), s.capacity());
466 }
467
468 #[test]
469 fn test_zombie_ops_option() {
470 let none: Option<Vec<u8>> = None;
471 let some: Option<Vec<u8>> = Some(vec![1, 2, 3]);
472
473 assert_eq!(none.heap_size(), 0);
475
476 assert_eq!(some.heap_size(), some.as_ref().unwrap().capacity());
478 }
479
480 #[test]
481 fn test_zombie_ops_result() {
482 let ok: Result<Vec<u8>, String> = Ok(vec![1, 2, 3]);
483 let err: Result<Vec<u8>, String> = Err(String::from("error"));
484
485 assert_eq!(ok.heap_size(), ok.as_ref().unwrap().capacity());
487
488 assert_eq!(err.heap_size(), err.as_ref().unwrap_err().capacity());
490 }
491
492 #[test]
493 fn test_zombie_ops_tuple() {
494 let t = (vec![1u8, 2, 3], String::from("test"));
495 let expected_heap = t.0.capacity() + t.1.capacity();
496 assert_eq!(t.heap_size(), expected_heap);
497 }
498
499 #[test]
500 fn test_zombie_ops_box() {
501 let b: Box<Vec<u8>> = Box::new(vec![1, 2, 3]);
502 let expected = Vec::<u8>::stack_size() + b.capacity();
504 assert_eq!(b.heap_size(), expected);
505 }
506
507 #[test]
508 fn test_zombie_ops_array() {
509 let arr: [i32; 4] = [1, 2, 3, 4];
511 assert_eq!(arr.heap_size(), 0);
512 assert_eq!(arr.get_size(), std::mem::size_of::<[i32; 4]>());
513 }
514
515 #[derive(Clone)]
517 struct TestStruct {
518 data: Vec<u8>,
519 name: String,
520 }
521
522 impl ZombieOps for TestStruct {
523 fn heap_size(&self) -> usize {
524 self.data.heap_size() + self.name.heap_size()
525 }
526 }
527
528 #[test]
529 fn test_zombie_ops_manual_impl() {
530 let s = TestStruct {
531 data: vec![0; 10],
532 name: String::from("test"),
533 };
534 let expected_heap = s.data.capacity() + s.name.capacity();
535 assert_eq!(s.heap_size(), expected_heap);
536 assert_eq!(
537 s.get_size(),
538 TestStruct::stack_size() + expected_heap
539 );
540 }
541
542 #[test]
543 fn test_zombie_ops_simple_struct() {
544 #[derive(Clone)]
545 struct SimpleStruct {
546 _x: usize,
547 }
548
549 impl ZombieOps for SimpleStruct {}
550
551 let s = SimpleStruct { _x: 100 };
552 assert_eq!(s.heap_size(), 0);
553 assert_eq!(s.get_size(), SimpleStruct::stack_size());
554 }
555}