From 788f818cc53fbd68267c2c6ab5de8655938414cb Mon Sep 17 00:00:00 2001 From: baude Date: Mon, 25 Feb 2019 14:26:18 -0600 Subject: 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 --- cmd/podman/commands.go | 8 --- cmd/podman/commands_remoteclient.go | 5 -- cmd/podman/pod.go | 3 +- cmd/podman/pod_stats.go | 47 +++++---------- cmd/podman/pod_top.go | 31 +++------- cmd/podman/varlink/io.podman.varlink | 7 ++- pkg/adapter/pods.go | 60 +++++++++++++++++++ pkg/adapter/pods_remote.go | 108 +++++++++++++++++++++++++++++++++++ pkg/varlinkapi/containers.go | 51 +++++++++++++++++ pkg/varlinkapi/pods.go | 31 ++++++++++ 10 files changed, 277 insertions(+), 74 deletions(-) diff --git a/cmd/podman/commands.go b/cmd/podman/commands.go index c261e54e2..483ada332 100644 --- a/cmd/podman/commands.go +++ b/cmd/podman/commands.go @@ -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, diff --git a/cmd/podman/commands_remoteclient.go b/cmd/podman/commands_remoteclient.go index 081043b25..a278761c1 100644 --- a/cmd/podman/commands_remoteclient.go +++ b/cmd/podman/commands_remoteclient.go @@ -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{} diff --git a/cmd/podman/pod.go b/cmd/podman/pod.go index c1350bd4d..7067f2429 100644 --- a/cmd/podman/pod.go +++ b/cmd/podman/pod.go @@ -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()) } diff --git a/cmd/podman/pod_stats.go b/cmd/podman/pod_stats.go index f5edd21f8..2761ce9cc 100644 --- a/cmd/podman/pod_stats.go +++ b/cmd/podman/pod_stats.go @@ -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 diff --git a/cmd/podman/pod_top.go b/cmd/podman/pod_top.go index 6a26e3dff..c5383d376 100644 --- a/cmd/podman/pod_top.go +++ b/cmd/podman/pod_top.go @@ -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) + w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0) + psOutput, err := runtime.PodTop(c, descriptors) if err != nil { return err } - - w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0) for _, proc := range psOutput { fmt.Fprintln(w, proc) } diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 73e6c15f9..a50b7dd13 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -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) diff --git a/pkg/adapter/pods.go b/pkg/adapter/pods.go index 706a8fe96..669971789 100644 --- a/pkg/adapter/pods.go +++ b/pkg/adapter/pods.go @@ -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 +} diff --git a/pkg/adapter/pods_remote.go b/pkg/adapter/pods_remote.go index 220f7163f..ef8de90a6 100644 --- a/pkg/adapter/pods_remote.go +++ b/pkg/adapter/pods_remote.go @@ -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 +} diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index 27b8d15d2..61da19c83 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -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 +} diff --git a/pkg/varlinkapi/pods.go b/pkg/varlinkapi/pods.go index 4ca4c4270..c79cee4c2 100644 --- a/pkg/varlinkapi/pods.go +++ b/pkg/varlinkapi/pods.go @@ -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) +} -- cgit v1.2.3-54-g00ecf