From 2124bbc3ba6d5df057ef3f20ad4203242d09a1ac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Chigot?= <clement.chigot@atos.net>
Date: Thu, 16 Jan 2020 11:28:39 +0100
Subject: [PATCH] all: add AIX providers

Add a implementation without CGO for AIX.
Host information are retrieved using AIX commands (uname, oslevel)
Process information are retrieved by /proc folder.

A CGO implementation could be added to more accurate syscalls and libraries
(getprocs, libperfstat, etc). But this version is enoguh for now.

A few features are still missing:
 - Host CPU usage since boot time: there is no easy way to retrieve it.
 - Process' environment
---
 providers/aix/boottime_aix.go     |  74 ++++++++++
 providers/aix/defs_aix.go         |  44 ++++++
 providers/aix/host_aix.go         | 206 +++++++++++++++++++++++++++
 providers/aix/kernel_aix.go       |  40 ++++++
 providers/aix/machineid_aix.go    |  34 +++++
 providers/aix/os_aix.go           |  81 +++++++++++
 providers/aix/process_aix.go      | 224 ++++++++++++++++++++++++++++++
 providers/aix/ztypes_aix_ppc64.go | 177 +++++++++++++++++++++++
 system.go                         |   1 +
 10 files changed, 887 insertions(+)
 create mode 100644 providers/aix/boottime_aix.go
 create mode 100644 providers/aix/defs_aix.go
 create mode 100644 providers/aix/host_aix.go
 create mode 100644 providers/aix/kernel_aix.go
 create mode 100644 providers/aix/machineid_aix.go
 create mode 100644 providers/aix/os_aix.go
 create mode 100644 providers/aix/process_aix.go
 create mode 100644 providers/aix/ztypes_aix_ppc64.go

