use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tracing::info;
use crate::metrics::{MetricId, ScanMetadataCompleted, ScanType, TableType};
pub(crate) struct ScanMetrics {
num_add_files_seen: AtomicU64,
num_add_files_seen_from_delta_files: AtomicU64,
num_selected_add_files: AtomicU64,
selected_add_files_bytes: AtomicU64,
num_remove_files_seen_from_delta_files: AtomicU64,
num_non_file_actions: AtomicU64,
num_predicate_filtered: AtomicU64,
peak_hash_set_size: AtomicUsize,
dedup_visitor_time_ns: AtomicU64,
action_transform_time_ns: AtomicU64,
predicate_eval_time_ns: AtomicU64,
}
impl Default for ScanMetrics {
fn default() -> Self {
Self {
num_add_files_seen: AtomicU64::new(0),
num_add_files_seen_from_delta_files: AtomicU64::new(0),
num_selected_add_files: AtomicU64::new(0),
selected_add_files_bytes: AtomicU64::new(0),
num_remove_files_seen_from_delta_files: AtomicU64::new(0),
num_non_file_actions: AtomicU64::new(0),
num_predicate_filtered: AtomicU64::new(0),
peak_hash_set_size: AtomicUsize::new(0),
dedup_visitor_time_ns: AtomicU64::new(0),
action_transform_time_ns: AtomicU64::new(0),
predicate_eval_time_ns: AtomicU64::new(0),
}
}
}
impl ScanMetrics {
pub(crate) fn record_add_file_seen(&self, is_from_delta_file: bool) {
self.num_add_files_seen.fetch_add(1, Ordering::Relaxed);
if is_from_delta_file {
self.num_add_files_seen_from_delta_files
.fetch_add(1, Ordering::Relaxed);
}
}
pub(crate) fn record_selected_add_file(&self, bytes: u64) {
self.num_selected_add_files.fetch_add(1, Ordering::Relaxed);
self.selected_add_files_bytes
.fetch_add(bytes, Ordering::Relaxed);
}
pub(crate) fn incr_remove_files_seen_from_delta_files(&self) {
self.num_remove_files_seen_from_delta_files
.fetch_add(1, Ordering::Relaxed);
}
pub(crate) fn incr_non_file_actions(&self) {
self.num_non_file_actions.fetch_add(1, Ordering::Relaxed);
}
pub(crate) fn add_predicate_filtered(&self, value: u64) {
self.num_predicate_filtered
.fetch_add(value, Ordering::Relaxed);
}
pub(crate) fn update_peak_hash_set_size(&self, value: usize) {
self.peak_hash_set_size.fetch_max(value, Ordering::Relaxed);
}
pub(crate) fn add_dedup_visitor_time_ns(&self, duration_ns: u64) {
self.dedup_visitor_time_ns
.fetch_add(duration_ns, Ordering::Relaxed);
}
pub(crate) fn add_action_transform_time_ns(&self, duration_ns: u64) {
self.action_transform_time_ns
.fetch_add(duration_ns, Ordering::Relaxed);
}
pub(crate) fn add_predicate_eval_time_ns(&self, duration_ns: u64) {
self.predicate_eval_time_ns
.fetch_add(duration_ns, Ordering::Relaxed);
}
pub(crate) fn reset_counters(&self) {
self.num_add_files_seen.store(0, Ordering::Relaxed);
self.num_add_files_seen_from_delta_files
.store(0, Ordering::Relaxed);
self.num_selected_add_files.store(0, Ordering::Relaxed);
self.selected_add_files_bytes.store(0, Ordering::Relaxed);
self.num_remove_files_seen_from_delta_files
.store(0, Ordering::Relaxed);
self.num_non_file_actions.store(0, Ordering::Relaxed);
self.num_predicate_filtered.store(0, Ordering::Relaxed);
self.dedup_visitor_time_ns.store(0, Ordering::Relaxed);
self.action_transform_time_ns.store(0, Ordering::Relaxed);
self.predicate_eval_time_ns.store(0, Ordering::Relaxed);
}
pub(crate) fn to_event(
&self,
operation_id: MetricId,
is_catalog_managed: bool,
correlation_id: Option<Arc<str>>,
scan_type: ScanType,
duration: Duration,
) -> ScanMetadataCompleted {
ScanMetadataCompleted {
operation_id,
table_type: TableType::from_catalog_managed(is_catalog_managed),
correlation_id,
scan_type,
duration,
num_add_files_seen: self.num_add_files_seen.load(Ordering::Relaxed),
num_add_files_seen_from_delta_files: self
.num_add_files_seen_from_delta_files
.load(Ordering::Relaxed),
num_selected_add_files: self.num_selected_add_files.load(Ordering::Relaxed),
selected_add_files_bytes: self.selected_add_files_bytes.load(Ordering::Relaxed),
num_remove_files_seen_from_delta_files: self
.num_remove_files_seen_from_delta_files
.load(Ordering::Relaxed),
num_non_file_actions: self.num_non_file_actions.load(Ordering::Relaxed),
num_predicate_filtered: self.num_predicate_filtered.load(Ordering::Relaxed),
peak_hash_set_size: self.peak_hash_set_size.load(Ordering::Relaxed),
dedup_visitor_time: Duration::from_nanos(
self.dedup_visitor_time_ns.load(Ordering::Relaxed),
),
action_transform_time: Duration::from_nanos(
self.action_transform_time_ns.load(Ordering::Relaxed),
),
predicate_eval_time: Duration::from_nanos(
self.predicate_eval_time_ns.load(Ordering::Relaxed),
),
}
}
pub(crate) fn log(&self, message: impl AsRef<str>) {
let add_files_seen = self.num_add_files_seen.load(Ordering::Relaxed);
let add_files_seen_from_delta_files = self
.num_add_files_seen_from_delta_files
.load(Ordering::Relaxed);
let selected_add_files = self.num_selected_add_files.load(Ordering::Relaxed);
let selected_add_files_bytes = self.selected_add_files_bytes.load(Ordering::Relaxed);
let remove_files_seen_from_delta_files = self
.num_remove_files_seen_from_delta_files
.load(Ordering::Relaxed);
let non_file_actions = self.num_non_file_actions.load(Ordering::Relaxed);
let predicate_filtered = self.num_predicate_filtered.load(Ordering::Relaxed);
let peak_hash_set_size = self.peak_hash_set_size.load(Ordering::Relaxed);
let dedup_visitor_time_ns = self.dedup_visitor_time_ns.load(Ordering::Relaxed);
let action_transform_time_ns = self.action_transform_time_ns.load(Ordering::Relaxed);
let predicate_eval_time_ns = self.predicate_eval_time_ns.load(Ordering::Relaxed);
info!(
add_files_seen,
add_files_seen_from_delta_files,
selected_add_files,
selected_add_files_bytes,
remove_files_seen_from_delta_files,
non_file_actions,
predicate_filtered,
peak_hash_set_size,
dedup_visitor_time_ns,
action_transform_time_ns,
predicate_eval_time_ns,
"{}",
message.as_ref()
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reset_counters_clears_phase_metrics_and_preserves_peak() {
let metrics = ScanMetrics::default();
metrics.record_add_file_seen(true);
metrics.record_selected_add_file(100);
metrics.incr_remove_files_seen_from_delta_files();
metrics.incr_non_file_actions();
metrics.add_predicate_filtered(1);
metrics.update_peak_hash_set_size(10);
metrics.add_dedup_visitor_time_ns(20);
metrics.add_action_transform_time_ns(30);
metrics.add_predicate_eval_time_ns(40);
metrics.reset_counters();
let event = metrics.to_event(MetricId::new(), false, None, ScanType::Full, Duration::ZERO);
assert_eq!(event.num_add_files_seen, 0);
assert_eq!(event.num_add_files_seen_from_delta_files, 0);
assert_eq!(event.num_selected_add_files, 0);
assert_eq!(event.selected_add_files_bytes, 0);
assert_eq!(event.num_remove_files_seen_from_delta_files, 0);
assert_eq!(event.num_non_file_actions, 0);
assert_eq!(event.num_predicate_filtered, 0);
assert_eq!(event.dedup_visitor_time, Duration::ZERO);
assert_eq!(event.action_transform_time, Duration::ZERO);
assert_eq!(event.predicate_eval_time, Duration::ZERO);
assert_eq!(event.peak_hash_set_size, 10);
}
}