From 1e7a75598e3678d1e1fabea76b295239d0d1ae76 Mon Sep 17 00:00:00 2001 From: Thomas Gruber Date: Thu, 25 Nov 2021 18:15:56 +0100 Subject: [PATCH] Add collector to read data from ipmitool or ipmi-sensors (fallback) (#9) * Add collector to read data from ipmitool or ipmi-sensors * Update IPMI collector to use own config * Add 'ipmitool sensor' parser --- collectors/README.md | 1 + collectors/ipmiMetric.go | 137 +++++++++++++++++++++++++++++++++++++++ metric-collector.go | 1 + 3 files changed, 139 insertions(+) create mode 100644 collectors/ipmiMetric.go diff --git a/collectors/README.md b/collectors/README.md index 364fa2d..b2f1837 100644 --- a/collectors/README.md +++ b/collectors/README.md @@ -40,6 +40,7 @@ The base class/configuration is located in `metricCollector.go`. * `cpustatMetric.go`: Read CPU specific values from `/proc/stat` * `topprocsMetric.go`: Reads the Top5 processes by their CPU usage * `nvidiaMetric.go`: Read data about Nvidia GPUs using the NVML library +* `ipmiMetric.go`: Collect data from `ipmitool` or as fallback `ipmi-sensors` If any of the collectors cannot be initialized, it is excluded from all further reads. Like if the Lustre stat file is not a valid path, no Lustre specific metrics will be recorded. diff --git a/collectors/ipmiMetric.go b/collectors/ipmiMetric.go new file mode 100644 index 0000000..bff2506 --- /dev/null +++ b/collectors/ipmiMetric.go @@ -0,0 +1,137 @@ +package collectors + +import ( + "errors" + lp "github.com/influxdata/line-protocol" + "log" + "strconv" + "strings" + "time" + "os" + "os/exec" + "encoding/json" +) + +const IPMITOOL_PATH = `/usr/bin/ipmitool` +const IPMISENSORS_PATH = `/usr/sbin/ipmi-sensors` + +type IpmiCollectorConfig struct { + ExcludeDevices []string `json:"exclude_devices"` + IpmitoolPath string `json:"ipmitool_path"` + IpmisensorsPath string `json:"ipmisensors_path"` +} + +type IpmiCollector struct { + MetricCollector + tags map[string]string + matches map[string]string + config IpmiCollectorConfig +} + +func (m *IpmiCollector) Init(config []byte) error { + m.name = "IpmiCollector" + m.setup() + if len(config) > 0 { + err := json.Unmarshal(config, &m.config) + if err != nil { + return err + } + } + _, err1 := os.Stat(m.config.IpmitoolPath) + _, err2 := os.Stat(m.config.IpmisensorsPath) + if err1 != nil { + m.config.IpmitoolPath = "" + } + if err2 != nil { + m.config.IpmisensorsPath = "" + } + if err1 != nil && err2 != nil { + return errors.New("No IPMI reader found") + } + m.init = true + return nil +} + +func ReadIpmiTool(cmd string, out *[]lp.MutableMetric) { + command := exec.Command(cmd, "sensor") + command.Wait() + stdout, err := command.Output() + if err != nil { + log.Print(err) + return + } + + ll := strings.Split(string(stdout), "\n") + + for _, line := range ll { + lv := strings.Split(line, "|") + if len(lv) < 3 { + continue + } + v, err := strconv.ParseFloat(strings.Trim(lv[1], " "), 64) + if err == nil { + name := strings.ToLower(strings.Replace(strings.Trim(lv[0], " "), " ", "_", -1)) + unit := strings.Trim(lv[2], " ") + if unit == "Volts" { + unit = "V" + } else if unit == "degrees C" { + unit = "C" + } else if unit == "degrees F" { + unit = "F" + } else if unit == "Watts" { + unit = "W" + } + + y, err := lp.New(name, map[string]string{"unit": unit, "type" : "node"}, map[string]interface{}{"value": v}, time.Now()) + if err == nil { + *out = append(*out, y) + } + } + } +} + +func ReadIpmiSensors(cmd string, out *[]lp.MutableMetric) { + + command := exec.Command(cmd, "--comma-separated-output", "--sdr-cache-recreate") + command.Wait() + stdout, err := command.Output() + if err != nil { + log.Print(err) + return + } + + ll := strings.Split(string(stdout), "\n") + + for _, line := range ll { + lv := strings.Split(line, ",") + if len(lv) > 3 { + v, err := strconv.ParseFloat(lv[3], 64) + if err == nil { + name := strings.ToLower(strings.Replace(lv[1], " ", "_", -1)) + y, err := lp.New(name, map[string]string{"unit": lv[4], "type" : "node"}, map[string]interface{}{"value": v}, time.Now()) + if err == nil { + *out = append(*out, y) + } + } + } + } +} + +func (m *IpmiCollector) Read(interval time.Duration, out *[]lp.MutableMetric) { + if len(m.config.IpmitoolPath) > 0 { + _, err := os.Stat(m.config.IpmitoolPath) + if err == nil { + ReadIpmiTool(m.config.IpmitoolPath, out) + } + } else if len(m.config.IpmisensorsPath) > 0 { + _, err := os.Stat(m.config.IpmisensorsPath) + if err == nil { + ReadIpmiSensors(m.config.IpmisensorsPath, out) + } + } +} + +func (m *IpmiCollector) Close() { + m.init = false + return +} diff --git a/metric-collector.go b/metric-collector.go index 4883468..b8449b7 100644 --- a/metric-collector.go +++ b/metric-collector.go @@ -30,6 +30,7 @@ var Collectors = map[string]collectors.MetricGetter{ "nvidia": &collectors.NvidiaCollector{}, "customcmd": &collectors.CustomCmdCollector{}, "diskstat": &collectors.DiskstatCollector{}, + "ipmistat" : &collectors.IpmiCollector{}, } var Sinks = map[string]sinks.SinkFuncs{