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