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