Skip to content

ForceInjection/pgcacher

 
 

Repository files navigation

pgcacher

pgcacher 用于获取文件的 page cache 统计信息。使用 pgcacher 命令可以了解指定进程的 fd 在 page cache 中占用了多少缓存空间。使用 pgcacher 可以了解指定文件列表是否被缓存在 page cache 中,以及被缓存的空间大小。

相比 pcstat,pgcacher 修复了进程文件列表不正确的问题。以前仅通过 /proc/{pid}/maps 获取,现在改为同时从 /proc/{pid}/maps/proc/{pid}/fd 获取。pgcacher 支持更多参数,例如 top、worker、limit、depth、least-size、exclude-files 和 include-files。😁

此外,pgcacher 的代码更加健壮,并且支持并发参数,可以更快速地计算 page cache 中的缓存占用情况。

🚀 pgcacher 比 pcstat 性能更好,并且随着文件数量的增加,性能差距会越来越明显。在大多数场景下,可达到 pcstat 的 5 倍速度。

pkg/pcstats 中的部分代码改编自 pcstat 和 hcache。

使用说明

pgcacher <-json|-terse|-default> <-bname> file file file
    -limit 限制显示的文件数量,默认值:500
    -depth 设置扫描目录的深度,默认值:0
    -worker 并发 worker 数量,默认值:2
    -pid 显示指定 pid 打开的所有文件(同时读取 /proc/{pid}/fd 与 /proc/{pid}/maps)
    -container 要分析的容器 ID(>=12 位十六进制字符);通过 /proc/<pid>/cgroup 解析为宿主机 PID,无需 docker/nsenter
    -top 扫描所有进程打开的文件,显示在 page cache 中占用内存空间最多的前几个文件,默认值:false
    -least-size 忽略小于 leastSize 的文件,例如 '10MB''15GB'
    -exclude-files 通过通配符排除指定文件,例如 'a*c?d''*xiaorui*,rfyiamcool'
    -include-files 通过通配符仅包含指定文件,例如 'a*c?d''*xiaorui?cc,rfyiamcool'
    -statblockdev 纳入进程持有的块设备(/dev/sdX、/dev/nvmeXnY、loop*、dm-* 等)的 page cache;默认关闭。开启后会对每个 /dev/* fd 执行 stat + BLKGETSIZE64 ioctl 以获取真实大小,可能显著变慢
    -enhanced-ns 启用增强型命名空间切换以获得更好的容器支持
    -verbose 启用详细日志以调试命名空间操作
    -json 以 JSON 格式输出
    -terse 打印简洁的机器可解析输出
    -bname 在输出中使用 basename(file)(适用于长路径)
    -plain 返回不带框线字符的数据
    -unicode 返回带 unicode 框线字符的数据

容器环境支持

pgcacher 为容器化环境提供了一流的支持。当分析运行在容器内的进程时,您有以下几种选择:

1. 内置 -container 参数(推荐)

无需 dockernsenter 或包装脚本。pgcacher 通过扫描 /proc/<pid>/cgroup 将容器 ID 解析为宿主机 PID,并自动启用跨命名空间切换。

# 接受完整或截断的容器 ID(>=12 位十六进制字符),
# 支持 Docker、containerd、CRI-O、Podman 和 Kubernetes。
sudo pgcacher -container <container_id> -top -limit 10

2. 使用 nsenter

# 获取容器主进程在宿主机上的 PID
CONTAINER_PID=$(docker inspect --format '{{.State.Pid}}' <container_name>)

# 使用 nsenter 进入容器的 mount / pid 命名空间后运行 pgcacher;
# 进入容器 PID namespace 后,容器主进程对应 PID 为 1
sudo nsenter -t $CONTAINER_PID -m -p pgcacher -pid 1

3. 使用提供的脚本

# 赋予脚本可执行权限
chmod +x scripts/pgcacher-container.sh

# 通过容器名称运行
./scripts/pgcacher-container.sh -c <container_name>

# 通过容器 ID 运行
./scripts/pgcacher-container.sh -i <container_id>

# 通过指定 PID 和附加参数运行
./scripts/pgcacher-container.sh -p <pid> -a "-top -limit 10"

4. 增强型命名空间切换(实验性)

# 为已知的宿主机 PID 启用增强型命名空间切换
sudo pgcacher -pid <container_pid> -enhanced-ns -verbose

有关详细用法示例和故障排查,请参阅 docs/container-usage.md

安装

1. 下载预编译二进制(推荐)

每次 v* 标签会通过 GitHub Actions + goreleaser 自动发布 linux/amd64linux/arm64 两个架构的 tar.gz 包,并附带 checksums.txt

# 选择最新版本: https://github.com/ForceInjection/pgcacher/releases/latest
VERSION=v0.4.0
ARCH=$(uname -m)   # x86_64 或 aarch64 → 替换为 arm64
[ "$ARCH" = "aarch64" ] && ARCH=arm64

curl -LO https://github.com/ForceInjection/pgcacher/releases/download/${VERSION}/pgcacher_${VERSION}_Linux_${ARCH}.tar.gz
tar -xzf pgcacher_${VERSION}_Linux_${ARCH}.tar.gz
sudo install -m 0755 pgcacher /usr/local/bin/pgcacher
pgcacher -h

如需校验完整性,下载同一发布页面的 checksums.txt 并运行 sha256sum -c checksums.txt

2. 从源码构建

git clone https://github.com/ForceInjection/pgcacher.git
cd pgcacher
make build        # 交叉编译 linux/amd64
sudo cp pgcacher /usr/local/bin/
pgcacher -h

如需在 Linux 主机上直接编译可改用 go build .(需 Go 1.21+)。

示例

以下示例均在 Linux 服务器(CentOS 8.x,内核 4.18,Kubernetes + containerd 节点)上实际运行采集,非模拟数据。

准备测试文件

# 生成三个随机内容的文件并读入 page cache
mkdir -p demo
dd if=/dev/urandom of=demo/sample_256m bs=1M count=256
dd if=/dev/urandom of=demo/sample_128m bs=1M count=128
dd if=/dev/urandom of=demo/sample_64m  bs=1M count=64
cat demo/sample_256m demo/sample_128m demo/sample_64m > /dev/null

1. 文件模式(默认 ASCII 表格)

$ sudo pgcacher demo/sample_256m demo/sample_128m demo/sample_64m
+------------------+----------------+-------------+----------------+-------------+---------+
| Name             | Size           │ Pages       │ Cached Size    │ Cached Pages│ Percent │
|------------------+----------------+-------------+----------------+-------------+---------|
| demo/sample_256m | 256.000M       | 65536       | 254.500M       | 65152       | 99.414  |
| demo/sample_128m | 128.000M       | 32768       | 128.000M       | 32768       | 100.000 |
| demo/sample_64m  | 64.000M        | 16384       | 64.000M        | 16384       | 100.000 |
|------------------+----------------+-------------+----------------+-------------+---------|
│ Sum              │ 448.000M       │ 114688      │ 446.500M       │ 114304      │ 99.665  │
+------------------+----------------+-------------+----------------+-------------+---------+

注意:sample_256m 的缓存占比是 99.414%,并非精确的 100%。这是真实环境下内存压力导致少量页面被回收的正常现象。

2. 目录递归扫描(-depth)

$ sudo pgcacher -depth 2 -least-size 50MB demo/
+------------------+----------------+-------------+----------------+-------------+---------+
| Name             | Size           │ Pages       │ Cached Size    │ Cached Pages│ Percent │
|------------------+----------------+-------------+----------------+-------------+---------|
| demo/sample_256m | 256.000M       | 65536       | 254.500M       | 65152       | 99.414  |
| demo/sample_128m | 128.000M       | 32768       | 128.000M       | 32768       | 100.000 |
| demo/sample_64m  | 64.000M        | 16384       | 64.000M        | 16384       | 100.000 |
|------------------+----------------+-------------+----------------+-------------+---------|
│ Sum              │ 448.000M       │ 114688      │ 446.500M       │ 114304      │ 99.665  │
+------------------+----------------+-------------+----------------+-------------+---------+

3. 按 PID 查看进程打开的文件(-pid)

PID 1(systemd)是所有 Linux 主机都存在的进程,下例展示其动态链接库的缓存占比:

$ sudo pgcacher -pid 1 -least-size 100KB -limit 8
+-------------------------------+----------------+-------------+----------------+-------------+---------+
| Name                          | Size           │ Pages       │ Cached Size    │ Cached Pages│ Percent │
|-------------------------------+----------------+-------------+----------------+-------------+---------|
| /usr/lib64/libc-2.28.so       | 17.411M        | 4458        | 3.999M         | 1024        | 22.970  |
| /usr/lib64/libpthread-2.28.so | 2.653M         | 680         | 2.653M         | 680         | 100.000 |
| /usr/lib64/ld-2.28.so         | 1.401M         | 359         | 1.401M         | 359         | 100.000 |
| /usr/lib/systemd/systemd      | 1.557M         | 399         | 1.190M         | 305         | 76.441  |
| /usr/lib64/libmount.so.1.1.0  | 271.297K       | 68          | 271.297K       | 68          | 100.000 |
| /usr/lib64/libblkid.so.1.1.0  | 259.352K       | 65          | 259.352K       | 65          | 100.000 |
| /usr/lib64/liblzma.so.5.2.2   | 153.750K       | 39          | 153.750K       | 39          | 100.000 |
| /usr/lib64/libselinux.so.1    | 152.094K       | 39          | 77.996K        | 20          | 51.282  |
|-------------------------------+----------------+-------------+----------------+-------------+---------|
│ Sum                           │ 23.840M        │ 6107        │ 9.989M         │ 2560        │ 41.919  │
+-------------------------------+----------------+-------------+----------------+-------------+---------+

libc-2.28.so 仅有 22.97% 的页面驻留在缓存中,这与 glibc 通过 demand-paging 按需加载代码段的特性一致。

4. 容器模式(-container)

在 Kubernetes + containerd 节点上直接传入容器 ID,无需预先查询宿主机 PID:

$ sudo pgcacher -container 75c1192030e92a808202ba8423fe82cb660ccf977d92453298336d0e2b734389 -verbose /etc/hostname
2026/04/30 13:33:41 container 75c1192030e9...d2b734389 -> host pid 720114
2026/04/30 13:33:41 Successfully switched to mnt namespace of pid 720114
+---------------+----------------+-------------+----------------+-------------+---------+
| Name          | Size           │ Pages       │ Cached Size    │ Cached Pages│ Percent │
|---------------+----------------+-------------+----------------+-------------+---------|
| /etc/hostname | 7B             | 1           | 0B             | 0           | 0.000   |
|---------------+----------------+-------------+----------------+-------------+---------|
│ Sum           │ 7B             │ 1           │ 0B             │ 0           │ 0.000   │
+---------------+----------------+-------------+----------------+-------------+---------+

容器 ID 通过扫描 /proc/<pid>/cgroup 解析为宿主机 PID(720114),随后自动切换到容器的 mount namespace 读取容器视角下的 /etc/hostname

5. Unicode 框线输出(-unicode)

$ sudo pgcacher -unicode demo/sample_256m demo/sample_128m demo/sample_64m
┌──────────────────┬────────────────┬─────────────┬────────────────┬─────────────┬─────────┐
│ Name             │ Size           │ Pages       │ Cached Size    │ Cached Pages│ Percent │
├──────────────────┼────────────────┼─────────────┼────────────────┼─────────────┼─────────┤
│ demo/sample_256m │ 256.000M       │ 65536       │ 214.402M       │ 54887       │ 83.751  │
│ demo/sample_128m │ 128.000M       │ 32768       │ 128.000M       │ 32768       │ 100.000 │
│ demo/sample_64m  │ 64.000M        │ 16384       │ 64.000M        │ 16384       │ 100.000 │
├──────────────────┼────────────────┼─────────────┼────────────────┼─────────────┼─────────┤
│ Sum              │ 448.000M       │ 114688      │ 406.402M       │ 104039      │ 90.715  │
└──────────────────┴────────────────┴─────────────┴────────────────┴─────────────┴─────────┘

6. 机器可解析格式(-json / -terse)

$ sudo pgcacher -json demo/sample_256m demo/sample_128m demo/sample_64m
[{"filename":"demo/sample_256m","size":268435456,"timestamp":"2026-04-30T13:33:10.409+08:00","mtime":"2026-04-30T13:26:18.753+08:00","pages":65536,"cached":63591,"uncached":1945,"percent":97.032},{"filename":"demo/sample_128m","size":134217728,"timestamp":"2026-04-30T13:33:10.409+08:00","mtime":"2026-04-30T13:26:19.775+08:00","pages":32768,"cached":32768,"uncached":0,"percent":100},{"filename":"demo/sample_64m","size":67108864,"timestamp":"2026-04-30T13:33:10.416+08:00","mtime":"2026-04-30T13:26:20.288+08:00","pages":16384,"cached":16384,"uncached":0,"percent":100}]

$ sudo pgcacher -terse demo/sample_256m demo/sample_128m demo/sample_64m
name,size,timestamp,mtime,pages,cached,percent
demo/sample_256m,268435456,1777527190,1777526778,65536,61031,93.126
demo/sample_128m,134217728,1777527190,1777526779,32768,32768,100
demo/sample_64m,67108864,1777527190,1777526780,16384,16384,100

pgcacher 设计

下面两张示意图来自原作者博客,概述了整体数据流与 mincore 探测机制:

架构、并发模型、容器/块设备支持等实现细节请参阅 docs/design.md

致谢

@tobert 提供的 pcstat

About

pgcacher is used to get page cache stats for files on Linux( 分析 page cache 缓存了哪些文件,文件在 page cache 的缓存占比)。

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages

  • Go 83.9%
  • Shell 16.0%
  • Makefile 0.1%