@cryptotaxi247 / netdata-1 / commits / e1c4f7fec

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 +}