diff --git a/providers/aix/boottime_aix.go b/providers/aix/boottime_aix.go
new file mode 100644
index 0000000..44ed245
--- /dev/null
+++ b/providers/aix/boottime_aix.go
@@ -0,0 +1,74 @@
+// 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 explicitly missing.
+type utmp struct {
+	User            [256]uint8
+	Id              [14]uint8
+	Line            [64]uint8
+	X_Pad1          int16
+	Pid             int32
+	Type            int16
+	X_Pad2          int16
+	Time            int64
+	Termination     int16
+	Exit            int16
+	Host            [256]uint8
+	X__dbl_word_pad int32
+	X__reservedA    [2]int32
+	X__reservedV    [6]int32
+}
+
+const (
+	BOOT_TIME = 2
+)
+
+// BootTime returns the time at which the machine was started, truncated to the nearest second
+func BootTime() (time.Time, error) {
+	// Get boot time from /etc/utmp
+	file, err := os.Open("/etc/utmp")
+	if err != nil {
+		return time.Time{}, errors.Wrapf(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 == BOOT_TIME {
+			return time.Unix(utmp.Time, 0), nil
+		}
+	}
+
+	return time.Time{}, errors.Wrapf(err, "failed to get host uptime: no utmp record")
+
+}
diff --git a/providers/aix/defs_aix.go b/providers/aix/defs_aix.go
new file mode 100644
index 0000000..f991557
--- /dev/null
+++ b/providers/aix/defs_aix.go
@@ -0,0 +1,44 @@
+// 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 <sys/types.h>
+#include <utmp.h>
+#include <sys/procfs.h>
+*/
+import "C"
+
+type psinfo C.psinfo_t
+type pr_timestruc64 C.pr_timestruc64_t
+type lwpsinfo C.lwpsinfo_t
+
+type prcred C.prcred_t
+
+type pstatus C.pstatus_t
+type pr_sigset C.pr_sigset_t
+type fltset C.fltset_t
+type lwpstatus C.lwpstatus_t
+type pr_siginfo64 C.pr_siginfo64_t
+type pr_stack64 C.pr_stack64_t
+type pr_sigaction64 C.struct_pr_sigaction64
+type prgregset C.prgregset_t
+type prfpregset C.prfpregset_t
+type pfamily C.pfamily_t
diff --git a/providers/aix/host_aix.go b/providers/aix/host_aix.go
new file mode 100644
index 0000000..6a6b38a
--- /dev/null
+++ b/providers/aix/host_aix.go
@@ -0,0 +1,206 @@
+// 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 (
+	"os"
+	"os/exec"
+	"strconv"
+	"strings"
+	"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{}
+
+func (s aixSystem) Host() (types.Host, error) {
+	return newHost()
+}
+
+type host struct {
+	info types.HostInfo
+}
+
+func Architecture() (string, error) {
+	return "ppc", nil
+}
+
+func (h *host) Info() types.HostInfo {
+	return h.info
+}
+
+func (h *host) CPUTime() (types.CPUTimes, error) {
+	return types.CPUTimes{}, types.ErrNotImplemented
+}
+
+func (h *host) Memory() (*types.HostMemoryInfo, error) {
+	var mem types.HostMemoryInfo
+
+	pagesize := os.Getpagesize()
+
+	// Use "svmon -G" output to get memory
+	// Output should be something similar to:
+	//               size       inuse        free         pin     virtual   mmode
+	// memory      2621440     2440556      180884      701953      790488     Ded
+	// pg space    2097152        3707
+	// ...
+	out, err := exec.Command("/usr/bin/svmon", "-G").Output()
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to get output of svmon")
+	}
+	var svmon_matrix [2][6]int
+	for i, l := range strings.SplitN(string(out), "\n", 4)[1:3] {
+		for j, s := range strings.Fields(l)[1:] {
+			// Remove mmode
+			if i == 0 && j == 5 {
+				continue
+			}
+			// Because of the space between "pg" and "space", they will be
+			// two different columns.
+			if i == 1 && j == 0 {
+				continue
+			}
+			svmon_matrix[i][j], err = strconv.Atoi(s)
+			if err != nil {
+				return nil, errors.Wrap(err, "failed to parse svmon")
+			}
+		}
+	}
+
+	mem.Total = uint64(svmon_matrix[0][0] * pagesize) // memory/size
+	mem.Used = uint64(svmon_matrix[0][1] * pagesize)  // memory/inuse
+	mem.Free = uint64(svmon_matrix[0][2] * pagesize)  // memory/free
+
+	// There is no real equivalent to memory available in AIX.
+	mem.Available = mem.Free
+
+	mem.VirtualTotal = mem.Total + uint64(svmon_matrix[1][1]*pagesize) // memory/size + pg space/size
+	mem.VirtualUsed = mem.Used + uint64(svmon_matrix[1][2]*pagesize)   // memory/inuse + pg space/inuse
+	mem.VirtualFree = mem.VirtualTotal - mem.VirtualUsed
+
+	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 0000000..a089296
--- /dev/null
+++ b/providers/aix/kernel_aix.go
@@ -0,0 +1,40 @@
+// 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 (
+	"os/exec"
+	"strings"
+
+	"github.com/pkg/errors"
+)
+
+var oslevel string
+
+func KernelVersion() (string, error) {
+	if oslevel != "" {
+		return oslevel, nil
+	}
+
+	out, err := exec.Command("/usr/bin/oslevel", "-s").Output()
+	if err != nil {
+		return "", errors.Wrap(err, "kernel version")
+	}
+	oslevel = strings.TrimSuffix(string(out), "\n")
+	return oslevel, nil
+}
diff --git a/providers/aix/machineid_aix.go b/providers/aix/machineid_aix.go
new file mode 100644
index 0000000..bef96a2
--- /dev/null
+++ b/providers/aix/machineid_aix.go
@@ -0,0 +1,34 @@
+// 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 (
+	"os/exec"
+	"strings"
+
+	"github.com/pkg/errors"
+)
+
+func MachineID() (string, error) {
+	output, err := exec.Command("/usr/bin/uname", "-f").Output()
+	if err != nil {
+		return "", errors.Wrapf(err, "error while retrieving machine-id")
+	}
+	return strings.TrimSpace(string(output)), nil
+
+}
diff --git a/providers/aix/os_aix.go b/providers/aix/os_aix.go
new file mode 100644
index 0000000..e363ec6
--- /dev/null
+++ b/providers/aix/os_aix.go
@@ -0,0 +1,81 @@
+// 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"
+)
+
+func OperatingSystem() (*types.OSInfo, error) {
+	return getOSInfo()
+}
+
+func getOSInfo() (*types.OSInfo, error) {
+	version, err := KernelVersion()
+	if err != nil {
+		return nil, err
+	}
+
+	// Parse output of "oslevel -s"
+	// AIX doesn't really follow major.minor.patch pattern.
+	// To be as closed as possible, this code associates:
+	//  - major = AIX versions (7.2, 7.1, 6.1, etc)
+	//    Note, as int are wanted, it will be 72, 71 or 61.
+	//  - minor = technical level
+	//  - patch = service pack
+	var major, minor, patch int
+	for i, v := range strings.SplitN(version, "-", 4) {
+		switch i {
+		case 0:
+			// "oslevel -s" will return 7200, 7100 or 6100,
+			// so the last 0 are removed to be more comprehensive.
+			major, _ = strconv.Atoi(v[0:2])
+		case 1:
+			minor, _ = strconv.Atoi(v)
+		case 2:
+			patch, _ = strconv.Atoi(v)
+		default:
+			break
+		}
+	}
+
+	// Take build version from "/proc/version" rather than the one returned
+	// "oslevel" as it's more precise.
+	proc_version, err := ioutil.ReadFile("/proc/version")
+	if err != nil {
+		return nil, errors.Wrapf(err, "failed to get OS info: cannot open /proc/version")
+	}
+	build := strings.SplitN(string(proc_version), "\n", 4)[2]
+
+	return &types.OSInfo{
+		Family:   "aix",
+		Platform: "aix",
+		Name:     "aix",
+		Version:  version,
+		Major:    major,
+		Minor:    minor,
+		Patch:    patch,
+		Build:    build,
+	}, nil
+}
diff --git a/providers/aix/process_aix.go b/providers/aix/process_aix.go
new file mode 100644
index 0000000..c8461f8
--- /dev/null
+++ b/providers/aix/process_aix.go
@@ -0,0 +1,224 @@
+// 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 (
+	"bytes"
+	"encoding/binary"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/pkg/errors"
+
+	"github.com/elastic/go-sysinfo/types"
+)
+
+func (s aixSystem) Processes() ([]types.Process, error) {
+	files, err := ioutil.ReadDir("/proc")
+	if err != nil {
+		return nil, errors.Wrapf(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
+}
+
+func (s aixSystem) Process(pid int) (types.Process, error) {
+	p := process{pid: pid}
+	return &p, nil
+}
+
+func (s aixSystem) Self() (types.Process, error) {
+	return s.Process(os.Getpid())
+}
+
+type process struct {
+	pid  int
+	info *types.ProcessInfo
+}
+
+func (p *process) PID() int {
+	return p.pid
+}
+
+func (p *process) Parent() (types.Process, error) {
+	info, err := p.Info()
+	if err != nil {
+		return nil, err
+	}
+	return &process{pid: info.PPID}, nil
+}
+
+func (p *process) Info() (types.ProcessInfo, error) {
+	if p.info != nil {
+		return *p.info, nil
+	}
+
+	var psinfo psinfo
+	if err := p.decodeProcfsFile("psinfo", &psinfo); err != nil {
+		return types.ProcessInfo{}, err
+	}
+
+	procdir := "/proc/" + strconv.Itoa(p.pid)
+	cwd, err := os.Readlink(procdir + "/cwd")
+	if err != nil {
+		if !os.IsNotExist(err) {
+			return types.ProcessInfo{}, errors.Wrapf(err, "error while reading %s/cwd", procdir)
+		}
+	}
+	cwd = strings.TrimSuffix(cwd, "/")
+
+	// psinfo.Psargs is the command launched up to 80 characters, both the executable path
+	// and its arguments
+	var exe, name string
+	args_list := strings.Split(convertBytesToString(psinfo.Psargs[:]), " ")
+
+	// If the first element of the arguments list (ie the executable path)
+	// is less that the 80 characters, we are sure that the whole name is present.
+	// Retrieve the name the pathname in psinfo.Psargs
+	if len(args_list[0]) < 80 {
+		name = filepath.Base(args_list[0])
+		// The process was launched using its absolute path, so we can retrieve
+		// the executable path from its "name".
+		if filepath.IsAbs(args_list[0]) {
+			exe = filepath.Clean(args_list[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"
+			exe = name
+		}
+	} else {
+		// TODO: parse ps ! !
+		// Use the value of psinfo.Fname, which is the last
+		// component of the exec()ed pathname up to 16 characters
+		name = convertBytesToString(psinfo.Fname[:])
+		exe = name
+	}
+
+	// Exe and Args aren't fully acurate but that's enough in a first instance
+	p.info = &types.ProcessInfo{
+		Name:      name,
+		PID:       p.pid,
+		PPID:      int(psinfo.Ppid),
+		CWD:       cwd,
+		Exe:       exe,
+		Args:      args_list,
+		StartTime: time.Unix(int64(psinfo.Start.Sec), int64(psinfo.Start.Sec)),
+	}
+
+	return *p.info, nil
+}
+
+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
+
+}
+
+func (p *process) Memory() (types.MemoryInfo, error) {
+	var mem types.MemoryInfo
+	pagesize := os.Getpagesize()
+
+	// use "svmon -P pid" output to get memory
+	// Output should be something similar to:
+	// (blank line)
+	// -------------------------------------------------------------------------------
+	//     Pid Command          Inuse      Pin     Pgsp  Virtual 64-bit Mthrd  16MB
+	// 20054382 sftp-server      36042    17636        0    35984      N     N     N
+	out, err := exec.Command("/usr/bin/svmon", "-P", strconv.Itoa(p.pid)).Output()
+	if err != nil {
+		return types.MemoryInfo{}, errors.Wrap(err, "failed to get output of svmon")
+	}
+
+	l := strings.SplitN(string(out), "\n", 4)[3]
+	for j, s := range strings.Fields(l)[2:5] {
+		val, err := strconv.Atoi(s)
+		if err != nil {
+			return types.MemoryInfo{}, errors.Wrap(err, "failed to parse svmon")
+		}
+		if j == 0 {
+			mem.Resident = uint64(val * pagesize) // inuse
+		}
+		if j == 2 {
+			mem.Virtual = mem.Resident + uint64(val*pagesize) // inuse + pgsp
+		}
+	}
+
+	return mem, nil
+}
+
+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 convertBytesToString(arr []byte) string {
+	n := bytes.IndexByte(arr, 0)
+	if n == -1 {
+		return string(arr[:])
+	}
+	return string(arr[:n])
+}
diff --git a/providers/aix/ztypes_aix_ppc64.go b/providers/aix/ztypes_aix_ppc64.go
new file mode 100644
index 0000000..e22b42c
--- /dev/null
+++ b/providers/aix/ztypes_aix_ppc64.go
@@ -0,0 +1,177 @@
+// Code generated by cmd/cgo -godefs; DO NOT EDIT.
+// cgo -godefs defs_aix.go
+
+package aix
+
+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  pr_timestruc64
+	Time   pr_timestruc64
+	Cid    uint16
+	X_pad2 uint16
+	Argc   uint32
+	Argv   uint64
+	Envp   uint64
+	Fname  [16]uint8
+	Psargs [80]uint8
+	X_pad  [8]uint64
+	Lwp    lwpsinfo
+}
+type pr_timestruc64 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 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         pr_sigset
+	Brkbase         uint64
+	Brksize         uint64
+	Stkbase         uint64
+	Stksize         uint64
+	Pid             uint64
+	Ppid            uint64
+	Pgid            uint64
+	Sid             uint64
+	Utime           pr_timestruc64
+	Stime           pr_timestruc64
+	Cutime          pr_timestruc64
+	Cstime          pr_timestruc64
+	Sigtrace        pr_sigset
+	Flttrace        fltset
+	Sysentry_offset uint32
+	Sysexit_offset  uint32
+	X_pad           [8]uint64
+	Lwp             lwpstatus
+}
+type pr_sigset 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  pr_sigset
+	Lwphold  pr_sigset
+	Info     pr_siginfo64
+	Altstack pr_stack64
+	Action   pr_sigaction64
+	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 pr_siginfo64 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 pr_stack64 struct {
+	Sp     uint64
+	Size   uint64
+	Flags  int32
+	X__pad [5]int32
+}
+type pr_sigaction64 struct {
+	Union  [8]byte
+	Mask   pr_sigset
+	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 90f8169..baf540b 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"
-- 
2.22.0

