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

     1  package queryrange
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"net/http"
    11  	strings "strings"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/prometheus/common/model"
    16  	"github.com/stretchr/testify/require"
    17  
    18  	"github.com/grafana/loki/pkg/loghttp"
    19  	"github.com/grafana/loki/pkg/logproto"
    20  	"github.com/grafana/loki/pkg/logqlmodel/stats"
    21  	"github.com/grafana/loki/pkg/querier/queryrange/queryrangebase"
    22  	"github.com/grafana/loki/pkg/util"
    23  )
    24  
    25  func init() {
    26  	time.Local = nil // for easier tests comparison
    27  }
    28  
    29  var (
    30  	start = testTime //  Marshalling the time drops the monotonic clock so we can't use time.Now
    31  	end   = start.Add(1 * time.Hour)
    32  )
    33  
    34  func Test_codec_DecodeRequest(t *testing.T) {
    35  	tests := []struct {
    36  		name       string
    37  		reqBuilder func() (*http.Request, error)
    38  		want       queryrangebase.Request
    39  		wantErr    bool
    40  	}{
    41  		{"wrong", func() (*http.Request, error) { return http.NewRequest(http.MethodGet, "/bad?step=bad", nil) }, nil, true},
    42  		{"query_range", func() (*http.Request, error) {
    43  			return http.NewRequest(http.MethodGet,
    44  				fmt.Sprintf(`/query_range?start=%d&end=%d&query={foo="bar"}&step=10&limit=200&direction=FORWARD`, start.UnixNano(), end.UnixNano()), nil)
    45  		}, &LokiRequest{
    46  			Query:     `{foo="bar"}`,
    47  			Limit:     200,
    48  			Step:      10000, // step is expected in ms
    49  			Direction: logproto.FORWARD,
    50  			Path:      "/query_range",
    51  			StartTs:   start,
    52  			EndTs:     end,
    53  		}, false},
    54  		{"query_range", func() (*http.Request, error) {
    55  			return http.NewRequest(http.MethodGet,
    56  				fmt.Sprintf(`/query_range?start=%d&end=%d&query={foo="bar"}&interval=10&limit=200&direction=BACKWARD`, start.UnixNano(), end.UnixNano()), nil)
    57  		}, &LokiRequest{
    58  			Query:     `{foo="bar"}`,
    59  			Limit:     200,
    60  			Step:      14000, // step is expected in ms; calculated default if request param not present
    61  			Interval:  10000, // interval is expected in ms
    62  			Direction: logproto.BACKWARD,
    63  			Path:      "/query_range",
    64  			StartTs:   start,
    65  			EndTs:     end,
    66  		}, false},
    67  		{"series", func() (*http.Request, error) {
    68  			return http.NewRequest(http.MethodGet,
    69  				fmt.Sprintf(`/series?start=%d&end=%d&match={foo="bar"}`, start.UnixNano(), end.UnixNano()), nil)
    70  		}, &LokiSeriesRequest{
    71  			Match:   []string{`{foo="bar"}`},
    72  			Path:    "/series",
    73  			StartTs: start,
    74  			EndTs:   end,
    75  		}, false},
    76  		{"labels", func() (*http.Request, error) {
    77  			return http.NewRequest(http.MethodGet,
    78  				fmt.Sprintf(`/label?start=%d&end=%d`, start.UnixNano(), end.UnixNano()), nil)
    79  		}, &LokiLabelNamesRequest{
    80  			Path:    "/label",
    81  			StartTs: start,
    82  			EndTs:   end,
    83  		}, false},
    84  		{"index_stats", func() (*http.Request, error) {
    85  			return LokiCodec.EncodeRequest(context.Background(), &logproto.IndexStatsRequest{
    86  				From:     model.TimeFromUnixNano(start.UnixNano()),
    87  				Through:  model.TimeFromUnixNano(end.UnixNano()),
    88  				Matchers: `{job="foo"}`,
    89  			})
    90  		}, &logproto.IndexStatsRequest{
    91  			From:     model.TimeFromUnixNano(start.UnixNano()),
    92  			Through:  model.TimeFromUnixNano(end.UnixNano()),
    93  			Matchers: `{job="foo"}`,
    94  		}, false},
    95  	}
    96  	for _, tt := range tests {
    97  		t.Run(tt.name, func(t *testing.T) {
    98  			req, err := tt.reqBuilder()
    99  			if err != nil {
   100  				t.Fatal(err)
   101  			}
   102  			got, err := LokiCodec.DecodeRequest(context.TODO(), req, nil)
   103  			if (err != nil) != tt.wantErr {
   104  				t.Errorf("codec.DecodeRequest() error = %v, wantErr %v", err, tt.wantErr)
   105  				return
   106  			}
   107  			require.Equal(t, got, tt.want)
   108  		})
   109  	}
   110  }
   111  
   112  func Test_codec_DecodeResponse(t *testing.T) {
   113  	tests := []struct {
   114  		name    string
   115  		res     *http.Response
   116  		req     queryrangebase.Request
   117  		want    queryrangebase.Response
   118  		wantErr bool
   119  	}{
   120  		{"500", &http.Response{StatusCode: 500, Body: ioutil.NopCloser(strings.NewReader("some error"))}, nil, nil, true},
   121  		{"no body", &http.Response{StatusCode: 200, Body: ioutil.NopCloser(badReader{})}, nil, nil, true},
   122  		{"bad json", &http.Response{StatusCode: 200, Body: ioutil.NopCloser(strings.NewReader(""))}, nil, nil, true},
   123  		{"not success", &http.Response{StatusCode: 200, Body: ioutil.NopCloser(strings.NewReader(`{"status":"fail"}`))}, nil, nil, true},
   124  		{"unknown", &http.Response{StatusCode: 200, Body: ioutil.NopCloser(strings.NewReader(`{"status":"success"}`))}, nil, nil, true},
   125  		{
   126  			"matrix", &http.Response{StatusCode: 200, Body: ioutil.NopCloser(strings.NewReader(matrixString))}, nil,
   127  			&LokiPromResponse{
   128  				Response: &queryrangebase.PrometheusResponse{
   129  					Status: loghttp.QueryStatusSuccess,
   130  					Data: queryrangebase.PrometheusData{
   131  						ResultType: loghttp.ResultTypeMatrix,
   132  						Result:     sampleStreams,
   133  					},
   134  				},
   135  				Statistics: statsResult,
   136  			}, false,
   137  		},
   138  		{
   139  			"matrix-empty-streams",
   140  			&http.Response{StatusCode: 200, Body: ioutil.NopCloser(strings.NewReader(matrixStringEmptyResult))},
   141  			nil,
   142  			&LokiPromResponse{
   143  				Response: &queryrangebase.PrometheusResponse{
   144  					Status: loghttp.QueryStatusSuccess,
   145  					Data: queryrangebase.PrometheusData{
   146  						ResultType: loghttp.ResultTypeMatrix,
   147  						Result:     make([]queryrangebase.SampleStream, 0), // shouldn't be nil.
   148  					},
   149  				},
   150  				Statistics: statsResult,
   151  			}, false,
   152  		},
   153  		{
   154  			"vector-empty-streams",
   155  			&http.Response{StatusCode: 200, Body: ioutil.NopCloser(strings.NewReader(vectorStringEmptyResult))},
   156  			nil,
   157  			&LokiPromResponse{
   158  				Response: &queryrangebase.PrometheusResponse{
   159  					Status: loghttp.QueryStatusSuccess,
   160  					Data: queryrangebase.PrometheusData{
   161  						ResultType: loghttp.ResultTypeVector,
   162  						Result:     make([]queryrangebase.SampleStream, 0), // shouldn't be nil.
   163  					},
   164  				},
   165  				Statistics: statsResult,
   166  			}, false,
   167  		},
   168  		{
   169  			"streams v1", &http.Response{StatusCode: 200, Body: ioutil.NopCloser(strings.NewReader(streamsString))},
   170  			&LokiRequest{Direction: logproto.FORWARD, Limit: 100, Path: "/loki/api/v1/query_range"},
   171  			&LokiResponse{
   172  				Status:    loghttp.QueryStatusSuccess,
   173  				Direction: logproto.FORWARD,
   174  				Limit:     100,
   175  				Version:   uint32(loghttp.VersionV1),
   176  				Data: LokiData{
   177  					ResultType: loghttp.ResultTypeStream,
   178  					Result:     logStreams,
   179  				},
   180  				Statistics: statsResult,
   181  			}, false,
   182  		},
   183  		{
   184  			"streams legacy", &http.Response{StatusCode: 200, Body: ioutil.NopCloser(strings.NewReader(streamsString))},
   185  			&LokiRequest{Direction: logproto.FORWARD, Limit: 100, Path: "/api/prom/query_range"},
   186  			&LokiResponse{
   187  				Status:    loghttp.QueryStatusSuccess,
   188  				Direction: logproto.FORWARD,
   189  				Limit:     100,
   190  				Version:   uint32(loghttp.VersionLegacy),
   191  				Data: LokiData{
   192  					ResultType: loghttp.ResultTypeStream,
   193  					Result:     logStreams,
   194  				},
   195  				Statistics: statsResult,
   196  			}, false,
   197  		},
   198  		{
   199  			"series", &http.Response{StatusCode: 200, Body: ioutil.NopCloser(strings.NewReader(seriesString))},
   200  			&LokiSeriesRequest{Path: "/loki/api/v1/series"},
   201  			&LokiSeriesResponse{
   202  				Status:  "success",
   203  				Version: uint32(loghttp.VersionV1),
   204  				Data:    seriesData,
   205  			}, false,
   206  		},
   207  		{
   208  			"labels legacy", &http.Response{StatusCode: 200, Body: ioutil.NopCloser(strings.NewReader(labelsString))},
   209  			&LokiLabelNamesRequest{Path: "/api/prom/label"},
   210  			&LokiLabelNamesResponse{
   211  				Status:  "success",
   212  				Version: uint32(loghttp.VersionLegacy),
   213  				Data:    labelsData,
   214  			}, false,
   215  		},
   216  		{
   217  			"index stats", &http.Response{StatusCode: 200, Body: ioutil.NopCloser(strings.NewReader(indexStatsString))},
   218  			&logproto.IndexStatsRequest{},
   219  			&IndexStatsResponse{
   220  				Response: &logproto.IndexStatsResponse{
   221  					Streams: 1,
   222  					Chunks:  2,
   223  					Bytes:   3,
   224  					Entries: 4,
   225  				},
   226  			}, false,
   227  		},
   228  	}
   229  	for _, tt := range tests {
   230  		t.Run(tt.name, func(t *testing.T) {
   231  			got, err := LokiCodec.DecodeResponse(context.TODO(), tt.res, tt.req)
   232  			if (err != nil) != tt.wantErr {
   233  				t.Errorf("codec.DecodeResponse() error = %v, wantErr %v", err, tt.wantErr)
   234  				return
   235  			}
   236  			require.Equal(t, tt.want, got)
   237  		})
   238  	}
   239  }
   240  
   241  func Test_codec_EncodeRequest(t *testing.T) {
   242  	// we only accept LokiRequest.
   243  	got, err := LokiCodec.EncodeRequest(context.TODO(), &queryrangebase.PrometheusRequest{})
   244  	require.Error(t, err)
   245  	require.Nil(t, got)
   246  
   247  	ctx := context.Background()
   248  	toEncode := &LokiRequest{
   249  		Query:     `{foo="bar"}`,
   250  		Limit:     200,
   251  		Step:      86400000, // nanoseconds
   252  		Interval:  10000000, // nanoseconds
   253  		Direction: logproto.FORWARD,
   254  		Path:      "/query_range",
   255  		StartTs:   start,
   256  		EndTs:     end,
   257  	}
   258  	got, err = LokiCodec.EncodeRequest(ctx, toEncode)
   259  	require.NoError(t, err)
   260  	require.Equal(t, ctx, got.Context())
   261  	require.Equal(t, "/loki/api/v1/query_range", got.URL.Path)
   262  	require.Equal(t, fmt.Sprintf("%d", start.UnixNano()), got.URL.Query().Get("start"))
   263  	require.Equal(t, fmt.Sprintf("%d", end.UnixNano()), got.URL.Query().Get("end"))
   264  	require.Equal(t, `{foo="bar"}`, got.URL.Query().Get("query"))
   265  	require.Equal(t, fmt.Sprintf("%d", 200), got.URL.Query().Get("limit"))
   266  	require.Equal(t, `FORWARD`, got.URL.Query().Get("direction"))
   267  	require.Equal(t, "86400.000000", got.URL.Query().Get("step"))
   268  	require.Equal(t, "10000.000000", got.URL.Query().Get("interval"))
   269  
   270  	// testing a full roundtrip
   271  	req, err := LokiCodec.DecodeRequest(context.TODO(), got, nil)
   272  	require.NoError(t, err)
   273  	require.Equal(t, toEncode.Query, req.(*LokiRequest).Query)
   274  	require.Equal(t, toEncode.Step, req.(*LokiRequest).Step)
   275  	require.Equal(t, toEncode.Interval, req.(*LokiRequest).Interval)
   276  	require.Equal(t, toEncode.StartTs, req.(*LokiRequest).StartTs)
   277  	require.Equal(t, toEncode.EndTs, req.(*LokiRequest).EndTs)
   278  	require.Equal(t, toEncode.Direction, req.(*LokiRequest).Direction)
   279  	require.Equal(t, toEncode.Limit, req.(*LokiRequest).Limit)
   280  	require.Equal(t, "/loki/api/v1/query_range", req.(*LokiRequest).Path)
   281  }
   282  
   283  func Test_codec_series_EncodeRequest(t *testing.T) {
   284  	got, err := LokiCodec.EncodeRequest(context.TODO(), &queryrangebase.PrometheusRequest{})
   285  	require.Error(t, err)
   286  	require.Nil(t, got)
   287  
   288  	ctx := context.Background()
   289  	toEncode := &LokiSeriesRequest{
   290  		Match:   []string{`{foo="bar"}`},
   291  		Path:    "/series",
   292  		StartTs: start,
   293  		EndTs:   end,
   294  	}
   295  	got, err = LokiCodec.EncodeRequest(ctx, toEncode)
   296  	require.NoError(t, err)
   297  	require.Equal(t, ctx, got.Context())
   298  	require.Equal(t, "/loki/api/v1/series", got.URL.Path)
   299  	require.Equal(t, fmt.Sprintf("%d", start.UnixNano()), got.URL.Query().Get("start"))
   300  	require.Equal(t, fmt.Sprintf("%d", end.UnixNano()), got.URL.Query().Get("end"))
   301  	require.Equal(t, `{foo="bar"}`, got.URL.Query().Get("match[]"))
   302  
   303  	// testing a full roundtrip
   304  	req, err := LokiCodec.DecodeRequest(context.TODO(), got, nil)
   305  	require.NoError(t, err)
   306  	require.Equal(t, toEncode.Match, req.(*LokiSeriesRequest).Match)
   307  	require.Equal(t, toEncode.StartTs, req.(*LokiSeriesRequest).StartTs)
   308  	require.Equal(t, toEncode.EndTs, req.(*LokiSeriesRequest).EndTs)
   309  	require.Equal(t, "/loki/api/v1/series", req.(*LokiSeriesRequest).Path)
   310  }
   311  
   312  func Test_codec_labels_EncodeRequest(t *testing.T) {
   313  	ctx := context.Background()
   314  	toEncode := &LokiLabelNamesRequest{
   315  		Path:    "/loki/api/v1/labels",
   316  		StartTs: start,
   317  		EndTs:   end,
   318  	}
   319  	got, err := LokiCodec.EncodeRequest(ctx, toEncode)
   320  	require.NoError(t, err)
   321  	require.Equal(t, ctx, got.Context())
   322  	require.Equal(t, "/loki/api/v1/labels", got.URL.Path)
   323  	require.Equal(t, fmt.Sprintf("%d", start.UnixNano()), got.URL.Query().Get("start"))
   324  	require.Equal(t, fmt.Sprintf("%d", end.UnixNano()), got.URL.Query().Get("end"))
   325  
   326  	// testing a full roundtrip
   327  	req, err := LokiCodec.DecodeRequest(context.TODO(), got, nil)
   328  	require.NoError(t, err)
   329  	require.Equal(t, toEncode.StartTs, req.(*LokiLabelNamesRequest).StartTs)
   330  	require.Equal(t, toEncode.EndTs, req.(*LokiLabelNamesRequest).EndTs)
   331  	require.Equal(t, "/loki/api/v1/labels", req.(*LokiLabelNamesRequest).Path)
   332  
   333  	// Test labels values endpoint
   334  	toEncode = &LokiLabelNamesRequest{
   335  		Path:    "/loki/api/v1/labels/__name__/values",
   336  		StartTs: start,
   337  		EndTs:   end,
   338  	}
   339  	got, err = LokiCodec.EncodeRequest(ctx, toEncode)
   340  	require.NoError(t, err)
   341  	require.Equal(t, ctx, got.Context())
   342  	require.Equal(t, "/loki/api/v1/labels/__name__/values", got.URL.Path)
   343  	require.Equal(t, fmt.Sprintf("%d", start.UnixNano()), got.URL.Query().Get("start"))
   344  	require.Equal(t, fmt.Sprintf("%d", end.UnixNano()), got.URL.Query().Get("end"))
   345  
   346  	// testing a full roundtrip
   347  	req, err = LokiCodec.DecodeRequest(context.TODO(), got, nil)
   348  	require.NoError(t, err)
   349  	require.Equal(t, toEncode.StartTs, req.(*LokiLabelNamesRequest).StartTs)
   350  	require.Equal(t, toEncode.EndTs, req.(*LokiLabelNamesRequest).EndTs)
   351  	require.Equal(t, "/loki/api/v1/labels/__name__/values", req.(*LokiLabelNamesRequest).Path)
   352  }
   353  
   354  func Test_codec_index_stats_EncodeRequest(t *testing.T) {
   355  	from, through := util.RoundToMilliseconds(start, end)
   356  	toEncode := &logproto.IndexStatsRequest{
   357  		From:     from,
   358  		Through:  through,
   359  		Matchers: `{job="foo"}`,
   360  	}
   361  	got, err := LokiCodec.EncodeRequest(context.Background(), toEncode)
   362  	require.Nil(t, err)
   363  	require.Equal(t, fmt.Sprintf("%d", from.UnixNano()), got.URL.Query().Get("start"))
   364  	require.Equal(t, fmt.Sprintf("%d", through.UnixNano()), got.URL.Query().Get("end"))
   365  	require.Equal(t, `{job="foo"}`, got.URL.Query().Get("query"))
   366  }
   367  
   368  func Test_codec_EncodeResponse(t *testing.T) {
   369  	tests := []struct {
   370  		name    string
   371  		res     queryrangebase.Response
   372  		body    string
   373  		wantErr bool
   374  	}{
   375  		{"error", &badResponse{}, "", true},
   376  		{"prom", &LokiPromResponse{
   377  			Response: &queryrangebase.PrometheusResponse{
   378  				Status: loghttp.QueryStatusSuccess,
   379  				Data: queryrangebase.PrometheusData{
   380  					ResultType: loghttp.ResultTypeMatrix,
   381  					Result:     sampleStreams,
   382  				},
   383  			},
   384  			Statistics: statsResult,
   385  		}, matrixString, false},
   386  		{
   387  			"loki v1",
   388  			&LokiResponse{
   389  				Status:    loghttp.QueryStatusSuccess,
   390  				Direction: logproto.FORWARD,
   391  				Limit:     100,
   392  				Version:   uint32(loghttp.VersionV1),
   393  				Data: LokiData{
   394  					ResultType: loghttp.ResultTypeStream,
   395  					Result:     logStreams,
   396  				},
   397  				Statistics: statsResult,
   398  			}, streamsString, false,
   399  		},
   400  		{
   401  			"loki legacy",
   402  			&LokiResponse{
   403  				Status:    loghttp.QueryStatusSuccess,
   404  				Direction: logproto.FORWARD,
   405  				Limit:     100,
   406  				Version:   uint32(loghttp.VersionLegacy),
   407  				Data: LokiData{
   408  					ResultType: loghttp.ResultTypeStream,
   409  					Result:     logStreams,
   410  				},
   411  				Statistics: statsResult,
   412  			}, streamsStringLegacy, false,
   413  		},
   414  		{
   415  			"loki series",
   416  			&LokiSeriesResponse{
   417  				Status:  "success",
   418  				Version: uint32(loghttp.VersionV1),
   419  				Data:    seriesData,
   420  			}, seriesString, false,
   421  		},
   422  		{
   423  			"loki labels",
   424  			&LokiLabelNamesResponse{
   425  				Status:  "success",
   426  				Version: uint32(loghttp.VersionV1),
   427  				Data:    labelsData,
   428  			}, labelsString, false,
   429  		},
   430  		{
   431  			"loki labels legacy",
   432  			&LokiLabelNamesResponse{
   433  				Status:  "success",
   434  				Version: uint32(loghttp.VersionLegacy),
   435  				Data:    labelsData,
   436  			}, labelsLegacyString, false,
   437  		},
   438  		{
   439  			"index stats",
   440  			&IndexStatsResponse{
   441  				Response: &logproto.IndexStatsResponse{
   442  					Streams: 1,
   443  					Chunks:  2,
   444  					Bytes:   3,
   445  					Entries: 4,
   446  				},
   447  			}, indexStatsString, false,
   448  		},
   449  	}
   450  	for _, tt := range tests {
   451  		t.Run(tt.name, func(t *testing.T) {
   452  			got, err := LokiCodec.EncodeResponse(context.TODO(), tt.res)
   453  			if (err != nil) != tt.wantErr {
   454  				t.Errorf("codec.EncodeResponse() error = %v, wantErr %v", err, tt.wantErr)
   455  				return
   456  			}
   457  			if err == nil {
   458  				require.Equal(t, 200, got.StatusCode)
   459  				body, err := ioutil.ReadAll(got.Body)
   460  				require.Nil(t, err)
   461  				bodyString := string(body)
   462  				require.JSONEq(t, tt.body, bodyString)
   463  			}
   464  		})
   465  	}
   466  }
   467  
   468  func Test_codec_MergeResponse(t *testing.T) {
   469  	tests := []struct {
   470  		name      string
   471  		responses []queryrangebase.Response
   472  		want      queryrangebase.Response
   473  		wantErr   bool
   474  	}{
   475  		{"empty", []queryrangebase.Response{}, nil, true},
   476  		{"unknown response", []queryrangebase.Response{&badResponse{}}, nil, true},
   477  		{
   478  			"prom",
   479  			[]queryrangebase.Response{
   480  				&LokiPromResponse{
   481  					Response: &queryrangebase.PrometheusResponse{
   482  						Status: loghttp.QueryStatusSuccess,
   483  						Data: queryrangebase.PrometheusData{
   484  							ResultType: loghttp.ResultTypeMatrix,
   485  							Result:     sampleStreams,
   486  						},
   487  					},
   488  				},
   489  			},
   490  			&LokiPromResponse{
   491  				Statistics: stats.Result{Summary: stats.Summary{Subqueries: 1}},
   492  				Response: &queryrangebase.PrometheusResponse{
   493  					Status: loghttp.QueryStatusSuccess,
   494  					Data: queryrangebase.PrometheusData{
   495  						ResultType: loghttp.ResultTypeMatrix,
   496  						Result:     sampleStreams,
   497  					},
   498  				},
   499  			},
   500  			false,
   501  		},
   502  		{
   503  			"loki backward",
   504  			[]queryrangebase.Response{
   505  				&LokiResponse{
   506  					Status:    loghttp.QueryStatusSuccess,
   507  					Direction: logproto.BACKWARD,
   508  					Limit:     100,
   509  					Version:   1,
   510  					Data: LokiData{
   511  						ResultType: loghttp.ResultTypeStream,
   512  						Result: []logproto.Stream{
   513  							{
   514  								Labels: `{foo="bar", level="error"}`,
   515  								Entries: []logproto.Entry{
   516  									{Timestamp: time.Unix(0, 2), Line: "2"},
   517  									{Timestamp: time.Unix(0, 1), Line: "1"},
   518  								},
   519  							},
   520  							{
   521  								Labels: `{foo="bar", level="debug"}`,
   522  								Entries: []logproto.Entry{
   523  									{Timestamp: time.Unix(0, 6), Line: "6"},
   524  									{Timestamp: time.Unix(0, 5), Line: "5"},
   525  								},
   526  							},
   527  						},
   528  					},
   529  				},
   530  				&LokiResponse{
   531  					Status:    loghttp.QueryStatusSuccess,
   532  					Direction: logproto.BACKWARD,
   533  					Limit:     100,
   534  					Version:   1,
   535  					Data: LokiData{
   536  						ResultType: loghttp.ResultTypeStream,
   537  						Result: []logproto.Stream{
   538  							{
   539  								Labels: `{foo="bar", level="error"}`,
   540  								Entries: []logproto.Entry{
   541  									{Timestamp: time.Unix(0, 10), Line: "10"},
   542  									{Timestamp: time.Unix(0, 9), Line: "9"},
   543  									{Timestamp: time.Unix(0, 9), Line: "9"},
   544  								},
   545  							},
   546  							{
   547  								Labels: `{foo="bar", level="debug"}`,
   548  								Entries: []logproto.Entry{
   549  									{Timestamp: time.Unix(0, 16), Line: "16"},
   550  									{Timestamp: time.Unix(0, 15), Line: "15"},
   551  								},
   552  							},
   553  						},
   554  					},
   555  				},
   556  			},
   557  			&LokiResponse{
   558  				Status:     loghttp.QueryStatusSuccess,
   559  				Direction:  logproto.BACKWARD,
   560  				Limit:      100,
   561  				Version:    1,
   562  				Statistics: stats.Result{Summary: stats.Summary{Subqueries: 2}},
   563  				Data: LokiData{
   564  					ResultType: loghttp.ResultTypeStream,
   565  					Result: []logproto.Stream{
   566  						{
   567  							Labels: `{foo="bar", level="error"}`,
   568  							Entries: []logproto.Entry{
   569  								{Timestamp: time.Unix(0, 10), Line: "10"},
   570  								{Timestamp: time.Unix(0, 9), Line: "9"},
   571  								{Timestamp: time.Unix(0, 9), Line: "9"},
   572  								{Timestamp: time.Unix(0, 2), Line: "2"},
   573  								{Timestamp: time.Unix(0, 1), Line: "1"},
   574  							},
   575  						},
   576  						{
   577  							Labels: `{foo="bar", level="debug"}`,
   578  							Entries: []logproto.Entry{
   579  								{Timestamp: time.Unix(0, 16), Line: "16"},
   580  								{Timestamp: time.Unix(0, 15), Line: "15"},
   581  								{Timestamp: time.Unix(0, 6), Line: "6"},
   582  								{Timestamp: time.Unix(0, 5), Line: "5"},
   583  							},
   584  						},
   585  					},
   586  				},
   587  			},
   588  			false,
   589  		},
   590  		{
   591  			"loki backward limited",
   592  			[]queryrangebase.Response{
   593  				&LokiResponse{
   594  					Status:    loghttp.QueryStatusSuccess,
   595  					Direction: logproto.BACKWARD,
   596  					Limit:     6,
   597  					Version:   1,
   598  					Data: LokiData{
   599  						ResultType: loghttp.ResultTypeStream,
   600  						Result: []logproto.Stream{
   601  							{
   602  								Labels: `{foo="bar", level="error"}`,
   603  								Entries: []logproto.Entry{
   604  									{Timestamp: time.Unix(0, 10), Line: "10"},
   605  									{Timestamp: time.Unix(0, 9), Line: "9"},
   606  									{Timestamp: time.Unix(0, 9), Line: "9"},
   607  								},
   608  							},
   609  							{
   610  								Labels: `{foo="bar", level="debug"}`,
   611  								Entries: []logproto.Entry{
   612  									{Timestamp: time.Unix(0, 16), Line: "16"},
   613  									{Timestamp: time.Unix(0, 15), Line: "15"},
   614  								},
   615  							},
   616  						},
   617  					},
   618  				},
   619  				&LokiResponse{
   620  					Status:    loghttp.QueryStatusSuccess,
   621  					Direction: logproto.BACKWARD,
   622  					Limit:     6,
   623  					Version:   1,
   624  					Data: LokiData{
   625  						ResultType: loghttp.ResultTypeStream,
   626  						Result: []logproto.Stream{
   627  							{
   628  								Labels: `{foo="bar", level="error"}`,
   629  								Entries: []logproto.Entry{
   630  									{Timestamp: time.Unix(0, 2), Line: "2"},
   631  									{Timestamp: time.Unix(0, 1), Line: "1"},
   632  								},
   633  							},
   634  							{
   635  								Labels: `{foo="bar", level="debug"}`,
   636  								Entries: []logproto.Entry{
   637  									{Timestamp: time.Unix(0, 6), Line: "6"},
   638  									{Timestamp: time.Unix(0, 5), Line: "5"},
   639  								},
   640  							},
   641  						},
   642  					},
   643  				},
   644  			},
   645  			&LokiResponse{
   646  				Status:     loghttp.QueryStatusSuccess,
   647  				Direction:  logproto.BACKWARD,
   648  				Limit:      6,
   649  				Version:    1,
   650  				Statistics: stats.Result{Summary: stats.Summary{Subqueries: 2}},
   651  				Data: LokiData{
   652  					ResultType: loghttp.ResultTypeStream,
   653  					Result: []logproto.Stream{
   654  						{
   655  							Labels: `{foo="bar", level="error"}`,
   656  							Entries: []logproto.Entry{
   657  								{Timestamp: time.Unix(0, 10), Line: "10"},
   658  								{Timestamp: time.Unix(0, 9), Line: "9"},
   659  								{Timestamp: time.Unix(0, 9), Line: "9"},
   660  							},
   661  						},
   662  						{
   663  							Labels: `{foo="bar", level="debug"}`,
   664  							Entries: []logproto.Entry{
   665  								{Timestamp: time.Unix(0, 16), Line: "16"},
   666  								{Timestamp: time.Unix(0, 15), Line: "15"},
   667  								{Timestamp: time.Unix(0, 6), Line: "6"},
   668  							},
   669  						},
   670  					},
   671  				},
   672  			},
   673  			false,
   674  		},
   675  		{
   676  			"loki forward",
   677  			[]queryrangebase.Response{
   678  				&LokiResponse{
   679  					Status:    loghttp.QueryStatusSuccess,
   680  					Direction: logproto.FORWARD,
   681  					Limit:     100,
   682  					Version:   1,
   683  					Data: LokiData{
   684  						ResultType: loghttp.ResultTypeStream,
   685  						Result: []logproto.Stream{
   686  							{
   687  								Labels: `{foo="bar", level="error"}`,
   688  								Entries: []logproto.Entry{
   689  									{Timestamp: time.Unix(0, 1), Line: "1"},
   690  									{Timestamp: time.Unix(0, 2), Line: "2"},
   691  								},
   692  							},
   693  							{
   694  								Labels: `{foo="bar", level="debug"}`,
   695  								Entries: []logproto.Entry{
   696  									{Timestamp: time.Unix(0, 5), Line: "5"},
   697  									{Timestamp: time.Unix(0, 6), Line: "6"},
   698  								},
   699  							},
   700  						},
   701  					},
   702  				},
   703  				&LokiResponse{
   704  					Status:    loghttp.QueryStatusSuccess,
   705  					Direction: logproto.FORWARD,
   706  					Limit:     100,
   707  					Version:   1,
   708  					Data: LokiData{
   709  						ResultType: loghttp.ResultTypeStream,
   710  						Result: []logproto.Stream{
   711  							{
   712  								Labels: `{foo="bar", level="error"}`,
   713  								Entries: []logproto.Entry{
   714  									{Timestamp: time.Unix(0, 9), Line: "9"},
   715  									{Timestamp: time.Unix(0, 10), Line: "10"},
   716  								},
   717  							},
   718  							{
   719  								Labels: `{foo="bar", level="debug"}`,
   720  								Entries: []logproto.Entry{
   721  									{Timestamp: time.Unix(0, 15), Line: "15"},
   722  									{Timestamp: time.Unix(0, 15), Line: "15"},
   723  									{Timestamp: time.Unix(0, 16), Line: "16"},
   724  								},
   725  							},
   726  						},
   727  					},
   728  				},
   729  			},
   730  			&LokiResponse{
   731  				Status:     loghttp.QueryStatusSuccess,
   732  				Direction:  logproto.FORWARD,
   733  				Limit:      100,
   734  				Version:    1,
   735  				Statistics: stats.Result{Summary: stats.Summary{Subqueries: 2}},
   736  				Data: LokiData{
   737  					ResultType: loghttp.ResultTypeStream,
   738  					Result: []logproto.Stream{
   739  						{
   740  							Labels: `{foo="bar", level="debug"}`,
   741  							Entries: []logproto.Entry{
   742  								{Timestamp: time.Unix(0, 5), Line: "5"},
   743  								{Timestamp: time.Unix(0, 6), Line: "6"},
   744  								{Timestamp: time.Unix(0, 15), Line: "15"},
   745  								{Timestamp: time.Unix(0, 15), Line: "15"},
   746  								{Timestamp: time.Unix(0, 16), Line: "16"},
   747  							},
   748  						},
   749  						{
   750  							Labels: `{foo="bar", level="error"}`,
   751  							Entries: []logproto.Entry{
   752  								{Timestamp: time.Unix(0, 1), Line: "1"},
   753  								{Timestamp: time.Unix(0, 2), Line: "2"},
   754  								{Timestamp: time.Unix(0, 9), Line: "9"},
   755  								{Timestamp: time.Unix(0, 10), Line: "10"},
   756  							},
   757  						},
   758  					},
   759  				},
   760  			},
   761  			false,
   762  		},
   763  		{
   764  			"loki forward limited",
   765  			[]queryrangebase.Response{
   766  				&LokiResponse{
   767  					Status:    loghttp.QueryStatusSuccess,
   768  					Direction: logproto.FORWARD,
   769  					Limit:     5,
   770  					Version:   1,
   771  					Data: LokiData{
   772  						ResultType: loghttp.ResultTypeStream,
   773  						Result: []logproto.Stream{
   774  							{
   775  								Labels: `{foo="bar", level="error"}`,
   776  								Entries: []logproto.Entry{
   777  									{Timestamp: time.Unix(0, 1), Line: "1"},
   778  									{Timestamp: time.Unix(0, 2), Line: "2"},
   779  								},
   780  							},
   781  							{
   782  								Labels: `{foo="bar", level="debug"}`,
   783  								Entries: []logproto.Entry{
   784  									{Timestamp: time.Unix(0, 5), Line: "5"},
   785  									{Timestamp: time.Unix(0, 6), Line: "6"},
   786  								},
   787  							},
   788  						},
   789  					},
   790  				},
   791  				&LokiResponse{
   792  					Status:    loghttp.QueryStatusSuccess,
   793  					Direction: logproto.FORWARD,
   794  					Limit:     5,
   795  					Version:   1,
   796  					Data: LokiData{
   797  						ResultType: loghttp.ResultTypeStream,
   798  						Result: []logproto.Stream{
   799  							{
   800  								Labels: `{foo="bar", level="error"}`,
   801  								Entries: []logproto.Entry{
   802  									{Timestamp: time.Unix(0, 9), Line: "9"},
   803  									{Timestamp: time.Unix(0, 10), Line: "10"},
   804  								},
   805  							},
   806  							{
   807  								Labels: `{foo="bar", level="debug"}`,
   808  								Entries: []logproto.Entry{
   809  									{Timestamp: time.Unix(0, 15), Line: "15"},
   810  									{Timestamp: time.Unix(0, 15), Line: "15"},
   811  									{Timestamp: time.Unix(0, 16), Line: "16"},
   812  								},
   813  							},
   814  						},
   815  					},
   816  				},
   817  			},
   818  			&LokiResponse{
   819  				Status:     loghttp.QueryStatusSuccess,
   820  				Direction:  logproto.FORWARD,
   821  				Limit:      5,
   822  				Version:    1,
   823  				Statistics: stats.Result{Summary: stats.Summary{Subqueries: 2}},
   824  				Data: LokiData{
   825  					ResultType: loghttp.ResultTypeStream,
   826  					Result: []logproto.Stream{
   827  						{
   828  							Labels: `{foo="bar", level="debug"}`,
   829  							Entries: []logproto.Entry{
   830  								{Timestamp: time.Unix(0, 5), Line: "5"},
   831  								{Timestamp: time.Unix(0, 6), Line: "6"},
   832  							},
   833  						},
   834  						{
   835  							Labels: `{foo="bar", level="error"}`,
   836  							Entries: []logproto.Entry{
   837  								{Timestamp: time.Unix(0, 1), Line: "1"},
   838  								{Timestamp: time.Unix(0, 2), Line: "2"},
   839  								{Timestamp: time.Unix(0, 9), Line: "9"},
   840  							},
   841  						},
   842  					},
   843  				},
   844  			},
   845  			false,
   846  		},
   847  		{
   848  			"loki series",
   849  			[]queryrangebase.Response{
   850  				&LokiSeriesResponse{
   851  					Status:  "success",
   852  					Version: 1,
   853  					Data: []logproto.SeriesIdentifier{
   854  						{
   855  							Labels: map[string]string{"filename": "/var/hostlog/apport.log", "job": "varlogs"},
   856  						},
   857  						{
   858  							Labels: map[string]string{"filename": "/var/hostlog/test.log", "job": "varlogs"},
   859  						},
   860  					},
   861  				},
   862  				&LokiSeriesResponse{
   863  					Status:  "success",
   864  					Version: 1,
   865  					Data: []logproto.SeriesIdentifier{
   866  						{
   867  							Labels: map[string]string{"filename": "/var/hostlog/apport.log", "job": "varlogs"},
   868  						},
   869  						{
   870  							Labels: map[string]string{"filename": "/var/hostlog/other.log", "job": "varlogs"},
   871  						},
   872  					},
   873  				},
   874  			},
   875  			&LokiSeriesResponse{
   876  				Status:  "success",
   877  				Version: 1,
   878  				Data: []logproto.SeriesIdentifier{
   879  					{
   880  						Labels: map[string]string{"filename": "/var/hostlog/apport.log", "job": "varlogs"},
   881  					},
   882  					{
   883  						Labels: map[string]string{"filename": "/var/hostlog/test.log", "job": "varlogs"},
   884  					},
   885  					{
   886  						Labels: map[string]string{"filename": "/var/hostlog/other.log", "job": "varlogs"},
   887  					},
   888  				},
   889  			},
   890  			false,
   891  		},
   892  		{
   893  			"loki labels",
   894  			[]queryrangebase.Response{
   895  				&LokiLabelNamesResponse{
   896  					Status:  "success",
   897  					Version: 1,
   898  					Data:    []string{"foo", "bar", "buzz"},
   899  				},
   900  				&LokiLabelNamesResponse{
   901  					Status:  "success",
   902  					Version: 1,
   903  					Data:    []string{"foo", "bar", "buzz"},
   904  				},
   905  				&LokiLabelNamesResponse{
   906  					Status:  "success",
   907  					Version: 1,
   908  					Data:    []string{"foo", "blip", "blop"},
   909  				},
   910  			},
   911  			&LokiLabelNamesResponse{
   912  				Status:  "success",
   913  				Version: 1,
   914  				Data:    []string{"foo", "bar", "buzz", "blip", "blop"},
   915  			},
   916  			false,
   917  		},
   918  	}
   919  	for _, tt := range tests {
   920  		t.Run(tt.name, func(t *testing.T) {
   921  			got, err := LokiCodec.MergeResponse(tt.responses...)
   922  			if (err != nil) != tt.wantErr {
   923  				t.Errorf("codec.MergeResponse() error = %v, wantErr %v", err, tt.wantErr)
   924  				return
   925  			}
   926  			require.Equal(t, tt.want, got)
   927  		})
   928  	}
   929  }
   930  
   931  type badResponse struct{}
   932  
   933  func (badResponse) Reset()                                                 {}
   934  func (badResponse) String() string                                         { return "noop" }
   935  func (badResponse) ProtoMessage()                                          {}
   936  func (badResponse) GetHeaders() []*queryrangebase.PrometheusResponseHeader { return nil }
   937  
   938  type badReader struct{}
   939  
   940  func (badReader) Read(p []byte) (n int, err error) {
   941  	return 0, errors.New("")
   942  }
   943  
   944  var (
   945  	statsResultString = `"stats" : {
   946  		"ingester" : {
   947  			"store": {
   948  				"chunk":{
   949  					"compressedBytes": 1,
   950  					"decompressedBytes": 2,
   951  					"decompressedLines": 3,
   952  					"headChunkBytes": 4,
   953  					"headChunkLines": 5,
   954  					"totalDuplicates": 8
   955  				},
   956  				"chunksDownloadTime": 0,
   957  				"totalChunksRef": 0,
   958  				"totalChunksDownloaded": 0
   959  			},
   960  			"totalBatches": 6,
   961  			"totalChunksMatched": 7,
   962  			"totalLinesSent": 9,
   963  			"totalReached": 10
   964  		},
   965  		"querier": {
   966  			"store" : {
   967  				"chunk": {
   968  					"compressedBytes": 11,
   969  					"decompressedBytes": 12,
   970  					"decompressedLines": 13,
   971  					"headChunkBytes": 14,
   972  					"headChunkLines": 15,
   973  					"totalDuplicates": 19
   974  				},
   975  				"chunksDownloadTime": 16,
   976  				"totalChunksRef": 17,
   977  				"totalChunksDownloaded": 18
   978  			}
   979  		},
   980  		"cache": {
   981  			"chunk": {
   982  				"entriesFound": 0,
   983  				"entriesRequested": 0,
   984  				"entriesStored": 0,
   985  				"bytesReceived": 0,
   986  				"bytesSent": 0,
   987  				"requests": 0
   988  			},
   989  			"index": {
   990  				"entriesFound": 0,
   991  				"entriesRequested": 0,
   992  				"entriesStored": 0,
   993  				"bytesReceived": 0,
   994  				"bytesSent": 0,
   995  				"requests": 0
   996  			},
   997  			"result": {
   998  				"entriesFound": 0,
   999  				"entriesRequested": 0,
  1000  				"entriesStored": 0,
  1001  				"bytesReceived": 0,
  1002  				"bytesSent": 0,
  1003  				"requests": 0
  1004  			}
  1005  		},
  1006  		"summary": {
  1007  			"bytesProcessedPerSecond": 20,
  1008  			"execTime": 22,
  1009  			"linesProcessedPerSecond": 23,
  1010  			"queueTime": 21,
  1011  			"subqueries": 1,
  1012  			"totalBytesProcessed": 24,
  1013                          "totalEntriesReturned": 10,
  1014  			"totalLinesProcessed": 25
  1015  		}
  1016  	},`
  1017  	matrixString = `{
  1018  	"data": {
  1019  	  ` + statsResultString + `
  1020  	  "resultType": "matrix",
  1021  	  "result": [
  1022  		{
  1023  		  "metric": {
  1024  			"filename": "\/var\/hostlog\/apport.log",
  1025  			"job": "varlogs"
  1026  		  },
  1027  		  "values": [
  1028  			  [
  1029  				1568404331.324,
  1030  				"0.013333333333333334"
  1031  			  ]
  1032  			]
  1033  		},
  1034  		{
  1035  		  "metric": {
  1036  			"filename": "\/var\/hostlog\/syslog",
  1037  			"job": "varlogs"
  1038  		  },
  1039  		  "values": [
  1040  				[
  1041  					1568404331.324,
  1042  					"3.45"
  1043  				],
  1044  				[
  1045  					1568404331.339,
  1046  					"4.45"
  1047  				]
  1048  			]
  1049  		}
  1050  	  ]
  1051  	},
  1052  	"status": "success"
  1053    }`
  1054  	matrixStringEmptyResult = `{
  1055  	"data": {
  1056  	  ` + statsResultString + `
  1057  	  "resultType": "matrix",
  1058  	  "result": []
  1059  	},
  1060  	"status": "success"
  1061    }`
  1062  	vectorStringEmptyResult = `{
  1063  	"data": {
  1064  	  ` + statsResultString + `
  1065  	  "resultType": "vector",
  1066  	  "result": []
  1067  	},
  1068  	"status": "success"
  1069    }`
  1070  
  1071  	sampleStreams = []queryrangebase.SampleStream{
  1072  		{
  1073  			Labels:  []logproto.LabelAdapter{{Name: "filename", Value: "/var/hostlog/apport.log"}, {Name: "job", Value: "varlogs"}},
  1074  			Samples: []logproto.LegacySample{{Value: 0.013333333333333334, TimestampMs: 1568404331324}},
  1075  		},
  1076  		{
  1077  			Labels:  []logproto.LabelAdapter{{Name: "filename", Value: "/var/hostlog/syslog"}, {Name: "job", Value: "varlogs"}},
  1078  			Samples: []logproto.LegacySample{{Value: 3.45, TimestampMs: 1568404331324}, {Value: 4.45, TimestampMs: 1568404331339}},
  1079  		},
  1080  	}
  1081  	streamsString = `{
  1082  		"status": "success",
  1083  		"data": {
  1084  			` + statsResultString + `
  1085  			"resultType": "streams",
  1086  			"result": [
  1087  				{
  1088  					"stream": {
  1089  						"test": "test"
  1090  					},
  1091  					"values":[
  1092  						[ "123456789012345", "super line" ]
  1093  					]
  1094  				},
  1095  				{
  1096  					"stream": {
  1097  						"test": "test2"
  1098  					},
  1099  					"values":[
  1100  						[ "123456789012346", "super line2" ]
  1101  					]
  1102  				}
  1103  			]
  1104  		}
  1105  	}`
  1106  	streamsStringLegacy = `{
  1107  		` + statsResultString + `"streams":[{"labels":"{test=\"test\"}","entries":[{"ts":"1970-01-02T10:17:36.789012345Z","line":"super line"}]},{"labels":"{test=\"test2\"}","entries":[{"ts":"1970-01-02T10:17:36.789012346Z","line":"super line2"}]}]}`
  1108  	logStreams = []logproto.Stream{
  1109  		{
  1110  			Labels: `{test="test"}`,
  1111  			Entries: []logproto.Entry{
  1112  				{
  1113  					Line:      "super line",
  1114  					Timestamp: time.Unix(0, 123456789012345).UTC(),
  1115  				},
  1116  			},
  1117  		},
  1118  		{
  1119  			Labels: `{test="test2"}`,
  1120  			Entries: []logproto.Entry{
  1121  				{
  1122  					Line:      "super line2",
  1123  					Timestamp: time.Unix(0, 123456789012346).UTC(),
  1124  				},
  1125  			},
  1126  		},
  1127  	}
  1128  	seriesString = `{
  1129  		"status": "success",
  1130  		"data": [
  1131  			{"filename": "/var/hostlog/apport.log", "job": "varlogs"},
  1132  			{"filename": "/var/hostlog/test.log", "job": "varlogs"}
  1133  		]
  1134  	}`
  1135  	seriesData = []logproto.SeriesIdentifier{
  1136  		{
  1137  			Labels: map[string]string{"filename": "/var/hostlog/apport.log", "job": "varlogs"},
  1138  		},
  1139  		{
  1140  			Labels: map[string]string{"filename": "/var/hostlog/test.log", "job": "varlogs"},
  1141  		},
  1142  	}
  1143  	labelsString = `{
  1144  		"status": "success",
  1145  		"data": [
  1146  			"foo",
  1147  			"bar"
  1148  		]
  1149  	}`
  1150  	labelsLegacyString = `{
  1151  		"values": [
  1152  			"foo",
  1153  			"bar"
  1154  		]
  1155  	}`
  1156  	indexStatsString = `{
  1157  		"streams": 1,
  1158  		"chunks": 2,
  1159  		"bytes": 3,
  1160  		"entries": 4
  1161  		}`
  1162  	labelsData  = []string{"foo", "bar"}
  1163  	statsResult = stats.Result{
  1164  		Summary: stats.Summary{
  1165  			BytesProcessedPerSecond: 20,
  1166  			QueueTime:               21,
  1167  			ExecTime:                22,
  1168  			LinesProcessedPerSecond: 23,
  1169  			TotalBytesProcessed:     24,
  1170  			Subqueries:              1,
  1171  			TotalLinesProcessed:     25,
  1172  			TotalEntriesReturned:    10,
  1173  		},
  1174  		Querier: stats.Querier{
  1175  			Store: stats.Store{
  1176  				Chunk: stats.Chunk{
  1177  					CompressedBytes:   11,
  1178  					DecompressedBytes: 12,
  1179  					DecompressedLines: 13,
  1180  					HeadChunkBytes:    14,
  1181  					HeadChunkLines:    15,
  1182  					TotalDuplicates:   19,
  1183  				},
  1184  				ChunksDownloadTime:    16,
  1185  				TotalChunksRef:        17,
  1186  				TotalChunksDownloaded: 18,
  1187  			},
  1188  		},
  1189  
  1190  		Ingester: stats.Ingester{
  1191  			Store: stats.Store{
  1192  				Chunk: stats.Chunk{
  1193  					CompressedBytes:   1,
  1194  					DecompressedBytes: 2,
  1195  					DecompressedLines: 3,
  1196  					HeadChunkBytes:    4,
  1197  					HeadChunkLines:    5,
  1198  					TotalDuplicates:   8,
  1199  				},
  1200  			},
  1201  			TotalBatches:       6,
  1202  			TotalChunksMatched: 7,
  1203  			TotalLinesSent:     9,
  1204  			TotalReached:       10,
  1205  		},
  1206  
  1207  		Caches: stats.Caches{
  1208  			Chunk:  stats.Cache{},
  1209  			Index:  stats.Cache{},
  1210  			Result: stats.Cache{},
  1211  		},
  1212  	}
  1213  )
  1214  
  1215  func BenchmarkResponseMerge(b *testing.B) {
  1216  	const (
  1217  		resps         = 10
  1218  		streams       = 100
  1219  		logsPerStream = 1000
  1220  	)
  1221  
  1222  	for _, tc := range []struct {
  1223  		desc  string
  1224  		limit uint32
  1225  		fn    func([]*LokiResponse, uint32, logproto.Direction) []logproto.Stream
  1226  	}{
  1227  		{
  1228  			"mergeStreams unlimited",
  1229  			uint32(streams * logsPerStream),
  1230  			mergeStreams,
  1231  		},
  1232  		{
  1233  			"mergeOrderedNonOverlappingStreams unlimited",
  1234  			uint32(streams * logsPerStream),
  1235  			mergeOrderedNonOverlappingStreams,
  1236  		},
  1237  		{
  1238  			"mergeStreams limited",
  1239  			uint32(streams*logsPerStream - 1),
  1240  			mergeStreams,
  1241  		},
  1242  		{
  1243  			"mergeOrderedNonOverlappingStreams limited",
  1244  			uint32(streams*logsPerStream - 1),
  1245  			mergeOrderedNonOverlappingStreams,
  1246  		},
  1247  	} {
  1248  		input := mkResps(resps, streams, logsPerStream, logproto.FORWARD)
  1249  		b.Run(tc.desc, func(b *testing.B) {
  1250  			for n := 0; n < b.N; n++ {
  1251  				tc.fn(input, tc.limit, logproto.FORWARD)
  1252  			}
  1253  		})
  1254  	}
  1255  }
  1256  
  1257  func mkResps(nResps, nStreams, nLogs int, direction logproto.Direction) (resps []*LokiResponse) {
  1258  	for i := 0; i < nResps; i++ {
  1259  		r := &LokiResponse{}
  1260  		for j := 0; j < nStreams; j++ {
  1261  			stream := logproto.Stream{
  1262  				Labels: fmt.Sprintf(`{foo="%d"}`, j),
  1263  			}
  1264  			// split nLogs evenly across all responses
  1265  			for k := i * (nLogs / nResps); k < (i+1)*(nLogs/nResps); k++ {
  1266  				stream.Entries = append(stream.Entries, logproto.Entry{
  1267  					Timestamp: time.Unix(int64(k), 0),
  1268  					Line:      fmt.Sprintf("%d", k),
  1269  				})
  1270  
  1271  				if direction == logproto.BACKWARD {
  1272  					for x, y := 0, len(stream.Entries)-1; x < len(stream.Entries)/2; x, y = x+1, y-1 {
  1273  						stream.Entries[x], stream.Entries[y] = stream.Entries[y], stream.Entries[x]
  1274  					}
  1275  				}
  1276  			}
  1277  			r.Data.Result = append(r.Data.Result, stream)
  1278  		}
  1279  		resps = append(resps, r)
  1280  	}
  1281  	return resps
  1282  }
  1283  
  1284  type buffer struct {
  1285  	buff []byte
  1286  	io.ReadCloser
  1287  }
  1288  
  1289  func (b *buffer) Bytes() []byte {
  1290  	return b.buff
  1291  }
  1292  
  1293  func Benchmark_CodecDecodeLogs(b *testing.B) {
  1294  	ctx := context.Background()
  1295  	resp, err := LokiCodec.EncodeResponse(ctx, &LokiResponse{
  1296  		Status:    loghttp.QueryStatusSuccess,
  1297  		Direction: logproto.BACKWARD,
  1298  		Version:   uint32(loghttp.VersionV1),
  1299  		Limit:     1000,
  1300  		Data: LokiData{
  1301  			ResultType: loghttp.ResultTypeStream,
  1302  			Result:     generateStream(),
  1303  		},
  1304  	})
  1305  	require.Nil(b, err)
  1306  
  1307  	buf, err := io.ReadAll(resp.Body)
  1308  	require.Nil(b, err)
  1309  	reader := bytes.NewReader(buf)
  1310  	resp.Body = &buffer{
  1311  		ReadCloser: ioutil.NopCloser(reader),
  1312  		buff:       buf,
  1313  	}
  1314  	b.ResetTimer()
  1315  	b.ReportAllocs()
  1316  
  1317  	for n := 0; n < b.N; n++ {
  1318  		_, _ = reader.Seek(0, io.SeekStart)
  1319  		result, err := LokiCodec.DecodeResponse(ctx, resp, &LokiRequest{
  1320  			Limit:     100,
  1321  			StartTs:   start,
  1322  			EndTs:     end,
  1323  			Direction: logproto.BACKWARD,
  1324  			Path:      "/loki/api/v1/query_range",
  1325  		})
  1326  		require.Nil(b, err)
  1327  		require.NotNil(b, result)
  1328  	}
  1329  }
  1330  
  1331  func Benchmark_CodecDecodeSamples(b *testing.B) {
  1332  	ctx := context.Background()
  1333  	resp, err := LokiCodec.EncodeResponse(ctx, &LokiPromResponse{
  1334  		Response: &queryrangebase.PrometheusResponse{
  1335  			Status: loghttp.QueryStatusSuccess,
  1336  			Data: queryrangebase.PrometheusData{
  1337  				ResultType: loghttp.ResultTypeMatrix,
  1338  				Result:     generateMatrix(),
  1339  			},
  1340  		},
  1341  	})
  1342  	require.Nil(b, err)
  1343  
  1344  	buf, err := io.ReadAll(resp.Body)
  1345  	require.Nil(b, err)
  1346  	reader := bytes.NewReader(buf)
  1347  	resp.Body = ioutil.NopCloser(reader)
  1348  	b.ResetTimer()
  1349  	b.ReportAllocs()
  1350  
  1351  	for n := 0; n < b.N; n++ {
  1352  		_, _ = reader.Seek(0, io.SeekStart)
  1353  		result, err := LokiCodec.DecodeResponse(ctx, resp, &LokiRequest{
  1354  			Limit:     100,
  1355  			StartTs:   start,
  1356  			EndTs:     end,
  1357  			Direction: logproto.BACKWARD,
  1358  			Path:      "/loki/api/v1/query_range",
  1359  		})
  1360  		require.Nil(b, err)
  1361  		require.NotNil(b, result)
  1362  	}
  1363  }
  1364  
  1365  func generateMatrix() (res []queryrangebase.SampleStream) {
  1366  	for i := 0; i < 100; i++ {
  1367  		s := queryrangebase.SampleStream{
  1368  			Labels:  []logproto.LabelAdapter{},
  1369  			Samples: []logproto.LegacySample{},
  1370  		}
  1371  		for j := 0; j < 1000; j++ {
  1372  			s.Samples = append(s.Samples, logproto.LegacySample{
  1373  				Value:       float64(j),
  1374  				TimestampMs: int64(j),
  1375  			})
  1376  		}
  1377  		res = append(res, s)
  1378  	}
  1379  	return res
  1380  }
  1381  
  1382  func generateStream() (res []logproto.Stream) {
  1383  	for i := 0; i < 1000; i++ {
  1384  		s := logproto.Stream{
  1385  			Labels: fmt.Sprintf(`{foo="%d", buzz="bar", cluster="us-central2", namespace="loki-dev", container="query-frontend"}`, i),
  1386  		}
  1387  		for j := 0; j < 10; j++ {
  1388  			s.Entries = append(s.Entries, logproto.Entry{Timestamp: time.Now(), Line: fmt.Sprintf("%d\nyolo", j)})
  1389  		}
  1390  		res = append(res, s)
  1391  	}
  1392  	return res
  1393  }