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

     1  //go:build darwin
     2  
     3  package process
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"github.com/isyscore/isc-gobase/system/common"
     9  	"github.com/isyscore/isc-gobase/system/cpu"
    10  	"github.com/isyscore/isc-gobase/system/net"
    11  	"github.com/tklauser/go-sysconf"
    12  	"golang.org/x/sys/unix"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"strconv"
    16  	"strings"
    17  	"time"
    18  )
    19  
    20  // copied from sys/sysctl.h
    21  const (
    22  	CTLKern          = 1  // "high kernel": proc, limits
    23  	KernProc         = 14 // struct: process entries
    24  	KernProcPID      = 1  // by process id
    25  	KernProcProc     = 8  // only return procs
    26  	KernProcAll      = 0  // everything
    27  	KernProcPathname = 12 // path to executable
    28  )
    29  
    30  var ClockTicks = 100 // default value
    31  
    32  func init() {
    33  	clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
    34  	// ignore errors
    35  	if err == nil {
    36  		ClockTicks = int(clkTck)
    37  	}
    38  }
    39  
    40  type _Ctype_struct___0 struct {
    41  	Pad uint64
    42  }
    43  
    44  func pidsWithContext(ctx context.Context) ([]int32, error) {
    45  	var ret []int32
    46  
    47  	pids, err := callPsWithContext(ctx, "pid", 0, false, false)
    48  	if err != nil {
    49  		return ret, err
    50  	}
    51  
    52  	for _, pid := range pids {
    53  		v, err := strconv.Atoi(pid[0])
    54  		if err != nil {
    55  			return ret, err
    56  		}
    57  		ret = append(ret, int32(v))
    58  	}
    59  
    60  	return ret, nil
    61  }
    62  
    63  func (p *Process) PpidWithContext(ctx context.Context) (int32, error) {
    64  	r, err := callPsWithContext(ctx, "ppid", p.Pid, false, false)
    65  	if err != nil {
    66  		return 0, err
    67  	}
    68  
    69  	v, err := strconv.Atoi(r[0][0])
    70  	if err != nil {
    71  		return 0, err
    72  	}
    73  
    74  	return int32(v), err
    75  }
    76  
    77  func (p *Process) NameWithContext(ctx context.Context) (string, error) {
    78  	k, err := p.getKProc()
    79  	if err != nil {
    80  		return "", err
    81  	}
    82  	name := common.IntToString(k.Proc.P_comm[:])
    83  
    84  	if len(name) >= 15 {
    85  		cmdName, err := p.cmdNameWithContext(ctx)
    86  		if err != nil {
    87  			return "", err
    88  		}
    89  		if len(cmdName) > 0 {
    90  			extendedName := filepath.Base(cmdName[0])
    91  			if strings.HasPrefix(extendedName, p.name) {
    92  				name = extendedName
    93  			} else {
    94  				name = cmdName[0]
    95  			}
    96  		}
    97  	}
    98  
    99  	return name, nil
   100  }
   101  
   102  func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) {
   103  	r, err := callPsWithContext(ctx, "command", p.Pid, false, false)
   104  	if err != nil {
   105  		return "", err
   106  	}
   107  	return strings.Join(r[0], " "), err
   108  }
   109  
   110  // cmdNameWithContext returns the command name (including spaces) without any arguments
   111  func (p *Process) cmdNameWithContext(ctx context.Context) ([]string, error) {
   112  	r, err := callPsWithContext(ctx, "command", p.Pid, false, true)
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	return r[0], err
   117  }
   118  
   119  // CmdlineSliceWithContext returns the command line arguments of the process as a slice with each
   120  // element being an argument. Because of current deficiencies in the way that the command
   121  // line arguments are found, single arguments that have spaces in the will actually be
   122  // reported as two separate items. In order to do something better CGO would be needed
   123  // to use the native darwin functions.
   124  func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) {
   125  	r, err := callPsWithContext(ctx, "command", p.Pid, false, false)
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  	return r[0], err
   130  }
   131  
   132  func (p *Process) createTimeWithContext(ctx context.Context) (int64, error) {
   133  	r, err := callPsWithContext(ctx, "etime", p.Pid, false, false)
   134  	if err != nil {
   135  		return 0, err
   136  	}
   137  
   138  	elapsedSegments := strings.Split(strings.Replace(r[0][0], "-", ":", 1), ":")
   139  	var elapsedDurations []time.Duration
   140  	for i := len(elapsedSegments) - 1; i >= 0; i-- {
   141  		p, err := strconv.ParseInt(elapsedSegments[i], 10, 0)
   142  		if err != nil {
   143  			return 0, err
   144  		}
   145  		elapsedDurations = append(elapsedDurations, time.Duration(p))
   146  	}
   147  
   148  	var elapsed = elapsedDurations[0] * time.Second
   149  	if len(elapsedDurations) > 1 {
   150  		elapsed += elapsedDurations[1] * time.Minute
   151  	}
   152  	if len(elapsedDurations) > 2 {
   153  		elapsed += elapsedDurations[2] * time.Hour
   154  	}
   155  	if len(elapsedDurations) > 3 {
   156  		elapsed += elapsedDurations[3] * time.Hour * 24
   157  	}
   158  
   159  	start := time.Now().Add(-elapsed)
   160  	return start.Unix() * 1000, nil
   161  }
   162  
   163  func (p *Process) ParentWithContext(ctx context.Context) (*Process, error) {
   164  	out, err := common.CallLsofWithContext(ctx, invoke, p.Pid, "-FR")
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  	for _, line := range out {
   169  		if len(line) >= 1 && line[0] == 'R' {
   170  			v, err := strconv.Atoi(line[1:])
   171  			if err != nil {
   172  				return nil, err
   173  			}
   174  			return NewProcessWithContext(ctx, int32(v))
   175  		}
   176  	}
   177  	return nil, fmt.Errorf("could not find parent line")
   178  }
   179  
   180  func (p *Process) StatusWithContext(ctx context.Context) (string, error) {
   181  	r, err := callPsWithContext(ctx, "state", p.Pid, false, false)
   182  	if err != nil {
   183  		return "", err
   184  	}
   185  
   186  	return r[0][0][0:1], err
   187  }
   188  
   189  func (p *Process) ForegroundWithContext(ctx context.Context) (bool, error) {
   190  	pid := p.Pid
   191  	ps, err := exec.LookPath("ps")
   192  	if err != nil {
   193  		return false, err
   194  	}
   195  	out, err := invoke.CommandWithContext(ctx, ps, "-o", "stat=", "-p", strconv.Itoa(int(pid)))
   196  	if err != nil {
   197  		return false, err
   198  	}
   199  	return strings.IndexByte(string(out), '+') != -1, nil
   200  }
   201  
   202  func (p *Process) UidsWithContext(ctx context.Context) ([]int32, error) {
   203  	k, err := p.getKProc()
   204  	if err != nil {
   205  		return nil, err
   206  	}
   207  
   208  	// See: http://unix.superglobalmegacorp.com/Net2/newsrc/sys/ucred.h.html
   209  	userEffectiveUID := int32(k.Eproc.Ucred.UID)
   210  
   211  	return []int32{userEffectiveUID}, nil
   212  }
   213  
   214  func (p *Process) GidsWithContext(ctx context.Context) ([]int32, error) {
   215  	k, err := p.getKProc()
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  
   220  	gids := make([]int32, 0, 3)
   221  	gids = append(gids, int32(k.Eproc.Pcred.P_rgid), int32(k.Eproc.Ucred.Ngroups), int32(k.Eproc.Pcred.P_svgid))
   222  
   223  	return gids, nil
   224  }
   225  
   226  func (p *Process) GroupsWithContext(ctx context.Context) ([]int32, error) {
   227  	return nil, common.ErrNotImplementedError
   228  	// k, err := p.getKProc()
   229  	// if err != nil {
   230  	// 	return nil, err
   231  	// }
   232  
   233  	// groups := make([]int32, k.Eproc.Ucred.Ngroups)
   234  	// for i := int16(0); i < k.Eproc.Ucred.Ngroups; i++ {
   235  	// 	groups[i] = int32(k.Eproc.Ucred.Groups[i])
   236  	// }
   237  
   238  	// return groups, nil
   239  }
   240  
   241  func (p *Process) TerminalWithContext(ctx context.Context) (string, error) {
   242  	return "", common.ErrNotImplementedError
   243  	/*
   244  		k, err := p.getKProc()
   245  		if err != nil {
   246  			return "", err
   247  		}
   248  
   249  		ttyNr := uint64(k.Eproc.Tdev)
   250  		termmap, err := getTerminalMap()
   251  		if err != nil {
   252  			return "", err
   253  		}
   254  
   255  		return termmap[ttyNr], nil
   256  	*/
   257  }
   258  
   259  func (p *Process) NiceWithContext(ctx context.Context) (int32, error) {
   260  	k, err := p.getKProc()
   261  	if err != nil {
   262  		return 0, err
   263  	}
   264  	return int32(k.Proc.P_nice), nil
   265  }
   266  
   267  func (p *Process) IOCountersWithContext(ctx context.Context) (*IOCountersStat, error) {
   268  	return nil, common.ErrNotImplementedError
   269  }
   270  
   271  func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) {
   272  	r, err := callPsWithContext(ctx, "utime,stime", p.Pid, true, false)
   273  	if err != nil {
   274  		return 0, err
   275  	}
   276  	return int32(len(r)), nil
   277  }
   278  
   279  func convertCPUTimes(s string) (ret float64, err error) {
   280  	var t int
   281  	var _tmp string
   282  	if strings.Contains(s, ":") {
   283  		_t := strings.Split(s, ":")
   284  		switch len(_t) {
   285  		case 3:
   286  			hour, err := strconv.Atoi(_t[0])
   287  			if err != nil {
   288  				return ret, err
   289  			}
   290  			t += hour * 60 * 60 * ClockTicks
   291  
   292  			mins, err := strconv.Atoi(_t[1])
   293  			if err != nil {
   294  				return ret, err
   295  			}
   296  			t += mins * 60 * ClockTicks
   297  			_tmp = _t[2]
   298  		case 2:
   299  			mins, err := strconv.Atoi(_t[0])
   300  			if err != nil {
   301  				return ret, err
   302  			}
   303  			t += mins * 60 * ClockTicks
   304  			_tmp = _t[1]
   305  		case 1, 0:
   306  			_tmp = s
   307  		default:
   308  			return ret, fmt.Errorf("wrong cpu time string")
   309  		}
   310  	} else {
   311  		_tmp = s
   312  	}
   313  
   314  	_t := strings.Split(_tmp, ".")
   315  	if err != nil {
   316  		return ret, err
   317  	}
   318  	h, err := strconv.Atoi(_t[0])
   319  	t += h * ClockTicks
   320  	h, err = strconv.Atoi(_t[1])
   321  	t += h
   322  	return float64(t) / float64(ClockTicks), nil
   323  }
   324  
   325  func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) {
   326  	r, err := callPsWithContext(ctx, "utime,stime", p.Pid, false, false)
   327  
   328  	if err != nil {
   329  		return nil, err
   330  	}
   331  
   332  	utime, err := convertCPUTimes(r[0][0])
   333  	if err != nil {
   334  		return nil, err
   335  	}
   336  	stime, err := convertCPUTimes(r[0][1])
   337  	if err != nil {
   338  		return nil, err
   339  	}
   340  
   341  	ret := &cpu.TimesStat{
   342  		CPU:    "cpu",
   343  		User:   utime,
   344  		System: stime,
   345  	}
   346  	return ret, nil
   347  }
   348  
   349  func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, error) {
   350  	r, err := callPsWithContext(ctx, "rss,vsize,pagein", p.Pid, false, false)
   351  	if err != nil {
   352  		return nil, err
   353  	}
   354  	rss, err := strconv.Atoi(r[0][0])
   355  	if err != nil {
   356  		return nil, err
   357  	}
   358  	vms, err := strconv.Atoi(r[0][1])
   359  	if err != nil {
   360  		return nil, err
   361  	}
   362  	pagein, err := strconv.Atoi(r[0][2])
   363  	if err != nil {
   364  		return nil, err
   365  	}
   366  
   367  	ret := &MemoryInfoStat{
   368  		RSS:  uint64(rss) * 1024,
   369  		VMS:  uint64(vms) * 1024,
   370  		Swap: uint64(pagein),
   371  	}
   372  
   373  	return ret, nil
   374  }
   375  
   376  func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) {
   377  	pids, err := common.CallPgrepWithContext(ctx, invoke, p.Pid)
   378  	if err != nil {
   379  		return nil, err
   380  	}
   381  	ret := make([]*Process, 0, len(pids))
   382  	for _, pid := range pids {
   383  		np, err := NewProcessWithContext(ctx, pid)
   384  		if err != nil {
   385  			return nil, err
   386  		}
   387  		ret = append(ret, np)
   388  	}
   389  	return ret, nil
   390  }
   391  
   392  func (p *Process) ConnectionsWithContext(ctx context.Context) ([]net.ConnectionStat, error) {
   393  	return net.ConnectionsPidWithContext(ctx, "all", p.Pid)
   394  }
   395  
   396  func (p *Process) ConnectionsMaxWithContext(ctx context.Context, max int) ([]net.ConnectionStat, error) {
   397  	return net.ConnectionsPidMaxWithContext(ctx, "all", p.Pid, max)
   398  }
   399  
   400  func ProcessesWithContext(ctx context.Context) ([]*Process, error) {
   401  	var out []*Process
   402  
   403  	pids, err := PidsWithContext(ctx)
   404  	if err != nil {
   405  		return out, err
   406  	}
   407  
   408  	for _, pid := range pids {
   409  		p, err := NewProcessWithContext(ctx, pid)
   410  		if err != nil {
   411  			continue
   412  		}
   413  		out = append(out, p)
   414  	}
   415  
   416  	return out, nil
   417  }
   418  
   419  // Returns a proc as defined here:
   420  // http://unix.superglobalmegacorp.com/Net2/newsrc/sys/kinfo_proc.h.html
   421  func (p *Process) getKProc() (*KinfoProc, error) {
   422  	buf, err := unix.SysctlRaw("kern.proc.pid", int(p.Pid))
   423  	if err != nil {
   424  		return nil, err
   425  	}
   426  	k, err := parseKinfoProc(buf)
   427  	if err != nil {
   428  		return nil, err
   429  	}
   430  
   431  	return &k, nil
   432  }
   433  
   434  // call ps command.
   435  // Return value deletes Header line(you must not input wrong arg).
   436  // And split by space. Caller have responsibility to manage.
   437  // If passed arg pid is 0, get information from all process.
   438  func callPsWithContext(ctx context.Context, arg string, pid int32, threadOption bool, nameOption bool) ([][]string, error) {
   439  	bin, err := exec.LookPath("ps")
   440  	if err != nil {
   441  		return [][]string{}, err
   442  	}
   443  
   444  	var cmd []string
   445  	if pid == 0 { // will get from all processes.
   446  		cmd = []string{"-ax", "-o", arg}
   447  	} else if threadOption {
   448  		cmd = []string{"-x", "-o", arg, "-M", "-p", strconv.Itoa(int(pid))}
   449  	} else {
   450  		cmd = []string{"-x", "-o", arg, "-p", strconv.Itoa(int(pid))}
   451  	}
   452  
   453  	if nameOption {
   454  		cmd = append(cmd, "-c")
   455  	}
   456  	out, err := invoke.CommandWithContext(ctx, bin, cmd...)
   457  	if err != nil {
   458  		return [][]string{}, err
   459  	}
   460  	lines := strings.Split(string(out), "\n")
   461  
   462  	var ret [][]string
   463  	for _, l := range lines[1:] {
   464  
   465  		var lr []string
   466  		if nameOption {
   467  			lr = append(lr, l)
   468  		} else {
   469  			for _, r := range strings.Split(l, " ") {
   470  				if r == "" {
   471  					continue
   472  				}
   473  				lr = append(lr, strings.TrimSpace(r))
   474  			}
   475  		}
   476  
   477  		if len(lr) != 0 {
   478  			ret = append(ret, lr)
   479  		}
   480  	}
   481  
   482  	return ret, nil
   483  }