Skip to main content

ave_actors_store/
config.rs

1use std::fmt::Display;
2
3use serde::{Deserialize, Serialize};
4
5/// How to size the database backend.
6///
7/// - `Profile` — use a predefined instance type: implies fixed vCPU and RAM.
8/// - `Custom`  — supply exact RAM (MB) and vCPU count manually.
9/// - Absent (`None` in `Config`) — auto-detect from the running host.
10#[derive(Serialize, Deserialize, Debug, Clone)]
11#[serde(rename_all = "snake_case")]
12pub enum MachineSpec {
13    Profile(MachineProfile),
14    Custom { ram_mb: u64, cpu_cores: usize },
15}
16
17/// Predefined instance profiles with fixed vCPU and RAM.
18/// They only exist to provide convenient default values — the actual
19/// DB tuning is derived from the resolved `ram_mb` and `cpu_cores`.
20///
21/// | Profile  | vCPU | RAM    |
22/// |----------|------|--------|
23/// | Nano     | 2    | 512 MB |
24/// | Micro    | 2    | 1 GB   |
25/// | Small    | 2    | 2 GB   |
26/// | Medium   | 2    | 4 GB   |
27/// | Large    | 2    | 8 GB   |
28/// | XLarge   | 4    | 16 GB  |
29/// | XXLarge  | 8    | 32 GB  |
30#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
31#[serde(rename_all = "snake_case")]
32pub enum MachineProfile {
33    Nano,
34    Micro,
35    Small,
36    Medium,
37    Large,
38    XLarge,
39    #[serde(rename = "2xlarge")]
40    XXLarge,
41}
42
43impl MachineProfile {
44    /// Canonical RAM for this profile in megabytes.
45    pub const fn ram_mb(self) -> u64 {
46        match self {
47            Self::Nano => 512,
48            Self::Micro => 1024,
49            Self::Small => 2048,
50            Self::Medium => 4096,
51            Self::Large => 8192,
52            Self::XLarge => 16384,
53            Self::XXLarge => 32768,
54        }
55    }
56
57    /// vCPU count for this profile.
58    pub const fn cpu_cores(self) -> usize {
59        match self {
60            Self::Nano => 2,
61            Self::Micro => 2,
62            Self::Small => 2,
63            Self::Medium => 2,
64            Self::Large => 2,
65            Self::XLarge => 4,
66            Self::XXLarge => 8,
67        }
68    }
69}
70
71impl Display for MachineProfile {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        match self {
74            Self::Nano => write!(f, "nano"),
75            Self::Micro => write!(f, "micro"),
76            Self::Small => write!(f, "small"),
77            Self::Medium => write!(f, "medium"),
78            Self::Large => write!(f, "large"),
79            Self::XLarge => write!(f, "xlarge"),
80            Self::XXLarge => write!(f, "2xlarge"),
81        }
82    }
83}
84
85/// Resolved machine parameters ready to be consumed by database backends.
86/// Database tuning is computed directly from these two values.
87pub struct ResolvedSpec {
88    pub ram_mb: u64,
89    pub cpu_cores: usize,
90}
91
92/// Resolve the final DB sizing parameters from a [`MachineSpec`]:
93///
94/// - `Profile(p)` → use the profile's canonical RAM and vCPU.
95/// - `Custom { ram_mb, cpu_cores }` → use the supplied values directly.
96/// - `None` → auto-detect total RAM and available CPU cores from the host.
97pub fn resolve_spec(spec: Option<MachineSpec>) -> ResolvedSpec {
98    match spec {
99        Some(MachineSpec::Profile(p)) => ResolvedSpec {
100            ram_mb: p.ram_mb(),
101            cpu_cores: p.cpu_cores(),
102        },
103        Some(MachineSpec::Custom { ram_mb, cpu_cores }) => {
104            ResolvedSpec { ram_mb, cpu_cores }
105        }
106        None => ResolvedSpec {
107            ram_mb: detect_total_memory_mb().unwrap_or(4096),
108            cpu_cores: detect_cpu_cores(),
109        },
110    }
111}
112
113/// Reads total physical RAM from `/proc/meminfo` on Linux and returns it in megabytes.
114///
115/// Returns `None` on non-Linux platforms or if the file cannot be parsed.
116pub fn detect_total_memory_mb() -> Option<u64> {
117    #[cfg(target_os = "linux")]
118    {
119        use std::fs;
120        let meminfo = fs::read_to_string("/proc/meminfo").ok()?;
121        for line in meminfo.lines() {
122            if let Some(rest) = line.strip_prefix("MemTotal:") {
123                let kb_str = rest.split_whitespace().next()?;
124                let kb: u64 = kb_str.parse().ok()?;
125                return Some(kb / 1024);
126            }
127        }
128        None
129    }
130    #[cfg(not(target_os = "linux"))]
131    {
132        None
133    }
134}
135
136/// Returns the number of logical CPU cores available to the process.
137///
138/// Falls back to `1` if the value cannot be determined.
139pub fn detect_cpu_cores() -> usize {
140    std::thread::available_parallelism()
141        .map(|n| n.get())
142        .unwrap_or(1)
143}