github.com/m3db/m3@v1.5.0/src/cmd/services/m3coordinator/ingest/m3msg/ingest.go (about)

     1  // Copyright (c) 2018 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 ingestm3msg
    22  
    23  import (
    24  	"bytes"
    25  	"context"
    26  
    27  	"github.com/m3db/m3/src/cmd/services/m3coordinator/downsample"
    28  	"github.com/m3db/m3/src/cmd/services/m3coordinator/server/m3msg"
    29  	"github.com/m3db/m3/src/metrics/metric/id"
    30  	"github.com/m3db/m3/src/metrics/policy"
    31  	"github.com/m3db/m3/src/query/models"
    32  	"github.com/m3db/m3/src/query/storage"
    33  	"github.com/m3db/m3/src/query/storage/m3/storagemetadata"
    34  	"github.com/m3db/m3/src/query/ts"
    35  	"github.com/m3db/m3/src/x/convert"
    36  	xerrors "github.com/m3db/m3/src/x/errors"
    37  	"github.com/m3db/m3/src/x/instrument"
    38  	"github.com/m3db/m3/src/x/pool"
    39  	"github.com/m3db/m3/src/x/retry"
    40  	"github.com/m3db/m3/src/x/sampler"
    41  	"github.com/m3db/m3/src/x/serialize"
    42  	xsync "github.com/m3db/m3/src/x/sync"
    43  	xtime "github.com/m3db/m3/src/x/time"
    44  
    45  	"github.com/uber-go/tally"
    46  	"go.uber.org/zap"
    47  )
    48  
    49  // Options configures the ingester.
    50  type Options struct {
    51  	Appender          storage.Appender
    52  	Workers           xsync.PooledWorkerPool
    53  	PoolOptions       pool.ObjectPoolOptions
    54  	TagDecoderPool    serialize.TagDecoderPool
    55  	RetryOptions      retry.Options
    56  	Sampler           *sampler.Sampler
    57  	InstrumentOptions instrument.Options
    58  	TagOptions        models.TagOptions
    59  }
    60  
    61  type ingestMetrics struct {
    62  	ingestInternalError     tally.Counter
    63  	ingestNonRetryableError tally.Counter
    64  	ingestSuccess           tally.Counter
    65  }
    66  
    67  func newIngestMetrics(scope tally.Scope) ingestMetrics {
    68  	return ingestMetrics{
    69  		ingestInternalError: scope.Tagged(map[string]string{
    70  			"error_type": "internal_error",
    71  		}).Counter("ingest-error"),
    72  		ingestNonRetryableError: scope.Tagged(map[string]string{
    73  			"error_type": "non_retryable_error",
    74  		}).Counter("ingest-error"),
    75  		ingestSuccess: scope.Counter("ingest-success"),
    76  	}
    77  }
    78  
    79  // Ingester ingests metrics with a worker pool.
    80  type Ingester struct {
    81  	workers xsync.PooledWorkerPool
    82  	p       pool.ObjectPool
    83  }
    84  
    85  // NewIngester creates an ingester.
    86  func NewIngester(
    87  	opts Options,
    88  ) *Ingester {
    89  	retrier := retry.NewRetrier(opts.RetryOptions)
    90  	m := newIngestMetrics(opts.InstrumentOptions.MetricsScope())
    91  	p := pool.NewObjectPool(opts.PoolOptions)
    92  	tagOpts := opts.TagOptions
    93  	if tagOpts == nil {
    94  		tagOpts = models.NewTagOptions()
    95  	}
    96  	p.Init(
    97  		func() interface{} {
    98  			// NB: we don't need a pool for the tag decoder since the ops are
    99  			// pooled, but currently this is the only way to get tag decoder.
   100  			tagDecoder := opts.TagDecoderPool.Get()
   101  			op := ingestOp{
   102  				s:       opts.Appender,
   103  				r:       retrier,
   104  				it:      serialize.NewMetricTagsIterator(tagDecoder, nil),
   105  				tagOpts: tagOpts,
   106  				p:       p,
   107  				m:       m,
   108  				logger:  opts.InstrumentOptions.Logger(),
   109  				sampler: opts.Sampler,
   110  			}
   111  			op.attemptFn = op.attempt
   112  			op.ingestFn = op.ingest
   113  			return &op
   114  		},
   115  	)
   116  	return &Ingester{
   117  		workers: opts.Workers,
   118  		p:       p,
   119  	}
   120  }
   121  
   122  // Ingest ingests a metric asynchronously with callback.
   123  func (i *Ingester) Ingest(
   124  	ctx context.Context,
   125  	id []byte,
   126  	metricNanos, encodeNanos int64,
   127  	value float64,
   128  	annotation []byte,
   129  	sp policy.StoragePolicy,
   130  	callback m3msg.Callbackable,
   131  ) {
   132  	op := i.p.Get().(*ingestOp)
   133  	op.c = ctx
   134  	op.id = id
   135  	op.metricNanos = metricNanos
   136  	op.value = value
   137  	op.annotation = annotation
   138  	op.sp = sp
   139  	op.callback = callback
   140  	i.workers.Go(op.ingestFn)
   141  }
   142  
   143  type ingestOp struct {
   144  	s         storage.Appender
   145  	r         retry.Retrier
   146  	it        id.SortedTagIterator
   147  	tagOpts   models.TagOptions
   148  	p         pool.ObjectPool
   149  	m         ingestMetrics
   150  	logger    *zap.Logger
   151  	sampler   *sampler.Sampler
   152  	attemptFn retry.Fn
   153  	ingestFn  func()
   154  
   155  	c           context.Context
   156  	id          []byte
   157  	metricNanos int64
   158  	value       float64
   159  	annotation  []byte
   160  	sp          policy.StoragePolicy
   161  	callback    m3msg.Callbackable
   162  	tags        models.Tags
   163  	datapoints  ts.Datapoints
   164  	q           storage.WriteQuery
   165  }
   166  
   167  func (op *ingestOp) sample() bool {
   168  	if op.sampler == nil {
   169  		return false
   170  	}
   171  	return op.sampler.Sample()
   172  }
   173  
   174  func (op *ingestOp) ingest() {
   175  	if err := op.resetWriteQuery(); err != nil {
   176  		op.m.ingestInternalError.Inc(1)
   177  		op.callback.Callback(m3msg.OnRetriableError)
   178  		op.p.Put(op)
   179  		if op.sample() {
   180  			op.logger.Error("could not reset ingest op", zap.Error(err))
   181  		}
   182  		return
   183  	}
   184  	if err := op.r.Attempt(op.attemptFn); err != nil {
   185  		nonRetryableErr := xerrors.IsNonRetryableError(err)
   186  		if nonRetryableErr {
   187  			op.callback.Callback(m3msg.OnNonRetriableError)
   188  			op.m.ingestNonRetryableError.Inc(1)
   189  		} else {
   190  			op.callback.Callback(m3msg.OnRetriableError)
   191  			op.m.ingestInternalError.Inc(1)
   192  		}
   193  
   194  		// NB(r): Always log non-retriable errors since they are usually
   195  		// a very small minority and when they go missing it can be frustrating
   196  		// not being able to find them (usually bad request errors).
   197  		if nonRetryableErr || op.sample() {
   198  			op.logger.Error("could not write ingest op",
   199  				zap.Error(err),
   200  				zap.Bool("retryableError", !nonRetryableErr))
   201  		}
   202  
   203  		op.p.Put(op)
   204  		return
   205  	}
   206  	op.m.ingestSuccess.Inc(1)
   207  	op.callback.Callback(m3msg.OnSuccess)
   208  	op.p.Put(op)
   209  }
   210  
   211  func (op *ingestOp) attempt() error {
   212  	return op.s.Write(op.c, &op.q)
   213  }
   214  
   215  func (op *ingestOp) resetWriteQuery() error {
   216  	if err := op.resetTags(); err != nil {
   217  		return err
   218  	}
   219  	op.resetDataPoints()
   220  	return op.q.Reset(storage.WriteQueryOptions{
   221  		Tags:       op.tags,
   222  		Datapoints: op.datapoints,
   223  		Unit:       convert.UnitForM3DB(op.sp.Resolution().Precision),
   224  		Attributes: storagemetadata.Attributes{
   225  			MetricsType: storagemetadata.AggregatedMetricsType,
   226  			Resolution:  op.sp.Resolution().Window,
   227  			Retention:   op.sp.Retention().Duration(),
   228  		},
   229  		Annotation: op.annotation,
   230  	})
   231  }
   232  
   233  func (op *ingestOp) resetTags() error {
   234  	op.it.Reset(op.id)
   235  	op.tags.Tags = op.tags.Tags[:0]
   236  	op.tags.Opts = op.tagOpts
   237  	for op.it.Next() {
   238  		name, value := op.it.Current()
   239  
   240  		// TODO_FIX_GRAPHITE_TAGGING: Using this string constant to track
   241  		// all places worth fixing this hack. There is at least one
   242  		// other path where flows back to the coordinator from the aggregator
   243  		// and this tag is interpreted, eventually need to handle more cleanly.
   244  		if bytes.Equal(name, downsample.MetricsOptionIDSchemeTagName) {
   245  			if bytes.Equal(value, downsample.GraphiteIDSchemeTagValue) &&
   246  				op.tags.Opts.IDSchemeType() != models.TypeGraphite {
   247  				// Restart iteration with graphite tag options parsing
   248  				op.it.Reset(op.id)
   249  				op.tags.Tags = op.tags.Tags[:0]
   250  				op.tags.Opts = op.tags.Opts.SetIDSchemeType(models.TypeGraphite)
   251  			}
   252  			// Continue, whether we updated and need to restart iteration,
   253  			// or if passing for the second time
   254  			continue
   255  		}
   256  
   257  		op.tags = op.tags.AddTagWithoutNormalizing(models.Tag{
   258  			Name:  name,
   259  			Value: value,
   260  		}.Clone())
   261  	}
   262  	op.tags.Normalize()
   263  	return op.it.Err()
   264  }
   265  
   266  func (op *ingestOp) resetDataPoints() {
   267  	if len(op.datapoints) != 1 {
   268  		op.datapoints = make(ts.Datapoints, 1)
   269  	}
   270  	op.datapoints[0].Timestamp = xtime.UnixNano(op.metricNanos)
   271  	op.datapoints[0].Value = op.value
   272  }