github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/metrics/sender/sender.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // Package sender contains the implementation of the metric
     5  // sender manifold.
     6  package sender
     7  
     8  import (
     9  	"fmt"
    10  	"net"
    11  	"path"
    12  	"runtime"
    13  	"time"
    14  
    15  	"github.com/juju/errors"
    16  
    17  	"github.com/juju/juju/api/metricsadder"
    18  	"github.com/juju/juju/apiserver/params"
    19  	"github.com/juju/juju/worker/metrics/spool"
    20  )
    21  
    22  const (
    23  	DefaultMetricsSendSocketName = "metrics-send.socket"
    24  )
    25  
    26  type stopper interface {
    27  	Stop() error
    28  }
    29  
    30  type sender struct {
    31  	client   metricsadder.MetricsAdderClient
    32  	factory  spool.MetricFactory
    33  	listener stopper
    34  }
    35  
    36  // Do sends metrics from the metric spool to the
    37  // controller via an api call.
    38  func (s *sender) Do(stop <-chan struct{}) (err error) {
    39  	defer func() {
    40  		// See bug https://pad/lv/1733469
    41  		// If this function which is run by a PeriodicWorker
    42  		// exits with an error, we need to call stop() to
    43  		// ensure the sender socket is closed.
    44  		if err != nil {
    45  			s.stop()
    46  		}
    47  	}()
    48  
    49  	reader, err := s.factory.Reader()
    50  	if err != nil {
    51  		return errors.Trace(err)
    52  	}
    53  	defer reader.Close()
    54  	err = s.sendMetrics(reader)
    55  	if spool.IsMetricsDataError(err) {
    56  		logger.Debugf("cannot send metrics: %v", err)
    57  		return nil
    58  	}
    59  	return err
    60  }
    61  
    62  func (s *sender) sendMetrics(reader spool.MetricReader) error {
    63  	batches, err := reader.Read()
    64  	if err != nil {
    65  		return errors.Annotate(err, "failed to open the metric reader")
    66  	}
    67  	var sendBatches []params.MetricBatchParam
    68  	for _, batch := range batches {
    69  		sendBatches = append(sendBatches, spool.APIMetricBatch(batch))
    70  	}
    71  	results, err := s.client.AddMetricBatches(sendBatches)
    72  	if err != nil {
    73  		return errors.Annotate(err, "could not send metrics")
    74  	}
    75  	for batchUUID, resultErr := range results {
    76  		// if we fail to send any metric batch we log a warning with the assumption that
    77  		// the unsent metric batches remain in the spool directory and will be sent to the
    78  		// controller when the network partition is restored.
    79  		if _, ok := resultErr.(*params.Error); ok || params.IsCodeAlreadyExists(resultErr) {
    80  			err := reader.Remove(batchUUID)
    81  			if err != nil {
    82  				logger.Errorf("could not remove batch %q from spool: %v", batchUUID, err)
    83  			}
    84  		} else {
    85  			logger.Errorf("failed to send batch %q: %v", batchUUID, resultErr)
    86  		}
    87  	}
    88  	return nil
    89  }
    90  
    91  // Handle sends metrics from the spool directory to the
    92  // controller.
    93  func (s *sender) Handle(c net.Conn, _ <-chan struct{}) (err error) {
    94  	defer func() {
    95  		if err != nil {
    96  			fmt.Fprintf(c, "%v\n", err)
    97  		} else {
    98  			fmt.Fprintf(c, "ok\n")
    99  		}
   100  		c.Close()
   101  	}()
   102  	// TODO(fwereade): 2016-03-17 lp:1558657
   103  	if err := c.SetDeadline(time.Now().Add(spool.DefaultTimeout)); err != nil {
   104  		return errors.Annotate(err, "failed to set the deadline")
   105  	}
   106  	reader, err := s.factory.Reader()
   107  	if err != nil {
   108  		return errors.Trace(err)
   109  	}
   110  	defer reader.Close()
   111  	return s.sendMetrics(reader)
   112  }
   113  
   114  func (s *sender) stop() {
   115  	if s.listener != nil {
   116  		s.listener.Stop()
   117  	}
   118  }
   119  
   120  var socketName = func(baseDir, unitTag string) string {
   121  	switch runtime.GOOS {
   122  	case "windows":
   123  		return fmt.Sprintf(`\\.\pipe\send-metrics-%s`, unitTag)
   124  	default:
   125  		return path.Join(baseDir, DefaultMetricsSendSocketName)
   126  	}
   127  }
   128  
   129  func newSender(client metricsadder.MetricsAdderClient, factory spool.MetricFactory, baseDir, unitTag string) (*sender, error) {
   130  	s := &sender{
   131  		client:  client,
   132  		factory: factory,
   133  	}
   134  	listener, err := newListener(s, baseDir, unitTag)
   135  	if err != nil {
   136  		return nil, errors.Trace(err)
   137  	}
   138  	s.listener = listener
   139  	return s, nil
   140  }
   141  
   142  var newListener = func(s spool.ConnectionHandler, baseDir, unitTag string) (stopper, error) {
   143  	listener, err := spool.NewSocketListener(socketName(baseDir, unitTag), s)
   144  	if err != nil {
   145  		return nil, errors.Trace(err)
   146  	}
   147  	return listener, nil
   148  }