评估内存3

Linux 内存健康评估脚本(进阶版)完整文档

0. 文档定位与目标

本文档用于配合并指导使用 Linux 内存健康评估 — 终极全功能脚本(进阶增强版),目标读者为:

  • SRE / 运维工程师

  • 内核 / 性能工程师

  • 容器 / 云平台维护人员

目标不是“查看内存”,而是:

在 OOM、性能抖动、软中断异常、kswapd 飙高之前,定位内存结构性风险与根因方向。


1. Linux 内存全景模型(脚本设计基础)

1.1 内存分类(逻辑视角)

物理内存
├─ 用户态
│  ├─ Anonymous (Active / Inactive)
│  └─ Swap-backed
├─ PageCache / Cached(文件内容)
├─ Buffers(块设备元数据)
├─ Slab(内核对象)
│  ├─ SReclaimable
│  └─ SUnreclaimable
└─ 内核其他(stack / pagetables / vmalloc)

1.2 本脚本的核心思想

  • 不看“用了多少”,看“谁在用 + 能不能回收”

  • 通过 比例 + 结构 + 行为指标 判定风险

  • 避免被 free -h / top 误导


2. Slab 内存(内核对象)的进阶解读

2.1 Slab 是什么

Slab 是 Linux 内核的对象级内存分配器(kmem_cache),用于高频、固定大小内核对象。

典型对象:

  • socket / sk_buff

  • inode / dentry

  • task_struct

  • 协议栈、文件系统元数据

2.2 SReclaimable vs SUnreclaimable

类型含义风险等级
SReclaimable 可回收对象(inode/dentry)
SUnreclaimable 不可回收对象(socket/task)

SUnreclaimable 是“真正的内核常驻内存”

2.3 脚本中的 Slab 判定逻辑

  • Slab 占比(Slab / MemTotal)

  • SUnreclaimable / Slab 比例

  • Slab TopN 集中度(是否单一子系统异常)

风险经验阈值

指标正常关注危险
Slab% <10% 10–20% >20%
SUnreclaimable / Slab <30% 30–50% >50%
Top3 集中度 <40% 40–60% >60%

3. 高阶内存 / 低阶内存(碎片风险)

3.1 Buddy System 与 order

Linux 物理内存按 2^order 页分配:

Order页数大小
0 1 4KB
1 2 8KB
2 4 16KB
≥3 ≥8 ≥32KB

3.2 高阶内存的危险性

  • 需要连续物理页

  • 易受内存碎片影响

  • 常见失败场景:

    • THP 分配

    • DMA

    • 网络大包

3.3 脚本如何检测碎片

  • /proc/buddyinfo 中 order>=3 可用页

  • kswapd CPU 占用

  • pgscan / pgsteal 比例

MemAvailable 高 ≠ 高阶内存充足


4. Buffer / Cache 的正确理解

4.1 Buffer

  • 块设备元数据缓存

  • 文件系统结构信息

  • 正常系统中占比极小(<5%)

4.2 Cache / PageCache

  • 文件内容缓存

  • mmap 文件页

  • 可随时回收

PageCache 高通常是“健康状态”,不是问题

4.3 Cache 风险判断

真正的风险不是 Cache 大,而是:

  • Cache 被频繁扫描(pgscan 高)

  • 但难以回收(pgsteal 低)

这意味着 Cache 被 匿名内存或 Slab 挤压


5. 匿名内存(Anon)与 OOM 风险

5.1 Anon 是什么

  • 进程堆、栈、匿名 mmap

  • OOM Killer 的主要目标

5.2 风险信号

指标含义
Anon >60% OOM 高风险
Active(anon) 高 回收困难
Swap 使用 内存压力已发生

6. 回收行为与压力指标(行为层)

6.1 kswapd

  • 后台回收线程

  • CPU 高 → 内存压力显著

6.2 vmstat 关键字段

字段含义
pgscan_* 扫描页数
pgsteal_* 回收页数
pgmajfault 重大缺页

6.3 PSI(Pressure Stall Information)

  • some:部分任务受阻

  • full:系统级内存阻塞

用于判断“是否真的卡住”。


7. 容器 / cgroup 视角

7.1 cgroup v1 / v2

  • memory.current / memory.max

  • memory.stat(anon / file / slab)

7.2 常见误区

  • 容器 OOM,但宿主 MemAvailable 充足

  • slab 不受 limit 直接限制(部分场景)


8. 自动判因逻辑(脚本核心价值)

脚本会根据多指标组合,给出根因方向

  • 匿名内存主导

  • Slab 不可回收对象

  • PageCache 被挤压

  • 高阶内存碎片

