核心指标采集:CPU、内存、磁盘、网络

系统监控这件事,说白了就是回答三个问题:现在机器什么状态?哪里快扛不住了?过去几分钟发生了什么?而回答这些问题,全靠那几个核心指标。

我刚开始做运维那会儿,习惯用 top 和 free 看一眼就完事。后来线上出过一次故障,CPU 空闲还有 30%,但业务就是卡得要死。查了半天才发现是 I/O wait 飙到了 60%。从那以后,我就再也不敢只看 CPU 使用率了。

这一章,咱们把 CPU、内存、磁盘、网络这四个维度的采集原理掰开揉碎,再配上 Python 代码。你跟着写一遍,基本就能搭出一个可用的采集器。

2.1 CPU 指标采集

CPU 指标是所有监控系统的起点。但很多人只盯着 %user 和 %idle,这其实不够。

2.1.1 核心指标清单

指标名 来源文件 含义 异常阈值参考
user /proc/stat 用户态进程占用 > 70% 持续 5 分钟
system /proc/stat 内核态系统调用占用 > 30% 需关注
iowait /proc/stat CPU 等待磁盘 I/O 完成 > 20% 说明磁盘有瓶颈
steal /proc/stat 被宿主机偷走的 CPU 时间 > 10% 说明虚拟机争抢严重
load1/5/15 /proc/loadavg 运行队列平均长度 超过 CPU 核数 * 0.7

这里有个坑,我踩过好几次。iowait 高不代表 CPU 忙,它只是表示 CPU 在等磁盘。你想想看,如果 iowait 到了 40%,但 user 只有 10%,这时候加 CPU 核数根本没用,得去查磁盘。

2.1.2 采集原理

Linux 把 CPU 时间片的使用情况记录在 /proc/stat 里。第一行就是总的 CPU 统计,格式长这样:

cpu  user  nice  system  idle  iowait  irq  softirq  steal  guest  guest_nice

这些都是 累计值,单位是 jiffies(通常 10ms)。要算使用率,得取两次快照做差值。

我的习惯:采集间隔至少 1 秒。太短了误差大,太长了丢失毛刺。生产环境我一般用 10 秒间隔,既能看清趋势,又不至于数据量太大。

2.1.3 Python 实现

import time

def read_cpu_stat():
    with open('/proc/stat', 'r') as f:
        line = f.readline().strip().split()
    # 跳过 'cpu' 标签,取后续所有数值
    values = list(map(int, line[1:]))
    return values

def calc_cpu_usage(prev, curr):
    # 总时间 = 所有项之和
    prev_total = sum(prev)
    curr_total = sum(curr)
    total_delta = curr_total - prev_total

    # 空闲时间 = idle + iowait
    prev_idle = prev[3] + prev[4]
    curr_idle = curr[3] + curr[4]
    idle_delta = curr_idle - prev_idle

    usage = (1.0 - idle_delta / total_delta) * 100
    return round(usage, 2)

# 使用示例
prev = read_cpu_stat()
time.sleep(1)
curr = read_cpu_stat()
print(f"CPU 使用率: {calc_cpu_usage(prev, curr)}%")
注意:/proc/stat 里的数值可能回绕(wrap around),虽然概率极低。严谨的做法是检测到差值小于 0 时跳过本次计算。

2.2 内存指标采集

内存这块,最容易让人误解的是 available 和 free 的区别。free 只是完全没被用的内存,available 才是真正可分配给新进程的内存。

2.2.1 关键指标

指标 来源 说明
MemTotal /proc/meminfo 物理内存总量
MemFree /proc/meminfo 完全未使用的内存
MemAvailable /proc/meminfo 可用于新启动进程的内存(含可回收缓存)
Buffers /proc/meminfo 块设备读写缓存
Cached /proc/meminfo 文件系统页缓存
SwapTotal / SwapFree /proc/meminfo 交换分区总量和剩余

我曾经遇到过一个案例:某台机器 MemFree 只剩 200MB,但 MemAvailable 还有 6GB。监控告警一直报内存不足,实际上系统运行得很稳。后来我把告警指标从 MemFree 改成了 MemAvailable,世界清净了。

2.2.2 Python 实现

def read_mem_info():
    mem = {}
    with open('/proc/meminfo', 'r') as f:
        for line in f:
            parts = line.split(':')
            if len(parts) == 2:
                key = parts[0].strip()
                # 值格式如 "16234568 kB"
                value_str = parts[1].strip().split()[0]
                mem[key] = int(value_str)
    return mem

def calc_mem_usage(mem):
    total = mem['MemTotal']
    available = mem['MemAvailable']
    usage = (1.0 - available / total) * 100
    return {
        'total_mb': total // 1024,
        'available_mb': available // 1024,
        'usage_pct': round(usage, 2)
    }

mem = read_mem_info()
print(calc_mem_usage(mem))
避坑指南:我曾经在 2.6 内核的老机器上用过 MemAvailable,结果发现这个字段不存在。后来加了个 fallback:如果读不到 MemAvailable,就用 MemFree + Buffers + Cached 来估算。

2.3 磁盘指标采集

