Unify SMART report format

This commit is contained in:
Yan Lin 2025-09-14 14:20:55 +02:00
parent a00f20c10a
commit 093c035fb2

View file

@ -64,19 +64,10 @@ main() {
continue continue
fi fi
# Detect drive type # Check if it's NVMe (for attribute parsing differences)
local drive_type="UNKNOWN" local is_nvme=false
if [[ "$device" == *"nvme"* ]]; then if [[ "$device" == *"nvme"* ]]; then
drive_type="NVMe" is_nvme=true
else
# Check if it's SSD or HDD based on rotation rate
local smart_info
smart_info=$(smartctl -i "$device" 2>/dev/null)
if echo "$smart_info" | grep -q "Rotation Rate:.*Solid State Device\|Rotation Rate:.*0 rpm"; then
drive_type="SSD"
elif echo "$smart_info" | grep -q "Rotation Rate:.*[0-9][0-9][0-9][0-9] rpm"; then
drive_type="HDD"
fi
fi fi
# Get SMART health # Get SMART health
@ -91,7 +82,7 @@ main() {
# Get enhanced SMART data # Get enhanced SMART data
local temp="N/A" local temp="N/A"
local power_hours="N/A" local power_hours="N/A"
local wear_info="N/A" local wear_info=""
local data_info="" local data_info=""
local error_info="" local error_info=""
@ -99,8 +90,8 @@ main() {
local smart_data local smart_data
smart_data=$(smartctl -A "$device" 2>/dev/null) smart_data=$(smartctl -A "$device" 2>/dev/null)
if [[ "$drive_type" == "NVMe" ]]; then if [[ "$is_nvme" == "true" ]]; then
# NVMe specific attributes # NVMe attributes (different format)
temp=$(echo "$smart_data" | awk '/^Temperature:/ {print $2}' | head -1) temp=$(echo "$smart_data" | awk '/^Temperature:/ {print $2}' | head -1)
if [[ -n "$temp" && "$temp" =~ ^[0-9]+$ ]]; then if [[ -n "$temp" && "$temp" =~ ^[0-9]+$ ]]; then
temp="${temp}C" temp="${temp}C"
@ -138,8 +129,10 @@ main() {
error_info=$(IFS=' '; echo "${error_parts[*]}") error_info=$(IFS=' '; echo "${error_parts[*]}")
fi fi
elif [[ "$drive_type" == "SSD" ]]; then else
# SATA SSD - format similar to NVMe # SATA/SAS drives - try to get all available attributes
# Temperature
temp=$(echo "$smart_data" | awk '/Temperature_Celsius/ {print $10}' | head -1) temp=$(echo "$smart_data" | awk '/Temperature_Celsius/ {print $10}' | head -1)
if [[ -n "$temp" && "$temp" =~ ^[0-9]+$ ]]; then if [[ -n "$temp" && "$temp" =~ ^[0-9]+$ ]]; then
temp="${temp}C" temp="${temp}C"
@ -147,38 +140,35 @@ main() {
temp="N/A" temp="N/A"
fi fi
# Power on hours
power_hours=$(echo "$smart_data" | awk '/Power_On_Hours/ {print $10}' | head -1) power_hours=$(echo "$smart_data" | awk '/Power_On_Hours/ {print $10}' | head -1)
# Wear indicator - convert to percentage used (inverse of wear level) # Wear indicators (for SSDs)
local wear_level media_wearout percentage_used local wear_level media_wearout percentage_used
wear_level=$(echo "$smart_data" | awk '/Wear_Leveling_Count/ {print $4}' | head -1) wear_level=$(echo "$smart_data" | awk '/Wear_Leveling_Count/ {print $4}' | head -1)
media_wearout=$(echo "$smart_data" | awk '/Media_Wearout_Indicator/ {print $4}' | head -1) media_wearout=$(echo "$smart_data" | awk '/Media_Wearout_Indicator/ {print $4}' | head -1)
if [[ -n "$wear_level" ]]; then if [[ -n "$wear_level" ]]; then
# Wear_Leveling_Count: 100 = new, 0 = worn out
percentage_used=$((100 - wear_level)) percentage_used=$((100 - wear_level))
wear_info="Wear: ${percentage_used}%" wear_info="Wear: ${percentage_used}%"
elif [[ -n "$media_wearout" ]]; then elif [[ -n "$media_wearout" ]]; then
# Media_Wearout_Indicator: 100 = new, 0 = worn out
percentage_used=$((100 - media_wearout)) percentage_used=$((100 - media_wearout))
wear_info="Wear: ${percentage_used}%" wear_info="Wear: ${percentage_used}%"
fi fi
# Data read/written - convert LBAs to human readable # Data read/written
local lbas_written lbas_read data_written data_read local lbas_written lbas_read data_written data_read
lbas_written=$(echo "$smart_data" | awk '/Total_LBAs_Written/ {print $10}' | head -1) lbas_written=$(echo "$smart_data" | awk '/Total_LBAs_Written/ {print $10}' | head -1)
lbas_read=$(echo "$smart_data" | awk '/Total_LBAs_Read/ {print $10}' | head -1) lbas_read=$(echo "$smart_data" | awk '/Total_LBAs_Read/ {print $10}' | head -1)
if [[ -n "$lbas_written" && -n "$lbas_read" ]]; then if [[ -n "$lbas_written" && -n "$lbas_read" ]]; then
# Convert LBAs to GB (1 LBA = 512 bytes) # Convert LBAs to GB (1 LBA = 512 bytes)
# Using bash arithmetic (integer math) - divide by 2097152 to get GB (512 * LBAs / 1073741824)
local gb_written_int gb_read_int local gb_written_int gb_read_int
gb_written_int=$((lbas_written / 2097152)) # LBAs * 512 / 1GB gb_written_int=$((lbas_written / 2097152)) # LBAs * 512 / 1GB
gb_read_int=$((lbas_read / 2097152)) gb_read_int=$((lbas_read / 2097152))
# Format with appropriate units # Format with appropriate units
if [[ $gb_written_int -gt 1000 ]]; then if [[ $gb_written_int -gt 1000 ]]; then
# Convert to TB with one decimal place
local tb_written_int=$((gb_written_int / 1024)) local tb_written_int=$((gb_written_int / 1024))
local tb_written_dec=$(( (gb_written_int % 1024) * 10 / 1024 )) local tb_written_dec=$(( (gb_written_int % 1024) * 10 / 1024 ))
data_written="${tb_written_int}.${tb_written_dec} TB" data_written="${tb_written_int}.${tb_written_dec} TB"
@ -187,7 +177,6 @@ main() {
fi fi
if [[ $gb_read_int -gt 1000 ]]; then if [[ $gb_read_int -gt 1000 ]]; then
# Convert to TB with one decimal place
local tb_read_int=$((gb_read_int / 1024)) local tb_read_int=$((gb_read_int / 1024))
local tb_read_dec=$(( (gb_read_int % 1024) * 10 / 1024 )) local tb_read_dec=$(( (gb_read_int % 1024) * 10 / 1024 ))
data_read="${tb_read_int}.${tb_read_dec} TB" data_read="${tb_read_int}.${tb_read_dec} TB"
@ -198,12 +187,12 @@ main() {
data_info="Data: R:${data_read} W:${data_written}" data_info="Data: R:${data_read} W:${data_written}"
fi fi
# Check for errors (similar to NVMe's unsafe shutdowns and media errors) # Check for various error indicators
local program_fail erase_fail reallocated power_cycles local power_cycles reallocated pending_sectors offline_uncorrectable
program_fail=$(echo "$smart_data" | awk '/Program_Fail_Count/ {print $10}' | head -1)
erase_fail=$(echo "$smart_data" | awk '/Erase_Fail_Count/ {print $10}' | head -1)
reallocated=$(echo "$smart_data" | awk '/Reallocated_Sector_Ct/ {print $10}' | head -1)
power_cycles=$(echo "$smart_data" | awk '/Power_Cycle_Count/ {print $10}' | head -1) power_cycles=$(echo "$smart_data" | awk '/Power_Cycle_Count/ {print $10}' | head -1)
reallocated=$(echo "$smart_data" | awk '/Reallocated_Sector_Ct/ {print $10}' | head -1)
pending_sectors=$(echo "$smart_data" | awk '/Current_Pending_Sector/ {print $10}' | head -1)
offline_uncorrectable=$(echo "$smart_data" | awk '/Offline_Uncorrectable/ {print $10}' | head -1)
local error_parts=() local error_parts=()
if [[ -n "$power_cycles" && "$power_cycles" -gt 0 ]]; then if [[ -n "$power_cycles" && "$power_cycles" -gt 0 ]]; then
@ -212,38 +201,15 @@ main() {
if [[ -n "$reallocated" && "$reallocated" -gt 0 ]]; then if [[ -n "$reallocated" && "$reallocated" -gt 0 ]]; then
error_parts+=("Reallocated:$reallocated") error_parts+=("Reallocated:$reallocated")
fi fi
if [[ -n "$program_fail" && "$program_fail" -gt 0 ]]; then if [[ -n "$pending_sectors" && "$pending_sectors" -gt 0 ]]; then
error_parts+=("ProgramFails:$program_fail") error_parts+=("PendingSectors:$pending_sectors")
fi fi
if [[ -n "$erase_fail" && "$erase_fail" -gt 0 ]]; then if [[ -n "$offline_uncorrectable" && "$offline_uncorrectable" -gt 0 ]]; then
error_parts+=("EraseFails:$erase_fail") error_parts+=("OfflineUncorrectable:$offline_uncorrectable")
fi fi
if [[ ${#error_parts[@]} -gt 0 ]]; then if [[ ${#error_parts[@]} -gt 0 ]]; then
error_info=$(IFS=' '; echo "${error_parts[*]}") error_info=$(IFS=' '; echo "${error_parts[*]}")
fi fi
elif [[ "$drive_type" == "HDD" ]]; then
# HDD specific attributes
temp=$(echo "$smart_data" | awk '/Temperature_Celsius/ {print $10}' | head -1)
if [[ -n "$temp" && "$temp" =~ ^[0-9]+$ ]]; then
temp="${temp}C"
else
temp="N/A"
fi
power_hours=$(echo "$smart_data" | awk '/Power_On_Hours/ {print $10}' | head -1)
local reallocated
reallocated=$(echo "$smart_data" | awk '/Reallocated_Sector_Ct/ {print $10}' | head -1)
if [[ -n "$reallocated" ]]; then
wear_info="Reallocated:$reallocated"
fi
local power_cycles
power_cycles=$(echo "$smart_data" | awk '/Power_Cycle_Count/ {print $10}' | head -1)
if [[ -n "$power_cycles" ]]; then
data_info="PowerCycles:$power_cycles"
fi
fi fi
echo " Temperature: $temp" echo " Temperature: $temp"
@ -255,7 +221,7 @@ main() {
# Format output # Format output
if [[ "$health" == "PASSED" ]]; then if [[ "$health" == "PASSED" ]]; then
report+="[OK] $device_name ($drive_type): $health\\n" report+="[OK] $device_name: $health\\n"
report+=" Temp: $temp" report+=" Temp: $temp"
if [[ "$power_hours" != "N/A" ]]; then if [[ "$power_hours" != "N/A" ]]; then
report+=", PowerOn: ${power_hours}h" report+=", PowerOn: ${power_hours}h"
@ -272,7 +238,7 @@ main() {
fi fi
healthy_drives=$((healthy_drives + 1)) healthy_drives=$((healthy_drives + 1))
else else
report+="[FAIL] $device_name ($drive_type): $health\\n" report+="[FAIL] $device_name: $health\\n"
if [[ "$temp" != "N/A" ]]; then if [[ "$temp" != "N/A" ]]; then
report+=" Temp: $temp\\n" report+=" Temp: $temp\\n"
fi fi