lcpfs 2026.1.102

LCP File System - A ZFS-inspired copy-on-write filesystem for Rust
// Copyright 2025 LunaOS Contributors
// SPDX-License-Identifier: Apache-2.0

//! Telemetry Module.
//!
//! Provides Prometheus-compatible metrics export for monitoring LCPFS
//! with Grafana dashboards. Supports counters, gauges, histograms,
//! and summaries.
//!
//! ## ZPL Collector
//!
//! The `zpl_collector` module provides real-time metrics collection from
//! the filesystem layer:
//!
//! ```rust,ignore
//! use lcpfs::telemetry::{init, zpl_collector};
//!
//! // Initialize telemetry
//! init();
//!
//! // Initialize LCPFS-specific metrics
//! zpl_collector::init_lcpfs_metrics().unwrap();
//!
//! // Record operations (typically called from ZPL layer)
//! zpl_collector::record_read(4096);
//! zpl_collector::record_write(1024);
//! zpl_collector::record_arc_hit();
//!
//! // Collect and export metrics periodically
//! zpl_collector::collect_all_metrics("tank");
//! ```

extern crate alloc;

mod export;
mod metrics;
mod types;
/// ZPL metrics collector for real filesystem statistics.
pub mod zpl_collector;

pub use export::*;
pub use metrics::*;
pub use types::*;

use alloc::string::String;
use alloc::vec::Vec;
use spin::Mutex;

/// Global metrics registry
static METRICS_REGISTRY: Mutex<Option<MetricsRegistry>> = Mutex::new(None);

/// Initialize the telemetry subsystem
pub fn init() {
    let mut registry = METRICS_REGISTRY.lock();
    if registry.is_none() {
        *registry = Some(MetricsRegistry::new());
    }
}

/// Telemetry API
pub struct Telemetry;

impl Telemetry {
    /// Register a counter metric
    pub fn register_counter(
        name: &str,
        help: &str,
        labels: &[&str],
    ) -> Result<MetricId, TelemetryError> {
        let mut registry = METRICS_REGISTRY.lock();
        let registry = registry.as_mut().ok_or(TelemetryError::NotInitialized)?;

        registry.register_counter(name, help, labels)
    }

    /// Register a gauge metric
    pub fn register_gauge(
        name: &str,
        help: &str,
        labels: &[&str],
    ) -> Result<MetricId, TelemetryError> {
        let mut registry = METRICS_REGISTRY.lock();
        let registry = registry.as_mut().ok_or(TelemetryError::NotInitialized)?;

        registry.register_gauge(name, help, labels)
    }

    /// Register a histogram metric
    pub fn register_histogram(
        name: &str,
        help: &str,
        labels: &[&str],
        buckets: &[f64],
    ) -> Result<MetricId, TelemetryError> {
        let mut registry = METRICS_REGISTRY.lock();
        let registry = registry.as_mut().ok_or(TelemetryError::NotInitialized)?;

        registry.register_histogram(name, help, labels, buckets)
    }

    /// Increment a counter
    pub fn counter_inc(id: MetricId, labels: &[&str]) {
        if let Some(ref mut registry) = *METRICS_REGISTRY.lock() {
            registry.counter_inc(id, labels, 1.0);
        }
    }

    /// Add to a counter
    pub fn counter_add(id: MetricId, labels: &[&str], value: f64) {
        if let Some(ref mut registry) = *METRICS_REGISTRY.lock() {
            registry.counter_inc(id, labels, value);
        }
    }

    /// Set a gauge value
    pub fn gauge_set(id: MetricId, labels: &[&str], value: f64) {
        if let Some(ref mut registry) = *METRICS_REGISTRY.lock() {
            registry.gauge_set(id, labels, value);
        }
    }

    /// Increment a gauge
    pub fn gauge_inc(id: MetricId, labels: &[&str]) {
        if let Some(ref mut registry) = *METRICS_REGISTRY.lock() {
            registry.gauge_add(id, labels, 1.0);
        }
    }

    /// Decrement a gauge
    pub fn gauge_dec(id: MetricId, labels: &[&str]) {
        if let Some(ref mut registry) = *METRICS_REGISTRY.lock() {
            registry.gauge_add(id, labels, -1.0);
        }
    }

