Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ debug/
Manifest
smartctl_exporter
*.exe
.aider*
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ go 1.22
require (
github.com/alecthomas/kingpin/v2 v2.4.0
github.com/prometheus/client_golang v1.20.5
github.com/prometheus/client_model v0.6.1
github.com/prometheus/common v0.61.0
github.com/prometheus/exporter-toolkit v0.13.2
github.com/stretchr/testify v1.10.0
github.com/tidwall/gjson v1.18.0
)

Expand All @@ -15,13 +17,14 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/jpillora/backoff v1.0.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/mdlayher/vsock v1.2.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
Expand All @@ -34,4 +37,5 @@ require (
golang.org/x/text v0.21.0 // indirect
google.golang.org/protobuf v1.35.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
23 changes: 23 additions & 0 deletions metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -354,4 +354,27 @@ var (
},
nil,
)
metricDeviceLastSelfTest = prometheus.NewDesc(
"smartctl_device_last_self_test",
"Device last SMART self test status",
[]string{
"device",
"type",
"lifetime_hours",
},
nil,
)

metricDeviceLastSelfTestInfo = prometheus.NewDesc(
"smartctl_device_last_self_test_info",
"Device last SMART self test info",
[]string{
"device",
"type",
"lifetime_hours",
"status",
"description",
},
nil,
)
)
2 changes: 1 addition & 1 deletion readjson.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func readFakeSMARTctl(logger *slog.Logger, device Device) gjson.Result {
// Get json from smartctl and parse it
func readSMARTctl(logger *slog.Logger, device Device) (gjson.Result, bool) {
start := time.Now()
var smartctlArgs = []string{"--json", "--info", "--health", "--attributes", "--tolerance=verypermissive", "--nocheck=standby", "--format=brief", "--log=error", "--device=" + device.Type, device.Name}
var smartctlArgs = []string{"--json", "--info", "--health", "--attributes", "--tolerance=verypermissive", "--nocheck=standby", "--format=brief", "--log=error", "--log=selftest", "--device=" + device.Type, device.Name}

logger.Debug("Calling smartctl with args", "args", strings.Join(smartctlArgs, " "))
out, err := exec.Command(*smartctlPath, smartctlArgs...).Output()
Expand Down
67 changes: 67 additions & 0 deletions smartctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,48 @@ func (smart *SMARTctl) mineDeviceErrorLog() {
}

func (smart *SMARTctl) mineDeviceSelfTestLog() {
// For SCSI devices
if smart.device.interface_ == "scsi" {
var lastTest gjson.Result
var maxHours int64

// Find the most recent test by power_on_time.hours
smart.json.ForEach(func(key, entry gjson.Result) bool {
if strings.HasPrefix(key.String(), "scsi_self_test_") {
hours := entry.Get("power_on_time.hours").Int()
if hours > maxHours {
maxHours = hours
lastTest = entry
}
}
return true
})

if lastTest.Exists() {
smart.ch <- prometheus.MustNewConstMetric(
metricDeviceLastSelfTest,
prometheus.GaugeValue,
lastTest.Get("result.value").Float(),
smart.device.device,
lastTest.Get("code.string").String(),
fmt.Sprintf("%d", maxHours),
)

smart.ch <- prometheus.MustNewConstMetric(
metricDeviceLastSelfTestInfo,
prometheus.GaugeValue,
1.0,
smart.device.device,
lastTest.Get("code.string").String(),
fmt.Sprintf("%d", maxHours),
fmt.Sprintf("%d", lastTest.Get("result.value").Int()),
lastTest.Get("result.string").String(),
)
}
return
}

// Original ATA/SAS handling
for logType, status := range smart.json.Get("ata_smart_self_test_log").Map() {
smart.ch <- prometheus.MustNewConstMetric(
metricDeviceSelfTestLogCount,
Expand All @@ -556,6 +598,31 @@ func (smart *SMARTctl) mineDeviceSelfTestLog() {
smart.device.device,
logType,
)

// Get the first (most recent) test from the table if it exists
table := status.Get("table")
if table.Exists() && len(table.Array()) > 0 {
lastTest := table.Array()[0]
smart.ch <- prometheus.MustNewConstMetric(
metricDeviceLastSelfTest,
prometheus.GaugeValue,
lastTest.Get("status.value").Float(),
smart.device.device,
lastTest.Get("type.string").String(),
fmt.Sprintf("%d", lastTest.Get("lifetime_hours").Int()),
)

smart.ch <- prometheus.MustNewConstMetric(
metricDeviceLastSelfTestInfo,
prometheus.GaugeValue,
1,
smart.device.device,
lastTest.Get("type.string").String(),
fmt.Sprintf("%d", lastTest.Get("lifetime_hours").Int()),
fmt.Sprintf("%d", lastTest.Get("status.value").Int()),
lastTest.Get("status.string").String(),
)
}
}
}

Expand Down
Loading