blob: 53740ff0b57cca12981e4f2572be2d3bbda9aa2b [file] [log] [blame]
// Copyright 2024, The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! This module provides policy to manage zram recompression feature.
//!
//! See this kernel document for the details of recompression.
//!
//! https://docs.kernel.org/admin-guide/blockdev/zram.html#recompression
#[cfg(test)]
mod tests;
use std::time::Duration;
use crate::os::MeminfoApi;
use crate::suspend_history::SuspendHistory;
use crate::time::BootTime;
use crate::zram::idle::calculate_idle_time;
use crate::zram::idle::set_zram_idle_time;
use crate::zram::SysfsZramApi;
/// Error from [ZramRecompression].
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// recompress too frequently
#[error("recompress too frequently")]
BackoffTime,
/// failure on setting zram idle
#[error("calculate zram idle {0}")]
CalculateIdle(#[from] crate::zram::idle::CalculateError),
/// failure on setting zram idle
#[error("set zram idle {0}")]
MarkIdle(std::io::Error),
/// failure on writing to /sys/block/zram0/recompress
#[error("recompress: {0}")]
Recompress(std::io::Error),
}
type Result<T> = std::result::Result<T, Error>;
/// Check whether zram recompression is activated by checking "/sys/block/zram0/recomp_algorithm".
pub fn is_zram_recompression_activated<Z: SysfsZramApi>() -> std::io::Result<bool> {
match Z::read_recomp_algorithm() {
Ok(recomp_algorithm) => Ok(!recomp_algorithm.is_empty()),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false),
Err(e) => Err(e),
}
}
/// The parameters for zram recompression.
pub struct Params {
/// The backoff time since last recompression.
pub backoff_duration: Duration,
/// The minimum idle duration to recompression. This is used for [calculate_idle_time].
pub min_idle: Duration,
/// The maximum idle duration to recompression. This is used for [calculate_idle_time].
pub max_idle: Duration,
/// Whether recompress huge and idle pages or not.
pub huge_idle: bool,
/// Whether recompress idle pages or not.
pub idle: bool,
/// Whether recompress huge pages or not.
pub huge: bool,
/// Maximum size in MiB to recompress.
pub max_mib: u64,
}
impl Default for Params {
fn default() -> Self {
Self {
// 30 minutes
backoff_duration: Duration::from_secs(1800),
// 2 hours
min_idle: Duration::from_secs(2 * 3600),
// 4 hours
max_idle: Duration::from_secs(4 * 3600),
huge_idle: true,
idle: true,
huge: true,
// 1 GiB
max_mib: 1024,
}
}
}
enum Mode {
HugeIdle,
Idle,
Huge,
}
/// [ZramRecompression] manages zram recompression policies.
pub struct ZramRecompression {
last_recompress_at: Option<BootTime>,
}
impl ZramRecompression {
/// Creates a new [ZramRecompression].
pub fn new() -> Self {
Self { last_recompress_at: None }
}
/// Recompress idle or huge zram pages.
pub fn mark_and_recompress<Z: SysfsZramApi, M: MeminfoApi>(
&mut self,
params: &Params,
suspend_history: &SuspendHistory,
now: BootTime,
) -> Result<()> {
if let Some(last_at) = self.last_recompress_at {
if now.saturating_duration_since(last_at) < params.backoff_duration {
return Err(Error::BackoffTime);
}
}
if params.huge_idle {
self.initiate_recompress::<Z, M>(params, Mode::HugeIdle, suspend_history, now)?;
}
if params.idle {
self.initiate_recompress::<Z, M>(params, Mode::Idle, suspend_history, now)?;
}
if params.huge {
self.initiate_recompress::<Z, M>(params, Mode::Huge, suspend_history, now)?;
}
Ok(())
}
fn initiate_recompress<Z: SysfsZramApi, M: MeminfoApi>(
&mut self,
params: &Params,
mode: Mode,
suspend_history: &SuspendHistory,
now: BootTime,
) -> Result<()> {
match mode {
Mode::HugeIdle | Mode::Idle => {
let idle_age = calculate_idle_time::<M>(params.min_idle, params.max_idle)?;
// Adjust idle age by suspend duration.
let idle_age = idle_age.saturating_add(
suspend_history.calculate_total_suspend_duration(idle_age, now),
);
set_zram_idle_time::<Z>(idle_age).map_err(Error::MarkIdle)?;
}
Mode::Huge => {}
}
let mode = match mode {
Mode::HugeIdle => "huge_idle",
Mode::Idle => "idle",
Mode::Huge => "huge",
};
let trigger = if params.max_mib > 0 {
format!("type={} threshold={}", mode, params.max_mib)
} else {
format!("type={mode}")
};
Z::recompress(&trigger).map_err(Error::Recompress)?;
self.last_recompress_at = Some(now);
Ok(())
}
}
// This is to suppress clippy::new_without_default.
impl Default for ZramRecompression {
fn default() -> Self {
Self::new()
}
}