磁盘监控有两个维度:空间使用率和I/O 性能。空间用 df 或 /proc/mounts 就能搞定,但 I/O 性能得看 /proc/diskstats。

2.3.1 磁盘空间

import os

def get_disk_usage(path='/'):
    stat = os.statvfs(path)
    total = stat.f_frsize * stat.f_blocks
    free = stat.f_frsize * stat.f_bfree
    used = total - free
    return {
        'total_gb': total / (1024**3),
        'used_gb': used / (1024**3),
        'usage_pct': round(used / total * 100, 2)
    }

print(get_disk_usage('/data'))

2.3.2 磁盘 I/O

/proc/diskstats 每行对应一个磁盘设备,格式如下:

major minor name rio rmerge rsect ruse wio wmerge wsect wuse running use aveq

关键指标:

  • r/s, w/s:每秒读写次数(IOPS)
  • rkB/s, wkB/s:每秒读写数据量(吞吐量)
  • await:平均每次 I/O 的等待时间(毫秒)
  • %util:磁盘忙绿百分比(接近 100% 说明有瓶颈)
def read_disk_stats(device='sda'):
    with open('/proc/diskstats', 'r') as f:
        for line in f:
            parts = line.strip().split()
            if parts[2] == device:
                return {
                    'rio': int(parts[3]),
                    'rmerge': int(parts[4]),
                    'rsect': int(parts[5]),
                    'ruse': int(parts[6]),
                    'wio': int(parts[7]),
                    'wmerge': int(parts[8]),
                    'wsect': int(parts[9]),
                    'wuse': int(parts[10]),
                    'running': int(parts[11]),
                    'use': int(parts[12]),
                    'aveq': int(parts[13])
                }
    return None
经验之谈:磁盘 I/O 的采样间隔建议 5 秒以上。因为磁盘指标波动大,间隔太短算出来的 %util 可能超过 100%(多核磁盘控制器场景),需要做归一化处理。

2.4 网络指标采集

网络监控的核心是 /proc/net/dev。每行一个网络接口,记录收发包的统计。

2.4.1 关键字段

字段 含义 单位
bytes 收发字节数 字节
packets 收发包数 个
errs 错误包数 个
drop 丢弃包数 个
fifo FIFO 缓冲区溢出 个

2.4.2 Python 实现

def read_net_dev(interface='eth0'):
    with open('/proc/net/dev', 'r') as f:
        lines = f.readlines()
    # 跳过前两行标题
    for line in lines[2:]:
        parts = line.strip().split(':')
        if len(parts) == 2 and parts[0].strip() == interface:
            stats = parts[1].split()
            return {
                'rx_bytes': int(stats[0]),
                'rx_packets': int(stats[1]),
                'rx_errs': int(stats[2]),
                'rx_drop': int(stats[3]),
                'tx_bytes': int(stats[8]),
                'tx_packets': int(stats[9]),
                'tx_errs': int(stats[10]),
                'tx_drop': int(stats[11])
            }
    return None

def calc_net_speed(prev, curr, interval):
    rx_speed = (curr['rx_bytes'] - prev['rx_bytes']) / interval
    tx_speed = (curr['tx_bytes'] - prev['tx_bytes']) / interval
    return {
        'rx_kbps': round(rx_speed / 1024, 2),
        'tx_kbps': round(tx_speed / 1024, 2)
    }
注意:/proc/net/dev 里的字节数是累计值,会回绕(32 位系统大约 4GB 后回绕)。建议用 64 位系统,或者检测到数值变小的时候重置基线。

2.5 知识体系总览

下面这张图把四个维度的采集路径和关键指标串起来了。你写代码的时候可以照着这个结构来组织。

系统指标采集知识体系 CPU 指标 内存指标 磁盘指标 网络指标 /proc/stat /proc/meminfo /proc/diskstats /proc/net/dev 关键指标 • user / system • iowait / steal • load1/5/15 关键指标 • MemTotal • MemAvailable • Swap 使用率 关键指标 • 空间使用率 • r/s, w/s (IOPS) • await, %util 关键指标 • 带宽 (bps) • 丢包率 • 错误包数 采集方式:定时轮询 /proc 文件系统 输出:结构化指标数据 (JSON / 时序数据库)

2.6 采集器设计要点

写采集器的时候,有几个设计原则我建议你记住:

  1. 一次读取,多次计算:比如读一次 /proc/stat,把 CPU、上下文切换、进程数都算出来,别反复打开文件。
  2. 异常数据要过滤:偶尔会读到 0 或负数,直接跳过,不要往监控系统里塞脏数据。
  3. 采集间隔要可配置:CPU 和网络可以 10 秒一次,磁盘 I/O 建议 30 秒,内存变化慢,60 秒都行。
  4. 做好数据缓存:计算速率需要上一次的快照,用字典缓存起来,别每次都重新读。
最后说一句:这一章给的代码都是最简版本,生产环境里你还需要加异常处理、日志、配置管理。但核心原理就这些——读 /proc、算差值、输出结构化数据。把这一步做扎实了,后面的异常诊断才有可靠的数据基础。