@cryptotaxi247 / netdata-1 / commits / 92b00666e

go.d smartctl add "extra_devices" option (#18140)

Ilya Mashchenko committed Jul 13, 2024 at 23:19 UTC 92b00666e19594d77b17fce91ef849ef9cbb3baf
10 files changed +252 -19
src/go/plugin/go.d/modules/smartctl/collect.go
+3 -2
@@ -42,7 +42,8 @@ func (s *Smartctl) collect() (map[string]int64, error) {
42 // TODO: make it concurrent
43 for _, d := range s.scannedDevices {
44 if err := s.collectScannedDevice(mx, d); err != nil {
45 - return nil, err
45 + s.Warning(err)
46 + continue
47 }
48 }
49
@@ -57,7 +58,7 @@ func (s *Smartctl) collect() (map[string]int64, error) {
58 func (s *Smartctl) collectScannedDevice(mx map[string]int64, scanDev *scanDevice) error {
59 resp, err := s.exec.deviceInfo(scanDev.name, scanDev.typ, s.NoCheckPowerMode)
60 if err != nil {
60 - if resp != nil && isDeviceOpenFailedNoSuchDevice(resp) {
61 + if resp != nil && isDeviceOpenFailedNoSuchDevice(resp) && !scanDev.extra {
62 s.Infof("smartctl reported that device '%s' type '%s' no longer exists", scanDev.name, scanDev.typ)
63 s.forceScan = true
64 return nil
src/go/plugin/go.d/modules/smartctl/config_schema.json
+43 -2
@@ -50,6 +50,36 @@
50 "type": "string",
51 "minimum": 1,
52 "default": "*"
53 + },
54 + "extra_devices": {
55 + "title": "Extra devices",
56 + "description": "Allows manual specification of devices not automatically detected by `smartctl --scan`. Each device entry must include both a name and a type.",
57 + "type": [
58 + "array",
59 + "null"
60 + ],
61 + "uniqueItems": true,
62 + "items": {
63 + "title": "Device",
64 + "type": [
65 + "object",
66 + "null"
67 + ],
68 + "required": [
69 + "name",
70 + "type"
71 + ],
72 + "properties": {
73 + "name": {
74 + "title": "Name",
75 + "type": "string"
76 + },
77 + "type": {
78 + "title": "Type",
79 + "type": "string"
80 + }
81 + }
82 + }
83 }
84 },
85 "additionalProperties": false,
@@ -75,9 +105,10 @@
105 ]
106 },
107 {
78 - "title": "Filtering",
108 + "title": "Devices",
109 "fields": [
80 - "device_selector"
110 + "device_selector",
111 + "extra_devices"
112 ]
113 }
114 ]
@@ -94,6 +125,16 @@
125 },
126 "device_selector": {
127 "ui:help": "Leave blank or use `*` to collect data for all devices."
128 + },
129 + "extra_devices": {
130 + "items": {
131 + "name": {
132 + "ui:placeholder": "/dev/sda"
133 + },
134 + "type": {
135 + "ui:placeholder": "jmb39x-q,3"
136 + }
137 + }
138 }
139 }
140 }
src/go/plugin/go.d/modules/smartctl/init.go
+7
@@ -17,6 +17,13 @@ func (s *Smartctl) validateConfig() error {
17 default:
18 return fmt.Errorf("invalid power mode '%s'", s.NoCheckPowerMode)
19 }
20 +
21 + for _, v := range s.ExtraDevices {
22 + if v.Name == "" || v.Type == "" {
23 + return fmt.Errorf("invalid extra device: name and type must both be provided, got name='%s' type='%s'", v.Name, v.Type)
24 + }
25 + }
26 +
27 return nil
28 }
29
src/go/plugin/go.d/modules/smartctl/metadata.yaml
+13
@@ -108,6 +108,10 @@ modules:
108 description: "Specifies a pattern to match the 'info name' of devices as reported by `smartctl --scan --json`."
109 default_value: "*"
110 required: false
111 + - name: extra_devices
112 + description: "Allows manual specification of devices not automatically detected by `smartctl --scan`. Each device entry must include both a name and a type. See \"Configuration Examples\" for details."
113 + default_value: "[]"
114 + required: false
115 - name: no_check_power_mode
116 description: "Skip data collection when the device is in a low-power mode. Prevents unnecessary disk spin-up."
117 default_value: standby
@@ -132,6 +136,15 @@ modules:
136 jobs:
137 - name: smartctl
138 devices_poll_interval: 60 # Collect S.M.A.R.T statistics every 60 seconds
139 + - name: Extra devices
140 + description: |
141 + This example demonstrates using `extra_devices` to manually add a storage device (`/dev/sdc`) not automatically detected by `smartctl --scan`.
142 + config: |
143 + jobs:
144 + - name: smartctl
145 + extra_devices:
146 + - name: /dev/sdc
147 + type: jmb39x-q,3
148 troubleshooting:
149 problems:
150 list: []
src/go/plugin/go.d/modules/smartctl/scan.go
+13 -2
@@ -12,6 +12,7 @@ type scanDevice struct {
12 name string
13 infoName string
14 typ string
15 + extra bool // added via config "extra_devices"
16 }
17
18 func (s *scanDevice) key() string {
@@ -65,11 +66,21 @@ func (s *Smartctl) scanDevices() (map[string]*scanDevice, error) {
66 devices[dev.key()] = dev
67 }
68
69 + s.Debugf("smartctl scan found %d devices", len(devices))
70 +
71 + for _, v := range s.ExtraDevices {
72 + if v.Name == "" || v.Type == "" {
73 + continue
74 + }
75 + dev := &scanDevice{name: v.Name, typ: v.Type, extra: true}
76 + if _, ok := devices[dev.key()]; !ok {
77 + devices[dev.key()] = dev
78 + }
79 + }
80 +
81 if len(devices) == 0 {
82 return nil, errors.New("no devices found during scan")
83 }
84
72 - s.Debugf("smartctl scan found %d devices", len(devices))
73 -
85 return devices, nil
86 }
src/go/plugin/go.d/modules/smartctl/smartctl.go
+15 -8
@@ -43,14 +43,21 @@ func New() *Smartctl {
43 }
44 }
45
46 -type Config struct {
47 - UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
48 - Timeout web.Duration `yaml:"timeout,omitempty" json:"timeout"`
49 - ScanEvery web.Duration `yaml:"scan_every,omitempty" json:"scan_every"`
50 - PollDevicesEvery web.Duration `yaml:"poll_devices_every,omitempty" json:"poll_devices_every"`
51 - NoCheckPowerMode string `yaml:"no_check_power_mode,omitempty" json:"no_check_power_mode"`
52 - DeviceSelector string `yaml:"device_selector,omitempty" json:"device_selector"`
53 -}
46 +type (
47 + Config struct {
48 + UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
49 + Timeout web.Duration `yaml:"timeout,omitempty" json:"timeout"`
50 + ScanEvery web.Duration `yaml:"scan_every,omitempty" json:"scan_every"`
51 + PollDevicesEvery web.Duration `yaml:"poll_devices_every,omitempty" json:"poll_devices_every"`
52 + NoCheckPowerMode string `yaml:"no_check_power_mode,omitempty" json:"no_check_power_mode"`
53 + DeviceSelector string `yaml:"device_selector,omitempty" json:"device_selector"`
54 + ExtraDevices []ConfigExtraDevice `yaml:"extra_devices,omitempty" json:"extra_devices"`
55 + }
56 + ConfigExtraDevice struct {
57 + Name string `yaml:"name" json:"name"`
58 + Type string `yaml:"type" json:"type"`
59 + }
60 +)
61
62 type (
63 Smartctl struct {
src/go/plugin/go.d/modules/smartctl/smartctl_test.go
+34 -3
@@ -26,6 +26,7 @@ var (
26
27 dataTypeNvmeScan, _ = os.ReadFile("testdata/type-nvme/scan.json")
28 dataTypeNvmeDeviceNvme0, _ = os.ReadFile("testdata/type-nvme/device-nvme0.json")
29 + dataTypeNvmeDeviceNvme1, _ = os.ReadFile("testdata/type-nvme/device-nvme1.json")
30
31 dataTypeScsiScan, _ = os.ReadFile("testdata/type-scsi/scan.json")
32 dataTypeScsiDeviceSda, _ = os.ReadFile("testdata/type-scsi/device-sda.json")
@@ -42,6 +43,7 @@ func Test_testDataIsValid(t *testing.T) {
43
44 "dataTypeNvmeScan": dataTypeNvmeScan,
45 "dataTypeNvmeDeviceNvme0": dataTypeNvmeDeviceNvme0,
46 + "dataTypeNvmeDeviceNvme1": dataTypeNvmeDeviceNvme1,
47
48 "dataTypeScsiScan": dataTypeScsiScan,
49 "dataTypeScsiDeviceSda": dataTypeScsiDeviceSda,
@@ -166,9 +168,10 @@ func TestSmartctl_Check(t *testing.T) {
168
169 func TestSmartctl_Collect(t *testing.T) {
170 tests := map[string]struct {
169 - prepareMock func() *mockSmartctlCliExec
170 - wantMetrics map[string]int64
171 - wantCharts int
171 + prepareMock func() *mockSmartctlCliExec
172 + prepareConfig func() Config
173 + wantMetrics map[string]int64
174 + wantCharts int
175 }{
176 "success type sata devices": {
177 prepareMock: prepareMockOkTypeSata,
@@ -295,6 +298,29 @@ func TestSmartctl_Collect(t *testing.T) {
298 "device_nvme0_type_nvme_temperature": 39,
299 },
300 },
301 + "success type nvme devices with extra": {
302 + prepareMock: prepareMockOkTypeNvme,
303 + prepareConfig: func() Config {
304 + cfg := New().Config
305 + cfg.ExtraDevices = []ConfigExtraDevice{
306 + {Name: "/dev/nvme1", Type: "nvme"},
307 + }
308 + return cfg
309 + },
310 + wantCharts: 8,
311 + wantMetrics: map[string]int64{
312 + "device_nvme0_type_nvme_power_cycle_count": 2,
313 + "device_nvme0_type_nvme_power_on_time": 11206800,
314 + "device_nvme0_type_nvme_smart_status_failed": 0,
315 + "device_nvme0_type_nvme_smart_status_passed": 1,
316 + "device_nvme0_type_nvme_temperature": 39,
317 + "device_nvme1_type_nvme_power_cycle_count": 5,
318 + "device_nvme1_type_nvme_power_on_time": 17038800,
319 + "device_nvme1_type_nvme_smart_status_failed": 0,
320 + "device_nvme1_type_nvme_smart_status_passed": 1,
321 + "device_nvme1_type_nvme_temperature": 36,
322 + },
323 + },
324 "success type scsi devices": {
325 prepareMock: prepareMockOkTypeScsi,
326 wantCharts: 7,
@@ -326,6 +352,9 @@ func TestSmartctl_Collect(t *testing.T) {
352 for name, test := range tests {
353 t.Run(name, func(t *testing.T) {
354 smart := New()
355 + if test.prepareConfig != nil {
356 + smart.Config = test.prepareConfig()
357 + }
358 mock := test.prepareMock()
359 smart.exec = mock
360 smart.ScanEvery = web.Duration(time.Microsecond * 1)
@@ -390,6 +419,8 @@ func prepareMockOkTypeNvme() *mockSmartctlCliExec {
419 switch deviceName {
420 case "/dev/nvme0":
421 return dataTypeNvmeDeviceNvme0, nil
422 + case "/dev/nvme1":
423 + return dataTypeNvmeDeviceNvme1, nil
424 default:
425 return nil, fmt.Errorf("unexpected device name %s", deviceName)
426 }
src/go/plugin/go.d/modules/smartctl/testdata/config.json
+7 -1
@@ -4,5 +4,11 @@
4 "scan_every": 123.123,
5 "poll_devices_every": 123.123,
6 "no_check_power_mode": "ok",
7 - "device_selector": "ok"
7 + "device_selector": "ok",
8 + "extra_devices": [
9 + {
10 + "name": "ok",
11 + "type": "ok"
12 + }
13 + ]
14 }
src/go/plugin/go.d/modules/smartctl/testdata/config.yaml
+4 -1
@@ -3,4 +3,7 @@ timeout: 123.123
3 scan_every: 123.123
4 poll_devices_every: 123.123
5 no_check_power_mode: "ok"
6 -"device_selector": "ok"
6 +device_selector: "ok"
7 +extra_devices:
8 + - name: "ok"
9 + type: "ok"
src/go/plugin/go.d/modules/smartctl/testdata/type-nvme/device-nvme1.json new
+113
@@ -0,0 +1,113 @@
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 + "--json",
17 + "--all",
18 + "/dev/nvme1",
19 + "--device",
20 + "nvme"
21 + ],
22 + "exit_status": 0
23 + },
24 + "local_time": {
25 + "time_t": 1720897758,
26 + "asctime": "Sat Jul 13 22:09:18 2024 EEST"
27 + },
28 + "device": {
29 + "name": "/dev/nvme1",
30 + "info_name": "/dev/nvme1",
31 + "type": "nvme",
32 + "protocol": "NVMe"
33 + },
34 + "model_name": "Seagate FireCuda 530 ZP4000GM30023",
35 + "serial_number": "REDACTED",
36 + "firmware_version": "REDACTED",
37 + "nvme_pci_vendor": {
38 + "id": 7089,
39 + "subsystem_id": 7089
40 + },
41 + "nvme_ieee_oui_identifier": 6584743,
42 + "nvme_total_capacity": 4000787030016,
43 + "nvme_unallocated_capacity": 0,
44 + "nvme_controller_id": 1,
45 + "nvme_version": {
46 + "string": "1.4",
47 + "value": 66560
48 + },
49 + "nvme_number_of_namespaces": 1,
50 + "nvme_namespaces": [
51 + {
52 + "id": 1,
53 + "size": {
54 + "blocks": 7814037168,
55 + "bytes": 4000787030016
56 + },
57 + "capacity": {
58 + "blocks": 7814037168,
59 + "bytes": 4000787030016
60 + },
61 + "utilization": {
62 + "blocks": 7814037168,
63 + "bytes": 4000787030016
64 + },
65 + "formatted_lba_size": 512,
66 + "eui64": {
67 + "oui": 6584743,
68 + "ext_id": 553497146765
69 + }
70 + }
71 + ],
72 + "user_capacity": {
73 + "blocks": 7814037168,
74 + "bytes": 4000787030016
75 + },
76 + "logical_block_size": 512,
77 + "smart_support": {
78 + "available": true,
79 + "enabled": true
80 + },
81 + "smart_status": {
82 + "passed": true,
83 + "nvme": {
84 + "value": 0
85 + }
86 + },
87 + "nvme_smart_health_information_log": {
88 + "critical_warning": 0,
89 + "temperature": 36,
90 + "available_spare": 100,
91 + "available_spare_threshold": 5,
92 + "percentage_used": 0,
93 + "data_units_read": 202,
94 + "data_units_written": 0,
95 + "host_reads": 2509,
96 + "host_writes": 0,
97 + "controller_busy_time": 0,
98 + "power_cycles": 5,
99 + "power_on_hours": 4733,
100 + "unsafe_shutdowns": 2,
101 + "media_errors": 0,
102 + "num_err_log_entries": 20,
103 + "warning_temp_time": 0,
104 + "critical_comp_time": 0
105 + },
106 + "temperature": {
107 + "current": 36
108 + },
109 + "power_cycle_count": 5,
110 + "power_on_time": {
111 + "hours": 4733
112 + }
113 +}