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