脚本怎样统计IO读写峰值数据

wen 实用脚本 30

脚本怎样统计IO读写峰值数据:从原理到实战的完整指南

目录导读

  1. IO峰值统计的核心价值:为什么需要监控IO峰值而非平均值?
  2. 底层原理与数据采集:/proc/diskstats 与 iostat 的解读
  3. 脚本实现方案:Bash、Python、Shell三套脚本详解
  4. 峰值识别算法:滑动窗口、百分位数与异常检测
  5. 性能优化与注意事项:避免过度监控拖垮系统
  6. 常见问题问答(Q&A)

IO峰值统计的核心价值

问:为什么IO峰值比平均值更重要?
答:以MySQL数据库为例,当平均IO写入为50MB/s时,如果峰值突发达到300MB/s,可能直接导致磁盘队列深度超标、查询延迟飙升甚至OOM,统计峰值能帮你:

脚本怎样统计IO读写峰值数据

  • 识别硬件瓶颈(如SSD写入寿命问题)
  • 优化应用层IO调度(如批量写入改为异步)
  • 容量规划(例如需要NVMe还是SATA SSD)

典型场景

  • 数据库备份时的突发写入监控
  • 视频转码服务对顺序读/写的冲击
  • 云服务器按IOPS计费时的成本分析

底层原理与数据采集

Linux系统中,IO统计的核心来源是 /proc/diskstats,每个磁盘设备的字段含义如下:

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

关键字段解释:

  • rio:读完成次数(累计)
  • wio:写完成次数
  • rsect:读扇区数(1扇区=512字节)
  • wsect:写扇区数
  • ruse:读IO耗时(毫秒累计)
  • wuse:写IO耗时

