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 }