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