lcpfs 2026.1.102

LCP File System - A ZFS-inspired copy-on-write filesystem for Rust
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
// Copyright 2025 LunaOS Contributors
// SPDX-License-Identifier: Apache-2.0

//! ZPL Metrics Collector
//!
//! Collects real-time metrics from the ZPL (ZFS POSIX Layer), ARC cache,
//! DMU, and other filesystem components for telemetry export.
//!
//! ## Architecture
//!
//! ```text
//! ┌─────────────────────────────────────────────────────────────────────┐
//! │                     Telemetry System                                │
//! │                  (Prometheus Export)                                │
//! └─────────────────────────────────────────────────────────────────────┘
//!//!//! ┌─────────────────────────────────────────────────────────────────────┐
//! │                   ZplMetricsCollector                               │
//! │                                                                     │
//! │  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐  │
//! │  │   ZPL   │  │   ARC   │  │   DMU   │  │  Dedup  │  │   ZIL   │  │
//! │  │ Stats   │  │  Stats  │  │  Stats  │  │  Stats  │  │  Stats  │  │
//! │  └─────────┘  └─────────┘  └─────────┘  └─────────┘  └─────────┘  │
//! └─────────────────────────────────────────────────────────────────────┘
//! ```
//!
//! ## Metrics Collected
//!
//! - **Storage**: used bytes, quota, file count
//! - **Operations**: read/write ops, read/write bytes
//! - **Cache**: ARC hits, ARC misses, L2ARC stats
//! - **Transactions**: TXG count, sync latency
//! - **Dedup**: ratio, table size, hits/misses

use alloc::string::ToString;
use alloc::vec::Vec;
use core::sync::atomic::{AtomicU64, Ordering};
use lazy_static::lazy_static;
use spin::Mutex;

use super::{MetricId, Telemetry, TelemetryError};
use crate::storage::zpl::ZPL;

// ═══════════════════════════════════════════════════════════════════════════════
// OPERATION COUNTERS (Global statistics)
// ═══════════════════════════════════════════════════════════════════════════════

/// Global read operations counter
pub static READ_OPS: AtomicU64 = AtomicU64::new(0);
/// Global write operations counter
pub static WRITE_OPS: AtomicU64 = AtomicU64::new(0);
/// Global bytes read counter
pub static BYTES_READ: AtomicU64 = AtomicU64::new(0);
/// Global bytes written counter
pub static BYTES_WRITTEN: AtomicU64 = AtomicU64::new(0);
/// Global fsync operations counter
pub static FSYNC_OPS: AtomicU64 = AtomicU64::new(0);
/// Global lookup operations counter
pub static LOOKUP_OPS: AtomicU64 = AtomicU64::new(0);
/// Global create operations counter
pub static CREATE_OPS: AtomicU64 = AtomicU64::new(0);
/// Global delete operations counter
pub static DELETE_OPS: AtomicU64 = AtomicU64::new(0);
/// Global open file handles
pub static OPEN_HANDLES: AtomicU64 = AtomicU64::new(0);

// ARC cache statistics
/// ARC cache hits
pub static ARC_HITS: AtomicU64 = AtomicU64::new(0);
/// ARC cache misses
pub static ARC_MISSES: AtomicU64 = AtomicU64::new(0);
/// ARC evictions
pub static ARC_EVICTIONS: AtomicU64 = AtomicU64::new(0);
/// L2ARC hits
pub static L2ARC_HITS: AtomicU64 = AtomicU64::new(0);
/// L2ARC misses
pub static L2ARC_MISSES: AtomicU64 = AtomicU64::new(0);

// Transaction group statistics
/// Current TXG number
pub static CURRENT_TXG: AtomicU64 = AtomicU64::new(1);
/// TXG sync count
pub static TXG_SYNCS: AtomicU64 = AtomicU64::new(0);
/// Total TXG sync time in microseconds
pub static TXG_SYNC_TIME_US: AtomicU64 = AtomicU64::new(0);

// Dedup statistics
/// Dedup hits
pub static DEDUP_HITS: AtomicU64 = AtomicU64::new(0);
/// Dedup misses (new blocks)
pub static DEDUP_MISSES: AtomicU64 = AtomicU64::new(0);
/// Dedup table entries
pub static DEDUP_ENTRIES: AtomicU64 = AtomicU64::new(0);

