文章

SP-01 · 硬盘健康可视化:将 SMART 数据转化为体检报告

#057 · 2026-04-18 · Python

引言:直击痛点

你有没有遇到过这样的场景:Mac 突然卡顿,打开"活动监视器"发现硬盘读写爆表,但系统自带的"磁盘工具"只能告诉你"验证成功",却看不到任何历史趋势?更糟糕的是,当你想向老板汇报"这台设备还能再战几年"时,只能拿出一堆冰冷的数字:138 TB 读取56.7 TB 写入寿命 1%……这些数字对非技术人员来说,就像天书。

今天我们要解决的痛点是:如何用 Python 将硬盘的 SMART 数据(读写量、寿命、温度、异常关机次数)转化为一张"一眼就懂"的健康报告图表?就像体检报告一样,用饼图、柱状图和颜色编码(绿色=健康,橙色=警告,红色=危险)让任何人都能秒懂硬盘状态。

核心比喻:硬盘就像一辆汽车,SMART 数据是它的"行车记录仪",而我们要做的,是把这些记录翻译成"保养建议单"


步步为营:核心逻辑拆解

Step 1:搭建 2×2 画布 —— 四象限布局的艺术

痛点解析:如果把 4 个指标(读写量、寿命、健康指标、使用统计)挤在一张图里,就像把 4 个人塞进一个电话亭,谁也看不清。我们需要一个"四宫格"布局,每个象限独立展示一个维度。

实战代码

import matplotlib.pyplot as plt


fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(12, 10))
fig.suptitle('硬盘健康状况报告 - Apple SSD AP2048Z', fontsize=16, fontweight='bold')

print("✓ 画布已创建:2 行 × 2 列 = 4 个独立展示区")

# 💻 终端预期输出:
# ✓ 画布已创建:2 行 × 2 列 = 4 个独立展示区

极客点评plt.subplots(2, 2) 就像在餐厅订了一个 4 人桌,每个人(每个指标)都有自己的位置,不会互相干扰。figsize=(12, 10) 确保这张"桌子"足够大,不会显得拥挤。


Step 2:读写数据量对比 —— 用柱状图讲故事

痛点解析:硬盘的读写比例能反映使用习惯:如果读取远大于写入(如 138 TB vs 56.7 TB),说明这是一台"消费型设备"(看视频、读文档多);如果写入接近读取,可能是"生产型设备"(剪视频、跑模型)。

实战代码

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(6, 4))

# 用不同颜色区分读写:绿色=读取(安全操作),蓝色=写入(磨损操作)
ax.bar(['累计读取', '累计写入'], [138, 56.7], color=['#4CAF50', '#2196F3'])
ax.set_ylabel('数据量 (TB)', fontsize=11)
ax.set_title('累计读写数据量', fontsize=12, fontweight='bold')

# 在柱子顶部标注具体数值(避免用户眯眼看坐标轴)
for i, v in enumerate([138, 56.7]):
    ax.text(i, v + 3, f'{v} TB', ha='center', fontweight='bold')

plt.tight_layout()
plt.savefig('step2_demo.png', dpi=150)
print("✓ 读写对比图已生成")

# 💻 终端预期输出:
# ✓ 读写对比图已生成

极客点评:这就像超市的收银小票,左边是"进货量"(读取),右边是"出货量"(写入)。绿色代表"只是看看"(读取不伤硬盘),蓝色代表"真金白银"(写入会消耗寿命)。


Step 3:寿命使用情况 —— 饼图的视觉冲击力

痛点解析:当你告诉老板"硬盘寿命还剩 99%",他可能无感。但如果你展示一个饼图,橙色只占 1%,灰色占 99%,他会立刻理解:“哦,这设备还很新!”

实战代码

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(6, 6))

# 饼图的精髓:用面积比例传达信息
sizes = [1, 99]  # 已使用 1%,剩余 99%
colors = ['#FF9800', '#E0E0E0']  # 橙色=警示,灰色=安全余量
labels = ['已使用 1%', '剩余 99%']

ax.pie(sizes, labels=labels, autopct='%1.0f%%', colors=colors, startangle=90)
ax.set_title('硬盘寿命使用情况', fontsize=12, fontweight='bold')

plt.savefig('step3_demo.png', dpi=150)
print("✓ 寿命饼图已生成:橙色区域越小越健康")

# 💻 终端预期输出:
# ✓ 寿命饼图已生成:橙色区域越小越健康

极客点评:饼图就像手机电量显示,1% 的橙色就像"低电量警告",但这里是反向的——橙色越小越好!startangle=90 让饼图从 12 点钟方向开始切,符合人类的阅读习惯。


