1#![allow(clippy::unwrap_used, clippy::expect_used)]
10
11use lazy_static::lazy_static;
12use prometheus::{
13 register_counter, register_counter_vec, register_gauge, register_gauge_vec, register_histogram_vec,
14 register_histogram, Counter, CounterVec, Gauge, GaugeVec, HistogramVec, Histogram, Registry,
15};
16use std::sync::atomic::{AtomicU64, Ordering};
17
18lazy_static! {
19 pub static ref REGISTRY: Registry = Registry::new();
21
22 pub static ref REPLICATION_LATENCY: HistogramVec = register_histogram_vec!(
24 "foxing_replication_latency_seconds",
25 "End-to-end latency from source event to target write",
26 &["target"]
27 ).unwrap();
28 pub static ref INODE_LOOKUP_DURATION: Histogram = register_histogram!(
30 "foxing_inode_lookup_duration_seconds",
31 "Time spent resolving inode to path",
32 vec![0.001, 0.01, 0.1, 1.0]
33 ).unwrap();
34
35 pub static ref BYTES_REPLICATED: CounterVec = register_counter_vec!(
37 "foxing_bytes_replicated_total",
38 "Bytes successfully written to target",
39 &["target"]
40 ).unwrap();
41 pub static ref RENAME_EVENTS: Counter = register_counter!(
43 "foxing_rename_events_total",
44 "Total rename operations processed"
45 ).unwrap();
46 pub static ref COALESCED_WRITES: Counter = register_counter!(
48 "foxing_coalesced_writes_total",
49 "Number of write events merged into larger chunks"
50 ).unwrap();
51
52 pub static ref COPY_METHOD_REFLINK: CounterVec = register_counter_vec!(
54 "foxing_copy_method_reflink_total",
55 "Writes handled via CoW Reflink",
56 &["target"]
57 ).unwrap();
58 pub static ref COPY_METHOD_OFFLOAD: CounterVec = register_counter_vec!(
60 "foxing_copy_method_offload_total",
61 "Writes handled via hardware/network offload",
62 &["target"]
63 ).unwrap();
64 pub static ref COPY_METHOD_STANDARD: CounterVec = register_counter_vec!(
66 "foxing_copy_method_standard_total",
67 "Writes handled via standard read/write",
68 &["target"]
69 ).unwrap();
70 pub static ref ATOMIC_WRITE_FALLBACKS: Counter = register_counter!(
72 "foxing_atomic_write_fallbacks_total",
73 "Number of atomic writes that failed and fell back to buffered/standard I/O"
74 ).unwrap();
75 pub static ref COPY_METHOD_SENDFILE_FALLBACK: CounterVec = register_counter_vec!(
77 "foxing_copy_method_sendfile_fallback_total",
78 "Copies that fell back from io_uring to sendfile",
79 &["target"]
80 ).unwrap();
81}
82
83lazy_static! {
84 pub static ref VERSIONING_FAILURES: Counter = register_counter!(
86 "foxing_versioning_failures_total",
87 "Failed attempts to create version snapshots"
88 ).unwrap();
89 pub static ref VERSIONING_SUCCESS: Counter = register_counter!(
91 "foxing_versioning_success_total",
92 "Successfully created version snapshots"
93 ).unwrap();
94
95 pub static ref GOVERNOR_STRESSED: Gauge = register_gauge!(
97 "foxing_governor_stressed",
98 "Current system stress state (1=stressed, 0=normal)"
99 ).unwrap();
100 pub static ref GOVERNOR_STRESS_SCORE: Gauge = register_gauge!(
102 "foxing_governor_stress_score",
103 "Current system stress score (0.0-1.0=OK, >1.0=Critical)"
104 ).unwrap();
105 pub static ref GOVERNOR_THROTTLED_EVENTS: Counter = register_counter!(
107 "foxing_governor_throttled_events_total",
108 "Events delayed due to governor pressure"
109 ).unwrap();
110 pub static ref GOVERNOR_LOAD_AVERAGE: GaugeVec = register_gauge_vec!(
112 "foxing_governor_load_average",
113 "System load average (1m, 5m, 15m)",
114 &["period"]
115 ).unwrap();
116 pub static ref GOVERNOR_PACING_DURATION_MS: Counter = register_counter!(
118 "foxing_governor_pacing_duration_milliseconds_total",
119 "Total time spent sleeping due to governor pacing"
120 ).unwrap();
121
122 pub static ref DM_STACK_DEPTH: Gauge = register_gauge!(
124 "foxing_dm_stack_depth",
125 "Number of device-mapper layers beneath the filesystem"
126 ).unwrap();
127 pub static ref DM_CRYPT_DETECTED: Gauge = register_gauge!(
129 "foxing_dm_crypt_detected",
130 "1 if dm-crypt (LUKS) detected in storage stack"
131 ).unwrap();
132 pub static ref STORAGE_PHYSICAL_BLOCK_SIZE: Gauge = register_gauge!(
134 "foxing_storage_physical_block_size",
135 "Physical block size of base storage device (bytes)"
136 ).unwrap();
137
138 pub static ref GLOBAL_BUFFER_LIMIT: Gauge = register_gauge!(
140 "foxing_global_buffer_limit_bytes",
141 "Maximum configured memory for buffers (Bytes)"
142 ).unwrap();
143 pub static ref GLOBAL_BUFFER_COUNT: AtomicU64 = AtomicU64::new(0);
145 pub static ref GLOBAL_MEMORY_USAGE_BYTES: Gauge = register_gauge!(
147 "foxing_global_memory_usage_bytes",
148 "Current total memory allocated for IO buffers across all workers"
149 ).unwrap();
150
151 pub static ref BUFFER_POOL_CAPACITY: Gauge = register_gauge!(
153 "foxing_buffer_pool_capacity_buffers",
154 "Number of buffers in the worker's buffer pool"
155 ).unwrap();
156 pub static ref BUFFER_POOL_CHUNK_SIZE: Gauge = register_gauge!(
158 "foxing_buffer_pool_chunk_size_bytes",
159 "Size of each buffer chunk in bytes"
160 ).unwrap();
161 pub static ref BUFFER_POOL_TOTAL_BYTES: Gauge = register_gauge!(
163 "foxing_buffer_pool_total_bytes",
164 "Total memory allocated to buffer pool"
165 ).unwrap();
166}
167
168lazy_static! {
169 pub static ref SIDECAR_FILES_CREATED: Counter = register_counter!(
171 "foxing_sidecar_files_created_total",
172 "Number of .foxing_meta files created (fallback metadata)"
173 ).unwrap();
174
175 pub static ref HASH_VERIFICATIONS_TOTAL: Counter = register_counter!(
177 "foxing_hash_verifications_total",
178 "Number of BLAKE3 hash verifications performed"
179 ).unwrap();
180 pub static ref HASH_CACHE_HITS: Counter = register_counter!(
182 "foxing_hash_cache_hits_total",
183 "Files skipped due to matching signature cache"
184 ).unwrap();
185 pub static ref NFS_BYPASS_KRB5_FALLBACK: Counter = register_counter!(
187 "foxing_nfs_bypass_krb5_fallback_total",
188 "NFS bypass unavailable due to Kerberos auth -- fell back to kernel VFS path"
189 ).unwrap();
190 pub static ref NFS_BYPASS_TLS_FALLBACK: Counter = register_counter!(
192 "foxing_nfs_bypass_tls_fallback_total",
193 "NFS bypass TLS upgrade skipped -- xprtsec=tls mount but tls feature disabled or STARTTLS rejected"
194 ).unwrap();
195 pub static ref NFS_BYPASS_DOUBLE_ENCRYPTION: Counter = register_counter!(
197 "foxing_nfs_bypass_double_encryption_total",
198 "sec=krb5p + xprtsec=tls detected -- redundant double encryption"
199 ).unwrap();
200 pub static ref NFS_BYPASS_KTLS_PROMOTED: Counter = register_counter!(
202 "foxing_nfs_bypass_ktls_promoted_total",
203 "kTLS kernel offload promoted after userspace TLS handshake"
204 ).unwrap();
205
206 pub static ref HASH_COMPUTATION_DURATION: Histogram = register_histogram!(
208 "foxing_hash_computation_seconds",
209 "Time spent computing BLAKE3 hashes"
210 ).unwrap();
211}
212
213pub fn initialize_metrics(global_limit_mb: u64) {
215 GLOBAL_BUFFER_LIMIT.set((global_limit_mb * 1024 * 1024) as f64);
216}
217
218pub struct BatchedCounter {
223 counter: Counter,
224 local_count: u64,
225 batch_size: u64,
226}
227
228impl BatchedCounter {
229 pub fn new(counter: Counter, batch_size: u64) -> Self {
230 Self {
231 counter,
232 local_count: 0,
233 batch_size,
234 }
235 }
236
237 #[inline]
238 pub fn inc(&mut self) {
239 self.local_count += 1;
240 if self.local_count >= self.batch_size {
241 self.flush();
242 }
243 }
244
245 #[inline]
246 pub fn inc_by(&mut self, val: u64) {
247 self.local_count += val;
248 if self.local_count >= self.batch_size {
249 self.flush();
250 }
251 }
252
253 #[inline]
254 pub fn flush(&mut self) {
255 if self.local_count > 0 {
256 self.counter.inc_by(self.local_count as f64);
257 self.local_count = 0;
258 }
259 }
260}
261
262impl Drop for BatchedCounter {
263 fn drop(&mut self) {
264 self.flush();
265 }
266}
267
268pub struct BatchedAtomicCounter {
272 global: &'static AtomicU64,
273 local_count: i64,
274 batch_size: i64,
275}
276
277impl BatchedAtomicCounter {
278 pub fn new(global: &'static AtomicU64, batch_size: i64) -> Self {
279 Self {
280 global,
281 local_count: 0,
282 batch_size,
283 }
284 }
285
286 #[inline]
287 pub fn inc(&mut self) {
288 self.local_count += 1;
289 if self.local_count >= self.batch_size {
290 self.flush();
291 }
292 }
293
294 #[inline]
295 pub fn dec(&mut self) {
296 self.local_count -= 1;
297 if self.local_count <= -self.batch_size {
298 self.flush();
299 }
300 }
301
302 #[inline]
303 pub fn flush(&mut self) {
304 if self.local_count != 0 {
305 if self.local_count > 0 {
306 self.global.fetch_add(self.local_count as u64, Ordering::Relaxed);
307 } else {
308 self.global.fetch_sub((-self.local_count) as u64, Ordering::Relaxed);
309 }
310 self.local_count = 0;
311 }
312 }
313}
314
315impl Drop for BatchedAtomicCounter {
316 fn drop(&mut self) {
317 self.flush();
318 }
319}
320
321lazy_static! {
322 pub static ref RDMA_RPC_LATENCY_US: Histogram = register_histogram!(
324 "foxing_rdma_rpc_latency_us",
325 "RDMA per-RPC round-trip latency in microseconds",
326 vec![1.0, 2.0, 5.0, 10.0, 20.0, 50.0, 100.0, 500.0, 1000.0]
327 ).expect("failed to register foxing_rdma_rpc_latency_us");
328}