diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..47894d5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -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`] diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..a2c571b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -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.] diff --git a/README.rst b/README.rst index 63f5f69..73a79ea 100644 --- a/README.rst +++ b/README.rst @@ -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 diff --git a/cpu/cpu_openbsd.go b/cpu/cpu_openbsd.go index 82b920f..0177626 100644 --- a/cpu/cpu_openbsd.go +++ b/cpu/cpu_openbsd.go @@ -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 } diff --git a/cpu/cpu_test.go b/cpu/cpu_test.go index 6082ffc..2bff340 100644 --- a/cpu/cpu_test.go +++ b/cpu/cpu_test.go @@ -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) { diff --git a/cpu/cpu_windows.go b/cpu/cpu_windows.go index 8aa691c..3959212 100644 --- a/cpu/cpu_windows.go +++ b/cpu/cpu_windows.go @@ -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) } diff --git a/disk/disk_linux.go b/disk/disk_linux.go index a511ac4..a6e5ee3 100644 --- a/disk/disk_linux.go +++ b/disk/disk_linux.go @@ -460,7 +460,7 @@ func GetLabel(name string) string { if err != nil { return "" } else { - return string(dmname) + return strings.TrimSpace(string(dmname)) } } diff --git a/disk/disk_windows.go b/disk/disk_windows.go index 326bc1f..081443b 100644 --- a/disk/disk_windows.go +++ b/disk/disk_windows.go @@ -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)), diff --git a/docker/docker_linux.go b/docker/docker_linux.go index 01d09eb..3f5a361 100644 --- a/docker/docker_linux.go +++ b/docker/docker_linux.go @@ -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 } } diff --git a/host/host_darwin.go b/host/host_darwin.go index 8241fc0..5cf22b7 100644 --- a/host/host_darwin.go +++ b/host/host_darwin.go @@ -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) { diff --git a/host/host_linux.go b/host/host_linux.go index 7ca5089..2c1ac6b 100644 --- a/host/host_linux.go +++ b/host/host_linux.go @@ -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] != "" { diff --git a/host/host_openbsd.go b/host/host_openbsd.go index 2ad64d7..bb16fca 100644 --- a/host/host_openbsd.go +++ b/host/host_openbsd.go @@ -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 diff --git a/mem/mem.go b/mem/mem.go index e505662..995364f 100644 --- a/mem/mem.go +++ b/mem/mem.go @@ -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"` diff --git a/mem/mem_linux.go b/mem/mem_linux.go index fcc9a3f..a3425d7 100644 --- a/mem/mem_linux.go +++ b/mem/mem_linux.go @@ -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 } diff --git a/mem/mem_test.go b/mem/mem_test.go index 2a18299..fdde2c2 100644 --- a/mem/mem_test.go +++ b/mem/mem_test.go @@ -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) } diff --git a/net/net_linux.go b/net/net_linux.go index 616c10a..71842fb 100644 --- a/net/net_linux.go +++ b/net/net_linux.go @@ -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) diff --git a/process/process.go b/process/process.go index a4b24f4..a9d10e0 100644 --- a/process/process.go +++ b/process/process.go @@ -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) { diff --git a/process/process_darwin.go b/process/process_darwin.go index 509ab3b..0e83030 100644 --- a/process/process_darwin.go +++ b/process/process_darwin.go @@ -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()) } diff --git a/process/process_fallback.go b/process/process_fallback.go index ca8b72f..f95eca6 100644 --- a/process/process_fallback.go +++ b/process/process_fallback.go @@ -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()) } diff --git a/process/process_freebsd.go b/process/process_freebsd.go index af2b3b1..e807e35 100644 --- a/process/process_freebsd.go +++ b/process/process_freebsd.go @@ -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()) } diff --git a/process/process_linux.go b/process/process_linux.go index f844101..00fdd33 100644 --- a/process/process_linux.go +++ b/process/process_linux.go @@ -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) } diff --git a/process/process_openbsd.go b/process/process_openbsd.go index b7b2cba..9e01ea7 100644 --- a/process/process_openbsd.go +++ b/process/process_openbsd.go @@ -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()) } diff --git a/process/process_test.go b/process/process_test.go index a95efc2..0d51837 100644 --- a/process/process_test.go +++ b/process/process_test.go @@ -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() diff --git a/process/process_windows.go b/process/process_windows.go index 82aed37..52bec7b 100644 --- a/process/process_windows.go +++ b/process/process_windows.go @@ -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 }