dmsc/observability/
prometheus.rs

1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of DMSC.
4//! The DMSC project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//!     http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18#![allow(non_snake_case)]
19
20//! # Prometheus Exporter
21//! 
22//! This module provides a Prometheus exporter implementation for the DMSC framework. It allows
23//! registering and managing Prometheus metrics (counters, gauges, histograms) and generating
24//! Grafana dashboards from these metrics.
25//! 
26//! ## Key Components
27//! 
28//! - **DMSCPrometheusExporter**: Main exporter class for managing Prometheus metrics
29//! 
30//! ## Design Principles
31//! 
32//! 1. **Prometheus Integration**: Uses the official prometheus crate for metric collection
33//! 2. **Thread Safety**: Uses Arc and RwLock for safe concurrent access
34//! 3. **Multiple Metric Types**: Supports Counter, Gauge, and Histogram metrics
35//! 4. **Grafana Integration**: Provides methods to generate Grafana dashboards and panels
36//! 5. **Easy to Use**: Simple API for registering and updating metrics
37//! 6. **Text Encoding**: Exports metrics in Prometheus text format
38//! 7. **Registry Management**: Maintains its own Prometheus registry
39//! 8. **Error Handling**: Comprehensive error handling with DMSCResult
40//! 
41//! ## Usage
42//! 
43//! ```rust
44//! use dmsc::prelude::*;
45//! 
46//! fn example() -> DMSCResult<()> {
47//!     // Create a new Prometheus exporter
48//!     let exporter = DMSCPrometheusExporter::new()?;
49//!     
50//!     // Register a counter metric
51//!     exporter.register_counter("http_requests_total", "Total number of HTTP requests")?;
52//!     
53//!     // Register a gauge metric
54//!     exporter.register_gauge("active_connections", "Number of active connections")?;
55//!     
56//!     // Register a histogram metric
57//!     exporter.register_histogram(
58//!         "response_time_seconds", 
59//!         "Response time in seconds", 
60//!         vec![0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0]
61//!     )?;
62//!     
63//!     // Update metrics
64//!     exporter.increment_counter("http_requests_total", 1.0)?;
65//!     exporter.set_gauge("active_connections", 10.0)?;
66//!     exporter.observe_histogram("response_time_seconds", 0.123)?;
67//!     
68//!     // Render metrics in Prometheus format
69//!     let metrics_text = exporter.render()?;
70//!     println!("Prometheus metrics:\n{}", metrics_text);
71//!     
72//!     // Generate a Grafana dashboard
73//!     let dashboard = exporter.generate_default_dashboard()?;
74//!     let dashboard_json = dashboard.to_json()?;
75//!     println!("Grafana dashboard JSON:\n{}", dashboard_json);
76//!     
77//!     Ok(())
78//! }
79//! ```
80
81use std::collections::HashMap;
82use std::sync::{Arc, RwLock};
83#[cfg(feature = "observability")]
84use prometheus::{Counter, Gauge, Histogram, Registry, Encoder, TextEncoder};
85use crate::core::DMSCResult;
86use crate::core::lock::RwLockExtensions;
87
88/// Prometheus exporter for managing metrics and generating Grafana dashboards.
89///
90/// This struct provides methods for registering and updating Prometheus metrics,
91/// as well as generating Grafana dashboards from these metrics.
92#[allow(dead_code)]
93#[derive(Debug, Clone)]
94pub struct DMSCPrometheusExporter {
95    /// Prometheus registry for managing metrics
96    registry: Arc<Registry>,
97    /// Map of registered counter metrics
98    counters: Arc<RwLock<HashMap<String, Counter>>>,
99    /// Map of registered gauge metrics
100    gauges: Arc<RwLock<HashMap<String, Gauge>>>,
101    /// Map of registered histogram metrics
102    histograms: Arc<RwLock<HashMap<String, Histogram>>>,
103}
104
105#[allow(dead_code)]
106impl DMSCPrometheusExporter {
107    /// Creates a new Prometheus exporter instance.
108    ///
109    /// # Returns
110    ///
111    /// A new DMSCPrometheusExporter instance wrapped in DMSCResult
112    pub fn new() -> DMSCResult<Self> {
113        let registry = Arc::new(Registry::new());
114        
115        Ok(DMSCPrometheusExporter {
116            registry: registry.clone(),
117            counters: Arc::new(RwLock::new(HashMap::new())),
118            gauges: Arc::new(RwLock::new(HashMap::new())),
119            histograms: Arc::new(RwLock::new(HashMap::new())),
120        })
121    }
122    
123    /// Registers a new counter metric.
124    ///
125    /// # Parameters
126    ///
127    /// - `name`: The name of the counter metric
128    /// - `help`: Help text describing the counter
129    ///
130    /// # Returns
131    ///
132    /// DMSCResult indicating success or failure
133    pub fn register_counter(&self, name: &str, help: &str) -> DMSCResult<()> {
134        let counter = Counter::new(name, help)?;
135        self.registry.register(Box::new(counter.clone()))?;
136        
137        let mut counters = self.counters.write_safe("counters for register")?;
138        counters.insert(name.to_string(), counter);
139        
140        Ok(())
141    }
142    
143    /// Increments a counter metric by the specified value.
144    ///
145    /// # Parameters
146    ///
147    /// - `name`: The name of the counter metric
148    /// - `value`: The value to increment by
149    ///
150    /// # Returns
151    ///
152    /// DMSCResult indicating success or failure
153    pub fn increment_counter(&self, name: &str, value: f64) -> DMSCResult<()> {
154        let counters = self.counters.read_safe("counters for increment")?;
155        if let Some(counter) = counters.get(name) {
156            counter.inc_by(value);
157            Ok(())
158        } else {
159            Err(crate::core::DMSCError::Io(format!("Counter {name} not found")))
160        }
161    }
162    
163    /// Registers a new gauge metric.
164    ///
165    /// # Parameters
166    ///
167    /// - `name`: The name of the gauge metric
168    /// - `help`: Help text describing the gauge
169    ///
170    /// # Returns
171    ///
172    /// DMSCResult indicating success or failure
173    pub fn register_gauge(&self, name: &str, help: &str) -> DMSCResult<()> {
174        let gauge = Gauge::new(name, help)?;
175        self.registry.register(Box::new(gauge.clone()))?;
176        
177        let mut gauges = self.gauges.write_safe("gauges for register")?;
178        gauges.insert(name.to_string(), gauge);
179        
180        Ok(())
181    }
182    
183    /// Sets a gauge metric to the specified value.
184    ///
185    /// # Parameters
186    ///
187    /// - `name`: The name of the gauge metric
188    /// - `value`: The value to set
189    ///
190    /// # Returns
191    ///
192    /// DMSCResult indicating success or failure
193    pub fn set_gauge(&self, name: &str, value: f64) -> DMSCResult<()> {
194        let gauges = self.gauges.read_safe("gauges for set")?;
195        if let Some(gauge) = gauges.get(name) {
196            gauge.set(value);
197            Ok(())
198        } else {
199            Err(crate::core::DMSCError::Io(format!("Gauge {name} not found")))
200        }
201    }
202    
203    /// Registers a new histogram metric.
204    ///
205    /// # Parameters
206    ///
207    /// - `name`: The name of the histogram metric
208    /// - `help`: Help text describing the histogram
209    /// - `buckets`: The histogram buckets
210    ///
211    /// # Returns
212    ///
213    /// DMSCResult indicating success or failure
214    pub fn register_histogram(&self, name: &str, help: &str, buckets: Vec<f64>) -> DMSCResult<()> {
215        let histogram = Histogram::with_opts(prometheus::HistogramOpts::new(name, help).buckets(buckets))?;
216        self.registry.register(Box::new(histogram.clone()))?;
217        
218        let mut histograms = self.histograms.write_safe("histograms for register")?;
219        histograms.insert(name.to_string(), histogram);
220        
221        Ok(())
222    }
223    
224    /// Observes a value in a histogram metric.
225    ///
226    /// # Parameters
227    ///
228    /// - `name`: The name of the histogram metric
229    /// - `value`: The value to observe
230    ///
231    /// # Returns
232    ///
233    /// DMSCResult indicating success or failure
234    pub fn observe_histogram(&self, name: &str, value: f64) -> DMSCResult<()> {
235        let histograms = self.histograms.read_safe("histograms for observe")?;
236        if let Some(histogram) = histograms.get(name) {
237            histogram.observe(value);
238            Ok(())
239        } else {
240            Err(crate::core::DMSCError::Io(format!("Histogram {name} not found")))
241        }
242    }
243    
244    /// Renders all metrics in Prometheus text format.
245    ///
246    /// # Returns
247    ///
248    /// A string containing all metrics in Prometheus text format wrapped in DMSCResult
249    pub fn render(&self) -> DMSCResult<String> {
250        let encoder = TextEncoder::new();
251        let metric_families = self.registry.gather();
252        let mut buffer = Vec::new();
253        encoder.encode(&metric_families, &mut buffer)?;
254        
255        String::from_utf8(buffer).map_err(|e| crate::core::DMSCError::Serde(format!("Invalid UTF-8 in Prometheus output: {}", e)))
256    }
257    
258    /// Adds a counter panel to a Grafana dashboard.
259    ///
260    /// # Parameters
261    ///
262    /// - `dashboard`: The Grafana dashboard to add the panel to
263    /// - `title`: The title of the panel
264    /// - `query`: The Prometheus query for the panel
265    ///
266    /// # Returns
267    ///
268    /// DMSCResult indicating success or failure
269    pub fn add_counter_panel(&self, dashboard: &mut crate::observability::grafana::DMSCGrafanaDashboard, title: &str, query: &str) -> DMSCResult<()> {
270        let mut generator = crate::observability::grafana::DMSCGrafanaDashboardGenerator::new();
271        let mut panel = generator.create_panel(title, "stat", 0, 0, 12, 8);
272        panel.targets.push(generator.create_prometheus_target(query, "A", None));
273        dashboard.panels.push(panel);
274        Ok(())
275    }
276    
277    /// Adds a gauge panel to a Grafana dashboard.
278    ///
279    /// # Parameters
280    ///
281    /// - `dashboard`: The Grafana dashboard to add the panel to
282    /// - `title`: The title of the panel
283    /// - `query`: The Prometheus query for the panel
284    ///
285    /// # Returns
286    ///
287    /// DMSCResult indicating success or failure
288    pub fn add_gauge_panel(&self, dashboard: &mut crate::observability::grafana::DMSCGrafanaDashboard, title: &str, query: &str) -> DMSCResult<()> {
289        let mut generator = crate::observability::grafana::DMSCGrafanaDashboardGenerator::new();
290        let mut panel = generator.create_panel(title, "gauge", 12, 0, 12, 8);
291        panel.targets.push(generator.create_prometheus_target(query, "A", None));
292        dashboard.panels.push(panel);
293        Ok(())
294    }
295    
296    /// Adds a stat panel to a Grafana dashboard.
297    ///
298    /// # Parameters
299    ///
300    /// - `dashboard`: The Grafana dashboard to add the panel to
301    /// - `title`: The title of the panel
302    /// - `query`: The Prometheus query for the panel
303    ///
304    /// # Returns
305    ///
306    /// DMSCResult indicating success or failure
307    pub fn add_stat_panel(&self, dashboard: &mut crate::observability::grafana::DMSCGrafanaDashboard, title: &str, query: &str) -> DMSCResult<()> {
308        let mut generator = crate::observability::grafana::DMSCGrafanaDashboardGenerator::new();
309        let mut panel = generator.create_panel(title, "stat", 0, 8, 12, 8);
310        panel.targets.push(generator.create_prometheus_target(query, "A", None));
311        dashboard.panels.push(panel);
312        Ok(())
313    }
314    
315    /// Generates a Grafana dashboard with default panels.
316    ///
317    /// # Parameters
318    ///
319    /// - `title`: The title of the dashboard
320    ///
321    /// # Returns
322    ///
323    /// A Grafana dashboard with default panels wrapped in DMSCResult
324    pub fn generate_dashboard(&self, title: &str) -> DMSCResult<crate::observability::grafana::DMSCGrafanaDashboard> {
325        let mut dashboard = crate::observability::grafana::DMSCGrafanaDashboard::new(title);
326        
327        self.add_counter_panel(&mut dashboard, "Request Count", "dms_requests_total")?;
328        self.add_gauge_panel(&mut dashboard, "Active Connections", "dms_active_connections")?;
329        self.add_stat_panel(&mut dashboard, "Response Time", "dms_response_time_seconds")?;
330        
331        Ok(dashboard)
332    }
333    
334    /// Generates a default Grafana dashboard with "DMSC Metrics Dashboard" title.
335    ///
336    /// # Returns
337    ///
338    /// A default Grafana dashboard wrapped in DMSCResult
339    pub fn generate_default_dashboard(&self) -> DMSCResult<crate::observability::grafana::DMSCGrafanaDashboard> {
340        self.generate_dashboard("DMSC Metrics Dashboard")
341    }
342}
343