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

     1  // Copyright (c) 2019 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 ingestcarbon implements a carbon ingester.
    22  package ingestcarbon
    23  
    24  import (
    25  	"bytes"
    26  	"context"
    27  	"errors"
    28  	"fmt"
    29  	"net"
    30  	"regexp"
    31  	"sort"
    32  	"sync"
    33  	"time"
    34  
    35  	"github.com/m3db/m3/src/cmd/services/m3coordinator/downsample"
    36  	"github.com/m3db/m3/src/cmd/services/m3coordinator/ingest"
    37  	"github.com/m3db/m3/src/cmd/services/m3query/config"
    38  	"github.com/m3db/m3/src/metrics/aggregation"
    39  	"github.com/m3db/m3/src/metrics/carbon"
    40  	"github.com/m3db/m3/src/metrics/policy"
    41  	"github.com/m3db/m3/src/query/graphite/graphite"
    42  	"github.com/m3db/m3/src/query/models"
    43  	"github.com/m3db/m3/src/query/storage/m3"
    44  	"github.com/m3db/m3/src/query/storage/m3/storagemetadata"
    45  	"github.com/m3db/m3/src/query/ts"
    46  	"github.com/m3db/m3/src/x/instrument"
    47  	"github.com/m3db/m3/src/x/pool"
    48  	m3xserver "github.com/m3db/m3/src/x/server"
    49  	xsync "github.com/m3db/m3/src/x/sync"
    50  	xtime "github.com/m3db/m3/src/x/time"
    51  
    52  	"github.com/uber-go/tally"
    53  	"go.uber.org/zap"
    54  	"go.uber.org/zap/zapcore"
    55  )
    56  
    57  const (
    58  	maxResourcePoolNameSize = 1024
    59  	maxPooledTagsSize       = 16
    60  	defaultResourcePoolSize = 4096
    61  )
    62  
    63  var (
    64  	// Used for parsing carbon names into tags.
    65  	carbonSeparatorByte  = byte('.')
    66  	carbonSeparatorBytes = []byte{carbonSeparatorByte}
    67  
    68  	errCannotGenerateTagsFromEmptyName = errors.New("cannot generate tags from empty name")
    69  	errIOptsMustBeSet                  = errors.New("carbon ingester options: instrument options must be st")
    70  	errWorkerPoolMustBeSet             = errors.New("carbon ingester options: worker pool must be set")
    71  )
    72  
    73  // Options configures the ingester.
    74  type Options struct {
    75  	InstrumentOptions instrument.Options
    76  	WorkerPool        xsync.PooledWorkerPool
    77  	IngesterConfig    config.CarbonIngesterConfiguration
    78  }
    79  
    80  // CarbonIngesterRules contains the carbon ingestion rules.
    81  type CarbonIngesterRules struct {
    82  	Rules []config.CarbonIngesterRuleConfiguration
    83  }
    84  
    85  // Validate validates the options struct.
    86  func (o *Options) Validate() error {
    87  	if o.InstrumentOptions == nil {
    88  		return errIOptsMustBeSet
    89  	}
    90  	if o.WorkerPool == nil {
    91  		return errWorkerPoolMustBeSet
    92  	}
    93  	return nil
    94  }
    95  
    96  // NewIngester returns an ingester for carbon metrics.
    97  func NewIngester(
    98  	downsamplerAndWriter ingest.DownsamplerAndWriter,
    99  	clusterNamespacesWatcher m3.ClusterNamespacesWatcher,
   100  	opts Options,
   101  ) (m3xserver.Handler, error) {
   102  	err := opts.Validate()
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	tagOpts := models.NewTagOptions().SetIDSchemeType(models.TypeGraphite)
   108  	err = tagOpts.Validate()
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	poolOpts := pool.NewObjectPoolOptions().
   114  		SetInstrumentOptions(opts.InstrumentOptions).
   115  		SetRefillLowWatermark(0).
   116  		SetRefillHighWatermark(0).
   117  		SetSize(defaultResourcePoolSize)
   118  
   119  	resourcePool := pool.NewObjectPool(poolOpts)
   120  	resourcePool.Init(func() interface{} {
   121  		return &lineResources{
   122  			name:       make([]byte, 0, maxResourcePoolNameSize),
   123  			datapoints: make([]ts.Datapoint, 1),
   124  			tags:       make([]models.Tag, 0, maxPooledTagsSize),
   125  		}
   126  	})
   127  
   128  	scope := opts.InstrumentOptions.MetricsScope()
   129  	metrics, err := newCarbonIngesterMetrics(scope)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  
   134  	ingester := &ingester{
   135  		downsamplerAndWriter: downsamplerAndWriter,
   136  		opts:                 opts,
   137  		logger:               opts.InstrumentOptions.Logger(),
   138  		tagOpts:              tagOpts,
   139  		metrics:              metrics,
   140  		lineResourcesPool:    resourcePool,
   141  	}
   142  	// No need to retain watch as NamespaceWatcher.Close() will handle closing any watches
   143  	// generated by creating listeners.
   144  	clusterNamespacesWatcher.RegisterListener(ingester)
   145  
   146  	return ingester, nil
   147  }
   148  
   149  type ingester struct {
   150  	downsamplerAndWriter ingest.DownsamplerAndWriter
   151  	opts                 Options
   152  	logger               *zap.Logger
   153  	metrics              carbonIngesterMetrics
   154  	tagOpts              models.TagOptions
   155  
   156  	lineResourcesPool pool.ObjectPool
   157  
   158  	sync.RWMutex
   159  	rules []ruleAndMatcher
   160  }
   161  
   162  func (i *ingester) OnUpdate(clusterNamespaces m3.ClusterNamespaces) {
   163  	i.Lock()
   164  	defer i.Unlock()
   165  
   166  	rules := i.regenerateIngestionRulesWithLock(clusterNamespaces)
   167  	if rules == nil {
   168  		namespaces := make([]string, 0, len(clusterNamespaces))
   169  		for _, ns := range clusterNamespaces {
   170  			namespaces = append(namespaces, ns.NamespaceID().String())
   171  		}
   172  		i.logger.Warn("generated empty carbon ingestion rules from latest cluster namespaces update. leaving"+
   173  			" current set of rules as-is.", zap.Strings("namespaces", namespaces))
   174  		return
   175  	}
   176  
   177  	compiledRules, err := i.compileRulesWithLock(*rules)
   178  	if err != nil {
   179  		i.logger.Error("failed to compile latest rules. continuing to use existing carbon ingestion "+
   180  			"rules", zap.Error(err))
   181  		return
   182  	}
   183  
   184  	i.rules = compiledRules
   185  }
   186  
   187  func (i *ingester) regenerateIngestionRulesWithLock(clusterNamespaces m3.ClusterNamespaces) *CarbonIngesterRules {
   188  	var (
   189  		rules = &CarbonIngesterRules{
   190  			Rules: i.opts.IngesterConfig.RulesOrDefault(clusterNamespaces),
   191  		}
   192  		namespacesByRetention = make(map[m3.RetentionResolution]m3.ClusterNamespace, len(clusterNamespaces))
   193  	)
   194  
   195  	for _, ns := range clusterNamespaces {
   196  		if ns.Options().Attributes().MetricsType == storagemetadata.AggregatedMetricsType {
   197  			resRet := m3.RetentionResolution{
   198  				Resolution: ns.Options().Attributes().Resolution,
   199  				Retention:  ns.Options().Attributes().Retention,
   200  			}
   201  			// This should never happen
   202  			if _, ok := namespacesByRetention[resRet]; ok {
   203  				i.logger.Error(
   204  					"cannot have namespaces with duplicate resolution and retention",
   205  					zap.String("resolution", resRet.Resolution.String()),
   206  					zap.String("retention", resRet.Retention.String()))
   207  				return nil
   208  			}
   209  
   210  			namespacesByRetention[resRet] = ns
   211  		}
   212  	}
   213  
   214  	// Validate rule policies.
   215  	for _, rule := range rules.Rules {
   216  		// Sort so we can detect duplicates.
   217  		sort.Slice(rule.Policies, func(i, j int) bool {
   218  			if rule.Policies[i].Resolution == rule.Policies[j].Resolution {
   219  				return rule.Policies[i].Retention < rule.Policies[j].Retention
   220  			}
   221  
   222  			return rule.Policies[i].Resolution < rule.Policies[j].Resolution
   223  		})
   224  
   225  		var lastPolicy config.CarbonIngesterStoragePolicyConfiguration
   226  		for idx, policy := range rule.Policies {
   227  			if policy == lastPolicy {
   228  				i.logger.Error(
   229  					"cannot include the same storage policy multiple times for a single carbon ingestion rule",
   230  					zap.String("pattern", rule.Pattern),
   231  					zap.String("resolution", policy.Resolution.String()),
   232  					zap.String("retention", policy.Retention.String()))
   233  				return nil
   234  			}
   235  
   236  			if idx > 0 && !rule.Aggregation.EnabledOrDefault() && policy.Resolution != lastPolicy.Resolution {
   237  				i.logger.Error(
   238  					"cannot include multiple storage policies with different resolutions if aggregation is disabled",
   239  					zap.String("pattern", rule.Pattern),
   240  					zap.String("resolution", policy.Resolution.String()),
   241  					zap.String("retention", policy.Retention.String()))
   242  				return nil
   243  			}
   244  
   245  			_, ok := namespacesByRetention[m3.RetentionResolution{
   246  				Resolution: policy.Resolution,
   247  				Retention:  policy.Retention,
   248  			}]
   249  
   250  			// Disallow storage policies that don't match any known M3DB clusters.
   251  			if !ok {
   252  				i.logger.Error(
   253  					"cannot enable carbon ingestion without a corresponding aggregated M3DB namespace",
   254  					zap.String("resolution", policy.Resolution.String()), zap.String("retention", policy.Retention.String()))
   255  				return nil
   256  			}
   257  
   258  			lastPolicy = policy
   259  		}
   260  	}
   261  
   262  	if len(rules.Rules) == 0 {
   263  		i.logger.Warn("no carbon ingestion rules were provided and no aggregated M3DB namespaces exist, carbon metrics will not be ingested")
   264  		return nil
   265  	}
   266  
   267  	if len(i.opts.IngesterConfig.Rules) == 0 {
   268  		i.logger.Info("no carbon ingestion rules were provided, all carbon metrics will be written to all aggregated M3DB namespaces")
   269  	}
   270  
   271  	return rules
   272  }
   273  
   274  func (i *ingester) Handle(conn net.Conn) {
   275  	var (
   276  		// Interfaces require a context be passed, but M3DB client already has timeouts
   277  		// built in and allocating a new context each time is expensive so we just pass
   278  		// the same context always and rely on M3DB client timeouts.
   279  		ctx     = context.Background()
   280  		wg      = sync.WaitGroup{}
   281  		s       = carbon.NewScanner(conn, i.opts.InstrumentOptions)
   282  		logger  = i.opts.InstrumentOptions.Logger()
   283  		rewrite = &i.opts.IngesterConfig.Rewrite
   284  	)
   285  
   286  	logger.Debug("handling new carbon ingestion connection")
   287  	for s.Scan() {
   288  		received := time.Now()
   289  		name, timestamp, value := s.Metric()
   290  
   291  		resources := i.getLineResources()
   292  
   293  		// Copy name since scanner bytes are recycled.
   294  		resources.name = copyAndRewrite(resources.name, name, rewrite)
   295  
   296  		wg.Add(1)
   297  		i.opts.WorkerPool.Go(func() {
   298  			ok := i.write(ctx, resources, xtime.ToUnixNano(timestamp), value)
   299  			if ok {
   300  				i.metrics.success.Inc(1)
   301  			}
   302  
   303  			now := time.Now()
   304  
   305  			// Always record age regardless of success/failure since
   306  			// sometimes errors can be due to how old the metrics are
   307  			// and not recording age would obscure this visibility from
   308  			// the metrics of how fresh/old the incoming metrics are.
   309  			age := now.Sub(timestamp)
   310  			i.metrics.ingestLatency.RecordDuration(age)
   311  
   312  			// Also record write latency (not relative to metric timestamp).
   313  			i.metrics.writeLatency.RecordDuration(now.Sub(received))
   314  
   315  			// The contract is that after the DownsamplerAndWriter returns, any resources
   316  			// that it needed to hold onto have already been copied.
   317  			i.putLineResources(resources)
   318  			wg.Done()
   319  		})
   320  
   321  		i.metrics.malformed.Inc(int64(s.MalformedCount))
   322  		s.MalformedCount = 0
   323  	}
   324  
   325  	if err := s.Err(); err != nil {
   326  		logger.Error("encountered error during carbon ingestion when scanning connection", zap.Error(err))
   327  	}
   328  
   329  	logger.Debug("waiting for outstanding carbon ingestion writes to complete")
   330  	wg.Wait()
   331  	logger.Debug("all outstanding writes completed, shutting down carbon ingestion handler")
   332  
   333  	// Don't close the connection, that is the server's responsibility.
   334  }
   335  
   336  func (i *ingester) write(
   337  	ctx context.Context,
   338  	resources *lineResources,
   339  	timestamp xtime.UnixNano,
   340  	value float64,
   341  ) bool {
   342  	downsampleAndStoragePolicies := ingest.WriteOptions{
   343  		// Set both of these overrides to true to indicate that only the exact mapping
   344  		// rules and storage policies that we provide should be used and that all
   345  		// default behavior (like performing all possible downsamplings and writing
   346  		// all data to the unaggregated namespace in storage) should be ignored.
   347  		DownsampleOverride: true,
   348  		WriteOverride:      true,
   349  	}
   350  
   351  	matched := 0
   352  	defer func() {
   353  		if matched == 0 {
   354  			// No policies matched.
   355  			debugLog := i.logger.Check(zapcore.DebugLevel, "no rules matched carbon metric, skipping")
   356  			if debugLog != nil {
   357  				debugLog.Write(zap.ByteString("name", resources.name))
   358  			}
   359  			return
   360  		}
   361  
   362  		debugLog := i.logger.Check(zapcore.DebugLevel, "successfully wrote carbon metric")
   363  		if debugLog != nil {
   364  			debugLog.Write(zap.ByteString("name", resources.name),
   365  				zap.Int("matchedRules", matched))
   366  		}
   367  	}()
   368  
   369  	i.RLock()
   370  	rules := i.rules
   371  	i.RUnlock()
   372  
   373  	for _, rule := range rules {
   374  		var matches bool
   375  		switch {
   376  		case rule.rule.Pattern == graphite.MatchAllPattern:
   377  			matches = true
   378  		case rule.regexp != nil:
   379  			matches = rule.regexp.Match(resources.name)
   380  		case len(rule.contains) != 0:
   381  			matches = bytes.Contains(resources.name, rule.contains)
   382  		}
   383  
   384  		if matches {
   385  			// Each rule should only have either mapping rules or storage policies so
   386  			// one of these should be a no-op.
   387  			downsampleAndStoragePolicies.DownsampleMappingRules = rule.mappingRules
   388  			downsampleAndStoragePolicies.WriteStoragePolicies = rule.storagePolicies
   389  
   390  			debugLog := i.logger.Check(zapcore.DebugLevel, "carbon metric matched by pattern")
   391  			if debugLog != nil {
   392  				debugLog.Write(zap.ByteString("name", resources.name),
   393  					zap.String("pattern", rule.rule.Pattern),
   394  					zap.Any("mappingRules", rule.mappingRules),
   395  					zap.Any("storagePolicies", rule.storagePolicies))
   396  			}
   397  
   398  			// Break because we only want to apply one rule per metric based on which
   399  			// ever one matches first.
   400  			err := i.writeWithOptions(ctx, resources, timestamp, value, downsampleAndStoragePolicies)
   401  			if err != nil {
   402  				return false
   403  			}
   404  
   405  			matched++
   406  
   407  			// If continue is not specified then we matched the current set of rules.
   408  			if !rule.rule.Continue {
   409  				break
   410  			}
   411  		}
   412  	}
   413  
   414  	return matched > 0
   415  }
   416  
   417  func (i *ingester) writeWithOptions(
   418  	ctx context.Context,
   419  	resources *lineResources,
   420  	timestamp xtime.UnixNano,
   421  	value float64,
   422  	opts ingest.WriteOptions,
   423  ) error {
   424  	resources.datapoints[0] = ts.Datapoint{Timestamp: timestamp, Value: value}
   425  	tags, err := GenerateTagsFromNameIntoSlice(resources.name, i.tagOpts, resources.tags)
   426  	if err != nil {
   427  		i.logger.Error("err generating tags from carbon",
   428  			zap.String("name", string(resources.name)), zap.Error(err))
   429  		i.metrics.malformed.Inc(1)
   430  		return err
   431  	}
   432  
   433  	err = i.downsamplerAndWriter.Write(ctx, tags, resources.datapoints,
   434  		xtime.Second, nil, opts, ts.SourceTypeGraphite)
   435  	if err != nil {
   436  		i.logger.Error("err writing carbon metric",
   437  			zap.String("name", string(resources.name)), zap.Error(err))
   438  		i.metrics.err.Inc(1)
   439  		return err
   440  	}
   441  
   442  	return nil
   443  }
   444  
   445  func (i *ingester) Close() {
   446  	// We don't maintain any state in-between connections so there is nothing to do here.
   447  }
   448  
   449  type carbonIngesterMetrics struct {
   450  	success       tally.Counter
   451  	err           tally.Counter
   452  	malformed     tally.Counter
   453  	ingestLatency tally.Histogram
   454  	writeLatency  tally.Histogram
   455  }
   456  
   457  func newCarbonIngesterMetrics(scope tally.Scope) (carbonIngesterMetrics, error) {
   458  	buckets, err := ingest.NewLatencyBuckets()
   459  	if err != nil {
   460  		return carbonIngesterMetrics{}, err
   461  	}
   462  	return carbonIngesterMetrics{
   463  		success:       scope.Counter("success"),
   464  		err:           scope.Counter("error"),
   465  		malformed:     scope.Counter("malformed"),
   466  		writeLatency:  scope.SubScope("write").Histogram("latency", buckets.WriteLatencyBuckets),
   467  		ingestLatency: scope.SubScope("ingest").Histogram("latency", buckets.IngestLatencyBuckets),
   468  	}, nil
   469  }
   470  
   471  // GenerateTagsFromName accepts a carbon metric name and blows it up into a list of
   472  // key-value pair tags such that an input like:
   473  //      foo.bar.baz
   474  // becomes
   475  //      __g0__:foo
   476  //      __g1__:bar
   477  //      __g2__:baz
   478  func GenerateTagsFromName(
   479  	name []byte,
   480  	opts models.TagOptions,
   481  ) (models.Tags, error) {
   482  	return generateTagsFromName(name, opts, nil)
   483  }
   484  
   485  // GenerateTagsFromNameIntoSlice does the same thing as GenerateTagsFromName except
   486  // it allows the caller to provide the slice into which the tags are appended.
   487  func GenerateTagsFromNameIntoSlice(
   488  	name []byte,
   489  	opts models.TagOptions,
   490  	tags []models.Tag,
   491  ) (models.Tags, error) {
   492  	return generateTagsFromName(name, opts, tags)
   493  }
   494  
   495  func generateTagsFromName(
   496  	name []byte,
   497  	opts models.TagOptions,
   498  	tags []models.Tag,
   499  ) (models.Tags, error) {
   500  	if len(name) == 0 {
   501  		return models.EmptyTags(), errCannotGenerateTagsFromEmptyName
   502  	}
   503  
   504  	numTags := bytes.Count(name, carbonSeparatorBytes) + 1
   505  
   506  	if cap(tags) >= numTags {
   507  		tags = tags[:0]
   508  	} else {
   509  		tags = make([]models.Tag, 0, numTags)
   510  	}
   511  
   512  	startIdx := 0
   513  	tagNum := 0
   514  	for i, charByte := range name {
   515  		if charByte == carbonSeparatorByte {
   516  			if i+1 < len(name) && name[i+1] == carbonSeparatorByte {
   517  				return models.EmptyTags(),
   518  					fmt.Errorf("carbon metric: %s has duplicate separator", string(name))
   519  			}
   520  
   521  			tags = append(tags, models.Tag{
   522  				Name:  graphite.TagName(tagNum),
   523  				Value: name[startIdx:i],
   524  			})
   525  			startIdx = i + 1
   526  			tagNum++
   527  		}
   528  	}
   529  
   530  	// Write out the final tag since the for loop above will miss anything
   531  	// after the final separator. Note, that we make sure that the final
   532  	// character in the name is not the separator because in that case there
   533  	// would be no additional tag to add. I.E if the input was:
   534  	//      foo.bar.baz
   535  	// then the for loop would append foo and bar, but we would still need to
   536  	// append baz, however, if the input was:
   537  	//      foo.bar.baz.
   538  	// then the foor loop would have appended foo, bar, and baz already.
   539  	if name[len(name)-1] != carbonSeparatorByte {
   540  		tags = append(tags, models.Tag{
   541  			Name:  graphite.TagName(tagNum),
   542  			Value: name[startIdx:],
   543  		})
   544  	}
   545  
   546  	return models.Tags{Opts: opts, Tags: tags}, nil
   547  }
   548  
   549  // Compile all the carbon ingestion rules into matcher so that we can
   550  // perform matching. Also, generate all the mapping rules and storage
   551  // policies that we will need to pass to the DownsamplerAndWriter upfront
   552  // so that we don't need to create them each time.
   553  //
   554  // Note that only one rule will be applied per metric and rules are applied
   555  // such that the first one that matches takes precedence. As a result we need
   556  // to make sure to maintain the order of the rules when we generate the compiled ones.
   557  func (i *ingester) compileRulesWithLock(rules CarbonIngesterRules) ([]ruleAndMatcher, error) {
   558  	compiledRules := make([]ruleAndMatcher, 0, len(rules.Rules))
   559  	for _, rule := range rules.Rules {
   560  		if rule.Pattern != "" && rule.Contains != "" {
   561  			return nil, fmt.Errorf(
   562  				"rule contains both pattern and contains: pattern=%s, contains=%s",
   563  				rule.Pattern, rule.Contains)
   564  		}
   565  
   566  		var (
   567  			contains []byte
   568  			compiled *regexp.Regexp
   569  		)
   570  		if rule.Contains != "" {
   571  			contains = []byte(rule.Contains)
   572  		} else {
   573  			var err error
   574  			compiled, err = regexp.Compile(rule.Pattern)
   575  			if err != nil {
   576  				return nil, err
   577  			}
   578  		}
   579  
   580  		storagePolicies := make([]policy.StoragePolicy, 0, len(rule.Policies))
   581  		for _, currPolicy := range rule.Policies {
   582  			storagePolicy := policy.NewStoragePolicy(
   583  				currPolicy.Resolution, xtime.Second, currPolicy.Retention)
   584  			storagePolicies = append(storagePolicies, storagePolicy)
   585  		}
   586  
   587  		compiledRule := ruleAndMatcher{
   588  			rule:     rule,
   589  			contains: contains,
   590  			regexp:   compiled,
   591  		}
   592  
   593  		if rule.Aggregation.EnabledOrDefault() {
   594  			compiledRule.mappingRules = []downsample.AutoMappingRule{
   595  				{
   596  					Aggregations: []aggregation.Type{rule.Aggregation.TypeOrDefault()},
   597  					Policies:     storagePolicies,
   598  				},
   599  			}
   600  		} else {
   601  			compiledRule.storagePolicies = storagePolicies
   602  		}
   603  		compiledRules = append(compiledRules, compiledRule)
   604  	}
   605  
   606  	return compiledRules, nil
   607  }
   608  
   609  func (i *ingester) getLineResources() *lineResources {
   610  	return i.lineResourcesPool.Get().(*lineResources)
   611  }
   612  
   613  func (i *ingester) putLineResources(l *lineResources) {
   614  	tooLargeForPool := cap(l.name) > maxResourcePoolNameSize ||
   615  		len(l.datapoints) > 1 || // We always write one datapoint at a time.
   616  		cap(l.datapoints) > 1 ||
   617  		cap(l.tags) > maxPooledTagsSize
   618  
   619  	if tooLargeForPool {
   620  		return
   621  	}
   622  
   623  	// Reset.
   624  	l.name = l.name[:0]
   625  	l.datapoints[0] = ts.Datapoint{}
   626  	for i := range l.tags {
   627  		// Free pointers.
   628  		l.tags[i] = models.Tag{}
   629  	}
   630  	l.tags = l.tags[:0]
   631  
   632  	i.lineResourcesPool.Put(l)
   633  }
   634  
   635  type lineResources struct {
   636  	name       []byte
   637  	datapoints []ts.Datapoint
   638  	tags       []models.Tag
   639  }
   640  
   641  type ruleAndMatcher struct {
   642  	rule            config.CarbonIngesterRuleConfiguration
   643  	regexp          *regexp.Regexp
   644  	contains        []byte
   645  	mappingRules    []downsample.AutoMappingRule
   646  	storagePolicies []policy.StoragePolicy
   647  }