From 6b630a841abc3014117707908496ff8d1f79c683 Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Wed, 15 May 2024 09:49:08 -0400 Subject: [PATCH 01/23] Add support for per CPU --- cpu/cpu_aix_nocgo.go | 65 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/cpu/cpu_aix_nocgo.go b/cpu/cpu_aix_nocgo.go index a77b4dbb7..bd0bb1700 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", "10", "1") + if err != nil { + return nil, err + } + lines := strings.Split(string(per_out), "\n") + if len(lines) < 5 { + return []TimesStat{}, common.ErrNotImplementedError + } + + hp := strings.Fields(lines[5]) // headers + for l := 6; l < len(lines)-1; l++ { + 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 + } + ct := &TimesStat{} + if t, err := strconv.ParseFloat(v[i], 64); err == nil { + switch header { + case `cpu`: + ct.CPU = t + 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) { From 8cc32696fee491c6e9e48622e2c4df092f851adb Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Wed, 15 May 2024 10:46:07 -0400 Subject: [PATCH 02/23] Add support for additional CPU info fields --- cpu/cpu_aix_nocgo.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cpu/cpu_aix_nocgo.go b/cpu/cpu_aix_nocgo.go index bd0bb1700..e4594b26c 100644 --- a/cpu/cpu_aix_nocgo.go +++ b/cpu/cpu_aix_nocgo.go @@ -129,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 From af3f4d204db9bf4d37176f6d1e3909e8e60fa0f9 Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Wed, 15 May 2024 12:55:11 -0400 Subject: [PATCH 03/23] Correct arguments for AIX percpu --- cpu/cpu_aix_nocgo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpu/cpu_aix_nocgo.go b/cpu/cpu_aix_nocgo.go index e4594b26c..99abaedd2 100644 --- a/cpu/cpu_aix_nocgo.go +++ b/cpu/cpu_aix_nocgo.go @@ -14,7 +14,7 @@ import ( func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { var ret []TimesStat if percpu { - per_out, err := invoke.CommandWithContext(ctx, "sar", "-u", "10", "1") + per_out, err := invoke.CommandWithContext(ctx, "sar", "-u", "-P", "ALL", "10", "1") if err != nil { return nil, err } From d816d96508bff3c8d1b3453d43e6b9c3537758cd Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Wed, 15 May 2024 12:57:58 -0400 Subject: [PATCH 04/23] Initial pass at AIX test integration --- cpu/cpu_aix_test.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 cpu/cpu_aix_test.go diff --git a/cpu/cpu_aix_test.go b/cpu/cpu_aix_test.go new file mode 100644 index 000000000..ff25006a6 --- /dev/null +++ b/cpu/cpu_aix_test.go @@ -0,0 +1,33 @@ +package cpu + +import ( + "testing" +) + +func TestTimes(t *testing.T) { + _, err := Times(true) + if err != nil { + t.Error("Times(true) failed") + } + _, err = Times(false) + if err != nil { + t.Error("Times(false) failed") + } +} + +func TestInfo(t *testing.T) { + { + l, err := Times(true) + if err != nil || len(l) == 0 { + t.Error("Times(true) failed") + } + t.Logf("Times(true): %#v", l) + } + { + l, err := Times(false) + if err != nil || len(l) == 0 { + t.Error("Times(false) failed") + } + t.Logf("Times(false): %#v", l) + } +} From 843d93a5898561a92209df0ebc0deecc522664bc Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Wed, 15 May 2024 12:59:23 -0400 Subject: [PATCH 05/23] Add AIX test data --- cpu/testdata/aix/prtconf | 68 ++++++++++++++++++++++++++++++++++ cpu/testdata/aix/sar-u-PALL101 | 11 ++++++ cpu/testdata/aix/sar-u101 | 7 ++++ 3 files changed, 86 insertions(+) create mode 100644 cpu/testdata/aix/prtconf create mode 100644 cpu/testdata/aix/sar-u-PALL101 create mode 100644 cpu/testdata/aix/sar-u101 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 From 0a1f2200776b85585df44f7e0c1a7749bae3c29e Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Wed, 15 May 2024 13:54:44 -0400 Subject: [PATCH 06/23] Remove tests, since my Go knowledge isn't yet deep enough for them --- cpu/cpu_aix_test.go | 33 --------------------------------- 1 file changed, 33 deletions(-) delete mode 100644 cpu/cpu_aix_test.go diff --git a/cpu/cpu_aix_test.go b/cpu/cpu_aix_test.go deleted file mode 100644 index ff25006a6..000000000 --- a/cpu/cpu_aix_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package cpu - -import ( - "testing" -) - -func TestTimes(t *testing.T) { - _, err := Times(true) - if err != nil { - t.Error("Times(true) failed") - } - _, err = Times(false) - if err != nil { - t.Error("Times(false) failed") - } -} - -func TestInfo(t *testing.T) { - { - l, err := Times(true) - if err != nil || len(l) == 0 { - t.Error("Times(true) failed") - } - t.Logf("Times(true): %#v", l) - } - { - l, err := Times(false) - if err != nil || len(l) == 0 { - t.Error("Times(false) failed") - } - t.Logf("Times(false): %#v", l) - } -} From 5d3cf2e9e7f17a736d20ed09d762e2d4363c868d Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Wed, 15 May 2024 13:55:37 -0400 Subject: [PATCH 07/23] Update AIX percpu minimum needed lines --- cpu/cpu_aix_nocgo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpu/cpu_aix_nocgo.go b/cpu/cpu_aix_nocgo.go index 99abaedd2..5f4bc499b 100644 --- a/cpu/cpu_aix_nocgo.go +++ b/cpu/cpu_aix_nocgo.go @@ -19,7 +19,7 @@ func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { return nil, err } lines := strings.Split(string(per_out), "\n") - if len(lines) < 5 { + if len(lines) < 6 { return []TimesStat{}, common.ErrNotImplementedError } From 52d61b8669999d2fa1075760aa5f837e9cb88a90 Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Wed, 15 May 2024 21:44:58 -0400 Subject: [PATCH 08/23] Add host support for AIX --- host/host_aix.go | 158 +++++++++++++++++++++++++++++++++++++++++ host/host_aix_ppc64.go | 48 +++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 host/host_aix.go create mode 100644 host/host_aix_ppc64.go diff --git a/host/host_aix.go b/host/host_aix.go new file mode 100644 index 000000000..b0cdb0f2f --- /dev/null +++ b/host/host_aix.go @@ -0,0 +1,158 @@ +//go:build aix +// +build aix + +package host + +import ( + "bytes" + "context" + "encoding/binary" + "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] +} + +func numProcs(ctx context.Context) (uint64, error) { + return common.NumProcsWithContext(ctx) +} + +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 +} + +func UptimeWithContext(ctx context.Context) (uint64, error) { + out, err := invoke.CommandWithContext(ctx, "uptime").Output() + 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 "Days" value to integer and roll it to minutes + days, err := strconv.Atoi(ut[2]) + if err != nil { + return 0, err + } + + // Split field 4 into hours and minutes + hm := strings.Split(ut[4], ":") + hours, err := strconv.Atoi(hm[0]) + if err != nil { + return 0, err + } + minutes, err := strconv.Atoi(strings.Replace(hm[1], ",", "", -1)) + if err != nil { + return 0, err + } + + // Stack them all together as minutes + total_time := (days * 24 * 60) + (hours * 60) + minutes + + return uint64(total_time), nil +} + +// This is probably broken, it doesn't seem to work even with CGO +func UsersWithContext(ctx context.Context) ([]UserStat, error) { + var ret []UserStat + ut, err := invoke.CommandWithContext(ctx, "w").Output() + + for i := 0; i < count; i++ { + b := buf[i*sizeOfUtmp : (i+1)*sizeOfUtmp] + + var u utmp + br := bytes.NewReader(b) + err := binary.Read(br, binary.LittleEndian, &u) + if err != nil { + continue + } + if u.Type != user_PROCESS { + continue + } + user := UserStat{ + User: common.IntToString(u.User[:]), + Terminal: common.IntToString(u.Line[:]), + Host: common.IntToString(u.Host[:]), + Started: int(u.Tv.Sec), + } + ret = append(ret, user) + } + + 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").Output() + if err != nil { + return "", "", "", err + } + platform = string(out[:]) + + // Set the family + out, err = invoke.CommandWithContext(ctx, "bootinfo", "-p").Output() + if err != nil { + return "", "", "", err + } + // Family seems to always be the second field from this uname, so pull that out + family = string(out[:]) + + // Set the version + out, err = invoke.CommandWithContext(ctx, "oslevel").Output() + if err != nil { + return "", "", "", err + } + version = string(out[:]) + + return platform, family, version, nil +} + +func KernelVersionWithContext(ctx context.Context) (version string, err error) { + out, err := invoke.CommandWithContext(ctx, "oslevel", "-s").Output() + if err != nil { + return "", err + } + version = string(out[:]) + + return version, nil +} + +func KernelArch() (arch string, err error) { + out, err := invoke.CommandWithContext(ctx, "bootinfo", "-y").Output() + if err != nil { + return "", err + } + arch = string(out[:]) + + return arch, nil +} diff --git a/host/host_aix_ppc64.go b/host/host_aix_ppc64.go new file mode 100644 index 000000000..781629378 --- /dev/null +++ b/host/host_aix_ppc64.go @@ -0,0 +1,48 @@ +//go:build aix && ppc64 +// +build aix,ppc64 + +// 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 = 0x + 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 +} From d551997afb1a5b4fdd2a4697423690f3d3453588 Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Wed, 15 May 2024 22:07:56 -0400 Subject: [PATCH 09/23] Fix typos and code misplacement in AIX CPU handling --- cpu/cpu_aix_nocgo.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cpu/cpu_aix_nocgo.go b/cpu/cpu_aix_nocgo.go index 5f4bc499b..9796cf571 100644 --- a/cpu/cpu_aix_nocgo.go +++ b/cpu/cpu_aix_nocgo.go @@ -25,6 +25,7 @@ func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { 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 @@ -43,8 +44,8 @@ func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { if pos < 0 { continue } - ct := &TimesStat{} - if t, err := strconv.ParseFloat(v[i], 64); err == nil { + + if t, err := strconv.ParseFloat(v[pos], 64); err == nil { switch header { case `cpu`: ct.CPU = t @@ -58,10 +59,9 @@ func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { ct.Idle = t } } - - // Valid CPU data, so append it - ret = append(ret, *ct) } + // Valid CPU data, so append it + ret = append(ret, *ct) } } else { out, err := invoke.CommandWithContext(ctx, "sar", "-u", "10", "1") From bd42769f8755216a5c6aa4e95286dcf2258f76d4 Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Wed, 15 May 2024 22:12:03 -0400 Subject: [PATCH 10/23] Add a working implementation of host.Users() for AIX --- host/host_aix.go | 52 ++++++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/host/host_aix.go b/host/host_aix.go index b0cdb0f2f..a0922ee5c 100644 --- a/host/host_aix.go +++ b/host/host_aix.go @@ -4,9 +4,7 @@ package host import ( - "bytes" "context" - "encoding/binary" "errors" "strconv" "strings" @@ -81,30 +79,40 @@ func UptimeWithContext(ctx context.Context) (uint64, error) { return uint64(total_time), nil } -// This is probably broken, it doesn't seem to work even with CGO +// 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 - ut, err := invoke.CommandWithContext(ctx, "w").Output() - - for i := 0; i < count; i++ { - b := buf[i*sizeOfUtmp : (i+1)*sizeOfUtmp] + out, err := invoke.CommandWithContext(ctx, "w").Output() + if err != nil { + return nil, err + } + lines := strings.Split(string(out), "\n") + if len(lines) < 3 { + return []UserStat{}, common.ErrNotImplementedError + } - var u utmp - br := bytes.NewReader(b) - err := binary.Read(br, binary.LittleEndian, &u) - if err != nil { - continue + hf := strings.Fields(lines[1]) // headers + for l := 2; l < len(lines)-1; 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 = t + case `tty`: + us.Terminal = t + } + } } - if u.Type != user_PROCESS { - continue - } - user := UserStat{ - User: common.IntToString(u.User[:]), - Terminal: common.IntToString(u.Line[:]), - Host: common.IntToString(u.Host[:]), - Started: int(u.Tv.Sec), - } - ret = append(ret, user) + + // Valid User data, so append it + ret = append(ret, *us) } return ret, nil From 9be5f7d0adeea2c79bd86f87998f31fcc9454277 Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Wed, 15 May 2024 22:51:58 -0400 Subject: [PATCH 11/23] Remove extraneous -1 from users function for AIX --- host/host_aix.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/host/host_aix.go b/host/host_aix.go index a0922ee5c..23a1a62d1 100644 --- a/host/host_aix.go +++ b/host/host_aix.go @@ -92,7 +92,7 @@ func UsersWithContext(ctx context.Context) ([]UserStat, error) { } hf := strings.Fields(lines[1]) // headers - for l := 2; l < len(lines)-1; l++ { + for l := 2; l < len(lines); l++ { v := strings.Fields(lines[l]) // values us := &UserStat{} for i, header := range hf { From ea8f72ed4a2014acb3b37efb89ed8a5bf13faa5b Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Thu, 16 May 2024 09:32:46 -0400 Subject: [PATCH 12/23] Add Usage for AIX nocgo --- disk/disk_aix_nocgo.go | 103 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/disk/disk_aix_nocgo.go b/disk/disk_aix_nocgo.go index 17e2b9c84..20e0ce319 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,105 @@ 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) { + var ret []UsageStat + out, err := invoke.CommandWithContext(ctx, "df", "-v") + if err != nil { + return nil, err + } + + 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 + us := &UsageStat{} + 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" { + continue + } + + us.Fstype, err = GetMountFSType(fs[i]) + if err != nil { + return nil, err + } + case `Path`: + us.Path = fs[i] + case `512-blocks`: + us.Total, err = strconv.Atoi(fs[i]) + if err != nil { + return nil, err + } + case `Used`: + us.Used, err = strconv.Atoi(fs[i]) + if err != nil { + return nil, err + } + case `Free`: + us.Free, err = strconv.Atoi(fs[i]) + if err != nil { + return nil, err + } + case `%Used`: + us.UsedPercent, err = strconv.ParseFloat(fs[i]) + if err != nil { + return nil, err + } + case `Ifree`: + us.InodesFree, err = strconv.Atoi(fs[i]) + if err != nil { + return nil, err + } + case `Iused`: + us.InodesUsed, err = strconv.Atoi(fs[i]) + if err != nil { + return nil, err + } + case `%Iused`: + us.InodesUsedPercent, err = strconv.ParseFloat(fs[i]) + if err != nil { + return nil, err + } + } + } + + // Calculated value, since it isn't returned by the command + us.InodesTotal = us.InodesUsed + us.InodesFree + + // Valid Usage data, so append it + ret = append(ret, *us) + } + + return *ret, nil +} + +func GetMountFSType(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 +} From 3bf34ce7fb5f8ba3714d3ae24ff46350fde4f1b4 Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Fri, 17 May 2024 14:39:44 -0400 Subject: [PATCH 13/23] Add AIX disk serial numbers for nocgo --- disk/disk_aix.go | 4 ---- disk/disk_aix_cgo.go | 5 +++++ disk/disk_aix_nocgo.go | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/disk/disk_aix.go b/disk/disk_aix.go index bc71712ea..2e1699b83 100644 --- a/disk/disk_aix.go +++ b/disk/disk_aix.go @@ -13,10 +13,6 @@ func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOC return nil, common.ErrNotImplementedError } -func SerialNumberWithContext(ctx context.Context, name string) (string, error) { - return "", common.ErrNotImplementedError -} - func LabelWithContext(ctx context.Context, name string) (string, error) { return "", common.ErrNotImplementedError } diff --git a/disk/disk_aix_cgo.go b/disk/disk_aix_cgo.go index aa534df30..ec8a30e09 100644 --- a/disk/disk_aix_cgo.go +++ b/disk/disk_aix_cgo.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/power-devops/perfstat" + "github.com/shirou/gopsutil/v3/internal/common" ) var FSType map[int]string @@ -74,3 +75,7 @@ func UsageWithContext(ctx context.Context, path string) (*UsageStat, error) { } return nil, fmt.Errorf("mountpoint %s not found", path) } + +func SerialNumberWithContext(ctx context.Context, name string) (string, error) { + return "", common.ErrNotImplementedError +} diff --git a/disk/disk_aix_nocgo.go b/disk/disk_aix_nocgo.go index 20e0ce319..c10b6d05b 100644 --- a/disk/disk_aix_nocgo.go +++ b/disk/disk_aix_nocgo.go @@ -5,10 +5,12 @@ package disk import ( "context" + "errors" "regexp" "strconv" "strings" + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza/errors" "github.com/shirou/gopsutil/v3/internal/common" "golang.org/x/sys/unix" ) @@ -182,3 +184,40 @@ func GetMountFSType(mp string) (string, error) { return "", nil } + +// Using lscfg and a device name, we can get the device information +// # lscfg -vl hdisk2 +// hdisk2 U8284.22A.21D72DW-V19-C22-T1-W500507680304A7D2-L2000000000000 MPIO FC 2145 + +// Manufacturer................IBM +// Machine Type and Model......2145 +// ROS Level and ID............0000 +// Device Specific.(Z0)........0000063268181002 +// Device Specific.(Z1)........00c0204 +// Serial Number...............600507630081029F5000000000000015 +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 eerial for disk") + } + return ret, nil + } + } + + sreturn ret, errors.New("serial entry not found for disk") +} From a0bbccacc61016762b20404657c26f15d8834bf7 Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Fri, 17 May 2024 15:25:05 -0400 Subject: [PATCH 14/23] Only handle virtual memory when specifically requested --- mem/mem_aix_nocgo.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 { From 8df26967090a080737ca4fcbdc2189aa8b8e540a Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Fri, 17 May 2024 20:41:16 -0400 Subject: [PATCH 15/23] Account for other possible formats output by AIX uptime command --- host/host_aix.go | 60 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/host/host_aix.go b/host/host_aix.go index 23a1a62d1..16dcf1c0e 100644 --- a/host/host_aix.go +++ b/host/host_aix.go @@ -47,6 +47,10 @@ func BootTimeWithContext(ctx context.Context) (btime uint64, err error) { return timeSince(ut), nil } +//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").Output() if err != nil { @@ -56,27 +60,53 @@ func UptimeWithContext(ctx context.Context) (uint64, error) { // Convert our uptime to a series of fields we can extract ut := strings.Fields(string(out[:])) - // Convert the second field "Days" value to integer and roll it to minutes - days, err := strconv.Atoi(ut[2]) - if err != nil { - return 0, err - } + // 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.Atoi(hm[0]) - if err != nil { - return 0, err - } - minutes, err := strconv.Atoi(strings.Replace(hm[1], ",", "", -1)) - 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 uint64(total_time), nil + return total_time, nil } // This is a weak implementation due to the limitations on retrieving this data in AIX From 0917790819c7d1299538034b93d541d6d1a19476 Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Fri, 17 May 2024 21:31:34 -0400 Subject: [PATCH 16/23] Remove inappropriate package addition --- disk/disk_aix_nocgo.go | 1 - 1 file changed, 1 deletion(-) diff --git a/disk/disk_aix_nocgo.go b/disk/disk_aix_nocgo.go index c10b6d05b..b8197b053 100644 --- a/disk/disk_aix_nocgo.go +++ b/disk/disk_aix_nocgo.go @@ -10,7 +10,6 @@ import ( "strconv" "strings" - "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza/errors" "github.com/shirou/gopsutil/v3/internal/common" "golang.org/x/sys/unix" ) From b4d95a499120359632399268a31b5e0fdbf08814 Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Fri, 17 May 2024 21:32:31 -0400 Subject: [PATCH 17/23] Raise minimum go version to 1.18 (required by changes) and run go mod tidy --- go.mod | 12 +++++++++++- go.sum | 10 ---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index aa8157a53..d56e015c8 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/shirou/gopsutil/v3 -go 1.15 +go 1.21.0 + +toolchain go1.22.3 require ( github.com/google/go-cmp v0.6.0 @@ -13,4 +15,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..41b2a1c54 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= @@ -17,13 +15,6 @@ github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFt 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 +32,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= From b133d602cecd6fd5660895945598839e49c05149 Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Fri, 17 May 2024 22:21:18 -0400 Subject: [PATCH 18/23] Ignore host_aix_ppc64 for now --- host/host_aix_ppc64.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/host/host_aix_ppc64.go b/host/host_aix_ppc64.go index 781629378..de9674b73 100644 --- a/host/host_aix_ppc64.go +++ b/host/host_aix_ppc64.go @@ -1,5 +1,5 @@ -//go:build aix && ppc64 -// +build aix,ppc64 +//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 @@ -7,7 +7,7 @@ package host const ( - sizeofPtr = 0x + sizeofPtr = 0x8 sizeofShort = 0x2 sizeofInt = 0x4 sizeofLong = 0x8 From 9bf502f825346c8930c945ddc8f892ec3bc1c3e2 Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Fri, 17 May 2024 23:55:25 -0400 Subject: [PATCH 19/23] Fix logic errors, syntax errors, and typos --- cpu/cpu_aix_nocgo.go | 2 +- disk/disk_aix_nocgo.go | 43 ++++++++++++++++++++++-------------------- disk/disk_unix.go | 4 ++-- host/host_aix.go | 43 ++++++++++++++++++++++-------------------- host/host_fallback.go | 4 ++-- 5 files changed, 51 insertions(+), 45 deletions(-) diff --git a/cpu/cpu_aix_nocgo.go b/cpu/cpu_aix_nocgo.go index 9796cf571..f3a3186aa 100644 --- a/cpu/cpu_aix_nocgo.go +++ b/cpu/cpu_aix_nocgo.go @@ -48,7 +48,7 @@ func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { if t, err := strconv.ParseFloat(v[pos], 64); err == nil { switch header { case `cpu`: - ct.CPU = t + ct.CPU = strconv.FormatFloat(t, 'f', -1, 64) case `%usr`: ct.User = t case `%sys`: diff --git a/disk/disk_aix_nocgo.go b/disk/disk_aix_nocgo.go index b8197b053..a7b6bf933 100644 --- a/disk/disk_aix_nocgo.go +++ b/disk/disk_aix_nocgo.go @@ -83,22 +83,22 @@ func getFsType(stat unix.Statfs_t) string { } func UsageWithContext(ctx context.Context, path string) (*UsageStat, error) { - var ret []UsageStat 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 + 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 - us := &UsageStat{} for i, header := range hf { // We're done in any of these use cases if i >= len(fs) { @@ -108,65 +108,68 @@ func UsageWithContext(ctx context.Context, path string) (*UsageStat, error) { switch header { case `Filesystem`: // This is not a valid fs for us to parse - if fs[i] == "/proc" || fs[i] == "/ahafs" { - continue + if fs[i] == "/proc" || fs[i] == "/ahafs" || fs[i] != path { + break } - us.Fstype, err = GetMountFSType(fs[i]) + ret.Fstype, err = GetMountFSTypeWithContext(ctx, fs[i]) if err != nil { return nil, err } case `Path`: - us.Path = fs[i] + ret.Path = fs[i] case `512-blocks`: - us.Total, err = strconv.Atoi(fs[i]) + total, err := strconv.ParseUint(fs[i], 10, 64) + ret.Total = total * blocksize if err != nil { return nil, err } case `Used`: - us.Used, err = strconv.Atoi(fs[i]) + ret.Used, err = strconv.ParseUint(fs[i], 10, 64) if err != nil { return nil, err } case `Free`: - us.Free, err = strconv.Atoi(fs[i]) + ret.Free, err = strconv.ParseUint(fs[i], 10, 64) if err != nil { return nil, err } case `%Used`: - us.UsedPercent, err = strconv.ParseFloat(fs[i]) + val, err := strconv.Atoi(strings.Replace(fs[i], "%", "", -1)) if err != nil { return nil, err } + ret.UsedPercent = float64(val) / float64(100) case `Ifree`: - us.InodesFree, err = strconv.Atoi(fs[i]) + ret.InodesFree, err = strconv.ParseUint(fs[i], 10, 64) if err != nil { return nil, err } case `Iused`: - us.InodesUsed, err = strconv.Atoi(fs[i]) + ret.InodesUsed, err = strconv.ParseUint(fs[i], 10, 64) if err != nil { return nil, err } case `%Iused`: - us.InodesUsedPercent, err = strconv.ParseFloat(fs[i]) + 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 - us.InodesTotal = us.InodesUsed + us.InodesFree + ret.InodesTotal = ret.InodesUsed + ret.InodesFree // Valid Usage data, so append it - ret = append(ret, *us) + return ret, nil } - return *ret, nil + return ret, nil } -func GetMountFSType(mp string) (string, error) { +func GetMountFSTypeWithContext(ctx context.Context, mp string) (string, error) { out, err := invoke.CommandWithContext(ctx, "mount") if err != nil { return "", err @@ -212,11 +215,11 @@ func SerialNumberWithContext(ctx context.Context, name string) (string, error) { if strings.HasPrefix(v, "Serial Number...............") { ret = strings.TrimPrefix(v, "Serial Number...............") if ret == "" { - return "", errors.New("empty eerial for disk") + return "", errors.New("empty serial for disk") } return ret, nil } } - sreturn ret, errors.New("serial entry not found for disk") + return ret, errors.New("serial entry not found for disk") } 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/host/host_aix.go b/host/host_aix.go index 16dcf1c0e..8574a6125 100644 --- a/host/host_aix.go +++ b/host/host_aix.go @@ -26,11 +26,11 @@ func HostIDWithContext(ctx context.Context) (string, error) { } // 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] + return strings.Split(string(out[:]), "\n")[0], nil } func numProcs(ctx context.Context) (uint64, error) { - return common.NumProcsWithContext(ctx) + return 0, common.ErrNotImplementedError } func BootTimeWithContext(ctx context.Context) (btime uint64, err error) { @@ -52,7 +52,7 @@ func BootTimeWithContext(ctx context.Context) (btime uint64, err error) { //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").Output() + out, err := invoke.CommandWithContext(ctx, "uptime") if err != nil { return 0, err } @@ -112,7 +112,7 @@ func UptimeWithContext(ctx context.Context) (uint64, error) { // 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").Output() + out, err := invoke.CommandWithContext(ctx, "w") if err != nil { return nil, err } @@ -134,9 +134,9 @@ func UsersWithContext(ctx context.Context) ([]UserStat, error) { if t, err := strconv.ParseFloat(v[i], 64); err == nil { switch header { case `User`: - us.User = t + us.User = strconv.FormatFloat(t, 'f', 1, 64) case `tty`: - us.Terminal = t + us.Terminal = strconv.FormatFloat(t, 'f', 1, 64) } } } @@ -151,46 +151,49 @@ func UsersWithContext(ctx context.Context) ([]UserStat, error) { // 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").Output() + out, err := invoke.CommandWithContext(ctx, "uname", "-s") if err != nil { return "", "", "", err } - platform = string(out[:]) + platform = strings.TrimRight(string(out[:]), "\n") // Set the family - out, err = invoke.CommandWithContext(ctx, "bootinfo", "-p").Output() - if err != nil { - return "", "", "", err - } - // Family seems to always be the second field from this uname, so pull that out - family = string(out[:]) + family = strings.TrimRight(string(out[:]), "\n") // Set the version - out, err = invoke.CommandWithContext(ctx, "oslevel").Output() + out, err = invoke.CommandWithContext(ctx, "oslevel") if err != nil { return "", "", "", err } - version = string(out[:]) + 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").Output() + out, err := invoke.CommandWithContext(ctx, "oslevel", "-s") if err != nil { return "", err } - version = string(out[:]) + version = strings.TrimRight(string(out[:]), "\n") return version, nil } func KernelArch() (arch string, err error) { - out, err := invoke.CommandWithContext(ctx, "bootinfo", "-y").Output() + out, err := invoke.Command("bootinfo", "-y") if err != nil { return "", err } - arch = string(out[:]) + 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_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 From 1d7b4a315e3d88ef94d578cd941071a4984e210d Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Sat, 18 May 2024 08:29:54 -0400 Subject: [PATCH 20/23] Revert accidental change of go version in go.mod (wasn't supposed to commit). Version set properly to 1.18, which is the new minimum needed for changes in this branch. Run go mod tidy again with version set back to 1.18. --- go.mod | 4 +--- go.sum | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/go.mod b/go.mod index d56e015c8..d4238a3de 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/shirou/gopsutil/v3 -go 1.21.0 - -toolchain go1.22.3 +go 1.18 require ( github.com/google/go-cmp v0.6.0 diff --git a/go.sum b/go.sum index 41b2a1c54..64e81120b 100644 --- a/go.sum +++ b/go.sum @@ -14,7 +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/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= From df9c9bf3400d8e28df3a7817f52419413270f595 Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Tue, 21 May 2024 19:18:11 -0400 Subject: [PATCH 21/23] Update min version in the readme to match new required min version. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fea8e7efd..b0d6872dc 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 From ff4ae36cc01a91583248b4dcaeedc1450b6c5071 Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Wed, 22 May 2024 08:25:39 -0400 Subject: [PATCH 22/23] Remove extraneous development note comments Move a function from nocgo to main file for disk package --- disk/disk_aix.go | 32 ++++++++++++++++++++++++++++++++ disk/disk_aix_cgo.go | 5 ----- disk/disk_aix_nocgo.go | 38 -------------------------------------- host/host_aix.go | 11 +++++++---- 4 files changed, 39 insertions(+), 47 deletions(-) diff --git a/disk/disk_aix.go b/disk/disk_aix.go index 2e1699b83..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" ) @@ -16,3 +18,33 @@ func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOC 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_cgo.go b/disk/disk_aix_cgo.go index ec8a30e09..aa534df30 100644 --- a/disk/disk_aix_cgo.go +++ b/disk/disk_aix_cgo.go @@ -8,7 +8,6 @@ import ( "fmt" "github.com/power-devops/perfstat" - "github.com/shirou/gopsutil/v3/internal/common" ) var FSType map[int]string @@ -75,7 +74,3 @@ func UsageWithContext(ctx context.Context, path string) (*UsageStat, error) { } return nil, fmt.Errorf("mountpoint %s not found", path) } - -func SerialNumberWithContext(ctx context.Context, name string) (string, error) { - return "", common.ErrNotImplementedError -} diff --git a/disk/disk_aix_nocgo.go b/disk/disk_aix_nocgo.go index a7b6bf933..fcb11617d 100644 --- a/disk/disk_aix_nocgo.go +++ b/disk/disk_aix_nocgo.go @@ -5,7 +5,6 @@ package disk import ( "context" - "errors" "regexp" "strconv" "strings" @@ -186,40 +185,3 @@ func GetMountFSTypeWithContext(ctx context.Context, mp string) (string, error) { return "", nil } - -// Using lscfg and a device name, we can get the device information -// # lscfg -vl hdisk2 -// hdisk2 U8284.22A.21D72DW-V19-C22-T1-W500507680304A7D2-L2000000000000 MPIO FC 2145 - -// Manufacturer................IBM -// Machine Type and Model......2145 -// ROS Level and ID............0000 -// Device Specific.(Z0)........0000063268181002 -// Device Specific.(Z1)........00c0204 -// Serial Number...............600507630081029F5000000000000015 -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/host/host_aix.go b/host/host_aix.go index 8574a6125..d06899ea0 100644 --- a/host/host_aix.go +++ b/host/host_aix.go @@ -47,10 +47,13 @@ func BootTimeWithContext(ctx context.Context) (btime uint64, err error) { return timeSince(ut), nil } -//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 +// 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 { From 125da53703827b62f034563a43cb7097c7bd62f9 Mon Sep 17 00:00:00 2001 From: Dylan Myers Date: Wed, 22 May 2024 08:53:41 -0400 Subject: [PATCH 23/23] Update the README charts with the AIX information --- README.md | 108 +++++++++++++++++++++++++++--------------------------- 1 file changed, 55 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index b0d6872dc..fabd59e38 100644 --- a/README.md +++ b/README.md @@ -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