Step 4:健康指标 —— 红绿灯式的状态编码

痛点解析:硬盘有 4 个关键健康指标:备用空间(100% 最佳)、数据错误(0 最佳)、介质错误(0 最佳)、温度(60°C 以下正常)。如果用表格展示,用户需要逐行对比;但如果用柱状图 + 颜色编码,一眼就能看出哪里有问题。

实战代码

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(8, 5))

indicators = ['剩余备用\n空间', '数据完整性\n错误', '介质\n错误', '温度\n(°C)']
values = [100, 0, 0, 61]
# 绿色=健康,黄色=需要关注
colors_ind = ['#4CAF50', '#4CAF50', '#4CAF50', '#FFC107']

bars = ax.bar(indicators, values, color=colors_ind)
ax.set_title('健康指标', fontsize=12, fontweight='bold')
ax.set_ylabel('数值', fontsize=11)

# 智能标注:百分比、温度、纯数字分别处理
for i, (bar, v) in enumerate(zip(bars, values)):
    label = f'{v}%' if i == 0 else (f'{v}°C' if i == 3 else str(v))
    ax.text(bar.get_x() + bar.get_width()/2, v + 2, label, 
            ha='center', fontweight='bold')

plt.tight_layout()
plt.savefig('step4_demo.png', dpi=150)
print("✓ 健康指标图已生成:绿色=安全,黄色=关注")

# 💻 终端预期输出:
# ✓ 健康指标图已生成:绿色=安全,黄色=关注

极客点评:这就像汽车仪表盘,绿灯亮表示"一切正常",黄灯亮表示"该保养了"。温度 61°C 虽然不算高,但用黄色提醒"别让它再热了"。


Step 5:使用统计 —— 揭示设备的"工作强度"

痛点解析:开机时长、电源循环次数、异常关机次数,这三个指标能反映设备的使用场景:如果异常关机次数多(如 17 次),可能是用户经常强制关机,或者电源不稳定。

实战代码

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(8, 5))

stats = ['开机时长\n(小时)', '电源循环\n(次)', '异常关机\n(次)']
stat_values = [957, 125, 17]
# 紫色=时长,蓝色=正常操作,红色=异常事件
colors_stat = ['#9C27B0', '#3F51B5', '#F44336']

ax.bar(stats, stat_values, color=colors_stat)
ax.set_title('使用统计', fontsize=12, fontweight='bold')
ax.set_ylabel('次数/小时', fontsize=11)

for i, v in enumerate(stat_values):
    ax.text(i, v + 20, str(v), ha='center', fontweight='bold')

plt.tight_layout()
plt.savefig('step5_demo.png', dpi=150)
print("✓ 使用统计图已生成:红色柱子越高越需要警惕")

# 💻 终端预期输出:
# ✓ 使用统计图已生成:红色柱子越高越需要警惕

极客点评:红色的"异常关机"柱子就像体检报告里的"血压偏高",虽然不致命,但需要改善使用习惯(比如别直接拔电源)。


极客实战:完整工程源码

"""
硬盘健康状况可视化报告生成器
功能:将 SMART 数据转化为 4 象限健康报告图表
适用场景:设备巡检、资产管理、向非技术人员汇报
"""
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from typing import List, Tuple

