1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

85
metric/deserialize.go Normal file
View file

@ -0,0 +1,85 @@
package metric
import (
"bytes"
"encoding/gob"
"errors"
"fmt"
"sync"
"github.com/influxdata/telegraf"
)
// storage for tracking data that can't be serialized to disk
var (
// grouped tracking metrics means that ID->Data association is not one to one,
// many metrics could be associated with one tracking ID so we cannot just
// clear this every time in FromBytes.
trackingStore = make(map[telegraf.TrackingID]telegraf.TrackingData)
mu = sync.Mutex{}
// ErrSkipTracking indicates that tracking information could not be found after
// deserializing a metric from bytes. In this case we should skip the metric
// and continue as if it does not exist.
ErrSkipTracking = errors.New("metric tracking data not found")
)
type serializedMetric struct {
M telegraf.Metric
TID telegraf.TrackingID
}
func ToBytes(m telegraf.Metric) ([]byte, error) {
var sm serializedMetric
if um, ok := m.(telegraf.UnwrappableMetric); ok {
sm.M = um.Unwrap()
} else {
sm.M = m
}
if tm, ok := m.(telegraf.TrackingMetric); ok {
sm.TID = tm.TrackingID()
mu.Lock()
trackingStore[sm.TID] = tm.TrackingData()
mu.Unlock()
}
var buf bytes.Buffer
encoder := gob.NewEncoder(&buf)
if err := encoder.Encode(&sm); err != nil {
return nil, fmt.Errorf("failed to encode metric to bytes: %w", err)
}
return buf.Bytes(), nil
}
func FromBytes(b []byte) (telegraf.Metric, error) {
buf := bytes.NewBuffer(b)
decoder := gob.NewDecoder(buf)
var sm *serializedMetric
if err := decoder.Decode(&sm); err != nil {
return nil, fmt.Errorf("failed to decode metric from bytes: %w", err)
}
m := sm.M
if sm.TID != 0 {
mu.Lock()
td := trackingStore[sm.TID]
if td == nil {
mu.Unlock()
return nil, ErrSkipTracking
}
rc := td.RefCount()
if rc <= 1 {
// only 1 metric left referencing this tracking ID, we can remove here since no subsequent metrics
// read can use this ID. If another metric in a metric group with this ID gets added later, it will
// simply be added back into the tracking store again.
trackingStore[sm.TID] = nil
}
mu.Unlock()
m = rebuildTrackingMetric(m, td)
}
return m, nil
}

7
metric/init.go Normal file
View file

@ -0,0 +1,7 @@
package metric
import "encoding/gob"
func Init() {
gob.RegisterName("metric.metric", &metric{})
}

387
metric/metric.go Normal file
View file

