github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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/filepath"
    13  	"time"
    14  
    15  	"github.com/juju/errors"
    16  	"github.com/juju/loggo"
    17  	"github.com/juju/names"
    18  	corecharm "gopkg.in/juju/charm.v6-unstable"
    19  	"gopkg.in/juju/charm.v6-unstable/hooks"
    20  
    21  	"github.com/juju/juju/agent"
    22  	"github.com/juju/juju/agent/tools"
    23  	"github.com/juju/juju/api/base"
    24  	uniterapi "github.com/juju/juju/api/uniter"
    25  	"github.com/juju/juju/worker"
    26  	"github.com/juju/juju/worker/charmdir"
    27  	"github.com/juju/juju/worker/dependency"
    28  	"github.com/juju/juju/worker/metrics/spool"
    29  	"github.com/juju/juju/worker/uniter/runner"
    30  	"github.com/juju/juju/worker/uniter/runner/context"
    31  	"github.com/juju/utils/os"
    32  )
    33  
    34  const defaultPeriod = 5 * time.Minute
    35  
    36  var (
    37  	logger = loggo.GetLogger("juju.worker.metrics.collect")
    38  )
    39  
    40  // ManifoldConfig identifies the resource names upon which the collect manifold
    41  // depends.
    42  type ManifoldConfig struct {
    43  	Period *time.Duration
    44  
    45  	AgentName       string
    46  	APICallerName   string
    47  	MetricSpoolName string
    48  	CharmDirName    string
    49  }
    50  
    51  // Manifold returns a collect-metrics manifold.
    52  func Manifold(config ManifoldConfig) dependency.Manifold {
    53  	return dependency.Manifold{
    54  		Inputs: []string{
    55  			config.AgentName,
    56  			config.APICallerName,
    57  			config.MetricSpoolName,
    58  			config.CharmDirName,
    59  		},
    60  		Start: func(getResource dependency.GetResourceFunc) (worker.Worker, error) {
    61  			collector, err := newCollect(config, getResource)
    62  			if err != nil {
    63  				return nil, err
    64  			}
    65  			return worker.NewPeriodicWorker(collector.Do, collector.period, worker.NewTimer), nil
    66  		},
    67  	}
    68  }
    69  
    70  // UnitCharmLookup can look up the charm URL for a unit tag.
    71  type UnitCharmLookup interface {
    72  	CharmURL(names.UnitTag) (*corecharm.URL, error)
    73  }
    74  
    75  var newCollect = func(config ManifoldConfig, getResource dependency.GetResourceFunc) (*collect, error) {
    76  	period := defaultPeriod
    77  	if config.Period != nil {
    78  		period = *config.Period
    79  	}
    80  
    81  	var agent agent.Agent
    82  	if err := getResource(config.AgentName, &agent); err != nil {
    83  		return nil, err
    84  	}
    85  	tag := agent.CurrentConfig().Tag()
    86  	unitTag, ok := tag.(names.UnitTag)
    87  	if !ok {
    88  		return nil, errors.Errorf("expected a unit tag, got %v", tag)
    89  	}
    90  
    91  	var apiCaller base.APICaller
    92  	if err := getResource(config.APICallerName, &apiCaller); err != nil {
    93  		return nil, err
    94  	}
    95  	uniterFacade := uniterapi.NewState(apiCaller, unitTag)
    96  
    97  	var metricFactory spool.MetricFactory
    98  	err := getResource(config.MetricSpoolName, &metricFactory)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	var charmdir charmdir.Consumer
   104  	err = getResource(config.CharmDirName, &charmdir)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  
   109  	collector := &collect{
   110  		period:          period,
   111  		agent:           agent,
   112  		unitCharmLookup: &unitCharmLookup{uniterFacade},
   113  		metricFactory:   metricFactory,
   114  		charmdir:        charmdir,
   115  	}
   116  	return collector, nil
   117  }
   118  
   119  type unitCharmLookup struct {
   120  	st *uniterapi.State
   121  }
   122  
   123  // CharmURL implements UnitCharmLookup.
   124  func (r *unitCharmLookup) CharmURL(unitTag names.UnitTag) (*corecharm.URL, error) {
   125  	unit, err := r.st.Unit(unitTag)
   126  	if err != nil {
   127  		return nil, errors.Trace(err)
   128  	}
   129  	return unit.CharmURL()
   130  }
   131  
   132  type collect struct {
   133  	period          time.Duration
   134  	agent           agent.Agent
   135  	unitCharmLookup UnitCharmLookup
   136  	metricFactory   spool.MetricFactory
   137  	charmdir        charmdir.Consumer
   138  }
   139  
   140  var errMetricsNotDefined = errors.New("no metrics defined")
   141  
   142  var newRecorder = func(unitTag names.UnitTag, paths context.Paths, unitCharm UnitCharmLookup, metricFactory spool.MetricFactory) (spool.MetricRecorder, error) {
   143  	ch, err := corecharm.ReadCharm(paths.GetCharmDir())
   144  	if err != nil {
   145  		return nil, errors.Trace(err)
   146  	}
   147  
   148  	chURL, err := unitCharm.CharmURL(unitTag)
   149  	if err != nil {
   150  		return nil, errors.Trace(err)
   151  	}
   152  
   153  	charmMetrics := map[string]corecharm.Metric{}
   154  	if ch.Metrics() != nil {
   155  		charmMetrics = ch.Metrics().Metrics
   156  	}
   157  	if len(charmMetrics) == 0 {
   158  		return nil, errMetricsNotDefined
   159  	}
   160  	return metricFactory.Recorder(charmMetrics, chURL.String(), unitTag.String())
   161  }
   162  
   163  // Do satisfies the worker.PeriodWorkerCall function type.
   164  func (w *collect) Do(stop <-chan struct{}) error {
   165  	err := w.charmdir.Run(w.do)
   166  	if err == charmdir.ErrNotAvailable {
   167  		logger.Debugf("cannot execute collect-metrics - charmdir locked")
   168  		return nil
   169  	}
   170  	return err
   171  }
   172  
   173  type collectPaths struct {
   174  	dataDir string
   175  	unitTag names.UnitTag
   176  }
   177  
   178  func (p *collectPaths) GetToolsDir() string {
   179  	return filepath.FromSlash(tools.ToolsDir(p.dataDir, p.unitTag.String()))
   180  }
   181  
   182  func (p *collectPaths) GetCharmDir() string {
   183  	return filepath.Join(p.baseDir(), "charm")
   184  }
   185  
   186  func (p *collectPaths) GetJujucSocket() string {
   187  	if os.HostOS() == os.Windows {
   188  		return fmt.Sprintf(`\\.\pipe\%s-metrics`, p.unitTag)
   189  	}
   190  	return filepath.Join(p.baseDir(), "metrics.socket@")
   191  }
   192  
   193  func (p *collectPaths) GetMetricsSpoolDir() string {
   194  	return ""
   195  }
   196  
   197  func (p *collectPaths) baseDir() string {
   198  	return filepath.Join(p.dataDir, "agents", p.unitTag.String())
   199  }
   200  
   201  func (w *collect) do() error {
   202  	logger.Tracef("recording metrics")
   203  
   204  	config := w.agent.CurrentConfig()
   205  	tag := config.Tag()
   206  	unitTag, ok := tag.(names.UnitTag)
   207  	if !ok {
   208  		return errors.Errorf("expected a unit tag, got %v", tag)
   209  	}
   210  	paths := &collectPaths{dataDir: config.DataDir(), unitTag: unitTag}
   211  
   212  	recorder, err := newRecorder(unitTag, paths, w.unitCharmLookup, w.metricFactory)
   213  	if errors.Cause(err) == errMetricsNotDefined {
   214  		logger.Tracef("%v", err)
   215  		return nil
   216  	} else if err != nil {
   217  		return errors.Annotate(err, "failed to instantiate metric recorder")
   218  	}
   219  
   220  	ctx := newHookContext(unitTag.String(), recorder)
   221  	err = ctx.addJujuUnitsMetric()
   222  	if err != nil {
   223  		return errors.Annotatef(err, "error adding 'juju-units' metric")
   224  	}
   225  
   226  	r := runner.NewRunner(ctx, paths)
   227  	err = r.RunHook(string(hooks.CollectMetrics))
   228  	if err != nil {
   229  		return errors.Annotatef(err, "error running 'collect-metrics' hook")
   230  	}
   231  	return nil
   232  }