github.com/dougm/docker@v1.5.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 log "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 } 28 go s.run() 29 return s 30 } 31 32 // statsCollector manages and provides container resource stats 33 type statsCollector struct { 34 m sync.Mutex 35 interval time.Duration 36 clockTicks uint64 37 publishers map[*Container]*pubsub.Publisher 38 } 39 40 // collect registers the container with the collector and adds it to 41 // the event loop for collection on the specified interval returning 42 // a channel for the subscriber to receive on. 43 func (s *statsCollector) collect(c *Container) chan interface{} { 44 s.m.Lock() 45 defer s.m.Unlock() 46 publisher, exists := s.publishers[c] 47 if !exists { 48 publisher = pubsub.NewPublisher(100*time.Millisecond, 1024) 49 s.publishers[c] = publisher 50 } 51 return publisher.Subscribe() 52 } 53 54 // stopCollection closes the channels for all subscribers and removes 55 // the container from metrics collection. 56 func (s *statsCollector) stopCollection(c *Container) { 57 s.m.Lock() 58 if publisher, exists := s.publishers[c]; exists { 59 publisher.Close() 60 delete(s.publishers, c) 61 } 62 s.m.Unlock() 63 } 64 65 // unsubscribe removes a specific subscriber from receiving updates for a container's stats. 66 func (s *statsCollector) unsubscribe(c *Container, ch chan interface{}) { 67 s.m.Lock() 68 publisher := s.publishers[c] 69 if publisher != nil { 70 publisher.Evict(ch) 71 if publisher.Len() == 0 { 72 delete(s.publishers, c) 73 } 74 } 75 s.m.Unlock() 76 } 77 78 func (s *statsCollector) run() { 79 for _ = range time.Tick(s.interval) { 80 for container, publisher := range s.publishers { 81 systemUsage, err := s.getSystemCpuUsage() 82 if err != nil { 83 log.Errorf("collecting system cpu usage for %s: %v", container.ID, err) 84 continue 85 } 86 stats, err := container.Stats() 87 if err != nil { 88 if err != execdriver.ErrNotRunning { 89 log.Errorf("collecting stats for %s: %v", container.ID, err) 90 } 91 continue 92 } 93 stats.SystemUsage = systemUsage 94 publisher.Publish(stats) 95 } 96 } 97 } 98 99 const nanoSeconds = 1e9 100 101 // getSystemCpuUSage returns the host system's cpu usage in nanoseconds 102 // for the system to match the cgroup readings are returned in the same format. 103 func (s *statsCollector) getSystemCpuUsage() (uint64, error) { 104 f, err := os.Open("/proc/stat") 105 if err != nil { 106 return 0, err 107 } 108 defer f.Close() 109 sc := bufio.NewScanner(f) 110 for sc.Scan() { 111 parts := strings.Fields(sc.Text()) 112 switch parts[0] { 113 case "cpu": 114 if len(parts) < 8 { 115 return 0, fmt.Errorf("invalid number of cpu fields") 116 } 117 var sum uint64 118 for _, i := range parts[1:8] { 119 v, err := strconv.ParseUint(i, 10, 64) 120 if err != nil { 121 return 0, fmt.Errorf("Unable to convert value %s to int: %s", i, err) 122 } 123 sum += v 124 } 125 return (sum * nanoSeconds) / s.clockTicks, nil 126 } 127 } 128 return 0, fmt.Errorf("invalid stat format") 129 }