Treat historical SMART failures as resolved

This commit is contained in:
Maxim
2026-08-03 21:17:15 +03:00
parent 05450bc949
commit 73de891571
2 changed files with 50 additions and 6 deletions

View File

@@ -25,6 +25,7 @@ type DiskMetrics struct {
SMARTStatus string `json:"smartStatus"` SMARTStatus string `json:"smartStatus"`
SMARTAvailable bool `json:"smartAvailable"` SMARTAvailable bool `json:"smartAvailable"`
AttentionReasons []string `json:"attentionReasons"` AttentionReasons []string `json:"attentionReasons"`
SMARTHistory []string `json:"smartHistory"`
Temperature *float64 `json:"temperatureCelsius"` Temperature *float64 `json:"temperatureCelsius"`
PowerOnHours *uint64 `json:"powerOnHours"` PowerOnHours *uint64 `json:"powerOnHours"`
WearUsedPercent *float64 `json:"wearUsedPercent"` WearUsedPercent *float64 `json:"wearUsedPercent"`
@@ -33,6 +34,7 @@ type DiskMetrics struct {
Uncorrectable uint64 `json:"uncorrectable"` Uncorrectable uint64 `json:"uncorrectable"`
CRCErrors uint64 `json:"crcErrors"` CRCErrors uint64 `json:"crcErrors"`
MediaErrors uint64 `json:"mediaErrors"` MediaErrors uint64 `json:"mediaErrors"`
SMARTErrorCount uint64 `json:"smartErrorCount"`
CounterChanges []string `json:"counterChanges"` CounterChanges []string `json:"counterChanges"`
LastSelfTest string `json:"lastSelfTest"` LastSelfTest string `json:"lastSelfTest"`
LastSelfTestAt string `json:"lastSelfTestAt"` LastSelfTestAt string `json:"lastSelfTestAt"`
@@ -203,7 +205,7 @@ func diskCounterChanges(old, current DiskMetrics) []string {
checks := []struct { checks := []struct {
label string label string
old, now uint64 old, now uint64
}{{"Reallocated вырос", old.Reallocated, current.Reallocated}, {"Pending вырос", old.Pending, current.Pending}, {"Uncorrectable вырос", old.Uncorrectable, current.Uncorrectable}, {"CRC-ошибки выросли", old.CRCErrors, current.CRCErrors}, {"Media errors выросли", old.MediaErrors, current.MediaErrors}} }{{"Reallocated вырос", old.Reallocated, current.Reallocated}, {"Pending вырос", old.Pending, current.Pending}, {"Uncorrectable вырос", old.Uncorrectable, current.Uncorrectable}, {"CRC-ошибки выросли", old.CRCErrors, current.CRCErrors}, {"Media errors выросли", old.MediaErrors, current.MediaErrors}, {"Ошибки SMART-журнала выросли", old.SMARTErrorCount, current.SMARTErrorCount}}
var changes []string var changes []string
for _, check := range checks { for _, check := range checks {
if check.now > check.old { if check.now > check.old {
@@ -488,16 +490,23 @@ func applyATAAttributes(disk *DiskMetrics, smart smartctlJSON) {
wear := float64(100 - attribute.Value) wear := float64(100 - attribute.Value)
disk.WearUsedPercent = &wear disk.WearUsedPercent = &wear
} }
if attribute.WhenFailed != "" && attribute.WhenFailed != "-" { if strings.EqualFold(attribute.WhenFailed, "FAILING_NOW") {
reason := attribute.Name + " вышел за порог" reason := attribute.Name + " вышел за порог"
if attribute.Raw.String != "" { if attribute.Raw.String != "" {
reason += " (" + attribute.Raw.String + ")" reason += " (" + attribute.Raw.String + ")"
} }
disk.AttentionReasons = append(disk.AttentionReasons, reason) disk.AttentionReasons = append(disk.AttentionReasons, reason)
} else if attribute.WhenFailed != "" && attribute.WhenFailed != "-" {
reason := attribute.Name + " ранее выходил за порог"
if attribute.Raw.String != "" {
reason += " (" + attribute.Raw.String + ")"
}
disk.SMARTHistory = append(disk.SMARTHistory, reason)
} }
} }
if smart.ATAErrorLog.Summary.Count > 0 { if smart.ATAErrorLog.Summary.Count > 0 {
disk.AttentionReasons = append(disk.AttentionReasons, "Ошибок в SMART-журнале: "+strconv.Itoa(smart.ATAErrorLog.Summary.Count)) disk.SMARTErrorCount = uint64(smart.ATAErrorLog.Summary.Count)
disk.SMARTHistory = append(disk.SMARTHistory, "Записей в SMART-журнале: "+strconv.Itoa(smart.ATAErrorLog.Summary.Count))
} }
} }
@@ -511,12 +520,12 @@ func applySMARTExitStatus(disk *DiskMetrics, status int) {
disk.AttentionReasons = append(disk.AttentionReasons, "Критический SMART-атрибут достиг порога") disk.AttentionReasons = append(disk.AttentionReasons, "Критический SMART-атрибут достиг порога")
} }
if status&32 != 0 { if status&32 != 0 {
disk.AttentionReasons = append(disk.AttentionReasons, "SMART-атрибут ранее находился ниже порога") disk.SMARTHistory = append(disk.SMARTHistory, "SMART-атрибут ранее находился ниже порога")
} }
if status&64 != 0 { if status&64 != 0 {
disk.AttentionReasons = append(disk.AttentionReasons, "В журнале SMART обнаружены ошибки") disk.SMARTHistory = append(disk.SMARTHistory, "В журнале SMART есть старые записи об ошибках")
} }
if status&128 != 0 { if status&128 != 0 {
disk.AttentionReasons = append(disk.AttentionReasons, "В журнале самотестирования есть ошибки") disk.SMARTHistory = append(disk.SMARTHistory, "В журнале самотестирования есть старые ошибки")
} }
} }

View File

@@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
) )
@@ -68,3 +69,37 @@ func TestApplyATAWearAndReason(t *testing.T) {
t.Fatalf("причина предупреждения не добавлена: %+v", disk) t.Fatalf("причина предупреждения не добавлена: %+v", disk)
} }
} }
func TestHistoricalATAFailureDoesNotRequireAttention(t *testing.T) {
var smart smartctlJSON
attribute := struct {
Name string `json:"name"`
Value int `json:"value"`
Threshold int `json:"thresh"`
WhenFailed string `json:"when_failed"`
Raw struct {
String string `json:"string"`
Value int64 `json:"value"`
} `json:"raw"`
}{Name: "Airflow_Temperature_Cel", WhenFailed: "In_the_past"}
attribute.Raw.String = "46 (Min/Max 26/47 #15)"
smart.ATAAttributes.Table = append(smart.ATAAttributes.Table, attribute)
disk := DiskMetrics{}
applyATAAttributes(&disk, smart)
applySMARTExitStatus(&disk, 32|64|128)
if len(disk.AttentionReasons) != 0 {
t.Fatalf("историческая ошибка стала активной: %+v", disk.AttentionReasons)
}
if len(disk.SMARTHistory) != 4 {
t.Fatalf("история SMART потеряна: %+v", disk.SMARTHistory)
}
}
func TestSMARTErrorLogGrowthRequiresAttention(t *testing.T) {
old := DiskMetrics{SMARTErrorCount: 2}
current := DiskMetrics{SMARTErrorCount: 3}
changes := diskCounterChanges(old, current)
if len(changes) != 1 || !strings.Contains(changes[0], "2 → 3") {
t.Fatalf("рост журнала не обнаружен: %+v", changes)
}
}