use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Duration;
use linkrs_core::types::Timestamp;
use super::execution::TransactionInfo;
const CONFLICT_WINDOW_BUCKETS: usize = 60;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ConflictBreakdown {
pub write_write: u64,
pub read_write: u64,
pub phantom: u64,
pub schema_generation: u64,
pub index_generation: u64,
}
#[derive(Debug, Default)]
pub struct TransactionMetrics {
pub avg_duration: Duration,
pub p50_duration: Duration,
pub p95_duration: Duration,
pub p99_duration: Duration,
pub long_transactions: Vec<TransactionInfo>,
pub total_count: u64,
pub conflict_rate: f64,
pub conflict_rate_windowed: f64,
pub active_transactions: u64,
pub committed_transactions: u64,
pub aborted_transactions: u64,
pub timeout_transactions: u64,
pub disconnect_transactions: u64,
pub cleanup_failure_transactions: u64,
pub hook_dispatch_total: u64,
pub hook_panic_total: u64,
pub hook_slow_total: u64,
pub active_statements: u64,
pub conflict_breakdown: ConflictBreakdown,
}
impl TransactionMetrics {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct TransactionResourceMetrics {
pub active_snapshots: u64,
pub pending_writes: i32,
pub committed_frontier_lag: Timestamp,
pub staged_wal_bytes: u64,
pub undo_bytes: u64,
pub checkpoint_drain_time: Duration,
}
#[derive(Debug)]
pub struct TransactionStats {
pub total_transactions: AtomicU64,
pub active_transactions: AtomicU64,
pub committed_transactions: AtomicU64,
pub aborted_transactions: AtomicU64,
pub timeout_transactions: AtomicU64,
pub conflict_transactions: AtomicU64,
pub conflict_write_write: AtomicU64,
pub conflict_read_write: AtomicU64,
pub conflict_phantom: AtomicU64,
pub conflict_schema_generation: AtomicU64,
pub conflict_index_generation: AtomicU64,
pub disconnect_transactions: AtomicU64,
pub recovery_abort_transactions: AtomicU64,
pub cleanup_failure_transactions: AtomicU64,
pub hook_dispatch_total: AtomicU64,
pub hook_panic_total: AtomicU64,
pub hook_slow_total: AtomicU64,
pub active_statements: AtomicU64,
window_buckets: Vec<AtomicU64>,
window_head: AtomicU64,
window_last_epoch_sec: AtomicU64,
}
impl Default for TransactionStats {
fn default() -> Self {
Self {
total_transactions: AtomicU64::new(0),
active_transactions: AtomicU64::new(0),
committed_transactions: AtomicU64::new(0),
aborted_transactions: AtomicU64::new(0),
timeout_transactions: AtomicU64::new(0),
conflict_transactions: AtomicU64::new(0),
conflict_write_write: AtomicU64::new(0),
conflict_read_write: AtomicU64::new(0),
conflict_phantom: AtomicU64::new(0),
conflict_schema_generation: AtomicU64::new(0),
conflict_index_generation: AtomicU64::new(0),
disconnect_transactions: AtomicU64::new(0),
recovery_abort_transactions: AtomicU64::new(0),
cleanup_failure_transactions: AtomicU64::new(0),
hook_dispatch_total: AtomicU64::new(0),
hook_panic_total: AtomicU64::new(0),
hook_slow_total: AtomicU64::new(0),
active_statements: AtomicU64::new(0),
window_buckets: (0..CONFLICT_WINDOW_BUCKETS)
.map(|_| AtomicU64::new(0))
.collect(),
window_head: AtomicU64::new(0),
window_last_epoch_sec: AtomicU64::new(0),
}
}
}
impl TransactionStats {
pub fn new() -> Self {
Self::default()
}
pub fn increment_total(&self) {
self.total_transactions.fetch_add(1, Ordering::Relaxed);
}
pub fn increment_active(&self) {
self.active_transactions.fetch_add(1, Ordering::Relaxed);
}
pub fn decrement_active(&self) {
self.active_transactions.fetch_sub(1, Ordering::Relaxed);
}
pub fn increment_committed(&self) {
self.committed_transactions.fetch_add(1, Ordering::Relaxed);
}
pub fn increment_aborted(&self) {
self.aborted_transactions.fetch_add(1, Ordering::Relaxed);
}
pub fn increment_timeout(&self) {
self.timeout_transactions.fetch_add(1, Ordering::Relaxed);
}
pub fn record_timeout(&self) {
self.increment_timeout();
}
pub fn increment_disconnect(&self) {
self.disconnect_transactions.fetch_add(1, Ordering::Relaxed);
}
pub fn increment_cleanup_failure(&self) {
self.cleanup_failure_transactions
.fetch_add(1, Ordering::Relaxed);
}
pub fn record_hook_dispatch(&self, delivered: usize, panics: usize) {
self.hook_dispatch_total
.fetch_add(delivered as u64, Ordering::Relaxed);
if panics > 0 {
self.hook_panic_total
.fetch_add(panics as u64, Ordering::Relaxed);
}
}
pub fn record_hook_slow(&self, slow: usize) {
if slow > 0 {
self.hook_slow_total
.fetch_add(slow as u64, Ordering::Relaxed);
}
}
pub fn begin_statement(&self) {
self.active_statements.fetch_add(1, Ordering::Relaxed);
}
pub fn end_statement(&self) {
let _ =
self.active_statements
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |value| {
value.checked_sub(1)
});
}
pub fn record_txn_begin(&self) {
self.increment_total();
self.increment_active();
}
pub fn record_txn_commit(&self) {
self.decrement_active();
self.increment_committed();
}
pub fn record_txn_rollback(&self) {
self.decrement_active();
self.increment_aborted();
}
pub fn record_txn_conflict(&self) {
self.conflict_transactions.fetch_add(1, Ordering::Relaxed);
self.record_conflict_in_window();
}
pub fn record_txn_conflict_with_type(&self, kind: crate::certify::ConflictType) {
self.conflict_transactions.fetch_add(1, Ordering::Relaxed);
match kind {
crate::certify::ConflictType::WriteWrite => {
self.conflict_write_write.fetch_add(1, Ordering::Relaxed);
}
crate::certify::ConflictType::ReadWrite => {
self.conflict_read_write.fetch_add(1, Ordering::Relaxed);
}
crate::certify::ConflictType::Phantom => {
self.conflict_phantom.fetch_add(1, Ordering::Relaxed);
}
crate::certify::ConflictType::SchemaGeneration => {
self.conflict_schema_generation
.fetch_add(1, Ordering::Relaxed);
}
crate::certify::ConflictType::IndexGeneration => {
self.conflict_index_generation
.fetch_add(1, Ordering::Relaxed);
}
}
self.record_conflict_in_window();
}
pub fn conflict_breakdown(&self) -> ConflictBreakdown {
ConflictBreakdown {
write_write: self.conflict_write_write.load(Ordering::Relaxed),
read_write: self.conflict_read_write.load(Ordering::Relaxed),
phantom: self.conflict_phantom.load(Ordering::Relaxed),
schema_generation: self.conflict_schema_generation.load(Ordering::Relaxed),
index_generation: self.conflict_index_generation.load(Ordering::Relaxed),
}
}
fn record_conflict_in_window(&self) {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let last = self.window_last_epoch_sec.load(Ordering::Relaxed);
let mut head = self.window_head.load(Ordering::Relaxed) as usize;
if now > last {
let elapsed = (now - last) as usize;
let n = CONFLICT_WINDOW_BUCKETS;
if elapsed >= n {
for bucket in &self.window_buckets {
bucket.store(0, Ordering::Relaxed);
}
head = 0;
} else {
for i in 0..elapsed {
let idx = (head + i) % n;
self.window_buckets[idx].store(0, Ordering::Relaxed);
}
head = (head + elapsed) % n;
}
self.window_head.store(head as u64, Ordering::Relaxed);
self.window_last_epoch_sec.store(now, Ordering::Relaxed);
}
let idx = head % CONFLICT_WINDOW_BUCKETS;
self.window_buckets[idx].fetch_add(1, Ordering::Relaxed);
}
pub fn conflict_rate(&self) -> f64 {
let total = self.total_transactions.load(Ordering::Relaxed);
if total == 0 {
0.0
} else {
let conflicts = self.conflict_transactions.load(Ordering::Relaxed);
conflicts as f64 / total as f64
}
}
pub fn conflict_rate_windowed(&self) -> f64 {
let total: u64 = self
.window_buckets
.iter()
.map(|b| b.load(Ordering::Relaxed))
.sum();
total as f64 / CONFLICT_WINDOW_BUCKETS as f64
}
}