这是“专家经验”的程序化表达。


9. 典型故障场景对照表

现象高概率原因
MemAvail 低 + Anon 高 应用内存泄漏
Slab 高 + SUn 高 网络 / 内核对象泄漏
Swap 用但 Cache 高 匿名内存挤压
OOM 无预警 高阶内存碎片

10. 使用建议与下一步

10.1 日常巡检

  • 定时执行脚本

  • 对比 Slab / Anon 增长趋势

10.2 出现异常后

  • slabtop / perf / smem / pmap

  • 定位具体进程或子系统

10.3 可扩展方向

  • 时间序列记录(泄漏检测)

  • PID 级匿名内存归因

  • K8s OOM 预测模型


11. 总结

这套脚本与文档的目标是:

让内存问题从“经验判断”变成“结构化诊断”。

它适用于:

  • 裸机

  • 虚拟机

  • 容器

  • 生产事故前与事故中

 

#!/usr/bin/env bash
# ==========================================================
# Linux 内存健康评估 — 终极进阶增强版(Production)
# ==========================================================
set -euo pipefail
IFS=$'\n\t'

# ---------------- Config ----------------
N=10
MEMINFO=/proc/meminfo
SLABINFO=/proc/slabinfo
CGROUP_V1=/sys/fs/cgroup/memory
CGROUP_ROOT=/sys/fs/cgroup
CHECK_NUMA=1

# ---------------- Colors ----------------
color(){ case "$1" in
  green)  echo -e "\033[32m$2\033[0m";;
  yellow) echo -e "\033[33m$2\033[0m";;
  red)    echo -e "\033[31m$2\033[0m";;
  bold)   echo -e "\033[1m$2\033[0m";;
  *) echo "$2";;
esac }

# ---------------- Helpers ----------------
has_cmd(){ command -v "$1" >/dev/null 2>&1; }
kb_to_gb(){ awk -v v="$1" 'BEGIN{printf "%.2f", v/1024/1024}'; }
safe_read(){ [[ -f "$1" ]] && cat "$1" 2>/dev/null || echo 0; }

get_field(){
  local key="$1"
  awk -v k="$key" '$1 ~ "^"k":" {for(i=1;i<=NF;i++){if($i~/^[0-9]+$/){print $i; exit}}}' "$MEMINFO"
}

num(){ [[ -z "${1:-}" ]] && echo 0 || echo "$1"; }
pct(){ awk -v a="$1" -v b="$2" 'BEGIN{if(b==0){print 0}else{printf "%.1f", a/b*100}}'; }

# ---------------- 浮点比较函数 ----------------
f_lt() { awk -v a="$1" -v b="$2" 'BEGIN{exit !(a<b)}'; }
f_gt() { awk -v a="$1" -v b="$2" 'BEGIN{exit !(a>b)}'; }

judge(){
  val="$1"; g="$2"; y="$3"; u="${4:-}"
  cmp=$(awk -v v="$val" -v g="$g" -v y="$y" 'BEGIN{if(v<g)print "G";else if(v<y)print "Y";else print "R"}')
  case "$cmp" in
    G) color green  "${val}${u} (✔ 正常)";;
    Y) color yellow "${val}${u} (⚠ 关注)";;
    R) color red    "${val}${u} (✘ 危险)";;
  esac
}

# ---------------- Read meminfo ----------------
MemTotal=$(num "$(get_field MemTotal)")
MemAvailable=$(num "$(get_field MemAvailable)")
Cached=$(num "$(get_field Cached)")
Buffers=$(num "$(get_field Buffers)")
Slab=$(num "$(get_field Slab)")
SReclaimable=$(num "$(get_field SReclaimable)")
SUnreclaimable=$(num "$(get_field SUnreclaim)")
ActiveAnon=$(num "$(get_field Active_anon)")
InactiveAnon=$(num "$(get_field Inactive_anon)")
SwapTotal=$(num "$(get_field SwapTotal)")
SwapFree=$(num "$(get_field SwapFree)")
PageTables=$(num "$(get_field PageTables)")
KernelStack=$(num "$(get_field KernelStack)")
Shmem=$(num "$(get_field Shmem)")

AnonTotal=$((ActiveAnon + InactiveAnon))
SwapUsed=$((SwapTotal - SwapFree))
PageCache=$Cached

kswapd_cpu=$(ps -eo comm,pcpu 2>/dev/null | awk '/kswapd/ {sum+=$2} END{printf "%.1f",sum+0}')

