diff --git a/disks.go b/disks.go index c63446a..6547567 100644 --- a/disks.go +++ b/disks.go @@ -25,6 +25,7 @@ type DiskMetrics struct { SMARTStatus string `json:"smartStatus"` SMARTAvailable bool `json:"smartAvailable"` AttentionReasons []string `json:"attentionReasons"` + SMARTHistory []string `json:"smartHistory"` Temperature *float64 `json:"temperatureCelsius"` PowerOnHours *uint64 `json:"powerOnHours"` WearUsedPercent *float64 `json:"wearUsedPercent"` @@ -33,6 +34,7 @@ type DiskMetrics struct { Uncorrectable uint64 `json:"uncorrectable"` CRCErrors uint64 `json:"crcErrors"` MediaErrors uint64 `json:"mediaErrors"` + SMARTErrorCount uint64 `json:"smartErrorCount"` CounterChanges []string `json:"counterChanges"` LastSelfTest string `json:"lastSelfTest"` LastSelfTestAt string `json:"lastSelfTestAt"` @@ -203,7 +205,7 @@ func diskCounterChanges(old, current DiskMetrics) []string { checks := []struct { label string 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 for _, check := range checks { if check.now > check.old { @@ -488,16 +490,23 @@ func applyATAAttributes(disk *DiskMetrics, smart smartctlJSON) { wear := float64(100 - attribute.Value) disk.WearUsedPercent = &wear } - if attribute.WhenFailed != "" && attribute.WhenFailed != "-" { + if strings.EqualFold(attribute.WhenFailed, "FAILING_NOW") { reason := attribute.Name + " вышел за порог" if attribute.Raw.String != "" { reason += " (" + attribute.Raw.String + ")" } 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 { - 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-атрибут достиг порога") } if status&32 != 0 { - disk.AttentionReasons = append(disk.AttentionReasons, "SMART-атрибут ранее находился ниже порога") + disk.SMARTHistory = append(disk.SMARTHistory, "SMART-атрибут ранее находился ниже порога") } if status&64 != 0 { - disk.AttentionReasons = append(disk.AttentionReasons, "В журнале SMART обнаружены ошибки") + disk.SMARTHistory = append(disk.SMARTHistory, "В журнале SMART есть старые записи об ошибках") } if status&128 != 0 { - disk.AttentionReasons = append(disk.AttentionReasons, "В журнале самотестирования есть ошибки") + disk.SMARTHistory = append(disk.SMARTHistory, "В журнале самотестирования есть старые ошибки") } } diff --git a/disks_test.go b/disks_test.go index 84be4e1..c9cb4b5 100644 --- a/disks_test.go +++ b/disks_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "os" "path/filepath" + "strings" "testing" ) @@ -68,3 +69,37 @@ func TestApplyATAWearAndReason(t *testing.T) { 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) + } +}