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
26 use crate::os::MeminfoApi;
27 use crate::suspend_history::SuspendHistory;
28 use crate::time::BootTime;
29 use crate::zram::idle::calculate_idle_time;
30 use crate::zram::idle::set_zram_idle_time;
31 use crate::zram::SysfsZramApi;
32
33 /// Error from [ZramRecompression].
34 #[derive(Debug, thiserror::Error)]
35 pub enum Error {
36 /// recompress too frequently
37 #[error("recompress too frequently")]
38 BackoffTime,
39 /// failure on setting zram idle
40 #[error("calculate zram idle {0}")]
41 CalculateIdle(#[from] crate::zram::idle::CalculateError),
42 /// failure on setting zram idle
43 #[error("set zram idle {0}")]
44 MarkIdle(std::io::Error),
45 /// failure on writing to /sys/block/zram0/recompress
46 #[error("recompress: {0}")]
47 Recompress(std::io::Error),
48 }
49
50 type Result<T> = std::result::Result<T, Error>;
51
52 /// Current zram recompression setup status
53 #[derive(Debug, PartialEq)]
54 pub enum ZramRecompressionStatus {
55 /// Zram writeback is not supported by the kernel.
56 Unsupported,
57 /// Zram recompression is supported but not configured yet.
58 NotConfigured,
59 /// Zram recompression was already activated.
60 Activated,
61 }
62
63 /// Check zram recompression setup status by reading `/sys/block/zram0/recomp_algorithm`.
get_zram_recompression_status<Z: SysfsZramApi>() -> std::io::Result<ZramRecompressionStatus>64 pub fn get_zram_recompression_status<Z: SysfsZramApi>() -> std::io::Result<ZramRecompressionStatus>
65 {
66 match Z::read_recomp_algorithm() {
67 Ok(recomp_algorithm) => {
68 if recomp_algorithm.is_empty() {
69 Ok(ZramRecompressionStatus::NotConfigured)
70 } else {
71 Ok(ZramRecompressionStatus::Activated)
72 }
73 }
74 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
75 Ok(ZramRecompressionStatus::Unsupported)
76 }
77 Err(e) => Err(e),
78 }
79 }
80
81 /// The parameters for zram recompression.
82 pub struct Params {
83 /// The backoff time since last recompression.
84 pub backoff_duration: Duration,
85 /// The minimum idle duration to recompression. This is used for [calculate_idle_time].
86 pub min_idle: Duration,
87 /// The maximum idle duration to recompression. This is used for [calculate_idle_time].
88 pub max_idle: Duration,
89 /// Whether recompress huge and idle pages or not.
90 pub huge_idle: bool,
91 /// Whether recompress idle pages or not.
92 pub idle: bool,
93 /// Whether recompress huge pages or not.
94 pub huge: bool,
95 /// The minimum size in bytes of zram pages to be considered for recompression.
96 pub threshold_bytes: u64,
97 }
98
99 impl Default for Params {
default() -> Self100 fn default() -> Self {
101 Self {
102 // 30 minutes
103 backoff_duration: Duration::from_secs(1800),
104 // 2 hours
105 min_idle: Duration::from_secs(2 * 3600),
106 // 4 hours
107 max_idle: Duration::from_secs(4 * 3600),
108 huge_idle: true,
109 idle: true,
110 huge: true,
111 // 1 KiB
112 threshold_bytes: 1024,
113 }
114 }
115 }
116
117 enum Mode {
118 HugeIdle,
119 Idle,
120 Huge,
121 }
122
123 /// [ZramRecompression] manages zram recompression policies.
124 pub struct ZramRecompression {
125 last_recompress_at: Option<BootTime>,
126 }
127
128 impl ZramRecompression {
129 /// Creates a new [ZramRecompression].
new() -> Self130 pub fn new() -> Self {
131 Self { last_recompress_at: None }
132 }
133
134 /// Recompress idle or huge zram pages.
mark_and_recompress<Z: SysfsZramApi, M: MeminfoApi>( &mut self, params: &Params, suspend_history: &SuspendHistory, now: BootTime, ) -> Result<()>135 pub fn mark_and_recompress<Z: SysfsZramApi, M: MeminfoApi>(
136 &mut self,
137 params: &Params,
138 suspend_history: &SuspendHistory,
139 now: BootTime,
140 ) -> Result<()> {
141 if let Some(last_at) = self.last_recompress_at {
142 if now.saturating_duration_since(last_at) < params.backoff_duration {
143 return Err(Error::BackoffTime);
144 }
145 }
146
147 if params.huge_idle {
148 self.initiate_recompress::<Z, M>(params, Mode::HugeIdle, suspend_history, now)?;
149 }
150 if params.idle {
151 self.initiate_recompress::<Z, M>(params, Mode::Idle, suspend_history, now)?;
152 }
153 if params.huge {
154 self.initiate_recompress::<Z, M>(params, Mode::Huge, suspend_history, now)?;
155 }
156
157 Ok(())
158 }
159
initiate_recompress<Z: SysfsZramApi, M: MeminfoApi>( &mut self, params: &Params, mode: Mode, suspend_history: &SuspendHistory, now: BootTime, ) -> Result<()>160 fn initiate_recompress<Z: SysfsZramApi, M: MeminfoApi>(
161 &mut self,
162 params: &Params,
163 mode: Mode,
164 suspend_history: &SuspendHistory,
165 now: BootTime,
166 ) -> Result<()> {
167 match mode {
168 Mode::HugeIdle | Mode::Idle => {
169 let idle_age = calculate_idle_time::<M>(params.min_idle, params.max_idle)?;
170 // Adjust idle age by suspend duration.
171 let idle_age = idle_age.saturating_add(
172 suspend_history.calculate_total_suspend_duration(idle_age, now),
173 );
174 set_zram_idle_time::<Z>(idle_age).map_err(Error::MarkIdle)?;
175 }
176 Mode::Huge => {}
177 }
178
179 let mode = match mode {
180 Mode::HugeIdle => "huge_idle",
181 Mode::Idle => "idle",
182 Mode::Huge => "huge",
183 };
184
185 let trigger = if params.threshold_bytes > 0 {
186 format!("type={} threshold={}", mode, params.threshold_bytes)
187 } else {
188 format!("type={mode}")
189 };
190
191 Z::recompress(&trigger).map_err(Error::Recompress)?;
192
193 self.last_recompress_at = Some(now);
194
195 Ok(())
196 }
197 }
198
199 // This is to suppress clippy::new_without_default.
200 impl Default for ZramRecompression {
default() -> Self201 fn default() -> Self {
202 Self::new()
203 }
204 }
205