169
selfstat/selfstat.go
Normal file
169
selfstat/selfstat.go
Normal file
@@ -0,0 +1,169 @@
|
||||
// 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"
|
||||
"log"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/metric"
|
||||
)
|
||||
|
||||
var (
|
||||
registry *rgstry
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
// Key is the unique measurement+tags key of the stat.
|
||||
Key() uint64
|
||||
|
||||
// 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(&stat{
|
||||
measurement: "internal_" + measurement,
|
||||
field: field,
|
||||
tags: 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.register(&timingStat{
|
||||
measurement: "internal_" + measurement,
|
||||
field: field,
|
||||
tags: tags,
|
||||
})
|
||||
}
|
||||
|
||||
// Metrics returns all registered stats as telegraf metrics.
|
||||
func Metrics() []telegraf.Metric {
|
||||
registry.mu.Lock()
|
||||
now := time.Now()
|
||||
metrics := make([]telegraf.Metric, len(registry.stats))
|
||||
i := 0
|
||||
for _, stats := range registry.stats {
|
||||
if len(stats) > 0 {
|
||||
var tags map[string]string
|
||||
var name string
|
||||
fields := map[string]interface{}{}
|
||||
j := 0
|
||||
for fieldname, stat := range stats {
|
||||
if j == 0 {
|
||||
tags = stat.Tags()
|
||||
name = stat.Name()
|
||||
}
|
||||
fields[fieldname] = stat.Get()
|
||||
j++
|
||||
}
|
||||
metric, err := metric.New(name, tags, fields, now)
|
||||
if err != nil {
|
||||
log.Printf("E! Error creating selfstat metric: %s", err)
|
||||
continue
|
||||
}
|
||||
metrics[i] = metric
|
||||
i++
|
||||
}
|
||||
}
|
||||
registry.mu.Unlock()
|
||||
return metrics
|
||||
}
|
||||
|
||||
type rgstry struct {
|
||||
stats map[uint64]map[string]Stat
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (r *rgstry) register(s Stat) Stat {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if stats, ok := r.stats[s.Key()]; ok {
|
||||
// measurement exists
|
||||
if stat, ok := stats[s.FieldName()]; ok {
|
||||
// field already exists, so don't create a new one
|
||||
return stat
|
||||
}
|
||||
r.stats[s.Key()][s.FieldName()] = s
|
||||
return s
|
||||
} else {
|
||||
// creating a new unique metric
|
||||
r.stats[s.Key()] = map[string]Stat{s.FieldName(): s}
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
func key(measurement string, tags map[string]string) uint64 {
|
||||
h := fnv.New64a()
|
||||
h.Write([]byte(measurement))
|
||||
|
||||
tmp := make([]string, len(tags))
|
||||
i := 0
|
||||
for k, v := range tags {
|
||||
tmp[i] = k + v
|
||||
i++
|
||||
}
|
||||
sort.Strings(tmp)
|
||||
|
||||
for _, s := range tmp {
|
||||
h.Write([]byte(s))
|
||||
}
|
||||
|
||||
return h.Sum64()
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry = &rgstry{
|
||||
stats: make(map[uint64]map[string]Stat),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user