Remove outputs blocking inputs when output is slow (#4938)

This commit is contained in:
Daniel Nelson
2018-11-05 13:34:28 -08:00
committed by GitHub
parent 74667cd681
commit 6e5c2f8bb6
59 changed files with 3615 additions and 2189 deletions

View File

@@ -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
View 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
View 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())
})
}
}