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