diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ce7a69e..b6ff7a88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added detection of containerized cgroup in Kubernetes [#80](https://github.com/elastic/go-sysinfo/pull/80) +- Add AIX support [#77](https://github.com/elastic/go-sysinfo/pull/77) ### Changed diff --git a/README.md b/README.md index d80c5bf1..3e28a047 100644 --- a/README.md +++ b/README.md @@ -31,25 +31,23 @@ if handleCounter, ok := process.(types.OpenHandleCounter); ok { These tables show what methods are implemented as well as the extra interfaces that are implemented. -| `Host` Features | Darwin | Linux | Windows | -|------------------|--------|-------|---------| -| `Info()` | x | x | x | -| `Memory()` | x | x | x | -| `CPUTimer` | x | x | x | -| `VMStat` | | x | | -| `NetworkCounters`| | x | | - -| `Process` Features | Darwin | Linux | Windows | -|------------------------|--------|-------|---------| -| `Info()` | x | x | x | -| `Memory()` | x | x | x | -| `User()` | x | x | x | -| `Parent()` | x | x | x | -| `CPUTimer` | x | x | x | -| `Environment` | x | x | | -| `OpenHandleEnumerator` | | x | | -| `OpenHandleCounter` | | x | | -| `Seccomp` | | x | | -| `Capabilities` | | x | | - - +| `Host` Features | Darwin | Linux | Windows | AIX/ppc64 | +|------------------|--------|-------|---------|-----------| +| `Info()` | x | x | x | x | +| `Memory()` | x | x | x | x | +| `CPUTimer` | x | x | x | x | +| `VMStat` | | x | | | +| `NetworkCounters`| | x | | | + +| `Process` Features | Darwin | Linux | Windows | AIX/ppc64 | +|------------------------|--------|-------|---------|-----------| +| `Info()` | x | x | x | x | +| `Memory()` | x | x | x | x | +| `User()` | x | x | x | x | +| `Parent()` | x | x | x | x | +| `CPUTimer` | x | x | x | x | +| `Environment` | x | x | | x | +| `OpenHandleEnumerator` | | x | | | +| `OpenHandleCounter` | | x | | | +| `Seccomp` | | x | | | +| `Capabilities` | | x | | | diff --git a/providers/aix/boottime_aix.go b/providers/aix/boottime_aix.go new file mode 100644 index 00000000..e8679609 --- /dev/null +++ b/providers/aix/boottime_aix.go @@ -0,0 +1,78 @@ +// 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. + +package aix + +import ( + "encoding/binary" + "os" + "time" + + "github.com/pkg/errors" +) + +// utmp can't be used by "encoding/binary" if generated by cgo, +// some pads will be missing. +type utmp struct { + User [256]uint8 + Id [14]uint8 + Line [64]uint8 + XPad1 int16 + Pid int32 + Type int16 + XPad2 int16 + Time int64 + Termination int16 + Exit int16 + Host [256]uint8 + Xdblwordpad int32 + XreservedA [2]int32 + XreservedV [6]int32 +} + +const ( + typeBootTime = 2 +) + +// BootTime returns the time at which the machine was started, truncated to the nearest second +func BootTime() (time.Time, error) { + return bootTime("/etc/utmp") +} + +func bootTime(filename string) (time.Time, error) { + // Get boot time from /etc/utmp + file, err := os.Open(filename) + if err != nil { + return time.Time{}, errors.Wrap(err, "failed to get host uptime: cannot open /etc/utmp") + } + + defer file.Close() + + for { + var utmp utmp + if err := binary.Read(file, binary.BigEndian, &utmp); err != nil { + break + } + + if utmp.Type == typeBootTime { + return time.Unix(utmp.Time, 0), nil + } + } + + return time.Time{}, errors.Wrap(err, "failed to get host uptime: no utmp record") + +} diff --git a/providers/aix/boottime_test.go b/providers/aix/boottime_test.go new file mode 100644 index 00000000..6d1a7763 --- /dev/null +++ b/providers/aix/boottime_test.go @@ -0,0 +1,36 @@ +// 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. + +// +build aix + +package aix + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBootTime(t *testing.T) { + bt, err := bootTime("testdata/utmp") + if err != nil { + t.Fatal(err) + } + + assert.EqualValues(t, bt.Unix(), 1585726535) + +} diff --git a/providers/aix/defs_aix.go b/providers/aix/defs_aix.go new file mode 100644 index 00000000..3537137b --- /dev/null +++ b/providers/aix/defs_aix.go @@ -0,0 +1,41 @@ +// 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. + +// +build ignore + +package aix + +/* +#include +#include +#include +*/ +import "C" + +type prcred C.prcred_t + +type pstatus C.pstatus_t +type prTimestruc64 C.pr_timestruc64_t +type prSigset C.pr_sigset_t +type fltset C.fltset_t +type lwpstatus C.lwpstatus_t +type prSiginfo64 C.pr_siginfo64_t +type prStack64 C.pr_stack64_t +type prSigaction64 C.struct_pr_sigaction64 +type prgregset C.prgregset_t +type prfpregset C.prfpregset_t +type pfamily C.pfamily_t diff --git a/providers/aix/doc.go b/providers/aix/doc.go new file mode 100644 index 00000000..6bc0787e --- /dev/null +++ b/providers/aix/doc.go @@ -0,0 +1,20 @@ +// 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. + +// Package aix implements the HostProvider and ProcessProvider interfaces +// for providing information about MacOS. +package aix diff --git a/providers/aix/host_aix.go b/providers/aix/host_aix.go new file mode 100644 index 00000000..15d408e7 --- /dev/null +++ b/providers/aix/host_aix.go @@ -0,0 +1,211 @@ +// 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. + +package aix + +/* +#cgo LDFLAGS: -L/usr/lib -lperfstat + +#include +#include +#include + +*/ +import "C" + +import ( + "os" + "time" + + "github.com/joeshaw/multierror" + "github.com/pkg/errors" + + "github.com/elastic/go-sysinfo/internal/registry" + "github.com/elastic/go-sysinfo/providers/shared" + "github.com/elastic/go-sysinfo/types" +) + +//go:generate sh -c "go tool cgo -godefs defs_aix.go | sed 's/*byte/uint64/g' > ztypes_aix_ppc64.go" +// As cgo will return some psinfo's fields with *byte, binary.Read will refuse this type. + +func init() { + registry.Register(aixSystem{}) +} + +type aixSystem struct{} + +// Host returns a new AIX host. +func (s aixSystem) Host() (types.Host, error) { + return newHost() +} + +type host struct { + info types.HostInfo +} + +// Architecture returns the architecture of the host +func Architecture() (string, error) { + return "ppc", nil +} + +// Info returns the host details. +func (h *host) Info() types.HostInfo { + return h.info +} + +// Info returns the current CPU usage of the host. +func (h *host) CPUTime() (types.CPUTimes, error) { + clock := uint64(C.sysconf(C._SC_CLK_TCK)) + tick2nsec := func(val uint64) uint64 { + return val * 1e9 / clock + } + + cpudata := C.perfstat_cpu_total_t{} + + if _, err := C.perfstat_cpu_total(nil, &cpudata, C.sizeof_perfstat_cpu_total_t, 1); err != nil { + return types.CPUTimes{}, errors.Wrap(err, "error while callin perfstat_cpu_total") + } + + return types.CPUTimes{ + User: time.Duration(tick2nsec(uint64(cpudata.user))), + System: time.Duration(tick2nsec(uint64(cpudata.sys))), + Idle: time.Duration(tick2nsec(uint64(cpudata.idle))), + IOWait: time.Duration(tick2nsec(uint64(cpudata.wait))), + }, nil +} + +// Memory returns the current memory usage of the host. +func (h *host) Memory() (*types.HostMemoryInfo, error) { + var mem types.HostMemoryInfo + + pagesize := uint64(os.Getpagesize()) + + meminfo := C.perfstat_memory_total_t{} + _, err := C.perfstat_memory_total(nil, &meminfo, C.sizeof_perfstat_memory_total_t, 1) + if err != nil { + return nil, errors.Wrap(err, "perfstat_memory_total failed") + } + + mem.Total = uint64(meminfo.real_total) * pagesize + mem.Free = uint64(meminfo.real_free) * pagesize + mem.Used = uint64(meminfo.real_inuse) * pagesize + + // There is no real equivalent to memory available in AIX. + mem.Available = mem.Free + + mem.VirtualTotal = uint64(meminfo.virt_total) * pagesize + mem.VirtualFree = mem.Free + uint64(meminfo.pgsp_free)*pagesize + mem.VirtualUsed = mem.VirtualTotal - mem.VirtualFree + + return &mem, nil +} + +func newHost() (*host, error) { + h := &host{} + r := &reader{} + r.architecture(h) + r.bootTime(h) + r.hostname(h) + r.network(h) + r.kernelVersion(h) + r.os(h) + r.time(h) + r.uniqueID(h) + return h, r.Err() +} + +type reader struct { + errs []error +} + +func (r *reader) addErr(err error) bool { + if err != nil { + if errors.Cause(err) != types.ErrNotImplemented { + r.errs = append(r.errs, err) + } + return true + } + return false +} + +func (r *reader) Err() error { + if len(r.errs) > 0 { + return &multierror.MultiError{Errors: r.errs} + } + return nil +} + +func (r *reader) architecture(h *host) { + v, err := Architecture() + if r.addErr(err) { + return + } + h.info.Architecture = v +} + +func (r *reader) bootTime(h *host) { + v, err := BootTime() + if r.addErr(err) { + return + } + h.info.BootTime = v +} + +func (r *reader) hostname(h *host) { + v, err := os.Hostname() + if r.addErr(err) { + return + } + h.info.Hostname = v +} + +func (r *reader) network(h *host) { + ips, macs, err := shared.Network() + if r.addErr(err) { + return + } + h.info.IPs = ips + h.info.MACs = macs +} + +func (r *reader) kernelVersion(h *host) { + v, err := KernelVersion() + if r.addErr(err) { + return + } + h.info.KernelVersion = v +} + +func (r *reader) os(h *host) { + v, err := OperatingSystem() + if r.addErr(err) { + return + } + h.info.OS = v +} + +func (r *reader) time(h *host) { + h.info.Timezone, h.info.TimezoneOffsetSec = time.Now().Zone() +} + +func (r *reader) uniqueID(h *host) { + v, err := MachineID() + if r.addErr(err) { + return + } + h.info.UniqueID = v +} diff --git a/providers/aix/kernel_aix.go b/providers/aix/kernel_aix.go new file mode 100644 index 00000000..8ba32525 --- /dev/null +++ b/providers/aix/kernel_aix.go @@ -0,0 +1,59 @@ +// 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. + +package aix + +/* +#include +*/ +import "C" + +import ( + "strconv" + + "github.com/pkg/errors" +) + +var oslevel string + +func getKernelVersion() (int, int, error) { + name := C.struct_utsname{} + if _, err := C.uname(&name); err != nil { + return 0, 0, errors.Wrap(err, "kernel version: uname") + } + + version, err := strconv.Atoi(C.GoString(&name.version[0])) + if err != nil { + return 0, 0, errors.Wrap(err, "parsing kernel version") + } + + release, err := strconv.Atoi(C.GoString(&name.release[0])) + if err != nil { + return 0, 0, errors.Wrap(err, "parsing kernel release") + } + return version, release, nil + +} + +// KernelVersion returns the version of AIX kernel +func KernelVersion() (string, error) { + major, minor, err := getKernelVersion() + if err != nil { + return "", err + } + return strconv.Itoa(major) + "." + strconv.Itoa(minor), nil +} diff --git a/providers/aix/machineid_aix.go b/providers/aix/machineid_aix.go new file mode 100644 index 00000000..e96b4a46 --- /dev/null +++ b/providers/aix/machineid_aix.go @@ -0,0 +1,35 @@ +// 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. + +package aix + +/* +#include +*/ +import "C" +import ( + "github.com/pkg/errors" +) + +// MachineID returns the id of the machine +func MachineID() (string, error) { + name := C.struct_utsname{} + if _, err := C.uname(&name); err != nil { + return "", errors.Wrap(err, "machine id") + } + return C.GoString(&name.machine[0]), nil +} diff --git a/providers/aix/os_aix.go b/providers/aix/os_aix.go new file mode 100644 index 00000000..b26d2fdc --- /dev/null +++ b/providers/aix/os_aix.go @@ -0,0 +1,58 @@ +// 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. + +package aix + +import ( + "io/ioutil" + "strconv" + "strings" + + "github.com/pkg/errors" + + "github.com/elastic/go-sysinfo/types" +) + +// OperatingSystem returns information of the host operating system +func OperatingSystem() (*types.OSInfo, error) { + return getOSInfo() +} + +func getOSInfo() (*types.OSInfo, error) { + major, minor, err := getKernelVersion() + if err != nil { + return nil, err + } + + // Retrieve build version from "/proc/version". + procVersion, err := ioutil.ReadFile("/proc/version") + if err != nil { + return nil, errors.Wrap(err, "failed to get OS info: cannot open /proc/version") + } + build := strings.SplitN(string(procVersion), "\n", 4)[2] + + return &types.OSInfo{ + Family: "aix", + Platform: "aix", + Name: "aix", + Version: strconv.Itoa(major) + "." + strconv.Itoa(minor), + Major: major, + Minor: minor, + Patch: 0, // No patch version + Build: build, + }, nil +} diff --git a/providers/aix/process_aix.go b/providers/aix/process_aix.go new file mode 100644 index 00000000..d4b45107 --- /dev/null +++ b/providers/aix/process_aix.go @@ -0,0 +1,302 @@ +// 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. + +package aix + +/* +#cgo LDFLAGS: -L/usr/lib -lperfstat + +#include +#include +#include + +*/ +import "C" + +import ( + "bytes" + "encoding/binary" + "io" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "strings" + "syscall" + "time" + "unsafe" + + "github.com/pkg/errors" + + "github.com/elastic/go-sysinfo/types" +) + +// Processes returns a list of all actives processes. +func (s aixSystem) Processes() ([]types.Process, error) { + // Retrieve processes using /proc instead of calling + // getprocs which will also retrieve kernel threads. + files, err := ioutil.ReadDir("/proc") + if err != nil { + return nil, errors.Wrap(err, "error while reading /proc") + } + + 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 (s 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{}, errors.Wrap(err, "error while calling getprocs") + } + + 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{}, errors.Wrap(err, "error while calling getargs") + } + + bbuf := bytes.NewBuffer(buf) + for { + arg, err := bbuf.ReadBytes(0) + if err == io.EOF || arg[0] == 0 { + break + } + if err != nil { + return types.ProcessInfo{}, errors.Wrap(err, "error while reading arguments") + } + + 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{}, errors.Wrapf(err, "error while reading /proc/%s/cwd", strconv.Itoa(p.pid)) + } + } + + 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, errors.Wrap(err, "error while calling getevars") + } + + 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, errors.Wrap(err, "error while calling getevars") + } + + pair := bytes.SplitN(chop(line), delim, 2) + if len(pair) != 2 { + return nil, errors.Wrap(err, "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{}, errors.Wrap(err, "error while calling getprocs") + } + + 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 errors.Wrapf(err, "error while opening %s", fileName) + } + defer file.Close() + + if err := binary.Read(file, binary.BigEndian, data); err != nil { + return errors.Wrapf(err, "error while decoding %s", fileName) + } + + return nil +} + +func chop(buf []byte) []byte { + return buf[0 : len(buf)-1] +} diff --git a/providers/aix/testdata/utmp b/providers/aix/testdata/utmp new file mode 100644 index 00000000..9c65e243 Binary files /dev/null and b/providers/aix/testdata/utmp differ diff --git a/providers/aix/ztypes_aix_ppc64.go b/providers/aix/ztypes_aix_ppc64.go new file mode 100644 index 00000000..118f2b91 --- /dev/null +++ b/providers/aix/ztypes_aix_ppc64.go @@ -0,0 +1,146 @@ +// 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. + +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_aix.go + +package aix + +type prcred struct { + Euid uint64 + Ruid uint64 + Suid uint64 + Egid uint64 + Rgid uint64 + Sgid uint64 + X_pad [8]uint64 + X_pad1 uint32 + Ngroups uint32 + Groups [1]uint64 +} + +type pstatus struct { + Flag uint32 + Flag2 uint32 + Flags uint32 + Nlwp uint32 + Stat uint8 + Dmodel uint8 + X_pad1 [6]uint8 + Sigpend prSigset + Brkbase uint64 + Brksize uint64 + Stkbase uint64 + Stksize uint64 + Pid uint64 + Ppid uint64 + Pgid uint64 + Sid uint64 + Utime prTimestruc64 + Stime prTimestruc64 + Cutime prTimestruc64 + Cstime prTimestruc64 + Sigtrace prSigset + Flttrace fltset + Sysentry_offset uint32 + Sysexit_offset uint32 + X_pad [8]uint64 + Lwp lwpstatus +} +type prTimestruc64 struct { + Sec int64 + Nsec int32 + X__pad uint32 +} +type prSigset struct { + Set [4]uint64 +} +type fltset struct { + Set [4]uint64 +} +type lwpstatus struct { + Lwpid uint64 + Flags uint32 + X_pad1 [1]uint8 + State uint8 + Cursig uint16 + Why uint16 + What uint16 + Policy uint32 + Clname [8]uint8 + Lwppend prSigset + Lwphold prSigset + Info prSiginfo64 + Altstack prStack64 + Action prSigaction64 + X_pad2 uint32 + Syscall uint16 + Nsysarg uint16 + Sysarg [8]uint64 + Errno int32 + Ptid uint32 + X_pad [9]uint64 + Reg prgregset + Fpreg prfpregset + Family pfamily +} +type prSiginfo64 struct { + Signo int32 + Errno int32 + Code int32 + Imm int32 + Status int32 + X__pad1 uint32 + Uid uint64 + Pid uint64 + Addr uint64 + Band int64 + Value [8]byte + X__pad [4]uint32 +} +type prStack64 struct { + Sp uint64 + Size uint64 + Flags int32 + X__pad [5]int32 +} +type prSigaction64 struct { + Union [8]byte + Mask prSigset + Flags int32 + X__pad [5]int32 +} +type prgregset struct { + X__iar uint64 + X__msr uint64 + X__cr uint64 + X__lr uint64 + X__ctr uint64 + X__xer uint64 + X__fpscr uint64 + X__fpscrx uint64 + X__gpr [32]uint64 + X__pad1 [8]uint64 +} +type prfpregset struct { + X__fpr [32]float64 +} +type pfamily struct { + Extoff uint64 + Extsize uint64 + Pad [14]uint64 +} diff --git a/system.go b/system.go index 90f81691..baf540bb 100644 --- a/system.go +++ b/system.go @@ -24,6 +24,7 @@ import ( "github.com/elastic/go-sysinfo/types" // Register host and process providers. + _ "github.com/elastic/go-sysinfo/providers/aix" _ "github.com/elastic/go-sysinfo/providers/darwin" _ "github.com/elastic/go-sysinfo/providers/linux" _ "github.com/elastic/go-sysinfo/providers/windows" diff --git a/system_test.go b/system_test.go index 5daabb01..b0a3550b 100644 --- a/system_test.go +++ b/system_test.go @@ -64,6 +64,12 @@ var expectedProcessFeatures = map[string]*ProcessFeatures{ OpenHandleEnumerator: false, OpenHandleCounter: true, }, + "aix": &ProcessFeatures{ + ProcessInfo: true, + Environment: true, + OpenHandleEnumerator: false, + OpenHandleCounter: false, + }, } func TestProcessFeaturesMatrix(t *testing.T) {