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