aboutsummaryrefslogtreecommitdiff
path: root/cmd/podman/stats.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/podman/stats.go')
-rw-r--r--cmd/podman/stats.go226
1 files changed, 226 insertions, 0 deletions
diff --git a/cmd/podman/stats.go b/cmd/podman/stats.go
new file mode 100644
index 000000000..cf54a8bfe
--- /dev/null
+++ b/cmd/podman/stats.go
@@ -0,0 +1,226 @@
+package main
+
+import (
+ "fmt"
+ "reflect"
+ "strings"
+ "time"
+
+ tm "github.com/buger/goterm"
+ "github.com/docker/go-units"
+ "github.com/pkg/errors"
+ "github.com/projectatomic/libpod/cmd/podman/formats"
+ "github.com/projectatomic/libpod/libpod"
+ "github.com/urfave/cli"
+)
+
+type statsOutputParams struct {
+ Container string `json:"name"`
+ ID string `json:"id"`
+ CPUPerc string `json:"cpu_percent"`
+ MemUsage string `json:"mem_usage"`
+ MemPerc string `json:"mem_percent"`
+ NetIO string `json:"netio"`
+ BlockIO string `json:"blocki"`
+ PIDS uint64 `json:"pids"`
+}
+
+var (
+ statsFlags = []cli.Flag{
+ cli.BoolFlag{
+ Name: "all, a",
+ Usage: "show all containers. Only running containers are shown by default. The default is false",
+ },
+ cli.BoolFlag{
+ Name: "no-stream",
+ Usage: "disable streaming stats and only pull the first result, default setting is false",
+ },
+ cli.StringFlag{
+ Name: "format",
+ Usage: "pretty-print container statistics using a Go template",
+ },
+ cli.BoolFlag{
+ Name: "no-reset",
+ Usage: "disable resetting the screen between intervals",
+ },
+ }
+
+ statsDescription = "display a live stream of one or more containers' resource usage statistics"
+ statsCommand = cli.Command{
+ Name: "stats",
+ Usage: "Display percentage of CPU, memory, network I/O, block I/O and PIDs for one or more containers",
+ Description: statsDescription,
+ Flags: statsFlags,
+ Action: statsCmd,
+ ArgsUsage: "",
+ }
+)
+
+func statsCmd(c *cli.Context) error {
+ if err := validateFlags(c, statsFlags); err != nil {
+ return err
+ }
+
+ runtime, err := getRuntime(c)
+ if err != nil {
+ return errors.Wrapf(err, "could not get runtime")
+ }
+ defer runtime.Shutdown(false)
+
+ times := -1
+ if c.Bool("no-stream") {
+ times = 1
+ }
+
+ var format string
+ var ctrs []*libpod.Container
+ var containerFunc func() ([]*libpod.Container, error)
+ all := c.Bool("all")
+
+ if c.IsSet("format") {
+ format = c.String("format")
+ } else {
+ format = genStatsFormat()
+ }
+
+ if len(c.Args()) > 0 {
+ containerFunc = func() ([]*libpod.Container, error) { return runtime.GetContainersByList(c.Args()) }
+ } else if all {
+ containerFunc = runtime.GetAllContainers
+ } else {
+ containerFunc = runtime.GetRunningContainers
+ }
+
+ ctrs, err = containerFunc()
+ if err != nil {
+ return errors.Wrapf(err, "unable to get list of containers")
+ }
+
+ containerStats := map[string]*libpod.ContainerStats{}
+ for _, ctr := range ctrs {
+ initialStats, err := ctr.GetContainerStats(&libpod.ContainerStats{})
+ if err != nil {
+ return err
+ }
+ containerStats[ctr.ID()] = initialStats
+ }
+ step := 1
+ if times == -1 {
+ times = 1
+ step = 0
+ }
+ for i := 0; i < times; i += step {
+ reportStats := []*libpod.ContainerStats{}
+ for _, ctr := range ctrs {
+ id := ctr.ID()
+ if _, ok := containerStats[ctr.ID()]; !ok {
+ initialStats, err := ctr.GetContainerStats(&libpod.ContainerStats{})
+ if err != nil {
+ return err
+ }
+ containerStats[id] = initialStats
+ }
+ stats, err := ctr.GetContainerStats(containerStats[id])
+ if err != nil {
+ return err
+ }
+ // replace the previous measurement with the current one
+ containerStats[id] = stats
+ reportStats = append(reportStats, stats)
+ }
+ ctrs, err = containerFunc()
+ if err != nil {
+ return err
+ }
+ if strings.ToLower(format) != formats.JSONString && !c.Bool("no-reset") {
+ tm.Clear()
+ tm.MoveCursor(1, 1)
+ tm.Flush()
+ }
+ outputStats(reportStats, format)
+ time.Sleep(time.Second)
+ }
+ return nil
+}
+
+func outputStats(stats []*libpod.ContainerStats, format string) error {
+ var out formats.Writer
+ var outputStats []statsOutputParams
+ for _, s := range stats {
+ outputStats = append(outputStats, getStatsOutputParams(s))
+ }
+ if len(outputStats) == 0 {
+ return nil
+ }
+ if strings.ToLower(format) == formats.JSONString {
+ out = formats.JSONStructArray{Output: statsToGeneric(outputStats, []statsOutputParams{})}
+ } else {
+ out = formats.StdoutTemplateArray{Output: statsToGeneric(outputStats, []statsOutputParams{}), Template: format, Fields: outputStats[0].headerMap()}
+ }
+ return formats.Writer(out).Out()
+}
+
+func genStatsFormat() (format string) {
+ return "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDS}}"
+}
+
+// imagesToGeneric creates an empty array of interfaces for output
+func statsToGeneric(templParams []statsOutputParams, JSONParams []statsOutputParams) (genericParams []interface{}) {
+ if len(templParams) > 0 {
+ for _, v := range templParams {
+ genericParams = append(genericParams, interface{}(v))
+ }
+ return
+ }
+ for _, v := range JSONParams {
+ genericParams = append(genericParams, interface{}(v))
+ }
+ return
+}
+
+// generate the header based on the template provided
+func (i *statsOutputParams) headerMap() map[string]string {
+ v := reflect.Indirect(reflect.ValueOf(i))
+ values := make(map[string]string)
+
+ for i := 0; i < v.NumField(); i++ {
+ key := v.Type().Field(i).Name
+ value := key
+ switch value {
+ case "CPUPerc":
+ value = "CPU%"
+ case "MemUsage":
+ value = "MemUsage/Limit"
+ case "MemPerc":
+ value = "Mem%"
+ }
+ values[key] = strings.ToUpper(splitCamelCase(value))
+ }
+ return values
+}
+
+func combineHumanValues(a, b uint64) string {
+ return fmt.Sprintf("%s / %s", units.HumanSize(float64(a)), units.HumanSize(float64(b)))
+}
+
+func floatToPercentString(f float64) string {
+ strippedFloat, err := libpod.RemoveScientificNotationFromFloat(f)
+ if err != nil {
+ // If things go bazinga, return a safe value
+ return "0.00 %"
+ }
+ return fmt.Sprintf("%.2f", strippedFloat) + "%"
+}
+
+func getStatsOutputParams(stats *libpod.ContainerStats) statsOutputParams {
+ return statsOutputParams{
+ Container: stats.ContainerID[:12],
+ ID: stats.ContainerID,
+ CPUPerc: floatToPercentString(stats.CPU),
+ MemUsage: combineHumanValues(stats.MemUsage, stats.MemLimit),
+ MemPerc: floatToPercentString(stats.MemPerc),
+ NetIO: combineHumanValues(stats.NetInput, stats.NetOutput),
+ BlockIO: combineHumanValues(stats.BlockInput, stats.BlockOutput),
+ PIDS: stats.PIDs,
+ }
+}