github.com/boomhut/fiber/v2@v2.0.0-20230603160335-b65c856e57d3/internal/gopsutil/cpu/cpu_linux.go (about)

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