// ═══════════════════════════════════════════════════════════════════════════════
// METRIC HANDLES
// ═══════════════════════════════════════════════════════════════════════════════

/// Registered metric IDs for LCPFS
#[derive(Debug, Clone)]
pub struct LcpfsMetrics {
    // Storage metrics
    /// Used bytes gauge
    pub used_bytes: MetricId,
    /// Quota bytes gauge
    pub quota_bytes: MetricId,
    /// Available bytes gauge
    pub available_bytes: MetricId,
    /// File count gauge
    pub file_count: MetricId,
    /// Directory count gauge
    pub dir_count: MetricId,

    // Operation counters
    /// Read operations counter
    pub read_ops: MetricId,
    /// Write operations counter
    pub write_ops: MetricId,
    /// Read bytes counter
    pub read_bytes: MetricId,
    /// Write bytes counter
    pub write_bytes: MetricId,
    /// Fsync operations counter
    pub fsync_ops: MetricId,
    /// Lookup operations counter
    pub lookup_ops: MetricId,
    /// Create operations counter
    pub create_ops: MetricId,
    /// Delete operations counter
    pub delete_ops: MetricId,
    /// Open file handles gauge
    pub open_handles: MetricId,

    // ARC cache metrics
    /// ARC hits counter
    pub arc_hits: MetricId,
    /// ARC misses counter
    pub arc_misses: MetricId,
    /// ARC hit ratio gauge
    pub arc_hit_ratio: MetricId,
    /// ARC size bytes gauge
    pub arc_size_bytes: MetricId,
    /// ARC evictions counter
    pub arc_evictions: MetricId,
    /// L2ARC hits counter
    pub l2arc_hits: MetricId,
    /// L2ARC misses counter
    pub l2arc_misses: MetricId,

    // Transaction metrics
    /// Current TXG gauge
    pub current_txg: MetricId,
    /// TXG sync count counter
    pub txg_syncs: MetricId,
    /// TXG sync latency histogram
    pub txg_sync_latency: MetricId,

    // Dedup metrics
    /// Dedup ratio gauge
    pub dedup_ratio: MetricId,
    /// Dedup hits counter
    pub dedup_hits: MetricId,
    /// Dedup misses counter
    pub dedup_misses: MetricId,
    /// Dedup table entries gauge
    pub dedup_entries: MetricId,

    // Error counters
    /// IO errors counter
    pub io_errors: MetricId,
    /// Checksum errors counter
    pub checksum_errors: MetricId,
}

lazy_static! {
    /// Global LCPFS metrics handles
    static ref LCPFS_METRICS: Mutex<Option<LcpfsMetrics>> = Mutex::new(None);
}

// ═══════════════════════════════════════════════════════════════════════════════
// COLLECTOR IMPLEMENTATION
// ═══════════════════════════════════════════════════════════════════════════════

