Skip to content

Commit

Permalink
libct/system.Stat: improve and speed up
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
kolyshkin committed Dec 1, 2020
1 parent 034292f commit ef6fd85
Showing 1 changed file with 26 additions and 12 deletions.
38 changes: 26 additions & 12 deletions libcontainer/system/proc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}

0 comments on commit ef6fd85

Please sign in to comment.