@ -0,0 +1,387 @@
package metric
import (
"fmt"
"hash/fnv"
"sort"
"time"
"github.com/influxdata/telegraf"
)
type metric struct {
MetricName string
MetricTags []*telegraf.Tag
MetricFields []*telegraf.Field
MetricTime time.Time
MetricType telegraf.ValueType
}
func New(
name string,
tags map[string]string,
fields map[string]interface{},
tm time.Time,
tp ...telegraf.ValueType,
) telegraf.Metric {
var vtype telegraf.ValueType
if len(tp) > 0 {
vtype = tp[0]
} else {
vtype = telegraf.Untyped
}
m := &metric{
MetricName: name,
MetricTags: nil,
MetricFields: nil,
MetricTime: tm,
MetricType: vtype,
}
if len(tags) > 0 {
m.MetricTags = make([]*telegraf.Tag, 0, len(tags))
for k, v := range tags {
m.MetricTags = append(m.MetricTags,
&telegraf.Tag{Key: k, Value: v})
}
sort.Slice(m.MetricTags, func(i, j int) bool { return m.MetricTags[i].Key < m.MetricTags[j].Key })
}
if len(fields) > 0 {
m.MetricFields = make([]*telegraf.Field, 0, len(fields))
for k, v := range fields {
v := convertField(v)
if v == nil {
continue
}
m.MetricFields = append(m.MetricFields, &telegraf.Field{Key: k, Value: v})
}
}
return m
}
// FromMetric returns a deep copy of the metric with any tracking information
// removed.
func FromMetric(other telegraf.Metric) telegraf.Metric {
m := &metric{
MetricName: other.Name(),
MetricTags: make([]*telegraf.Tag, len(other.TagList())),
MetricFields: make([]*telegraf.Field, len(other.FieldList())),
MetricTime: other.Time(),
MetricType: other.Type(),
}
for i, tag := range other.TagList() {
m.MetricTags[i] = &telegraf.Tag{Key: tag.Key, Value: tag.Value}
}
for i, field := range other.FieldList() {
m.MetricFields[i] = &telegraf.Field{Key: field.Key, Value: field.Value}
}
return m
}
func (m *metric) String() string {
return fmt.Sprintf("%s %v %v %d", m.MetricName, m.Tags(), m.Fields(), m.MetricTime.UnixNano())
}
func (m *metric) Name() string {
return m.MetricName
}
func (m *metric) Tags() map[string]string {
tags := make(map[string]string, len(m.MetricTags))
for _, tag := range m.MetricTags {
tags[tag.Key] = tag.Value
}
return tags
}
func (m *metric) TagList() []*telegraf.Tag {
return m.MetricTags
}
func (m *metric) Fields() map[string]interface{} {
fields := make(map[string]interface{}, len(m.MetricFields))
for _, field := range m.MetricFields {
fields[field.Key] = field.Value
}
return fields
}
func (m *metric) FieldList() []*telegraf.Field {
return m.MetricFields
}
func (m *metric) Time() time.Time {
return m.MetricTime
}
func (m *metric) Type() telegraf.ValueType {
return m.MetricType
}
func (m *metric) SetName(name string) {
m.MetricName = name
}
func (m *metric) AddPrefix(prefix string) {
m.MetricName = prefix + m.MetricName
}
func (m *metric) AddSuffix(suffix string) {
m.MetricName = m.MetricName + suffix
}
func (m *metric) AddTag(key, value string) {
for i, tag := range m.MetricTags {
if key > tag.Key {
continue
}
if key == tag.Key {
tag.Value = value
return
}
m.MetricTags = append(m.MetricTags, nil)
copy(m.MetricTags[i+1:], m.MetricTags[i:])
m.MetricTags[i] = &telegraf.Tag{Key: key, Value: value}
return
}
m.MetricTags = append(m.MetricTags, &telegraf.Tag{Key: key, Value: value})
}
func (m *metric) HasTag(key string) bool {
for _, tag := range m.MetricTags {
if tag.Key == key {
return true
}
}
return false
}
func (m *metric) GetTag(key string) (string, bool) {
for _, tag := range m.MetricTags {
if tag.Key == key {
return tag.Value, true
}
}
return "", false
}
func (m *metric) Tag(key string) string {
v, _ := m.GetTag(key)
return v
}
func (m *metric) RemoveTag(key string) {
for i, tag := range m.MetricTags {
if tag.Key == key {
copy(m.MetricTags[i:], m.MetricTags[i+1:])
m.MetricTags[len(m.MetricTags)-1] = nil
m.MetricTags = m.MetricTags[:len(m.MetricTags)-1]
return
}
}
}
func (m *metric) AddField(key string, value interface{}) {
for i, field := range m.MetricFields {
if key == field.Key {
m.MetricFields[i] = &telegraf.Field{Key: key, Value: convertField(value)}
return
}
}
m.MetricFields = append(m.MetricFields, &telegraf.Field{Key: key, Value: convertField(value)})
}
func (m *metric) HasField(key string) bool {
for _, field := range m.MetricFields {
if field.Key == key {
return true
}
}
return false
}
func (m *metric) GetField(key string) (interface{}, bool) {
for _, field := range m.MetricFields {
if field.Key == key {
return field.Value, true
}
}
return nil, false
}
func (m *metric) Field(key string) interface{} {
if v, found := m.GetField(key); found {
return v
}
return nil
}
func (m *metric) RemoveField(key string) {
for i, field := range m.MetricFields {
if field.Key == key {
copy(m.MetricFields[i:], m.MetricFields[i+1:])
m.MetricFields[len(m.MetricFields)-1] = nil
m.MetricFields = m.MetricFields[:len(m.MetricFields)-1]
return
}
}
}
func (m *metric) SetTime(t time.Time) {
m.MetricTime = t
}
func (m *metric) SetType(t telegraf.ValueType) {
m.MetricType = t
}
func (m *metric) Copy() telegraf.Metric {
m2 := &metric{
MetricName: m.MetricName,
MetricTags: make([]*telegraf.Tag, len(m.MetricTags)),
MetricFields: make([]*telegraf.Field, len(m.MetricFields)),
MetricTime: m.MetricTime,
MetricType: m.MetricType,
}
for i, tag := range m.MetricTags {
m2.MetricTags[i] = &telegraf.Tag{Key: tag.Key, Value: tag.Value}
}
for i, field := range m.MetricFields {
m2.MetricFields[i] = &telegraf.Field{Key: field.Key, Value: field.Value}
}
return m2
}
func (m *metric) HashID() uint64 {
h := fnv.New64a()
h.Write([]byte(m.MetricName))
h.Write([]byte("\n"))
for _, tag := range m.MetricTags {
h.Write([]byte(tag.Key))
h.Write([]byte("\n"))
h.Write([]byte(tag.Value))
h.Write([]byte("\n"))
}
return h.Sum64()
}
func (*metric) Accept() {
}
func (*metric) Reject() {
}
func (*metric) Drop() {
}
// Convert field to a supported type or nil if inconvertible
func convertField(v interface{}) interface{} {
switch v := v.(type) {
case float64:
return v
case int64:
return v
case string:
return v
case bool:
return v
case int:
return int64(v)
case uint:
return uint64(v)
case uint64:
return v
case []byte:
return string(v)
case int32:
return int64(v)
case int16:
return int64(v)
case int8:
return int64(v)
case uint32:
return uint64(v)
case uint16:
return uint64(v)
case uint8:
return uint64(v)
case float32:
return float64(v)
case *float64:
if v != nil {
return *v
}
case *int64:
if v != nil {
return *v
}
case *string:
if v != nil {
return *v
}
case *bool:
if v != nil {
return *v
}
case *int:
if v != nil {
return int64(*v)
}
case *uint:
if v != nil {
return uint64(*v)
}
case *uint64:
if v != nil {
return *v
}
case *[]byte:
if v != nil {
return string(*v)
}
case *int32:
if v != nil {
return int64(*v)
}
case *int16:
if v != nil {
return int64(*v)
}
case *int8:
if v != nil {
return int64(*v)
}
case *uint32:
if v != nil {
return uint64(*v)
}
case *uint16:
if v != nil {
return uint64(*v)
}
case *uint8:
if v != nil {
return uint64(*v)
}
case *float32:
if v != nil {
return float64(*v)
}
default:
return nil
}
return nil
}

