pagers-core 0.1.6

Core library for page cache diagnostics and control on Linux and macOS
Documentation

pagers

CI crates.io codecov

Portable file system page cache diagnostics and control for Linux and macOS.

pagers queries, touches, evicts, and locks the page cache using cachestat(2), mincore(2), posix_fadvise(2), and mlock(2). When stdout is a terminal it renders a live TUI with per-file residency maps. When piped, it emits plain text, key=value pairs, or JSON.

Install

From crates.io:

cargo install pagers

From source:

cargo install --path crates/pagers-cli

With Nix:

nix profile install github:LilDojd/pagers

..or from FlakeHub:

nix profile install "https://flakehub.com/f/LilDojd/pagers/*"

Pre-built binaries for each release are available on the GitHub releases page.

Docker

docker pull ghcr.io/lildojd/pagers:latest
docker run --rm -v /data:/data ghcr.io/lildojd/pagers query /data

MSRV: 1.88.0

Usage

# How much of /var/db is cached?
pagers query /var/db

# Load a database file into cache before starting your app
pagers touch /var/lib/mysql/ibdata1

# Evict log files to free memory for active data
pagers evict /var/log

# Lock critical files in RAM so they are never paged out
pagers lock -d /var/lib/redis/dump.rdb

# Lock files and the entire current address space
pagers lockall -d /data

# Machine-readable output
pagers query -o json /data | jq .
pagers query -o kv /data | grep TotalResidentPercent

# Process a list of paths from find(1)
find /srv -name '*.db' -print0 | pagers query -b - -0

# Only consider .dat files under 500M, in the first 1G of each file
pagers query -I '*.dat' -m 500M -p 0..1G /data

Subcommands

Command Description
query Show which pages of a file are in the page cache
touch Load pages into the page cache
evict Drop pages from the page cache
lock Touch + mlock(2) pages in physical memory
lockall Lock + mlockall(MCL_CURRENT)

Options

All subcommands accept these flags:

PATHS              Files or directories to process

-f                 Follow symbolic links
-F                 Stay on the same filesystem
-H                 Count hardlinked copies separately
-m, --max-file-size SIZE
                   Skip files larger than SIZE (e.g. 4k, 100M, 1.5G)
-p, --range RANGE  Byte range to operate on (e.g. 10K-20G, 100M..500M, 0,1G)
-i, --ignore GLOB  Ignore files matching pattern (repeatable)
-I, --filter GLOB  Only process files matching pattern (repeatable)
-b, --batch FILE   Read paths from FILE (- for stdin)
-0                 NUL-delimited paths in batch mode
-o, --output FMT   Output format: human (default), kv, json
-v                 Increase verbosity (repeatable)
-q                 Quiet (no output)

lock and lockall also accept:

-d, --daemon       Run as a daemon (block until signal)
--wait             Wait until all pages are locked (requires -d)
-P, --pidfile PATH Write PID to file

Size and range syntax

Sizes accept decimal and binary units: 4k, 100M, 1.5G, 2KiB, 8MiB. Scientific notation (1e2K) and fractional values (1.5G) work too. Ranges can be written as 10K-20G, 10K..20G, or 10K,20G. Open-ended ranges like -20G (from start) and 10K- (to end) are supported.

How it works

query uses the cachestat(2) syscall on Linux 6.5+ for single-syscall cache stats per file, falling back to mincore(2) on older kernels and on macOS. Returns per-file page residency maps and aggregate statistics.

touch issues posix_fadvise(POSIX_FADV_SEQUENTIAL | POSIX_FADV_WILLNEED) to kick off kernel readahead, then walks every page with volatile reads to guarantee residency. Pages already loaded by the kernel are instant cache hits; the rest trigger demand faults.

evict calls posix_fadvise(POSIX_FADV_DONTNEED) on Linux and msync(MS_INVALIDATE) on macOS to advise the kernel to drop cached pages.

lock touches pages into cache and then calls mlock(2) to wire them into physical memory. lockall additionally calls mlockall(MCL_CURRENT) after locking individual files.

Files are traversed in parallel using rayon. Memory-mapped I/O is handled by memmap2. The live TUI is built with ratatui.

Use cases

  • Database warm-up. pagers touch /var/lib/postgresql/data before starting a service to avoid cold-start I/O.
  • Deployment pre-warming. Touch shared libraries or application binaries after deploy so the first request does not pay the page-fault penalty.
  • Memory reclamation. pagers evict /var/log to return pages used by rotated logs back to the free pool.
  • Pinning critical data. pagers lock -d /var/lib/redis/dump.rdb keeps a dataset in RAM for the lifetime of the daemon process.
  • Cache auditing. pagers query -o json /data for monitoring scripts that track page cache residency over time.
  • CI/benchmarking. Evict file caches between benchmark runs for repeatable cold-start measurements.
  • Batch processing. Pipe paths from find(1) or a manifest file via -b - to operate on arbitrary file sets.

Comparison with vmtouch

vmtouch pagers
Language C Rust
Platforms Linux, FreeBSD, Solaris, macOS, HP-UX, OpenBSD Linux, macOS*
Cache query mincore(2) cachestat(2) on Linux 6.5+, mincore(2) fallback
Live TUI Sort of Yes
Daemon mode -d (requires -l/-L) -d for lock and lockall
Parallel traversal No Yes (rayon)
Range operations -p page ranges -p byte ranges with unit suffixes

Performance

macOS (M1)

Query and evict perform on par with vmtouch. Touch is 2–6x faster thanks to parallel traversal:

Benchmark vmtouch pagers Speedup
Query 10 GiB file 178 ms 178 ms 1.0x
Evict 10 GiB file 207 ms 208 ms 1.0x
Touch 10 GiB file 14.0 s 5.1 s 2.7x
Touch 1000 × 1 MiB files 1.51 s 239 ms 6.3x
Evict 1000 × 1 MiB files 49.7 ms 18.9 ms 2.6x

Linux (x86_64, kernel 6.19)

Query is 2–10x faster thanks to cachestat(2). Touch is ~2x faster on directory trees via parallel traversal:

Benchmark vmtouch pagers Speedup
Query 10 GiB cached 42 ms 18 ms 2.3x
Query 10 GiB uncached 16 ms 1.5 ms 10.6x
Evict 10 GiB file 1.15 s 1.16 s 1.0x
Touch 10 GiB file 1.16 s 1.19 s 1.0x
Touch 1000 × 1 MiB files 173 ms 90 ms 1.9x

See also

  • vmtouch — the og page cache control tool
  • cachestat(2) — Linux 6.5+ syscall for page cache statistics
  • mincore(2) — determine whether pages are resident in memory
  • posix_fadvise(2) — predeclare an access pattern for file data
  • mlock(2) — lock pages in memory