package collectors import ( "bufio" "bytes" "encoding/json" "errors" "fmt" "io" "log" "os/exec" "strconv" "strings" "time" cclog "github.com/ClusterCockpit/cc-metric-collector/pkg/ccLogger" lp "github.com/ClusterCockpit/cc-energy-manager/pkg/cc-message" ) const IPMISENSORS_PATH = `ipmi-sensors` type IpmiCollector struct { metricCollector config struct { ExcludeDevices []string `json:"exclude_devices"` IpmitoolPath string `json:"ipmitool_path"` IpmisensorsPath string `json:"ipmisensors_path"` } ipmitool string ipmisensors string } func (m *IpmiCollector) Init(config json.RawMessage) error { // Check if already initialized if m.init { return nil } m.name = "IpmiCollector" m.setup() m.parallel = true m.meta = map[string]string{ "source": m.name, "group": "IPMI", } // default path to IPMI tools m.config.IpmitoolPath = "ipmitool" m.config.IpmisensorsPath = "ipmi-sensors" if len(config) > 0 { err := json.Unmarshal(config, &m.config) if err != nil { return err } } // Check if executables ipmitool or ipmisensors are found p, err := exec.LookPath(m.config.IpmitoolPath) if err == nil { m.ipmitool = p } p, err = exec.LookPath(m.config.IpmisensorsPath) if err == nil { m.ipmisensors = p } if len(m.ipmitool) == 0 && len(m.ipmisensors) == 0 { return errors.New("no IPMI reader found") } m.init = true return nil } func (m *IpmiCollector) readIpmiTool(cmd string, output chan lp.CCMessage) { // Setup ipmitool command command := exec.Command(cmd, "sensor") stdout, _ := command.StdoutPipe() errBuf := new(bytes.Buffer) command.Stderr = errBuf // start command if err := command.Start(); err != nil { cclog.ComponentError( m.name, fmt.Sprintf("readIpmiTool(): Failed to start command \"%s\": %v", command.String(), err), ) return } // Read command output scanner := bufio.NewScanner(stdout) for scanner.Scan() { lv := strings.Split(scanner.Text(), "|") if len(lv) < 3 { continue } v, err := strconv.ParseFloat(strings.TrimSpace(lv[1]), 64) if err == nil { name := strings.ToLower(strings.Replace(strings.TrimSpace(lv[0]), " ", "_", -1)) unit := strings.TrimSpace(lv[2]) if unit == "Volts" { unit = "Volts" } else if unit == "degrees C" { unit = "degC" } else if unit == "degrees F" { unit = "degF" } else if unit == "Watts" { unit = "Watts" } y, err := lp.NewMessage(name, map[string]string{"type": "node"}, m.meta, map[string]interface{}{"value": v}, time.Now()) if err == nil { y.AddMeta("unit", unit) output <- y } } } // Wait for command end if err := command.Wait(); err != nil { errMsg, _ := io.ReadAll(errBuf) cclog.ComponentError( m.name, fmt.Sprintf("readIpmiTool(): Failed to wait for the end of command \"%s\": %v\n", command.String(), err), fmt.Sprintf("readIpmiTool(): command stderr: \"%s\"\n", string(errMsg)), ) return } } func (m *IpmiCollector) readIpmiSensors(cmd string, output chan lp.CCMessage) { 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.NewMessage(name, map[string]string{"type": "node"}, m.meta, map[string]interface{}{"value": v}, time.Now()) if err == nil { if len(lv) > 4 { y.AddMeta("unit", lv[4]) } output <- y } } } } } func (m *IpmiCollector) Read(interval time.Duration, output chan lp.CCMessage) { // Check if already initialized if !m.init { return } if len(m.config.IpmitoolPath) > 0 { m.readIpmiTool(m.config.IpmitoolPath, output) } else if len(m.config.IpmisensorsPath) > 0 { m.readIpmiSensors(m.config.IpmisensorsPath, output) } } func (m *IpmiCollector) Close() { m.init = false }