328
metric/metric_test.go Normal file
View file

@ -0,0 +1,328 @@
package metric
import (
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf"
)
func TestNewMetric(t *testing.T) {
now := time.Now()
tags := map[string]string{
"host": "localhost",
"datacenter": "us-east-1",
}
fields := map[string]interface{}{
"usage_idle": float64(99),
"usage_busy": float64(1),
}
m := New("cpu", tags, fields, now)
require.Equal(t, "cpu", m.Name())
require.Equal(t, tags, m.Tags())
require.Equal(t, fields, m.Fields())
require.Len(t, m.FieldList(), 2)
require.Equal(t, now, m.Time())
}
// cpu value=1
func baseMetric() telegraf.Metric {
tags := map[string]string{}
fields := map[string]interface{}{
"value": float64(1),
}
now := time.Now()
m := New("cpu", tags, fields, now)
return m
}
func TestHasTag(t *testing.T) {
m := baseMetric()
require.False(t, m.HasTag("host"))
m.AddTag("host", "localhost")
require.True(t, m.HasTag("host"))
m.RemoveTag("host")
require.False(t, m.HasTag("host"))
}
func TestAddTagOverwrites(t *testing.T) {
m := baseMetric()
m.AddTag("host", "localhost")
m.AddTag("host", "example.org")
value, ok := m.GetTag("host")
require.True(t, ok)
require.Equal(t, "example.org", value)
require.Len(t, m.TagList(), 1)
}
func TestRemoveTagNoEffectOnMissingTags(t *testing.T) {
m := baseMetric()
m.RemoveTag("foo")
m.AddTag("a", "x")
m.RemoveTag("foo")
m.RemoveTag("bar")
value, ok := m.GetTag("a")
require.True(t, ok)
require.Equal(t, "x", value)
}
func TestGetTag(t *testing.T) {
m := baseMetric()
_, ok := m.GetTag("host")
require.False(t, ok)
m.AddTag("host", "localhost")
value, ok := m.GetTag("host")
require.True(t, ok)
require.Equal(t, "localhost", value)
m.RemoveTag("host")
_, ok = m.GetTag("host")
require.False(t, ok)
}
func TestHasField(t *testing.T) {
m := baseMetric()
require.False(t, m.HasField("x"))
m.AddField("x", 42.0)
require.True(t, m.HasField("x"))
m.RemoveTag("x")
require.False(t, m.HasTag("x"))
}
func TestAddFieldOverwrites(t *testing.T) {
m := baseMetric()
m.AddField("value", 1.0)
m.AddField("value", 42.0)
require.Len(t, m.FieldList(), 1)
value, ok := m.GetField("value")
require.True(t, ok)
require.InDelta(t, 42.0, value, 0.001)
}
func TestAddFieldChangesType(t *testing.T) {
m := baseMetric()
m.AddField("value", 1.0)
m.AddField("value", "xyzzy")
require.Len(t, m.FieldList(), 1)
value, ok := m.GetField("value")
require.True(t, ok)
require.Equal(t, "xyzzy", value)
}
func TestRemoveFieldNoEffectOnMissingFields(t *testing.T) {
m := baseMetric()
m.RemoveField("foo")
m.AddField("a", "x")
m.RemoveField("foo")
m.RemoveField("bar")
value, ok := m.GetField("a")
require.True(t, ok)
require.Equal(t, "x", value)
}
func TestGetField(t *testing.T) {
m := baseMetric()
_, ok := m.GetField("foo")
require.False(t, ok)
m.AddField("foo", "bar")
value, ok := m.GetField("foo")
require.True(t, ok)
require.Equal(t, "bar", value)
m.RemoveTag("foo")
_, ok = m.GetTag("foo")
require.False(t, ok)
}
func TestTagList_Sorted(t *testing.T) {
m := baseMetric()
m.AddTag("b", "y")
m.AddTag("c", "z")
m.AddTag("a", "x")
taglist := m.TagList()
require.Equal(t, "a", taglist[0].Key)
require.Equal(t, "b", taglist[1].Key)
require.Equal(t, "c", taglist[2].Key)
}
func TestEquals(t *testing.T) {
now := time.Now()
m1 := New("cpu",
map[string]string{
"host": "localhost",
},
map[string]interface{}{
"value": 42.0,
},
now,
)
m2 := New("cpu",
map[string]string{
"host": "localhost",
},
map[string]interface{}{
"value": 42.0,
},
now,
)
lhs := m1.(*metric)
require.Equal(t, lhs, m2)
m3 := m2.Copy()
require.Equal(t, lhs, m3)
m3.AddTag("a", "x")
require.NotEqual(t, lhs, m3)
}
func TestHashID(t *testing.T) {
m := New(
"cpu",
map[string]string{
"datacenter": "us-east-1",
"mytag": "foo",
"another": "tag",
},
map[string]interface{}{
"value": float64(1),
},
time.Now(),
)
hash := m.HashID()
// adding a field doesn't change the hash:
m.AddField("foo", int64(100))
require.Equal(t, hash, m.HashID())
// removing a non-existent tag doesn't change the hash:
m.RemoveTag("no-op")
require.Equal(t, hash, m.HashID())
// adding a tag does change it:
m.AddTag("foo", "bar")
require.NotEqual(t, hash, m.HashID())
hash = m.HashID()
// removing a tag also changes it:
m.RemoveTag("mytag")
require.NotEqual(t, hash, m.HashID())
}
func TestHashID_Consistency(t *testing.T) {
m := New(
"cpu",
map[string]string{
"datacenter": "us-east-1",
"mytag": "foo",
"another": "tag",
},
map[string]interface{}{
"value": float64(1),
},
time.Now(),
)
hash := m.HashID()
m2 := New(
"cpu",
map[string]string{
"datacenter": "us-east-1",
"mytag": "foo",
"another": "tag",
},
map[string]interface{}{
"value": float64(1),
},
time.Now(),
)
require.Equal(t, hash, m2.HashID())
m3 := m.Copy()
require.Equal(t, m2.HashID(), m3.HashID())
}
func TestHashID_Delimiting(t *testing.T) {
m1 := New(
"cpu",
map[string]string{
"a": "x",
"b": "y",
"c": "z",
},
map[string]interface{}{
"value": float64(1),
},
time.Now(),
)
m2 := New(
"cpu",
map[string]string{
"a": "xbycz",
},
map[string]interface{}{
"value": float64(1),
},
time.Now(),
)
require.NotEqual(t, m1.HashID(), m2.HashID())
}
func TestSetName(t *testing.T) {
m := baseMetric()
m.SetName("foo")
require.Equal(t, "foo", m.Name())
}
func TestAddPrefix(t *testing.T) {
m := baseMetric()
m.AddPrefix("foo_")
require.Equal(t, "foo_cpu", m.Name())
m.AddPrefix("foo_")
require.Equal(t, "foo_foo_cpu", m.Name())
}
func TestAddSuffix(t *testing.T) {
m := baseMetric()
m.AddSuffix("_foo")
require.Equal(t, "cpu_foo", m.Name())
m.AddSuffix("_foo")
require.Equal(t, "cpu_foo_foo", m.Name())
}
func TestValueType(t *testing.T) {
now := time.Now()
tags := map[string]string{}
fields := map[string]interface{}{
"value": float64(42),
}
m := New("cpu", tags, fields, now, telegraf.Gauge)
require.Equal(t, telegraf.Gauge, m.Type())
}