PageCachePct=$(pct $PageCache $MemTotal)
AnonPct=$(pct $AnonTotal $MemTotal)
SlabPct=$(pct $Slab $MemTotal)
MemAvailPct=$(pct $MemAvailable $MemTotal)

# ---------------- Header ----------------
echo "===================== Linux 内存健康评估(终极进阶版) ====================="
echo "总内存: $(kb_to_gb $MemTotal) GB"
echo -n "可用内存: "; judge $MemAvailPct 20 10 "%"

# ==========================================================
# Slab 深度分析
# ==========================================================
echo "▶ Slab 内存(内核对象)"
echo -n "Slab 占比: "; judge $SlabPct 10 20 "%"

SUnPct=$(pct $SUnreclaimable $Slab)
echo -n "SUnreclaimable / Slab: "; judge $SUnPct 30 50 "%"

if [[ -f $SLABINFO ]]; then
  echo
  echo "Slab Top $N(按占用):"
  awk 'NR>2{print $1,$2*$3}' "$SLABINFO" | sort -k2 -nr | head -n $N
fi

# ==========================================================
# PageCache / Buffer 行为分析
# ==========================================================
echo
echo "▶ PageCache / Buffers"
echo -n "PageCache 占比: "; judge $PageCachePct 50 70 "%"
echo -n "Buffers 占比: "; judge $(pct $Buffers $MemTotal) 5 10 "%"

if [[ -f /proc/vmstat ]]; then
  pgscan=$(awk '/pgscan_kswapd/ {print $2}' /proc/vmstat)
  pgsteal=$(awk '/pgsteal_kswapd/ {print $2}' /proc/vmstat)
  echo "pgscan_kswapd=$pgscan pgsteal_kswapd=$pgsteal"
  if (( pgscan > pgsteal * 2 )); then
    color red "PageCache 被频繁扫描但难以回收(被挤压)"
  fi
fi

# ==========================================================
# 匿名内存 & OOM 风险
# ==========================================================
echo
echo "▶ 匿名内存(Anon)"
echo -n "Anon 占比: "; judge $AnonPct 50 70 "%"
if f_gt "$ActiveAnon" "$((MemTotal/2))"; then
  color red "Active(anon) >50%,OOM 高风险"
fi

# ==========================================================
# Swap & kswapd
# ==========================================================
echo
echo "▶ Swap"
SwapMB=$((SwapUsed/1024))
if (( SwapMB==0 )); then
  color green "Swap 未使用"
else
  color yellow "SwapUsed=${SwapMB}MB"
fi

echo
echo "▶ kswapd CPU"
if f_lt "$kswapd_cpu" 3; then
  color green "$kswapd_cpu%"
elif f_lt "$kswapd_cpu" 5; then
  color yellow "$kswapd_cpu%"
else
  color red "$kswapd_cpu%"
fi

# ==========================================================
# 高阶内存 / 碎片风险
# ==========================================================
echo
echo "▶ 高阶内存 / 碎片风险"
if [[ -f /proc/buddyinfo ]]; then
  HighOrderFree=$(awk '{for(i=6;i<=NF;i++){if(i-6>=3)sum+=$i}} END{print sum+0}' /proc/buddyinfo)
  echo "High-order free pages: $HighOrderFree"
  if (( HighOrderFree < 100 )); then
    color red "高阶连续页严重不足(THP / DMA / 网络风险)"
  elif (( HighOrderFree < 500 )); then
    color yellow "高阶连续页偏少(碎片化)"
  else
    color green "高阶内存充足"
  fi
fi

# ==========================================================
# PSI
# ==========================================================
echo
echo "▶ PSI (memory)"
[[ -f /proc/pressure/memory ]] && cat /proc/pressure/memory

# ==========================================================
# 自动判因
# ==========================================================
echo
echo "===================== 自动判因结论 ====================="
if f_lt "$MemAvailPct" 10 && f_gt "$AnonPct" 60; then
  echo "- 根因方向:匿名内存(进程占用)"
elif f_gt "$SlabPct" 20 && f_gt "$SUnPct" 50; then
  echo "- 根因方向:Slab 不可回收对象(内核/网络/驱动)"
elif f_gt "$PageCachePct" 60 && (( pgscan > pgsteal )); then
  echo "- 根因方向:PageCache 被挤压"
elif (( HighOrderFree < 100 )); then
  echo "- 根因方向:内存碎片(高阶分配失败)"
else
  echo "- 当前未发现明确单点内存瓶颈"
fi

echo "===================== 评估完成 ====================="

  

 


(完)

posted on 2026-01-16 10:57  吃草的青蛙  阅读(1)  评论(0)    收藏  举报

导航