...

Source file src/github.com/shirou/gopsutil/process/process_darwin.go

Documentation: github.com/shirou/gopsutil/process

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

View as plain text