Merge pull request #49734 from Shaggy84675/49709-fix_system_cpu_usage_stat

Fix docker stats parsing with large amount of interrupts
This commit is contained in:
Paweł Gronowski
2025-04-07 16:41:25 +00:00
committed by GitHub
3 changed files with 185 additions and 10 deletions

View File

@@ -311,6 +311,8 @@ const (
nanoSecondsPerSecond = 1e9
)
var procStatPath = "/proc/stat"
// getSystemCPUUsage returns the host system's cpu usage in
// nanoseconds and number of online CPUs. An error is returned
// if the format of the underlying file does not match.
@@ -320,17 +322,28 @@ const (
// provided. See `man 5 proc` for details on specific field
// information.
func getSystemCPUUsage() (cpuUsage uint64, cpuNum uint32, _ error) {
f, err := os.Open("/proc/stat")
f, err := os.Open(procStatPath)
if err != nil {
return 0, 0, err
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
if len(line) < 4 || line[:3] != "cpu" {
break // Assume all cpu* records are at the front, like glibc https://github.com/bminor/glibc/blob/5d00c201b9a2da768a79ea8d5311f257871c0b43/sysdeps/unix/sysv/linux/getsysstats.c#L108-L135
rdr := bufio.NewReaderSize(f, 1024)
for {
data, isPartial, err := rdr.ReadLine()
if err != nil {
return 0, 0, fmt.Errorf("error scanning '%s' file: %w", procStatPath, err)
}
// Assume all cpu* records are at the start of the file, like glibc:
// https://github.com/bminor/glibc/blob/5d00c201b9a2da768a79ea8d5311f257871c0b43/sysdeps/unix/sysv/linux/getsysstats.c#L108-L135
if isPartial || len(data) < 4 {
break
}
line := string(data)
if line[:3] != "cpu" {
break
}
if line[3] == ' ' {
parts := strings.Fields(line)
@@ -352,9 +365,5 @@ func getSystemCPUUsage() (cpuUsage uint64, cpuNum uint32, _ error) {
cpuNum++
}
}
if err := scanner.Err(); err != nil {
return 0, 0, fmt.Errorf("error scanning '/proc/stat' file: %w", err)
}
return cpuUsage, cpuNum, nil
}

30
daemon/stats_unix_test.go Normal file
View File

@@ -0,0 +1,30 @@
//go:build !windows
package daemon
import (
"os"
"path/filepath"
"testing"
"gotest.tools/v3/assert"
)
func TestGetSystemCPUUsageParsing(t *testing.T) {
dummyFilePath := filepath.Join("testdata", "stat")
expectedCpuUsage := uint64(65647090000000)
expectedCpuNum := uint32(128)
origStatPath := procStatPath
procStatPath = dummyFilePath
defer func() { procStatPath = origStatPath }()
_, err := os.Stat(dummyFilePath)
assert.NilError(t, err)
cpuUsage, cpuNum, err := getSystemCPUUsage()
assert.Equal(t, cpuUsage, expectedCpuUsage)
assert.Equal(t, cpuNum, expectedCpuNum)
assert.NilError(t, err)
}

136
daemon/testdata/stat vendored Normal file

File diff suppressed because one or more lines are too long