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

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