Skip to content

Commit

Permalink
Merge pull request #1514 from atoulme/faster_file_read
Browse files Browse the repository at this point in the history
Fix Processes() calls with many cores
  • Loading branch information
shirou authored Aug 29, 2023
2 parents 89575bd + 54c31d8 commit a09263d
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 30 deletions.
24 changes: 24 additions & 0 deletions internal/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,30 @@ func ReadLines(filename string) ([]string, error) {
return ReadLinesOffsetN(filename, 0, -1)
}

// ReadLine reads a file and returns the first occurrence of a line that is prefixed with prefix.
func ReadLine(filename string, prefix string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
defer f.Close()
r := bufio.NewReader(f)
for {
line, err := r.ReadString('\n')
if err != nil {
if err == io.EOF {
break
}
return "", err
}
if strings.HasPrefix(line, prefix) {
return line, nil
}
}

return "", nil
}

// ReadLinesOffsetN reads contents from file and splits them by new line.
// The offset tells at which line number to start.
// The count determines the number of lines to read (starting from offset):
Expand Down
69 changes: 39 additions & 30 deletions internal/common/common_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,38 @@ func BootTimeWithContext(ctx context.Context) (uint64, error) {
return 0, err
}

statFile := "stat"
useStatFile := true
if system == "lxc" && role == "guest" {
// if lxc, /proc/uptime is used.
statFile = "uptime"
useStatFile = false
} else if system == "docker" && role == "guest" {
// also docker, guest
statFile = "uptime"
useStatFile = false
}

filename := HostProcWithContext(ctx, statFile)
if useStatFile {
return readBootTimeStat(ctx)
}

filename := HostProcWithContext(ctx, "uptime")
lines, err := ReadLines(filename)
if err != nil {
return handleBootTimeFileReadErr(err)
}
if len(lines) != 1 {
return 0, fmt.Errorf("wrong uptime format")
}
f := strings.Fields(lines[0])
b, err := strconv.ParseFloat(f[0], 64)
if err != nil {
return 0, err
}
currentTime := float64(time.Now().UnixNano()) / float64(time.Second)
t := currentTime - b
return uint64(t), nil
}

func handleBootTimeFileReadErr(err error) (uint64, error) {
if os.IsPermission(err) {
var info syscall.Sysinfo_t
err := syscall.Sysinfo(&info)
Expand All @@ -84,39 +105,27 @@ func BootTimeWithContext(ctx context.Context) (uint64, error) {
t := currentTime - int64(info.Uptime)
return uint64(t), nil
}
return 0, err
}

func readBootTimeStat(ctx context.Context) (uint64, error) {
filename := HostProcWithContext(ctx, "stat")
line, err := ReadLine(filename, "btime")
if err != nil {
return 0, err
return handleBootTimeFileReadErr(err)
}

if statFile == "stat" {
for _, line := range lines {
if strings.HasPrefix(line, "btime") {
f := strings.Fields(line)
if len(f) != 2 {
return 0, fmt.Errorf("wrong btime format")
}
b, err := strconv.ParseInt(f[1], 10, 64)
if err != nil {
return 0, err
}
t := uint64(b)
return t, nil
}
}
} else if statFile == "uptime" {
if len(lines) != 1 {
return 0, fmt.Errorf("wrong uptime format")
if strings.HasPrefix(line, "btime") {
f := strings.Fields(line)
if len(f) != 2 {
return 0, fmt.Errorf("wrong btime format")
}
f := strings.Fields(lines[0])
b, err := strconv.ParseFloat(f[0], 64)
b, err := strconv.ParseInt(f[1], 10, 64)
if err != nil {
return 0, err
}
currentTime := float64(time.Now().UnixNano()) / float64(time.Second)
t := currentTime - b
return uint64(t), nil
t := uint64(b)
return t, nil
}

return 0, fmt.Errorf("could not find btime")
}

Expand Down
9 changes: 9 additions & 0 deletions process/process_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/shirou/gopsutil/v3/internal/common"
)
Expand Down Expand Up @@ -862,3 +863,11 @@ func BenchmarkProcessPpid(b *testing.B) {
p.Ppid()
}
}

func BenchmarkProcesses(b *testing.B) {
for i := 0; i < b.N; i++ {
ps, err := Processes()
require.NoError(b, err)
require.Greater(b, len(ps), 0)
}
}

0 comments on commit a09263d

Please sign in to comment.