1 // Copyright 2024, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! This module provides policy to manage zram recompression feature.
16 //!
17 //! See this kernel document for the details of recompression.
18 //!
19 //! https://docs.kernel.org/admin-guide/blockdev/zram.html#recompression
20
21 #[cfg(test)]
22 mod tests;
23
24 use std::time::Duration;
25 use std::time::Instant;
26
27 use crate::os::MeminfoApi;
28 use crate::zram::idle::calculate_idle_time;
29 use crate::zram::idle::set_zram_idle_time;
30 use crate::zram::SysfsZramApi;
31
32 /// Error from [ZramRecompression].
33 #[derive(Debug, thiserror::Error)]
34 pub enum Error {
35 /// recompress too frequently
36 #[error("recompress too frequently")]
37 BackoffTime,
38 /// failure on setting zram idle
39 #[error("calculate zram idle {0}")]
40 CalculateIdle(#[from] crate::zram::idle::CalculateError),
41 /// failure on setting zram idle
42 #[error("set zram idle {0}")]
43 MarkIdle(std::io::Error),
44 /// failure on writing to /sys/block/zram0/recompress
45 #[error("recompress: {0}")]
46 Recompress(std::io::Error),
47 }
48
49 type Result<T> = std::result::Result<T, Error>;
50
51 /// Check whether zram recompression is activated by checking "/sys/block/zram0/recomp_algorithm".
is_zram_recompression_activated<Z: SysfsZramApi>() -> std::io::Result<bool>52 pub fn is_zram_recompression_activated<Z: SysfsZramApi>() -> std::io::Result<bool> {
53 match Z::read_recomp_algorithm() {
54 Ok(recomp_algorithm) => Ok(!recomp_algorithm.is_empty()),
55 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false),
56 Err(e) => Err(e),
57 }
58 }
59
60 /// The parameters for zram recompression.
61 pub struct Params {
62 /// The backoff time since last recompression.
63 pub backoff_duration: Duration,
64 /// The minimum idle duration to recompression. This is used for [calculate_idle_time].
65 pub min_idle: Duration,
66 /// The maximum idle duration to recompression. This is used for [calculate_idle_time].
67 pub max_idle: Duration,
68 /// Whether recompress huge and idle pages or not.
69 pub huge_idle: bool,
70 /// Whether recompress idle pages or not.
71 pub idle: bool,
72 /// Whether recompress huge pages or not.
73 pub huge: bool,
74 /// Maximum size in MiB to recompress.
75 pub max_mib: u64,
76 }
77
78 impl Default for Params {
default() -> Self79 fn default() -> Self {
80 Self {
81 // 30 minutes
82 backoff_duration: Duration::from_secs(1800),
83 // 2 hours
84 min_idle: Duration::from_secs(2 * 3600),
85 // 4 hours
86 max_idle: Duration::from_secs(4 * 3600),
87 huge_idle: true,
88 idle: true,
89 huge: true,
90 // 1 GiB
91 max_mib: 1024,
92 }
93 }
94 }
95
96 enum Mode {
97 HugeIdle,
98 Idle,
99 Huge,
100 }
101
102 /// [ZramRecompression] manages zram recompression policies.
103 pub struct ZramRecompression {
104 last_recompress_at: Option<Instant>,
105 }
106
107 impl ZramRecompression {
108 /// Creates a new [ZramRecompression].
new() -> Self109 pub fn new() -> Self {
110 Self { last_recompress_at: None }
111 }
112
113 /// Recompress idle or huge zram pages.
mark_and_recompress<Z: SysfsZramApi, M: MeminfoApi>( &mut self, params: &Params, now: Instant, ) -> Result<()>114 pub fn mark_and_recompress<Z: SysfsZramApi, M: MeminfoApi>(
115 &mut self,
116 params: &Params,
117 now: Instant,
118 ) -> Result<()> {
119 if let Some(last_at) = self.last_recompress_at {
120 if now - last_at < params.backoff_duration {
121 return Err(Error::BackoffTime);
122 }
123 }
124
125 if params.huge_idle {
126 self.initiate_recompress::<Z, M>(params, Mode::HugeIdle, now)?;
127 }
128 if params.idle {
129 self.initiate_recompress::<Z, M>(params, Mode::Idle, now)?;
130 }
131 if params.huge {
132 self.initiate_recompress::<Z, M>(params, Mode::Huge, now)?;
133 }
134
135 Ok(())
136 }
137
initiate_recompress<Z: SysfsZramApi, M: MeminfoApi>( &mut self, params: &Params, mode: Mode, now: Instant, ) -> Result<()>138 fn initiate_recompress<Z: SysfsZramApi, M: MeminfoApi>(
139 &mut self,
140 params: &Params,
141 mode: Mode,
142 now: Instant,
143 ) -> Result<()> {
144 match mode {
145 Mode::HugeIdle | Mode::Idle => {
146 let idle_age = calculate_idle_time::<M>(params.min_idle, params.max_idle)?;
147 // TODO: adjust the idle_age by suspend duration.
148 set_zram_idle_time::<Z>(idle_age).map_err(Error::MarkIdle)?;
149 }
150 Mode::Huge => {}
151 }
152
153 let mode = match mode {
154 Mode::HugeIdle => "huge_idle",
155 Mode::Idle => "idle",
156 Mode::Huge => "huge",
157 };
158
159 let trigger = if params.max_mib > 0 {
160 format!("type={} threshold={}", mode, params.max_mib)
161 } else {
162 format!("type={mode}")
163 };
164
165 Z::recompress(&trigger).map_err(Error::Recompress)?;
166
167 self.last_recompress_at = Some(now);
168
169 Ok(())
170 }
171 }
172
173 // This is to suppress clippy::new_without_default.
174 impl Default for ZramRecompression {
default() -> Self175 fn default() -> Self {
176 Self::new()
177 }
178 }
179