dsfb_database/residual/
mod.rs1use serde::{Deserialize, Serialize};
14
15pub mod cache_io;
16pub mod cardinality;
17pub mod contention;
18pub mod plan_regression;
19pub mod workload_phase;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
25pub enum ResidualClass {
26 PlanRegression,
28 Cardinality,
30 Contention,
32 CacheIo,
34 WorkloadPhase,
36}
37
38impl ResidualClass {
39 pub const ALL: [ResidualClass; 5] = [
40 Self::PlanRegression,
41 Self::Cardinality,
42 Self::Contention,
43 Self::CacheIo,
44 Self::WorkloadPhase,
45 ];
46
47 pub fn name(&self) -> &'static str {
48 match self {
49 Self::PlanRegression => "plan_regression",
50 Self::Cardinality => "cardinality",
51 Self::Contention => "contention",
52 Self::CacheIo => "cache_io",
53 Self::WorkloadPhase => "workload_phase",
54 }
55 }
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct ResidualSample {
63 pub t: f64,
64 pub class: ResidualClass,
65 pub value: f64,
69 pub channel: Option<String>,
72}
73
74impl ResidualSample {
75 pub fn new(t: f64, class: ResidualClass, value: f64) -> Self {
76 debug_assert!(value.is_finite(), "residual value must be finite");
77 Self {
78 t,
79 class,
80 value,
81 channel: None,
82 }
83 }
84
85 pub fn with_channel(mut self, channel: impl Into<String>) -> Self {
86 self.channel = Some(channel.into());
87 self
88 }
89}
90
91#[derive(Debug, Clone, Default, Serialize, Deserialize)]
95pub struct ResidualStream {
96 pub source: String,
98 pub samples: Vec<ResidualSample>,
100}
101
102impl ResidualStream {
103 pub fn new(source: impl Into<String>) -> Self {
104 Self {
105 source: source.into(),
106 samples: Vec::new(),
107 }
108 }
109
110 pub fn push(&mut self, s: ResidualSample) {
111 self.samples.push(s);
112 }
113
114 pub fn sort(&mut self) {
115 self.samples.sort_by(|a, b| {
116 debug_assert!(
117 a.t.is_finite() && b.t.is_finite(),
118 "residual t must be finite"
119 );
120 a.t.partial_cmp(&b.t).unwrap_or(std::cmp::Ordering::Equal)
121 });
122 }
123
124 pub fn len(&self) -> usize {
125 self.samples.len()
126 }
127
128 pub fn is_empty(&self) -> bool {
129 self.samples.is_empty()
130 }
131
132 pub fn duration(&self) -> f64 {
133 match (self.samples.first(), self.samples.last()) {
134 (Some(a), Some(b)) => b.t - a.t,
135 (None, None) => 0.0,
136 (None, Some(_)) | (Some(_), None) => {
137 debug_assert!(false, "first/last disagree on emptiness");
138 0.0
139 }
140 }
141 }
142
143 pub fn iter_class(&self, class: ResidualClass) -> impl Iterator<Item = &ResidualSample> + '_ {
146 self.samples.iter().filter(move |s| s.class == class)
147 }
148
149 pub fn fingerprint(&self) -> [u8; 32] {
152 use sha2::{Digest, Sha256};
153 let mut h = Sha256::new();
154 h.update(self.source.as_bytes());
155 for s in &self.samples {
156 h.update(s.t.to_le_bytes());
157 h.update((s.class as u8).to_le_bytes());
158 h.update(s.value.to_le_bytes());
159 if let Some(c) = &s.channel {
160 h.update(c.as_bytes());
161 }
162 h.update(b"|");
163 }
164 h.finalize().into()
165 }
166}