1use std::sync::atomic::Ordering;
5use std::sync::Arc;
6
7use super::Store;
8
9#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
13pub struct WriteCounters {
14 pub admission_waits: u64,
16 pub admission_wait_ms: u64,
18 pub checkpoints: u64,
21 pub checkpoint_wait_ms: u64,
23 pub wal_bytes_appended: u64,
25 pub applied_bytes: u64,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35pub enum CommitPhase {
36 AdmissionWait,
38 Build,
40 Expand,
42 Durable,
45 WalEncode,
47 WalAppendSync,
49 BackendApply,
51 Derive,
53 Publish,
55}
56
57impl CommitPhase {
58 pub const ALL: [Self; 9] = [
60 Self::AdmissionWait,
61 Self::Build,
62 Self::Expand,
63 Self::Durable,
64 Self::WalEncode,
65 Self::WalAppendSync,
66 Self::BackendApply,
67 Self::Derive,
68 Self::Publish,
69 ];
70
71 #[must_use]
73 pub const fn label(self) -> &'static str {
74 match self {
75 Self::AdmissionWait => "admission_wait",
76 Self::Build => "build",
77 Self::Expand => "expand",
78 Self::Durable => "durable",
79 Self::WalEncode => "wal_encode",
80 Self::WalAppendSync => "wal_append_sync",
81 Self::BackendApply => "backend_apply",
82 Self::Derive => "derive",
83 Self::Publish => "publish",
84 }
85 }
86}
87
88pub type CommitObserver = dyn Fn(CommitPhase, f64) + Send + Sync;
90
91#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
93pub struct BackendLockTimes {
94 pub wait_ns: u64,
96 pub hold_ns: u64,
98 pub hold_max_ns: u64,
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub enum WriterHolder {
107 Publish,
109 HistoryGc,
111 Checkpoint,
113 Pause,
115}
116
117impl WriterHolder {
118 pub const ALL: [Self; 4] = [
120 Self::Publish,
121 Self::HistoryGc,
122 Self::Checkpoint,
123 Self::Pause,
124 ];
125
126 #[must_use]
128 pub const fn label(self) -> &'static str {
129 match self {
130 Self::Publish => "publish",
131 Self::HistoryGc => "history_gc",
132 Self::Checkpoint => "checkpoint",
133 Self::Pause => "pause",
134 }
135 }
136
137 pub(crate) const fn index(self) -> usize {
138 match self {
139 Self::Publish => 0,
140 Self::HistoryGc => 1,
141 Self::Checkpoint => 2,
142 Self::Pause => 3,
143 }
144 }
145}
146
147pub const WRITER_HOLD_BOUNDS: [f64; 12] = [
149 0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0,
150];
151
152#[derive(Debug, Clone, Copy, Default, PartialEq)]
154pub struct WriterHolds {
155 pub counts: [u64; WRITER_HOLD_BOUNDS.len() + 1],
158 pub sum_seconds: f64,
160}
161
162impl WriterHolds {
163 #[must_use]
165 pub fn plus(mut self, other: &Self) -> Self {
166 for (total, count) in self.counts.iter_mut().zip(other.counts) {
167 *total = total.saturating_add(count);
168 }
169 self.sum_seconds += other.sum_seconds;
170 self
171 }
172}
173
174impl Store {
175 pub fn set_commit_observer(&self, observer: Arc<CommitObserver>) {
179 *self.health.cleanup(self.commit_observer.write()) = Some(observer);
180 }
181
182 pub(crate) fn observe_phase(&self, phase: CommitPhase, started: std::time::Instant) {
184 let observer = self.health.cleanup(self.commit_observer.read()).clone();
185 if let Some(observer) = observer {
186 observer(phase, started.elapsed().as_secs_f64());
187 }
188 }
189
190 #[must_use]
192 pub fn backend_lock_times(&self) -> BackendLockTimes {
193 self.all_backends()
194 .iter()
195 .map(|backend| backend.lock_times())
196 .fold(BackendLockTimes::default(), |total, times| {
197 BackendLockTimes {
198 wait_ns: total.wait_ns.saturating_add(times.wait_ns),
199 hold_ns: total.hold_ns.saturating_add(times.hold_ns),
200 hold_max_ns: total.hold_max_ns.max(times.hold_max_ns),
201 }
202 })
203 }
204
205 #[must_use]
207 pub fn writer_holds(&self) -> Vec<(WriterHolder, WriterHolds)> {
208 let mut totals = [WriterHolds::default(); WriterHolder::ALL.len()];
209 for backend in self.all_backends() {
210 for (holder, holds) in backend.writer_holds() {
211 totals[holder.index()] = totals[holder.index()].plus(&holds);
212 }
213 }
214 WriterHolder::ALL.into_iter().zip(totals).collect()
215 }
216
217 #[must_use]
220 pub fn copy_on_write(&self) -> Option<crate::CopyOnWrite> {
221 self.health.cleanup(self.copy_on_write.lock()).clone()
222 }
223
224 #[must_use]
226 pub fn data_dir(&self) -> Option<std::path::PathBuf> {
227 self.health
228 .cleanup(self.data_path.lock())
229 .as_deref()
230 .and_then(std::path::Path::parent)
231 .map(std::path::Path::to_path_buf)
232 }
233
234 #[must_use]
236 pub fn write_counters(&self) -> WriteCounters {
237 WriteCounters {
238 admission_waits: self.admission_waits.load(Ordering::Relaxed),
239 admission_wait_ms: self.admission_wait_ms.load(Ordering::Relaxed),
240 checkpoints: self.checkpoints.load(Ordering::Relaxed),
241 checkpoint_wait_ms: self.checkpoint_wait_ms.load(Ordering::Relaxed),
242 wal_bytes_appended: self.wal_bytes_appended.load(Ordering::Relaxed),
243 applied_bytes: self.applied_bytes.load(Ordering::Relaxed),
244 }
245 }
246
247 #[must_use]
249 pub fn conflict_window_bytes(&self) -> usize {
250 self.health.cleanup(self.conflicts.lock()).bytes()
251 }
252}