summaryrefslogtreecommitdiff
path: root/cmd/kpod/stats.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/kpod/stats.go')
-rw-r--r--cmd/kpod/stats.go245
1 files changed, 245 insertions, 0 deletions
diff --git a/cmd/kpod/stats.go b/cmd/kpod/stats.go
new file mode 100644
index 000000000..8548354a9
--- /dev/null
+++ b/cmd/kpod/stats.go
@@ -0,0 +1,245 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "strings"
+ "text/template"
+ "time"
+
+ "github.com/docker/go-units"
+
+ tm "github.com/buger/goterm"
+ "github.com/projectatomic/libpod/libkpod"
+ "github.com/projectatomic/libpod/oci"
+ "github.com/pkg/errors"
+ "github.com/urfave/cli"
+)
+
+var printf func(format string, a ...interface{}) (n int, err error)
+var println func(a ...interface{}) (n int, err error)
+
+type statsOutputParams struct {
+ Container string
+ ID string
+ CPUPerc string
+ MemUsage string
+ MemPerc string
+ NetIO string
+ BlockIO string
+ PIDs uint64
+}
+
+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: "json",
+ Usage: "output container statistics in json format",
+ },
+ }
+
+ 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
+ }
+ config, err := getConfig(c)
+ if err != nil {
+ return errors.Wrapf(err, "could not read config")
+ }
+ containerServer, err := libkpod.New(config)
+ if err != nil {
+ return errors.Wrapf(err, "could not create container server")
+ }
+ defer containerServer.Shutdown()
+ err = containerServer.Update()
+ if err != nil {
+ return errors.Wrapf(err, "could not update list of containers")
+ }
+ times := -1
+ if c.Bool("no-stream") {
+ times = 1
+ }
+ statsChan := make(chan []*libkpod.ContainerStats)
+ // iterate over the channel until it is closed
+ go func() {
+ // print using goterm
+ printf = tm.Printf
+ println = tm.Println
+ for stats := range statsChan {
+ // Continually refresh statistics
+ tm.Clear()
+ tm.MoveCursor(1, 1)
+ outputStats(stats, c.String("format"), c.Bool("json"))
+ tm.Flush()
+ time.Sleep(time.Second)
+ }
+ }()
+ return getStats(containerServer, c.Args(), c.Bool("all"), statsChan, times)
+}
+
+func getStats(server *libkpod.ContainerServer, args []string, all bool, statsChan chan []*libkpod.ContainerStats, times int) error {
+ ctrs, err := server.ListContainers(isRunning, ctrInList(args))
+ if err != nil {
+ return err
+ }
+ containerStats := map[string]*libkpod.ContainerStats{}
+ for _, ctr := range ctrs {
+ initialStats, err := server.GetContainerStats(ctr, &libkpod.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 := []*libkpod.ContainerStats{}
+ for _, ctr := range ctrs {
+ id := ctr.ID()
+ if _, ok := containerStats[ctr.ID()]; !ok {
+ initialStats, err := server.GetContainerStats(ctr, &libkpod.ContainerStats{})
+ if err != nil {
+ return err
+ }
+ containerStats[id] = initialStats
+ }
+ stats, err := server.GetContainerStats(ctr, containerStats[id])
+ if err != nil {
+ return err
+ }
+ // replace the previous measurement with the current one
+ containerStats[id] = stats
+ reportStats = append(reportStats, stats)
+ }
+ statsChan <- reportStats
+
+ err := server.Update()
+ if err != nil {
+ return err
+ }
+ ctrs, err = server.ListContainers(isRunning, ctrInList(args))
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func outputStats(stats []*libkpod.ContainerStats, format string, json bool) error {
+ if format == "" {
+ outputStatsHeader()
+ }
+ if json {
+ return outputStatsAsJSON(stats)
+ }
+ var err error
+ for _, s := range stats {
+ if format == "" {
+ outputStatsUsingFormatString(s)
+ } else {
+ params := getStatsOutputParams(s)
+ err2 := outputStatsUsingTemplate(format, params)
+ if err2 != nil {
+ err = errors.Wrapf(err, err2.Error())
+ }
+ }
+ }
+ return err
+}
+
+func outputStatsHeader() {
+ printf("%-64s %-16s %-32s %-16s %-24s %-24s %s\n", "CONTAINER", "CPU %", "MEM USAGE / MEM LIMIT", "MEM %", "NET I/O", "BLOCK I/O", "PIDS")
+}
+
+func outputStatsUsingFormatString(stats *libkpod.ContainerStats) {
+ printf("%-64s %-16s %-32s %-16s %-24s %-24s %d\n", stats.Container, floatToPercentString(stats.CPU), combineHumanValues(stats.MemUsage, stats.MemLimit), floatToPercentString(stats.MemPerc), combineHumanValues(stats.NetInput, stats.NetOutput), combineHumanValues(stats.BlockInput, stats.BlockOutput), stats.PIDs)
+}
+
+func combineHumanValues(a, b uint64) string {
+ return fmt.Sprintf("%s / %s", units.HumanSize(float64(a)), units.HumanSize(float64(b)))
+}
+
+func floatToPercentString(f float64) string {
+ return fmt.Sprintf("%.2f %s", f, "%")
+}
+
+func getStatsOutputParams(stats *libkpod.ContainerStats) statsOutputParams {
+ return statsOutputParams{
+ Container: stats.Container,
+ ID: stats.Container,
+ 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,
+ }
+}
+
+func outputStatsUsingTemplate(format string, params statsOutputParams) error {
+ tmpl, err := template.New("stats").Parse(format)
+ if err != nil {
+ return errors.Wrapf(err, "template parsing error")
+ }
+
+ err = tmpl.Execute(os.Stdout, params)
+ if err != nil {
+ return err
+ }
+ println()
+ return nil
+}
+
+func outputStatsAsJSON(stats []*libkpod.ContainerStats) error {
+ s, err := json.Marshal(stats)
+ if err != nil {
+ return err
+ }
+ println(s)
+ return nil
+}
+
+func isRunning(ctr *oci.Container) bool {
+ return ctr.State().Status == "running"
+}
+
+func ctrInList(idsOrNames []string) func(ctr *oci.Container) bool {
+ if len(idsOrNames) == 0 {
+ return func(*oci.Container) bool { return true }
+ }
+ return func(ctr *oci.Container) bool {
+ for _, idOrName := range idsOrNames {
+ if strings.HasPrefix(ctr.ID(), idOrName) || strings.HasSuffix(ctr.Name(), idOrName) {
+ return true
+ }
+ }
+ return false
+ }
+}