github.com/gondor/docker@v1.9.0-rc1/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/daemon/execdriver"
    15  	derr "github.com/docker/docker/errors"
    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  		clockTicksPerSecond: 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  	clockTicksPerSecond 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  		// it does not make sense in the first iteration,
    93  		// but saves allocations in further iterations
    94  		pairs = pairs[:0]
    95  
    96  		s.m.Lock()
    97  		for container, publisher := range s.publishers {
    98  			// copy pointers here to release the lock ASAP
    99  			pairs = append(pairs, publishersPair{container, publisher})
   100  		}
   101  		s.m.Unlock()
   102  		if len(pairs) == 0 {
   103  			continue
   104  		}
   105  
   106  		systemUsage, err := s.getSystemCPUUsage()
   107  		if err != nil {
   108  			logrus.Errorf("collecting system cpu usage: %v", err)
   109  			continue
   110  		}
   111  
   112  		for _, pair := range pairs {
   113  			stats, err := pair.container.stats()
   114  			if err != nil {
   115  				if err != execdriver.ErrNotRunning {
   116  					logrus.Errorf("collecting stats for %s: %v", pair.container.ID, err)
   117  				}
   118  				continue
   119  			}
   120  			stats.SystemUsage = systemUsage
   121  			pair.publisher.Publish(stats)
   122  		}
   123  	}
   124  }
   125  
   126  const nanoSecondsPerSecond = 1e9
   127  
   128  // getSystemCPUUsage returns the host system's cpu usage in
   129  // nanoseconds. An error is returned if the format of the underlying
   130  // file does not match.
   131  //
   132  // Uses /proc/stat defined by POSIX. Looks for the cpu
   133  // statistics line and then sums up the first seven fields
   134  // provided. See `man 5 proc` for details on specific field
   135  // information.
   136  func (s *statsCollector) getSystemCPUUsage() (uint64, error) {
   137  	var line string
   138  	f, err := os.Open("/proc/stat")
   139  	if err != nil {
   140  		return 0, err
   141  	}
   142  	defer func() {
   143  		s.bufReader.Reset(nil)
   144  		f.Close()
   145  	}()
   146  	s.bufReader.Reset(f)
   147  	err = nil
   148  	for err == nil {
   149  		line, err = s.bufReader.ReadString('\n')
   150  		if err != nil {
   151  			break
   152  		}
   153  		parts := strings.Fields(line)
   154  		switch parts[0] {
   155  		case "cpu":
   156  			if len(parts) < 8 {
   157  				return 0, derr.ErrorCodeBadCPUFields
   158  			}
   159  			var totalClockTicks uint64
   160  			for _, i := range parts[1:8] {
   161  				v, err := strconv.ParseUint(i, 10, 64)
   162  				if err != nil {
   163  					return 0, derr.ErrorCodeBadCPUInt.WithArgs(i, err)
   164  				}
   165  				totalClockTicks += v
   166  			}
   167  			return (totalClockTicks * nanoSecondsPerSecond) /
   168  				s.clockTicksPerSecond, nil
   169  		}
   170  	}
   171  	return 0, derr.ErrorCodeBadStatFormat
   172  }