Skip to main content

statsig_rust/interned_values/interned_store/
mmap_artifact.rs

1use std::{fs::Metadata, path::Path, time::UNIX_EPOCH};
2
3use crate::StatsigErr;
4
5use super::{
6    legacy_mmap_v1_path_for_sdk_key,
7    mmap_manifest::{inspect_mmap_v2_publication, MmapV2Publication},
8    mmap_manifest_path_for_sdk_key, mmap_v2_path_for_sdk_key, InternedStore,
9    LEGACY_MMAP_FORMAT_VERSION,
10};
11
12/// Describes the interned mmap artifacts linked at the SDK-owned location.
13///
14/// This intentionally omits the SDK key, filesystem path, and platform-specific
15/// file identity used to validate publication. V1 states are informational;
16/// the V2-only reader rejects them.
17#[non_exhaustive]
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
19pub enum MmapArtifactState {
20    /// No V1 artifact or V2 publication is linked at the SDK-owned location.
21    Missing,
22    /// A legacy V1 artifact is linked and no V2 publication was observed.
23    LegacyV1,
24    /// A V2 publication failed manifest identity validation while a legacy V1
25    /// artifact remains linked.
26    FallbackV1,
27    /// The V2 artifact and manifest identities match.
28    CommittedV2,
29    /// A V2-only artifact and manifest do not form a committed identity, so
30    /// readers must retry rather than fall back.
31    IncompleteV2,
32    /// V2 publication validation failed and no V1 fallback is linked.
33    Invalid,
34}
35
36impl MmapArtifactState {
37    /// Returns a stable, bounded label suitable for telemetry.
38    pub const fn as_str(self) -> &'static str {
39        match self {
40            Self::Missing => "missing",
41            Self::LegacyV1 => "legacy_v1",
42            Self::FallbackV1 => "fallback_v1",
43            Self::CommittedV2 => "committed_v2",
44            Self::IncompleteV2 => "incomplete_v2",
45            Self::Invalid => "invalid",
46        }
47    }
48}
49
50/// Safe metadata for an SDK-owned interned mmap artifact.
51///
52/// Byte counts cover only currently linked V1, V2, and manifest files. They do
53/// not include unlinked generations that remain mapped by readers or transient
54/// writer files. State and format reflect a reader-selectable publication
55/// observed during inspection. Linked file sizes and modification times are
56/// best-effort and may span a concurrent atomic publication. Filesystem
57/// capacity is best-effort and is omitted on unsupported platforms or when the
58/// backing filesystem cannot be inspected. A reported V1 format is not
59/// selectable by the V2-only reader.
60#[non_exhaustive]
61#[derive(Clone, Debug, Eq, PartialEq)]
62pub struct MmapArtifactSnapshot {
63    pub state: MmapArtifactState,
64    /// Format of the linked legacy or committed artifact. Unavailable or
65    /// invalid artifacts report `None`.
66    pub format_version: Option<u32>,
67    pub v1_bytes: Option<u64>,
68    pub v2_bytes: Option<u64>,
69    pub manifest_bytes: Option<u64>,
70    pub total_linked_bytes: u64,
71    pub linked_file_count: u64,
72    /// Newest whole-second modification timestamp among the linked files.
73    pub newest_linked_modified_unix_seconds: Option<u64>,
74    pub filesystem_capacity_bytes: Option<u64>,
75    pub filesystem_available_bytes: Option<u64>,
76}
77
78impl InternedStore {
79    /// Inspects the SDK-owned artifact without exposing its derived path or file
80    /// identity.
81    ///
82    /// V2 state uses the same committed-manifest identity validation as
83    /// `preload_mmap`. The snapshot is informational and does not map or parse
84    /// either archived config payload.
85    pub fn inspect_mmap_artifact(sdk_key: &str) -> Result<MmapArtifactSnapshot, StatsigErr> {
86        let v1_path = legacy_mmap_v1_path_for_sdk_key(sdk_key);
87        let v2_path = mmap_v2_path_for_sdk_key(sdk_key);
88        let manifest_path = mmap_manifest_path_for_sdk_key(sdk_key);
89
90        let publication = inspect_mmap_v2_publication(&manifest_path, &v1_path, &v2_path)?;
91        let v1_metadata = metadata_if_linked(&v1_path)?;
92        let manifest_metadata = metadata_if_linked(&manifest_path)?;
93
94        let (state, format_version, committed_v2_metadata) = match publication {
95            MmapV2Publication::Absent if v1_metadata.is_some() => (
96                MmapArtifactState::LegacyV1,
97                Some(LEGACY_MMAP_FORMAT_VERSION),
98                None,
99            ),
100            MmapV2Publication::Absent => (MmapArtifactState::Missing, None, None),
101            MmapV2Publication::Committed(file) => {
102                let metadata = file
103                    .metadata()
104                    .map_err(|error| StatsigErr::FileError(error.to_string()))?;
105                (
106                    MmapArtifactState::CommittedV2,
107                    Some(crate::interned_values::INTERNED_MMAP_FORMAT_VERSION),
108                    Some(metadata),
109                )
110            }
111            MmapV2Publication::Incomplete => (MmapArtifactState::IncompleteV2, None, None),
112            MmapV2Publication::Invalid(_) if v1_metadata.is_some() => (
113                MmapArtifactState::FallbackV1,
114                Some(LEGACY_MMAP_FORMAT_VERSION),
115                None,
116            ),
117            MmapV2Publication::Invalid(_) => (MmapArtifactState::Invalid, None, None),
118        };
119
120        let v2_metadata = match committed_v2_metadata {
121            Some(metadata) => Some(metadata),
122            None => metadata_if_linked(&v2_path)?,
123        };
124        let v1_bytes = v1_metadata.as_ref().map(Metadata::len);
125        let v2_bytes = v2_metadata.as_ref().map(Metadata::len);
126        let manifest_bytes = manifest_metadata.as_ref().map(Metadata::len);
127        let total_linked_bytes = [v1_bytes, v2_bytes, manifest_bytes]
128            .into_iter()
129            .flatten()
130            .fold(0_u64, u64::saturating_add);
131        let linked_file_count = [v1_bytes, v2_bytes, manifest_bytes]
132            .into_iter()
133            .filter(Option::is_some)
134            .count() as u64;
135        let newest_linked_modified_unix_seconds = [
136            v1_metadata.as_ref(),
137            v2_metadata.as_ref(),
138            manifest_metadata.as_ref(),
139        ]
140        .into_iter()
141        .flatten()
142        .filter_map(modified_unix_seconds)
143        .max();
144        let (filesystem_capacity_bytes, filesystem_available_bytes) = manifest_path
145            .parent()
146            .map(filesystem_space)
147            .unwrap_or((None, None));
148
149        Ok(MmapArtifactSnapshot {
150            state,
151            format_version,
152            v1_bytes,
153            v2_bytes,
154            manifest_bytes,
155            total_linked_bytes,
156            linked_file_count,
157            newest_linked_modified_unix_seconds,
158            filesystem_capacity_bytes,
159            filesystem_available_bytes,
160        })
161    }
162}
163
164fn metadata_if_linked(path: &Path) -> Result<Option<Metadata>, StatsigErr> {
165    match path.metadata() {
166        Ok(metadata) => Ok(Some(metadata)),
167        Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),
168        Err(error) => Err(StatsigErr::FileError(error.to_string())),
169    }
170}
171
172fn modified_unix_seconds(metadata: &Metadata) -> Option<u64> {
173    metadata
174        .modified()
175        .ok()?
176        .duration_since(UNIX_EPOCH)
177        .ok()
178        .map(|duration| duration.as_secs())
179}
180
181#[cfg(any(unix, windows))]
182fn filesystem_space(path: &Path) -> (Option<u64>, Option<u64>) {
183    fs4::statvfs(path)
184        .map(|stats| (Some(stats.total_space()), Some(stats.available_space())))
185        .unwrap_or((None, None))
186}
187
188#[cfg(not(any(unix, windows)))]
189fn filesystem_space(_path: &Path) -> (Option<u64>, Option<u64>) {
190    (None, None)
191}