github.com/tompao/docker@v1.9.1/daemon/stats_collector_unix.go (about) 1 // +build !windows 2 3 package daemon 4 5 import ( 6 "bufio" 7 "os" 8 "strconv" 9 "strings" 10 "sync" 11 "time" 12 13 "github.com/Sirupsen/logrus" 14 "github.com/docker/docker/daemon/execdriver" 15 derr "github.com/docker/docker/errors" 16 "github.com/docker/docker/pkg/pubsub" 17 lntypes "github.com/docker/libnetwork/types" 18 "github.com/opencontainers/runc/libcontainer" 19 "github.com/opencontainers/runc/libcontainer/system" 20 ) 21 22 // newStatsCollector returns a new statsCollector that collections 23 // network and cgroup stats for a registered container at the specified 24 // interval. The collector allows non-running containers to be added 25 // and will start processing stats when they are started. 26 func newStatsCollector(interval time.Duration) *statsCollector { 27 s := &statsCollector{ 28 interval: interval, 29 publishers: make(map[*Container]*pubsub.Publisher), 30 clockTicksPerSecond: uint64(system.GetClockTicks()), 31 bufReader: bufio.NewReaderSize(nil, 128), 32 } 33 go s.run() 34 return s 35 } 36 37 // statsCollector manages and provides container resource stats 38 type statsCollector struct { 39 m sync.Mutex 40 interval time.Duration 41 clockTicksPerSecond uint64 42 publishers map[*Container]*pubsub.Publisher 43 bufReader *bufio.Reader 44 } 45 46 // collect registers the container with the collector and adds it to 47 // the event loop for collection on the specified interval returning 48 // a channel for the subscriber to receive on. 49 func (s *statsCollector) collect(c *Container) chan interface{} { 50 s.m.Lock() 51 defer s.m.Unlock() 52 publisher, exists := s.publishers[c] 53 if !exists { 54 publisher = pubsub.NewPublisher(100*time.Millisecond, 1024) 55 s.publishers[c] = publisher 56 } 57 return publisher.Subscribe() 58 } 59 60 // stopCollection closes the channels for all subscribers and removes 61 // the container from metrics collection. 62 func (s *statsCollector) stopCollection(c *Container) { 63 s.m.Lock() 64 if publisher, exists := s.publishers[c]; exists { 65 publisher.Close() 66 delete(s.publishers, c) 67 } 68 s.m.Unlock() 69 } 70 71 // unsubscribe removes a specific subscriber from receiving updates for a container's stats. 72 func (s *statsCollector) unsubscribe(c *Container, ch chan interface{}) { 73 s.m.Lock() 74 publisher := s.publishers[c] 75 if publisher != nil { 76 publisher.Evict(ch) 77 if publisher.Len() == 0 { 78 delete(s.publishers, c) 79 } 80 } 81 s.m.Unlock() 82 } 83 84 func (s *statsCollector) run() { 85 type publishersPair struct { 86 container *Container 87 publisher *pubsub.Publisher 88 } 89 // we cannot determine the capacity here. 90 // it will grow enough in first iteration 91 var pairs []publishersPair 92 93 for range time.Tick(s.interval) { 94 // it does not make sense in the first iteration, 95 // but saves allocations in further iterations 96 pairs = pairs[:0] 97 98 s.m.Lock() 99 for container, publisher := range s.publishers { 100 // copy pointers here to release the lock ASAP 101 pairs = append(pairs, publishersPair{container, publisher}) 102 } 103 s.m.Unlock() 104 if len(pairs) == 0 { 105 continue 106 } 107 108 systemUsage, err := s.getSystemCPUUsage() 109 if err != nil { 110 logrus.Errorf("collecting system cpu usage: %v", err) 111 continue 112 } 113 114 for _, pair := range pairs { 115 stats, err := pair.container.stats() 116 if err != nil { 117 if err != execdriver.ErrNotRunning { 118 logrus.Errorf("collecting stats for %s: %v", pair.container.ID, err) 119 } 120 continue 121 } 122 stats.SystemUsage = systemUsage 123 124 // Retrieve the nw statistics from libnetwork and inject them in the Stats 125 if nwStats, err := s.getNetworkStats(pair.container); err == nil { 126 stats.Interfaces = nwStats 127 } 128 pair.publisher.Publish(stats) 129 } 130 } 131 } 132 133 const nanoSecondsPerSecond = 1e9 134 135 // getSystemCPUUsage returns the host system's cpu usage in 136 // nanoseconds. An error is returned if the format of the underlying 137 // file does not match. 138 // 139 // Uses /proc/stat defined by POSIX. Looks for the cpu 140 // statistics line and then sums up the first seven fields 141 // provided. See `man 5 proc` for details on specific field 142 // information. 143 func (s *statsCollector) getSystemCPUUsage() (uint64, error) { 144 var line string 145 f, err := os.Open("/proc/stat") 146 if err != nil { 147 return 0, err 148 } 149 defer func() { 150 s.bufReader.Reset(nil) 151 f.Close() 152 }() 153 s.bufReader.Reset(f) 154 err = nil 155 for err == nil { 156 line, err = s.bufReader.ReadString('\n') 157 if err != nil { 158 break 159 } 160 parts := strings.Fields(line) 161 switch parts[0] { 162 case "cpu": 163 if len(parts) < 8 { 164 return 0, derr.ErrorCodeBadCPUFields 165 } 166 var totalClockTicks uint64 167 for _, i := range parts[1:8] { 168 v, err := strconv.ParseUint(i, 10, 64) 169 if err != nil { 170 return 0, derr.ErrorCodeBadCPUInt.WithArgs(i, err) 171 } 172 totalClockTicks += v 173 } 174 return (totalClockTicks * nanoSecondsPerSecond) / 175 s.clockTicksPerSecond, nil 176 } 177 } 178 return 0, derr.ErrorCodeBadStatFormat 179 } 180 181 func (s *statsCollector) getNetworkStats(c *Container) ([]*libcontainer.NetworkInterface, error) { 182 var list []*libcontainer.NetworkInterface 183 184 sb, err := c.daemon.netController.SandboxByID(c.NetworkSettings.SandboxID) 185 if err != nil { 186 return list, err 187 } 188 189 stats, err := sb.Statistics() 190 if err != nil { 191 return list, err 192 } 193 194 // Convert libnetwork nw stats into libcontainer nw stats 195 for ifName, ifStats := range stats { 196 list = append(list, convertLnNetworkStats(ifName, ifStats)) 197 } 198 199 return list, nil 200 } 201 202 func convertLnNetworkStats(name string, stats *lntypes.InterfaceStatistics) *libcontainer.NetworkInterface { 203 n := &libcontainer.NetworkInterface{Name: name} 204 n.RxBytes = stats.RxBytes 205 n.RxPackets = stats.RxPackets 206 n.RxErrors = stats.RxErrors 207 n.RxDropped = stats.RxDropped 208 n.TxBytes = stats.TxBytes 209 n.TxPackets = stats.TxPackets 210 n.TxErrors = stats.TxErrors 211 n.TxDropped = stats.TxDropped 212 return n 213 }