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