...

Source file src/github.com/shirou/gopsutil/cpu/cpu_linux.go

Documentation: github.com/shirou/gopsutil/cpu

     1  // +build linux
     2  
     3  package cpu
     4  
     5  import (
     6  	"context"
     7  	"errors"
     8  	"fmt"
     9  	"path/filepath"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/shirou/gopsutil/internal/common"
    14  	"github.com/tklauser/go-sysconf"
    15  )
    16  
    17  var ClocksPerSec = float64(100)
    18  
    19  func init() {
    20  	clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
    21  	// ignore errors
    22  	if err == nil {
    23  		ClocksPerSec = float64(clkTck)
    24  	}
    25  }
    26  
    27  func Times(percpu bool) ([]TimesStat, error) {
    28  	return TimesWithContext(context.Background(), percpu)
    29  }
    30  
    31  func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
    32  	filename := common.HostProc("stat")
    33  	var lines = []string{}
    34  	if percpu {
    35  		statlines, err := common.ReadLines(filename)
    36  		if err != nil || len(statlines) < 2 {
    37  			return []TimesStat{}, nil
    38  		}
    39  		for _, line := range statlines[1:] {
    40  			if !strings.HasPrefix(line, "cpu") {
    41  				break
    42  			}
    43  			lines = append(lines, line)
    44  		}
    45  	} else {
    46  		lines, _ = common.ReadLinesOffsetN(filename, 0, 1)
    47  	}
    48  
    49  	ret := make([]TimesStat, 0, len(lines))
    50  
    51  	for _, line := range lines {
    52  		ct, err := parseStatLine(line)
    53  		if err != nil {
    54  			continue
    55  		}
    56  		ret = append(ret, *ct)
    57  
    58  	}
    59  	return ret, nil
    60  }
    61  
    62  func sysCPUPath(cpu int32, relPath string) string {
    63  	return common.HostSys(fmt.Sprintf("devices/system/cpu/cpu%d", cpu), relPath)
    64  }
    65  
    66  func finishCPUInfo(c *InfoStat) error {
    67  	var lines []string
    68  	var err error
    69  	var value float64
    70  
    71  	if len(c.CoreID) == 0 {
    72  		lines, err = common.ReadLines(sysCPUPath(c.CPU, "topology/core_id"))
    73  		if err == nil {
    74  			c.CoreID = lines[0]
    75  		}
    76  	}
    77  
    78  	// override the value of c.Mhz with cpufreq/cpuinfo_max_freq regardless
    79  	// of the value from /proc/cpuinfo because we want to report the maximum
    80  	// clock-speed of the CPU for c.Mhz, matching the behaviour of Windows
    81  	lines, err = common.ReadLines(sysCPUPath(c.CPU, "cpufreq/cpuinfo_max_freq"))
    82  	// if we encounter errors below such as there are no cpuinfo_max_freq file,
    83  	// we just ignore. so let Mhz is 0.
    84  	if err != nil || len(lines) == 0 {
    85  		return nil
    86  	}
    87  	value, err = strconv.ParseFloat(lines[0], 64)
    88  	if err != nil {
    89  		return nil
    90  	}
    91  	c.Mhz = value / 1000.0 // value is in kHz
    92  	if c.Mhz > 9999 {
    93  		c.Mhz = c.Mhz / 1000.0 // value in Hz
    94  	}
    95  	return nil
    96  }
    97  
    98  // CPUInfo on linux will return 1 item per physical thread.
    99  //
   100  // CPUs have three levels of counting: sockets, cores, threads.
   101  // Cores with HyperThreading count as having 2 threads per core.
   102  // Sockets often come with many physical CPU cores.
   103  // For example a single socket board with two cores each with HT will
   104  // return 4 CPUInfoStat structs on Linux and the "Cores" field set to 1.
   105  func Info() ([]InfoStat, error) {
   106  	return InfoWithContext(context.Background())
   107  }
   108  
   109  func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
   110  	filename := common.HostProc("cpuinfo")
   111  	lines, _ := common.ReadLines(filename)
   112  
   113  	var ret []InfoStat
   114  	var processorName string
   115  
   116  	c := InfoStat{CPU: -1, Cores: 1}
   117  	for _, line := range lines {
   118  		fields := strings.Split(line, ":")
   119  		if len(fields) < 2 {
   120  			continue
   121  		}
   122  		key := strings.TrimSpace(fields[0])
   123  		value := strings.TrimSpace(fields[1])
   124  
   125  		switch key {
   126  		case "Processor":
   127  			processorName = value
   128  		case "processor":
   129  			if c.CPU >= 0 {
   130  				err := finishCPUInfo(&c)
   131  				if err != nil {
   132  					return ret, err
   133  				}
   134  				ret = append(ret, c)
   135  			}
   136  			c = InfoStat{Cores: 1, ModelName: processorName}
   137  			t, err := strconv.ParseInt(value, 10, 64)
   138  			if err != nil {
   139  				return ret, err
   140  			}
   141  			c.CPU = int32(t)
   142  		case "vendorId", "vendor_id":
   143  			c.VendorID = value
   144  		case "CPU implementer":
   145  			if v, err := strconv.ParseUint(value, 0, 8); err == nil {
   146  				switch v {
   147  				case 0x41:
   148  					c.VendorID = "ARM"
   149  				case 0x42:
   150  					c.VendorID = "Broadcom"
   151  				case 0x43:
   152  					c.VendorID = "Cavium"
   153  				case 0x44:
   154  					c.VendorID = "DEC"
   155  				case 0x46:
   156  					c.VendorID = "Fujitsu"
   157  				case 0x48:
   158  					c.VendorID = "HiSilicon"
   159  				case 0x49:
   160  					c.VendorID = "Infineon"
   161  				case 0x4d:
   162  					c.VendorID = "Motorola/Freescale"
   163  				case 0x4e:
   164  					c.VendorID = "NVIDIA"
   165  				case 0x50:
   166  					c.VendorID = "APM"
   167  				case 0x51:
   168  					c.VendorID = "Qualcomm"
   169  				case 0x56:
   170  					c.VendorID = "Marvell"
   171  				case 0x61:
   172  					c.VendorID = "Apple"
   173  				case 0x69:
   174  					c.VendorID = "Intel"
   175  				case 0xc0:
   176  					c.VendorID = "Ampere"
   177  				}
   178  			}
   179  		case "cpu family":
   180  			c.Family = value
   181  		case "model", "CPU part":
   182  			c.Model = value
   183  		case "model name", "cpu":
   184  			c.ModelName = value
   185  			if strings.Contains(value, "POWER8") ||
   186  				strings.Contains(value, "POWER7") {
   187  				c.Model = strings.Split(value, " ")[0]
   188  				c.Family = "POWER"
   189  				c.VendorID = "IBM"
   190  			}
   191  		case "stepping", "revision", "CPU revision":
   192  			val := value
   193  
   194  			if key == "revision" {
   195  				val = strings.Split(value, ".")[0]
   196  			}
   197  
   198  			t, err := strconv.ParseInt(val, 10, 64)
   199  			if err != nil {
   200  				return ret, err
   201  			}
   202  			c.Stepping = int32(t)
   203  		case "cpu MHz", "clock":
   204  			// treat this as the fallback value, thus we ignore error
   205  			if t, err := strconv.ParseFloat(strings.Replace(value, "MHz", "", 1), 64); err == nil {
   206  				c.Mhz = t
   207  			}
   208  		case "cache size":
   209  			t, err := strconv.ParseInt(strings.Replace(value, " KB", "", 1), 10, 64)
   210  			if err != nil {
   211  				return ret, err
   212  			}
   213  			c.CacheSize = int32(t)
   214  		case "physical id":
   215  			c.PhysicalID = value
   216  		case "core id":
   217  			c.CoreID = value
   218  		case "flags", "Features":
   219  			c.Flags = strings.FieldsFunc(value, func(r rune) bool {
   220  				return r == ',' || r == ' '
   221  			})
   222  		case "microcode":
   223  			c.Microcode = value
   224  		}
   225  	}
   226  	if c.CPU >= 0 {
   227  		err := finishCPUInfo(&c)
   228  		if err != nil {
   229  			return ret, err
   230  		}
   231  		ret = append(ret, c)
   232  	}
   233  	return ret, nil
   234  }
   235  
   236  func parseStatLine(line string) (*TimesStat, error) {
   237  	fields := strings.Fields(line)
   238  
   239  	if len(fields) == 0 {
   240  		return nil, errors.New("stat does not contain cpu info")
   241  	}
   242  
   243  	if strings.HasPrefix(fields[0], "cpu") == false {
   244  		return nil, errors.New("not contain cpu")
   245  	}
   246  
   247  	cpu := fields[0]
   248  	if cpu == "cpu" {
   249  		cpu = "cpu-total"
   250  	}
   251  	user, err := strconv.ParseFloat(fields[1], 64)
   252  	if err != nil {
   253  		return nil, err
   254  	}
   255  	nice, err := strconv.ParseFloat(fields[2], 64)
   256  	if err != nil {
   257  		return nil, err
   258  	}
   259  	system, err := strconv.ParseFloat(fields[3], 64)
   260  	if err != nil {
   261  		return nil, err
   262  	}
   263  	idle, err := strconv.ParseFloat(fields[4], 64)
   264  	if err != nil {
   265  		return nil, err
   266  	}
   267  	iowait, err := strconv.ParseFloat(fields[5], 64)
   268  	if err != nil {
   269  		return nil, err
   270  	}
   271  	irq, err := strconv.ParseFloat(fields[6], 64)
   272  	if err != nil {
   273  		return nil, err
   274  	}
   275  	softirq, err := strconv.ParseFloat(fields[7], 64)
   276  	if err != nil {
   277  		return nil, err
   278  	}
   279  
   280  	ct := &TimesStat{
   281  		CPU:     cpu,
   282  		User:    user / ClocksPerSec,
   283  		Nice:    nice / ClocksPerSec,
   284  		System:  system / ClocksPerSec,
   285  		Idle:    idle / ClocksPerSec,
   286  		Iowait:  iowait / ClocksPerSec,
   287  		Irq:     irq / ClocksPerSec,
   288  		Softirq: softirq / ClocksPerSec,
   289  	}
   290  	if len(fields) > 8 { // Linux >= 2.6.11
   291  		steal, err := strconv.ParseFloat(fields[8], 64)
   292  		if err != nil {
   293  			return nil, err
   294  		}
   295  		ct.Steal = steal / ClocksPerSec
   296  	}
   297  	if len(fields) > 9 { // Linux >= 2.6.24
   298  		guest, err := strconv.ParseFloat(fields[9], 64)
   299  		if err != nil {
   300  			return nil, err
   301  		}
   302  		ct.Guest = guest / ClocksPerSec
   303  	}
   304  	if len(fields) > 10 { // Linux >= 3.2.0
   305  		guestNice, err := strconv.ParseFloat(fields[10], 64)
   306  		if err != nil {
   307  			return nil, err
   308  		}
   309  		ct.GuestNice = guestNice / ClocksPerSec
   310  	}
   311  
   312  	return ct, nil
   313  }
   314  
   315  func CountsWithContext(ctx context.Context, logical bool) (int, error) {
   316  	if logical {
   317  		ret := 0
   318  		// https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_pslinux.py#L599
   319  		procCpuinfo := common.HostProc("cpuinfo")
   320  		lines, err := common.ReadLines(procCpuinfo)
   321  		if err == nil {
   322  			for _, line := range lines {
   323  				line = strings.ToLower(line)
   324  				if strings.HasPrefix(line, "processor")  {
   325  					_, err = strconv.Atoi(strings.TrimSpace(line[strings.IndexByte(line, ':')+1:]))
   326  					if err == nil {
   327  						ret++
   328  					}
   329  				}
   330  			}
   331  		}
   332  		if ret == 0 {
   333  			procStat := common.HostProc("stat")
   334  			lines, err = common.ReadLines(procStat)
   335  			if err != nil {
   336  				return 0, err
   337  			}
   338  			for _, line := range lines {
   339  				if len(line) >= 4 && strings.HasPrefix(line, "cpu") && '0' <= line[3] && line[3] <= '9' { // `^cpu\d` regexp matching
   340  					ret++
   341  				}
   342  			}
   343  		}
   344  		return ret, nil
   345  	}
   346  	// physical cores
   347  	// https://github.com/giampaolo/psutil/blob/8415355c8badc9c94418b19bdf26e622f06f0cce/psutil/_pslinux.py#L615-L628
   348  	var threadSiblingsLists = make(map[string]bool)
   349  	// These 2 files are the same but */core_cpus_list is newer while */thread_siblings_list is deprecated and may disappear in the future.
   350  	// https://www.kernel.org/doc/Documentation/admin-guide/cputopology.rst
   351  	// https://github.com/giampaolo/psutil/pull/1727#issuecomment-707624964
   352  	// https://lkml.org/lkml/2019/2/26/41
   353  	for _, glob := range []string{"devices/system/cpu/cpu[0-9]*/topology/core_cpus_list", "devices/system/cpu/cpu[0-9]*/topology/thread_siblings_list"} {
   354  		if files, err := filepath.Glob(common.HostSys(glob)); err == nil {
   355  			for _, file := range files {
   356  				lines, err := common.ReadLines(file)
   357  				if err != nil || len(lines) != 1 {
   358  					continue
   359  				}
   360  				threadSiblingsLists[lines[0]] = true
   361  			}
   362  			ret := len(threadSiblingsLists)
   363  			if ret != 0 {
   364  				return ret, nil
   365  			}
   366  		}
   367  	}
   368  	// https://github.com/giampaolo/psutil/blob/122174a10b75c9beebe15f6c07dcf3afbe3b120d/psutil/_pslinux.py#L631-L652
   369  	filename := common.HostProc("cpuinfo")
   370  	lines, err := common.ReadLines(filename)
   371  	if err != nil {
   372  		return 0, err
   373  	}
   374  	mapping := make(map[int]int)
   375  	currentInfo := make(map[string]int)
   376  	for _, line := range lines {
   377  		line = strings.ToLower(strings.TrimSpace(line))
   378  		if line == "" {
   379  			// new section
   380  			id, okID := currentInfo["physical id"]
   381  			cores, okCores := currentInfo["cpu cores"]
   382  			if okID && okCores {
   383  				mapping[id] = cores
   384  			}
   385  			currentInfo = make(map[string]int)
   386  			continue
   387  		}
   388  		fields := strings.Split(line, ":")
   389  		if len(fields) < 2 {
   390  			continue
   391  		}
   392  		fields[0] = strings.TrimSpace(fields[0])
   393  		if fields[0] == "physical id" || fields[0] == "cpu cores" {
   394  			val, err := strconv.Atoi(strings.TrimSpace(fields[1]))
   395  			if err != nil {
   396  				continue
   397  			}
   398  			currentInfo[fields[0]] = val
   399  		}
   400  	}
   401  	ret := 0
   402  	for _, v := range mapping {
   403  		ret += v
   404  	}
   405  	return ret, nil
   406  }
   407  

View as plain text