pg_blast_radius/
workload.rs1use serde::{Deserialize, Serialize};
2
3use crate::locks::DmlKind;
4use crate::types::LockMode;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct QueryFamily {
8 pub queryid: i64,
9 pub normalised_sql: String,
10 pub label: String,
11 pub tables: Vec<String>,
12 pub dml_kind: DmlKind,
13 pub lock_mode: LockMode,
14 pub calls_per_sec: f64,
15 pub mean_exec_ms: f64,
16 pub p95_exec_ms: Option<f64>,
17}
18
19impl QueryFamily {
20 pub fn calls_per_min(&self) -> f64 {
21 self.calls_per_sec * 60.0
22 }
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct TransactionBaseline {
27 pub active_sessions: i64,
28 pub idle_in_transaction: i64,
29 pub median_age_ms: f64,
30 pub p95_age_ms: f64,
31 pub max_age_ms: f64,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct WorkloadProfile {
36 pub query_families: Vec<QueryFamily>,
37 pub transaction_baseline: TransactionBaseline,
38 pub collected_at: String,
39 pub stats_reset: Option<String>,
40 #[serde(default)]
41 pub stats_window_seconds: Option<f64>,
42 pub unparseable_queries: usize,
43}
44
45impl WorkloadProfile {
46 pub fn families_for_table(&self, table: &str) -> Vec<&QueryFamily> {
47 self.query_families
48 .iter()
49 .filter(|qf| qf.tables.iter().any(|t| t == table || t.ends_with(&format!(".{table}"))))
50 .collect()
51 }
52
53 pub fn table_qps(&self, table: &str) -> f64 {
54 self.families_for_table(table)
55 .iter()
56 .map(|qf| qf.calls_per_sec)
57 .sum()
58 }
59}
60
61pub fn make_label(sql: &str) -> String {
62 let trimmed = sql.trim();
63 if trimmed.len() <= 60 {
64 trimmed.to_string()
65 } else {
66 format!("{}...", &trimmed[..57])
67 }
68}