/// Initialize LCPFS telemetry metrics
pub fn init_lcpfs_metrics() -> Result<(), TelemetryError> {
    let mut metrics_guard = LCPFS_METRICS.lock();
    if metrics_guard.is_some() {
        return Ok(()); // Already initialized
    }

    // Storage metrics
    let used_bytes = Telemetry::register_gauge(
        "lcpfs_storage_used_bytes",
        "Used storage space in bytes",
        &["pool"],
    )?;
    let quota_bytes = Telemetry::register_gauge(
        "lcpfs_storage_quota_bytes",
        "Storage quota in bytes (0 = unlimited)",
        &["pool"],
    )?;
    let available_bytes = Telemetry::register_gauge(
        "lcpfs_storage_available_bytes",
        "Available storage space in bytes",
        &["pool"],
    )?;
    let file_count = Telemetry::register_gauge(
        "lcpfs_objects_files_total",
        "Total number of files",
        &["pool"],
    )?;
    let dir_count = Telemetry::register_gauge(
        "lcpfs_objects_directories_total",
        "Total number of directories",
        &["pool"],
    )?;

    // Operation counters
    let read_ops = Telemetry::register_counter(
        "lcpfs_operations_read_total",
        "Total read operations",
        &["pool"],
    )?;
    let write_ops = Telemetry::register_counter(
        "lcpfs_operations_write_total",
        "Total write operations",
        &["pool"],
    )?;
    let read_bytes =
        Telemetry::register_counter("lcpfs_io_read_bytes_total", "Total bytes read", &["pool"])?;
    let write_bytes = Telemetry::register_counter(
        "lcpfs_io_write_bytes_total",
        "Total bytes written",
        &["pool"],
    )?;
    let fsync_ops = Telemetry::register_counter(
        "lcpfs_operations_fsync_total",
        "Total fsync operations",
        &["pool"],
    )?;
    let lookup_ops = Telemetry::register_counter(
        "lcpfs_operations_lookup_total",
        "Total lookup operations",
        &["pool"],
    )?;
    let create_ops = Telemetry::register_counter(
        "lcpfs_operations_create_total",
        "Total create operations",
        &["pool"],
    )?;
    let delete_ops = Telemetry::register_counter(
        "lcpfs_operations_delete_total",
        "Total delete operations",
        &["pool"],
    )?;
    let open_handles = Telemetry::register_gauge(
        "lcpfs_handles_open",
        "Currently open file handles",
        &["pool"],
    )?;

    // ARC cache metrics
    let arc_hits =
        Telemetry::register_counter("lcpfs_arc_hits_total", "ARC cache hits", &["pool"])?;
    let arc_misses =
        Telemetry::register_counter("lcpfs_arc_misses_total", "ARC cache misses", &["pool"])?;
    let arc_hit_ratio = Telemetry::register_gauge(
        "lcpfs_arc_hit_ratio",
        "ARC cache hit ratio (0.0-1.0)",
        &["pool"],
    )?;
    let arc_size_bytes =
        Telemetry::register_gauge("lcpfs_arc_size_bytes", "ARC cache size in bytes", &["pool"])?;
    let arc_evictions = Telemetry::register_counter(
        "lcpfs_arc_evictions_total",
        "ARC cache evictions",
        &["pool"],
    )?;
    let l2arc_hits =
        Telemetry::register_counter("lcpfs_l2arc_hits_total", "L2ARC cache hits", &["pool"])?;
    let l2arc_misses =
        Telemetry::register_counter("lcpfs_l2arc_misses_total", "L2ARC cache misses", &["pool"])?;

    // Transaction metrics
    let current_txg = Telemetry::register_gauge(
        "lcpfs_txg_current",
        "Current transaction group number",
        &["pool"],
    )?;
    let txg_syncs = Telemetry::register_counter(
        "lcpfs_txg_syncs_total",
        "Total TXG sync operations",
        &["pool"],
    )?;
    let txg_sync_latency = Telemetry::register_histogram(
        "lcpfs_txg_sync_latency_seconds",
        "TXG sync latency in seconds",
        &["pool"],
        &[
            0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0,
        ],
    )?;

    // Dedup metrics
    let dedup_ratio = Telemetry::register_gauge(
        "lcpfs_dedup_ratio",
        "Deduplication ratio (1.0 = no dedup, >1.0 = space savings)",
        &["pool"],
    )?;
    let dedup_hits = Telemetry::register_counter(
        "lcpfs_dedup_hits_total",
        "Dedup table hits (duplicate blocks found)",
        &["pool"],
    )?;
    let dedup_misses = Telemetry::register_counter(
        "lcpfs_dedup_misses_total",
        "Dedup table misses (new unique blocks)",
        &["pool"],
    )?;
    let dedup_entries =
        Telemetry::register_gauge("lcpfs_dedup_entries", "Dedup table entry count", &["pool"])?;

    // Error counters
    let io_errors = Telemetry::register_counter(
        "lcpfs_errors_io_total",
        "Total I/O errors",
        &["pool", "type"],
    )?;
    let checksum_errors = Telemetry::register_counter(
        "lcpfs_errors_checksum_total",
        "Total checksum errors",
        &["pool"],
    )?;

    *metrics_guard = Some(LcpfsMetrics {
        used_bytes,
        quota_bytes,
        available_bytes,
        file_count,
        dir_count,
        read_ops,
        write_ops,
        read_bytes,
        write_bytes,
        fsync_ops,
        lookup_ops,
        create_ops,
        delete_ops,
        open_handles,
        arc_hits,
        arc_misses,
        arc_hit_ratio,
        arc_size_bytes,
        arc_evictions,
        l2arc_hits,
        l2arc_misses,
        current_txg,
        txg_syncs,
        txg_sync_latency,
        dedup_ratio,
        dedup_hits,
        dedup_misses,
        dedup_entries,
        io_errors,
        checksum_errors,
    });

    Ok(())
}

