1use std::cell::RefCell;
9use std::sync::atomic::{AtomicBool, Ordering};
10use std::time::Instant;
11
12static ENABLED: AtomicBool = AtomicBool::new(false);
14
15pub fn init() {
21 if std::env::var("SUI_EVAL_PERF").ok().as_deref() == Some("1") {
22 ENABLED.store(true, Ordering::Relaxed);
23 }
24}
25
26pub fn set_enabled(on: bool) {
31 ENABLED.store(on, Ordering::Relaxed);
32}
33
34#[inline(always)]
36pub fn enabled() -> bool {
37 ENABLED.load(Ordering::Relaxed)
38}
39
40#[repr(u8)]
45#[derive(Clone, Copy)]
46pub enum Counter {
47 EvalExpr = 0,
48 ForceValue = 1,
49 ThunkForce = 2,
50 ThunkHit = 3,
51 Import = 4,
52 ImportHit = 5,
53 Apply = 6,
54 Select = 7,
55 Attrset = 8,
56 EnvClone = 9,
57 EnvLookup = 10,
58 EnvLookupDepth = 11,
59 ExprIdent = 12,
61 ExprLiteral = 13,
62 ExprStr = 14,
63 ExprList = 15,
64 ExprAttrs = 16,
65 ExprSelect = 17,
66 ExprApply = 18,
67 ExprLetIn = 19,
68 ExprIfElse = 20,
69 ExprWith = 21,
70 ExprLambda = 22,
71 ExprOther = 23,
72 DeadBindingsSkipped = 24,
74 ExprBinOp = 25,
76 ExprHasAttr = 26,
77 ExprUnaryOp = 27,
78 ExprAssert = 28,
79 ExprPath = 29,
80 OverlayFlattenAttempt = 30,
89 OverlayFlattenBuild = 31,
90 OverlayFlattenEntries = 32,
91 OverlayCreated = 33,
92 SortedEntriesCalls = 34,
97 SortedEntriesRows = 35,
98 ListConcatCalls = 36,
106 ListConcatElemsCopied = 37,
107 ListConcatElemsReused = 38,
108 AttrsEqStructuralCalls = 39,
118 AttrsEqEntriesCloneElided = 40,
119 WithScopeCacheClone = 41,
129 SlashDeferredTailClone = 42,
130 ThunkStoreWrites = 43,
131 ThunkStoreLoopMutated = 44,
138 ThunkStoreRedundant = 45,
143 SelfRecWalkCalls = 46,
157 SelfRecWalkNodes = 47,
158 ThunkSiteMaybeOther = 48,
169 ThunkSiteApplyArg = 49,
170 ThunkSiteMaybeIdent = 50,
171 ThunkSiteLetForward = 51,
172 ThunkSiteOther = 52,
173 ThunkSiteInheritSrc = 53,
177 ThunkSiteNative = 54,
178 ThunkSiteEvaluated = 55,
179}
180
181const NUM_COUNTERS: usize = 56;
182
183const COUNTER_NAMES: [&str; NUM_COUNTERS] = [
185 "eval_expr",
186 "force_value",
187 "thunk_forces",
188 "thunk_hits",
189 "imports",
190 "import_hits",
191 "apply",
192 "select",
193 "attrsets",
194 "env_clones",
195 "env_lookups",
196 "env_lookup_depth",
197 "expr_ident",
198 "expr_literal",
199 "expr_str",
200 "expr_list",
201 "expr_attrs",
202 "expr_select",
203 "expr_apply",
204 "expr_letin",
205 "expr_ifelse",
206 "expr_with",
207 "expr_lambda",
208 "expr_other",
209 "dead_bindings_skipped",
210 "expr_binop",
211 "expr_hasattr",
212 "expr_unaryop",
213 "expr_assert",
214 "expr_path",
215 "overlay_flatten_attempt",
216 "overlay_flatten_build",
217 "overlay_flatten_entries",
218 "overlay_created",
219 "sorted_entries_calls",
220 "sorted_entries_rows",
221 "list_concat_calls",
222 "list_concat_elems_copied",
223 "list_concat_elems_reused",
224 "attrs_eq_structural_calls",
225 "attrs_eq_entries_clone_elided",
226 "with_scope_cache_clone",
227 "slash_deferred_tail_clone",
228 "thunk_store_writes",
229 "thunk_store_loop_mutated",
230 "thunk_store_redundant",
231 "self_rec_walk_calls",
232 "self_rec_walk_nodes",
233 "thunk_site_maybe_other",
234 "thunk_site_apply_arg",
235 "thunk_site_maybe_ident",
236 "thunk_site_let_forward",
237 "thunk_site_other",
238 "thunk_site_inherit_src",
239 "thunk_site_native",
240 "thunk_site_evaluated",
241];
242
243struct PerfCounters {
244 counts: [u64; NUM_COUNTERS],
245}
246
247impl Default for PerfCounters {
248 fn default() -> Self {
249 Self {
250 counts: [0; NUM_COUNTERS],
251 }
252 }
253}
254
255impl PerfCounters {
256 #[inline(always)]
257 fn inc(&mut self, counter: Counter) {
258 self.counts[counter as usize] += 1;
259 }
260
261 #[inline(always)]
262 fn add(&mut self, counter: Counter, n: u64) {
263 self.counts[counter as usize] += n;
264 }
265
266 #[inline(always)]
267 fn get(&self, counter: Counter) -> u64 {
268 self.counts[counter as usize]
269 }
270}
271
272thread_local! {
273 static COUNTERS: RefCell<PerfCounters> = RefCell::new(PerfCounters::default());
274 static START: RefCell<Option<Instant>> = RefCell::new(None);
275}
276
277pub fn start() {
278 if enabled() {
279 START.with(|s| *s.borrow_mut() = Some(Instant::now()));
280 }
281}
282
283const PROGRESS_INTERVAL: u64 = 1_000_000;
285
286#[inline(always)]
287pub fn inc(counter: Counter) {
288 if !enabled() {
289 return;
290 }
291 COUNTERS.with(|c| {
292 let mut c = c.borrow_mut();
293 c.inc(counter);
294 if matches!(counter, Counter::EvalExpr)
296 && c.get(Counter::EvalExpr) % PROGRESS_INTERVAL == 0
297 {
298 let elapsed = START.with(|s| {
299 s.borrow()
300 .map(|s| s.elapsed().as_secs_f64())
301 .unwrap_or(0.0)
302 });
303 eprintln!(
304 "[perf] {:.1}s | eval:{} force:{} thunk_f:{} thunk_h:{} import:{}({}) apply:{} select:{} attrset:{} env_c:{} env_l:{}",
305 elapsed,
306 c.get(Counter::EvalExpr),
307 c.get(Counter::ForceValue),
308 c.get(Counter::ThunkForce),
309 c.get(Counter::ThunkHit),
310 c.get(Counter::Import),
311 c.get(Counter::ImportHit),
312 c.get(Counter::Apply),
313 c.get(Counter::Select),
314 c.get(Counter::Attrset),
315 c.get(Counter::EnvClone),
316 c.get(Counter::EnvLookup),
317 );
318 eprintln!(
320 " [id:{} ap:{} if:{} let:{} sel:{} at:{} w:{} lam:{} lit:{} str:{} list:{} ot:{}]",
321 c.get(Counter::ExprIdent),
322 c.get(Counter::ExprApply),
323 c.get(Counter::ExprIfElse),
324 c.get(Counter::ExprLetIn),
325 c.get(Counter::ExprSelect),
326 c.get(Counter::ExprAttrs),
327 c.get(Counter::ExprWith),
328 c.get(Counter::ExprLambda),
329 c.get(Counter::ExprLiteral),
330 c.get(Counter::ExprStr),
331 c.get(Counter::ExprList),
332 c.get(Counter::ExprOther),
333 );
334 let binop = c.get(Counter::ExprBinOp);
336 let hasattr = c.get(Counter::ExprHasAttr);
337 let unary = c.get(Counter::ExprUnaryOp);
338 let assert = c.get(Counter::ExprAssert);
339 let path = c.get(Counter::ExprPath);
340 if binop + hasattr + unary + assert + path > 0 {
341 eprintln!(
342 " [binop:{binop} hasattr:{hasattr} unary:{unary} assert:{assert} path:{path}]",
343 );
344 }
345 let dead = c.get(Counter::DeadBindingsSkipped);
347 let created = crate::trace::get_thunks_created();
349 let forced = crate::trace::get_thunks_forced();
350 if created > 0 {
351 let waste = (1.0 - forced as f64 / created as f64) * 100.0;
352 eprintln!(" [thunks created:{created} forced:{forced} waste:{waste:.0}% dead_skipped:{dead}]");
353 }
354 crate::eval::dump_force_sites();
356 }
357 });
358}
359
360#[inline(always)]
362pub fn add(counter: Counter, n: u64) {
363 if !enabled() {
364 return;
365 }
366 COUNTERS.with(|c| {
367 c.borrow_mut().add(counter, n);
368 });
369}
370
371pub fn report() {
372 if !enabled() {
373 return;
374 }
375 COUNTERS.with(|c| {
376 let c = c.borrow();
377 let elapsed = START.with(|s| {
378 s.borrow()
379 .map(|s| s.elapsed().as_secs_f64())
380 .unwrap_or(0.0)
381 });
382 let lookups = c.get(Counter::EnvLookup);
383 let depth_total = c.get(Counter::EnvLookupDepth);
384 let avg_lookup = if lookups > 0 {
385 depth_total as f64 / lookups as f64
386 } else {
387 0.0
388 };
389 eprintln!("\n=== sui-eval performance ===");
390 eprintln!("elapsed: {elapsed:.2}s");
391 eprintln!("eval_expr: {}", c.get(Counter::EvalExpr));
392 eprintln!("force_value: {}", c.get(Counter::ForceValue));
393 eprintln!("thunk_forces: {}", c.get(Counter::ThunkForce));
394 eprintln!("thunk_hits: {}", c.get(Counter::ThunkHit));
395 eprintln!(
396 "imports: {} ({} cached)",
397 c.get(Counter::Import),
398 c.get(Counter::ImportHit)
399 );
400 eprintln!("apply: {}", c.get(Counter::Apply));
401 eprintln!("select: {}", c.get(Counter::Select));
402 eprintln!("attrsets: {}", c.get(Counter::Attrset));
403 eprintln!("env_clones: {}", c.get(Counter::EnvClone));
404 eprintln!(
405 "env_lookups: {} (avg depth {avg_lookup:.1})",
406 lookups
407 );
408 let total = c.get(Counter::EvalExpr);
410 if total > 0 {
411 eprintln!("--- expression breakdown ---");
412 for (counter, name) in [
413 (Counter::ExprIdent, "ident"),
414 (Counter::ExprApply, "apply"),
415 (Counter::ExprLetIn, "let-in"),
416 (Counter::ExprIfElse, "if-else"),
417 (Counter::ExprSelect, "select"),
418 (Counter::ExprAttrs, "attrset"),
419 (Counter::ExprWith, "with"),
420 (Counter::ExprLambda, "lambda"),
421 (Counter::ExprLiteral, "literal"),
422 (Counter::ExprStr, "string"),
423 (Counter::ExprList, "list"),
424 (Counter::ExprBinOp, "binop"),
425 (Counter::ExprHasAttr, "hasattr"),
426 (Counter::ExprUnaryOp, "unaryop"),
427 (Counter::ExprAssert, "assert"),
428 (Counter::ExprPath, "path"),
429 (Counter::ExprOther, "other"),
430 ] {
431 let n = c.get(counter);
432 if n > 0 {
433 let pct = (n as f64 / total as f64) * 100.0;
434 eprintln!(" {name:<12} {n:>12} ({pct:.1}%)");
435 }
436 }
437 }
438 let dead = c.get(Counter::DeadBindingsSkipped);
440 if dead > 0 {
441 eprintln!("dead_skipped: {dead}");
442 }
443 let ov_created = c.get(Counter::OverlayCreated);
445 let ov_attempt = c.get(Counter::OverlayFlattenAttempt);
446 let ov_build = c.get(Counter::OverlayFlattenBuild);
447 let ov_entries = c.get(Counter::OverlayFlattenEntries);
448 if ov_created > 0 || ov_attempt > 0 {
449 let hit = ov_attempt.saturating_sub(ov_build);
450 let hit_rate = if ov_attempt > 0 {
451 (hit as f64 / ov_attempt as f64) * 100.0
452 } else {
453 0.0
454 };
455 eprintln!("--- overlay (`//`) flatten ---");
456 eprintln!(" overlays_created: {ov_created}");
457 eprintln!(" flatten_attempts: {ov_attempt}");
458 eprintln!(" flatten_builds: {ov_build} (cache-miss = real O(n) merge)");
459 eprintln!(" cache_hit_rate: {hit_rate:.1}%");
460 eprintln!(" entries_merged: {ov_entries} (sum of left+right over all builds)");
461 if ov_created > 0 {
462 let builds_per_overlay = ov_build as f64 / ov_created as f64;
463 eprintln!(" builds_per_overlay:{builds_per_overlay:.2}");
464 }
465 let flatten_ms = crate::trace::get_overlay_flatten_nanos() as f64 / 1_000_000.0;
466 let pct = if elapsed > 0.0 {
467 (flatten_ms / 1000.0 / elapsed) * 100.0
468 } else {
469 0.0
470 };
471 eprintln!(" flatten_walltime: {flatten_ms:.1}ms ({pct:.1}% of eval, incl. nested)");
472 }
473 let se_calls = c.get(Counter::SortedEntriesCalls);
474 let se_rows = c.get(Counter::SortedEntriesRows);
475 if se_calls > 0 {
476 let se_ms = crate::trace::get_sorted_entries_nanos() as f64 / 1_000_000.0;
477 let se_pct = if elapsed > 0.0 {
478 (se_ms / 1000.0 / elapsed) * 100.0
479 } else {
480 0.0
481 };
482 eprintln!("--- sorted_entries (attrNames/iter) ---");
483 eprintln!(" calls: {se_calls}");
484 eprintln!(" rows_sorted: {se_rows}");
485 eprintln!(" walltime: {se_ms:.1}ms ({se_pct:.1}% of eval)");
486 }
487 let lc_calls = c.get(Counter::ListConcatCalls);
492 let lc_copied = c.get(Counter::ListConcatElemsCopied);
493 let lc_reused = c.get(Counter::ListConcatElemsReused);
494 if lc_calls > 0 {
495 let total = lc_copied + lc_reused;
496 let reuse_pct = if total > 0 {
497 (lc_reused as f64 / total as f64) * 100.0
498 } else {
499 0.0
500 };
501 eprintln!("--- list concat (`++` / concatLists) ---");
502 eprintln!(" calls: {lc_calls}");
503 eprintln!(" elems_copied: {lc_copied}");
504 eprintln!(" elems_reused: {lc_reused} (in-place, Rc uniquely owned)");
505 eprintln!(" reuse_rate: {reuse_pct:.1}%");
506 }
507 let eq_calls = c.get(Counter::AttrsEqStructuralCalls);
509 let eq_elided = c.get(Counter::AttrsEqEntriesCloneElided);
510 if eq_calls > 0 {
511 eprintln!("--- attrs structural eq (`==` fallback) ---");
512 eprintln!(" structural_calls: {eq_calls}");
513 eprintln!(
514 " map_clones_elided: {} ({eq_elided} entries not cloned — was 2 FxHashMap clones/call)",
515 eq_calls * 2
516 );
517 }
518 let wc = c.get(Counter::WithScopeCacheClone);
522 let sc = c.get(Counter::SlashDeferredTailClone);
523 let ts = c.get(Counter::ThunkStoreWrites);
524 if wc + sc + ts > 0 {
525 eprintln!("--- M2 RISKY-tier waste probes ---");
526 eprintln!(" with_scope_cache_clone: {wc} (C-with; O(1) HAMT clone each)");
527 eprintln!(" slash_deferred_tail_clone:{sc} (C-slash; O(1) HAMT clone, then COW-merged)");
528 eprintln!(" thunk_store_writes: {ts} (C-store; repr+cache double-store per force)");
529 let tm = c.get(Counter::ThunkStoreLoopMutated);
530 eprintln!(" thunk_store_loop_mutated: {tm} (C-store; Store#2 content ≠ Store#1 — collapse NOT content-neutral if >0)");
531 let tr = c.get(Counter::ThunkStoreRedundant);
532 eprintln!(" thunk_store_redundant: {tr} (C-store; Store#2 = pure redundant rewrite — provably skippable)");
533 }
534 let sr_calls = c.get(Counter::SelfRecWalkCalls);
538 let sr_nodes = c.get(Counter::SelfRecWalkNodes);
539 if sr_calls > 0 {
540 let nodes_per_call = sr_nodes as f64 / sr_calls as f64;
541 let sr_ms = crate::trace::get_self_rec_walk_nanos() as f64 / 1_000_000.0;
542 let sr_pct = if elapsed > 0.0 {
543 (sr_ms / 1000.0 / elapsed) * 100.0
544 } else {
545 0.0
546 };
547 eprintln!("--- Storm A: referenced_idents (self/mutual-rec walk) ---");
548 eprintln!(" walk_calls: {sr_calls} (= binding RHS subtree walks)");
549 eprintln!(" nodes_walked: {sr_nodes} (total rnix descendants visited)");
550 eprintln!(" nodes_per_call: {nodes_per_call:.1}");
551 eprintln!(" walltime: {sr_ms:.1}ms ({sr_pct:.1}% of eval)");
552 }
553 let s_maybe = c.get(Counter::ThunkSiteMaybeOther);
557 let s_apply = c.get(Counter::ThunkSiteApplyArg);
558 let s_ident = c.get(Counter::ThunkSiteMaybeIdent);
559 let s_recfwd = c.get(Counter::ThunkSiteLetForward);
560 let s_withid = c.get(Counter::ThunkSiteOther);
561 let s_tagged = s_maybe + s_apply + s_ident + s_recfwd + s_withid;
562 let created = crate::trace::get_thunks_created();
563 let s_rest = created.saturating_sub(s_tagged);
566 if s_tagged > 0 {
567 eprintln!("--- thunk-creation site attribution ---");
568 eprintln!(" maybe_thunk `_` arm: {s_maybe}");
569 eprintln!(" apply lambda-arg: {s_apply}");
570 eprintln!(" maybe_thunk ident fb: {s_ident}");
571 eprintln!(" recursive let/rec: {s_recfwd}");
572 eprintln!(" with-ident deferred: {s_withid}");
573 let s_inh = c.get(Counter::ThunkSiteInheritSrc);
574 let s_nat = c.get(Counter::ThunkSiteNative);
575 let s_ev = c.get(Counter::ThunkSiteEvaluated);
576 eprintln!(" inherit-select: {s_inh}");
577 eprintln!(" native (flake input): {s_nat}");
578 eprintln!(" evaluated (pre-done): {s_ev}");
579 let s_rest2 = s_rest.saturating_sub(s_inh + s_nat + s_ev);
580 eprintln!(" rest (select-src/…): {s_rest2}");
581 eprintln!(" thunks_created: {created}");
582 }
583 crate::trace::report_maybe_other_kinds();
584 crate::trace::report_thunk_stats();
586 eprintln!("===========================\n");
587 });
588}
589
590#[allow(dead_code)]
592pub fn counter_name(counter: Counter) -> &'static str {
593 COUNTER_NAMES[counter as usize]
594}
595
596#[derive(Clone, Debug)]
611pub struct PerfSnapshot {
612 pub elapsed: Option<std::time::Duration>,
615 pub counters: [u64; NUM_COUNTERS],
617 pub thunks_created: u64,
619 pub thunks_forced: u64,
621}
622
623impl PerfSnapshot {
624 #[must_use]
626 pub fn zero() -> Self {
627 Self {
628 elapsed: None,
629 counters: [0; NUM_COUNTERS],
630 thunks_created: 0,
631 thunks_forced: 0,
632 }
633 }
634
635 #[must_use]
637 pub fn get(&self, counter: Counter) -> u64 {
638 self.counters[counter as usize]
639 }
640
641 #[must_use]
646 pub fn delta_from(&self, other: &PerfSnapshot) -> PerfSnapshot {
647 let mut counters = [0u64; NUM_COUNTERS];
648 for i in 0..NUM_COUNTERS {
649 counters[i] = self.counters[i].saturating_sub(other.counters[i]);
650 }
651 let elapsed = match (self.elapsed, other.elapsed) {
652 (Some(a), Some(b)) => Some(a.saturating_sub(b)),
653 _ => self.elapsed,
654 };
655 PerfSnapshot {
656 elapsed,
657 counters,
658 thunks_created: self.thunks_created.saturating_sub(other.thunks_created),
659 thunks_forced: self.thunks_forced.saturating_sub(other.thunks_forced),
660 }
661 }
662
663 #[must_use]
667 pub fn thunk_hit_rate(&self) -> Option<f64> {
668 let created = self.thunks_created;
669 if created == 0 {
670 return None;
671 }
672 #[allow(clippy::cast_precision_loss)]
673 let r = self.thunks_forced as f64 / created as f64;
674 Some(r)
675 }
676
677 #[must_use]
681 pub fn dominant_expr_kind(&self) -> Option<(Counter, u64)> {
682 let kinds = [
683 Counter::ExprIdent,
684 Counter::ExprLiteral,
685 Counter::ExprStr,
686 Counter::ExprList,
687 Counter::ExprAttrs,
688 Counter::ExprSelect,
689 Counter::ExprApply,
690 Counter::ExprLetIn,
691 Counter::ExprIfElse,
692 Counter::ExprWith,
693 Counter::ExprLambda,
694 Counter::ExprBinOp,
695 Counter::ExprHasAttr,
696 Counter::ExprUnaryOp,
697 Counter::ExprAssert,
698 Counter::ExprPath,
699 Counter::ExprOther,
700 ];
701 kinds
702 .iter()
703 .map(|&k| (k, self.get(k)))
704 .filter(|&(_, n)| n > 0)
705 .max_by_key(|&(_, n)| n)
706 }
707}
708
709#[must_use]
714pub fn snapshot() -> PerfSnapshot {
715 let mut out = [0u64; NUM_COUNTERS];
716 COUNTERS.with(|c| {
717 let c = c.borrow();
718 out.copy_from_slice(&c.counts);
719 });
720 let elapsed = START.with(|s| s.borrow().map(|s| s.elapsed()));
721 PerfSnapshot {
722 elapsed,
723 counters: out,
724 thunks_created: crate::trace::get_thunks_created(),
725 thunks_forced: crate::trace::get_thunks_forced(),
726 }
727}
728
729pub fn reset() {
732 COUNTERS.with(|c| {
733 let mut c = c.borrow_mut();
734 *c = PerfCounters::default();
735 });
736 START.with(|s| *s.borrow_mut() = Some(Instant::now()));
737 crate::trace::reset_thunk_stats();
738}
739
740pub fn with_scope<F, R>(f: F) -> (R, PerfSnapshot)
747where
748 F: FnOnce() -> R,
749{
750 let prev_enabled = enabled();
751 set_enabled(true);
752 reset();
753 let before = snapshot();
754 let result = f();
755 let after = snapshot();
756 let delta = after.delta_from(&before);
757 set_enabled(prev_enabled);
758 (result, delta)
759}
760
761#[cfg(test)]
762mod tests {
763 use super::*;
764
765 #[test]
766 fn counter_enum_has_30_variants() {
767 assert_eq!(NUM_COUNTERS, 56);
772 assert_eq!(COUNTER_NAMES.len(), NUM_COUNTERS);
773 assert_eq!(Counter::OverlayFlattenAttempt as usize, 30);
774 assert_eq!(Counter::OverlayCreated as usize, 33);
775 assert_eq!(Counter::SortedEntriesCalls as usize, 34);
776 assert_eq!(Counter::SortedEntriesRows as usize, 35);
777 assert_eq!(Counter::ListConcatCalls as usize, 36);
778 assert_eq!(Counter::ListConcatElemsCopied as usize, 37);
779 assert_eq!(Counter::ListConcatElemsReused as usize, 38);
780 assert_eq!(Counter::AttrsEqStructuralCalls as usize, 39);
781 assert_eq!(Counter::AttrsEqEntriesCloneElided as usize, 40);
782 assert_eq!(Counter::WithScopeCacheClone as usize, 41);
783 assert_eq!(Counter::SlashDeferredTailClone as usize, 42);
784 assert_eq!(Counter::ThunkStoreWrites as usize, 43);
785 assert_eq!(Counter::ThunkStoreLoopMutated as usize, 44);
786 assert_eq!(Counter::ThunkStoreRedundant as usize, 45);
787 assert_eq!(Counter::SelfRecWalkCalls as usize, 46);
788 assert_eq!(Counter::SelfRecWalkNodes as usize, 47);
789 assert_eq!(Counter::ThunkSiteMaybeOther as usize, 48);
790 assert_eq!(Counter::ThunkSiteApplyArg as usize, 49);
791 assert_eq!(Counter::ThunkSiteMaybeIdent as usize, 50);
792 assert_eq!(Counter::ThunkSiteLetForward as usize, 51);
793 assert_eq!(Counter::ThunkSiteOther as usize, 52);
794 assert_eq!(Counter::ThunkSiteInheritSrc as usize, 53);
795 assert_eq!(Counter::ThunkSiteNative as usize, 54);
796 assert_eq!(Counter::ThunkSiteEvaluated as usize, 55);
797 assert_eq!(Counter::EvalExpr as usize, 0);
798 assert_eq!(Counter::ForceValue as usize, 1);
799 assert_eq!(Counter::ThunkForce as usize, 2);
800 assert_eq!(Counter::ThunkHit as usize, 3);
801 assert_eq!(Counter::Import as usize, 4);
802 assert_eq!(Counter::ImportHit as usize, 5);
803 assert_eq!(Counter::Apply as usize, 6);
804 assert_eq!(Counter::Select as usize, 7);
805 assert_eq!(Counter::Attrset as usize, 8);
806 assert_eq!(Counter::EnvClone as usize, 9);
807 assert_eq!(Counter::EnvLookup as usize, 10);
808 assert_eq!(Counter::EnvLookupDepth as usize, 11);
809 assert_eq!(Counter::DeadBindingsSkipped as usize, 24);
810 assert_eq!(Counter::ExprBinOp as usize, 25);
811 assert_eq!(Counter::ExprPath as usize, 29);
812 }
813
814 #[test]
815 fn inc_does_not_panic_when_disabled() {
816 ENABLED.store(false, Ordering::Relaxed);
818 inc(Counter::EvalExpr);
820 inc(Counter::ForceValue);
821 inc(Counter::ThunkForce);
822 }
823
824 #[test]
825 fn counter_variant_maps_to_correct_index() {
826 assert_eq!(counter_name(Counter::EvalExpr), "eval_expr");
827 assert_eq!(counter_name(Counter::ForceValue), "force_value");
828 assert_eq!(counter_name(Counter::ThunkForce), "thunk_forces");
829 assert_eq!(counter_name(Counter::ThunkHit), "thunk_hits");
830 assert_eq!(counter_name(Counter::Import), "imports");
831 assert_eq!(counter_name(Counter::ImportHit), "import_hits");
832 assert_eq!(counter_name(Counter::Apply), "apply");
833 assert_eq!(counter_name(Counter::Select), "select");
834 assert_eq!(counter_name(Counter::Attrset), "attrsets");
835 assert_eq!(counter_name(Counter::EnvClone), "env_clones");
836 assert_eq!(counter_name(Counter::EnvLookup), "env_lookups");
837 assert_eq!(counter_name(Counter::EnvLookupDepth), "env_lookup_depth");
838 }
839
840 #[test]
841 fn add_increments_by_given_amount() {
842 let mut counters = PerfCounters::default();
843 assert_eq!(counters.get(Counter::EvalExpr), 0);
844 counters.add(Counter::EvalExpr, 5);
845 assert_eq!(counters.get(Counter::EvalExpr), 5);
846 counters.add(Counter::EvalExpr, 3);
847 assert_eq!(counters.get(Counter::EvalExpr), 8);
848 }
849}