106
metric/series_grouper.go Normal file
View file

@ -0,0 +1,106 @@
package metric
import (
"encoding/binary"
"hash/maphash"
"sort"
"time"
"github.com/influxdata/telegraf"
)
// NewSeriesGrouper returns a type that can be used to group fields by series
// and time, so that fields which share these values will be combined into a
// single telegraf.Metric.
//
// This is useful to build telegraf.Metric's when all fields for a series are
// not available at once.
//
// ex:
// - cpu,host=localhost usage_time=42
// - cpu,host=localhost idle_time=42
// + cpu,host=localhost idle_time=42,usage_time=42
func NewSeriesGrouper() *SeriesGrouper {
return &SeriesGrouper{
metrics: make(map[uint64]telegraf.Metric),
ordered: make([]telegraf.Metric, 0),
hashSeed: maphash.MakeSeed(),
}
}
type SeriesGrouper struct {
metrics map[uint64]telegraf.Metric
ordered []telegraf.Metric
hashSeed maphash.Seed
}
// Add adds a field key and value to the series.
func (g *SeriesGrouper) Add(
measurement string,
tags map[string]string,
tm time.Time,
field string,
fieldValue interface{},
) {
taglist := make([]*telegraf.Tag, 0, len(tags))
for k, v := range tags {
taglist = append(taglist,
&telegraf.Tag{Key: k, Value: v})
}
sort.Slice(taglist, func(i, j int) bool { return taglist[i].Key < taglist[j].Key })
id := groupID(g.hashSeed, measurement, taglist, tm)
m := g.metrics[id]
if m == nil {
m = New(measurement, tags, map[string]interface{}{field: fieldValue}, tm)
g.metrics[id] = m
g.ordered = append(g.ordered, m)
} else {
m.AddField(field, fieldValue)
}
}
// AddMetric adds a metric to the series, merging with any previous matching metrics.
func (g *SeriesGrouper) AddMetric(
metric telegraf.Metric,
) {
id := groupID(g.hashSeed, metric.Name(), metric.TagList(), metric.Time())
m := g.metrics[id]
if m == nil {
m = metric.Copy()
g.metrics[id] = m
g.ordered = append(g.ordered, m)
} else {
for _, f := range metric.FieldList() {
m.AddField(f.Key, f.Value)
}
}
}
// Metrics returns the metrics grouped by series and time.
func (g *SeriesGrouper) Metrics() []telegraf.Metric {
return g.ordered
}
func groupID(seed maphash.Seed, measurement string, taglist []*telegraf.Tag, tm time.Time) uint64 {
var mh maphash.Hash
mh.SetSeed(seed)
mh.WriteString(measurement)
mh.WriteByte(0)
for _, tag := range taglist {
mh.WriteString(tag.Key)
mh.WriteByte(0)
mh.WriteString(tag.Value)
mh.WriteByte(0)
}
mh.WriteByte(0)
var tsBuf [8]byte
binary.BigEndian.PutUint64(tsBuf[:], uint64(tm.UnixNano()))
mh.Write(tsBuf[:])
return mh.Sum64()
}

