use crate::journal::Lsn;
use crate::Result;
use std::time::SystemTime;
pub trait JournalBackend: Send + Sync {
fn append(&self, record: &[u8]) -> Result<Lsn>;
fn append_batch(&self, records: &[&[u8]]) -> Result<Vec<Lsn>>;
fn flush(&self, up_to: Lsn) -> Result<()>;
fn read(&self, lsn: Lsn) -> Result<Vec<u8>>;
fn backend_kind(&self) -> JournalBackendKind;
fn health(&self) -> JournalBackendHealth;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum JournalBackendKind {
KernelIoUring,
KernelDirect,
KernelBuffered,
Spdk,
}
impl JournalBackendKind {
#[must_use]
#[inline]
pub const fn as_str(self) -> &'static str {
match self {
JournalBackendKind::KernelIoUring => "kernel-io-uring",
JournalBackendKind::KernelDirect => "kernel-direct",
JournalBackendKind::KernelBuffered => "kernel-buffered",
JournalBackendKind::Spdk => "spdk",
}
}
#[must_use]
#[inline]
pub const fn is_kernel(self) -> bool {
matches!(
self,
JournalBackendKind::KernelIoUring
| JournalBackendKind::KernelDirect
| JournalBackendKind::KernelBuffered
)
}
}
impl std::fmt::Display for JournalBackendKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct JournalBackendHealth {
pub backend: JournalBackendKind,
pub queue_depth_current: usize,
pub queue_depth_max: usize,
pub appends_per_second: u64,
pub avg_append_latency_us: u64,
pub p99_append_latency_us: u64,
pub failed_appends: u64,
}
impl JournalBackendHealth {
#[must_use]
#[inline]
pub const fn empty(backend: JournalBackendKind) -> Self {
Self {
backend,
queue_depth_current: 0,
queue_depth_max: 0,
appends_per_second: 0,
avg_append_latency_us: 0,
p99_append_latency_us: 0,
failed_appends: 0,
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct JournalBackendInfo {
pub selected: JournalBackendKind,
pub selection_reason: String,
pub fallbacks_skipped: Vec<(JournalBackendKind, String)>,
pub opened_at: SystemTime,
}
impl JournalBackendInfo {
#[must_use]
pub fn single(selected: JournalBackendKind, selection_reason: impl Into<String>) -> Self {
Self {
selected,
selection_reason: selection_reason.into(),
fallbacks_skipped: Vec::new(),
opened_at: SystemTime::now(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_backend_kind_as_str_round_trip_via_match() {
let labels: Vec<&str> = [
JournalBackendKind::KernelIoUring,
JournalBackendKind::KernelDirect,
JournalBackendKind::KernelBuffered,
JournalBackendKind::Spdk,
]
.iter()
.map(|k| k.as_str())
.collect();
assert_eq!(
labels,
vec![
"kernel-io-uring",
"kernel-direct",
"kernel-buffered",
"spdk"
]
);
}
#[test]
fn test_backend_kind_display_matches_as_str() {
for k in [
JournalBackendKind::KernelIoUring,
JournalBackendKind::KernelDirect,
JournalBackendKind::KernelBuffered,
JournalBackendKind::Spdk,
] {
assert_eq!(k.to_string(), k.as_str());
}
}
#[test]
fn test_backend_kind_is_kernel_classifies_correctly() {
assert!(JournalBackendKind::KernelIoUring.is_kernel());
assert!(JournalBackendKind::KernelDirect.is_kernel());
assert!(JournalBackendKind::KernelBuffered.is_kernel());
assert!(!JournalBackendKind::Spdk.is_kernel());
}
#[test]
fn test_backend_health_empty_constructor_zeroes_counters() {
let h = JournalBackendHealth::empty(JournalBackendKind::KernelBuffered);
assert_eq!(h.backend, JournalBackendKind::KernelBuffered);
assert_eq!(h.queue_depth_current, 0);
assert_eq!(h.queue_depth_max, 0);
assert_eq!(h.appends_per_second, 0);
assert_eq!(h.avg_append_latency_us, 0);
assert_eq!(h.p99_append_latency_us, 0);
assert_eq!(h.failed_appends, 0);
}
#[test]
fn test_backend_info_single_no_fallbacks() {
let info = JournalBackendInfo::single(
JournalBackendKind::KernelBuffered,
"explicit Method::Sync request",
);
assert_eq!(info.selected, JournalBackendKind::KernelBuffered);
assert!(info.selection_reason.contains("Method::Sync"));
assert!(info.fallbacks_skipped.is_empty());
let elapsed = info
.opened_at
.elapsed()
.unwrap_or(std::time::Duration::from_secs(0));
assert!(elapsed < std::time::Duration::from_secs(2));
}
}