@cryptotaxi247 / netdata-1 / commits / d3a385de8

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"