Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions cpu/cpu.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ func Percent(interval time.Duration, percpu bool) ([]float64, error) {
}

func PercentWithContext(ctx context.Context, interval time.Duration, percpu bool) ([]float64, error) {
// AIX TimesWithContext returns instantaneous percentages (not cumulative
// ticks), so the delta math in calculateBusy does not apply.
if runtime.GOOS == "aix" {
return aixPercent(ctx, percpu)
}

if interval <= 0 {
return percentUsedFromLastCallWithContext(ctx, percpu)
}
Expand Down Expand Up @@ -180,6 +186,10 @@ func percentUsedFromLastCall(percpu bool) ([]float64, error) {
}

func percentUsedFromLastCallWithContext(ctx context.Context, percpu bool) ([]float64, error) {
if runtime.GOOS == "aix" {
return aixPercent(ctx, percpu)
}

cpuTimes, err := TimesWithContext(ctx, percpu)
if err != nil {
return nil, err
Expand Down
14 changes: 14 additions & 0 deletions cpu/cpu_aix.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,17 @@ func Times(percpu bool) ([]TimesStat, error) {
func Info() ([]InfoStat, error) {
return InfoWithContext(context.Background())
}

// aixPercent returns CPU busy percentages directly from TimesWithContext,
// which on AIX already contains instantaneous percentage values.
func aixPercent(ctx context.Context, percpu bool) ([]float64, error) {
times, err := TimesWithContext(ctx, percpu)
if err != nil {
return nil, err
}
ret := make([]float64, len(times))
for i, t := range times {
ret[i] = t.User + t.System + t.Iowait
}
return ret, nil
}
24 changes: 18 additions & 6 deletions cpu/cpu_aix_nocgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
var ret []TimesStat
if percpu {
perOut, err := invoke.CommandWithContext(ctx, "sar", "-u", "-P", "ALL", "10", "1")
perOut, err := invoke.CommandWithContext(ctx, "sar", "-u", "-P", "ALL", "1", "1")
if err != nil {
return nil, err
}
Expand All @@ -25,11 +25,24 @@ 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
if len(v) == 0 {
continue
}

// Determine the CPU field position: first line has a timestamp
// prefix, continuation lines do not
cpuField := strings.TrimSpace(v[0])
if l == 6 && len(v) > 1 {
cpuField = strings.TrimSpace(v[1])
}
if _, err := strconv.Atoi(cpuField); err != nil {
continue
}

ct := &TimesStat{}
for i, header := range hp {
// We're done in any of these use cases
if i >= len(v) || v[0] == "-" {
if i >= len(v) {
break
}

Expand Down Expand Up @@ -60,11 +73,10 @@ func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
}
}
}
// Valid CPU data, so append it
ret = append(ret, *ct)
}
} else {
out, err := invoke.CommandWithContext(ctx, "sar", "-u", "10", "1")
out, err := invoke.CommandWithContext(ctx, "sar", "-u", "1", "1")
if err != nil {
return nil, err
}
Expand Down
14 changes: 14 additions & 0 deletions cpu/cpu_notaix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build !aix

package cpu

import (
"context"

"github.com/shirou/gopsutil/v4/internal/common"
)

func aixPercent(_ context.Context, _ bool) ([]float64, error) {
return nil, common.ErrNotImplementedError
}
52 changes: 46 additions & 6 deletions disk/disk_aix_nocgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,46 @@ var (
}
)

func IOCountersWithContext(_ context.Context, _ ...string) (map[string]IOCountersStat, error) {
return nil, common.ErrNotImplementedError
func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOCountersStat, error) {
out, err := invoke.CommandWithContext(ctx, "iostat", "-d")
if err != nil {
return nil, err
}

ret := make(map[string]IOCountersStat)
lines := strings.Split(string(out), "\n")
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) < 6 {
continue
}
// Skip the header line
if fields[0] == "Disks:" {
continue
}

name := fields[0]
if len(names) > 0 && !common.StringsHas(names, name) {
continue
}

kbRead, err := strconv.ParseUint(fields[4], 10, 64)
if err != nil {
continue
}
kbWritten, err := strconv.ParseUint(fields[5], 10, 64)
if err != nil {
continue
}

ret[name] = IOCountersStat{
Name: name,
ReadBytes: kbRead * 1024,
WriteBytes: kbWritten * 1024,
}
}

return ret, nil
}

