pkg.re/essentialkaos/ek@v12.36.0+incompatible/system/process/process_cpu.go (about)

     1  // +build linux
     2  
     3  package process
     4  
     5  // ////////////////////////////////////////////////////////////////////////////////// //
     6  //                                                                                    //
     7  //                         Copyright (c) 2021 ESSENTIAL KAOS                          //
     8  //      Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0>     //
     9  //                                                                                    //
    10  // ////////////////////////////////////////////////////////////////////////////////// //
    11  
    12  import (
    13  	"bufio"
    14  	"errors"
    15  	"fmt"
    16  	"os"
    17  	"strconv"
    18  	"time"
    19  
    20  	"pkg.re/essentialkaos/ek.v12/strutil"
    21  )
    22  
    23  // ////////////////////////////////////////////////////////////////////////////////// //
    24  
    25  // Process state flags
    26  const (
    27  	STATE_RUNNING   = "R"
    28  	STATE_SLEEPING  = "S"
    29  	STATE_DISK_WAIT = "D"
    30  	STATE_ZOMBIE    = "Z"
    31  	STATE_STOPPED   = "T"
    32  	STATE_DEAD      = "X"
    33  	STATE_WAKEKILL  = "K"
    34  	STATE_WAKING    = "W"
    35  	STATE_PARKED    = "P"
    36  )
    37  
    38  // ////////////////////////////////////////////////////////////////////////////////// //
    39  
    40  // ProcInfo contains partial info from /proc/[PID]/stat
    41  type ProcInfo struct {
    42  	PID        int    `json:"pid"`         // The process ID
    43  	Comm       string `json:"comm"`        // The filename of the executable, in parentheses
    44  	State      string `json:"state"`       // Process state
    45  	PPID       int    `json:"ppid"`        // The PID of the parent of this process
    46  	Session    int    `json:"session"`     // The session ID of the process
    47  	TTYNR      int    `json:"tty_nr"`      // The controlling terminal of the process
    48  	TPGid      int    `json:"tpgid"`       // The ID of the foreground process group of the controlling terminal of the process
    49  	UTime      uint64 `json:"utime"`       // Amount of time that this process has been scheduled in user mode, measured in clock ticks
    50  	STime      uint64 `json:"stime"`       // Amount of time that this process has been scheduled in kernel mode, measured in clock ticks
    51  	CUTime     uint64 `json:"cutime"`      // Amount of time that this process's waited-for children have been scheduled in user mode, measured in clock ticks
    52  	CSTime     uint64 `json:"cstime"`      // Amount of time that this process's waited-for children have been scheduled in kernel mode, measured in clock ticks
    53  	Priority   int    `json:"priority"`    // Priority
    54  	Nice       int    `json:"nice"`        // The nice value
    55  	NumThreads int    `json:"num_threads"` // Number of threads in this process
    56  }
    57  
    58  // ProcSample contains value for usage calculation
    59  type ProcSample uint
    60  
    61  // ////////////////////////////////////////////////////////////////////////////////// //
    62  
    63  // ToSample converts ProcInfo to ProcSample for CPU usage calculation
    64  func (pi *ProcInfo) ToSample() ProcSample {
    65  	return ProcSample(pi.UTime + pi.STime + pi.CUTime + pi.CSTime)
    66  }
    67  
    68  // ////////////////////////////////////////////////////////////////////////////////// //
    69  
    70  // GetInfo returns process info from procfs
    71  func GetInfo(pid int) (*ProcInfo, error) {
    72  	fd, err := os.OpenFile(procFS+"/"+strconv.Itoa(pid)+"/stat", os.O_RDONLY, 0)
    73  
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	defer fd.Close()
    79  
    80  	r := bufio.NewReader(fd)
    81  	text, _ := r.ReadString('\n')
    82  
    83  	if len(text) < 20 {
    84  		return nil, errors.New("Can't parse stat file for given process")
    85  	}
    86  
    87  	return parseStatData(text)
    88  }
    89  
    90  // codebeat:disable[LOC,ABC]
    91  
    92  // GetSample returns ProcSample for CPU usage calculation
    93  func GetSample(pid int) (ProcSample, error) {
    94  	fd, err := os.OpenFile(procFS+"/"+strconv.Itoa(pid)+"/stat", os.O_RDONLY, 0)
    95  
    96  	if err != nil {
    97  		return 0, err
    98  	}
    99  
   100  	defer fd.Close()
   101  
   102  	r := bufio.NewReader(fd)
   103  	text, _ := r.ReadString('\n')
   104  
   105  	if len(text) < 20 {
   106  		return 0, errors.New("Can't parse stat file for given process")
   107  	}
   108  
   109  	return parseSampleData(text)
   110  }
   111  
   112  // codebeat:enable[LOC,ABC]
   113  
   114  // CalculateCPUUsage calculates CPU usage
   115  func CalculateCPUUsage(s1, s2 ProcSample, duration time.Duration) float64 {
   116  	total := float64(s2 - s1)
   117  	seconds := float64(duration) / float64(time.Second)
   118  
   119  	return 100.0 * ((total / getHZ()) / seconds)
   120  }
   121  
   122  // ////////////////////////////////////////////////////////////////////////////////// //
   123  
   124  // codebeat:disable[LOC,ABC]
   125  
   126  // parseStatData parses CPU stats data
   127  func parseStatData(text string) (*ProcInfo, error) {
   128  	var err error
   129  
   130  	info := &ProcInfo{}
   131  
   132  	for i := 0; i < 20; i++ {
   133  		switch i {
   134  		case 0:
   135  			info.PID, err = parseIntField(strutil.ReadField(text, i, true), i)
   136  		case 1:
   137  			info.Comm = strutil.ReadField(text, i, true)
   138  		case 2:
   139  			info.State = strutil.ReadField(text, i, true)
   140  		case 3:
   141  			info.PPID, err = parseIntField(strutil.ReadField(text, i, true), i)
   142  		case 5:
   143  			info.Session, err = parseIntField(strutil.ReadField(text, i, true), i)
   144  		case 6:
   145  			info.TTYNR, err = parseIntField(strutil.ReadField(text, i, true), i)
   146  		case 7:
   147  			info.TPGid, err = parseIntField(strutil.ReadField(text, i, true), i)
   148  		case 13:
   149  			info.UTime, err = parseUint64Field(strutil.ReadField(text, i, true), i)
   150  		case 14:
   151  			info.STime, err = parseUint64Field(strutil.ReadField(text, i, true), i)
   152  		case 15:
   153  			info.CUTime, err = parseUint64Field(strutil.ReadField(text, i, true), i)
   154  		case 16:
   155  			info.CSTime, err = parseUint64Field(strutil.ReadField(text, i, true), i)
   156  		case 17:
   157  			info.Priority, err = parseIntField(strutil.ReadField(text, i, true), i)
   158  		case 18:
   159  			info.Nice, err = parseIntField(strutil.ReadField(text, i, true), i)
   160  		case 19:
   161  			info.NumThreads, err = parseIntField(strutil.ReadField(text, i, true), i)
   162  		}
   163  
   164  		if err != nil {
   165  			return nil, err
   166  		}
   167  	}
   168  
   169  	return info, nil
   170  }
   171  
   172  // parseSampleData extracts CPU sample info
   173  func parseSampleData(text string) (ProcSample, error) {
   174  	var err error
   175  	var utime, stime, cutime, cstime uint64
   176  
   177  	for i := 13; i < 17; i++ {
   178  		value := strutil.ReadField(text, i, true)
   179  
   180  		switch i {
   181  		case 13:
   182  			utime, err = parseUint64Field(value, i)
   183  		case 14:
   184  			stime, err = parseUint64Field(value, i)
   185  		case 15:
   186  			cutime, err = parseUint64Field(value, i)
   187  		case 16:
   188  			cstime, err = parseUint64Field(value, i)
   189  		}
   190  
   191  		if err != nil {
   192  			return ProcSample(0), err
   193  		}
   194  	}
   195  
   196  	return ProcSample(utime + stime + cutime + cstime), nil
   197  }
   198  
   199  // codebeat:enable[LOC,ABC]
   200  
   201  // parseIntField parses int value of field
   202  func parseIntField(s string, field int) (int, error) {
   203  	v, err := strconv.Atoi(s)
   204  
   205  	if err != nil {
   206  		return 0, fmt.Errorf("Can't parse stat field %d: %w", field, err)
   207  	}
   208  
   209  	return v, nil
   210  }
   211  
   212  // parseIntField parses uint value of field
   213  func parseUint64Field(s string, field int) (uint64, error) {
   214  	v, err := strconv.ParseUint(s, 10, 64)
   215  
   216  	if err != nil {
   217  		return 0, fmt.Errorf("Can't parse stat field %d: %w", field, err)
   218  	}
   219  
   220  	return v, nil
   221  }
   222  
   223  // getHZ returns number of processor clock ticks per second
   224  func getHZ() float64 {
   225  	// CLK_TCK is a constant on Linux
   226  	// https://git.musl-libc.org/cgit/musl/tree/src/conf/sysconf.c#n30
   227  	return 100.0
   228  }