#[cfg(feature = "std")]
use std::fs;
#[cfg(feature = "std")]
use std::path::Path;
pub const DEFAULT_BUFFER_SIZE: usize = 64 * 1024;
pub const ZRAM_BUFFER_SIZE: usize = 4 * 1024 * 1024;
pub const SMALL_BUFFER_SIZE: usize = 16 * 1024;
#[derive(Debug, Clone)]
pub struct ZramConfig {
pub available: bool,
pub gpu_enabled: bool,
pub algorithm: CompressionAlgorithm,
pub buffer_size: usize,
pub entropy_threshold: f32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CompressionAlgorithm {
#[default]
Lz4,
Zstd,
None,
}
impl ZramConfig {
#[cfg(feature = "std")]
pub fn detect() -> Self {
let available = is_zram_available_impl();
let gpu_enabled = is_gpu_zram_available();
let algorithm = detect_compression_algorithm();
Self::from_detected(available, gpu_enabled, algorithm)
}
fn from_detected(available: bool, gpu_enabled: bool, algorithm: CompressionAlgorithm) -> Self {
Self {
available,
gpu_enabled,
algorithm,
buffer_size: select_buffer_size(available, gpu_enabled),
entropy_threshold: 7.5,
}
}
#[cfg(not(feature = "std"))]
pub fn detect() -> Self {
Self::default()
}
}
impl Default for ZramConfig {
fn default() -> Self {
Self {
available: false,
gpu_enabled: false,
algorithm: CompressionAlgorithm::Lz4,
buffer_size: DEFAULT_BUFFER_SIZE,
entropy_threshold: 7.5,
}
}
}
#[cfg(feature = "std")]
pub fn is_available() -> bool {
is_zram_available_impl()
}
#[cfg(not(feature = "std"))]
pub fn is_available() -> bool {
false
}
#[cfg(feature = "std")]
pub fn is_trueno_ublk_mount(path: &Path) -> bool {
if let Ok(mounts) = fs::read_to_string("/proc/mounts") {
let path_str = path.to_string_lossy();
if check_mounts_for_ublk(&mounts, &path_str) {
return true;
}
}
let trueno_marker = Path::new("/run/trueno-ublk");
if trueno_marker.exists() {
let path_str = path.to_string_lossy();
if is_trueno_cache_path(&path_str) {
return true;
}
}
false
}
fn check_mounts_for_ublk(mounts_content: &str, path_str: &str) -> bool {
for line in mounts_content.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
let device = parts[0];
let mount_point = parts[1];
if path_str.starts_with(mount_point) && device.contains("ublk") {
return true;
}
}
}
false
}
fn is_trueno_cache_path(path_str: &str) -> bool {
path_str.contains("whisper-cache") || path_str.contains("trueno")
}
#[cfg(not(feature = "std"))]
pub fn is_trueno_ublk_mount(_path: &[u8]) -> bool {
false
}
#[cfg(feature = "std")]
pub fn optimal_buffer_size() -> usize {
if is_gpu_zram_available() {
ZRAM_BUFFER_SIZE } else {
DEFAULT_BUFFER_SIZE }
}
#[cfg(not(feature = "std"))]
pub fn optimal_buffer_size() -> usize {
DEFAULT_BUFFER_SIZE
}
#[cfg(feature = "std")]
pub fn optimal_buffer_size_for_path(path: &Path) -> usize {
if is_trueno_ublk_mount(path) {
ZRAM_BUFFER_SIZE
} else {
DEFAULT_BUFFER_SIZE
}
}
#[cfg(feature = "std")]
fn scan_ublk_gpu_devices() -> bool {
let Ok(entries) = fs::read_dir("/sys/class/ublk-control") else {
return false;
};
for entry in entries.flatten() {
let gpu_path = entry.path().join("gpu");
if gpu_path.exists() {
if let Ok(content) = fs::read_to_string(&gpu_path) {
if content.trim() == "1" {
return true;
}
}
}
}
false
}
#[cfg(feature = "std")]
fn is_gpu_zram_available() -> bool {
Path::new("/run/trueno-ublk/gpu").exists() || scan_ublk_gpu_devices()
}
#[cfg(feature = "std")]
fn is_zram_available_impl() -> bool {
if Path::new("/run/trueno-ublk").exists() {
return true;
}
if Path::new("/dev/zram0").exists() {
return true;
}
if let Ok(entries) = fs::read_dir("/sys/block") {
for entry in entries.flatten() {
if entry.file_name().to_string_lossy().starts_with("zram") {
return true;
}
}
}
false
}
fn select_buffer_size(available: bool, gpu_enabled: bool) -> usize {
if available {
if gpu_enabled {
ZRAM_BUFFER_SIZE } else {
DEFAULT_BUFFER_SIZE }
} else {
DEFAULT_BUFFER_SIZE
}
}
fn detect_algorithm_from_content(
trueno_algo: Option<&str>,
sysfs_algo: Option<&str>,
) -> CompressionAlgorithm {
if let Some(algo) = trueno_algo {
let parsed = parse_algorithm_name(algo.trim());
if parsed != CompressionAlgorithm::Lz4 || algo.trim().to_lowercase() == "lz4" {
return parsed;
}
}
if let Some(algo) = sysfs_algo {
return parse_comp_algorithm_sysfs(algo);
}
CompressionAlgorithm::Lz4
}
#[cfg(feature = "std")]
fn detect_compression_algorithm() -> CompressionAlgorithm {
let trueno = fs::read_to_string("/run/trueno-ublk/algorithm").ok();
let sysfs = fs::read_to_string("/sys/block/zram0/comp_algorithm").ok();
detect_algorithm_from_content(trueno.as_deref(), sysfs.as_deref())
}
fn parse_algorithm_name(name: &str) -> CompressionAlgorithm {
match name.to_lowercase().as_str() {
"zstd" => CompressionAlgorithm::Zstd,
"none" => CompressionAlgorithm::None,
_ => CompressionAlgorithm::Lz4,
}
}
fn parse_comp_algorithm_sysfs(content: &str) -> CompressionAlgorithm {
for part in content.split_whitespace() {
if part.starts_with('[') && part.ends_with(']') {
let current = &part[1..part.len() - 1];
return parse_algorithm_name(current);
}
}
CompressionAlgorithm::Lz4
}
pub fn estimate_compression_ratio(data_type: DataType) -> f32 {
match data_type {
DataType::ModelWeightsFp32 => 1.7, DataType::ModelWeightsInt8 => 1.1, DataType::KvCache => 2.5, DataType::PcmAudio => 3.0, DataType::MelSpectrogram => 3.5, DataType::CompressedAudio => 1.0, DataType::OutputText => 4.5, }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DataType {
ModelWeightsFp32,
ModelWeightsInt8,
KvCache,
PcmAudio,
MelSpectrogram,
CompressedAudio,
OutputText,
}
pub fn estimate_memory_savings(
model_size_mb: usize,
kv_cache_mb: usize,
buffer_mb: usize,
quantized: bool,
) -> MemorySavings {
let model_ratio = if quantized {
estimate_compression_ratio(DataType::ModelWeightsInt8)
} else {
estimate_compression_ratio(DataType::ModelWeightsFp32)
};
let kv_ratio = estimate_compression_ratio(DataType::KvCache);
let buffer_ratio = estimate_compression_ratio(DataType::PcmAudio);
let original_total = model_size_mb + kv_cache_mb + buffer_mb;
let compressed_model = (model_size_mb as f32 / model_ratio) as usize;
let compressed_kv = (kv_cache_mb as f32 / kv_ratio) as usize;
let compressed_buffer = (buffer_mb as f32 / buffer_ratio) as usize;
let compressed_total = compressed_model + compressed_kv + compressed_buffer;
MemorySavings {
original_mb: original_total,
compressed_mb: compressed_total,
savings_percent: ((1.0 - (compressed_total as f32 / original_total as f32)) * 100.0)
as usize,
}
}
#[derive(Debug, Clone)]
pub struct MemorySavings {
pub original_mb: usize,
pub compressed_mb: usize,
pub savings_percent: usize,
}
#[cfg(test)]
#[path = "zram_tests.rs"]
mod tests;