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

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"encoding/json"
     8  	"time"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  	"github.com/juju/names"
    13  	"gopkg.in/juju/charm.v6-unstable"
    14  	"gopkg.in/mgo.v2"
    15  	"gopkg.in/mgo.v2/bson"
    16  	"gopkg.in/mgo.v2/txn"
    17  )
    18  
    19  var metricsLogger = loggo.GetLogger("juju.state.metrics")
    20  
    21  const (
    22  	CleanupAge = time.Hour * 24
    23  )
    24  
    25  // MetricBatch represents a batch of metrics reported from a unit.
    26  // These will be received from the unit in batches.
    27  // The main contents of the metric (key, value) is defined
    28  // by the charm author and sent from the unit via a call to
    29  // add-metric
    30  type MetricBatch struct {
    31  	st  *State
    32  	doc metricBatchDoc
    33  }
    34  
    35  type metricBatchDoc struct {
    36  	UUID        string    `bson:"_id"`
    37  	ModelUUID   string    `bson:"model-uuid"`
    38  	Unit        string    `bson:"unit"`
    39  	CharmUrl    string    `bson:"charmurl"`
    40  	Sent        bool      `bson:"sent"`
    41  	DeleteTime  time.Time `bson:"delete-time"`
    42  	Created     time.Time `bson:"created"`
    43  	Metrics     []Metric  `bson:"metrics"`
    44  	Credentials []byte    `bson:"credentials"`
    45  }
    46  
    47  // Metric represents a single Metric.
    48  type Metric struct {
    49  	Key   string    `bson:"key"`
    50  	Value string    `bson:"value"`
    51  	Time  time.Time `bson:"time"`
    52  }
    53  
    54  // validate checks that the MetricBatch contains valid metrics.
    55  func (m *MetricBatch) validate() error {
    56  	charmUrl, err := charm.ParseURL(m.doc.CharmUrl)
    57  	if err != nil {
    58  		return errors.Trace(err)
    59  	}
    60  	chrm, err := m.st.Charm(charmUrl)
    61  	if err != nil {
    62  		return errors.Trace(err)
    63  	}
    64  	chrmMetrics := chrm.Metrics()
    65  	if chrmMetrics == nil {
    66  		return errors.Errorf("charm doesn't implement metrics")
    67  	}
    68  	for _, m := range m.doc.Metrics {
    69  		if err := chrmMetrics.ValidateMetric(m.Key, m.Value); err != nil {
    70  			return errors.Trace(err)
    71  		}
    72  	}
    73  	return nil
    74  }
    75  
    76  // BatchParam contains the properties of the metrics batch used when creating a metrics
    77  // batch.
    78  type BatchParam struct {
    79  	UUID     string
    80  	CharmURL string
    81  	Created  time.Time
    82  	Metrics  []Metric
    83  	Unit     names.UnitTag
    84  }
    85  
    86  // AddMetrics adds a new batch of metrics to the database.
    87  func (st *State) AddMetrics(batch BatchParam) (*MetricBatch, error) {
    88  	if len(batch.Metrics) == 0 {
    89  		return nil, errors.New("cannot add a batch of 0 metrics")
    90  	}
    91  	charmURL, err := charm.ParseURL(batch.CharmURL)
    92  	if err != nil {
    93  		return nil, errors.NewNotValid(err, "could not parse charm URL")
    94  	}
    95  
    96  	unit, err := st.Unit(batch.Unit.Id())
    97  	if err != nil {
    98  		return nil, errors.Trace(err)
    99  	}
   100  	service, err := unit.Service()
   101  	if err != nil {
   102  		return nil, errors.Trace(err)
   103  	}
   104  
   105  	metric := &MetricBatch{
   106  		st: st,
   107  		doc: metricBatchDoc{
   108  			UUID:        batch.UUID,
   109  			ModelUUID:   st.ModelUUID(),
   110  			Unit:        batch.Unit.Id(),
   111  			CharmUrl:    charmURL.String(),
   112  			Sent:        false,
   113  			Created:     batch.Created,
   114  			Metrics:     batch.Metrics,
   115  			Credentials: service.MetricCredentials(),
   116  		},
   117  	}
   118  	if err := metric.validate(); err != nil {
   119  		return nil, err
   120  	}
   121  	buildTxn := func(attempt int) ([]txn.Op, error) {
   122  		if attempt > 0 {
   123  			notDead, err := isNotDead(st, unitsC, batch.Unit.Id())
   124  			if err != nil || !notDead {
   125  				return nil, errors.NotFoundf(batch.Unit.Id())
   126  			}
   127  			exists, err := st.MetricBatch(batch.UUID)
   128  			if exists != nil && err == nil {
   129  				return nil, errors.AlreadyExistsf("metrics batch UUID %q", batch.UUID)
   130  			}
   131  			if !errors.IsNotFound(err) {
   132  				return nil, errors.Trace(err)
   133  			}
   134  		}
   135  		ops := []txn.Op{{
   136  			C:      unitsC,
   137  			Id:     st.docID(batch.Unit.Id()),
   138  			Assert: notDeadDoc,
   139  		}, {
   140  			C:      metricsC,
   141  			Id:     metric.UUID(),
   142  			Assert: txn.DocMissing,
   143  			Insert: &metric.doc,
   144  		}}
   145  		return ops, nil
   146  	}
   147  	err = st.run(buildTxn)
   148  	if err != nil {
   149  		return nil, errors.Trace(err)
   150  	}
   151  
   152  	return metric, nil
   153  }
   154  
   155  // AllMetricBatches returns all metric batches currently stored in state.
   156  // TODO (tasdomas): this method is currently only used in the uniter worker test -
   157  //                  it needs to be modified to restrict the scope of the values it
   158  //                  returns if it is to be used outside of tests.
   159  func (st *State) AllMetricBatches() ([]MetricBatch, error) {
   160  	c, closer := st.getCollection(metricsC)
   161  	defer closer()
   162  	docs := []metricBatchDoc{}
   163  	err := c.Find(nil).All(&docs)
   164  	if err != nil {
   165  		return nil, errors.Trace(err)
   166  	}
   167  	results := make([]MetricBatch, len(docs))
   168  	for i, doc := range docs {
   169  		results[i] = MetricBatch{st: st, doc: doc}
   170  	}
   171  	return results, nil
   172  }
   173  
   174  func (st *State) queryLocalMetricBatches(query bson.M) ([]MetricBatch, error) {
   175  	c, closer := st.getCollection(metricsC)
   176  	defer closer()
   177  	docs := []metricBatchDoc{}
   178  	if query == nil {
   179  		query = bson.M{}
   180  	}
   181  	query["charmurl"] = bson.M{"$regex": "^local:"}
   182  	err := c.Find(query).All(&docs)
   183  	if err != nil {
   184  		return nil, errors.Trace(err)
   185  	}
   186  	results := make([]MetricBatch, len(docs))
   187  	for i, doc := range docs {
   188  		results[i] = MetricBatch{st: st, doc: doc}
   189  	}
   190  	return results, nil
   191  }
   192  
   193  // MetricBatchesUnit returns metric batches for the given unit.
   194  func (st *State) MetricBatchesForUnit(unit string) ([]MetricBatch, error) {
   195  	return st.queryLocalMetricBatches(bson.M{"unit": unit})
   196  }
   197  
   198  // MetricBatchesUnit returns metric batches for the given service.
   199  func (st *State) MetricBatchesForService(service string) ([]MetricBatch, error) {
   200  	svc, err := st.Service(service)
   201  	if err != nil {
   202  		return nil, errors.Trace(err)
   203  	}
   204  	units, err := svc.AllUnits()
   205  	if err != nil {
   206  		return nil, errors.Trace(err)
   207  	}
   208  	unitNames := make([]bson.M, len(units))
   209  	for i, u := range units {
   210  		unitNames[i] = bson.M{"unit": u.Name()}
   211  	}
   212  	return st.queryLocalMetricBatches(bson.M{"$or": unitNames})
   213  }
   214  
   215  // MetricBatch returns the metric batch with the given id.
   216  func (st *State) MetricBatch(id string) (*MetricBatch, error) {
   217  	c, closer := st.getCollection(metricsC)
   218  	defer closer()
   219  	doc := metricBatchDoc{}
   220  	err := c.Find(bson.M{"_id": id}).One(&doc)
   221  	if err == mgo.ErrNotFound {
   222  		return nil, errors.NotFoundf("metric %v", id)
   223  	}
   224  	if err != nil {
   225  		return nil, err
   226  	}
   227  	return &MetricBatch{st: st, doc: doc}, nil
   228  }
   229  
   230  // CleanupOldMetrics looks for metrics that are 24 hours old (or older)
   231  // and have been sent. Any metrics it finds are deleted.
   232  func (st *State) CleanupOldMetrics() error {
   233  	// TODO(fwereade): 2016-03-17 lp:1558657
   234  	now := time.Now()
   235  	metrics, closer := st.getCollection(metricsC)
   236  	defer closer()
   237  	// Nothing else in the system will interact with sent metrics, and nothing needs
   238  	// to watch them either; so in this instance it's safe to do an end run around the
   239  	// mgo/txn package. See State.cleanupRelationSettings for a similar situation.
   240  	metricsW := metrics.Writeable()
   241  	// TODO (mattyw) iter over this.
   242  	info, err := metricsW.RemoveAll(bson.M{
   243  		"sent":        true,
   244  		"delete-time": bson.M{"$lte": now},
   245  	})
   246  	if err == nil {
   247  		metricsLogger.Tracef("cleanup removed %d metrics", info.Removed)
   248  	}
   249  	return errors.Trace(err)
   250  }
   251  
   252  // MetricsToSend returns batchSize metrics that need to be sent
   253  // to the collector
   254  func (st *State) MetricsToSend(batchSize int) ([]*MetricBatch, error) {
   255  	var docs []metricBatchDoc
   256  	c, closer := st.getCollection(metricsC)
   257  	defer closer()
   258  	err := c.Find(bson.M{
   259  		"sent": false,
   260  	}).Limit(batchSize).All(&docs)
   261  	if err != nil {
   262  		return nil, errors.Trace(err)
   263  	}
   264  
   265  	batch := make([]*MetricBatch, len(docs))
   266  	for i, doc := range docs {
   267  		batch[i] = &MetricBatch{st: st, doc: doc}
   268  
   269  	}
   270  
   271  	return batch, nil
   272  }
   273  
   274  // CountOfUnsentMetrics returns the number of metrics that
   275  // haven't been sent to the collection service.
   276  func (st *State) CountOfUnsentMetrics() (int, error) {
   277  	c, closer := st.getCollection(metricsC)
   278  	defer closer()
   279  	return c.Find(bson.M{
   280  		"sent": false,
   281  	}).Count()
   282  }
   283  
   284  // CountOfSentMetrics returns the number of metrics that
   285  // have been sent to the collection service and have not
   286  // been removed by the cleanup worker.
   287  func (st *State) CountOfSentMetrics() (int, error) {
   288  	c, closer := st.getCollection(metricsC)
   289  	defer closer()
   290  	return c.Find(bson.M{
   291  		"sent": true,
   292  	}).Count()
   293  }
   294  
   295  // MarshalJSON defines how the MetricBatch type should be
   296  // converted to json.
   297  func (m *MetricBatch) MarshalJSON() ([]byte, error) {
   298  	return json.Marshal(m.doc)
   299  }
   300  
   301  // UUID returns to uuid of the metric.
   302  func (m *MetricBatch) UUID() string {
   303  	return m.doc.UUID
   304  }
   305  
   306  // ModelUUID returns the model UUID this metric applies to.
   307  func (m *MetricBatch) ModelUUID() string {
   308  	return m.doc.ModelUUID
   309  }
   310  
   311  // Unit returns the name of the unit this metric was generated in.
   312  func (m *MetricBatch) Unit() string {
   313  	return m.doc.Unit
   314  }
   315  
   316  // CharmURL returns the charm url for the charm this metric was generated in.
   317  func (m *MetricBatch) CharmURL() string {
   318  	return m.doc.CharmUrl
   319  }
   320  
   321  // Created returns the time this metric batch was created.
   322  func (m *MetricBatch) Created() time.Time {
   323  	return m.doc.Created
   324  }
   325  
   326  // Sent returns a flag to tell us if this metric has been sent to the metric
   327  // collection service
   328  func (m *MetricBatch) Sent() bool {
   329  	return m.doc.Sent
   330  }
   331  
   332  // Metrics returns the metrics in this batch.
   333  func (m *MetricBatch) Metrics() []Metric {
   334  	result := make([]Metric, len(m.doc.Metrics))
   335  	copy(result, m.doc.Metrics)
   336  	return result
   337  }
   338  
   339  // SetSent marks the metric has having been sent at
   340  // the specified time.
   341  func (m *MetricBatch) SetSent(t time.Time) error {
   342  	deleteTime := t.UTC().Add(CleanupAge)
   343  	ops := setSentOps([]string{m.UUID()}, deleteTime)
   344  	if err := m.st.runTransaction(ops); err != nil {
   345  		return errors.Annotatef(err, "cannot set metric sent for metric %q", m.UUID())
   346  	}
   347  
   348  	m.doc.Sent = true
   349  	m.doc.DeleteTime = deleteTime
   350  	return nil
   351  }
   352  
   353  // Credentials returns any credentials associated with the metric batch.
   354  func (m *MetricBatch) Credentials() []byte {
   355  	return m.doc.Credentials
   356  }
   357  
   358  func setSentOps(batchUUIDs []string, deleteTime time.Time) []txn.Op {
   359  	ops := make([]txn.Op, len(batchUUIDs))
   360  	for i, u := range batchUUIDs {
   361  		ops[i] = txn.Op{
   362  			C:      metricsC,
   363  			Id:     u,
   364  			Assert: txn.DocExists,
   365  			Update: bson.M{"$set": bson.M{"sent": true, "delete-time": deleteTime}},
   366  		}
   367  	}
   368  	return ops
   369  }
   370  
   371  // SetMetricBatchesSent sets sent on each MetricBatch corresponding to the uuids provided.
   372  func (st *State) SetMetricBatchesSent(batchUUIDs []string) error {
   373  	// TODO(fwereade): 2016-03-17 lp:1558657
   374  	deleteTime := time.Now().UTC().Add(CleanupAge)
   375  	ops := setSentOps(batchUUIDs, deleteTime)
   376  	if err := st.runTransaction(ops); err != nil {
   377  		return errors.Annotatef(err, "cannot set metric sent in bulk call")
   378  	}
   379  	return nil
   380  }