!!!!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:
Jason Kulatunga
2021-06-27 10:55:18 -07:00
parent fd4f0429e4
commit 8a46931399
53 changed files with 4343 additions and 10759 deletions
@@ -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
//}