summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbaude <bbaude@redhat.com>2019-02-10 08:58:46 -0600
committerbaude <bbaude@redhat.com>2019-02-10 11:27:09 -0600
commit4994fecd4604e17844d9e91fa710407701d6d85a (patch)
tree20bcb3c8e15f07a1c2a7d889d7c4813cd1d25086
parentc86e8f180c8b0cdbd1bafe327c4671370096c45f (diff)
downloadpodman-4994fecd4604e17844d9e91fa710407701d6d85a.tar.gz
podman-4994fecd4604e17844d9e91fa710407701d6d85a.tar.bz2
podman-4994fecd4604e17844d9e91fa710407701d6d85a.zip
Podman pod stats -- fix GO template output
Go templates were not being processed or printed correctly for podman pod stats. Added the ability to do templates as well as honor the table identifier. Fixes #2258 Signed-off-by: baude <bbaude@redhat.com>
-rw-r--r--cmd/podman/pod_stats.go130
-rw-r--r--docs/podman-stats.1.md9
-rw-r--r--test/e2e/pod_stats_test.go23
3 files changed, 138 insertions, 24 deletions
diff --git a/cmd/podman/pod_stats.go b/cmd/podman/pod_stats.go
index ccbcf9f7c..02dee68ac 100644
--- a/cmd/podman/pod_stats.go
+++ b/cmd/podman/pod_stats.go
@@ -2,7 +2,11 @@ package main
import (
"fmt"
+ "html/template"
+ "os"
+ "reflect"
"strings"
+ "text/tabwriter"
"time"
"encoding/json"
@@ -136,6 +140,25 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error {
step = 0
}
+ headerNames := make(map[string]string)
+ if c.Format != "" {
+ // Make a map of the field names for the headers
+ v := reflect.ValueOf(podStatOut{})
+ t := v.Type()
+ for i := 0; i < t.NumField(); i++ {
+ value := strings.ToUpper(splitCamelCase(t.Field(i).Name))
+ switch value {
+ case "CPU":
+ value = value + " %"
+ case "MEM":
+ value = value + " %"
+ case "MEM USAGE":
+ value = "MEM USAGE / LIMIT"
+ }
+ headerNames[t.Field(i).Name] = value
+ }
+ }
+
for i := 0; i < times; i += step {
var newStats []*libpod.PodContainerStats
for _, p := range pods {
@@ -163,7 +186,14 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error {
outputJson(newStats)
} else {
- outputToStdOut(newStats)
+ results := podContainerStatsToPodStatOut(newStats)
+ if len(format) == 0 {
+ outputToStdOut(results)
+ } else {
+ if err := printPSFormat(c.Format, results, headerNames); err != nil {
+ return err
+ }
+ }
}
time.Sleep(time.Second)
previousPodStats := new([]*libpod.PodContainerStats)
@@ -177,28 +207,88 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error {
return nil
}
-func outputToStdOut(stats []*libpod.PodContainerStats) {
- outFormat := ("%-14s %-14s %-12s %-6s %-19s %-6s %-19s %-19s %-4s\n")
- fmt.Printf(outFormat, "POD", "CID", "NAME", "CPU %", "MEM USAGE/ LIMIT", "MEM %", "NET IO", "BLOCK IO", "PIDS")
- for _, i := range stats {
- if len(i.ContainerStats) == 0 {
- fmt.Printf(outFormat, i.Pod.ID()[:12], "--", "--", "--", "--", "--", "--", "--", "--")
- }
- for _, c := range i.ContainerStats {
- cpu := floatToPercentString(c.CPU)
- memUsage := combineHumanValues(c.MemUsage, c.MemLimit)
- memPerc := floatToPercentString(c.MemPerc)
- netIO := combineHumanValues(c.NetInput, c.NetOutput)
- blockIO := combineHumanValues(c.BlockInput, c.BlockOutput)
- pids := pidsToString(c.PIDs)
- containerName := c.Name
- if len(c.Name) > 10 {
- containerName = containerName[:10]
+func podContainerStatsToPodStatOut(stats []*libpod.PodContainerStats) []*podStatOut {
+ var out []*podStatOut
+ for _, p := range stats {
+ for _, c := range p.ContainerStats {
+ o := podStatOut{
+ CPU: floatToPercentString(c.CPU),
+ MemUsage: combineHumanValues(c.MemUsage, c.MemLimit),
+ Mem: floatToPercentString(c.MemPerc),
+ NetIO: combineHumanValues(c.NetInput, c.NetOutput),
+ BlockIO: combineHumanValues(c.BlockInput, c.BlockOutput),
+ PIDS: pidsToString(c.PIDs),
+ CID: c.ContainerID[:12],
+ Name: c.Name,
+ Pod: p.Pod.ID()[:12],
}
- fmt.Printf(outFormat, i.Pod.ID()[:12], c.ContainerID[:12], containerName, cpu, memUsage, memPerc, netIO, blockIO, pids)
+ out = append(out, &o)
+ }
+ }
+ return out
+}
+
+type podStatOut struct {
+ CPU string
+ MemUsage string
+ Mem string
+ NetIO string
+ BlockIO string
+ PIDS string
+ Pod string
+ CID string
+ Name string
+}
+
+func printPSFormat(format string, stats []*podStatOut, headerNames map[string]string) error {
+ if len(stats) == 0 {
+ return nil
+ }
+
+ // Use a tabwriter to align column format
+ w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
+ // Spit out the header if "table" is present in the format
+ if strings.HasPrefix(format, "table") {
+ hformat := strings.Replace(strings.TrimSpace(format[5:]), " ", "\t", -1)
+ format = hformat
+ headerTmpl, err := template.New("header").Parse(hformat)
+ if err != nil {
+ return err
+ }
+ if err := headerTmpl.Execute(w, headerNames); err != nil {
+ return err
+ }
+ fmt.Fprintln(w, "")
+ }
+
+ // Spit out the data rows now
+ dataTmpl, err := template.New("data").Parse(format)
+ if err != nil {
+ return err
+ }
+ for _, container := range stats {
+ if err := dataTmpl.Execute(w, container); err != nil {
+ return err
+ }
+ fmt.Fprintln(w, "")
+ }
+ // Flush the writer
+ return w.Flush()
+
+}
+
+func outputToStdOut(stats []*podStatOut) {
+ w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
+ outFormat := ("%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n")
+ fmt.Fprintf(w, outFormat, "POD", "CID", "NAME", "CPU %", "MEM USAGE/ LIMIT", "MEM %", "NET IO", "BLOCK IO", "PIDS")
+ for _, i := range stats {
+ if len(stats) == 0 {
+ fmt.Fprintf(w, outFormat, i.Pod, "--", "--", "--", "--", "--", "--", "--", "--")
+ } else {
+ fmt.Fprintf(w, outFormat, i.Pod, i.CID, i.Name, i.CPU, i.MemUsage, i.Mem, i.NetIO, i.BlockIO, i.PIDS)
}
}
- fmt.Println()
+ w.Flush()
}
func getPreviousPodContainerStats(podID string, prev []*libpod.PodContainerStats) map[string]*libpod.ContainerStats {
diff --git a/docs/podman-stats.1.md b/docs/podman-stats.1.md
index 8fc765326..d0b56b2e6 100644
--- a/docs/podman-stats.1.md
+++ b/docs/podman-stats.1.md
@@ -36,16 +36,17 @@ Valid placeholders for the Go template are listed below:
| **Placeholder** | **Description** |
| --------------- | --------------- |
-| .ID | Container ID |
+| .Pod | Pod ID |
+| .CID | Container ID |
| .Name | Container Name |
-| .CPUPerc | CPU percentage |
+| .CPU | CPU percentage |
| .MemUsage | Memory usage |
-| .MemPerc | Memory percentage |
+| .Mem | Memory percentage |
| .NetIO | Network IO |
| .BlockIO | Block IO |
| .PIDS | Number of PIDs |
-
+When using a GO template, you may preceed the format with `table` to print headers.
## EXAMPLE
```
diff --git a/test/e2e/pod_stats_test.go b/test/e2e/pod_stats_test.go
index 43d089a24..e330c3a39 100644
--- a/test/e2e/pod_stats_test.go
+++ b/test/e2e/pod_stats_test.go
@@ -147,5 +147,28 @@ var _ = Describe("Podman pod stats", func() {
Expect(stats.ExitCode()).To(Equal(0))
Expect(stats.IsJSONOutputValid()).To(BeTrue())
})
+ It("podman stats with GO template", func() {
+ _, ec, podid := podmanTest.CreatePod("")
+ Expect(ec).To(Equal(0))
+
+ session := podmanTest.RunTopContainerInPod("", podid)
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ stats := podmanTest.Podman([]string{"pod", "stats", "-a", "--no-reset", "--no-stream", "--format", "\"table {{.CID}} {{.Pod}} {{.Mem}} {{.MemUsage}} {{.CPU}} {{.NetIO}} {{.BlockIO}} {{.PIDS}} {{.Pod}}\""})
+ stats.WaitWithDefaultTimeout()
+ Expect(stats.ExitCode()).To(Equal(0))
+ })
+
+ It("podman stats with invalid GO template", func() {
+ _, ec, podid := podmanTest.CreatePod("")
+ Expect(ec).To(Equal(0))
+
+ session := podmanTest.RunTopContainerInPod("", podid)
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ stats := podmanTest.Podman([]string{"pod", "stats", "-a", "--no-reset", "--no-stream", "--format", "\"table {{.ID}} \""})
+ stats.WaitWithDefaultTimeout()
+ Expect(stats.ExitCode()).ToNot(Equal(0))
+ })
})