github.com/influxdata/telegraf@v1.30.3/testutil/accumulator.go (about) 1 package testutil 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "reflect" 7 "sync" 8 "sync/atomic" 9 "testing" 10 "time" 11 12 "github.com/stretchr/testify/require" 13 14 "github.com/influxdata/telegraf" 15 "github.com/influxdata/telegraf/metric" 16 ) 17 18 // Metric defines a single point measurement 19 type Metric struct { 20 Measurement string 21 Tags map[string]string 22 Fields map[string]interface{} 23 Time time.Time 24 Type telegraf.ValueType 25 } 26 27 func (p *Metric) String() string { 28 return fmt.Sprintf("%s %v %v", p.Measurement, p.Tags, p.Fields) 29 } 30 31 // Accumulator defines a mocked out accumulator 32 type Accumulator struct { 33 nMetrics uint64 // Needs to be first to avoid unaligned atomic operations on 32-bit archs 34 Metrics []*Metric 35 accumulated []telegraf.Metric 36 Discard bool 37 Errors []error 38 debug bool 39 deliverChan chan telegraf.DeliveryInfo 40 delivered []telegraf.DeliveryInfo 41 42 TimeFunc func() time.Time 43 44 sync.Mutex 45 *sync.Cond 46 } 47 48 func (a *Accumulator) NMetrics() uint64 { 49 return atomic.LoadUint64(&a.nMetrics) 50 } 51 52 func (a *Accumulator) NDelivered() int { 53 a.Lock() 54 defer a.Unlock() 55 return len(a.delivered) 56 } 57 58 // GetTelegrafMetrics returns all the metrics collected by the accumulator 59 // If you are getting race conditions here then you are not waiting for all of your metrics to arrive: see Wait() 60 func (a *Accumulator) GetTelegrafMetrics() []telegraf.Metric { 61 a.Lock() 62 defer a.Unlock() 63 metrics := make([]telegraf.Metric, 0, len(a.accumulated)) 64 metrics = append(metrics, a.accumulated...) 65 return metrics 66 } 67 68 func (a *Accumulator) GetDeliveries() []telegraf.DeliveryInfo { 69 a.Lock() 70 defer a.Unlock() 71 info := make([]telegraf.DeliveryInfo, 0, len(a.delivered)) 72 info = append(info, a.delivered...) 73 return info 74 } 75 76 func (a *Accumulator) FirstError() error { 77 if len(a.Errors) == 0 { 78 return nil 79 } 80 return a.Errors[0] 81 } 82 83 func (a *Accumulator) ClearMetrics() { 84 a.Lock() 85 defer a.Unlock() 86 atomic.StoreUint64(&a.nMetrics, 0) 87 a.Metrics = make([]*Metric, 0) 88 a.accumulated = make([]telegraf.Metric, 0) 89 } 90 91 func (a *Accumulator) addMeasurement( 92 measurement string, 93 tags map[string]string, 94 fields map[string]interface{}, 95 tp telegraf.ValueType, 96 timestamp ...time.Time, 97 ) { 98 a.Lock() 99 defer a.Unlock() 100 atomic.AddUint64(&a.nMetrics, 1) 101 if a.Cond != nil { 102 a.Cond.Broadcast() 103 } 104 if a.Discard { 105 return 106 } 107 108 if len(fields) == 0 { 109 return 110 } 111 112 tagsCopy := map[string]string{} 113 for k, v := range tags { 114 tagsCopy[k] = v 115 } 116 117 fieldsCopy := map[string]interface{}{} 118 for k, v := range fields { 119 fieldsCopy[k] = v 120 } 121 122 var t time.Time 123 if len(timestamp) > 0 { 124 t = timestamp[0] 125 } else { 126 if a.TimeFunc == nil { 127 t = time.Now() 128 } else { 129 t = a.TimeFunc() 130 } 131 } 132 133 if a.debug { 134 pretty, _ := json.MarshalIndent(fields, "", " ") 135 prettyTags, _ := json.MarshalIndent(tags, "", " ") 136 msg := fmt.Sprintf("Adding Measurement [%s]\nFields:%s\nTags:%s\n", 137 measurement, string(pretty), string(prettyTags)) 138 fmt.Print(msg) 139 } 140 141 m := &Metric{ 142 Measurement: measurement, 143 Fields: fieldsCopy, 144 Tags: tagsCopy, 145 Time: t, 146 Type: tp, 147 } 148 149 a.Metrics = append(a.Metrics, m) 150 a.accumulated = append(a.accumulated, FromTestMetric(m)) 151 } 152 153 // AddFields adds a measurement point with a specified timestamp. 154 func (a *Accumulator) AddFields( 155 measurement string, 156 fields map[string]interface{}, 157 tags map[string]string, 158 timestamp ...time.Time, 159 ) { 160 a.addMeasurement(measurement, tags, fields, telegraf.Untyped, timestamp...) 161 } 162 163 func (a *Accumulator) AddCounter( 164 measurement string, 165 fields map[string]interface{}, 166 tags map[string]string, 167 timestamp ...time.Time, 168 ) { 169 a.addMeasurement(measurement, tags, fields, telegraf.Counter, timestamp...) 170 } 171 172 func (a *Accumulator) AddGauge( 173 measurement string, 174 fields map[string]interface{}, 175 tags map[string]string, 176 timestamp ...time.Time, 177 ) { 178 a.addMeasurement(measurement, tags, fields, telegraf.Gauge, timestamp...) 179 } 180 181 func (a *Accumulator) AddMetrics(metrics []telegraf.Metric) { 182 for _, m := range metrics { 183 a.AddMetric(m) 184 } 185 } 186 187 func (a *Accumulator) AddSummary( 188 measurement string, 189 fields map[string]interface{}, 190 tags map[string]string, 191 timestamp ...time.Time, 192 ) { 193 a.addMeasurement(measurement, tags, fields, telegraf.Summary, timestamp...) 194 } 195 196 func (a *Accumulator) AddHistogram( 197 measurement string, 198 fields map[string]interface{}, 199 tags map[string]string, 200 timestamp ...time.Time, 201 ) { 202 a.addMeasurement(measurement, tags, fields, telegraf.Histogram, timestamp...) 203 } 204 205 func (a *Accumulator) AddMetric(m telegraf.Metric) { 206 a.Lock() 207 defer a.Unlock() 208 atomic.AddUint64(&a.nMetrics, 1) 209 if a.Cond != nil { 210 a.Cond.Broadcast() 211 } 212 if a.Discard { 213 return 214 } 215 216 // Drop metrics without fields 217 if len(m.FieldList()) == 0 { 218 return 219 } 220 221 a.Metrics = append(a.Metrics, ToTestMetric(m)) 222 a.accumulated = append(a.accumulated, m) 223 } 224 225 func (a *Accumulator) WithTracking(maxTracked int) telegraf.TrackingAccumulator { 226 a.deliverChan = make(chan telegraf.DeliveryInfo, maxTracked) 227 a.delivered = make([]telegraf.DeliveryInfo, 0, maxTracked) 228 return a 229 } 230 231 func (a *Accumulator) AddTrackingMetric(m telegraf.Metric) telegraf.TrackingID { 232 dm, id := metric.WithTracking(m, a.onDelivery) 233 a.AddMetric(dm) 234 return id 235 } 236 237 func (a *Accumulator) AddTrackingMetricGroup(group []telegraf.Metric) telegraf.TrackingID { 238 db, id := metric.WithGroupTracking(group, a.onDelivery) 239 for _, m := range db { 240 a.AddMetric(m) 241 } 242 return id 243 } 244 245 func (a *Accumulator) onDelivery(info telegraf.DeliveryInfo) { 246 select { 247 case a.deliverChan <- info: 248 default: 249 // This is a programming error in the input. More items were sent for 250 // tracking than space requested. 251 panic("channel is full") 252 } 253 } 254 255 func (a *Accumulator) Delivered() <-chan telegraf.DeliveryInfo { 256 return a.deliverChan 257 } 258 259 // AddError appends the given error to Accumulator.Errors. 260 func (a *Accumulator) AddError(err error) { 261 if err == nil { 262 return 263 } 264 a.Lock() 265 a.Errors = append(a.Errors, err) 266 if a.Cond != nil { 267 a.Cond.Broadcast() 268 } 269 a.Unlock() 270 } 271 272 func (a *Accumulator) SetPrecision(_ time.Duration) { 273 } 274 275 func (a *Accumulator) DisablePrecision() { 276 } 277 278 func (a *Accumulator) Debug() bool { 279 // stub for implementing Accumulator interface. 280 return a.debug 281 } 282 283 func (a *Accumulator) SetDebug(debug bool) { 284 // stub for implementing Accumulator interface. 285 a.debug = debug 286 } 287 288 // Get gets the specified measurement point from the accumulator 289 func (a *Accumulator) Get(measurement string) (*Metric, bool) { 290 for _, p := range a.Metrics { 291 if p.Measurement == measurement { 292 return p, true 293 } 294 } 295 296 return nil, false 297 } 298 299 func (a *Accumulator) HasTag(measurement string, key string) bool { 300 for _, p := range a.Metrics { 301 if p.Measurement == measurement { 302 _, ok := p.Tags[key] 303 return ok 304 } 305 } 306 return false 307 } 308 309 func (a *Accumulator) TagSetValue(measurement string, key string) string { 310 for _, p := range a.Metrics { 311 if p.Measurement == measurement { 312 v, ok := p.Tags[key] 313 if ok { 314 return v 315 } 316 } 317 } 318 return "" 319 } 320 321 func (a *Accumulator) TagValue(measurement string, key string) string { 322 for _, p := range a.Metrics { 323 if p.Measurement == measurement { 324 v, ok := p.Tags[key] 325 if !ok { 326 return "" 327 } 328 return v 329 } 330 } 331 return "" 332 } 333 334 // GatherError calls the given Gather function and returns the first error found. 335 func (a *Accumulator) GatherError(gf func(telegraf.Accumulator) error) error { 336 if err := gf(a); err != nil { 337 return err 338 } 339 if len(a.Errors) > 0 { 340 return a.Errors[0] 341 } 342 return nil 343 } 344 345 // NFields returns the total number of fields in the accumulator, across all 346 // measurements 347 func (a *Accumulator) NFields() int { 348 a.Lock() 349 defer a.Unlock() 350 counter := 0 351 for _, pt := range a.Metrics { 352 for range pt.Fields { 353 counter++ 354 } 355 } 356 return counter 357 } 358 359 // Wait waits for the given number of metrics to be added to the accumulator. 360 func (a *Accumulator) Wait(n int) { 361 a.Lock() 362 defer a.Unlock() 363 if a.Cond == nil { 364 a.Cond = sync.NewCond(&a.Mutex) 365 } 366 for int(a.NMetrics()) < n { 367 a.Cond.Wait() 368 } 369 } 370 371 // WaitError waits for the given number of errors to be added to the accumulator. 372 func (a *Accumulator) WaitError(n int) { 373 a.Lock() 374 if a.Cond == nil { 375 a.Cond = sync.NewCond(&a.Mutex) 376 } 377 for len(a.Errors) < n { 378 a.Cond.Wait() 379 } 380 a.Unlock() 381 } 382 383 func (a *Accumulator) AssertContainsTaggedFields( 384 t *testing.T, 385 measurement string, 386 fields map[string]interface{}, 387 tags map[string]string, 388 ) { 389 a.Lock() 390 defer a.Unlock() 391 for _, p := range a.Metrics { 392 if !reflect.DeepEqual(tags, p.Tags) { 393 continue 394 } 395 396 if p.Measurement == measurement && reflect.DeepEqual(fields, p.Fields) { 397 return 398 } 399 } 400 // We've failed. spit out some debug logging 401 for _, p := range a.Metrics { 402 if p.Measurement == measurement { 403 t.Log("measurement", p.Measurement, "tags", p.Tags, "fields", p.Fields) 404 } 405 } 406 msg := fmt.Sprintf("unknown measurement %q with tags %v", measurement, tags) 407 require.Fail(t, msg) 408 } 409 410 func (a *Accumulator) AssertDoesNotContainsTaggedFields( 411 t *testing.T, 412 measurement string, 413 fields map[string]interface{}, 414 tags map[string]string, 415 ) { 416 a.Lock() 417 defer a.Unlock() 418 for _, p := range a.Metrics { 419 if !reflect.DeepEqual(tags, p.Tags) { 420 continue 421 } 422 423 if p.Measurement == measurement && reflect.DeepEqual(fields, p.Fields) { 424 msg := fmt.Sprintf( 425 "found measurement %s with tagged fields (tags %v) which should not be there", 426 measurement, tags) 427 require.Fail(t, msg) 428 } 429 } 430 } 431 func (a *Accumulator) AssertContainsFields( 432 t *testing.T, 433 measurement string, 434 fields map[string]interface{}, 435 ) { 436 a.Lock() 437 defer a.Unlock() 438 for _, p := range a.Metrics { 439 if p.Measurement == measurement { 440 require.Equal(t, fields, p.Fields) 441 return 442 } 443 } 444 msg := fmt.Sprintf("unknown measurement %q", measurement) 445 require.Fail(t, msg) 446 } 447 448 func (a *Accumulator) HasPoint( 449 measurement string, 450 tags map[string]string, 451 fieldKey string, 452 fieldValue interface{}, 453 ) bool { 454 a.Lock() 455 defer a.Unlock() 456 for _, p := range a.Metrics { 457 if p.Measurement != measurement { 458 continue 459 } 460 461 if !reflect.DeepEqual(tags, p.Tags) { 462 continue 463 } 464 465 v, ok := p.Fields[fieldKey] 466 if ok && reflect.DeepEqual(v, fieldValue) { 467 return true 468 } 469 } 470 return false 471 } 472 473 func (a *Accumulator) AssertDoesNotContainMeasurement(t *testing.T, measurement string) { 474 a.Lock() 475 defer a.Unlock() 476 for _, p := range a.Metrics { 477 if p.Measurement == measurement { 478 msg := "found unexpected measurement " + measurement 479 require.Fail(t, msg) 480 } 481 } 482 } 483 484 // HasTimestamp returns true if the measurement has a matching Time value 485 func (a *Accumulator) HasTimestamp(measurement string, timestamp time.Time) bool { 486 a.Lock() 487 defer a.Unlock() 488 for _, p := range a.Metrics { 489 if p.Measurement == measurement { 490 return timestamp.Equal(p.Time) 491 } 492 } 493 494 return false 495 } 496 497 // HasField returns true if the given measurement has a field with the given 498 // name 499 func (a *Accumulator) HasField(measurement string, field string) bool { 500 a.Lock() 501 defer a.Unlock() 502 for _, p := range a.Metrics { 503 if p.Measurement == measurement { 504 if _, ok := p.Fields[field]; ok { 505 return true 506 } 507 } 508 } 509 510 return false 511 } 512 513 // HasIntField returns true if the measurement has an Int value 514 func (a *Accumulator) HasIntField(measurement string, field string) bool { 515 a.Lock() 516 defer a.Unlock() 517 for _, p := range a.Metrics { 518 if p.Measurement == measurement { 519 for fieldname, value := range p.Fields { 520 if fieldname == field { 521 _, ok := value.(int) 522 return ok 523 } 524 } 525 } 526 } 527 528 return false 529 } 530 531 // HasInt64Field returns true if the measurement has an Int64 value 532 func (a *Accumulator) HasInt64Field(measurement string, field string) bool { 533 a.Lock() 534 defer a.Unlock() 535 for _, p := range a.Metrics { 536 if p.Measurement == measurement { 537 for fieldname, value := range p.Fields { 538 if fieldname == field { 539 _, ok := value.(int64) 540 return ok 541 } 542 } 543 } 544 } 545 546 return false 547 } 548 549 // HasInt32Field returns true if the measurement has an Int value 550 func (a *Accumulator) HasInt32Field(measurement string, field string) bool { 551 a.Lock() 552 defer a.Unlock() 553 for _, p := range a.Metrics { 554 if p.Measurement == measurement { 555 for fieldname, value := range p.Fields { 556 if fieldname == field { 557 _, ok := value.(int32) 558 return ok 559 } 560 } 561 } 562 } 563 564 return false 565 } 566 567 // HasStringField returns true if the measurement has a String value 568 func (a *Accumulator) HasStringField(measurement string, field string) bool { 569 a.Lock() 570 defer a.Unlock() 571 for _, p := range a.Metrics { 572 if p.Measurement == measurement { 573 for fieldname, value := range p.Fields { 574 if fieldname == field { 575 _, ok := value.(string) 576 return ok 577 } 578 } 579 } 580 } 581 582 return false 583 } 584 585 // HasUIntField returns true if the measurement has a UInt value 586 func (a *Accumulator) HasUIntField(measurement string, field string) bool { 587 a.Lock() 588 defer a.Unlock() 589 for _, p := range a.Metrics { 590 if p.Measurement == measurement { 591 for fieldname, value := range p.Fields { 592 if fieldname == field { 593 _, ok := value.(uint64) 594 return ok 595 } 596 } 597 } 598 } 599 600 return false 601 } 602 603 // HasFloatField returns true if the given measurement has a float value 604 func (a *Accumulator) HasFloatField(measurement string, field string) bool { 605 a.Lock() 606 defer a.Unlock() 607 for _, p := range a.Metrics { 608 if p.Measurement == measurement { 609 for fieldname, value := range p.Fields { 610 if fieldname == field { 611 _, ok := value.(float64) 612 return ok 613 } 614 } 615 } 616 } 617 618 return false 619 } 620 621 // HasMeasurement returns true if the accumulator has a measurement with the 622 // given name 623 func (a *Accumulator) HasMeasurement(measurement string) bool { 624 a.Lock() 625 defer a.Unlock() 626 for _, p := range a.Metrics { 627 if p.Measurement == measurement { 628 return true 629 } 630 } 631 return false 632 } 633 634 // IntField returns the int value of the given measurement and field or false. 635 func (a *Accumulator) IntField(measurement string, field string) (int, bool) { 636 a.Lock() 637 defer a.Unlock() 638 for _, p := range a.Metrics { 639 if p.Measurement == measurement { 640 for fieldname, value := range p.Fields { 641 if fieldname == field { 642 v, ok := value.(int) 643 return v, ok 644 } 645 } 646 } 647 } 648 649 return 0, false 650 } 651 652 // Int64Field returns the int64 value of the given measurement and field or false. 653 func (a *Accumulator) Int64Field(measurement string, field string) (int64, bool) { 654 a.Lock() 655 defer a.Unlock() 656 for _, p := range a.Metrics { 657 if p.Measurement == measurement { 658 for fieldname, value := range p.Fields { 659 if fieldname == field { 660 v, ok := value.(int64) 661 return v, ok 662 } 663 } 664 } 665 } 666 667 return 0, false 668 } 669 670 // Uint64Field returns the int64 value of the given measurement and field or false. 671 func (a *Accumulator) Uint64Field(measurement string, field string) (uint64, bool) { 672 a.Lock() 673 defer a.Unlock() 674 for _, p := range a.Metrics { 675 if p.Measurement == measurement { 676 for fieldname, value := range p.Fields { 677 if fieldname == field { 678 v, ok := value.(uint64) 679 return v, ok 680 } 681 } 682 } 683 } 684 685 return 0, false 686 } 687 688 // Int32Field returns the int32 value of the given measurement and field or false. 689 func (a *Accumulator) Int32Field(measurement string, field string) (int32, bool) { 690 a.Lock() 691 defer a.Unlock() 692 for _, p := range a.Metrics { 693 if p.Measurement == measurement { 694 for fieldname, value := range p.Fields { 695 if fieldname == field { 696 v, ok := value.(int32) 697 return v, ok 698 } 699 } 700 } 701 } 702 703 return 0, false 704 } 705 706 // FloatField returns the float64 value of the given measurement and field or false. 707 func (a *Accumulator) FloatField(measurement string, field string) (float64, bool) { 708 a.Lock() 709 defer a.Unlock() 710 for _, p := range a.Metrics { 711 if p.Measurement == measurement { 712 for fieldname, value := range p.Fields { 713 if fieldname == field { 714 v, ok := value.(float64) 715 return v, ok 716 } 717 } 718 } 719 } 720 721 return 0.0, false 722 } 723 724 // StringField returns the string value of the given measurement and field or false. 725 func (a *Accumulator) StringField(measurement string, field string) (string, bool) { 726 a.Lock() 727 defer a.Unlock() 728 for _, p := range a.Metrics { 729 if p.Measurement == measurement { 730 for fieldname, value := range p.Fields { 731 if fieldname == field { 732 v, ok := value.(string) 733 return v, ok 734 } 735 } 736 } 737 } 738 return "", false 739 } 740 741 // BoolField returns the bool value of the given measurement and field or false. 742 func (a *Accumulator) BoolField(measurement string, field string) (v bool, ok bool) { 743 a.Lock() 744 defer a.Unlock() 745 for _, p := range a.Metrics { 746 if p.Measurement == measurement { 747 for fieldname, value := range p.Fields { 748 if fieldname == field { 749 v, ok = value.(bool) 750 return v, ok 751 } 752 } 753 } 754 } 755 756 return false, false 757 } 758 759 // NopAccumulator is used for benchmarking to isolate the plugin from the internal 760 // telegraf accumulator machinery. 761 type NopAccumulator struct{} 762 763 func (n *NopAccumulator) AddFields(_ string, _ map[string]interface{}, _ map[string]string, _ ...time.Time) { 764 } 765 func (n *NopAccumulator) AddGauge(_ string, _ map[string]interface{}, _ map[string]string, _ ...time.Time) { 766 } 767 func (n *NopAccumulator) AddCounter(_ string, _ map[string]interface{}, _ map[string]string, _ ...time.Time) { 768 } 769 func (n *NopAccumulator) AddSummary(_ string, _ map[string]interface{}, _ map[string]string, _ ...time.Time) { 770 } 771 func (n *NopAccumulator) AddHistogram(_ string, _ map[string]interface{}, _ map[string]string, _ ...time.Time) { 772 } 773 func (n *NopAccumulator) AddMetric(telegraf.Metric) {} 774 func (n *NopAccumulator) SetPrecision(_ time.Duration) {} 775 func (n *NopAccumulator) AddError(_ error) {} 776 func (n *NopAccumulator) WithTracking(_ int) telegraf.TrackingAccumulator { return nil }