mirror of
https://github.com/containers/podman.git
synced 2025-12-01 18:49:18 +08:00
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>
This commit is contained in:
@@ -2,7 +2,11 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -136,6 +140,25 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error {
|
|||||||
step = 0
|
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 {
|
for i := 0; i < times; i += step {
|
||||||
var newStats []*libpod.PodContainerStats
|
var newStats []*libpod.PodContainerStats
|
||||||
for _, p := range pods {
|
for _, p := range pods {
|
||||||
@@ -163,7 +186,14 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error {
|
|||||||
outputJson(newStats)
|
outputJson(newStats)
|
||||||
|
|
||||||
} else {
|
} 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)
|
time.Sleep(time.Second)
|
||||||
previousPodStats := new([]*libpod.PodContainerStats)
|
previousPodStats := new([]*libpod.PodContainerStats)
|
||||||
@@ -177,28 +207,88 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func outputToStdOut(stats []*libpod.PodContainerStats) {
|
func podContainerStatsToPodStatOut(stats []*libpod.PodContainerStats) []*podStatOut {
|
||||||
outFormat := ("%-14s %-14s %-12s %-6s %-19s %-6s %-19s %-19s %-4s\n")
|
var out []*podStatOut
|
||||||
fmt.Printf(outFormat, "POD", "CID", "NAME", "CPU %", "MEM USAGE/ LIMIT", "MEM %", "NET IO", "BLOCK IO", "PIDS")
|
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],
|
||||||
|
}
|
||||||
|
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 {
|
for _, i := range stats {
|
||||||
if len(i.ContainerStats) == 0 {
|
if len(stats) == 0 {
|
||||||
fmt.Printf(outFormat, i.Pod.ID()[:12], "--", "--", "--", "--", "--", "--", "--", "--")
|
fmt.Fprintf(w, outFormat, i.Pod, "--", "--", "--", "--", "--", "--", "--", "--")
|
||||||
}
|
} else {
|
||||||
for _, c := range i.ContainerStats {
|
fmt.Fprintf(w, outFormat, i.Pod, i.CID, i.Name, i.CPU, i.MemUsage, i.Mem, i.NetIO, i.BlockIO, i.PIDS)
|
||||||
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]
|
|
||||||
}
|
|
||||||
fmt.Printf(outFormat, i.Pod.ID()[:12], c.ContainerID[:12], containerName, cpu, memUsage, memPerc, netIO, blockIO, pids)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Println()
|
w.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPreviousPodContainerStats(podID string, prev []*libpod.PodContainerStats) map[string]*libpod.ContainerStats {
|
func getPreviousPodContainerStats(podID string, prev []*libpod.PodContainerStats) map[string]*libpod.ContainerStats {
|
||||||
|
|||||||
@@ -36,16 +36,17 @@ Valid placeholders for the Go template are listed below:
|
|||||||
|
|
||||||
| **Placeholder** | **Description** |
|
| **Placeholder** | **Description** |
|
||||||
| --------------- | --------------- |
|
| --------------- | --------------- |
|
||||||
| .ID | Container ID |
|
| .Pod | Pod ID |
|
||||||
|
| .CID | Container ID |
|
||||||
| .Name | Container Name |
|
| .Name | Container Name |
|
||||||
| .CPUPerc | CPU percentage |
|
| .CPU | CPU percentage |
|
||||||
| .MemUsage | Memory usage |
|
| .MemUsage | Memory usage |
|
||||||
| .MemPerc | Memory percentage |
|
| .Mem | Memory percentage |
|
||||||
| .NetIO | Network IO |
|
| .NetIO | Network IO |
|
||||||
| .BlockIO | Block IO |
|
| .BlockIO | Block IO |
|
||||||
| .PIDS | Number of PIDs |
|
| .PIDS | Number of PIDs |
|
||||||
|
|
||||||
|
When using a GO template, you may preceed the format with `table` to print headers.
|
||||||
## EXAMPLE
|
## EXAMPLE
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -147,5 +147,28 @@ var _ = Describe("Podman pod stats", func() {
|
|||||||
Expect(stats.ExitCode()).To(Equal(0))
|
Expect(stats.ExitCode()).To(Equal(0))
|
||||||
Expect(stats.IsJSONOutputValid()).To(BeTrue())
|
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))
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user