diff --git a/Makefile b/Makefile index cd75f78..83dabed 100644 --- a/Makefile +++ b/Makefile @@ -13,14 +13,21 @@ check: ## Check BUILD_FAIL_PATTERN=grep -v "exec format error" | grep "build failed" && exit 1 || exit 0 build_test: ## test only buildable # Supported operating systems - GOOS=linux go test ./... | $(BUILD_FAIL_PATTERN) + GOOS=linux GOARCH=amd64 go test ./... | $(BUILD_FAIL_PATTERN) + GOOS=linux GOARCH=386 go test ./... | $(BUILD_FAIL_PATTERN) + GOOS=linux GOARCH=arm go test ./... | $(BUILD_FAIL_PATTERN) + GOOS=linux GOARCH=arm64 go test ./... | $(BUILD_FAIL_PATTERN) GOOS=freebsd go test ./... | $(BUILD_FAIL_PATTERN) -# GOOS=openbsd go test ./... | $(BUILD_FAIL_PATTERN) CGO_ENABLED=0 GOOS=darwin go test ./... | $(BUILD_FAIL_PATTERN) GOOS=windows go test ./... | $(BUILD_FAIL_PATTERN) # Operating systems supported for building only (not implemented error if used) GOOS=solaris go test ./... | $(BUILD_FAIL_PATTERN) -# GOOS=dragonfly go test ./... | $(BUILD_FAIL_PATTERN) + GOOS=dragonfly go test ./... | $(BUILD_FAIL_PATTERN) GOOS=netbsd go test ./... | $(BUILD_FAIL_PATTERN) + # cross build to OpenBSD not worked since process has "C" +# GOOS=openbsd go test ./... | $(BUILD_FAIL_PATTERN) + +ifeq ($(shell uname -s), Darwin) CGO_ENABLED=1 GOOS=darwin go test ./... | $(BUILD_FAIL_PATTERN) +endif @echo 'Successfully built on all known operating systems' diff --git a/README.rst b/README.rst index 9a707ba..63f5f69 100644 --- a/README.rst +++ b/README.rst @@ -10,7 +10,7 @@ gopsutil: psutil for golang .. image:: https://godoc.org/github.com/shirou/gopsutil?status.svg :target: http://godoc.org/github.com/shirou/gopsutil -This is a port of psutil (http://pythonhosted.org/psutil/). The challenge is porting all +This is a port of psutil (https://github.com/giampaolo/psutil). The challenge is porting all psutil functions on some architectures. @@ -117,6 +117,10 @@ Several methods have been added which are not present in psutil, but will provid - VirtualizationSystem (ex: "LXC") - VirtualizationRole (ex: "guest"/"host") +- IOCounters + + - Label (linux only) The registered `device mapper name `_ + - cpu/CPUInfo() (linux, freebsd) - CPU (ex: 0, 1, ...) @@ -296,7 +300,7 @@ Related Works I have been influenced by the following great works: -- psutil: http://pythonhosted.org/psutil/ +- psutil: https://github.com/giampaolo/psutil - dstat: https://github.com/dagwieers/dstat - gosigar: https://github.com/cloudfoundry/gosigar/ - goprocinfo: https://github.com/c9s/goprocinfo diff --git a/cpu/cpu.go b/cpu/cpu.go index 049869e..ceaf77f 100644 --- a/cpu/cpu.go +++ b/cpu/cpu.go @@ -54,10 +54,9 @@ 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) diff --git a/cpu/cpu_darwin.go b/cpu/cpu_darwin.go index 001517e..74d2737 100644 --- a/cpu/cpu_darwin.go +++ b/cpu/cpu_darwin.go @@ -45,7 +45,7 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) { if err != nil { return ret, err } - out, err := invoke.Command(sysctl, "machdep.cpu") + out, err := invoke.CommandWithContext(ctx, sysctl, "machdep.cpu") if err != nil { return ret, err } @@ -99,7 +99,7 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) { // 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") + out, err = invoke.CommandWithContext(ctx, sysctl, "hw.cpufrequency") if err != nil { return ret, err } diff --git a/cpu/cpu_linux.go b/cpu/cpu_linux.go index 0f3d928..23b0952 100644 --- a/cpu/cpu_linux.go +++ b/cpu/cpu_linux.go @@ -20,7 +20,7 @@ func init() { 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) diff --git a/cpu/cpu_solaris.go b/cpu/cpu_solaris.go index 0899f41..117fd90 100644 --- a/cpu/cpu_solaris.go +++ b/cpu/cpu_solaris.go @@ -47,7 +47,7 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) { 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) } @@ -56,7 +56,7 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) { 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) } diff --git a/cpu/cpu_solaris_test.go b/cpu/cpu_solaris_test.go index b22d385..1612753 100644 --- a/cpu/cpu_solaris_test.go +++ b/cpu/cpu_solaris_test.go @@ -102,6 +102,9 @@ func TestParseProcessorInfo(t *testing.T) { } cpus, err := parseProcessorInfo(string(content)) + if err != nil { + t.Errorf("cannot parse processor info: %s", err) + } if !reflect.DeepEqual(tc.expected, cpus) { t.Fatalf("Bad Processor Info\nExpected: %v\n Actual: %v", tc.expected, cpus) diff --git a/cpu/cpu_windows.go b/cpu/cpu_windows.go index af65721..8aa691c 100644 --- a/cpu/cpu_windows.go +++ b/cpu/cpu_windows.go @@ -90,8 +90,6 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) { var ret []InfoStat var dst []Win32_Processor q := wmi.CreateQuery(&dst, "") - ctx, cancel := context.WithTimeout(context.Background(), common.Timeout) - defer cancel() if err := common.WMIQueryWithContext(ctx, q, &dst); err != nil { return ret, err } @@ -129,9 +127,11 @@ func PerfInfoWithContext(ctx context.Context) ([]Win32_PerfFormattedData_Counter var ret []Win32_PerfFormattedData_Counters_ProcessorInformation q := wmi.CreateQuery(&ret, "") - ctx, cancel := context.WithTimeout(context.Background(), common.Timeout) - defer cancel() err := common.WMIQueryWithContext(ctx, q, &ret) + if err != nil { + return []Win32_PerfFormattedData_Counters_ProcessorInformation{}, err + } + return ret, err } @@ -144,9 +144,10 @@ func ProcInfo() ([]Win32_PerfFormattedData_PerfOS_System, error) { func ProcInfoWithContext(ctx context.Context) ([]Win32_PerfFormattedData_PerfOS_System, error) { var ret []Win32_PerfFormattedData_PerfOS_System q := wmi.CreateQuery(&ret, "") - ctx, cancel := context.WithTimeout(context.Background(), common.Timeout) - defer cancel() err := common.WMIQueryWithContext(ctx, q, &ret) + if err != nil { + return []Win32_PerfFormattedData_PerfOS_System{}, err + } return ret, err } diff --git a/disk/disk.go b/disk/disk.go index a2c4720..38d8a8f 100644 --- a/disk/disk.go +++ b/disk/disk.go @@ -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 { diff --git a/disk/disk_darwin.go b/disk/disk_darwin.go index 82ffacb..2b1d000 100644 --- a/disk/disk_darwin.go +++ b/disk/disk_darwin.go @@ -22,8 +22,10 @@ func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, erro if err != nil { return ret, err } - fs := make([]Statfs_t, count) - _, err = Getfsstat(fs, MntWait) + fs := make([]Statfs, count) + if _, err = Getfsstat(fs, MntWait); err != nil { + return ret, err + } for _, stat := range fs { opts := "rw" if stat.Flags&MntReadOnly != 0 { @@ -92,16 +94,16 @@ func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, erro return ret, nil } -func Getfsstat(buf []Statfs_t, flags int) (n int, err error) { +func Getfsstat(buf []Statfs, 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) { +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_t{}) * uintptr(len(buf)) + bufsize = unsafe.Sizeof(Statfs{}) * uintptr(len(buf)) } r0, _, e1 := unix.Syscall(SYS_GETFSSTAT64, uintptr(_p0), bufsize, uintptr(flags)) n = int(r0) diff --git a/disk/disk_darwin_386.go b/disk/disk_darwin_386.go new file mode 100644 index 0000000..bd83a4a --- /dev/null +++ b/disk/disk_darwin_386.go @@ -0,0 +1,59 @@ +// +build darwin +// +build 386 + +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 +) + +// https://github.com/golang/go/blob/master/src/syscall/ztypes_darwin_386.go#L82 +type Statfs 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 +} diff --git a/disk/disk_darwin_amd64.go b/disk/disk_darwin_amd64.go index f58e213..ec40a75 100644 --- a/disk/disk_darwin_amd64.go +++ b/disk/disk_darwin_amd64.go @@ -38,7 +38,7 @@ const ( MntNFS4ACLs = 0x00000010 ) -type Statfs_t struct { +type Statfs struct { Bsize uint32 Iosize int32 Blocks uint64 diff --git a/disk/disk_darwin_arm64.go b/disk/disk_darwin_arm64.go index 52bcf4c..0e3f670 100644 --- a/disk/disk_darwin_arm64.go +++ b/disk/disk_darwin_arm64.go @@ -38,7 +38,7 @@ const ( MntNFS4ACLs = 0x00000010 ) -type Statfs_t struct { +type Statfs struct { Bsize uint32 Iosize int32 Blocks uint64 diff --git a/disk/disk_freebsd.go b/disk/disk_freebsd.go index bfb6580..2e0966a 100644 --- a/disk/disk_freebsd.go +++ b/disk/disk_freebsd.go @@ -29,7 +29,9 @@ func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, erro } fs := make([]Statfs, count) - _, err = Getfsstat(fs, MNT_WAIT) + if _, err = Getfsstat(fs, MNT_WAIT); err != nil { + return ret, err + } for _, stat := range fs { opts := "rw" diff --git a/disk/disk_linux.go b/disk/disk_linux.go index 0bc6e2f..6d0ca96 100644 --- a/disk/disk_linux.go +++ b/disk/disk_linux.go @@ -3,9 +3,12 @@ package disk import ( + "bufio" + "bytes" "context" "fmt" - "os/exec" + "io/ioutil" + "path/filepath" "strconv" "strings" @@ -289,6 +292,11 @@ func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOC ret := make(map[string]IOCountersStat, 0) empty := IOCountersStat{} + // use only basename such as "/dev/sda1" to "sda1" + for i, name := range names { + names[i] = filepath.Base(name) + } + for _, line := range lines { fields := strings.Fields(line) if len(fields) < 14 { @@ -364,6 +372,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 @@ -376,28 +386,55 @@ 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) + // Try to get the serial from udev data + udevDataPath := fmt.Sprintf("/run/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] + } + } + } - // does not return error, just an empty string - if err != nil { + // 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 := fmt.Sprintf("/sys/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 "" } - 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 - } - return values[1] + + dmname, err := ioutil.ReadFile(dmname_filename) + if err != nil { + return "" + } else { + return string(dmname) } - return "" } func getFsType(stat unix.Statfs_t) string { diff --git a/disk/disk_openbsd.go b/disk/disk_openbsd.go index 0ac752a..6fdf386 100644 --- a/disk/disk_openbsd.go +++ b/disk/disk_openbsd.go @@ -27,7 +27,9 @@ func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, erro } fs := make([]Statfs, count) - _, err = Getfsstat(fs, MNT_WAIT) + if _, err = Getfsstat(fs, MNT_WAIT); err != nil { + return ret, err + } for _, stat := range fs { opts := "rw" diff --git a/disk/disk_test.go b/disk/disk_test.go index 436b8a5..a0251cb 100644 --- a/disk/disk_test.go +++ b/disk/disk_test.go @@ -25,6 +25,8 @@ func TestDisk_partitions(t *testing.T) { if err != nil || len(ret) == 0 { t.Errorf("error %v", err) } + t.Log(ret) + empty := PartitionStat{} if len(ret) == 0 { t.Errorf("ret is empty") @@ -46,6 +48,7 @@ func TestDisk_io_counters(t *testing.T) { } empty := IOCountersStat{} for part, io := range ret { + t.Log(part, io) if io == empty { t.Errorf("io_counter error %v, %v", part, io) } @@ -93,7 +96,7 @@ func TestDiskIOCountersStat_String(t *testing.T) { WriteBytes: 400, SerialNumber: "SERIAL", } - e := `{"readCount":100,"mergedReadCount":0,"writeCount":200,"mergedWriteCount":0,"readBytes":300,"writeBytes":400,"readTime":0,"writeTime":0,"iopsInProgress":0,"ioTime":0,"weightedIO":0,"name":"sd01","serialNumber":"SERIAL"}` + e := `{"readCount":100,"mergedReadCount":0,"writeCount":200,"mergedWriteCount":0,"readBytes":300,"writeBytes":400,"readTime":0,"writeTime":0,"iopsInProgress":0,"ioTime":0,"weightedIO":0,"name":"sd01","serialNumber":"SERIAL","label":""}` if e != fmt.Sprintf("%v", v) { t.Errorf("DiskUsageStat string is invalid: %v", v) } diff --git a/disk/disk_windows.go b/disk/disk_windows.go index 7389b5a..80b1127 100644 --- a/disk/disk_windows.go +++ b/disk/disk_windows.go @@ -32,6 +32,13 @@ type Win32_PerfFormattedData struct { AvgDisksecPerRead uint64 AvgDisksecPerWrite uint64 } +type win32_DiskDrive struct { + DeviceID string + SerialNumber string +} +type win32_DiskPartition struct { + DeviceID string +} const WaitMSec = 500 @@ -144,8 +151,6 @@ func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOC ret := make(map[string]IOCountersStat, 0) var dst []Win32_PerfFormattedData - ctx, cancel := context.WithTimeout(context.Background(), common.Timeout) - defer cancel() err := common.WMIQueryWithContext(ctx, "SELECT * FROM Win32_PerfFormattedData_PerfDisk_LogicalDisk", &dst) if err != nil { return ret, err @@ -159,7 +164,7 @@ func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOC continue } - ret[d.Name] = IOCountersStat{ + tmpIO := IOCountersStat{ Name: d.Name, ReadCount: uint64(d.AvgDiskReadQueueLength), WriteCount: d.AvgDiskWriteQueueLength, @@ -168,6 +173,27 @@ func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOC ReadTime: d.AvgDisksecPerRead, WriteTime: d.AvgDisksecPerWrite, } + tmpIO.SerialNumber = GetDiskSerialNumber(d.Name) + ret[d.Name] = tmpIO } return ret, nil } + +// return disk serial number(not volume serial number) of given device or empty string on error. Name of device is drive letter, eg. C: +func GetDiskSerialNumber(name string) string { + return GetDiskSerialNumberWithContext(context.Background(), name) +} + +func GetDiskSerialNumberWithContext(ctx context.Context, name string) string { + var diskPart []win32_DiskPartition + var diskDrive []win32_DiskDrive + err := common.WMIQueryWithContext(ctx, "Associators of {Win32_LogicalDisk.DeviceID='"+name+"'} where AssocClass=Win32_LogicalDiskToPartition", &diskPart) + if err != nil || len(diskPart) <= 0 { + return "" + } + err = common.WMIQueryWithContext(ctx, "Associators of {Win32_DiskPartition.DeviceID='"+diskPart[0].DeviceID+"'} where AssocClass=Win32_DiskDriveToDiskPartition", &diskDrive) + if err != nil || len(diskDrive) <= 0 { + return "" + } + return diskDrive[0].SerialNumber +} diff --git a/docker/docker.go b/docker/docker.go index 1d932cf..76791c2 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -1,6 +1,7 @@ package docker import ( + "encoding/json" "errors" "github.com/shirou/gopsutil/internal/common" @@ -9,11 +10,7 @@ import ( var ErrDockerNotAvailable = errors.New("docker not available") var ErrCgroupNotAvailable = errors.New("cgroup not available") -var invoke common.Invoker - -func init() { - invoke = common.Invoke{} -} +var invoke common.Invoker = common.Invoke{} type CgroupMemStat struct { ContainerID string `json:"containerID"` @@ -50,6 +47,11 @@ type CgroupMemStat struct { MemFailCnt uint64 `json:"memoryFailcnt"` } +func (m CgroupMemStat) String() string { + s, _ := json.Marshal(m) + return string(s) +} + type CgroupDockerStat struct { ContainerID string `json:"containerID"` Name string `json:"name"` @@ -57,3 +59,8 @@ type CgroupDockerStat struct { Status string `json:"status"` Running bool `json:"running"` } + +func (c CgroupDockerStat) String() string { + s, _ := json.Marshal(c) + return string(s) +} diff --git a/docker/docker_linux.go b/docker/docker_linux.go index 8c722a0..4dcb477 100644 --- a/docker/docker_linux.go +++ b/docker/docker_linux.go @@ -4,7 +4,6 @@ package docker import ( "context" - "encoding/json" "fmt" "os" "os/exec" @@ -28,7 +27,7 @@ func GetDockerStatWithContext(ctx context.Context) ([]CgroupDockerStat, error) { return nil, ErrDockerNotAvailable } - out, err := invoke.Command(path, "ps", "-a", "--no-trunc", "--format", "{{.ID}}|{{.Image}}|{{.Names}}|{{.Status}}") + out, err := invoke.CommandWithContext(ctx, path, "ps", "-a", "--no-trunc", "--format", "{{.ID}}|{{.Image}}|{{.Names}}|{{.Status}}") if err != nil { return []CgroupDockerStat{}, err } @@ -57,11 +56,6 @@ func GetDockerStatWithContext(ctx context.Context) ([]CgroupDockerStat, error) { return ret, nil } -func (c CgroupDockerStat) String() string { - s, _ := json.Marshal(c) - return string(s) -} - // GetDockerIDList returnes a list of DockerID. // This requires certain permission. func GetDockerIDList() ([]string, error) { @@ -74,7 +68,7 @@ func GetDockerIDListWithContext(ctx context.Context) ([]string, error) { return nil, ErrDockerNotAvailable } - out, err := invoke.Command(path, "ps", "-q", "--no-trunc") + out, err := invoke.CommandWithContext(ctx, path, "ps", "-q", "--no-trunc") if err != nil { return []string{}, err } @@ -245,11 +239,6 @@ func CgroupMemDockerWithContext(ctx context.Context, containerID string) (*Cgrou return CgroupMem(containerID, common.HostSys("fs/cgroup/memory/docker")) } -func (m CgroupMemStat) String() string { - s, _ := json.Marshal(m) - return string(s) -} - // getCgroupFilePath constructs file path to get targetted stats file. func getCgroupFilePath(containerID, base, target, file string) string { if len(base) == 0 { diff --git a/docker/docker_notlinux.go b/docker/docker_notlinux.go index 088b3db..7a93c45 100644 --- a/docker/docker_notlinux.go +++ b/docker/docker_notlinux.go @@ -4,8 +4,6 @@ package docker import ( "context" - "encoding/json" - "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/internal/common" ) @@ -65,8 +63,3 @@ func CgroupMemDocker(containerid string) (*CgroupMemStat, error) { func CgroupMemDockerWithContext(ctx context.Context, containerid string) (*CgroupMemStat, error) { return CgroupMem(containerid, common.HostSys("fs/cgroup/memory/docker")) } - -func (m CgroupMemStat) String() string { - s, _ := json.Marshal(m) - return string(s) -} diff --git a/host/host.go b/host/host.go index a256a6c..1e9e9bb 100644 --- a/host/host.go +++ b/host/host.go @@ -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. diff --git a/host/host_darwin.go b/host/host_darwin.go index c82d990..5a4066a 100644 --- a/host/host_darwin.go +++ b/host/host_darwin.go @@ -40,7 +40,7 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) { uname, err := exec.LookPath("uname") if err == nil { - out, err := invoke.Command(uname, "-r") + out, err := invoke.CommandWithContext(ctx, uname, "-r") if err == nil { ret.KernelVersion = strings.ToLower(strings.TrimSpace(string(out))) } @@ -70,7 +70,7 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) { ret.Procs = uint64(len(procs)) } - values, err := common.DoSysctrl("kern.uuid") + values, err := common.DoSysctrlWithContext(ctx, "kern.uuid") if err == nil && len(values) == 1 && values[0] != "" { ret.HostID = strings.ToLower(values[0]) } @@ -90,7 +90,7 @@ func BootTimeWithContext(ctx context.Context) (uint64, error) { if t != 0 { return t, nil } - values, err := common.DoSysctrl("kern.boottime") + values, err := common.DoSysctrlWithContext(ctx, "kern.boottime") if err != nil { return 0, err } @@ -188,12 +188,12 @@ func PlatformInformationWithContext(ctx context.Context) (string, string, string return "", "", "", err } - out, err := invoke.Command(uname, "-s") + out, err := invoke.CommandWithContext(ctx, uname, "-s") if err == nil { platform = strings.ToLower(strings.TrimSpace(string(out))) } - 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))) } @@ -217,3 +217,11 @@ 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 +} diff --git a/host/host_darwin_cgo.go b/host/host_darwin_cgo.go deleted file mode 100644 index f0a4370..0000000 --- a/host/host_darwin_cgo.go +++ /dev/null @@ -1,51 +0,0 @@ -// +build darwin -// +build cgo - -package host - -// #cgo LDFLAGS: -framework IOKit -// #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_tmp(C.CString(key), C.CELSIUS)), - }) - } - return temperatures, nil -} diff --git a/host/host_darwin_nocgo.go b/host/host_darwin_nocgo.go deleted file mode 100644 index 7869f8c..0000000 --- a/host/host_darwin_nocgo.go +++ /dev/null @@ -1,18 +0,0 @@ -// +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 -} diff --git a/host/host_fallback.go b/host/host_fallback.go index f6537a9..e80d7ea 100644 --- a/host/host_fallback.go +++ b/host/host_fallback.go @@ -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 +} diff --git a/host/host_freebsd.go b/host/host_freebsd.go index 2f51c31..00a8519 100644 --- a/host/host_freebsd.go +++ b/host/host_freebsd.go @@ -8,7 +8,6 @@ import ( "encoding/binary" "io/ioutil" "os" - "os/exec" "runtime" "strings" "sync/atomic" @@ -168,25 +167,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))) - } - - out, err = invoke.Command(uname, "-r") - if err == nil { - version = strings.ToLower(strings.TrimSpace(string(out))) + version, err := unix.Sysctl("kern.osrelease") + if err != nil { + return "", "", "", err } - return platform, family, version, nil + return strings.ToLower(platform), "", strings.ToLower(version), nil } func Virtualization() (string, string, error) { diff --git a/host/host_linux.go b/host/host_linux.go index 65f3b64..0e09f15 100644 --- a/host/host_linux.go +++ b/host/host_linux.go @@ -104,25 +104,55 @@ func BootTimeWithContext(ctx context.Context) (uint64, error) { if t != 0 { return t, nil } - filename := common.HostProc("stat") + + 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 := 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 + + 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 } - 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") @@ -580,13 +610,35 @@ func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, err } } - for _, match := range files { - match = strings.Split(match, "_")[0] - name, err := ioutil.ReadFile(filepath.Join(filepath.Dir(match), "name")) + // example directory + // device/ temp1_crit_alarm temp2_crit_alarm temp3_crit_alarm temp4_crit_alarm temp5_crit_alarm temp6_crit_alarm temp7_crit_alarm + // name temp1_input temp2_input temp3_input temp4_input temp5_input temp6_input temp7_input + // power/ temp1_label temp2_label temp3_label temp4_label temp5_label temp6_label temp7_label + // subsystem/ temp1_max temp2_max temp3_max temp4_max temp5_max temp6_max temp7_max + // temp1_crit temp2_crit temp3_crit temp4_crit temp5_crit temp6_crit temp7_crit uevent + for _, file := range files { + filename := strings.Split(filepath.Base(file), "_") + if filename[1] == "label" { + // Do not try to read the temperature of the label file + continue + } + + // Get the label of the temperature you are reading + var label string + c, _ := ioutil.ReadFile(filepath.Join(filepath.Dir(file), filename[0]+"_label")) + if c != nil { + //format the label from "Core 0" to "core0_" + label = fmt.Sprintf("%s_", strings.Join(strings.Split(strings.TrimSpace(strings.ToLower(string(c))), " "), "")) + } + + // Get the name of the tempearture you are reading + name, err := ioutil.ReadFile(filepath.Join(filepath.Dir(file), "name")) if err != nil { return temperatures, err } - current, err := ioutil.ReadFile(match + "_input") + + // Get the temperature reading + current, err := ioutil.ReadFile(file) if err != nil { return temperatures, err } @@ -594,8 +646,10 @@ func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, err if err != nil { continue } + + tempName := strings.TrimSpace(strings.ToLower(string(strings.Join(filename[1:], "")))) temperatures = append(temperatures, TemperatureStat{ - SensorKey: strings.TrimSpace(string(name)), + SensorKey: fmt.Sprintf("%s_%s%s", strings.TrimSpace(string(name)), label, tempName), Temperature: temperature / 1000.0, }) } diff --git a/host/host_linux_mips64.go b/host/host_linux_mips64.go new file mode 100644 index 0000000..b0fca09 --- /dev/null +++ b/host/host_linux_mips64.go @@ -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 +} diff --git a/host/host_linux_mips64le.go b/host/host_linux_mips64le.go new file mode 100644 index 0000000..b0fca09 --- /dev/null +++ b/host/host_linux_mips64le.go @@ -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 +} diff --git a/host/host_openbsd.go b/host/host_openbsd.go index 83c2f7d..2ad64d7 100644 --- a/host/host_openbsd.go +++ b/host/host_openbsd.go @@ -113,12 +113,12 @@ func PlatformInformationWithContext(ctx context.Context) (string, string, string return "", "", "", err } - out, err := invoke.Command(uname, "-s") + out, err := invoke.CommandWithContext(ctx, uname, "-s") if err == nil { platform = strings.ToLower(strings.TrimSpace(string(out))) } - out, err = invoke.Command(uname, "-r") + out, err = invoke.CommandWithContext(ctx, uname, "-r") if err == nil { version = strings.ToLower(strings.TrimSpace(string(out))) } diff --git a/host/host_solaris.go b/host/host_solaris.go index 2560c06..bb83bfc 100644 --- a/host/host_solaris.go +++ b/host/host_solaris.go @@ -38,7 +38,7 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) { return nil, err } - out, err := invoke.Command(uname, "-srv") + out, err := invoke.CommandWithContext(ctx, uname, "-srv") if err != nil { return nil, err } @@ -87,7 +87,7 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) { // If everything works, use the current zone ID as the HostID if present. zonename, err := exec.LookPath("/usr/bin/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() { @@ -114,7 +114,7 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) { if result.HostID == "" { hostID, err := exec.LookPath("/usr/bin/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() { @@ -156,7 +156,7 @@ func BootTimeWithContext(ctx context.Context) (uint64, error) { 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 } @@ -220,7 +220,7 @@ func KernelVersionWithContext(ctx context.Context) (string, error) { return "", err } - out, err := invoke.Command(uname, "-srv") + out, err := invoke.CommandWithContext(ctx, uname, "-srv") if err != nil { return "", err } @@ -231,3 +231,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 +} diff --git a/host/host_test.go b/host/host_test.go index c420580..c211334 100644 --- a/host/host_test.go +++ b/host/host_test.go @@ -2,6 +2,7 @@ package host import ( "fmt" + "os" "testing" ) @@ -20,6 +21,10 @@ func TestHostInfo(t *testing.T) { } 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) @@ -30,6 +35,9 @@ func TestUptime(t *testing.T) { } 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) @@ -40,11 +48,16 @@ func TestBoot_time(t *testing.T) { if v < 946652400 { t.Errorf("Invalid Boottime, older than 2000-01-01") } + t.Logf("first boot time: %d", v) v2, err := BootTime() + if err != nil { + t.Errorf("error %v", err) + } if v != v2 { t.Errorf("cached boot time is different") } + t.Logf("second boot time: %d", v2) } func TestUsers(t *testing.T) { @@ -138,3 +151,15 @@ func TestKernelVersion(t *testing.T) { t.Logf("KernelVersion(): %s", version) } + +func TestPlatformInformation(t *testing.T) { + platform, family, version, err := PlatformInformation() + if err != nil { + t.Errorf("PlatformInformation() failed, %v", err) + } + if platform == "" { + t.Errorf("PlatformInformation() retuns empty: %v", platform) + } + + t.Logf("PlatformInformation(): %v, %v, %v", platform, family, version) +} diff --git a/host/host_windows.go b/host/host_windows.go index 123eddf..6d1ba88 100644 --- a/host/host_windows.go +++ b/host/host_windows.go @@ -57,7 +57,7 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) { } { - platform, family, version, err := PlatformInformation() + platform, family, version, err := PlatformInformationWithContext(ctx) if err == nil { ret.Platform = platform ret.PlatformFamily = family diff --git a/host/include/smc.c b/host/include/smc.c deleted file mode 100644 index 30a232b..0000000 --- a/host/include/smc.c +++ /dev/null @@ -1,700 +0,0 @@ -/* - * Apple System Management Controller (SMC) API from user space for Intel based - * Macs. Works by talking to the AppleSMC.kext (kernel extension), the driver - * for the SMC. - * - * smc.c - * libsmc - * - * Copyright (C) 2014 beltex - * - * Based off of fork from: - * osx-cpu-temp - * - * With credits to: - * - * Copyright (C) 2006 devnull - * Apple System Management Control (SMC) Tool - * - * Copyright (C) 2006 Hendrik Holtmann - * smcFanControl - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include -#include -#include "smc.h" - - -//------------------------------------------------------------------------------ -// MARK: MACROS -//------------------------------------------------------------------------------ - - -/** -Name of the SMC IOService as seen in the IORegistry. You can view it either via -command line with ioreg or through the IORegistryExplorer app (found on Apple's -developer site - Hardware IO Tools for Xcode) -*/ -#define IOSERVICE_SMC "AppleSMC" - - -/** -IOService for getting machine model name -*/ -#define IOSERVICE_MODEL "IOPlatformExpertDevice" - - -/** -SMC data types - 4 byte multi-character constants - -Sources: See TMP SMC keys in smc.h - -http://stackoverflow.com/questions/22160746/fpe2-and-sp78-data-types -*/ -#define DATA_TYPE_UINT8 "ui8 " -#define DATA_TYPE_UINT16 "ui16" -#define DATA_TYPE_UINT32 "ui32" -#define DATA_TYPE_FLAG "flag" -#define DATA_TYPE_FPE2 "fpe2" -#define DATA_TYPE_SFDS "{fds" -#define DATA_TYPE_SP78 "sp78" - - -//------------------------------------------------------------------------------ -// MARK: GLOBAL VARS -//------------------------------------------------------------------------------ - - -/** -Our connection to the SMC -*/ -static io_connect_t conn; - - -/** -Number of characters in an SMC key -*/ -static const int SMC_KEY_SIZE = 4; - - -/** -Number of characters in a data type "key" returned from the SMC. See data type -macros. -*/ -static const int DATA_TYPE_SIZE = 4; - - -//------------------------------------------------------------------------------ -// MARK: ENUMS -//------------------------------------------------------------------------------ - - -/** -Defined by AppleSMC.kext. See SMCParamStruct. - -These are SMC specific return codes -*/ -typedef enum { - kSMCSuccess = 0, - kSMCError = 1, - kSMCKeyNotFound = 0x84 -} kSMC_t; - - -/** -Defined by AppleSMC.kext. See SMCParamStruct. - -Function selectors. Used to tell the SMC which function inside it to call. -*/ -typedef enum { - kSMCUserClientOpen = 0, - kSMCUserClientClose = 1, - kSMCHandleYPCEvent = 2, - kSMCReadKey = 5, - kSMCWriteKey = 6, - kSMCGetKeyCount = 7, - kSMCGetKeyFromIndex = 8, - kSMCGetKeyInfo = 9 -} selector_t; - - -//------------------------------------------------------------------------------ -// MARK: STRUCTS -//------------------------------------------------------------------------------ - - -/** -Defined by AppleSMC.kext. See SMCParamStruct. -*/ -typedef struct { - unsigned char major; - unsigned char minor; - unsigned char build; - unsigned char reserved; - unsigned short release; -} SMCVersion; - - -/** -Defined by AppleSMC.kext. See SMCParamStruct. -*/ -typedef struct { - uint16_t version; - uint16_t length; - uint32_t cpuPLimit; - uint32_t gpuPLimit; - uint32_t memPLimit; -} SMCPLimitData; - - -/** -Defined by AppleSMC.kext. See SMCParamStruct. - -- dataSize : How many values written to SMCParamStruct.bytes -- dataType : Type of data written to SMCParamStruct.bytes. This lets us know how - to interpret it (translate it to human readable) -*/ -typedef struct { - IOByteCount dataSize; - uint32_t dataType; - uint8_t dataAttributes; -} SMCKeyInfoData; - - -/** -Defined by AppleSMC.kext. - -This is the predefined struct that must be passed to communicate with the -AppleSMC driver. While the driver is closed source, the definition of this -struct happened to appear in the Apple PowerManagement project at around -version 211, and soon after disappeared. It can be seen in the PrivateLib.c -file under pmconfigd. - -https://www.opensource.apple.com/source/PowerManagement/PowerManagement-211/ -*/ -typedef struct { - uint32_t key; - SMCVersion vers; - SMCPLimitData pLimitData; - SMCKeyInfoData keyInfo; - uint8_t result; - uint8_t status; - uint8_t data8; - uint32_t data32; - uint8_t bytes[32]; -} SMCParamStruct; - - -/** -Used for returning data from the SMC. -*/ -typedef struct { - uint8_t data[32]; - uint32_t dataType; - uint32_t dataSize; - kSMC_t kSMC; -} smc_return_t; - - -//------------------------------------------------------------------------------ -// MARK: HELPERS - TYPE CONVERSION -//------------------------------------------------------------------------------ - - -/** -Convert data from SMC of fpe2 type to human readable. - -:param: data Data from the SMC to be converted. Assumed data size of 2. -:returns: Converted data -*/ -static unsigned int from_fpe2(uint8_t data[32]) -{ - unsigned int ans = 0; - - // Data type for fan calls - fpe2 - // This is assumend to mean floating point, with 2 exponent bits - // http://stackoverflow.com/questions/22160746/fpe2-and-sp78-data-types - ans += data[0] << 6; - ans += data[1] << 2; - - return ans; -} - - -/** -Convert to fpe2 data type to be passed to SMC. - -:param: val Value to convert -:param: data Pointer to data array to place result -*/ -static void to_fpe2(unsigned int val, uint8_t *data) -{ - data[0] = val >> 6; - data[1] = (val << 2) ^ (data[0] << 8); -} - - -/** -Convert SMC key to uint32_t. This must be done to pass it to the SMC. - -:param: key The SMC key to convert -:returns: uint32_t translation. - Returns zero if key is not 4 characters in length. -*/ -static uint32_t to_uint32_t(char *key) -{ - uint32_t ans = 0; - uint32_t shift = 24; - - // SMC key is expected to be 4 bytes - thus 4 chars - if (strlen(key) != SMC_KEY_SIZE) { - return 0; - } - - for (int i = 0; i < SMC_KEY_SIZE; i++) { - ans += key[i] << shift; - shift -= 8; - } - - return ans; -} - - -/** -For converting the dataType return from the SMC to human readable 4 byte -multi-character constant. -*/ -static void to_string(uint32_t val, char *dataType) -{ - int shift = 24; - - for (int i = 0; i < DATA_TYPE_SIZE; i++) { - // To get each char, we shift it into the lower 8 bits, and then & by - // 255 to insolate it - dataType[i] = (val >> shift) & 0xff; - shift -= 8; - } -} - - -//------------------------------------------------------------------------------ -// MARK: HELPERS - TMP CONVERSION -//------------------------------------------------------------------------------ - - -/** -Celsius to Fahrenheit -*/ -static double to_fahrenheit(double tmp) -{ - // http://en.wikipedia.org/wiki/Fahrenheit#Definition_and_conversions - return (tmp * 1.8) + 32; -} - - -/** -Celsius to Kelvin -*/ -static double to_kelvin(double tmp) -{ - // http://en.wikipedia.org/wiki/Kelvin - return tmp + 273.15; -} - - -//------------------------------------------------------------------------------ -// MARK: "PRIVATE" FUNCTIONS -//------------------------------------------------------------------------------ - - -/** -Make a call to the SMC - -:param: inputStruct Struct that holds data telling the SMC what you want -:param: outputStruct Struct holding the SMC's response -:returns: I/O Kit return code -*/ -static kern_return_t call_smc(SMCParamStruct *inputStruct, - SMCParamStruct *outputStruct) -{ - kern_return_t result; - size_t inputStructCnt = sizeof(SMCParamStruct); - size_t outputStructCnt = sizeof(SMCParamStruct); - - result = IOConnectCallStructMethod(conn, kSMCHandleYPCEvent, - inputStruct, - inputStructCnt, - outputStruct, - &outputStructCnt); - - if (result != kIOReturnSuccess) { - // IOReturn error code lookup. See "Accessing Hardware From Applications - // -> Handling Errors" Apple doc - result = err_get_code(result); - } - - return result; -} - - -/** -Read data from the SMC - -:param: key The SMC key -*/ -static kern_return_t read_smc(char *key, smc_return_t *result_smc) -{ - kern_return_t result; - SMCParamStruct inputStruct; - SMCParamStruct outputStruct; - - memset(&inputStruct, 0, sizeof(SMCParamStruct)); - memset(&outputStruct, 0, sizeof(SMCParamStruct)); - memset(result_smc, 0, sizeof(smc_return_t)); - - // First call to AppleSMC - get key info - inputStruct.key = to_uint32_t(key); - inputStruct.data8 = kSMCGetKeyInfo; - - result = call_smc(&inputStruct, &outputStruct); - result_smc->kSMC = outputStruct.result; - - if (result != kIOReturnSuccess || outputStruct.result != kSMCSuccess) { - return result; - } - - // Store data for return - result_smc->dataSize = outputStruct.keyInfo.dataSize; - result_smc->dataType = outputStruct.keyInfo.dataType; - - - // Second call to AppleSMC - now we can get the data - inputStruct.keyInfo.dataSize = outputStruct.keyInfo.dataSize; - inputStruct.data8 = kSMCReadKey; - - result = call_smc(&inputStruct, &outputStruct); - result_smc->kSMC = outputStruct.result; - - if (result != kIOReturnSuccess || outputStruct.result != kSMCSuccess) { - return result; - } - - memcpy(result_smc->data, outputStruct.bytes, sizeof(outputStruct.bytes)); - - return result; -} - - -/** -Write data to the SMC. - -:returns: IOReturn IOKit return code -*/ -static kern_return_t write_smc(char *key, smc_return_t *result_smc) -{ - kern_return_t result; - SMCParamStruct inputStruct; - SMCParamStruct outputStruct; - - memset(&inputStruct, 0, sizeof(SMCParamStruct)); - memset(&outputStruct, 0, sizeof(SMCParamStruct)); - - // First call to AppleSMC - get key info - inputStruct.key = to_uint32_t(key); - inputStruct.data8 = kSMCGetKeyInfo; - - result = call_smc(&inputStruct, &outputStruct); - result_smc->kSMC = outputStruct.result; - - if (result != kIOReturnSuccess || outputStruct.result != kSMCSuccess) { - return result; - } - - // Check data is correct - if (result_smc->dataSize != outputStruct.keyInfo.dataSize || - result_smc->dataType != outputStruct.keyInfo.dataType) { - return kIOReturnBadArgument; - } - - // Second call to AppleSMC - now we can write the data - inputStruct.data8 = kSMCWriteKey; - inputStruct.keyInfo.dataSize = outputStruct.keyInfo.dataSize; - - // Set data to write - memcpy(inputStruct.bytes, result_smc->data, sizeof(result_smc->data)); - - result = call_smc(&inputStruct, &outputStruct); - result_smc->kSMC = outputStruct.result; - - return result; -} - - -/** -Get the model name of the machine. -*/ -static kern_return_t get_machine_model(io_name_t model) -{ - io_service_t service; - kern_return_t result; - - service = IOServiceGetMatchingService(kIOMasterPortDefault, - IOServiceMatching(IOSERVICE_MODEL)); - - if (service == 0) { - printf("ERROR: %s NOT FOUND\n", IOSERVICE_MODEL); - return kIOReturnError; - } - - // Get the model name - result = IORegistryEntryGetName(service, model); - IOObjectRelease(service); - - return result; -} - - -//------------------------------------------------------------------------------ -// MARK: "PUBLIC" FUNCTIONS -//------------------------------------------------------------------------------ - - -kern_return_t open_smc(void) -{ - kern_return_t result; - io_service_t service; - - service = IOServiceGetMatchingService(kIOMasterPortDefault, - IOServiceMatching(IOSERVICE_SMC)); - - if (service == 0) { - // NOTE: IOServiceMatching documents 0 on failure - printf("ERROR: %s NOT FOUND\n", IOSERVICE_SMC); - return kIOReturnError; - } - - result = IOServiceOpen(service, mach_task_self(), 0, &conn); - IOObjectRelease(service); - - return result; -} - - -kern_return_t close_smc(void) -{ - return IOServiceClose(conn); -} - - -bool is_key_valid(char *key) -{ - bool ans = false; - kern_return_t result; - smc_return_t result_smc; - - if (strlen(key) != SMC_KEY_SIZE) { - printf("ERROR: Invalid key size - must be 4 chars\n"); - return ans; - } - - // Try a read and see if it succeeds - result = read_smc(key, &result_smc); - - if (result == kIOReturnSuccess && result_smc.kSMC == kSMCSuccess) { - ans = true; - } - - return ans; -} - - -double get_tmp(char *key, tmp_unit_t unit) -{ - kern_return_t result; - smc_return_t result_smc; - - result = read_smc(key, &result_smc); - - if (!(result == kIOReturnSuccess && - result_smc.dataSize == 2 && - result_smc.dataType == to_uint32_t(DATA_TYPE_SP78))) { - // Error - return 0.0; - } - - // TODO: Create from_sp78() convert function - double tmp = result_smc.data[0]; - - switch (unit) { - case CELSIUS: - break; - case FAHRENHEIT: - tmp = to_fahrenheit(tmp); - break; - case KELVIN: - tmp = to_kelvin(tmp); - break; - } - - return tmp; -} - - -bool is_battery_powered(void) -{ - kern_return_t result; - smc_return_t result_smc; - - result = read_smc(BATT_PWR, &result_smc); - - if (!(result == kIOReturnSuccess && - result_smc.dataSize == 1 && - result_smc.dataType == to_uint32_t(DATA_TYPE_FLAG))) { - // Error - return false; - } - - return result_smc.data[0]; -} - - -bool is_optical_disk_drive_full(void) -{ - kern_return_t result; - smc_return_t result_smc; - - result = read_smc(ODD_FULL, &result_smc); - - if (!(result == kIOReturnSuccess && - result_smc.dataSize == 1 && - result_smc.dataType == to_uint32_t(DATA_TYPE_FLAG))) { - // Error - return false; - } - - return result_smc.data[0]; -} - - -//------------------------------------------------------------------------------ -// MARK: FAN FUNCTIONS -//------------------------------------------------------------------------------ - - -bool get_fan_name(unsigned int fan_num, fan_name_t name) -{ - char key[5]; - kern_return_t result; - smc_return_t result_smc; - - sprintf(key, "F%dID", fan_num); - result = read_smc(key, &result_smc); - - if (!(result == kIOReturnSuccess && - result_smc.dataSize == 16 && - result_smc.dataType == to_uint32_t(DATA_TYPE_SFDS))) { - return false; - } - - - /* - We know the data size is 16 bytes and the type is "{fds", a custom - struct defined by the AppleSMC.kext. See TMP enum sources for the - struct. - - The last 12 bytes contain the name of the fan, an array of chars, hence - the loop range. - */ - int index = 0; - for (int i = 4; i < 16; i++) { - // Check if at the end (name may not be full 12 bytes) - // Could check for 0 (null), but instead we check for 32 (space). This - // is a hack to remove whitespace. :) - if (result_smc.data[i] == 32) { - break; - } - - name[index] = result_smc.data[i]; - index++; - } - - return true; -} - - -int get_num_fans(void) -{ - kern_return_t result; - smc_return_t result_smc; - - result = read_smc(NUM_FANS, &result_smc); - - if (!(result == kIOReturnSuccess && - result_smc.dataSize == 1 && - result_smc.dataType == to_uint32_t(DATA_TYPE_UINT8))) { - // Error - return -1; - } - - return result_smc.data[0]; -} - - -unsigned int get_fan_rpm(unsigned int fan_num) -{ - char key[5]; - kern_return_t result; - smc_return_t result_smc; - - sprintf(key, "F%dAc", fan_num); - result = read_smc(key, &result_smc); - - if (!(result == kIOReturnSuccess && - result_smc.dataSize == 2 && - result_smc.dataType == to_uint32_t(DATA_TYPE_FPE2))) { - // Error - return 0; - } - - return from_fpe2(result_smc.data); -} - - -bool set_fan_min_rpm(unsigned int fan_num, unsigned int rpm, bool auth) -{ - // TODO: Add rpm val safety check - char key[5]; - bool ans = false; - kern_return_t result; - smc_return_t result_smc; - - memset(&result_smc, 0, sizeof(smc_return_t)); - - // TODO: Don't use magic number - result_smc.dataSize = 2; - result_smc.dataType = to_uint32_t(DATA_TYPE_FPE2); - to_fpe2(rpm, result_smc.data); - - sprintf(key, "F%dMn", fan_num); - result = write_smc(key, &result_smc); - - if (result == kIOReturnSuccess && result_smc.kSMC == kSMCSuccess) { - ans = true; - } - - return ans; -} diff --git a/host/include/smc.h b/host/include/smc.h deleted file mode 100644 index b156368..0000000 --- a/host/include/smc.h +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Apple System Management Controller (SMC) API from user space for Intel based - * Macs. Works by talking to the AppleSMC.kext (kernel extension), the driver - * for the SMC. - * - * smc.h - * libsmc - * - * Copyright (C) 2014 beltex - * - * Based off of fork from: - * osx-cpu-temp - * - * With credits to: - * - * Copyright (C) 2006 devnull - * Apple System Management Control (SMC) Tool - * - * Copyright (C) 2006 Hendrik Holtmann - * smcFanControl - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include - - -//------------------------------------------------------------------------------ -// MARK: MACROS -//------------------------------------------------------------------------------ - - -/** -SMC keys for temperature sensors - 4 byte multi-character constants - -Not applicable to all Mac's of course. In adition, the definition of the codes -may not be 100% accurate necessarily. Finally, list is incomplete. - -Presumed letter translations: - -- T = Temperature (if first char) -- C = CPU -- G = GPU -- P = Proximity -- D = Diode -- H = Heatsink - -Sources: - -- https://www.apple.com/downloads/dashboard/status/istatpro.html -- https://github.com/hholtmann/smcFanControl -- https://github.com/jedda/OSX-Monitoring-Tools -- http://www.parhelia.ch/blog/statics/k3_keys.html -*/ -#define AMBIENT_AIR_0 "TA0P" -#define AMBIENT_AIR_1 "TA1P" -#define CPU_0_DIODE "TC0D" -#define CPU_0_HEATSINK "TC0H" -#define CPU_0_PROXIMITY "TC0P" -#define ENCLOSURE_BASE_0 "TB0T" -#define ENCLOSURE_BASE_1 "TB1T" -#define ENCLOSURE_BASE_2 "TB2T" -#define ENCLOSURE_BASE_3 "TB3T" -#define GPU_0_DIODE "TG0D" -#define GPU_0_HEATSINK "TG0H" -#define GPU_0_PROXIMITY "TG0P" -#define HARD_DRIVE_BAY "TH0P" -#define MEMORY_SLOT_0 "TM0S" -#define MEMORY_SLOTS_PROXIMITY "TM0P" -#define NORTHBRIDGE "TN0H" -#define NORTHBRIDGE_DIODE "TN0D" -#define NORTHBRIDGE_PROXIMITY "TN0P" -#define THUNDERBOLT_0 "TI0P" -#define THUNDERBOLT_1 "TI1P" -#define WIRELESS_MODULE "TW0P" - - -/** -SMC keys for fans - 4 byte multi-character constants - -Number of fans on Macs vary of course, thus not all keys will be applicable. - -Presumed letter translations: - -- F = Fan -- Ac = Acutal -- Mn = Min -- Mx = Max -- Sf = Safe -- Tg = Target - -Sources: See TMP SMC keys -*/ -#define FAN_0 "F0Ac" -#define FAN_0_MIN_RPM "F0Mn" -#define FAN_0_MAX_RPM "F0Mx" -#define FAN_0_SAFE_RPM "F0Sf" -#define FAN_0_TARGET_RPM "F0Tg" -#define FAN_1 "F1Ac" -#define FAN_1_MIN_RPM "F1Mn" -#define FAN_1_MAX_RPM "F1Mx" -#define FAN_1_SAFE_RPM "F1Sf" -#define FAN_1_TARGET_RPM "F1Tg" -#define FAN_2 "F2Ac" -#define FAN_2_MIN_RPM "F2Mn" -#define FAN_2_MAX_RPM "F2Mx" -#define FAN_2_SAFE_RPM "F2Sf" -#define FAN_2_TARGET_RPM "F2Tg" -#define NUM_FANS "FNum" -#define FORCE_BITS "FS! " - - -/** -Misc SMC keys - 4 byte multi-character constants - -Sources: See TMP SMC keys -*/ -#define BATT_PWR "BATP" -#define NUM_KEYS "#KEY" -#define ODD_FULL "MSDI" - - -//------------------------------------------------------------------------------ -// MARK: TYPES -//------------------------------------------------------------------------------ - - -typedef char fan_name_t[13]; - - -//------------------------------------------------------------------------------ -// MARK: ENUMS -//------------------------------------------------------------------------------ - - -typedef enum { - CELSIUS, - FAHRENHEIT, - KELVIN -} tmp_unit_t; - - -//------------------------------------------------------------------------------ -// MARK: PROTOTYPES -//------------------------------------------------------------------------------ - - -/** -Open a connection to the SMC - -:returns: kIOReturnSuccess on successful connection to the SMC. -*/ -kern_return_t open_smc(void); - - -/** -Close connection to the SMC - -:returns: kIOReturnSuccess on successful close of connection to the SMC. -*/ -kern_return_t close_smc(void); - - -/** -Check if an SMC key is valid. Useful for determining if a certain machine has -particular sensor or fan for example. - -:param: key The SMC key to check. 4 byte multi-character constant. Must be 4 - characters in length. -:returns: True if the key is found, false otherwise -*/ -bool is_key_valid(char *key); - - -/** -Get the current temperature from a sensor - -:param: key The temperature sensor to read from -:param: unit The unit for the temperature value. -:returns: Temperature of sensor. If the sensor is not found, or an error - occurs, return will be zero -*/ -double get_tmp(char *key, tmp_unit_t unit); - - -/** -Is the machine being powered by the battery? - -:returns: True if it is, false otherwise -*/ -bool is_battery_powered(void); - - -/** -Is there a CD in the optical disk drive (ODD)? - -:returns: True if there is, false otherwise -*/ -bool is_optical_disk_drive_full(void); - - -/** -Get the name of a fan. - -:param: fanNum The number of the fan to check -:param: name The name of the fan. Return will be empty on error. -:returns: True if successful, false otherwise. -*/ -bool get_fan_name(unsigned int fan_num, fan_name_t name); - - -/** -Get the number of fans on this machine. - -:returns: The number of fans. If an error occurs, return will be -1. -*/ -int get_num_fans(void); - - -/** -Get the current speed (RPM - revolutions per minute) of a fan. - -:param: fan_num The number of the fan to check -:returns: The fan RPM. If the fan is not found, or an error occurs, return - will be zero -*/ -UInt get_fan_rpm(UInt fan_num); - - -/** -Set the minimum speed (RPM - revolutions per minute) of a fan. This method -requires root privileges. By minimum we mean that OS X can interject and -raise the fan speed if needed, however it will not go below this. - -WARNING: You are playing with hardware here, BE CAREFUL. - -:param: fan_num The number of the fan to set -:param: rpm The speed you would like to set the fan to. -:param: auth Should the function do authentication? -:return: True if successful, false otherwise -*/ -bool set_fan_min_rpm(unsigned int fan_num, unsigned int rpm, bool auth); diff --git a/internal/common/common.go b/internal/common/common.go index fcee6be..f9373ee 100644 --- a/internal/common/common.go +++ b/internal/common/common.go @@ -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. diff --git a/internal/common/common_darwin.go b/internal/common/common_darwin.go index 2b6d4c1..3e85cc0 100644 --- a/internal/common/common_darwin.go +++ b/internal/common/common_darwin.go @@ -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) { +func DoSysctrlWithContext(ctx context.Context, mib string) ([]string, error) { sysctl, err := exec.LookPath("/usr/sbin/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 { diff --git a/internal/common/common_test.go b/internal/common/common_test.go index 3b05d53..cd33388 100644 --- a/internal/common/common_test.go +++ b/internal/common/common_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "reflect" + "runtime" "strings" "testing" ) @@ -92,6 +93,9 @@ func TestPathExists(t *testing.T) { } 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) diff --git a/internal/common/common_unix.go b/internal/common/common_unix.go index cc934dd..750a592 100644 --- a/internal/common/common_unix.go +++ b/internal/common/common_unix.go @@ -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,7 +21,7 @@ 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 err.Error() == "exit status 1" && len(out) == 0 { @@ -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 } diff --git a/internal/common/common_windows.go b/internal/common/common_windows.go index 1dffe61..b02c5cf 100644 --- a/internal/common/common_windows.go +++ b/internal/common/common_windows.go @@ -115,6 +115,12 @@ func CreateCounter(query windows.Handle, pname, cname string) (*CounterInfo, err // WMIQueryWithContext - wraps wmi.Query with a timed-out context to avoid hanging func WMIQueryWithContext(ctx context.Context, query string, dst interface{}, connectServerArgs ...interface{}) error { + if _, ok := ctx.Deadline(); !ok { + ctxTimeout, cancel := context.WithTimeout(ctx, Timeout) + defer cancel() + ctx = ctxTimeout + } + errChan := make(chan error, 1) go func() { errChan <- wmi.Query(query, dst, connectServerArgs...) diff --git a/load/load.go b/load/load.go index dfe32a1..9085889 100644 --- a/load/load.go +++ b/load/load.go @@ -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 AvgStat struct { Load1 float64 `json:"load1"` diff --git a/load/load_bsd.go b/load/load_bsd.go index cf524ef..dfac10c 100644 --- a/load/load_bsd.go +++ b/load/load_bsd.go @@ -49,7 +49,7 @@ func MiscWithContext(ctx context.Context) (*MiscStat, error) { if err != nil { return nil, err } - out, err := invoke.Command(bin, "axo", "state") + out, err := invoke.CommandWithContext(ctx, bin, "axo", "state") if err != nil { return nil, err } diff --git a/load/load_darwin.go b/load/load_darwin.go index 50f626c..cd7b74d 100644 --- a/load/load_darwin.go +++ b/load/load_darwin.go @@ -16,7 +16,7 @@ func Avg() (*AvgStat, error) { } func AvgWithContext(ctx context.Context) (*AvgStat, error) { - values, err := common.DoSysctrl("vm.loadavg") + values, err := common.DoSysctrlWithContext(ctx, "vm.loadavg") if err != nil { return nil, err } @@ -56,7 +56,7 @@ func MiscWithContext(ctx context.Context) (*MiscStat, error) { if err != nil { return nil, err } - out, err := invoke.Command(bin, "axo", "state") + out, err := invoke.CommandWithContext(ctx, bin, "axo", "state") if err != nil { return nil, err } diff --git a/mem/mem.go b/mem/mem.go index 87dfb53..3d71d96 100644 --- a/mem/mem.go +++ b/mem/mem.go @@ -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. @@ -49,15 +45,31 @@ type VirtualMemoryStat struct { // 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"` + 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 { diff --git a/mem/mem_darwin.go b/mem/mem_darwin.go index 3e259a0..4fe7009 100644 --- a/mem/mem_darwin.go +++ b/mem/mem_darwin.go @@ -35,7 +35,7 @@ func SwapMemory() (*SwapMemoryStat, error) { func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) { var ret *SwapMemoryStat - swapUsage, err := common.DoSysctrl("vm.swapusage") + swapUsage, err := common.DoSysctrlWithContext(ctx, "vm.swapusage") if err != nil { return ret, err } diff --git a/mem/mem_freebsd.go b/mem/mem_freebsd.go index e691227..2d65f80 100644 --- a/mem/mem_freebsd.go +++ b/mem/mem_freebsd.go @@ -5,9 +5,7 @@ package mem import ( "context" "errors" - "os/exec" - "strconv" - "strings" + "unsafe" "golang.org/x/sys/unix" ) @@ -41,7 +39,7 @@ func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) { if err != nil { return nil, err } - buffers, err := unix.SysctlUint32("vfs.bufspace") + buffers, err := unix.SysctlUint64("vfs.bufspace") if err != nil { return nil, err } @@ -69,53 +67,66 @@ 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()) } +// Constants from vm/vm_param.h +// nolint: golint +const ( + XSWDEV_VERSION = 1 +) + +// Types from vm/vm_param.h +type xswdev 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) { - swapinfo, err := exec.LookPath("swapinfo") + // FreeBSD can have multiple swap devices so we total them up + i, err := unix.SysctlUint32("vm.nswapdev") if err != nil { return nil, err } - out, err := invoke.Command(swapinfo) + if i == 0 { + return nil, errors.New("no swap devices found") + } + + c := int(i) + + i, err = unix.SysctlUint32("vm.stats.vm.v_page_size") 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 - } + pageSize := uint64(i) - 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) + 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 } - up_v, err := strconv.ParseFloat(u, 64) - if err != nil { - return nil, err + + xsw := (*xswdev)(unsafe.Pointer(&buf[0])) + if xsw.Version != XSWDEV_VERSION { + return nil, errors.New("xswdev version mismatch") } + s.Total += uint64(xsw.NBlks) + s.Used += uint64(xsw.Used) + } - return &SwapMemoryStat{ - Total: total_v, - Used: used_v, - Free: free_v, - UsedPercent: up_v, - }, nil + 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 nil, errors.New("no swap devices found") + return s, nil } diff --git a/mem/mem_linux.go b/mem/mem_linux.go index fe6c4e1..fcc9a3f 100644 --- a/mem/mem_linux.go +++ b/mem/mem_linux.go @@ -65,13 +65,43 @@ func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) { 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 + ret.Used = ret.Total - ret.Free - ret.Buffers - ret.Cached + ret.UsedPercent = float64(ret.Used) / float64(ret.Total) * 100.0 return ret, nil } diff --git a/mem/mem_openbsd.go b/mem/mem_openbsd.go index e4834f3..35472a3 100644 --- a/mem/mem_openbsd.go +++ b/mem/mem_openbsd.go @@ -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 } diff --git a/mem/mem_solaris.go b/mem/mem_solaris.go index d6c6a5f..0736bc4 100644 --- a/mem/mem_solaris.go +++ b/mem/mem_solaris.go @@ -57,7 +57,8 @@ func zoneName() (string, error) { return "", err } - out, err := invoke.Command(zonename) + ctx := context.Background() + out, err := invoke.CommandWithContext(ctx, zonename) if err != nil { return "", err } @@ -73,7 +74,8 @@ func globalZoneMemoryCapacity() (uint64, error) { return 0, err } - out, err := invoke.Command(prtconf) + ctx := context.Background() + out, err := invoke.CommandWithContext(ctx, prtconf) if err != nil { return 0, err } @@ -99,7 +101,8 @@ func nonGlobalZoneMemoryCapacity() (uint64, error) { 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 } diff --git a/mem/mem_test.go b/mem/mem_test.go index 4a1ae7b..118047c 100644 --- a/mem/mem_test.go +++ b/mem/mem_test.go @@ -57,7 +57,7 @@ func TestVirtualMemoryStat_String(t *testing.T) { UsedPercent: 30.1, Free: 40, } - e := `{"total":10,"available":20,"used":30,"usedPercent":30.1,"free":40,"active":0,"inactive":0,"wired":0,"buffers":0,"cached":0,"writeback":0,"dirty":0,"writebacktmp":0,"shared":0,"slab":0,"pagetables":0,"swapcached":0}` + 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,"commitlimit":0,"committedas":0,"hightotal":0,"highfree":0,"lowtotal":0,"lowfree":0,"swaptotal":0,"swapfree":0,"mapped":0,"vmalloctotal":0,"vmallocused":0,"vmallocchunk":0,"hugepagestotal":0,"hugepagesfree":0,"hugepagesize":0}` if e != fmt.Sprintf("%v", v) { t.Errorf("VirtualMemoryStat string is invalid: %v", v) } diff --git a/mem/mem_windows.go b/mem/mem_windows.go index d40f6cf..cfdf8bd 100644 --- a/mem/mem_windows.go +++ b/mem/mem_windows.go @@ -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 diff --git a/net/net.go b/net/net.go index 428e68e..c31f512 100644 --- a/net/net.go +++ b/net/net.go @@ -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 diff --git a/net/net_darwin.go b/net/net_darwin.go index 2afb0f0..0d89280 100644 --- a/net/net_darwin.go +++ b/net/net_darwin.go @@ -180,7 +180,7 @@ func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat, } // 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 } @@ -208,7 +208,7 @@ func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat, 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 +227,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)) diff --git a/net/net_freebsd.go b/net/net_freebsd.go index 9daed8d..ce02415 100644 --- a/net/net_freebsd.go +++ b/net/net_freebsd.go @@ -21,7 +21,7 @@ func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat, 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 } diff --git a/net/net_openbsd.go b/net/net_openbsd.go index 4b194eb..3e74e8f 100644 --- a/net/net_openbsd.go +++ b/net/net_openbsd.go @@ -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 } @@ -290,7 +290,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 diff --git a/net/net_test.go b/net/net_test.go index 1559387..80f56af 100644 --- a/net/net_test.go +++ b/net/net_test.go @@ -61,6 +61,9 @@ func TestNetConnectionStatString(t *testing.T) { func TestNetIOCountersAll(t *testing.T) { v, err := IOCounters(false) + if err != nil { + t.Errorf("Could not get NetIOCounters: %v", err) + } per, err := IOCounters(true) if err != nil { t.Errorf("Could not get NetIOCounters: %v", err) diff --git a/net/net_unix.go b/net/net_unix.go index 5ceb9cc..e3ff262 100644 --- a/net/net_unix.go +++ b/net/net_unix.go @@ -66,7 +66,7 @@ func ConnectionsPidWithContext(ctx context.Context, kind string, pid int32) ([]C return ret, common.ErrNotImplementedError } - r, err := common.CallLsof(invoke, pid, args...) + r, err := common.CallLsofWithContext(ctx, invoke, pid, args...) if err != nil { return nil, err } diff --git a/process/process.go b/process/process.go index 09b1e6a..a4b24f4 100644 --- a/process/process.go +++ b/process/process.go @@ -3,6 +3,7 @@ package process import ( "context" "encoding/json" + "errors" "runtime" "time" @@ -11,11 +12,10 @@ 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") +) type Process struct { Pid int32 `json:"pid"` @@ -31,6 +31,8 @@ type Process struct { lastCPUTimes *cpu.TimesStat lastCPUTime time.Time + + tgid int32 } type OpenFilesStat struct { diff --git a/process/process_darwin.go b/process/process_darwin.go index 005ae3c..c949154 100644 --- a/process/process_darwin.go +++ b/process/process_darwin.go @@ -51,7 +51,7 @@ func Pids() ([]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 +72,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 } @@ -96,6 +96,9 @@ func (p *Process) NameWithContext(ctx context.Context) (string, error) { return common.IntToString(k.Proc.P_comm[:]), nil } +func (p *Process) Tgid() (int32, error) { + return 0, common.ErrNotImplementedError +} func (p *Process) Exe() (string, error) { return p.ExeWithContext(context.Background()) } @@ -116,9 +119,9 @@ func (p *Process) ExeWithContext(ctx context.Context) (string, error) { 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\\//\\//") + lsof := exec.CommandContext(ctx, lsof_bin, "-p", strconv.Itoa(int(p.Pid)), "-Fpfn") + awk := exec.CommandContext(ctx, awk_bin, "NR==5{print}") + sed := exec.CommandContext(ctx, sed_bin, "s/n\\//\\//") output, _, err := common.Pipeline(lsof, awk, sed) @@ -138,7 +141,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 } @@ -155,7 +158,7 @@ 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 } @@ -166,7 +169,7 @@ func (p *Process) CreateTime() (int64, error) { } func (p *Process) CreateTimeWithContext(ctx context.Context) (int64, error) { - r, err := callPs("etime", p.Pid, false) + r, err := callPsWithContext(ctx, "etime", p.Pid, false) if err != nil { return 0, err } @@ -207,7 +210,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 } @@ -229,7 +232,7 @@ 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 } @@ -347,7 +350,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 } @@ -392,7 +395,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 @@ -426,7 +429,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 } @@ -464,7 +467,7 @@ func (p *Process) Children() ([]*Process, error) { } 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 } @@ -520,6 +523,10 @@ func (p *Process) MemoryMapsWithContext(ctx context.Context, grouped bool) (*[]M } func Processes() ([]*Process, error) { + return ProcessesWithContext(context.Background()) +} + +func ProcessesWithContext(ctx context.Context) ([]*Process, error) { results := []*Process{} mib := []int32{CTLKern, KernProc, KernProcAll, 0} @@ -602,7 +609,7 @@ func NewProcess(pid int32) (*Process, error) { // 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 @@ -616,7 +623,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 } diff --git a/process/process_fallback.go b/process/process_fallback.go index bc3bb1d..2f18afe 100644 --- a/process/process_fallback.go +++ b/process/process_fallback.go @@ -54,6 +54,9 @@ func (p *Process) Name() (string, error) { func (p *Process) NameWithContext(ctx context.Context) (string, error) { return "", common.ErrNotImplementedError } +func (p *Process) Tgid() (int32, error) { + return 0, common.ErrNotImplementedError +} func (p *Process) Exe() (string, error) { return p.ExeWithContext(context.Background()) } diff --git a/process/process_freebsd.go b/process/process_freebsd.go index 7b85e8a..af2b3b1 100644 --- a/process/process_freebsd.go +++ b/process/process_freebsd.go @@ -63,6 +63,9 @@ func (p *Process) NameWithContext(ctx context.Context) (string, error) { return common.IntToString(k.Comm[:]), nil } +func (p *Process) Tgid() (int32, error) { + return 0, common.ErrNotImplementedError +} func (p *Process) Exe() (string, error) { return p.ExeWithContext(context.Background()) } @@ -352,7 +355,7 @@ func (p *Process) Children() ([]*Process, error) { } 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 } @@ -408,6 +411,10 @@ func (p *Process) MemoryMapsWithContext(ctx context.Context, grouped bool) (*[]M } func Processes() ([]*Process, error) { + return ProcessesWithContext(context.Background()) +} + +func ProcessesWithContext(ctx context.Context) ([]*Process, error) { results := []*Process{} mib := []int32{CTLKern, KernProc, KernProcProc, 0} diff --git a/process/process_linux.go b/process/process_linux.go index a9aed88..341420b 100644 --- a/process/process_linux.go +++ b/process/process_linux.go @@ -7,7 +7,6 @@ import ( "bytes" "context" "encoding/json" - "errors" "fmt" "io/ioutil" "math" @@ -23,10 +22,7 @@ import ( "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 @@ -109,6 +105,16 @@ func (p *Process) NameWithContext(ctx context.Context) (string, error) { return p.name, nil } +// 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 { + return 0, err + } + } + return p.tgid, nil +} + // Exe returns executable path of the process. func (p *Process) Exe() (string, error) { return p.ExeWithContext(context.Background()) @@ -225,10 +231,15 @@ func (p *Process) Terminal() (string, error) { } func (p *Process) TerminalWithContext(ctx context.Context) (string, error) { - terminal, _, _, _, _, _, err := p.fillFromStat() + t, _, _, _, _, _, err := p.fillFromStat() + if err != nil { + return "", err + } + termmap, err := getTerminalMap() if err != nil { return "", err } + terminal := termmap[t] return terminal, nil } @@ -451,7 +462,7 @@ func (p *Process) Children() ([]*Process, error) { } 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 @@ -985,6 +996,12 @@ func (p *Process) fillFromStatusWithContext(ctx context.Context) error { return err } p.parent = int32(pval) + case "Tgid": + pval, err := strconv.ParseInt(value, 10, 32) + if err != nil { + return err + } + p.tgid = int32(pval) case "Uid": p.uids = make([]int32, 0, 4) for _, i := range strings.Split(value, "\t") { @@ -1099,11 +1116,11 @@ func (p *Process) fillFromStatusWithContext(ctx context.Context) error { return nil } -func (p *Process) fillFromTIDStat(tid int32) (string, int32, *cpu.TimesStat, int64, uint32, int32, error) { +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) (string, int32, *cpu.TimesStat, int64, uint32, int32, error) { +func (p *Process) fillFromTIDStatWithContext(ctx context.Context, tid int32) (uint64, int32, *cpu.TimesStat, int64, uint32, int32, error) { pid := p.Pid var statPath string @@ -1115,7 +1132,7 @@ func (p *Process) fillFromTIDStatWithContext(ctx context.Context, tid int32) (st contents, err := ioutil.ReadFile(statPath) if err != nil { - return "", 0, nil, 0, 0, 0, err + return 0, 0, nil, 0, 0, 0, err } fields := strings.Fields(string(contents)) @@ -1124,28 +1141,23 @@ func (p *Process) fillFromTIDStatWithContext(ctx context.Context, tid int32) (st i++ } - termmap, err := getTerminalMap() - terminal := "" - if err == nil { - t, err := strconv.ParseUint(fields[i+5], 10, 64) - if err != nil { - return "", 0, nil, 0, 0, 0, err - } - terminal = termmap[t] + terminal, err := strconv.ParseUint(fields[i+5], 10, 64) + if err != nil { + return 0, 0, nil, 0, 0, 0, err } ppid, err := strconv.ParseInt(fields[i+2], 10, 32) if err != nil { - return "", 0, nil, 0, 0, 0, err + return 0, 0, nil, 0, 0, 0, err } utime, err := strconv.ParseFloat(fields[i+12], 64) if err != nil { - return "", 0, nil, 0, 0, 0, err + return 0, 0, nil, 0, 0, 0, err } stime, err := strconv.ParseFloat(fields[i+13], 64) if err != nil { - return "", 0, nil, 0, 0, 0, err + return 0, 0, nil, 0, 0, 0, err } cpuTimes := &cpu.TimesStat{ @@ -1157,12 +1169,15 @@ func (p *Process) fillFromTIDStatWithContext(ctx context.Context, tid int32) (st bootTime, _ := host.BootTime() t, err := strconv.ParseUint(fields[i+20], 10, 64) if err != nil { - return "", 0, nil, 0, 0, 0, err + return 0, 0, nil, 0, 0, 0, err } ctime := (t / uint64(ClockTicks)) + uint64(bootTime) createTime := int64(ctime * 1000) rtpriority, err := strconv.ParseInt(fields[i+16], 10, 32) + if err != nil { + return 0, 0, nil, 0, 0, 0, err + } if rtpriority < 0 { rtpriority = rtpriority*-1 - 1 } else { @@ -1177,11 +1192,11 @@ func (p *Process) fillFromTIDStatWithContext(ctx context.Context, tid int32) (st return terminal, int32(ppid), cpuTimes, createTime, uint32(rtpriority), nice, nil } -func (p *Process) fillFromStat() (string, int32, *cpu.TimesStat, int64, uint32, int32, error) { +func (p *Process) fillFromStat() (uint64, int32, *cpu.TimesStat, int64, uint32, int32, error) { return p.fillFromStatWithContext(context.Background()) } -func (p *Process) fillFromStatWithContext(ctx context.Context) (string, int32, *cpu.TimesStat, int64, uint32, int32, error) { +func (p *Process) fillFromStatWithContext(ctx context.Context) (uint64, int32, *cpu.TimesStat, int64, uint32, int32, error) { return p.fillFromTIDStat(-1) } @@ -1197,6 +1212,10 @@ func PidsWithContext(ctx context.Context) ([]int32, error) { // Process returns a slice of pointers to Process structs for all // currently running processes. func Processes() ([]*Process, error) { + return ProcessesWithContext(context.Background()) +} + +func ProcessesWithContext(ctx context.Context) ([]*Process, error) { out := []*Process{} pids, err := Pids() diff --git a/process/process_openbsd.go b/process/process_openbsd.go index 2a8eec7..b7b2cba 100644 --- a/process/process_openbsd.go +++ b/process/process_openbsd.go @@ -5,6 +5,7 @@ package process import ( "C" "bytes" + "context" "encoding/binary" "strings" "unsafe" @@ -15,7 +16,6 @@ import ( net "github.com/shirou/gopsutil/net" "golang.org/x/sys/unix" ) -import "context" // MemoryInfoExStat is different between OSes type MemoryInfoExStat struct { @@ -66,6 +66,9 @@ func (p *Process) NameWithContext(ctx context.Context) (string, error) { return common.IntToString(k.Comm[:]), nil } +func (p *Process) Tgid() (int32, error) { + return 0, common.ErrNotImplementedError +} func (p *Process) Exe() (string, error) { return p.ExeWithContext(context.Background()) } @@ -342,7 +345,7 @@ func (p *Process) Children() ([]*Process, error) { } 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 } @@ -398,6 +401,10 @@ func (p *Process) MemoryMapsWithContext(ctx context.Context, grouped bool) (*[]M } func Processes() ([]*Process, error) { + return ProcessesWithContext(context.Background()) +} + +func ProcessesWithContext(ctx context.Context) ([]*Process, error) { results := []*Process{} buf, length, err := CallKernProcSyscall(KernProcAll, 0) diff --git a/process/process_posix.go b/process/process_posix.go index d058278..8ffb6b7 100644 --- a/process/process_posix.go +++ b/process/process_posix.go @@ -26,6 +26,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 +48,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) } diff --git a/process/process_test.go b/process/process_test.go index 84c24bd..7cdcedb 100644 --- a/process/process_test.go +++ b/process/process_test.go @@ -85,6 +85,9 @@ func Test_Process_memory_maps(t *testing.T) { checkPid := os.Getpid() ret, err := NewProcess(int32(checkPid)) + if err != nil { + t.Errorf("error %v", err) + } mmaps, err := ret.MemoryMaps(false) if err != nil { @@ -301,6 +304,10 @@ func Test_Process_CpuPercentLoop(t *testing.T) { } func Test_Process_CreateTime(t *testing.T) { + if os.Getenv("CIRCLECI") == "true" { + t.Skip("Skip CI") + } + p := testGetProcess() c, err := p.CreateTime() diff --git a/process/process_windows.go b/process/process_windows.go index c875ec2..e81d67d 100644 --- a/process/process_windows.go +++ b/process/process_windows.go @@ -144,8 +144,6 @@ func GetWin32ProcWithContext(ctx context.Context, pid int32) ([]Win32_Process, e var dst []Win32_Process query := fmt.Sprintf("WHERE ProcessId = %d", pid) q := wmi.CreateQuery(&dst, query) - ctx, cancel := context.WithTimeout(context.Background(), common.Timeout) - defer cancel() err := common.WMIQueryWithContext(ctx, q, &dst) if err != nil { return []Win32_Process{}, fmt.Errorf("could not get win32Proc: %s", err) @@ -170,6 +168,10 @@ func (p *Process) NameWithContext(ctx context.Context) (string, error) { return name, nil } +func (p *Process) Tgid() (int32, error) { + return 0, common.ErrNotImplementedError +} + func (p *Process) Exe() (string, error) { return p.ExeWithContext(context.Background()) } @@ -268,6 +270,9 @@ func (p *Process) UsernameWithContext(ctx context.Context) (string, error) { } defer token.Close() tokenUser, err := token.GetTokenUser() + if err != nil { + return "", err + } user, domain, _, err := tokenUser.User.Sid.LookupAccount("") return domain + "\\" + user, err @@ -398,7 +403,7 @@ func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) } // User and kernel times are represented as a FILETIME structure - // wich contains a 64-bit value representing the number of + // which contains a 64-bit value representing the number of // 100-nanosecond intervals since January 1, 1601 (UTC): // http://msdn.microsoft.com/en-us/library/ms724284(VS.85).aspx // To convert it into a float representing the seconds that the @@ -453,8 +458,6 @@ func (p *Process) Children() ([]*Process, error) { func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) { var dst []Win32_Process query := wmi.CreateQuery(&dst, fmt.Sprintf("Where ParentProcessId = %d", p.Pid)) - ctx, cancel := context.WithTimeout(context.Background(), common.Timeout) - defer cancel() err := common.WMIQueryWithContext(ctx, query, &dst) if err != nil { return nil, err @@ -596,6 +599,10 @@ func getFromSnapProcess(pid int32) (int32, int32, string, error) { // Get processes func Processes() ([]*Process, error) { + return ProcessesWithContext(context.Background()) +} + +func ProcessesWithContext(ctx context.Context) ([]*Process, error) { pids, err := Pids() if err != nil { return []*Process{}, fmt.Errorf("could not get Processes %s", err)