github.com/gofiber/fiber/v2@v2.47.0/internal/gopsutil/process/process_linux.go (about) 1 //go:build linux 2 // +build linux 3 4 package process 5 6 import ( 7 "bufio" 8 "bytes" 9 "context" 10 "encoding/json" 11 "fmt" 12 "math" 13 "os" 14 "path/filepath" 15 "strconv" 16 "strings" 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 var PageSize = uint64(os.Getpagesize()) 25 26 const ( 27 PrioProcess = 0 // linux/resource.h 28 ClockTicks = 100 // C.sysconf(C._SC_CLK_TCK) 29 ) 30 31 // MemoryInfoExStat is different between OSes 32 type MemoryInfoExStat struct { 33 RSS uint64 `json:"rss"` // bytes 34 VMS uint64 `json:"vms"` // bytes 35 Shared uint64 `json:"shared"` // bytes 36 Text uint64 `json:"text"` // bytes 37 Lib uint64 `json:"lib"` // bytes 38 Data uint64 `json:"data"` // bytes 39 Dirty uint64 `json:"dirty"` // bytes 40 } 41 42 func (m MemoryInfoExStat) String() string { 43 s, _ := json.Marshal(m) 44 return string(s) 45 } 46 47 type MemoryMapsStat struct { 48 Path string `json:"path"` 49 Rss uint64 `json:"rss"` 50 Size uint64 `json:"size"` 51 Pss uint64 `json:"pss"` 52 SharedClean uint64 `json:"sharedClean"` 53 SharedDirty uint64 `json:"sharedDirty"` 54 PrivateClean uint64 `json:"privateClean"` 55 PrivateDirty uint64 `json:"privateDirty"` 56 Referenced uint64 `json:"referenced"` 57 Anonymous uint64 `json:"anonymous"` 58 Swap uint64 `json:"swap"` 59 } 60 61 // String returns JSON value of the process. 62 func (m MemoryMapsStat) String() string { 63 s, _ := json.Marshal(m) 64 return string(s) 65 } 66 67 // Ppid returns Parent Process ID of the process. 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 _, ppid, _, _, _, _, _, err := p.fillFromStatWithContext(ctx) 74 if err != nil { 75 return -1, err 76 } 77 return ppid, nil 78 } 79 80 // Name returns name of the process. 81 func (p *Process) Name() (string, error) { 82 return p.NameWithContext(context.Background()) 83 } 84 85 func (p *Process) NameWithContext(ctx context.Context) (string, error) { 86 if p.name == "" { 87 if err := p.fillFromStatusWithContext(ctx); err != nil { 88 return "", err 89 } 90 } 91 return p.name, nil 92 } 93 94 // Tgid returns tgid, a Linux-synonym for user-space Pid 95 func (p *Process) Tgid() (int32, error) { 96 if p.tgid == 0 { 97 if err := p.fillFromStatusWithContext(context.Background()); err != nil { 98 return 0, err 99 } 100 } 101 return p.tgid, nil 102 } 103 104 // Exe returns executable path of the process. 105 func (p *Process) Exe() (string, error) { 106 return p.ExeWithContext(context.Background()) 107 } 108 109 func (p *Process) ExeWithContext(ctx context.Context) (string, error) { 110 return p.fillFromExeWithContext(ctx) 111 } 112 113 // Cmdline returns the command line arguments of the process as a string with 114 // each argument separated by 0x20 ascii character. 115 func (p *Process) Cmdline() (string, error) { 116 return p.CmdlineWithContext(context.Background()) 117 } 118 119 func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) { 120 return p.fillFromCmdlineWithContext(ctx) 121 } 122 123 // CmdlineSlice returns the command line arguments of the process as a slice with each 124 // element being an argument. 125 func (p *Process) CmdlineSlice() ([]string, error) { 126 return p.CmdlineSliceWithContext(context.Background()) 127 } 128 129 func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) { 130 return p.fillSliceFromCmdlineWithContext(ctx) 131 } 132 133 func (p *Process) createTimeWithContext(ctx context.Context) (int64, error) { 134 _, _, _, createTime, _, _, _, err := p.fillFromStatWithContext(ctx) 135 if err != nil { 136 return 0, err 137 } 138 return createTime, nil 139 } 140 141 // Cwd returns current working directory of the process. 142 func (p *Process) Cwd() (string, error) { 143 return p.CwdWithContext(context.Background()) 144 } 145 146 func (p *Process) CwdWithContext(ctx context.Context) (string, error) { 147 return p.fillFromCwdWithContext(ctx) 148 } 149 150 // Parent returns parent Process of the process. 151 func (p *Process) Parent() (*Process, error) { 152 return p.ParentWithContext(context.Background()) 153 } 154 155 func (p *Process) ParentWithContext(ctx context.Context) (*Process, error) { 156 err := p.fillFromStatusWithContext(ctx) 157 if err != nil { 158 return nil, err 159 } 160 if p.parent == 0 { 161 return nil, fmt.Errorf("wrong number of parents") 162 } 163 return NewProcess(p.parent) 164 } 165 166 // Status returns the process status. 167 // Return value could be one of these. 168 // R: Running S: Sleep T: Stop I: Idle 169 // Z: Zombie W: Wait L: Lock 170 // The character is same within all supported platforms. 171 func (p *Process) Status() (string, error) { 172 return p.StatusWithContext(context.Background()) 173 } 174 175 func (p *Process) StatusWithContext(ctx context.Context) (string, error) { 176 err := p.fillFromStatusWithContext(ctx) 177 if err != nil { 178 return "", err 179 } 180 return p.status, nil 181 } 182 183 // Foreground returns true if the process is in foreground, false otherwise. 184 func (p *Process) Foreground() (bool, error) { 185 return p.ForegroundWithContext(context.Background()) 186 } 187 188 func (p *Process) ForegroundWithContext(ctx context.Context) (bool, error) { 189 // see https://github.com/shirou/gopsutil/issues/596#issuecomment-432707831 for implementation details 190 pid := p.Pid 191 statPath := common.HostProc(strconv.Itoa(int(pid)), "stat") 192 contents, err := os.ReadFile(statPath) 193 if err != nil { 194 return false, err 195 } 196 fields := strings.Fields(string(contents)) 197 if len(fields) < 8 { 198 return false, fmt.Errorf("insufficient data in %s", statPath) 199 } 200 pgid := fields[4] 201 tpgid := fields[7] 202 return pgid == tpgid, nil 203 } 204 205 // Uids returns user ids of the process as a slice of the int 206 func (p *Process) Uids() ([]int32, error) { 207 return p.UidsWithContext(context.Background()) 208 } 209 210 func (p *Process) UidsWithContext(ctx context.Context) ([]int32, error) { 211 err := p.fillFromStatusWithContext(ctx) 212 if err != nil { 213 return []int32{}, err 214 } 215 return p.uids, nil 216 } 217 218 // Gids returns group ids of the process as a slice of the int 219 func (p *Process) Gids() ([]int32, error) { 220 return p.GidsWithContext(context.Background()) 221 } 222 223 func (p *Process) GidsWithContext(ctx context.Context) ([]int32, error) { 224 err := p.fillFromStatusWithContext(ctx) 225 if err != nil { 226 return []int32{}, err 227 } 228 return p.gids, nil 229 } 230 231 func (p *Process) GroupsWithContext(ctx context.Context) ([]int32, error) { 232 err := p.fillFromStatusWithContext(ctx) 233 if err != nil { 234 return []int32{}, err 235 } 236 return p.groups, nil 237 } 238 239 // Terminal returns a terminal which is associated with the process. 240 func (p *Process) Terminal() (string, error) { 241 return p.TerminalWithContext(context.Background()) 242 } 243 244 func (p *Process) TerminalWithContext(ctx context.Context) (string, error) { 245 t, _, _, _, _, _, _, err := p.fillFromStatWithContext(ctx) 246 if err != nil { 247 return "", err 248 } 249 termmap, err := getTerminalMap() 250 if err != nil { 251 return "", err 252 } 253 terminal := termmap[t] 254 return terminal, nil 255 } 256 257 // Nice returns a nice value (priority). 258 // Notice: gopsutil can not set nice value. 259 func (p *Process) Nice() (int32, error) { 260 return p.NiceWithContext(context.Background()) 261 } 262 263 func (p *Process) NiceWithContext(ctx context.Context) (int32, error) { 264 _, _, _, _, _, nice, _, err := p.fillFromStatWithContext(ctx) 265 if err != nil { 266 return 0, err 267 } 268 return nice, nil 269 } 270 271 // IOnice returns process I/O nice value (priority). 272 func (p *Process) IOnice() (int32, error) { 273 return p.IOniceWithContext(context.Background()) 274 } 275 276 func (p *Process) IOniceWithContext(ctx context.Context) (int32, error) { 277 return 0, common.ErrNotImplementedError 278 } 279 280 // Rlimit returns Resource Limits. 281 func (p *Process) Rlimit() ([]RlimitStat, error) { 282 return p.RlimitWithContext(context.Background()) 283 } 284 285 func (p *Process) RlimitWithContext(ctx context.Context) ([]RlimitStat, error) { 286 return p.RlimitUsage(false) 287 } 288 289 // RlimitUsage returns Resource Limits. 290 // If gatherUsed is true, the currently used value will be gathered and added 291 // to the resulting RlimitStat. 292 func (p *Process) RlimitUsage(gatherUsed bool) ([]RlimitStat, error) { 293 return p.RlimitUsageWithContext(context.Background(), gatherUsed) 294 } 295 296 func (p *Process) RlimitUsageWithContext(ctx context.Context, gatherUsed bool) ([]RlimitStat, error) { 297 rlimits, err := p.fillFromLimitsWithContext(ctx) 298 if !gatherUsed || err != nil { 299 return rlimits, err 300 } 301 302 _, _, _, _, rtprio, nice, _, err := p.fillFromStatWithContext(ctx) 303 if err != nil { 304 return nil, err 305 } 306 if err := p.fillFromStatusWithContext(ctx); err != nil { 307 return nil, err 308 } 309 310 for i := range rlimits { 311 rs := &rlimits[i] 312 switch rs.Resource { 313 case RLIMIT_CPU: 314 times, err := p.Times() 315 if err != nil { 316 return nil, err 317 } 318 rs.Used = uint64(times.User + times.System) 319 case RLIMIT_DATA: 320 rs.Used = uint64(p.memInfo.Data) 321 case RLIMIT_STACK: 322 rs.Used = uint64(p.memInfo.Stack) 323 case RLIMIT_RSS: 324 rs.Used = uint64(p.memInfo.RSS) 325 case RLIMIT_NOFILE: 326 n, err := p.NumFDs() 327 if err != nil { 328 return nil, err 329 } 330 rs.Used = uint64(n) 331 case RLIMIT_MEMLOCK: 332 rs.Used = uint64(p.memInfo.Locked) 333 case RLIMIT_AS: 334 rs.Used = uint64(p.memInfo.VMS) 335 case RLIMIT_LOCKS: 336 //TODO we can get the used value from /proc/$pid/locks. But linux doesn't enforce it, so not a high priority. 337 case RLIMIT_SIGPENDING: 338 rs.Used = p.sigInfo.PendingProcess 339 case RLIMIT_NICE: 340 // The rlimit for nice is a little unusual, in that 0 means the niceness cannot be decreased beyond the current value, but it can be increased. 341 // So effectively: if rs.Soft == 0 { rs.Soft = rs.Used } 342 rs.Used = uint64(nice) 343 case RLIMIT_RTPRIO: 344 rs.Used = uint64(rtprio) 345 } 346 } 347 348 return rlimits, err 349 } 350 351 // IOCounters returns IO Counters. 352 func (p *Process) IOCounters() (*IOCountersStat, error) { 353 return p.IOCountersWithContext(context.Background()) 354 } 355 356 func (p *Process) IOCountersWithContext(ctx context.Context) (*IOCountersStat, error) { 357 return p.fillFromIOWithContext(ctx) 358 } 359 360 // NumCtxSwitches returns the number of the context switches of the process. 361 func (p *Process) NumCtxSwitches() (*NumCtxSwitchesStat, error) { 362 return p.NumCtxSwitchesWithContext(context.Background()) 363 } 364 365 func (p *Process) NumCtxSwitchesWithContext(ctx context.Context) (*NumCtxSwitchesStat, error) { 366 err := p.fillFromStatusWithContext(ctx) 367 if err != nil { 368 return nil, err 369 } 370 return p.numCtxSwitches, nil 371 } 372 373 // NumFDs returns the number of File Descriptors used by the process. 374 func (p *Process) NumFDs() (int32, error) { 375 return p.NumFDsWithContext(context.Background()) 376 } 377 378 func (p *Process) NumFDsWithContext(ctx context.Context) (int32, error) { 379 _, fnames, err := p.fillFromfdListWithContext(ctx) 380 return int32(len(fnames)), err 381 } 382 383 // NumThreads returns the number of threads used by the process. 384 func (p *Process) NumThreads() (int32, error) { 385 return p.NumThreadsWithContext(context.Background()) 386 } 387 388 func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) { 389 err := p.fillFromStatusWithContext(ctx) 390 if err != nil { 391 return 0, err 392 } 393 return p.numThreads, nil 394 } 395 396 func (p *Process) Threads() (map[int32]*cpu.TimesStat, error) { 397 return p.ThreadsWithContext(context.Background()) 398 } 399 400 func (p *Process) ThreadsWithContext(ctx context.Context) (map[int32]*cpu.TimesStat, error) { 401 ret := make(map[int32]*cpu.TimesStat) 402 taskPath := common.HostProc(strconv.Itoa(int(p.Pid)), "task") 403 404 tids, err := readPidsFromDir(taskPath) 405 if err != nil { 406 return nil, err 407 } 408 409 for _, tid := range tids { 410 _, _, cpuTimes, _, _, _, _, err := p.fillFromTIDStatWithContext(ctx, tid) 411 if err != nil { 412 return nil, err 413 } 414 ret[tid] = cpuTimes 415 } 416 417 return ret, nil 418 } 419 420 // Times returns CPU times of the process. 421 func (p *Process) Times() (*cpu.TimesStat, error) { 422 return p.TimesWithContext(context.Background()) 423 } 424 425 func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) { 426 _, _, cpuTimes, _, _, _, _, err := p.fillFromStatWithContext(ctx) 427 if err != nil { 428 return nil, err 429 } 430 return cpuTimes, nil 431 } 432 433 // CPUAffinity returns CPU affinity of the process. 434 // 435 // Notice: Not implemented yet. 436 func (p *Process) CPUAffinity() ([]int32, error) { 437 return p.CPUAffinityWithContext(context.Background()) 438 } 439 440 func (p *Process) CPUAffinityWithContext(ctx context.Context) ([]int32, error) { 441 return nil, common.ErrNotImplementedError 442 } 443 444 // MemoryInfo returns platform in-dependend memory information, such as RSS, VMS and Swap 445 func (p *Process) MemoryInfo() (*MemoryInfoStat, error) { 446 return p.MemoryInfoWithContext(context.Background()) 447 } 448 449 func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, error) { 450 meminfo, _, err := p.fillFromStatmWithContext(ctx) 451 if err != nil { 452 return nil, err 453 } 454 return meminfo, nil 455 } 456 457 // MemoryInfoEx returns platform dependend memory information. 458 func (p *Process) MemoryInfoEx() (*MemoryInfoExStat, error) { 459 return p.MemoryInfoExWithContext(context.Background()) 460 } 461 462 func (p *Process) MemoryInfoExWithContext(ctx context.Context) (*MemoryInfoExStat, error) { 463 _, memInfoEx, err := p.fillFromStatmWithContext(ctx) 464 if err != nil { 465 return nil, err 466 } 467 return memInfoEx, nil 468 } 469 470 // PageFaultsInfo returns the process's page fault counters 471 func (p *Process) PageFaults() (*PageFaultsStat, error) { 472 return p.PageFaultsWithContext(context.Background()) 473 } 474 475 func (p *Process) PageFaultsWithContext(ctx context.Context) (*PageFaultsStat, error) { 476 _, _, _, _, _, _, pageFaults, err := p.fillFromStatWithContext(ctx) 477 if err != nil { 478 return nil, err 479 } 480 return pageFaults, nil 481 482 } 483 484 // Children returns a slice of Process of the process. 485 func (p *Process) Children() ([]*Process, error) { 486 return p.ChildrenWithContext(context.Background()) 487 } 488 489 func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) { 490 pids, err := common.CallPgrepWithContext(ctx, invoke, p.Pid) 491 if err != nil { 492 if pids == nil || len(pids) == 0 { 493 return nil, ErrorNoChildren 494 } 495 return nil, err 496 } 497 ret := make([]*Process, 0, len(pids)) 498 for _, pid := range pids { 499 np, err := NewProcess(pid) 500 if err != nil { 501 return nil, err 502 } 503 ret = append(ret, np) 504 } 505 return ret, nil 506 } 507 508 // OpenFiles returns a slice of OpenFilesStat opend by the process. 509 // OpenFilesStat includes a file path and file descriptor. 510 func (p *Process) OpenFiles() ([]OpenFilesStat, error) { 511 return p.OpenFilesWithContext(context.Background()) 512 } 513 514 func (p *Process) OpenFilesWithContext(ctx context.Context) ([]OpenFilesStat, error) { 515 _, ofs, err := p.fillFromfdWithContext(ctx) 516 if err != nil { 517 return nil, err 518 } 519 ret := make([]OpenFilesStat, len(ofs)) 520 for i, o := range ofs { 521 ret[i] = *o 522 } 523 524 return ret, nil 525 } 526 527 // Connections returns a slice of net.ConnectionStat used by the process. 528 // This returns all kind of the connection. This measn TCP, UDP or UNIX. 529 func (p *Process) Connections() ([]net.ConnectionStat, error) { 530 return p.ConnectionsWithContext(context.Background()) 531 } 532 533 func (p *Process) ConnectionsWithContext(ctx context.Context) ([]net.ConnectionStat, error) { 534 return net.ConnectionsPid("all", p.Pid) 535 } 536 537 // Connections returns a slice of net.ConnectionStat used by the process at most `max` 538 func (p *Process) ConnectionsMax(max int) ([]net.ConnectionStat, error) { 539 return p.ConnectionsMaxWithContext(context.Background(), max) 540 } 541 542 func (p *Process) ConnectionsMaxWithContext(ctx context.Context, max int) ([]net.ConnectionStat, error) { 543 return net.ConnectionsPidMax("all", p.Pid, max) 544 } 545 546 // NetIOCounters returns NetIOCounters of the process. 547 func (p *Process) NetIOCounters(pernic bool) ([]net.IOCountersStat, error) { 548 return p.NetIOCountersWithContext(context.Background(), pernic) 549 } 550 551 func (p *Process) NetIOCountersWithContext(ctx context.Context, pernic bool) ([]net.IOCountersStat, error) { 552 filename := common.HostProc(strconv.Itoa(int(p.Pid)), "net/dev") 553 return net.IOCountersByFile(pernic, filename) 554 } 555 556 // MemoryMaps get memory maps from /proc/(pid)/smaps 557 func (p *Process) MemoryMaps(grouped bool) (*[]MemoryMapsStat, error) { 558 return p.MemoryMapsWithContext(context.Background(), grouped) 559 } 560 561 func (p *Process) MemoryMapsWithContext(ctx context.Context, grouped bool) (*[]MemoryMapsStat, error) { 562 pid := p.Pid 563 var ret []MemoryMapsStat 564 if grouped { 565 ret = make([]MemoryMapsStat, 1) 566 } 567 smapsPath := common.HostProc(strconv.Itoa(int(pid)), "smaps") 568 contents, err := os.ReadFile(smapsPath) 569 if err != nil { 570 return nil, err 571 } 572 lines := strings.Split(string(contents), "\n") 573 574 // function of parsing a block 575 getBlock := func(first_line []string, block []string) (MemoryMapsStat, error) { 576 m := MemoryMapsStat{} 577 m.Path = first_line[len(first_line)-1] 578 579 for _, line := range block { 580 if strings.Contains(line, "VmFlags") { 581 continue 582 } 583 field := strings.Split(line, ":") 584 if len(field) < 2 { 585 continue 586 } 587 v := strings.Trim(field[1], "kB") // remove last "kB" 588 v = strings.TrimSpace(v) 589 t, err := strconv.ParseUint(v, 10, 64) 590 if err != nil { 591 return m, err 592 } 593 594 switch field[0] { 595 case "Size": 596 m.Size = t 597 case "Rss": 598 m.Rss = t 599 case "Pss": 600 m.Pss = t 601 case "Shared_Clean": 602 m.SharedClean = t 603 case "Shared_Dirty": 604 m.SharedDirty = t 605 case "Private_Clean": 606 m.PrivateClean = t 607 case "Private_Dirty": 608 m.PrivateDirty = t 609 case "Referenced": 610 m.Referenced = t 611 case "Anonymous": 612 m.Anonymous = t 613 case "Swap": 614 m.Swap = t 615 } 616 } 617 return m, nil 618 } 619 620 blocks := make([]string, 16) 621 for _, line := range lines { 622 fields := strings.Fields(line) 623 if len(fields) > 0 && !strings.HasSuffix(fields[0], ":") { 624 // new block section 625 if len(blocks) > 0 { 626 g, err := getBlock(fields, blocks) 627 if err != nil { 628 return &ret, err 629 } 630 if grouped { 631 ret[0].Size += g.Size 632 ret[0].Rss += g.Rss 633 ret[0].Pss += g.Pss 634 ret[0].SharedClean += g.SharedClean 635 ret[0].SharedDirty += g.SharedDirty 636 ret[0].PrivateClean += g.PrivateClean 637 ret[0].PrivateDirty += g.PrivateDirty 638 ret[0].Referenced += g.Referenced 639 ret[0].Anonymous += g.Anonymous 640 ret[0].Swap += g.Swap 641 } else { 642 ret = append(ret, g) 643 } 644 } 645 // starts new block 646 blocks = make([]string, 16) 647 } else { 648 blocks = append(blocks, line) 649 } 650 } 651 652 return &ret, nil 653 } 654 655 /** 656 ** Internal functions 657 **/ 658 659 func limitToInt(val string) (int32, error) { 660 if val == "unlimited" { 661 return math.MaxInt32, nil 662 } else { 663 res, err := strconv.ParseInt(val, 10, 32) 664 if err != nil { 665 return 0, err 666 } 667 return int32(res), nil 668 } 669 } 670 671 // Get num_fds from /proc/(pid)/limits 672 func (p *Process) fillFromLimitsWithContext(ctx context.Context) ([]RlimitStat, error) { 673 pid := p.Pid 674 limitsFile := common.HostProc(strconv.Itoa(int(pid)), "limits") 675 d, err := os.Open(limitsFile) 676 if err != nil { 677 return nil, err 678 } 679 defer d.Close() 680 681 var limitStats []RlimitStat 682 683 limitsScanner := bufio.NewScanner(d) 684 for limitsScanner.Scan() { 685 var statItem RlimitStat 686 687 str := strings.Fields(limitsScanner.Text()) 688 689 // Remove the header line 690 if strings.Contains(str[len(str)-1], "Units") { 691 continue 692 } 693 694 // Assert that last item is a Hard limit 695 statItem.Hard, err = limitToInt(str[len(str)-1]) 696 if err != nil { 697 // On error remove last item an try once again since it can be unit or header line 698 str = str[:len(str)-1] 699 statItem.Hard, err = limitToInt(str[len(str)-1]) 700 if err != nil { 701 return nil, err 702 } 703 } 704 // Remove last item from string 705 str = str[:len(str)-1] 706 707 //Now last item is a Soft limit 708 statItem.Soft, err = limitToInt(str[len(str)-1]) 709 if err != nil { 710 return nil, err 711 } 712 // Remove last item from string 713 str = str[:len(str)-1] 714 715 //The rest is a stats name 716 resourceName := strings.Join(str, " ") 717 switch resourceName { 718 case "Max cpu time": 719 statItem.Resource = RLIMIT_CPU 720 case "Max file size": 721 statItem.Resource = RLIMIT_FSIZE 722 case "Max data size": 723 statItem.Resource = RLIMIT_DATA 724 case "Max stack size": 725 statItem.Resource = RLIMIT_STACK 726 case "Max core file size": 727 statItem.Resource = RLIMIT_CORE 728 case "Max resident set": 729 statItem.Resource = RLIMIT_RSS 730 case "Max processes": 731 statItem.Resource = RLIMIT_NPROC 732 case "Max open files": 733 statItem.Resource = RLIMIT_NOFILE 734 case "Max locked memory": 735 statItem.Resource = RLIMIT_MEMLOCK 736 case "Max address space": 737 statItem.Resource = RLIMIT_AS 738 case "Max file locks": 739 statItem.Resource = RLIMIT_LOCKS 740 case "Max pending signals": 741 statItem.Resource = RLIMIT_SIGPENDING 742 case "Max msgqueue size": 743 statItem.Resource = RLIMIT_MSGQUEUE 744 case "Max nice priority": 745 statItem.Resource = RLIMIT_NICE 746 case "Max realtime priority": 747 statItem.Resource = RLIMIT_RTPRIO 748 case "Max realtime timeout": 749 statItem.Resource = RLIMIT_RTTIME 750 default: 751 continue 752 } 753 754 limitStats = append(limitStats, statItem) 755 } 756 757 if err := limitsScanner.Err(); err != nil { 758 return nil, err 759 } 760 761 return limitStats, nil 762 } 763 764 // Get list of /proc/(pid)/fd files 765 func (p *Process) fillFromfdListWithContext(ctx context.Context) (string, []string, error) { 766 pid := p.Pid 767 statPath := common.HostProc(strconv.Itoa(int(pid)), "fd") 768 d, err := os.Open(statPath) 769 if err != nil { 770 return statPath, []string{}, err 771 } 772 defer d.Close() 773 fnames, err := d.Readdirnames(-1) 774 return statPath, fnames, err 775 } 776 777 // Get num_fds from /proc/(pid)/fd 778 func (p *Process) fillFromfdWithContext(ctx context.Context) (int32, []*OpenFilesStat, error) { 779 statPath, fnames, err := p.fillFromfdListWithContext(ctx) 780 if err != nil { 781 return 0, nil, err 782 } 783 numFDs := int32(len(fnames)) 784 785 var openfiles []*OpenFilesStat 786 for _, fd := range fnames { 787 fpath := filepath.Join(statPath, fd) 788 filepath, err := os.Readlink(fpath) 789 if err != nil { 790 continue 791 } 792 t, err := strconv.ParseUint(fd, 10, 64) 793 if err != nil { 794 return numFDs, openfiles, err 795 } 796 o := &OpenFilesStat{ 797 Path: filepath, 798 Fd: t, 799 } 800 openfiles = append(openfiles, o) 801 } 802 803 return numFDs, openfiles, nil 804 } 805 806 // Get cwd from /proc/(pid)/cwd 807 func (p *Process) fillFromCwdWithContext(ctx context.Context) (string, error) { 808 pid := p.Pid 809 cwdPath := common.HostProc(strconv.Itoa(int(pid)), "cwd") 810 cwd, err := os.Readlink(cwdPath) 811 if err != nil { 812 return "", err 813 } 814 return string(cwd), nil 815 } 816 817 // Get exe from /proc/(pid)/exe 818 func (p *Process) fillFromExeWithContext(ctx context.Context) (string, error) { 819 pid := p.Pid 820 exePath := common.HostProc(strconv.Itoa(int(pid)), "exe") 821 exe, err := os.Readlink(exePath) 822 if err != nil { 823 return "", err 824 } 825 return string(exe), nil 826 } 827 828 // Get cmdline from /proc/(pid)/cmdline 829 func (p *Process) fillFromCmdlineWithContext(ctx context.Context) (string, error) { 830 pid := p.Pid 831 cmdPath := common.HostProc(strconv.Itoa(int(pid)), "cmdline") 832 cmdline, err := os.ReadFile(cmdPath) 833 if err != nil { 834 return "", err 835 } 836 ret := strings.FieldsFunc(string(cmdline), func(r rune) bool { 837 if r == '\u0000' { 838 return true 839 } 840 return false 841 }) 842 843 return strings.Join(ret, " "), nil 844 } 845 846 func (p *Process) fillSliceFromCmdlineWithContext(ctx context.Context) ([]string, error) { 847 pid := p.Pid 848 cmdPath := common.HostProc(strconv.Itoa(int(pid)), "cmdline") 849 cmdline, err := os.ReadFile(cmdPath) 850 if err != nil { 851 return nil, err 852 } 853 if len(cmdline) == 0 { 854 return nil, nil 855 } 856 if cmdline[len(cmdline)-1] == 0 { 857 cmdline = cmdline[:len(cmdline)-1] 858 } 859 parts := bytes.Split(cmdline, []byte{0}) 860 var strParts []string 861 for _, p := range parts { 862 strParts = append(strParts, string(p)) 863 } 864 865 return strParts, nil 866 } 867 868 // Get IO status from /proc/(pid)/io 869 func (p *Process) fillFromIOWithContext(ctx context.Context) (*IOCountersStat, error) { 870 pid := p.Pid 871 ioPath := common.HostProc(strconv.Itoa(int(pid)), "io") 872 ioline, err := os.ReadFile(ioPath) 873 if err != nil { 874 return nil, err 875 } 876 lines := strings.Split(string(ioline), "\n") 877 ret := &IOCountersStat{} 878 879 for _, line := range lines { 880 field := strings.Fields(line) 881 if len(field) < 2 { 882 continue 883 } 884 t, err := strconv.ParseUint(field[1], 10, 64) 885 if err != nil { 886 return nil, err 887 } 888 param := field[0] 889 if strings.HasSuffix(param, ":") { 890 param = param[:len(param)-1] 891 } 892 switch param { 893 case "syscr": 894 ret.ReadCount = t 895 case "syscw": 896 ret.WriteCount = t 897 case "read_bytes": 898 ret.ReadBytes = t 899 case "write_bytes": 900 ret.WriteBytes = t 901 } 902 } 903 904 return ret, nil 905 } 906 907 // Get memory info from /proc/(pid)/statm 908 func (p *Process) fillFromStatmWithContext(ctx context.Context) (*MemoryInfoStat, *MemoryInfoExStat, error) { 909 pid := p.Pid 910 memPath := common.HostProc(strconv.Itoa(int(pid)), "statm") 911 contents, err := os.ReadFile(memPath) 912 if err != nil { 913 return nil, nil, err 914 } 915 fields := strings.Split(string(contents), " ") 916 917 vms, err := strconv.ParseUint(fields[0], 10, 64) 918 if err != nil { 919 return nil, nil, err 920 } 921 rss, err := strconv.ParseUint(fields[1], 10, 64) 922 if err != nil { 923 return nil, nil, err 924 } 925 memInfo := &MemoryInfoStat{ 926 RSS: rss * PageSize, 927 VMS: vms * PageSize, 928 } 929 930 shared, err := strconv.ParseUint(fields[2], 10, 64) 931 if err != nil { 932 return nil, nil, err 933 } 934 text, err := strconv.ParseUint(fields[3], 10, 64) 935 if err != nil { 936 return nil, nil, err 937 } 938 lib, err := strconv.ParseUint(fields[4], 10, 64) 939 if err != nil { 940 return nil, nil, err 941 } 942 dirty, err := strconv.ParseUint(fields[5], 10, 64) 943 if err != nil { 944 return nil, nil, err 945 } 946 947 memInfoEx := &MemoryInfoExStat{ 948 RSS: rss * PageSize, 949 VMS: vms * PageSize, 950 Shared: shared * PageSize, 951 Text: text * PageSize, 952 Lib: lib * PageSize, 953 Dirty: dirty * PageSize, 954 } 955 956 return memInfo, memInfoEx, nil 957 } 958 959 // Get various status from /proc/(pid)/status 960 func (p *Process) fillFromStatusWithContext(ctx context.Context) error { 961 pid := p.Pid 962 statPath := common.HostProc(strconv.Itoa(int(pid)), "status") 963 contents, err := os.ReadFile(statPath) 964 if err != nil { 965 return err 966 } 967 lines := strings.Split(string(contents), "\n") 968 p.numCtxSwitches = &NumCtxSwitchesStat{} 969 p.memInfo = &MemoryInfoStat{} 970 p.sigInfo = &SignalInfoStat{} 971 for _, line := range lines { 972 tabParts := strings.SplitN(line, "\t", 2) 973 if len(tabParts) < 2 { 974 continue 975 } 976 value := tabParts[1] 977 switch strings.TrimRight(tabParts[0], ":") { 978 case "Name": 979 p.name = strings.Trim(value, " \t") 980 if len(p.name) >= 15 { 981 cmdlineSlice, err := p.CmdlineSlice() 982 if err != nil { 983 return err 984 } 985 if len(cmdlineSlice) > 0 { 986 extendedName := filepath.Base(cmdlineSlice[0]) 987 if strings.HasPrefix(extendedName, p.name) { 988 p.name = extendedName 989 } else { 990 p.name = cmdlineSlice[0] 991 } 992 } 993 } 994 case "State": 995 p.status = value[0:1] 996 case "PPid", "Ppid": 997 pval, err := strconv.ParseInt(value, 10, 32) 998 if err != nil { 999 return err 1000 } 1001 p.parent = int32(pval) 1002 case "Tgid": 1003 pval, err := strconv.ParseInt(value, 10, 32) 1004 if err != nil { 1005 return err 1006 } 1007 p.tgid = int32(pval) 1008 case "Uid": 1009 p.uids = make([]int32, 0, 4) 1010 for _, i := range strings.Split(value, "\t") { 1011 v, err := strconv.ParseInt(i, 10, 32) 1012 if err != nil { 1013 return err 1014 } 1015 p.uids = append(p.uids, int32(v)) 1016 } 1017 case "Gid": 1018 p.gids = make([]int32, 0, 4) 1019 for _, i := range strings.Split(value, "\t") { 1020 v, err := strconv.ParseInt(i, 10, 32) 1021 if err != nil { 1022 return err 1023 } 1024 p.gids = append(p.gids, int32(v)) 1025 } 1026 case "Groups": 1027 groups := strings.Fields(value) 1028 p.groups = make([]int32, 0, len(groups)) 1029 for _, i := range groups { 1030 v, err := strconv.ParseInt(i, 10, 32) 1031 if err != nil { 1032 return err 1033 } 1034 p.groups = append(p.groups, int32(v)) 1035 } 1036 case "Threads": 1037 v, err := strconv.ParseInt(value, 10, 32) 1038 if err != nil { 1039 return err 1040 } 1041 p.numThreads = int32(v) 1042 case "voluntary_ctxt_switches": 1043 v, err := strconv.ParseInt(value, 10, 64) 1044 if err != nil { 1045 return err 1046 } 1047 p.numCtxSwitches.Voluntary = v 1048 case "nonvoluntary_ctxt_switches": 1049 v, err := strconv.ParseInt(value, 10, 64) 1050 if err != nil { 1051 return err 1052 } 1053 p.numCtxSwitches.Involuntary = v 1054 case "VmRSS": 1055 value := strings.Trim(value, " kB") // remove last "kB" 1056 v, err := strconv.ParseUint(value, 10, 64) 1057 if err != nil { 1058 return err 1059 } 1060 p.memInfo.RSS = v * 1024 1061 case "VmSize": 1062 value := strings.Trim(value, " kB") // remove last "kB" 1063 v, err := strconv.ParseUint(value, 10, 64) 1064 if err != nil { 1065 return err 1066 } 1067 p.memInfo.VMS = v * 1024 1068 case "VmSwap": 1069 value := strings.Trim(value, " kB") // remove last "kB" 1070 v, err := strconv.ParseUint(value, 10, 64) 1071 if err != nil { 1072 return err 1073 } 1074 p.memInfo.Swap = v * 1024 1075 case "VmHWM": 1076 value := strings.Trim(value, " kB") // remove last "kB" 1077 v, err := strconv.ParseUint(value, 10, 64) 1078 if err != nil { 1079 return err 1080 } 1081 p.memInfo.HWM = v * 1024 1082 case "VmData": 1083 value := strings.Trim(value, " kB") // remove last "kB" 1084 v, err := strconv.ParseUint(value, 10, 64) 1085 if err != nil { 1086 return err 1087 } 1088 p.memInfo.Data = v * 1024 1089 case "VmStk": 1090 value := strings.Trim(value, " kB") // remove last "kB" 1091 v, err := strconv.ParseUint(value, 10, 64) 1092 if err != nil { 1093 return err 1094 } 1095 p.memInfo.Stack = v * 1024 1096 case "VmLck": 1097 value := strings.Trim(value, " kB") // remove last "kB" 1098 v, err := strconv.ParseUint(value, 10, 64) 1099 if err != nil { 1100 return err 1101 } 1102 p.memInfo.Locked = v * 1024 1103 case "SigPnd": 1104 v, err := strconv.ParseUint(value, 16, 64) 1105 if err != nil { 1106 return err 1107 } 1108 p.sigInfo.PendingThread = v 1109 case "ShdPnd": 1110 v, err := strconv.ParseUint(value, 16, 64) 1111 if err != nil { 1112 return err 1113 } 1114 p.sigInfo.PendingProcess = v 1115 case "SigBlk": 1116 v, err := strconv.ParseUint(value, 16, 64) 1117 if err != nil { 1118 return err 1119 } 1120 p.sigInfo.Blocked = v 1121 case "SigIgn": 1122 v, err := strconv.ParseUint(value, 16, 64) 1123 if err != nil { 1124 return err 1125 } 1126 p.sigInfo.Ignored = v 1127 case "SigCgt": 1128 v, err := strconv.ParseUint(value, 16, 64) 1129 if err != nil { 1130 return err 1131 } 1132 p.sigInfo.Caught = v 1133 } 1134 1135 } 1136 return nil 1137 } 1138 1139 func (p *Process) fillFromTIDStatWithContext(ctx context.Context, tid int32) (uint64, int32, *cpu.TimesStat, int64, uint32, int32, *PageFaultsStat, error) { 1140 pid := p.Pid 1141 var statPath string 1142 1143 if tid == -1 { 1144 statPath = common.HostProc(strconv.Itoa(int(pid)), "stat") 1145 } else { 1146 statPath = common.HostProc(strconv.Itoa(int(pid)), "task", strconv.Itoa(int(tid)), "stat") 1147 } 1148 1149 contents, err := os.ReadFile(statPath) 1150 if err != nil { 1151 return 0, 0, nil, 0, 0, 0, nil, err 1152 } 1153 fields := strings.Fields(string(contents)) 1154 1155 i := 1 1156 for !strings.HasSuffix(fields[i], ")") { 1157 i++ 1158 } 1159 1160 terminal, err := strconv.ParseUint(fields[i+5], 10, 64) 1161 if err != nil { 1162 return 0, 0, nil, 0, 0, 0, nil, err 1163 } 1164 1165 ppid, err := strconv.ParseInt(fields[i+2], 10, 32) 1166 if err != nil { 1167 return 0, 0, nil, 0, 0, 0, nil, err 1168 } 1169 utime, err := strconv.ParseFloat(fields[i+12], 64) 1170 if err != nil { 1171 return 0, 0, nil, 0, 0, 0, nil, err 1172 } 1173 1174 stime, err := strconv.ParseFloat(fields[i+13], 64) 1175 if err != nil { 1176 return 0, 0, nil, 0, 0, 0, nil, err 1177 } 1178 1179 // There is no such thing as iotime in stat file. As an approximation, we 1180 // will use delayacct_blkio_ticks (aggregated block I/O delays, as per Linux 1181 // docs). Note: I am assuming at least Linux 2.6.18 1182 iotime, err := strconv.ParseFloat(fields[i+40], 64) 1183 if err != nil { 1184 iotime = 0 // Ancient linux version, most likely 1185 } 1186 1187 cpuTimes := &cpu.TimesStat{ 1188 CPU: "cpu", 1189 User: float64(utime / ClockTicks), 1190 System: float64(stime / ClockTicks), 1191 Iowait: float64(iotime / ClockTicks), 1192 } 1193 1194 bootTime, _ := common.BootTimeWithContext(ctx) 1195 t, err := strconv.ParseUint(fields[i+20], 10, 64) 1196 if err != nil { 1197 return 0, 0, nil, 0, 0, 0, nil, err 1198 } 1199 ctime := (t / uint64(ClockTicks)) + uint64(bootTime) 1200 createTime := int64(ctime * 1000) 1201 1202 rtpriority, err := strconv.ParseInt(fields[i+16], 10, 32) 1203 if err != nil { 1204 return 0, 0, nil, 0, 0, 0, nil, err 1205 } 1206 if rtpriority < 0 { 1207 rtpriority = rtpriority*-1 - 1 1208 } else { 1209 rtpriority = 0 1210 } 1211 1212 // p.Nice = mustParseInt32(fields[18]) 1213 // use syscall instead of parse Stat file 1214 snice, _ := unix.Getpriority(PrioProcess, int(pid)) 1215 nice := int32(snice) // FIXME: is this true? 1216 1217 minFault, err := strconv.ParseUint(fields[i+8], 10, 64) 1218 if err != nil { 1219 return 0, 0, nil, 0, 0, 0, nil, err 1220 } 1221 cMinFault, err := strconv.ParseUint(fields[i+9], 10, 64) 1222 if err != nil { 1223 return 0, 0, nil, 0, 0, 0, nil, err 1224 } 1225 majFault, err := strconv.ParseUint(fields[i+10], 10, 64) 1226 if err != nil { 1227 return 0, 0, nil, 0, 0, 0, nil, err 1228 } 1229 cMajFault, err := strconv.ParseUint(fields[i+11], 10, 64) 1230 if err != nil { 1231 return 0, 0, nil, 0, 0, 0, nil, err 1232 } 1233 1234 faults := &PageFaultsStat{ 1235 MinorFaults: minFault, 1236 MajorFaults: majFault, 1237 ChildMinorFaults: cMinFault, 1238 ChildMajorFaults: cMajFault, 1239 } 1240 1241 return terminal, int32(ppid), cpuTimes, createTime, uint32(rtpriority), nice, faults, nil 1242 } 1243 1244 func (p *Process) fillFromStatWithContext(ctx context.Context) (uint64, int32, *cpu.TimesStat, int64, uint32, int32, *PageFaultsStat, error) { 1245 return p.fillFromTIDStatWithContext(ctx, -1) 1246 } 1247 1248 func pidsWithContext(ctx context.Context) ([]int32, error) { 1249 return readPidsFromDir(common.HostProc()) 1250 } 1251 1252 // Process returns a slice of pointers to Process structs for all 1253 // currently running processes. 1254 func Processes() ([]*Process, error) { 1255 return ProcessesWithContext(context.Background()) 1256 } 1257 1258 func ProcessesWithContext(ctx context.Context) ([]*Process, error) { 1259 out := []*Process{} 1260 1261 pids, err := PidsWithContext(ctx) 1262 if err != nil { 1263 return out, err 1264 } 1265 1266 for _, pid := range pids { 1267 p, err := NewProcess(pid) 1268 if err != nil { 1269 continue 1270 } 1271 out = append(out, p) 1272 } 1273 1274 return out, nil 1275 } 1276 1277 func readPidsFromDir(path string) ([]int32, error) { 1278 var ret []int32 1279 1280 d, err := os.Open(path) 1281 if err != nil { 1282 return nil, err 1283 } 1284 defer d.Close() 1285 1286 fnames, err := d.Readdirnames(-1) 1287 if err != nil { 1288 return nil, err 1289 } 1290 for _, fname := range fnames { 1291 pid, err := strconv.ParseInt(fname, 10, 32) 1292 if err != nil { 1293 // if not numeric name, just skip 1294 continue 1295 } 1296 ret = append(ret, int32(pid)) 1297 } 1298 1299 return ret, nil 1300 }