From 60c32eb847785a0044943a412759f482b41123f5 Mon Sep 17 00:00:00 2001 From: Patrick Hemmer Date: Wed, 5 Apr 2017 12:01:59 -0400 Subject: [PATCH] add current values to rlimit retrieval --- process/process.go | 25 +++++-- process/process_darwin.go | 4 + process/process_fallback.go | 3 + process/process_freebsd.go | 4 + process/process_linux.go | 145 ++++++++++++++++++++++++++++++++---- process/process_openbsd.go | 4 + process/process_windows.go | 5 ++ 7 files changed, 170 insertions(+), 20 deletions(-) diff --git a/process/process.go b/process/process.go index b97f777f..84ba495e 100644 --- a/process/process.go +++ b/process/process.go @@ -26,6 +26,7 @@ type Process struct { gids []int32 numThreads int32 memInfo *MemoryInfoStat + sigInfo *SignalInfoStat lastCPUTimes *cpu.TimesStat lastCPUTime time.Time @@ -37,15 +38,27 @@ type OpenFilesStat struct { } type MemoryInfoStat struct { - RSS uint64 `json:"rss"` // bytes - VMS uint64 `json:"vms"` // bytes - Swap uint64 `json:"swap"` // bytes + RSS uint64 `json:"rss"` // bytes + VMS uint64 `json:"vms"` // bytes + Data uint64 `json:"data"` // bytes + Stack uint64 `json:"stack"` // bytes + Locked uint64 `json:"locked"` // bytes + Swap uint64 `json:"swap"` // bytes +} + +type SignalInfoStat struct { + PendingProcess uint64 `json:"pending_process"` + PendingThread uint64 `json:"pending_thread"` + Blocked uint64 `json:"blocked"` + Ignored uint64 `json:"ignored"` + Caught uint64 `json:"caught"` } type RlimitStat struct { - Resource int32 `json:"resource"` - Soft int32 `json:"soft"` - Hard int32 `json:"hard"` + Resource int32 `json:"resource"` + Soft int32 `json:"soft"` //TODO too small. needs to be uint64 + Hard int32 `json:"hard"` //TODO too small. needs to be uint64 + Used uint64 `json:"used"` } type IOCountersStat struct { diff --git a/process/process_darwin.go b/process/process_darwin.go index 883c88bb..568fb652 100644 --- a/process/process_darwin.go +++ b/process/process_darwin.go @@ -248,6 +248,10 @@ func (p *Process) Rlimit() ([]RlimitStat, error) { var rlimit []RlimitStat return rlimit, common.ErrNotImplementedError } +func (p *Process) RlimitUsage(_ bool) ([]RlimitStat, error) { + var rlimit []RlimitStat + return rlimit, common.ErrNotImplementedError +} func (p *Process) IOCounters() (*IOCountersStat, error) { return nil, common.ErrNotImplementedError } diff --git a/process/process_fallback.go b/process/process_fallback.go index ed622a81..bf09875a 100644 --- a/process/process_fallback.go +++ b/process/process_fallback.go @@ -80,6 +80,9 @@ func (p *Process) IOnice() (int32, error) { func (p *Process) Rlimit() ([]RlimitStat, error) { return nil, common.ErrNotImplementedError } +func (p *Process) RlimitUsage(_ bool) ([]RlimitStat, error) { + return nil, common.ErrNotImplementedError +} func (p *Process) IOCounters() (*IOCountersStat, error) { return nil, common.ErrNotImplementedError } diff --git a/process/process_freebsd.go b/process/process_freebsd.go index 5362128e..90e16ec8 100644 --- a/process/process_freebsd.go +++ b/process/process_freebsd.go @@ -176,6 +176,10 @@ func (p *Process) Rlimit() ([]RlimitStat, error) { var rlimit []RlimitStat return rlimit, common.ErrNotImplementedError } +func (p *Process) RlimitUsage(_ bool) ([]RlimitStat, error) { + var rlimit []RlimitStat + return rlimit, common.ErrNotImplementedError +} func (p *Process) IOCounters() (*IOCountersStat, error) { k, err := p.getKProc() if err != nil { diff --git a/process/process_linux.go b/process/process_linux.go index 5b5c6bcc..9277b029 100644 --- a/process/process_linux.go +++ b/process/process_linux.go @@ -83,7 +83,7 @@ func NewProcess(pid int32) (*Process, error) { // Ppid returns Parent Process ID of the process. func (p *Process) Ppid() (int32, error) { - _, ppid, _, _, _, err := p.fillFromStat() + _, ppid, _, _, _, _, err := p.fillFromStat() if err != nil { return -1, err } @@ -119,7 +119,7 @@ func (p *Process) CmdlineSlice() ([]string, error) { // CreateTime returns created time of the process in seconds since the epoch, in UTC. func (p *Process) CreateTime() (int64, error) { - _, _, _, createTime, _, err := p.fillFromStat() + _, _, _, createTime, _, _, err := p.fillFromStat() if err != nil { return 0, err } @@ -176,7 +176,7 @@ func (p *Process) Gids() ([]int32, error) { // Terminal returns a terminal which is associated with the process. func (p *Process) Terminal() (string, error) { - terminal, _, _, _, _, err := p.fillFromStat() + terminal, _, _, _, _, _, err := p.fillFromStat() if err != nil { return "", err } @@ -186,7 +186,7 @@ func (p *Process) Terminal() (string, error) { // Nice returns a nice value (priority). // Notice: gopsutil can not set nice value. func (p *Process) Nice() (int32, error) { - _, _, _, _, nice, err := p.fillFromStat() + _, _, _, _, _, nice, err := p.fillFromStat() if err != nil { return 0, err } @@ -200,7 +200,65 @@ func (p *Process) IOnice() (int32, error) { // Rlimit returns Resource Limits. func (p *Process) Rlimit() ([]RlimitStat, error) { - return p.fillFromLimits() + return p.RlimitUsage(false) +} + +// RlimitUsage returns Resource Limits. +// If gatherUsed is true, the currently used value will be gathered and added +// to the resulting RlimitStat. +func (p *Process) RlimitUsage(gatherUsed bool) ([]RlimitStat, error) { + rlimits, err := p.fillFromLimits() + if !gatherUsed || err != nil { + return rlimits, err + } + + _, _, _, _, rtprio, nice, err := p.fillFromStat() + if err != nil { + return nil, err + } + if err := p.fillFromStatus(); err != nil { + return nil, err + } + + for i := range rlimits { + rs := &rlimits[i] + switch rs.Resource { + case RLIMIT_CPU: + times, err := p.Times() + if err != nil { + return nil, err + } + rs.Used = uint64(times.User + times.System) + case RLIMIT_DATA: + rs.Used = uint64(p.memInfo.Data) + case RLIMIT_STACK: + rs.Used = uint64(p.memInfo.Stack) + case RLIMIT_RSS: + rs.Used = uint64(p.memInfo.RSS) + case RLIMIT_NOFILE: + n, err := p.NumFDs() + if err != nil { + return nil, err + } + rs.Used = uint64(n) + case RLIMIT_MEMLOCK: + rs.Used = uint64(p.memInfo.Locked) + case RLIMIT_AS: + rs.Used = uint64(p.memInfo.VMS) + case RLIMIT_LOCKS: + //TODO we can get the used value from /proc/$pid/locks. But linux doesn't enforce it, so not a high priority. + case RLIMIT_SIGPENDING: + rs.Used = p.sigInfo.PendingProcess + case RLIMIT_NICE: + // 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. + // So effectively: if rs.Soft == 0 { rs.Soft = rs.Used } + rs.Used = uint64(nice) + case RLIMIT_RTPRIO: + rs.Used = uint64(rtprio) + } + } + + return rlimits, err } // IOCounters returns IO Counters. @@ -242,7 +300,7 @@ func (p *Process) Threads() (map[string]string, error) { // Times returns CPU times of the process. func (p *Process) Times() (*cpu.TimesStat, error) { - _, _, cpuTimes, _, _, err := p.fillFromStat() + _, _, cpuTimes, _, _, _, err := p.fillFromStat() if err != nil { return nil, err } @@ -711,6 +769,7 @@ func (p *Process) fillFromStatus() error { lines := strings.Split(string(contents), "\n") p.numCtxSwitches = &NumCtxSwitchesStat{} p.memInfo = &MemoryInfoStat{} + p.sigInfo = &SignalInfoStat{} for _, line := range lines { tabParts := strings.SplitN(line, "\t", 2) if len(tabParts) < 2 { @@ -797,18 +856,69 @@ func (p *Process) fillFromStatus() error { return err } p.memInfo.Swap = v * 1024 + case "VmData": + value := strings.Trim(value, " kB") // remove last "kB" + v, err := strconv.ParseUint(value, 10, 64) + if err != nil { + return err + } + p.memInfo.Data = v * 1024 + case "VmStk": + value := strings.Trim(value, " kB") // remove last "kB" + v, err := strconv.ParseUint(value, 10, 64) + if err != nil { + return err + } + p.memInfo.Stack = v * 1024 + case "VmLck": + value := strings.Trim(value, " kB") // remove last "kB" + v, err := strconv.ParseUint(value, 10, 64) + if err != nil { + return err + } + p.memInfo.Locked = v * 1024 + case "SigPnd": + v, err := strconv.ParseUint(value, 16, 64) + if err != nil { + return err + } + p.sigInfo.PendingThread = v + case "ShdPnd": + v, err := strconv.ParseUint(value, 16, 64) + if err != nil { + return err + } + p.sigInfo.PendingProcess = v + case "SigBlk": + v, err := strconv.ParseUint(value, 16, 64) + if err != nil { + return err + } + p.sigInfo.Blocked = v + case "SigIgn": + v, err := strconv.ParseUint(value, 16, 64) + if err != nil { + return err + } + p.sigInfo.Ignored = v + case "SigCgt": + v, err := strconv.ParseUint(value, 16, 64) + if err != nil { + return err + } + p.sigInfo.Caught = v } } return nil } -func (p *Process) fillFromStat() (string, int32, *cpu.TimesStat, int64, int32, error) { +func (p *Process) fillFromStat() (string, int32, *cpu.TimesStat, int64, uint32, int32, error) { pid := p.Pid statPath := common.HostProc(strconv.Itoa(int(pid)), "stat") contents, err := ioutil.ReadFile(statPath) if err != nil { - return "", 0, nil, 0, 0, err + return "", 0, nil, 0, 0, 0, err } fields := strings.Fields(string(contents)) @@ -822,23 +932,23 @@ func (p *Process) fillFromStat() (string, int32, *cpu.TimesStat, int64, int32, e if err == nil { t, err := strconv.ParseUint(fields[i+5], 10, 64) if err != nil { - return "", 0, nil, 0, 0, err + return "", 0, nil, 0, 0, 0, err } terminal = termmap[t] } ppid, err := strconv.ParseInt(fields[i+2], 10, 32) if err != nil { - return "", 0, nil, 0, 0, err + return "", 0, nil, 0, 0, 0, err } utime, err := strconv.ParseFloat(fields[i+12], 64) if err != nil { - return "", 0, nil, 0, 0, err + return "", 0, nil, 0, 0, 0, err } stime, err := strconv.ParseFloat(fields[i+13], 64) if err != nil { - return "", 0, nil, 0, 0, err + return "", 0, nil, 0, 0, 0, err } cpuTimes := &cpu.TimesStat{ @@ -850,17 +960,24 @@ func (p *Process) fillFromStat() (string, int32, *cpu.TimesStat, int64, int32, e bootTime, _ := host.BootTime() t, err := strconv.ParseUint(fields[i+20], 10, 64) if err != nil { - return "", 0, nil, 0, 0, err + return "", 0, nil, 0, 0, 0, err } ctime := (t / uint64(ClockTicks)) + uint64(bootTime) createTime := int64(ctime * 1000) + rtpriority, err := strconv.ParseInt(fields[i+16], 10, 32) + if rtpriority < 0 { + rtpriority = rtpriority*-1 - 1 + } else { + rtpriority = 0 + } + // p.Nice = mustParseInt32(fields[18]) // use syscall instead of parse Stat file snice, _ := syscall.Getpriority(PrioProcess, int(pid)) nice := int32(snice) // FIXME: is this true? - return terminal, int32(ppid), cpuTimes, createTime, nice, nil + return terminal, int32(ppid), cpuTimes, createTime, uint32(rtpriority), nice, nil } // Pids returns a slice of process ID list which are running now. diff --git a/process/process_openbsd.go b/process/process_openbsd.go index b51826ce..759422ad 100644 --- a/process/process_openbsd.go +++ b/process/process_openbsd.go @@ -170,6 +170,10 @@ func (p *Process) Rlimit() ([]RlimitStat, error) { var rlimit []RlimitStat return rlimit, common.ErrNotImplementedError } +func (p *Process) RlimitUsage(_ bool) ([]RlimitStat, error) { + var rlimit []RlimitStat + return rlimit, common.ErrNotImplementedError +} func (p *Process) IOCounters() (*IOCountersStat, error) { k, err := p.getKProc() if err != nil { diff --git a/process/process_windows.go b/process/process_windows.go index 7507ab03..365d20fb 100644 --- a/process/process_windows.go +++ b/process/process_windows.go @@ -212,6 +212,11 @@ func (p *Process) Rlimit() ([]RlimitStat, error) { return rlimit, common.ErrNotImplementedError } +func (p *Process) RlimitUsage(_ bool) ([]RlimitStat, error) { + var rlimit []RlimitStat + + return rlimit, common.ErrNotImplementedError +} func (p *Process) IOCounters() (*IOCountersStat, error) { dst, err := GetWin32Proc(p.Pid)