From 9af0e02676376979e2ba30122d97f09c2e07473e Mon Sep 17 00:00:00 2001 From: cdoern Date: Fri, 11 Jun 2021 08:35:01 -0400 Subject: [PATCH] Podman Stats additional features added Avg Cpu calculation and CPU up time to podman stats. Adding different feature sets in different PRs, CPU first. resolves #9258 Signed-off-by: cdoern --- cmd/podman/containers/stats.go | 16 +++++++++++++++- libpod/define/containerstate.go | 10 +++++++++- libpod/stats.go | 11 +++++++++++ test/e2e/stats_test.go | 11 +++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/cmd/podman/containers/stats.go b/cmd/podman/containers/stats.go index 568e410d28ef..b251cb3268f1 100644 --- a/cmd/podman/containers/stats.go +++ b/cmd/podman/containers/stats.go @@ -146,7 +146,9 @@ func stats(cmd *cobra.Command, args []string) error { func outputStats(reports []define.ContainerStats) error { headers := report.Headers(define.ContainerStats{}, map[string]string{ "ID": "ID", + "UpTime": "CPU TIME", "CPUPerc": "CPU %", + "AVGCPU": "Avg CPU %", "MemUsage": "MEM USAGE / LIMIT", "MemUsageBytes": "MEM USAGE / LIMIT", "MemPerc": "MEM %", @@ -166,7 +168,7 @@ func outputStats(reports []define.ContainerStats) error { if report.IsJSON(statsOptions.Format) { return outputJSON(stats) } - format := "{{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDS}}\n" + format := "{{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDS}}\t{{.UpTime}}\t{{.AVGCPU}}\n" if len(statsOptions.Format) > 0 { format = report.NormalizeFormat(statsOptions.Format) } @@ -202,6 +204,14 @@ func (s *containerStats) CPUPerc() string { return floatToPercentString(s.CPU) } +func (s *containerStats) AVGCPU() string { + return floatToPercentString(s.AvgCPU) +} + +func (s *containerStats) Up() string { + return fmt.Sprint(s.UpTime) +} + func (s *containerStats) MemPerc() string { return floatToPercentString(s.ContainerStats.MemPerc) } @@ -257,7 +267,9 @@ func outputJSON(stats []containerStats) error { type jstat struct { Id string `json:"id"` // nolint Name string `json:"name"` + CPUTime string `json:"cpu_time"` CpuPercent string `json:"cpu_percent"` // nolint + AverageCPU string `json:"avg_cpu"` MemUsage string `json:"mem_usage"` MemPerc string `json:"mem_percent"` NetIO string `json:"net_io"` @@ -269,7 +281,9 @@ func outputJSON(stats []containerStats) error { jstats = append(jstats, jstat{ Id: j.ID(), Name: j.Name, + CPUTime: j.Up(), CpuPercent: j.CPUPerc(), + AverageCPU: j.AVGCPU(), MemUsage: j.MemUsage(), MemPerc: j.MemPerc(), NetIO: j.NetIO(), diff --git a/libpod/define/containerstate.go b/libpod/define/containerstate.go index 5d2bc90990dc..fc272beaa4d5 100644 --- a/libpod/define/containerstate.go +++ b/libpod/define/containerstate.go @@ -1,6 +1,10 @@ package define -import "github.com/pkg/errors" +import ( + "time" + + "github.com/pkg/errors" +) // ContainerStatus represents the current state of a container type ContainerStatus int @@ -120,12 +124,14 @@ func (s ContainerExecStatus) String() string { // ContainerStats contains the statistics information for a running container type ContainerStats struct { + AvgCPU float64 ContainerID string Name string PerCPU []uint64 CPU float64 CPUNano uint64 CPUSystemNano uint64 + DataPoints int64 SystemNano uint64 MemUsage uint64 MemLimit uint64 @@ -135,4 +141,6 @@ type ContainerStats struct { BlockInput uint64 BlockOutput uint64 PIDs uint64 + UpTime time.Duration + Duration uint64 } diff --git a/libpod/stats.go b/libpod/stats.go index f4732b4fc9fa..50728a7411ef 100644 --- a/libpod/stats.go +++ b/libpod/stats.go @@ -56,7 +56,11 @@ func (c *Container) GetContainerStats(previousStats *define.ContainerStats) (*de previousCPU := previousStats.CPUNano now := uint64(time.Now().UnixNano()) + stats.Duration = cgroupStats.CPU.Usage.Total + stats.UpTime = time.Duration(stats.Duration) stats.CPU = calculateCPUPercent(cgroupStats, previousCPU, now, previousStats.SystemNano) + stats.AvgCPU = calculateAvgCPU(stats.CPU, previousStats.AvgCPU, previousStats.DataPoints) + stats.DataPoints = previousStats.DataPoints + 1 stats.MemUsage = cgroupStats.Memory.Usage.Usage stats.MemLimit = getMemLimit(cgroupStats.Memory.Usage.Limit) stats.MemPerc = (float64(stats.MemUsage) / float64(stats.MemLimit)) * 100 @@ -127,3 +131,10 @@ func calculateBlockIO(stats *cgroups.Metrics) (read uint64, write uint64) { } return } + +// calculateAvgCPU calculates the avg CPU percentage given the previous average and the number of data points. +func calculateAvgCPU(statsCPU float64, prevAvg float64, prevData int64) float64 { + total := prevAvg + avgPer := ((total * float64(prevData)) + statsCPU) / (float64(prevData) + 1) + return avgPer +} diff --git a/test/e2e/stats_test.go b/test/e2e/stats_test.go index 2218d72b5a17..cea1975d3c23 100644 --- a/test/e2e/stats_test.go +++ b/test/e2e/stats_test.go @@ -83,6 +83,17 @@ var _ = Describe("Podman stats", func() { Expect(session.ExitCode()).To(Equal(0)) }) + It("podman stats only output CPU data", func() { + session := podmanTest.RunTopContainer("") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"stats", "--all", "--no-stream", "--format", "\"{{.ID}} {{.UpTime}} {{.AVGCPU}}\""}) + session.WaitWithDefaultTimeout() + Expect(session.LineInOutputContains("UpTime").To(BeTrue()) + Expect(session.LineInOutputContains("AVGCPU").To(BeTrue()) + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman stats with json output", func() { var found bool session := podmanTest.RunTopContainer("")