golang CPU usage
Asked Answered
P

1

6

I am aware of [1]. With a few lines of code, I just want to extract the current CPU usage from the top n processes with the most CPU usages. More or less the top 5 rows of top. Using github.com/shirou/gopsutil/process this is straight-forward:

// file: gotop.go
package main

import (
  "log"
  "time"
  "sort"

  "github.com/shirou/gopsutil/process"
)


type ProcInfo struct{
  Name  string
  Usage float64
} 

type ByUsage []ProcInfo

func (a ByUsage) Len() int      { return len(a) }
func (a ByUsage) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByUsage) Less(i, j int) bool {
  return a[i].Usage > a[j].Usage
}


func main() {

  for {
    processes, _ := process.Processes()

    var procinfos []ProcInfo
    for _, p := range processes{
      a, _ := p.CPUPercent()
      n, _ := p.Name()
      procinfos = append(procinfos, ProcInfo{n, a})
    }
    sort.Sort(ByUsage(procinfos))

    for _, p := range procinfos[:5]{
      log.Printf("   %s -> %f", p.Name, p.Usage)
    }
    time.Sleep(3 * time.Second)
  }
}

While the refresh rate in this implementation gotop is 3 seconds like top does, gotop has approx. 5-times higher demand on CPU usage to get these values like top does. Is there any trick to more efficiently read the 5 topmost consuming processes? I also tried to find the implementation of top to see how this is implemented there.

Is psutils responsible for this slown-down? I found as cpustat implemented in GO as well. But even sudo ./cpustat -i 3000 -s 1 seems to be not as efficient as top.

The main motivation is to monitor the usage of the current machine with a fairly small amount of computational effort so that it can run as a service in the background.

It seems, even htop is only reading /proc/stat.

edit as proposed in the comments here is the result when profiling

Showing top 10 nodes out of 46 (cum >= 70ms)
      flat  flat%   sum%        cum   cum%
      40ms 40.00% 40.00%       40ms 40.00%  syscall.Syscall
      10ms 10.00% 50.00%       30ms 30.00%  github.com/shirou/gopsutil/process.(*Process).fillFromStatusWithContext
      10ms 10.00% 60.00%       30ms 30.00%  io/ioutil.ReadFile
      10ms 10.00% 70.00%       10ms 10.00%  runtime.slicebytetostring
      10ms 10.00% 80.00%       20ms 20.00%  strings.FieldsFunc
      10ms 10.00% 90.00%       10ms 10.00%  syscall.Syscall6
      10ms 10.00%   100%       10ms 10.00%  unicode.IsSpace
         0     0%   100%       10ms 10.00%  bytes.(*Buffer).ReadFrom
         0     0%   100%       70ms 70.00%  github.com/shirou/gopsutil/process.(*Process).CPUPercent
         0     0%   100%       70ms 70.00%  github.com/shirou/gopsutil/process.(*Process).CPUPercentWithContext

Seems like the syscall takes forever. A tree dump is here: https://gist.github.com/PatWie/4fa528b7d7b1d0b5c1b665c056671477

This changes the question into: - Is the syscall the issue? - Are there any c-sources for the top program? I just found the implementation of htop - Is there an easy fix? I consider to write it in c and just wrap it for go.

Pero answered 9/1, 2018 at 19:20 Comment(5)
Please profile it so we can determine which part is the bottle-neck.Sooty
Just a thought: top is a venerable ancient (read: optimized over and over again, even with a complete rewrite, iirc) and written in C (no GC and alike). As always with programming languages: Go is awesome for some things, not for everything.Menadione
I added the result from profiling. Is my conclusion correct? Further, I did not found the implementation of top.Pero
It seems, that golang is indeed very inefficient or at least the library github.com/shirou/gopsutil/process for my use case. I ended up by implementing it directly in c and just calling it by cgo, see github.com/PatWie/cpuinfoPero
I googled "top source code" and found: github.com/torvalds/linux/blob/master/tools/perf/builtin-top.cVespucci
S
0

github.com/shirou/gopsutil/process uses ioutil.ReadFile which access the filesystem less efficiently than top. In particular, ReadFile:

  • calls Stat which adds an extra unnecessary Syscall.
  • uses os.Open instead of unix.Openat + os.NewFile which causes extra kernel time traversing /proc when resolving the path. os.NewFile is still a little inefficient since it always checks whether the file descriptor is non-blocking. This can be avoided by using the golang.org/x/sys/unix or syscall packages directly.

Retrieving process details under Linux is fairly inefficient in general (lots of filesystem scanning, marshalling text data). However, you can achieve similar performance to top with Go by fixing the filesystem access (as described above).

Sublimate answered 23/9, 2021 at 14:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.