providers/aix/process_aix_ppc64.go (195 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. //go:build aix && ppc64 && cgo package aix /* #cgo LDFLAGS: -L/usr/lib -lperfstat #include <libperfstat.h> #include <procinfo.h> #include <sys/proc.h> */ import "C" import ( "bytes" "encoding/binary" "errors" "fmt" "io" "io/ioutil" "os" "path/filepath" "strconv" "strings" "syscall" "time" "unsafe" "github.com/elastic/go-sysinfo/types" ) // Processes returns a list of all actives processes. func (aixSystem) Processes() ([]types.Process, error) { // Retrieve processes using /proc instead of calling // getprocs which will also retrieve kernel threads. files, err := os.ReadDir("/proc") if err != nil { return nil, fmt.Errorf("error while reading /proc: %w", err) } processes := make([]types.Process, 0, len(files)) for _, f := range files { // Check that the file is a correct process directory. // /proc also contains special files (/proc/version) and threads // directories (/proc/pid directory but without any "as" file) if _, err := os.Stat("/proc/" + f.Name() + "/as"); err == nil { pid, _ := strconv.Atoi(f.Name()) processes = append(processes, &process{pid: pid}) } } return processes, nil } // Process returns the process designed by PID. func (aixSystem) Process(pid int) (types.Process, error) { p := process{pid: pid} return &p, nil } // Self returns the current process. func (s aixSystem) Self() (types.Process, error) { return s.Process(os.Getpid()) } type process struct { pid int info *types.ProcessInfo env map[string]string } // PID returns the PID of a process. func (p *process) PID() int { return p.pid } // Parent returns the parent of a process. func (p *process) Parent() (types.Process, error) { info, err := p.Info() if err != nil { return nil, err } return &process{pid: info.PPID}, nil } // Info returns all information about the process. func (p *process) Info() (types.ProcessInfo, error) { if p.info != nil { return *p.info, nil } p.info = &types.ProcessInfo{ PID: p.pid, } // Retrieve PPID and StartTime info := C.struct_procsinfo64{} cpid := C.pid_t(p.pid) num, err := C.getprocs(unsafe.Pointer(&info), C.sizeof_struct_procsinfo64, nil, 0, &cpid, 1) if num != 1 { err = syscall.ESRCH } if err != nil { return types.ProcessInfo{}, fmt.Errorf("error while calling getprocs: %w", err) } p.info.PPID = int(info.pi_ppid) // pi_start is the time in second since the process have started. p.info.StartTime = time.Unix(0, int64(uint64(info.pi_start)*1000*uint64(time.Millisecond))) // Retrieve arguments and executable name // If buffer is not large enough, args are truncated buf := make([]byte, 8192) var args []string if _, err := C.getargs(unsafe.Pointer(&info), C.sizeof_struct_procsinfo64, (*C.char)(&buf[0]), 8192); err != nil { return types.ProcessInfo{}, fmt.Errorf("error while calling getargs: %w", err) } bbuf := bytes.NewBuffer(buf) for { arg, err := bbuf.ReadBytes(0) if err == io.EOF || arg[0] == 0 { break } if err != nil { return types.ProcessInfo{}, fmt.Errorf("error while reading arguments: %w", err) } args = append(args, string(chop(arg))) } // For some special programs, getargs might return an empty buffer. if len(args) == 0 { args = append(args, "") } // The first element of the arguments list is the executable path. // There are some exceptions which don't have an executable path // but rather a special name directly in args[0]. if strings.Contains(args[0], "sshd: ") { // ssh connections can be named "sshd: root@pts/11". // If we are using filepath.Base, the result will only // be 11 because of the last "/". p.info.Name = args[0] } else { p.info.Name = filepath.Base(args[0]) } // The process was launched using its absolute path, so we can retrieve // the executable path from its "name". if filepath.IsAbs(args[0]) { p.info.Exe = filepath.Clean(args[0]) } else { // TODO: improve this case. The executable full path can still // be retrieve in some cases. Look at os/executable_path.go // in the stdlib. // For the moment, let's "exe" be the same as "name" p.info.Exe = p.info.Name } p.info.Args = args // Get CWD cwd, err := os.Readlink("/proc/" + strconv.Itoa(p.pid) + "/cwd") if err != nil { if !os.IsNotExist(err) { return types.ProcessInfo{}, fmt.Errorf("error while reading /proc/%s/cwd: %w", strconv.Itoa(p.pid), err) } } p.info.CWD = strings.TrimSuffix(cwd, "/") return *p.info, nil } // Environment returns the environment of a process. func (p *process) Environment() (map[string]string, error) { if p.env != nil { return p.env, nil } p.env = map[string]string{} /* If buffer is not large enough, args are truncated */ buf := make([]byte, 8192) info := C.struct_procsinfo64{} info.pi_pid = C.pid_t(p.pid) if _, err := C.getevars(unsafe.Pointer(&info), C.sizeof_struct_procsinfo64, (*C.char)(&buf[0]), 8192); err != nil { return nil, fmt.Errorf("error while calling getevars: %w", err) } bbuf := bytes.NewBuffer(buf) delim := []byte{61} // "=" for { line, err := bbuf.ReadBytes(0) if err == io.EOF || line[0] == 0 { break } if err != nil { return nil, fmt.Errorf("error while calling getevars: %w", err) } pair := bytes.SplitN(chop(line), delim, 2) if len(pair) != 2 { return nil, errors.New("error reading process environment") } p.env[string(pair[0])] = string(pair[1]) } return p.env, nil } // User returns the user IDs of a process. func (p *process) User() (types.UserInfo, error) { var prcred prcred if err := p.decodeProcfsFile("cred", &prcred); err != nil { return types.UserInfo{}, err } return types.UserInfo{ UID: strconv.Itoa(int(prcred.Ruid)), EUID: strconv.Itoa(int(prcred.Euid)), SUID: strconv.Itoa(int(prcred.Suid)), GID: strconv.Itoa(int(prcred.Rgid)), EGID: strconv.Itoa(int(prcred.Egid)), SGID: strconv.Itoa(int(prcred.Sgid)), }, nil } // Memory returns the current memory usage of a process. func (p *process) Memory() (types.MemoryInfo, error) { var mem types.MemoryInfo pagesize := uint64(os.Getpagesize()) info := C.struct_procsinfo64{} cpid := C.pid_t(p.pid) num, err := C.getprocs(unsafe.Pointer(&info), C.sizeof_struct_procsinfo64, nil, 0, &cpid, 1) if num != 1 { err = syscall.ESRCH } if err != nil { return types.MemoryInfo{}, fmt.Errorf("error while calling getprocs: %w", err) } mem.Resident = uint64(info.pi_drss+info.pi_trss) * pagesize mem.Virtual = uint64(info.pi_dvm) * pagesize return mem, nil } // CPUTime returns the current CPU usage of a process. func (p *process) CPUTime() (types.CPUTimes, error) { var pstatus pstatus if err := p.decodeProcfsFile("status", &pstatus); err != nil { return types.CPUTimes{}, err } return types.CPUTimes{ User: time.Duration(pstatus.Utime.Sec*1e9 + int64(pstatus.Utime.Nsec)), System: time.Duration(pstatus.Stime.Sec*1e9 + int64(pstatus.Stime.Nsec)), }, nil } func (p *process) decodeProcfsFile(name string, data interface{}) error { fileName := "/proc/" + strconv.Itoa(p.pid) + "/" + name file, err := os.Open(fileName) if err != nil { return fmt.Errorf("error while opening %s: %w", fileName, err) } defer file.Close() if err := binary.Read(file, binary.BigEndian, data); err != nil { return fmt.Errorf("error while decoding %s: %w", fileName, err) } return nil } func chop(buf []byte) []byte { return buf[0 : len(buf)-1] }