#!/bin/bash
# smart_monitor.sh
# 支持SAS和SATA硬盘的SMART监控，带详情汇总（精简输出版）
# apt install smartmontools jq
set -eo pipefail  # 严格模式，增强错误检测

LOGDIR="/mnt/smart/smart_monitor"
mkdir -p "$LOGDIR"

# 用于记录所有设备的监控结果（供最后汇总）
declare -a MONITORED_DEVICES=()  # 存储已监控的设备列表
declare -A DEVICE_SUMMARY=()     # 存储每个设备的关键信息汇总

# 检查依赖工具
check_dependencies() {
    local missing=0
    for tool in smartctl jq; do
        if ! command -v "$tool" &>/dev/null; then
            echo "[ERROR] 缺少必要工具: $tool，请先安装"
            missing=1
        fi
    done
    if (( missing == 1 )); then
        exit 1
    fi
}

# 关键指标定义（英文与中文映射）
declare -A KEY_ATTRS=(
  ["Reallocated_Sector_Ct"]="重映射扇区数"
  ["Power_On_Hours"]="通电时间(小时)"
  ["Spin_Retry_Count"]="重试旋转次数"
  ["End-to-End_Error"]="端到端错误数"
  ["Reported_Uncorrect"]="不可纠正错误数"
  ["Command_Timeout"]="命令超时次数"
  ["Airflow_Temperature_Cel"]="气流温度(摄氏度)"
  ["Temperature_Celsius"]="温度(摄氏度)"
  ["Current_Pending_Sector"]="当前待映射扇区数"
  ["Offline_Uncorrectable"]="离线不可纠正扇区数"
  ["UDMA_CRC_Error_Count"]="UDMA CRC错误数"
  ["Non-medium_error_count"]="非介质错误数"
  ["Start_Stop_Cycles"]="启停次数"
  ["Load_Unload_Cycles"]="加载卸载次数"
  ["Grown_Defect_Count"]="增长缺陷数"
)

# 读取单盘SMART数据，输出json格式（中文字段）
get_smart_json() {
  local dev="$1"
  local type=""
  local json="{"
  local smart_output=""

  # 获取硬盘类型（SAS/SATA，优化检测逻辑）
  # 1. 优先检测SAS协议
  type=$(smartctl -i "$dev" 2>/dev/null | grep -i 'Transport protocol' | awk '{print $3}')
  # 2. 未检测到则尝试SATA标识
  if [[ -z "$type" ]]; then
    if smartctl -i "$dev" 2>/dev/null | grep -qi 'SATA Version'; then
      type="SATA"
    fi
  fi
  # 3. 仍未检测到则尝试接口类型
  if [[ -z "$type" ]]; then
    type=$(smartctl -i "$dev" 2>/dev/null | grep -i 'Interface' | awk '{print $2}')
  fi
  # 4. 最终无法识别则标记为未知
  [[ -z "$type" ]] && type="未知"
  
  echo "$type" > "$LOGDIR/.tmp_${dev//\//_}_type"  # 临时保存硬盘类型

  # 获取健康状态
  local health
  health=$(smartctl -H "$dev" 2>/dev/null | grep -Ei "SMART (overall-health|status|Health Status)" | awk -F': ' '{gsub(/^ +| +$/, "", $2); print $2}')
  [[ -z "$health" ]] && health="UNKNOWN"
  # 健康状态中文映射
  case "$health" in
    "PASSED") health="正常" ;;
    "FAILED") health="失败" ;;
    "OK") health="正常" ;;
    *) health="未知($health)" ;;
  esac
  json+="\"健康状态\":\"$health\","

  # 获取完整SMART数据
  smart_output=$(smartctl -a "$dev" 2>/dev/null)
  if [[ -z "$smart_output" ]]; then
    echo "[WARN] 设备 $dev 不支持SMART或无权限访问" >&2
    json+="\"错误\":\"不支持SMART\"}"
    echo "$json"
    return
  fi

  # 根据硬盘类型采集指标
  if [[ "$type" == "SAS" ]]; then
    # SAS硬盘指标采集
    local nonmed=$(echo "$smart_output" | grep -i "Non-medium error count" | awk '{print $4}')
    local temp=$(echo "$smart_output" | grep -i "Current Drive Temperature" | awk '{print $4}')
    local realloc=$(echo "$smart_output" | grep -i "Reallocated_Sector_Ct" | awk '{print $10}')
    
    local power_on_hours=$(echo "$smart_output" | grep -i "Accumulated power on time, hours:minutes" | awk '{print $6}' | cut -d: -f1)
    local start_stop=$(echo "$smart_output" | grep -i "Accumulated start-stop cycles" | awk '{print $4}')
    local load_unload=$(echo "$smart_output" | grep -i "Accumulated load-unload cycles" | awk '{print $4}')
    local grown_defects=$(echo "$smart_output" | grep -i "Elements in grown defect list" | awk '{print $5}')

    # 处理空值和非数字值
    [[ -z "$nonmed" || ! "$nonmed" =~ ^[0-9]+$ ]] && nonmed=0
    [[ -z "$temp" || ! "$temp" =~ ^[0-9]+$ ]] && temp=0
    [[ -z "$realloc" || ! "$realloc" =~ ^[0-9]+$ ]] && realloc=0
    [[ -z "$power_on_hours" || ! "$power_on_hours" =~ ^[0-9]+$ ]] && power_on_hours=0
    [[ -z "$start_stop" || ! "$start_stop" =~ ^[0-9]+$ ]] && start_stop=0
    [[ -z "$load_unload" || ! "$load_unload" =~ ^[0-9]+$ ]] && load_unload=0
    [[ -z "$grown_defects" || ! "$grown_defects" =~ ^[0-9]+$ ]] && grown_defects=0

    # 组装JSON（使用中文键名）
    json+="\"${KEY_ATTRS["Non-medium_error_count"]}\":$nonmed,"
    json+="\"${KEY_ATTRS["Temperature_Celsius"]}\":$temp,"
    json+="\"${KEY_ATTRS["Reallocated_Sector_Ct"]}\":$realloc,"
    json+="\"${KEY_ATTRS["Power_On_Hours"]}\":$power_on_hours,"
    json+="\"${KEY_ATTRS["Start_Stop_Cycles"]}\":$start_stop,"
    json+="\"${KEY_ATTRS["Load_Unload_Cycles"]}\":$load_unload,"
    json+="\"${KEY_ATTRS["Grown_Defect_Count"]}\":$grown_defects"

  else
    # SATA硬盘指标采集
    local attr_count=0
    for eng_attr in "${!KEY_ATTRS[@]}"; do
      # 跳过SAS专用指标
      if [[ "$eng_attr" =~ (Start_Stop_Cycles|Load_Unload_Cycles|Grown_Defect_Count) ]]; then
        continue
      fi
      
      grep_key=$(echo "$eng_attr" | sed 's/_/[ _]/g')
      val=$(echo "$smart_output" | grep -iE "$grep_key" | awk '{print $10}' | head -1)
      
      if [[ -z "$val" || ! "$val" =~ ^[0-9]+$ ]]; then
        val=0
        # 只对非SATA设备或非SAS专用指标输出DEBUG日志
        if [[ "$type" != "SATA" || "$eng_attr" != "Non-medium_error_count" ]]; then
          echo "[DEBUG] 设备 $dev 未找到指标 $eng_attr（${KEY_ATTRS[$eng_attr]}），默认设为0" >&2
        fi
      else
        ((attr_count++))
      fi
      
      json+="\"${KEY_ATTRS[$eng_attr]}\":$val,"
    done

    if (( attr_count == 0 )); then
      echo "[WARN] 设备 $dev 未获取到任何SMART指标" >&2
    fi

    # 去除末尾逗号
    json="${json%,}"
  fi

  json+="}"
  echo "$json"
}

