1use std::cell::Cell;
16use std::ffi::OsStr;
17use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
18
19pub mod alloc;
20pub mod process;
21
22static ENABLED: AtomicBool = AtomicBool::new(false);
23
24#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
30#[non_exhaustive]
31pub struct Counts {
32 pub dir_opens: u64,
34 pub dir_enumeration_calls: u64,
42 pub dir_entries: u64,
44 pub stats: u64,
50 pub file_opens: u64,
52 pub file_reads: u64,
54 pub bytes_read: u64,
56 pub allocs: u64,
58 pub reallocs: u64,
60 pub frees: u64,
62 pub bytes_allocated: u64,
64 pub upserts: u64,
66 pub parent_resolutions: u64,
68 pub parent_memo_hits: u64,
70 pub rollup_merges: u64,
72 pub control_reads: u64,
74 pub control_refused: u64,
81 pub control_sources_shared: u64,
83 pub ignore_patterns_tested: u64,
86 pub ignore_bucket_probes: u64,
90 pub ignore_bucket_hits: u64,
92 pub entries_allocated: u64,
94 pub baseline_batches: u64,
96 pub baseline_accepted_ops: u64,
98 pub opened_batches: u64,
100 pub opened_accepted_ops: u64,
102 pub public_batches: u64,
104 pub public_accepted_ops: u64,
106 pub ancestry_overlay_inserts: u64,
108 pub ancestry_path_comparisons: u64,
110 pub ancestry_parent_proofs: u64,
112 pub scanner_prepare_us: u64,
114 pub scanner_control_projection_us: u64,
116 pub scanner_reduce_us: u64,
118 pub content_sidecar_read_us: u64,
120 pub content_sidecar_parse_us: u64,
122 pub content_sidecar_candidates_us: u64,
124 pub content_sidecar_apply_us: u64,
126 pub detached_builds: u64,
128 pub detached_entries: u64,
130 pub detached_walk_us: u64,
132 pub detached_finish_us: u64,
134 pub effect_paths: u64,
136 pub effect_path_bytes: u64,
138 pub impact_candidates: u64,
140 pub impact_ancestor_visits: u64,
142 pub impact_retained_dirty_paths: u64,
144 pub impact_all_dirty: u64,
146 pub journal_retained_commits: u64,
148 pub journal_cloned_commits: u64,
150 pub journal_oversized_commits: u64,
152 pub journal_dropped_commits: u64,
154 pub adaptive_calibration_chunks: u64,
156 pub adaptive_calibration_entries: u64,
158 pub adaptive_calibration_work_us: u64,
164 pub adaptive_scale_ups: u64,
166 pub adaptive_policy_undecided: u64,
173}
174
175impl Counts {
176 const ZERO: Self = Self {
177 dir_opens: 0,
178 dir_enumeration_calls: 0,
179 dir_entries: 0,
180 stats: 0,
181 file_opens: 0,
182 file_reads: 0,
183 bytes_read: 0,
184 allocs: 0,
185 reallocs: 0,
186 frees: 0,
187 bytes_allocated: 0,
188 upserts: 0,
189 parent_resolutions: 0,
190 parent_memo_hits: 0,
191 rollup_merges: 0,
192 control_reads: 0,
193 control_refused: 0,
194 control_sources_shared: 0,
195 ignore_patterns_tested: 0,
196 ignore_bucket_probes: 0,
197 ignore_bucket_hits: 0,
198 entries_allocated: 0,
199 baseline_batches: 0,
200 baseline_accepted_ops: 0,
201 opened_batches: 0,
202 opened_accepted_ops: 0,
203 public_batches: 0,
204 public_accepted_ops: 0,
205 ancestry_overlay_inserts: 0,
206 ancestry_path_comparisons: 0,
207 ancestry_parent_proofs: 0,
208 scanner_prepare_us: 0,
209 scanner_control_projection_us: 0,
210 scanner_reduce_us: 0,
211 content_sidecar_read_us: 0,
212 content_sidecar_parse_us: 0,
213 content_sidecar_candidates_us: 0,
214 content_sidecar_apply_us: 0,
215 detached_builds: 0,
216 detached_entries: 0,
217 detached_walk_us: 0,
218 detached_finish_us: 0,
219 effect_paths: 0,
220 effect_path_bytes: 0,
221 impact_candidates: 0,
222 impact_ancestor_visits: 0,
223 impact_retained_dirty_paths: 0,
224 impact_all_dirty: 0,
225 journal_retained_commits: 0,
226 journal_cloned_commits: 0,
227 journal_oversized_commits: 0,
228 journal_dropped_commits: 0,
229 adaptive_calibration_chunks: 0,
230 adaptive_calibration_entries: 0,
231 adaptive_calibration_work_us: 0,
232 adaptive_scale_ups: 0,
233 adaptive_policy_undecided: 0,
234 };
235
236 #[must_use]
238 pub fn rows(&self) -> Vec<(&'static str, &'static str, u64)> {
239 vec![
240 ("filesystem operations", "directory opens", self.dir_opens),
241 ("filesystem operations", "directory enumeration calls", self.dir_enumeration_calls),
242 ("filesystem operations", "directory entries enumerated", self.dir_entries),
243 ("filesystem operations", "metadata stats", self.stats),
244 ("filesystem operations", "file opens", self.file_opens),
245 ("filesystem operations", "file read calls", self.file_reads),
246 ("filesystem operations", "bytes read from files", self.bytes_read),
247 ("memory", "allocations", self.allocs),
248 ("memory", "reallocations", self.reallocs),
249 ("memory", "frees", self.frees),
250 ("memory", "bytes allocated", self.bytes_allocated),
251 ("index", "upserts applied", self.upserts),
252 ("index", "parent path resolutions", self.parent_resolutions),
253 ("index", "parent memo hits", self.parent_memo_hits),
254 ("index", "roll-up merges", self.rollup_merges),
255 ("control state", "control files read", self.control_reads),
256 ("control state", "control sources refused", self.control_refused),
257 ("control state", "control sources shared", self.control_sources_shared),
258 ("control state", "ignore patterns tested", self.ignore_patterns_tested),
259 ("control state", "ignore bucket probes", self.ignore_bucket_probes),
260 ("control state", "ignore bucket hits", self.ignore_bucket_hits),
261 ("index", "index entries allocated", self.entries_allocated),
262 ("mutation provenance", "baseline batches", self.baseline_batches),
263 ("mutation provenance", "baseline accepted ops", self.baseline_accepted_ops),
264 ("mutation provenance", "opened batches", self.opened_batches),
265 ("mutation provenance", "opened accepted ops", self.opened_accepted_ops),
266 ("mutation provenance", "public batches", self.public_batches),
267 ("mutation provenance", "public accepted ops", self.public_accepted_ops),
268 ("mutation preflight", "ancestry overlay inserts", self.ancestry_overlay_inserts),
269 ("mutation preflight", "same-parent path comparisons", self.ancestry_path_comparisons),
270 ("mutation preflight", "parent chains proved", self.ancestry_parent_proofs),
271 ("mutation timing", "scanner preparation microseconds", self.scanner_prepare_us),
272 (
273 "mutation timing",
274 "scanner control projection microseconds",
275 self.scanner_control_projection_us,
276 ),
277 ("mutation timing", "scanner reduction microseconds", self.scanner_reduce_us),
278 (
279 "content sidecar timing",
280 "sidecar image read microseconds",
281 self.content_sidecar_read_us,
282 ),
283 ("content sidecar timing", "sidecar parse microseconds", self.content_sidecar_parse_us),
284 (
285 "content sidecar timing",
286 "sidecar candidate-map microseconds",
287 self.content_sidecar_candidates_us,
288 ),
289 ("content sidecar timing", "sidecar apply microseconds", self.content_sidecar_apply_us),
290 ("detached builder", "builds", self.detached_builds),
291 ("detached builder", "entries", self.detached_entries),
292 ("detached builder", "walk microseconds", self.detached_walk_us),
293 ("detached builder", "finish microseconds", self.detached_finish_us),
294 ("mutation consequences", "effective paths retained", self.effect_paths),
295 ("mutation consequences", "effective path bytes", self.effect_path_bytes),
296 ("mutation consequences", "impact candidates", self.impact_candidates),
297 ("mutation consequences", "impact ancestor visits", self.impact_ancestor_visits),
298 (
299 "mutation consequences",
300 "impact dirty paths retained",
301 self.impact_retained_dirty_paths,
302 ),
303 ("mutation consequences", "impact all-dirty transitions", self.impact_all_dirty),
304 ("mutation journal", "commits retained", self.journal_retained_commits),
305 ("mutation journal", "commits cloned", self.journal_cloned_commits),
306 ("mutation journal", "oversized commits", self.journal_oversized_commits),
307 ("mutation journal", "older commits dropped", self.journal_dropped_commits),
308 ("adaptive scan policy", "calibration chunks", self.adaptive_calibration_chunks),
309 ("adaptive scan policy", "calibration entries", self.adaptive_calibration_entries),
310 (
311 "adaptive scan policy",
312 "calibration worker microseconds",
313 self.adaptive_calibration_work_us,
314 ),
315 ("adaptive scan policy", "reserve expansions", self.adaptive_scale_ups),
316 ("adaptive scan policy", "walks left undecided", self.adaptive_policy_undecided),
317 ]
318 }
319
320 #[must_use]
322 pub fn is_empty(&self) -> bool {
323 *self == Self::default()
324 }
325
326 fn add(&mut self, other: &Self) {
328 self.dir_opens = self.dir_opens.saturating_add(other.dir_opens);
329 self.dir_enumeration_calls =
330 self.dir_enumeration_calls.saturating_add(other.dir_enumeration_calls);
331 self.dir_entries = self.dir_entries.saturating_add(other.dir_entries);
332 self.stats = self.stats.saturating_add(other.stats);
333 self.file_opens = self.file_opens.saturating_add(other.file_opens);
334 self.file_reads = self.file_reads.saturating_add(other.file_reads);
335 self.bytes_read = self.bytes_read.saturating_add(other.bytes_read);
336 self.allocs = self.allocs.saturating_add(other.allocs);
337 self.reallocs = self.reallocs.saturating_add(other.reallocs);
338 self.frees = self.frees.saturating_add(other.frees);
339 self.bytes_allocated = self.bytes_allocated.saturating_add(other.bytes_allocated);
340 self.upserts = self.upserts.saturating_add(other.upserts);
341 self.parent_resolutions = self.parent_resolutions.saturating_add(other.parent_resolutions);
342 self.parent_memo_hits = self.parent_memo_hits.saturating_add(other.parent_memo_hits);
343 self.rollup_merges = self.rollup_merges.saturating_add(other.rollup_merges);
344 self.control_reads = self.control_reads.saturating_add(other.control_reads);
345 self.control_refused = self.control_refused.saturating_add(other.control_refused);
346 self.control_sources_shared =
347 self.control_sources_shared.saturating_add(other.control_sources_shared);
348 self.ignore_patterns_tested =
349 self.ignore_patterns_tested.saturating_add(other.ignore_patterns_tested);
350 self.ignore_bucket_probes =
351 self.ignore_bucket_probes.saturating_add(other.ignore_bucket_probes);
352 self.ignore_bucket_hits = self.ignore_bucket_hits.saturating_add(other.ignore_bucket_hits);
353 self.entries_allocated = self.entries_allocated.saturating_add(other.entries_allocated);
354 self.baseline_batches = self.baseline_batches.saturating_add(other.baseline_batches);
355 self.baseline_accepted_ops =
356 self.baseline_accepted_ops.saturating_add(other.baseline_accepted_ops);
357 self.opened_batches = self.opened_batches.saturating_add(other.opened_batches);
358 self.opened_accepted_ops =
359 self.opened_accepted_ops.saturating_add(other.opened_accepted_ops);
360 self.public_batches = self.public_batches.saturating_add(other.public_batches);
361 self.public_accepted_ops =
362 self.public_accepted_ops.saturating_add(other.public_accepted_ops);
363 self.ancestry_overlay_inserts =
364 self.ancestry_overlay_inserts.saturating_add(other.ancestry_overlay_inserts);
365 self.ancestry_path_comparisons =
366 self.ancestry_path_comparisons.saturating_add(other.ancestry_path_comparisons);
367 self.ancestry_parent_proofs =
368 self.ancestry_parent_proofs.saturating_add(other.ancestry_parent_proofs);
369 self.scanner_prepare_us = self.scanner_prepare_us.saturating_add(other.scanner_prepare_us);
370 self.scanner_control_projection_us =
371 self.scanner_control_projection_us.saturating_add(other.scanner_control_projection_us);
372 self.scanner_reduce_us = self.scanner_reduce_us.saturating_add(other.scanner_reduce_us);
373 self.content_sidecar_read_us =
374 self.content_sidecar_read_us.saturating_add(other.content_sidecar_read_us);
375 self.content_sidecar_parse_us =
376 self.content_sidecar_parse_us.saturating_add(other.content_sidecar_parse_us);
377 self.content_sidecar_candidates_us =
378 self.content_sidecar_candidates_us.saturating_add(other.content_sidecar_candidates_us);
379 self.content_sidecar_apply_us =
380 self.content_sidecar_apply_us.saturating_add(other.content_sidecar_apply_us);
381 self.detached_builds = self.detached_builds.saturating_add(other.detached_builds);
382 self.detached_entries = self.detached_entries.saturating_add(other.detached_entries);
383 self.detached_walk_us = self.detached_walk_us.saturating_add(other.detached_walk_us);
384 self.detached_finish_us = self.detached_finish_us.saturating_add(other.detached_finish_us);
385 self.effect_paths = self.effect_paths.saturating_add(other.effect_paths);
386 self.effect_path_bytes = self.effect_path_bytes.saturating_add(other.effect_path_bytes);
387 self.impact_candidates = self.impact_candidates.saturating_add(other.impact_candidates);
388 self.impact_ancestor_visits =
389 self.impact_ancestor_visits.saturating_add(other.impact_ancestor_visits);
390 self.impact_retained_dirty_paths =
391 self.impact_retained_dirty_paths.saturating_add(other.impact_retained_dirty_paths);
392 self.impact_all_dirty = self.impact_all_dirty.saturating_add(other.impact_all_dirty);
393 self.journal_retained_commits =
394 self.journal_retained_commits.saturating_add(other.journal_retained_commits);
395 self.journal_cloned_commits =
396 self.journal_cloned_commits.saturating_add(other.journal_cloned_commits);
397 self.journal_oversized_commits =
398 self.journal_oversized_commits.saturating_add(other.journal_oversized_commits);
399 self.journal_dropped_commits =
400 self.journal_dropped_commits.saturating_add(other.journal_dropped_commits);
401 self.adaptive_calibration_chunks =
402 self.adaptive_calibration_chunks.saturating_add(other.adaptive_calibration_chunks);
403 self.adaptive_calibration_entries =
404 self.adaptive_calibration_entries.saturating_add(other.adaptive_calibration_entries);
405 self.adaptive_calibration_work_us =
406 self.adaptive_calibration_work_us.saturating_add(other.adaptive_calibration_work_us);
407 self.adaptive_scale_ups = self.adaptive_scale_ups.saturating_add(other.adaptive_scale_ups);
408 self.adaptive_policy_undecided =
409 self.adaptive_policy_undecided.saturating_add(other.adaptive_policy_undecided);
410 }
411
412 #[must_use]
414 pub fn per(&self, denominator: u64) -> Vec<(&'static str, &'static str, f64)> {
415 self.rows()
416 .into_iter()
417 .map(|(group, label, value)| (group, label, ratio(value, denominator)))
418 .collect()
419 }
420}
421
422struct GlobalCounts {
423 dir_opens: AtomicU64,
424 dir_enumeration_calls: AtomicU64,
425 dir_entries: AtomicU64,
426 stats: AtomicU64,
427 file_opens: AtomicU64,
428 file_reads: AtomicU64,
429 bytes_read: AtomicU64,
430 allocs: AtomicU64,
431 reallocs: AtomicU64,
432 frees: AtomicU64,
433 bytes_allocated: AtomicU64,
434 upserts: AtomicU64,
435 parent_resolutions: AtomicU64,
436 parent_memo_hits: AtomicU64,
437 rollup_merges: AtomicU64,
438 control_reads: AtomicU64,
439 control_refused: AtomicU64,
440 control_sources_shared: AtomicU64,
441 ignore_patterns_tested: AtomicU64,
442 ignore_bucket_probes: AtomicU64,
443 ignore_bucket_hits: AtomicU64,
444 entries_allocated: AtomicU64,
445 baseline_batches: AtomicU64,
446 baseline_accepted_ops: AtomicU64,
447 opened_batches: AtomicU64,
448 opened_accepted_ops: AtomicU64,
449 public_batches: AtomicU64,
450 public_accepted_ops: AtomicU64,
451 ancestry_overlay_inserts: AtomicU64,
452 ancestry_path_comparisons: AtomicU64,
453 ancestry_parent_proofs: AtomicU64,
454 scanner_prepare_us: AtomicU64,
455 scanner_control_projection_us: AtomicU64,
456 scanner_reduce_us: AtomicU64,
457 content_sidecar_read_us: AtomicU64,
458 content_sidecar_parse_us: AtomicU64,
459 content_sidecar_candidates_us: AtomicU64,
460 content_sidecar_apply_us: AtomicU64,
461 detached_builds: AtomicU64,
462 detached_entries: AtomicU64,
463 detached_walk_us: AtomicU64,
464 detached_finish_us: AtomicU64,
465 effect_paths: AtomicU64,
466 effect_path_bytes: AtomicU64,
467 impact_candidates: AtomicU64,
468 impact_ancestor_visits: AtomicU64,
469 impact_retained_dirty_paths: AtomicU64,
470 impact_all_dirty: AtomicU64,
471 journal_retained_commits: AtomicU64,
472 journal_cloned_commits: AtomicU64,
473 journal_oversized_commits: AtomicU64,
474 journal_dropped_commits: AtomicU64,
475 adaptive_calibration_chunks: AtomicU64,
476 adaptive_calibration_entries: AtomicU64,
477 adaptive_calibration_work_us: AtomicU64,
478 adaptive_scale_ups: AtomicU64,
479 adaptive_policy_undecided: AtomicU64,
480}
481
482impl GlobalCounts {
483 const fn new() -> Self {
484 Self {
485 dir_opens: AtomicU64::new(0),
486 dir_enumeration_calls: AtomicU64::new(0),
487 dir_entries: AtomicU64::new(0),
488 stats: AtomicU64::new(0),
489 file_opens: AtomicU64::new(0),
490 file_reads: AtomicU64::new(0),
491 bytes_read: AtomicU64::new(0),
492 allocs: AtomicU64::new(0),
493 reallocs: AtomicU64::new(0),
494 frees: AtomicU64::new(0),
495 bytes_allocated: AtomicU64::new(0),
496 upserts: AtomicU64::new(0),
497 parent_resolutions: AtomicU64::new(0),
498 parent_memo_hits: AtomicU64::new(0),
499 rollup_merges: AtomicU64::new(0),
500 control_reads: AtomicU64::new(0),
501 control_refused: AtomicU64::new(0),
502 control_sources_shared: AtomicU64::new(0),
503 ignore_patterns_tested: AtomicU64::new(0),
504 ignore_bucket_probes: AtomicU64::new(0),
505 ignore_bucket_hits: AtomicU64::new(0),
506 entries_allocated: AtomicU64::new(0),
507 baseline_batches: AtomicU64::new(0),
508 baseline_accepted_ops: AtomicU64::new(0),
509 opened_batches: AtomicU64::new(0),
510 opened_accepted_ops: AtomicU64::new(0),
511 public_batches: AtomicU64::new(0),
512 public_accepted_ops: AtomicU64::new(0),
513 ancestry_overlay_inserts: AtomicU64::new(0),
514 ancestry_path_comparisons: AtomicU64::new(0),
515 ancestry_parent_proofs: AtomicU64::new(0),
516 scanner_prepare_us: AtomicU64::new(0),
517 scanner_control_projection_us: AtomicU64::new(0),
518 scanner_reduce_us: AtomicU64::new(0),
519 content_sidecar_read_us: AtomicU64::new(0),
520 content_sidecar_parse_us: AtomicU64::new(0),
521 content_sidecar_candidates_us: AtomicU64::new(0),
522 content_sidecar_apply_us: AtomicU64::new(0),
523 detached_builds: AtomicU64::new(0),
524 detached_entries: AtomicU64::new(0),
525 detached_walk_us: AtomicU64::new(0),
526 detached_finish_us: AtomicU64::new(0),
527 effect_paths: AtomicU64::new(0),
528 effect_path_bytes: AtomicU64::new(0),
529 impact_candidates: AtomicU64::new(0),
530 impact_ancestor_visits: AtomicU64::new(0),
531 impact_retained_dirty_paths: AtomicU64::new(0),
532 impact_all_dirty: AtomicU64::new(0),
533 journal_retained_commits: AtomicU64::new(0),
534 journal_cloned_commits: AtomicU64::new(0),
535 journal_oversized_commits: AtomicU64::new(0),
536 journal_dropped_commits: AtomicU64::new(0),
537 adaptive_calibration_chunks: AtomicU64::new(0),
538 adaptive_calibration_entries: AtomicU64::new(0),
539 adaptive_calibration_work_us: AtomicU64::new(0),
540 adaptive_scale_ups: AtomicU64::new(0),
541 adaptive_policy_undecided: AtomicU64::new(0),
542 }
543 }
544
545 fn add(&self, counts: &Counts) {
546 atomic_saturating_add(&self.dir_opens, counts.dir_opens);
547 atomic_saturating_add(&self.dir_enumeration_calls, counts.dir_enumeration_calls);
548 atomic_saturating_add(&self.dir_entries, counts.dir_entries);
549 atomic_saturating_add(&self.stats, counts.stats);
550 atomic_saturating_add(&self.file_opens, counts.file_opens);
551 atomic_saturating_add(&self.file_reads, counts.file_reads);
552 atomic_saturating_add(&self.bytes_read, counts.bytes_read);
553 atomic_saturating_add(&self.allocs, counts.allocs);
554 atomic_saturating_add(&self.reallocs, counts.reallocs);
555 atomic_saturating_add(&self.frees, counts.frees);
556 atomic_saturating_add(&self.bytes_allocated, counts.bytes_allocated);
557 atomic_saturating_add(&self.upserts, counts.upserts);
558 atomic_saturating_add(&self.parent_resolutions, counts.parent_resolutions);
559 atomic_saturating_add(&self.parent_memo_hits, counts.parent_memo_hits);
560 atomic_saturating_add(&self.rollup_merges, counts.rollup_merges);
561 atomic_saturating_add(&self.control_reads, counts.control_reads);
562 atomic_saturating_add(&self.control_refused, counts.control_refused);
563 atomic_saturating_add(&self.control_sources_shared, counts.control_sources_shared);
564 atomic_saturating_add(&self.ignore_patterns_tested, counts.ignore_patterns_tested);
565 atomic_saturating_add(&self.ignore_bucket_probes, counts.ignore_bucket_probes);
566 atomic_saturating_add(&self.ignore_bucket_hits, counts.ignore_bucket_hits);
567 atomic_saturating_add(&self.entries_allocated, counts.entries_allocated);
568 atomic_saturating_add(&self.baseline_batches, counts.baseline_batches);
569 atomic_saturating_add(&self.baseline_accepted_ops, counts.baseline_accepted_ops);
570 atomic_saturating_add(&self.opened_batches, counts.opened_batches);
571 atomic_saturating_add(&self.opened_accepted_ops, counts.opened_accepted_ops);
572 atomic_saturating_add(&self.public_batches, counts.public_batches);
573 atomic_saturating_add(&self.public_accepted_ops, counts.public_accepted_ops);
574 atomic_saturating_add(&self.ancestry_overlay_inserts, counts.ancestry_overlay_inserts);
575 atomic_saturating_add(&self.ancestry_path_comparisons, counts.ancestry_path_comparisons);
576 atomic_saturating_add(&self.ancestry_parent_proofs, counts.ancestry_parent_proofs);
577 atomic_saturating_add(&self.scanner_prepare_us, counts.scanner_prepare_us);
578 atomic_saturating_add(
579 &self.scanner_control_projection_us,
580 counts.scanner_control_projection_us,
581 );
582 atomic_saturating_add(&self.scanner_reduce_us, counts.scanner_reduce_us);
583 atomic_saturating_add(&self.content_sidecar_read_us, counts.content_sidecar_read_us);
584 atomic_saturating_add(&self.content_sidecar_parse_us, counts.content_sidecar_parse_us);
585 atomic_saturating_add(
586 &self.content_sidecar_candidates_us,
587 counts.content_sidecar_candidates_us,
588 );
589 atomic_saturating_add(&self.content_sidecar_apply_us, counts.content_sidecar_apply_us);
590 atomic_saturating_add(&self.detached_builds, counts.detached_builds);
591 atomic_saturating_add(&self.detached_entries, counts.detached_entries);
592 atomic_saturating_add(&self.detached_walk_us, counts.detached_walk_us);
593 atomic_saturating_add(&self.detached_finish_us, counts.detached_finish_us);
594 atomic_saturating_add(&self.effect_paths, counts.effect_paths);
595 atomic_saturating_add(&self.effect_path_bytes, counts.effect_path_bytes);
596 atomic_saturating_add(&self.impact_candidates, counts.impact_candidates);
597 atomic_saturating_add(&self.impact_ancestor_visits, counts.impact_ancestor_visits);
598 atomic_saturating_add(
599 &self.impact_retained_dirty_paths,
600 counts.impact_retained_dirty_paths,
601 );
602 atomic_saturating_add(&self.impact_all_dirty, counts.impact_all_dirty);
603 atomic_saturating_add(&self.journal_retained_commits, counts.journal_retained_commits);
604 atomic_saturating_add(&self.journal_cloned_commits, counts.journal_cloned_commits);
605 atomic_saturating_add(&self.journal_oversized_commits, counts.journal_oversized_commits);
606 atomic_saturating_add(&self.journal_dropped_commits, counts.journal_dropped_commits);
607 atomic_saturating_add(
608 &self.adaptive_calibration_chunks,
609 counts.adaptive_calibration_chunks,
610 );
611 atomic_saturating_add(
612 &self.adaptive_calibration_entries,
613 counts.adaptive_calibration_entries,
614 );
615 atomic_saturating_add(
616 &self.adaptive_calibration_work_us,
617 counts.adaptive_calibration_work_us,
618 );
619 atomic_saturating_add(&self.adaptive_scale_ups, counts.adaptive_scale_ups);
620 atomic_saturating_add(&self.adaptive_policy_undecided, counts.adaptive_policy_undecided);
621 }
622
623 fn snapshot(&self) -> Counts {
624 Counts {
625 dir_opens: self.dir_opens.load(Ordering::Relaxed),
626 dir_enumeration_calls: self.dir_enumeration_calls.load(Ordering::Relaxed),
627 dir_entries: self.dir_entries.load(Ordering::Relaxed),
628 stats: self.stats.load(Ordering::Relaxed),
629 file_opens: self.file_opens.load(Ordering::Relaxed),
630 file_reads: self.file_reads.load(Ordering::Relaxed),
631 bytes_read: self.bytes_read.load(Ordering::Relaxed),
632 allocs: self.allocs.load(Ordering::Relaxed),
633 reallocs: self.reallocs.load(Ordering::Relaxed),
634 frees: self.frees.load(Ordering::Relaxed),
635 bytes_allocated: self.bytes_allocated.load(Ordering::Relaxed),
636 upserts: self.upserts.load(Ordering::Relaxed),
637 parent_resolutions: self.parent_resolutions.load(Ordering::Relaxed),
638 parent_memo_hits: self.parent_memo_hits.load(Ordering::Relaxed),
639 rollup_merges: self.rollup_merges.load(Ordering::Relaxed),
640 control_reads: self.control_reads.load(Ordering::Relaxed),
641 control_refused: self.control_refused.load(Ordering::Relaxed),
642 control_sources_shared: self.control_sources_shared.load(Ordering::Relaxed),
643 ignore_patterns_tested: self.ignore_patterns_tested.load(Ordering::Relaxed),
644 ignore_bucket_probes: self.ignore_bucket_probes.load(Ordering::Relaxed),
645 ignore_bucket_hits: self.ignore_bucket_hits.load(Ordering::Relaxed),
646 entries_allocated: self.entries_allocated.load(Ordering::Relaxed),
647 baseline_batches: self.baseline_batches.load(Ordering::Relaxed),
648 baseline_accepted_ops: self.baseline_accepted_ops.load(Ordering::Relaxed),
649 opened_batches: self.opened_batches.load(Ordering::Relaxed),
650 opened_accepted_ops: self.opened_accepted_ops.load(Ordering::Relaxed),
651 public_batches: self.public_batches.load(Ordering::Relaxed),
652 public_accepted_ops: self.public_accepted_ops.load(Ordering::Relaxed),
653 ancestry_overlay_inserts: self.ancestry_overlay_inserts.load(Ordering::Relaxed),
654 ancestry_path_comparisons: self.ancestry_path_comparisons.load(Ordering::Relaxed),
655 ancestry_parent_proofs: self.ancestry_parent_proofs.load(Ordering::Relaxed),
656 scanner_prepare_us: self.scanner_prepare_us.load(Ordering::Relaxed),
657 scanner_control_projection_us: self
658 .scanner_control_projection_us
659 .load(Ordering::Relaxed),
660 scanner_reduce_us: self.scanner_reduce_us.load(Ordering::Relaxed),
661 content_sidecar_read_us: self.content_sidecar_read_us.load(Ordering::Relaxed),
662 content_sidecar_parse_us: self.content_sidecar_parse_us.load(Ordering::Relaxed),
663 content_sidecar_candidates_us: self
664 .content_sidecar_candidates_us
665 .load(Ordering::Relaxed),
666 content_sidecar_apply_us: self.content_sidecar_apply_us.load(Ordering::Relaxed),
667 detached_builds: self.detached_builds.load(Ordering::Relaxed),
668 detached_entries: self.detached_entries.load(Ordering::Relaxed),
669 detached_walk_us: self.detached_walk_us.load(Ordering::Relaxed),
670 detached_finish_us: self.detached_finish_us.load(Ordering::Relaxed),
671 effect_paths: self.effect_paths.load(Ordering::Relaxed),
672 effect_path_bytes: self.effect_path_bytes.load(Ordering::Relaxed),
673 impact_candidates: self.impact_candidates.load(Ordering::Relaxed),
674 impact_ancestor_visits: self.impact_ancestor_visits.load(Ordering::Relaxed),
675 impact_retained_dirty_paths: self.impact_retained_dirty_paths.load(Ordering::Relaxed),
676 impact_all_dirty: self.impact_all_dirty.load(Ordering::Relaxed),
677 journal_retained_commits: self.journal_retained_commits.load(Ordering::Relaxed),
678 journal_cloned_commits: self.journal_cloned_commits.load(Ordering::Relaxed),
679 journal_oversized_commits: self.journal_oversized_commits.load(Ordering::Relaxed),
680 journal_dropped_commits: self.journal_dropped_commits.load(Ordering::Relaxed),
681 adaptive_calibration_chunks: self.adaptive_calibration_chunks.load(Ordering::Relaxed),
682 adaptive_calibration_entries: self.adaptive_calibration_entries.load(Ordering::Relaxed),
683 adaptive_calibration_work_us: self.adaptive_calibration_work_us.load(Ordering::Relaxed),
684 adaptive_scale_ups: self.adaptive_scale_ups.load(Ordering::Relaxed),
685 adaptive_policy_undecided: self.adaptive_policy_undecided.load(Ordering::Relaxed),
686 }
687 }
688
689 fn reset(&self) {
690 self.dir_opens.store(0, Ordering::Relaxed);
691 self.dir_enumeration_calls.store(0, Ordering::Relaxed);
692 self.dir_entries.store(0, Ordering::Relaxed);
693 self.stats.store(0, Ordering::Relaxed);
694 self.file_opens.store(0, Ordering::Relaxed);
695 self.file_reads.store(0, Ordering::Relaxed);
696 self.bytes_read.store(0, Ordering::Relaxed);
697 self.allocs.store(0, Ordering::Relaxed);
698 self.reallocs.store(0, Ordering::Relaxed);
699 self.frees.store(0, Ordering::Relaxed);
700 self.bytes_allocated.store(0, Ordering::Relaxed);
701 self.upserts.store(0, Ordering::Relaxed);
702 self.parent_resolutions.store(0, Ordering::Relaxed);
703 self.parent_memo_hits.store(0, Ordering::Relaxed);
704 self.rollup_merges.store(0, Ordering::Relaxed);
705 self.control_reads.store(0, Ordering::Relaxed);
706 self.control_refused.store(0, Ordering::Relaxed);
707 self.control_sources_shared.store(0, Ordering::Relaxed);
708 self.ignore_patterns_tested.store(0, Ordering::Relaxed);
709 self.ignore_bucket_probes.store(0, Ordering::Relaxed);
710 self.ignore_bucket_hits.store(0, Ordering::Relaxed);
711 self.entries_allocated.store(0, Ordering::Relaxed);
712 self.baseline_batches.store(0, Ordering::Relaxed);
713 self.baseline_accepted_ops.store(0, Ordering::Relaxed);
714 self.opened_batches.store(0, Ordering::Relaxed);
715 self.opened_accepted_ops.store(0, Ordering::Relaxed);
716 self.public_batches.store(0, Ordering::Relaxed);
717 self.public_accepted_ops.store(0, Ordering::Relaxed);
718 self.ancestry_overlay_inserts.store(0, Ordering::Relaxed);
719 self.ancestry_path_comparisons.store(0, Ordering::Relaxed);
720 self.ancestry_parent_proofs.store(0, Ordering::Relaxed);
721 self.scanner_prepare_us.store(0, Ordering::Relaxed);
722 self.scanner_control_projection_us.store(0, Ordering::Relaxed);
723 self.scanner_reduce_us.store(0, Ordering::Relaxed);
724 self.content_sidecar_read_us.store(0, Ordering::Relaxed);
725 self.content_sidecar_parse_us.store(0, Ordering::Relaxed);
726 self.content_sidecar_candidates_us.store(0, Ordering::Relaxed);
727 self.content_sidecar_apply_us.store(0, Ordering::Relaxed);
728 self.detached_builds.store(0, Ordering::Relaxed);
729 self.detached_entries.store(0, Ordering::Relaxed);
730 self.detached_walk_us.store(0, Ordering::Relaxed);
731 self.detached_finish_us.store(0, Ordering::Relaxed);
732 self.effect_paths.store(0, Ordering::Relaxed);
733 self.effect_path_bytes.store(0, Ordering::Relaxed);
734 self.impact_candidates.store(0, Ordering::Relaxed);
735 self.impact_ancestor_visits.store(0, Ordering::Relaxed);
736 self.impact_retained_dirty_paths.store(0, Ordering::Relaxed);
737 self.impact_all_dirty.store(0, Ordering::Relaxed);
738 self.journal_retained_commits.store(0, Ordering::Relaxed);
739 self.journal_cloned_commits.store(0, Ordering::Relaxed);
740 self.journal_oversized_commits.store(0, Ordering::Relaxed);
741 self.journal_dropped_commits.store(0, Ordering::Relaxed);
742 self.adaptive_calibration_chunks.store(0, Ordering::Relaxed);
743 self.adaptive_calibration_entries.store(0, Ordering::Relaxed);
744 self.adaptive_calibration_work_us.store(0, Ordering::Relaxed);
745 self.adaptive_scale_ups.store(0, Ordering::Relaxed);
746 self.adaptive_policy_undecided.store(0, Ordering::Relaxed);
747 }
748}
749
750static GLOBAL: GlobalCounts = GlobalCounts::new();
751
752struct LocalCounts {
753 counts: Cell<Counts>,
754}
755
756impl LocalCounts {
757 const fn new() -> Self {
758 Self { counts: Cell::new(Counts::ZERO) }
759 }
760}
761
762impl Drop for LocalCounts {
763 fn drop(&mut self) {
764 GLOBAL.add(&self.counts.replace(Counts::default()));
765 }
766}
767
768std::thread_local! {
769 static IN_COUNTER: Cell<bool> = const { Cell::new(false) };
773 static LOCAL: LocalCounts = const { LocalCounts::new() };
774}
775
776struct ReentryGuard<'a>(&'a Cell<bool>);
777
778impl<'a> ReentryGuard<'a> {
779 fn enter(cell: &'a Cell<bool>) -> Option<Self> {
780 if cell.replace(true) { None } else { Some(Self(cell)) }
781 }
782}
783
784impl Drop for ReentryGuard<'_> {
785 fn drop(&mut self) {
786 self.0.set(false);
787 }
788}
789
790pub(crate) struct ThreadFlushGuard;
792
793impl Drop for ThreadFlushGuard {
794 fn drop(&mut self) {
795 flush_thread();
796 }
797}
798
799pub(crate) const fn thread_flush_guard() -> ThreadFlushGuard {
801 ThreadFlushGuard
802}
803
804#[must_use]
810pub struct Measurement {
811 process_before: Option<process::Snapshot>,
812}
813
814impl Measurement {
815 pub fn from_env() -> Self {
817 if !enable_from_env() {
818 return Self { process_before: None };
819 }
820
821 let process_before = process::Snapshot::now();
825 reset();
826 Self { process_before: Some(process_before) }
827 }
828
829 #[must_use]
831 pub fn finish(mut self) -> Option<String> {
832 let before = self.process_before.take()?;
833 flush_thread();
834 let counts = snapshot();
835 let process = process::Snapshot::now().since(&before);
836 enable(false);
837 Some(render(&counts, &process))
838 }
839}
840
841impl Drop for Measurement {
842 fn drop(&mut self) {
843 if self.process_before.is_some() {
844 enable(false);
845 }
846 }
847}
848
849pub fn enable(on: bool) {
851 ENABLED.store(on, Ordering::Relaxed);
852}
853
854pub fn enable_from_env() -> bool {
859 enable_from_value(std::env::var_os("FDU_COUNTERS").as_deref())
860}
861
862fn enable_from_value(value: Option<&OsStr>) -> bool {
863 let on = value.is_some_and(|value| {
864 value.to_str().is_none_or(|text| {
865 let text = text.trim();
866 !(text.is_empty()
867 || text == "0"
868 || text.eq_ignore_ascii_case("false")
869 || text.eq_ignore_ascii_case("no")
870 || text.eq_ignore_ascii_case("off"))
871 })
872 });
873 enable(on);
874 on
875}
876
877#[inline]
879#[must_use]
880pub fn enabled() -> bool {
881 ENABLED.load(Ordering::Relaxed)
882}
883
884#[must_use]
886pub(crate) fn elapsed_micros(started: std::time::Instant) -> u64 {
887 u64::try_from(started.elapsed().as_micros()).unwrap_or(u64::MAX)
888}
889
890pub(crate) fn add_elapsed(started: Option<std::time::Instant>, add: impl FnOnce(&mut Counts, u64)) {
892 if let Some(started) = started {
893 bump(|counts| add(counts, elapsed_micros(started)));
894 }
895}
896
897#[inline]
902pub fn bump(f: impl FnOnce(&mut Counts)) {
903 if !enabled() {
904 return;
905 }
906
907 let mut f = Some(f);
908 let stored_locally = IN_COUNTER
909 .try_with(|reentry| {
910 let Some(_guard) = ReentryGuard::enter(reentry) else { return false };
911 LOCAL
912 .try_with(|local| {
913 if let Some(update) = f.take() {
914 let mut counts = local.counts.get();
915 update(&mut counts);
916 local.counts.set(counts);
917 }
918 })
919 .is_ok()
920 })
921 .unwrap_or(false);
922
923 if !stored_locally {
924 let mut delta = Counts::default();
925 if let Some(update) = f {
926 update(&mut delta);
927 GLOBAL.add(&delta);
928 }
929 }
930}
931
932pub fn flush_thread() {
934 let _ = LOCAL.try_with(|local| GLOBAL.add(&local.counts.replace(Counts::default())));
935}
936
937#[must_use]
939pub fn snapshot() -> Counts {
940 let mut total = GLOBAL.snapshot();
941 let _ = LOCAL.try_with(|local| total.add(&local.counts.get()));
942 total
943}
944
945#[must_use]
951pub fn thread_snapshot() -> Counts {
952 LOCAL.try_with(|local| local.counts.get()).unwrap_or_default()
953}
954
955pub fn reset() {
960 let _ = LOCAL.try_with(|local| local.counts.set(Counts::default()));
961 GLOBAL.reset();
962}
963
964#[must_use]
966pub const fn system_allocator() -> alloc::CountingAlloc<std::alloc::System> {
967 alloc::CountingAlloc::system(alloc::fdu_sinks())
968}
969
970#[must_use]
972pub fn render(counts: &Counts, process: &process::Snapshot) -> String {
973 let mut out = render_rows(&counts.rows());
974 out.push('\n');
975 out.push_str(&process.render());
976 out
977}
978
979fn record_alloc(size: u64) {
980 bump(|counts| {
981 counts.allocs = counts.allocs.saturating_add(1);
982 counts.bytes_allocated = counts.bytes_allocated.saturating_add(size);
983 });
984}
985
986fn record_realloc(growth: u64) {
987 bump(|counts| {
988 counts.reallocs = counts.reallocs.saturating_add(1);
989 counts.bytes_allocated = counts.bytes_allocated.saturating_add(growth);
990 });
991}
992
993fn record_dealloc() {
994 bump(|counts| counts.frees = counts.frees.saturating_add(1));
995}
996
997fn atomic_saturating_add(target: &AtomicU64, value: u64) {
998 if value == 0 {
999 return;
1000 }
1001 let _ = target.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
1002 Some(current.saturating_add(value))
1003 });
1004}
1005
1006#[allow(clippy::cast_precision_loss)]
1007fn ratio(numerator: u64, denominator: u64) -> f64 {
1008 let denominator = if denominator == 0 { 1.0 } else { denominator as f64 };
1009 numerator as f64 / denominator
1010}
1011
1012fn render_rows(rows: &[(&str, &str, u64)]) -> String {
1013 use std::fmt::Write as _;
1014
1015 let width = rows.iter().map(|(_, label, _)| label.len()).max().unwrap_or(0);
1016 let mut output = String::new();
1017 let mut previous = None;
1018 for (group, label, value) in rows {
1019 if previous != Some(*group) {
1020 if previous.is_some() {
1021 output.push('\n');
1022 }
1023 let _ = writeln!(output, "[{group}]");
1024 previous = Some(*group);
1025 }
1026 let _ = writeln!(output, " {label:<width$} {value:>13}");
1027 }
1028 output
1029}
1030
1031#[cfg(test)]
1033pub(crate) fn test_serial() -> std::sync::MutexGuard<'static, ()> {
1034 static SERIAL: std::sync::Mutex<()> = std::sync::Mutex::new(());
1035 SERIAL.lock().unwrap_or_else(std::sync::PoisonError::into_inner)
1036}
1037
1038#[cfg(test)]
1040pub(crate) fn test_thread_snapshot() -> Counts {
1041 thread_snapshot()
1042}
1043
1044#[cfg(test)]
1046pub(crate) fn test_thread_reset() {
1047 let _ = LOCAL.try_with(|local| local.counts.set(Counts::default()));
1048}
1049
1050#[cfg(test)]
1051mod tests {
1052 use super::*;
1053
1054 #[test]
1055 fn an_exiting_thread_folds_without_a_manual_flush() {
1056 let _serial = test_serial();
1057 enable(true);
1058 reset();
1059
1060 std::thread::spawn(|| bump(|counts| counts.dir_opens = 7)).join().expect("counting worker");
1061
1062 assert!(snapshot().dir_opens >= 7);
1065 reset();
1066 enable(false);
1067 }
1068
1069 #[test]
1070 fn thread_snapshot_excludes_workers_without_clearing_local_counts() {
1071 let _serial = test_serial();
1072 enable(true);
1073 reset();
1074 bump(|counts| counts.dir_opens = 3);
1075
1076 std::thread::spawn(|| bump(|counts| counts.dir_opens = 7)).join().expect("worker");
1077
1078 assert_eq!(thread_snapshot().dir_opens, 3);
1079 assert_eq!(thread_snapshot().dir_opens, 3, "reading must not consume counts");
1080 assert!(snapshot().dir_opens >= 10);
1081 flush_thread();
1082 assert_eq!(thread_snapshot().dir_opens, 0);
1083 reset();
1084 enable(false);
1085 }
1086
1087 #[test]
1088 fn a_falsey_value_disables_a_previously_enabled_process() {
1089 let _serial = test_serial();
1090 enable(true);
1091
1092 assert!(!enable_from_value(Some(OsStr::new("FALSE"))));
1093 assert!(!enabled());
1094 }
1095
1096 #[test]
1097 fn ratios_treat_a_zero_denominator_as_one() {
1098 let counts = Counts { allocs: 10, ..Counts::default() };
1099 let allocations = counts
1100 .per(0)
1101 .into_iter()
1102 .find(|(_, label, _)| *label == "allocations")
1103 .expect("allocation row");
1104 assert!((allocations.2 - 10.0).abs() < f64::EPSILON);
1105 }
1106}