use std::{fs::Metadata, path::Path, time::UNIX_EPOCH};
use crate::StatsigErr;
use super::{
legacy_mmap_v1_path_for_sdk_key,
mmap_manifest::{inspect_mmap_v2_publication, MmapV2Publication},
mmap_manifest_path_for_sdk_key, mmap_v2_path_for_sdk_key, InternedStore,
LEGACY_MMAP_FORMAT_VERSION,
};
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MmapArtifactState {
Missing,
LegacyV1,
FallbackV1,
CommittedV2,
IncompleteV2,
Invalid,
}
impl MmapArtifactState {
pub const fn as_str(self) -> &'static str {
match self {
Self::Missing => "missing",
Self::LegacyV1 => "legacy_v1",
Self::FallbackV1 => "fallback_v1",
Self::CommittedV2 => "committed_v2",
Self::IncompleteV2 => "incomplete_v2",
Self::Invalid => "invalid",
}
}
}
#[non_exhaustive]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MmapArtifactSnapshot {
pub state: MmapArtifactState,
pub format_version: Option<u32>,
pub v1_bytes: Option<u64>,
pub v2_bytes: Option<u64>,
pub manifest_bytes: Option<u64>,
pub total_linked_bytes: u64,
pub linked_file_count: u64,
pub newest_linked_modified_unix_seconds: Option<u64>,
pub filesystem_capacity_bytes: Option<u64>,
pub filesystem_available_bytes: Option<u64>,
}
impl InternedStore {
pub fn inspect_mmap_artifact(sdk_key: &str) -> Result<MmapArtifactSnapshot, StatsigErr> {
let v1_path = legacy_mmap_v1_path_for_sdk_key(sdk_key);
let v2_path = mmap_v2_path_for_sdk_key(sdk_key);
let manifest_path = mmap_manifest_path_for_sdk_key(sdk_key);
let publication = inspect_mmap_v2_publication(&manifest_path, &v1_path, &v2_path)?;
let v1_metadata = metadata_if_linked(&v1_path)?;
let manifest_metadata = metadata_if_linked(&manifest_path)?;
let (state, format_version, committed_v2_metadata) = match publication {
MmapV2Publication::Absent if v1_metadata.is_some() => (
MmapArtifactState::LegacyV1,
Some(LEGACY_MMAP_FORMAT_VERSION),
None,
),
MmapV2Publication::Absent => (MmapArtifactState::Missing, None, None),
MmapV2Publication::Committed(file) => {
let metadata = file
.metadata()
.map_err(|error| StatsigErr::FileError(error.to_string()))?;
(
MmapArtifactState::CommittedV2,
Some(crate::interned_values::INTERNED_MMAP_FORMAT_VERSION),
Some(metadata),
)
}
MmapV2Publication::Incomplete => (MmapArtifactState::IncompleteV2, None, None),
MmapV2Publication::Invalid(_) if v1_metadata.is_some() => (
MmapArtifactState::FallbackV1,
Some(LEGACY_MMAP_FORMAT_VERSION),
None,
),
MmapV2Publication::Invalid(_) => (MmapArtifactState::Invalid, None, None),
};
let v2_metadata = match committed_v2_metadata {
Some(metadata) => Some(metadata),
None => metadata_if_linked(&v2_path)?,
};
let v1_bytes = v1_metadata.as_ref().map(Metadata::len);
let v2_bytes = v2_metadata.as_ref().map(Metadata::len);
let manifest_bytes = manifest_metadata.as_ref().map(Metadata::len);
let total_linked_bytes = [v1_bytes, v2_bytes, manifest_bytes]
.into_iter()
.flatten()
.fold(0_u64, u64::saturating_add);
let linked_file_count = [v1_bytes, v2_bytes, manifest_bytes]
.into_iter()
.filter(Option::is_some)
.count() as u64;
let newest_linked_modified_unix_seconds = [
v1_metadata.as_ref(),
v2_metadata.as_ref(),
manifest_metadata.as_ref(),
]
.into_iter()
.flatten()
.filter_map(modified_unix_seconds)
.max();
let (filesystem_capacity_bytes, filesystem_available_bytes) = manifest_path
.parent()
.map(filesystem_space)
.unwrap_or((None, None));
Ok(MmapArtifactSnapshot {
state,
format_version,
v1_bytes,
v2_bytes,
manifest_bytes,
total_linked_bytes,
linked_file_count,
newest_linked_modified_unix_seconds,
filesystem_capacity_bytes,
filesystem_available_bytes,
})
}
}
fn metadata_if_linked(path: &Path) -> Result<Option<Metadata>, StatsigErr> {
match path.metadata() {
Ok(metadata) => Ok(Some(metadata)),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),
Err(error) => Err(StatsigErr::FileError(error.to_string())),
}
}
fn modified_unix_seconds(metadata: &Metadata) -> Option<u64> {
metadata
.modified()
.ok()?
.duration_since(UNIX_EPOCH)
.ok()
.map(|duration| duration.as_secs())
}
#[cfg(any(unix, windows))]
fn filesystem_space(path: &Path) -> (Option<u64>, Option<u64>) {
fs4::statvfs(path)
.map(|stats| (Some(stats.total_space()), Some(stats.available_space())))
.unwrap_or((None, None))
}
#[cfg(not(any(unix, windows)))]
fn filesystem_space(_path: &Path) -> (Option<u64>, Option<u64>) {
(None, None)
}