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