峰值计算逻辑

  1. 间隔 t 秒采样两次(如 t=1
  2. 差值计算:IOPS = (wio2 - wio1) / t
  3. 吞吐量:Bw = (wsect2 - wsect1) * 512 / t
  4. 持续记录最大值可达峰值

推荐采样间隔

  • 秒级(1秒):捕获瞬间峰值
  • 分钟级(60秒):辅助分析趋势

脚本实现方案

Bash脚本(轻量级)

#!/bin/bash
DEVICE="sda"
INTERVAL=1
OUTPUT="io_peak.log"
MAX_READ_IOPS=0
MAX_WRITE_IOPS=0
trap "echo 'Stopped'; exit" SIGINT
while true; do
    prev=( $(awk "/$DEVICE/ {print \$12,\$13}" /proc/diskstats) )
    # 只读写完成次数(字段12读,13写)
    sleep $INTERVAL
    curr=( $(awk "/$DEVICE/ {print \$12,\$13}" /proc/diskstats) )
    r_iops=$(( (${curr[0]} - ${prev[0]}) / INTERVAL ))
    w_iops=$(( (${curr[1]} - ${prev[1]}) / INTERVAL ))
    # 更新峰值
    [[ $r_iops -gt $MAX_READ_IOPS ]] && MAX_READ_IOPS=$r_iops
    [[ $w_iops -gt $MAX_WRITE_IOPS ]] && MAX_WRITE_IOPS=$w_iops
    echo "$(date) R_IOPS=$r_iops W_IOPS=$w_iops" >> $OUTPUT
    echo "Peak so far: R=$MAX_READ_IOPS W=$MAX_WRITE_IOPS"
done

优点:无需第三方工具,纯Bash
缺点:只能统计IOPS,无法计算吞吐量(需改用rsect/wsect

Python脚本(功能完整)

#!/usr/bin/env python3
import time, sys
def get_disk_stats(device):
    with open('/proc/diskstats', 'r') as f:
        for line in f:
            cols = line.split()
            if cols[2] == device:
                return {
                    'reads': int(cols[3]),    # rio
                    'writes': int(cols[7]),   # wio
                    'read_sectors': int(cols[5]),   # rsect
                    'write_sectors': int(cols[9])   # wsect
                }
    return None
def monitor(device='sda', interval=1, log_file='io_peak.log'):
    peak = {'riops':0, 'wiops':0, 'rbw':0, 'wbw':0}
    prev = get_disk_stats(device)
    if not prev:
        print("Device not found")
        sys.exit(1)
    try:
        while True:
            time.sleep(interval)
            curr = get_disk_stats(device)
            riops = (curr['reads'] - prev['reads']) // interval
            wiops = (curr['writes'] - prev['writes']) // interval
            rbw = (curr['read_sectors'] - prev['read_sectors']) * 512 // interval
            wbw = (curr['write_sectors'] - prev['write_sectors']) * 512 // interval
            # 峰值更新
            for k, v in [('riops',riops), ('wiops',wiops), ('rbw',rbw), ('wbw',wbw)]:
                if v > peak[k]: peak[k] = v
            # 记录
            log_line = f"{time.strftime('%Y%m%d_%H%M%S')} R_IOPS={riops} W_IOPS={wiops} R_BW={rbw} W_BW={wbw}"
            with open(log_file, 'a') as f:
                f.write(log_line + '\n')
            print(f"\rPeak: {peak}", end=' ')
            prev = curr
    except KeyboardInterrupt:
        print("\nFinal Peak:", peak)
if __name__ == '__main__':
    monitor(device='sda')

优点:时间精度高,支持多字段,易于扩展
安装:无需额外pip包

Shell结合iostat(建议生产环境)

#!/bin/bash
iostat -x 1 3600 | awk '
/NVM|sd|vd/ {   # 匹配常用磁盘设备
    if(NF>=14) {
        r_iops=$4; w_iops=$5; r_bw=$6; w_bw=$7;
        print strftime("%Y%m%d_%H%M%S"), r_iops, w_iops, r_bw, w_bw;
        # 峰值记录
        if(r_iops > max_r) max_r = r_iops;
        if(w_iops > max_w) max_w = w_iops;
    }
}
END { print "Peak: R_IOPS=" max_r " W_IOPS=" max_w }
' > io_peak.log &

注意:需安装sysstat


峰值识别算法

1 滑动窗口法

每N秒计算窗口内最大值,避免单点噪声:

window = collections.deque(maxlen=60)  # 保存60个采样点
while True:
    value = get_current_iops()
    window.append(value)
    peak_in_window = max(window)

2 百分位数法

P99/P95峰值比绝对最大值更有参考意义:

sorted_values = sorted(values)
p99_index = int(0.99 * len(sorted_values))
p99_peak = sorted_values[p99_index]

3 异常检测

当IOPS连续3次超过基线+20%时触发告警:

if (( current_iops > baseline * 1.2 && count++ >= 3 )); then
    echo "Abnormal IO peak detected"
fi

性能优化与注意事项

1 采样频率与精度

  • 1秒间隔:对系统CPU额外消耗约0.5%
  • 建议在非CPU敏感机器上运行
  • 如果监控全设备,使用/proc/diskstats单次读取所有磁盘

2 避免死循环导致日志爆炸

# 限制监控时长或添加日志轮转
timeout 7200 ./monitor.sh  # 最多监控2小时
logrotate -f /path/to/io_peak.log

3 多磁盘并行监控

threads = []
for dev in ['sda', 'sdb']:
    t = threading.Thread(target=monitor, args=(dev,))
    threads.append(t)
    t.start()

4 输出格式优化

  • 使用JSON格式便于接入Prometheus:
    echo "{\"timestamp\":\"$(date -Iseconds)\",\"device\":\"sda\",\"r_iops\":$riops,\"w_iops\":$wiops}"

常见问题问答(Q&A)

Q1:为什么脚本统计的峰值与iostat看到的峰值不同?
A:iostat默认使用1秒间隔,但会进行平均化处理,建议直接用脚本采集原始/proc/diskstats差值,避免iostat的过滤机制。

Q2:如何统计NVMe设备的峰值?
A:NVMe设备在/proc/diskstats中显示为nvme0n1,直接匹配即可,对于多命名空间,注意识别nvme0n1p1分区。

Q3:峰值数据如何可视化?
A:可输出到Prometheus的exporter格式,再通过Grafana展示,或者使用gnuplot脚本实时绘制图表。

Q4:防止脚本被误杀有哪些手段?
A:设置nohup + 系统服务:nohup ./monitor.sh &,或编写Systemd服务单元([Service] + Restart=always)。

Q5:统计IO读写峰值时,需要关注哪些指标?
A:重点关注:峰值IOPS(决定磁盘响应能力)、峰值带宽(决定传输能力)、峰值服务时间(决定延迟稳定性),建议同时记录时间戳便于事后回溯。


扩展阅读

  • Linux内核文档:Documentation/iostats.txt
  • 基于BPF的IO栈分析工具(bcc/bpftrace)可实时捕获每次IO的延迟分布

(全文共约1600字)

抱歉,评论功能暂时关闭!