github.com/erriapo/docker@v1.6.0-rc2/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  	log "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  	}
    28  	go s.run()
    29  	return s
    30  }
    31  
    32  // statsCollector manages and provides container resource stats
    33  type statsCollector struct {
    34  	m          sync.Mutex
    35  	interval   time.Duration
    36  	clockTicks uint64
    37  	publishers map[*Container]*pubsub.Publisher
    38  }
    39  
    40  // collect registers the container with the collector and adds it to
    41  // the event loop for collection on the specified interval returning
    42  // a channel for the subscriber to receive on.
    43  func (s *statsCollector) collect(c *Container) chan interface{} {
    44  	s.m.Lock()
    45  	defer s.m.Unlock()
    46  	publisher, exists := s.publishers[c]
    47  	if !exists {
    48  		publisher = pubsub.NewPublisher(100*time.Millisecond, 1024)
    49  		s.publishers[c] = publisher
    50  	}
    51  	return publisher.Subscribe()
    52  }
    53  
    54  // stopCollection closes the channels for all subscribers and removes
    55  // the container from metrics collection.
    56  func (s *statsCollector) stopCollection(c *Container) {
    57  	s.m.Lock()
    58  	if publisher, exists := s.publishers[c]; exists {
    59  		publisher.Close()
    60  		delete(s.publishers, c)
    61  	}
    62  	s.m.Unlock()
    63  }
    64  
    65  // unsubscribe removes a specific subscriber from receiving updates for a container's stats.
    66  func (s *statsCollector) unsubscribe(c *Container, ch chan interface{}) {
    67  	s.m.Lock()
    68  	publisher := s.publishers[c]
    69  	if publisher != nil {
    70  		publisher.Evict(ch)
    71  		if publisher.Len() == 0 {
    72  			delete(s.publishers, c)
    73  		}
    74  	}
    75  	s.m.Unlock()
    76  }
    77  
    78  func (s *statsCollector) run() {
    79  	for _ = range time.Tick(s.interval) {
    80  		for container, publisher := range s.publishers {
    81  			systemUsage, err := s.getSystemCpuUsage()
    82  			if err != nil {
    83  				log.Errorf("collecting system cpu usage for %s: %v", container.ID, err)
    84  				continue
    85  			}
    86  			stats, err := container.Stats()
    87  			if err != nil {
    88  				if err != execdriver.ErrNotRunning {
    89  					log.Errorf("collecting stats for %s: %v", container.ID, err)
    90  				}
    91  				continue
    92  			}
    93  			stats.SystemUsage = systemUsage
    94  			publisher.Publish(stats)
    95  		}
    96  	}
    97  }
    98  
    99  const nanoSeconds = 1e9
   100  
   101  // getSystemCpuUSage returns the host system's cpu usage in nanoseconds
   102  // for the system to match the cgroup readings are returned in the same format.
   103  func (s *statsCollector) getSystemCpuUsage() (uint64, error) {
   104  	f, err := os.Open("/proc/stat")
   105  	if err != nil {
   106  		return 0, err
   107  	}
   108  	defer f.Close()
   109  	sc := bufio.NewScanner(f)
   110  	for sc.Scan() {
   111  		parts := strings.Fields(sc.Text())
   112  		switch parts[0] {
   113  		case "cpu":
   114  			if len(parts) < 8 {
   115  				return 0, fmt.Errorf("invalid number of cpu fields")
   116  			}
   117  			var sum uint64
   118  			for _, i := range parts[1:8] {
   119  				v, err := strconv.ParseUint(i, 10, 64)
   120  				if err != nil {
   121  					return 0, fmt.Errorf("Unable to convert value %s to int: %s", i, err)
   122  				}
   123  				sum += v
   124  			}
   125  			return (sum * nanoSeconds) / s.clockTicks, nil
   126  		}
   127  	}
   128  	return 0, fmt.Errorf("invalid stat format")
   129  }