View file

@ -0,0 +1,37 @@
package metric
import (
"hash/maphash"
"testing"
"time"
)
var m = New(
"mymetric",
map[string]string{
"host": "host.example.com",
"mykey": "myvalue",
"another key": "another value",
},
map[string]interface{}{
"f1": 1,
"f2": 2,
"f3": 3,
"f4": 4,
"f5": 5,
"f6": 6,
"f7": 7,
"f8": 8,
},
time.Now(),
)
var result uint64
var hashSeed = maphash.MakeSeed()
func BenchmarkGroupID(b *testing.B) {
for n := 0; n < b.N; n++ {
result = groupID(hashSeed, m.Name(), m.TagList(), m.Time())
}
}

195
metric/tracking.go Normal file
View file

@ -0,0 +1,195 @@
package metric
import (
"runtime"
"sync/atomic"
"github.com/influxdata/telegraf"
)
// NotifyFunc is called when a tracking metric is done being processed with
// the tracking information.
type NotifyFunc = func(track telegraf.DeliveryInfo)
// WithTracking adds tracking to the metric and registers the notify function
// to be called when processing is complete.
func WithTracking(metric telegraf.Metric, fn NotifyFunc) (telegraf.Metric, telegraf.TrackingID) {
return newTrackingMetric(metric, fn)
}
// WithGroupTracking adds tracking to the metrics and registers the notify
// function to be called when processing is complete.
func WithGroupTracking(metric []telegraf.Metric, fn NotifyFunc) ([]telegraf.Metric, telegraf.TrackingID) {
return newTrackingMetricGroup(metric, fn)
}
var (
lastID uint64
finalizer func(*trackingData)
)
func newTrackingID() telegraf.TrackingID {
return telegraf.TrackingID(atomic.AddUint64(&lastID, 1))
}
type trackingData struct {
//nolint:revive // method is already named ID
Id telegraf.TrackingID
Rc int32
AcceptCount int32
RejectCount int32
notifyFunc NotifyFunc
}
func (d *trackingData) incr() {
atomic.AddInt32(&d.Rc, 1)
}
func (d *trackingData) RefCount() int32 {
return d.Rc
}
func (d *trackingData) decr() int32 {
return atomic.AddInt32(&d.Rc, -1)
}
func (d *trackingData) accept() {
atomic.AddInt32(&d.AcceptCount, 1)
}
func (d *trackingData) reject() {
atomic.AddInt32(&d.RejectCount, 1)
}
func (d *trackingData) notify() {
d.notifyFunc(
&deliveryInfo{
id: d.Id,
accepted: int(d.AcceptCount),
rejected: int(d.RejectCount),
},
)
}
type trackingMetric struct {
telegraf.Metric
d *trackingData
}
func newTrackingMetric(metric telegraf.Metric, fn NotifyFunc) (telegraf.Metric, telegraf.TrackingID) {
m := &trackingMetric{
Metric: metric,
d: &trackingData{
Id: newTrackingID(),
Rc: 1,
AcceptCount: 0,
RejectCount: 0,
notifyFunc: fn,
},
}
if finalizer != nil {
runtime.SetFinalizer(m.d, finalizer)
}
return m, m.d.Id
}
func rebuildTrackingMetric(metric telegraf.Metric, td telegraf.TrackingData) telegraf.Metric {
return &trackingMetric{
Metric: metric,
d: td.(*trackingData),
}
}
func newTrackingMetricGroup(group []telegraf.Metric, fn NotifyFunc) ([]telegraf.Metric, telegraf.TrackingID) {
d := &trackingData{
Id: newTrackingID(),
Rc: 0,
AcceptCount: 0,
RejectCount: 0,
notifyFunc: fn,
}
for i, m := range group {
d.incr()
dm := &trackingMetric{
Metric: m,
d: d,
}
group[i] = dm
}
if finalizer != nil {
runtime.SetFinalizer(d, finalizer)
}
if len(group) == 0 {
d.notify()
}
return group, d.Id
}
func (m *trackingMetric) Copy() telegraf.Metric {
m.d.incr()
return &trackingMetric{
Metric: m.Metric.Copy(),
d: m.d,
}
}
func (m *trackingMetric) Accept() {
m.d.accept()
m.decr()
}
func (m *trackingMetric) Reject() {
m.d.reject()
m.decr()
}
func (m *trackingMetric) Drop() {
m.decr()
}
func (m *trackingMetric) decr() {
v := m.d.decr()
if v < 0 {
panic("negative refcount")
}
if v == 0 {
m.d.notify()
}
}
// Unwrap allows to access the underlying metric directly e.g. for go-templates
func (m *trackingMetric) TrackingID() telegraf.TrackingID {
return m.d.Id
}
func (m *trackingMetric) TrackingData() telegraf.TrackingData {
return m.d
}
// Unwrap allows to access the underlying metric directly e.g. for go-templates
func (m *trackingMetric) Unwrap() telegraf.Metric {
return m.Metric
}
type deliveryInfo struct {
id telegraf.TrackingID
accepted int
rejected int
}
func (r *deliveryInfo) ID() telegraf.TrackingID {
return r.id
}
func (r *deliveryInfo) Delivered() bool {
return r.rejected == 0
}
func (d *trackingData) ID() telegraf.TrackingID {
return d.Id
}

