pkg.re/essentialkaos/ek.10@v12.41.0+incompatible/system/info_cpu_linux.go (about)

     1  package system
     2  
     3  // ////////////////////////////////////////////////////////////////////////////////// //
     4  //                                                                                    //
     5  //                         Copyright (c) 2022 ESSENTIAL KAOS                          //
     6  //      Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0>     //
     7  //                                                                                    //
     8  // ////////////////////////////////////////////////////////////////////////////////// //
     9  
    10  import (
    11  	"bufio"
    12  	"errors"
    13  	"io/ioutil"
    14  	"strconv"
    15  	"strings"
    16  	"time"
    17  
    18  	"pkg.re/essentialkaos/ek.v12/strutil"
    19  )
    20  
    21  // ////////////////////////////////////////////////////////////////////////////////// //
    22  
    23  // Path to file with CPU stats in procfs
    24  var procStatFile = "/proc/stat"
    25  
    26  // Path to file with info about CPU
    27  var cpuInfoFile = "/proc/cpuinfo"
    28  
    29  // Files with CPU info
    30  var (
    31  	cpuPossibleFile = "/sys/devices/system/cpu/possible"
    32  	cpuPresentFile  = "/sys/devices/system/cpu/present"
    33  	cpuOnlineFile   = "/sys/devices/system/cpu/online"
    34  	cpuOfflineFile  = "/sys/devices/system/cpu/offline"
    35  )
    36  
    37  // ////////////////////////////////////////////////////////////////////////////////// //
    38  
    39  // GetCPUUsage returns info about CPU usage
    40  func GetCPUUsage(duration time.Duration) (*CPUUsage, error) {
    41  	c1, err := GetCPUStats()
    42  
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  
    47  	time.Sleep(duration)
    48  
    49  	c2, err := GetCPUStats()
    50  
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  
    55  	return CalculateCPUUsage(c1, c2), nil
    56  }
    57  
    58  // It's ok to have so complex method for calculation
    59  // codebeat:disable[CYCLO]
    60  
    61  // CalculateCPUUsage calcualtes CPU usage based on CPUStats
    62  func CalculateCPUUsage(c1, c2 *CPUStats) *CPUUsage {
    63  	prevIdle := c1.Idle + c1.Wait
    64  	idle := c2.Idle + c2.Wait
    65  
    66  	prevNonIdle := c1.User + c1.Nice + c1.System + c1.IRQ + c1.SRQ + c1.Steal
    67  	nonIdle := c2.User + c2.Nice + c2.System + c2.IRQ + c2.SRQ + c2.Steal
    68  
    69  	prevTotal := prevIdle + prevNonIdle
    70  	total := idle + nonIdle
    71  
    72  	totalDiff := float64(total - prevTotal)
    73  	idleDiff := float64(idle - prevIdle)
    74  	allTotalDiff := float64(c2.Total - c1.Total)
    75  
    76  	return &CPUUsage{
    77  		System:  (float64(c2.System-c1.System) / allTotalDiff) * 100,
    78  		User:    (float64(c2.User-c1.User) / allTotalDiff) * 100,
    79  		Nice:    (float64(c2.Nice-c1.Nice) / allTotalDiff) * 100,
    80  		Wait:    (float64(c2.Wait-c1.Wait) / allTotalDiff) * 100,
    81  		Idle:    (float64(c2.Idle-c1.Idle) / allTotalDiff) * 100,
    82  		Average: ((totalDiff - idleDiff) / totalDiff) * 100.0,
    83  		Count:   c2.Count,
    84  	}
    85  }
    86  
    87  // codebeat:enable[CYCLO]
    88  
    89  // GetCPUStats returns basic CPU stats
    90  func GetCPUStats() (*CPUStats, error) {
    91  	s, closer, err := getFileScanner(procStatFile)
    92  
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	defer closer()
    98  
    99  	return parseCPUStats(s)
   100  }
   101  
   102  // GetCPUInfo returns slice with info about CPUs
   103  func GetCPUInfo() ([]*CPUInfo, error) {
   104  	s, closer, err := getFileScanner(cpuInfoFile)
   105  
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  
   110  	defer closer()
   111  
   112  	return parseCPUInfo(s)
   113  }
   114  
   115  // GetCPUCount returns info about CPU
   116  func GetCPUCount() (CPUCount, error) {
   117  	possible, err := ioutil.ReadFile(cpuPossibleFile)
   118  
   119  	if err != nil {
   120  		return CPUCount{}, err
   121  	}
   122  
   123  	present, err := ioutil.ReadFile(cpuPresentFile)
   124  
   125  	if err != nil {
   126  		return CPUCount{}, err
   127  	}
   128  
   129  	online, err := ioutil.ReadFile(cpuOnlineFile)
   130  
   131  	if err != nil {
   132  		return CPUCount{}, err
   133  	}
   134  
   135  	offline, err := ioutil.ReadFile(cpuOfflineFile)
   136  
   137  	if err != nil {
   138  		return CPUCount{}, err
   139  	}
   140  
   141  	return CPUCount{
   142  		Possible: parseCPUCountInfo(string(possible)),
   143  		Present:  parseCPUCountInfo(string(present)),
   144  		Online:   parseCPUCountInfo(string(online)),
   145  		Offline:  parseCPUCountInfo(string(offline)),
   146  	}, nil
   147  }
   148  
   149  // ////////////////////////////////////////////////////////////////////////////////// //
   150  
   151  // codebeat:disable[LOC,ABC,CYCLO]
   152  
   153  // parseCPUStats parses cpu stats data
   154  func parseCPUStats(s *bufio.Scanner) (*CPUStats, error) {
   155  	var err error
   156  
   157  	stats := &CPUStats{}
   158  
   159  	for s.Scan() {
   160  		text := s.Text()
   161  
   162  		if len(text) < 4 || text[:3] != "cpu" {
   163  			continue
   164  		}
   165  
   166  		if text[:4] != "cpu " {
   167  			stats.Count++
   168  			continue
   169  		}
   170  
   171  		stats.User, err = strconv.ParseUint(strutil.ReadField(text, 1, true), 10, 64)
   172  
   173  		if err != nil {
   174  			return nil, errors.New("Can't parse field 1 as unsigned integer in " + procStatFile)
   175  		}
   176  
   177  		stats.Nice, err = strconv.ParseUint(strutil.ReadField(text, 2, true), 10, 64)
   178  
   179  		if err != nil {
   180  			return nil, errors.New("Can't parse field 2 as unsigned integer in " + procStatFile)
   181  		}
   182  
   183  		stats.System, err = strconv.ParseUint(strutil.ReadField(text, 3, true), 10, 64)
   184  
   185  		if err != nil {
   186  			return nil, errors.New("Can't parse field 3 as unsigned integer in " + procStatFile)
   187  		}
   188  
   189  		stats.Idle, err = strconv.ParseUint(strutil.ReadField(text, 4, true), 10, 64)
   190  
   191  		if err != nil {
   192  			return nil, errors.New("Can't parse field 4 as unsigned integer in " + procStatFile)
   193  		}
   194  
   195  		stats.Wait, err = strconv.ParseUint(strutil.ReadField(text, 5, true), 10, 64)
   196  
   197  		if err != nil {
   198  			return nil, errors.New("Can't parse field 5 as unsigned integer in " + procStatFile)
   199  		}
   200  
   201  		stats.IRQ, err = strconv.ParseUint(strutil.ReadField(text, 6, true), 10, 64)
   202  
   203  		if err != nil {
   204  			return nil, errors.New("Can't parse field 6 as unsigned integer in " + procStatFile)
   205  		}
   206  
   207  		stats.SRQ, err = strconv.ParseUint(strutil.ReadField(text, 7, true), 10, 64)
   208  
   209  		if err != nil {
   210  			return nil, errors.New("Can't parse field 7 as unsigned integer in " + procStatFile)
   211  		}
   212  
   213  		stats.Steal, err = strconv.ParseUint(strutil.ReadField(text, 8, true), 10, 64)
   214  
   215  		if err != nil {
   216  			return nil, errors.New("Can't parse field 8 as unsigned integer in " + procStatFile)
   217  		}
   218  
   219  	}
   220  
   221  	if stats.Count == 0 {
   222  		return nil, errors.New("Can't parse file " + procStatFile)
   223  	}
   224  
   225  	stats.Total = stats.User + stats.System + stats.Nice + stats.Idle + stats.Wait + stats.IRQ + stats.SRQ + stats.Steal
   226  
   227  	return stats, nil
   228  }
   229  
   230  // parseCPUInfo parses cpu info data
   231  func parseCPUInfo(s *bufio.Scanner) ([]*CPUInfo, error) {
   232  	var (
   233  		err      error
   234  		info     []*CPUInfo
   235  		vendor   string
   236  		model    string
   237  		siblings int
   238  		cores    int
   239  		cache    uint64
   240  		speed    float64
   241  		id       int
   242  	)
   243  
   244  	for s.Scan() {
   245  		text := s.Text()
   246  
   247  		switch {
   248  		case strings.HasPrefix(text, "vendor_id"):
   249  			vendor = strings.Trim(strutil.ReadField(text, 1, false, ":"), " ")
   250  
   251  		case strings.HasPrefix(text, "model name"):
   252  			model = strings.Trim(strutil.ReadField(text, 1, false, ":"), " ")
   253  
   254  		case strings.HasPrefix(text, "cache size"):
   255  			cache, err = parseSize(strings.Trim(strutil.ReadField(text, 1, false, ":"), " KB"))
   256  
   257  		case strings.HasPrefix(text, "cpu MHz"):
   258  			speed, err = strconv.ParseFloat(strings.Trim(strutil.ReadField(text, 1, false, ":"), " "), 64)
   259  
   260  		case strings.HasPrefix(text, "physical id"):
   261  			id, err = strconv.Atoi(strings.Trim(strutil.ReadField(text, 1, false, ":"), " "))
   262  
   263  		case strings.HasPrefix(text, "siblings"):
   264  			siblings, err = strconv.Atoi(strings.Trim(strutil.ReadField(text, 1, false, ":"), " "))
   265  
   266  		case strings.HasPrefix(text, "cpu cores"):
   267  			cores, err = strconv.Atoi(strings.Trim(strutil.ReadField(text, 1, false, ":"), " "))
   268  
   269  		case strings.HasPrefix(text, "flags"):
   270  			if len(info) < id+1 {
   271  				info = append(info, &CPUInfo{vendor, model, cores, siblings, cache, nil})
   272  			}
   273  
   274  			if id < len(info) && info[id] != nil {
   275  				info[id].Speed = append(info[id].Speed, speed)
   276  			}
   277  		}
   278  
   279  		if err != nil {
   280  			return nil, err
   281  		}
   282  	}
   283  
   284  	if vendor == "" && model == "" {
   285  		return nil, errors.New("Can't parse cpuinfo file")
   286  	}
   287  
   288  	return info, nil
   289  }
   290  
   291  // codebeat:enable[LOC,ABC,CYCLO]
   292  
   293  // parseCPUCountInfo parses CPU count data
   294  func parseCPUCountInfo(data string) uint32 {
   295  	startNum := strings.Trim(strutil.ReadField(data, 0, false, "-"), "\n\r")
   296  	endNum := strings.Trim(strutil.ReadField(data, 1, false, "-"), "\n\r")
   297  
   298  	start, _ := strconv.ParseUint(startNum, 10, 32)
   299  	end, _ := strconv.ParseUint(endNum, 10, 32)
   300  
   301  	return uint32(end-start) + 1
   302  }
   303  
   304  // getCPUArchBits returns CPU architecture bits (32/64)
   305  func getCPUArchBits() int {
   306  	s, closer, err := getFileScanner(cpuInfoFile)
   307  
   308  	if err != nil {
   309  		return 0
   310  	}
   311  
   312  	defer closer()
   313  
   314  	for s.Scan() {
   315  		text := s.Text()
   316  
   317  		if !strings.HasPrefix(text, "flags") {
   318  			continue
   319  		}
   320  
   321  		// lm - 64 / tm - 32 / rm - 16
   322  		if strings.Contains(text, " lm ") {
   323  			return 64
   324  		}
   325  	}
   326  
   327  	return 32
   328  }