320 lines
8.2 KiB
Go
320 lines
8.2 KiB
Go
//go:build linux && amd64
|
|
|
|
package intel_powerstat
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
|
|
ptel "github.com/intel/powertelemetry"
|
|
|
|
"github.com/influxdata/telegraf"
|
|
)
|
|
|
|
// cpuMetricType is an enum type to identify core metrics.
|
|
type cpuMetricType int
|
|
|
|
// cpuMetricType enum defines supported core metrics.
|
|
const (
|
|
// metric relying on cpuFreq
|
|
cpuFrequency cpuMetricType = iota
|
|
|
|
// metric relying on msr
|
|
cpuTemperature
|
|
|
|
// metrics relying on msr with storage
|
|
cpuC0StateResidency
|
|
cpuC1StateResidency
|
|
cpuC3StateResidency
|
|
cpuC6StateResidency
|
|
cpuC7StateResidency
|
|
cpuBusyCycles // alias of cpuC0StateResidency
|
|
cpuBusyFrequency
|
|
|
|
// metrics relying on perf
|
|
cpuC0SubstateC01Percent
|
|
cpuC0SubstateC02Percent
|
|
cpuC0SubstateC0WaitPercent
|
|
)
|
|
|
|
// Helper method to return a string representation of a core metric.
|
|
func (m cpuMetricType) String() string {
|
|
switch m {
|
|
case cpuFrequency:
|
|
return "cpu_frequency"
|
|
case cpuTemperature:
|
|
return "cpu_temperature"
|
|
case cpuBusyFrequency:
|
|
return "cpu_busy_frequency"
|
|
case cpuC0StateResidency:
|
|
return "cpu_c0_state_residency"
|
|
case cpuC1StateResidency:
|
|
return "cpu_c1_state_residency"
|
|
case cpuC3StateResidency:
|
|
return "cpu_c3_state_residency"
|
|
case cpuC6StateResidency:
|
|
return "cpu_c6_state_residency"
|
|
case cpuC7StateResidency:
|
|
return "cpu_c7_state_residency"
|
|
case cpuBusyCycles:
|
|
return "cpu_busy_cycles"
|
|
case cpuC0SubstateC01Percent:
|
|
return "cpu_c0_substate_c01"
|
|
case cpuC0SubstateC02Percent:
|
|
return "cpu_c0_substate_c02"
|
|
case cpuC0SubstateC0WaitPercent:
|
|
return "cpu_c0_substate_c0_wait"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// UnmarshalText parses the cpu metric from the TOML config file
|
|
func (m *cpuMetricType) UnmarshalText(data []byte) (err error) {
|
|
parsedMetric, err := cpuMetricTypeFromString(string(data))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
*m = parsedMetric
|
|
return nil
|
|
}
|
|
|
|
func cpuMetricTypeFromString(metric string) (cpuMetricType, error) {
|
|
switch metric {
|
|
case "cpu_frequency":
|
|
return cpuFrequency, nil
|
|
case "cpu_temperature":
|
|
return cpuTemperature, nil
|
|
case "cpu_busy_frequency":
|
|
return cpuBusyFrequency, nil
|
|
case "cpu_c0_state_residency":
|
|
return cpuC0StateResidency, nil
|
|
case "cpu_c1_state_residency":
|
|
return cpuC1StateResidency, nil
|
|
case "cpu_c3_state_residency":
|
|
return cpuC3StateResidency, nil
|
|
case "cpu_c6_state_residency":
|
|
return cpuC6StateResidency, nil
|
|
case "cpu_c7_state_residency":
|
|
return cpuC7StateResidency, nil
|
|
case "cpu_busy_cycles":
|
|
return cpuBusyCycles, nil
|
|
case "cpu_c0_substate_c01":
|
|
return cpuC0SubstateC01Percent, nil
|
|
case "cpu_c0_substate_c02":
|
|
return cpuC0SubstateC02Percent, nil
|
|
case "cpu_c0_substate_c0_wait":
|
|
return cpuC0SubstateC0WaitPercent, nil
|
|
}
|
|
|
|
return -1, fmt.Errorf("invalid cpu metric specified: %q", metric)
|
|
}
|
|
|
|
// packageMetricType is an enum type to identify package metrics.
|
|
type packageMetricType int
|
|
|
|
// packageMetricType enum defines supported package metrics.
|
|
const (
|
|
// metrics relying on rapl
|
|
packageCurrentPowerConsumption packageMetricType = iota
|
|
packageCurrentDramPowerConsumption
|
|
packageThermalDesignPower
|
|
|
|
// metrics relying on msr
|
|
packageCPUBaseFrequency
|
|
|
|
// hybrid metric relying on uncoreFreq as a primary mechanism and on msr as fallback mechanism.
|
|
packageUncoreFrequency
|
|
|
|
// metrics relying on msr
|
|
packageTurboLimit
|
|
)
|
|
|
|
// Helper method to return a string representation of a package metric.
|
|
func (m packageMetricType) String() string {
|
|
switch m {
|
|
case packageCurrentPowerConsumption:
|
|
return "current_power_consumption"
|
|
case packageCurrentDramPowerConsumption:
|
|
return "current_dram_power_consumption"
|
|
case packageThermalDesignPower:
|
|
return "thermal_design_power"
|
|
case packageCPUBaseFrequency:
|
|
return "cpu_base_frequency"
|
|
case packageUncoreFrequency:
|
|
return "uncore_frequency"
|
|
case packageTurboLimit:
|
|
return "max_turbo_frequency"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// UnmarshalText parses the package metric from the TOML config file
|
|
func (m *packageMetricType) UnmarshalText(data []byte) (err error) {
|
|
parsedMetric, err := packageMetricTypeFromString(string(data))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
*m = parsedMetric
|
|
return nil
|
|
}
|
|
|
|
func packageMetricTypeFromString(metric string) (packageMetricType, error) {
|
|
switch metric {
|
|
case "current_power_consumption":
|
|
return packageCurrentPowerConsumption, nil
|
|
case "current_dram_power_consumption":
|
|
return packageCurrentDramPowerConsumption, nil
|
|
case "thermal_design_power":
|
|
return packageThermalDesignPower, nil
|
|
case "cpu_base_frequency":
|
|
return packageCPUBaseFrequency, nil
|
|
case "uncore_frequency":
|
|
return packageUncoreFrequency, nil
|
|
case "max_turbo_frequency":
|
|
return packageTurboLimit, nil
|
|
}
|
|
|
|
return -1, fmt.Errorf("invalid package metric specified: %q", metric)
|
|
}
|
|
|
|
// numeric is a type constraint definition.
|
|
type numeric interface {
|
|
float64 | uint64
|
|
}
|
|
|
|
// metricInfoProvider provides measurement name, fields, and tags needed by the accumulator to add a metric.
|
|
type metricInfoProvider interface {
|
|
// measurement returns a string with the name of measurement.
|
|
measurement() string
|
|
|
|
// fields returns a map of string keys with metric name and metric values.
|
|
fields() (map[string]interface{}, error)
|
|
|
|
// tags returns a map of string key and string value to add additional metric-specific information.
|
|
tags() map[string]string
|
|
|
|
// name returns the name of a metric.
|
|
name() string
|
|
}
|
|
|
|
// addMetric takes a metricInfoProvider interface and adds metric information to an accumulator.
|
|
func addMetric(acc telegraf.Accumulator, m metricInfoProvider, logOnceMap map[string]struct{}) {
|
|
fields, err := m.fields()
|
|
if err == nil {
|
|
acc.AddGauge(
|
|
m.measurement(),
|
|
fields,
|
|
m.tags(),
|
|
)
|
|
return
|
|
}
|
|
|
|
// Always add to the accumulator errors not related to module not initialized.
|
|
var moduleErr *ptel.ModuleNotInitializedError
|
|
if !errors.As(err, &moduleErr) {
|
|
acc.AddError(err)
|
|
return
|
|
}
|
|
|
|
// Add only once module not initialized error related to module and metric name.
|
|
logErrorOnce(
|
|
acc,
|
|
logOnceMap,
|
|
fmt.Sprintf("%s_%s", moduleErr.Name, m.name()),
|
|
fmt.Errorf("failed to get %q: %w", m.name(), moduleErr),
|
|
)
|
|
}
|
|
|
|
// metricCommon has metric information common to different types.
|
|
type metricCommon struct {
|
|
metric interface{}
|
|
units string
|
|
}
|
|
|
|
func (m *metricCommon) name() string {
|
|
switch m.metric.(type) {
|
|
case cpuMetricType:
|
|
return m.metric.(cpuMetricType).String()
|
|
case packageMetricType:
|
|
return m.metric.(packageMetricType).String()
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func (m *metricCommon) measurement() string {
|
|
switch m.metric.(type) {
|
|
case cpuMetricType:
|
|
return "powerstat_core"
|
|
case packageMetricType:
|
|
return "powerstat_package"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// cpuMetric is a generic type that has the information to identify a CPU-related metric,
|
|
// as well as function to retrieve its value at any time. Implements metricAdder interface.
|
|
type cpuMetric[T numeric] struct {
|
|
metricCommon
|
|
|
|
cpuID int
|
|
coreID int
|
|
packageID int
|
|
fetchFn func(cpuID int) (T, error)
|
|
}
|
|
|
|
func (m *cpuMetric[T]) fields() (map[string]interface{}, error) {
|
|
val, err := m.fetchFn(m.cpuID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get %q for CPU ID %v: %w", m.metric, m.cpuID, err)
|
|
}
|
|
|
|
return map[string]interface{}{
|
|
fmt.Sprintf("%s_%s", m.metric, m.units): round(val),
|
|
}, nil
|
|
}
|
|
|
|
func (m *cpuMetric[T]) tags() map[string]string {
|
|
return map[string]string{
|
|
"core_id": strconv.Itoa(m.coreID),
|
|
"cpu_id": strconv.Itoa(m.cpuID),
|
|
"package_id": strconv.Itoa(m.packageID),
|
|
}
|
|
}
|
|
|
|
// packageMetric is a generic type that has the information to identify a package-related metric,
|
|
// as well as the function to retrieve its value at any time. Implements metricAdder interface.
|
|
type packageMetric[T numeric] struct {
|
|
metricCommon
|
|
|
|
packageID int
|
|
fetchFn func(packageID int) (T, error)
|
|
}
|
|
|
|
func (m *packageMetric[T]) fields() (map[string]interface{}, error) {
|
|
val, err := m.fetchFn(m.packageID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get %q for package ID %v: %w", m.metric, m.packageID, err)
|
|
}
|
|
|
|
return map[string]interface{}{
|
|
fmt.Sprintf("%s_%s", m.metric, m.units): round(val),
|
|
}, nil
|
|
}
|
|
|
|
func (m *packageMetric[T]) tags() map[string]string {
|
|
return map[string]string{
|
|
"package_id": strconv.Itoa(m.packageID),
|
|
}
|
|
}
|
|
|
|
// round returns the result of rounding the argument, only if it's a 64 bit floating-point type.
|
|
func round[T numeric](val T) T {
|
|
if v, ok := any(val).(float64); ok {
|
|
val = T(math.Round(v*100) / 100)
|
|
}
|
|
return val
|
|
}
|