From 56d9e0b7beff206a2dd91aee6ad7ceda7959925a Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Thu, 18 Jun 2020 10:17:23 +0200 Subject: [PATCH 1/6] cpu: support for DragonflyBSD --- cpu/cpu_dragonfly.go | 173 +++++++++++++++++++++++++++++++++++++++++++++ cpu/cpu_dragonfly_amd64.go | 9 +++ cpu/cpu_fallback.go | 2 +- 3 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 cpu/cpu_dragonfly.go create mode 100644 cpu/cpu_dragonfly_amd64.go diff --git a/cpu/cpu_dragonfly.go b/cpu/cpu_dragonfly.go new file mode 100644 index 0000000..57beffa --- /dev/null +++ b/cpu/cpu_dragonfly.go @@ -0,0 +1,173 @@ +package cpu + +import ( + "context" + "fmt" + "os/exec" + "reflect" + "regexp" + "runtime" + "strconv" + "strings" + "unsafe" + + "github.com/shirou/gopsutil/internal/common" + "golang.org/x/sys/unix" +) + +var ClocksPerSec = float64(128) +var cpuMatch = regexp.MustCompile(`^CPU:`) +var originMatch = regexp.MustCompile(`Origin\s*=\s*"(.+)"\s+Id\s*=\s*(.+)\s+Family\s*=\s*(.+)\s+Model\s*=\s*(.+)\s+Stepping\s*=\s*(.+)`) +var featuresMatch = regexp.MustCompile(`Features=.+<(.+)>`) +var featuresMatch2 = regexp.MustCompile(`Features2=[a-f\dx]+<(.+)>`) +var cpuEnd = regexp.MustCompile(`^Trying to mount root`) +var cpuCores = regexp.MustCompile(`FreeBSD/SMP: (\d*) package\(s\) x (\d*) core\(s\)`) +var cpuTimesSize int +var emptyTimes cpuTimes + +func init() { + getconf, err := exec.LookPath("getconf") + if err != nil { + return + } + out, err := invoke.Command(getconf, "CLK_TCK") + // ignore errors + if err == nil { + i, err := strconv.ParseFloat(strings.TrimSpace(string(out)), 64) + if err == nil { + ClocksPerSec = float64(i) + } + } +} + +func timeStat(name string, t *cpuTimes) *TimesStat { + return &TimesStat{ + User: float64(t.User) / ClocksPerSec, + Nice: float64(t.Nice) / ClocksPerSec, + System: float64(t.Sys) / ClocksPerSec, + Idle: float64(t.Idle) / ClocksPerSec, + Irq: float64(t.Intr) / ClocksPerSec, + CPU: name, + } +} + +func Times(percpu bool) ([]TimesStat, error) { + return TimesWithContext(context.Background(), percpu) +} + +func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { + if percpu { + buf, err := unix.SysctlRaw("kern.cp_times") + if err != nil { + return nil, err + } + + // We can't do this in init due to the conflict with cpu.init() + if cpuTimesSize == 0 { + cpuTimesSize = int(reflect.TypeOf(cpuTimes{}).Size()) + } + + ncpus := len(buf) / cpuTimesSize + ret := make([]TimesStat, 0, ncpus) + for i := 0; i < ncpus; i++ { + times := (*cpuTimes)(unsafe.Pointer(&buf[i*cpuTimesSize])) + if *times == emptyTimes { + // CPU not present + continue + } + ret = append(ret, *timeStat(fmt.Sprintf("cpu%d", len(ret)), times)) + } + return ret, nil + } + + buf, err := unix.SysctlRaw("kern.cp_time") + if err != nil { + return nil, err + } + + times := (*cpuTimes)(unsafe.Pointer(&buf[0])) + return []TimesStat{*timeStat("cpu-total", times)}, nil +} + +// Returns only one InfoStat on FreeBSD. The information regarding core +// count, however is accurate and it is assumed that all InfoStat attributes +// are the same across CPUs. +func Info() ([]InfoStat, error) { + return InfoWithContext(context.Background()) +} + +func InfoWithContext(ctx context.Context) ([]InfoStat, error) { + const dmesgBoot = "/var/run/dmesg.boot" + + c, num, err := parseDmesgBoot(dmesgBoot) + if err != nil { + return nil, err + } + + var u32 uint32 + if u32, err = unix.SysctlUint32("hw.clockrate"); err != nil { + return nil, err + } + c.Mhz = float64(u32) + + if u32, err = unix.SysctlUint32("hw.ncpu"); err != nil { + return nil, err + } + c.Cores = int32(u32) + + if c.ModelName, err = unix.Sysctl("hw.model"); err != nil { + return nil, err + } + + ret := make([]InfoStat, num) + for i := 0; i < num; i++ { + ret[i] = c + } + + return ret, nil +} + +func parseDmesgBoot(fileName string) (InfoStat, int, error) { + c := InfoStat{} + lines, _ := common.ReadLines(fileName) + cpuNum := 1 // default cpu num is 1 + for _, line := range lines { + if matches := cpuEnd.FindStringSubmatch(line); matches != nil { + break + } else if matches := originMatch.FindStringSubmatch(line); matches != nil { + c.VendorID = matches[1] + c.Family = matches[3] + c.Model = matches[4] + t, err := strconv.ParseInt(matches[5], 10, 32) + if err != nil { + return c, 0, fmt.Errorf("unable to parse FreeBSD CPU stepping information from %q: %v", line, err) + } + c.Stepping = int32(t) + } else if matches := featuresMatch.FindStringSubmatch(line); matches != nil { + for _, v := range strings.Split(matches[1], ",") { + c.Flags = append(c.Flags, strings.ToLower(v)) + } + } else if matches := featuresMatch2.FindStringSubmatch(line); matches != nil { + for _, v := range strings.Split(matches[1], ",") { + c.Flags = append(c.Flags, strings.ToLower(v)) + } + } else if matches := cpuCores.FindStringSubmatch(line); matches != nil { + t, err := strconv.ParseInt(matches[1], 10, 32) + if err != nil { + return c, 0, fmt.Errorf("unable to parse FreeBSD CPU Nums from %q: %v", line, err) + } + cpuNum = int(t) + t2, err := strconv.ParseInt(matches[2], 10, 32) + if err != nil { + return c, 0, fmt.Errorf("unable to parse FreeBSD CPU cores from %q: %v", line, err) + } + c.Cores = int32(t2) + } + } + + return c, cpuNum, nil +} + +func CountsWithContext(ctx context.Context, logical bool) (int, error) { + return runtime.NumCPU(), nil +} diff --git a/cpu/cpu_dragonfly_amd64.go b/cpu/cpu_dragonfly_amd64.go new file mode 100644 index 0000000..57e1452 --- /dev/null +++ b/cpu/cpu_dragonfly_amd64.go @@ -0,0 +1,9 @@ +package cpu + +type cpuTimes struct { + User uint64 + Nice uint64 + Sys uint64 + Intr uint64 + Idle uint64 +} diff --git a/cpu/cpu_fallback.go b/cpu/cpu_fallback.go index fbb0608..5551c49 100644 --- a/cpu/cpu_fallback.go +++ b/cpu/cpu_fallback.go @@ -1,4 +1,4 @@ -// +build !darwin,!linux,!freebsd,!openbsd,!solaris,!windows +// +build !darwin,!linux,!freebsd,!openbsd,!solaris,!windows,!dragonfly package cpu From 9390667f0fec5a22e157fa13c72835ed4d66c9ec Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Sun, 21 Jun 2020 12:21:18 +0200 Subject: [PATCH 2/6] Update cpu/cpu_dragonfly.go Co-authored-by: shirou --- cpu/cpu_dragonfly.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpu/cpu_dragonfly.go b/cpu/cpu_dragonfly.go index 57beffa..a9862c3 100644 --- a/cpu/cpu_dragonfly.go +++ b/cpu/cpu_dragonfly.go @@ -89,7 +89,7 @@ func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { return []TimesStat{*timeStat("cpu-total", times)}, nil } -// Returns only one InfoStat on FreeBSD. The information regarding core +// Returns only one InfoStat on DragonflyBSD. The information regarding core // count, however is accurate and it is assumed that all InfoStat attributes // are the same across CPUs. func Info() ([]InfoStat, error) { From 771601b2920ed2f148609473a8ebe7e800572a5e Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Mon, 22 Jun 2020 11:53:27 +0200 Subject: [PATCH 3/6] use sysctl instead of parsing dmesg output --- cpu/cpu_dragonfly.go | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/cpu/cpu_dragonfly.go b/cpu/cpu_dragonfly.go index a9862c3..bb33ab8 100644 --- a/cpu/cpu_dragonfly.go +++ b/cpu/cpu_dragonfly.go @@ -21,7 +21,6 @@ var originMatch = regexp.MustCompile(`Origin\s*=\s*"(.+)"\s+Id\s*=\s*(.+)\s+Fami var featuresMatch = regexp.MustCompile(`Features=.+<(.+)>`) var featuresMatch2 = regexp.MustCompile(`Features2=[a-f\dx]+<(.+)>`) var cpuEnd = regexp.MustCompile(`^Trying to mount root`) -var cpuCores = regexp.MustCompile(`FreeBSD/SMP: (\d*) package\(s\) x (\d*) core\(s\)`) var cpuTimesSize int var emptyTimes cpuTimes @@ -99,7 +98,7 @@ func Info() ([]InfoStat, error) { func InfoWithContext(ctx context.Context) ([]InfoStat, error) { const dmesgBoot = "/var/run/dmesg.boot" - c, num, err := parseDmesgBoot(dmesgBoot) + c, err := parseDmesgBoot(dmesgBoot) if err != nil { return nil, err } @@ -110,10 +109,13 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) { } c.Mhz = float64(u32) - if u32, err = unix.SysctlUint32("hw.ncpu"); err != nil { + var num int + var buf []byte + if buf, err = unix.SysctlRaw("hw.hw.cpu_topology.tree"); err != nil { return nil, err } - c.Cores = int32(u32) + num = strings.Count(string(buf), "CHIP") + c.Cores = int32(strings.Count(string(buf), "CORE") / num) if c.ModelName, err = unix.Sysctl("hw.model"); err != nil { return nil, err @@ -127,10 +129,9 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) { return ret, nil } -func parseDmesgBoot(fileName string) (InfoStat, int, error) { +func parseDmesgBoot(fileName string) (InfoStat, error) { c := InfoStat{} lines, _ := common.ReadLines(fileName) - cpuNum := 1 // default cpu num is 1 for _, line := range lines { if matches := cpuEnd.FindStringSubmatch(line); matches != nil { break @@ -151,21 +152,10 @@ func parseDmesgBoot(fileName string) (InfoStat, int, error) { for _, v := range strings.Split(matches[1], ",") { c.Flags = append(c.Flags, strings.ToLower(v)) } - } else if matches := cpuCores.FindStringSubmatch(line); matches != nil { - t, err := strconv.ParseInt(matches[1], 10, 32) - if err != nil { - return c, 0, fmt.Errorf("unable to parse FreeBSD CPU Nums from %q: %v", line, err) - } - cpuNum = int(t) - t2, err := strconv.ParseInt(matches[2], 10, 32) - if err != nil { - return c, 0, fmt.Errorf("unable to parse FreeBSD CPU cores from %q: %v", line, err) - } - c.Cores = int32(t2) } } - return c, cpuNum, nil + return c, nil } func CountsWithContext(ctx context.Context, logical bool) (int, error) { From 89684570486df59d8da65b4c13a008cffe6c7c81 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Mon, 22 Jun 2020 11:56:02 +0200 Subject: [PATCH 4/6] fix typo --- cpu/cpu_dragonfly.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpu/cpu_dragonfly.go b/cpu/cpu_dragonfly.go index bb33ab8..d87f15d 100644 --- a/cpu/cpu_dragonfly.go +++ b/cpu/cpu_dragonfly.go @@ -141,7 +141,7 @@ func parseDmesgBoot(fileName string) (InfoStat, error) { c.Model = matches[4] t, err := strconv.ParseInt(matches[5], 10, 32) if err != nil { - return c, 0, fmt.Errorf("unable to parse FreeBSD CPU stepping information from %q: %v", line, err) + return c, fmt.Errorf("unable to parse FreeBSD CPU stepping information from %q: %v", line, err) } c.Stepping = int32(t) } else if matches := featuresMatch.FindStringSubmatch(line); matches != nil { From 877e0a6603605665eb6d588609dbd9e22f6401d4 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Mon, 22 Jun 2020 11:59:22 +0200 Subject: [PATCH 5/6] more typo fixes --- cpu/cpu_dragonfly.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpu/cpu_dragonfly.go b/cpu/cpu_dragonfly.go index d87f15d..d5c6d46 100644 --- a/cpu/cpu_dragonfly.go +++ b/cpu/cpu_dragonfly.go @@ -111,7 +111,7 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) { var num int var buf []byte - if buf, err = unix.SysctlRaw("hw.hw.cpu_topology.tree"); err != nil { + if buf, err = unix.SysctlRaw("hw.cpu_topology.tree"); err != nil { return nil, err } num = strings.Count(string(buf), "CHIP") From baec973ff662a9a839888621107c37cc95a69abb Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Mon, 22 Jun 2020 13:03:18 +0200 Subject: [PATCH 6/6] Fix stepping extraction --- cpu/cpu_dragonfly.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/cpu/cpu_dragonfly.go b/cpu/cpu_dragonfly.go index d5c6d46..eed5bea 100644 --- a/cpu/cpu_dragonfly.go +++ b/cpu/cpu_dragonfly.go @@ -17,7 +17,7 @@ import ( var ClocksPerSec = float64(128) var cpuMatch = regexp.MustCompile(`^CPU:`) -var originMatch = regexp.MustCompile(`Origin\s*=\s*"(.+)"\s+Id\s*=\s*(.+)\s+Family\s*=\s*(.+)\s+Model\s*=\s*(.+)\s+Stepping\s*=\s*(.+)`) +var originMatch = regexp.MustCompile(`Origin\s*=\s*"(.+)"\s+Id\s*=\s*(.+)\s+Stepping\s*=\s*(.+)`) var featuresMatch = regexp.MustCompile(`Features=.+<(.+)>`) var featuresMatch2 = regexp.MustCompile(`Features2=[a-f\dx]+<(.+)>`) var cpuEnd = regexp.MustCompile(`^Trying to mount root`) @@ -110,11 +110,11 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) { c.Mhz = float64(u32) var num int - var buf []byte - if buf, err = unix.SysctlRaw("hw.cpu_topology.tree"); err != nil { + var buf string + if buf, err = unix.Sysctl("hw.cpu_topology.tree"); err != nil { return nil, err } - num = strings.Count(string(buf), "CHIP") + num = strings.Count(buf, "CHIP") c.Cores = int32(strings.Count(string(buf), "CORE") / num) if c.ModelName, err = unix.Sysctl("hw.model"); err != nil { @@ -137,11 +137,9 @@ func parseDmesgBoot(fileName string) (InfoStat, error) { break } else if matches := originMatch.FindStringSubmatch(line); matches != nil { c.VendorID = matches[1] - c.Family = matches[3] - c.Model = matches[4] - t, err := strconv.ParseInt(matches[5], 10, 32) + t, err := strconv.ParseInt(matches[2], 10, 32) if err != nil { - return c, fmt.Errorf("unable to parse FreeBSD CPU stepping information from %q: %v", line, err) + return c, fmt.Errorf("unable to parse DragonflyBSD CPU stepping information from %q: %v", line, err) } c.Stepping = int32(t) } else if matches := featuresMatch.FindStringSubmatch(line); matches != nil {