github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/ingester/ingester_test.go (about)

     1  package ingester
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"math"
     8  	"math/rand"
     9  	"net/http"
    10  	"os"
    11  	"path/filepath"
    12  	"sort"
    13  	"strconv"
    14  	"strings"
    15  	"sync"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/go-kit/log"
    20  	"github.com/grafana/dskit/ring"
    21  	"github.com/grafana/dskit/services"
    22  	"github.com/prometheus/client_golang/prometheus"
    23  	"github.com/prometheus/client_golang/prometheus/testutil"
    24  	"github.com/prometheus/common/model"
    25  	"github.com/prometheus/prometheus/pkg/labels"
    26  	"github.com/stretchr/testify/assert"
    27  	"github.com/stretchr/testify/require"
    28  	"github.com/weaveworks/common/httpgrpc"
    29  	"github.com/weaveworks/common/user"
    30  	"google.golang.org/grpc"
    31  
    32  	"github.com/cortexproject/cortex/pkg/chunk"
    33  	promchunk "github.com/cortexproject/cortex/pkg/chunk/encoding"
    34  	"github.com/cortexproject/cortex/pkg/cortexpb"
    35  	"github.com/cortexproject/cortex/pkg/ingester/client"
    36  	"github.com/cortexproject/cortex/pkg/util/chunkcompat"
    37  	"github.com/cortexproject/cortex/pkg/util/test"
    38  	"github.com/cortexproject/cortex/pkg/util/validation"
    39  )
    40  
    41  type testStore struct {
    42  	mtx sync.Mutex
    43  	// Chunks keyed by userID.
    44  	chunks map[string][]chunk.Chunk
    45  }
    46  
    47  func newTestStore(t require.TestingT, cfg Config, clientConfig client.Config, limits validation.Limits, reg prometheus.Registerer) (*testStore, *Ingester) {
    48  	store := &testStore{
    49  		chunks: map[string][]chunk.Chunk{},
    50  	}
    51  	overrides, err := validation.NewOverrides(limits, nil)
    52  	require.NoError(t, err)
    53  
    54  	ing, err := New(cfg, clientConfig, overrides, store, reg, log.NewNopLogger())
    55  	require.NoError(t, err)
    56  	require.NoError(t, services.StartAndAwaitRunning(context.Background(), ing))
    57  
    58  	return store, ing
    59  }
    60  
    61  func newDefaultTestStore(t testing.TB) (*testStore, *Ingester) {
    62  	t.Helper()
    63  
    64  	return newTestStore(t,
    65  		defaultIngesterTestConfig(t),
    66  		defaultClientTestConfig(),
    67  		defaultLimitsTestConfig(), nil)
    68  }
    69  
    70  func (s *testStore) Put(ctx context.Context, chunks []chunk.Chunk) error {
    71  	if len(chunks) == 0 {
    72  		return nil
    73  	}
    74  	s.mtx.Lock()
    75  	defer s.mtx.Unlock()
    76  
    77  	for _, chunk := range chunks {
    78  		for _, v := range chunk.Metric {
    79  			if v.Value == "" {
    80  				return fmt.Errorf("Chunk has blank label %q", v.Name)
    81  			}
    82  		}
    83  	}
    84  	userID := chunks[0].UserID
    85  	s.chunks[userID] = append(s.chunks[userID], chunks...)
    86  	return nil
    87  }
    88  
    89  func (s *testStore) Stop() {}
    90  
    91  // check that the store is holding data equivalent to what we expect
    92  func (s *testStore) checkData(t *testing.T, userIDs []string, testData map[string]model.Matrix) {
    93  	s.mtx.Lock()
    94  	defer s.mtx.Unlock()
    95  	for _, userID := range userIDs {
    96  		res, err := chunk.ChunksToMatrix(context.Background(), s.chunks[userID], model.Time(0), model.Time(math.MaxInt64))
    97  		require.NoError(t, err)
    98  		sort.Sort(res)
    99  		assert.Equal(t, testData[userID], res, "userID %s", userID)
   100  	}
   101  }
   102  
   103  func buildTestMatrix(numSeries int, samplesPerSeries int, offset int) model.Matrix {
   104  	m := make(model.Matrix, 0, numSeries)
   105  	for i := 0; i < numSeries; i++ {
   106  		ss := model.SampleStream{
   107  			Metric: model.Metric{
   108  				model.MetricNameLabel: model.LabelValue(fmt.Sprintf("testmetric_%d", i)),
   109  				model.JobLabel:        model.LabelValue(fmt.Sprintf("testjob%d", i%2)),
   110  			},
   111  			Values: make([]model.SamplePair, 0, samplesPerSeries),
   112  		}
   113  		for j := 0; j < samplesPerSeries; j++ {
   114  			ss.Values = append(ss.Values, model.SamplePair{
   115  				Timestamp: model.Time(i + j + offset),
   116  				Value:     model.SampleValue(i + j + offset),
   117  			})
   118  		}
   119  		m = append(m, &ss)
   120  	}
   121  	sort.Sort(m)
   122  	return m
   123  }
   124  
   125  func matrixToSamples(m model.Matrix) []cortexpb.Sample {
   126  	var samples []cortexpb.Sample
   127  	for _, ss := range m {
   128  		for _, sp := range ss.Values {
   129  			samples = append(samples, cortexpb.Sample{
   130  				TimestampMs: int64(sp.Timestamp),
   131  				Value:       float64(sp.Value),
   132  			})
   133  		}
   134  	}
   135  	return samples
   136  }
   137  
   138  // Return one copy of the labels per sample
   139  func matrixToLables(m model.Matrix) []labels.Labels {
   140  	var labels []labels.Labels
   141  	for _, ss := range m {
   142  		for range ss.Values {
   143  			labels = append(labels, cortexpb.FromLabelAdaptersToLabels(cortexpb.FromMetricsToLabelAdapters(ss.Metric)))
   144  		}
   145  	}
   146  	return labels
   147  }
   148  
   149  func runTestQuery(ctx context.Context, t *testing.T, ing *Ingester, ty labels.MatchType, n, v string) (model.Matrix, *client.QueryRequest, error) {
   150  	return runTestQueryTimes(ctx, t, ing, ty, n, v, model.Earliest, model.Latest)
   151  }
   152  
   153  func runTestQueryTimes(ctx context.Context, t *testing.T, ing *Ingester, ty labels.MatchType, n, v string, start, end model.Time) (model.Matrix, *client.QueryRequest, error) {
   154  	matcher, err := labels.NewMatcher(ty, n, v)
   155  	if err != nil {
   156  		return nil, nil, err
   157  	}
   158  	req, err := client.ToQueryRequest(start, end, []*labels.Matcher{matcher})
   159  	if err != nil {
   160  		return nil, nil, err
   161  	}
   162  	resp, err := ing.Query(ctx, req)
   163  	if err != nil {
   164  		return nil, nil, err
   165  	}
   166  	res := client.FromQueryResponse(resp)
   167  	sort.Sort(res)
   168  	return res, req, nil
   169  }
   170  
   171  func pushTestMetadata(t *testing.T, ing *Ingester, numMetadata, metadataPerMetric int) ([]string, map[string][]*cortexpb.MetricMetadata) {
   172  	userIDs := []string{"1", "2", "3"}
   173  
   174  	// Create test metadata.
   175  	// Map of userIDs, to map of metric => metadataSet
   176  	testData := map[string][]*cortexpb.MetricMetadata{}
   177  	for _, userID := range userIDs {
   178  		metadata := make([]*cortexpb.MetricMetadata, 0, metadataPerMetric)
   179  		for i := 0; i < numMetadata; i++ {
   180  			metricName := fmt.Sprintf("testmetric_%d", i)
   181  			for j := 0; j < metadataPerMetric; j++ {
   182  				m := &cortexpb.MetricMetadata{MetricFamilyName: metricName, Help: fmt.Sprintf("a help for %d", j), Unit: "", Type: cortexpb.COUNTER}
   183  				metadata = append(metadata, m)
   184  			}
   185  		}
   186  		testData[userID] = metadata
   187  	}
   188  
   189  	// Append metadata.
   190  	for _, userID := range userIDs {
   191  		ctx := user.InjectOrgID(context.Background(), userID)
   192  		_, err := ing.Push(ctx, cortexpb.ToWriteRequest(nil, nil, testData[userID], cortexpb.API))
   193  		require.NoError(t, err)
   194  	}
   195  
   196  	return userIDs, testData
   197  }
   198  
   199  func pushTestSamples(t testing.TB, ing *Ingester, numSeries, samplesPerSeries, offset int) ([]string, map[string]model.Matrix) {
   200  	userIDs := []string{"1", "2", "3"}
   201  
   202  	// Create test samples.
   203  	testData := map[string]model.Matrix{}
   204  	for i, userID := range userIDs {
   205  		testData[userID] = buildTestMatrix(numSeries, samplesPerSeries, i+offset)
   206  	}
   207  
   208  	// Append samples.
   209  	for _, userID := range userIDs {
   210  		ctx := user.InjectOrgID(context.Background(), userID)
   211  		_, err := ing.Push(ctx, cortexpb.ToWriteRequest(matrixToLables(testData[userID]), matrixToSamples(testData[userID]), nil, cortexpb.API))
   212  		require.NoError(t, err)
   213  	}
   214  
   215  	return userIDs, testData
   216  }
   217  
   218  func retrieveTestSamples(t *testing.T, ing *Ingester, userIDs []string, testData map[string]model.Matrix) {
   219  	// Read samples back via ingester queries.
   220  	for _, userID := range userIDs {
   221  		ctx := user.InjectOrgID(context.Background(), userID)
   222  		res, req, err := runTestQuery(ctx, t, ing, labels.MatchRegexp, model.JobLabel, ".+")
   223  		require.NoError(t, err)
   224  		assert.Equal(t, testData[userID], res)
   225  
   226  		s := stream{
   227  			ctx: ctx,
   228  		}
   229  		err = ing.QueryStream(req, &s)
   230  		require.NoError(t, err)
   231  
   232  		res, err = chunkcompat.StreamsToMatrix(model.Earliest, model.Latest, s.responses)
   233  		require.NoError(t, err)
   234  		assert.Equal(t, testData[userID].String(), res.String())
   235  	}
   236  }
   237  
   238  func TestIngesterAppend(t *testing.T) {
   239  	store, ing := newDefaultTestStore(t)
   240  	userIDs, testData := pushTestSamples(t, ing, 10, 1000, 0)
   241  	retrieveTestSamples(t, ing, userIDs, testData)
   242  
   243  	// Read samples back via chunk store.
   244  	require.NoError(t, services.StopAndAwaitTerminated(context.Background(), ing))
   245  	store.checkData(t, userIDs, testData)
   246  }
   247  
   248  func TestIngesterMetadataAppend(t *testing.T) {
   249  	for _, tc := range []struct {
   250  		desc              string
   251  		numMetadata       int
   252  		metadataPerMetric int
   253  		expectedMetrics   int
   254  		expectedMetadata  int
   255  		err               error
   256  	}{
   257  		{"with no metadata", 0, 0, 0, 0, nil},
   258  		{"with one metadata per metric", 10, 1, 10, 10, nil},
   259  		{"with multiple metadata per metric", 10, 3, 10, 30, nil},
   260  	} {
   261  		t.Run(tc.desc, func(t *testing.T) {
   262  			limits := defaultLimitsTestConfig()
   263  			limits.MaxLocalMetadataPerMetric = 50
   264  			_, ing := newTestStore(t, defaultIngesterTestConfig(t), defaultClientTestConfig(), limits, nil)
   265  			userIDs, _ := pushTestMetadata(t, ing, tc.numMetadata, tc.metadataPerMetric)
   266  
   267  			for _, userID := range userIDs {
   268  				ctx := user.InjectOrgID(context.Background(), userID)
   269  				resp, err := ing.MetricsMetadata(ctx, nil)
   270  
   271  				if tc.err != nil {
   272  					require.Equal(t, tc.err, err)
   273  				} else {
   274  					require.NoError(t, err)
   275  					require.NotNil(t, resp)
   276  
   277  					metricTracker := map[string]bool{}
   278  					for _, m := range resp.Metadata {
   279  						_, ok := metricTracker[m.GetMetricFamilyName()]
   280  						if !ok {
   281  							metricTracker[m.GetMetricFamilyName()] = true
   282  						}
   283  					}
   284  
   285  					require.Equal(t, tc.expectedMetrics, len(metricTracker))
   286  					require.Equal(t, tc.expectedMetadata, len(resp.Metadata))
   287  				}
   288  			}
   289  		})
   290  	}
   291  }
   292  
   293  func TestIngesterPurgeMetadata(t *testing.T) {
   294  	cfg := defaultIngesterTestConfig(t)
   295  	cfg.MetadataRetainPeriod = 20 * time.Millisecond
   296  	_, ing := newTestStore(t, cfg, defaultClientTestConfig(), defaultLimitsTestConfig(), nil)
   297  	userIDs, _ := pushTestMetadata(t, ing, 10, 3)
   298  
   299  	time.Sleep(40 * time.Millisecond)
   300  	for _, userID := range userIDs {
   301  		ctx := user.InjectOrgID(context.Background(), userID)
   302  		ing.purgeUserMetricsMetadata()
   303  
   304  		resp, err := ing.MetricsMetadata(ctx, nil)
   305  		require.NoError(t, err)
   306  		assert.Equal(t, 0, len(resp.GetMetadata()))
   307  	}
   308  }
   309  
   310  func TestIngesterMetadataMetrics(t *testing.T) {
   311  	reg := prometheus.NewPedanticRegistry()
   312  	cfg := defaultIngesterTestConfig(t)
   313  	cfg.MetadataRetainPeriod = 20 * time.Millisecond
   314  	_, ing := newTestStore(t, cfg, defaultClientTestConfig(), defaultLimitsTestConfig(), reg)
   315  	_, _ = pushTestMetadata(t, ing, 10, 3)
   316  
   317  	pushTestMetadata(t, ing, 10, 3)
   318  	pushTestMetadata(t, ing, 10, 3) // We push the _exact_ same metrics again to ensure idempotency. Metadata is kept as a set so there shouldn't be a change of metrics.
   319  
   320  	metricNames := []string{
   321  		"cortex_ingester_memory_metadata_created_total",
   322  		"cortex_ingester_memory_metadata_removed_total",
   323  		"cortex_ingester_memory_metadata",
   324  	}
   325  
   326  	assert.NoError(t, testutil.GatherAndCompare(reg, strings.NewReader(`
   327  		# HELP cortex_ingester_memory_metadata The current number of metadata in memory.
   328  		# TYPE cortex_ingester_memory_metadata gauge
   329  		cortex_ingester_memory_metadata 90
   330  		# HELP cortex_ingester_memory_metadata_created_total The total number of metadata that were created per user
   331  		# TYPE cortex_ingester_memory_metadata_created_total counter
   332  		cortex_ingester_memory_metadata_created_total{user="1"} 30
   333  		cortex_ingester_memory_metadata_created_total{user="2"} 30
   334  		cortex_ingester_memory_metadata_created_total{user="3"} 30
   335  	`), metricNames...))
   336  
   337  	time.Sleep(40 * time.Millisecond)
   338  	ing.purgeUserMetricsMetadata()
   339  	assert.NoError(t, testutil.GatherAndCompare(reg, strings.NewReader(`
   340  		# HELP cortex_ingester_memory_metadata The current number of metadata in memory.
   341  		# TYPE cortex_ingester_memory_metadata gauge
   342  		cortex_ingester_memory_metadata 0
   343  		# HELP cortex_ingester_memory_metadata_created_total The total number of metadata that were created per user
   344  		# TYPE cortex_ingester_memory_metadata_created_total counter
   345  		cortex_ingester_memory_metadata_created_total{user="1"} 30
   346  		cortex_ingester_memory_metadata_created_total{user="2"} 30
   347  		cortex_ingester_memory_metadata_created_total{user="3"} 30
   348  		# HELP cortex_ingester_memory_metadata_removed_total The total number of metadata that were removed per user.
   349  		# TYPE cortex_ingester_memory_metadata_removed_total counter
   350  		cortex_ingester_memory_metadata_removed_total{user="1"} 30
   351  		cortex_ingester_memory_metadata_removed_total{user="2"} 30
   352  		cortex_ingester_memory_metadata_removed_total{user="3"} 30
   353  	`), metricNames...))
   354  
   355  }
   356  
   357  func TestIngesterSendsOnlySeriesWithData(t *testing.T) {
   358  	_, ing := newDefaultTestStore(t)
   359  
   360  	userIDs, _ := pushTestSamples(t, ing, 10, 1000, 0)
   361  
   362  	// Read samples back via ingester queries.
   363  	for _, userID := range userIDs {
   364  		ctx := user.InjectOrgID(context.Background(), userID)
   365  		_, req, err := runTestQueryTimes(ctx, t, ing, labels.MatchRegexp, model.JobLabel, ".+", model.Latest.Add(-15*time.Second), model.Latest)
   366  		require.NoError(t, err)
   367  
   368  		s := stream{
   369  			ctx: ctx,
   370  		}
   371  		err = ing.QueryStream(req, &s)
   372  		require.NoError(t, err)
   373  
   374  		// Nothing should be selected.
   375  		require.Equal(t, 0, len(s.responses))
   376  	}
   377  
   378  	// Read samples back via chunk store.
   379  	require.NoError(t, services.StopAndAwaitTerminated(context.Background(), ing))
   380  }
   381  
   382  func TestIngesterIdleFlush(t *testing.T) {
   383  	// Create test ingester with short flush cycle
   384  	cfg := defaultIngesterTestConfig(t)
   385  	cfg.FlushCheckPeriod = 20 * time.Millisecond
   386  	cfg.MaxChunkIdle = 100 * time.Millisecond
   387  	cfg.RetainPeriod = 500 * time.Millisecond
   388  	store, ing := newTestStore(t, cfg, defaultClientTestConfig(), defaultLimitsTestConfig(), nil)
   389  
   390  	userIDs, testData := pushTestSamples(t, ing, 4, 100, 0)
   391  
   392  	// wait beyond idle time so samples flush
   393  	time.Sleep(cfg.MaxChunkIdle * 3)
   394  
   395  	store.checkData(t, userIDs, testData)
   396  
   397  	// Check data is still retained by ingester
   398  	for _, userID := range userIDs {
   399  		ctx := user.InjectOrgID(context.Background(), userID)
   400  		res, _, err := runTestQuery(ctx, t, ing, labels.MatchRegexp, model.JobLabel, ".+")
   401  		require.NoError(t, err)
   402  		assert.Equal(t, testData[userID], res)
   403  	}
   404  
   405  	// now wait beyond retain time so chunks are removed from memory
   406  	time.Sleep(cfg.RetainPeriod)
   407  
   408  	// Check data has gone from ingester
   409  	for _, userID := range userIDs {
   410  		ctx := user.InjectOrgID(context.Background(), userID)
   411  		res, _, err := runTestQuery(ctx, t, ing, labels.MatchRegexp, model.JobLabel, ".+")
   412  		require.NoError(t, err)
   413  		assert.Equal(t, model.Matrix{}, res)
   414  	}
   415  
   416  	require.NoError(t, services.StopAndAwaitTerminated(context.Background(), ing))
   417  }
   418  
   419  func TestIngesterSpreadFlush(t *testing.T) {
   420  	// Create test ingester with short flush cycle
   421  	cfg := defaultIngesterTestConfig(t)
   422  	cfg.SpreadFlushes = true
   423  	cfg.FlushCheckPeriod = 20 * time.Millisecond
   424  	store, ing := newTestStore(t, cfg, defaultClientTestConfig(), defaultLimitsTestConfig(), nil)
   425  
   426  	userIDs, testData := pushTestSamples(t, ing, 4, 100, 0)
   427  
   428  	// add another sample with timestamp at the end of the cycle to trigger
   429  	// head closes and get an extra chunk so we will flush the first one
   430  	_, _ = pushTestSamples(t, ing, 4, 1, int(cfg.MaxChunkAge.Seconds()-1)*1000)
   431  
   432  	// wait beyond flush time so first set of samples should be sent to store
   433  	// (you'd think a shorter wait, like period*2, would work, but Go timers are not reliable enough for that)
   434  	time.Sleep(cfg.FlushCheckPeriod * 10)
   435  
   436  	// check the first set of samples has been sent to the store
   437  	store.checkData(t, userIDs, testData)
   438  
   439  	require.NoError(t, services.StopAndAwaitTerminated(context.Background(), ing))
   440  }
   441  
   442  type stream struct {
   443  	grpc.ServerStream
   444  	ctx       context.Context
   445  	responses []*client.QueryStreamResponse
   446  }
   447  
   448  func (s *stream) Context() context.Context {
   449  	return s.ctx
   450  }
   451  
   452  func (s *stream) Send(response *client.QueryStreamResponse) error {
   453  	s.responses = append(s.responses, response)
   454  	return nil
   455  }
   456  
   457  func TestIngesterAppendOutOfOrderAndDuplicate(t *testing.T) {
   458  	_, ing := newDefaultTestStore(t)
   459  	defer services.StopAndAwaitTerminated(context.Background(), ing) //nolint:errcheck
   460  
   461  	m := labelPairs{
   462  		{Name: model.MetricNameLabel, Value: "testmetric"},
   463  	}
   464  	ctx := context.Background()
   465  	err := ing.append(ctx, userID, m, 1, 0, cortexpb.API, nil)
   466  	require.NoError(t, err)
   467  
   468  	// Two times exactly the same sample (noop).
   469  	err = ing.append(ctx, userID, m, 1, 0, cortexpb.API, nil)
   470  	require.NoError(t, err)
   471  
   472  	// Earlier sample than previous one.
   473  	err = ing.append(ctx, userID, m, 0, 0, cortexpb.API, nil)
   474  	require.Contains(t, err.Error(), "sample timestamp out of order")
   475  	errResp, ok := err.(*validationError)
   476  	require.True(t, ok)
   477  	require.Equal(t, errResp.code, 400)
   478  
   479  	// Same timestamp as previous sample, but different value.
   480  	err = ing.append(ctx, userID, m, 1, 1, cortexpb.API, nil)
   481  	require.Contains(t, err.Error(), "sample with repeated timestamp but different value")
   482  	errResp, ok = err.(*validationError)
   483  	require.True(t, ok)
   484  	require.Equal(t, errResp.code, 400)
   485  }
   486  
   487  // Test that blank labels are removed by the ingester
   488  func TestIngesterAppendBlankLabel(t *testing.T) {
   489  	_, ing := newDefaultTestStore(t)
   490  	defer services.StopAndAwaitTerminated(context.Background(), ing) //nolint:errcheck
   491  
   492  	lp := labelPairs{
   493  		{Name: model.MetricNameLabel, Value: "testmetric"},
   494  		{Name: "foo", Value: ""},
   495  		{Name: "bar", Value: ""},
   496  	}
   497  	ctx := user.InjectOrgID(context.Background(), userID)
   498  	err := ing.append(ctx, userID, lp, 1, 0, cortexpb.API, nil)
   499  	require.NoError(t, err)
   500  
   501  	res, _, err := runTestQuery(ctx, t, ing, labels.MatchEqual, labels.MetricName, "testmetric")
   502  	require.NoError(t, err)
   503  
   504  	expected := model.Matrix{
   505  		{
   506  			Metric: model.Metric{labels.MetricName: "testmetric"},
   507  			Values: []model.SamplePair{
   508  				{Timestamp: 1, Value: 0},
   509  			},
   510  		},
   511  	}
   512  
   513  	assert.Equal(t, expected, res)
   514  }
   515  
   516  func TestIngesterUserLimitExceeded(t *testing.T) {
   517  	limits := defaultLimitsTestConfig()
   518  	limits.MaxLocalSeriesPerUser = 1
   519  	limits.MaxLocalMetricsWithMetadataPerUser = 1
   520  
   521  	dir, err := ioutil.TempDir("", "limits")
   522  	require.NoError(t, err)
   523  	defer func() {
   524  		require.NoError(t, os.RemoveAll(dir))
   525  	}()
   526  
   527  	chunksDir := filepath.Join(dir, "chunks")
   528  	blocksDir := filepath.Join(dir, "blocks")
   529  	require.NoError(t, os.Mkdir(chunksDir, os.ModePerm))
   530  	require.NoError(t, os.Mkdir(blocksDir, os.ModePerm))
   531  
   532  	chunksIngesterGenerator := func() *Ingester {
   533  		cfg := defaultIngesterTestConfig(t)
   534  		cfg.WALConfig.WALEnabled = true
   535  		cfg.WALConfig.Recover = true
   536  		cfg.WALConfig.Dir = chunksDir
   537  		cfg.WALConfig.CheckpointDuration = 100 * time.Minute
   538  
   539  		_, ing := newTestStore(t, cfg, defaultClientTestConfig(), limits, nil)
   540  		return ing
   541  	}
   542  
   543  	blocksIngesterGenerator := func() *Ingester {
   544  		ing, err := prepareIngesterWithBlocksStorageAndLimits(t, defaultIngesterTestConfig(t), limits, blocksDir, nil)
   545  		require.NoError(t, err)
   546  		require.NoError(t, services.StartAndAwaitRunning(context.Background(), ing))
   547  		// Wait until it's ACTIVE
   548  		test.Poll(t, time.Second, ring.ACTIVE, func() interface{} {
   549  			return ing.lifecycler.GetState()
   550  		})
   551  
   552  		return ing
   553  	}
   554  
   555  	tests := []string{"chunks", "blocks"}
   556  	for i, ingGenerator := range []func() *Ingester{chunksIngesterGenerator, blocksIngesterGenerator} {
   557  		t.Run(tests[i], func(t *testing.T) {
   558  			ing := ingGenerator()
   559  
   560  			userID := "1"
   561  			// Series
   562  			labels1 := labels.Labels{{Name: labels.MetricName, Value: "testmetric"}, {Name: "foo", Value: "bar"}}
   563  			sample1 := cortexpb.Sample{
   564  				TimestampMs: 0,
   565  				Value:       1,
   566  			}
   567  			sample2 := cortexpb.Sample{
   568  				TimestampMs: 1,
   569  				Value:       2,
   570  			}
   571  			labels3 := labels.Labels{{Name: labels.MetricName, Value: "testmetric"}, {Name: "foo", Value: "biz"}}
   572  			sample3 := cortexpb.Sample{
   573  				TimestampMs: 1,
   574  				Value:       3,
   575  			}
   576  			// Metadata
   577  			metadata1 := &cortexpb.MetricMetadata{MetricFamilyName: "testmetric", Help: "a help for testmetric", Type: cortexpb.COUNTER}
   578  			metadata2 := &cortexpb.MetricMetadata{MetricFamilyName: "testmetric2", Help: "a help for testmetric2", Type: cortexpb.COUNTER}
   579  
   580  			// Append only one series and one metadata first, expect no error.
   581  			ctx := user.InjectOrgID(context.Background(), userID)
   582  			_, err = ing.Push(ctx, cortexpb.ToWriteRequest([]labels.Labels{labels1}, []cortexpb.Sample{sample1}, []*cortexpb.MetricMetadata{metadata1}, cortexpb.API))
   583  			require.NoError(t, err)
   584  
   585  			testLimits := func() {
   586  				// Append to two series, expect series-exceeded error.
   587  				_, err = ing.Push(ctx, cortexpb.ToWriteRequest([]labels.Labels{labels1, labels3}, []cortexpb.Sample{sample2, sample3}, nil, cortexpb.API))
   588  				httpResp, ok := httpgrpc.HTTPResponseFromError(err)
   589  				require.True(t, ok, "returned error is not an httpgrpc response")
   590  				assert.Equal(t, http.StatusBadRequest, int(httpResp.Code))
   591  				assert.Equal(t, wrapWithUser(makeLimitError(perUserSeriesLimit, ing.limiter.FormatError(userID, errMaxSeriesPerUserLimitExceeded)), userID).Error(), string(httpResp.Body))
   592  
   593  				// Append two metadata, expect no error since metadata is a best effort approach.
   594  				_, err = ing.Push(ctx, cortexpb.ToWriteRequest(nil, nil, []*cortexpb.MetricMetadata{metadata1, metadata2}, cortexpb.API))
   595  				require.NoError(t, err)
   596  
   597  				// Read samples back via ingester queries.
   598  				res, _, err := runTestQuery(ctx, t, ing, labels.MatchEqual, model.MetricNameLabel, "testmetric")
   599  				require.NoError(t, err)
   600  
   601  				expected := model.Matrix{
   602  					{
   603  						Metric: cortexpb.FromLabelAdaptersToMetric(cortexpb.FromLabelsToLabelAdapters(labels1)),
   604  						Values: []model.SamplePair{
   605  							{
   606  								Timestamp: model.Time(sample1.TimestampMs),
   607  								Value:     model.SampleValue(sample1.Value),
   608  							},
   609  							{
   610  								Timestamp: model.Time(sample2.TimestampMs),
   611  								Value:     model.SampleValue(sample2.Value),
   612  							},
   613  						},
   614  					},
   615  				}
   616  
   617  				// Verify samples
   618  				require.Equal(t, expected, res)
   619  
   620  				// Verify metadata
   621  				m, err := ing.MetricsMetadata(ctx, nil)
   622  				require.NoError(t, err)
   623  				assert.Equal(t, []*cortexpb.MetricMetadata{metadata1}, m.Metadata)
   624  			}
   625  
   626  			testLimits()
   627  
   628  			// Limits should hold after restart.
   629  			services.StopAndAwaitTerminated(context.Background(), ing) //nolint:errcheck
   630  			ing = ingGenerator()
   631  			defer services.StopAndAwaitTerminated(context.Background(), ing) //nolint:errcheck
   632  
   633  			testLimits()
   634  		})
   635  	}
   636  
   637  }
   638  
   639  func TestIngesterMetricLimitExceeded(t *testing.T) {
   640  	limits := defaultLimitsTestConfig()
   641  	limits.MaxLocalSeriesPerMetric = 1
   642  	limits.MaxLocalMetadataPerMetric = 1
   643  
   644  	dir, err := ioutil.TempDir("", "limits")
   645  	require.NoError(t, err)
   646  	defer func() {
   647  		require.NoError(t, os.RemoveAll(dir))
   648  	}()
   649  
   650  	chunksDir := filepath.Join(dir, "chunks")
   651  	blocksDir := filepath.Join(dir, "blocks")
   652  	require.NoError(t, os.Mkdir(chunksDir, os.ModePerm))
   653  	require.NoError(t, os.Mkdir(blocksDir, os.ModePerm))
   654  
   655  	chunksIngesterGenerator := func() *Ingester {
   656  		cfg := defaultIngesterTestConfig(t)
   657  		cfg.WALConfig.WALEnabled = true
   658  		cfg.WALConfig.Recover = true
   659  		cfg.WALConfig.Dir = chunksDir
   660  		cfg.WALConfig.CheckpointDuration = 100 * time.Minute
   661  
   662  		_, ing := newTestStore(t, cfg, defaultClientTestConfig(), limits, nil)
   663  		return ing
   664  	}
   665  
   666  	blocksIngesterGenerator := func() *Ingester {
   667  		ing, err := prepareIngesterWithBlocksStorageAndLimits(t, defaultIngesterTestConfig(t), limits, blocksDir, nil)
   668  		require.NoError(t, err)
   669  		require.NoError(t, services.StartAndAwaitRunning(context.Background(), ing))
   670  		// Wait until it's ACTIVE
   671  		test.Poll(t, time.Second, ring.ACTIVE, func() interface{} {
   672  			return ing.lifecycler.GetState()
   673  		})
   674  
   675  		return ing
   676  	}
   677  
   678  	tests := []string{"chunks", "blocks"}
   679  	for i, ingGenerator := range []func() *Ingester{chunksIngesterGenerator, blocksIngesterGenerator} {
   680  		t.Run(tests[i], func(t *testing.T) {
   681  			ing := ingGenerator()
   682  
   683  			userID := "1"
   684  			labels1 := labels.Labels{{Name: labels.MetricName, Value: "testmetric"}, {Name: "foo", Value: "bar"}}
   685  			sample1 := cortexpb.Sample{
   686  				TimestampMs: 0,
   687  				Value:       1,
   688  			}
   689  			sample2 := cortexpb.Sample{
   690  				TimestampMs: 1,
   691  				Value:       2,
   692  			}
   693  			labels3 := labels.Labels{{Name: labels.MetricName, Value: "testmetric"}, {Name: "foo", Value: "biz"}}
   694  			sample3 := cortexpb.Sample{
   695  				TimestampMs: 1,
   696  				Value:       3,
   697  			}
   698  
   699  			// Metadata
   700  			metadata1 := &cortexpb.MetricMetadata{MetricFamilyName: "testmetric", Help: "a help for testmetric", Type: cortexpb.COUNTER}
   701  			metadata2 := &cortexpb.MetricMetadata{MetricFamilyName: "testmetric", Help: "a help for testmetric2", Type: cortexpb.COUNTER}
   702  
   703  			// Append only one series and one metadata first, expect no error.
   704  			ctx := user.InjectOrgID(context.Background(), userID)
   705  			_, err = ing.Push(ctx, cortexpb.ToWriteRequest([]labels.Labels{labels1}, []cortexpb.Sample{sample1}, []*cortexpb.MetricMetadata{metadata1}, cortexpb.API))
   706  			require.NoError(t, err)
   707  
   708  			testLimits := func() {
   709  				// Append two series, expect series-exceeded error.
   710  				_, err = ing.Push(ctx, cortexpb.ToWriteRequest([]labels.Labels{labels1, labels3}, []cortexpb.Sample{sample2, sample3}, nil, cortexpb.API))
   711  				httpResp, ok := httpgrpc.HTTPResponseFromError(err)
   712  				require.True(t, ok, "returned error is not an httpgrpc response")
   713  				assert.Equal(t, http.StatusBadRequest, int(httpResp.Code))
   714  				assert.Equal(t, wrapWithUser(makeMetricLimitError(perMetricSeriesLimit, labels3, ing.limiter.FormatError(userID, errMaxSeriesPerMetricLimitExceeded)), userID).Error(), string(httpResp.Body))
   715  
   716  				// Append two metadata for the same metric. Drop the second one, and expect no error since metadata is a best effort approach.
   717  				_, err = ing.Push(ctx, cortexpb.ToWriteRequest(nil, nil, []*cortexpb.MetricMetadata{metadata1, metadata2}, cortexpb.API))
   718  				require.NoError(t, err)
   719  
   720  				// Read samples back via ingester queries.
   721  				res, _, err := runTestQuery(ctx, t, ing, labels.MatchEqual, model.MetricNameLabel, "testmetric")
   722  				require.NoError(t, err)
   723  
   724  				// Verify Series
   725  				expected := model.Matrix{
   726  					{
   727  						Metric: cortexpb.FromLabelAdaptersToMetric(cortexpb.FromLabelsToLabelAdapters(labels1)),
   728  						Values: []model.SamplePair{
   729  							{
   730  								Timestamp: model.Time(sample1.TimestampMs),
   731  								Value:     model.SampleValue(sample1.Value),
   732  							},
   733  							{
   734  								Timestamp: model.Time(sample2.TimestampMs),
   735  								Value:     model.SampleValue(sample2.Value),
   736  							},
   737  						},
   738  					},
   739  				}
   740  
   741  				assert.Equal(t, expected, res)
   742  
   743  				// Verify metadata
   744  				m, err := ing.MetricsMetadata(ctx, nil)
   745  				require.NoError(t, err)
   746  				assert.Equal(t, []*cortexpb.MetricMetadata{metadata1}, m.Metadata)
   747  			}
   748  
   749  			testLimits()
   750  
   751  			// Limits should hold after restart.
   752  			services.StopAndAwaitTerminated(context.Background(), ing) //nolint:errcheck
   753  			ing = ingGenerator()
   754  			defer services.StopAndAwaitTerminated(context.Background(), ing) //nolint:errcheck
   755  
   756  			testLimits()
   757  		})
   758  	}
   759  }
   760  
   761  func TestIngesterValidation(t *testing.T) {
   762  	_, ing := newDefaultTestStore(t)
   763  	defer services.StopAndAwaitTerminated(context.Background(), ing) //nolint:errcheck
   764  	userID := "1"
   765  	ctx := user.InjectOrgID(context.Background(), userID)
   766  	m := labelPairs{{Name: labels.MetricName, Value: "testmetric"}}
   767  
   768  	// As a setup, let's append samples.
   769  	err := ing.append(context.Background(), userID, m, 1, 0, cortexpb.API, nil)
   770  	require.NoError(t, err)
   771  
   772  	for _, tc := range []struct {
   773  		desc    string
   774  		lbls    []labels.Labels
   775  		samples []cortexpb.Sample
   776  		err     error
   777  	}{
   778  		{
   779  			desc: "With multiple append failures, only return the first error.",
   780  			lbls: []labels.Labels{
   781  				{{Name: labels.MetricName, Value: "testmetric"}},
   782  				{{Name: labels.MetricName, Value: "testmetric"}},
   783  			},
   784  			samples: []cortexpb.Sample{
   785  				{TimestampMs: 0, Value: 0}, // earlier timestamp, out of order.
   786  				{TimestampMs: 1, Value: 2}, // same timestamp different value.
   787  			},
   788  			err: httpgrpc.Errorf(http.StatusBadRequest, `user=1: sample timestamp out of order; last timestamp: 0.001, incoming timestamp: 0 for series {__name__="testmetric"}`),
   789  		},
   790  	} {
   791  		t.Run(tc.desc, func(t *testing.T) {
   792  			_, err := ing.Push(ctx, cortexpb.ToWriteRequest(tc.lbls, tc.samples, nil, cortexpb.API))
   793  			require.Equal(t, tc.err, err)
   794  		})
   795  	}
   796  }
   797  
   798  func BenchmarkIngesterSeriesCreationLocking(b *testing.B) {
   799  	for i := 1; i <= 32; i++ {
   800  		b.Run(strconv.Itoa(i), func(b *testing.B) {
   801  			for n := 0; n < b.N; n++ {
   802  				benchmarkIngesterSeriesCreationLocking(b, i)
   803  			}
   804  		})
   805  	}
   806  }
   807  
   808  func benchmarkIngesterSeriesCreationLocking(b *testing.B, parallelism int) {
   809  	_, ing := newDefaultTestStore(b)
   810  	defer services.StopAndAwaitTerminated(context.Background(), ing) //nolint:errcheck
   811  
   812  	var (
   813  		wg     sync.WaitGroup
   814  		series = int(1e4)
   815  		ctx    = context.Background()
   816  	)
   817  	wg.Add(parallelism)
   818  	ctx = user.InjectOrgID(ctx, "1")
   819  	for i := 0; i < parallelism; i++ {
   820  		seriesPerGoroutine := series / parallelism
   821  		go func(from, through int) {
   822  			defer wg.Done()
   823  
   824  			for j := from; j < through; j++ {
   825  				_, err := ing.Push(ctx, &cortexpb.WriteRequest{
   826  					Timeseries: []cortexpb.PreallocTimeseries{
   827  						{
   828  							TimeSeries: &cortexpb.TimeSeries{
   829  								Labels: []cortexpb.LabelAdapter{
   830  									{Name: model.MetricNameLabel, Value: fmt.Sprintf("metric_%d", j)},
   831  								},
   832  								Samples: []cortexpb.Sample{
   833  									{TimestampMs: int64(j), Value: float64(j)},
   834  								},
   835  							},
   836  						},
   837  					},
   838  				})
   839  				require.NoError(b, err)
   840  			}
   841  
   842  		}(i*seriesPerGoroutine, (i+1)*seriesPerGoroutine)
   843  	}
   844  
   845  	wg.Wait()
   846  }
   847  
   848  func BenchmarkIngesterPush(b *testing.B) {
   849  	limits := defaultLimitsTestConfig()
   850  	benchmarkIngesterPush(b, limits, false)
   851  }
   852  
   853  func BenchmarkIngesterPushErrors(b *testing.B) {
   854  	limits := defaultLimitsTestConfig()
   855  	limits.MaxLocalSeriesPerMetric = 1
   856  	benchmarkIngesterPush(b, limits, true)
   857  }
   858  
   859  // Construct a set of realistic-looking samples, all with slightly different label sets
   860  func benchmarkData(nSeries int) (allLabels []labels.Labels, allSamples []cortexpb.Sample) {
   861  	for j := 0; j < nSeries; j++ {
   862  		labels := chunk.BenchmarkLabels.Copy()
   863  		for i := range labels {
   864  			if labels[i].Name == "cpu" {
   865  				labels[i].Value = fmt.Sprintf("cpu%02d", j)
   866  			}
   867  		}
   868  		allLabels = append(allLabels, labels)
   869  		allSamples = append(allSamples, cortexpb.Sample{TimestampMs: 0, Value: float64(j)})
   870  	}
   871  	return
   872  }
   873  
   874  func benchmarkIngesterPush(b *testing.B, limits validation.Limits, errorsExpected bool) {
   875  	cfg := defaultIngesterTestConfig(b)
   876  	clientCfg := defaultClientTestConfig()
   877  
   878  	const (
   879  		series  = 100
   880  		samples = 100
   881  	)
   882  
   883  	allLabels, allSamples := benchmarkData(series)
   884  	ctx := user.InjectOrgID(context.Background(), "1")
   885  
   886  	encodings := []struct {
   887  		name string
   888  		e    promchunk.Encoding
   889  	}{
   890  		{"DoubleDelta", promchunk.DoubleDelta},
   891  		{"Varbit", promchunk.Varbit},
   892  		{"Bigchunk", promchunk.Bigchunk},
   893  	}
   894  
   895  	for _, enc := range encodings {
   896  		b.Run(fmt.Sprintf("encoding=%s", enc.name), func(b *testing.B) {
   897  			b.ResetTimer()
   898  			for iter := 0; iter < b.N; iter++ {
   899  				_, ing := newTestStore(b, cfg, clientCfg, limits, nil)
   900  				// Bump the timestamp on each of our test samples each time round the loop
   901  				for j := 0; j < samples; j++ {
   902  					for i := range allSamples {
   903  						allSamples[i].TimestampMs = int64(j + 1)
   904  					}
   905  					_, err := ing.Push(ctx, cortexpb.ToWriteRequest(allLabels, allSamples, nil, cortexpb.API))
   906  					if !errorsExpected {
   907  						require.NoError(b, err)
   908  					}
   909  				}
   910  				_ = services.StopAndAwaitTerminated(context.Background(), ing)
   911  			}
   912  		})
   913  	}
   914  
   915  }
   916  
   917  func BenchmarkIngester_QueryStream(b *testing.B) {
   918  	cfg := defaultIngesterTestConfig(b)
   919  	clientCfg := defaultClientTestConfig()
   920  	limits := defaultLimitsTestConfig()
   921  	_, ing := newTestStore(b, cfg, clientCfg, limits, nil)
   922  	ctx := user.InjectOrgID(context.Background(), "1")
   923  
   924  	const (
   925  		series  = 2000
   926  		samples = 1000
   927  	)
   928  
   929  	allLabels, allSamples := benchmarkData(series)
   930  
   931  	// Bump the timestamp and set a random value on each of our test samples each time round the loop
   932  	for j := 0; j < samples; j++ {
   933  		for i := range allSamples {
   934  			allSamples[i].TimestampMs = int64(j + 1)
   935  			allSamples[i].Value = rand.Float64()
   936  		}
   937  		_, err := ing.Push(ctx, cortexpb.ToWriteRequest(allLabels, allSamples, nil, cortexpb.API))
   938  		require.NoError(b, err)
   939  	}
   940  
   941  	req := &client.QueryRequest{
   942  		StartTimestampMs: 0,
   943  		EndTimestampMs:   samples + 1,
   944  
   945  		Matchers: []*client.LabelMatcher{{
   946  			Type:  client.EQUAL,
   947  			Name:  model.MetricNameLabel,
   948  			Value: "container_cpu_usage_seconds_total",
   949  		}},
   950  	}
   951  
   952  	mockStream := &mockQueryStreamServer{ctx: ctx}
   953  
   954  	b.ResetTimer()
   955  
   956  	for ix := 0; ix < b.N; ix++ {
   957  		err := ing.QueryStream(req, mockStream)
   958  		require.NoError(b, err)
   959  	}
   960  }
   961  
   962  func TestIngesterActiveSeries(t *testing.T) {
   963  	metricLabelAdapters := []cortexpb.LabelAdapter{{Name: labels.MetricName, Value: "test"}}
   964  	metricLabels := cortexpb.FromLabelAdaptersToLabels(metricLabelAdapters)
   965  	metricNames := []string{
   966  		"cortex_ingester_active_series",
   967  	}
   968  	userID := "test"
   969  
   970  	tests := map[string]struct {
   971  		reqs                []*cortexpb.WriteRequest
   972  		expectedMetrics     string
   973  		disableActiveSeries bool
   974  	}{
   975  		"should succeed on valid series and metadata": {
   976  			reqs: []*cortexpb.WriteRequest{
   977  				cortexpb.ToWriteRequest(
   978  					[]labels.Labels{metricLabels},
   979  					[]cortexpb.Sample{{Value: 1, TimestampMs: 9}},
   980  					nil,
   981  					cortexpb.API),
   982  				cortexpb.ToWriteRequest(
   983  					[]labels.Labels{metricLabels},
   984  					[]cortexpb.Sample{{Value: 2, TimestampMs: 10}},
   985  					nil,
   986  					cortexpb.API),
   987  			},
   988  			expectedMetrics: `
   989  				# HELP cortex_ingester_active_series Number of currently active series per user.
   990  				# TYPE cortex_ingester_active_series gauge
   991  				cortex_ingester_active_series{user="test"} 1
   992  			`,
   993  		},
   994  		"successful push, active series disabled": {
   995  			disableActiveSeries: true,
   996  			reqs: []*cortexpb.WriteRequest{
   997  				cortexpb.ToWriteRequest(
   998  					[]labels.Labels{metricLabels},
   999  					[]cortexpb.Sample{{Value: 1, TimestampMs: 9}},
  1000  					nil,
  1001  					cortexpb.API),
  1002  				cortexpb.ToWriteRequest(
  1003  					[]labels.Labels{metricLabels},
  1004  					[]cortexpb.Sample{{Value: 2, TimestampMs: 10}},
  1005  					nil,
  1006  					cortexpb.API),
  1007  			},
  1008  			expectedMetrics: ``,
  1009  		},
  1010  	}
  1011  
  1012  	for testName, testData := range tests {
  1013  		t.Run(testName, func(t *testing.T) {
  1014  			registry := prometheus.NewRegistry()
  1015  
  1016  			// Create a mocked ingester
  1017  			cfg := defaultIngesterTestConfig(t)
  1018  			cfg.LifecyclerConfig.JoinAfter = 0
  1019  			cfg.ActiveSeriesMetricsEnabled = !testData.disableActiveSeries
  1020  
  1021  			_, i := newTestStore(t,
  1022  				cfg,
  1023  				defaultClientTestConfig(),
  1024  				defaultLimitsTestConfig(), registry)
  1025  
  1026  			defer services.StopAndAwaitTerminated(context.Background(), i) //nolint:errcheck
  1027  
  1028  			ctx := user.InjectOrgID(context.Background(), userID)
  1029  
  1030  			// Wait until the ingester is ACTIVE
  1031  			test.Poll(t, 100*time.Millisecond, ring.ACTIVE, func() interface{} {
  1032  				return i.lifecycler.GetState()
  1033  			})
  1034  
  1035  			// Push timeseries
  1036  			for _, req := range testData.reqs {
  1037  				_, err := i.Push(ctx, req)
  1038  				assert.NoError(t, err)
  1039  			}
  1040  
  1041  			// Update active series for metrics check.
  1042  			if !testData.disableActiveSeries {
  1043  				i.userStatesMtx.Lock()
  1044  				i.userStates.purgeAndUpdateActiveSeries(time.Now().Add(-i.cfg.ActiveSeriesMetricsIdleTimeout))
  1045  				i.userStatesMtx.Unlock()
  1046  			}
  1047  
  1048  			// Check tracked Prometheus metrics
  1049  			err := testutil.GatherAndCompare(registry, strings.NewReader(testData.expectedMetrics), metricNames...)
  1050  			assert.NoError(t, err)
  1051  		})
  1052  	}
  1053  }
  1054  
  1055  func TestGetIgnoreSeriesLimitForMetricNamesMap(t *testing.T) {
  1056  	cfg := Config{}
  1057  
  1058  	require.Nil(t, cfg.getIgnoreSeriesLimitForMetricNamesMap())
  1059  
  1060  	cfg.IgnoreSeriesLimitForMetricNames = ", ,,,"
  1061  	require.Nil(t, cfg.getIgnoreSeriesLimitForMetricNamesMap())
  1062  
  1063  	cfg.IgnoreSeriesLimitForMetricNames = "foo, bar, ,"
  1064  	require.Equal(t, map[string]struct{}{"foo": {}, "bar": {}}, cfg.getIgnoreSeriesLimitForMetricNamesMap())
  1065  }