github.com/circular-dark/docker@v1.7.0/api/client/stats.go (about) 1 package client 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "net/url" 8 "sort" 9 "strings" 10 "sync" 11 "text/tabwriter" 12 "time" 13 14 "github.com/docker/docker/api/types" 15 flag "github.com/docker/docker/pkg/mflag" 16 "github.com/docker/docker/pkg/units" 17 ) 18 19 type containerStats struct { 20 Name string 21 CPUPercentage float64 22 Memory float64 23 MemoryLimit float64 24 MemoryPercentage float64 25 NetworkRx float64 26 NetworkTx float64 27 mu sync.RWMutex 28 err error 29 } 30 31 func (s *containerStats) Collect(cli *DockerCli, streamStats bool) { 32 v := url.Values{} 33 if streamStats { 34 v.Set("stream", "1") 35 } else { 36 v.Set("stream", "0") 37 } 38 stream, _, err := cli.call("GET", "/containers/"+s.Name+"/stats?"+v.Encode(), nil, nil) 39 if err != nil { 40 s.mu.Lock() 41 s.err = err 42 s.mu.Unlock() 43 return 44 } 45 defer stream.Close() 46 var ( 47 previousCPU uint64 48 previousSystem uint64 49 dec = json.NewDecoder(stream) 50 u = make(chan error, 1) 51 ) 52 go func() { 53 for { 54 var v *types.Stats 55 if err := dec.Decode(&v); err != nil { 56 u <- err 57 return 58 } 59 var ( 60 memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0 61 cpuPercent = 0.0 62 ) 63 previousCPU = v.PreCpuStats.CpuUsage.TotalUsage 64 previousSystem = v.PreCpuStats.SystemUsage 65 cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v) 66 s.mu.Lock() 67 s.CPUPercentage = cpuPercent 68 s.Memory = float64(v.MemoryStats.Usage) 69 s.MemoryLimit = float64(v.MemoryStats.Limit) 70 s.MemoryPercentage = memPercent 71 s.NetworkRx = float64(v.Network.RxBytes) 72 s.NetworkTx = float64(v.Network.TxBytes) 73 s.mu.Unlock() 74 u <- nil 75 if !streamStats { 76 return 77 } 78 } 79 }() 80 for { 81 select { 82 case <-time.After(2 * time.Second): 83 // zero out the values if we have not received an update within 84 // the specified duration. 85 s.mu.Lock() 86 s.CPUPercentage = 0 87 s.Memory = 0 88 s.MemoryPercentage = 0 89 s.mu.Unlock() 90 case err := <-u: 91 if err != nil { 92 s.mu.Lock() 93 s.err = err 94 s.mu.Unlock() 95 return 96 } 97 } 98 if !streamStats { 99 return 100 } 101 } 102 } 103 104 func (s *containerStats) Display(w io.Writer) error { 105 s.mu.RLock() 106 defer s.mu.RUnlock() 107 if s.err != nil { 108 return s.err 109 } 110 fmt.Fprintf(w, "%s\t%.2f%%\t%s/%s\t%.2f%%\t%s/%s\n", 111 s.Name, 112 s.CPUPercentage, 113 units.HumanSize(s.Memory), units.HumanSize(s.MemoryLimit), 114 s.MemoryPercentage, 115 units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx)) 116 return nil 117 } 118 119 // CmdStats displays a live stream of resource usage statistics for one or more containers. 120 // 121 // This shows real-time information on CPU usage, memory usage, and network I/O. 122 // 123 // Usage: docker stats CONTAINER [CONTAINER...] 124 func (cli *DockerCli) CmdStats(args ...string) error { 125 cmd := cli.Subcmd("stats", "CONTAINER [CONTAINER...]", "Display a live stream of one or more containers' resource usage statistics", true) 126 noStream := cmd.Bool([]string{"-no-stream"}, false, "Disable streaming stats and only pull the first result") 127 cmd.Require(flag.Min, 1) 128 cmd.ParseFlags(args, true) 129 130 names := cmd.Args() 131 sort.Strings(names) 132 var ( 133 cStats []*containerStats 134 w = tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) 135 ) 136 printHeader := func() { 137 if !*noStream { 138 fmt.Fprint(cli.out, "\033[2J") 139 fmt.Fprint(cli.out, "\033[H") 140 } 141 io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE/LIMIT\tMEM %\tNET I/O\n") 142 } 143 for _, n := range names { 144 s := &containerStats{Name: n} 145 cStats = append(cStats, s) 146 go s.Collect(cli, !*noStream) 147 } 148 // do a quick pause so that any failed connections for containers that do not exist are able to be 149 // evicted before we display the initial or default values. 150 time.Sleep(1500 * time.Millisecond) 151 var errs []string 152 for _, c := range cStats { 153 c.mu.Lock() 154 if c.err != nil { 155 errs = append(errs, fmt.Sprintf("%s: %v", c.Name, c.err)) 156 } 157 c.mu.Unlock() 158 } 159 if len(errs) > 0 { 160 return fmt.Errorf("%s", strings.Join(errs, ", ")) 161 } 162 for range time.Tick(500 * time.Millisecond) { 163 printHeader() 164 toRemove := []int{} 165 for i, s := range cStats { 166 if err := s.Display(w); err != nil && !*noStream { 167 toRemove = append(toRemove, i) 168 } 169 } 170 for j := len(toRemove) - 1; j >= 0; j-- { 171 i := toRemove[j] 172 cStats = append(cStats[:i], cStats[i+1:]...) 173 } 174 if len(cStats) == 0 { 175 return nil 176 } 177 w.Flush() 178 if *noStream { 179 break 180 } 181 } 182 return nil 183 } 184 185 func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.Stats) float64 { 186 var ( 187 cpuPercent = 0.0 188 // calculate the change for the cpu usage of the container in between readings 189 cpuDelta = float64(v.CpuStats.CpuUsage.TotalUsage - previousCPU) 190 // calculate the change for the entire system between readings 191 systemDelta = float64(v.CpuStats.SystemUsage - previousSystem) 192 ) 193 194 if systemDelta > 0.0 && cpuDelta > 0.0 { 195 cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CpuStats.CpuUsage.PercpuUsage)) * 100.0 196 } 197 return cpuPercent 198 }