Skip to content

Commit ebb94a6

Browse files
committed
Add metrics framework for Snapshot creation
1 parent e10d72b commit ebb94a6

4 files changed

Lines changed: 176 additions & 1 deletion

File tree

kernel/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ pub mod error;
9393
pub mod expressions;
9494
mod log_compaction;
9595
mod log_path;
96+
pub mod metrics;
9697
pub mod scan;
9798
pub mod schema;
9899
pub mod snapshot;
@@ -709,6 +710,14 @@ pub trait Engine: AsAny {
709710

710711
/// Get the connector provided [`ParquetHandler`].
711712
fn parquet_handler(&self) -> Arc<dyn ParquetHandler>;
713+
714+
/// Get the connector provided [`metrics::MetricsReporter`] for metrics collection.
715+
///
716+
/// Returns an optional reporter that will be notified of metrics from Delta operations.
717+
/// The default implementation returns None (no metrics reporting).
718+
fn get_metrics_reporter(&self) -> Option<Arc<dyn crate::metrics::MetricsReporter>> {
719+
None
720+
}
712721
}
713722

714723
// we have an 'internal' feature flag: default-engine-base, which is actually just the shared

kernel/src/metrics/mod.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
//! Metrics collection for Delta Kernel operations.
2+
//!
3+
//! This module provides metrics tracking for various Delta operations including
4+
//! snapshot creation, scans, and transactions. Metrics are collected during operations
5+
//! and can be reported via the `MetricsReporter` trait.
6+
//!
7+
//! # Example: Implementing a Custom MetricsReporter
8+
//!
9+
//! ```
10+
//! use std::sync::Arc;
11+
//! use delta_kernel::metrics::{MetricsReporter, SnapshotMetrics};
12+
//!
13+
//! struct LoggingReporter;
14+
//!
15+
//! impl MetricsReporter for LoggingReporter {
16+
//! fn report_snapshot(&self, metrics: &SnapshotMetrics) {
17+
//! println!("Snapshot creation took {:?}", metrics.load_snapshot_total_duration);
18+
//! println!(" - Protocol/Metadata load: {:?}", metrics.load_protocol_metadata_duration);
19+
//! println!(" - Log segment load: {:?}", metrics.load_log_segment_duration);
20+
//! println!(" - Files: {} commits, {} checkpoints, {} compactions",
21+
//! metrics.num_commit_files,
22+
//! metrics.num_checkpoint_files,
23+
//! metrics.num_compaction_files);
24+
//! }
25+
//! }
26+
//! ```
27+
//!
28+
//! # Example: Implementing a Composite Reporter
29+
//!
30+
//! If you need to send metrics to multiple destinations, you can create a composite reporter:
31+
//!
32+
//! ```
33+
//! use std::sync::Arc;
34+
//! use delta_kernel::metrics::{MetricsReporter, SnapshotMetrics};
35+
//!
36+
//! struct CompositeReporter {
37+
//! reporters: Vec<Arc<dyn MetricsReporter>>,
38+
//! }
39+
//!
40+
//! impl MetricsReporter for CompositeReporter {
41+
//! fn report_snapshot(&self, metrics: &SnapshotMetrics) {
42+
//! for reporter in &self.reporters {
43+
//! reporter.report_snapshot(metrics);
44+
//! }
45+
//! }
46+
//! }
47+
//! ```
48+
49+
use std::time::{Duration, Instant};
50+
51+
/// Trait for reporting metrics from Delta operations.
52+
///
53+
/// Implementations of this trait receive metrics after operations complete
54+
/// and can forward them to monitoring systems like Prometheus, DataDog, etc.
55+
pub trait MetricsReporter: Send + Sync {
56+
/// Report snapshot creation metrics.
57+
fn report_snapshot(&self, metrics: &SnapshotMetrics);
58+
}
59+
60+
/// Metrics collected during snapshot creation.
61+
///
62+
/// These metrics track the time spent in various phases of loading a snapshot,
63+
/// including log segment construction and protocol/metadata loading.
64+
/// They also track counts of files processed during snapshot creation.
65+
#[derive(Debug, Clone, Default)]
66+
pub struct SnapshotMetrics {
67+
/// Total time spent loading the snapshot (all operations combined).
68+
pub load_snapshot_total_duration: Duration,
69+
70+
/// Time spent loading protocol and metadata actions.
71+
pub load_protocol_metadata_duration: Duration,
72+
73+
/// Time spent building the log segment (listing and organizing log files).
74+
pub load_log_segment_duration: Duration,
75+
76+
/// Number of commit files processed during snapshot creation.
77+
pub num_commit_files: u64,
78+
79+
/// Number of checkpoint files processed during snapshot creation.
80+
pub num_checkpoint_files: u64,
81+
82+
/// Number of compaction files processed during snapshot creation.
83+
pub num_compaction_files: u64,
84+
}
85+
86+
/// A simple timer for tracking operation durations.
87+
///
88+
/// # Example
89+
/// ```ignore
90+
/// let timer = Timer::new();
91+
/// // ... do work ...
92+
/// let duration = timer.elapsed();
93+
/// ```
94+
#[derive(Debug)]
95+
pub struct Timer {
96+
start: Instant,
97+
}
98+
99+
impl Timer {
100+
/// Create a new timer that starts immediately.
101+
pub fn new() -> Self {
102+
Self {
103+
start: Instant::now(),
104+
}
105+
}
106+
107+
/// Get the elapsed time as a Duration since this timer was created.
108+
pub fn elapsed(&self) -> Duration {
109+
self.start.elapsed()
110+
}
111+
}
112+
113+
impl Default for Timer {
114+
fn default() -> Self {
115+
Self::new()
116+
}
117+
}

