diff --git a/README.md b/README.md index fea8e7efd..fabd59e38 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ See https://pkg.go.dev/github.com/shirou/gopsutil/v3 or https://godocs.io/github ## Requirements -- go1.16 or above is required. +- go1.18 or above is required. ## More Info @@ -184,28 +184,29 @@ Some code is ported from Ohai. Many thanks. - x: works - b: almost works, but something is broken - -|name |Linux |FreeBSD |OpenBSD |macOS |Windows |Solaris |Plan 9 | -|----------------------|-------|---------|---------|--------|---------|---------|---------| -|cpu\_times |x |x |x |x |x | |b | -|cpu\_count |x |x |x |x |x | |x | -|cpu\_percent |x |x |x |x |x | | | -|cpu\_times\_percent |x |x |x |x |x | | | -|virtual\_memory |x |x |x |x |x | b |x | -|swap\_memory |x |x |x |x | | |x | -|disk\_partitions |x |x |x |x |x | | | -|disk\_io\_counters |x |x |x | | | | | -|disk\_usage |x |x |x |x |x | | | -|net\_io\_counters |x |x |x |b |x | | | -|boot\_time |x |x |x |x |x | | | -|users |x |x |x |x |x | | | -|pids |x |x |x |x |x | | | -|pid\_exists |x |x |x |x |x | | | -|net\_connections |x |x |x |x | | | | -|net\_protocols |x | | | | | | | -|net\_if\_addrs | | | | | | | | -|net\_if\_stats | | | | | | | | -|netfilter\_conntrack |x | | | | | | | +- c: works in CGO only + +|name |Linux |FreeBSD |OpenBSD |macOS |Windows |Solaris |Plan 9 |AIX | +|----------------------|-------|---------|---------|--------|---------|---------|---------|---------| +|cpu\_times |x |x |x |x |x | |b |x | +|cpu\_count |x |x |x |x |x | |x |x | +|cpu\_percent |x |x |x |x |x | | |x | +|cpu\_times\_percent |x |x |x |x |x | | |x | +|virtual\_memory |x |x |x |x |x | b |x |x | +|swap\_memory |x |x |x |x | | |x |X | +|disk\_partitions |x |x |x |x |x | | |x | +|disk\_io\_counters |x |x |x | | | | | | +|disk\_usage |x |x |x |x |x | | |x | +|net\_io\_counters |x |x |x |b |x | | | | +|boot\_time |x |x |x |x |x | | |X | +|users |x |x |x |x |x | | |x | +|pids |x |x |x |x |x | | | | +|pid\_exists |x |x |x |x |x | | | | +|net\_connections |x |x |x |x | | | |x | +|net\_protocols |x | | | | | | |x | +|net\_if\_addrs | | | | | | | |x | +|net\_if\_stats | | | | | | | |x | +|netfilter\_conntrack |x | | | | | | | | ### Process class @@ -254,37 +255,37 @@ Some code is ported from Ohai. Many thanks. ### Original Metrics -|item |Linux |FreeBSD |OpenBSD |macOS |Windows |Solaris | -|-----------------|-------|---------|---------|--------|--------|---------| -|**HostInfo** | | | | | | | -|hostname |x |x |x |x |x |x | -|uptime |x |x |x |x | |x | -|process |x |x |x | | |x | -|os |x |x |x |x |x |x | -|platform |x |x |x |x | |x | -|platformfamily |x |x |x |x | |x | -|virtualization |x | | | | | | -|**CPU** | | | | | | | -|VendorID |x |x |x |x |x |x | -|Family |x |x |x |x |x |x | -|Model |x |x |x |x |x |x | -|Stepping |x |x |x |x |x |x | -|PhysicalID |x | | | | |x | -|CoreID |x | | | | |x | -|Cores |x | | | |x |x | -|ModelName |x |x |x |x |x |x | -|Microcode |x | | | | |x | -|**LoadAvg** | | | | | | | -|Load1 |x |x |x |x | | | -|Load5 |x |x |x |x | | | -|Load15 |x |x |x |x | | | -|**GetDockerID** | | | | | | | -|container id |x |no |no |no |no | | -|**CgroupsCPU** | | | | | | | -|user |x |no |no |no |no | | -|system |x |no |no |no |no | | -|**CgroupsMem** | | | | | | | -|various |x |no |no |no |no | | +|item |Linux |FreeBSD |OpenBSD |macOS |Windows |Solaris |AIX | +|-----------------|-------|---------|---------|--------|--------|---------|---------| +|**HostInfo** | | | | | | | | +|hostname |x |x |x |x |x |x |X | +|uptime |x |x |x |x | |x |x | +|process |x |x |x | | |x | | +|os |x |x |x |x |x |x |x | +|platform |x |x |x |x | |x |x | +|platformfamily |x |x |x |x | |x |x | +|virtualization |x | | | | | | | +|**CPU** | | | | | | | | +|VendorID |x |x |x |x |x |x |x | +|Family |x |x |x |x |x |x |x | +|Model |x |x |x |x |x |x |x | +|Stepping |x |x |x |x |x |x | | +|PhysicalID |x | | | | |x | | +|CoreID |x | | | | |x | | +|Cores |x | | | |x |x |x | +|ModelName |x |x |x |x |x |x |x | +|Microcode |x | | | | |x | | +|**LoadAvg** | | | | | | | | +|Load1 |x |x |x |x | | |x | +|Load5 |x |x |x |x | | |x | +|Load15 |x |x |x |x | | |x | +|**GetDockerID** | | | | | | | | +|container id |x |no |no |no |no | | | +|**CgroupsCPU** | | | | | | | | +|user |x |no |no |no |no | | | +|system |x |no |no |no |no | | | +|**CgroupsMem** | | | | | | | | +|various |x |no |no |no |no | | | - future work - process_iter @@ -292,6 +293,7 @@ Some code is ported from Ohai. Many thanks. - Process class - as_dict - wait + - AIX processes ## License diff --git a/cpu/cpu_aix_nocgo.go b/cpu/cpu_aix_nocgo.go index a77b4dbb7..f3a3186aa 100644 --- a/cpu/cpu_aix_nocgo.go +++ b/cpu/cpu_aix_nocgo.go @@ -12,8 +12,57 @@ import ( ) func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { + var ret []TimesStat if percpu { - return []TimesStat{}, common.ErrNotImplementedError + per_out, err := invoke.CommandWithContext(ctx, "sar", "-u", "-P", "ALL", "10", "1") + if err != nil { + return nil, err + } + lines := strings.Split(string(per_out), "\n") + if len(lines) < 6 { + return []TimesStat{}, common.ErrNotImplementedError + } + + hp := strings.Fields(lines[5]) // headers + for l := 6; l < len(lines)-1; l++ { + ct := &TimesStat{} + v := strings.Fields(lines[l]) // values + for i, header := range hp { + // We're done in any of these use cases + if i >= len(v) || v[0] == "-" { + break + } + + // Position variable for v + pos := i + // There is a missing field at the beginning of all but the first line + // so adjust the position + if l > 6 { + pos = i - 1 + } + // We don't want invalid positions + if pos < 0 { + continue + } + + if t, err := strconv.ParseFloat(v[pos], 64); err == nil { + switch header { + case `cpu`: + ct.CPU = strconv.FormatFloat(t, 'f', -1, 64) + case `%usr`: + ct.User = t + case `%sys`: + ct.System = t + case `%wio`: + ct.Iowait = t + case `%idle`: + ct.Idle = t + } + } + } + // Valid CPU data, so append it + ret = append(ret, *ct) + } } else { out, err := invoke.CommandWithContext(ctx, "sar", "-u", "10", "1") if err != nil { @@ -24,26 +73,28 @@ func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { return []TimesStat{}, common.ErrNotImplementedError } - ret := TimesStat{CPU: "cpu-total"} + ct := &TimesStat{CPU: "cpu-total"} h := strings.Fields(lines[len(lines)-3]) // headers v := strings.Fields(lines[len(lines)-2]) // values for i, header := range h { if t, err := strconv.ParseFloat(v[i], 64); err == nil { switch header { case `%usr`: - ret.User = t + ct.User = t case `%sys`: - ret.System = t + ct.System = t case `%wio`: - ret.Iowait = t + ct.Iowait = t case `%idle`: - ret.Idle = t + ct.Idle = t } } } - return []TimesStat{ret}, nil + ret = append(ret, *ct) } + + return ret, nil } func InfoWithContext(ctx context.Context) ([]InfoStat, error) { @@ -78,6 +129,20 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) { } } break + } else if strings.HasPrefix(line, "System Model:") { + p := strings.Split(string(line), ":") + if p != nil { + ret.VendorID = strings.TrimSpace(p[1]) + } + } else if strings.HasPrefix(line, "Processor Type:") { + p := strings.Split(string(line), ":") + if p != nil { + c := strings.Split(string(p[1]), "_") + if c != nil { + ret.Family = strings.TrimSpace(c[0]) + ret.Model = strings.TrimSpace(c[1]) + } + } } } return []InfoStat{ret}, nil diff --git a/cpu/testdata/aix/prtconf b/cpu/testdata/aix/prtconf new file mode 100644 index 000000000..7d4d93a97 --- /dev/null +++ b/cpu/testdata/aix/prtconf @@ -0,0 +1,68 @@ +System Model: IBM pSeries (emulated by qemu) +Machine Serial Number: Not Available +Processor Type: PowerPC_POWER8 +Processor Implementation Mode: POWER 8 +Processor Version: PV_8_Compat +Number Of Processors: 4 +Processor Clock Speed: 1000 MHz +CPU Type: 64-bit +Kernel Type: 64-bit +LPAR Info: 0 aix_7200-04-02-2027 +Memory Size: 4096 MB +Good Memory Size: 4096 MB +Platform Firmware level: Not Available +Firmware Version: SLOF,HEAD +Console Login: enable +Auto Restart: true +Full Core: false +NX Crypto Acceleration: Not Capable +In-Core Crypto Acceleration: Capable, but not Enabled + +en0 +Network Information + Host Name: aix72-dylan + IP Address: 192.168.124.53 + Sub Netmask: + Gateway: 192.168.124.1 + Name Server: + Domain Name: + +Paging Space Information + Total Paging Space: 512MB + Percent Used: 1% + +Volume Groups Information +============================================================================== +Active VGs +============================================================================== +rootvg: +PV_NAME PV STATE TOTAL PPs FREE PPs FREE DISTRIBUTION +hdisk0 active 999 809 199..193..17..200..200 +============================================================================== + +INSTALLED RESOURCE LIST + +The following resources are installed on the machine. ++/- = Added or deleted from Resource List. +* = Diagnostic support not available. + + Model Architecture: chrp + Model Implementation: Uni-Processor, PCI bus + ++ sys0 System Object ++ sysplanar0 System Planar +* vio0 Virtual I/O Bus +* ent0 Virtual I/O Ethernet Adapter (l-lan) +* vscsi0 Virtual SCSI Client Adapter +* cd0 Virtual SCSI Optical Served by VIO Server +* vsa0 LPAR Virtual Serial Adapter +* vty0 Asynchronous Terminal +* pci0 PCI Bus +* scsi0 qemu_virtio-scsi-pci:0000:00:02.0 Virtio SCSI Client Adapter (f41a0800) +* hdisk0 qemu_virtio-scsi-pci:0000:00:02.0-LW_0 MPIO Other Virtio SCSI Disk Drive ++ L2cache0 L2 Cache ++ mem0 Memory ++ proc0 Processor ++ proc1 Processor ++ proc2 Processor ++ proc3 Processor diff --git a/cpu/testdata/aix/sar-u-PALL101 b/cpu/testdata/aix/sar-u-PALL101 new file mode 100644 index 000000000..2a3dc63e6 --- /dev/null +++ b/cpu/testdata/aix/sar-u-PALL101 @@ -0,0 +1,11 @@ + +AIX aix72-dylan 2 7 000000000000 05/15/24 + +System configuration: lcpu=4 ent=4.00 mode=Capped + +11:19:03 cpu %usr %sys %wio %idle physc %entc +11:19:13 0 1 11 0 88 1.00 25.0 + 1 0 0 0 100 1.00 25.0 + 2 0 0 0 100 1.00 25.0 + 3 0 0 0 100 1.00 25.0 + - 0 3 0 97 4.00 100.0 diff --git a/cpu/testdata/aix/sar-u101 b/cpu/testdata/aix/sar-u101 new file mode 100644 index 000000000..e3dbb13f2 --- /dev/null +++ b/cpu/testdata/aix/sar-u101 @@ -0,0 +1,7 @@ + +AIX aix72-dylan 2 7 000000000000 05/15/24 + +System configuration: lcpu=4 ent=4.00 mode=Capped + +11:19:44 %usr %sys %wio %idle physc %entc +11:19:54 0 3 0 96 4.00 100.0 diff --git a/disk/disk_aix.go b/disk/disk_aix.go index bc71712ea..ac8d09120 100644 --- a/disk/disk_aix.go +++ b/disk/disk_aix.go @@ -5,6 +5,8 @@ package disk import ( "context" + "errors" + "strings" "github.com/shirou/gopsutil/v3/internal/common" ) @@ -13,10 +15,36 @@ func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOC return nil, common.ErrNotImplementedError } -func SerialNumberWithContext(ctx context.Context, name string) (string, error) { +func LabelWithContext(ctx context.Context, name string) (string, error) { return "", common.ErrNotImplementedError } -func LabelWithContext(ctx context.Context, name string) (string, error) { - return "", common.ErrNotImplementedError +// Using lscfg and a device name, we can get the device information +// This is a pure go implementation, and should be moved to disk_aix_nocgo.go +// if a more efficient CGO method is introduced in disk_aix_cgo.go +func SerialNumberWithContext(ctx context.Context, name string) (string, error) { + // This isn't linux, these aren't actual disk devices + if strings.HasPrefix(name, "/dev/") { + return "", errors.New("devices on /dev are not physical disks on aix") + } + out, err := invoke.CommandWithContext(ctx, "lscfg", "-vl", name) + if err != nil { + return "", err + } + + ret := "" + // Kind of inefficient, but it works + lines := strings.Split(string(out[:]), "\n") + for line := 1; line < len(lines); line++ { + v := strings.TrimSpace(lines[line]) + if strings.HasPrefix(v, "Serial Number...............") { + ret = strings.TrimPrefix(v, "Serial Number...............") + if ret == "" { + return "", errors.New("empty serial for disk") + } + return ret, nil + } + } + + return ret, errors.New("serial entry not found for disk") } diff --git a/disk/disk_aix_nocgo.go b/disk/disk_aix_nocgo.go index 17e2b9c84..fcb11617d 100644 --- a/disk/disk_aix_nocgo.go +++ b/disk/disk_aix_nocgo.go @@ -6,6 +6,7 @@ package disk import ( "context" "regexp" + "strconv" "strings" "github.com/shirou/gopsutil/v3/internal/common" @@ -79,3 +80,108 @@ func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, erro func getFsType(stat unix.Statfs_t) string { return FSType[int(stat.Vfstype)] } + +func UsageWithContext(ctx context.Context, path string) (*UsageStat, error) { + out, err := invoke.CommandWithContext(ctx, "df", "-v") + if err != nil { + return nil, err + } + + ret := &UsageStat{} + + blocksize := uint64(512) + lines := strings.Split(string(out), "\n") + if len(lines) < 2 { + return &UsageStat{}, common.ErrNotImplementedError + } + + hf := strings.Fields(strings.Replace(lines[0], "Mounted on", "Path", -1)) // headers + for line := 1; line < len(lines); line++ { + fs := strings.Fields(lines[line]) // values + for i, header := range hf { + // We're done in any of these use cases + if i >= len(fs) { + break + } + + switch header { + case `Filesystem`: + // This is not a valid fs for us to parse + if fs[i] == "/proc" || fs[i] == "/ahafs" || fs[i] != path { + break + } + + ret.Fstype, err = GetMountFSTypeWithContext(ctx, fs[i]) + if err != nil { + return nil, err + } + case `Path`: + ret.Path = fs[i] + case `512-blocks`: + total, err := strconv.ParseUint(fs[i], 10, 64) + ret.Total = total * blocksize + if err != nil { + return nil, err + } + case `Used`: + ret.Used, err = strconv.ParseUint(fs[i], 10, 64) + if err != nil { + return nil, err + } + case `Free`: + ret.Free, err = strconv.ParseUint(fs[i], 10, 64) + if err != nil { + return nil, err + } + case `%Used`: + val, err := strconv.Atoi(strings.Replace(fs[i], "%", "", -1)) + if err != nil { + return nil, err + } + ret.UsedPercent = float64(val) / float64(100) + case `Ifree`: + ret.InodesFree, err = strconv.ParseUint(fs[i], 10, 64) + if err != nil { + return nil, err + } + case `Iused`: + ret.InodesUsed, err = strconv.ParseUint(fs[i], 10, 64) + if err != nil { + return nil, err + } + case `%Iused`: + val, err := strconv.Atoi(strings.Replace(fs[i], "%", "", -1)) + if err != nil { + return nil, err + } + ret.InodesUsedPercent = float64(val) / float64(100) + } + } + + // Calculated value, since it isn't returned by the command + ret.InodesTotal = ret.InodesUsed + ret.InodesFree + + // Valid Usage data, so append it + return ret, nil + } + + return ret, nil +} + +func GetMountFSTypeWithContext(ctx context.Context, mp string) (string, error) { + out, err := invoke.CommandWithContext(ctx, "mount") + if err != nil { + return "", err + } + + // Kind of inefficient, but it works + lines := strings.Split(string(out[:]), "\n") + for line := 1; line < len(lines); line++ { + fields := strings.Fields(lines[line]) + if strings.TrimSpace(fields[0]) == mp { + return fields[2], nil + } + } + + return "", nil +} diff --git a/disk/disk_unix.go b/disk/disk_unix.go index 1e7352408..4cef8cbac 100644 --- a/disk/disk_unix.go +++ b/disk/disk_unix.go @@ -1,5 +1,5 @@ -//go:build freebsd || linux || darwin || (aix && !cgo) -// +build freebsd linux darwin aix,!cgo +//go:build freebsd || linux || darwin +// +build freebsd linux darwin package disk diff --git a/go.mod b/go.mod index aa8157a53..d4238a3de 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/shirou/gopsutil/v3 -go 1.15 +go 1.18 require ( github.com/google/go-cmp v0.6.0 @@ -13,4 +13,12 @@ require ( golang.org/x/sys v0.20.0 ) +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + retract v3.22.11 diff --git a/go.sum b/go.sum index 1c0a78124..64e81120b 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,8 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= @@ -16,14 +14,6 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:Om github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= -github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= @@ -41,6 +31,5 @@ golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/host/host_aix.go b/host/host_aix.go new file mode 100644 index 000000000..d06899ea0 --- /dev/null +++ b/host/host_aix.go @@ -0,0 +1,202 @@ +//go:build aix +// +build aix + +package host + +import ( + "context" + "errors" + "strconv" + "strings" + + "github.com/shirou/gopsutil/v3/internal/common" +) + +// from https://www.ibm.com/docs/en/aix/7.2?topic=files-utmph-file +const ( + user_PROCESS = 7 + + hostTemperatureScale = 1000.0 // Not part of the linked file, but kept just in case it becomes relevant +) + +func HostIDWithContext(ctx context.Context) (string, error) { + out, err := invoke.CommandWithContext(ctx, "uname", "-u") + if err != nil { + return "", err + } + + // The command always returns an extra newline, so we make use of Split() to get only the first line + return strings.Split(string(out[:]), "\n")[0], nil +} + +func numProcs(ctx context.Context) (uint64, error) { + return 0, common.ErrNotImplementedError +} + +func BootTimeWithContext(ctx context.Context) (btime uint64, err error) { + ut, err := UptimeWithContext(ctx) + if err != nil { + return 0, err + } + + if ut <= 0 { + return 0, errors.New("Uptime was not set, so cannot calculate boot time from it.") + } + + ut = ut * 60 + return timeSince(ut), nil +} + +// This function takes multiple formats of output frmo the uptime +// command and converts the data into minutes. +// Some examples of uptime output that this command handles: +// 11:54AM up 13 mins, 1 user, load average: 2.78, 2.62, 1.79 +// 12:41PM up 1 hr, 1 user, load average: 2.47, 2.85, 2.83 +// 07:43PM up 5 hrs, 1 user, load average: 3.27, 2.91, 2.72 +// 11:18:23 up 83 days, 18:29, 4 users, load average: 0.16, 0.03, 0.01 +func UptimeWithContext(ctx context.Context) (uint64, error) { + out, err := invoke.CommandWithContext(ctx, "uptime") + if err != nil { + return 0, err + } + + // Convert our uptime to a series of fields we can extract + ut := strings.Fields(string(out[:])) + + // Convert the second field value to integer + var days uint64 = 0 + var hours uint64 = 0 + var minutes uint64 = 0 + if ut[3] == "days," { + days, err = strconv.ParseUint(ut[2], 10, 64) + if err != nil { + return 0, err + } + + // Split field 4 into hours and minutes + hm := strings.Split(ut[4], ":") + hours, err = strconv.ParseUint(hm[0], 10, 64) + if err != nil { + return 0, err + } + minutes, err = strconv.ParseUint(strings.Replace(hm[1], ",", "", -1), 10, 64) + if err != nil { + return 0, err + } + } else if ut[3] == "hr," || ut[3] == "hrs," { + hours, err = strconv.ParseUint(ut[2], 10, 64) + if err != nil { + return 0, err + } + } else if ut[3] == "mins," { + minutes, err = strconv.ParseUint(ut[2], 10, 64) + if err != nil { + return 0, err + } + } else if _, err := strconv.ParseInt(ut[3], 10, 64); err == nil && strings.Contains(ut[2], ":") { + // Split field 2 into hours and minutes + hm := strings.Split(ut[2], ":") + hours, err = strconv.ParseUint(hm[0], 10, 64) + if err != nil { + return 0, err + } + minutes, err = strconv.ParseUint(strings.Replace(hm[1], ",", "", -1), 10, 64) + if err != nil { + return 0, err + } + } + + // Stack them all together as minutes + total_time := (days * 24 * 60) + (hours * 60) + minutes + + return total_time, nil +} + +// This is a weak implementation due to the limitations on retrieving this data in AIX +func UsersWithContext(ctx context.Context) ([]UserStat, error) { + var ret []UserStat + out, err := invoke.CommandWithContext(ctx, "w") + if err != nil { + return nil, err + } + lines := strings.Split(string(out), "\n") + if len(lines) < 3 { + return []UserStat{}, common.ErrNotImplementedError + } + + hf := strings.Fields(lines[1]) // headers + for l := 2; l < len(lines); l++ { + v := strings.Fields(lines[l]) // values + us := &UserStat{} + for i, header := range hf { + // We're done in any of these use cases + if i >= len(v) || v[0] == "-" { + break + } + + if t, err := strconv.ParseFloat(v[i], 64); err == nil { + switch header { + case `User`: + us.User = strconv.FormatFloat(t, 'f', 1, 64) + case `tty`: + us.Terminal = strconv.FormatFloat(t, 'f', 1, 64) + } + } + } + + // Valid User data, so append it + ret = append(ret, *us) + } + + return ret, nil +} + +// Much of this function could be static. However, to be future proofed, I've made it call the OS for the information in all instances. +func PlatformInformationWithContext(ctx context.Context) (platform string, family string, version string, err error) { + // Set the platform (which should always, and only be, "AIX") from `uname -s` + out, err := invoke.CommandWithContext(ctx, "uname", "-s") + if err != nil { + return "", "", "", err + } + platform = strings.TrimRight(string(out[:]), "\n") + + // Set the family + family = strings.TrimRight(string(out[:]), "\n") + + // Set the version + out, err = invoke.CommandWithContext(ctx, "oslevel") + if err != nil { + return "", "", "", err + } + version = strings.TrimRight(string(out[:]), "\n") + + return platform, family, version, nil +} + +func KernelVersionWithContext(ctx context.Context) (version string, err error) { + out, err := invoke.CommandWithContext(ctx, "oslevel", "-s") + if err != nil { + return "", err + } + version = strings.TrimRight(string(out[:]), "\n") + + return version, nil +} + +func KernelArch() (arch string, err error) { + out, err := invoke.Command("bootinfo", "-y") + if err != nil { + return "", err + } + arch = strings.TrimRight(string(out[:]), "\n") + + return arch, nil +} + +func VirtualizationWithContext(ctx context.Context) (string, string, error) { + return "", "", common.ErrNotImplementedError +} + +func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) { + return nil, common.ErrNotImplementedError +} diff --git a/host/host_aix_ppc64.go b/host/host_aix_ppc64.go new file mode 100644 index 000000000..de9674b73 --- /dev/null +++ b/host/host_aix_ppc64.go @@ -0,0 +1,48 @@ +//go:build aix && ppc64 && cgo +// +build aix,ppc64,cgo + +// Guessed at from the following document: +// https://www.ibm.com/docs/sl/ibm-mq/9.2?topic=platforms-standard-data-types-aix-linux-windows + +package host + +const ( + sizeofPtr = 0x8 + sizeofShort = 0x2 + sizeofInt = 0x4 + sizeofLong = 0x8 + sizeofLongLong = 0x8 + sizeOfUtmp = 0x180 +) + +type ( + _C_short int16 + _C_int int32 + _C_long int64 + _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__glibc_reserved [20]int8 +} + +type exit_status struct { + Termination int16 + Exit int16 +} + +type timeval struct { + Sec int64 + Usec int64 +} diff --git a/host/host_fallback.go b/host/host_fallback.go index a393ca15d..150ccf008 100644 --- a/host/host_fallback.go +++ b/host/host_fallback.go @@ -1,5 +1,5 @@ -//go:build !darwin && !linux && !freebsd && !openbsd && !netbsd && !solaris && !windows -// +build !darwin,!linux,!freebsd,!openbsd,!netbsd,!solaris,!windows +//go:build !darwin && !linux && !freebsd && !openbsd && !netbsd && !solaris && !windows && !aix +// +build !darwin,!linux,!freebsd,!openbsd,!netbsd,!solaris,!windows,!aix package host diff --git a/mem/mem_aix_nocgo.go b/mem/mem_aix_nocgo.go index cc6a76d2f..027879d9a 100644 --- a/mem/mem_aix_nocgo.go +++ b/mem/mem_aix_nocgo.go @@ -12,7 +12,7 @@ import ( ) func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) { - vmem, swap, err := callSVMon(ctx) + vmem, swap, err := callSVMon(ctx, true) if err != nil { return nil, err } @@ -25,7 +25,7 @@ func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) { } func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) { - _, swap, err := callSVMon(ctx) + _, swap, err := callSVMon(ctx, false) if err != nil { return nil, err } @@ -35,7 +35,7 @@ func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) { return swap, nil } -func callSVMon(ctx context.Context) (*VirtualMemoryStat, *SwapMemoryStat, error) { +func callSVMon(ctx context.Context, virt bool) (*VirtualMemoryStat, *SwapMemoryStat, error) { out, err := invoke.CommandWithContext(ctx, "svmon", "-G") if err != nil { return nil, nil, err @@ -45,7 +45,7 @@ func callSVMon(ctx context.Context) (*VirtualMemoryStat, *SwapMemoryStat, error) vmem := &VirtualMemoryStat{} swap := &SwapMemoryStat{} for _, line := range strings.Split(string(out), "\n") { - if strings.HasPrefix(line, "memory") { + if virt && strings.HasPrefix(line, "memory") { p := strings.Fields(line) if len(p) > 2 { if t, err := strconv.ParseUint(p[1], 10, 64); err == nil {