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

     1  package queryrange
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  	"sync"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/prometheus/prometheus/model/labels"
    12  	"github.com/prometheus/prometheus/promql"
    13  	"github.com/stretchr/testify/require"
    14  	"github.com/weaveworks/common/user"
    15  	"go.uber.org/atomic"
    16  
    17  	"github.com/grafana/loki/pkg/logproto"
    18  	"github.com/grafana/loki/pkg/logqlmodel"
    19  	"github.com/grafana/loki/pkg/querier/queryrange/queryrangebase"
    20  	"github.com/grafana/loki/pkg/storage/config"
    21  	util_log "github.com/grafana/loki/pkg/util/log"
    22  	"github.com/grafana/loki/pkg/util/marshal"
    23  )
    24  
    25  func TestLimits(t *testing.T) {
    26  	l := fakeLimits{
    27  		splits: map[string]time.Duration{"a": time.Minute},
    28  	}
    29  
    30  	wrapped := WithSplitByLimits(l, time.Hour)
    31  
    32  	// Test default
    33  	require.Equal(t, wrapped.QuerySplitDuration("b"), time.Hour)
    34  	// Ensure we override the underlying implementation
    35  	require.Equal(t, wrapped.QuerySplitDuration("a"), time.Hour)
    36  
    37  	r := &LokiRequest{
    38  		Query:   "qry",
    39  		StartTs: time.Now(),
    40  		Step:    int64(time.Minute / time.Millisecond),
    41  	}
    42  
    43  	require.Equal(
    44  		t,
    45  		fmt.Sprintf("%s:%s:%d:%d:%d", "a", r.GetQuery(), r.GetStep(), r.GetStart()/int64(time.Hour/time.Millisecond), int64(time.Hour)),
    46  		cacheKeyLimits{wrapped}.GenerateCacheKey("a", r),
    47  	)
    48  }
    49  
    50  func Test_seriesLimiter(t *testing.T) {
    51  	cfg := testConfig
    52  	cfg.CacheResults = false
    53  	// split in 7 with 2 in // max.
    54  	l := WithSplitByLimits(fakeLimits{maxSeries: 1, maxQueryParallelism: 2}, time.Hour)
    55  	tpw, stopper, err := NewTripperware(cfg, util_log.Logger, l, config.SchemaConfig{}, nil, nil)
    56  	if stopper != nil {
    57  		defer stopper.Stop()
    58  	}
    59  	require.NoError(t, err)
    60  
    61  	lreq := &LokiRequest{
    62  		Query:     `rate({app="foo"} |= "foo"[1m])`,
    63  		Limit:     1000,
    64  		Step:      30000, // 30sec
    65  		StartTs:   testTime.Add(-6 * time.Hour),
    66  		EndTs:     testTime,
    67  		Direction: logproto.FORWARD,
    68  		Path:      "/query_range",
    69  	}
    70  
    71  	ctx := user.InjectOrgID(context.Background(), "1")
    72  	req, err := LokiCodec.EncodeRequest(ctx, lreq)
    73  	require.NoError(t, err)
    74  
    75  	req = req.WithContext(ctx)
    76  	err = user.InjectOrgIDIntoHTTPRequest(ctx, req)
    77  	require.NoError(t, err)
    78  
    79  	rt, err := newfakeRoundTripper()
    80  	require.NoError(t, err)
    81  	defer rt.Close()
    82  
    83  	count, h := promqlResult(matrix)
    84  	rt.setHandler(h)
    85  
    86  	_, err = tpw(rt).RoundTrip(req)
    87  	require.NoError(t, err)
    88  	require.Equal(t, 7, *count)
    89  
    90  	// 2 series should not be allowed.
    91  	c := new(int)
    92  	m := &sync.Mutex{}
    93  	h = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
    94  		m.Lock()
    95  		defer m.Unlock()
    96  		defer func() {
    97  			*c++
    98  		}()
    99  		// first time returns  a single series
   100  		if *c == 0 {
   101  			if err := marshal.WriteQueryResponseJSON(logqlmodel.Result{Data: matrix}, rw); err != nil {
   102  				panic(err)
   103  			}
   104  			return
   105  		}
   106  		// second time returns a different series.
   107  		if err := marshal.WriteQueryResponseJSON(logqlmodel.Result{
   108  			Data: promql.Matrix{
   109  				{
   110  					Points: []promql.Point{
   111  						{
   112  							T: toMs(testTime.Add(-4 * time.Hour)),
   113  							V: 0.013333333333333334,
   114  						},
   115  					},
   116  					Metric: []labels.Label{
   117  						{
   118  							Name:  "filename",
   119  							Value: `/var/hostlog/apport.log`,
   120  						},
   121  						{
   122  							Name:  "job",
   123  							Value: "anotherjob",
   124  						},
   125  					},
   126  				},
   127  			},
   128  		}, rw); err != nil {
   129  			panic(err)
   130  		}
   131  	})
   132  	rt.setHandler(h)
   133  
   134  	_, err = tpw(rt).RoundTrip(req)
   135  	require.Error(t, err)
   136  	require.LessOrEqual(t, *c, 4)
   137  }
   138  
   139  func Test_MaxQueryParallelism(t *testing.T) {
   140  	maxQueryParallelism := 2
   141  	f, err := newfakeRoundTripper()
   142  	require.Nil(t, err)
   143  	var count atomic.Int32
   144  	var max atomic.Int32
   145  	f.setHandler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
   146  		cur := count.Inc()
   147  		if cur > max.Load() {
   148  			max.Store(cur)
   149  		}
   150  		defer count.Dec()
   151  		// simulate some work
   152  		time.Sleep(20 * time.Millisecond)
   153  	}))
   154  	ctx := user.InjectOrgID(context.Background(), "foo")
   155  
   156  	r, err := http.NewRequestWithContext(ctx, "GET", "/query_range", http.NoBody)
   157  	require.Nil(t, err)
   158  
   159  	_, _ = NewLimitedRoundTripper(f, LokiCodec, fakeLimits{maxQueryParallelism: maxQueryParallelism},
   160  		queryrangebase.MiddlewareFunc(func(next queryrangebase.Handler) queryrangebase.Handler {
   161  			return queryrangebase.HandlerFunc(func(c context.Context, r queryrangebase.Request) (queryrangebase.Response, error) {
   162  				var wg sync.WaitGroup
   163  				for i := 0; i < 10; i++ {
   164  					wg.Add(1)
   165  					go func() {
   166  						defer wg.Done()
   167  						_, _ = next.Do(c, &LokiRequest{})
   168  					}()
   169  				}
   170  				wg.Wait()
   171  				return nil, nil
   172  			})
   173  		}),
   174  	).RoundTrip(r)
   175  	maxFound := int(max.Load())
   176  	require.LessOrEqual(t, maxFound, maxQueryParallelism, "max query parallelism: ", maxFound, " went over the configured one:", maxQueryParallelism)
   177  }
   178  
   179  func Test_MaxQueryParallelismLateScheduling(t *testing.T) {
   180  	maxQueryParallelism := 2
   181  	f, err := newfakeRoundTripper()
   182  	require.Nil(t, err)
   183  
   184  	f.setHandler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
   185  		// simulate some work
   186  		time.Sleep(20 * time.Millisecond)
   187  	}))
   188  	ctx := user.InjectOrgID(context.Background(), "foo")
   189  
   190  	r, err := http.NewRequestWithContext(ctx, "GET", "/query_range", http.NoBody)
   191  	require.Nil(t, err)
   192  
   193  	_, _ = NewLimitedRoundTripper(f, LokiCodec, fakeLimits{maxQueryParallelism: maxQueryParallelism},
   194  		queryrangebase.MiddlewareFunc(func(next queryrangebase.Handler) queryrangebase.Handler {
   195  			return queryrangebase.HandlerFunc(func(c context.Context, r queryrangebase.Request) (queryrangebase.Response, error) {
   196  				for i := 0; i < 10; i++ {
   197  					go func() {
   198  						_, _ = next.Do(c, &LokiRequest{})
   199  					}()
   200  				}
   201  				return nil, nil
   202  			})
   203  		}),
   204  	).RoundTrip(r)
   205  }
   206  
   207  func Test_MaxQueryParallelismDisable(t *testing.T) {
   208  	maxQueryParallelism := 0
   209  	f, err := newfakeRoundTripper()
   210  	require.Nil(t, err)
   211  
   212  	f.setHandler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
   213  		// simulate some work
   214  		time.Sleep(20 * time.Millisecond)
   215  	}))
   216  	ctx := user.InjectOrgID(context.Background(), "foo")
   217  
   218  	r, err := http.NewRequestWithContext(ctx, "GET", "/query_range", http.NoBody)
   219  	require.Nil(t, err)
   220  
   221  	_, err = NewLimitedRoundTripper(f, LokiCodec, fakeLimits{maxQueryParallelism: maxQueryParallelism},
   222  		queryrangebase.MiddlewareFunc(func(next queryrangebase.Handler) queryrangebase.Handler {
   223  			return queryrangebase.HandlerFunc(func(c context.Context, r queryrangebase.Request) (queryrangebase.Response, error) {
   224  				for i := 0; i < 10; i++ {
   225  					go func() {
   226  						_, _ = next.Do(c, &LokiRequest{})
   227  					}()
   228  				}
   229  				return nil, nil
   230  			})
   231  		}),
   232  	).RoundTrip(r)
   233  	require.Error(t, err)
   234  }
   235  
   236  func Test_MaxQueryLookBack(t *testing.T) {
   237  	tpw, stopper, err := NewTripperware(testConfig, util_log.Logger, fakeLimits{
   238  		maxQueryLookback:    1 * time.Hour,
   239  		maxQueryParallelism: 1,
   240  	}, config.SchemaConfig{}, nil, nil)
   241  	if stopper != nil {
   242  		defer stopper.Stop()
   243  	}
   244  	require.NoError(t, err)
   245  	rt, err := newfakeRoundTripper()
   246  	require.NoError(t, err)
   247  	defer rt.Close()
   248  
   249  	lreq := &LokiRequest{
   250  		Query:     `{app="foo"} |= "foo"`,
   251  		Limit:     10000,
   252  		StartTs:   testTime.Add(-6 * time.Hour),
   253  		EndTs:     testTime,
   254  		Direction: logproto.FORWARD,
   255  		Path:      "/loki/api/v1/query_range",
   256  	}
   257  
   258  	ctx := user.InjectOrgID(context.Background(), "1")
   259  	req, err := LokiCodec.EncodeRequest(ctx, lreq)
   260  	require.NoError(t, err)
   261  
   262  	req = req.WithContext(ctx)
   263  	err = user.InjectOrgIDIntoHTTPRequest(ctx, req)
   264  	require.NoError(t, err)
   265  
   266  	_, err = tpw(rt).RoundTrip(req)
   267  	require.NoError(t, err)
   268  }
   269  
   270  func Test_GenerateCacheKey_NoDivideZero(t *testing.T) {
   271  	l := cacheKeyLimits{WithSplitByLimits(nil, 0)}
   272  	start := time.Now()
   273  	r := &LokiRequest{
   274  		Query:   "qry",
   275  		StartTs: start,
   276  		Step:    int64(time.Minute / time.Millisecond),
   277  	}
   278  
   279  	require.Equal(
   280  		t,
   281  		fmt.Sprintf("foo:qry:%d:0:0", r.GetStep()),
   282  		l.GenerateCacheKey("foo", r),
   283  	)
   284  }