diff --git a/process/process_aix.go b/process/process_aix.go new file mode 100644 index 0000000000..3a89c98521 --- /dev/null +++ b/process/process_aix.go @@ -0,0 +1,330 @@ +// SPDX-License-Identifier: BSD-3-Clause +//go:build aix + +package process + +import ( + "bytes" + "context" + "encoding/binary" + "os" + "strconv" + "strings" + + "github.com/shirou/gopsutil/v4/cpu" + "github.com/shirou/gopsutil/v4/internal/common" + "github.com/shirou/gopsutil/v4/net" +) + +// MemoryMapsStat is not available on AIX. +type MemoryMapsStat struct{} + +// MemoryInfoExStat is not available on AIX. +type MemoryInfoExStat struct{} + +func readPsinfo(ctx context.Context, pid int32) (*psinfo, error) { + f, err := os.Open(common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "psinfo")) + if err != nil { + return nil, err + } + defer f.Close() + + var psi psinfo + if err := binary.Read(f, binary.BigEndian, &psi); err != nil { + return nil, err + } + return &psi, nil +} + +func nullTerminatedString(b []byte) string { + if idx := bytes.IndexByte(b, 0); idx >= 0 { + return string(b[:idx]) + } + return string(b) +} + +func pidsWithContext(ctx context.Context) ([]int32, error) { + dir, err := os.Open(common.HostProcWithContext(ctx)) + if err != nil { + return nil, err + } + defer dir.Close() + + names, err := dir.Readdirnames(-1) + if err != nil { + return nil, err + } + + pids := make([]int32, 0, len(names)) + for _, name := range names { + pid, err := strconv.ParseInt(name, 10, 32) + if err != nil { + continue // skip non-numeric entries (e.g. "net", "sys") + } + pids = append(pids, int32(pid)) + } + return pids, nil +} + +func ProcessesWithContext(ctx context.Context) ([]*Process, error) { + pids, err := pidsWithContext(ctx) + if err != nil { + return nil, err + } + ret := make([]*Process, 0, len(pids)) + for _, pid := range pids { + // create Process struct directly to avoid the redundant PidExists check + ret = append(ret, &Process{Pid: pid}) + } + return ret, nil +} + +func (p *Process) PpidWithContext(ctx context.Context) (int32, error) { + psi, err := readPsinfo(ctx, p.Pid) + if err != nil { + return 0, err + } + // psinfo stores PIDs as uint64; safe to truncate to int32 on AIX where PIDs fit in 32 bits. + return int32(psi.Ppid), nil +} + +func (p *Process) NameWithContext(ctx context.Context) (string, error) { + psi, err := readPsinfo(ctx, p.Pid) + if err != nil { + return "", err + } + if name := nullTerminatedString(psi.Fname[:]); name != "" { + return name, nil + } + // PID 0 is the swapper/idle process; its Fname is empty but ps shows "swapper". + if p.Pid == 0 { + return "swapper", nil + } + return "", nil +} + +func (*Process) TgidWithContext(_ context.Context) (int32, error) { + return 0, common.ErrNotImplementedError +} + +func (*Process) ExeWithContext(_ context.Context) (string, error) { + return "", common.ErrNotImplementedError +} + +func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) { + psi, err := readPsinfo(ctx, p.Pid) + if err != nil { + return "", err + } + // Psargs is empty for kernel threads, fall back to Fname (the executable name), + // which is what ps uses as well. + if args := nullTerminatedString(psi.Psargs[:]); args != "" { + return args, nil + } + if name := nullTerminatedString(psi.Fname[:]); name != "" { + return name, nil + } + // PID 0 is the swapper/idle process, its Fname and Psargs are both empty + // but ps shows "swapper". + if p.Pid == 0 { + return "swapper", nil + } + return "", nil +} + +func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) { + psi, err := readPsinfo(ctx, p.Pid) + if err != nil { + return nil, err + } + // Psargs is empty for kernel threads, fall back to Fname (the executable name), + // which is what ps uses as well. + args := nullTerminatedString(psi.Psargs[:]) + if args == "" { + args = nullTerminatedString(psi.Fname[:]) + } + // PID 0 is the swapper/idle process, its Fname and Psargs are both empty + // but ps shows "swapper". + if args == "" && p.Pid == 0 { + args = "swapper" + } + if args == "" { + return nil, nil + } + return strings.Fields(args), nil +} + +func (p *Process) createTimeWithContext(ctx context.Context) (int64, error) { + psi, err := readPsinfo(ctx, p.Pid) + if err != nil { + return 0, err + } + return psi.Start.Sec*1000 + int64(psi.Start.Nsec)/1000000, nil +} + +func (*Process) CwdWithContext(_ context.Context) (string, error) { + return "", common.ErrNotImplementedError +} + +func (p *Process) StatusWithContext(ctx context.Context) ([]string, error) { + psi, err := readPsinfo(ctx, p.Pid) + if err != nil { + return nil, err + } + return []string{convertStatusChar(string([]byte{psi.Lwp.Sname}))}, nil +} + +func (*Process) ForegroundWithContext(_ context.Context) (bool, error) { + return false, common.ErrNotImplementedError +} + +func (p *Process) UidsWithContext(ctx context.Context) ([]uint32, error) { + psi, err := readPsinfo(ctx, p.Pid) + if err != nil { + return nil, err + } + // psinfo stores UIDs as uint64; safe to truncate to uint32 on AIX where UIDs fit in 32 bits. + // real, effective, saved (psinfo doesn't expose saved, use real as fallback) + return []uint32{uint32(psi.Uid), uint32(psi.Euid), uint32(psi.Uid)}, nil +} + +func (p *Process) GidsWithContext(ctx context.Context) ([]uint32, error) { + psi, err := readPsinfo(ctx, p.Pid) + if err != nil { + return nil, err + } + // psinfo stores GIDs as uint64; safe to truncate to uint32 on AIX where GIDs fit in 32 bits. + // real, effective, saved (psinfo doesn't expose saved, use real as fallback) + return []uint32{uint32(psi.Gid), uint32(psi.Egid), uint32(psi.Gid)}, nil +} + +func (*Process) GroupsWithContext(_ context.Context) ([]uint32, error) { + return nil, common.ErrNotImplementedError +} + +func (*Process) TerminalWithContext(_ context.Context) (string, error) { + return "", common.ErrNotImplementedError +} + +func (p *Process) NiceWithContext(ctx context.Context) (int32, error) { + psi, err := readPsinfo(ctx, p.Pid) + if err != nil { + return 0, err + } + // Returns the raw pr_nice value from lwpsinfo_t, this is not the same as what ps displays + return int32(psi.Lwp.Nice), nil +} + +func (*Process) IOniceWithContext(_ context.Context) (int32, error) { + return 0, common.ErrNotImplementedError +} + +func (p *Process) RlimitWithContext(ctx context.Context) ([]RlimitStat, error) { + return p.RlimitUsageWithContext(ctx, false) +} + +func (*Process) RlimitUsageWithContext(_ context.Context, _ bool) ([]RlimitStat, error) { + return nil, common.ErrNotImplementedError +} + +func (*Process) NumCtxSwitchesWithContext(_ context.Context) (*NumCtxSwitchesStat, error) { + return nil, common.ErrNotImplementedError +} + +func (*Process) NumFDsWithContext(_ context.Context) (int32, error) { + return 0, common.ErrNotImplementedError +} + +func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) { + psi, err := readPsinfo(ctx, p.Pid) + if err != nil { + return 0, err + } + return int32(psi.Nlwp), nil +} + +func (*Process) ThreadsWithContext(_ context.Context) (map[int32]*cpu.TimesStat, error) { + return nil, common.ErrNotImplementedError +} + +func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) { + psi, err := readPsinfo(ctx, p.Pid) + if err != nil { + return nil, err + } + // psinfo only provides combined user+system time + combined := float64(psi.Time.Sec) + float64(psi.Time.Nsec)/1e9 + return &cpu.TimesStat{ + CPU: "cpu", + User: combined, + System: 0, + }, nil +} + +func (*Process) CPUAffinityWithContext(_ context.Context) ([]int32, error) { + return nil, common.ErrNotImplementedError +} + +func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, error) { + psi, err := readPsinfo(ctx, p.Pid) + if err != nil { + return nil, err + } + // pr_size and pr_rssize are in KB per documentation + return &MemoryInfoStat{ + RSS: psi.Rssize * 1024, + VMS: psi.Size * 1024, + }, nil +} + +func (*Process) MemoryInfoExWithContext(_ context.Context) (*MemoryInfoExStat, error) { + return nil, common.ErrNotImplementedError +} + +func (*Process) PageFaultsWithContext(_ context.Context) (*PageFaultsStat, error) { + return nil, common.ErrNotImplementedError +} + +func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) { + pids, err := pidsWithContext(ctx) + if err != nil { + return nil, err + } + ret := make([]*Process, 0) + for _, pid := range pids { + psi, err := readPsinfo(ctx, pid) + if err != nil { + continue + } + // psinfo stores PIDs as uint64; safe to truncate to int32 on AIX where PIDs fit in 32 bits. + if int32(psi.Ppid) == p.Pid { + // create Process struct directly to avoid the redundant PidExists check + ret = append(ret, &Process{Pid: pid}) + } + } + return ret, nil +} + +func (*Process) OpenFilesWithContext(_ context.Context) ([]OpenFilesStat, error) { + return nil, common.ErrNotImplementedError +} + +func (*Process) ConnectionsWithContext(_ context.Context) ([]net.ConnectionStat, error) { + return nil, common.ErrNotImplementedError +} + +func (*Process) ConnectionsMaxWithContext(_ context.Context, _ int) ([]net.ConnectionStat, error) { + return nil, common.ErrNotImplementedError +} + +func (*Process) MemoryMapsWithContext(_ context.Context, _ bool) (*[]MemoryMapsStat, error) { + return nil, common.ErrNotImplementedError +} + +func (*Process) IOCountersWithContext(_ context.Context) (*IOCountersStat, error) { + return nil, common.ErrNotImplementedError +} + +func (*Process) EnvironWithContext(_ context.Context) ([]string, error) { + return nil, common.ErrNotImplementedError +} diff --git a/process/process_aix_ppc64.go b/process/process_aix_ppc64.go new file mode 100644 index 0000000000..1fc0d9978e --- /dev/null +++ b/process/process_aix_ppc64.go @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs types_aix.go | sed 's/\*byte/uint64/' + +package process + +type prTimestruc64 struct { + Sec int64 + Nsec int32 + X__pad uint32 +} +type lwpSinfo struct { + Lwpid uint64 + Addr uint64 + Wchan uint64 + Flag uint32 + Wtype uint8 + State uint8 + Sname uint8 + Nice uint8 + Pri int32 + Policy uint32 + Clname [8]uint8 + Onpro int32 + Bindpro int32 + Ptid uint32 + X_pad1 uint32 + X_pad [7]uint64 +} +type psinfo struct { + Flag uint32 + Flag2 uint32 + Nlwp uint32 + X_pad1 uint32 + Uid uint64 + Euid uint64 + Gid uint64 + Egid uint64 + Pid uint64 + Ppid uint64 + Pgid uint64 + Sid uint64 + Ttydev uint64 + Addr uint64 + Size uint64 + Rssize uint64 + Start prTimestruc64 + Time prTimestruc64 + Cid uint16 + X_pad2 uint16 + Argc uint32 + Argv uint64 + Envp uint64 + Fname [16]uint8 + Psargs [80]uint8 + X_pad [8]uint64 + Lwp lwpSinfo +} diff --git a/process/process_aix_test.go b/process/process_aix_test.go new file mode 100644 index 0000000000..aec9a630c3 --- /dev/null +++ b/process/process_aix_test.go @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: BSD-3-Clause +//go:build aix + +package process + +import ( + "bytes" + "context" + "encoding/binary" + "os" + "path/filepath" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// fillPsinfo writes the Fname and Psargs as bytes in the psinfo struct and returns it +func fillPsinfo(psi psinfo, fname, psargs string) psinfo { + copy(psi.Fname[:], fname) + copy(psi.Psargs[:], psargs) + return psi +} + +// writeFakePsinfo serializes psi as big-endian and writes it to +// //psinfo, creating the directory as needed. +func writeFakePsinfo(t *testing.T, dir string, psi psinfo) { + t.Helper() + pidDir := filepath.Join(dir, strconv.FormatUint(psi.Pid, 10)) + require.NoError(t, os.MkdirAll(pidDir, 0o755)) + + var buf bytes.Buffer + require.NoError(t, binary.Write(&buf, binary.BigEndian, &psi)) + require.NoError(t, os.WriteFile(filepath.Join(pidDir, "psinfo"), buf.Bytes(), 0o600)) +} + +// setupFakeProc creates a temp directory with fake psinfo files, +// sets HOST_PROC to point at it, and returns a context. +func setupFakeProc(t *testing.T, procs ...psinfo) context.Context { + t.Helper() + dir := t.TempDir() + for i := range procs { + writeFakePsinfo(t, dir, procs[i]) + } + t.Setenv("HOST_PROC", dir) + return context.Background() +} + +// mockProc is the main test process fixture +var mockProc = fillPsinfo(psinfo{ + Pid: 1234, + Ppid: 100, + Uid: 501, + Euid: 502, + Gid: 20, + Egid: 21, + Nlwp: 3, + Size: 256, + Rssize: 64, + Start: prTimestruc64{Sec: 1700000000, Nsec: 123000000}, + Time: prTimestruc64{Sec: 5, Nsec: 250000000}, + Lwp: lwpSinfo{Nice: 65, Sname: 'R'}, +}, "myproc", "/usr/bin/myproc -v --flag arg") + +// childProc is a child of mockProc (Ppid == mockProc.Pid) +var childProc = fillPsinfo(psinfo{ + Pid: 5678, + Ppid: 1234, + Uid: 501, + Euid: 501, + Gid: 20, + Egid: 20, + Nlwp: 1, + Size: 128, + Rssize: 32, + Lwp: lwpSinfo{Sname: 'S'}, +}, "child", "/usr/bin/child") + +func TestPsinfoPpid(t *testing.T) { + ctx := setupFakeProc(t, mockProc) + p := &Process{Pid: int32(mockProc.Pid)} + ppid, err := p.PpidWithContext(ctx) + require.NoError(t, err) + assert.Equal(t, int32(mockProc.Ppid), ppid) +} + +func TestPsinfoName(t *testing.T) { + ctx := setupFakeProc(t, mockProc) + p := &Process{Pid: int32(mockProc.Pid)} + name, err := p.NameWithContext(ctx) + require.NoError(t, err) + assert.Equal(t, "myproc", name) +} + +func TestPsinfoCmdline(t *testing.T) { + ctx := setupFakeProc(t, mockProc) + p := &Process{Pid: int32(mockProc.Pid)} + cmd, err := p.CmdlineWithContext(ctx) + require.NoError(t, err) + assert.Equal(t, "/usr/bin/myproc -v --flag arg", cmd) +} + +func TestPsinfoCmdlineSlice(t *testing.T) { + ctx := setupFakeProc(t, mockProc) + p := &Process{Pid: int32(mockProc.Pid)} + args, err := p.CmdlineSliceWithContext(ctx) + require.NoError(t, err) + assert.Equal(t, []string{"/usr/bin/myproc", "-v", "--flag", "arg"}, args) +} + +func TestPsinfoCmdlineSliceEmpty(t *testing.T) { + // Both Psargs and Fname empty → nil + ctx := setupFakeProc(t, psinfo{Pid: 9999, Ppid: 1, Lwp: lwpSinfo{Sname: 'S'}}) + p := &Process{Pid: 9999} + args, err := p.CmdlineSliceWithContext(ctx) + require.NoError(t, err) + assert.Nil(t, args) +} + +func TestPsinfoCmdlineFallsBackToFname(t *testing.T) { + // Psargs empty (kernel thread) → falls back to Fname, same as ps + ctx := setupFakeProc(t, fillPsinfo(psinfo{Pid: 9999, Ppid: 1, Lwp: lwpSinfo{Sname: 'S'}}, "kthread", "")) + p := &Process{Pid: 9999} + cmd, err := p.CmdlineWithContext(ctx) + require.NoError(t, err) + assert.Equal(t, "kthread", cmd) + + args, err := p.CmdlineSliceWithContext(ctx) + require.NoError(t, err) + assert.Equal(t, []string{"kthread"}, args) +} + +func TestPsinfoCreateTime(t *testing.T) { + ctx := setupFakeProc(t, mockProc) + p := &Process{Pid: int32(mockProc.Pid)} + ct, err := p.CreateTimeWithContext(ctx) + require.NoError(t, err) + // 1700000000 * 1000 + 123000000 / 1000000 = 1700000000000 + 123 = 1700000000123 + assert.Equal(t, int64(1700000000123), ct) +} + +func TestPsinfoStatus(t *testing.T) { + tests := []struct { + sname byte + expected string + }{ + {'R', Running}, + {'S', Sleep}, + {'Z', Zombie}, + {'T', Stop}, + {'I', Idle}, + {'X', UnknownState}, // unknown → UnknownState + } + for _, tt := range tests { + ctx := setupFakeProc(t, psinfo{Pid: 42, Ppid: 1, Lwp: lwpSinfo{Sname: tt.sname}}) + p := &Process{Pid: 42} + status, err := p.StatusWithContext(ctx) + require.NoError(t, err) + assert.Equal(t, []string{tt.expected}, status, "sname=%c", tt.sname) + } +} + +func TestPsinfoUids(t *testing.T) { + ctx := setupFakeProc(t, mockProc) + p := &Process{Pid: int32(mockProc.Pid)} + uids, err := p.UidsWithContext(ctx) + require.NoError(t, err) + // real=501, effective=502, saved=501 (fallback to real) + assert.Equal(t, []uint32{501, 502, 501}, uids) +} + +func TestPsinfoGids(t *testing.T) { + ctx := setupFakeProc(t, mockProc) + p := &Process{Pid: int32(mockProc.Pid)} + gids, err := p.GidsWithContext(ctx) + require.NoError(t, err) + // real=20, effective=21, saved=20 (fallback to real) + assert.Equal(t, []uint32{20, 21, 20}, gids) +} + +func TestPsinfoNice(t *testing.T) { + ctx := setupFakeProc(t, mockProc) + p := &Process{Pid: int32(mockProc.Pid)} + nice, err := p.NiceWithContext(ctx) + require.NoError(t, err) + assert.Equal(t, int32(65), nice) // raw pr_nice value +} + +func TestPsinfoNumThreads(t *testing.T) { + ctx := setupFakeProc(t, mockProc) + p := &Process{Pid: int32(mockProc.Pid)} + n, err := p.NumThreadsWithContext(ctx) + require.NoError(t, err) + assert.Equal(t, int32(3), n) +} + +func TestPsinfoTimes(t *testing.T) { + ctx := setupFakeProc(t, mockProc) + p := &Process{Pid: int32(mockProc.Pid)} + times, err := p.TimesWithContext(ctx) + require.NoError(t, err) + assert.Equal(t, "cpu", times.CPU) + assert.InDelta(t, 5.25, times.User, 1e-6) + assert.InDelta(t, float64(0), times.System, 1e-6) +} + +func TestPsinfoMemoryInfo(t *testing.T) { + ctx := setupFakeProc(t, mockProc) + p := &Process{Pid: int32(mockProc.Pid)} + mem, err := p.MemoryInfoWithContext(ctx) + require.NoError(t, err) + // pr_rssize=64 KB, pr_size=256 KB → bytes + assert.Equal(t, uint64(64)*1024, mem.RSS) + assert.Equal(t, uint64(256)*1024, mem.VMS) +} + +func TestPsinfoPids(t *testing.T) { + ctx := setupFakeProc(t, mockProc, childProc) + pids, err := pidsWithContext(ctx) + require.NoError(t, err) + assert.ElementsMatch(t, []int32{1234, 5678}, pids) +} + +func TestPsinfoPidsSkipsNonNumeric(t *testing.T) { + dir := t.TempDir() + writeFakePsinfo(t, dir, mockProc) + require.NoError(t, os.MkdirAll(filepath.Join(dir, "net"), 0o755)) + require.NoError(t, os.MkdirAll(filepath.Join(dir, "sys"), 0o755)) + t.Setenv("HOST_PROC", dir) + pids, err := pidsWithContext(context.Background()) + require.NoError(t, err) + assert.Equal(t, []int32{1234}, pids) +} + +func TestPsinfoProcessesWithContext(t *testing.T) { + ctx := setupFakeProc(t, mockProc, childProc) + procs, err := ProcessesWithContext(ctx) + require.NoError(t, err) + require.Len(t, procs, 2) + + pids := make(map[int32]struct{}) + for _, p := range procs { + pids[p.Pid] = struct{}{} + } + assert.Contains(t, pids, int32(mockProc.Pid)) + assert.Contains(t, pids, int32(childProc.Pid)) +} + +func TestPsinfoChildren(t *testing.T) { + ctx := setupFakeProc(t, mockProc, childProc) + parent := &Process{Pid: int32(mockProc.Pid)} + children, err := parent.ChildrenWithContext(ctx) + require.NoError(t, err) + require.Len(t, children, 1) + assert.Equal(t, int32(childProc.Pid), children[0].Pid) +} + +func TestPsinfoChildrenNone(t *testing.T) { + ctx := setupFakeProc(t, mockProc) + parent := &Process{Pid: int32(mockProc.Pid)} + children, err := parent.ChildrenWithContext(ctx) + require.NoError(t, err) + assert.Empty(t, children) +} + +func TestPsinfoMissingFile(t *testing.T) { + ctx := setupFakeProc(t) // empty proc dir + p := &Process{Pid: 9999} + _, err := p.NameWithContext(ctx) + assert.Error(t, err) +} + +func TestPsinfoSwapper(t *testing.T) { + // PID 0 (swapper) has empty Fname and Psargs; should return "swapper". + ctx := setupFakeProc(t, psinfo{Pid: 0, Lwp: lwpSinfo{Sname: 'R'}}) + p := &Process{Pid: 0} + + name, err := p.NameWithContext(ctx) + require.NoError(t, err) + assert.Equal(t, "swapper", name) + + cmd, err := p.CmdlineWithContext(ctx) + require.NoError(t, err) + assert.Equal(t, "swapper", cmd) + + args, err := p.CmdlineSliceWithContext(ctx) + require.NoError(t, err) + assert.Equal(t, []string{"swapper"}, args) +} diff --git a/process/process_fallback.go b/process/process_fallback.go index 699311a9ca..d923bd7523 100644 --- a/process/process_fallback.go +++ b/process/process_fallback.go @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -//go:build !darwin && !linux && !freebsd && !openbsd && !windows && !solaris && !plan9 +//go:build !darwin && !linux && !freebsd && !openbsd && !windows && !solaris && !plan9 && !aix package process diff --git a/process/process_posix.go b/process/process_posix.go index 9f0e93f31a..6956d5f26e 100644 --- a/process/process_posix.go +++ b/process/process_posix.go @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -//go:build linux || freebsd || openbsd || darwin || solaris +//go:build linux || freebsd || openbsd || darwin || solaris || aix package process diff --git a/process/process_test.go b/process/process_test.go index b3363d3307..5c309baca7 100644 --- a/process/process_test.go +++ b/process/process_test.go @@ -126,6 +126,9 @@ func TestCmdLine(t *testing.T) { } func TestCmdLineSlice(t *testing.T) { + if runtime.GOOS == "aix" { + t.Skip("AIX: psinfo.Psargs is limited to 79 characters, so long command lines are truncated and cannot match os.Args exactly") + } p := testGetProcess() v, err := p.CmdlineSlice() @@ -196,6 +199,9 @@ func TestNumCtx(t *testing.T) { } func TestNice(t *testing.T) { + if runtime.GOOS == "aix" { + t.Skip("AIX: psinfo returns a raw kernel nice value (pr_nice) that does not map to the POSIX [-20, 19] range") + } p := testGetProcess() // https://github.com/shirou/gopsutil/issues/1532 @@ -268,6 +274,9 @@ func TestName(t *testing.T) { // #nosec G204 func TestLong_Name_With_Spaces(t *testing.T) { + if runtime.GOOS == "aix" { + t.Skip("AIX: psinfo.Fname is limited to 15 characters, so process names longer than 15 chars are truncated") + } tmpdir, err := os.MkdirTemp("", "") require.NoErrorf(t, err, "unable to create temp dir %v", err) defer os.RemoveAll(tmpdir) // clean up @@ -307,6 +316,9 @@ func TestLong_Name_With_Spaces(t *testing.T) { // #nosec G204 func TestLong_Name(t *testing.T) { + if runtime.GOOS == "aix" { + t.Skip("AIX: psinfo.Fname is limited to 15 characters, so process names longer than 15 chars are truncated") + } tmpdir, err := os.MkdirTemp("", "") require.NoErrorf(t, err, "unable to create temp dir %v", err) defer os.RemoveAll(tmpdir) // clean up @@ -579,6 +591,9 @@ func TestUsername(t *testing.T) { } func TestCPUTimes(t *testing.T) { + if runtime.GOOS == "aix" { + t.Skip("AIX: psinfo.pr_time is only updated on context switch, not continuously; short-duration CPU measurements are unreliable") + } pid := os.Getpid() process, err := NewProcess(int32(pid)) if errors.Is(err, common.ErrNotImplementedError) { diff --git a/process/types_aix.go b/process/types_aix.go new file mode 100644 index 0000000000..72e1838358 --- /dev/null +++ b/process/types_aix.go @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: BSD-3-Clause +//go:build ignore + +// Input to cgo -godefs. See mktypes.sh in the root directory. +// go tool cgo -godefs types_aix.go | sed 's/\*byte/uint64/' > process_aix_ppc64.go + +package process + +/* +#include +*/ +import "C" + +type prTimestruc64 C.struct_pr_timestruc64 +type lwpSinfo C.struct_lwpsinfo +type psinfo C.struct_psinfo