136 lines
3.1 KiB
Go
136 lines
3.1 KiB
Go
|
//go:generate ../../../tools/readme_config_includer/generator
|
||
|
//go:build linux
|
||
|
|
||
|
package sensors
|
||
|
|
||
|
import (
|
||
|
_ "embed"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"os/exec"
|
||
|
"regexp"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"github.com/influxdata/telegraf"
|
||
|
"github.com/influxdata/telegraf/config"
|
||
|
"github.com/influxdata/telegraf/internal"
|
||
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||
|
)
|
||
|
|
||
|
//go:embed sample.conf
|
||
|
var sampleConfig string
|
||
|
|
||
|
var (
|
||
|
execCommand = exec.Command // execCommand is used to mock commands in tests.
|
||
|
numberRegp = regexp.MustCompile("[0-9]+")
|
||
|
defaultTimeout = config.Duration(5 * time.Second)
|
||
|
)
|
||
|
|
||
|
const cmd = "sensors"
|
||
|
|
||
|
type Sensors struct {
|
||
|
RemoveNumbers bool `toml:"remove_numbers"`
|
||
|
Timeout config.Duration `toml:"timeout"`
|
||
|
path string
|
||
|
}
|
||
|
|
||
|
func (*Sensors) SampleConfig() string {
|
||
|
return sampleConfig
|
||
|
}
|
||
|
|
||
|
func (s *Sensors) Init() error {
|
||
|
// Set defaults
|
||
|
if s.path == "" {
|
||
|
path, err := exec.LookPath(cmd)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("looking up %q failed: %w", cmd, err)
|
||
|
}
|
||
|
s.path = path
|
||
|
}
|
||
|
|
||
|
// Check parameters
|
||
|
if s.path == "" {
|
||
|
return fmt.Errorf("no path specified for %q", cmd)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (s *Sensors) Gather(acc telegraf.Accumulator) error {
|
||
|
if len(s.path) == 0 {
|
||
|
return errors.New("sensors not found: verify that lm-sensors package is installed and that sensors is in your PATH")
|
||
|
}
|
||
|
|
||
|
return s.parse(acc)
|
||
|
}
|
||
|
|
||
|
// parse forks the command:
|
||
|
//
|
||
|
// sensors -u -A
|
||
|
//
|
||
|
// and parses the output to add it to the telegraf.Accumulator.
|
||
|
func (s *Sensors) parse(acc telegraf.Accumulator) error {
|
||
|
tags := make(map[string]string)
|
||
|
fields := make(map[string]interface{})
|
||
|
chip := ""
|
||
|
cmd := execCommand(s.path, "-A", "-u")
|
||
|
out, err := internal.StdOutputTimeout(cmd, time.Duration(s.Timeout))
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to run command %q: %w - %s", strings.Join(cmd.Args, " "), err, string(out))
|
||
|
}
|
||
|
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
|
||
|
for _, line := range lines {
|
||
|
if len(line) == 0 {
|
||
|
acc.AddFields("sensors", fields, tags)
|
||
|
chip = ""
|
||
|
tags = make(map[string]string)
|
||
|
fields = make(map[string]interface{})
|
||
|
continue
|
||
|
}
|
||
|
if len(chip) == 0 {
|
||
|
chip = line
|
||
|
tags["chip"] = chip
|
||
|
continue
|
||
|
}
|
||
|
if !strings.HasPrefix(line, " ") {
|
||
|
if len(tags) > 1 {
|
||
|
acc.AddFields("sensors", fields, tags)
|
||
|
}
|
||
|
fields = make(map[string]interface{})
|
||
|
tags = map[string]string{
|
||
|
"chip": chip,
|
||
|
"feature": strings.TrimRight(snake(line), ":"),
|
||
|
}
|
||
|
} else {
|
||
|
splitted := strings.Split(line, ":")
|
||
|
fieldName := strings.TrimSpace(splitted[0])
|
||
|
if s.RemoveNumbers {
|
||
|
fieldName = numberRegp.ReplaceAllString(fieldName, "")
|
||
|
}
|
||
|
fieldValue, err := strconv.ParseFloat(strings.TrimSpace(splitted[1]), 64)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
fields[fieldName] = fieldValue
|
||
|
}
|
||
|
}
|
||
|
acc.AddFields("sensors", fields, tags)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// snake converts string to snake case
|
||
|
func snake(input string) string {
|
||
|
return strings.ToLower(strings.ReplaceAll(strings.TrimSpace(input), " ", "_"))
|
||
|
}
|
||
|
|
||
|
func init() {
|
||
|
inputs.Add("sensors", func() telegraf.Input {
|
||
|
return &Sensors{
|
||
|
RemoveNumbers: true,
|
||
|
Timeout: defaultTimeout,
|
||
|
}
|
||
|
})
|
||
|
}
|