podman-remote pod top|stats

this is the final enablement for the pod subcommand.  it includes the
ability to run podman-remote pod top and stats.

Signed-off-by: baude <bbaude@redhat.com>
This commit is contained in:
baude
2019-02-25 14:26:18 -06:00
parent 7418ff988b
commit 788f818cc5
10 changed files with 279 additions and 76 deletions

View File

@ -85,14 +85,6 @@ func getContainerSubCommands() []*cobra.Command {
}
}
// Commands that the local client implements
func getPodSubCommands() []*cobra.Command {
return []*cobra.Command{
_podStatsCommand,
_podTopCommand,
}
}
func getGenerateSubCommands() []*cobra.Command {
return []*cobra.Command{
_containerKubeCommand,

View File

@ -28,11 +28,6 @@ func getContainerSubCommands() []*cobra.Command {
return []*cobra.Command{}
}
// commands that only the remoteclient implements
func getPodSubCommands() []*cobra.Command {
return []*cobra.Command{}
}
// commands that only the remoteclient implements
func getGenerateSubCommands() []*cobra.Command {
return []*cobra.Command{}

View File

@ -29,12 +29,13 @@ var podSubCommands = []*cobra.Command{
_podRestartCommand,
_podRmCommand,
_podStartCommand,
_podStatsCommand,
_podStopCommand,
_podTopCommand,
_podUnpauseCommand,
}
func init() {
podCommand.AddCommand(podSubCommands...)
podCommand.AddCommand(getPodSubCommands()...)
podCommand.SetUsageTemplate(UsageTemplate())
}

View File

@ -13,8 +13,8 @@ import (
tm "github.com/buger/goterm"
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/cmd/podman/formats"
"github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/adapter"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/ulule/deepcopier"
@ -51,10 +51,6 @@ func init() {
}
func podStatsCmd(c *cliconfig.PodStatsValues) error {
var (
podFunc func() ([]*libpod.Pod, error)
)
format := c.Format
all := c.All
latest := c.Latest
@ -76,7 +72,7 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error {
all = true
}
runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand)
runtime, err := adapter.GetRuntime(&c.PodmanCommand)
if err != nil {
return errors.Wrapf(err, "could not get runtime")
}
@ -87,29 +83,12 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error {
times = 1
}
if len(c.InputArgs) > 0 {
podFunc = func() ([]*libpod.Pod, error) { return getPodsByList(c.InputArgs, runtime) }
} else if latest {
podFunc = func() ([]*libpod.Pod, error) {
latestPod, err := runtime.GetLatestPod()
if err != nil {
return nil, err
}
return []*libpod.Pod{latestPod}, err
}
} else if all {
podFunc = runtime.GetAllPods
} else {
podFunc = runtime.GetRunningPods
}
pods, err := podFunc()
pods, err := runtime.GetStatPods(c)
if err != nil {
return errors.Wrapf(err, "unable to get a list of pods")
}
// First we need to get an initial pass of pod/ctr stats (these are not printed)
var podStats []*libpod.PodContainerStats
var podStats []*adapter.PodContainerStats
for _, p := range pods {
cons, err := p.AllContainersByID()
if err != nil {
@ -120,7 +99,7 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error {
for _, c := range cons {
emptyStats[c] = &libpod.ContainerStats{}
}
ps := libpod.PodContainerStats{
ps := adapter.PodContainerStats{
Pod: p,
ContainerStats: emptyStats,
}
@ -128,10 +107,10 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error {
}
// Create empty container stat results for our first pass
var previousPodStats []*libpod.PodContainerStats
var previousPodStats []*adapter.PodContainerStats
for _, p := range pods {
cs := make(map[string]*libpod.ContainerStats)
pcs := libpod.PodContainerStats{
pcs := adapter.PodContainerStats{
Pod: p,
ContainerStats: cs,
}
@ -164,7 +143,7 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error {
}
for i := 0; i < times; i += step {
var newStats []*libpod.PodContainerStats
var newStats []*adapter.PodContainerStats
for _, p := range pods {
prevStat := getPreviousPodContainerStats(p.ID(), previousPodStats)
newPodStats, err := p.GetPodStats(prevStat)
@ -174,7 +153,7 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error {
if err != nil {
return err
}
newPod := libpod.PodContainerStats{
newPod := adapter.PodContainerStats{
Pod: p,
ContainerStats: newPodStats,
}
@ -202,7 +181,7 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error {
time.Sleep(time.Second)
previousPodStats := new([]*libpod.PodContainerStats)
deepcopier.Copy(newStats).To(previousPodStats)
pods, err = podFunc()
pods, err = runtime.GetStatPods(c)
if err != nil {
return err
}
@ -211,7 +190,7 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error {
return nil
}
func podContainerStatsToPodStatOut(stats []*libpod.PodContainerStats) []*podStatOut {
func podContainerStatsToPodStatOut(stats []*adapter.PodContainerStats) []*podStatOut {
var out []*podStatOut
for _, p := range stats {
for _, c := range p.ContainerStats {
@ -295,7 +274,7 @@ func outputToStdOut(stats []*podStatOut) {
w.Flush()
}
func getPreviousPodContainerStats(podID string, prev []*libpod.PodContainerStats) map[string]*libpod.ContainerStats {
func getPreviousPodContainerStats(podID string, prev []*adapter.PodContainerStats) map[string]*libpod.ContainerStats {
for _, p := range prev {
if podID == p.Pod.ID() {
return p.ContainerStats
@ -304,7 +283,7 @@ func getPreviousPodContainerStats(podID string, prev []*libpod.PodContainerStats
return map[string]*libpod.ContainerStats{}
}
func outputJson(stats []*libpod.PodContainerStats) error {
func outputJson(stats []*adapter.PodContainerStats) error {
b, err := json.MarshalIndent(&stats, "", " ")
if err != nil {
return err

View File

@ -2,13 +2,12 @@ package main
import (
"fmt"
"github.com/containers/libpod/pkg/adapter"
"os"
"strings"
"text/tabwriter"
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -50,8 +49,9 @@ func init() {
}
func podTopCmd(c *cliconfig.PodTopValues) error {
var pod *libpod.Pod
var err error
var (
descriptors []string
)
args := c.InputArgs
if c.ListDescriptors {
@ -67,39 +67,22 @@ func podTopCmd(c *cliconfig.PodTopValues) error {
return errors.Errorf("you must provide the name or id of a running pod")
}
runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand)
runtime, err := adapter.GetRuntime(&c.PodmanCommand)
if err != nil {
return errors.Wrapf(err, "error creating libpod runtime")
}
defer runtime.Shutdown(false)
var descriptors []string
if c.Latest {
descriptors = args
pod, err = runtime.GetLatestPod()
} else {
descriptors = args[1:]
pod, err = runtime.LookupPod(args[0])
}
if err != nil {
return errors.Wrapf(err, "unable to lookup requested container")
}
podStatus, err := shared.GetPodStatus(pod)
if err != nil {
return err
}
if podStatus != "Running" {
return errors.Errorf("pod top can only be used on pods with at least one running container")
}
psOutput, err := pod.GetPodPidInformation(descriptors)
if err != nil {
return err
}
w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0)
psOutput, err := runtime.PodTop(c, descriptors)
if err != nil {
return err
}
for _, proc := range psOutput {
fmt.Fprintln(w, proc)
}

View File

@ -549,6 +549,10 @@ method ExportContainer(name: string, path: string) -> (tarfile: string)
# ~~~
method GetContainerStats(name: string) -> (container: ContainerStats)
# GetContainerStatsWithHistory takes a previous set of container statistics and uses libpod functions
# to calculate the containers statistics based on current and previous measurements.
method GetContainerStatsWithHistory(previousStats: ContainerStats) -> (container: ContainerStats)
# This method has not be implemented yet.
# method ResizeContainerTty() -> (notimplemented: NotImplemented)
@ -952,8 +956,7 @@ method RemovePod(name: string, force: bool) -> (pod: string)
# This method has not be implemented yet.
# method WaitPod() -> (notimplemented: NotImplemented)
# This method has not been implemented yet.
# method TopPod() -> (notimplemented: NotImplemented)
method TopPod(pod: string, latest: bool, descriptors: []string) -> (stats: []string)
# GetPodStats takes the name or ID of a pod and returns a pod name and slice of ContainerStats structure which
# contains attributes like memory and cpu usage. If the pod cannot be found, a [PodNotFound](#PodNotFound)

View File

@ -4,6 +4,7 @@ package adapter
import (
"context"
"github.com/pkg/errors"
"strings"
"github.com/containers/libpod/cmd/podman/cliconfig"
@ -17,6 +18,13 @@ type Pod struct {
*libpod.Pod
}
// PodContainerStats is struct containing an adapter Pod and a libpod
// ContainerStats and is used primarily for outputing pod stats.
type PodContainerStats struct {
Pod *Pod
ContainerStats map[string]*libpod.ContainerStats
}
// RemovePods ...
func (r *LocalRuntime) RemovePods(ctx context.Context, cli *cliconfig.PodRmValues) ([]string, []error) {
var (
@ -321,3 +329,55 @@ func (r *LocalRuntime) RestartPods(ctx context.Context, c *cliconfig.PodRestartV
return restartIDs, containerErrors, restartErrors
}
// PodTop is a wrapper function to call GetPodPidInformation in libpod and return its results
// for output
func (r *LocalRuntime) PodTop(c *cliconfig.PodTopValues, descriptors []string) ([]string, error) {
var (
pod *Pod
err error
)
if c.Latest {
pod, err = r.GetLatestPod()
} else {
pod, err = r.LookupPod(c.InputArgs[0])
}
if err != nil {
return nil, errors.Wrapf(err, "unable to lookup requested container")
}
podStatus, err := pod.GetPodStatus()
if err != nil {
return nil, errors.Wrapf(err, "unable to get status for pod %s", pod.ID())
}
if podStatus != "Running" {
return nil, errors.Errorf("pod top can only be used on pods with at least one running container")
}
return pod.GetPodPidInformation(descriptors)
}
// GetStatPods returns pods for use in pod stats
func (r *LocalRuntime) GetStatPods(c *cliconfig.PodStatsValues) ([]*Pod, error) {
var (
adapterPods []*Pod
pods []*libpod.Pod
err error
)
if len(c.InputArgs) > 0 || c.Latest || c.All {
pods, err = shortcuts.GetPodsByContext(c.All, c.Latest, c.InputArgs, r.Runtime)
} else {
pods, err = r.Runtime.GetRunningPods()
}
if err != nil {
return nil, err
}
// convert libpod pods to adapter pods
for _, p := range pods {
adapterPod := Pod{
p,
}
adapterPods = append(adapterPods, &adapterPod)
}
return adapterPods, nil
}

View File

@ -12,6 +12,7 @@ import (
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/varlinkapi"
"github.com/pkg/errors"
"github.com/ulule/deepcopier"
)
@ -21,6 +22,13 @@ type Pod struct {
remotepod
}
// PodContainerStats is struct containing an adapter Pod and a libpod
// ContainerStats and is used primarily for outputing pod stats.
type PodContainerStats struct {
Pod *Pod
ContainerStats map[string]*libpod.ContainerStats
}
type remotepod struct {
config *libpod.PodConfig
state *libpod.PodInspectState
@ -399,3 +407,103 @@ func (r *LocalRuntime) RestartPods(ctx context.Context, c *cliconfig.PodRestartV
}
return restartIDs, nil, restartErrors
}
// PodTop gets top statistics for a pod
func (r *LocalRuntime) PodTop(c *cliconfig.PodTopValues, descriptors []string) ([]string, error) {
var (
latest bool
podName string
)
if c.Latest {
latest = true
} else {
podName = c.InputArgs[0]
}
return iopodman.TopPod().Call(r.Conn, podName, latest, descriptors)
}
// GetStatPods returns pods for use in pod stats
func (r *LocalRuntime) GetStatPods(c *cliconfig.PodStatsValues) ([]*Pod, error) {
var (
pods []*Pod
err error
podIDs []string
running bool
)
if len(c.InputArgs) > 0 || c.Latest || c.All {
podIDs, err = iopodman.GetPodsByContext().Call(r.Conn, c.All, c.Latest, c.InputArgs)
} else {
podIDs, err = iopodman.GetPodsByContext().Call(r.Conn, true, false, []string{})
running = true
}
if err != nil {
return nil, err
}
for _, p := range podIDs {
pod, err := r.Inspect(p)
if err != nil {
return nil, err
}
if running {
status, err := pod.GetPodStatus()
if err != nil {
// if we cannot get the status of the pod, skip and move on
continue
}
if strings.ToUpper(status) != "RUNNING" {
// if the pod is not running, skip and move on as well
continue
}
}
pods = append(pods, pod)
}
return pods, nil
}
// GetPodStats returns the stats for each of its containers
func (p *Pod) GetPodStats(previousContainerStats map[string]*libpod.ContainerStats) (map[string]*libpod.ContainerStats, error) {
var (
ok bool
prevStat *libpod.ContainerStats
)
newContainerStats := make(map[string]*libpod.ContainerStats)
containers, err := p.AllContainers()
if err != nil {
return nil, err
}
for _, c := range containers {
if prevStat, ok = previousContainerStats[c.ID()]; !ok {
prevStat = &libpod.ContainerStats{ContainerID: c.ID()}
}
cStats := iopodman.ContainerStats{
Id: prevStat.ContainerID,
Name: prevStat.Name,
Cpu: prevStat.CPU,
Cpu_nano: int64(prevStat.CPUNano),
System_nano: int64(prevStat.SystemNano),
Mem_usage: int64(prevStat.MemUsage),
Mem_limit: int64(prevStat.MemLimit),
Mem_perc: prevStat.MemPerc,
Net_input: int64(prevStat.NetInput),
Net_output: int64(prevStat.NetOutput),
Block_input: int64(prevStat.BlockInput),
Block_output: int64(prevStat.BlockOutput),
Pids: int64(prevStat.PIDs),
}
stats, err := iopodman.GetContainerStatsWithHistory().Call(p.Runtime.Conn, cStats)
if err != nil {
return nil, err
}
newStats := varlinkapi.ContainerStatsToLibpodContainerStats(stats)
// If the container wasn't running, don't include it
// but also suppress the error
if err != nil && errors.Cause(err) != libpod.ErrCtrStateInvalid {
return nil, err
}
if err == nil {
newContainerStats[c.ID()] = &newStats
}
}
return newContainerStats, nil
}

View File

@ -552,3 +552,54 @@ func (i *LibpodAPI) ContainerStateData(call iopodman.VarlinkCall, name string) e
}
return call.ReplyContainerStateData(string(b))
}
// GetContainerStatsWithHistory is a varlink endpoint that returns container stats based on current and
// previous statistics
func (i *LibpodAPI) GetContainerStatsWithHistory(call iopodman.VarlinkCall, prevStats iopodman.ContainerStats) error {
con, err := i.Runtime.LookupContainer(prevStats.Id)
if err != nil {
return call.ReplyContainerNotFound(prevStats.Id, err.Error())
}
previousStats := ContainerStatsToLibpodContainerStats(prevStats)
stats, err := con.GetContainerStats(&previousStats)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
cStats := iopodman.ContainerStats{
Id: stats.ContainerID,
Name: stats.Name,
Cpu: stats.CPU,
Cpu_nano: int64(stats.CPUNano),
System_nano: int64(stats.SystemNano),
Mem_usage: int64(stats.MemUsage),
Mem_limit: int64(stats.MemLimit),
Mem_perc: stats.MemPerc,
Net_input: int64(stats.NetInput),
Net_output: int64(stats.NetOutput),
Block_input: int64(stats.BlockInput),
Block_output: int64(stats.BlockOutput),
Pids: int64(stats.PIDs),
}
return call.ReplyGetContainerStatsWithHistory(cStats)
}
// ContainerStatsToLibpodContainerStats converts the varlink containerstats to a libpod
// container stats
func ContainerStatsToLibpodContainerStats(stats iopodman.ContainerStats) libpod.ContainerStats {
cstats := libpod.ContainerStats{
ContainerID: stats.Id,
Name: stats.Name,
CPU: stats.Cpu,
CPUNano: uint64(stats.Cpu_nano),
SystemNano: uint64(stats.System_nano),
MemUsage: uint64(stats.Mem_usage),
MemLimit: uint64(stats.Mem_limit),
MemPerc: stats.Mem_perc,
NetInput: uint64(stats.Net_input),
NetOutput: uint64(stats.Net_output),
BlockInput: uint64(stats.Block_input),
BlockOutput: uint64(stats.Block_output),
PIDs: uint64(stats.Pids),
}
return cstats
}

View File

@ -2,6 +2,7 @@ package varlinkapi
import (
"encoding/json"
"fmt"
"github.com/containers/libpod/pkg/adapter/shortcuts"
"github.com/containers/libpod/pkg/rootless"
"syscall"
@ -299,3 +300,33 @@ func (i *LibpodAPI) PodStateData(call iopodman.VarlinkCall, name string) error {
}
return call.ReplyPodStateData(string(b))
}
// TopPod provides the top stats for a given or latest pod
func (i *LibpodAPI) TopPod(call iopodman.VarlinkCall, name string, latest bool, descriptors []string) error {
var (
pod *libpod.Pod
err error
)
if latest {
name = "latest"
pod, err = i.Runtime.GetLatestPod()
} else {
pod, err = i.Runtime.LookupPod(name)
}
if err != nil {
return call.ReplyPodNotFound(name, err.Error())
}
podStatus, err := shared.GetPodStatus(pod)
if err != nil {
return call.ReplyErrorOccurred(fmt.Sprintf("unable to get status for pod %s", pod.ID()))
}
if podStatus != "Running" {
return call.ReplyErrorOccurred("pod top can only be used on pods with at least one running container")
}
reply, err := pod.GetPodPidInformation(descriptors)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyTopPod(reply)
}