/// Collect and update all metrics from ZPL and related subsystems
pub fn collect_all_metrics(pool: &str) {
    let metrics = match LCPFS_METRICS.lock().as_ref() {
        Some(m) => m.clone(),
        None => return, // Not initialized
    };

    let labels = &[pool];

    // Collect storage metrics from ZPL
    {
        let zpl = ZPL.lock();
        Telemetry::gauge_set(metrics.used_bytes, labels, zpl.get_used_bytes() as f64);
        Telemetry::gauge_set(metrics.quota_bytes, labels, zpl.get_quota() as f64);

        // Calculate available bytes (quota - used, or max if no quota)
        let available = if zpl.get_quota() > 0 {
            zpl.get_quota().saturating_sub(zpl.get_used_bytes())
        } else {
            u64::MAX
        };
        Telemetry::gauge_set(metrics.available_bytes, labels, available as f64);
    }

    // Update operation counters from atomic statistics
    Telemetry::counter_add(
        metrics.read_ops,
        labels,
        READ_OPS.swap(0, Ordering::Relaxed) as f64,
    );
    Telemetry::counter_add(
        metrics.write_ops,
        labels,
        WRITE_OPS.swap(0, Ordering::Relaxed) as f64,
    );
    Telemetry::counter_add(
        metrics.read_bytes,
        labels,
        BYTES_READ.swap(0, Ordering::Relaxed) as f64,
    );
    Telemetry::counter_add(
        metrics.write_bytes,
        labels,
        BYTES_WRITTEN.swap(0, Ordering::Relaxed) as f64,
    );
    Telemetry::counter_add(
        metrics.fsync_ops,
        labels,
        FSYNC_OPS.swap(0, Ordering::Relaxed) as f64,
    );
    Telemetry::counter_add(
        metrics.lookup_ops,
        labels,
        LOOKUP_OPS.swap(0, Ordering::Relaxed) as f64,
    );
    Telemetry::counter_add(
        metrics.create_ops,
        labels,
        CREATE_OPS.swap(0, Ordering::Relaxed) as f64,
    );
    Telemetry::counter_add(
        metrics.delete_ops,
        labels,
        DELETE_OPS.swap(0, Ordering::Relaxed) as f64,
    );
    Telemetry::gauge_set(
        metrics.open_handles,
        labels,
        OPEN_HANDLES.load(Ordering::Relaxed) as f64,
    );

    // ARC cache metrics
    let arc_hits_val = ARC_HITS.swap(0, Ordering::Relaxed);
    let arc_misses_val = ARC_MISSES.swap(0, Ordering::Relaxed);
    Telemetry::counter_add(metrics.arc_hits, labels, arc_hits_val as f64);
    Telemetry::counter_add(metrics.arc_misses, labels, arc_misses_val as f64);
    Telemetry::counter_add(
        metrics.arc_evictions,
        labels,
        ARC_EVICTIONS.swap(0, Ordering::Relaxed) as f64,
    );

    // Calculate hit ratio
    let total_accesses = arc_hits_val + arc_misses_val;
    if total_accesses > 0 {
        let hit_ratio = arc_hits_val as f64 / total_accesses as f64;
        Telemetry::gauge_set(metrics.arc_hit_ratio, labels, hit_ratio);
    }

    // L2ARC metrics
    Telemetry::counter_add(
        metrics.l2arc_hits,
        labels,
        L2ARC_HITS.swap(0, Ordering::Relaxed) as f64,
    );
    Telemetry::counter_add(
        metrics.l2arc_misses,
        labels,
        L2ARC_MISSES.swap(0, Ordering::Relaxed) as f64,
    );

    // Try to get ARC size from the cache module
    if let Ok(arc_stats) = get_arc_stats() {
        Telemetry::gauge_set(metrics.arc_size_bytes, labels, arc_stats.size_bytes as f64);
    }

    // TXG metrics
    Telemetry::gauge_set(
        metrics.current_txg,
        labels,
        CURRENT_TXG.load(Ordering::Relaxed) as f64,
    );
    Telemetry::counter_add(
        metrics.txg_syncs,
        labels,
        TXG_SYNCS.swap(0, Ordering::Relaxed) as f64,
    );

    // Dedup metrics
    let dedup_hits_val = DEDUP_HITS.swap(0, Ordering::Relaxed);
    let dedup_misses_val = DEDUP_MISSES.swap(0, Ordering::Relaxed);
    Telemetry::counter_add(metrics.dedup_hits, labels, dedup_hits_val as f64);
    Telemetry::counter_add(metrics.dedup_misses, labels, dedup_misses_val as f64);
    Telemetry::gauge_set(
        metrics.dedup_entries,
        labels,
        DEDUP_ENTRIES.load(Ordering::Relaxed) as f64,
    );

    // Calculate dedup ratio
    if dedup_misses_val > 0 {
        let dedup_ratio = (dedup_hits_val + dedup_misses_val) as f64 / dedup_misses_val as f64;
        Telemetry::gauge_set(metrics.dedup_ratio, labels, dedup_ratio);
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// INSTRUMENTATION HELPERS
// ═══════════════════════════════════════════════════════════════════════════════

/// Record a read operation
#[inline]
pub fn record_read(bytes: u64) {
    READ_OPS.fetch_add(1, Ordering::Relaxed);
    BYTES_READ.fetch_add(bytes, Ordering::Relaxed);
}

/// Record a write operation
#[inline]
pub fn record_write(bytes: u64) {
    WRITE_OPS.fetch_add(1, Ordering::Relaxed);
    BYTES_WRITTEN.fetch_add(bytes, Ordering::Relaxed);
}

/// Record an fsync operation
#[inline]
pub fn record_fsync() {
    FSYNC_OPS.fetch_add(1, Ordering::Relaxed);
}

/// Record a lookup operation
#[inline]
pub fn record_lookup() {
    LOOKUP_OPS.fetch_add(1, Ordering::Relaxed);
}

/// Record a create operation
#[inline]
pub fn record_create() {
    CREATE_OPS.fetch_add(1, Ordering::Relaxed);
}

/// Record a delete operation
#[inline]
pub fn record_delete() {
    DELETE_OPS.fetch_add(1, Ordering::Relaxed);
}

/// Record a file handle open
#[inline]
pub fn record_handle_open() {
    OPEN_HANDLES.fetch_add(1, Ordering::Relaxed);
}

/// Record a file handle close
#[inline]
pub fn record_handle_close() {
    OPEN_HANDLES.fetch_sub(1, Ordering::Relaxed);
}

/// Record an ARC cache hit
#[inline]
pub fn record_arc_hit() {
    ARC_HITS.fetch_add(1, Ordering::Relaxed);
}

/// Record an ARC cache miss
#[inline]
pub fn record_arc_miss() {
    ARC_MISSES.fetch_add(1, Ordering::Relaxed);
}

/// Record an ARC eviction
#[inline]
pub fn record_arc_eviction() {
    ARC_EVICTIONS.fetch_add(1, Ordering::Relaxed);
}

/// Record an L2ARC hit
#[inline]
pub fn record_l2arc_hit() {
    L2ARC_HITS.fetch_add(1, Ordering::Relaxed);
}

/// Record an L2ARC miss
#[inline]
pub fn record_l2arc_miss() {
    L2ARC_MISSES.fetch_add(1, Ordering::Relaxed);
}

/// Record a TXG sync with latency in microseconds
#[inline]
pub fn record_txg_sync(latency_us: u64) {
    TXG_SYNCS.fetch_add(1, Ordering::Relaxed);
    TXG_SYNC_TIME_US.fetch_add(latency_us, Ordering::Relaxed);
    CURRENT_TXG.fetch_add(1, Ordering::Relaxed);

    // Also record in histogram if metrics are initialized
    if let Some(metrics) = LCPFS_METRICS.lock().as_ref() {
        let latency_secs = latency_us as f64 / 1_000_000.0;
        Telemetry::histogram_observe(metrics.txg_sync_latency, &["default"], latency_secs);
    }
}

/// Record a dedup hit
#[inline]
pub fn record_dedup_hit() {
    DEDUP_HITS.fetch_add(1, Ordering::Relaxed);
}

/// Record a dedup miss (new unique block)
#[inline]
pub fn record_dedup_miss() {
    DEDUP_MISSES.fetch_add(1, Ordering::Relaxed);
}

/// Update dedup table entry count
#[inline]
pub fn update_dedup_entries(count: u64) {
    DEDUP_ENTRIES.store(count, Ordering::Relaxed);
}

/// Record an I/O error
pub fn record_io_error(pool: &str, error_type: &str) {
    if let Some(metrics) = LCPFS_METRICS.lock().as_ref() {
        Telemetry::counter_inc(metrics.io_errors, &[pool, error_type]);
    }
}

/// Record a checksum error
pub fn record_checksum_error(pool: &str) {
    if let Some(metrics) = LCPFS_METRICS.lock().as_ref() {
        Telemetry::counter_inc(metrics.checksum_errors, &[pool]);
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// ARC STATS HELPER
// ═══════════════════════════════════════════════════════════════════════════════

/// ARC statistics
#[derive(Debug, Clone, Default)]
pub struct ArcStats {
    /// Cache size in bytes
    pub size_bytes: u64,
    /// Target size in bytes
    pub target_bytes: u64,
    /// Minimum size in bytes
    pub min_bytes: u64,
    /// Maximum size in bytes
    pub max_bytes: u64,
}

/// Get ARC statistics from the cache module
fn get_arc_stats() -> Result<ArcStats, &'static str> {
    // Try to get from the ARC module
    use crate::cache::arc::ARC;

    let arc = ARC.lock();
    Ok(ArcStats {
        size_bytes: arc.current_size as u64,
        target_bytes: arc.max_bytes as u64 / 2, // Target is typically half max
        min_bytes: arc.max_bytes as u64 / 8,    // Min is typically 1/8 max
        max_bytes: arc.max_bytes as u64,
    })
}

// ═══════════════════════════════════════════════════════════════════════════════
// TESTS
// ═══════════════════════════════════════════════════════════════════════════════

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_record_operations() {
        // Reset counters
        READ_OPS.store(0, Ordering::Relaxed);
        WRITE_OPS.store(0, Ordering::Relaxed);
        BYTES_READ.store(0, Ordering::Relaxed);
        BYTES_WRITTEN.store(0, Ordering::Relaxed);

        record_read(1024);
        record_read(2048);
        record_write(512);

        assert_eq!(READ_OPS.load(Ordering::Relaxed), 2);
        assert_eq!(WRITE_OPS.load(Ordering::Relaxed), 1);
        assert_eq!(BYTES_READ.load(Ordering::Relaxed), 3072);
        assert_eq!(BYTES_WRITTEN.load(Ordering::Relaxed), 512);
    }

    #[test]
    fn test_arc_counters() {
        ARC_HITS.store(0, Ordering::Relaxed);
        ARC_MISSES.store(0, Ordering::Relaxed);

        record_arc_hit();
        record_arc_hit();
        record_arc_miss();

        assert_eq!(ARC_HITS.load(Ordering::Relaxed), 2);
        assert_eq!(ARC_MISSES.load(Ordering::Relaxed), 1);
    }

    #[test]
    fn test_handle_tracking() {
        OPEN_HANDLES.store(0, Ordering::Relaxed);

        record_handle_open();
        record_handle_open();
        record_handle_close();

        assert_eq!(OPEN_HANDLES.load(Ordering::Relaxed), 1);
    }

    #[test]
    fn test_txg_sync() {
        TXG_SYNCS.store(0, Ordering::Relaxed);
        CURRENT_TXG.store(100, Ordering::Relaxed);

        record_txg_sync(5000); // 5ms

        assert_eq!(TXG_SYNCS.load(Ordering::Relaxed), 1);
        assert_eq!(CURRENT_TXG.load(Ordering::Relaxed), 101);
    }

    #[test]
    fn test_dedup_counters() {
        DEDUP_HITS.store(0, Ordering::Relaxed);
        DEDUP_MISSES.store(0, Ordering::Relaxed);
        DEDUP_ENTRIES.store(0, Ordering::Relaxed);

        record_dedup_hit();
        record_dedup_hit();
        record_dedup_miss();
        update_dedup_entries(100);

        assert_eq!(DEDUP_HITS.load(Ordering::Relaxed), 2);
        assert_eq!(DEDUP_MISSES.load(Ordering::Relaxed), 1);
        assert_eq!(DEDUP_ENTRIES.load(Ordering::Relaxed), 100);
    }
}