mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-02 17:50:39 +00:00
140 lines
3.4 KiB
Go
140 lines
3.4 KiB
Go
package process
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
|
|
linuxproc "github.com/c9s/goprocinfo/linux"
|
|
|
|
"github.com/weaveworks/scope/common/fs"
|
|
"github.com/weaveworks/scope/probe/host"
|
|
)
|
|
|
|
type walker struct {
|
|
procRoot string
|
|
}
|
|
|
|
// NewWalker creates a new process Walker.
|
|
func NewWalker(procRoot string) Walker {
|
|
return &walker{procRoot: procRoot}
|
|
}
|
|
|
|
func readStats(path string) (ppid, threads int, jiffies, rss uint64, err error) {
|
|
var (
|
|
buf []byte
|
|
userJiffies, sysJiffies, rssPages uint64
|
|
)
|
|
buf, err = fs.ReadFile(path)
|
|
if err != nil {
|
|
return
|
|
}
|
|
splits := strings.Fields(string(buf))
|
|
if len(splits) < 24 {
|
|
err = fmt.Errorf("Invalid /proc/PID/stat")
|
|
return
|
|
}
|
|
ppid, err = strconv.Atoi(splits[3])
|
|
if err != nil {
|
|
return
|
|
}
|
|
threads, err = strconv.Atoi(splits[19])
|
|
if err != nil {
|
|
return
|
|
}
|
|
userJiffies, err = strconv.ParseUint(splits[13], 10, 64)
|
|
if err != nil {
|
|
return
|
|
}
|
|
sysJiffies, err = strconv.ParseUint(splits[14], 10, 64)
|
|
if err != nil {
|
|
return
|
|
}
|
|
jiffies = userJiffies + sysJiffies
|
|
rssPages, err = strconv.ParseUint(splits[23], 10, 64)
|
|
if err != nil {
|
|
return
|
|
}
|
|
rss = rssPages * uint64(os.Getpagesize())
|
|
return
|
|
}
|
|
|
|
// Walk walks the supplied directory (expecting it to look like /proc)
|
|
// and marshalls the files into instances of Process, which it then
|
|
// passes one-by-one to the supplied function. Walk is only made public
|
|
// so that is can be tested.
|
|
func (w *walker) Walk(f func(Process, Process)) error {
|
|
dirEntries, err := fs.ReadDirNames(w.procRoot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, filename := range dirEntries {
|
|
pid, err := strconv.Atoi(filename)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
ppid, threads, jiffies, rss, err := readStats(path.Join(w.procRoot, filename, "stat"))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
openFiles, err := fs.ReadDirNames(path.Join(w.procRoot, filename, "fd"))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
cmdline, name := "", "(unknown)"
|
|
if cmdlineBuf, err := cachedReadFile(path.Join(w.procRoot, filename, "cmdline")); err == nil {
|
|
// like proc, treat name as the first element of command line
|
|
i := bytes.IndexByte(cmdlineBuf, '\000')
|
|
if i == -1 {
|
|
i = len(cmdlineBuf)
|
|
}
|
|
name = string(cmdlineBuf[:i])
|
|
cmdlineBuf = bytes.Replace(cmdlineBuf, []byte{'\000'}, []byte{' '}, -1)
|
|
cmdline = string(cmdlineBuf)
|
|
}
|
|
|
|
f(Process{
|
|
PID: pid,
|
|
PPID: ppid,
|
|
Name: name,
|
|
Cmdline: cmdline,
|
|
Threads: threads,
|
|
Jiffies: jiffies,
|
|
RSSBytes: rss,
|
|
OpenFilesCount: len(openFiles),
|
|
}, Process{})
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
var previousStat = linuxproc.CPUStat{}
|
|
|
|
// GetDeltaTotalJiffies returns the number of jiffies that have passed since it
|
|
// was last called. In that respect, it is side-effect-ful.
|
|
func GetDeltaTotalJiffies() (uint64, float64, error) {
|
|
stat, err := linuxproc.ReadStat(host.ProcStat)
|
|
if err != nil {
|
|
return 0, 0.0, err
|
|
}
|
|
|
|
var (
|
|
currentStat = stat.CPUStatAll
|
|
prevTotal = (previousStat.Idle + previousStat.IOWait + previousStat.User +
|
|
previousStat.Nice + previousStat.System + previousStat.IRQ +
|
|
previousStat.SoftIRQ + previousStat.Steal)
|
|
currentTotal = (currentStat.Idle + currentStat.IOWait + currentStat.User +
|
|
currentStat.Nice + currentStat.System + currentStat.IRQ +
|
|
currentStat.SoftIRQ + currentStat.Steal)
|
|
)
|
|
previousStat = currentStat
|
|
return currentTotal - prevTotal, float64(len(stat.CPUStats)) * 100., nil
|
|
}
|