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 }