diff options
author | Andrew Biba <andrew.biba@zabbix.com> | 2022-04-06 15:49:51 +0300 |
---|---|---|
committer | Andrew Biba <andrew.biba@zabbix.com> | 2022-04-06 15:49:51 +0300 |
commit | f721d53c0bfc1371fe9e45c97ae6c2c5057d579b (patch) | |
tree | dbd40b870feaaef2f6b6ce01b92dc7ec6bfda394 /src | |
parent | f64e246298a9e60ef3ed94f95c506aab4264b6bd (diff) | |
parent | 43b03544c94598d1be9454f08587b6d70a180d0e (diff) |
...G.....T [ZBXNEXT-7559] updated smart.disk.discovery and smart.disk.get metrics for Zabbix agent 2 and updated S.M.A.R.T passive and active templates
* commit '43b03544c94598d1be9454f08587b6d70a180d0e':
...G.....T [ZBXNEXT-7559] updated chnagelog file
...G...... [ZBXNEXT-7559] added debug logging of output buffer in the case of system command execution failure
...G...... [ZBXNEXT-7559] added -n flag to sudo execution of smartctl for smart.* plugin in Zabbix agent 2
...G...... [ZBXNEXT-7559] fixed smart.disk.discovery for megaraid
...G...... [ZBXNEXT-7559] code clean up
...G...... [ZBXNEXT-7559] added raidtype to smart.disk.discovery response and as second parameter for smart.disk.get
...G...... [ZBXNEXT-7559] code clean up
...G...... [ZBXNEXT-7559] fixed smart.disk.get[path] attribute raw field json key
...G...... [ZBXNEXT-7559] changed self_test_passed output key to better match rest of keys
...G...... [ZBXNEXT-7559] changed attributes in smart.disk.discovery to be an array
.......... [ZBXNEXT-7559] added change log
...G...... [ZBXNEXT-7559] fixed smart.disk.discovery path output field for raid devices
...G...... [ZBXNEXT-7559] fixed smart.disk.get[path] for raid devices
...G...... [ZBXNEXT-7559] code clean up
...G...... [ZBXNEXT-7559] code clean up
...G...... [ZBXNEXT-7559] updated unit test
...G...... [ZBXNEXT-7559] added unit test
...G...... [ZBXNEXT-7559] added PATH parameter to smart.disk.get key in Zabbix agent 2
...G.....T [ZBXNEXT-7559] updated smart.disk.discovery, smart.disk.get metrics for Zabbix agent 2 and S.M.A.R.T passive and active templates
Diffstat (limited to 'src')
-rw-r--r-- | src/go/pkg/zbxcmd/zbxcmd_nix.go | 5 | ||||
-rw-r--r-- | src/go/plugins/smart/smart.go | 270 | ||||
-rw-r--r-- | src/go/plugins/smart/smart_nix.go | 2 | ||||
-rw-r--r-- | src/go/plugins/smart/smart_test.go | 422 | ||||
-rw-r--r-- | src/go/plugins/smart/smartfs.go | 125 |
5 files changed, 748 insertions, 76 deletions
diff --git a/src/go/pkg/zbxcmd/zbxcmd_nix.go b/src/go/pkg/zbxcmd/zbxcmd_nix.go index addd629406e..59f83a49303 100644 --- a/src/go/pkg/zbxcmd/zbxcmd_nix.go +++ b/src/go/pkg/zbxcmd/zbxcmd_nix.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows /* ** Zabbix @@ -65,7 +64,9 @@ func execute(s string, timeout time.Duration, path string, strict bool) (string, // we need to check error after t.Stop so we can inform the user if timeout was reached and Zabbix agent2 terminated the command if strict && werr != nil && !errors.Is(werr, syscall.ECHILD) { - return "", fmt.Errorf("Command execution failed: %s", werr) + log.Debugf("Command [%s] execution failed: %s\n%s", s, werr, b.String()) + + return "", fmt.Errorf("Command execution failed: %w", werr) } if MaxExecuteOutputLenB <= len(b.String()) { diff --git a/src/go/plugins/smart/smart.go b/src/go/plugins/smart/smart.go index f71407b6b97..36bb16d9b8a 100644 --- a/src/go/plugins/smart/smart.go +++ b/src/go/plugins/smart/smart.go @@ -21,12 +21,27 @@ package smart import ( "encoding/json" + "fmt" + "strings" "zabbix.com/pkg/conf" "zabbix.com/pkg/plugin" "zabbix.com/pkg/zbxerr" ) +const ( + twoParameters = 2 + oneParameter = 1 + all = 0 + + firstParameter = 0 + secondParameter = 1 + + diskGet = "smart.disk.get" + diskDiscovery = "smart.disk.discovery" + attributeDiscovery = "smart.attribute.discovery" +) + // Options - type Options struct { plugin.SystemOptions `conf:"optional,name=System"` @@ -61,7 +76,7 @@ func (p *Plugin) Validate(options interface{}) error { // Export - func (p *Plugin) Export(key string, params []string, ctx plugin.ContextProvider) (result interface{}, err error) { - if len(params) > 0 { + if len(params) > 0 && key != diskGet { return nil, zbxerr.ErrorTooManyParameters } @@ -72,84 +87,217 @@ func (p *Plugin) Export(key string, params []string, ctx plugin.ContextProvider) var jsonArray []byte switch key { - case "smart.disk.discovery": - out := []device{} - - r, err := p.execute(false) + case diskDiscovery: + jsonArray, err = p.diskDiscovery() if err != nil { - return nil, err - } - - for _, dev := range r.devices { - out = append(out, device{ - Name: cutPrefix(dev.Info.Name), - DeviceType: getType(dev.Info.DevType, dev.RotationRate, dev.SmartAttributes.Table), - Model: dev.ModelName, SerialNumber: dev.SerialNumber, - }) + return nil, zbxerr.ErrorCannotFetchData.Wrap(err) } - jsonArray, err = json.Marshal(out) + case diskGet: + jsonArray, err = p.diskGet(params) if err != nil { - return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err) + return nil, zbxerr.ErrorCannotFetchData.Wrap(err) } - case "smart.disk.get": - r, err := p.execute(true) + case attributeDiscovery: + jsonArray, err = p.attributeDiscovery() if err != nil { - return nil, err + return nil, zbxerr.ErrorCannotFetchData.Wrap(err) } - fields, err := setDiskFields(r.jsonDevices) - if err != nil { - return nil, err - } + default: + return nil, zbxerr.ErrorUnsupportedMetric + } - if fields == nil { - jsonArray, err = json.Marshal([]string{}) - if err != nil { - return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err) - } + return string(jsonArray), nil +} - break - } +func (p *Plugin) diskDiscovery() (jsonArray []byte, err error) { + out := []device{} + + r, err := p.execute(false) + if err != nil { + return nil, err + } + + for _, dev := range r.devices { + out = append(out, device{ + Name: cutPrefix(dev.Info.Name), + DeviceType: getType(dev.Info.DevType, dev.RotationRate, dev.SmartAttributes.Table), + Model: dev.ModelName, + SerialNumber: dev.SerialNumber, + Path: dev.Info.name, + RaidType: dev.Info.raidType, + Attributes: getAttributes(dev), + }) + } + + jsonArray, err = json.Marshal(out) + if err != nil { + return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err) + } + + return +} + +func (p *Plugin) diskGet(params []string) ([]byte, error) { + switch len(params) { + case twoParameters: + return p.diskGetSingle(params[firstParameter], params[secondParameter]) + case oneParameter: + return p.diskGetSingle(params[firstParameter], "") + case all: + return p.diskGetAll() + default: + return nil, zbxerr.ErrorTooManyParameters + } +} + +func (p *Plugin) diskGetSingle(path, raidType string) ([]byte, error) { + executable := path + + if raidType != "" { + executable = fmt.Sprintf("%s -d %s", executable, raidType) + } + + device, err := p.executeSingle(executable) + if err != nil { + return nil, err + } + + out, err := setSingleDiskFields(device) + if err != nil { + return nil, err + } + + jsonArray, err := json.Marshal(out) + if err != nil { + return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err) + } + + return jsonArray, nil +} + +func (p *Plugin) diskGetAll() (jsonArray []byte, err error) { + r, err := p.execute(true) + if err != nil { + return nil, err + } - jsonArray, err = json.Marshal(fields) + fields, err := setDiskFields(r.jsonDevices) + if err != nil { + return nil, err + } + + if fields == nil { + jsonArray, err = json.Marshal([]string{}) if err != nil { return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err) } - case "smart.attribute.discovery": - out := []attribute{} + return + } - r, err := p.execute(false) - if err != nil { - return nil, err - } + jsonArray, err = json.Marshal(fields) + if err != nil { + return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err) + } - for _, dev := range r.devices { - t := getAttributeType(dev.Info.DevType, dev.RotationRate, dev.SmartAttributes.Table) - for _, attr := range dev.SmartAttributes.Table { - out = append( - out, attribute{ - Name: cutPrefix(dev.Info.Name), - DeviceType: t, - ID: attr.ID, - Attrname: attr.Attrname, - Thresh: attr.Thresh, - }) - } + return +} + +func (p *Plugin) attributeDiscovery() (jsonArray []byte, err error) { + out := []attribute{} + + r, err := p.execute(false) + if err != nil { + return nil, err + } + + for _, dev := range r.devices { + t := getAttributeType(dev.Info.DevType, dev.RotationRate, dev.SmartAttributes.Table) + for _, attr := range dev.SmartAttributes.Table { + out = append( + out, attribute{ + Name: cutPrefix(dev.Info.Name), + DeviceType: t, + ID: attr.ID, + Attrname: attr.Attrname, + Thresh: attr.Thresh, + }) } + } - jsonArray, err = json.Marshal(out) - if err != nil { - return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err) + jsonArray, err = json.Marshal(out) + if err != nil { + return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err) + } + + return +} + +// setSingleDiskFields goes through provided device json data and sets required output fields. +// It returns an error if there is an issue with unmarshal for the provided input JSON map. +func setSingleDiskFields(dev []byte) (out map[string]interface{}, err error) { + attr := make(map[string]interface{}) + if err = json.Unmarshal(dev, &attr); err != nil { + return out, zbxerr.ErrorCannotUnmarshalJSON.Wrap(err) + } + + var sd singleDevice + if err = json.Unmarshal(dev, &sd); err != nil { + return out, zbxerr.ErrorCannotUnmarshalJSON.Wrap(err) + } + + diskType := getType(getTypeFromJson(attr), getRateFromJson(attr), getTablesFromJson(attr)) + + out = map[string]interface{}{} + out["disk_type"] = diskType + out["firmware_version"] = sd.Firmware + out["model_name"] = sd.ModelName + out["serial_number"] = sd.SerialNumber + out["exit_status"] = sd.Smartctl.ExitStatus + + var errors []string + for _, msg := range sd.Smartctl.Messages { + errors = append(errors, msg.Str) + } + + out["error"] = strings.Join(errors, ", ") + out["self_test_passed"] = setSelfTest(sd) + + if diskType == nvmeType { + out["temperature"] = sd.HealthLog.Temperature + out["power_on_time"] = sd.HealthLog.PowerOnTime + out["critical_warning"] = sd.HealthLog.CriticalWarning + out["media_errors"] = sd.HealthLog.MediaErrors + out["percentage_used"] = sd.HealthLog.Percentage_used + } else { + out["temperature"] = sd.Temperature.Current + out["power_on_time"] = sd.PowerOnTime.Hours + out["critical_warning"] = 0 + out["media_errors"] = 0 + out["percentage_used"] = 0 + } + + for _, a := range sd.SmartAttributes.Table { + if a.Name == unknownAttrName { + continue } - default: - return nil, zbxerr.ErrorUnsupportedMetric + out[strings.ToLower(a.Name)] = singleRequestAttribute{a.Raw.Value, a.Raw.Str} } - return string(jsonArray), nil + return +} + +// setSelfTest determines if device is self test capable and if the test is passed. +func setSelfTest(sd singleDevice) *bool { + if sd.Data.Capabilities.SelfTestsSupported { + return &sd.Data.SelfTest.Status.Passed + } + + return nil } // setDiskFields goes through provided device json map and sets disk_name @@ -242,6 +390,18 @@ func getAttributeType(devType string, rate int, tables []table) string { return getTypeByRateAndAttr(rate, tables) } +func getAttributes(in deviceParser) (out string) { + for _, table := range in.SmartAttributes.Table { + if table.Attrname == unknownAttrName { + continue + } + + out = out + " " + table.Attrname + } + + return strings.TrimSpace(out) +} + func getType(devType string, rate int, tables []table) string { switch devType { case nvmeType: diff --git a/src/go/plugins/smart/smart_nix.go b/src/go/plugins/smart/smart_nix.go index 447406c1d3b..5e87a39de9c 100644 --- a/src/go/plugins/smart/smart_nix.go +++ b/src/go/plugins/smart/smart_nix.go @@ -39,7 +39,7 @@ func (p *Plugin) executeSmartctl(args string, strict bool) ([]byte, error) { var out string var err error - executable := fmt.Sprintf("sudo %s %s", path, args) + executable := fmt.Sprintf("sudo -n %s %s", path, args) p.Tracef("executing smartctl command: %s", executable) diff --git a/src/go/plugins/smart/smart_test.go b/src/go/plugins/smart/smart_test.go index eb73c16c8c6..bce0bc96f34 100644 --- a/src/go/plugins/smart/smart_test.go +++ b/src/go/plugins/smart/smart_test.go @@ -25,20 +25,390 @@ import ( "testing" ) +const ( + nvme = `{ + "smartctl": { + "exit_status": 0 + }, + "device": { + "name": "/dev/nvme0", + "type": "nvme" + }, + "model_name": "INTEL SSDPEKNW512G8H", + "serial_number": "BTNH115603K7512A", + "firmware_version": "HPS1", + "smart_status": { + "passed": true + }, + "nvme_smart_health_information_log": { + "critical_warning": 0, + "temperature": 25, + "percentage_used": 0, + "power_on_hours": 2222, + "media_errors": 0 + } + }` + + hdd = `{ + "json_format_version": [ + 1, + 0 + ], + "smartctl": { + "version": [ + 7, + 2 + ], + "svn_revision": "5155", + "platform_info": "x86_64-linux-5.13.0-30-generic", + "build_info": "(local build)", + "argv": [ + "smartctl", + "-a", + "-j", + "/dev/sda" + ], + "exit_status": 0 + }, + "device": { + "name": "/dev/sda", + "info_name": "/dev/sda [SAT]", + "type": "sat", + "protocol": "ATA" + }, + "model_family": "Seagate Surveillance", + "model_name": "ST1000VX000-1ES162", + "serial_number": "Z4Y7SJBD", + "wwn": { + "naa": 5, + "oui": 3152, + "id": 2071267458 + }, + "firmware_version": "CV26", + "rotation_rate": 7200, + "ata_smart_data": { + "self_test": { + "status": { + "value": 0, + "string": "completed without error", + "passed": true + } + }, + "capabilities": { + "self_tests_supported": true + } + }, + "ata_smart_attributes": { + "table": [ + { + "id": 1, + "name": "Raw_Read_Error_Rate", + "raw": { + "value": 182786912, + "string": "182786912" + } + }, + { + "id": 3, + "name": "Spin_Up_Time", + "raw": { + "value": 0, + "string": "0" + } + } + ] + }, + "power_on_time": { + "hours": 39153 + }, + "temperature": { + "current": 30 + } + }` + + ssd = ` + { + "json_format_version": [ + 1, + 0 + ], + "smartctl": { + "exit_status": 0 + }, + "device": { + "name": "/dev/sda", + "info_name": "/dev/sda", + "type": "ata", + "protocol": "ATA" + }, + "model_name": "TS128GMTS800", + "serial_number": "D486530350", + "firmware_version": "O1225G", + "rotation_rate": 0, + "smart_status": { + "passed": true + }, + "ata_smart_data": { + "self_test": { + "status": { + "passed": true + } + }, + "capabilities": { + "values": [ + 113, + 2 + ], + "self_tests_supported": true + } + }, + "ata_smart_attributes": { + "table": [ + { + "name": "Raw_Read_Error_Rate", + "value": 100, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "name": "Reallocated_Sector_Ct", + "raw": { + "value": 10, + "string": "10" + } + } + ] + }, + "power_on_time": { + "hours": 732 + }, + "temperature": { + "current": 18 + } + }` + + ssdUnknown = ` + { + "json_format_version": [ + 1, + 0 + ], + "smartctl": { + "exit_status": 0 + }, + "device": { + "name": "/dev/sda", + "info_name": "/dev/sda", + "type": "ata", + "protocol": "ATA" + }, + "model_name": "TS128GMTS800", + "serial_number": "D486530350", + "firmware_version": "O1225G", + "rotation_rate": 0, + "smart_status": { + "passed": true + }, + "ata_smart_data": { + "self_test": { + "status": { + "passed": true + } + }, + "capabilities": { + "values": [ + 113, + 2 + ], + "self_tests_supported": true + } + }, + "ata_smart_attributes": { + "table": [ + { + "name": "Raw_Read_Error_Rate", + "value": 100, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "name": "Unknown_Attribute", + "value": 0, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "name": "Reallocated_Sector_Ct", + "raw": { + "value": 10, + "string": "10" + } + } + ] + }, + "power_on_time": { + "hours": 732 + }, + "temperature": { + "current": 18 + } + }` +) + var ( table1 = table{"test1", 1, 11} table2 = table{"test2", 2, 22} table3 = table{"test3", 3, 33} table4 = table{"test4", 4, 44} attrTable = table{"Spin_Up_Time", 5, 55} + unknown = table{"Unknown_Attribute", 0, 0} ) +func Test_setSingleDiskFields(t *testing.T) { + var nilReference *bool + + selftestSuccess := true + + type args struct { + dev []byte + } + tests := []struct { + name string + args args + wantOut map[string]interface{} + wantErr bool + }{ + { + "nvme_device", + args{[]byte(nvme)}, + map[string]interface{}{ + "critical_warning": 0, + "disk_type": "nvme", + "error": "", + "exit_status": 0, + "firmware_version": "HPS1", + "media_errors": 0, + "model_name": "INTEL SSDPEKNW512G8H", + "percentage_used": 0, + "power_on_time": 2222, + "self_test_passed": nilReference, + "serial_number": "BTNH115603K7512A", + "temperature": 25, + }, + false, + }, + { + "hdd_device", + args{[]byte(hdd)}, + map[string]interface{}{ + "critical_warning": 0, + "disk_type": "hdd", + "error": "", + "exit_status": 0, + "firmware_version": "CV26", + "media_errors": 0, + "model_name": "ST1000VX000-1ES162", + "percentage_used": 0, + "power_on_time": 39153, + "self_test_passed": &selftestSuccess, + "serial_number": "Z4Y7SJBD", + "temperature": 30, + "raw_read_error_rate": singleRequestAttribute{ + Value: 182786912, + Raw: "182786912", + }, + "spin_up_time": singleRequestAttribute{ + Value: 0, + Raw: "0", + }, + }, + false, + }, + { + "ssd_device", + args{[]byte(ssd)}, + map[string]interface{}{ + "critical_warning": 0, + "disk_type": "ssd", + "error": "", + "exit_status": 0, + "firmware_version": "O1225G", + "media_errors": 0, + "model_name": "TS128GMTS800", + "percentage_used": 0, + "power_on_time": 732, + "self_test_passed": &selftestSuccess, + "serial_number": "D486530350", + "temperature": 18, + "raw_read_error_rate": singleRequestAttribute{ + Value: 0, + Raw: "0", + }, + "reallocated_sector_ct": singleRequestAttribute{ + Value: 10, + Raw: "10", + }, + }, + false, + }, + { + "ssd_device_with_unknown_attribute", + args{[]byte(ssdUnknown)}, + map[string]interface{}{ + "critical_warning": 0, + "disk_type": "ssd", + "error": "", + "exit_status": 0, + "firmware_version": "O1225G", + "media_errors": 0, + "model_name": "TS128GMTS800", + "percentage_used": 0, + "power_on_time": 732, + "self_test_passed": &selftestSuccess, + "serial_number": "D486530350", + "temperature": 18, + "raw_read_error_rate": singleRequestAttribute{ + Value: 0, + Raw: "0", + }, + "reallocated_sector_ct": singleRequestAttribute{ + Value: 10, + Raw: "10", + }, + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotOut, err := setSingleDiskFields(tt.args.dev) + if (err != nil) != tt.wantErr { + t.Errorf("setSingleDiskFields() error = %v, wantErr %v", err, tt.wantErr) + + return + } + + if !reflect.DeepEqual(gotOut, tt.wantOut) { + t.Errorf("setSingleDiskFields() = %v, want %v", gotOut, tt.wantOut) + } + }) + } +} + func Test_setDiskFields(t *testing.T) { - //nolint:lll - jsonSdaStr := `{"device": {"name": "/dev/sda","info_name": "/dev/sda [SAT]","type": "sat","protocol": "ATA"},"rotation_rate": 0}` - //nolint:lll + jsonSdaStr := `{ + "device": {"name": "/dev/sda","info_name": "/dev/sda [SAT]","type": "sat","protocol": "ATA"},"rotation_rate": 0 + }` sdaOutStr := map[string]interface{}{ - "device": map[string]interface{}{"name": "/dev/sda", "info_name": "/dev/sda [SAT]", "type": "sat", "protocol": "ATA"}, + "device": map[string]interface{}{ + "name": "/dev/sda", "info_name": "/dev/sda [SAT]", "type": "sat", "protocol": "ATA", + }, "disk_name": "sda", "disk_type": "ssd", "rotation_rate": 0, } @@ -196,6 +566,50 @@ func Test_getAttributeType(t *testing.T) { } } +func Test_getAttributes(t *testing.T) { + type args struct { + in deviceParser + } + tests := []struct { + name string + args args + want string + }{ + { + "attributes_set", + args{deviceParser{SmartAttributes: smartAttributes{Table: []table{table1, table2}}}}, + "test1 test2", + }, + { + "attributes_table_empty", + args{deviceParser{SmartAttributes: smartAttributes{Table: []table{}}}}, + "", + }, + { + "unknown_attributes_table_empty", + args{deviceParser{SmartAttributes: smartAttributes{Table: []table{table1, unknown, table2}}}}, + "test1 test2", + }, + { + "attributes_missing", + args{deviceParser{}}, + "", + }, + { + "parser_missing", + args{}, + "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getAttributes(tt.args.in); got != tt.want { + t.Errorf("getAttributes() = %v, want %v", got, tt.want) + } + }) + } +} + func Test_getType(t *testing.T) { type args struct { devType string diff --git a/src/go/plugins/smart/smartfs.go b/src/go/plugins/smart/smartfs.go index a01b87c4422..b695e853ba4 100644 --- a/src/go/plugins/smart/smartfs.go +++ b/src/go/plugins/smart/smartfs.go @@ -42,7 +42,8 @@ const ( ssdType = "ssd" hddType = "hdd" - spinUpAttrName = "Spin_Up_Time" + spinUpAttrName = "Spin_Up_Time" + unknownAttrName = "Unknown_Attribute" ataSmartAttrFieldName = "ata_smart_attributes" ataSmartAttrTableFieldName = "table" @@ -68,12 +69,79 @@ type device struct { DeviceType string `json:"{#DISKTYPE}"` Model string `json:"{#MODEL}"` SerialNumber string `json:"{#SN}"` + Path string `json:"{#PATH}"` + RaidType string `json:"{#RAIDTYPE}"` + Attributes string `json:"{#ATTRIBUTES}"` } type jsonDevice struct { serialNumber string jsonData string } +type singleDevice struct { + DiskType string `json:"disk_type"` + Firmware string `json:"firmware_version"` + ModelName string `json:"model_name"` + SerialNumber string `json:"serial_number"` + Smartctl smartctlField `json:"smartctl"` + HealthLog healthLog `json:"nvme_smart_health_information_log"` + SmartAttributes singelRequestTables `json:"ata_smart_attributes"` + Data ataData `json:"ata_smart_data"` + Temperature temperature `json:"temperature"` + PowerOnTime power `json:"power_on_time"` + Err string `json:"-"` + SelfTest bool `json:"-"` +} + +type healthLog struct { + Temperature int `json:"temperature"` + PowerOnTime int `json:"power_on_hours"` + CriticalWarning int `json:"critical_warning"` + MediaErrors int `json:"media_errors"` + Percentage_used int `json:"percentage_used"` +} + +type temperature struct { + Current int `json:"current"` +} + +type power struct { + Hours int `json:"hours"` +} + +type singelRequestTables struct { + Table []singelRequestRaw `json:"table"` +} + +type singelRequestRaw struct { + Name string `json:"name"` + Raw rawField `json:"raw"` +} + +type singleRequestAttribute struct { + Value int `json:"value"` + Raw string `json:"raw"` +} + +type rawField struct { + Value int `json:"value"` + Str string `json:"string"` +} + +type ataData struct { + SelfTest selfTest `json:"self_test"` + Capabilities capabilities `json:"capabilities"` +} +type capabilities struct { + SelfTestsSupported bool `json:"self_tests_supported"` +} +type selfTest struct { + Status status `json:"status"` +} +type status struct { + Passed bool `json:"passed"` +} + type attribute struct { Name string `json:"{#NAME}"` DeviceType string `json:"{#DISKTYPE}"` @@ -96,6 +164,8 @@ type deviceInfo struct { Name string `json:"name"` InfoName string `json:"info_name"` DevType string `json:"type"` + name string `json:"-"` + raidType string `json:"-"` } type smartctl struct { @@ -184,7 +254,17 @@ func (p *Plugin) execute(jsonRunner bool) (*runner, error) { return r, err } -//executeBase executed runners for basic devices retrived from smartctl +// executeSingle returns device data for single device from smartctl based on provided path. +func (p *Plugin) executeSingle(path string) (device []byte, err error) { + device, err = p.executeSmartctl(fmt.Sprintf("-a %s -j", path), false) + if err != nil { + return nil, fmt.Errorf("Failed to execute smartctl: %w.", err) + } + + return +} + +//executeBase executed runners for basic devices retrieved from smartctl. func (r *runner) executeBase(basicDev []deviceInfo, jsonRunner bool) error { r.startBasicRunners(jsonRunner) @@ -197,7 +277,7 @@ func (r *runner) executeBase(basicDev []deviceInfo, jsonRunner bool) error { return r.waitForExecution() } -//executeRaids executes runners for raid devices (except megaraid) retrived from smartctl +//executeRaids executes runners for raid devices (except megaraid) retrieved from smartctl func (r *runner) executeRaids(raids []deviceInfo, jsonRunner bool) { raidTypes := []string{"3ware", "areca", "cciss", "sat"} @@ -216,7 +296,7 @@ func (r *runner) executeRaids(raids []deviceInfo, jsonRunner bool) { r.waitForRaidExecution(r.raidDone) } -//executeMegaRaids executes runners for megaraid devices retrived from smartctl +//executeMegaRaids executes runners for megaraid devices retrieved from smartctl func (r *runner) executeMegaRaids(megaraids []deviceInfo, jsonRunner bool) { r.megaraids = make(chan raidParameters, len(megaraids)) @@ -381,6 +461,8 @@ func (r *runner) getBasicDevices(jsonRunner bool) { return } + dp.Info.name = name + r.mux.Lock() if jsonRunner { @@ -454,10 +536,14 @@ runner: } if dp.SmartStatus != nil { + dp.Info.name = raid.name + if raid.rType == satType { dp.Info.Name = fmt.Sprintf("%s %s", raid.name, raid.rType) + dp.Info.raidType = raid.rType } else { dp.Info.Name = fmt.Sprintf("%s %s,%d", raid.name, raid.rType, i) + dp.Info.raidType = fmt.Sprintf("%s,%d", raid.rType, i) } if r.setRaidDevices(dp, device, raid.rType, jsonRunner) { @@ -523,6 +609,8 @@ func (r *runner) getMegaRaidDevices(jsonRunner bool) { } dp.Info.Name = fmt.Sprintf("%s %s", raid.name, raid.rType) + dp.Info.name = raid.name + dp.Info.raidType = raid.rType r.setRaidDevices(dp, device, raid.rType, jsonRunner) } @@ -619,32 +707,41 @@ func (dp *deviceParser) checkErr() (err error) { func (p *Plugin) getDevices() (basic, raid, megaraid []deviceInfo, err error) { basicTmp, err := p.scanDevices("--scan -j") if err != nil { - return nil, nil, nil, fmt.Errorf("Failed to scan for devices: %s.", err) + return nil, nil, nil, fmt.Errorf("Failed to scan for devices: %w.", err) } raidTmp, err := p.scanDevices("--scan -d sat -j") if err != nil { - return nil, nil, nil, fmt.Errorf("Failed to scan for sat devices: %s.", err) + return nil, nil, nil, fmt.Errorf("Failed to scan for sat devices: %w.", err) } -raid: - for _, tmp := range basicTmp { - for _, r := range raidTmp { + basic, raid, megaraid = formatDeviceOutput(basicTmp, raidTmp) + + return +} + +// formatDeviceOutput removes raid devices from basic device list and separates megaraid devices from the rest of raid +// devices. +func formatDeviceOutput(basic, raid []deviceInfo) (basicDev, raidDev, megaraidDev []deviceInfo) { +loop: + for _, tmp := range basic { + for _, r := range raid { if tmp.Name == r.Name { - continue raid + continue loop } } - basic = append(basic, tmp) + basicDev = append(basicDev, tmp) } - for _, r := range raidTmp { + for _, r := range raid { if strings.Contains(r.DevType, "megaraid") { - megaraid = append(megaraid, r) + megaraidDev = append(megaraidDev, r) + continue } - raid = append(raid, r) + raidDev = append(raidDev, r) } return |