From 093c035fb2dff84139f2e3d16e4a2c463220d4e8 Mon Sep 17 00:00:00 2001 From: Yan Lin Date: Sun, 14 Sep 2025 14:20:55 +0200 Subject: [PATCH] Unify SMART report format --- scripts/daily-smart-report.sh | 82 ++++++++++------------------------- 1 file changed, 24 insertions(+), 58 deletions(-) diff --git a/scripts/daily-smart-report.sh b/scripts/daily-smart-report.sh index 06ab9f2..cdee6ec 100755 --- a/scripts/daily-smart-report.sh +++ b/scripts/daily-smart-report.sh @@ -64,19 +64,10 @@ main() { continue fi - # Detect drive type - local drive_type="UNKNOWN" + # Check if it's NVMe (for attribute parsing differences) + local is_nvme=false if [[ "$device" == *"nvme"* ]]; then - drive_type="NVMe" - 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 + is_nvme=true fi # Get SMART health @@ -91,7 +82,7 @@ main() { # Get enhanced SMART data local temp="N/A" local power_hours="N/A" - local wear_info="N/A" + local wear_info="" local data_info="" local error_info="" @@ -99,8 +90,8 @@ main() { local smart_data smart_data=$(smartctl -A "$device" 2>/dev/null) - if [[ "$drive_type" == "NVMe" ]]; then - # NVMe specific attributes + if [[ "$is_nvme" == "true" ]]; then + # NVMe attributes (different format) temp=$(echo "$smart_data" | awk '/^Temperature:/ {print $2}' | head -1) if [[ -n "$temp" && "$temp" =~ ^[0-9]+$ ]]; then temp="${temp}C" @@ -138,8 +129,10 @@ main() { error_info=$(IFS=' '; echo "${error_parts[*]}") fi - elif [[ "$drive_type" == "SSD" ]]; then - # SATA SSD - format similar to NVMe + else + # SATA/SAS drives - try to get all available attributes + + # Temperature temp=$(echo "$smart_data" | awk '/Temperature_Celsius/ {print $10}' | head -1) if [[ -n "$temp" && "$temp" =~ ^[0-9]+$ ]]; then temp="${temp}C" @@ -147,38 +140,35 @@ main() { temp="N/A" fi + # Power on hours 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 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) if [[ -n "$wear_level" ]]; then - # Wear_Leveling_Count: 100 = new, 0 = worn out percentage_used=$((100 - wear_level)) wear_info="Wear: ${percentage_used}%" elif [[ -n "$media_wearout" ]]; then - # Media_Wearout_Indicator: 100 = new, 0 = worn out percentage_used=$((100 - media_wearout)) wear_info="Wear: ${percentage_used}%" fi - # Data read/written - convert LBAs to human readable + # Data read/written local lbas_written lbas_read data_written data_read 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) if [[ -n "$lbas_written" && -n "$lbas_read" ]]; then # 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 gb_written_int=$((lbas_written / 2097152)) # LBAs * 512 / 1GB gb_read_int=$((lbas_read / 2097152)) # Format with appropriate units 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_dec=$(( (gb_written_int % 1024) * 10 / 1024 )) data_written="${tb_written_int}.${tb_written_dec} TB" @@ -187,7 +177,6 @@ main() { fi 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_dec=$(( (gb_read_int % 1024) * 10 / 1024 )) data_read="${tb_read_int}.${tb_read_dec} TB" @@ -198,12 +187,12 @@ main() { data_info="Data: R:${data_read} W:${data_written}" fi - # Check for errors (similar to NVMe's unsafe shutdowns and media errors) - local program_fail erase_fail reallocated power_cycles - 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) + # Check for various error indicators + local power_cycles reallocated pending_sectors offline_uncorrectable 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=() if [[ -n "$power_cycles" && "$power_cycles" -gt 0 ]]; then @@ -212,38 +201,15 @@ main() { if [[ -n "$reallocated" && "$reallocated" -gt 0 ]]; then error_parts+=("Reallocated:$reallocated") fi - if [[ -n "$program_fail" && "$program_fail" -gt 0 ]]; then - error_parts+=("ProgramFails:$program_fail") + if [[ -n "$pending_sectors" && "$pending_sectors" -gt 0 ]]; then + error_parts+=("PendingSectors:$pending_sectors") fi - if [[ -n "$erase_fail" && "$erase_fail" -gt 0 ]]; then - error_parts+=("EraseFails:$erase_fail") + if [[ -n "$offline_uncorrectable" && "$offline_uncorrectable" -gt 0 ]]; then + error_parts+=("OfflineUncorrectable:$offline_uncorrectable") fi if [[ ${#error_parts[@]} -gt 0 ]]; then error_info=$(IFS=' '; echo "${error_parts[*]}") 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 echo " Temperature: $temp" @@ -255,7 +221,7 @@ main() { # Format output if [[ "$health" == "PASSED" ]]; then - report+="[OK] $device_name ($drive_type): $health\\n" + report+="[OK] $device_name: $health\\n" report+=" Temp: $temp" if [[ "$power_hours" != "N/A" ]]; then report+=", PowerOn: ${power_hours}h" @@ -272,7 +238,7 @@ main() { fi healthy_drives=$((healthy_drives + 1)) else - report+="[FAIL] $device_name ($drive_type): $health\\n" + report+="[FAIL] $device_name: $health\\n" if [[ "$temp" != "N/A" ]]; then report+=" Temp: $temp\\n" fi