github.com/gofiber/fiber/v2@v2.47.0/internal/gopsutil/process/process_darwin.go (about)

     1  //go:build darwin
     2  // +build darwin
     3  
     4  package process
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"encoding/binary"
    10  	"fmt"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"strconv"
    14  	"strings"
    15  	"time"
    16  	"unsafe"
    17  
    18  	"github.com/gofiber/fiber/v2/internal/gopsutil/common"
    19  	"github.com/gofiber/fiber/v2/internal/gopsutil/cpu"
    20  	"github.com/gofiber/fiber/v2/internal/gopsutil/net"
    21  	"golang.org/x/sys/unix"
    22  )
    23  
    24  // copied from sys/sysctl.h
    25  const (
    26  	CTLKern          = 1  // "high kernel": proc, limits
    27  	KernProc         = 14 // struct: process entries
    28  	KernProcPID      = 1  // by process id
    29  	KernProcProc     = 8  // only return procs
    30  	KernProcAll      = 0  // everything
    31  	KernProcPathname = 12 // path to executable
    32  )
    33  
    34  const (
    35  	ClockTicks = 100 // C.sysconf(C._SC_CLK_TCK)
    36  )
    37  
    38  type _Ctype_struct___0 struct {
    39  	Pad uint64
    40  }
    41  
    42  // MemoryInfoExStat is different between OSes
    43  type MemoryInfoExStat struct {
    44  }
    45  
    46  type MemoryMapsStat struct {
    47  }
    48  
    49  func pidsWithContext(ctx context.Context) ([]int32, error) {
    50  	var ret []int32
    51  
    52  	pids, err := callPsWithContext(ctx, "pid", 0, false)
    53  	if err != nil {
    54  		return ret, err
    55  	}
    56  
    57  	for _, pid := range pids {
    58  		v, err := strconv.Atoi(pid[0])
    59  		if err != nil {
    60  			return ret, err
    61  		}
    62  		ret = append(ret, int32(v))
    63  	}
    64  
    65  	return ret, nil
    66  }
    67  
    68  func (p *Process) Ppid() (int32, error) {
    69  	return p.PpidWithContext(context.Background())
    70  }
    71  
    72  func (p *Process) PpidWithContext(ctx context.Context) (int32, error) {
    73  	r, err := callPsWithContext(ctx, "ppid", p.Pid, false)
    74  	if err != nil {
    75  		return 0, err
    76  	}
    77  
    78  	v, err := strconv.Atoi(r[0][0])
    79  	if err != nil {
    80  		return 0, err
    81  	}
    82  
    83  	return int32(v), err
    84  }
    85  func (p *Process) Name() (string, error) {
    86  	return p.NameWithContext(context.Background())
    87  }
    88  
    89  func (p *Process) NameWithContext(ctx context.Context) (string, error) {
    90  	k, err := p.getKProc()
    91  	if err != nil {
    92  		return "", err
    93  	}
    94  	name := common.IntToString(k.Proc.P_comm[:])
    95  
    96  	if len(name) >= 15 {
    97  		cmdlineSlice, err := p.CmdlineSliceWithContext(ctx)
    98  		if err != nil {
    99  			return "", err
   100  		}
   101  		if len(cmdlineSlice) > 0 {
   102  			extendedName := filepath.Base(cmdlineSlice[0])
   103  			if strings.HasPrefix(extendedName, p.name) {
   104  				name = extendedName
   105  			} else {
   106  				name = cmdlineSlice[0]
   107  			}
   108  		}
   109  	}
   110  
   111  	return name, nil
   112  }
   113  func (p *Process) Tgid() (int32, error) {
   114  	return 0, common.ErrNotImplementedError
   115  }
   116  func (p *Process) Exe() (string, error) {
   117  	return p.ExeWithContext(context.Background())
   118  }
   119  
   120  // Cmdline returns the command line arguments of the process as a string with
   121  // each argument separated by 0x20 ascii character.
   122  func (p *Process) Cmdline() (string, error) {
   123  	return p.CmdlineWithContext(context.Background())
   124  }
   125  
   126  func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) {
   127  	r, err := callPsWithContext(ctx, "command", p.Pid, false)
   128  	if err != nil {
   129  		return "", err
   130  	}
   131  	return strings.Join(r[0], " "), err
   132  }
   133  
   134  // CmdlineSlice returns the command line arguments of the process as a slice with each
   135  // element being an argument. Because of current deficiencies in the way that the command
   136  // line arguments are found, single arguments that have spaces in the will actually be
   137  // reported as two separate items. In order to do something better CGO would be needed
   138  // to use the native darwin functions.
   139  func (p *Process) CmdlineSlice() ([]string, error) {
   140  	return p.CmdlineSliceWithContext(context.Background())
   141  }
   142  
   143  func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) {
   144  	r, err := callPsWithContext(ctx, "command", p.Pid, false)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  	return r[0], err
   149  }
   150  
   151  func (p *Process) createTimeWithContext(ctx context.Context) (int64, error) {
   152  	r, err := callPsWithContext(ctx, "etime", p.Pid, false)
   153  	if err != nil {
   154  		return 0, err
   155  	}
   156  
   157  	elapsedSegments := strings.Split(strings.Replace(r[0][0], "-", ":", 1), ":")
   158  	var elapsedDurations []time.Duration
   159  	for i := len(elapsedSegments) - 1; i >= 0; i-- {
   160  		p, err := strconv.ParseInt(elapsedSegments[i], 10, 0)
   161  		if err != nil {
   162  			return 0, err
   163  		}
   164  		elapsedDurations = append(elapsedDurations, time.Duration(p))
   165  	}
   166  
   167  	var elapsed = time.Duration(elapsedDurations[0]) * time.Second
   168  	if len(elapsedDurations) > 1 {
   169  		elapsed += time.Duration(elapsedDurations[1]) * time.Minute
   170  	}
   171  	if len(elapsedDurations) > 2 {
   172  		elapsed += time.Duration(elapsedDurations[2]) * time.Hour
   173  	}
   174  	if len(elapsedDurations) > 3 {
   175  		elapsed += time.Duration(elapsedDurations[3]) * time.Hour * 24
   176  	}
   177  
   178  	start := time.Now().Add(-elapsed)
   179  	return start.Unix() * 1000, nil
   180  }
   181  func (p *Process) Cwd() (string, error) {
   182  	return p.CwdWithContext(context.Background())
   183  }
   184  
   185  func (p *Process) CwdWithContext(ctx context.Context) (string, error) {
   186  	return "", common.ErrNotImplementedError
   187  }
   188  func (p *Process) Parent() (*Process, error) {
   189  	return p.ParentWithContext(context.Background())
   190  }
   191  
   192  func (p *Process) ParentWithContext(ctx context.Context) (*Process, error) {
   193  	rr, err := common.CallLsofWithContext(ctx, invoke, p.Pid, "-FR")
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  	for _, r := range rr {
   198  		if strings.HasPrefix(r, "p") { // skip if process
   199  			continue
   200  		}
   201  		l := string(r)
   202  		v, err := strconv.Atoi(strings.Replace(l, "R", "", 1))
   203  		if err != nil {
   204  			return nil, err
   205  		}
   206  		return NewProcess(int32(v))
   207  	}
   208  	return nil, fmt.Errorf("could not find parent line")
   209  }
   210  func (p *Process) Status() (string, error) {
   211  	return p.StatusWithContext(context.Background())
   212  }
   213  
   214  func (p *Process) StatusWithContext(ctx context.Context) (string, error) {
   215  	r, err := callPsWithContext(ctx, "state", p.Pid, false)
   216  	if err != nil {
   217  		return "", err
   218  	}
   219  
   220  	return r[0][0][0:1], err
   221  }
   222  
   223  func (p *Process) Foreground() (bool, error) {
   224  	return p.ForegroundWithContext(context.Background())
   225  }
   226  
   227  func (p *Process) ForegroundWithContext(ctx context.Context) (bool, error) {
   228  	// see https://github.com/shirou/gopsutil/issues/596#issuecomment-432707831 for implementation details
   229  	pid := p.Pid
   230  	ps, err := exec.LookPath("ps")
   231  	if err != nil {
   232  		return false, err
   233  	}
   234  	out, err := invoke.CommandWithContext(ctx, ps, "-o", "stat=", "-p", strconv.Itoa(int(pid)))
   235  	if err != nil {
   236  		return false, err
   237  	}
   238  	return strings.IndexByte(string(out), '+') != -1, nil
   239  }
   240  
   241  func (p *Process) Uids() ([]int32, error) {
   242  	return p.UidsWithContext(context.Background())
   243  }
   244  
   245  func (p *Process) UidsWithContext(ctx context.Context) ([]int32, error) {
   246  	k, err := p.getKProc()
   247  	if err != nil {
   248  		return nil, err
   249  	}
   250  
   251  	// See: http://unix.superglobalmegacorp.com/Net2/newsrc/sys/ucred.h.html
   252  	userEffectiveUID := int32(k.Eproc.Ucred.UID)
   253  
   254  	return []int32{userEffectiveUID}, nil
   255  }
   256  func (p *Process) Gids() ([]int32, error) {
   257  	return p.GidsWithContext(context.Background())
   258  }
   259  
   260  func (p *Process) GidsWithContext(ctx context.Context) ([]int32, error) {
   261  	k, err := p.getKProc()
   262  	if err != nil {
   263  		return nil, err
   264  	}
   265  
   266  	gids := make([]int32, 0, 3)
   267  	gids = append(gids, int32(k.Eproc.Pcred.P_rgid), int32(k.Eproc.Ucred.Ngroups), int32(k.Eproc.Pcred.P_svgid))
   268  
   269  	return gids, nil
   270  }
   271  
   272  func (p *Process) GroupsWithContext(ctx context.Context) ([]int32, error) {
   273  	k, err := p.getKProc()
   274  	if err != nil {
   275  		return nil, err
   276  	}
   277  
   278  	groups := make([]int32, k.Eproc.Ucred.Ngroups)
   279  	for i := int16(0); i < k.Eproc.Ucred.Ngroups; i++ {
   280  		groups[i] = int32(k.Eproc.Ucred.Groups[i])
   281  	}
   282  
   283  	return groups, nil
   284  }
   285  func (p *Process) Terminal() (string, error) {
   286  	return p.TerminalWithContext(context.Background())
   287  }
   288  
   289  func (p *Process) TerminalWithContext(ctx context.Context) (string, error) {
   290  	return "", common.ErrNotImplementedError
   291  	/*
   292  		k, err := p.getKProc()
   293  		if err != nil {
   294  			return "", err
   295  		}
   296  
   297  		ttyNr := uint64(k.Eproc.Tdev)
   298  		termmap, err := getTerminalMap()
   299  		if err != nil {
   300  			return "", err
   301  		}
   302  
   303  		return termmap[ttyNr], nil
   304  	*/
   305  }
   306  func (p *Process) Nice() (int32, error) {
   307  	return p.NiceWithContext(context.Background())
   308  }
   309  
   310  func (p *Process) NiceWithContext(ctx context.Context) (int32, error) {
   311  	k, err := p.getKProc()
   312  	if err != nil {
   313  		return 0, err
   314  	}
   315  	return int32(k.Proc.P_nice), nil
   316  }
   317  func (p *Process) IOnice() (int32, error) {
   318  	return p.IOniceWithContext(context.Background())
   319  }
   320  
   321  func (p *Process) IOniceWithContext(ctx context.Context) (int32, error) {
   322  	return 0, common.ErrNotImplementedError
   323  }
   324  func (p *Process) Rlimit() ([]RlimitStat, error) {
   325  	return p.RlimitWithContext(context.Background())
   326  }
   327  
   328  func (p *Process) RlimitWithContext(ctx context.Context) ([]RlimitStat, error) {
   329  	var rlimit []RlimitStat
   330  	return rlimit, common.ErrNotImplementedError
   331  }
   332  func (p *Process) RlimitUsage(gatherUsed bool) ([]RlimitStat, error) {
   333  	return p.RlimitUsageWithContext(context.Background(), gatherUsed)
   334  }
   335  
   336  func (p *Process) RlimitUsageWithContext(ctx context.Context, gatherUsed bool) ([]RlimitStat, error) {
   337  	var rlimit []RlimitStat
   338  	return rlimit, common.ErrNotImplementedError
   339  }
   340  func (p *Process) IOCounters() (*IOCountersStat, error) {
   341  	return p.IOCountersWithContext(context.Background())
   342  }
   343  
   344  func (p *Process) IOCountersWithContext(ctx context.Context) (*IOCountersStat, error) {
   345  	return nil, common.ErrNotImplementedError
   346  }
   347  func (p *Process) NumCtxSwitches() (*NumCtxSwitchesStat, error) {
   348  	return p.NumCtxSwitchesWithContext(context.Background())
   349  }
   350  
   351  func (p *Process) NumCtxSwitchesWithContext(ctx context.Context) (*NumCtxSwitchesStat, error) {
   352  	return nil, common.ErrNotImplementedError
   353  }
   354  func (p *Process) NumFDs() (int32, error) {
   355  	return p.NumFDsWithContext(context.Background())
   356  }
   357  
   358  func (p *Process) NumFDsWithContext(ctx context.Context) (int32, error) {
   359  	return 0, common.ErrNotImplementedError
   360  }
   361  func (p *Process) NumThreads() (int32, error) {
   362  	return p.NumThreadsWithContext(context.Background())
   363  }
   364  
   365  func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) {
   366  	r, err := callPsWithContext(ctx, "utime,stime", p.Pid, true)
   367  	if err != nil {
   368  		return 0, err
   369  	}
   370  	return int32(len(r)), nil
   371  }
   372  func (p *Process) Threads() (map[int32]*cpu.TimesStat, error) {
   373  	return p.ThreadsWithContext(context.Background())
   374  }
   375  
   376  func (p *Process) ThreadsWithContext(ctx context.Context) (map[int32]*cpu.TimesStat, error) {
   377  	ret := make(map[int32]*cpu.TimesStat)
   378  	return ret, common.ErrNotImplementedError
   379  }
   380  
   381  func convertCPUTimes(s string) (ret float64, err error) {
   382  	var t int
   383  	var _tmp string
   384  	if strings.Contains(s, ":") {
   385  		_t := strings.Split(s, ":")
   386  		switch len(_t) {
   387  		case 3:
   388  			hour, err := strconv.Atoi(_t[0])
   389  			if err != nil {
   390  				return ret, err
   391  			}
   392  			t += hour * 60 * 60 * ClockTicks
   393  
   394  			mins, err := strconv.Atoi(_t[1])
   395  			if err != nil {
   396  				return ret, err
   397  			}
   398  			t += mins * 60 * ClockTicks
   399  			_tmp = _t[2]
   400  		case 2:
   401  			mins, err := strconv.Atoi(_t[0])
   402  			if err != nil {
   403  				return ret, err
   404  			}
   405  			t += mins * 60 * ClockTicks
   406  			_tmp = _t[1]
   407  		case 1, 0:
   408  			_tmp = s
   409  		default:
   410  			return ret, fmt.Errorf("wrong cpu time string")
   411  		}
   412  	} else {
   413  		_tmp = s
   414  	}
   415  
   416  	_t := strings.Split(_tmp, ".")
   417  	if err != nil {
   418  		return ret, err
   419  	}
   420  	h, _ := strconv.Atoi(_t[0])
   421  	t += h * ClockTicks
   422  	h, _ = strconv.Atoi(_t[1])
   423  	t += h
   424  	return float64(t) / ClockTicks, nil
   425  }
   426  func (p *Process) Times() (*cpu.TimesStat, error) {
   427  	return p.TimesWithContext(context.Background())
   428  }
   429  
   430  func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) {
   431  	r, err := callPsWithContext(ctx, "utime,stime", p.Pid, false)
   432  
   433  	if err != nil {
   434  		return nil, err
   435  	}
   436  
   437  	utime, err := convertCPUTimes(r[0][0])
   438  	if err != nil {
   439  		return nil, err
   440  	}
   441  	stime, err := convertCPUTimes(r[0][1])
   442  	if err != nil {
   443  		return nil, err
   444  	}
   445  
   446  	ret := &cpu.TimesStat{
   447  		CPU:    "cpu",
   448  		User:   utime,
   449  		System: stime,
   450  	}
   451  	return ret, nil
   452  }
   453  func (p *Process) CPUAffinity() ([]int32, error) {
   454  	return p.CPUAffinityWithContext(context.Background())
   455  }
   456  
   457  func (p *Process) CPUAffinityWithContext(ctx context.Context) ([]int32, error) {
   458  	return nil, common.ErrNotImplementedError
   459  }
   460  func (p *Process) MemoryInfo() (*MemoryInfoStat, error) {
   461  	return p.MemoryInfoWithContext(context.Background())
   462  }
   463  
   464  func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, error) {
   465  	r, err := callPsWithContext(ctx, "rss,vsize,pagein", p.Pid, false)
   466  	if err != nil {
   467  		return nil, err
   468  	}
   469  	rss, err := strconv.Atoi(r[0][0])
   470  	if err != nil {
   471  		return nil, err
   472  	}
   473  	vms, err := strconv.Atoi(r[0][1])
   474  	if err != nil {
   475  		return nil, err
   476  	}
   477  	pagein, err := strconv.Atoi(r[0][2])
   478  	if err != nil {
   479  		return nil, err
   480  	}
   481  
   482  	ret := &MemoryInfoStat{
   483  		RSS:  uint64(rss) * 1024,
   484  		VMS:  uint64(vms) * 1024,
   485  		Swap: uint64(pagein),
   486  	}
   487  
   488  	return ret, nil
   489  }
   490  func (p *Process) MemoryInfoEx() (*MemoryInfoExStat, error) {
   491  	return p.MemoryInfoExWithContext(context.Background())
   492  }
   493  
   494  func (p *Process) MemoryInfoExWithContext(ctx context.Context) (*MemoryInfoExStat, error) {
   495  	return nil, common.ErrNotImplementedError
   496  }
   497  
   498  func (p *Process) PageFaults() (*PageFaultsStat, error) {
   499  	return p.PageFaultsWithContext(context.Background())
   500  }
   501  
   502  func (p *Process) PageFaultsWithContext(ctx context.Context) (*PageFaultsStat, error) {
   503  	return nil, common.ErrNotImplementedError
   504  }
   505  
   506  func (p *Process) Children() ([]*Process, error) {
   507  	return p.ChildrenWithContext(context.Background())
   508  }
   509  
   510  func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) {
   511  	pids, err := common.CallPgrepWithContext(ctx, invoke, p.Pid)
   512  	if err != nil {
   513  		return nil, err
   514  	}
   515  	ret := make([]*Process, 0, len(pids))
   516  	for _, pid := range pids {
   517  		np, err := NewProcess(pid)
   518  		if err != nil {
   519  			return nil, err
   520  		}
   521  		ret = append(ret, np)
   522  	}
   523  	return ret, nil
   524  }
   525  
   526  func (p *Process) OpenFiles() ([]OpenFilesStat, error) {
   527  	return p.OpenFilesWithContext(context.Background())
   528  }
   529  
   530  func (p *Process) OpenFilesWithContext(ctx context.Context) ([]OpenFilesStat, error) {
   531  	return nil, common.ErrNotImplementedError
   532  }
   533  
   534  func (p *Process) Connections() ([]net.ConnectionStat, error) {
   535  	return p.ConnectionsWithContext(context.Background())
   536  }
   537  
   538  func (p *Process) ConnectionsWithContext(ctx context.Context) ([]net.ConnectionStat, error) {
   539  	return net.ConnectionsPid("all", p.Pid)
   540  }
   541  
   542  // Connections returns a slice of net.ConnectionStat used by the process at most `max`
   543  func (p *Process) ConnectionsMax(max int) ([]net.ConnectionStat, error) {
   544  	return p.ConnectionsMaxWithContext(context.Background(), max)
   545  }
   546  
   547  func (p *Process) ConnectionsMaxWithContext(ctx context.Context, max int) ([]net.ConnectionStat, error) {
   548  	return net.ConnectionsPidMax("all", p.Pid, max)
   549  }
   550  
   551  func (p *Process) NetIOCounters(pernic bool) ([]net.IOCountersStat, error) {
   552  	return p.NetIOCountersWithContext(context.Background(), pernic)
   553  }
   554  
   555  func (p *Process) NetIOCountersWithContext(ctx context.Context, pernic bool) ([]net.IOCountersStat, error) {
   556  	return nil, common.ErrNotImplementedError
   557  }
   558  
   559  func (p *Process) MemoryMaps(grouped bool) (*[]MemoryMapsStat, error) {
   560  	return p.MemoryMapsWithContext(context.Background(), grouped)
   561  }
   562  
   563  func (p *Process) MemoryMapsWithContext(ctx context.Context, grouped bool) (*[]MemoryMapsStat, error) {
   564  	var ret []MemoryMapsStat
   565  	return &ret, common.ErrNotImplementedError
   566  }
   567  
   568  func Processes() ([]*Process, error) {
   569  	return ProcessesWithContext(context.Background())
   570  }
   571  
   572  func ProcessesWithContext(ctx context.Context) ([]*Process, error) {
   573  	out := []*Process{}
   574  
   575  	pids, err := PidsWithContext(ctx)
   576  	if err != nil {
   577  		return out, err
   578  	}
   579  
   580  	for _, pid := range pids {
   581  		p, err := NewProcess(pid)
   582  		if err != nil {
   583  			continue
   584  		}
   585  		out = append(out, p)
   586  	}
   587  
   588  	return out, nil
   589  }
   590  
   591  func parseKinfoProc(buf []byte) (KinfoProc, error) {
   592  	var k KinfoProc
   593  	br := bytes.NewReader(buf)
   594  
   595  	err := common.Read(br, binary.LittleEndian, &k)
   596  	if err != nil {
   597  		return k, err
   598  	}
   599  
   600  	return k, nil
   601  }
   602  
   603  // Returns a proc as defined here:
   604  // http://unix.superglobalmegacorp.com/Net2/newsrc/sys/kinfo_proc.h.html
   605  func (p *Process) getKProc() (*KinfoProc, error) {
   606  	return p.getKProcWithContext(context.Background())
   607  }
   608  
   609  func (p *Process) getKProcWithContext(ctx context.Context) (*KinfoProc, error) {
   610  	mib := []int32{CTLKern, KernProc, KernProcPID, p.Pid}
   611  	length := uint64(unsafe.Sizeof(KinfoProc{}))
   612  	buf := make([]byte, length)
   613  	_, _, syserr := unix.Syscall6(
   614  		202, // unix.SYS___SYSCTL https://github.com/golang/sys/blob/76b94024e4b621e672466e8db3d7f084e7ddcad2/unix/zsysnum_darwin_amd64.go#L146
   615  		uintptr(unsafe.Pointer(&mib[0])),
   616  		uintptr(len(mib)),
   617  		uintptr(unsafe.Pointer(&buf[0])),
   618  		uintptr(unsafe.Pointer(&length)),
   619  		0,
   620  		0)
   621  	if syserr != 0 {
   622  		return nil, syserr
   623  	}
   624  	k, err := parseKinfoProc(buf)
   625  	if err != nil {
   626  		return nil, err
   627  	}
   628  
   629  	return &k, nil
   630  }
   631  
   632  // call ps command.
   633  // Return value deletes Header line(you must not input wrong arg).
   634  // And splited by Space. Caller have responsibility to manage.
   635  // If passed arg pid is 0, get information from all process.
   636  func callPsWithContext(ctx context.Context, arg string, pid int32, threadOption bool) ([][]string, error) {
   637  	bin, err := exec.LookPath("ps")
   638  	if err != nil {
   639  		return [][]string{}, err
   640  	}
   641  
   642  	var cmd []string
   643  	if pid == 0 { // will get from all processes.
   644  		cmd = []string{"-ax", "-o", arg}
   645  	} else if threadOption {
   646  		cmd = []string{"-x", "-o", arg, "-M", "-p", strconv.Itoa(int(pid))}
   647  	} else {
   648  		cmd = []string{"-x", "-o", arg, "-p", strconv.Itoa(int(pid))}
   649  	}
   650  	out, err := invoke.CommandWithContext(ctx, bin, cmd...)
   651  	if err != nil {
   652  		return [][]string{}, err
   653  	}
   654  	lines := strings.Split(string(out), "\n")
   655  
   656  	var ret [][]string
   657  	for _, l := range lines[1:] {
   658  		var lr []string
   659  		for _, r := range strings.Split(l, " ") {
   660  			if r == "" {
   661  				continue
   662  			}
   663  			lr = append(lr, strings.TrimSpace(r))
   664  		}
   665  		if len(lr) != 0 {
   666  			ret = append(ret, lr)
   667  		}
   668  	}
   669  
   670  	return ret, nil
   671  }