github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/querier/queryrange/split_by_interval_test.go (about)

     1  package queryrange
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"runtime"
     7  	"strconv"
     8  	"sync"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/stretchr/testify/require"
    13  	"github.com/weaveworks/common/user"
    14  
    15  	"github.com/grafana/loki/pkg/logqlmodel/stats"
    16  	"github.com/grafana/loki/pkg/querier/queryrange/queryrangebase"
    17  
    18  	"github.com/grafana/loki/pkg/loghttp"
    19  	"github.com/grafana/loki/pkg/logproto"
    20  )
    21  
    22  var nilMetrics = NewSplitByMetrics(nil)
    23  
    24  func Test_splitQuery(t *testing.T) {
    25  	buildLokiRequest := func(start, end time.Time) queryrangebase.Request {
    26  		return &LokiRequest{
    27  			Query:     "foo",
    28  			Limit:     1,
    29  			Step:      2,
    30  			StartTs:   start,
    31  			EndTs:     end,
    32  			Direction: logproto.BACKWARD,
    33  			Path:      "/path",
    34  		}
    35  	}
    36  
    37  	buildLokiRequestWithInterval := func(start, end time.Time) queryrangebase.Request {
    38  		return &LokiRequest{
    39  			Query:     "foo",
    40  			Limit:     1,
    41  			Interval:  2,
    42  			StartTs:   start,
    43  			EndTs:     end,
    44  			Direction: logproto.BACKWARD,
    45  			Path:      "/path",
    46  		}
    47  	}
    48  
    49  	buildLokiSeriesRequest := func(start, end time.Time) queryrangebase.Request {
    50  		return &LokiSeriesRequest{
    51  			Match:   []string{"match1"},
    52  			StartTs: start,
    53  			EndTs:   end,
    54  			Path:    "/series",
    55  			Shards:  []string{"shard1"},
    56  		}
    57  	}
    58  
    59  	buildLokiLabelNamesRequest := func(start, end time.Time) queryrangebase.Request {
    60  		return &LokiLabelNamesRequest{
    61  			StartTs: start,
    62  			EndTs:   end,
    63  			Path:    "/labels",
    64  		}
    65  	}
    66  
    67  	type interval struct {
    68  		start, end time.Time
    69  	}
    70  	for requestType, tc := range map[string]struct {
    71  		requestBuilderFunc func(start, end time.Time) queryrangebase.Request
    72  		endTimeInclusive   bool
    73  	}{
    74  		"LokiRequest": {
    75  			buildLokiRequest,
    76  			false,
    77  		},
    78  		"LokiRequestWithInterval": {
    79  			buildLokiRequestWithInterval,
    80  			false,
    81  		},
    82  		"LokiSeriesRequest": {
    83  			buildLokiSeriesRequest,
    84  			true,
    85  		},
    86  		"LokiLabelNamesRequest": {
    87  			buildLokiLabelNamesRequest,
    88  			true,
    89  		},
    90  	} {
    91  		expectedSplitGap := time.Duration(0)
    92  		if tc.endTimeInclusive {
    93  			expectedSplitGap = time.Millisecond
    94  		}
    95  		for name, intervals := range map[string]struct {
    96  			inp      interval
    97  			expected []interval
    98  		}{
    99  			"no_change": {
   100  				inp: interval{
   101  					start: time.Unix(0, 0),
   102  					end:   time.Unix(0, (1 * time.Hour).Nanoseconds()),
   103  				},
   104  				expected: []interval{
   105  					{
   106  						start: time.Unix(0, 0),
   107  						end:   time.Unix(0, (1 * time.Hour).Nanoseconds()),
   108  					},
   109  				},
   110  			},
   111  			"align_start": {
   112  				inp: interval{
   113  					start: time.Unix(0, (5 * time.Minute).Nanoseconds()),
   114  					end:   time.Unix(0, (2 * time.Hour).Nanoseconds()),
   115  				},
   116  				expected: []interval{
   117  					{
   118  						start: time.Unix(0, (5 * time.Minute).Nanoseconds()),
   119  						end:   time.Unix(0, (1 * time.Hour).Nanoseconds()).Add(-expectedSplitGap),
   120  					},
   121  					{
   122  						start: time.Unix(0, (1 * time.Hour).Nanoseconds()),
   123  						end:   time.Unix(0, (2 * time.Hour).Nanoseconds()),
   124  					},
   125  				},
   126  			},
   127  			"align_end": {
   128  				inp: interval{
   129  					start: time.Unix(0, 0),
   130  					end:   time.Unix(0, (115 * time.Minute).Nanoseconds()),
   131  				},
   132  				expected: []interval{
   133  					{
   134  						start: time.Unix(0, 0),
   135  						end:   time.Unix(0, (1 * time.Hour).Nanoseconds()).Add(-expectedSplitGap),
   136  					},
   137  					{
   138  						start: time.Unix(0, (1 * time.Hour).Nanoseconds()),
   139  						end:   time.Unix(0, (115 * time.Minute).Nanoseconds()),
   140  					},
   141  				},
   142  			},
   143  			"align_both": {
   144  				inp: interval{
   145  					start: time.Unix(0, (5 * time.Minute).Nanoseconds()),
   146  					end:   time.Unix(0, (175 * time.Minute).Nanoseconds()),
   147  				},
   148  				expected: []interval{
   149  					{
   150  						start: time.Unix(0, (5 * time.Minute).Nanoseconds()),
   151  						end:   time.Unix(0, (1 * time.Hour).Nanoseconds()).Add(-expectedSplitGap),
   152  					},
   153  					{
   154  						start: time.Unix(0, (1 * time.Hour).Nanoseconds()),
   155  						end:   time.Unix(0, (2 * time.Hour).Nanoseconds()).Add(-expectedSplitGap),
   156  					},
   157  					{
   158  						start: time.Unix(0, (2 * time.Hour).Nanoseconds()),
   159  						end:   time.Unix(0, (175 * time.Minute).Nanoseconds()),
   160  					},
   161  				},
   162  			},
   163  			"no_align": {
   164  				inp: interval{
   165  					start: time.Unix(0, (5 * time.Minute).Nanoseconds()),
   166  					end:   time.Unix(0, (55 * time.Minute).Nanoseconds()),
   167  				},
   168  				expected: []interval{
   169  					{
   170  						start: time.Unix(0, (5 * time.Minute).Nanoseconds()),
   171  						end:   time.Unix(0, (55 * time.Minute).Nanoseconds()),
   172  					},
   173  				},
   174  			},
   175  		} {
   176  			t.Run(fmt.Sprintf("%s - %s", name, requestType), func(t *testing.T) {
   177  				inp := tc.requestBuilderFunc(intervals.inp.start, intervals.inp.end)
   178  				var want []queryrangebase.Request
   179  				for _, interval := range intervals.expected {
   180  					want = append(want, tc.requestBuilderFunc(interval.start, interval.end))
   181  				}
   182  				splits, err := splitByTime(inp, time.Hour)
   183  				require.NoError(t, err)
   184  				require.Equal(t, want, splits)
   185  			})
   186  		}
   187  	}
   188  }
   189  
   190  func Test_splitMetricQuery(t *testing.T) {
   191  	const seconds = 1e3 // 1e3 milliseconds per second.
   192  
   193  	for i, tc := range []struct {
   194  		input    queryrangebase.Request
   195  		expected []queryrangebase.Request
   196  		interval time.Duration
   197  	}{
   198  		// the step is lower than the interval therefore we should split only once.
   199  		{
   200  			input: &LokiRequest{
   201  				StartTs: time.Unix(0, 0),
   202  				EndTs:   time.Unix(0, 60*time.Minute.Nanoseconds()),
   203  				Step:    15 * seconds,
   204  				Query:   `rate({app="foo"}[1m])`,
   205  			},
   206  			expected: []queryrangebase.Request{
   207  				&LokiRequest{
   208  					StartTs: time.Unix(0, 0),
   209  					EndTs:   time.Unix(0, 60*time.Minute.Nanoseconds()),
   210  					Step:    15 * seconds,
   211  					Query:   `rate({app="foo"}[1m])`,
   212  				},
   213  			},
   214  			interval: 24 * time.Hour,
   215  		},
   216  		{
   217  			input: &LokiRequest{
   218  				StartTs: time.Unix(0, 0),
   219  				EndTs:   time.Unix(60*60, 0),
   220  				Step:    15 * seconds,
   221  				Query:   `rate({app="foo"}[1m])`,
   222  			},
   223  			expected: []queryrangebase.Request{
   224  				&LokiRequest{
   225  					StartTs: time.Unix(0, 0),
   226  					EndTs:   time.Unix(60*60, 0),
   227  					Step:    15 * seconds,
   228  					Query:   `rate({app="foo"}[1m])`,
   229  				},
   230  			},
   231  			interval: 3 * time.Hour,
   232  		},
   233  		{
   234  			input: &LokiRequest{
   235  				StartTs: time.Unix(0, 0),
   236  				EndTs:   time.Unix(24*3600, 0),
   237  				Step:    15 * seconds,
   238  				Query:   `rate({app="foo"}[1m])`,
   239  			},
   240  			expected: []queryrangebase.Request{
   241  				&LokiRequest{
   242  					StartTs: time.Unix(0, 0),
   243  					EndTs:   time.Unix(24*3600, 0),
   244  					Step:    15 * seconds,
   245  					Query:   `rate({app="foo"}[1m])`,
   246  				},
   247  			},
   248  			interval: 24 * time.Hour,
   249  		},
   250  		{
   251  			input: &LokiRequest{
   252  				StartTs: time.Unix(0, 0),
   253  				EndTs:   time.Unix(3*3600, 0),
   254  				Step:    15 * seconds,
   255  				Query:   `rate({app="foo"}[1m])`,
   256  			},
   257  			expected: []queryrangebase.Request{
   258  				&LokiRequest{
   259  					StartTs: time.Unix(0, 0),
   260  					EndTs:   time.Unix(3*3600, 0),
   261  					Step:    15 * seconds,
   262  					Query:   `rate({app="foo"}[1m])`,
   263  				},
   264  			},
   265  			interval: 3 * time.Hour,
   266  		},
   267  		{
   268  			input: &LokiRequest{
   269  				StartTs: time.Unix(0, 0),
   270  				EndTs:   time.Unix(2*24*3600, 0),
   271  				Step:    15 * seconds,
   272  				Query:   `rate({app="foo"}[1m])`,
   273  			},
   274  			expected: []queryrangebase.Request{
   275  				&LokiRequest{
   276  					StartTs: time.Unix(0, 0),
   277  					EndTs:   time.Unix((24*3600)-15, 0),
   278  					Step:    15 * seconds,
   279  					Query:   `rate({app="foo"}[1m])`,
   280  				},
   281  				&LokiRequest{
   282  					StartTs: time.Unix((24 * 3600), 0),
   283  					EndTs:   time.Unix((2 * 24 * 3600), 0),
   284  					Step:    15 * seconds,
   285  					Query:   `rate({app="foo"}[1m])`,
   286  				},
   287  			},
   288  			interval: 24 * time.Hour,
   289  		},
   290  		{
   291  			input: &LokiRequest{
   292  				StartTs: time.Unix(0, 0),
   293  				EndTs:   time.Unix(2*3*3600, 0),
   294  				Step:    15 * seconds,
   295  				Query:   `rate({app="foo"}[1m])`,
   296  			},
   297  			expected: []queryrangebase.Request{
   298  				&LokiRequest{
   299  					StartTs: time.Unix(0, 0),
   300  					EndTs:   time.Unix((3*3600)-15, 0),
   301  					Step:    15 * seconds,
   302  					Query:   `rate({app="foo"}[1m])`,
   303  				},
   304  				&LokiRequest{
   305  					StartTs: time.Unix((3 * 3600), 0),
   306  					EndTs:   time.Unix((2 * 3 * 3600), 0),
   307  					Step:    15 * seconds,
   308  					Query:   `rate({app="foo"}[1m])`,
   309  				},
   310  			},
   311  			interval: 3 * time.Hour,
   312  		},
   313  		{
   314  			input: &LokiRequest{
   315  				StartTs: time.Unix(3*3600, 0),
   316  				EndTs:   time.Unix(3*24*3600, 0),
   317  				Step:    15 * seconds,
   318  				Query:   `rate({app="foo"}[1m])`,
   319  			},
   320  			expected: []queryrangebase.Request{
   321  				&LokiRequest{
   322  					StartTs: time.Unix(3*3600, 0),
   323  					EndTs:   time.Unix((24*3600)-15, 0),
   324  					Step:    15 * seconds,
   325  					Query:   `rate({app="foo"}[1m])`,
   326  				},
   327  				&LokiRequest{
   328  					StartTs: time.Unix(24*3600, 0),
   329  					EndTs:   time.Unix((2*24*3600)-15, 0),
   330  					Step:    15 * seconds,
   331  					Query:   `rate({app="foo"}[1m])`,
   332  				},
   333  				&LokiRequest{
   334  					StartTs: time.Unix(2*24*3600, 0),
   335  					EndTs:   time.Unix(3*24*3600, 0),
   336  					Step:    15 * seconds,
   337  					Query:   `rate({app="foo"}[1m])`,
   338  				},
   339  			},
   340  			interval: 24 * time.Hour,
   341  		},
   342  		{
   343  			input: &LokiRequest{
   344  				StartTs: time.Unix(2*3600, 0),
   345  				EndTs:   time.Unix(3*3*3600, 0),
   346  				Step:    15 * seconds,
   347  				Query:   `rate({app="foo"}[1m])`,
   348  			},
   349  			expected: []queryrangebase.Request{
   350  				&LokiRequest{
   351  					StartTs: time.Unix(2*3600, 0),
   352  					EndTs:   time.Unix((3*3600)-15, 0),
   353  					Step:    15 * seconds,
   354  					Query:   `rate({app="foo"}[1m])`,
   355  				},
   356  				&LokiRequest{
   357  					StartTs: time.Unix(3*3600, 0),
   358  					EndTs:   time.Unix((2*3*3600)-15, 0),
   359  					Step:    15 * seconds,
   360  					Query:   `rate({app="foo"}[1m])`,
   361  				},
   362  				&LokiRequest{
   363  					StartTs: time.Unix(2*3*3600, 0),
   364  					EndTs:   time.Unix(3*3*3600, 0),
   365  					Step:    15 * seconds,
   366  					Query:   `rate({app="foo"}[1m])`,
   367  				},
   368  			},
   369  			interval: 3 * time.Hour,
   370  		},
   371  
   372  		// step not a multiple of interval
   373  		// start time already step aligned
   374  		{
   375  			input: &LokiRequest{
   376  				StartTs: time.Unix(2*3600-9, 0), // 2h mod 17s = 9s
   377  				EndTs:   time.Unix(3*3*3600, 0),
   378  				Step:    17 * seconds,
   379  				Query:   `rate({app="foo"}[1m])`,
   380  			},
   381  			expected: []queryrangebase.Request{
   382  				&LokiRequest{
   383  					StartTs: time.Unix(2*3600-9, 0),
   384  					EndTs:   time.Unix((3*3600)-5, 0), // 3h mod 17s = 5s
   385  					Step:    17 * seconds,
   386  					Query:   `rate({app="foo"}[1m])`,
   387  				},
   388  				&LokiRequest{
   389  					StartTs: time.Unix((3*3600)+12, 0),
   390  					EndTs:   time.Unix((2*3*3600)-10, 0), // 6h mod 17s = 10s
   391  					Step:    17 * seconds,
   392  					Query:   `rate({app="foo"}[1m])`,
   393  				},
   394  				&LokiRequest{
   395  					StartTs: time.Unix(2*3*3600+7, 0),
   396  					EndTs:   time.Unix(3*3*3600+2, 0), // 9h mod 17s = 2s
   397  					Step:    17 * seconds,
   398  					Query:   `rate({app="foo"}[1m])`,
   399  				},
   400  			},
   401  			interval: 3 * time.Hour,
   402  		},
   403  		// end time already step aligned
   404  		{
   405  			input: &LokiRequest{
   406  				StartTs: time.Unix(2*3600, 0),
   407  				EndTs:   time.Unix(3*3*3600+2, 0), // 9h mod 17s = 2s
   408  				Step:    17 * seconds,
   409  				Query:   `rate({app="foo"}[1m])`,
   410  			},
   411  			expected: []queryrangebase.Request{
   412  				&LokiRequest{
   413  					StartTs: time.Unix(2*3600-9, 0),   // 2h mod 17s = 9s
   414  					EndTs:   time.Unix((3*3600)-5, 0), // 3h mod 17s = 5s
   415  					Step:    17 * seconds,
   416  					Query:   `rate({app="foo"}[1m])`,
   417  				},
   418  				&LokiRequest{
   419  					StartTs: time.Unix((3*3600)+12, 0),
   420  					EndTs:   time.Unix((2*3*3600)-10, 0), // 6h mod 17s = 10s
   421  					Step:    17 * seconds,
   422  					Query:   `rate({app="foo"}[1m])`,
   423  				},
   424  				&LokiRequest{
   425  					StartTs: time.Unix(2*3*3600+7, 0),
   426  					EndTs:   time.Unix(3*3*3600+2, 0),
   427  					Step:    17 * seconds,
   428  					Query:   `rate({app="foo"}[1m])`,
   429  				},
   430  			},
   431  			interval: 3 * time.Hour,
   432  		},
   433  		// start & end time not aligned with step
   434  		{
   435  			input: &LokiRequest{
   436  				StartTs: time.Unix(2*3600, 0),
   437  				EndTs:   time.Unix(3*3*3600, 0),
   438  				Step:    17 * seconds,
   439  				Query:   `rate({app="foo"}[1m])`,
   440  			},
   441  			expected: []queryrangebase.Request{
   442  				&LokiRequest{
   443  					StartTs: time.Unix(2*3600-9, 0),   // 2h mod 17s = 9s
   444  					EndTs:   time.Unix((3*3600)-5, 0), // 3h mod 17s = 5s
   445  					Step:    17 * seconds,
   446  					Query:   `rate({app="foo"}[1m])`,
   447  				},
   448  				&LokiRequest{
   449  					StartTs: time.Unix((3*3600)+12, 0),
   450  					EndTs:   time.Unix((2*3*3600)-10, 0), // 6h mod 17s = 10s
   451  					Step:    17 * seconds,
   452  					Query:   `rate({app="foo"}[1m])`,
   453  				},
   454  				&LokiRequest{
   455  					StartTs: time.Unix(2*3*3600+7, 0),
   456  					EndTs:   time.Unix(3*3*3600+2, 0), // 9h mod 17s = 2s
   457  					Step:    17 * seconds,
   458  					Query:   `rate({app="foo"}[1m])`,
   459  				},
   460  			},
   461  			interval: 3 * time.Hour,
   462  		},
   463  
   464  		// step larger than split interval
   465  		{
   466  			input: &LokiRequest{
   467  				StartTs: time.Unix(0, 0),
   468  				EndTs:   time.Unix(25*3600, 0),
   469  				Step:    6 * 3600 * seconds,
   470  				Query:   `rate({app="foo"}[1m])`,
   471  			},
   472  			expected: []queryrangebase.Request{
   473  				&LokiRequest{
   474  					StartTs: time.Unix(0, 0),
   475  					EndTs:   time.Unix(6*3600, 0),
   476  					Step:    6 * 3600 * seconds,
   477  					Query:   `rate({app="foo"}[1m])`,
   478  				},
   479  				&LokiRequest{
   480  					StartTs: time.Unix(6*3600, 0),
   481  					EndTs:   time.Unix(12*3600, 0),
   482  					Step:    6 * 3600 * seconds,
   483  					Query:   `rate({app="foo"}[1m])`,
   484  				},
   485  				&LokiRequest{
   486  					StartTs: time.Unix(12*3600, 0),
   487  					EndTs:   time.Unix(18*3600, 0),
   488  					Step:    6 * 3600 * seconds,
   489  					Query:   `rate({app="foo"}[1m])`,
   490  				},
   491  				&LokiRequest{
   492  					StartTs: time.Unix(18*3600, 0),
   493  					EndTs:   time.Unix(24*3600, 0),
   494  					Step:    6 * 3600 * seconds,
   495  					Query:   `rate({app="foo"}[1m])`,
   496  				},
   497  				&LokiRequest{
   498  					StartTs: time.Unix(24*3600, 0),
   499  					EndTs:   time.Unix(30*3600, 0),
   500  					Step:    6 * 3600 * seconds,
   501  					Query:   `rate({app="foo"}[1m])`,
   502  				},
   503  			},
   504  			interval: 15 * time.Minute,
   505  		},
   506  		{
   507  			input: &LokiRequest{
   508  				StartTs: time.Unix(1*3600, 0),
   509  				EndTs:   time.Unix(3*3600, 0),
   510  				Step:    6 * 3600 * seconds,
   511  				Query:   `rate({app="foo"}[1m])`,
   512  			},
   513  			expected: []queryrangebase.Request{
   514  				&LokiRequest{
   515  					StartTs: time.Unix(0, 0),
   516  					EndTs:   time.Unix(6*3600, 0),
   517  					Step:    6 * 3600 * seconds,
   518  					Query:   `rate({app="foo"}[1m])`,
   519  				},
   520  			},
   521  			interval: 15 * time.Minute,
   522  		},
   523  		// reduce split by to 6h instead of 1h
   524  		{
   525  			input: &LokiRequest{
   526  				StartTs: time.Unix(2*3600, 0),
   527  				EndTs:   time.Unix(3*3*3600, 0),
   528  				Step:    15 * seconds,
   529  				Query:   `rate({app="foo"}[6h])`,
   530  			},
   531  			expected: []queryrangebase.Request{
   532  				&LokiRequest{
   533  					StartTs: time.Unix(2*3600, 0),
   534  					EndTs:   time.Unix((6*3600)-15, 0),
   535  					Step:    15 * seconds,
   536  					Query:   `rate({app="foo"}[6h])`,
   537  				},
   538  				&LokiRequest{
   539  					StartTs: time.Unix(6*3600, 0),
   540  					EndTs:   time.Unix(3*3*3600, 0),
   541  					Step:    15 * seconds,
   542  					Query:   `rate({app="foo"}[6h])`,
   543  				},
   544  			},
   545  			interval: 1 * time.Hour,
   546  		},
   547  		// range vector too large we don't want to split it
   548  		{
   549  			input: &LokiRequest{
   550  				StartTs: time.Unix(2*3600, 0),
   551  				EndTs:   time.Unix(3*3*3600, 0),
   552  				Step:    15 * seconds,
   553  				Query:   `rate({app="foo"}[7d])`,
   554  			},
   555  			expected: []queryrangebase.Request{
   556  				&LokiRequest{
   557  					StartTs: time.Unix(2*3600, 0),
   558  					EndTs:   time.Unix(3*3*3600, 0),
   559  					Step:    15 * seconds,
   560  					Query:   `rate({app="foo"}[7d])`,
   561  				},
   562  			},
   563  			interval: 15 * time.Minute,
   564  		},
   565  	} {
   566  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   567  			splits, err := splitMetricByTime(tc.input, tc.interval)
   568  			require.NoError(t, err)
   569  			for i, s := range splits {
   570  				s := s.(*LokiRequest)
   571  				t.Logf(" want: %d start:%s end:%s \n", i, s.StartTs, s.EndTs)
   572  			}
   573  			require.Equal(t, tc.expected, splits)
   574  		})
   575  	}
   576  }
   577  
   578  func Test_splitByInterval_Do(t *testing.T) {
   579  	ctx := user.InjectOrgID(context.Background(), "1")
   580  	next := queryrangebase.HandlerFunc(func(_ context.Context, r queryrangebase.Request) (queryrangebase.Response, error) {
   581  		return &LokiResponse{
   582  			Status:    loghttp.QueryStatusSuccess,
   583  			Direction: r.(*LokiRequest).Direction,
   584  			Limit:     r.(*LokiRequest).Limit,
   585  			Version:   uint32(loghttp.VersionV1),
   586  			Data: LokiData{
   587  				ResultType: loghttp.ResultTypeStream,
   588  				Result: []logproto.Stream{
   589  					{
   590  						Labels: `{foo="bar", level="debug"}`,
   591  						Entries: []logproto.Entry{
   592  							{Timestamp: time.Unix(0, r.(*LokiRequest).StartTs.UnixNano()), Line: fmt.Sprintf("%d", r.(*LokiRequest).StartTs.UnixNano())},
   593  						},
   594  					},
   595  				},
   596  			},
   597  		}, nil
   598  	})
   599  
   600  	l := WithSplitByLimits(fakeLimits{maxQueryParallelism: 1}, time.Hour)
   601  	split := SplitByIntervalMiddleware(
   602  		l,
   603  		LokiCodec,
   604  		splitByTime,
   605  		nilMetrics,
   606  	).Wrap(next)
   607  
   608  	tests := []struct {
   609  		name string
   610  		req  *LokiRequest
   611  		want *LokiResponse
   612  	}{
   613  		{
   614  			"backward",
   615  			&LokiRequest{
   616  				StartTs:   time.Unix(0, 0),
   617  				EndTs:     time.Unix(0, (4 * time.Hour).Nanoseconds()),
   618  				Query:     "",
   619  				Limit:     1000,
   620  				Step:      1,
   621  				Direction: logproto.BACKWARD,
   622  				Path:      "/api/prom/query_range",
   623  			},
   624  			&LokiResponse{
   625  				Status:     loghttp.QueryStatusSuccess,
   626  				Direction:  logproto.BACKWARD,
   627  				Limit:      1000,
   628  				Version:    1,
   629  				Statistics: stats.Result{Summary: stats.Summary{Subqueries: 4}},
   630  				Data: LokiData{
   631  					ResultType: loghttp.ResultTypeStream,
   632  					Result: []logproto.Stream{
   633  						{
   634  							Labels: `{foo="bar", level="debug"}`,
   635  							Entries: []logproto.Entry{
   636  								{Timestamp: time.Unix(0, 3*time.Hour.Nanoseconds()), Line: fmt.Sprintf("%d", 3*time.Hour.Nanoseconds())},
   637  								{Timestamp: time.Unix(0, 2*time.Hour.Nanoseconds()), Line: fmt.Sprintf("%d", 2*time.Hour.Nanoseconds())},
   638  								{Timestamp: time.Unix(0, time.Hour.Nanoseconds()), Line: fmt.Sprintf("%d", time.Hour.Nanoseconds())},
   639  								{Timestamp: time.Unix(0, 0), Line: fmt.Sprintf("%d", 0)},
   640  							},
   641  						},
   642  					},
   643  				},
   644  			},
   645  		},
   646  		{
   647  			"forward",
   648  			&LokiRequest{
   649  				StartTs:   time.Unix(0, 0),
   650  				EndTs:     time.Unix(0, (4 * time.Hour).Nanoseconds()),
   651  				Query:     "",
   652  				Limit:     1000,
   653  				Step:      1,
   654  				Direction: logproto.FORWARD,
   655  				Path:      "/api/prom/query_range",
   656  			},
   657  			&LokiResponse{
   658  				Status:     loghttp.QueryStatusSuccess,
   659  				Direction:  logproto.FORWARD,
   660  				Statistics: stats.Result{Summary: stats.Summary{Subqueries: 4}},
   661  				Limit:      1000,
   662  				Version:    1,
   663  				Data: LokiData{
   664  					ResultType: loghttp.ResultTypeStream,
   665  					Result: []logproto.Stream{
   666  						{
   667  							Labels: `{foo="bar", level="debug"}`,
   668  							Entries: []logproto.Entry{
   669  								{Timestamp: time.Unix(0, 0), Line: fmt.Sprintf("%d", 0)},
   670  								{Timestamp: time.Unix(0, time.Hour.Nanoseconds()), Line: fmt.Sprintf("%d", time.Hour.Nanoseconds())},
   671  								{Timestamp: time.Unix(0, 2*time.Hour.Nanoseconds()), Line: fmt.Sprintf("%d", 2*time.Hour.Nanoseconds())},
   672  								{Timestamp: time.Unix(0, 3*time.Hour.Nanoseconds()), Line: fmt.Sprintf("%d", 3*time.Hour.Nanoseconds())},
   673  							},
   674  						},
   675  					},
   676  				},
   677  			},
   678  		},
   679  		{
   680  			"forward limited",
   681  			&LokiRequest{
   682  				StartTs:   time.Unix(0, 0),
   683  				EndTs:     time.Unix(0, (4 * time.Hour).Nanoseconds()),
   684  				Query:     "",
   685  				Limit:     2,
   686  				Step:      1,
   687  				Direction: logproto.FORWARD,
   688  				Path:      "/api/prom/query_range",
   689  			},
   690  			&LokiResponse{
   691  				Status:     loghttp.QueryStatusSuccess,
   692  				Direction:  logproto.FORWARD,
   693  				Limit:      2,
   694  				Version:    1,
   695  				Statistics: stats.Result{Summary: stats.Summary{Subqueries: 2}},
   696  				Data: LokiData{
   697  					ResultType: loghttp.ResultTypeStream,
   698  					Result: []logproto.Stream{
   699  						{
   700  							Labels: `{foo="bar", level="debug"}`,
   701  							Entries: []logproto.Entry{
   702  								{Timestamp: time.Unix(0, 0), Line: fmt.Sprintf("%d", 0)},
   703  								{Timestamp: time.Unix(0, time.Hour.Nanoseconds()), Line: fmt.Sprintf("%d", time.Hour.Nanoseconds())},
   704  							},
   705  						},
   706  					},
   707  				},
   708  			},
   709  		},
   710  		{
   711  			"backward limited",
   712  			&LokiRequest{
   713  				StartTs:   time.Unix(0, 0),
   714  				EndTs:     time.Unix(0, (4 * time.Hour).Nanoseconds()),
   715  				Query:     "",
   716  				Limit:     2,
   717  				Step:      1,
   718  				Direction: logproto.BACKWARD,
   719  				Path:      "/api/prom/query_range",
   720  			},
   721  			&LokiResponse{
   722  				Status:     loghttp.QueryStatusSuccess,
   723  				Direction:  logproto.BACKWARD,
   724  				Limit:      2,
   725  				Version:    1,
   726  				Statistics: stats.Result{Summary: stats.Summary{Subqueries: 2}},
   727  				Data: LokiData{
   728  					ResultType: loghttp.ResultTypeStream,
   729  					Result: []logproto.Stream{
   730  						{
   731  							Labels: `{foo="bar", level="debug"}`,
   732  							Entries: []logproto.Entry{
   733  								{Timestamp: time.Unix(0, 3*time.Hour.Nanoseconds()), Line: fmt.Sprintf("%d", 3*time.Hour.Nanoseconds())},
   734  								{Timestamp: time.Unix(0, 2*time.Hour.Nanoseconds()), Line: fmt.Sprintf("%d", 2*time.Hour.Nanoseconds())},
   735  							},
   736  						},
   737  					},
   738  				},
   739  			},
   740  		},
   741  	}
   742  
   743  	for _, tt := range tests {
   744  		t.Run(tt.name, func(t *testing.T) {
   745  			res, err := split.Do(ctx, tt.req)
   746  			require.NoError(t, err)
   747  			require.Equal(t, tt.want, res)
   748  		})
   749  	}
   750  }
   751  
   752  func Test_series_splitByInterval_Do(t *testing.T) {
   753  	ctx := user.InjectOrgID(context.Background(), "1")
   754  	next := queryrangebase.HandlerFunc(func(_ context.Context, r queryrangebase.Request) (queryrangebase.Response, error) {
   755  		return &LokiSeriesResponse{
   756  			Status:  "success",
   757  			Version: uint32(loghttp.VersionV1),
   758  			Data: []logproto.SeriesIdentifier{
   759  				{
   760  					Labels: map[string]string{"filename": "/var/hostlog/apport.log", "job": "varlogs"},
   761  				},
   762  				{
   763  					Labels: map[string]string{"filename": "/var/hostlog/test.log", "job": "varlogs"},
   764  				},
   765  				{
   766  					Labels: map[string]string{"filename": "/var/hostlog/test.log", "job": "varlogs"},
   767  				},
   768  			},
   769  		}, nil
   770  	})
   771  
   772  	l := WithSplitByLimits(fakeLimits{maxQueryParallelism: 1}, time.Hour)
   773  	split := SplitByIntervalMiddleware(
   774  		l,
   775  		LokiCodec,
   776  		splitByTime,
   777  		nilMetrics,
   778  	).Wrap(next)
   779  
   780  	tests := []struct {
   781  		name string
   782  		req  *LokiSeriesRequest
   783  		want *LokiSeriesResponse
   784  	}{
   785  		{
   786  			"backward",
   787  			&LokiSeriesRequest{
   788  				StartTs: time.Unix(0, 0),
   789  				EndTs:   time.Unix(0, (4 * time.Hour).Nanoseconds()),
   790  				Match:   []string{`{job="varlogs"}`},
   791  				Path:    "/loki/api/v1/series",
   792  			},
   793  			&LokiSeriesResponse{
   794  				Status:  "success",
   795  				Version: 1,
   796  				Data: []logproto.SeriesIdentifier{
   797  					{
   798  						Labels: map[string]string{"filename": "/var/hostlog/apport.log", "job": "varlogs"},
   799  					},
   800  					{
   801  						Labels: map[string]string{"filename": "/var/hostlog/test.log", "job": "varlogs"},
   802  					},
   803  				},
   804  			},
   805  		},
   806  	}
   807  
   808  	for _, tt := range tests {
   809  		t.Run(tt.name, func(t *testing.T) {
   810  			res, err := split.Do(ctx, tt.req)
   811  			require.NoError(t, err)
   812  			require.Equal(t, tt.want, res)
   813  		})
   814  	}
   815  }
   816  
   817  func Test_ExitEarly(t *testing.T) {
   818  	ctx := user.InjectOrgID(context.Background(), "1")
   819  
   820  	var callCt int
   821  	var mtx sync.Mutex
   822  
   823  	next := queryrangebase.HandlerFunc(func(_ context.Context, r queryrangebase.Request) (queryrangebase.Response, error) {
   824  		time.Sleep(time.Millisecond) // artificial delay to minimize race condition exposure in test
   825  
   826  		mtx.Lock()
   827  		defer mtx.Unlock()
   828  		callCt++
   829  
   830  		return &LokiResponse{
   831  			Status:    loghttp.QueryStatusSuccess,
   832  			Direction: r.(*LokiRequest).Direction,
   833  			Limit:     r.(*LokiRequest).Limit,
   834  			Version:   uint32(loghttp.VersionV1),
   835  			Data: LokiData{
   836  				ResultType: loghttp.ResultTypeStream,
   837  				Result: []logproto.Stream{
   838  					{
   839  						Labels: `{foo="bar", level="debug"}`,
   840  						Entries: []logproto.Entry{
   841  							{
   842  								Timestamp: time.Unix(0, r.(*LokiRequest).StartTs.UnixNano()),
   843  								Line:      fmt.Sprintf("%d", r.(*LokiRequest).StartTs.UnixNano()),
   844  							},
   845  						},
   846  					},
   847  				},
   848  			},
   849  		}, nil
   850  	})
   851  
   852  	l := WithSplitByLimits(fakeLimits{maxQueryParallelism: 1}, time.Hour)
   853  	split := SplitByIntervalMiddleware(
   854  		l,
   855  		LokiCodec,
   856  		splitByTime,
   857  		nilMetrics,
   858  	).Wrap(next)
   859  
   860  	req := &LokiRequest{
   861  		StartTs:   time.Unix(0, 0),
   862  		EndTs:     time.Unix(0, (4 * time.Hour).Nanoseconds()),
   863  		Query:     "",
   864  		Limit:     2,
   865  		Step:      1,
   866  		Direction: logproto.FORWARD,
   867  		Path:      "/api/prom/query_range",
   868  	}
   869  
   870  	expected := &LokiResponse{
   871  		Status:    loghttp.QueryStatusSuccess,
   872  		Direction: logproto.FORWARD,
   873  		Limit:     2,
   874  		Version:   1,
   875  		Statistics: stats.Result{
   876  			Summary: stats.Summary{
   877  				Subqueries: 2,
   878  			},
   879  		},
   880  		Data: LokiData{
   881  			ResultType: loghttp.ResultTypeStream,
   882  			Result: []logproto.Stream{
   883  				{
   884  					Labels: `{foo="bar", level="debug"}`,
   885  					Entries: []logproto.Entry{
   886  						{
   887  							Timestamp: time.Unix(0, 0),
   888  							Line:      fmt.Sprintf("%d", 0),
   889  						},
   890  						{
   891  							Timestamp: time.Unix(0, time.Hour.Nanoseconds()),
   892  							Line:      fmt.Sprintf("%d", time.Hour.Nanoseconds()),
   893  						},
   894  					},
   895  				},
   896  			},
   897  		},
   898  	}
   899  
   900  	res, err := split.Do(ctx, req)
   901  
   902  	require.Equal(t, int(req.Limit), callCt)
   903  	require.NoError(t, err)
   904  	require.Equal(t, expected, res)
   905  }
   906  
   907  func Test_DoesntDeadlock(t *testing.T) {
   908  	n := 10
   909  
   910  	next := queryrangebase.HandlerFunc(func(_ context.Context, r queryrangebase.Request) (queryrangebase.Response, error) {
   911  		return &LokiResponse{
   912  			Status:    loghttp.QueryStatusSuccess,
   913  			Direction: r.(*LokiRequest).Direction,
   914  			Limit:     r.(*LokiRequest).Limit,
   915  			Version:   uint32(loghttp.VersionV1),
   916  			Data: LokiData{
   917  				ResultType: loghttp.ResultTypeStream,
   918  				Result: []logproto.Stream{
   919  					{
   920  						Labels: `{foo="bar", level="debug"}`,
   921  						Entries: []logproto.Entry{
   922  							{
   923  								Timestamp: time.Unix(0, r.(*LokiRequest).StartTs.UnixNano()),
   924  								Line:      fmt.Sprintf("%d", r.(*LokiRequest).StartTs.UnixNano()),
   925  							},
   926  						},
   927  					},
   928  				},
   929  			},
   930  		}, nil
   931  	})
   932  
   933  	l := WithSplitByLimits(fakeLimits{maxQueryParallelism: n}, time.Hour)
   934  	split := SplitByIntervalMiddleware(
   935  		l,
   936  		LokiCodec,
   937  		splitByTime,
   938  		nilMetrics,
   939  	).Wrap(next)
   940  
   941  	// split into n requests w/ n/2 limit, ensuring unused responses are cleaned up properly
   942  	req := &LokiRequest{
   943  		StartTs:   time.Unix(0, 0),
   944  		EndTs:     time.Unix(0, (time.Duration(n) * time.Hour).Nanoseconds()),
   945  		Query:     "",
   946  		Limit:     uint32(n / 2),
   947  		Step:      1,
   948  		Direction: logproto.FORWARD,
   949  		Path:      "/api/prom/query_range",
   950  	}
   951  
   952  	ctx := user.InjectOrgID(context.Background(), "1")
   953  
   954  	startingGoroutines := runtime.NumGoroutine()
   955  
   956  	// goroutines shouldn't blow up across 100 rounds
   957  	for i := 0; i < 100; i++ {
   958  		res, err := split.Do(ctx, req)
   959  		require.NoError(t, err)
   960  		require.Equal(t, 1, len(res.(*LokiResponse).Data.Result))
   961  		require.Equal(t, n/2, len(res.(*LokiResponse).Data.Result[0].Entries))
   962  
   963  	}
   964  	runtime.GC()
   965  	endingGoroutines := runtime.NumGoroutine()
   966  
   967  	// give runtime a bit of slack when catching up -- this isn't an exact science :(
   968  	// Allow for 1% increase in goroutines
   969  	require.LessOrEqual(t, endingGoroutines, startingGoroutines*101/100)
   970  }