go.d smartctl (#17536)
Ilya Mashchenko committed
Apr 30, 2024 at 18:24 UTC
e1c4f7fec367a610c9c8beea1789c51c6434d51a
23 files changed
+2973
-2
src/go/collectors/go.d.plugin/README.md
+2
-2
@@ -118,6 +118,7 @@ see the appropriate collector readme.
118
| [sensors](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules) | Hardware Sensors |
119
| [SNMP](https://github.com/netdata/netdata/blob/master/src/go/collectors/go.d.plugin/modules/snmp) | SNMP |
120
| [squidlog](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/squidlog) | Squid |
121
+| [smartctl](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/smartctl) | S.M.A.R.T Storage Devices |
122
| [storcli](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/storcli) | Broadcom Hardware RAID |
123
| [supervisord](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/supervisord) | Supervisor |
124
| [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
214
./go.d.plugin -d -m <module name>
215
```
216
216
-Change `<module name>` to the module name you want to debug. See the [whole list](#available-modules) of available
217
-modules.
217
+Change `<module name>` to the [module name](#available-modules) you want to debug.
218
219
## Netdata Community
220
src/go/collectors/go.d.plugin/config/go.d.conf
+1
@@ -80,6 +80,7 @@ modules:
80
# sensors: yes
81
# snmp: yes
82
# squidlog: yes
83
+# smartctl: yes
84
# storcli: yes
85
# supervisord: yes
86
# systemdunits: yes
src/go/collectors/go.d.plugin/config/go.d/smartctl.conf
new
+5
@@ -0,0 +1,5 @@
1
+## All available configuration options, their descriptions and default values:
2
+## https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/smartctl#readme
3
+
4
+jobs:
5
+ - name: smartctl
src/go/collectors/go.d.plugin/go.mod
+3
@@ -119,6 +119,9 @@ require (
119
github.com/sirupsen/logrus v1.9.3 // indirect
120
github.com/spf13/cast v1.3.1 // indirect
121
github.com/spf13/pflag v1.0.5 // indirect
122
+ github.com/tidwall/gjson v1.17.1 // indirect
123
+ github.com/tidwall/match v1.1.1 // indirect
124
+ github.com/tidwall/pretty v1.2.0 // indirect
125
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
126
github.com/xdg-go/scram v1.1.2 // indirect
127
github.com/xdg-go/stringprep v1.0.4 // indirect
src/go/collectors/go.d.plugin/go.sum
+6
@@ -332,6 +332,12 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
332
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
333
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
334
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
335
+github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
336
+github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
337
+github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
338
+github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
339
+github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
340
+github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
341
github.com/tomasen/fcgi_client v0.0.0-20180423082037-2bb3d819fd19 h1:ZCmSnT6CLGhfoQ2lPEhL4nsJstKDCw1F1RfN8/smTCU=
342
github.com/tomasen/fcgi_client v0.0.0-20180423082037-2bb3d819fd19/go.mod h1:SXTY+QvI+KTTKXQdg0zZ7nx0u94QWh8ZAwBQYsW9cqk=
343
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
src/go/collectors/go.d.plugin/modules/init.go
+1
@@ -70,6 +70,7 @@ import (
70
_ "github.com/netdata/netdata/go/go.d.plugin/modules/redis"
71
_ "github.com/netdata/netdata/go/go.d.plugin/modules/scaleio"
72
_ "github.com/netdata/netdata/go/go.d.plugin/modules/sensors"
73
+ _ "github.com/netdata/netdata/go/go.d.plugin/modules/smartctl"
74
_ "github.com/netdata/netdata/go/go.d.plugin/modules/snmp"
75
_ "github.com/netdata/netdata/go/go.d.plugin/modules/squidlog"
76
_ "github.com/netdata/netdata/go/go.d.plugin/modules/storcli"
src/go/collectors/go.d.plugin/modules/smartctl/charts.go
new
+237
@@ -0,0 +1,237 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package smartctl
4
+
5
+import (
6
+ "fmt"
7
+ "strings"
8
+
9
+ "github.com/netdata/netdata/go/go.d.plugin/agent/module"
10
+)
11
+
12
+const (
13
+ prioDeviceSmartStatus = module.Priority + iota
14
+ prioDeviceAtaSmartErrorLogCount
15
+ prioDevicePowerOnTime
16
+ prioDeviceTemperature
17
+ prioDevicePowerCycleCount
18
+
19
+ prioDeviceSmartAttributeDecoded
20
+ prioDeviceSmartAttributeNormalized
21
+)
22
+
23
+var deviceChartsTmpl = module.Charts{
24
+ devicePowerOnTimeChartTmpl.Copy(),
25
+ deviceTemperatureChartTmpl.Copy(),
26
+ devicePowerCycleCountChartTmpl.Copy(),
27
+ deviceSmartStatusChartTmpl.Copy(),
28
+ deviceAtaSmartErrorLogCountChartTmpl.Copy(),
29
+}
30
+
31
+var (
32
+ deviceSmartStatusChartTmpl = module.Chart{
33
+ ID: "device_%s_type_%s_smart_status",
34
+ Title: "Device smart status",
35
+ Units: "status",
36
+ Fam: "smart status",
37
+ Ctx: "smartctl.device_smart_status",
38
+ Type: module.Line,
39
+ Priority: prioDeviceSmartStatus,
40
+ Dims: module.Dims{
41
+ {ID: "device_%s_type_%s_smart_status_passed", Name: "passed"},
42
+ {ID: "device_%s_type_%s_smart_status_failed", Name: "failed"},
43
+ },
44
+ }
45
+ deviceAtaSmartErrorLogCountChartTmpl = module.Chart{
46
+ ID: "device_%s_type_%s_ata_smart_error_log_count",
47
+ Title: "Device ATA smart error log count",
48
+ Units: "logs",
49
+ Fam: "smart error log",
50
+ Ctx: "smartctl.device_ata_smart_error_log_count",
51
+ Type: module.Line,
52
+ Priority: prioDeviceAtaSmartErrorLogCount,
53
+ Dims: module.Dims{
54
+ {ID: "device_%s_type_%s_ata_smart_error_log_summary_count", Name: "error_log"},
55
+ },
56
+ }
57
+ devicePowerOnTimeChartTmpl = module.Chart{
58
+ ID: "device_%s_type_%s_power_on_time",
59
+ Title: "Device power on time",
60
+ Units: "seconds",
61
+ Fam: "power on time",
62
+ Ctx: "smartctl.device_power_on_time",
63
+ Type: module.Line,
64
+ Priority: prioDevicePowerOnTime,
65
+ Dims: module.Dims{
66
+ {ID: "device_%s_type_%s_power_on_time", Name: "power_on_time"},
67
+ },
68
+ }
69
+ deviceTemperatureChartTmpl = module.Chart{
70
+ ID: "device_%s_type_%s_temperature",
71
+ Title: "Device temperature",
72
+ Units: "Celsius",
73
+ Fam: "temperature",
74
+ Ctx: "smartctl.device_temperature",
75
+ Type: module.Line,
76
+ Priority: prioDeviceTemperature,
77
+ Dims: module.Dims{
78
+ {ID: "device_%s_type_%s_temperature", Name: "temperature"},
79
+ },
80
+ }
81
+ devicePowerCycleCountChartTmpl = module.Chart{
82
+ ID: "device_%s_type_%s_power_cycle_count",
83
+ Title: "Device power cycles",
84
+ Units: "cycles",
85
+ Fam: "power cycles",
86
+ Ctx: "smartctl.device_power_cycles_count",
87
+ Type: module.Line,
88
+ Priority: prioDevicePowerCycleCount,
89
+ Dims: module.Dims{
90
+ {ID: "device_%s_type_%s_power_cycle_count", Name: "power"},
91
+ },
92
+ }
93
+)
94
+
95
+var (
96
+ deviceSmartAttributeDecodedChartTmpl = module.Chart{
97
+ ID: "device_%s_type_%s_smart_attr_%s",
98
+ Title: "Device smart attribute %s",
99
+ Units: "value",
100
+ Fam: "attr %s",
101
+ Ctx: "smartctl.device_smart_attr_%s",
102
+ Type: module.Line,
103
+ Priority: prioDeviceSmartAttributeDecoded,
104
+ Dims: module.Dims{
105
+ {ID: "device_%s_type_%s_attr_%s_decoded", Name: "%s"},
106
+ },
107
+ }
108
+ deviceSmartAttributeNormalizedChartTmpl = module.Chart{
109
+ ID: "device_%s_type_%s_smart_attr_%s_normalized",
110
+ Title: "Device smart attribute normalized %s",
111
+ Units: "value",
112
+ Fam: "attr %s",
113
+ Ctx: "smartctl.device_smart_attr_%s_normalized",
114
+ Type: module.Line,
115
+ Priority: prioDeviceSmartAttributeNormalized,
116
+ Dims: module.Dims{
117
+ {ID: "device_%s_type_%s_attr_%s_normalized", Name: "%s"},
118
+ },
119
+ }
120
+)
121
+
122
+func (s *Smartctl) addDeviceCharts(dev *smartDevice) {
123
+ charts := module.Charts{}
124
+
125
+ if cs := s.newDeviceCharts(dev); cs != nil && len(*cs) > 0 {
126
+ if err := charts.Add(*cs...); err != nil {
127
+ s.Warning(err)
128
+ }
129
+ }
130
+ if cs := s.newDeviceSmartAttrCharts(dev); cs != nil && len(*cs) > 0 {
131
+ if err := charts.Add(*cs...); err != nil {
132
+ s.Warning(err)
133
+ }
134
+ }
135
+
136
+ if err := s.Charts().Add(charts...); err != nil {
137
+ s.Warning(err)
138
+ }
139
+}
140
+
141
+func (s *Smartctl) removeDeviceCharts(scanDev *scanDevice) {
142
+ px := fmt.Sprintf("device_%s_%s_", scanDev.shortName(), scanDev.typ)
143
+
144
+ for _, chart := range *s.Charts() {
145
+ if strings.HasPrefix(chart.ID, px) {
146
+ chart.MarkRemove()
147
+ chart.MarkNotCreated()
148
+ }
149
+ }
150
+}
151
+
152
+func (s *Smartctl) newDeviceCharts(dev *smartDevice) *module.Charts {
153
+
154
+ charts := deviceChartsTmpl.Copy()
155
+
156
+ if _, ok := dev.powerOnTime(); !ok {
157
+ _ = charts.Remove(devicePowerOnTimeChartTmpl.ID)
158
+ }
159
+ if _, ok := dev.temperature(); !ok {
160
+ _ = charts.Remove(deviceTemperatureChartTmpl.ID)
161
+ }
162
+ if _, ok := dev.powerCycleCount(); !ok {
163
+ _ = charts.Remove(devicePowerOnTimeChartTmpl.ID)
164
+ }
165
+ if _, ok := dev.smartStatusPassed(); !ok {
166
+ _ = charts.Remove(deviceSmartStatusChartTmpl.ID)
167
+ }
168
+ if _, ok := dev.ataSmartErrorLogCount(); !ok {
169
+ _ = charts.Remove(deviceAtaSmartErrorLogCountChartTmpl.ID)
170
+ }
171
+
172
+ for _, chart := range *charts {
173
+ chart.ID = fmt.Sprintf(chart.ID, dev.deviceName(), dev.deviceType())
174
+ chart.Labels = []module.Label{
175
+ {Key: "device_name", Value: dev.deviceName()},
176
+ {Key: "device_type", Value: dev.deviceType()},
177
+ {Key: "model_name", Value: dev.modelName()},
178
+ {Key: "serial_number", Value: dev.serialNumber()},
179
+ }
180
+ for _, dim := range chart.Dims {
181
+ dim.ID = fmt.Sprintf(dim.ID, dev.deviceName(), dev.deviceType())
182
+ }
183
+ }
184
+
185
+ return charts
186
+}
187
+
188
+func (s *Smartctl) newDeviceSmartAttrCharts(dev *smartDevice) *module.Charts {
189
+ attrs, ok := dev.ataSmartAttributeTable()
190
+ if !ok {
191
+ return nil
192
+ }
193
+ charts := module.Charts{}
194
+
195
+ for _, attr := range attrs {
196
+ if !isSmartAttrValid(attr) || strings.HasPrefix(attr.name(), "Unknown") {
197
+ continue
198
+ }
199
+
200
+ cs := module.Charts{
201
+ deviceSmartAttributeDecodedChartTmpl.Copy(),
202
+ deviceSmartAttributeNormalizedChartTmpl.Copy(),
203
+ }
204
+
205
+ name := cleanAttributeName(attr)
206
+
207
+ // FIXME: attribute charts unit
208
+ for _, chart := range cs {
209
+ chart.ID = fmt.Sprintf(chart.ID, dev.deviceName(), dev.deviceType(), name)
210
+ chart.Title = fmt.Sprintf(chart.Title, attr.name())
211
+ chart.Fam = fmt.Sprintf(chart.Fam, name)
212
+ chart.Ctx = fmt.Sprintf(chart.Ctx, name)
213
+ chart.Labels = []module.Label{
214
+ {Key: "device_name", Value: dev.deviceName()},
215
+ {Key: "device_type", Value: dev.deviceType()},
216
+ {Key: "model_name", Value: dev.modelName()},
217
+ {Key: "serial_number", Value: dev.serialNumber()},
218
+ }
219
+ for _, dim := range chart.Dims {
220
+ dim.ID = fmt.Sprintf(dim.ID, dev.deviceName(), dev.deviceType(), name)
221
+ dim.Name = fmt.Sprintf(dim.Name, name)
222
+ }
223
+ }
224
+
225
+ if err := charts.Add(cs...); err != nil {
226
+ s.Warning(err)
227
+ }
228
+ }
229
+
230
+ return &charts
231
+}
232
+
233
+var attrNameReplacer = strings.NewReplacer(" ", "_", "/", "_")
234
+
235
+func cleanAttributeName(attr *smartAttribute) string {
236
+ return strings.ToLower(attrNameReplacer.Replace(attr.name()))
237
+}
src/go/collectors/go.d.plugin/modules/smartctl/collect.go
new
+189
@@ -0,0 +1,189 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package smartctl
4
+
5
+import (
6
+ "fmt"
7
+ "maps"
8
+ "slices"
9
+ "strconv"
10
+ "strings"
11
+ "time"
12
+
13
+ "github.com/tidwall/gjson"
14
+)
15
+
16
+func (s *Smartctl) collect() (map[string]int64, error) {
17
+ now := time.Now()
18
+
19
+ if s.forceScan || s.isTimeToScan(now) {
20
+ devices, err := s.scanDevices()
21
+ if err != nil {
22
+ return nil, err
23
+ }
24
+
25
+ for k, dev := range s.scannedDevices {
26
+ if _, ok := devices[k]; !ok {
27
+ delete(s.scannedDevices, k)
28
+ delete(s.seenDevices, k)
29
+ s.removeDeviceCharts(dev)
30
+ }
31
+ }
32
+
33
+ s.forceDevicePoll = !maps.Equal(s.scannedDevices, devices)
34
+ s.scannedDevices = devices
35
+ s.lastScanTime = now
36
+ s.forceScan = false
37
+ }
38
+
39
+ if s.forceDevicePoll || s.isTimeToPollDevices(now) {
40
+ mx := make(map[string]int64)
41
+
42
+ // TODO: make it concurrent
43
+ for _, d := range s.scannedDevices {
44
+ if err := s.collectScannedDevice(mx, d); err != nil {
45
+ return nil, err
46
+ }
47
+ }
48
+
49
+ s.forceDevicePoll = false
50
+ s.lastDevicePollTime = now
51
+ s.mx = mx
52
+ }
53
+
54
+ return s.mx, nil
55
+}
56
+
57
+func (s *Smartctl) collectScannedDevice(mx map[string]int64, scanDev *scanDevice) error {
58
+ resp, err := s.exec.deviceInfo(scanDev.name, scanDev.typ, s.NoCheckPowerMode)
59
+ if err != nil {
60
+ if resp != nil && isDeviceOpenFailedNoSuchDevice(resp) {
61
+ s.Infof("smartctl reported that device '%s' type '%s' no longer exists", scanDev.name, scanDev.typ)
62
+ s.forceScan = true
63
+ return nil
64
+ }
65
+ return fmt.Errorf("failed to get device info for '%s' type '%s': %v", scanDev.name, scanDev.typ, err)
66
+ }
67
+
68
+ if isDeviceInLowerPowerMode(resp) {
69
+ s.Debugf("device '%s' type '%s' is in a low-power mode, skipping", scanDev.name, scanDev.typ)
70
+ return nil
71
+ }
72
+
73
+ dev := newSmartDevice(resp)
74
+ if !isSmartDeviceValid(dev) {
75
+ return nil
76
+ }
77
+
78
+ if !s.seenDevices[scanDev.key()] {
79
+ s.seenDevices[scanDev.key()] = true
80
+ s.addDeviceCharts(dev)
81
+ }
82
+
83
+ s.collectSmartDevice(mx, dev)
84
+
85
+ return nil
86
+}
87
+
88
+func (s *Smartctl) collectSmartDevice(mx map[string]int64, dev *smartDevice) {
89
+ px := fmt.Sprintf("device_%s_type_%s_", dev.deviceName(), dev.deviceType())
90
+
91
+ if v, ok := dev.powerOnTime(); ok {
92
+ mx[px+"power_on_time"] = v
93
+ }
94
+ if v, ok := dev.temperature(); ok {
95
+ mx[px+"temperature"] = v
96
+ }
97
+ if v, ok := dev.powerCycleCount(); ok {
98
+ mx[px+"power_cycle_count"] = v
99
+ }
100
+ if v, ok := dev.smartStatusPassed(); ok {
101
+ mx[px+"smart_status_passed"] = 0
102
+ mx[px+"smart_status_failed"] = 0
103
+ if v {
104
+ mx[px+"smart_status_passed"] = 1
105
+ } else {
106
+ mx[px+"smart_status_failed"] = 1
107
+ }
108
+ }
109
+ if v, ok := dev.ataSmartErrorLogCount(); ok {
110
+ mx[px+"ata_smart_error_log_summary_count"] = v
111
+ }
112
+
113
+ if attrs, ok := dev.ataSmartAttributeTable(); ok {
114
+ for _, attr := range attrs {
115
+ if !isSmartAttrValid(attr) {
116
+ continue
117
+ }
118
+ n := strings.ToLower(attr.name())
119
+ n = strings.ReplaceAll(n, " ", "_")
120
+ px := fmt.Sprintf("%sattr_%s_", px, n)
121
+
122
+ if v, err := strconv.ParseInt(attr.value(), 10, 64); err == nil {
123
+ mx[px+"normalized"] = v
124
+ }
125
+
126
+ if v, err := strconv.ParseInt(attr.rawValue(), 10, 64); err == nil {
127
+ mx[px+"raw"] = v
128
+ }
129
+
130
+ rs := strings.TrimSpace(attr.rawString())
131
+ if i := strings.IndexByte(rs, ' '); i != -1 {
132
+ rs = rs[:i]
133
+ }
134
+ if v, err := strconv.ParseInt(rs, 10, 64); err == nil {
135
+ mx[px+"decoded"] = v
136
+ }
137
+ }
138
+ }
139
+}
140
+
141
+func (s *Smartctl) isTimeToScan(now time.Time) bool {
142
+ return now.After(s.lastScanTime.Add(s.ScanEvery.Duration()))
143
+}
144
+
145
+func (s *Smartctl) isTimeToPollDevices(now time.Time) bool {
146
+ return now.After(s.lastDevicePollTime.Add(s.PollDevicesEvery.Duration()))
147
+
148
+}
149
+
150
+func isSmartDeviceValid(d *smartDevice) bool {
151
+ return d.deviceName() != "" && d.deviceType() != ""
152
+}
153
+
154
+func isSmartAttrValid(a *smartAttribute) bool {
155
+ return a.id() != "" && a.name() != ""
156
+}
157
+
158
+func isDeviceInLowerPowerMode(r *gjson.Result) bool {
159
+ if !isExitStatusHasBit(r, 1) {
160
+ return false
161
+ }
162
+
163
+ messages := r.Get("smartctl.messages").Array()
164
+
165
+ return slices.ContainsFunc(messages, func(msg gjson.Result) bool {
166
+ text := msg.Get("string").String()
167
+ return strings.HasPrefix(text, "Device is in") && strings.Contains(text, "mode")
168
+ })
169
+}
170
+
171
+func isDeviceOpenFailedNoSuchDevice(r *gjson.Result) bool {
172
+ if !isExitStatusHasBit(r, 1) {
173
+ return false
174
+ }
175
+
176
+ messages := r.Get("smartctl.messages").Array()
177
+
178
+ return slices.ContainsFunc(messages, func(msg gjson.Result) bool {
179
+ text := msg.Get("string").String()
180
+ return strings.HasSuffix(text, "No such device")
181
+ })
182
+}
183
+
184
+func isExitStatusHasBit(r *gjson.Result, bit int) bool {
185
+ // https://manpages.debian.org/bullseye/smartmontools/smartctl.8.en.html#EXIT_STATUS
186
+ status := int(r.Get("smartctl.exit_status").Int())
187
+ mask := 1 << bit
188
+ return (status & mask) != 0
189
+}
src/go/collectors/go.d.plugin/modules/smartctl/config_schema.json
new
+67
@@ -0,0 +1,67 @@
1
+{
2
+ "jsonSchema": {
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "Smartctl collector configuration.",
5
+ "type": "object",
6
+ "properties": {
7
+ "update_every": {
8
+ "title": "Update every",
9
+ "description": "Interval for updating Netdata charts, measured in seconds. Collector might use cached data if less than **Devices poll interval**.",
10
+ "type": "integer",
11
+ "minimum": 1,
12
+ "default": 10
13
+ },
14
+ "timeout": {
15
+ "title": "Timeout",
16
+ "description": "Timeout for executing the `smartctl` binary, specified in seconds.",
17
+ "type": "number",
18
+ "minimum": 0.5,
19
+ "default": 5
20
+ },
21
+ "scan_every": {
22
+ "title": "Scan interval",
23
+ "description": "Interval for discovering new devices using `smartctl --scan`, measured in seconds.",
24
+ "type": "number",
25
+ "minimum": 1,
26
+ "default": 900
27
+ },
28
+ "poll_devices_every": {
29
+ "title": "Devices poll interval",
30
+ "description": "Interval for gathering data for every device, measured in seconds. Data is cached for this interval.",
31
+ "type": "number",
32
+ "minimum": 1,
33
+ "default": 300
34
+ },
35
+ "no_check_power_mode": {
36
+ "title": "No check power mode",
37
+ "description": "ATA only. Skip data collection when the device is in a low-power mode. Prevents unnecessary disk spin-up.",
38
+ "type": "string",
39
+ "enum": [
40
+ "standby",
41
+ "never",
42
+ "sleep",
43
+ "idle"
44
+ ],
45
+ "default": "standby"
46
+ }
47
+ },
48
+ "additionalProperties": false,
49
+ "patternProperties": {
50
+ "^name$": {}
51
+ }
52
+ },
53
+ "uiSchema": {
54
+ "uiOptions": {
55
+ "fullPage": true
56
+ },
57
+ "timeout": {
58
+ "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
59
+ },
60
+ "no_check_power_mode": {
61
+ "ui:widget": "radio",
62
+ "ui:options": {
63
+ "inline": true
64
+ }
65
+ }
66
+ }
67
+}
src/go/collectors/go.d.plugin/modules/smartctl/exec.go
new
+82
@@ -0,0 +1,82 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package smartctl
4
+
5
+import (
6
+ "context"
7
+ "errors"
8
+ "fmt"
9
+ "os/exec"
10
+ "time"
11
+
12
+ "github.com/netdata/netdata/go/go.d.plugin/logger"
13
+
14
+ "github.com/tidwall/gjson"
15
+)
16
+
17
+func newSmartctlCliExec(ndsudoPath string, timeout time.Duration, log *logger.Logger) *smartctlCliExec {
18
+ return &smartctlCliExec{
19
+ Logger: log,
20
+ ndsudoPath: ndsudoPath,
21
+ timeout: timeout,
22
+ }
23
+}
24
+
25
+type smartctlCliExec struct {
26
+ *logger.Logger
27
+
28
+ ndsudoPath string
29
+ timeout time.Duration
30
+}
31
+
32
+func (e *smartctlCliExec) scan() (*gjson.Result, error) {
33
+ return e.execute("smartctl-json-scan")
34
+}
35
+
36
+func (e *smartctlCliExec) deviceInfo(deviceName, deviceType, powerMode string) (*gjson.Result, error) {
37
+ return e.execute("smartctl-json-device-info",
38
+ "--deviceName", deviceName,
39
+ "--deviceType", deviceType,
40
+ "--powerMode", powerMode,
41
+ )
42
+}
43
+
44
+func (e *smartctlCliExec) execute(args ...string) (*gjson.Result, error) {
45
+ ctx, cancel := context.WithTimeout(context.Background(), e.timeout)
46
+ defer cancel()
47
+
48
+ cmd := exec.CommandContext(ctx, e.ndsudoPath, args...)
49
+ e.Debugf("executing '%s'", cmd)
50
+
51
+ bs, err := cmd.Output()
52
+ if err != nil {
53
+ if errors.Is(err, context.DeadlineExceeded) || isExecExitCode(err, 1) || len(bs) == 0 {
54
+ return nil, fmt.Errorf("'%s' execution failed: %v", cmd, err)
55
+ }
56
+ }
57
+ if len(bs) == 0 {
58
+ return nil, fmt.Errorf("'%s' returned no output", cmd)
59
+ }
60
+
61
+ if !gjson.ValidBytes(bs) {
62
+ return nil, fmt.Errorf("'%s' returned invalid JSON output", cmd)
63
+ }
64
+
65
+ res := gjson.ParseBytes(bs)
66
+ if !res.Get("smartctl.exit_status").Exists() {
67
+ return nil, fmt.Errorf("'%s' returned unexpected data", cmd)
68
+ }
69
+
70
+ for _, msg := range res.Get("smartctl.messages").Array() {
71
+ if msg.Get("severity").String() == "error" {
72
+ return &res, fmt.Errorf("'%s' reported an error: %s", cmd, msg.Get("string"))
73
+ }
74
+ }
75
+
76
+ return &res, nil
77
+}
78
+
79
+func isExecExitCode(err error, exitCode int) bool {
80
+ var v *exec.ExitError
81
+ return errors.As(err, &v) && v.ExitCode() == exitCode
82
+}
src/go/collectors/go.d.plugin/modules/smartctl/init.go
new
+32
@@ -0,0 +1,32 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package smartctl
4
+
5
+import (
6
+ "fmt"
7
+ "os"
8
+ "path/filepath"
9
+
10
+ "github.com/netdata/netdata/go/go.d.plugin/agent/executable"
11
+)
12
+
13
+func (s *Smartctl) validateConfig() error {
14
+ switch s.NoCheckPowerMode {
15
+ case "never", "sleep", "standby", "idle":
16
+ default:
17
+ return fmt.Errorf("invalid power mode '%s'", s.NoCheckPowerMode)
18
+ }
19
+ return nil
20
+}
21
+
22
+func (s *Smartctl) initSmartctlCli() (smartctlCli, error) {
23
+ ndsudoPath := filepath.Join(executable.Directory, "ndsudo")
24
+ if _, err := os.Stat(ndsudoPath); err != nil {
25
+ return nil, fmt.Errorf("ndsudo executable not found: %v", err)
26
+
27
+ }
28
+
29
+ smartctlExec := newSmartctlCliExec(ndsudoPath, s.Timeout.Duration(), s.Logger)
30
+
31
+ return smartctlExec, nil
32
+}
src/go/collectors/go.d.plugin/modules/smartctl/metadata.yaml
new
+154
@@ -0,0 +1,154 @@
1
+plugin_name: go.d.plugin
2
+modules:
3
+ - meta:
4
+ id: collector-go.d.plugin-smartctl
5
+ plugin_name: go.d.plugin
6
+ module_name: smartctl
7
+ monitored_instance:
8
+ name: S.M.A.R.T.
9
+ link: "https://linux.die.net/man/8/smartd"
10
+ icon_filename: "smart.png"
11
+ categories:
12
+ - data-collection.hardware-devices-and-sensors
13
+ keywords:
14
+ - smart
15
+ - S.M.A.R.T.
16
+ - SCSI devices
17
+ - ATA devices
18
+ related_resources:
19
+ integrations:
20
+ list: []
21
+ info_provided_to_referring_integrations:
22
+ description: ""
23
+ most_popular: false
24
+ overview:
25
+ data_collection:
26
+ metrics_description: |
27
+ This collector monitors the health status of storage devices by analyzing S.M.A.R.T. (Self-Monitoring, Analysis, and Reporting Technology) counters.
28
+ It relies on the [`smartctl`](https://linux.die.net/man/8/smartctl) CLI tool but avoids directly executing the binary.
29
+ Instead, it utilizes `ndsudo`, a Netdata helper specifically designed to run privileged commands securely within the Netdata environment.
30
+ This approach eliminates the need to use `sudo`, improving security and potentially simplifying permission management.
31
+
32
+ Executed commands:
33
+ - `smartctl --json --scan`
34
+ - `smartctl --json --all {deviceName} --device {deviceType} --nocheck {powerMode}`
35
+ method_description: ""
36
+ supported_platforms:
37
+ include: []
38
+ exclude: []
39
+ multi_instance: false
40
+ additional_permissions:
41
+ description: ""
42
+ default_behavior:
43
+ auto_detection:
44
+ description: ""
45
+ limits:
46
+ description: ""
47
+ performance_impact:
48
+ description: ""
49
+ setup:
50
+ prerequisites:
51
+ list: []
52
+ configuration:
53
+ file:
54
+ name: go.d/smartctl.conf
55
+ options:
56
+ description: |
57
+ The following options can be defined globally: update_every.
58
+ folding:
59
+ title: Config options
60
+ enabled: true
61
+ list:
62
+ - name: update_every
63
+ description: interval for updating Netdata charts, measured in seconds. Collector might use cached data if less than **Devices poll interval**.
64
+ default_value: 10
65
+ required: false
66
+ - name: timeout
67
+ description: smartctl binary execution timeout.
68
+ default_value: 5
69
+ required: false
70
+ - name: scan_every
71
+ description: interval for discovering new devices using `smartctl --scan`, measured in seconds.
72
+ default_value: 900
73
+ required: false
74
+ - name: poll_devices_every
75
+ description: interval for gathering data for every device, measured in seconds. Data is cached for this interval.
76
+ default_value: 300
77
+ required: false
78
+ examples:
79
+ folding:
80
+ title: Config
81
+ enabled: true
82
+ list:
83
+ - name: Custom devices poll interval
84
+ description: Allows you to override the default devices poll interval (data collection).
85
+ config: |
86
+ jobs:
87
+ - name: smartctl
88
+ devices_poll_interval: 60 # Collect S.M.A.R.T statistics every 60 seconds
89
+ troubleshooting:
90
+ problems:
91
+ list: []
92
+ alerts: []
93
+ metrics:
94
+ folding:
95
+ title: Metrics
96
+ enabled: false
97
+ description: ""
98
+ availability: []
99
+ scopes:
100
+ - name: controller
101
+ description: These metrics refer to the Storage Device.
102
+ labels:
103
+ - name: device_name
104
+ description: Device name
105
+ - name: device_type
106
+ description: Device type
107
+ - name: model_name
108
+ description: Model name
109
+ - name: serial_number
110
+ description: Serial number
111
+ metrics:
112
+ - name: smartctl.device_smart_status
113
+ description: Device smart status
114
+ unit: status
115
+ chart_type: line
116
+ dimensions:
117
+ - name: passed
118
+ - name: failed
119
+ - name: smartctl.device_ata_smart_error_log_count
120
+ description: Device ATA smart error log count
121
+ unit: logs
122
+ chart_type: line
123
+ dimensions:
124
+ - name: error_log
125
+ - name: smartctl.device_power_on_time
126
+ description: Device power on time
127
+ unit: seconds
128
+ chart_type: line
129
+ dimensions:
130
+ - name: power_on_time
131
+ - name: smartctl.device_temperature
132
+ description: Device temperature
133
+ unit: Celsius
134
+ chart_type: line
135
+ dimensions:
136
+ - name: temperature
137
+ - name: smartctl.device_power_cycles_count
138
+ description: Device power cycles
139
+ unit: cycles
140
+ chart_type: line
141
+ dimensions:
142
+ - name: power
143
+ - name: smartctl.device_smart_attr_{attribute_name}
144
+ description: Device smart attribute {attribute_name}
145
+ unit: '{attribute_unit}'
146
+ chart_type: line
147
+ dimensions:
148
+ - name: '{attribute_name}'
149
+ - name: smartctl.device_smart_attr_{attribute_name}_normalized
150
+ description: Device smart attribute {attribute_name} normalized
151
+ unit: value
152
+ chart_type: line
153
+ dimensions:
154
+ - name: '{attribute_name}'
src/go/collectors/go.d.plugin/modules/smartctl/scan.go
new
+68
@@ -0,0 +1,68 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package smartctl
4
+
5
+import (
6
+ "errors"
7
+ "fmt"
8
+ "strings"
9
+)
10
+
11
+type scanDevice struct {
12
+ name string
13
+ infoName string
14
+ typ string
15
+}
16
+
17
+func (s *scanDevice) key() string {
18
+ return fmt.Sprintf("%s|%s", s.name, s.typ)
19
+}
20
+
21
+func (s *scanDevice) shortName() string {
22
+ return strings.TrimPrefix(s.name, "/dev/")
23
+}
24
+
25
+func (s *Smartctl) scanDevices() (map[string]*scanDevice, error) {
26
+ resp, err := s.exec.scan()
27
+ if err != nil {
28
+ return nil, err
29
+ }
30
+
31
+ devices := make(map[string]*scanDevice)
32
+
33
+ for _, d := range resp.Get("devices").Array() {
34
+ dev := &scanDevice{
35
+ name: d.Get("name").String(),
36
+ infoName: d.Get("info_name").String(),
37
+ typ: d.Get("type").String(),
38
+ }
39
+
40
+ if dev.name == "" || dev.typ == "" {
41
+ continue
42
+ }
43
+
44
+ if dev.typ == "scsi" {
45
+ // `smartctl --scan` attempts to guess the device type based on the path, but this can be unreliable.
46
+ // Accurate device type information is crucial because we use the `--device` option to gather data.
47
+ // Using the wrong type can lead to issues.
48
+ // For example, using 'scsi' for 'sat' devices prevents `smartctl` from issuing the necessary ATA commands.
49
+ resp, _ := s.exec.deviceInfo(dev.name, dev.typ, s.NoCheckPowerMode)
50
+ if resp != nil && isExitStatusHasBit(resp, 2) {
51
+ newType := "sat"
52
+ s.Debugf("changing device '%s' type '%s' -> '%s'", dev.name, dev.typ, newType)
53
+ dev.typ = newType
54
+ }
55
+ s.Debugf("smartctl scan found device '%s' type '%s' info_name '%s'", dev.name, dev.typ, dev.infoName)
56
+ }
57
+
58
+ devices[dev.key()] = dev
59
+ }
60
+
61
+ if len(devices) == 0 {
62
+ return nil, errors.New("no devices found on scan")
63
+ }
64
+
65
+ s.Infof("smartctl scan found %d devices", len(devices))
66
+
67
+ return devices, nil
68
+}
src/go/collectors/go.d.plugin/modules/smartctl/smart_device.go
new
+123
@@ -0,0 +1,123 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package smartctl
4
+
5
+import (
6
+ "strings"
7
+
8
+ "github.com/tidwall/gjson"
9
+)
10
+
11
+func newSmartDevice(deviceData *gjson.Result) *smartDevice {
12
+ return &smartDevice{
13
+ data: deviceData,
14
+ }
15
+}
16
+
17
+type smartDevice struct {
18
+ data *gjson.Result
19
+}
20
+
21
+func (d *smartDevice) deviceName() string {
22
+ v := d.data.Get("device.name").String()
23
+ return strings.TrimPrefix(v, "/dev/")
24
+}
25
+
26
+func (d *smartDevice) deviceType() string {
27
+ return d.data.Get("device.type").String()
28
+}
29
+
30
+func (d *smartDevice) deviceProtocol() string {
31
+ return d.data.Get("device.protocol").String()
32
+}
33
+
34
+func (d *smartDevice) serialNumber() string {
35
+ return d.data.Get("serial_number").String()
36
+}
37
+
38
+func (d *smartDevice) modelName() string {
39
+ for _, s := range []string{"model_name", "scsi_model_name"} {
40
+ if v := d.data.Get(s); v.Exists() {
41
+ return v.String()
42
+ }
43
+ }
44
+ return "unknown"
45
+}
46
+
47
+func (d *smartDevice) powerOnTime() (int64, bool) {
48
+ h := d.data.Get("power_on_time.hours")
49
+ if !h.Exists() {
50
+ return 0, false
51
+ }
52
+ m := d.data.Get("power_on_time.minutes")
53
+ return h.Int()*60*60 + m.Int()*60, true
54
+}
55
+
56
+func (d *smartDevice) temperature() (int64, bool) {
57
+ v := d.data.Get("temperature.current")
58
+ return v.Int(), v.Exists()
59
+}
60
+
61
+func (d *smartDevice) powerCycleCount() (int64, bool) {
62
+ v := d.data.Get("power_cycle_count")
63
+ if v.Exists() {
64
+ return v.Int(), true
65
+ }
66
+ v = d.data.Get("scsi_start_stop_cycle_counter.accumulated_start_stop_cycles")
67
+ return v.Int(), v.Exists()
68
+}
69
+
70
+func (d *smartDevice) smartStatusPassed() (bool, bool) {
71
+ v := d.data.Get("smart_status.passed")
72
+ return v.Bool(), v.Exists()
73
+}
74
+
75
+func (d *smartDevice) ataSmartErrorLogCount() (int64, bool) {
76
+ v := d.data.Get("ata_smart_error_log.summary.count")
77
+ return v.Int(), v.Exists()
78
+}
79
+
80
+func (d *smartDevice) ataSmartAttributeTable() ([]*smartAttribute, bool) {
81
+ table := d.data.Get("ata_smart_attributes.table")
82
+ if !table.Exists() || !table.IsArray() {
83
+ return nil, false
84
+ }
85
+
86
+ var attrs []*smartAttribute
87
+
88
+ for _, data := range table.Array() {
89
+ attrs = append(attrs, newSmartDeviceAttribute(data))
90
+ }
91
+
92
+ return attrs, true
93
+}
94
+
95
+func newSmartDeviceAttribute(attrData gjson.Result) *smartAttribute {
96
+ return &smartAttribute{
97
+ data: attrData,
98
+ }
99
+}
100
+
101
+type smartAttribute struct {
102
+ data gjson.Result
103
+}
104
+
105
+func (a *smartAttribute) id() string {
106
+ return a.data.Get("id").String()
107
+}
108
+
109
+func (a *smartAttribute) name() string {
110
+ return a.data.Get("name").String()
111
+}
112
+
113
+func (a *smartAttribute) value() string {
114
+ return a.data.Get("value").String()
115
+}
116
+
117
+func (a *smartAttribute) rawValue() string {
118
+ return a.data.Get("raw.value").String()
119
+}
120
+
121
+func (a *smartAttribute) rawString() string {
122
+ return a.data.Get("raw.string").String()
123
+}
src/go/collectors/go.d.plugin/modules/smartctl/smartctl.go
new
+126
@@ -0,0 +1,126 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package smartctl
4
+
5
+import (
6
+ _ "embed"
7
+ "errors"
8
+ "time"
9
+
10
+ "github.com/netdata/netdata/go/go.d.plugin/agent/module"
11
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/web"
12
+
13
+ "github.com/tidwall/gjson"
14
+)
15
+
16
+//go:embed "config_schema.json"
17
+var configSchema string
18
+
19
+func init() {
20
+ module.Register("smartctl", module.Creator{
21
+ JobConfigSchema: configSchema,
22
+ Defaults: module.Defaults{
23
+ UpdateEvery: 10,
24
+ },
25
+ Create: func() module.Module { return New() },
26
+ })
27
+}
28
+
29
+func New() *Smartctl {
30
+ return &Smartctl{
31
+ Config: Config{
32
+ Timeout: web.Duration(time.Second * 5),
33
+ ScanEvery: web.Duration(time.Minute * 15),
34
+ PollDevicesEvery: web.Duration(time.Minute * 5),
35
+ NoCheckPowerMode: "standby",
36
+ },
37
+ charts: &module.Charts{},
38
+ seenDevices: make(map[string]bool),
39
+ }
40
+}
41
+
42
+type Config struct {
43
+ UpdateEvery int `yaml:"update_every" json:"update_every"`
44
+ Timeout web.Duration `yaml:"timeout" json:"timeout"`
45
+ ScanEvery web.Duration `yaml:"scan_every" json:"scan_every"`
46
+ PollDevicesEvery web.Duration `yaml:"poll_devices_every" json:"poll_devices_every"`
47
+ NoCheckPowerMode string `yaml:"no_check_power_mode" json:"no_check_power_mode"`
48
+}
49
+
50
+type (
51
+ Smartctl struct {
52
+ module.Base
53
+ Config `yaml:",inline" data:""`
54
+
55
+ charts *module.Charts
56
+
57
+ exec smartctlCli
58
+
59
+ lastScanTime time.Time
60
+ forceScan bool
61
+ scannedDevices map[string]*scanDevice
62
+
63
+ lastDevicePollTime time.Time
64
+ forceDevicePoll bool
65
+
66
+ seenDevices map[string]bool
67
+ mx map[string]int64
68
+ }
69
+ smartctlCli interface {
70
+ scan() (*gjson.Result, error)
71
+ deviceInfo(deviceName, deviceType, powerMode string) (*gjson.Result, error)
72
+ }
73
+)
74
+
75
+func (s *Smartctl) Configuration() any {
76
+ return s.Config
77
+}
78
+
79
+func (s *Smartctl) Init() error {
80
+ if err := s.validateConfig(); err != nil {
81
+ s.Errorf("config validation error: %s", err)
82
+ return err
83
+ }
84
+
85
+ smartctlExec, err := s.initSmartctlCli()
86
+ if err != nil {
87
+ s.Errorf("smartctl exec initialization: %v", err)
88
+ return err
89
+ }
90
+ s.exec = smartctlExec
91
+
92
+ return nil
93
+}
94
+
95
+func (s *Smartctl) Check() error {
96
+ mx, err := s.collect()
97
+ if err != nil {
98
+ s.Error(err)
99
+ return err
100
+ }
101
+
102
+ if len(mx) == 0 {
103
+ return errors.New("no metrics collected")
104
+ }
105
+
106
+ return nil
107
+}
108
+
109
+func (s *Smartctl) Charts() *module.Charts {
110
+ return s.charts
111
+}
112
+
113
+func (s *Smartctl) Collect() map[string]int64 {
114
+ mx, err := s.collect()
115
+ if err != nil {
116
+ s.Error(err)
117
+ }
118
+
119
+ if len(mx) == 0 {
120
+ return nil
121
+ }
122
+
123
+ return mx
124
+}
125
+
126
+func (s *Smartctl) Cleanup() {}
src/go/collectors/go.d.plugin/modules/smartctl/smartctl_test.go
new
+436
@@ -0,0 +1,436 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package smartctl
4
+
5
+import (
6
+ "fmt"
7
+ "os"
8
+ "testing"
9
+ "time"
10
+
11
+ "github.com/netdata/netdata/go/go.d.plugin/agent/module"
12
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/web"
13
+
14
+ "github.com/stretchr/testify/assert"
15
+ "github.com/stretchr/testify/require"
16
+ "github.com/tidwall/gjson"
17
+)
18
+
19
+var (
20
+ dataConfigJSON, _ = os.ReadFile("testdata/config.json")
21
+ dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
22
+
23
+ dataTypeSataScan, _ = os.ReadFile("testdata/type-sat/scan.json")
24
+ dataTypeSataDeviceHDDSda, _ = os.ReadFile("testdata/type-sat/device-hdd-sda.json")
25
+ dataTypeSataDeviceSSDSdc, _ = os.ReadFile("testdata/type-sat/device-ssd-sdc.json")
26
+
27
+ dataTypeNvmeScan, _ = os.ReadFile("testdata/type-nvme/scan.json")
28
+ dataTypeNvmeDeviceNvme0, _ = os.ReadFile("testdata/type-nvme/device-nvme0.json")
29
+)
30
+
31
+func Test_testDataIsValid(t *testing.T) {
32
+ for name, data := range map[string][]byte{
33
+ "dataConfigJSON": dataConfigJSON,
34
+ "dataConfigYAML": dataConfigYAML,
35
+
36
+ "dataTypeSataScan": dataTypeSataScan,
37
+ "dataTypeSataDeviceHDDSda": dataTypeSataDeviceHDDSda,
38
+ "dataTypeSataDeviceSSDSdc": dataTypeSataDeviceSSDSdc,
39
+
40
+ "dataTypeNvmeScan": dataTypeNvmeScan,
41
+ "dataTypeNvmeDeviceNvme0": dataTypeNvmeDeviceNvme0,
42
+ } {
43
+ require.NotNil(t, data, name)
44
+ }
45
+}
46
+
47
+func TestSmartctl_ConfigurationSerialize(t *testing.T) {
48
+ module.TestConfigurationSerialize(t, &Smartctl{}, dataConfigJSON, dataConfigYAML)
49
+}
50
+
51
+func TestSmartctl_Init(t *testing.T) {
52
+ tests := map[string]struct {
53
+ config Config
54
+ wantFail bool
55
+ }{
56
+ "fails if invalid power mode": {
57
+ wantFail: true,
58
+ config: func() Config {
59
+ cfg := New().Config
60
+ cfg.NoCheckPowerMode = "invalid"
61
+ return cfg
62
+ }(),
63
+ },
64
+ "fails if 'ndsudo' not found": {
65
+ wantFail: true,
66
+ config: New().Config,
67
+ },
68
+ }
69
+
70
+ for name, test := range tests {
71
+ t.Run(name, func(t *testing.T) {
72
+ smart := New()
73
+
74
+ if test.wantFail {
75
+ assert.Error(t, smart.Init())
76
+ } else {
77
+ assert.NoError(t, smart.Init())
78
+ }
79
+ })
80
+ }
81
+}
82
+
83
+func TestSmartctl_Cleanup(t *testing.T) {
84
+ tests := map[string]struct {
85
+ prepare func() *Smartctl
86
+ }{
87
+ "not initialized exec": {
88
+ prepare: func() *Smartctl {
89
+ return New()
90
+ },
91
+ },
92
+ "after check": {
93
+ prepare: func() *Smartctl {
94
+ smart := New()
95
+ smart.exec = prepareMockOkTypeSata()
96
+ _ = smart.Check()
97
+ return smart
98
+ },
99
+ },
100
+ "after collect": {
101
+ prepare: func() *Smartctl {
102
+ smart := New()
103
+ smart.exec = prepareMockOkTypeSata()
104
+ _ = smart.Collect()
105
+ return smart
106
+ },
107
+ },
108
+ }
109
+
110
+ for name, test := range tests {
111
+ t.Run(name, func(t *testing.T) {
112
+ smart := test.prepare()
113
+
114
+ assert.NotPanics(t, smart.Cleanup)
115
+ })
116
+ }
117
+}
118
+
119
+func TestSmartctl_Check(t *testing.T) {
120
+ tests := map[string]struct {
121
+ prepareMock func() *mockSmartctlCliExec
122
+ wantFail bool
123
+ }{
124
+ "success type sata devices": {
125
+ wantFail: false,
126
+ prepareMock: prepareMockOkTypeSata,
127
+ },
128
+ "success type nvme devices": {
129
+ wantFail: false,
130
+ prepareMock: prepareMockOkTypeNvme,
131
+ },
132
+ "error on scan": {
133
+ wantFail: true,
134
+ prepareMock: prepareMockErrOnScan,
135
+ },
136
+ "unexpected response on scan": {
137
+ wantFail: true,
138
+ prepareMock: prepareMockUnexpectedResponse,
139
+ },
140
+ "empty response on scan": {
141
+ wantFail: true,
142
+ prepareMock: prepareMockEmptyResponse,
143
+ },
144
+ }
145
+
146
+ for name, test := range tests {
147
+ t.Run(name, func(t *testing.T) {
148
+ smart := New()
149
+ mock := test.prepareMock()
150
+ smart.exec = mock
151
+
152
+ if test.wantFail {
153
+ assert.Error(t, smart.Check())
154
+ } else {
155
+ assert.NoError(t, smart.Check())
156
+ }
157
+ })
158
+ }
159
+}
160
+
161
+func TestSmartctl_Collect(t *testing.T) {
162
+ tests := map[string]struct {
163
+ prepareMock func() *mockSmartctlCliExec
164
+ wantMetrics map[string]int64
165
+ wantCharts int
166
+ }{
167
+ "success type sata devices": {
168
+ prepareMock: prepareMockOkTypeSata,
169
+ wantCharts: 68,
170
+ wantMetrics: map[string]int64{
171
+ "device_sda_type_sat_ata_smart_error_log_summary_count": 0,
172
+ "device_sda_type_sat_attr_current_pending_sector_decoded": 0,
173
+ "device_sda_type_sat_attr_current_pending_sector_normalized": 100,
174
+ "device_sda_type_sat_attr_current_pending_sector_raw": 0,
175
+ "device_sda_type_sat_attr_load_cycle_count_decoded": 360,
176
+ "device_sda_type_sat_attr_load_cycle_count_normalized": 100,
177
+ "device_sda_type_sat_attr_load_cycle_count_raw": 360,
178
+ "device_sda_type_sat_attr_offline_uncorrectable_decoded": 0,
179
+ "device_sda_type_sat_attr_offline_uncorrectable_normalized": 100,
180
+ "device_sda_type_sat_attr_offline_uncorrectable_raw": 0,
181
+ "device_sda_type_sat_attr_power-off_retract_count_decoded": 360,
182
+ "device_sda_type_sat_attr_power-off_retract_count_normalized": 100,
183
+ "device_sda_type_sat_attr_power-off_retract_count_raw": 360,
184
+ "device_sda_type_sat_attr_power_cycle_count_decoded": 12,
185
+ "device_sda_type_sat_attr_power_cycle_count_normalized": 100,
186
+ "device_sda_type_sat_attr_power_cycle_count_raw": 12,
187
+ "device_sda_type_sat_attr_power_on_hours_decoded": 8244,
188
+ "device_sda_type_sat_attr_power_on_hours_normalized": 99,
189
+ "device_sda_type_sat_attr_power_on_hours_raw": 8244,
190
+ "device_sda_type_sat_attr_raw_read_error_rate_decoded": 0,
191
+ "device_sda_type_sat_attr_raw_read_error_rate_normalized": 100,
192
+ "device_sda_type_sat_attr_raw_read_error_rate_raw": 0,
193
+ "device_sda_type_sat_attr_reallocated_event_count_decoded": 0,
194
+ "device_sda_type_sat_attr_reallocated_event_count_normalized": 100,
195
+ "device_sda_type_sat_attr_reallocated_event_count_raw": 0,
196
+ "device_sda_type_sat_attr_reallocated_sector_ct_decoded": 0,
197
+ "device_sda_type_sat_attr_reallocated_sector_ct_normalized": 100,
198
+ "device_sda_type_sat_attr_reallocated_sector_ct_raw": 0,
199
+ "device_sda_type_sat_attr_seek_error_rate_decoded": 0,
200
+ "device_sda_type_sat_attr_seek_error_rate_normalized": 100,
201
+ "device_sda_type_sat_attr_seek_error_rate_raw": 0,
202
+ "device_sda_type_sat_attr_seek_time_performance_decoded": 15,
203
+ "device_sda_type_sat_attr_seek_time_performance_normalized": 140,
204
+ "device_sda_type_sat_attr_seek_time_performance_raw": 15,
205
+ "device_sda_type_sat_attr_spin_retry_count_decoded": 0,
206
+ "device_sda_type_sat_attr_spin_retry_count_normalized": 100,
207
+ "device_sda_type_sat_attr_spin_retry_count_raw": 0,
208
+ "device_sda_type_sat_attr_spin_up_time_decoded": 281,
209
+ "device_sda_type_sat_attr_spin_up_time_normalized": 86,
210
+ "device_sda_type_sat_attr_spin_up_time_raw": 25788088601,
211
+ "device_sda_type_sat_attr_start_stop_count_decoded": 12,
212
+ "device_sda_type_sat_attr_start_stop_count_normalized": 100,
213
+ "device_sda_type_sat_attr_start_stop_count_raw": 12,
214
+ "device_sda_type_sat_attr_temperature_celsius_decoded": 49,
215
+ "device_sda_type_sat_attr_temperature_celsius_normalized": 43,
216
+ "device_sda_type_sat_attr_temperature_celsius_raw": 240519741489,
217
+ "device_sda_type_sat_attr_throughput_performance_decoded": 48,
218
+ "device_sda_type_sat_attr_throughput_performance_normalized": 148,
219
+ "device_sda_type_sat_attr_throughput_performance_raw": 48,
220
+ "device_sda_type_sat_attr_udma_crc_error_count_decoded": 0,
221
+ "device_sda_type_sat_attr_udma_crc_error_count_normalized": 100,
222
+ "device_sda_type_sat_attr_udma_crc_error_count_raw": 0,
223
+ "device_sda_type_sat_attr_unknown_attribute_decoded": 100,
224
+ "device_sda_type_sat_attr_unknown_attribute_normalized": 100,
225
+ "device_sda_type_sat_attr_unknown_attribute_raw": 100,
226
+ "device_sda_type_sat_power_cycle_count": 12,
227
+ "device_sda_type_sat_power_on_time": 29678400,
228
+ "device_sda_type_sat_smart_status_failed": 0,
229
+ "device_sda_type_sat_smart_status_passed": 1,
230
+ "device_sda_type_sat_temperature": 49,
231
+ "device_sdc_type_sat_ata_smart_error_log_summary_count": 0,
232
+ "device_sdc_type_sat_attr_available_reservd_space_decoded": 100,
233
+ "device_sdc_type_sat_attr_available_reservd_space_normalized": 100,
234
+ "device_sdc_type_sat_attr_available_reservd_space_raw": 100,
235
+ "device_sdc_type_sat_attr_command_timeout_decoded": 0,
236
+ "device_sdc_type_sat_attr_command_timeout_normalized": 100,
237
+ "device_sdc_type_sat_attr_command_timeout_raw": 0,
238
+ "device_sdc_type_sat_attr_end-to-end_error_decoded": 0,
239
+ "device_sdc_type_sat_attr_end-to-end_error_normalized": 100,
240
+ "device_sdc_type_sat_attr_end-to-end_error_raw": 0,
241
+ "device_sdc_type_sat_attr_media_wearout_indicator_decoded": 65406,
242
+ "device_sdc_type_sat_attr_media_wearout_indicator_normalized": 100,
243
+ "device_sdc_type_sat_attr_media_wearout_indicator_raw": 65406,
244
+ "device_sdc_type_sat_attr_power_cycle_count_decoded": 13,
245
+ "device_sdc_type_sat_attr_power_cycle_count_normalized": 100,
246
+ "device_sdc_type_sat_attr_power_cycle_count_raw": 13,
247
+ "device_sdc_type_sat_attr_power_on_hours_decoded": 8244,
248
+ "device_sdc_type_sat_attr_power_on_hours_normalized": 100,
249
+ "device_sdc_type_sat_attr_power_on_hours_raw": 8244,
250
+ "device_sdc_type_sat_attr_reallocated_sector_ct_decoded": 0,
251
+ "device_sdc_type_sat_attr_reallocated_sector_ct_normalized": 100,
252
+ "device_sdc_type_sat_attr_reallocated_sector_ct_raw": 0,
253
+ "device_sdc_type_sat_attr_reported_uncorrect_decoded": 0,
254
+ "device_sdc_type_sat_attr_reported_uncorrect_normalized": 100,
255
+ "device_sdc_type_sat_attr_reported_uncorrect_raw": 0,
256
+ "device_sdc_type_sat_attr_temperature_celsius_decoded": 27,
257
+ "device_sdc_type_sat_attr_temperature_celsius_normalized": 73,
258
+ "device_sdc_type_sat_attr_temperature_celsius_raw": 184684970011,
259
+ "device_sdc_type_sat_attr_total_lbas_read_decoded": 76778,
260
+ "device_sdc_type_sat_attr_total_lbas_read_normalized": 253,
261
+ "device_sdc_type_sat_attr_total_lbas_read_raw": 76778,
262
+ "device_sdc_type_sat_attr_total_lbas_written_decoded": 173833,
263
+ "device_sdc_type_sat_attr_total_lbas_written_normalized": 253,
264
+ "device_sdc_type_sat_attr_total_lbas_written_raw": 173833,
265
+ "device_sdc_type_sat_attr_udma_crc_error_count_decoded": 0,
266
+ "device_sdc_type_sat_attr_udma_crc_error_count_normalized": 100,
267
+ "device_sdc_type_sat_attr_udma_crc_error_count_raw": 0,
268
+ "device_sdc_type_sat_attr_unknown_attribute_decoded": 0,
269
+ "device_sdc_type_sat_attr_unknown_attribute_normalized": 0,
270
+ "device_sdc_type_sat_attr_unknown_attribute_raw": 0,
271
+ "device_sdc_type_sat_attr_unknown_ssd_attribute_decoded": 4694419309637,
272
+ "device_sdc_type_sat_attr_unknown_ssd_attribute_normalized": 4,
273
+ "device_sdc_type_sat_attr_unknown_ssd_attribute_raw": 4694419309637,
274
+ "device_sdc_type_sat_power_cycle_count": 13,
275
+ "device_sdc_type_sat_power_on_time": 29678400,
276
+ "device_sdc_type_sat_smart_status_failed": 0,
277
+ "device_sdc_type_sat_smart_status_passed": 1,
278
+ "device_sdc_type_sat_temperature": 27,
279
+ },
280
+ },
281
+ "success type nvme devices": {
282
+ prepareMock: prepareMockOkTypeNvme,
283
+ wantCharts: 4,
284
+ wantMetrics: map[string]int64{
285
+ "device_nvme0_type_nvme_power_cycle_count": 2,
286
+ "device_nvme0_type_nvme_power_on_time": 11206800,
287
+ "device_nvme0_type_nvme_smart_status_failed": 0,
288
+ "device_nvme0_type_nvme_smart_status_passed": 1,
289
+ "device_nvme0_type_nvme_temperature": 39,
290
+ },
291
+ },
292
+ "error on scan": {
293
+ prepareMock: prepareMockErrOnScan,
294
+ },
295
+ "unexpected response on scan": {
296
+ prepareMock: prepareMockUnexpectedResponse,
297
+ },
298
+ "empty response on scan": {
299
+ prepareMock: prepareMockEmptyResponse,
300
+ },
301
+ }
302
+
303
+ for name, test := range tests {
304
+ t.Run(name, func(t *testing.T) {
305
+ smart := New()
306
+ mock := test.prepareMock()
307
+ smart.exec = mock
308
+ smart.ScanEvery = web.Duration(time.Microsecond * 1)
309
+ smart.PollDevicesEvery = web.Duration(time.Microsecond * 1)
310
+
311
+ var mx map[string]int64
312
+ for i := 0; i < 10; i++ {
313
+ mx = smart.Collect()
314
+ }
315
+
316
+ assert.Equal(t, test.wantMetrics, mx)
317
+ assert.Len(t, *smart.Charts(), test.wantCharts)
318
+ testMetricsHasAllChartsDims(t, smart, mx)
319
+ })
320
+ }
321
+}
322
+
323
+func testMetricsHasAllChartsDims(t *testing.T, smart *Smartctl, mx map[string]int64) {
324
+ for _, chart := range *smart.Charts() {
325
+ if chart.Obsolete {
326
+ continue
327
+ }
328
+ for _, dim := range chart.Dims {
329
+ _, ok := mx[dim.ID]
330
+ assert.Truef(t, ok, "collected metrics has no data for dim '%s' chart '%s'", dim.ID, chart.ID)
331
+ }
332
+ for _, v := range chart.Vars {
333
+ _, ok := mx[v.ID]
334
+ assert.Truef(t, ok, "collected metrics has no data for var '%s' chart '%s'", v.ID, chart.ID)
335
+ }
336
+ }
337
+}
338
+
339
+func prepareMockOkTypeSata() *mockSmartctlCliExec {
340
+ return &mockSmartctlCliExec{
341
+ errOnScan: false,
342
+ scanData: dataTypeSataScan,
343
+ deviceDataFunc: func(deviceName, deviceType, powerMode string) ([]byte, error) {
344
+ if deviceType != "sat" {
345
+ return nil, fmt.Errorf("unexpected device type %s", deviceType)
346
+ }
347
+ switch deviceName {
348
+ case "/dev/sda":
349
+ return dataTypeSataDeviceHDDSda, nil
350
+ case "/dev/sdc":
351
+ return dataTypeSataDeviceSSDSdc, nil
352
+ default:
353
+ return nil, fmt.Errorf("unexpected device name %s", deviceName)
354
+ }
355
+ },
356
+ }
357
+}
358
+
359
+func prepareMockOkTypeNvme() *mockSmartctlCliExec {
360
+ return &mockSmartctlCliExec{
361
+ errOnScan: false,
362
+ scanData: dataTypeNvmeScan,
363
+ deviceDataFunc: func(deviceName, deviceType, powerMode string) ([]byte, error) {
364
+ if deviceType != "nvme" {
365
+ return nil, fmt.Errorf("unexpected device type %s", deviceType)
366
+ }
367
+ switch deviceName {
368
+ case "/dev/nvme0":
369
+ return dataTypeNvmeDeviceNvme0, nil
370
+ default:
371
+ return nil, fmt.Errorf("unexpected device name %s", deviceName)
372
+ }
373
+ },
374
+ }
375
+}
376
+
377
+func prepareMockErrOnScan() *mockSmartctlCliExec {
378
+ return &mockSmartctlCliExec{
379
+ errOnScan: true,
380
+ }
381
+}
382
+
383
+func prepareMockUnexpectedResponse() *mockSmartctlCliExec {
384
+ return &mockSmartctlCliExec{
385
+ scanData: []byte(randomJsonData),
386
+ deviceDataFunc: func(_, _, _ string) ([]byte, error) { return []byte(randomJsonData), nil },
387
+ }
388
+}
389
+
390
+func prepareMockEmptyResponse() *mockSmartctlCliExec {
391
+ return &mockSmartctlCliExec{}
392
+}
393
+
394
+type mockSmartctlCliExec struct {
395
+ errOnScan bool
396
+ scanData []byte
397
+ deviceDataFunc func(deviceName, deviceType, powerMode string) ([]byte, error)
398
+}
399
+
400
+func (m *mockSmartctlCliExec) scan() (*gjson.Result, error) {
401
+ if m.errOnScan {
402
+ return nil, fmt.Errorf("mock.scan() error")
403
+ }
404
+ res := gjson.ParseBytes(m.scanData)
405
+ return &res, nil
406
+}
407
+
408
+func (m *mockSmartctlCliExec) deviceInfo(deviceName, deviceType, powerMode string) (*gjson.Result, error) {
409
+ if m.deviceDataFunc == nil {
410
+ return nil, nil
411
+ }
412
+ bs, err := m.deviceDataFunc(deviceName, deviceType, powerMode)
413
+ if err != nil {
414
+ return nil, err
415
+ }
416
+ res := gjson.ParseBytes(bs)
417
+ return &res, nil
418
+}
419
+
420
+var randomJsonData = `
421
+{
422
+ "elephant": {
423
+ "burn": false,
424
+ "mountain": true,
425
+ "fog": false,
426
+ "skin": -1561907625,
427
+ "burst": "anyway",
428
+ "shadow": 1558616893
429
+ },
430
+ "start": "ever",
431
+ "base": 2093056027,
432
+ "mission": -2007590351,
433
+ "victory": 999053756,
434
+ "die": false
435
+}
436
+`
src/go/collectors/go.d.plugin/modules/smartctl/testdata/config.json
new
+7
@@ -0,0 +1,7 @@
1
+{
2
+ "update_every": 123,
3
+ "timeout": 123.123,
4
+ "scan_every": 123.123,
5
+ "poll_devices_every": 123.123,
6
+ "no_check_power_mode": "ok"
7
+}
src/go/collectors/go.d.plugin/modules/smartctl/testdata/config.yaml
new
+5
@@ -0,0 +1,5 @@
1
+update_every: 123
2
+timeout: 123.123
3
+scan_every: 123.123
4
+poll_devices_every: 123.123
5
+no_check_power_mode: "ok"
src/go/collectors/go.d.plugin/modules/smartctl/testdata/type-nvme/device-nvme0.json
new
+112
@@ -0,0 +1,112 @@
1
+{
2
+ "json_format_version": [
3
+ 1,
4
+ 0
5
+ ],
6
+ "smartctl": {
7
+ "version": [
8
+ 7,
9
+ 3
10
+ ],
11
+ "svn_revision": "5338",
12
+ "platform_info": "REDACTED",
13
+ "build_info": "(local build)",
14
+ "argv": [
15
+ "smartctl",
16
+ "--all",
17
+ "--json",
18
+ "--device=nvme",
19
+ "/dev/nvme0"
20
+ ],
21
+ "exit_status": 0
22
+ },
23
+ "local_time": {
24
+ "time_t": 1714480742,
25
+ "asctime": "Tue Apr 30 15:39:02 2024 EEST"
26
+ },
27
+ "device": {
28
+ "name": "/dev/nvme0",
29
+ "info_name": "/dev/nvme0",
30
+ "type": "nvme",
31
+ "protocol": "NVMe"
32
+ },
33
+ "model_name": "Seagate FireCuda 530 ZP4000GM30023",
34
+ "serial_number": "REDACTED",
35
+ "firmware_version": "REDACTED",
36
+ "nvme_pci_vendor": {
37
+ "id": 7089,
38
+ "subsystem_id": 7089
39
+ },
40
+ "nvme_ieee_oui_identifier": 6584743,
41
+ "nvme_total_capacity": 4000787030016,
42
+ "nvme_unallocated_capacity": 0,
43
+ "nvme_controller_id": 1,
44
+ "nvme_version": {
45
+ "string": "1.4",
46
+ "value": 66560
47
+ },
48
+ "nvme_number_of_namespaces": 1,
49
+ "nvme_namespaces": [
50
+ {
51
+ "id": 1,
52
+ "size": {
53
+ "blocks": 7814037168,
54
+ "bytes": 4000787030016
55
+ },
56
+ "capacity": {
57
+ "blocks": 7814037168,
58
+ "bytes": 4000787030016
59
+ },
60
+ "utilization": {
61
+ "blocks": 7814037168,
62
+ "bytes": 4000787030016
63
+ },
64
+ "formatted_lba_size": 512,
65
+ "eui64": {
66
+ "oui": 6584743,
67
+ "ext_id": 553497146765
68
+ }
69
+ }
70
+ ],
71
+ "user_capacity": {
72
+ "blocks": 7814037168,
73
+ "bytes": 4000787030016
74
+ },
75
+ "logical_block_size": 512,
76
+ "smart_support": {
77
+ "available": true,
78
+ "enabled": true
79
+ },
80
+ "smart_status": {
81
+ "passed": true,
82
+ "nvme": {
83
+ "value": 0
84
+ }
85
+ },
86
+ "nvme_smart_health_information_log": {
87
+ "critical_warning": 0,
88
+ "temperature": 39,
89
+ "available_spare": 100,
90
+ "available_spare_threshold": 5,
91
+ "percentage_used": 0,
92
+ "data_units_read": 52,
93
+ "data_units_written": 0,
94
+ "host_reads": 550,
95
+ "host_writes": 0,
96
+ "controller_busy_time": 0,
97
+ "power_cycles": 2,
98
+ "power_on_hours": 3113,
99
+ "unsafe_shutdowns": 1,
100
+ "media_errors": 0,
101
+ "num_err_log_entries": 4,
102
+ "warning_temp_time": 0,
103
+ "critical_comp_time": 0
104
+ },
105
+ "temperature": {
106
+ "current": 39
107
+ },
108
+ "power_cycle_count": 2,
109
+ "power_on_time": {
110
+ "hours": 3113
111
+ }
112
+}
src/go/collectors/go.d.plugin/modules/smartctl/testdata/type-nvme/scan.json
new
+29
@@ -0,0 +1,29 @@
1
+{
2
+ "json_format_version": [
3
+ 1,
4
+ 0
5
+ ],
6
+ "smartctl": {
7
+ "version": [
8
+ 7,
9
+ 3
10
+ ],
11
+ "svn_revision": "5338",
12
+ "platform_info": "REDACTED",
13
+ "build_info": "(local build)",
14
+ "argv": [
15
+ "smartctl",
16
+ "--scan",
17
+ "--json"
18
+ ],
19
+ "exit_status": 0
20
+ },
21
+ "devices": [
22
+ {
23
+ "name": "/dev/nvme0",
24
+ "info_name": "/dev/nvme0",
25
+ "type": "nvme",
26
+ "protocol": "NVMe"
27
+ }
28
+ ]
29
+}
src/go/collectors/go.d.plugin/modules/smartctl/testdata/type-sat/device-hdd-sda.json
new
+601
@@ -0,0 +1,601 @@
1
+{
2
+ "json_format_version": [
3
+ 1,
4
+ 0
5
+ ],
6
+ "smartctl": {
7
+ "version": [
8
+ 7,
9
+ 3
10
+ ],
11
+ "svn_revision": "5338",
12
+ "platform_info": "REDACTED",
13
+ "build_info": "(local build)",
14
+ "argv": [
15
+ "smartctl",
16
+ "--all",
17
+ "--json",
18
+ "--device=sat",
19
+ "/dev/sda"
20
+ ],
21
+ "drive_database_version": {
22
+ "string": "7.3/5319"
23
+ },
24
+ "exit_status": 0
25
+ },
26
+ "local_time": {
27
+ "time_t": 1714480013,
28
+ "asctime": "Tue Apr 30 15:26:53 2024 EEST"
29
+ },
30
+ "device": {
31
+ "name": "/dev/sda",
32
+ "info_name": "/dev/sda [SAT]",
33
+ "type": "sat",
34
+ "protocol": "ATA"
35
+ },
36
+ "model_name": "WDC WD181KRYZ-01AGBB0",
37
+ "serial_number": "REDACTED",
38
+ "wwn": {
39
+ "naa": 5,
40
+ "oui": 3274,
41
+ "id": 11659362274
42
+ },
43
+ "firmware_version": "REDACTED",
44
+ "user_capacity": {
45
+ "blocks": 35156656128,
46
+ "bytes": 18000207937536
47
+ },
48
+ "logical_block_size": 512,
49
+ "physical_block_size": 4096,
50
+ "rotation_rate": 7200,
51
+ "form_factor": {
52
+ "ata_value": 2,
53
+ "name": "3.5 inches"
54
+ },
55
+ "trim": {
56
+ "supported": false
57
+ },
58
+ "in_smartctl_database": false,
59
+ "ata_version": {
60
+ "string": "ACS-4 published, ANSI INCITS 529-2018",
61
+ "major_value": 4092,
62
+ "minor_value": 156
63
+ },
64
+ "sata_version": {
65
+ "string": "SATA 3.3",
66
+ "value": 511
67
+ },
68
+ "interface_speed": {
69
+ "max": {
70
+ "sata_value": 14,
71
+ "string": "6.0 Gb/s",
72
+ "units_per_second": 60,
73
+ "bits_per_unit": 100000000
74
+ },
75
+ "current": {
76
+ "sata_value": 3,
77
+ "string": "6.0 Gb/s",
78
+ "units_per_second": 60,
79
+ "bits_per_unit": 100000000
80
+ }
81
+ },
82
+ "smart_support": {
83
+ "available": true,
84
+ "enabled": true
85
+ },
86
+ "smart_status": {
87
+ "passed": true
88
+ },
89
+ "ata_smart_data": {
90
+ "offline_data_collection": {
91
+ "status": {
92
+ "value": 130,
93
+ "string": "was completed without error",
94
+ "passed": true
95
+ },
96
+ "completion_seconds": 101
97
+ },
98
+ "self_test": {
99
+ "status": {
100
+ "value": 0,
101
+ "string": "completed without error",
102
+ "passed": true
103
+ },
104
+ "polling_minutes": {
105
+ "short": 2,
106
+ "extended": 1883
107
+ }
108
+ },
109
+ "capabilities": {
110
+ "values": [
111
+ 91,
112
+ 3
113
+ ],
114
+ "exec_offline_immediate_supported": true,
115
+ "offline_is_aborted_upon_new_cmd": false,
116
+ "offline_surface_scan_supported": true,
117
+ "self_tests_supported": true,
118
+ "conveyance_self_test_supported": false,
119
+ "selective_self_test_supported": true,
120
+ "attribute_autosave_enabled": true,
121
+ "error_logging_supported": true,
122
+ "gp_logging_supported": true
123
+ }
124
+ },
125
+ "ata_sct_capabilities": {
126
+ "value": 61,
127
+ "error_recovery_control_supported": true,
128
+ "feature_control_supported": true,
129
+ "data_table_supported": true
130
+ },
131
+ "ata_smart_attributes": {
132
+ "revision": 16,
133
+ "table": [
134
+ {
135
+ "id": 1,
136
+ "name": "Raw_Read_Error_Rate",
137
+ "value": 100,
138
+ "worst": 100,
139
+ "thresh": 1,
140
+ "when_failed": "",
141
+ "flags": {
142
+ "value": 11,
143
+ "string": "PO-R-- ",
144
+ "prefailure": true,
145
+ "updated_online": true,
146
+ "performance": false,
147
+ "error_rate": true,
148
+ "event_count": false,
149
+ "auto_keep": false
150
+ },
151
+ "raw": {
152
+ "value": 0,
153
+ "string": "0"
154
+ }
155
+ },
156
+ {
157
+ "id": 2,
158
+ "name": "Throughput_Performance",
159
+ "value": 148,
160
+ "worst": 148,
161
+ "thresh": 54,
162
+ "when_failed": "",
163
+ "flags": {
164
+ "value": 5,
165
+ "string": "P-S--- ",
166
+ "prefailure": true,
167
+ "updated_online": false,
168
+ "performance": true,
169
+ "error_rate": false,
170
+ "event_count": false,
171
+ "auto_keep": false
172
+ },
173
+ "raw": {
174
+ "value": 48,
175
+ "string": "48"
176
+ }
177
+ },
178
+ {
179
+ "id": 3,
180
+ "name": "Spin_Up_Time",
181
+ "value": 86,
182
+ "worst": 86,
183
+ "thresh": 1,
184
+ "when_failed": "",
185
+ "flags": {
186
+ "value": 7,
187
+ "string": "POS--- ",
188
+ "prefailure": true,
189
+ "updated_online": true,
190
+ "performance": true,
191
+ "error_rate": false,
192
+ "event_count": false,
193
+ "auto_keep": false
194
+ },
195
+ "raw": {
196
+ "value": 25788088601,
197
+ "string": "281 (Average 279)"
198
+ }
199
+ },
200
+ {
201
+ "id": 4,
202
+ "name": "Start_Stop_Count",
203
+ "value": 100,
204
+ "worst": 100,
205
+ "thresh": 0,
206
+ "when_failed": "",
207
+ "flags": {
208
+ "value": 18,
209
+ "string": "-O--C- ",
210
+ "prefailure": false,
211
+ "updated_online": true,
212
+ "performance": false,
213
+ "error_rate": false,
214
+ "event_count": true,
215
+ "auto_keep": false
216
+ },
217
+ "raw": {
218
+ "value": 12,
219
+ "string": "12"
220
+ }
221
+ },
222
+ {
223
+ "id": 5,
224
+ "name": "Reallocated_Sector_Ct",
225
+ "value": 100,
226
+ "worst": 100,
227
+ "thresh": 1,
228
+ "when_failed": "",
229
+ "flags": {
230
+ "value": 51,
231
+ "string": "PO--CK ",
232
+ "prefailure": true,
233
+ "updated_online": true,
234
+ "performance": false,
235
+ "error_rate": false,
236
+ "event_count": true,
237
+ "auto_keep": true
238
+ },
239
+ "raw": {
240
+ "value": 0,
241
+ "string": "0"
242
+ }
243
+ },
244
+ {
245
+ "id": 7,
246
+ "name": "Seek_Error_Rate",
247
+ "value": 100,
248
+ "worst": 100,
249
+ "thresh": 1,
250
+ "when_failed": "",
251
+ "flags": {
252
+ "value": 11,
253
+ "string": "PO-R-- ",
254
+ "prefailure": true,
255
+ "updated_online": true,
256
+ "performance": false,
257
+ "error_rate": true,
258
+ "event_count": false,
259
+ "auto_keep": false
260
+ },
261
+ "raw": {
262
+ "value": 0,
263
+ "string": "0"
264
+ }
265
+ },
266
+ {
267
+ "id": 8,
268
+ "name": "Seek_Time_Performance",
269
+ "value": 140,
270
+ "worst": 140,
271
+ "thresh": 20,
272
+ "when_failed": "",
273
+ "flags": {
274
+ "value": 5,
275
+ "string": "P-S--- ",
276
+ "prefailure": true,
277
+ "updated_online": false,
278
+ "performance": true,
279
+ "error_rate": false,
280
+ "event_count": false,
281
+ "auto_keep": false
282
+ },
283
+ "raw": {
284
+ "value": 15,
285
+ "string": "15"
286
+ }
287
+ },
288
+ {
289
+ "id": 9,
290
+ "name": "Power_On_Hours",
291
+ "value": 99,
292
+ "worst": 99,
293
+ "thresh": 0,
294
+ "when_failed": "",
295
+ "flags": {
296
+ "value": 18,
297
+ "string": "-O--C- ",
298
+ "prefailure": false,
299
+ "updated_online": true,
300
+ "performance": false,
301
+ "error_rate": false,
302
+ "event_count": true,
303
+ "auto_keep": false
304
+ },
305
+ "raw": {
306
+ "value": 8244,
307
+ "string": "8244"
308
+ }
309
+ },
310
+ {
311
+ "id": 10,
312
+ "name": "Spin_Retry_Count",
313
+ "value": 100,
314
+ "worst": 100,
315
+ "thresh": 1,
316
+ "when_failed": "",
317
+ "flags": {
318
+ "value": 19,
319
+ "string": "PO--C- ",
320
+ "prefailure": true,
321
+ "updated_online": true,
322
+ "performance": false,
323
+ "error_rate": false,
324
+ "event_count": true,
325
+ "auto_keep": false
326
+ },
327
+ "raw": {
328
+ "value": 0,
329
+ "string": "0"
330
+ }
331
+ },
332
+ {
333
+ "id": 12,
334
+ "name": "Power_Cycle_Count",
335
+ "value": 100,
336
+ "worst": 100,
337
+ "thresh": 0,
338
+ "when_failed": "",
339
+ "flags": {
340
+ "value": 50,
341
+ "string": "-O--CK ",
342
+ "prefailure": false,
343
+ "updated_online": true,
344
+ "performance": false,
345
+ "error_rate": false,
346
+ "event_count": true,
347
+ "auto_keep": true
348
+ },
349
+ "raw": {
350
+ "value": 12,
351
+ "string": "12"
352
+ }
353
+ },
354
+ {
355
+ "id": 22,
356
+ "name": "Unknown_Attribute",
357
+ "value": 100,
358
+ "worst": 100,
359
+ "thresh": 25,
360
+ "when_failed": "",
361
+ "flags": {
362
+ "value": 35,
363
+ "string": "PO---K ",
364
+ "prefailure": true,
365
+ "updated_online": true,
366
+ "performance": false,
367
+ "error_rate": false,
368
+ "event_count": false,
369
+ "auto_keep": true
370
+ },
371
+ "raw": {
372
+ "value": 100,
373
+ "string": "100"
374
+ }
375
+ },
376
+ {
377
+ "id": 192,
378
+ "name": "Power-Off_Retract_Count",
379
+ "value": 100,
380
+ "worst": 100,
381
+ "thresh": 0,
382
+ "when_failed": "",
383
+ "flags": {
384
+ "value": 50,
385
+ "string": "-O--CK ",
386
+ "prefailure": false,
387
+ "updated_online": true,
388
+ "performance": false,
389
+ "error_rate": false,
390
+ "event_count": true,
391
+ "auto_keep": true
392
+ },
393
+ "raw": {
394
+ "value": 360,
395
+ "string": "360"
396
+ }
397
+ },
398
+ {
399
+ "id": 193,
400
+ "name": "Load_Cycle_Count",
401
+ "value": 100,
402
+ "worst": 100,
403
+ "thresh": 0,
404
+ "when_failed": "",
405
+ "flags": {
406
+ "value": 18,
407
+ "string": "-O--C- ",
408
+ "prefailure": false,
409
+ "updated_online": true,
410
+ "performance": false,
411
+ "error_rate": false,
412
+ "event_count": true,
413
+ "auto_keep": false
414
+ },
415
+ "raw": {
416
+ "value": 360,
417
+ "string": "360"
418
+ }
419
+ },
420
+ {
421
+ "id": 194,
422
+ "name": "Temperature_Celsius",
423
+ "value": 43,
424
+ "worst": 43,
425
+ "thresh": 0,
426
+ "when_failed": "",
427
+ "flags": {
428
+ "value": 2,
429
+ "string": "-O---- ",
430
+ "prefailure": false,
431
+ "updated_online": true,
432
+ "performance": false,
433
+ "error_rate": false,
434
+ "event_count": false,
435
+ "auto_keep": false
436
+ },
437
+ "raw": {
438
+ "value": 240519741489,
439
+ "string": "49 (Min/Max 24/56)"
440
+ }
441
+ },
442
+ {
443
+ "id": 196,
444
+ "name": "Reallocated_Event_Count",
445
+ "value": 100,
446
+ "worst": 100,
447
+ "thresh": 0,
448
+ "when_failed": "",
449
+ "flags": {
450
+ "value": 50,
451
+ "string": "-O--CK ",
452
+ "prefailure": false,
453
+ "updated_online": true,
454
+ "performance": false,
455
+ "error_rate": false,
456
+ "event_count": true,
457
+ "auto_keep": true
458
+ },
459
+ "raw": {
460
+ "value": 0,
461
+ "string": "0"
462
+ }
463
+ },
464
+ {
465
+ "id": 197,
466
+ "name": "Current_Pending_Sector",
467
+ "value": 100,
468
+ "worst": 100,
469
+ "thresh": 0,
470
+ "when_failed": "",
471
+ "flags": {
472
+ "value": 34,
473
+ "string": "-O---K ",
474
+ "prefailure": false,
475
+ "updated_online": true,
476
+ "performance": false,
477
+ "error_rate": false,
478
+ "event_count": false,
479
+ "auto_keep": true
480
+ },
481
+ "raw": {
482
+ "value": 0,
483
+ "string": "0"
484
+ }
485
+ },
486
+ {
487
+ "id": 198,
488
+ "name": "Offline_Uncorrectable",
489
+ "value": 100,
490
+ "worst": 100,
491
+ "thresh": 0,
492
+ "when_failed": "",
493
+ "flags": {
494
+ "value": 8,
495
+ "string": "---R-- ",
496
+ "prefailure": false,
497
+ "updated_online": false,
498
+ "performance": false,
499
+ "error_rate": true,
500
+ "event_count": false,
501
+ "auto_keep": false
502
+ },
503
+ "raw": {
504
+ "value": 0,
505
+ "string": "0"
506
+ }
507
+ },
508
+ {
509
+ "id": 199,
510
+ "name": "UDMA_CRC_Error_Count",
511
+ "value": 100,
512
+ "worst": 100,
513
+ "thresh": 0,
514
+ "when_failed": "",
515
+ "flags": {
516
+ "value": 10,
517
+ "string": "-O-R-- ",
518
+ "prefailure": false,
519
+ "updated_online": true,
520
+ "performance": false,
521
+ "error_rate": true,
522
+ "event_count": false,
523
+ "auto_keep": false
524
+ },
525
+ "raw": {
526
+ "value": 0,
527
+ "string": "0"
528
+ }
529
+ }
530
+ ]
531
+ },
532
+ "power_on_time": {
533
+ "hours": 8244
534
+ },
535
+ "power_cycle_count": 12,
536
+ "temperature": {
537
+ "current": 49
538
+ },
539
+ "ata_smart_error_log": {
540
+ "summary": {
541
+ "revision": 1,
542
+ "count": 0
543
+ }
544
+ },
545
+ "ata_smart_self_test_log": {
546
+ "standard": {
547
+ "revision": 1,
548
+ "count": 0
549
+ }
550
+ },
551
+ "ata_smart_selective_self_test_log": {
552
+ "revision": 1,
553
+ "table": [
554
+ {
555
+ "lba_min": 0,
556
+ "lba_max": 0,
557
+ "status": {
558
+ "value": 0,
559
+ "string": "Not_testing"
560
+ }
561
+ },
562
+ {
563
+ "lba_min": 0,
564
+ "lba_max": 0,
565
+ "status": {
566
+ "value": 0,
567
+ "string": "Not_testing"
568
+ }
569
+ },
570
+ {
571
+ "lba_min": 0,
572
+ "lba_max": 0,
573
+ "status": {
574
+ "value": 0,
575
+ "string": "Not_testing"
576
+ }
577
+ },
578
+ {
579
+ "lba_min": 0,
580
+ "lba_max": 0,
581
+ "status": {
582
+ "value": 0,
583
+ "string": "Not_testing"
584
+ }
585
+ },
586
+ {
587
+ "lba_min": 0,
588
+ "lba_max": 0,
589
+ "status": {
590
+ "value": 0,
591
+ "string": "Not_testing"
592
+ }
593
+ }
594
+ ],
595
+ "flags": {
596
+ "value": 0,
597
+ "remainder_scan_enabled": false
598
+ },
599
+ "power_up_scan_resume_minutes": 0
600
+ }
601
+}
src/go/collectors/go.d.plugin/modules/smartctl/testdata/type-sat/device-ssd-sdc.json
new
+652
@@ -0,0 +1,652 @@
1
+{
2
+ "json_format_version": [
3
+ 1,
4
+ 0
5
+ ],
6
+ "smartctl": {
7
+ "version": [
8
+ 7,
9
+ 3
10
+ ],
11
+ "svn_revision": "5338",
12
+ "platform_info": "REDACTED",
13
+ "build_info": "(local build)",
14
+ "argv": [
15
+ "smartctl",
16
+ "--all",
17
+ "--json",
18
+ "--device=sat",
19
+ "/dev/sdc"
20
+ ],
21
+ "drive_database_version": {
22
+ "string": "7.3/5319"
23
+ },
24
+ "exit_status": 0
25
+ },
26
+ "local_time": {
27
+ "time_t": 1714480059,
28
+ "asctime": "Tue Apr 30 15:27:39 2024 EEST"
29
+ },
30
+ "device": {
31
+ "name": "/dev/sdc",
32
+ "info_name": "/dev/sdc [SAT]",
33
+ "type": "sat",
34
+ "protocol": "ATA"
35
+ },
36
+ "model_name": "WDC WDS400T1R0A-68A4W0",
37
+ "serial_number": "REDACTED",
38
+ "wwn": {
39
+ "naa": 5,
40
+ "oui": 6980,
41
+ "id": 37319905210
42
+ },
43
+ "firmware_version": "REDACTED",
44
+ "user_capacity": {
45
+ "blocks": 7814037168,
46
+ "bytes": 4000787030016
47
+ },
48
+ "logical_block_size": 512,
49
+ "physical_block_size": 512,
50
+ "rotation_rate": 0,
51
+ "form_factor": {
52
+ "ata_value": 3,
53
+ "name": "2.5 inches"
54
+ },
55
+ "trim": {
56
+ "supported": true,
57
+ "deterministic": true,
58
+ "zeroed": true
59
+ },
60
+ "in_smartctl_database": false,
61
+ "ata_version": {
62
+ "string": "ACS-4 T13/BSR INCITS 529 revision 5",
63
+ "major_value": 4080,
64
+ "minor_value": 94
65
+ },
66
+ "sata_version": {
67
+ "string": "SATA 3.3",
68
+ "value": 511
69
+ },
70
+ "interface_speed": {
71
+ "max": {
72
+ "sata_value": 14,
73
+ "string": "6.0 Gb/s",
74
+ "units_per_second": 60,
75
+ "bits_per_unit": 100000000
76
+ },
77
+ "current": {
78
+ "sata_value": 3,
79
+ "string": "6.0 Gb/s",
80
+ "units_per_second": 60,
81
+ "bits_per_unit": 100000000
82
+ }
83
+ },
84
+ "smart_support": {
85
+ "available": true,
86
+ "enabled": true
87
+ },
88
+ "smart_status": {
89
+ "passed": true
90
+ },
91
+ "ata_smart_data": {
92
+ "offline_data_collection": {
93
+ "status": {
94
+ "value": 0,
95
+ "string": "was never started"
96
+ },
97
+ "completion_seconds": 0
98
+ },
99
+ "self_test": {
100
+ "status": {
101
+ "value": 0,
102
+ "string": "completed without error",
103
+ "passed": true
104
+ },
105
+ "polling_minutes": {
106
+ "short": 2,
107
+ "extended": 10
108
+ }
109
+ },
110
+ "capabilities": {
111
+ "values": [
112
+ 17,
113
+ 3
114
+ ],
115
+ "exec_offline_immediate_supported": true,
116
+ "offline_is_aborted_upon_new_cmd": false,
117
+ "offline_surface_scan_supported": false,
118
+ "self_tests_supported": true,
119
+ "conveyance_self_test_supported": false,
120
+ "selective_self_test_supported": false,
121
+ "attribute_autosave_enabled": true,
122
+ "error_logging_supported": true,
123
+ "gp_logging_supported": true
124
+ }
125
+ },
126
+ "ata_smart_attributes": {
127
+ "revision": 4,
128
+ "table": [
129
+ {
130
+ "id": 5,
131
+ "name": "Reallocated_Sector_Ct",
132
+ "value": 100,
133
+ "worst": 100,
134
+ "flags": {
135
+ "value": 50,
136
+ "string": "-O--CK ",
137
+ "prefailure": false,
138
+ "updated_online": true,
139
+ "performance": false,
140
+ "error_rate": false,
141
+ "event_count": true,
142
+ "auto_keep": true
143
+ },
144
+ "raw": {
145
+ "value": 0,
146
+ "string": "0"
147
+ }
148
+ },
149
+ {
150
+ "id": 9,
151
+ "name": "Power_On_Hours",
152
+ "value": 100,
153
+ "worst": 100,
154
+ "flags": {
155
+ "value": 50,
156
+ "string": "-O--CK ",
157
+ "prefailure": false,
158
+ "updated_online": true,
159
+ "performance": false,
160
+ "error_rate": false,
161
+ "event_count": true,
162
+ "auto_keep": true
163
+ },
164
+ "raw": {
165
+ "value": 8244,
166
+ "string": "8244"
167
+ }
168
+ },
169
+ {
170
+ "id": 12,
171
+ "name": "Power_Cycle_Count",
172
+ "value": 100,
173
+ "worst": 100,
174
+ "flags": {
175
+ "value": 50,
176
+ "string": "-O--CK ",
177
+ "prefailure": false,
178
+ "updated_online": true,
179
+ "performance": false,
180
+ "error_rate": false,
181
+ "event_count": true,
182
+ "auto_keep": true
183
+ },
184
+ "raw": {
185
+ "value": 13,
186
+ "string": "13"
187
+ }
188
+ },
189
+ {
190
+ "id": 165,
191
+ "name": "Unknown_Attribute",
192
+ "value": 100,
193
+ "worst": 100,
194
+ "flags": {
195
+ "value": 50,
196
+ "string": "-O--CK ",
197
+ "prefailure": false,
198
+ "updated_online": true,
199
+ "performance": false,
200
+ "error_rate": false,
201
+ "event_count": true,
202
+ "auto_keep": true
203
+ },
204
+ "raw": {
205
+ "value": 515627355118,
206
+ "string": "515627355118"
207
+ }
208
+ },
209
+ {
210
+ "id": 166,
211
+ "name": "Unknown_Attribute",
212
+ "value": 100,
213
+ "worst": 100,
214
+ "flags": {
215
+ "value": 50,
216
+ "string": "-O--CK ",
217
+ "prefailure": false,
218
+ "updated_online": true,
219
+ "performance": false,
220
+ "error_rate": false,
221
+ "event_count": true,
222
+ "auto_keep": true
223
+ },
224
+ "raw": {
225
+ "value": 1,
226
+ "string": "1"
227
+ }
228
+ },
229
+ {
230
+ "id": 167,
231
+ "name": "Unknown_Attribute",
232
+ "value": 100,
233
+ "worst": 100,
234
+ "flags": {
235
+ "value": 50,
236
+ "string": "-O--CK ",
237
+ "prefailure": false,
238
+ "updated_online": true,
239
+ "performance": false,
240
+ "error_rate": false,
241
+ "event_count": true,
242
+ "auto_keep": true
243
+ },
244
+ "raw": {
245
+ "value": 84,
246
+ "string": "84"
247
+ }
248
+ },
249
+ {
250
+ "id": 168,
251
+ "name": "Unknown_Attribute",
252
+ "value": 100,
253
+ "worst": 100,
254
+ "flags": {
255
+ "value": 50,
256
+ "string": "-O--CK ",
257
+ "prefailure": false,
258
+ "updated_online": true,
259
+ "performance": false,
260
+ "error_rate": false,
261
+ "event_count": true,
262
+ "auto_keep": true
263
+ },
264
+ "raw": {
265
+ "value": 34,
266
+ "string": "34"
267
+ }
268
+ },
269
+ {
270
+ "id": 169,
271
+ "name": "Unknown_Attribute",
272
+ "value": 100,
273
+ "worst": 100,
274
+ "flags": {
275
+ "value": 50,
276
+ "string": "-O--CK ",
277
+ "prefailure": false,
278
+ "updated_online": true,
279
+ "performance": false,
280
+ "error_rate": false,
281
+ "event_count": true,
282
+ "auto_keep": true
283
+ },
284
+ "raw": {
285
+ "value": 1600,
286
+ "string": "1600"
287
+ }
288
+ },
289
+ {
290
+ "id": 170,
291
+ "name": "Unknown_Attribute",
292
+ "value": 100,
293
+ "worst": 100,
294
+ "flags": {
295
+ "value": 50,
296
+ "string": "-O--CK ",
297
+ "prefailure": false,
298
+ "updated_online": true,
299
+ "performance": false,
300
+ "error_rate": false,
301
+ "event_count": true,
302
+ "auto_keep": true
303
+ },
304
+ "raw": {
305
+ "value": 0,
306
+ "string": "0"
307
+ }
308
+ },
309
+ {
310
+ "id": 171,
311
+ "name": "Unknown_Attribute",
312
+ "value": 100,
313
+ "worst": 100,
314
+ "flags": {
315
+ "value": 50,
316
+ "string": "-O--CK ",
317
+ "prefailure": false,
318
+ "updated_online": true,
319
+ "performance": false,
320
+ "error_rate": false,
321
+ "event_count": true,
322
+ "auto_keep": true
323
+ },
324
+ "raw": {
325
+ "value": 0,
326
+ "string": "0"
327
+ }
328
+ },
329
+ {
330
+ "id": 172,
331
+ "name": "Unknown_Attribute",
332
+ "value": 100,
333
+ "worst": 100,
334
+ "flags": {
335
+ "value": 50,
336
+ "string": "-O--CK ",
337
+ "prefailure": false,
338
+ "updated_online": true,
339
+ "performance": false,
340
+ "error_rate": false,
341
+ "event_count": true,
342
+ "auto_keep": true
343
+ },
344
+ "raw": {
345
+ "value": 0,
346
+ "string": "0"
347
+ }
348
+ },
349
+ {
350
+ "id": 173,
351
+ "name": "Unknown_Attribute",
352
+ "value": 100,
353
+ "worst": 100,
354
+ "flags": {
355
+ "value": 50,
356
+ "string": "-O--CK ",
357
+ "prefailure": false,
358
+ "updated_online": true,
359
+ "performance": false,
360
+ "error_rate": false,
361
+ "event_count": true,
362
+ "auto_keep": true
363
+ },
364
+ "raw": {
365
+ "value": 15,
366
+ "string": "15"
367
+ }
368
+ },
369
+ {
370
+ "id": 174,
371
+ "name": "Unknown_Attribute",
372
+ "value": 100,
373
+ "worst": 100,
374
+ "flags": {
375
+ "value": 50,
376
+ "string": "-O--CK ",
377
+ "prefailure": false,
378
+ "updated_online": true,
379
+ "performance": false,
380
+ "error_rate": false,
381
+ "event_count": true,
382
+ "auto_keep": true
383
+ },
384
+ "raw": {
385
+ "value": 4,
386
+ "string": "4"
387
+ }
388
+ },
389
+ {
390
+ "id": 184,
391
+ "name": "End-to-End_Error",
392
+ "value": 100,
393
+ "worst": 100,
394
+ "flags": {
395
+ "value": 50,
396
+ "string": "-O--CK ",
397
+ "prefailure": false,
398
+ "updated_online": true,
399
+ "performance": false,
400
+ "error_rate": false,
401
+ "event_count": true,
402
+ "auto_keep": true
403
+ },
404
+ "raw": {
405
+ "value": 0,
406
+ "string": "0"
407
+ }
408
+ },
409
+ {
410
+ "id": 187,
411
+ "name": "Reported_Uncorrect",
412
+ "value": 100,
413
+ "worst": 100,
414
+ "flags": {
415
+ "value": 50,
416
+ "string": "-O--CK ",
417
+ "prefailure": false,
418
+ "updated_online": true,
419
+ "performance": false,
420
+ "error_rate": false,
421
+ "event_count": true,
422
+ "auto_keep": true
423
+ },
424
+ "raw": {
425
+ "value": 0,
426
+ "string": "0"
427
+ }
428
+ },
429
+ {
430
+ "id": 188,
431
+ "name": "Command_Timeout",
432
+ "value": 100,
433
+ "worst": 100,
434
+ "flags": {
435
+ "value": 50,
436
+ "string": "-O--CK ",
437
+ "prefailure": false,
438
+ "updated_online": true,
439
+ "performance": false,
440
+ "error_rate": false,
441
+ "event_count": true,
442
+ "auto_keep": true
443
+ },
444
+ "raw": {
445
+ "value": 0,
446
+ "string": "0"
447
+ }
448
+ },
449
+ {
450
+ "id": 194,
451
+ "name": "Temperature_Celsius",
452
+ "value": 73,
453
+ "worst": 43,
454
+ "flags": {
455
+ "value": 34,
456
+ "string": "-O---K ",
457
+ "prefailure": false,
458
+ "updated_online": true,
459
+ "performance": false,
460
+ "error_rate": false,
461
+ "event_count": false,
462
+ "auto_keep": true
463
+ },
464
+ "raw": {
465
+ "value": 184684970011,
466
+ "string": "27 (Min/Max 21/43)"
467
+ }
468
+ },
469
+ {
470
+ "id": 199,
471
+ "name": "UDMA_CRC_Error_Count",
472
+ "value": 100,
473
+ "worst": 100,
474
+ "flags": {
475
+ "value": 50,
476
+ "string": "-O--CK ",
477
+ "prefailure": false,
478
+ "updated_online": true,
479
+ "performance": false,
480
+ "error_rate": false,
481
+ "event_count": true,
482
+ "auto_keep": true
483
+ },
484
+ "raw": {
485
+ "value": 0,
486
+ "string": "0"
487
+ }
488
+ },
489
+ {
490
+ "id": 230,
491
+ "name": "Unknown_SSD_Attribute",
492
+ "value": 4,
493
+ "worst": 4,
494
+ "flags": {
495
+ "value": 50,
496
+ "string": "-O--CK ",
497
+ "prefailure": false,
498
+ "updated_online": true,
499
+ "performance": false,
500
+ "error_rate": false,
501
+ "event_count": true,
502
+ "auto_keep": true
503
+ },
504
+ "raw": {
505
+ "value": 4694419309637,
506
+ "string": "4694419309637"
507
+ }
508
+ },
509
+ {
510
+ "id": 232,
511
+ "name": "Available_Reservd_Space",
512
+ "value": 100,
513
+ "worst": 100,
514
+ "thresh": 4,
515
+ "when_failed": "",
516
+ "flags": {
517
+ "value": 51,
518
+ "string": "PO--CK ",
519
+ "prefailure": true,
520
+ "updated_online": true,
521
+ "performance": false,
522
+ "error_rate": false,
523
+ "event_count": true,
524
+ "auto_keep": true
525
+ },
526
+ "raw": {
527
+ "value": 100,
528
+ "string": "100"
529
+ }
530
+ },
531
+ {
532
+ "id": 233,
533
+ "name": "Media_Wearout_Indicator",
534
+ "value": 100,
535
+ "worst": 100,
536
+ "flags": {
537
+ "value": 50,
538
+ "string": "-O--CK ",
539
+ "prefailure": false,
540
+ "updated_online": true,
541
+ "performance": false,
542
+ "error_rate": false,
543
+ "event_count": true,
544
+ "auto_keep": true
545
+ },
546
+ "raw": {
547
+ "value": 65406,
548
+ "string": "65406"
549
+ }
550
+ },
551
+ {
552
+ "id": 234,
553
+ "name": "Unknown_Attribute",
554
+ "value": 100,
555
+ "worst": 100,
556
+ "flags": {
557
+ "value": 50,
558
+ "string": "-O--CK ",
559
+ "prefailure": false,
560
+ "updated_online": true,
561
+ "performance": false,
562
+ "error_rate": false,
563
+ "event_count": true,
564
+ "auto_keep": true
565
+ },
566
+ "raw": {
567
+ "value": 190852,
568
+ "string": "190852"
569
+ }
570
+ },
571
+ {
572
+ "id": 241,
573
+ "name": "Total_LBAs_Written",
574
+ "value": 253,
575
+ "worst": 253,
576
+ "flags": {
577
+ "value": 48,
578
+ "string": "----CK ",
579
+ "prefailure": false,
580
+ "updated_online": false,
581
+ "performance": false,
582
+ "error_rate": false,
583
+ "event_count": true,
584
+ "auto_keep": true
585
+ },
586
+ "raw": {
587
+ "value": 173833,
588
+ "string": "173833"
589
+ }
590
+ },
591
+ {
592
+ "id": 242,
593
+ "name": "Total_LBAs_Read",
594
+ "value": 253,
595
+ "worst": 253,
596
+ "flags": {
597
+ "value": 48,
598
+ "string": "----CK ",
599
+ "prefailure": false,
600
+ "updated_online": false,
601
+ "performance": false,
602
+ "error_rate": false,
603
+ "event_count": true,
604
+ "auto_keep": true
605
+ },
606
+ "raw": {
607
+ "value": 76778,
608
+ "string": "76778"
609
+ }
610
+ },
611
+ {
612
+ "id": 244,
613
+ "name": "Unknown_Attribute",
614
+ "value": 0,
615
+ "worst": 100,
616
+ "flags": {
617
+ "value": 50,
618
+ "string": "-O--CK ",
619
+ "prefailure": false,
620
+ "updated_online": true,
621
+ "performance": false,
622
+ "error_rate": false,
623
+ "event_count": true,
624
+ "auto_keep": true
625
+ },
626
+ "raw": {
627
+ "value": 0,
628
+ "string": "0"
629
+ }
630
+ }
631
+ ]
632
+ },
633
+ "power_on_time": {
634
+ "hours": 8244
635
+ },
636
+ "power_cycle_count": 13,
637
+ "temperature": {
638
+ "current": 27
639
+ },
640
+ "ata_smart_error_log": {
641
+ "summary": {
642
+ "revision": 1,
643
+ "count": 0
644
+ }
645
+ },
646
+ "ata_smart_self_test_log": {
647
+ "standard": {
648
+ "revision": 1,
649
+ "count": 0
650
+ }
651
+ }
652
+}
src/go/collectors/go.d.plugin/modules/smartctl/testdata/type-sat/scan.json
new
+35
@@ -0,0 +1,35 @@
1
+{
2
+ "json_format_version": [
3
+ 1,
4
+ 0
5
+ ],
6
+ "smartctl": {
7
+ "version": [
8
+ 7,
9
+ 3
10
+ ],
11
+ "svn_revision": "5338",
12
+ "platform_info": "REDACTED",
13
+ "build_info": "(local build)",
14
+ "argv": [
15
+ "smartctl",
16
+ "--scan",
17
+ "--json"
18
+ ],
19
+ "exit_status": 0
20
+ },
21
+ "devices": [
22
+ {
23
+ "name": "/dev/sda",
24
+ "info_name": "/dev/sda [SAT]",
25
+ "type": "sat",
26
+ "protocol": "ATA"
27
+ },
28
+ {
29
+ "name": "/dev/sdc",
30
+ "info_name": "/dev/sdc [SAT]",
31
+ "type": "sat",
32
+ "protocol": "ATA"
33
+ }
34
+ ]
35
+}