!!!!WIP!!!!
adding InfluxDB - influxdb added to dockerfile - influxdb s6 service - influxdb config - adding defaults to config - creating a DeviceRepo interface (multiple db backends) - implemented DeviceRepo interface as ScruitnyRepository
This commit is contained in:
@@ -119,14 +119,28 @@ type SmartInfo struct {
|
||||
FeatureControlSupported bool `json:"feature_control_supported"`
|
||||
DataTableSupported bool `json:"data_table_supported"`
|
||||
} `json:"ata_sct_capabilities"`
|
||||
AtaSctTemperatureHistory struct {
|
||||
Version int `json:"version"`
|
||||
SamplingPeriodMinutes int64 `json:"sampling_period_minutes"`
|
||||
LoggingIntervalMinutes int64 `json:"logging_interval_minutes"`
|
||||
Temperature struct {
|
||||
OpLimitMin int `json:"op_limit_min"`
|
||||
OpLimitMax int `json:"op_limit_max"`
|
||||
LimitMin int `json:"limit_min"`
|
||||
LimitMax int `json:"limit_max"`
|
||||
} `json:"temperature"`
|
||||
Size int `json:"size"`
|
||||
Index int `json:"index"`
|
||||
Table []int64 `json:"table"`
|
||||
} `json:"ata_sct_temperature_history"`
|
||||
AtaSmartAttributes struct {
|
||||
Revision int `json:"revision"`
|
||||
Table []struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Value int `json:"value"`
|
||||
Worst int `json:"worst"`
|
||||
Thresh int `json:"thresh"`
|
||||
Value int64 `json:"value"`
|
||||
Worst int64 `json:"worst"`
|
||||
Thresh int64 `json:"thresh"`
|
||||
WhenFailed string `json:"when_failed"`
|
||||
Flags struct {
|
||||
Value int `json:"value"`
|
||||
@@ -237,48 +251,48 @@ type SmartInfo struct {
|
||||
FormattedLbaSize int `json:"formatted_lba_size"`
|
||||
} `json:"nvme_namespaces"`
|
||||
NvmeSmartHealthInformationLog struct {
|
||||
CriticalWarning int `json:"critical_warning"`
|
||||
Temperature int `json:"temperature"`
|
||||
AvailableSpare int `json:"available_spare"`
|
||||
AvailableSpareThreshold int `json:"available_spare_threshold"`
|
||||
PercentageUsed int `json:"percentage_used"`
|
||||
DataUnitsRead int `json:"data_units_read"`
|
||||
DataUnitsWritten int `json:"data_units_written"`
|
||||
HostReads int `json:"host_reads"`
|
||||
HostWrites int `json:"host_writes"`
|
||||
ControllerBusyTime int `json:"controller_busy_time"`
|
||||
PowerCycles int `json:"power_cycles"`
|
||||
PowerOnHours int `json:"power_on_hours"`
|
||||
UnsafeShutdowns int `json:"unsafe_shutdowns"`
|
||||
MediaErrors int `json:"media_errors"`
|
||||
NumErrLogEntries int `json:"num_err_log_entries"`
|
||||
WarningTempTime int `json:"warning_temp_time"`
|
||||
CriticalCompTime int `json:"critical_comp_time"`
|
||||
CriticalWarning int64 `json:"critical_warning"`
|
||||
Temperature int64 `json:"temperature"`
|
||||
AvailableSpare int64 `json:"available_spare"`
|
||||
AvailableSpareThreshold int64 `json:"available_spare_threshold"`
|
||||
PercentageUsed int64 `json:"percentage_used"`
|
||||
DataUnitsRead int64 `json:"data_units_read"`
|
||||
DataUnitsWritten int64 `json:"data_units_written"`
|
||||
HostReads int64 `json:"host_reads"`
|
||||
HostWrites int64 `json:"host_writes"`
|
||||
ControllerBusyTime int64 `json:"controller_busy_time"`
|
||||
PowerCycles int64 `json:"power_cycles"`
|
||||
PowerOnHours int64 `json:"power_on_hours"`
|
||||
UnsafeShutdowns int64 `json:"unsafe_shutdowns"`
|
||||
MediaErrors int64 `json:"media_errors"`
|
||||
NumErrLogEntries int64 `json:"num_err_log_entries"`
|
||||
WarningTempTime int64 `json:"warning_temp_time"`
|
||||
CriticalCompTime int64 `json:"critical_comp_time"`
|
||||
} `json:"nvme_smart_health_information_log"`
|
||||
|
||||
// SCSI Protocol Specific Fields
|
||||
Vendor string `json:"vendor"`
|
||||
Product string `json:"product"`
|
||||
ScsiVersion string `json:"scsi_version"`
|
||||
ScsiGrownDefectList int `json:"scsi_grown_defect_list"`
|
||||
ScsiGrownDefectList int64 `json:"scsi_grown_defect_list"`
|
||||
ScsiErrorCounterLog struct {
|
||||
Read struct {
|
||||
ErrorsCorrectedByEccfast int `json:"errors_corrected_by_eccfast"`
|
||||
ErrorsCorrectedByEccdelayed int `json:"errors_corrected_by_eccdelayed"`
|
||||
ErrorsCorrectedByRereadsRewrites int `json:"errors_corrected_by_rereads_rewrites"`
|
||||
TotalErrorsCorrected int `json:"total_errors_corrected"`
|
||||
CorrectionAlgorithmInvocations int `json:"correction_algorithm_invocations"`
|
||||
ErrorsCorrectedByEccfast int64 `json:"errors_corrected_by_eccfast"`
|
||||
ErrorsCorrectedByEccdelayed int64 `json:"errors_corrected_by_eccdelayed"`
|
||||
ErrorsCorrectedByRereadsRewrites int64 `json:"errors_corrected_by_rereads_rewrites"`
|
||||
TotalErrorsCorrected int64 `json:"total_errors_corrected"`
|
||||
CorrectionAlgorithmInvocations int64 `json:"correction_algorithm_invocations"`
|
||||
GigabytesProcessed string `json:"gigabytes_processed"`
|
||||
TotalUncorrectedErrors int `json:"total_uncorrected_errors"`
|
||||
TotalUncorrectedErrors int64 `json:"total_uncorrected_errors"`
|
||||
} `json:"read"`
|
||||
Write struct {
|
||||
ErrorsCorrectedByEccfast int `json:"errors_corrected_by_eccfast"`
|
||||
ErrorsCorrectedByEccdelayed int `json:"errors_corrected_by_eccdelayed"`
|
||||
ErrorsCorrectedByRereadsRewrites int `json:"errors_corrected_by_rereads_rewrites"`
|
||||
TotalErrorsCorrected int `json:"total_errors_corrected"`
|
||||
CorrectionAlgorithmInvocations int `json:"correction_algorithm_invocations"`
|
||||
ErrorsCorrectedByEccfast int64 `json:"errors_corrected_by_eccfast"`
|
||||
ErrorsCorrectedByEccdelayed int64 `json:"errors_corrected_by_eccdelayed"`
|
||||
ErrorsCorrectedByRereadsRewrites int64 `json:"errors_corrected_by_rereads_rewrites"`
|
||||
TotalErrorsCorrected int64 `json:"total_errors_corrected"`
|
||||
CorrectionAlgorithmInvocations int64 `json:"correction_algorithm_invocations"`
|
||||
GigabytesProcessed string `json:"gigabytes_processed"`
|
||||
TotalUncorrectedErrors int `json:"total_uncorrected_errors"`
|
||||
TotalUncorrectedErrors int64 `json:"total_uncorrected_errors"`
|
||||
} `json:"write"`
|
||||
} `json:"scsi_error_counter_log"`
|
||||
}
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/models/collector"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DeviceWrapper struct {
|
||||
Success bool `json:"success"`
|
||||
Errors []error `json:"errors"`
|
||||
Data []Device `json:"data"`
|
||||
}
|
||||
|
||||
const DeviceProtocolAta = "ATA"
|
||||
const DeviceProtocolScsi = "SCSI"
|
||||
const DeviceProtocolNvme = "NVMe"
|
||||
|
||||
type Device struct {
|
||||
//GORM attributes, see: http://gorm.io/docs/conventions.html
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt *time.Time
|
||||
|
||||
WWN string `json:"wwn" gorm:"primary_key"`
|
||||
HostId string `json:"host_id"`
|
||||
|
||||
DeviceName string `json:"device_name"`
|
||||
Manufacturer string `json:"manufacturer"`
|
||||
ModelName string `json:"model_name"`
|
||||
InterfaceType string `json:"interface_type"`
|
||||
InterfaceSpeed string `json:"interface_speed"`
|
||||
SerialNumber string `json:"serial_number"`
|
||||
Firmware string `json:"firmware"`
|
||||
RotationSpeed int `json:"rotational_speed"`
|
||||
Capacity int64 `json:"capacity"`
|
||||
FormFactor string `json:"form_factor"`
|
||||
SmartSupport bool `json:"smart_support"`
|
||||
DeviceProtocol string `json:"device_protocol"` //protocol determines which smart attribute types are available (ATA, NVMe, SCSI)
|
||||
DeviceType string `json:"device_type"` //device type is used for querying with -d/t flag, should only be used by collector.
|
||||
SmartResults []Smart `gorm:"foreignkey:DeviceWWN" json:"smart_results"`
|
||||
}
|
||||
|
||||
func (dv *Device) IsAta() bool {
|
||||
return dv.DeviceProtocol == DeviceProtocolAta
|
||||
}
|
||||
|
||||
func (dv *Device) IsScsi() bool {
|
||||
return dv.DeviceProtocol == DeviceProtocolScsi
|
||||
}
|
||||
|
||||
func (dv *Device) IsNvme() bool {
|
||||
return dv.DeviceProtocol == DeviceProtocolNvme
|
||||
}
|
||||
|
||||
//This method requires a device with an array of SmartResults.
|
||||
//It will remove all SmartResults other than the first (the latest one)
|
||||
//All removed SmartResults, will be processed, grouping SmartAtaAttribute by attribute_id
|
||||
// and adding theme to an array called History.
|
||||
func (dv *Device) SquashHistory() error {
|
||||
if len(dv.SmartResults) <= 1 {
|
||||
return nil //no ataHistory found. ignore
|
||||
}
|
||||
|
||||
latestSmartResultSlice := dv.SmartResults[0:1]
|
||||
historicalSmartResultSlice := dv.SmartResults[1:]
|
||||
|
||||
//re-assign the latest slice to the SmartResults field
|
||||
dv.SmartResults = latestSmartResultSlice
|
||||
|
||||
//process the historical slice for ATA data
|
||||
if len(dv.SmartResults[0].AtaAttributes) > 0 {
|
||||
ataHistory := map[int][]SmartAtaAttribute{}
|
||||
for _, smartResult := range historicalSmartResultSlice {
|
||||
for _, smartAttribute := range smartResult.AtaAttributes {
|
||||
if _, ok := ataHistory[smartAttribute.AttributeId]; !ok {
|
||||
ataHistory[smartAttribute.AttributeId] = []SmartAtaAttribute{}
|
||||
}
|
||||
ataHistory[smartAttribute.AttributeId] = append(ataHistory[smartAttribute.AttributeId], smartAttribute)
|
||||
}
|
||||
}
|
||||
|
||||
//now assign the historical slices to the AtaAttributes in the latest SmartResults
|
||||
for sandx, smartAttribute := range dv.SmartResults[0].AtaAttributes {
|
||||
if attributeHistory, ok := ataHistory[smartAttribute.AttributeId]; ok {
|
||||
dv.SmartResults[0].AtaAttributes[sandx].History = attributeHistory
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//process the historical slice for Nvme data
|
||||
if len(dv.SmartResults[0].NvmeAttributes) > 0 {
|
||||
nvmeHistory := map[string][]SmartNvmeAttribute{}
|
||||
for _, smartResult := range historicalSmartResultSlice {
|
||||
for _, smartAttribute := range smartResult.NvmeAttributes {
|
||||
if _, ok := nvmeHistory[smartAttribute.AttributeId]; !ok {
|
||||
nvmeHistory[smartAttribute.AttributeId] = []SmartNvmeAttribute{}
|
||||
}
|
||||
nvmeHistory[smartAttribute.AttributeId] = append(nvmeHistory[smartAttribute.AttributeId], smartAttribute)
|
||||
}
|
||||
}
|
||||
|
||||
//now assign the historical slices to the AtaAttributes in the latest SmartResults
|
||||
for sandx, smartAttribute := range dv.SmartResults[0].NvmeAttributes {
|
||||
if attributeHistory, ok := nvmeHistory[smartAttribute.AttributeId]; ok {
|
||||
dv.SmartResults[0].NvmeAttributes[sandx].History = attributeHistory
|
||||
}
|
||||
}
|
||||
}
|
||||
//process the historical slice for Scsi data
|
||||
if len(dv.SmartResults[0].ScsiAttributes) > 0 {
|
||||
scsiHistory := map[string][]SmartScsiAttribute{}
|
||||
for _, smartResult := range historicalSmartResultSlice {
|
||||
for _, smartAttribute := range smartResult.ScsiAttributes {
|
||||
if _, ok := scsiHistory[smartAttribute.AttributeId]; !ok {
|
||||
scsiHistory[smartAttribute.AttributeId] = []SmartScsiAttribute{}
|
||||
}
|
||||
scsiHistory[smartAttribute.AttributeId] = append(scsiHistory[smartAttribute.AttributeId], smartAttribute)
|
||||
}
|
||||
}
|
||||
|
||||
//now assign the historical slices to the AtaAttributes in the latest SmartResults
|
||||
for sandx, smartAttribute := range dv.SmartResults[0].ScsiAttributes {
|
||||
if attributeHistory, ok := scsiHistory[smartAttribute.AttributeId]; ok {
|
||||
dv.SmartResults[0].ScsiAttributes[sandx].History = attributeHistory
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dv *Device) ApplyMetadataRules() error {
|
||||
|
||||
//embed metadata in the latest smart attributes object
|
||||
if len(dv.SmartResults) > 0 {
|
||||
for ndx, attr := range dv.SmartResults[0].AtaAttributes {
|
||||
attr.PopulateAttributeStatus()
|
||||
dv.SmartResults[0].AtaAttributes[ndx] = attr
|
||||
}
|
||||
|
||||
for ndx, attr := range dv.SmartResults[0].NvmeAttributes {
|
||||
attr.PopulateAttributeStatus()
|
||||
dv.SmartResults[0].NvmeAttributes[ndx] = attr
|
||||
|
||||
}
|
||||
|
||||
for ndx, attr := range dv.SmartResults[0].ScsiAttributes {
|
||||
attr.PopulateAttributeStatus()
|
||||
dv.SmartResults[0].ScsiAttributes[ndx] = attr
|
||||
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// This function is called every time the collector sends SMART data to the API.
|
||||
// It can be used to update device data that can change over time.
|
||||
func (dv *Device) UpdateFromCollectorSmartInfo(info collector.SmartInfo) error {
|
||||
dv.Firmware = info.FirmwareVersion
|
||||
return nil
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package db
|
||||
|
||||
import "time"
|
||||
|
||||
type SelfTest struct {
|
||||
//GORM attributes, see: http://gorm.io/docs/conventions.html
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt *time.Time
|
||||
|
||||
DeviceWWN string
|
||||
Device Device `json:"-" gorm:"foreignkey:DeviceWWN"` // use DeviceWWN as foreign key
|
||||
|
||||
Date time.Time
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/metadata"
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/models/collector"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
const SmartWhenFailedFailingNow = "FAILING_NOW"
|
||||
const SmartWhenFailedInThePast = "IN_THE_PAST"
|
||||
|
||||
const SmartStatusPassed = "passed"
|
||||
const SmartStatusFailed = "failed"
|
||||
|
||||
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"` // SmartStatusPassed or SmartStatusFailed
|
||||
|
||||
//Metrics
|
||||
Temp int64 `json:"temp"`
|
||||
PowerOnHours int64 `json:"power_on_hours"`
|
||||
PowerCycleCount int64 `json:"power_cycle_count"`
|
||||
|
||||
AtaAttributes []SmartAtaAttribute `json:"ata_attributes" gorm:"foreignkey:SmartId"`
|
||||
NvmeAttributes []SmartNvmeAttribute `json:"nvme_attributes" gorm:"foreignkey:SmartId"`
|
||||
ScsiAttributes []SmartScsiAttribute `json:"scsi_attributes" gorm:"foreignkey:SmartId"`
|
||||
}
|
||||
|
||||
//Parse Collector SMART data results and create Smart object (and associated SmartAtaAttribute entries)
|
||||
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
|
||||
|
||||
// process ATA/NVME/SCSI protocol data
|
||||
if info.Device.Protocol == DeviceProtocolAta {
|
||||
sm.ProcessAtaSmartInfo(info)
|
||||
} else if info.Device.Protocol == DeviceProtocolNvme {
|
||||
sm.ProcessNvmeSmartInfo(info)
|
||||
} else if info.Device.Protocol == DeviceProtocolScsi {
|
||||
sm.ProcessScsiSmartInfo(info)
|
||||
}
|
||||
|
||||
if info.SmartStatus.Passed {
|
||||
sm.SmartStatus = SmartStatusPassed
|
||||
} else {
|
||||
sm.SmartStatus = SmartStatusFailed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//generate SmartAtaAttribute entries from Scrutiny Collector Smart data.
|
||||
func (sm *Smart) ProcessAtaSmartInfo(info collector.SmartInfo) {
|
||||
sm.AtaAttributes = []SmartAtaAttribute{}
|
||||
for _, collectorAttr := range info.AtaSmartAttributes.Table {
|
||||
attrModel := SmartAtaAttribute{
|
||||
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.AtaMetadata[collectorAttr.ID]; ok {
|
||||
attrModel.Name = smartMetadata.DisplayName
|
||||
if smartMetadata.Transform != nil {
|
||||
attrModel.TransformedValue = smartMetadata.Transform(attrModel.Value, attrModel.RawValue, attrModel.RawString)
|
||||
}
|
||||
}
|
||||
sm.AtaAttributes = append(sm.AtaAttributes, attrModel)
|
||||
}
|
||||
}
|
||||
|
||||
//generate SmartNvmeAttribute entries from Scrutiny Collector Smart data.
|
||||
func (sm *Smart) ProcessNvmeSmartInfo(info collector.SmartInfo) {
|
||||
sm.NvmeAttributes = []SmartNvmeAttribute{
|
||||
{AttributeId: "critical_warning", Name: "Critical Warning", Value: info.NvmeSmartHealthInformationLog.CriticalWarning, Threshold: 0},
|
||||
{AttributeId: "temperature", Name: "Temperature", Value: info.NvmeSmartHealthInformationLog.Temperature, Threshold: -1},
|
||||
{AttributeId: "available_spare", Name: "Available Spare", Value: info.NvmeSmartHealthInformationLog.AvailableSpare, Threshold: info.NvmeSmartHealthInformationLog.AvailableSpareThreshold},
|
||||
{AttributeId: "percentage_used", Name: "Percentage Used", Value: info.NvmeSmartHealthInformationLog.PercentageUsed, Threshold: 100},
|
||||
{AttributeId: "data_units_read", Name: "Data Units Read", Value: info.NvmeSmartHealthInformationLog.DataUnitsRead, Threshold: -1},
|
||||
{AttributeId: "data_units_written", Name: "Data Units Written", Value: info.NvmeSmartHealthInformationLog.DataUnitsWritten, Threshold: -1},
|
||||
{AttributeId: "host_reads", Name: "Host Reads", Value: info.NvmeSmartHealthInformationLog.HostReads, Threshold: -1},
|
||||
{AttributeId: "host_writes", Name: "Host Writes", Value: info.NvmeSmartHealthInformationLog.HostWrites, Threshold: -1},
|
||||
{AttributeId: "controller_busy_time", Name: "Controller Busy Time", Value: info.NvmeSmartHealthInformationLog.ControllerBusyTime, Threshold: -1},
|
||||
{AttributeId: "power_cycles", Name: "Power Cycles", Value: info.NvmeSmartHealthInformationLog.PowerCycles, Threshold: -1},
|
||||
{AttributeId: "power_on_hours", Name: "Power on Hours", Value: info.NvmeSmartHealthInformationLog.PowerOnHours, Threshold: -1},
|
||||
{AttributeId: "unsafe_shutdowns", Name: "Unsafe Shutdowns", Value: info.NvmeSmartHealthInformationLog.UnsafeShutdowns, Threshold: -1},
|
||||
{AttributeId: "media_errors", Name: "Media Errors", Value: info.NvmeSmartHealthInformationLog.MediaErrors, Threshold: 0},
|
||||
{AttributeId: "num_err_log_entries", Name: "Numb Err Log Entries", Value: info.NvmeSmartHealthInformationLog.NumErrLogEntries, Threshold: 0},
|
||||
{AttributeId: "warning_temp_time", Name: "Warning Temp Time", Value: info.NvmeSmartHealthInformationLog.WarningTempTime, Threshold: -1},
|
||||
{AttributeId: "critical_comp_time", Name: "Critical CompTime", Value: info.NvmeSmartHealthInformationLog.CriticalCompTime, Threshold: -1},
|
||||
}
|
||||
}
|
||||
|
||||
//generate SmartScsiAttribute entries from Scrutiny Collector Smart data.
|
||||
func (sm *Smart) ProcessScsiSmartInfo(info collector.SmartInfo) {
|
||||
sm.ScsiAttributes = []SmartScsiAttribute{
|
||||
{AttributeId: "scsi_grown_defect_list", Name: "Grown Defect List", Value: info.ScsiGrownDefectList, Threshold: 0},
|
||||
{AttributeId: "read.errors_corrected_by_eccfast", Name: "Read Errors Corrected by ECC Fast", Value: info.ScsiErrorCounterLog.Read.ErrorsCorrectedByEccfast, Threshold: -1},
|
||||
{AttributeId: "read.errors_corrected_by_eccdelayed", Name: "Read Errors Corrected by ECC Delayed", Value: info.ScsiErrorCounterLog.Read.ErrorsCorrectedByEccdelayed, Threshold: -1},
|
||||
{AttributeId: "read.errors_corrected_by_rereads_rewrites", Name: "Read Errors Corrected by ReReads/ReWrites", Value: info.ScsiErrorCounterLog.Read.ErrorsCorrectedByRereadsRewrites, Threshold: 0},
|
||||
{AttributeId: "read.total_errors_corrected", Name: "Read Total Errors Corrected", Value: info.ScsiErrorCounterLog.Read.TotalErrorsCorrected, Threshold: -1},
|
||||
{AttributeId: "read.correction_algorithm_invocations", Name: "Read Correction Algorithm Invocations", Value: info.ScsiErrorCounterLog.Read.CorrectionAlgorithmInvocations, Threshold: -1},
|
||||
{AttributeId: "read.total_uncorrected_errors", Name: "Read Total Uncorrected Errors", Value: info.ScsiErrorCounterLog.Read.TotalUncorrectedErrors, Threshold: 0},
|
||||
{AttributeId: "write.errors_corrected_by_eccfast", Name: "Write Errors Corrected by ECC Fast", Value: info.ScsiErrorCounterLog.Write.ErrorsCorrectedByEccfast, Threshold: -1},
|
||||
{AttributeId: "write.errors_corrected_by_eccdelayed", Name: "Write Errors Corrected by ECC Delayed", Value: info.ScsiErrorCounterLog.Write.ErrorsCorrectedByEccdelayed, Threshold: -1},
|
||||
{AttributeId: "write.errors_corrected_by_rereads_rewrites", Name: "Write Errors Corrected by ReReads/ReWrites", Value: info.ScsiErrorCounterLog.Write.ErrorsCorrectedByRereadsRewrites, Threshold: 0},
|
||||
{AttributeId: "write.total_errors_corrected", Name: "Write Total Errors Corrected", Value: info.ScsiErrorCounterLog.Write.TotalErrorsCorrected, Threshold: -1},
|
||||
{AttributeId: "write.correction_algorithm_invocations", Name: "Write Correction Algorithm Invocations", Value: info.ScsiErrorCounterLog.Write.CorrectionAlgorithmInvocations, Threshold: -1},
|
||||
{AttributeId: "write.total_uncorrected_errors", Name: "Write Total Uncorrected Errors", Value: info.ScsiErrorCounterLog.Write.TotalUncorrectedErrors, Threshold: 0},
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/metadata"
|
||||
"gorm.io/gorm"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const SmartAttributeStatusPassed = "passed"
|
||||
const SmartAttributeStatusFailed = "failed"
|
||||
const SmartAttributeStatusWarning = "warn"
|
||||
|
||||
type SmartAtaAttribute 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 []SmartAtaAttribute `gorm:"-" json:"history,omitempty"`
|
||||
}
|
||||
|
||||
//populate attribute status, using SMART Thresholds & Observed Metadata
|
||||
func (sa *SmartAtaAttribute) PopulateAttributeStatus() {
|
||||
if strings.ToUpper(sa.WhenFailed) == SmartWhenFailedFailingNow {
|
||||
//this attribute has previously failed
|
||||
sa.Status = SmartAttributeStatusFailed
|
||||
sa.StatusReason = "Attribute is failing manufacturer SMART threshold"
|
||||
|
||||
} else if strings.ToUpper(sa.WhenFailed) == SmartWhenFailedInThePast {
|
||||
sa.Status = SmartAttributeStatusWarning
|
||||
sa.StatusReason = "Attribute has previously failed manufacturer SMART threshold"
|
||||
}
|
||||
|
||||
if smartMetadata, ok := metadata.AtaMetadata[sa.AttributeId]; ok {
|
||||
sa.MetadataObservedThresholdStatus(smartMetadata)
|
||||
}
|
||||
|
||||
//check if status is blank, set to "passed"
|
||||
if len(sa.Status) == 0 {
|
||||
sa.Status = SmartAttributeStatusPassed
|
||||
}
|
||||
}
|
||||
|
||||
// compare the attribute (raw, normalized, transformed) value to observed thresholds, and update status if necessary
|
||||
func (sa *SmartAtaAttribute) MetadataObservedThresholdStatus(smartMetadata metadata.AtaAttributeMetadata) {
|
||||
//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
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/metadata"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type SmartNvmeAttribute struct {
|
||||
gorm.Model
|
||||
|
||||
SmartId int `json:"smart_id"`
|
||||
Smart Device `json:"-" gorm:"foreignkey:SmartId"` // use SmartId as foreign key
|
||||
|
||||
AttributeId string `json:"attribute_id"` //json string from smartctl
|
||||
Name string `json:"name"`
|
||||
Value int `json:"value"`
|
||||
Threshold int `json:"thresh"`
|
||||
|
||||
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 []SmartNvmeAttribute `gorm:"-" json:"history,omitempty"`
|
||||
}
|
||||
|
||||
//populate attribute status, using SMART Thresholds & Observed Metadata
|
||||
func (sa *SmartNvmeAttribute) PopulateAttributeStatus() {
|
||||
|
||||
//-1 is a special number meaning no threshold.
|
||||
if sa.Threshold != -1 {
|
||||
if smartMetadata, ok := metadata.NmveMetadata[sa.AttributeId]; ok {
|
||||
//check what the ideal is. Ideal tells us if we our recorded value needs to be above, or below the threshold
|
||||
if (smartMetadata.Ideal == "low" && sa.Value > sa.Threshold) ||
|
||||
(smartMetadata.Ideal == "high" && sa.Value < sa.Threshold) {
|
||||
sa.Status = SmartAttributeStatusFailed
|
||||
sa.StatusReason = "Attribute is failing recommended SMART threshold"
|
||||
}
|
||||
}
|
||||
}
|
||||
//TODO: eventually figure out the critical_warning bits and determine correct error messages here.
|
||||
|
||||
//check if status is blank, set to "passed"
|
||||
if len(sa.Status) == 0 {
|
||||
sa.Status = SmartAttributeStatusPassed
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/metadata"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type SmartScsiAttribute struct {
|
||||
gorm.Model
|
||||
|
||||
SmartId int `json:"smart_id"`
|
||||
Smart Device `json:"-" gorm:"foreignkey:SmartId"` // use SmartId as foreign key
|
||||
|
||||
AttributeId string `json:"attribute_id"` //json string from smartctl
|
||||
Name string `json:"name"`
|
||||
Value int `json:"value"`
|
||||
Threshold int `json:"thresh"`
|
||||
|
||||
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 []SmartScsiAttribute `gorm:"-" json:"history,omitempty"`
|
||||
}
|
||||
|
||||
//populate attribute status, using SMART Thresholds & Observed Metadata
|
||||
func (sa *SmartScsiAttribute) PopulateAttributeStatus() {
|
||||
|
||||
//-1 is a special number meaning no threshold.
|
||||
if sa.Threshold != -1 {
|
||||
if smartMetadata, ok := metadata.NmveMetadata[sa.AttributeId]; ok {
|
||||
//check what the ideal is. Ideal tells us if we our recorded value needs to be above, or below the threshold
|
||||
if (smartMetadata.Ideal == "low" && sa.Value > sa.Threshold) ||
|
||||
(smartMetadata.Ideal == "high" && sa.Value < sa.Threshold) {
|
||||
sa.Status = SmartAttributeStatusFailed
|
||||
sa.StatusReason = "Attribute is failing recommended SMART threshold"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//check if status is blank, set to "passed"
|
||||
if len(sa.Status) == 0 {
|
||||
sa.Status = SmartAttributeStatusPassed
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
package db_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/models/collector"
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/models/db"
|
||||
"github.com/stretchr/testify/require"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFromCollectorSmartInfo(t *testing.T) {
|
||||
//setup
|
||||
smartDataFile, err := os.Open("../testdata/smart-ata.json")
|
||||
require.NoError(t, err)
|
||||
defer smartDataFile.Close()
|
||||
|
||||
var smartJson collector.SmartInfo
|
||||
|
||||
smartDataBytes, err := ioutil.ReadAll(smartDataFile)
|
||||
require.NoError(t, err)
|
||||
err = json.Unmarshal(smartDataBytes, &smartJson)
|
||||
require.NoError(t, err)
|
||||
|
||||
//test
|
||||
smartMdl := db.Smart{}
|
||||
err = smartMdl.FromCollectorSmartInfo("WWN-test", smartJson)
|
||||
|
||||
//assert
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "WWN-test", smartMdl.DeviceWWN)
|
||||
require.Equal(t, "passed", smartMdl.SmartStatus)
|
||||
require.Equal(t, 18, len(smartMdl.AtaAttributes))
|
||||
require.Equal(t, 0, len(smartMdl.NvmeAttributes))
|
||||
require.Equal(t, 0, len(smartMdl.ScsiAttributes))
|
||||
|
||||
//check that temperature was correctly parsed
|
||||
for _, attr := range smartMdl.AtaAttributes {
|
||||
if attr.AttributeId == 194 {
|
||||
require.Equal(t, int64(163210330144), attr.RawValue)
|
||||
require.Equal(t, int64(32), attr.TransformedValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromCollectorSmartInfo_Fail(t *testing.T) {
|
||||
//setup
|
||||
smartDataFile, err := os.Open("../testdata/smart-fail.json")
|
||||
require.NoError(t, err)
|
||||
defer smartDataFile.Close()
|
||||
|
||||
var smartJson collector.SmartInfo
|
||||
|
||||
smartDataBytes, err := ioutil.ReadAll(smartDataFile)
|
||||
require.NoError(t, err)
|
||||
err = json.Unmarshal(smartDataBytes, &smartJson)
|
||||
require.NoError(t, err)
|
||||
|
||||
//test
|
||||
smartMdl := db.Smart{}
|
||||
err = smartMdl.FromCollectorSmartInfo("WWN-test", smartJson)
|
||||
|
||||
//assert
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "WWN-test", smartMdl.DeviceWWN)
|
||||
require.Equal(t, "failed", smartMdl.SmartStatus)
|
||||
require.Equal(t, 0, len(smartMdl.AtaAttributes))
|
||||
require.Equal(t, 0, len(smartMdl.NvmeAttributes))
|
||||
require.Equal(t, 0, len(smartMdl.ScsiAttributes))
|
||||
}
|
||||
|
||||
func TestFromCollectorSmartInfo_Fail2(t *testing.T) {
|
||||
//setup
|
||||
smartDataFile, err := os.Open("../testdata/smart-fail2.json")
|
||||
require.NoError(t, err)
|
||||
defer smartDataFile.Close()
|
||||
|
||||
var smartJson collector.SmartInfo
|
||||
|
||||
smartDataBytes, err := ioutil.ReadAll(smartDataFile)
|
||||
require.NoError(t, err)
|
||||
err = json.Unmarshal(smartDataBytes, &smartJson)
|
||||
require.NoError(t, err)
|
||||
|
||||
//test
|
||||
smartMdl := db.Smart{}
|
||||
err = smartMdl.FromCollectorSmartInfo("WWN-test", smartJson)
|
||||
|
||||
//assert
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "WWN-test", smartMdl.DeviceWWN)
|
||||
require.Equal(t, "failed", smartMdl.SmartStatus)
|
||||
require.Equal(t, 17, len(smartMdl.AtaAttributes))
|
||||
require.Equal(t, 0, len(smartMdl.NvmeAttributes))
|
||||
require.Equal(t, 0, len(smartMdl.ScsiAttributes))
|
||||
}
|
||||
|
||||
func TestFromCollectorSmartInfo_Nvme(t *testing.T) {
|
||||
//setup
|
||||
smartDataFile, err := os.Open("../testdata/smart-nvme.json")
|
||||
require.NoError(t, err)
|
||||
defer smartDataFile.Close()
|
||||
|
||||
var smartJson collector.SmartInfo
|
||||
|
||||
smartDataBytes, err := ioutil.ReadAll(smartDataFile)
|
||||
require.NoError(t, err)
|
||||
err = json.Unmarshal(smartDataBytes, &smartJson)
|
||||
require.NoError(t, err)
|
||||
|
||||
//test
|
||||
smartMdl := db.Smart{}
|
||||
err = smartMdl.FromCollectorSmartInfo("WWN-test", smartJson)
|
||||
|
||||
//assert
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "WWN-test", smartMdl.DeviceWWN)
|
||||
require.Equal(t, "passed", smartMdl.SmartStatus)
|
||||
require.Equal(t, 0, len(smartMdl.AtaAttributes))
|
||||
require.Equal(t, 16, len(smartMdl.NvmeAttributes))
|
||||
require.Equal(t, 0, len(smartMdl.ScsiAttributes))
|
||||
|
||||
require.Equal(t, 111303174, smartMdl.NvmeAttributes[6].Value)
|
||||
require.Equal(t, 83170961, smartMdl.NvmeAttributes[7].Value)
|
||||
}
|
||||
|
||||
func TestFromCollectorSmartInfo_Scsi(t *testing.T) {
|
||||
//setup
|
||||
smartDataFile, err := os.Open("../testdata/smart-scsi.json")
|
||||
require.NoError(t, err)
|
||||
defer smartDataFile.Close()
|
||||
|
||||
var smartJson collector.SmartInfo
|
||||
|
||||
smartDataBytes, err := ioutil.ReadAll(smartDataFile)
|
||||
require.NoError(t, err)
|
||||
err = json.Unmarshal(smartDataBytes, &smartJson)
|
||||
require.NoError(t, err)
|
||||
|
||||
//test
|
||||
smartMdl := db.Smart{}
|
||||
err = smartMdl.FromCollectorSmartInfo("WWN-test", smartJson)
|
||||
|
||||
//assert
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "WWN-test", smartMdl.DeviceWWN)
|
||||
require.Equal(t, "passed", smartMdl.SmartStatus)
|
||||
require.Equal(t, 0, len(smartMdl.AtaAttributes))
|
||||
require.Equal(t, 0, len(smartMdl.NvmeAttributes))
|
||||
require.Equal(t, 13, len(smartMdl.ScsiAttributes))
|
||||
|
||||
require.Equal(t, 56, smartMdl.ScsiAttributes[0].Value)
|
||||
require.Equal(t, 300357663, smartMdl.ScsiAttributes[4].Value) //total_errors_corrected
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg"
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/models/collector"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DeviceWrapper struct {
|
||||
Success bool `json:"success"`
|
||||
Errors []error `json:"errors"`
|
||||
Data []Device `json:"data"`
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
//GORM attributes, see: http://gorm.io/docs/conventions.html
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt *time.Time
|
||||
|
||||
WWN string `json:"wwn" gorm:"primary_key"`
|
||||
|
||||
DeviceName string `json:"device_name"`
|
||||
Manufacturer string `json:"manufacturer"`
|
||||
ModelName string `json:"model_name"`
|
||||
InterfaceType string `json:"interface_type"`
|
||||
InterfaceSpeed string `json:"interface_speed"`
|
||||
SerialNumber string `json:"serial_number"`
|
||||
Firmware string `json:"firmware"`
|
||||
RotationSpeed int `json:"rotational_speed"`
|
||||
Capacity int64 `json:"capacity"`
|
||||
FormFactor string `json:"form_factor"`
|
||||
SmartSupport bool `json:"smart_support"`
|
||||
DeviceProtocol string `json:"device_protocol"` //protocol determines which smart attribute types are available (ATA, NVMe, SCSI)
|
||||
DeviceType string `json:"device_type"` //device type is used for querying with -d/t flag, should only be used by collector.
|
||||
|
||||
// User provided metadata
|
||||
Label string `json:"label"`
|
||||
HostId string `json:"host_id"`
|
||||
|
||||
// Data set by Scrutiny
|
||||
DeviceStatus pkg.DeviceStatus `json:"device_status"`
|
||||
}
|
||||
|
||||
func (dv *Device) IsAta() bool {
|
||||
return dv.DeviceProtocol == pkg.DeviceProtocolAta
|
||||
}
|
||||
|
||||
func (dv *Device) IsScsi() bool {
|
||||
return dv.DeviceProtocol == pkg.DeviceProtocolScsi
|
||||
}
|
||||
|
||||
func (dv *Device) IsNvme() bool {
|
||||
return dv.DeviceProtocol == pkg.DeviceProtocolNvme
|
||||
}
|
||||
|
||||
//
|
||||
////This method requires a device with an array of SmartResults.
|
||||
////It will remove all SmartResults other than the first (the latest one)
|
||||
////All removed SmartResults, will be processed, grouping SmartAtaAttribute by attribute_id
|
||||
//// and adding theme to an array called History.
|
||||
//func (dv *Device) SquashHistory() error {
|
||||
// if len(dv.SmartResults) <= 1 {
|
||||
// return nil //no ataHistory found. ignore
|
||||
// }
|
||||
//
|
||||
// latestSmartResultSlice := dv.SmartResults[0:1]
|
||||
// historicalSmartResultSlice := dv.SmartResults[1:]
|
||||
//
|
||||
// //re-assign the latest slice to the SmartResults field
|
||||
// dv.SmartResults = latestSmartResultSlice
|
||||
//
|
||||
// //process the historical slice for ATA data
|
||||
// if len(dv.SmartResults[0].AtaAttributes) > 0 {
|
||||
// ataHistory := map[int][]SmartAtaAttribute{}
|
||||
// for _, smartResult := range historicalSmartResultSlice {
|
||||
// for _, smartAttribute := range smartResult.AtaAttributes {
|
||||
// if _, ok := ataHistory[smartAttribute.AttributeId]; !ok {
|
||||
// ataHistory[smartAttribute.AttributeId] = []SmartAtaAttribute{}
|
||||
// }
|
||||
// ataHistory[smartAttribute.AttributeId] = append(ataHistory[smartAttribute.AttributeId], smartAttribute)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// //now assign the historical slices to the AtaAttributes in the latest SmartResults
|
||||
// for sandx, smartAttribute := range dv.SmartResults[0].AtaAttributes {
|
||||
// if attributeHistory, ok := ataHistory[smartAttribute.AttributeId]; ok {
|
||||
// dv.SmartResults[0].AtaAttributes[sandx].History = attributeHistory
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// //process the historical slice for Nvme data
|
||||
// if len(dv.SmartResults[0].NvmeAttributes) > 0 {
|
||||
// nvmeHistory := map[string][]SmartNvmeAttribute{}
|
||||
// for _, smartResult := range historicalSmartResultSlice {
|
||||
// for _, smartAttribute := range smartResult.NvmeAttributes {
|
||||
// if _, ok := nvmeHistory[smartAttribute.AttributeId]; !ok {
|
||||
// nvmeHistory[smartAttribute.AttributeId] = []SmartNvmeAttribute{}
|
||||
// }
|
||||
// nvmeHistory[smartAttribute.AttributeId] = append(nvmeHistory[smartAttribute.AttributeId], smartAttribute)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// //now assign the historical slices to the AtaAttributes in the latest SmartResults
|
||||
// for sandx, smartAttribute := range dv.SmartResults[0].NvmeAttributes {
|
||||
// if attributeHistory, ok := nvmeHistory[smartAttribute.AttributeId]; ok {
|
||||
// dv.SmartResults[0].NvmeAttributes[sandx].History = attributeHistory
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// //process the historical slice for Scsi data
|
||||
// if len(dv.SmartResults[0].ScsiAttributes) > 0 {
|
||||
// scsiHistory := map[string][]SmartScsiAttribute{}
|
||||
// for _, smartResult := range historicalSmartResultSlice {
|
||||
// for _, smartAttribute := range smartResult.ScsiAttributes {
|
||||
// if _, ok := scsiHistory[smartAttribute.AttributeId]; !ok {
|
||||
// scsiHistory[smartAttribute.AttributeId] = []SmartScsiAttribute{}
|
||||
// }
|
||||
// scsiHistory[smartAttribute.AttributeId] = append(scsiHistory[smartAttribute.AttributeId], smartAttribute)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// //now assign the historical slices to the AtaAttributes in the latest SmartResults
|
||||
// for sandx, smartAttribute := range dv.SmartResults[0].ScsiAttributes {
|
||||
// if attributeHistory, ok := scsiHistory[smartAttribute.AttributeId]; ok {
|
||||
// dv.SmartResults[0].ScsiAttributes[sandx].History = attributeHistory
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return nil
|
||||
//}
|
||||
//
|
||||
//func (dv *Device) ApplyMetadataRules() error {
|
||||
//
|
||||
// //embed metadata in the latest smart attributes object
|
||||
// if len(dv.SmartResults) > 0 {
|
||||
// for ndx, attr := range dv.SmartResults[0].AtaAttributes {
|
||||
// attr.PopulateAttributeStatus()
|
||||
// dv.SmartResults[0].AtaAttributes[ndx] = attr
|
||||
// }
|
||||
//
|
||||
// for ndx, attr := range dv.SmartResults[0].NvmeAttributes {
|
||||
// attr.PopulateAttributeStatus()
|
||||
// dv.SmartResults[0].NvmeAttributes[ndx] = attr
|
||||
//
|
||||
// }
|
||||
//
|
||||
// for ndx, attr := range dv.SmartResults[0].ScsiAttributes {
|
||||
// attr.PopulateAttributeStatus()
|
||||
// dv.SmartResults[0].ScsiAttributes[ndx] = attr
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// return nil
|
||||
//}
|
||||
|
||||
// This function is called every time the collector sends SMART data to the API.
|
||||
// It can be used to update device data that can change over time.
|
||||
func (dv *Device) UpdateFromCollectorSmartInfo(info collector.SmartInfo) error {
|
||||
dv.Firmware = info.FirmwareVersion
|
||||
dv.DeviceProtocol = info.Device.Protocol
|
||||
|
||||
if !info.SmartStatus.Passed {
|
||||
dv.DeviceStatus = pkg.Set(dv.DeviceStatus, pkg.DeviceStatusFailedSmart)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/models/measurements"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DeviceSummary struct {
|
||||
Device Device `json:"device"`
|
||||
|
||||
SmartResults *SmartSummary `json:"smart,omitempty"`
|
||||
TempHistory []measurements.SmartTemperature `json:"temp_history,omitempty"`
|
||||
}
|
||||
type SmartSummary struct {
|
||||
// Collector Summary Data
|
||||
CollectorDate time.Time `json:"collector_date,omitempty"`
|
||||
Temp int64 `json:"temp,omitempty"`
|
||||
PowerOnHours int64 `json:"power_on_hours,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package measurements
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg"
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/metadata"
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/models/collector"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Smart struct {
|
||||
Date time.Time `json:"date"`
|
||||
DeviceWWN string `json:"device_wwn"` //(tag)
|
||||
DeviceProtocol string `json:"device_protocol"`
|
||||
|
||||
//Metrics (fields)
|
||||
Temp int64 `json:"temp"`
|
||||
PowerOnHours int64 `json:"power_on_hours"`
|
||||
PowerCycleCount int64 `json:"power_cycle_count"`
|
||||
|
||||
//Attributes (fields)
|
||||
Attributes map[string]SmartAttribute `json:"attrs"`
|
||||
}
|
||||
|
||||
func (sm *Smart) Flatten() (tags map[string]string, fields map[string]interface{}) {
|
||||
tags = map[string]string{
|
||||
"device_wwn": sm.DeviceWWN,
|
||||
"device_protocol": sm.DeviceProtocol,
|
||||
}
|
||||
|
||||
fields = map[string]interface{}{
|
||||
"temp": sm.Temp,
|
||||
"power_on_hours": sm.PowerOnHours,
|
||||
"power_cycle_count": sm.PowerCycleCount,
|
||||
}
|
||||
|
||||
for _, attr := range sm.Attributes {
|
||||
for attrKey, attrVal := range attr.Flatten() {
|
||||
fields[attrKey] = attrVal
|
||||
}
|
||||
}
|
||||
|
||||
return tags, fields
|
||||
}
|
||||
|
||||
func NewSmartFromInfluxDB(attrs map[string]interface{}) (*Smart, error) {
|
||||
//go though the massive map returned from influxdb. If a key is associated with the Smart struct, assign it. If it starts with "attr.*" group it by attributeId, and pass to attribute inflate.
|
||||
|
||||
sm := Smart{
|
||||
//required fields
|
||||
Date: attrs["_time"].(time.Time),
|
||||
DeviceWWN: attrs["device_wwn"].(string),
|
||||
DeviceProtocol: attrs["device_protocol"].(string),
|
||||
|
||||
Attributes: map[string]SmartAttribute{},
|
||||
}
|
||||
|
||||
log.Printf("Prefetched Smart: %v\n", sm)
|
||||
|
||||
//two steps (because we dont know the
|
||||
for key, val := range attrs {
|
||||
log.Printf("Found Attribute (%s = %v)\n", key, val)
|
||||
|
||||
switch key {
|
||||
case "temp":
|
||||
sm.Temp = val.(int64)
|
||||
case "power_on_hours":
|
||||
sm.PowerOnHours = val.(int64)
|
||||
case "power_cycle_count":
|
||||
sm.PowerCycleCount = val.(int64)
|
||||
default:
|
||||
// this key is unknown.
|
||||
if !strings.HasPrefix(key, "attr.") {
|
||||
continue
|
||||
}
|
||||
//this is a attribute, lets group it with its related "siblings", populating a SmartAttribute object
|
||||
keyParts := strings.Split(key, ".")
|
||||
attributeId := keyParts[1]
|
||||
if _, ok := sm.Attributes[attributeId]; !ok {
|
||||
// init the attribute group
|
||||
if sm.DeviceProtocol == pkg.DeviceProtocolAta {
|
||||
sm.Attributes[attributeId] = &SmartAtaAttribute{}
|
||||
} else if sm.DeviceProtocol == pkg.DeviceProtocolNvme {
|
||||
sm.Attributes[attributeId] = &SmartNvmeAttribute{}
|
||||
} else if sm.DeviceProtocol == pkg.DeviceProtocolScsi {
|
||||
sm.Attributes[attributeId] = &SmartScsiAttribute{}
|
||||
} else {
|
||||
return nil, fmt.Errorf("Unknown Device Protocol: %s", sm.DeviceProtocol)
|
||||
}
|
||||
}
|
||||
|
||||
sm.Attributes[attributeId].Inflate(key, val)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
log.Printf("########NUMBER OF ATTRIBUTES: %v", len(sm.Attributes))
|
||||
log.Printf("########SMART: %v", sm)
|
||||
|
||||
//panic("ERROR HERE.")
|
||||
|
||||
//log.Printf("Sm.Attributes: %v", sm.Attributes)
|
||||
//log.Printf("sm.Attributes[attributeId]: %v", sm.Attributes[attributeId])
|
||||
|
||||
return &sm, nil
|
||||
}
|
||||
|
||||
//Parse Collector SMART data results and create Smart object (and associated SmartAtaAttribute entries)
|
||||
func (sm *Smart) FromCollectorSmartInfo(wwn string, info collector.SmartInfo) error {
|
||||
sm.DeviceWWN = wwn
|
||||
sm.Date = time.Unix(info.LocalTime.TimeT, 0)
|
||||
|
||||
//smart metrics
|
||||
sm.Temp = info.Temperature.Current
|
||||
sm.PowerCycleCount = info.PowerCycleCount
|
||||
sm.PowerOnHours = info.PowerOnTime.Hours
|
||||
|
||||
sm.DeviceProtocol = info.Device.Protocol
|
||||
// process ATA/NVME/SCSI protocol data
|
||||
sm.Attributes = map[string]SmartAttribute{}
|
||||
if sm.DeviceProtocol == pkg.DeviceProtocolAta {
|
||||
sm.ProcessAtaSmartInfo(info)
|
||||
} else if sm.DeviceProtocol == pkg.DeviceProtocolNvme {
|
||||
sm.ProcessNvmeSmartInfo(info)
|
||||
} else if sm.DeviceProtocol == pkg.DeviceProtocolScsi {
|
||||
sm.ProcessScsiSmartInfo(info)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//generate SmartAtaAttribute entries from Scrutiny Collector Smart data.
|
||||
func (sm *Smart) ProcessAtaSmartInfo(info collector.SmartInfo) {
|
||||
for _, collectorAttr := range info.AtaSmartAttributes.Table {
|
||||
attrModel := SmartAtaAttribute{
|
||||
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.AtaMetadata[collectorAttr.ID]; ok {
|
||||
attrModel.Name = smartMetadata.DisplayName
|
||||
if smartMetadata.Transform != nil {
|
||||
attrModel.TransformedValue = smartMetadata.Transform(attrModel.Value, attrModel.RawValue, attrModel.RawString)
|
||||
}
|
||||
}
|
||||
sm.Attributes[string(collectorAttr.ID)] = &attrModel
|
||||
}
|
||||
}
|
||||
|
||||
//generate SmartNvmeAttribute entries from Scrutiny Collector Smart data.
|
||||
func (sm *Smart) ProcessNvmeSmartInfo(info collector.SmartInfo) {
|
||||
sm.Attributes = map[string]SmartAttribute{
|
||||
"critical_warning": &SmartNvmeAttribute{AttributeId: "critical_warning", Name: "Critical Warning", Value: info.NvmeSmartHealthInformationLog.CriticalWarning, Threshold: 0},
|
||||
"temperature": &SmartNvmeAttribute{AttributeId: "temperature", Name: "Temperature", Value: info.NvmeSmartHealthInformationLog.Temperature, Threshold: -1},
|
||||
"available_spare": &SmartNvmeAttribute{AttributeId: "available_spare", Name: "Available Spare", Value: info.NvmeSmartHealthInformationLog.AvailableSpare, Threshold: info.NvmeSmartHealthInformationLog.AvailableSpareThreshold},
|
||||
"percentage_used": &SmartNvmeAttribute{AttributeId: "percentage_used", Name: "Percentage Used", Value: info.NvmeSmartHealthInformationLog.PercentageUsed, Threshold: 100},
|
||||
"data_units_read": &SmartNvmeAttribute{AttributeId: "data_units_read", Name: "Data Units Read", Value: info.NvmeSmartHealthInformationLog.DataUnitsRead, Threshold: -1},
|
||||
"data_units_written": &SmartNvmeAttribute{AttributeId: "data_units_written", Name: "Data Units Written", Value: info.NvmeSmartHealthInformationLog.DataUnitsWritten, Threshold: -1},
|
||||
"host_reads": &SmartNvmeAttribute{AttributeId: "host_reads", Name: "Host Reads", Value: info.NvmeSmartHealthInformationLog.HostReads, Threshold: -1},
|
||||
"host_writes": &SmartNvmeAttribute{AttributeId: "host_writes", Name: "Host Writes", Value: info.NvmeSmartHealthInformationLog.HostWrites, Threshold: -1},
|
||||
"controller_busy_time": &SmartNvmeAttribute{AttributeId: "controller_busy_time", Name: "Controller Busy Time", Value: info.NvmeSmartHealthInformationLog.ControllerBusyTime, Threshold: -1},
|
||||
"power_cycles": &SmartNvmeAttribute{AttributeId: "power_cycles", Name: "Power Cycles", Value: info.NvmeSmartHealthInformationLog.PowerCycles, Threshold: -1},
|
||||
"power_on_hours": &SmartNvmeAttribute{AttributeId: "power_on_hours", Name: "Power on Hours", Value: info.NvmeSmartHealthInformationLog.PowerOnHours, Threshold: -1},
|
||||
"unsafe_shutdowns": &SmartNvmeAttribute{AttributeId: "unsafe_shutdowns", Name: "Unsafe Shutdowns", Value: info.NvmeSmartHealthInformationLog.UnsafeShutdowns, Threshold: -1},
|
||||
"media_errors": &SmartNvmeAttribute{AttributeId: "media_errors", Name: "Media Errors", Value: info.NvmeSmartHealthInformationLog.MediaErrors, Threshold: 0},
|
||||
"num_err_log_entries": &SmartNvmeAttribute{AttributeId: "num_err_log_entries", Name: "Numb Err Log Entries", Value: info.NvmeSmartHealthInformationLog.NumErrLogEntries, Threshold: 0},
|
||||
"warning_temp_time": &SmartNvmeAttribute{AttributeId: "warning_temp_time", Name: "Warning Temp Time", Value: info.NvmeSmartHealthInformationLog.WarningTempTime, Threshold: -1},
|
||||
"critical_comp_time": &SmartNvmeAttribute{AttributeId: "critical_comp_time", Name: "Critical CompTime", Value: info.NvmeSmartHealthInformationLog.CriticalCompTime, Threshold: -1},
|
||||
}
|
||||
}
|
||||
|
||||
//generate SmartScsiAttribute entries from Scrutiny Collector Smart data.
|
||||
func (sm *Smart) ProcessScsiSmartInfo(info collector.SmartInfo) {
|
||||
sm.Attributes = map[string]SmartAttribute{
|
||||
"scsi_grown_defect_list": &SmartScsiAttribute{AttributeId: "scsi_grown_defect_list", Name: "Grown Defect List", Value: info.ScsiGrownDefectList, Threshold: 0},
|
||||
"read_errors_corrected_by_eccfast": &SmartScsiAttribute{AttributeId: "read_errors_corrected_by_eccfast", Name: "Read Errors Corrected by ECC Fast", Value: info.ScsiErrorCounterLog.Read.ErrorsCorrectedByEccfast, Threshold: -1},
|
||||
"read_errors_corrected_by_eccdelayed": &SmartScsiAttribute{AttributeId: "read_errors_corrected_by_eccdelayed", Name: "Read Errors Corrected by ECC Delayed", Value: info.ScsiErrorCounterLog.Read.ErrorsCorrectedByEccdelayed, Threshold: -1},
|
||||
"read_errors_corrected_by_rereads_rewrites": &SmartScsiAttribute{AttributeId: "read_errors_corrected_by_rereads_rewrites", Name: "Read Errors Corrected by ReReads/ReWrites", Value: info.ScsiErrorCounterLog.Read.ErrorsCorrectedByRereadsRewrites, Threshold: 0},
|
||||
"read_total_errors_corrected": &SmartScsiAttribute{AttributeId: "read_total_errors_corrected", Name: "Read Total Errors Corrected", Value: info.ScsiErrorCounterLog.Read.TotalErrorsCorrected, Threshold: -1},
|
||||
"read_correction_algorithm_invocations": &SmartScsiAttribute{AttributeId: "read_correction_algorithm_invocations", Name: "Read Correction Algorithm Invocations", Value: info.ScsiErrorCounterLog.Read.CorrectionAlgorithmInvocations, Threshold: -1},
|
||||
"read_total_uncorrected_errors": &SmartScsiAttribute{AttributeId: "read_total_uncorrected_errors", Name: "Read Total Uncorrected Errors", Value: info.ScsiErrorCounterLog.Read.TotalUncorrectedErrors, Threshold: 0},
|
||||
"write_errors_corrected_by_eccfast": &SmartScsiAttribute{AttributeId: "write_errors_corrected_by_eccfast", Name: "Write Errors Corrected by ECC Fast", Value: info.ScsiErrorCounterLog.Write.ErrorsCorrectedByEccfast, Threshold: -1},
|
||||
"write_errors_corrected_by_eccdelayed": &SmartScsiAttribute{AttributeId: "write_errors_corrected_by_eccdelayed", Name: "Write Errors Corrected by ECC Delayed", Value: info.ScsiErrorCounterLog.Write.ErrorsCorrectedByEccdelayed, Threshold: -1},
|
||||
"write_errors_corrected_by_rereads_rewrites": &SmartScsiAttribute{AttributeId: "write_errors_corrected_by_rereads_rewrites", Name: "Write Errors Corrected by ReReads/ReWrites", Value: info.ScsiErrorCounterLog.Write.ErrorsCorrectedByRereadsRewrites, Threshold: 0},
|
||||
"write_total_errors_corrected": &SmartScsiAttribute{AttributeId: "write_total_errors_corrected", Name: "Write Total Errors Corrected", Value: info.ScsiErrorCounterLog.Write.TotalErrorsCorrected, Threshold: -1},
|
||||
"write_correction_algorithm_invocations": &SmartScsiAttribute{AttributeId: "write_correction_algorithm_invocations", Name: "Write Correction Algorithm Invocations", Value: info.ScsiErrorCounterLog.Write.CorrectionAlgorithmInvocations, Threshold: -1},
|
||||
"write_total_uncorrected_errors": &SmartScsiAttribute{AttributeId: "write_total_uncorrected_errors", Name: "Write Total Uncorrected Errors", Value: info.ScsiErrorCounterLog.Write.TotalUncorrectedErrors, Threshold: 0},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package measurements
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const SmartAttributeStatusPassed = "passed"
|
||||
const SmartAttributeStatusFailed = "failed"
|
||||
const SmartAttributeStatusWarning = "warn"
|
||||
|
||||
type SmartAtaAttribute struct {
|
||||
AttributeId int `json:"attribute_id"`
|
||||
Name string `json:"name"`
|
||||
Value int64 `json:"value"`
|
||||
Threshold int64 `json:"thresh"`
|
||||
Worst int64 `json:"worst"`
|
||||
RawValue int64 `json:"raw_value"`
|
||||
RawString string `json:"raw_string"`
|
||||
WhenFailed string `json:"when_failed"`
|
||||
|
||||
//Generated data
|
||||
TransformedValue int64 `json:"transformed_value"`
|
||||
Status string `json:"status,omitempty"`
|
||||
StatusReason string `json:"status_reason,omitempty"`
|
||||
FailureRate float64 `json:"failure_rate,omitempty"`
|
||||
}
|
||||
|
||||
func (sa *SmartAtaAttribute) Flatten() map[string]interface{} {
|
||||
|
||||
idString := strconv.Itoa(sa.AttributeId)
|
||||
|
||||
return map[string]interface{}{
|
||||
fmt.Sprintf("attr.%s.attribute_id", idString): idString,
|
||||
fmt.Sprintf("attr.%s.name", idString): sa.Name,
|
||||
fmt.Sprintf("attr.%s.value", idString): sa.Value,
|
||||
fmt.Sprintf("attr.%s.worst", idString): sa.Worst,
|
||||
fmt.Sprintf("attr.%s.thresh", idString): sa.Threshold,
|
||||
fmt.Sprintf("attr.%s.raw_value", idString): sa.RawValue,
|
||||
fmt.Sprintf("attr.%s.raw_string", idString): sa.RawString,
|
||||
fmt.Sprintf("attr.%s.when_failed", idString): sa.WhenFailed,
|
||||
}
|
||||
}
|
||||
func (sa *SmartAtaAttribute) Inflate(key string, val interface{}) {
|
||||
if val == nil {
|
||||
return
|
||||
}
|
||||
keyParts := strings.Split(key, ".")
|
||||
|
||||
switch keyParts[2] {
|
||||
case "attribute_id":
|
||||
attrId, err := strconv.Atoi(val.(string))
|
||||
if err == nil {
|
||||
sa.AttributeId = attrId
|
||||
}
|
||||
case "name":
|
||||
sa.Name = val.(string)
|
||||
case "value":
|
||||
sa.Value = val.(int64)
|
||||
case "worst":
|
||||
sa.Worst = val.(int64)
|
||||
case "thresh":
|
||||
sa.Threshold = val.(int64)
|
||||
case "raw_value":
|
||||
sa.RawValue = val.(int64)
|
||||
case "raw_string":
|
||||
sa.RawString = val.(string)
|
||||
case "when_failed":
|
||||
sa.WhenFailed = val.(string)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
////populate attribute status, using SMART Thresholds & Observed Metadata
|
||||
//func (sa *SmartAtaAttribute) PopulateAttributeStatus() {
|
||||
// if strings.ToUpper(sa.WhenFailed) == SmartWhenFailedFailingNow {
|
||||
// //this attribute has previously failed
|
||||
// sa.Status = SmartAttributeStatusFailed
|
||||
// sa.StatusReason = "Attribute is failing manufacturer SMART threshold"
|
||||
//
|
||||
// } else if strings.ToUpper(sa.WhenFailed) == SmartWhenFailedInThePast {
|
||||
// sa.Status = SmartAttributeStatusWarning
|
||||
// sa.StatusReason = "Attribute has previously failed manufacturer SMART threshold"
|
||||
// }
|
||||
//
|
||||
// if smartMetadata, ok := metadata.AtaMetadata[sa.AttributeId]; ok {
|
||||
// sa.MetadataObservedThresholdStatus(smartMetadata)
|
||||
// }
|
||||
//
|
||||
// //check if status is blank, set to "passed"
|
||||
// if len(sa.Status) == 0 {
|
||||
// sa.Status = SmartAttributeStatusPassed
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// compare the attribute (raw, normalized, transformed) value to observed thresholds, and update status if necessary
|
||||
//func (sa *SmartAtaAttribute) MetadataObservedThresholdStatus(smartMetadata metadata.AtaAttributeMetadata) {
|
||||
// //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
|
||||
//}
|
||||
@@ -0,0 +1,6 @@
|
||||
package measurements
|
||||
|
||||
type SmartAttribute interface {
|
||||
Flatten() (fields map[string]interface{})
|
||||
Inflate(key string, val interface{})
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package measurements
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SmartNvmeAttribute struct {
|
||||
AttributeId string `json:"attribute_id"` //json string from smartctl
|
||||
Name string `json:"name"`
|
||||
Value int64 `json:"value"`
|
||||
Threshold int64 `json:"thresh"`
|
||||
|
||||
TransformedValue int64 `json:"transformed_value"`
|
||||
Status string `json:"status,omitempty"`
|
||||
StatusReason string `json:"status_reason,omitempty"`
|
||||
FailureRate float64 `json:"failure_rate,omitempty"`
|
||||
}
|
||||
|
||||
func (sa *SmartNvmeAttribute) Flatten() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
fmt.Sprintf("attr.%s.attribute_id", sa.AttributeId): sa.AttributeId,
|
||||
fmt.Sprintf("attr.%s.name", sa.AttributeId): sa.Name,
|
||||
fmt.Sprintf("attr.%s.value", sa.AttributeId): sa.Value,
|
||||
fmt.Sprintf("attr.%s.thresh", sa.AttributeId): sa.Threshold,
|
||||
}
|
||||
}
|
||||
func (sa *SmartNvmeAttribute) Inflate(key string, val interface{}) {
|
||||
if val == nil {
|
||||
return
|
||||
}
|
||||
|
||||
keyParts := strings.Split(key, ".")
|
||||
|
||||
switch keyParts[2] {
|
||||
case "attribute_id":
|
||||
sa.AttributeId = val.(string)
|
||||
case "name":
|
||||
sa.Name = val.(string)
|
||||
case "value":
|
||||
sa.Value = val.(int64)
|
||||
case "thresh":
|
||||
sa.Threshold = val.(int64)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
////populate attribute status, using SMART Thresholds & Observed Metadata
|
||||
//func (sa *SmartNvmeAttribute) PopulateAttributeStatus() {
|
||||
//
|
||||
// //-1 is a special number meaning no threshold.
|
||||
// if sa.Threshold != -1 {
|
||||
// if smartMetadata, ok := metadata.NmveMetadata[sa.AttributeId]; ok {
|
||||
// //check what the ideal is. Ideal tells us if we our recorded value needs to be above, or below the threshold
|
||||
// if (smartMetadata.Ideal == "low" && sa.Value > sa.Threshold) ||
|
||||
// (smartMetadata.Ideal == "high" && sa.Value < sa.Threshold) {
|
||||
// sa.Status = SmartAttributeStatusFailed
|
||||
// sa.StatusReason = "Attribute is failing recommended SMART threshold"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// //TODO: eventually figure out the critical_warning bits and determine correct error messages here.
|
||||
//
|
||||
// //check if status is blank, set to "passed"
|
||||
// if len(sa.Status) == 0 {
|
||||
// sa.Status = SmartAttributeStatusPassed
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,67 @@
|
||||
package measurements
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SmartScsiAttribute struct {
|
||||
AttributeId string `json:"attribute_id"` //json string from smartctl
|
||||
Name string `json:"name"`
|
||||
Value int64 `json:"value"`
|
||||
Threshold int64 `json:"thresh"`
|
||||
|
||||
TransformedValue int64 `json:"transformed_value"`
|
||||
Status string `json:"status,omitempty"`
|
||||
StatusReason string `json:"status_reason,omitempty"`
|
||||
FailureRate float64 `json:"failure_rate,omitempty"`
|
||||
}
|
||||
|
||||
func (sa *SmartScsiAttribute) Flatten() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
fmt.Sprintf("attr.%s.attribute_id", sa.AttributeId): sa.AttributeId,
|
||||
fmt.Sprintf("attr.%s.name", sa.AttributeId): sa.Name,
|
||||
fmt.Sprintf("attr.%s.value", sa.AttributeId): sa.Value,
|
||||
fmt.Sprintf("attr.%s.thresh", sa.AttributeId): sa.Threshold,
|
||||
}
|
||||
}
|
||||
func (sa *SmartScsiAttribute) Inflate(key string, val interface{}) {
|
||||
if val == nil {
|
||||
return
|
||||
}
|
||||
|
||||
keyParts := strings.Split(key, ".")
|
||||
|
||||
switch keyParts[2] {
|
||||
case "attribute_id":
|
||||
sa.AttributeId = val.(string)
|
||||
case "name":
|
||||
sa.Name = val.(string)
|
||||
case "value":
|
||||
sa.Value = val.(int64)
|
||||
case "thresh":
|
||||
sa.Threshold = val.(int64)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
////populate attribute status, using SMART Thresholds & Observed Metadata
|
||||
//func (sa *SmartScsiAttribute) PopulateAttributeStatus() {
|
||||
//
|
||||
// //-1 is a special number meaning no threshold.
|
||||
// if sa.Threshold != -1 {
|
||||
// if smartMetadata, ok := metadata.NmveMetadata[sa.AttributeId]; ok {
|
||||
// //check what the ideal is. Ideal tells us if we our recorded value needs to be above, or below the threshold
|
||||
// if (smartMetadata.Ideal == "low" && sa.Value > sa.Threshold) ||
|
||||
// (smartMetadata.Ideal == "high" && sa.Value < sa.Threshold) {
|
||||
// sa.Status = SmartAttributeStatusFailed
|
||||
// sa.StatusReason = "Attribute is failing recommended SMART threshold"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// //check if status is blank, set to "passed"
|
||||
// if len(sa.Status) == 0 {
|
||||
// sa.Status = SmartAttributeStatusPassed
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,29 @@
|
||||
package measurements
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type SmartTemperature struct {
|
||||
Date time.Time `json:"date"`
|
||||
Temp int64 `json:"temp"`
|
||||
}
|
||||
|
||||
func (st *SmartTemperature) Flatten() (tags map[string]string, fields map[string]interface{}) {
|
||||
fields = map[string]interface{}{
|
||||
"temp": st.Temp,
|
||||
}
|
||||
tags = map[string]string{}
|
||||
|
||||
return tags, fields
|
||||
}
|
||||
|
||||
func (st *SmartTemperature) Inflate(key string, val interface{}) {
|
||||
if val == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if key == "temp" {
|
||||
st.Temp = val.(int64)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package measurements_test
|
||||
|
||||
//func TestFromCollectorSmartInfo(t *testing.T) {
|
||||
// //setup
|
||||
// smartDataFile, err := os.Open("../testdata/smart-ata.json")
|
||||
// require.NoError(t, err)
|
||||
// defer smartDataFile.Close()
|
||||
//
|
||||
// var smartJson collector.SmartInfo
|
||||
//
|
||||
// smartDataBytes, err := ioutil.ReadAll(smartDataFile)
|
||||
// require.NoError(t, err)
|
||||
// err = json.Unmarshal(smartDataBytes, &smartJson)
|
||||
// require.NoError(t, err)
|
||||
//
|
||||
// //test
|
||||
// smartMdl := db.Smart{}
|
||||
// err = smartMdl.FromCollectorSmartInfo("WWN-test", smartJson)
|
||||
//
|
||||
// //assert
|
||||
// require.NoError(t, err)
|
||||
// require.Equal(t, "WWN-test", smartMdl.DeviceWWN)
|
||||
// require.Equal(t, "passed", smartMdl.SmartStatus)
|
||||
// require.Equal(t, 18, len(smartMdl.Attributes))
|
||||
//
|
||||
// //check that temperature was correctly parsed
|
||||
// for _, attr := range smartMdl.Attributes {
|
||||
// if attr.AttributeId == 194 {
|
||||
// require.Equal(t, int64(163210330144), attr.RawValue)
|
||||
// require.Equal(t, int64(32), attr.TransformedValue)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//func TestFromCollectorSmartInfo_Fail(t *testing.T) {
|
||||
// //setup
|
||||
// smartDataFile, err := os.Open("../testdata/smart-fail.json")
|
||||
// require.NoError(t, err)
|
||||
// defer smartDataFile.Close()
|
||||
//
|
||||
// var smartJson collector.SmartInfo
|
||||
//
|
||||
// smartDataBytes, err := ioutil.ReadAll(smartDataFile)
|
||||
// require.NoError(t, err)
|
||||
// err = json.Unmarshal(smartDataBytes, &smartJson)
|
||||
// require.NoError(t, err)
|
||||
//
|
||||
// //test
|
||||
// smartMdl := db.Smart{}
|
||||
// err = smartMdl.FromCollectorSmartInfo("WWN-test", smartJson)
|
||||
//
|
||||
// //assert
|
||||
// require.NoError(t, err)
|
||||
// require.Equal(t, "WWN-test", smartMdl.DeviceWWN)
|
||||
// require.Equal(t, "failed", smartMdl.SmartStatus)
|
||||
// require.Equal(t, 0, len(smartMdl.AtaAttributes))
|
||||
// require.Equal(t, 0, len(smartMdl.NvmeAttributes))
|
||||
// require.Equal(t, 0, len(smartMdl.ScsiAttributes))
|
||||
//}
|
||||
//
|
||||
//func TestFromCollectorSmartInfo_Fail2(t *testing.T) {
|
||||
// //setup
|
||||
// smartDataFile, err := os.Open("../testdata/smart-fail2.json")
|
||||
// require.NoError(t, err)
|
||||
// defer smartDataFile.Close()
|
||||
//
|
||||
// var smartJson collector.SmartInfo
|
||||
//
|
||||
// smartDataBytes, err := ioutil.ReadAll(smartDataFile)
|
||||
// require.NoError(t, err)
|
||||
// err = json.Unmarshal(smartDataBytes, &smartJson)
|
||||
// require.NoError(t, err)
|
||||
//
|
||||
// //test
|
||||
// smartMdl := db.Smart{}
|
||||
// err = smartMdl.FromCollectorSmartInfo("WWN-test", smartJson)
|
||||
//
|
||||
// //assert
|
||||
// require.NoError(t, err)
|
||||
// require.Equal(t, "WWN-test", smartMdl.DeviceWWN)
|
||||
// require.Equal(t, "failed", smartMdl.SmartStatus)
|
||||
// require.Equal(t, 17, len(smartMdl.Attributes))
|
||||
//}
|
||||
//
|
||||
//func TestFromCollectorSmartInfo_Nvme(t *testing.T) {
|
||||
// //setup
|
||||
// smartDataFile, err := os.Open("../testdata/smart-nvme.json")
|
||||
// require.NoError(t, err)
|
||||
// defer smartDataFile.Close()
|
||||
//
|
||||
// var smartJson collector.SmartInfo
|
||||
//
|
||||
// smartDataBytes, err := ioutil.ReadAll(smartDataFile)
|
||||
// require.NoError(t, err)
|
||||
// err = json.Unmarshal(smartDataBytes, &smartJson)
|
||||
// require.NoError(t, err)
|
||||
//
|
||||
// //test
|
||||
// smartMdl := db.Smart{}
|
||||
// err = smartMdl.FromCollectorSmartInfo("WWN-test", smartJson)
|
||||
//
|
||||
// //assert
|
||||
// require.NoError(t, err)
|
||||
// require.Equal(t, "WWN-test", smartMdl.DeviceWWN)
|
||||
// require.Equal(t, "passed", smartMdl.SmartStatus)
|
||||
// require.Equal(t, 0, len(smartMdl.AtaAttributes))
|
||||
// require.Equal(t, 16, len(smartMdl.NvmeAttributes))
|
||||
// require.Equal(t, 0, len(smartMdl.ScsiAttributes))
|
||||
//
|
||||
// require.Equal(t, 111303174, smartMdl.NvmeAttributes[6].Value)
|
||||
// require.Equal(t, 83170961, smartMdl.NvmeAttributes[7].Value)
|
||||
//}
|
||||
//
|
||||
//func TestFromCollectorSmartInfo_Scsi(t *testing.T) {
|
||||
// //setup
|
||||
// smartDataFile, err := os.Open("../testdata/smart-scsi.json")
|
||||
// require.NoError(t, err)
|
||||
// defer smartDataFile.Close()
|
||||
//
|
||||
// var smartJson collector.SmartInfo
|
||||
//
|
||||
// smartDataBytes, err := ioutil.ReadAll(smartDataFile)
|
||||
// require.NoError(t, err)
|
||||
// err = json.Unmarshal(smartDataBytes, &smartJson)
|
||||
// require.NoError(t, err)
|
||||
//
|
||||
// //test
|
||||
// smartMdl := db.Smart{}
|
||||
// err = smartMdl.FromCollectorSmartInfo("WWN-test", smartJson)
|
||||
//
|
||||
// //assert
|
||||
// require.NoError(t, err)
|
||||
// require.Equal(t, "WWN-test", smartMdl.DeviceWWN)
|
||||
// require.Equal(t, "passed", smartMdl.SmartStatus)
|
||||
// require.Equal(t, 0, len(smartMdl.AtaAttributes))
|
||||
// require.Equal(t, 0, len(smartMdl.NvmeAttributes))
|
||||
// require.Equal(t, 13, len(smartMdl.ScsiAttributes))
|
||||
//
|
||||
// require.Equal(t, 56, smartMdl.ScsiAttributes[0].Value)
|
||||
// require.Equal(t, 300357663, smartMdl.ScsiAttributes[4].Value) //total_errors_corrected
|
||||
//}
|
||||
@@ -0,0 +1,5 @@
|
||||
package models
|
||||
|
||||
// Temperature Format
|
||||
// Date Format
|
||||
// Device History window
|
||||
@@ -0,0 +1,846 @@
|
||||
{
|
||||
"json_format_version": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"smartctl": {
|
||||
"version": [
|
||||
7,
|
||||
0
|
||||
],
|
||||
"svn_revision": "4883",
|
||||
"platform_info": "x86_64-linux-4.19.128-flatcar",
|
||||
"build_info": "(local build)",
|
||||
"argv": [
|
||||
"smartctl",
|
||||
"-j",
|
||||
"-a",
|
||||
"/dev/sdb"
|
||||
],
|
||||
"exit_status": 0
|
||||
},
|
||||
"device": {
|
||||
"name": "/dev/sdb",
|
||||
"info_name": "/dev/sdb [SAT]",
|
||||
"type": "sat",
|
||||
"protocol": "ATA"
|
||||
},
|
||||
"model_name": "WDC WD140EDFZ-11A0VA0",
|
||||
"serial_number": "9RK1XXXX",
|
||||
"wwn": {
|
||||
"naa": 5,
|
||||
"oui": 3274,
|
||||
"id": 10283057623
|
||||
},
|
||||
"firmware_version": "81.00A81",
|
||||
"user_capacity": {
|
||||
"blocks": 27344764928,
|
||||
"bytes": 14000519643136
|
||||
},
|
||||
"logical_block_size": 512,
|
||||
"physical_block_size": 4096,
|
||||
"rotation_rate": 5400,
|
||||
"form_factor": {
|
||||
"ata_value": 2,
|
||||
"name": "3.5 inches"
|
||||
},
|
||||
"in_smartctl_database": false,
|
||||
"ata_version": {
|
||||
"string": "ACS-2, ATA8-ACS T13/1699-D revision 4",
|
||||
"major_value": 1020,
|
||||
"minor_value": 41
|
||||
},
|
||||
"sata_version": {
|
||||
"string": "SATA 3.2",
|
||||
"value": 255
|
||||
},
|
||||
"interface_speed": {
|
||||
"max": {
|
||||
"sata_value": 14,
|
||||
"string": "6.0 Gb/s",
|
||||
"units_per_second": 60,
|
||||
"bits_per_unit": 100000000
|
||||
},
|
||||
"current": {
|
||||
"sata_value": 3,
|
||||
"string": "6.0 Gb/s",
|
||||
"units_per_second": 60,
|
||||
"bits_per_unit": 100000000
|
||||
}
|
||||
},
|
||||
"local_time": {
|
||||
"time_t": 1611419146,
|
||||
"asctime": "Sun Jun 30 00:03:30 2021 UTC"
|
||||
},
|
||||
"smart_status": {
|
||||
"passed": true
|
||||
},
|
||||
"ata_smart_data": {
|
||||
"offline_data_collection": {
|
||||
"status": {
|
||||
"value": 130,
|
||||
"string": "was completed without error",
|
||||
"passed": true
|
||||
},
|
||||
"completion_seconds": 101
|
||||
},
|
||||
"self_test": {
|
||||
"status": {
|
||||
"value": 241,
|
||||
"string": "in progress, 10% remaining",
|
||||
"remaining_percent": 10
|
||||
},
|
||||
"polling_minutes": {
|
||||
"short": 2,
|
||||
"extended": 1479
|
||||
}
|
||||
},
|
||||
"capabilities": {
|
||||
"values": [
|
||||
91,
|
||||
3
|
||||
],
|
||||
"exec_offline_immediate_supported": true,
|
||||
"offline_is_aborted_upon_new_cmd": false,
|
||||
"offline_surface_scan_supported": true,
|
||||
"self_tests_supported": true,
|
||||
"conveyance_self_test_supported": false,
|
||||
"selective_self_test_supported": true,
|
||||
"attribute_autosave_enabled": true,
|
||||
"error_logging_supported": true,
|
||||
"gp_logging_supported": true
|
||||
}
|
||||
},
|
||||
"ata_sct_capabilities": {
|
||||
"value": 61,
|
||||
"error_recovery_control_supported": true,
|
||||
"feature_control_supported": true,
|
||||
"data_table_supported": true
|
||||
},
|
||||
"ata_smart_attributes": {
|
||||
"revision": 16,
|
||||
"table": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Raw_Read_Error_Rate",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 1,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 11,
|
||||
"string": "PO-R-- ",
|
||||
"prefailure": true,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": true,
|
||||
"event_count": false,
|
||||
"auto_keep": false
|
||||
},
|
||||
"raw": {
|
||||
"value": 0,
|
||||
"string": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Throughput_Performance",
|
||||
"value": 135,
|
||||
"worst": 135,
|
||||
"thresh": 54,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 4,
|
||||
"string": "--S--- ",
|
||||
"prefailure": false,
|
||||
"updated_online": false,
|
||||
"performance": true,
|
||||
"error_rate": false,
|
||||
"event_count": false,
|
||||
"auto_keep": false
|
||||
},
|
||||
"raw": {
|
||||
"value": 108,
|
||||
"string": "108"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Spin_Up_Time",
|
||||
"value": 81,
|
||||
"worst": 81,
|
||||
"thresh": 1,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 7,
|
||||
"string": "POS--- ",
|
||||
"prefailure": true,
|
||||
"updated_online": true,
|
||||
"performance": true,
|
||||
"error_rate": false,
|
||||
"event_count": false,
|
||||
"auto_keep": false
|
||||
},
|
||||
"raw": {
|
||||
"value": 30089675132,
|
||||
"string": "380 (Average 380)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Start_Stop_Count",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 0,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 18,
|
||||
"string": "-O--C- ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": false
|
||||
},
|
||||
"raw": {
|
||||
"value": 9,
|
||||
"string": "9"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Reallocated_Sector_Ct",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 1,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 51,
|
||||
"string": "PO--CK ",
|
||||
"prefailure": true,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 0,
|
||||
"string": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"name": "Seek_Error_Rate",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 1,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 10,
|
||||
"string": "-O-R-- ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": true,
|
||||
"event_count": false,
|
||||
"auto_keep": false
|
||||
},
|
||||
"raw": {
|
||||
"value": 0,
|
||||
"string": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"name": "Seek_Time_Performance",
|
||||
"value": 133,
|
||||
"worst": 133,
|
||||
"thresh": 20,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 4,
|
||||
"string": "--S--- ",
|
||||
"prefailure": false,
|
||||
"updated_online": false,
|
||||
"performance": true,
|
||||
"error_rate": false,
|
||||
"event_count": false,
|
||||
"auto_keep": false
|
||||
},
|
||||
"raw": {
|
||||
"value": 18,
|
||||
"string": "18"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"name": "Power_On_Hours",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 0,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 18,
|
||||
"string": "-O--C- ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": false
|
||||
},
|
||||
"raw": {
|
||||
"value": 1730,
|
||||
"string": "1730"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"name": "Spin_Retry_Count",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 1,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 18,
|
||||
"string": "-O--C- ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": false
|
||||
},
|
||||
"raw": {
|
||||
"value": 0,
|
||||
"string": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"name": "Power_Cycle_Count",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 0,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 9,
|
||||
"string": "9"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"name": "Unknown_Attribute",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 25,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 35,
|
||||
"string": "PO---K ",
|
||||
"prefailure": true,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": false,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 100,
|
||||
"string": "100"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 192,
|
||||
"name": "Power-Off_Retract_Count",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 0,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 329,
|
||||
"string": "329"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 193,
|
||||
"name": "Load_Cycle_Count",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 0,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 18,
|
||||
"string": "-O--C- ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": false
|
||||
},
|
||||
"raw": {
|
||||
"value": 329,
|
||||
"string": "329"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 194,
|
||||
"name": "Temperature_Celsius",
|
||||
"value": 51,
|
||||
"worst": 51,
|
||||
"thresh": 0,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 2,
|
||||
"string": "-O---- ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": false,
|
||||
"auto_keep": false
|
||||
},
|
||||
"raw": {
|
||||
"value": 163210330144,
|
||||
"string": "32 (Min/Max 24/38)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 196,
|
||||
"name": "Reallocated_Event_Count",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 0,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 0,
|
||||
"string": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 197,
|
||||
"name": "Current_Pending_Sector",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 0,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 34,
|
||||
"string": "-O---K ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": false,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 0,
|
||||
"string": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 198,
|
||||
"name": "Offline_Uncorrectable",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 0,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 8,
|
||||
"string": "---R-- ",
|
||||
"prefailure": false,
|
||||
"updated_online": false,
|
||||
"performance": false,
|
||||
"error_rate": true,
|
||||
"event_count": false,
|
||||
"auto_keep": false
|
||||
},
|
||||
"raw": {
|
||||
"value": 0,
|
||||
"string": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 199,
|
||||
"name": "UDMA_CRC_Error_Count",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 0,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 10,
|
||||
"string": "-O-R-- ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": true,
|
||||
"event_count": false,
|
||||
"auto_keep": false
|
||||
},
|
||||
"raw": {
|
||||
"value": 0,
|
||||
"string": "0"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"power_on_time": {
|
||||
"hours": 1730
|
||||
},
|
||||
"power_cycle_count": 9,
|
||||
"temperature": {
|
||||
"current": 32
|
||||
},
|
||||
"ata_smart_error_log": {
|
||||
"summary": {
|
||||
"revision": 1,
|
||||
"count": 0
|
||||
}
|
||||
},
|
||||
"ata_smart_self_test_log": {
|
||||
"standard": {
|
||||
"revision": 1,
|
||||
"table": [
|
||||
{
|
||||
"type": {
|
||||
"value": 1,
|
||||
"string": "Short offline"
|
||||
},
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Completed without error",
|
||||
"passed": true
|
||||
},
|
||||
"lifetime_hours": 1708
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"value": 1,
|
||||
"string": "Short offline"
|
||||
},
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Completed without error",
|
||||
"passed": true
|
||||
},
|
||||
"lifetime_hours": 1684
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"value": 1,
|
||||
"string": "Short offline"
|
||||
},
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Completed without error",
|
||||
"passed": true
|
||||
},
|
||||
"lifetime_hours": 1661
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"value": 1,
|
||||
"string": "Short offline"
|
||||
},
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Completed without error",
|
||||
"passed": true
|
||||
},
|
||||
"lifetime_hours": 1636
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"value": 2,
|
||||
"string": "Extended offline"
|
||||
},
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Completed without error",
|
||||
"passed": true
|
||||
},
|
||||
"lifetime_hours": 1624
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"value": 1,
|
||||
"string": "Short offline"
|
||||
},
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Completed without error",
|
||||
"passed": true
|
||||
},
|
||||
"lifetime_hours": 1541
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"value": 1,
|
||||
"string": "Short offline"
|
||||
},
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Completed without error",
|
||||
"passed": true
|
||||
},
|
||||
"lifetime_hours": 1517
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"value": 1,
|
||||
"string": "Short offline"
|
||||
},
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Completed without error",
|
||||
"passed": true
|
||||
},
|
||||
"lifetime_hours": 1493
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"value": 1,
|
||||
"string": "Short offline"
|
||||
},
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Completed without error",
|
||||
"passed": true
|
||||
},
|
||||
"lifetime_hours": 1469
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"value": 1,
|
||||
"string": "Short offline"
|
||||
},
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Completed without error",
|
||||
"passed": true
|
||||
},
|
||||
"lifetime_hours": 1445
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"value": 2,
|
||||
"string": "Extended offline"
|
||||
},
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Completed without error",
|
||||
"passed": true
|
||||
},
|
||||
"lifetime_hours": 1439
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"value": 1,
|
||||
"string": "Short offline"
|
||||
},
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Completed without error",
|
||||
"passed": true
|
||||
},
|
||||
"lifetime_hours": 1373
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"value": 1,
|
||||
"string": "Short offline"
|
||||
},
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Completed without error",
|
||||
"passed": true
|
||||
},
|
||||
"lifetime_hours": 1349
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"value": 1,
|
||||
"string": "Short offline"
|
||||
},
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Completed without error",
|
||||
"passed": true
|
||||
},
|
||||
"lifetime_hours": 1325
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"value": 1,
|
||||
"string": "Short offline"
|
||||
},
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Completed without error",
|
||||
"passed": true
|
||||
},
|
||||
"lifetime_hours": 1301
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"value": 1,
|
||||
"string": "Short offline"
|
||||
},
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Completed without error",
|
||||
"passed": true
|
||||
},
|
||||
"lifetime_hours": 1277
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"value": 1,
|
||||
"string": "Short offline"
|
||||
},
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Completed without error",
|
||||
"passed": true
|
||||
},
|
||||
"lifetime_hours": 1253
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"value": 2,
|
||||
"string": "Extended offline"
|
||||
},
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Completed without error",
|
||||
"passed": true
|
||||
},
|
||||
"lifetime_hours": 1252
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"value": 1,
|
||||
"string": "Short offline"
|
||||
},
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Completed without error",
|
||||
"passed": true
|
||||
},
|
||||
"lifetime_hours": 1205
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"value": 1,
|
||||
"string": "Short offline"
|
||||
},
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Completed without error",
|
||||
"passed": true
|
||||
},
|
||||
"lifetime_hours": 1181
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"value": 1,
|
||||
"string": "Short offline"
|
||||
},
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Completed without error",
|
||||
"passed": true
|
||||
},
|
||||
"lifetime_hours": 1157
|
||||
}
|
||||
],
|
||||
"count": 21,
|
||||
"error_count_total": 0,
|
||||
"error_count_outdated": 0
|
||||
}
|
||||
},
|
||||
"ata_smart_selective_self_test_log": {
|
||||
"revision": 1,
|
||||
"table": [
|
||||
{
|
||||
"lba_min": 0,
|
||||
"lba_max": 0,
|
||||
"status": {
|
||||
"value": 241,
|
||||
"string": "Not_testing"
|
||||
}
|
||||
},
|
||||
{
|
||||
"lba_min": 0,
|
||||
"lba_max": 0,
|
||||
"status": {
|
||||
"value": 241,
|
||||
"string": "Not_testing"
|
||||
}
|
||||
},
|
||||
{
|
||||
"lba_min": 0,
|
||||
"lba_max": 0,
|
||||
"status": {
|
||||
"value": 241,
|
||||
"string": "Not_testing"
|
||||
}
|
||||
},
|
||||
{
|
||||
"lba_min": 0,
|
||||
"lba_max": 0,
|
||||
"status": {
|
||||
"value": 241,
|
||||
"string": "Not_testing"
|
||||
}
|
||||
},
|
||||
{
|
||||
"lba_min": 0,
|
||||
"lba_max": 0,
|
||||
"status": {
|
||||
"value": 241,
|
||||
"string": "Not_testing"
|
||||
}
|
||||
}
|
||||
],
|
||||
"flags": {
|
||||
"value": 0,
|
||||
"remainder_scan_enabled": false
|
||||
},
|
||||
"power_up_scan_resume_minutes": 0
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user