2017-03-12 04:54:02 +08:00
|
|
|
package cpu
|
|
|
|
|
|
|
|
import (
|
2017-12-31 14:25:49 +08:00
|
|
|
"context"
|
2017-03-12 04:54:02 +08:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"regexp"
|
2019-03-03 21:38:33 +08:00
|
|
|
"runtime"
|
2017-03-12 04:54:02 +08:00
|
|
|
"sort"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
2021-02-16 22:27:05 +08:00
|
|
|
|
|
|
|
"github.com/tklauser/go-sysconf"
|
2017-03-12 04:54:02 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
var ClocksPerSec = float64(128)
|
|
|
|
|
|
|
|
func init() {
|
2021-02-16 22:27:05 +08:00
|
|
|
clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
|
2017-03-12 04:54:02 +08:00
|
|
|
// ignore errors
|
|
|
|
if err == nil {
|
2021-02-16 22:27:05 +08:00
|
|
|
ClocksPerSec = float64(clkTck)
|
2017-03-12 04:54:02 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-23 05:54:41 +08:00
|
|
|
// sum all values in a float64 map with float64 keys
|
2019-06-02 04:05:46 +08:00
|
|
|
func msum(x map[float64]float64) float64 {
|
|
|
|
total := 0.0
|
|
|
|
for _, y := range x {
|
|
|
|
total += y
|
|
|
|
}
|
|
|
|
return total
|
|
|
|
}
|
|
|
|
|
2017-03-12 04:54:02 +08:00
|
|
|
func Times(percpu bool) ([]TimesStat, error) {
|
2017-12-31 14:25:49 +08:00
|
|
|
return TimesWithContext(context.Background(), percpu)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
|
2022-03-05 00:18:03 +08:00
|
|
|
kstatSysOut, err := invoke.CommandWithContext(ctx, "kstat", "-p", "cpu_stat:*:*:/^idle$|^user$|^kernel$|^iowait$|^swap$/")
|
2019-06-02 04:05:46 +08:00
|
|
|
if err != nil {
|
2022-03-05 00:18:03 +08:00
|
|
|
return nil, fmt.Errorf("cannot execute kstat: %s", err)
|
2019-06-02 04:05:46 +08:00
|
|
|
}
|
|
|
|
cpu := make(map[float64]float64)
|
|
|
|
idle := make(map[float64]float64)
|
|
|
|
user := make(map[float64]float64)
|
|
|
|
kern := make(map[float64]float64)
|
|
|
|
iowt := make(map[float64]float64)
|
2021-12-23 05:54:41 +08:00
|
|
|
// swap := make(map[float64]float64)
|
2019-06-02 22:58:35 +08:00
|
|
|
re := regexp.MustCompile(`[:\s]+`)
|
2019-06-07 20:39:57 +08:00
|
|
|
for _, line := range strings.Split(string(kstatSysOut), "\n") {
|
2019-06-02 22:58:35 +08:00
|
|
|
fields := re.Split(line, -1)
|
2019-06-02 04:05:46 +08:00
|
|
|
if fields[0] != "cpu_stat" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
cpuNumber, err := strconv.ParseFloat(fields[1], 64)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("cannot parse cpu number: %s", err)
|
|
|
|
}
|
|
|
|
cpu[cpuNumber] = cpuNumber
|
|
|
|
switch fields[3] {
|
|
|
|
case "idle":
|
|
|
|
idle[cpuNumber], err = strconv.ParseFloat(fields[4], 64)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("cannot parse idle: %s", err)
|
|
|
|
}
|
|
|
|
case "user":
|
|
|
|
user[cpuNumber], err = strconv.ParseFloat(fields[4], 64)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("cannot parse user: %s", err)
|
|
|
|
}
|
|
|
|
case "kernel":
|
|
|
|
kern[cpuNumber], err = strconv.ParseFloat(fields[4], 64)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("cannot parse kernel: %s", err)
|
|
|
|
}
|
|
|
|
case "iowait":
|
|
|
|
iowt[cpuNumber], err = strconv.ParseFloat(fields[4], 64)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("cannot parse iowait: %s", err)
|
|
|
|
}
|
|
|
|
//not sure how this translates, don't report, add to kernel, something else?
|
|
|
|
/*case "swap":
|
|
|
|
swap[cpuNumber], err = strconv.ParseFloat(fields[4], 64)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("cannot parse swap: %s", err)
|
|
|
|
} */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ret := make([]TimesStat, 0, len(cpu))
|
|
|
|
if percpu {
|
|
|
|
for _, c := range cpu {
|
|
|
|
ct := &TimesStat{
|
|
|
|
CPU: fmt.Sprintf("cpu%d", int(cpu[c])),
|
|
|
|
Idle: idle[c] / ClocksPerSec,
|
|
|
|
User: user[c] / ClocksPerSec,
|
|
|
|
System: kern[c] / ClocksPerSec,
|
|
|
|
Iowait: iowt[c] / ClocksPerSec,
|
|
|
|
}
|
|
|
|
ret = append(ret, *ct)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ct := &TimesStat{
|
|
|
|
CPU: "cpu-total",
|
|
|
|
Idle: msum(idle) / ClocksPerSec,
|
|
|
|
User: msum(user) / ClocksPerSec,
|
|
|
|
System: msum(kern) / ClocksPerSec,
|
|
|
|
Iowait: msum(iowt) / ClocksPerSec,
|
|
|
|
}
|
|
|
|
ret = append(ret, *ct)
|
|
|
|
}
|
|
|
|
return ret, nil
|
2017-03-12 04:54:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func Info() ([]InfoStat, error) {
|
2017-12-31 14:25:49 +08:00
|
|
|
return InfoWithContext(context.Background())
|
|
|
|
}
|
|
|
|
|
|
|
|
func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
|
2022-03-05 00:18:03 +08:00
|
|
|
psrInfoOut, err := invoke.CommandWithContext(ctx, "psrinfo", "-p", "-v")
|
2017-03-12 04:54:02 +08:00
|
|
|
if err != nil {
|
2017-03-15 21:43:20 +08:00
|
|
|
return nil, fmt.Errorf("cannot execute psrinfo: %s", err)
|
2017-03-12 04:54:02 +08:00
|
|
|
}
|
|
|
|
|
2022-03-05 00:18:03 +08:00
|
|
|
procs, err := parseProcessorInfo(string(psrInfoOut))
|
2017-03-12 04:54:02 +08:00
|
|
|
if err != nil {
|
2022-03-05 00:18:03 +08:00
|
|
|
return nil, fmt.Errorf("error parsing psrinfo output: %s", err)
|
2017-03-12 04:54:02 +08:00
|
|
|
}
|
|
|
|
|
2022-03-05 00:18:03 +08:00
|
|
|
isaInfoOut, err := invoke.CommandWithContext(ctx, "isainfo", "-b", "-v")
|
2017-03-12 04:54:02 +08:00
|
|
|
if err != nil {
|
2022-03-05 00:18:03 +08:00
|
|
|
return nil, fmt.Errorf("cannot execute isainfo: %s", err)
|
2017-03-12 04:54:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
flags, err := parseISAInfo(string(isaInfoOut))
|
|
|
|
if err != nil {
|
2017-03-15 21:43:20 +08:00
|
|
|
return nil, fmt.Errorf("error parsing isainfo output: %s", err)
|
2017-03-12 04:54:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
result := make([]InfoStat, 0, len(flags))
|
|
|
|
for _, proc := range procs {
|
|
|
|
procWithFlags := proc
|
|
|
|
procWithFlags.Flags = flags
|
|
|
|
result = append(result, procWithFlags)
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var flagsMatch = regexp.MustCompile(`[\w\.]+`)
|
|
|
|
|
|
|
|
func parseISAInfo(cmdOutput string) ([]string, error) {
|
|
|
|
words := flagsMatch.FindAllString(cmdOutput, -1)
|
|
|
|
|
|
|
|
// Sanity check the output
|
|
|
|
if len(words) < 4 || words[1] != "bit" || words[3] != "applications" {
|
2017-03-15 21:43:20 +08:00
|
|
|
return nil, errors.New("attempted to parse invalid isainfo output")
|
2017-03-12 04:54:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
flags := make([]string, len(words)-4)
|
|
|
|
for i, val := range words[4:] {
|
|
|
|
flags[i] = val
|
|
|
|
}
|
|
|
|
sort.Strings(flags)
|
|
|
|
|
|
|
|
return flags, nil
|
|
|
|
}
|
|
|
|
|
2021-05-08 21:41:03 +08:00
|
|
|
var psrInfoMatch = regexp.MustCompile(`The physical processor has (?:([\d]+) virtual processors? \(([\d-]+)\)|([\d]+) cores and ([\d]+) virtual processors[^\n]+)\n(?:\s+ The core has.+\n)*\s+.+ \((\w+) ([\S]+) family (.+) model (.+) step (.+) clock (.+) MHz\)\n[\s]*(.*)`)
|
2017-03-12 04:54:02 +08:00
|
|
|
|
|
|
|
const (
|
2017-03-15 21:43:20 +08:00
|
|
|
psrNumCoresOffset = 1
|
2017-03-12 04:54:02 +08:00
|
|
|
psrNumCoresHTOffset = 3
|
2017-03-15 21:43:20 +08:00
|
|
|
psrNumHTOffset = 4
|
|
|
|
psrVendorIDOffset = 5
|
|
|
|
psrFamilyOffset = 7
|
|
|
|
psrModelOffset = 8
|
|
|
|
psrStepOffset = 9
|
|
|
|
psrClockOffset = 10
|
|
|
|
psrModelNameOffset = 11
|
2017-03-12 04:54:02 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
func parseProcessorInfo(cmdOutput string) ([]InfoStat, error) {
|
|
|
|
matches := psrInfoMatch.FindAllStringSubmatch(cmdOutput, -1)
|
|
|
|
|
|
|
|
var infoStatCount int32
|
|
|
|
result := make([]InfoStat, 0, len(matches))
|
|
|
|
for physicalIndex, physicalCPU := range matches {
|
|
|
|
var step int32
|
|
|
|
var clock float64
|
|
|
|
|
|
|
|
if physicalCPU[psrStepOffset] != "" {
|
|
|
|
stepParsed, err := strconv.ParseInt(physicalCPU[psrStepOffset], 10, 32)
|
|
|
|
if err != nil {
|
2017-03-15 21:43:20 +08:00
|
|
|
return nil, fmt.Errorf("cannot parse value %q for step as 32-bit integer: %s", physicalCPU[9], err)
|
2017-03-12 04:54:02 +08:00
|
|
|
}
|
|
|
|
step = int32(stepParsed)
|
|
|
|
}
|
|
|
|
|
|
|
|
if physicalCPU[psrClockOffset] != "" {
|
|
|
|
clockParsed, err := strconv.ParseInt(physicalCPU[psrClockOffset], 10, 64)
|
|
|
|
if err != nil {
|
2017-03-15 21:43:20 +08:00
|
|
|
return nil, fmt.Errorf("cannot parse value %q for clock as 32-bit integer: %s", physicalCPU[10], err)
|
2017-03-12 04:54:02 +08:00
|
|
|
}
|
|
|
|
clock = float64(clockParsed)
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
|
|
|
var numCores int64
|
|
|
|
var numHT int64
|
|
|
|
switch {
|
|
|
|
case physicalCPU[psrNumCoresOffset] != "":
|
|
|
|
numCores, err = strconv.ParseInt(physicalCPU[psrNumCoresOffset], 10, 32)
|
|
|
|
if err != nil {
|
2017-03-15 21:43:20 +08:00
|
|
|
return nil, fmt.Errorf("cannot parse value %q for core count as 32-bit integer: %s", physicalCPU[1], err)
|
2017-03-12 04:54:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < int(numCores); i++ {
|
|
|
|
result = append(result, InfoStat{
|
|
|
|
CPU: infoStatCount,
|
|
|
|
PhysicalID: strconv.Itoa(physicalIndex),
|
|
|
|
CoreID: strconv.Itoa(i),
|
|
|
|
Cores: 1,
|
|
|
|
VendorID: physicalCPU[psrVendorIDOffset],
|
|
|
|
ModelName: physicalCPU[psrModelNameOffset],
|
|
|
|
Family: physicalCPU[psrFamilyOffset],
|
|
|
|
Model: physicalCPU[psrModelOffset],
|
|
|
|
Stepping: step,
|
|
|
|
Mhz: clock,
|
|
|
|
})
|
|
|
|
infoStatCount++
|
|
|
|
}
|
|
|
|
case physicalCPU[psrNumCoresHTOffset] != "":
|
|
|
|
numCores, err = strconv.ParseInt(physicalCPU[psrNumCoresHTOffset], 10, 32)
|
|
|
|
if err != nil {
|
2017-03-15 21:43:20 +08:00
|
|
|
return nil, fmt.Errorf("cannot parse value %q for core count as 32-bit integer: %s", physicalCPU[3], err)
|
2017-03-12 04:54:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
numHT, err = strconv.ParseInt(physicalCPU[psrNumHTOffset], 10, 32)
|
|
|
|
if err != nil {
|
2017-03-15 21:43:20 +08:00
|
|
|
return nil, fmt.Errorf("cannot parse value %q for hyperthread count as 32-bit integer: %s", physicalCPU[4], err)
|
2017-03-12 04:54:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < int(numCores); i++ {
|
|
|
|
result = append(result, InfoStat{
|
|
|
|
CPU: infoStatCount,
|
|
|
|
PhysicalID: strconv.Itoa(physicalIndex),
|
|
|
|
CoreID: strconv.Itoa(i),
|
|
|
|
Cores: int32(numHT) / int32(numCores),
|
|
|
|
VendorID: physicalCPU[psrVendorIDOffset],
|
|
|
|
ModelName: physicalCPU[psrModelNameOffset],
|
|
|
|
Family: physicalCPU[psrFamilyOffset],
|
|
|
|
Model: physicalCPU[psrModelOffset],
|
|
|
|
Stepping: step,
|
|
|
|
Mhz: clock,
|
|
|
|
})
|
|
|
|
infoStatCount++
|
|
|
|
}
|
|
|
|
default:
|
2017-03-15 21:43:20 +08:00
|
|
|
return nil, errors.New("values for cores with and without hyperthreading are both set")
|
2017-03-12 04:54:02 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return result, nil
|
|
|
|
}
|
2019-03-03 21:38:33 +08:00
|
|
|
|
|
|
|
func CountsWithContext(ctx context.Context, logical bool) (int, error) {
|
|
|
|
return runtime.NumCPU(), nil
|
|
|
|
}
|