github.com/isyscore/isc-gobase@v1.5.3-0.20231218061332-cbc7451899e9/system/process/process_linux.go (about) 1 //go:build linux 2 3 package process 4 5 import ( 6 "bufio" 7 "bytes" 8 "context" 9 "encoding/json" 10 "fmt" 11 "github.com/isyscore/isc-gobase/system/common" 12 "github.com/isyscore/isc-gobase/system/cpu" 13 "github.com/isyscore/isc-gobase/system/net" 14 "github.com/tklauser/go-sysconf" 15 "golang.org/x/sys/unix" 16 "io/ioutil" 17 "math" 18 "os" 19 "path/filepath" 20 "strconv" 21 "strings" 22 ) 23 24 var PageSize = uint64(os.Getpagesize()) 25 26 const PrioProcess = 0 // linux/resource.h 27 28 var ClockTicks = 100 // default value 29 30 func init() { 31 clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK) 32 // ignore errors 33 if err == nil { 34 ClockTicks = int(clkTck) 35 } 36 } 37 38 // MemoryInfoExStat is different between OSes 39 type MemoryInfoExStat struct { 40 RSS uint64 `json:"rss"` // bytes 41 VMS uint64 `json:"vms"` // bytes 42 Shared uint64 `json:"shared"` // bytes 43 Text uint64 `json:"text"` // bytes 44 Lib uint64 `json:"lib"` // bytes 45 Data uint64 `json:"data"` // bytes 46 Dirty uint64 `json:"dirty"` // bytes 47 } 48 49 func (m MemoryInfoExStat) String() string { 50 s, _ := json.Marshal(m) 51 return string(s) 52 } 53 54 type MemoryMapsStat struct { 55 Path string `json:"path"` 56 Rss uint64 `json:"rss"` 57 Size uint64 `json:"size"` 58 Pss uint64 `json:"pss"` 59 SharedClean uint64 `json:"sharedClean"` 60 SharedDirty uint64 `json:"sharedDirty"` 61 PrivateClean uint64 `json:"privateClean"` 62 PrivateDirty uint64 `json:"privateDirty"` 63 Referenced uint64 `json:"referenced"` 64 Anonymous uint64 `json:"anonymous"` 65 Swap uint64 `json:"swap"` 66 } 67 68 // String returns JSON value of the process. 69 func (m MemoryMapsStat) String() string { 70 s, _ := json.Marshal(m) 71 return string(s) 72 } 73 74 func (p *Process) PpidWithContext(ctx context.Context) (int32, error) { 75 _, ppid, _, _, _, _, _, err := p.fillFromStatWithContext(ctx) 76 if err != nil { 77 return -1, err 78 } 79 return ppid, nil 80 } 81 82 func (p *Process) NameWithContext(ctx context.Context) (string, error) { 83 if p.name == "" { 84 if err := p.fillNameWithContext(ctx); err != nil { 85 return "", err 86 } 87 } 88 return p.name, nil 89 } 90 91 func (p *Process) TgidWithContext(ctx context.Context) (int32, error) { 92 if p.tgid == 0 { 93 if err := p.fillFromStatusWithContext(ctx); err != nil { 94 return 0, err 95 } 96 } 97 return p.tgid, nil 98 } 99 100 func (p *Process) ExeWithContext(ctx context.Context) (string, error) { 101 return p.fillFromExeWithContext(ctx) 102 } 103 104 func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) { 105 return p.fillFromCmdlineWithContext(ctx) 106 } 107 108 func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) { 109 return p.fillSliceFromCmdlineWithContext(ctx) 110 } 111 112 func (p *Process) createTimeWithContext(ctx context.Context) (int64, error) { 113 _, _, _, createTime, _, _, _, err := p.fillFromStatWithContext(ctx) 114 if err != nil { 115 return 0, err 116 } 117 return createTime, nil 118 } 119 120 func (p *Process) CwdWithContext(ctx context.Context) (string, error) { 121 return p.fillFromCwdWithContext(ctx) 122 } 123 124 func (p *Process) ParentWithContext(ctx context.Context) (*Process, error) { 125 err := p.fillFromStatusWithContext(ctx) 126 if err != nil { 127 return nil, err 128 } 129 if p.parent == 0 { 130 return nil, fmt.Errorf("wrong number of parents") 131 } 132 return NewProcessWithContext(ctx, p.parent) 133 } 134 135 func (p *Process) StatusWithContext(ctx context.Context) (string, error) { 136 err := p.fillFromStatusWithContext(ctx) 137 if err != nil { 138 return "", err 139 } 140 return p.status, nil 141 } 142 143 func (p *Process) ForegroundWithContext(ctx context.Context) (bool, error) { 144 pid := p.Pid 145 statPath := common.HostProc(strconv.Itoa(int(pid)), "stat") 146 contents, err := ioutil.ReadFile(statPath) 147 if err != nil { 148 return false, err 149 } 150 fields := strings.Fields(string(contents)) 151 if len(fields) < 8 { 152 return false, fmt.Errorf("insufficient data in %s", statPath) 153 } 154 pgid := fields[4] 155 tpgid := fields[7] 156 return pgid == tpgid, nil 157 } 158 159 func (p *Process) UidsWithContext(ctx context.Context) ([]int32, error) { 160 err := p.fillFromStatusWithContext(ctx) 161 if err != nil { 162 return []int32{}, err 163 } 164 return p.uids, nil 165 } 166 167 func (p *Process) GidsWithContext(ctx context.Context) ([]int32, error) { 168 err := p.fillFromStatusWithContext(ctx) 169 if err != nil { 170 return []int32{}, err 171 } 172 return p.gids, nil 173 } 174 175 func (p *Process) GroupsWithContext(ctx context.Context) ([]int32, error) { 176 err := p.fillFromStatusWithContext(ctx) 177 if err != nil { 178 return []int32{}, err 179 } 180 return p.groups, nil 181 } 182 183 func (p *Process) TerminalWithContext(ctx context.Context) (string, error) { 184 t, _, _, _, _, _, _, err := p.fillFromStatWithContext(ctx) 185 if err != nil { 186 return "", err 187 } 188 termmap, err := getTerminalMap() 189 if err != nil { 190 return "", err 191 } 192 terminal := termmap[t] 193 return terminal, nil 194 } 195 196 func (p *Process) NiceWithContext(ctx context.Context) (int32, error) { 197 _, _, _, _, _, nice, _, err := p.fillFromStatWithContext(ctx) 198 if err != nil { 199 return 0, err 200 } 201 return nice, nil 202 } 203 204 func (p *Process) IOniceWithContext(ctx context.Context) (int32, error) { 205 return 0, common.ErrNotImplementedError 206 } 207 208 func (p *Process) RlimitWithContext(ctx context.Context) ([]RlimitStat, error) { 209 return p.RlimitUsageWithContext(ctx, false) 210 } 211 212 func (p *Process) RlimitUsageWithContext(ctx context.Context, gatherUsed bool) ([]RlimitStat, error) { 213 rlimits, err := p.fillFromLimitsWithContext(ctx) 214 if !gatherUsed || err != nil { 215 return rlimits, err 216 } 217 218 _, _, _, _, rtprio, nice, _, err := p.fillFromStatWithContext(ctx) 219 if err != nil { 220 return nil, err 221 } 222 if err := p.fillFromStatusWithContext(ctx); err != nil { 223 return nil, err 224 } 225 226 for i := range rlimits { 227 rs := &rlimits[i] 228 switch rs.Resource { 229 case RLIMIT_CPU: 230 times, err := p.TimesWithContext(ctx) 231 if err != nil { 232 return nil, err 233 } 234 rs.Used = uint64(times.User + times.System) 235 case RLIMIT_DATA: 236 rs.Used = uint64(p.memInfo.Data) 237 case RLIMIT_STACK: 238 rs.Used = uint64(p.memInfo.Stack) 239 case RLIMIT_RSS: 240 rs.Used = uint64(p.memInfo.RSS) 241 case RLIMIT_NOFILE: 242 n, err := p.NumFDsWithContext(ctx) 243 if err != nil { 244 return nil, err 245 } 246 rs.Used = uint64(n) 247 case RLIMIT_MEMLOCK: 248 rs.Used = uint64(p.memInfo.Locked) 249 case RLIMIT_AS: 250 rs.Used = uint64(p.memInfo.VMS) 251 case RLIMIT_LOCKS: 252 //TODO we can get the used value from /proc/$pid/locks. But linux doesn't enforce it, so not a high priority. 253 case RLIMIT_SIGPENDING: 254 rs.Used = p.sigInfo.PendingProcess 255 case RLIMIT_NICE: 256 // 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. 257 // So effectively: if rs.Soft == 0 { rs.Soft = rs.Used } 258 rs.Used = uint64(nice) 259 case RLIMIT_RTPRIO: 260 rs.Used = uint64(rtprio) 261 } 262 } 263 264 return rlimits, err 265 } 266 267 func (p *Process) IOCountersWithContext(ctx context.Context) (*IOCountersStat, error) { 268 return p.fillFromIOWithContext(ctx) 269 } 270 271 func (p *Process) NumCtxSwitchesWithContext(ctx context.Context) (*NumCtxSwitchesStat, error) { 272 err := p.fillFromStatusWithContext(ctx) 273 if err != nil { 274 return nil, err 275 } 276 return p.numCtxSwitches, nil 277 } 278 279 func (p *Process) NumFDsWithContext(ctx context.Context) (int32, error) { 280 _, fnames, err := p.fillFromfdListWithContext(ctx) 281 return int32(len(fnames)), err 282 } 283 284 func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) { 285 err := p.fillFromStatusWithContext(ctx) 286 if err != nil { 287 return 0, err 288 } 289 return p.numThreads, nil 290 } 291 292 func (p *Process) ThreadsWithContext(ctx context.Context) (map[int32]*cpu.TimesStat, error) { 293 ret := make(map[int32]*cpu.TimesStat) 294 taskPath := common.HostProc(strconv.Itoa(int(p.Pid)), "task") 295 296 tids, err := readPidsFromDir(taskPath) 297 if err != nil { 298 return nil, err 299 } 300 301 for _, tid := range tids { 302 _, _, cpuTimes, _, _, _, _, err := p.fillFromTIDStatWithContext(ctx, tid) 303 if err != nil { 304 return nil, err 305 } 306 ret[tid] = cpuTimes 307 } 308 309 return ret, nil 310 } 311 312 func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) { 313 _, _, cpuTimes, _, _, _, _, err := p.fillFromStatWithContext(ctx) 314 if err != nil { 315 return nil, 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(ctx) 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(ctx) 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 349 func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) { 350 pids, err := common.CallPgrepWithContext(ctx, invoke, p.Pid) 351 if err != nil { 352 if len(pids) == 0 { 353 return nil, ErrorNoChildren 354 } 355 return nil, err 356 } 357 ret := make([]*Process, 0, len(pids)) 358 for _, pid := range pids { 359 np, err := NewProcessWithContext(ctx, pid) 360 if err != nil { 361 return nil, err 362 } 363 ret = append(ret, np) 364 } 365 return ret, nil 366 } 367 368 func (p *Process) OpenFilesWithContext(ctx context.Context) ([]OpenFilesStat, error) { 369 _, ofs, err := p.fillFromfdWithContext(ctx) 370 if err != nil { 371 return nil, err 372 } 373 ret := make([]OpenFilesStat, len(ofs)) 374 for i, o := range ofs { 375 ret[i] = *o 376 } 377 378 return ret, nil 379 } 380 381 func (p *Process) ConnectionsWithContext(ctx context.Context) ([]net.ConnectionStat, error) { 382 return net.ConnectionsPidWithContext(ctx, "all", p.Pid) 383 } 384 385 func (p *Process) ConnectionsMaxWithContext(ctx context.Context, max int) ([]net.ConnectionStat, error) { 386 return net.ConnectionsPidMaxWithContext(ctx, "all", p.Pid, max) 387 } 388 389 func (p *Process) NetIOCountersWithContext(ctx context.Context, pernic bool) ([]net.IOCountersStat, error) { 390 filename := common.HostProc(strconv.Itoa(int(p.Pid)), "net/dev") 391 return net.IOCountersByFileWithContext(ctx, pernic, filename) 392 } 393 394 func (p *Process) MemoryMapsWithContext(ctx context.Context, grouped bool) (*[]MemoryMapsStat, error) { 395 pid := p.Pid 396 var ret []MemoryMapsStat 397 if grouped { 398 ret = make([]MemoryMapsStat, 1) 399 } 400 smapsPath := common.HostProc(strconv.Itoa(int(pid)), "smaps") 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 for i, line := range lines { 456 fields := strings.Fields(line) 457 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 limitToInt(val string) (int32, error) { 507 if val == "unlimited" { 508 return math.MaxInt32, nil 509 } else { 510 res, err := strconv.ParseInt(val, 10, 32) 511 if err != nil { 512 return 0, err 513 } 514 return int32(res), nil 515 } 516 } 517 518 // Get name from /proc/(pid)/comm or /proc/(pid)/status 519 func (p *Process) fillNameWithContext(ctx context.Context) error { 520 err := p.fillFromCommWithContext(ctx) 521 if err == nil && p.name != "" && len(p.name) < 15 { 522 return nil 523 } 524 return p.fillFromStatusWithContext(ctx) 525 } 526 527 // Get name from /proc/(pid)/comm 528 func (p *Process) fillFromCommWithContext(ctx context.Context) error { 529 pid := p.Pid 530 statPath := common.HostProc(strconv.Itoa(int(pid)), "comm") 531 contents, err := ioutil.ReadFile(statPath) 532 if err != nil { 533 return err 534 } 535 536 p.name = strings.TrimSuffix(string(contents), "\n") 537 return nil 538 } 539 540 // Get num_fds from /proc/(pid)/limits 541 func (p *Process) fillFromLimitsWithContext(ctx context.Context) ([]RlimitStat, error) { 542 pid := p.Pid 543 limitsFile := common.HostProc(strconv.Itoa(int(pid)), "limits") 544 d, err := os.Open(limitsFile) 545 if err != nil { 546 return nil, err 547 } 548 defer d.Close() 549 550 var limitStats []RlimitStat 551 552 limitsScanner := bufio.NewScanner(d) 553 for limitsScanner.Scan() { 554 var statItem RlimitStat 555 556 str := strings.Fields(limitsScanner.Text()) 557 558 // Remove the header line 559 if strings.Contains(str[len(str)-1], "Units") { 560 continue 561 } 562 563 // Assert that last item is a Hard limit 564 statItem.Hard, err = limitToInt(str[len(str)-1]) 565 if err != nil { 566 // On error remove the last item an try once again since it can be unit or header line 567 str = str[:len(str)-1] 568 statItem.Hard, err = limitToInt(str[len(str)-1]) 569 if err != nil { 570 return nil, err 571 } 572 } 573 // Remove last item from string 574 str = str[:len(str)-1] 575 576 //Now last item is a Soft limit 577 statItem.Soft, err = limitToInt(str[len(str)-1]) 578 if err != nil { 579 return nil, err 580 } 581 // Remove last item from string 582 str = str[:len(str)-1] 583 584 //The rest is a stats name 585 resourceName := strings.Join(str, " ") 586 switch resourceName { 587 case "Max cpu time": 588 statItem.Resource = RLIMIT_CPU 589 case "Max file size": 590 statItem.Resource = RLIMIT_FSIZE 591 case "Max data size": 592 statItem.Resource = RLIMIT_DATA 593 case "Max stack size": 594 statItem.Resource = RLIMIT_STACK 595 case "Max core file size": 596 statItem.Resource = RLIMIT_CORE 597 case "Max resident set": 598 statItem.Resource = RLIMIT_RSS 599 case "Max processes": 600 statItem.Resource = RLIMIT_NPROC 601 case "Max open files": 602 statItem.Resource = RLIMIT_NOFILE 603 case "Max locked memory": 604 statItem.Resource = RLIMIT_MEMLOCK 605 case "Max address space": 606 statItem.Resource = RLIMIT_AS 607 case "Max file locks": 608 statItem.Resource = RLIMIT_LOCKS 609 case "Max pending signals": 610 statItem.Resource = RLIMIT_SIGPENDING 611 case "Max msgqueue size": 612 statItem.Resource = RLIMIT_MSGQUEUE 613 case "Max nice priority": 614 statItem.Resource = RLIMIT_NICE 615 case "Max realtime priority": 616 statItem.Resource = RLIMIT_RTPRIO 617 case "Max realtime timeout": 618 statItem.Resource = RLIMIT_RTTIME 619 default: 620 continue 621 } 622 623 limitStats = append(limitStats, statItem) 624 } 625 626 if err := limitsScanner.Err(); err != nil { 627 return nil, err 628 } 629 630 return limitStats, nil 631 } 632 633 // Get list of /proc/(pid)/fd files 634 func (p *Process) fillFromfdListWithContext(ctx context.Context) (string, []string, error) { 635 pid := p.Pid 636 statPath := common.HostProc(strconv.Itoa(int(pid)), "fd") 637 d, err := os.Open(statPath) 638 if err != nil { 639 return statPath, []string{}, err 640 } 641 defer d.Close() 642 fnames, err := d.Readdirnames(-1) 643 return statPath, fnames, err 644 } 645 646 // Get num_fds from /proc/(pid)/fd 647 func (p *Process) fillFromfdWithContext(ctx context.Context) (int32, []*OpenFilesStat, error) { 648 statPath, fnames, err := p.fillFromfdListWithContext(ctx) 649 if err != nil { 650 return 0, nil, err 651 } 652 numFDs := int32(len(fnames)) 653 654 var openfiles []*OpenFilesStat 655 for _, fd := range fnames { 656 fpath := filepath.Join(statPath, fd) 657 filepath, err := os.Readlink(fpath) 658 if err != nil { 659 continue 660 } 661 t, err := strconv.ParseUint(fd, 10, 64) 662 if err != nil { 663 return numFDs, openfiles, err 664 } 665 o := &OpenFilesStat{ 666 Path: filepath, 667 Fd: t, 668 } 669 openfiles = append(openfiles, o) 670 } 671 672 return numFDs, openfiles, nil 673 } 674 675 // Get cwd from /proc/(pid)/cwd 676 func (p *Process) fillFromCwdWithContext(ctx context.Context) (string, error) { 677 pid := p.Pid 678 cwdPath := common.HostProc(strconv.Itoa(int(pid)), "cwd") 679 cwd, err := os.Readlink(cwdPath) 680 if err != nil { 681 return "", err 682 } 683 return string(cwd), nil 684 } 685 686 // Get exe from /proc/(pid)/exe 687 func (p *Process) fillFromExeWithContext(ctx context.Context) (string, error) { 688 pid := p.Pid 689 exePath := common.HostProc(strconv.Itoa(int(pid)), "exe") 690 exe, err := os.Readlink(exePath) 691 if err != nil { 692 return "", err 693 } 694 return string(exe), nil 695 } 696 697 // Get cmdline from /proc/(pid)/cmdline 698 func (p *Process) fillFromCmdlineWithContext(ctx context.Context) (string, error) { 699 pid := p.Pid 700 cmdPath := common.HostProc(strconv.Itoa(int(pid)), "cmdline") 701 cmdline, err := ioutil.ReadFile(cmdPath) 702 if err != nil { 703 return "", err 704 } 705 ret := strings.FieldsFunc(string(cmdline), func(r rune) bool { 706 return r == '\u0000' 707 }) 708 709 return strings.Join(ret, " "), nil 710 } 711 712 func (p *Process) fillSliceFromCmdlineWithContext(ctx context.Context) ([]string, error) { 713 pid := p.Pid 714 cmdPath := common.HostProc(strconv.Itoa(int(pid)), "cmdline") 715 cmdline, err := ioutil.ReadFile(cmdPath) 716 if err != nil { 717 return nil, err 718 } 719 if len(cmdline) == 0 { 720 return nil, nil 721 } 722 if cmdline[len(cmdline)-1] == 0 { 723 cmdline = cmdline[:len(cmdline)-1] 724 } 725 parts := bytes.Split(cmdline, []byte{0}) 726 var strParts []string 727 for _, p := range parts { 728 strParts = append(strParts, string(p)) 729 } 730 731 return strParts, nil 732 } 733 734 // Get IO status from /proc/(pid)/io 735 func (p *Process) fillFromIOWithContext(ctx context.Context) (*IOCountersStat, error) { 736 pid := p.Pid 737 ioPath := common.HostProc(strconv.Itoa(int(pid)), "io") 738 ioline, err := ioutil.ReadFile(ioPath) 739 if err != nil { 740 return nil, err 741 } 742 lines := strings.Split(string(ioline), "\n") 743 ret := &IOCountersStat{} 744 745 for _, line := range lines { 746 field := strings.Fields(line) 747 if len(field) < 2 { 748 continue 749 } 750 t, err := strconv.ParseUint(field[1], 10, 64) 751 if err != nil { 752 return nil, err 753 } 754 param := field[0] 755 if strings.HasSuffix(param, ":") { 756 param = param[:len(param)-1] 757 } 758 switch param { 759 case "syscr": 760 ret.ReadCount = t 761 case "syscw": 762 ret.WriteCount = t 763 case "read_bytes": 764 ret.ReadBytes = t 765 case "write_bytes": 766 ret.WriteBytes = t 767 } 768 } 769 770 return ret, nil 771 } 772 773 // Get memory info from /proc/(pid)/statm 774 func (p *Process) fillFromStatmWithContext(ctx context.Context) (*MemoryInfoStat, *MemoryInfoExStat, error) { 775 pid := p.Pid 776 memPath := common.HostProc(strconv.Itoa(int(pid)), "statm") 777 contents, err := ioutil.ReadFile(memPath) 778 if err != nil { 779 return nil, nil, err 780 } 781 fields := strings.Split(string(contents), " ") 782 783 vms, err := strconv.ParseUint(fields[0], 10, 64) 784 if err != nil { 785 return nil, nil, err 786 } 787 rss, err := strconv.ParseUint(fields[1], 10, 64) 788 if err != nil { 789 return nil, nil, err 790 } 791 memInfo := &MemoryInfoStat{ 792 RSS: rss * PageSize, 793 VMS: vms * PageSize, 794 } 795 796 shared, err := strconv.ParseUint(fields[2], 10, 64) 797 if err != nil { 798 return nil, nil, err 799 } 800 text, err := strconv.ParseUint(fields[3], 10, 64) 801 if err != nil { 802 return nil, nil, err 803 } 804 lib, err := strconv.ParseUint(fields[4], 10, 64) 805 if err != nil { 806 return nil, nil, err 807 } 808 dirty, err := strconv.ParseUint(fields[5], 10, 64) 809 if err != nil { 810 return nil, nil, err 811 } 812 813 memInfoEx := &MemoryInfoExStat{ 814 RSS: rss * PageSize, 815 VMS: vms * PageSize, 816 Shared: shared * PageSize, 817 Text: text * PageSize, 818 Lib: lib * PageSize, 819 Dirty: dirty * PageSize, 820 } 821 822 return memInfo, memInfoEx, nil 823 } 824 825 // Get various status from /proc/(pid)/status 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.CmdlineSlice() 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 = 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) fillFromTIDStatWithContext(ctx context.Context, tid int32) (uint64, int32, *cpu.TimesStat, int64, uint32, int32, *PageFaultsStat, error) { 1025 pid := p.Pid 1026 var statPath string 1027 1028 if tid == -1 { 1029 statPath = common.HostProc(strconv.Itoa(int(pid)), "stat") 1030 } else { 1031 statPath = common.HostProc(strconv.Itoa(int(pid)), "task", strconv.Itoa(int(tid)), "stat") 1032 } 1033 1034 contents, err := ioutil.ReadFile(statPath) 1035 if err != nil { 1036 return 0, 0, nil, 0, 0, 0, nil, err 1037 } 1038 // Indexing from one, as described in `man proc` about the file /proc/[pid]/stat 1039 fields := splitProcStat(contents) 1040 1041 terminal, err := strconv.ParseUint(fields[7], 10, 64) 1042 if err != nil { 1043 return 0, 0, nil, 0, 0, 0, nil, err 1044 } 1045 1046 ppid, err := strconv.ParseInt(fields[4], 10, 32) 1047 if err != nil { 1048 return 0, 0, nil, 0, 0, 0, nil, err 1049 } 1050 utime, err := strconv.ParseFloat(fields[14], 64) 1051 if err != nil { 1052 return 0, 0, nil, 0, 0, 0, nil, err 1053 } 1054 1055 stime, err := strconv.ParseFloat(fields[15], 64) 1056 if err != nil { 1057 return 0, 0, nil, 0, 0, 0, nil, err 1058 } 1059 1060 // There is no such thing as iotime in stat file. As an approximation, we 1061 // will use delayacct_blkio_ticks (aggregated block I/O delays, as per Linux 1062 // docs). Note: I am assuming at least Linux 2.6.18 1063 var iotime float64 1064 if len(fields) > 42 { 1065 iotime, err = strconv.ParseFloat(fields[42], 64) 1066 if err != nil { 1067 iotime = 0 // Ancient linux version, most likely 1068 } 1069 } else { 1070 iotime = 0 // e.g. SmartOS containers 1071 } 1072 1073 cpuTimes := &cpu.TimesStat{ 1074 CPU: "cpu", 1075 User: utime / float64(ClockTicks), 1076 System: stime / float64(ClockTicks), 1077 Iowait: iotime / float64(ClockTicks), 1078 } 1079 1080 bootTime, _ := common.BootTimeWithContext(ctx) 1081 t, err := strconv.ParseUint(fields[22], 10, 64) 1082 if err != nil { 1083 return 0, 0, nil, 0, 0, 0, nil, err 1084 } 1085 ctime := (t / uint64(ClockTicks)) + uint64(bootTime) 1086 createTime := int64(ctime * 1000) 1087 1088 rtpriority, err := strconv.ParseInt(fields[18], 10, 32) 1089 if err != nil { 1090 return 0, 0, nil, 0, 0, 0, nil, err 1091 } 1092 if rtpriority < 0 { 1093 rtpriority = rtpriority*-1 - 1 1094 } else { 1095 rtpriority = 0 1096 } 1097 1098 // p.Nice = mustParseInt32(fields[18]) 1099 // use syscall instead of parse Stat file 1100 snice, _ := unix.Getpriority(PrioProcess, int(pid)) 1101 nice := int32(snice) // FIXME: is this true? 1102 1103 minFault, err := strconv.ParseUint(fields[10], 10, 64) 1104 if err != nil { 1105 return 0, 0, nil, 0, 0, 0, nil, err 1106 } 1107 cMinFault, err := strconv.ParseUint(fields[11], 10, 64) 1108 if err != nil { 1109 return 0, 0, nil, 0, 0, 0, nil, err 1110 } 1111 majFault, err := strconv.ParseUint(fields[12], 10, 64) 1112 if err != nil { 1113 return 0, 0, nil, 0, 0, 0, nil, err 1114 } 1115 cMajFault, err := strconv.ParseUint(fields[13], 10, 64) 1116 if err != nil { 1117 return 0, 0, nil, 0, 0, 0, nil, err 1118 } 1119 1120 faults := &PageFaultsStat{ 1121 MinorFaults: minFault, 1122 MajorFaults: majFault, 1123 ChildMinorFaults: cMinFault, 1124 ChildMajorFaults: cMajFault, 1125 } 1126 1127 return terminal, int32(ppid), cpuTimes, createTime, uint32(rtpriority), nice, faults, nil 1128 } 1129 1130 func (p *Process) fillFromStatWithContext(ctx context.Context) (uint64, int32, *cpu.TimesStat, int64, uint32, int32, *PageFaultsStat, error) { 1131 return p.fillFromTIDStatWithContext(ctx, -1) 1132 } 1133 1134 func pidsWithContext(ctx context.Context) ([]int32, error) { 1135 return readPidsFromDir(common.HostProc()) 1136 } 1137 1138 func ProcessesWithContext(ctx context.Context) ([]*Process, error) { 1139 out := []*Process{} 1140 1141 pids, err := PidsWithContext(ctx) 1142 if err != nil { 1143 return out, err 1144 } 1145 1146 for _, pid := range pids { 1147 p, err := NewProcessWithContext(ctx, pid) 1148 if err != nil { 1149 continue 1150 } 1151 out = append(out, p) 1152 } 1153 1154 return out, nil 1155 } 1156 1157 func readPidsFromDir(path string) ([]int32, error) { 1158 var ret []int32 1159 1160 d, err := os.Open(path) 1161 if err != nil { 1162 return nil, err 1163 } 1164 defer d.Close() 1165 1166 fnames, err := d.Readdirnames(-1) 1167 if err != nil { 1168 return nil, err 1169 } 1170 for _, fname := range fnames { 1171 pid, err := strconv.ParseInt(fname, 10, 32) 1172 if err != nil { 1173 // if not numeric name, just skip 1174 continue 1175 } 1176 ret = append(ret, int32(pid)) 1177 } 1178 1179 return ret, nil 1180 } 1181 1182 func splitProcStat(content []byte) []string { 1183 nameStart := bytes.IndexByte(content, '(') 1184 nameEnd := bytes.LastIndexByte(content, ')') 1185 restFields := strings.Fields(string(content[nameEnd+2:])) // +2 skip ') ' 1186 name := content[nameStart+1 : nameEnd] 1187 pid := strings.TrimSpace(string(content[:nameStart])) 1188 fields := make([]string, 3, len(restFields)+3) 1189 fields[1] = string(pid) 1190 fields[2] = string(name) 1191 fields = append(fields, restFields...) 1192 return fields 1193 }