gopsutils: v2.20.2

Signed-off-by: Yoan Blanc <yoan@dosimple.ch>
This commit is contained in:
Yoan Blanc
2020-03-15 09:36:59 +01:00
parent 9032cbe304
commit f80cbe86a1
127 changed files with 4834 additions and 11211 deletions

View File

@@ -275,7 +275,7 @@ func (h *HostCpuStatsCalculator) Calculate(times cpu.TimesStat) (idle float64, u
currentSystem := times.System
currentTotal := times.Total()
currentBusy := times.User + times.System + times.Nice + times.Iowait + times.Irq +
times.Softirq + times.Steal + times.Guest + times.GuestNice + times.Stolen
times.Softirq + times.Steal + times.Guest + times.GuestNice
deltaTotal := currentTotal - h.prevTotal
idle = ((currentIdle - h.prevIdle) / deltaTotal) * 100

View File

@@ -4,7 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"runtime"
"math"
"strconv"
"strings"
"sync"
@@ -28,7 +28,6 @@ type TimesStat struct {
Steal float64 `json:"steal"`
Guest float64 `json:"guest"`
GuestNice float64 `json:"guestNice"`
Stolen float64 `json:"stolen"`
}
type InfoStat struct {
@@ -54,24 +53,20 @@ type lastPercent struct {
}
var lastCPUPercent lastPercent
var invoke common.Invoker
var invoke common.Invoker = common.Invoke{}
func init() {
invoke = common.Invoke{}
lastCPUPercent.Lock()
lastCPUPercent.lastCPUTimes, _ = Times(false)
lastCPUPercent.lastPerCPUTimes, _ = Times(true)
lastCPUPercent.Unlock()
}
// Counts returns the number of physical or logical cores in the system
func Counts(logical bool) (int, error) {
return CountsWithContext(context.Background(), logical)
}
func CountsWithContext(ctx context.Context, logical bool) (int, error) {
return runtime.NumCPU(), nil
}
func (c TimesStat) String() string {
v := []string{
`"cpu":"` + c.CPU + `"`,
@@ -85,7 +80,6 @@ func (c TimesStat) String() string {
`"steal":` + strconv.FormatFloat(c.Steal, 'f', 1, 64),
`"guest":` + strconv.FormatFloat(c.Guest, 'f', 1, 64),
`"guestNice":` + strconv.FormatFloat(c.GuestNice, 'f', 1, 64),
`"stolen":` + strconv.FormatFloat(c.Stolen, 'f', 1, 64),
}
return `{` + strings.Join(v, ",") + `}`
@@ -93,8 +87,8 @@ func (c TimesStat) String() string {
// Total returns the total number of seconds in a CPUTimesStat
func (c TimesStat) Total() float64 {
total := c.User + c.System + c.Nice + c.Iowait + c.Irq + c.Softirq + c.Steal +
c.Guest + c.GuestNice + c.Idle + c.Stolen
total := c.User + c.System + c.Nice + c.Iowait + c.Irq + c.Softirq +
c.Steal + c.Idle
return total
}
@@ -105,7 +99,7 @@ func (c InfoStat) String() string {
func getAllBusy(t TimesStat) (float64, float64) {
busy := t.User + t.System + t.Nice + t.Iowait + t.Irq +
t.Softirq + t.Steal + t.Guest + t.GuestNice + t.Stolen
t.Softirq + t.Steal
return busy + t.Idle, busy
}
@@ -117,9 +111,9 @@ func calculateBusy(t1, t2 TimesStat) float64 {
return 0
}
if t2All <= t1All {
return 1
return 100
}
return (t2Busy - t1Busy) / (t2All - t1All) * 100
return math.Min(100, math.Max(0, (t2Busy-t1Busy)/(t2All-t1All)*100))
}
func calculateAllBusy(t1, t2 []TimesStat) ([]float64, error) {

View File

@@ -4,9 +4,10 @@ package cpu
import (
"context"
"os/exec"
"strconv"
"strings"
"golang.org/x/sys/unix"
)
// sys/resource.h
@@ -41,75 +42,62 @@ func Info() ([]InfoStat, error) {
func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
var ret []InfoStat
sysctl, err := exec.LookPath("/usr/sbin/sysctl")
if err != nil {
return ret, err
}
out, err := invoke.Command(sysctl, "machdep.cpu")
if err != nil {
return ret, err
}
c := InfoStat{}
for _, line := range strings.Split(string(out), "\n") {
values := strings.Fields(line)
if len(values) < 1 {
continue
}
t, err := strconv.ParseInt(values[1], 10, 64)
// err is not checked here because some value is string.
if strings.HasPrefix(line, "machdep.cpu.brand_string") {
c.ModelName = strings.Join(values[1:], " ")
} else if strings.HasPrefix(line, "machdep.cpu.family") {
c.Family = values[1]
} else if strings.HasPrefix(line, "machdep.cpu.model") {
c.Model = values[1]
} else if strings.HasPrefix(line, "machdep.cpu.stepping") {
if err != nil {
return ret, err
}
c.Stepping = int32(t)
} else if strings.HasPrefix(line, "machdep.cpu.features") {
for _, v := range values[1:] {
c.Flags = append(c.Flags, strings.ToLower(v))
}
} else if strings.HasPrefix(line, "machdep.cpu.leaf7_features") {
for _, v := range values[1:] {
c.Flags = append(c.Flags, strings.ToLower(v))
}
} else if strings.HasPrefix(line, "machdep.cpu.extfeatures") {
for _, v := range values[1:] {
c.Flags = append(c.Flags, strings.ToLower(v))
}
} else if strings.HasPrefix(line, "machdep.cpu.core_count") {
if err != nil {
return ret, err
}
c.Cores = int32(t)
} else if strings.HasPrefix(line, "machdep.cpu.cache.size") {
if err != nil {
return ret, err
}
c.CacheSize = int32(t)
} else if strings.HasPrefix(line, "machdep.cpu.vendor") {
c.VendorID = values[1]
c.ModelName, _ = unix.Sysctl("machdep.cpu.brand_string")
family, _ := unix.SysctlUint32("machdep.cpu.family")
c.Family = strconv.FormatUint(uint64(family), 10)
model, _ := unix.SysctlUint32("machdep.cpu.model")
c.Model = strconv.FormatUint(uint64(model), 10)
stepping, _ := unix.SysctlUint32("machdep.cpu.stepping")
c.Stepping = int32(stepping)
features, err := unix.Sysctl("machdep.cpu.features")
if err == nil {
for _, v := range strings.Fields(features) {
c.Flags = append(c.Flags, strings.ToLower(v))
}
}
leaf7Features, err := unix.Sysctl("machdep.cpu.leaf7_features")
if err == nil {
for _, v := range strings.Fields(leaf7Features) {
c.Flags = append(c.Flags, strings.ToLower(v))
}
}
extfeatures, err := unix.Sysctl("machdep.cpu.extfeatures")
if err == nil {
for _, v := range strings.Fields(extfeatures) {
c.Flags = append(c.Flags, strings.ToLower(v))
}
}
cores, _ := unix.SysctlUint32("machdep.cpu.core_count")
c.Cores = int32(cores)
cacheSize, _ := unix.SysctlUint32("machdep.cpu.cache.size")
c.CacheSize = int32(cacheSize)
c.VendorID, _ = unix.Sysctl("machdep.cpu.vendor")
// Use the rated frequency of the CPU. This is a static value and does not
// account for low power or Turbo Boost modes.
out, err = invoke.Command(sysctl, "hw.cpufrequency")
cpuFrequency, err := unix.SysctlUint64("hw.cpufrequency")
if err != nil {
return ret, err
}
values := strings.Fields(string(out))
hz, err := strconv.ParseFloat(values[1], 64)
if err != nil {
return ret, err
}
c.Mhz = hz / 1000000.0
c.Mhz = float64(cpuFrequency) / 1000000.0
return append(ret, c), nil
}
func CountsWithContext(ctx context.Context, logical bool) (int, error) {
var cpuArgument string
if logical {
cpuArgument = "hw.logicalcpu"
} else {
cpuArgument = "hw.physicalcpu"
}
count, err := unix.SysctlUint32(cpuArgument)
if err != nil {
return 0, err
}
return int(count), nil
}

View File

@@ -4,6 +4,7 @@ package cpu
import (
"context"
"runtime"
"github.com/shirou/gopsutil/internal/common"
)
@@ -23,3 +24,7 @@ func Info() ([]InfoStat, error) {
func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
return []InfoStat{}, common.ErrNotImplementedError
}
func CountsWithContext(ctx context.Context, logical bool) (int, error) {
return runtime.NumCPU(), nil
}

View File

@@ -6,6 +6,7 @@ import (
"os/exec"
"reflect"
"regexp"
"runtime"
"strconv"
"strings"
"unsafe"
@@ -25,7 +26,7 @@ var cpuTimesSize int
var emptyTimes cpuTimes
func init() {
getconf, err := exec.LookPath("/usr/bin/getconf")
getconf, err := exec.LookPath("getconf")
if err != nil {
return
}
@@ -166,3 +167,7 @@ func parseDmesgBoot(fileName string) (InfoStat, int, error) {
return c, cpuNum, nil
}
func CountsWithContext(ctx context.Context, logical bool) (int, error) {
return runtime.NumCPU(), nil
}

View File

@@ -0,0 +1,9 @@
package cpu
type cpuTimes struct {
User uint32
Nice uint32
Sys uint32
Intr uint32
Idle uint32
}

View File

@@ -0,0 +1,9 @@
package cpu
type cpuTimes struct {
User uint64
Nice uint64
Sys uint64
Intr uint64
Idle uint64
}

View File

@@ -1,40 +0,0 @@
package cpu
import (
"path/filepath"
"runtime"
"testing"
"github.com/shirou/gopsutil/internal/common"
)
func TestParseDmesgBoot(t *testing.T) {
if runtime.GOOS != "freebsd" {
t.SkipNow()
}
var cpuTests = []struct {
file string
cpuNum int
cores int32
}{
{"1cpu_2core.txt", 1, 2},
{"1cpu_4core.txt", 1, 4},
{"2cpu_4core.txt", 2, 4},
}
for _, tt := range cpuTests {
v, num, err := parseDmesgBoot(filepath.Join("testdata", "freebsd", tt.file))
if err != nil {
t.Errorf("parseDmesgBoot failed(%s), %v", tt.file, err)
}
if num != tt.cpuNum {
t.Errorf("parseDmesgBoot wrong length(%s), %v", tt.file, err)
}
if v.Cores != tt.cores {
t.Errorf("parseDmesgBoot wrong core(%s), %v", tt.file, err)
}
if !common.StringsContains(v.Flags, "fpu") {
t.Errorf("parseDmesgBoot fail to parse features(%s), %v", tt.file, err)
}
}
}

View File

@@ -13,19 +13,19 @@ import (
"github.com/shirou/gopsutil/internal/common"
)
var cpu_tick = float64(100)
var CPUTick = float64(100)
func init() {
getconf, err := exec.LookPath("/usr/bin/getconf")
getconf, err := exec.LookPath("getconf")
if err != nil {
return
}
out, err := invoke.Command(getconf, "CLK_TCK")
out, err := invoke.CommandWithContext(context.Background(), getconf, "CLK_TCK")
// ignore errors
if err == nil {
i, err := strconv.ParseFloat(strings.TrimSpace(string(out)), 64)
if err == nil {
cpu_tick = float64(i)
CPUTick = i
}
}
}
@@ -87,7 +87,7 @@ func finishCPUInfo(c *InfoStat) error {
lines, err = common.ReadLines(sysCPUPath(c.CPU, "cpufreq/cpuinfo_max_freq"))
// if we encounter errors below such as there are no cpuinfo_max_freq file,
// we just ignore. so let Mhz is 0.
if err != nil {
if err != nil || len(lines) == 0 {
return nil
}
value, err = strconv.ParseFloat(lines[0], 64)
@@ -212,7 +212,6 @@ func parseStatLine(line string) (*TimesStat, error) {
}
if strings.HasPrefix(fields[0], "cpu") == false {
// return CPUTimesStat{}, e
return nil, errors.New("not contain cpu")
}
@@ -251,35 +250,103 @@ func parseStatLine(line string) (*TimesStat, error) {
ct := &TimesStat{
CPU: cpu,
User: float64(user) / cpu_tick,
Nice: float64(nice) / cpu_tick,
System: float64(system) / cpu_tick,
Idle: float64(idle) / cpu_tick,
Iowait: float64(iowait) / cpu_tick,
Irq: float64(irq) / cpu_tick,
Softirq: float64(softirq) / cpu_tick,
User: user / CPUTick,
Nice: nice / CPUTick,
System: system / CPUTick,
Idle: idle / CPUTick,
Iowait: iowait / CPUTick,
Irq: irq / CPUTick,
Softirq: softirq / CPUTick,
}
if len(fields) > 8 { // Linux >= 2.6.11
steal, err := strconv.ParseFloat(fields[8], 64)
if err != nil {
return nil, err
}
ct.Steal = float64(steal) / cpu_tick
ct.Steal = steal / CPUTick
}
if len(fields) > 9 { // Linux >= 2.6.24
guest, err := strconv.ParseFloat(fields[9], 64)
if err != nil {
return nil, err
}
ct.Guest = float64(guest) / cpu_tick
ct.Guest = guest / CPUTick
}
if len(fields) > 10 { // Linux >= 3.2.0
guestNice, err := strconv.ParseFloat(fields[10], 64)
if err != nil {
return nil, err
}
ct.GuestNice = float64(guestNice) / cpu_tick
ct.GuestNice = guestNice / CPUTick
}
return ct, nil
}
func CountsWithContext(ctx context.Context, logical bool) (int, error) {
if logical {
ret := 0
// https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_pslinux.py#L599
procCpuinfo := common.HostProc("cpuinfo")
lines, err := common.ReadLines(procCpuinfo)
if err == nil {
for _, line := range lines {
line = strings.ToLower(line)
if strings.HasPrefix(line, "processor") {
ret++
}
}
}
if ret == 0 {
procStat := common.HostProc("stat")
lines, err = common.ReadLines(procStat)
if err != nil {
return 0, err
}
for _, line := range lines {
if len(line) >= 4 && strings.HasPrefix(line, "cpu") && '0' <= line[3] && line[3] <= '9' { // `^cpu\d` regexp matching
ret++
}
}
}
return ret, nil
}
// physical cores https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_pslinux.py#L628
filename := common.HostProc("cpuinfo")
lines, err := common.ReadLines(filename)
if err != nil {
return 0, err
}
mapping := make(map[int]int)
currentInfo := make(map[string]int)
for _, line := range lines {
line = strings.ToLower(strings.TrimSpace(line))
if line == "" {
// new section
id, okID := currentInfo["physical id"]
cores, okCores := currentInfo["cpu cores"]
if okID && okCores {
mapping[id] = cores
}
currentInfo = make(map[string]int)
continue
}
fields := strings.Split(line, ":")
if len(fields) < 2 {
continue
}
fields[0] = strings.TrimSpace(fields[0])
if fields[0] == "physical id" || fields[0] == "cpu cores" {
val, err := strconv.Atoi(strings.TrimSpace(fields[1]))
if err != nil {
continue
}
currentInfo[fields[0]] = val
}
}
ret := 0
for _, v := range mapping {
ret += v
}
return ret, nil
}

View File

@@ -1,40 +0,0 @@
package cpu
import (
"os"
"testing"
)
func TestTimesEmpty(t *testing.T) {
orig := os.Getenv("HOST_PROC")
os.Setenv("HOST_PROC", "testdata/linux/times_empty")
_, err := Times(true)
if err != nil {
t.Error("Times(true) failed")
}
_, err = Times(false)
if err != nil {
t.Error("Times(false) failed")
}
os.Setenv("HOST_PROC", orig)
}
func TestCPUparseStatLine_424(t *testing.T) {
orig := os.Getenv("HOST_PROC")
os.Setenv("HOST_PROC", "testdata/linux/424/proc")
{
l, err := Times(true)
if err != nil || len(l) == 0 {
t.Error("Times(true) failed")
}
t.Logf("Times(true): %#v", l)
}
{
l, err := Times(false)
if err != nil || len(l) == 0 {
t.Error("Times(false) failed")
}
t.Logf("Times(false): %#v", l)
}
os.Setenv("HOST_PROC", orig)
}

View File

@@ -8,15 +8,17 @@ import (
"encoding/binary"
"fmt"
"os/exec"
"runtime"
"strconv"
"strings"
"syscall"
"github.com/shirou/gopsutil/internal/common"
"golang.org/x/sys/unix"
)
// sys/sched.h
const (
var (
CPUser = 0
CPNice = 1
CPSys = 2
@@ -28,6 +30,9 @@ const (
// sys/sysctl.h
const (
CTLKern = 1 // "high kernel": proc, limits
CTLHw = 6 // CTL_HW
SMT = 24 // HW_SMT
NCpuOnline = 25 // HW_NCPUONLINE
KernCptime = 40 // KERN_CPTIME
KernCptime2 = 71 // KERN_CPTIME2
)
@@ -35,18 +40,52 @@ 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)
if err == nil {
ClocksPerSec = float64(i)
func() {
getconf, err := exec.LookPath("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)
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 smt() (bool, error) {
mib := []int32{CTLHw, SMT}
buf, _, err := common.CallSyscall(mib)
if err != nil {
return false, err
}
var ret bool
br := bytes.NewReader(buf)
if err := binary.Read(br, binary.LittleEndian, &ret); err != nil {
return false, err
}
return ret, nil
}
func Times(percpu bool) ([]TimesStat, error) {
@@ -63,13 +102,27 @@ func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
ncpu = 1
}
smt, err := smt()
if err == syscall.EOPNOTSUPP {
// if hw.smt is not applicable for this platform (e.g. i386),
// pretend it's enabled
smt = true
} else if err != nil {
return nil, err
}
for i := 0; i < ncpu; i++ {
var cpuTimes [CPUStates]int64
j := i
if !smt {
j *= 2
}
var cpuTimes = make([]int32, CPUStates)
var mib []int32
if percpu {
mib = []int32{CTLKern, KernCptime}
mib = []int32{CTLKern, KernCptime2, int32(j)}
} else {
mib = []int32{CTLKern, KernCptime2, int32(i)}
mib = []int32{CTLKern, KernCptime}
}
buf, _, err := common.CallSyscall(mib)
if err != nil {
@@ -88,10 +141,10 @@ func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
Idle: float64(cpuTimes[CPIdle]) / ClocksPerSec,
Irq: float64(cpuTimes[CPIntr]) / ClocksPerSec,
}
if !percpu {
c.CPU = "cpu-total"
if percpu {
c.CPU = fmt.Sprintf("cpu%d", j)
} else {
c.CPU = fmt.Sprintf("cpu%d", i)
c.CPU = "cpu-total"
}
ret = append(ret, c)
}
@@ -106,14 +159,37 @@ 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")
var u32 uint32
if u32, err = unix.SysctlUint32("hw.cpuspeed"); err != nil {
return nil, err
}
c.Mhz = float64(u32)
mib := []int32{CTLHw, NCpuOnline}
buf, _, err := common.CallSyscall(mib)
if err != nil {
return nil, err
}
c.ModelName = v
var ncpu int32
br := bytes.NewReader(buf)
err = binary.Read(br, binary.LittleEndian, &ncpu)
if err != nil {
return nil, err
}
c.Cores = ncpu
if c.ModelName, err = unix.Sysctl("hw.model"); err != nil {
return nil, err
}
return append(ret, c), nil
}
func CountsWithContext(ctx context.Context, logical bool) (int, error) {
return runtime.NumCPU(), nil
}

View File

@@ -6,17 +6,16 @@ import (
"fmt"
"os/exec"
"regexp"
"runtime"
"sort"
"strconv"
"strings"
"github.com/shirou/gopsutil/internal/common"
)
var ClocksPerSec = float64(128)
func init() {
getconf, err := exec.LookPath("/usr/bin/getconf")
getconf, err := exec.LookPath("getconf")
if err != nil {
return
}
@@ -30,12 +29,97 @@ func init() {
}
}
//sum all values in a float64 map with float64 keys
func msum(x map[float64]float64) float64 {
total := 0.0
for _, y := range x {
total += y
}
return total
}
func Times(percpu bool) ([]TimesStat, error) {
return TimesWithContext(context.Background(), percpu)
}
func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
return []TimesStat{}, common.ErrNotImplementedError
kstatSys, err := exec.LookPath("kstat")
if err != nil {
return nil, fmt.Errorf("cannot find kstat: %s", err)
}
cpu := make(map[float64]float64)
idle := make(map[float64]float64)
user := make(map[float64]float64)
kern := make(map[float64]float64)
iowt := make(map[float64]float64)
//swap := make(map[float64]float64)
kstatSysOut, err := invoke.CommandWithContext(ctx, kstatSys, "-p", "cpu_stat:*:*:/^idle$|^user$|^kernel$|^iowait$|^swap$/")
if err != nil {
return nil, fmt.Errorf("cannot execute kstat: %s", err)
}
re := regexp.MustCompile(`[:\s]+`)
for _, line := range strings.Split(string(kstatSysOut), "\n") {
fields := re.Split(line, -1)
if fields[0] != "cpu_stat" {
continue
}
cpuNumber, err := strconv.ParseFloat(fields[1], 64)
if err != nil {
return nil, fmt.Errorf("cannot parse cpu number: %s", err)
}
cpu[cpuNumber] = cpuNumber
switch fields[3] {
case "idle":
idle[cpuNumber], err = strconv.ParseFloat(fields[4], 64)
if err != nil {
return nil, fmt.Errorf("cannot parse idle: %s", err)
}
case "user":
user[cpuNumber], err = strconv.ParseFloat(fields[4], 64)
if err != nil {
return nil, fmt.Errorf("cannot parse user: %s", err)
}
case "kernel":
kern[cpuNumber], err = strconv.ParseFloat(fields[4], 64)
if err != nil {
return nil, fmt.Errorf("cannot parse kernel: %s", err)
}
case "iowait":
iowt[cpuNumber], err = strconv.ParseFloat(fields[4], 64)
if err != nil {
return nil, fmt.Errorf("cannot parse iowait: %s", err)
}
//not sure how this translates, don't report, add to kernel, something else?
/*case "swap":
swap[cpuNumber], err = strconv.ParseFloat(fields[4], 64)
if err != nil {
return nil, fmt.Errorf("cannot parse swap: %s", err)
} */
}
}
ret := make([]TimesStat, 0, len(cpu))
if percpu {
for _, c := range cpu {
ct := &TimesStat{
CPU: fmt.Sprintf("cpu%d", int(cpu[c])),
Idle: idle[c] / ClocksPerSec,
User: user[c] / ClocksPerSec,
System: kern[c] / ClocksPerSec,
Iowait: iowt[c] / ClocksPerSec,
}
ret = append(ret, *ct)
}
} else {
ct := &TimesStat{
CPU: "cpu-total",
Idle: msum(idle) / ClocksPerSec,
User: msum(user) / ClocksPerSec,
System: msum(kern) / ClocksPerSec,
Iowait: msum(iowt) / ClocksPerSec,
}
ret = append(ret, *ct)
}
return ret, nil
}
func Info() ([]InfoStat, error) {
@@ -43,20 +127,20 @@ func Info() ([]InfoStat, error) {
}
func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
psrInfo, err := exec.LookPath("/usr/sbin/psrinfo")
psrInfo, err := exec.LookPath("psrinfo")
if err != nil {
return nil, fmt.Errorf("cannot find psrinfo: %s", err)
}
psrInfoOut, err := invoke.Command(psrInfo, "-p", "-v")
psrInfoOut, err := invoke.CommandWithContext(ctx, psrInfo, "-p", "-v")
if err != nil {
return nil, fmt.Errorf("cannot execute psrinfo: %s", err)
}
isaInfo, err := exec.LookPath("/usr/bin/isainfo")
isaInfo, err := exec.LookPath("isainfo")
if err != nil {
return nil, fmt.Errorf("cannot find isainfo: %s", err)
}
isaInfoOut, err := invoke.Command(isaInfo, "-b", "-v")
isaInfoOut, err := invoke.CommandWithContext(ctx, isaInfo, "-b", "-v")
if err != nil {
return nil, fmt.Errorf("cannot execute isainfo: %s", err)
}
@@ -196,3 +280,7 @@ func parseProcessorInfo(cmdOutput string) ([]InfoStat, error) {
}
return result, nil
}
func CountsWithContext(ctx context.Context, logical bool) (int, error) {
return runtime.NumCPU(), nil
}

View File

@@ -1,110 +0,0 @@
package cpu
import (
"io/ioutil"
"path/filepath"
"reflect"
"sort"
"testing"
)
func TestParseISAInfo(t *testing.T) {
cases := []struct {
filename string
expected []string
}{
{
"1cpu_1core_isainfo.txt",
[]string{"rdseed", "adx", "avx2", "fma", "bmi2", "bmi1", "rdrand", "f16c", "vmx",
"avx", "xsave", "pclmulqdq", "aes", "movbe", "sse4.2", "sse4.1", "ssse3", "popcnt",
"tscp", "cx16", "sse3", "sse2", "sse", "fxsr", "mmx", "cmov", "amd_sysc", "cx8",
"tsc", "fpu"},
},
{
"2cpu_1core_isainfo.txt",
[]string{"rdseed", "adx", "avx2", "fma", "bmi2", "bmi1", "rdrand", "f16c", "vmx",
"avx", "xsave", "pclmulqdq", "aes", "movbe", "sse4.2", "sse4.1", "ssse3", "popcnt",
"tscp", "cx16", "sse3", "sse2", "sse", "fxsr", "mmx", "cmov", "amd_sysc", "cx8",
"tsc", "fpu"},
},
{
"2cpu_8core_isainfo.txt",
[]string{"vmx", "avx", "xsave", "pclmulqdq", "aes", "sse4.2", "sse4.1", "ssse3", "popcnt",
"tscp", "cx16", "sse3", "sse2", "sse", "fxsr", "mmx", "cmov", "amd_sysc", "cx8",
"tsc", "fpu"},
},
}
for _, tc := range cases {
content, err := ioutil.ReadFile(filepath.Join("testdata", "solaris", tc.filename))
if err != nil {
t.Errorf("cannot read test case: %s", err)
}
sort.Strings(tc.expected)
flags, err := parseISAInfo(string(content))
if err != nil {
t.Fatalf("parseISAInfo: %s", err)
}
if !reflect.DeepEqual(tc.expected, flags) {
t.Fatalf("Bad flags\nExpected: %v\n Actual: %v", tc.expected, flags)
}
}
}
func TestParseProcessorInfo(t *testing.T) {
cases := []struct {
filename string
expected []InfoStat
}{
{
"1cpu_1core_psrinfo.txt",
[]InfoStat{
{CPU: 0, VendorID: "GenuineIntel", Family: "6", Model: "78", Stepping: 3, PhysicalID: "0", CoreID: "0", Cores: 1, ModelName: "Intel(r) Core(tm) i7-6567U CPU @ 3.30GHz", Mhz: 3312},
},
},
{
"2cpu_1core_psrinfo.txt",
[]InfoStat{
{CPU: 0, VendorID: "GenuineIntel", Family: "6", Model: "78", Stepping: 3, PhysicalID: "0", CoreID: "0", Cores: 1, ModelName: "Intel(r) Core(tm) i7-6567U CPU @ 3.30GHz", Mhz: 3312},
{CPU: 1, VendorID: "GenuineIntel", Family: "6", Model: "78", Stepping: 3, PhysicalID: "1", CoreID: "0", Cores: 1, ModelName: "Intel(r) Core(tm) i7-6567U CPU @ 3.30GHz", Mhz: 3312},
},
},
{
"2cpu_8core_psrinfo.txt",
[]InfoStat{
{CPU: 0, VendorID: "GenuineIntel", Family: "6", Model: "45", Stepping: 7, PhysicalID: "0", CoreID: "0", Cores: 2, ModelName: "Intel(r) Xeon(r) CPU E5-2670 0 @ 2.60GHz", Mhz: 2600},
{CPU: 1, VendorID: "GenuineIntel", Family: "6", Model: "45", Stepping: 7, PhysicalID: "0", CoreID: "1", Cores: 2, ModelName: "Intel(r) Xeon(r) CPU E5-2670 0 @ 2.60GHz", Mhz: 2600},
{CPU: 2, VendorID: "GenuineIntel", Family: "6", Model: "45", Stepping: 7, PhysicalID: "0", CoreID: "2", Cores: 2, ModelName: "Intel(r) Xeon(r) CPU E5-2670 0 @ 2.60GHz", Mhz: 2600},
{CPU: 3, VendorID: "GenuineIntel", Family: "6", Model: "45", Stepping: 7, PhysicalID: "0", CoreID: "3", Cores: 2, ModelName: "Intel(r) Xeon(r) CPU E5-2670 0 @ 2.60GHz", Mhz: 2600},
{CPU: 4, VendorID: "GenuineIntel", Family: "6", Model: "45", Stepping: 7, PhysicalID: "0", CoreID: "4", Cores: 2, ModelName: "Intel(r) Xeon(r) CPU E5-2670 0 @ 2.60GHz", Mhz: 2600},
{CPU: 5, VendorID: "GenuineIntel", Family: "6", Model: "45", Stepping: 7, PhysicalID: "0", CoreID: "5", Cores: 2, ModelName: "Intel(r) Xeon(r) CPU E5-2670 0 @ 2.60GHz", Mhz: 2600},
{CPU: 6, VendorID: "GenuineIntel", Family: "6", Model: "45", Stepping: 7, PhysicalID: "0", CoreID: "6", Cores: 2, ModelName: "Intel(r) Xeon(r) CPU E5-2670 0 @ 2.60GHz", Mhz: 2600},
{CPU: 7, VendorID: "GenuineIntel", Family: "6", Model: "45", Stepping: 7, PhysicalID: "0", CoreID: "7", Cores: 2, ModelName: "Intel(r) Xeon(r) CPU E5-2670 0 @ 2.60GHz", Mhz: 2600},
{CPU: 8, VendorID: "GenuineIntel", Family: "6", Model: "45", Stepping: 7, PhysicalID: "1", CoreID: "0", Cores: 2, ModelName: "Intel(r) Xeon(r) CPU E5-2670 0 @ 2.60GHz", Mhz: 2600},
{CPU: 9, VendorID: "GenuineIntel", Family: "6", Model: "45", Stepping: 7, PhysicalID: "1", CoreID: "1", Cores: 2, ModelName: "Intel(r) Xeon(r) CPU E5-2670 0 @ 2.60GHz", Mhz: 2600},
{CPU: 10, VendorID: "GenuineIntel", Family: "6", Model: "45", Stepping: 7, PhysicalID: "1", CoreID: "2", Cores: 2, ModelName: "Intel(r) Xeon(r) CPU E5-2670 0 @ 2.60GHz", Mhz: 2600},
{CPU: 11, VendorID: "GenuineIntel", Family: "6", Model: "45", Stepping: 7, PhysicalID: "1", CoreID: "3", Cores: 2, ModelName: "Intel(r) Xeon(r) CPU E5-2670 0 @ 2.60GHz", Mhz: 2600},
{CPU: 12, VendorID: "GenuineIntel", Family: "6", Model: "45", Stepping: 7, PhysicalID: "1", CoreID: "4", Cores: 2, ModelName: "Intel(r) Xeon(r) CPU E5-2670 0 @ 2.60GHz", Mhz: 2600},
{CPU: 13, VendorID: "GenuineIntel", Family: "6", Model: "45", Stepping: 7, PhysicalID: "1", CoreID: "5", Cores: 2, ModelName: "Intel(r) Xeon(r) CPU E5-2670 0 @ 2.60GHz", Mhz: 2600},
{CPU: 14, VendorID: "GenuineIntel", Family: "6", Model: "45", Stepping: 7, PhysicalID: "1", CoreID: "6", Cores: 2, ModelName: "Intel(r) Xeon(r) CPU E5-2670 0 @ 2.60GHz", Mhz: 2600},
{CPU: 15, VendorID: "GenuineIntel", Family: "6", Model: "45", Stepping: 7, PhysicalID: "1", CoreID: "7", Cores: 2, ModelName: "Intel(r) Xeon(r) CPU E5-2670 0 @ 2.60GHz", Mhz: 2600},
},
},
}
for _, tc := range cases {
content, err := ioutil.ReadFile(filepath.Join("testdata", "solaris", tc.filename))
if err != nil {
t.Errorf("cannot read test case: %s", err)
}
cpus, err := parseProcessorInfo(string(content))
if !reflect.DeepEqual(tc.expected, cpus) {
t.Fatalf("Bad Processor Info\nExpected: %v\n Actual: %v", tc.expected, cpus)
}
}
}

View File

@@ -1,145 +0,0 @@
package cpu
import (
"fmt"
"os"
"runtime"
"testing"
"time"
)
func TestCpu_times(t *testing.T) {
v, err := Times(false)
if err != nil {
t.Errorf("error %v", err)
}
if len(v) == 0 {
t.Error("could not get CPUs ", err)
}
empty := TimesStat{}
for _, vv := range v {
if vv == empty {
t.Errorf("could not get CPU User: %v", vv)
}
}
}
func TestCpu_counts(t *testing.T) {
v, err := Counts(true)
if err != nil {
t.Errorf("error %v", err)
}
if v == 0 {
t.Errorf("could not get CPU counts: %v", v)
}
}
func TestCPUTimeStat_String(t *testing.T) {
v := TimesStat{
CPU: "cpu0",
User: 100.1,
System: 200.1,
Idle: 300.1,
}
e := `{"cpu":"cpu0","user":100.1,"system":200.1,"idle":300.1,"nice":0.0,"iowait":0.0,"irq":0.0,"softirq":0.0,"steal":0.0,"guest":0.0,"guestNice":0.0,"stolen":0.0}`
if e != fmt.Sprintf("%v", v) {
t.Errorf("CPUTimesStat string is invalid: %v", v)
}
}
func TestCpuInfo(t *testing.T) {
v, err := Info()
if err != nil {
t.Errorf("error %v", err)
}
if len(v) == 0 {
t.Errorf("could not get CPU Info")
}
for _, vv := range v {
if vv.ModelName == "" {
t.Errorf("could not get CPU Info: %v", vv)
}
}
}
func testCPUPercent(t *testing.T, percpu bool) {
numcpu := runtime.NumCPU()
testCount := 3
if runtime.GOOS != "windows" {
testCount = 100
v, err := Percent(time.Millisecond, percpu)
if err != nil {
t.Errorf("error %v", err)
}
// Skip CircleCI which CPU num is different
if os.Getenv("CIRCLECI") != "true" {
if (percpu && len(v) != numcpu) || (!percpu && len(v) != 1) {
t.Fatalf("wrong number of entries from CPUPercent: %v", v)
}
}
}
for i := 0; i < testCount; i++ {
duration := time.Duration(10) * time.Microsecond
v, err := Percent(duration, percpu)
if err != nil {
t.Errorf("error %v", err)
}
for _, percent := range v {
// Check for slightly greater then 100% to account for any rounding issues.
if percent < 0.0 || percent > 100.0001*float64(numcpu) {
t.Fatalf("CPUPercent value is invalid: %f", percent)
}
}
}
}
func testCPUPercentLastUsed(t *testing.T, percpu bool) {
numcpu := runtime.NumCPU()
testCount := 10
if runtime.GOOS != "windows" {
testCount = 2
v, err := Percent(time.Millisecond, percpu)
if err != nil {
t.Errorf("error %v", err)
}
// Skip CircleCI which CPU num is different
if os.Getenv("CIRCLECI") != "true" {
if (percpu && len(v) != numcpu) || (!percpu && len(v) != 1) {
t.Fatalf("wrong number of entries from CPUPercent: %v", v)
}
}
}
for i := 0; i < testCount; i++ {
v, err := Percent(0, percpu)
if err != nil {
t.Errorf("error %v", err)
}
time.Sleep(1 * time.Millisecond)
for _, percent := range v {
// Check for slightly greater then 100% to account for any rounding issues.
if percent < 0.0 || percent > 100.0001*float64(numcpu) {
t.Fatalf("CPUPercent value is invalid: %f", percent)
}
}
}
}
func TestCPUPercent(t *testing.T) {
testCPUPercent(t, false)
}
func TestCPUPercentPerCpu(t *testing.T) {
testCPUPercent(t, true)
}
func TestCPUPercentIntervalZero(t *testing.T) {
testCPUPercentLastUsed(t, false)
}
func TestCPUPercentIntervalZeroPerCPU(t *testing.T) {
testCPUPercentLastUsed(t, true)
}

View File

@@ -12,30 +12,35 @@ import (
"golang.org/x/sys/windows"
)
var (
procGetActiveProcessorCount = common.Modkernel32.NewProc("GetActiveProcessorCount")
procGetNativeSystemInfo = common.Modkernel32.NewProc("GetNativeSystemInfo")
)
type Win32_Processor struct {
LoadPercentage *uint16
Family uint16
Manufacturer string
Name string
NumberOfLogicalProcessors uint32
NumberOfCores uint32
ProcessorID *string
Stepping *string
MaxClockSpeed uint32
}
// Win32_PerfFormattedData_Counters_ProcessorInformation stores instance value of the perf counters
type Win32_PerfFormattedData_Counters_ProcessorInformation struct {
Name string
PercentDPCTime uint64
PercentIdleTime uint64
PercentUserTime uint64
PercentProcessorTime uint64
PercentInterruptTime uint64
PercentPriorityTime uint64
PercentPrivilegedTime uint64
InterruptsPerSec uint32
ProcessorFrequency uint32
DPCRate uint32
// SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION
// defined in windows api doc with the following
// https://docs.microsoft.com/en-us/windows/desktop/api/winternl/nf-winternl-ntquerysysteminformation#system_processor_performance_information
// additional fields documented here
// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/processor_performance.htm
type win32_SystemProcessorPerformanceInformation struct {
IdleTime int64 // idle time in 100ns (this is not a filetime).
KernelTime int64 // kernel time in 100ns. kernel time includes idle time. (this is not a filetime).
UserTime int64 // usertime in 100ns (this is not a filetime).
DpcTime int64 // dpc time in 100ns (this is not a filetime).
InterruptTime int64 // interrupt time in 100ns
InterruptCount uint32
}
// Win32_PerfFormattedData_PerfOS_System struct to have count of processes and processor queue length
@@ -44,6 +49,17 @@ type Win32_PerfFormattedData_PerfOS_System struct {
ProcessorQueueLength uint32
}
const (
win32_TicksPerSecond = 10000000.0
// systemProcessorPerformanceInformationClass information class to query with NTQuerySystemInformation
// https://processhacker.sourceforge.io/doc/ntexapi_8h.html#ad5d815b48e8f4da1ef2eb7a2f18a54e0
win32_SystemProcessorPerformanceInformationClass = 8
// size of systemProcessorPerformanceInfoSize in memory
win32_SystemProcessorPerformanceInfoSize = uint32(unsafe.Sizeof(win32_SystemProcessorPerformanceInformation{}))
)
// Times returns times stat per cpu and combined for all CPUs
func Times(percpu bool) ([]TimesStat, error) {
return TimesWithContext(context.Background(), percpu)
@@ -117,24 +133,6 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
return ret, nil
}
// 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_PerfFormattedData_Counters_ProcessorInformation, error) {
var ret []Win32_PerfFormattedData_Counters_ProcessorInformation
q := wmi.CreateQuery(&ret, "")
err := common.WMIQueryWithContext(ctx, q, &ret)
if err != nil {
return []Win32_PerfFormattedData_Counters_ProcessorInformation{}, err
}
return ret, err
}
// ProcInfo returns processes count and processor queue length in the system.
// There is a single queue for processor even on multiprocessors systems.
func ProcInfo() ([]Win32_PerfFormattedData_PerfOS_System, error) {
@@ -154,19 +152,104 @@ 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) {
var ret []TimesStat
stats, err := PerfInfo()
stats, err := perfInfo()
if err != nil {
return nil, err
}
for _, v := range stats {
for core, v := range stats {
c := TimesStat{
CPU: v.Name,
User: float64(v.PercentUserTime),
System: float64(v.PercentPrivilegedTime),
Idle: float64(v.PercentIdleTime),
Irq: float64(v.PercentInterruptTime),
CPU: fmt.Sprintf("cpu%d", core),
User: float64(v.UserTime) / win32_TicksPerSecond,
System: float64(v.KernelTime-v.IdleTime) / win32_TicksPerSecond,
Idle: float64(v.IdleTime) / win32_TicksPerSecond,
Irq: float64(v.InterruptTime) / win32_TicksPerSecond,
}
ret = append(ret, c)
}
return ret, nil
}
// makes call to Windows API function to retrieve performance information for each core
func perfInfo() ([]win32_SystemProcessorPerformanceInformation, error) {
// Make maxResults large for safety.
// We can't invoke the api call with a results array that's too small.
// If we have more than 2056 cores on a single host, then it's probably the future.
maxBuffer := 2056
// buffer for results from the windows proc
resultBuffer := make([]win32_SystemProcessorPerformanceInformation, maxBuffer)
// size of the buffer in memory
bufferSize := uintptr(win32_SystemProcessorPerformanceInfoSize) * uintptr(maxBuffer)
// size of the returned response
var retSize uint32
// Invoke windows api proc.
// The returned err from the windows dll proc will always be non-nil even when successful.
// See https://godoc.org/golang.org/x/sys/windows#LazyProc.Call for more information
retCode, _, err := common.ProcNtQuerySystemInformation.Call(
win32_SystemProcessorPerformanceInformationClass, // System Information Class -> SystemProcessorPerformanceInformation
uintptr(unsafe.Pointer(&resultBuffer[0])), // pointer to first element in result buffer
bufferSize, // size of the buffer in memory
uintptr(unsafe.Pointer(&retSize)), // pointer to the size of the returned results the windows proc will set this
)
// check return code for errors
if retCode != 0 {
return nil, fmt.Errorf("call to NtQuerySystemInformation returned %d. err: %s", retCode, err.Error())
}
// calculate the number of returned elements based on the returned size
numReturnedElements := retSize / win32_SystemProcessorPerformanceInfoSize
// trim results to the number of returned elements
resultBuffer = resultBuffer[:numReturnedElements]
return resultBuffer, nil
}
// SystemInfo is an equivalent representation of SYSTEM_INFO in the Windows API.
// https://msdn.microsoft.com/en-us/library/ms724958%28VS.85%29.aspx?f=255&MSPPError=-2147217396
// https://github.com/elastic/go-windows/blob/bb1581babc04d5cb29a2bfa7a9ac6781c730c8dd/kernel32.go#L43
type systemInfo struct {
wProcessorArchitecture uint16
wReserved uint16
dwPageSize uint32
lpMinimumApplicationAddress uintptr
lpMaximumApplicationAddress uintptr
dwActiveProcessorMask uintptr
dwNumberOfProcessors uint32
dwProcessorType uint32
dwAllocationGranularity uint32
wProcessorLevel uint16
wProcessorRevision uint16
}
func CountsWithContext(ctx context.Context, logical bool) (int, error) {
if logical {
// https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_psutil_windows.c#L97
err := procGetActiveProcessorCount.Find()
if err == nil { // Win7+
ret, _, _ := procGetActiveProcessorCount.Call(uintptr(0xffff)) // ALL_PROCESSOR_GROUPS is 0xffff according to Rust's winapi lib https://docs.rs/winapi/*/x86_64-pc-windows-msvc/src/winapi/shared/ntdef.rs.html#120
if ret != 0 {
return int(ret), nil
}
}
var systemInfo systemInfo
_, _, err = procGetNativeSystemInfo.Call(uintptr(unsafe.Pointer(&systemInfo)))
if systemInfo.dwNumberOfProcessors == 0 {
return 0, err
}
return int(systemInfo.dwNumberOfProcessors), nil
}
// physical cores https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_psutil_windows.c#L499
// for the time being, try with unreliable and slow WMI call…
var dst []Win32_Processor
q := wmi.CreateQuery(&dst, "")
if err := common.WMIQueryWithContext(ctx, q, &dst); err != nil {
return 0, err
}
var count uint32
for _, d := range dst {
count += d.NumberOfCores
}
return int(count), nil
}

View File

@@ -6,11 +6,7 @@ import (
"github.com/shirou/gopsutil/internal/common"
)
var invoke common.Invoker
func init() {
invoke = common.Invoke{}
}
var invoke common.Invoker = common.Invoke{}
type UsageStat struct {
Path string `json:"path"`
@@ -46,6 +42,7 @@ type IOCountersStat struct {
WeightedIO uint64 `json:"weightedIO"`
Name string `json:"name"`
SerialNumber string `json:"serialNumber"`
Label string `json:"label"`
}
func (d UsageStat) String() string {

131
vendor/github.com/shirou/gopsutil/disk/disk_darwin.c generated vendored Normal file
View File

@@ -0,0 +1,131 @@
// https://github.com/lufia/iostat/blob/9f7362b77ad333b26c01c99de52a11bdb650ded2/iostat_darwin.c
#include <stdint.h>
#include <CoreFoundation/CoreFoundation.h>
#include "disk_darwin.h"
#define IOKIT 1 /* to get io_name_t in device_types.h */
#include <IOKit/IOKitLib.h>
#include <IOKit/storage/IOBlockStorageDriver.h>
#include <IOKit/storage/IOMedia.h>
#include <IOKit/IOBSD.h>
#include <mach/mach_host.h>
static int getdrivestat(io_registry_entry_t d, DriveStats *stat);
static int fillstat(io_registry_entry_t d, DriveStats *stat);
int
readdrivestat(DriveStats a[], int n)
{
mach_port_t port;
CFMutableDictionaryRef match;
io_iterator_t drives;
io_registry_entry_t d;
kern_return_t status;
int na, rv;
IOMasterPort(bootstrap_port, &port);
match = IOServiceMatching("IOMedia");
CFDictionaryAddValue(match, CFSTR(kIOMediaWholeKey), kCFBooleanTrue);
status = IOServiceGetMatchingServices(port, match, &drives);
if(status != KERN_SUCCESS)
return -1;
na = 0;
while(na < n && (d=IOIteratorNext(drives)) > 0){
rv = getdrivestat(d, &a[na]);
if(rv < 0)
return -1;
if(rv > 0)
na++;
IOObjectRelease(d);
}
IOObjectRelease(drives);
return na;
}
static int
getdrivestat(io_registry_entry_t d, DriveStats *stat)
{
io_registry_entry_t parent;
kern_return_t status;
CFDictionaryRef props;
CFStringRef name;
CFNumberRef num;
int rv;
memset(stat, 0, sizeof *stat);
status = IORegistryEntryGetParentEntry(d, kIOServicePlane, &parent);
if(status != KERN_SUCCESS)
return -1;
if(!IOObjectConformsTo(parent, "IOBlockStorageDriver")){
IOObjectRelease(parent);
return 0;
}
status = IORegistryEntryCreateCFProperties(d, (CFMutableDictionaryRef *)&props, kCFAllocatorDefault, kNilOptions);
if(status != KERN_SUCCESS){
IOObjectRelease(parent);
return -1;
}
name = (CFStringRef)CFDictionaryGetValue(props, CFSTR(kIOBSDNameKey));
CFStringGetCString(name, stat->name, NAMELEN, CFStringGetSystemEncoding());
num = (CFNumberRef)CFDictionaryGetValue(props, CFSTR(kIOMediaSizeKey));
CFNumberGetValue(num, kCFNumberSInt64Type, &stat->size);
num = (CFNumberRef)CFDictionaryGetValue(props, CFSTR(kIOMediaPreferredBlockSizeKey));
CFNumberGetValue(num, kCFNumberSInt64Type, &stat->blocksize);
CFRelease(props);
rv = fillstat(parent, stat);
IOObjectRelease(parent);
if(rv < 0)
return -1;
return 1;
}
static struct {
char *key;
size_t off;
} statstab[] = {
{kIOBlockStorageDriverStatisticsBytesReadKey, offsetof(DriveStats, read)},
{kIOBlockStorageDriverStatisticsBytesWrittenKey, offsetof(DriveStats, written)},
{kIOBlockStorageDriverStatisticsReadsKey, offsetof(DriveStats, nread)},
{kIOBlockStorageDriverStatisticsWritesKey, offsetof(DriveStats, nwrite)},
{kIOBlockStorageDriverStatisticsTotalReadTimeKey, offsetof(DriveStats, readtime)},
{kIOBlockStorageDriverStatisticsTotalWriteTimeKey, offsetof(DriveStats, writetime)},
{kIOBlockStorageDriverStatisticsLatentReadTimeKey, offsetof(DriveStats, readlat)},
{kIOBlockStorageDriverStatisticsLatentWriteTimeKey, offsetof(DriveStats, writelat)},
};
static int
fillstat(io_registry_entry_t d, DriveStats *stat)
{
CFDictionaryRef props, v;
CFNumberRef num;
kern_return_t status;
typeof(statstab[0]) *bp, *ep;
status = IORegistryEntryCreateCFProperties(d, (CFMutableDictionaryRef *)&props, kCFAllocatorDefault, kNilOptions);
if(status != KERN_SUCCESS)
return -1;
v = (CFDictionaryRef)CFDictionaryGetValue(props, CFSTR(kIOBlockStorageDriverStatisticsKey));
if(v == NULL){
CFRelease(props);
return -1;
}
ep = &statstab[sizeof(statstab)/sizeof(statstab[0])];
for(bp = &statstab[0]; bp < ep; bp++){
CFStringRef s;
s = CFStringCreateWithCString(kCFAllocatorDefault, bp->key, CFStringGetSystemEncoding());
num = (CFNumberRef)CFDictionaryGetValue(v, s);
if(num)
CFNumberGetValue(num, kCFNumberSInt64Type, ((char*)stat)+bp->off);
CFRelease(s);
}
CFRelease(props);
return 0;
}

View File

@@ -5,7 +5,6 @@ package disk
import (
"context"
"path"
"unsafe"
"github.com/shirou/gopsutil/internal/common"
"golang.org/x/sys/unix"
@@ -18,61 +17,51 @@ func Partitions(all bool) ([]PartitionStat, error) {
func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, error) {
var ret []PartitionStat
count, err := Getfsstat(nil, MntWait)
count, err := unix.Getfsstat(nil, unix.MNT_WAIT)
if err != nil {
return ret, err
}
fs := make([]Statfs_t, count)
_, err = Getfsstat(fs, MntWait)
fs := make([]unix.Statfs_t, count)
if _, err = unix.Getfsstat(fs, unix.MNT_WAIT); err != nil {
return ret, err
}
for _, stat := range fs {
opts := "rw"
if stat.Flags&MntReadOnly != 0 {
if stat.Flags&unix.MNT_RDONLY != 0 {
opts = "ro"
}
if stat.Flags&MntSynchronous != 0 {
if stat.Flags&unix.MNT_SYNCHRONOUS != 0 {
opts += ",sync"
}
if stat.Flags&MntNoExec != 0 {
if stat.Flags&unix.MNT_NOEXEC != 0 {
opts += ",noexec"
}
if stat.Flags&MntNoSuid != 0 {
if stat.Flags&unix.MNT_NOSUID != 0 {
opts += ",nosuid"
}
if stat.Flags&MntUnion != 0 {
if stat.Flags&unix.MNT_UNION != 0 {
opts += ",union"
}
if stat.Flags&MntAsync != 0 {
if stat.Flags&unix.MNT_ASYNC != 0 {
opts += ",async"
}
if stat.Flags&MntSuidDir != 0 {
opts += ",suiddir"
if stat.Flags&unix.MNT_DONTBROWSE != 0 {
opts += ",nobrowse"
}
if stat.Flags&MntSoftDep != 0 {
opts += ",softdep"
if stat.Flags&unix.MNT_AUTOMOUNTED != 0 {
opts += ",automounted"
}
if stat.Flags&MntNoSymFollow != 0 {
opts += ",nosymfollow"
if stat.Flags&unix.MNT_JOURNALED != 0 {
opts += ",journaled"
}
if stat.Flags&MntGEOMJournal != 0 {
opts += ",gjounalc"
}
if stat.Flags&MntMultilabel != 0 {
if stat.Flags&unix.MNT_MULTILABEL != 0 {
opts += ",multilabel"
}
if stat.Flags&MntACLs != 0 {
opts += ",acls"
if stat.Flags&unix.MNT_NOATIME != 0 {
opts += ",noatime"
}
if stat.Flags&MntNoATime != 0 {
opts += ",noattime"
}
if stat.Flags&MntClusterRead != 0 {
opts += ",nocluster"
}
if stat.Flags&MntClusterWrite != 0 {
opts += ",noclusterw"
}
if stat.Flags&MntNFS4ACLs != 0 {
opts += ",nfs4acls"
if stat.Flags&unix.MNT_NODEV != 0 {
opts += ",nodev"
}
d := PartitionStat{
Device: common.IntToString(stat.Mntfromname[:]),
@@ -92,25 +81,6 @@ func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, erro
return ret, nil
}
func Getfsstat(buf []Statfs_t, flags int) (n int, err error) {
return GetfsstatWithContext(context.Background(), buf, flags)
}
func GetfsstatWithContext(ctx context.Context, buf []Statfs_t, flags int) (n int, err error) {
var _p0 unsafe.Pointer
var bufsize uintptr
if len(buf) > 0 {
_p0 = unsafe.Pointer(&buf[0])
bufsize = unsafe.Sizeof(Statfs_t{}) * uintptr(len(buf))
}
r0, _, e1 := unix.Syscall(SYS_GETFSSTAT64, uintptr(_p0), bufsize, uintptr(flags))
n = int(r0)
if e1 != 0 {
err = e1
}
return
}
func getFsType(stat unix.Statfs_t) string {
return common.IntToString(stat.Fstypename[:])
}

View File

@@ -1,164 +1,33 @@
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/storage/IOBlockStorageDriver.h>
#include <IOKit/storage/IOMedia.h>
#include <IOKit/IOBSD.h>
// https://github.com/lufia/iostat/blob/9f7362b77ad333b26c01c99de52a11bdb650ded2/iostat_darwin.h
typedef struct DriveStats DriveStats;
typedef struct CPUStats CPUStats;
// The iterator of all things disk. Allocated by StartIOCounterFetch, released
// by EndIOCounterFetch.
static io_iterator_t diskIter;
enum {
NDRIVE = 16,
NAMELEN = 31
};
// Begins fetching IO counters.
//
// Returns 1 if the fetch started successfully, false otherwise.
//
// If the fetch was started successfully, you must call EndIOCounterFetch once
// done to release resources.
int StartIOCounterFetch()
{
if (IOServiceGetMatchingServices(kIOMasterPortDefault,
IOServiceMatching(kIOMediaClass),
&diskIter) != kIOReturnSuccess) {
return 0;
}
struct DriveStats {
char name[NAMELEN+1];
int64_t size;
int64_t blocksize;
return 1;
}
int64_t read;
int64_t written;
int64_t nread;
int64_t nwrite;
int64_t readtime;
int64_t writetime;
int64_t readlat;
int64_t writelat;
};
// Releases resources from fetching IO counters.
void EndIOCounterFetch()
{
IOObjectRelease(diskIter);
}
// The current disk entry of interest. Allocated by FetchNextDisk(), released by
// ReadDiskInfo().
static io_registry_entry_t diskEntry;
// The parent of diskEntry. Same lifetimes.
static io_registry_entry_t parentEntry;
// Fetches the next disk. Note that a disk entry is allocated, and will be held
// until it is processed and freed by ReadDiskInfo.
int FetchNextDisk()
{
while ((diskEntry = IOIteratorNext(diskIter)) != 0) {
// We are iterating IOMedia. We need to get the parent too (IOBSD).
if (IORegistryEntryGetParentEntry(diskEntry, kIOServicePlane, &parentEntry) != kIOReturnSuccess) {
// something is wrong...
IOObjectRelease(diskEntry);
continue;
}
if (!IOObjectConformsTo(parentEntry, "IOBlockStorageDriver")) {
// no use to us, try the next disk
IOObjectRelease(diskEntry);
IOObjectRelease(parentEntry);
continue;
}
// Got a disk OK.
return 1;
}
// No more disks.
return 0;
}
// Reads the current disk (from iteration) info into DiskInfo struct.
// Once done, all resources from the current iteration of reading are freed,
// ready for FetchNextDisk() to be called again.
int ReadDiskInfo(DiskInfo *info)
{
// Parent props. Allocated by us.
CFDictionaryRef parentProps = NULL;
// Disk props. Allocated by us.
CFDictionaryRef diskProps = NULL;
// Disk stats, fetched by us, but not allocated by us.
CFDictionaryRef stats = NULL;
if (IORegistryEntryCreateCFProperties(diskEntry, (CFMutableDictionaryRef *)&parentProps,
kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess)
{
// can't get parent props, give up
CFRelease(parentProps);
IOObjectRelease(diskEntry);
IOObjectRelease(parentEntry);
return -1;
}
if (IORegistryEntryCreateCFProperties(parentEntry, (CFMutableDictionaryRef *)&diskProps,
kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess)
{
// can't get disk props, give up
CFRelease(parentProps);
CFRelease(diskProps);
IOObjectRelease(diskEntry);
IOObjectRelease(parentEntry);
return -1;
}
// Start fetching
CFStringRef cfDiskName = (CFStringRef)CFDictionaryGetValue(parentProps, CFSTR(kIOBSDNameKey));
CFStringGetCString(cfDiskName, info->DiskName, MAX_DISK_NAME, CFStringGetSystemEncoding());
stats = (CFDictionaryRef)CFDictionaryGetValue( diskProps, CFSTR(kIOBlockStorageDriverStatisticsKey));
if (stats == NULL) {
// stat fetch failed...
CFRelease(parentProps);
CFRelease(diskProps);
IOObjectRelease(parentEntry);
IOObjectRelease(diskEntry);
return -1;
}
CFNumberRef cfnum;
if ((cfnum = (CFNumberRef)CFDictionaryGetValue(stats, CFSTR(kIOBlockStorageDriverStatisticsReadsKey)))) {
CFNumberGetValue(cfnum, kCFNumberSInt64Type, &info->Reads);
} else {
info->Reads = 0;
}
if ((cfnum = (CFNumberRef)CFDictionaryGetValue(stats, CFSTR(kIOBlockStorageDriverStatisticsWritesKey)))) {
CFNumberGetValue(cfnum, kCFNumberSInt64Type, &info->Writes);
} else {
info->Writes = 0;
}
if ((cfnum = (CFNumberRef)CFDictionaryGetValue(stats, CFSTR(kIOBlockStorageDriverStatisticsBytesReadKey)))) {
CFNumberGetValue(cfnum, kCFNumberSInt64Type, &info->ReadBytes);
} else {
info->ReadBytes = 0;
}
if ((cfnum = (CFNumberRef)CFDictionaryGetValue(stats, CFSTR(kIOBlockStorageDriverStatisticsBytesWrittenKey)))) {
CFNumberGetValue(cfnum, kCFNumberSInt64Type, &info->WriteBytes);
} else {
info->WriteBytes = 0;
}
if ((cfnum = (CFNumberRef)CFDictionaryGetValue(stats, CFSTR(kIOBlockStorageDriverStatisticsTotalReadTimeKey)))) {
CFNumberGetValue(cfnum, kCFNumberSInt64Type, &info->ReadTime);
} else {
info->ReadTime = 0;
}
if ((cfnum = (CFNumberRef)CFDictionaryGetValue(stats, CFSTR(kIOBlockStorageDriverStatisticsTotalWriteTimeKey)))) {
CFNumberGetValue(cfnum, kCFNumberSInt64Type, &info->WriteTime);
} else {
info->WriteTime = 0;
}
// note: read/write time are in ns, but we want ms.
info->ReadTime = info->ReadTime / 1000 / 1000;
info->WriteTime = info->WriteTime / 1000 / 1000;
CFRelease(parentProps);
CFRelease(diskProps);
IOObjectRelease(parentEntry);
IOObjectRelease(diskEntry);
return 0;
}
struct CPUStats {
natural_t user;
natural_t nice;
natural_t sys;
natural_t idle;
};
extern int readdrivestat(DriveStats a[], int n);
extern int readcpustat(CPUStats *cpu);

View File

@@ -1,58 +0,0 @@
// +build darwin
// +build amd64
package disk
const (
MntWait = 1
MfsNameLen = 15 /* length of fs type name, not inc. nul */
MNameLen = 90 /* length of buffer for returned name */
MFSTYPENAMELEN = 16 /* length of fs type name including null */
MAXPATHLEN = 1024
MNAMELEN = MAXPATHLEN
SYS_GETFSSTAT64 = 347
)
type Fsid struct{ val [2]int32 } /* file system id type */
type uid_t int32
// sys/mount.h
const (
MntReadOnly = 0x00000001 /* read only filesystem */
MntSynchronous = 0x00000002 /* filesystem written synchronously */
MntNoExec = 0x00000004 /* can't exec from filesystem */
MntNoSuid = 0x00000008 /* don't honor setuid bits on fs */
MntUnion = 0x00000020 /* union with underlying filesystem */
MntAsync = 0x00000040 /* filesystem written asynchronously */
MntSuidDir = 0x00100000 /* special handling of SUID on dirs */
MntSoftDep = 0x00200000 /* soft updates being done */
MntNoSymFollow = 0x00400000 /* do not follow symlinks */
MntGEOMJournal = 0x02000000 /* GEOM journal support enabled */
MntMultilabel = 0x04000000 /* MAC support for individual objects */
MntACLs = 0x08000000 /* ACL support enabled */
MntNoATime = 0x10000000 /* disable update of file access time */
MntClusterRead = 0x40000000 /* disable cluster read */
MntClusterWrite = 0x80000000 /* disable cluster write */
MntNFS4ACLs = 0x00000010
)
type Statfs_t struct {
Bsize uint32
Iosize int32
Blocks uint64
Bfree uint64
Bavail uint64
Files uint64
Ffree uint64
Fsid Fsid
Owner uint32
Type uint32
Flags uint32
Fssubtype uint32
Fstypename [16]int8
Mntonname [1024]int8
Mntfromname [1024]int8
Reserved [8]uint32
}

View File

@@ -1,58 +0,0 @@
// +build darwin
// +build arm64
package disk
const (
MntWait = 1
MfsNameLen = 15 /* length of fs type name, not inc. nul */
MNameLen = 90 /* length of buffer for returned name */
MFSTYPENAMELEN = 16 /* length of fs type name including null */
MAXPATHLEN = 1024
MNAMELEN = MAXPATHLEN
SYS_GETFSSTAT64 = 347
)
type Fsid struct{ val [2]int32 } /* file system id type */
type uid_t int32
// sys/mount.h
const (
MntReadOnly = 0x00000001 /* read only filesystem */
MntSynchronous = 0x00000002 /* filesystem written synchronously */
MntNoExec = 0x00000004 /* can't exec from filesystem */
MntNoSuid = 0x00000008 /* don't honor setuid bits on fs */
MntUnion = 0x00000020 /* union with underlying filesystem */
MntAsync = 0x00000040 /* filesystem written asynchronously */
MntSuidDir = 0x00100000 /* special handling of SUID on dirs */
MntSoftDep = 0x00200000 /* soft updates being done */
MntNoSymFollow = 0x00400000 /* do not follow symlinks */
MntGEOMJournal = 0x02000000 /* GEOM journal support enabled */
MntMultilabel = 0x04000000 /* MAC support for individual objects */
MntACLs = 0x08000000 /* ACL support enabled */
MntNoATime = 0x10000000 /* disable update of file access time */
MntClusterRead = 0x40000000 /* disable cluster read */
MntClusterWrite = 0x80000000 /* disable cluster write */
MntNFS4ACLs = 0x00000010
)
type Statfs_t struct {
Bsize uint32
Iosize int32
Blocks uint64
Bfree uint64
Bavail uint64
Files uint64
Ffree uint64
Fsid Fsid
Owner uint32
Type uint32
Flags uint32
Fssubtype uint32
Fstypename [16]int8
Mntonname [1024]int8
Mntfromname [1024]int8
Reserved [8]uint32
}

View File

@@ -4,32 +4,15 @@
package disk
/*
#cgo LDFLAGS: -lobjc -framework Foundation -framework IOKit
#cgo LDFLAGS: -framework CoreFoundation -framework IOKit
#include <stdint.h>
// ### enough?
const int MAX_DISK_NAME = 100;
typedef struct
{
char DiskName[MAX_DISK_NAME];
int64_t Reads;
int64_t Writes;
int64_t ReadBytes;
int64_t WriteBytes;
int64_t ReadTime;
int64_t WriteTime;
} DiskInfo;
#include <CoreFoundation/CoreFoundation.h>
#include "disk_darwin.h"
*/
import "C"
import (
"context"
"errors"
"strings"
"unsafe"
"github.com/shirou/gopsutil/internal/common"
)
@@ -39,57 +22,28 @@ func IOCounters(names ...string) (map[string]IOCountersStat, error) {
}
func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOCountersStat, error) {
if C.StartIOCounterFetch() == 0 {
return nil, errors.New("Unable to fetch disk list")
var buf [C.NDRIVE]C.DriveStats
n, err := C.readdrivestat(&buf[0], C.int(len(buf)))
if err != nil {
return nil, err
}
// Clean up when we are done.
defer C.EndIOCounterFetch()
ret := make(map[string]IOCountersStat, 0)
for {
res := C.FetchNextDisk()
if res == -1 {
return nil, errors.New("Unable to fetch disk information")
} else if res == 0 {
break // done
}
di := C.DiskInfo{}
if C.ReadDiskInfo((*C.DiskInfo)(unsafe.Pointer(&di))) == -1 {
return nil, errors.New("Unable to fetch disk properties")
}
// Used to only get the necessary part of the C string.
isRuneNull := func(r rune) bool {
return r == '\u0000'
}
// Map from the darwin-specific C struct to the Go type
//
// ### missing: IopsInProgress, WeightedIO, MergedReadCount,
// MergedWriteCount, SerialNumber
// IOKit can give us at least the serial number I think...
for i := 0; i < int(n); i++ {
d := IOCountersStat{
// Note: The Go type wants unsigned values, but CFNumberGetValue
// doesn't appear to be able to give us unsigned values. So, we
// cast, and hope for the best.
ReadBytes: uint64(di.ReadBytes),
WriteBytes: uint64(di.WriteBytes),
ReadCount: uint64(di.Reads),
WriteCount: uint64(di.Writes),
ReadTime: uint64(di.ReadTime),
WriteTime: uint64(di.WriteTime),
IoTime: uint64(di.ReadTime + di.WriteTime),
Name: strings.TrimFunc(C.GoStringN(&di.DiskName[0], C.MAX_DISK_NAME), isRuneNull),
ReadBytes: uint64(buf[i].read),
WriteBytes: uint64(buf[i].written),
ReadCount: uint64(buf[i].nread),
WriteCount: uint64(buf[i].nwrite),
ReadTime: uint64(buf[i].readtime / 1000 / 1000), // note: read/write time are in ns, but we want ms.
WriteTime: uint64(buf[i].writetime / 1000 / 1000),
IoTime: uint64((buf[i].readtime + buf[i].writetime) / 1000 / 1000),
Name: C.GoString(&buf[i].name[0]),
}
if len(names) > 0 && !common.StringsHas(names, d.Name) {
continue
}
ret[d.Name] = d
}
return ret, nil
}

View File

@@ -8,7 +8,6 @@ import (
"encoding/binary"
"path"
"strconv"
"unsafe"
"golang.org/x/sys/unix"
@@ -23,63 +22,65 @@ func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, erro
var ret []PartitionStat
// get length
count, err := unix.Getfsstat(nil, MNT_WAIT)
count, err := unix.Getfsstat(nil, unix.MNT_WAIT)
if err != nil {
return ret, err
}
fs := make([]Statfs, count)
_, err = Getfsstat(fs, MNT_WAIT)
fs := make([]unix.Statfs_t, count)
if _, err = unix.Getfsstat(fs, unix.MNT_WAIT); err != nil {
return ret, err
}
for _, stat := range fs {
opts := "rw"
if stat.Flags&MNT_RDONLY != 0 {
if stat.Flags&unix.MNT_RDONLY != 0 {
opts = "ro"
}
if stat.Flags&MNT_SYNCHRONOUS != 0 {
if stat.Flags&unix.MNT_SYNCHRONOUS != 0 {
opts += ",sync"
}
if stat.Flags&MNT_NOEXEC != 0 {
if stat.Flags&unix.MNT_NOEXEC != 0 {
opts += ",noexec"
}
if stat.Flags&MNT_NOSUID != 0 {
if stat.Flags&unix.MNT_NOSUID != 0 {
opts += ",nosuid"
}
if stat.Flags&MNT_UNION != 0 {
if stat.Flags&unix.MNT_UNION != 0 {
opts += ",union"
}
if stat.Flags&MNT_ASYNC != 0 {
if stat.Flags&unix.MNT_ASYNC != 0 {
opts += ",async"
}
if stat.Flags&MNT_SUIDDIR != 0 {
if stat.Flags&unix.MNT_SUIDDIR != 0 {
opts += ",suiddir"
}
if stat.Flags&MNT_SOFTDEP != 0 {
if stat.Flags&unix.MNT_SOFTDEP != 0 {
opts += ",softdep"
}
if stat.Flags&MNT_NOSYMFOLLOW != 0 {
if stat.Flags&unix.MNT_NOSYMFOLLOW != 0 {
opts += ",nosymfollow"
}
if stat.Flags&MNT_GJOURNAL != 0 {
opts += ",gjounalc"
if stat.Flags&unix.MNT_GJOURNAL != 0 {
opts += ",gjournal"
}
if stat.Flags&MNT_MULTILABEL != 0 {
if stat.Flags&unix.MNT_MULTILABEL != 0 {
opts += ",multilabel"
}
if stat.Flags&MNT_ACLS != 0 {
if stat.Flags&unix.MNT_ACLS != 0 {
opts += ",acls"
}
if stat.Flags&MNT_NOATIME != 0 {
opts += ",noattime"
if stat.Flags&unix.MNT_NOATIME != 0 {
opts += ",noatime"
}
if stat.Flags&MNT_NOCLUSTERR != 0 {
opts += ",nocluster"
if stat.Flags&unix.MNT_NOCLUSTERR != 0 {
opts += ",noclusterr"
}
if stat.Flags&MNT_NOCLUSTERW != 0 {
if stat.Flags&unix.MNT_NOCLUSTERW != 0 {
opts += ",noclusterw"
}
if stat.Flags&MNT_NFS4ACLS != 0 {
opts += ",nfs4acls"
if stat.Flags&unix.MNT_NFS4ACLS != 0 {
opts += ",nfsv4acls"
}
d := PartitionStat{
@@ -156,27 +157,6 @@ func (b Bintime) Compute() float64 {
// BT2LD(time) ((long double)(time).sec + (time).frac * BINTIME_SCALE)
// Getfsstat is borrowed from pkg/syscall/syscall_freebsd.go
// change Statfs_t to Statfs in order to get more information
func Getfsstat(buf []Statfs, flags int) (n int, err error) {
return GetfsstatWithContext(context.Background(), buf, flags)
}
func GetfsstatWithContext(ctx context.Context, buf []Statfs, flags int) (n int, err error) {
var _p0 unsafe.Pointer
var bufsize uintptr
if len(buf) > 0 {
_p0 = unsafe.Pointer(&buf[0])
bufsize = unsafe.Sizeof(Statfs{}) * uintptr(len(buf))
}
r0, _, e1 := unix.Syscall(unix.SYS_GETFSSTAT, uintptr(_p0), bufsize, uintptr(flags))
n = int(r0)
if e1 != 0 {
err = e1
}
return
}
func parseDevstat(buf []byte) (Devstat, error) {
var ds Devstat
br := bytes.NewReader(buf)

View File

@@ -15,28 +15,6 @@ const (
DEVSTAT_READ = 0x01
DEVSTAT_WRITE = 0x02
DEVSTAT_FREE = 0x03
MNT_RDONLY = 0x00000001
MNT_SYNCHRONOUS = 0x00000002
MNT_NOEXEC = 0x00000004
MNT_NOSUID = 0x00000008
MNT_UNION = 0x00000020
MNT_ASYNC = 0x00000040
MNT_SUIDDIR = 0x00100000
MNT_SOFTDEP = 0x00200000
MNT_NOSYMFOLLOW = 0x00400000
MNT_GJOURNAL = 0x02000000
MNT_MULTILABEL = 0x04000000
MNT_ACLS = 0x08000000
MNT_NOATIME = 0x10000000
MNT_NOCLUSTERR = 0x40000000
MNT_NOCLUSTERW = 0x80000000
MNT_NFS4ACLS = 0x00000010
MNT_WAIT = 1
MNT_NOWAIT = 2
MNT_LAZY = 3
MNT_SUSPEND = 4
)
const (
@@ -51,34 +29,6 @@ type (
_C_long_double int64
)
type Statfs struct {
Version uint32
Type uint32
Flags uint64
Bsize uint64
Iosize uint64
Blocks uint64
Bfree uint64
Bavail int64
Files uint64
Ffree int64
Syncwrites uint64
Asyncwrites uint64
Syncreads uint64
Asyncreads uint64
Spare [10]uint64
Namemax uint32
Owner uint32
Fsid Fsid
Charspare [80]int8
Fstypename [16]int8
Mntfromname [88]int8
Mntonname [88]int8
}
type Fsid struct {
Val [2]int32
}
type Devstat struct {
Sequence0 uint32
Allocated int32

View File

@@ -15,28 +15,6 @@ const (
DEVSTAT_READ = 0x01
DEVSTAT_WRITE = 0x02
DEVSTAT_FREE = 0x03
MNT_RDONLY = 0x00000001
MNT_SYNCHRONOUS = 0x00000002
MNT_NOEXEC = 0x00000004
MNT_NOSUID = 0x00000008
MNT_UNION = 0x00000020
MNT_ASYNC = 0x00000040
MNT_SUIDDIR = 0x00100000
MNT_SOFTDEP = 0x00200000
MNT_NOSYMFOLLOW = 0x00400000
MNT_GJOURNAL = 0x02000000
MNT_MULTILABEL = 0x04000000
MNT_ACLS = 0x08000000
MNT_NOATIME = 0x10000000
MNT_NOCLUSTERR = 0x40000000
MNT_NOCLUSTERW = 0x80000000
MNT_NFS4ACLS = 0x00000010
MNT_WAIT = 1
MNT_NOWAIT = 2
MNT_LAZY = 3
MNT_SUSPEND = 4
)
const (
@@ -51,34 +29,6 @@ type (
_C_long_double int64
)
type Statfs struct {
Version uint32
Type uint32
Flags uint64
Bsize uint64
Iosize uint64
Blocks uint64
Bfree uint64
Bavail int64
Files uint64
Ffree int64
Syncwrites uint64
Asyncwrites uint64
Syncreads uint64
Asyncreads uint64
Spare [10]uint64
Namemax uint32
Owner uint32
Fsid Fsid
Charspare [80]int8
Fstypename [16]int8
Mntfromname [88]int8
Mntonname [88]int8
}
type Fsid struct {
Val [2]int32
}
type Devstat struct {
Sequence0 uint32
Allocated int32

View File

@@ -0,0 +1,62 @@
// Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types_freebsd.go
package disk
const (
sizeofPtr = 0x4
sizeofShort = 0x2
sizeofInt = 0x4
sizeofLong = 0x4
sizeofLongLong = 0x8
sizeofLongDouble = 0x8
DEVSTAT_NO_DATA = 0x00
DEVSTAT_READ = 0x01
DEVSTAT_WRITE = 0x02
DEVSTAT_FREE = 0x03
)
const (
sizeOfDevstat = 0xf0
)
type (
_C_short int16
_C_int int32
_C_long int32
_C_long_long int64
_C_long_double int64
)
type Devstat struct {
Sequence0 uint32
Allocated int32
Start_count uint32
End_count uint32
Busy_from Bintime
Dev_links _Ctype_struct___0
Device_number uint32
Device_name [16]int8
Unit_number int32
Bytes [4]uint64
Operations [4]uint64
Duration [4]Bintime
Busy_time Bintime
Creation_time Bintime
Block_size uint32
Tag_types [3]uint64
Flags uint32
Device_type uint32
Priority uint32
Id *byte
Sequence1 uint32
}
type Bintime struct {
Sec int32
Frac uint64
}
type _Ctype_struct___0 struct {
Empty uint32
}

View File

@@ -0,0 +1,65 @@
// +build freebsd
// +build arm64
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
// cgo -godefs disk/types_freebsd.go
package disk
const (
sizeofPtr = 0x8
sizeofShort = 0x2
sizeofInt = 0x4
sizeofLong = 0x8
sizeofLongLong = 0x8
sizeofLongDouble = 0x8
DEVSTAT_NO_DATA = 0x00
DEVSTAT_READ = 0x01
DEVSTAT_WRITE = 0x02
DEVSTAT_FREE = 0x03
)
const (
sizeOfDevstat = 0x120
)
type (
_C_short int16
_C_int int32
_C_long int64
_C_long_long int64
_C_long_double int64
)
type Devstat struct {
Sequence0 uint32
Allocated int32
Start_count uint32
End_count uint32
Busy_from Bintime
Dev_links _Ctype_struct___0
Device_number uint32
Device_name [16]int8
Unit_number int32
Bytes [4]uint64
Operations [4]uint64
Duration [4]Bintime
Busy_time Bintime
Creation_time Bintime
Block_size uint32
Tag_types [3]uint64
Flags uint32
Device_type uint32
Priority uint32
Id *byte
Sequence1 uint32
Pad_cgo_0 [4]byte
}
type Bintime struct {
Sec int64
Frac uint64
}
type _Ctype_struct___0 struct {
Empty uint64
}

View File

@@ -3,16 +3,18 @@
package disk
import (
"bufio"
"bytes"
"context"
"fmt"
"os/exec"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"golang.org/x/sys/unix"
"github.com/shirou/gopsutil/internal/common"
"golang.org/x/sys/unix"
)
const (
@@ -45,6 +47,7 @@ const (
FUSE_SUPER_MAGIC = 0x65735546
FUTEXFS_SUPER_MAGIC = 0xBAD1DEA
HFS_SUPER_MAGIC = 0x4244
HFSPLUS_SUPER_MAGIC = 0x482b
HOSTFS_SUPER_MAGIC = 0x00c0ffee
HPFS_SUPER_MAGIC = 0xF995E849
HUGETLBFS_MAGIC = 0x958458f6
@@ -154,6 +157,7 @@ var fsTypeMap = map[int64]string{
GFS_SUPER_MAGIC: "gfs/gfs2", /* 0x1161970 remote */
GPFS_SUPER_MAGIC: "gpfs", /* 0x47504653 remote */
HFS_SUPER_MAGIC: "hfs", /* 0x4244 local */
HFSPLUS_SUPER_MAGIC: "hfsplus", /* 0x482b local */
HPFS_SUPER_MAGIC: "hpfs", /* 0xF995E849 local */
HUGETLBFS_MAGIC: "hugetlbfs", /* 0x958458F6 local */
MTD_INODE_FS_SUPER_MAGIC: "inodefs", /* 0x11307854 local */
@@ -222,30 +226,95 @@ func Partitions(all bool) ([]PartitionStat, error) {
}
func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, error) {
filename := common.HostProc("self/mounts")
useMounts := false
filename := common.HostProc("self/mountinfo")
lines, err := common.ReadLines(filename)
if err != nil {
return nil, err
if err != err.(*os.PathError) {
return nil, err
}
// if kernel does not support self/mountinfo, fallback to self/mounts (<2.6.26)
useMounts = true
filename = common.HostProc("self/mounts")
lines, err = common.ReadLines(filename)
if err != nil {
return nil, err
}
}
fs, err := getFileSystems()
if err != nil {
if err != nil && !all {
return nil, err
}
ret := make([]PartitionStat, 0, len(lines))
for _, line := range lines {
fields := strings.Fields(line)
d := PartitionStat{
Device: fields[0],
Mountpoint: fields[1],
Fstype: fields[2],
Opts: fields[3],
}
if all == false {
if d.Device == "none" || !common.StringsHas(fs, d.Fstype) {
continue
var d PartitionStat
if useMounts {
fields := strings.Fields(line)
d = PartitionStat{
Device: fields[0],
Mountpoint: unescapeFstab(fields[1]),
Fstype: fields[2],
Opts: fields[3],
}
if !all {
if d.Device == "none" || !common.StringsHas(fs, d.Fstype) {
continue
}
}
} else {
// a line of self/mountinfo has the following structure:
// 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
// (1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11)
// split the mountinfo line by the separator hyphen
parts := strings.Split(line, " - ")
if len(parts) != 2 {
return nil, fmt.Errorf("found invalid mountinfo line in file %s: %s ", filename, line)
}
fields := strings.Fields(parts[0])
blockDeviceID := fields[2]
mountPoint := fields[4]
mountOpts := fields[5]
fields = strings.Fields(parts[1])
fstype := fields[0]
device := fields[1]
d = PartitionStat{
Device: device,
Mountpoint: unescapeFstab(mountPoint),
Fstype: fstype,
Opts: mountOpts,
}
if !all {
if d.Device == "none" || !common.StringsHas(fs, d.Fstype) {
continue
}
}
if strings.HasPrefix(d.Device, "/dev/mapper/") {
devpath, err := filepath.EvalSymlinks(common.HostDev(strings.Replace(d.Device, "/dev", "", -1)))
if err == nil {
d.Device = devpath
}
}
// /dev/root is not the real device name
// so we get the real device name from its major/minor number
if d.Device == "/dev/root" {
devpath, err := os.Readlink(common.HostSys("/dev/block/" + blockDeviceID))
if err != nil {
return nil, err
}
d.Device = strings.Replace(d.Device, "root", filepath.Base(devpath), 1)
}
}
ret = append(ret, d)
@@ -370,6 +439,8 @@ func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOC
d.Name = name
d.SerialNumber = GetDiskSerialNumber(name)
d.Label = GetLabel(name)
ret[name] = d
}
return ret, nil
@@ -382,30 +453,57 @@ func GetDiskSerialNumber(name string) string {
}
func GetDiskSerialNumberWithContext(ctx context.Context, name string) string {
n := fmt.Sprintf("--name=%s", name)
udevadm, err := exec.LookPath("/sbin/udevadm")
var stat unix.Stat_t
err := unix.Stat(name, &stat)
if err != nil {
return ""
}
major := unix.Major(uint64(stat.Rdev))
minor := unix.Minor(uint64(stat.Rdev))
out, err := invoke.Command(udevadm, "info", "--query=property", n)
// does not return error, just an empty string
if err != nil {
return ""
}
lines := strings.Split(string(out), "\n")
for _, line := range lines {
values := strings.Split(line, "=")
if len(values) < 2 || values[0] != "ID_SERIAL" {
// only get ID_SERIAL, not ID_SERIAL_SHORT
continue
// Try to get the serial from udev data
udevDataPath := common.HostRun(fmt.Sprintf("udev/data/b%d:%d", major, minor))
if udevdata, err := ioutil.ReadFile(udevDataPath); err == nil {
scanner := bufio.NewScanner(bytes.NewReader(udevdata))
for scanner.Scan() {
values := strings.Split(scanner.Text(), "=")
if len(values) == 2 && values[0] == "E:ID_SERIAL" {
return values[1]
}
}
return values[1]
}
// Try to get the serial from sysfs, look at the disk device (minor 0) directly
// because if it is a partition it is not going to contain any device information
devicePath := common.HostSys(fmt.Sprintf("dev/block/%d:0/device", major))
model, _ := ioutil.ReadFile(filepath.Join(devicePath, "model"))
serial, _ := ioutil.ReadFile(filepath.Join(devicePath, "serial"))
if len(model) > 0 && len(serial) > 0 {
return fmt.Sprintf("%s_%s", string(model), string(serial))
}
return ""
}
// GetLabel returns label of given device or empty string on error.
// Name of device is expected, eg. /dev/sda
// Supports label based on devicemapper name
// See https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-block-dm
func GetLabel(name string) string {
// Try label based on devicemapper name
dmname_filename := common.HostSys(fmt.Sprintf("block/%s/dm/name", name))
if !common.PathExists(dmname_filename) {
return ""
}
dmname, err := ioutil.ReadFile(dmname_filename)
if err != nil {
return ""
} else {
return strings.TrimSpace(string(dmname))
}
}
func getFsType(stat unix.Statfs_t) string {
t := int64(stat.Type)
ret, ok := fsTypeMap[t]

View File

@@ -7,7 +7,6 @@ import (
"context"
"encoding/binary"
"path"
"unsafe"
"github.com/shirou/gopsutil/internal/common"
"golang.org/x/sys/unix"
@@ -21,34 +20,45 @@ func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, erro
var ret []PartitionStat
// get length
count, err := unix.Getfsstat(nil, MNT_WAIT)
count, err := unix.Getfsstat(nil, unix.MNT_WAIT)
if err != nil {
return ret, err
}
fs := make([]Statfs, count)
_, err = Getfsstat(fs, MNT_WAIT)
fs := make([]unix.Statfs_t, count)
if _, err = unix.Getfsstat(fs, unix.MNT_WAIT); err != nil {
return ret, err
}
for _, stat := range fs {
opts := "rw"
if stat.F_flags&MNT_RDONLY != 0 {
if stat.F_flags&unix.MNT_RDONLY != 0 {
opts = "ro"
}
if stat.F_flags&MNT_SYNCHRONOUS != 0 {
if stat.F_flags&unix.MNT_SYNCHRONOUS != 0 {
opts += ",sync"
}
if stat.F_flags&MNT_NOEXEC != 0 {
if stat.F_flags&unix.MNT_NOEXEC != 0 {
opts += ",noexec"
}
if stat.F_flags&MNT_NOSUID != 0 {
if stat.F_flags&unix.MNT_NOSUID != 0 {
opts += ",nosuid"
}
if stat.F_flags&MNT_NODEV != 0 {
if stat.F_flags&unix.MNT_NODEV != 0 {
opts += ",nodev"
}
if stat.F_flags&MNT_ASYNC != 0 {
if stat.F_flags&unix.MNT_ASYNC != 0 {
opts += ",async"
}
if stat.F_flags&unix.MNT_SOFTDEP != 0 {
opts += ",softdep"
}
if stat.F_flags&unix.MNT_NOATIME != 0 {
opts += ",noatime"
}
if stat.F_flags&unix.MNT_WXALLOWED != 0 {
opts += ",wxallowed"
}
d := PartitionStat{
Device: common.IntToString(stat.F_mntfromname[:]),
@@ -112,27 +122,6 @@ func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOC
// BT2LD(time) ((long double)(time).sec + (time).frac * BINTIME_SCALE)
// Getfsstat is borrowed from pkg/syscall/syscall_freebsd.go
// change Statfs_t to Statfs in order to get more information
func Getfsstat(buf []Statfs, flags int) (n int, err error) {
return GetfsstatWithContext(context.Background(), buf, flags)
}
func GetfsstatWithContext(ctx context.Context, buf []Statfs, flags int) (n int, err error) {
var _p0 unsafe.Pointer
var bufsize uintptr
if len(buf) > 0 {
_p0 = unsafe.Pointer(&buf[0])
bufsize = unsafe.Sizeof(Statfs{}) * uintptr(len(buf))
}
r0, _, e1 := unix.Syscall(unix.SYS_GETFSSTAT, uintptr(_p0), bufsize, uintptr(flags))
n = int(r0)
if e1 != 0 {
err = e1
}
return
}
func parseDiskstats(buf []byte) (Diskstats, error) {
var ds Diskstats
br := bytes.NewReader(buf)

View File

@@ -0,0 +1,35 @@
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
// cgo -godefs types_openbsd.go
package disk
const (
DEVSTAT_NO_DATA = 0x00
DEVSTAT_READ = 0x01
DEVSTAT_WRITE = 0x02
DEVSTAT_FREE = 0x03
)
const (
sizeOfDiskstats = 0x60
)
type Diskstats struct {
Name [16]int8
Busy int32
Rxfer uint64
Wxfer uint64
Seek uint64
Rbytes uint64
Wbytes uint64
Attachtime Timeval
Timestamp Timeval
Time Timeval
}
type Timeval struct {
Sec int64
Usec int32
}
type Diskstat struct{}
type Bintime struct{}

View File

@@ -1,71 +1,19 @@
// Created by cgo -godefs - DO NOT EDIT
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
// cgo -godefs types_openbsd.go
package disk
const (
sizeofPtr = 0x8
sizeofShort = 0x2
sizeofInt = 0x4
sizeofLong = 0x8
sizeofLongLong = 0x8
sizeofLongDouble = 0x8
DEVSTAT_NO_DATA = 0x00
DEVSTAT_READ = 0x01
DEVSTAT_WRITE = 0x02
DEVSTAT_FREE = 0x03
MNT_RDONLY = 0x00000001
MNT_SYNCHRONOUS = 0x00000002
MNT_NOEXEC = 0x00000004
MNT_NOSUID = 0x00000008
MNT_NODEV = 0x00000010
MNT_ASYNC = 0x00000040
MNT_WAIT = 1
MNT_NOWAIT = 2
MNT_LAZY = 3
)
const (
sizeOfDiskstats = 0x70
)
type (
_C_short int16
_C_int int32
_C_long int64
_C_long_long int64
_C_long_double int64
)
type Statfs struct {
F_flags uint32
F_bsize uint32
F_iosize uint32
Pad_cgo_0 [4]byte
F_blocks uint64
F_bfree uint64
F_bavail int64
F_files uint64
F_ffree uint64
F_favail int64
F_syncwrites uint64
F_syncreads uint64
F_asyncwrites uint64
F_asyncreads uint64
F_fsid Fsid
F_namemax uint32
F_owner uint32
F_ctime uint64
F_fstypename [16]int8
F_mntonname [90]int8
F_mntfromname [90]int8
F_mntfromspec [90]int8
Pad_cgo_1 [2]byte
Mount_info [160]byte
}
type Diskstats struct {
Name [16]int8
Busy int32
@@ -79,9 +27,6 @@ type Diskstats struct {
Timestamp Timeval
Time Timeval
}
type Fsid struct {
Val [2]int32
}
type Timeval struct {
Sec int64
Usec int64

View File

@@ -4,11 +4,12 @@ package disk
import (
"context"
"strconv"
"golang.org/x/sys/unix"
)
// Usage returns a file system usage. path is a filessytem path such
// Usage returns a file system usage. path is a filesystem path such
// as "/", not device file path like "/dev/vda1". If you want to use
// a return value of disk.Partitions, use "Mountpoint" not "Device".
func Usage(path string) (*UsageStat, error) {
@@ -24,7 +25,7 @@ func UsageWithContext(ctx context.Context, path string) (*UsageStat, error) {
bsize := stat.Bsize
ret := &UsageStat{
Path: path,
Path: unescapeFstab(path),
Fstype: getFsType(stat),
Total: (uint64(stat.Blocks) * uint64(bsize)),
Free: (uint64(stat.Bavail) * uint64(bsize)),
@@ -46,11 +47,22 @@ func UsageWithContext(ctx context.Context, path string) (*UsageStat, error) {
ret.InodesUsedPercent = (float64(ret.InodesUsed) / float64(ret.InodesTotal)) * 100.0
}
if ret.Total == 0 {
if (ret.Used + ret.Free) == 0 {
ret.UsedPercent = 0
} else {
ret.UsedPercent = (float64(ret.Used) / float64(ret.Total)) * 100.0
// We don't use ret.Total to calculate percent.
// see https://github.com/shirou/gopsutil/issues/562
ret.UsedPercent = (float64(ret.Used) / float64(ret.Used+ret.Free)) * 100.0
}
return ret, nil
}
// Unescape escaped octal chars (like space 040, ampersand 046 and backslash 134) to their real value in fstab fields issue#555
func unescapeFstab(path string) string {
escaped, err := strconv.Unquote(`"` + path + `"`)
if err != nil {
return path
}
return escaped
}

View File

@@ -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 (
@@ -40,8 +40,6 @@ func Usage(path string) (*UsageStat, error) {
}
func UsageWithContext(ctx context.Context, path string) (*UsageStat, error) {
ret := &UsageStat{}
lpFreeBytesAvailable := int64(0)
lpTotalNumberOfBytes := int64(0)
lpTotalNumberOfFreeBytes := int64(0)
@@ -53,7 +51,7 @@ func UsageWithContext(ctx context.Context, path string) (*UsageStat, error) {
if diskret == 0 {
return nil, err
}
ret = &UsageStat{
ret := &UsageStat{
Path: path,
Total: uint64(lpTotalNumberOfBytes),
Free: uint64(lpTotalNumberOfFreeBytes),
@@ -83,9 +81,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 +95,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)),

View File

@@ -6,11 +6,7 @@ import (
"github.com/shirou/gopsutil/internal/common"
)
var invoke common.Invoker
func init() {
invoke = common.Invoke{}
}
var invoke common.Invoker = common.Invoke{}
// A HostInfoStat describes the host status.
// This is not in the psutil but it useful.
@@ -24,6 +20,7 @@ type InfoStat struct {
PlatformFamily string `json:"platformFamily"` // ex: debian, rhel
PlatformVersion string `json:"platformVersion"` // version of the complete OS
KernelVersion string `json:"kernelVersion"` // version of the OS kernel (if available)
KernelArch string `json:"kernelArch"` // native cpu architecture queried at runtime, as returned by `uname -m` or empty string in case of error
VirtualizationSystem string `json:"virtualizationSystem"`
VirtualizationRole string `json:"virtualizationRole"` // guest or host
HostID string `json:"hostid"` // ex: uuid

View File

@@ -10,7 +10,6 @@ import (
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"sync/atomic"
"time"
@@ -18,6 +17,7 @@ import (
"github.com/shirou/gopsutil/internal/common"
"github.com/shirou/gopsutil/process"
"golang.org/x/sys/unix"
)
// from utmpx.h
@@ -38,12 +38,14 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
ret.Hostname = hostname
}
uname, err := exec.LookPath("uname")
kernelVersion, err := KernelVersionWithContext(ctx)
if err == nil {
out, err := invoke.Command(uname, "-r")
if err == nil {
ret.KernelVersion = strings.ToLower(strings.TrimSpace(string(out)))
}
ret.KernelVersion = kernelVersion
}
kernelArch, err := kernelArch()
if err == nil {
ret.KernelArch = kernelArch
}
platform, family, pver, err := PlatformInformation()
@@ -70,9 +72,9 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
ret.Procs = uint64(len(procs))
}
values, err := common.DoSysctrl("kern.uuid")
if err == nil && len(values) == 1 && values[0] != "" {
ret.HostID = strings.ToLower(values[0])
uuid, err := unix.Sysctl("kern.uuid")
if err == nil && uuid != "" {
ret.HostID = strings.ToLower(uuid)
}
return ret, nil
@@ -86,24 +88,22 @@ func BootTime() (uint64, error) {
}
func BootTimeWithContext(ctx context.Context) (uint64, error) {
// https://github.com/AaronO/dashd/blob/222e32ef9f7a1f9bea4a8da2c3627c4cb992f860/probe/probe_darwin.go
t := atomic.LoadUint64(&cachedBootTime)
if t != 0 {
return t, nil
}
values, err := common.DoSysctrl("kern.boottime")
value, err := unix.Sysctl("kern.boottime")
if err != nil {
return 0, err
}
// ex: { sec = 1392261637, usec = 627534 } Thu Feb 13 12:20:37 2014
v := strings.Replace(values[2], ",", "", 1)
boottime, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return 0, err
}
t = uint64(boottime)
atomic.StoreUint64(&cachedBootTime, t)
bytes := []byte(value[:])
var boottime uint64
boottime = uint64(bytes[0]) + uint64(bytes[1])*256 + uint64(bytes[2])*256*256 + uint64(bytes[3])*256*256*256
return t, nil
atomic.StoreUint64(&cachedBootTime, boottime)
return boottime, nil
}
func uptime(boot uint64) uint64 {
@@ -115,7 +115,7 @@ func Uptime() (uint64, error) {
}
func UptimeWithContext(ctx context.Context) (uint64, error) {
boot, err := BootTime()
boot, err := BootTimeWithContext(ctx)
if err != nil {
return 0, err
}
@@ -183,21 +183,27 @@ 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.Command(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.Command(sw_vers, "-productVersion")
out, err := invoke.CommandWithContext(ctx, sw_vers, "-productVersion")
if err == nil {
pver = strings.ToLower(strings.TrimSpace(string(out)))
}
// check if the macos server version file exists
_, err = os.Stat("/System/Library/CoreServices/ServerVersion.plist")
// server file doesn't exist
if os.IsNotExist(err) {
family = "Standalone Workstation"
} else {
family = "Server"
}
return platform, family, pver, nil
}
@@ -214,14 +220,6 @@ func KernelVersion() (string, error) {
}
func KernelVersionWithContext(ctx context.Context) (string, error) {
_, _, version, err := PlatformInformation()
return version, err
}
func SensorsTemperatures() ([]TemperatureStat, error) {
return SensorsTemperaturesWithContext(context.Background())
}
func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
return []TemperatureStat{}, common.ErrNotImplementedError
version, err := unix.Sysctl("kern.osrelease")
return strings.ToLower(version), err
}

View File

@@ -0,0 +1,53 @@
// +build darwin
// +build cgo
package host
// #cgo LDFLAGS: -framework IOKit
// #include <stdio.h>
// #include <string.h>
// #include "include/smc.c"
import "C"
import "context"
func SensorsTemperatures() ([]TemperatureStat, error) {
return SensorsTemperaturesWithContext(context.Background())
}
func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
temperatureKeys := []string{
C.AMBIENT_AIR_0,
C.AMBIENT_AIR_1,
C.CPU_0_DIODE,
C.CPU_0_HEATSINK,
C.CPU_0_PROXIMITY,
C.ENCLOSURE_BASE_0,
C.ENCLOSURE_BASE_1,
C.ENCLOSURE_BASE_2,
C.ENCLOSURE_BASE_3,
C.GPU_0_DIODE,
C.GPU_0_HEATSINK,
C.GPU_0_PROXIMITY,
C.HARD_DRIVE_BAY,
C.MEMORY_SLOT_0,
C.MEMORY_SLOTS_PROXIMITY,
C.NORTHBRIDGE,
C.NORTHBRIDGE_DIODE,
C.NORTHBRIDGE_PROXIMITY,
C.THUNDERBOLT_0,
C.THUNDERBOLT_1,
C.WIRELESS_MODULE,
}
var temperatures []TemperatureStat
C.open_smc()
defer C.close_smc()
for _, key := range temperatureKeys {
temperatures = append(temperatures, TemperatureStat{
SensorKey: key,
Temperature: float64(C.get_temperature(C.CString(key))),
})
}
return temperatures, nil
}

View File

@@ -0,0 +1,18 @@
// +build darwin
// +build !cgo
package host
import (
"context"
"github.com/shirou/gopsutil/internal/common"
)
func SensorsTemperatures() ([]TemperatureStat, error) {
return SensorsTemperaturesWithContext(context.Background())
}
func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
return []TemperatureStat{}, common.ErrNotImplementedError
}

View File

@@ -55,3 +55,11 @@ func KernelVersion() (string, error) {
func KernelVersionWithContext(ctx context.Context) (string, error) {
return "", common.ErrNotImplementedError
}
func PlatformInformation() (string, string, string, error) {
return PlatformInformationWithContext(context.Background())
}
func PlatformInformationWithContext(ctx context.Context) (string, string, string, error) {
return "", "", "", common.ErrNotImplementedError
}

View File

@@ -7,8 +7,8 @@ import (
"context"
"encoding/binary"
"io/ioutil"
"math"
"os"
"os/exec"
"runtime"
"strings"
"sync/atomic"
@@ -50,6 +50,11 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
ret.KernelVersion = version
}
kernelArch, err := kernelArch()
if err == nil {
ret.KernelArch = kernelArch
}
system, role, err := Virtualization()
if err == nil {
ret.VirtualizationSystem = system
@@ -144,11 +149,11 @@ func UsersWithContext(ctx context.Context) ([]UserStat, error) {
b := buf[i*sizeOfUtmpx : (i+1)*sizeOfUtmpx]
var u Utmpx
br := bytes.NewReader(b)
err := binary.Read(br, binary.LittleEndian, &u)
err := binary.Read(br, binary.BigEndian, &u)
if err != nil || u.Type != 4 {
continue
}
sec := (binary.LittleEndian.Uint32(u.Tv.Sec[:])) / 2 // TODO:
sec := math.Floor(float64(u.Tv) / 1000000)
user := UserStat{
User: common.IntToString(u.User[:]),
Terminal: common.IntToString(u.Line[:]),
@@ -168,25 +173,17 @@ func PlatformInformation() (string, string, string, error) {
}
func PlatformInformationWithContext(ctx context.Context) (string, string, string, error) {
platform := ""
family := ""
version := ""
uname, err := exec.LookPath("uname")
platform, err := unix.Sysctl("kern.ostype")
if err != nil {
return "", "", "", err
}
out, err := invoke.Command(uname, "-s")
if err == nil {
platform = strings.ToLower(strings.TrimSpace(string(out)))
version, err := unix.Sysctl("kern.osrelease")
if err != nil {
return "", "", "", err
}
out, err = invoke.Command(uname, "-r")
if err == nil {
version = strings.ToLower(strings.TrimSpace(string(out)))
}
return platform, family, version, nil
return strings.ToLower(platform), "", strings.ToLower(version), nil
}
func Virtualization() (string, string, error) {

View File

@@ -1,4 +1,4 @@
// Created by cgo -godefs - DO NOT EDIT
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
// cgo -godefs types_freebsd.go
package host
@@ -9,7 +9,7 @@ const (
sizeofInt = 0x4
sizeofLong = 0x4
sizeofLongLong = 0x8
sizeOfUtmpx = 197 // TODO why should 197
sizeOfUtmpx = 0xc5
)
type (
@@ -27,17 +27,11 @@ type Utmp struct {
}
type Utmpx struct {
Type int16
Tv Timeval
Type uint8
Tv uint64
Id [8]int8
Pid int32
Pid uint32
User [32]int8
Line [16]int8
Host [125]int8
// X__ut_spare [64]int8
}
type Timeval struct {
Sec [4]byte
Usec [3]byte
Host [128]int8
}

View File

@@ -1,4 +1,4 @@
// Created by cgo -godefs - DO NOT EDIT
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
// cgo -godefs types_freebsd.go
package host
@@ -9,7 +9,7 @@ const (
sizeofInt = 0x4
sizeofLong = 0x8
sizeofLongLong = 0x8
sizeOfUtmpx = 197 // TODO: why should 197, not 0x118
sizeOfUtmpx = 0xc5
)
type (
@@ -27,18 +27,11 @@ type Utmp struct {
}
type Utmpx struct {
Type int16
Tv Timeval
Type uint8
Tv uint64
Id [8]int8
Pid int32
Pid uint32
User [32]int8
Line [16]int8
Host [125]int8
// Host [128]int8
// X__ut_spare [64]int8
}
type Timeval struct {
Sec [4]byte
Usec [3]byte
Host [128]int8
}

View File

@@ -1,4 +1,4 @@
// Created by cgo -godefs - DO NOT EDIT
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
// cgo -godefs types_freebsd.go
package host
@@ -9,7 +9,7 @@ const (
sizeofInt = 0x4
sizeofLong = 0x8
sizeofLongLong = 0x8
sizeOfUtmpx = 197 // TODO: why should 197, not 0x118
sizeOfUtmpx = 0xc5
)
type (
@@ -27,18 +27,11 @@ type Utmp struct {
}
type Utmpx struct {
Type int16
Tv Timeval
Type uint8
Tv uint64
Id [8]int8
Pid int32
Pid uint32
User [32]int8
Line [16]int8
Host [125]int8
// Host [128]int8
// X__ut_spare [64]int8
}
type Timeval struct {
Sec [4]byte
Usec [3]byte
Host [128]int8
}

View File

@@ -0,0 +1,39 @@
// +build freebsd
// +build arm64
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
// cgo -godefs host/types_freebsd.go
package host
const (
sizeofPtr = 0x8
sizeofShort = 0x2
sizeofInt = 0x4
sizeofLong = 0x8
sizeofLongLong = 0x8
sizeOfUtmpx = 0xc5
)
type (
_C_short int16
_C_int int32
_C_long int64
_C_long_long int64
)
type Utmp struct {
Line [8]int8
Name [16]int8
Host [16]int8
Time int32
}
type Utmpx struct {
Type uint8
Tv uint64
Id [8]int8
Pid uint32
User [32]int8
Line [16]int8
Host [128]int8
}

View File

@@ -15,10 +15,10 @@ import (
"runtime"
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/shirou/gopsutil/internal/common"
"golang.org/x/sys/unix"
)
type LSB struct {
@@ -56,6 +56,11 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
ret.KernelVersion = kernelVersion
}
kernelArch, err := kernelArch()
if err == nil {
ret.KernelArch = kernelArch
}
system, role, err := Virtualization()
if err == nil {
ret.VirtualizationSystem = system
@@ -73,7 +78,11 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
}
sysProductUUID := common.HostSys("class/dmi/id/product_uuid")
machineID := common.HostEtc("machine-id")
procSysKernelRandomBootID := common.HostProc("sys/kernel/random/boot_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,62 +90,33 @@ 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] != "" {
ret.HostID = strings.ToLower(values[0])
lines, err := common.ReadLines(procSysKernelRandomBootID)
if err == nil && len(lines) > 0 && lines[0] != "" {
ret.HostID = strings.ToLower(lines[0])
}
}
return ret, nil
}
// cachedBootTime must be accessed via atomic.Load/StoreUint64
var cachedBootTime uint64
// BootTime returns the system boot time expressed in seconds since the epoch.
func BootTime() (uint64, error) {
return BootTimeWithContext(context.Background())
}
func BootTimeWithContext(ctx context.Context) (uint64, error) {
t := atomic.LoadUint64(&cachedBootTime)
if t != 0 {
return t, nil
}
system, role, err := Virtualization()
if err != nil {
return 0, err
}
statFile := "stat"
if system == "lxc" && role == "guest" {
// if lxc, /proc/uptime is used.
statFile = "uptime"
}
filename := common.HostProc(statFile)
lines, err := common.ReadLines(filename)
if err != nil {
return 0, err
}
for _, line := range lines {
if strings.HasPrefix(line, "btime") {
f := strings.Fields(line)
if len(f) != 2 {
return 0, fmt.Errorf("wrong btime format")
}
b, err := strconv.ParseInt(f[1], 10, 64)
if err != nil {
return 0, err
}
t = uint64(b)
atomic.StoreUint64(&cachedBootTime, t)
return t, nil
}
}
return 0, fmt.Errorf("could not find btime")
return common.BootTimeWithContext(ctx)
}
func uptime(boot uint64) uint64 {
@@ -202,26 +182,6 @@ func UsersWithContext(ctx context.Context) ([]UserStat, error) {
}
func getOSRelease() (platform string, version string, err error) {
contents, err := common.ReadLines(common.HostEtc("os-release"))
if err != nil {
return "", "", nil // return empty
}
for _, line := range contents {
field := strings.Split(line, "=")
if len(field) < 2 {
continue
}
switch field[0] {
case "ID": // use ID for lowercase
platform = field[1]
case "VERSION":
version = field[1]
}
}
return platform, version, nil
}
func getLSB() (*LSB, error) {
ret := &LSB{}
if common.PathExists(common.HostEtc("lsb-release")) {
@@ -246,7 +206,7 @@ func getLSB() (*LSB, error) {
}
}
} else if common.PathExists("/usr/bin/lsb_release") {
lsb_release, err := exec.LookPath("/usr/bin/lsb_release")
lsb_release, err := exec.LookPath("lsb_release")
if err != nil {
return ret, err
}
@@ -300,6 +260,12 @@ func PlatformInformationWithContext(ctx context.Context) (platform string, famil
if err == nil {
version = getRedhatishVersion(contents)
}
} else if common.PathExists(common.HostEtc("slackware-version")) {
platform = "slackware"
contents, err := common.ReadLines(common.HostEtc("slackware-version"))
if err == nil {
version = getSlackwareVersion(contents)
}
} else if common.PathExists(common.HostEtc("debian_version")) {
if lsb.ID == "Ubuntu" {
platform = "ubuntu"
@@ -314,7 +280,7 @@ func PlatformInformationWithContext(ctx context.Context) (platform string, famil
platform = "debian"
}
contents, err := common.ReadLines(common.HostEtc("debian_version"))
if err == nil {
if err == nil && len(contents) > 0 && contents[0] != "" {
version = contents[0]
}
}
@@ -349,11 +315,11 @@ func PlatformInformationWithContext(ctx context.Context) (platform string, famil
} else if common.PathExists(common.HostEtc("alpine-release")) {
platform = "alpine"
contents, err := common.ReadLines(common.HostEtc("alpine-release"))
if err == nil && len(contents) > 0 {
if err == nil && len(contents) > 0 && contents[0] != "" {
version = contents[0]
}
} else if common.PathExists(common.HostEtc("os-release")) {
p, v, err := getOSRelease()
p, v, err := common.GetOSRelease()
if err == nil {
platform = p
version = v
@@ -382,7 +348,7 @@ func PlatformInformationWithContext(ctx context.Context) (platform string, famil
family = "fedora"
case "oracle", "centos", "redhat", "scientific", "enterpriseenterprise", "amazon", "xenserver", "cloudlinux", "ibm_powerkvm":
family = "rhel"
case "suse", "opensuse":
case "suse", "opensuse", "sles":
family = "suse"
case "gentoo":
family = "gentoo"
@@ -396,6 +362,8 @@ func PlatformInformationWithContext(ctx context.Context) (platform string, famil
family = "alpine"
case "coreos":
family = "coreos"
case "solus":
family = "solus"
}
return platform, family, version, nil
@@ -407,19 +375,18 @@ func KernelVersion() (version string, err error) {
}
func KernelVersionWithContext(ctx context.Context) (version string, err error) {
filename := common.HostProc("sys/kernel/osrelease")
if common.PathExists(filename) {
contents, err := common.ReadLines(filename)
if err != nil {
return "", err
}
if len(contents) > 0 {
version = contents[0]
}
var utsname unix.Utsname
err = unix.Uname(&utsname)
if err != nil {
return "", err
}
return string(utsname.Release[:bytes.IndexByte(utsname.Release[:], 0)]), nil
}
return version, nil
func getSlackwareVersion(contents []string) string {
c := strings.ToLower(strings.Join(contents, ""))
c = strings.Replace(c, "slackware ", "", 1)
return c
}
func getRedhatishVersion(contents []string) string {
@@ -470,106 +437,7 @@ func Virtualization() (string, string, error) {
}
func VirtualizationWithContext(ctx context.Context) (string, string, error) {
var system string
var role string
filename := common.HostProc("xen")
if common.PathExists(filename) {
system = "xen"
role = "guest" // assume guest
if common.PathExists(filepath.Join(filename, "capabilities")) {
contents, err := common.ReadLines(filepath.Join(filename, "capabilities"))
if err == nil {
if common.StringsContains(contents, "control_d") {
role = "host"
}
}
}
}
filename = common.HostProc("modules")
if common.PathExists(filename) {
contents, err := common.ReadLines(filename)
if err == nil {
if common.StringsContains(contents, "kvm") {
system = "kvm"
role = "host"
} else if common.StringsContains(contents, "vboxdrv") {
system = "vbox"
role = "host"
} else if common.StringsContains(contents, "vboxguest") {
system = "vbox"
role = "guest"
} else if common.StringsContains(contents, "vmware") {
system = "vmware"
role = "guest"
}
}
}
filename = common.HostProc("cpuinfo")
if common.PathExists(filename) {
contents, err := common.ReadLines(filename)
if err == nil {
if common.StringsContains(contents, "QEMU Virtual CPU") ||
common.StringsContains(contents, "Common KVM processor") ||
common.StringsContains(contents, "Common 32-bit KVM processor") {
system = "kvm"
role = "guest"
}
}
}
filename = common.HostProc()
if common.PathExists(filepath.Join(filename, "bc", "0")) {
system = "openvz"
role = "host"
} else if common.PathExists(filepath.Join(filename, "vz")) {
system = "openvz"
role = "guest"
}
// not use dmidecode because it requires root
if common.PathExists(filepath.Join(filename, "self", "status")) {
contents, err := common.ReadLines(filepath.Join(filename, "self", "status"))
if err == nil {
if common.StringsContains(contents, "s_context:") ||
common.StringsContains(contents, "VxID:") {
system = "linux-vserver"
}
// TODO: guest or host
}
}
if common.PathExists(filepath.Join(filename, "self", "cgroup")) {
contents, err := common.ReadLines(filepath.Join(filename, "self", "cgroup"))
if err == nil {
if common.StringsContains(contents, "lxc") {
system = "lxc"
role = "guest"
} else if common.StringsContains(contents, "docker") {
system = "docker"
role = "guest"
} else if common.StringsContains(contents, "machine-rkt") {
system = "rkt"
role = "guest"
} else if common.PathExists("/usr/bin/lxc-version") {
system = "lxc"
role = "host"
}
}
}
if common.PathExists(common.HostEtc("os-release")) {
p, _, err := getOSRelease()
if err == nil && p == "coreos" {
system = "rkt" // Is it true?
role = "host"
}
}
return system, role, nil
return common.VirtualizationWithContext(ctx)
}
func SensorsTemperatures() ([]TemperatureStat, error) {
@@ -590,6 +458,7 @@ func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, err
return temperatures, err
}
}
var warns Warnings
// example directory
// device/ temp1_crit_alarm temp2_crit_alarm temp3_crit_alarm temp4_crit_alarm temp5_crit_alarm temp6_crit_alarm temp7_crit_alarm
@@ -612,19 +481,22 @@ func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, err
label = fmt.Sprintf("%s_", strings.Join(strings.Split(strings.TrimSpace(strings.ToLower(string(c))), " "), ""))
}
// Get the name of the tempearture you are reading
// Get the name of the temperature you are reading
name, err := ioutil.ReadFile(filepath.Join(filepath.Dir(file), "name"))
if err != nil {
return temperatures, err
warns.Add(err)
continue
}
// Get the temperature reading
current, err := ioutil.ReadFile(file)
if err != nil {
return temperatures, err
warns.Add(err)
continue
}
temperature, err := strconv.ParseFloat(strings.TrimSpace(string(current)), 64)
if err != nil {
warns.Add(err)
continue
}
@@ -634,5 +506,5 @@ func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, err
Temperature: temperature / 1000.0,
})
}
return temperatures, nil
return temperatures, warns.Reference()
}

View File

@@ -0,0 +1,43 @@
// Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types_linux.go
package host
const (
sizeofPtr = 0x4
sizeofShort = 0x2
sizeofInt = 0x4
sizeofLong = 0x4
sizeofLongLong = 0x8
sizeOfUtmp = 0x180
)
type (
_C_short int16
_C_int int32
_C_long int32
_C_long_long int64
)
type utmp struct {
Type int16
Pad_cgo_0 [2]byte
Pid int32
Line [32]int8
Id [4]int8
User [32]int8
Host [256]int8
Exit exit_status
Session int32
Tv timeval
Addr_v6 [4]int32
X__unused [20]int8
}
type exit_status struct {
Termination int16
Exit int16
}
type timeval struct {
Sec int32
Usec int32
}

View File

@@ -0,0 +1,43 @@
// Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types_linux.go
package host
const (
sizeofPtr = 0x4
sizeofShort = 0x2
sizeofInt = 0x4
sizeofLong = 0x4
sizeofLongLong = 0x8
sizeOfUtmp = 0x180
)
type (
_C_short int16
_C_int int32
_C_long int32
_C_long_long int64
)
type utmp struct {
Type int16
Pad_cgo_0 [2]byte
Pid int32
Line [32]int8
Id [4]int8
User [32]int8
Host [256]int8
Exit exit_status
Session int32
Tv timeval
Addr_v6 [4]int32
X__unused [20]int8
}
type exit_status struct {
Termination int16
Exit int16
}
type timeval struct {
Sec int32
Usec int32
}

View File

@@ -1,61 +0,0 @@
// +build linux
package host
import (
"testing"
)
func TestGetRedhatishVersion(t *testing.T) {
var ret string
c := []string{"Rawhide"}
ret = getRedhatishVersion(c)
if ret != "rawhide" {
t.Errorf("Could not get version rawhide: %v", ret)
}
c = []string{"Fedora release 15 (Lovelock)"}
ret = getRedhatishVersion(c)
if ret != "15" {
t.Errorf("Could not get version fedora: %v", ret)
}
c = []string{"Enterprise Linux Server release 5.5 (Carthage)"}
ret = getRedhatishVersion(c)
if ret != "5.5" {
t.Errorf("Could not get version redhat enterprise: %v", ret)
}
c = []string{""}
ret = getRedhatishVersion(c)
if ret != "" {
t.Errorf("Could not get version with no value: %v", ret)
}
}
func TestGetRedhatishPlatform(t *testing.T) {
var ret string
c := []string{"red hat"}
ret = getRedhatishPlatform(c)
if ret != "redhat" {
t.Errorf("Could not get platform redhat: %v", ret)
}
c = []string{"Fedora release 15 (Lovelock)"}
ret = getRedhatishPlatform(c)
if ret != "fedora" {
t.Errorf("Could not get platform fedora: %v", ret)
}
c = []string{"Enterprise Linux Server release 5.5 (Carthage)"}
ret = getRedhatishPlatform(c)
if ret != "enterprise" {
t.Errorf("Could not get platform redhat enterprise: %v", ret)
}
c = []string{""}
ret = getRedhatishPlatform(c)
if ret != "" {
t.Errorf("Could not get platform with no value: %v", ret)
}
}

View File

@@ -8,15 +8,15 @@ import (
"encoding/binary"
"io/ioutil"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"sync/atomic"
"time"
"unsafe"
"github.com/shirou/gopsutil/internal/common"
"github.com/shirou/gopsutil/process"
"golang.org/x/sys/unix"
)
const (
@@ -40,6 +40,11 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
ret.Hostname = hostname
}
kernelArch, err := kernelArch()
if err == nil {
ret.KernelArch = kernelArch
}
platform, family, version, err := PlatformInformation()
if err == nil {
ret.Platform = platform
@@ -66,20 +71,28 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
return ret, nil
}
// cachedBootTime must be accessed via atomic.Load/StoreUint64
var cachedBootTime uint64
func BootTime() (uint64, error) {
return BootTimeWithContext(context.Background())
}
func BootTimeWithContext(ctx context.Context) (uint64, error) {
val, err := common.DoSysctrl("kern.boottime")
// https://github.com/AaronO/dashd/blob/222e32ef9f7a1f9bea4a8da2c3627c4cb992f860/probe/probe_darwin.go
t := atomic.LoadUint64(&cachedBootTime)
if t != 0 {
return t, nil
}
value, err := unix.Sysctl("kern.boottime")
if err != nil {
return 0, err
}
bytes := []byte(value[:])
var boottime uint64
boottime = uint64(bytes[0]) + uint64(bytes[1])*256 + uint64(bytes[2])*256*256 + uint64(bytes[3])*256*256*256
boottime, err := strconv.ParseUint(val[0], 10, 64)
if err != nil {
return 0, err
}
atomic.StoreUint64(&cachedBootTime, boottime)
return boottime, nil
}
@@ -108,19 +121,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.Command(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.Command(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

15
vendor/github.com/shirou/gopsutil/host/host_posix.go generated vendored Normal file
View File

@@ -0,0 +1,15 @@
// +build linux freebsd openbsd darwin solaris
package host
import (
"bytes"
"golang.org/x/sys/unix"
)
func kernelArch() (string, error) {
var utsname unix.Utsname
err := unix.Uname(&utsname)
return string(utsname.Machine[:bytes.IndexByte(utsname.Machine[:], 0)]), err
}

View File

@@ -33,12 +33,12 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
result.Hostname = hostname
// Parse versions from output of `uname(1)`
uname, err := exec.LookPath("/usr/bin/uname")
uname, err := exec.LookPath("uname")
if err != nil {
return nil, err
}
out, err := invoke.Command(uname, "-srv")
out, err := invoke.CommandWithContext(ctx, uname, "-srv")
if err != nil {
return nil, err
}
@@ -54,6 +54,11 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
result.PlatformVersion = fields[2]
}
kernelArch, err := kernelArch()
if err == nil {
result.KernelArch = kernelArch
}
// Find distribution name from /etc/release
fh, err := os.Open("/etc/release")
if err != nil {
@@ -85,9 +90,9 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
switch result.Platform {
case "SmartOS":
// If everything works, use the current zone ID as the HostID if present.
zonename, err := exec.LookPath("/usr/bin/zonename")
zonename, err := exec.LookPath("zonename")
if err == nil {
out, err := invoke.Command(zonename)
out, err := invoke.CommandWithContext(ctx, zonename)
if err == nil {
sc := bufio.NewScanner(bytes.NewReader(out))
for sc.Scan() {
@@ -112,9 +117,9 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
// this point there are no hardware facilities available. This behavior
// matches that of other supported OSes.
if result.HostID == "" {
hostID, err := exec.LookPath("/usr/bin/hostid")
hostID, err := exec.LookPath("hostid")
if err == nil {
out, err := invoke.Command(hostID)
out, err := invoke.CommandWithContext(ctx, hostID)
if err == nil {
sc := bufio.NewScanner(bytes.NewReader(out))
for sc.Scan() {
@@ -151,12 +156,12 @@ func BootTime() (uint64, error) {
}
func BootTimeWithContext(ctx context.Context) (uint64, error) {
kstat, err := exec.LookPath("/usr/bin/kstat")
kstat, err := exec.LookPath("kstat")
if err != nil {
return 0, err
}
out, err := invoke.Command(kstat, "-p", "unix:0:system_misc:boot_time")
out, err := invoke.CommandWithContext(ctx, kstat, "-p", "unix:0:system_misc:boot_time")
if err != nil {
return 0, err
}
@@ -215,12 +220,12 @@ func KernelVersion() (string, error) {
func KernelVersionWithContext(ctx context.Context) (string, error) {
// Parse versions from output of `uname(1)`
uname, err := exec.LookPath("/usr/bin/uname")
uname, err := exec.LookPath("uname")
if err != nil {
return "", err
}
out, err := invoke.Command(uname, "-srv")
out, err := invoke.CommandWithContext(ctx, uname, "-srv")
if err != nil {
return "", err
}
@@ -231,3 +236,18 @@ func KernelVersionWithContext(ctx context.Context) (string, error) {
}
return "", fmt.Errorf("could not get kernel version")
}
func PlatformInformation() (platform string, family string, version string, err error) {
return PlatformInformationWithContext(context.Background())
}
func PlatformInformationWithContext(ctx context.Context) (platform string, family string, version string, err error) {
/* This is not finished yet at all. Please contribute! */
version, err = KernelVersion()
if err != nil {
return "", "", "", err
}
return "solaris", "solaris", version, nil
}

View File

@@ -1,150 +0,0 @@
package host
import (
"fmt"
"os"
"testing"
)
func TestHostInfo(t *testing.T) {
v, err := Info()
if err != nil {
t.Errorf("error %v", err)
}
empty := &InfoStat{}
if v == empty {
t.Errorf("Could not get hostinfo %v", v)
}
if v.Procs == 0 {
t.Errorf("Could not determine the number of host processes")
}
}
func TestUptime(t *testing.T) {
if os.Getenv("CIRCLECI") == "true" {
t.Skip("Skip CI")
}
v, err := Uptime()
if err != nil {
t.Errorf("error %v", err)
}
if v == 0 {
t.Errorf("Could not get up time %v", v)
}
}
func TestBoot_time(t *testing.T) {
if os.Getenv("CIRCLECI") == "true" {
t.Skip("Skip CI")
}
v, err := BootTime()
if err != nil {
t.Errorf("error %v", err)
}
if v == 0 {
t.Errorf("Could not get boot time %v", v)
}
if v < 946652400 {
t.Errorf("Invalid Boottime, older than 2000-01-01")
}
t.Logf("first boot time: %d", v)
v2, err := BootTime()
if v != v2 {
t.Errorf("cached boot time is different")
}
t.Logf("second boot time: %d", v2)
}
func TestUsers(t *testing.T) {
v, err := Users()
if err != nil {
t.Errorf("error %v", err)
}
empty := UserStat{}
if len(v) == 0 {
t.Fatal("Users is empty")
}
for _, u := range v {
if u == empty {
t.Errorf("Could not Users %v", v)
}
}
}
func TestHostInfoStat_String(t *testing.T) {
v := InfoStat{
Hostname: "test",
Uptime: 3000,
Procs: 100,
OS: "linux",
Platform: "ubuntu",
BootTime: 1447040000,
HostID: "edfd25ff-3c9c-b1a4-e660-bd826495ad35",
}
e := `{"hostname":"test","uptime":3000,"bootTime":1447040000,"procs":100,"os":"linux","platform":"ubuntu","platformFamily":"","platformVersion":"","kernelVersion":"","virtualizationSystem":"","virtualizationRole":"","hostid":"edfd25ff-3c9c-b1a4-e660-bd826495ad35"}`
if e != fmt.Sprintf("%v", v) {
t.Errorf("HostInfoStat string is invalid: %v", v)
}
}
func TestUserStat_String(t *testing.T) {
v := UserStat{
User: "user",
Terminal: "term",
Host: "host",
Started: 100,
}
e := `{"user":"user","terminal":"term","host":"host","started":100}`
if e != fmt.Sprintf("%v", v) {
t.Errorf("UserStat string is invalid: %v", v)
}
}
func TestHostGuid(t *testing.T) {
hi, err := Info()
if err != nil {
t.Error(err)
}
if hi.HostID == "" {
t.Error("Host id is empty")
} else {
t.Logf("Host id value: %v", hi.HostID)
}
}
func TestTemperatureStat_String(t *testing.T) {
v := TemperatureStat{
SensorKey: "CPU",
Temperature: 1.1,
}
s := `{"sensorKey":"CPU","sensorTemperature":1.1}`
if s != fmt.Sprintf("%v", v) {
t.Errorf("TemperatureStat string is invalid")
}
}
func TestVirtualization(t *testing.T) {
system, role, err := Virtualization()
if err != nil {
t.Errorf("Virtualization() failed, %v", err)
}
if system == "" || role == "" {
t.Errorf("Virtualization() retuns empty system or role: %s, %s", system, role)
}
t.Logf("Virtualization(): %s, %s", system, role)
}
func TestKernelVersion(t *testing.T) {
version, err := KernelVersion()
if err != nil {
t.Errorf("KernelVersion() failed, %v", err)
}
if version == "" {
t.Errorf("KernelVersion() retuns empty: %s", version)
}
t.Logf("KernelVersion(): %s", version)
}

View File

@@ -5,10 +5,12 @@ package host
import (
"context"
"fmt"
"math"
"os"
"runtime"
"strings"
"sync/atomic"
"syscall"
"time"
"unsafe"
@@ -20,15 +22,46 @@ import (
var (
procGetSystemTimeAsFileTime = common.Modkernel32.NewProc("GetSystemTimeAsFileTime")
osInfo *Win32_OperatingSystem
procGetTickCount32 = common.Modkernel32.NewProc("GetTickCount")
procGetTickCount64 = common.Modkernel32.NewProc("GetTickCount64")
procGetNativeSystemInfo = common.Modkernel32.NewProc("GetNativeSystemInfo")
procRtlGetVersion = common.ModNt.NewProc("RtlGetVersion")
)
type Win32_OperatingSystem struct {
Version string
Caption string
ProductType uint32
BuildNumber string
LastBootUpTime time.Time
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_osversioninfoexw
type osVersionInfoExW struct {
dwOSVersionInfoSize uint32
dwMajorVersion uint32
dwMinorVersion uint32
dwBuildNumber uint32
dwPlatformId uint32
szCSDVersion [128]uint16
wServicePackMajor uint16
wServicePackMinor uint16
wSuiteMask uint16
wProductType uint8
wReserved uint8
}
type systemInfo struct {
wProcessorArchitecture uint16
wReserved uint16
dwPageSize uint32
lpMinimumApplicationAddress uintptr
lpMaximumApplicationAddress uintptr
dwActiveProcessorMask uintptr
dwNumberOfProcessors uint32
dwProcessorType uint32
dwAllocationGranularity uint32
wProcessorLevel uint16
wProcessorRevision uint16
}
type msAcpi_ThermalZoneTemperature struct {
Active bool
CriticalTripPoint uint32
CurrentTemperature uint32
InstanceName string
}
func Info() (*InfoStat, error) {
@@ -59,7 +92,14 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
}
{
boot, err := BootTime()
kernelArch, err := kernelArch()
if err == nil {
ret.KernelArch = kernelArch
}
}
{
boot, err := BootTimeWithContext(ctx)
if err == nil {
ret.BootTime = boot
ret.Uptime, _ = Uptime()
@@ -69,12 +109,12 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
{
hostID, err := getMachineGuid()
if err == nil {
ret.HostID = strings.ToLower(hostID)
ret.HostID = hostID
}
}
{
procs, err := process.Pids()
procs, err := process.PidsWithContext(ctx)
if err == nil {
ret.Procs = uint64(len(procs))
}
@@ -84,6 +124,8 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) {
}
func getMachineGuid() (string, error) {
// there has been reports of issues on 32bit using golang.org/x/sys/windows/registry, see https://github.com/shirou/gopsutil/pull/312#issuecomment-277422612
// for rationale of using windows.RegOpenKeyEx/RegQueryValueEx instead of registry.OpenKey/GetStringValue
var h windows.Handle
err := windows.RegOpenKeyEx(windows.HKEY_LOCAL_MACHINE, windows.StringToUTF16Ptr(`SOFTWARE\Microsoft\Cryptography`), 0, windows.KEY_READ|windows.KEY_WOW64_64KEY, &h)
if err != nil {
@@ -108,24 +150,7 @@ func getMachineGuid() (string, error) {
return "", fmt.Errorf("HostID incorrect: %q\n", hostID)
}
return hostID, nil
}
func GetOSInfo() (Win32_OperatingSystem, error) {
return GetOSInfoWithContext(context.Background())
}
func GetOSInfoWithContext(ctx context.Context) (Win32_OperatingSystem, error) {
var dst []Win32_OperatingSystem
q := wmi.CreateQuery(&dst, "")
err := common.WMIQueryWithContext(ctx, q, &dst)
if err != nil {
return Win32_OperatingSystem{}, err
}
osInfo = &dst[0]
return dst[0], nil
return strings.ToLower(hostID), nil
}
func Uptime() (uint64, error) {
@@ -133,18 +158,19 @@ func Uptime() (uint64, error) {
}
func UptimeWithContext(ctx context.Context) (uint64, error) {
if osInfo == nil {
_, err := GetOSInfoWithContext(ctx)
if err != nil {
return 0, err
}
procGetTickCount := procGetTickCount64
err := procGetTickCount64.Find()
if err != nil {
procGetTickCount = procGetTickCount32 // handle WinXP, but keep in mind that "the time will wrap around to zero if the system is run continuously for 49.7 days." from MSDN
}
now := time.Now()
t := osInfo.LastBootUpTime.Local()
return uint64(now.Sub(t).Seconds()), nil
r1, _, lastErr := syscall.Syscall(procGetTickCount.Addr(), 0, 0, 0, 0)
if lastErr != 0 {
return 0, lastErr
}
return uint64((time.Duration(r1) * time.Millisecond).Seconds()), nil
}
func bootTime(up uint64) uint64 {
func bootTimeFromUptime(up uint64) uint64 {
return uint64(time.Now().Unix()) - up
}
@@ -164,7 +190,7 @@ func BootTimeWithContext(ctx context.Context) (uint64, error) {
if err != nil {
return 0, err
}
t = bootTime(up)
t = bootTimeFromUptime(up)
atomic.StoreUint64(&cachedBootTime, t)
return t, nil
}
@@ -174,18 +200,50 @@ func PlatformInformation() (platform string, family string, version string, err
}
func PlatformInformationWithContext(ctx context.Context) (platform string, family string, version string, err error) {
if osInfo == nil {
_, err = GetOSInfoWithContext(ctx)
if err != nil {
return
}
// GetVersionEx lies on Windows 8.1 and returns as Windows 8 if we don't declare compatibility in manifest
// RtlGetVersion bypasses this lying layer and returns the true Windows version
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/nf-wdm-rtlgetversion
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_osversioninfoexw
var osInfo osVersionInfoExW
osInfo.dwOSVersionInfoSize = uint32(unsafe.Sizeof(osInfo))
ret, _, err := procRtlGetVersion.Call(uintptr(unsafe.Pointer(&osInfo)))
if ret != 0 {
return
}
// Platform
platform = strings.Trim(osInfo.Caption, " ")
var h windows.Handle // like getMachineGuid(), we query the registry using the raw windows.RegOpenKeyEx/RegQueryValueEx
err = windows.RegOpenKeyEx(windows.HKEY_LOCAL_MACHINE, windows.StringToUTF16Ptr(`SOFTWARE\Microsoft\Windows NT\CurrentVersion`), 0, windows.KEY_READ|windows.KEY_WOW64_64KEY, &h)
if err != nil {
return
}
defer windows.RegCloseKey(h)
var bufLen uint32
var valType uint32
err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`ProductName`), nil, &valType, nil, &bufLen)
if err != nil {
return
}
regBuf := make([]uint16, bufLen/2+1)
err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`ProductName`), nil, &valType, (*byte)(unsafe.Pointer(&regBuf[0])), &bufLen)
if err != nil {
return
}
platform = windows.UTF16ToString(regBuf[:])
if !strings.HasPrefix(platform, "Microsoft") {
platform = "Microsoft " + platform
}
err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`CSDVersion`), nil, &valType, nil, &bufLen) // append Service Pack number, only on success
if err == nil { // don't return an error if only the Service Pack retrieval fails
regBuf = make([]uint16, bufLen/2+1)
err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`CSDVersion`), nil, &valType, (*byte)(unsafe.Pointer(&regBuf[0])), &bufLen)
if err == nil {
platform += " " + windows.UTF16ToString(regBuf[:])
}
}
// PlatformFamily
switch osInfo.ProductType {
switch osInfo.wProductType {
case 1:
family = "Standalone Workstation"
case 2:
@@ -195,9 +253,9 @@ func PlatformInformationWithContext(ctx context.Context) (platform string, famil
}
// Platform Version
version = fmt.Sprintf("%s Build %s", osInfo.Version, osInfo.BuildNumber)
version = fmt.Sprintf("%d.%d.%d Build %d", osInfo.dwMajorVersion, osInfo.dwMinorVersion, osInfo.dwBuildNumber, osInfo.dwBuildNumber)
return
return platform, family, version, nil
}
func Users() ([]UserStat, error) {
@@ -215,7 +273,30 @@ func SensorsTemperatures() ([]TemperatureStat, error) {
}
func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
return []TemperatureStat{}, common.ErrNotImplementedError
var ret []TemperatureStat
var dst []msAcpi_ThermalZoneTemperature
q := wmi.CreateQuery(&dst, "")
if err := common.WMIQueryWithContext(ctx, q, &dst, nil, "root/wmi"); err != nil {
return ret, err
}
for _, v := range dst {
ts := TemperatureStat{
SensorKey: v.InstanceName,
Temperature: kelvinToCelsius(v.CurrentTemperature, 2),
}
ret = append(ret, ts)
}
return ret, nil
}
func kelvinToCelsius(temp uint32, n int) float64 {
// wmi return temperature Kelvin * 10, so need to divide the result by 10,
// and then minus 273.15 to get °Celsius.
t := float64(temp/10) - 273.15
n10 := math.Pow10(n)
return math.Trunc((t+0.5/n10)*n10) / n10
}
func Virtualization() (string, string, error) {
@@ -234,3 +315,35 @@ func KernelVersionWithContext(ctx context.Context) (string, error) {
_, _, version, err := PlatformInformation()
return version, err
}
func kernelArch() (string, error) {
var systemInfo systemInfo
procGetNativeSystemInfo.Call(uintptr(unsafe.Pointer(&systemInfo)))
const (
PROCESSOR_ARCHITECTURE_INTEL = 0
PROCESSOR_ARCHITECTURE_ARM = 5
PROCESSOR_ARCHITECTURE_ARM64 = 12
PROCESSOR_ARCHITECTURE_IA64 = 6
PROCESSOR_ARCHITECTURE_AMD64 = 9
)
switch systemInfo.wProcessorArchitecture {
case PROCESSOR_ARCHITECTURE_INTEL:
if systemInfo.wProcessorLevel < 3 {
return "i386", nil
}
if systemInfo.wProcessorLevel > 6 {
return "i686", nil
}
return fmt.Sprintf("i%d86", systemInfo.wProcessorLevel), nil
case PROCESSOR_ARCHITECTURE_ARM:
return "arm", nil
case PROCESSOR_ARCHITECTURE_ARM64:
return "aarch64", nil
case PROCESSOR_ARCHITECTURE_IA64:
return "ia64", nil
case PROCESSOR_ARCHITECTURE_AMD64:
return "x86_64", nil
}
return "", nil
}

25
vendor/github.com/shirou/gopsutil/host/types.go generated vendored Normal file
View File

@@ -0,0 +1,25 @@
package host
import (
"fmt"
)
type Warnings struct {
List []error
}
func (w *Warnings) Add(err error) {
w.List = append(w.List, err)
}
func (w *Warnings) Reference() error {
if len(w.List) > 0 {
return w
} else {
return nil
}
}
func (w *Warnings) Error() string {
return fmt.Sprintf("Number of warnings: %v", len(w.List))
}

View File

@@ -1,17 +0,0 @@
// +build ignore
// plus hand editing about timeval
/*
Input to cgo -godefs.
*/
package host
/*
#include <sys/time.h>
#include <utmpx.h>
*/
import "C"
type Utmpx C.struct_utmpx
type Timeval C.struct_timeval

View File

@@ -1,44 +0,0 @@
// +build ignore
/*
Input to cgo -godefs.
*/
package host
/*
#define KERNEL
#include <sys/types.h>
#include <sys/time.h>
#include <utmpx.h>
enum {
sizeofPtr = sizeof(void*),
};
*/
import "C"
// Machine characteristics; for internal use.
const (
sizeofPtr = C.sizeofPtr
sizeofShort = C.sizeof_short
sizeofInt = C.sizeof_int
sizeofLong = C.sizeof_long
sizeofLongLong = C.sizeof_longlong
sizeOfUtmpx = C.sizeof_struct_utmpx
)
// Basic types
type (
_C_short C.short
_C_int C.int
_C_long C.long
_C_long_long C.longlong
)
type Utmp C.struct_utmp
type Utmpx C.struct_utmpx
type Timeval C.struct_timeval

View File

@@ -1,42 +0,0 @@
// +build ignore
/*
Input to cgo -godefs.
*/
package host
/*
#include <sys/types.h>
#include <utmp.h>
enum {
sizeofPtr = sizeof(void*),
};
*/
import "C"
// Machine characteristics; for internal use.
const (
sizeofPtr = C.sizeofPtr
sizeofShort = C.sizeof_short
sizeofInt = C.sizeof_int
sizeofLong = C.sizeof_long
sizeofLongLong = C.sizeof_longlong
sizeOfUtmp = C.sizeof_struct_utmp
)
// Basic types
type (
_C_short C.short
_C_int C.int
_C_long C.long
_C_long_long C.longlong
)
type utmp C.struct_utmp
type exit_status C.struct_exit_status
type timeval C.struct_timeval

View File

@@ -1,43 +0,0 @@
// +build ignore
/*
Input to cgo -godefs.
*/
package host
/*
#define KERNEL
#include <sys/types.h>
#include <sys/time.h>
#include <utmp.h>
enum {
sizeofPtr = sizeof(void*),
};
*/
import "C"
// Machine characteristics; for internal use.
const (
sizeofPtr = C.sizeofPtr
sizeofShort = C.sizeof_short
sizeofInt = C.sizeof_int
sizeofLong = C.sizeof_long
sizeofLongLong = C.sizeof_longlong
sizeOfUtmp = C.sizeof_struct_utmp
)
// Basic types
type (
_C_short C.short
_C_int C.int
_C_long C.long
_C_long_long C.longlong
)
type Utmp C.struct_utmp
type Timeval C.struct_timeval

View File

@@ -32,15 +32,19 @@ var (
type Invoker interface {
Command(string, ...string) ([]byte, error)
CommandWithContext(context.Context, string, ...string) ([]byte, error)
}
type Invoke struct{}
func (i Invoke) Command(name string, arg ...string) ([]byte, error) {
ctxt, cancel := context.WithTimeout(context.Background(), Timeout)
ctx, cancel := context.WithTimeout(context.Background(), Timeout)
defer cancel()
return i.CommandWithContext(ctx, name, arg...)
}
cmd := exec.CommandContext(ctxt, name, arg...)
func (i Invoke) CommandWithContext(ctx context.Context, name string, arg ...string) ([]byte, error) {
cmd := exec.CommandContext(ctx, name, arg...)
var buf bytes.Buffer
cmd.Stdout = &buf
@@ -84,6 +88,10 @@ func (i FakeInvoke) Command(name string, arg ...string) ([]byte, error) {
return []byte{}, fmt.Errorf("could not find testdata: %s", fpath)
}
func (i FakeInvoke) CommandWithContext(ctx context.Context, name string, arg ...string) ([]byte, error) {
return i.Command(name, arg...)
}
var ErrNotImplementedError = errors.New("not implemented yet")
// ReadLines reads contents from a file and splits them by new lines.
@@ -205,6 +213,12 @@ func ReadInts(filename string) ([]int64, error) {
return ret, nil
}
// Parse Hex to uint32 without error
func HexToUint32(hex string) uint32 {
vv, _ := strconv.ParseUint(hex, 16, 32)
return uint32(vv)
}
// Parse to int32 without error
func mustParseInt32(val string) int32 {
vv, _ := strconv.ParseInt(val, 10, 32)
@@ -320,47 +334,12 @@ func HostVar(combineWith ...string) string {
return GetEnv("HOST_VAR", "/var", combineWith...)
}
// https://gist.github.com/kylelemons/1525278
func Pipeline(cmds ...*exec.Cmd) ([]byte, []byte, error) {
// Require at least one command
if len(cmds) < 1 {
return nil, nil, nil
}
func HostRun(combineWith ...string) string {
return GetEnv("HOST_RUN", "/run", combineWith...)
}
// Collect the output from the command(s)
var output bytes.Buffer
var stderr bytes.Buffer
last := len(cmds) - 1
for i, cmd := range cmds[:last] {
var err error
// Connect each command's stdin to the previous command's stdout
if cmds[i+1].Stdin, err = cmd.StdoutPipe(); err != nil {
return nil, nil, err
}
// Connect each command's stderr to a buffer
cmd.Stderr = &stderr
}
// Connect the output and error for the last command
cmds[last].Stdout, cmds[last].Stderr = &output, &stderr
// Start each command
for _, cmd := range cmds {
if err := cmd.Start(); err != nil {
return output.Bytes(), stderr.Bytes(), err
}
}
// Wait for each command to complete
for _, cmd := range cmds {
if err := cmd.Wait(); err != nil {
return output.Bytes(), stderr.Bytes(), err
}
}
// Return the pipeline output and the collected standard error
return output.Bytes(), stderr.Bytes(), nil
func HostDev(combineWith ...string) string {
return GetEnv("HOST_DEV", "/dev", combineWith...)
}
// getSysctrlEnv sets LC_ALL=C in a list of env vars for use when running

View File

@@ -3,6 +3,7 @@
package common
import (
"context"
"os"
"os/exec"
"strings"
@@ -11,12 +12,12 @@ import (
"golang.org/x/sys/unix"
)
func DoSysctrl(mib string) ([]string, error) {
sysctl, err := exec.LookPath("/usr/sbin/sysctl")
func DoSysctrlWithContext(ctx context.Context, mib string) ([]string, error) {
sysctl, err := exec.LookPath("sysctl")
if err != nil {
return []string{}, err
}
cmd := exec.Command(sysctl, "-n", mib)
cmd := exec.CommandContext(ctx, sysctl, "-n", mib)
cmd.Env = getSysctrlEnv(os.Environ())
out, err := cmd.Output()
if err != nil {

View File

@@ -3,6 +3,7 @@
package common
import (
"fmt"
"os"
"os/exec"
"strings"
@@ -11,8 +12,23 @@ import (
"golang.org/x/sys/unix"
)
func SysctlUint(mib string) (uint64, error) {
buf, err := unix.SysctlRaw(mib)
if err != nil {
return 0, err
}
if len(buf) == 8 { // 64 bit
return *(*uint64)(unsafe.Pointer(&buf[0])), nil
}
if len(buf) == 4 { // 32bit
t := *(*uint32)(unsafe.Pointer(&buf[0]))
return uint64(t), nil
}
return 0, fmt.Errorf("unexpected size: %s, %d", mib, len(buf))
}
func DoSysctrl(mib string) ([]string, error) {
sysctl, err := exec.LookPath("/sbin/sysctl")
sysctl, err := exec.LookPath("sysctl")
if err != nil {
return []string{}, err
}

View File

@@ -3,13 +3,19 @@
package common
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"sync/atomic"
"time"
)
func DoSysctrl(mib string) ([]string, error) {
sysctl, err := exec.LookPath("/sbin/sysctl")
sysctl, err := exec.LookPath("sysctl")
if err != nil {
return []string{}, err
}
@@ -37,5 +43,222 @@ func NumProcs() (uint64, error) {
if err != nil {
return 0, err
}
return uint64(len(list)), err
var cnt uint64
for _, v := range list {
if _, err = strconv.ParseUint(v, 10, 64); err == nil {
cnt++
}
}
return cnt, nil
}
// cachedBootTime must be accessed via atomic.Load/StoreUint64
var cachedBootTime uint64
func BootTimeWithContext(ctx context.Context) (uint64, error) {
t := atomic.LoadUint64(&cachedBootTime)
if t != 0 {
return t, nil
}
system, role, err := Virtualization()
if err != nil {
return 0, err
}
statFile := "stat"
if system == "lxc" && role == "guest" {
// if lxc, /proc/uptime is used.
statFile = "uptime"
} else if system == "docker" && role == "guest" {
// also docker, guest
statFile = "uptime"
}
filename := HostProc(statFile)
lines, err := ReadLines(filename)
if err != nil {
return 0, err
}
if statFile == "stat" {
for _, line := range lines {
if strings.HasPrefix(line, "btime") {
f := strings.Fields(line)
if len(f) != 2 {
return 0, fmt.Errorf("wrong btime format")
}
b, err := strconv.ParseInt(f[1], 10, 64)
if err != nil {
return 0, err
}
t = uint64(b)
atomic.StoreUint64(&cachedBootTime, t)
return t, nil
}
}
} else if statFile == "uptime" {
if len(lines) != 1 {
return 0, fmt.Errorf("wrong uptime format")
}
f := strings.Fields(lines[0])
b, err := strconv.ParseFloat(f[0], 64)
if err != nil {
return 0, err
}
t = uint64(time.Now().Unix()) - uint64(b)
atomic.StoreUint64(&cachedBootTime, t)
return t, nil
}
return 0, fmt.Errorf("could not find btime")
}
func Virtualization() (string, string, error) {
return VirtualizationWithContext(context.Background())
}
func VirtualizationWithContext(ctx context.Context) (string, string, error) {
var system string
var role string
filename := HostProc("xen")
if PathExists(filename) {
system = "xen"
role = "guest" // assume guest
if PathExists(filepath.Join(filename, "capabilities")) {
contents, err := ReadLines(filepath.Join(filename, "capabilities"))
if err == nil {
if StringsContains(contents, "control_d") {
role = "host"
}
}
}
}
filename = HostProc("modules")
if PathExists(filename) {
contents, err := ReadLines(filename)
if err == nil {
if StringsContains(contents, "kvm") {
system = "kvm"
role = "host"
} else if StringsContains(contents, "vboxdrv") {
system = "vbox"
role = "host"
} else if StringsContains(contents, "vboxguest") {
system = "vbox"
role = "guest"
} else if StringsContains(contents, "vmware") {
system = "vmware"
role = "guest"
}
}
}
filename = HostProc("cpuinfo")
if PathExists(filename) {
contents, err := ReadLines(filename)
if err == nil {
if StringsContains(contents, "QEMU Virtual CPU") ||
StringsContains(contents, "Common KVM processor") ||
StringsContains(contents, "Common 32-bit KVM processor") {
system = "kvm"
role = "guest"
}
}
}
filename = HostProc("bus/pci/devices")
if PathExists(filename) {
contents, err := ReadLines(filename)
if err == nil {
if StringsContains(contents, "virtio-pci") {
role = "guest"
}
}
}
filename = HostProc()
if PathExists(filepath.Join(filename, "bc", "0")) {
system = "openvz"
role = "host"
} else if PathExists(filepath.Join(filename, "vz")) {
system = "openvz"
role = "guest"
}
// not use dmidecode because it requires root
if PathExists(filepath.Join(filename, "self", "status")) {
contents, err := ReadLines(filepath.Join(filename, "self", "status"))
if err == nil {
if StringsContains(contents, "s_context:") ||
StringsContains(contents, "VxID:") {
system = "linux-vserver"
}
// TODO: guest or host
}
}
if PathExists(filepath.Join(filename, "self", "cgroup")) {
contents, err := ReadLines(filepath.Join(filename, "self", "cgroup"))
if err == nil {
if StringsContains(contents, "lxc") {
system = "lxc"
role = "guest"
} else if StringsContains(contents, "docker") {
system = "docker"
role = "guest"
} else if StringsContains(contents, "machine-rkt") {
system = "rkt"
role = "guest"
} else if PathExists("/usr/bin/lxc-version") {
system = "lxc"
role = "host"
}
}
}
if PathExists(HostEtc("os-release")) {
p, _, err := GetOSRelease()
if err == nil && p == "coreos" {
system = "rkt" // Is it true?
role = "host"
}
}
return system, role, nil
}
func GetOSRelease() (platform string, version string, err error) {
contents, err := ReadLines(HostEtc("os-release"))
if err != nil {
return "", "", nil // return empty
}
for _, line := range contents {
field := strings.Split(line, "=")
if len(field) < 2 {
continue
}
switch field[0] {
case "ID": // use ID for lowercase
platform = trimQuotes(field[1])
case "VERSION":
version = trimQuotes(field[1])
}
}
return platform, version, nil
}
// Remove quotes of the source string
func trimQuotes(s string) string {
if len(s) >= 2 {
if s[0] == '"' && s[len(s)-1] == '"' {
return s[1 : len(s)-1]
}
}
return s
}

View File

@@ -12,7 +12,7 @@ import (
)
func DoSysctrl(mib string) ([]string, error) {
sysctl, err := exec.LookPath("/sbin/sysctl")
sysctl, err := exec.LookPath("sysctl")
if err != nil {
return []string{}, err
}

View File

@@ -1,133 +0,0 @@
package common
import (
"fmt"
"os"
"reflect"
"runtime"
"strings"
"testing"
)
func TestReadlines(t *testing.T) {
ret, err := ReadLines("common_test.go")
if err != nil {
t.Error(err)
}
if !strings.Contains(ret[0], "package common") {
t.Error("could not read correctly")
}
}
func TestReadLinesOffsetN(t *testing.T) {
ret, err := ReadLinesOffsetN("common_test.go", 2, 1)
if err != nil {
t.Error(err)
}
fmt.Println(ret[0])
if !strings.Contains(ret[0], `import (`) {
t.Error("could not read correctly")
}
}
func TestIntToString(t *testing.T) {
src := []int8{65, 66, 67}
dst := IntToString(src)
if dst != "ABC" {
t.Error("could not convert")
}
}
func TestByteToString(t *testing.T) {
src := []byte{65, 66, 67}
dst := ByteToString(src)
if dst != "ABC" {
t.Error("could not convert")
}
src = []byte{0, 65, 66, 67}
dst = ByteToString(src)
if dst != "ABC" {
t.Error("could not convert")
}
}
func TestmustParseInt32(t *testing.T) {
ret := mustParseInt32("11111")
if ret != int32(11111) {
t.Error("could not parse")
}
}
func TestmustParseUint64(t *testing.T) {
ret := mustParseUint64("11111")
if ret != uint64(11111) {
t.Error("could not parse")
}
}
func TestmustParseFloat64(t *testing.T) {
ret := mustParseFloat64("11111.11")
if ret != float64(11111.11) {
t.Error("could not parse")
}
ret = mustParseFloat64("11111")
if ret != float64(11111) {
t.Error("could not parse")
}
}
func TestStringsContains(t *testing.T) {
target, err := ReadLines("common_test.go")
if err != nil {
t.Error(err)
}
if !StringsContains(target, "func TestStringsContains(t *testing.T) {") {
t.Error("cloud not test correctly")
}
}
func TestPathExists(t *testing.T) {
if !PathExists("common_test.go") {
t.Error("exists but return not exists")
}
if PathExists("should_not_exists.go") {
t.Error("not exists but return exists")
}
}
func TestHostEtc(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("windows doesn't have etc")
}
p := HostEtc("mtab")
if p != "/etc/mtab" {
t.Errorf("invalid HostEtc, %s", p)
}
}
func TestGetSysctrlEnv(t *testing.T) {
// Append case
env := getSysctrlEnv([]string{"FOO=bar"})
if !reflect.DeepEqual(env, []string{"FOO=bar", "LC_ALL=C"}) {
t.Errorf("unexpected append result from getSysctrlEnv: %q", env)
}
// Replace case
env = getSysctrlEnv([]string{"FOO=bar", "LC_ALL=en_US.UTF-8"})
if !reflect.DeepEqual(env, []string{"FOO=bar", "LC_ALL=C"}) {
t.Errorf("unexpected replace result from getSysctrlEnv: %q", env)
}
// Test against real env
env = getSysctrlEnv(os.Environ())
found := false
for _, v := range env {
if v == "LC_ALL=C" {
found = true
continue
}
if strings.HasPrefix(v, "LC_ALL") {
t.Fatalf("unexpected LC_ALL value: %q", v)
}
}
if !found {
t.Errorf("unexpected real result from getSysctrlEnv: %q", env)
}
}

View File

@@ -3,12 +3,13 @@
package common
import (
"context"
"os/exec"
"strconv"
"strings"
)
func CallLsof(invoke Invoker, pid int32, args ...string) ([]string, error) {
func CallLsofWithContext(ctx context.Context, invoke Invoker, pid int32, args ...string) ([]string, error) {
var cmd []string
if pid == 0 { // will get from all processes.
cmd = []string{"-a", "-n", "-P"}
@@ -20,9 +21,9 @@ func CallLsof(invoke Invoker, pid int32, args ...string) ([]string, error) {
if err != nil {
return []string{}, err
}
out, err := invoke.Command(lsof, cmd...)
out, err := invoke.CommandWithContext(ctx, lsof, cmd...)
if err != nil {
// if no pid found, lsof returnes code 1.
// if no pid found, lsof returns code 1.
if err.Error() == "exit status 1" && len(out) == 0 {
return []string{}, nil
}
@@ -39,14 +40,14 @@ func CallLsof(invoke Invoker, pid int32, args ...string) ([]string, error) {
return ret, nil
}
func CallPgrep(invoke Invoker, pid int32) ([]int32, error) {
func CallPgrepWithContext(ctx context.Context, invoke Invoker, pid int32) ([]int32, error) {
var cmd []string
cmd = []string{"-P", strconv.Itoa(int(pid))}
pgrep, err := exec.LookPath("pgrep")
if err != nil {
return []int32{}, err
}
out, err := invoke.Command(pgrep, cmd...)
out, err := invoke.CommandWithContext(ctx, pgrep, cmd...)
if err != nil {
return []int32{}, err
}

View File

@@ -4,6 +4,9 @@ package common
import (
"context"
"path/filepath"
"strings"
"syscall"
"unsafe"
"github.com/StackExchange/wmi"
@@ -47,10 +50,10 @@ const (
)
var (
Modkernel32 = windows.NewLazyDLL("kernel32.dll")
ModNt = windows.NewLazyDLL("ntdll.dll")
ModPdh = windows.NewLazyDLL("pdh.dll")
ModPsapi = windows.NewLazyDLL("psapi.dll")
Modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
ModNt = windows.NewLazySystemDLL("ntdll.dll")
ModPdh = windows.NewLazySystemDLL("pdh.dll")
ModPsapi = windows.NewLazySystemDLL("psapi.dll")
ProcGetSystemTimes = Modkernel32.NewProc("GetSystemTimes")
ProcNtQuerySystemInformation = ModNt.NewProc("NtQuerySystemInformation")
@@ -59,6 +62,8 @@ var (
PdhCollectQueryData = ModPdh.NewProc("PdhCollectQueryData")
PdhGetFormattedCounterValue = ModPdh.NewProc("PdhGetFormattedCounterValue")
PdhCloseQuery = ModPdh.NewProc("PdhCloseQuery")
procQueryDosDeviceW = Modkernel32.NewProc("QueryDosDeviceW")
)
type FILETIME struct {
@@ -133,3 +138,23 @@ func WMIQueryWithContext(ctx context.Context, query string, dst interface{}, con
return err
}
}
// Convert paths using native DOS format like:
// "\Device\HarddiskVolume1\Windows\systemew\file.txt"
// into:
// "C:\Windows\systemew\file.txt"
func ConvertDOSPath(p string) string {
rawDrive := strings.Join(strings.Split(p, `\`)[:3], `\`)
for d := 'A'; d <= 'Z'; d++ {
szDeviceName := string(d) + ":"
szTarget := make([]uint16, 512)
ret, _, _ := procQueryDosDeviceW.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(szDeviceName))),
uintptr(unsafe.Pointer(&szTarget[0])),
uintptr(len(szTarget)))
if ret != 0 && windows.UTF16ToString(szTarget[:]) == rawDrive {
return filepath.Join(szDeviceName, p[len(rawDrive):])
}
}
return p
}

View File

@@ -6,11 +6,7 @@ import (
"github.com/shirou/gopsutil/internal/common"
)
var invoke common.Invoker
func init() {
invoke = common.Invoke{}
}
var invoke common.Invoker = common.Invoke{}
// Memory usage statistics. Total, Available and Used contain numbers of bytes
// for human consumption.
@@ -46,18 +42,40 @@ type VirtualMemoryStat struct {
Inactive uint64 `json:"inactive"`
Wired uint64 `json:"wired"`
// FreeBSD specific numbers:
// https://reviews.freebsd.org/D8467
Laundry uint64 `json:"laundry"`
// Linux specific numbers
// https://www.centos.org/docs/5/html/5.1/Deployment_Guide/s2-proc-meminfo.html
// https://www.kernel.org/doc/Documentation/filesystems/proc.txt
Buffers uint64 `json:"buffers"`
Cached uint64 `json:"cached"`
Writeback uint64 `json:"writeback"`
Dirty uint64 `json:"dirty"`
WritebackTmp uint64 `json:"writebacktmp"`
Shared uint64 `json:"shared"`
Slab uint64 `json:"slab"`
PageTables uint64 `json:"pagetables"`
SwapCached uint64 `json:"swapcached"`
// https://www.kernel.org/doc/Documentation/vm/overcommit-accounting
Buffers uint64 `json:"buffers"`
Cached uint64 `json:"cached"`
Writeback uint64 `json:"writeback"`
Dirty uint64 `json:"dirty"`
WritebackTmp uint64 `json:"writebacktmp"`
Shared uint64 `json:"shared"`
Slab uint64 `json:"slab"`
SReclaimable uint64 `json:"sreclaimable"`
SUnreclaim uint64 `json:"sunreclaim"`
PageTables uint64 `json:"pagetables"`
SwapCached uint64 `json:"swapcached"`
CommitLimit uint64 `json:"commitlimit"`
CommittedAS uint64 `json:"committedas"`
HighTotal uint64 `json:"hightotal"`
HighFree uint64 `json:"highfree"`
LowTotal uint64 `json:"lowtotal"`
LowFree uint64 `json:"lowfree"`
SwapTotal uint64 `json:"swaptotal"`
SwapFree uint64 `json:"swapfree"`
Mapped uint64 `json:"mapped"`
VMallocTotal uint64 `json:"vmalloctotal"`
VMallocUsed uint64 `json:"vmallocused"`
VMallocChunk uint64 `json:"vmallocchunk"`
HugePagesTotal uint64 `json:"hugepagestotal"`
HugePagesFree uint64 `json:"hugepagesfree"`
HugePageSize uint64 `json:"hugepagesize"`
}
type SwapMemoryStat struct {
@@ -67,6 +85,9 @@ type SwapMemoryStat struct {
UsedPercent float64 `json:"usedPercent"`
Sin uint64 `json:"sin"`
Sout uint64 `json:"sout"`
PgIn uint64 `json:"pgin"`
PgOut uint64 `json:"pgout"`
PgFault uint64 `json:"pgfault"`
}
func (m VirtualMemoryStat) String() string {

View File

@@ -5,10 +5,9 @@ package mem
import (
"context"
"encoding/binary"
"strconv"
"strings"
"fmt"
"unsafe"
"github.com/shirou/gopsutil/internal/common"
"golang.org/x/sys/unix"
)
@@ -27,46 +26,42 @@ func getHwMemsize() (uint64, error) {
return total, nil
}
// xsw_usage in sys/sysctl.h
type swapUsage struct {
Total uint64
Avail uint64
Used uint64
Pagesize int32
Encrypted bool
}
// SwapMemory returns swapinfo.
func SwapMemory() (*SwapMemoryStat, error) {
return SwapMemoryWithContext(context.Background())
}
func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
// https://github.com/yanllearnn/go-osstat/blob/ae8a279d26f52ec946a03698c7f50a26cfb427e3/memory/memory_darwin.go
var ret *SwapMemoryStat
swapUsage, err := common.DoSysctrl("vm.swapusage")
value, err := unix.SysctlRaw("vm.swapusage")
if err != nil {
return ret, err
}
total := strings.Replace(swapUsage[2], "M", "", 1)
used := strings.Replace(swapUsage[5], "M", "", 1)
free := strings.Replace(swapUsage[8], "M", "", 1)
total_v, err := strconv.ParseFloat(total, 64)
if err != nil {
return nil, err
}
used_v, err := strconv.ParseFloat(used, 64)
if err != nil {
return nil, err
}
free_v, err := strconv.ParseFloat(free, 64)
if err != nil {
return nil, err
if len(value) != 32 {
return ret, fmt.Errorf("unexpected output of sysctl vm.swapusage: %v (len: %d)", value, len(value))
}
swap := (*swapUsage)(unsafe.Pointer(&value[0]))
u := float64(0)
if total_v != 0 {
u = ((total_v - free_v) / total_v) * 100.0
if swap.Total != 0 {
u = ((float64(swap.Total) - float64(swap.Avail)) / float64(swap.Total)) * 100.0
}
// vm.swapusage shows "M", multiply 1024 * 1024 to convert bytes.
ret = &SwapMemoryStat{
Total: uint64(total_v * 1024 * 1024),
Used: uint64(used_v * 1024 * 1024),
Free: uint64(free_v * 1024 * 1024),
Total: swap.Total,
Used: swap.Used,
Free: swap.Avail,
UsedPercent: u,
}

View File

@@ -1,46 +0,0 @@
// +build darwin
package mem
import (
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestVirtualMemoryDarwin(t *testing.T) {
v, err := VirtualMemory()
assert.Nil(t, err)
outBytes, err := invoke.Command("/usr/sbin/sysctl", "hw.memsize")
assert.Nil(t, err)
outString := string(outBytes)
outString = strings.TrimSpace(outString)
outParts := strings.Split(outString, " ")
actualTotal, err := strconv.ParseInt(outParts[1], 10, 64)
assert.Nil(t, err)
assert.Equal(t, uint64(actualTotal), v.Total)
assert.True(t, v.Available > 0)
assert.Equal(t, v.Available, v.Free+v.Inactive, "%v", v)
assert.True(t, v.Used > 0)
assert.True(t, v.Used < v.Total)
assert.True(t, v.UsedPercent > 0)
assert.True(t, v.UsedPercent < 100)
assert.True(t, v.Free > 0)
assert.True(t, v.Free < v.Available)
assert.True(t, v.Active > 0)
assert.True(t, v.Active < v.Total)
assert.True(t, v.Inactive > 0)
assert.True(t, v.Inactive < v.Total)
assert.True(t, v.Wired > 0)
assert.True(t, v.Wired < v.Total)
}

View File

@@ -5,11 +5,11 @@ package mem
import (
"context"
"errors"
"os/exec"
"strconv"
"strings"
"unsafe"
"golang.org/x/sys/unix"
"github.com/shirou/gopsutil/internal/common"
)
func VirtualMemory() (*VirtualMemoryStat, error) {
@@ -17,51 +17,62 @@ func VirtualMemory() (*VirtualMemoryStat, error) {
}
func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
pageSize, err := unix.SysctlUint32("vm.stats.vm.v_page_size")
pageSize, err := common.SysctlUint("vm.stats.vm.v_page_size")
if err != nil {
return nil, err
}
pageCount, err := unix.SysctlUint32("vm.stats.vm.v_page_count")
if err != nil {
return nil, err
}
free, err := unix.SysctlUint32("vm.stats.vm.v_free_count")
if err != nil {
return nil, err
}
active, err := unix.SysctlUint32("vm.stats.vm.v_active_count")
if err != nil {
return nil, err
}
inactive, err := unix.SysctlUint32("vm.stats.vm.v_inactive_count")
if err != nil {
return nil, err
}
cached, err := unix.SysctlUint32("vm.stats.vm.v_cache_count")
if err != nil {
return nil, err
}
buffers, err := unix.SysctlUint32("vfs.bufspace")
if err != nil {
return nil, err
}
wired, err := unix.SysctlUint32("vm.stats.vm.v_wire_count")
physmem, err := common.SysctlUint("hw.physmem")
if err != nil {
return nil, err
}
p := uint64(pageSize)
free, err := common.SysctlUint("vm.stats.vm.v_free_count")
if err != nil {
return nil, err
}
active, err := common.SysctlUint("vm.stats.vm.v_active_count")
if err != nil {
return nil, err
}
inactive, err := common.SysctlUint("vm.stats.vm.v_inactive_count")
if err != nil {
return nil, err
}
buffers, err := common.SysctlUint("vfs.bufspace")
if err != nil {
return nil, err
}
wired, err := common.SysctlUint("vm.stats.vm.v_wire_count")
if err != nil {
return nil, err
}
var cached, laundry uint64
osreldate, _ := common.SysctlUint("kern.osreldate")
if osreldate < 1102000 {
cached, err = common.SysctlUint("vm.stats.vm.v_cache_count")
if err != nil {
return nil, err
}
} else {
laundry, err = common.SysctlUint("vm.stats.vm.v_laundry_count")
if err != nil {
return nil, err
}
}
p := pageSize
ret := &VirtualMemoryStat{
Total: uint64(pageCount) * p,
Free: uint64(free) * p,
Active: uint64(active) * p,
Inactive: uint64(inactive) * p,
Cached: uint64(cached) * p,
Buffers: uint64(buffers),
Wired: uint64(wired) * p,
Total: physmem,
Free: free * p,
Active: active * p,
Inactive: inactive * p,
Cached: cached * p,
Buffers: buffers,
Wired: wired * p,
Laundry: laundry * p,
}
ret.Available = ret.Inactive + ret.Cached + ret.Free
ret.Available = ret.Inactive + ret.Cached + ret.Free + ret.Laundry
ret.Used = ret.Total - ret.Available
ret.UsedPercent = float64(ret.Used) / float64(ret.Total) * 100.0
@@ -69,53 +80,88 @@ func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
}
// Return swapinfo
// FreeBSD can have multiple swap devices. but use only first device
func SwapMemory() (*SwapMemoryStat, error) {
return SwapMemoryWithContext(context.Background())
}
func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
swapinfo, err := exec.LookPath("swapinfo")
if err != nil {
return nil, err
}
// Constants from vm/vm_param.h
// nolint: golint
const (
XSWDEV_VERSION11 = 1
XSWDEV_VERSION = 2
)
out, err := invoke.Command(swapinfo)
if err != nil {
return nil, err
}
for _, line := range strings.Split(string(out), "\n") {
values := strings.Fields(line)
// skip title line
if len(values) == 0 || values[0] == "Device" {
continue
}
u := strings.Replace(values[4], "%", "", 1)
total_v, err := strconv.ParseUint(values[1], 10, 64)
if err != nil {
return nil, err
}
used_v, err := strconv.ParseUint(values[2], 10, 64)
if err != nil {
return nil, err
}
free_v, err := strconv.ParseUint(values[3], 10, 64)
if err != nil {
return nil, err
}
up_v, err := strconv.ParseFloat(u, 64)
if err != nil {
return nil, err
}
return &SwapMemoryStat{
Total: total_v,
Used: used_v,
Free: free_v,
UsedPercent: up_v,
}, nil
}
return nil, errors.New("no swap devices found")
// Types from vm/vm_param.h
type xswdev struct {
Version uint32 // Version is the version
Dev uint64 // Dev is the device identifier
Flags int32 // Flags is the swap flags applied to the device
NBlks int32 // NBlks is the total number of blocks
Used int32 // Used is the number of blocks used
}
// xswdev11 is a compatibility for under FreeBSD 11
// sys/vm/swap_pager.c
type xswdev11 struct {
Version uint32 // Version is the version
Dev uint32 // Dev is the device identifier
Flags int32 // Flags is the swap flags applied to the device
NBlks int32 // NBlks is the total number of blocks
Used int32 // Used is the number of blocks used
}
func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
// FreeBSD can have multiple swap devices so we total them up
i, err := common.SysctlUint("vm.nswapdev")
if err != nil {
return nil, err
}
if i == 0 {
return nil, errors.New("no swap devices found")
}
c := int(i)
i, err = common.SysctlUint("vm.stats.vm.v_page_size")
if err != nil {
return nil, err
}
pageSize := i
var buf []byte
s := &SwapMemoryStat{}
for n := 0; n < c; n++ {
buf, err = unix.SysctlRaw("vm.swap_info", n)
if err != nil {
return nil, err
}
// first, try to parse with version 2
xsw := (*xswdev)(unsafe.Pointer(&buf[0]))
if xsw.Version == XSWDEV_VERSION11 {
// this is version 1, so try to parse again
xsw := (*xswdev11)(unsafe.Pointer(&buf[0]))
if xsw.Version != XSWDEV_VERSION11 {
return nil, errors.New("xswdev version mismatch(11)")
}
s.Total += uint64(xsw.NBlks)
s.Used += uint64(xsw.Used)
} else if xsw.Version != XSWDEV_VERSION {
return nil, errors.New("xswdev version mismatch")
} else {
s.Total += uint64(xsw.NBlks)
s.Used += uint64(xsw.Used)
}
}
if s.Total != 0 {
s.UsedPercent = float64(s.Used) / float64(s.Total) * 100
}
s.Total *= pageSize
s.Used *= pageSize
s.Free = s.Total - s.Used
return s, nil
}

View File

@@ -4,6 +4,9 @@ package mem
import (
"context"
"encoding/json"
"math"
"os"
"strconv"
"strings"
@@ -11,17 +14,56 @@ import (
"golang.org/x/sys/unix"
)
type VirtualMemoryExStat struct {
ActiveFile uint64 `json:"activefile"`
InactiveFile uint64 `json:"inactivefile"`
ActiveAnon uint64 `json:"activeanon"`
InactiveAnon uint64 `json:"inactiveanon"`
Unevictable uint64 `json:"unevictable"`
}
func (v VirtualMemoryExStat) String() string {
s, _ := json.Marshal(v)
return string(s)
}
func VirtualMemory() (*VirtualMemoryStat, error) {
return VirtualMemoryWithContext(context.Background())
}
func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
vm, _, err := fillFromMeminfoWithContext(ctx)
if err != nil {
return nil, err
}
return vm, nil
}
func VirtualMemoryEx() (*VirtualMemoryExStat, error) {
return VirtualMemoryExWithContext(context.Background())
}
func VirtualMemoryExWithContext(ctx context.Context) (*VirtualMemoryExStat, error) {
_, vmEx, err := fillFromMeminfoWithContext(ctx)
if err != nil {
return nil, err
}
return vmEx, nil
}
func fillFromMeminfoWithContext(ctx context.Context) (*VirtualMemoryStat, *VirtualMemoryExStat, error) {
filename := common.HostProc("meminfo")
lines, _ := common.ReadLines(filename)
// flag if MemAvailable is in /proc/meminfo (kernel 3.14+)
memavail := false
activeFile := false // "Active(file)" not available: 2.6.28 / Dec 2008
inactiveFile := false // "Inactive(file)" not available: 2.6.28 / Dec 2008
sReclaimable := false // "SReclaimable:" not available: 2.6.19 / Nov 2006
ret := &VirtualMemoryStat{}
retEx := &VirtualMemoryExStat{}
for _, line := range lines {
fields := strings.Split(line, ":")
if len(fields) != 2 {
@@ -33,7 +75,7 @@ func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
t, err := strconv.ParseUint(value, 10, 64)
if err != nil {
return ret, err
return ret, retEx,err
}
switch key {
case "MemTotal":
@@ -51,6 +93,18 @@ func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
ret.Active = t * 1024
case "Inactive":
ret.Inactive = t * 1024
case "Active(anon)":
retEx.ActiveAnon = t * 1024
case "Inactive(anon)":
retEx.InactiveAnon = t * 1024
case "Active(file)":
activeFile = true
retEx.ActiveFile = t * 1024
case "Inactive(file)":
inactiveFile = true
retEx.InactiveFile = t * 1024
case "Unevictable":
retEx.Unevictable = t * 1024
case "Writeback":
ret.Writeback = t * 1024
case "WritebackTmp":
@@ -61,19 +115,62 @@ func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
ret.Shared = t * 1024
case "Slab":
ret.Slab = t * 1024
case "SReclaimable":
sReclaimable = true
ret.SReclaimable = t * 1024
case "SUnreclaim":
ret.SUnreclaim = t * 1024
case "PageTables":
ret.PageTables = t * 1024
case "SwapCached":
ret.SwapCached = t * 1024
case "CommitLimit":
ret.CommitLimit = t * 1024
case "Committed_AS":
ret.CommittedAS = t * 1024
case "HighTotal":
ret.HighTotal = t * 1024
case "HighFree":
ret.HighFree = t * 1024
case "LowTotal":
ret.LowTotal = t * 1024
case "LowFree":
ret.LowFree = t * 1024
case "SwapTotal":
ret.SwapTotal = t * 1024
case "SwapFree":
ret.SwapFree = t * 1024
case "Mapped":
ret.Mapped = t * 1024
case "VmallocTotal":
ret.VMallocTotal = t * 1024
case "VmallocUsed":
ret.VMallocUsed = t * 1024
case "VmallocChunk":
ret.VMallocChunk = t * 1024
case "HugePages_Total":
ret.HugePagesTotal = t
case "HugePages_Free":
ret.HugePagesFree = t
case "Hugepagesize":
ret.HugePageSize = t * 1024
}
}
if !memavail {
ret.Available = ret.Free + ret.Buffers + ret.Cached
}
ret.Used = ret.Total - ret.Available
ret.UsedPercent = float64(ret.Total-ret.Available) / float64(ret.Total) * 100.0
return ret, nil
ret.Cached += ret.SReclaimable
if !memavail {
if activeFile && inactiveFile && sReclaimable {
ret.Available = calcuateAvailVmem(ret, retEx)
} else {
ret.Available = ret.Cached + ret.Free
}
}
ret.Used = ret.Total - ret.Free - ret.Buffers - ret.Cached
ret.UsedPercent = float64(ret.Used) / float64(ret.Total) * 100.0
return ret, retEx, nil
}
func SwapMemory() (*SwapMemoryStat, error) {
@@ -117,7 +214,69 @@ func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
continue
}
ret.Sout = value * 4 * 1024
case "pgpgin":
value, err := strconv.ParseUint(fields[1], 10, 64)
if err != nil {
continue
}
ret.PgIn = value * 4 * 1024
case "pgpgout":
value, err := strconv.ParseUint(fields[1], 10, 64)
if err != nil {
continue
}
ret.PgOut = value * 4 * 1024
case "pgfault":
value, err := strconv.ParseUint(fields[1], 10, 64)
if err != nil {
continue
}
ret.PgFault = value * 4 * 1024
}
}
return ret, nil
}
// calcuateAvailVmem is a fallback under kernel 3.14 where /proc/meminfo does not provide
// "MemAvailable:" column. It reimplements an algorithm from the link below
// https://github.com/giampaolo/psutil/pull/890
func calcuateAvailVmem(ret *VirtualMemoryStat, retEx *VirtualMemoryExStat) uint64 {
var watermarkLow uint64
fn := common.HostProc("zoneinfo")
lines, err := common.ReadLines(fn)
if err != nil {
return ret.Free + ret.Cached // fallback under kernel 2.6.13
}
pagesize := uint64(os.Getpagesize())
watermarkLow = 0
for _, line := range lines {
fields := strings.Fields(line)
if strings.HasPrefix(fields[0], "low") {
lowValue, err := strconv.ParseUint(fields[1], 10, 64)
if err != nil {
lowValue = 0
}
watermarkLow += lowValue
}
}
watermarkLow *= pagesize
availMemory := ret.Free - watermarkLow
pageCache := retEx.ActiveFile + retEx.InactiveFile
pageCache -= uint64(math.Min(float64(pageCache/2), float64(watermarkLow)))
availMemory += pageCache
availMemory += ret.SReclaimable - uint64(math.Min(float64(ret.SReclaimable/2.0), float64(watermarkLow)))
if availMemory < 0 {
availMemory = 0
}
return availMemory
}

View File

@@ -99,7 +99,7 @@ func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
return nil, err
}
out, err := invoke.Command(swapctl, "-sk")
out, err := invoke.CommandWithContext(ctx, swapctl, "-sk")
if err != nil {
return &SwapMemoryStat{}, nil
}

View File

@@ -52,12 +52,13 @@ func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
}
func zoneName() (string, error) {
zonename, err := exec.LookPath("/usr/bin/zonename")
zonename, err := exec.LookPath("zonename")
if err != nil {
return "", err
}
out, err := invoke.Command(zonename)
ctx := context.Background()
out, err := invoke.CommandWithContext(ctx, zonename)
if err != nil {
return "", err
}
@@ -68,12 +69,13 @@ func zoneName() (string, error) {
var globalZoneMemoryCapacityMatch = regexp.MustCompile(`memory size: ([\d]+) Megabytes`)
func globalZoneMemoryCapacity() (uint64, error) {
prtconf, err := exec.LookPath("/usr/sbin/prtconf")
prtconf, err := exec.LookPath("prtconf")
if err != nil {
return 0, err
}
out, err := invoke.Command(prtconf)
ctx := context.Background()
out, err := invoke.CommandWithContext(ctx, prtconf)
if err != nil {
return 0, err
}
@@ -94,12 +96,13 @@ func globalZoneMemoryCapacity() (uint64, error) {
var kstatMatch = regexp.MustCompile(`([^\s]+)[\s]+([^\s]*)`)
func nonGlobalZoneMemoryCapacity() (uint64, error) {
kstat, err := exec.LookPath("/usr/bin/kstat")
kstat, err := exec.LookPath("kstat")
if err != nil {
return 0, err
}
out, err := invoke.Command(kstat, "-p", "-c", "zone_memory_cap", "memory_cap:*:*:physcap")
ctx := context.Background()
out, err := invoke.CommandWithContext(ctx, kstat, "-p", "-c", "zone_memory_cap", "memory_cap:*:*:physcap")
if err != nil {
return 0, err
}

View File

@@ -1,77 +0,0 @@
package mem
import (
"fmt"
"runtime"
"testing"
"github.com/stretchr/testify/assert"
)
func TestVirtual_memory(t *testing.T) {
if runtime.GOOS == "solaris" {
t.Skip("Only .Total is supported on Solaris")
}
v, err := VirtualMemory()
if err != nil {
t.Errorf("error %v", err)
}
empty := &VirtualMemoryStat{}
if v == empty {
t.Errorf("error %v", v)
}
assert.True(t, v.Total > 0)
assert.True(t, v.Available > 0)
assert.True(t, v.Used > 0)
assert.Equal(t, v.Total, v.Available+v.Used,
"Total should be computable from available + used: %v", v)
assert.True(t, v.Free > 0)
assert.True(t, v.Available > v.Free,
"Free should be a subset of Available: %v", v)
assert.InDelta(t, v.UsedPercent,
100*float64(v.Used)/float64(v.Total), 0.1,
"UsedPercent should be how many percent of Total is Used: %v", v)
}
func TestSwap_memory(t *testing.T) {
v, err := SwapMemory()
if err != nil {
t.Errorf("error %v", err)
}
empty := &SwapMemoryStat{}
if v == empty {
t.Errorf("error %v", v)
}
}
func TestVirtualMemoryStat_String(t *testing.T) {
v := VirtualMemoryStat{
Total: 10,
Available: 20,
Used: 30,
UsedPercent: 30.1,
Free: 40,
}
e := `{"total":10,"available":20,"used":30,"usedPercent":30.1,"free":40,"active":0,"inactive":0,"wired":0,"buffers":0,"cached":0,"writeback":0,"dirty":0,"writebacktmp":0,"shared":0,"slab":0,"pagetables":0,"swapcached":0}`
if e != fmt.Sprintf("%v", v) {
t.Errorf("VirtualMemoryStat string is invalid: %v", v)
}
}
func TestSwapMemoryStat_String(t *testing.T) {
v := SwapMemoryStat{
Total: 10,
Used: 30,
Free: 40,
UsedPercent: 30.1,
}
e := `{"total":10,"used":30,"free":40,"usedPercent":30.1,"sin":0,"sout":0}`
if e != fmt.Sprintf("%v", v) {
t.Errorf("SwapMemoryStat string is invalid: %v", v)
}
}

View File

@@ -80,11 +80,17 @@ func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
tot := perfInfo.commitLimit * perfInfo.pageSize
used := perfInfo.commitTotal * perfInfo.pageSize
free := tot - used
var usedPercent float64
if tot == 0 {
usedPercent = 0
} else {
usedPercent = float64(used) / float64(tot)
}
ret := &SwapMemoryStat{
Total: tot,
Used: used,
Free: free,
UsedPercent: float64(used / tot),
UsedPercent: usedPercent,
}
return ret, nil

View File

@@ -1,34 +0,0 @@
// +build ignore
/*
Input to cgo -godefs.
*/
package mem
/*
#include <sys/types.h>
#include <sys/mount.h>
#include <sys/sysctl.h>
#include <uvm/uvmexp.h>
*/
import "C"
// Machine characteristics; for internal use.
const (
CTLVm = 2
CTLVfs = 10
VmUvmexp = 4 // get uvmexp
VfsGeneric = 0
VfsBcacheStat = 3
)
const (
sizeOfUvmexp = C.sizeof_struct_uvmexp
sizeOfBcachestats = C.sizeof_struct_bcachestats
)
type Uvmexp C.struct_uvmexp
type Bcachestats C.struct_bcachestats

View File

@@ -12,11 +12,7 @@ import (
"github.com/shirou/gopsutil/internal/common"
)
var invoke common.Invoker
func init() {
invoke = common.Invoke{}
}
var invoke common.Invoker = common.Invoke{}
type IOCountersStat struct {
Name string `json:"name"` // interface name
@@ -62,6 +58,7 @@ type InterfaceAddr struct {
}
type InterfaceStat struct {
Index int `json:"index"`
MTU int `json:"mtu"` // maximum transmission unit
Name string `json:"name"` // e.g., "en0", "lo0", "eth0.100"
HardwareAddr string `json:"hardwareaddr"` // IEEE MAC-48, EUI-48 and EUI-64 form
@@ -74,7 +71,98 @@ type FilterStat struct {
ConnTrackMax int64 `json:"conntrackMax"`
}
// ConntrackStat has conntrack summary info
type ConntrackStat struct {
Entries uint32 `json:"entries"` // Number of entries in the conntrack table
Searched uint32 `json:"searched"` // Number of conntrack table lookups performed
Found uint32 `json:"found"` // Number of searched entries which were successful
New uint32 `json:"new"` // Number of entries added which were not expected before
Invalid uint32 `json:"invalid"` // Number of packets seen which can not be tracked
Ignore uint32 `json:"ignore"` // Packets seen which are already connected to an entry
Delete uint32 `json:"delete"` // Number of entries which were removed
DeleteList uint32 `json:"delete_list"` // Number of entries which were put to dying list
Insert uint32 `json:"insert"` // Number of entries inserted into the list
InsertFailed uint32 `json:"insert_failed"` // # insertion attempted but failed (same entry exists)
Drop uint32 `json:"drop"` // Number of packets dropped due to conntrack failure.
EarlyDrop uint32 `json:"early_drop"` // Dropped entries to make room for new ones, if maxsize reached
IcmpError uint32 `json:"icmp_error"` // Subset of invalid. Packets that can't be tracked d/t error
ExpectNew uint32 `json:"expect_new"` // Entries added after an expectation was already present
ExpectCreate uint32 `json:"expect_create"` // Expectations added
ExpectDelete uint32 `json:"expect_delete"` // Expectations deleted
SearchRestart uint32 `json:"search_restart"` // Conntrack table lookups restarted due to hashtable resizes
}
func NewConntrackStat(e uint32, s uint32, f uint32, n uint32, inv uint32, ign uint32, del uint32, dlst uint32, ins uint32, insfail uint32, drop uint32, edrop uint32, ie uint32, en uint32, ec uint32, ed uint32, sr uint32) *ConntrackStat {
return &ConntrackStat{
Entries: e,
Searched: s,
Found: f,
New: n,
Invalid: inv,
Ignore: ign,
Delete: del,
DeleteList: dlst,
Insert: ins,
InsertFailed: insfail,
Drop: drop,
EarlyDrop: edrop,
IcmpError: ie,
ExpectNew: en,
ExpectCreate: ec,
ExpectDelete: ed,
SearchRestart: sr,
}
}
type ConntrackStatList struct {
items []*ConntrackStat
}
func NewConntrackStatList() *ConntrackStatList {
return &ConntrackStatList{
items: []*ConntrackStat{},
}
}
func (l *ConntrackStatList) Append(c *ConntrackStat) {
l.items = append(l.items, c)
}
func (l *ConntrackStatList) Items() []ConntrackStat {
items := make([]ConntrackStat, len(l.items), len(l.items))
for i, el := range l.items {
items[i] = *el
}
return items
}
// Summary returns a single-element list with totals from all list items.
func (l *ConntrackStatList) Summary() []ConntrackStat {
summary := NewConntrackStat(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
for _, cs := range l.items {
summary.Entries += cs.Entries
summary.Searched += cs.Searched
summary.Found += cs.Found
summary.New += cs.New
summary.Invalid += cs.Invalid
summary.Ignore += cs.Ignore
summary.Delete += cs.Delete
summary.DeleteList += cs.DeleteList
summary.Insert += cs.Insert
summary.InsertFailed += cs.InsertFailed
summary.Drop += cs.Drop
summary.EarlyDrop += cs.EarlyDrop
summary.IcmpError += cs.IcmpError
summary.ExpectNew += cs.ExpectNew
summary.ExpectCreate += cs.ExpectCreate
summary.ExpectDelete += cs.ExpectDelete
summary.SearchRestart += cs.SearchRestart
}
return []ConntrackStat{*summary}
}
var constMap = map[string]int{
"unix": syscall.AF_UNIX,
"TCP": syscall.SOCK_STREAM,
"UDP": syscall.SOCK_DGRAM,
"IPv4": syscall.AF_INET,
@@ -111,6 +199,11 @@ func (n InterfaceAddr) String() string {
return string(s)
}
func (n ConntrackStat) String() string {
s, _ := json.Marshal(n)
return string(s)
}
func Interfaces() ([]InterfaceStat, error) {
return InterfacesWithContext(context.Background())
}
@@ -141,6 +234,7 @@ func InterfacesWithContext(ctx context.Context) ([]InterfaceStat, error) {
}
r := InterfaceStat{
Index: ifi.Index,
Name: ifi.Name,
MTU: ifi.MTU,
HardwareAddr: ifi.HardwareAddr.String(),
@@ -182,10 +276,15 @@ func getIOCountersAll(n []IOCountersStat) ([]IOCountersStat, error) {
func parseNetLine(line string) (ConnectionStat, error) {
f := strings.Fields(line)
if len(f) < 9 {
if len(f) < 8 {
return ConnectionStat{}, fmt.Errorf("wrong line,%s", line)
}
if len(f) == 8 {
f = append(f, f[7])
f[7] = "unix"
}
pid, err := strconv.Atoi(f[1])
if err != nil {
return ConnectionStat{}, err
@@ -203,9 +302,14 @@ func parseNetLine(line string) (ConnectionStat, error) {
return ConnectionStat{}, fmt.Errorf("unknown type, %s", f[7])
}
laddr, raddr, err := parseNetAddr(f[8])
if err != nil {
return ConnectionStat{}, fmt.Errorf("failed to parse netaddr, %s", f[8])
var laddr, raddr Addr
if f[7] == "unix" {
laddr.IP = f[8]
} else {
laddr, raddr, err = parseNetAddr(f[8])
if err != nil {
return ConnectionStat{}, fmt.Errorf("failed to parse netaddr, %s", f[8])
}
}
n := ConnectionStat{

425
vendor/github.com/shirou/gopsutil/net/net_aix.go generated vendored Normal file
View File

@@ -0,0 +1,425 @@
// +build aix
package net
import (
"context"
"fmt"
"os/exec"
"regexp"
"strconv"
"strings"
"syscall"
"github.com/shirou/gopsutil/internal/common"
)
func parseNetstatI(output string) ([]IOCountersStat, error) {
lines := strings.Split(string(output), "\n")
ret := make([]IOCountersStat, 0, len(lines)-1)
exists := make([]string, 0, len(ret))
// Check first line is header
if len(lines) > 0 && strings.Fields(lines[0])[0] != "Name" {
return nil, fmt.Errorf("not a 'netstat -i' output")
}
for _, line := range lines[1:] {
values := strings.Fields(line)
if len(values) < 1 || values[0] == "Name" {
continue
}
if common.StringsHas(exists, values[0]) {
// skip if already get
continue
}
exists = append(exists, values[0])
if len(values) < 9 {
continue
}
base := 1
// sometimes Address is omitted
if len(values) < 10 {
base = 0
}
parsed := make([]uint64, 0, 5)
vv := []string{
values[base+3], // Ipkts == PacketsRecv
values[base+4], // Ierrs == Errin
values[base+5], // Opkts == PacketsSent
values[base+6], // Oerrs == Errout
values[base+8], // Drops == Dropout
}
for _, target := range vv {
if target == "-" {
parsed = append(parsed, 0)
continue
}
t, err := strconv.ParseUint(target, 10, 64)
if err != nil {
return nil, err
}
parsed = append(parsed, t)
}
n := IOCountersStat{
Name: values[0],
PacketsRecv: parsed[0],
Errin: parsed[1],
PacketsSent: parsed[2],
Errout: parsed[3],
Dropout: parsed[4],
}
ret = append(ret, n)
}
return ret, nil
}
func IOCounters(pernic bool) ([]IOCountersStat, error) {
return IOCountersWithContext(context.Background(), pernic)
}
func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat, error) {
netstat, err := exec.LookPath("netstat")
if err != nil {
return nil, err
}
out, err := invoke.CommandWithContext(ctx, netstat, "-idn")
if err != nil {
return nil, err
}
iocounters, err := parseNetstatI(string(out))
if err != nil {
return nil, err
}
if pernic == false {
return getIOCountersAll(iocounters)
}
return iocounters, nil
}
// NetIOCountersByFile is an method which is added just a compatibility for linux.
func IOCountersByFile(pernic bool, filename string) ([]IOCountersStat, error) {
return IOCountersByFileWithContext(context.Background(), pernic, filename)
}
func IOCountersByFileWithContext(ctx context.Context, pernic bool, filename string) ([]IOCountersStat, error) {
return IOCounters(pernic)
}
func FilterCounters() ([]FilterStat, error) {
return FilterCountersWithContext(context.Background())
}
func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) {
return nil, common.ErrNotImplementedError
}
func ConntrackStats(percpu bool) ([]ConntrackStat, error) {
return ConntrackStatsWithContext(context.Background(), percpu)
}
func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) {
return nil, common.ErrNotImplementedError
}
func ProtoCounters(protocols []string) ([]ProtoCountersStat, error) {
return ProtoCountersWithContext(context.Background(), protocols)
}
func ProtoCountersWithContext(ctx context.Context, protocols []string) ([]ProtoCountersStat, error) {
return nil, common.ErrNotImplementedError
}
func parseNetstatNetLine(line string) (ConnectionStat, error) {
f := strings.Fields(line)
if len(f) < 5 {
return ConnectionStat{}, fmt.Errorf("wrong line,%s", line)
}
var netType, netFamily uint32
switch f[0] {
case "tcp", "tcp4":
netType = syscall.SOCK_STREAM
netFamily = syscall.AF_INET
case "udp", "udp4":
netType = syscall.SOCK_DGRAM
netFamily = syscall.AF_INET
case "tcp6":
netType = syscall.SOCK_STREAM
netFamily = syscall.AF_INET6
case "udp6":
netType = syscall.SOCK_DGRAM
netFamily = syscall.AF_INET6
default:
return ConnectionStat{}, fmt.Errorf("unknown type, %s", f[0])
}
laddr, raddr, err := parseNetstatAddr(f[3], f[4], netFamily)
if err != nil {
return ConnectionStat{}, fmt.Errorf("failed to parse netaddr, %s %s", f[3], f[4])
}
n := ConnectionStat{
Fd: uint32(0), // not supported
Family: uint32(netFamily),
Type: uint32(netType),
Laddr: laddr,
Raddr: raddr,
Pid: int32(0), // not supported
}
if len(f) == 6 {
n.Status = f[5]
}
return n, nil
}
var portMatch = regexp.MustCompile(`(.*)\.(\d+)$`)
// This function only works for netstat returning addresses with a "."
// before the port (0.0.0.0.22 instead of 0.0.0.0:22).
func parseNetstatAddr(local string, remote string, family uint32) (laddr Addr, raddr Addr, err error) {
parse := func(l string) (Addr, error) {
matches := portMatch.FindStringSubmatch(l)
if matches == nil {
return Addr{}, fmt.Errorf("wrong addr, %s", l)
}
host := matches[1]
port := matches[2]
if host == "*" {
switch family {
case syscall.AF_INET:
host = "0.0.0.0"
case syscall.AF_INET6:
host = "::"
default:
return Addr{}, fmt.Errorf("unknown family, %d", family)
}
}
lport, err := strconv.Atoi(port)
if err != nil {
return Addr{}, err
}
return Addr{IP: host, Port: uint32(lport)}, nil
}
laddr, err = parse(local)
if remote != "*.*" { // remote addr exists
raddr, err = parse(remote)
if err != nil {
return laddr, raddr, err
}
}
return laddr, raddr, err
}
func parseNetstatUnixLine(f []string) (ConnectionStat, error) {
if len(f) < 8 {
return ConnectionStat{}, fmt.Errorf("wrong number of fields: expected >=8 got %d", len(f))
}
var netType uint32
switch f[1] {
case "dgram":
netType = syscall.SOCK_DGRAM
case "stream":
netType = syscall.SOCK_STREAM
default:
return ConnectionStat{}, fmt.Errorf("unknown type: %s", f[1])
}
// Some Unix Socket don't have any address associated
addr := ""
if len(f) == 9 {
addr = f[8]
}
c := ConnectionStat{
Fd: uint32(0), // not supported
Family: uint32(syscall.AF_UNIX),
Type: uint32(netType),
Laddr: Addr{
IP: addr,
},
Status: "NONE",
Pid: int32(0), // not supported
}
return c, nil
}
// Return true if proto is the corresponding to the kind parameter
// Only for Inet lines
func hasCorrectInetProto(kind, proto string) bool {
switch kind {
case "all", "inet":
return true
case "unix":
return false
case "inet4":
return !strings.HasSuffix(proto, "6")
case "inet6":
return strings.HasSuffix(proto, "6")
case "tcp":
return proto == "tcp" || proto == "tcp4" || proto == "tcp6"
case "tcp4":
return proto == "tcp" || proto == "tcp4"
case "tcp6":
return proto == "tcp6"
case "udp":
return proto == "udp" || proto == "udp4" || proto == "udp6"
case "udp4":
return proto == "udp" || proto == "udp4"
case "udp6":
return proto == "udp6"
}
return false
}
func parseNetstatA(output string, kind string) ([]ConnectionStat, error) {
var ret []ConnectionStat
lines := strings.Split(string(output), "\n")
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) < 1 {
continue
}
if strings.HasPrefix(fields[0], "f1") {
// Unix lines
if len(fields) < 2 {
// every unix connections have two lines
continue
}
c, err := parseNetstatUnixLine(fields)
if err != nil {
return nil, fmt.Errorf("failed to parse Unix Address (%s): %s", line, err)
}
ret = append(ret, c)
} else if strings.HasPrefix(fields[0], "tcp") || strings.HasPrefix(fields[0], "udp") {
// Inet lines
if !hasCorrectInetProto(kind, fields[0]) {
continue
}
// On AIX, netstat display some connections with "*.*" as local addresses
// Skip them as they aren't real connections.
if fields[3] == "*.*" {
continue
}
c, err := parseNetstatNetLine(line)
if err != nil {
return nil, fmt.Errorf("failed to parse Inet Address (%s): %s", line, err)
}
ret = append(ret, c)
} else {
// Header lines
continue
}
}
return ret, nil
}
func Connections(kind string) ([]ConnectionStat, error) {
return ConnectionsWithContext(context.Background(), kind)
}
func ConnectionsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
args := []string{"-na"}
switch strings.ToLower(kind) {
default:
fallthrough
case "":
kind = "all"
case "all":
// nothing to add
case "inet", "inet4", "inet6":
args = append(args, "-finet")
case "tcp", "tcp4", "tcp6":
args = append(args, "-finet")
case "udp", "udp4", "udp6":
args = append(args, "-finet")
case "unix":
args = append(args, "-funix")
}
netstat, err := exec.LookPath("netstat")
if err != nil {
return nil, err
}
out, err := invoke.CommandWithContext(ctx, netstat, args...)
if err != nil {
return nil, err
}
ret, err := parseNetstatA(string(out), kind)
if err != nil {
return nil, err
}
return ret, nil
}
func ConnectionsMax(kind string, max int) ([]ConnectionStat, error) {
return ConnectionsMaxWithContext(context.Background(), kind, max)
}
func ConnectionsMaxWithContext(ctx context.Context, kind string, max int) ([]ConnectionStat, error) {
return []ConnectionStat{}, common.ErrNotImplementedError
}
// Return a list of network connections opened, omitting `Uids`.
// WithoutUids functions are reliant on implementation details. They may be altered to be an alias for Connections or be
// removed from the API in the future.
func ConnectionsWithoutUids(kind string) ([]ConnectionStat, error) {
return ConnectionsWithoutUidsWithContext(context.Background(), kind)
}
func ConnectionsWithoutUidsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
return ConnectionsMaxWithoutUidsWithContext(ctx, kind, 0)
}
func ConnectionsMaxWithoutUidsWithContext(ctx context.Context, kind string, max int) ([]ConnectionStat, error) {
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, 0, max)
}
func ConnectionsPidWithoutUids(kind string, pid int32) ([]ConnectionStat, error) {
return ConnectionsPidWithoutUidsWithContext(context.Background(), kind, pid)
}
func ConnectionsPidWithoutUidsWithContext(ctx context.Context, kind string, pid int32) ([]ConnectionStat, error) {
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, 0)
}
func ConnectionsPidMaxWithoutUids(kind string, pid int32, max int) ([]ConnectionStat, error) {
return ConnectionsPidMaxWithoutUidsWithContext(context.Background(), kind, pid, max)
}
func ConnectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, max int) ([]ConnectionStat, error) {
return connectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, max)
}
func connectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, max int) ([]ConnectionStat, error) {
return []ConnectionStat{}, common.ErrNotImplementedError
}

View File

@@ -6,6 +6,7 @@ import (
"context"
"errors"
"fmt"
"github.com/shirou/gopsutil/internal/common"
"os/exec"
"regexp"
"strconv"
@@ -42,7 +43,7 @@ func parseNetstatLine(line string) (stat *IOCountersStat, linkID *uint, err erro
base := 1
numberColumns := len(columns)
// sometimes Address is ommitted
// sometimes Address is omitted
if numberColumns < 12 {
base = 0
}
@@ -174,13 +175,13 @@ func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat,
retIndex int
)
netstat, err := exec.LookPath("/usr/sbin/netstat")
netstat, err := exec.LookPath("netstat")
if err != nil {
return nil, err
}
// try to get all interface metrics, and hope there won't be any truncated
out, err := invoke.Command(netstat, "-ibdnW")
out, err := invoke.CommandWithContext(ctx, netstat, "-ibdnW")
if err != nil {
return nil, err
}
@@ -204,11 +205,11 @@ func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat,
}
} else {
// duplicated interface, list all interfaces
ifconfig, err := exec.LookPath("/sbin/ifconfig")
ifconfig, err := exec.LookPath("ifconfig")
if err != nil {
return nil, err
}
if out, err = invoke.Command(ifconfig, "-l"); err != nil {
if out, err = invoke.CommandWithContext(ctx, ifconfig, "-l"); err != nil {
return nil, err
}
interfaceNames := strings.Fields(strings.TrimRight(string(out), endOfLine))
@@ -227,7 +228,7 @@ func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat,
}
if truncated {
// run netstat with -I$ifacename
if out, err = invoke.Command(netstat, "-ibdnWI"+interfaceName); err != nil {
if out, err = invoke.CommandWithContext(ctx, netstat, "-ibdnWI"+interfaceName); err != nil {
return nil, err
}
parsedIfaces, err := parseNetstatOutput(string(out))
@@ -271,6 +272,14 @@ func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) {
return nil, errors.New("NetFilterCounters not implemented for darwin")
}
func ConntrackStats(percpu bool) ([]ConntrackStat, error) {
return ConntrackStatsWithContext(context.Background(), percpu)
}
func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) {
return nil, common.ErrNotImplementedError
}
// NetProtoCounters returns network statistics for the entire system
// If protocols is empty then all protocols are returned, otherwise
// just the protocols in the list are returned.

View File

@@ -1,140 +0,0 @@
package net
import (
"testing"
assert "github.com/stretchr/testify/require"
)
const (
netstatTruncated = `Name Mtu Network Address Ipkts Ierrs Ibytes Opkts Oerrs Obytes Coll Drop
lo0 16384 <Link#1> 31241 0 3769823 31241 0 3769823 0 0
lo0 16384 ::1/128 ::1 31241 - 3769823 31241 - 3769823 - -
lo0 16384 127 127.0.0.1 31241 - 3769823 31241 - 3769823 - -
lo0 16384 fe80::1%lo0 fe80:1::1 31241 - 3769823 31241 - 3769823 - -
gif0* 1280 <Link#2> 0 0 0 0 0 0 0 0
stf0* 1280 <Link#3> 0 0 0 0 0 0 0 0
utun8 1500 <Link#88> 286 0 27175 0 0 0 0 0
utun8 1500 <Link#90> 286 0 29554 0 0 0 0 0
utun8 1500 <Link#92> 286 0 29244 0 0 0 0 0
utun8 1500 <Link#93> 286 0 28267 0 0 0 0 0
utun8 1500 <Link#95> 286 0 28593 0 0 0 0 0`
netstatNotTruncated = `Name Mtu Network Address Ipkts Ierrs Ibytes Opkts Oerrs Obytes Coll Drop
lo0 16384 <Link#1> 27190978 0 12824763793 27190978 0 12824763793 0 0
lo0 16384 ::1/128 ::1 27190978 - 12824763793 27190978 - 12824763793 - -
lo0 16384 127 127.0.0.1 27190978 - 12824763793 27190978 - 12824763793 - -
lo0 16384 fe80::1%lo0 fe80:1::1 27190978 - 12824763793 27190978 - 12824763793 - -
gif0* 1280 <Link#2> 0 0 0 0 0 0 0 0
stf0* 1280 <Link#3> 0 0 0 0 0 0 0 0
en0 1500 <Link#4> a8:66:7f:dd:ee:ff 5708989 0 7295722068 3494252 0 379533492 0 230
en0 1500 fe80::aa66: fe80:4::aa66:7fff 5708989 - 7295722068 3494252 - 379533492 - -`
)
func TestparseNetstatLineHeader(t *testing.T) {
stat, linkIkd, err := parseNetstatLine(`Name Mtu Network Address Ipkts Ierrs Ibytes Opkts Oerrs Obytes Coll Drop`)
assert.Nil(t, linkIkd)
assert.Nil(t, stat)
assert.Error(t, err)
assert.Equal(t, errNetstatHeader, err)
}
func assertLoopbackStat(t *testing.T, err error, stat *IOCountersStat) {
assert.NoError(t, err)
assert.Equal(t, 869107, stat.PacketsRecv)
assert.Equal(t, 0, stat.Errin)
assert.Equal(t, 169411755, stat.BytesRecv)
assert.Equal(t, 869108, stat.PacketsSent)
assert.Equal(t, 1, stat.Errout)
assert.Equal(t, 169411756, stat.BytesSent)
}
func TestparseNetstatLineLink(t *testing.T) {
stat, linkID, err := parseNetstatLine(
`lo0 16384 <Link#1> 869107 0 169411755 869108 1 169411756 0 0`,
)
assertLoopbackStat(t, err, stat)
assert.NotNil(t, linkID)
assert.Equal(t, uint(1), *linkID)
}
func TestparseNetstatLineIPv6(t *testing.T) {
stat, linkID, err := parseNetstatLine(
`lo0 16384 ::1/128 ::1 869107 - 169411755 869108 1 169411756 - -`,
)
assertLoopbackStat(t, err, stat)
assert.Nil(t, linkID)
}
func TestparseNetstatLineIPv4(t *testing.T) {
stat, linkID, err := parseNetstatLine(
`lo0 16384 127 127.0.0.1 869107 - 169411755 869108 1 169411756 - -`,
)
assertLoopbackStat(t, err, stat)
assert.Nil(t, linkID)
}
func TestParseNetstatOutput(t *testing.T) {
nsInterfaces, err := parseNetstatOutput(netstatNotTruncated)
assert.NoError(t, err)
assert.Len(t, nsInterfaces, 8)
for index := range nsInterfaces {
assert.NotNil(t, nsInterfaces[index].stat, "Index %d", index)
}
assert.NotNil(t, nsInterfaces[0].linkID)
assert.Equal(t, uint(1), *nsInterfaces[0].linkID)
assert.Nil(t, nsInterfaces[1].linkID)
assert.Nil(t, nsInterfaces[2].linkID)
assert.Nil(t, nsInterfaces[3].linkID)
assert.NotNil(t, nsInterfaces[4].linkID)
assert.Equal(t, uint(2), *nsInterfaces[4].linkID)
assert.NotNil(t, nsInterfaces[5].linkID)
assert.Equal(t, uint(3), *nsInterfaces[5].linkID)
assert.NotNil(t, nsInterfaces[6].linkID)
assert.Equal(t, uint(4), *nsInterfaces[6].linkID)
assert.Nil(t, nsInterfaces[7].linkID)
mapUsage := newMapInterfaceNameUsage(nsInterfaces)
assert.False(t, mapUsage.isTruncated())
assert.Len(t, mapUsage.notTruncated(), 4)
}
func TestParseNetstatTruncated(t *testing.T) {
nsInterfaces, err := parseNetstatOutput(netstatTruncated)
assert.NoError(t, err)
assert.Len(t, nsInterfaces, 11)
for index := range nsInterfaces {
assert.NotNil(t, nsInterfaces[index].stat, "Index %d", index)
}
const truncatedIface = "utun8"
assert.NotNil(t, nsInterfaces[6].linkID)
assert.Equal(t, uint(88), *nsInterfaces[6].linkID)
assert.Equal(t, truncatedIface, nsInterfaces[6].stat.Name)
assert.NotNil(t, nsInterfaces[7].linkID)
assert.Equal(t, uint(90), *nsInterfaces[7].linkID)
assert.Equal(t, truncatedIface, nsInterfaces[7].stat.Name)
assert.NotNil(t, nsInterfaces[8].linkID)
assert.Equal(t, uint(92), *nsInterfaces[8].linkID)
assert.Equal(t, truncatedIface, nsInterfaces[8].stat.Name)
assert.NotNil(t, nsInterfaces[9].linkID)
assert.Equal(t, uint(93), *nsInterfaces[9].linkID)
assert.Equal(t, truncatedIface, nsInterfaces[9].stat.Name)
assert.NotNil(t, nsInterfaces[10].linkID)
assert.Equal(t, uint(95), *nsInterfaces[10].linkID)
assert.Equal(t, truncatedIface, nsInterfaces[10].stat.Name)
mapUsage := newMapInterfaceNameUsage(nsInterfaces)
assert.True(t, mapUsage.isTruncated())
assert.Equal(t, 3, len(mapUsage.notTruncated()), "en0, gif0 and stf0")
}

View File

@@ -1,4 +1,4 @@
// +build !darwin,!linux,!freebsd,!openbsd,!windows
// +build !aix,!darwin,!linux,!freebsd,!openbsd,!windows
package net
@@ -24,6 +24,14 @@ func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) {
return []FilterStat{}, common.ErrNotImplementedError
}
func ConntrackStats(percpu bool) ([]ConntrackStat, error) {
return ConntrackStatsWithContext(context.Background(), percpu)
}
func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) {
return nil, common.ErrNotImplementedError
}
func ProtoCounters(protocols []string) ([]ProtoCountersStat, error) {
return ProtoCountersWithContext(context.Background(), protocols)
}
@@ -47,3 +55,38 @@ func ConnectionsMax(kind string, max int) ([]ConnectionStat, error) {
func ConnectionsMaxWithContext(ctx context.Context, kind string, max int) ([]ConnectionStat, error) {
return []ConnectionStat{}, common.ErrNotImplementedError
}
// Return a list of network connections opened, omitting `Uids`.
// WithoutUids functions are reliant on implementation details. They may be altered to be an alias for Connections or be
// removed from the API in the future.
func ConnectionsWithoutUids(kind string) ([]ConnectionStat, error) {
return ConnectionsWithoutUidsWithContext(context.Background(), kind)
}
func ConnectionsWithoutUidsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
return ConnectionsMaxWithoutUidsWithContext(ctx, kind, 0)
}
func ConnectionsMaxWithoutUidsWithContext(ctx context.Context, kind string, max int) ([]ConnectionStat, error) {
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, 0, max)
}
func ConnectionsPidWithoutUids(kind string, pid int32) ([]ConnectionStat, error) {
return ConnectionsPidWithoutUidsWithContext(context.Background(), kind, pid)
}
func ConnectionsPidWithoutUidsWithContext(ctx context.Context, kind string, pid int32) ([]ConnectionStat, error) {
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, 0)
}
func ConnectionsPidMaxWithoutUids(kind string, pid int32, max int) ([]ConnectionStat, error) {
return ConnectionsPidMaxWithoutUidsWithContext(context.Background(), kind, pid, max)
}
func ConnectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, max int) ([]ConnectionStat, error) {
return connectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, max)
}
func connectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, max int) ([]ConnectionStat, error) {
return []ConnectionStat{}, common.ErrNotImplementedError
}

View File

@@ -17,11 +17,11 @@ func IOCounters(pernic bool) ([]IOCountersStat, error) {
}
func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat, error) {
netstat, err := exec.LookPath("/usr/bin/netstat")
netstat, err := exec.LookPath("netstat")
if err != nil {
return nil, err
}
out, err := invoke.Command(netstat, "-ibdnW")
out, err := invoke.CommandWithContext(ctx, netstat, "-ibdnW")
if err != nil {
return nil, err
}
@@ -45,7 +45,7 @@ func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat,
continue
}
base := 1
// sometimes Address is ommitted
// sometimes Address is omitted
if len(values) < 13 {
base = 0
}
@@ -112,6 +112,14 @@ func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) {
return nil, errors.New("NetFilterCounters not implemented for freebsd")
}
func ConntrackStats(percpu bool) ([]ConntrackStat, error) {
return ConntrackStatsWithContext(context.Background(), percpu)
}
func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) {
return nil, errors.New("ConntrackStats not implemented for freebsd")
}
// NetProtoCounters returns network statistics for the entire system
// If protocols is empty then all protocols are returned, otherwise
// just the protocols in the list are returned.

View File

@@ -8,6 +8,7 @@ import (
"encoding/hex"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"os"
@@ -18,6 +19,26 @@ import (
"github.com/shirou/gopsutil/internal/common"
)
const ( // Conntrack Column numbers
CT_ENTRIES = iota
CT_SEARCHED
CT_FOUND
CT_NEW
CT_INVALID
CT_IGNORE
CT_DELETE
CT_DELETE_LIST
CT_INSERT
CT_INSERT_FAILED
CT_DROP
CT_EARLY_DROP
CT_ICMP_ERROR
CT_EXPECT_NEW
CT_EXPECT_CREATE
CT_EXPECT_DELETE
CT_SEARCH_RESTART
)
// NetIOCounters returnes network I/O statistics for every network
// interface installed on the system. If pernic argument is false,
// return only sum of all information (which name is 'all'). If true,
@@ -232,6 +253,58 @@ func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) {
return stats, nil
}
// ConntrackStats returns more detailed info about the conntrack table
func ConntrackStats(percpu bool) ([]ConntrackStat, error) {
return ConntrackStatsWithContext(context.Background(), percpu)
}
// ConntrackStatsWithContext returns more detailed info about the conntrack table
func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) {
return conntrackStatsFromFile(common.HostProc("net/stat/nf_conntrack"), percpu)
}
// conntrackStatsFromFile returns more detailed info about the conntrack table
// from `filename`
// If 'percpu' is false, the result will contain exactly one item with totals/summary
func conntrackStatsFromFile(filename string, percpu bool) ([]ConntrackStat, error) {
lines, err := common.ReadLines(filename)
if err != nil {
return nil, err
}
statlist := NewConntrackStatList()
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) == 17 && fields[0] != "entries" {
statlist.Append(NewConntrackStat(
common.HexToUint32(fields[CT_ENTRIES]),
common.HexToUint32(fields[CT_SEARCHED]),
common.HexToUint32(fields[CT_FOUND]),
common.HexToUint32(fields[CT_NEW]),
common.HexToUint32(fields[CT_INVALID]),
common.HexToUint32(fields[CT_IGNORE]),
common.HexToUint32(fields[CT_DELETE]),
common.HexToUint32(fields[CT_DELETE_LIST]),
common.HexToUint32(fields[CT_INSERT]),
common.HexToUint32(fields[CT_INSERT_FAILED]),
common.HexToUint32(fields[CT_DROP]),
common.HexToUint32(fields[CT_EARLY_DROP]),
common.HexToUint32(fields[CT_ICMP_ERROR]),
common.HexToUint32(fields[CT_EXPECT_NEW]),
common.HexToUint32(fields[CT_EXPECT_CREATE]),
common.HexToUint32(fields[CT_EXPECT_DELETE]),
common.HexToUint32(fields[CT_SEARCH_RESTART]),
))
}
}
if percpu {
return statlist.Items(), nil
}
return statlist.Summary(), nil
}
// http://students.mimuw.edu.pl/lxr/source/include/net/tcp_states.h
var TCPStatuses = map[string]string{
"01": "ESTABLISHED",
@@ -328,32 +401,36 @@ func ConnectionsMaxWithContext(ctx context.Context, kind string, max int) ([]Con
return ConnectionsPidMax(kind, 0, max)
}
// Return a list of network connections opened, omitting `Uids`.
// WithoutUids functions are reliant on implementation details. They may be altered to be an alias for Connections or be
// removed from the API in the future.
func ConnectionsWithoutUids(kind string) ([]ConnectionStat, error) {
return ConnectionsWithoutUidsWithContext(context.Background(), kind)
}
func ConnectionsWithoutUidsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
return ConnectionsMaxWithoutUidsWithContext(ctx, kind, 0)
}
func ConnectionsMaxWithoutUidsWithContext(ctx context.Context, kind string, max int) ([]ConnectionStat, error) {
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, 0, max)
}
// Return a list of network connections opened by a process.
func ConnectionsPid(kind string, pid int32) ([]ConnectionStat, error) {
return ConnectionsPidWithContext(context.Background(), kind, pid)
}
func ConnectionsPidWithoutUids(kind string, pid int32) ([]ConnectionStat, error) {
return ConnectionsPidWithoutUidsWithContext(context.Background(), kind, pid)
}
func ConnectionsPidWithContext(ctx context.Context, kind string, pid int32) ([]ConnectionStat, error) {
tmap, ok := netConnectionKindMap[kind]
if !ok {
return nil, fmt.Errorf("invalid kind, %s", kind)
}
root := common.HostProc()
var err error
var inodes map[string][]inodeMap
if pid == 0 {
inodes, err = getProcInodesAll(root, 0)
} else {
inodes, err = getProcInodes(root, pid, 0)
if len(inodes) == 0 {
// no connection for the pid
return []ConnectionStat{}, nil
}
}
if err != nil {
return nil, fmt.Errorf("cound not get pid(s), %d: %s", pid, err)
}
return statsFromInodes(root, pid, tmap, inodes)
return ConnectionsPidMaxWithContext(ctx, kind, pid, 0)
}
func ConnectionsPidWithoutUidsWithContext(ctx context.Context, kind string, pid int32) ([]ConnectionStat, error) {
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, 0)
}
// Return up to `max` network connections opened by a process.
@@ -361,7 +438,19 @@ func ConnectionsPidMax(kind string, pid int32, max int) ([]ConnectionStat, error
return ConnectionsPidMaxWithContext(context.Background(), kind, pid, max)
}
func ConnectionsPidMaxWithoutUids(kind string, pid int32, max int) ([]ConnectionStat, error) {
return ConnectionsPidMaxWithoutUidsWithContext(context.Background(), kind, pid, max)
}
func ConnectionsPidMaxWithContext(ctx context.Context, kind string, pid int32, max int) ([]ConnectionStat, error) {
return connectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, max, false)
}
func ConnectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, max int) ([]ConnectionStat, error) {
return connectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, max, true)
}
func connectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, max int, skipUids bool) ([]ConnectionStat, error) {
tmap, ok := netConnectionKindMap[kind]
if !ok {
return nil, fmt.Errorf("invalid kind, %s", kind)
@@ -379,12 +468,12 @@ func ConnectionsPidMaxWithContext(ctx context.Context, kind string, pid int32, m
}
}
if err != nil {
return nil, fmt.Errorf("cound not get pid(s), %d", pid)
return nil, fmt.Errorf("cound not get pid(s), %d: %s", pid, err)
}
return statsFromInodes(root, pid, tmap, inodes)
return statsFromInodes(root, pid, tmap, inodes, skipUids)
}
func statsFromInodes(root string, pid int32, tmap []netConnectionKindType, inodes map[string][]inodeMap) ([]ConnectionStat, error) {
func statsFromInodes(root string, pid int32, tmap []netConnectionKindType, inodes map[string][]inodeMap, skipUids bool) ([]ConnectionStat, error) {
dupCheckMap := make(map[string]struct{})
var ret []ConnectionStat
@@ -393,11 +482,13 @@ 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:
fallthrough
case syscall.AF_INET6:
case syscall.AF_INET, syscall.AF_INET6:
ls, err = processInet(path, t, inodes, pid)
case syscall.AF_UNIX:
ls, err = processUnix(path, t, inodes, pid)
@@ -429,9 +520,11 @@ func statsFromInodes(root string, pid int32, tmap []netConnectionKindType, inode
conn.Pid = c.pid
}
// fetch process owner Real, effective, saved set, and filesystem UIDs
proc := process{Pid: conn.Pid}
conn.Uids, _ = proc.getUids()
if !skipUids {
// fetch process owner Real, effective, saved set, and filesystem UIDs
proc := process{Pid: conn.Pid}
conn.Uids, _ = proc.getUids()
}
ret = append(ret, conn)
dupCheckMap[connKey] = struct{}{}
@@ -579,7 +672,7 @@ func getProcInodesAll(root string, max int) (map[string][]inodeMap, error) {
t, err := getProcInodes(root, pid, max)
if err != nil {
// skip if permission error or no longer exists
if os.IsPermission(err) || os.IsNotExist(err) {
if os.IsPermission(err) || os.IsNotExist(err) || err == io.EOF {
continue
}
return ret, err

View File

@@ -1,163 +0,0 @@
package net
import (
"fmt"
"io/ioutil"
"os"
"strings"
"syscall"
"testing"
"github.com/shirou/gopsutil/internal/common"
"github.com/stretchr/testify/assert"
)
func TestIOCountersByFileParsing(t *testing.T) {
// Prpare a temporary file, which will be read during the test
tmpfile, err := ioutil.TempFile("", "proc_dev_net")
defer os.Remove(tmpfile.Name()) // clean up
assert.Nil(t, err, "Temporary file creation failed: ", err)
cases := [4][2]string{
[2]string{"eth0: ", "eth1: "},
[2]string{"eth0:0: ", "eth1:0: "},
[2]string{"eth0:", "eth1:"},
[2]string{"eth0:0:", "eth1:0:"},
}
for _, testCase := range cases {
err = tmpfile.Truncate(0)
assert.Nil(t, err, "Temporary file truncating problem: ", err)
// Parse interface name for assertion
interface0 := strings.TrimSpace(testCase[0])
interface0 = interface0[:len(interface0)-1]
interface1 := strings.TrimSpace(testCase[1])
interface1 = interface1[:len(interface1)-1]
// Replace the interfaces from the test case
proc := []byte(fmt.Sprintf("Inter-| Receive | Transmit\n face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed\n %s1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16\n %s100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600\n", testCase[0], testCase[1]))
// Write /proc/net/dev sample output
_, err = tmpfile.Write(proc)
assert.Nil(t, err, "Temporary file writing failed: ", err)
counters, err := IOCountersByFile(true, tmpfile.Name())
assert.Nil(t, err)
assert.NotEmpty(t, counters)
assert.Equal(t, 2, len(counters))
assert.Equal(t, interface0, counters[0].Name)
assert.Equal(t, 1, int(counters[0].BytesRecv))
assert.Equal(t, 2, int(counters[0].PacketsRecv))
assert.Equal(t, 3, int(counters[0].Errin))
assert.Equal(t, 4, int(counters[0].Dropin))
assert.Equal(t, 5, int(counters[0].Fifoin))
assert.Equal(t, 9, int(counters[0].BytesSent))
assert.Equal(t, 10, int(counters[0].PacketsSent))
assert.Equal(t, 11, int(counters[0].Errout))
assert.Equal(t, 12, int(counters[0].Dropout))
assert.Equal(t, 13, int(counters[0].Fifoout))
assert.Equal(t, interface1, counters[1].Name)
assert.Equal(t, 100, int(counters[1].BytesRecv))
assert.Equal(t, 200, int(counters[1].PacketsRecv))
assert.Equal(t, 300, int(counters[1].Errin))
assert.Equal(t, 400, int(counters[1].Dropin))
assert.Equal(t, 500, int(counters[1].Fifoin))
assert.Equal(t, 900, int(counters[1].BytesSent))
assert.Equal(t, 1000, int(counters[1].PacketsSent))
assert.Equal(t, 1100, int(counters[1].Errout))
assert.Equal(t, 1200, int(counters[1].Dropout))
assert.Equal(t, 1300, int(counters[1].Fifoout))
}
err = tmpfile.Close()
assert.Nil(t, err, "Temporary file closing failed: ", err)
}
func TestGetProcInodesAll(t *testing.T) {
if os.Getenv("CIRCLECI") == "true" {
t.Skip("Skip CI")
}
root := common.HostProc("")
v, err := getProcInodesAll(root, 0)
assert.Nil(t, err)
assert.NotEmpty(t, v)
}
func TestConnectionsMax(t *testing.T) {
if os.Getenv("CIRCLECI") == "true" {
t.Skip("Skip CI")
}
max := 10
v, err := ConnectionsMax("tcp", max)
assert.Nil(t, err)
assert.NotEmpty(t, v)
cxByPid := map[int32]int{}
for _, cx := range v {
if cx.Pid > 0 {
cxByPid[cx.Pid]++
}
}
for _, c := range cxByPid {
assert.True(t, c <= max)
}
}
type AddrTest struct {
IP string
Port int
Error bool
}
func TestDecodeAddress(t *testing.T) {
assert := assert.New(t)
addr := map[string]AddrTest{
"0500000A:0016": {
IP: "10.0.0.5",
Port: 22,
},
"0100007F:D1C2": {
IP: "127.0.0.1",
Port: 53698,
},
"11111:0035": {
Error: true,
},
"0100007F:BLAH": {
Error: true,
},
"0085002452100113070057A13F025401:0035": {
IP: "2400:8500:1301:1052:a157:7:154:23f",
Port: 53,
},
"00855210011307F025401:0035": {
Error: true,
},
}
for src, dst := range addr {
family := syscall.AF_INET
if len(src) > 13 {
family = syscall.AF_INET6
}
addr, err := decodeAddress(uint32(family), src)
if dst.Error {
assert.NotNil(err, src)
} else {
assert.Nil(err, src)
assert.Equal(dst.IP, addr.IP, src)
assert.Equal(dst.Port, int(addr.Port), src)
}
}
}
func TestReverse(t *testing.T) {
src := []byte{0x01, 0x02, 0x03}
assert.Equal(t, []byte{0x03, 0x02, 0x01}, Reverse(src))
}

View File

@@ -41,7 +41,7 @@ func ParseNetstat(output string, mode string,
continue
}
base := 1
// sometimes Address is ommitted
// sometimes Address is omitted
if len(values) < columns {
base = 0
}
@@ -106,11 +106,11 @@ func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat,
if err != nil {
return nil, err
}
out, err := invoke.Command(netstat, "-inb")
out, err := invoke.CommandWithContext(ctx, netstat, "-inb")
if err != nil {
return nil, err
}
out2, err := invoke.Command(netstat, "-ind")
out2, err := invoke.CommandWithContext(ctx, netstat, "-ind")
if err != nil {
return nil, err
}
@@ -156,6 +156,14 @@ func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) {
return nil, errors.New("NetFilterCounters not implemented for openbsd")
}
func ConntrackStats(percpu bool) ([]ConntrackStat, error) {
return ConntrackStatsWithContext(context.Background(), percpu)
}
func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) {
return nil, common.ErrNotImplementedError
}
// NetProtoCounters returns network statistics for the entire system
// If protocols is empty then all protocols are returned, otherwise
// just the protocols in the list are returned.
@@ -290,7 +298,7 @@ func ConnectionsWithContext(ctx context.Context, kind string) ([]ConnectionStat,
if err != nil {
return nil, err
}
out, err := invoke.Command(netstat, args...)
out, err := invoke.CommandWithContext(ctx, netstat, args...)
if err != nil {
return nil, err

View File

@@ -1,229 +0,0 @@
package net
import (
"fmt"
"os"
"runtime"
"testing"
"github.com/shirou/gopsutil/internal/common"
)
func TestAddrString(t *testing.T) {
v := Addr{IP: "192.168.0.1", Port: 8000}
s := fmt.Sprintf("%v", v)
if s != "{\"ip\":\"192.168.0.1\",\"port\":8000}" {
t.Errorf("Addr string is invalid: %v", v)
}
}
func TestNetIOCountersStatString(t *testing.T) {
v := IOCountersStat{
Name: "test",
BytesSent: 100,
}
e := `{"name":"test","bytesSent":100,"bytesRecv":0,"packetsSent":0,"packetsRecv":0,"errin":0,"errout":0,"dropin":0,"dropout":0,"fifoin":0,"fifoout":0}`
if e != fmt.Sprintf("%v", v) {
t.Errorf("NetIOCountersStat string is invalid: %v", v)
}
}
func TestNetProtoCountersStatString(t *testing.T) {
v := ProtoCountersStat{
Protocol: "tcp",
Stats: map[string]int64{
"MaxConn": -1,
"ActiveOpens": 4000,
"PassiveOpens": 3000,
},
}
e := `{"protocol":"tcp","stats":{"ActiveOpens":4000,"MaxConn":-1,"PassiveOpens":3000}}`
if e != fmt.Sprintf("%v", v) {
t.Errorf("NetProtoCountersStat string is invalid: %v", v)
}
}
func TestNetConnectionStatString(t *testing.T) {
v := ConnectionStat{
Fd: 10,
Family: 10,
Type: 10,
Uids: []int32{10, 10},
}
e := `{"fd":10,"family":10,"type":10,"localaddr":{"ip":"","port":0},"remoteaddr":{"ip":"","port":0},"status":"","uids":[10,10],"pid":0}`
if e != fmt.Sprintf("%v", v) {
t.Errorf("NetConnectionStat string is invalid: %v", v)
}
}
func TestNetIOCountersAll(t *testing.T) {
v, err := IOCounters(false)
per, err := IOCounters(true)
if err != nil {
t.Errorf("Could not get NetIOCounters: %v", err)
}
if len(v) != 1 {
t.Errorf("Could not get NetIOCounters: %v", v)
}
if v[0].Name != "all" {
t.Errorf("Invalid NetIOCounters: %v", v)
}
var pr uint64
for _, p := range per {
pr += p.PacketsRecv
}
if v[0].PacketsRecv != pr {
t.Errorf("invalid sum value: %v, %v", v[0].PacketsRecv, pr)
}
}
func TestNetIOCountersPerNic(t *testing.T) {
v, err := IOCounters(true)
if err != nil {
t.Errorf("Could not get NetIOCounters: %v", err)
}
if len(v) == 0 {
t.Errorf("Could not get NetIOCounters: %v", v)
}
for _, vv := range v {
if vv.Name == "" {
t.Errorf("Invalid NetIOCounters: %v", vv)
}
}
}
func TestGetNetIOCountersAll(t *testing.T) {
n := []IOCountersStat{
{
Name: "a",
BytesRecv: 10,
PacketsRecv: 10,
},
{
Name: "b",
BytesRecv: 10,
PacketsRecv: 10,
Errin: 10,
},
}
ret, err := getIOCountersAll(n)
if err != nil {
t.Error(err)
}
if len(ret) != 1 {
t.Errorf("invalid return count")
}
if ret[0].Name != "all" {
t.Errorf("invalid return name")
}
if ret[0].BytesRecv != 20 {
t.Errorf("invalid count bytesrecv")
}
if ret[0].Errin != 10 {
t.Errorf("invalid count errin")
}
}
func TestNetInterfaces(t *testing.T) {
v, err := Interfaces()
if err != nil {
t.Errorf("Could not get NetInterfaceStat: %v", err)
}
if len(v) == 0 {
t.Errorf("Could not get NetInterfaceStat: %v", err)
}
for _, vv := range v {
if vv.Name == "" {
t.Errorf("Invalid NetInterface: %v", vv)
}
}
}
func TestNetProtoCountersStatsAll(t *testing.T) {
v, err := ProtoCounters(nil)
if err != nil {
t.Fatalf("Could not get NetProtoCounters: %v", err)
}
if len(v) == 0 {
t.Fatalf("Could not get NetProtoCounters: %v", err)
}
for _, vv := range v {
if vv.Protocol == "" {
t.Errorf("Invalid NetProtoCountersStat: %v", vv)
}
if len(vv.Stats) == 0 {
t.Errorf("Invalid NetProtoCountersStat: %v", vv)
}
}
}
func TestNetProtoCountersStats(t *testing.T) {
v, err := ProtoCounters([]string{"tcp", "ip"})
if err != nil {
t.Fatalf("Could not get NetProtoCounters: %v", err)
}
if len(v) == 0 {
t.Fatalf("Could not get NetProtoCounters: %v", err)
}
if len(v) != 2 {
t.Fatalf("Go incorrect number of NetProtoCounters: %v", err)
}
for _, vv := range v {
if vv.Protocol != "tcp" && vv.Protocol != "ip" {
t.Errorf("Invalid NetProtoCountersStat: %v", vv)
}
if len(vv.Stats) == 0 {
t.Errorf("Invalid NetProtoCountersStat: %v", vv)
}
}
}
func TestNetConnections(t *testing.T) {
if ci := os.Getenv("CI"); ci != "" { // skip if test on drone.io
return
}
v, err := Connections("inet")
if err != nil {
t.Errorf("could not get NetConnections: %v", err)
}
if len(v) == 0 {
t.Errorf("could not get NetConnections: %v", v)
}
for _, vv := range v {
if vv.Family == 0 {
t.Errorf("invalid NetConnections: %v", vv)
}
}
}
func TestNetFilterCounters(t *testing.T) {
if ci := os.Getenv("CI"); ci != "" { // skip if test on drone.io
return
}
if runtime.GOOS == "linux" {
// some test environment has not the path.
if !common.PathExists("/proc/sys/net/netfilter/nf_conntrackCount") {
t.SkipNow()
}
}
v, err := FilterCounters()
if err != nil {
t.Errorf("could not get NetConnections: %v", err)
}
if len(v) == 0 {
t.Errorf("could not get NetConnections: %v", v)
}
for _, vv := range v {
if vv.ConnTrackMax == 0 {
t.Errorf("nf_conntrackMax needs to be greater than zero: %v", vv)
}
}
}

View File

@@ -63,10 +63,10 @@ func ConnectionsPidWithContext(ctx context.Context, kind string, pid int32) ([]C
case "udp6":
args = append(args, "6udp")
case "unix":
return ret, common.ErrNotImplementedError
args = []string{"-U"}
}
r, err := common.CallLsof(invoke, pid, args...)
r, err := common.CallLsofWithContext(ctx, invoke, pid, args...)
if err != nil {
return nil, err
}
@@ -94,3 +94,38 @@ func ConnectionsPidMax(kind string, pid int32, max int) ([]ConnectionStat, error
func ConnectionsPidMaxWithContext(ctx context.Context, kind string, pid int32, max int) ([]ConnectionStat, error) {
return []ConnectionStat{}, common.ErrNotImplementedError
}
// Return a list of network connections opened, omitting `Uids`.
// WithoutUids functions are reliant on implementation details. They may be altered to be an alias for Connections or be
// removed from the API in the future.
func ConnectionsWithoutUids(kind string) ([]ConnectionStat, error) {
return ConnectionsWithoutUidsWithContext(context.Background(), kind)
}
func ConnectionsWithoutUidsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
return ConnectionsMaxWithoutUidsWithContext(ctx, kind, 0)
}
func ConnectionsMaxWithoutUidsWithContext(ctx context.Context, kind string, max int) ([]ConnectionStat, error) {
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, 0, max)
}
func ConnectionsPidWithoutUids(kind string, pid int32) ([]ConnectionStat, error) {
return ConnectionsPidWithoutUidsWithContext(context.Background(), kind, pid)
}
func ConnectionsPidWithoutUidsWithContext(ctx context.Context, kind string, pid int32) ([]ConnectionStat, error) {
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, 0)
}
func ConnectionsPidMaxWithoutUids(kind string, pid int32, max int) ([]ConnectionStat, error) {
return ConnectionsPidMaxWithoutUidsWithContext(context.Background(), kind, pid, max)
}
func ConnectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, max int) ([]ConnectionStat, error) {
return connectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, max)
}
func connectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, max int) ([]ConnectionStat, error) {
return []ConnectionStat{}, common.ErrNotImplementedError
}

View File

@@ -5,17 +5,21 @@ package net
import (
"context"
"errors"
"fmt"
"net"
"os"
"syscall"
"unsafe"
"github.com/shirou/gopsutil/internal/common"
"golang.org/x/sys/windows"
)
var (
modiphlpapi = windows.NewLazyDLL("iphlpapi.dll")
modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll")
procGetExtendedTCPTable = modiphlpapi.NewProc("GetExtendedTcpTable")
procGetExtendedUDPTable = modiphlpapi.NewProc("GetExtendedUdpTable")
procGetIfEntry2 = modiphlpapi.NewProc("GetIfEntry2")
)
const (
@@ -30,6 +34,105 @@ const (
TCPTableOwnerModuleAll
)
type netConnectionKindType struct {
family uint32
sockType uint32
filename string
}
var kindTCP4 = netConnectionKindType{
family: syscall.AF_INET,
sockType: syscall.SOCK_STREAM,
filename: "tcp",
}
var kindTCP6 = netConnectionKindType{
family: syscall.AF_INET6,
sockType: syscall.SOCK_STREAM,
filename: "tcp6",
}
var kindUDP4 = netConnectionKindType{
family: syscall.AF_INET,
sockType: syscall.SOCK_DGRAM,
filename: "udp",
}
var kindUDP6 = netConnectionKindType{
family: syscall.AF_INET6,
sockType: syscall.SOCK_DGRAM,
filename: "udp6",
}
var netConnectionKindMap = map[string][]netConnectionKindType{
"all": {kindTCP4, kindTCP6, kindUDP4, kindUDP6},
"tcp": {kindTCP4, kindTCP6},
"tcp4": {kindTCP4},
"tcp6": {kindTCP6},
"udp": {kindUDP4, kindUDP6},
"udp4": {kindUDP4},
"udp6": {kindUDP6},
"inet": {kindTCP4, kindTCP6, kindUDP4, kindUDP6},
"inet4": {kindTCP4, kindUDP4},
"inet6": {kindTCP6, kindUDP6},
}
// https://github.com/microsoft/ethr/blob/aecdaf923970e5a9b4c461b4e2e3963d781ad2cc/plt_windows.go#L114-L170
type guid struct {
Data1 uint32
Data2 uint16
Data3 uint16
Data4 [8]byte
}
const (
maxStringSize = 256
maxPhysAddressLength = 32
pad0for64_4for32 = 0
)
type mibIfRow2 struct {
InterfaceLuid uint64
InterfaceIndex uint32
InterfaceGuid guid
Alias [maxStringSize + 1]uint16
Description [maxStringSize + 1]uint16
PhysicalAddressLength uint32
PhysicalAddress [maxPhysAddressLength]uint8
PermanentPhysicalAddress [maxPhysAddressLength]uint8
Mtu uint32
Type uint32
TunnelType uint32
MediaType uint32
PhysicalMediumType uint32
AccessType uint32
DirectionType uint32
InterfaceAndOperStatusFlags uint32
OperStatus uint32
AdminStatus uint32
MediaConnectState uint32
NetworkGuid guid
ConnectionType uint32
padding1 [pad0for64_4for32]byte
TransmitLinkSpeed uint64
ReceiveLinkSpeed uint64
InOctets uint64
InUcastPkts uint64
InNUcastPkts uint64
InDiscards uint64
InErrors uint64
InUnknownProtos uint64
InUcastOctets uint64
InMulticastOctets uint64
InBroadcastOctets uint64
OutOctets uint64
OutUcastPkts uint64
OutNUcastPkts uint64
OutDiscards uint64
OutErrors uint64
OutUcastOctets uint64
OutMulticastOctets uint64
OutBroadcastOctets uint64
OutQLen uint64
}
func IOCounters(pernic bool) ([]IOCountersStat, error) {
return IOCountersWithContext(context.Background(), pernic)
}
@@ -39,34 +142,59 @@ func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat,
if err != nil {
return nil, err
}
var ret []IOCountersStat
var counters []IOCountersStat
for _, ifi := range ifs {
c := IOCountersStat{
Name: ifi.Name,
err = procGetIfEntry2.Find()
if err == nil { // Vista+, uint64 values (issue#693)
for _, ifi := range ifs {
c := IOCountersStat{
Name: ifi.Name,
}
row := mibIfRow2{InterfaceIndex: uint32(ifi.Index)}
ret, _, err := procGetIfEntry2.Call(uintptr(unsafe.Pointer(&row)))
if ret != 0 {
return nil, os.NewSyscallError("GetIfEntry2", err)
}
c.BytesSent = uint64(row.OutOctets)
c.BytesRecv = uint64(row.InOctets)
c.PacketsSent = uint64(row.OutUcastPkts)
c.PacketsRecv = uint64(row.InUcastPkts)
c.Errin = uint64(row.InErrors)
c.Errout = uint64(row.OutErrors)
c.Dropin = uint64(row.InDiscards)
c.Dropout = uint64(row.OutDiscards)
counters = append(counters, c)
}
} else { // WinXP fallback, uint32 values
for _, ifi := range ifs {
c := IOCountersStat{
Name: ifi.Name,
}
row := windows.MibIfRow{Index: uint32(ifi.Index)}
e := windows.GetIfEntry(&row)
if e != nil {
return nil, os.NewSyscallError("GetIfEntry", e)
row := windows.MibIfRow{Index: uint32(ifi.Index)}
err = windows.GetIfEntry(&row)
if err != nil {
return nil, os.NewSyscallError("GetIfEntry", err)
}
c.BytesSent = uint64(row.OutOctets)
c.BytesRecv = uint64(row.InOctets)
c.PacketsSent = uint64(row.OutUcastPkts)
c.PacketsRecv = uint64(row.InUcastPkts)
c.Errin = uint64(row.InErrors)
c.Errout = uint64(row.OutErrors)
c.Dropin = uint64(row.InDiscards)
c.Dropout = uint64(row.OutDiscards)
counters = append(counters, c)
}
c.BytesSent = uint64(row.OutOctets)
c.BytesRecv = uint64(row.InOctets)
c.PacketsSent = uint64(row.OutUcastPkts)
c.PacketsRecv = uint64(row.InUcastPkts)
c.Errin = uint64(row.InErrors)
c.Errout = uint64(row.OutErrors)
c.Dropin = uint64(row.InDiscards)
c.Dropout = uint64(row.OutDiscards)
ret = append(ret, c)
}
if pernic == false {
return getIOCountersAll(ret)
if !pernic {
return getIOCountersAll(counters)
}
return ret, nil
return counters, nil
}
// NetIOCountersByFile is an method which is added just a compatibility for linux.
@@ -78,15 +206,71 @@ func IOCountersByFileWithContext(ctx context.Context, pernic bool, filename stri
return IOCounters(pernic)
}
// Return a list of network connections opened by a process
// Return a list of network connections
// Available kind:
// reference to netConnectionKindMap
func Connections(kind string) ([]ConnectionStat, error) {
return ConnectionsWithContext(context.Background(), kind)
}
func ConnectionsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
var ret []ConnectionStat
return ConnectionsPidWithContext(ctx, kind, 0)
}
return ret, common.ErrNotImplementedError
// ConnectionsPid Return a list of network connections opened by a process
func ConnectionsPid(kind string, pid int32) ([]ConnectionStat, error) {
return ConnectionsPidWithContext(context.Background(), kind, pid)
}
func ConnectionsPidWithContext(ctx context.Context, kind string, pid int32) ([]ConnectionStat, error) {
tmap, ok := netConnectionKindMap[kind]
if !ok {
return nil, fmt.Errorf("invalid kind, %s", kind)
}
return getProcInet(tmap, pid)
}
func getProcInet(kinds []netConnectionKindType, pid int32) ([]ConnectionStat, error) {
stats := make([]ConnectionStat, 0)
for _, kind := range kinds {
s, err := getNetStatWithKind(kind)
if err != nil {
continue
}
if pid == 0 {
stats = append(stats, s...)
} else {
for _, ns := range s {
if ns.Pid != pid {
continue
}
stats = append(stats, ns)
}
}
}
return stats, nil
}
func getNetStatWithKind(kindType netConnectionKindType) ([]ConnectionStat, error) {
if kindType.filename == "" {
return nil, fmt.Errorf("kind filename must be required")
}
switch kindType.filename {
case kindTCP4.filename:
return getTCPConnections(kindTCP4.family)
case kindTCP6.filename:
return getTCPConnections(kindTCP6.family)
case kindUDP4.filename:
return getUDPConnections(kindUDP4.family)
case kindUDP6.filename:
return getUDPConnections(kindUDP6.family)
}
return nil, fmt.Errorf("invalid kind filename, %s", kindType.filename)
}
// Return a list of network connections opened returning at most `max`
@@ -99,6 +283,41 @@ func ConnectionsMaxWithContext(ctx context.Context, kind string, max int) ([]Con
return []ConnectionStat{}, common.ErrNotImplementedError
}
// Return a list of network connections opened, omitting `Uids`.
// WithoutUids functions are reliant on implementation details. They may be altered to be an alias for Connections or be
// removed from the API in the future.
func ConnectionsWithoutUids(kind string) ([]ConnectionStat, error) {
return ConnectionsWithoutUidsWithContext(context.Background(), kind)
}
func ConnectionsWithoutUidsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
return ConnectionsMaxWithoutUidsWithContext(ctx, kind, 0)
}
func ConnectionsMaxWithoutUidsWithContext(ctx context.Context, kind string, max int) ([]ConnectionStat, error) {
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, 0, max)
}
func ConnectionsPidWithoutUids(kind string, pid int32) ([]ConnectionStat, error) {
return ConnectionsPidWithoutUidsWithContext(context.Background(), kind, pid)
}
func ConnectionsPidWithoutUidsWithContext(ctx context.Context, kind string, pid int32) ([]ConnectionStat, error) {
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, 0)
}
func ConnectionsPidMaxWithoutUids(kind string, pid int32, max int) ([]ConnectionStat, error) {
return ConnectionsPidMaxWithoutUidsWithContext(context.Background(), kind, pid, max)
}
func ConnectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, max int) ([]ConnectionStat, error) {
return connectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, max)
}
func connectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, max int) ([]ConnectionStat, error) {
return []ConnectionStat{}, common.ErrNotImplementedError
}
func FilterCounters() ([]FilterStat, error) {
return FilterCountersWithContext(context.Background())
}
@@ -107,6 +326,15 @@ func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) {
return nil, errors.New("NetFilterCounters not implemented for windows")
}
func ConntrackStats(percpu bool) ([]ConntrackStat, error) {
return ConntrackStatsWithContext(context.Background(), percpu)
}
func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) {
return nil, common.ErrNotImplementedError
}
// NetProtoCounters returns network statistics for the entire system
// If protocols is empty then all protocols are returned, otherwise
// just the protocols in the list are returned.
@@ -118,3 +346,429 @@ func ProtoCounters(protocols []string) ([]ProtoCountersStat, error) {
func ProtoCountersWithContext(ctx context.Context, protocols []string) ([]ProtoCountersStat, error) {
return nil, errors.New("NetProtoCounters not implemented for windows")
}
func getTableUintptr(family uint32, buf []byte) uintptr {
var (
pmibTCPTable pmibTCPTableOwnerPidAll
pmibTCP6Table pmibTCP6TableOwnerPidAll
p uintptr
)
switch family {
case kindTCP4.family:
if len(buf) > 0 {
pmibTCPTable = (*mibTCPTableOwnerPid)(unsafe.Pointer(&buf[0]))
p = uintptr(unsafe.Pointer(pmibTCPTable))
} else {
p = uintptr(unsafe.Pointer(pmibTCPTable))
}
case kindTCP6.family:
if len(buf) > 0 {
pmibTCP6Table = (*mibTCP6TableOwnerPid)(unsafe.Pointer(&buf[0]))
p = uintptr(unsafe.Pointer(pmibTCP6Table))
} else {
p = uintptr(unsafe.Pointer(pmibTCP6Table))
}
}
return p
}
func getTableInfo(filename string, table interface{}) (index, step, length int) {
switch filename {
case kindTCP4.filename:
index = int(unsafe.Sizeof(table.(pmibTCPTableOwnerPidAll).DwNumEntries))
step = int(unsafe.Sizeof(table.(pmibTCPTableOwnerPidAll).Table))
length = int(table.(pmibTCPTableOwnerPidAll).DwNumEntries)
case kindTCP6.filename:
index = int(unsafe.Sizeof(table.(pmibTCP6TableOwnerPidAll).DwNumEntries))
step = int(unsafe.Sizeof(table.(pmibTCP6TableOwnerPidAll).Table))
length = int(table.(pmibTCP6TableOwnerPidAll).DwNumEntries)
case kindUDP4.filename:
index = int(unsafe.Sizeof(table.(pmibUDPTableOwnerPid).DwNumEntries))
step = int(unsafe.Sizeof(table.(pmibUDPTableOwnerPid).Table))
length = int(table.(pmibUDPTableOwnerPid).DwNumEntries)
case kindUDP6.filename:
index = int(unsafe.Sizeof(table.(pmibUDP6TableOwnerPid).DwNumEntries))
step = int(unsafe.Sizeof(table.(pmibUDP6TableOwnerPid).Table))
length = int(table.(pmibUDP6TableOwnerPid).DwNumEntries)
}
return
}
func getTCPConnections(family uint32) ([]ConnectionStat, error) {
var (
p uintptr
buf []byte
size uint32
pmibTCPTable pmibTCPTableOwnerPidAll
pmibTCP6Table pmibTCP6TableOwnerPidAll
)
if family == 0 {
return nil, fmt.Errorf("faimly must be required")
}
for {
switch family {
case kindTCP4.family:
if len(buf) > 0 {
pmibTCPTable = (*mibTCPTableOwnerPid)(unsafe.Pointer(&buf[0]))
p = uintptr(unsafe.Pointer(pmibTCPTable))
} else {
p = uintptr(unsafe.Pointer(pmibTCPTable))
}
case kindTCP6.family:
if len(buf) > 0 {
pmibTCP6Table = (*mibTCP6TableOwnerPid)(unsafe.Pointer(&buf[0]))
p = uintptr(unsafe.Pointer(pmibTCP6Table))
} else {
p = uintptr(unsafe.Pointer(pmibTCP6Table))
}
}
err := getExtendedTcpTable(p,
&size,
true,
family,
tcpTableOwnerPidAll,
0)
if err == nil {
break
}
if err != windows.ERROR_INSUFFICIENT_BUFFER {
return nil, err
}
buf = make([]byte, size)
}
var (
index, step int
length int
)
stats := make([]ConnectionStat, 0)
switch family {
case kindTCP4.family:
index, step, length = getTableInfo(kindTCP4.filename, pmibTCPTable)
case kindTCP6.family:
index, step, length = getTableInfo(kindTCP6.filename, pmibTCP6Table)
}
if length == 0 {
return nil, nil
}
for i := 0; i < length; i++ {
switch family {
case kindTCP4.family:
mibs := (*mibTCPRowOwnerPid)(unsafe.Pointer(&buf[index]))
ns := mibs.convertToConnectionStat()
stats = append(stats, ns)
case kindTCP6.family:
mibs := (*mibTCP6RowOwnerPid)(unsafe.Pointer(&buf[index]))
ns := mibs.convertToConnectionStat()
stats = append(stats, ns)
}
index += step
}
return stats, nil
}
func getUDPConnections(family uint32) ([]ConnectionStat, error) {
var (
p uintptr
buf []byte
size uint32
pmibUDPTable pmibUDPTableOwnerPid
pmibUDP6Table pmibUDP6TableOwnerPid
)
if family == 0 {
return nil, fmt.Errorf("faimly must be required")
}
for {
switch family {
case kindUDP4.family:
if len(buf) > 0 {
pmibUDPTable = (*mibUDPTableOwnerPid)(unsafe.Pointer(&buf[0]))
p = uintptr(unsafe.Pointer(pmibUDPTable))
} else {
p = uintptr(unsafe.Pointer(pmibUDPTable))
}
case kindUDP6.family:
if len(buf) > 0 {
pmibUDP6Table = (*mibUDP6TableOwnerPid)(unsafe.Pointer(&buf[0]))
p = uintptr(unsafe.Pointer(pmibUDP6Table))
} else {
p = uintptr(unsafe.Pointer(pmibUDP6Table))
}
}
err := getExtendedUdpTable(
p,
&size,
true,
family,
udpTableOwnerPid,
0,
)
if err == nil {
break
}
if err != windows.ERROR_INSUFFICIENT_BUFFER {
return nil, err
}
buf = make([]byte, size)
}
var (
index, step, length int
)
stats := make([]ConnectionStat, 0)
switch family {
case kindUDP4.family:
index, step, length = getTableInfo(kindUDP4.filename, pmibUDPTable)
case kindUDP6.family:
index, step, length = getTableInfo(kindUDP6.filename, pmibUDP6Table)
}
if length == 0 {
return nil, nil
}
for i := 0; i < length; i++ {
switch family {
case kindUDP4.family:
mibs := (*mibUDPRowOwnerPid)(unsafe.Pointer(&buf[index]))
ns := mibs.convertToConnectionStat()
stats = append(stats, ns)
case kindUDP4.family:
mibs := (*mibUDP6RowOwnerPid)(unsafe.Pointer(&buf[index]))
ns := mibs.convertToConnectionStat()
stats = append(stats, ns)
}
index += step
}
return stats, nil
}
// tcpStatuses https://msdn.microsoft.com/en-us/library/windows/desktop/bb485761(v=vs.85).aspx
var tcpStatuses = map[mibTCPState]string{
1: "CLOSED",
2: "LISTEN",
3: "SYN_SENT",
4: "SYN_RECEIVED",
5: "ESTABLISHED",
6: "FIN_WAIT_1",
7: "FIN_WAIT_2",
8: "CLOSE_WAIT",
9: "CLOSING",
10: "LAST_ACK",
11: "TIME_WAIT",
12: "DELETE",
}
func getExtendedTcpTable(pTcpTable uintptr, pdwSize *uint32, bOrder bool, ulAf uint32, tableClass tcpTableClass, reserved uint32) (errcode error) {
r1, _, _ := syscall.Syscall6(procGetExtendedTCPTable.Addr(), 6, pTcpTable, uintptr(unsafe.Pointer(pdwSize)), getUintptrFromBool(bOrder), uintptr(ulAf), uintptr(tableClass), uintptr(reserved))
if r1 != 0 {
errcode = syscall.Errno(r1)
}
return
}
func getExtendedUdpTable(pUdpTable uintptr, pdwSize *uint32, bOrder bool, ulAf uint32, tableClass udpTableClass, reserved uint32) (errcode error) {
r1, _, _ := syscall.Syscall6(procGetExtendedUDPTable.Addr(), 6, pUdpTable, uintptr(unsafe.Pointer(pdwSize)), getUintptrFromBool(bOrder), uintptr(ulAf), uintptr(tableClass), uintptr(reserved))
if r1 != 0 {
errcode = syscall.Errno(r1)
}
return
}
func getUintptrFromBool(b bool) uintptr {
if b {
return 1
}
return 0
}
const anySize = 1
// type MIB_TCP_STATE int32
type mibTCPState int32
type tcpTableClass int32
const (
tcpTableBasicListener tcpTableClass = iota
tcpTableBasicConnections
tcpTableBasicAll
tcpTableOwnerPidListener
tcpTableOwnerPidConnections
tcpTableOwnerPidAll
tcpTableOwnerModuleListener
tcpTableOwnerModuleConnections
tcpTableOwnerModuleAll
)
type udpTableClass int32
const (
udpTableBasic udpTableClass = iota
udpTableOwnerPid
udpTableOwnerModule
)
// TCP
type mibTCPRowOwnerPid struct {
DwState uint32
DwLocalAddr uint32
DwLocalPort uint32
DwRemoteAddr uint32
DwRemotePort uint32
DwOwningPid uint32
}
func (m *mibTCPRowOwnerPid) convertToConnectionStat() ConnectionStat {
ns := ConnectionStat{
Family: kindTCP4.family,
Type: kindTCP4.sockType,
Laddr: Addr{
IP: parseIPv4HexString(m.DwLocalAddr),
Port: uint32(decodePort(m.DwLocalPort)),
},
Raddr: Addr{
IP: parseIPv4HexString(m.DwRemoteAddr),
Port: uint32(decodePort(m.DwRemotePort)),
},
Pid: int32(m.DwOwningPid),
Status: tcpStatuses[mibTCPState(m.DwState)],
}
return ns
}
type mibTCPTableOwnerPid struct {
DwNumEntries uint32
Table [anySize]mibTCPRowOwnerPid
}
type mibTCP6RowOwnerPid struct {
UcLocalAddr [16]byte
DwLocalScopeId uint32
DwLocalPort uint32
UcRemoteAddr [16]byte
DwRemoteScopeId uint32
DwRemotePort uint32
DwState uint32
DwOwningPid uint32
}
func (m *mibTCP6RowOwnerPid) convertToConnectionStat() ConnectionStat {
ns := ConnectionStat{
Family: kindTCP6.family,
Type: kindTCP6.sockType,
Laddr: Addr{
IP: parseIPv6HexString(m.UcLocalAddr),
Port: uint32(decodePort(m.DwLocalPort)),
},
Raddr: Addr{
IP: parseIPv6HexString(m.UcRemoteAddr),
Port: uint32(decodePort(m.DwRemotePort)),
},
Pid: int32(m.DwOwningPid),
Status: tcpStatuses[mibTCPState(m.DwState)],
}
return ns
}
type mibTCP6TableOwnerPid struct {
DwNumEntries uint32
Table [anySize]mibTCP6RowOwnerPid
}
type pmibTCPTableOwnerPidAll *mibTCPTableOwnerPid
type pmibTCP6TableOwnerPidAll *mibTCP6TableOwnerPid
// UDP
type mibUDPRowOwnerPid struct {
DwLocalAddr uint32
DwLocalPort uint32
DwOwningPid uint32
}
func (m *mibUDPRowOwnerPid) convertToConnectionStat() ConnectionStat {
ns := ConnectionStat{
Family: kindUDP4.family,
Type: kindUDP4.sockType,
Laddr: Addr{
IP: parseIPv4HexString(m.DwLocalAddr),
Port: uint32(decodePort(m.DwLocalPort)),
},
Pid: int32(m.DwOwningPid),
}
return ns
}
type mibUDPTableOwnerPid struct {
DwNumEntries uint32
Table [anySize]mibUDPRowOwnerPid
}
type mibUDP6RowOwnerPid struct {
UcLocalAddr [16]byte
DwLocalScopeId uint32
DwLocalPort uint32
DwOwningPid uint32
}
func (m *mibUDP6RowOwnerPid) convertToConnectionStat() ConnectionStat {
ns := ConnectionStat{
Family: kindUDP6.family,
Type: kindUDP6.sockType,
Laddr: Addr{
IP: parseIPv6HexString(m.UcLocalAddr),
Port: uint32(decodePort(m.DwLocalPort)),
},
Pid: int32(m.DwOwningPid),
}
return ns
}
type mibUDP6TableOwnerPid struct {
DwNumEntries uint32
Table [anySize]mibUDP6RowOwnerPid
}
type pmibUDPTableOwnerPid *mibUDPTableOwnerPid
type pmibUDP6TableOwnerPid *mibUDP6TableOwnerPid
func decodePort(port uint32) uint16 {
return syscall.Ntohs(uint16(port))
}
func parseIPv4HexString(addr uint32) string {
return fmt.Sprintf("%d.%d.%d.%d", addr&255, addr>>8&255, addr>>16&255, addr>>24&255)
}
func parseIPv6HexString(addr [16]byte) string {
var ret [16]byte
for i := 0; i < 16; i++ {
ret[i] = uint8(addr[i])
}
// convert []byte to net.IP
ip := net.IP(ret[:])
return ip.String()
}

View File

@@ -3,7 +3,9 @@ package process
import (
"context"
"encoding/json"
"errors"
"runtime"
"sort"
"time"
"github.com/shirou/gopsutil/cpu"
@@ -11,11 +13,11 @@ import (
"github.com/shirou/gopsutil/mem"
)
var invoke common.Invoker
func init() {
invoke = common.Invoke{}
}
var (
invoke common.Invoker = common.Invoke{}
ErrorNoChildren = errors.New("process does not have children")
ErrorProcessNotRunning = errors.New("process does not exist")
)
type Process struct {
Pid int32 `json:"pid"`
@@ -28,6 +30,7 @@ type Process struct {
numThreads int32
memInfo *MemoryInfoStat
sigInfo *SignalInfoStat
createTime int64
lastCPUTimes *cpu.TimesStat
lastCPUTime time.Time
@@ -43,6 +46,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 +80,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 (
@@ -127,23 +138,50 @@ func (p NumCtxSwitchesStat) String() string {
return string(s)
}
// Pids returns a slice of process ID list which are running now.
func Pids() ([]int32, error) {
return PidsWithContext(context.Background())
}
func PidsWithContext(ctx context.Context) ([]int32, error) {
pids, err := pidsWithContext(ctx)
sort.Slice(pids, func(i, j int) bool { return pids[i] < pids[j] })
return pids, err
}
// NewProcess creates a new Process instance, it only stores the pid and
// checks that the process exists. Other method on Process can be used
// to get more information about the process. An error will be returned
// if the process does not exist.
func NewProcess(pid int32) (*Process, error) {
p := &Process{Pid: pid}
exists, err := PidExists(pid)
if err != nil {
return p, err
}
if !exists {
return p, ErrorProcessNotRunning
}
p.CreateTime()
return p, nil
}
func PidExists(pid int32) (bool, error) {
return PidExistsWithContext(context.Background(), pid)
}
func PidExistsWithContext(ctx context.Context, pid int32) (bool, error) {
pids, err := Pids()
// 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
}
for _, i := range pids {
if i == pid {
return true, err
}
}
return false, err
return !fg, err
}
// If interval is 0, return difference from last call(non-blocking).
@@ -185,6 +223,41 @@ func (p *Process) PercentWithContext(ctx context.Context, interval time.Duration
return ret, nil
}
// IsRunning returns whether the process is still running or not.
func (p *Process) IsRunning() (bool, error) {
return p.IsRunningWithContext(context.Background())
}
func (p *Process) IsRunningWithContext(ctx context.Context) (bool, error) {
createTime, err := p.CreateTimeWithContext(ctx)
if err != nil {
return false, err
}
p2, err := NewProcess(p.Pid)
if err == ErrorProcessNotRunning {
return false, nil
}
createTime2, err := p2.CreateTimeWithContext(ctx)
if err != nil {
return false, err
}
return createTime == createTime2, nil
}
// CreateTime returns created time of the process in milliseconds since the epoch, in UTC.
func (p *Process) CreateTime() (int64, error) {
return p.CreateTimeWithContext(context.Background())
}
func (p *Process) CreateTimeWithContext(ctx context.Context) (int64, error) {
if p.createTime != 0 {
return p.createTime, nil
}
createTime, err := p.createTimeWithContext(ctx)
p.createTime = createTime
return p.createTime, err
}
func calculatePercent(t1, t2 *cpu.TimesStat, delta float64, numcpu int) float64 {
if delta == 0 {
return 0

View File

@@ -8,6 +8,7 @@ import (
"encoding/binary"
"fmt"
"os/exec"
"path/filepath"
"strconv"
"strings"
"time"
@@ -44,14 +45,10 @@ type MemoryInfoExStat struct {
type MemoryMapsStat struct {
}
func Pids() ([]int32, error) {
return PidsWithContext(context.Background())
}
func PidsWithContext(ctx context.Context) ([]int32, error) {
func pidsWithContext(ctx context.Context) ([]int32, error) {
var ret []int32
pids, err := callPs("pid", 0, false)
pids, err := callPsWithContext(ctx, "pid", 0, false)
if err != nil {
return ret, err
}
@@ -72,7 +69,7 @@ func (p *Process) Ppid() (int32, error) {
}
func (p *Process) PpidWithContext(ctx context.Context) (int32, error) {
r, err := callPs("ppid", p.Pid, false)
r, err := callPsWithContext(ctx, "ppid", p.Pid, false)
if err != nil {
return 0, err
}
@@ -93,8 +90,24 @@ func (p *Process) NameWithContext(ctx context.Context) (string, error) {
if err != nil {
return "", err
}
name := common.IntToString(k.Proc.P_comm[:])
return common.IntToString(k.Proc.P_comm[:]), nil
if len(name) >= 15 {
cmdlineSlice, err := p.CmdlineSliceWithContext(ctx)
if err != nil {
return "", err
}
if len(cmdlineSlice) > 0 {
extendedName := filepath.Base(cmdlineSlice[0])
if strings.HasPrefix(extendedName, p.name) {
name = extendedName
} else {
name = cmdlineSlice[0]
}
}
}
return name, nil
}
func (p *Process) Tgid() (int32, error) {
return 0, common.ErrNotImplementedError
@@ -103,37 +116,6 @@ func (p *Process) Exe() (string, error) {
return p.ExeWithContext(context.Background())
}
func (p *Process) ExeWithContext(ctx context.Context) (string, error) {
lsof_bin, err := exec.LookPath("lsof")
if err != nil {
return "", err
}
awk_bin, err := exec.LookPath("awk")
if err != nil {
return "", err
}
sed_bin, err := exec.LookPath("sed")
if err != nil {
return "", err
}
lsof := exec.Command(lsof_bin, "-p", strconv.Itoa(int(p.Pid)), "-Fpfn")
awk := exec.Command(awk_bin, "NR==5{print}")
sed := exec.Command(sed_bin, "s/n\\//\\//")
output, _, err := common.Pipeline(lsof, awk, sed)
if err != nil {
return "", err
}
ret := strings.TrimSpace(string(output))
return ret, nil
}
// Cmdline returns the command line arguments of the process as a string with
// each argument separated by 0x20 ascii character.
func (p *Process) Cmdline() (string, error) {
@@ -141,7 +123,7 @@ func (p *Process) Cmdline() (string, error) {
}
func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) {
r, err := callPs("command", p.Pid, false)
r, err := callPsWithContext(ctx, "command", p.Pid, false)
if err != nil {
return "", err
}
@@ -158,18 +140,15 @@ func (p *Process) CmdlineSlice() ([]string, error) {
}
func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) {
r, err := callPs("command", p.Pid, false)
r, err := callPsWithContext(ctx, "command", p.Pid, false)
if err != nil {
return nil, err
}
return r[0], err
}
func (p *Process) CreateTime() (int64, error) {
return p.CreateTimeWithContext(context.Background())
}
func (p *Process) CreateTimeWithContext(ctx context.Context) (int64, error) {
r, err := callPs("etime", p.Pid, false)
func (p *Process) createTimeWithContext(ctx context.Context) (int64, error) {
r, err := callPsWithContext(ctx, "etime", p.Pid, false)
if err != nil {
return 0, err
}
@@ -210,7 +189,7 @@ func (p *Process) Parent() (*Process, error) {
}
func (p *Process) ParentWithContext(ctx context.Context) (*Process, error) {
rr, err := common.CallLsof(invoke, p.Pid, "-FR")
rr, err := common.CallLsofWithContext(ctx, invoke, p.Pid, "-FR")
if err != nil {
return nil, err
}
@@ -232,13 +211,32 @@ func (p *Process) Status() (string, error) {
}
func (p *Process) StatusWithContext(ctx context.Context) (string, error) {
r, err := callPs("state", p.Pid, false)
r, err := callPsWithContext(ctx, "state", p.Pid, false)
if err != nil {
return "", err
}
return r[0][0], err
return r[0][0][0:1], 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())
}
@@ -350,7 +348,7 @@ func (p *Process) NumThreads() (int32, error) {
}
func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) {
r, err := callPs("utime,stime", p.Pid, true)
r, err := callPsWithContext(ctx, "utime,stime", p.Pid, true)
if err != nil {
return 0, err
}
@@ -370,12 +368,32 @@ func convertCPUTimes(s string) (ret float64, err error) {
var _tmp string
if strings.Contains(s, ":") {
_t := strings.Split(s, ":")
hour, err := strconv.Atoi(_t[0])
if err != nil {
return ret, err
switch len(_t) {
case 3:
hour, err := strconv.Atoi(_t[0])
if err != nil {
return ret, err
}
t += hour * 60 * 60 * ClockTicks
mins, err := strconv.Atoi(_t[1])
if err != nil {
return ret, err
}
t += mins * 60 * ClockTicks
_tmp = _t[2]
case 2:
mins, err := strconv.Atoi(_t[0])
if err != nil {
return ret, err
}
t += mins * 60 * ClockTicks
_tmp = _t[1]
case 1, 0:
_tmp = s
default:
return ret, fmt.Errorf("wrong cpu time string")
}
t += hour * 60 * 100
_tmp = _t[1]
} else {
_tmp = s
}
@@ -385,7 +403,7 @@ func convertCPUTimes(s string) (ret float64, err error) {
return ret, err
}
h, err := strconv.Atoi(_t[0])
t += h * 100
t += h * ClockTicks
h, err = strconv.Atoi(_t[1])
t += h
return float64(t) / ClockTicks, nil
@@ -395,7 +413,7 @@ func (p *Process) Times() (*cpu.TimesStat, error) {
}
func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) {
r, err := callPs("utime,stime", p.Pid, false)
r, err := callPsWithContext(ctx, "utime,stime", p.Pid, false)
if err != nil {
return nil, err
@@ -429,7 +447,7 @@ func (p *Process) MemoryInfo() (*MemoryInfoStat, error) {
}
func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, error) {
r, err := callPs("rss,vsize,pagein", p.Pid, false)
r, err := callPsWithContext(ctx, "rss,vsize,pagein", p.Pid, false)
if err != nil {
return nil, err
}
@@ -462,12 +480,20 @@ 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) {
pids, err := common.CallPgrep(invoke, p.Pid)
pids, err := common.CallPgrepWithContext(ctx, invoke, p.Pid)
if err != nil {
return nil, err
}
@@ -498,6 +524,15 @@ func (p *Process) ConnectionsWithContext(ctx context.Context) ([]net.ConnectionS
return net.ConnectionsPid("all", p.Pid)
}
// Connections returns a slice of net.ConnectionStat used by the process at most `max`
func (p *Process) ConnectionsMax(max int) ([]net.ConnectionStat, error) {
return p.ConnectionsMaxWithContext(context.Background(), max)
}
func (p *Process) ConnectionsMaxWithContext(ctx context.Context, max int) ([]net.ConnectionStat, error) {
return net.ConnectionsPidMax("all", p.Pid, max)
}
func (p *Process) NetIOCounters(pernic bool) ([]net.IOCountersStat, error) {
return p.NetIOCountersWithContext(context.Background(), pernic)
}
@@ -506,13 +541,6 @@ func (p *Process) NetIOCountersWithContext(ctx context.Context, pernic bool) ([]
return nil, common.ErrNotImplementedError
}
func (p *Process) IsRunning() (bool, error) {
return p.IsRunningWithContext(context.Background())
}
func (p *Process) IsRunningWithContext(ctx context.Context) (bool, error) {
return true, common.ErrNotImplementedError
}
func (p *Process) MemoryMaps(grouped bool) (*[]MemoryMapsStat, error) {
return p.MemoryMapsWithContext(context.Background(), grouped)
}
@@ -527,34 +555,22 @@ func Processes() ([]*Process, error) {
}
func ProcessesWithContext(ctx context.Context) ([]*Process, error) {
results := []*Process{}
out := []*Process{}
mib := []int32{CTLKern, KernProc, KernProcAll, 0}
buf, length, err := common.CallSyscall(mib)
pids, err := PidsWithContext(ctx)
if err != nil {
return results, err
return out, err
}
// get kinfo_proc size
k := KinfoProc{}
procinfoLen := int(unsafe.Sizeof(k))
count := int(length / uint64(procinfoLen))
// parse buf to procs
for i := 0; i < count; i++ {
b := buf[i*procinfoLen : i*procinfoLen+procinfoLen]
k, err := parseKinfoProc(b)
for _, pid := range pids {
p, err := NewProcess(pid)
if err != nil {
continue
}
p, err := NewProcess(int32(k.Proc.P_pid))
if err != nil {
continue
}
results = append(results, p)
out = append(out, p)
}
return results, nil
return out, nil
}
func parseKinfoProc(buf []byte) (KinfoProc, error) {
@@ -599,17 +615,11 @@ func (p *Process) getKProcWithContext(ctx context.Context) (*KinfoProc, error) {
return &k, nil
}
func NewProcess(pid int32) (*Process, error) {
p := &Process{Pid: pid}
return p, nil
}
// call ps command.
// Return value deletes Header line(you must not input wrong arg).
// And splited by Space. Caller have responsibility to manage.
// If passed arg pid is 0, get information from all process.
func callPs(arg string, pid int32, threadOption bool) ([][]string, error) {
func callPsWithContext(ctx context.Context, arg string, pid int32, threadOption bool) ([][]string, error) {
bin, err := exec.LookPath("ps")
if err != nil {
return [][]string{}, err
@@ -623,7 +633,7 @@ func callPs(arg string, pid int32, threadOption bool) ([][]string, error) {
} else {
cmd = []string{"-x", "-o", arg, "-p", strconv.Itoa(int(pid))}
}
out, err := invoke.Command(bin, cmd...)
out, err := invoke.CommandWithContext(ctx, bin, cmd...)
if err != nil {
return [][]string{}, err
}

View File

@@ -0,0 +1,30 @@
// +build darwin
// +build cgo
package process
// #include <stdlib.h>
// #include <libproc.h>
import "C"
import (
"context"
"fmt"
"unsafe"
)
func (p *Process) ExeWithContext(ctx context.Context) (string, error) {
var c C.char // need a var for unsafe.Sizeof need a var
const bufsize = C.PROC_PIDPATHINFO_MAXSIZE * unsafe.Sizeof(c)
buffer := (*C.char)(C.malloc(C.size_t(bufsize)))
defer C.free(unsafe.Pointer(buffer))
ret, err := C.proc_pidpath(C.int(p.Pid), unsafe.Pointer(buffer), C.uint32_t(bufsize))
if err != nil {
return "", err
}
if ret <= 0 {
return "", fmt.Errorf("unknown error: proc_pidpath returned %d", ret)
}
return C.GoString(buffer), nil
}

View File

@@ -0,0 +1,34 @@
// +build darwin
// +build !cgo
package process
import (
"context"
"fmt"
"os/exec"
"strconv"
"strings"
)
func (p *Process) ExeWithContext(ctx context.Context) (string, error) {
lsof_bin, err := exec.LookPath("lsof")
if err != nil {
return "", err
}
out, err := invoke.CommandWithContext(ctx, lsof_bin, "-p", strconv.Itoa(int(p.Pid)), "-Fpfn")
if err != nil {
return "", fmt.Errorf("bad call to lsof: %s", err)
}
txtFound := 0
lines := strings.Split(string(out), "\n")
for i := 1; i < len(lines); i += 2 {
if lines[i] == "ftxt" {
txtFound++
if txtFound == 2 {
return lines[i-1][1:], nil
}
}
}
return "", fmt.Errorf("missing txt data returned by lsof")
}

View File

@@ -28,18 +28,33 @@ type MemoryMapsStat struct {
type MemoryInfoExStat struct {
}
func Pids() ([]int32, error) {
return PidsWithContext(context.Background())
}
func PidsWithContext(ctx context.Context) ([]int32, error) {
func pidsWithContext(ctx context.Context) ([]int32, error) {
return []int32{}, common.ErrNotImplementedError
}
func NewProcess(pid int32) (*Process, error) {
func Processes() ([]*Process, error) {
return nil, common.ErrNotImplementedError
}
func ProcessesWithContext(ctx context.Context) ([]*Process, error) {
return nil, common.ErrNotImplementedError
}
func PidExistsWithContext(ctx context.Context, pid int32) (bool, error) {
pids, err := PidsWithContext(ctx)
if err != nil {
return false, err
}
for _, i := range pids {
if i == pid {
return true, err
}
}
return false, err
}
func (p *Process) Ppid() (int32, error) {
return p.PpidWithContext(context.Background())
}
@@ -78,11 +93,8 @@ func (p *Process) CmdlineSlice() ([]string, error) {
func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) {
return []string{}, common.ErrNotImplementedError
}
func (p *Process) CreateTime() (int64, error) {
return p.CreateTimeWithContext(context.Background())
}
func (p *Process) CreateTimeWithContext(ctx context.Context) (int64, error) {
func (p *Process) createTimeWithContext(ctx context.Context) (int64, error) {
return 0, common.ErrNotImplementedError
}
func (p *Process) Cwd() (string, error) {
@@ -106,6 +118,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())
}
@@ -218,6 +237,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())
}
@@ -239,6 +264,15 @@ func (p *Process) Connections() ([]net.ConnectionStat, error) {
func (p *Process) ConnectionsWithContext(ctx context.Context) ([]net.ConnectionStat, error) {
return []net.ConnectionStat{}, common.ErrNotImplementedError
}
func (p *Process) ConnectionsMax(max int) ([]net.ConnectionStat, error) {
return p.ConnectionsMaxWithContext(context.Background(), max)
}
func (p *Process) ConnectionsMaxWithContext(ctx context.Context, max int) ([]net.ConnectionStat, error) {
return []net.ConnectionStat{}, common.ErrNotImplementedError
}
func (p *Process) NetIOCounters(pernic bool) ([]net.IOCountersStat, error) {
return p.NetIOCountersWithContext(context.Background(), pernic)
}
@@ -246,13 +280,7 @@ func (p *Process) NetIOCounters(pernic bool) ([]net.IOCountersStat, error) {
func (p *Process) NetIOCountersWithContext(ctx context.Context, pernic bool) ([]net.IOCountersStat, error) {
return []net.IOCountersStat{}, common.ErrNotImplementedError
}
func (p *Process) IsRunning() (bool, error) {
return p.IsRunningWithContext(context.Background())
}
func (p *Process) IsRunningWithContext(ctx context.Context) (bool, error) {
return true, common.ErrNotImplementedError
}
func (p *Process) MemoryMaps(grouped bool) (*[]MemoryMapsStat, error) {
return p.MemoryMapsWithContext(context.Background(), grouped)
}

View File

@@ -6,6 +6,9 @@ import (
"bytes"
"context"
"encoding/binary"
"os/exec"
"path/filepath"
"strconv"
"strings"
cpu "github.com/shirou/gopsutil/cpu"
@@ -21,11 +24,7 @@ type MemoryInfoExStat struct {
type MemoryMapsStat struct {
}
func Pids() ([]int32, error) {
return PidsWithContext(context.Background())
}
func PidsWithContext(ctx context.Context) ([]int32, error) {
func pidsWithContext(ctx context.Context) ([]int32, error) {
var ret []int32
procs, err := Processes()
if err != nil {
@@ -60,8 +59,24 @@ func (p *Process) NameWithContext(ctx context.Context) (string, error) {
if err != nil {
return "", err
}
name := common.IntToString(k.Comm[:])
return common.IntToString(k.Comm[:]), nil
if len(name) >= 15 {
cmdlineSlice, err := p.CmdlineSliceWithContext(ctx)
if err != nil {
return "", err
}
if len(cmdlineSlice) > 0 {
extendedName := filepath.Base(cmdlineSlice[0])
if strings.HasPrefix(extendedName, p.name) {
name = extendedName
} else {
name = cmdlineSlice[0]
}
}
}
return name, nil
}
func (p *Process) Tgid() (int32, error) {
return 0, common.ErrNotImplementedError
@@ -118,11 +133,8 @@ func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error)
return strParts, nil
}
func (p *Process) CreateTime() (int64, error) {
return p.CreateTimeWithContext(context.Background())
}
func (p *Process) CreateTimeWithContext(ctx context.Context) (int64, error) {
func (p *Process) createTimeWithContext(ctx context.Context) (int64, error) {
return 0, common.ErrNotImplementedError
}
func (p *Process) Cwd() (string, error) {
@@ -168,6 +180,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,12 +381,20 @@ 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) {
pids, err := common.CallPgrep(invoke, p.Pid)
pids, err := common.CallPgrepWithContext(ctx, invoke, p.Pid)
if err != nil {
return nil, err
}
@@ -386,6 +425,15 @@ func (p *Process) ConnectionsWithContext(ctx context.Context) ([]net.ConnectionS
return nil, common.ErrNotImplementedError
}
// Connections returns a slice of net.ConnectionStat used by the process at most `max`
func (p *Process) ConnectionsMax(max int) ([]net.ConnectionStat, error) {
return p.ConnectionsMaxWithContext(context.Background(), max)
}
func (p *Process) ConnectionsMaxWithContext(ctx context.Context, max int) ([]net.ConnectionStat, error) {
return []net.ConnectionStat{}, common.ErrNotImplementedError
}
func (p *Process) NetIOCounters(pernic bool) ([]net.IOCountersStat, error) {
return p.NetIOCountersWithContext(context.Background(), pernic)
}
@@ -394,13 +442,6 @@ func (p *Process) NetIOCountersWithContext(ctx context.Context, pernic bool) ([]
return nil, common.ErrNotImplementedError
}
func (p *Process) IsRunning() (bool, error) {
return p.IsRunningWithContext(context.Background())
}
func (p *Process) IsRunningWithContext(ctx context.Context) (bool, error) {
return true, common.ErrNotImplementedError
}
func (p *Process) MemoryMaps(grouped bool) (*[]MemoryMapsStat, error) {
return p.MemoryMapsWithContext(context.Background(), grouped)
}
@@ -472,9 +513,3 @@ func (p *Process) getKProcWithContext(ctx context.Context) (*KinfoProc, error) {
}
return &k, nil
}
func NewProcess(pid int32) (*Process, error) {
p := &Process{Pid: pid}
return p, nil
}

View File

@@ -0,0 +1,201 @@
// +build freebsd
// +build arm64
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
// cgo -godefs process/types_freebsd.go
package process
const (
CTLKern = 1
KernProc = 14
KernProcPID = 1
KernProcProc = 8
KernProcPathname = 12
KernProcArgs = 7
)
const (
sizeofPtr = 0x8
sizeofShort = 0x2
sizeofInt = 0x4
sizeofLong = 0x8
sizeofLongLong = 0x8
)
const (
sizeOfKinfoVmentry = 0x488
sizeOfKinfoProc = 0x440
)
const (
SIDL = 1
SRUN = 2
SSLEEP = 3
SSTOP = 4
SZOMB = 5
SWAIT = 6
SLOCK = 7
)
type (
_C_short int16
_C_int int32
_C_long int64
_C_long_long int64
)
type Timespec struct {
Sec int64
Nsec int64
}
type Timeval struct {
Sec int64
Usec int64
}
type Rusage struct {
Utime Timeval
Stime Timeval
Maxrss int64
Ixrss int64
Idrss int64
Isrss int64
Minflt int64
Majflt int64
Nswap int64
Inblock int64
Oublock int64
Msgsnd int64
Msgrcv int64
Nsignals int64
Nvcsw int64
Nivcsw int64
}
type Rlimit struct {
Cur int64
Max int64
}
type KinfoProc struct {
Structsize int32
Layout int32
Args *int64 /* pargs */
Paddr *int64 /* proc */
Addr *int64 /* user */
Tracep *int64 /* vnode */
Textvp *int64 /* vnode */
Fd *int64 /* filedesc */
Vmspace *int64 /* vmspace */
Wchan *byte
Pid int32
Ppid int32
Pgid int32
Tpgid int32
Sid int32
Tsid int32
Jobc int16
Spare_short1 int16
Tdev_freebsd11 uint32
Siglist [16]byte /* sigset */
Sigmask [16]byte /* sigset */
Sigignore [16]byte /* sigset */
Sigcatch [16]byte /* sigset */
Uid uint32
Ruid uint32
Svuid uint32
Rgid uint32
Svgid uint32
Ngroups int16
Spare_short2 int16
Groups [16]uint32
Size uint64
Rssize int64
Swrss int64
Tsize int64
Dsize int64
Ssize int64
Xstat uint16
Acflag uint16
Pctcpu uint32
Estcpu uint32
Slptime uint32
Swtime uint32
Cow uint32
Runtime uint64
Start Timeval
Childtime Timeval
Flag int64
Kiflag int64
Traceflag int32
Stat uint8
Nice int8
Lock uint8
Rqindex uint8
Oncpu_old uint8
Lastcpu_old uint8
Tdname [17]uint8
Wmesg [9]uint8
Login [18]uint8
Lockname [9]uint8
Comm [20]int8
Emul [17]uint8
Loginclass [18]uint8
Moretdname [4]uint8
Sparestrings [46]uint8
Spareints [2]int32
Tdev uint64
Oncpu int32
Lastcpu int32
Tracer int32
Flag2 int32
Fibnum int32
Cr_flags uint32
Jid int32
Numthreads int32
Tid int32
Pri Priority
Rusage Rusage
Rusage_ch Rusage
Pcb *int64 /* pcb */
Kstack *byte
Udata *byte
Tdaddr *int64 /* thread */
Spareptrs [6]*byte
Sparelongs [12]int64
Sflag int64
Tdflags int64
}
type Priority struct {
Class uint8
Level uint8
Native uint8
User uint8
}
type KinfoVmentry struct {
Structsize int32
Type int32
Start uint64
End uint64
Offset uint64
Vn_fileid uint64
Vn_fsid_freebsd11 uint32
Flags int32
Resident int32
Private_resident int32
Protection int32
Ref_count int32
Shadow_count int32
Vn_type int32
Vn_size uint64
Vn_rdev_freebsd11 uint32
Vn_mode uint16
Status uint16
Vn_fsid uint64
Vn_rdev uint64
X_kve_ispare [8]int32
Path [1024]uint8
}

View File

@@ -7,7 +7,6 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"math"
@@ -17,16 +16,12 @@ import (
"strings"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/host"
"github.com/shirou/gopsutil/internal/common"
"github.com/shirou/gopsutil/net"
"golang.org/x/sys/unix"
)
var (
ErrorNoChildren = errors.New("process does not have children")
PageSize = uint64(os.Getpagesize())
)
var PageSize = uint64(os.Getpagesize())
const (
PrioProcess = 0 // linux/resource.h
@@ -69,26 +64,13 @@ func (m MemoryMapsStat) String() string {
return string(s)
}
// NewProcess creates a new Process instance, it only stores the pid and
// checks that the process exists. Other method on Process can be used
// to get more information about the process. An error will be returned
// if the process does not exist.
func NewProcess(pid int32) (*Process, error) {
p := &Process{
Pid: int32(pid),
}
file, err := os.Open(common.HostProc(strconv.Itoa(int(p.Pid))))
defer file.Close()
return p, err
}
// Ppid returns Parent Process ID of the process.
func (p *Process) Ppid() (int32, error) {
return p.PpidWithContext(context.Background())
}
func (p *Process) PpidWithContext(ctx context.Context) (int32, error) {
_, ppid, _, _, _, _, err := p.fillFromStat()
_, ppid, _, _, _, _, _, err := p.fillFromStatWithContext(ctx)
if err != nil {
return -1, err
}
@@ -102,7 +84,7 @@ func (p *Process) Name() (string, error) {
func (p *Process) NameWithContext(ctx context.Context) (string, error) {
if p.name == "" {
if err := p.fillFromStatus(); err != nil {
if err := p.fillFromStatusWithContext(ctx); err != nil {
return "", err
}
}
@@ -112,7 +94,7 @@ func (p *Process) NameWithContext(ctx context.Context) (string, error) {
// Tgid returns tgid, a Linux-synonym for user-space Pid
func (p *Process) Tgid() (int32, error) {
if p.tgid == 0 {
if err := p.fillFromStatus(); err != nil {
if err := p.fillFromStatusWithContext(context.Background()); err != nil {
return 0, err
}
}
@@ -125,7 +107,7 @@ func (p *Process) Exe() (string, error) {
}
func (p *Process) ExeWithContext(ctx context.Context) (string, error) {
return p.fillFromExe()
return p.fillFromExeWithContext(ctx)
}
// Cmdline returns the command line arguments of the process as a string with
@@ -135,7 +117,7 @@ func (p *Process) Cmdline() (string, error) {
}
func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) {
return p.fillFromCmdline()
return p.fillFromCmdlineWithContext(ctx)
}
// CmdlineSlice returns the command line arguments of the process as a slice with each
@@ -145,16 +127,11 @@ func (p *Process) CmdlineSlice() ([]string, error) {
}
func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) {
return p.fillSliceFromCmdline()
return p.fillSliceFromCmdlineWithContext(ctx)
}
// CreateTime returns created time of the process in milliseconds since the epoch, in UTC.
func (p *Process) CreateTime() (int64, error) {
return p.CreateTimeWithContext(context.Background())
}
func (p *Process) CreateTimeWithContext(ctx context.Context) (int64, error) {
_, _, _, createTime, _, _, err := p.fillFromStat()
func (p *Process) createTimeWithContext(ctx context.Context) (int64, error) {
_, _, _, createTime, _, _, _, err := p.fillFromStatWithContext(ctx)
if err != nil {
return 0, err
}
@@ -167,7 +144,7 @@ func (p *Process) Cwd() (string, error) {
}
func (p *Process) CwdWithContext(ctx context.Context) (string, error) {
return p.fillFromCwd()
return p.fillFromCwdWithContext(ctx)
}
// Parent returns parent Process of the process.
@@ -176,7 +153,7 @@ func (p *Process) Parent() (*Process, error) {
}
func (p *Process) ParentWithContext(ctx context.Context) (*Process, error) {
err := p.fillFromStatus()
err := p.fillFromStatusWithContext(ctx)
if err != nil {
return nil, err
}
@@ -190,26 +167,48 @@ func (p *Process) ParentWithContext(ctx context.Context) (*Process, error) {
// Return value could be one of these.
// R: Running S: Sleep T: Stop I: Idle
// Z: Zombie W: Wait L: Lock
// The charactor is same within all supported platforms.
// The character is same within all supported platforms.
func (p *Process) Status() (string, error) {
return p.StatusWithContext(context.Background())
}
func (p *Process) StatusWithContext(ctx context.Context) (string, error) {
err := p.fillFromStatus()
err := p.fillFromStatusWithContext(ctx)
if err != nil {
return "", err
}
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())
}
func (p *Process) UidsWithContext(ctx context.Context) ([]int32, error) {
err := p.fillFromStatus()
err := p.fillFromStatusWithContext(ctx)
if err != nil {
return []int32{}, err
}
@@ -222,7 +221,7 @@ func (p *Process) Gids() ([]int32, error) {
}
func (p *Process) GidsWithContext(ctx context.Context) ([]int32, error) {
err := p.fillFromStatus()
err := p.fillFromStatusWithContext(ctx)
if err != nil {
return []int32{}, err
}
@@ -235,7 +234,7 @@ func (p *Process) Terminal() (string, error) {
}
func (p *Process) TerminalWithContext(ctx context.Context) (string, error) {
t, _, _, _, _, _, err := p.fillFromStat()
t, _, _, _, _, _, _, err := p.fillFromStatWithContext(ctx)
if err != nil {
return "", err
}
@@ -254,7 +253,7 @@ func (p *Process) Nice() (int32, error) {
}
func (p *Process) NiceWithContext(ctx context.Context) (int32, error) {
_, _, _, _, _, nice, err := p.fillFromStat()
_, _, _, _, _, nice, _, err := p.fillFromStatWithContext(ctx)
if err != nil {
return 0, err
}
@@ -287,16 +286,16 @@ func (p *Process) RlimitUsage(gatherUsed bool) ([]RlimitStat, error) {
}
func (p *Process) RlimitUsageWithContext(ctx context.Context, gatherUsed bool) ([]RlimitStat, error) {
rlimits, err := p.fillFromLimits()
rlimits, err := p.fillFromLimitsWithContext(ctx)
if !gatherUsed || err != nil {
return rlimits, err
}
_, _, _, _, rtprio, nice, err := p.fillFromStat()
_, _, _, _, rtprio, nice, _, err := p.fillFromStatWithContext(ctx)
if err != nil {
return nil, err
}
if err := p.fillFromStatus(); err != nil {
if err := p.fillFromStatusWithContext(ctx); err != nil {
return nil, err
}
@@ -347,7 +346,7 @@ func (p *Process) IOCounters() (*IOCountersStat, error) {
}
func (p *Process) IOCountersWithContext(ctx context.Context) (*IOCountersStat, error) {
return p.fillFromIO()
return p.fillFromIOWithContext(ctx)
}
// NumCtxSwitches returns the number of the context switches of the process.
@@ -356,7 +355,7 @@ func (p *Process) NumCtxSwitches() (*NumCtxSwitchesStat, error) {
}
func (p *Process) NumCtxSwitchesWithContext(ctx context.Context) (*NumCtxSwitchesStat, error) {
err := p.fillFromStatus()
err := p.fillFromStatusWithContext(ctx)
if err != nil {
return nil, err
}
@@ -369,7 +368,7 @@ func (p *Process) NumFDs() (int32, error) {
}
func (p *Process) NumFDsWithContext(ctx context.Context) (int32, error) {
_, fnames, err := p.fillFromfdList()
_, fnames, err := p.fillFromfdListWithContext(ctx)
return int32(len(fnames)), err
}
@@ -379,7 +378,7 @@ func (p *Process) NumThreads() (int32, error) {
}
func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) {
err := p.fillFromStatus()
err := p.fillFromStatusWithContext(ctx)
if err != nil {
return 0, err
}
@@ -400,7 +399,7 @@ func (p *Process) ThreadsWithContext(ctx context.Context) (map[int32]*cpu.TimesS
}
for _, tid := range tids {
_, _, cpuTimes, _, _, _, err := p.fillFromTIDStat(tid)
_, _, cpuTimes, _, _, _, _, err := p.fillFromTIDStatWithContext(ctx, tid)
if err != nil {
return nil, err
}
@@ -416,7 +415,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.fillFromStatWithContext(ctx)
if err != nil {
return nil, err
}
@@ -440,7 +439,7 @@ func (p *Process) MemoryInfo() (*MemoryInfoStat, error) {
}
func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, error) {
meminfo, _, err := p.fillFromStatm()
meminfo, _, err := p.fillFromStatmWithContext(ctx)
if err != nil {
return nil, err
}
@@ -453,20 +452,34 @@ func (p *Process) MemoryInfoEx() (*MemoryInfoExStat, error) {
}
func (p *Process) MemoryInfoExWithContext(ctx context.Context) (*MemoryInfoExStat, error) {
_, memInfoEx, err := p.fillFromStatm()
_, memInfoEx, err := p.fillFromStatmWithContext(ctx)
if err != nil {
return nil, err
}
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.fillFromStatWithContext(ctx)
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())
}
func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) {
pids, err := common.CallPgrep(invoke, p.Pid)
pids, err := common.CallPgrepWithContext(ctx, invoke, p.Pid)
if err != nil {
if pids == nil || len(pids) == 0 {
return nil, ErrorNoChildren
@@ -491,7 +504,7 @@ func (p *Process) OpenFiles() ([]OpenFilesStat, error) {
}
func (p *Process) OpenFilesWithContext(ctx context.Context) ([]OpenFilesStat, error) {
_, ofs, err := p.fillFromfd()
_, ofs, err := p.fillFromfdWithContext(ctx)
if err != nil {
return nil, err
}
@@ -513,6 +526,15 @@ func (p *Process) ConnectionsWithContext(ctx context.Context) ([]net.ConnectionS
return net.ConnectionsPid("all", p.Pid)
}
// Connections returns a slice of net.ConnectionStat used by the process at most `max`
func (p *Process) ConnectionsMax(max int) ([]net.ConnectionStat, error) {
return p.ConnectionsMaxWithContext(context.Background(), max)
}
func (p *Process) ConnectionsMaxWithContext(ctx context.Context, max int) ([]net.ConnectionStat, error) {
return net.ConnectionsPidMax("all", p.Pid, max)
}
// NetIOCounters returns NetIOCounters of the process.
func (p *Process) NetIOCounters(pernic bool) ([]net.IOCountersStat, error) {
return p.NetIOCountersWithContext(context.Background(), pernic)
@@ -523,16 +545,6 @@ func (p *Process) NetIOCountersWithContext(ctx context.Context, pernic bool) ([]
return net.IOCountersByFile(pernic, filename)
}
// IsRunning returns whether the process is running or not.
// Not implemented yet.
func (p *Process) IsRunning() (bool, error) {
return p.IsRunningWithContext(context.Background())
}
func (p *Process) IsRunningWithContext(ctx context.Context) (bool, error) {
return true, common.ErrNotImplementedError
}
// MemoryMaps get memory maps from /proc/(pid)/smaps
func (p *Process) MemoryMaps(grouped bool) (*[]MemoryMapsStat, error) {
return p.MemoryMapsWithContext(context.Background(), grouped)
@@ -541,6 +553,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 {
@@ -603,7 +618,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)
@@ -632,10 +660,6 @@ func limitToInt(val string) (int32, error) {
}
// Get num_fds from /proc/(pid)/limits
func (p *Process) fillFromLimits() ([]RlimitStat, error) {
return p.fillFromLimitsWithContext(context.Background())
}
func (p *Process) fillFromLimitsWithContext(ctx context.Context) ([]RlimitStat, error) {
pid := p.Pid
limitsFile := common.HostProc(strconv.Itoa(int(pid)), "limits")
@@ -729,10 +753,6 @@ func (p *Process) fillFromLimitsWithContext(ctx context.Context) ([]RlimitStat,
}
// Get list of /proc/(pid)/fd files
func (p *Process) fillFromfdList() (string, []string, error) {
return p.fillFromfdListWithContext(context.Background())
}
func (p *Process) fillFromfdListWithContext(ctx context.Context) (string, []string, error) {
pid := p.Pid
statPath := common.HostProc(strconv.Itoa(int(pid)), "fd")
@@ -746,12 +766,8 @@ func (p *Process) fillFromfdListWithContext(ctx context.Context) (string, []stri
}
// Get num_fds from /proc/(pid)/fd
func (p *Process) fillFromfd() (int32, []*OpenFilesStat, error) {
return p.fillFromfdWithContext(context.Background())
}
func (p *Process) fillFromfdWithContext(ctx context.Context) (int32, []*OpenFilesStat, error) {
statPath, fnames, err := p.fillFromfdList()
statPath, fnames, err := p.fillFromfdListWithContext(ctx)
if err != nil {
return 0, nil, err
}
@@ -779,10 +795,6 @@ func (p *Process) fillFromfdWithContext(ctx context.Context) (int32, []*OpenFile
}
// Get cwd from /proc/(pid)/cwd
func (p *Process) fillFromCwd() (string, error) {
return p.fillFromCwdWithContext(context.Background())
}
func (p *Process) fillFromCwdWithContext(ctx context.Context) (string, error) {
pid := p.Pid
cwdPath := common.HostProc(strconv.Itoa(int(pid)), "cwd")
@@ -794,10 +806,6 @@ func (p *Process) fillFromCwdWithContext(ctx context.Context) (string, error) {
}
// Get exe from /proc/(pid)/exe
func (p *Process) fillFromExe() (string, error) {
return p.fillFromExeWithContext(context.Background())
}
func (p *Process) fillFromExeWithContext(ctx context.Context) (string, error) {
pid := p.Pid
exePath := common.HostProc(strconv.Itoa(int(pid)), "exe")
@@ -809,10 +817,6 @@ func (p *Process) fillFromExeWithContext(ctx context.Context) (string, error) {
}
// Get cmdline from /proc/(pid)/cmdline
func (p *Process) fillFromCmdline() (string, error) {
return p.fillFromCmdlineWithContext(context.Background())
}
func (p *Process) fillFromCmdlineWithContext(ctx context.Context) (string, error) {
pid := p.Pid
cmdPath := common.HostProc(strconv.Itoa(int(pid)), "cmdline")
@@ -830,10 +834,6 @@ func (p *Process) fillFromCmdlineWithContext(ctx context.Context) (string, error
return strings.Join(ret, " "), nil
}
func (p *Process) fillSliceFromCmdline() ([]string, error) {
return p.fillSliceFromCmdlineWithContext(context.Background())
}
func (p *Process) fillSliceFromCmdlineWithContext(ctx context.Context) ([]string, error) {
pid := p.Pid
cmdPath := common.HostProc(strconv.Itoa(int(pid)), "cmdline")
@@ -857,10 +857,6 @@ func (p *Process) fillSliceFromCmdlineWithContext(ctx context.Context) ([]string
}
// Get IO status from /proc/(pid)/io
func (p *Process) fillFromIO() (*IOCountersStat, error) {
return p.fillFromIOWithContext(context.Background())
}
func (p *Process) fillFromIOWithContext(ctx context.Context) (*IOCountersStat, error) {
pid := p.Pid
ioPath := common.HostProc(strconv.Itoa(int(pid)), "io")
@@ -900,10 +896,6 @@ func (p *Process) fillFromIOWithContext(ctx context.Context) (*IOCountersStat, e
}
// Get memory info from /proc/(pid)/statm
func (p *Process) fillFromStatm() (*MemoryInfoStat, *MemoryInfoExStat, error) {
return p.fillFromStatmWithContext(context.Background())
}
func (p *Process) fillFromStatmWithContext(ctx context.Context) (*MemoryInfoStat, *MemoryInfoExStat, error) {
pid := p.Pid
memPath := common.HostProc(strconv.Itoa(int(pid)), "statm")
@@ -956,10 +948,6 @@ func (p *Process) fillFromStatmWithContext(ctx context.Context) (*MemoryInfoStat
}
// Get various status from /proc/(pid)/status
func (p *Process) fillFromStatus() error {
return p.fillFromStatusWithContext(context.Background())
}
func (p *Process) fillFromStatusWithContext(ctx context.Context) error {
pid := p.Pid
statPath := common.HostProc(strconv.Itoa(int(pid)), "status")
@@ -989,6 +977,8 @@ func (p *Process) fillFromStatusWithContext(ctx context.Context) error {
extendedName := filepath.Base(cmdlineSlice[0])
if strings.HasPrefix(extendedName, p.name) {
p.name = extendedName
} else {
p.name = cmdlineSlice[0]
}
}
}
@@ -1063,6 +1053,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)
@@ -1120,11 +1117,7 @@ func (p *Process) fillFromStatusWithContext(ctx context.Context) error {
return nil
}
func (p *Process) fillFromTIDStat(tid int32) (uint64, int32, *cpu.TimesStat, int64, uint32, int32, 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
@@ -1136,7 +1129,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))
@@ -1147,21 +1140,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{
@@ -1170,15 +1163,18 @@ func (p *Process) fillFromTIDStatWithContext(ctx context.Context, tid int32) (ui
System: float64(stime / ClockTicks),
}
bootTime, _ := host.BootTime()
bootTime, _ := common.BootTimeWithContext(ctx)
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, nil, err
}
if rtpriority < 0 {
rtpriority = rtpriority*-1 - 1
} else {
@@ -1190,23 +1186,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) {
return p.fillFromStatWithContext(context.Background())
func (p *Process) fillFromStatWithContext(ctx context.Context) (uint64, int32, *cpu.TimesStat, int64, uint32, int32, *PageFaultsStat, error) {
return p.fillFromTIDStatWithContext(ctx, -1)
}
func (p *Process) fillFromStatWithContext(ctx context.Context) (uint64, int32, *cpu.TimesStat, int64, uint32, int32, error) {
return p.fillFromTIDStat(-1)
}
// Pids returns a slice of process ID list which are running now.
func Pids() ([]int32, error) {
return PidsWithContext(context.Background())
}
func PidsWithContext(ctx context.Context) ([]int32, error) {
func pidsWithContext(ctx context.Context) ([]int32, error) {
return readPidsFromDir(common.HostProc())
}
@@ -1219,7 +1230,7 @@ func Processes() ([]*Process, error) {
func ProcessesWithContext(ctx context.Context) ([]*Process, error) {
out := []*Process{}
pids, err := Pids()
pids, err := PidsWithContext(ctx)
if err != nil {
return out, err
}

View File

@@ -5,7 +5,11 @@ package process
import (
"C"
"bytes"
"context"
"encoding/binary"
"os/exec"
"path/filepath"
"strconv"
"strings"
"unsafe"
@@ -15,7 +19,6 @@ import (
net "github.com/shirou/gopsutil/net"
"golang.org/x/sys/unix"
)
import "context"
// MemoryInfoExStat is different between OSes
type MemoryInfoExStat struct {
@@ -24,11 +27,7 @@ type MemoryInfoExStat struct {
type MemoryMapsStat struct {
}
func Pids() ([]int32, error) {
return PidsWithContext(context.Background())
}
func PidsWithContext(ctx context.Context) ([]int32, error) {
func pidsWithContext(ctx context.Context) ([]int32, error) {
var ret []int32
procs, err := Processes()
if err != nil {
@@ -63,8 +62,24 @@ func (p *Process) NameWithContext(ctx context.Context) (string, error) {
if err != nil {
return "", err
}
name := common.IntToString(k.Comm[:])
return common.IntToString(k.Comm[:]), nil
if len(name) >= 15 {
cmdlineSlice, err := p.CmdlineSliceWithContext(ctx)
if err != nil {
return "", err
}
if len(cmdlineSlice) > 0 {
extendedName := filepath.Base(cmdlineSlice[0])
if strings.HasPrefix(extendedName, p.name) {
name = extendedName
} else {
name = cmdlineSlice[0]
}
}
}
return name, nil
}
func (p *Process) Tgid() (int32, error) {
return 0, common.ErrNotImplementedError
@@ -116,11 +131,7 @@ func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) {
return strings.Join(argv, " "), nil
}
func (p *Process) CreateTime() (int64, error) {
return p.CreateTimeWithContext(context.Background())
}
func (p *Process) CreateTimeWithContext(ctx context.Context) (int64, error) {
func (p *Process) createTimeWithContext(ctx context.Context) (int64, error) {
return 0, common.ErrNotImplementedError
}
func (p *Process) Cwd() (string, error) {
@@ -162,6 +173,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,12 +368,20 @@ 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) {
pids, err := common.CallPgrep(invoke, p.Pid)
pids, err := common.CallPgrepWithContext(ctx, invoke, p.Pid)
if err != nil {
return nil, err
}
@@ -376,6 +412,14 @@ func (p *Process) ConnectionsWithContext(ctx context.Context) ([]net.ConnectionS
return nil, common.ErrNotImplementedError
}
func (p *Process) ConnectionsMax(max int) ([]net.ConnectionStat, error) {
return p.ConnectionsMaxWithContext(context.Background(), max)
}
func (p *Process) ConnectionsMaxWithContext(ctx context.Context, max int) ([]net.ConnectionStat, error) {
return []net.ConnectionStat{}, common.ErrNotImplementedError
}
func (p *Process) NetIOCounters(pernic bool) ([]net.IOCountersStat, error) {
return p.NetIOCountersWithContext(context.Background(), pernic)
}
@@ -384,13 +428,6 @@ func (p *Process) NetIOCountersWithContext(ctx context.Context, pernic bool) ([]
return nil, common.ErrNotImplementedError
}
func (p *Process) IsRunning() (bool, error) {
return p.IsRunningWithContext(context.Background())
}
func (p *Process) IsRunningWithContext(ctx context.Context) (bool, error) {
return true, common.ErrNotImplementedError
}
func (p *Process) MemoryMaps(grouped bool) (*[]MemoryMapsStat, error) {
return p.MemoryMapsWithContext(context.Background(), grouped)
}
@@ -461,12 +498,6 @@ func (p *Process) getKProcWithContext(ctx context.Context) (*KinfoProc, error) {
return &k, nil
}
func NewProcess(pid int32) (*Process, error) {
p := &Process{Pid: pid}
return p, nil
}
func CallKernProcSyscall(op int32, arg int32) ([]byte, uint64, error) {
return CallKernProcSyscallWithContext(context.Background(), op, arg)
}

View File

@@ -4,6 +4,7 @@ package process
import (
"context"
"fmt"
"os"
"os/user"
"path/filepath"
@@ -11,6 +12,7 @@ import (
"strings"
"syscall"
"github.com/shirou/gopsutil/internal/common"
"golang.org/x/sys/unix"
)
@@ -26,6 +28,9 @@ func getTerminalMap() (map[uint64]string, error) {
defer d.Close()
devnames, err := d.Readdirnames(-1)
if err != nil {
return nil, err
}
for _, devname := range devnames {
if strings.HasPrefix(devname, "/dev/tty") {
termfiles = append(termfiles, "/dev/tty/"+devname)
@@ -45,6 +50,9 @@ func getTerminalMap() (map[uint64]string, error) {
if ptsnames == nil {
defer ptsd.Close()
ptsnames, err = ptsd.Readdirnames(-1)
if err != nil {
return nil, err
}
for _, ptsname := range ptsnames {
termfiles = append(termfiles, "/dev/pts/"+ptsname)
}
@@ -63,6 +71,49 @@ func getTerminalMap() (map[uint64]string, error) {
return ret, nil
}
func PidExistsWithContext(ctx context.Context, pid int32) (bool, error) {
if pid <= 0 {
return false, fmt.Errorf("invalid pid %v", pid)
}
proc, err := os.FindProcess(int(pid))
if err != nil {
return false, err
}
if _, err := os.Stat(common.HostProc()); err == nil { //Means that proc filesystem exist
// Checking PID existence based on existence of /<HOST_PROC>/proc/<PID> folder
// This covers the case when running inside container with a different process namespace (by default)
_, err := os.Stat(common.HostProc(strconv.Itoa(int(pid))))
if os.IsNotExist(err) {
return false, nil
}
return err == nil, err
}
//'/proc' filesystem is not exist, checking of PID existence is done via signalling the process
//Make sense only if we run in the same process namespace
err = proc.Signal(syscall.Signal(0))
if err == nil {
return true, nil
}
if err.Error() == "os: process already finished" {
return false, nil
}
errno, ok := err.(syscall.Errno)
if !ok {
return false, err
}
switch errno {
case syscall.ESRCH:
return false, nil
case syscall.EPERM:
return true, nil
}
return false, err
}
// SendSignal sends a unix.Signal to the process.
// Currently, SIGSTOP, SIGCONT, SIGTERM and SIGKILL are supported.
func (p *Process) SendSignal(sig syscall.Signal) error {

View File

@@ -1,20 +0,0 @@
// +build linux freebsd
package process
import (
"os"
"testing"
"golang.org/x/sys/unix"
)
func Test_SendSignal(t *testing.T) {
checkPid := os.Getpid()
p, _ := NewProcess(int32(checkPid))
err := p.SendSignal(unix.SIGCONT)
if err != nil {
t.Errorf("send signal %v", err)
}
}

View File

@@ -1,446 +0,0 @@
package process
import (
"fmt"
"os"
"os/exec"
"os/user"
"reflect"
"runtime"
"strings"
"sync"
"testing"
"time"
"github.com/shirou/gopsutil/internal/common"
"github.com/stretchr/testify/assert"
)
var mu sync.Mutex
func testGetProcess() Process {
checkPid := os.Getpid() // process.test
ret, _ := NewProcess(int32(checkPid))
return *ret
}
func Test_Pids(t *testing.T) {
ret, err := Pids()
if err != nil {
t.Errorf("error %v", err)
}
if len(ret) == 0 {
t.Errorf("could not get pids %v", ret)
}
}
func Test_Pids_Fail(t *testing.T) {
if runtime.GOOS != "darwin" {
t.Skip("darwin only")
}
mu.Lock()
defer mu.Unlock()
invoke = common.FakeInvoke{Suffix: "fail"}
ret, err := Pids()
invoke = common.Invoke{}
if err != nil {
t.Errorf("error %v", err)
}
if len(ret) != 9 {
t.Errorf("wrong getted pid nums: %v/%d", ret, len(ret))
}
}
func Test_Pid_exists(t *testing.T) {
checkPid := os.Getpid()
ret, err := PidExists(int32(checkPid))
if err != nil {
t.Errorf("error %v", err)
}
if ret == false {
t.Errorf("could not get process exists: %v", ret)
}
}
func Test_NewProcess(t *testing.T) {
checkPid := os.Getpid()
ret, err := NewProcess(int32(checkPid))
if err != nil {
t.Errorf("error %v", err)
}
empty := &Process{}
if runtime.GOOS != "windows" { // Windows pid is 0
if empty == ret {
t.Errorf("error %v", ret)
}
}
}
func Test_Process_memory_maps(t *testing.T) {
checkPid := os.Getpid()
ret, err := NewProcess(int32(checkPid))
mmaps, err := ret.MemoryMaps(false)
if err != nil {
t.Errorf("memory map get error %v", err)
}
empty := MemoryMapsStat{}
for _, m := range *mmaps {
if m == empty {
t.Errorf("memory map get error %v", m)
}
}
}
func Test_Process_MemoryInfo(t *testing.T) {
p := testGetProcess()
v, err := p.MemoryInfo()
if err != nil {
t.Errorf("geting memory info error %v", err)
}
empty := MemoryInfoStat{}
if v == nil || *v == empty {
t.Errorf("could not get memory info %v", v)
}
}
func Test_Process_CmdLine(t *testing.T) {
p := testGetProcess()
v, err := p.Cmdline()
if err != nil {
t.Errorf("geting cmdline error %v", err)
}
if !strings.Contains(v, "process.test") {
t.Errorf("invalid cmd line %v", v)
}
}
func Test_Process_CmdLineSlice(t *testing.T) {
p := testGetProcess()
v, err := p.CmdlineSlice()
if err != nil {
t.Fatalf("geting cmdline slice error %v", err)
}
if !reflect.DeepEqual(v, os.Args) {
t.Errorf("returned cmdline slice not as expected:\nexp: %v\ngot: %v", os.Args, v)
}
}
func Test_Process_Ppid(t *testing.T) {
p := testGetProcess()
v, err := p.Ppid()
if err != nil {
t.Errorf("geting ppid error %v", err)
}
if v == 0 {
t.Errorf("return value is 0 %v", v)
}
}
func Test_Process_Status(t *testing.T) {
p := testGetProcess()
v, err := p.Status()
if err != nil {
t.Errorf("geting status error %v", err)
}
if v != "R" && v != "S" {
t.Errorf("could not get state %v", v)
}
}
func Test_Process_Terminal(t *testing.T) {
p := testGetProcess()
_, err := p.Terminal()
if err != nil {
t.Errorf("geting terminal error %v", err)
}
/*
if v == "" {
t.Errorf("could not get terminal %v", v)
}
*/
}
func Test_Process_IOCounters(t *testing.T) {
p := testGetProcess()
v, err := p.IOCounters()
if err != nil {
t.Errorf("geting iocounter error %v", err)
return
}
empty := &IOCountersStat{}
if v == empty {
t.Errorf("error %v", v)
}
}
func Test_Process_NumCtx(t *testing.T) {
p := testGetProcess()
_, err := p.NumCtxSwitches()
if err != nil {
t.Errorf("geting numctx error %v", err)
return
}
}
func Test_Process_Nice(t *testing.T) {
p := testGetProcess()
n, err := p.Nice()
if err != nil {
t.Errorf("geting nice error %v", err)
}
if n != 0 && n != 20 && n != 8 {
t.Errorf("invalid nice: %d", n)
}
}
func Test_Process_NumThread(t *testing.T) {
p := testGetProcess()
n, err := p.NumThreads()
if err != nil {
t.Errorf("geting NumThread error %v", err)
}
if n < 0 {
t.Errorf("invalid NumThread: %d", n)
}
}
func Test_Process_Threads(t *testing.T) {
p := testGetProcess()
n, err := p.NumThreads()
if err != nil {
t.Errorf("geting NumThread error %v", err)
}
if n < 0 {
t.Errorf("invalid NumThread: %d", n)
}
ts, err := p.Threads()
if err != nil {
t.Errorf("geting Threads error %v", err)
}
if len(ts) != int(n) {
t.Errorf("unexpected number of threads: %v vs %v", len(ts), n)
}
}
func Test_Process_Name(t *testing.T) {
p := testGetProcess()
n, err := p.Name()
if err != nil {
t.Errorf("geting name error %v", err)
}
if !strings.Contains(n, "process.test") {
t.Errorf("invalid Exe %s", n)
}
}
func Test_Process_Exe(t *testing.T) {
p := testGetProcess()
n, err := p.Exe()
if err != nil {
t.Errorf("geting Exe error %v", err)
}
if !strings.Contains(n, "process.test") {
t.Errorf("invalid Exe %s", n)
}
}
func Test_Process_CpuPercent(t *testing.T) {
p := testGetProcess()
percent, err := p.Percent(0)
if err != nil {
t.Errorf("error %v", err)
}
duration := time.Duration(1000) * time.Microsecond
time.Sleep(duration)
percent, err = p.Percent(0)
if err != nil {
t.Errorf("error %v", err)
}
numcpu := runtime.NumCPU()
// if percent < 0.0 || percent > 100.0*float64(numcpu) { // TODO
if percent < 0.0 {
t.Fatalf("CPUPercent value is invalid: %f, %d", percent, numcpu)
}
}
func Test_Process_CpuPercentLoop(t *testing.T) {
p := testGetProcess()
numcpu := runtime.NumCPU()
for i := 0; i < 2; i++ {
duration := time.Duration(100) * time.Microsecond
percent, err := p.Percent(duration)
if err != nil {
t.Errorf("error %v", err)
}
// if percent < 0.0 || percent > 100.0*float64(numcpu) { // TODO
if percent < 0.0 {
t.Fatalf("CPUPercent value is invalid: %f, %d", percent, numcpu)
}
}
}
func Test_Process_CreateTime(t *testing.T) {
if os.Getenv("CIRCLECI") == "true" {
t.Skip("Skip CI")
}
p := testGetProcess()
c, err := p.CreateTime()
if err != nil {
t.Errorf("error %v", err)
}
if c < 1420000000 {
t.Errorf("process created time is wrong.")
}
gotElapsed := time.Since(time.Unix(int64(c/1000), 0))
maxElapsed := time.Duration(5 * time.Second)
if gotElapsed >= maxElapsed {
t.Errorf("this process has not been running for %v", gotElapsed)
}
}
func Test_Parent(t *testing.T) {
p := testGetProcess()
c, err := p.Parent()
if err != nil {
t.Fatalf("error %v", err)
}
if c == nil {
t.Fatalf("could not get parent")
}
if c.Pid == 0 {
t.Fatalf("wrong parent pid")
}
}
func Test_Connections(t *testing.T) {
p := testGetProcess()
c, err := p.Connections()
if err != nil {
t.Fatalf("error %v", err)
}
// TODO:
// Since go test open no conneciton, ret is empty.
// should invoke child process or other solutions.
if len(c) != 0 {
t.Fatalf("wrong connections")
}
}
func Test_Children(t *testing.T) {
p, err := NewProcess(1)
if err != nil {
t.Fatalf("new process error %v", err)
}
c, err := p.Children()
if err != nil {
t.Fatalf("error %v", err)
}
if len(c) == 0 {
t.Fatalf("children is empty")
}
}
func Test_Username(t *testing.T) {
myPid := os.Getpid()
currentUser, _ := user.Current()
myUsername := currentUser.Username
process, _ := NewProcess(int32(myPid))
pidUsername, _ := process.Username()
assert.Equal(t, myUsername, pidUsername)
t.Log(pidUsername)
}
func Test_CPUTimes(t *testing.T) {
pid := os.Getpid()
process, err := NewProcess(int32(pid))
assert.Nil(t, err)
spinSeconds := 0.2
cpuTimes0, err := process.Times()
assert.Nil(t, err)
// Spin for a duration of spinSeconds
t0 := time.Now()
tGoal := t0.Add(time.Duration(spinSeconds*1000) * time.Millisecond)
assert.Nil(t, err)
for time.Now().Before(tGoal) {
// This block intentionally left blank
}
cpuTimes1, err := process.Times()
assert.Nil(t, err)
if cpuTimes0 == nil || cpuTimes1 == nil {
t.FailNow()
}
measuredElapsed := cpuTimes1.Total() - cpuTimes0.Total()
message := fmt.Sprintf("Measured %fs != spun time of %fs\ncpuTimes0=%v\ncpuTimes1=%v",
measuredElapsed, spinSeconds, cpuTimes0, cpuTimes1)
assert.True(t, measuredElapsed > float64(spinSeconds)/5, message)
assert.True(t, measuredElapsed < float64(spinSeconds)*5, message)
}
func Test_OpenFiles(t *testing.T) {
pid := os.Getpid()
p, err := NewProcess(int32(pid))
assert.Nil(t, err)
v, err := p.OpenFiles()
assert.Nil(t, err)
assert.NotEmpty(t, v) // test always open files.
for _, vv := range v {
assert.NotEqual(t, "", vv.Path)
}
}
func Test_Kill(t *testing.T) {
var cmd *exec.Cmd
if runtime.GOOS == "windows" {
cmd = exec.Command("choice", "/C", "YN", "/D", "Y", "/t", "3")
} else {
cmd = exec.Command("sleep", "3")
}
var wg sync.WaitGroup
wg.Add(1)
go func() {
assert.NotNil(t, cmd.Run())
wg.Done()
}()
time.Sleep(100 * time.Millisecond)
p, err := NewProcess(int32(cmd.Process.Pid))
assert.Nil(t, err)
assert.Nil(t, p.Kill())
wg.Wait()
}

View File

@@ -15,18 +15,21 @@ import (
cpu "github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/internal/common"
net "github.com/shirou/gopsutil/net"
"github.com/shirou/w32"
"golang.org/x/sys/windows"
)
const (
NoMoreFiles = 0x12
MaxPathLength = 260
)
var (
modpsapi = windows.NewLazyDLL("psapi.dll")
procGetProcessMemoryInfo = modpsapi.NewProc("GetProcessMemoryInfo")
modpsapi = windows.NewLazySystemDLL("psapi.dll")
procGetProcessMemoryInfo = modpsapi.NewProc("GetProcessMemoryInfo")
procGetProcessImageFileNameW = modpsapi.NewProc("GetProcessImageFileNameW")
advapi32 = windows.NewLazySystemDLL("advapi32.dll")
procLookupPrivilegeValue = advapi32.NewProc("LookupPrivilegeValueW")
procAdjustTokenPrivileges = advapi32.NewProc("AdjustTokenPrivileges")
procQueryFullProcessImageNameW = common.Modkernel32.NewProc("QueryFullProcessImageNameW")
procGetPriorityClass = common.Modkernel32.NewProc("GetPriorityClass")
procGetProcessIoCounters = common.Modkernel32.NewProc("GetProcessIoCounters")
)
type SystemProcessInformation struct {
@@ -51,6 +54,17 @@ type MemoryInfoExStat struct {
type MemoryMapsStat struct {
}
// ioCounters is an equivalent representation of IO_COUNTERS in the Windows API.
// https://docs.microsoft.com/windows/win32/api/winnt/ns-winnt-io_counters
type ioCounters struct {
ReadOperationCount uint64
WriteOperationCount uint64
OtherOperationCount uint64
ReadTransferCount uint64
WriteTransferCount uint64
OtherTransferCount uint64
}
type Win32_Process struct {
Name string
ExecutablePath *string
@@ -90,15 +104,64 @@ 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) {
return PidsWithContext(context.Background())
}
func PidsWithContext(ctx context.Context) ([]int32, error) {
func pidsWithContext(ctx context.Context) ([]int32, error) {
// inspired by https://gist.github.com/henkman/3083408
// and https://github.com/giampaolo/psutil/blob/1c3a15f637521ba5c0031283da39c733fda53e4c/psutil/arch/windows/process_info.c#L315-L329
var ret []int32
@@ -108,8 +171,8 @@ func PidsWithContext(ctx context.Context) ([]int32, error) {
for {
ps := make([]uint32, psSize)
if !w32.EnumProcesses(ps, uint32(len(ps)), &read) {
return nil, fmt.Errorf("could not get w32.EnumProcesses")
if err := windows.EnumProcesses(ps, &read); err != nil {
return nil, err
}
if uint32(len(ps)) == read { // ps buffer was too small to host every results, retry with a bigger one
psSize += 1024
@@ -124,6 +187,44 @@ func PidsWithContext(ctx context.Context) ([]int32, error) {
}
func PidExistsWithContext(ctx context.Context, pid int32) (bool, error) {
if pid == 0 { // special case for pid 0 System Idle Process
return true, nil
}
if pid < 0 {
return false, fmt.Errorf("invalid pid %v", pid)
}
if pid%4 != 0 {
// OpenProcess will succeed even on non-existing pid here https://devblogs.microsoft.com/oldnewthing/20080606-00/?p=22043
// so we list every pid just to be sure and be future-proof
pids, err := PidsWithContext(ctx)
if err != nil {
return false, err
}
for _, i := range pids {
if i == pid {
return true, err
}
}
return false, err
}
const STILL_ACTIVE = 259 // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess
h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))
if err == windows.ERROR_ACCESS_DENIED {
return true, nil
}
if err == windows.ERROR_INVALID_PARAMETER {
return false, nil
}
if err != nil {
return false, err
}
defer syscall.CloseHandle(syscall.Handle(h))
var exitCode uint32
err = windows.GetExitCodeProcess(h, &exitCode)
return exitCode == STILL_ACTIVE, err
}
func (p *Process) Ppid() (int32, error) {
return p.PpidWithContext(context.Background())
}
@@ -177,11 +278,30 @@ func (p *Process) Exe() (string, error) {
}
func (p *Process) ExeWithContext(ctx context.Context) (string, error) {
dst, err := GetWin32Proc(p.Pid)
c, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(p.Pid))
if err != nil {
return "", fmt.Errorf("could not get ExecutablePath: %s", err)
return "", err
}
return *dst[0].ExecutablePath, nil
defer windows.CloseHandle(c)
buf := make([]uint16, syscall.MAX_LONG_PATH)
size := uint32(syscall.MAX_LONG_PATH)
if err := procQueryFullProcessImageNameW.Find(); err == nil { // Vista+
ret, _, err := procQueryFullProcessImageNameW.Call(
uintptr(c),
uintptr(0),
uintptr(unsafe.Pointer(&buf[0])),
uintptr(unsafe.Pointer(&size)))
if ret == 0 {
return "", err
}
return windows.UTF16ToString(buf[:]), nil
}
// XP fallback
ret, _, err := procGetProcessImageFileNameW.Call(uintptr(c), uintptr(unsafe.Pointer(&buf[0])), uintptr(size))
if ret == 0 {
return "", err
}
return common.ConvertDOSPath(windows.UTF16ToString(buf[:])), nil
}
func (p *Process) Cmdline() (string, error) {
@@ -189,7 +309,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,18 +324,14 @@ 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
}
return strings.Split(cmdline, " "), nil
}
func (p *Process) CreateTime() (int64, error) {
return p.CreateTimeWithContext(context.Background())
}
func (p *Process) CreateTimeWithContext(ctx context.Context) (int64, error) {
func (p *Process) createTimeWithContext(ctx context.Context) (int64, error) {
ru, err := getRusage(p.Pid)
if err != nil {
return 0, fmt.Errorf("could not get CreationDate: %s", err)
@@ -236,12 +352,12 @@ func (p *Process) Parent() (*Process, error) {
}
func (p *Process) ParentWithContext(ctx context.Context) (*Process, error) {
dst, err := GetWin32Proc(p.Pid)
ppid, err := p.PpidWithContext(ctx)
if err != nil {
return nil, fmt.Errorf("could not get ParentProcessID: %s", err)
}
return NewProcess(int32(dst[0].ParentProcessID))
return NewProcess(ppid)
}
func (p *Process) Status() (string, error) {
return p.StatusWithContext(context.Background())
@@ -250,26 +366,37 @@ 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())
}
func (p *Process) UsernameWithContext(ctx context.Context) (string, error) {
pid := p.Pid
// 0x1000 is PROCESS_QUERY_LIMITED_INFORMATION
c, err := syscall.OpenProcess(0x1000, false, uint32(pid))
c, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))
if err != nil {
return "", err
}
defer syscall.CloseHandle(c)
defer windows.CloseHandle(c)
var token syscall.Token
err = syscall.OpenProcessToken(c, syscall.TOKEN_QUERY, &token)
err = syscall.OpenProcessToken(syscall.Handle(c), syscall.TOKEN_QUERY, &token)
if err != nil {
return "", err
}
defer token.Close()
tokenUser, err := token.GetTokenUser()
if err != nil {
return "", err
}
user, domain, _, err := tokenUser.User.Sid.LookupAccount("")
return domain + "\\" + user, err
@@ -300,17 +427,38 @@ func (p *Process) TerminalWithContext(ctx context.Context) (string, error) {
return "", common.ErrNotImplementedError
}
// Nice returnes priority in Windows
// priorityClasses maps a win32 priority class to its WMI equivalent Win32_Process.Priority
// https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-getpriorityclass
// https://docs.microsoft.com/en-us/windows/desktop/cimwin32prov/win32-process
var priorityClasses = map[int]int32{
0x00008000: 10, // ABOVE_NORMAL_PRIORITY_CLASS
0x00004000: 6, // BELOW_NORMAL_PRIORITY_CLASS
0x00000080: 13, // HIGH_PRIORITY_CLASS
0x00000040: 4, // IDLE_PRIORITY_CLASS
0x00000020: 8, // NORMAL_PRIORITY_CLASS
0x00000100: 24, // REALTIME_PRIORITY_CLASS
}
// Nice returns priority in Windows
func (p *Process) Nice() (int32, error) {
return p.NiceWithContext(context.Background())
}
func (p *Process) NiceWithContext(ctx context.Context) (int32, error) {
dst, err := GetWin32Proc(p.Pid)
c, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(p.Pid))
if err != nil {
return 0, fmt.Errorf("could not get Priority: %s", err)
return 0, err
}
return int32(dst[0].Priority), nil
defer windows.CloseHandle(c)
ret, _, err := procGetPriorityClass.Call(uintptr(c))
if ret == 0 {
return 0, err
}
priority, ok := priorityClasses[int(ret)]
if !ok {
return 0, fmt.Errorf("unknown priority class %v", ret)
}
return priority, nil
}
func (p *Process) IOnice() (int32, error) {
return p.IOniceWithContext(context.Background())
@@ -343,18 +491,24 @@ func (p *Process) IOCounters() (*IOCountersStat, error) {
}
func (p *Process) IOCountersWithContext(ctx context.Context) (*IOCountersStat, error) {
dst, err := GetWin32Proc(p.Pid)
if err != nil || len(dst) == 0 {
return nil, fmt.Errorf("could not get Win32Proc: %s", err)
c, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(p.Pid))
if err != nil {
return nil, err
}
ret := &IOCountersStat{
ReadCount: uint64(dst[0].ReadOperationCount),
ReadBytes: uint64(dst[0].ReadTransferCount),
WriteCount: uint64(dst[0].WriteOperationCount),
WriteBytes: uint64(dst[0].WriteTransferCount),
defer windows.CloseHandle(c)
var ioCounters ioCounters
ret, _, err := procGetProcessIoCounters.Call(uintptr(c), uintptr(unsafe.Pointer(&ioCounters)))
if ret == 0 {
return nil, err
}
stats := &IOCountersStat{
ReadCount: ioCounters.ReadOperationCount,
ReadBytes: ioCounters.ReadTransferCount,
WriteCount: ioCounters.WriteOperationCount,
WriteBytes: ioCounters.WriteTransferCount,
}
return ret, nil
return stats, nil
}
func (p *Process) NumCtxSwitches() (*NumCtxSwitchesStat, error) {
return p.NumCtxSwitchesWithContext(context.Background())
@@ -375,11 +529,11 @@ func (p *Process) NumThreads() (int32, error) {
}
func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) {
dst, err := GetWin32Proc(p.Pid)
_, ret, _, err := getFromSnapProcess(p.Pid)
if err != nil {
return 0, fmt.Errorf("could not get ThreadCount: %s", err)
return 0, err
}
return int32(dst[0].ThreadCount), nil
return ret, nil
}
func (p *Process) Threads() (map[int32]*cpu.TimesStat, error) {
return p.ThreadsWithContext(context.Background())
@@ -448,27 +602,41 @@ 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{}
for _, proc := range dst {
p, err := NewProcess(int32(proc.ProcessID))
if err != nil {
continue
}
out = append(out, p)
snap, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, uint32(0))
if err != nil {
return out, err
}
defer windows.CloseHandle(snap)
var pe32 windows.ProcessEntry32
pe32.Size = uint32(unsafe.Sizeof(pe32))
if err := windows.Process32First(snap, &pe32); err != nil {
return out, err
}
for {
if pe32.ParentProcessID == uint32(p.Pid) {
p, err := NewProcess(int32(pe32.ProcessID))
if err == nil {
out = append(out, p)
}
}
if err = windows.Process32Next(snap, &pe32); err != nil {
break
}
}
return out, nil
}
@@ -485,7 +653,15 @@ func (p *Process) Connections() ([]net.ConnectionStat, error) {
}
func (p *Process) ConnectionsWithContext(ctx context.Context) ([]net.ConnectionStat, error) {
return nil, common.ErrNotImplementedError
return net.ConnectionsPidWithContext(ctx, "all", p.Pid)
}
func (p *Process) ConnectionsMax(max int) ([]net.ConnectionStat, error) {
return p.ConnectionsMaxWithContext(context.Background(), max)
}
func (p *Process) ConnectionsMaxWithContext(ctx context.Context, max int) ([]net.ConnectionStat, error) {
return []net.ConnectionStat{}, common.ErrNotImplementedError
}
func (p *Process) NetIOCounters(pernic bool) ([]net.IOCountersStat, error) {
@@ -496,14 +672,6 @@ func (p *Process) NetIOCountersWithContext(ctx context.Context, pernic bool) ([]
return nil, common.ErrNotImplementedError
}
func (p *Process) IsRunning() (bool, error) {
return p.IsRunningWithContext(context.Background())
}
func (p *Process) IsRunningWithContext(ctx context.Context) (bool, error) {
return true, common.ErrNotImplementedError
}
func (p *Process) MemoryMaps(grouped bool) (*[]MemoryMapsStat, error) {
return p.MemoryMapsWithContext(context.Background(), grouped)
}
@@ -513,12 +681,6 @@ func (p *Process) MemoryMapsWithContext(ctx context.Context, grouped bool) (*[]M
return &ret, common.ErrNotImplementedError
}
func NewProcess(pid int32) (*Process, error) {
p := &Process{Pid: pid}
return p, nil
}
func (p *Process) SendSignal(sig windows.Signal) error {
return p.SendSignalWithContext(context.Background(), sig)
}
@@ -547,16 +709,13 @@ func (p *Process) Terminate() error {
}
func (p *Process) TerminateWithContext(ctx context.Context) error {
// PROCESS_TERMINATE = 0x0001
proc := w32.OpenProcess(0x0001, false, uint32(p.Pid))
ret := w32.TerminateProcess(proc, 0)
w32.CloseHandle(proc)
if ret == false {
return windows.GetLastError()
} else {
return nil
proc, err := windows.OpenProcess(windows.PROCESS_TERMINATE, false, uint32(p.Pid))
if err != nil {
return err
}
err = windows.TerminateProcess(proc, 0)
windows.CloseHandle(proc)
return err
}
func (p *Process) Kill() error {
@@ -569,29 +728,26 @@ func (p *Process) KillWithContext(ctx context.Context) error {
}
func getFromSnapProcess(pid int32) (int32, int32, string, error) {
snap := w32.CreateToolhelp32Snapshot(w32.TH32CS_SNAPPROCESS, uint32(pid))
if snap == 0 {
return 0, 0, "", windows.GetLastError()
snap, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, uint32(pid))
if err != nil {
return 0, 0, "", err
}
defer w32.CloseHandle(snap)
var pe32 w32.PROCESSENTRY32
pe32.DwSize = uint32(unsafe.Sizeof(pe32))
if w32.Process32First(snap, &pe32) == false {
return 0, 0, "", windows.GetLastError()
defer windows.CloseHandle(snap)
var pe32 windows.ProcessEntry32
pe32.Size = uint32(unsafe.Sizeof(pe32))
if err = windows.Process32First(snap, &pe32); err != nil {
return 0, 0, "", err
}
if pe32.Th32ProcessID == uint32(pid) {
szexe := windows.UTF16ToString(pe32.SzExeFile[:])
return int32(pe32.Th32ParentProcessID), int32(pe32.CntThreads), szexe, nil
}
for w32.Process32Next(snap, &pe32) {
if pe32.Th32ProcessID == uint32(pid) {
szexe := windows.UTF16ToString(pe32.SzExeFile[:])
return int32(pe32.Th32ParentProcessID), int32(pe32.CntThreads), szexe, nil
for {
if pe32.ProcessID == uint32(pid) {
szexe := windows.UTF16ToString(pe32.ExeFile[:])
return int32(pe32.ParentProcessID), int32(pe32.Threads), szexe, nil
}
if err = windows.Process32Next(snap, &pe32); err != nil {
break
}
}
return 0, 0, "", fmt.Errorf("Couldn't find pid: %d", pid)
return 0, 0, "", fmt.Errorf("couldn't find pid: %d", pid)
}
// Get processes
@@ -600,21 +756,22 @@ func Processes() ([]*Process, error) {
}
func ProcessesWithContext(ctx context.Context) ([]*Process, error) {
pids, err := Pids()
out := []*Process{}
pids, err := PidsWithContext(ctx)
if err != nil {
return []*Process{}, fmt.Errorf("could not get Processes %s", err)
return out, fmt.Errorf("could not get Processes %s", err)
}
results := []*Process{}
for _, pid := range pids {
p, err := NewProcess(int32(pid))
p, err := NewProcess(pid)
if err != nil {
continue
}
results = append(results, p)
out = append(out, p)
}
return results, nil
return out, nil
}
func getProcInfo(pid int32) (*SystemProcessInformation, error) {
@@ -638,7 +795,7 @@ func getProcInfo(pid int32) (*SystemProcessInformation, error) {
func getRusage(pid int32) (*windows.Rusage, error) {
var CPU windows.Rusage
c, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION, false, uint32(pid))
c, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))
if err != nil {
return nil, err
}
@@ -653,8 +810,7 @@ func getRusage(pid int32) (*windows.Rusage, error) {
func getMemoryInfo(pid int32) (PROCESS_MEMORY_COUNTERS, error) {
var mem PROCESS_MEMORY_COUNTERS
// PROCESS_QUERY_LIMITED_INFORMATION is 0x1000
c, err := windows.OpenProcess(0x1000, false, uint32(pid))
c, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))
if err != nil {
return mem, err
}
@@ -688,8 +844,7 @@ type SYSTEM_TIMES struct {
func getProcessCPUTimes(pid int32) (SYSTEM_TIMES, error) {
var times SYSTEM_TIMES
// PROCESS_QUERY_LIMITED_INFORMATION is 0x1000
h, err := windows.OpenProcess(0x1000, false, uint32(pid))
h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))
if err != nil {
return times, err
}

Some files were not shown because too many files have changed in this diff Show More