use serde::{Deserialize, Serialize};
pub mod cache_io;
pub mod cardinality;
pub mod contention;
pub mod plan_regression;
pub mod workload_phase;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ResidualClass {
PlanRegression,
Cardinality,
Contention,
CacheIo,
WorkloadPhase,
}
impl ResidualClass {
pub const ALL: [ResidualClass; 5] = [
Self::PlanRegression,
Self::Cardinality,
Self::Contention,
Self::CacheIo,
Self::WorkloadPhase,
];
pub fn name(&self) -> &'static str {
match self {
Self::PlanRegression => "plan_regression",
Self::Cardinality => "cardinality",
Self::Contention => "contention",
Self::CacheIo => "cache_io",
Self::WorkloadPhase => "workload_phase",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResidualSample {
pub t: f64,
pub class: ResidualClass,
pub value: f64,
pub channel: Option<String>,
}
impl ResidualSample {
pub fn new(t: f64, class: ResidualClass, value: f64) -> Self {
debug_assert!(value.is_finite(), "residual value must be finite");
Self {
t,
class,
value,
channel: None,
}
}
pub fn with_channel(mut self, channel: impl Into<String>) -> Self {
self.channel = Some(channel.into());
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ResidualStream {
pub source: String,
pub samples: Vec<ResidualSample>,
}
impl ResidualStream {
pub fn new(source: impl Into<String>) -> Self {
Self {
source: source.into(),
samples: Vec::new(),
}
}
pub fn push(&mut self, s: ResidualSample) {
self.samples.push(s);
}
pub fn sort(&mut self) {
self.samples.sort_by(|a, b| {
debug_assert!(
a.t.is_finite() && b.t.is_finite(),
"residual t must be finite"
);
a.t.partial_cmp(&b.t).unwrap_or(std::cmp::Ordering::Equal)
});
}
pub fn len(&self) -> usize {
self.samples.len()
}
pub fn is_empty(&self) -> bool {
self.samples.is_empty()
}
pub fn duration(&self) -> f64 {
match (self.samples.first(), self.samples.last()) {
(Some(a), Some(b)) => b.t - a.t,
(None, None) => 0.0,
(None, Some(_)) | (Some(_), None) => {
debug_assert!(false, "first/last disagree on emptiness");
0.0
}
}
}
pub fn iter_class(&self, class: ResidualClass) -> impl Iterator<Item = &ResidualSample> + '_ {
self.samples.iter().filter(move |s| s.class == class)
}
pub fn fingerprint(&self) -> [u8; 32] {
use sha2::{Digest, Sha256};
let mut h = Sha256::new();
h.update(self.source.as_bytes());
for s in &self.samples {
h.update(s.t.to_le_bytes());
h.update((s.class as u8).to_le_bytes());
h.update(s.value.to_le_bytes());
if let Some(c) = &s.channel {
h.update(c.as_bytes());
}
h.update(b"|");
}
h.finalize().into()
}
}