use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed};
use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
static SKIPPED_OVER_MAX_SIZE: AtomicUsize = AtomicUsize::new(0);
static SKIPPED_BINARY: AtomicUsize = AtomicUsize::new(0);
static SKIPPED_EXCLUDED: AtomicUsize = AtomicUsize::new(0);
static SKIPPED_UNREADABLE: AtomicUsize = AtomicUsize::new(0);
static GIT_OBJECT_UNREADABLE: AtomicUsize = AtomicUsize::new(0);
static SKIPPED_ARCHIVE_TRUNCATED: AtomicUsize = AtomicUsize::new(0);
static BINARY_SECTION_NAME_UNRESOLVED: AtomicUsize = AtomicUsize::new(0);
static SOURCE_TRUNCATED: AtomicUsize = AtomicUsize::new(0);
static STRUCTURED_SOURCE_PARSE_FAILURES: AtomicUsize = AtomicUsize::new(0);
static ARCHIVE_DUPLICATE_SCAN_UNAVAILABLE: AtomicUsize = AtomicUsize::new(0);
static GIT_LFS_POINTER: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct SkipCounts {
pub over_max_size: usize,
pub binary: usize,
pub excluded: usize,
pub unreadable: usize,
pub git_object_unreadable: usize,
pub archive_truncated: usize,
pub binary_section_name_unresolved: usize,
pub source_truncated: usize,
pub structured_source_parse_failures: usize,
pub archive_duplicate_scan_unavailable: usize,
pub git_lfs_pointer: usize,
}
impl SkipCounts {
pub fn total(&self) -> usize {
self.over_max_size + self.binary + self.excluded + self.unreadable + self.archive_truncated
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SourceSkipEvent {
OverMaxSize,
Binary,
Excluded,
Unreadable,
GitObjectUnreadable,
ArchiveTruncated,
#[cfg(feature = "binary")]
BinarySectionNameUnresolved,
SourceTruncated,
StructuredSourceParseFailure,
ArchiveDuplicateScanUnavailable,
GitLfsPointer,
}
impl SourceSkipEvent {
fn counter(self) -> &'static AtomicUsize {
match self {
Self::OverMaxSize => &SKIPPED_OVER_MAX_SIZE,
Self::Binary => &SKIPPED_BINARY,
Self::Excluded => &SKIPPED_EXCLUDED,
Self::Unreadable => &SKIPPED_UNREADABLE,
Self::GitObjectUnreadable => &GIT_OBJECT_UNREADABLE,
Self::ArchiveTruncated => &SKIPPED_ARCHIVE_TRUNCATED,
#[cfg(feature = "binary")]
Self::BinarySectionNameUnresolved => &BINARY_SECTION_NAME_UNRESOLVED,
Self::SourceTruncated => &SOURCE_TRUNCATED,
Self::StructuredSourceParseFailure => &STRUCTURED_SOURCE_PARSE_FAILURES,
Self::ArchiveDuplicateScanUnavailable => &ARCHIVE_DUPLICATE_SCAN_UNAVAILABLE,
Self::GitLfsPointer => &GIT_LFS_POINTER,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[must_use = "source skip events must be recorded through the typed recorder so coverage gaps remain surfaced"]
pub(crate) struct RecordedSkipEvent {
event: SourceSkipEvent,
previous: usize,
delta: usize,
}
pub(crate) fn record_skip_event(event: SourceSkipEvent) -> RecordedSkipEvent {
record_skip_events(event, 1)
}
pub(crate) fn record_skip_events(event: SourceSkipEvent, delta: usize) -> RecordedSkipEvent {
let previous = event.counter().fetch_add(delta, Relaxed);
RecordedSkipEvent {
event,
previous,
delta,
}
}
pub fn skip_counts() -> SkipCounts {
SkipCounts {
over_max_size: SKIPPED_OVER_MAX_SIZE.load(Relaxed),
binary: SKIPPED_BINARY.load(Relaxed),
excluded: SKIPPED_EXCLUDED.load(Relaxed),
unreadable: SKIPPED_UNREADABLE.load(Relaxed),
git_object_unreadable: GIT_OBJECT_UNREADABLE.load(Relaxed),
archive_truncated: SKIPPED_ARCHIVE_TRUNCATED.load(Relaxed),
binary_section_name_unresolved: BINARY_SECTION_NAME_UNRESOLVED.load(Relaxed),
source_truncated: SOURCE_TRUNCATED.load(Relaxed),
structured_source_parse_failures: STRUCTURED_SOURCE_PARSE_FAILURES.load(Relaxed),
archive_duplicate_scan_unavailable: ARCHIVE_DUPLICATE_SCAN_UNAVAILABLE.load(Relaxed),
git_lfs_pointer: GIT_LFS_POINTER.load(Relaxed),
}
}
pub fn merge_skip_count_deltas(deltas: &SkipCounts) {
let add = |counter: &AtomicUsize, delta: usize| {
if delta > 0 {
counter.fetch_add(delta, Relaxed);
}
};
add(&SKIPPED_OVER_MAX_SIZE, deltas.over_max_size);
add(&SKIPPED_BINARY, deltas.binary);
add(&SKIPPED_UNREADABLE, deltas.unreadable);
add(&GIT_OBJECT_UNREADABLE, deltas.git_object_unreadable);
add(&SKIPPED_ARCHIVE_TRUNCATED, deltas.archive_truncated);
add(
&BINARY_SECTION_NAME_UNRESOLVED,
deltas.binary_section_name_unresolved,
);
add(&SOURCE_TRUNCATED, deltas.source_truncated);
add(
&STRUCTURED_SOURCE_PARSE_FAILURES,
deltas.structured_source_parse_failures,
);
add(
&ARCHIVE_DUPLICATE_SCAN_UNAVAILABLE,
deltas.archive_duplicate_scan_unavailable,
);
add(&GIT_LFS_POINTER, deltas.git_lfs_pointer);
}
pub fn git_object_unreadable() -> usize {
skip_counts().git_object_unreadable
}
pub(crate) fn reset_skip_counters() {
SKIPPED_OVER_MAX_SIZE.store(0, Relaxed);
SKIPPED_BINARY.store(0, Relaxed);
SKIPPED_EXCLUDED.store(0, Relaxed);
SKIPPED_UNREADABLE.store(0, Relaxed);
GIT_OBJECT_UNREADABLE.store(0, Relaxed);
SKIPPED_ARCHIVE_TRUNCATED.store(0, Relaxed);
BINARY_SECTION_NAME_UNRESOLVED.store(0, Relaxed);
SOURCE_TRUNCATED.store(0, Relaxed);
STRUCTURED_SOURCE_PARSE_FAILURES.store(0, Relaxed);
ARCHIVE_DUPLICATE_SCAN_UNAVAILABLE.store(0, Relaxed);
GIT_LFS_POINTER.store(0, Relaxed);
}
pub fn reset_skipped_over_max_size() {
reset_skip_counters();
}
static SCAN_GATE: RwLock<()> = RwLock::new(());
static SCAN_GATE_ARMED: AtomicBool = AtomicBool::new(false);
thread_local! {
static IN_EXCLUSIVE_SCAN_SCOPE: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
pub struct ScanCounterScope {
_write: RwLockWriteGuard<'static, ()>,
}
impl Drop for ScanCounterScope {
fn drop(&mut self) {
IN_EXCLUSIVE_SCAN_SCOPE.with(|in_scope| in_scope.set(false));
}
}
pub(crate) fn enter_exclusive_scan_scope() -> ScanCounterScope {
SCAN_GATE_ARMED.store(true, Relaxed);
let write = SCAN_GATE
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner());
IN_EXCLUSIVE_SCAN_SCOPE.with(|in_scope| in_scope.set(true));
ScanCounterScope { _write: write }
}
pub(crate) struct ScanReadLease {
_lease: Option<RwLockReadGuard<'static, ()>>,
}
pub(crate) fn acquire_scan_read_lease() -> ScanReadLease {
if !SCAN_GATE_ARMED.load(Relaxed) {
return ScanReadLease { _lease: None };
}
if IN_EXCLUSIVE_SCAN_SCOPE.with(|in_scope| in_scope.get()) {
return ScanReadLease { _lease: None };
}
ScanReadLease {
_lease: Some(
SCAN_GATE
.read()
.unwrap_or_else(|poisoned| poisoned.into_inner()),
),
}
}
struct LeasedScanIter<'a, T> {
_lease: ScanReadLease,
inner: Box<dyn Iterator<Item = T> + 'a>,
}
impl<T> Iterator for LeasedScanIter<'_, T> {
type Item = T;
fn next(&mut self) -> Option<T> {
self.inner.next()
}
}
pub(crate) fn attach_scan_lease<'a, T: 'a>(
lease: ScanReadLease,
inner: Box<dyn Iterator<Item = T> + 'a>,
) -> Box<dyn Iterator<Item = T> + 'a> {
if lease._lease.is_some() {
Box::new(LeasedScanIter {
_lease: lease,
inner,
})
} else {
inner
}
}
pub(crate) fn gate_scan<'a, T: 'a>(
build: impl FnOnce() -> Box<dyn Iterator<Item = T> + 'a>,
) -> Box<dyn Iterator<Item = T> + 'a> {
let lease = acquire_scan_read_lease();
let inner = build();
attach_scan_lease(lease, inner)
}
pub(crate) fn set_skip_counts_for_test(counts: SkipCounts) {
SKIPPED_OVER_MAX_SIZE.store(counts.over_max_size, Relaxed);
SKIPPED_BINARY.store(counts.binary, Relaxed);
SKIPPED_EXCLUDED.store(counts.excluded, Relaxed);
SKIPPED_UNREADABLE.store(counts.unreadable, Relaxed);
GIT_OBJECT_UNREADABLE.store(counts.git_object_unreadable, Relaxed);
SKIPPED_ARCHIVE_TRUNCATED.store(counts.archive_truncated, Relaxed);
BINARY_SECTION_NAME_UNRESOLVED.store(counts.binary_section_name_unresolved, Relaxed);
SOURCE_TRUNCATED.store(counts.source_truncated, Relaxed);
STRUCTURED_SOURCE_PARSE_FAILURES.store(counts.structured_source_parse_failures, Relaxed);
ARCHIVE_DUPLICATE_SCAN_UNAVAILABLE.store(counts.archive_duplicate_scan_unavailable, Relaxed);
GIT_LFS_POINTER.store(counts.git_lfs_pointer, Relaxed);
}