mirror of
https://github.com/netdata/netdata.git
synced 2025-04-16 10:31:07 +00:00
go.d smartctl (#17536)
This commit is contained in:
parent
b1bb1e9ded
commit
e1c4f7fec3
23 changed files with 2973 additions and 2 deletions
|
@ -118,6 +118,7 @@ see the appropriate collector readme.
|
|||
| [sensors](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules) | Hardware Sensors |
|
||||
| [SNMP](https://github.com/netdata/netdata/blob/master/src/go/collectors/go.d.plugin/modules/snmp) | SNMP |
|
||||
| [squidlog](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/squidlog) | Squid |
|
||||
| [smartctl](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/smartctl) | S.M.A.R.T Storage Devices |
|
||||
| [storcli](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/storcli) | Broadcom Hardware RAID |
|
||||
| [supervisord](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/supervisord) | Supervisor |
|
||||
| [systemdunits](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/systemdunits) | Systemd unit state |
|
||||
|
@ -213,8 +214,7 @@ sudo su -s /bin/bash netdata
|
|||
./go.d.plugin -d -m <module name>
|
||||
```
|
||||
|
||||
Change `<module name>` to the module name you want to debug. See the [whole list](#available-modules) of available
|
||||
modules.
|
||||
Change `<module name>` to the [module name](#available-modules) you want to debug.
|
||||
|
||||
## Netdata Community
|
||||
|
||||
|
|
|
@ -80,6 +80,7 @@ modules:
|
|||
# sensors: yes
|
||||
# snmp: yes
|
||||
# squidlog: yes
|
||||
# smartctl: yes
|
||||
# storcli: yes
|
||||
# supervisord: yes
|
||||
# systemdunits: yes
|
||||
|
|
5
src/go/collectors/go.d.plugin/config/go.d/smartctl.conf
Normal file
5
src/go/collectors/go.d.plugin/config/go.d/smartctl.conf
Normal file
|
@ -0,0 +1,5 @@
|
|||
## All available configuration options, their descriptions and default values:
|
||||
## https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/smartctl#readme
|
||||
|
||||
jobs:
|
||||
- name: smartctl
|
|
@ -119,6 +119,9 @@ require (
|
|||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/tidwall/gjson v1.17.1 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
|
|
|
@ -332,6 +332,12 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
|||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
|
||||
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tomasen/fcgi_client v0.0.0-20180423082037-2bb3d819fd19 h1:ZCmSnT6CLGhfoQ2lPEhL4nsJstKDCw1F1RfN8/smTCU=
|
||||
github.com/tomasen/fcgi_client v0.0.0-20180423082037-2bb3d819fd19/go.mod h1:SXTY+QvI+KTTKXQdg0zZ7nx0u94QWh8ZAwBQYsW9cqk=
|
||||
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
||||
|
|
|
@ -70,6 +70,7 @@ import (
|
|||
_ "github.com/netdata/netdata/go/go.d.plugin/modules/redis"
|
||||
_ "github.com/netdata/netdata/go/go.d.plugin/modules/scaleio"
|
||||
_ "github.com/netdata/netdata/go/go.d.plugin/modules/sensors"
|
||||
_ "github.com/netdata/netdata/go/go.d.plugin/modules/smartctl"
|
||||
_ "github.com/netdata/netdata/go/go.d.plugin/modules/snmp"
|
||||
_ "github.com/netdata/netdata/go/go.d.plugin/modules/squidlog"
|
||||
_ "github.com/netdata/netdata/go/go.d.plugin/modules/storcli"
|
||||
|
|
237
src/go/collectors/go.d.plugin/modules/smartctl/charts.go
Normal file
237
src/go/collectors/go.d.plugin/modules/smartctl/charts.go
Normal file
|
@ -0,0 +1,237 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package smartctl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/netdata/netdata/go/go.d.plugin/agent/module"
|
||||
)
|
||||
|
||||
const (
|
||||
prioDeviceSmartStatus = module.Priority + iota
|
||||
prioDeviceAtaSmartErrorLogCount
|
||||
prioDevicePowerOnTime
|
||||
prioDeviceTemperature
|
||||
prioDevicePowerCycleCount
|
||||
|
||||
prioDeviceSmartAttributeDecoded
|
||||
prioDeviceSmartAttributeNormalized
|
||||
)
|
||||
|
||||
var deviceChartsTmpl = module.Charts{
|
||||
devicePowerOnTimeChartTmpl.Copy(),
|
||||
deviceTemperatureChartTmpl.Copy(),
|
||||
devicePowerCycleCountChartTmpl.Copy(),
|
||||
deviceSmartStatusChartTmpl.Copy(),
|
||||
deviceAtaSmartErrorLogCountChartTmpl.Copy(),
|
||||
}
|
||||
|
||||
var (
|
||||
deviceSmartStatusChartTmpl = module.Chart{
|
||||
ID: "device_%s_type_%s_smart_status",
|
||||
Title: "Device smart status",
|
||||
Units: "status",
|
||||
Fam: "smart status",
|
||||
Ctx: "smartctl.device_smart_status",
|
||||
Type: module.Line,
|
||||
Priority: prioDeviceSmartStatus,
|
||||
Dims: module.Dims{
|
||||
{ID: "device_%s_type_%s_smart_status_passed", Name: "passed"},
|
||||
{ID: "device_%s_type_%s_smart_status_failed", Name: "failed"},
|
||||
},
|
||||
}
|
||||
deviceAtaSmartErrorLogCountChartTmpl = module.Chart{
|
||||
ID: "device_%s_type_%s_ata_smart_error_log_count",
|
||||
Title: "Device ATA smart error log count",
|
||||
Units: "logs",
|
||||
Fam: "smart error log",
|
||||
Ctx: "smartctl.device_ata_smart_error_log_count",
|
||||
Type: module.Line,
|
||||
Priority: prioDeviceAtaSmartErrorLogCount,
|
||||
Dims: module.Dims{
|
||||
{ID: "device_%s_type_%s_ata_smart_error_log_summary_count", Name: "error_log"},
|
||||
},
|
||||
}
|
||||
devicePowerOnTimeChartTmpl = module.Chart{
|
||||
ID: "device_%s_type_%s_power_on_time",
|
||||
Title: "Device power on time",
|
||||
Units: "seconds",
|
||||
Fam: "power on time",
|
||||
Ctx: "smartctl.device_power_on_time",
|
||||
Type: module.Line,
|
||||
Priority: prioDevicePowerOnTime,
|
||||
Dims: module.Dims{
|
||||
{ID: "device_%s_type_%s_power_on_time", Name: "power_on_time"},
|
||||
},
|
||||
}
|
||||
deviceTemperatureChartTmpl = module.Chart{
|
||||
ID: "device_%s_type_%s_temperature",
|
||||
Title: "Device temperature",
|
||||
Units: "Celsius",
|
||||
Fam: "temperature",
|
||||
Ctx: "smartctl.device_temperature",
|
||||
Type: module.Line,
|
||||
Priority: prioDeviceTemperature,
|
||||
Dims: module.Dims{
|
||||
{ID: "device_%s_type_%s_temperature", Name: "temperature"},
|
||||
},
|
||||
}
|
||||
devicePowerCycleCountChartTmpl = module.Chart{
|
||||
ID: "device_%s_type_%s_power_cycle_count",
|
||||
Title: "Device power cycles",
|
||||
Units: "cycles",
|
||||
Fam: "power cycles",
|
||||
Ctx: "smartctl.device_power_cycles_count",
|
||||
Type: module.Line,
|
||||
Priority: prioDevicePowerCycleCount,
|
||||
Dims: module.Dims{
|
||||
{ID: "device_%s_type_%s_power_cycle_count", Name: "power"},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
deviceSmartAttributeDecodedChartTmpl = module.Chart{
|
||||
ID: "device_%s_type_%s_smart_attr_%s",
|
||||
Title: "Device smart attribute %s",
|
||||
Units: "value",
|
||||
Fam: "attr %s",
|
||||
Ctx: "smartctl.device_smart_attr_%s",
|
||||
Type: module.Line,
|
||||
Priority: prioDeviceSmartAttributeDecoded,
|
||||
Dims: module.Dims{
|
||||
{ID: "device_%s_type_%s_attr_%s_decoded", Name: "%s"},
|
||||
},
|
||||
}
|
||||
deviceSmartAttributeNormalizedChartTmpl = module.Chart{
|
||||
ID: "device_%s_type_%s_smart_attr_%s_normalized",
|
||||
Title: "Device smart attribute normalized %s",
|
||||
Units: "value",
|
||||
Fam: "attr %s",
|
||||
Ctx: "smartctl.device_smart_attr_%s_normalized",
|
||||
Type: module.Line,
|
||||
Priority: prioDeviceSmartAttributeNormalized,
|
||||
Dims: module.Dims{
|
||||
{ID: "device_%s_type_%s_attr_%s_normalized", Name: "%s"},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func (s *Smartctl) addDeviceCharts(dev *smartDevice) {
|
||||
charts := module.Charts{}
|
||||
|
||||
if cs := s.newDeviceCharts(dev); cs != nil && len(*cs) > 0 {
|
||||
if err := charts.Add(*cs...); err != nil {
|
||||
s.Warning(err)
|
||||
}
|
||||
}
|
||||
if cs := s.newDeviceSmartAttrCharts(dev); cs != nil && len(*cs) > 0 {
|
||||
if err := charts.Add(*cs...); err != nil {
|
||||
s.Warning(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.Charts().Add(charts...); err != nil {
|
||||
s.Warning(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Smartctl) removeDeviceCharts(scanDev *scanDevice) {
|
||||
px := fmt.Sprintf("device_%s_%s_", scanDev.shortName(), scanDev.typ)
|
||||
|
||||
for _, chart := range *s.Charts() {
|
||||
if strings.HasPrefix(chart.ID, px) {
|
||||
chart.MarkRemove()
|
||||
chart.MarkNotCreated()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Smartctl) newDeviceCharts(dev *smartDevice) *module.Charts {
|
||||
|
||||
charts := deviceChartsTmpl.Copy()
|
||||
|
||||
if _, ok := dev.powerOnTime(); !ok {
|
||||
_ = charts.Remove(devicePowerOnTimeChartTmpl.ID)
|
||||
}
|
||||
if _, ok := dev.temperature(); !ok {
|
||||
_ = charts.Remove(deviceTemperatureChartTmpl.ID)
|
||||
}
|
||||
if _, ok := dev.powerCycleCount(); !ok {
|
||||
_ = charts.Remove(devicePowerOnTimeChartTmpl.ID)
|
||||
}
|
||||
if _, ok := dev.smartStatusPassed(); !ok {
|
||||
_ = charts.Remove(deviceSmartStatusChartTmpl.ID)
|
||||
}
|
||||
if _, ok := dev.ataSmartErrorLogCount(); !ok {
|
||||
_ = charts.Remove(deviceAtaSmartErrorLogCountChartTmpl.ID)
|
||||
}
|
||||
|
||||
for _, chart := range *charts {
|
||||
chart.ID = fmt.Sprintf(chart.ID, dev.deviceName(), dev.deviceType())
|
||||
chart.Labels = []module.Label{
|
||||
{Key: "device_name", Value: dev.deviceName()},
|
||||
{Key: "device_type", Value: dev.deviceType()},
|
||||
{Key: "model_name", Value: dev.modelName()},
|
||||
{Key: "serial_number", Value: dev.serialNumber()},
|
||||
}
|
||||
for _, dim := range chart.Dims {
|
||||
dim.ID = fmt.Sprintf(dim.ID, dev.deviceName(), dev.deviceType())
|
||||
}
|
||||
}
|
||||
|
||||
return charts
|
||||
}
|
||||
|
||||
func (s *Smartctl) newDeviceSmartAttrCharts(dev *smartDevice) *module.Charts {
|
||||
attrs, ok := dev.ataSmartAttributeTable()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
charts := module.Charts{}
|
||||
|
||||
for _, attr := range attrs {
|
||||
if !isSmartAttrValid(attr) || strings.HasPrefix(attr.name(), "Unknown") {
|
||||
continue
|
||||
}
|
||||
|
||||
cs := module.Charts{
|
||||
deviceSmartAttributeDecodedChartTmpl.Copy(),
|
||||
deviceSmartAttributeNormalizedChartTmpl.Copy(),
|
||||
}
|
||||
|
||||
name := cleanAttributeName(attr)
|
||||
|
||||
// FIXME: attribute charts unit
|
||||
for _, chart := range cs {
|
||||
chart.ID = fmt.Sprintf(chart.ID, dev.deviceName(), dev.deviceType(), name)
|
||||
chart.Title = fmt.Sprintf(chart.Title, attr.name())
|
||||
chart.Fam = fmt.Sprintf(chart.Fam, name)
|
||||
chart.Ctx = fmt.Sprintf(chart.Ctx, name)
|
||||
chart.Labels = []module.Label{
|
||||
{Key: "device_name", Value: dev.deviceName()},
|
||||
{Key: "device_type", Value: dev.deviceType()},
|
||||
{Key: "model_name", Value: dev.modelName()},
|
||||
{Key: "serial_number", Value: dev.serialNumber()},
|
||||
}
|
||||
for _, dim := range chart.Dims {
|
||||
dim.ID = fmt.Sprintf(dim.ID, dev.deviceName(), dev.deviceType(), name)
|
||||
dim.Name = fmt.Sprintf(dim.Name, name)
|
||||
}
|
||||
}
|
||||
|
||||
if err := charts.Add(cs...); err != nil {
|
||||
s.Warning(err)
|
||||
}
|
||||
}
|
||||
|
||||
return &charts
|
||||
}
|
||||
|
||||
var attrNameReplacer = strings.NewReplacer(" ", "_", "/", "_")
|
||||
|
||||
func cleanAttributeName(attr *smartAttribute) string {
|
||||
return strings.ToLower(attrNameReplacer.Replace(attr.name()))
|
||||
}
|
189
src/go/collectors/go.d.plugin/modules/smartctl/collect.go
Normal file
189
src/go/collectors/go.d.plugin/modules/smartctl/collect.go
Normal file
|
@ -0,0 +1,189 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package smartctl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func (s *Smartctl) collect() (map[string]int64, error) {
|
||||
now := time.Now()
|
||||
|
||||
if s.forceScan || s.isTimeToScan(now) {
|
||||
devices, err := s.scanDevices()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for k, dev := range s.scannedDevices {
|
||||
if _, ok := devices[k]; !ok {
|
||||
delete(s.scannedDevices, k)
|
||||
delete(s.seenDevices, k)
|
||||
s.removeDeviceCharts(dev)
|
||||
}
|
||||
}
|
||||
|
||||
s.forceDevicePoll = !maps.Equal(s.scannedDevices, devices)
|
||||
s.scannedDevices = devices
|
||||
s.lastScanTime = now
|
||||
s.forceScan = false
|
||||
}
|
||||
|
||||
if s.forceDevicePoll || s.isTimeToPollDevices(now) {
|
||||
mx := make(map[string]int64)
|
||||
|
||||
// TODO: make it concurrent
|
||||
for _, d := range s.scannedDevices {
|
||||
if err := s.collectScannedDevice(mx, d); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
s.forceDevicePoll = false
|
||||
s.lastDevicePollTime = now
|
||||
s.mx = mx
|
||||
}
|
||||
|
||||
return s.mx, nil
|
||||
}
|
||||
|
||||
func (s *Smartctl) collectScannedDevice(mx map[string]int64, scanDev *scanDevice) error {
|
||||
resp, err := s.exec.deviceInfo(scanDev.name, scanDev.typ, s.NoCheckPowerMode)
|
||||
if err != nil {
|
||||
if resp != nil && isDeviceOpenFailedNoSuchDevice(resp) {
|
||||
s.Infof("smartctl reported that device '%s' type '%s' no longer exists", scanDev.name, scanDev.typ)
|
||||
s.forceScan = true
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to get device info for '%s' type '%s': %v", scanDev.name, scanDev.typ, err)
|
||||
}
|
||||
|
||||
if isDeviceInLowerPowerMode(resp) {
|
||||
s.Debugf("device '%s' type '%s' is in a low-power mode, skipping", scanDev.name, scanDev.typ)
|
||||
return nil
|
||||
}
|
||||
|
||||
dev := newSmartDevice(resp)
|
||||
if !isSmartDeviceValid(dev) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !s.seenDevices[scanDev.key()] {
|
||||
s.seenDevices[scanDev.key()] = true
|
||||
s.addDeviceCharts(dev)
|
||||
}
|
||||
|
||||
s.collectSmartDevice(mx, dev)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Smartctl) collectSmartDevice(mx map[string]int64, dev *smartDevice) {
|
||||
px := fmt.Sprintf("device_%s_type_%s_", dev.deviceName(), dev.deviceType())
|
||||
|
||||
if v, ok := dev.powerOnTime(); ok {
|
||||
mx[px+"power_on_time"] = v
|
||||
}
|
||||
if v, ok := dev.temperature(); ok {
|
||||
mx[px+"temperature"] = v
|
||||
}
|
||||
if v, ok := dev.powerCycleCount(); ok {
|
||||
mx[px+"power_cycle_count"] = v
|
||||
}
|
||||
if v, ok := dev.smartStatusPassed(); ok {
|
||||
mx[px+"smart_status_passed"] = 0
|
||||
mx[px+"smart_status_failed"] = 0
|
||||
if v {
|
||||
mx[px+"smart_status_passed"] = 1
|
||||
} else {
|
||||
mx[px+"smart_status_failed"] = 1
|
||||
}
|
||||
}
|
||||
if v, ok := dev.ataSmartErrorLogCount(); ok {
|
||||
mx[px+"ata_smart_error_log_summary_count"] = v
|
||||
}
|
||||
|
||||
if attrs, ok := dev.ataSmartAttributeTable(); ok {
|
||||
for _, attr := range attrs {
|
||||
if !isSmartAttrValid(attr) {
|
||||
continue
|
||||
}
|
||||
n := strings.ToLower(attr.name())
|
||||
n = strings.ReplaceAll(n, " ", "_")
|
||||
px := fmt.Sprintf("%sattr_%s_", px, n)
|
||||
|
||||
if v, err := strconv.ParseInt(attr.value(), 10, 64); err == nil {
|
||||
mx[px+"normalized"] = v
|
||||
}
|
||||
|
||||
if v, err := strconv.ParseInt(attr.rawValue(), 10, 64); err == nil {
|
||||
mx[px+"raw"] = v
|
||||
}
|
||||
|
||||
rs := strings.TrimSpace(attr.rawString())
|
||||
if i := strings.IndexByte(rs, ' '); i != -1 {
|
||||
rs = rs[:i]
|
||||
}
|
||||
if v, err := strconv.ParseInt(rs, 10, 64); err == nil {
|
||||
mx[px+"decoded"] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Smartctl) isTimeToScan(now time.Time) bool {
|
||||
return now.After(s.lastScanTime.Add(s.ScanEvery.Duration()))
|
||||
}
|
||||
|
||||
func (s *Smartctl) isTimeToPollDevices(now time.Time) bool {
|
||||
return now.After(s.lastDevicePollTime.Add(s.PollDevicesEvery.Duration()))
|
||||
|
||||
}
|
||||
|
||||
func isSmartDeviceValid(d *smartDevice) bool {
|
||||
return d.deviceName() != "" && d.deviceType() != ""
|
||||
}
|
||||
|
||||
func isSmartAttrValid(a *smartAttribute) bool {
|
||||
return a.id() != "" && a.name() != ""
|
||||
}
|
||||
|
||||
func isDeviceInLowerPowerMode(r *gjson.Result) bool {
|
||||
if !isExitStatusHasBit(r, 1) {
|
||||
return false
|
||||
}
|
||||
|
||||
messages := r.Get("smartctl.messages").Array()
|
||||
|
||||
return slices.ContainsFunc(messages, func(msg gjson.Result) bool {
|
||||
text := msg.Get("string").String()
|
||||
return strings.HasPrefix(text, "Device is in") && strings.Contains(text, "mode")
|
||||
})
|
||||
}
|
||||
|
||||
func isDeviceOpenFailedNoSuchDevice(r *gjson.Result) bool {
|
||||
if !isExitStatusHasBit(r, 1) {
|
||||
return false
|
||||
}
|
||||
|
||||
messages := r.Get("smartctl.messages").Array()
|
||||
|
||||
return slices.ContainsFunc(messages, func(msg gjson.Result) bool {
|
||||
text := msg.Get("string").String()
|
||||
return strings.HasSuffix(text, "No such device")
|
||||
})
|
||||
}
|
||||
|
||||
func isExitStatusHasBit(r *gjson.Result, bit int) bool {
|
||||
// https://manpages.debian.org/bullseye/smartmontools/smartctl.8.en.html#EXIT_STATUS
|
||||
status := int(r.Get("smartctl.exit_status").Int())
|
||||
mask := 1 << bit
|
||||
return (status & mask) != 0
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"jsonSchema": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Smartctl collector configuration.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"update_every": {
|
||||
"title": "Update every",
|
||||
"description": "Interval for updating Netdata charts, measured in seconds. Collector might use cached data if less than **Devices poll interval**.",
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"default": 10
|
||||
},
|
||||
"timeout": {
|
||||
"title": "Timeout",
|
||||
"description": "Timeout for executing the `smartctl` binary, specified in seconds.",
|
||||
"type": "number",
|
||||
"minimum": 0.5,
|
||||
"default": 5
|
||||
},
|
||||
"scan_every": {
|
||||
"title": "Scan interval",
|
||||
"description": "Interval for discovering new devices using `smartctl --scan`, measured in seconds.",
|
||||
"type": "number",
|
||||
"minimum": 1,
|
||||
"default": 900
|
||||
},
|
||||
"poll_devices_every": {
|
||||
"title": "Devices poll interval",
|
||||
"description": "Interval for gathering data for every device, measured in seconds. Data is cached for this interval.",
|
||||
"type": "number",
|
||||
"minimum": 1,
|
||||
"default": 300
|
||||
},
|
||||
"no_check_power_mode": {
|
||||
"title": "No check power mode",
|
||||
"description": "ATA only. Skip data collection when the device is in a low-power mode. Prevents unnecessary disk spin-up.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"standby",
|
||||
"never",
|
||||
"sleep",
|
||||
"idle"
|
||||
],
|
||||
"default": "standby"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {
|
||||
"^name$": {}
|
||||
}
|
||||
},
|
||||
"uiSchema": {
|
||||
"uiOptions": {
|
||||
"fullPage": true
|
||||
},
|
||||
"timeout": {
|
||||
"ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
|
||||
},
|
||||
"no_check_power_mode": {
|
||||
"ui:widget": "radio",
|
||||
"ui:options": {
|
||||
"inline": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
82
src/go/collectors/go.d.plugin/modules/smartctl/exec.go
Normal file
82
src/go/collectors/go.d.plugin/modules/smartctl/exec.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package smartctl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/netdata/netdata/go/go.d.plugin/logger"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func newSmartctlCliExec(ndsudoPath string, timeout time.Duration, log *logger.Logger) *smartctlCliExec {
|
||||
return &smartctlCliExec{
|
||||
Logger: log,
|
||||
ndsudoPath: ndsudoPath,
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
type smartctlCliExec struct {
|
||||
*logger.Logger
|
||||
|
||||
ndsudoPath string
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (e *smartctlCliExec) scan() (*gjson.Result, error) {
|
||||
return e.execute("smartctl-json-scan")
|
||||
}
|
||||
|
||||
func (e *smartctlCliExec) deviceInfo(deviceName, deviceType, powerMode string) (*gjson.Result, error) {
|
||||
return e.execute("smartctl-json-device-info",
|
||||
"--deviceName", deviceName,
|
||||
"--deviceType", deviceType,
|
||||
"--powerMode", powerMode,
|
||||
)
|
||||
}
|
||||
|
||||
func (e *smartctlCliExec) execute(args ...string) (*gjson.Result, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), e.timeout)
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(ctx, e.ndsudoPath, args...)
|
||||
e.Debugf("executing '%s'", cmd)
|
||||
|
||||
bs, err := cmd.Output()
|
||||
if err != nil {
|
||||
if errors.Is(err, context.DeadlineExceeded) || isExecExitCode(err, 1) || len(bs) == 0 {
|
||||
return nil, fmt.Errorf("'%s' execution failed: %v", cmd, err)
|
||||
}
|
||||
}
|
||||
if len(bs) == 0 {
|
||||
return nil, fmt.Errorf("'%s' returned no output", cmd)
|
||||
}
|
||||
|
||||
if !gjson.ValidBytes(bs) {
|
||||
return nil, fmt.Errorf("'%s' returned invalid JSON output", cmd)
|
||||
}
|
||||
|
||||
res := gjson.ParseBytes(bs)
|
||||
if !res.Get("smartctl.exit_status").Exists() {
|
||||
return nil, fmt.Errorf("'%s' returned unexpected data", cmd)
|
||||
}
|
||||
|
||||
for _, msg := range res.Get("smartctl.messages").Array() {
|
||||
if msg.Get("severity").String() == "error" {
|
||||
return &res, fmt.Errorf("'%s' reported an error: %s", cmd, msg.Get("string"))
|
||||
}
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func isExecExitCode(err error, exitCode int) bool {
|
||||
var v *exec.ExitError
|
||||
return errors.As(err, &v) && v.ExitCode() == exitCode
|
||||
}
|
32
src/go/collectors/go.d.plugin/modules/smartctl/init.go
Normal file
32
src/go/collectors/go.d.plugin/modules/smartctl/init.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package smartctl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/netdata/netdata/go/go.d.plugin/agent/executable"
|
||||
)
|
||||
|
||||
func (s *Smartctl) validateConfig() error {
|
||||
switch s.NoCheckPowerMode {
|
||||
case "never", "sleep", "standby", "idle":
|
||||
default:
|
||||
return fmt.Errorf("invalid power mode '%s'", s.NoCheckPowerMode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Smartctl) initSmartctlCli() (smartctlCli, error) {
|
||||
ndsudoPath := filepath.Join(executable.Directory, "ndsudo")
|
||||
if _, err := os.Stat(ndsudoPath); err != nil {
|
||||
return nil, fmt.Errorf("ndsudo executable not found: %v", err)
|
||||
|
||||
}
|
||||
|
||||
smartctlExec := newSmartctlCliExec(ndsudoPath, s.Timeout.Duration(), s.Logger)
|
||||
|
||||
return smartctlExec, nil
|
||||
}
|
154
src/go/collectors/go.d.plugin/modules/smartctl/metadata.yaml
Normal file
154
src/go/collectors/go.d.plugin/modules/smartctl/metadata.yaml
Normal file
|
@ -0,0 +1,154 @@
|
|||
plugin_name: go.d.plugin
|
||||
modules:
|
||||
- meta:
|
||||
id: collector-go.d.plugin-smartctl
|
||||
plugin_name: go.d.plugin
|
||||
module_name: smartctl
|
||||
monitored_instance:
|
||||
name: S.M.A.R.T.
|
||||
link: "https://linux.die.net/man/8/smartd"
|
||||
icon_filename: "smart.png"
|
||||
categories:
|
||||
- data-collection.hardware-devices-and-sensors
|
||||
keywords:
|
||||
- smart
|
||||
- S.M.A.R.T.
|
||||
- SCSI devices
|
||||
- ATA devices
|
||||
related_resources:
|
||||
integrations:
|
||||
list: []
|
||||
info_provided_to_referring_integrations:
|
||||
description: ""
|
||||
most_popular: false
|
||||
overview:
|
||||
data_collection:
|
||||
metrics_description: |
|
||||
This collector monitors the health status of storage devices by analyzing S.M.A.R.T. (Self-Monitoring, Analysis, and Reporting Technology) counters.
|
||||
It relies on the [`smartctl`](https://linux.die.net/man/8/smartctl) CLI tool but avoids directly executing the binary.
|
||||
Instead, it utilizes `ndsudo`, a Netdata helper specifically designed to run privileged commands securely within the Netdata environment.
|
||||
This approach eliminates the need to use `sudo`, improving security and potentially simplifying permission management.
|
||||
|
||||
Executed commands:
|
||||
- `smartctl --json --scan`
|
||||
- `smartctl --json --all {deviceName} --device {deviceType} --nocheck {powerMode}`
|
||||
method_description: ""
|
||||
supported_platforms:
|
||||
include: []
|
||||
exclude: []
|
||||
multi_instance: false
|
||||
additional_permissions:
|
||||
description: ""
|
||||
default_behavior:
|
||||
auto_detection:
|
||||
description: ""
|
||||
limits:
|
||||
description: ""
|
||||
performance_impact:
|
||||
description: ""
|
||||
setup:
|
||||
prerequisites:
|
||||
list: []
|
||||
configuration:
|
||||
file:
|
||||
name: go.d/smartctl.conf
|
||||
options:
|
||||
description: |
|
||||
The following options can be defined globally: update_every.
|
||||
folding:
|
||||
title: Config options
|
||||
enabled: true
|
||||
list:
|
||||
- name: update_every
|
||||
description: interval for updating Netdata charts, measured in seconds. Collector might use cached data if less than **Devices poll interval**.
|
||||
default_value: 10
|
||||
required: false
|
||||
- name: timeout
|
||||
description: smartctl binary execution timeout.
|
||||
default_value: 5
|
||||
required: false
|
||||
- name: scan_every
|
||||
description: interval for discovering new devices using `smartctl --scan`, measured in seconds.
|
||||
default_value: 900
|
||||
required: false
|
||||
- name: poll_devices_every
|
||||
description: interval for gathering data for every device, measured in seconds. Data is cached for this interval.
|
||||
default_value: 300
|
||||
required: false
|
||||
examples:
|
||||
folding:
|
||||
title: Config
|
||||
enabled: true
|
||||
list:
|
||||
- name: Custom devices poll interval
|
||||
description: Allows you to override the default devices poll interval (data collection).
|
||||
config: |
|
||||
jobs:
|
||||
- name: smartctl
|
||||
devices_poll_interval: 60 # Collect S.M.A.R.T statistics every 60 seconds
|
||||
troubleshooting:
|
||||
problems:
|
||||
list: []
|
||||
alerts: []
|
||||
metrics:
|
||||
folding:
|
||||
title: Metrics
|
||||
enabled: false
|
||||
description: ""
|
||||
availability: []
|
||||
scopes:
|
||||
- name: controller
|
||||
description: These metrics refer to the Storage Device.
|
||||
labels:
|
||||
- name: device_name
|
||||
description: Device name
|
||||
- name: device_type
|
||||
description: Device type
|
||||
- name: model_name
|
||||
description: Model name
|
||||
- name: serial_number
|
||||
description: Serial number
|
||||
metrics:
|
||||
- name: smartctl.device_smart_status
|
||||
description: Device smart status
|
||||
unit: status
|
||||
chart_type: line
|
||||
dimensions:
|
||||
- name: passed
|
||||
- name: failed
|
||||
- name: smartctl.device_ata_smart_error_log_count
|
||||
description: Device ATA smart error log count
|
||||
unit: logs
|
||||
chart_type: line
|
||||
dimensions:
|
||||
- name: error_log
|
||||
- name: smartctl.device_power_on_time
|
||||
description: Device power on time
|
||||
unit: seconds
|
||||
chart_type: line
|
||||
dimensions:
|
||||
- name: power_on_time
|
||||
- name: smartctl.device_temperature
|
||||
description: Device temperature
|
||||
unit: Celsius
|
||||
chart_type: line
|
||||
dimensions:
|
||||
- name: temperature
|
||||
- name: smartctl.device_power_cycles_count
|
||||
description: Device power cycles
|
||||
unit: cycles
|
||||
chart_type: line
|
||||
dimensions:
|
||||
- name: power
|
||||
- name: smartctl.device_smart_attr_{attribute_name}
|
||||
description: Device smart attribute {attribute_name}
|
||||
unit: '{attribute_unit}'
|
||||
chart_type: line
|
||||
dimensions:
|
||||
- name: '{attribute_name}'
|
||||
- name: smartctl.device_smart_attr_{attribute_name}_normalized
|
||||
description: Device smart attribute {attribute_name} normalized
|
||||
unit: value
|
||||
chart_type: line
|
||||
dimensions:
|
||||
- name: '{attribute_name}'
|
68
src/go/collectors/go.d.plugin/modules/smartctl/scan.go
Normal file
68
src/go/collectors/go.d.plugin/modules/smartctl/scan.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package smartctl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type scanDevice struct {
|
||||
name string
|
||||
infoName string
|
||||
typ string
|
||||
}
|
||||
|
||||
func (s *scanDevice) key() string {
|
||||
return fmt.Sprintf("%s|%s", s.name, s.typ)
|
||||
}
|
||||
|
||||
func (s *scanDevice) shortName() string {
|
||||
return strings.TrimPrefix(s.name, "/dev/")
|
||||
}
|
||||
|
||||
func (s *Smartctl) scanDevices() (map[string]*scanDevice, error) {
|
||||
resp, err := s.exec.scan()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
devices := make(map[string]*scanDevice)
|
||||
|
||||
for _, d := range resp.Get("devices").Array() {
|
||||
dev := &scanDevice{
|
||||
name: d.Get("name").String(),
|
||||
infoName: d.Get("info_name").String(),
|
||||
typ: d.Get("type").String(),
|
||||
}
|
||||
|
||||
if dev.name == "" || dev.typ == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if dev.typ == "scsi" {
|
||||
// `smartctl --scan` attempts to guess the device type based on the path, but this can be unreliable.
|
||||
// Accurate device type information is crucial because we use the `--device` option to gather data.
|
||||
// Using the wrong type can lead to issues.
|
||||
// For example, using 'scsi' for 'sat' devices prevents `smartctl` from issuing the necessary ATA commands.
|
||||
resp, _ := s.exec.deviceInfo(dev.name, dev.typ, s.NoCheckPowerMode)
|
||||
if resp != nil && isExitStatusHasBit(resp, 2) {
|
||||
newType := "sat"
|
||||
s.Debugf("changing device '%s' type '%s' -> '%s'", dev.name, dev.typ, newType)
|
||||
dev.typ = newType
|
||||
}
|
||||
s.Debugf("smartctl scan found device '%s' type '%s' info_name '%s'", dev.name, dev.typ, dev.infoName)
|
||||
}
|
||||
|
||||
devices[dev.key()] = dev
|
||||
}
|
||||
|
||||
if len(devices) == 0 {
|
||||
return nil, errors.New("no devices found on scan")
|
||||
}
|
||||
|
||||
s.Infof("smartctl scan found %d devices", len(devices))
|
||||
|
||||
return devices, nil
|
||||
}
|
123
src/go/collectors/go.d.plugin/modules/smartctl/smart_device.go
Normal file
123
src/go/collectors/go.d.plugin/modules/smartctl/smart_device.go
Normal file
|
@ -0,0 +1,123 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package smartctl
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func newSmartDevice(deviceData *gjson.Result) *smartDevice {
|
||||
return &smartDevice{
|
||||
data: deviceData,
|
||||
}
|
||||
}
|
||||
|
||||
type smartDevice struct {
|
||||
data *gjson.Result
|
||||
}
|
||||
|
||||
func (d *smartDevice) deviceName() string {
|
||||
v := d.data.Get("device.name").String()
|
||||
return strings.TrimPrefix(v, "/dev/")
|
||||
}
|
||||
|
||||
func (d *smartDevice) deviceType() string {
|
||||
return d.data.Get("device.type").String()
|
||||
}
|
||||
|
||||
func (d *smartDevice) deviceProtocol() string {
|
||||
return d.data.Get("device.protocol").String()
|
||||
}
|
||||
|
||||
func (d *smartDevice) serialNumber() string {
|
||||
return d.data.Get("serial_number").String()
|
||||
}
|
||||
|
||||
func (d *smartDevice) modelName() string {
|
||||
for _, s := range []string{"model_name", "scsi_model_name"} {
|
||||
if v := d.data.Get(s); v.Exists() {
|
||||
return v.String()
|
||||
}
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func (d *smartDevice) powerOnTime() (int64, bool) {
|
||||
h := d.data.Get("power_on_time.hours")
|
||||
if !h.Exists() {
|
||||
return 0, false
|
||||
}
|
||||
m := d.data.Get("power_on_time.minutes")
|
||||
return h.Int()*60*60 + m.Int()*60, true
|
||||
}
|
||||
|
||||
func (d *smartDevice) temperature() (int64, bool) {
|
||||
v := d.data.Get("temperature.current")
|
||||
return v.Int(), v.Exists()
|
||||
}
|
||||
|
||||
func (d *smartDevice) powerCycleCount() (int64, bool) {
|
||||
v := d.data.Get("power_cycle_count")
|
||||
if v.Exists() {
|
||||
return v.Int(), true
|
||||
}
|
||||
v = d.data.Get("scsi_start_stop_cycle_counter.accumulated_start_stop_cycles")
|
||||
return v.Int(), v.Exists()
|
||||
}
|
||||
|
||||
func (d *smartDevice) smartStatusPassed() (bool, bool) {
|
||||
v := d.data.Get("smart_status.passed")
|
||||
return v.Bool(), v.Exists()
|
||||
}
|
||||
|
||||
func (d *smartDevice) ataSmartErrorLogCount() (int64, bool) {
|
||||
v := d.data.Get("ata_smart_error_log.summary.count")
|
||||
return v.Int(), v.Exists()
|
||||
}
|
||||
|
||||
func (d *smartDevice) ataSmartAttributeTable() ([]*smartAttribute, bool) {
|
||||
table := d.data.Get("ata_smart_attributes.table")
|
||||
if !table.Exists() || !table.IsArray() {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
var attrs []*smartAttribute
|
||||
|
||||
for _, data := range table.Array() {
|
||||
attrs = append(attrs, newSmartDeviceAttribute(data))
|
||||
}
|
||||
|
||||
return attrs, true
|
||||
}
|
||||
|
||||
func newSmartDeviceAttribute(attrData gjson.Result) *smartAttribute {
|
||||
return &smartAttribute{
|
||||
data: attrData,
|
||||
}
|
||||
}
|
||||
|
||||
type smartAttribute struct {
|
||||
data gjson.Result
|
||||
}
|
||||
|
||||
func (a *smartAttribute) id() string {
|
||||
return a.data.Get("id").String()
|
||||
}
|
||||
|
||||
func (a *smartAttribute) name() string {
|
||||
return a.data.Get("name").String()
|
||||
}
|
||||
|
||||
func (a *smartAttribute) value() string {
|
||||
return a.data.Get("value").String()
|
||||
}
|
||||
|
||||
func (a *smartAttribute) rawValue() string {
|
||||
return a.data.Get("raw.value").String()
|
||||
}
|
||||
|
||||
func (a *smartAttribute) rawString() string {
|
||||
return a.data.Get("raw.string").String()
|
||||
}
|
126
src/go/collectors/go.d.plugin/modules/smartctl/smartctl.go
Normal file
126
src/go/collectors/go.d.plugin/modules/smartctl/smartctl.go
Normal file
|
@ -0,0 +1,126 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package smartctl
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/netdata/netdata/go/go.d.plugin/agent/module"
|
||||
"github.com/netdata/netdata/go/go.d.plugin/pkg/web"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
//go:embed "config_schema.json"
|
||||
var configSchema string
|
||||
|
||||
func init() {
|
||||
module.Register("smartctl", module.Creator{
|
||||
JobConfigSchema: configSchema,
|
||||
Defaults: module.Defaults{
|
||||
UpdateEvery: 10,
|
||||
},
|
||||
Create: func() module.Module { return New() },
|
||||
})
|
||||
}
|
||||
|
||||
func New() *Smartctl {
|
||||
return &Smartctl{
|
||||
Config: Config{
|
||||
Timeout: web.Duration(time.Second * 5),
|
||||
ScanEvery: web.Duration(time.Minute * 15),
|
||||
PollDevicesEvery: web.Duration(time.Minute * 5),
|
||||
NoCheckPowerMode: "standby",
|
||||
},
|
||||
charts: &module.Charts{},
|
||||
seenDevices: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
UpdateEvery int `yaml:"update_every" json:"update_every"`
|
||||
Timeout web.Duration `yaml:"timeout" json:"timeout"`
|
||||
ScanEvery web.Duration `yaml:"scan_every" json:"scan_every"`
|
||||
PollDevicesEvery web.Duration `yaml:"poll_devices_every" json:"poll_devices_every"`
|
||||
NoCheckPowerMode string `yaml:"no_check_power_mode" json:"no_check_power_mode"`
|
||||
}
|
||||
|
||||
type (
|
||||
Smartctl struct {
|
||||
module.Base
|
||||
Config `yaml:",inline" data:""`
|
||||
|
||||
charts *module.Charts
|
||||
|
||||
exec smartctlCli
|
||||
|
||||
lastScanTime time.Time
|
||||
forceScan bool
|
||||
scannedDevices map[string]*scanDevice
|
||||
|
||||
lastDevicePollTime time.Time
|
||||
forceDevicePoll bool
|
||||
|
||||
seenDevices map[string]bool
|
||||
mx map[string]int64
|
||||
}
|
||||
smartctlCli interface {
|
||||
scan() (*gjson.Result, error)
|
||||
deviceInfo(deviceName, deviceType, powerMode string) (*gjson.Result, error)
|
||||
}
|
||||
)
|
||||
|
||||
func (s *Smartctl) Configuration() any {
|
||||
return s.Config
|
||||
}
|
||||
|
||||
func (s *Smartctl) Init() error {
|
||||
if err := s.validateConfig(); err != nil {
|
||||
s.Errorf("config validation error: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
smartctlExec, err := s.initSmartctlCli()
|
||||
if err != nil {
|
||||
s.Errorf("smartctl exec initialization: %v", err)
|
||||
return err
|
||||
}
|
||||
s.exec = smartctlExec
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Smartctl) Check() error {
|
||||
mx, err := s.collect()
|
||||
if err != nil {
|
||||
s.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(mx) == 0 {
|
||||
return errors.New("no metrics collected")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Smartctl) Charts() *module.Charts {
|
||||
return s.charts
|
||||
}
|
||||
|
||||
func (s *Smartctl) Collect() map[string]int64 {
|
||||
mx, err := s.collect()
|
||||
if err != nil {
|
||||
s.Error(err)
|
||||
}
|
||||
|
||||
if len(mx) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return mx
|
||||
}
|
||||
|
||||
func (s *Smartctl) Cleanup() {}
|
436
src/go/collectors/go.d.plugin/modules/smartctl/smartctl_test.go
Normal file
436
src/go/collectors/go.d.plugin/modules/smartctl/smartctl_test.go
Normal file
|
@ -0,0 +1,436 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package smartctl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/netdata/netdata/go/go.d.plugin/agent/module"
|
||||
"github.com/netdata/netdata/go/go.d.plugin/pkg/web"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
var (
|
||||
dataConfigJSON, _ = os.ReadFile("testdata/config.json")
|
||||
dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
|
||||
|
||||
dataTypeSataScan, _ = os.ReadFile("testdata/type-sat/scan.json")
|
||||
dataTypeSataDeviceHDDSda, _ = os.ReadFile("testdata/type-sat/device-hdd-sda.json")
|
||||
dataTypeSataDeviceSSDSdc, _ = os.ReadFile("testdata/type-sat/device-ssd-sdc.json")
|
||||
|
||||
dataTypeNvmeScan, _ = os.ReadFile("testdata/type-nvme/scan.json")
|
||||
dataTypeNvmeDeviceNvme0, _ = os.ReadFile("testdata/type-nvme/device-nvme0.json")
|
||||
)
|
||||
|
||||
func Test_testDataIsValid(t *testing.T) {
|
||||
for name, data := range map[string][]byte{
|
||||
"dataConfigJSON": dataConfigJSON,
|
||||
"dataConfigYAML": dataConfigYAML,
|
||||
|
||||
"dataTypeSataScan": dataTypeSataScan,
|
||||
"dataTypeSataDeviceHDDSda": dataTypeSataDeviceHDDSda,
|
||||
"dataTypeSataDeviceSSDSdc": dataTypeSataDeviceSSDSdc,
|
||||
|
||||
"dataTypeNvmeScan": dataTypeNvmeScan,
|
||||
"dataTypeNvmeDeviceNvme0": dataTypeNvmeDeviceNvme0,
|
||||
} {
|
||||
require.NotNil(t, data, name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSmartctl_ConfigurationSerialize(t *testing.T) {
|
||||
module.TestConfigurationSerialize(t, &Smartctl{}, dataConfigJSON, dataConfigYAML)
|
||||
}
|
||||
|
||||
func TestSmartctl_Init(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
config Config
|
||||
wantFail bool
|
||||
}{
|
||||
"fails if invalid power mode": {
|
||||
wantFail: true,
|
||||
config: func() Config {
|
||||
cfg := New().Config
|
||||
cfg.NoCheckPowerMode = "invalid"
|
||||
return cfg
|
||||
}(),
|
||||
},
|
||||
"fails if 'ndsudo' not found": {
|
||||
wantFail: true,
|
||||
config: New().Config,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
smart := New()
|
||||
|
||||
if test.wantFail {
|
||||
assert.Error(t, smart.Init())
|
||||
} else {
|
||||
assert.NoError(t, smart.Init())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSmartctl_Cleanup(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
prepare func() *Smartctl
|
||||
}{
|
||||
"not initialized exec": {
|
||||
prepare: func() *Smartctl {
|
||||
return New()
|
||||
},
|
||||
},
|
||||
"after check": {
|
||||
prepare: func() *Smartctl {
|
||||
smart := New()
|
||||
smart.exec = prepareMockOkTypeSata()
|
||||
_ = smart.Check()
|
||||
return smart
|
||||
},
|
||||
},
|
||||
"after collect": {
|
||||
prepare: func() *Smartctl {
|
||||
smart := New()
|
||||
smart.exec = prepareMockOkTypeSata()
|
||||
_ = smart.Collect()
|
||||
return smart
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
smart := test.prepare()
|
||||
|
||||
assert.NotPanics(t, smart.Cleanup)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSmartctl_Check(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
prepareMock func() *mockSmartctlCliExec
|
||||
wantFail bool
|
||||
}{
|
||||
"success type sata devices": {
|
||||
wantFail: false,
|
||||
prepareMock: prepareMockOkTypeSata,
|
||||
},
|
||||
"success type nvme devices": {
|
||||
wantFail: false,
|
||||
prepareMock: prepareMockOkTypeNvme,
|
||||
},
|
||||
"error on scan": {
|
||||
wantFail: true,
|
||||
prepareMock: prepareMockErrOnScan,
|
||||
},
|
||||
"unexpected response on scan": {
|
||||
wantFail: true,
|
||||
prepareMock: prepareMockUnexpectedResponse,
|
||||
},
|
||||
"empty response on scan": {
|
||||
wantFail: true,
|
||||
prepareMock: prepareMockEmptyResponse,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
smart := New()
|
||||
mock := test.prepareMock()
|
||||
smart.exec = mock
|
||||
|
||||
if test.wantFail {
|
||||
assert.Error(t, smart.Check())
|
||||
} else {
|
||||
assert.NoError(t, smart.Check())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSmartctl_Collect(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
prepareMock func() *mockSmartctlCliExec
|
||||
wantMetrics map[string]int64
|
||||
wantCharts int
|
||||
}{
|
||||
"success type sata devices": {
|
||||
prepareMock: prepareMockOkTypeSata,
|
||||
wantCharts: 68,
|
||||
wantMetrics: map[string]int64{
|
||||
"device_sda_type_sat_ata_smart_error_log_summary_count": 0,
|
||||
"device_sda_type_sat_attr_current_pending_sector_decoded": 0,
|
||||
"device_sda_type_sat_attr_current_pending_sector_normalized": 100,
|
||||
"device_sda_type_sat_attr_current_pending_sector_raw": 0,
|
||||
"device_sda_type_sat_attr_load_cycle_count_decoded": 360,
|
||||
"device_sda_type_sat_attr_load_cycle_count_normalized": 100,
|
||||
"device_sda_type_sat_attr_load_cycle_count_raw": 360,
|
||||
"device_sda_type_sat_attr_offline_uncorrectable_decoded": 0,
|
||||
"device_sda_type_sat_attr_offline_uncorrectable_normalized": 100,
|
||||
"device_sda_type_sat_attr_offline_uncorrectable_raw": 0,
|
||||
"device_sda_type_sat_attr_power-off_retract_count_decoded": 360,
|
||||
"device_sda_type_sat_attr_power-off_retract_count_normalized": 100,
|
||||
"device_sda_type_sat_attr_power-off_retract_count_raw": 360,
|
||||
"device_sda_type_sat_attr_power_cycle_count_decoded": 12,
|
||||
"device_sda_type_sat_attr_power_cycle_count_normalized": 100,
|
||||
"device_sda_type_sat_attr_power_cycle_count_raw": 12,
|
||||
"device_sda_type_sat_attr_power_on_hours_decoded": 8244,
|
||||
"device_sda_type_sat_attr_power_on_hours_normalized": 99,
|
||||
"device_sda_type_sat_attr_power_on_hours_raw": 8244,
|
||||
"device_sda_type_sat_attr_raw_read_error_rate_decoded": 0,
|
||||
"device_sda_type_sat_attr_raw_read_error_rate_normalized": 100,
|
||||
"device_sda_type_sat_attr_raw_read_error_rate_raw": 0,
|
||||
"device_sda_type_sat_attr_reallocated_event_count_decoded": 0,
|
||||
"device_sda_type_sat_attr_reallocated_event_count_normalized": 100,
|
||||
"device_sda_type_sat_attr_reallocated_event_count_raw": 0,
|
||||
"device_sda_type_sat_attr_reallocated_sector_ct_decoded": 0,
|
||||
"device_sda_type_sat_attr_reallocated_sector_ct_normalized": 100,
|
||||
"device_sda_type_sat_attr_reallocated_sector_ct_raw": 0,
|
||||
"device_sda_type_sat_attr_seek_error_rate_decoded": 0,
|
||||
"device_sda_type_sat_attr_seek_error_rate_normalized": 100,
|
||||
"device_sda_type_sat_attr_seek_error_rate_raw": 0,
|
||||
"device_sda_type_sat_attr_seek_time_performance_decoded": 15,
|
||||
"device_sda_type_sat_attr_seek_time_performance_normalized": 140,
|
||||
"device_sda_type_sat_attr_seek_time_performance_raw": 15,
|
||||
"device_sda_type_sat_attr_spin_retry_count_decoded": 0,
|
||||
"device_sda_type_sat_attr_spin_retry_count_normalized": 100,
|
||||
"device_sda_type_sat_attr_spin_retry_count_raw": 0,
|
||||
"device_sda_type_sat_attr_spin_up_time_decoded": 281,
|
||||
"device_sda_type_sat_attr_spin_up_time_normalized": 86,
|
||||
"device_sda_type_sat_attr_spin_up_time_raw": 25788088601,
|
||||
"device_sda_type_sat_attr_start_stop_count_decoded": 12,
|
||||
"device_sda_type_sat_attr_start_stop_count_normalized": 100,
|
||||
"device_sda_type_sat_attr_start_stop_count_raw": 12,
|
||||
"device_sda_type_sat_attr_temperature_celsius_decoded": 49,
|
||||
"device_sda_type_sat_attr_temperature_celsius_normalized": 43,
|
||||
"device_sda_type_sat_attr_temperature_celsius_raw": 240519741489,
|
||||
"device_sda_type_sat_attr_throughput_performance_decoded": 48,
|
||||
"device_sda_type_sat_attr_throughput_performance_normalized": 148,
|
||||
"device_sda_type_sat_attr_throughput_performance_raw": 48,
|
||||
"device_sda_type_sat_attr_udma_crc_error_count_decoded": 0,
|
||||
"device_sda_type_sat_attr_udma_crc_error_count_normalized": 100,
|
||||
"device_sda_type_sat_attr_udma_crc_error_count_raw": 0,
|
||||
"device_sda_type_sat_attr_unknown_attribute_decoded": 100,
|
||||
"device_sda_type_sat_attr_unknown_attribute_normalized": 100,
|
||||
"device_sda_type_sat_attr_unknown_attribute_raw": 100,
|
||||
"device_sda_type_sat_power_cycle_count": 12,
|
||||
"device_sda_type_sat_power_on_time": 29678400,
|
||||
"device_sda_type_sat_smart_status_failed": 0,
|
||||
"device_sda_type_sat_smart_status_passed": 1,
|
||||
"device_sda_type_sat_temperature": 49,
|
||||
"device_sdc_type_sat_ata_smart_error_log_summary_count": 0,
|
||||
"device_sdc_type_sat_attr_available_reservd_space_decoded": 100,
|
||||
"device_sdc_type_sat_attr_available_reservd_space_normalized": 100,
|
||||
"device_sdc_type_sat_attr_available_reservd_space_raw": 100,
|
||||
"device_sdc_type_sat_attr_command_timeout_decoded": 0,
|
||||
"device_sdc_type_sat_attr_command_timeout_normalized": 100,
|
||||
"device_sdc_type_sat_attr_command_timeout_raw": 0,
|
||||
"device_sdc_type_sat_attr_end-to-end_error_decoded": 0,
|
||||
"device_sdc_type_sat_attr_end-to-end_error_normalized": 100,
|
||||
"device_sdc_type_sat_attr_end-to-end_error_raw": 0,
|
||||
"device_sdc_type_sat_attr_media_wearout_indicator_decoded": 65406,
|
||||
"device_sdc_type_sat_attr_media_wearout_indicator_normalized": 100,
|
||||
"device_sdc_type_sat_attr_media_wearout_indicator_raw": 65406,
|
||||
"device_sdc_type_sat_attr_power_cycle_count_decoded": 13,
|
||||
"device_sdc_type_sat_attr_power_cycle_count_normalized": 100,
|
||||
"device_sdc_type_sat_attr_power_cycle_count_raw": 13,
|
||||
"device_sdc_type_sat_attr_power_on_hours_decoded": 8244,
|
||||
"device_sdc_type_sat_attr_power_on_hours_normalized": 100,
|
||||
"device_sdc_type_sat_attr_power_on_hours_raw": 8244,
|
||||
"device_sdc_type_sat_attr_reallocated_sector_ct_decoded": 0,
|
||||
"device_sdc_type_sat_attr_reallocated_sector_ct_normalized": 100,
|
||||
"device_sdc_type_sat_attr_reallocated_sector_ct_raw": 0,
|
||||
"device_sdc_type_sat_attr_reported_uncorrect_decoded": 0,
|
||||
"device_sdc_type_sat_attr_reported_uncorrect_normalized": 100,
|
||||
"device_sdc_type_sat_attr_reported_uncorrect_raw": 0,
|
||||
"device_sdc_type_sat_attr_temperature_celsius_decoded": 27,
|
||||
"device_sdc_type_sat_attr_temperature_celsius_normalized": 73,
|
||||
"device_sdc_type_sat_attr_temperature_celsius_raw": 184684970011,
|
||||
"device_sdc_type_sat_attr_total_lbas_read_decoded": 76778,
|
||||
"device_sdc_type_sat_attr_total_lbas_read_normalized": 253,
|
||||
"device_sdc_type_sat_attr_total_lbas_read_raw": 76778,
|
||||
"device_sdc_type_sat_attr_total_lbas_written_decoded": 173833,
|
||||
"device_sdc_type_sat_attr_total_lbas_written_normalized": 253,
|
||||
"device_sdc_type_sat_attr_total_lbas_written_raw": 173833,
|
||||
"device_sdc_type_sat_attr_udma_crc_error_count_decoded": 0,
|
||||
"device_sdc_type_sat_attr_udma_crc_error_count_normalized": 100,
|
||||
"device_sdc_type_sat_attr_udma_crc_error_count_raw": 0,
|
||||
"device_sdc_type_sat_attr_unknown_attribute_decoded": 0,
|
||||
"device_sdc_type_sat_attr_unknown_attribute_normalized": 0,
|
||||
"device_sdc_type_sat_attr_unknown_attribute_raw": 0,
|
||||
"device_sdc_type_sat_attr_unknown_ssd_attribute_decoded": 4694419309637,
|
||||
"device_sdc_type_sat_attr_unknown_ssd_attribute_normalized": 4,
|
||||
"device_sdc_type_sat_attr_unknown_ssd_attribute_raw": 4694419309637,
|
||||
"device_sdc_type_sat_power_cycle_count": 13,
|
||||
"device_sdc_type_sat_power_on_time": 29678400,
|
||||
"device_sdc_type_sat_smart_status_failed": 0,
|
||||
"device_sdc_type_sat_smart_status_passed": 1,
|
||||
"device_sdc_type_sat_temperature": 27,
|
||||
},
|
||||
},
|
||||
"success type nvme devices": {
|
||||
prepareMock: prepareMockOkTypeNvme,
|
||||
wantCharts: 4,
|
||||
wantMetrics: map[string]int64{
|
||||
"device_nvme0_type_nvme_power_cycle_count": 2,
|
||||
"device_nvme0_type_nvme_power_on_time": 11206800,
|
||||
"device_nvme0_type_nvme_smart_status_failed": 0,
|
||||
"device_nvme0_type_nvme_smart_status_passed": 1,
|
||||
"device_nvme0_type_nvme_temperature": 39,
|
||||
},
|
||||
},
|
||||
"error on scan": {
|
||||
prepareMock: prepareMockErrOnScan,
|
||||
},
|
||||
"unexpected response on scan": {
|
||||
prepareMock: prepareMockUnexpectedResponse,
|
||||
},
|
||||
"empty response on scan": {
|
||||
prepareMock: prepareMockEmptyResponse,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
smart := New()
|
||||
mock := test.prepareMock()
|
||||
smart.exec = mock
|
||||
smart.ScanEvery = web.Duration(time.Microsecond * 1)
|
||||
smart.PollDevicesEvery = web.Duration(time.Microsecond * 1)
|
||||
|
||||
var mx map[string]int64
|
||||
for i := 0; i < 10; i++ {
|
||||
mx = smart.Collect()
|
||||
}
|
||||
|
||||
assert.Equal(t, test.wantMetrics, mx)
|
||||
assert.Len(t, *smart.Charts(), test.wantCharts)
|
||||
testMetricsHasAllChartsDims(t, smart, mx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testMetricsHasAllChartsDims(t *testing.T, smart *Smartctl, mx map[string]int64) {
|
||||
for _, chart := range *smart.Charts() {
|
||||
if chart.Obsolete {
|
||||
continue
|
||||
}
|
||||
for _, dim := range chart.Dims {
|
||||
_, ok := mx[dim.ID]
|
||||
assert.Truef(t, ok, "collected metrics has no data for dim '%s' chart '%s'", dim.ID, chart.ID)
|
||||
}
|
||||
for _, v := range chart.Vars {
|
||||
_, ok := mx[v.ID]
|
||||
assert.Truef(t, ok, "collected metrics has no data for var '%s' chart '%s'", v.ID, chart.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func prepareMockOkTypeSata() *mockSmartctlCliExec {
|
||||
return &mockSmartctlCliExec{
|
||||
errOnScan: false,
|
||||
scanData: dataTypeSataScan,
|
||||
deviceDataFunc: func(deviceName, deviceType, powerMode string) ([]byte, error) {
|
||||
if deviceType != "sat" {
|
||||
return nil, fmt.Errorf("unexpected device type %s", deviceType)
|
||||
}
|
||||
switch deviceName {
|
||||
case "/dev/sda":
|
||||
return dataTypeSataDeviceHDDSda, nil
|
||||
case "/dev/sdc":
|
||||
return dataTypeSataDeviceSSDSdc, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected device name %s", deviceName)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func prepareMockOkTypeNvme() *mockSmartctlCliExec {
|
||||
return &mockSmartctlCliExec{
|
||||
errOnScan: false,
|
||||
scanData: dataTypeNvmeScan,
|
||||
deviceDataFunc: func(deviceName, deviceType, powerMode string) ([]byte, error) {
|
||||
if deviceType != "nvme" {
|
||||
return nil, fmt.Errorf("unexpected device type %s", deviceType)
|
||||
}
|
||||
switch deviceName {
|
||||
case "/dev/nvme0":
|
||||
return dataTypeNvmeDeviceNvme0, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected device name %s", deviceName)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func prepareMockErrOnScan() *mockSmartctlCliExec {
|
||||
return &mockSmartctlCliExec{
|
||||
errOnScan: true,
|
||||
}
|
||||
}
|
||||
|
||||
func prepareMockUnexpectedResponse() *mockSmartctlCliExec {
|
||||
return &mockSmartctlCliExec{
|
||||
scanData: []byte(randomJsonData),
|
||||
deviceDataFunc: func(_, _, _ string) ([]byte, error) { return []byte(randomJsonData), nil },
|
||||
}
|
||||
}
|
||||
|
||||
func prepareMockEmptyResponse() *mockSmartctlCliExec {
|
||||
return &mockSmartctlCliExec{}
|
||||
}
|
||||
|
||||
type mockSmartctlCliExec struct {
|
||||
errOnScan bool
|
||||
scanData []byte
|
||||
deviceDataFunc func(deviceName, deviceType, powerMode string) ([]byte, error)
|
||||
}
|
||||
|
||||
func (m *mockSmartctlCliExec) scan() (*gjson.Result, error) {
|
||||
if m.errOnScan {
|
||||
return nil, fmt.Errorf("mock.scan() error")
|
||||
}
|
||||
res := gjson.ParseBytes(m.scanData)
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (m *mockSmartctlCliExec) deviceInfo(deviceName, deviceType, powerMode string) (*gjson.Result, error) {
|
||||
if m.deviceDataFunc == nil {
|
||||
return nil, nil
|
||||
}
|
||||
bs, err := m.deviceDataFunc(deviceName, deviceType, powerMode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := gjson.ParseBytes(bs)
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
var randomJsonData = `
|
||||
{
|
||||
"elephant": {
|
||||
"burn": false,
|
||||
"mountain": true,
|
||||
"fog": false,
|
||||
"skin": -1561907625,
|
||||
"burst": "anyway",
|
||||
"shadow": 1558616893
|
||||
},
|
||||
"start": "ever",
|
||||
"base": 2093056027,
|
||||
"mission": -2007590351,
|
||||
"victory": 999053756,
|
||||
"die": false
|
||||
}
|
||||
`
|
7
src/go/collectors/go.d.plugin/modules/smartctl/testdata/config.json
vendored
Normal file
7
src/go/collectors/go.d.plugin/modules/smartctl/testdata/config.json
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"update_every": 123,
|
||||
"timeout": 123.123,
|
||||
"scan_every": 123.123,
|
||||
"poll_devices_every": 123.123,
|
||||
"no_check_power_mode": "ok"
|
||||
}
|
5
src/go/collectors/go.d.plugin/modules/smartctl/testdata/config.yaml
vendored
Normal file
5
src/go/collectors/go.d.plugin/modules/smartctl/testdata/config.yaml
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
update_every: 123
|
||||
timeout: 123.123
|
||||
scan_every: 123.123
|
||||
poll_devices_every: 123.123
|
||||
no_check_power_mode: "ok"
|
112
src/go/collectors/go.d.plugin/modules/smartctl/testdata/type-nvme/device-nvme0.json
vendored
Normal file
112
src/go/collectors/go.d.plugin/modules/smartctl/testdata/type-nvme/device-nvme0.json
vendored
Normal file
|
@ -0,0 +1,112 @@
|
|||
{
|
||||
"json_format_version": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"smartctl": {
|
||||
"version": [
|
||||
7,
|
||||
3
|
||||
],
|
||||
"svn_revision": "5338",
|
||||
"platform_info": "REDACTED",
|
||||
"build_info": "(local build)",
|
||||
"argv": [
|
||||
"smartctl",
|
||||
"--all",
|
||||
"--json",
|
||||
"--device=nvme",
|
||||
"/dev/nvme0"
|
||||
],
|
||||
"exit_status": 0
|
||||
},
|
||||
"local_time": {
|
||||
"time_t": 1714480742,
|
||||
"asctime": "Tue Apr 30 15:39:02 2024 EEST"
|
||||
},
|
||||
"device": {
|
||||
"name": "/dev/nvme0",
|
||||
"info_name": "/dev/nvme0",
|
||||
"type": "nvme",
|
||||
"protocol": "NVMe"
|
||||
},
|
||||
"model_name": "Seagate FireCuda 530 ZP4000GM30023",
|
||||
"serial_number": "REDACTED",
|
||||
"firmware_version": "REDACTED",
|
||||
"nvme_pci_vendor": {
|
||||
"id": 7089,
|
||||
"subsystem_id": 7089
|
||||
},
|
||||
"nvme_ieee_oui_identifier": 6584743,
|
||||
"nvme_total_capacity": 4000787030016,
|
||||
"nvme_unallocated_capacity": 0,
|
||||
"nvme_controller_id": 1,
|
||||
"nvme_version": {
|
||||
"string": "1.4",
|
||||
"value": 66560
|
||||
},
|
||||
"nvme_number_of_namespaces": 1,
|
||||
"nvme_namespaces": [
|
||||
{
|
||||
"id": 1,
|
||||
"size": {
|
||||
"blocks": 7814037168,
|
||||
"bytes": 4000787030016
|
||||
},
|
||||
"capacity": {
|
||||
"blocks": 7814037168,
|
||||
"bytes": 4000787030016
|
||||
},
|
||||
"utilization": {
|
||||
"blocks": 7814037168,
|
||||
"bytes": 4000787030016
|
||||
},
|
||||
"formatted_lba_size": 512,
|
||||
"eui64": {
|
||||
"oui": 6584743,
|
||||
"ext_id": 553497146765
|
||||
}
|
||||
}
|
||||
],
|
||||
"user_capacity": {
|
||||
"blocks": 7814037168,
|
||||
"bytes": 4000787030016
|
||||
},
|
||||
"logical_block_size": 512,
|
||||
"smart_support": {
|
||||
"available": true,
|
||||
"enabled": true
|
||||
},
|
||||
"smart_status": {
|
||||
"passed": true,
|
||||
"nvme": {
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"nvme_smart_health_information_log": {
|
||||
"critical_warning": 0,
|
||||
"temperature": 39,
|
||||
"available_spare": 100,
|
||||
"available_spare_threshold": 5,
|
||||
"percentage_used": 0,
|
||||
"data_units_read": 52,
|
||||
"data_units_written": 0,
|
||||
"host_reads": 550,
|
||||
"host_writes": 0,
|
||||
"controller_busy_time": 0,
|
||||
"power_cycles": 2,
|
||||
"power_on_hours": 3113,
|
||||
"unsafe_shutdowns": 1,
|
||||
"media_errors": 0,
|
||||
"num_err_log_entries": 4,
|
||||
"warning_temp_time": 0,
|
||||
"critical_comp_time": 0
|
||||
},
|
||||
"temperature": {
|
||||
"current": 39
|
||||
},
|
||||
"power_cycle_count": 2,
|
||||
"power_on_time": {
|
||||
"hours": 3113
|
||||
}
|
||||
}
|
29
src/go/collectors/go.d.plugin/modules/smartctl/testdata/type-nvme/scan.json
vendored
Normal file
29
src/go/collectors/go.d.plugin/modules/smartctl/testdata/type-nvme/scan.json
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"json_format_version": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"smartctl": {
|
||||
"version": [
|
||||
7,
|
||||
3
|
||||
],
|
||||
"svn_revision": "5338",
|
||||
"platform_info": "REDACTED",
|
||||
"build_info": "(local build)",
|
||||
"argv": [
|
||||
"smartctl",
|
||||
"--scan",
|
||||
"--json"
|
||||
],
|
||||
"exit_status": 0
|
||||
},
|
||||
"devices": [
|
||||
{
|
||||
"name": "/dev/nvme0",
|
||||
"info_name": "/dev/nvme0",
|
||||
"type": "nvme",
|
||||
"protocol": "NVMe"
|
||||
}
|
||||
]
|
||||
}
|
601
src/go/collectors/go.d.plugin/modules/smartctl/testdata/type-sat/device-hdd-sda.json
vendored
Normal file
601
src/go/collectors/go.d.plugin/modules/smartctl/testdata/type-sat/device-hdd-sda.json
vendored
Normal file
|
@ -0,0 +1,601 @@
|
|||
{
|
||||
"json_format_version": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"smartctl": {
|
||||
"version": [
|
||||
7,
|
||||
3
|
||||
],
|
||||
"svn_revision": "5338",
|
||||
"platform_info": "REDACTED",
|
||||
"build_info": "(local build)",
|
||||
"argv": [
|
||||
"smartctl",
|
||||
"--all",
|
||||
"--json",
|
||||
"--device=sat",
|
||||
"/dev/sda"
|
||||
],
|
||||
"drive_database_version": {
|
||||
"string": "7.3/5319"
|
||||
},
|
||||
"exit_status": 0
|
||||
},
|
||||
"local_time": {
|
||||
"time_t": 1714480013,
|
||||
"asctime": "Tue Apr 30 15:26:53 2024 EEST"
|
||||
},
|
||||
"device": {
|
||||
"name": "/dev/sda",
|
||||
"info_name": "/dev/sda [SAT]",
|
||||
"type": "sat",
|
||||
"protocol": "ATA"
|
||||
},
|
||||
"model_name": "WDC WD181KRYZ-01AGBB0",
|
||||
"serial_number": "REDACTED",
|
||||
"wwn": {
|
||||
"naa": 5,
|
||||
"oui": 3274,
|
||||
"id": 11659362274
|
||||
},
|
||||
"firmware_version": "REDACTED",
|
||||
"user_capacity": {
|
||||
"blocks": 35156656128,
|
||||
"bytes": 18000207937536
|
||||
},
|
||||
"logical_block_size": 512,
|
||||
"physical_block_size": 4096,
|
||||
"rotation_rate": 7200,
|
||||
"form_factor": {
|
||||
"ata_value": 2,
|
||||
"name": "3.5 inches"
|
||||
},
|
||||
"trim": {
|
||||
"supported": false
|
||||
},
|
||||
"in_smartctl_database": false,
|
||||
"ata_version": {
|
||||
"string": "ACS-4 published, ANSI INCITS 529-2018",
|
||||
"major_value": 4092,
|
||||
"minor_value": 156
|
||||
},
|
||||
"sata_version": {
|
||||
"string": "SATA 3.3",
|
||||
"value": 511
|
||||
},
|
||||
"interface_speed": {
|
||||
"max": {
|
||||
"sata_value": 14,
|
||||
"string": "6.0 Gb/s",
|
||||
"units_per_second": 60,
|
||||
"bits_per_unit": 100000000
|
||||
},
|
||||
"current": {
|
||||
"sata_value": 3,
|
||||
"string": "6.0 Gb/s",
|
||||
"units_per_second": 60,
|
||||
"bits_per_unit": 100000000
|
||||
}
|
||||
},
|
||||
"smart_support": {
|
||||
"available": true,
|
||||
"enabled": true
|
||||
},
|
||||
"smart_status": {
|
||||
"passed": true
|
||||
},
|
||||
"ata_smart_data": {
|
||||
"offline_data_collection": {
|
||||
"status": {
|
||||
"value": 130,
|
||||
"string": "was completed without error",
|
||||
"passed": true
|
||||
},
|
||||
"completion_seconds": 101
|
||||
},
|
||||
"self_test": {
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "completed without error",
|
||||
"passed": true
|
||||
},
|
||||
"polling_minutes": {
|
||||
"short": 2,
|
||||
"extended": 1883
|
||||
}
|
||||
},
|
||||
"capabilities": {
|
||||
"values": [
|
||||
91,
|
||||
3
|
||||
],
|
||||
"exec_offline_immediate_supported": true,
|
||||
"offline_is_aborted_upon_new_cmd": false,
|
||||
"offline_surface_scan_supported": true,
|
||||
"self_tests_supported": true,
|
||||
"conveyance_self_test_supported": false,
|
||||
"selective_self_test_supported": true,
|
||||
"attribute_autosave_enabled": true,
|
||||
"error_logging_supported": true,
|
||||
"gp_logging_supported": true
|
||||
}
|
||||
},
|
||||
"ata_sct_capabilities": {
|
||||
"value": 61,
|
||||
"error_recovery_control_supported": true,
|
||||
"feature_control_supported": true,
|
||||
"data_table_supported": true
|
||||
},
|
||||
"ata_smart_attributes": {
|
||||
"revision": 16,
|
||||
"table": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Raw_Read_Error_Rate",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 1,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 11,
|
||||
"string": "PO-R-- ",
|
||||
"prefailure": true,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": true,
|
||||
"event_count": false,
|
||||
"auto_keep": false
|
||||
},
|
||||
"raw": {
|
||||
"value": 0,
|
||||
"string": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Throughput_Performance",
|
||||
"value": 148,
|
||||
"worst": 148,
|
||||
"thresh": 54,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 5,
|
||||
"string": "P-S--- ",
|
||||
"prefailure": true,
|
||||
"updated_online": false,
|
||||
"performance": true,
|
||||
"error_rate": false,
|
||||
"event_count": false,
|
||||
"auto_keep": false
|
||||
},
|
||||
"raw": {
|
||||
"value": 48,
|
||||
"string": "48"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Spin_Up_Time",
|
||||
"value": 86,
|
||||
"worst": 86,
|
||||
"thresh": 1,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 7,
|
||||
"string": "POS--- ",
|
||||
"prefailure": true,
|
||||
"updated_online": true,
|
||||
"performance": true,
|
||||
"error_rate": false,
|
||||
"event_count": false,
|
||||
"auto_keep": false
|
||||
},
|
||||
"raw": {
|
||||
"value": 25788088601,
|
||||
"string": "281 (Average 279)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Start_Stop_Count",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 0,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 18,
|
||||
"string": "-O--C- ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": false
|
||||
},
|
||||
"raw": {
|
||||
"value": 12,
|
||||
"string": "12"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Reallocated_Sector_Ct",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 1,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 51,
|
||||
"string": "PO--CK ",
|
||||
"prefailure": true,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 0,
|
||||
"string": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"name": "Seek_Error_Rate",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 1,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 11,
|
||||
"string": "PO-R-- ",
|
||||
"prefailure": true,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": true,
|
||||
"event_count": false,
|
||||
"auto_keep": false
|
||||
},
|
||||
"raw": {
|
||||
"value": 0,
|
||||
"string": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"name": "Seek_Time_Performance",
|
||||
"value": 140,
|
||||
"worst": 140,
|
||||
"thresh": 20,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 5,
|
||||
"string": "P-S--- ",
|
||||
"prefailure": true,
|
||||
"updated_online": false,
|
||||
"performance": true,
|
||||
"error_rate": false,
|
||||
"event_count": false,
|
||||
"auto_keep": false
|
||||
},
|
||||
"raw": {
|
||||
"value": 15,
|
||||
"string": "15"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"name": "Power_On_Hours",
|
||||
"value": 99,
|
||||
"worst": 99,
|
||||
"thresh": 0,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 18,
|
||||
"string": "-O--C- ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": false
|
||||
},
|
||||
"raw": {
|
||||
"value": 8244,
|
||||
"string": "8244"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"name": "Spin_Retry_Count",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 1,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 19,
|
||||
"string": "PO--C- ",
|
||||
"prefailure": true,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": false
|
||||
},
|
||||
"raw": {
|
||||
"value": 0,
|
||||
"string": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"name": "Power_Cycle_Count",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 0,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 12,
|
||||
"string": "12"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"name": "Unknown_Attribute",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 25,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 35,
|
||||
"string": "PO---K ",
|
||||
"prefailure": true,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": false,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 100,
|
||||
"string": "100"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 192,
|
||||
"name": "Power-Off_Retract_Count",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 0,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 360,
|
||||
"string": "360"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 193,
|
||||
"name": "Load_Cycle_Count",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 0,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 18,
|
||||
"string": "-O--C- ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": false
|
||||
},
|
||||
"raw": {
|
||||
"value": 360,
|
||||
"string": "360"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 194,
|
||||
"name": "Temperature_Celsius",
|
||||
"value": 43,
|
||||
"worst": 43,
|
||||
"thresh": 0,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 2,
|
||||
"string": "-O---- ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": false,
|
||||
"auto_keep": false
|
||||
},
|
||||
"raw": {
|
||||
"value": 240519741489,
|
||||
"string": "49 (Min/Max 24/56)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 196,
|
||||
"name": "Reallocated_Event_Count",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 0,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 0,
|
||||
"string": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 197,
|
||||
"name": "Current_Pending_Sector",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 0,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 34,
|
||||
"string": "-O---K ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": false,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 0,
|
||||
"string": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 198,
|
||||
"name": "Offline_Uncorrectable",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 0,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 8,
|
||||
"string": "---R-- ",
|
||||
"prefailure": false,
|
||||
"updated_online": false,
|
||||
"performance": false,
|
||||
"error_rate": true,
|
||||
"event_count": false,
|
||||
"auto_keep": false
|
||||
},
|
||||
"raw": {
|
||||
"value": 0,
|
||||
"string": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 199,
|
||||
"name": "UDMA_CRC_Error_Count",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 0,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 10,
|
||||
"string": "-O-R-- ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": true,
|
||||
"event_count": false,
|
||||
"auto_keep": false
|
||||
},
|
||||
"raw": {
|
||||
"value": 0,
|
||||
"string": "0"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"power_on_time": {
|
||||
"hours": 8244
|
||||
},
|
||||
"power_cycle_count": 12,
|
||||
"temperature": {
|
||||
"current": 49
|
||||
},
|
||||
"ata_smart_error_log": {
|
||||
"summary": {
|
||||
"revision": 1,
|
||||
"count": 0
|
||||
}
|
||||
},
|
||||
"ata_smart_self_test_log": {
|
||||
"standard": {
|
||||
"revision": 1,
|
||||
"count": 0
|
||||
}
|
||||
},
|
||||
"ata_smart_selective_self_test_log": {
|
||||
"revision": 1,
|
||||
"table": [
|
||||
{
|
||||
"lba_min": 0,
|
||||
"lba_max": 0,
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Not_testing"
|
||||
}
|
||||
},
|
||||
{
|
||||
"lba_min": 0,
|
||||
"lba_max": 0,
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Not_testing"
|
||||
}
|
||||
},
|
||||
{
|
||||
"lba_min": 0,
|
||||
"lba_max": 0,
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Not_testing"
|
||||
}
|
||||
},
|
||||
{
|
||||
"lba_min": 0,
|
||||
"lba_max": 0,
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Not_testing"
|
||||
}
|
||||
},
|
||||
{
|
||||
"lba_min": 0,
|
||||
"lba_max": 0,
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "Not_testing"
|
||||
}
|
||||
}
|
||||
],
|
||||
"flags": {
|
||||
"value": 0,
|
||||
"remainder_scan_enabled": false
|
||||
},
|
||||
"power_up_scan_resume_minutes": 0
|
||||
}
|
||||
}
|
652
src/go/collectors/go.d.plugin/modules/smartctl/testdata/type-sat/device-ssd-sdc.json
vendored
Normal file
652
src/go/collectors/go.d.plugin/modules/smartctl/testdata/type-sat/device-ssd-sdc.json
vendored
Normal file
|
@ -0,0 +1,652 @@
|
|||
{
|
||||
"json_format_version": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"smartctl": {
|
||||
"version": [
|
||||
7,
|
||||
3
|
||||
],
|
||||
"svn_revision": "5338",
|
||||
"platform_info": "REDACTED",
|
||||
"build_info": "(local build)",
|
||||
"argv": [
|
||||
"smartctl",
|
||||
"--all",
|
||||
"--json",
|
||||
"--device=sat",
|
||||
"/dev/sdc"
|
||||
],
|
||||
"drive_database_version": {
|
||||
"string": "7.3/5319"
|
||||
},
|
||||
"exit_status": 0
|
||||
},
|
||||
"local_time": {
|
||||
"time_t": 1714480059,
|
||||
"asctime": "Tue Apr 30 15:27:39 2024 EEST"
|
||||
},
|
||||
"device": {
|
||||
"name": "/dev/sdc",
|
||||
"info_name": "/dev/sdc [SAT]",
|
||||
"type": "sat",
|
||||
"protocol": "ATA"
|
||||
},
|
||||
"model_name": "WDC WDS400T1R0A-68A4W0",
|
||||
"serial_number": "REDACTED",
|
||||
"wwn": {
|
||||
"naa": 5,
|
||||
"oui": 6980,
|
||||
"id": 37319905210
|
||||
},
|
||||
"firmware_version": "REDACTED",
|
||||
"user_capacity": {
|
||||
"blocks": 7814037168,
|
||||
"bytes": 4000787030016
|
||||
},
|
||||
"logical_block_size": 512,
|
||||
"physical_block_size": 512,
|
||||
"rotation_rate": 0,
|
||||
"form_factor": {
|
||||
"ata_value": 3,
|
||||
"name": "2.5 inches"
|
||||
},
|
||||
"trim": {
|
||||
"supported": true,
|
||||
"deterministic": true,
|
||||
"zeroed": true
|
||||
},
|
||||
"in_smartctl_database": false,
|
||||
"ata_version": {
|
||||
"string": "ACS-4 T13/BSR INCITS 529 revision 5",
|
||||
"major_value": 4080,
|
||||
"minor_value": 94
|
||||
},
|
||||
"sata_version": {
|
||||
"string": "SATA 3.3",
|
||||
"value": 511
|
||||
},
|
||||
"interface_speed": {
|
||||
"max": {
|
||||
"sata_value": 14,
|
||||
"string": "6.0 Gb/s",
|
||||
"units_per_second": 60,
|
||||
"bits_per_unit": 100000000
|
||||
},
|
||||
"current": {
|
||||
"sata_value": 3,
|
||||
"string": "6.0 Gb/s",
|
||||
"units_per_second": 60,
|
||||
"bits_per_unit": 100000000
|
||||
}
|
||||
},
|
||||
"smart_support": {
|
||||
"available": true,
|
||||
"enabled": true
|
||||
},
|
||||
"smart_status": {
|
||||
"passed": true
|
||||
},
|
||||
"ata_smart_data": {
|
||||
"offline_data_collection": {
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "was never started"
|
||||
},
|
||||
"completion_seconds": 0
|
||||
},
|
||||
"self_test": {
|
||||
"status": {
|
||||
"value": 0,
|
||||
"string": "completed without error",
|
||||
"passed": true
|
||||
},
|
||||
"polling_minutes": {
|
||||
"short": 2,
|
||||
"extended": 10
|
||||
}
|
||||
},
|
||||
"capabilities": {
|
||||
"values": [
|
||||
17,
|
||||
3
|
||||
],
|
||||
"exec_offline_immediate_supported": true,
|
||||
"offline_is_aborted_upon_new_cmd": false,
|
||||
"offline_surface_scan_supported": false,
|
||||
"self_tests_supported": true,
|
||||
"conveyance_self_test_supported": false,
|
||||
"selective_self_test_supported": false,
|
||||
"attribute_autosave_enabled": true,
|
||||
"error_logging_supported": true,
|
||||
"gp_logging_supported": true
|
||||
}
|
||||
},
|
||||
"ata_smart_attributes": {
|
||||
"revision": 4,
|
||||
"table": [
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Reallocated_Sector_Ct",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 0,
|
||||
"string": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"name": "Power_On_Hours",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 8244,
|
||||
"string": "8244"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"name": "Power_Cycle_Count",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 13,
|
||||
"string": "13"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 165,
|
||||
"name": "Unknown_Attribute",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 515627355118,
|
||||
"string": "515627355118"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 166,
|
||||
"name": "Unknown_Attribute",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 1,
|
||||
"string": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 167,
|
||||
"name": "Unknown_Attribute",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 84,
|
||||
"string": "84"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 168,
|
||||
"name": "Unknown_Attribute",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 34,
|
||||
"string": "34"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 169,
|
||||
"name": "Unknown_Attribute",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 1600,
|
||||
"string": "1600"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 170,
|
||||
"name": "Unknown_Attribute",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 0,
|
||||
"string": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 171,
|
||||
"name": "Unknown_Attribute",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 0,
|
||||
"string": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 172,
|
||||
"name": "Unknown_Attribute",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 0,
|
||||
"string": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 173,
|
||||
"name": "Unknown_Attribute",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 15,
|
||||
"string": "15"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 174,
|
||||
"name": "Unknown_Attribute",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 4,
|
||||
"string": "4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 184,
|
||||
"name": "End-to-End_Error",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 0,
|
||||
"string": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 187,
|
||||
"name": "Reported_Uncorrect",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 0,
|
||||
"string": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 188,
|
||||
"name": "Command_Timeout",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 0,
|
||||
"string": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 194,
|
||||
"name": "Temperature_Celsius",
|
||||
"value": 73,
|
||||
"worst": 43,
|
||||
"flags": {
|
||||
"value": 34,
|
||||
"string": "-O---K ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": false,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 184684970011,
|
||||
"string": "27 (Min/Max 21/43)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 199,
|
||||
"name": "UDMA_CRC_Error_Count",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 0,
|
||||
"string": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 230,
|
||||
"name": "Unknown_SSD_Attribute",
|
||||
"value": 4,
|
||||
"worst": 4,
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 4694419309637,
|
||||
"string": "4694419309637"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 232,
|
||||
"name": "Available_Reservd_Space",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"thresh": 4,
|
||||
"when_failed": "",
|
||||
"flags": {
|
||||
"value": 51,
|
||||
"string": "PO--CK ",
|
||||
"prefailure": true,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 100,
|
||||
"string": "100"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 233,
|
||||
"name": "Media_Wearout_Indicator",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 65406,
|
||||
"string": "65406"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 234,
|
||||
"name": "Unknown_Attribute",
|
||||
"value": 100,
|
||||
"worst": 100,
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 190852,
|
||||
"string": "190852"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 241,
|
||||
"name": "Total_LBAs_Written",
|
||||
"value": 253,
|
||||
"worst": 253,
|
||||
"flags": {
|
||||
"value": 48,
|
||||
"string": "----CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": false,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 173833,
|
||||
"string": "173833"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 242,
|
||||
"name": "Total_LBAs_Read",
|
||||
"value": 253,
|
||||
"worst": 253,
|
||||
"flags": {
|
||||
"value": 48,
|
||||
"string": "----CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": false,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 76778,
|
||||
"string": "76778"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 244,
|
||||
"name": "Unknown_Attribute",
|
||||
"value": 0,
|
||||
"worst": 100,
|
||||
"flags": {
|
||||
"value": 50,
|
||||
"string": "-O--CK ",
|
||||
"prefailure": false,
|
||||
"updated_online": true,
|
||||
"performance": false,
|
||||
"error_rate": false,
|
||||
"event_count": true,
|
||||
"auto_keep": true
|
||||
},
|
||||
"raw": {
|
||||
"value": 0,
|
||||
"string": "0"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"power_on_time": {
|
||||
"hours": 8244
|
||||
},
|
||||
"power_cycle_count": 13,
|
||||
"temperature": {
|
||||
"current": 27
|
||||
},
|
||||
"ata_smart_error_log": {
|
||||
"summary": {
|
||||
"revision": 1,
|
||||
"count": 0
|
||||
}
|
||||
},
|
||||
"ata_smart_self_test_log": {
|
||||
"standard": {
|
||||
"revision": 1,
|
||||
"count": 0
|
||||
}
|
||||
}
|
||||
}
|
35
src/go/collectors/go.d.plugin/modules/smartctl/testdata/type-sat/scan.json
vendored
Normal file
35
src/go/collectors/go.d.plugin/modules/smartctl/testdata/type-sat/scan.json
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"json_format_version": [
|
||||
1,
|
||||
0
|
||||
],
|
||||
"smartctl": {
|
||||
"version": [
|
||||
7,
|
||||
3
|
||||
],
|
||||
"svn_revision": "5338",
|
||||
"platform_info": "REDACTED",
|
||||
"build_info": "(local build)",
|
||||
"argv": [
|
||||
"smartctl",
|
||||
"--scan",
|
||||
"--json"
|
||||
],
|
||||
"exit_status": 0
|
||||
},
|
||||
"devices": [
|
||||
{
|
||||
"name": "/dev/sda",
|
||||
"info_name": "/dev/sda [SAT]",
|
||||
"type": "sat",
|
||||
"protocol": "ATA"
|
||||
},
|
||||
{
|
||||
"name": "/dev/sdc",
|
||||
"info_name": "/dev/sdc [SAT]",
|
||||
"type": "sat",
|
||||
"protocol": "ATA"
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Add table
Reference in a new issue