    /// Observe a histogram value
    pub fn histogram_observe(id: MetricId, labels: &[&str], value: f64) {
        if let Some(ref mut registry) = *METRICS_REGISTRY.lock() {
            registry.histogram_observe(id, labels, value);
        }
    }

    /// Export all metrics in Prometheus format
    pub fn export_prometheus() -> Result<String, TelemetryError> {
        let registry = METRICS_REGISTRY.lock();
        let registry = registry.as_ref().ok_or(TelemetryError::NotInitialized)?;

        Ok(export_prometheus_format(registry))
    }

    /// Export metrics as JSON
    pub fn export_json() -> Result<String, TelemetryError> {
        let registry = METRICS_REGISTRY.lock();
        let registry = registry.as_ref().ok_or(TelemetryError::NotInitialized)?;

        Ok(export_json_format(registry))
    }

    /// Get list of all registered metrics
    pub fn list_metrics() -> Result<Vec<MetricInfo>, TelemetryError> {
        let registry = METRICS_REGISTRY.lock();
        let registry = registry.as_ref().ok_or(TelemetryError::NotInitialized)?;

        Ok(registry.list_metrics())
    }

    /// Reset all metrics
    pub fn reset() {
        if let Some(ref mut registry) = *METRICS_REGISTRY.lock() {
            registry.reset();
        }
    }

    /// Collect standard LCPFS metrics
    pub fn collect_standard_metrics(dataset: &str) {
        collect_filesystem_metrics(dataset);
    }
}

/// Built-in LCPFS metrics
pub mod builtin {
    use super::*;

    /// Initialize built-in LCPFS metrics
    pub fn init_builtin_metrics() -> Result<BuiltinMetrics, TelemetryError> {
        let read_ops = Telemetry::register_counter(
            "lcpfs_read_ops_total",
            "Total number of read operations",
            &["dataset"],
        )?;

        let write_ops = Telemetry::register_counter(
            "lcpfs_write_ops_total",
            "Total number of write operations",
            &["dataset"],
        )?;

        let read_bytes = Telemetry::register_counter(
            "lcpfs_read_bytes_total",
            "Total bytes read",
            &["dataset"],
        )?;

        let write_bytes = Telemetry::register_counter(
            "lcpfs_write_bytes_total",
            "Total bytes written",
            &["dataset"],
        )?;

        let used_bytes = Telemetry::register_gauge(
            "lcpfs_used_bytes",
            "Used storage space in bytes",
            &["dataset"],
        )?;

        let free_bytes = Telemetry::register_gauge(
            "lcpfs_free_bytes",
            "Free storage space in bytes",
            &["dataset"],
        )?;

        let file_count =
            Telemetry::register_gauge("lcpfs_file_count", "Number of files", &["dataset"])?;

        let read_latency = Telemetry::register_histogram(
            "lcpfs_read_latency_seconds",
            "Read operation latency in seconds",
            &["dataset"],
            &[0.0001, 0.0005, 0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1.0],
        )?;

        let write_latency = Telemetry::register_histogram(
            "lcpfs_write_latency_seconds",
            "Write operation latency in seconds",
            &["dataset"],
            &[0.0001, 0.0005, 0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1.0],
        )?;

        Ok(BuiltinMetrics {
            read_ops,
            write_ops,
            read_bytes,
            write_bytes,
            used_bytes,
            free_bytes,
            file_count,
            read_latency,
            write_latency,
        })
    }

    /// Built-in metric handles
    #[derive(Debug, Clone)]
    pub struct BuiltinMetrics {
        /// 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,
        /// Used bytes gauge
        pub used_bytes: MetricId,
        /// Free bytes gauge
        pub free_bytes: MetricId,
        /// File count gauge
        pub file_count: MetricId,
        /// Read latency histogram
        pub read_latency: MetricId,
        /// Write latency histogram
        pub write_latency: MetricId,
    }
}

/// Collect filesystem metrics for a dataset
fn collect_filesystem_metrics(dataset: &str) {
    // Use the ZPL collector to gather real metrics
    zpl_collector::collect_all_metrics(dataset);
}

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

    #[test]
    fn test_init() {
        init();
        let registry = METRICS_REGISTRY.lock();
        assert!(registry.is_some());
    }
}