Remove outputs blocking inputs when output is slow (#4938)
This commit is contained in:
@@ -248,6 +248,15 @@ func (m *metric) HashID() uint64 {
|
||||
return h.Sum64()
|
||||
}
|
||||
|
||||
func (m *metric) Accept() {
|
||||
}
|
||||
|
||||
func (m *metric) Reject() {
|
||||
}
|
||||
|
||||
func (m *metric) Drop() {
|
||||
}
|
||||
|
||||
// Convert field to a supported type or nil if unconvertible
|
||||
func convertField(v interface{}) interface{} {
|
||||
switch v := v.(type) {
|
||||
|
||||
171
metric/tracking.go
Normal file
171
metric/tracking.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package metric
|
||||
|
||||
import (
|
||||
"log"
|
||||
"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)
|
||||
}
|
||||
|
||||
// WithBatchTracking 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)
|
||||
}
|
||||
|
||||
func EnableDebugFinalizer() {
|
||||
finalizer = debugFinalizer
|
||||
}
|
||||
|
||||
var (
|
||||
lastID uint64
|
||||
finalizer func(*trackingData)
|
||||
)
|
||||
|
||||
func newTrackingID() telegraf.TrackingID {
|
||||
atomic.AddUint64(&lastID, 1)
|
||||
return telegraf.TrackingID(lastID)
|
||||
}
|
||||
|
||||
func debugFinalizer(d *trackingData) {
|
||||
rc := atomic.LoadInt32(&d.rc)
|
||||
if rc != 0 {
|
||||
log.Fatalf("E! [agent] metric collected with non-zero reference count rc: %d", rc)
|
||||
}
|
||||
}
|
||||
|
||||
type trackingData struct {
|
||||
id telegraf.TrackingID
|
||||
rc int32
|
||||
acceptCount int32
|
||||
rejectCount int32
|
||||
notify NotifyFunc
|
||||
}
|
||||
|
||||
func (d *trackingData) incr() {
|
||||
atomic.AddInt32(&d.rc, 1)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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,
|
||||
notify: fn,
|
||||
},
|
||||
}
|
||||
|
||||
if finalizer != nil {
|
||||
runtime.SetFinalizer(m.d, finalizer)
|
||||
}
|
||||
return m, m.d.id
|
||||
}
|
||||
|
||||
func newTrackingMetricGroup(group []telegraf.Metric, fn NotifyFunc) ([]telegraf.Metric, telegraf.TrackingID) {
|
||||
d := &trackingData{
|
||||
id: newTrackingID(),
|
||||
rc: 0,
|
||||
acceptCount: 0,
|
||||
rejectCount: 0,
|
||||
notify: fn,
|
||||
}
|
||||
|
||||
for i, m := range group {
|
||||
d.incr()
|
||||
dm := &trackingMetric{
|
||||
Metric: m,
|
||||
d: d,
|
||||
}
|
||||
group[i] = dm
|
||||
|
||||
}
|
||||
if finalizer != nil {
|
||||
runtime.SetFinalizer(d, finalizer)
|
||||
}
|
||||
|
||||
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(
|
||||
&deliveryInfo{
|
||||
id: m.d.id,
|
||||
accepted: int(m.d.acceptCount),
|
||||
rejected: int(m.d.rejectCount),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
260
metric/tracking_test.go
Normal file
260
metric/tracking_test.go
Normal file
@@ -0,0 +1,260 @@
|
||||
package metric
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func mustMetric(
|
||||
name string,
|
||||
tags map[string]string,
|
||||
fields map[string]interface{},
|
||||
tm time.Time,
|
||||
tp ...telegraf.ValueType,
|
||||
) telegraf.Metric {
|
||||
m, err := New(name, tags, fields, tm, tp...)
|
||||
if err != nil {
|
||||
panic("mustMetric")
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
type deliveries struct {
|
||||
Info map[telegraf.TrackingID]telegraf.DeliveryInfo
|
||||
}
|
||||
|
||||
func (d *deliveries) onDelivery(info telegraf.DeliveryInfo) {
|
||||
d.Info[info.ID()] = info
|
||||
}
|
||||
|
||||
func TestTracking(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
metric telegraf.Metric
|
||||
actions func(metric telegraf.Metric)
|
||||
delivered bool
|
||||
}{
|
||||
{
|
||||
name: "accept",
|
||||
metric: mustMetric(
|
||||
"cpu",
|
||||
map[string]string{},
|
||||
map[string]interface{}{
|
||||
"value": 42,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
actions: func(m telegraf.Metric) {
|
||||
m.Accept()
|
||||
},
|
||||
delivered: true,
|
||||
},
|
||||
{
|
||||
name: "reject",
|
||||
metric: mustMetric(
|
||||
"cpu",
|
||||
map[string]string{},
|
||||
map[string]interface{}{
|
||||
"value": 42,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
actions: func(m telegraf.Metric) {
|
||||
m.Reject()
|
||||
},
|
||||
delivered: false,
|
||||
},
|
||||
{
|
||||
name: "accept copy",
|
||||
metric: mustMetric(
|
||||
"cpu",
|
||||
map[string]string{},
|
||||
map[string]interface{}{
|
||||
"value": 42,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
actions: func(m telegraf.Metric) {
|
||||
m2 := m.Copy()
|
||||
m.Accept()
|
||||
m2.Accept()
|
||||
},
|
||||
delivered: true,
|
||||
},
|
||||
{
|
||||
name: "copy with accept and done",
|
||||
metric: mustMetric(
|
||||
"cpu",
|
||||
map[string]string{},
|
||||
map[string]interface{}{
|
||||
"value": 42,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
actions: func(m telegraf.Metric) {
|
||||
m2 := m.Copy()
|
||||
m.Accept()
|
||||
m2.Drop()
|
||||
},
|
||||
delivered: true,
|
||||
},
|
||||
{
|
||||
name: "copy with mixed delivery",
|
||||
metric: mustMetric(
|
||||
"cpu",
|
||||
map[string]string{},
|
||||
map[string]interface{}{
|
||||
"value": 42,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
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())
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user