use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct DbStats {
pub live_buckets: usize,
pub active_snapshots: usize,
pub oldest_snapshot_seq: u64,
pub oldest_snapshot_lag: u64,
pub scan_internal_records: u64,
pub scan_user_keys: u64,
pub scan_tombstone_hidden_keys: u64,
pub memtable_bytes: u64,
pub immutable_memtables: usize,
pub l0_tables: usize,
pub total_tables: usize,
pub level_tables: Vec<LevelStats>,
pub level_filters: Vec<LevelFilterStats>,
pub table_bytes: u64,
pub wal_bytes_pending_sync: u64,
pub live_blob_files: usize,
pub live_blob_bytes: u64,
pub stale_blob_files: usize,
pub stale_blob_bytes: u64,
pub obsolete_blob_files: usize,
pub obsolete_blob_bytes: u64,
pub blob_gc_runs: u64,
pub blob_gc_input_bytes: u64,
pub blob_gc_output_bytes: u64,
pub blob_gc_discarded_bytes: u64,
pub blob_read_count: u64,
pub blob_read_bytes: u64,
pub compaction_runs: u64,
pub compaction_input_tables: u64,
pub compaction_output_tables: u64,
pub compaction_input_bytes: u64,
pub compaction_output_bytes: u64,
pub compaction_levels: Vec<CompactionLevelStats>,
pub compaction_triggers: Vec<CompactionTriggerStats>,
pub compaction_skips: Vec<CompactionSkipStats>,
pub commit_sequences_allocated: u64,
pub commit_visible_sequence: u64,
pub commit_open_slots: usize,
pub commit_skipped_slots: u64,
pub wal_shards: usize,
pub wal_open_shards: usize,
pub wal_queue_capacity: usize,
pub wal_records_accepted: u64,
pub wal_bytes_accepted: u64,
pub storage_uses_sync_adapter: bool,
pub storage_uses_platform_io_driver: bool,
pub storage_uses_platform_async_io: bool,
pub storage_sync_adapter_tasks: u64,
pub storage_sync_adapter_queue_capacity: usize,
pub storage_sync_adapter_queued_tasks: usize,
pub storage_sync_adapter_submitted_tasks: u64,
pub storage_sync_adapter_completed_tasks: u64,
pub storage_sync_adapter_rejected_tasks: u64,
pub storage_sync_adapter_total_runtime_micros: u64,
pub storage_platform_async_io_tasks: u64,
pub storage_platform_thread_pool_managed_async_tasks: u64,
pub storage_platform_sync_fallback_tasks: u64,
pub storage_platform_io_operations: PlatformIoOperationStats,
pub storage_inline_tasks: u64,
pub storage_operations: StorageOperationStats,
pub maintenance_cooperative_yields: u64,
pub maintenance_budget_exhaustions: u64,
pub block_cache_hits: u64,
pub block_cache_misses: u64,
pub read_path: ReadPathStats,
pub filters: FilterStats,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct StorageOperationMetric {
pub requests: u64,
pub total_latency_micros: u64,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct StorageOperationStats {
pub open_read: StorageOperationMetric,
pub len: StorageOperationMetric,
pub read_exact_at: StorageOperationMetric,
pub read_exact_at_owned: StorageOperationMetric,
pub read_object_bytes: StorageOperationMetric,
pub open_append: StorageOperationMetric,
pub append: StorageOperationMetric,
pub persist: StorageOperationMetric,
pub rewrite_wal: StorageOperationMetric,
pub acquire_writer_lease: StorageOperationMetric,
pub create_directory_all: StorageOperationMetric,
pub list_directory_files: StorageOperationMetric,
pub sync_directory_after_renames: StorageOperationMetric,
pub read_current_manifest: StorageOperationMetric,
pub publish_manifest: StorageOperationMetric,
pub write_object: StorageOperationMetric,
pub delete_object: StorageOperationMetric,
pub list_objects: StorageOperationMetric,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct PlatformIoClassCounters {
pub true_platform_async: u64,
pub platform_native_async_but_partial: u64,
pub thread_pool_managed_async: u64,
pub blocking_fallback: u64,
pub unsupported: u64,
}
impl PlatformIoClassCounters {
#[must_use]
pub fn total(self) -> u64 {
self.true_platform_async
.saturating_add(self.platform_native_async_but_partial)
.saturating_add(self.thread_pool_managed_async)
.saturating_add(self.blocking_fallback)
.saturating_add(self.unsupported)
}
#[must_use]
pub fn is_empty(self) -> bool {
self.total() == 0
}
#[must_use]
pub fn non_true_platform_async_total(self) -> u64 {
self.platform_native_async_but_partial
.saturating_add(self.thread_pool_managed_async)
.saturating_add(self.blocking_fallback)
.saturating_add(self.unsupported)
}
#[must_use]
pub fn fallback_total(self) -> u64 {
self.non_true_platform_async_total()
}
#[must_use]
pub fn uses_true_platform_async(self) -> bool {
self.true_platform_async > 0
}
#[must_use]
pub fn uses_non_true_platform_async(self) -> bool {
self.non_true_platform_async_total() > 0
}
#[must_use]
pub fn uses_fallback(self) -> bool {
self.uses_non_true_platform_async()
}
#[must_use]
pub fn has_unsupported(self) -> bool {
self.unsupported > 0
}
fn saturating_add_assign(&mut self, other: Self) {
self.true_platform_async = self
.true_platform_async
.saturating_add(other.true_platform_async);
self.platform_native_async_but_partial = self
.platform_native_async_but_partial
.saturating_add(other.platform_native_async_but_partial);
self.thread_pool_managed_async = self
.thread_pool_managed_async
.saturating_add(other.thread_pool_managed_async);
self.blocking_fallback = self
.blocking_fallback
.saturating_add(other.blocking_fallback);
self.unsupported = self.unsupported.saturating_add(other.unsupported);
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct PlatformIoOperationStats {
pub length_lookup: PlatformIoClassCounters,
pub random_read: PlatformIoClassCounters,
pub whole_object_read: PlatformIoClassCounters,
pub temp_write_rename_publish: PlatformIoClassCounters,
pub append_open: PlatformIoClassCounters,
pub append: PlatformIoClassCounters,
pub persist: PlatformIoClassCounters,
pub wal_rewrite: PlatformIoClassCounters,
pub delete: PlatformIoClassCounters,
pub directory_create: PlatformIoClassCounters,
pub directory_sync: PlatformIoClassCounters,
pub directory_listing: PlatformIoClassCounters,
pub writer_lease: PlatformIoClassCounters,
}
impl PlatformIoOperationStats {
#[must_use]
pub fn total(self) -> PlatformIoClassCounters {
let mut total = PlatformIoClassCounters::default();
total.saturating_add_assign(self.length_lookup);
total.saturating_add_assign(self.random_read);
total.saturating_add_assign(self.whole_object_read);
total.saturating_add_assign(self.temp_write_rename_publish);
total.saturating_add_assign(self.append_open);
total.saturating_add_assign(self.append);
total.saturating_add_assign(self.persist);
total.saturating_add_assign(self.wal_rewrite);
total.saturating_add_assign(self.delete);
total.saturating_add_assign(self.directory_create);
total.saturating_add_assign(self.directory_sync);
total.saturating_add_assign(self.directory_listing);
total.saturating_add_assign(self.writer_lease);
total
}
}
#[derive(Debug, Default)]
pub(crate) struct BlobReadMetrics {
count: AtomicU64,
bytes: AtomicU64,
}
impl BlobReadMetrics {
pub(crate) fn record(&self, bytes: u64) {
self.count.fetch_add(1, Ordering::Relaxed);
self.bytes.fetch_add(bytes, Ordering::Relaxed);
}
pub(crate) fn snapshot(&self) -> (u64, u64) {
(
self.count.load(Ordering::Acquire),
self.bytes.load(Ordering::Acquire),
)
}
}
#[derive(Debug, Default)]
pub(crate) struct ScanWasteMetrics {
internal_records: AtomicU64,
user_keys: AtomicU64,
tombstone_hidden_keys: AtomicU64,
}
impl ScanWasteMetrics {
pub(crate) fn record_group(&self, group_records: u64, outcome: ScanGroupOutcome) {
self.internal_records
.fetch_add(group_records, Ordering::Relaxed);
match outcome {
ScanGroupOutcome::Visible => {
self.user_keys.fetch_add(1, Ordering::Relaxed);
}
ScanGroupOutcome::HiddenByDelete => {
self.tombstone_hidden_keys.fetch_add(1, Ordering::Relaxed);
}
ScanGroupOutcome::NoVisibleVersion => {}
}
}
pub(crate) fn snapshot(&self) -> ScanWasteSnapshot {
ScanWasteSnapshot {
internal_records: self.internal_records.load(Ordering::Acquire),
user_keys: self.user_keys.load(Ordering::Acquire),
tombstone_hidden_keys: self.tombstone_hidden_keys.load(Ordering::Acquire),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ScanGroupOutcome {
Visible,
HiddenByDelete,
NoVisibleVersion,
}
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct ScanWasteSnapshot {
pub(crate) internal_records: u64,
pub(crate) user_keys: u64,
pub(crate) tombstone_hidden_keys: u64,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct LevelStats {
pub level: u32,
pub tables: usize,
pub bytes: u64,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CompactionLevelStats {
pub level: u32,
pub input_tables: u64,
pub output_tables: u64,
pub input_bytes: u64,
pub output_bytes: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum CompactionTrigger {
L0Overlap,
LevelSize,
MultiTableLevel,
TombstoneDebt,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CompactionTriggerStats {
pub trigger: CompactionTrigger,
pub runs: u64,
pub input_tables: u64,
pub output_tables: u64,
pub input_bytes: u64,
pub output_bytes: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum CompactionSkip {
LowerLevelLazy,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CompactionSkipStats {
pub skip: CompactionSkip,
pub occurrences: u64,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct FilterStats {
pub table_point_hits: u64,
pub table_point_misses: u64,
pub table_point_false_positives: u64,
pub table_prefix_hits: u64,
pub table_prefix_misses: u64,
pub table_prefix_false_positives: u64,
pub block_point_hits: u64,
pub block_point_misses: u64,
pub block_point_false_positives: u64,
pub block_prefix_hits: u64,
pub block_prefix_misses: u64,
pub block_prefix_false_positives: u64,
}
impl FilterStats {
#[must_use]
pub fn table_point_false_positive_rate(&self) -> Option<f64> {
let allowed_absent = self
.table_point_false_positives
.saturating_add(self.table_point_misses);
if allowed_absent == 0 {
return None;
}
#[allow(clippy::cast_precision_loss)]
Some(self.table_point_false_positives as f64 / allowed_absent as f64)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct LevelFilterStats {
pub level: u32,
pub tables: usize,
pub filters: FilterStats,
pub filter_resident_bytes: u64,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ReadPathStats {
pub point_table_probes: u64,
pub point_l0_table_probes: u64,
pub point_non_l0_table_probes: u64,
pub point_l0_lookup_keys: u64,
pub point_l0_overlap_extra_table_probes: u64,
pub batch_point_input_keys: u64,
pub batch_point_unique_keys: u64,
pub batch_point_table_groups: u64,
pub batch_point_l0_lookup_keys: u64,
pub batch_point_l0_overlap_extra_table_probes: u64,
pub point_index_partition_probes: u64,
pub point_block_metadata_probes: u64,
pub point_data_block_reads: u64,
pub point_filter_misses: u64,
pub range_table_probes: u64,
pub range_l0_table_probes: u64,
pub range_non_l0_table_probes: u64,
pub range_tombstone_table_probes: u64,
pub prefix_table_probes: u64,
pub prefix_tombstone_table_probes: u64,
pub prefix_block_metadata_probes: u64,
pub prefix_data_block_reads: u64,
pub prefix_filter_misses: u64,
}
impl ReadPathStats {
pub(crate) fn saturating_add_assign(&mut self, other: Self) {
self.point_table_probes = self
.point_table_probes
.saturating_add(other.point_table_probes);
self.point_l0_table_probes = self
.point_l0_table_probes
.saturating_add(other.point_l0_table_probes);
self.point_non_l0_table_probes = self
.point_non_l0_table_probes
.saturating_add(other.point_non_l0_table_probes);
self.point_l0_lookup_keys = self
.point_l0_lookup_keys
.saturating_add(other.point_l0_lookup_keys);
self.point_l0_overlap_extra_table_probes = self
.point_l0_overlap_extra_table_probes
.saturating_add(other.point_l0_overlap_extra_table_probes);
self.batch_point_input_keys = self
.batch_point_input_keys
.saturating_add(other.batch_point_input_keys);
self.batch_point_unique_keys = self
.batch_point_unique_keys
.saturating_add(other.batch_point_unique_keys);
self.batch_point_table_groups = self
.batch_point_table_groups
.saturating_add(other.batch_point_table_groups);
self.batch_point_l0_lookup_keys = self
.batch_point_l0_lookup_keys
.saturating_add(other.batch_point_l0_lookup_keys);
self.batch_point_l0_overlap_extra_table_probes = self
.batch_point_l0_overlap_extra_table_probes
.saturating_add(other.batch_point_l0_overlap_extra_table_probes);
self.point_index_partition_probes = self
.point_index_partition_probes
.saturating_add(other.point_index_partition_probes);
self.point_block_metadata_probes = self
.point_block_metadata_probes
.saturating_add(other.point_block_metadata_probes);
self.point_data_block_reads = self
.point_data_block_reads
.saturating_add(other.point_data_block_reads);
self.point_filter_misses = self
.point_filter_misses
.saturating_add(other.point_filter_misses);
self.range_table_probes = self
.range_table_probes
.saturating_add(other.range_table_probes);
self.range_l0_table_probes = self
.range_l0_table_probes
.saturating_add(other.range_l0_table_probes);
self.range_non_l0_table_probes = self
.range_non_l0_table_probes
.saturating_add(other.range_non_l0_table_probes);
self.range_tombstone_table_probes = self
.range_tombstone_table_probes
.saturating_add(other.range_tombstone_table_probes);
self.prefix_table_probes = self
.prefix_table_probes
.saturating_add(other.prefix_table_probes);
self.prefix_tombstone_table_probes = self
.prefix_tombstone_table_probes
.saturating_add(other.prefix_tombstone_table_probes);
self.prefix_block_metadata_probes = self
.prefix_block_metadata_probes
.saturating_add(other.prefix_block_metadata_probes);
self.prefix_data_block_reads = self
.prefix_data_block_reads
.saturating_add(other.prefix_data_block_reads);
self.prefix_filter_misses = self
.prefix_filter_misses
.saturating_add(other.prefix_filter_misses);
}
}
impl FilterStats {
pub(crate) fn saturating_add_assign(&mut self, other: Self) {
self.table_point_hits = self.table_point_hits.saturating_add(other.table_point_hits);
self.table_point_misses = self
.table_point_misses
.saturating_add(other.table_point_misses);
self.table_point_false_positives = self
.table_point_false_positives
.saturating_add(other.table_point_false_positives);
self.table_prefix_hits = self
.table_prefix_hits
.saturating_add(other.table_prefix_hits);
self.table_prefix_misses = self
.table_prefix_misses
.saturating_add(other.table_prefix_misses);
self.table_prefix_false_positives = self
.table_prefix_false_positives
.saturating_add(other.table_prefix_false_positives);
self.block_point_hits = self.block_point_hits.saturating_add(other.block_point_hits);
self.block_point_misses = self
.block_point_misses
.saturating_add(other.block_point_misses);
self.block_point_false_positives = self
.block_point_false_positives
.saturating_add(other.block_point_false_positives);
self.block_prefix_hits = self
.block_prefix_hits
.saturating_add(other.block_prefix_hits);
self.block_prefix_misses = self
.block_prefix_misses
.saturating_add(other.block_prefix_misses);
self.block_prefix_false_positives = self
.block_prefix_false_positives
.saturating_add(other.block_prefix_false_positives);
}
}
#[cfg(test)]
mod tests {
use super::{FilterStats, PlatformIoClassCounters, PlatformIoOperationStats};
#[test]
fn table_point_false_positive_rate_uses_allowed_absent_probes() {
let stats = FilterStats {
table_point_false_positives: 1,
table_point_misses: 3,
..FilterStats::default()
};
assert_eq!(stats.table_point_false_positive_rate(), Some(0.25));
}
#[test]
fn table_point_false_positive_rate_is_none_without_absent_probes() {
let stats = FilterStats {
table_point_hits: 10,
..FilterStats::default()
};
assert_eq!(stats.table_point_false_positive_rate(), None);
}
#[test]
fn platform_io_class_counter_helpers_summarize_classes() {
let counters = PlatformIoClassCounters {
true_platform_async: 2,
platform_native_async_but_partial: 3,
thread_pool_managed_async: 5,
blocking_fallback: 7,
unsupported: 11,
};
assert_eq!(counters.total(), 28);
assert_eq!(counters.non_true_platform_async_total(), 26);
assert_eq!(counters.fallback_total(), 26);
assert!(!counters.is_empty());
assert!(counters.uses_true_platform_async());
assert!(counters.uses_non_true_platform_async());
assert!(counters.uses_fallback());
assert!(counters.has_unsupported());
assert!(PlatformIoClassCounters::default().is_empty());
}
#[test]
fn platform_io_operation_stats_total_saturates_by_class() {
let stats = PlatformIoOperationStats {
length_lookup: PlatformIoClassCounters {
true_platform_async: u64::MAX,
thread_pool_managed_async: 1,
..PlatformIoClassCounters::default()
},
random_read: PlatformIoClassCounters {
true_platform_async: 1,
platform_native_async_but_partial: 2,
blocking_fallback: 3,
unsupported: 4,
..PlatformIoClassCounters::default()
},
directory_listing: PlatformIoClassCounters {
blocking_fallback: 5,
..PlatformIoClassCounters::default()
},
..PlatformIoOperationStats::default()
};
let total = stats.total();
assert_eq!(total.true_platform_async, u64::MAX);
assert_eq!(total.platform_native_async_but_partial, 2);
assert_eq!(total.thread_pool_managed_async, 1);
assert_eq!(total.blocking_fallback, 8);
assert_eq!(total.unsupported, 4);
}
}