# 对比两个json文件，打印变化（适配中文字段）
compare_json() {
  local oldfile="$1"
  local newfile="$2"
  local dev="$3"
  local alert=0  # 0:无异常 1:有异常
  local changes=()  # 记录当前设备的所有变化
  local old_data=$(cat "$oldfile" 2>/dev/null)
  local new_data=$(cat "$newfile" 2>/dev/null)

  # 先判断数据是否完全一致
  if [[ "$old_data" == "$new_data" ]]; then
    # 数据无变化，只打印简单提示
    echo "[DEBUG] 对比设备 $dev：数据无变化"
    return 0
  fi

  # 数据有变化时才打印详细对比
  echo "[DEBUG] 对比设备 $dev"
  echo "[DEBUG] 旧数据: $old_data"
  echo "[DEBUG] 新数据: $new_data"

  # 中文关键字段与英文对应关系（用于告警判断）
  declare -A CHINESE_TO_ENGLISH=(
    ["重映射扇区数"]="Reallocated_Sector_Ct"
    ["当前待映射扇区数"]="Current_Pending_Sector"
    ["离线不可纠正扇区数"]="Offline_Uncorrectable"
    ["非介质错误数"]="Non-medium_error_count"
    ["不可纠正错误数"]="Reported_Uncorrect"
    ["UDMA CRC错误数"]="UDMA_CRC_Error_Count"
    ["增长缺陷数"]="Grown_Defect_Count"
    ["命令超时次数"]="Command_Timeout"
  )

  local keys=$(jq -r 'keys[]' "$newfile" 2>/dev/null)
  [[ -z "$keys" ]] && { echo "[ERROR] 新数据格式错误"; return 1; }

  for k in $keys; do
    oldv=$(jq -r --arg k "$k" '.[$k] // "N/A"' "$oldfile" 2>/dev/null)
    newv=$(jq -r --arg k "$k" '.[$k] // "N/A"' "$newfile" 2>/dev/null)

    if [[ "$k" == "健康状态" ]]; then
      if [[ "$oldv" != "$newv" && "$oldv" != "N/A" ]]; then
        local msg="[ALERT] $dev SMART健康状态变化: $oldv -> $newv"
        echo "$msg"
        changes+=("$msg")
        alert=1
      fi
      continue
    fi

    [[ "$k" == "错误" ]] && continue

    if [[ "$oldv" =~ ^[0-9]+$ && "$newv" =~ ^[0-9]+$ ]]; then
      diff=$((newv - oldv))
      if (( diff > 0 )); then
        # 检查是否是需要特殊处理的指标
        if [[ "$k" == "命令超时次数" && $diff -gt 1000 ]]; then
          local msg="[WARN] $dev SMART指标 $k 急剧增加 $diff (从 $oldv 到 $newv)"
          echo "$msg"
          changes+=("$msg")
          alert=1
        elif [[ -n "${CHINESE_TO_ENGLISH[$k]}" ]]; then
          local msg="[WARN] $dev SMART指标 $k 增加了 $diff (从 $oldv 到 $newv)"
          echo "$msg"
          changes+=("$msg")
          alert=1
        fi
      fi
    fi
  done

  # 记录当前设备的变化（用于最后汇总）
  DEVICE_SUMMARY["$dev"]="${changes[*]}"
  
  return $alert  # 关键：返回1表示有异常，0表示无异常
}