func PartitionsWithContext(ctx context.Context, _ bool) ([]PartitionStat, error) {
Expand Down Expand Up @@ -131,21 +169,23 @@ func UsageWithContext(ctx context.Context, path string) (*UsageStat, error) {
return nil, err
}
case `Used`:
ret.Used, err = strconv.ParseUint(fs[i], 10, 64)
used, err := strconv.ParseUint(fs[i], 10, 64)
if err != nil {
return nil, err
}
ret.Used = used * blocksize
case `Free`:
ret.Free, err = strconv.ParseUint(fs[i], 10, 64)
free, err := strconv.ParseUint(fs[i], 10, 64)
if err != nil {
return nil, err
}
ret.Free = free * blocksize
case `%Used`:
val, err := strconv.ParseInt(strings.ReplaceAll(fs[i], "%", ""), 10, 32)
if err != nil {
return nil, err
}
ret.UsedPercent = float64(val) / float64(100)
ret.UsedPercent = float64(val)
case `Ifree`:
ret.InodesFree, err = strconv.ParseUint(fs[i], 10, 64)
if err != nil {
Expand All @@ -161,7 +201,7 @@ func UsageWithContext(ctx context.Context, path string) (*UsageStat, error) {
if err != nil {
return nil, err
}
ret.InodesUsedPercent = float64(val) / float64(100)
ret.InodesUsedPercent = float64(val)
}
}

Expand Down
10 changes: 10 additions & 0 deletions load/load_aix.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,13 @@ func Avg() (*AvgStat, error) {
func Misc() (*MiscStat, error) {
return MiscWithContext(context.Background())
}

// SystemCalls returns the number of system calls since boot.
func SystemCalls() (int, error) {
return SystemCallsWithContext(context.Background())
}

// Interrupts returns the number of interrupts since boot.
func Interrupts() (int, error) {
return InterruptsWithContext(context.Background())
}
17 changes: 17 additions & 0 deletions load/load_aix_cgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"unsafe"

"github.com/power-devops/perfstat"
"github.com/shirou/gopsutil/v4/internal/common"
)

func AvgWithContext(ctx context.Context) (*AvgStat, error) {
Expand Down Expand Up @@ -81,3 +82,19 @@ func MiscWithContext(ctx context.Context) (*MiscStat, error) {

return &ret, nil
}

func SystemCallsWithContext(_ context.Context) (int, error) {
c, err := perfstat.CpuTotalStat()
if err != nil {
return 0, err
}
return int(c.Syscall), nil
}

func InterruptsWithContext(_ context.Context) (int, error) {
c, err := perfstat.CpuTotalStat()
if err != nil {
return 0, err
}
return int(c.DevIntrs), nil
}
109 changes: 100 additions & 9 deletions load/load_aix_nocgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,19 @@ import (

var separator = regexp.MustCompile(`,?\s+`)

// testInvoker is used for dependency injection in tests
var testInvoker common.Invoker

// getInvoker returns the test invoker if set, otherwise returns the default
func getInvoker() common.Invoker {
if testInvoker != nil {
return testInvoker
}
return common.Invoke{}
}

func AvgWithContext(ctx context.Context) (*AvgStat, error) {
line, err := invoke.CommandWithContext(ctx, "uptime")
line, err := getInvoker().CommandWithContext(ctx, "uptime")
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -44,24 +55,104 @@ func AvgWithContext(ctx context.Context) (*AvgStat, error) {
return nil, common.ErrNotImplementedError
}

// SystemCallsWithContext returns the cumulative number of system calls since boot
func SystemCallsWithContext(ctx context.Context) (int, error) {
_, _, syscalls, err := getVmstatMetrics(ctx)
return syscalls, err
}

// InterruptsWithContext returns the cumulative number of device interrupts since boot
func InterruptsWithContext(ctx context.Context) (int, error) {
_, interrupts, _, err := getVmstatMetrics(ctx)
return interrupts, err
}

func MiscWithContext(ctx context.Context) (*MiscStat, error) {
out, err := invoke.CommandWithContext(ctx, "ps", "-Ao", "state")
out, err := getInvoker().CommandWithContext(ctx, "ps", "-e", "-o", "state")
if err != nil {
return nil, err
}

ret := &MiscStat{}
for _, line := range strings.Split(string(out), "\n") {
ret.ProcsTotal++
lines := strings.Split(string(out), "\n")

for _, line := range lines {
line = strings.TrimSpace(line)

// Skip header line and empty lines
if line == "ST" || line == "STATE" || line == "S" || line == "" {
continue
}

// Count processes by state (AIX process states from official docs)
// A = Active (running or ready to run)
// W = Swapped (not in main memory)
// I = Idle (waiting for startup)
// Z = Canceled (zombie - terminated, waiting for parent)
// T = Stopped (trace stopped)
// O = Nonexistent
switch line {
case "R":
case "A":
case "A", "I":
// Active or Idle processes (ready to run or awaiting startup)
ret.ProcsRunning++
case "T":
case "W", "T", "Z":
// Swapped, Stopped, or Zombie processes (blocked/not runnable)
ret.ProcsBlocked++
default:
continue
}
ret.ProcsTotal++
}

// Get context switches from vmstat
ctxt, _, _, err := getVmstatMetrics(ctx)
if err == nil {
ret.Ctxt = ctxt
}

return ret, nil
}

// getVmstatMetrics runs vmstat -s and returns cumulative since-boot counters
// for context switches, device interrupts, and syscalls.
func getVmstatMetrics(ctx context.Context) (ctxt, interrupts, syscalls int, err error) {
out, err := getInvoker().CommandWithContext(ctx, "vmstat", "-s")
if err != nil {
return 0, 0, 0, err
}

// vmstat -s output format: <whitespace><number> <description>
// Example lines:
// 5842393706 cpu context switches
// 33412179 device interrupts
// 12918944607 syscalls
lines := strings.Split(string(out), "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" {
continue
}

// Split into number and description at the first space after the number
idx := strings.IndexByte(line, ' ')
if idx < 0 {
continue
}
valStr := line[:idx]
desc := strings.TrimSpace(line[idx+1:])

v, parseErr := strconv.Atoi(valStr)
if parseErr != nil {
continue
}

switch desc {
case "cpu context switches":
ctxt = v
case "device interrupts":
interrupts = v
case "syscalls":
syscalls = v
}
}

return ctxt, interrupts, syscalls, nil
}
Loading
Loading