github.com/isyscore/isc-gobase@v1.5.3-0.20231218061332-cbc7451899e9/system/cpu/cpu_linux.go (about)

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