2019-08-14 22:34:49 +02:00
package main
import (
"fmt"
"io/ioutil"
2020-10-29 22:35:49 +01:00
"os"
2019-08-14 22:34:49 +02:00
"os/exec"
"strings"
2019-08-15 23:01:16 +02:00
"time"
2019-08-14 22:34:49 +02:00
"github.com/tidwall/gjson"
)
2019-08-15 23:01:16 +02:00
// JSONCache caching json
type JSONCache struct {
JSON gjson . Result
LastCollect time . Time
}
var (
jsonCache map [ string ] JSONCache
)
func init ( ) {
jsonCache = make ( map [ string ] JSONCache )
}
2019-08-14 22:34:49 +02:00
// Parse json to gjson object
2019-08-15 23:01:16 +02:00
func parseJSON ( data string ) gjson . Result {
2019-08-14 22:34:49 +02:00
if ! gjson . Valid ( data ) {
2019-08-15 23:01:16 +02:00
return gjson . Parse ( "{}" )
2019-08-14 22:34:49 +02:00
}
2019-08-15 23:01:16 +02:00
return gjson . Parse ( data )
2019-08-14 22:34:49 +02:00
}
// Reading fake smartctl json
2019-08-15 23:01:16 +02:00
func readFakeSMARTctl ( device string ) gjson . Result {
2022-06-16 10:08:20 +02:00
s := strings . Split ( device , "/" )
filename := fmt . Sprintf ( "debug/%s.json" , s [ len ( s ) - 1 ] )
2019-08-14 22:34:49 +02:00
logger . Verbose ( "Read fake S.M.A.R.T. data from json: %s" , filename )
jsonFile , err := ioutil . ReadFile ( filename )
if err != nil {
logger . Error ( "Fake S.M.A.R.T. data reading error: %s" , err )
return parseJSON ( "{}" )
}
return parseJSON ( string ( jsonFile ) )
}
// Get json from smartctl and parse it
2020-10-29 22:35:49 +01:00
func readSMARTctl ( device string ) ( gjson . Result , bool ) {
2019-08-15 23:01:16 +02:00
logger . Debug ( "Collecting S.M.A.R.T. counters, device: %s" , device )
2019-08-14 22:34:49 +02:00
out , err := exec . Command ( options . SMARTctl . SMARTctlLocation , "--json" , "--xall" , device ) . Output ( )
if err != nil {
2019-08-15 23:01:16 +02:00
logger . Warning ( "S.M.A.R.T. output reading error: %s" , err )
2019-08-14 22:34:49 +02:00
}
2020-10-29 22:35:49 +01:00
json := parseJSON ( string ( out ) )
rcOk := resultCodeIsOk ( json . Get ( "smartctl.exit_status" ) . Int ( ) )
jsonOk := jsonIsOk ( json )
return json , rcOk && jsonOk
2019-08-14 22:34:49 +02:00
}
2020-10-06 13:05:00 +02:00
func readSMARTctlDevices ( ) gjson . Result {
logger . Debug ( "Collecting devices" )
out , err := exec . Command ( options . SMARTctl . SMARTctlLocation , "--json" , "--scan-open" ) . Output ( )
if err != nil {
logger . Warning ( "S.M.A.R.T. output reading error: %s" , err )
}
return parseJSON ( string ( out ) )
}
2019-08-14 22:34:49 +02:00
// Select json source and parse
2020-10-29 22:35:49 +01:00
func readData ( device string ) ( gjson . Result , error ) {
2019-08-14 22:34:49 +02:00
if options . SMARTctl . FakeJSON {
2020-10-29 22:35:49 +01:00
return readFakeSMARTctl ( device ) , nil
2019-08-14 22:34:49 +02:00
}
2019-08-15 23:01:16 +02:00
2020-10-29 22:35:49 +01:00
if _ , err := os . Stat ( device ) ; err == nil {
cacheValue , cacheOk := jsonCache [ device ]
timeToScan := false
if cacheOk {
timeToScan = time . Now ( ) . After ( cacheValue . LastCollect . Add ( options . SMARTctl . CollectPeriodDuration ) )
} else {
timeToScan = true
}
if timeToScan {
json , ok := readSMARTctl ( device )
if ok {
jsonCache [ device ] = JSONCache { JSON : json , LastCollect : time . Now ( ) }
return jsonCache [ device ] . JSON , nil
}
return gjson . Parse ( "{}" ) , fmt . Errorf ( "smartctl returned bad data for device %s" , device )
}
return gjson . Parse ( "{}" ) , fmt . Errorf ( "Too early collect called for device %s" , device )
}
return gjson . Parse ( "{}" ) , fmt . Errorf ( "Device %s unavialable" , device )
}
// Parse smartctl return code
func resultCodeIsOk ( SMARTCtlResult int64 ) bool {
result := true
if SMARTCtlResult > 0 {
bits := fmt . Sprintf ( "%08b" , SMARTCtlResult )
// logger.Debug("Return code: %d: %s", SMARTCtlResult, bits)
if bits [ 0 ] == '1' {
logger . Error ( "Command line did not parse." )
result = false
}
if bits [ 1 ] == '1' {
logger . Error ( "Device open failed, device did not return an IDENTIFY DEVICE structure, or device is in a low-power mode" )
result = false
}
if bits [ 2 ] == '1' {
logger . Warning ( "Some SMART or other ATA command to the disk failed, or there was a checksum error in a SMART data structure" )
}
if bits [ 3 ] == '1' {
logger . Warning ( "SMART status check returned 'DISK FAILING'." )
}
if bits [ 4 ] == '1' {
logger . Warning ( "We found prefail Attributes <= threshold." )
}
if bits [ 5 ] == '1' {
logger . Warning ( "SMART status check returned 'DISK OK' but we found that some (usage or prefail) Attributes have been <= threshold at some time in the past." )
}
if bits [ 6 ] == '1' {
logger . Warning ( "The device error log contains records of errors." )
}
if bits [ 7 ] == '1' {
logger . Warning ( "The device self-test log contains records of errors. [ATA only] Failed self-tests outdated by a newer successful extended self-test are ignored." )
}
}
return result
}
// Check json
func jsonIsOk ( json gjson . Result ) bool {
messages := json . Get ( "smartctl.messages" )
// logger.Debug(messages.String())
if messages . Exists ( ) {
for _ , message := range messages . Array ( ) {
if message . Get ( "severity" ) . String ( ) == "error" {
logger . Error ( message . Get ( "string" ) . String ( ) )
return false
}
2019-08-15 23:01:16 +02:00
}
}
2020-10-29 22:35:49 +01:00
return true
2019-08-14 22:34:49 +02:00
}