package cpu
import (
"context"
"errors"
"fmt"
"path/filepath"
"strconv"
"strings"
"github.com/tklauser/go-sysconf"
"github.com/shirou/gopsutil/v3/internal/common"
)
var ClocksPerSec = float64 (100 )
var armModelToModelName = map [uint64 ]string {
0x810 : "ARM810" ,
0x920 : "ARM920" ,
0x922 : "ARM922" ,
0x926 : "ARM926" ,
0x940 : "ARM940" ,
0x946 : "ARM946" ,
0x966 : "ARM966" ,
0xa20 : "ARM1020" ,
0xa22 : "ARM1022" ,
0xa26 : "ARM1026" ,
0xb02 : "ARM11 MPCore" ,
0xb36 : "ARM1136" ,
0xb56 : "ARM1156" ,
0xb76 : "ARM1176" ,
0xc05 : "Cortex-A5" ,
0xc07 : "Cortex-A7" ,
0xc08 : "Cortex-A8" ,
0xc09 : "Cortex-A9" ,
0xc0d : "Cortex-A17" ,
0xc0f : "Cortex-A15" ,
0xc0e : "Cortex-A17" ,
0xc14 : "Cortex-R4" ,
0xc15 : "Cortex-R5" ,
0xc17 : "Cortex-R7" ,
0xc18 : "Cortex-R8" ,
0xc20 : "Cortex-M0" ,
0xc21 : "Cortex-M1" ,
0xc23 : "Cortex-M3" ,
0xc24 : "Cortex-M4" ,
0xc27 : "Cortex-M7" ,
0xc60 : "Cortex-M0+" ,
0xd01 : "Cortex-A32" ,
0xd02 : "Cortex-A34" ,
0xd03 : "Cortex-A53" ,
0xd04 : "Cortex-A35" ,
0xd05 : "Cortex-A55" ,
0xd06 : "Cortex-A65" ,
0xd07 : "Cortex-A57" ,
0xd08 : "Cortex-A72" ,
0xd09 : "Cortex-A73" ,
0xd0a : "Cortex-A75" ,
0xd0b : "Cortex-A76" ,
0xd0c : "Neoverse-N1" ,
0xd0d : "Cortex-A77" ,
0xd0e : "Cortex-A76AE" ,
0xd13 : "Cortex-R52" ,
0xd20 : "Cortex-M23" ,
0xd21 : "Cortex-M33" ,
0xd40 : "Neoverse-V1" ,
0xd41 : "Cortex-A78" ,
0xd42 : "Cortex-A78AE" ,
0xd43 : "Cortex-A65AE" ,
0xd44 : "Cortex-X1" ,
0xd46 : "Cortex-A510" ,
0xd47 : "Cortex-A710" ,
0xd48 : "Cortex-X2" ,
0xd49 : "Neoverse-N2" ,
0xd4a : "Neoverse-E1" ,
0xd4b : "Cortex-A78C" ,
0xd4c : "Cortex-X1C" ,
0xd4d : "Cortex-A715" ,
0xd4e : "Cortex-X3" ,
}
func init() {
clkTck , err := sysconf .Sysconf (sysconf .SC_CLK_TCK )
if err == nil {
ClocksPerSec = float64 (clkTck )
}
}
func Times (percpu bool ) ([]TimesStat , error ) {
return TimesWithContext (context .Background (), percpu )
}
func TimesWithContext (ctx context .Context , percpu bool ) ([]TimesStat , error ) {
filename := common .HostProcWithContext (ctx , "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(ctx context .Context , cpu int32 , relPath string ) string {
return common .HostSysWithContext (ctx , fmt .Sprintf ("devices/system/cpu/cpu%d" , cpu ), relPath )
}
func finishCPUInfo(ctx context .Context , c *InfoStat ) {
var lines []string
var err error
var value float64
if len (c .CoreID ) == 0 {
lines , err = common .ReadLines (sysCPUPath (ctx , c .CPU , "topology/core_id" ))
if err == nil {
c .CoreID = lines [0 ]
}
}
lines , err = common .ReadLines (sysCPUPath (ctx , c .CPU , "cpufreq/cpuinfo_max_freq" ))
if err != nil || len (lines ) == 0 {
return
}
value , err = strconv .ParseFloat (lines [0 ], 64 )
if err != nil {
return
}
c .Mhz = value / 1000.0
if c .Mhz > 9999 {
c .Mhz = c .Mhz / 1000.0
}
}
func Info () ([]InfoStat , error ) {
return InfoWithContext (context .Background ())
}
func InfoWithContext (ctx context .Context ) ([]InfoStat , error ) {
filename := common .HostProcWithContext (ctx , "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" , "cpu number" :
if c .CPU >= 0 {
finishCPUInfo (ctx , &c )
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
if strings .Contains (value , "S390" ) {
processorName = "S390"
}
case "CPU implementer" :
if v , err := strconv .ParseUint (value , 0 , 8 ); err == nil {
switch v {
case 0x41 :
c .VendorID = "ARM"
case 0x42 :
c .VendorID = "Broadcom"
case 0x43 :
c .VendorID = "Cavium"
case 0x44 :
c .VendorID = "DEC"
case 0x46 :
c .VendorID = "Fujitsu"
case 0x48 :
c .VendorID = "HiSilicon"
case 0x49 :
c .VendorID = "Infineon"
case 0x4d :
c .VendorID = "Motorola/Freescale"
case 0x4e :
c .VendorID = "NVIDIA"
case 0x50 :
c .VendorID = "APM"
case 0x51 :
c .VendorID = "Qualcomm"
case 0x56 :
c .VendorID = "Marvell"
case 0x61 :
c .VendorID = "Apple"
case 0x69 :
c .VendorID = "Intel"
case 0xc0 :
c .VendorID = "Ampere"
}
}
case "cpu family" :
c .Family = value
case "model" , "CPU part" :
c .Model = value
if c .VendorID == "ARM" {
if v , err := strconv .ParseUint (c .Model , 0 , 16 ); err == nil {
modelName , exist := armModelToModelName [v ]
if exist {
c .ModelName = modelName
} else {
c .ModelName = "Undefined"
}
}
}
case "Model Name" , "model name" , "cpu" :
c .ModelName = value
if strings .Contains (value , "POWER" ) {
c .Model = strings .Split (value , " " )[0 ]
c .Family = "POWER"
c .VendorID = "IBM"
}
case "stepping" , "revision" , "CPU 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" , "cpu MHz dynamic" :
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 {
finishCPUInfo (ctx , &c )
ret = append (ret , c )
}
return ret , nil
}
func parseStatLine(line string ) (*TimesStat , error ) {
fields := strings .Fields (line )
if len (fields ) < 8 {
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 .HostProcWithContext (ctx , "cpuinfo" )
lines , err := common .ReadLines (procCpuinfo )
if err == nil {
for _ , line := range lines {
line = strings .ToLower (line )
if strings .HasPrefix (line , "processor" ) {
_, err = strconv .Atoi (strings .TrimSpace (line [strings .IndexByte (line , ':' )+1 :]))
if err == nil {
ret ++
}
}
}
}
if ret == 0 {
procStat := common .HostProcWithContext (ctx , "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 )
for _ , glob := range []string {"devices/system/cpu/cpu[0-9]*/topology/core_cpus_list" , "devices/system/cpu/cpu[0-9]*/topology/thread_siblings_list" } {
if files , err := filepath .Glob (common .HostSysWithContext (ctx , glob )); 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 .HostProcWithContext (ctx , "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.8.2 . (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 @zigo_101 (reachable from the left QR code) to get the latest news of Golds .