github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/agent/metricsender/metricsender.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // Package metricsender contains functions for sending
     5  // metrics from a controller to a remote metric collector.
     6  package metricsender
     7  
     8  import (
     9  	"github.com/juju/clock"
    10  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  	wireformat "github.com/juju/romulus/wireformat/metrics"
    13  
    14  	"github.com/juju/juju/state"
    15  )
    16  
    17  var logger = loggo.GetLogger("juju.apiserver.metricsender")
    18  
    19  // MetricSender defines the interface used to send metrics
    20  // to a collection service.
    21  type MetricSender interface {
    22  	Send([]*wireformat.MetricBatch) (*wireformat.Response, error)
    23  }
    24  
    25  type SenderFactory func(url string) MetricSender
    26  
    27  var (
    28  	defaultMaxBatchesPerSend               = 1000
    29  	defaultSenderFactory     SenderFactory = func(url string) MetricSender {
    30  		return &HTTPSender{url: url}
    31  	}
    32  )
    33  
    34  func handleModelResponse(st ModelBackend, modelUUID string, modelResp wireformat.EnvResponse) int {
    35  	err := st.SetMetricBatchesSent(modelResp.AcknowledgedBatches)
    36  	if err != nil {
    37  		logger.Errorf("failed to set sent on metrics %v", err)
    38  	}
    39  	for unitName, status := range modelResp.UnitStatuses {
    40  		unit, err := st.Unit(unitName)
    41  		if err != nil {
    42  			logger.Errorf("failed to retrieve unit %q: %v", unitName, err)
    43  			continue
    44  		}
    45  		err = unit.SetMeterStatus(status.Status, status.Info)
    46  		if err != nil {
    47  			logger.Errorf("failed to set unit %q meter status to %v: %v", unitName, status, err)
    48  		}
    49  	}
    50  	if modelResp.ModelStatus.Status != "" {
    51  		err = st.SetModelMeterStatus(
    52  			modelResp.ModelStatus.Status,
    53  			modelResp.ModelStatus.Info,
    54  		)
    55  		if err != nil {
    56  			logger.Errorf("failed to set the model meter status: %v", err)
    57  		}
    58  	}
    59  	return len(modelResp.AcknowledgedBatches)
    60  }
    61  
    62  func handleResponse(mm *state.MetricsManager, st ModelBackend, response wireformat.Response) int {
    63  	var acknowledgedBatches int
    64  	for modelUUID, modelResp := range response.EnvResponses {
    65  		acks := handleModelResponse(st, modelUUID, modelResp)
    66  		acknowledgedBatches += acks
    67  	}
    68  	if response.NewGracePeriod > 0 && response.NewGracePeriod != mm.GracePeriod() {
    69  		err := mm.SetGracePeriod(response.NewGracePeriod)
    70  		if err != nil {
    71  			logger.Errorf("failed to set new grace period %v", err)
    72  		}
    73  	}
    74  	return acknowledgedBatches
    75  }
    76  
    77  // SendMetrics will send any unsent metrics
    78  // over the MetricSender interface in batches
    79  // no larger than batchSize.
    80  func SendMetrics(st ModelBackend, sender MetricSender, clock clock.Clock, batchSize int, transmitVendorMetrics bool) error {
    81  	metricsManager, err := st.MetricsManager()
    82  	if err != nil {
    83  		return errors.Trace(err)
    84  	}
    85  	sent := 0
    86  	held := 0
    87  	for {
    88  		metrics, err := st.MetricsToSend(batchSize)
    89  		if err != nil {
    90  			return errors.Trace(err)
    91  		}
    92  		modelName := st.Name()
    93  		lenM := len(metrics)
    94  		if lenM == 0 {
    95  			if sent == 0 {
    96  				logger.Debugf("nothing to send")
    97  			} else {
    98  				logger.Debugf("done sending")
    99  			}
   100  			break
   101  		}
   102  
   103  		var wireData []*wireformat.MetricBatch
   104  		var heldBatches []string
   105  		heldBatchUnits := map[string]bool{}
   106  		for _, m := range metrics {
   107  			if !transmitVendorMetrics && len(m.Credentials()) == 0 {
   108  				heldBatches = append(heldBatches, m.UUID())
   109  				heldBatchUnits[m.Unit()] = true
   110  			} else {
   111  				wireData = append(wireData, ToWire(m, modelName))
   112  			}
   113  		}
   114  		response, err := sender.Send(wireData)
   115  		if err != nil {
   116  			logger.Errorf("%+v", err)
   117  			if incErr := metricsManager.IncrementConsecutiveErrors(); incErr != nil {
   118  				logger.Errorf("failed to increment error count %v", incErr)
   119  				return errors.Trace(errors.Wrap(err, incErr))
   120  			}
   121  			return errors.Trace(err)
   122  		}
   123  		if response != nil {
   124  			// TODO (mattyw) We are currently ignoring errors during response handling.
   125  			acknowledged := handleResponse(metricsManager, st, *response)
   126  			// Stop sending if there are no acknowledged batches.
   127  			if acknowledged == 0 {
   128  				logger.Debugf("got 0 acks, ending send loop")
   129  				break
   130  			}
   131  			if err := metricsManager.SetLastSuccessfulSend(clock.Now()); err != nil {
   132  				err = errors.Annotate(err, "failed to set successful send time")
   133  				logger.Warningf("%v", err)
   134  				return errors.Trace(err)
   135  			}
   136  		}
   137  		// Mark held metric batches as sent so that they can be cleaned up later.
   138  		if len(heldBatches) > 0 {
   139  			err := st.SetMetricBatchesSent(heldBatches)
   140  			if err != nil {
   141  				return errors.Annotatef(err, "failed to mark metric batches as sent for %s", st.ModelTag())
   142  			}
   143  		}
   144  
   145  		setHeldBatchUnitMeterStatus(st, heldBatchUnits)
   146  
   147  		sent += len(wireData)
   148  		held += len(heldBatches)
   149  	}
   150  
   151  	unsent, err := st.CountOfUnsentMetrics()
   152  	if err != nil {
   153  		return errors.Trace(err)
   154  	}
   155  	sentStored, err := st.CountOfSentMetrics()
   156  	if err != nil {
   157  		return errors.Trace(err)
   158  	}
   159  	logger.Debugf("metrics collection summary for %s: sent:%d unsent:%d held:%d (%d sent metrics stored)", st.ModelTag(), sent, unsent, held, sentStored)
   160  
   161  	return nil
   162  }
   163  
   164  func setHeldBatchUnitMeterStatus(st ModelBackend, units map[string]bool) {
   165  	for unitID := range units {
   166  		unit, err := st.Unit(unitID)
   167  		if err != nil {
   168  			logger.Warningf("failed to get unit for setting held batch meter status: %v", err)
   169  		}
   170  		if err = unit.SetMeterStatus("RED", "transmit-vendor-metrics turned off"); err != nil {
   171  			logger.Warningf("failed to set held batch meter status: %v", err)
   172  		}
   173  	}
   174  }
   175  
   176  // DefaultMaxBatchesPerSend returns the default number of batches per send.
   177  func DefaultMaxBatchesPerSend() int {
   178  	return defaultMaxBatchesPerSend
   179  }
   180  
   181  // DefaultSenderFactory returns the default sender factory.
   182  func DefaultSenderFactory() SenderFactory {
   183  	return defaultSenderFactory
   184  }
   185  
   186  // ToWire converts the state.MetricBatch into a type
   187  // that can be sent over the wire to the collector.
   188  func ToWire(mb *state.MetricBatch, modelName string) *wireformat.MetricBatch {
   189  	metrics := make([]wireformat.Metric, len(mb.Metrics()))
   190  	for i, m := range mb.Metrics() {
   191  		metrics[i] = wireformat.Metric{
   192  			Key:    m.Key,
   193  			Value:  m.Value,
   194  			Time:   m.Time.UTC(),
   195  			Labels: m.Labels,
   196  		}
   197  	}
   198  	return &wireformat.MetricBatch{
   199  		UUID:           mb.UUID(),
   200  		ModelUUID:      mb.ModelUUID(),
   201  		ModelName:      modelName,
   202  		UnitName:       mb.Unit(),
   203  		CharmUrl:       mb.CharmURL(),
   204  		Created:        mb.Created().UTC(),
   205  		Metrics:        metrics,
   206  		Credentials:    mb.Credentials(),
   207  		SLACredentials: mb.SLACredentials(),
   208  	}
   209  }