Merge branch 'master' into master

pull/593/head
Nico Vinzens 6 years ago committed by GitHub
commit 1a7a39a789
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,26 @@
---
name: Bug report
about: Create a report to help us improve gopsutil
---
**Describe the bug**
[A clear and concise description of what the bug is.]
**To Reproduce**
```go
// paste example code reproducing the bug you are reporting
```
**Expected behavior**
[A clear and concise description of what you expected to happen.]
**Environment (please complete the following information):**
- [ ] Windows: [paste the result of `ver`]
- [ ] Linux: [paste contents of `/etc/os-release` and the result of `uname -a`]
- [ ] Mac OS: [paste the result of `sw_vers` and `uname -a`
- [ ] FreeBSD: [paste the result of `freebsd-version -k -r -u` and `uname -a`]
- [ ] OpenBSD: [paste the result of `uname -a`]
**Additional context**
[Cross-compiling? Paste the command you are using to cross-compile and the result of the corresponding `go env`]

@ -0,0 +1,17 @@
---
name: Feature request
about: Suggest an idea for gopsutil
---
**Is your feature request related to a problem? Please describe.**
[A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]]
**Describe the solution you'd like**
[A clear and concise description of what you want to happen.]
**Describe alternatives you've considered**
[A clear and concise description of any alternative solutions or features you've considered.]
**Additional context**
[Add any other context or screenshots about the feature request here.]

@ -241,6 +241,7 @@ parent x x x x
children x x x x x
connections x x x
is_running
page_faults x
================ ===== ======= ======= ====== =======
Original Metrics

@ -16,7 +16,7 @@ import (
)
// sys/sched.h
const (
var (
CPUser = 0
CPNice = 1
CPSys = 2
@ -35,18 +35,36 @@ const (
var ClocksPerSec = float64(128)
func init() {
getconf, err := exec.LookPath("/usr/bin/getconf")
if err != nil {
return
}
out, err := invoke.Command(getconf, "CLK_TCK")
// ignore errors
if err == nil {
i, err := strconv.ParseFloat(strings.TrimSpace(string(out)), 64)
func() {
getconf, err := exec.LookPath("/usr/bin/getconf")
if err != nil {
return
}
out, err := invoke.Command(getconf, "CLK_TCK")
// ignore errors
if err == nil {
ClocksPerSec = float64(i)
i, err := strconv.ParseFloat(strings.TrimSpace(string(out)), 64)
if err == nil {
ClocksPerSec = float64(i)
}
}
}
}()
func() {
v, err := unix.Sysctl("kern.osrelease") // can't reuse host.PlatformInformation because of circular import
if err != nil {
return
}
v = strings.ToLower(v)
version, err := strconv.ParseFloat(v, 64)
if err != nil {
return
}
if version >= 6.4 {
CPIntr = 4
CPIdle = 5
CPUStates = 6
}
}()
}
func Times(percpu bool) ([]TimesStat, error) {
@ -64,7 +82,7 @@ func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
}
for i := 0; i < ncpu; i++ {
var cpuTimes [CPUStates]int64
var cpuTimes = make([]int64, CPUStates)
var mib []int32
if percpu {
mib = []int32{CTLKern, KernCptime}
@ -106,14 +124,24 @@ func Info() ([]InfoStat, error) {
func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
var ret []InfoStat
var err error
c := InfoStat{}
v, err := unix.Sysctl("hw.model")
if err != nil {
var u32 uint32
if u32, err = unix.SysctlUint32("hw.cpuspeed"); err != nil {
return nil, err
}
c.Mhz = float64(u32)
if u32, err = unix.SysctlUint32("hw.ncpuonline"); err != nil {
return nil, err
}
c.Cores = int32(u32)
if c.ModelName, err = unix.Sysctl("hw.model"); err != nil {
return nil, err
}
c.ModelName = v
return append(ret, c), nil
}

@ -6,6 +6,8 @@ import (
"runtime"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestCpu_times(t *testing.T) {
@ -22,6 +24,34 @@ func TestCpu_times(t *testing.T) {
t.Errorf("could not get CPU User: %v", vv)
}
}
// test sum of per cpu stats is within margin of error for cpu total stats
cpuTotal, err := Times(false)
if err != nil {
t.Errorf("error %v", err)
}
if len(cpuTotal) == 0 {
t.Error("could not get CPUs ", err)
}
perCPU, err := Times(true)
if err != nil {
t.Errorf("error %v", err)
}
if len(perCPU) == 0 {
t.Error("could not get CPUs ", err)
}
var perCPUUserTimeSum float64
var perCPUSystemTimeSum float64
var perCPUIdleTimeSum float64
for _, pc := range perCPU {
perCPUUserTimeSum += pc.User
perCPUSystemTimeSum += pc.System
perCPUIdleTimeSum += pc.Idle
}
margin := 2.0
assert.InEpsilon(t, cpuTotal[0].User, perCPUUserTimeSum, margin)
assert.InEpsilon(t, cpuTotal[0].System, perCPUSystemTimeSum, margin)
assert.InEpsilon(t, cpuTotal[0].Idle, perCPUIdleTimeSum, margin)
}
func TestCpu_counts(t *testing.T) {

@ -23,8 +23,7 @@ type Win32_Processor struct {
MaxClockSpeed uint32
}
// Win32_PerfFormattedData_Counters_ProcessorInformation stores instance value of the perf counters
type Win32_PerfFormattedData_Counters_ProcessorInformation struct {
type win32_PerfRawData_Counters_ProcessorInformation struct {
Name string
PercentDPCTime uint64
PercentIdleTime uint64
@ -44,6 +43,10 @@ type Win32_PerfFormattedData_PerfOS_System struct {
ProcessorQueueLength uint32
}
const (
win32_TicksPerSecond = 10000000.0
)
// Times returns times stat per cpu and combined for all CPUs
func Times(percpu bool) ([]TimesStat, error) {
return TimesWithContext(context.Background(), percpu)
@ -51,7 +54,7 @@ func Times(percpu bool) ([]TimesStat, error) {
func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
if percpu {
return perCPUTimes()
return perCPUTimesWithContext(ctx)
}
var ret []TimesStat
@ -119,17 +122,13 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
// PerfInfo returns the performance counter's instance value for ProcessorInformation.
// Name property is the key by which overall, per cpu and per core metric is known.
func PerfInfo() ([]Win32_PerfFormattedData_Counters_ProcessorInformation, error) {
return PerfInfoWithContext(context.Background())
}
func perfInfoWithContext(ctx context.Context) ([]win32_PerfRawData_Counters_ProcessorInformation, error) {
var ret []win32_PerfRawData_Counters_ProcessorInformation
func PerfInfoWithContext(ctx context.Context) ([]Win32_PerfFormattedData_Counters_ProcessorInformation, error) {
var ret []Win32_PerfFormattedData_Counters_ProcessorInformation
q := wmi.CreateQuery(&ret, "")
q := wmi.CreateQuery(&ret, "WHERE NOT Name LIKE '%_Total'")
err := common.WMIQueryWithContext(ctx, q, &ret)
if err != nil {
return []Win32_PerfFormattedData_Counters_ProcessorInformation{}, err
return []win32_PerfRawData_Counters_ProcessorInformation{}, err
}
return ret, err
@ -152,19 +151,19 @@ func ProcInfoWithContext(ctx context.Context) ([]Win32_PerfFormattedData_PerfOS_
}
// perCPUTimes returns times stat per cpu, per core and overall for all CPUs
func perCPUTimes() ([]TimesStat, error) {
func perCPUTimesWithContext(ctx context.Context) ([]TimesStat, error) {
var ret []TimesStat
stats, err := PerfInfo()
stats, err := perfInfoWithContext(ctx)
if err != nil {
return nil, err
}
for _, v := range stats {
c := TimesStat{
CPU: v.Name,
User: float64(v.PercentUserTime),
System: float64(v.PercentPrivilegedTime),
Idle: float64(v.PercentIdleTime),
Irq: float64(v.PercentInterruptTime),
User: float64(v.PercentUserTime) / win32_TicksPerSecond,
System: float64(v.PercentPrivilegedTime) / win32_TicksPerSecond,
Idle: float64(v.PercentIdleTime) / win32_TicksPerSecond,
Irq: float64(v.PercentInterruptTime) / win32_TicksPerSecond,
}
ret = append(ret, c)
}

@ -460,7 +460,7 @@ func GetLabel(name string) string {
if err != nil {
return ""
} else {
return string(dmname)
return strings.TrimSpace(string(dmname))
}
}

@ -15,7 +15,7 @@ var (
procGetDiskFreeSpaceExW = common.Modkernel32.NewProc("GetDiskFreeSpaceExW")
procGetLogicalDriveStringsW = common.Modkernel32.NewProc("GetLogicalDriveStringsW")
procGetDriveType = common.Modkernel32.NewProc("GetDriveTypeW")
provGetVolumeInformation = common.Modkernel32.NewProc("GetVolumeInformationW")
procGetVolumeInformation = common.Modkernel32.NewProc("GetVolumeInformationW")
)
var (
@ -83,9 +83,6 @@ func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, erro
for _, v := range lpBuffer {
if v >= 65 && v <= 90 {
path := string(v) + ":"
if path == "A:" || path == "B:" { // skip floppy drives
continue
}
typepath, _ := windows.UTF16PtrFromString(path)
typeret, _, _ := procGetDriveType.Call(uintptr(unsafe.Pointer(typepath)))
if typeret == 0 {
@ -100,7 +97,7 @@ func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, erro
lpFileSystemFlags := int64(0)
lpFileSystemNameBuffer := make([]byte, 256)
volpath, _ := windows.UTF16PtrFromString(string(v) + ":/")
driveret, _, err := provGetVolumeInformation.Call(
driveret, _, err := procGetVolumeInformation.Call(
uintptr(unsafe.Pointer(volpath)),
uintptr(unsafe.Pointer(&lpVolumeNameBuffer[0])),
uintptr(len(lpVolumeNameBuffer)),

@ -149,6 +149,10 @@ func CgroupCPUDocker(containerid string) (*cpu.TimesStat, error) {
return CgroupCPUDockerWithContext(context.Background(), containerid)
}
func CgroupCPUUsageDocker(containerid string) (float64, error) {
return CgroupCPUDockerUsageWithContext(context.Background(), containerid)
}
func CgroupCPUDockerWithContext(ctx context.Context, containerid string) (*cpu.TimesStat, error) {
return CgroupCPU(containerid, common.HostSys("fs/cgroup/cpuacct/docker"))
}
@ -196,43 +200,43 @@ func CgroupMemWithContext(ctx context.Context, containerID string, base string)
ret.Pgfault = v
case "pgmajfault":
ret.Pgmajfault = v
case "inactiveAnon":
case "inactiveAnon", "inactive_anon":
ret.InactiveAnon = v
case "activeAnon":
case "activeAnon", "active_anon":
ret.ActiveAnon = v
case "inactiveFile":
case "inactiveFile", "inactive_file":
ret.InactiveFile = v
case "activeFile":
case "activeFile", "active_file":
ret.ActiveFile = v
case "unevictable":
ret.Unevictable = v
case "hierarchicalMemoryLimit":
case "hierarchicalMemoryLimit", "hierarchical_memory_limit":
ret.HierarchicalMemoryLimit = v
case "totalCache":
case "totalCache", "total_cache":
ret.TotalCache = v
case "totalRss":
case "totalRss", "total_rss":
ret.TotalRSS = v
case "totalRssHuge":
case "totalRssHuge", "total_rss_huge":
ret.TotalRSSHuge = v
case "totalMappedFile":
case "totalMappedFile", "total_mapped_file":
ret.TotalMappedFile = v
case "totalPgpgin":
case "totalPgpgin", "total_pgpgin":
ret.TotalPgpgIn = v
case "totalPgpgout":
case "totalPgpgout", "total_pgpgout":
ret.TotalPgpgOut = v
case "totalPgfault":
case "totalPgfault", "total_pgfault":
ret.TotalPgFault = v
case "totalPgmajfault":
case "totalPgmajfault", "total_pgmajfault":
ret.TotalPgMajFault = v
case "totalInactiveAnon":
case "totalInactiveAnon", "total_inactive_anon":
ret.TotalInactiveAnon = v
case "totalActiveAnon":
case "totalActiveAnon", "total_active_anon":
ret.TotalActiveAnon = v
case "totalInactiveFile":
case "totalInactiveFile", "total_inactive_file":
ret.TotalInactiveFile = v
case "totalActiveFile":
case "totalActiveFile", "total_active_file":
ret.TotalActiveFile = v
case "totalUnevictable":
case "totalUnevictable", "total_unevictable":
ret.TotalUnevictable = v
}
}

@ -18,6 +18,7 @@ import (
"github.com/shirou/gopsutil/internal/common"
"github.com/shirou/gopsutil/process"
"golang.org/x/sys/unix"
)
// from utmpx.h
@ -180,17 +181,13 @@ func PlatformInformationWithContext(ctx context.Context) (string, string, string
if err != nil {
return "", "", "", err
}
uname, err := exec.LookPath("uname")
if err != nil {
return "", "", "", err
}
out, err := invoke.CommandWithContext(ctx, uname, "-s")
p, err := unix.Sysctl("kern.ostype")
if err == nil {
platform = strings.ToLower(strings.TrimSpace(string(out)))
platform = strings.ToLower(p)
}
out, err = invoke.CommandWithContext(ctx, sw_vers, "-productVersion")
out, err := invoke.CommandWithContext(ctx, sw_vers, "-productVersion")
if err == nil {
pver = strings.ToLower(strings.TrimSpace(string(out)))
}
@ -211,16 +208,8 @@ func KernelVersion() (string, error) {
}
func KernelVersionWithContext(ctx context.Context) (string, error) {
uname, err := exec.LookPath("uname")
if err != nil {
return "", err
}
out, err := invoke.CommandWithContext(ctx, uname, "-r")
if err != nil {
return "", err
}
version := strings.ToLower(strings.TrimSpace(string(out)))
return version, err
version, err := unix.Sysctl("kern.osrelease")
return strings.ToLower(version), err
}
func SensorsTemperatures() ([]TemperatureStat, error) {

@ -73,7 +73,10 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
}
sysProductUUID := common.HostSys("class/dmi/id/product_uuid")
machineID := common.HostEtc("machine-id")
switch {
// In order to read this file, needs to be supported by kernel/arch and run as root
// so having fallback is important
case common.PathExists(sysProductUUID):
lines, err := common.ReadLines(sysProductUUID)
if err == nil && len(lines) > 0 && lines[0] != "" {
@ -81,6 +84,16 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
break
}
fallthrough
// Fallback on GNU Linux systems with systemd, readable by everyone
case common.PathExists(machineID):
lines, err := common.ReadLines(machineID)
if err == nil && len(lines) > 0 && len(lines[0]) == 32 {
st := lines[0]
ret.HostID = fmt.Sprintf("%s-%s-%s-%s-%s", st[0:8], st[8:12], st[12:16], st[16:20], st[20:32])
break
}
fallthrough
// Not stable between reboot, but better than nothing
default:
values, err := common.DoSysctrl("kernel.random.boot_id")
if err == nil && len(values) == 1 && values[0] != "" {

@ -8,7 +8,6 @@ import (
"encoding/binary"
"io/ioutil"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
@ -17,6 +16,7 @@ import (
"github.com/shirou/gopsutil/internal/common"
"github.com/shirou/gopsutil/process"
"golang.org/x/sys/unix"
)
const (
@ -108,19 +108,14 @@ func PlatformInformationWithContext(ctx context.Context) (string, string, string
platform := ""
family := ""
version := ""
uname, err := exec.LookPath("uname")
if err != nil {
return "", "", "", err
}
out, err := invoke.CommandWithContext(ctx, uname, "-s")
p, err := unix.Sysctl("kern.ostype")
if err == nil {
platform = strings.ToLower(strings.TrimSpace(string(out)))
platform = strings.ToLower(p)
}
out, err = invoke.CommandWithContext(ctx, uname, "-r")
v, err := unix.Sysctl("kern.osrelease")
if err == nil {
version = strings.ToLower(strings.TrimSpace(string(out)))
version = strings.ToLower(v)
}
return platform, family, version, nil

@ -44,7 +44,7 @@ type VirtualMemoryStat struct {
// FreeBSD specific numbers:
// https://reviews.freebsd.org/D8467
Laundry uint64 `json:"laundry"`
Laundry uint64 `json:"laundry"`
// Linux specific numbers
// https://www.centos.org/docs/5/html/5.1/Deployment_Guide/s2-proc-meminfo.html
@ -57,6 +57,7 @@ type VirtualMemoryStat struct {
WritebackTmp uint64 `json:"writebacktmp"`
Shared uint64 `json:"shared"`
Slab uint64 `json:"slab"`
SReclaimable uint64 `json:"sreclaimable"`
PageTables uint64 `json:"pagetables"`
SwapCached uint64 `json:"swapcached"`
CommitLimit uint64 `json:"commitlimit"`

@ -61,6 +61,8 @@ func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
ret.Shared = t * 1024
case "Slab":
ret.Slab = t * 1024
case "SReclaimable":
ret.SReclaimable = t * 1024
case "PageTables":
ret.PageTables = t * 1024
case "SwapCached":
@ -97,6 +99,9 @@ func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
ret.HugePageSize = t * 1024
}
}
ret.Cached += ret.SReclaimable
if !memavail {
ret.Available = ret.Free + ret.Buffers + ret.Cached
}

@ -28,11 +28,14 @@ func TestVirtual_memory(t *testing.T) {
total := v.Used + v.Free + v.Buffers + v.Cached
totalStr := "used + free + buffers + cached"
if runtime.GOOS == "windows" {
switch runtime.GOOS {
case "windows":
total = v.Used + v.Available
totalStr = "used + available"
}
if runtime.GOOS == "freebsd" {
case "darwin":
total = v.Used + v.Free + v.Cached + v.Inactive
totalStr = "used + free + cached + inactive"
case "freebsd":
total = v.Used + v.Free + v.Cached + v.Inactive + v.Laundry
totalStr = "used + free + cached + inactive + laundry"
}
@ -71,7 +74,7 @@ func TestVirtualMemoryStat_String(t *testing.T) {
UsedPercent: 30.1,
Free: 40,
}
e := `{"total":10,"available":20,"used":30,"usedPercent":30.1,"free":40,"active":0,"inactive":0,"wired":0,"laundry":0,"buffers":0,"cached":0,"writeback":0,"dirty":0,"writebacktmp":0,"shared":0,"slab":0,"pagetables":0,"swapcached":0,"commitlimit":0,"committedas":0,"hightotal":0,"highfree":0,"lowtotal":0,"lowfree":0,"swaptotal":0,"swapfree":0,"mapped":0,"vmalloctotal":0,"vmallocused":0,"vmallocchunk":0,"hugepagestotal":0,"hugepagesfree":0,"hugepagesize":0}`
e := `{"total":10,"available":20,"used":30,"usedPercent":30.1,"free":40,"active":0,"inactive":0,"wired":0,"laundry":0,"buffers":0,"cached":0,"writeback":0,"dirty":0,"writebacktmp":0,"shared":0,"slab":0,"sreclaimable":0,"pagetables":0,"swapcached":0,"commitlimit":0,"committedas":0,"hightotal":0,"highfree":0,"lowtotal":0,"lowfree":0,"swaptotal":0,"swapfree":0,"mapped":0,"vmalloctotal":0,"vmallocused":0,"vmallocchunk":0,"hugepagestotal":0,"hugepagesfree":0,"hugepagesize":0}`
if e != fmt.Sprintf("%v", v) {
t.Errorf("VirtualMemoryStat string is invalid: %v", v)
}

@ -393,7 +393,11 @@ func statsFromInodes(root string, pid int32, tmap []netConnectionKindType, inode
var path string
var connKey string
var ls []connTmp
path = fmt.Sprintf("%s/net/%s", root, t.filename)
if pid == 0 {
path = fmt.Sprintf("%s/net/%s", root, t.filename)
} else {
path = fmt.Sprintf("%s/%d/net/%s", root, pid, t.filename)
}
switch t.family {
case syscall.AF_INET, syscall.AF_INET6:
ls, err = processInet(path, t, inodes, pid)

@ -43,6 +43,7 @@ type OpenFilesStat struct {
type MemoryInfoStat struct {
RSS uint64 `json:"rss"` // bytes
VMS uint64 `json:"vms"` // bytes
HWM uint64 `json:"hwm"` // bytes
Data uint64 `json:"data"` // bytes
Stack uint64 `json:"stack"` // bytes
Locked uint64 `json:"locked"` // bytes
@ -76,6 +77,13 @@ type NumCtxSwitchesStat struct {
Involuntary int64 `json:"involuntary"`
}
type PageFaultsStat struct {
MinorFaults uint64 `json:"minorFaults"`
MajorFaults uint64 `json:"majorFaults"`
ChildMinorFaults uint64 `json:"childMinorFaults"`
ChildMajorFaults uint64 `json:"childMajorFaults"`
}
// Resource limit constants are from /usr/include/x86_64-linux-gnu/bits/resource.h
// from libc6-dev package in Ubuntu 16.10
const (
@ -146,6 +154,19 @@ func PidExistsWithContext(ctx context.Context, pid int32) (bool, error) {
return false, err
}
// Background returns true if the process is in background, false otherwise.
func (p *Process) Background() (bool, error) {
return p.BackgroundWithContext(context.Background())
}
func (p *Process) BackgroundWithContext(ctx context.Context) (bool, error) {
fg, err := p.ForegroundWithContext(ctx)
if err != nil {
return false, err
}
return !fg, err
}
// If interval is 0, return difference from last call(non-blocking).
// If interval > 0, wait interval sec and return diffrence between start and end.
func (p *Process) Percent(interval time.Duration) (float64, error) {

@ -239,6 +239,25 @@ func (p *Process) StatusWithContext(ctx context.Context) (string, error) {
return r[0][0], err
}
func (p *Process) Foreground() (bool, error) {
return p.ForegroundWithContext(context.Background())
}
func (p *Process) ForegroundWithContext(ctx context.Context) (bool, error) {
// see https://github.com/shirou/gopsutil/issues/596#issuecomment-432707831 for implementation details
pid := p.Pid
ps, err := exec.LookPath("ps")
if err != nil {
return false, err
}
out, err := invoke.CommandWithContext(ctx, ps, "-o", "stat=", "-p", strconv.Itoa(int(pid)))
if err != nil {
return false, err
}
return strings.IndexByte(string(out), '+') != -1, nil
}
func (p *Process) Uids() ([]int32, error) {
return p.UidsWithContext(context.Background())
}
@ -462,6 +481,14 @@ func (p *Process) MemoryInfoExWithContext(ctx context.Context) (*MemoryInfoExSta
return nil, common.ErrNotImplementedError
}
func (p *Process) PageFaults() (*PageFaultsStat, error) {
return p.PageFaultsWithContext(context.Background())
}
func (p *Process) PageFaultsWithContext(ctx context.Context) (*PageFaultsStat, error) {
return nil, common.ErrNotImplementedError
}
func (p *Process) Children() ([]*Process, error) {
return p.ChildrenWithContext(context.Background())
}

@ -114,6 +114,13 @@ func (p *Process) Status() (string, error) {
func (p *Process) StatusWithContext(ctx context.Context) (string, error) {
return "", common.ErrNotImplementedError
}
func (p *Process) Foreground() (bool, error) {
return p.ForegroundWithContext(context.Background())
}
func (p *Process) ForegroundWithContext(ctx context.Context) (bool, error) {
return false, common.ErrNotImplementedError
}
func (p *Process) Uids() ([]int32, error) {
return p.UidsWithContext(context.Background())
}
@ -226,6 +233,12 @@ func (p *Process) MemoryInfoEx() (*MemoryInfoExStat, error) {
func (p *Process) MemoryInfoExWithContext(ctx context.Context) (*MemoryInfoExStat, error) {
return nil, common.ErrNotImplementedError
}
func (p *Process) PageFaults() (*PageFaultsStat, error) {
return p.PageFaultsWithContext(context.Background())
}
func (p *Process) PageFaultsWithContext(ctx context.Context) (*PageFaultsStat, error) {
return nil, common.ErrNotImplementedError
}
func (p *Process) Children() ([]*Process, error) {
return p.ChildrenWithContext(context.Background())
}

@ -6,6 +6,8 @@ import (
"bytes"
"context"
"encoding/binary"
"os/exec"
"strconv"
"strings"
cpu "github.com/shirou/gopsutil/cpu"
@ -168,6 +170,25 @@ func (p *Process) StatusWithContext(ctx context.Context) (string, error) {
return s, nil
}
func (p *Process) Foreground() (bool, error) {
return p.ForegroundWithContext(context.Background())
}
func (p *Process) ForegroundWithContext(ctx context.Context) (bool, error) {
// see https://github.com/shirou/gopsutil/issues/596#issuecomment-432707831 for implementation details
pid := p.Pid
ps, err := exec.LookPath("ps")
if err != nil {
return false, err
}
out, err := invoke.CommandWithContext(ctx, ps, "-o", "stat=", "-p", strconv.Itoa(int(pid)))
if err != nil {
return false, err
}
return strings.IndexByte(string(out), '+') != -1, nil
}
func (p *Process) Uids() ([]int32, error) {
return p.UidsWithContext(context.Background())
}
@ -350,6 +371,14 @@ func (p *Process) MemoryInfoExWithContext(ctx context.Context) (*MemoryInfoExSta
return nil, common.ErrNotImplementedError
}
func (p *Process) PageFaults() (*PageFaultsStat, error) {
return p.PageFaultsWithContext(context.Background())
}
func (p *Process) PageFaultsWithContext(ctx context.Context) (*PageFaultsStat, error) {
return nil, common.ErrNotImplementedError
}
func (p *Process) Children() ([]*Process, error) {
return p.ChildrenWithContext(context.Background())
}

@ -84,7 +84,7 @@ func (p *Process) Ppid() (int32, error) {
}
func (p *Process) PpidWithContext(ctx context.Context) (int32, error) {
_, ppid, _, _, _, _, err := p.fillFromStat()
_, ppid, _, _, _, _, _, err := p.fillFromStat()
if err != nil {
return -1, err
}
@ -150,7 +150,7 @@ func (p *Process) CreateTime() (int64, error) {
}
func (p *Process) CreateTimeWithContext(ctx context.Context) (int64, error) {
_, _, _, createTime, _, _, err := p.fillFromStat()
_, _, _, createTime, _, _, _, err := p.fillFromStat()
if err != nil {
return 0, err
}
@ -199,6 +199,28 @@ func (p *Process) StatusWithContext(ctx context.Context) (string, error) {
return p.status, nil
}
// Foreground returns true if the process is in foreground, false otherwise.
func (p *Process) Foreground() (bool, error) {
return p.ForegroundWithContext(context.Background())
}
func (p *Process) ForegroundWithContext(ctx context.Context) (bool, error) {
// see https://github.com/shirou/gopsutil/issues/596#issuecomment-432707831 for implementation details
pid := p.Pid
statPath := common.HostProc(strconv.Itoa(int(pid)), "stat")
contents, err := ioutil.ReadFile(statPath)
if err != nil {
return false, err
}
fields := strings.Fields(string(contents))
if len(fields) < 8 {
return false, fmt.Errorf("insufficient data in %s", statPath)
}
pgid := fields[4]
tpgid := fields[7]
return pgid == tpgid, nil
}
// Uids returns user ids of the process as a slice of the int
func (p *Process) Uids() ([]int32, error) {
return p.UidsWithContext(context.Background())
@ -231,7 +253,7 @@ func (p *Process) Terminal() (string, error) {
}
func (p *Process) TerminalWithContext(ctx context.Context) (string, error) {
t, _, _, _, _, _, err := p.fillFromStat()
t, _, _, _, _, _, _, err := p.fillFromStat()
if err != nil {
return "", err
}
@ -250,7 +272,7 @@ func (p *Process) Nice() (int32, error) {
}
func (p *Process) NiceWithContext(ctx context.Context) (int32, error) {
_, _, _, _, _, nice, err := p.fillFromStat()
_, _, _, _, _, nice, _, err := p.fillFromStat()
if err != nil {
return 0, err
}
@ -288,7 +310,7 @@ func (p *Process) RlimitUsageWithContext(ctx context.Context, gatherUsed bool) (
return rlimits, err
}
_, _, _, _, rtprio, nice, err := p.fillFromStat()
_, _, _, _, rtprio, nice, _, err := p.fillFromStat()
if err != nil {
return nil, err
}
@ -396,7 +418,7 @@ func (p *Process) ThreadsWithContext(ctx context.Context) (map[int32]*cpu.TimesS
}
for _, tid := range tids {
_, _, cpuTimes, _, _, _, err := p.fillFromTIDStat(tid)
_, _, cpuTimes, _, _, _, _, err := p.fillFromTIDStat(tid)
if err != nil {
return nil, err
}
@ -412,7 +434,7 @@ func (p *Process) Times() (*cpu.TimesStat, error) {
}
func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) {
_, _, cpuTimes, _, _, _, err := p.fillFromStat()
_, _, cpuTimes, _, _, _, _, err := p.fillFromStat()
if err != nil {
return nil, err
}
@ -456,6 +478,20 @@ func (p *Process) MemoryInfoExWithContext(ctx context.Context) (*MemoryInfoExSta
return memInfoEx, nil
}
// PageFaultsInfo returns the process's page fault counters
func (p *Process) PageFaults() (*PageFaultsStat, error) {
return p.PageFaultsWithContext(context.Background())
}
func (p *Process) PageFaultsWithContext(ctx context.Context) (*PageFaultsStat, error) {
_, _, _, _, _, _, pageFaults, err := p.fillFromStat()
if err != nil {
return nil, err
}
return pageFaults, nil
}
// Children returns a slice of Process of the process.
func (p *Process) Children() ([]*Process, error) {
return p.ChildrenWithContext(context.Background())
@ -537,6 +573,9 @@ func (p *Process) MemoryMaps(grouped bool) (*[]MemoryMapsStat, error) {
func (p *Process) MemoryMapsWithContext(ctx context.Context, grouped bool) (*[]MemoryMapsStat, error) {
pid := p.Pid
var ret []MemoryMapsStat
if grouped {
ret = make([]MemoryMapsStat, 1)
}
smapsPath := common.HostProc(strconv.Itoa(int(pid)), "smaps")
contents, err := ioutil.ReadFile(smapsPath)
if err != nil {
@ -599,7 +638,20 @@ func (p *Process) MemoryMapsWithContext(ctx context.Context, grouped bool) (*[]M
if err != nil {
return &ret, err
}
ret = append(ret, g)
if grouped {
ret[0].Size += g.Size
ret[0].Rss += g.Rss
ret[0].Pss += g.Pss
ret[0].SharedClean += g.SharedClean
ret[0].SharedDirty += g.SharedDirty
ret[0].PrivateClean += g.PrivateClean
ret[0].PrivateDirty += g.PrivateDirty
ret[0].Referenced += g.Referenced
ret[0].Anonymous += g.Anonymous
ret[0].Swap += g.Swap
} else {
ret = append(ret, g)
}
}
// starts new block
blocks = make([]string, 16)
@ -1061,6 +1113,13 @@ func (p *Process) fillFromStatusWithContext(ctx context.Context) error {
return err
}
p.memInfo.Swap = v * 1024
case "VmHWM":
value := strings.Trim(value, " kB") // remove last "kB"
v, err := strconv.ParseUint(value, 10, 64)
if err != nil {
return err
}
p.memInfo.HWM = v * 1024
case "VmData":
value := strings.Trim(value, " kB") // remove last "kB"
v, err := strconv.ParseUint(value, 10, 64)
@ -1118,11 +1177,11 @@ func (p *Process) fillFromStatusWithContext(ctx context.Context) error {
return nil
}
func (p *Process) fillFromTIDStat(tid int32) (uint64, int32, *cpu.TimesStat, int64, uint32, int32, error) {
func (p *Process) fillFromTIDStat(tid int32) (uint64, int32, *cpu.TimesStat, int64, uint32, int32, *PageFaultsStat, error) {
return p.fillFromTIDStatWithContext(context.Background(), tid)
}
func (p *Process) fillFromTIDStatWithContext(ctx context.Context, tid int32) (uint64, int32, *cpu.TimesStat, int64, uint32, int32, error) {
func (p *Process) fillFromTIDStatWithContext(ctx context.Context, tid int32) (uint64, int32, *cpu.TimesStat, int64, uint32, int32, *PageFaultsStat, error) {
pid := p.Pid
var statPath string
@ -1134,7 +1193,7 @@ func (p *Process) fillFromTIDStatWithContext(ctx context.Context, tid int32) (ui
contents, err := ioutil.ReadFile(statPath)
if err != nil {
return 0, 0, nil, 0, 0, 0, err
return 0, 0, nil, 0, 0, 0, nil, err
}
fields := strings.Fields(string(contents))
@ -1145,21 +1204,21 @@ func (p *Process) fillFromTIDStatWithContext(ctx context.Context, tid int32) (ui
terminal, err := strconv.ParseUint(fields[i+5], 10, 64)
if err != nil {
return 0, 0, nil, 0, 0, 0, err
return 0, 0, nil, 0, 0, 0, nil, err
}
ppid, err := strconv.ParseInt(fields[i+2], 10, 32)
if err != nil {
return 0, 0, nil, 0, 0, 0, err
return 0, 0, nil, 0, 0, 0, nil, err
}
utime, err := strconv.ParseFloat(fields[i+12], 64)
if err != nil {
return 0, 0, nil, 0, 0, 0, err
return 0, 0, nil, 0, 0, 0, nil, err
}
stime, err := strconv.ParseFloat(fields[i+13], 64)
if err != nil {
return 0, 0, nil, 0, 0, 0, err
return 0, 0, nil, 0, 0, 0, nil, err
}
cpuTimes := &cpu.TimesStat{
@ -1171,14 +1230,14 @@ func (p *Process) fillFromTIDStatWithContext(ctx context.Context, tid int32) (ui
bootTime, _ := host.BootTime()
t, err := strconv.ParseUint(fields[i+20], 10, 64)
if err != nil {
return 0, 0, nil, 0, 0, 0, err
return 0, 0, nil, 0, 0, 0, nil, err
}
ctime := (t / uint64(ClockTicks)) + uint64(bootTime)
createTime := int64(ctime * 1000)
rtpriority, err := strconv.ParseInt(fields[i+16], 10, 32)
if err != nil {
return 0, 0, nil, 0, 0, 0, err
return 0, 0, nil, 0, 0, 0, nil, err
}
if rtpriority < 0 {
rtpriority = rtpriority*-1 - 1
@ -1191,14 +1250,38 @@ func (p *Process) fillFromTIDStatWithContext(ctx context.Context, tid int32) (ui
snice, _ := unix.Getpriority(PrioProcess, int(pid))
nice := int32(snice) // FIXME: is this true?
return terminal, int32(ppid), cpuTimes, createTime, uint32(rtpriority), nice, nil
minFault, err := strconv.ParseUint(fields[i+8], 10, 64)
if err != nil {
return 0, 0, nil, 0, 0, 0, nil, err
}
cMinFault, err := strconv.ParseUint(fields[i+9], 10, 64)
if err != nil {
return 0, 0, nil, 0, 0, 0, nil, err
}
majFault, err := strconv.ParseUint(fields[i+10], 10, 64)
if err != nil {
return 0, 0, nil, 0, 0, 0, nil, err
}
cMajFault, err := strconv.ParseUint(fields[i+11], 10, 64)
if err != nil {
return 0, 0, nil, 0, 0, 0, nil, err
}
faults := &PageFaultsStat{
MinorFaults: minFault,
MajorFaults: majFault,
ChildMinorFaults: cMinFault,
ChildMajorFaults: cMajFault,
}
return terminal, int32(ppid), cpuTimes, createTime, uint32(rtpriority), nice, faults, nil
}
func (p *Process) fillFromStat() (uint64, int32, *cpu.TimesStat, int64, uint32, int32, error) {
func (p *Process) fillFromStat() (uint64, int32, *cpu.TimesStat, int64, uint32, int32, *PageFaultsStat, error) {
return p.fillFromStatWithContext(context.Background())
}
func (p *Process) fillFromStatWithContext(ctx context.Context) (uint64, int32, *cpu.TimesStat, int64, uint32, int32, error) {
func (p *Process) fillFromStatWithContext(ctx context.Context) (uint64, int32, *cpu.TimesStat, int64, uint32, int32, *PageFaultsStat, error) {
return p.fillFromTIDStat(-1)
}

@ -7,6 +7,8 @@ import (
"bytes"
"context"
"encoding/binary"
"os/exec"
"strconv"
"strings"
"unsafe"
@ -162,6 +164,23 @@ func (p *Process) StatusWithContext(ctx context.Context) (string, error) {
return s, nil
}
func (p *Process) Foreground() (bool, error) {
return p.ForegroundWithContext(context.Background())
}
func (p *Process) ForegroundWithContext(ctx context.Context) (bool, error) {
// see https://github.com/shirou/gopsutil/issues/596#issuecomment-432707831 for implementation details
pid := p.Pid
ps, err := exec.LookPath("ps")
if err != nil {
return false, err
}
out, err := invoke.CommandWithContext(ctx, ps, "-o", "stat=", "-p", strconv.Itoa(int(pid)))
if err != nil {
return false, err
}
return strings.IndexByte(string(out), '+') != -1, nil
}
func (p *Process) Uids() ([]int32, error) {
return p.UidsWithContext(context.Background())
}
@ -340,6 +359,14 @@ func (p *Process) MemoryInfoExWithContext(ctx context.Context) (*MemoryInfoExSta
return nil, common.ErrNotImplementedError
}
func (p *Process) PageFaults() (*PageFaultsStat, error) {
return p.PageFaultsWithContext(context.Background())
}
func (p *Process) PageFaultsWithContext(ctx context.Context) (*PageFaultsStat, error) {
return nil, common.ErrNotImplementedError
}
func (p *Process) Children() ([]*Process, error) {
return p.ChildrenWithContext(context.Background())
}

@ -89,6 +89,7 @@ func Test_Process_memory_maps(t *testing.T) {
t.Errorf("error %v", err)
}
// ungrouped memory maps
mmaps, err := ret.MemoryMaps(false)
if err != nil {
t.Errorf("memory map get error %v", err)
@ -99,6 +100,18 @@ func Test_Process_memory_maps(t *testing.T) {
t.Errorf("memory map get error %v", m)
}
}
// grouped memory maps
mmaps, err = ret.MemoryMaps(true)
if err != nil {
t.Errorf("memory map get error %v", err)
}
if len(*mmaps) != 1 {
t.Errorf("grouped memory maps length (%v) is not equal to 1", len(*mmaps))
}
if (*mmaps)[0] == empty {
t.Errorf("memory map is empty")
}
}
func Test_Process_MemoryInfo(t *testing.T) {
p := testGetProcess()

@ -27,6 +27,10 @@ const (
var (
modpsapi = windows.NewLazySystemDLL("psapi.dll")
procGetProcessMemoryInfo = modpsapi.NewProc("GetProcessMemoryInfo")
advapi32 = windows.NewLazySystemDLL("advapi32.dll")
procLookupPrivilegeValue = advapi32.NewProc("LookupPrivilegeValueW")
procAdjustTokenPrivileges = advapi32.NewProc("AdjustTokenPrivileges")
)
type SystemProcessInformation struct {
@ -90,8 +94,61 @@ type Win32_Process struct {
WorkingSetSize uint64
}
type winLUID struct {
LowPart winDWord
HighPart winLong
}
// LUID_AND_ATTRIBUTES
type winLUIDAndAttributes struct {
Luid winLUID
Attributes winDWord
}
// TOKEN_PRIVILEGES
type winTokenPriviledges struct {
PrivilegeCount winDWord
Privileges [1]winLUIDAndAttributes
}
type winLong int32
type winDWord uint32
func init() {
wmi.DefaultClient.AllowMissingFields = true
// enable SeDebugPrivilege https://github.com/midstar/proci/blob/6ec79f57b90ba3d9efa2a7b16ef9c9369d4be875/proci_windows.go#L80-L119
handle, err := syscall.GetCurrentProcess()
if err != nil {
return
}
var token syscall.Token
err = syscall.OpenProcessToken(handle, 0x0028, &token)
if err != nil {
return
}
defer token.Close()
tokenPriviledges := winTokenPriviledges{PrivilegeCount: 1}
lpName := syscall.StringToUTF16("SeDebugPrivilege")
ret, _, _ := procLookupPrivilegeValue.Call(
0,
uintptr(unsafe.Pointer(&lpName[0])),
uintptr(unsafe.Pointer(&tokenPriviledges.Privileges[0].Luid)))
if ret == 0 {
return
}
tokenPriviledges.Privileges[0].Attributes = 0x00000002 // SE_PRIVILEGE_ENABLED
procAdjustTokenPrivileges.Call(
uintptr(token),
0,
uintptr(unsafe.Pointer(&tokenPriviledges)),
uintptr(unsafe.Sizeof(tokenPriviledges)),
0,
0)
}
func Pids() ([]int32, error) {
@ -177,7 +234,20 @@ func (p *Process) Exe() (string, error) {
}
func (p *Process) ExeWithContext(ctx context.Context) (string, error) {
dst, err := GetWin32Proc(p.Pid)
if p.Pid != 0 { // 0 or null is the current process for CreateToolhelp32Snapshot
snap := w32.CreateToolhelp32Snapshot(w32.TH32CS_SNAPMODULE|w32.TH32CS_SNAPMODULE32, uint32(p.Pid))
if snap != 0 { // don't report errors here, fallback to WMI instead
defer w32.CloseHandle(snap)
var me32 w32.MODULEENTRY32
me32.Size = uint32(unsafe.Sizeof(me32))
if w32.Module32First(snap, &me32) {
szexepath := windows.UTF16ToString(me32.SzExePath[:])
return szexepath, nil
}
}
}
dst, err := GetWin32ProcWithContext(ctx, p.Pid)
if err != nil {
return "", fmt.Errorf("could not get ExecutablePath: %s", err)
}
@ -189,7 +259,7 @@ func (p *Process) Cmdline() (string, error) {
}
func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) {
dst, err := GetWin32Proc(p.Pid)
dst, err := GetWin32ProcWithContext(ctx, p.Pid)
if err != nil {
return "", fmt.Errorf("could not get CommandLine: %s", err)
}
@ -204,7 +274,7 @@ func (p *Process) CmdlineSlice() ([]string, error) {
}
func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) {
cmdline, err := p.Cmdline()
cmdline, err := p.CmdlineWithContext(ctx)
if err != nil {
return nil, err
}
@ -250,6 +320,15 @@ func (p *Process) Status() (string, error) {
func (p *Process) StatusWithContext(ctx context.Context) (string, error) {
return "", common.ErrNotImplementedError
}
func (p *Process) Foreground() (bool, error) {
return p.ForegroundWithContext(context.Background())
}
func (p *Process) ForegroundWithContext(ctx context.Context) (bool, error) {
return false, common.ErrNotImplementedError
}
func (p *Process) Username() (string, error) {
return p.UsernameWithContext(context.Background())
}
@ -309,7 +388,7 @@ func (p *Process) Nice() (int32, error) {
}
func (p *Process) NiceWithContext(ctx context.Context) (int32, error) {
dst, err := GetWin32Proc(p.Pid)
dst, err := GetWin32ProcWithContext(ctx, p.Pid)
if err != nil {
return 0, fmt.Errorf("could not get Priority: %s", err)
}
@ -346,7 +425,7 @@ func (p *Process) IOCounters() (*IOCountersStat, error) {
}
func (p *Process) IOCountersWithContext(ctx context.Context) (*IOCountersStat, error) {
dst, err := GetWin32Proc(p.Pid)
dst, err := GetWin32ProcWithContext(ctx, p.Pid)
if err != nil || len(dst) == 0 {
return nil, fmt.Errorf("could not get Win32Proc: %s", err)
}
@ -378,7 +457,7 @@ func (p *Process) NumThreads() (int32, error) {
}
func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) {
dst, err := GetWin32Proc(p.Pid)
dst, err := GetWin32ProcWithContext(ctx, p.Pid)
if err != nil {
return 0, fmt.Errorf("could not get ThreadCount: %s", err)
}
@ -451,27 +530,46 @@ func (p *Process) MemoryInfoExWithContext(ctx context.Context) (*MemoryInfoExSta
return nil, common.ErrNotImplementedError
}
func (p *Process) PageFaults() (*PageFaultsStat, error) {
return p.PageFaultsWithContext(context.Background())
}
func (p *Process) PageFaultsWithContext(ctx context.Context) (*PageFaultsStat, error) {
return nil, common.ErrNotImplementedError
}
func (p *Process) Children() ([]*Process, error) {
return p.ChildrenWithContext(context.Background())
}
func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) {
var dst []Win32_Process
query := wmi.CreateQuery(&dst, fmt.Sprintf("Where ParentProcessId = %d", p.Pid))
err := common.WMIQueryWithContext(ctx, query, &dst)
if err != nil {
return nil, err
out := []*Process{}
snap := w32.CreateToolhelp32Snapshot(w32.TH32CS_SNAPPROCESS, uint32(0))
if snap == 0 {
return out, windows.GetLastError()
}
defer w32.CloseHandle(snap)
var pe32 w32.PROCESSENTRY32
pe32.DwSize = uint32(unsafe.Sizeof(pe32))
if w32.Process32First(snap, &pe32) == false {
return out, windows.GetLastError()
}
out := []*Process{}
for _, proc := range dst {
p, err := NewProcess(int32(proc.ProcessID))
if err != nil {
continue
if pe32.Th32ParentProcessID == uint32(p.Pid) {
p, err := NewProcess(int32(pe32.Th32ProcessID))
if err == nil {
out = append(out, p)
}
out = append(out, p)
}
for w32.Process32Next(snap, &pe32) {
if pe32.Th32ParentProcessID == uint32(p.Pid) {
p, err := NewProcess(int32(pe32.Th32ProcessID))
if err == nil {
out = append(out, p)
}
}
}
return out, nil
}

Loading…
Cancel
Save