kernel/src/snapshot.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::checkpoint::CheckpointWriter;
1111
use crate::committer::Committer;
1212
use crate::listed_log_files::ListedLogFiles;
1313
use crate::log_segment::LogSegment;
14+
use crate::metrics::{SnapshotMetrics, Timer};
1415
use crate::path::ParsedLogPath;
1516
use crate::scan::ScanBuilder;
1617
use crate::schema::SchemaRef;
@@ -254,9 +255,47 @@ impl Snapshot {
254255
log_segment: LogSegment,
255256
engine: &dyn Engine,
256257
) -> DeltaResult<Self> {
258+
Self::try_new_from_log_segment_with_metrics(
259+
location,
260+
log_segment,
261+
engine,
262+
std::time::Duration::default(),
263+
)
264+
}
265+
266+
pub(crate) fn try_new_from_log_segment_with_metrics(
267+
location: Url,
268+
log_segment: LogSegment,
269+
engine: &dyn Engine,
270+
load_log_segment_duration: std::time::Duration,
271+
) -> DeltaResult<Self> {
272+
let timer = Timer::new();
273+
257274
let (metadata, protocol) = log_segment.read_metadata(engine)?;
275+
let load_protocol_metadata_duration = timer.elapsed();
276+
258277
let table_configuration =
259278
TableConfiguration::try_new(metadata, protocol, location, log_segment.end_version)?;
279+
280+
let num_commit_files = log_segment.ascending_commit_files.len() as u64;
281+
let num_checkpoint_files = log_segment.checkpoint_parts.len() as u64;
282+
let num_compaction_files = log_segment.ascending_compaction_files.len() as u64;
283+
284+
let load_snapshot_total_duration = timer.elapsed();
285+
286+
let metrics = SnapshotMetrics {
287+
load_snapshot_total_duration,
288+
load_protocol_metadata_duration,
289+
load_log_segment_duration,
290+
num_commit_files,
291+
num_checkpoint_files,
292+
num_compaction_files,
293+
};
294+
295+
if let Some(reporter) = engine.get_metrics_reporter() {
296+
reporter.report_snapshot(&metrics);
297+
}
298+
260299
Ok(Self {
261300
log_segment,
262301
table_configuration,

kernel/src/snapshot/builder.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Builder for creating [`Snapshot`] instances.
22
use crate::log_path::LogPath;
33
use crate::log_segment::LogSegment;
4+
use crate::metrics::Timer;
45
use crate::snapshot::SnapshotRef;
56
use crate::{DeltaResult, Engine, Error, Snapshot, Version};
67

@@ -83,13 +84,22 @@ impl SnapshotBuilder {
8384
pub fn build(self, engine: &dyn Engine) -> DeltaResult<SnapshotRef> {
8485
let log_tail = self.log_tail.into_iter().map(Into::into).collect();
8586
if let Some(table_root) = self.table_root {
87+
let timer = Timer::new();
8688
let log_segment = LogSegment::for_snapshot(
8789
engine.storage_handler().as_ref(),
8890
table_root.join("_delta_log/")?,
8991
log_tail,
9092
self.version,
9193
)?;
92-
Ok(Snapshot::try_new_from_log_segment(table_root, log_segment, engine)?.into())
94+
let load_log_segment_duration = timer.elapsed();
95+
96+
Ok(Snapshot::try_new_from_log_segment_with_metrics(
97+
table_root,
98+
log_segment,
99+
engine,
100+
load_log_segment_duration,
101+
)?
102+
.into())
93103
} else {
94104
let existing_snapshot = self.existing_snapshot.ok_or_else(|| {
95105
Error::internal_error(

0 commit comments

Comments
 (0)