diff --git a/pkg/kubelet/winstats/network_stats.go b/pkg/kubelet/winstats/network_stats.go index 0a9d21a3a98..b584d64381d 100644 --- a/pkg/kubelet/winstats/network_stats.go +++ b/pkg/kubelet/winstats/network_stats.go @@ -40,14 +40,14 @@ const ( // networkCounter contains the counters for network adapters. type networkCounter struct { - packetsReceivedPerSecondCounter *perfCounter - packetsSentPerSecondCounter *perfCounter - bytesReceivedPerSecondCounter *perfCounter - bytesSentPerSecondCounter *perfCounter - packetsReceivedDiscardedCounter *perfCounter - packetsReceivedErrorsCounter *perfCounter - packetsOutboundDiscardedCounter *perfCounter - packetsOutboundErrorsCounter *perfCounter + packetsReceivedPerSecondCounter perfCounter + packetsSentPerSecondCounter perfCounter + bytesReceivedPerSecondCounter perfCounter + bytesSentPerSecondCounter perfCounter + packetsReceivedDiscardedCounter perfCounter + packetsReceivedErrorsCounter perfCounter + packetsOutboundDiscardedCounter perfCounter + packetsOutboundErrorsCounter perfCounter mu sync.RWMutex adapterStats map[string]cadvisorapi.InterfaceStats diff --git a/pkg/kubelet/winstats/network_stats_test.go b/pkg/kubelet/winstats/network_stats_test.go new file mode 100644 index 00000000000..a9e18f90863 --- /dev/null +++ b/pkg/kubelet/winstats/network_stats_test.go @@ -0,0 +1,176 @@ +//go:build windows +// +build windows + +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package winstats + +import ( + "errors" + "testing" + + cadvisorapi "github.com/google/cadvisor/info/v1" + "github.com/stretchr/testify/assert" +) + +const fakeAdapterName = "fake-adapter" + +type fakePerfCounterImpl struct { + // Returned name. + name string + // Returned value. + value uint64 + // If the perfCounter should raise an error. + raiseError bool +} + +func (p *fakePerfCounterImpl) getData() (uint64, error) { + if p.raiseError { + return 0, errors.New("Expected getData error.") + } + return p.value, nil +} + +func (p *fakePerfCounterImpl) getDataList() (map[string]uint64, error) { + if p.raiseError { + return nil, errors.New("Expected getDataList error.") + } + + data := make(map[string]uint64) + data[p.name] = p.value + return data, nil +} + +func newFakedNetworkCounters(raiseError bool) *networkCounter { + counters := make([]*fakePerfCounterImpl, 8) + for i := 0; i < 8; i++ { + counters[i] = &fakePerfCounterImpl{ + name: fakeAdapterName, + value: 1, + raiseError: raiseError, + } + } + return &networkCounter{ + packetsReceivedPerSecondCounter: counters[0], + packetsSentPerSecondCounter: counters[1], + bytesReceivedPerSecondCounter: counters[2], + bytesSentPerSecondCounter: counters[3], + packetsReceivedDiscardedCounter: counters[4], + packetsReceivedErrorsCounter: counters[5], + packetsOutboundDiscardedCounter: counters[6], + packetsOutboundErrorsCounter: counters[7], + adapterStats: map[string]cadvisorapi.InterfaceStats{}, + } +} + +func TestNewNetworkCounters(t *testing.T) { + _, err := newNetworkCounters() + assert.NoError(t, err) +} + +func TestNetworkGetData(t *testing.T) { + netCounter := newFakedNetworkCounters(false) + + // Add a net adapter that no longer exists in the adapterStats cache. It will + // have to be cleaned up after processing the data. + netCounter.adapterStats["other-fake-adapter"] = cadvisorapi.InterfaceStats{} + + data, err := netCounter.getData() + assert.NoError(t, err) + + // Make sure that we only have data from a single net adapter. + expectedStats := cadvisorapi.InterfaceStats{ + Name: fakeAdapterName, + RxPackets: 1, + TxPackets: 1, + RxBytes: 1, + TxBytes: 1, + RxDropped: 1, + RxErrors: 1, + TxDropped: 1, + TxErrors: 1, + } + assert.Equal(t, []cadvisorapi.InterfaceStats{expectedStats}, data) + + // The returned data is cumulative, so the resulting values should be double on a second call. + data, err = netCounter.getData() + assert.NoError(t, err) + expectedStats = cadvisorapi.InterfaceStats{ + Name: fakeAdapterName, + RxPackets: 2, + TxPackets: 2, + RxBytes: 2, + TxBytes: 2, + RxDropped: 1, + RxErrors: 1, + TxDropped: 1, + TxErrors: 1, + } + assert.Equal(t, []cadvisorapi.InterfaceStats{expectedStats}, data) +} + +func TestNetworkGetDataFailures(t *testing.T) { + netCounter := newFakedNetworkCounters(true) + + _, err := netCounter.getData() + expectedMsg := "Expected getDataList error." + if err == nil || err.Error() != expectedMsg { + t.Fatalf("expected error message `%s` but got `%v`", expectedMsg, err) + } + + _, err = netCounter.getData() + netCounter.packetsReceivedPerSecondCounter.(*fakePerfCounterImpl).raiseError = false + if err == nil || err.Error() != expectedMsg { + t.Fatalf("expected error message `%s` but got `%v`", expectedMsg, err) + } + + _, err = netCounter.getData() + netCounter.packetsSentPerSecondCounter.(*fakePerfCounterImpl).raiseError = false + if err == nil || err.Error() != expectedMsg { + t.Fatalf("expected error message `%s` but got `%v`", expectedMsg, err) + } + + _, err = netCounter.getData() + netCounter.bytesReceivedPerSecondCounter.(*fakePerfCounterImpl).raiseError = false + if err == nil || err.Error() != expectedMsg { + t.Fatalf("expected error message `%s` but got `%v`", expectedMsg, err) + } + + _, err = netCounter.getData() + netCounter.bytesSentPerSecondCounter.(*fakePerfCounterImpl).raiseError = false + if err == nil || err.Error() != expectedMsg { + t.Fatalf("expected error message `%s` but got `%v`", expectedMsg, err) + } + + _, err = netCounter.getData() + netCounter.packetsReceivedDiscardedCounter.(*fakePerfCounterImpl).raiseError = false + if err == nil || err.Error() != expectedMsg { + t.Fatalf("expected error message `%s` but got `%v`", expectedMsg, err) + } + + _, err = netCounter.getData() + netCounter.packetsReceivedErrorsCounter.(*fakePerfCounterImpl).raiseError = false + if err == nil || err.Error() != expectedMsg { + t.Fatalf("expected error message `%s` but got `%v`", expectedMsg, err) + } + + _, err = netCounter.getData() + netCounter.packetsOutboundDiscardedCounter.(*fakePerfCounterImpl).raiseError = false + if err == nil || err.Error() != expectedMsg { + t.Fatalf("expected error message `%s` but got `%v`", expectedMsg, err) + } +} diff --git a/pkg/kubelet/winstats/perfcounter_nodestats.go b/pkg/kubelet/winstats/perfcounter_nodestats.go index 9bd4e13796a..41bdd21168b 100644 --- a/pkg/kubelet/winstats/perfcounter_nodestats.go +++ b/pkg/kubelet/winstats/perfcounter_nodestats.go @@ -202,9 +202,9 @@ func (p *perfCounterNodeStatsClient) getNodeInfo() nodeInfo { return p.nodeInfo } -func (p *perfCounterNodeStatsClient) collectMetricsData(cpuCounter, memWorkingSetCounter, memCommittedBytesCounter *perfCounter, networkAdapterCounter *networkCounter) { +func (p *perfCounterNodeStatsClient) collectMetricsData(cpuCounter, memWorkingSetCounter, memCommittedBytesCounter perfCounter, networkAdapterCounter *networkCounter) { cpuValue, err := cpuCounter.getData() - cpuCores := runtime.NumCPU() + cpuCores := ProcessorCount() if err != nil { klog.ErrorS(err, "Unable to get cpu perf counter data") return diff --git a/pkg/kubelet/winstats/perfcounter_nodestats_test.go b/pkg/kubelet/winstats/perfcounter_nodestats_test.go new file mode 100644 index 00000000000..11f42331fd9 --- /dev/null +++ b/pkg/kubelet/winstats/perfcounter_nodestats_test.go @@ -0,0 +1,188 @@ +//go:build windows +// +build windows + +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package winstats + +import ( + "os" + "strconv" + "testing" + "time" + + cadvisorapi "github.com/google/cadvisor/info/v1" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/util/wait" +) + +func TestMonitoring(t *testing.T) { + counterClient, err := NewPerfCounterClient() + assert.NoError(t, err) + + // assert that startMonitoring has been called. nodeInfo should be set. + assert.NotNil(t, counterClient.(*StatsClient).client.getNodeInfo()) + + // Wait until we get a non-zero node metrics. + if pollErr := wait.Poll(100*time.Millisecond, 5*perfCounterUpdatePeriod, func() (bool, error) { + metrics, _ := counterClient.(*StatsClient).client.getNodeMetrics() + if metrics.memoryPrivWorkingSetBytes != 0 { + return true, nil + } + + return false, nil + }); pollErr != nil { + t.Fatalf("Encountered error: `%v'", pollErr) + } +} + +func TestGetMachineInfo(t *testing.T) { + p := perfCounterNodeStatsClient{ + nodeInfo: nodeInfo{ + memoryPhysicalCapacityBytes: 100, + }, + } + + machineInfo, err := p.getMachineInfo() + assert.NoError(t, err) + assert.Equal(t, uint64(100), machineInfo.MemoryCapacity) + hostname, _ := os.Hostname() + assert.Equal(t, hostname, machineInfo.MachineID) + + // Check if it's an UUID. + _, err = uuid.Parse(machineInfo.SystemUUID) + assert.NoError(t, err) + + id, err := strconv.Atoi(machineInfo.BootID) + assert.NoError(t, err) + assert.NotZero(t, id) +} + +func TestGetVersionInfo(t *testing.T) { + client := perfCounterNodeStatsClient{ + nodeInfo: nodeInfo{ + kernelVersion: "foo", + osImageVersion: "lish", + }, + } + + info, _ := client.getVersionInfo() + expected := &cadvisorapi.VersionInfo{ + KernelVersion: "foo", + ContainerOsVersion: "lish", + } + assert.Equal(t, expected, info) +} + +func TestCollectMetricsData(t *testing.T) { + p := perfCounterNodeStatsClient{} + + cpuCounter := &fakePerfCounterImpl{ + value: 1, + raiseError: true, + } + memWorkingSetCounter := &fakePerfCounterImpl{ + value: 2, + raiseError: true, + } + memCommittedBytesCounter := &fakePerfCounterImpl{ + value: 3, + raiseError: true, + } + networkAdapterCounter := newFakedNetworkCounters(true) + + // Checking the error cases first. + p.collectMetricsData(cpuCounter, memWorkingSetCounter, memCommittedBytesCounter, networkAdapterCounter) + metrics, _ := p.getNodeMetrics() + expectedMetrics := nodeMetrics{} + assert.Equal(t, expectedMetrics, metrics) + + cpuCounter.raiseError = false + p.collectMetricsData(cpuCounter, memWorkingSetCounter, memCommittedBytesCounter, networkAdapterCounter) + metrics, _ = p.getNodeMetrics() + assert.Equal(t, expectedMetrics, metrics) + + memWorkingSetCounter.raiseError = false + p.collectMetricsData(cpuCounter, memWorkingSetCounter, memCommittedBytesCounter, networkAdapterCounter) + metrics, _ = p.getNodeMetrics() + assert.Equal(t, expectedMetrics, metrics) + + memCommittedBytesCounter.raiseError = false + p.collectMetricsData(cpuCounter, memWorkingSetCounter, memCommittedBytesCounter, networkAdapterCounter) + metrics, _ = p.getNodeMetrics() + assert.Equal(t, expectedMetrics, metrics) + + networkAdapterCounter = newFakedNetworkCounters(false) + p.collectMetricsData(cpuCounter, memWorkingSetCounter, memCommittedBytesCounter, networkAdapterCounter) + metrics, _ = p.getNodeMetrics() + expectedMetrics = nodeMetrics{ + cpuUsageCoreNanoSeconds: uint64(ProcessorCount()) * 1e7, + cpuUsageNanoCores: 0, + memoryPrivWorkingSetBytes: 2, + memoryCommittedBytes: 3, + interfaceStats: networkAdapterCounter.listInterfaceStats(), + timeStamp: time.Now(), + } + assert.Equal(t, expectedMetrics, metrics) +} + +func TestConvertCPUValue(t *testing.T) { + testCases := []struct { + cpuValue uint64 + expected uint64 + }{ + {cpuValue: uint64(50), expected: uint64(2000000000)}, + {cpuValue: uint64(0), expected: uint64(0)}, + {cpuValue: uint64(100), expected: uint64(4000000000)}, + } + var cpuCores = 4 + + for _, tc := range testCases { + p := perfCounterNodeStatsClient{} + newValue := p.convertCPUValue(cpuCores, tc.cpuValue) + assert.Equal(t, tc.expected, newValue) + } +} + +func TestGetCPUUsageNanoCores(t *testing.T) { + testCases := []struct { + latestValue uint64 + previousValue uint64 + expected uint64 + }{ + {latestValue: uint64(0), previousValue: uint64(0), expected: uint64(0)}, + {latestValue: uint64(2000000000), previousValue: uint64(0), expected: uint64(200000000)}, + {latestValue: uint64(5000000000), previousValue: uint64(2000000000), expected: uint64(300000000)}, + } + + for _, tc := range testCases { + p := perfCounterNodeStatsClient{} + p.cpuUsageCoreNanoSecondsCache = cpuUsageCoreNanoSecondsCache{ + latestValue: tc.latestValue, + previousValue: tc.previousValue, + } + cpuUsageNanoCores := p.getCPUUsageNanoCores() + assert.Equal(t, tc.expected, cpuUsageNanoCores) + } +} + +func testGetPhysicallyInstalledSystemMemoryBytes(t *testing.T) { + totalMemory, err := getPhysicallyInstalledSystemMemoryBytes() + assert.NoError(t, err) + assert.NotZero(t, totalMemory) +} diff --git a/pkg/kubelet/winstats/perfcounters.go b/pkg/kubelet/winstats/perfcounters.go index 01573e8ecbf..4296684ff21 100644 --- a/pkg/kubelet/winstats/perfcounters.go +++ b/pkg/kubelet/winstats/perfcounters.go @@ -41,12 +41,17 @@ const ( defaultCachePeriod = 10 * time.Second ) -type perfCounter struct { +type perfCounter interface { + getData() (uint64, error) + getDataList() (map[string]uint64, error) +} + +type perfCounterImpl struct { queryHandle win_pdh.PDH_HQUERY counterHandle win_pdh.PDH_HCOUNTER } -func newPerfCounter(counter string) (*perfCounter, error) { +func newPerfCounter(counter string) (perfCounter, error) { var queryHandle win_pdh.PDH_HQUERY var counterHandle win_pdh.PDH_HCOUNTER @@ -65,35 +70,20 @@ func newPerfCounter(counter string) (*perfCounter, error) { return nil, fmt.Errorf("unable to collect data from counter. Error code is %x", ret) } - return &perfCounter{ + return &perfCounterImpl{ queryHandle: queryHandle, counterHandle: counterHandle, }, nil } // getData is used for getting data without * in counter name. -func (p *perfCounter) getData() (uint64, error) { - ret := win_pdh.PdhCollectQueryData(p.queryHandle) - if ret != win_pdh.ERROR_SUCCESS { - return 0, fmt.Errorf("unable to collect data from counter. Error code is %x", ret) - } - - var bufSize, bufCount uint32 - var size = uint32(unsafe.Sizeof(win_pdh.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE{})) - var emptyBuf [1]win_pdh.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE // need at least 1 addressable null ptr. - var data uint64 - - ret = win_pdh.PdhGetFormattedCounterArrayDouble(p.counterHandle, &bufSize, &bufCount, &emptyBuf[0]) - if ret != win_pdh.PDH_MORE_DATA { - return 0, fmt.Errorf("unable to collect data from counter. Error code is %x", ret) - } - - filledBuf := make([]win_pdh.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE, bufCount*size) - ret = win_pdh.PdhGetFormattedCounterArrayDouble(p.counterHandle, &bufSize, &bufCount, &filledBuf[0]) - if ret != win_pdh.ERROR_SUCCESS { - return 0, fmt.Errorf("unable to collect data from counter. Error code is %x", ret) +func (p *perfCounterImpl) getData() (uint64, error) { + filledBuf, bufCount, err := p.getQueriedData() + if err != nil { + return 0, err } + var data uint64 = 0 for i := 0; i < int(bufCount); i++ { c := filledBuf[i] data = uint64(c.FmtValue.DoubleValue) @@ -102,29 +92,14 @@ func (p *perfCounter) getData() (uint64, error) { return data, nil } -// getData is used for getting data with * in counter name. -func (p *perfCounter) getDataList() (map[string]uint64, error) { - ret := win_pdh.PdhCollectQueryData(p.queryHandle) - if ret != win_pdh.ERROR_SUCCESS { - return nil, fmt.Errorf("unable to collect data from counter. Error code is %x", ret) +// getDataList is used for getting data with * in counter name. +func (p *perfCounterImpl) getDataList() (map[string]uint64, error) { + filledBuf, bufCount, err := p.getQueriedData() + if err != nil { + return nil, err } - var bufSize, bufCount uint32 - var size = uint32(unsafe.Sizeof(win_pdh.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE{})) - var emptyBuf [1]win_pdh.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE // need at least 1 addressable null ptr. data := map[string]uint64{} - - ret = win_pdh.PdhGetFormattedCounterArrayDouble(p.counterHandle, &bufSize, &bufCount, &emptyBuf[0]) - if ret != win_pdh.PDH_MORE_DATA { - return nil, fmt.Errorf("unable to collect data from counter. Error code is %x", ret) - } - - filledBuf := make([]win_pdh.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE, bufCount*size) - ret = win_pdh.PdhGetFormattedCounterArrayDouble(p.counterHandle, &bufSize, &bufCount, &filledBuf[0]) - if ret != win_pdh.ERROR_SUCCESS { - return nil, fmt.Errorf("unable to collect data from counter. Error code is %x", ret) - } - for i := 0; i < int(bufCount); i++ { c := filledBuf[i] value := uint64(c.FmtValue.DoubleValue) @@ -134,3 +109,28 @@ func (p *perfCounter) getDataList() (map[string]uint64, error) { return data, nil } + +// getQueriedData is used for getting data using the given query handle. +func (p *perfCounterImpl) getQueriedData() ([]win_pdh.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE, uint32, error) { + ret := win_pdh.PdhCollectQueryData(p.queryHandle) + if ret != win_pdh.ERROR_SUCCESS { + return nil, 0, fmt.Errorf("unable to collect data from counter. Error code is %x", ret) + } + + var bufSize, bufCount uint32 + var size = uint32(unsafe.Sizeof(win_pdh.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE{})) + var emptyBuf [1]win_pdh.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE // need at least 1 addressable null ptr. + + ret = win_pdh.PdhGetFormattedCounterArrayDouble(p.counterHandle, &bufSize, &bufCount, &emptyBuf[0]) + if ret != win_pdh.PDH_MORE_DATA { + return nil, 0, fmt.Errorf("unable to collect data from counter. Error code is %x", ret) + } + + filledBuf := make([]win_pdh.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE, bufCount*size) + ret = win_pdh.PdhGetFormattedCounterArrayDouble(p.counterHandle, &bufSize, &bufCount, &filledBuf[0]) + if ret != win_pdh.ERROR_SUCCESS { + return nil, 0, fmt.Errorf("unable to collect data from counter. Error code is %x", ret) + } + + return filledBuf, bufCount, nil +} diff --git a/pkg/kubelet/winstats/perfcounters_test.go b/pkg/kubelet/winstats/perfcounters_test.go new file mode 100644 index 00000000000..151aab4eaea --- /dev/null +++ b/pkg/kubelet/winstats/perfcounters_test.go @@ -0,0 +1,136 @@ +//go:build windows +// +build windows + +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package winstats + +import ( + "testing" + "time" + + "k8s.io/apimachinery/pkg/util/wait" +) + +func TestPerfCounter(t *testing.T) { + testCases := map[string]struct { + counter string + skipCheck bool + expectErr bool + expectedErrMsg string + }{ + "CPU Query": { + counter: cpuQuery, + }, + "Memory Prvate Working Set Query": { + counter: memoryPrivWorkingSetQuery, + }, + "Memory Committed Bytes Query": { + counter: memoryCommittedBytesQuery, + }, + "Net Adapter Packets Received/sec Query": { + counter: packetsReceivedPerSecondQuery, + skipCheck: true, + }, + "Net Adapter Packets Sent/sec Query": { + counter: packetsSentPerSecondQuery, + skipCheck: true, + }, + "Net Adapter Bytes Received/sec Query": { + counter: bytesReceivedPerSecondQuery, + skipCheck: true, + }, + "Net Adapter Bytes Sent/sec Query": { + counter: bytesSentPerSecondQuery, + skipCheck: true, + }, + "Net Adapter Packets Received Discarded Query": { + counter: packetsReceivedDiscardedQuery, + skipCheck: true, + }, + "Net Adapter Packets Received Errors Query": { + counter: packetsReceivedErrorsQuery, + skipCheck: true, + }, + "Net Adapter Packets Outbound Discarded Query": { + counter: packetsOutboundDiscardedQuery, + skipCheck: true, + }, + "Net Adapter Packets Outbound Errors Query": { + counter: packetsOutboundErrorsQuery, + skipCheck: true, + }, + "Invalid Query": { + counter: "foo", + expectErr: true, + expectedErrMsg: "unable to add process counter: foo. Error code is c0000bc0", + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + counter, err := newPerfCounter(tc.counter) + if tc.expectErr { + if err == nil || err.Error() != tc.expectedErrMsg { + t.Fatalf("expected error message `%s` but got `%v`", tc.expectedErrMsg, err) + } + return + } + + // There are some counters that we can't expect to see any non-zero values, like the + // networking-related counters. + if tc.skipCheck { + return + } + + // Wait until we get a non-zero perf counter data. + if pollErr := wait.Poll(100*time.Millisecond, 5*perfCounterUpdatePeriod, func() (bool, error) { + data, err := counter.getData() + if err != nil { + return false, err + } + + if data != 0 { + return true, nil + } + + return false, nil + }); pollErr != nil { + t.Fatalf("Encountered error: `%v'", pollErr) + return + } + + // Check that we have at least one non-zero value in the data list. + if pollErr := wait.Poll(100*time.Millisecond, 5*perfCounterUpdatePeriod, func() (bool, error) { + dataList, err := counter.getDataList() + if err != nil { + return false, err + } + + for _, value := range dataList { + if value != 0 { + return true, nil + } + } + + return false, nil + }); pollErr != nil { + t.Fatalf("Encountered error: `%v'", pollErr) + } + }) + } +} diff --git a/pkg/kubelet/winstats/winstats_test.go b/pkg/kubelet/winstats/winstats_test.go index 93f142109fb..7bdb5cba301 100644 --- a/pkg/kubelet/winstats/winstats_test.go +++ b/pkg/kubelet/winstats/winstats_test.go @@ -20,6 +20,7 @@ limitations under the License. package winstats import ( + "os" "testing" "time" @@ -133,44 +134,24 @@ func TestWinVersionInfo(t *testing.T) { KernelVersion: "v42"}) } -func TestConvertCPUValue(t *testing.T) { - testCases := []struct { - cpuValue uint64 - expected uint64 - }{ - {cpuValue: uint64(50), expected: uint64(2000000000)}, - {cpuValue: uint64(0), expected: uint64(0)}, - {cpuValue: uint64(100), expected: uint64(4000000000)}, - } - var cpuCores = 4 +func TestGetDirFsInfo(t *testing.T) { + c := getClient(t) - for _, tc := range testCases { - p := perfCounterNodeStatsClient{} - newValue := p.convertCPUValue(cpuCores, tc.cpuValue) - assert.Equal(t, tc.expected, newValue) - } -} - -func TestGetCPUUsageNanoCores(t *testing.T) { - testCases := []struct { - latestValue uint64 - previousValue uint64 - expected uint64 - }{ - {latestValue: uint64(0), previousValue: uint64(0), expected: uint64(0)}, - {latestValue: uint64(2000000000), previousValue: uint64(0), expected: uint64(200000000)}, - {latestValue: uint64(5000000000), previousValue: uint64(2000000000), expected: uint64(300000000)}, + // Try with a non-existent path. + _, err := c.GetDirFsInfo("foo/lish") + expectedErrMsg := "The system cannot find the path specified." + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected error message `%s` but got `%v`", expectedErrMsg, err) } - for _, tc := range testCases { - p := perfCounterNodeStatsClient{} - p.cpuUsageCoreNanoSecondsCache = cpuUsageCoreNanoSecondsCache{ - latestValue: tc.latestValue, - previousValue: tc.previousValue, - } - cpuUsageNanoCores := p.getCPUUsageNanoCores() - assert.Equal(t, tc.expected, cpuUsageNanoCores) - } + dir, err := os.MkdirTemp("", "fsinfo") + assert.NoError(t, err) + defer os.RemoveAll(dir) + + fsInfo, err := c.GetDirFsInfo(dir) + assert.NoError(t, err) + assert.NotZero(t, fsInfo.Capacity) + assert.NotZero(t, fsInfo.Available) } func getClient(t *testing.T) Client {