You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
151 lines
5.1 KiB
151 lines
5.1 KiB
package db
|
|
|
|
import (
|
|
"github.com/analogj/scrutiny/webapp/backend/pkg/metadata"
|
|
"github.com/analogj/scrutiny/webapp/backend/pkg/models/collector"
|
|
"github.com/jinzhu/gorm"
|
|
"time"
|
|
)
|
|
|
|
const SmartWhenFailedFailingNow = "FAILING_NOW"
|
|
const SmartWhenFailedInThePast = "IN_THE_PAST"
|
|
|
|
type Smart struct {
|
|
gorm.Model
|
|
|
|
DeviceWWN string `json:"device_wwn"`
|
|
Device Device `json:"-" gorm:"foreignkey:DeviceWWN"` // use DeviceWWN as foreign key
|
|
|
|
TestDate time.Time `json:"date"`
|
|
SmartStatus string `json:"smart_status"`
|
|
|
|
//Metrics
|
|
Temp int64 `json:"temp"`
|
|
PowerOnHours int64 `json:"power_on_hours"`
|
|
PowerCycleCount int64 `json:"power_cycle_count"`
|
|
|
|
SmartAttributes []SmartAttribute `json:"smart_attributes" gorm:"foreignkey:SmartId"`
|
|
}
|
|
|
|
func (sm *Smart) FromCollectorSmartInfo(wwn string, info collector.SmartInfo) error {
|
|
sm.DeviceWWN = wwn
|
|
sm.TestDate = time.Unix(info.LocalTime.TimeT, 0)
|
|
|
|
//smart metrics
|
|
sm.Temp = info.Temperature.Current
|
|
sm.PowerCycleCount = info.PowerCycleCount
|
|
sm.PowerOnHours = info.PowerOnTime.Hours
|
|
|
|
sm.SmartAttributes = []SmartAttribute{}
|
|
for _, collectorAttr := range info.AtaSmartAttributes.Table {
|
|
attrModel := SmartAttribute{
|
|
AttributeId: collectorAttr.ID,
|
|
Name: collectorAttr.Name,
|
|
Value: collectorAttr.Value,
|
|
Worst: collectorAttr.Worst,
|
|
Threshold: collectorAttr.Thresh,
|
|
RawValue: collectorAttr.Raw.Value,
|
|
RawString: collectorAttr.Raw.String,
|
|
WhenFailed: collectorAttr.WhenFailed,
|
|
}
|
|
|
|
//now that we've parsed the data from the smartctl response, lets match it against our metadata rules and add additional Scrutiny specific data.
|
|
if smartMetadata, ok := metadata.AtaSmartAttributes[collectorAttr.ID]; ok {
|
|
attrModel.Name = smartMetadata.DisplayName
|
|
if smartMetadata.Transform != nil {
|
|
attrModel.TransformedValue = smartMetadata.Transform(attrModel.Value, attrModel.RawValue, attrModel.RawString)
|
|
}
|
|
}
|
|
sm.SmartAttributes = append(sm.SmartAttributes, attrModel)
|
|
}
|
|
|
|
if info.SmartStatus.Passed {
|
|
sm.SmartStatus = "passed"
|
|
} else {
|
|
sm.SmartStatus = "failed"
|
|
}
|
|
return nil
|
|
}
|
|
|
|
const SmartAttributeStatusPassed = "passed"
|
|
const SmartAttributeStatusFailed = "failed"
|
|
const SmartAttributeStatusWarning = "warn"
|
|
|
|
type SmartAttribute struct {
|
|
gorm.Model
|
|
|
|
SmartId int `json:"smart_id"`
|
|
Smart Device `json:"-" gorm:"foreignkey:SmartId"` // use SmartId as foreign key
|
|
|
|
AttributeId int `json:"attribute_id"`
|
|
Name string `json:"name"`
|
|
Value int `json:"value"`
|
|
Worst int `json:"worst"`
|
|
Threshold int `json:"thresh"`
|
|
RawValue int64 `json:"raw_value"`
|
|
RawString string `json:"raw_string"`
|
|
WhenFailed string `json:"when_failed"`
|
|
|
|
TransformedValue int64 `json:"transformed_value"`
|
|
Status string `gorm:"-" json:"status,omitempty"`
|
|
StatusReason string `gorm:"-" json:"status_reason,omitempty"`
|
|
FailureRate float64 `gorm:"-" json:"failure_rate,omitempty"`
|
|
History []SmartAttribute `gorm:"-" json:"history,omitempty"`
|
|
}
|
|
|
|
// compare the attribute (raw, normalized, transformed) value to observed thresholds, and update status if necessary
|
|
func (sa *SmartAttribute) MetadataObservedThresholdStatus(smartMetadata metadata.AtaSmartAttribute) {
|
|
//TODO: multiple rules
|
|
// try to predict the failure rates for observed thresholds that have 0 failure rate and error bars.
|
|
// - if the attribute is critical
|
|
// - the failure rate is over 10 - set to failed
|
|
// - the attribute does not match any threshold, set to warn
|
|
// - if the attribute is not critical
|
|
// - if failure rate is above 20 - set to failed
|
|
// - if failure rate is above 10 but below 20 - set to warn
|
|
|
|
//update the smart attribute status based on Observed thresholds.
|
|
var value int64
|
|
if smartMetadata.DisplayType == metadata.AtaSmartAttributeDisplayTypeNormalized {
|
|
value = int64(sa.Value)
|
|
} else if smartMetadata.DisplayType == metadata.AtaSmartAttributeDisplayTypeTransformed {
|
|
value = sa.TransformedValue
|
|
} else {
|
|
value = sa.RawValue
|
|
}
|
|
|
|
for _, obsThresh := range smartMetadata.ObservedThresholds {
|
|
|
|
//check if "value" is in this bucket
|
|
if ((obsThresh.Low == obsThresh.High) && value == obsThresh.Low) ||
|
|
(obsThresh.Low < value && value <= obsThresh.High) {
|
|
sa.FailureRate = obsThresh.AnnualFailureRate
|
|
|
|
if smartMetadata.Critical {
|
|
if obsThresh.AnnualFailureRate >= 0.10 {
|
|
sa.Status = SmartAttributeStatusFailed
|
|
sa.StatusReason = "Observed Failure Rate for Critical Attribute is greater than 10%"
|
|
}
|
|
} else {
|
|
if obsThresh.AnnualFailureRate >= 0.20 {
|
|
sa.Status = SmartAttributeStatusFailed
|
|
sa.StatusReason = "Observed Failure Rate for Attribute is greater than 20%"
|
|
} else if obsThresh.AnnualFailureRate >= 0.10 {
|
|
sa.Status = SmartAttributeStatusWarning
|
|
sa.StatusReason = "Observed Failure Rate for Attribute is greater than 10%"
|
|
}
|
|
}
|
|
|
|
//we've found the correct bucket, we can drop out of this loop
|
|
return
|
|
}
|
|
}
|
|
// no bucket found
|
|
if smartMetadata.Critical {
|
|
sa.Status = SmartAttributeStatusWarning
|
|
sa.StatusReason = "Could not determine Observed Failure Rate for Critical Attribute"
|
|
}
|
|
|
|
return
|
|
}
|