mirror of https://github.com/containers/podman.git
vendor latest containers/psgo
Signed-off-by: Valentin Rothberg <vrothberg@suse.com> Closes: #1162 Approved by: rhatdan
This commit is contained in:
parent
d9ae17400d
commit
159f7f179b
|
@ -24,7 +24,7 @@ to run containers such as CRI-O, the last started container could be from either
|
||||||
|
|
||||||
The following descriptors are supported in addition to the AIX format descriptors mentioned in ps (1):
|
The following descriptors are supported in addition to the AIX format descriptors mentioned in ps (1):
|
||||||
|
|
||||||
**args,comm,etime,group,nice,pcpu,pgid,pid,ppid,rgroup,ruser,time,tty,user,vsz**
|
**args, capbnd, capeff, capinh, capprm, comm, etime, group, hgroup, hpid, huser, label, nice, pcpu, pgid, pid, ppid, rgroup, ruser, seccomp, state, time, tty, user, vsz**
|
||||||
|
|
||||||
**capbnd**
|
**capbnd**
|
||||||
|
|
||||||
|
@ -42,14 +42,30 @@ The following descriptors are supported in addition to the AIX format descriptor
|
||||||
|
|
||||||
Set of permitted capabilities. See capabilities (7) for more information.
|
Set of permitted capabilities. See capabilities (7) for more information.
|
||||||
|
|
||||||
**seccomp**
|
**hgroup**
|
||||||
|
|
||||||
Seccomp mode of the process (i.e., disabled, strict or filter). See seccomp (2) for more information.
|
The corresponding effective group of a container process on the host.
|
||||||
|
|
||||||
|
**hpid**
|
||||||
|
|
||||||
|
The corresponding host PID of a container process.
|
||||||
|
|
||||||
|
**huser**
|
||||||
|
|
||||||
|
The corresponding effective user of a container process on the host.
|
||||||
|
|
||||||
**label**
|
**label**
|
||||||
|
|
||||||
Current security attributes of the process.
|
Current security attributes of the process.
|
||||||
|
|
||||||
|
**seccomp**
|
||||||
|
|
||||||
|
Seccomp mode of the process (i.e., disabled, strict or filter). See seccomp (2) for more information.
|
||||||
|
|
||||||
|
**state**
|
||||||
|
|
||||||
|
Process state codes (e.g, **R** for *running*, **S** for *sleeping*). See proc(5) for more information.
|
||||||
|
|
||||||
## EXAMPLES
|
## EXAMPLES
|
||||||
|
|
||||||
By default, `podman-top` prints data similar to `ps -ef`:
|
By default, `podman-top` prints data similar to `ps -ef`:
|
||||||
|
@ -72,7 +88,7 @@ PID SECCOMP COMMAND %CPU
|
||||||
```
|
```
|
||||||
|
|
||||||
## SEE ALSO
|
## SEE ALSO
|
||||||
podman(1), ps(1), seccomp(2), capabilities(7)
|
podman(1), ps(1), seccomp(2), proc(5), capabilities(7)
|
||||||
|
|
||||||
## HISTORY
|
## HISTORY
|
||||||
December 2017, Originally compiled by Brent Baude <bbaude@redhat.com>
|
December 2017, Originally compiled by Brent Baude <bbaude@redhat.com>
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containers/psgo/ps"
|
"github.com/containers/psgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetContainerPidInformation returns process-related data of all processes in
|
// GetContainerPidInformation returns process-related data of all processes in
|
||||||
|
@ -19,12 +19,23 @@ import (
|
||||||
// For more details, please refer to github.com/containers/psgo.
|
// For more details, please refer to github.com/containers/psgo.
|
||||||
func (c *Container) GetContainerPidInformation(descriptors []string) ([]string, error) {
|
func (c *Container) GetContainerPidInformation(descriptors []string) ([]string, error) {
|
||||||
pid := strconv.Itoa(c.state.PID)
|
pid := strconv.Itoa(c.state.PID)
|
||||||
format := strings.Join(descriptors, ",")
|
// TODO: psgo returns a [][]string to give users the ability to apply
|
||||||
return ps.JoinNamespaceAndProcessInfo(pid, format)
|
// filters on the data. We need to change the API here and the
|
||||||
|
// varlink API to return a [][]string if we want to make use of
|
||||||
|
// filtering.
|
||||||
|
psgoOutput, err := psgo.JoinNamespaceAndProcessInfo(pid, descriptors)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res := []string{}
|
||||||
|
for _, out := range psgoOutput {
|
||||||
|
res = append(res, strings.Join(out, "\t"))
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetContainerPidInformationDescriptors returns a string slice of all supported
|
// GetContainerPidInformationDescriptors returns a string slice of all supported
|
||||||
// format descriptors of GetContainerPidInformation.
|
// format descriptors of GetContainerPidInformation.
|
||||||
func GetContainerPidInformationDescriptors() ([]string, error) {
|
func GetContainerPidInformationDescriptors() ([]string, error) {
|
||||||
return ps.ListDescriptors(), nil
|
return psgo.ListDescriptors(), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ github.com/containernetworking/cni v0.7.0-alpha1
|
||||||
github.com/containernetworking/plugins 1fb94a4222eafc6f948eacdca9c9f2158b427e53
|
github.com/containernetworking/plugins 1fb94a4222eafc6f948eacdca9c9f2158b427e53
|
||||||
github.com/containers/image c6e0eee0f8eb38e78ae2e44a9aeea0576f451617
|
github.com/containers/image c6e0eee0f8eb38e78ae2e44a9aeea0576f451617
|
||||||
github.com/containers/storage afdedba2d2ad573350aee35033d4e0c58fdbd57b
|
github.com/containers/storage afdedba2d2ad573350aee35033d4e0c58fdbd57b
|
||||||
github.com/containers/psgo 4ccd87a37eaec61a669da89ffacb0f79f3550943
|
github.com/containers/psgo 382fc951fe0a8aba62043862ce1a56f77524db87
|
||||||
github.com/coreos/go-systemd v14
|
github.com/coreos/go-systemd v14
|
||||||
github.com/cri-o/ocicni master
|
github.com/cri-o/ocicni master
|
||||||
github.com/cyphar/filepath-securejoin v0.2.1
|
github.com/cyphar/filepath-securejoin v0.2.1
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
[](https://godoc.org/github.com/containers/psgo/ps) [](https://travis-ci.org/containers/psgo)
|
[](https://godoc.org/github.com/containers/psgo) [](https://travis-ci.org/containers/psgo)
|
||||||
|
|
||||||
# psgo
|
# psgo
|
||||||
A ps(1) AIX-format compatible golang library extended with various descriptors useful for displaying container-related data.
|
A ps(1) AIX-format compatible golang library extended with various descriptors useful for displaying container-related data.
|
||||||
|
@ -7,17 +7,17 @@ The idea behind the library is to provide an easy to use way of extracting proce
|
||||||
|
|
||||||
This library aims to make things a bit more comfortable, especially for container runtimes, as the API allows to join the mount namespace of a given process and will parse `/proc` and `/dev/` from there. The API consists of the following functions:
|
This library aims to make things a bit more comfortable, especially for container runtimes, as the API allows to join the mount namespace of a given process and will parse `/proc` and `/dev/` from there. The API consists of the following functions:
|
||||||
|
|
||||||
- `ps.ProcessInfo(format string) ([]string, error)`
|
- `psgo.ProcessInfo(descriptors []string) ([][]string, error)`
|
||||||
- ProcessInfo returns the process information of all processes in the current mount namespace. The input format must be a comma-separated list of supported AIX format descriptors. If the input string is empty, the DefaultFormat is used. The return value is a slice of tab-separated strings, to easily use the output for column-based formatting (e.g., with the `text/tabwriter` package).
|
- ProcessInfo returns the process information of all processes in the current mount namespace. The input descriptors must be a slice of supported AIX format descriptors in the normal form or in the code form, if supported. If the input descriptor slice is empty, the `psgo.DefaultDescriptors` are used. The return value contains the string slice of process data, one per process.
|
||||||
|
|
||||||
- `ps.JoinNamespaceAndProcessInfo(pid, format string) ([]string, error)`
|
- `psgo.JoinNamespaceAndProcessInfo(pid string, descriptors []string) ([][]string, error)`
|
||||||
- JoinNamespaceAndProcessInfo has the same semantics as ProcessInfo but joins the mount namespace of the specified pid before extracting data from /proc. This way, we can extract the `/proc` data from a container without executing any command inside the container.
|
- JoinNamespaceAndProcessInfo has the same semantics as ProcessInfo but joins the mount namespace of the specified pid before extracting data from /proc. This way, we can extract the `/proc` data from a container without executing any command inside the container.
|
||||||
|
|
||||||
- `ps.ListDescriptors() []string`
|
- `psgo.ListDescriptors() []string`
|
||||||
- ListDescriptors returns a sorted string slice of all supported AIX format descriptors in the normal form (e.g., "args,comm,user"). It can be useful in the context of bash-completion, help messages, etc.
|
- ListDescriptors returns a sorted string slice of all supported AIX format descriptors in the normal form (e.g., "args,comm,user"). It can be useful in the context of bash-completion, help messages, etc.
|
||||||
|
|
||||||
### Listing processes
|
### Listing processes
|
||||||
We can use the [psgo](https://github.com/containers/psgo/blob/master/psgo.go) tool from this project to test the core components of this library. First, let's build `psgo` via `make build`. The binary is now located under `./bin/psgo`. By default `psgo` displays data about all running processes in the current mount namespace, similar to the output of `ps -ef`.
|
We can use the [psgo](https://github.com/containers/psgo/blob/master/sample/sample.go) sample tool from this project to test the core components of this library. First, let's build `psgo` via `make build`. The binary is now located under `./bin/psgo`. By default `psgo` displays data about all running processes in the current mount namespace, similar to the output of `ps -ef`.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ ./bin/psgo | head -n5
|
$ ./bin/psgo | head -n5
|
||||||
|
@ -46,18 +46,26 @@ root 1 0 0.000 17.249905587s ? 0s sleep
|
||||||
### Format descriptors
|
### Format descriptors
|
||||||
The ps library is compatible with all AIX format descriptors of the ps command-line utility (see `man 1 ps` for details) but it also supports some additional descriptors that can be useful when seeking specific process-related information.
|
The ps library is compatible with all AIX format descriptors of the ps command-line utility (see `man 1 ps` for details) but it also supports some additional descriptors that can be useful when seeking specific process-related information.
|
||||||
|
|
||||||
|
- **capbnd**
|
||||||
|
- Set of bounding capabilities. See capabilities(7) for more information.
|
||||||
|
- **capeff**
|
||||||
|
- Set of effective capabilities. See capabilities(7) for more information.
|
||||||
- **capinh**
|
- **capinh**
|
||||||
- Set of inheritable capabilities. See capabilities(7) for more information.
|
- Set of inheritable capabilities. See capabilities(7) for more information.
|
||||||
- **capprm**
|
- **capprm**
|
||||||
- Set of permitted capabilities. See capabilities(7) for more information.
|
- Set of permitted capabilities. See capabilities(7) for more information.
|
||||||
- **capeff**
|
- **hgroup**
|
||||||
- Set of effective capabilities. See capabilities (7) for more information.
|
- The corresponding effective group of a container process on the host.
|
||||||
- **capbnd**
|
- **hpid**
|
||||||
- Set of bounding capabilities. See capabilities (7) for more information.
|
- The corresponding host PID of a container process.
|
||||||
- **seccomp**
|
- **huser**
|
||||||
- Seccomp mode of the process (i.e., disabled, strict or filter). See seccomp (2) for more information.
|
- The corresponding effective user of a container process on the host.
|
||||||
- **label**
|
- **label**
|
||||||
- Current security attributes of the process.
|
- Current security attributes of the process.
|
||||||
|
- **seccomp**
|
||||||
|
- Seccomp mode of the process (i.e., disabled, strict or filter). See seccomp(2) for more information.
|
||||||
|
- **state**
|
||||||
|
- Process state codes (e.g, **R** for *running*, **S** for *sleeping*). See proc(5) for more information.
|
||||||
|
|
||||||
We can try out different format descriptors with the psgo binary:
|
We can try out different format descriptors with the psgo binary:
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
package ps
|
// Package capabilities provides a mapping from common kernel bit masks to the
|
||||||
|
// alphanumerical represenation of kernel capabilities. See capabilities(7)
|
||||||
|
// for additional information.
|
||||||
|
package capabilities
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// capabilities are a mapping from a numerical value to the textual
|
// capabilities are a mapping from a numerical value to the textual
|
||||||
|
@ -47,16 +50,17 @@ var (
|
||||||
36: "BLOCK_SUSPEND",
|
36: "BLOCK_SUSPEND",
|
||||||
37: "AUDIT_READ",
|
37: "AUDIT_READ",
|
||||||
}
|
}
|
||||||
// fullCAPs represents the value of a bitmask with a full capability
|
|
||||||
|
// FullCAPs represents the value of a bitmask with a full capability
|
||||||
// set.
|
// set.
|
||||||
fullCAPs = uint64(0x3FFFFFFFFF)
|
FullCAPs = uint64(0x3FFFFFFFFF)
|
||||||
)
|
)
|
||||||
|
|
||||||
// maskToCaps iterates over mask and returns a slice of corresponding
|
// TranslateMask iterates over mask and returns a slice of corresponding
|
||||||
// capabilities. If a bit is out of range of known capabilities, it is set as
|
// capabilities. If a bit is out of range of known capabilities, it is set as
|
||||||
// "unknown" to catch potential regressions when new capabilities are added to
|
// "unknown" to catch potential regressions when new capabilities are added to
|
||||||
// the kernel.
|
// the kernel.
|
||||||
func maskToCaps(mask uint64) []string {
|
func TranslateMask(mask uint64) []string {
|
||||||
caps := []string{}
|
caps := []string{}
|
||||||
for i := uint(0); i < 64; i++ {
|
for i := uint(0); i < 64; i++ {
|
||||||
if (mask>>i)&0x1 == 1 {
|
if (mask>>i)&0x1 == 1 {
|
|
@ -1,4 +1,4 @@
|
||||||
package ps
|
package dev
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
@ -6,15 +6,44 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
// tty represents a tty including its minor and major device number and the
|
// TTY represents a tty including its minor and major device number and the
|
||||||
// path to the tty.
|
// path to the tty.
|
||||||
type tty struct {
|
type TTY struct {
|
||||||
// minor device number
|
// Minor device number.
|
||||||
minor uint64
|
Minor uint64
|
||||||
// major device number
|
// Major device number.
|
||||||
major uint64
|
Major uint64
|
||||||
// path to the tty
|
// Path to the tty device.
|
||||||
device string
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
// cache TTYs to avoid redundant lookups
|
||||||
|
var devices *[]TTY
|
||||||
|
|
||||||
|
// FindTTY return the corresponding TTY to the ttyNr or nil of non could be
|
||||||
|
// found.
|
||||||
|
func FindTTY(ttyNr uint64) (*TTY, error) {
|
||||||
|
// (man 5 proc) The minor device number is contained in the combination
|
||||||
|
// of bits 31 to 20 and 7 to 0; the major device number is in bits 15
|
||||||
|
// to 8.
|
||||||
|
maj := (ttyNr >> 8) & 0xFF
|
||||||
|
min := (ttyNr & 0xFF) | ((ttyNr >> 20) & 0xFFF)
|
||||||
|
|
||||||
|
if devices == nil {
|
||||||
|
devs, err := getTTYs()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
devices = devs
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, t := range *devices {
|
||||||
|
if t.Minor == min && t.Major == maj {
|
||||||
|
return &t, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// majDevNum returns the major device number of rdev (see stat_t.Rdev).
|
// majDevNum returns the major device number of rdev (see stat_t.Rdev).
|
||||||
|
@ -27,37 +56,8 @@ func minDevNum(rdev uint64) uint64 {
|
||||||
return (rdev & 0xff) | ((rdev >> 12) & 0xfff00)
|
return (rdev & 0xff) | ((rdev >> 12) & 0xfff00)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ttyNrToDev returns the major and minor number of the tty device from
|
|
||||||
// /proc/$pid/stat.tty_nr as described in man 5 proc.
|
|
||||||
func ttyNrToDev(ttyNr uint64) (uint64, uint64) {
|
|
||||||
// (man 5 proc) The minor device number is contained in the combination
|
|
||||||
// of bits 31 to 20 and 7 to 0; the major device number is in bits 15
|
|
||||||
// to 8.
|
|
||||||
maj := (ttyNr >> 8) & 0xFF
|
|
||||||
min := (ttyNr & 0xFF) | ((ttyNr >> 20) & 0xFFF)
|
|
||||||
return maj, min
|
|
||||||
}
|
|
||||||
|
|
||||||
// findTTY returns a tty with the corresponding major and minor device number
|
|
||||||
// or nil if no matching tty is found.
|
|
||||||
func findTTY(maj, min uint64) (*tty, error) {
|
|
||||||
if len(ttyDevices) == 0 {
|
|
||||||
var err error
|
|
||||||
ttyDevices, err = getTTYs()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, t := range ttyDevices {
|
|
||||||
if t.minor == min && t.major == maj {
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getTTYs parses /dev for tty and pts devices.
|
// getTTYs parses /dev for tty and pts devices.
|
||||||
func getTTYs() ([]*tty, error) {
|
func getTTYs() (*[]TTY, error) {
|
||||||
devDir, err := os.Open("/dev/")
|
devDir, err := os.Open("/dev/")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -90,7 +90,7 @@ func getTTYs() ([]*tty, error) {
|
||||||
devices = append(devices, "/dev/pts/"+d)
|
devices = append(devices, "/dev/pts/"+d)
|
||||||
}
|
}
|
||||||
|
|
||||||
ttys := []*tty{}
|
ttys := []TTY{}
|
||||||
for _, dev := range devices {
|
for _, dev := range devices {
|
||||||
fi, err := os.Stat(dev)
|
fi, err := os.Stat(dev)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -101,13 +101,13 @@ func getTTYs() ([]*tty, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
s := fi.Sys().(*syscall.Stat_t)
|
s := fi.Sys().(*syscall.Stat_t)
|
||||||
t := tty{
|
t := TTY{
|
||||||
minor: minDevNum(s.Rdev),
|
Minor: minDevNum(s.Rdev),
|
||||||
major: majDevNum(s.Rdev),
|
Major: majDevNum(s.Rdev),
|
||||||
device: dev,
|
Path: dev,
|
||||||
}
|
}
|
||||||
ttys = append(ttys, &t)
|
ttys = append(ttys, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ttys, nil
|
return &ttys, nil
|
||||||
}
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
// Package host extracts data from the host, such as the system's boot time or
|
||||||
|
// the tick rate of the system clock.
|
||||||
|
package host
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <unistd.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// cache host queries to redundant calculations
|
||||||
|
clockTicks *int64
|
||||||
|
bootTime *int64
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClockTicks returns sysconf(SC_CLK_TCK).
|
||||||
|
func ClockTicks() int64 {
|
||||||
|
if clockTicks == nil {
|
||||||
|
ticks := int64(C.sysconf(C._SC_CLK_TCK))
|
||||||
|
clockTicks = &ticks
|
||||||
|
}
|
||||||
|
return *clockTicks
|
||||||
|
}
|
||||||
|
|
||||||
|
// BootTime parses /proc/uptime returns the boot time in seconds since the
|
||||||
|
// Epoch, 1970-01-01 00:00:00 +0000 (UTC).
|
||||||
|
func BootTime() (int64, error) {
|
||||||
|
if bootTime != nil {
|
||||||
|
return *bootTime, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open("/proc/stat")
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
btimeStr := ""
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
fields := strings.Fields(scanner.Text())
|
||||||
|
if len(fields) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if fields[0] == "btime" {
|
||||||
|
btimeStr = fields[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(btimeStr) == 0 {
|
||||||
|
return 0, fmt.Errorf("couldn't extract boot time from /proc/stat")
|
||||||
|
}
|
||||||
|
|
||||||
|
btimeSec, err := strconv.ParseInt(btimeStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("error parsing boot time from /proc/stat: %s", err)
|
||||||
|
}
|
||||||
|
bootTime = &btimeSec
|
||||||
|
return btimeSec, nil
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package proc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseAttrCurrent returns the contents of /proc/$pid/attr/current of "?" if
|
||||||
|
// labeling is not supported on the host.
|
||||||
|
func ParseAttrCurrent(pid string) (string, error) {
|
||||||
|
data, err := ioutil.ReadFile(fmt.Sprintf("/proc/%s/attr/current", pid))
|
||||||
|
if err != nil {
|
||||||
|
_, err = os.Stat(fmt.Sprintf("/proc/%s", pid))
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
// PID doesn't exist
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// PID exists but labeling seems to be unsupported
|
||||||
|
return "?", nil
|
||||||
|
}
|
||||||
|
return strings.Trim(string(data), "\n"), nil
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package proc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseCmdLine parses a /proc/$pid/cmdline file and returns a string slice.
|
||||||
|
func ParseCmdLine(pid string) ([]string, error) {
|
||||||
|
data, err := ioutil.ReadFile(fmt.Sprintf("/proc/%s/cmdline", pid))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdLine := []string{}
|
||||||
|
for _, rawCmd := range bytes.Split(data, []byte{0}) {
|
||||||
|
cmdLine = append(cmdLine, string(rawCmd))
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmdLine, nil
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package proc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParsePIDNamespace returns the content of /proc/$pid/ns/pid.
|
||||||
|
func ParsePIDNamespace(pid string) (string, error) {
|
||||||
|
pidNS, err := os.Readlink(fmt.Sprintf("/proc/%s/ns/pid", pid))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return pidNS, nil
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
package proc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetPIDs extracts and returns all PIDs from /proc.
|
||||||
|
func GetPIDs() ([]string, error) {
|
||||||
|
procDir, err := os.Open("/proc/")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer procDir.Close()
|
||||||
|
|
||||||
|
// extract string slice of all directories in procDir
|
||||||
|
pidDirs, err := procDir.Readdirnames(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pids := []string{}
|
||||||
|
for _, pidDir := range pidDirs {
|
||||||
|
_, err := strconv.Atoi(pidDir)
|
||||||
|
if err != nil {
|
||||||
|
// skip non-numerical entries (e.g., `/proc/softirqs`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pids = append(pids, pidDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pids, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// pidCgroupPath returns the path to the pid's pids cgroup.
|
||||||
|
func pidCgroupPath(pid string) (string, error) {
|
||||||
|
f, err := os.Open(fmt.Sprintf("/proc/%s/cgroup", pid))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
fields := strings.Split(scanner.Text(), ":")
|
||||||
|
if len(fields) != 3 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if fields[1] == "pids" {
|
||||||
|
return fmt.Sprintf("/sys/fs/cgroup/pids/%s/cgroup.procs", fields[2]), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("couldn't find pids group for PID %s", pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPIDsFromCgroup returns a strings slice of all pids listesd in pid's pids
|
||||||
|
// cgroup.
|
||||||
|
func GetPIDsFromCgroup(pid string) ([]string, error) {
|
||||||
|
cgroupPath, err := pidCgroupPath(pid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(cgroupPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
pids := []string{}
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
pids = append(pids, scanner.Text())
|
||||||
|
}
|
||||||
|
return pids, nil
|
||||||
|
}
|
|
@ -1,116 +1,72 @@
|
||||||
package ps
|
package proc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
// Stat is a direct translation of a `/proc/[pid]/stat` file as described in
|
||||||
#include <unistd.h>
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
// getClockTicks returns sysconf(SC_CLK_TCK).
|
|
||||||
func getClockTicks() int64 {
|
|
||||||
return int64(C.sysconf(C._SC_CLK_TCK))
|
|
||||||
}
|
|
||||||
|
|
||||||
// bootTime parses /proc/uptime returns the time.Time of system boot.
|
|
||||||
func getBootTime() (int64, error) {
|
|
||||||
f, err := os.Open("/proc/stat")
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
btimeStr := ""
|
|
||||||
scanner := bufio.NewScanner(f)
|
|
||||||
for scanner.Scan() {
|
|
||||||
fields := strings.Fields(scanner.Text())
|
|
||||||
if len(fields) < 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if fields[0] == "btime" {
|
|
||||||
btimeStr = fields[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(btimeStr) == 0 {
|
|
||||||
return 0, fmt.Errorf("couldn't extract boot time from /proc/stat")
|
|
||||||
}
|
|
||||||
|
|
||||||
btimeSec, err := strconv.ParseInt(btimeStr, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("error parsing boot time from /proc/stat: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return btimeSec, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// stat is a direct translation of a `/proc/[pid]/stat` file as described in
|
|
||||||
// the proc(5) manpage. Please note that it is not a full translation as not
|
// the proc(5) manpage. Please note that it is not a full translation as not
|
||||||
// all fields are in the scope of this library and higher indices are
|
// all fields are in the scope of this library and higher indices are
|
||||||
// Kernel-version dependent.
|
// Kernel-version dependent.
|
||||||
type stat struct {
|
type Stat struct {
|
||||||
// (1) The process ID
|
// (1) The process ID
|
||||||
pid string
|
Pid string
|
||||||
// (2) The filename of the executable, in parentheses. This is visible
|
// (2) The filename of the executable, in parentheses. This is visible
|
||||||
// whether or not the executable is swapped out.
|
// whether or not the executable is swapped out.
|
||||||
comm string
|
Comm string
|
||||||
// (3) The process state (e.g., running, sleeping, zombie, dead).
|
// (3) The process state (e.g., running, sleeping, zombie, dead).
|
||||||
// Refer to proc(5) for further deatils.
|
// Refer to proc(5) for further deatils.
|
||||||
state string
|
State string
|
||||||
// (4) The PID of the parent of this process.
|
// (4) The PID of the parent of this process.
|
||||||
ppid string
|
Ppid string
|
||||||
// (5) The process group ID of the process.
|
// (5) The process group ID of the process.
|
||||||
pgrp string
|
Pgrp string
|
||||||
// (6) The session ID of the process.
|
// (6) The session ID of the process.
|
||||||
session string
|
Session string
|
||||||
// (7) The controlling terminal of the process. (The minor device
|
// (7) The controlling terminal of the process. (The minor device
|
||||||
// number is contained in the combination of bits 31 to 20 and 7 to 0;
|
// number is contained in the combination of bits 31 to 20 and 7 to 0;
|
||||||
// the major device number is in bits 15 to 8.)
|
// the major device number is in bits 15 to 8.)
|
||||||
ttyNr string
|
TtyNr string
|
||||||
// (8) The ID of the foreground process group of the controlling
|
// (8) The ID of the foreground process group of the controlling
|
||||||
// terminal of the process.
|
// terminal of the process.
|
||||||
tpgid string
|
Tpgid string
|
||||||
// (9) The kernel flags word of the process. For bit meanings, see the
|
// (9) The kernel flags word of the process. For bit meanings, see the
|
||||||
// PF_* defines in the Linux kernel source file
|
// PF_* defines in the Linux kernel source file
|
||||||
// include/linux/sched.h. Details depend on the kernel version.
|
// include/linux/sched.h. Details depend on the kernel version.
|
||||||
flags string
|
Flags string
|
||||||
// (10) The number of minor faults the process has made which have not
|
// (10) The number of minor faults the process has made which have not
|
||||||
// required loading a memory page from disk.
|
// required loading a memory page from disk.
|
||||||
minflt string
|
Minflt string
|
||||||
// (11) The number of minor faults that the process's waited-for
|
// (11) The number of minor faults that the process's waited-for
|
||||||
// children have made.
|
// children have made.
|
||||||
cminflt string
|
Cminflt string
|
||||||
// (12) The number of major faults the process has made which have
|
// (12) The number of major faults the process has made which have
|
||||||
// required loading a memory page from disk.
|
// required loading a memory page from disk.
|
||||||
majflt string
|
Majflt string
|
||||||
// (13) The number of major faults that the process's waited-for
|
// (13) The number of major faults that the process's waited-for
|
||||||
// children have made.
|
// children have made.
|
||||||
cmajflt string
|
Cmajflt string
|
||||||
// (14) Amount of time that this process has been scheduled in user
|
// (14) Amount of time that this process has been scheduled in user
|
||||||
// mode, measured in clock ticks (divide by
|
// mode, measured in clock ticks (divide by
|
||||||
// sysconf(_SC_CLK_TCK)). This includes guest time, guest_time
|
// sysconf(_SC_CLK_TCK)). This includes guest time, guest_time
|
||||||
// (time spent running a virtual CPU, see below), so that applications
|
// (time spent running a virtual CPU, see below), so that applications
|
||||||
// that are not aware of the guest time field do not lose that time
|
// that are not aware of the guest time field do not lose that time
|
||||||
// from their calculations.
|
// from their calculations.
|
||||||
utime string
|
Utime string
|
||||||
// (15) Amount of time that this process has been scheduled in kernel
|
// (15) Amount of time that this process has been scheduled in kernel
|
||||||
// mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)).
|
// mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)).
|
||||||
stime string
|
Stime string
|
||||||
// (16) Amount of time that this process's waited-for children have
|
// (16) Amount of time that this process's waited-for children have
|
||||||
// been scheduled in user mode, measured in clock ticks (divide by
|
// been scheduled in user mode, measured in clock ticks (divide by
|
||||||
// sysconf(_SC_CLK_TCK)). (See also times(2).) This includes guest
|
// sysconf(_SC_CLK_TCK)). (See also times(2).) This includes guest
|
||||||
// time, cguest_time (time spent running a virtual CPU, see below).
|
// time, cguest_time (time spent running a virtual CPU, see below).
|
||||||
cutime string
|
Cutime string
|
||||||
// (17) Amount of time that this process's waited-for children have
|
// (17) Amount of time that this process's waited-for children have
|
||||||
// been scheduled in kernel mode, measured in clock ticks (divide by
|
// been scheduled in kernel mode, measured in clock ticks (divide by
|
||||||
// sysconf(_SC_CLK_TCK)).
|
// sysconf(_SC_CLK_TCK)).
|
||||||
cstime string
|
Cstime string
|
||||||
// (18) (Explanation for Linux 2.6+) For processes running a real-time
|
// (18) (Explanation for Linux 2.6+) For processes running a real-time
|
||||||
// scheduling policy (policy below; see sched_setscheduler(2)), this is
|
// scheduling policy (policy below; see sched_setscheduler(2)), this is
|
||||||
// the negated scheduling pri- ority, minus one; that is, a number
|
// the negated scheduling pri- ority, minus one; that is, a number
|
||||||
|
@ -120,43 +76,39 @@ type stat struct {
|
||||||
// in the kernel. The kernel stores nice values as numbers in the
|
// in the kernel. The kernel stores nice values as numbers in the
|
||||||
// range 0 (high) to 39 (low), corresponding to the user-visible nice
|
// range 0 (high) to 39 (low), corresponding to the user-visible nice
|
||||||
// range of -20 to 19.
|
// range of -20 to 19.
|
||||||
priority string
|
Priority string
|
||||||
// (19) The nice value (see setpriority(2)), a value in the range 19
|
// (19) The nice value (see setpriority(2)), a value in the range 19
|
||||||
// (low priority) to -20 (high priority).
|
// (low priority) to -20 (high priority).
|
||||||
nice string
|
Nice string
|
||||||
// (20) Number of threads in this process (since Linux 2.6). Before
|
// (20) Number of threads in this process (since Linux 2.6). Before
|
||||||
// kernel 2.6, this field was hard coded to 0 as a placeholder for an
|
// kernel 2.6, this field was hard coded to 0 as a placeholder for an
|
||||||
// earlier removed field.
|
// earlier removed field.
|
||||||
numThreads string
|
NumThreads string
|
||||||
// (21) The time in jiffies before the next SIGALRM is sent to the
|
// (21) The time in jiffies before the next SIGALRM is sent to the
|
||||||
// process due to an interval timer. Since kernel 2.6.17, this
|
// process due to an interval timer. Since kernel 2.6.17, this
|
||||||
// field is no longer maintained, and is hard coded as 0.
|
// field is no longer maintained, and is hard coded as 0.
|
||||||
itrealvalue string
|
Itrealvalue string
|
||||||
// (22) The time the process started after system boot. In kernels
|
// (22) The time the process started after system boot. In kernels
|
||||||
// before Linux 2.6, this value was expressed in jiffies. Since
|
// before Linux 2.6, this value was expressed in jiffies. Since
|
||||||
// Linux 2.6, the value is expressed in clock ticks (divide by
|
// Linux 2.6, the value is expressed in clock ticks (divide by
|
||||||
// sysconf(_SC_CLK_TCK)).
|
// sysconf(_SC_CLK_TCK)).
|
||||||
starttime string
|
Starttime string
|
||||||
// (23) Virtual memory size in bytes.
|
// (23) Virtual memory size in bytes.
|
||||||
vsize string
|
Vsize string
|
||||||
}
|
}
|
||||||
|
|
||||||
// readStat is used for mocking in unit tests.
|
// readStat is used for mocking in unit tests.
|
||||||
var readStat = func(path string) ([]string, error) {
|
var readStat = func(path string) ([]string, error) {
|
||||||
data, err := ioutil.ReadFile(path)
|
data, err := ioutil.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
|
||||||
err = errNoSuchPID
|
|
||||||
}
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.Fields(string(data)), nil
|
return strings.Fields(string(data)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseStat parses the /proc/$pid/stat file and returns a stat.
|
// ParseStat parses the /proc/$pid/stat file and returns a Stat.
|
||||||
func parseStat(path string) (*stat, error) {
|
func ParseStat(pid string) (*Stat, error) {
|
||||||
fields, err := readStat(path)
|
fields, err := readStat(fmt.Sprintf("/proc/%s/stat", pid))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -165,29 +117,29 @@ func parseStat(path string) (*stat, error) {
|
||||||
return fields[i-1]
|
return fields[i-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
return &stat{
|
return &Stat{
|
||||||
pid: fieldAt(1),
|
Pid: fieldAt(1),
|
||||||
comm: fieldAt(2),
|
Comm: fieldAt(2),
|
||||||
state: fieldAt(3),
|
State: fieldAt(3),
|
||||||
ppid: fieldAt(4),
|
Ppid: fieldAt(4),
|
||||||
pgrp: fieldAt(5),
|
Pgrp: fieldAt(5),
|
||||||
session: fieldAt(6),
|
Session: fieldAt(6),
|
||||||
ttyNr: fieldAt(7),
|
TtyNr: fieldAt(7),
|
||||||
tpgid: fieldAt(8),
|
Tpgid: fieldAt(8),
|
||||||
flags: fieldAt(9),
|
Flags: fieldAt(9),
|
||||||
minflt: fieldAt(10),
|
Minflt: fieldAt(10),
|
||||||
cminflt: fieldAt(11),
|
Cminflt: fieldAt(11),
|
||||||
majflt: fieldAt(12),
|
Majflt: fieldAt(12),
|
||||||
cmajflt: fieldAt(13),
|
Cmajflt: fieldAt(13),
|
||||||
utime: fieldAt(14),
|
Utime: fieldAt(14),
|
||||||
stime: fieldAt(15),
|
Stime: fieldAt(15),
|
||||||
cutime: fieldAt(16),
|
Cutime: fieldAt(16),
|
||||||
cstime: fieldAt(17),
|
Cstime: fieldAt(17),
|
||||||
priority: fieldAt(18),
|
Priority: fieldAt(18),
|
||||||
nice: fieldAt(19),
|
Nice: fieldAt(19),
|
||||||
numThreads: fieldAt(20),
|
NumThreads: fieldAt(20),
|
||||||
itrealvalue: fieldAt(21),
|
Itrealvalue: fieldAt(21),
|
||||||
starttime: fieldAt(22),
|
Starttime: fieldAt(22),
|
||||||
vsize: fieldAt(23),
|
Vsize: fieldAt(23),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package ps
|
package proc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
@ -9,164 +9,161 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// status is a direct translation of a `/proc/[pid]/status`, wich provides much
|
// Status is a direct translation of a `/proc/[pid]/status`, wich provides much
|
||||||
// of the information in /proc/[pid]/stat and /proc/[pid]/statm in a format
|
// of the information in /proc/[pid]/stat and /proc/[pid]/statm in a format
|
||||||
// that's easier for humans to parse.
|
// that's easier for humans to parse.
|
||||||
type status struct {
|
type Status struct {
|
||||||
// Name: Command run by this process.
|
// Name: Command run by this process.
|
||||||
name string
|
Name string
|
||||||
// Umask: Process umask, expressed in octal with a leading zero; see
|
// Umask: Process umask, expressed in octal with a leading zero; see
|
||||||
// umask(2). (Since Linux 4.7.)
|
// umask(2). (Since Linux 4.7.)
|
||||||
umask string
|
Umask string
|
||||||
// State: Current state of the process. One of "R (running)", "S
|
// State: Current state of the process. One of "R (running)", "S
|
||||||
// (sleeping)", "D (disk sleep)", "T (stopped)", "T (tracing stop)", "Z
|
// (sleeping)", "D (disk sleep)", "T (stopped)", "T (tracing stop)", "Z
|
||||||
// (zombie)", or "X (dead)".
|
// (zombie)", or "X (dead)".
|
||||||
state string
|
State string
|
||||||
// Tgid: Thread group ID (i.e., Process ID).
|
// Tgid: Thread group ID (i.e., Process ID).
|
||||||
tgid string
|
Tgid string
|
||||||
// Ngid: NUMA group ID (0 if none; since Linux 3.13).
|
// Ngid: NUMA group ID (0 if none; since Linux 3.13).
|
||||||
ngid string
|
Ngid string
|
||||||
// Pid: Thread ID (see gettid(2)).
|
// Pid: Thread ID (see gettid(2)).
|
||||||
pid string
|
Pid string
|
||||||
// PPid: PID of parent process.
|
// PPid: PID of parent process.
|
||||||
pPid string
|
PPid string
|
||||||
// TracerPid: PID of process tracing this process (0 if not being traced).
|
// TracerPid: PID of process tracing this process (0 if not being traced).
|
||||||
tracerPid string
|
TracerPid string
|
||||||
// Uids: Real, effective, saved set, and filesystem.
|
// Uids: Real, effective, saved set, and filesystem.
|
||||||
uids []string
|
Uids []string
|
||||||
// Gids: Real, effective, saved set, and filesystem.
|
// Gids: Real, effective, saved set, and filesystem.
|
||||||
gids []string
|
Gids []string
|
||||||
// FDSize: Number of file descriptor slots currently allocated.
|
// FDSize: Number of file descriptor slots currently allocated.
|
||||||
fdSize string
|
FdSize string
|
||||||
// Groups: Supplementary group list.
|
// Groups: Supplementary group list.
|
||||||
groups []string
|
Groups []string
|
||||||
// NStgid : Thread group ID (i.e., PID) in each of the PID namespaces
|
// NStgid : Thread group ID (i.e., PID) in each of the PID namespaces
|
||||||
// of which [pid] is a member. The leftmost entry shows the value
|
// of which [pid] is a member. The leftmost entry shows the value
|
||||||
// with respect to the PID namespace of the reading process, followed
|
// with respect to the PID namespace of the reading process, followed
|
||||||
// by the value in successively nested inner namespaces. (Since Linux
|
// by the value in successively nested inner namespaces. (Since Linux
|
||||||
// 4.1.)
|
// 4.1.)
|
||||||
nStgid string
|
NStgid string
|
||||||
// NSpid: Thread ID in each of the PID namespaces of which [pid] is a
|
// NSpid: Thread ID in each of the PID namespaces of which [pid] is a
|
||||||
// member. The fields are ordered as for NStgid. (Since Linux 4.1.)
|
// member. The fields are ordered as for NStgid. (Since Linux 4.1.)
|
||||||
nSpid string
|
NSpid []string
|
||||||
// NSpgid: Process group ID in each of the PID namespaces of which
|
// NSpgid: Process group ID in each of the PID namespaces of which
|
||||||
// [pid] is a member. The fields are ordered as for NStgid. (Since
|
// [pid] is a member. The fields are ordered as for NStgid. (Since
|
||||||
// Linux 4.1.)
|
// Linux 4.1.)
|
||||||
nSpgid string
|
NSpgid string
|
||||||
// NSsid: descendant namespace session ID hierarchy Session ID in
|
// NSsid: descendant namespace session ID hierarchy Session ID in
|
||||||
// each of the PID names- paces of which [pid] is a member. The fields
|
// each of the PID names- paces of which [pid] is a member. The fields
|
||||||
// are ordered as for NStgid. (Since Linux 4.1.)
|
// are ordered as for NStgid. (Since Linux 4.1.)
|
||||||
nSsid string
|
NSsid string
|
||||||
// VmPeak: Peak virtual memory size.
|
// VMPeak: Peak virtual memory size.
|
||||||
vmPeak string
|
VMPeak string
|
||||||
// VmSize: Virtual memory size.
|
// VMSize: Virtual memory size.
|
||||||
vmSize string
|
VMSize string
|
||||||
// VmLck: Locked memory size (see mlock(3)).
|
// VMLck: Locked memory size (see mlock(3)).
|
||||||
vmLCK string
|
VMLCK string
|
||||||
// VmPin: Pinned memory size (since Linux 3.2). These are pages
|
// VMPin: Pinned memory size (since Linux 3.2). These are pages
|
||||||
// that can't be moved because something needs to directly access
|
// that can't be moved because something needs to directly access
|
||||||
// physical memory.
|
// physical memory.
|
||||||
vmPin string
|
VMPin string
|
||||||
// VmHWM: Peak resident set size ("high water mark").
|
// VMHWM: Peak resident set size ("high water mark").
|
||||||
vmHWM string
|
VMHWM string
|
||||||
// VmRSS: Resident set size. Note that the value here is the sum of
|
// VMRSS: Resident set size. Note that the value here is the sum of
|
||||||
// RssAnon, RssFile, and RssShmem.
|
// RssAnon, RssFile, and RssShmem.
|
||||||
vmRSS string
|
VMRSS string
|
||||||
// RssAnon: Size of resident anonymous memory. (since Linux 4.5).
|
// RssAnon: Size of resident anonymous memory. (since Linux 4.5).
|
||||||
rssAnon string
|
RssAnon string
|
||||||
// RssFile: Size of resident file mappings. (since Linux 4.5).
|
// RssFile: Size of resident file mappings. (since Linux 4.5).
|
||||||
rssFile string
|
RssFile string
|
||||||
// RssShmem: Size of resident shared memory (includes System V
|
// RssShmem: Size of resident shared memory (includes System V
|
||||||
// shared memory, mappings from tmpfs(5), and shared anonymous
|
// shared memory, mappings from tmpfs(5), and shared anonymous
|
||||||
// mappings). (since Linux 4.5).
|
// mappings). (since Linux 4.5).
|
||||||
rssShmem string
|
RssShmem string
|
||||||
// VmData: Size of data segment.
|
// VMData: Size of data segment.
|
||||||
vmData string
|
VMData string
|
||||||
// VmStk: Size of stack segment.
|
// VMStk: Size of stack segment.
|
||||||
vmStk string
|
VMStk string
|
||||||
// VmExe: Size of text segment.
|
// VMExe: Size of text segment.
|
||||||
vmExe string
|
VMExe string
|
||||||
// VmLib: Shared library code size.
|
// VMLib: Shared library code size.
|
||||||
vmLib string
|
VMLib string
|
||||||
// VmPTE: Page table entries size (since Linux 2.6.10).
|
// VMPTE: Page table entries size (since Linux 2.6.10).
|
||||||
vmPTE string
|
VMPTE string
|
||||||
// VmPMD: Size of second-level page tables (since Linux 4.0).
|
// VMPMD: Size of second-level page tables (since Linux 4.0).
|
||||||
vmPMD string
|
VMPMD string
|
||||||
// VmSwap: Swapped-out virtual memory size by anonymous private pages;
|
// VMSwap: Swapped-out virtual memory size by anonymous private pages;
|
||||||
// shmem swap usage is not included (since Linux 2.6.34).
|
// shmem swap usage is not included (since Linux 2.6.34).
|
||||||
vmSwap string
|
VMSwap string
|
||||||
// HugetlbPages: Size of hugetlb memory portions. (since Linux 4.4).
|
// HugetlbPages: Size of hugetlb memory portions. (since Linux 4.4).
|
||||||
hugetlbPages string
|
HugetlbPages string
|
||||||
// Threads: Number of threads in process containing this thread.
|
// Threads: Number of threads in process containing this thread.
|
||||||
threads string
|
Threads string
|
||||||
// SigQ: This field contains two slash-separated numbers that relate to
|
// SigQ: This field contains two slash-separated numbers that relate to
|
||||||
// queued signals for the real user ID of this process. The first of
|
// queued signals for the real user ID of this process. The first of
|
||||||
// these is the number of currently queued signals for this real
|
// these is the number of currently queued signals for this real
|
||||||
// user ID, and the second is the resource limit on the number of
|
// user ID, and the second is the resource limit on the number of
|
||||||
// queued signals for this process (see the description of
|
// queued signals for this process (see the description of
|
||||||
// RLIMIT_SIGPENDING in getr- limit(2)).
|
// RLIMIT_SIGPENDING in getr- limit(2)).
|
||||||
sigQ string
|
SigQ string
|
||||||
// SigPnd: Number of signals pending for thread and for (see pthreads(7)).
|
// SigPnd: Number of signals pending for thread and for (see pthreads(7)).
|
||||||
sigPnd string
|
SigPnd string
|
||||||
// ShdPnd: Number of signals pending for process as a whole (see
|
// ShdPnd: Number of signals pending for process as a whole (see
|
||||||
// signal(7)).
|
// signal(7)).
|
||||||
shdPnd string
|
ShdPnd string
|
||||||
// SigBlk: Mask indicating signals being blocked (see signal(7)).
|
// SigBlk: Mask indicating signals being blocked (see signal(7)).
|
||||||
sigBlk string
|
SigBlk string
|
||||||
// SigIgn: Mask indicating signals being ignored (see signal(7)).
|
// SigIgn: Mask indicating signals being ignored (see signal(7)).
|
||||||
sigIgn string
|
SigIgn string
|
||||||
// SigCgt: Mask indicating signals being blocked caught (see signal(7)).
|
// SigCgt: Mask indicating signals being blocked caught (see signal(7)).
|
||||||
sigCgt string
|
SigCgt string
|
||||||
// CapInh: Mask of capabilities enabled in inheritable sets (see
|
// CapInh: Mask of capabilities enabled in inheritable sets (see
|
||||||
// capabilities(7)).
|
// capabilities(7)).
|
||||||
capInh string
|
CapInh string
|
||||||
// CapPrm: Mask of capabilities enabled in permitted sets (see
|
// CapPrm: Mask of capabilities enabled in permitted sets (see
|
||||||
// capabilities(7)).
|
// capabilities(7)).
|
||||||
capPrm string
|
CapPrm string
|
||||||
// CapEff: Mask of capabilities enabled in effective sets (see
|
// CapEff: Mask of capabilities enabled in effective sets (see
|
||||||
// capabilities(7)).
|
// capabilities(7)).
|
||||||
capEff string
|
CapEff string
|
||||||
// CapBnd: Capability Bounding set (since Linux 2.6.26, see
|
// CapBnd: Capability Bounding set (since Linux 2.6.26, see
|
||||||
// capabilities(7)).
|
// capabilities(7)).
|
||||||
capBnd string
|
CapBnd string
|
||||||
// CapAmb: Ambient capability set (since Linux 4.3, see capabilities(7)).
|
// CapAmb: Ambient capability set (since Linux 4.3, see capabilities(7)).
|
||||||
capAmb string
|
CapAmb string
|
||||||
// NoNewPrivs: Value of the no_new_privs bit (since Linux 4.10, see
|
// NoNewPrivs: Value of the no_new_privs bit (since Linux 4.10, see
|
||||||
// prctl(2)).
|
// prctl(2)).
|
||||||
noNewPrivs string
|
NoNewPrivs string
|
||||||
// Seccomp: Seccomp mode of the process (since Linux 3.8, see
|
// Seccomp: Seccomp mode of the process (since Linux 3.8, see
|
||||||
// seccomp(2)). 0 means SEC- COMP_MODE_DISABLED; 1 means
|
// seccomp(2)). 0 means SEC- COMP_MODE_DISABLED; 1 means
|
||||||
// SECCOMP_MODE_STRICT; 2 means SECCOMP_MODE_FILTER. This field is
|
// SECCOMP_MODE_STRICT; 2 means SECCOMP_MODE_FILTER. This field is
|
||||||
// provided only if the kernel was built with the CONFIG_SECCOMP kernel
|
// provided only if the kernel was built with the CONFIG_SECCOMP kernel
|
||||||
// configu- ration option enabled.
|
// configu- ration option enabled.
|
||||||
seccomp string
|
Seccomp string
|
||||||
// Cpus_allowed: Mask of CPUs on which this process may run
|
// Cpus_allowed: Mask of CPUs on which this process may run
|
||||||
// (since Linux 2.6.24, see cpuset(7)).
|
// (since Linux 2.6.24, see cpuset(7)).
|
||||||
cpusAllowed string
|
CpusAllowed string
|
||||||
// Cpus_allowed_list: Same as previous, but in "list format" (since
|
// Cpus_allowed_list: Same as previous, but in "list format" (since
|
||||||
// Linux 2.6.26, see cpuset(7)).
|
// Linux 2.6.26, see cpuset(7)).
|
||||||
cpusAllowedList string
|
CpusAllowedList string
|
||||||
// Mems_allowed: Mask of memory nodes allowed to this process
|
// Mems_allowed: Mask of memory nodes allowed to this process
|
||||||
// (since Linux 2.6.24, see cpuset(7)).
|
// (since Linux 2.6.24, see cpuset(7)).
|
||||||
memsAllowed string
|
MemsAllowed string
|
||||||
// Mems_allowed_list: Same as previous, but in "list format" (since
|
// Mems_allowed_list: Same as previous, but in "list format" (since
|
||||||
// Linux 2.6.26, see cpuset(7)).
|
// Linux 2.6.26, see cpuset(7)).
|
||||||
memsAllowedList string
|
MemsAllowedList string
|
||||||
// voluntaryCtxtSwitches: Number of voluntary context switches
|
// voluntaryCtxtSwitches: Number of voluntary context switches
|
||||||
// (since Linux 2.6.23).
|
// (since Linux 2.6.23).
|
||||||
voluntaryCtxtSwitches string
|
VoluntaryCtxtSwitches string
|
||||||
// nonvoluntaryCtxtSwitches: Number of involuntary context switches
|
// nonvoluntaryCtxtSwitches: Number of involuntary context switches
|
||||||
// (since Linux 2.6.23).
|
// (since Linux 2.6.23).
|
||||||
nonvoluntaryCtxtSwitches string
|
NonvoluntaryCtxtSwitches string
|
||||||
}
|
}
|
||||||
|
|
||||||
// readStatus is used for mocking in unit tests.
|
// readStatus is used for mocking in unit tests.
|
||||||
var readStatus = func(path string) ([]string, error) {
|
var readStatus = func(path string) ([]string, error) {
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
|
||||||
err = errNoSuchPID
|
|
||||||
}
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
lines := []string{}
|
lines := []string{}
|
||||||
|
@ -177,14 +174,15 @@ var readStatus = func(path string) ([]string, error) {
|
||||||
return lines, nil
|
return lines, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseStatus parses the /proc/$pid/status file and returns a *status.
|
// ParseStatus parses the /proc/$pid/status file and returns a *Status.
|
||||||
func parseStatus(path string) (*status, error) {
|
func ParseStatus(pid string) (*Status, error) {
|
||||||
|
path := fmt.Sprintf("/proc/%s/status", pid)
|
||||||
lines, err := readStatus(path)
|
lines, err := readStatus(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s := status{}
|
s := Status{}
|
||||||
errUnexpectedInput := errors.New(fmt.Sprintf("unexpected input from %s", path))
|
errUnexpectedInput := errors.New(fmt.Sprintf("unexpected input from %s", path))
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
fields := strings.Fields(line)
|
fields := strings.Fields(line)
|
||||||
|
@ -194,119 +192,117 @@ func parseStatus(path string) (*status, error) {
|
||||||
|
|
||||||
switch fields[0] {
|
switch fields[0] {
|
||||||
case "Name:":
|
case "Name:":
|
||||||
s.name = fields[1]
|
s.Name = fields[1]
|
||||||
case "Umask:":
|
case "Umask:":
|
||||||
s.umask = fields[1]
|
s.Umask = fields[1]
|
||||||
case "State:":
|
case "State:":
|
||||||
s.state = fields[1]
|
s.State = fields[1]
|
||||||
case "Tgid:":
|
case "Tgid:":
|
||||||
s.tgid = fields[1]
|
s.Tgid = fields[1]
|
||||||
case "Ngid:":
|
case "Ngid:":
|
||||||
s.ngid = fields[1]
|
s.Ngid = fields[1]
|
||||||
case "Pid:":
|
case "Pid:":
|
||||||
s.pid = fields[1]
|
s.Pid = fields[1]
|
||||||
case "PPid:":
|
case "PPid:":
|
||||||
s.pPid = fields[1]
|
s.PPid = fields[1]
|
||||||
case "TracerPid:":
|
case "TracerPid:":
|
||||||
s.tracerPid = fields[1]
|
s.TracerPid = fields[1]
|
||||||
case "Uid:":
|
case "Uid:":
|
||||||
if len(fields) != 5 {
|
if len(fields) != 5 {
|
||||||
return nil, errors.Wrap(errUnexpectedInput, line)
|
return nil, errors.Wrap(errUnexpectedInput, line)
|
||||||
}
|
}
|
||||||
s.uids = []string{fields[1], fields[2], fields[3], fields[4]}
|
s.Uids = []string{fields[1], fields[2], fields[3], fields[4]}
|
||||||
case "Gid:":
|
case "Gid:":
|
||||||
if len(fields) != 5 {
|
if len(fields) != 5 {
|
||||||
return nil, errors.Wrap(errUnexpectedInput, line)
|
return nil, errors.Wrap(errUnexpectedInput, line)
|
||||||
}
|
}
|
||||||
s.gids = []string{fields[1], fields[2], fields[3], fields[4]}
|
s.Gids = []string{fields[1], fields[2], fields[3], fields[4]}
|
||||||
case "FDSize:":
|
case "FDSize:":
|
||||||
s.fdSize = fields[1]
|
s.FdSize = fields[1]
|
||||||
case "Groups:":
|
case "Groups:":
|
||||||
for _, g := range fields[1:] {
|
s.Groups = fields[1:]
|
||||||
s.groups = append(s.groups, g)
|
|
||||||
}
|
|
||||||
case "NStgid:":
|
case "NStgid:":
|
||||||
s.nStgid = fields[1]
|
s.NStgid = fields[1]
|
||||||
case "NSpid:":
|
case "NSpid:":
|
||||||
s.nSpid = fields[1]
|
s.NSpid = fields[1:]
|
||||||
case "NSpgid:":
|
case "NSpgid:":
|
||||||
s.nSpgid = fields[1]
|
s.NSpgid = fields[1]
|
||||||
case "NSsid:":
|
case "NSsid:":
|
||||||
s.nSsid = fields[1]
|
s.NSsid = fields[1]
|
||||||
case "VmPeak:":
|
case "VmPeak:":
|
||||||
s.vmPeak = fields[1]
|
s.VMPeak = fields[1]
|
||||||
case "VmSize:":
|
case "VmSize:":
|
||||||
s.vmSize = fields[1]
|
s.VMSize = fields[1]
|
||||||
case "VmLck:":
|
case "VmLck:":
|
||||||
s.vmLCK = fields[1]
|
s.VMLCK = fields[1]
|
||||||
case "VmPin:":
|
case "VmPin:":
|
||||||
s.vmPin = fields[1]
|
s.VMPin = fields[1]
|
||||||
case "VmHWM:":
|
case "VmHWM:":
|
||||||
s.vmHWM = fields[1]
|
s.VMHWM = fields[1]
|
||||||
case "VmRSS:":
|
case "VmRSS:":
|
||||||
s.vmRSS = fields[1]
|
s.VMRSS = fields[1]
|
||||||
case "RssAnon:":
|
case "RssAnon:":
|
||||||
s.rssAnon = fields[1]
|
s.RssAnon = fields[1]
|
||||||
case "RssFile:":
|
case "RssFile:":
|
||||||
s.rssFile = fields[1]
|
s.RssFile = fields[1]
|
||||||
case "RssShmem:":
|
case "RssShmem:":
|
||||||
s.rssShmem = fields[1]
|
s.RssShmem = fields[1]
|
||||||
case "VmData:":
|
case "VmData:":
|
||||||
s.vmData = fields[1]
|
s.VMData = fields[1]
|
||||||
case "VmStk:":
|
case "VmStk:":
|
||||||
s.vmStk = fields[1]
|
s.VMStk = fields[1]
|
||||||
case "VmExe:":
|
case "VmExe:":
|
||||||
s.vmExe = fields[1]
|
s.VMExe = fields[1]
|
||||||
case "VmLib:":
|
case "VmLib:":
|
||||||
s.vmLib = fields[1]
|
s.VMLib = fields[1]
|
||||||
case "VmPTE:":
|
case "VmPTE:":
|
||||||
s.vmPTE = fields[1]
|
s.VMPTE = fields[1]
|
||||||
case "VmPMD:":
|
case "VmPMD:":
|
||||||
s.vmPMD = fields[1]
|
s.VMPMD = fields[1]
|
||||||
case "VmSwap:":
|
case "VmSwap:":
|
||||||
s.vmSwap = fields[1]
|
s.VMSwap = fields[1]
|
||||||
case "HugetlbPages:":
|
case "HugetlbPages:":
|
||||||
s.hugetlbPages = fields[1]
|
s.HugetlbPages = fields[1]
|
||||||
case "Threads:":
|
case "Threads:":
|
||||||
s.threads = fields[1]
|
s.Threads = fields[1]
|
||||||
case "SigQ:":
|
case "SigQ:":
|
||||||
s.sigQ = fields[1]
|
s.SigQ = fields[1]
|
||||||
case "SigPnd:":
|
case "SigPnd:":
|
||||||
s.sigPnd = fields[1]
|
s.SigPnd = fields[1]
|
||||||
case "ShdPnd:":
|
case "ShdPnd:":
|
||||||
s.shdPnd = fields[1]
|
s.ShdPnd = fields[1]
|
||||||
case "SigBlk:":
|
case "SigBlk:":
|
||||||
s.sigBlk = fields[1]
|
s.SigBlk = fields[1]
|
||||||
case "SigIgn:":
|
case "SigIgn:":
|
||||||
s.sigIgn = fields[1]
|
s.SigIgn = fields[1]
|
||||||
case "SigCgt:":
|
case "SigCgt:":
|
||||||
s.sigCgt = fields[1]
|
s.SigCgt = fields[1]
|
||||||
case "CapInh:":
|
case "CapInh:":
|
||||||
s.capInh = fields[1]
|
s.CapInh = fields[1]
|
||||||
case "CapPrm:":
|
case "CapPrm:":
|
||||||
s.capPrm = fields[1]
|
s.CapPrm = fields[1]
|
||||||
case "CapEff:":
|
case "CapEff:":
|
||||||
s.capEff = fields[1]
|
s.CapEff = fields[1]
|
||||||
case "CapBnd:":
|
case "CapBnd:":
|
||||||
s.capBnd = fields[1]
|
s.CapBnd = fields[1]
|
||||||
case "CapAmb:":
|
case "CapAmb:":
|
||||||
s.capAmb = fields[1]
|
s.CapAmb = fields[1]
|
||||||
case "NoNewPrivs:":
|
case "NoNewPrivs:":
|
||||||
s.noNewPrivs = fields[1]
|
s.NoNewPrivs = fields[1]
|
||||||
case "Seccomp:":
|
case "Seccomp:":
|
||||||
s.seccomp = fields[1]
|
s.Seccomp = fields[1]
|
||||||
case "Cpus_allowed:":
|
case "Cpus_allowed:":
|
||||||
s.cpusAllowed = fields[1]
|
s.CpusAllowed = fields[1]
|
||||||
case "Cpus_allowed_list:":
|
case "Cpus_allowed_list:":
|
||||||
s.cpusAllowedList = fields[1]
|
s.CpusAllowedList = fields[1]
|
||||||
case "Mems_allowed:":
|
case "Mems_allowed:":
|
||||||
s.memsAllowed = fields[1]
|
s.MemsAllowed = fields[1]
|
||||||
case "Mems_allowed_list:":
|
case "Mems_allowed_list:":
|
||||||
s.memsAllowedList = fields[1]
|
s.MemsAllowedList = fields[1]
|
||||||
case "voluntary_ctxt_switches:":
|
case "voluntary_ctxt_switches:":
|
||||||
s.voluntaryCtxtSwitches = fields[1]
|
s.VoluntaryCtxtSwitches = fields[1]
|
||||||
case "nonvoluntary_ctxt_switches:":
|
case "nonvoluntary_ctxt_switches:":
|
||||||
s.nonvoluntaryCtxtSwitches = fields[1]
|
s.NonvoluntaryCtxtSwitches = fields[1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,205 @@
|
||||||
|
package process
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containers/psgo/internal/host"
|
||||||
|
"github.com/containers/psgo/internal/proc"
|
||||||
|
"github.com/opencontainers/runc/libcontainer/user"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Process includes process-related from the /proc FS.
|
||||||
|
type Process struct {
|
||||||
|
// PID is the process ID.
|
||||||
|
Pid string
|
||||||
|
// Stat contains data from /proc/$pid/stat.
|
||||||
|
Stat proc.Stat
|
||||||
|
// Status containes data from /proc/$pid/status.
|
||||||
|
Status proc.Status
|
||||||
|
// CmdLine containes data from /proc/$pid/cmdline.
|
||||||
|
CmdLine []string
|
||||||
|
// Label containers data from /proc/$pid/attr/current.
|
||||||
|
Label string
|
||||||
|
// PidNS contains data from /proc/$pid/ns/pid.
|
||||||
|
PidNS string
|
||||||
|
// Huser is the effective host user of a container process.
|
||||||
|
Huser string
|
||||||
|
// Hgroup is the effective host group of a container process.
|
||||||
|
Hgroup string
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupGID returns the textual group ID, if it can be optained, or the
|
||||||
|
// decimal representation otherwise.
|
||||||
|
func LookupGID(gid string) (string, error) {
|
||||||
|
gidNum, err := strconv.Atoi(gid)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "error parsing group ID")
|
||||||
|
}
|
||||||
|
g, err := user.LookupGid(gidNum)
|
||||||
|
if err != nil {
|
||||||
|
return gid, nil
|
||||||
|
}
|
||||||
|
return g.Name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupUID return the textual user ID, if it can be optained, or the decimal
|
||||||
|
// representation otherwise.
|
||||||
|
func LookupUID(uid string) (string, error) {
|
||||||
|
uidNum, err := strconv.Atoi(uid)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "error parsing user ID")
|
||||||
|
}
|
||||||
|
u, err := user.LookupUid(uidNum)
|
||||||
|
if err != nil {
|
||||||
|
return uid, nil
|
||||||
|
}
|
||||||
|
return u.Name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new Process with the specified pid and parses the relevant
|
||||||
|
// data from /proc and /dev.
|
||||||
|
func New(pid string) (*Process, error) {
|
||||||
|
p := Process{Pid: pid}
|
||||||
|
|
||||||
|
if err := p.parseStat(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := p.parseStatus(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := p.parseCmdLine(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := p.parsePIDNamespace(); err != nil {
|
||||||
|
// Ignore permission errors as those occur for some pids when
|
||||||
|
// the caller has limited permissions.
|
||||||
|
if !os.IsPermission(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := p.parseLabel(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromPIDs creates a new Process for each pid.
|
||||||
|
func FromPIDs(pids []string) ([]*Process, error) {
|
||||||
|
processes := []*Process{}
|
||||||
|
for _, pid := range pids {
|
||||||
|
p, err := New(pid)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
// proc parsing is racy
|
||||||
|
// Let's ignore "does not exist" errors
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
processes = append(processes, p)
|
||||||
|
}
|
||||||
|
return processes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseStat parses /proc/$pid/stat.
|
||||||
|
func (p *Process) parseStat() error {
|
||||||
|
s, err := proc.ParseStat(p.Pid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.Stat = *s
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseStatus parses /proc/$pid/status.
|
||||||
|
func (p *Process) parseStatus() error {
|
||||||
|
s, err := proc.ParseStatus(p.Pid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.Status = *s
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseCmdLine parses /proc/$pid/cmdline.
|
||||||
|
func (p *Process) parseCmdLine() error {
|
||||||
|
s, err := proc.ParseCmdLine(p.Pid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.CmdLine = s
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parsePIDNamespace parses all host-related data fields.
|
||||||
|
func (p *Process) parsePIDNamespace() error {
|
||||||
|
pidNS, err := proc.ParsePIDNamespace(p.Pid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.PidNS = pidNS
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseLabel parses the security label.
|
||||||
|
func (p *Process) parseLabel() error {
|
||||||
|
label, err := proc.ParseAttrCurrent(p.Pid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.Label = label
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHostData sets all host-related data fields.
|
||||||
|
func (p *Process) SetHostData() error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
p.Huser, err = LookupUID(p.Status.Uids[1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Hgroup, err = LookupGID(p.Status.Gids[1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ElapsedTime returns the time.Duration since process p was created.
|
||||||
|
func (p *Process) ElapsedTime() (time.Duration, error) {
|
||||||
|
sinceBoot, err := strconv.ParseInt(p.Stat.Starttime, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sinceBoot = sinceBoot / host.ClockTicks()
|
||||||
|
|
||||||
|
bootTime, err := host.BootTime()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
created := time.Unix(sinceBoot+bootTime, 0)
|
||||||
|
return (time.Now()).Sub(created), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CPUTime returns the cumlative CPU time of process p as a time.Duration.
|
||||||
|
func (p *Process) CPUTime() (time.Duration, error) {
|
||||||
|
user, err := strconv.ParseInt(p.Stat.Utime, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
system, err := strconv.ParseInt(p.Stat.Stime, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
secs := (user + system) / host.ClockTicks()
|
||||||
|
cpu := time.Unix(secs, 0)
|
||||||
|
return cpu.Sub(time.Unix(0, 0)), nil
|
||||||
|
}
|
|
@ -1,34 +0,0 @@
|
||||||
package ps
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// readCmdline can be used for mocking in unit tests.
|
|
||||||
func readCmdline(path string) (string, error) {
|
|
||||||
data, err := ioutil.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
err = errNoSuchPID
|
|
||||||
}
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(data), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseCmdline parses a /proc/$pid/cmdline file and returns a string slice.
|
|
||||||
func parseCmdline(path string) ([]string, error) {
|
|
||||||
raw, err := readCmdline(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmdLine := []string{}
|
|
||||||
for _, rawCmd := range bytes.Split([]byte(raw), []byte{0}) {
|
|
||||||
cmdLine = append(cmdLine, string(rawCmd))
|
|
||||||
}
|
|
||||||
return cmdLine, nil
|
|
||||||
}
|
|
560
vendor/github.com/containers/psgo/ps/ps.go → vendor/github.com/containers/psgo/psgo.go
generated
vendored
560
vendor/github.com/containers/psgo/ps/ps.go → vendor/github.com/containers/psgo/psgo.go
generated
vendored
|
@ -1,4 +1,4 @@
|
||||||
// Package ps is a ps (1) AIX-format compatible golang library extended with
|
// Package psgo is a ps (1) AIX-format compatible golang library extended with
|
||||||
// various descriptors useful for displaying container-related data.
|
// various descriptors useful for displaying container-related data.
|
||||||
//
|
//
|
||||||
// The idea behind the library is to provide an easy to use way of extracting
|
// The idea behind the library is to provide an easy to use way of extracting
|
||||||
|
@ -10,46 +10,88 @@
|
||||||
//
|
//
|
||||||
// Please visit https://github.com/containers/psgo for further details about
|
// Please visit https://github.com/containers/psgo for further details about
|
||||||
// supported format descriptors and to see some usage examples.
|
// supported format descriptors and to see some usage examples.
|
||||||
package ps
|
package psgo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/opencontainers/runc/libcontainer/user"
|
"github.com/containers/psgo/internal/capabilities"
|
||||||
|
"github.com/containers/psgo/internal/dev"
|
||||||
|
"github.com/containers/psgo/internal/proc"
|
||||||
|
"github.com/containers/psgo/internal/process"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultFormat is the `ps -ef` compatible default format.
|
// processFunc is used to map a given aixFormatDescriptor to a corresponding
|
||||||
const DefaultFormat = "user,pid,ppid,pcpu,etime,tty,time,comm"
|
// function extracting the desired data from a process.
|
||||||
|
type processFunc func(*process.Process) (string, error)
|
||||||
|
|
||||||
|
// aixFormatDescriptor as mentioned in the ps(1) manpage. A given descriptor
|
||||||
|
// can either be specified via its code (e.g., "%C") or its normal representation
|
||||||
|
// (e.g., "pcpu") and will be printed under its corresponding header (e.g, "%CPU").
|
||||||
|
type aixFormatDescriptor struct {
|
||||||
|
// code descriptor in the short form (e.g., "%C").
|
||||||
|
code string
|
||||||
|
// normal descriptor in the long form (e.g., "pcpu").
|
||||||
|
normal string
|
||||||
|
// header of the descriptor (e.g., "%CPU").
|
||||||
|
header string
|
||||||
|
// onHost controls if data of the corresponding host processes will be
|
||||||
|
// extracted as well.
|
||||||
|
onHost bool
|
||||||
|
// procFN points to the corresponding method to etract the desired data.
|
||||||
|
procFn processFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// translateDescriptors parses the descriptors and returns a correspodning slice of
|
||||||
|
// aixFormatDescriptors. Descriptors can be specified in the normal and in the
|
||||||
|
// code form (if supported). If the descriptors slice is empty, the
|
||||||
|
// `DefaultDescriptors` is used.
|
||||||
|
func translateDescriptors(descriptors []string) ([]aixFormatDescriptor, error) {
|
||||||
|
if len(descriptors) == 0 {
|
||||||
|
descriptors = DefaultDescriptors
|
||||||
|
}
|
||||||
|
|
||||||
|
formatDescriptors := []aixFormatDescriptor{}
|
||||||
|
for _, d := range descriptors {
|
||||||
|
d = strings.TrimSpace(d)
|
||||||
|
found := false
|
||||||
|
for _, aix := range aixFormatDescriptors {
|
||||||
|
if d == aix.code || d == aix.normal {
|
||||||
|
formatDescriptors = append(formatDescriptors, aix)
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return nil, errors.Wrapf(ErrUnkownDescriptor, "'%s'", d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatDescriptors, nil
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// DefaultDescriptors is the `ps -ef` compatible default format.
|
||||||
|
DefaultDescriptors = []string{"user", "pid", "ppid", "pcpu", "etime", "tty", "time", "comm"}
|
||||||
|
|
||||||
// ErrUnkownDescriptor is returned when an unknown descriptor is parsed.
|
// ErrUnkownDescriptor is returned when an unknown descriptor is parsed.
|
||||||
ErrUnkownDescriptor = errors.New("unknown descriptor")
|
ErrUnkownDescriptor = errors.New("unknown descriptor")
|
||||||
|
|
||||||
// errNoSuchPID is returned when `/proc/PID` does not exist (anymore).
|
// hostProcesses are the processes on the host. It should only be used
|
||||||
errNoSuchPID = errors.New("PID does not exist in /proc")
|
// in the context of containers and is meant to display data of the
|
||||||
|
// container processes from the host's (i.e., calling process) view.
|
||||||
|
// Currently, all host processes contain only the required data from
|
||||||
|
// /proc/$pid/status.
|
||||||
|
hostProcesses []*process.Process
|
||||||
|
|
||||||
// bootTime holds the host's boot time. Singleton to safe some time and
|
aixFormatDescriptors = []aixFormatDescriptor{
|
||||||
// energy.
|
|
||||||
bootTime int64
|
|
||||||
|
|
||||||
// clockTicks is the value of sysconf(SC_CLK_TCK)
|
|
||||||
clockTicks = getClockTicks()
|
|
||||||
|
|
||||||
// ttyDevices is a slice of ttys. Singledton to safe some time and
|
|
||||||
// energy.
|
|
||||||
ttyDevices []*tty
|
|
||||||
|
|
||||||
descriptors = []aixFormatDescriptor{
|
|
||||||
{
|
{
|
||||||
code: "%C",
|
code: "%C",
|
||||||
normal: "pcpu",
|
normal: "pcpu",
|
||||||
|
@ -170,165 +212,36 @@ var (
|
||||||
header: "LABEL",
|
header: "LABEL",
|
||||||
procFn: processLABEL,
|
procFn: processLABEL,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
normal: "hpid",
|
||||||
|
header: "HPID",
|
||||||
|
onHost: true,
|
||||||
|
procFn: processHPID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
normal: "huser",
|
||||||
|
header: "HUSER",
|
||||||
|
onHost: true,
|
||||||
|
procFn: processHUSER,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
normal: "hgroup",
|
||||||
|
header: "HGROUP",
|
||||||
|
onHost: true,
|
||||||
|
procFn: processHGROUP,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
normal: "state",
|
||||||
|
header: "STATE",
|
||||||
|
procFn: processState,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// process includes a process ID and the corresponding data from /proc/pid/stat,
|
|
||||||
// /proc/pid/status and from /prod/pid/cmdline.
|
|
||||||
type process struct {
|
|
||||||
pid int
|
|
||||||
pstat *stat
|
|
||||||
pstatus *status
|
|
||||||
cmdline []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// elapsedTime returns the time.Duration since process p was created.
|
|
||||||
func (p *process) elapsedTime() (time.Duration, error) {
|
|
||||||
sinceBoot, err := strconv.ParseInt(p.pstat.starttime, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
sinceBoot = sinceBoot / clockTicks
|
|
||||||
|
|
||||||
if bootTime == 0 {
|
|
||||||
bootTime, err = getBootTime()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
created := time.Unix(sinceBoot+bootTime, 0)
|
|
||||||
return (time.Now()).Sub(created), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cpuTime returns the cumlative CPU time of process p as a time.Duration.
|
|
||||||
func (p *process) cpuTime() (time.Duration, error) {
|
|
||||||
user, err := strconv.ParseInt(p.pstat.utime, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
system, err := strconv.ParseInt(p.pstat.stime, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
secs := (user + system) / clockTicks
|
|
||||||
cpu := time.Unix(secs, 0)
|
|
||||||
return cpu.Sub(time.Unix(0, 0)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// processes returns a process slice of processes mentioned in /proc.
|
|
||||||
func processes() ([]*process, error) {
|
|
||||||
pids, err := getPIDs()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
processes := []*process{}
|
|
||||||
for _, pid := range pids {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
p process
|
|
||||||
)
|
|
||||||
p.pid = pid
|
|
||||||
p.pstat, err = parseStat(fmt.Sprintf("/proc/%d/stat", pid))
|
|
||||||
if err != nil {
|
|
||||||
if err == errNoSuchPID {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p.pstatus, err = parseStatus(fmt.Sprintf("/proc/%d/status", pid))
|
|
||||||
if err != nil {
|
|
||||||
if err == errNoSuchPID {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p.cmdline, err = parseCmdline(fmt.Sprintf("/proc/%d/cmdline", pid))
|
|
||||||
if err != nil {
|
|
||||||
if err == errNoSuchPID {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
processes = append(processes, &p)
|
|
||||||
}
|
|
||||||
|
|
||||||
return processes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPIDs extracts and returns all PIDs from /proc.
|
|
||||||
func getPIDs() ([]int, error) {
|
|
||||||
procDir, err := os.Open("/proc/")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer procDir.Close()
|
|
||||||
|
|
||||||
// extract string slice of all directories in procDir
|
|
||||||
pidDirs, err := procDir.Readdirnames(0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert pidDirs to int
|
|
||||||
pids := []int{}
|
|
||||||
for _, pidDir := range pidDirs {
|
|
||||||
pid, err := strconv.Atoi(pidDir)
|
|
||||||
if err != nil {
|
|
||||||
// skip non-numerical entries (e.g., `/proc/softirqs`)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pids = append(pids, pid)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pids, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// processFunc is used to map a given aixFormatDescriptor to a corresponding
|
|
||||||
// function extracting the desired data from a process.
|
|
||||||
type processFunc func(*process) (string, error)
|
|
||||||
|
|
||||||
// aixFormatDescriptor as mentioned in the ps(1) manpage. A given descriptor
|
|
||||||
// can either be specified via its code (e.g., "%C") or its normal representation
|
|
||||||
// (e.g., "pcpu") and will be printed under its corresponding header (e.g, "%CPU").
|
|
||||||
type aixFormatDescriptor struct {
|
|
||||||
code string
|
|
||||||
normal string
|
|
||||||
header string
|
|
||||||
procFn processFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
// processDescriptors calls each `procFn` of all formatDescriptors on each
|
|
||||||
// process and returns an array of tab-separated strings.
|
|
||||||
func processDescriptors(formatDescriptors []aixFormatDescriptor, processes []*process) ([]string, error) {
|
|
||||||
data := []string{}
|
|
||||||
// create header
|
|
||||||
headerArr := []string{}
|
|
||||||
for _, desc := range formatDescriptors {
|
|
||||||
headerArr = append(headerArr, desc.header)
|
|
||||||
}
|
|
||||||
data = append(data, strings.Join(headerArr, "\t"))
|
|
||||||
|
|
||||||
// dispatch all descriptor functions on each process
|
|
||||||
for _, proc := range processes {
|
|
||||||
pData := []string{}
|
|
||||||
for _, desc := range formatDescriptors {
|
|
||||||
dataStr, err := desc.procFn(proc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pData = append(pData, dataStr)
|
|
||||||
}
|
|
||||||
data = append(data, strings.Join(pData, "\t"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListDescriptors returns a string slice of all supported AIX format
|
// ListDescriptors returns a string slice of all supported AIX format
|
||||||
// descriptors in the normal form.
|
// descriptors in the normal form.
|
||||||
func ListDescriptors() (list []string) {
|
func ListDescriptors() (list []string) {
|
||||||
for _, d := range descriptors {
|
for _, d := range aixFormatDescriptors {
|
||||||
list = append(list, d.normal)
|
list = append(list, d.normal)
|
||||||
}
|
}
|
||||||
sort.Strings(list)
|
sort.Strings(list)
|
||||||
|
@ -337,13 +250,27 @@ func ListDescriptors() (list []string) {
|
||||||
|
|
||||||
// JoinNamespaceAndProcessInfo has the same semantics as ProcessInfo but joins
|
// JoinNamespaceAndProcessInfo has the same semantics as ProcessInfo but joins
|
||||||
// the mount namespace of the specified pid before extracting data from `/proc`.
|
// the mount namespace of the specified pid before extracting data from `/proc`.
|
||||||
func JoinNamespaceAndProcessInfo(pid, format string) ([]string, error) {
|
func JoinNamespaceAndProcessInfo(pid string, descriptors []string) ([][]string, error) {
|
||||||
var (
|
var (
|
||||||
data []string
|
data [][]string
|
||||||
dataErr error
|
dataErr error
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
)
|
)
|
||||||
|
|
||||||
|
aixDescriptors, err := translateDescriptors(descriptors)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract data from host processes only on-demand / when at least one
|
||||||
|
// of the specified descriptors requires host data
|
||||||
|
for _, d := range aixDescriptors {
|
||||||
|
if d.onHost {
|
||||||
|
setHostProcesses(pid)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
@ -362,7 +289,19 @@ func JoinNamespaceAndProcessInfo(pid, format string) ([]string, error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
unix.Setns(int(fd.Fd()), unix.CLONE_NEWNS)
|
unix.Setns(int(fd.Fd()), unix.CLONE_NEWNS)
|
||||||
data, dataErr = ProcessInfo(format)
|
|
||||||
|
pids, err := proc.GetPIDs()
|
||||||
|
if err != nil {
|
||||||
|
dataErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
processes, err := process.FromPIDs(pids)
|
||||||
|
if err != nil {
|
||||||
|
dataErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, dataErr = processDescriptors(aixDescriptors, processes)
|
||||||
}()
|
}()
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
|
@ -372,120 +311,137 @@ func JoinNamespaceAndProcessInfo(pid, format string) ([]string, error) {
|
||||||
// ProcessInfo returns the process information of all processes in the current
|
// ProcessInfo returns the process information of all processes in the current
|
||||||
// mount namespace. The input format must be a comma-separated list of
|
// mount namespace. The input format must be a comma-separated list of
|
||||||
// supported AIX format descriptors. If the input string is empty, the
|
// supported AIX format descriptors. If the input string is empty, the
|
||||||
// DefaultFormat is used.
|
// `DefaultDescriptors` is used.
|
||||||
// The return value is an array of tab-separated strings, to easily use the
|
// The return value is an array of tab-separated strings, to easily use the
|
||||||
// output for column-based formatting (e.g., with the `text/tabwriter` package).
|
// output for column-based formatting (e.g., with the `text/tabwriter` package).
|
||||||
func ProcessInfo(format string) ([]string, error) {
|
func ProcessInfo(descriptors []string) ([][]string, error) {
|
||||||
if len(format) == 0 {
|
aixDescriptors, err := translateDescriptors(descriptors)
|
||||||
format = DefaultFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
formatDescriptors, err := parseDescriptors(format)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
processes, err := processes()
|
pids, err := proc.GetPIDs()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
processes, err := process.FromPIDs(pids)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return processDescriptors(formatDescriptors, processes)
|
return processDescriptors(aixDescriptors, processes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseDescriptors parses the input string and returns a correspodning array
|
// setHostProcesses sets `hostProcesses`.
|
||||||
// of aixFormatDescriptors, which are expected to be separated by commas.
|
func setHostProcesses(pid string) error {
|
||||||
// The input format is "desc1, desc2, ..., desN" where a given descriptor can be
|
// get processes
|
||||||
// specified both, in the code and in the normal form. A concrete example is
|
pids, err := proc.GetPIDsFromCgroup(pid)
|
||||||
// "pid, %C, nice, %a".
|
if err != nil {
|
||||||
func parseDescriptors(input string) ([]aixFormatDescriptor, error) {
|
return err
|
||||||
formatDescriptors := []aixFormatDescriptor{}
|
|
||||||
for _, s := range strings.Split(input, ",") {
|
|
||||||
s = strings.TrimSpace(s)
|
|
||||||
found := false
|
|
||||||
for _, d := range descriptors {
|
|
||||||
if s == d.code || s == d.normal {
|
|
||||||
formatDescriptors = append(formatDescriptors, d)
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return nil, errors.Wrapf(ErrUnkownDescriptor, "'%s'", s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return formatDescriptors, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// lookupGID returns the textual group ID, if it can be optained, or the
|
processes, err := process.FromPIDs(pids)
|
||||||
// decimal input representation otherwise.
|
|
||||||
func lookupGID(gid string) (string, error) {
|
|
||||||
gidNum, err := strconv.Atoi(gid)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Wrap(err, "error parsing group ID")
|
return err
|
||||||
}
|
}
|
||||||
g, err := user.LookupGid(gidNum)
|
|
||||||
|
// set the additional host data
|
||||||
|
for _, p := range processes {
|
||||||
|
if err := p.SetHostData(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hostProcesses = processes
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processDescriptors calls each `procFn` of all formatDescriptors on each
|
||||||
|
// process and returns an array of tab-separated strings.
|
||||||
|
func processDescriptors(formatDescriptors []aixFormatDescriptor, processes []*process.Process) ([][]string, error) {
|
||||||
|
data := [][]string{}
|
||||||
|
// create header
|
||||||
|
header := []string{}
|
||||||
|
for _, desc := range formatDescriptors {
|
||||||
|
header = append(header, desc.header)
|
||||||
|
}
|
||||||
|
data = append(data, header)
|
||||||
|
|
||||||
|
// dispatch all descriptor functions on each process
|
||||||
|
for _, proc := range processes {
|
||||||
|
pData := []string{}
|
||||||
|
for _, desc := range formatDescriptors {
|
||||||
|
dataStr, err := desc.procFn(proc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gid, nil
|
return nil, err
|
||||||
}
|
}
|
||||||
return g.Name, nil
|
pData = append(pData, dataStr)
|
||||||
|
}
|
||||||
|
data = append(data, pData)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// findHostProcess returns the corresponding process from `hostProcesses` or
|
||||||
|
// nil if non is found.
|
||||||
|
func findHostProcess(p *process.Process) *process.Process {
|
||||||
|
for _, hp := range hostProcesses {
|
||||||
|
// We expect the host process to be in another namespace, so
|
||||||
|
// /proc/$pid/status.NSpid must have at least two entries.
|
||||||
|
if len(hp.Status.NSpid) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// The process' PID must match the one in the NS of the host
|
||||||
|
// process and both must share the same pid NS.
|
||||||
|
if p.Pid == hp.Status.NSpid[1] && p.PidNS == hp.PidNS {
|
||||||
|
return hp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// processGROUP returns the effective group ID of the process. This will be
|
// processGROUP returns the effective group ID of the process. This will be
|
||||||
// the textual group ID, if it can be optained, or a decimal representation
|
// the textual group ID, if it can be optained, or a decimal representation
|
||||||
// otherwise.
|
// otherwise.
|
||||||
func processGROUP(p *process) (string, error) {
|
func processGROUP(p *process.Process) (string, error) {
|
||||||
return lookupGID(p.pstatus.gids[1])
|
return process.LookupGID(p.Status.Gids[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
// processRGROUP returns the real group ID of the process. This will be
|
// processRGROUP returns the real group ID of the process. This will be
|
||||||
// the textual group ID, if it can be optained, or a decimal representation
|
// the textual group ID, if it can be optained, or a decimal representation
|
||||||
// otherwise.
|
// otherwise.
|
||||||
func processRGROUP(p *process) (string, error) {
|
func processRGROUP(p *process.Process) (string, error) {
|
||||||
return lookupGID(p.pstatus.gids[0])
|
return process.LookupGID(p.Status.Gids[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// processPPID returns the parent process ID of process p.
|
// processPPID returns the parent process ID of process p.
|
||||||
func processPPID(p *process) (string, error) {
|
func processPPID(p *process.Process) (string, error) {
|
||||||
return p.pstatus.pPid, nil
|
return p.Status.PPid, nil
|
||||||
}
|
|
||||||
|
|
||||||
// lookupUID return the textual user ID, if it can be optained, or the decimal
|
|
||||||
// input representation otherwise.
|
|
||||||
func lookupUID(uid string) (string, error) {
|
|
||||||
uidNum, err := strconv.Atoi(uid)
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.Wrap(err, "error parsing user ID")
|
|
||||||
}
|
|
||||||
u, err := user.LookupUid(uidNum)
|
|
||||||
if err != nil {
|
|
||||||
return uid, nil
|
|
||||||
}
|
|
||||||
return u.Name, nil
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// processUSER returns the effective user name of the process. This will be
|
// processUSER returns the effective user name of the process. This will be
|
||||||
// the textual group ID, if it can be optained, or a decimal representation
|
// the textual group ID, if it can be optained, or a decimal representation
|
||||||
// otherwise.
|
// otherwise.
|
||||||
func processUSER(p *process) (string, error) {
|
func processUSER(p *process.Process) (string, error) {
|
||||||
return lookupUID(p.pstatus.uids[1])
|
return process.LookupUID(p.Status.Uids[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
// processRUSER returns the effective user name of the process. This will be
|
// processRUSER returns the effective user name of the process. This will be
|
||||||
// the textual group ID, if it can be optained, or a decimal representation
|
// the textual group ID, if it can be optained, or a decimal representation
|
||||||
// otherwise.
|
// otherwise.
|
||||||
func processRUSER(p *process) (string, error) {
|
func processRUSER(p *process.Process) (string, error) {
|
||||||
return lookupUID(p.pstatus.uids[0])
|
return process.LookupUID(p.Status.Uids[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// processName returns the name of process p in the format "[$name]".
|
// processName returns the name of process p in the format "[$name]".
|
||||||
func processName(p *process) (string, error) {
|
func processName(p *process.Process) (string, error) {
|
||||||
return fmt.Sprintf("[%s]", p.pstatus.name), nil
|
return fmt.Sprintf("[%s]", p.Status.Name), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// processARGS returns the command of p with all its arguments.
|
// processARGS returns the command of p with all its arguments.
|
||||||
func processARGS(p *process) (string, error) {
|
func processARGS(p *process.Process) (string, error) {
|
||||||
args := p.cmdline
|
args := p.CmdLine
|
||||||
// ps (1) returns "[$name]" if command/args are empty
|
// ps (1) returns "[$name]" if command/args are empty
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return processName(p)
|
return processName(p)
|
||||||
|
@ -494,8 +450,8 @@ func processARGS(p *process) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// processCOMM returns the command name (i.e., executable name) of process p.
|
// processCOMM returns the command name (i.e., executable name) of process p.
|
||||||
func processCOMM(p *process) (string, error) {
|
func processCOMM(p *process.Process) (string, error) {
|
||||||
args := p.cmdline
|
args := p.CmdLine
|
||||||
// ps (1) returns "[$name]" if command/args are empty
|
// ps (1) returns "[$name]" if command/args are empty
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return processName(p)
|
return processName(p)
|
||||||
|
@ -505,28 +461,28 @@ func processCOMM(p *process) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// processNICE returns the nice value of process p.
|
// processNICE returns the nice value of process p.
|
||||||
func processNICE(p *process) (string, error) {
|
func processNICE(p *process.Process) (string, error) {
|
||||||
return p.pstat.nice, nil
|
return p.Stat.Nice, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// processPID returns the process ID of process p.
|
// processPID returns the process ID of process p.
|
||||||
func processPID(p *process) (string, error) {
|
func processPID(p *process.Process) (string, error) {
|
||||||
return p.pstatus.pid, nil
|
return p.Pid, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// processPGID returns the process group ID of process p.
|
// processPGID returns the process group ID of process p.
|
||||||
func processPGID(p *process) (string, error) {
|
func processPGID(p *process.Process) (string, error) {
|
||||||
return p.pstat.pgrp, nil
|
return p.Stat.Pgrp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// processPCPU returns how many percent of the CPU time process p uses as
|
// processPCPU returns how many percent of the CPU time process p uses as
|
||||||
// a three digit float as string.
|
// a three digit float as string.
|
||||||
func processPCPU(p *process) (string, error) {
|
func processPCPU(p *process.Process) (string, error) {
|
||||||
elapsed, err := p.elapsedTime()
|
elapsed, err := p.ElapsedTime()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
cpu, err := p.cpuTime()
|
cpu, err := p.CPUTime()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -536,8 +492,8 @@ func processPCPU(p *process) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// processETIME returns the elapsed time since the process was started.
|
// processETIME returns the elapsed time since the process was started.
|
||||||
func processETIME(p *process) (string, error) {
|
func processETIME(p *process.Process) (string, error) {
|
||||||
elapsed, err := p.elapsedTime()
|
elapsed, err := p.ElapsedTime()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
@ -545,8 +501,8 @@ func processETIME(p *process) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// processTIME returns the cumulative CPU time of process p.
|
// processTIME returns the cumulative CPU time of process p.
|
||||||
func processTIME(p *process) (string, error) {
|
func processTIME(p *process.Process) (string, error) {
|
||||||
cpu, err := p.cpuTime()
|
cpu, err := p.CPUTime()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -554,29 +510,28 @@ func processTIME(p *process) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// processTTY returns the controlling tty (terminal) of process p.
|
// processTTY returns the controlling tty (terminal) of process p.
|
||||||
func processTTY(p *process) (string, error) {
|
func processTTY(p *process.Process) (string, error) {
|
||||||
ttyNr, err := strconv.ParseUint(p.pstat.ttyNr, 10, 64)
|
ttyNr, err := strconv.ParseUint(p.Stat.TtyNr, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
maj, min := ttyNrToDev(ttyNr)
|
tty, err := dev.FindTTY(ttyNr)
|
||||||
t, err := findTTY(maj, min)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ttyS := "?"
|
ttyS := "?"
|
||||||
if t != nil {
|
if tty != nil {
|
||||||
ttyS = strings.TrimPrefix(t.device, "/dev/")
|
ttyS = strings.TrimPrefix(tty.Path, "/dev/")
|
||||||
}
|
}
|
||||||
return ttyS, nil
|
return ttyS, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// processVSZ returns the virtual memory size of process p in KiB (1024-byte
|
// processVSZ returns the virtual memory size of process p in KiB (1024-byte
|
||||||
// units).
|
// units).
|
||||||
func processVSZ(p *process) (string, error) {
|
func processVSZ(p *process.Process) (string, error) {
|
||||||
vmsize, err := strconv.Atoi(p.pstat.vsize)
|
vmsize, err := strconv.Atoi(p.Stat.Vsize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -591,10 +546,10 @@ func parseCAP(cap string) (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if mask == fullCAPs {
|
if mask == capabilities.FullCAPs {
|
||||||
return "full", nil
|
return "full", nil
|
||||||
}
|
}
|
||||||
caps := maskToCaps(mask)
|
caps := capabilities.TranslateMask(mask)
|
||||||
if len(caps) == 0 {
|
if len(caps) == 0 {
|
||||||
return "none", nil
|
return "none", nil
|
||||||
}
|
}
|
||||||
|
@ -605,35 +560,35 @@ func parseCAP(cap string) (string, error) {
|
||||||
// processCAPINH returns the set of inheritable capabilties associated with
|
// processCAPINH returns the set of inheritable capabilties associated with
|
||||||
// process p. If all capabilties are set, "full" is returned. If no
|
// process p. If all capabilties are set, "full" is returned. If no
|
||||||
// capability is enabled, "none" is returned.
|
// capability is enabled, "none" is returned.
|
||||||
func processCAPINH(p *process) (string, error) {
|
func processCAPINH(p *process.Process) (string, error) {
|
||||||
return parseCAP(p.pstatus.capInh)
|
return parseCAP(p.Status.CapInh)
|
||||||
}
|
}
|
||||||
|
|
||||||
// processCAPPRM returns the set of permitted capabilties associated with
|
// processCAPPRM returns the set of permitted capabilties associated with
|
||||||
// process p. If all capabilties are set, "full" is returned. If no
|
// process p. If all capabilties are set, "full" is returned. If no
|
||||||
// capability is enabled, "none" is returned.
|
// capability is enabled, "none" is returned.
|
||||||
func processCAPPRM(p *process) (string, error) {
|
func processCAPPRM(p *process.Process) (string, error) {
|
||||||
return parseCAP(p.pstatus.capPrm)
|
return parseCAP(p.Status.CapPrm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// processCAPEFF returns the set of effective capabilties associated with
|
// processCAPEFF returns the set of effective capabilties associated with
|
||||||
// process p. If all capabilties are set, "full" is returned. If no
|
// process p. If all capabilties are set, "full" is returned. If no
|
||||||
// capability is enabled, "none" is returned.
|
// capability is enabled, "none" is returned.
|
||||||
func processCAPEFF(p *process) (string, error) {
|
func processCAPEFF(p *process.Process) (string, error) {
|
||||||
return parseCAP(p.pstatus.capEff)
|
return parseCAP(p.Status.CapEff)
|
||||||
}
|
}
|
||||||
|
|
||||||
// processCAPBND returns the set of bounding capabilties associated with
|
// processCAPBND returns the set of bounding capabilties associated with
|
||||||
// process p. If all capabilties are set, "full" is returned. If no
|
// process p. If all capabilties are set, "full" is returned. If no
|
||||||
// capability is enabled, "none" is returned.
|
// capability is enabled, "none" is returned.
|
||||||
func processCAPBND(p *process) (string, error) {
|
func processCAPBND(p *process.Process) (string, error) {
|
||||||
return parseCAP(p.pstatus.capBnd)
|
return parseCAP(p.Status.CapBnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// processSECCOMP returns the seccomp mode of the process (i.e., disabled,
|
// processSECCOMP returns the seccomp mode of the process (i.e., disabled,
|
||||||
// strict or filter) or "?" if /proc/$pid/status.seccomp has a unknown value.
|
// strict or filter) or "?" if /proc/$pid/status.seccomp has a unknown value.
|
||||||
func processSECCOMP(p *process) (string, error) {
|
func processSECCOMP(p *process.Process) (string, error) {
|
||||||
switch p.pstatus.seccomp {
|
switch p.Status.Seccomp {
|
||||||
case "0":
|
case "0":
|
||||||
return "disabled", nil
|
return "disabled", nil
|
||||||
case "1":
|
case "1":
|
||||||
|
@ -645,18 +600,41 @@ func processSECCOMP(p *process) (string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// processLABEL returns the process label of process p.
|
// processLABEL returns the process label of process p or "?" if the system
|
||||||
func processLABEL(p *process) (string, error) {
|
// doesn't support labeling.
|
||||||
data, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/attr/current", p.pid))
|
func processLABEL(p *process.Process) (string, error) {
|
||||||
if err != nil {
|
return p.Label, nil
|
||||||
if os.IsNotExist(err) {
|
|
||||||
// make sure the pid does not exist,
|
|
||||||
// could be system does not support labeling.
|
|
||||||
if _, err2 := os.Stat(fmt.Sprintf("/proc/%d", p.pid)); err2 != nil {
|
|
||||||
return "", errNoSuchPID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// processHPID returns the PID of the corresponding host process of the
|
||||||
|
// (container) or "?" if no corresponding process could be found.
|
||||||
|
func processHPID(p *process.Process) (string, error) {
|
||||||
|
if hp := findHostProcess(p); hp != nil {
|
||||||
|
return hp.Pid, nil
|
||||||
}
|
}
|
||||||
return "", err
|
return "?", nil
|
||||||
}
|
}
|
||||||
return strings.Trim(string(data), "\x00"), nil
|
|
||||||
|
// processHUSER returns the effective user ID of the corresponding host process
|
||||||
|
// of the (container) or "?" if no corresponding process could be found.
|
||||||
|
func processHUSER(p *process.Process) (string, error) {
|
||||||
|
if hp := findHostProcess(p); hp != nil {
|
||||||
|
return hp.Huser, nil
|
||||||
|
}
|
||||||
|
return "?", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processHGROUP returns the effective group ID of the corresponding host
|
||||||
|
// process of the (container) or "?" if no corresponding process could be
|
||||||
|
// found.
|
||||||
|
func processHGROUP(p *process.Process) (string, error) {
|
||||||
|
if hp := findHostProcess(p); hp != nil {
|
||||||
|
return hp.Hgroup, nil
|
||||||
|
}
|
||||||
|
return "?", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processState returns the process state of process p.
|
||||||
|
func processState(p *process.Process) (string, error) {
|
||||||
|
return p.Status.State, nil
|
||||||
}
|
}
|
Loading…
Reference in New Issue