|
- // 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 *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, 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 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
- return
- }
-
- 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 = &Registry{
- stats: make(map[uint64]map[string]Stat),
- }
- }
|