From 6101585fd2af36c2dd147eedec054057469d1033 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 25 Jul 2024 09:30:11 +0200 Subject: [PATCH] Extract linux specifc code from psutils for reading CPU times --- go.mod | 2 +- psutil/process.go | 20 --------- psutil/process_linux.go | 97 +++++++++++++++++++++++++++++++++++++++++ psutil/process_other.go | 24 ++++++++++ 4 files changed, 122 insertions(+), 21 deletions(-) create mode 100644 psutil/process_linux.go create mode 100644 psutil/process_other.go diff --git a/go.mod b/go.mod index d41163e9..a19cd943 100644 --- a/go.mod +++ b/go.mod @@ -40,6 +40,7 @@ require ( github.com/stretchr/testify v1.9.0 github.com/swaggo/echo-swagger v1.4.1 github.com/swaggo/swag v1.16.3 + github.com/tklauser/go-sysconf v0.3.14 github.com/vektah/gqlparser/v2 v2.5.16 github.com/xeipuuv/gojsonschema v1.2.0 go.etcd.io/bbolt v1.3.10 @@ -104,7 +105,6 @@ require ( github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sosodev/duration v1.3.1 // indirect github.com/swaggo/files/v2 v2.0.1 // indirect - github.com/tklauser/go-sysconf v0.3.14 // indirect github.com/tklauser/numcpus v0.8.0 // indirect github.com/urfave/cli/v2 v2.27.2 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect diff --git a/psutil/process.go b/psutil/process.go index dc190ef3..0789f553 100644 --- a/psutil/process.go +++ b/psutil/process.go @@ -142,26 +142,6 @@ func (p *process) Resume() error { return p.proc.Resume() } -func (p *process) cpuTimes() (*cpuTimesStat, error) { - times, err := p.proc.Times() - if err != nil { - return nil, err - } - - s := &cpuTimesStat{ - total: cpuTotal(times), - system: times.System, - user: times.User, - } - - s.other = s.total - s.system - s.user - if s.other < 0.0001 { - s.other = 0 - } - - return s, nil -} - func (p *process) CPUPercent() (*CPUInfoStat, error) { var diff float64 diff --git a/psutil/process_linux.go b/psutil/process_linux.go new file mode 100644 index 00000000..e5b8894a --- /dev/null +++ b/psutil/process_linux.go @@ -0,0 +1,97 @@ +//go:build linux +// +build linux + +package psutil + +import ( + "bytes" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/tklauser/go-sysconf" +) + +// Extracted from "github.com/shirou/gopsutil/v3/process/process_linux.go" +// We only need the CPU times. p.proc.Times() calls a function that is +// doing more than we actually need. + +var clockTicks = 100 // default value + +func init() { + clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK) + // ignore errors + if err == nil { + clockTicks = int(clkTck) + } +} + +func (p *process) cpuTimes() (*cpuTimesStat, error) { + value := os.Getenv("HOST_PROC") + if value == "" { + value = "/proc" + } + + path := filepath.Join(value, strconv.FormatInt(int64(p.pid), 10), "stat") + + contents, err := os.ReadFile(path) + if err != nil { + return nil, err + } + // Indexing from one, as described in `man proc` about the file /proc/[pid]/stat + fields := splitProcStat(contents) + + utime, err := strconv.ParseFloat(fields[14], 64) + if err != nil { + return nil, err + } + + stime, err := strconv.ParseFloat(fields[15], 64) + if err != nil { + return nil, err + } + + // There is no such thing as iotime in stat file. As an approximation, we + // will use delayacct_blkio_ticks (aggregated block I/O delays, as per Linux + // docs). Note: I am assuming at least Linux 2.6.18 + var iotime float64 + if len(fields) > 42 { + iotime, err = strconv.ParseFloat(fields[42], 64) + if err != nil { + iotime = 0 // Ancient linux version, most likely + } + } else { + iotime = 0 // e.g. SmartOS containers + } + + userTime := utime / float64(clockTicks) + systemTime := stime / float64(clockTicks) + iowaitTime := iotime / float64(clockTicks) + + s := &cpuTimesStat{ + total: userTime + systemTime + iowaitTime, + system: systemTime, + user: userTime, + other: iowaitTime, + } + + if s.other < 0.0001 { + s.other = 0 + } + + return s, nil +} + +func splitProcStat(content []byte) []string { + nameStart := bytes.IndexByte(content, '(') + nameEnd := bytes.LastIndexByte(content, ')') + restFields := strings.Fields(string(content[nameEnd+2:])) // +2 skip ') ' + name := content[nameStart+1 : nameEnd] + pid := strings.TrimSpace(string(content[:nameStart])) + fields := make([]string, 3, len(restFields)+3) + fields[1] = string(pid) + fields[2] = string(name) + fields = append(fields, restFields...) + return fields +} diff --git a/psutil/process_other.go b/psutil/process_other.go new file mode 100644 index 00000000..f5464906 --- /dev/null +++ b/psutil/process_other.go @@ -0,0 +1,24 @@ +//go:build !linux +// +build !linux + +package psutil + +func (p *process) cpuTimes() (*cpuTimesStat, error) { + times, err := p.proc.Times() + if err != nil { + return nil, err + } + + s := &cpuTimesStat{ + total: cpuTotal(times), + system: times.System, + user: times.User, + } + + s.other = s.total - s.system - s.user + if s.other < 0.0001 { + s.other = 0 + } + + return s, nil +}