215 lines
4.5 KiB
Go
215 lines
4.5 KiB
Go
|
package models
|
||
|
|
||
|
import (
|
||
|
"sync"
|
||
|
|
||
|
"github.com/influxdata/telegraf"
|
||
|
"github.com/influxdata/telegraf/selfstat"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
AgentMetricsWritten = selfstat.Register("agent", "metrics_written", map[string]string{})
|
||
|
AgentMetricsDropped = selfstat.Register("agent", "metrics_dropped", map[string]string{})
|
||
|
)
|
||
|
|
||
|
// Buffer stores metrics in a circular buffer.
|
||
|
type Buffer struct {
|
||
|
sync.Mutex
|
||
|
buf []telegraf.Metric
|
||
|
first int // index of the first/oldest metric
|
||
|
last int // one after the index of the last/newest metric
|
||
|
size int // number of metrics currently in the buffer
|
||
|
cap int // the capacity of the buffer
|
||
|
|
||
|
batchFirst int // index of the first metric in the batch
|
||
|
batchLast int // one after the index of the last metric in the batch
|
||
|
batchSize int // number of metrics current in the batch
|
||
|
|
||
|
MetricsAdded selfstat.Stat
|
||
|
MetricsWritten selfstat.Stat
|
||
|
MetricsDropped selfstat.Stat
|
||
|
}
|
||
|
|
||
|
// NewBuffer returns a new empty Buffer with the given capacity.
|
||
|
func NewBuffer(name string, capacity int) *Buffer {
|
||
|
b := &Buffer{
|
||
|
buf: make([]telegraf.Metric, capacity),
|
||
|
first: 0,
|
||
|
last: 0,
|
||
|
size: 0,
|
||
|
cap: capacity,
|
||
|
|
||
|
MetricsAdded: selfstat.Register(
|
||
|
"write",
|
||
|
"metrics_added",
|
||
|
map[string]string{"output": name},
|
||
|
),
|
||
|
MetricsWritten: selfstat.Register(
|
||
|
"write",
|
||
|
"metrics_written",
|
||
|
map[string]string{"output": name},
|
||
|
),
|
||
|
MetricsDropped: selfstat.Register(
|
||
|
"write",
|
||
|
"metrics_dropped",
|
||
|
map[string]string{"output": name},
|
||
|
),
|
||
|
}
|
||
|
return b
|
||
|
}
|
||
|
|
||
|
// Len returns the number of metrics currently in the buffer.
|
||
|
func (b *Buffer) Len() int {
|
||
|
b.Lock()
|
||
|
defer b.Unlock()
|
||
|
|
||
|
return b.size
|
||
|
}
|
||
|
|
||
|
func (b *Buffer) metricAdded() {
|
||
|
b.MetricsAdded.Incr(1)
|
||
|
}
|
||
|
|
||
|
func (b *Buffer) metricWritten(metric telegraf.Metric) {
|
||
|
AgentMetricsWritten.Incr(1)
|
||
|
b.MetricsWritten.Incr(1)
|
||
|
metric.Accept()
|
||
|
}
|
||
|
|
||
|
func (b *Buffer) metricDropped(metric telegraf.Metric) {
|
||
|
AgentMetricsDropped.Incr(1)
|
||
|
b.MetricsDropped.Incr(1)
|
||
|
metric.Reject()
|
||
|
}
|
||
|
|
||
|
func (b *Buffer) inBatch() bool {
|
||
|
if b.batchSize == 0 {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
if b.batchFirst < b.batchLast {
|
||
|
return b.last >= b.batchFirst && b.last < b.batchLast
|
||
|
} else {
|
||
|
return b.last >= b.batchFirst || b.last < b.batchLast
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (b *Buffer) add(m telegraf.Metric) {
|
||
|
// Check if Buffer is full
|
||
|
if b.size == b.cap {
|
||
|
if b.batchSize == 0 {
|
||
|
// No batch taken by the output, we can drop the metric now.
|
||
|
b.metricDropped(b.buf[b.last])
|
||
|
} else if b.inBatch() {
|
||
|
// There is an outstanding batch and this will overwrite a metric
|
||
|
// in it, delay the dropping only in case the batch gets rejected.
|
||
|
b.batchSize--
|
||
|
b.batchFirst++
|
||
|
b.batchFirst %= b.cap
|
||
|
} else {
|
||
|
// There is an outstanding batch, but this overwrites a metric
|
||
|
// outside of it.
|
||
|
b.metricDropped(b.buf[b.last])
|
||
|
}
|
||
|
}
|
||
|
|
||
|
b.metricAdded()
|
||
|
|
||
|
b.buf[b.last] = m
|
||
|
b.last++
|
||
|
b.last %= b.cap
|
||
|
|
||
|
if b.size == b.cap {
|
||
|
b.first++
|
||
|
b.first %= b.cap
|
||
|
}
|
||
|
|
||
|
b.size = min(b.size+1, b.cap)
|
||
|
}
|
||
|
|
||
|
// Add adds metrics to the buffer
|
||
|
func (b *Buffer) Add(metrics ...telegraf.Metric) {
|
||
|
b.Lock()
|
||
|
defer b.Unlock()
|
||
|
|
||
|
for i := range metrics {
|
||
|
b.add(metrics[i])
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Batch returns a slice containing up to batchSize of the most recently added
|
||
|
// metrics.
|
||
|
//
|
||
|
// The metrics contained in the batch are not removed from the buffer, instead
|
||
|
// the last batch is recorded and removed only if Accept is called.
|
||
|
func (b *Buffer) Batch(batchSize int) []telegraf.Metric {
|
||
|
b.Lock()
|
||
|
defer b.Unlock()
|
||
|
|
||
|
outLen := min(b.size, batchSize)
|
||
|
out := make([]telegraf.Metric, outLen)
|
||
|
if outLen == 0 {
|
||
|
return out
|
||
|
}
|
||
|
|
||
|
b.batchFirst = b.first
|
||
|
b.batchLast = b.first + outLen
|
||
|
b.batchLast %= b.cap
|
||
|
b.batchSize = outLen
|
||
|
|
||
|
until := min(b.cap, b.first+outLen)
|
||
|
|
||
|
n := copy(out, b.buf[b.first:until])
|
||
|
if n < outLen {
|
||
|
copy(out[n:], b.buf[:outLen-n])
|
||
|
}
|
||
|
return out
|
||
|
}
|
||
|
|
||
|
// Accept removes the metrics contained in the last batch.
|
||
|
func (b *Buffer) Accept(batch []telegraf.Metric) {
|
||
|
b.Lock()
|
||
|
defer b.Unlock()
|
||
|
|
||
|
for _, m := range batch {
|
||
|
b.metricWritten(m)
|
||
|
}
|
||
|
|
||
|
if b.batchSize > 0 {
|
||
|
b.size -= b.batchSize
|
||
|
b.first += b.batchSize
|
||
|
b.first %= b.cap
|
||
|
}
|
||
|
|
||
|
b.resetBatch()
|
||
|
}
|
||
|
|
||
|
// Reject clears the current batch record so that calls to Accept will have no
|
||
|
// effect.
|
||
|
func (b *Buffer) Reject(batch []telegraf.Metric) {
|
||
|
b.Lock()
|
||
|
defer b.Unlock()
|
||
|
|
||
|
if len(batch) > b.batchSize {
|
||
|
// Part or all of the batch was dropped before reject was called.
|
||
|
for _, m := range batch[b.batchSize:] {
|
||
|
b.metricDropped(m)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
b.resetBatch()
|
||
|
}
|
||
|
|
||
|
func (b *Buffer) resetBatch() {
|
||
|
b.batchFirst = 0
|
||
|
b.batchLast = 0
|
||
|
b.batchSize = 0
|
||
|
}
|
||
|
|
||
|
func min(a, b int) int {
|
||
|
if b < a {
|
||
|
return b
|
||
|
}
|
||
|
return a
|
||
|
}
|