• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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