veza/veza-stream-server/src/monitoring/grafana_dashboards.rs

614 lines
22 KiB
Rust
Raw Normal View History

2025-12-03 19:36:56 +00:00
/// Module Grafana pour dashboards production
use std::sync::Arc;
use tokio::sync::RwLock;
// Note: Use tracing::info! macro directly instead of importing
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use crate::error::AppError;
/// Gestionnaire de dashboards Grafana
#[derive(Debug)]
pub struct GrafanaManager {
dashboards: Arc<RwLock<Vec<GrafanaDashboard>>>,
config: GrafanaConfig,
}
/// Configuration Grafana
#[derive(Debug, Clone)]
pub struct GrafanaConfig {
pub grafana_url: String,
pub api_key: Option<String>,
pub org_id: u32,
pub datasource_uid: String,
}
/// Dashboard Grafana
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GrafanaDashboard {
pub id: String,
pub title: String,
pub description: String,
pub tags: Vec<String>,
pub panels: Vec<GrafanaPanel>,
pub template_variables: Vec<TemplateVariable>,
pub time_range: TimeRange,
pub refresh_interval: String,
}
/// Panel Grafana
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GrafanaPanel {
pub id: u32,
pub title: String,
pub panel_type: PanelType,
pub targets: Vec<PrometheusQuery>,
pub position: PanelPosition,
pub options: PanelOptions,
pub thresholds: Option<Vec<Threshold>>,
}
/// Types de panels
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PanelType {
Graph,
Stat,
Table,
Heatmap,
Gauge,
BarGauge,
Logs,
NodeGraph,
}
/// Requête Prometheus
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrometheusQuery {
pub expr: String,
pub legend: String,
pub interval: Option<String>,
pub instant: bool,
}
/// Position du panel
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PanelPosition {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
}
/// Options du panel
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PanelOptions {
pub unit: Option<String>,
pub decimals: Option<u32>,
pub min: Option<f64>,
pub max: Option<f64>,
pub color_mode: Option<String>,
}
/// Seuil d'alerte
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Threshold {
pub value: f64,
pub color: String,
pub op: String, // "gt", "lt", etc.
}
/// Variable de template
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemplateVariable {
pub name: String,
pub label: String,
pub var_type: String,
pub query: String,
pub multi: bool,
}
/// Plage de temps
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimeRange {
pub from: String,
pub to: String,
}
impl Default for GrafanaConfig {
fn default() -> Self {
Self {
grafana_url: "http://localhost:3000".to_string(),
api_key: None,
org_id: 1,
datasource_uid: "prometheus".to_string(),
}
}
}
impl GrafanaManager {
/// Crée un nouveau gestionnaire Grafana
pub async fn new() -> Result<Self, AppError> {
tracing::info!("📊 Initialisation Grafana Manager");
let mut manager = Self {
dashboards: Arc::new(RwLock::new(Vec::new())),
config: GrafanaConfig::default(),
};
// Créer dashboards par défaut
manager.create_default_dashboards().await?;
Ok(manager)
}
/// Crée les dashboards par défaut
async fn create_default_dashboards(&mut self) -> Result<(), AppError> {
tracing::info!("🎨 Création dashboards Grafana par défaut");
let dashboards = vec![
self.create_system_overview_dashboard(),
self.create_application_metrics_dashboard(),
self.create_business_metrics_dashboard(),
self.create_alerts_dashboard(),
self.create_performance_dashboard(),
];
let mut dashboard_store = self.dashboards.write().await;
*dashboard_store = dashboards;
tracing::info!("✅ {} dashboards créés", dashboard_store.len());
Ok(())
}
/// Dashboard Vue d'ensemble système
fn create_system_overview_dashboard(&self) -> GrafanaDashboard {
GrafanaDashboard {
id: "system-overview".to_string(),
title: "🖥️ System Overview".to_string(),
description: "Vue d'ensemble des métriques système".to_string(),
tags: vec!["system".to_string(), "overview".to_string()],
panels: vec![
// CPU Usage
GrafanaPanel {
id: 1,
title: "CPU Usage".to_string(),
panel_type: PanelType::Gauge,
targets: vec![PrometheusQuery {
expr: "system_cpu_usage_percent".to_string(),
legend: "CPU %".to_string(),
interval: None,
instant: true,
}],
position: PanelPosition {
x: 0,
y: 0,
width: 6,
height: 4,
},
options: PanelOptions {
unit: Some("percent".to_string()),
decimals: Some(1),
min: Some(0.0),
max: Some(100.0),
color_mode: Some("thresholds".to_string()),
},
thresholds: Some(vec![
Threshold {
value: 70.0,
color: "yellow".to_string(),
op: "gt".to_string(),
},
Threshold {
value: 85.0,
color: "red".to_string(),
op: "gt".to_string(),
},
]),
},
// Memory Usage
GrafanaPanel {
id: 2,
title: "Memory Usage".to_string(),
panel_type: PanelType::Gauge,
targets: vec![PrometheusQuery {
expr: "system_memory_usage_bytes / system_memory_total_bytes * 100"
.to_string(),
legend: "Memory %".to_string(),
interval: None,
instant: true,
}],
position: PanelPosition {
x: 6,
y: 0,
width: 6,
height: 4,
},
options: PanelOptions {
unit: Some("percent".to_string()),
decimals: Some(1),
min: Some(0.0),
max: Some(100.0),
color_mode: Some("thresholds".to_string()),
},
thresholds: Some(vec![
Threshold {
value: 75.0,
color: "yellow".to_string(),
op: "gt".to_string(),
},
Threshold {
value: 90.0,
color: "red".to_string(),
op: "gt".to_string(),
},
]),
},
// Network I/O
GrafanaPanel {
id: 3,
title: "Network I/O".to_string(),
panel_type: PanelType::Graph,
targets: vec![
PrometheusQuery {
expr: "rate(system_network_rx_bytes_total[5m])".to_string(),
legend: "RX".to_string(),
interval: Some("30s".to_string()),
instant: false,
},
PrometheusQuery {
expr: "rate(system_network_tx_bytes_total[5m])".to_string(),
legend: "TX".to_string(),
interval: Some("30s".to_string()),
instant: false,
},
],
position: PanelPosition {
x: 0,
y: 4,
width: 12,
height: 6,
},
options: PanelOptions {
unit: Some("bytes".to_string()),
decimals: Some(2),
min: None,
max: None,
color_mode: None,
},
thresholds: None,
},
],
template_variables: vec![],
time_range: TimeRange {
from: "now-1h".to_string(),
to: "now".to_string(),
},
refresh_interval: "30s".to_string(),
}
}
/// Dashboard Métriques Application
fn create_application_metrics_dashboard(&self) -> GrafanaDashboard {
GrafanaDashboard {
id: "application-metrics".to_string(),
title: "🚀 Application Metrics".to_string(),
description: "Métriques de performance de l'application".to_string(),
tags: vec!["application".to_string(), "performance".to_string()],
panels: vec![
// Active Connections
GrafanaPanel {
id: 1,
title: "Active Connections".to_string(),
panel_type: PanelType::Stat,
targets: vec![PrometheusQuery {
expr: "stream_connections_active".to_string(),
legend: "Connections".to_string(),
interval: None,
instant: true,
}],
position: PanelPosition {
x: 0,
y: 0,
width: 3,
height: 3,
},
options: PanelOptions {
unit: Some("short".to_string()),
decimals: Some(0),
min: None,
max: None,
color_mode: Some("value".to_string()),
},
thresholds: Some(vec![
Threshold {
value: 50000.0,
color: "green".to_string(),
op: "gt".to_string(),
},
Threshold {
value: 80000.0,
color: "yellow".to_string(),
op: "gt".to_string(),
},
Threshold {
value: 95000.0,
color: "red".to_string(),
op: "gt".to_string(),
},
]),
},
// Requests per Second
GrafanaPanel {
id: 2,
title: "Requests/sec".to_string(),
panel_type: PanelType::Stat,
targets: vec![PrometheusQuery {
expr: "rate(http_requests_total[1m])".to_string(),
legend: "RPS".to_string(),
interval: None,
instant: true,
}],
position: PanelPosition {
x: 3,
y: 0,
width: 3,
height: 3,
},
options: PanelOptions {
unit: Some("reqps".to_string()),
decimals: Some(1),
min: None,
max: None,
color_mode: Some("value".to_string()),
},
thresholds: None,
},
// Response Time Percentiles
GrafanaPanel {
id: 3,
title: "Response Time Percentiles".to_string(),
panel_type: PanelType::Graph,
targets: vec![
PrometheusQuery {
expr: "histogram_quantile(0.50, http_request_duration_seconds)"
.to_string(),
legend: "P50".to_string(),
interval: Some("30s".to_string()),
instant: false,
},
PrometheusQuery {
expr: "histogram_quantile(0.95, http_request_duration_seconds)"
.to_string(),
legend: "P95".to_string(),
interval: Some("30s".to_string()),
instant: false,
},
PrometheusQuery {
expr: "histogram_quantile(0.99, http_request_duration_seconds)"
.to_string(),
legend: "P99".to_string(),
interval: Some("30s".to_string()),
instant: false,
},
],
position: PanelPosition {
x: 6,
y: 0,
width: 6,
height: 6,
},
options: PanelOptions {
unit: Some("ms".to_string()),
decimals: Some(2),
min: Some(0.0),
max: None,
color_mode: None,
},
thresholds: Some(vec![Threshold {
value: 50.0,
color: "red".to_string(),
op: "gt".to_string(),
}]),
},
// Error Rate
GrafanaPanel {
id: 4,
title: "Error Rate".to_string(),
panel_type: PanelType::Graph,
targets: vec![PrometheusQuery {
expr: "rate(stream_errors_total[5m]) / rate(http_requests_total[5m]) * 100"
.to_string(),
legend: "Error %".to_string(),
interval: Some("1m".to_string()),
instant: false,
}],
position: PanelPosition {
x: 0,
y: 6,
width: 6,
height: 4,
},
options: PanelOptions {
unit: Some("percent".to_string()),
decimals: Some(3),
min: Some(0.0),
max: None,
color_mode: None,
},
thresholds: Some(vec![
Threshold {
value: 0.1,
color: "yellow".to_string(),
op: "gt".to_string(),
},
Threshold {
value: 1.0,
color: "red".to_string(),
op: "gt".to_string(),
},
]),
},
],
template_variables: vec![],
time_range: TimeRange {
from: "now-6h".to_string(),
to: "now".to_string(),
},
refresh_interval: "15s".to_string(),
}
}
/// Dashboard Métriques Business
fn create_business_metrics_dashboard(&self) -> GrafanaDashboard {
GrafanaDashboard {
id: "business-metrics".to_string(),
title: "💼 Business Metrics".to_string(),
description: "Métriques business et engagement utilisateur".to_string(),
tags: vec!["business".to_string(), "revenue".to_string()],
panels: vec![
// Active Users
GrafanaPanel {
id: 1,
title: "Active Users".to_string(),
panel_type: PanelType::Stat,
targets: vec![PrometheusQuery {
expr: "business_active_users".to_string(),
legend: "Users".to_string(),
interval: None,
instant: true,
}],
position: PanelPosition {
x: 0,
y: 0,
width: 4,
height: 4,
},
options: PanelOptions {
unit: Some("short".to_string()),
decimals: Some(0),
min: None,
max: None,
color_mode: Some("value".to_string()),
},
thresholds: None,
},
// Revenue
GrafanaPanel {
id: 2,
title: "Total Revenue".to_string(),
panel_type: PanelType::Stat,
targets: vec![PrometheusQuery {
expr: "business_revenue_total".to_string(),
legend: "Revenue".to_string(),
interval: None,
instant: true,
}],
position: PanelPosition {
x: 4,
y: 0,
width: 4,
height: 4,
},
options: PanelOptions {
unit: Some("currencyUSD".to_string()),
decimals: Some(2),
min: None,
max: None,
color_mode: Some("value".to_string()),
},
thresholds: None,
},
// Premium Subscriptions
GrafanaPanel {
id: 3,
title: "Premium Subscriptions".to_string(),
panel_type: PanelType::Stat,
targets: vec![PrometheusQuery {
expr: "business_premium_subscriptions".to_string(),
legend: "Premium".to_string(),
interval: None,
instant: true,
}],
position: PanelPosition {
x: 8,
y: 0,
width: 4,
height: 4,
},
options: PanelOptions {
unit: Some("short".to_string()),
decimals: Some(0),
min: None,
max: None,
color_mode: Some("value".to_string()),
},
thresholds: None,
},
],
template_variables: vec![],
time_range: TimeRange {
from: "now-24h".to_string(),
to: "now".to_string(),
},
refresh_interval: "1m".to_string(),
}
}
/// Dashboard Alertes
fn create_alerts_dashboard(&self) -> GrafanaDashboard {
GrafanaDashboard {
id: "alerts".to_string(),
title: "🚨 Alerts & Incidents".to_string(),
description: "Monitoring des alertes et incidents".to_string(),
tags: vec!["alerts".to_string(), "incidents".to_string()],
panels: vec![],
template_variables: vec![],
time_range: TimeRange {
from: "now-24h".to_string(),
to: "now".to_string(),
},
refresh_interval: "1m".to_string(),
}
}
/// Dashboard Performance
fn create_performance_dashboard(&self) -> GrafanaDashboard {
GrafanaDashboard {
id: "performance".to_string(),
title: "⚡ Performance Deep Dive".to_string(),
description: "Analyse détaillée des performances".to_string(),
tags: vec!["performance".to_string(), "deep-dive".to_string()],
panels: vec![],
template_variables: vec![],
time_range: TimeRange {
from: "now-1h".to_string(),
to: "now".to_string(),
},
refresh_interval: "10s".to_string(),
}
}
/// Exporte un dashboard au format JSON Grafana
pub async fn export_dashboard(&self, dashboard_id: &str) -> Result<Value, AppError> {
let dashboards = self.dashboards.read().await;
if let Some(dashboard) = dashboards.iter().find(|d| d.id == dashboard_id) {
Ok(json!({
"dashboard": dashboard,
"folderId": 0,
"overwrite": true
}))
} else {
Err(AppError::InvalidData {
message: format!("Dashboard not found: {}", dashboard_id),
})
}
}
/// Liste tous les dashboards disponibles
pub async fn list_dashboards(&self) -> Vec<String> {
let dashboards = self.dashboards.read().await;
dashboards.iter().map(|d| d.id.clone()).collect()
}
}