package cpu
import (
"context"
"errors"
"fmt"
"os/exec"
"path/filepath"
"strconv"
"strings"
"github.com/gofiber/fiber/v2/internal/gopsutil/common"
)
var ClocksPerSec = float64 (100 )
func init() {
getconf , err := exec .LookPath ("getconf" )
if err != nil {
return
}
out , err := invoke .CommandWithContext (context .Background (), getconf , "CLK_TCK" )
if err == nil {
i , err := strconv .ParseFloat (strings .TrimSpace (string (out )), 64 )
if err == nil {
ClocksPerSec = i
}
}
}
func Times (percpu bool ) ([]TimesStat , error ) {
return TimesWithContext (context .Background (), percpu )
}
func TimesWithContext (ctx context .Context , percpu bool ) ([]TimesStat , error ) {
filename := common .HostProc ("stat" )
lines := []string {}
if percpu {
statlines , err := common .ReadLines (filename )
if err != nil || len (statlines ) < 2 {
return []TimesStat {}, nil
}
for _ , line := range statlines [1 :] {
if !strings .HasPrefix (line , "cpu" ) {
break
}
lines = append (lines , line )
}
} else {
lines , _ = common .ReadLinesOffsetN (filename , 0 , 1 )
}
ret := make ([]TimesStat , 0 , len (lines ))
for _ , line := range lines {
ct , err := parseStatLine (line )
if err != nil {
continue
}
ret = append (ret , *ct )
}
return ret , nil
}
func sysCPUPath(cpu int32 , relPath string ) string {
return common .HostSys (fmt .Sprintf ("devices/system/cpu/cpu%d" , cpu ), relPath )
}
func finishCPUInfo(c *InfoStat ) error {
var lines []string
var err error
var value float64
if len (c .CoreID ) == 0 {
lines , err = common .ReadLines (sysCPUPath (c .CPU , "topology/core_id" ))
if err == nil {
c .CoreID = lines [0 ]
}
}
lines , err = common .ReadLines (sysCPUPath (c .CPU , "cpufreq/cpuinfo_max_freq" ))
if err != nil || len (lines ) == 0 {
return nil
}
value , err = strconv .ParseFloat (lines [0 ], 64 )
if err != nil {
return nil
}
c .Mhz = value / 1000.0
if c .Mhz > 9999 {
c .Mhz = c .Mhz / 1000.0
}
return nil
}
func Info () ([]InfoStat , error ) {
return InfoWithContext (context .Background ())
}
func InfoWithContext (ctx context .Context ) ([]InfoStat , error ) {
filename := common .HostProc ("cpuinfo" )
lines , _ := common .ReadLines (filename )
var ret []InfoStat
var processorName string
c := InfoStat {CPU : -1 , Cores : 1 }
for _ , line := range lines {
fields := strings .Split (line , ":" )
if len (fields ) < 2 {
continue
}
key := strings .TrimSpace (fields [0 ])
value := strings .TrimSpace (fields [1 ])
switch key {
case "Processor" :
processorName = value
case "processor" :
if c .CPU >= 0 {
err := finishCPUInfo (&c )
if err != nil {
return ret , err
}
ret = append (ret , c )
}
c = InfoStat {Cores : 1 , ModelName : processorName }
t , err := strconv .ParseInt (value , 10 , 64 )
if err != nil {
return ret , err
}
c .CPU = int32 (t )
case "vendorId" , "vendor_id" :
c .VendorID = value
case "cpu family" :
c .Family = value
case "model" :
c .Model = value
case "model name" , "cpu" :
c .ModelName = value
if strings .Contains (value , "POWER8" ) ||
strings .Contains (value , "POWER7" ) {
c .Model = strings .Split (value , " " )[0 ]
c .Family = "POWER"
c .VendorID = "IBM"
}
case "stepping" , "revision" :
val := value
if key == "revision" {
val = strings .Split (value , "." )[0 ]
}
t , err := strconv .ParseInt (val , 10 , 64 )
if err != nil {
return ret , err
}
c .Stepping = int32 (t )
case "cpu MHz" , "clock" :
if t , err := strconv .ParseFloat (strings .Replace (value , "MHz" , "" , 1 ), 64 ); err == nil {
c .Mhz = t
}
case "cache size" :
t , err := strconv .ParseInt (strings .Replace (value , " KB" , "" , 1 ), 10 , 64 )
if err != nil {
return ret , err
}
c .CacheSize = int32 (t )
case "physical id" :
c .PhysicalID = value
case "core id" :
c .CoreID = value
case "flags" , "Features" :
c .Flags = strings .FieldsFunc (value , func (r rune ) bool {
return r == ',' || r == ' '
})
case "microcode" :
c .Microcode = value
}
}
if c .CPU >= 0 {
err := finishCPUInfo (&c )
if err != nil {
return ret , err
}
ret = append (ret , c )
}
return ret , nil
}
func parseStatLine(line string ) (*TimesStat , error ) {
fields := strings .Fields (line )
if len (fields ) == 0 {
return nil , errors .New ("stat does not contain cpu info" )
}
if !strings .HasPrefix (fields [0 ], "cpu" ) {
return nil , errors .New ("not contain cpu" )
}
cpu := fields [0 ]
if cpu == "cpu" {
cpu = "cpu-total"
}
user , err := strconv .ParseFloat (fields [1 ], 64 )
if err != nil {
return nil , err
}
nice , err := strconv .ParseFloat (fields [2 ], 64 )
if err != nil {
return nil , err
}
system , err := strconv .ParseFloat (fields [3 ], 64 )
if err != nil {
return nil , err
}
idle , err := strconv .ParseFloat (fields [4 ], 64 )
if err != nil {
return nil , err
}
iowait , err := strconv .ParseFloat (fields [5 ], 64 )
if err != nil {
return nil , err
}
irq , err := strconv .ParseFloat (fields [6 ], 64 )
if err != nil {
return nil , err
}
softirq , err := strconv .ParseFloat (fields [7 ], 64 )
if err != nil {
return nil , err
}
ct := &TimesStat {
CPU : cpu ,
User : user / ClocksPerSec ,
Nice : nice / ClocksPerSec ,
System : system / ClocksPerSec ,
Idle : idle / ClocksPerSec ,
Iowait : iowait / ClocksPerSec ,
Irq : irq / ClocksPerSec ,
Softirq : softirq / ClocksPerSec ,
}
if len (fields ) > 8 {
steal , err := strconv .ParseFloat (fields [8 ], 64 )
if err != nil {
return nil , err
}
ct .Steal = steal / ClocksPerSec
}
if len (fields ) > 9 {
guest , err := strconv .ParseFloat (fields [9 ], 64 )
if err != nil {
return nil , err
}
ct .Guest = guest / ClocksPerSec
}
if len (fields ) > 10 {
guestNice , err := strconv .ParseFloat (fields [10 ], 64 )
if err != nil {
return nil , err
}
ct .GuestNice = guestNice / ClocksPerSec
}
return ct , nil
}
func CountsWithContext (ctx context .Context , logical bool ) (int , error ) {
if logical {
ret := 0
procCpuinfo := common .HostProc ("cpuinfo" )
lines , err := common .ReadLines (procCpuinfo )
if err == nil {
for _ , line := range lines {
line = strings .ToLower (line )
if strings .HasPrefix (line , "processor" ) {
ret ++
}
}
}
if ret == 0 {
procStat := common .HostProc ("stat" )
lines , err = common .ReadLines (procStat )
if err != nil {
return 0 , err
}
for _ , line := range lines {
if len (line ) >= 4 && strings .HasPrefix (line , "cpu" ) && '0' <= line [3 ] && line [3 ] <= '9' {
ret ++
}
}
}
return ret , nil
}
threadSiblingsLists := make (map [string ]bool )
if files , err := filepath .Glob (common .HostSys ("devices/system/cpu/cpu[0-9]*/topology/thread_siblings_list" )); err == nil {
for _ , file := range files {
lines , err := common .ReadLines (file )
if err != nil || len (lines ) != 1 {
continue
}
threadSiblingsLists [lines [0 ]] = true
}
ret := len (threadSiblingsLists )
if ret != 0 {
return ret , nil
}
}
filename := common .HostProc ("cpuinfo" )
lines , err := common .ReadLines (filename )
if err != nil {
return 0 , err
}
mapping := make (map [int ]int )
currentInfo := make (map [string ]int )
for _ , line := range lines {
line = strings .ToLower (strings .TrimSpace (line ))
if line == "" {
id , okID := currentInfo ["physical id" ]
cores , okCores := currentInfo ["cpu cores" ]
if okID && okCores {
mapping [id ] = cores
}
currentInfo = make (map [string ]int )
continue
}
fields := strings .Split (line , ":" )
if len (fields ) < 2 {
continue
}
fields [0 ] = strings .TrimSpace (fields [0 ])
if fields [0 ] == "physical id" || fields [0 ] == "cpu cores" {
val , err := strconv .Atoi (strings .TrimSpace (fields [1 ]))
if err != nil {
continue
}
currentInfo [fields [0 ]] = val
}
}
ret := 0
for _ , v := range mapping {
ret += v
}
return ret , nil
}
The pages are generated with Golds v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .