From ef6fd85c534fb9bc8f99530b7c098cc709714b19 Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Mon, 30 Nov 2020 12:26:20 -0800 Subject: [PATCH] libct/system.Stat: improve and speed up Rewrite parseStat() to make it faster: - do not use fmt.Scanf as it is very slow; - avoid splitting data into 20+ fields, of which we only need 2; - use LastIndexByte instead of LastIndex. This results in about 8x speedup. Before: > BenchmarkParseStat-4 129914 7950 ns/op After: > BenchmarkParseStat-4 1207336 985 ns/op While at it, do not ignore any possible errors, and properly wrap those. Signed-off-by: Kir Kolyshkin --- libcontainer/system/proc.go | 38 +++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/libcontainer/system/proc.go b/libcontainer/system/proc.go index b73cf70b434..d64e2ca3d8a 100644 --- a/libcontainer/system/proc.go +++ b/libcontainer/system/proc.go @@ -3,9 +3,12 @@ package system import ( "fmt" "io/ioutil" + "math/bits" "path/filepath" "strconv" "strings" + + "github.com/pkg/errors" ) // State is the status of a process. @@ -75,29 +78,40 @@ func parseStat(data string) (stat Stat_t, err error) { // From proc(5), field 2 could contain space and is inside `(` and `)`. // The following is an example: // 89653 (gunicorn: maste) S 89630 89653 89653 0 -1 4194560 29689 28896 0 3 146 32 76 19 20 0 1 0 2971844 52965376 3920 18446744073709551615 1 1 0 0 0 0 0 16781312 137447943 0 0 0 17 1 0 0 0 0 0 0 0 0 0 0 0 0 0 - i := strings.LastIndex(data, ")") + i := strings.LastIndexByte(data, ')') if i <= 2 || i >= len(data)-1 { return stat, fmt.Errorf("invalid stat data: %q", data) } - parts := strings.SplitN(data[:i], "(", 2) + parts := strings.SplitN(data[:i], " (", 2) if len(parts) != 2 { return stat, fmt.Errorf("invalid stat data: %q", data) } stat.Name = parts[1] - _, err = fmt.Sscanf(parts[0], "%d", &stat.PID) + pid, err := strconv.ParseUint(parts[0], 10, bits.UintSize) if err != nil { - return stat, err + return stat, errors.Wrapf(err, "invalid stat data: %q", data) + } + stat.PID = uint(pid) + + // Remove the first two fields and a space after. + data = data[i+2:] + stat.State = State(data[0]) + + // StartTime is field 22, data is at field 3 now, so we need to skip 19 spaces. + skipSpaces := 22 - 3 + for i = 0; skipSpaces > 0; i++ { + if data[i] == ' ' { + skipSpaces-- + } + } + data = data[i:] + data = data[0:strings.IndexByte(data, ' ')] + stat.StartTime, err = strconv.ParseUint(data, 10, 64) + if err != nil { + return stat, errors.Wrapf(err, "invalid stat data: %q", data) } - // parts indexes should be offset by 3 from the field number given - // proc(5), because parts is zero-indexed and we've removed fields - // one (PID) and two (Name) in the paren-split. - parts = strings.Split(data[i+2:], " ") - var state int - fmt.Sscanf(parts[3-3], "%c", &state) - stat.State = State(state) - fmt.Sscanf(parts[22-3], "%d", &stat.StartTime) return stat, nil }