def generate_disk_health_report(
    read_tb: float = 138.0,
    write_tb: float = 56.7,
    life_used_percent: int = 1,
    spare_percent: int = 100,
    data_errors: int = 0,
    media_errors: int = 0,
    temperature_c: int = 61,
    power_on_hours: int = 957,
    power_cycles: int = 125,
    unsafe_shutdowns: int = 17,
    output_path: str = 'disk_health_report.png'
) -> None:
    """
    生成硬盘健康报告图表
    
    Args:
        read_tb: 累计读取数据量(TB)
        write_tb: 累计写入数据量(TB)
        life_used_percent: 寿命使用百分比
        spare_percent: 剩余备用空间百分比
        data_errors: 数据完整性错误次数
        media_errors: 介质错误次数
        temperature_c: 当前温度(摄氏度)
        power_on_hours: 累计开机时长(小时)
        power_cycles: 电源循环次数
        unsafe_shutdowns: 异常关机次数
        output_path: 输出文件路径
    """
    # 创建 2×2 子图布局
    fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(12, 10))
    fig.suptitle('硬盘健康状况报告 - Apple SSD AP2048Z', 
                 fontsize=16, fontweight='bold')

    # 1. 读写数据量对比
    ax1.bar(['累计读取', '累计写入'], [read_tb, write_tb], 
            color=['#4CAF50', '#2196F3'])
    ax1.set_ylabel('数据量 (TB)', fontsize=11)
    ax1.set_title('累计读写数据量', fontsize=12, fontweight='bold')
    for i, v in enumerate([read_tb, write_tb]):
        ax1.text(i, v + 3, f'{v} TB', ha='center', fontweight='bold')

    # 2. 寿命使用情况
    life_remaining = 100 - life_used_percent
    sizes = [life_used_percent, life_remaining]
    colors = ['#FF9800', '#E0E0E0']
    ax2.pie(sizes, labels=[f'已使用 {life_used_percent}%', 
                           f'剩余 {life_remaining}%'], 
            autopct='%1.0f%%', colors=colors, startangle=90)
    ax2.set_title('硬盘寿命使用情况', fontsize=12, fontweight='bold')

    # 3. 健康指标
    indicators = ['剩余备用\n空间', '数据完整性\n错误', '介质\n错误', '温度\n(°C)']
    values = [spare_percent, data_errors, media_errors, temperature_c]
    # 动态颜色:温度 > 70°C 变红,否则绿色/黄色
    colors_ind = [
        '#4CAF50',  # 备用空间
        '#4CAF50' if data_errors == 0 else '#F44336',  # 数据错误
        '#4CAF50' if media_errors == 0 else '#F44336',  # 介质错误
        '#4CAF50' if temperature_c < 60 else ('#FFC107' if temperature_c < 70 else '#F44336')
    ]
    bars = ax3.bar(indicators, values, color=colors_ind)
    ax3.set_title('健康指标', fontsize=12, fontweight='bold')
    ax3.set_ylabel('数值', fontsize=11)
    for i, (bar, v) in enumerate(zip(bars, values)):
        label = f'{v}%' if i == 0 else (f'{v}°C' if i == 3 else str(v))
        ax3.text(bar.get_x() + bar.get_width()/2, v + 2, label, 
                ha='center', fontweight='bold')

    # 4. 使用统计
    stats = ['开机时长\n(小时)', '电源循环\n(次)', '异常关机\n(次)']
    stat_values = [power_on_hours, power_cycles, unsafe_shutdowns]
    ax4.bar(stats, stat_values, color=['#9C27B0', '#3F51B5', '#F44336'])
    ax4.set_title('使用统计', fontsize=12, fontweight='bold')
    ax4.set_ylabel('次数/小时', fontsize=11)
    for i, v in enumerate(stat_values):
        ax4.text(i, v + 20, str(v), ha='center', fontweight='bold')

    # 保存图表
    plt.tight_layout()
    plt.savefig(output_path, dpi=300, bbox_inches='tight')
    print(f"✓ 图表已保存为: {output_path}")
    
    # 生成健康评分(0-100 分)
    health_score = calculate_health_score(
        life_used_percent, spare_percent, data_errors, 
        media_errors, temperature_c, unsafe_shutdowns
    )
    print(f"✓ 综合健康评分: {health_score}/100")


def calculate_health_score(
    life_used: int, spare: int, data_err: int, 
    media_err: int, temp: int, unsafe: int
) -> int:
    """计算硬盘综合健康评分"""
    score = 100
    score -= life_used  # 寿命每用 1% 扣 1 分
    score -= (100 - spare)  # 备用空间每少 1% 扣 1 分
    score -= data_err * 5  # 每个数据错误扣 5 分
    score -= media_err * 10  # 每个介质错误扣 10 分
    score -= max(0, (temp - 60) * 2)  # 温度超 60°C 每度扣 2 分
    score -= unsafe * 2  # 每次异常关机扣 2 分
    return max(0, score)


if __name__ == '__main__':
    # 使用默认参数生成报告
    generate_disk_health_report()
    
    # 或者自定义参数(模拟一个"亚健康"硬盘)
    # generate_disk_health_report(
    #     life_used_percent=15,
    #     temperature_c=75,
    #     unsafe_shutdowns=50,
    #     output_path='unhealthy_disk.png'
    # )

架构师结语

这个工具的核心价值不在于"画图",而在于将冰冷的数字转化为决策依据。当你向老板汇报"这台设备还能用 3 年"时,一张健康报告图表的说服力,远胜于一堆 SMART 日志。

记住三个工程心智:

  1. 可视化是沟通的桥梁:技术人员看数字,非技术人员看颜色。
  2. 颜色编码是视觉语言:绿色=放心,黄色=关注,红色=立刻处理。
  3. 参数化设计是可扩展性的基石:今天监控硬盘,明天可以监控服务器、网络设备,甚至员工的工作状态。

现在,去把你的 Mac 的 SMART 数据(可以用 smartctl -a /dev/disk0 获取)喂给这个脚本,生成你的第一份硬盘体检报告吧!