github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/ingester/instance_test.go (about)

     1  package ingester
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math/rand"
     7  	"runtime"
     8  	"sort"
     9  	"sync"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/grafana/loki/pkg/logql/syntax"
    14  	"github.com/grafana/loki/pkg/querier/astmapper"
    15  	"github.com/grafana/loki/pkg/storage/chunk"
    16  	"github.com/grafana/loki/pkg/storage/config"
    17  
    18  	"github.com/pkg/errors"
    19  	"github.com/prometheus/common/model"
    20  	"github.com/prometheus/prometheus/model/labels"
    21  	"github.com/stretchr/testify/require"
    22  
    23  	"github.com/grafana/loki/pkg/logproto"
    24  	"github.com/grafana/loki/pkg/logql"
    25  	loki_runtime "github.com/grafana/loki/pkg/runtime"
    26  	"github.com/grafana/loki/pkg/validation"
    27  )
    28  
    29  func defaultConfig() *Config {
    30  	cfg := Config{
    31  		BlockSize:     512,
    32  		ChunkEncoding: "gzip",
    33  		IndexShards:   32,
    34  	}
    35  	if err := cfg.Validate(); err != nil {
    36  		panic(errors.Wrap(err, "error building default test config"))
    37  	}
    38  	return &cfg
    39  }
    40  
    41  func MustParseDayTime(s string) config.DayTime {
    42  	t, err := time.Parse("2006-01-02", s)
    43  	if err != nil {
    44  		panic(err)
    45  	}
    46  	return config.DayTime{Time: model.TimeFromUnix(t.Unix())}
    47  }
    48  
    49  var defaultPeriodConfigs = []config.PeriodConfig{
    50  	{
    51  		From:      MustParseDayTime("1900-01-01"),
    52  		IndexType: config.StorageTypeBigTable,
    53  	},
    54  }
    55  
    56  var NilMetrics = newIngesterMetrics(nil)
    57  
    58  func TestLabelsCollisions(t *testing.T) {
    59  	limits, err := validation.NewOverrides(defaultLimitsTestConfig(), nil)
    60  	require.NoError(t, err)
    61  	limiter := NewLimiter(limits, NilMetrics, &ringCountMock{count: 1}, 1)
    62  
    63  	i, err := newInstance(defaultConfig(), defaultPeriodConfigs, "test", limiter, loki_runtime.DefaultTenantConfigs(), noopWAL{}, NilMetrics, &OnceSwitch{}, nil)
    64  	require.Nil(t, err)
    65  
    66  	// avoid entries from the future.
    67  	tt := time.Now().Add(-5 * time.Minute)
    68  
    69  	// Notice how labels aren't sorted.
    70  	err = i.Push(context.Background(), &logproto.PushRequest{Streams: []logproto.Stream{
    71  		// both label sets have FastFingerprint=e002a3a451262627
    72  		{Labels: "{app=\"l\",uniq0=\"0\",uniq1=\"1\"}", Entries: entries(5, tt.Add(time.Minute))},
    73  		{Labels: "{uniq0=\"1\",app=\"m\",uniq1=\"1\"}", Entries: entries(5, tt)},
    74  
    75  		// e002a3a451262247
    76  		{Labels: "{app=\"l\",uniq0=\"1\",uniq1=\"0\"}", Entries: entries(5, tt.Add(time.Minute))},
    77  		{Labels: "{uniq1=\"0\",app=\"m\",uniq0=\"0\"}", Entries: entries(5, tt)},
    78  
    79  		// e002a2a4512624f4
    80  		{Labels: "{app=\"l\",uniq0=\"0\",uniq1=\"0\"}", Entries: entries(5, tt.Add(time.Minute))},
    81  		{Labels: "{uniq0=\"1\",uniq1=\"0\",app=\"m\"}", Entries: entries(5, tt)},
    82  	}})
    83  	require.NoError(t, err)
    84  }
    85  
    86  func TestConcurrentPushes(t *testing.T) {
    87  	limits, err := validation.NewOverrides(defaultLimitsTestConfig(), nil)
    88  	require.NoError(t, err)
    89  	limiter := NewLimiter(limits, NilMetrics, &ringCountMock{count: 1}, 1)
    90  
    91  	inst, err := newInstance(defaultConfig(), defaultPeriodConfigs, "test", limiter, loki_runtime.DefaultTenantConfigs(), noopWAL{}, NilMetrics, &OnceSwitch{}, nil)
    92  	require.Nil(t, err)
    93  
    94  	const (
    95  		concurrent          = 10
    96  		iterations          = 100
    97  		entriesPerIteration = 100
    98  	)
    99  
   100  	uniqueLabels := map[string]bool{}
   101  	startChannel := make(chan struct{})
   102  
   103  	wg := sync.WaitGroup{}
   104  	for i := 0; i < concurrent; i++ {
   105  		l := makeRandomLabels()
   106  		for uniqueLabels[l.String()] {
   107  			l = makeRandomLabels()
   108  		}
   109  		uniqueLabels[l.String()] = true
   110  
   111  		wg.Add(1)
   112  		go func(labels string) {
   113  			defer wg.Done()
   114  
   115  			<-startChannel
   116  
   117  			tt := time.Now().Add(-5 * time.Minute)
   118  
   119  			for i := 0; i < iterations; i++ {
   120  				err := inst.Push(context.Background(), &logproto.PushRequest{Streams: []logproto.Stream{
   121  					{Labels: labels, Entries: entries(entriesPerIteration, tt)},
   122  				}})
   123  
   124  				require.NoError(t, err)
   125  
   126  				tt = tt.Add(entriesPerIteration * time.Nanosecond)
   127  			}
   128  		}(l.String())
   129  	}
   130  
   131  	time.Sleep(100 * time.Millisecond) // ready
   132  	close(startChannel)                // go!
   133  
   134  	wg.Wait()
   135  	// test passes if no goroutine reports error
   136  }
   137  
   138  func TestSyncPeriod(t *testing.T) {
   139  	limits, err := validation.NewOverrides(defaultLimitsTestConfig(), nil)
   140  	require.NoError(t, err)
   141  	limiter := NewLimiter(limits, NilMetrics, &ringCountMock{count: 1}, 1)
   142  
   143  	const (
   144  		syncPeriod = 1 * time.Minute
   145  		randomStep = time.Second
   146  		entries    = 1000
   147  		minUtil    = 0.20
   148  	)
   149  
   150  	inst, err := newInstance(defaultConfig(), defaultPeriodConfigs, "test", limiter, loki_runtime.DefaultTenantConfigs(), noopWAL{}, NilMetrics, &OnceSwitch{}, nil)
   151  	require.Nil(t, err)
   152  
   153  	lbls := makeRandomLabels()
   154  
   155  	tt := time.Now()
   156  
   157  	var result []logproto.Entry
   158  	for i := 0; i < entries; i++ {
   159  		result = append(result, logproto.Entry{Timestamp: tt, Line: fmt.Sprintf("hello %d", i)})
   160  		tt = tt.Add(time.Duration(1 + rand.Int63n(randomStep.Nanoseconds())))
   161  	}
   162  	pr := &logproto.PushRequest{Streams: []logproto.Stream{{Labels: lbls.String(), Entries: result}}}
   163  	err = inst.Push(context.Background(), pr)
   164  	require.NoError(t, err)
   165  
   166  	// let's verify results
   167  	s, err := inst.getOrCreateStream(pr.Streams[0], recordPool.GetRecord())
   168  	require.NoError(t, err)
   169  
   170  	// make sure each chunk spans max 'sync period' time
   171  	for _, c := range s.chunks {
   172  		start, end := c.chunk.Bounds()
   173  		span := end.Sub(start)
   174  
   175  		const format = "15:04:05.000"
   176  		t.Log(start.Format(format), "--", end.Format(format), span, c.chunk.Utilization())
   177  
   178  		require.True(t, span < syncPeriod || c.chunk.Utilization() >= minUtil)
   179  	}
   180  }
   181  
   182  func setupTestStreams(t *testing.T) (*instance, time.Time, int) {
   183  	t.Helper()
   184  	limits, err := validation.NewOverrides(defaultLimitsTestConfig(), nil)
   185  	require.NoError(t, err)
   186  	limiter := NewLimiter(limits, NilMetrics, &ringCountMock{count: 1}, 1)
   187  	indexShards := 2
   188  
   189  	// just some random values
   190  	cfg := defaultConfig()
   191  	cfg.SyncPeriod = 1 * time.Minute
   192  	cfg.SyncMinUtilization = 0.20
   193  	cfg.IndexShards = indexShards
   194  
   195  	instance, err := newInstance(cfg, defaultPeriodConfigs, "test", limiter, loki_runtime.DefaultTenantConfigs(), noopWAL{}, NilMetrics, &OnceSwitch{}, nil)
   196  	require.Nil(t, err)
   197  
   198  	currentTime := time.Now()
   199  
   200  	testStreams := []logproto.Stream{
   201  		{Labels: "{app=\"test\",job=\"varlogs\"}", Entries: entries(5, currentTime)},
   202  		{Labels: "{app=\"test2\",job=\"varlogs\"}", Entries: entries(5, currentTime.Add(6*time.Nanosecond))},
   203  	}
   204  
   205  	for _, testStream := range testStreams {
   206  		stream, err := instance.getOrCreateStream(testStream, recordPool.GetRecord())
   207  		require.NoError(t, err)
   208  		chunk := newStream(cfg, limiter, "fake", 0, nil, true, NilMetrics).NewChunk()
   209  		for _, entry := range testStream.Entries {
   210  			err = chunk.Append(&entry)
   211  			require.NoError(t, err)
   212  		}
   213  		stream.chunks = append(stream.chunks, chunkDesc{chunk: chunk})
   214  	}
   215  
   216  	return instance, currentTime, indexShards
   217  }
   218  
   219  func Test_LabelQuery(t *testing.T) {
   220  	instance, currentTime, _ := setupTestStreams(t)
   221  	start := &[]time.Time{currentTime.Add(11 * time.Nanosecond)}[0]
   222  	end := &[]time.Time{currentTime.Add(12 * time.Nanosecond)}[0]
   223  	m, err := labels.NewMatcher(labels.MatchEqual, "app", "test")
   224  	require.NoError(t, err)
   225  
   226  	tests := []struct {
   227  		name             string
   228  		req              *logproto.LabelRequest
   229  		expectedResponse logproto.LabelResponse
   230  		matchers         []*labels.Matcher
   231  	}{
   232  		{
   233  			"label names - no matchers",
   234  			&logproto.LabelRequest{
   235  				Start: start,
   236  				End:   end,
   237  			},
   238  			logproto.LabelResponse{
   239  				Values: []string{"app", "job"},
   240  			},
   241  			nil,
   242  		},
   243  		{
   244  			"label names - with matcher",
   245  			&logproto.LabelRequest{
   246  				Start: start,
   247  				End:   end,
   248  			},
   249  			logproto.LabelResponse{
   250  				Values: []string{"app", "job"},
   251  			},
   252  			[]*labels.Matcher{m},
   253  		},
   254  		{
   255  			"label values - no matchers",
   256  			&logproto.LabelRequest{
   257  				Name:   "app",
   258  				Values: true,
   259  				Start:  start,
   260  				End:    end,
   261  			},
   262  			logproto.LabelResponse{
   263  				Values: []string{"test", "test2"},
   264  			},
   265  			nil,
   266  		},
   267  		{
   268  			"label values - with matcher",
   269  			&logproto.LabelRequest{
   270  				Name:   "app",
   271  				Values: true,
   272  				Start:  start,
   273  				End:    end,
   274  			},
   275  			logproto.LabelResponse{
   276  				Values: []string{"test"},
   277  			},
   278  			[]*labels.Matcher{m},
   279  		},
   280  	}
   281  
   282  	for _, tc := range tests {
   283  		t.Run(tc.name, func(t *testing.T) {
   284  			resp, err := instance.Label(context.Background(), tc.req, tc.matchers...)
   285  			require.NoError(t, err)
   286  
   287  			require.Equal(t, tc.expectedResponse.Values, resp.Values)
   288  		})
   289  	}
   290  }
   291  
   292  func Test_SeriesQuery(t *testing.T) {
   293  	instance, currentTime, indexShards := setupTestStreams(t)
   294  
   295  	tests := []struct {
   296  		name             string
   297  		req              *logproto.SeriesRequest
   298  		expectedResponse []logproto.SeriesIdentifier
   299  	}{
   300  		{
   301  			"non overlapping request",
   302  			&logproto.SeriesRequest{
   303  				Start:  currentTime.Add(11 * time.Nanosecond),
   304  				End:    currentTime.Add(12 * time.Nanosecond),
   305  				Groups: []string{`{job="varlogs"}`},
   306  			},
   307  			[]logproto.SeriesIdentifier{},
   308  		},
   309  		{
   310  			"overlapping request",
   311  			&logproto.SeriesRequest{
   312  				Start:  currentTime.Add(1 * time.Nanosecond),
   313  				End:    currentTime.Add(7 * time.Nanosecond),
   314  				Groups: []string{`{job="varlogs"}`},
   315  			},
   316  			[]logproto.SeriesIdentifier{
   317  				{Labels: map[string]string{"app": "test", "job": "varlogs"}},
   318  				{Labels: map[string]string{"app": "test2", "job": "varlogs"}},
   319  			},
   320  		},
   321  		{
   322  			"overlapping request with shard param",
   323  			&logproto.SeriesRequest{
   324  				Start:  currentTime.Add(1 * time.Nanosecond),
   325  				End:    currentTime.Add(7 * time.Nanosecond),
   326  				Groups: []string{`{job="varlogs"}`},
   327  				Shards: []string{astmapper.ShardAnnotation{
   328  					Shard: 1,
   329  					Of:    indexShards,
   330  				}.String()},
   331  			},
   332  			[]logproto.SeriesIdentifier{
   333  				// Separated by shard number
   334  				{Labels: map[string]string{"app": "test2", "job": "varlogs"}},
   335  			},
   336  		},
   337  		{
   338  			"request end time overlaps stream start time",
   339  			&logproto.SeriesRequest{
   340  				Start:  currentTime.Add(1 * time.Nanosecond),
   341  				End:    currentTime.Add(6 * time.Nanosecond),
   342  				Groups: []string{`{job="varlogs"}`},
   343  			},
   344  			[]logproto.SeriesIdentifier{
   345  				{Labels: map[string]string{"app": "test", "job": "varlogs"}},
   346  			},
   347  		},
   348  		{
   349  			"request start time overlaps stream end time",
   350  			&logproto.SeriesRequest{
   351  				Start:  currentTime.Add(10 * time.Nanosecond),
   352  				End:    currentTime.Add(11 * time.Nanosecond),
   353  				Groups: []string{`{job="varlogs"}`},
   354  			},
   355  			[]logproto.SeriesIdentifier{
   356  				{Labels: map[string]string{"app": "test2", "job": "varlogs"}},
   357  			},
   358  		},
   359  	}
   360  
   361  	for _, tc := range tests {
   362  		t.Run(tc.name, func(t *testing.T) {
   363  			resp, err := instance.Series(context.Background(), tc.req)
   364  			require.NoError(t, err)
   365  
   366  			sort.Slice(resp.Series, func(i, j int) bool {
   367  				return resp.Series[i].String() < resp.Series[j].String()
   368  			})
   369  			sort.Slice(tc.expectedResponse, func(i, j int) bool {
   370  				return tc.expectedResponse[i].String() < tc.expectedResponse[j].String()
   371  			})
   372  			require.Equal(t, tc.expectedResponse, resp.Series)
   373  		})
   374  	}
   375  }
   376  
   377  func entries(n int, t time.Time) []logproto.Entry {
   378  	result := make([]logproto.Entry, 0, n)
   379  	for i := 0; i < n; i++ {
   380  		result = append(result, logproto.Entry{Timestamp: t, Line: fmt.Sprintf("hello %d", i)})
   381  		t = t.Add(time.Nanosecond)
   382  	}
   383  	return result
   384  }
   385  
   386  var labelNames = []string{"app", "instance", "namespace", "user", "cluster"}
   387  
   388  func makeRandomLabels() labels.Labels {
   389  	ls := labels.NewBuilder(nil)
   390  	for _, ln := range labelNames {
   391  		ls.Set(ln, fmt.Sprintf("%d", rand.Int31()))
   392  	}
   393  	return ls.Labels()
   394  }
   395  
   396  func Benchmark_PushInstance(b *testing.B) {
   397  	limits, err := validation.NewOverrides(defaultLimitsTestConfig(), nil)
   398  	require.NoError(b, err)
   399  	limiter := NewLimiter(limits, NilMetrics, &ringCountMock{count: 1}, 1)
   400  
   401  	i, _ := newInstance(&Config{IndexShards: 1}, defaultPeriodConfigs, "test", limiter, loki_runtime.DefaultTenantConfigs(), noopWAL{}, NilMetrics, &OnceSwitch{}, nil)
   402  	ctx := context.Background()
   403  
   404  	for n := 0; n < b.N; n++ {
   405  		_ = i.Push(ctx, &logproto.PushRequest{
   406  			Streams: []logproto.Stream{
   407  				{
   408  					Labels: `{cpu="10",endpoint="https",instance="10.253.57.87:9100",job="node-exporter",mode="idle",namespace="observability",pod="node-exporter-l454v",service="node-exporter"}`,
   409  					Entries: []logproto.Entry{
   410  						{Timestamp: time.Now(), Line: "1"},
   411  						{Timestamp: time.Now(), Line: "2"},
   412  						{Timestamp: time.Now(), Line: "3"},
   413  					},
   414  				},
   415  				{
   416  					Labels: `{cpu="35",endpoint="https",instance="10.253.57.87:9100",job="node-exporter",mode="idle",namespace="observability",pod="node-exporter-l454v",service="node-exporter"}`,
   417  					Entries: []logproto.Entry{
   418  						{Timestamp: time.Now(), Line: "1"},
   419  						{Timestamp: time.Now(), Line: "2"},
   420  						{Timestamp: time.Now(), Line: "3"},
   421  					},
   422  				},
   423  				{
   424  					Labels: `{cpu="89",endpoint="https",instance="10.253.57.87:9100",job="node-exporter",mode="idle",namespace="observability",pod="node-exporter-l454v",service="node-exporter"}`,
   425  					Entries: []logproto.Entry{
   426  						{Timestamp: time.Now(), Line: "1"},
   427  						{Timestamp: time.Now(), Line: "2"},
   428  						{Timestamp: time.Now(), Line: "3"},
   429  					},
   430  				},
   431  			},
   432  		})
   433  	}
   434  }
   435  
   436  func Benchmark_instance_addNewTailer(b *testing.B) {
   437  	l := defaultLimitsTestConfig()
   438  	l.MaxLocalStreamsPerUser = 100000
   439  	limits, err := validation.NewOverrides(l, nil)
   440  	require.NoError(b, err)
   441  	limiter := NewLimiter(limits, NilMetrics, &ringCountMock{count: 1}, 1)
   442  
   443  	ctx := context.Background()
   444  
   445  	inst, _ := newInstance(&Config{}, defaultPeriodConfigs, "test", limiter, loki_runtime.DefaultTenantConfigs(), noopWAL{}, NilMetrics, &OnceSwitch{}, nil)
   446  	t, err := newTailer("foo", `{namespace="foo",pod="bar",instance=~"10.*"}`, nil, 10)
   447  	require.NoError(b, err)
   448  	for i := 0; i < 10000; i++ {
   449  		require.NoError(b, inst.Push(ctx, &logproto.PushRequest{
   450  			Streams: []logproto.Stream{},
   451  		}))
   452  	}
   453  	b.Run("addNewTailer", func(b *testing.B) {
   454  		for n := 0; n < b.N; n++ {
   455  			_ = inst.addNewTailer(context.Background(), t)
   456  		}
   457  	})
   458  	lbs := makeRandomLabels()
   459  	b.Run("addTailersToNewStream", func(b *testing.B) {
   460  		for n := 0; n < b.N; n++ {
   461  			inst.addTailersToNewStream(newStream(nil, limiter, "fake", 0, lbs, true, NilMetrics))
   462  		}
   463  	})
   464  }
   465  
   466  func Benchmark_OnceSwitch(b *testing.B) {
   467  	threads := runtime.GOMAXPROCS(0)
   468  
   469  	// limit threads
   470  	if threads > 4 {
   471  		threads = 4
   472  	}
   473  
   474  	for n := 0; n < b.N; n++ {
   475  		x := &OnceSwitch{}
   476  		var wg sync.WaitGroup
   477  		for i := 0; i < threads; i++ {
   478  			wg.Add(1)
   479  			go func() {
   480  				for i := 0; i < 1000; i++ {
   481  					x.Trigger()
   482  				}
   483  				wg.Done()
   484  			}()
   485  		}
   486  		wg.Wait()
   487  	}
   488  }
   489  
   490  func Test_Iterator(t *testing.T) {
   491  	instance := defaultInstance(t)
   492  
   493  	it, err := instance.Query(context.TODO(),
   494  		logql.SelectLogParams{
   495  			QueryRequest: &logproto.QueryRequest{
   496  				Selector:  `{job="3"} | logfmt`,
   497  				Limit:     uint32(2),
   498  				Start:     time.Unix(0, 0),
   499  				End:       time.Unix(0, 100000000),
   500  				Direction: logproto.BACKWARD,
   501  			},
   502  		},
   503  	)
   504  	require.NoError(t, err)
   505  
   506  	// assert the order is preserved.
   507  	var res *logproto.QueryResponse
   508  	require.NoError(t,
   509  		sendBatches(context.TODO(), it,
   510  			fakeQueryServer(
   511  				func(qr *logproto.QueryResponse) error {
   512  					res = qr
   513  					return nil
   514  				},
   515  			),
   516  			int32(2)),
   517  	)
   518  	require.Equal(t, 2, len(res.Streams))
   519  	// each entry translated into a unique stream
   520  	require.Equal(t, 1, len(res.Streams[0].Entries))
   521  	require.Equal(t, 1, len(res.Streams[1].Entries))
   522  	// sort by entries we expect 9 and 8 this is because readbatch uses a map to build the response.
   523  	// map have no order guarantee
   524  	sort.Slice(res.Streams, func(i, j int) bool {
   525  		return res.Streams[i].Entries[0].Timestamp.UnixNano() > res.Streams[j].Entries[0].Timestamp.UnixNano()
   526  	})
   527  	require.Equal(t, int64(9), res.Streams[0].Entries[0].Timestamp.UnixNano())
   528  	require.Equal(t, int64(8), res.Streams[1].Entries[0].Timestamp.UnixNano())
   529  }
   530  
   531  type testFilter struct{}
   532  
   533  func (t *testFilter) ForRequest(ctx context.Context) chunk.Filterer {
   534  	return t
   535  }
   536  
   537  func (t *testFilter) ShouldFilter(lbs labels.Labels) bool {
   538  	return lbs.Get("log_stream") == "dispatcher"
   539  }
   540  
   541  func Test_ChunkFilter(t *testing.T) {
   542  	instance := defaultInstance(t)
   543  	instance.chunkFilter = &testFilter{}
   544  
   545  	it, err := instance.Query(context.TODO(),
   546  		logql.SelectLogParams{
   547  			QueryRequest: &logproto.QueryRequest{
   548  				Selector:  `{job="3"}`,
   549  				Limit:     uint32(2),
   550  				Start:     time.Unix(0, 0),
   551  				End:       time.Unix(0, 100000000),
   552  				Direction: logproto.BACKWARD,
   553  			},
   554  		},
   555  	)
   556  	require.NoError(t, err)
   557  	defer it.Close()
   558  
   559  	for it.Next() {
   560  		require.NoError(t, it.Error())
   561  		lbs, err := syntax.ParseLabels(it.Labels())
   562  		require.NoError(t, err)
   563  		require.NotEqual(t, "dispatcher", lbs.Get("log_stream"))
   564  	}
   565  }
   566  
   567  func Test_QueryWithDelete(t *testing.T) {
   568  	instance := defaultInstance(t)
   569  
   570  	it, err := instance.Query(context.TODO(),
   571  		logql.SelectLogParams{
   572  			QueryRequest: &logproto.QueryRequest{
   573  				Selector:  `{job="3"}`,
   574  				Limit:     uint32(2),
   575  				Start:     time.Unix(0, 0),
   576  				End:       time.Unix(0, 100000000),
   577  				Direction: logproto.BACKWARD,
   578  				Deletes: []*logproto.Delete{
   579  					{
   580  						Selector: `{log_stream="worker"}`,
   581  						Start:    0,
   582  						End:      10,
   583  					},
   584  					{
   585  						Selector: `{log_stream="dispatcher"}`,
   586  						Start:    0,
   587  						End:      5,
   588  					},
   589  					{
   590  						Selector: `{log_stream="dispatcher"} |= "9"`,
   591  						Start:    0,
   592  						End:      10,
   593  					},
   594  				},
   595  			},
   596  		},
   597  	)
   598  	require.NoError(t, err)
   599  	defer it.Close()
   600  
   601  	var logs []string
   602  	for it.Next() {
   603  		logs = append(logs, it.Entry().Line)
   604  	}
   605  
   606  	require.Equal(t, logs, []string{`msg="dispatcher_7"`})
   607  }
   608  
   609  func Test_QuerySampleWithDelete(t *testing.T) {
   610  	instance := defaultInstance(t)
   611  
   612  	it, err := instance.QuerySample(context.TODO(),
   613  		logql.SelectSampleParams{
   614  			SampleQueryRequest: &logproto.SampleQueryRequest{
   615  				Selector: `count_over_time({job="3"}[5m])`,
   616  				Start:    time.Unix(0, 0),
   617  				End:      time.Unix(0, 100000000),
   618  				Deletes: []*logproto.Delete{
   619  					{
   620  						Selector: `{log_stream="worker"}`,
   621  						Start:    0,
   622  						End:      10,
   623  					},
   624  					{
   625  						Selector: `{log_stream="dispatcher"}`,
   626  						Start:    0,
   627  						End:      5,
   628  					},
   629  					{
   630  						Selector: `{log_stream="dispatcher"} |= "9"`,
   631  						Start:    0,
   632  						End:      10,
   633  					},
   634  				},
   635  			},
   636  		},
   637  	)
   638  	require.NoError(t, err)
   639  	defer it.Close()
   640  
   641  	var samples []float64
   642  	for it.Next() {
   643  		samples = append(samples, it.Sample().Value)
   644  	}
   645  
   646  	require.Equal(t, samples, []float64{1.})
   647  }
   648  
   649  func defaultInstance(t *testing.T) *instance {
   650  	ingesterConfig := defaultIngesterTestConfig(t)
   651  	defaultLimits := defaultLimitsTestConfig()
   652  	overrides, err := validation.NewOverrides(defaultLimits, nil)
   653  	require.NoError(t, err)
   654  	instance, err := newInstance(
   655  		&ingesterConfig,
   656  		defaultPeriodConfigs,
   657  		"fake",
   658  		NewLimiter(overrides, NilMetrics, &ringCountMock{count: 1}, 1),
   659  		loki_runtime.DefaultTenantConfigs(),
   660  		noopWAL{},
   661  		NilMetrics,
   662  		nil,
   663  		nil,
   664  	)
   665  	require.Nil(t, err)
   666  	insertData(t, instance)
   667  
   668  	return instance
   669  }
   670  
   671  func insertData(t *testing.T, instance *instance) {
   672  	for i := 0; i < 10; i++ {
   673  		// nolint
   674  		stream := "dispatcher"
   675  		if i%2 == 0 {
   676  			stream = "worker"
   677  		}
   678  		require.NoError(t,
   679  			instance.Push(context.TODO(), &logproto.PushRequest{
   680  				Streams: []logproto.Stream{
   681  					{
   682  						Labels: fmt.Sprintf(`{host="agent", log_stream="%s",job="3"}`, stream),
   683  						Entries: []logproto.Entry{
   684  							{Timestamp: time.Unix(0, int64(i)), Line: fmt.Sprintf(`msg="%s_%d"`, stream, i)},
   685  						},
   686  					},
   687  				},
   688  			}),
   689  		)
   690  	}
   691  }
   692  
   693  type fakeQueryServer func(*logproto.QueryResponse) error
   694  
   695  func (f fakeQueryServer) Send(res *logproto.QueryResponse) error {
   696  	return f(res)
   697  }
   698  func (f fakeQueryServer) Context() context.Context { return context.TODO() }