github.com/baris/docker@v1.7.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  	"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  		bufReader:  bufio.NewReaderSize(nil, 128),
    28  	}
    29  	go s.run()
    30  	return s
    31  }
    32  
    33  // statsCollector manages and provides container resource stats
    34  type statsCollector struct {
    35  	m          sync.Mutex
    36  	interval   time.Duration
    37  	clockTicks uint64
    38  	publishers map[*Container]*pubsub.Publisher
    39  	bufReader  *bufio.Reader
    40  }
    41  
    42  // collect registers the container with the collector and adds it to
    43  // the event loop for collection on the specified interval returning
    44  // a channel for the subscriber to receive on.
    45  func (s *statsCollector) collect(c *Container) chan interface{} {
    46  	s.m.Lock()
    47  	defer s.m.Unlock()
    48  	publisher, exists := s.publishers[c]
    49  	if !exists {
    50  		publisher = pubsub.NewPublisher(100*time.Millisecond, 1024)
    51  		s.publishers[c] = publisher
    52  	}
    53  	return publisher.Subscribe()
    54  }
    55  
    56  // stopCollection closes the channels for all subscribers and removes
    57  // the container from metrics collection.
    58  func (s *statsCollector) stopCollection(c *Container) {
    59  	s.m.Lock()
    60  	if publisher, exists := s.publishers[c]; exists {
    61  		publisher.Close()
    62  		delete(s.publishers, c)
    63  	}
    64  	s.m.Unlock()
    65  }
    66  
    67  // unsubscribe removes a specific subscriber from receiving updates for a container's stats.
    68  func (s *statsCollector) unsubscribe(c *Container, ch chan interface{}) {
    69  	s.m.Lock()
    70  	publisher := s.publishers[c]
    71  	if publisher != nil {
    72  		publisher.Evict(ch)
    73  		if publisher.Len() == 0 {
    74  			delete(s.publishers, c)
    75  		}
    76  	}
    77  	s.m.Unlock()
    78  }
    79  
    80  func (s *statsCollector) run() {
    81  	type publishersPair struct {
    82  		container *Container
    83  		publisher *pubsub.Publisher
    84  	}
    85  	// we cannot determine the capacity here.
    86  	// it will grow enough in first iteration
    87  	var pairs []publishersPair
    88  
    89  	for range time.Tick(s.interval) {
    90  		systemUsage, err := s.getSystemCpuUsage()
    91  		if err != nil {
    92  			logrus.Errorf("collecting system cpu usage: %v", err)
    93  			continue
    94  		}
    95  
    96  		// it does not make sense in the first iteration,
    97  		// but saves allocations in further iterations
    98  		pairs = pairs[:0]
    99  
   100  		s.m.Lock()
   101  		for container, publisher := range s.publishers {
   102  			// copy pointers here to release the lock ASAP
   103  			pairs = append(pairs, publishersPair{container, publisher})
   104  		}
   105  		s.m.Unlock()
   106  
   107  		for _, pair := range pairs {
   108  			stats, err := pair.container.Stats()
   109  			if err != nil {
   110  				if err != execdriver.ErrNotRunning {
   111  					logrus.Errorf("collecting stats for %s: %v", pair.container.ID, err)
   112  				}
   113  				continue
   114  			}
   115  			stats.SystemUsage = systemUsage
   116  			pair.publisher.Publish(stats)
   117  		}
   118  	}
   119  }
   120  
   121  const nanoSeconds = 1e9
   122  
   123  // getSystemCpuUSage returns the host system's cpu usage in nanoseconds
   124  // for the system to match the cgroup readings are returned in the same format.
   125  func (s *statsCollector) getSystemCpuUsage() (uint64, error) {
   126  	var line string
   127  	f, err := os.Open("/proc/stat")
   128  	if err != nil {
   129  		return 0, err
   130  	}
   131  	defer func() {
   132  		s.bufReader.Reset(nil)
   133  		f.Close()
   134  	}()
   135  	s.bufReader.Reset(f)
   136  	err = nil
   137  	for err == nil {
   138  		line, err = s.bufReader.ReadString('\n')
   139  		if err != nil {
   140  			break
   141  		}
   142  		parts := strings.Fields(line)
   143  		switch parts[0] {
   144  		case "cpu":
   145  			if len(parts) < 8 {
   146  				return 0, fmt.Errorf("invalid number of cpu fields")
   147  			}
   148  			var sum uint64
   149  			for _, i := range parts[1:8] {
   150  				v, err := strconv.ParseUint(i, 10, 64)
   151  				if err != nil {
   152  					return 0, fmt.Errorf("Unable to convert value %s to int: %s", i, err)
   153  				}
   154  				sum += v
   155  			}
   156  			return (sum * nanoSeconds) / s.clockTicks, nil
   157  		}
   158  	}
   159  	return 0, fmt.Errorf("invalid stat format")
   160  }