github.com/m3db/m3@v1.5.0/src/aggregator/client/m3msg_client.go (about)

     1  // Copyright (c) 2020 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package client
    22  
    23  import (
    24  	"fmt"
    25  	"sync"
    26  
    27  	"github.com/uber-go/tally"
    28  	"go.uber.org/zap"
    29  
    30  	"github.com/m3db/m3/src/aggregator/sharding"
    31  	"github.com/m3db/m3/src/metrics/generated/proto/metricpb"
    32  	"github.com/m3db/m3/src/metrics/metadata"
    33  	"github.com/m3db/m3/src/metrics/metric"
    34  	"github.com/m3db/m3/src/metrics/metric/aggregated"
    35  	"github.com/m3db/m3/src/metrics/metric/id"
    36  	"github.com/m3db/m3/src/metrics/metric/unaggregated"
    37  	"github.com/m3db/m3/src/metrics/policy"
    38  	"github.com/m3db/m3/src/msg/producer"
    39  	"github.com/m3db/m3/src/x/clock"
    40  	"github.com/m3db/m3/src/x/instrument"
    41  )
    42  
    43  var _ AdminClient = (*M3MsgClient)(nil)
    44  
    45  // M3MsgClient sends metrics to M3 Aggregator over m3msg.
    46  type M3MsgClient struct {
    47  	m3msg   m3msgClient
    48  	nowFn   clock.NowFn
    49  	shardFn sharding.ShardFn
    50  	metrics m3msgClientMetrics
    51  }
    52  
    53  type m3msgClient struct {
    54  	producer    producer.Producer
    55  	numShards   uint32
    56  	messagePool *messagePool
    57  }
    58  
    59  // NewM3MsgClient creates a new M3 Aggregator client that uses M3Msg.
    60  func NewM3MsgClient(opts Options) (Client, error) {
    61  	if err := opts.Validate(); err != nil {
    62  		return nil, err
    63  	}
    64  
    65  	m3msgOpts := opts.M3MsgOptions()
    66  	if err := m3msgOpts.Validate(); err != nil {
    67  		return nil, err
    68  	}
    69  
    70  	producer := m3msgOpts.Producer()
    71  	if err := producer.Init(); err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	msgClient := m3msgClient{
    76  		producer:    producer,
    77  		numShards:   producer.NumShards(),
    78  		messagePool: newMessagePool(),
    79  	}
    80  
    81  	var (
    82  		iOpts  = opts.InstrumentOptions()
    83  		logger = iOpts.Logger()
    84  	)
    85  
    86  	logger.Info("creating M3MsgClient", zap.Uint32("numShards", msgClient.numShards))
    87  
    88  	return &M3MsgClient{
    89  		m3msg:   msgClient,
    90  		nowFn:   opts.ClockOptions().NowFn(),
    91  		shardFn: opts.ShardFn(),
    92  		metrics: newM3msgClientMetrics(iOpts.MetricsScope(), iOpts.TimerOptions()),
    93  	}, nil
    94  }
    95  
    96  // Init just satisfies Client interface, M3Msg client does not need explicit initialization.
    97  func (c *M3MsgClient) Init() error {
    98  	return nil
    99  }
   100  
   101  // WriteUntimedCounter writes untimed counter metrics.
   102  func (c *M3MsgClient) WriteUntimedCounter(
   103  	counter unaggregated.Counter,
   104  	metadatas metadata.StagedMetadatas,
   105  ) error {
   106  	callStart := c.nowFn()
   107  	payload := payloadUnion{
   108  		payloadType: untimedType,
   109  		untimed: untimedPayload{
   110  			metric:    counter.ToUnion(),
   111  			metadatas: metadatas,
   112  		},
   113  	}
   114  	err := c.write(counter.ID, payload)
   115  	c.metrics.writeUntimedCounter.ReportSuccessOrError(err, c.nowFn().Sub(callStart))
   116  	return err
   117  }
   118  
   119  // WriteUntimedBatchTimer writes untimed batch timer metrics.
   120  func (c *M3MsgClient) WriteUntimedBatchTimer(
   121  	batchTimer unaggregated.BatchTimer,
   122  	metadatas metadata.StagedMetadatas,
   123  ) error {
   124  	callStart := c.nowFn()
   125  	payload := payloadUnion{
   126  		payloadType: untimedType,
   127  		untimed: untimedPayload{
   128  			metric:    batchTimer.ToUnion(),
   129  			metadatas: metadatas,
   130  		},
   131  	}
   132  	err := c.write(batchTimer.ID, payload)
   133  	c.metrics.writeUntimedBatchTimer.ReportSuccessOrError(err, c.nowFn().Sub(callStart))
   134  	return err
   135  }
   136  
   137  // WriteUntimedGauge writes untimed gauge metrics.
   138  func (c *M3MsgClient) WriteUntimedGauge(
   139  	gauge unaggregated.Gauge,
   140  	metadatas metadata.StagedMetadatas,
   141  ) error {
   142  	callStart := c.nowFn()
   143  	payload := payloadUnion{
   144  		payloadType: untimedType,
   145  		untimed: untimedPayload{
   146  			metric:    gauge.ToUnion(),
   147  			metadatas: metadatas,
   148  		},
   149  	}
   150  	err := c.write(gauge.ID, payload)
   151  	c.metrics.writeUntimedGauge.ReportSuccessOrError(err, c.nowFn().Sub(callStart))
   152  	return err
   153  }
   154  
   155  // WriteTimed writes timed metrics.
   156  func (c *M3MsgClient) WriteTimed(
   157  	metric aggregated.Metric,
   158  	metadata metadata.TimedMetadata,
   159  ) error {
   160  	callStart := c.nowFn()
   161  	payload := payloadUnion{
   162  		payloadType: timedType,
   163  		timed: timedPayload{
   164  			metric:   metric,
   165  			metadata: metadata,
   166  		},
   167  	}
   168  	err := c.write(metric.ID, payload)
   169  	c.metrics.writeForwarded.ReportSuccessOrError(err, c.nowFn().Sub(callStart))
   170  	return err
   171  }
   172  
   173  // WritePassthrough writes passthrough metrics.
   174  func (c *M3MsgClient) WritePassthrough(
   175  	metric aggregated.Metric,
   176  	storagePolicy policy.StoragePolicy,
   177  ) error {
   178  	callStart := c.nowFn()
   179  	payload := payloadUnion{
   180  		payloadType: passthroughType,
   181  		passthrough: passthroughPayload{
   182  			metric:        metric,
   183  			storagePolicy: storagePolicy,
   184  		},
   185  	}
   186  	err := c.write(metric.ID, payload)
   187  	c.metrics.writePassthrough.ReportSuccessOrError(err, c.nowFn().Sub(callStart))
   188  	return err
   189  }
   190  
   191  // WriteTimedWithStagedMetadatas writes timed metrics with staged metadatas.
   192  func (c *M3MsgClient) WriteTimedWithStagedMetadatas(
   193  	metric aggregated.Metric,
   194  	metadatas metadata.StagedMetadatas,
   195  ) error {
   196  	callStart := c.nowFn()
   197  	payload := payloadUnion{
   198  		payloadType: timedWithStagedMetadatasType,
   199  		timedWithStagedMetadatas: timedWithStagedMetadatas{
   200  			metric:    metric,
   201  			metadatas: metadatas,
   202  		},
   203  	}
   204  	err := c.write(metric.ID, payload)
   205  	c.metrics.writeForwarded.ReportSuccessOrError(err, c.nowFn().Sub(callStart))
   206  	return err
   207  }
   208  
   209  // WriteForwarded writes forwarded metrics.
   210  func (c *M3MsgClient) WriteForwarded(
   211  	metric aggregated.ForwardedMetric,
   212  	metadata metadata.ForwardMetadata,
   213  ) error {
   214  	callStart := c.nowFn()
   215  	payload := payloadUnion{
   216  		payloadType: forwardedType,
   217  		forwarded: forwardedPayload{
   218  			metric:   metric,
   219  			metadata: metadata,
   220  		},
   221  	}
   222  	err := c.write(metric.ID, payload)
   223  	c.metrics.writeForwarded.ReportSuccessOrError(err, c.nowFn().Sub(callStart))
   224  	return err
   225  }
   226  
   227  //nolint:gocritic
   228  func (c *M3MsgClient) write(metricID id.RawID, payload payloadUnion) error {
   229  	shard := c.shardFn(metricID, c.m3msg.numShards)
   230  
   231  	msg := c.m3msg.messagePool.Get()
   232  	if err := msg.Encode(shard, payload); err != nil {
   233  		msg.Finalize(producer.Dropped)
   234  		return err
   235  	}
   236  
   237  	if err := c.m3msg.producer.Produce(msg); err != nil {
   238  		msg.Finalize(producer.Dropped)
   239  		return err
   240  	}
   241  
   242  	return nil
   243  }
   244  
   245  // Flush satisfies Client interface, as M3Msg client does not need explicit flushing.
   246  func (c *M3MsgClient) Flush() error {
   247  	return nil
   248  }
   249  
   250  // Close closes the client.
   251  func (c *M3MsgClient) Close() error {
   252  	c.m3msg.producer.Close(producer.WaitForConsumption)
   253  	return nil
   254  }
   255  
   256  type m3msgClientMetrics struct {
   257  	writeUntimedCounter    instrument.MethodMetrics
   258  	writeUntimedBatchTimer instrument.MethodMetrics
   259  	writeUntimedGauge      instrument.MethodMetrics
   260  	writePassthrough       instrument.MethodMetrics
   261  	writeForwarded         instrument.MethodMetrics
   262  }
   263  
   264  func newM3msgClientMetrics(
   265  	scope tally.Scope,
   266  	opts instrument.TimerOptions,
   267  ) m3msgClientMetrics {
   268  	return m3msgClientMetrics{
   269  		writeUntimedCounter:    instrument.NewMethodMetrics(scope, "writeUntimedCounter", opts),
   270  		writeUntimedBatchTimer: instrument.NewMethodMetrics(scope, "writeUntimedBatchTimer", opts),
   271  		writeUntimedGauge:      instrument.NewMethodMetrics(scope, "writeUntimedGauge", opts),
   272  		writePassthrough:       instrument.NewMethodMetrics(scope, "writePassthrough", opts),
   273  		writeForwarded:         instrument.NewMethodMetrics(scope, "writeForwarded", opts),
   274  	}
   275  }
   276  
   277  type messagePool struct {
   278  	pool sync.Pool
   279  }
   280  
   281  func newMessagePool() *messagePool {
   282  	p := &messagePool{}
   283  	p.pool.New = func() interface{} {
   284  		return newMessage(p)
   285  	}
   286  	return p
   287  }
   288  
   289  func (m *messagePool) Get() *message {
   290  	return m.pool.Get().(*message)
   291  }
   292  
   293  func (m *messagePool) Put(msg *message) {
   294  	m.pool.Put(msg)
   295  }
   296  
   297  // Ensure message implements m3msg producer message interface.
   298  var _ producer.Message = (*message)(nil)
   299  
   300  type message struct {
   301  	pool  *messagePool
   302  	shard uint32
   303  
   304  	metric metricpb.MetricWithMetadatas
   305  	cm     metricpb.CounterWithMetadatas
   306  	bm     metricpb.BatchTimerWithMetadatas
   307  	gm     metricpb.GaugeWithMetadatas
   308  	fm     metricpb.ForwardedMetricWithMetadata
   309  	tm     metricpb.TimedMetricWithMetadata
   310  	tms    metricpb.TimedMetricWithMetadatas
   311  
   312  	buf []byte
   313  }
   314  
   315  func newMessage(pool *messagePool) *message {
   316  	return &message{
   317  		pool: pool,
   318  	}
   319  }
   320  
   321  // Encode encodes a m3msg payload
   322  //nolint:gocyclo,gocritic
   323  func (m *message) Encode(
   324  	shard uint32,
   325  	payload payloadUnion,
   326  ) error {
   327  	m.shard = shard
   328  
   329  	switch payload.payloadType {
   330  	case untimedType:
   331  		switch payload.untimed.metric.Type {
   332  		case metric.CounterType:
   333  			value := unaggregated.CounterWithMetadatas{
   334  				Counter:         payload.untimed.metric.Counter(),
   335  				StagedMetadatas: payload.untimed.metadatas,
   336  			}
   337  			if err := value.ToProto(&m.cm); err != nil {
   338  				return err
   339  			}
   340  
   341  			m.metric = metricpb.MetricWithMetadatas{
   342  				Type:                 metricpb.MetricWithMetadatas_COUNTER_WITH_METADATAS,
   343  				CounterWithMetadatas: &m.cm,
   344  			}
   345  		case metric.TimerType:
   346  			value := unaggregated.BatchTimerWithMetadatas{
   347  				BatchTimer:      payload.untimed.metric.BatchTimer(),
   348  				StagedMetadatas: payload.untimed.metadatas,
   349  			}
   350  			if err := value.ToProto(&m.bm); err != nil {
   351  				return err
   352  			}
   353  
   354  			m.metric = metricpb.MetricWithMetadatas{
   355  				Type:                    metricpb.MetricWithMetadatas_BATCH_TIMER_WITH_METADATAS,
   356  				BatchTimerWithMetadatas: &m.bm,
   357  			}
   358  		case metric.GaugeType:
   359  			value := unaggregated.GaugeWithMetadatas{
   360  				Gauge:           payload.untimed.metric.Gauge(),
   361  				StagedMetadatas: payload.untimed.metadatas,
   362  			}
   363  			if err := value.ToProto(&m.gm); err != nil {
   364  				return err
   365  			}
   366  
   367  			m.metric = metricpb.MetricWithMetadatas{
   368  				Type:               metricpb.MetricWithMetadatas_GAUGE_WITH_METADATAS,
   369  				GaugeWithMetadatas: &m.gm,
   370  			}
   371  		default:
   372  			return fmt.Errorf("unrecognized metric type: %v",
   373  				payload.untimed.metric.Type)
   374  		}
   375  	case forwardedType:
   376  		value := aggregated.ForwardedMetricWithMetadata{
   377  			ForwardedMetric: payload.forwarded.metric,
   378  			ForwardMetadata: payload.forwarded.metadata,
   379  		}
   380  		if err := value.ToProto(&m.fm); err != nil {
   381  			return err
   382  		}
   383  
   384  		m.metric = metricpb.MetricWithMetadatas{
   385  			Type:                        metricpb.MetricWithMetadatas_FORWARDED_METRIC_WITH_METADATA,
   386  			ForwardedMetricWithMetadata: &m.fm,
   387  		}
   388  	case timedType:
   389  		value := aggregated.TimedMetricWithMetadata{
   390  			Metric:        payload.timed.metric,
   391  			TimedMetadata: payload.timed.metadata,
   392  		}
   393  		if err := value.ToProto(&m.tm); err != nil {
   394  			return err
   395  		}
   396  
   397  		m.metric = metricpb.MetricWithMetadatas{
   398  			Type:                    metricpb.MetricWithMetadatas_TIMED_METRIC_WITH_METADATA,
   399  			TimedMetricWithMetadata: &m.tm,
   400  		}
   401  	case timedWithStagedMetadatasType:
   402  		value := aggregated.TimedMetricWithMetadatas{
   403  			Metric:          payload.timedWithStagedMetadatas.metric,
   404  			StagedMetadatas: payload.timedWithStagedMetadatas.metadatas,
   405  		}
   406  		if err := value.ToProto(&m.tms); err != nil {
   407  			return err
   408  		}
   409  
   410  		m.metric = metricpb.MetricWithMetadatas{
   411  			Type:                     metricpb.MetricWithMetadatas_TIMED_METRIC_WITH_METADATAS,
   412  			TimedMetricWithMetadatas: &m.tms,
   413  		}
   414  	default:
   415  		return fmt.Errorf("unrecognized payload type: %v",
   416  			payload.payloadType)
   417  	}
   418  
   419  	size := m.metric.Size()
   420  	if size > cap(m.buf) {
   421  		const growthFactor = 2
   422  		m.buf = make([]byte, int(growthFactor*float64(size)))
   423  	}
   424  
   425  	// Resize buffer to exactly how long we need for marshaling.
   426  	m.buf = m.buf[:size]
   427  
   428  	_, err := m.metric.MarshalTo(m.buf)
   429  	return err
   430  }
   431  
   432  func (m *message) Shard() uint32 {
   433  	return m.shard
   434  }
   435  
   436  func (m *message) Bytes() []byte {
   437  	return m.buf
   438  }
   439  
   440  func (m *message) Size() int {
   441  	return len(m.buf)
   442  }
   443  
   444  func (m *message) Finalize(reason producer.FinalizeReason) {
   445  	// Return to pool.
   446  	m.pool.Put(m)
   447  }