github.com/walkingsparrow/docker@v1.4.2-0.20151218153551-b708a2249bfa/daemon/stats_collector_unix.go (about)

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