1#![allow(non_snake_case)]
19
20use serde::{Serialize, Deserialize};
74use crate::core::DMSCResult;
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct DMSCGrafanaTarget {
79 pub expr: String,
80 pub ref_id: String,
81 pub legend_format: Option<String>,
82 pub interval: Option<String>,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct DMSCGridPos {
88 pub h: i32,
89 pub w: i32,
90 pub x: i32,
91 pub y: i32,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct DMSCGrafanaPanel {
97 pub id: i32,
98 pub title: String,
99 pub type_: String,
100 pub targets: Vec<DMSCGrafanaTarget>,
101 pub grid_pos: DMSCGridPos,
102 pub field_config: serde_json::Value,
103 pub options: serde_json::Value,
104 pub description: Option<String>,
105 pub datasource: Option<String>,
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct DMSCGrafanaTimeRange {
111 pub from: String,
112 pub to: String,
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct DMSCGrafanaTag {
118 pub term: String,
119 pub color: Option<String>,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct DMSCGrafanaDashboard {
125 pub title: String,
126 pub panels: Vec<DMSCGrafanaPanel>,
127 pub tags: Vec<DMSCGrafanaTag>,
128 pub time: DMSCGrafanaTimeRange,
129 pub refresh: String,
130 pub timezone: String,
131 pub schema_version: i32,
132 pub uid: Option<String>,
133 pub version: i32,
134}
135
136pub struct DMSCGrafanaDashboardGenerator {
138 next_panel_id: i32,
139}
140
141#[allow(dead_code)]
142impl DMSCGrafanaDashboard {
143 pub fn new(title: &str) -> Self {
144 DMSCGrafanaDashboard {
145 title: title.to_string(),
146 panels: Vec::new(),
147 tags: vec![DMSCGrafanaTag { term: "dms".to_string(), color: Some("#1F77B4".to_string()) }],
148 time: DMSCGrafanaTimeRange { from: "now-1h".to_string(), to: "now".to_string() },
149 refresh: "5s".to_string(),
150 timezone: "browser".to_string(),
151 schema_version: 38,
152 uid: None,
153 version: 1,
154 }
155 }
156
157 pub fn add_panel(&mut self, panel: DMSCGrafanaPanel) -> DMSCResult<()> {
158 self.panels.push(panel);
159 Ok(())
160 }
161
162 pub fn to_json(&self) -> DMSCResult<String> {
163 serde_json::to_string(self).map_err(|e| crate::core::DMSCError::Serde(e.to_string()))
164 }
165}
166
167impl DMSCGrafanaDashboardGenerator {
168 pub fn new() -> Self {
169 DMSCGrafanaDashboardGenerator {
170 next_panel_id: 1,
171 }
172 }
173
174 fn title_case(&self, s: &str) -> String {
176 let mut result = String::new();
177 let mut capitalize_next = true;
178
179 for c in s.chars() {
180 if c == '_' || c == ' ' {
181 result.push(' ');
182 capitalize_next = true;
183 } else if capitalize_next {
184 result.push(c.to_ascii_uppercase());
185 capitalize_next = false;
186 } else {
187 result.push(c.to_ascii_lowercase());
188 }
189 }
190
191 result
192 }
193
194 pub fn create_dashboard(&self, title: &str) -> DMSCGrafanaDashboard {
196 DMSCGrafanaDashboard::new(title)
197 }
198
199 pub fn create_prometheus_target(&self, expr: &str, ref_id: &str, legend_format: Option<&str>) -> DMSCGrafanaTarget {
201 DMSCGrafanaTarget {
202 expr: expr.to_string(),
203 ref_id: ref_id.to_string(),
204 legend_format: legend_format.map(|s| s.to_string()),
205 interval: None,
206 }
207 }
208
209 pub fn create_panel(&mut self, title: &str, panel_type: &str, x: i32, y: i32, w: i32, h: i32) -> DMSCGrafanaPanel {
211 let panel_id = self.next_panel_id;
212 self.next_panel_id += 1;
213
214 DMSCGrafanaPanel {
215 id: panel_id,
216 title: title.to_string(),
217 type_: panel_type.to_string(),
218 targets: Vec::new(),
219 grid_pos: DMSCGridPos { h, w, x, y },
220 field_config: serde_json::json!({ "defaults": {}, "overrides": [] }),
221 options: serde_json::json!({}),
222 description: None,
223 datasource: Some("Prometheus".to_string()),
224 }
225 }
226
227 pub fn create_request_rate_panel(&mut self, x: i32, y: i32, w: i32, h: i32) -> DMSCGrafanaPanel {
229 let mut panel = self.create_panel("Request Rate", "timeseries", x, y, w, h);
230 panel.targets.push(self.create_prometheus_target(
231 "rate(dms_requests_total[5m])",
232 "A",
233 Some("{{instance}}")
234 ));
235 panel
236 }
237
238 pub fn create_request_duration_panel(&mut self, x: i32, y: i32, w: i32, h: i32) -> DMSCGrafanaPanel {
240 let mut panel = self.create_panel("Request Duration", "timeseries", x, y, w, h);
241 panel.targets.push(self.create_prometheus_target(
242 "histogram_quantile(0.95, sum(rate(dms_request_duration_seconds_bucket[5m])) by (le))",
243 "A",
244 Some("95th Percentile")
245 ));
246 panel.targets.push(self.create_prometheus_target(
247 "histogram_quantile(0.5, sum(rate(dms_request_duration_seconds_bucket[5m])) by (le))",
248 "B",
249 Some("50th Percentile")
250 ));
251 panel
252 }
253
254 pub fn create_active_connections_panel(&mut self, x: i32, y: i32, w: i32, h: i32) -> DMSCGrafanaPanel {
256 let mut panel = self.create_panel("Active Connections", "stat", x, y, w, h);
257 panel.targets.push(self.create_prometheus_target(
258 "dms_active_connections",
259 "A",
260 None
261 ));
262 panel
263 }
264
265 pub fn create_error_rate_panel(&mut self, x: i32, y: i32, w: i32, h: i32) -> DMSCGrafanaPanel {
267 let mut panel = self.create_panel("Error Rate", "timeseries", x, y, w, h);
268 panel.targets.push(self.create_prometheus_target(
269 "rate(dms_errors_total[5m])",
270 "A",
271 Some("{{instance}}")
272 ));
273 panel
274 }
275
276 pub fn create_cache_metrics_panel(&mut self, x: i32, y: i32, w: i32, h: i32) -> DMSCGrafanaPanel {
278 let mut panel = self.create_panel("Cache Metrics", "timeseries", x, y, w, h);
279 panel.targets.push(self.create_prometheus_target(
280 "rate(dms_cache_hits_total[5m])",
281 "A",
282 Some("Hits")
283 ));
284 panel.targets.push(self.create_prometheus_target(
285 "rate(dms_cache_misses_total[5m])",
286 "B",
287 Some("Misses")
288 ));
289 panel
290 }
291
292 pub fn create_db_query_time_panel(&mut self, x: i32, y: i32, w: i32, h: i32) -> DMSCGrafanaPanel {
294 let mut panel = self.create_panel("Database Query Time", "timeseries", x, y, w, h);
295 panel.targets.push(self.create_prometheus_target(
296 "histogram_quantile(0.95, sum(rate(dms_db_query_duration_seconds_bucket[5m])) by (le))",
297 "A",
298 Some("95th Percentile")
299 ));
300 panel
301 }
302
303 pub fn generate_default_dashboard(&mut self) -> DMSCResult<DMSCGrafanaDashboard> {
305 let mut dashboard = self.create_dashboard("DMSC Default Dashboard");
306
307 dashboard.add_panel(self.create_request_rate_panel(0, 0, 12, 8))?;
309 dashboard.add_panel(self.create_request_duration_panel(12, 0, 12, 8))?;
310
311 dashboard.add_panel(self.create_error_rate_panel(0, 8, 12, 8))?;
313 dashboard.add_panel(self.create_active_connections_panel(12, 8, 6, 8))?;
314
315 dashboard.add_panel(self.create_cache_metrics_panel(0, 16, 12, 8))?;
317 dashboard.add_panel(self.create_db_query_time_panel(12, 16, 12, 8))?;
318
319 Ok(dashboard)
320 }
321
322 pub fn generate_auto_dashboard(&mut self, metrics: Vec<&str>, dashboard_title: &str) -> DMSCResult<DMSCGrafanaDashboard> {
336 let mut dashboard = self.create_dashboard(dashboard_title);
337
338 let mut counter_metrics = Vec::new();
340 let mut gauge_metrics = Vec::new();
341 let mut histogram_metrics = Vec::new();
342
343 for metric in metrics {
344 if metric.ends_with("_total") || metric.contains("count") {
345 counter_metrics.push(metric);
346 } else if metric.ends_with("_seconds") || metric.ends_with("_bytes") || metric.contains("time") {
347 histogram_metrics.push(metric);
348 } else {
349 gauge_metrics.push(metric);
350 }
351 }
352
353 let mut current_row = 0;
355
356 for (i, metric) in counter_metrics.iter().enumerate() {
358 let panel = self.create_counter_panel(*metric, i as i32 * 12, current_row, 12, 8);
359 dashboard.add_panel(panel)?;
360 if (i + 1) % 2 == 0 {
361 current_row += 8;
362 }
363 }
364
365 if counter_metrics.len() % 2 != 0 {
366 current_row += 8;
367 }
368
369 for (i, metric) in gauge_metrics.iter().enumerate() {
371 let panel = self.create_gauge_panel(*metric, i as i32 * 12, current_row, 12, 8);
372 dashboard.add_panel(panel)?;
373 if (i + 1) % 2 == 0 {
374 current_row += 8;
375 }
376 }
377
378 if gauge_metrics.len() % 2 != 0 {
379 current_row += 8;
380 }
381
382 for (i, metric) in histogram_metrics.iter().enumerate() {
384 let panel = self.create_histogram_panel(*metric, i as i32 * 12, current_row, 12, 8);
385 dashboard.add_panel(panel)?;
386 if (i + 1) % 2 == 0 {
387 current_row += 8;
388 }
389 }
390
391 Ok(dashboard)
392 }
393
394 pub fn create_counter_panel(&mut self, metric_name: &str, x: i32, y: i32, w: i32, h: i32) -> DMSCGrafanaPanel {
396 let title = self.title_case(metric_name);
397 let query = format!("rate({}[5m])", metric_name);
398
399 let mut panel = self.create_panel(&title, "timeseries", x, y, w, h);
400 panel.targets.push(self.create_prometheus_target(&query, "A", Some("{{instance}}")));
401 panel
402 }
403
404 pub fn create_gauge_panel(&mut self, metric_name: &str, x: i32, y: i32, w: i32, h: i32) -> DMSCGrafanaPanel {
406 let title = self.title_case(metric_name);
407
408 let mut panel = self.create_panel(&title, "stat", x, y, w, h);
409 panel.targets.push(self.create_prometheus_target(metric_name, "A", None));
410 panel
411 }
412
413 pub fn create_histogram_panel(&mut self, metric_name: &str, x: i32, y: i32, w: i32, h: i32) -> DMSCGrafanaPanel {
415 let title = self.title_case(metric_name);
416 let query_95 = format!("histogram_quantile(0.95, sum(rate({}_bucket[5m])) by (le))", metric_name);
417 let query_50 = format!("histogram_quantile(0.5, sum(rate({}_bucket[5m])) by (le))", metric_name);
418
419 let mut panel = self.create_panel(&title, "timeseries", x, y, w, h);
420 panel.targets.push(self.create_prometheus_target(&query_95, "A", Some("95th Percentile")));
421 panel.targets.push(self.create_prometheus_target(&query_50, "B", Some("50th Percentile")));
422 panel
423 }
424}