mirror of
https://github.com/ClusterCockpit/cc-metric-collector.git
synced 2026-07-11 04:00:38 +02:00
Add MegwareEurekaCollector
This commit is contained in:
@@ -21,36 +21,37 @@ import (
|
||||
|
||||
// Map of all available metric collectors
|
||||
var AvailableCollectors = map[string]MetricCollector{
|
||||
"likwid": new(LikwidCollector),
|
||||
"loadavg": new(LoadavgCollector),
|
||||
"memstat": new(MemstatCollector),
|
||||
"netstat": new(NetstatCollector),
|
||||
"ibstat": new(InfinibandCollector),
|
||||
"lustrestat": new(LustreCollector),
|
||||
"cpustat": new(CpustatCollector),
|
||||
"topprocs": new(TopProcsCollector),
|
||||
"nvidia": new(NvidiaCollector),
|
||||
"customcmd": new(CustomCmdCollector),
|
||||
"iostat": new(IOstatCollector),
|
||||
"diskstat": new(DiskstatCollector),
|
||||
"tempstat": new(TempCollector),
|
||||
"ipmistat": new(IpmiCollector),
|
||||
"gpfs": new(GpfsCollector),
|
||||
"cpufreq": new(CPUFreqCollector),
|
||||
"cpufreq_cpuinfo": new(CPUFreqCpuInfoCollector),
|
||||
"nfs3stat": new(Nfs3Collector),
|
||||
"nfs4stat": new(Nfs4Collector),
|
||||
"numastats": new(NUMAStatsCollector),
|
||||
"beegfs_meta": new(BeegfsMetaCollector),
|
||||
"beegfs_storage": new(BeegfsStorageCollector),
|
||||
"rapl": new(RAPLCollector),
|
||||
"rocm_smi": new(RocmSmiCollector),
|
||||
"self": new(SelfCollector),
|
||||
"schedstat": new(SchedstatCollector),
|
||||
"nfsiostat": new(NfsIOStatCollector),
|
||||
"slurm_cgroup": new(SlurmCgroupCollector),
|
||||
"smartmon": new(SmartMonCollector),
|
||||
"lenovo_dense_power": new(LenovoDensePowerCollector),
|
||||
"likwid": new(LikwidCollector),
|
||||
"loadavg": new(LoadavgCollector),
|
||||
"memstat": new(MemstatCollector),
|
||||
"netstat": new(NetstatCollector),
|
||||
"ibstat": new(InfinibandCollector),
|
||||
"lustrestat": new(LustreCollector),
|
||||
"cpustat": new(CpustatCollector),
|
||||
"topprocs": new(TopProcsCollector),
|
||||
"nvidia": new(NvidiaCollector),
|
||||
"customcmd": new(CustomCmdCollector),
|
||||
"iostat": new(IOstatCollector),
|
||||
"diskstat": new(DiskstatCollector),
|
||||
"tempstat": new(TempCollector),
|
||||
"ipmistat": new(IpmiCollector),
|
||||
"gpfs": new(GpfsCollector),
|
||||
"cpufreq": new(CPUFreqCollector),
|
||||
"cpufreq_cpuinfo": new(CPUFreqCpuInfoCollector),
|
||||
"nfs3stat": new(Nfs3Collector),
|
||||
"nfs4stat": new(Nfs4Collector),
|
||||
"numastats": new(NUMAStatsCollector),
|
||||
"beegfs_meta": new(BeegfsMetaCollector),
|
||||
"beegfs_storage": new(BeegfsStorageCollector),
|
||||
"rapl": new(RAPLCollector),
|
||||
"rocm_smi": new(RocmSmiCollector),
|
||||
"self": new(SelfCollector),
|
||||
"schedstat": new(SchedstatCollector),
|
||||
"nfsiostat": new(NfsIOStatCollector),
|
||||
"slurm_cgroup": new(SlurmCgroupCollector),
|
||||
"smartmon": new(SmartMonCollector),
|
||||
"lenovo_dense_power": new(LenovoDensePowerCollector),
|
||||
"megware_eureka": new(MegwareEurekaCollector),
|
||||
}
|
||||
|
||||
// Metric collector manager data structure
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
// Copyright (C) NHR@FAU, University Erlangen-Nuremberg.
|
||||
// All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// additional authors:
|
||||
// Michael Panzlaff (NHR@FAU)
|
||||
|
||||
package collectors
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
cclog "github.com/ClusterCockpit/cc-lib/v2/ccLogger"
|
||||
lp "github.com/ClusterCockpit/cc-lib/v2/ccMessage"
|
||||
)
|
||||
|
||||
// MPS refers to Monolithic Power Systems, from which the MP5922 is used
|
||||
// to measure telemetry on the Eureka platform.
|
||||
type mpsData struct {
|
||||
cnt int64 `json:"Cnt"` // No idea what this is, ignore
|
||||
energy float64 `json:"Energy"`
|
||||
vin float64 `json:"Vin"`
|
||||
iin float64 `json:"Iin"`
|
||||
pin float64 `json:"Pin"`
|
||||
pinAvg float64 `json:"PinAvg"`
|
||||
vout float64 `json:"Vout"`
|
||||
iout float64 `json:"Iout"`
|
||||
pout float64 `json:"Pout"`
|
||||
standbyVout float64 `json:"StandbyVout"`
|
||||
standbyIout float64 `json:"StandbyIout"`
|
||||
standbyPout float64 `json:"StandbyPout"`
|
||||
tempBusbar float64 `json:"TempBusbar"`
|
||||
tempSsd float64 `json:"TempSsd"`
|
||||
tempMps float64 `json:"TempMps"`
|
||||
// No idea what the ones below mean exactl. Ignore them for now.
|
||||
energyTime int64 `json:"EnergyTime"`
|
||||
energyAccumulator int64 `json:"EnergyAccumulator"`
|
||||
energyRolloverCnt int64 `json:"EnergyRolloverCnt"`
|
||||
energySampleCntU24 int64 `json:"EnergySampleCntU24"`
|
||||
|
||||
timestamp time.Time
|
||||
}
|
||||
|
||||
type MegwareEurekaCollector struct {
|
||||
metricCollector
|
||||
|
||||
config struct {
|
||||
U20Path string `json:"u20_path"`
|
||||
Sudo bool `json:"use_sudo"`
|
||||
}
|
||||
|
||||
u20path string
|
||||
|
||||
energyValLast float64
|
||||
energyTimeLast time.Time
|
||||
}
|
||||
|
||||
func (m *MegwareEurekaCollector) Init(config json.RawMessage) error {
|
||||
// Check if already initialized
|
||||
if m.init {
|
||||
return nil
|
||||
}
|
||||
|
||||
m.name = "MegwareEureka"
|
||||
if err := m.setup(); err != nil {
|
||||
return fmt.Errorf("%s Init(): setup() call failed: %w", m.name, err)
|
||||
}
|
||||
m.meta = map[string]string{
|
||||
"source": m.name,
|
||||
"group": "U20",
|
||||
}
|
||||
|
||||
m.config.U20Path = "u20"
|
||||
|
||||
if len(config) > 0 {
|
||||
d := json.NewDecoder(bytes.NewReader(config))
|
||||
d.DisallowUnknownFields()
|
||||
if err := d.Decode(&m.config); err != nil {
|
||||
return fmt.Errorf("%s Init(): Error decoding JSON config: %w", m.name, err)
|
||||
}
|
||||
}
|
||||
|
||||
m.u20path = m.config.U20Path
|
||||
|
||||
data, err := m.readMpsData()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Energy reading test failed: %w", err)
|
||||
}
|
||||
|
||||
m.energyValLast = data.energy
|
||||
m.energyTimeLast = time.Now()
|
||||
m.init = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MegwareEurekaCollector) readMpsData() (*mpsData, error) {
|
||||
argv := make([]string, 0)
|
||||
if m.config.Sudo {
|
||||
argv = append(argv, "sudo", "-n")
|
||||
}
|
||||
|
||||
argv = append(argv, m.u20path, "values", "--read", "GET_MPS_POLL_VALUES")
|
||||
|
||||
cmd := exec.Command(argv[0], argv[1:]...)
|
||||
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to run u20: %w (stdout=%s stderr=%s)", err, stdout.String(), stderr.String())
|
||||
}
|
||||
|
||||
var u20output struct {
|
||||
getMpsPollValues mpsData `json:"GET_MPS_POLL_VALUES"`
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(stdout.String()), &u20output)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to decode u20 JSON output: %w (stdout=%s)", err, stdout.String())
|
||||
}
|
||||
|
||||
u20output.getMpsPollValues.timestamp = time.Now()
|
||||
|
||||
return &u20output.getMpsPollValues, nil
|
||||
}
|
||||
|
||||
func (m *MegwareEurekaCollector) Read(interval time.Duration, output chan lp.CCMessage) {
|
||||
// Check if already initialized
|
||||
if !m.init {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := m.readMpsData()
|
||||
if err != nil {
|
||||
cclog.ComponentErrorf(m.name, "readMpsData failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
powerVal := 0.0
|
||||
|
||||
if data.timestamp.After(m.energyTimeLast) {
|
||||
// Important, m.energy comes in Wh, so multiply by 3600 to get Ws (aka Joule)
|
||||
energyValDiff := data.energy - m.energyValLast
|
||||
energyTimeDiff := data.timestamp.Sub(m.energyTimeLast)
|
||||
powerVal = energyValDiff * 3600 / energyTimeDiff.Seconds()
|
||||
|
||||
m.energyValLast = data.energy
|
||||
m.energyTimeLast = data.timestamp
|
||||
}
|
||||
|
||||
metricNamePrefix := "eureka_"
|
||||
metricMap := map[string]struct{
|
||||
value any
|
||||
unit string
|
||||
}{
|
||||
"power": { value: powerVal, unit: "Watts" },
|
||||
"vin": { value: data.vin, unit: "Volts" },
|
||||
"iin": { value: data.iin, unit: "Amperes" },
|
||||
"pin": { value: data.pin, unit: "Watts" },
|
||||
"pin_avg": { value: data.pinAvg, unit: "Watts" },
|
||||
"vout": { value: data.vout, unit: "Volts" },
|
||||
"iout": { value: data.iout, unit: "Amperes" },
|
||||
"pout": { value: data.pout, unit: "Watts" },
|
||||
"standby_vout": { value: data.standbyVout, unit: "Volts" },
|
||||
"standby_iout": { value: data.standbyIout, unit: "Amperes" },
|
||||
"standby_pout": { value: data.standbyPout, unit: "Watts" },
|
||||
"temp_busbar": { value: data.tempBusbar, unit: "degC" },
|
||||
"temp_ssd": { value: data.tempSsd, unit: "degC" },
|
||||
"temp_mps": { value: data.tempMps, unit: "degC" },
|
||||
}
|
||||
|
||||
for metricName, metricData := range metricMap {
|
||||
metricName = metricNamePrefix + metricName
|
||||
metric, err := lp.NewMetric(metricName, map[string]string{"type": "node"}, m.meta, metricData.value, data.timestamp)
|
||||
if err != nil {
|
||||
cclog.ComponentErrorf(m.name, "lp.NewMetric failed: %v", err)
|
||||
return
|
||||
}
|
||||
metric.AddMeta("unit", metricData.unit)
|
||||
output <- metric
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MegwareEurekaCollector) Close() {
|
||||
m.init = false
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<!--
|
||||
---
|
||||
title: Megware Eureka collector
|
||||
description: Collect power and other metrics of Megware Eureka machines using u20
|
||||
categories: [cc-metric-collector]
|
||||
tags: ['Admin']
|
||||
weight: 2
|
||||
hugo_path: docs/reference/cc-metric-collector/collectors/lenovoDensePower.md
|
||||
---
|
||||
-->
|
||||
|
||||
## `lenovo_dense_power` collector
|
||||
|
||||
```json
|
||||
"megware_eureka": {
|
||||
"u20_path": "/path/to/ipmitool",
|
||||
"use_sudo": true
|
||||
}
|
||||
```
|
||||
|
||||
The `megware_eureka` collector reads power and other metrics from Megware Eureka machines via `u20`.
|
||||
If the u20 tool is available for your machines, it's possible that this collector is compatible.
|
||||
If you don't have access to u20, please contact your Megware sales person.
|
||||
|
||||
You can test the collector compatibility by running the following command:
|
||||
|
||||
```
|
||||
$ u20 values --read GET_MPS_POLL_VALUES
|
||||
```
|
||||
|
||||
If this returns a JSON with energy, voltage and current readings, you're fine.
|
||||
|
||||
In addition, `u20` typically requires root to run.
|
||||
In order to run `cc-metric-collector` without root priviliges, you can enable `use_sudo`.
|
||||
Add a file like this in `/etc/sudoers.d/` to allow `cc-metric-collector` to run the required commands:
|
||||
|
||||
```
|
||||
# Do not log the following sudo commands from monitoring, since this causes a lot of log spam.
|
||||
# However keep log_denied enabled, to detect failures
|
||||
Defaults: monitoring !log_allowed, !pam_session
|
||||
|
||||
# Allow to use u20 for Megware power readings
|
||||
monitoring ALL = (root) NOPASSWD:/usr/bin/u20 values --read GET_MPS_POLL_VALUES
|
||||
```
|
||||
Reference in New Issue
Block a user