Address review comments
This commit is contained in:
@@ -21,7 +21,7 @@ type DeviceRepo interface {
|
|||||||
DeleteDevice(ctx context.Context, wwn string) error
|
DeleteDevice(ctx context.Context, wwn string) error
|
||||||
|
|
||||||
SaveSmartAttributes(ctx context.Context, wwn string, collectorSmartData collector.SmartInfo) (measurements.Smart, error)
|
SaveSmartAttributes(ctx context.Context, wwn string, collectorSmartData collector.SmartInfo) (measurements.Smart, error)
|
||||||
GetSmartAttributeHistory(ctx context.Context, wwn string, durationKey string, n int, offset int, attributes []string) ([]measurements.Smart, error)
|
GetSmartAttributeHistory(ctx context.Context, wwn string, durationKey string, selectEntries int, selectEntriesOffset int, attributes []string) ([]measurements.Smart, error)
|
||||||
|
|
||||||
SaveSmartTemperature(ctx context.Context, wwn string, deviceProtocol string, collectorSmartData collector.SmartInfo) error
|
SaveSmartTemperature(ctx context.Context, wwn string, deviceProtocol string, collectorSmartData collector.SmartInfo) error
|
||||||
|
|
||||||
|
|||||||
@@ -31,14 +31,17 @@ func (sr *scrutinyRepository) SaveSmartAttributes(ctx context.Context, wwn strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetSmartAttributeHistory MUST return in sorted order, where newest entries are at the beginning of the list, and oldest are at the end.
|
// GetSmartAttributeHistory MUST return in sorted order, where newest entries are at the beginning of the list, and oldest are at the end.
|
||||||
func (sr *scrutinyRepository) GetSmartAttributeHistory(ctx context.Context, wwn string, durationKey string, n int, offset int, attributes []string) ([]measurements.Smart, error) {
|
// When selectEntries is > 0, only the most recent selectEntries database entries are returned, starting from the selectEntriesOffset entry.
|
||||||
|
// For example, with selectEntries = 5, selectEntries = 0, the most recent 5 are returned. With selectEntries = 3, selectEntries = 2, entries
|
||||||
|
// 2 to 4 are returned (2 being the third newest, since it is zero-indexed)
|
||||||
|
func (sr *scrutinyRepository) GetSmartAttributeHistory(ctx context.Context, wwn string, durationKey string, selectEntries int, selectEntriesOffset int, attributes []string) ([]measurements.Smart, error) {
|
||||||
// Get SMartResults from InfluxDB
|
// Get SMartResults from InfluxDB
|
||||||
|
|
||||||
//TODO: change the filter startrange to a real number.
|
//TODO: change the filter startrange to a real number.
|
||||||
|
|
||||||
// Get parser flux query result
|
// Get parser flux query result
|
||||||
//appConfig.GetString("web.influxdb.bucket")
|
//appConfig.GetString("web.influxdb.bucket")
|
||||||
queryStr := sr.aggregateSmartAttributesQuery(wwn, durationKey, n, offset, attributes)
|
queryStr := sr.aggregateSmartAttributesQuery(wwn, durationKey, selectEntries, selectEntriesOffset, attributes)
|
||||||
log.Infoln(queryStr)
|
log.Infoln(queryStr)
|
||||||
|
|
||||||
smartResults := []measurements.Smart{}
|
smartResults := []measurements.Smart{}
|
||||||
@@ -97,7 +100,7 @@ func (sr *scrutinyRepository) saveDatapoint(influxWriteApi api.WriteAPIBlocking,
|
|||||||
return influxWriteApi.WritePoint(ctx, p)
|
return influxWriteApi.WritePoint(ctx, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sr *scrutinyRepository) aggregateSmartAttributesQuery(wwn string, durationKey string, n int, offset int, attributes []string) string {
|
func (sr *scrutinyRepository) aggregateSmartAttributesQuery(wwn string, durationKey string, selectEntries int, selectEntriesOffset int, attributes []string) string {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
@@ -147,7 +150,7 @@ func (sr *scrutinyRepository) aggregateSmartAttributesQuery(wwn string, duration
|
|||||||
if len(nestedDurationKeys) == 1 {
|
if len(nestedDurationKeys) == 1 {
|
||||||
//there's only one bucket being queried, no need to union, just aggregate the dataset and return
|
//there's only one bucket being queried, no need to union, just aggregate the dataset and return
|
||||||
partialQueryStr = append(partialQueryStr, []string{
|
partialQueryStr = append(partialQueryStr, []string{
|
||||||
sr.generateSmartAttributesSubquery(wwn, nestedDurationKeys[0], n, offset, attributes),
|
sr.generateSmartAttributesSubquery(wwn, nestedDurationKeys[0], selectEntries, selectEntriesOffset, attributes),
|
||||||
fmt.Sprintf(`%sData`, nestedDurationKeys[0]),
|
fmt.Sprintf(`%sData`, nestedDurationKeys[0]),
|
||||||
`|> sort(columns: ["_time"], desc: true)`,
|
`|> sort(columns: ["_time"], desc: true)`,
|
||||||
`|> yield()`,
|
`|> yield()`,
|
||||||
@@ -159,10 +162,10 @@ func (sr *scrutinyRepository) aggregateSmartAttributesQuery(wwn string, duration
|
|||||||
subQueryNames := []string{}
|
subQueryNames := []string{}
|
||||||
for _, nestedDurationKey := range nestedDurationKeys {
|
for _, nestedDurationKey := range nestedDurationKeys {
|
||||||
subQueryNames = append(subQueryNames, fmt.Sprintf(`%sData`, nestedDurationKey))
|
subQueryNames = append(subQueryNames, fmt.Sprintf(`%sData`, nestedDurationKey))
|
||||||
if n > 0 {
|
if selectEntries > 0 {
|
||||||
// We only need the last `n + offset` # of entries from each table to guarantee we can
|
// We only need the last `n + offset` # of entries from each table to guarantee we can
|
||||||
// get the last `n` # of entries starting from `offset` of the union
|
// get the last `n` # of entries starting from `offset` of the union
|
||||||
subQueries = append(subQueries, sr.generateSmartAttributesSubquery(wwn, nestedDurationKey, n+offset, 0, attributes))
|
subQueries = append(subQueries, sr.generateSmartAttributesSubquery(wwn, nestedDurationKey, selectEntries+selectEntriesOffset, 0, attributes))
|
||||||
} else {
|
} else {
|
||||||
subQueries = append(subQueries, sr.generateSmartAttributesSubquery(wwn, nestedDurationKey, 0, 0, attributes))
|
subQueries = append(subQueries, sr.generateSmartAttributesSubquery(wwn, nestedDurationKey, 0, 0, attributes))
|
||||||
}
|
}
|
||||||
@@ -173,15 +176,15 @@ func (sr *scrutinyRepository) aggregateSmartAttributesQuery(wwn string, duration
|
|||||||
`|> group()`,
|
`|> group()`,
|
||||||
`|> sort(columns: ["_time"], desc: true)`,
|
`|> sort(columns: ["_time"], desc: true)`,
|
||||||
}...)
|
}...)
|
||||||
if n > 0 {
|
if selectEntries > 0 {
|
||||||
partialQueryStr = append(partialQueryStr, fmt.Sprintf(`|> tail(n: %d, offset: %d)`, n, offset))
|
partialQueryStr = append(partialQueryStr, fmt.Sprintf(`|> tail(n: %d, offset: %d)`, selectEntries, selectEntriesOffset))
|
||||||
}
|
}
|
||||||
partialQueryStr = append(partialQueryStr, `|> yield(name: "last")`)
|
partialQueryStr = append(partialQueryStr, `|> yield(name: "last")`)
|
||||||
|
|
||||||
return strings.Join(partialQueryStr, "\n")
|
return strings.Join(partialQueryStr, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sr *scrutinyRepository) generateSmartAttributesSubquery(wwn string, durationKey string, n int, offset int, attributes []string) string {
|
func (sr *scrutinyRepository) generateSmartAttributesSubquery(wwn string, durationKey string, selectEntries int, selectEntriesOffset int, attributes []string) string {
|
||||||
bucketName := sr.lookupBucketName(durationKey)
|
bucketName := sr.lookupBucketName(durationKey)
|
||||||
durationRange := sr.lookupDuration(durationKey)
|
durationRange := sr.lookupDuration(durationKey)
|
||||||
|
|
||||||
@@ -191,8 +194,8 @@ func (sr *scrutinyRepository) generateSmartAttributesSubquery(wwn string, durati
|
|||||||
`|> filter(fn: (r) => r["_measurement"] == "smart" )`,
|
`|> filter(fn: (r) => r["_measurement"] == "smart" )`,
|
||||||
fmt.Sprintf(`|> filter(fn: (r) => r["device_wwn"] == "%s" )`, wwn),
|
fmt.Sprintf(`|> filter(fn: (r) => r["device_wwn"] == "%s" )`, wwn),
|
||||||
}
|
}
|
||||||
if n > 0 {
|
if selectEntries > 0 {
|
||||||
partialQueryStr = append(partialQueryStr, fmt.Sprintf(`|> tail(n: %d, offset: %d)`, n, offset))
|
partialQueryStr = append(partialQueryStr, fmt.Sprintf(`|> tail(n: %d, offset: %d)`, selectEntries, selectEntriesOffset))
|
||||||
}
|
}
|
||||||
partialQueryStr = append(partialQueryStr, "|> schema.fieldsAsCols()")
|
partialQueryStr = append(partialQueryStr, "|> schema.fieldsAsCols()")
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const NotifyFailureTypeSmartFailure = "SmartFailure"
|
|||||||
const NotifyFailureTypeScrutinyFailure = "ScrutinyFailure"
|
const NotifyFailureTypeScrutinyFailure = "ScrutinyFailure"
|
||||||
|
|
||||||
// ShouldNotify check if the error Message should be filtered (level mismatch or filtered_attributes)
|
// ShouldNotify check if the error Message should be filtered (level mismatch or filtered_attributes)
|
||||||
func ShouldNotify(device models.Device, smartAttrs measurements.Smart, statusThreshold pkg.MetricsStatusThreshold, statusFilterAttributes pkg.MetricsStatusFilterAttributes, repeatNotifications bool, c *gin.Context, deviceRepo database.DeviceRepo) bool {
|
func ShouldNotify(logger logrus.FieldLogger, device models.Device, smartAttrs measurements.Smart, statusThreshold pkg.MetricsStatusThreshold, statusFilterAttributes pkg.MetricsStatusFilterAttributes, repeatNotifications bool, c *gin.Context, deviceRepo database.DeviceRepo) bool {
|
||||||
// 1. check if the device is healthy
|
// 1. check if the device is healthy
|
||||||
if device.DeviceStatus == pkg.DeviceStatusPassed {
|
if device.DeviceStatus == pkg.DeviceStatusPassed {
|
||||||
return false
|
return false
|
||||||
@@ -62,12 +62,16 @@ func ShouldNotify(device models.Device, smartAttrs measurements.Smart, statusThr
|
|||||||
}
|
}
|
||||||
|
|
||||||
var failingAttributes []string
|
var failingAttributes []string
|
||||||
|
// Loop through the attributes to find the failing ones
|
||||||
for attrId, attrData := range smartAttrs.Attributes {
|
for attrId, attrData := range smartAttrs.Attributes {
|
||||||
var status pkg.AttributeStatus = attrData.GetStatus()
|
var status pkg.AttributeStatus = attrData.GetStatus()
|
||||||
|
// Skip over passing attributes
|
||||||
if status == pkg.AttributeStatusPassed {
|
if status == pkg.AttributeStatusPassed {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the user only wants to consider critical attributes, we have to check
|
||||||
|
// if the not-passing attribute is critical or not
|
||||||
if statusFilterAttributes == pkg.MetricsStatusFilterAttributesCritical {
|
if statusFilterAttributes == pkg.MetricsStatusFilterAttributesCritical {
|
||||||
critical := false
|
critical := false
|
||||||
if device.IsScsi() {
|
if device.IsScsi() {
|
||||||
@@ -82,34 +86,38 @@ func ShouldNotify(device models.Device, smartAttrs measurements.Smart, statusThr
|
|||||||
}
|
}
|
||||||
critical = thresholds.AtaMetadata[attrIdInt].Critical
|
critical = thresholds.AtaMetadata[attrIdInt].Critical
|
||||||
}
|
}
|
||||||
|
// Skip non-critical, non-passing attributes when this setting is on
|
||||||
if !critical {
|
if !critical {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Record any attribute that doesn't get skipped by the above two checks
|
||||||
failingAttributes = append(failingAttributes, attrId)
|
failingAttributes = append(failingAttributes, attrId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the user doesn't want repeated notifications when the failing value doesn't change, we need to get the last value from the db
|
||||||
|
var lastPoints []measurements.Smart
|
||||||
|
var err error
|
||||||
if !repeatNotifications {
|
if !repeatNotifications {
|
||||||
lastPoints, err := deviceRepo.GetSmartAttributeHistory(c, c.Param("wwn"), database.DURATION_KEY_FOREVER, 1, 1, failingAttributes)
|
lastPoints, err = deviceRepo.GetSmartAttributeHistory(c, c.Param("wwn"), database.DURATION_KEY_FOREVER, 1, 1, failingAttributes)
|
||||||
if err == nil && len(lastPoints) > 1 {
|
if err == nil || len(lastPoints) < 1 {
|
||||||
for _, attrId := range failingAttributes {
|
logger.Warningln("Could not get the most recent data points from the database. This is expected to happen only if this is the very first submission of data for the device.")
|
||||||
if lastPoints[0].Attributes[attrId].GetTransformedValue() != smartAttrs.Attributes[attrId].GetTransformedValue() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
return true
|
}
|
||||||
} else {
|
for _, attrId := range failingAttributes {
|
||||||
for _, attr := range failingAttributes {
|
attrStatus := smartAttrs.Attributes[attrId].GetStatus()
|
||||||
attrStatus := smartAttrs.Attributes[attr].GetStatus()
|
if pkg.AttributeStatusHas(attrStatus, requiredAttrStatus) {
|
||||||
if pkg.AttributeStatusHas(attrStatus, requiredAttrStatus) {
|
if repeatNotifications {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// This is checked again here to avoid repeating the entire for loop in the check above.
|
||||||
|
// Probably unnoticeably worse performance, but cleaner code.
|
||||||
|
if err != nil || len(lastPoints) < 1 || lastPoints[0].Attributes[attrId].GetTransformedValue() != smartAttrs.Attributes[attrId].GetTransformedValue() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ func UploadDeviceMetrics(c *gin.Context) {
|
|||||||
|
|
||||||
//check for error
|
//check for error
|
||||||
if notify.ShouldNotify(
|
if notify.ShouldNotify(
|
||||||
|
logger,
|
||||||
updatedDevice,
|
updatedDevice,
|
||||||
smartData,
|
smartData,
|
||||||
pkg.MetricsStatusThreshold(appConfig.GetInt(fmt.Sprintf("%s.metrics.status_threshold", config.DB_USER_SETTINGS_SUBKEY))),
|
pkg.MetricsStatusThreshold(appConfig.GetInt(fmt.Sprintf("%s.metrics.status_threshold", config.DB_USER_SETTINGS_SUBKEY))),
|
||||||
|
|||||||
Reference in New Issue
Block a user