301
metric/tracking_test.go Normal file
View file

@ -0,0 +1,301 @@
package metric
import (
"sync"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf"
)
func mustMetric(
name string,
tags map[string]string,
fields map[string]interface{},
tm time.Time,
tp ...telegraf.ValueType,
) telegraf.Metric {
m := New(name, tags, fields, tm, tp...)
return m
}
type deliveries struct {
Info map[telegraf.TrackingID]telegraf.DeliveryInfo
}
func (d *deliveries) onDelivery(info telegraf.DeliveryInfo) {
d.Info[info.ID()] = info
}
func TestNewTrackingID(t *testing.T) {
var wg sync.WaitGroup
var a [100000]telegraf.TrackingID
var b [100000]telegraf.TrackingID
wg.Add(2)
go func() {
for i := 0; i < len(a); i++ {
a[i] = newTrackingID()
}
wg.Done()
}()
go func() {
for i := 0; i < len(b); i++ {
b[i] = newTrackingID()
}
wg.Done()
}()
wg.Wait()
// Find any duplicate TrackingIDs in arrays a and b. Arrays must be sorted in increasing order.
for i, j := 0, 0; i < len(a) && j < len(b); {
if a[i] == b[j] {
t.Errorf("Duplicate TrackingID: a[%d]==%d and b[%d]==%d.", i, a[i], j, b[j])
break
}
if a[i] > b[j] {
j++
continue
}
if a[i] < b[j] {
i++
continue
}
}
}
func TestTracking(t *testing.T) {
tests := []struct {
name string
metric telegraf.Metric
actions func(metric telegraf.Metric)
delivered bool
}{
{
name: "accept",
metric: mustMetric(
"memory",
map[string]string{},
map[string]interface{}{
"value": 42,
},
time.Unix(0, 0),
telegraf.Gauge,
),
actions: func(m telegraf.Metric) {
m.Accept()
},
delivered: true,
},
{
name: "reject",
metric: mustMetric(
"memory",
map[string]string{},
map[string]interface{}{
"value": 42,
},
time.Unix(0, 0),
telegraf.Gauge,
),
actions: func(m telegraf.Metric) {
m.Reject()
},
delivered: false,
},
{
name: "accept copy",
metric: mustMetric(
"memory",
map[string]string{},
map[string]interface{}{
"value": 42,
},
time.Unix(0, 0),
telegraf.Gauge,
),
actions: func(m telegraf.Metric) {
m2 := m.Copy()
m.Accept()
m2.Accept()
},
delivered: true,
},
{
name: "copy with accept and done",
metric: mustMetric(
"memory",
map[string]string{},
map[string]interface{}{
"value": 42,
},
time.Unix(0, 0),
telegraf.Gauge,
),
actions: func(m telegraf.Metric) {
m2 := m.Copy()
m.Accept()
m2.Drop()
},
delivered: true,
},
{
name: "copy with mixed delivery",
metric: mustMetric(
"memory",
map[string]string{},
map[string]interface{}{
"value": 42,
},
time.Unix(0, 0),
telegraf.Gauge,
),
actions: func(m telegraf.Metric) {
m2 := m.Copy()
m.Accept()
m2.Reject()
},
delivered: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := &deliveries{
Info: make(map[telegraf.TrackingID]telegraf.DeliveryInfo),
}
metric, id := WithTracking(tt.metric, d.onDelivery)
tt.actions(metric)
info := d.Info[id]
require.Equal(t, tt.delivered, info.Delivered())
})
}
}
func TestGroupTracking(t *testing.T) {
tests := []struct {
name string
metrics []telegraf.Metric
actions func(metrics []telegraf.Metric)
delivered bool
}{
{
name: "accept",
metrics: []telegraf.Metric{
mustMetric(
"cpu",
map[string]string{},
map[string]interface{}{
"value": 42,
},
time.Unix(0, 0),
),
mustMetric(
"cpu",
map[string]string{},
map[string]interface{}{
"value": 42,
},
time.Unix(0, 0),
),
},
actions: func(metrics []telegraf.Metric) {
metrics[0].Accept()
metrics[1].Accept()
},
delivered: true,
},
{
name: "reject",
metrics: []telegraf.Metric{
mustMetric(
"cpu",
map[string]string{},
map[string]interface{}{
"value": 42,
},
time.Unix(0, 0),
),
mustMetric(
"cpu",
map[string]string{},
map[string]interface{}{
"value": 42,
},
time.Unix(0, 0),
),
},
actions: func(metrics []telegraf.Metric) {
metrics[0].Reject()
metrics[1].Reject()
},
delivered: false,
},
{
name: "remove",
metrics: []telegraf.Metric{
mustMetric(
"cpu",
map[string]string{},
map[string]interface{}{
"value": 42,
},
time.Unix(0, 0),
),
mustMetric(
"cpu",
map[string]string{},
map[string]interface{}{
"value": 42,
},
time.Unix(0, 0),
),
},
actions: func(metrics []telegraf.Metric) {
metrics[0].Drop()
metrics[1].Drop()
},
delivered: true,
},
{
name: "mixed",
metrics: []telegraf.Metric{
mustMetric(
"cpu",
map[string]string{},
map[string]interface{}{
"value": 42,
},
time.Unix(0, 0),
),
mustMetric(
"cpu",
map[string]string{},
map[string]interface{}{
"value": 42,
},
time.Unix(0, 0),
),
},
actions: func(metrics []telegraf.Metric) {
metrics[0].Accept()
metrics[1].Reject()
},
delivered: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := &deliveries{
Info: make(map[telegraf.TrackingID]telegraf.DeliveryInfo),
}
metrics, id := WithGroupTracking(tt.metrics, d.onDelivery)
tt.actions(metrics)
info := d.Info[id]
require.Equal(t, tt.delivered, info.Delivered())
})
}
}