github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/worker/metrics/collect/manifold.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // Package collect provides a worker that executes the collect-metrics hook
     5  // periodically, as long as the workload has been started (between start and
     6  // stop hooks). collect-metrics executes in its own execution context, which is
     7  // restricted to avoid contention with uniter "lifecycle" hooks.
     8  package collect
     9  
    10  import (
    11  	"fmt"
    12  	"path"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/juju/errors"
    17  	"github.com/juju/loggo"
    18  	"github.com/juju/names"
    19  	"github.com/juju/utils/os"
    20  	corecharm "gopkg.in/juju/charm.v6-unstable"
    21  	"gopkg.in/juju/charm.v6-unstable/hooks"
    22  
    23  	"github.com/juju/juju/agent"
    24  	"github.com/juju/juju/worker"
    25  	"github.com/juju/juju/worker/dependency"
    26  	"github.com/juju/juju/worker/fortress"
    27  	"github.com/juju/juju/worker/metrics/spool"
    28  	"github.com/juju/juju/worker/uniter"
    29  	"github.com/juju/juju/worker/uniter/charm"
    30  	"github.com/juju/juju/worker/uniter/runner"
    31  	"github.com/juju/juju/worker/uniter/runner/context"
    32  )
    33  
    34  const (
    35  	defaultPeriod     = 5 * time.Minute
    36  	defaultSocketName = "metrics-collect.socket"
    37  )
    38  
    39  var (
    40  	logger = loggo.GetLogger("juju.worker.metrics.collect")
    41  
    42  	// errMetricsNotDefined is returned when the charm the uniter is running does
    43  	// not declared any metrics.
    44  	errMetricsNotDefined = errors.New("no metrics defined")
    45  
    46  	// readCharm function reads the charm directory and extracts declared metrics and the charm url.
    47  	readCharm = func(unitTag names.UnitTag, paths context.Paths) (*corecharm.URL, map[string]corecharm.Metric, error) {
    48  		ch, err := corecharm.ReadCharm(paths.GetCharmDir())
    49  		if err != nil {
    50  			return nil, nil, errors.Annotatef(err, "failed to read charm from: %v", paths.GetCharmDir())
    51  		}
    52  		chURL, err := charm.ReadCharmURL(path.Join(paths.GetCharmDir(), charm.CharmURLPath))
    53  		if err != nil {
    54  			return nil, nil, errors.Trace(err)
    55  		}
    56  		charmMetrics := map[string]corecharm.Metric{}
    57  		if ch.Metrics() != nil {
    58  			charmMetrics = ch.Metrics().Metrics
    59  		}
    60  		return chURL, charmMetrics, nil
    61  	}
    62  
    63  	// newRecorder returns a struct that implements the spool.MetricRecorder
    64  	// interface.
    65  	newRecorder = func(unitTag names.UnitTag, paths context.Paths, metricFactory spool.MetricFactory) (spool.MetricRecorder, error) {
    66  		chURL, charmMetrics, err := readCharm(unitTag, paths)
    67  		if err != nil {
    68  			return nil, errors.Trace(err)
    69  		}
    70  		if len(charmMetrics) == 0 {
    71  			return nil, errMetricsNotDefined
    72  		}
    73  		return metricFactory.Recorder(charmMetrics, chURL.String(), unitTag.String())
    74  	}
    75  
    76  	newSocketListener = func(path string, handler spool.ConnectionHandler) (stopper, error) {
    77  		return spool.NewSocketListener(path, handler)
    78  	}
    79  )
    80  
    81  type stopper interface {
    82  	Stop()
    83  }
    84  
    85  // ManifoldConfig identifies the resource names upon which the collect manifold
    86  // depends.
    87  type ManifoldConfig struct {
    88  	Period *time.Duration
    89  
    90  	AgentName       string
    91  	MetricSpoolName string
    92  	CharmDirName    string
    93  }
    94  
    95  // Manifold returns a collect-metrics manifold.
    96  func Manifold(config ManifoldConfig) dependency.Manifold {
    97  	return dependency.Manifold{
    98  		Inputs: []string{
    99  			config.AgentName,
   100  			config.MetricSpoolName,
   101  			config.CharmDirName,
   102  		},
   103  		Start: func(context dependency.Context) (worker.Worker, error) {
   104  			collector, err := newCollect(config, context)
   105  			if err != nil {
   106  				return nil, err
   107  			}
   108  			return spool.NewPeriodicWorker(collector.Do, collector.period, worker.NewTimer, collector.stop), nil
   109  		},
   110  	}
   111  }
   112  
   113  func socketName(baseDir, unitTag string) string {
   114  	if os.HostOS() == os.Windows {
   115  		return fmt.Sprintf(`\\.\pipe\collect-metrics-%s`, unitTag)
   116  	}
   117  	return path.Join(baseDir, defaultSocketName)
   118  }
   119  
   120  func newCollect(config ManifoldConfig, context dependency.Context) (*collect, error) {
   121  	period := defaultPeriod
   122  	if config.Period != nil {
   123  		period = *config.Period
   124  	}
   125  
   126  	var agent agent.Agent
   127  	if err := context.Get(config.AgentName, &agent); err != nil {
   128  		return nil, err
   129  	}
   130  
   131  	var metricFactory spool.MetricFactory
   132  	err := context.Get(config.MetricSpoolName, &metricFactory)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	var charmdir fortress.Guest
   138  	err = context.Get(config.CharmDirName, &charmdir)
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  
   143  	agentConfig := agent.CurrentConfig()
   144  	tag := agentConfig.Tag()
   145  	unitTag, ok := tag.(names.UnitTag)
   146  	if !ok {
   147  		return nil, errors.Errorf("expected a unit tag, got %v", tag)
   148  	}
   149  	paths := uniter.NewWorkerPaths(agentConfig.DataDir(), unitTag, "metrics-collect")
   150  	runner := &hookRunner{
   151  		unitTag: unitTag.String(),
   152  		paths:   paths,
   153  	}
   154  	var listener stopper
   155  	charmURL, validMetrics, err := readCharm(unitTag, paths)
   156  	if err != nil {
   157  		return nil, errors.Trace(err)
   158  	}
   159  	if len(validMetrics) > 0 && charmURL.Schema == "local" {
   160  		h := newHandler(handlerConfig{
   161  			unitTag:        unitTag,
   162  			charmURL:       charmURL,
   163  			validMetrics:   validMetrics,
   164  			metricsFactory: metricFactory,
   165  			runner:         runner,
   166  		})
   167  		listener, err = newSocketListener(socketName(paths.State.BaseDir, unitTag.String()), h)
   168  		if err != nil {
   169  			return nil, err
   170  		}
   171  	}
   172  	collector := &collect{
   173  		period:        period,
   174  		agent:         agent,
   175  		metricFactory: metricFactory,
   176  		charmdir:      charmdir,
   177  		listener:      listener,
   178  		runner:        runner,
   179  	}
   180  
   181  	return collector, nil
   182  }
   183  
   184  type collect struct {
   185  	period        time.Duration
   186  	agent         agent.Agent
   187  	metricFactory spool.MetricFactory
   188  	charmdir      fortress.Guest
   189  	listener      stopper
   190  	runner        *hookRunner
   191  }
   192  
   193  func (w *collect) stop() {
   194  	if w.listener != nil {
   195  		w.listener.Stop()
   196  	}
   197  }
   198  
   199  // Do satisfies the worker.PeriodWorkerCall function type.
   200  func (w *collect) Do(stop <-chan struct{}) error {
   201  	config := w.agent.CurrentConfig()
   202  	tag := config.Tag()
   203  	unitTag, ok := tag.(names.UnitTag)
   204  	if !ok {
   205  		return errors.Errorf("expected a unit tag, got %v", tag)
   206  	}
   207  	paths := uniter.NewWorkerPaths(config.DataDir(), unitTag, "metrics-collect")
   208  
   209  	recorder, err := newRecorder(unitTag, paths, w.metricFactory)
   210  	if errors.Cause(err) == errMetricsNotDefined {
   211  		logger.Tracef("%v", err)
   212  		return nil
   213  	} else if err != nil {
   214  		return errors.Annotate(err, "failed to instantiate metric recorder")
   215  	}
   216  
   217  	err = w.charmdir.Visit(func() error {
   218  		return w.runner.do(recorder)
   219  	}, stop)
   220  	if err == fortress.ErrAborted {
   221  		logger.Tracef("cannot execute collect-metrics: %v", err)
   222  		return nil
   223  	}
   224  	return err
   225  }
   226  
   227  type hookRunner struct {
   228  	m sync.Mutex
   229  
   230  	unitTag string
   231  	paths   uniter.Paths
   232  }
   233  
   234  func (h *hookRunner) do(recorder spool.MetricRecorder) error {
   235  	h.m.Lock()
   236  	defer h.m.Unlock()
   237  	logger.Tracef("recording metrics")
   238  
   239  	ctx := newHookContext(h.unitTag, recorder)
   240  	err := ctx.addJujuUnitsMetric()
   241  	if err != nil {
   242  		return errors.Annotatef(err, "error adding 'juju-units' metric")
   243  	}
   244  
   245  	r := runner.NewRunner(ctx, h.paths)
   246  	err = r.RunHook(string(hooks.CollectMetrics))
   247  	if err != nil {
   248  		return errors.Annotatef(err, "error running 'collect-metrics' hook")
   249  	}
   250  	return nil
   251  }