这是一个面向 openEuler etmem 的最小穿刺验证仓库,用来在上真实 VM / OpenClaw 之前,先回答两个问题:
- 当前服务器的 etmem 扫描链路是否可用,能否观察某个进程的冷热内存分布。
- 当前服务器的 etmem
slide换出链路是否可用,能否把冷匿名页换到 swap,从而降低进程 resident memory。
仓库包含四个入口:
etmem-cslide-hotness-demo:真正走etmemd + cslide + etmem engine showtaskpages/showhostpages,用于用 etmem 分析 VM/QEMU 实例的冷热温。etmem-slide-monitor:没有 AEP/SCM 时使用slide,自动生成 etmem 配置、启动 etmemd、监控目标 PID 的 RSS/Swap,并可输出冷热占比诊断。etmem-swap-demo:启动etmemd和slide工程,把合成靶进程的冷匿名页换出,用VmRSS/VmSwap/MemAvailable验证效果。etmem-scan-demo:底层 fallback,只读/proc/<pid>/idle_pages,不启动etmemd,用于验证 etmem_scan 内核接口。
先澄清两个容易混淆的词:
sample/samples是“采样点/采样次数”。例如--samples 5或SCAN_SAMPLES=5表示 warmup 之后输出 5 次冷热扫描结果。- scan-only demo 不需要
/etc/etmem配置,也不调用etmem命令,它直接读 etmem 的内核接口/proc/<pid>/idle_pages,只能算底层扫描 fallback。 - 要“用 etmem 分析冷热温”,请优先使用
etmem-cslide-hotness-demo。它会生成/etc/etmem配置,启动etmemd,并调用etmem engine showtaskpages/showhostpages。
官方背景参考:
- openEuler etmem 用户指南:https://docs.openeuler.org/zh/docs/22.03_LTS_SP4/server/memory_storage/etmem/etmem_user_guide.html
- etmem 仓库:https://gitee.com/openeuler/etmem
适合先做本机穿刺验证:
- 验证 openEuler 内核是否提供
etmem_scan/etmem_swap - 验证 etmem 用户态工具
etmem/etmemd是否能正常工作 - 在不触碰业务的情况下,确认“冷页识别”和“冷页换出”两条链路
- 对真实进程、QEMU 进程或 OpenClaw 实例做冷热观测
不直接证明生产收益:
- 合成 demo 不能替代真实业务压测
- swap 后端太慢时,内存下降可能伴随明显尾延迟
- 直接对 QEMU 进程做 swap 前,需要先确认热路径不会被换出
在 openEuler 目标服务器上执行。
git clone https://github.com/HaotianChen616/etmemDemo.git
cd etmemDemo如果服务器只能走 SSH:
git clone git@github.com:HaotianChen616/etmemDemo.git
cd etmemDemopython3 --version
uname -a
cat /etc/os-releasescan-only demo 只需要 python3 和内核 /proc/<pid>/idle_pages 接口。
swap demo 还需要:
command -v etmem
command -v etmemd如果发行版仓库中已有包,优先使用包管理器:
sudo dnf install -y etmem || sudo yum install -y etmem如果没有包,按 openEuler 官方文档从源码编译:
sudo yum -y install libboundscheck cmake gcc gcc-c++ make
git clone https://atomgit.com/openeuler/etmem.git
cd etmem
mkdir -p build
cd build
cmake ..
make编译后需要确保 etmem 和 etmemd 在 PATH 中,或把二进制复制到服务器标准路径。
sudo modprobe etmem_scan
sudo modprobe etmem_swap
lsmod | grep etmem预期至少看到:
etmem_scan
etmem_swap
如果只是跑 etmem-scan-demo,只需要 etmem_scan。如果跑 etmem-swap-demo,两个模块都需要。
swap demo 必须启用 swap:
swapon --show
grep -E 'SwapTotal|SwapFree' /proc/meminfo如果 SwapTotal 为 0,先配置 swap。测试优先使用 zram 或 NVMe swap,避免慢盘把结论变成 IO 抖动。
如果目标是 VM/OpenClaw 实例,优先用 cslide hotness demo。它走的是完整 etmem 用户态链路:
/etc/etmem config -> etmemd -> cslide -> showtaskpages/showhostpages
运行:
cd etmem-cslide-hotness-demo
sudo TARGET_PID=<QEMU_PID> NODE_PAIR=<AEP_NODE,DRAM_NODE> ./run_etmem_cslide_hotness_demo.sh示例:
sudo TARGET_PID=12345 NODE_PAIR=2,0 SAMPLES=5 SAMPLE_INTERVAL_SEC=30 ./run_etmem_cslide_hotness_demo.sh脚本默认 NODE_MIG_QUOTA=0,也就是先分析、不主动迁移。它会生成类似 /etc/etmem/etmem-cslide-hotness-<pid>.conf 的配置文件,然后执行:
etmemd -l 0 -s etmem_hotness_<script_pid>
etmem obj add -f /etc/etmem/etmem-cslide-hotness-<script_pid>.conf -s etmem_hotness_<script_pid>
etmem project start -n hotness_probe -s etmem_hotness_<script_pid>
etmem engine showtaskpages -t vm_hotness -n hotness_probe -e cslide -s etmem_hotness_<script_pid>
etmem engine showhostpages -n hotness_probe -e cslide -s etmem_hotness_<script_pid>如果当前机器没有适合 cslide 的 VM hugepage / 分级内存环境,再使用下面的 etmem_scan 旁路观测。
这个阶段只读 /proc/<pid>/idle_pages。页面状态来自 etmem_scan,仓里的脚本负责把状态按 slide 风格累计成随时间变化的 hot/cold 占比。它不经过 etmemd 的 project 调度,也不等价于 slide 最终 swap 选页结果。
先跑合成靶进程,确认 raw scan 链路:
cd etmem-scan-demo
sudo ./run_etmem_scan_demo.sh更强信号:
sudo TOTAL_MB=4096 HOT_MB=128 SCAN_INTERVAL_SEC=15 SCAN_SAMPLES=5 ./run_etmem_scan_demo.sh如果想用接近 etmem slide 源码 loop/sleep/interval/T 的方式看随时间变化的冷热占比,可以直接跑:
sudo python3 ./scan_idle_pages.py \
--pid <PID> \
--warmup \
--loop 3 \
--sleep 10 \
--interval 30 \
--t 2 \
--watch \
--vma-filter slide-anon \
--min-vma-kb 0 \
--csv /tmp/etmem-scan-summary.csv这表示每个样本内部扫描 3 轮;sleep=10 是轮内 scan 后等待,interval=30 是开始下一次扫描任务前的外层窗口;read/access 权重为 1,dirty/write 权重为 3,累计权重 < 2 判为 cold,>= 2 判为 hot。
预期现象:
- 输出中
hot接近HOT_MB cold接近TOTAL_MB - HOT_MBcold_ratio稳定在较高水平- 生成 summary CSV 和 per-VMA CSV
扫描真实进程:
sudo python3 ./scan_idle_pages.py \
--pid <PID> \
--warmup \
--interval 30 \
--samples 5 \
--vma-filter slide-anon \
--csv /tmp/etmem-scan-summary.csv \
--vma-csv /tmp/etmem-scan-vmas.csv扫描 QEMU / VM 进程可从这个参数组合开始:
sudo python3 ./scan_idle_pages.py \
--pid <QEMU_PID> \
--warmup \
--interval 30 \
--samples 5 \
--vma-filter rw-private \
--min-vma-kb 2048 \
--csv /tmp/qemu-scan-summary.csv \
--vma-csv /tmp/qemu-scan-vmas.csv注意:读取 idle_pages 会清 accessed bit。--warmup 的作用是先清一次基线,再等待外层 interval 窗口,后续样本才更接近“这些扫描窗口内谁热、谁冷”。
slide 的 count >= T 判断发生在一次 scan job 的 loop 轮扫描完成之后,不是在每一轮 page scan 后立刻分级。
以当前 etmem 源码的 page scan 路径理解:
interval进入 task 的 thread timer,用于触发下一次 scan job。loop是一次 scan job 内的扫描轮数。sleep在一次 scan job 内每轮扫描后执行,包括最后一轮扫描后。- 每轮从
/proc/<pid>/idle_pages得到页面状态后,read/access 累计权重1,dirty/write 累计权重3,idle 累计权重0。 - 这次 job 的
loop轮都扫完后,count >= T进入 hot pages,count < T进入 cold pages,再把 cold pages 交给 slide 的迁移路径。 count是一次 job 内的累计值,不是进程生命周期内的永久访问计数。
flowchart TD
A["etmem project start"] --> B["slide task 创建 thread timer"]
B --> C["等待 interval"]
C --> D["触发一次 scan job<br/>slide_executor(pid)"]
D --> E{"check_should_swap()"}
E -- "否" --> F["本次 job 结束"]
F --> C
E -- "是" --> G["etmemd_do_scan()"]
G --> H["获取目标 PID 的 VMA 列表"]
H --> I["round = 1"]
I --> J["读取 /proc/<pid>/idle_pages<br/>遍历 VMA 并解析页面状态"]
J --> K{"本轮页面状态"}
K -- "idle" --> L["weight = 0"]
K -- "read/access" --> M["weight = 1"]
K -- "dirty/write" --> N["weight = 3"]
K -- "hole" --> O["跳过 non-present 范围"]
L --> P["累计 page_refs.count"]
M --> P
N --> P
O --> Q["本轮扫描继续"]
P --> Q
Q --> R["本轮扫描结束后 sleep"]
R --> S{"round < loop ?"}
S -- "是" --> T["round = round + 1"]
T --> J
S -- "否" --> U["loop 轮扫描完成<br/>本次 job 的 count 已累计"]
U --> V["sort_page_refs()"]
V --> W["slide_policy_interface()"]
W --> X{"count >= T ?"}
X -- "是" --> Y["hot_pages"]
X -- "否" --> Z["cold_pages"]
Z --> AA["slide_do_migrate()<br/>走冷页迁移/换出路径"]
Y --> AB["本次 job 收尾"]
AA --> AB
AB --> C
例如:
loop=3
sleep=10
interval=30
T=2
一次节奏可以看成:
等待 interval=30s
-> round 1 scan
-> sleep 10s
-> round 2 scan
-> sleep 10s
-> round 3 scan
-> sleep 10s
-> 按本次 job 的累计 count 和 T 分 hot/cold
-> cold pages 进入 slide 迁移路径
读取 /proc/<pid>/idle_pages 不是只读展示一份静态报表,而是在做一次 etmem_scan 采样。它检查页面在当前观测窗口里的 accessed/dirty/idle 状态,并为后续窗口重置这类访问痕迹。
所以“会影响 accessed bit”的含义是:
- 影响的是后续扫描看到的冷热采样窗口,不是修改业务内存内容。
- 它不是把目标页内容逐页读一遍、把业务页读热。
- 单独运行本仓库的 scan observer 没问题;它自己通过
warmup、interval、loop、sleep定义采样窗口。 - 要避免的是同一个 PID 同时被
etmemd slide、另一个 scan observer 或其他idle_pages读取者并发扫描。先读到访问痕迹的一方可能把这段痕迹消费掉,后读的一方会看到被切碎的窗口。
sequenceDiagram
participant App as "目标进程"
participant Bits as "页访问状态"
participant Sidecar as "scan observer"
participant Slide as "etmemd slide"
Slide->>Bits: "扫描并建立观察窗口"
App->>Bits: "访问页 P,accessed 状态出现"
Sidecar->>Bits: "读取 idle_pages"
Bits-->>Sidecar: "返回 P=accessed"
Sidecar->>Bits: "为下一次采样重置访问痕迹"
Slide->>Bits: "稍后再次扫描 P"
Bits-->>Slide: "若期间无新访问,P 可能表现为 idle"
因此建议分两段做实验:
- 冷热画像阶段:只运行 scan observer,观察 hot/cold 占比和稳定性。
- slide 换出阶段:关闭旁路冷热扫描,只看
VmRSS、VmSwap、major fault 和业务延迟。
如果你已经有目标 PID,或者想让脚本启动目标进程后自动 attach,使用 slide monitor:
cd ../etmem-slide-monitor
sudo ./run_etmem_slide_monitor.sh \
--pid <PID> \
--loop 3 \
--sleep 10 \
--interval 30 \
--t 2 \
--sample-interval 30让脚本启动目标进程:
sudo ./run_etmem_slide_monitor.sh \
--loop 3 \
--sleep 10 \
--interval 30 \
--t 2 \
--duration 3600 \
-- /path/to/your_app --arg1 --arg2如果只想做合成冷页换出穿刺,再进入 swap demo:
回到仓库根目录或进入 swap demo:
cd ../etmem-swap-demo
sudo ./run_etmem_swap_demo.sh更强信号:
sudo TOTAL_MB=4096 HOT_MB=128 DURATION_SEC=180 ./run_etmem_swap_demo.sh如果希望按常规习惯把 etmem 配置文件放到 /etc/etmem,这样跑:
sudo CONFIG_DIR=/etc/etmem TOTAL_MB=4096 HOT_MB=128 DURATION_SEC=180 ./run_etmem_swap_demo.sh说明:etmem 官方示例常把配置放在 /etc/etmem,但 etmem obj add -f <config> 接收的是配置文件路径,并不要求 demo 必须写死到 /etc/etmem。本仓库默认把配置写到 /tmp/etmem-swap-demo.*/,便于一次运行一个隔离工作目录;使用 CONFIG_DIR=/etc/etmem 时,脚本会把生成的 etmem-demo-slide-<pid>.conf 放到 /etc/etmem。
可选回读验证:
sudo REFAULT_AFTER=1 ./run_etmem_swap_demo.sh预期通过条件:
VmRSS明显下降VmSwap明显上升MemAvailable通常回升- 脚本输出
PASS
输出示例:
before rss= 1035 MB vmswap= 0 MB memavail= 64000 MB swapfree= 8192 MB
running rss= 160 MB vmswap= 875 MB memavail= 64870 MB swapfree= 7317 MB
after rss= 150 MB vmswap= 885 MB memavail= 64880 MB swapfree= 7307 MB
PASS: etmem swapped cold anonymous pages and lowered target resident memory.
swap demo 实际执行的 etmem 配置类似这样,脚本运行时也会直接打印:
[project]
name=demo_slide
loop=3
interval=5
sleep=10
sysmem_threshold=100
swapcache_high_wmark=5
swapcache_low_wmark=3
[engine]
name=slide
project=demo_slide
[task]
project=demo_slide
engine=slide
name=coldmem_demo
type=pid
value=<coldmem_target_pid>
T=1
max_threads=1
swap_threshold=0g
swap_flag=no脚本实际调用的 etmem 命令是:
etmemd -l 0 -s etmem_demo_<script_pid>
etmem obj add -f <generated_config_file> -s etmem_demo_<script_pid>
etmem project start -n demo_slide -s etmem_demo_<script_pid>
etmem project stop -n demo_slide -s etmem_demo_<script_pid>
etmem obj del -f <generated_config_file> -s etmem_demo_<script_pid>建议按这个顺序做,不要直接上 swap:
- 对 VM/OpenClaw 优先用
etmem-cslide-hotness-demo,观察 etmem 输出的冷热温。 - 如果 cslide 不适用,再用
etmem-scan-demo做底层 scan fallback。 - 连续采样至少 5 到 10 个窗口,确认 cold ratio 稳定,而不是业务空闲假象。
- 同时采集业务指标:QPS/FPS、p50/p95/p99、失败率、模型输出质量。
- 如果冷数据集中在可接受的匿名缓冲区,再做小流量 swap 验证。
- 先保守参数,确认没有明显 major fault 尖刺,再逐步扩大换出规模。
真实进程建议采集:
pid=<PID>
cat /proc/$pid/status | grep -E 'VmRSS|VmSwap'
cat /proc/$pid/smaps_rollup
pidstat -r -u -d -p $pid 5
vmstat 5QEMU / VM 场景额外采集:
numastat -p <QEMU_PID>
grep -E 'AnonHugePages|Rss|Pss|Swap' /proc/<QEMU_PID>/smaps_rollupcslide hotness 阶段通过:
etmemd能启动etmem obj add和etmem project start成功etmem engine showtaskpages能输出目标 task 页面访问情况- 多轮采样能看到可解释的 hot / warm / cold 分布
raw scan fallback 阶段通过:
/proc/<pid>/idle_pages存在- 合成 demo 能看到接近
HOT_MB的热内存和接近TOTAL_MB - HOT_MB的冷内存 - 真实进程的 cold ratio 在业务负载期间稳定可解释
swap 阶段通过:
/proc/<pid>/swap_pages存在etmemd能启动,etmem obj add和project start成功- 合成 demo 的
VmRSS下降,VmSwap上升 - 可选
REFAULT_AFTER=1后 RSS 能回升,证明换出的页可正常 fault back
真实业务阶段通过:
- 内存收益达到预期,例如 DRAM/RSS 降低 15% 到 30%
- QPS/FPS 下降在可接受范围内
- p95/p99 没有持续尖刺
pswpin、major fault 不在热路径持续增长
/proc/<pid>/idle_pages 不存在:
sudo modprobe etmem_scan如果仍不存在,说明当前内核不包含 etmem scan 支持,或模块没有随内核安装。
/proc/<pid>/swap_pages 不存在:
sudo modprobe etmem_swap如果仍不存在,无法跑 swap demo。
command -v etmem 失败:
先用包管理器安装,或按官方文档源码编译 etmem。
swap demo 没有 VmSwap 增长:
- 检查
swapon --show - 检查
etmem-swap-demo输出的etmem.log - 增大
TOTAL_MB - 增大
DURATION_SEC - 降低业务访问强度,确保目标页真的冷下来
RSS 降了但业务抖动:
- 换用 zram 或 NVMe swap
- 增大观测窗口,只换出更冷的页
- 对真实业务优先考虑
MADV_SWAPFLAG方式标记可换出的内存区域 - 不要直接把模型权重、KV cache 或推理热路径内存纳入换出候选
.
├── README.md
├── etmem-cslide-hotness-demo
│ ├── README.md
│ └── run_etmem_cslide_hotness_demo.sh
├── etmem-scan-demo
│ ├── README.md
│ ├── run_etmem_scan_demo.sh
│ └── scan_idle_pages.py
└── etmem-swap-demo
├── README.md
├── coldmem_target.py
└── run_etmem_swap_demo.sh