# 主监控流程
monitor_device() {
  local dev="$1"
  local date_str=$(date +%Y%m%d)
  
  # 提取设备名称（如从"/dev/sdb"中获取"sdb"）
  local dev_name=$(basename "$dev")
  
  # 生成文件名
  local lastfile="$LOGDIR/smart_${dev_name}_last.json"
  local newfile="$LOGDIR/smart_${dev_name}_${date_str}.json"

  echo "[INFO] 开始采集 $dev SMART数据..."
  local jsondata=$(get_smart_json "$dev")
  
  if ! jq . >/dev/null 2>&1 <<< "$jsondata"; then
    echo "[ERROR] $dev 采集的数据格式错误: $jsondata"
    return 1
  fi

  echo "$jsondata" > "$newfile"
  echo "[INFO] $dev 数据已保存至 $newfile"

  # 记录已监控的设备
  MONITORED_DEVICES+=("$dev")

  if [[ -f "$lastfile" ]]; then
    # 关键修复：用!取反，当compare_json返回1（有异常）时才执行ALERT
    if ! compare_json "$lastfile" "$newfile" "$dev"; then
      echo "[ALERT] $dev 检测到异常指标变化！"
    else
      echo "[OK] $dev SMART状态无异常变化"
      DEVICE_SUMMARY["$dev"]="无异常变化"  # 无异常时记录状态
    fi
  else
    echo "[INFO] $dev 首次采集SMART数据，已记录为基准值"
    DEVICE_SUMMARY["$dev"]="首次采集，无历史数据对比"
  fi

  cp -f "$newfile" "$lastfile"
}

# 打印运行详情汇总
print_summary() {
  echo -e "\n====================================="
  echo "          SMART监控详情汇总          "
  echo "====================================="
  echo "监控时间: $(date +"%Y-%m-%d %H:%M:%S")"
  echo "监控设备总数: ${#MONITORED_DEVICES[@]}"
  echo "====================================="

  for dev in "${MONITORED_DEVICES[@]}"; do
    local dev_name=$(basename "$dev")
    local type=$(cat "$LOGDIR/.tmp_${dev//\//_}_type" 2>/dev/null || echo "未知")
    local lastfile="$LOGDIR/smart_${dev_name}_last.json"
    
    echo -e "\n【设备】$dev（$type）"
    echo "-------------------------------------"
    echo "当前关键状态:"
    # 提取并打印关键指标（修复jq语法，添加默认值处理）
    jq -r '
      .["健康状态"] // "未知",
      "温度: " + ((.["温度(摄氏度)"] // "未知") | tostring) + "℃",
      "重映射扇区数: " + ((.["重映射扇区数"] // "未知") | tostring),
      "通电时间: " + ((.["通电时间(小时)"] // "未知") | tostring) + "小时"
    ' "$lastfile" 2>/dev/null || echo "无法读取状态数据"
    
    echo -e "\n变化情况:"
    echo "${DEVICE_SUMMARY[$dev]}"
    rm -f "$LOGDIR/.tmp_${dev//\//_}_type"  # 清理临时文件
  done

  echo -e "\n====================================="
  echo "          监控结束                  "
  echo "====================================="
}

# 主程序入口
main() {
  check_dependencies
  
  local devices=("$@")
  if [[ ${#devices[@]} -eq 0 ]]; then
    devices=(/dev/sd[a-z] /dev/sg[0-9] /dev/sr[0-9])
  fi

  for d in "${devices[@]}"; do
    if [[ -b "$d" ]]; then
      monitor_device "$d"
    else
      :  # 空操作，不输出非块设备信息
    fi
  done

  # 运行结束后打印详情汇总
  print_summary
}

main "$@"

