use serde::{Deserialize, Serialize};
#[must_use]
pub fn host_memory_size() -> u64 {
#[cfg(target_os = "macos")]
{
let mut size: u64 = 0;
let mut len = std::mem::size_of::<u64>();
let ret = unsafe {
libc::sysctlbyname(
c"hw.memsize".as_ptr(),
std::ptr::addr_of_mut!(size).cast(),
&mut len,
std::ptr::null_mut(),
0,
)
};
if ret == 0 { size } else { 0 }
}
#[cfg(target_os = "linux")]
{
let physical = unsafe {
let mut info: libc::sysinfo = std::mem::zeroed();
if libc::sysinfo(&mut info) == 0 {
info.totalram * u64::from(info.mem_unit)
} else {
return 0;
}
};
let cgroup = cgroup_memory_limit();
if cgroup > 0 && cgroup < physical {
cgroup
} else {
physical
}
}
}
#[cfg(target_os = "linux")]
fn cgroup_memory_limit() -> u64 {
if let Some(v) = cgroup_v2_memory_limit() {
return v;
}
if let Some(v) = cgroup_v1_memory_limit() {
return v;
}
0
}
#[cfg(target_os = "linux")]
fn cgroup_v2_memory_limit() -> Option<u64> {
let cgroup_path = std::fs::read_to_string("/proc/self/cgroup")
.ok()?
.lines()
.find(|l| l.starts_with("0::"))
.map(|l| l.strip_prefix("0::").unwrap_or("/").to_string())?;
let paths = [
format!("/sys/fs/cgroup{cgroup_path}/memory.max"),
"/sys/fs/cgroup/memory.max".to_string(),
];
for path in &paths {
if let Ok(s) = std::fs::read_to_string(path) {
let s = s.trim();
if s != "max" {
if let Ok(v) = s.parse::<u64>() {
return Some(v);
}
}
}
}
None
}
#[cfg(target_os = "linux")]
fn cgroup_v1_memory_limit() -> Option<u64> {
let cgroup_path = std::fs::read_to_string("/proc/self/cgroup")
.ok()?
.lines()
.find_map(|l| {
let parts: Vec<&str> = l.splitn(3, ':').collect();
if parts.len() == 3 && parts[1].split(',').any(|c| c == "memory") {
Some(parts[2].to_string())
} else {
None
}
})?;
let paths = [
format!("/sys/fs/cgroup/memory{cgroup_path}/memory.limit_in_bytes"),
"/sys/fs/cgroup/memory/memory.limit_in_bytes".to_string(),
];
for path in &paths {
if let Ok(s) = std::fs::read_to_string(path) {
let s = s.trim();
if let Ok(v) = s.parse::<u64>() {
if v < (1 << 62) {
return Some(v);
}
}
}
}
None
}
#[must_use]
pub fn default_vm_memory_size() -> u64 {
const MIN_DEFAULT: u64 = 512 * 1024 * 1024; const MAX_DEFAULT: u64 = 16 * 1024 * 1024 * 1024; const FALLBACK: u64 = 4 * 1024 * 1024 * 1024; const MIB: u64 = 1024 * 1024;
let host = host_memory_size();
if host == 0 {
return FALLBACK;
}
let size = (host / 2).clamp(MIN_DEFAULT, MAX_DEFAULT);
size & !(MIB - 1)
}
#[must_use]
pub fn default_vm_cpu_count() -> u32 {
std::thread::available_parallelism().map_or(4, |n| u32::try_from(n.get()).unwrap_or(u32::MAX))
}
pub fn warn_memory_exceeds_host_half(memory_size: u64) {
let host_mem = host_memory_size();
if host_mem > 0 && memory_size > host_mem / 2 {
tracing::warn!(
"VM memory {}MB exceeds 50% of host RAM ({}MB total) — host may experience memory pressure",
memory_size / (1024 * 1024),
host_mem / (1024 * 1024),
);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum CpuArch {
X86_64,
Aarch64,
}
impl CpuArch {
#[must_use]
pub const fn native() -> Self {
#[cfg(target_arch = "x86_64")]
{
Self::X86_64
}
#[cfg(target_arch = "aarch64")]
{
Self::Aarch64
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
compile_error!("Unsupported CPU architecture")
}
}
}
#[derive(Debug, Clone)]
pub struct PlatformCapabilities {
pub supported_archs: Vec<CpuArch>,
pub max_vcpus: u32,
pub max_memory: u64,
pub nested_virt: bool,
pub rosetta: bool,
}
impl Default for PlatformCapabilities {
fn default() -> Self {
Self {
supported_archs: vec![CpuArch::native()],
max_vcpus: 1,
max_memory: 1024 * 1024 * 1024, nested_virt: false,
rosetta: false,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Registers {
pub rax: u64,
pub rbx: u64,
pub rcx: u64,
pub rdx: u64,
pub rsi: u64,
pub rdi: u64,
pub rsp: u64,
pub rbp: u64,
pub r8: u64,
pub r9: u64,
pub r10: u64,
pub r11: u64,
pub r12: u64,
pub r13: u64,
pub r14: u64,
pub r15: u64,
pub rip: u64,
pub rflags: u64,
}
#[derive(Debug, Clone)]
pub enum VcpuExit {
Halt,
IoOut {
port: u16,
size: u8,
data: u64,
},
IoIn {
port: u16,
size: u8,
},
MmioRead {
addr: u64,
size: u8,
},
MmioWrite {
addr: u64,
size: u8,
data: u64,
},
Hypercall {
nr: u64,
args: [u64; 6],
},
SystemReset,
Shutdown,
Debug,
Unknown(i32),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VirtioDeviceConfig {
pub device_type: VirtioDeviceType,
pub config: Vec<u8>,
pub path: Option<String>,
pub read_only: bool,
pub tag: Option<String>,
#[serde(skip)]
pub net_fd: Option<i32>,
pub mac_address: Option<String>,
}
impl VirtioDeviceConfig {
pub fn block(path: impl Into<String>, read_only: bool) -> Self {
Self {
device_type: VirtioDeviceType::Block,
config: Vec::new(),
path: Some(path.into()),
read_only,
tag: None,
net_fd: None,
mac_address: None,
}
}
#[must_use]
pub const fn network() -> Self {
Self {
device_type: VirtioDeviceType::Net,
config: Vec::new(),
path: None,
read_only: false,
tag: None,
net_fd: None,
mac_address: None,
}
}
pub fn network_with_mac(mac_address: impl Into<String>) -> Self {
Self {
device_type: VirtioDeviceType::Net,
config: Vec::new(),
path: None,
read_only: false,
tag: None,
net_fd: None,
mac_address: Some(mac_address.into()),
}
}
#[must_use]
pub const fn network_file_handle(fd: i32) -> Self {
Self {
device_type: VirtioDeviceType::Net,
config: Vec::new(),
path: None,
read_only: false,
tag: None,
net_fd: Some(fd),
mac_address: None,
}
}
pub fn network_file_handle_with_mac(fd: i32, mac_address: impl Into<String>) -> Self {
Self {
device_type: VirtioDeviceType::Net,
config: Vec::new(),
path: None,
read_only: false,
tag: None,
net_fd: Some(fd),
mac_address: Some(mac_address.into()),
}
}
#[must_use]
pub const fn console() -> Self {
Self {
device_type: VirtioDeviceType::Console,
config: Vec::new(),
path: None,
read_only: false,
tag: None,
net_fd: None,
mac_address: None,
}
}
pub fn filesystem(path: impl Into<String>, tag: impl Into<String>, read_only: bool) -> Self {
Self {
device_type: VirtioDeviceType::Fs,
config: Vec::new(),
path: Some(path.into()),
read_only,
tag: Some(tag.into()),
net_fd: None,
mac_address: None,
}
}
#[must_use]
pub const fn vsock() -> Self {
Self {
device_type: VirtioDeviceType::Vsock,
config: Vec::new(),
path: None,
read_only: false,
tag: None,
net_fd: None,
mac_address: None,
}
}
#[must_use]
pub const fn entropy() -> Self {
Self {
device_type: VirtioDeviceType::Rng,
config: Vec::new(),
path: None,
read_only: false,
tag: None,
net_fd: None,
mac_address: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum VirtioDeviceType {
Block,
Net,
Console,
Fs,
Vsock,
Rng,
Balloon,
Gpu,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BalloonStats {
pub target_bytes: u64,
pub current_bytes: u64,
pub configured_bytes: u64,
}
impl BalloonStats {
#[must_use]
pub const fn effective_memory(&self) -> u64 {
self.configured_bytes.saturating_sub(self.current_bytes)
}
#[must_use]
pub fn target_percent(&self) -> f64 {
if self.configured_bytes == 0 {
return 100.0;
}
(self.target_bytes as f64 / self.configured_bytes as f64) * 100.0
}
}
impl VirtioDeviceConfig {
#[must_use]
pub const fn balloon() -> Self {
Self {
device_type: VirtioDeviceType::Balloon,
config: Vec::new(),
path: None,
read_only: false,
tag: None,
net_fd: None,
mac_address: None,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Arm64Registers {
pub x: [u64; 31],
pub sp: u64,
pub pc: u64,
pub pstate: u64,
pub fpcr: u64,
pub fpsr: u64,
pub v: [[u64; 2]; 32],
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VcpuSnapshot {
pub id: u32,
pub arch: CpuArch,
pub x86_regs: Option<Registers>,
pub arm64_regs: Option<Arm64Registers>,
pub extra_state: Vec<u8>,
}
impl VcpuSnapshot {
#[must_use]
pub const fn new_x86(id: u32, regs: Registers) -> Self {
Self {
id,
arch: CpuArch::X86_64,
x86_regs: Some(regs),
arm64_regs: None,
extra_state: Vec::new(),
}
}
#[must_use]
pub const fn new_arm64(id: u32, regs: Arm64Registers) -> Self {
Self {
id,
arch: CpuArch::Aarch64,
x86_regs: None,
arm64_regs: Some(regs),
extra_state: Vec::new(),
}
}
#[must_use]
pub fn is_placeholder(&self) -> bool {
match (&self.x86_regs, &self.arm64_regs) {
(Some(regs), _) => regs.rip == 0 && regs.rsp == 0 && regs.rflags == 0,
(_, Some(regs)) => regs.pc == 0 && regs.sp == 0 && regs.pstate == 0,
(None, None) => true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceSnapshot {
pub device_type: VirtioDeviceType,
pub name: String,
pub state: Vec<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryRegionSnapshot {
pub guest_addr: u64,
pub size: u64,
pub read_only: bool,
pub file_offset: u64,
}
#[derive(Debug, Clone)]
pub struct DirtyPageInfo {
pub guest_addr: u64,
pub size: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VmSnapshot {
pub version: u32,
pub arch: CpuArch,
pub vcpus: Vec<VcpuSnapshot>,
pub devices: Vec<DeviceSnapshot>,
pub memory_regions: Vec<MemoryRegionSnapshot>,
pub total_memory: u64,
pub compressed: bool,
pub compression: Option<String>,
pub parent_id: Option<String>,
}