Skip to content

Commit

Permalink
compression-algorithm=algo(param,param) recompalgo(param,param) (glob…
Browse files Browse the repository at this point in the history
…al,recomp,params)

Ref: systemd#178 (comment)
  • Loading branch information
nabijaczleweli committed Nov 20, 2024
1 parent a6df332 commit fdaaca0
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 19 deletions.
10 changes: 7 additions & 3 deletions man/zram-generator.conf.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,14 @@ Devices with the final size of *0* will be discarded.

Specifies the algorithm used to compress the zram device.

This takes a literal string, representing the algorithm to use.<br />
Consult */sys/block/zram0/comp_algorithm* for a list of currently loaded compression algorithms, but note that additional ones may be loaded on demand.
This takes a whitespace-separated list string, representing the algorithms to use, and parameters in parenteses.<br />
Consult */sys/block/zram0/comp_algorithm* (and *.../recomp_algorithm*) for a list of currently loaded compression algorithms, but note that additional ones may be loaded on demand.

If unset, none will be configured and the kernel's default will be used.
If unset, none will be configured and the kernel's default will be used.<br />
If more than one is given, and recompression is enabled in the kernel, subsequent ones will be set as the recompression algorithms, with decreasing priority.

If a compression algorithm is suffixed with a parenthesised comma-separated list of parameters, those are given to `.../algorithm_params` (and `.../recompress`).
A parenthesised parameter list *without* a compression algorithm is set as the global recompression parameters.

* `writeback-device`=

Expand Down
61 changes: 57 additions & 4 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub struct Device {

/// Default: `DEFAULT_ZRAM_SIZE`
pub zram_size: Option<(String, fasteval::ExpressionI, fasteval::Slab)>,
pub compression_algorithm: Option<String>,
pub compression_algorithms: Algorithms,
pub writeback_dev: Option<PathBuf>,
pub disksize: u64,

Expand Down Expand Up @@ -50,7 +50,7 @@ impl Device {
name,
host_memory_limit_mb: None,
zram_size: None,
compression_algorithm: None,
compression_algorithms: Default::default(),
writeback_dev: None,
disksize: 0,
zram_resident_limit: None,
Expand Down Expand Up @@ -165,7 +165,7 @@ impl fmt::Display for Device {
.as_ref()
.map(|zs| &zs.0[..])
.unwrap_or(DEFAULT_RESIDENT_LIMIT),
self.compression_algorithm.as_deref().unwrap_or("<default>"),
self.compression_algorithms,
self.writeback_dev.as_deref().unwrap_or_else(|| Path::new("<none>")).display(),
self.options
)?;
Expand Down Expand Up @@ -196,6 +196,35 @@ impl fmt::Display for OptMB {
}
}

#[derive(Default, Debug, PartialEq, Eq)]
pub struct Algorithms {
pub compression_algorithms: Vec<(String, String)>, // algorithm, params; first one is real compression, later ones are recompression
pub recompression_global: String, // params
}
impl fmt::Display for Algorithms {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.compression_algorithms[..] {
[] => f.write_str("<default>")?,
[(first, firstparams), more @ ..] => {
f.write_str(first)?;
if !firstparams.is_empty() {
write!(f, " ({})", firstparams)?;
}
for (algo, params) in more {
write!(f, " then {}", algo)?;
if !params.is_empty() {
write!(f, " ({})", params)?;
}
}
}
}
if !self.recompression_global.is_empty() {
write!(f, "(global recompress: {})", self.recompression_global)?;
}
Ok(())
}
}

struct RamNs(f64);
impl fasteval::EvalNamespace for RamNs {
fn lookup(&mut self, name: &str, args: Vec<f64>, _: &mut String) -> Option<f64> {
Expand Down Expand Up @@ -360,6 +389,21 @@ fn parse_size_expr(
))
}

fn parse_compression_algorithm_params(whole: &str) -> (String, String) {
if let Some(paren) = whole.find('(') {
let (algo, mut params) = whole.split_at(paren);
dbg!((algo, params));
params = &params[1..];
if params.ends_with(')') {
params = &params[..params.len() - 1];
}
dbg!((algo, params));
return (algo.to_string(), params.replace(',', " "));
} else {
return (whole.to_string(), String::new());
}
}

