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

     1  package process
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"runtime"
     8  	"sort"
     9  	"sync"
    10  	"syscall"
    11  	"time"
    12  
    13  	"github.com/isyscore/isc-gobase/system/common"
    14  	"github.com/isyscore/isc-gobase/system/cpu"
    15  	"github.com/isyscore/isc-gobase/system/mem"
    16  	"github.com/isyscore/isc-gobase/system/net"
    17  )
    18  
    19  var (
    20  	invoke                 common.Invoker = common.Invoke{}
    21  	ErrorNoChildren                       = errors.New("process does not have children")
    22  	ErrorProcessNotRunning                = errors.New("process does not exist")
    23  )
    24  
    25  type Process struct {
    26  	Pid            int32 `json:"pid"`
    27  	name           string
    28  	status         string
    29  	parent         int32
    30  	parentMutex    sync.RWMutex // for windows ppid cache
    31  	numCtxSwitches *NumCtxSwitchesStat
    32  	uids           []int32
    33  	gids           []int32
    34  	groups         []int32
    35  	numThreads     int32
    36  	memInfo        *MemoryInfoStat
    37  	sigInfo        *SignalInfoStat
    38  	createTime     int64
    39  
    40  	lastCPUTimes *cpu.TimesStat
    41  	lastCPUTime  time.Time
    42  
    43  	tgid int32
    44  }
    45  
    46  type OpenFilesStat struct {
    47  	Path string `json:"path"`
    48  	Fd   uint64 `json:"fd"`
    49  }
    50  
    51  type MemoryInfoStat struct {
    52  	RSS    uint64 `json:"rss"`    // bytes
    53  	VMS    uint64 `json:"vms"`    // bytes
    54  	HWM    uint64 `json:"hwm"`    // bytes
    55  	Data   uint64 `json:"data"`   // bytes
    56  	Stack  uint64 `json:"stack"`  // bytes
    57  	Locked uint64 `json:"locked"` // bytes
    58  	Swap   uint64 `json:"swap"`   // bytes
    59  }
    60  
    61  type SignalInfoStat struct {
    62  	PendingProcess uint64 `json:"pending_process"`
    63  	PendingThread  uint64 `json:"pending_thread"`
    64  	Blocked        uint64 `json:"blocked"`
    65  	Ignored        uint64 `json:"ignored"`
    66  	Caught         uint64 `json:"caught"`
    67  }
    68  
    69  type RlimitStat struct {
    70  	Resource int32  `json:"resource"`
    71  	Soft     int32  `json:"soft"` //TODO too small. needs to be uint64
    72  	Hard     int32  `json:"hard"` //TODO too small. needs to be uint64
    73  	Used     uint64 `json:"used"`
    74  }
    75  
    76  type IOCountersStat struct {
    77  	ReadCount  uint64 `json:"readCount"`
    78  	WriteCount uint64 `json:"writeCount"`
    79  	ReadBytes  uint64 `json:"readBytes"`
    80  	WriteBytes uint64 `json:"writeBytes"`
    81  }
    82  
    83  type NumCtxSwitchesStat struct {
    84  	Voluntary   int64 `json:"voluntary"`
    85  	Involuntary int64 `json:"involuntary"`
    86  }
    87  
    88  type PageFaultsStat struct {
    89  	MinorFaults      uint64 `json:"minorFaults"`
    90  	MajorFaults      uint64 `json:"majorFaults"`
    91  	ChildMinorFaults uint64 `json:"childMinorFaults"`
    92  	ChildMajorFaults uint64 `json:"childMajorFaults"`
    93  }
    94  
    95  // Resource limit constants are from /usr/include/x86_64-linux-gnu/bits/resource.h
    96  // from libc6-dev package in Ubuntu 16.10
    97  const (
    98  	RLIMIT_CPU        int32 = 0
    99  	RLIMIT_FSIZE      int32 = 1
   100  	RLIMIT_DATA       int32 = 2
   101  	RLIMIT_STACK      int32 = 3
   102  	RLIMIT_CORE       int32 = 4
   103  	RLIMIT_RSS        int32 = 5
   104  	RLIMIT_NPROC      int32 = 6
   105  	RLIMIT_NOFILE     int32 = 7
   106  	RLIMIT_MEMLOCK    int32 = 8
   107  	RLIMIT_AS         int32 = 9
   108  	RLIMIT_LOCKS      int32 = 10
   109  	RLIMIT_SIGPENDING int32 = 11
   110  	RLIMIT_MSGQUEUE   int32 = 12
   111  	RLIMIT_NICE       int32 = 13
   112  	RLIMIT_RTPRIO     int32 = 14
   113  	RLIMIT_RTTIME     int32 = 15
   114  )
   115  
   116  func (p *Process) String() string {
   117  	s, _ := json.Marshal(p)
   118  	return string(s)
   119  }
   120  
   121  func (o OpenFilesStat) String() string {
   122  	s, _ := json.Marshal(o)
   123  	return string(s)
   124  }
   125  
   126  func (m MemoryInfoStat) String() string {
   127  	s, _ := json.Marshal(m)
   128  	return string(s)
   129  }
   130  
   131  func (r RlimitStat) String() string {
   132  	s, _ := json.Marshal(r)
   133  	return string(s)
   134  }
   135  
   136  func (i IOCountersStat) String() string {
   137  	s, _ := json.Marshal(i)
   138  	return string(s)
   139  }
   140  
   141  func (p NumCtxSwitchesStat) String() string {
   142  	s, _ := json.Marshal(p)
   143  	return string(s)
   144  }
   145  
   146  // Pids returns a slice of process ID list which are running now.
   147  func Pids() ([]int32, error) {
   148  	return PidsWithContext(context.Background())
   149  }
   150  
   151  func PidsWithContext(ctx context.Context) ([]int32, error) {
   152  	pids, err := pidsWithContext(ctx)
   153  	sort.Slice(pids, func(i, j int) bool { return pids[i] < pids[j] })
   154  	return pids, err
   155  }
   156  
   157  // Processes returns a slice of pointers to Process structs for all
   158  // currently running processes.
   159  func Processes() ([]*Process, error) {
   160  	return ProcessesWithContext(context.Background())
   161  }
   162  
   163  // NewProcess creates a new Process instance, it only stores the pid and
   164  // checks that the process exists. Other method on Process can be used
   165  // to get more information about the process. An error will be returned
   166  // if the process does not exist.
   167  func NewProcess(pid int32) (*Process, error) {
   168  	return NewProcessWithContext(context.Background(), pid)
   169  }
   170  
   171  func NewProcessWithContext(ctx context.Context, pid int32) (*Process, error) {
   172  	p := &Process{
   173  		Pid: pid,
   174  	}
   175  
   176  	exists, err := PidExistsWithContext(ctx, pid)
   177  	if err != nil {
   178  		return p, err
   179  	}
   180  	if !exists {
   181  		return p, ErrorProcessNotRunning
   182  	}
   183  	_, _ = p.CreateTimeWithContext(ctx)
   184  	return p, nil
   185  }
   186  
   187  func PidExists(pid int32) (bool, error) {
   188  	return PidExistsWithContext(context.Background(), pid)
   189  }
   190  
   191  // Background returns true if the process is in background, false otherwise.
   192  func (p *Process) Background() (bool, error) {
   193  	return p.BackgroundWithContext(context.Background())
   194  }
   195  
   196  func (p *Process) BackgroundWithContext(ctx context.Context) (bool, error) {
   197  	fg, err := p.ForegroundWithContext(ctx)
   198  	if err != nil {
   199  		return false, err
   200  	}
   201  	return !fg, err
   202  }
   203  
   204  // If interval is 0, return difference from last call(non-blocking).
   205  // If interval > 0, wait interval sec and return diffrence between start and end.
   206  func (p *Process) Percent(interval time.Duration) (float64, error) {
   207  	return p.PercentWithContext(context.Background(), interval)
   208  }
   209  
   210  func (p *Process) PercentWithContext(ctx context.Context, interval time.Duration) (float64, error) {
   211  	cpuTimes, err := p.TimesWithContext(ctx)
   212  	if err != nil {
   213  		return 0, err
   214  	}
   215  	now := time.Now()
   216  
   217  	if interval > 0 {
   218  		p.lastCPUTimes = cpuTimes
   219  		p.lastCPUTime = now
   220  		if err := common.Sleep(ctx, interval); err != nil {
   221  			return 0, err
   222  		}
   223  		cpuTimes, err = p.TimesWithContext(ctx)
   224  		now = time.Now()
   225  		if err != nil {
   226  			return 0, err
   227  		}
   228  	} else {
   229  		if p.lastCPUTimes == nil {
   230  			// invoked first time
   231  			p.lastCPUTimes = cpuTimes
   232  			p.lastCPUTime = now
   233  			return 0, nil
   234  		}
   235  	}
   236  
   237  	numcpu := runtime.NumCPU()
   238  	delta := (now.Sub(p.lastCPUTime).Seconds()) * float64(numcpu)
   239  	ret := calculatePercent(p.lastCPUTimes, cpuTimes, delta, numcpu)
   240  	p.lastCPUTimes = cpuTimes
   241  	p.lastCPUTime = now
   242  	return ret, nil
   243  }
   244  
   245  // IsRunning returns whether the process is still running or not.
   246  func (p *Process) IsRunning() (bool, error) {
   247  	return p.IsRunningWithContext(context.Background())
   248  }
   249  
   250  func (p *Process) IsRunningWithContext(ctx context.Context) (bool, error) {
   251  	createTime, err := p.CreateTimeWithContext(ctx)
   252  	if err != nil {
   253  		return false, err
   254  	}
   255  	p2, err := NewProcessWithContext(ctx, p.Pid)
   256  	if err == ErrorProcessNotRunning {
   257  		return false, nil
   258  	}
   259  	createTime2, err := p2.CreateTimeWithContext(ctx)
   260  	if err != nil {
   261  		return false, err
   262  	}
   263  	return createTime == createTime2, nil
   264  }
   265  
   266  // CreateTime returns created time of the process in milliseconds since the epoch, in UTC.
   267  func (p *Process) CreateTime() (int64, error) {
   268  	return p.CreateTimeWithContext(context.Background())
   269  }
   270  
   271  func (p *Process) CreateTimeWithContext(ctx context.Context) (int64, error) {
   272  	if p.createTime != 0 {
   273  		return p.createTime, nil
   274  	}
   275  	createTime, err := p.createTimeWithContext(ctx)
   276  	p.createTime = createTime
   277  	return p.createTime, err
   278  }
   279  
   280  func calculatePercent(t1, t2 *cpu.TimesStat, delta float64, numcpu int) float64 {
   281  	if delta == 0 {
   282  		return 0
   283  	}
   284  	delta_proc := t2.Total() - t1.Total()
   285  	overall_percent := ((delta_proc / delta) * 100) * float64(numcpu)
   286  	return overall_percent
   287  }
   288  
   289  // MemoryPercent returns how many percent of the total RAM this process uses
   290  func (p *Process) MemoryPercent() (float32, error) {
   291  	return p.MemoryPercentWithContext(context.Background())
   292  }
   293  
   294  func (p *Process) MemoryPercentWithContext(ctx context.Context) (float32, error) {
   295  	machineMemory, err := mem.VirtualMemoryWithContext(ctx)
   296  	if err != nil {
   297  		return 0, err
   298  	}
   299  	total := machineMemory.Total
   300  
   301  	processMemory, err := p.MemoryInfoWithContext(ctx)
   302  	if err != nil {
   303  		return 0, err
   304  	}
   305  	used := processMemory.RSS
   306  
   307  	return 100 * float32(used) / float32(total), nil
   308  }
   309  
   310  // CPUPercent returns how many percent of the CPU time this process uses
   311  func (p *Process) CPUPercent() (float64, error) {
   312  	return p.CPUPercentWithContext(context.Background())
   313  }
   314  
   315  func (p *Process) CPUPercentWithContext(ctx context.Context) (float64, error) {
   316  	crt_time, err := p.createTimeWithContext(ctx)
   317  	if err != nil {
   318  		return 0, err
   319  	}
   320  
   321  	cput, err := p.TimesWithContext(ctx)
   322  	if err != nil {
   323  		return 0, err
   324  	}
   325  
   326  	created := time.Unix(0, crt_time*int64(time.Millisecond))
   327  	totalTime := time.Since(created).Seconds()
   328  	if totalTime <= 0 {
   329  		return 0, nil
   330  	}
   331  
   332  	return 100 * cput.Total() / totalTime, nil
   333  }
   334  
   335  // Groups returns all group IDs(include supplementary groups) of the process as a slice of the int
   336  func (p *Process) Groups() ([]int32, error) {
   337  	return p.GroupsWithContext(context.Background())
   338  }
   339  
   340  // Ppid returns Parent Process ID of the process.
   341  func (p *Process) Ppid() (int32, error) {
   342  	return p.PpidWithContext(context.Background())
   343  }
   344  
   345  // Name returns name of the process.
   346  func (p *Process) Name() (string, error) {
   347  	return p.NameWithContext(context.Background())
   348  }
   349  
   350  // Exe returns executable path of the process.
   351  func (p *Process) Exe() (string, error) {
   352  	return p.ExeWithContext(context.Background())
   353  }
   354  
   355  // Cmdline returns the command line arguments of the process as a string with
   356  // each argument separated by 0x20 ascii character.
   357  func (p *Process) Cmdline() (string, error) {
   358  	return p.CmdlineWithContext(context.Background())
   359  }
   360  
   361  // CmdlineSlice returns the command line arguments of the process as a slice with each
   362  // element being an argument.
   363  func (p *Process) CmdlineSlice() ([]string, error) {
   364  	return p.CmdlineSliceWithContext(context.Background())
   365  }
   366  
   367  // Cwd returns current working directory of the process.
   368  func (p *Process) Cwd() (string, error) {
   369  	return p.CwdWithContext(context.Background())
   370  }
   371  
   372  // Parent returns parent Process of the process.
   373  func (p *Process) Parent() (*Process, error) {
   374  	return p.ParentWithContext(context.Background())
   375  }
   376  
   377  // Status returns the process status.
   378  // Return value could be one of these.
   379  // R: Running S: Sleep T: Stop I: Idle
   380  // Z: Zombie W: Wait L: Lock
   381  // The character is same within all supported platforms.
   382  func (p *Process) Status() (string, error) {
   383  	return p.StatusWithContext(context.Background())
   384  }
   385  
   386  // Foreground returns true if the process is in foreground, false otherwise.
   387  func (p *Process) Foreground() (bool, error) {
   388  	return p.ForegroundWithContext(context.Background())
   389  }
   390  
   391  // Uids returns user ids of the process as a slice of the int
   392  func (p *Process) Uids() ([]int32, error) {
   393  	return p.UidsWithContext(context.Background())
   394  }
   395  
   396  // Gids returns group ids of the process as a slice of the int
   397  func (p *Process) Gids() ([]int32, error) {
   398  	return p.GidsWithContext(context.Background())
   399  }
   400  
   401  // Terminal returns a terminal which is associated with the process.
   402  func (p *Process) Terminal() (string, error) {
   403  	return p.TerminalWithContext(context.Background())
   404  }
   405  
   406  // Nice returns a nice value (priority).
   407  func (p *Process) Nice() (int32, error) {
   408  	return p.NiceWithContext(context.Background())
   409  }
   410  
   411  // IOnice returns process I/O nice value (priority).
   412  func (p *Process) IOnice() (int32, error) {
   413  	return p.IOniceWithContext(context.Background())
   414  }
   415  
   416  // Rlimit returns Resource Limits.
   417  func (p *Process) Rlimit() ([]RlimitStat, error) {
   418  	return p.RlimitWithContext(context.Background())
   419  }
   420  
   421  // RlimitUsage returns Resource Limits.
   422  // If gatherUsed is true, the currently used value will be gathered and added
   423  // to the resulting RlimitStat.
   424  func (p *Process) RlimitUsage(gatherUsed bool) ([]RlimitStat, error) {
   425  	return p.RlimitUsageWithContext(context.Background(), gatherUsed)
   426  }
   427  
   428  // IOCounters returns IO Counters.
   429  func (p *Process) IOCounters() (*IOCountersStat, error) {
   430  	return p.IOCountersWithContext(context.Background())
   431  }
   432  
   433  // NumCtxSwitches returns the number of the context switches of the process.
   434  func (p *Process) NumCtxSwitches() (*NumCtxSwitchesStat, error) {
   435  	return p.NumCtxSwitchesWithContext(context.Background())
   436  }
   437  
   438  // NumFDs returns the number of File Descriptors used by the process.
   439  func (p *Process) NumFDs() (int32, error) {
   440  	return p.NumFDsWithContext(context.Background())
   441  }
   442  
   443  // NumThreads returns the number of threads used by the process.
   444  func (p *Process) NumThreads() (int32, error) {
   445  	return p.NumThreadsWithContext(context.Background())
   446  }
   447  
   448  func (p *Process) Threads() (map[int32]*cpu.TimesStat, error) {
   449  	return p.ThreadsWithContext(context.Background())
   450  }
   451  
   452  // Times returns CPU times of the process.
   453  func (p *Process) Times() (*cpu.TimesStat, error) {
   454  	return p.TimesWithContext(context.Background())
   455  }
   456  
   457  // CPUAffinity returns CPU affinity of the process.
   458  func (p *Process) CPUAffinity() ([]int32, error) {
   459  	return p.CPUAffinityWithContext(context.Background())
   460  }
   461  
   462  // MemoryInfo returns generic process memory information,
   463  // such as RSS and VMS.
   464  func (p *Process) MemoryInfo() (*MemoryInfoStat, error) {
   465  	return p.MemoryInfoWithContext(context.Background())
   466  }
   467  
   468  // MemoryInfoEx returns platform-specific process memory information.
   469  func (p *Process) MemoryInfoEx() (*MemoryInfoExStat, error) {
   470  	return p.MemoryInfoExWithContext(context.Background())
   471  }
   472  
   473  // PageFaults returns the process's page fault counters.
   474  func (p *Process) PageFaults() (*PageFaultsStat, error) {
   475  	return p.PageFaultsWithContext(context.Background())
   476  }
   477  
   478  // Children returns the children of the process represented as a slice
   479  // of pointers to Process type.
   480  func (p *Process) Children() ([]*Process, error) {
   481  	return p.ChildrenWithContext(context.Background())
   482  }
   483  
   484  // OpenFiles returns a slice of OpenFilesStat opend by the process.
   485  // OpenFilesStat includes a file path and file descriptor.
   486  func (p *Process) OpenFiles() ([]OpenFilesStat, error) {
   487  	return p.OpenFilesWithContext(context.Background())
   488  }
   489  
   490  // Connections returns a slice of net.ConnectionStat used by the process.
   491  // This returns all kind of the connection. This means TCP, UDP or UNIX.
   492  func (p *Process) Connections() ([]net.ConnectionStat, error) {
   493  	return p.ConnectionsWithContext(context.Background())
   494  }
   495  
   496  // ConnectionsMax returns a slice of net.ConnectionStat used by the process at most `max`.
   497  func (p *Process) ConnectionsMax(max int) ([]net.ConnectionStat, error) {
   498  	return p.ConnectionsMaxWithContext(context.Background(), max)
   499  }
   500  
   501  // NetIOCounters returns NetIOCounters of the process.
   502  func (p *Process) NetIOCounters(pernic bool) ([]net.IOCountersStat, error) {
   503  	return p.NetIOCountersWithContext(context.Background(), pernic)
   504  }
   505  
   506  // MemoryMaps get memory maps from /proc/(pid)/smaps
   507  func (p *Process) MemoryMaps(grouped bool) (*[]MemoryMapsStat, error) {
   508  	return p.MemoryMapsWithContext(context.Background(), grouped)
   509  }
   510  
   511  // Tgid returns thread group id of the process.
   512  func (p *Process) Tgid() (int32, error) {
   513  	return p.TgidWithContext(context.Background())
   514  }
   515  
   516  // SendSignal sends a unix.Signal to the process.
   517  func (p *Process) SendSignal(sig syscall.Signal) error {
   518  	return p.SendSignalWithContext(context.Background(), sig)
   519  }
   520  
   521  // Suspend sends SIGSTOP to the process.
   522  func (p *Process) Suspend() error {
   523  	return p.SuspendWithContext(context.Background())
   524  }
   525  
   526  // Resume sends SIGCONT to the process.
   527  func (p *Process) Resume() error {
   528  	return p.ResumeWithContext(context.Background())
   529  }
   530  
   531  // Terminate sends SIGTERM to the process.
   532  func (p *Process) Terminate() error {
   533  	return p.TerminateWithContext(context.Background())
   534  }
   535  
   536  // Kill sends SIGKILL to the process.
   537  func (p *Process) Kill() error {
   538  	return p.KillWithContext(context.Background())
   539  }
   540  
   541  // Username returns a username of the process.
   542  func (p *Process) Username() (string, error) {
   543  	return p.UsernameWithContext(context.Background())
   544  }
   545  
   546  // Environ returns the environment variables of the process.
   547  func (p *Process) Environ() ([]string, error) {
   548  	return p.EnvironWithContext(context.Background())
   549  }