2017-10-04 22:15:58 +00:00
|
|
|
package smart
|
|
|
|
|
|
|
|
import (
|
2018-04-02 20:55:10 +00:00
|
|
|
"bufio"
|
2017-10-04 22:15:58 +00:00
|
|
|
"fmt"
|
|
|
|
"os/exec"
|
2017-12-08 21:22:41 +00:00
|
|
|
"path"
|
2017-10-04 22:15:58 +00:00
|
|
|
"regexp"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"syscall"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/influxdata/telegraf"
|
|
|
|
"github.com/influxdata/telegraf/internal"
|
|
|
|
"github.com/influxdata/telegraf/plugins/inputs"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// Device Model: APPLE SSD SM256E
|
2019-05-07 22:20:03 +00:00
|
|
|
// Product: HUH721212AL5204
|
|
|
|
// Model Number: TS128GMTE850
|
|
|
|
modelInfo = regexp.MustCompile("^(Device Model|Product|Model Number):\\s+(.*)$")
|
2017-10-04 22:15:58 +00:00
|
|
|
// Serial Number: S0X5NZBC422720
|
2019-09-30 17:41:25 +00:00
|
|
|
serialInfo = regexp.MustCompile("(?i)^Serial Number:\\s+(.*)$")
|
2017-10-04 22:15:58 +00:00
|
|
|
// LU WWN Device Id: 5 002538 655584d30
|
2019-05-07 22:20:03 +00:00
|
|
|
wwnInfo = regexp.MustCompile("^LU WWN Device Id:\\s+(.*)$")
|
2017-10-04 22:15:58 +00:00
|
|
|
// User Capacity: 251,000,193,024 bytes [251 GB]
|
2019-05-07 22:20:03 +00:00
|
|
|
usercapacityInfo = regexp.MustCompile("^User Capacity:\\s+([0-9,]+)\\s+bytes.*$")
|
2017-10-04 22:15:58 +00:00
|
|
|
// SMART support is: Enabled
|
2019-05-07 22:20:03 +00:00
|
|
|
smartEnabledInfo = regexp.MustCompile("^SMART support is:\\s+(\\w+)$")
|
2017-10-04 22:15:58 +00:00
|
|
|
// SMART overall-health self-assessment test result: PASSED
|
2019-05-07 22:20:03 +00:00
|
|
|
// SMART Health Status: OK
|
2017-10-04 22:15:58 +00:00
|
|
|
// PASSED, FAILED, UNKNOWN
|
2019-05-07 22:20:03 +00:00
|
|
|
smartOverallHealth = regexp.MustCompile("^(SMART overall-health self-assessment test result|SMART Health Status):\\s+(\\w+).*$")
|
|
|
|
|
2019-07-12 21:25:45 +00:00
|
|
|
// sasNvmeAttr is a SAS or NVME SMART attribute
|
|
|
|
sasNvmeAttr = regexp.MustCompile(`^([^:]+):\s+(.+)$`)
|
2017-10-04 22:15:58 +00:00
|
|
|
|
|
|
|
// ID# ATTRIBUTE_NAME FLAGS VALUE WORST THRESH FAIL RAW_VALUE
|
|
|
|
// 1 Raw_Read_Error_Rate -O-RC- 200 200 000 - 0
|
|
|
|
// 5 Reallocated_Sector_Ct PO--CK 100 100 000 - 0
|
|
|
|
// 192 Power-Off_Retract_Count -O--C- 097 097 000 - 14716
|
2019-05-07 22:20:03 +00:00
|
|
|
attribute = regexp.MustCompile("^\\s*([0-9]+)\\s(\\S+)\\s+([-P][-O][-S][-R][-C][-K])\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9-]+)\\s+([-\\w]+)\\s+([\\w\\+\\.]+).*$")
|
2017-10-04 22:15:58 +00:00
|
|
|
|
|
|
|
deviceFieldIds = map[string]string{
|
|
|
|
"1": "read_error_rate",
|
|
|
|
"7": "seek_error_rate",
|
2019-05-07 22:20:03 +00:00
|
|
|
"190": "temp_c",
|
2017-10-04 22:15:58 +00:00
|
|
|
"194": "temp_c",
|
|
|
|
"199": "udma_crc_errors",
|
|
|
|
}
|
2019-07-12 21:25:45 +00:00
|
|
|
|
|
|
|
sasNvmeAttributes = map[string]struct {
|
|
|
|
ID string
|
|
|
|
Name string
|
|
|
|
Parse func(fields, deviceFields map[string]interface{}, str string) error
|
|
|
|
}{
|
|
|
|
"Accumulated start-stop cycles": {
|
|
|
|
ID: "4",
|
|
|
|
Name: "Start_Stop_Count",
|
|
|
|
},
|
|
|
|
"Accumulated load-unload cycles": {
|
|
|
|
ID: "193",
|
|
|
|
Name: "Load_Cycle_Count",
|
|
|
|
},
|
|
|
|
"Current Drive Temperature": {
|
|
|
|
ID: "194",
|
|
|
|
Name: "Temperature_Celsius",
|
|
|
|
Parse: parseTemperature,
|
|
|
|
},
|
|
|
|
"Temperature": {
|
|
|
|
ID: "194",
|
|
|
|
Name: "Temperature_Celsius",
|
|
|
|
Parse: parseTemperature,
|
|
|
|
},
|
|
|
|
"Power Cycles": {
|
|
|
|
ID: "12",
|
|
|
|
Name: "Power_Cycle_Count",
|
|
|
|
},
|
|
|
|
"Power On Hours": {
|
|
|
|
ID: "9",
|
|
|
|
Name: "Power_On_Hours",
|
|
|
|
},
|
|
|
|
"Media and Data Integrity Errors": {
|
|
|
|
Name: "Media_and_Data_Integrity_Errors",
|
|
|
|
},
|
|
|
|
"Error Information Log Entries": {
|
|
|
|
Name: "Error_Information_Log_Entries",
|
|
|
|
},
|
|
|
|
"Critical Warning": {
|
|
|
|
Name: "Critical_Warning",
|
|
|
|
Parse: func(fields, _ map[string]interface{}, str string) error {
|
|
|
|
var value int64
|
|
|
|
if _, err := fmt.Sscanf(str, "0x%x", &value); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
fields["raw_value"] = value
|
|
|
|
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"Available Spare": {
|
|
|
|
Name: "Available_Spare",
|
|
|
|
Parse: func(fields, deviceFields map[string]interface{}, str string) error {
|
2020-05-15 22:43:32 +00:00
|
|
|
return parseCommaSeparatedInt(fields, deviceFields, strings.TrimSuffix(str, "%"))
|
2019-07-12 21:25:45 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2017-10-04 22:15:58 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type Smart struct {
|
|
|
|
Path string
|
|
|
|
Nocheck string
|
|
|
|
Attributes bool
|
|
|
|
Excludes []string
|
|
|
|
Devices []string
|
|
|
|
UseSudo bool
|
2019-08-13 17:24:44 +00:00
|
|
|
Timeout internal.Duration
|
2017-10-04 22:15:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var sampleConfig = `
|
|
|
|
## Optionally specify the path to the smartctl executable
|
|
|
|
# path = "/usr/bin/smartctl"
|
2019-05-07 22:20:03 +00:00
|
|
|
|
2017-10-04 22:15:58 +00:00
|
|
|
## On most platforms smartctl requires root access.
|
|
|
|
## Setting 'use_sudo' to true will make use of sudo to run smartctl.
|
|
|
|
## Sudo must be configured to to allow the telegraf user to run smartctl
|
2019-05-07 22:20:03 +00:00
|
|
|
## without a password.
|
2017-10-04 22:15:58 +00:00
|
|
|
# use_sudo = false
|
2019-05-07 22:20:03 +00:00
|
|
|
|
2017-10-04 22:15:58 +00:00
|
|
|
## Skip checking disks in this power mode. Defaults to
|
|
|
|
## "standby" to not wake up disks that have stoped rotating.
|
|
|
|
## See --nocheck in the man pages for smartctl.
|
|
|
|
## smartctl version 5.41 and 5.42 have faulty detection of
|
|
|
|
## power mode and might require changing this value to
|
|
|
|
## "never" depending on your disks.
|
|
|
|
# nocheck = "standby"
|
2019-05-07 22:20:03 +00:00
|
|
|
|
2019-08-02 17:48:40 +00:00
|
|
|
## Gather all returned S.M.A.R.T. attribute metrics and the detailed
|
|
|
|
## information from each drive into the 'smart_attribute' measurement.
|
2017-10-04 22:15:58 +00:00
|
|
|
# attributes = false
|
2019-05-07 22:20:03 +00:00
|
|
|
|
2017-10-04 22:15:58 +00:00
|
|
|
## Optionally specify devices to exclude from reporting.
|
|
|
|
# excludes = [ "/dev/pass6" ]
|
2019-05-07 22:20:03 +00:00
|
|
|
|
2017-10-04 22:15:58 +00:00
|
|
|
## Optionally specify devices and device type, if unset
|
|
|
|
## a scan (smartctl --scan) for S.M.A.R.T. devices will
|
|
|
|
## done and all found will be included except for the
|
|
|
|
## excluded in excludes.
|
|
|
|
# devices = [ "/dev/ada0 -d atacam" ]
|
2019-08-13 17:24:44 +00:00
|
|
|
|
|
|
|
## Timeout for the smartctl command to complete.
|
|
|
|
# timeout = "30s"
|
2017-10-04 22:15:58 +00:00
|
|
|
`
|
|
|
|
|
2019-08-13 17:24:44 +00:00
|
|
|
func NewSmart() *Smart {
|
|
|
|
return &Smart{
|
|
|
|
Timeout: internal.Duration{Duration: time.Second * 30},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-04 22:15:58 +00:00
|
|
|
func (m *Smart) SampleConfig() string {
|
|
|
|
return sampleConfig
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Smart) Description() string {
|
|
|
|
return "Read metrics from storage devices supporting S.M.A.R.T."
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Smart) Gather(acc telegraf.Accumulator) error {
|
|
|
|
if len(m.Path) == 0 {
|
|
|
|
return fmt.Errorf("smartctl not found: verify that smartctl is installed and that smartctl is in your PATH")
|
|
|
|
}
|
|
|
|
|
|
|
|
devices := m.Devices
|
|
|
|
if len(devices) == 0 {
|
|
|
|
var err error
|
|
|
|
devices, err = m.scan()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m.getAttributes(acc, devices)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wrap with sudo
|
2019-08-13 17:24:44 +00:00
|
|
|
var runCmd = func(timeout internal.Duration, sudo bool, command string, args ...string) ([]byte, error) {
|
2019-05-07 22:20:03 +00:00
|
|
|
cmd := exec.Command(command, args...)
|
2017-10-04 22:15:58 +00:00
|
|
|
if sudo {
|
2019-05-07 22:20:03 +00:00
|
|
|
cmd = exec.Command("sudo", append([]string{"-n", command}, args...)...)
|
2017-10-04 22:15:58 +00:00
|
|
|
}
|
2019-08-13 17:24:44 +00:00
|
|
|
return internal.CombinedOutputTimeout(cmd, timeout.Duration)
|
2017-10-04 22:15:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Scan for S.M.A.R.T. devices
|
|
|
|
func (m *Smart) scan() ([]string, error) {
|
2019-08-13 17:24:44 +00:00
|
|
|
out, err := runCmd(m.Timeout, m.UseSudo, m.Path, "--scan")
|
2017-10-04 22:15:58 +00:00
|
|
|
if err != nil {
|
2019-05-07 22:20:03 +00:00
|
|
|
return []string{}, fmt.Errorf("failed to run command '%s --scan': %s - %s", m.Path, err, string(out))
|
2017-10-04 22:15:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
devices := []string{}
|
|
|
|
for _, line := range strings.Split(string(out), "\n") {
|
2017-12-09 02:03:12 +00:00
|
|
|
dev := strings.Split(line, " ")
|
2017-10-04 22:15:58 +00:00
|
|
|
if len(dev) > 1 && !excludedDev(m.Excludes, strings.TrimSpace(dev[0])) {
|
|
|
|
devices = append(devices, strings.TrimSpace(dev[0]))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return devices, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func excludedDev(excludes []string, deviceLine string) bool {
|
|
|
|
device := strings.Split(deviceLine, " ")
|
|
|
|
if len(device) != 0 {
|
|
|
|
for _, exclude := range excludes {
|
|
|
|
if device[0] == exclude {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get info and attributes for each S.M.A.R.T. device
|
|
|
|
func (m *Smart) getAttributes(acc telegraf.Accumulator, devices []string) {
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
wg.Add(len(devices))
|
|
|
|
|
|
|
|
for _, device := range devices {
|
2019-08-13 17:24:44 +00:00
|
|
|
go gatherDisk(acc, m.Timeout, m.UseSudo, m.Attributes, m.Path, m.Nocheck, device, &wg)
|
2017-10-04 22:15:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
wg.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Command line parse errors are denoted by the exit code having the 0 bit set.
|
|
|
|
// All other errors are drive/communication errors and should be ignored.
|
|
|
|
func exitStatus(err error) (int, error) {
|
|
|
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
|
|
|
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
|
|
|
return status.ExitStatus(), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
2019-08-13 17:24:44 +00:00
|
|
|
func gatherDisk(acc telegraf.Accumulator, timeout internal.Duration, usesudo, collectAttributes bool, smartctl, nocheck, device string, wg *sync.WaitGroup) {
|
2017-10-04 22:15:58 +00:00
|
|
|
defer wg.Done()
|
|
|
|
// smartctl 5.41 & 5.42 have are broken regarding handling of --nocheck/-n
|
2019-05-07 22:20:03 +00:00
|
|
|
args := []string{"--info", "--health", "--attributes", "--tolerance=verypermissive", "-n", nocheck, "--format=brief"}
|
2017-10-04 22:15:58 +00:00
|
|
|
args = append(args, strings.Split(device, " ")...)
|
2019-08-13 17:24:44 +00:00
|
|
|
out, e := runCmd(timeout, usesudo, smartctl, args...)
|
2017-10-04 22:15:58 +00:00
|
|
|
outStr := string(out)
|
|
|
|
|
|
|
|
// Ignore all exit statuses except if it is a command line parse error
|
|
|
|
exitStatus, er := exitStatus(e)
|
|
|
|
if er != nil {
|
2019-05-07 22:20:03 +00:00
|
|
|
acc.AddError(fmt.Errorf("failed to run command '%s %s': %s - %s", smartctl, strings.Join(args, " "), e, outStr))
|
2017-10-04 22:15:58 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-05-07 22:20:03 +00:00
|
|
|
deviceTags := map[string]string{}
|
|
|
|
deviceNode := strings.Split(device, " ")[0]
|
|
|
|
deviceTags["device"] = path.Base(deviceNode)
|
|
|
|
deviceFields := make(map[string]interface{})
|
|
|
|
deviceFields["exit_status"] = exitStatus
|
|
|
|
|
2018-04-02 20:55:10 +00:00
|
|
|
scanner := bufio.NewScanner(strings.NewReader(outStr))
|
|
|
|
|
|
|
|
for scanner.Scan() {
|
|
|
|
line := scanner.Text()
|
2017-10-04 22:15:58 +00:00
|
|
|
|
2019-05-07 22:20:03 +00:00
|
|
|
model := modelInfo.FindStringSubmatch(line)
|
|
|
|
if len(model) > 2 {
|
|
|
|
deviceTags["model"] = model[2]
|
2017-10-04 22:15:58 +00:00
|
|
|
}
|
|
|
|
|
2019-05-07 22:20:03 +00:00
|
|
|
serial := serialInfo.FindStringSubmatch(line)
|
2017-10-04 22:15:58 +00:00
|
|
|
if len(serial) > 1 {
|
2019-05-07 22:20:03 +00:00
|
|
|
deviceTags["serial_no"] = serial[1]
|
2017-10-04 22:15:58 +00:00
|
|
|
}
|
|
|
|
|
2019-05-07 22:20:03 +00:00
|
|
|
wwn := wwnInfo.FindStringSubmatch(line)
|
2017-10-04 22:15:58 +00:00
|
|
|
if len(wwn) > 1 {
|
2019-05-07 22:20:03 +00:00
|
|
|
deviceTags["wwn"] = strings.Replace(wwn[1], " ", "", -1)
|
2017-10-04 22:15:58 +00:00
|
|
|
}
|
|
|
|
|
2019-05-07 22:20:03 +00:00
|
|
|
capacity := usercapacityInfo.FindStringSubmatch(line)
|
2017-10-04 22:15:58 +00:00
|
|
|
if len(capacity) > 1 {
|
2019-05-07 22:20:03 +00:00
|
|
|
deviceTags["capacity"] = strings.Replace(capacity[1], ",", "", -1)
|
2017-10-04 22:15:58 +00:00
|
|
|
}
|
|
|
|
|
2019-05-07 22:20:03 +00:00
|
|
|
enabled := smartEnabledInfo.FindStringSubmatch(line)
|
2017-10-04 22:15:58 +00:00
|
|
|
if len(enabled) > 1 {
|
2019-05-07 22:20:03 +00:00
|
|
|
deviceTags["enabled"] = enabled[1]
|
2017-10-04 22:15:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
health := smartOverallHealth.FindStringSubmatch(line)
|
2019-05-07 22:20:03 +00:00
|
|
|
if len(health) > 2 {
|
|
|
|
deviceFields["health_ok"] = (health[2] == "PASSED" || health[2] == "OK")
|
2017-10-04 22:15:58 +00:00
|
|
|
}
|
|
|
|
|
2019-05-07 22:20:03 +00:00
|
|
|
tags := map[string]string{}
|
|
|
|
fields := make(map[string]interface{})
|
2017-10-04 22:15:58 +00:00
|
|
|
|
2019-06-25 18:51:51 +00:00
|
|
|
if collectAttributes {
|
2019-08-06 00:36:34 +00:00
|
|
|
keys := [...]string{"device", "model", "serial_no", "wwn", "capacity", "enabled"}
|
|
|
|
for _, key := range keys {
|
|
|
|
if value, ok := deviceTags[key]; ok {
|
|
|
|
tags[key] = value
|
|
|
|
}
|
2019-06-25 18:51:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-07 22:20:03 +00:00
|
|
|
attr := attribute.FindStringSubmatch(line)
|
2017-10-04 22:15:58 +00:00
|
|
|
if len(attr) > 1 {
|
2020-01-15 01:05:28 +00:00
|
|
|
// attribute has been found, add it only if collectAttributes is true
|
2019-05-07 22:20:03 +00:00
|
|
|
if collectAttributes {
|
2017-10-04 22:15:58 +00:00
|
|
|
tags["id"] = attr[1]
|
|
|
|
tags["name"] = attr[2]
|
|
|
|
tags["flags"] = attr[3]
|
|
|
|
|
|
|
|
fields["exit_status"] = exitStatus
|
|
|
|
if i, err := strconv.ParseInt(attr[4], 10, 64); err == nil {
|
|
|
|
fields["value"] = i
|
|
|
|
}
|
|
|
|
if i, err := strconv.ParseInt(attr[5], 10, 64); err == nil {
|
|
|
|
fields["worst"] = i
|
|
|
|
}
|
|
|
|
if i, err := strconv.ParseInt(attr[6], 10, 64); err == nil {
|
|
|
|
fields["threshold"] = i
|
|
|
|
}
|
|
|
|
|
|
|
|
tags["fail"] = attr[7]
|
|
|
|
if val, err := parseRawValue(attr[8]); err == nil {
|
|
|
|
fields["raw_value"] = val
|
|
|
|
}
|
|
|
|
|
|
|
|
acc.AddFields("smart_attribute", fields, tags)
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the attribute matches on the one in deviceFieldIds
|
|
|
|
// save the raw value to a field.
|
|
|
|
if field, ok := deviceFieldIds[attr[1]]; ok {
|
|
|
|
if val, err := parseRawValue(attr[8]); err == nil {
|
2019-05-07 22:20:03 +00:00
|
|
|
deviceFields[field] = val
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2020-01-15 01:05:28 +00:00
|
|
|
// what was found is not a vendor attribute
|
|
|
|
if matches := sasNvmeAttr.FindStringSubmatch(line); len(matches) > 2 {
|
|
|
|
if attr, ok := sasNvmeAttributes[matches[1]]; ok {
|
|
|
|
tags["name"] = attr.Name
|
|
|
|
if attr.ID != "" {
|
|
|
|
tags["id"] = attr.ID
|
|
|
|
}
|
2019-07-12 21:25:45 +00:00
|
|
|
|
2020-05-15 22:43:32 +00:00
|
|
|
parse := parseCommaSeparatedInt
|
2020-01-15 01:05:28 +00:00
|
|
|
if attr.Parse != nil {
|
|
|
|
parse = attr.Parse
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := parse(fields, deviceFields, matches[2]); err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// if the field is classified as an attribute, only add it
|
|
|
|
// if collectAttributes is true
|
|
|
|
if collectAttributes {
|
2019-07-12 21:25:45 +00:00
|
|
|
acc.AddFields("smart_attribute", fields, tags)
|
2019-05-07 22:20:03 +00:00
|
|
|
}
|
2017-10-04 22:15:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-05-07 22:20:03 +00:00
|
|
|
acc.AddFields("smart_device", deviceFields, deviceTags)
|
2017-10-04 22:15:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func parseRawValue(rawVal string) (int64, error) {
|
|
|
|
// Integer
|
|
|
|
if i, err := strconv.ParseInt(rawVal, 10, 64); err == nil {
|
|
|
|
return i, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Duration: 65h+33m+09.259s
|
|
|
|
unit := regexp.MustCompile("^(.*)([hms])$")
|
|
|
|
parts := strings.Split(rawVal, "+")
|
|
|
|
if len(parts) == 0 {
|
|
|
|
return 0, fmt.Errorf("Couldn't parse RAW_VALUE '%s'", rawVal)
|
|
|
|
}
|
|
|
|
|
|
|
|
duration := int64(0)
|
|
|
|
for _, part := range parts {
|
|
|
|
timePart := unit.FindStringSubmatch(part)
|
|
|
|
if len(timePart) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
switch timePart[2] {
|
|
|
|
case "h":
|
|
|
|
duration += parseInt(timePart[1]) * int64(3600)
|
|
|
|
case "m":
|
|
|
|
duration += parseInt(timePart[1]) * int64(60)
|
|
|
|
case "s":
|
|
|
|
// drop fractions of seconds
|
|
|
|
duration += parseInt(strings.Split(timePart[1], ".")[0])
|
|
|
|
default:
|
|
|
|
// Unknown, ignore
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return duration, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseInt(str string) int64 {
|
|
|
|
if i, err := strconv.ParseInt(str, 10, 64); err == nil {
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2020-05-15 22:43:32 +00:00
|
|
|
func parseCommaSeparatedInt(fields, _ map[string]interface{}, str string) error {
|
2019-07-12 21:25:45 +00:00
|
|
|
i, err := strconv.ParseInt(strings.Replace(str, ",", "", -1), 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
fields["raw_value"] = i
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseTemperature(fields, deviceFields map[string]interface{}, str string) error {
|
|
|
|
var temp int64
|
|
|
|
if _, err := fmt.Sscanf(str, "%d C", &temp); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
fields["raw_value"] = temp
|
|
|
|
deviceFields["temp_c"] = temp
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-10-04 22:15:58 +00:00
|
|
|
func init() {
|
|
|
|
inputs.Add("smart", func() telegraf.Input {
|
2019-08-13 17:24:44 +00:00
|
|
|
m := NewSmart()
|
|
|
|
path, _ := exec.LookPath("smartctl")
|
|
|
|
if len(path) > 0 {
|
|
|
|
m.Path = path
|
|
|
|
}
|
|
|
|
m.Nocheck = "standby"
|
|
|
|
return m
|
2017-10-04 22:15:58 +00:00
|
|
|
})
|
|
|
|
}
|