Reputation: 4450
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.
Upvotes: 8
Views: 8946
Reputation: 1354
github.com/shirou/gopsutil/process
uses ioutil.ReadFile
which access the filesystem less efficiently than top. In particular, ReadFile
:
Stat
which adds an extra unnecessary Syscall.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).
Upvotes: 0