Skip to content

Commit fbb4111

Browse files
committed
Change to swapfc_chunk_size=20%
1 parent 50c6f55 commit fbb4111

5 files changed

Lines changed: 146 additions & 126 deletions

File tree

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,11 @@ makepkg -si
6262
## Usage
6363

6464
```bash
65-
# Check status (run as root for detailed zswap stats)
66-
sudo systemd-swap status
65+
# Check status (works without root, extra stats when root)
66+
systemd-swap status
6767

68-
# View available compression algorithms
69-
systemd-swap compression
68+
# Show help
69+
systemd-swap --help
7070

7171
# Restart after config changes
7272
sudo systemctl restart systemd-swap
@@ -135,7 +135,7 @@ zram_writeback_size=1G # Auto backing file size
135135
################################################################################
136136
# SwapFC - Dynamic swap files
137137
################################################################################
138-
swapfc_chunk_size=512M # Size of each swap file
138+
swapfc_chunk_size=512M # Size: 512M, 1G, 10% (of RAM)
139139
swapfc_max_count=32 # Maximum swap files
140140
swapfc_free_ram_perc=35 # Create when free RAM < this %
141141
swapfc_free_swap_perc=25 # Create more when free swap < this %

include/swap-default.conf

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ swap_mode=auto
2424
################################################################################
2525

2626
zswap_compressor=zstd # lzo lz4 zstd lzo-rle lz4hc
27-
zswap_max_pool_percent=45 # Max % of RAM for compressed pool
27+
zswap_max_pool_percent=50 # Max % of RAM for compressed pool
2828
zswap_zpool=zsmalloc # Memory allocator (zsmalloc better than zbud)
2929
zswap_shrinker_enabled=0 # Proactively move cold pages to swap
3030
zswap_accept_threshold=90 # Accept pages again after pool was full (%)
@@ -38,7 +38,7 @@ zswap_accept_threshold=90 # Accept pages again after pool was full (%)
3838
################################################################################
3939

4040
# zram_size supports: 1G, 512M, 50%, 100% (full RAM)
41-
zram_size=50% # Size of zram device
41+
zram_size=80% # Size of zram device
4242
zram_alg=zstd # Compression: lzo lz4 zstd lzo-rle lz4hc
4343
zram_prio=32767 # Swap priority (highest = used first)
4444

@@ -56,7 +56,8 @@ zram_writeback_size=1G # Size of auto-created backing file
5656
# Creates swap files on-demand as memory pressure increases
5757
################################################################################
5858

59-
swapfc_chunk_size=512M # Size of each swap file
59+
# swapfc_chunk_size supports: 1G, 512M, 10% (percentage of RAM)
60+
swapfc_chunk_size=20% # Size of each swap file
6061
swapfc_max_count=32 # Maximum number of swap files
6162
swapfc_min_count=1 # Minimum (1 for zswap backing)
6263
swapfc_free_ram_perc=35 # Create swap when free RAM < this %

src/main.rs

Lines changed: 51 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ enum Commands {
3232
Stop,
3333
/// Show swap status information
3434
Status,
35-
/// List available compression algorithms
36-
Compression,
3735
}
3836

3937
/// Swap strategy based on filesystem detection
@@ -49,11 +47,17 @@ enum SwapMode {
4947
fn main() {
5048
let cli = Cli::parse();
5149

52-
let result = match cli.command.unwrap_or(Commands::Status) {
53-
Commands::Start => start(),
54-
Commands::Stop => stop(false),
55-
Commands::Status => status(),
56-
Commands::Compression => compression(),
50+
let result = match cli.command {
51+
Some(Commands::Start) => start(),
52+
Some(Commands::Stop) => stop(false),
53+
Some(Commands::Status) => status(),
54+
None => {
55+
// No subcommand provided, show help
56+
use clap::CommandFactory;
57+
Cli::command().print_help().ok();
58+
println!();
59+
return;
60+
}
5761
};
5862

5963
if let Err(e) = result {
@@ -368,7 +372,7 @@ fn status() -> Result<(), Box<dyn std::error::Error>> {
368372
}
369373

370374
let swap_stats = get_mem_stats(&["MemTotal", "SwapTotal", "SwapFree"])?;
371-
let mem_total = swap_stats["MemTotal"];
375+
let _mem_total = swap_stats["MemTotal"];
372376
let swap_total = swap_stats["SwapTotal"];
373377
let swap_used = swap_total - swap_stats["SwapFree"];
374378

@@ -380,59 +384,50 @@ fn status() -> Result<(), Box<dyn std::error::Error>> {
380384
println!(" zpool: {}", zswap.zpool);
381385
println!(" max_pool_percent: {}%", zswap.max_pool_percent);
382386

383-
if is_root && (zswap.pool_size > 0 || zswap.stored_pages > 0) {
384-
let page_size = get_page_size();
385-
let stored_bytes = zswap.stored_pages * page_size;
386-
let ratio = zswap.compression_ratio(page_size);
387-
let pool_util = zswap.pool_utilization_percent(mem_total);
388-
389-
println!();
390-
println!(" === Pool Statistics (debugfs) ===");
391-
println!(" pool_size: {} ({:.1} MiB)", zswap.pool_size, zswap.pool_size as f64 / 1024.0 / 1024.0);
392-
println!(" stored_pages: {} ({:.1} MiB uncompressed)", zswap.stored_pages, stored_bytes as f64 / 1024.0 / 1024.0);
393-
println!(" pool_utilization: {}%", pool_util);
394-
println!(" compress_ratio: {:.0}%", ratio * 100.0);
395-
println!(" same_filled_pages: {}", zswap.same_filled_pages);
396-
println!();
397-
println!(" === Writeback Statistics ===");
398-
println!(" written_back_pages: {} ({:.1} MiB)",
399-
zswap.written_back_pages,
400-
(zswap.written_back_pages * page_size) as f64 / 1024.0 / 1024.0);
401-
println!(" pool_limit_hit: {}", zswap.pool_limit_hit);
402-
println!(" reject_reclaim_fail: {}", zswap.reject_reclaim_fail);
403-
404-
// Show effective swap usage
405-
if swap_used > 0 {
406-
let disk_used = swap_used.saturating_sub(stored_bytes);
407-
println!();
408-
println!(" === Effective Swap Usage ===");
409-
println!(" kernel_reported_used: {:.1} MiB", swap_used as f64 / 1024.0 / 1024.0);
410-
println!(" in_zswap_pool (RAM): {:.1} MiB", stored_bytes as f64 / 1024.0 / 1024.0);
411-
println!(" actual_disk_used: {:.1} MiB", disk_used as f64 / 1024.0 / 1024.0);
412-
let percent_in_ram = if swap_used > 0 {
413-
(stored_bytes as f64 / swap_used as f64) * 100.0
387+
// Try to get basic stats from /proc/meminfo (works without root!)
388+
if let Ok(usage) = systemd_swap::meminfo::get_effective_swap_usage() {
389+
if usage.zswap_active {
390+
let zswap_original = swap_used.saturating_sub(usage.swap_used_disk);
391+
let zswap_compressed = usage.zswap_pool_bytes;
392+
393+
let ratio = if zswap_original > 0 {
394+
(zswap_compressed as f64 / zswap_original as f64) * 100.0
414395
} else {
415396
0.0
416397
};
417-
println!(" swap_in_ram: {:.0}%", percent_in_ram);
418-
}
419-
} else if zswap.pool_size > 0 || zswap.stored_pages > 0 {
420-
// Non-root, basic info only
421-
let page_size = get_page_size();
422-
let stored_bytes = zswap.stored_pages * page_size;
423-
let ratio = if zswap.stored_pages > 0 {
424-
(zswap.pool_size as f64 / stored_bytes as f64) * 100.0
425-
} else {
426-
0.0
427-
};
428398

429-
println!(" pool_size: {} bytes", zswap.pool_size);
430-
println!(" stored_pages: {}", zswap.stored_pages);
431-
println!(" compress_ratio: {:.0}%", ratio);
399+
println!();
400+
println!(" === Pool Statistics ===");
401+
println!(" pool_size: {:.1} MiB (compressed)", zswap_compressed as f64 / 1024.0 / 1024.0);
402+
println!(" stored_data: {:.1} MiB (original)", zswap_original as f64 / 1024.0 / 1024.0);
403+
println!(" pool_utilization: {}%", usage.zswap_pool_percent);
404+
println!(" compress_ratio: {:.0}%", ratio);
405+
406+
// If running as root, show additional debugfs stats
407+
if is_root && (zswap.stored_pages > 0 || zswap.written_back_pages > 0) {
408+
let page_size = get_page_size();
409+
410+
println!();
411+
println!(" === Writeback Statistics (debugfs) ===");
412+
println!(" stored_pages: {}", zswap.stored_pages);
413+
println!(" same_filled_pages: {}", zswap.same_filled_pages);
414+
println!(" written_back_pages: {} ({:.1} MiB)",
415+
zswap.written_back_pages,
416+
(zswap.written_back_pages * page_size) as f64 / 1024.0 / 1024.0);
417+
println!(" pool_limit_hit: {}", zswap.pool_limit_hit);
418+
println!(" reject_reclaim_fail: {}", zswap.reject_reclaim_fail);
419+
}
432420

433-
if swap_used > 0 {
434-
let percent = (stored_bytes as f64 / swap_used as f64) * 100.0;
435-
println!(" zswap_store/swap_store: {}/{} ({:.0}%)", stored_bytes, swap_used, percent);
421+
// Show effective swap usage
422+
if swap_used > 0 {
423+
println!();
424+
println!(" === Effective Swap Usage ===");
425+
println!(" kernel_reported_used: {:.1} MiB", swap_used as f64 / 1024.0 / 1024.0);
426+
println!(" in_zswap_pool (RAM): {:.1} MiB", zswap_original as f64 / 1024.0 / 1024.0);
427+
println!(" actual_disk_used: {:.1} MiB", usage.swap_used_disk as f64 / 1024.0 / 1024.0);
428+
let percent_in_ram = (zswap_original as f64 / swap_used as f64) * 100.0;
429+
println!(" swap_in_ram: {:.0}%", percent_in_ram);
430+
}
436431
}
437432
}
438433
}
@@ -473,33 +468,3 @@ fn status() -> Result<(), Box<dyn std::error::Error>> {
473468
Ok(())
474469
}
475470

476-
/// Show available compression algorithms
477-
fn compression() -> Result<(), Box<dyn std::error::Error>> {
478-
let crypto = fs::read_to_string("/proc/crypto")?;
479-
480-
print!("Found loaded compression algorithms: ");
481-
482-
let mut first = true;
483-
let mut current_name = String::new();
484-
485-
for line in crypto.lines() {
486-
let line = line.trim();
487-
if let Some(name) = line.strip_prefix("name") {
488-
current_name = name.trim_start_matches(':').trim().to_string();
489-
} else if let Some(typ) = line.strip_prefix("type") {
490-
let current_type = typ.trim_start_matches(':').trim();
491-
492-
if current_type == "compression" && !current_name.is_empty() {
493-
if first {
494-
first = false;
495-
} else {
496-
print!(", ");
497-
}
498-
print!("{}", current_name);
499-
}
500-
}
501-
}
502-
503-
println!();
504-
Ok(())
505-
}

src/meminfo.rs

Lines changed: 71 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,21 @@ pub struct EffectiveSwapUsage {
175175
/// When zswap is active, the kernel reports swap usage based on allocated slots,
176176
/// but most of those pages may still be in zswap's RAM pool and not written to disk.
177177
/// This function calculates the actual disk pressure.
178+
///
179+
/// Uses /proc/meminfo (Zswap, Zswapped) for basic stats - works without root!
180+
/// Optionally uses debugfs for additional statistics when running as root.
178181
pub fn get_effective_swap_usage() -> Result<EffectiveSwapUsage> {
182+
// Try to get zswap stats from /proc/meminfo (available without root!)
183+
// These fields were added in kernel 5.x
184+
let zswap_fields = get_mem_stats_optional(&["Zswap", "Zswapped"]);
185+
let (zswap_compressed, zswap_original) = match zswap_fields {
186+
Ok(fields) => (
187+
fields.get("Zswap").copied().unwrap_or(0),
188+
fields.get("Zswapped").copied().unwrap_or(0),
189+
),
190+
Err(_) => (0, 0),
191+
};
192+
179193
let stats = get_mem_stats(&["MemTotal", "SwapTotal", "SwapFree"])?;
180194
let swap_total = stats["SwapTotal"];
181195
let swap_free = stats["SwapFree"];
@@ -186,45 +200,72 @@ pub fn get_effective_swap_usage() -> Result<EffectiveSwapUsage> {
186200
swap_total,
187201
swap_free,
188202
swap_used_kernel,
189-
zswap_pool_bytes: 0,
190-
swap_used_disk: swap_used_kernel, // Default: assume all on disk
203+
zswap_pool_bytes: zswap_compressed,
204+
swap_used_disk: swap_used_kernel.saturating_sub(zswap_original),
191205
zswap_pool_percent: 0,
192-
zswap_active: false,
206+
zswap_active: zswap_original > 0 || zswap_compressed > 0,
193207
};
194208

195-
// Check zswap status
196-
if let Some(zswap_stats) = get_zswap_stats() {
197-
let page_size = get_page_size();
198-
let stored_bytes = zswap_stats.stored_pages * page_size;
199-
200-
if stored_bytes > 0 {
201-
result.zswap_active = true;
202-
result.zswap_pool_bytes = zswap_stats.pool_total_size;
203-
204-
// Actual disk usage = kernel reported usage - pages in zswap pool
205-
// Pages in zswap pool are still in RAM, not written to disk
206-
result.swap_used_disk = swap_used_kernel.saturating_sub(stored_bytes);
207-
208-
// Calculate zswap pool utilization
209-
// Read max_pool_percent from sysfs
210-
let max_pool_percent: u64 = std::fs::read_to_string(
211-
"/sys/module/zswap/parameters/max_pool_percent"
212-
)
213-
.ok()
214-
.and_then(|s| s.trim().parse().ok())
215-
.unwrap_or(20);
216-
217-
let max_pool_size = mem_total * max_pool_percent / 100;
218-
if max_pool_size > 0 {
219-
result.zswap_pool_percent =
220-
((zswap_stats.pool_total_size * 100) / max_pool_size).min(100) as u8;
221-
}
209+
// Calculate pool utilization if zswap is active
210+
if result.zswap_active {
211+
let max_pool_percent: u64 = std::fs::read_to_string(
212+
"/sys/module/zswap/parameters/max_pool_percent"
213+
)
214+
.ok()
215+
.and_then(|s| s.trim().parse().ok())
216+
.unwrap_or(20);
217+
218+
let max_pool_size = mem_total * max_pool_percent / 100;
219+
if max_pool_size > 0 {
220+
result.zswap_pool_percent =
221+
((zswap_compressed * 100) / max_pool_size).min(100) as u8;
222222
}
223223
}
224224

225225
Ok(result)
226226
}
227227

228+
/// Read memory stats from /proc/meminfo, ignoring missing fields
229+
fn get_mem_stats_optional(fields: &[&str]) -> Result<HashMap<String, u64>> {
230+
let mut stats = HashMap::new();
231+
let mut remaining: HashSet<&str> = fields.iter().copied().collect();
232+
233+
let file = File::open("/proc/meminfo")?;
234+
let reader = BufReader::new(file);
235+
236+
for line in reader.lines() {
237+
let line = line?;
238+
239+
if let Some(colon_pos) = line.find(':') {
240+
let key = &line[..colon_pos];
241+
242+
if remaining.contains(key) {
243+
let value_part = line[colon_pos + 1..].trim();
244+
let parts: Vec<&str> = value_part.split_whitespace().collect();
245+
246+
let value = if parts.len() >= 2 && parts[1] == "kB" {
247+
parts[0].parse::<u64>().ok().map(|v| v * 1024)
248+
} else if !parts.is_empty() {
249+
parts[0].parse::<u64>().ok()
250+
} else {
251+
None
252+
};
253+
254+
if let Some(v) = value {
255+
stats.insert(key.to_string(), v);
256+
remaining.remove(key);
257+
258+
if remaining.is_empty() {
259+
break;
260+
}
261+
}
262+
}
263+
}
264+
}
265+
266+
Ok(stats)
267+
}
268+
228269
/// Get effective free swap percentage accounting for zswap
229270
///
230271
/// This returns a more accurate picture of swap pressure:

src/swapfc.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,22 @@ impl SwapFcConfig {
9090
}
9191
}
9292

93-
/// Parse size string like "512M" or "1G" to bytes
93+
/// Parse size string like "512M", "1G", or "10%" (percentage of RAM) to bytes
9494
fn parse_size(s: &str) -> Result<u64> {
9595
let s = s.trim();
96+
97+
// Check for percentage (e.g., "10%", "50%")
98+
if let Some(percent_str) = s.strip_suffix('%') {
99+
let percent: u64 = percent_str.parse().map_err(|_| SwapFcError::InvalidPath)?;
100+
if percent > 100 {
101+
return Err(SwapFcError::InvalidPath);
102+
}
103+
104+
// Get total RAM and calculate percentage
105+
let ram_size = crate::meminfo::get_ram_size().map_err(|_| SwapFcError::InvalidPath)?;
106+
return Ok(ram_size * percent / 100);
107+
}
108+
96109
let (num, suffix) = s.split_at(s.len().saturating_sub(1));
97110

98111
let multiplier = match suffix.to_uppercase().as_str() {
@@ -101,7 +114,7 @@ fn parse_size(s: &str) -> Result<u64> {
101114
"G" => 1024 * 1024 * 1024,
102115
"T" => 1024 * 1024 * 1024 * 1024,
103116
_ => {
104-
// No suffix, try parsing whole string
117+
// No suffix, try parsing whole string as bytes
105118
return s.parse().map_err(|_| SwapFcError::InvalidPath);
106119
}
107120
};

0 commit comments

Comments
 (0)