Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
197
selfstat/selfstat.go
Normal file
197
selfstat/selfstat.go
Normal file
|
@ -0,0 +1,197 @@
|
|||
// Package selfstat is a package for tracking and collecting internal statistics
|
||||
// about telegraf. Metrics can be registered using this package, and then
|
||||
// incremented or set within your code. If the inputs.internal plugin is enabled,
|
||||
// then all registered stats will be collected as they would by any other input
|
||||
// plugin.
|
||||
package selfstat
|
||||
|
||||
import (
|
||||
"hash/fnv"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/metric"
|
||||
)
|
||||
|
||||
var (
|
||||
registry *Registry
|
||||
)
|
||||
|
||||
// Stat is an interface for dealing with telegraf statistics collected
|
||||
// on itself.
|
||||
type Stat interface {
|
||||
// Name is the name of the measurement
|
||||
Name() string
|
||||
|
||||
// FieldName is the name of the measurement field
|
||||
FieldName() string
|
||||
|
||||
// Tags is a tag map. Each time this is called a new map is allocated.
|
||||
Tags() map[string]string
|
||||
|
||||
// Incr increments a regular stat by 'v'.
|
||||
// in the case of a timing stat, increment adds the timing to the cache.
|
||||
Incr(v int64)
|
||||
|
||||
// Set sets a regular stat to 'v'.
|
||||
// in the case of a timing stat, set adds the timing to the cache.
|
||||
Set(v int64)
|
||||
|
||||
// Get gets the value of the stat. In the case of timings, this returns
|
||||
// an average value of all timings received since the last call to Get().
|
||||
// If no timings were received, it returns the previous value.
|
||||
Get() int64
|
||||
}
|
||||
|
||||
// Register registers the given measurement, field, and tags in the selfstat
|
||||
// registry. If given an identical measurement, it will return the stat that's
|
||||
// already been registered.
|
||||
//
|
||||
// The returned Stat can be incremented by the consumer of Register(), and it's
|
||||
// value will be returned as a telegraf metric when Metrics() is called.
|
||||
func Register(measurement, field string, tags map[string]string) Stat {
|
||||
return registry.register("internal_"+measurement, field, tags)
|
||||
}
|
||||
|
||||
// RegisterTiming registers the given measurement, field, and tags in the selfstat
|
||||
// registry. If given an identical measurement, it will return the stat that's
|
||||
// already been registered.
|
||||
//
|
||||
// Timing stats differ from regular stats in that they accumulate multiple
|
||||
// "timings" added to them, and will return the average when Get() is called.
|
||||
// After Get() is called, the average is cleared and the next timing returned
|
||||
// from Get() will only reflect timings added since the previous call to Get().
|
||||
// If Get() is called without receiving any new timings, then the previous value
|
||||
// is used.
|
||||
//
|
||||
// In other words, timings are an averaged metric that get cleared on each call
|
||||
// to Get().
|
||||
//
|
||||
// The returned Stat can be incremented by the consumer of Register(), and it's
|
||||
// value will be returned as a telegraf metric when Metrics() is called.
|
||||
func RegisterTiming(measurement, field string, tags map[string]string) Stat {
|
||||
return registry.registerTiming("internal_"+measurement, field, tags)
|
||||
}
|
||||
|
||||
// Metrics returns all registered stats as telegraf metrics.
|
||||
func Metrics() []telegraf.Metric {
|
||||
registry.mu.Lock()
|
||||
now := time.Now()
|
||||
metrics := make([]telegraf.Metric, 0, len(registry.stats))
|
||||
for _, stats := range registry.stats {
|
||||
if len(stats) > 0 {
|
||||
var tags map[string]string
|
||||
var name string
|
||||
fields := make(map[string]interface{}, len(stats))
|
||||
j := 0
|
||||
for fieldname, stat := range stats {
|
||||
if j == 0 {
|
||||
tags = stat.Tags()
|
||||
name = stat.Name()
|
||||
}
|
||||
fields[fieldname] = stat.Get()
|
||||
j++
|
||||
}
|
||||
m := metric.New(name, tags, fields, now)
|
||||
metrics = append(metrics, m)
|
||||
}
|
||||
}
|
||||
registry.mu.Unlock()
|
||||
return metrics
|
||||
}
|
||||
|
||||
type Registry struct {
|
||||
stats map[uint64]map[string]Stat
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (r *Registry) register(measurement, field string, tags map[string]string) Stat {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
key := key(measurement, tags)
|
||||
if stat, ok := registry.get(key, field); ok {
|
||||
return stat
|
||||
}
|
||||
|
||||
t := make(map[string]string, len(tags))
|
||||
for k, v := range tags {
|
||||
t[k] = v
|
||||
}
|
||||
|
||||
s := &stat{
|
||||
measurement: measurement,
|
||||
field: field,
|
||||
tags: t,
|
||||
}
|
||||
registry.set(key, s)
|
||||
return s
|
||||
}
|
||||
|
||||
func (r *Registry) registerTiming(measurement, field string, tags map[string]string) Stat {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
key := key(measurement, tags)
|
||||
if stat, ok := registry.get(key, field); ok {
|
||||
return stat
|
||||
}
|
||||
|
||||
t := make(map[string]string, len(tags))
|
||||
for k, v := range tags {
|
||||
t[k] = v
|
||||
}
|
||||
|
||||
s := &timingStat{
|
||||
measurement: measurement,
|
||||
field: field,
|
||||
tags: t,
|
||||
}
|
||||
registry.set(key, s)
|
||||
return s
|
||||
}
|
||||
|
||||
func (r *Registry) get(key uint64, field string) (Stat, bool) {
|
||||
if _, ok := r.stats[key]; !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if stat, ok := r.stats[key][field]; ok {
|
||||
return stat, true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (r *Registry) set(key uint64, s Stat) {
|
||||
if _, ok := r.stats[key]; !ok {
|
||||
r.stats[key] = make(map[string]Stat)
|
||||
}
|
||||
|
||||
r.stats[key][s.FieldName()] = s
|
||||
}
|
||||
|
||||
func key(measurement string, tags map[string]string) uint64 {
|
||||
h := fnv.New64a()
|
||||
h.Write([]byte(measurement))
|
||||
|
||||
tmp := make([]string, 0, len(tags))
|
||||
for k, v := range tags {
|
||||
tmp = append(tmp, k+v)
|
||||
}
|
||||
sort.Strings(tmp)
|
||||
|
||||
for _, s := range tmp {
|
||||
h.Write([]byte(s))
|
||||
}
|
||||
|
||||
return h.Sum64()
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry = &Registry{
|
||||
stats: make(map[uint64]map[string]Stat),
|
||||
}
|
||||
}
|
213
selfstat/selfstat_test.go
Normal file
213
selfstat/selfstat_test.go
Normal file
|
@ -0,0 +1,213 @@
|
|||
package selfstat
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
)
|
||||
|
||||
var (
|
||||
// only allow one test at a time
|
||||
// this is because we are dealing with a global registry
|
||||
testLock sync.Mutex
|
||||
a int64
|
||||
)
|
||||
|
||||
// testCleanup resets the global registry for test cleanup & unlocks the test lock
|
||||
func testCleanup() {
|
||||
registry = &Registry{
|
||||
stats: make(map[uint64]map[string]Stat),
|
||||
}
|
||||
testLock.Unlock()
|
||||
}
|
||||
|
||||
func BenchmarkStats(b *testing.B) {
|
||||
testLock.Lock()
|
||||
defer testCleanup()
|
||||
b1 := Register("benchmark1", "test_field1", map[string]string{"test": "foo"})
|
||||
for n := 0; n < b.N; n++ {
|
||||
b1.Incr(1)
|
||||
b1.Incr(3)
|
||||
a = b1.Get()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTimingStats(b *testing.B) {
|
||||
testLock.Lock()
|
||||
defer testCleanup()
|
||||
b2 := RegisterTiming("benchmark2", "test_field1", map[string]string{"test": "foo"})
|
||||
for n := 0; n < b.N; n++ {
|
||||
b2.Incr(1)
|
||||
b2.Incr(3)
|
||||
a = b2.Get()
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterAndIncrAndSet(t *testing.T) {
|
||||
testLock.Lock()
|
||||
defer testCleanup()
|
||||
s1 := Register("test", "test_field1", map[string]string{"test": "foo"})
|
||||
s2 := Register("test", "test_field2", map[string]string{"test": "foo"})
|
||||
require.Equal(t, int64(0), s1.Get())
|
||||
|
||||
s1.Incr(10)
|
||||
s1.Incr(5)
|
||||
require.Equal(t, int64(15), s1.Get())
|
||||
|
||||
s1.Set(12)
|
||||
require.Equal(t, int64(12), s1.Get())
|
||||
|
||||
s1.Incr(-2)
|
||||
require.Equal(t, int64(10), s1.Get())
|
||||
|
||||
s2.Set(101)
|
||||
require.Equal(t, int64(101), s2.Get())
|
||||
|
||||
// make sure that the same field returns the same metric
|
||||
// this one should be the same as s2.
|
||||
foo := Register("test", "test_field2", map[string]string{"test": "foo"})
|
||||
require.Equal(t, int64(101), foo.Get())
|
||||
|
||||
// check that tags are consistent
|
||||
require.Equal(t, map[string]string{"test": "foo"}, foo.Tags())
|
||||
require.Equal(t, "internal_test", foo.Name())
|
||||
}
|
||||
|
||||
func TestRegisterTimingAndIncrAndSet(t *testing.T) {
|
||||
testLock.Lock()
|
||||
defer testCleanup()
|
||||
s1 := RegisterTiming("test", "test_field1_ns", map[string]string{"test": "foo"})
|
||||
s2 := RegisterTiming("test", "test_field2_ns", map[string]string{"test": "foo"})
|
||||
require.Equal(t, int64(0), s1.Get())
|
||||
|
||||
s1.Incr(10)
|
||||
s1.Incr(5)
|
||||
require.Equal(t, int64(7), s1.Get())
|
||||
// previous value is used on subsequent calls to Get()
|
||||
require.Equal(t, int64(7), s1.Get())
|
||||
|
||||
s1.Set(12)
|
||||
require.Equal(t, int64(12), s1.Get())
|
||||
|
||||
s1.Incr(-2)
|
||||
require.Equal(t, int64(-2), s1.Get())
|
||||
|
||||
s2.Set(101)
|
||||
require.Equal(t, int64(101), s2.Get())
|
||||
|
||||
// make sure that the same field returns the same metric
|
||||
// this one should be the same as s2.
|
||||
foo := RegisterTiming("test", "test_field2_ns", map[string]string{"test": "foo"})
|
||||
require.Equal(t, int64(101), foo.Get())
|
||||
|
||||
// check that tags are consistent
|
||||
require.Equal(t, map[string]string{"test": "foo"}, foo.Tags())
|
||||
require.Equal(t, "internal_test", foo.Name())
|
||||
}
|
||||
|
||||
func TestStatKeyConsistency(t *testing.T) {
|
||||
lhs := key("internal_stats", map[string]string{
|
||||
"foo": "bar",
|
||||
"bar": "baz",
|
||||
"whose": "first",
|
||||
})
|
||||
rhs := key("internal_stats", map[string]string{
|
||||
"foo": "bar",
|
||||
"bar": "baz",
|
||||
"whose": "first",
|
||||
})
|
||||
require.Equal(t, lhs, rhs)
|
||||
}
|
||||
|
||||
func TestRegisterMetricsAndVerify(t *testing.T) {
|
||||
testLock.Lock()
|
||||
defer testCleanup()
|
||||
|
||||
// register two metrics with the same key
|
||||
s1 := RegisterTiming("test_timing", "test_field1_ns", map[string]string{"test": "foo"})
|
||||
s2 := RegisterTiming("test_timing", "test_field2_ns", map[string]string{"test": "foo"})
|
||||
s1.Incr(10)
|
||||
s2.Incr(15)
|
||||
require.Len(t, Metrics(), 1)
|
||||
|
||||
// register two more metrics with different keys
|
||||
s3 := RegisterTiming("test_timing", "test_field1_ns", map[string]string{"test": "bar"})
|
||||
s4 := RegisterTiming("test_timing", "test_field2_ns", map[string]string{"test": "baz"})
|
||||
s3.Incr(10)
|
||||
s4.Incr(15)
|
||||
require.Len(t, Metrics(), 3)
|
||||
|
||||
// register some non-timing metrics
|
||||
s5 := Register("test", "test_field1", map[string]string{"test": "bar"})
|
||||
s6 := Register("test", "test_field2", map[string]string{"test": "baz"})
|
||||
Register("test", "test_field3", map[string]string{"test": "baz"})
|
||||
s5.Incr(10)
|
||||
s5.Incr(18)
|
||||
s6.Incr(15)
|
||||
require.Len(t, Metrics(), 5)
|
||||
|
||||
acc := testutil.Accumulator{}
|
||||
acc.AddMetrics(Metrics())
|
||||
|
||||
// verify s1 & s2
|
||||
acc.AssertContainsTaggedFields(t, "internal_test_timing",
|
||||
map[string]interface{}{
|
||||
"test_field1_ns": int64(10),
|
||||
"test_field2_ns": int64(15),
|
||||
},
|
||||
map[string]string{
|
||||
"test": "foo",
|
||||
},
|
||||
)
|
||||
|
||||
// verify s3
|
||||
acc.AssertContainsTaggedFields(t, "internal_test_timing",
|
||||
map[string]interface{}{
|
||||
"test_field1_ns": int64(10),
|
||||
},
|
||||
map[string]string{
|
||||
"test": "bar",
|
||||
},
|
||||
)
|
||||
|
||||
// verify s4
|
||||
acc.AssertContainsTaggedFields(t, "internal_test_timing",
|
||||
map[string]interface{}{
|
||||
"test_field2_ns": int64(15),
|
||||
},
|
||||
map[string]string{
|
||||
"test": "baz",
|
||||
},
|
||||
)
|
||||
|
||||
// verify s5
|
||||
acc.AssertContainsTaggedFields(t, "internal_test",
|
||||
map[string]interface{}{
|
||||
"test_field1": int64(28),
|
||||
},
|
||||
map[string]string{
|
||||
"test": "bar",
|
||||
},
|
||||
)
|
||||
|
||||
// verify s6 & s7
|
||||
acc.AssertContainsTaggedFields(t, "internal_test",
|
||||
map[string]interface{}{
|
||||
"test_field2": int64(15),
|
||||
"test_field3": int64(0),
|
||||
},
|
||||
map[string]string{
|
||||
"test": "baz",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestRegisterCopy(t *testing.T) {
|
||||
tags := map[string]string{"input": "mem", "alias": "mem1"}
|
||||
stat := Register("gather", "metrics_gathered", tags)
|
||||
tags["new"] = "value"
|
||||
require.NotEqual(t, tags, stat.Tags())
|
||||
}
|
42
selfstat/stat.go
Normal file
42
selfstat/stat.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
package selfstat
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type stat struct {
|
||||
v int64
|
||||
measurement string
|
||||
field string
|
||||
tags map[string]string
|
||||
}
|
||||
|
||||
func (s *stat) Incr(v int64) {
|
||||
atomic.AddInt64(&s.v, v)
|
||||
}
|
||||
|
||||
func (s *stat) Set(v int64) {
|
||||
atomic.StoreInt64(&s.v, v)
|
||||
}
|
||||
|
||||
func (s *stat) Get() int64 {
|
||||
return atomic.LoadInt64(&s.v)
|
||||
}
|
||||
|
||||
func (s *stat) Name() string {
|
||||
return s.measurement
|
||||
}
|
||||
|
||||
func (s *stat) FieldName() string {
|
||||
return s.field
|
||||
}
|
||||
|
||||
// Tags returns a copy of the stat's tags.
|
||||
// NOTE this allocates a new map every time it is called.
|
||||
func (s *stat) Tags() map[string]string {
|
||||
m := make(map[string]string, len(s.tags))
|
||||
for k, v := range s.tags {
|
||||
m[k] = v
|
||||
}
|
||||
return m
|
||||
}
|
58
selfstat/timingStat.go
Normal file
58
selfstat/timingStat.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package selfstat
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type timingStat struct {
|
||||
measurement string
|
||||
field string
|
||||
tags map[string]string
|
||||
v int64
|
||||
prev int64
|
||||
count int64
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (s *timingStat) Incr(v int64) {
|
||||
s.mu.Lock()
|
||||
s.v += v
|
||||
s.count++
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *timingStat) Set(v int64) {
|
||||
s.Incr(v)
|
||||
}
|
||||
|
||||
func (s *timingStat) Get() int64 {
|
||||
var avg int64
|
||||
s.mu.Lock()
|
||||
if s.count > 0 {
|
||||
s.prev, avg = s.v/s.count, s.v/s.count
|
||||
s.v = 0
|
||||
s.count = 0
|
||||
} else {
|
||||
avg = s.prev
|
||||
}
|
||||
s.mu.Unlock()
|
||||
return avg
|
||||
}
|
||||
|
||||
func (s *timingStat) Name() string {
|
||||
return s.measurement
|
||||
}
|
||||
|
||||
func (s *timingStat) FieldName() string {
|
||||
return s.field
|
||||
}
|
||||
|
||||
// Tags returns a copy of the timingStat's tags.
|
||||
// NOTE this allocates a new map every time it is called.
|
||||
func (s *timingStat) Tags() map[string]string {
|
||||
m := make(map[string]string, len(s.tags))
|
||||
for k, v := range s.tags {
|
||||
m[k] = v
|
||||
}
|
||||
return m
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue