go.d smartctl small improvements (#17565)
Ilya Mashchenko committed
May 1, 2024 at 12:28 UTC
d3a385de850dbe8b2a66c5c4b9132b81e233aa89
9 files changed
+98
-13
src/go/collectors/go.d.plugin/modules/smartctl/charts.go
+13
@@ -259,12 +259,25 @@ func attributeUnit(attrName string) string {
259
"Offline_Uncorrectable": "sectors",
260
"Pending_Sector_Count": "sectors",
261
"Reallocated_Sector_Ct": "sectors",
262
+ "Current_Pending_Sector": "sectors",
263
+ "Reported_Uncorrect": "errors",
264
+ "Command_Timeout": "events",
265
}
266
267
if unit, ok := units[attrName]; ok {
268
return unit
269
}
270
271
+ // TODO: convert to bytes during data collection? (examples: NAND_Writes_32MiB, Flash_Writes_GiB)
272
+ if strings.HasSuffix(attrName, "MiB") || strings.HasSuffix(attrName, "GiB") {
273
+ if strings.Contains(attrName, "Writes") {
274
+ return "writes"
275
+ }
276
+ if strings.Contains(attrName, "Reads") {
277
+ return "reads"
278
+ }
279
+ }
280
+
281
if strings.Contains(attrName, "Error") {
282
return "errors"
283
}
src/go/collectors/go.d.plugin/modules/smartctl/config_schema.json
+32
@@ -43,6 +43,13 @@
43
"idle"
44
],
45
"default": "standby"
46
+ },
47
+ "device_selector": {
48
+ "title": "Device selector",
49
+ "description": "Specifies a [pattern](https://github.com/netdata/netdata/tree/master/src/libnetdata/simple_pattern#readme) to match the 'info name' of devices as reported by `smartctl --scan --json`. Only devices whose 'info name' matches this pattern will be collected.",
50
+ "type": "string",
51
+ "minimum": 1,
52
+ "default": "*"
53
}
54
},
55
"additionalProperties": false,
@@ -54,14 +61,39 @@
61
"uiOptions": {
62
"fullPage": true
63
},
64
+ "ui:flavour": "tabs",
65
+ "ui:options": {
66
+ "tabs": [
67
+ {
68
+ "title": "Base",
69
+ "fields": [
70
+ "update_every",
71
+ "timeout",
72
+ "scan_every",
73
+ "poll_devices_every",
74
+ "no_check_power_mode"
75
+ ]
76
+ },
77
+ {
78
+ "title": "Filtering",
79
+ "fields": [
80
+ "device_selector"
81
+ ]
82
+ }
83
+ ]
84
+ },
85
"timeout": {
86
"ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
87
},
88
"no_check_power_mode": {
89
+ "ui:help": "`never` - check the device always; `sleep` - check the device unless it is in SLEEP mode; `standby` - check the device unless it is in SLEEP or STANDBY mode; `idle` - check the device unless it is in SLEEP, STANDBY or IDLE mode.",
90
"ui:widget": "radio",
91
"ui:options": {
92
"inline": true
93
}
94
+ },
95
+ "device_selector": {
96
+ "ui:help": "Leave blank or use `*` to collect data for all devices."
97
}
98
}
99
}
src/go/collectors/go.d.plugin/modules/smartctl/init.go
+14
@@ -8,6 +8,7 @@ import (
8
"path/filepath"
9
10
"github.com/netdata/netdata/go/go.d.plugin/agent/executable"
11
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/matcher"
12
)
13
14
func (s *Smartctl) validateConfig() error {
@@ -19,6 +20,19 @@ func (s *Smartctl) validateConfig() error {
20
return nil
21
}
22
23
+func (s *Smartctl) initDeviceSelector() (matcher.Matcher, error) {
24
+ if s.DeviceSelector == "" {
25
+ return matcher.TRUE(), nil
26
+ }
27
+
28
+ m, err := matcher.NewSimplePatternsMatcher(s.DeviceSelector)
29
+ if err != nil {
30
+ return nil, err
31
+ }
32
+
33
+ return m, nil
34
+}
35
+
36
func (s *Smartctl) initSmartctlCli() (smartctlCli, error) {
37
ndsudoPath := filepath.Join(executable.Directory, "ndsudo")
38
if _, err := os.Stat(ndsudoPath); err != nil {
src/go/collectors/go.d.plugin/modules/smartctl/metadata.yaml
+4
@@ -75,6 +75,10 @@ modules:
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
+ - name: device_selector
79
+ description: "Specifies a pattern to match the 'info name' of devices as reported by `smartctl --scan --json`."
80
+ default_value: "*"
81
+ required: false
82
examples:
83
folding:
84
title: Config
src/go/collectors/go.d.plugin/modules/smartctl/scan.go
+14
-7
@@ -25,7 +25,7 @@ func (s *scanDevice) shortName() string {
25
func (s *Smartctl) scanDevices() (map[string]*scanDevice, error) {
26
resp, err := s.exec.scan()
27
if err != nil {
28
- return nil, err
28
+ return nil, fmt.Errorf("failed to scan devices: %v", err)
29
}
30
31
devices := make(map[string]*scanDevice)
@@ -34,10 +34,16 @@ func (s *Smartctl) scanDevices() (map[string]*scanDevice, error) {
34
dev := &scanDevice{
35
name: d.Get("name").String(),
36
infoName: d.Get("info_name").String(),
37
- typ: d.Get("type").String(),
37
+ typ: d.Get("type").String(), // guessed type (we do '--scan' not '--scan-open')
38
}
39
40
if dev.name == "" || dev.typ == "" {
41
+ s.Warningf("device info missing required fields (name: '%s', type: '%s'), skipping", dev.name, dev.typ)
42
+ continue
43
+ }
44
+
45
+ if !s.deviceSr.MatchString(dev.infoName) {
46
+ s.Debugf("device %s does not match selector, skipping it", dev.infoName)
47
continue
48
}
49
@@ -48,18 +54,19 @@ func (s *Smartctl) scanDevices() (map[string]*scanDevice, error) {
54
// For example, using 'scsi' for 'sat' devices prevents `smartctl` from issuing the necessary ATA commands.
55
resp, _ := s.exec.deviceInfo(dev.name, dev.typ, s.NoCheckPowerMode)
56
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
57
+ correctType := "sat"
58
+ s.Debugf("changing device '%s' type '%s' -> '%s'", dev.name, dev.typ, correctType)
59
+ dev.typ = correctType
60
}
55
- s.Debugf("smartctl scan found device '%s' type '%s' info_name '%s'", dev.name, dev.typ, dev.infoName)
61
}
62
63
+ s.Debugf("smartctl scan found device '%s' type '%s' info_name '%s'", dev.name, dev.typ, dev.infoName)
64
+
65
devices[dev.key()] = dev
66
}
67
68
if len(devices) == 0 {
62
- return nil, errors.New("no devices found on scan")
69
+ return nil, errors.New("no devices found during scan")
70
}
71
72
s.Infof("smartctl scan found %d devices", len(devices))
src/go/collectors/go.d.plugin/modules/smartctl/smart_device.go
+5
-5
@@ -59,12 +59,12 @@ func (d *smartDevice) temperature() (int64, bool) {
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
62
+ for _, s := range []string{"power_cycle_count", "scsi_start_stop_cycle_counter.accumulated_start_stop_cycles"} {
63
+ if v := d.data.Get(s); v.Exists() {
64
+ return v.Int(), true
65
+ }
66
}
66
- v = d.data.Get("scsi_start_stop_cycle_counter.accumulated_start_stop_cycles")
67
- return v.Int(), v.Exists()
67
+ return 0, false
68
}
69
70
func (d *smartDevice) smartStatusPassed() (bool, bool) {
src/go/collectors/go.d.plugin/modules/smartctl/smartctl.go
+13
@@ -8,6 +8,7 @@ import (
8
"time"
9
10
"github.com/netdata/netdata/go/go.d.plugin/agent/module"
11
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/matcher"
12
"github.com/netdata/netdata/go/go.d.plugin/pkg/web"
13
14
"github.com/tidwall/gjson"
@@ -33,8 +34,10 @@ func New() *Smartctl {
34
ScanEvery: web.Duration(time.Minute * 15),
35
PollDevicesEvery: web.Duration(time.Minute * 5),
36
NoCheckPowerMode: "standby",
37
+ DeviceSelector: "*",
38
},
39
charts: &module.Charts{},
40
+ deviceSr: matcher.TRUE(),
41
seenDevices: make(map[string]bool),
42
}
43
}
@@ -45,6 +48,7 @@ type Config struct {
48
ScanEvery web.Duration `yaml:"scan_every" json:"scan_every"`
49
PollDevicesEvery web.Duration `yaml:"poll_devices_every" json:"poll_devices_every"`
50
NoCheckPowerMode string `yaml:"no_check_power_mode" json:"no_check_power_mode"`
51
+ DeviceSelector string `yaml:"device_selector" json:"device_selector"`
52
}
53
54
type (
@@ -56,6 +60,8 @@ type (
60
61
exec smartctlCli
62
63
+ deviceSr matcher.Matcher
64
+
65
lastScanTime time.Time
66
forceScan bool
67
scannedDevices map[string]*scanDevice
@@ -82,6 +88,13 @@ func (s *Smartctl) Init() error {
88
return err
89
}
90
91
+ sr, err := s.initDeviceSelector()
92
+ if err != nil {
93
+ s.Errorf("device selector initialization: %v", err)
94
+ return err
95
+ }
96
+ s.deviceSr = sr
97
+
98
smartctlExec, err := s.initSmartctlCli()
99
if err != nil {
100
s.Errorf("smartctl exec initialization: %v", err)
src/go/collectors/go.d.plugin/modules/smartctl/testdata/config.json
+2
-1
@@ -3,5 +3,6 @@
3
"timeout": 123.123,
4
"scan_every": 123.123,
5
"poll_devices_every": 123.123,
6
- "no_check_power_mode": "ok"
6
+ "no_check_power_mode": "ok",
7
+ "device_selector": "ok"
8
}
src/go/collectors/go.d.plugin/modules/smartctl/testdata/config.yaml
+1
@@ -3,3 +3,4 @@ timeout: 123.123
3
scan_every: 123.123
4
poll_devices_every: 123.123
5
no_check_power_mode: "ok"
6
+"device_selector": "ok"