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 }