mirror of
https://github.com/podman-container-tools/podman.git
synced 2026-08-08 09:55:42 +00:00
For the AutocompleteFormat function we expect the correct template struct which is used in the golang template. The function can handle both struct and pointer to a struct. Using the reference is more efficient since it doe snot have to copy the whole struct. Also change some structs to use he actual type from the template instead of some nested one to make sure it has to correct fields. Signed-off-by: Paul Holzinger <pholzing@redhat.com>
289 lines
8.1 KiB
Go
289 lines
8.1 KiB
Go
package containers
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
tm "github.com/buger/goterm"
|
|
"github.com/containers/common/pkg/completion"
|
|
"github.com/containers/common/pkg/report"
|
|
"github.com/containers/podman/v4/cmd/podman/common"
|
|
"github.com/containers/podman/v4/cmd/podman/registry"
|
|
"github.com/containers/podman/v4/cmd/podman/validate"
|
|
"github.com/containers/podman/v4/libpod/define"
|
|
"github.com/containers/podman/v4/pkg/domain/entities"
|
|
"github.com/containers/podman/v4/utils"
|
|
"github.com/docker/go-units"
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
statsDescription = "Display percentage of CPU, memory, network I/O, block I/O and PIDs for one or more containers."
|
|
statsCommand = &cobra.Command{
|
|
Use: "stats [options] [CONTAINER...]",
|
|
Short: "Display a live stream of container resource usage statistics",
|
|
Long: statsDescription,
|
|
RunE: stats,
|
|
Args: checkStatOptions,
|
|
ValidArgsFunction: common.AutocompleteContainersRunning,
|
|
Example: `podman stats --all --no-stream
|
|
podman stats ctrID
|
|
podman stats --no-stream --format "table {{.ID}} {{.Name}} {{.MemUsage}}" ctrID`,
|
|
}
|
|
|
|
containerStatsCommand = &cobra.Command{
|
|
Use: statsCommand.Use,
|
|
Short: statsCommand.Short,
|
|
Long: statsCommand.Long,
|
|
RunE: statsCommand.RunE,
|
|
Args: checkStatOptions,
|
|
ValidArgsFunction: statsCommand.ValidArgsFunction,
|
|
Example: `podman container stats --all --no-stream
|
|
podman container stats ctrID
|
|
podman container stats --no-stream --format "table {{.ID}} {{.Name}} {{.MemUsage}}" ctrID`,
|
|
}
|
|
)
|
|
|
|
// statsOptionsCLI is used for storing CLI arguments. Some fields are later
|
|
// used in the backend.
|
|
type statsOptionsCLI struct {
|
|
All bool
|
|
Format string
|
|
Latest bool
|
|
NoReset bool
|
|
NoStream bool
|
|
Interval int
|
|
}
|
|
|
|
var (
|
|
statsOptions statsOptionsCLI
|
|
)
|
|
|
|
func statFlags(cmd *cobra.Command) {
|
|
flags := cmd.Flags()
|
|
|
|
flags.BoolVarP(&statsOptions.All, "all", "a", false, "Show all containers. Only running containers are shown by default. The default is false")
|
|
|
|
formatFlagName := "format"
|
|
flags.StringVar(&statsOptions.Format, formatFlagName, "", "Pretty-print container statistics to JSON or using a Go template")
|
|
_ = cmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&containerStats{}))
|
|
|
|
flags.BoolVar(&statsOptions.NoReset, "no-reset", false, "Disable resetting the screen between intervals")
|
|
flags.BoolVar(&statsOptions.NoStream, "no-stream", false, "Disable streaming stats and only pull the first result, default setting is false")
|
|
intervalFlagName := "interval"
|
|
flags.IntVarP(&statsOptions.Interval, intervalFlagName, "i", 5, "Time in seconds between stats reports")
|
|
_ = cmd.RegisterFlagCompletionFunc(intervalFlagName, completion.AutocompleteNone)
|
|
}
|
|
|
|
func init() {
|
|
registry.Commands = append(registry.Commands, registry.CliCommand{
|
|
Command: statsCommand,
|
|
})
|
|
statFlags(statsCommand)
|
|
validate.AddLatestFlag(statsCommand, &statsOptions.Latest)
|
|
|
|
registry.Commands = append(registry.Commands, registry.CliCommand{
|
|
Command: containerStatsCommand,
|
|
Parent: containerCmd,
|
|
})
|
|
statFlags(containerStatsCommand)
|
|
validate.AddLatestFlag(containerStatsCommand, &statsOptions.Latest)
|
|
}
|
|
|
|
// stats is different in that it will assume running containers if
|
|
// no input is given, so we need to validate differently
|
|
func checkStatOptions(cmd *cobra.Command, args []string) error {
|
|
opts := 0
|
|
if statsOptions.All {
|
|
opts++
|
|
}
|
|
if statsOptions.Latest {
|
|
opts++
|
|
}
|
|
if len(args) > 0 {
|
|
opts++
|
|
}
|
|
if opts > 1 {
|
|
return errors.Errorf("--all, --latest and containers cannot be used together")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func stats(cmd *cobra.Command, args []string) error {
|
|
// Convert to the entities options. We should not leak CLI-only
|
|
// options into the backend and separate concerns.
|
|
opts := entities.ContainerStatsOptions{
|
|
Latest: statsOptions.Latest,
|
|
Stream: !statsOptions.NoStream,
|
|
Interval: statsOptions.Interval,
|
|
}
|
|
statsChan, err := registry.ContainerEngine().ContainerStats(registry.Context(), args, opts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for report := range statsChan {
|
|
if report.Error != nil {
|
|
return report.Error
|
|
}
|
|
if err := outputStats(cmd, report.Stats); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func outputStats(cmd *cobra.Command, 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 %",
|
|
"NetIO": "NET IO",
|
|
"BlockIO": "BLOCK IO",
|
|
"PIDS": "PIDS",
|
|
})
|
|
if !statsOptions.NoReset {
|
|
tm.Clear()
|
|
tm.MoveCursor(1, 1)
|
|
tm.Flush()
|
|
}
|
|
stats := make([]containerStats, 0, len(reports))
|
|
for _, r := range reports {
|
|
stats = append(stats, containerStats{r})
|
|
}
|
|
if report.IsJSON(statsOptions.Format) {
|
|
return outputJSON(stats)
|
|
}
|
|
|
|
rpt := report.New(os.Stdout, cmd.Name())
|
|
defer rpt.Flush()
|
|
|
|
var err error
|
|
if cmd.Flags().Changed("format") {
|
|
rpt, err = rpt.Parse(report.OriginUser, statsOptions.Format)
|
|
} else {
|
|
format := "{{range .}}{{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDS}}\t{{.UpTime}}\t{{.AVGCPU}}\n{{end -}}"
|
|
rpt, err = rpt.Parse(report.OriginPodman, format)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if rpt.RenderHeaders {
|
|
if err := rpt.Execute(headers); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return rpt.Execute(stats)
|
|
}
|
|
|
|
type containerStats struct {
|
|
define.ContainerStats
|
|
}
|
|
|
|
func (s *containerStats) ID() string {
|
|
return s.ContainerID[0:12]
|
|
}
|
|
|
|
func (s *containerStats) CPUPerc() string {
|
|
return floatToPercentString(s.CPU)
|
|
}
|
|
|
|
func (s *containerStats) AVGCPU() string {
|
|
return floatToPercentString(s.AvgCPU)
|
|
}
|
|
|
|
func (s *containerStats) Up() string {
|
|
return (s.UpTime.String())
|
|
}
|
|
|
|
func (s *containerStats) MemPerc() string {
|
|
return floatToPercentString(s.ContainerStats.MemPerc)
|
|
}
|
|
|
|
func (s *containerStats) NetIO() string {
|
|
return combineHumanValues(s.NetInput, s.NetOutput)
|
|
}
|
|
|
|
func (s *containerStats) BlockIO() string {
|
|
return combineHumanValues(s.BlockInput, s.BlockOutput)
|
|
}
|
|
|
|
func (s *containerStats) PIDS() string {
|
|
if s.PIDs == 0 {
|
|
// If things go bazinga, return a safe value
|
|
return "--"
|
|
}
|
|
return fmt.Sprintf("%d", s.PIDs)
|
|
}
|
|
|
|
func (s *containerStats) MemUsage() string {
|
|
return combineHumanValues(s.ContainerStats.MemUsage, s.ContainerStats.MemLimit)
|
|
}
|
|
|
|
func (s *containerStats) MemUsageBytes() string {
|
|
return combineBytesValues(s.ContainerStats.MemUsage, s.ContainerStats.MemLimit)
|
|
}
|
|
|
|
func floatToPercentString(f float64) string {
|
|
strippedFloat, err := utils.RemoveScientificNotationFromFloat(f)
|
|
if err != nil || strippedFloat == 0 {
|
|
// If things go bazinga, return a safe value
|
|
return "--"
|
|
}
|
|
return fmt.Sprintf("%.2f", strippedFloat) + "%"
|
|
}
|
|
|
|
func combineHumanValues(a, b uint64) string {
|
|
if a == 0 && b == 0 {
|
|
return "-- / --"
|
|
}
|
|
return fmt.Sprintf("%s / %s", units.HumanSize(float64(a)), units.HumanSize(float64(b)))
|
|
}
|
|
|
|
func combineBytesValues(a, b uint64) string {
|
|
if a == 0 && b == 0 {
|
|
return "-- / --"
|
|
}
|
|
return fmt.Sprintf("%s / %s", units.BytesSize(float64(a)), units.BytesSize(float64(b)))
|
|
}
|
|
|
|
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"`
|
|
BlockIO string `json:"block_io"`
|
|
Pids string `json:"pids"`
|
|
}
|
|
jstats := make([]jstat, 0, len(stats))
|
|
for _, j := range stats {
|
|
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(),
|
|
BlockIO: j.BlockIO(),
|
|
Pids: j.PIDS(),
|
|
})
|
|
}
|
|
b, err := json.MarshalIndent(jstats, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(string(b))
|
|
return nil
|
|
}
|