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  }