github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/cmd/podman/pod_stats.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"html/template"
     6  	"os"
     7  	"reflect"
     8  	"strings"
     9  	"text/tabwriter"
    10  	"time"
    11  
    12  	tm "github.com/buger/goterm"
    13  	"github.com/containers/buildah/pkg/formats"
    14  	"github.com/containers/libpod/cmd/podman/cliconfig"
    15  	"github.com/containers/libpod/libpod"
    16  	"github.com/containers/libpod/libpod/define"
    17  	"github.com/containers/libpod/pkg/adapter"
    18  	"github.com/containers/libpod/pkg/cgroups"
    19  	"github.com/containers/libpod/pkg/rootless"
    20  	"github.com/pkg/errors"
    21  	"github.com/spf13/cobra"
    22  )
    23  
    24  var (
    25  	podStatsCommand     cliconfig.PodStatsValues
    26  	podStatsDescription = `For each specified pod this command will display percentage of CPU, memory, network I/O, block I/O and PIDs for containers in one the pods.`
    27  
    28  	_podStatsCommand = &cobra.Command{
    29  		Use:   "stats [flags] [POD...]",
    30  		Short: "Display a live stream of resource usage statistics for the containers in one or more pods",
    31  		Long:  podStatsDescription,
    32  		RunE: func(cmd *cobra.Command, args []string) error {
    33  			podStatsCommand.InputArgs = args
    34  			podStatsCommand.GlobalFlags = MainGlobalOpts
    35  			podStatsCommand.Remote = remoteclient
    36  			return podStatsCmd(&podStatsCommand)
    37  		},
    38  		Example: `podman stats -a --no-stream
    39    podman stats --no-reset ctrID
    40    podman stats --no-stream --format "table {{.ID}} {{.Name}} {{.MemUsage}}" ctrID`,
    41  	}
    42  )
    43  
    44  func init() {
    45  	podStatsCommand.Command = _podStatsCommand
    46  	podStatsCommand.SetHelpTemplate(HelpTemplate())
    47  	podStatsCommand.SetUsageTemplate(UsageTemplate())
    48  	flags := podStatsCommand.Flags()
    49  	flags.BoolVarP(&podStatsCommand.All, "all", "a", false, "Provide stats for all running pods")
    50  	flags.StringVar(&podStatsCommand.Format, "format", "", "Pretty-print container statistics to JSON or using a Go template")
    51  	flags.BoolVarP(&podStatsCommand.Latest, "latest", "l", false, "Provide stats on the latest pod podman is aware of")
    52  	flags.BoolVar(&podStatsCommand.NoStream, "no-stream", false, "Disable streaming stats and only pull the first result, default setting is false")
    53  	flags.BoolVar(&podStatsCommand.NoReset, "no-reset", false, "Disable resetting the screen between intervals")
    54  	markFlagHiddenForRemoteClient("latest", flags)
    55  }
    56  
    57  func podStatsCmd(c *cliconfig.PodStatsValues) error {
    58  	if rootless.IsRootless() {
    59  		unified, err := cgroups.IsCgroup2UnifiedMode()
    60  		if err != nil {
    61  			return err
    62  		}
    63  		if !unified {
    64  			return errors.New("stats is not supported in rootless mode without cgroups v2")
    65  		}
    66  	}
    67  
    68  	format := c.Format
    69  	all := c.All
    70  	latest := c.Latest
    71  	ctr := 0
    72  	if all {
    73  		ctr += 1
    74  	}
    75  	if latest {
    76  		ctr += 1
    77  	}
    78  	if len(c.InputArgs) > 0 {
    79  		ctr += 1
    80  	}
    81  
    82  	if ctr > 1 {
    83  		return errors.Errorf("--all, --latest and containers cannot be used together")
    84  	}
    85  
    86  	runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand)
    87  	if err != nil {
    88  		return errors.Wrapf(err, "could not get runtime")
    89  	}
    90  	defer runtime.DeferredShutdown(false)
    91  
    92  	times := -1
    93  	if c.NoStream {
    94  		times = 1
    95  	}
    96  
    97  	pods, err := runtime.GetStatPods(c)
    98  	if err != nil {
    99  		return errors.Wrapf(err, "unable to get a list of pods")
   100  	}
   101  
   102  	// Create empty container stat results for our first pass
   103  	var previousPodStats []*adapter.PodContainerStats
   104  	for _, p := range pods {
   105  		cs := make(map[string]*libpod.ContainerStats)
   106  		pcs := adapter.PodContainerStats{
   107  			Pod:            p,
   108  			ContainerStats: cs,
   109  		}
   110  		previousPodStats = append(previousPodStats, &pcs)
   111  	}
   112  
   113  	step := 1
   114  	if times == -1 {
   115  		times = 1
   116  		step = 0
   117  	}
   118  
   119  	headerNames := make(map[string]string)
   120  	if c.Format != "" {
   121  		// Make a map of the field names for the headers
   122  		v := reflect.ValueOf(podStatOut{})
   123  		t := v.Type()
   124  		for i := 0; i < t.NumField(); i++ {
   125  			value := strings.ToUpper(splitCamelCase(t.Field(i).Name))
   126  			switch value {
   127  			case "CPU", "MEM":
   128  				value += " %"
   129  			case "MEM USAGE":
   130  				value = "MEM USAGE / LIMIT"
   131  			}
   132  			headerNames[t.Field(i).Name] = value
   133  		}
   134  	}
   135  
   136  	for i := 0; i < times; i += step {
   137  		var newStats []*adapter.PodContainerStats
   138  		for _, p := range pods {
   139  			prevStat := getPreviousPodContainerStats(p.ID(), previousPodStats)
   140  			newPodStats, err := p.GetPodStats(prevStat)
   141  			if errors.Cause(err) == define.ErrNoSuchPod {
   142  				continue
   143  			}
   144  			if err != nil {
   145  				return err
   146  			}
   147  			newPod := adapter.PodContainerStats{
   148  				Pod:            p,
   149  				ContainerStats: newPodStats,
   150  			}
   151  			newStats = append(newStats, &newPod)
   152  		}
   153  		//Output
   154  		if strings.ToLower(format) != formats.JSONString && !c.NoReset {
   155  			tm.Clear()
   156  			tm.MoveCursor(1, 1)
   157  			tm.Flush()
   158  		}
   159  		if strings.ToLower(format) == formats.JSONString {
   160  			if err := outputJson(newStats); err != nil {
   161  				return err
   162  			}
   163  
   164  		} else {
   165  			results := podContainerStatsToPodStatOut(newStats)
   166  			if len(format) == 0 {
   167  				outputToStdOut(results)
   168  			} else if err := printPSFormat(c.Format, results, headerNames); err != nil {
   169  				return err
   170  			}
   171  		}
   172  		time.Sleep(time.Second)
   173  		previousPodStats := new([]*libpod.PodContainerStats)
   174  		if err := libpod.JSONDeepCopy(newStats, previousPodStats); err != nil {
   175  			return err
   176  		}
   177  		pods, err = runtime.GetStatPods(c)
   178  		if err != nil {
   179  			return err
   180  		}
   181  	}
   182  
   183  	return nil
   184  }
   185  
   186  func podContainerStatsToPodStatOut(stats []*adapter.PodContainerStats) []*podStatOut {
   187  	var out []*podStatOut
   188  	for _, p := range stats {
   189  		for _, c := range p.ContainerStats {
   190  			o := podStatOut{
   191  				CPU:      floatToPercentString(c.CPU),
   192  				MemUsage: combineHumanValues(c.MemUsage, c.MemLimit),
   193  				Mem:      floatToPercentString(c.MemPerc),
   194  				NetIO:    combineHumanValues(c.NetInput, c.NetOutput),
   195  				BlockIO:  combineHumanValues(c.BlockInput, c.BlockOutput),
   196  				PIDS:     pidsToString(c.PIDs),
   197  				CID:      c.ContainerID[:12],
   198  				Name:     c.Name,
   199  				Pod:      p.Pod.ID()[:12],
   200  			}
   201  			out = append(out, &o)
   202  		}
   203  	}
   204  	return out
   205  }
   206  
   207  type podStatOut struct {
   208  	CPU      string
   209  	MemUsage string
   210  	Mem      string
   211  	NetIO    string
   212  	BlockIO  string
   213  	PIDS     string
   214  	Pod      string
   215  	CID      string
   216  	Name     string
   217  }
   218  
   219  func printPSFormat(format string, stats []*podStatOut, headerNames map[string]string) error {
   220  	if len(stats) == 0 {
   221  		return nil
   222  	}
   223  
   224  	// Use a tabwriter to align column format
   225  	w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
   226  	// Spit out the header if "table" is present in the format
   227  	if strings.HasPrefix(format, "table") {
   228  		hformat := strings.Replace(strings.TrimSpace(format[5:]), " ", "\t", -1)
   229  		format = hformat
   230  		headerTmpl, err := template.New("header").Parse(hformat)
   231  		if err != nil {
   232  			return err
   233  		}
   234  		if err := headerTmpl.Execute(w, headerNames); err != nil {
   235  			return err
   236  		}
   237  		fmt.Fprintln(w, "")
   238  	}
   239  
   240  	// Spit out the data rows now
   241  	dataTmpl, err := template.New("data").Parse(format)
   242  	if err != nil {
   243  		return err
   244  	}
   245  	for _, container := range stats {
   246  		if err := dataTmpl.Execute(w, container); err != nil {
   247  			return err
   248  		}
   249  		fmt.Fprintln(w, "")
   250  	}
   251  	// Flush the writer
   252  	return w.Flush()
   253  
   254  }
   255  
   256  func outputToStdOut(stats []*podStatOut) {
   257  	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
   258  	outFormat := "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n"
   259  	fmt.Fprintf(w, outFormat, "POD", "CID", "NAME", "CPU %", "MEM USAGE/ LIMIT", "MEM %", "NET IO", "BLOCK IO", "PIDS")
   260  	for _, i := range stats {
   261  		if len(stats) == 0 {
   262  			fmt.Fprintf(w, outFormat, i.Pod, "--", "--", "--", "--", "--", "--", "--", "--")
   263  		} else {
   264  			fmt.Fprintf(w, outFormat, i.Pod, i.CID, i.Name, i.CPU, i.MemUsage, i.Mem, i.NetIO, i.BlockIO, i.PIDS)
   265  		}
   266  	}
   267  	w.Flush()
   268  }
   269  
   270  func getPreviousPodContainerStats(podID string, prev []*adapter.PodContainerStats) map[string]*libpod.ContainerStats {
   271  	for _, p := range prev {
   272  		if podID == p.Pod.ID() {
   273  			return p.ContainerStats
   274  		}
   275  	}
   276  	return map[string]*libpod.ContainerStats{}
   277  }
   278  
   279  func outputJson(stats []*adapter.PodContainerStats) error {
   280  	b, err := json.MarshalIndent(&stats, "", "     ")
   281  	if err != nil {
   282  		return err
   283  	}
   284  	fmt.Println(string(b))
   285  	return nil
   286  }