From 9c3f66affc75c96a5dd9d30226451d1cd01de2ba Mon Sep 17 00:00:00 2001 From: FORGE <78164141+LucaLin233@users.noreply.github.com> Date: Sat, 24 Oct 2026 02:47:59 +0800 Subject: [PATCH] perf(system-optimize): tune zram sizing and swap policy Cap the large-memory zram expression at 8 GiB, render swap-priority explicitly, unify swappiness at 100 for the pure-zram case, narrow the THP defrag conversion to always, and split the comma-bearing size expression correctly. --- modules/system-optimize.sh | 48 +++++++--- tests/test-system-optimize.sh | 164 +++++++++++++++++++++++++++++++++- 2 files changed, 201 insertions(+), 11 deletions(-) diff --git a/modules/system-optimize.sh b/modules/system-optimize.sh index 94ca799..3628805 100644 --- a/modules/system-optimize.sh +++ b/modules/system-optimize.sh @@ -458,6 +458,15 @@ get_zram_size() { ' /proc/swaps } +get_zram_priority() { + awk ' + $1 == "/dev/zram0" { + print $5 + exit + } + ' /proc/swaps +} + is_zram_active() { systemctl is-active --quiet systemd-zram-setup@zram0.service && [[ -b /dev/zram0 ]] && @@ -843,24 +852,19 @@ setup_zswap() { get_optimal_zram_config() { local mem_mb="$1" local zram_size - local swappiness - # 小内存优先避免 OOM;大内存则减少不必要的压缩和换页。 + # 纯 zram 部署:各档位统一 swappiness=100,优先使用压缩内存而不是磁盘 swap。 if (( mem_mb <= 512 )); then zram_size="ram * 2" - swappiness=60 elif (( mem_mb <= 1024 )); then zram_size="ram * 1.5" - swappiness=60 elif (( mem_mb <= 4096 )); then zram_size="ram" - swappiness=40 else - zram_size="ram / 2" - swappiness=20 + zram_size="min(ram / 2, 8192)" fi - echo "${zram_size},${swappiness}" + echo "${zram_size},100" } write_zram_config() { @@ -872,6 +876,7 @@ write_zram_config() { [zram0] zram-size = ${zram_size} compression-algorithm = zstd +swap-priority = 100 EOF then log "写入 Zram 配置失败:$ZRAM_CONFIG" "error" @@ -1241,8 +1246,13 @@ setup_zram() { fi config=$(get_optimal_zram_config "$mem_mb") + # 表达式自身含逗号(min(ram / 2, 8192)),必须从最后一个逗号切分。 zram_size="${config%,*}" - swappiness="${config#*,}" + swappiness="${config##*,}" + if [[ ! "$swappiness" =~ ^[0-9]+$ ]]; then + log "无法解析 Zram swappiness:$config" "error" + return 1 + fi echo "检测到内存: ${mem_mb}MB" echo "目标 Zram: ${zram_size},swappiness=${swappiness}" @@ -1451,6 +1461,19 @@ EOF fi } +thp_mode_supported() { + local path="$1" + local mode="$2" + + awk -v mode="$mode" '{ + for (i = 1; i <= NF; i++) { + token = $i + gsub(/[\[\]]/, "", token) + if (token == mode) { found = 1 } + } + } END { exit !found }' "$path" +} + get_active_thp_mode() { local path="$1" awk '{ @@ -1488,8 +1511,13 @@ setup_thp_policy() { case "$defrag_mode" in always|defer|defer+madvise|madvise|never) ;; *) return 1 ;; esac defrag_target="$defrag_mode" case "$defrag_mode" in - always|defer) defrag_target="defer+madvise" ;; + always) defrag_target="defer+madvise" ;; esac + if [[ "$defrag_target" != "$defrag_mode" ]] && + ! thp_mode_supported "$defrag_path" "$defrag_target"; then + log "内核不支持 THP defrag 模式 $defrag_target,跳过写入" "warn" + return 1 + fi fi if ! printf '%s\n' "$enabled_mode" > "$STATE_DIR/thp-enabled.previous-runtime"; then diff --git a/tests/test-system-optimize.sh b/tests/test-system-optimize.sh index 2c32041..36f0887 100644 --- a/tests/test-system-optimize.sh +++ b/tests/test-system-optimize.sh @@ -520,7 +520,7 @@ unset status MEM_MB=$(awk '/^MemTotal:/ {print int($2 / 1024)}' /proc/meminfo) TARGET_CONFIG=$(get_optimal_zram_config "$MEM_MB") TARGET_SIZE="${TARGET_CONFIG%,*}" -TARGET_SWAPPINESS="${TARGET_CONFIG#*,}" +TARGET_SWAPPINESS="${TARGET_CONFIG##*,}" # 这些函数在模块里已有定义,必须在 source 之后覆盖才能生效。 install_zram_generator() { @@ -835,4 +835,166 @@ else printf 'SKIP: main aggregation needs root (EUID=%s)\n' "$EUID" fi + +# ---------------------------------------------------------------- PR 2: zram sizing table + +assert_eq "ram * 2,100" "$(get_optimal_zram_config 512)" "512 MiB keeps 2x sizing" +assert_eq "ram * 1.5,100" "$(get_optimal_zram_config 513)" "513 MiB moves to 1.5x sizing" +assert_eq "ram * 1.5,100" "$(get_optimal_zram_config 1024)" "1024 MiB stays at 1.5x sizing" +assert_eq "ram,100" "$(get_optimal_zram_config 1025)" "1025 MiB moves to 1x sizing" +assert_eq "ram,100" "$(get_optimal_zram_config 4096)" "4096 MiB stays at 1x sizing" +assert_eq "min(ram / 2, 8192),100" "$(get_optimal_zram_config 4097)" "4097 MiB caps at half of RAM" +assert_eq "min(ram / 2, 8192),100" "$(get_optimal_zram_config 16384)" "16384 MiB caps at half of RAM" +assert_eq "min(ram / 2, 8192),100" "$(get_optimal_zram_config 32768)" "32768 MiB keeps the 8 GiB ceiling" + +comma_config=$(get_optimal_zram_config 8192) +assert_eq "min(ram / 2, 8192)" "${comma_config%,*}" "size expression is cut at the last comma" +assert_eq "100" "${comma_config##*,}" "swappiness is cut after the last comma" + +# ---------------------------------------------------------------- PR 2: rendered policy + +reset_sandbox +assert_ok "write zram config with swap policy" write_zram_config "ram" 100 +assert_eq "100" "$(get_zram_config_value swap-priority)" "generator config renders swap-priority=100" +assert_eq "zstd" "$(get_zram_config_value compression-algorithm)" "generator config keeps zstd" +assert_eq "100" "$(get_zram_sysctl_value vm.swappiness)" "sysctl renders swappiness=100" +assert_eq "0" "$(get_zram_sysctl_value vm.page-cluster)" "sysctl keeps page-cluster=0" +assert_not_contains "$(cat "$SYSCTL_CONFIG")" "zswap" "sysctl still has no zswap knob" +assert_not_contains "$(cat "$ZRAM_CONFIG")" "mem_limit" "generator config adds no mem_limit" +assert_not_contains "$(cat "$ZRAM_CONFIG")" "writeback" "generator config adds no writeback" + +# ---------------------------------------------------------------- PR 2: priority matching + +saved_optimal_fn=$(declare -f get_optimal_zram_config) +get_optimal_zram_config() { printf 'min(ram / 2, 8192),100\n'; } + +reset_sandbox +seed_device "min(ram / 2, 8192)" +seed_sysctl 100 +seed_runtime_zram "min(ram / 2, 8192)" +write_zswap_config +printf 'N\n' > "$ZSWAP_ENABLED" +ZRAM_ACTIVE=true +ZRAM_USED=0 +ZRAM_PRIORITY=100 +assert_ok "comma expression is parsed and the size text is preserved" setup_zram >/dev/null 2>&1 +assert_eq "min(ram / 2, 8192)" "$(get_zram_config_value zram-size)" "size expression keeps its comma" +assert_eq "100" "$(get_zram_sysctl_value vm.swappiness)" "parsed swappiness is 100" +assert_no_call "systemctl stop" "missing priority text with running 100 does not rebuild" + +reset_sandbox +seed_device "min(ram / 2, 8192)" +seed_sysctl 100 +seed_runtime_zram "min(ram / 2, 8192)" +write_zswap_config +printf 'N\n' > "$ZSWAP_ENABLED" +ZRAM_ACTIVE=true +ZRAM_USED=0 +ZRAM_PRIORITY=50 +assert_ok "priority drift rebuilds the device" setup_zram >/dev/null 2>&1 +assert_call "systemctl stop" "running priority other than 100 triggers a rebuild" +assert_eq "100" "$(get_zram_config_value swap-priority)" "rebuild writes priority 100" + +reset_sandbox +seed_device "min(ram / 2, 8192)" +seed_sysctl 100 +seed_runtime_zram "min(ram / 2, 8192)" +write_zswap_config +printf 'N\n' > "$ZSWAP_ENABLED" +ZRAM_ACTIVE=true +ZRAM_USED=2048 +ZRAM_PRIORITY=50 +assert_ok "priority drift with used pages is deferred" setup_zram >/dev/null 2>&1 +assert_no_call "systemctl stop" "used pages still block the priority rebuild" +[[ -f "$STATE_DIR/zram-reconfigure.pending" ]] || fail "deferred priority rebuild must leave a pending marker" +pass "deferred priority rebuild leaves a pending marker" + +eval "$saved_optimal_fn" + +# ---------------------------------------------------------------- PR 2: THP defrag + +# get_active_thp_mode 是既有实现:busybox awk 不会把 gsub 结果写回 $i, +# 因此本环境用桩提供 modes,只测本 PR 的转换与写入门槛;CI 的 gawk/mawk 走真实实现。 +THP_DIR="$SYSFS_ROOT/kernel/mm/transparent_hugepage" +probe_file="$TEMP_DIR/thp-probe" +printf 'always [madvise] never\n' > "$probe_file" +if [[ "$(get_active_thp_mode "$probe_file")" == "madvise" ]]; then + printf 'PASS: get_active_thp_mode strips mode brackets in this environment\n' +else + printf 'SKIP: awk here does not strip THP mode brackets; THP policy is unit-tested with a stubbed getter\n' +fi + +THP_ENABLED_MODE="" +THP_DEFRAG_MODE="" +get_active_thp_mode() { + case "$1" in + */defrag) printf '%s\n' "$THP_DEFRAG_MODE" ;; + *) printf '%s\n' "$THP_ENABLED_MODE" ;; + esac +} + +reset_sandbox +THP_ENABLED_MODE=always +THP_DEFRAG_MODE=always +printf 'always [madvise] never\n' > "$THP_DIR/enabled" +printf 'always defer defer+madvise madvise never\n' > "$THP_DIR/defrag" +assert_ok "THP always defrag is relaxed" setup_thp_policy +assert_eq "madvise" "$(cat "$THP_DIR/enabled")" "THP enabled always becomes madvise" +assert_eq "defer+madvise" "$(cat "$THP_DIR/defrag")" "THP defrag always becomes defer+madvise" +assert_contains "$(cat "$THP_CONFIG")" "defer+madvise" "tmpfiles config records the defrag target" +assert_eq "always" "$(cat "$STATE_DIR/thp-defrag.previous-runtime")" "pre-change defrag mode is snapshotted" + +reset_sandbox +THP_ENABLED_MODE=madvise +THP_DEFRAG_MODE=defer +printf 'always [madvise] never\n' > "$THP_DIR/enabled" +printf 'always defer defer+madvise madvise never\n' > "$THP_DIR/defrag" +assert_ok "THP defer defrag is kept" setup_thp_policy +assert_eq "defer" "$(cat "$THP_DIR/defrag")" "defer defrag is left untouched" + +reset_sandbox +THP_ENABLED_MODE=never +THP_DEFRAG_MODE=madvise +printf 'always madvise [never]\n' > "$THP_DIR/enabled" +printf 'always defer defer+madvise madvise never\n' > "$THP_DIR/defrag" +assert_ok "THP madvise/never modes are kept" setup_thp_policy +assert_eq "never" "$(cat "$THP_DIR/enabled")" "never enabled mode is untouched" +assert_eq "madvise" "$(cat "$THP_DIR/defrag")" "madvise defrag is untouched" + +reset_sandbox +THP_ENABLED_MODE=madvise +THP_DEFRAG_MODE=always +printf 'always [madvise] never\n' > "$THP_DIR/enabled" +printf 'always defer madvise never\n' > "$THP_DIR/defrag" +assert_fail "unsupported defrag target degrades instead of forcing" setup_thp_policy +assert_eq "always defer madvise never" "$(cat "$THP_DIR/defrag")" "unsupported defrag mode is not written" +assert_eq "always [madvise] never" "$(cat "$THP_DIR/enabled")" "unsupported defrag aborts before touching enabled" + +reset_sandbox +THP_ENABLED_MODE=madvise +THP_DEFRAG_MODE="" +printf 'always [madvise] never\n' > "$THP_DIR/enabled" +rm -f "$THP_DIR/defrag" +assert_ok "missing defrag node is skipped" setup_thp_policy +assert_eq "madvise" "$(cat "$THP_DIR/enabled")" "missing defrag still applies the enabled policy" + +# ---------------------------------------------------------------- PR 2: rollback + +reset_sandbox +write_zram_config "min(ram / 2, 8192)" 100 +printf '# old zram config\n[zram0]\nzram-size = ram\ncompression-algorithm = lz4\n' > "$ZRAM_CONFIG.previous-backup" +printf 'vm.swappiness=60\nvm.page-cluster=3\n' > "$STATE_DIR/zram.previous-runtime" +for target in "$SYSCTL_CONFIG" "$SYSTEM_TUNING_CONFIG" "$JOURNALD_DROPIN" "$THP_CONFIG" \ + "$ZSWAP_SERVICE_CONFIG" "$ZRAM_SWAP_DROPIN" "$SWAP_TARGET_DROPIN"; do + install -D -m 0600 /dev/null "$target.previous-absent" +done +printf 'N\n' > "$ZSWAP_ENABLED" +printf 'N\n' > "$STATE_DIR/zswap-enabled.previous-runtime" +reset_calls +assert_ok "rollback restores the previous zram policy" restore_system_configs previous +assert_contains "$(cat "$ZRAM_CONFIG")" "compression-algorithm = lz4" "previous generator config is restored" +assert_call "sysctl -w vm.swappiness=60" "rollback restores vm.swappiness" +assert_call "sysctl -w vm.page-cluster=3" "rollback restores vm.page-cluster" +assert_no_call "swapoff" "rollback never forces swapoff" + printf 'All system-optimize tests passed.\n'