1use std::sync::Arc;
5
6use super::Store;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum CommitPhase {
12 AdmissionWait,
14 Build,
16 Expand,
18 Durable,
21 WalEncode,
23 WalAppendSync,
25 BackendApply,
27 Derive,
29 Publish,
31}
32
33impl CommitPhase {
34 pub const ALL: [Self; 9] = [
36 Self::AdmissionWait,
37 Self::Build,
38 Self::Expand,
39 Self::Durable,
40 Self::WalEncode,
41 Self::WalAppendSync,
42 Self::BackendApply,
43 Self::Derive,
44 Self::Publish,
45 ];
46
47 #[must_use]
49 pub const fn label(self) -> &'static str {
50 match self {
51 Self::AdmissionWait => "admission_wait",
52 Self::Build => "build",
53 Self::Expand => "expand",
54 Self::Durable => "durable",
55 Self::WalEncode => "wal_encode",
56 Self::WalAppendSync => "wal_append_sync",
57 Self::BackendApply => "backend_apply",
58 Self::Derive => "derive",
59 Self::Publish => "publish",
60 }
61 }
62}
63
64pub type CommitObserver = dyn Fn(CommitPhase, f64) + Send + Sync;
66
67#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
69pub struct BackendLockTimes {
70 pub wait_ns: u64,
72 pub hold_ns: u64,
74 pub hold_max_ns: u64,
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum WriterHolder {
83 Publish,
85 HistoryGc,
87 Checkpoint,
89 Pause,
91}
92
93impl WriterHolder {
94 pub const ALL: [Self; 4] = [
96 Self::Publish,
97 Self::HistoryGc,
98 Self::Checkpoint,
99 Self::Pause,
100 ];
101
102 #[must_use]
104 pub const fn label(self) -> &'static str {
105 match self {
106 Self::Publish => "publish",
107 Self::HistoryGc => "history_gc",
108 Self::Checkpoint => "checkpoint",
109 Self::Pause => "pause",
110 }
111 }
112
113 pub(crate) const fn index(self) -> usize {
114 match self {
115 Self::Publish => 0,
116 Self::HistoryGc => 1,
117 Self::Checkpoint => 2,
118 Self::Pause => 3,
119 }
120 }
121}
122
123pub const WRITER_HOLD_BOUNDS: [f64; 12] = [
125 0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0,
126];
127
128#[derive(Debug, Clone, Copy, Default, PartialEq)]
130pub struct WriterHolds {
131 pub counts: [u64; WRITER_HOLD_BOUNDS.len() + 1],
134 pub sum_seconds: f64,
136}
137
138impl WriterHolds {
139 #[must_use]
141 pub fn plus(mut self, other: &Self) -> Self {
142 for (total, count) in self.counts.iter_mut().zip(other.counts) {
143 *total = total.saturating_add(count);
144 }
145 self.sum_seconds += other.sum_seconds;
146 self
147 }
148}
149
150impl Store {
151 pub fn set_commit_observer(&self, observer: Arc<CommitObserver>) {
155 *self.health.cleanup(self.commit_observer.write()) = Some(observer);
156 }
157
158 pub(crate) fn observe_phase(&self, phase: CommitPhase, started: std::time::Instant) {
160 let observer = self.health.cleanup(self.commit_observer.read()).clone();
161 if let Some(observer) = observer {
162 observer(phase, started.elapsed().as_secs_f64());
163 }
164 }
165
166 #[must_use]
168 pub fn backend_lock_times(&self) -> BackendLockTimes {
169 self.all_backends()
170 .iter()
171 .map(|backend| backend.lock_times())
172 .fold(BackendLockTimes::default(), |total, times| {
173 BackendLockTimes {
174 wait_ns: total.wait_ns.saturating_add(times.wait_ns),
175 hold_ns: total.hold_ns.saturating_add(times.hold_ns),
176 hold_max_ns: total.hold_max_ns.max(times.hold_max_ns),
177 }
178 })
179 }
180
181 #[must_use]
183 pub fn writer_holds(&self) -> Vec<(WriterHolder, WriterHolds)> {
184 let mut totals = [WriterHolds::default(); WriterHolder::ALL.len()];
185 for backend in self.all_backends() {
186 for (holder, holds) in backend.writer_holds() {
187 totals[holder.index()] = totals[holder.index()].plus(&holds);
188 }
189 }
190 WriterHolder::ALL.into_iter().zip(totals).collect()
191 }
192
193 #[must_use]
196 pub fn copy_on_write(&self) -> Option<crate::CopyOnWrite> {
197 self.health.cleanup(self.copy_on_write.lock()).clone()
198 }
199
200 #[must_use]
202 pub fn data_dir(&self) -> Option<std::path::PathBuf> {
203 self.health
204 .cleanup(self.data_path.lock())
205 .as_deref()
206 .and_then(std::path::Path::parent)
207 .map(std::path::Path::to_path_buf)
208 }
209
210 #[must_use]
212 pub fn conflict_window_bytes(&self) -> usize {
213 self.health.cleanup(self.conflicts.lock()).bytes()
214 }
215}