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