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