mirror of
https://github.com/ClusterCockpit/cc-metric-collector.git
synced 2026-07-19 16:00:38 +02:00
Compare commits
22 Commits
cclog_update
...
v0.8.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a859af358 | |||
| c4fb350ad2 | |||
| 14000c0ff0 | |||
| f6d33fb29f | |||
| 025a5c2861 | |||
| 8d1df385ce | |||
| bda952f85a | |||
| 81fa732e79 | |||
| 93622b72ac | |||
| f48eb458e8 | |||
| 9fb3d2300c | |||
| 4372cb7994 | |||
| a38804ab13 | |||
| 852ff18468 | |||
| 61570f9ecf | |||
| 3098e16ded | |||
| d53ec9233d | |||
| aaabcc639f | |||
| 14d624fd22 | |||
| 9677a916be | |||
| 8b373cc2c0 | |||
| f6260a8fb2 |
@@ -50,6 +50,8 @@ var AvailableCollectors = map[string]MetricCollector{
|
|||||||
"nfsiostat": new(NfsIOStatCollector),
|
"nfsiostat": new(NfsIOStatCollector),
|
||||||
"slurm_cgroup": new(SlurmCgroupCollector),
|
"slurm_cgroup": new(SlurmCgroupCollector),
|
||||||
"smartmon": new(SmartMonCollector),
|
"smartmon": new(SmartMonCollector),
|
||||||
|
"lenovo_dense_power": new(LenovoDensePowerCollector),
|
||||||
|
"megware_eureka": new(MegwareEurekaCollector),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metric collector manager data structure
|
// Metric collector manager data structure
|
||||||
|
|||||||
@@ -116,6 +116,9 @@ func (m *CPUFreqCpuInfoCollector) Init(_ json.RawMessage) error {
|
|||||||
physicalPackageID = ""
|
physicalPackageID = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return fmt.Errorf("%s Init(): Call to scanner.Err failed: %w", m.name, err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := file.Close(); err != nil {
|
if err := file.Close(); err != nil {
|
||||||
return fmt.Errorf("%s Init(): Call to file.Close() failed: %w", m.name, err)
|
return fmt.Errorf("%s Init(): Call to file.Close() failed: %w", m.name, err)
|
||||||
@@ -179,6 +182,9 @@ func (m *CPUFreqCpuInfoCollector) Read(interval time.Duration, output chan lp.CC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
cclog.ComponentErrorf(m.name, "Read(): Call to scanner.Err failed: %s", err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *CPUFreqCpuInfoCollector) Close() {
|
func (m *CPUFreqCpuInfoCollector) Close() {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ type CpustatCollector struct {
|
|||||||
func (m *CpustatCollector) Init(config json.RawMessage) error {
|
func (m *CpustatCollector) Init(config json.RawMessage) error {
|
||||||
m.name = "CpustatCollector"
|
m.name = "CpustatCollector"
|
||||||
if err := m.setup(); err != nil {
|
if err := m.setup(); err != nil {
|
||||||
return fmt.Errorf("%s Init(): setup() call failed: %w", m.name, err)
|
return fmt.Errorf("%s Init(): setup() call failed: %s", m.name, err.Error())
|
||||||
}
|
}
|
||||||
m.parallel = true
|
m.parallel = true
|
||||||
m.meta = map[string]string{
|
m.meta = map[string]string{
|
||||||
@@ -58,7 +58,7 @@ func (m *CpustatCollector) Init(config json.RawMessage) error {
|
|||||||
d := json.NewDecoder(bytes.NewReader(config))
|
d := json.NewDecoder(bytes.NewReader(config))
|
||||||
d.DisallowUnknownFields()
|
d.DisallowUnknownFields()
|
||||||
if err := d.Decode(&m.config); err != nil {
|
if err := d.Decode(&m.config); err != nil {
|
||||||
return fmt.Errorf("%s Init(): Error decoding JSON config: %w", m.name, err)
|
return fmt.Errorf("%s Init(): Error decoding JSON config: %s", m.name, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
matches := map[string]int{
|
matches := map[string]int{
|
||||||
@@ -85,7 +85,7 @@ func (m *CpustatCollector) Init(config json.RawMessage) error {
|
|||||||
// Check input file
|
// Check input file
|
||||||
file, err := os.Open(CPUSTATFILE)
|
file, err := os.Open(CPUSTATFILE)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s Init(): Failed to open file '%s': %w", m.name, CPUSTATFILE, err)
|
return fmt.Errorf("%s Init(): Failed to open file '%s': %s", m.name, CPUSTATFILE, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre-generate tags for all CPUs
|
// Pre-generate tags for all CPUs
|
||||||
@@ -117,10 +117,13 @@ func (m *CpustatCollector) Init(config json.RawMessage) error {
|
|||||||
num_cpus++
|
num_cpus++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return fmt.Errorf("%s Init(): Call to scanner.Err failed: %s", m.name, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
// Close file
|
// Close file
|
||||||
if err := file.Close(); err != nil {
|
if err := file.Close(); err != nil {
|
||||||
return fmt.Errorf("%s Init(): Failed to close file '%s': %w", m.name, CPUSTATFILE, err)
|
return fmt.Errorf("%s Init(): Failed to close file '%s': %s", m.name, CPUSTATFILE, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
m.lastTimestamp = time.Now()
|
m.lastTimestamp = time.Now()
|
||||||
@@ -173,13 +176,13 @@ func (m *CpustatCollector) Read(interval time.Duration, output chan lp.CCMessage
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.ComponentError(
|
cclog.ComponentError(
|
||||||
m.name,
|
m.name,
|
||||||
fmt.Sprintf("Read(): Failed to open file '%s': %v", CPUSTATFILE, err))
|
fmt.Sprintf("Read(): Failed to open file '%s': %v", CPUSTATFILE, err.Error()))
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := file.Close(); err != nil {
|
if err := file.Close(); err != nil {
|
||||||
cclog.ComponentError(
|
cclog.ComponentError(
|
||||||
m.name,
|
m.name,
|
||||||
fmt.Sprintf("Read(): Failed to close file '%s': %v", string(CPUSTATFILE), err))
|
fmt.Sprintf("Read(): Failed to close file '%s': %v", string(CPUSTATFILE), err.Error()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -194,6 +197,9 @@ func (m *CpustatCollector) Read(interval time.Duration, output chan lp.CCMessage
|
|||||||
num_cpus++
|
num_cpus++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
cclog.ComponentErrorf(m.name, "Init(): Call to scanner.Err failed: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
if !m.config.excludeNumCPUs {
|
if !m.config.excludeNumCPUs {
|
||||||
if num_cpus_metric, err := lp.NewMetric("num_cpus", m.nodetags, m.meta, num_cpus, now); err == nil {
|
if num_cpus_metric, err := lp.NewMetric("num_cpus", m.nodetags, m.meta, num_cpus, now); err == nil {
|
||||||
|
|||||||
@@ -147,6 +147,11 @@ mountLoop:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
cclog.ComponentErrorf(
|
||||||
|
m.name,
|
||||||
|
"Read(): Call to scanner.Err failed: %s", err.Error())
|
||||||
|
}
|
||||||
if m.allowedMetrics["part_max_used"] {
|
if m.allowedMetrics["part_max_used"] {
|
||||||
y, err := lp.NewMetric("part_max_used", map[string]string{"type": "node"}, m.meta, int(part_max_used), time.Now())
|
y, err := lp.NewMetric("part_max_used", map[string]string{"type": "node"}, m.meta, int(part_max_used), time.Now())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
@@ -48,13 +48,13 @@ func (m *IOstatCollector) Init(config json.RawMessage) error {
|
|||||||
m.parallel = true
|
m.parallel = true
|
||||||
m.meta = map[string]string{"source": m.name, "group": "Disk"}
|
m.meta = map[string]string{"source": m.name, "group": "Disk"}
|
||||||
if err := m.setup(); err != nil {
|
if err := m.setup(); err != nil {
|
||||||
return fmt.Errorf("%s Init(): setup() call failed: %w", m.name, err)
|
return fmt.Errorf("%s Init(): setup() call failed: %s", m.name, err.Error())
|
||||||
}
|
}
|
||||||
if len(config) > 0 {
|
if len(config) > 0 {
|
||||||
d := json.NewDecoder(bytes.NewReader(config))
|
d := json.NewDecoder(bytes.NewReader(config))
|
||||||
d.DisallowUnknownFields()
|
d.DisallowUnknownFields()
|
||||||
if err := d.Decode(&m.config); err != nil {
|
if err := d.Decode(&m.config); err != nil {
|
||||||
return fmt.Errorf("%s Init(): Error decoding JSON config: %w", m.name, err)
|
return fmt.Errorf("%s Init(): Error decoding JSON config: %s", m.name, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// https://www.kernel.org/doc/html/latest/admin-guide/iostats.html
|
// https://www.kernel.org/doc/html/latest/admin-guide/iostats.html
|
||||||
@@ -89,7 +89,7 @@ func (m *IOstatCollector) Init(config json.RawMessage) error {
|
|||||||
}
|
}
|
||||||
file, err := os.Open(IOSTATFILE)
|
file, err := os.Open(IOSTATFILE)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s Init(): Failed to open file \"%s\": %w", m.name, IOSTATFILE, err)
|
return fmt.Errorf("%s Init(): Failed to open file \"%s\": %s", m.name, IOSTATFILE, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
scanner := bufio.NewScanner(file)
|
scanner := bufio.NewScanner(file)
|
||||||
@@ -130,8 +130,11 @@ func (m *IOstatCollector) Init(config json.RawMessage) error {
|
|||||||
lastValues: lastValues,
|
lastValues: lastValues,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return fmt.Errorf("%s Init(): Failed to scan content of file %q: %s", m.name, IOSTATFILE, err.Error())
|
||||||
|
}
|
||||||
if err := file.Close(); err != nil {
|
if err := file.Close(); err != nil {
|
||||||
return fmt.Errorf("%s Init(): Failed to close file \"%s\": %w", m.name, IOSTATFILE, err)
|
return fmt.Errorf("%s Init(): Failed to close file \"%s\": %s", m.name, IOSTATFILE, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
m.init = true
|
m.init = true
|
||||||
@@ -147,14 +150,14 @@ func (m *IOstatCollector) Read(interval time.Duration, output chan lp.CCMessage)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.ComponentError(
|
cclog.ComponentError(
|
||||||
m.name,
|
m.name,
|
||||||
fmt.Sprintf("Read(): Failed to open file '%s': %v", IOSTATFILE, err))
|
fmt.Sprintf("Read(): Failed to open file '%s': %s", IOSTATFILE, err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := file.Close(); err != nil {
|
if err := file.Close(); err != nil {
|
||||||
cclog.ComponentError(
|
cclog.ComponentError(
|
||||||
m.name,
|
m.name,
|
||||||
fmt.Sprintf("Read(): Failed to close file '%s': %v", IOSTATFILE, err))
|
fmt.Sprintf("Read(): Failed to close file '%s': %s", IOSTATFILE, err.Error()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -198,6 +201,11 @@ func (m *IOstatCollector) Read(interval time.Duration, output chan lp.CCMessage)
|
|||||||
}
|
}
|
||||||
m.devices[device] = entry
|
m.devices[device] = entry
|
||||||
}
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
cclog.ComponentErrorf(
|
||||||
|
m.name,
|
||||||
|
"Read(): Failed to scan content of file %q: %s", IOSTATFILE, err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *IOstatCollector) Close() {
|
func (m *IOstatCollector) Close() {
|
||||||
|
|||||||
@@ -31,10 +31,12 @@ type IpmiCollector struct {
|
|||||||
IpmitoolPath string `json:"ipmitool_path"`
|
IpmitoolPath string `json:"ipmitool_path"`
|
||||||
IpmisensorsPath string `json:"ipmisensors_path"`
|
IpmisensorsPath string `json:"ipmisensors_path"`
|
||||||
Sudo bool `json:"use_sudo"`
|
Sudo bool `json:"use_sudo"`
|
||||||
|
IncludeMetrics []string `json:"include_metrics"`
|
||||||
}
|
}
|
||||||
|
|
||||||
ipmitool string
|
ipmitool string
|
||||||
ipmisensors string
|
ipmisensors string
|
||||||
|
includeMetrics map[string]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *IpmiCollector) Init(config json.RawMessage) error {
|
func (m *IpmiCollector) Init(config json.RawMessage) error {
|
||||||
@@ -64,6 +66,15 @@ func (m *IpmiCollector) Init(config json.RawMessage) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read metrics to include
|
||||||
|
m.includeMetrics = make(map[string]bool)
|
||||||
|
for _, metric := range m.config.IncludeMetrics {
|
||||||
|
metric = strings.ToLower(strings.TrimSpace(metric))
|
||||||
|
if metric != "" {
|
||||||
|
m.includeMetrics[metric] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m.ipmitool = m.config.IpmitoolPath
|
m.ipmitool = m.config.IpmitoolPath
|
||||||
m.ipmisensors = m.config.IpmisensorsPath
|
m.ipmisensors = m.config.IpmisensorsPath
|
||||||
|
|
||||||
@@ -145,6 +156,11 @@ func (m *IpmiCollector) readIpmiTool(output chan lp.CCMessage) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
name := strings.ToLower(strings.ReplaceAll(strings.TrimSpace(lv[0]), " ", "_"))
|
name := strings.ToLower(strings.ReplaceAll(strings.TrimSpace(lv[0]), " ", "_"))
|
||||||
|
|
||||||
|
if len(m.includeMetrics) > 0 && !m.includeMetrics[name] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
unit := strings.TrimSpace(lv[2])
|
unit := strings.TrimSpace(lv[2])
|
||||||
switch unit {
|
switch unit {
|
||||||
case "Volts":
|
case "Volts":
|
||||||
@@ -165,6 +181,9 @@ func (m *IpmiCollector) readIpmiTool(output chan lp.CCMessage) error {
|
|||||||
y.AddMeta("unit", unit)
|
y.AddMeta("unit", unit)
|
||||||
output <- y
|
output <- y
|
||||||
}
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return fmt.Errorf("failed to scan output of command: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
// Wait for command end
|
// Wait for command end
|
||||||
if err := command.Wait(); err != nil {
|
if err := command.Wait(); err != nil {
|
||||||
@@ -209,6 +228,11 @@ func (m *IpmiCollector) readIpmiSensors(output chan lp.CCMessage) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
name := strings.ToLower(strings.ReplaceAll(lv[1], " ", "_"))
|
name := strings.ToLower(strings.ReplaceAll(lv[1], " ", "_"))
|
||||||
|
|
||||||
|
if len(m.includeMetrics) > 0 && !m.includeMetrics[name] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
y, err := lp.NewMetric(name, map[string]string{"type": "node"}, m.meta, v, time.Now())
|
y, err := lp.NewMetric(name, map[string]string{"type": "node"}, m.meta, v, time.Now())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.ComponentErrorf(m.name, "Failed to create message: %v", err)
|
cclog.ComponentErrorf(m.name, "Failed to create message: %v", err)
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ hugo_path: docs/reference/cc-metric-collector/collectors/ipmi.md
|
|||||||
"ipmistat": {
|
"ipmistat": {
|
||||||
"ipmitool_path": "/path/to/ipmitool",
|
"ipmitool_path": "/path/to/ipmitool",
|
||||||
"ipmisensors_path": "/path/to/ipmi-sensors",
|
"ipmisensors_path": "/path/to/ipmi-sensors",
|
||||||
"use_sudo": true
|
"use_sudo": true,
|
||||||
|
"include_metrics" : []
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -36,3 +37,5 @@ Defaults: monitoring !log_allowed, !pam_session
|
|||||||
monitoring ALL = (root) NOPASSWD:/usr/bin/ipmitool sensor
|
monitoring ALL = (root) NOPASSWD:/usr/bin/ipmitool sensor
|
||||||
monitoring ALL = (root) NOPASSWD:/usr/sbin/ipmi-sensors --comma-separated-output --sdr-cache-recreate
|
monitoring ALL = (root) NOPASSWD:/usr/sbin/ipmi-sensors --comma-separated-output --sdr-cache-recreate
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If `include_ipmi_metrics` contains any entry, ipmistat collector will only submit these metrics. Any other values will get discarded.
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
// 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/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
cclog "github.com/ClusterCockpit/cc-lib/v2/ccLogger"
|
||||||
|
lp "github.com/ClusterCockpit/cc-lib/v2/ccMessage"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LenovoDensePowerCollector struct {
|
||||||
|
metricCollector
|
||||||
|
|
||||||
|
config struct {
|
||||||
|
IpmitoolPath string `json:"ipmitool_path"`
|
||||||
|
Sudo bool `json:"use_sudo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
ipmitool string
|
||||||
|
|
||||||
|
energyValLast float64
|
||||||
|
energyTimeLast time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *LenovoDensePowerCollector) Init(config json.RawMessage) error {
|
||||||
|
// Check if already initialized
|
||||||
|
if m.init {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
m.name = "LenovoDensePowerCollector"
|
||||||
|
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": "IPMI",
|
||||||
|
}
|
||||||
|
|
||||||
|
m.config.IpmitoolPath = "ipmitool"
|
||||||
|
|
||||||
|
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.ipmitool = m.config.IpmitoolPath
|
||||||
|
|
||||||
|
energyVal, energyTime, err := m.readEnergy()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("energy reading test failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.energyValLast = energyVal
|
||||||
|
m.energyTimeLast = energyTime
|
||||||
|
m.init = true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *LenovoDensePowerCollector) readEnergy() (float64, time.Time, error) {
|
||||||
|
argv := make([]string, 0)
|
||||||
|
if m.config.Sudo {
|
||||||
|
argv = append(argv, "sudo", "-n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Lenovo hardware wants this magic byte string
|
||||||
|
lenovoRequestEnergyMsg := []uint8{0x3a, 0x32, 4, 2, 0, 0, 0}
|
||||||
|
|
||||||
|
argv = append(argv, m.ipmitool, "raw")
|
||||||
|
|
||||||
|
for _, val := range lenovoRequestEnergyMsg {
|
||||||
|
argv = append(argv, strconv.Itoa(int(val)))
|
||||||
|
}
|
||||||
|
|
||||||
|
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 0, time.Unix(0, 0), fmt.Errorf("failed to run ipmitool: %w (stdout=%s stderr=%s)", err, stdout.String(), stderr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := hex.DecodeString(strings.ReplaceAll(strings.TrimSpace(stdout.String()), " ", ""))
|
||||||
|
if err != nil {
|
||||||
|
return 0, time.Unix(0, 0), fmt.Errorf("unable to decode ipmitool hex output: %w (stdout=%s)", err, stdout.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) != 14 {
|
||||||
|
return 0, time.Unix(0, 0), fmt.Errorf("result must be 14 bytes as specified in the documentation")
|
||||||
|
}
|
||||||
|
|
||||||
|
// data is laid out like this:
|
||||||
|
// data[0 to 1]: FIFO index of value (for debug only)
|
||||||
|
// data[2 to 5]: Integer part of energy in J (little endian)
|
||||||
|
// data[6 to 7]: Fraction part of energy in mJ (little endian)
|
||||||
|
// data[8 to 11]: Integer part of Unix time of measurement in seconds (little endian)
|
||||||
|
// data[12 to 13]: Fraction part of Unix time of measurement in milliseconds (little endian)
|
||||||
|
wholeJoules := (uint32(data[2]) << 0) | (uint32(data[3]) << 8) | (uint32(data[4]) << 16) | (uint32(data[5]) << 24)
|
||||||
|
milliJoules := uint16(data[6]) | (uint16(data[7]) << 8)
|
||||||
|
finalJoules := float64(wholeJoules) + float64(milliJoules)*1e-3
|
||||||
|
|
||||||
|
wholeSeconds := (uint32(data[8]) << 0) | (uint32(data[9]) << 8) | (uint32(data[10]) << 16) | (uint32(data[11]) << 24)
|
||||||
|
milliSeconds := uint16(data[12]) | (uint16(data[13]) << 8)
|
||||||
|
finalTime := time.Unix(int64(wholeSeconds), (int64(milliSeconds) * 1000000))
|
||||||
|
|
||||||
|
return finalJoules, finalTime, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *LenovoDensePowerCollector) Read(interval time.Duration, output chan lp.CCMessage) {
|
||||||
|
// Check if already initialized
|
||||||
|
if !m.init {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
energyVal, energyTime, err := m.readEnergy()
|
||||||
|
if err != nil {
|
||||||
|
cclog.ComponentErrorf(m.name, "readEnergy failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
powerVal := 0.0
|
||||||
|
if energyTime.Compare(m.energyTimeLast) != 0 {
|
||||||
|
// Check for overflow
|
||||||
|
energyValDiff := int64(energyVal) - int64(m.energyValLast)
|
||||||
|
if energyVal < m.energyValLast {
|
||||||
|
energyValDiff += int64(0x100000000)
|
||||||
|
}
|
||||||
|
energyTimeDiff := energyTime.Sub(m.energyTimeLast)
|
||||||
|
if energyTime.Before(m.energyTimeLast) {
|
||||||
|
energyTimeDiff = energyTimeDiff + 0x100000000*time.Second
|
||||||
|
}
|
||||||
|
powerVal = float64(energyValDiff) / energyTimeDiff.Seconds()
|
||||||
|
|
||||||
|
m.energyValLast = energyVal
|
||||||
|
m.energyTimeLast = energyTime
|
||||||
|
}
|
||||||
|
|
||||||
|
metric, err := lp.NewMetric("lenovo_node_power", map[string]string{"type": "node"}, m.meta, powerVal, energyTime)
|
||||||
|
if err != nil {
|
||||||
|
cclog.ComponentErrorf(m.name, "lp.NewMetric failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
metric.AddMeta("unit", "Watts")
|
||||||
|
output <- metric
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *LenovoDensePowerCollector) Close() {
|
||||||
|
m.init = false
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<!--
|
||||||
|
---
|
||||||
|
title: Lenovo Dense Power collector
|
||||||
|
description: Collect power of Lenovo Dense machines using ipmitool raw
|
||||||
|
categories: [cc-metric-collector]
|
||||||
|
tags: ['Admin']
|
||||||
|
weight: 2
|
||||||
|
hugo_path: docs/reference/cc-metric-collector/collectors/lenovoDensePower.md
|
||||||
|
---
|
||||||
|
-->
|
||||||
|
|
||||||
|
## `lenovo_dense_power` collector
|
||||||
|
|
||||||
|
```json
|
||||||
|
"lenovo_dense_power": {
|
||||||
|
"ipmitool_path": "/path/to/ipmitool",
|
||||||
|
"use_sudo": true,
|
||||||
|
"include_metrics" : []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `lenovo_dense_power` collector reads power from Lenovo Dense machines via `ipmitool` (`ipmitool raw ...`). This collector is known to only work on the following machines. Others are not supported, so use at your *OWN* risk:
|
||||||
|
|
||||||
|
System | Compatibility | Power measured
|
||||||
|
--- | --- | ---
|
||||||
|
Lenovo ThinkSystem SD650 V2/V3 | untested, but should work | node + riser1 + riser2
|
||||||
|
Lenovo ThinkSystem SD650-N V2/ SD650-I V3 | untested, but should work | node + riser1 + riser2 + gpus
|
||||||
|
Lenovo ThinkSystem SD665-N V3 | tested, known to work | node + riser1 + riser2 + gpus
|
||||||
|
|
||||||
|
To test if your system *may* be supported, you can run the following command *at your own risk*:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ipmitool raw 58 50 4 2 0 0 0
|
||||||
|
c4 00 83 a9 cb d3 a2 03 df db 47 6a d8 01
|
||||||
|
```
|
||||||
|
|
||||||
|
Exactly 14 bytes should be returned.
|
||||||
|
In case not or another error occurs your hardware is likely not supported.
|
||||||
|
|
||||||
|
In addition, `ipmitool` typically require 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 ipmitool for Lenovo power readings
|
||||||
|
monitoring ALL = (root) NOPASSWD:/usr/bin/ipmitool raw 58 50 4 2 0 0 0
|
||||||
|
```
|
||||||
@@ -38,7 +38,7 @@ import (
|
|||||||
topo "github.com/ClusterCockpit/cc-metric-collector/pkg/ccTopology"
|
topo "github.com/ClusterCockpit/cc-metric-collector/pkg/ccTopology"
|
||||||
"github.com/NVIDIA/go-nvml/pkg/dl"
|
"github.com/NVIDIA/go-nvml/pkg/dl"
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
"golang.design/x/thread"
|
"golang.design/x/runtime/thread"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -0,0 +1,197 @@
|
|||||||
|
// 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(stdout.Bytes(), &u20output)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to decode u20 JSON output: %w (stdout=%s)", err, stdout.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("string: %+v\n", stdout.String())
|
||||||
|
fmt.Printf("obj: %+v\n", u20output)
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -45,6 +46,37 @@ type SlurmCgroupCollector struct {
|
|||||||
|
|
||||||
const defaultCgroupBase = "/sys/fs/cgroup/system.slice/slurmstepd.scope"
|
const defaultCgroupBase = "/sys/fs/cgroup/system.slice/slurmstepd.scope"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Slurm cgroup v2 directory layout:
|
||||||
|
// - Slurm <= 25.11: job_<numeric job id>
|
||||||
|
// - Slurm >= 26.05: SLUID, encoded as "s" + 13 Crockford Base32 characters
|
||||||
|
jobIDDirRE = regexp.MustCompile(`^job_[0-9]+$`)
|
||||||
|
sluidDirRE = regexp.MustCompile(`(?i)^s[0-9A-HJKMNP-TV-Z]{13}$`)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *SlurmCgroupCollector) findSlurmJobDirs() ([]string, error) {
|
||||||
|
entries, err := os.ReadDir(m.cgroupBase)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jobDirs := make([]string, 0)
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
if !entry.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
name := entry.Name()
|
||||||
|
|
||||||
|
if jobIDDirRE.MatchString(name) || sluidDirRE.MatchString(name) {
|
||||||
|
jobDirs = append(jobDirs, filepath.Join(m.cgroupBase, name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return jobDirs, nil
|
||||||
|
}
|
||||||
|
|
||||||
func ParseCPUs(cpuset string) ([]int, error) {
|
func ParseCPUs(cpuset string) ([]int, error) {
|
||||||
var result []int
|
var result []int
|
||||||
if cpuset == "" {
|
if cpuset == "" {
|
||||||
@@ -237,10 +269,9 @@ func (m *SlurmCgroupCollector) Read(interval time.Duration, output chan lp.CCMes
|
|||||||
delete(m.cpuUsed, k)
|
delete(m.cpuUsed, k)
|
||||||
}
|
}
|
||||||
|
|
||||||
globPattern := filepath.Join(m.cgroupBase, "job_*")
|
jobDirs, err := m.findSlurmJobDirs()
|
||||||
jobDirs, err := filepath.Glob(globPattern)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.ComponentError(m.name, "Error globbing job directories:", err.Error())
|
cclog.ComponentError(m.name, "Error reading job directories:", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ go 1.25.0
|
|||||||
require (
|
require (
|
||||||
github.com/ClusterCockpit/cc-lib/v2 v2.12.0
|
github.com/ClusterCockpit/cc-lib/v2 v2.12.0
|
||||||
github.com/ClusterCockpit/go-rocm-smi v0.4.0
|
github.com/ClusterCockpit/go-rocm-smi v0.4.0
|
||||||
github.com/NVIDIA/go-nvml v0.13.0-1
|
github.com/NVIDIA/go-nvml v0.13.2-0
|
||||||
github.com/PaesslerAG/gval v1.2.4
|
github.com/PaesslerAG/gval v1.2.4
|
||||||
github.com/fsnotify/fsnotify v1.10.1
|
github.com/fsnotify/fsnotify v1.10.1
|
||||||
github.com/tklauser/go-sysconf v0.4.0
|
github.com/tklauser/go-sysconf v0.4.0
|
||||||
golang.design/x/thread v0.3.2
|
golang.design/x/runtime v0.3.0
|
||||||
golang.org/x/sys v0.45.0
|
golang.org/x/sys v0.45.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
|
|||||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||||
github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8=
|
github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8=
|
||||||
github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w=
|
github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w=
|
||||||
github.com/NVIDIA/go-nvml v0.13.0-1 h1:OLX8Jq3dONuPOQPC7rndB6+iDmDakw0XTYgzMxObkEw=
|
|
||||||
github.com/NVIDIA/go-nvml v0.13.0-1/go.mod h1:+KNA7c7gIBH7SKSJ1ntlwkfN80zdx8ovl4hrK3LmPt4=
|
github.com/NVIDIA/go-nvml v0.13.0-1/go.mod h1:+KNA7c7gIBH7SKSJ1ntlwkfN80zdx8ovl4hrK3LmPt4=
|
||||||
|
github.com/NVIDIA/go-nvml v0.13.2-0 h1:7M4cFG62wSUHw8i0XSiNU7ejKODytTS6ZrW/vgB2NSI=
|
||||||
|
github.com/NVIDIA/go-nvml v0.13.2-0/go.mod h1:ahi2psRYoa+wYUBIrZPRO+wJs9lcvMhxSSkjjvsJJNQ=
|
||||||
github.com/PaesslerAG/gval v1.2.4 h1:rhX7MpjJlcxYwL2eTTYIOBUyEKZ+A96T9vQySWkVUiU=
|
github.com/PaesslerAG/gval v1.2.4 h1:rhX7MpjJlcxYwL2eTTYIOBUyEKZ+A96T9vQySWkVUiU=
|
||||||
github.com/PaesslerAG/gval v1.2.4/go.mod h1:XRFLwvmkTEdYziLdaCeCa5ImcGVrfQbeNUbVR+C6xac=
|
github.com/PaesslerAG/gval v1.2.4/go.mod h1:XRFLwvmkTEdYziLdaCeCa5ImcGVrfQbeNUbVR+C6xac=
|
||||||
github.com/PaesslerAG/jsonpath v0.1.0 h1:gADYeifvlqK3R3i2cR5B4DGgxLXIPb3TRTH1mGi0jPI=
|
github.com/PaesslerAG/jsonpath v0.1.0 h1:gADYeifvlqK3R3i2cR5B4DGgxLXIPb3TRTH1mGi0jPI=
|
||||||
@@ -173,8 +174,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
|||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
||||||
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
|
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
|
||||||
golang.design/x/thread v0.3.2 h1:FmD1glspGrQCe6FuQLmSrT6wz2CSzq7vKVDluyiMnqo=
|
golang.design/x/runtime v0.3.0 h1:8bA+GQfO8A18JXJQlQA56pEc+Sgrbo4cmLzjzmJ58ZI=
|
||||||
golang.design/x/thread v0.3.2/go.mod h1:6+Hi2rMOgMHZdKDWaqNHyWtoFUx1HxZ06LfHPh5Z/hQ=
|
golang.design/x/runtime v0.3.0/go.mod h1:PjVKQjImLdSrvjIw4FmrnCPq9BGs5PMBHb8i6b2BN9I=
|
||||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||||
golang.org/x/exp v0.0.0-20231005195138-3e424a577f31 h1:9k5exFQKQglLo+RoP+4zMjOFE14P6+vyR0baDAi0Rcs=
|
golang.org/x/exp v0.0.0-20231005195138-3e424a577f31 h1:9k5exFQKQglLo+RoP+4zMjOFE14P6+vyR0baDAi0Rcs=
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ func (c *metricAggregator) Eval(starttime time.Time, endtime time.Time, metrics
|
|||||||
vars["starttime"] = starttime
|
vars["starttime"] = starttime
|
||||||
vars["endtime"] = endtime
|
vars["endtime"] = endtime
|
||||||
for _, f := range c.functions {
|
for _, f := range c.functions {
|
||||||
cclog.ComponentDebug("MetricCache", "COLLECT", f.Name, "COND", f.Condition)
|
cclog.ComponentDebugf("MetricCache", "COLLECT %s COND '%s'", f.Name, f.Condition)
|
||||||
var valuesFloat64 []float64
|
var valuesFloat64 []float64
|
||||||
var valuesFloat32 []float32
|
var valuesFloat32 []float32
|
||||||
var valuesInt []int
|
var valuesInt []int
|
||||||
@@ -140,7 +140,7 @@ func (c *metricAggregator) Eval(starttime time.Time, endtime time.Time, metrics
|
|||||||
vars["metric"] = m
|
vars["metric"] = m
|
||||||
value, err := f.gvalCond.EvalBool(context.Background(), vars)
|
value, err := f.gvalCond.EvalBool(context.Background(), vars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.ComponentError("MetricCache", "COLLECT", f.Name, "COND", f.Condition, ":", err.Error())
|
cclog.ComponentErrorf("MetricCache", "COLLECT %s COND '%s' : %s", f.Name, f.Condition, err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if value {
|
if value {
|
||||||
@@ -160,7 +160,7 @@ func (c *metricAggregator) Eval(starttime time.Time, endtime time.Time, metrics
|
|||||||
case bool:
|
case bool:
|
||||||
valuesBool = append(valuesBool, x)
|
valuesBool = append(valuesBool, x)
|
||||||
default:
|
default:
|
||||||
cclog.ComponentError("MetricCache", "COLLECT ADD VALUE", v, "FAILED")
|
cclog.ComponentErrorf("MetricCache", "COLLECT ADD VALUE %v FAILED", v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
matches = append(matches, m)
|
matches = append(matches, m)
|
||||||
@@ -213,13 +213,13 @@ func (c *metricAggregator) Eval(starttime time.Time, endtime time.Time, metrics
|
|||||||
vars["values"] = valuesBool
|
vars["values"] = valuesBool
|
||||||
len_values = len(valuesBool)
|
len_values = len(valuesBool)
|
||||||
}
|
}
|
||||||
cclog.ComponentDebug("MetricCache", "EVALUATE", f.Name, "METRICS", len_values, "CALC", f.Function)
|
cclog.ComponentDebugf("MetricCache", "EVALUATE %s METRICS %d CALC '%s'", f.Name, len_values, f.Function)
|
||||||
|
|
||||||
vars["metrics"] = matches
|
vars["metrics"] = matches
|
||||||
if len_values > 0 {
|
if len_values > 0 {
|
||||||
value, err := gval.Evaluate(f.Function, vars, c.language)
|
value, err := gval.Evaluate(f.Function, vars, c.language)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.ComponentError("MetricCache", "EVALUATE", f.Name, "METRICS", len_values, "CALC", f.Function, ":", err.Error())
|
cclog.ComponentErrorf("MetricCache", "EVALUATE %s METRICS %d CALC '%s': %s", f.Name, len_values, f.Function, err.Error())
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,12 +273,12 @@ func (c *metricAggregator) Eval(starttime time.Time, endtime time.Time, metrics
|
|||||||
case string:
|
case string:
|
||||||
m, err = lp.NewMessage(f.Name, tags, meta, map[string]any{"value": t}, starttime)
|
m, err = lp.NewMessage(f.Name, tags, meta, map[string]any{"value": t}, starttime)
|
||||||
default:
|
default:
|
||||||
cclog.ComponentError("MetricCache", "Gval returned invalid type", t, "skipping metric", f.Name)
|
cclog.ComponentErrorf("MetricCache", "Gval returned invalid type %s skipping metric %s", t, f.Name)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.ComponentError("MetricCache", "Cannot create metric from Gval result", value, ":", err.Error())
|
cclog.ComponentErrorf("MetricCache", "Cannot create metric from Gval result %v: %s", value, err.Error())
|
||||||
}
|
}
|
||||||
cclog.ComponentDebug("MetricCache", "SEND", m)
|
cclog.ComponentDebugf("MetricCache", "SEND %s", m.ToLineProtocol(nil))
|
||||||
select {
|
select {
|
||||||
case c.output <- m:
|
case c.output <- m:
|
||||||
default:
|
default:
|
||||||
@@ -295,12 +295,12 @@ func (c *metricAggregator) AddAggregation(name, function, condition string, tags
|
|||||||
newcond := strings.ReplaceAll(condition, "'", "\"")
|
newcond := strings.ReplaceAll(condition, "'", "\"")
|
||||||
gvalCond, err := gval.Full(metricCacheLanguage).NewEvaluable(newcond)
|
gvalCond, err := gval.Full(metricCacheLanguage).NewEvaluable(newcond)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.ComponentError("MetricAggregator", "Cannot add aggregation, invalid if condition", newcond, ":", err.Error())
|
cclog.ComponentErrorf("MetricAggregator", "Cannot add aggregation, invalid if condition '%s': %s", newcond, err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
gvalFunc, err := gval.Full(metricCacheLanguage).NewEvaluable(newfunc)
|
gvalFunc, err := gval.Full(metricCacheLanguage).NewEvaluable(newfunc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.ComponentError("MetricAggregator", "Cannot add aggregation, invalid function condition", newfunc, ":", err.Error())
|
cclog.ComponentErrorf("MetricAggregator", "Cannot add aggregation, invalid function condition %s: %s", newfunc, err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, agg := range c.functions {
|
for _, agg := range c.functions {
|
||||||
|
|||||||
Reference in New Issue
Block a user