fn parse_line(dev: &mut Device, key: &str, value: &str) -> Result<()> {
match key {
"host-memory-limit" | "memory-limit" => {
Expand All @@ -376,7 +420,16 @@ fn parse_line(dev: &mut Device, key: &str, value: &str) -> Result<()> {
}

"compression-algorithm" => {
dev.compression_algorithm = Some(value.to_string());
dev.compression_algorithms = value.split_whitespace().fold(Default::default(), |mut algos, s| {
let (algo, params) = parse_compression_algorithm_params(s);
dbg!((&algo, &params));
if algo.is_empty() {
algos.recompression_global = params;
} else {
algos.compression_algorithms.push((algo, params));
}
algos
});
}

"writeback-device" => {
Expand Down
2 changes: 1 addition & 1 deletion src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ pub fn run_generator(devices: &[Device], output_directory: &Path, fake_mode: boo

let compressors: BTreeSet<_> = devices
.iter()
.flat_map(|device| device.compression_algorithm.as_deref())
.flat_map(|device| device.compression_algorithms.compression_algorithms.iter().map(|(a, _)| a.as_ref()))
.collect();

if !compressors.is_empty() {
Expand Down
41 changes: 35 additions & 6 deletions src/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,49 @@ pub fn run_device_setup(device: Option<Device>, device_name: &str) -> Result<()>

let device_sysfs_path = Path::new("/sys/block").join(device_name);

if let Some(ref compression_algorithm) = device.compression_algorithm {
let comp_algorithm_path = device_sysfs_path.join("comp_algorithm");
match fs::write(&comp_algorithm_path, compression_algorithm) {
Ok(_) => {}
for (prio, (algo, params)) in device.compression_algorithms.compression_algorithms.iter().enumerate() {
let params = if params.is_empty() { None } else { Some(params) };
let (path, data, add_pathdata) = if prio == 0 {
(device_sysfs_path.join("comp_algorithm"), algo,
params.as_ref().map(|p| (device_sysfs_path.join("algorithm_params"), format!("algo={} {}", algo, p))))
} else {
(
device_sysfs_path.join("recomp_algorithm"),
&format!("algo={} priority={}", algo, prio),
params.as_ref().map(|p| (device_sysfs_path.join("recompress"), format!("{} priority={}", p, prio))),
)
};

match fs::write(&path, data) {
Ok(_) => {
if let Some((add_path, add_data)) = add_pathdata {
match fs::write(add_path, add_data) {
Ok(_) => {}
Err(err) => {
warn!(
"Warning: algorithm {:?} supplemental data {:?} not written: {}",
algo, data, err,
);
}
}
}
}
Err(err) if err.kind() == ErrorKind::InvalidInput => {
warn!(
"Warning: algorithm {:?} not recognised; consult {} for a list of available ones",
compression_algorithm, comp_algorithm_path.display(),
algo, path.display(),
);
}
Err(err) if err.kind() == ErrorKind::PermissionDenied && prio != 0 => {
warn!(
"Warning: recompression algorithm {:?} requested but recompression not available ({} doesn't exist)",
algo, path.display(),
);
}
err @ Err(_) => err.with_context(|| {
format!(
"Failed to configure compression algorithm into {}",
comp_algorithm_path.display()
path.display()
)
})?,
}
Expand Down
2 changes: 1 addition & 1 deletion tests/09-zram-size/etc/systemd/zram-generator.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[zram0]
compression-algorithm = zstd
compression-algorithm = zstd(dictionary=/etc/gaming,level=9) (recompargs)
host-memory-limit = 2050
zram-size = min(0.75 * ram, 6000)
6 changes: 3 additions & 3 deletions tests/test_cases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ fn test_02_zstd() {
assert!(d.is_swap());
assert_eq!(d.host_memory_limit_mb, Some(2050));
assert_eq!(d.zram_size.as_ref().map(z_s_name), Some("ram * 0.75"));
assert_eq!(d.compression_algorithm.as_ref().unwrap(), "zstd");
assert_eq!(d.compression_algorithms, config::Algorithms { compression_algorithms: vec![("zstd".into(), "".into())], ..Default::default() });
assert_eq!(d.options, "discard");
}

Expand Down Expand Up @@ -259,7 +259,7 @@ fn test_09_zram_size() {
d.zram_size.as_ref().map(z_s_name),
Some("min(0.75 * ram, 6000)")
);
assert_eq!(d.compression_algorithm.as_ref().unwrap(), "zstd");
assert_eq!(d.compression_algorithms, config::Algorithms { compression_algorithms: vec![("zstd".into(), "dictionary=/etc/gaming level=9".into())], recompression_global: "recompargs".into() });
}

#[test]
Expand All @@ -283,7 +283,7 @@ fn test_10_example() {
d.zram_size.as_ref().map(z_s_name),
Some("min(ram / 10, 2048)")
);
assert_eq!(d.compression_algorithm.as_deref(), Some("lzo-rle"));
assert_eq!(d.compression_algorithms, config::Algorithms { compression_algorithms: vec![("lzo-rle".into(), "".into()), ("zstd".into(), "level=3".into())], ..Default::default() });
assert_eq!(d.options, "");
}
"zram1" => {
Expand Down
8 changes: 7 additions & 1 deletion zram-generator.conf.example
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,13 @@ zram-resident-limit = 0

# The compression algorithm to use for the zram device,
# or leave unspecified to keep the kernel default.
compression-algorithm = lzo-rle
#
# Subsequent algorithms are used for recompression.
# Comma-separated parameters may be specified in parentheses.
#
# Parameters without a compression algorithm are set as
# global recompression parameters.
compression-algorithm = lzo-rle zstd(level=3) (type=idle)

# By default, file systems and swap areas are trimmed on-the-go
# by setting "discard".
Expand Down

0 comments on commit fdaaca0

Please sign in to comment.