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