use crate::store::cold_start::rebuild::OpenIndexReport;
use crate::store::stats::{
FrontierView, PlatformEvidenceSummary, StoreDiagnostics, WriterPressure,
};
use crate::store::RestartPolicy;
use serde::{Deserialize, Serialize};
use std::path::Path;
pub const STORE_RESOURCE_REPORT_SCHEMA_VERSION: u32 = 1;
pub type StoreResourceHash = [u8; 32];
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum StoreResourceReportError {
BodyEncoding {
message: String,
},
}
impl std::fmt::Display for StoreResourceReportError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::BodyEncoding { message } => {
write!(f, "store resource report body encoding failed: {message}")
}
}
}
}
impl std::error::Error for StoreResourceReportError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum StoreResourceRestartPolicyShape {
Once,
Bounded {
max_restarts: u32,
within_ms: u64,
},
}
fn restart_policy_shape(policy: &RestartPolicy) -> StoreResourceRestartPolicyShape {
match policy {
RestartPolicy::Once => StoreResourceRestartPolicyShape::Once,
RestartPolicy::Bounded {
max_restarts,
within_ms,
} => StoreResourceRestartPolicyShape::Bounded {
max_restarts: *max_restarts,
within_ms: *within_ms,
},
#[allow(unreachable_patterns)]
_ => StoreResourceRestartPolicyShape::Once,
}
}
#[must_use]
pub fn store_data_dir_identity_hash(path: &Path) -> StoreResourceHash {
let canonical;
let identity_path = match std::fs::canonicalize(path) {
Ok(path) => {
canonical = path;
canonical.as_path()
}
Err(_) => path,
};
let bytes =
crate::store::platform::path_identity::path_bytes_for_identity_digest(identity_path);
crate::evidence::content_hash(&bytes)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct StoreResourceFrontierBody {
pub accepted_wall_ms: u64,
pub accepted_global_sequence: u64,
pub written_wall_ms: u64,
pub written_global_sequence: u64,
pub durable_wall_ms: u64,
pub durable_global_sequence: u64,
pub visible_wall_ms: u64,
pub visible_global_sequence: u64,
pub applied_wall_ms: u64,
pub applied_global_sequence: u64,
pub emitted_wall_ms: u64,
pub emitted_global_sequence: u64,
pub visible_minus_durable_seq: i64,
pub oldest_pending_write_age_ms: Option<u64>,
}
impl From<FrontierView> for StoreResourceFrontierBody {
fn from(f: FrontierView) -> Self {
Self {
accepted_wall_ms: f.accepted_hlc.wall_ms,
accepted_global_sequence: f.accepted_hlc.global_sequence,
written_wall_ms: f.written_hlc.wall_ms,
written_global_sequence: f.written_hlc.global_sequence,
durable_wall_ms: f.durable_hlc.wall_ms,
durable_global_sequence: f.durable_hlc.global_sequence,
visible_wall_ms: f.current_visible_hlc.wall_ms,
visible_global_sequence: f.current_visible_hlc.global_sequence,
applied_wall_ms: f.applied_hlc.wall_ms,
applied_global_sequence: f.applied_hlc.global_sequence,
emitted_wall_ms: f.emitted_hlc.wall_ms,
emitted_global_sequence: f.emitted_hlc.global_sequence,
visible_minus_durable_seq: f.visible_minus_durable_seq,
oldest_pending_write_age_ms: f.oldest_pending_write_age_ms,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct StoreResourceReportBody {
pub schema_version: u32,
pub data_dir_identity_hash: StoreResourceHash,
pub event_count: u64,
pub global_sequence: u64,
pub visible_sequence: u64,
pub segment_max_bytes: u64,
pub fd_budget: u64,
pub restart_policy: StoreResourceRestartPolicyShape,
pub writer_pressure: WriterPressure,
pub frontier: StoreResourceFrontierBody,
pub index_topology: String,
pub tile_count: u64,
pub open_report: Option<OpenIndexReport>,
pub platform_evidence: PlatformEvidenceSummary,
}
#[must_use]
pub fn store_resource_report_body_from_diagnostics(
d: &StoreDiagnostics,
) -> StoreResourceReportBody {
StoreResourceReportBody {
schema_version: STORE_RESOURCE_REPORT_SCHEMA_VERSION,
data_dir_identity_hash: store_data_dir_identity_hash(&d.data_dir),
event_count: u64::try_from(d.event_count).unwrap_or(u64::MAX),
global_sequence: d.global_sequence,
visible_sequence: d.visible_sequence,
segment_max_bytes: d.segment_max_bytes,
fd_budget: u64::try_from(d.fd_budget).unwrap_or(u64::MAX),
restart_policy: restart_policy_shape(&d.restart_policy),
writer_pressure: d.writer_pressure,
frontier: StoreResourceFrontierBody::from(d.frontier),
index_topology: d.index_topology.to_string(),
tile_count: u64::try_from(d.tile_count).unwrap_or(u64::MAX),
open_report: d.open_report.clone(),
platform_evidence: d.platform_evidence.clone(),
}
}
pub fn store_resource_report_body_hash(
body: &StoreResourceReportBody,
) -> Result<StoreResourceHash, StoreResourceReportError> {
crate::evidence::report_body_hash(body, |message| StoreResourceReportError::BodyEncoding {
message,
})
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct StoreResourceEvidenceReport {
pub body: StoreResourceReportBody,
pub body_hash: StoreResourceHash,
pub generated_at_unix_ms: Option<u64>,
pub batpak_version: Option<String>,
pub diagnostics: Vec<String>,
}
pub type StoreResourceEnvelope = StoreResourceEvidenceReport;
pub fn store_resource_evidence_report_from_diagnostics(
d: &StoreDiagnostics,
) -> Result<StoreResourceEvidenceReport, StoreResourceReportError> {
let body = store_resource_report_body_from_diagnostics(d);
let body_hash = store_resource_report_body_hash(&body)?;
Ok(StoreResourceEvidenceReport {
body,
body_hash,
generated_at_unix_ms: None,
batpak_version: None,
diagnostics: Vec::new(),
})
}
#[cfg(test)]
mod tests {
use super::store_data_dir_identity_hash;
#[test]
fn data_dir_identity_hash_canonicalizes_existing_path_spellings() {
let dir = tempfile::TempDir::new().expect("create temp dir");
let raw_spelling = dir.path().join(".");
let canonical = std::fs::canonicalize(dir.path()).expect("canonicalize temp dir");
assert_eq!(
store_data_dir_identity_hash(&raw_spelling),
store_data_dir_identity_hash(&canonical)
);
}
}