Skip to content

Commit

Permalink
Podman pod stats -- fix GO template output
Browse files Browse the repository at this point in the history
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 containers#2258

Signed-off-by: baude <[email protected]>
  • Loading branch information
baude committed Feb 10, 2019
1 parent c86e8f1 commit 4994fec
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 24 deletions.
130 changes: 110 additions & 20 deletions cmd/podman/pod_stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ package main

import (
"fmt"
"html/template"
"os"
"reflect"
"strings"
"text/tabwriter"
"time"

"encoding/json"
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand All @@ -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 {
Expand Down
9 changes: 5 additions & 4 deletions docs/podman-stats.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

```
Expand Down
23 changes: 23 additions & 0 deletions test/e2e/pod_stats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
})

})

0 comments on commit 4994fec

Please sign in to comment.