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