Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

system/process fixes on AIX #61

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]

### Added
- Add process existence check for AIX in system/process #61

### Changed

Expand All @@ -17,6 +18,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).

- Fix thread safety in process code #43
- Fix process package build on AIX #54
- Fix out-of-range panic on AIX in system/process while getting exe from procargs #61
- Implement GetPIDState for AIX in system/process #61

## [0.4.4]

Expand Down
20 changes: 20 additions & 0 deletions metric/system/process/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ import (
"context"
"fmt"
"os"
"runtime"
"sort"
"strings"
"syscall"
"time"

psutil "github.com/shirou/gopsutil/process"
Expand Down Expand Up @@ -62,6 +64,23 @@ func ListStates(hostfs resolve.Resolver) ([]ProcState, error) {
// GetPIDState returns the state of a given PID
// It will return ProcNotExist if the process was not found.
func GetPIDState(hostfs resolve.Resolver, pid int) (PidState, error) {
// As psutils doesn't support AIX, we use GetInfoForPid directly.
// It returns an ESRCH if no process is found.
if runtime.GOOS == "aix" {
state, err := GetInfoForPid(hostfs, pid)
if err == syscall.ESRCH {
// We assume that syscall.ESRCH is mapped for all GOOS
// this package is compiled for. If not, we will have to give this
// to a build-flag controlled function to check.
return "", ProcNotExist
}
if err != nil {
return "", fmt.Errorf("error getting PID info for %d: %w", pid, err)
}

return state.State, nil
}

// This library still doesn't have a good cross-platform way to distinguish between "does not eixst" and other process errors.
// This is a fairly difficult problem to solve in a cross-platform way
exists, err := psutil.PidExistsWithContext(context.Background(), int32(pid))
Expand All @@ -71,6 +90,7 @@ func GetPIDState(hostfs resolve.Resolver, pid int) (PidState, error) {
if !exists {
return "", ProcNotExist
}

//GetInfoForPid will return the smallest possible dataset for a PID
procState, err := GetInfoForPid(hostfs, pid)
if err != nil {
Expand Down
50 changes: 47 additions & 3 deletions metric/system/process/process_aix.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ func (procStats *Stats) FetchPids() (ProcsMap, []ProcState, error) {
procMap := make(ProcsMap, 0)
var plist []ProcState
for {
// getprocs first argument is a void*
num, err := C.getprocs(unsafe.Pointer(&info), C.sizeof_struct_procsinfo64, nil, 0, &pid, 1)
if err != nil {
return nil, nil, fmt.Errorf("error fetching PIDs: %w", err)
Expand All @@ -62,11 +61,39 @@ func (procStats *Stats) FetchPids() (ProcsMap, []ProcState, error) {
return procMap, plist, nil
}

// procExists returns true if a process is present in the AIX process table
func procExists(pid int) (bool, error) {
info := [20]C.struct_procsinfo64{}
idxPtr := C.pid_t(0)

for {
num, err := C.getprocs(unsafe.Pointer(&info), C.sizeof_struct_procsinfo64, nil, 0, &idxPtr, 20)
if err != nil {
return false, fmt.Errorf("error fetching PID at IndexPointer %d: %w", int(idxPtr), err)
}

if num == 0 {
break
}

for i := 0; i < len(info); i++ {
if int(info[i].pi_pid) == pid {
return true, nil
}
}

}

return false, nil
}

// GetInfoForPid returns basic info for the process
func GetInfoForPid(_ resolve.Resolver, pid int) (ProcState, error) {
info := C.struct_procsinfo64{}
cpid := C.pid_t(pid)

// getprocs uses an IndexPointer, which doesn't need to correlate in every case to the PID
// but here we fetch a count of 1 procs and therefore it should match it.
num, err := C.getprocs(unsafe.Pointer(&info), C.sizeof_struct_procsinfo64, nil, 0, &cpid, 1)
if err != nil {
return ProcState{}, fmt.Errorf("error in getprocs: %w", err)
Expand All @@ -75,6 +102,16 @@ func GetInfoForPid(_ resolve.Resolver, pid int) (ProcState, error) {
return ProcState{}, syscall.ESRCH
}

// Since getprocs can return a proccess with pi_state=Running which is already
// dead, we need to check if it really exist.
ok, err := procExists(pid)
if err != nil {
return ProcState{}, fmt.Errorf("error in procExists: %w", err)
}
if !ok {
return ProcState{}, syscall.ESRCH
}

state := ProcState{}
state.Pid = opt.IntWith(pid)

Expand Down Expand Up @@ -109,12 +146,16 @@ func GetInfoForPid(_ resolve.Resolver, pid int) (ProcState, error) {
return state, nil
}

// FillPidMetrics is the aix implementation
// FillPidMetrics is the aix implementation. If the process died in the meantime and is still present in the
// process table, this call still succeeds.
func FillPidMetrics(_ resolve.Resolver, pid int, state ProcState, filter func(string) bool) (ProcState, error) {
pagesize := uint64(os.Getpagesize())
info := C.struct_procsinfo64{}
cpid := C.pid_t(pid)

// getprocs uses an IndexPointer, which doesn't need to correlate in every case to the PID
// but here we fetch a count of 1 procs and therefore it should match it.
// A dead process could still be looked up when directly using the IndexPointer as pid.
num, err := C.getprocs(unsafe.Pointer(&info), C.sizeof_struct_procsinfo64, nil, 0, &cpid, 1)
if err != nil {
return state, fmt.Errorf("error in getprocs: %w", err)
Expand Down Expand Up @@ -155,8 +196,11 @@ func FillPidMetrics(_ resolve.Resolver, pid int, state ProcState, filter func(st

args = append(args, stripNullByte(arg))
}

state.Args = args
state.Exe = args[0]
if len(args) > 0 {
state.Exe = args[0]
}

// get env vars
buf = make([]byte, 8192)
Expand Down
4 changes: 2 additions & 2 deletions metric/system/process/process_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
// specific language governing permissions and limitations
// under the License.

//go:build darwin || freebsd || linux || windows
// +build darwin freebsd linux windows
//go:build darwin || freebsd || linux || windows || aix
// +build darwin freebsd linux windows aix

package process

Expand Down