github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/util/marshal/marshal_test.go (about)

     1  package marshal
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"testing"
     7  	"time"
     8  
     9  	json "github.com/json-iterator/go"
    10  	"github.com/prometheus/prometheus/model/labels"
    11  	"github.com/prometheus/prometheus/promql"
    12  	"github.com/prometheus/prometheus/promql/parser"
    13  	"github.com/stretchr/testify/require"
    14  
    15  	"github.com/grafana/loki/pkg/loghttp"
    16  	legacy "github.com/grafana/loki/pkg/loghttp/legacy"
    17  	"github.com/grafana/loki/pkg/logproto"
    18  	"github.com/grafana/loki/pkg/logqlmodel"
    19  )
    20  
    21  // covers responses from /loki/api/v1/query_range and /loki/api/v1/query
    22  var queryTests = []struct {
    23  	actual   parser.Value
    24  	expected string
    25  }{
    26  	{
    27  		logqlmodel.Streams{
    28  			logproto.Stream{
    29  				Entries: []logproto.Entry{
    30  					{
    31  						Timestamp: time.Unix(0, 123456789012345),
    32  						Line:      "super line",
    33  					},
    34  				},
    35  				Labels: `{test="test"}`,
    36  			},
    37  		},
    38  		`{
    39  			"status": "success",
    40  			"data": {
    41  				"resultType": "streams",
    42  				"result": [
    43  					{
    44  						"stream": {
    45  							"test": "test"
    46  						},
    47  						"values":[
    48  							[ "123456789012345", "super line" ]
    49  						]
    50  					}
    51  				],
    52  				"stats" : {
    53  					"ingester" : {
    54  						"store": {
    55  							"chunksDownloadTime": 0,
    56  							"totalChunksRef": 0,
    57  							"totalChunksDownloaded": 0,
    58  							"chunk" :{
    59  								"compressedBytes": 0,
    60  								"decompressedBytes": 0,
    61  								"decompressedLines": 0,
    62  								"headChunkBytes": 0,
    63  								"headChunkLines": 0,
    64  								"totalDuplicates": 0
    65  							}
    66  						},
    67  						"totalBatches": 0,
    68  						"totalChunksMatched": 0,
    69  						"totalLinesSent": 0,
    70  						"totalReached": 0
    71  					},
    72  					"querier": {
    73  						"store": {
    74  							"chunksDownloadTime": 0,
    75  							"totalChunksRef": 0,
    76  							"totalChunksDownloaded": 0,
    77  							"chunk" :{
    78  								"compressedBytes": 0,
    79  								"decompressedBytes": 0,
    80  								"decompressedLines": 0,
    81  								"headChunkBytes": 0,
    82  								"headChunkLines": 0,
    83  								"totalDuplicates": 0
    84  							}
    85  						}
    86  					},
    87  					"cache": {
    88  						"chunk": {
    89  							"entriesFound": 0,
    90  							"entriesRequested": 0,
    91  							"entriesStored": 0,
    92  							"bytesReceived": 0,
    93  							"bytesSent": 0,
    94  							"requests": 0
    95  						},
    96  						"index": {
    97  							"entriesFound": 0,
    98  							"entriesRequested": 0,
    99  							"entriesStored": 0,
   100  							"bytesReceived": 0,
   101  							"bytesSent": 0,
   102  							"requests": 0
   103  						},
   104  						"result": {
   105  							"entriesFound": 0,
   106  							"entriesRequested": 0,
   107  							"entriesStored": 0,
   108  							"bytesReceived": 0,
   109  							"bytesSent": 0,
   110  							"requests": 0
   111  						}
   112  					},
   113  					"summary": {
   114  						"bytesProcessedPerSecond": 0,
   115  						"execTime": 0,
   116  						"linesProcessedPerSecond": 0,
   117  						"queueTime": 0,
   118  						"subqueries": 0,
   119  						"totalBytesProcessed":0,
   120                                                  "totalEntriesReturned":0,
   121  						"totalLinesProcessed":0
   122  					}
   123  				}
   124  			}
   125  		}`,
   126  	},
   127  	// vector test
   128  	{
   129  		promql.Vector{
   130  			{
   131  				Point: promql.Point{
   132  					T: 1568404331324,
   133  					V: 0.013333333333333334,
   134  				},
   135  				Metric: []labels.Label{
   136  					{
   137  						Name:  "filename",
   138  						Value: `/var/hostlog/apport.log`,
   139  					},
   140  					{
   141  						Name:  "job",
   142  						Value: "varlogs",
   143  					},
   144  				},
   145  			},
   146  			{
   147  				Point: promql.Point{
   148  					T: 1568404331324,
   149  					V: 3.45,
   150  				},
   151  				Metric: []labels.Label{
   152  					{
   153  						Name:  "filename",
   154  						Value: `/var/hostlog/syslog`,
   155  					},
   156  					{
   157  						Name:  "job",
   158  						Value: "varlogs",
   159  					},
   160  				},
   161  			},
   162  		},
   163  		`{
   164  			"data": {
   165  			  "resultType": "vector",
   166  			  "result": [
   167  				{
   168  				  "metric": {
   169  					"filename": "\/var\/hostlog\/apport.log",
   170  					"job": "varlogs"
   171  				  },
   172  				  "value": [
   173  					1568404331.324,
   174  					"0.013333333333333334"
   175  				  ]
   176  				},
   177  				{
   178  				  "metric": {
   179  					"filename": "\/var\/hostlog\/syslog",
   180  					"job": "varlogs"
   181  				  },
   182  				  "value": [
   183  					1568404331.324,
   184  					"3.45"
   185  				  ]
   186  				}
   187  			  ],
   188  			  "stats" : {
   189  				"ingester" : {
   190  					"store": {
   191  						"chunksDownloadTime": 0,
   192  						"totalChunksRef": 0,
   193  						"totalChunksDownloaded": 0,
   194  						"chunk" :{
   195  							"compressedBytes": 0,
   196  							"decompressedBytes": 0,
   197  							"decompressedLines": 0,
   198  							"headChunkBytes": 0,
   199  							"headChunkLines": 0,
   200  							"totalDuplicates": 0
   201  						}
   202  					},
   203  					"totalBatches": 0,
   204  					"totalChunksMatched": 0,
   205  					"totalLinesSent": 0,
   206  					"totalReached": 0
   207  				},
   208  				"querier": {
   209  					"store": {
   210  						"chunksDownloadTime": 0,
   211  						"totalChunksRef": 0,
   212  						"totalChunksDownloaded": 0,
   213  						"chunk" :{
   214  							"compressedBytes": 0,
   215  							"decompressedBytes": 0,
   216  							"decompressedLines": 0,
   217  							"headChunkBytes": 0,
   218  							"headChunkLines": 0,
   219  							"totalDuplicates": 0
   220  						}
   221  					}
   222  				},
   223  				"cache": {
   224  					"chunk": {
   225  						"entriesFound": 0,
   226  						"entriesRequested": 0,
   227  						"entriesStored": 0,
   228  						"bytesReceived": 0,
   229  						"bytesSent": 0,
   230  						"requests": 0
   231  					},
   232  					"index": {
   233  						"entriesFound": 0,
   234  						"entriesRequested": 0,
   235  						"entriesStored": 0,
   236  						"bytesReceived": 0,
   237  						"bytesSent": 0,
   238  						"requests": 0
   239  					},
   240  					"result": {
   241  						"entriesFound": 0,
   242  						"entriesRequested": 0,
   243  						"entriesStored": 0,
   244  						"bytesReceived": 0,
   245  						"bytesSent": 0,
   246  						"requests": 0
   247  					}
   248  				},
   249  				"summary": {
   250  					"bytesProcessedPerSecond": 0,
   251  					"execTime": 0,
   252  					"linesProcessedPerSecond": 0,
   253  					"queueTime": 0,
   254  					"subqueries": 0,
   255  					"totalBytesProcessed":0,
   256                                          "totalEntriesReturned":0,
   257  					"totalLinesProcessed":0
   258  				}
   259  			  }
   260  			},
   261  			"status": "success"
   262  		  }`,
   263  	},
   264  	// matrix test
   265  	{
   266  		promql.Matrix{
   267  			{
   268  				Points: []promql.Point{
   269  					{
   270  						T: 1568404331324,
   271  						V: 0.013333333333333334,
   272  					},
   273  				},
   274  				Metric: []labels.Label{
   275  					{
   276  						Name:  "filename",
   277  						Value: `/var/hostlog/apport.log`,
   278  					},
   279  					{
   280  						Name:  "job",
   281  						Value: "varlogs",
   282  					},
   283  				},
   284  			},
   285  			{
   286  				Points: []promql.Point{
   287  					{
   288  						T: 1568404331324,
   289  						V: 3.45,
   290  					},
   291  					{
   292  						T: 1568404331339,
   293  						V: 4.45,
   294  					},
   295  				},
   296  				Metric: []labels.Label{
   297  					{
   298  						Name:  "filename",
   299  						Value: `/var/hostlog/syslog`,
   300  					},
   301  					{
   302  						Name:  "job",
   303  						Value: "varlogs",
   304  					},
   305  				},
   306  			},
   307  		},
   308  		`{
   309  			"data": {
   310  			  "resultType": "matrix",
   311  			  "result": [
   312  				{
   313  				  "metric": {
   314  					"filename": "\/var\/hostlog\/apport.log",
   315  					"job": "varlogs"
   316  				  },
   317  				  "values": [
   318  					  [
   319  						1568404331.324,
   320  						"0.013333333333333334"
   321  					  ]
   322  					]
   323  				},
   324  				{
   325  				  "metric": {
   326  					"filename": "\/var\/hostlog\/syslog",
   327  					"job": "varlogs"
   328  				  },
   329  				  "values": [
   330  						[
   331  							1568404331.324,
   332  							"3.45"
   333  						],
   334  						[
   335  							1568404331.339,
   336  							"4.45"
   337  						]
   338  					]
   339  				}
   340  			  ],
   341  			  "stats" : {
   342  				"ingester" : {
   343  					"store": {
   344  						"chunksDownloadTime": 0,
   345  						"totalChunksRef": 0,
   346  						"totalChunksDownloaded": 0,
   347  						"chunk" :{
   348  							"compressedBytes": 0,
   349  							"decompressedBytes": 0,
   350  							"decompressedLines": 0,
   351  							"headChunkBytes": 0,
   352  							"headChunkLines": 0,
   353  							"totalDuplicates": 0
   354  						}
   355  					},
   356  					"totalBatches": 0,
   357  					"totalChunksMatched": 0,
   358  					"totalLinesSent": 0,
   359  					"totalReached": 0
   360  				},
   361  				"querier": {
   362  					"store": {
   363  						"chunksDownloadTime": 0,
   364  						"totalChunksRef": 0,
   365  						"totalChunksDownloaded": 0,
   366  						"chunk" :{
   367  							"compressedBytes": 0,
   368  							"decompressedBytes": 0,
   369  							"decompressedLines": 0,
   370  							"headChunkBytes": 0,
   371  							"headChunkLines": 0,
   372  							"totalDuplicates": 0
   373  						}
   374  					}
   375  				},
   376  				"cache": {
   377  					"chunk": {
   378  						"entriesFound": 0,
   379  						"entriesRequested": 0,
   380  						"entriesStored": 0,
   381  						"bytesReceived": 0,
   382  						"bytesSent": 0,
   383  						"requests": 0
   384  					},
   385  					"index": {
   386  						"entriesFound": 0,
   387  						"entriesRequested": 0,
   388  						"entriesStored": 0,
   389  						"bytesReceived": 0,
   390  						"bytesSent": 0,
   391  						"requests": 0
   392  					},
   393  					"result": {
   394  						"entriesFound": 0,
   395  						"entriesRequested": 0,
   396  						"entriesStored": 0,
   397  						"bytesReceived": 0,
   398  						"bytesSent": 0,
   399  						"requests": 0
   400  					}
   401  				},
   402  				"summary": {
   403  					"bytesProcessedPerSecond": 0,
   404  					"execTime": 0,
   405  					"linesProcessedPerSecond": 0,
   406  					"queueTime": 0,
   407  					"subqueries": 0,
   408  					"totalBytesProcessed":0,
   409                                          "totalEntriesReturned":0,
   410  					"totalLinesProcessed":0
   411  				}
   412  			  }
   413  			},
   414  			"status": "success"
   415  		  }`,
   416  	},
   417  }
   418  
   419  // covers responses from /loki/api/v1/labels and /loki/api/v1/label/{name}/values
   420  var labelTests = []struct {
   421  	actual   logproto.LabelResponse
   422  	expected string
   423  }{
   424  	{
   425  		logproto.LabelResponse{
   426  			Values: []string{
   427  				"label1",
   428  				"test",
   429  				"value",
   430  			},
   431  		},
   432  		`{"status": "success", "data": ["label1", "test", "value"]}`,
   433  	},
   434  }
   435  
   436  // covers responses from /loki/api/v1/tail
   437  var tailTests = []struct {
   438  	actual   legacy.TailResponse
   439  	expected string
   440  }{
   441  	{
   442  		legacy.TailResponse{
   443  			Streams: []logproto.Stream{
   444  				{
   445  					Entries: []logproto.Entry{
   446  						{
   447  							Timestamp: time.Unix(0, 123456789012345),
   448  							Line:      "super line",
   449  						},
   450  					},
   451  					Labels: "{test=\"test\"}",
   452  				},
   453  			},
   454  			DroppedEntries: []legacy.DroppedEntry{
   455  				{
   456  					Timestamp: time.Unix(0, 123456789022345),
   457  					Labels:    "{test=\"test\"}",
   458  				},
   459  			},
   460  		},
   461  		`{
   462  			"streams": [
   463  				{
   464  					"stream": {
   465  						"test": "test"
   466  					},
   467  					"values":[
   468  						[ "123456789012345", "super line" ]
   469  					]
   470  				}
   471  			],
   472  			"dropped_entries": [
   473  				{
   474  					"timestamp": "123456789022345",
   475  					"labels": {
   476  						"test": "test"
   477  					}
   478  				}
   479  			]
   480  		}`,
   481  	},
   482  }
   483  
   484  func Test_WriteQueryResponseJSON(t *testing.T) {
   485  	for i, queryTest := range queryTests {
   486  		var b bytes.Buffer
   487  		err := WriteQueryResponseJSON(logqlmodel.Result{Data: queryTest.actual}, &b)
   488  		require.NoError(t, err)
   489  
   490  		testJSONBytesEqual(t, []byte(queryTest.expected), b.Bytes(), "Query Test %d failed", i)
   491  	}
   492  }
   493  
   494  func Test_WriteLabelResponseJSON(t *testing.T) {
   495  	for i, labelTest := range labelTests {
   496  		var b bytes.Buffer
   497  		err := WriteLabelResponseJSON(labelTest.actual, &b)
   498  		require.NoError(t, err)
   499  
   500  		testJSONBytesEqual(t, []byte(labelTest.expected), b.Bytes(), "Label Test %d failed", i)
   501  	}
   502  }
   503  
   504  func Test_MarshalTailResponse(t *testing.T) {
   505  	for i, tailTest := range tailTests {
   506  		// convert logproto to model objects
   507  		model, err := NewTailResponse(tailTest.actual)
   508  		require.NoError(t, err)
   509  
   510  		// marshal model object
   511  		bytes, err := json.Marshal(model)
   512  		require.NoError(t, err)
   513  
   514  		testJSONBytesEqual(t, []byte(tailTest.expected), bytes, "Tail Test %d failed", i)
   515  	}
   516  }
   517  
   518  func Test_QueryResponseMarshalLoop(t *testing.T) {
   519  	for i, queryTest := range queryTests {
   520  		value, err := NewResultValue(queryTest.actual)
   521  		require.NoError(t, err)
   522  
   523  		q := loghttp.QueryResponse{
   524  			Status: "success",
   525  			Data: loghttp.QueryResponseData{
   526  				ResultType: value.Type(),
   527  				Result:     value,
   528  			},
   529  		}
   530  		var expected loghttp.QueryResponse
   531  
   532  		bytes, err := json.Marshal(q)
   533  		require.NoError(t, err)
   534  
   535  		err = json.Unmarshal(bytes, &expected)
   536  		require.NoError(t, err)
   537  
   538  		require.Equalf(t, q, expected, "Query Marshal Loop %d failed", i)
   539  	}
   540  }
   541  
   542  func Test_QueryResponseResultType(t *testing.T) {
   543  	for i, queryTest := range queryTests {
   544  		value, err := NewResultValue(queryTest.actual)
   545  		require.NoError(t, err)
   546  
   547  		switch value.Type() {
   548  		case loghttp.ResultTypeStream:
   549  			require.IsTypef(t, loghttp.Streams{}, value, "Incorrect type %d", i)
   550  		case loghttp.ResultTypeMatrix:
   551  			require.IsTypef(t, loghttp.Matrix{}, value, "Incorrect type %d", i)
   552  		case loghttp.ResultTypeVector:
   553  			require.IsTypef(t, loghttp.Vector{}, value, "Incorrect type %d", i)
   554  		default:
   555  			require.Fail(t, "Unknown result type %s", value.Type())
   556  		}
   557  	}
   558  }
   559  
   560  func Test_LabelResponseMarshalLoop(t *testing.T) {
   561  	for i, labelTest := range labelTests {
   562  		var r loghttp.LabelResponse
   563  
   564  		err := json.Unmarshal([]byte(labelTest.expected), &r)
   565  		require.NoError(t, err)
   566  
   567  		jsonOut, err := json.Marshal(r)
   568  		require.NoError(t, err)
   569  
   570  		testJSONBytesEqual(t, []byte(labelTest.expected), jsonOut, "Label Marshal Loop %d failed", i)
   571  	}
   572  }
   573  
   574  func Test_TailResponseMarshalLoop(t *testing.T) {
   575  	for i, tailTest := range tailTests {
   576  		var r loghttp.TailResponse
   577  
   578  		err := json.Unmarshal([]byte(tailTest.expected), &r)
   579  		require.NoError(t, err)
   580  
   581  		jsonOut, err := json.Marshal(r)
   582  		require.NoError(t, err)
   583  
   584  		testJSONBytesEqual(t, []byte(tailTest.expected), jsonOut, "Tail Marshal Loop %d failed", i)
   585  	}
   586  }
   587  
   588  func Test_WriteSeriesResponseJSON(t *testing.T) {
   589  	for i, tc := range []struct {
   590  		input    logproto.SeriesResponse
   591  		expected string
   592  	}{
   593  		{
   594  			logproto.SeriesResponse{
   595  				Series: []logproto.SeriesIdentifier{
   596  					{
   597  						Labels: map[string]string{
   598  							"a": "1",
   599  							"b": "2",
   600  						},
   601  					},
   602  					{
   603  						Labels: map[string]string{
   604  							"c": "3",
   605  							"d": "4",
   606  						},
   607  					},
   608  				},
   609  			},
   610  			`{"status":"success","data":[{"a":"1","b":"2"},{"c":"3","d":"4"}]}`,
   611  		},
   612  	} {
   613  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
   614  			var b bytes.Buffer
   615  			err := WriteSeriesResponseJSON(tc.input, &b)
   616  			require.NoError(t, err)
   617  
   618  			testJSONBytesEqual(t, []byte(tc.expected), b.Bytes(), "Label Test %d failed", i)
   619  		})
   620  	}
   621  }
   622  
   623  func testJSONBytesEqual(t *testing.T, expected []byte, actual []byte, msg string, args ...interface{}) {
   624  	var expectedValue map[string]interface{}
   625  	err := json.Unmarshal(expected, &expectedValue)
   626  	require.NoError(t, err)
   627  
   628  	var actualValue map[string]interface{}
   629  	err = json.Unmarshal(actual, &actualValue)
   630  	require.NoError(t, err)
   631  
   632  	require.Equalf(t, expectedValue, actualValue, msg, args)
   633  }
   634  
   635  func Benchmark_Encode(b *testing.B) {
   636  	buf := bytes.NewBuffer(nil)
   637  
   638  	for n := 0; n < b.N; n++ {
   639  		for _, queryTest := range queryTests {
   640  			require.NoError(b, WriteQueryResponseJSON(logqlmodel.Result{Data: queryTest.actual}, buf))
   641  		}
   642  	}
   643  }
   644  
   645  type WebsocketWriterFunc func(int, []byte) error
   646  
   647  func (w WebsocketWriterFunc) WriteMessage(t int, d []byte) error { return w(t, d) }
   648  
   649  func Test_WriteTailResponseJSON(t *testing.T) {
   650  	require.NoError(t,
   651  		WriteTailResponseJSON(legacy.TailResponse{
   652  			Streams: []logproto.Stream{
   653  				{Labels: `{app="foo"}`, Entries: []logproto.Entry{{Timestamp: time.Unix(0, 1), Line: `foobar`}}},
   654  			},
   655  			DroppedEntries: []legacy.DroppedEntry{
   656  				{Timestamp: time.Unix(0, 2), Labels: `{app="dropped"}`},
   657  			},
   658  		},
   659  			WebsocketWriterFunc(func(i int, b []byte) error {
   660  				require.Equal(t, `{"streams":[{"stream":{"app":"foo"},"values":[["1","foobar"]]}],"dropped_entries":[{"timestamp":"2","labels":{"app":"dropped"}}]}`, string(b))
   661  				return nil
   662  			}),
   663  		),
   664  	)
   665  }