2022-08-05 03:37:13 +02:00
// Copyright 2022 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
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 ]
2022-08-05 03:09:55 +02:00
if ! cacheOk || time . Now ( ) . After ( cacheValue . LastCollect . Add ( options . SMARTctl . CollectPeriodDuration ) ) {
2020-10-29 22:35:49 +01:00
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 )
}
2022-08-05 03:09:55 +02:00
return cacheValue . JSON , nil
2020-10-29 22:35:49 +01:00
}
return gjson . Parse ( "{}" ) , fmt . Errorf ( "Device %s unavialable" , device )
}
// Parse smartctl return code
func resultCodeIsOk ( SMARTCtlResult int64 ) bool {
result := true
if SMARTCtlResult > 0 {
2022-03-17 23:52:15 +01:00
b := SMARTCtlResult
if ( b & 1 ) != 0 {
2020-10-29 22:35:49 +01:00
logger . Error ( "Command line did not parse." )
result = false
}
2022-03-17 23:52:15 +01:00
if ( b & ( 1 << 1 ) ) != 0 {
2020-10-29 22:35:49 +01:00
logger . Error ( "Device open failed, device did not return an IDENTIFY DEVICE structure, or device is in a low-power mode" )
result = false
}
2022-03-17 23:52:15 +01:00
if ( b & ( 1 << 2 ) ) != 0 {
2020-10-29 22:35:49 +01:00
logger . Warning ( "Some SMART or other ATA command to the disk failed, or there was a checksum error in a SMART data structure" )
}
2022-03-17 23:52:15 +01:00
if ( b & ( 1 << 3 ) ) != 0 {
2020-10-29 22:35:49 +01:00
logger . Warning ( "SMART status check returned 'DISK FAILING'." )
}
2022-03-17 23:52:15 +01:00
if ( b & ( 1 << 4 ) ) != 0 {
2020-10-29 22:35:49 +01:00
logger . Warning ( "We found prefail Attributes <= threshold." )
}
2022-03-17 23:52:15 +01:00
if ( b & ( 1 << 5 ) ) != 0 {
2020-10-29 22:35:49 +01:00
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." )
}
2022-03-17 23:52:15 +01:00
if ( b & ( 1 << 6 ) ) != 0 {
2020-10-29 22:35:49 +01:00
logger . Warning ( "The device error log contains records of errors." )
}
2022-03-17 23:52:15 +01:00
if ( b & ( 1 << 7 ) ) != 0 {
2020-10-29 22:35:49 +01:00
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
}