github.com/gunjan5/docker@v1.8.2/daemon/stats_collector_unix.go (about) 1 // +build !windows 2 3 package daemon 4 5 import ( 6 "bufio" 7 "fmt" 8 "os" 9 "strconv" 10 "strings" 11 "sync" 12 "time" 13 14 "github.com/Sirupsen/logrus" 15 "github.com/docker/docker/daemon/execdriver" 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 clockTicks: 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 clockTicks 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 systemUsage, err := s.getSystemCpuUsage() 93 if err != nil { 94 logrus.Errorf("collecting system cpu usage: %v", err) 95 continue 96 } 97 98 // it does not make sense in the first iteration, 99 // but saves allocations in further iterations 100 pairs = pairs[:0] 101 102 s.m.Lock() 103 for container, publisher := range s.publishers { 104 // copy pointers here to release the lock ASAP 105 pairs = append(pairs, publishersPair{container, publisher}) 106 } 107 s.m.Unlock() 108 109 for _, pair := range pairs { 110 stats, err := pair.container.Stats() 111 if err != nil { 112 if err != execdriver.ErrNotRunning { 113 logrus.Errorf("collecting stats for %s: %v", pair.container.ID, err) 114 } 115 continue 116 } 117 stats.SystemUsage = systemUsage 118 pair.publisher.Publish(stats) 119 } 120 } 121 } 122 123 const nanoSeconds = 1e9 124 125 // getSystemCpuUSage returns the host system's cpu usage in nanoseconds 126 // for the system to match the cgroup readings are returned in the same format. 127 func (s *statsCollector) getSystemCpuUsage() (uint64, error) { 128 var line string 129 f, err := os.Open("/proc/stat") 130 if err != nil { 131 return 0, err 132 } 133 defer func() { 134 s.bufReader.Reset(nil) 135 f.Close() 136 }() 137 s.bufReader.Reset(f) 138 err = nil 139 for err == nil { 140 line, err = s.bufReader.ReadString('\n') 141 if err != nil { 142 break 143 } 144 parts := strings.Fields(line) 145 switch parts[0] { 146 case "cpu": 147 if len(parts) < 8 { 148 return 0, fmt.Errorf("invalid number of cpu fields") 149 } 150 var sum uint64 151 for _, i := range parts[1:8] { 152 v, err := strconv.ParseUint(i, 10, 64) 153 if err != nil { 154 return 0, fmt.Errorf("Unable to convert value %s to int: %s", i, err) 155 } 156 sum += v 157 } 158 return (sum * nanoSeconds) / s.clockTicks, nil 159 } 160 } 161 return 0, fmt.Errorf("invalid stat format") 162 }