diff --git a/cpu/cpu.go b/cpu/cpu.go index daf3524..ec0a85c 100644 --- a/cpu/cpu.go +++ b/cpu/cpu.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "math" "strconv" "strings" "sync" @@ -110,9 +111,9 @@ func calculateBusy(t1, t2 TimesStat) float64 { return 0 } if t2All <= t1All { - return 1 + return 100 } - return (t2Busy - t1Busy) / (t2All - t1All) * 100 + return math.Min(100, math.Max(0, (t2Busy-t1Busy)/(t2All-t1All)*100)) } func calculateAllBusy(t1, t2 []TimesStat) ([]float64, error) { diff --git a/cpu/cpu_openbsd.go b/cpu/cpu_openbsd.go index 353991e..92a8bd7 100644 --- a/cpu/cpu_openbsd.go +++ b/cpu/cpu_openbsd.go @@ -11,6 +11,7 @@ import ( "runtime" "strconv" "strings" + "syscall" "github.com/shirou/gopsutil/internal/common" "golang.org/x/sys/unix" @@ -29,6 +30,9 @@ var ( // sys/sysctl.h const ( CTLKern = 1 // "high kernel": proc, limits + CTLHw = 6 // CTL_HW + SMT = 24 // HW_SMT + NCpuOnline = 25 // HW_NCPUONLINE KernCptime = 40 // KERN_CPTIME KernCptime2 = 71 // KERN_CPTIME2 ) @@ -68,6 +72,22 @@ func init() { }() } +func smt() (bool, error) { + mib := []int32{CTLHw, SMT} + buf, _, err := common.CallSyscall(mib) + if err != nil { + return false, err + } + + var ret bool + br := bytes.NewReader(buf) + if err := binary.Read(br, binary.LittleEndian, &ret); err != nil { + return false, err + } + + return ret, nil +} + func Times(percpu bool) ([]TimesStat, error) { return TimesWithContext(context.Background(), percpu) } @@ -82,13 +102,27 @@ func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { ncpu = 1 } + smt, err := smt() + if err == syscall.EOPNOTSUPP { + // if hw.smt is not applicable for this platform (e.g. i386), + // pretend it's enabled + smt = true + } else if err != nil { + return nil, err + } + for i := 0; i < ncpu; i++ { - var cpuTimes = make([]int64, CPUStates) + j := i + if !smt { + j *= 2 + } + + var cpuTimes = make([]int32, CPUStates) var mib []int32 if percpu { - mib = []int32{CTLKern, KernCptime} + mib = []int32{CTLKern, KernCptime2, int32(j)} } else { - mib = []int32{CTLKern, KernCptime2, int32(i)} + mib = []int32{CTLKern, KernCptime} } buf, _, err := common.CallSyscall(mib) if err != nil { @@ -107,10 +141,10 @@ func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { Idle: float64(cpuTimes[CPIdle]) / ClocksPerSec, Irq: float64(cpuTimes[CPIntr]) / ClocksPerSec, } - if !percpu { - c.CPU = "cpu-total" + if percpu { + c.CPU = fmt.Sprintf("cpu%d", j) } else { - c.CPU = fmt.Sprintf("cpu%d", i) + c.CPU = "cpu-total" } ret = append(ret, c) } @@ -135,10 +169,19 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) { } c.Mhz = float64(u32) - if u32, err = unix.SysctlUint32("hw.ncpuonline"); err != nil { + mib := []int32{CTLHw, NCpuOnline} + buf, _, err := common.CallSyscall(mib) + if err != nil { + return nil, err + } + + var ncpu int32 + br := bytes.NewReader(buf) + err = binary.Read(br, binary.LittleEndian, &ncpu) + if err != nil { return nil, err } - c.Cores = int32(u32) + c.Cores = ncpu if c.ModelName, err = unix.Sysctl("hw.model"); err != nil { return nil, err diff --git a/cpu/cpu_solaris.go b/cpu/cpu_solaris.go index 947ac97..3de0984 100644 --- a/cpu/cpu_solaris.go +++ b/cpu/cpu_solaris.go @@ -10,8 +10,6 @@ import ( "sort" "strconv" "strings" - - "github.com/shirou/gopsutil/internal/common" ) var ClocksPerSec = float64(128) @@ -31,12 +29,97 @@ func init() { } } +//sum all values in a float64 map with float64 keys +func msum(x map[float64]float64) float64 { + total := 0.0 + for _, y := range x { + total += y + } + return total +} + func Times(percpu bool) ([]TimesStat, error) { return TimesWithContext(context.Background(), percpu) } func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { - return []TimesStat{}, common.ErrNotImplementedError + kstatSys, err := exec.LookPath("kstat") + if err != nil { + return nil, fmt.Errorf("cannot find kstat: %s", err) + } + cpu := make(map[float64]float64) + idle := make(map[float64]float64) + user := make(map[float64]float64) + kern := make(map[float64]float64) + iowt := make(map[float64]float64) + //swap := make(map[float64]float64) + kstatSysOut, err := invoke.CommandWithContext(ctx, kstatSys, "-p", "cpu_stat:*:*:/^idle$|^user$|^kernel$|^iowait$|^swap$/") + if err != nil { + return nil, fmt.Errorf("cannot execute kstat: %s", err) + } + re := regexp.MustCompile(`[:\s]+`) + for _, line := range strings.Split(string(kstatSysOut), "\n") { + fields := re.Split(line, -1) + if fields[0] != "cpu_stat" { + continue + } + cpuNumber, err := strconv.ParseFloat(fields[1], 64) + if err != nil { + return nil, fmt.Errorf("cannot parse cpu number: %s", err) + } + cpu[cpuNumber] = cpuNumber + switch fields[3] { + case "idle": + idle[cpuNumber], err = strconv.ParseFloat(fields[4], 64) + if err != nil { + return nil, fmt.Errorf("cannot parse idle: %s", err) + } + case "user": + user[cpuNumber], err = strconv.ParseFloat(fields[4], 64) + if err != nil { + return nil, fmt.Errorf("cannot parse user: %s", err) + } + case "kernel": + kern[cpuNumber], err = strconv.ParseFloat(fields[4], 64) + if err != nil { + return nil, fmt.Errorf("cannot parse kernel: %s", err) + } + case "iowait": + iowt[cpuNumber], err = strconv.ParseFloat(fields[4], 64) + if err != nil { + return nil, fmt.Errorf("cannot parse iowait: %s", err) + } + //not sure how this translates, don't report, add to kernel, something else? + /*case "swap": + swap[cpuNumber], err = strconv.ParseFloat(fields[4], 64) + if err != nil { + return nil, fmt.Errorf("cannot parse swap: %s", err) + } */ + } + } + ret := make([]TimesStat, 0, len(cpu)) + if percpu { + for _, c := range cpu { + ct := &TimesStat{ + CPU: fmt.Sprintf("cpu%d", int(cpu[c])), + Idle: idle[c] / ClocksPerSec, + User: user[c] / ClocksPerSec, + System: kern[c] / ClocksPerSec, + Iowait: iowt[c] / ClocksPerSec, + } + ret = append(ret, *ct) + } + } else { + ct := &TimesStat{ + CPU: "cpu-total", + Idle: msum(idle) / ClocksPerSec, + User: msum(user) / ClocksPerSec, + System: msum(kern) / ClocksPerSec, + Iowait: msum(iowt) / ClocksPerSec, + } + ret = append(ret, *ct) + } + return ret, nil } func Info() ([]InfoStat, error) { diff --git a/disk/disk_linux.go b/disk/disk_linux.go index 51482c0..340f973 100644 --- a/disk/disk_linux.go +++ b/disk/disk_linux.go @@ -47,6 +47,7 @@ const ( FUSE_SUPER_MAGIC = 0x65735546 FUTEXFS_SUPER_MAGIC = 0xBAD1DEA HFS_SUPER_MAGIC = 0x4244 + HFSPLUS_SUPER_MAGIC = 0x482b HOSTFS_SUPER_MAGIC = 0x00c0ffee HPFS_SUPER_MAGIC = 0xF995E849 HUGETLBFS_MAGIC = 0x958458f6 @@ -156,6 +157,7 @@ var fsTypeMap = map[int64]string{ GFS_SUPER_MAGIC: "gfs/gfs2", /* 0x1161970 remote */ GPFS_SUPER_MAGIC: "gpfs", /* 0x47504653 remote */ HFS_SUPER_MAGIC: "hfs", /* 0x4244 local */ + HFSPLUS_SUPER_MAGIC: "hfsplus", /* 0x482b local */ HPFS_SUPER_MAGIC: "hpfs", /* 0xF995E849 local */ HUGETLBFS_MAGIC: "hugetlbfs", /* 0x958458F6 local */ MTD_INODE_FS_SUPER_MAGIC: "inodefs", /* 0x11307854 local */ @@ -298,6 +300,14 @@ func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, erro } } + if strings.HasPrefix(d.Device, "/dev/mapper/") { + devpath, err := filepath.EvalSymlinks(d.Device) + if err != nil { + return nil, err + } + d.Device = devpath + } + // /dev/root is not the real device name // so we get the real device name from its major/minor number if d.Device == "/dev/root" { diff --git a/disk/disk_openbsd_386.go b/disk/disk_openbsd_386.go new file mode 100644 index 0000000..bee3cc1 --- /dev/null +++ b/disk/disk_openbsd_386.go @@ -0,0 +1,89 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs types_openbsd.go + +package disk + +const ( + sizeofPtr = 0x4 + sizeofShort = 0x2 + sizeofInt = 0x4 + sizeofLong = 0x4 + sizeofLongLong = 0x8 + sizeofLongDouble = 0x8 + + DEVSTAT_NO_DATA = 0x00 + DEVSTAT_READ = 0x01 + DEVSTAT_WRITE = 0x02 + DEVSTAT_FREE = 0x03 + + MNT_RDONLY = 0x00000001 + MNT_SYNCHRONOUS = 0x00000002 + MNT_NOEXEC = 0x00000004 + MNT_NOSUID = 0x00000008 + MNT_NODEV = 0x00000010 + MNT_ASYNC = 0x00000040 + + MNT_WAIT = 1 + MNT_NOWAIT = 2 + MNT_LAZY = 3 +) + +const ( + sizeOfDiskstats = 0x60 +) + +type ( + _C_short int16 + _C_int int32 + _C_long int32 + _C_long_long int64 + _C_long_double int64 +) + +type Statfs struct { + F_flags uint32 + F_bsize uint32 + F_iosize uint32 + F_blocks uint64 + F_bfree uint64 + F_bavail int64 + F_files uint64 + F_ffree uint64 + F_favail int64 + F_syncwrites uint64 + F_syncreads uint64 + F_asyncwrites uint64 + F_asyncreads uint64 + F_fsid Fsid + F_namemax uint32 + F_owner uint32 + F_ctime uint64 + F_fstypename [16]int8 + F_mntonname [90]int8 + F_mntfromname [90]int8 + F_mntfromspec [90]int8 + Pad_cgo_0 [2]byte + Mount_info [160]byte +} +type Diskstats struct { + Name [16]int8 + Busy int32 + Rxfer uint64 + Wxfer uint64 + Seek uint64 + Rbytes uint64 + Wbytes uint64 + Attachtime Timeval + Timestamp Timeval + Time Timeval +} +type Fsid struct { + Val [2]int32 +} +type Timeval struct { + Sec int64 + Usec int32 +} + +type Diskstat struct{} +type Bintime struct{} diff --git a/host/freebsd_headers/utxdb.h b/host/freebsd_headers/utxdb.h new file mode 100644 index 0000000..912dd0f --- /dev/null +++ b/host/freebsd_headers/utxdb.h @@ -0,0 +1,63 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2010 Ed Schouten + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _UTXDB_H_ +#define _UTXDB_H_ + +#include + +#define _PATH_UTX_ACTIVE "/var/run/utx.active" +#define _PATH_UTX_LASTLOGIN "/var/log/utx.lastlogin" +#define _PATH_UTX_LOG "/var/log/utx.log" + +/* + * Entries in struct futx are ordered by how often they are used. In + * utx.log only entries will be written until the last non-zero byte, + * which means we want to put the hostname at the end. Most primitive + * records only store a ut_type and ut_tv, which means we want to store + * those at the front. + */ + +struct utmpx; + +struct futx { + uint8_t fu_type; + uint64_t fu_tv; + char fu_id[8]; + uint32_t fu_pid; + char fu_user[32]; + char fu_line[16]; + char fu_host[128]; +} __packed; + +void utx_to_futx(const struct utmpx *, struct futx *); +struct utmpx *futx_to_utx(const struct futx *); + +#endif /* !_UTXDB_H_ */ diff --git a/host/host_darwin.go b/host/host_darwin.go index 9f2b6b4..de8277f 100644 --- a/host/host_darwin.go +++ b/host/host_darwin.go @@ -189,6 +189,16 @@ func PlatformInformationWithContext(ctx context.Context) (string, string, string pver = strings.ToLower(strings.TrimSpace(string(out))) } + // check if the macos server version file exists + _, err = os.Stat("/System/Library/CoreServices/ServerVersion.plist") + + // server file doesn't exist + if os.IsNotExist(err) { + family = "Standalone Workstation" + } else { + family = "Server" + } + return platform, family, pver, nil } diff --git a/host/host_freebsd.go b/host/host_freebsd.go index 00a8519..9cf654e 100644 --- a/host/host_freebsd.go +++ b/host/host_freebsd.go @@ -7,6 +7,7 @@ import ( "context" "encoding/binary" "io/ioutil" + "math" "os" "runtime" "strings" @@ -143,11 +144,11 @@ func UsersWithContext(ctx context.Context) ([]UserStat, error) { b := buf[i*sizeOfUtmpx : (i+1)*sizeOfUtmpx] var u Utmpx br := bytes.NewReader(b) - err := binary.Read(br, binary.LittleEndian, &u) + err := binary.Read(br, binary.BigEndian, &u) if err != nil || u.Type != 4 { continue } - sec := (binary.LittleEndian.Uint32(u.Tv.Sec[:])) / 2 // TODO: + sec := math.Floor(float64(u.Tv) / 1000000) user := UserStat{ User: common.IntToString(u.User[:]), Terminal: common.IntToString(u.Line[:]), diff --git a/host/host_freebsd_386.go b/host/host_freebsd_386.go index 7f06d8f..88453d2 100644 --- a/host/host_freebsd_386.go +++ b/host/host_freebsd_386.go @@ -1,4 +1,4 @@ -// Created by cgo -godefs - DO NOT EDIT +// Code generated by cmd/cgo -godefs; DO NOT EDIT. // cgo -godefs types_freebsd.go package host @@ -9,7 +9,7 @@ const ( sizeofInt = 0x4 sizeofLong = 0x4 sizeofLongLong = 0x8 - sizeOfUtmpx = 197 // TODO why should 197 + sizeOfUtmpx = 0xc5 ) type ( @@ -27,17 +27,11 @@ type Utmp struct { } type Utmpx struct { - Type int16 - Tv Timeval + Type uint8 + Tv uint64 Id [8]int8 - Pid int32 + Pid uint32 User [32]int8 Line [16]int8 - Host [125]int8 - // X__ut_spare [64]int8 -} - -type Timeval struct { - Sec [4]byte - Usec [3]byte + Host [128]int8 } diff --git a/host/host_freebsd_amd64.go b/host/host_freebsd_amd64.go index 3f015f0..8af74b0 100644 --- a/host/host_freebsd_amd64.go +++ b/host/host_freebsd_amd64.go @@ -1,4 +1,4 @@ -// Created by cgo -godefs - DO NOT EDIT +// Code generated by cmd/cgo -godefs; DO NOT EDIT. // cgo -godefs types_freebsd.go package host @@ -9,7 +9,7 @@ const ( sizeofInt = 0x4 sizeofLong = 0x8 sizeofLongLong = 0x8 - sizeOfUtmpx = 197 // TODO: why should 197, not 0x118 + sizeOfUtmpx = 0xc5 ) type ( @@ -27,18 +27,11 @@ type Utmp struct { } type Utmpx struct { - Type int16 - Tv Timeval + Type uint8 + Tv uint64 Id [8]int8 - Pid int32 + Pid uint32 User [32]int8 Line [16]int8 - Host [125]int8 - // Host [128]int8 - // X__ut_spare [64]int8 -} - -type Timeval struct { - Sec [4]byte - Usec [3]byte + Host [128]int8 } diff --git a/host/host_freebsd_arm.go b/host/host_freebsd_arm.go index ac74980..f7d6ede 100644 --- a/host/host_freebsd_arm.go +++ b/host/host_freebsd_arm.go @@ -1,4 +1,4 @@ -// Created by cgo -godefs - DO NOT EDIT +// Code generated by cmd/cgo -godefs; DO NOT EDIT. // cgo -godefs types_freebsd.go package host @@ -9,7 +9,7 @@ const ( sizeofInt = 0x4 sizeofLong = 0x8 sizeofLongLong = 0x8 - sizeOfUtmpx = 197 // TODO: why should 197, not 0x118 + sizeOfUtmpx = 0xc5 ) type ( @@ -27,18 +27,11 @@ type Utmp struct { } type Utmpx struct { - Type int16 - Tv Timeval + Type uint8 + Tv uint64 Id [8]int8 - Pid int32 + Pid uint32 User [32]int8 Line [16]int8 - Host [125]int8 - // Host [128]int8 - // X__ut_spare [64]int8 -} - -type Timeval struct { - Sec [4]byte - Usec [3]byte + Host [128]int8 } diff --git a/host/host_linux.go b/host/host_linux.go index ed0ba38..02ff554 100644 --- a/host/host_linux.go +++ b/host/host_linux.go @@ -15,7 +15,6 @@ import ( "runtime" "strconv" "strings" - "sync/atomic" "time" "github.com/shirou/gopsutil/internal/common" @@ -105,71 +104,13 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) { return ret, nil } -// cachedBootTime must be accessed via atomic.Load/StoreUint64 -var cachedBootTime uint64 - // BootTime returns the system boot time expressed in seconds since the epoch. func BootTime() (uint64, error) { return BootTimeWithContext(context.Background()) } func BootTimeWithContext(ctx context.Context) (uint64, error) { - t := atomic.LoadUint64(&cachedBootTime) - if t != 0 { - return t, nil - } - - system, role, err := Virtualization() - if err != nil { - return 0, err - } - - statFile := "stat" - if system == "lxc" && role == "guest" { - // if lxc, /proc/uptime is used. - statFile = "uptime" - } 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 - } - - if statFile == "stat" { - for _, line := range lines { - if strings.HasPrefix(line, "btime") { - f := strings.Fields(line) - if len(f) != 2 { - return 0, fmt.Errorf("wrong btime format") - } - b, err := strconv.ParseInt(f[1], 10, 64) - if err != nil { - return 0, err - } - t = uint64(b) - atomic.StoreUint64(&cachedBootTime, t) - return t, nil - } - } - } else if statFile == "uptime" { - if len(lines) != 1 { - return 0, fmt.Errorf("wrong uptime format") - } - f := strings.Fields(lines[0]) - b, err := strconv.ParseFloat(f[0], 64) - if err != nil { - return 0, err - } - t = uint64(time.Now().Unix()) - uint64(b) - atomic.StoreUint64(&cachedBootTime, t) - return t, nil - } - - return 0, fmt.Errorf("could not find btime") + return common.BootTimeWithContext(ctx) } func uptime(boot uint64) uint64 { @@ -235,26 +176,6 @@ func UsersWithContext(ctx context.Context) ([]UserStat, error) { } -func getOSRelease() (platform string, version string, err error) { - contents, err := common.ReadLines(common.HostEtc("os-release")) - if err != nil { - return "", "", nil // return empty - } - for _, line := range contents { - field := strings.Split(line, "=") - if len(field) < 2 { - continue - } - switch field[0] { - case "ID": // use ID for lowercase - platform = field[1] - case "VERSION": - version = field[1] - } - } - return platform, version, nil -} - func getLSB() (*LSB, error) { ret := &LSB{} if common.PathExists(common.HostEtc("lsb-release")) { @@ -392,7 +313,7 @@ func PlatformInformationWithContext(ctx context.Context) (platform string, famil version = contents[0] } } else if common.PathExists(common.HostEtc("os-release")) { - p, v, err := getOSRelease() + p, v, err := common.GetOSRelease() if err == nil { platform = p version = v @@ -421,7 +342,7 @@ func PlatformInformationWithContext(ctx context.Context) (platform string, famil family = "fedora" case "oracle", "centos", "redhat", "scientific", "enterpriseenterprise", "amazon", "xenserver", "cloudlinux", "ibm_powerkvm": family = "rhel" - case "suse", "opensuse": + case "suse", "opensuse", "sles": family = "suse" case "gentoo": family = "gentoo" @@ -435,6 +356,8 @@ func PlatformInformationWithContext(ctx context.Context) (platform string, famil family = "alpine" case "coreos": family = "coreos" + case "solus": + family = "solus" } return platform, family, version, nil @@ -515,116 +438,7 @@ func Virtualization() (string, string, error) { } func VirtualizationWithContext(ctx context.Context) (string, string, error) { - var system string - var role string - - filename := common.HostProc("xen") - if common.PathExists(filename) { - system = "xen" - role = "guest" // assume guest - - if common.PathExists(filepath.Join(filename, "capabilities")) { - contents, err := common.ReadLines(filepath.Join(filename, "capabilities")) - if err == nil { - if common.StringsContains(contents, "control_d") { - role = "host" - } - } - } - } - - filename = common.HostProc("modules") - if common.PathExists(filename) { - contents, err := common.ReadLines(filename) - if err == nil { - if common.StringsContains(contents, "kvm") { - system = "kvm" - role = "host" - } else if common.StringsContains(contents, "vboxdrv") { - system = "vbox" - role = "host" - } else if common.StringsContains(contents, "vboxguest") { - system = "vbox" - role = "guest" - } else if common.StringsContains(contents, "vmware") { - system = "vmware" - role = "guest" - } - } - } - - filename = common.HostProc("cpuinfo") - if common.PathExists(filename) { - contents, err := common.ReadLines(filename) - if err == nil { - if common.StringsContains(contents, "QEMU Virtual CPU") || - common.StringsContains(contents, "Common KVM processor") || - common.StringsContains(contents, "Common 32-bit KVM processor") { - system = "kvm" - role = "guest" - } - } - } - - filename = common.HostProc("bus/pci/devices") - if common.PathExists(filename) { - contents, err := common.ReadLines(filename) - if err == nil { - if common.StringsContains(contents, "virtio-pci") { - role = "guest" - } - } - } - - filename = common.HostProc() - if common.PathExists(filepath.Join(filename, "bc", "0")) { - system = "openvz" - role = "host" - } else if common.PathExists(filepath.Join(filename, "vz")) { - system = "openvz" - role = "guest" - } - - // not use dmidecode because it requires root - if common.PathExists(filepath.Join(filename, "self", "status")) { - contents, err := common.ReadLines(filepath.Join(filename, "self", "status")) - if err == nil { - - if common.StringsContains(contents, "s_context:") || - common.StringsContains(contents, "VxID:") { - system = "linux-vserver" - } - // TODO: guest or host - } - } - - if common.PathExists(filepath.Join(filename, "self", "cgroup")) { - contents, err := common.ReadLines(filepath.Join(filename, "self", "cgroup")) - if err == nil { - if common.StringsContains(contents, "lxc") { - system = "lxc" - role = "guest" - } else if common.StringsContains(contents, "docker") { - system = "docker" - role = "guest" - } else if common.StringsContains(contents, "machine-rkt") { - system = "rkt" - role = "guest" - } else if common.PathExists("/usr/bin/lxc-version") { - system = "lxc" - role = "host" - } - } - } - - if common.PathExists(common.HostEtc("os-release")) { - p, _, err := getOSRelease() - if err == nil && p == "coreos" { - system = "rkt" // Is it true? - role = "host" - } - } - return system, role, nil + return common.VirtualizationWithContext(ctx) } func SensorsTemperatures() ([]TemperatureStat, error) { @@ -645,6 +459,7 @@ func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, err return temperatures, err } } + var warns Warnings // example directory // device/ temp1_crit_alarm temp2_crit_alarm temp3_crit_alarm temp4_crit_alarm temp5_crit_alarm temp6_crit_alarm temp7_crit_alarm @@ -670,16 +485,19 @@ func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, err // Get the name of the temperature you are reading name, err := ioutil.ReadFile(filepath.Join(filepath.Dir(file), "name")) if err != nil { - return temperatures, err + warns.Add(err) + continue } // Get the temperature reading current, err := ioutil.ReadFile(file) if err != nil { - return temperatures, err + warns.Add(err) + continue } temperature, err := strconv.ParseFloat(strings.TrimSpace(string(current)), 64) if err != nil { + warns.Add(err) continue } @@ -689,5 +507,5 @@ func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, err Temperature: temperature / 1000.0, }) } - return temperatures, nil + return temperatures, warns.Reference() } diff --git a/host/types.go b/host/types.go new file mode 100644 index 0000000..1766e7e --- /dev/null +++ b/host/types.go @@ -0,0 +1,25 @@ +package host + +import ( + "fmt" +) + +type Warnings struct { + List []error +} + +func (w *Warnings) Add(err error) { + w.List = append(w.List, err) +} + +func (w *Warnings) Reference() error { + if len(w.List) > 0 { + return w + } else { + return nil + } +} + +func (w *Warnings) Error() string { + return fmt.Sprintf("Number of warnings: %v", len(w.List)) +} diff --git a/host/types_freebsd.go b/host/types_freebsd.go index e70677f..bbdce0c 100644 --- a/host/types_freebsd.go +++ b/host/types_freebsd.go @@ -11,6 +11,7 @@ package host #include #include #include +#include "freebsd_headers/utxdb.h" enum { sizeofPtr = sizeof(void*), @@ -27,7 +28,7 @@ const ( sizeofInt = C.sizeof_int sizeofLong = C.sizeof_long sizeofLongLong = C.sizeof_longlong - sizeOfUtmpx = C.sizeof_struct_utmpx + sizeOfUtmpx = C.sizeof_struct_futx ) // Basic types @@ -39,6 +40,5 @@ type ( _C_long_long C.longlong ) -type Utmp C.struct_utmp -type Utmpx C.struct_utmpx -type Timeval C.struct_timeval +type Utmp C.struct_utmp // for FreeBSD 9.0 compatibility +type Utmpx C.struct_futx diff --git a/internal/common/common.go b/internal/common/common.go index df72e65..4ca8bc9 100644 --- a/internal/common/common.go +++ b/internal/common/common.go @@ -213,6 +213,12 @@ func ReadInts(filename string) ([]int64, error) { return ret, nil } +// Parse Hex to uint32 without error +func HexToUint32(hex string) uint32 { + vv, _ := strconv.ParseUint(hex, 16, 32) + return uint32(vv) +} + // Parse to int32 without error func mustParseInt32(val string) int32 { vv, _ := strconv.ParseInt(val, 10, 32) diff --git a/internal/common/common_linux.go b/internal/common/common_linux.go index c65151a..ea2860e 100644 --- a/internal/common/common_linux.go +++ b/internal/common/common_linux.go @@ -3,9 +3,15 @@ package common import ( + "context" + "fmt" "os" "os/exec" + "path/filepath" + "strconv" "strings" + "sync/atomic" + "time" ) func DoSysctrl(mib string) ([]string, error) { @@ -39,3 +45,212 @@ func NumProcs() (uint64, error) { } return uint64(len(list)), err } + +// cachedBootTime must be accessed via atomic.Load/StoreUint64 +var cachedBootTime uint64 + +func BootTimeWithContext(ctx context.Context) (uint64, error) { + t := atomic.LoadUint64(&cachedBootTime) + if t != 0 { + return t, nil + } + + system, role, err := Virtualization() + if err != nil { + return 0, err + } + + statFile := "stat" + if system == "lxc" && role == "guest" { + // if lxc, /proc/uptime is used. + statFile = "uptime" + } else if system == "docker" && role == "guest" { + // also docker, guest + statFile = "uptime" + } + + filename := HostProc(statFile) + lines, err := ReadLines(filename) + if err != nil { + return 0, err + } + + if statFile == "stat" { + for _, line := range lines { + if strings.HasPrefix(line, "btime") { + f := strings.Fields(line) + if len(f) != 2 { + return 0, fmt.Errorf("wrong btime format") + } + b, err := strconv.ParseInt(f[1], 10, 64) + if err != nil { + return 0, err + } + t = uint64(b) + atomic.StoreUint64(&cachedBootTime, t) + return t, nil + } + } + } else if statFile == "uptime" { + if len(lines) != 1 { + return 0, fmt.Errorf("wrong uptime format") + } + f := strings.Fields(lines[0]) + b, err := strconv.ParseFloat(f[0], 64) + if err != nil { + return 0, err + } + t = uint64(time.Now().Unix()) - uint64(b) + atomic.StoreUint64(&cachedBootTime, t) + return t, nil + } + + return 0, fmt.Errorf("could not find btime") +} + +func Virtualization() (string, string, error) { + return VirtualizationWithContext(context.Background()) +} + +func VirtualizationWithContext(ctx context.Context) (string, string, error) { + var system string + var role string + + filename := HostProc("xen") + if PathExists(filename) { + system = "xen" + role = "guest" // assume guest + + if PathExists(filepath.Join(filename, "capabilities")) { + contents, err := ReadLines(filepath.Join(filename, "capabilities")) + if err == nil { + if StringsContains(contents, "control_d") { + role = "host" + } + } + } + } + + filename = HostProc("modules") + if PathExists(filename) { + contents, err := ReadLines(filename) + if err == nil { + if StringsContains(contents, "kvm") { + system = "kvm" + role = "host" + } else if StringsContains(contents, "vboxdrv") { + system = "vbox" + role = "host" + } else if StringsContains(contents, "vboxguest") { + system = "vbox" + role = "guest" + } else if StringsContains(contents, "vmware") { + system = "vmware" + role = "guest" + } + } + } + + filename = HostProc("cpuinfo") + if PathExists(filename) { + contents, err := ReadLines(filename) + if err == nil { + if StringsContains(contents, "QEMU Virtual CPU") || + StringsContains(contents, "Common KVM processor") || + StringsContains(contents, "Common 32-bit KVM processor") { + system = "kvm" + role = "guest" + } + } + } + + filename = HostProc("bus/pci/devices") + if PathExists(filename) { + contents, err := ReadLines(filename) + if err == nil { + if StringsContains(contents, "virtio-pci") { + role = "guest" + } + } + } + + filename = HostProc() + if PathExists(filepath.Join(filename, "bc", "0")) { + system = "openvz" + role = "host" + } else if PathExists(filepath.Join(filename, "vz")) { + system = "openvz" + role = "guest" + } + + // not use dmidecode because it requires root + if PathExists(filepath.Join(filename, "self", "status")) { + contents, err := ReadLines(filepath.Join(filename, "self", "status")) + if err == nil { + + if StringsContains(contents, "s_context:") || + StringsContains(contents, "VxID:") { + system = "linux-vserver" + } + // TODO: guest or host + } + } + + if PathExists(filepath.Join(filename, "self", "cgroup")) { + contents, err := ReadLines(filepath.Join(filename, "self", "cgroup")) + if err == nil { + if StringsContains(contents, "lxc") { + system = "lxc" + role = "guest" + } else if StringsContains(contents, "docker") { + system = "docker" + role = "guest" + } else if StringsContains(contents, "machine-rkt") { + system = "rkt" + role = "guest" + } else if PathExists("/usr/bin/lxc-version") { + system = "lxc" + role = "host" + } + } + } + + if PathExists(HostEtc("os-release")) { + p, _, err := GetOSRelease() + if err == nil && p == "coreos" { + system = "rkt" // Is it true? + role = "host" + } + } + return system, role, nil +} + +func GetOSRelease() (platform string, version string, err error) { + contents, err := ReadLines(HostEtc("os-release")) + if err != nil { + return "", "", nil // return empty + } + for _, line := range contents { + field := strings.Split(line, "=") + if len(field) < 2 { + continue + } + switch field[0] { + case "ID": // use ID for lowercase + platform = trimQuotes(field[1]) + case "VERSION": + version = trimQuotes(field[1]) + } + } + return platform, version, nil +} + +// Remove quotes of the source string +func trimQuotes(s string) string { + if len(s) >= 2 { + if s[0] == '"' && s[len(s)-1] == '"' { + return s[1 : len(s)-1] + } + } + return s +} diff --git a/internal/common/common_test.go b/internal/common/common_test.go index cd33388..3d6ae10 100644 --- a/internal/common/common_test.go +++ b/internal/common/common_test.go @@ -51,6 +51,12 @@ func TestByteToString(t *testing.T) { } } +func TestHexToUint32(t *testing.T) { + if HexToUint32("FFFFFFFF") != 4294967295 { + t.Error("Could not convert") + } +} + func TestmustParseInt32(t *testing.T) { ret := mustParseInt32("11111") if ret != int32(11111) { diff --git a/internal/common/common_windows.go b/internal/common/common_windows.go index 0997c9b..dbc8b67 100644 --- a/internal/common/common_windows.go +++ b/internal/common/common_windows.go @@ -4,6 +4,9 @@ package common import ( "context" + "path/filepath" + "strings" + "syscall" "unsafe" "github.com/StackExchange/wmi" @@ -59,6 +62,8 @@ var ( PdhCollectQueryData = ModPdh.NewProc("PdhCollectQueryData") PdhGetFormattedCounterValue = ModPdh.NewProc("PdhGetFormattedCounterValue") PdhCloseQuery = ModPdh.NewProc("PdhCloseQuery") + + procQueryDosDeviceW = Modkernel32.NewProc("QueryDosDeviceW") ) type FILETIME struct { @@ -133,3 +138,23 @@ func WMIQueryWithContext(ctx context.Context, query string, dst interface{}, con return err } } + +// Convert paths using native DOS format like: +// "\Device\HarddiskVolume1\Windows\systemew\file.txt" +// into: +// "C:\Windows\systemew\file.txt" +func ConvertDOSPath(p string) string { + rawDrive := strings.Join(strings.Split(p, `\`)[:3], `\`) + + for d := 'A'; d <= 'Z'; d++ { + szDeviceName := string(d) + ":" + szTarget := make([]uint16, 512) + ret, _, _ := procQueryDosDeviceW.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(szDeviceName))), + uintptr(unsafe.Pointer(&szTarget[0])), + uintptr(len(szTarget))) + if ret != 0 && windows.UTF16ToString(szTarget[:]) == rawDrive { + return filepath.Join(szDeviceName, p[len(rawDrive):]) + } + } + return p +} diff --git a/net/net.go b/net/net.go index a31a434..c9a4baf 100644 --- a/net/net.go +++ b/net/net.go @@ -71,6 +71,96 @@ type FilterStat struct { ConnTrackMax int64 `json:"conntrackMax"` } +// ConntrackStat has conntrack summary info +type ConntrackStat struct { + Entries uint32 `json:"entries"` // Number of entries in the conntrack table + Searched uint32 `json:"searched"` // Number of conntrack table lookups performed + Found uint32 `json:"found"` // Number of searched entries which were successful + New uint32 `json:"new"` // Number of entries added which were not expected before + Invalid uint32 `json:"invalid"` // Number of packets seen which can not be tracked + Ignore uint32 `json:"ignore"` // Packets seen which are already connected to an entry + Delete uint32 `json:"delete"` // Number of entries which were removed + DeleteList uint32 `json:"delete_list"` // Number of entries which were put to dying list + Insert uint32 `json:"insert"` // Number of entries inserted into the list + InsertFailed uint32 `json:"insert_failed"` // # insertion attempted but failed (same entry exists) + Drop uint32 `json:"drop"` // Number of packets dropped due to conntrack failure. + EarlyDrop uint32 `json:"early_drop"` // Dropped entries to make room for new ones, if maxsize reached + IcmpError uint32 `json:"icmp_error"` // Subset of invalid. Packets that can't be tracked d/t error + ExpectNew uint32 `json:"expect_new"` // Entries added after an expectation was already present + ExpectCreate uint32 `json:"expect_create"` // Expectations added + ExpectDelete uint32 `json:"expect_delete"` // Expectations deleted + SearchRestart uint32 `json:"search_restart"` // Conntrack table lookups restarted due to hashtable resizes +} + +func NewConntrackStat(e uint32, s uint32, f uint32, n uint32, inv uint32, ign uint32, del uint32, dlst uint32, ins uint32, insfail uint32, drop uint32, edrop uint32, ie uint32, en uint32, ec uint32, ed uint32, sr uint32) *ConntrackStat { + return &ConntrackStat{ + Entries: e, + Searched: s, + Found: f, + New: n, + Invalid: inv, + Ignore: ign, + Delete: del, + DeleteList: dlst, + Insert: ins, + InsertFailed: insfail, + Drop: drop, + EarlyDrop: edrop, + IcmpError: ie, + ExpectNew: en, + ExpectCreate: ec, + ExpectDelete: ed, + SearchRestart: sr, + } +} + +type ConntrackStatList struct { + items []*ConntrackStat +} + +func NewConntrackStatList() *ConntrackStatList { + return &ConntrackStatList{ + items: []*ConntrackStat{}, + } +} + +func (l *ConntrackStatList) Append(c *ConntrackStat) { + l.items = append(l.items, c) +} + +func (l *ConntrackStatList) Items() []ConntrackStat { + items := make([]ConntrackStat, len(l.items), len(l.items)) + for i, el := range l.items { + items[i] = *el + } + return items +} + +// Summary returns a single-element list with totals from all list items. +func (l *ConntrackStatList) Summary() []ConntrackStat { + summary := NewConntrackStat(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + for _, cs := range l.items { + summary.Entries += cs.Entries + summary.Searched += cs.Searched + summary.Found += cs.Found + summary.New += cs.New + summary.Invalid += cs.Invalid + summary.Ignore += cs.Ignore + summary.Delete += cs.Delete + summary.DeleteList += cs.DeleteList + summary.Insert += cs.Insert + summary.InsertFailed += cs.InsertFailed + summary.Drop += cs.Drop + summary.EarlyDrop += cs.EarlyDrop + summary.IcmpError += cs.IcmpError + summary.ExpectNew += cs.ExpectNew + summary.ExpectCreate += cs.ExpectCreate + summary.ExpectDelete += cs.ExpectDelete + summary.SearchRestart += cs.SearchRestart + } + return []ConntrackStat{*summary} +} + var constMap = map[string]int{ "unix": syscall.AF_UNIX, "TCP": syscall.SOCK_STREAM, @@ -109,6 +199,11 @@ func (n InterfaceAddr) String() string { return string(s) } +func (n ConntrackStat) String() string { + s, _ := json.Marshal(n) + return string(s) +} + func Interfaces() ([]InterfaceStat, error) { return InterfacesWithContext(context.Background()) } diff --git a/net/net_darwin.go b/net/net_darwin.go index ebebc2f..1daed86 100644 --- a/net/net_darwin.go +++ b/net/net_darwin.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "github.com/shirou/gopsutil/internal/common" "os/exec" "regexp" "strconv" @@ -271,6 +272,14 @@ func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) { return nil, errors.New("NetFilterCounters not implemented for darwin") } +func ConntrackStats(percpu bool) ([]ConntrackStat, error) { + return ConntrackStatsWithContext(context.Background(), percpu) +} + +func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) { + return nil, common.ErrNotImplementedError +} + // NetProtoCounters returns network statistics for the entire system // If protocols is empty then all protocols are returned, otherwise // just the protocols in the list are returned. diff --git a/net/net_fallback.go b/net/net_fallback.go index 7c5e632..0991347 100644 --- a/net/net_fallback.go +++ b/net/net_fallback.go @@ -24,6 +24,14 @@ func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) { return []FilterStat{}, common.ErrNotImplementedError } +func ConntrackStats(percpu bool) ([]ConntrackStat, error) { + return ConntrackStatsWithContext(context.Background(), percpu) +} + +func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) { + return nil, common.ErrNotImplementedError +} + func ProtoCounters(protocols []string) ([]ProtoCountersStat, error) { return ProtoCountersWithContext(context.Background(), protocols) } diff --git a/net/net_freebsd.go b/net/net_freebsd.go index 84b970a..2284d98 100644 --- a/net/net_freebsd.go +++ b/net/net_freebsd.go @@ -112,6 +112,14 @@ func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) { return nil, errors.New("NetFilterCounters not implemented for freebsd") } +func ConntrackStats(percpu bool) ([]ConntrackStat, error) { + return ConntrackStatsWithContext(context.Background(), percpu) +} + +func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) { + return nil, errors.New("ConntrackStats not implemented for freebsd") +} + // NetProtoCounters returns network statistics for the entire system // If protocols is empty then all protocols are returned, otherwise // just the protocols in the list are returned. diff --git a/net/net_linux.go b/net/net_linux.go index 71842fb..c8d70ed 100644 --- a/net/net_linux.go +++ b/net/net_linux.go @@ -8,6 +8,7 @@ import ( "encoding/hex" "errors" "fmt" + "io" "io/ioutil" "net" "os" @@ -18,6 +19,26 @@ import ( "github.com/shirou/gopsutil/internal/common" ) +const ( // Conntrack Column numbers + CT_ENTRIES = iota + CT_SEARCHED + CT_FOUND + CT_NEW + CT_INVALID + CT_IGNORE + CT_DELETE + CT_DELETE_LIST + CT_INSERT + CT_INSERT_FAILED + CT_DROP + CT_EARLY_DROP + CT_ICMP_ERROR + CT_EXPECT_NEW + CT_EXPECT_CREATE + CT_EXPECT_DELETE + CT_SEARCH_RESTART +) + // NetIOCounters returnes network I/O statistics for every network // interface installed on the system. If pernic argument is false, // return only sum of all information (which name is 'all'). If true, @@ -232,6 +253,58 @@ func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) { return stats, nil } +// ConntrackStats returns more detailed info about the conntrack table +func ConntrackStats(percpu bool) ([]ConntrackStat, error) { + return ConntrackStatsWithContext(context.Background(), percpu) +} + +// ConntrackStatsWithContext returns more detailed info about the conntrack table +func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) { + return conntrackStatsFromFile(common.HostProc("net/stat/nf_conntrack"), percpu) +} + +// conntrackStatsFromFile returns more detailed info about the conntrack table +// from `filename` +// If 'percpu' is false, the result will contain exactly one item with totals/summary +func conntrackStatsFromFile(filename string, percpu bool) ([]ConntrackStat, error) { + lines, err := common.ReadLines(filename) + if err != nil { + return nil, err + } + + statlist := NewConntrackStatList() + + for _, line := range lines { + fields := strings.Fields(line) + if len(fields) == 17 && fields[0] != "entries" { + statlist.Append(NewConntrackStat( + common.HexToUint32(fields[CT_ENTRIES]), + common.HexToUint32(fields[CT_SEARCHED]), + common.HexToUint32(fields[CT_FOUND]), + common.HexToUint32(fields[CT_NEW]), + common.HexToUint32(fields[CT_INVALID]), + common.HexToUint32(fields[CT_IGNORE]), + common.HexToUint32(fields[CT_DELETE]), + common.HexToUint32(fields[CT_DELETE_LIST]), + common.HexToUint32(fields[CT_INSERT]), + common.HexToUint32(fields[CT_INSERT_FAILED]), + common.HexToUint32(fields[CT_DROP]), + common.HexToUint32(fields[CT_EARLY_DROP]), + common.HexToUint32(fields[CT_ICMP_ERROR]), + common.HexToUint32(fields[CT_EXPECT_NEW]), + common.HexToUint32(fields[CT_EXPECT_CREATE]), + common.HexToUint32(fields[CT_EXPECT_DELETE]), + common.HexToUint32(fields[CT_SEARCH_RESTART]), + )) + } + } + + if percpu { + return statlist.Items(), nil + } + return statlist.Summary(), nil +} + // http://students.mimuw.edu.pl/lxr/source/include/net/tcp_states.h var TCPStatuses = map[string]string{ "01": "ESTABLISHED", @@ -581,7 +654,7 @@ func getProcInodesAll(root string, max int) (map[string][]inodeMap, error) { t, err := getProcInodes(root, pid, max) if err != nil { // skip if permission error or no longer exists - if os.IsPermission(err) || os.IsNotExist(err) { + if os.IsPermission(err) || os.IsNotExist(err) || err == io.EOF { continue } return ret, err diff --git a/net/net_linux_test.go b/net/net_linux_test.go index 566a17b..cf19fcd 100644 --- a/net/net_linux_test.go +++ b/net/net_linux_test.go @@ -161,3 +161,142 @@ func TestReverse(t *testing.T) { src := []byte{0x01, 0x02, 0x03} assert.Equal(t, []byte{0x03, 0x02, 0x01}, Reverse(src)) } + +func TestConntrackStatFileParsing(t *testing.T) { + tmpfile, err := ioutil.TempFile("", "proc_net_stat_conntrack") + defer os.Remove(tmpfile.Name()) + assert.Nil(t, err, "Temporary file creation failed: ", err) + + data := []byte(` +entries searched found new invalid ignore delete delete_list insert insert_failed drop early_drop icmp_error expect_new expect_create expect_delete search_restart +0000007b 00000000 00000000 00000000 000b115a 00000084 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 0000004a +0000007b 00000000 00000000 00000000 0007eee5 00000068 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000035 +0000007b 00000000 00000000 00000000 0090346b 00000057 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000025 +0000007b 00000000 00000000 00000000 0005920f 00000069 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000064 +0000007b 00000000 00000000 00000000 000331ff 00000059 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 0000003b +0000007b 00000000 00000000 00000000 000314ea 00000066 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000054 +0000007b 00000000 00000000 00000000 0002b270 00000055 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 0000003d +0000007b 00000000 00000000 00000000 0002f67d 00000057 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000042 +`) + + // Expected results + slist := NewConntrackStatList() + + slist.Append(&ConntrackStat{ + Entries: 123, + Searched: 0, + Found: 0, + New: 0, + Invalid: 725338, + Ignore: 132, + Delete: 0, + DeleteList: 0, + Insert: 0, + InsertFailed: 0, + Drop: 0, + EarlyDrop: 0, + IcmpError: 0, + ExpectNew: 0, + ExpectCreate: 0, + ExpectDelete: 0, + SearchRestart: 74, + }) + slist.Append(&ConntrackStat{123, 0, 0, 0, 519909, 104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 53}) + + slist.Append(&ConntrackStat{123, 0, 0, 0, 9450603, 87, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37}) + slist.Append(&ConntrackStat{123, 0, 0, 0, 365071, 105, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}) + + slist.Append(&ConntrackStat{123, 0, 0, 0, 209407, 89, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59}) + slist.Append(&ConntrackStat{123, 0, 0, 0, 201962, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84}) + + slist.Append(&ConntrackStat{123, 0, 0, 0, 176752, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 61}) + slist.Append(&ConntrackStat{123, 0, 0, 0, 194173, 87, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66}) + + // Write data to tempfile + _, err = tmpfile.Write(data) + assert.Nil(t, err, "Temporary file writing failed: ", err) + + // Function under test + stats, err := conntrackStatsFromFile(tmpfile.Name(), true) + assert.Equal(t, 8, len(stats), "Expected 8 results") + + summary := &ConntrackStat{} + for i, exp := range slist.Items() { + st := stats[i] + + assert.Equal(t, exp.Entries, st.Entries) + summary.Entries += st.Entries + + assert.Equal(t, exp.Searched, st.Searched) + summary.Searched += st.Searched + + assert.Equal(t, exp.Found, st.Found) + summary.Found += st.Found + + assert.Equal(t, exp.New, st.New) + summary.New += st.New + + assert.Equal(t, exp.Invalid, st.Invalid) + summary.Invalid += st.Invalid + + assert.Equal(t, exp.Ignore, st.Ignore) + summary.Ignore += st.Ignore + + assert.Equal(t, exp.Delete, st.Delete) + summary.Delete += st.Delete + + assert.Equal(t, exp.DeleteList, st.DeleteList) + summary.DeleteList += st.DeleteList + + assert.Equal(t, exp.Insert, st.Insert) + summary.Insert += st.Insert + + assert.Equal(t, exp.InsertFailed, st.InsertFailed) + summary.InsertFailed += st.InsertFailed + + assert.Equal(t, exp.Drop, st.Drop) + summary.Drop += st.Drop + + assert.Equal(t, exp.EarlyDrop, st.EarlyDrop) + summary.EarlyDrop += st.EarlyDrop + + assert.Equal(t, exp.IcmpError, st.IcmpError) + summary.IcmpError += st.IcmpError + + assert.Equal(t, exp.ExpectNew, st.ExpectNew) + summary.ExpectNew += st.ExpectNew + + assert.Equal(t, exp.ExpectCreate, st.ExpectCreate) + summary.ExpectCreate += st.ExpectCreate + + assert.Equal(t, exp.ExpectDelete, st.ExpectDelete) + summary.ExpectDelete += st.ExpectDelete + + assert.Equal(t, exp.SearchRestart, st.SearchRestart) + summary.SearchRestart += st.SearchRestart + } + + // Test summary grouping + totals, err := conntrackStatsFromFile(tmpfile.Name(), false) + for i, st := range totals { + assert.Equal(t, summary.Entries, st.Entries) + assert.Equal(t, summary.Searched, st.Searched) + assert.Equal(t, summary.Found, st.Found) + assert.Equal(t, summary.New, st.New) + assert.Equal(t, summary.Invalid, st.Invalid) + assert.Equal(t, summary.Ignore, st.Ignore) + assert.Equal(t, summary.Delete, st.Delete) + assert.Equal(t, summary.DeleteList, st.DeleteList) + assert.Equal(t, summary.Insert, st.Insert) + assert.Equal(t, summary.InsertFailed, st.InsertFailed) + assert.Equal(t, summary.Drop, st.Drop) + assert.Equal(t, summary.EarlyDrop, st.EarlyDrop) + assert.Equal(t, summary.IcmpError, st.IcmpError) + assert.Equal(t, summary.ExpectNew, st.ExpectNew) + assert.Equal(t, summary.ExpectCreate, st.ExpectCreate) + assert.Equal(t, summary.ExpectDelete, st.ExpectDelete) + assert.Equal(t, summary.SearchRestart, st.SearchRestart) + + assert.Equal(t, 0, i) // Should only have one element + } +} diff --git a/net/net_openbsd.go b/net/net_openbsd.go index 0faa70e..3cf0a89 100644 --- a/net/net_openbsd.go +++ b/net/net_openbsd.go @@ -156,6 +156,14 @@ func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) { return nil, errors.New("NetFilterCounters not implemented for openbsd") } +func ConntrackStats(percpu bool) ([]ConntrackStat, error) { + return ConntrackStatsWithContext(context.Background(), percpu) +} + +func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) { + return nil, common.ErrNotImplementedError +} + // NetProtoCounters returns network statistics for the entire system // If protocols is empty then all protocols are returned, otherwise // just the protocols in the list are returned. diff --git a/net/net_windows.go b/net/net_windows.go index 61eb6ec..d40e416 100644 --- a/net/net_windows.go +++ b/net/net_windows.go @@ -19,6 +19,7 @@ var ( modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll") procGetExtendedTCPTable = modiphlpapi.NewProc("GetExtendedTcpTable") procGetExtendedUDPTable = modiphlpapi.NewProc("GetExtendedUdpTable") + procGetIfEntry2 = modiphlpapi.NewProc("GetIfEntry2") ) const ( @@ -73,6 +74,65 @@ var netConnectionKindMap = map[string][]netConnectionKindType{ "inet6": {kindTCP6, kindUDP6}, } +// https://github.com/microsoft/ethr/blob/aecdaf923970e5a9b4c461b4e2e3963d781ad2cc/plt_windows.go#L114-L170 +type guid struct { + Data1 uint32 + Data2 uint16 + Data3 uint16 + Data4 [8]byte +} + +const ( + maxStringSize = 256 + maxPhysAddressLength = 32 + pad0for64_4for32 = 0 +) + +type mibIfRow2 struct { + InterfaceLuid uint64 + InterfaceIndex uint32 + InterfaceGuid guid + Alias [maxStringSize + 1]uint16 + Description [maxStringSize + 1]uint16 + PhysicalAddressLength uint32 + PhysicalAddress [maxPhysAddressLength]uint8 + PermanentPhysicalAddress [maxPhysAddressLength]uint8 + Mtu uint32 + Type uint32 + TunnelType uint32 + MediaType uint32 + PhysicalMediumType uint32 + AccessType uint32 + DirectionType uint32 + InterfaceAndOperStatusFlags uint32 + OperStatus uint32 + AdminStatus uint32 + MediaConnectState uint32 + NetworkGuid guid + ConnectionType uint32 + padding1 [pad0for64_4for32]byte + TransmitLinkSpeed uint64 + ReceiveLinkSpeed uint64 + InOctets uint64 + InUcastPkts uint64 + InNUcastPkts uint64 + InDiscards uint64 + InErrors uint64 + InUnknownProtos uint64 + InUcastOctets uint64 + InMulticastOctets uint64 + InBroadcastOctets uint64 + OutOctets uint64 + OutUcastPkts uint64 + OutNUcastPkts uint64 + OutDiscards uint64 + OutErrors uint64 + OutUcastOctets uint64 + OutMulticastOctets uint64 + OutBroadcastOctets uint64 + OutQLen uint64 +} + func IOCounters(pernic bool) ([]IOCountersStat, error) { return IOCountersWithContext(context.Background(), pernic) } @@ -82,34 +142,59 @@ func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat, if err != nil { return nil, err } - var ret []IOCountersStat + var counters []IOCountersStat + + err = procGetIfEntry2.Find() + if err == nil { // Vista+, uint64 values (issue#693) + for _, ifi := range ifs { + c := IOCountersStat{ + Name: ifi.Name, + } - for _, ifi := range ifs { - c := IOCountersStat{ - Name: ifi.Name, + row := mibIfRow2{InterfaceIndex: uint32(ifi.Index)} + ret, _, err := procGetIfEntry2.Call(uintptr(unsafe.Pointer(&row))) + if ret != 0 { + return nil, os.NewSyscallError("GetIfEntry2", err) + } + c.BytesSent = uint64(row.OutOctets) + c.BytesRecv = uint64(row.InOctets) + c.PacketsSent = uint64(row.OutUcastPkts) + c.PacketsRecv = uint64(row.InUcastPkts) + c.Errin = uint64(row.InErrors) + c.Errout = uint64(row.OutErrors) + c.Dropin = uint64(row.InDiscards) + c.Dropout = uint64(row.OutDiscards) + + counters = append(counters, c) } + } else { // WinXP fallback, uint32 values + for _, ifi := range ifs { + c := IOCountersStat{ + Name: ifi.Name, + } - row := windows.MibIfRow{Index: uint32(ifi.Index)} - e := windows.GetIfEntry(&row) - if e != nil { - return nil, os.NewSyscallError("GetIfEntry", e) + row := windows.MibIfRow{Index: uint32(ifi.Index)} + err = windows.GetIfEntry(&row) + if err != nil { + return nil, os.NewSyscallError("GetIfEntry", err) + } + c.BytesSent = uint64(row.OutOctets) + c.BytesRecv = uint64(row.InOctets) + c.PacketsSent = uint64(row.OutUcastPkts) + c.PacketsRecv = uint64(row.InUcastPkts) + c.Errin = uint64(row.InErrors) + c.Errout = uint64(row.OutErrors) + c.Dropin = uint64(row.InDiscards) + c.Dropout = uint64(row.OutDiscards) + + counters = append(counters, c) } - c.BytesSent = uint64(row.OutOctets) - c.BytesRecv = uint64(row.InOctets) - c.PacketsSent = uint64(row.OutUcastPkts) - c.PacketsRecv = uint64(row.InUcastPkts) - c.Errin = uint64(row.InErrors) - c.Errout = uint64(row.OutErrors) - c.Dropin = uint64(row.InDiscards) - c.Dropout = uint64(row.OutDiscards) - - ret = append(ret, c) } - if pernic == false { - return getIOCountersAll(ret) + if !pernic { + return getIOCountersAll(counters) } - return ret, nil + return counters, nil } // NetIOCountersByFile is an method which is added just a compatibility for linux. @@ -206,6 +291,15 @@ func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) { return nil, errors.New("NetFilterCounters not implemented for windows") } +func ConntrackStats(percpu bool) ([]ConntrackStat, error) { + return ConntrackStatsWithContext(context.Background(), percpu) +} + +func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) { + return nil, common.ErrNotImplementedError +} + + // NetProtoCounters returns network statistics for the entire system // If protocols is empty then all protocols are returned, otherwise // just the protocols in the list are returned. diff --git a/process/process_linux.go b/process/process_linux.go index 8ae99ff..1367b5c 100644 --- a/process/process_linux.go +++ b/process/process_linux.go @@ -16,7 +16,6 @@ import ( "strings" "github.com/shirou/gopsutil/cpu" - "github.com/shirou/gopsutil/host" "github.com/shirou/gopsutil/internal/common" "github.com/shirou/gopsutil/net" "golang.org/x/sys/unix" @@ -70,12 +69,8 @@ func (m MemoryMapsStat) String() string { // to get more information about the process. An error will be returned // if the process does not exist. func NewProcess(pid int32) (*Process, error) { - p := &Process{ - Pid: int32(pid), - } - file, err := os.Open(common.HostProc(strconv.Itoa(int(p.Pid)))) - defer file.Close() - return p, err + _, err := os.Stat(common.HostProc(strconv.Itoa(int(pid)))) + return &Process{Pid: pid}, err } // Ppid returns Parent Process ID of the process. @@ -1236,7 +1231,7 @@ func (p *Process) fillFromTIDStatWithContext(ctx context.Context, tid int32) (ui System: float64(stime / ClockTicks), } - bootTime, _ := host.BootTime() + bootTime, _ := common.BootTimeWithContext(ctx) t, err := strconv.ParseUint(fields[i+20], 10, 64) if err != nil { return 0, 0, nil, 0, 0, 0, nil, err diff --git a/process/process_windows.go b/process/process_windows.go index 2520958..96c3b1f 100644 --- a/process/process_windows.go +++ b/process/process_windows.go @@ -25,12 +25,16 @@ const ( ) var ( - modpsapi = windows.NewLazySystemDLL("psapi.dll") - procGetProcessMemoryInfo = modpsapi.NewProc("GetProcessMemoryInfo") + modpsapi = windows.NewLazySystemDLL("psapi.dll") + procGetProcessMemoryInfo = modpsapi.NewProc("GetProcessMemoryInfo") + procGetProcessImageFileNameW = modpsapi.NewProc("GetProcessImageFileNameW") advapi32 = windows.NewLazySystemDLL("advapi32.dll") procLookupPrivilegeValue = advapi32.NewProc("LookupPrivilegeValueW") procAdjustTokenPrivileges = advapi32.NewProc("AdjustTokenPrivileges") + + procQueryFullProcessImageNameW = common.Modkernel32.NewProc("QueryFullProcessImageNameW") + procGetPriorityClass = common.Modkernel32.NewProc("GetPriorityClass") ) type SystemProcessInformation struct { @@ -234,24 +238,31 @@ func (p *Process) Exe() (string, error) { } func (p *Process) ExeWithContext(ctx context.Context) (string, error) { - if p.Pid != 0 { // 0 or null is the current process for CreateToolhelp32Snapshot - snap := w32.CreateToolhelp32Snapshot(w32.TH32CS_SNAPMODULE|w32.TH32CS_SNAPMODULE32, uint32(p.Pid)) - if snap != 0 { // don't report errors here, fallback to WMI instead - defer w32.CloseHandle(snap) - var me32 w32.MODULEENTRY32 - me32.Size = uint32(unsafe.Sizeof(me32)) - - if w32.Module32First(snap, &me32) { - szexepath := windows.UTF16ToString(me32.SzExePath[:]) - return szexepath, nil - } + // 0x1000 is PROCESS_QUERY_LIMITED_INFORMATION + c, err := syscall.OpenProcess(0x1000, false, uint32(p.Pid)) + if err != nil { + return "", err + } + defer syscall.CloseHandle(c) + buf := make([]uint16, syscall.MAX_LONG_PATH) + size := uint32(syscall.MAX_LONG_PATH) + if err := procQueryFullProcessImageNameW.Find(); err == nil { // Vista+ + ret, _, err := procQueryFullProcessImageNameW.Call( + uintptr(c), + uintptr(0), + uintptr(unsafe.Pointer(&buf[0])), + uintptr(unsafe.Pointer(&size))) + if ret == 0 { + return "", err } + return windows.UTF16ToString(buf[:]), nil } - dst, err := GetWin32ProcWithContext(ctx, p.Pid) - if err != nil { - return "", fmt.Errorf("could not get ExecutablePath: %s", err) + // XP fallback + ret, _, err := procGetProcessImageFileNameW.Call(uintptr(c), uintptr(unsafe.Pointer(&buf[0])), uintptr(size)) + if ret == 0 { + return "", err } - return *dst[0].ExecutablePath, nil + return common.ConvertDOSPath(windows.UTF16ToString(buf[:])), nil } func (p *Process) Cmdline() (string, error) { @@ -382,17 +393,39 @@ func (p *Process) TerminalWithContext(ctx context.Context) (string, error) { return "", common.ErrNotImplementedError } +// priorityClasses maps a win32 priority class to its WMI equivalent Win32_Process.Priority +// https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-getpriorityclass +// https://docs.microsoft.com/en-us/windows/desktop/cimwin32prov/win32-process +var priorityClasses = map[int]int32{ + 0x00008000: 10, // ABOVE_NORMAL_PRIORITY_CLASS + 0x00004000: 6, // BELOW_NORMAL_PRIORITY_CLASS + 0x00000080: 13, // HIGH_PRIORITY_CLASS + 0x00000040: 4, // IDLE_PRIORITY_CLASS + 0x00000020: 8, // NORMAL_PRIORITY_CLASS + 0x00000100: 24, // REALTIME_PRIORITY_CLASS +} + // Nice returns priority in Windows func (p *Process) Nice() (int32, error) { return p.NiceWithContext(context.Background()) } func (p *Process) NiceWithContext(ctx context.Context) (int32, error) { - dst, err := GetWin32ProcWithContext(ctx, p.Pid) + // 0x1000 is PROCESS_QUERY_LIMITED_INFORMATION + c, err := syscall.OpenProcess(0x1000, false, uint32(p.Pid)) if err != nil { - return 0, fmt.Errorf("could not get Priority: %s", err) + return 0, err } - return int32(dst[0].Priority), nil + defer syscall.CloseHandle(c) + ret, _, err := procGetPriorityClass.Call(uintptr(c)) + if ret == 0 { + return 0, err + } + priority, ok := priorityClasses[int(ret)] + if !ok { + return 0, fmt.Errorf("unknown priority class %s", ret) + } + return priority, nil } func (p *Process) IOnice() (int32, error) { return p.IOniceWithContext(context.Background()) @@ -551,24 +584,19 @@ func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) { defer w32.CloseHandle(snap) var pe32 w32.PROCESSENTRY32 pe32.DwSize = uint32(unsafe.Sizeof(pe32)) - if w32.Process32First(snap, &pe32) == false { + if !w32.Process32First(snap, &pe32) { return out, windows.GetLastError() } - - if pe32.Th32ParentProcessID == uint32(p.Pid) { - p, err := NewProcess(int32(pe32.Th32ProcessID)) - if err == nil { - out = append(out, p) - } - } - - for w32.Process32Next(snap, &pe32) { + for { if pe32.Th32ParentProcessID == uint32(p.Pid) { p, err := NewProcess(int32(pe32.Th32ProcessID)) if err == nil { out = append(out, p) } } + if !w32.Process32Next(snap, &pe32) { + break + } } return out, nil } @@ -685,22 +713,19 @@ func getFromSnapProcess(pid int32) (int32, int32, string, error) { defer w32.CloseHandle(snap) var pe32 w32.PROCESSENTRY32 pe32.DwSize = uint32(unsafe.Sizeof(pe32)) - if w32.Process32First(snap, &pe32) == false { + if !w32.Process32First(snap, &pe32) { return 0, 0, "", windows.GetLastError() } - - if pe32.Th32ProcessID == uint32(pid) { - szexe := windows.UTF16ToString(pe32.SzExeFile[:]) - return int32(pe32.Th32ParentProcessID), int32(pe32.CntThreads), szexe, nil - } - - for w32.Process32Next(snap, &pe32) { + for { if pe32.Th32ProcessID == uint32(pid) { szexe := windows.UTF16ToString(pe32.SzExeFile[:]) return int32(pe32.Th32ParentProcessID), int32(pe32.CntThreads), szexe, nil } + if !w32.Process32Next(snap, &pe32) { + break + } } - return 0, 0, "", fmt.Errorf("Couldn't find pid: %d", pid) + return 0, 0, "", fmt.Errorf("couldn't find pid: %d", pid) } // Get processes