一、为什么要关心硬件资源利用率
做性能测试的时候,我们最常听到的问题是:“系统TPS上不去,是不是服务器太差了?”但“服务器太差”到底是差在哪儿?是CPU不够快?内存不够大?还是硬盘读写太慢?如果不去看硬件资源利用率,就只能靠猜。猜来猜去,最后浪费了时间,还可能花冤枉钱买不对硬件。
打个比方,服务器就像是个小餐馆。CPU是厨师,负责炒菜;内存是备菜台,食材先放那里;磁盘是仓库,冻肉和存货都在里面;网络则是传菜通道。客人多的时候,哪个环节最先撑不住?可能是厨师累得满头大汗,炒不过来(CPU饱和);也可能是备菜台堆满了没地方放菜(内存不足);还有可能是仓库取货太慢,厨师等着肉解冻(磁盘慢);再或者传菜员在过道里挤成一团(网络拥堵)。只有定位到具体是哪个环节顶不住了,才能针对性地升级。
应用场景其实很广:新系统上线前的容量评估、云服务器选型时对比不同规格、老服务器性能优化、硬件升级后的效果验收,都需要用到资源利用率分析。尤其是“硬件升级效果验证”这个场景,很多团队以为升完级就完事了,不对比数据,结果升了也白升。
二、把数据拿到手:先学会“体检”
2.1 用Python一秒钟看到当前状态
Python里有一个特别好用的库叫psutil,它把操作系统的各种资源信息都封装成了简单函数。在写监控脚本之前,我们可以先用它快速查看当前状态。下面这个示例展示了获取CPU、内存、磁盘、网络IO的快照,注意磁盘和网络返回的是累计值,不是实时速率。
# 技术栈:Python 3.8+,需要安装psutil库(pip install psutil)
import psutil
# CPU使用率,interval=1表示等1秒再取平均值,能反映真实负载
cpu = psutil.cpu_percent(interval=1)
print(f"整机CPU使用率: {cpu}%")
# 每个CPU核的使用率,方便看多核是否均衡
per_core = psutil.cpu_percent(interval=1, percpu=True)
print(f"每个核的使用率: {per_core}")
# 内存情况,virtual_memory()返回一个命名元组
mem = psutil.virtual_memory()
print(f"物理内存总量: {mem.total / (1024**3):.2f} GB")
print(f"物理内存已用: {mem.used / (1024**3):.2f} GB")
print(f"内存使用率: {mem.percent}%")
# 磁盘IO计数器,read_bytes是从开机到现在的累计值
disk = psutil.disk_io_counters()
print(f"磁盘累计读: {disk.read_bytes / (1024**2):.2f} MB")
print(f"磁盘累计写: {disk.write_bytes / (1024**2):.2f} MB")
# 网络IO,同样也是累计值
net = psutil.net_io_counters()
print(f"网卡累计发送: {net.bytes_sent / (1024**2):.2f} MB")
print(f"网卡累计接收: {net.bytes_recv / (1024**2):.2f} MB")
看到这里,你可能已经明白:psutil就像是一个医疗听诊器,各种数值都能一把抓。不过只有一次快照还不够,性能测试往往持续很长时间,我们需要连续采集。
2.2 写一个采集脚本,把数据存起来
考虑到性能测试一般跑5分钟到几小时,我建议写一个循环,每隔几秒采一次,并且把数据存成CSV文件。CSV格式简单,Excel里能直接打开,后期用Python分析也方便。
# 技术栈:Python 3.8+,依赖psutil库
import psutil
import time
import csv
from datetime import datetime
def collect_once():
"""采集一次系统资源数据,返回一个字典"""
data = {}
data['timestamp'] = datetime.now().strftime('%H:%M:%S')
# CPU使用率,这里如果不传interval,psutil会返回上次调用后的差值,速度更快
data['cpu_percent'] = psutil.cpu_percent(interval=None)
# 内存
mem = psutil.virtual_memory()
data['mem_percent'] = mem.percent
data['mem_used_gb'] = round(mem.used / (1024**3), 2)
# 磁盘IO累计值(单位MB)
disk = psutil.disk_io_counters()
data['disk_read_mb'] = round(disk.read_bytes / (1024**2), 2)
data['disk_write_mb'] = round(disk.write_bytes / (1024**2), 2)
# 网络IO累计值(单位MB)
net = psutil.net_io_counters()
data['net_sent_mb'] = round(net.bytes_sent / (1024**2), 2)
data['net_recv_mb'] = round(net.bytes_recv / (1024**2), 2)
# 额外加一个CPU的iowait百分比,Linux下表示等待磁盘IO的时间占比
cpu_times = psutil.cpu_times()
# iowait只在Linux有,别的系统可能没有该属性,用getattr避免报错
iowait = getattr(cpu_times, 'iowait', None)
if iowait is not None:
# 计算iowait在所有CPU时间中的占比,实际上是比例,这里转成百分数
total = sum(cpu_times._asdict().values())
data['iowait_percent'] = round(iowait / total * 100, 2)
else:
data['iowait_percent'] = 0.0
return data
def run_monitor(duration_sec=60, interval_sec=5, output_file='resource_data.csv'):
"""持续监控duration_sec秒,每隔interval_sec秒采一次,写入CSV"""
fieldnames = ['timestamp', 'cpu_percent', 'mem_percent', 'mem_used_gb',
'disk_read_mb', 'disk_write_mb', 'net_sent_mb', 'net_recv_mb',
'iowait_percent']
# 注意'w'模式会覆盖已有文件,每次测试前最好先备份旧数据
with open(output_file, 'w', newline='') as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
start = time.time()
# 只要还没到时长,就一直循环
while time.time() - start < duration_sec:
row = collect_once()
writer.writerow(row)
# 打印当前行,方便实时盯屏
print(f"采样: {row}")
time.sleep(interval_sec)
print(f"采集完成,结果保存在 {output_file}")
if __name__ == '__main__':
# 示例:采集60秒,每5秒一次
run_monitor(duration_sec=60, interval_sec=5)
这段脚本已经可以应对大部分场景。不过要提醒大家:采集间隔不要太短,否则监控脚本自身就会占用CPU和磁盘,干扰被测系统。一般5秒一次足够,如果压测时间短(比如1分钟),可以改成2秒一次。
2.3 真正关心的是“速率”,不是“累计值”
上面的CSV里存的是磁盘累计读了多少字节、网络累计收发多少字节。累计值只会往上涨,看不出当前读写快慢。所以我们需要在分析时做差:用后一个值减前一个值,再除以时间间隔,得到这段时间内的平均速率。下面的示例演示了如何实时计算磁盘读写速率。
# 技术栈:Python 3.8+,依赖psutil
import psutil
import time
def get_disk_bytes():
"""获取磁盘累计读写的字节数"""
io = psutil.disk_io_counters()
return io.read_bytes, io.write_bytes
def get_io_rate(interval=1):
"""计算interval秒内的磁盘平均读写速率,返回MB/s"""
# 先取第一次值
r1, w1 = get_disk_bytes()
time.sleep(interval) # 等一下
# 再取第二次值
r2, w2 = get_disk_bytes()
read_rate = (r2 - r1) / interval / (1024**2) # 字节/秒 -> MB/秒
write_rate = (w2 - w1) / interval / (1024**2)
return read_rate, write_rate
if __name__ == '__main__':
# 采样2秒,得到平均速率
r, w = get_io_rate(2)
print(f"磁盘读速率: {r:.2f} MB/s")
print(f"磁盘写速率: {w:.2f} MB/s")
# 同样是累计值,也可以套用到网络IO上,大家可以自己写一个
这种“两次采样作差”的思路,在分析性能数据时非常重要。不管是磁盘、网络还是计数器,最终都要变成“每秒钟能处理多少”才有意义。
三、拿到数据后,怎么判断瓶颈在哪儿
数据是一串数字,怎么从中读出故事?下面说几个最常见的判断规则。
3.1 看CPU:超过80%就亮黄灯
CPU使用率超过80%,说明系统已经忙得快喘不过气了。如果此时磁盘和网络都很闲,那就是CPU在硬扛。比如数据压缩、图像处理、复杂的业务逻辑,都是吃CPU的。这时候升级CPU(加核数或换更快的型号)往往立竿见影。但要注意,如果应用只有一个线程在工作,那加再多的核也没用,得先改代码把任务拆成多线程。
3.2 看内存:swap就像“最后的备胎”
系统内存不够时,会把一部分不常用的数据挪到磁盘上,这就是swap。虽然看起来能“再顶一顶”,但磁盘比内存慢几个数量级,一发生大量swap,性能立刻崩塌。怎么判断内存够不够?看两个指标:内存使用率是否长期在95%以上,以及swap的换入换出量是否持续非零。psutil.swap_memory()可以直接看到si和so,也就是每秒从磁盘换入换出的量。如果这两个值不为0,请尽快加内存。
3.3 看磁盘:等不到IO,所有线程都会卡住
磁盘慢常常表现为CPU的iowait很高。比如我们在采集脚本中记录了iowait_percent,如果这个值持续大于10%,说明CPU有很多时间在等待磁盘干活。具体磁盘是不是“满负荷”,可以看实际读写速率是否接近硬件上限。举例:一块普通SATA固态硬盘,顺序读写也就500MB/s左右;机械硬盘就更慢,100多MB/s。如果速率连续几次都贴着上限,那就是磁盘瓶颈。
3.4 看网络:带宽占满可不只是快慢问题
网络IO和磁盘一样,先看速率。如果发送和接收速率接近网卡上限(千兆网卡的理论上限大约110MB/s),网络就会成为瓶颈。表现就是请求大量排队,响应时间变长。这时升级万兆网卡、用多网卡绑定,或者调整系统里的网络缓冲区大小,都是可以尝试的方向。
3.5 综合看:CPU和内存都很高,磁盘很低,说明什么?
假设我们负责的是一个数据库服务。测试时发现CPU使用率90%,内存使用率98%,磁盘读写速率却只有10MB/s。这说明数据基本都在内存里,CPU忙着处理逻辑,但内存快满了。等内存满了,系统就会开始用swap,磁盘也会突然忙起来。所以现在的短板其实是内存,而不是CPU。如果只加CPU,内存不足的问题依然在,性能提升会很有限。这告诉我们:要同时看多项指标,互相印证,别只看一个数字就下结论。
四、硬件升级效果验证:不靠感觉,靠数据
很多团队在升级完硬件后,只会说“感觉快了不少”。但感觉会骗人,而且业务繁忙程度、测试时间不同都会影响判断。我们得设计一套可重复的验证方案。
4.1 升级前,先打一个“基线”
想象一个跑步测试:想知道一双新跑鞋有没有用,你得先在旧跑鞋下跑一次,记录成绩。硬件升级也一样,升级前必须跑一轮压测,记录下所有业务指标(TPS、响应时间、错误率)和资源利用率数据,作为基线。没有基线,后面的一切对比都是耍流氓。基线测试最好重复3次,取平均值和波动范围,避免单次偶然误差。
4.2 控制变量:一次只动一个硬件
A/B测试的精髓在于“只改一个变量”。如果你想验证内存升级的效果,那就只换内存,其他CPU、磁盘、网络都保持不变,软件版本、压测脚本、并发数也要完全一致。如果又加内存又换SSD,到时候两项指标都变了,你很难分清是哪个的功劳。
4.3 用Python对比升级前后的指标
假设我们升级了内存,在相同压力下记录了三个TPS样本,用下面这段代码算平均值和提升百分比。
# 技术栈:Python 3.8+,使用内置statistics模块
import statistics
def show_improvement(before, after, metric_name="TPS"):
"""对比升级前后的指标列表,打印平均值和变化率"""
avg_b = statistics.mean(before)
avg_a = statistics.mean(after)
# 提升百分比,注意如果avg_b为0需要特殊处理,这里假定不会是0
change = (avg_a - avg_b) / avg_b * 100
print(f"—— {metric_name}对比 ——")
print(f"升级前各次值: {before}")
print(f"升级后各次值: {after}")
print(f"升级前平均值: {avg_b:.2f}")
print(f"升级后平均值: {avg_a:.2f}")
print(f"提升比例: {change:.2f}%")
# 同时看看波动情况,用标准差表示稳定性
std_b = statistics.stdev(before)
std_a = statistics.stdev(after)
print(f"升级前标准差: {std_b:.2f},升级后标准差: {std_a:.2f}")
# 模拟数据:升级前三次测试的TPS
tps_before = [1200, 1180, 1220]
# 升级后三次测试的TPS
tps_after = [1550, 1610, 1580]
show_improvement(tps_before, tps_after, "TPS")
运行后你会发现提升比例大约在32%左右,而且标准差不比之前大,说明提升是稳定的。但这里有个陷阱:样本量只有3,可能是随机波动。所以我们要引入一点点统计学。
4.4 用配对t检验确认差异是否显著
配对t检验适合“同一个对象在两种条件下的测量值”这类场景,比如同一台服务器升级前后分别测得的指标。下面的示例使用scipy库来判断差异是否具有统计显著性。
# 技术栈:Python 3.8+,需要安装scipy(pip install scipy)
from scipy import stats
# 升级前后各收集5次响应时间(毫秒)
before = [235, 241, 238, 244, 232] # 升级前5轮压测的平均响应时间
after = [210, 215, 208, 213, 212] # 升级后5轮压测的平均响应时间
# 进行配对t检验,零假设是“升级前后响应时间没有差别”
t_stat, p_value = stats.ttest_rel(after, before)
print(f"t统计量: {t_stat:.4f}")
print(f"p值: {p_value:.4f}")
# 通常取p<0.05为显著差异
if p_value < 0.05:
print("结论:升级前后差异显著,升级有效果")
else:
print("结论:差异不显著,需要更多测试数据或检查实验过程")
注意,p值小只说明“差异不是偶然造成的”,但能不能带来业务收益,还得看差值大小。如果响应时间只少了5毫秒,用户根本感知不到,那升级的投入产出比可能就不高。建议同时检查置信区间,这里就不再展开例子了,大家了解思路即可。
4.5 别忘了长稳测试
很多硬件问题要持续运行一段时间才暴露。比如内存泄漏、散热不良导致性能下降等。所以升级后最好再跑一个小时以上的稳定性测试,一边压测一边用前面的采集脚本记录资源利用率。如果发现CPU使用率缓慢爬升,或者iowait越来越高,说明环境仍不正常,不要急着收工。
五、这套分析方法的优缺点
5.1 优点
- 直观:一张CSV表里就能看到所有资源指标的走势,比人肉盯屏幕靠谱。
- 可复现:只要固定监控脚本和压测参数,别人也能照做,方便团队协作。
- 成本低:psutil是开源库,Python也是免费的,不用买商业监控软件。
- 数据可追溯:采集下来的数据可以留档,以后出问题还能重新翻出来分析。
5.2 缺点
- 有一定侵入性:监控脚本自己也会消耗CPU、内存和磁盘,如果采集频率太高,会干扰测试结果。
- 粒度有限:psutil拿到的多是系统级聚合数据,想深入进程级、线程级的跟踪,还需要配合其他工具,比如py-spy、perf等。
- 判断阈值需要经验:本文提到的80%、10%都属于经验值,实际业务要结合自家情况调整。
- 依赖环境:iowait字段在Windows上不存在,跨平台使用时需要写兼容代码。
六、注意事项和常见坑
这里再唠叨几个实际工作中容易踩的坑,帮你少走弯路。
- 测试前清理后台任务:Windows的Windows Update、Linux的cron任务、监控系统本身的采集器,都可能突然占用资源,干扰数据。
- 压测机也要监控:服务器资源利用率好看了,但压力机自己CPU爆了,请求发不出去,结果照样不可信。
- 注意采样间隔与压测持续时间的匹配:如果压测只跑1分钟,却10秒才采一次,一共才6个点,统计意义大打折扣。建议至少采30个点。
- 升级后确认系统真的识别了新硬件:比如加了内存,用free -h看总内存是不是变了;换了SSD,看磁盘型号和分区是否正常。有时候硬件插得不好,系统压根没认出来。
- 别忽视软件层面的优化:硬件升级毕竟要花钱,有时改个配置、换个算法反而更有效。分析资源利用率的最终目的是寻找真瓶颈,能软解决的优先软解决。
七、总结
硬件资源利用率分析是性能测试里最实在的一环,它能把“感觉慢”变成“哪个资源慢”。通过psutil采集数据、用CSV记录下来,再结合平均值、峰值和t检验,我们就能科学地评估硬件升级的效果。记住,永远要先测基线再升级,升级后做A/B对比,同时多测几轮消除误差。只有这样,你向领导汇报“升级后TPS提升了30%”时,才敢拍着胸脯保证数据是准的。希望大家在自己的项目里也试试这套方法,少花冤枉钱,少熬无意义的夜。
评论
围绕“性能测试中硬件资源利用率的分析方法及硬件升级的效果验证方案”参与讨论