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