mirror of
https://github.com/moby/moby.git
synced 2026-01-11 02:31:44 +00:00
Improve CPU usage parsing and error reporting
This fix address issues where the scanner was unable to properly parse longer outputs from /proc/stat. This could happen on an ARM machine with large amount of CPU cores (and interrupts). By switching to reader we have more control over data parsing and dump unnecessary data Signed-off-by: Patrik Leifert <patrikleifert@hotmail.com>
This commit is contained in:
@@ -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
30
daemon/stats_unix_test.go
Normal 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
136
daemon/testdata/stat
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user