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 }