github.com/GuanceCloud/cliutils@v1.1.21/lineproto/lineproto_test.go (about) 1 // Unless explicitly stated otherwise all files in this repository are licensed 2 // under the MIT License. 3 // This product includes software developed at Guance Cloud (https://www.guance.com/). 4 // Copyright 2021-present Guance, Inc. 5 6 package lineproto 7 8 import ( 9 "fmt" 10 "math" 11 "testing" 12 "time" 13 14 "github.com/GuanceCloud/cliutils" 15 "github.com/GuanceCloud/cliutils/testutil" 16 "github.com/influxdata/influxdb1-client/models" 17 influxdb "github.com/influxdata/influxdb1-client/v2" 18 ) 19 20 func parseLineProto(t *testing.T, data []byte, precision string) (models.Points, error) { 21 t.Helper() 22 23 if len(data) == 0 { 24 return nil, fmt.Errorf("empty data") 25 } 26 27 return models.ParsePointsWithPrecision(data, time.Now().UTC(), precision) 28 } 29 30 func TestAdjustTags(t *testing.T) { 31 cases := []struct { 32 tags map[string]string 33 }{} 34 35 _ = cases 36 } 37 38 func TestAdjustKV(t *testing.T) { 39 cases := []struct { 40 name, x, y string 41 }{ 42 { 43 name: "x with trailling backslash", 44 x: "x\\", 45 y: "x", 46 }, 47 48 { 49 name: "x with line break", 50 x: ` 51 x 52 def`, 53 y: " x def", 54 }, 55 } 56 57 for _, tc := range cases { 58 t.Run(tc.name, func(t *testing.T) { 59 testutil.Equals(t, tc.y, adjustKV(tc.x)) 60 }) 61 } 62 } 63 64 func TestMakeLineProtoPointWithWarnings(t *testing.T) { 65 cases := []struct { 66 tname string // test name 67 name string 68 tags map[string]string 69 fields map[string]interface{} 70 ts time.Time 71 opt *Option 72 expect string 73 warnTypes []string 74 fail bool 75 }{ 76 { 77 tname: `64k-field-value-length`, 78 name: "some", 79 fields: map[string]interface{}{ 80 "key": func() string { 81 const str = "1234567890" 82 var out string 83 for { 84 out += str 85 if len(out) > 64*1024 { 86 break 87 } 88 } 89 return out 90 }(), 91 }, 92 opt: func() *Option { 93 opt := NewDefaultOption() 94 opt.MaxFieldValueLen = 0 95 opt.Time = time.Unix(0, 123) 96 return opt 97 }(), 98 99 expect: fmt.Sprintf(`some key="%s" 123`, func() string { 100 const str = "1234567890" 101 var out string 102 for { 103 out += str 104 if len(out) > 64*1024 { 105 break 106 } 107 } 108 return out 109 }()), 110 }, 111 112 { 113 tname: `max-field-value-length`, 114 name: "some", 115 fields: map[string]interface{}{"key": "too-long-field-value-123"}, 116 opt: func() *Option { 117 opt := NewDefaultOption() 118 opt.MaxFieldValueLen = 2 119 opt.Time = time.Unix(0, 123) 120 return opt 121 }(), 122 expect: "some key=\"to\" 123", 123 warnTypes: []string{WarnMaxFieldValueLen}, 124 }, 125 126 { 127 tname: `max-field-key-length`, 128 name: "some", 129 fields: map[string]interface{}{"too-long-field-key": "123"}, 130 opt: func() *Option { 131 opt := NewDefaultOption() 132 opt.MaxFieldKeyLen = 2 133 opt.Time = time.Unix(0, 123) 134 return opt 135 }(), 136 expect: "some to=\"123\" 123", 137 warnTypes: []string{WarnMaxFieldKeyLen}, 138 }, 139 140 { 141 tname: `max-tag-value-length`, 142 name: "some", 143 fields: map[string]interface{}{"f1": 1}, 144 tags: map[string]string{"key": "too-long-tag-value-123"}, 145 opt: func() *Option { 146 opt := NewDefaultOption() 147 opt.MaxTagValueLen = 2 148 opt.Time = time.Unix(0, 123) 149 return opt 150 }(), 151 warnTypes: []string{WarnMaxTagValueLen}, 152 expect: "some,key=to f1=1i 123", 153 }, 154 { 155 tname: `disable-string-field`, 156 name: "some", 157 fields: map[string]interface{}{"f1": 1, "f2": "this is a string"}, 158 tags: map[string]string{"key": "string"}, 159 opt: func() *Option { 160 opt := NewDefaultOption() 161 opt.DisableStringField = true 162 opt.Time = time.Unix(0, 123) 163 return opt 164 }(), 165 warnTypes: []string{WarnInvalidFieldValueType}, 166 expect: "some,key=string f1=1i 123", 167 }, 168 169 { 170 tname: `max tag key length`, 171 name: "some", 172 fields: map[string]interface{}{"f1": 1}, 173 tags: map[string]string{"too-long-tag-key": "123"}, 174 opt: func() *Option { 175 opt := NewDefaultOption() 176 opt.MaxTagKeyLen = 2 177 opt.Time = time.Unix(0, 123) 178 return opt 179 }(), 180 warnTypes: []string{WarnMaxTagKeyLen}, 181 expect: "some,to=123 f1=1i 123", 182 }, 183 184 { 185 tname: `empty measurement name`, 186 name: "", // empty 187 fields: map[string]interface{}{"f.1": 1, "f2": uint64(32)}, 188 tags: map[string]string{"t.1": "abc", "t2": "32"}, 189 190 opt: func() *Option { 191 opt := NewDefaultOption() 192 opt.Time = time.Unix(0, 123) 193 opt.EnablePointInKey = true 194 return opt 195 }(), 196 197 fail: true, 198 }, 199 200 { 201 tname: `enable point in metric point`, 202 name: "abc", 203 fields: map[string]interface{}{"f.1": 1, "f2": uint64(32)}, 204 tags: map[string]string{"t.1": "abc", "t2": "32"}, 205 206 opt: func() *Option { 207 opt := NewDefaultOption() 208 opt.Time = time.Unix(0, 123) 209 opt.EnablePointInKey = true 210 return opt 211 }(), 212 213 expect: "abc,t.1=abc,t2=32 f.1=1i,f2=32i 123", 214 }, 215 216 { 217 tname: `enable point in metric point`, 218 name: "abc", 219 fields: map[string]interface{}{"f.1": 1, "f2": uint64(32)}, 220 tags: map[string]string{"t1": "abc", "t2": "32"}, 221 222 opt: func() *Option { 223 opt := NewDefaultOption() 224 opt.Time = time.Unix(0, 123) 225 opt.EnablePointInKey = true 226 return opt 227 }(), 228 expect: "abc,t1=abc,t2=32 f.1=1i,f2=32i 123", 229 }, 230 231 { 232 tname: `with disabled field keys`, 233 name: "abc", 234 fields: map[string]interface{}{"f1": 1, "f2": uint64(32)}, 235 tags: map[string]string{"t1": "abc", "t2": "32"}, 236 237 opt: func() *Option { 238 opt := NewDefaultOption() 239 opt.DisabledFieldKeys = []string{"f1"} 240 return opt 241 }(), 242 243 fail: true, 244 }, 245 246 { 247 tname: `with disabled tag keys`, 248 name: "abc", 249 fields: map[string]interface{}{"f1": 1, "f2": uint64(32)}, 250 tags: map[string]string{"t1": "abc", "t2": "32"}, 251 252 opt: func() *Option { 253 opt := NewDefaultOption() 254 opt.DisabledTagKeys = []string{"t2"} 255 return opt 256 }(), 257 258 fail: true, 259 }, 260 261 { 262 tname: `int exceed int64-max under non-strict mode`, 263 name: "abc", 264 fields: map[string]interface{}{"f1": 1, "f2": uint64(32)}, 265 expect: "abc f1=1i,f2=32i 123", 266 opt: func() *Option { 267 opt := NewDefaultOption() 268 opt.Time = time.Unix(0, 123) 269 return opt 270 }(), 271 272 fail: false, 273 }, 274 275 { 276 tname: `int exceed int64-max under non-strict mode`, 277 name: "abc", 278 fields: map[string]interface{}{"f1": 1, "f2": uint64(math.MaxInt64) + 1}, 279 expect: "abc f1=1i 123", // f2 dropped 280 warnTypes: []string{WarnMaxFieldValueInt}, 281 opt: func() *Option { 282 opt := NewDefaultOption() 283 opt.Time = time.Unix(0, 123) 284 opt.Strict = false 285 return opt 286 }(), 287 288 fail: false, 289 }, 290 291 { 292 tname: `int exceed int64-max under strict mode`, 293 name: "abc", 294 fields: map[string]interface{}{"f1": 1, "f2": uint64(math.MaxInt64) + 1}, 295 296 opt: func() *Option { 297 opt := NewDefaultOption() 298 opt.Time = time.Unix(0, 123) 299 return opt 300 }(), 301 302 fail: true, 303 }, 304 305 { 306 tname: `extra tags and field exceed max tags`, 307 name: "abc", 308 fields: map[string]interface{}{"f1": 1, "f2": "3"}, 309 tags: map[string]string{"t1": "def", "t2": "abc"}, 310 311 opt: func() *Option { 312 opt := NewDefaultOption() 313 opt.Time = time.Unix(0, 123) 314 opt.MaxTags = 2 315 opt.MaxFields = 1 316 opt.ExtraTags = map[string]string{ 317 "etag1": "1", 318 "etag2": "2", 319 } 320 return opt 321 }(), 322 warnTypes: []string{WarnMaxTags, WarnMaxFields}, 323 expect: "abc,etag1=1,etag2=2 f1=1i 123", // f2 dropped, 324 }, 325 326 { 327 tname: `extra tags exceed max tags`, 328 name: "abc", 329 fields: map[string]interface{}{"f1": 1}, 330 tags: map[string]string{"t1": "def", "t2": "abc"}, 331 warnTypes: []string{WarnMaxTags}, 332 opt: func() *Option { 333 opt := NewDefaultOption() 334 opt.Time = time.Unix(0, 123) 335 opt.MaxTags = 2 336 opt.ExtraTags = map[string]string{ 337 "etag1": "1", 338 "etag2": "2", 339 } 340 opt.Time = time.Unix(0, 123) 341 return opt 342 }(), 343 expect: "abc,etag1=1,etag2=2 f1=1i 123", 344 }, 345 346 { 347 tname: `extra tags not exceed max tags`, 348 name: "abc", 349 fields: map[string]interface{}{"f1": 1}, 350 tags: map[string]string{"t1": "def", "t2": "abc"}, 351 expect: "abc,etag1=1,etag2=2,t1=def,t2=abc f1=1i 123", 352 353 opt: func() *Option { 354 opt := NewDefaultOption() 355 opt.Time = time.Unix(0, 123) 356 opt.MaxTags = 4 357 opt.ExtraTags = map[string]string{ 358 "etag1": "1", 359 "etag2": "2", 360 } 361 return opt 362 }(), 363 364 fail: false, 365 }, 366 367 { 368 tname: `only extra tags`, 369 name: "abc", 370 fields: map[string]interface{}{"f1": 1}, 371 expect: "abc,etag1=1,etag2=2 f1=1i 123", 372 373 opt: func() *Option { 374 opt := NewDefaultOption() 375 opt.Time = time.Unix(0, 123) 376 opt.MaxTags = 4 377 opt.ExtraTags = map[string]string{ 378 "etag1": "1", 379 "etag2": "2", 380 } 381 return opt 382 }(), 383 384 fail: false, 385 }, 386 387 { 388 tname: `exceed max tags`, 389 name: "abc", 390 fields: map[string]interface{}{"f1": 1, "f2": nil}, 391 tags: map[string]string{"t1": "def", "t2": "abc"}, 392 opt: func() *Option { 393 opt := NewDefaultOption() 394 opt.Time = time.Unix(0, 123) 395 opt.MaxTags = 1 396 return opt 397 }(), 398 warnTypes: []string{WarnMaxTags}, 399 fail: true, 400 }, 401 402 { 403 tname: `exceed max field`, 404 name: "abc", 405 fields: map[string]interface{}{"f1": 1, "f2": 2}, 406 tags: map[string]string{"t1": "def"}, 407 opt: func() *Option { 408 opt := NewDefaultOption() 409 opt.Time = time.Unix(0, 123) 410 opt.MaxFields = 1 411 opt.Time = time.Unix(0, 123) 412 return opt 413 }(), 414 warnTypes: []string{WarnMaxFields}, 415 expect: "abc,t1=def f1=1i 123", 416 }, 417 418 { 419 tname: `field key with "."`, 420 name: "abc", 421 fields: map[string]interface{}{"f1.a": 1}, 422 tags: map[string]string{"t1.a": "def"}, 423 opt: NewDefaultOption(), 424 fail: true, 425 }, 426 427 { 428 tname: `field key with "."`, 429 name: "abc", 430 fields: map[string]interface{}{"f1.a": 1}, 431 tags: map[string]string{"t1": "def"}, 432 opt: NewDefaultOption(), 433 fail: true, 434 }, 435 436 { 437 tname: `tag key with "."`, 438 name: "abc", 439 fields: map[string]interface{}{"f1": 1, "f2": nil}, 440 tags: map[string]string{"t1.a": "def"}, 441 opt: NewDefaultOption(), 442 fail: true, 443 }, 444 445 { 446 tname: `nil field, not allowed`, 447 name: "abc", 448 fields: map[string]interface{}{"f1": 1, "f2": nil}, 449 tags: map[string]string{"t1": "def"}, 450 opt: func() *Option { 451 opt := NewDefaultOption() 452 opt.Time = time.Unix(0, 123) 453 return opt 454 }(), 455 fail: true, 456 }, 457 458 { 459 tname: `same key in field and tag`, 460 name: "abc", 461 fields: map[string]interface{}{"f1": 1, "f2": 2}, 462 tags: map[string]string{"f1": "def"}, 463 opt: func() *Option { 464 opt := NewDefaultOption() 465 opt.Time = time.Unix(0, 123) 466 return opt 467 }(), 468 warnTypes: []string{WarnSameTagFieldKey}, 469 expect: "abc,f1=def f2=2i 123", 470 }, 471 472 { 473 tname: `no tag`, 474 name: "abc", 475 fields: map[string]interface{}{"f1": 1}, 476 tags: nil, 477 opt: func() *Option { 478 opt := NewDefaultOption() 479 opt.Time = time.Unix(0, 123) 480 return opt 481 }(), 482 expect: "abc f1=1i 123", 483 }, 484 485 { 486 tname: `no filed`, 487 name: "abc", 488 fields: nil, 489 tags: map[string]string{"f1": "def"}, 490 opt: NewDefaultOption(), 491 fail: true, 492 }, 493 494 { 495 tname: `field-val with '\n'`, 496 name: "abc", 497 fields: map[string]interface{}{"f1": `abc 498 123`}, 499 opt: func() *Option { 500 opt := NewDefaultOption() 501 opt.Time = time.Unix(0, 123) 502 opt.Strict = false 503 return opt 504 }(), 505 expect: `abc f1="abc 506 123" 123`, 507 fail: false, 508 }, 509 510 { 511 tname: `tag-k/v with '\n' under non-strict`, 512 name: "abc", 513 tags: map[string]string{ 514 "tag1": `abc 515 123`, 516 `tag 517 2`: `def 518 456\`, 519 }, 520 fields: map[string]interface{}{"f1": 123}, 521 opt: func() *Option { 522 opt := NewDefaultOption() 523 opt.Time = time.Unix(0, 123) 524 opt.Strict = false 525 return opt 526 }(), 527 expect: "abc,tag\\ 2=def\\ 456,tag1=abc\\ 123 f1=123i 123", 528 fail: false, 529 }, 530 531 { 532 tname: `tag-k/v with '\n' under strict`, 533 name: "abc", 534 tags: map[string]string{ 535 "tag1": `abc 536 123`, 537 `tag 538 2`: `def 539 456\`, 540 }, 541 fields: map[string]interface{}{"f1": 123}, 542 opt: func() *Option { 543 opt := NewDefaultOption() 544 opt.Time = time.Unix(0, 123) 545 return opt 546 }(), 547 fail: true, 548 }, 549 550 { 551 tname: `ok case`, 552 name: "abc", 553 tags: nil, 554 fields: map[string]interface{}{"f1": 123}, 555 opt: func() *Option { 556 opt := NewDefaultOption() 557 opt.Time = time.Unix(0, 123) 558 return opt 559 }(), 560 expect: "abc f1=123i 123", 561 fail: false, 562 }, 563 564 { 565 tname: `tag key with backslash`, 566 name: "abc", 567 tags: map[string]string{"tag1": "val1", `tag2\`: `val2\`}, 568 fields: map[string]interface{}{"f1": 123}, 569 opt: func() *Option { 570 opt := NewDefaultOption() 571 opt.Time = time.Unix(0, 123) 572 return opt 573 }(), 574 fail: true, 575 }, 576 577 { 578 tname: `auto fix tag-key, tag-value under non-strict mode`, 579 name: "abc", 580 tags: map[string]string{"tag1": "val1", `tag2\`: `val2\`}, 581 fields: map[string]interface{}{"f1": 123}, 582 583 opt: func() *Option { 584 opt := NewDefaultOption() 585 opt.Time = time.Unix(0, 123) 586 opt.Strict = false 587 return opt 588 }(), 589 expect: "abc,tag1=val1,tag2=val2 f1=123i 123", 590 fail: false, 591 }, 592 593 { 594 tname: `under strict: error`, 595 name: "abc", 596 tags: map[string]string{"tag1": "val1", `tag2\`: `val2\`}, 597 fields: map[string]interface{}{"f1": 123}, 598 599 opt: func() *Option { 600 opt := NewDefaultOption() 601 opt.Time = time.Unix(0, 123) 602 return opt 603 }(), 604 605 expect: "abc,tag1=val1,tag2=val2 f1=123i 123", 606 fail: true, 607 }, 608 609 { 610 tname: `under strict: field is nil`, 611 name: "abc", 612 tags: map[string]string{"tag1": "val1", `tag2`: `val2`}, 613 fields: map[string]interface{}{"f1": 123, "f2": nil}, 614 615 opt: func() *Option { 616 opt := NewDefaultOption() 617 opt.Time = time.Unix(0, 123) 618 return opt 619 }(), 620 621 expect: "abc,tag1=val1,tag2=val2 f1=123i 123", 622 fail: true, 623 }, 624 625 { 626 tname: `under strict: field is map`, 627 name: "abc", 628 tags: map[string]string{"tag1": "val1", `tag2`: `val2`}, 629 fields: map[string]interface{}{"f1": 123, "f2": map[string]interface{}{"a": "b"}}, 630 631 opt: func() *Option { 632 opt := NewDefaultOption() 633 opt.Time = time.Unix(0, 123) 634 return opt 635 }(), 636 637 expect: "abc,tag1=val1,tag2=val2 f1=123i 123", 638 fail: true, 639 }, 640 641 { 642 tname: `under strict: field is object`, 643 name: "abc", 644 tags: map[string]string{"tag1": "val1", `tag2`: `val2`}, 645 fields: map[string]interface{}{"f1": 123, "f2": struct{ a string }{a: "abc"}}, 646 647 opt: func() *Option { 648 opt := NewDefaultOption() 649 opt.Time = time.Unix(0, 123) 650 return opt 651 }(), 652 653 expect: "abc,tag1=val1,tag2=val2 f1=123i 123", 654 fail: true, 655 }, 656 657 { 658 tname: `under non-strict, ignore nil field`, 659 name: "abc", 660 tags: map[string]string{"tag1": "val1", `tag2\`: `val2\`}, 661 fields: map[string]interface{}{"f1": 123, "f2": nil}, 662 663 opt: func() *Option { 664 opt := NewDefaultOption() 665 opt.Time = time.Unix(0, 123) 666 opt.Strict = false 667 return opt 668 }(), 669 670 expect: "abc,tag1=val1,tag2=val2 f1=123i 123", 671 fail: false, 672 }, 673 674 { 675 tname: `under strict, utf8 characters in metric-name`, 676 name: "abc≈≈≈≈øøππ†®", 677 tags: map[string]string{"tag1": "val1", `tag2`: `val2`}, 678 fields: map[string]interface{}{"f1": 123}, 679 680 opt: func() *Option { 681 opt := NewDefaultOption() 682 opt.Time = time.Unix(0, 123) 683 return opt 684 }(), 685 686 expect: "abc≈≈≈≈øøππ†®,tag1=val1,tag2=val2 f1=123i 123", 687 fail: false, 688 }, 689 690 { 691 tname: `under strict, utf8 characters in metric-name, fields, tags`, 692 name: "abc≈≈≈≈øøππ†®", 693 tags: map[string]string{"tag1": "val1", `tag2`: `val2`, "tag3": `ºª•¶§∞¢£`}, 694 fields: map[string]interface{}{"f1": 123, "f2": "¡™£¢∞§¶•ªº"}, 695 opt: func() *Option { 696 opt := NewDefaultOption() 697 opt.Time = time.Unix(0, 123) 698 return opt 699 }(), 700 701 expect: `abc≈≈≈≈øøππ†®,tag1=val1,tag2=val2,tag3=ºª•¶§∞¢£ f1=123i,f2="¡™£¢∞§¶•ªº" 123`, 702 fail: false, 703 }, 704 705 { 706 tname: `missing field`, 707 name: "abc≈≈≈≈øøππ†®", 708 tags: map[string]string{"tag1": "val1", `tag2`: `val2`, "tag3": `ºª•¶§∞¢£`}, 709 710 opt: func() *Option { 711 opt := NewDefaultOption() 712 opt.Time = time.Unix(0, 123) 713 return opt 714 }(), 715 716 expect: `abc≈≈≈≈øøππ†®,tag1=val1,tag2=val2,tag3=ºª•¶§∞¢£ f1=123i,f2="¡™£¢∞§¶•ªº" 123`, 717 fail: true, 718 }, 719 720 { 721 tname: `new line in field`, 722 name: "abc", 723 tags: map[string]string{"tag1": "val1"}, 724 fields: map[string]interface{}{ 725 "f1": `aaa 726 bbb 727 ccc`, 728 }, 729 opt: func() *Option { 730 opt := NewDefaultOption() 731 opt.Time = time.Unix(0, 123) 732 return opt 733 }(), 734 735 expect: `abc,tag1=val1 f1="aaa 736 bbb 737 ccc" 123`, 738 }, 739 } 740 741 for i, tc := range cases { 742 t.Run(tc.tname, func(t *testing.T) { 743 pt, warnings, err := MakeLineProtoPointWithWarnings(tc.name, tc.tags, tc.fields, tc.opt) 744 745 if len(tc.warnTypes) == 0 { 746 testutil.Equals(t, 0, len(warnings)) 747 } 748 for _, warnType := range tc.warnTypes { 749 isFound := false 750 for _, w := range warnings { 751 if w.WarningType == warnType { 752 isFound = true 753 break 754 } 755 } 756 if isFound { 757 continue 758 } else { 759 t.Fail() 760 t.Logf("[%d]: expected warning type %s, but not found", i, warnType) 761 } 762 } 763 if tc.fail { 764 testutil.NotOk(t, err, "") 765 t.Logf("[%d] expect error: %s", i, err) 766 } else { 767 testutil.Ok(t, err) 768 x := pt.String() 769 testutil.Equals(t, tc.expect, x) 770 _, err := parseLineProto(t, []byte(x), "n") 771 testutil.Equals(t, err, nil) 772 fmt.Printf("\n[%d]%s\n", i, x) 773 } 774 }) 775 } 776 } 777 778 func TestParsePoint(t *testing.T) { 779 newPoint := func(m string, 780 tags map[string]string, 781 fields map[string]interface{}, 782 ts ...time.Time, 783 ) *influxdb.Point { 784 pt, err := influxdb.NewPoint(m, tags, fields, ts...) 785 if err != nil { 786 t.Fatal(err) // should never been here 787 } 788 789 return pt 790 } 791 792 __32mbString := cliutils.CreateRandomString(32 * 1024 * 1024) 793 __65kbString := cliutils.CreateRandomString(65 * 1024) 794 795 cases := []struct { 796 name string 797 data []byte 798 opt *Option 799 expect []*influxdb.Point 800 fail bool 801 }{ 802 { 803 name: `32mb-field`, 804 data: []byte(fmt.Sprintf(`abc f1="%s" 123`, __32mbString)), 805 opt: func() *Option { 806 opt := NewDefaultOption() 807 opt.MaxFieldValueLen = 32 * 1024 * 1024 808 opt.Time = time.Unix(0, 123) 809 return opt 810 }(), 811 812 expect: []*influxdb.Point{ 813 newPoint("abc", 814 nil, 815 map[string]interface{}{"f1": __32mbString}, 816 time.Unix(0, 123)), 817 }, 818 }, 819 { 820 name: `65k-field`, 821 data: []byte(fmt.Sprintf(`abc f1="%s" 123`, __65kbString)), 822 opt: func() *Option { 823 opt := NewDefaultOption() 824 opt.MaxFieldValueLen = 0 825 opt.Time = time.Unix(0, 123) 826 return opt 827 }(), 828 829 expect: []*influxdb.Point{ 830 newPoint("abc", 831 nil, 832 map[string]interface{}{"f1": __65kbString}, 833 time.Unix(0, 123)), 834 }, 835 }, 836 837 { 838 name: `with disabled field`, 839 data: []byte(`abc,t1=1,t2=2 f1=1i,f2=2,f3="abc" 123`), 840 opt: &Option{DisabledFieldKeys: []string{"f1"}}, 841 fail: true, 842 }, 843 844 { 845 name: `with disabled tags`, 846 data: []byte(`abc,t1=1,t2=2 f1=1i,f2=2,f3="abc" 123`), 847 opt: &Option{DisabledTagKeys: []string{"t1"}}, 848 fail: true, 849 }, 850 851 { 852 name: `exceed max tags`, 853 data: []byte(`abc,t1=1,t2=2 f1=1i,f2=2,f3="abc" 123`), 854 opt: &Option{Time: time.Unix(0, 123), MaxTags: 1}, 855 fail: true, 856 }, 857 858 { 859 name: `exceed max fields`, 860 data: []byte(`abc f1=1i,f2=2,f3="abc" 123`), 861 opt: &Option{Time: time.Unix(0, 123), MaxFields: 2}, 862 fail: true, 863 }, 864 865 { 866 name: `tag key with .`, 867 data: []byte(`abc,tag.1=xxx f1=1i,f2=2,f3="abc" 123`), 868 opt: &Option{Time: time.Unix(0, 123)}, 869 fail: true, 870 }, 871 872 { 873 name: `field key with .`, 874 data: []byte(`abc f.1=1i,f2=2,f3="abc" 123`), 875 opt: &Option{Time: time.Unix(0, 123)}, 876 fail: true, 877 }, 878 879 { 880 name: `with comments`, 881 data: []byte(`abc f1=1i,f2=2,f3="abc" 123 882 # some comments 883 abc f1=1i,f2=2,f3="abc" 456 884 # other comments with leading spaces 885 abc f1=1i,f2=2,f3="abc" 789 886 887 `), 888 opt: &Option{Time: time.Unix(0, 123)}, 889 expect: []*influxdb.Point{ 890 newPoint("abc", 891 nil, 892 map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"}, 893 time.Unix(0, 123)), 894 895 newPoint("abc", 896 nil, 897 map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"}, 898 time.Unix(0, 456)), 899 900 newPoint("abc", 901 nil, 902 map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"}, 903 time.Unix(0, 789)), 904 }, 905 }, 906 907 { 908 name: `same key in field and tag, dup tag comes from ExtraTags`, 909 data: []byte(`abc b="abc",a=1i 123`), 910 opt: &Option{Time: time.Unix(0, 123), ExtraTags: map[string]string{"a": "456"}}, // dup tag from Option 911 fail: true, 912 }, 913 914 { 915 name: `same key in tags and fields`, 916 data: []byte(`abc,b=abc a=1i,b="abc" 123`), 917 opt: &Option{Time: time.Unix(0, 123)}, 918 fail: true, 919 }, 920 921 { 922 name: `same key in fields`, 923 data: []byte(`abc,b=abc a=1i,c="abc",c=f 123`), 924 opt: &Option{Time: time.Unix(0, 123)}, 925 fail: true, 926 }, 927 928 { 929 name: `same key in tag`, 930 data: []byte(`abc,b=abc,b=xyz a=1i 123`), 931 fail: true, 932 }, 933 934 { 935 name: `empty data`, 936 data: nil, 937 fail: true, 938 }, 939 940 { 941 name: "normal case", 942 data: []byte(`abc,tag1=1,tag2=2 f1=1i,f2=2,f3="abc" 123`), 943 opt: &Option{Time: time.Unix(0, 123)}, 944 expect: []*influxdb.Point{ 945 newPoint("abc", 946 map[string]string{"tag1": "1", "tag2": "2"}, 947 map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"}, 948 time.Unix(0, 123)), 949 }, 950 }, 951 952 { 953 name: `no tags`, 954 data: []byte(`abc f1=1i,f2=2,f3="abc" 123`), 955 opt: &Option{Time: time.Unix(0, 123)}, 956 expect: []*influxdb.Point{ 957 newPoint("abc", 958 nil, 959 map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"}, 960 time.Unix(0, 123)), 961 }, 962 }, 963 964 { 965 name: `multiple empty lines in body`, 966 data: []byte(`abc f1=1i,f2=2,f3="abc" 123 967 968 abc f1=1i,f2=2,f3="abc" 456 969 970 abc f1=1i,f2=2,f3="abc" 789 971 972 `), 973 opt: &Option{Time: time.Unix(0, 123)}, 974 expect: []*influxdb.Point{ 975 newPoint("abc", 976 nil, 977 map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"}, 978 time.Unix(0, 123)), 979 980 newPoint("abc", 981 nil, 982 map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"}, 983 time.Unix(0, 456)), 984 985 newPoint("abc", 986 nil, 987 map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"}, 988 time.Unix(0, 789)), 989 }, 990 }, 991 992 { 993 name: `no fields`, 994 fail: true, 995 data: []byte(`abc,tag1=1,tag2=2 123123`), 996 }, 997 998 { 999 name: `parse with extra tags`, 1000 data: []byte(`abc f1=1i,f2=2,f3="abc" 123`), 1001 opt: &Option{ 1002 Time: time.Unix(0, 123), 1003 ExtraTags: map[string]string{"tag1": "1", "tag2": "2"}, 1004 }, 1005 expect: []*influxdb.Point{ 1006 newPoint("abc", 1007 map[string]string{"tag1": "1", "tag2": "2"}, 1008 map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"}, 1009 time.Unix(0, 123)), 1010 }, 1011 }, 1012 1013 { 1014 name: `extra tag key with '\' suffix`, 1015 data: []byte(`abc f1=1i,f2=2,f3="abc" 123`), 1016 opt: &Option{ 1017 Time: time.Unix(0, 123), 1018 ExtraTags: map[string]string{`tag1\`: `1`, "tag2": `2`}, 1019 }, 1020 expect: []*influxdb.Point{ 1021 newPoint("abc", 1022 map[string]string{`tag1\`: "1", "tag2": `2`}, 1023 map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"}, 1024 time.Unix(0, 123)), 1025 }, 1026 }, 1027 1028 { 1029 name: `extra tag val with '\' suffix`, 1030 data: []byte(`abc f1=1i,f2=2,f3="abc" 123`), 1031 opt: &Option{ 1032 Time: time.Unix(0, 123), 1033 ExtraTags: map[string]string{`tag1`: `1,`, "tag2": `2\`, "tag3": `3`}, 1034 }, 1035 expect: []*influxdb.Point{ 1036 newPoint("abc", 1037 map[string]string{`tag1`: "1,", "tag2": `2\`, "tag3": `3`}, 1038 map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"}, 1039 time.Unix(0, 123)), 1040 }, 1041 }, 1042 1043 { 1044 name: `extra tag kv with '\'`, 1045 data: []byte(`abc f1=1i,f2=2,f3="abc" 123`), 1046 opt: &Option{ 1047 Time: time.Unix(0, 123), 1048 ExtraTags: map[string]string{`tag\1`: `1`, "tag2": `2\34`}, 1049 }, 1050 expect: []*influxdb.Point{ 1051 newPoint("abc", 1052 map[string]string{`tag\1`: "1", "tag2": `2\34`}, 1053 map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"}, 1054 time.Unix(0, 123)), 1055 }, 1056 }, 1057 1058 { 1059 name: `tag kv with '\': missing tag value`, 1060 fail: true, 1061 data: []byte(`abc,tag1\=1,tag2=2\ f1=1i 123123`), 1062 }, 1063 1064 { 1065 name: `parse with callback: no point`, 1066 data: []byte(`abc f1=1i,f2=2,f3="abc" 123`), 1067 opt: &Option{ 1068 Time: time.Unix(0, 123), 1069 Callback: func(p models.Point) (models.Point, error) { 1070 return nil, nil 1071 }, 1072 }, 1073 fail: true, 1074 }, 1075 1076 { 1077 name: `parse with callback failed`, 1078 data: []byte(`abc f1=1i,f2=2,f3="abc" 123`), 1079 opt: &Option{ 1080 Time: time.Unix(0, 123), 1081 Callback: func(p models.Point) (models.Point, error) { 1082 return nil, fmt.Errorf("callback failed") 1083 }, 1084 }, 1085 fail: true, 1086 }, 1087 1088 { 1089 name: `parse with callback`, 1090 data: []byte(`abc f1=1i,f2=2,f3="abc" 123`), 1091 opt: &Option{ 1092 Time: time.Unix(0, 123), 1093 Callback: func(p models.Point) (models.Point, error) { 1094 if string(p.Name()) == "abc" { 1095 t.Logf("haha, we get measurement `abc'") 1096 } 1097 p.AddTag("callback-added-tag", "callback-added-tag-value") 1098 return p, nil 1099 }, 1100 }, 1101 expect: []*influxdb.Point{ 1102 newPoint("abc", 1103 map[string]string{"callback-added-tag": "callback-added-tag-value"}, 1104 map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"}, 1105 time.Unix(0, 123)), 1106 }, 1107 }, 1108 } 1109 1110 for _, tc := range cases { 1111 t.Run(tc.name, func(t *testing.T) { 1112 pts, err := ParsePoints(tc.data, tc.opt) 1113 if tc.fail { 1114 testutil.NotOk(t, err, "") 1115 t.Logf("expect error: %s", err) 1116 } else { 1117 testutil.Ok(t, err) 1118 1119 for idx, pt := range pts { 1120 if len(tc.expect) > 0 { 1121 _ = idx 1122 1123 got := pt.String() 1124 1125 pts, err := parseLineProto(t, []byte(got), "n") 1126 if err != nil { 1127 t.Logf("parseLineProto failed") 1128 continue 1129 } 1130 1131 for _, pt := range pts { 1132 fields, err := pt.Fields() 1133 testutil.Ok(t, err) 1134 1135 for k, v := range fields { 1136 switch x := v.(type) { 1137 case string: 1138 t.Logf("%s: %s", k, cliutils.StringTrim(x, 32)) 1139 default: 1140 t.Logf("%s: %v", k, x) 1141 } 1142 } 1143 } 1144 } 1145 } 1146 } 1147 }) 1148 } 1149 } 1150 1151 func TestParseLineProto(t *testing.T) { 1152 __32mbString := cliutils.CreateRandomString(32 * 1024 * 1024) 1153 __65kbString := cliutils.CreateRandomString(65 * 1024) 1154 1155 cases := []struct { 1156 data []byte 1157 prec string 1158 fail bool 1159 name string 1160 check func(pts models.Points) error 1161 }{ 1162 { 1163 name: `nil data`, 1164 data: nil, 1165 prec: "n", 1166 fail: true, 1167 }, 1168 1169 { 1170 name: `no data`, 1171 data: []byte(""), 1172 prec: "n", 1173 fail: true, 1174 }, 1175 1176 { 1177 name: `with multiple empty lines`, 1178 data: []byte(`abc,tag1=1,tag2=2 f1=1i,f2=2,f3="abc" 1179 abc,tag1=1,tag2=2 f1=1i,f2=2,f3="abc" 1180 abc,tag1=1,tag2=2 f1=1i,f2=2,f3="abc" 1181 1182 `), 1183 prec: "n", 1184 }, 1185 1186 { 1187 name: `missing field`, 1188 data: []byte(`abc,tag1=1,tag2=2 f1=1i,f2=2,f3="abc" 1189 abc,tag1=1,tag2=2 f1=1i,f2=2,f3="abc" 1190 abc,tag1=1,tag2=2 f1=1i,f2=2,f3="abc" 1191 abc 1192 `), 1193 prec: "n", 1194 fail: true, 1195 }, 1196 1197 { 1198 name: `missing tag`, 1199 data: []byte(`abc,tag1=1,tag2=2 f1=1i,f2=2,f3="abc" 1200 abc,tag1=1,tag2=2 f1=1i,f2=2,f3="abc" 1201 abc,tag1=1,tag2=2 f1=1i,f2=2,f3="abc" 123456789 1202 abc f1=1i,f2=2,f3="abc" 1203 `), 1204 prec: "n", 1205 }, 1206 1207 { 1208 name: `65kb-field-key`, 1209 data: []byte(fmt.Sprintf(`abc,tag1=1,tag2=2 "%s"="hello" 123`, func() string { 1210 return __65kbString 1211 }())), 1212 1213 fail: true, 1214 prec: "n", 1215 }, 1216 1217 { 1218 name: `65kb-tag-key`, 1219 data: []byte(fmt.Sprintf(`abc,tag1=1,%s=2 f1="hello" 123`, func() string { 1220 return __65kbString 1221 }())), 1222 1223 fail: true, 1224 prec: "n", 1225 }, 1226 1227 { 1228 name: `65kb-measurement-name`, 1229 data: []byte(fmt.Sprintf(`%s,tag1=1,t2=2 f1="hello" 123`, func() string { 1230 return __65kbString 1231 }())), 1232 1233 fail: true, 1234 prec: "n", 1235 }, 1236 1237 { 1238 name: `32mb-field`, 1239 data: []byte(fmt.Sprintf(`abc,tag1=1,tag2=2 f3="%s" 123`, func() string { 1240 return __32mbString 1241 }())), 1242 1243 check: func(pts models.Points) error { 1244 if len(pts) != 1 { 1245 return fmt.Errorf("expect only 1 point, got %d", len(pts)) 1246 } 1247 1248 fields, err := pts[0].Fields() 1249 if err != nil { 1250 return err 1251 } 1252 1253 if v, ok := fields["f3"]; !ok { 1254 return fmt.Errorf("field f3 missing") 1255 } else if v != __32mbString { 1256 return fmt.Errorf("field f3 not expected") 1257 } 1258 return nil 1259 }, 1260 1261 prec: "n", 1262 }, 1263 } 1264 1265 for _, tc := range cases { 1266 t.Run(tc.name, func(t *testing.T) { 1267 pts, err := parseLineProto(t, tc.data, tc.prec) 1268 1269 if tc.fail { 1270 testutil.NotOk(t, err, "") 1271 t.Logf("expect error: %s", cliutils.LeftStringTrim(err.Error(), 64)) 1272 } else { 1273 testutil.Ok(t, err) 1274 } 1275 1276 if tc.check != nil { 1277 testutil.Ok(t, tc.check(pts)) 1278 } 1279 }) 1280 } 1281 }