1
0
Fork 0
telegraf/plugins/inputs/intel_powerstat/intel_powerstat_test.go

5224 lines
134 KiB
Go
Raw Normal View History

//go:build linux && amd64
package intel_powerstat
import (
"errors"
"fmt"
"strconv"
"testing"
ptel "github.com/intel/powertelemetry"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf/testutil"
)
type parsePackageMetricTestCase struct {
name string
metrics []packageMetricType
parsed []packageMetricType
err error
}
type parseCPUMetricTestCase struct {
name string
metrics []cpuMetricType
parsed []cpuMetricType
err error
}
func TestParsePackageMetrics(t *testing.T) {
testCases := []parsePackageMetricTestCase{
{
name: "NilSlice",
metrics: nil,
parsed: []packageMetricType{
packageCurrentPowerConsumption,
packageCurrentDramPowerConsumption,
packageThermalDesignPower,
},
},
{
name: "EmptySlice",
metrics: make([]packageMetricType, 0),
parsed: make([]packageMetricType, 0),
},
{
name: "HasDuplicates",
metrics: []packageMetricType{
packageCurrentPowerConsumption,
packageThermalDesignPower,
packageCurrentDramPowerConsumption,
packageThermalDesignPower, // duplicate
},
err: errors.New("package metrics contains duplicates"),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
p := &PowerStat{
PackageMetrics: tc.metrics,
}
err := p.parsePackageMetrics()
if tc.err != nil {
require.ErrorContains(t, err, tc.err.Error())
} else {
require.NoError(t, err)
require.Equal(t, tc.parsed, p.PackageMetrics)
}
})
}
}
func TestParseCPUMetrics(t *testing.T) {
testCases := []parseCPUMetricTestCase{
{
name: "NilSlice",
metrics: nil,
parsed: nil,
},
{
name: "EmptySlice",
metrics: make([]cpuMetricType, 0),
parsed: make([]cpuMetricType, 0),
},
{
name: "HasDuplicates",
metrics: []cpuMetricType{
cpuC0StateResidency,
cpuC1StateResidency,
cpuTemperature,
cpuC0StateResidency, // duplicate
},
err: errors.New("cpu metrics contains duplicates"),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
p := &PowerStat{
CPUMetrics: tc.metrics,
}
err := p.parseCPUMetrics()
if tc.err != nil {
require.ErrorContains(t, err, tc.err.Error())
} else {
require.NoError(t, err)
require.Equal(t, tc.parsed, p.CPUMetrics)
}
})
}
}
func TestParseCPUTimeRelatedMsrMetrics(t *testing.T) {
testCases := []parseCPUMetricTestCase{
{
name: "EmptySlice",
metrics: make([]cpuMetricType, 0),
parsed: make([]cpuMetricType, 0),
},
{
name: "NotFound",
metrics: []cpuMetricType{
// Metric not relying on MSR.
cpuFrequency,
// Metric relying on single MSR read.
cpuTemperature,
// Metrics relying on perf events.
cpuC0SubstateC01Percent,
cpuC0SubstateC02Percent,
cpuC0SubstateC0WaitPercent,
},
parsed: make([]cpuMetricType, 0),
},
{
name: "Found",
metrics: []cpuMetricType{
// Metric not relying on MSR.
cpuFrequency,
// Metric relying on single MSR read.
cpuTemperature,
// Metrics relying on time-related MSR offset reads.
cpuC0StateResidency,
cpuC1StateResidency,
cpuC3StateResidency,
cpuC6StateResidency,
cpuC7StateResidency,
cpuBusyCycles,
cpuBusyFrequency,
// Metrics relying on perf events.
cpuC0SubstateC01Percent,
cpuC0SubstateC02Percent,
},
parsed: []cpuMetricType{
cpuC0StateResidency,
cpuC1StateResidency,
cpuC3StateResidency,
cpuC6StateResidency,
cpuC7StateResidency,
cpuBusyCycles,
cpuBusyFrequency,
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
p := &PowerStat{
CPUMetrics: tc.metrics,
}
p.parseCPUTimeRelatedMsrMetrics()
require.Equal(t, tc.parsed, p.parsedCPUTimedMsrMetrics)
})
}
}
func TestParseCPUPerfMetrics(t *testing.T) {
testCases := []parseCPUMetricTestCase{
{
name: "EmptySlice",
metrics: make([]cpuMetricType, 0),
parsed: make([]cpuMetricType, 0),
},
{
name: "NotFound",
metrics: []cpuMetricType{
// Metric not relying on MSR.
cpuFrequency,
// Metric relying on single MSR read.
cpuTemperature,
// Metrics relying on time-related MSR offset reads.
cpuC3StateResidency,
cpuC6StateResidency,
cpuBusyFrequency,
},
parsed: make([]cpuMetricType, 0),
},
{
name: "Found",
metrics: []cpuMetricType{
// Metric not relying on MSR.
cpuFrequency,
// Metric relying on single MSR read.
cpuTemperature,
// Metrics relying on perf events.
cpuC0SubstateC01Percent,
cpuC0SubstateC02Percent,
cpuC0SubstateC0WaitPercent,
// Metrics relying on time-related MSR offset reads.
cpuC3StateResidency,
cpuC6StateResidency,
cpuBusyFrequency,
},
parsed: []cpuMetricType{
cpuC0SubstateC01Percent,
cpuC0SubstateC02Percent,
cpuC0SubstateC0WaitPercent,
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
p := &PowerStat{
CPUMetrics: tc.metrics,
}
p.parseCPUPerfMetrics()
require.Equal(t, tc.parsed, p.parsedCPUPerfMetrics)
})
}
}
func TestParsePackageRaplMetrics(t *testing.T) {
testCases := []parsePackageMetricTestCase{
{
name: "EmptySlice",
metrics: make([]packageMetricType, 0),
parsed: make([]packageMetricType, 0),
},
{
name: "NotFound",
metrics: []packageMetricType{
// Metrics not relying on rapl.
packageTurboLimit,
packageCPUBaseFrequency,
packageUncoreFrequency,
},
parsed: make([]packageMetricType, 0),
},
{
name: "Found",
metrics: []packageMetricType{
// Metrics not relying on rapl.
packageTurboLimit,
packageCPUBaseFrequency,
packageUncoreFrequency,
// Metrics relying on rapl.
packageCurrentPowerConsumption,
packageCurrentDramPowerConsumption,
packageThermalDesignPower,
},
parsed: []packageMetricType{
packageCurrentPowerConsumption,
packageCurrentDramPowerConsumption,
packageThermalDesignPower,
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
p := &PowerStat{
PackageMetrics: tc.metrics,
}
p.parsePackageRaplMetrics()
require.Equal(t, tc.parsed, p.parsedPackageRaplMetrics)
})
}
}
func TestParsePackageMsrMetrics(t *testing.T) {
testCases := []parsePackageMetricTestCase{
{
name: "EmptySlice",
metrics: make([]packageMetricType, 0),
parsed: make([]packageMetricType, 0),
},
{
name: "NotFound",
metrics: []packageMetricType{
// Metrics not relying on msr.
packageCurrentPowerConsumption,
packageCurrentDramPowerConsumption,
packageThermalDesignPower,
},
parsed: make([]packageMetricType, 0),
},
{
name: "Found",
metrics: []packageMetricType{
// Metrics not relying on msr.
packageCurrentPowerConsumption,
packageCurrentDramPowerConsumption,
packageThermalDesignPower,
// Metrics relying uniquely on msr.
packageTurboLimit,
packageCPUBaseFrequency,
},
parsed: []packageMetricType{
packageTurboLimit,
packageCPUBaseFrequency,
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
p := &PowerStat{
PackageMetrics: tc.metrics,
}
p.parsePackageMsrMetrics()
require.Equal(t, tc.parsed, p.parsedPackageMsrMetrics)
})
}
}
func TestParseCoreRange(t *testing.T) {
testCases := []struct {
name string
coreRange string
cores []int
err error
}{
{
name: "InvalidFormat",
coreRange: "1,3",
cores: nil,
err: errors.New("invalid core range format"),
},
{
name: "LowerBoundNonNumeric",
coreRange: "a-10",
cores: nil,
err: errors.New("failed to parse low bounds' core range"),
},
{
name: "MissingLowerBound",
coreRange: "-10",
cores: nil,
err: errors.New("failed to parse low bounds' core range"),
},
{
name: "HigherBoundNonNumeric",
coreRange: "0-a",
cores: nil,
err: errors.New("failed to parse high bounds' core range"),
},
{
name: "MissingHigherBound",
coreRange: "0-",
cores: nil,
err: errors.New("failed to parse high bounds' core range"),
},
{
name: "InvalidBounds",
coreRange: "10-1",
cores: nil,
err: errors.New("high bound of core range cannot be less than low bound"),
},
{
name: "SingleCore",
coreRange: "1-1",
cores: []int{1},
},
{
name: "CoreRange",
coreRange: "5-10",
cores: []int{5, 6, 7, 8, 9, 10},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
coresOut, err := parseCoreRange(tc.coreRange)
require.Equal(t, tc.cores, coresOut)
if tc.err != nil {
require.ErrorContains(t, err, tc.err.Error())
} else {
require.NoError(t, err)
}
})
}
}
func TestParseGroupCores(t *testing.T) {
testCases := []struct {
name string
coreGroup string
cores []int
err error
}{
{
name: "FailedToParseCoreRange",
coreGroup: "1-a,7,9,11",
cores: nil,
err: errors.New("failed to parse core range \"1-a\""),
},
{
name: "FailedToParseSingleCore",
coreGroup: "1-5,7,b,11",
cores: nil,
err: errors.New("failed to parse single core"),
},
{
name: "Ok",
coreGroup: "1-5,7,9,11",
cores: []int{1, 2, 3, 4, 5, 7, 9, 11},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
coresOut, err := parseGroupCores(tc.coreGroup)
require.Equal(t, tc.cores, coresOut)
if tc.err != nil {
require.ErrorContains(t, err, tc.err.Error())
} else {
require.NoError(t, err)
}
})
}
}
func TestHasDuplicate(t *testing.T) {
t.Run("Int", func(t *testing.T) {
t.Run("False", func(t *testing.T) {
nums := []int{0, 1, 2, 3}
require.False(t, hasDuplicate(nums))
})
t.Run("True", func(t *testing.T) {
nums := []uint32{0, 1, 2, 3, 4, 5, 6, 1}
require.True(t, hasDuplicate(nums))
})
})
t.Run("String", func(t *testing.T) {
t.Run("False", func(t *testing.T) {
strs := []string{"1", "2", "3", "4"}
require.False(t, hasDuplicate(strs))
})
t.Run("True", func(t *testing.T) {
strs := []string{"1", "2", "3", "1"}
require.True(t, hasDuplicate(strs))
})
})
}
func TestParseCores(t *testing.T) {
testCases := []struct {
name string
coreGroups []string
cores []int
err error
}{
{
name: "InvalidCoreGroup",
coreGroups: []string{"1-4,11", "10-b"},
cores: nil,
err: errors.New("failed to parse core group"),
},
{
name: "FoundDuplicates",
coreGroups: []string{"1-4,11", "10-12"},
cores: nil,
err: errors.New("core values cannot be duplicated"),
},
{
name: "CoresIsNil",
coreGroups: nil,
cores: make([]int, 0),
},
{
name: "CoresIsEmpty",
coreGroups: make([]string, 0),
cores: make([]int, 0),
},
{
name: "Ok",
coreGroups: []string{"1-4,6", "8", "10-12", "15,20"},
cores: []int{1, 2, 3, 4, 6, 8, 10, 11, 12, 15, 20},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
coresOut, err := parseCores(tc.coreGroups)
require.Equal(t, tc.cores, coresOut)
if tc.err != nil {
require.ErrorContains(t, err, tc.err.Error())
} else {
require.NoError(t, err)
}
})
}
}
func TestParseConfig(t *testing.T) {
t.Run("BothCPUOptionsProvided", func(t *testing.T) {
p := &PowerStat{
IncludedCPUs: []string{"0-10,20-22"},
ExcludedCPUs: []string{"0"},
}
require.ErrorContains(t, p.parseConfig(), "both 'included_cpus' and 'excluded_cpus' configured; provide only one or none of the two")
})
t.Run("FailedToParseIncludedCPUs", func(t *testing.T) {
p := &PowerStat{
// has duplicates
IncludedCPUs: []string{"1-4,11", "10-12"},
}
require.ErrorContains(t, p.parseConfig(), "failed to parse included CPUs")
})
t.Run("FailedToParseExcludedCPUs", func(t *testing.T) {
p := &PowerStat{
// has non-numeric CPU ID
ExcludedCPUs: []string{"1-4,b"},
}
require.ErrorContains(t, p.parseConfig(), "failed to parse excluded CPUs")
})
t.Run("FailedToParseCPUMetrics", func(t *testing.T) {
p := &PowerStat{
// has duplicates
CPUMetrics: []cpuMetricType{
cpuFrequency,
cpuTemperature,
cpuFrequency, // duplicate
},
}
require.ErrorContains(t, p.parseConfig(), "failed to parse cpu metrics")
})
t.Run("EventDefinitionsNotProvidedForPerf", func(t *testing.T) {
p := &PowerStat{
// has duplicates
CPUMetrics: []cpuMetricType{
cpuC0SubstateC01Percent,
},
}
require.ErrorContains(t, p.parseConfig(), "'event_definitions' contains an empty path")
})
t.Run("EventDefinitionsDoesNotExist", func(t *testing.T) {
p := &PowerStat{
// has duplicates
CPUMetrics: []cpuMetricType{
cpuC0SubstateC02Percent,
},
EventDefinitions: "./testdata/doesNotExist.json",
}
require.ErrorContains(t, p.parseConfig(), "'event_definitions' file \"./testdata/doesNotExist.json\" does not exist")
})
t.Run("NoMetricsProvided", func(t *testing.T) {
p := &PowerStat{
// Disable default package metrics.
PackageMetrics: make([]packageMetricType, 0),
}
require.ErrorContains(t, p.parseConfig(), "no metrics were found in the configuration file")
})
t.Run("DisablePackageMetrics", func(t *testing.T) {
p := &PowerStat{
CPUMetrics: []cpuMetricType{
cpuBusyFrequency,
},
// Disable default package metrics.
PackageMetrics: make([]packageMetricType, 0),
}
require.NoError(t, p.parseConfig())
require.Empty(t, p.PackageMetrics)
require.Len(t, p.CPUMetrics, 1)
})
t.Run("DefaultPackageMetrics", func(t *testing.T) {
p := &PowerStat{
PackageMetrics: nil, // default package metrics
}
require.NoError(t, p.parseConfig())
require.Equal(t,
[]packageMetricType{
packageCurrentPowerConsumption,
packageCurrentDramPowerConsumption,
packageThermalDesignPower,
},
p.PackageMetrics)
})
t.Run("IncludedCPUs", func(t *testing.T) {
p := &PowerStat{
IncludedCPUs: []string{"0-5"},
}
require.NoError(t, p.parseConfig())
require.Equal(t, []int{0, 1, 2, 3, 4, 5}, p.parsedIncludedCores)
require.Nil(t, p.parsedExcludedCores)
})
t.Run("ExcludedCPUs", func(t *testing.T) {
p := &PowerStat{
ExcludedCPUs: []string{"2-6", "8", "10"},
}
require.NoError(t, p.parseConfig())
require.Equal(t, []int{2, 3, 4, 5, 6, 8, 10}, p.parsedExcludedCores)
require.Nil(t, p.parsedIncludedCores)
})
t.Run("MetricsWithIncludedCPUs", func(t *testing.T) {
p := &PowerStat{
IncludedCPUs: []string{"0-3,6"},
CPUMetrics: []cpuMetricType{
cpuC0StateResidency,
cpuC1StateResidency,
cpuC3StateResidency,
},
PackageMetrics: []packageMetricType{
packageUncoreFrequency,
packageTurboLimit,
},
}
require.NoError(t, p.parseConfig())
require.Equal(t, []int{0, 1, 2, 3, 6}, p.parsedIncludedCores)
// Check flags
require.True(t, p.needsMsrCPU)
require.True(t, p.needsTimeRelatedMsr)
require.False(t, p.needsCoreFreq)
require.False(t, p.needsPerf)
})
}
type mockOptGenerator struct {
mock.Mock
}
func (m *mockOptGenerator) generate(cfg optConfig) []ptel.Option {
args := m.Called(cfg)
return args.Get(0).([]ptel.Option)
}
func TestSampleConfig(t *testing.T) {
p := &PowerStat{}
require.NotEmpty(t, p.SampleConfig())
}
func TestInit(t *testing.T) {
t.Run("FailedToDisableUnsupportedMetrics", func(t *testing.T) {
t.Setenv("HOST_PROC", "./testdata/cpu_model_missing")
p := &PowerStat{}
require.ErrorContains(t, p.Init(), "error occurred while parsing CPU model")
})
t.Run("FailedToParseConfigWithDuplicates", func(t *testing.T) {
t.Setenv("HOST_PROC", "./testdata")
logger := &testutil.CaptureLogger{}
p := &PowerStat{
// has duplicates
IncludedCPUs: []string{"1-4,11", "10-12"},
Log: logger,
}
require.ErrorContains(t, p.Init(), "failed to parse included CPUs")
require.Empty(t, logger.Warnings())
})
t.Run("FailedToParseConfigWithNegativeTimeout", func(t *testing.T) {
t.Setenv("HOST_PROC", "./testdata")
logger := &testutil.CaptureLogger{}
p := &PowerStat{
// negative value
MsrReadTimeout: -2,
Log: logger,
}
require.ErrorContains(t, p.Init(), "msr_read_timeout should be positive number or equal to 0 (to disable timeouts)")
require.Empty(t, logger.Warnings())
})
}
func TestStart(t *testing.T) {
t.Run("FailedToStartCPUDoesNotExist", func(t *testing.T) {
t.Setenv("HOST_PROC", "./testdata")
acc := &testutil.Accumulator{}
logger := &testutil.CaptureLogger{}
p := &PowerStat{
// has CPU ID out of bounds
parsedIncludedCores: []int{0, 9},
Log: logger,
option: &optGenerator{},
}
require.ErrorContains(t, p.Start(acc), "failed to initialize metric fetcher interface")
require.Empty(t, logger.Warnings())
})
t.Run("FailedToStartNotSupportedCPUVendor", func(t *testing.T) {
t.Setenv("HOST_PROC", "./testdata/vendor_not_supported")
acc := &testutil.Accumulator{}
logger := &testutil.CaptureLogger{}
p := &PowerStat{
Log: logger,
option: &optGenerator{},
}
require.ErrorContains(t, p.Start(acc), "host processor is not supported")
})
t.Run("WithWarning", func(t *testing.T) {
t.Setenv("HOST_PROC", "./testdata")
acc := &testutil.Accumulator{}
logger := &testutil.CaptureLogger{}
mOptGenerator := &mockOptGenerator{}
mOptGenerator.On("generate", mock.AnythingOfType("optConfig")).Return(
[]ptel.Option{
ptel.WithRapl("/dummy/path"),
},
)
p := &PowerStat{
PackageMetrics: []packageMetricType{
packageCurrentPowerConsumption, // needs rapl
},
Log: logger,
option: mOptGenerator,
}
require.NoError(t, p.Start(acc))
require.Len(t, logger.Warnings(), 1)
require.Contains(t, logger.Warnings()[0], "Plugin started with errors")
})
}
func TestGather(t *testing.T) {
t.Run("WithoutMetrics", func(t *testing.T) {
acc := &testutil.Accumulator{}
p := &PowerStat{
PackageMetrics: make([]packageMetricType, 0),
}
require.NoError(t, p.Gather(acc))
require.Empty(t, acc.Errors)
require.Empty(t, acc.GetTelegrafMetrics())
})
t.Run("WithDefaultPackageMetrics", func(t *testing.T) {
packageID := 0
packagePower := 10.0
dramPower := 5.0
thermalDesignPower := 20.0
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting available package IDs.
mFetcher.On("GetPackageIDs").Return([]int{packageID}).Once()
// mock getting current package power consumption metric.
mFetcher.On("GetCurrentPackagePowerConsumptionWatts", packageID).Return(packagePower, nil).Once()
// mock getting current dram power consumption metric.
mFetcher.On("GetCurrentDramPowerConsumptionWatts", packageID).Return(dramPower, nil).Once()
// mock getting package thermal design power metric.
mFetcher.On("GetPackageThermalDesignPowerWatts", packageID).Return(thermalDesignPower, nil).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
require.NoError(t, p.Gather(acc))
require.Empty(t, acc.Errors)
require.Len(t, acc.GetTelegrafMetrics(), 3)
require.True(t, acc.HasField("powerstat_package", "current_power_consumption_watts"))
require.True(t, acc.HasField("powerstat_package", "current_dram_power_consumption_watts"))
require.True(t, acc.HasField("powerstat_package", "thermal_design_power_watts"))
mFetcher.AssertExpectations(t)
})
t.Run("WithPackageMetrics", func(t *testing.T) {
packageIDs := []int{0, 1, 2, 3}
baseFreq := uint64(200)
packagePower := 30.0
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting available package IDs.
mFetcher.On("GetPackageIDs").Return(packageIDs).Once()
// mock getting CPU base frequency metric.
mFetcher.On("GetCPUBaseFrequency", mock.AnythingOfType("int")).Return(baseFreq, nil).Times(len(packageIDs))
// mock getting current package power consumption metric.
mFetcher.On("GetCurrentPackagePowerConsumptionWatts", mock.AnythingOfType("int")).Return(packagePower, nil).Times(len(packageIDs))
p := &PowerStat{
PackageMetrics: []packageMetricType{
packageCurrentPowerConsumption,
packageCPUBaseFrequency,
},
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
require.NoError(t, p.Gather(acc))
require.Empty(t, acc.Errors)
require.Len(t, acc.GetTelegrafMetrics(), 8)
require.True(t, acc.HasField("powerstat_package", "cpu_base_frequency_mhz"))
require.True(t, acc.HasField("powerstat_package", "current_power_consumption_watts"))
mFetcher.AssertExpectations(t)
})
t.Run("WithCPUMetrics", func(t *testing.T) {
cpuIDs := []int{0, 1, 2, 3}
cpuFreq := 123.5
cpuTemp := uint64(20)
cpuBusyFreq := 456.7
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting available CPU IDs with access to msr registers and coreFreq.
mFetcher.On("GetMsrCPUIDs").Return(cpuIDs).Once()
// mock getting core ID for CPU IDs.
mFetcher.On("GetCPUCoreID", mock.AnythingOfType("int")).Return(1, nil).Times(len(cpuIDs))
// mock getting package ID for CPU IDs.
mFetcher.On("GetCPUPackageID", mock.AnythingOfType("int")).Return(1, nil).Times(len(cpuIDs))
// mock updating msr time-related metrics for CPU IDs.
mFetcher.On("UpdatePerCPUMetrics", mock.AnythingOfType("int")).Return(nil).Times(len(cpuIDs))
// mock getting CPU frequency for CPU IDs.
mFetcher.On("GetCPUFrequency", mock.AnythingOfType("int")).Return(cpuFreq, nil).Times(len(cpuIDs))
// mock getting CPU temperature metric for CPU IDs.
mFetcher.On("GetCPUTemperature", mock.AnythingOfType("int")).Return(cpuTemp, nil).Times(len(cpuIDs))
// mock getting CPU busy frequency metric for CPU IDs.
mFetcher.On("GetCPUBusyFrequencyMhz", mock.AnythingOfType("int")).Return(cpuBusyFreq, nil).Times(len(cpuIDs))
p := &PowerStat{
// Disables package metrics
PackageMetrics: make([]packageMetricType, 0),
CPUMetrics: []cpuMetricType{
cpuFrequency,
cpuTemperature,
cpuBusyFrequency,
},
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
require.NoError(t, p.Gather(acc))
require.Empty(t, acc.Errors)
require.Len(t, acc.GetTelegrafMetrics(), 12)
require.True(t, acc.HasField("powerstat_core", "cpu_frequency_mhz"))
require.True(t, acc.HasField("powerstat_core", "cpu_temperature_celsius"))
require.True(t, acc.HasField("powerstat_core", "cpu_busy_frequency_mhz"))
mFetcher.AssertExpectations(t)
})
t.Run("WithPerfMetrics", func(t *testing.T) {
cpuIDs := []int{0, 1, 2}
c01 := 0.1
c02 := 1.2
c0Wait := 2.3
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock reading perf events.
mFetcher.On("ReadPerfEvents").Return(nil).Once()
// mock getting available CPU IDs with access to msr registers and coreFreq.
mFetcher.On("GetPerfCPUIDs").Return(cpuIDs).Once()
// mock getting core ID for CPU IDs.
mFetcher.On("GetCPUCoreID", mock.AnythingOfType("int")).Return(1, nil).Times(len(cpuIDs))
// mock getting package ID for CPU IDs.
mFetcher.On("GetCPUPackageID", mock.AnythingOfType("int")).Return(1, nil).Times(len(cpuIDs))
// mock getting CPU C01 metric.
mFetcher.On("GetCPUC0SubstateC01Percent", mock.AnythingOfType("int")).Return(c01, nil).Times(len(cpuIDs))
// mock getting CPU C02 metric.
mFetcher.On("GetCPUC0SubstateC02Percent", mock.AnythingOfType("int")).Return(c02, nil).Times(len(cpuIDs))
// mock getting CPU C0Wait metric.
mFetcher.On("GetCPUC0SubstateC0WaitPercent", mock.AnythingOfType("int")).Return(c0Wait, nil).Times(len(cpuIDs))
p := &PowerStat{
// Disables package metrics
PackageMetrics: make([]packageMetricType, 0),
CPUMetrics: []cpuMetricType{
cpuC0SubstateC01Percent,
cpuC0SubstateC02Percent,
cpuC0SubstateC0WaitPercent,
},
EventDefinitions: "./testdata/sapphirerapids_core.json",
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
require.NoError(t, p.Gather(acc))
require.Empty(t, acc.Errors)
require.Len(t, acc.GetTelegrafMetrics(), 9)
require.True(t, acc.HasField("powerstat_core", "cpu_c0_substate_c01_percent"))
require.True(t, acc.HasField("powerstat_core", "cpu_c0_substate_c02_percent"))
require.True(t, acc.HasField("powerstat_core", "cpu_c0_substate_c0_wait_percent"))
mFetcher.AssertExpectations(t)
})
t.Run("WithPerfAndMsrCPUMetrics", func(t *testing.T) {
cpuIDsMsr := []int{0, 1, 2, 3}
cpuIDsPerf := []int{0, 1}
c1 := 0.5
c6 := 1.5
c01 := 0.1
c02 := 1.2
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting available CPU IDs with access to msr registers.
mFetcher.On("GetMsrCPUIDs").Return(cpuIDsMsr).Once()
// mock getting core ID for CPU IDs.
mFetcher.On("GetCPUCoreID", mock.AnythingOfType("int")).Return(1, nil).Times(len(cpuIDsMsr) + len(cpuIDsPerf))
// mock getting package ID for CPU IDs.
mFetcher.On("GetCPUPackageID", mock.AnythingOfType("int")).Return(1, nil).Times(len(cpuIDsMsr) + len(cpuIDsPerf))
// mock updating msr time-related metrics for CPU IDs.
mFetcher.On("UpdatePerCPUMetrics", mock.AnythingOfType("int")).Return(nil).Times(len(cpuIDsMsr))
// mock getting CPU C1 state residency metric for CPU IDs.
mFetcher.On("GetCPUC1StateResidency", mock.AnythingOfType("int")).Return(c1, nil).Times(len(cpuIDsMsr))
// mock getting CPU C6 state residency metric for CPU IDs.
mFetcher.On("GetCPUC6StateResidency", mock.AnythingOfType("int")).Return(c6, nil).Times(len(cpuIDsMsr))
// mock reading perf events.
mFetcher.On("ReadPerfEvents").Return(nil).Once()
// mock getting available CPU IDs with access to msr registers and coreFreq.
mFetcher.On("GetPerfCPUIDs").Return(cpuIDsPerf).Once()
// mock getting CPU C01 metric.
mFetcher.On("GetCPUC0SubstateC01Percent", mock.AnythingOfType("int")).Return(c01, nil).Times(len(cpuIDsPerf))
// mock getting CPU C02 metric.
mFetcher.On("GetCPUC0SubstateC02Percent", mock.AnythingOfType("int")).Return(c02, nil).Times(len(cpuIDsPerf))
p := &PowerStat{
// Disables package metrics
PackageMetrics: make([]packageMetricType, 0),
CPUMetrics: []cpuMetricType{
cpuC0SubstateC01Percent,
cpuC0SubstateC02Percent,
cpuC1StateResidency,
cpuC6StateResidency,
},
EventDefinitions: "./testdata/sapphirerapids_core.json",
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
require.NoError(t, p.Gather(acc))
require.Empty(t, acc.Errors)
require.Len(t, acc.GetTelegrafMetrics(), 12)
require.True(t, acc.HasField("powerstat_core", "cpu_c0_substate_c01_percent"))
require.True(t, acc.HasField("powerstat_core", "cpu_c0_substate_c02_percent"))
require.True(t, acc.HasField("powerstat_core", "cpu_c1_state_residency_percent"))
require.True(t, acc.HasField("powerstat_core", "cpu_c6_state_residency_percent"))
mFetcher.AssertExpectations(t)
})
t.Run("WithCPUAndPackageMetrics", func(t *testing.T) {
cpuIDs := []int{10, 12}
packageIDs := []int{0, 1, 2, 3}
dieIDs := []int{0, 1}
c7 := 0.12
initMin := 200.0
initMax := 1200.0
currMin := 300.0
currMax := 1300.0
curr := 800.0
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting available CPU IDs with access to msr registers and coreFreq.
mFetcher.On("GetMsrCPUIDs").Return(cpuIDs).Once()
// mock getting core ID for CPU IDs.
mFetcher.On("GetCPUCoreID", mock.AnythingOfType("int")).Return(1, nil).Times(len(cpuIDs))
// mock getting package ID for CPU IDs.
mFetcher.On("GetCPUPackageID", mock.AnythingOfType("int")).Return(1, nil).Times(len(cpuIDs))
// mock updating msr time-related metrics for CPU IDs.
mFetcher.On("UpdatePerCPUMetrics", mock.AnythingOfType("int")).Return(nil).Times(len(cpuIDs))
// mock getting C7 state residency metric for CPU IDs.
mFetcher.On("GetCPUC7StateResidency", mock.AnythingOfType("int")).Return(c7, nil).Times(len(cpuIDs))
// mock getting available package IDs.
mFetcher.On("GetPackageIDs").Return(packageIDs).Once()
// mock getting die IDs for package ID.
mFetcher.On("GetPackageDieIDs", mock.AnythingOfType("int")).Return(dieIDs, nil).Times(len(packageIDs))
// mock getting initial minimum uncore frequency limit.
mFetcher.On("GetInitialUncoreFrequencyMin", mock.AnythingOfType("int"), mock.AnythingOfType("int")).Return(initMin, nil).
Times(len(packageIDs) * len(dieIDs))
// mock getting initial maximum uncore frequency limit.
mFetcher.On("GetInitialUncoreFrequencyMax", mock.AnythingOfType("int"), mock.AnythingOfType("int")).Return(initMax, nil).
Times(len(packageIDs) * len(dieIDs))
// mock getting custom minimum uncore frequency limit.
mFetcher.On("GetCustomizedUncoreFrequencyMin", mock.AnythingOfType("int"), mock.AnythingOfType("int")).Return(currMin, nil).
Times(len(packageIDs) * len(dieIDs))
// mock getting custom maximum uncore frequency limit.
mFetcher.On("GetCustomizedUncoreFrequencyMax", mock.AnythingOfType("int"), mock.AnythingOfType("int")).Return(currMax, nil).
Times(len(packageIDs) * len(dieIDs))
// mock getting current uncore frequency value.
mFetcher.On("GetCurrentUncoreFrequency", mock.AnythingOfType("int"), mock.AnythingOfType("int")).Return(curr, nil).Times(len(packageIDs) * len(dieIDs))
p := &PowerStat{
PackageMetrics: []packageMetricType{
packageUncoreFrequency,
},
CPUMetrics: []cpuMetricType{
cpuC7StateResidency,
},
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
require.NoError(t, p.Gather(acc))
require.Empty(t, acc.Errors)
require.Len(t, acc.GetTelegrafMetrics(), 18)
require.True(t, acc.HasField("powerstat_core", "cpu_c7_state_residency_percent"))
require.True(t, acc.HasField("powerstat_package", "uncore_frequency_limit_mhz_min"))
require.True(t, acc.HasField("powerstat_package", "uncore_frequency_limit_mhz_max"))
require.True(t, acc.HasField("powerstat_package", "uncore_frequency_mhz_cur"))
require.True(t, acc.HasTag("powerstat_package", "type"))
mFetcher.AssertExpectations(t)
})
}
func TestStop(t *testing.T) {
t.Run("NoErrorWithoutPerf", func(t *testing.T) {
logger := &testutil.CaptureLogger{}
p := &PowerStat{
Log: logger,
needsPerf: false,
}
p.Stop()
require.Empty(t, logger.Errors())
})
t.Run("FailedToDeactivatePerfEvents", func(t *testing.T) {
logger := &testutil.CaptureLogger{}
mFetcher := &fetcherMock{}
// mock deactivating perf events.
mFetcher.On("DeactivatePerfEvents").Return(errors.New("mock error")).Once()
p := &PowerStat{
Log: logger,
fetcher: mFetcher,
needsPerf: true,
}
p.Stop()
require.Len(t, logger.Errors(), 1)
require.Contains(t, logger.Errors()[0], "Failed to deactivate perf events")
})
t.Run("NoErrorWithPerf", func(t *testing.T) {
logger := &testutil.CaptureLogger{}
mFetcher := &fetcherMock{}
// mock deactivating perf events.
mFetcher.On("DeactivatePerfEvents").Return(nil).Once()
p := &PowerStat{
Log: logger,
fetcher: mFetcher,
needsPerf: true,
}
p.Stop()
require.Empty(t, logger.Errors())
})
}
func TestDisableUnsupportedMetrics(t *testing.T) {
t.Run("ModelMissing", func(t *testing.T) {
t.Setenv("HOST_PROC", "./testdata/cpu_model_missing")
p := &PowerStat{}
err := p.disableUnsupportedMetrics()
require.Error(t, err, "error occurred while parsing CPU model")
})
t.Run("MsrFlagNotFound", func(t *testing.T) {
t.Setenv("HOST_PROC", "./testdata/msr_flag_not_found")
logger := &testutil.CaptureLogger{}
p := &PowerStat{
CPUMetrics: []cpuMetricType{
// Metrics relying on msr flag
cpuC0StateResidency,
cpuC1StateResidency,
cpuC6StateResidency,
cpuBusyFrequency,
cpuBusyCycles,
cpuTemperature,
},
PackageMetrics: []packageMetricType{
// Metrics relying on msr flag
packageCPUBaseFrequency,
packageTurboLimit,
// Metrics not relying on msr flag
packageCurrentPowerConsumption,
packageThermalDesignPower,
},
Log: logger,
}
err := p.disableUnsupportedMetrics()
require.NoError(t, err)
require.Empty(t, p.CPUMetrics)
require.Len(t, p.PackageMetrics, 2)
require.Contains(t, p.PackageMetrics, packageCurrentPowerConsumption)
require.Contains(t, p.PackageMetrics, packageThermalDesignPower)
require.Len(t, logger.Warnings(), 8)
})
t.Run("AperfMperfFlagNotFound", func(t *testing.T) {
t.Setenv("HOST_PROC", "./testdata/aperfmperf_flag_not_found")
logger := &testutil.CaptureLogger{}
p := &PowerStat{
CPUMetrics: []cpuMetricType{
// Metrics relying on aperfmperf flag
cpuC0StateResidency,
cpuC1StateResidency,
cpuBusyFrequency,
cpuBusyCycles,
// Metrics not relying on aperfmperf flag
cpuTemperature,
},
Log: logger,
}
err := p.disableUnsupportedMetrics()
require.NoError(t, err)
require.Len(t, p.CPUMetrics, 1)
require.Contains(t, p.CPUMetrics, cpuTemperature)
require.Len(t, logger.Warnings(), 4)
})
t.Run("DtsFlagNotFound", func(t *testing.T) {
t.Setenv("HOST_PROC", "./testdata/dts_flag_not_found")
logger := &testutil.CaptureLogger{}
p := &PowerStat{
CPUMetrics: []cpuMetricType{
// Metrics relying on dts flag
cpuTemperature,
// Metrics not relying on dts flag
cpuBusyFrequency,
},
PackageMetrics: make([]packageMetricType, 0),
Log: logger,
}
err := p.disableUnsupportedMetrics()
require.NoError(t, err)
require.Len(t, p.CPUMetrics, 1)
require.Contains(t, p.CPUMetrics, cpuBusyFrequency)
require.Len(t, logger.Warnings(), 1)
})
t.Run("ModelNotSupported", func(t *testing.T) {
t.Setenv("HOST_PROC", "./testdata/model_not_supported")
logger := &testutil.CaptureLogger{}
p := &PowerStat{
CPUMetrics: []cpuMetricType{
// Metrics not supported by CPU
cpuTemperature,
cpuC1StateResidency,
cpuC3StateResidency,
cpuC6StateResidency,
cpuC7StateResidency,
},
PackageMetrics: []packageMetricType{
// Metrics not supported by CPU
packageCPUBaseFrequency,
packageUncoreFrequency,
},
Log: logger,
}
err := p.disableUnsupportedMetrics()
require.NoError(t, err)
require.Empty(t, p.CPUMetrics)
require.Contains(t, p.PackageMetrics, packageUncoreFrequency)
require.Len(t, logger.Warnings(), 6)
})
}
func TestDisableCPUMetric(t *testing.T) {
t.Run("NoMetricsRemoved", func(t *testing.T) {
expStartLen := 1
expEndLen := 1
logger := &testutil.CaptureLogger{}
p := &PowerStat{
CPUMetrics: []cpuMetricType{cpuC1StateResidency},
Log: logger,
}
require.Len(t, p.CPUMetrics, expStartLen)
p.disableCPUMetric(cpuC3StateResidency)
require.Len(t, p.CPUMetrics, expEndLen)
require.Empty(t, logger.Warnings())
})
t.Run("TwoMetricsRemoved", func(t *testing.T) {
expStartLen := 3
expEndLen := 1
logger := &testutil.CaptureLogger{}
p := &PowerStat{
CPUMetrics: []cpuMetricType{cpuC1StateResidency, cpuC3StateResidency, cpuC6StateResidency},
Log: logger,
}
require.Len(t, p.CPUMetrics, expStartLen)
p.disableCPUMetric(cpuC3StateResidency)
p.disableCPUMetric(cpuC1StateResidency)
require.Len(t, p.CPUMetrics, expEndLen)
require.Len(t, logger.Warnings(), 2)
require.Contains(t, logger.Warnings()[0], "\"cpu_c3_state_residency\" is not supported by CPU, metric will not be gathered")
require.Contains(t, logger.Warnings()[1], "\"cpu_c1_state_residency\" is not supported by CPU, metric will not be gathered")
})
}
func TestDisablePackageMetric(t *testing.T) {
t.Run("NoMetricsRemoved", func(t *testing.T) {
expStartLen := 1
expEndLen := 1
logger := &testutil.CaptureLogger{}
p := &PowerStat{
PackageMetrics: []packageMetricType{packageCurrentPowerConsumption},
Log: logger,
}
require.Len(t, p.PackageMetrics, expStartLen)
p.disablePackageMetric(packageCPUBaseFrequency)
require.Len(t, p.PackageMetrics, expEndLen)
require.Empty(t, logger.Warnings())
})
t.Run("TwoMetricsRemoved", func(t *testing.T) {
expStartLen := 3
expEndLen := 1
logger := &testutil.CaptureLogger{}
p := &PowerStat{
PackageMetrics: []packageMetricType{packageCurrentPowerConsumption, packageTurboLimit, packageCPUBaseFrequency},
Log: logger,
}
require.Len(t, p.PackageMetrics, expStartLen)
p.disablePackageMetric(packageCPUBaseFrequency)
p.disablePackageMetric(packageTurboLimit)
require.Len(t, p.PackageMetrics, expEndLen)
require.Len(t, logger.Warnings(), 2)
require.Contains(t, logger.Warnings()[0], "\"cpu_base_frequency\" is not supported by CPU, metric will not be gathered")
require.Contains(t, logger.Warnings()[1], "\"max_turbo_frequency\" is not supported by CPU, metric will not be gathered")
})
}
type fetcherMock struct {
mock.Mock
}
func (m *fetcherMock) GetMsrCPUIDs() []int {
args := m.Called()
if args.Get(0) == nil {
return nil
}
return args.Get(0).([]int)
}
func (m *fetcherMock) GetPerfCPUIDs() []int {
args := m.Called()
if args.Get(0) == nil {
return nil
}
return args.Get(0).([]int)
}
func (m *fetcherMock) GetPackageIDs() []int {
args := m.Called()
if args.Get(0) == nil {
return nil
}
return args.Get(0).([]int)
}
func (m *fetcherMock) GetCPUPackageID(cpuID int) (int, error) {
args := m.Called(cpuID)
return args.Int(0), args.Error(1)
}
func (m *fetcherMock) GetCPUCoreID(cpuID int) (int, error) {
args := m.Called(cpuID)
return args.Int(0), args.Error(1)
}
func (m *fetcherMock) GetPackageDieIDs(packageID int) ([]int, error) {
args := m.Called(packageID)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).([]int), args.Error(1)
}
func (m *fetcherMock) GetCPUFrequency(cpuID int) (float64, error) {
args := m.Called(cpuID)
return args.Get(0).(float64), args.Error(1)
}
func (m *fetcherMock) UpdatePerCPUMetrics(cpuID int) error {
args := m.Called(cpuID)
return args.Error(0)
}
func (m *fetcherMock) GetCPUTemperature(cpuID int) (uint64, error) {
args := m.Called(cpuID)
return args.Get(0).(uint64), args.Error(1)
}
func (m *fetcherMock) GetCPUC0StateResidency(cpuID int) (float64, error) {
args := m.Called(cpuID)
return args.Get(0).(float64), args.Error(1)
}
func (m *fetcherMock) GetCPUC1StateResidency(cpuID int) (float64, error) {
args := m.Called(cpuID)
return args.Get(0).(float64), args.Error(1)
}
func (m *fetcherMock) GetCPUC3StateResidency(cpuID int) (float64, error) {
args := m.Called(cpuID)
return args.Get(0).(float64), args.Error(1)
}
func (m *fetcherMock) GetCPUC6StateResidency(cpuID int) (float64, error) {
args := m.Called(cpuID)
return args.Get(0).(float64), args.Error(1)
}
func (m *fetcherMock) GetCPUC7StateResidency(cpuID int) (float64, error) {
args := m.Called(cpuID)
return args.Get(0).(float64), args.Error(1)
}
func (m *fetcherMock) GetCPUBusyFrequencyMhz(cpuID int) (float64, error) {
args := m.Called(cpuID)
return args.Get(0).(float64), args.Error(1)
}
func (m *fetcherMock) ReadPerfEvents() error {
args := m.Called()
return args.Error(0)
}
func (m *fetcherMock) DeactivatePerfEvents() error {
args := m.Called()
return args.Error(0)
}
func (m *fetcherMock) GetCPUC0SubstateC01Percent(cpuID int) (float64, error) {
args := m.Called(cpuID)
return args.Get(0).(float64), args.Error(1)
}
func (m *fetcherMock) GetCPUC0SubstateC02Percent(cpuID int) (float64, error) {
args := m.Called(cpuID)
return args.Get(0).(float64), args.Error(1)
}
func (m *fetcherMock) GetCPUC0SubstateC0WaitPercent(cpuID int) (float64, error) {
args := m.Called(cpuID)
return args.Get(0).(float64), args.Error(1)
}
func (m *fetcherMock) GetCPUBaseFrequency(packageID int) (uint64, error) {
args := m.Called(packageID)
return args.Get(0).(uint64), args.Error(1)
}
func (m *fetcherMock) GetInitialUncoreFrequencyMin(packageID, dieID int) (float64, error) {
args := m.Called(packageID, dieID)
return args.Get(0).(float64), args.Error(1)
}
func (m *fetcherMock) GetCustomizedUncoreFrequencyMin(packageID, dieID int) (float64, error) {
args := m.Called(packageID, dieID)
return args.Get(0).(float64), args.Error(1)
}
func (m *fetcherMock) GetInitialUncoreFrequencyMax(packageID, dieID int) (float64, error) {
args := m.Called(packageID, dieID)
return args.Get(0).(float64), args.Error(1)
}
func (m *fetcherMock) GetCustomizedUncoreFrequencyMax(packageID, dieID int) (float64, error) {
args := m.Called(packageID, dieID)
return args.Get(0).(float64), args.Error(1)
}
func (m *fetcherMock) GetCurrentUncoreFrequency(packageID, dieID int) (float64, error) {
args := m.Called(packageID, dieID)
return args.Get(0).(float64), args.Error(1)
}
func (m *fetcherMock) GetCurrentPackagePowerConsumptionWatts(packageID int) (float64, error) {
args := m.Called(packageID)
return args.Get(0).(float64), args.Error(1)
}
func (m *fetcherMock) GetCurrentDramPowerConsumptionWatts(packageID int) (float64, error) {
args := m.Called(packageID)
return args.Get(0).(float64), args.Error(1)
}
func (m *fetcherMock) GetPackageThermalDesignPowerWatts(packageID int) (float64, error) {
args := m.Called(packageID)
return args.Get(0).(float64), args.Error(1)
}
func (m *fetcherMock) GetMaxTurboFreqList(packageID int) ([]ptel.MaxTurboFreq, error) {
args := m.Called(packageID)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).([]ptel.MaxTurboFreq), args.Error(1)
}
func TestAddCPUMetrics(t *testing.T) {
// Disable package metrics when parseConfig method is called.
packageMetrics := make([]packageMetricType, 0)
t.Run("NoAvailableCPUs", func(t *testing.T) {
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting available CPU IDs with access to msr registers.
mFetcher.On("GetMsrCPUIDs").Return(nil).Once()
p := &PowerStat{
fetcher: mFetcher,
}
p.addCPUMetrics(acc)
require.Empty(t, acc.Errors)
require.Empty(t, acc.GetTelegrafMetrics())
mFetcher.AssertExpectations(t)
})
t.Run("WithDataCPUIDErrors", func(t *testing.T) {
t.Run("SingleCPUID", func(t *testing.T) {
cpuID := 0
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting available CPU IDs with access to msr registers and coreFreq.
mFetcher.On("GetMsrCPUIDs").Return([]int{cpuID}).Once()
// mock getting core ID for CPU ID.
mFetcher.On("GetCPUCoreID", cpuID).Return(0, errors.New("mock error")).Once()
p := &PowerStat{
fetcher: mFetcher,
}
p.addCPUMetrics(acc)
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get coreFreq and/or msr metrics for CPU ID %v", cpuID))
require.Empty(t, acc.GetTelegrafMetrics())
mFetcher.AssertExpectations(t)
})
t.Run("MultipleCPUIDs", func(t *testing.T) {
cpuID := 1
coreID := 2
packageID := 3
cpuFreq := 500.0
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting available CPU IDs with access to msr registers and coreFreq.
mFetcher.On("GetMsrCPUIDs").Return([]int{0, cpuID}).Once()
// mock getting core ID for CPU ID 0.
mFetcher.On("GetCPUCoreID", 0).Return(0, errors.New("mock error")).Once()
// mock getting core ID for CPU ID 1.
mFetcher.On("GetCPUCoreID", cpuID).Return(coreID, nil).Once()
// mock getting package ID for CPU ID 1.
mFetcher.On("GetCPUPackageID", cpuID).Return(packageID, nil).Once()
// mock getting CPU frequency for CPU ID 1.
mFetcher.On("GetCPUFrequency", cpuID).Return(cpuFreq, nil).Once()
p := &PowerStat{
CPUMetrics: []cpuMetricType{
// Metric which relies on coreFreq.
cpuFrequency,
// Metrics which do not rely on coreFreq nor msr.
cpuC0SubstateC01Percent,
cpuC0SubstateC02Percent,
},
PackageMetrics: packageMetrics,
EventDefinitions: "./testdata/sapphirerapids_core.json",
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
p.addCPUMetrics(acc)
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), "failed to get coreFreq and/or msr metrics for CPU ID 0")
require.Len(t, acc.GetTelegrafMetrics(), 1)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_frequency_mhz": cpuFreq,
},
// tags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
mFetcher.AssertExpectations(t)
})
})
t.Run("WithCoreFreqMetrics", func(t *testing.T) {
cpuFreq := 500.0
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting available CPU IDs with access to coreFreq.
mFetcher.On("GetMsrCPUIDs").Return([]int{0, 1}).Once()
// mock getting corresponding core ID to CPU IDs 0 and 1.
mFetcher.On("GetCPUCoreID", mock.AnythingOfType("int")).Return(0, nil).Twice()
// mock getting corresponding package ID to CPU IDs 0 and 1.
mFetcher.On("GetCPUPackageID", mock.AnythingOfType("int")).Return(1, nil).Twice()
// mock getting CPU frequency for CPU ID 0.
mFetcher.On("GetCPUFrequency", 0).Return(cpuFreq, nil).Once()
// mock getting CPU frequency for CPU ID 1.
mFetcher.On("GetCPUFrequency", 1).Return(0.0, errors.New("mock error")).Once()
p := &PowerStat{
CPUMetrics: []cpuMetricType{
// Metric which relies on coreFreq.
cpuFrequency,
// Metrics which do not rely on coreFreq nor msr
cpuC0SubstateC01Percent,
cpuC0SubstateC02Percent,
},
PackageMetrics: packageMetrics,
EventDefinitions: "./testdata/sapphirerapids_core.json",
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
p.addCPUMetrics(acc)
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q for CPU ID 1", cpuFrequency))
require.Len(t, acc.GetTelegrafMetrics(), 1)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_frequency_mhz": cpuFreq,
},
// tags
map[string]string{
"cpu_id": "0",
"core_id": "0",
"package_id": "1",
},
)
mFetcher.AssertExpectations(t)
})
t.Run("WithMsrMetrics", func(t *testing.T) {
t.Run("SingleRead", func(t *testing.T) {
cpuTemp := uint64(18)
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting available CPU IDs with access to msr registers.
mFetcher.On("GetMsrCPUIDs").Return([]int{0, 1}).Once()
// mock getting corresponding core ID to CPU IDs 0 and 1.
mFetcher.On("GetCPUCoreID", mock.AnythingOfType("int")).Return(0, nil).Twice()
// mock getting corresponding package ID to CPU IDs 0 and 1.
mFetcher.On("GetCPUPackageID", mock.AnythingOfType("int")).Return(1, nil).Twice()
// mock getting CPU temperature metric for CPU ID 0.
mFetcher.On("GetCPUTemperature", 0).Return(cpuTemp, nil).Once()
// mock getting CPU temperature metric for CPU ID 1.
mFetcher.On("GetCPUTemperature", 1).Return(uint64(0), errors.New("mock error")).Once()
p := &PowerStat{
CPUMetrics: []cpuMetricType{
// Metrics which rely on single-read msr registers.
cpuTemperature,
// Metrics which do not rely on coreFreq nor msr
cpuC0SubstateC01Percent,
cpuC0SubstateC02Percent,
},
PackageMetrics: packageMetrics,
EventDefinitions: "./testdata/sapphirerapids_core.json",
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
p.addCPUMetrics(acc)
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q for CPU ID 1", cpuTemperature))
require.Len(t, acc.GetTelegrafMetrics(), 1)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_temperature_celsius": cpuTemp,
},
// tags
map[string]string{
"cpu_id": "0",
"core_id": "0",
"package_id": "1",
},
)
mFetcher.AssertExpectations(t)
})
t.Run("TimeRelated", func(t *testing.T) {
cpuBusyFreq := 750.0
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting available CPU IDs with access to msr registers.
mFetcher.On("GetMsrCPUIDs").Return([]int{0, 1}).Once()
// mock getting corresponding core ID to CPU IDs 0 and 1.
mFetcher.On("GetCPUCoreID", mock.AnythingOfType("int")).Return(0, nil).Twice()
// mock getting corresponding package ID to CPU IDs 0 and 1.
mFetcher.On("GetCPUPackageID", mock.AnythingOfType("int")).Return(1, nil).Twice()
// mock updating msr time-related metrics for CPU ID 0.
mFetcher.On("UpdatePerCPUMetrics", 0).Return(errors.New("mock error")).Once()
// mock updating msr time-related metrics for CPU ID 1.
mFetcher.On("UpdatePerCPUMetrics", 1).Return(nil).Once()
// mock getting CPU busy frequency metric for CPU ID 1.
mFetcher.On("GetCPUBusyFrequencyMhz", 1).Return(cpuBusyFreq, nil).Once()
p := &PowerStat{
CPUMetrics: []cpuMetricType{
// Metrics which rely on time-related msr reads.
cpuBusyFrequency,
// Metrics which do not rely on coreFreq nor msr
cpuC0SubstateC01Percent,
cpuC0SubstateC02Percent,
cpuC0SubstateC0WaitPercent,
},
PackageMetrics: packageMetrics,
EventDefinitions: "./testdata/sapphirerapids_core.json",
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
p.addCPUMetrics(acc)
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), "failed to update MSR time-related metrics for CPU ID 0")
require.Len(t, acc.GetTelegrafMetrics(), 1)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_busy_frequency_mhz": cpuBusyFreq,
},
// tags
map[string]string{
"cpu_id": "1",
"core_id": "0",
"package_id": "1",
},
)
mFetcher.AssertExpectations(t)
})
})
}
func TestAddPerCPUMsrMetrics(t *testing.T) {
// Disable package metrics when parseConfig method is called.
packageMetrics := make([]packageMetricType, 0)
t.Run("WithoutMsrMetrics", func(t *testing.T) {
cpuID := 0
coreID := 1
packageID := 0
acc := &testutil.Accumulator{}
p := &PowerStat{
CPUMetrics: []cpuMetricType{
// metrics which do not rely on msr
cpuFrequency,
cpuC0SubstateC01Percent,
},
}
p.addPerCPUMsrMetrics(acc, cpuID, coreID, packageID)
require.Empty(t, acc.Errors)
require.Empty(t, acc.GetTelegrafMetrics())
})
t.Run("WithSingleMsrReadMetrics", func(t *testing.T) {
cpuID := 0
coreID := 1
packageID := 0
cpuMetrics := []cpuMetricType{
// metric that relies on a single msr read.
cpuTemperature,
// metrics that do not rely on msr.
cpuFrequency,
cpuC0SubstateC01Percent,
}
t.Run("WithError", func(t *testing.T) {
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting CPU temperature metric.
mFetcher.On("GetCPUTemperature", cpuID).Return(uint64(0), errors.New("mock error")).Once()
p := &PowerStat{
CPUMetrics: cpuMetrics,
PackageMetrics: packageMetrics,
EventDefinitions: "./testdata/sapphirerapids_core.json",
fetcher: mFetcher,
logOnce: map[string]struct{}{},
}
require.NoError(t, p.parseConfig())
p.addPerCPUMsrMetrics(acc, cpuID, coreID, packageID)
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q for CPU ID %v", cpuTemperature, cpuID))
require.Empty(t, p.logOnce)
require.Empty(t, acc.GetTelegrafMetrics())
mFetcher.AssertExpectations(t)
})
t.Run("WithModuleNotInitializedError", func(t *testing.T) {
acc := &testutil.Accumulator{}
mErr := &ptel.ModuleNotInitializedError{Name: "msr"}
mFetcher := &fetcherMock{}
// mock getting CPU temperature metric.
mFetcher.On("GetCPUTemperature", cpuID).Return(uint64(0), mErr).Twice()
p := &PowerStat{
CPUMetrics: cpuMetrics,
PackageMetrics: packageMetrics,
EventDefinitions: "./testdata/sapphirerapids_core.json",
fetcher: mFetcher,
logOnce: map[string]struct{}{},
}
require.NoError(t, p.parseConfig())
// First call adds the error to the accumulator and logOnce map.
p.addPerCPUMsrMetrics(acc, cpuID, coreID, packageID)
// Second call detects previous error in logOnce map and skips adding it to the accumulator.
p.addPerCPUMsrMetrics(acc, cpuID, coreID, packageID)
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q: %v", cpuTemperature, mErr))
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, p.logOnce, 1)
require.Contains(t, p.logOnce, "msr_cpu_temperature")
mFetcher.AssertExpectations(t)
})
t.Run("WithoutErrors", func(t *testing.T) {
cpuTemp := uint64(20)
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting CPU temperature metric.
mFetcher.On("GetCPUTemperature", cpuID).Return(cpuTemp, nil).Once()
p := &PowerStat{
CPUMetrics: cpuMetrics,
PackageMetrics: packageMetrics,
EventDefinitions: "./testdata/sapphirerapids_core.json",
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
p.addPerCPUMsrMetrics(acc, cpuID, coreID, packageID)
require.Empty(t, acc.Errors)
require.Len(t, acc.GetTelegrafMetrics(), 1)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_temperature_celsius": cpuTemp,
},
// tags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
mFetcher.AssertExpectations(t)
})
})
t.Run("WithTimeRelatedMsrMetrics", func(t *testing.T) {
cpuID := 0
coreID := 1
packageID := 0
c1State := 5.15
c6State := 8.10
cpuMetrics := []cpuMetricType{
// metrics that rely on a time-related msr.
cpuC1StateResidency,
cpuC6StateResidency,
// metrics which do not rely on msr.
cpuFrequency,
cpuC0SubstateC01Percent,
}
t.Run("FailedToUpdate", func(t *testing.T) {
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock updating msr time-related metrics.
mFetcher.On("UpdatePerCPUMetrics", cpuID).Return(errors.New("mock error")).Once()
p := &PowerStat{
CPUMetrics: cpuMetrics,
PackageMetrics: packageMetrics,
EventDefinitions: "./testdata/sapphirerapids_core.json",
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
p.addPerCPUMsrMetrics(acc, cpuID, coreID, packageID)
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to update MSR time-related metrics for CPU ID %v", cpuID))
require.Empty(t, acc.GetTelegrafMetrics())
mFetcher.AssertExpectations(t)
})
t.Run("FailedToUpdateModuleNotInitializedError", func(t *testing.T) {
acc := &testutil.Accumulator{}
mErr := &ptel.ModuleNotInitializedError{Name: "msr"}
mFetcher := &fetcherMock{}
// mock updating msr time-related metrics.
mFetcher.On("UpdatePerCPUMetrics", cpuID).Return(mErr).Twice()
p := &PowerStat{
CPUMetrics: cpuMetrics,
PackageMetrics: packageMetrics,
EventDefinitions: "./testdata/sapphirerapids_core.json",
fetcher: mFetcher,
logOnce: map[string]struct{}{},
}
require.NoError(t, p.parseConfig())
// First call adds the error to the accumulator and key to logOnce map.
p.addPerCPUMsrMetrics(acc, cpuID, coreID, packageID)
// Second call detects previous error in logOnce map and skips adding it to the accumulator.
p.addPerCPUMsrMetrics(acc, cpuID, coreID, packageID)
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to update MSR time-related metrics: %v", mErr))
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, p.logOnce, 1)
require.Contains(t, p.logOnce, "msr_time_related")
mFetcher.AssertExpectations(t)
})
t.Run("WithoutErrors", func(t *testing.T) {
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock updating msr time-related metrics.
mFetcher.On("UpdatePerCPUMetrics", cpuID).Return(nil).Once()
// mock getting C1 state residency.
mFetcher.On("GetCPUC1StateResidency", cpuID).Return(c1State, nil).Once()
// mock getting C6 state residency.
mFetcher.On("GetCPUC6StateResidency", cpuID).Return(c6State, nil).Once()
p := &PowerStat{
CPUMetrics: cpuMetrics,
PackageMetrics: packageMetrics,
EventDefinitions: "./testdata/sapphirerapids_core.json",
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
p.addPerCPUMsrMetrics(acc, cpuID, coreID, packageID)
require.Empty(t, acc.Errors)
require.Len(t, acc.GetTelegrafMetrics(), 2)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_c1_state_residency_percent": c1State,
},
// flags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_c6_state_residency_percent": c6State,
},
// flags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
mFetcher.AssertExpectations(t)
})
})
}
func TestAddCPUTimeRelatedMsrMetrics(t *testing.T) {
cpuID := 0
coreID := 1
packageID := 0
c0State := 3.0
c1State := 2.0
c3State := 1.5
c6State := 1.0
busyCycles := 0.5
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting CPU C0 state residency value.
mFetcher.On("GetCPUC0StateResidency", cpuID).Return(c0State, nil).Once()
// mock getting CPU C1 state residency value.
mFetcher.On("GetCPUC1StateResidency", cpuID).Return(c1State, nil).Once()
// mock getting CPU C3 state residency value.
mFetcher.On("GetCPUC3StateResidency", cpuID).Return(c3State, nil).Once()
// mock getting CPU C6 state residency value.
mFetcher.On("GetCPUC6StateResidency", cpuID).Return(c6State, nil).Once()
// mock getting CPU C0 state residency value, triggered when calling add CPU busy cycle metric.
mFetcher.On("GetCPUC0StateResidency", cpuID).Return(busyCycles, nil).Once()
p := &PowerStat{
CPUMetrics: []cpuMetricType{
// Metrics which are not time-related MSR.
cpuFrequency,
cpuTemperature,
cpuC0SubstateC01Percent,
// Time-related MSR metrics.
cpuC0StateResidency,
cpuC1StateResidency,
cpuC3StateResidency,
cpuC6StateResidency,
cpuBusyCycles,
},
PackageMetrics: make([]packageMetricType, 0),
EventDefinitions: "./testdata/sapphirerapids_core.json",
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUTimeRelatedMsrMetrics(acc, cpuID, coreID, packageID)
require.Empty(t, acc.Errors)
require.Len(t, acc.GetTelegrafMetrics(), 5)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_c0_state_residency_percent": c0State,
},
// tags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_c1_state_residency_percent": c1State,
},
// tags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_c6_state_residency_percent": c6State,
},
// tags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_c3_state_residency_percent": c3State,
},
// tags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_busy_cycles_percent": busyCycles,
},
// tags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
mFetcher.AssertExpectations(t)
}
func TestAddCPUPerfMetrics(t *testing.T) {
// Disable package metrics when parseConfig method is called.
packageMetrics := make([]packageMetricType, 0)
t.Run("FailedToReadPerfEvents", func(t *testing.T) {
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock reading perf events.
mFetcher.On("ReadPerfEvents").Return(errors.New("mock error")).Once()
p := &PowerStat{
fetcher: mFetcher,
}
p.addCPUPerfMetrics(acc)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), "failed to read perf events")
mFetcher.AssertExpectations(t)
})
t.Run("FailedToReadPerfEventsModuleNotInitializedError", func(t *testing.T) {
acc := &testutil.Accumulator{}
mErr := &ptel.ModuleNotInitializedError{Name: "perf"}
mFetcher := &fetcherMock{}
// mock reading perf events.
mFetcher.On("ReadPerfEvents").Return(mErr).Twice()
p := &PowerStat{
fetcher: mFetcher,
logOnce: map[string]struct{}{},
}
// First call adds the error to the accumulator and key to logOnce map.
p.addCPUPerfMetrics(acc)
// Second call detects previous error in logOnce map and skips adding it to the accumulator.
p.addCPUPerfMetrics(acc)
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to read perf events: %v", mErr))
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, p.logOnce, 1)
require.Contains(t, p.logOnce, "perf_read")
mFetcher.AssertExpectations(t)
})
t.Run("NoAvailableCPUs", func(t *testing.T) {
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock reading perf events.
mFetcher.On("ReadPerfEvents").Return(nil).Once()
// mock getting available CPU IDs for perf events.
mFetcher.On("GetPerfCPUIDs").Return(nil).Once()
p := &PowerStat{
CPUMetrics: []cpuMetricType{
// Metrics which do not rely on perf.
cpuFrequency,
cpuTemperature,
cpuBusyCycles,
// Metrics which rely on perf.
cpuC0SubstateC01Percent,
cpuC0SubstateC02Percent,
cpuC0SubstateC0WaitPercent,
},
PackageMetrics: make([]packageMetricType, 0),
EventDefinitions: "./testdata/sapphirerapids_core.json",
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
p.addCPUPerfMetrics(acc)
require.Empty(t, acc.Errors)
require.Empty(t, acc.GetTelegrafMetrics())
mFetcher.AssertExpectations(t)
})
t.Run("FailedToGetDataCPUID", func(t *testing.T) {
t.Run("SingleCPUID", func(t *testing.T) {
cpuID := 0
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock reading perf events.
mFetcher.On("ReadPerfEvents").Return(nil).Once()
// mock getting available CPU IDs for perf events.
mFetcher.On("GetPerfCPUIDs").Return([]int{cpuID}).Once()
// mock getting corresponding core ID to CPU ID 0.
mFetcher.On("GetCPUCoreID", cpuID).Return(1, nil).Once()
// mock getting corresponding package ID to CPU ID 0.
mFetcher.On("GetCPUPackageID", cpuID).Return(0, errors.New("mock error")).Once()
p := &PowerStat{
CPUMetrics: []cpuMetricType{
// Metrics which do not rely on perf.
cpuFrequency,
cpuTemperature,
cpuBusyCycles,
// Metrics which rely on perf.
cpuC0SubstateC01Percent,
cpuC0SubstateC02Percent,
cpuC0SubstateC0WaitPercent,
},
EventDefinitions: "./testdata/sapphirerapids_core.json",
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
p.addCPUPerfMetrics(acc)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get perf metrics for CPU ID %v", cpuID))
mFetcher.AssertExpectations(t)
})
t.Run("MultipleCPUIDs", func(t *testing.T) {
cpuID := 0
coreID := 2
packageID := 3
c01Percent := 0.2
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock reading perf events.
mFetcher.On("ReadPerfEvents").Return(nil).Once()
// mock getting available CPU IDs for perf events.
mFetcher.On("GetPerfCPUIDs").Return([]int{cpuID, 1}).Once()
// mock getting corresponding core ID to CPU ID 0.
mFetcher.On("GetCPUCoreID", cpuID).Return(coreID, nil).Once()
// mock getting corresponding package ID to CPU ID 0.
mFetcher.On("GetCPUPackageID", cpuID).Return(packageID, nil).Once()
// mock getting CPU C01 metric.
mFetcher.On("GetCPUC0SubstateC01Percent", cpuID).Return(c01Percent, nil).Once()
// mock getting corresponding core ID to CPU ID 1.
mFetcher.On("GetCPUCoreID", 1).Return(5, nil).Once()
// mock getting corresponding package ID to CPU ID 1.
mFetcher.On("GetCPUPackageID", 1).Return(0, errors.New("mock error")).Once()
p := &PowerStat{
CPUMetrics: []cpuMetricType{
// Metrics which do not rely on perf.
cpuFrequency,
cpuTemperature,
cpuC6StateResidency,
// Metrics which rely on perf.
cpuC0SubstateC01Percent,
},
PackageMetrics: packageMetrics,
EventDefinitions: "./testdata/sapphirerapids_core.json",
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
p.addCPUPerfMetrics(acc)
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), "failed to get perf metrics for CPU ID 1")
require.Len(t, acc.GetTelegrafMetrics(), 1)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_c0_substate_c01_percent": c01Percent,
},
// tags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
mFetcher.AssertExpectations(t)
})
})
t.Run("WithError", func(t *testing.T) {
cpuID := 0
coreID := 1
packageID := 0
c01Percent := 0.5
c0Wait := 2.5
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock reading perf events.
mFetcher.On("ReadPerfEvents").Return(nil).Once()
// mock getting available CPU IDs for perf events.
mFetcher.On("GetPerfCPUIDs").Return([]int{cpuID}).Once()
// mock getting corresponding core ID to CPU ID 0.
mFetcher.On("GetCPUCoreID", cpuID).Return(coreID, nil).Once()
// mock getting corresponding package ID to CPU ID 0.
mFetcher.On("GetCPUPackageID", cpuID).Return(packageID, nil).Once()
// mock getting CPU C01 metric.
mFetcher.On("GetCPUC0SubstateC01Percent", cpuID).Return(c01Percent, nil).Once()
// mock getting CPU C02 metric.
mFetcher.On("GetCPUC0SubstateC02Percent", cpuID).Return(0.0, errors.New("mock error")).Once()
// mock getting CPU C0Wait metric.
mFetcher.On("GetCPUC0SubstateC0WaitPercent", cpuID).Return(c0Wait, nil).Once()
p := &PowerStat{
CPUMetrics: []cpuMetricType{
// Metrics which do not rely on perf.
cpuFrequency,
cpuTemperature,
cpuC6StateResidency,
// Metrics which rely on perf.
cpuC0SubstateC01Percent,
cpuC0SubstateC02Percent,
cpuC0SubstateC0WaitPercent,
},
PackageMetrics: packageMetrics,
EventDefinitions: "./testdata/sapphirerapids_core.json",
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
p.addCPUPerfMetrics(acc)
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q for CPU ID %v", cpuC0SubstateC02Percent, cpuID))
require.Len(t, acc.GetTelegrafMetrics(), 2)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_c0_substate_c01_percent": c01Percent,
},
// tags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_c0_substate_c0_wait_percent": c0Wait,
},
// tags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
})
}
func TestAddPerCPUPerfMetrics(t *testing.T) {
cpuID := 0
coreID := 1
packageID := 0
c01Percent := 1.09
c02Percent := 2.12
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting CPU C01 metric.
mFetcher.On("GetCPUC0SubstateC01Percent", cpuID).Return(c01Percent, nil).Once()
// mock getting CPU C02 metric.
mFetcher.On("GetCPUC0SubstateC02Percent", cpuID).Return(c02Percent, nil).Once()
p := &PowerStat{
CPUMetrics: []cpuMetricType{
// Metrics which do not rely on perf.
cpuFrequency,
cpuTemperature,
cpuC6StateResidency,
// Metrics which rely on perf.
cpuC0SubstateC01Percent,
cpuC0SubstateC02Percent,
},
PackageMetrics: make([]packageMetricType, 0),
EventDefinitions: "./testdata/sapphirerapids_core.json",
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
require.Empty(t, acc.GetTelegrafMetrics())
p.addPerCPUPerfMetrics(acc, cpuID, coreID, packageID)
require.Len(t, acc.GetTelegrafMetrics(), 2)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_c0_substate_c01_percent": c01Percent,
},
// tags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_c0_substate_c02_percent": c02Percent,
},
// tags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
mFetcher.AssertExpectations(t)
}
func TestGetDataCPUID(t *testing.T) {
t.Run("FailedToGetCoreID", func(t *testing.T) {
cpuID := 1
mFetcher := &fetcherMock{}
// mock getting core ID corresponding to the CPU ID.
mFetcher.On("GetCPUCoreID", cpuID).Return(0, errors.New("mock error")).Once()
coreID, packageID, err := getDataCPUID(mFetcher, cpuID)
require.Equal(t, 0, coreID)
require.Equal(t, 0, packageID)
require.ErrorContains(t, err, fmt.Sprintf("failed to get core ID from CPU ID %v", cpuID))
mFetcher.AssertExpectations(t)
})
t.Run("FailedToGetPackageID", func(t *testing.T) {
cpuID := 1
mFetcher := &fetcherMock{}
// mock getting core ID corresponding to the CPU ID.
mFetcher.On("GetCPUCoreID", cpuID).Return(1, nil).Once()
// mock getting package ID corresponding to the CPU ID.
mFetcher.On("GetCPUPackageID", cpuID).Return(0, errors.New("mock error")).Once()
coreID, packageID, err := getDataCPUID(mFetcher, cpuID)
require.Equal(t, 0, coreID)
require.Equal(t, 0, packageID)
require.ErrorContains(t, err, fmt.Sprintf("failed to get package ID from CPU ID %v", cpuID))
mFetcher.AssertExpectations(t)
})
t.Run("Ok", func(t *testing.T) {
cpuID := 1
mFetcher := &fetcherMock{}
// mock getting core ID corresponding to the CPU ID.
mFetcher.On("GetCPUCoreID", cpuID).Return(1, nil).Once()
// mock getting package ID corresponding to the CPU ID.
mFetcher.On("GetCPUPackageID", cpuID).Return(2, nil).Once()
coreID, packageID, err := getDataCPUID(mFetcher, cpuID)
require.Equal(t, 1, coreID)
require.Equal(t, 2, packageID)
require.NoError(t, err)
mFetcher.AssertExpectations(t)
})
}
func TestAddPackageMetrics(t *testing.T) {
t.Run("NoPackageIDs", func(t *testing.T) {
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting available package IDs.
mFetcher.On("GetPackageIDs").Return(nil).Once()
p := &PowerStat{
fetcher: mFetcher,
}
p.addPackageMetrics(acc)
require.Empty(t, acc.Errors)
require.Empty(t, acc.GetTelegrafMetrics())
})
t.Run("WithRaplMetrics", func(t *testing.T) {
tdp := 80.0
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting available package IDs.
mFetcher.On("GetPackageIDs").Return([]int{0, 1}).Once()
// mock getting package thermal design power metric for CPU ID 0.
mFetcher.On("GetPackageThermalDesignPowerWatts", 0).Return(tdp, nil).Once()
// mock getting package thermal design power metric for CPU ID 1.
mFetcher.On("GetPackageThermalDesignPowerWatts", 1).Return(0.0, errors.New("mock error")).Once()
p := &PowerStat{
PackageMetrics: []packageMetricType{
// metrics which rely on rapl
packageThermalDesignPower,
},
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
p.addPackageMetrics(acc)
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q for package ID 1", packageThermalDesignPower))
require.Len(t, acc.GetTelegrafMetrics(), 1)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_package",
// fields
map[string]interface{}{
"thermal_design_power_watts": tdp,
},
// tags
map[string]string{
"package_id": "0",
},
)
mFetcher.AssertExpectations(t)
})
t.Run("WithMsrMetrics", func(t *testing.T) {
baseFreq := uint64(400)
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting available package IDs.
mFetcher.On("GetPackageIDs").Return([]int{0, 1}).Once()
// mock getting CPU base frequency metric, for package ID 0.
mFetcher.On("GetCPUBaseFrequency", 0).Return(uint64(0), errors.New("mock error")).Once()
// mock getting CPU base frequency metric, for package ID 1.
mFetcher.On("GetCPUBaseFrequency", 1).Return(baseFreq, nil).Once()
p := &PowerStat{
PackageMetrics: []packageMetricType{
// metrics which rely on msr
packageCPUBaseFrequency,
},
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
p.addPackageMetrics(acc)
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q for package ID 0", packageCPUBaseFrequency))
require.Len(t, acc.GetTelegrafMetrics(), 1)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_package",
// fields
map[string]interface{}{
"cpu_base_frequency_mhz": baseFreq,
},
// tags
map[string]string{
"package_id": "1",
},
)
mFetcher.AssertExpectations(t)
})
t.Run("WithUncoreFreqMetric", func(t *testing.T) {
dieID := 0
initMin := 500.0
initMax := 2500.0
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting available package IDs.
mFetcher.On("GetPackageIDs").Return([]int{0, 1}).Once()
// mock getting die IDs for package ID.
mFetcher.On("GetPackageDieIDs", mock.AnythingOfType("int")).Return([]int{dieID}, nil).Twice()
// mock getting initial minimum uncore frequency limit.
mFetcher.On("GetInitialUncoreFrequencyMin", mock.AnythingOfType("int"), dieID).Return(initMin, nil).Twice()
// mock getting initial maximum uncore frequency limit.
mFetcher.On("GetInitialUncoreFrequencyMax", mock.AnythingOfType("int"), dieID).Return(initMax, nil).Twice()
// mock getting custom minimum uncore frequency limit.
mFetcher.On("GetCustomizedUncoreFrequencyMin", mock.AnythingOfType("int"), dieID).Return(600.0, nil).Twice()
// mock getting custom maximum uncore frequency limit.
mFetcher.On("GetCustomizedUncoreFrequencyMax", mock.AnythingOfType("int"), dieID).Return(0.0, errors.New("mock error")).Twice()
p := &PowerStat{
PackageMetrics: []packageMetricType{
packageUncoreFrequency,
},
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
p.addPackageMetrics(acc)
require.Len(t, acc.Errors, 2)
require.ErrorContains(t, acc.Errors[0], fmt.Sprintf("failed to get current uncore frequency values for package ID 0 and die ID %v", dieID))
require.ErrorContains(t, acc.Errors[1], fmt.Sprintf("failed to get current uncore frequency values for package ID 1 and die ID %v", dieID))
require.Len(t, acc.GetTelegrafMetrics(), 2)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_package",
// fields
map[string]interface{}{
"uncore_frequency_limit_mhz_min": initMin,
"uncore_frequency_limit_mhz_max": initMax,
},
// tags
map[string]string{
"package_id": "0",
"type": "initial",
"die": strconv.Itoa(dieID),
},
)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_package",
// fields
map[string]interface{}{
"uncore_frequency_limit_mhz_min": initMin,
"uncore_frequency_limit_mhz_max": initMax,
},
// tags
map[string]string{
"package_id": "1",
"type": "initial",
"die": strconv.Itoa(dieID),
},
)
mFetcher.AssertExpectations(t)
})
}
func TestAddPerPackageRaplMetrics(t *testing.T) {
t.Run("WithoutRaplMetrics", func(t *testing.T) {
packageID := 0
acc := &testutil.Accumulator{}
p := &PowerStat{
PackageMetrics: []packageMetricType{
// metrics which do not rely on rapl
packageCPUBaseFrequency,
packageUncoreFrequency,
packageTurboLimit,
},
}
p.addPerPackageRaplMetrics(acc, packageID)
require.Empty(t, acc.Errors)
require.Empty(t, acc.GetTelegrafMetrics())
})
t.Run("WithModuleNotInitializedError", func(t *testing.T) {
packageID := 0
acc := &testutil.Accumulator{}
raplNotInitErr := &ptel.ModuleNotInitializedError{Name: "rapl"}
mError := fmt.Errorf("mock error: %w", raplNotInitErr)
mFetcher := &fetcherMock{}
// mock getting current dram power consumption metric.
mFetcher.On("GetCurrentDramPowerConsumptionWatts", packageID).Return(0.0, mError).Twice()
// mock getting package thermal design power metric.
mFetcher.On("GetPackageThermalDesignPowerWatts", packageID).Return(0.0, mError).Twice()
p := &PowerStat{
PackageMetrics: []packageMetricType{
// metrics which rely on rapl
packageCurrentDramPowerConsumption,
packageThermalDesignPower,
// metrics which do not rely on rapl
packageCPUBaseFrequency,
packageUncoreFrequency,
packageTurboLimit,
},
fetcher: mFetcher,
logOnce: map[string]struct{}{},
}
require.NoError(t, p.parseConfig())
// First call adds the error to the accumulator and logOnce map.
p.addPerPackageRaplMetrics(acc, packageID)
// Second call detects previous error in logOnce map and skips adding it to the accumulator.
p.addPerPackageRaplMetrics(acc, packageID)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 2)
require.ErrorContains(t, acc.Errors[0], fmt.Sprintf("failed to get %q: %v", packageCurrentDramPowerConsumption, raplNotInitErr))
require.ErrorContains(t, acc.Errors[1], fmt.Sprintf("failed to get %q: %v", packageThermalDesignPower, raplNotInitErr))
require.Len(t, p.logOnce, 2)
require.Contains(t, p.logOnce, "rapl_current_dram_power_consumption")
require.Contains(t, p.logOnce, "rapl_thermal_design_power")
mFetcher.AssertExpectations(t)
})
t.Run("WithErrors", func(t *testing.T) {
packageID := 0
currPower := 30.0
tdp := 80.0
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting current package power consumption metric.
mFetcher.On("GetCurrentPackagePowerConsumptionWatts", packageID).Return(currPower, nil).Once()
// mock getting current dram power consumption metric.
mFetcher.On("GetCurrentDramPowerConsumptionWatts", packageID).Return(0.0, errors.New("mock error")).Once()
// mock getting package thermal design power metric.
mFetcher.On("GetPackageThermalDesignPowerWatts", packageID).Return(tdp, nil).Once()
p := &PowerStat{
PackageMetrics: []packageMetricType{
// metrics which rely on rapl
packageCurrentPowerConsumption,
packageCurrentDramPowerConsumption,
packageThermalDesignPower,
// metrics which do not rely on rapl
packageCPUBaseFrequency,
packageUncoreFrequency,
packageTurboLimit,
},
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
p.addPerPackageRaplMetrics(acc, packageID)
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q for package ID %v", packageCurrentDramPowerConsumption, packageID))
require.Len(t, acc.GetTelegrafMetrics(), 2)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_package",
// fields
map[string]interface{}{
"current_power_consumption_watts": currPower,
},
// tags
map[string]string{
"package_id": strconv.Itoa(packageID),
},
)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_package",
// fields
map[string]interface{}{
"thermal_design_power_watts": tdp,
},
// tags
map[string]string{
"package_id": strconv.Itoa(packageID),
},
)
mFetcher.AssertExpectations(t)
})
t.Run("WithoutErrors", func(t *testing.T) {
packageID := 0
currPower := 10.0
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting current dram power consumption metric.
mFetcher.On("GetCurrentDramPowerConsumptionWatts", packageID).Return(currPower, nil).Once()
p := &PowerStat{
PackageMetrics: []packageMetricType{
// metrics which rely on rapl
packageCurrentDramPowerConsumption,
// metrics which do not rely on rapl
packageCPUBaseFrequency,
packageUncoreFrequency,
packageTurboLimit,
},
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
p.addPerPackageRaplMetrics(acc, packageID)
require.Empty(t, acc.Errors)
require.Len(t, acc.GetTelegrafMetrics(), 1)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_package",
// fields
map[string]interface{}{
"current_dram_power_consumption_watts": currPower,
},
// tags
map[string]string{
"package_id": strconv.Itoa(packageID),
},
)
mFetcher.AssertExpectations(t)
})
}
func TestAddPerPackageMsrMetrics(t *testing.T) {
t.Run("WithoutMsrMetrics", func(t *testing.T) {
packageID := 0
acc := &testutil.Accumulator{}
p := &PowerStat{
PackageMetrics: []packageMetricType{
// metrics which do not rely on msr
packageCurrentPowerConsumption,
packageCurrentDramPowerConsumption,
packageThermalDesignPower,
},
}
p.addPerPackageMsrMetrics(acc, packageID)
require.Empty(t, acc.Errors)
require.Empty(t, acc.GetTelegrafMetrics())
})
t.Run("WithModuleNotInitializedError", func(t *testing.T) {
packageID := 0
acc := &testutil.Accumulator{}
msrNotInitErr := &ptel.ModuleNotInitializedError{Name: "msr"}
mError := fmt.Errorf("mock error: %w", msrNotInitErr)
mFetcher := &fetcherMock{}
// mock getting CPU base frequency metric.
mFetcher.On("GetCPUBaseFrequency", packageID).Return(uint64(400), mError).Twice()
// mock getting max turbo frequency list.
mFetcher.On("GetMaxTurboFreqList", packageID).Return(nil, mError).Twice()
p := &PowerStat{
PackageMetrics: []packageMetricType{
// metrics which rely on msr
packageCPUBaseFrequency,
packageTurboLimit,
// metrics which do not rely on msr
packageCurrentPowerConsumption,
packageCurrentDramPowerConsumption,
packageThermalDesignPower,
},
fetcher: mFetcher,
logOnce: map[string]struct{}{},
}
require.NoError(t, p.parseConfig())
// First call adds the error to the accumulator and logOnce map.
p.addPerPackageMsrMetrics(acc, packageID)
// Second call detects previous error in logOnce map and skips adding it to the accumulator.
p.addPerPackageMsrMetrics(acc, packageID)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 2)
require.ErrorContains(t, acc.Errors[0], fmt.Sprintf("failed to get %q: %v", packageCPUBaseFrequency, msrNotInitErr))
require.ErrorContains(t, acc.Errors[1], fmt.Sprintf("failed to get %q: %v", packageTurboLimit, msrNotInitErr))
require.Len(t, p.logOnce, 2)
require.Contains(t, p.logOnce, "msr_cpu_base_frequency")
require.Contains(t, p.logOnce, "msr_max_turbo_frequency")
mFetcher.AssertExpectations(t)
})
t.Run("WithErrors", func(t *testing.T) {
packageID := 0
baseFreq := uint64(400)
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting CPU base frequency metric.
mFetcher.On("GetCPUBaseFrequency", packageID).Return(baseFreq, nil).Once()
// mock getting max turbo frequency list.
mFetcher.On("GetMaxTurboFreqList", packageID).Return(nil, errors.New("mock error")).Once()
p := &PowerStat{
PackageMetrics: []packageMetricType{
// metrics which rely on msr
packageCPUBaseFrequency,
packageTurboLimit,
// metrics which do not rely on msr
packageCurrentPowerConsumption,
packageCurrentDramPowerConsumption,
packageThermalDesignPower,
},
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
p.addPerPackageMsrMetrics(acc, packageID)
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q for package ID %v", packageTurboLimit, packageID))
require.Len(t, acc.GetTelegrafMetrics(), 1)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_package",
// fields
map[string]interface{}{
"cpu_base_frequency_mhz": baseFreq,
},
// tags
map[string]string{
"package_id": strconv.Itoa(packageID),
},
)
mFetcher.AssertExpectations(t)
})
t.Run("WithoutErrors", func(t *testing.T) {
packageID := 0
baseFreq := uint64(400)
maxTurboFreqList := []ptel.MaxTurboFreq{
{
Value: 1000,
ActiveCores: 10,
},
}
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting CPU base frequency metric.
mFetcher.On("GetCPUBaseFrequency", packageID).Return(baseFreq, nil).Once()
// mock getting max turbo frequency list.
mFetcher.On("GetMaxTurboFreqList", packageID).Return(maxTurboFreqList, nil).Once()
p := &PowerStat{
PackageMetrics: []packageMetricType{
// metrics which rely on msr
packageCPUBaseFrequency,
packageTurboLimit,
// metrics which do not rely on msr
packageCurrentPowerConsumption,
packageCurrentDramPowerConsumption,
packageThermalDesignPower,
},
fetcher: mFetcher,
}
require.NoError(t, p.parseConfig())
p.addPerPackageMsrMetrics(acc, packageID)
require.Empty(t, acc.Errors)
require.Len(t, acc.GetTelegrafMetrics(), 2)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_package",
// fields
map[string]interface{}{
"cpu_base_frequency_mhz": baseFreq,
},
// tags
map[string]string{
"package_id": strconv.Itoa(packageID),
},
)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_package",
// fields
map[string]interface{}{
"cpu_base_frequency_mhz": baseFreq,
},
// tags
map[string]string{
"package_id": strconv.Itoa(packageID),
},
)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_package",
// fields
map[string]interface{}{
"max_turbo_frequency_mhz": maxTurboFreqList[0].Value,
},
// tags
map[string]string{
"package_id": strconv.Itoa(packageID),
"active_cores": strconv.Itoa(int(maxTurboFreqList[0].ActiveCores)),
},
)
mFetcher.AssertExpectations(t)
})
}
func TestAddCPUFrequency(t *testing.T) {
t.Run("FailedToGetMetric", func(t *testing.T) {
acc := &testutil.Accumulator{}
cpuID := 0
coreID := 1
packageID := 0
mFetcher := &fetcherMock{}
// mock getting CPU frequency metric.
mFetcher.On("GetCPUFrequency", cpuID).Return(0.0, errors.New("mock error")).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUFrequency(acc, cpuID, coreID, packageID)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q for CPU ID %v", cpuFrequency, cpuID))
mFetcher.AssertExpectations(t)
})
t.Run("Rounded", func(t *testing.T) {
cpuID := 0
coreID := 1
packageID := 0
cpuFreq := 800.001
cpuFreqExp := 800.0
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting CPU frequency metric.
mFetcher.On("GetCPUFrequency", cpuID).Return(cpuFreq, nil).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUFrequency(acc, cpuID, coreID, packageID)
require.Len(t, acc.GetTelegrafMetrics(), 1)
require.True(t, acc.HasFloatField("powerstat_core", "cpu_frequency_mhz"))
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_frequency_mhz": cpuFreqExp,
},
// tags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
mFetcher.AssertExpectations(t)
})
}
func TestAddCPUTemperature(t *testing.T) {
t.Run("FailedToGetMetric", func(t *testing.T) {
acc := &testutil.Accumulator{}
cpuID := 0
coreID := 1
packageID := 0
mFetcher := &fetcherMock{}
// mock getting CPU temperature metric.
mFetcher.On("GetCPUTemperature", cpuID).Return(uint64(0), errors.New("mock error")).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUTemperature(acc, cpuID, coreID, packageID)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q for CPU ID %v", cpuTemperature, cpuID))
mFetcher.AssertExpectations(t)
})
t.Run("Ok", func(t *testing.T) {
acc := &testutil.Accumulator{}
cpuID := 0
coreID := 1
packageID := 0
cpuTemp := uint64(25)
mFetcher := &fetcherMock{}
// mock getting cpu temperature metric.
mFetcher.On("GetCPUTemperature", cpuID).Return(cpuTemp, nil).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUTemperature(acc, cpuID, coreID, packageID)
require.Len(t, acc.GetTelegrafMetrics(), 1)
require.True(t, acc.HasUIntField("powerstat_core", "cpu_temperature_celsius"))
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_temperature_celsius": cpuTemp,
},
// tags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
mFetcher.AssertExpectations(t)
})
}
func TestAddCPUC0StateResidency(t *testing.T) {
t.Run("FailedToGetMetric", func(t *testing.T) {
acc := &testutil.Accumulator{}
cpuID := 0
coreID := 1
packageID := 0
mFetcher := &fetcherMock{}
// mock getting CPU C0 state residency metric.
mFetcher.On("GetCPUC0StateResidency", cpuID).Return(0.0, errors.New("mock error")).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUC0StateResidency(acc, cpuID, coreID, packageID)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q for CPU ID %v", cpuC0StateResidency, cpuID))
mFetcher.AssertExpectations(t)
})
t.Run("Rounded", func(t *testing.T) {
cpuID := 0
coreID := 1
packageID := 0
c0State := 10.1199
c0StateExp := 10.12
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting CPU C0 state residency metric.
mFetcher.On("GetCPUC0StateResidency", cpuID).Return(c0State, nil).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUC0StateResidency(acc, cpuID, coreID, packageID)
require.Len(t, acc.GetTelegrafMetrics(), 1)
require.True(t, acc.HasFloatField("powerstat_core", "cpu_c0_state_residency_percent"))
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_c0_state_residency_percent": c0StateExp,
},
// tags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
mFetcher.AssertExpectations(t)
})
}
func TestAddCPUC1StateResidency(t *testing.T) {
t.Run("FailedToGetMetric", func(t *testing.T) {
acc := &testutil.Accumulator{}
cpuID := 0
coreID := 1
packageID := 0
mFetcher := &fetcherMock{}
// mock getting CPU C1 state residency metric.
mFetcher.On("GetCPUC1StateResidency", cpuID).Return(0.0, errors.New("mock error")).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUC1StateResidency(acc, cpuID, coreID, packageID)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q for CPU ID %v", cpuC1StateResidency, cpuID))
mFetcher.AssertExpectations(t)
})
t.Run("Rounded", func(t *testing.T) {
acc := &testutil.Accumulator{}
cpuID := 0
coreID := 1
packageID := 0
c1State := 10.1144
c1StateExp := 10.11
mFetcher := &fetcherMock{}
// mock getting CPU C1 state residency metric.
mFetcher.On("GetCPUC1StateResidency", cpuID).Return(c1State, nil).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUC1StateResidency(acc, cpuID, coreID, packageID)
require.Len(t, acc.GetTelegrafMetrics(), 1)
require.True(t, acc.HasFloatField("powerstat_core", "cpu_c1_state_residency_percent"))
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_c1_state_residency_percent": c1StateExp,
},
// tags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
mFetcher.AssertExpectations(t)
})
}
func TestAddCPUC3StateResidency(t *testing.T) {
t.Run("FailedToGetMetric", func(t *testing.T) {
acc := &testutil.Accumulator{}
cpuID := 0
coreID := 1
packageID := 0
mFetcher := &fetcherMock{}
// mock getting CPU C3 state residency metric.
mFetcher.On("GetCPUC3StateResidency", cpuID).Return(0.0, errors.New("mock error")).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUC3StateResidency(acc, cpuID, coreID, packageID)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q for CPU ID %v", cpuC3StateResidency, cpuID))
mFetcher.AssertExpectations(t)
})
t.Run("Rounded", func(t *testing.T) {
acc := &testutil.Accumulator{}
cpuID := 0
coreID := 1
packageID := 0
c3State := 20.1178
c3StateExp := 20.12
mFetcher := &fetcherMock{}
// mock getting CPU C3 state residency metric.
mFetcher.On("GetCPUC3StateResidency", cpuID).Return(c3State, nil).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUC3StateResidency(acc, cpuID, coreID, packageID)
require.Len(t, acc.GetTelegrafMetrics(), 1)
require.True(t, acc.HasFloatField("powerstat_core", "cpu_c3_state_residency_percent"))
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_c3_state_residency_percent": c3StateExp,
},
// tags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
mFetcher.AssertExpectations(t)
})
}
func TestAddCPUC6StateResidency(t *testing.T) {
t.Run("FailedToGetMetric", func(t *testing.T) {
acc := &testutil.Accumulator{}
cpuID := 0
coreID := 1
packageID := 0
mFetcher := &fetcherMock{}
// mock getting CPU C6 state residency metric.
mFetcher.On("GetCPUC6StateResidency", cpuID).Return(0.0, errors.New("mock error")).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUC6StateResidency(acc, cpuID, coreID, packageID)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q for CPU ID %v", cpuC6StateResidency, cpuID))
mFetcher.AssertExpectations(t)
})
t.Run("Rounded", func(t *testing.T) {
acc := &testutil.Accumulator{}
cpuID := 0
coreID := 1
packageID := 0
c6State := 9.115
c6StateExp := 9.12
mFetcher := &fetcherMock{}
// mock getting CPU C6 state residency metric.
mFetcher.On("GetCPUC6StateResidency", cpuID).Return(c6State, nil).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUC6StateResidency(acc, cpuID, coreID, packageID)
require.Len(t, acc.GetTelegrafMetrics(), 1)
require.True(t, acc.HasFloatField("powerstat_core", "cpu_c6_state_residency_percent"))
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_c6_state_residency_percent": c6StateExp,
},
// tags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
mFetcher.AssertExpectations(t)
})
}
func TestAddCPUC7StateResidency(t *testing.T) {
t.Run("FailedToGetMetric", func(t *testing.T) {
acc := &testutil.Accumulator{}
cpuID := 0
coreID := 1
packageID := 0
mFetcher := &fetcherMock{}
// mock getting CPU C7 state residency metric.
mFetcher.On("GetCPUC7StateResidency", cpuID).Return(0.0, errors.New("mock error")).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUC7StateResidency(acc, cpuID, coreID, packageID)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q for CPU ID %v", cpuC7StateResidency, cpuID))
mFetcher.AssertExpectations(t)
})
t.Run("Rounded", func(t *testing.T) {
acc := &testutil.Accumulator{}
cpuID := 0
coreID := 1
packageID := 0
c7State := 9.1149
c7StateExp := 9.11
mFetcher := &fetcherMock{}
// mock getting CPU C7 state residency metric.
mFetcher.On("GetCPUC7StateResidency", cpuID).Return(c7State, nil).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUC7StateResidency(acc, cpuID, coreID, packageID)
require.Len(t, acc.GetTelegrafMetrics(), 1)
require.True(t, acc.HasFloatField("powerstat_core", "cpu_c7_state_residency_percent"))
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_c7_state_residency_percent": c7StateExp,
},
// tags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
mFetcher.AssertExpectations(t)
})
}
func TestAddCPUBusyFrequency(t *testing.T) {
t.Run("FailedToGetMetric", func(t *testing.T) {
acc := &testutil.Accumulator{}
cpuID := 0
coreID := 1
packageID := 0
mFetcher := &fetcherMock{}
// mock getting CPU busy frequency metric.
mFetcher.On("GetCPUBusyFrequencyMhz", cpuID).Return(0.0, errors.New("mock error")).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUBusyFrequency(acc, cpuID, coreID, packageID)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q for CPU ID %v", cpuBusyFrequency, cpuID))
mFetcher.AssertExpectations(t)
})
t.Run("Rounded", func(t *testing.T) {
acc := &testutil.Accumulator{}
cpuID := 0
coreID := 1
packageID := 0
cpuBusyFreq := 800.119
cpuBusyFreqExp := 800.12
mFetcher := &fetcherMock{}
// mock getting CPU busy frequency metric.
mFetcher.On("GetCPUBusyFrequencyMhz", cpuID).Return(cpuBusyFreq, nil).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUBusyFrequency(acc, cpuID, coreID, packageID)
require.Len(t, acc.GetTelegrafMetrics(), 1)
require.True(t, acc.HasFloatField("powerstat_core", "cpu_busy_frequency_mhz"))
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_busy_frequency_mhz": cpuBusyFreqExp,
},
// tags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
mFetcher.AssertExpectations(t)
})
}
func TestAddCPUBusyCycles(t *testing.T) {
t.Run("FailedToGetMetric", func(t *testing.T) {
acc := &testutil.Accumulator{}
cpuID := 0
coreID := 1
packageID := 0
mFetcher := &fetcherMock{}
// mock getting CPU busy cycles metric.
mFetcher.On("GetCPUC0StateResidency", cpuID).Return(0.0, errors.New("mock error")).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUBusyCycles(acc, cpuID, coreID, packageID)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q for CPU ID %v", cpuBusyCycles, cpuID))
mFetcher.AssertExpectations(t)
})
t.Run("Rounded", func(t *testing.T) {
acc := &testutil.Accumulator{}
cpuID := 0
coreID := 1
packageID := 0
cpuBusyCycles := 10.1149
cpuBusyCyclesExp := 10.11
mFetcher := &fetcherMock{}
// mock getting CPU C0 state residency metric.
mFetcher.On("GetCPUC0StateResidency", cpuID).Return(cpuBusyCycles, nil).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUBusyCycles(acc, cpuID, coreID, packageID)
require.Len(t, acc.GetTelegrafMetrics(), 1)
require.True(t, acc.HasFloatField("powerstat_core", "cpu_busy_cycles_percent"))
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_busy_cycles_percent": cpuBusyCyclesExp,
},
// tags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
mFetcher.AssertExpectations(t)
})
}
func TestAddCPUC0SubstateC01Percent(t *testing.T) {
t.Run("FailedToGetMetric", func(t *testing.T) {
acc := &testutil.Accumulator{}
cpuID := 0
coreID := 1
packageID := 0
mFetcher := &fetcherMock{}
// mock getting CPU C01 metric.
mFetcher.On("GetCPUC0SubstateC01Percent", cpuID).Return(0.0, errors.New("mock error")).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUC0SubstateC01Percent(acc, cpuID, coreID, packageID)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q for CPU ID %v", cpuC0SubstateC01Percent, cpuID))
mFetcher.AssertExpectations(t)
})
t.Run("Rounded", func(t *testing.T) {
acc := &testutil.Accumulator{}
cpuID := 0
coreID := 1
packageID := 0
c01Percent := 5.9229
c01PercentExp := 5.92
mFetcher := &fetcherMock{}
// mock getting CPU C01 metric.
mFetcher.On("GetCPUC0SubstateC01Percent", cpuID).Return(c01Percent, nil).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUC0SubstateC01Percent(acc, cpuID, coreID, packageID)
require.Len(t, acc.GetTelegrafMetrics(), 1)
require.True(t, acc.HasFloatField("powerstat_core", "cpu_c0_substate_c01_percent"))
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_c0_substate_c01_percent": c01PercentExp,
},
// tags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
mFetcher.AssertExpectations(t)
})
}
func TestAddCPUC0SubstateC02Percent(t *testing.T) {
t.Run("FailedToGetMetric", func(t *testing.T) {
acc := &testutil.Accumulator{}
cpuID := 0
coreID := 1
packageID := 0
mFetcher := &fetcherMock{}
// mock getting CPU C02 metric.
mFetcher.On("GetCPUC0SubstateC02Percent", cpuID).Return(0.0, errors.New("mock error")).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUC0SubstateC02Percent(acc, cpuID, coreID, packageID)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q for CPU ID %v", cpuC0SubstateC02Percent, cpuID))
mFetcher.AssertExpectations(t)
})
t.Run("Rounded", func(t *testing.T) {
acc := &testutil.Accumulator{}
cpuID := 0
coreID := 1
packageID := 0
c02Percent := 0.001
c02PercentExp := 0.0
mFetcher := &fetcherMock{}
// mock getting CPU C02 metric.
mFetcher.On("GetCPUC0SubstateC02Percent", cpuID).Return(c02Percent, nil).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUC0SubstateC02Percent(acc, cpuID, coreID, packageID)
require.Len(t, acc.GetTelegrafMetrics(), 1)
require.True(t, acc.HasFloatField("powerstat_core", "cpu_c0_substate_c02_percent"))
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_c0_substate_c02_percent": c02PercentExp,
},
// tags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
mFetcher.AssertExpectations(t)
})
}
func TestAddCPUC0SubstateC0WaitPercent(t *testing.T) {
t.Run("FailedToGetMetric", func(t *testing.T) {
acc := &testutil.Accumulator{}
cpuID := 0
coreID := 1
packageID := 0
mFetcher := &fetcherMock{}
// mock getting CPU C0Wait metric.
mFetcher.On("GetCPUC0SubstateC0WaitPercent", cpuID).Return(0.0, errors.New("mock error")).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUC0SubstateC0WaitPercent(acc, cpuID, coreID, packageID)
require.Empty(t, acc.GetTelegrafMetrics(), 0)
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q for CPU ID %v", cpuC0SubstateC0WaitPercent, cpuID))
mFetcher.AssertExpectations(t)
})
t.Run("Rounded", func(t *testing.T) {
acc := &testutil.Accumulator{}
cpuID := 0
coreID := 1
packageID := 0
c0WaitPercent := 0.995
c0WaitPercentExp := 1.0
mFetcher := &fetcherMock{}
// mock getting CPU C0Wait metric.
mFetcher.On("GetCPUC0SubstateC0WaitPercent", cpuID).Return(c0WaitPercent, nil).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUC0SubstateC0WaitPercent(acc, cpuID, coreID, packageID)
require.Len(t, acc.GetTelegrafMetrics(), 1)
require.True(t, acc.HasFloatField("powerstat_core", "cpu_c0_substate_c0_wait_percent"))
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_core",
// fields
map[string]interface{}{
"cpu_c0_substate_c0_wait_percent": c0WaitPercentExp,
},
// tags
map[string]string{
"cpu_id": strconv.Itoa(cpuID),
"core_id": strconv.Itoa(coreID),
"package_id": strconv.Itoa(packageID),
},
)
mFetcher.AssertExpectations(t)
})
}
func TestAddCurrentPackagePowerConsumption(t *testing.T) {
t.Run("FailedToGetMetric", func(t *testing.T) {
acc := &testutil.Accumulator{}
packageID := 0
mFetcher := &fetcherMock{}
// mock getting current package power consumption metric.
mFetcher.On("GetCurrentPackagePowerConsumptionWatts", packageID).Return(0.0, errors.New("mock error")).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCurrentPackagePower(acc, packageID)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q for package ID %v", packageCurrentPowerConsumption, packageID))
mFetcher.AssertExpectations(t)
})
t.Run("Rounded", func(t *testing.T) {
acc := &testutil.Accumulator{}
packageID := 0
currPower := float64(30.1999)
currPowerRounded := float64(30.2)
mFetcher := &fetcherMock{}
// mock getting current package power consumption metric.
mFetcher.On("GetCurrentPackagePowerConsumptionWatts", packageID).Return(currPower, nil).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCurrentPackagePower(acc, packageID)
require.Len(t, acc.GetTelegrafMetrics(), 1)
require.True(t, acc.HasFloatField("powerstat_package", "current_power_consumption_watts"))
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_package",
// fields
map[string]interface{}{
"current_power_consumption_watts": currPowerRounded,
},
// tags
map[string]string{
"package_id": strconv.Itoa(packageID),
},
)
mFetcher.AssertExpectations(t)
})
}
func TestAddCurrentDramPowerConsumption(t *testing.T) {
t.Run("FailedToGetMetric", func(t *testing.T) {
acc := &testutil.Accumulator{}
packageID := 0
mFetcher := &fetcherMock{}
// mock getting current dram power consumption metric.
mFetcher.On("GetCurrentDramPowerConsumptionWatts", packageID).Return(0.0, errors.New("mock error")).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCurrentDramPower(acc, packageID)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q for package ID %v", packageCurrentDramPowerConsumption, packageID))
mFetcher.AssertExpectations(t)
})
t.Run("Rounded", func(t *testing.T) {
acc := &testutil.Accumulator{}
packageID := 0
currPower := float64(30.8235)
currPowerRounded := float64(30.82)
mFetcher := &fetcherMock{}
// mock getting current dram power consumption metric.
mFetcher.On("GetCurrentDramPowerConsumptionWatts", packageID).Return(currPower, nil).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCurrentDramPower(acc, packageID)
require.Len(t, acc.GetTelegrafMetrics(), 1)
require.True(t, acc.HasFloatField("powerstat_package", "current_dram_power_consumption_watts"))
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_package",
// fields
map[string]interface{}{
"current_dram_power_consumption_watts": currPowerRounded,
},
// tags
map[string]string{
"package_id": strconv.Itoa(packageID),
},
)
mFetcher.AssertExpectations(t)
})
}
func TestAddThermalDesignPower(t *testing.T) {
t.Run("FailedToGetMetric", func(t *testing.T) {
acc := &testutil.Accumulator{}
packageID := 0
mFetcher := &fetcherMock{}
// mock getting package thermal design power metric.
mFetcher.On("GetPackageThermalDesignPowerWatts", packageID).Return(0.0, errors.New("mock error")).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addThermalDesignPower(acc, packageID)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q for package ID %v", packageThermalDesignPower, packageID))
mFetcher.AssertExpectations(t)
})
t.Run("Rounded", func(t *testing.T) {
acc := &testutil.Accumulator{}
packageID := 0
tdp := float64(80.1999)
tdpRounded := float64(80.2)
mFetcher := &fetcherMock{}
// mock getting package thermal design power metric.
mFetcher.On("GetPackageThermalDesignPowerWatts", packageID).Return(tdp, nil).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addThermalDesignPower(acc, packageID)
require.Len(t, acc.GetTelegrafMetrics(), 1)
require.True(t, acc.HasFloatField("powerstat_package", "thermal_design_power_watts"))
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_package",
// fields
map[string]interface{}{
"thermal_design_power_watts": tdpRounded,
},
// tags
map[string]string{
"package_id": strconv.Itoa(packageID),
},
)
mFetcher.AssertExpectations(t)
})
}
func TestAddCPUBaseFrequency(t *testing.T) {
t.Run("FailedToGetMetric", func(t *testing.T) {
acc := &testutil.Accumulator{}
packageID := 0
mFetcher := &fetcherMock{}
// mock getting CPU base frequency metric.
mFetcher.On("GetCPUBaseFrequency", packageID).Return(uint64(0), errors.New("mock error")).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUBaseFrequency(acc, packageID)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q for package ID %v", packageCPUBaseFrequency, packageID))
mFetcher.AssertExpectations(t)
})
t.Run("Ok", func(t *testing.T) {
acc := &testutil.Accumulator{}
packageID := 0
baseFreq := uint64(700)
mFetcher := &fetcherMock{}
// mock getting CPU base frequency metric.
mFetcher.On("GetCPUBaseFrequency", packageID).Return(baseFreq, nil).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addCPUBaseFrequency(acc, packageID)
require.Len(t, acc.GetTelegrafMetrics(), 1)
require.True(t, acc.HasUIntField("powerstat_package", "cpu_base_frequency_mhz"))
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_package",
// fields
map[string]interface{}{
"cpu_base_frequency_mhz": baseFreq,
},
// tags
map[string]string{
"package_id": strconv.Itoa(packageID),
},
)
mFetcher.AssertExpectations(t)
})
}
func TestAddUncoreFrequency(t *testing.T) {
packageID, dieID := 1, 0
t.Run("FailedToGetDieIDs", func(t *testing.T) {
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting die IDs for package ID.
mFetcher.On("GetPackageDieIDs", packageID).Return(nil, errors.New("mock error")).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addUncoreFrequency(acc, packageID)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 1)
require.ErrorContains(
t,
acc.FirstError(),
fmt.Sprintf("failed to get die IDs for package ID %v", packageID),
)
})
t.Run("FailedToGetInitialLimits", func(t *testing.T) {
currMin := 500.0
currMax := 2500.0
curr := 1000.0
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting die IDs for package ID.
mFetcher.On("GetPackageDieIDs", packageID).Return([]int{dieID}, nil).Once()
// mock getting initial minimum uncore frequency limit.
mFetcher.On("GetInitialUncoreFrequencyMin", packageID, dieID).Return(800.0, nil).Once()
// mock getting initial maximum uncore frequency limit.
mFetcher.On("GetInitialUncoreFrequencyMax", packageID, dieID).Return(0.0, errors.New("mock error")).Once()
// mock getting custom minimum uncore frequency limit.
mFetcher.On("GetCustomizedUncoreFrequencyMin", packageID, dieID).Return(currMin, nil).Once()
// mock getting custom maximum uncore frequency limit.
mFetcher.On("GetCustomizedUncoreFrequencyMax", packageID, dieID).Return(currMax, nil).Once()
// mock getting current uncore frequency value.
mFetcher.On("GetCurrentUncoreFrequency", packageID, dieID).Return(curr, nil).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addUncoreFrequency(acc, packageID)
require.Len(t, acc.Errors, 1)
require.ErrorContains(
t,
acc.FirstError(),
fmt.Sprintf("failed to get initial uncore frequency limits for package ID %v and die ID %v", packageID, dieID),
)
require.Len(t, acc.GetTelegrafMetrics(), 1)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_package",
// fields
map[string]interface{}{
"uncore_frequency_limit_mhz_min": currMin,
"uncore_frequency_limit_mhz_max": currMax,
"uncore_frequency_mhz_cur": uint64(curr),
},
// tags
map[string]string{
"package_id": strconv.Itoa(packageID),
"type": "current",
"die": strconv.Itoa(dieID),
},
)
mFetcher.AssertExpectations(t)
})
t.Run("FailedToGetCurrentValues", func(t *testing.T) {
initMin := 300.0
initMax := 1200.0
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting die IDs for package ID.
mFetcher.On("GetPackageDieIDs", packageID).Return([]int{dieID}, nil).Once()
// mock getting initial minimum uncore frequency limit.
mFetcher.On("GetInitialUncoreFrequencyMin", packageID, dieID).Return(initMin, nil).Once()
// mock getting initial maximum uncore frequency limit.
mFetcher.On("GetInitialUncoreFrequencyMax", packageID, dieID).Return(initMax, nil).Once()
// mock getting custom minimum uncore frequency limit.
mFetcher.On("GetCustomizedUncoreFrequencyMin", packageID, dieID).Return(500.0, nil).Once()
// mock getting custom maximum uncore frequency limit.
mFetcher.On("GetCustomizedUncoreFrequencyMax", packageID, dieID).Return(1300.0, nil).Once()
// mock getting current uncore frequency value.
mFetcher.On("GetCurrentUncoreFrequency", packageID, dieID).Return(0.0, errors.New("mock error")).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addUncoreFrequency(acc, packageID)
require.Len(t, acc.Errors, 1)
require.ErrorContains(
t,
acc.FirstError(),
fmt.Sprintf("failed to get current uncore frequency values for package ID %v and die ID %v", packageID, dieID),
)
require.Len(t, acc.GetTelegrafMetrics(), 1)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_package",
// fields
map[string]interface{}{
"uncore_frequency_limit_mhz_min": initMin,
"uncore_frequency_limit_mhz_max": initMax,
},
// tags
map[string]string{
"package_id": strconv.Itoa(packageID),
"type": "initial",
"die": strconv.Itoa(dieID),
},
)
mFetcher.AssertExpectations(t)
})
t.Run("Ok", func(t *testing.T) {
initMin := 300.0
initMax := 1200.0
currMin := 500.0
currMax := 2500.0
curr := 1000.0
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting die IDs for package ID.
mFetcher.On("GetPackageDieIDs", packageID).Return([]int{dieID}, nil).Once()
// mock getting initial minimum uncore frequency limit.
mFetcher.On("GetInitialUncoreFrequencyMin", packageID, dieID).Return(initMin, nil).Once()
// mock getting initial maximum uncore frequency limit.
mFetcher.On("GetInitialUncoreFrequencyMax", packageID, dieID).Return(initMax, nil).Once()
// mock getting custom minimum uncore frequency limit.
mFetcher.On("GetCustomizedUncoreFrequencyMin", packageID, dieID).Return(currMin, nil).Once()
// mock getting custom maximum uncore frequency limit.
mFetcher.On("GetCustomizedUncoreFrequencyMax", packageID, dieID).Return(currMax, nil).Once()
// mock getting current uncore frequency value.
mFetcher.On("GetCurrentUncoreFrequency", packageID, dieID).Return(curr, nil).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addUncoreFrequency(acc, packageID)
require.Empty(t, acc.Errors)
require.Len(t, acc.GetTelegrafMetrics(), 2)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_package",
// fields
map[string]interface{}{
"uncore_frequency_limit_mhz_min": initMin,
"uncore_frequency_limit_mhz_max": initMax,
},
// tags
map[string]string{
"package_id": strconv.Itoa(packageID),
"type": "initial",
"die": strconv.Itoa(dieID),
},
)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_package",
// fields
map[string]interface{}{
"uncore_frequency_limit_mhz_min": currMin,
"uncore_frequency_limit_mhz_max": currMax,
"uncore_frequency_mhz_cur": uint64(curr),
},
// tags
map[string]string{
"package_id": strconv.Itoa(packageID),
"type": "current",
"die": strconv.Itoa(dieID),
},
)
mFetcher.AssertExpectations(t)
})
}
func TestAddUncoreFrequencyInitialLimits(t *testing.T) {
packageID, dieID := 0, 0
t.Run("WithModuleNotInitializedError", func(t *testing.T) {
acc := &testutil.Accumulator{}
uncoreFreqErr := &ptel.ModuleNotInitializedError{Name: "uncore_frequency"}
mError := fmt.Errorf("mock error: %w", uncoreFreqErr)
mFetcher := &fetcherMock{}
// mock getting initial minimum uncore frequency limit.
mFetcher.On("GetInitialUncoreFrequencyMin", packageID, dieID).Return(0.0, mError).Twice()
p := &PowerStat{
fetcher: mFetcher,
logOnce: map[string]struct{}{},
}
require.Empty(t, acc.GetTelegrafMetrics())
// First call adds the error to the accumulator and logOnce map.
p.addUncoreFrequencyInitialLimits(acc, packageID, dieID)
// Second call detects previous error in logOnce map and skips adding it to the accumulator.
p.addUncoreFrequencyInitialLimits(acc, packageID, dieID)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q initial limits", packageUncoreFrequency))
require.ErrorContains(t, acc.FirstError(), uncoreFreqErr.Error())
require.Len(t, p.logOnce, 1)
require.Contains(t, p.logOnce, fmt.Sprintf("%s_%s_initial", "uncore_frequency", packageUncoreFrequency))
mFetcher.AssertExpectations(t)
})
t.Run("WithError", func(t *testing.T) {
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting initial minimum uncore frequency limit.
mFetcher.On("GetInitialUncoreFrequencyMin", packageID, dieID).Return(0.0, errors.New("mock error")).Once()
p := &PowerStat{
fetcher: mFetcher,
logOnce: map[string]struct{}{},
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addUncoreFrequencyInitialLimits(acc, packageID, dieID)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 1)
require.ErrorContains(
t,
acc.FirstError(),
fmt.Sprintf("failed to get initial uncore frequency limits for package ID %v and die ID %v", packageID, dieID),
)
require.Empty(t, p.logOnce)
mFetcher.AssertExpectations(t)
})
t.Run("Ok", func(t *testing.T) {
initMin := 300.0
initMax := 1200.0
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting initial minimum uncore frequency limit.
mFetcher.On("GetInitialUncoreFrequencyMin", packageID, dieID).Return(initMin, nil).Once()
// mock getting initial maximum uncore frequency limit.
mFetcher.On("GetInitialUncoreFrequencyMax", packageID, dieID).Return(initMax, nil).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addUncoreFrequencyInitialLimits(acc, packageID, dieID)
require.Empty(t, acc.Errors)
require.Len(t, acc.GetTelegrafMetrics(), 1)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_package",
// fields
map[string]interface{}{
"uncore_frequency_limit_mhz_min": initMin,
"uncore_frequency_limit_mhz_max": initMax,
},
// tags
map[string]string{
"package_id": strconv.Itoa(packageID),
"type": "initial",
"die": strconv.Itoa(dieID),
},
)
mFetcher.AssertExpectations(t)
})
}
func TestAddUncoreFrequencyCurrentValues(t *testing.T) {
packageID, dieID := 0, 0
t.Run("WithModuleNotInitializedError", func(t *testing.T) {
acc := &testutil.Accumulator{}
uncoreFreqErr := &ptel.ModuleNotInitializedError{Name: "uncore_frequency"}
mError := fmt.Errorf("mock error: %w", uncoreFreqErr)
mFetcher := &fetcherMock{}
// mock getting custom minimum uncore frequency limit.
mFetcher.On("GetCustomizedUncoreFrequencyMin", packageID, dieID).Return(0.0, mError).Twice()
p := &PowerStat{
fetcher: mFetcher,
logOnce: map[string]struct{}{},
}
require.Empty(t, acc.GetTelegrafMetrics())
// First call adds the error to the accumulator and logOnce map.
p.addUncoreFrequencyCurrentValues(acc, packageID, dieID)
// Second call detects previous error in logOnce map and skips adding it to the accumulator.
p.addUncoreFrequencyCurrentValues(acc, packageID, dieID)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q current value and limits", packageUncoreFrequency))
require.ErrorContains(t, acc.FirstError(), uncoreFreqErr.Error())
require.Len(t, p.logOnce, 1)
require.Contains(t, p.logOnce, fmt.Sprintf("%s_%s_current", "uncore_frequency", packageUncoreFrequency))
mFetcher.AssertExpectations(t)
})
t.Run("WithError", func(t *testing.T) {
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting custom minimum uncore frequency limit.
mFetcher.On("GetCustomizedUncoreFrequencyMin", packageID, dieID).Return(0.0, errors.New("mock error")).Once()
p := &PowerStat{
fetcher: mFetcher,
logOnce: map[string]struct{}{},
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addUncoreFrequencyCurrentValues(acc, packageID, dieID)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 1)
require.ErrorContains(
t,
acc.FirstError(),
fmt.Sprintf("failed to get current uncore frequency values for package ID %v and die ID %v", packageID, dieID),
)
require.Empty(t, p.logOnce)
mFetcher.AssertExpectations(t)
})
t.Run("Ok", func(t *testing.T) {
currMin := 500.0
currMax := 2500.0
curr := 1000.0
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting custom minimum uncore frequency limit.
mFetcher.On("GetCustomizedUncoreFrequencyMin", packageID, dieID).Return(currMin, nil).Once()
// mock getting custom maximum uncore frequency limit.
mFetcher.On("GetCustomizedUncoreFrequencyMax", packageID, dieID).Return(currMax, nil).Once()
// mock getting current uncore frequency value.
mFetcher.On("GetCurrentUncoreFrequency", packageID, dieID).Return(curr, nil).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addUncoreFrequencyCurrentValues(acc, packageID, dieID)
require.Empty(t, acc.Errors)
require.Len(t, acc.GetTelegrafMetrics(), 1)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_package",
// fields
map[string]interface{}{
"uncore_frequency_limit_mhz_min": currMin,
"uncore_frequency_limit_mhz_max": currMax,
"uncore_frequency_mhz_cur": uint64(curr),
},
// tags
map[string]string{
"package_id": strconv.Itoa(packageID),
"type": "current",
"die": strconv.Itoa(dieID),
},
)
mFetcher.AssertExpectations(t)
})
}
func TestGetUncoreFreqInitialLimits(t *testing.T) {
packageID, dieID := 0, 0
t.Run("FailsToGetInitialMinLimit", func(t *testing.T) {
mFetcher := &fetcherMock{}
// mock getting initial minimum uncore frequency limit.
mFetcher.On("GetInitialUncoreFrequencyMin", packageID, dieID).Return(0.0, errors.New("mock error")).Once()
initMin, initMax, err := getUncoreFreqInitialLimits(mFetcher, packageID, dieID)
require.ErrorContains(t, err, "failed to get initial minimum uncore frequency limit")
require.Zero(t, initMin)
require.Zero(t, initMax)
mFetcher.AssertExpectations(t)
})
t.Run("FailsToGetInitialMaxLimit", func(t *testing.T) {
mFetcher := &fetcherMock{}
// mock getting initial minimum uncore frequency limit.
mFetcher.On("GetInitialUncoreFrequencyMin", packageID, dieID).Return(800.0, nil).Once()
// mock getting initial maximum uncore frequency limit.
mFetcher.On("GetInitialUncoreFrequencyMax", packageID, dieID).Return(0.0, errors.New("mock error")).Once()
initMin, initMax, err := getUncoreFreqInitialLimits(mFetcher, packageID, dieID)
require.ErrorContains(t, err, "failed to get initial maximum uncore frequency limit")
require.Zero(t, initMin)
require.Zero(t, initMax)
mFetcher.AssertExpectations(t)
})
t.Run("Ok", func(t *testing.T) {
initMinExp := 300.0
initMaxExp := 1500.0
mFetcher := &fetcherMock{}
// mock getting initial minimum uncore frequency limit.
mFetcher.On("GetInitialUncoreFrequencyMin", packageID, dieID).Return(initMinExp, nil).Once()
// mock getting initial maximum uncore frequency limit.
mFetcher.On("GetInitialUncoreFrequencyMax", packageID, dieID).Return(initMaxExp, nil).Once()
initMin, initMax, err := getUncoreFreqInitialLimits(mFetcher, packageID, dieID)
require.NoError(t, err)
require.InDelta(t, initMinExp, initMin, testutil.DefaultDelta)
require.InDelta(t, initMaxExp, initMax, testutil.DefaultDelta)
mFetcher.AssertExpectations(t)
})
}
func TestGetUncoreFreqCurrentValues(t *testing.T) {
packageID, dieID := 0, 0
t.Run("FailsToGetCurrentMinLimit", func(t *testing.T) {
mFetcher := &fetcherMock{}
// mock getting current minimum uncore frequency limit.
mFetcher.On("GetCustomizedUncoreFrequencyMin", packageID, dieID).Return(0.0, errors.New("mock error")).Once()
values, err := getUncoreFreqCurrentValues(mFetcher, packageID, dieID)
require.ErrorContains(t, err, "failed to get current minimum uncore frequency limit")
require.Equal(t, uncoreFreqValues{}, values)
mFetcher.AssertExpectations(t)
})
t.Run("FailsToGetCurrentMaxLimit", func(t *testing.T) {
mFetcher := &fetcherMock{}
// mock getting current minimum uncore frequency limit.
mFetcher.On("GetCustomizedUncoreFrequencyMin", packageID, dieID).Return(1000.0, nil).Once()
// mock getting current maximum uncore frequency limit.
mFetcher.On("GetCustomizedUncoreFrequencyMax", packageID, dieID).Return(0.0, errors.New("mock error")).Once()
values, err := getUncoreFreqCurrentValues(mFetcher, packageID, dieID)
require.ErrorContains(t, err, "failed to get current maximum uncore frequency limit")
require.Equal(t, uncoreFreqValues{}, values)
mFetcher.AssertExpectations(t)
})
t.Run("FailsToGetCurrentValue", func(t *testing.T) {
mFetcher := &fetcherMock{}
// mock getting custom minimum uncore frequency limit.
mFetcher.On("GetCustomizedUncoreFrequencyMin", packageID, dieID).Return(1000.0, nil).Once()
// mock getting custom maximum uncore frequency limit.
mFetcher.On("GetCustomizedUncoreFrequencyMax", packageID, dieID).Return(2000.0, nil).Once()
// mock getting current uncore frequency value.
mFetcher.On("GetCurrentUncoreFrequency", packageID, dieID).Return(0.0, errors.New("mock error")).Once()
values, err := getUncoreFreqCurrentValues(mFetcher, packageID, dieID)
require.ErrorContains(t, err, "failed to get current uncore frequency")
require.Equal(t, uncoreFreqValues{}, values)
mFetcher.AssertExpectations(t)
})
t.Run("Ok", func(t *testing.T) {
minUncore := 500.0
maxUncore := 1500.0
current := 750.0
uncoreFreqValExp := uncoreFreqValues{
currMin: minUncore,
currMax: maxUncore,
curr: current,
}
mFetcher := &fetcherMock{}
// mock getting custom minimum uncore frequency limit.
mFetcher.On("GetCustomizedUncoreFrequencyMin", packageID, dieID).Return(minUncore, nil).Once()
// mock getting custom maximum uncore frequency limit.
mFetcher.On("GetCustomizedUncoreFrequencyMax", packageID, dieID).Return(maxUncore, nil).Once()
// mock getting current uncore frequency value.
mFetcher.On("GetCurrentUncoreFrequency", packageID, dieID).Return(current, nil).Once()
values, err := getUncoreFreqCurrentValues(mFetcher, packageID, dieID)
require.NoError(t, err)
require.Equal(t, uncoreFreqValExp, values)
mFetcher.AssertExpectations(t)
})
}
func TestAddMaxTurboFreqLimits(t *testing.T) {
t.Run("FailedToGetMetricModuleNotInitializedError", func(t *testing.T) {
packageID := 1
acc := &testutil.Accumulator{}
mErr := &ptel.ModuleNotInitializedError{Name: "msr"}
mFetcher := &fetcherMock{}
// mock getting max turbo frequency list.
mFetcher.On("GetMaxTurboFreqList", packageID).Return(nil, mErr).Twice()
p := &PowerStat{
fetcher: mFetcher,
logOnce: map[string]struct{}{},
}
require.Empty(t, acc.GetTelegrafMetrics())
// First call adds the error to the accumulator and key to logOnce map.
p.addMaxTurboFreqLimits(acc, packageID)
// Second call detects previous error in logOnce map and skips adding it to the accumulator.
p.addMaxTurboFreqLimits(acc, packageID)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q: %v", packageTurboLimit, mErr))
require.Len(t, p.logOnce, 1)
require.Contains(t, p.logOnce, "msr_max_turbo_frequency")
mFetcher.AssertExpectations(t)
})
t.Run("FailedToGetMetric", func(t *testing.T) {
packageID := 1
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting max turbo frequency list.
mFetcher.On("GetMaxTurboFreqList", packageID).Return(nil, errors.New("mock error")).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addMaxTurboFreqLimits(acc, packageID)
require.Empty(t, acc.GetTelegrafMetrics())
require.Len(t, acc.Errors, 1)
require.ErrorContains(t, acc.FirstError(), fmt.Sprintf("failed to get %q for package ID %v", packageTurboLimit, packageID))
mFetcher.AssertExpectations(t)
})
t.Run("CPUIsHybrid", func(t *testing.T) {
packageID := 1
maxTurboFreqList := []ptel.MaxTurboFreq{
{
Value: 1000,
ActiveCores: 10,
Secondary: true,
},
{
Value: 2000,
ActiveCores: 20,
},
}
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting max turbo frequency list.
mFetcher.On("GetMaxTurboFreqList", packageID).Return(maxTurboFreqList, nil).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addMaxTurboFreqLimits(acc, packageID)
require.Empty(t, acc.Errors)
require.Len(t, acc.GetTelegrafMetrics(), 2)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_package",
// fields
map[string]interface{}{
"max_turbo_frequency_mhz": maxTurboFreqList[0].Value,
},
// tags
map[string]string{
"package_id": strconv.Itoa(packageID),
"active_cores": strconv.Itoa(int(maxTurboFreqList[0].ActiveCores)),
"hybrid": "secondary",
},
)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_package",
// fields
map[string]interface{}{
"max_turbo_frequency_mhz": maxTurboFreqList[1].Value,
},
// tags
map[string]string{
"package_id": strconv.Itoa(packageID),
"active_cores": strconv.Itoa(int(maxTurboFreqList[1].ActiveCores)),
"hybrid": "primary",
},
)
mFetcher.AssertExpectations(t)
})
t.Run("CPUIsNotHybrid", func(t *testing.T) {
packageID := 1
maxTurboFreqList := []ptel.MaxTurboFreq{
{
Value: 1000,
ActiveCores: 10,
},
{
Value: 2000,
ActiveCores: 20,
},
}
acc := &testutil.Accumulator{}
mFetcher := &fetcherMock{}
// mock getting max turbo frequency list.
mFetcher.On("GetMaxTurboFreqList", packageID).Return(maxTurboFreqList, nil).Once()
p := &PowerStat{
fetcher: mFetcher,
}
require.Empty(t, acc.GetTelegrafMetrics())
p.addMaxTurboFreqLimits(acc, packageID)
require.Empty(t, acc.Errors)
require.Len(t, acc.GetTelegrafMetrics(), 2)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_package",
// fields
map[string]interface{}{
"max_turbo_frequency_mhz": maxTurboFreqList[0].Value,
},
// tags
map[string]string{
"package_id": strconv.Itoa(packageID),
"active_cores": strconv.Itoa(int(maxTurboFreqList[0].ActiveCores)),
},
)
acc.AssertContainsTaggedFields(
t,
// measurement
"powerstat_package",
// fields
map[string]interface{}{
"max_turbo_frequency_mhz": maxTurboFreqList[1].Value,
},
// tags
map[string]string{
"package_id": strconv.Itoa(packageID),
"active_cores": strconv.Itoa(int(maxTurboFreqList[1].ActiveCores)),
},
)
mFetcher.AssertExpectations(t)
})
}