Merge branch 'master' into master

This commit is contained in:
amandahla 2016-10-14 13:35:30 -03:00 committed by GitHub
commit e1f297e24b
50 changed files with 2826 additions and 1050 deletions

View File

@ -2,6 +2,8 @@
### Release Notes ### Release Notes
- Telegraf now supports two new types of plugins: processors & aggregators.
- On systemd Telegraf will no longer redirect it's stdout to /var/log/telegraf/telegraf.log. - On systemd Telegraf will no longer redirect it's stdout to /var/log/telegraf/telegraf.log.
On most systems, the logs will be directed to the systemd journal and can be On most systems, the logs will be directed to the systemd journal and can be
accessed by `journalctl -u telegraf.service`. Consult the systemd journal accessed by `journalctl -u telegraf.service`. Consult the systemd journal
@ -11,6 +13,7 @@ continue sending logs to /var/log/telegraf/telegraf.log.
### Features ### Features
- [#1726](https://github.com/influxdata/telegraf/issues/1726): Processor & Aggregator plugin support.
- [#1861](https://github.com/influxdata/telegraf/pull/1861): adding the tags in the graylog output plugin - [#1861](https://github.com/influxdata/telegraf/pull/1861): adding the tags in the graylog output plugin
- [#1732](https://github.com/influxdata/telegraf/pull/1732): Telegraf systemd service, log to journal. - [#1732](https://github.com/influxdata/telegraf/pull/1732): Telegraf systemd service, log to journal.
- [#1782](https://github.com/influxdata/telegraf/pull/1782): Allow numeric and non-string values for tag_keys. - [#1782](https://github.com/influxdata/telegraf/pull/1782): Allow numeric and non-string values for tag_keys.
@ -58,6 +61,13 @@ continue sending logs to /var/log/telegraf/telegraf.log.
- [#1854](https://github.com/influxdata/telegraf/pull/1853): SQL Server waitstats truncation bug. - [#1854](https://github.com/influxdata/telegraf/pull/1853): SQL Server waitstats truncation bug.
- [#1810](https://github.com/influxdata/telegraf/issues/1810): Fix logparser common log format: numbers in ident. - [#1810](https://github.com/influxdata/telegraf/issues/1810): Fix logparser common log format: numbers in ident.
- [#1793](https://github.com/influxdata/telegraf/pull/1793): Fix JSON Serialization in OpenTSDB output. - [#1793](https://github.com/influxdata/telegraf/pull/1793): Fix JSON Serialization in OpenTSDB output.
- [#1731](https://github.com/influxdata/telegraf/issues/1731): Fix Graphite template ordering, use most specific.
- [#1836](https://github.com/influxdata/telegraf/pull/1836): Fix snmp table field initialization for non-automatic table.
- [#1724](https://github.com/influxdata/telegraf/issues/1724): cgroups path being parsed as metric.
- [#1886](https://github.com/influxdata/telegraf/issues/1886): Fix phpfpm fcgi client panic when URL does not exist.
- [#1344](https://github.com/influxdata/telegraf/issues/1344): Fix config file parse error logging.
- [#1771](https://github.com/influxdata/telegraf/issues/1771): Delete nil fields in the metric maker.
- [#870](https://github.com/influxdata/telegraf/issues/870): Fix MySQL special characters in DSN parsing.
## v1.0.1 [2016-09-26] ## v1.0.1 [2016-09-26]

View File

@ -2,7 +2,7 @@
1. [Sign the CLA](http://influxdb.com/community/cla.html) 1. [Sign the CLA](http://influxdb.com/community/cla.html)
1. Make changes or write plugin (see below for details) 1. Make changes or write plugin (see below for details)
1. Add your plugin to `plugins/inputs/all/all.go` or `plugins/outputs/all/all.go` 1. Add your plugin to one of: `plugins/{inputs,outputs,aggregators,processors}/all/all.go`
1. If your plugin requires a new Go package, 1. If your plugin requires a new Go package,
[add it](https://github.com/influxdata/telegraf/blob/master/CONTRIBUTING.md#adding-a-dependency) [add it](https://github.com/influxdata/telegraf/blob/master/CONTRIBUTING.md#adding-a-dependency)
1. Write a README for your plugin, if it's an input plugin, it should be structured 1. Write a README for your plugin, if it's an input plugin, it should be structured
@ -16,8 +16,8 @@ for a good example.
## GoDoc ## GoDoc
Public interfaces for inputs, outputs, metrics, and the accumulator can be found Public interfaces for inputs, outputs, processors, aggregators, metrics,
on the GoDoc and the accumulator can be found on the GoDoc
[![GoDoc](https://godoc.org/github.com/influxdata/telegraf?status.svg)](https://godoc.org/github.com/influxdata/telegraf) [![GoDoc](https://godoc.org/github.com/influxdata/telegraf?status.svg)](https://godoc.org/github.com/influxdata/telegraf)
@ -46,7 +46,7 @@ and submit new inputs.
### Input Plugin Guidelines ### Input Plugin Guidelines
* A plugin must conform to the `telegraf.Input` interface. * A plugin must conform to the [`telegraf.Input`](https://godoc.org/github.com/influxdata/telegraf#Input) interface.
* Input Plugins should call `inputs.Add` in their `init` function to register themselves. * Input Plugins should call `inputs.Add` in their `init` function to register themselves.
See below for a quick example. See below for a quick example.
* Input Plugins must be added to the * Input Plugins must be added to the
@ -177,7 +177,7 @@ similar constructs.
### Output Plugin Guidelines ### Output Plugin Guidelines
* An output must conform to the `outputs.Output` interface. * An output must conform to the [`telegraf.Output`](https://godoc.org/github.com/influxdata/telegraf#Output) interface.
* Outputs should call `outputs.Add` in their `init` function to register themselves. * Outputs should call `outputs.Add` in their `init` function to register themselves.
See below for a quick example. See below for a quick example.
* To be available within Telegraf itself, plugins must add themselves to the * To be available within Telegraf itself, plugins must add themselves to the
@ -275,6 +275,186 @@ and `Stop()` methods.
* Same as the `Output` guidelines, except that they must conform to the * Same as the `Output` guidelines, except that they must conform to the
`output.ServiceOutput` interface. `output.ServiceOutput` interface.
## Processor Plugins
This section is for developers who want to create a new processor plugin.
### Processor Plugin Guidelines
* A processor must conform to the [`telegraf.Processor`](https://godoc.org/github.com/influxdata/telegraf#Processor) interface.
* Processors should call `processors.Add` in their `init` function to register themselves.
See below for a quick example.
* To be available within Telegraf itself, plugins must add themselves to the
`github.com/influxdata/telegraf/plugins/processors/all/all.go` file.
* The `SampleConfig` function should return valid toml that describes how the
processor can be configured. This is include in `telegraf -sample-config`.
* The `Description` function should say in one line what this processor does.
### Processor Example
```go
package printer
// printer.go
import (
"fmt"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/processors"
)
type Printer struct {
}
var sampleConfig = `
`
func (p *Printer) SampleConfig() string {
return sampleConfig
}
func (p *Printer) Description() string {
return "Print all metrics that pass through this filter."
}
func (p *Printer) Apply(in ...telegraf.Metric) []telegraf.Metric {
for _, metric := range in {
fmt.Println(metric.String())
}
return in
}
func init() {
processors.Add("printer", func() telegraf.Processor {
return &Printer{}
})
}
```
## Aggregator Plugins
This section is for developers who want to create a new aggregator plugin.
### Aggregator Plugin Guidelines
* A aggregator must conform to the [`telegraf.Aggregator`](https://godoc.org/github.com/influxdata/telegraf#Aggregator) interface.
* Aggregators should call `aggregators.Add` in their `init` function to register themselves.
See below for a quick example.
* To be available within Telegraf itself, plugins must add themselves to the
`github.com/influxdata/telegraf/plugins/aggregators/all/all.go` file.
* The `SampleConfig` function should return valid toml that describes how the
aggregator can be configured. This is include in `telegraf -sample-config`.
* The `Description` function should say in one line what this aggregator does.
* The Aggregator plugin will need to keep caches of metrics that have passed
through it. This should be done using the builtin `HashID()` function of each
metric.
* When the `Reset()` function is called, all caches should be cleared.
### Aggregator Example
```go
package min
// min.go
import (
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/aggregators"
)
type Min struct {
// caches for metric fields, names, and tags
fieldCache map[uint64]map[string]float64
nameCache map[uint64]string
tagCache map[uint64]map[string]string
}
func NewMin() telegraf.Aggregator {
m := &Min{}
m.Reset()
return m
}
var sampleConfig = `
## period is the flush & clear interval of the aggregator.
period = "30s"
## If true drop_original will drop the original metrics and
## only send aggregates.
drop_original = false
`
func (m *Min) SampleConfig() string {
return sampleConfig
}
func (m *Min) Description() string {
return "Keep the aggregate min of each metric passing through."
}
func (m *Min) Add(in telegraf.Metric) {
id := in.HashID()
if _, ok := m.nameCache[id]; !ok {
// hit an uncached metric, create caches for first time:
m.nameCache[id] = in.Name()
m.tagCache[id] = in.Tags()
m.fieldCache[id] = make(map[string]float64)
for k, v := range in.Fields() {
if fv, ok := convert(v); ok {
m.fieldCache[id][k] = fv
}
}
} else {
for k, v := range in.Fields() {
if fv, ok := convert(v); ok {
if _, ok := m.fieldCache[id][k]; !ok {
// hit an uncached field of a cached metric
m.fieldCache[id][k] = fv
continue
}
if fv < m.fieldCache[id][k] {
// set new minimum
m.fieldCache[id][k] = fv
}
}
}
}
}
func (m *Min) Push(acc telegraf.Accumulator) {
for id, _ := range m.nameCache {
fields := map[string]interface{}{}
for k, v := range m.fieldCache[id] {
fields[k+"_min"] = v
}
acc.AddFields(m.nameCache[id], fields, m.tagCache[id])
}
}
func (m *Min) Reset() {
m.fieldCache = make(map[uint64]map[string]float64)
m.nameCache = make(map[uint64]string)
m.tagCache = make(map[uint64]map[string]string)
}
func convert(in interface{}) (float64, bool) {
switch v := in.(type) {
case float64:
return v, true
case int64:
return float64(v), true
default:
return 0, false
}
}
func init() {
aggregators.Add("min", func() telegraf.Aggregator {
return NewMin()
})
}
```
## Unit Tests ## Unit Tests
### Execute short tests ### Execute short tests

6
Godeps
View File

@ -19,7 +19,7 @@ github.com/eclipse/paho.mqtt.golang 0f7a459f04f13a41b7ed752d47944528d4bf9a86
github.com/go-sql-driver/mysql 1fca743146605a172a266e1654e01e5cd5669bee github.com/go-sql-driver/mysql 1fca743146605a172a266e1654e01e5cd5669bee
github.com/gobwas/glob 49571a1557cd20e6a2410adc6421f85b66c730b5 github.com/gobwas/glob 49571a1557cd20e6a2410adc6421f85b66c730b5
github.com/golang/protobuf 552c7b9542c194800fd493123b3798ef0a832032 github.com/golang/protobuf 552c7b9542c194800fd493123b3798ef0a832032
github.com/golang/snappy 427fb6fc07997f43afa32f35e850833760e489a7 github.com/golang/snappy d9eb7a3d35ec988b8585d4a0068e462c27d28380
github.com/gonuts/go-shellquote e842a11b24c6abfb3dd27af69a17f482e4b483c2 github.com/gonuts/go-shellquote e842a11b24c6abfb3dd27af69a17f482e4b483c2
github.com/gorilla/context 1ea25387ff6f684839d82767c1733ff4d4d15d0a github.com/gorilla/context 1ea25387ff6f684839d82767c1733ff4d4d15d0a
github.com/gorilla/mux c9e326e2bdec29039a3761c07bece13133863e1e github.com/gorilla/mux c9e326e2bdec29039a3761c07bece13133863e1e
@ -27,7 +27,7 @@ github.com/hailocab/go-hostpool e80d13ce29ede4452c43dea11e79b9bc8a15b478
github.com/hashicorp/consul 5aa90455ce78d4d41578bafc86305e6e6b28d7d2 github.com/hashicorp/consul 5aa90455ce78d4d41578bafc86305e6e6b28d7d2
github.com/hpcloud/tail b2940955ab8b26e19d43a43c4da0475dd81bdb56 github.com/hpcloud/tail b2940955ab8b26e19d43a43c4da0475dd81bdb56
github.com/influxdata/config b79f6829346b8d6e78ba73544b1e1038f1f1c9da github.com/influxdata/config b79f6829346b8d6e78ba73544b1e1038f1f1c9da
github.com/influxdata/influxdb e094138084855d444195b252314dfee9eae34cab github.com/influxdata/influxdb fc57c0f7c635df3873f3d64f0ed2100ddc94d5ae
github.com/influxdata/toml af4df43894b16e3fd2b788d01bd27ad0776ef2d0 github.com/influxdata/toml af4df43894b16e3fd2b788d01bd27ad0776ef2d0
github.com/influxdata/wlog 7c63b0a71ef8300adc255344d275e10e5c3a71ec github.com/influxdata/wlog 7c63b0a71ef8300adc255344d275e10e5c3a71ec
github.com/kardianos/osext 29ae4ffbc9a6fe9fb2bc5029050ce6996ea1d3bc github.com/kardianos/osext 29ae4ffbc9a6fe9fb2bc5029050ce6996ea1d3bc
@ -56,7 +56,7 @@ github.com/wvanbergen/kafka 46f9a1cf3f670edec492029fadded9c2d9e18866
github.com/wvanbergen/kazoo-go 0f768712ae6f76454f987c3356177e138df258f8 github.com/wvanbergen/kazoo-go 0f768712ae6f76454f987c3356177e138df258f8
github.com/yuin/gopher-lua bf3808abd44b1e55143a2d7f08571aaa80db1808 github.com/yuin/gopher-lua bf3808abd44b1e55143a2d7f08571aaa80db1808
github.com/zensqlmonitor/go-mssqldb ffe5510c6fa5e15e6d983210ab501c815b56b363 github.com/zensqlmonitor/go-mssqldb ffe5510c6fa5e15e6d983210ab501c815b56b363
golang.org/x/crypto 5dc8cb4b8a8eb076cbb5a06bc3b8682c15bdbbd3 golang.org/x/crypto c197bcf24cde29d3f73c7b4ac6fd41f4384e8af6
golang.org/x/net 6acef71eb69611914f7a30939ea9f6e194c78172 golang.org/x/net 6acef71eb69611914f7a30939ea9f6e194c78172
golang.org/x/text a71fd10341b064c10f4a81ceac72bcf70f26ea34 golang.org/x/text a71fd10341b064c10f4a81ceac72bcf70f26ea34
gopkg.in/dancannon/gorethink.v1 7d1af5be49cb5ecc7b177bf387d232050299d6ef gopkg.in/dancannon/gorethink.v1 7d1af5be49cb5ecc7b177bf387d232050299d6ef

View File

@ -86,42 +86,43 @@ if you don't have it already. You also must build with golang version 1.5+.
## How to use it: ## How to use it:
```console ```console
$ telegraf -help $ telegraf --help
Telegraf, The plugin-driven server agent for collecting and reporting metrics. Telegraf, The plugin-driven server agent for collecting and reporting metrics.
Usage: Usage:
telegraf <flags> telegraf [commands|flags]
The flags are: The commands & flags are:
-config <file> configuration file to load config print out full sample configuration to stdout
-test gather metrics once, print them to stdout, and exit version print the version to stdout
-sample-config print out full sample configuration to stdout
-config-directory directory containing additional *.conf files --config <file> configuration file to load
-input-filter filter the input plugins to enable, separator is : --test gather metrics once, print them to stdout, and exit
-output-filter filter the output plugins to enable, separator is : --config-directory directory containing additional *.conf files
-usage print usage for a plugin, ie, 'telegraf -usage mysql' --input-filter filter the input plugins to enable, separator is :
-debug print metrics as they're generated to stdout --output-filter filter the output plugins to enable, separator is :
-quiet run in quiet mode --usage print usage for a plugin, ie, 'telegraf -usage mysql'
-version print the version to stdout --debug print metrics as they're generated to stdout
--quiet run in quiet mode
Examples: Examples:
# generate a telegraf config file: # generate a telegraf config file:
telegraf -sample-config > telegraf.conf telegraf config > telegraf.conf
# generate config with only cpu input & influxdb output plugins defined # generate config with only cpu input & influxdb output plugins defined
telegraf -sample-config -input-filter cpu -output-filter influxdb telegraf config -input-filter cpu -output-filter influxdb
# run a single telegraf collection, outputing metrics to stdout # run a single telegraf collection, outputing metrics to stdout
telegraf -config telegraf.conf -test telegraf --config telegraf.conf -test
# run telegraf with all plugins defined in config file # run telegraf with all plugins defined in config file
telegraf -config telegraf.conf telegraf --config telegraf.conf
# run telegraf, enabling the cpu & memory input, and influxdb output plugins # run telegraf, enabling the cpu & memory input, and influxdb output plugins
telegraf -config telegraf.conf -input-filter cpu:mem -output-filter influxdb telegraf --config telegraf.conf -input-filter cpu:mem -output-filter influxdb
``` ```
## Configuration ## Configuration

View File

@ -2,9 +2,8 @@ package telegraf
import "time" import "time"
// Accumulator is an interface for "accumulating" metrics from input plugin(s). // Accumulator is an interface for "accumulating" metrics from plugin(s).
// The metrics are sent down a channel shared between all input plugins and then // The metrics are sent down a channel shared between all plugins.
// flushed on the configured flush_interval.
type Accumulator interface { type Accumulator interface {
// AddFields adds a metric to the accumulator with the given measurement // AddFields adds a metric to the accumulator with the given measurement
// name, fields, and tags (and timestamp). If a timestamp is not provided, // name, fields, and tags (and timestamp). If a timestamp is not provided,
@ -29,12 +28,7 @@ type Accumulator interface {
tags map[string]string, tags map[string]string,
t ...time.Time) t ...time.Time)
AddError(err error)
Debug() bool
SetDebug(enabled bool)
SetPrecision(precision, interval time.Duration) SetPrecision(precision, interval time.Duration)
DisablePrecision() AddError(err error)
} }

View File

@ -1,37 +1,40 @@
package agent package agent
import ( import (
"fmt"
"log" "log"
"math"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal/models"
) )
type MetricMaker interface {
Name() string
MakeMetric(
measurement string,
fields map[string]interface{},
tags map[string]string,
mType telegraf.ValueType,
t time.Time,
) telegraf.Metric
}
func NewAccumulator( func NewAccumulator(
inputConfig *models.InputConfig, maker MetricMaker,
metrics chan telegraf.Metric, metrics chan telegraf.Metric,
) *accumulator { ) *accumulator {
acc := accumulator{} acc := accumulator{
acc.metrics = metrics maker: maker,
acc.inputConfig = inputConfig metrics: metrics,
acc.precision = time.Nanosecond precision: time.Nanosecond,
}
return &acc return &acc
} }
type accumulator struct { type accumulator struct {
metrics chan telegraf.Metric metrics chan telegraf.Metric
defaultTags map[string]string maker MetricMaker
debug bool
// print every point added to the accumulator
trace bool
inputConfig *models.InputConfig
precision time.Duration precision time.Duration
@ -44,7 +47,7 @@ func (ac *accumulator) AddFields(
tags map[string]string, tags map[string]string,
t ...time.Time, t ...time.Time,
) { ) {
if m := ac.makeMetric(measurement, fields, tags, telegraf.Untyped, t...); m != nil { if m := ac.maker.MakeMetric(measurement, fields, tags, telegraf.Untyped, ac.getTime(t)); m != nil {
ac.metrics <- m ac.metrics <- m
} }
} }
@ -55,7 +58,7 @@ func (ac *accumulator) AddGauge(
tags map[string]string, tags map[string]string,
t ...time.Time, t ...time.Time,
) { ) {
if m := ac.makeMetric(measurement, fields, tags, telegraf.Gauge, t...); m != nil { if m := ac.maker.MakeMetric(measurement, fields, tags, telegraf.Gauge, ac.getTime(t)); m != nil {
ac.metrics <- m ac.metrics <- m
} }
} }
@ -66,114 +69,11 @@ func (ac *accumulator) AddCounter(
tags map[string]string, tags map[string]string,
t ...time.Time, t ...time.Time,
) { ) {
if m := ac.makeMetric(measurement, fields, tags, telegraf.Counter, t...); m != nil { if m := ac.maker.MakeMetric(measurement, fields, tags, telegraf.Counter, ac.getTime(t)); m != nil {
ac.metrics <- m ac.metrics <- m
} }
} }
// makeMetric either returns a metric, or returns nil if the metric doesn't
// need to be created (because of filtering, an error, etc.)
func (ac *accumulator) makeMetric(
measurement string,
fields map[string]interface{},
tags map[string]string,
mType telegraf.ValueType,
t ...time.Time,
) telegraf.Metric {
if len(fields) == 0 || len(measurement) == 0 {
return nil
}
if tags == nil {
tags = make(map[string]string)
}
// Override measurement name if set
if len(ac.inputConfig.NameOverride) != 0 {
measurement = ac.inputConfig.NameOverride
}
// Apply measurement prefix and suffix if set
if len(ac.inputConfig.MeasurementPrefix) != 0 {
measurement = ac.inputConfig.MeasurementPrefix + measurement
}
if len(ac.inputConfig.MeasurementSuffix) != 0 {
measurement = measurement + ac.inputConfig.MeasurementSuffix
}
// Apply plugin-wide tags if set
for k, v := range ac.inputConfig.Tags {
if _, ok := tags[k]; !ok {
tags[k] = v
}
}
// Apply daemon-wide tags if set
for k, v := range ac.defaultTags {
if _, ok := tags[k]; !ok {
tags[k] = v
}
}
// Apply the metric filter(s)
if ok := ac.inputConfig.Filter.Apply(measurement, fields, tags); !ok {
return nil
}
for k, v := range fields {
// Validate uint64 and float64 fields
switch val := v.(type) {
case uint64:
// InfluxDB does not support writing uint64
if val < uint64(9223372036854775808) {
fields[k] = int64(val)
} else {
fields[k] = int64(9223372036854775807)
}
continue
case float64:
// NaNs are invalid values in influxdb, skip measurement
if math.IsNaN(val) || math.IsInf(val, 0) {
if ac.debug {
log.Printf("I! Measurement [%s] field [%s] has a NaN or Inf "+
"field, skipping",
measurement, k)
}
delete(fields, k)
continue
}
}
fields[k] = v
}
var timestamp time.Time
if len(t) > 0 {
timestamp = t[0]
} else {
timestamp = time.Now()
}
timestamp = timestamp.Round(ac.precision)
var m telegraf.Metric
var err error
switch mType {
case telegraf.Counter:
m, err = telegraf.NewCounterMetric(measurement, tags, fields, timestamp)
case telegraf.Gauge:
m, err = telegraf.NewGaugeMetric(measurement, tags, fields, timestamp)
default:
m, err = telegraf.NewMetric(measurement, tags, fields, timestamp)
}
if err != nil {
log.Printf("E! Error adding point [%s]: %s\n", measurement, err.Error())
return nil
}
if ac.trace {
fmt.Println("> " + m.String())
}
return m
}
// AddError passes a runtime error to the accumulator. // AddError passes a runtime error to the accumulator.
// The error will be tagged with the plugin name and written to the log. // The error will be tagged with the plugin name and written to the log.
func (ac *accumulator) AddError(err error) { func (ac *accumulator) AddError(err error) {
@ -182,23 +82,7 @@ func (ac *accumulator) AddError(err error) {
} }
atomic.AddUint64(&ac.errCount, 1) atomic.AddUint64(&ac.errCount, 1)
//TODO suppress/throttle consecutive duplicate errors? //TODO suppress/throttle consecutive duplicate errors?
log.Printf("E! Error in input [%s]: %s", ac.inputConfig.Name, err) log.Printf("E! Error in plugin [%s]: %s", ac.maker.Name(), err)
}
func (ac *accumulator) Debug() bool {
return ac.debug
}
func (ac *accumulator) SetDebug(debug bool) {
ac.debug = debug
}
func (ac *accumulator) Trace() bool {
return ac.trace
}
func (ac *accumulator) SetTrace(trace bool) {
ac.trace = trace
} }
// SetPrecision takes two time.Duration objects. If the first is non-zero, // SetPrecision takes two time.Duration objects. If the first is non-zero,
@ -222,17 +106,12 @@ func (ac *accumulator) SetPrecision(precision, interval time.Duration) {
} }
} }
func (ac *accumulator) DisablePrecision() { func (ac accumulator) getTime(t []time.Time) time.Time {
ac.precision = time.Nanosecond var timestamp time.Time
} if len(t) > 0 {
timestamp = t[0]
func (ac *accumulator) setDefaultTags(tags map[string]string) { } else {
ac.defaultTags = tags timestamp = time.Now()
}
func (ac *accumulator) addDefaultTag(key, value string) {
if ac.defaultTags == nil {
ac.defaultTags = make(map[string]string)
} }
ac.defaultTags[key] = value return timestamp.Round(ac.precision)
} }

View File

@ -4,24 +4,21 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"log" "log"
"math"
"os" "os"
"testing" "testing"
"time" "time"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal/models"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestAdd(t *testing.T) { func TestAdd(t *testing.T) {
a := accumulator{}
now := time.Now() now := time.Now()
a.metrics = make(chan telegraf.Metric, 10) metrics := make(chan telegraf.Metric, 10)
defer close(a.metrics) defer close(metrics)
a.inputConfig = &models.InputConfig{} a := NewAccumulator(&TestMetricMaker{}, metrics)
a.AddFields("acctest", a.AddFields("acctest",
map[string]interface{}{"value": float64(101)}, map[string]interface{}{"value": float64(101)},
@ -33,97 +30,142 @@ func TestAdd(t *testing.T) {
map[string]interface{}{"value": float64(101)}, map[string]interface{}{"value": float64(101)},
map[string]string{"acc": "test"}, now) map[string]string{"acc": "test"}, now)
testm := <-a.metrics testm := <-metrics
actual := testm.String() actual := testm.String()
assert.Contains(t, actual, "acctest value=101") assert.Contains(t, actual, "acctest value=101")
testm = <-a.metrics testm = <-metrics
actual = testm.String() actual = testm.String()
assert.Contains(t, actual, "acctest,acc=test value=101") assert.Contains(t, actual, "acctest,acc=test value=101")
testm = <-a.metrics testm = <-metrics
actual = testm.String() actual = testm.String()
assert.Equal(t, assert.Equal(t,
fmt.Sprintf("acctest,acc=test value=101 %d", now.UnixNano()), fmt.Sprintf("acctest,acc=test value=101 %d", now.UnixNano()),
actual) actual)
} }
func TestAddGauge(t *testing.T) { func TestAddFields(t *testing.T) {
a := accumulator{}
now := time.Now() now := time.Now()
a.metrics = make(chan telegraf.Metric, 10) metrics := make(chan telegraf.Metric, 10)
defer close(a.metrics) defer close(metrics)
a.inputConfig = &models.InputConfig{} a := NewAccumulator(&TestMetricMaker{}, metrics)
a.AddGauge("acctest", fields := map[string]interface{}{
map[string]interface{}{"value": float64(101)}, "usage": float64(99),
map[string]string{}) }
a.AddGauge("acctest", a.AddFields("acctest", fields, map[string]string{})
map[string]interface{}{"value": float64(101)}, a.AddGauge("acctest", fields, map[string]string{"acc": "test"})
map[string]string{"acc": "test"}) a.AddCounter("acctest", fields, map[string]string{"acc": "test"}, now)
a.AddGauge("acctest",
map[string]interface{}{"value": float64(101)},
map[string]string{"acc": "test"}, now)
testm := <-a.metrics testm := <-metrics
actual := testm.String() actual := testm.String()
assert.Contains(t, actual, "acctest value=101") assert.Contains(t, actual, "acctest usage=99")
assert.Equal(t, testm.Type(), telegraf.Gauge)
testm = <-a.metrics testm = <-metrics
actual = testm.String() actual = testm.String()
assert.Contains(t, actual, "acctest,acc=test value=101") assert.Contains(t, actual, "acctest,acc=test usage=99")
assert.Equal(t, testm.Type(), telegraf.Gauge)
testm = <-a.metrics testm = <-metrics
actual = testm.String() actual = testm.String()
assert.Equal(t, assert.Equal(t,
fmt.Sprintf("acctest,acc=test value=101 %d", now.UnixNano()), fmt.Sprintf("acctest,acc=test usage=99 %d", now.UnixNano()),
actual) actual)
assert.Equal(t, testm.Type(), telegraf.Gauge)
} }
func TestAddCounter(t *testing.T) { func TestAccAddError(t *testing.T) {
a := accumulator{} errBuf := bytes.NewBuffer(nil)
now := time.Now() log.SetOutput(errBuf)
a.metrics = make(chan telegraf.Metric, 10) defer log.SetOutput(os.Stderr)
defer close(a.metrics)
a.inputConfig = &models.InputConfig{}
a.AddCounter("acctest", metrics := make(chan telegraf.Metric, 10)
defer close(metrics)
a := NewAccumulator(&TestMetricMaker{}, metrics)
a.AddError(fmt.Errorf("foo"))
a.AddError(fmt.Errorf("bar"))
a.AddError(fmt.Errorf("baz"))
errs := bytes.Split(errBuf.Bytes(), []byte{'\n'})
assert.EqualValues(t, 3, a.errCount)
require.Len(t, errs, 4) // 4 because of trailing newline
assert.Contains(t, string(errs[0]), "TestPlugin")
assert.Contains(t, string(errs[0]), "foo")
assert.Contains(t, string(errs[1]), "TestPlugin")
assert.Contains(t, string(errs[1]), "bar")
assert.Contains(t, string(errs[2]), "TestPlugin")
assert.Contains(t, string(errs[2]), "baz")
}
func TestAddNoIntervalWithPrecision(t *testing.T) {
now := time.Date(2006, time.February, 10, 12, 0, 0, 82912748, time.UTC)
metrics := make(chan telegraf.Metric, 10)
defer close(metrics)
a := NewAccumulator(&TestMetricMaker{}, metrics)
a.SetPrecision(0, time.Second)
a.AddFields("acctest",
map[string]interface{}{"value": float64(101)}, map[string]interface{}{"value": float64(101)},
map[string]string{}) map[string]string{})
a.AddCounter("acctest", a.AddFields("acctest",
map[string]interface{}{"value": float64(101)}, map[string]interface{}{"value": float64(101)},
map[string]string{"acc": "test"}) map[string]string{"acc": "test"})
a.AddCounter("acctest", a.AddFields("acctest",
map[string]interface{}{"value": float64(101)}, map[string]interface{}{"value": float64(101)},
map[string]string{"acc": "test"}, now) map[string]string{"acc": "test"}, now)
testm := <-a.metrics testm := <-a.metrics
actual := testm.String() actual := testm.String()
assert.Contains(t, actual, "acctest value=101") assert.Contains(t, actual, "acctest value=101")
assert.Equal(t, testm.Type(), telegraf.Counter)
testm = <-a.metrics testm = <-a.metrics
actual = testm.String() actual = testm.String()
assert.Contains(t, actual, "acctest,acc=test value=101") assert.Contains(t, actual, "acctest,acc=test value=101")
assert.Equal(t, testm.Type(), telegraf.Counter)
testm = <-a.metrics testm = <-a.metrics
actual = testm.String() actual = testm.String()
assert.Equal(t, assert.Equal(t,
fmt.Sprintf("acctest,acc=test value=101 %d", now.UnixNano()), fmt.Sprintf("acctest,acc=test value=101 %d", int64(1139572800000000000)),
actual)
}
func TestAddDisablePrecision(t *testing.T) {
now := time.Date(2006, time.February, 10, 12, 0, 0, 82912748, time.UTC)
metrics := make(chan telegraf.Metric, 10)
defer close(metrics)
a := NewAccumulator(&TestMetricMaker{}, metrics)
a.SetPrecision(time.Nanosecond, 0)
a.AddFields("acctest",
map[string]interface{}{"value": float64(101)},
map[string]string{})
a.AddFields("acctest",
map[string]interface{}{"value": float64(101)},
map[string]string{"acc": "test"})
a.AddFields("acctest",
map[string]interface{}{"value": float64(101)},
map[string]string{"acc": "test"}, now)
testm := <-a.metrics
actual := testm.String()
assert.Contains(t, actual, "acctest value=101")
testm = <-a.metrics
actual = testm.String()
assert.Contains(t, actual, "acctest,acc=test value=101")
testm = <-a.metrics
actual = testm.String()
assert.Equal(t,
fmt.Sprintf("acctest,acc=test value=101 %d", int64(1139572800082912748)),
actual) actual)
assert.Equal(t, testm.Type(), telegraf.Counter)
} }
func TestAddNoPrecisionWithInterval(t *testing.T) { func TestAddNoPrecisionWithInterval(t *testing.T) {
a := accumulator{}
now := time.Date(2006, time.February, 10, 12, 0, 0, 82912748, time.UTC) now := time.Date(2006, time.February, 10, 12, 0, 0, 82912748, time.UTC)
a.metrics = make(chan telegraf.Metric, 10) metrics := make(chan telegraf.Metric, 10)
defer close(a.metrics) defer close(metrics)
a.inputConfig = &models.InputConfig{} a := NewAccumulator(&TestMetricMaker{}, metrics)
a.SetPrecision(0, time.Second) a.SetPrecision(0, time.Second)
a.AddFields("acctest", a.AddFields("acctest",
@ -151,79 +193,11 @@ func TestAddNoPrecisionWithInterval(t *testing.T) {
actual) actual)
} }
func TestAddNoIntervalWithPrecision(t *testing.T) {
a := accumulator{}
now := time.Date(2006, time.February, 10, 12, 0, 0, 82912748, time.UTC)
a.metrics = make(chan telegraf.Metric, 10)
defer close(a.metrics)
a.inputConfig = &models.InputConfig{}
a.SetPrecision(time.Second, time.Millisecond)
a.AddFields("acctest",
map[string]interface{}{"value": float64(101)},
map[string]string{})
a.AddFields("acctest",
map[string]interface{}{"value": float64(101)},
map[string]string{"acc": "test"})
a.AddFields("acctest",
map[string]interface{}{"value": float64(101)},
map[string]string{"acc": "test"}, now)
testm := <-a.metrics
actual := testm.String()
assert.Contains(t, actual, "acctest value=101")
testm = <-a.metrics
actual = testm.String()
assert.Contains(t, actual, "acctest,acc=test value=101")
testm = <-a.metrics
actual = testm.String()
assert.Equal(t,
fmt.Sprintf("acctest,acc=test value=101 %d", int64(1139572800000000000)),
actual)
}
func TestAddDisablePrecision(t *testing.T) {
a := accumulator{}
now := time.Date(2006, time.February, 10, 12, 0, 0, 82912748, time.UTC)
a.metrics = make(chan telegraf.Metric, 10)
defer close(a.metrics)
a.inputConfig = &models.InputConfig{}
a.SetPrecision(time.Second, time.Millisecond)
a.DisablePrecision()
a.AddFields("acctest",
map[string]interface{}{"value": float64(101)},
map[string]string{})
a.AddFields("acctest",
map[string]interface{}{"value": float64(101)},
map[string]string{"acc": "test"})
a.AddFields("acctest",
map[string]interface{}{"value": float64(101)},
map[string]string{"acc": "test"}, now)
testm := <-a.metrics
actual := testm.String()
assert.Contains(t, actual, "acctest value=101")
testm = <-a.metrics
actual = testm.String()
assert.Contains(t, actual, "acctest,acc=test value=101")
testm = <-a.metrics
actual = testm.String()
assert.Equal(t,
fmt.Sprintf("acctest,acc=test value=101 %d", int64(1139572800082912748)),
actual)
}
func TestDifferentPrecisions(t *testing.T) { func TestDifferentPrecisions(t *testing.T) {
a := accumulator{}
now := time.Date(2006, time.February, 10, 12, 0, 0, 82912748, time.UTC) now := time.Date(2006, time.February, 10, 12, 0, 0, 82912748, time.UTC)
a.metrics = make(chan telegraf.Metric, 10) metrics := make(chan telegraf.Metric, 10)
defer close(a.metrics) defer close(metrics)
a.inputConfig = &models.InputConfig{} a := NewAccumulator(&TestMetricMaker{}, metrics)
a.SetPrecision(0, time.Second) a.SetPrecision(0, time.Second)
a.AddFields("acctest", a.AddFields("acctest",
@ -266,349 +240,100 @@ func TestDifferentPrecisions(t *testing.T) {
actual) actual)
} }
func TestAddDefaultTags(t *testing.T) { func TestAddGauge(t *testing.T) {
a := accumulator{}
a.addDefaultTag("default", "tag")
now := time.Now() now := time.Now()
a.metrics = make(chan telegraf.Metric, 10) metrics := make(chan telegraf.Metric, 10)
defer close(a.metrics) defer close(metrics)
a.inputConfig = &models.InputConfig{} a := NewAccumulator(&TestMetricMaker{}, metrics)
a.AddFields("acctest", a.AddGauge("acctest",
map[string]interface{}{"value": float64(101)}, map[string]interface{}{"value": float64(101)},
map[string]string{}) map[string]string{})
a.AddFields("acctest", a.AddGauge("acctest",
map[string]interface{}{"value": float64(101)}, map[string]interface{}{"value": float64(101)},
map[string]string{"acc": "test"}) map[string]string{"acc": "test"})
a.AddFields("acctest", a.AddGauge("acctest",
map[string]interface{}{"value": float64(101)}, map[string]interface{}{"value": float64(101)},
map[string]string{"acc": "test"}, now) map[string]string{"acc": "test"}, now)
testm := <-a.metrics testm := <-metrics
actual := testm.String()
assert.Contains(t, actual, "acctest,default=tag value=101")
testm = <-a.metrics
actual = testm.String()
assert.Contains(t, actual, "acctest,acc=test,default=tag value=101")
testm = <-a.metrics
actual = testm.String()
assert.Equal(t,
fmt.Sprintf("acctest,acc=test,default=tag value=101 %d", now.UnixNano()),
actual)
}
func TestAddFields(t *testing.T) {
a := accumulator{}
now := time.Now()
a.metrics = make(chan telegraf.Metric, 10)
defer close(a.metrics)
a.inputConfig = &models.InputConfig{}
fields := map[string]interface{}{
"usage": float64(99),
}
a.AddFields("acctest", fields, map[string]string{})
a.AddFields("acctest", fields, map[string]string{"acc": "test"})
a.AddFields("acctest", fields, map[string]string{"acc": "test"}, now)
testm := <-a.metrics
actual := testm.String()
assert.Contains(t, actual, "acctest usage=99")
testm = <-a.metrics
actual = testm.String()
assert.Contains(t, actual, "acctest,acc=test usage=99")
testm = <-a.metrics
actual = testm.String()
assert.Equal(t,
fmt.Sprintf("acctest,acc=test usage=99 %d", now.UnixNano()),
actual)
}
// Test that all Inf fields get dropped, and not added to metrics channel
func TestAddInfFields(t *testing.T) {
inf := math.Inf(1)
ninf := math.Inf(-1)
a := accumulator{}
now := time.Now()
a.metrics = make(chan telegraf.Metric, 10)
defer close(a.metrics)
a.inputConfig = &models.InputConfig{}
fields := map[string]interface{}{
"usage": inf,
"nusage": ninf,
}
a.AddFields("acctest", fields, map[string]string{})
a.AddFields("acctest", fields, map[string]string{"acc": "test"})
a.AddFields("acctest", fields, map[string]string{"acc": "test"}, now)
assert.Len(t, a.metrics, 0)
// test that non-inf fields are kept and not dropped
fields["notinf"] = float64(100)
a.AddFields("acctest", fields, map[string]string{})
testm := <-a.metrics
actual := testm.String()
assert.Contains(t, actual, "acctest notinf=100")
}
// Test that nan fields are dropped and not added
func TestAddNaNFields(t *testing.T) {
nan := math.NaN()
a := accumulator{}
now := time.Now()
a.metrics = make(chan telegraf.Metric, 10)
defer close(a.metrics)
a.inputConfig = &models.InputConfig{}
fields := map[string]interface{}{
"usage": nan,
}
a.AddFields("acctest", fields, map[string]string{})
a.AddFields("acctest", fields, map[string]string{"acc": "test"})
a.AddFields("acctest", fields, map[string]string{"acc": "test"}, now)
assert.Len(t, a.metrics, 0)
// test that non-nan fields are kept and not dropped
fields["notnan"] = float64(100)
a.AddFields("acctest", fields, map[string]string{})
testm := <-a.metrics
actual := testm.String()
assert.Contains(t, actual, "acctest notnan=100")
}
func TestAddUint64Fields(t *testing.T) {
a := accumulator{}
now := time.Now()
a.metrics = make(chan telegraf.Metric, 10)
defer close(a.metrics)
a.inputConfig = &models.InputConfig{}
fields := map[string]interface{}{
"usage": uint64(99),
}
a.AddFields("acctest", fields, map[string]string{})
a.AddFields("acctest", fields, map[string]string{"acc": "test"})
a.AddFields("acctest", fields, map[string]string{"acc": "test"}, now)
testm := <-a.metrics
actual := testm.String()
assert.Contains(t, actual, "acctest usage=99i")
testm = <-a.metrics
actual = testm.String()
assert.Contains(t, actual, "acctest,acc=test usage=99i")
testm = <-a.metrics
actual = testm.String()
assert.Equal(t,
fmt.Sprintf("acctest,acc=test usage=99i %d", now.UnixNano()),
actual)
}
func TestAddUint64Overflow(t *testing.T) {
a := accumulator{}
now := time.Now()
a.metrics = make(chan telegraf.Metric, 10)
defer close(a.metrics)
a.inputConfig = &models.InputConfig{}
fields := map[string]interface{}{
"usage": uint64(9223372036854775808),
}
a.AddFields("acctest", fields, map[string]string{})
a.AddFields("acctest", fields, map[string]string{"acc": "test"})
a.AddFields("acctest", fields, map[string]string{"acc": "test"}, now)
testm := <-a.metrics
actual := testm.String()
assert.Contains(t, actual, "acctest usage=9223372036854775807i")
testm = <-a.metrics
actual = testm.String()
assert.Contains(t, actual, "acctest,acc=test usage=9223372036854775807i")
testm = <-a.metrics
actual = testm.String()
assert.Equal(t,
fmt.Sprintf("acctest,acc=test usage=9223372036854775807i %d", now.UnixNano()),
actual)
}
func TestAddInts(t *testing.T) {
a := accumulator{}
a.addDefaultTag("default", "tag")
now := time.Now()
a.metrics = make(chan telegraf.Metric, 10)
defer close(a.metrics)
a.inputConfig = &models.InputConfig{}
a.AddFields("acctest",
map[string]interface{}{"value": int(101)},
map[string]string{})
a.AddFields("acctest",
map[string]interface{}{"value": int32(101)},
map[string]string{"acc": "test"})
a.AddFields("acctest",
map[string]interface{}{"value": int64(101)},
map[string]string{"acc": "test"}, now)
testm := <-a.metrics
actual := testm.String()
assert.Contains(t, actual, "acctest,default=tag value=101i")
testm = <-a.metrics
actual = testm.String()
assert.Contains(t, actual, "acctest,acc=test,default=tag value=101i")
testm = <-a.metrics
actual = testm.String()
assert.Equal(t,
fmt.Sprintf("acctest,acc=test,default=tag value=101i %d", now.UnixNano()),
actual)
}
func TestAddFloats(t *testing.T) {
a := accumulator{}
a.addDefaultTag("default", "tag")
now := time.Now()
a.metrics = make(chan telegraf.Metric, 10)
defer close(a.metrics)
a.inputConfig = &models.InputConfig{}
a.AddFields("acctest",
map[string]interface{}{"value": float32(101)},
map[string]string{"acc": "test"})
a.AddFields("acctest",
map[string]interface{}{"value": float64(101)},
map[string]string{"acc": "test"}, now)
testm := <-a.metrics
actual := testm.String()
assert.Contains(t, actual, "acctest,acc=test,default=tag value=101")
testm = <-a.metrics
actual = testm.String()
assert.Equal(t,
fmt.Sprintf("acctest,acc=test,default=tag value=101 %d", now.UnixNano()),
actual)
}
func TestAddStrings(t *testing.T) {
a := accumulator{}
a.addDefaultTag("default", "tag")
now := time.Now()
a.metrics = make(chan telegraf.Metric, 10)
defer close(a.metrics)
a.inputConfig = &models.InputConfig{}
a.AddFields("acctest",
map[string]interface{}{"value": "test"},
map[string]string{"acc": "test"})
a.AddFields("acctest",
map[string]interface{}{"value": "foo"},
map[string]string{"acc": "test"}, now)
testm := <-a.metrics
actual := testm.String()
assert.Contains(t, actual, "acctest,acc=test,default=tag value=\"test\"")
testm = <-a.metrics
actual = testm.String()
assert.Equal(t,
fmt.Sprintf("acctest,acc=test,default=tag value=\"foo\" %d", now.UnixNano()),
actual)
}
func TestAddBools(t *testing.T) {
a := accumulator{}
a.addDefaultTag("default", "tag")
now := time.Now()
a.metrics = make(chan telegraf.Metric, 10)
defer close(a.metrics)
a.inputConfig = &models.InputConfig{}
a.AddFields("acctest",
map[string]interface{}{"value": true}, map[string]string{"acc": "test"})
a.AddFields("acctest",
map[string]interface{}{"value": false}, map[string]string{"acc": "test"}, now)
testm := <-a.metrics
actual := testm.String()
assert.Contains(t, actual, "acctest,acc=test,default=tag value=true")
testm = <-a.metrics
actual = testm.String()
assert.Equal(t,
fmt.Sprintf("acctest,acc=test,default=tag value=false %d", now.UnixNano()),
actual)
}
// Test that tag filters get applied to metrics.
func TestAccFilterTags(t *testing.T) {
a := accumulator{}
now := time.Now()
a.metrics = make(chan telegraf.Metric, 10)
defer close(a.metrics)
filter := models.Filter{
TagExclude: []string{"acc"},
}
assert.NoError(t, filter.Compile())
a.inputConfig = &models.InputConfig{}
a.inputConfig.Filter = filter
a.AddFields("acctest",
map[string]interface{}{"value": float64(101)},
map[string]string{})
a.AddFields("acctest",
map[string]interface{}{"value": float64(101)},
map[string]string{"acc": "test"})
a.AddFields("acctest",
map[string]interface{}{"value": float64(101)},
map[string]string{"acc": "test"}, now)
testm := <-a.metrics
actual := testm.String() actual := testm.String()
assert.Contains(t, actual, "acctest value=101") assert.Contains(t, actual, "acctest value=101")
assert.Equal(t, testm.Type(), telegraf.Gauge)
testm = <-a.metrics testm = <-metrics
actual = testm.String() actual = testm.String()
assert.Contains(t, actual, "acctest value=101") assert.Contains(t, actual, "acctest,acc=test value=101")
assert.Equal(t, testm.Type(), telegraf.Gauge)
testm = <-a.metrics testm = <-metrics
actual = testm.String() actual = testm.String()
assert.Equal(t, assert.Equal(t,
fmt.Sprintf("acctest value=101 %d", now.UnixNano()), fmt.Sprintf("acctest,acc=test value=101 %d", now.UnixNano()),
actual) actual)
assert.Equal(t, testm.Type(), telegraf.Gauge)
} }
func TestAccAddError(t *testing.T) { func TestAddCounter(t *testing.T) {
errBuf := bytes.NewBuffer(nil) now := time.Now()
log.SetOutput(errBuf) metrics := make(chan telegraf.Metric, 10)
defer log.SetOutput(os.Stderr) defer close(metrics)
a := NewAccumulator(&TestMetricMaker{}, metrics)
a := accumulator{} a.AddCounter("acctest",
a.inputConfig = &models.InputConfig{} map[string]interface{}{"value": float64(101)},
a.inputConfig.Name = "mock_plugin" map[string]string{})
a.AddCounter("acctest",
map[string]interface{}{"value": float64(101)},
map[string]string{"acc": "test"})
a.AddCounter("acctest",
map[string]interface{}{"value": float64(101)},
map[string]string{"acc": "test"}, now)
a.AddError(fmt.Errorf("foo")) testm := <-metrics
a.AddError(fmt.Errorf("bar")) actual := testm.String()
a.AddError(fmt.Errorf("baz")) assert.Contains(t, actual, "acctest value=101")
assert.Equal(t, testm.Type(), telegraf.Counter)
errs := bytes.Split(errBuf.Bytes(), []byte{'\n'}) testm = <-metrics
assert.EqualValues(t, 3, a.errCount) actual = testm.String()
require.Len(t, errs, 4) // 4 because of trailing newline assert.Contains(t, actual, "acctest,acc=test value=101")
assert.Contains(t, string(errs[0]), "mock_plugin") assert.Equal(t, testm.Type(), telegraf.Counter)
assert.Contains(t, string(errs[0]), "foo")
assert.Contains(t, string(errs[1]), "mock_plugin") testm = <-metrics
assert.Contains(t, string(errs[1]), "bar") actual = testm.String()
assert.Contains(t, string(errs[2]), "mock_plugin") assert.Equal(t,
assert.Contains(t, string(errs[2]), "baz") fmt.Sprintf("acctest,acc=test value=101 %d", now.UnixNano()),
actual)
assert.Equal(t, testm.Type(), telegraf.Counter)
}
type TestMetricMaker struct {
}
func (tm *TestMetricMaker) Name() string {
return "TestPlugin"
}
func (tm *TestMetricMaker) MakeMetric(
measurement string,
fields map[string]interface{},
tags map[string]string,
mType telegraf.ValueType,
t time.Time,
) telegraf.Metric {
switch mType {
case telegraf.Untyped:
if m, err := telegraf.NewMetric(measurement, tags, fields, t); err == nil {
return m
}
case telegraf.Counter:
if m, err := telegraf.NewCounterMetric(measurement, tags, fields, t); err == nil {
return m
}
case telegraf.Gauge:
if m, err := telegraf.NewGaugeMetric(measurement, tags, fields, t); err == nil {
return m
}
}
return nil
} }

View File

@ -89,7 +89,7 @@ func panicRecover(input *models.RunningInput) {
trace := make([]byte, 2048) trace := make([]byte, 2048)
runtime.Stack(trace, true) runtime.Stack(trace, true)
log.Printf("E! FATAL: Input [%s] panicked: %s, Stack:\n%s\n", log.Printf("E! FATAL: Input [%s] panicked: %s, Stack:\n%s\n",
input.Name, err, trace) input.Name(), err, trace)
log.Println("E! PLEASE REPORT THIS PANIC ON GITHUB with " + log.Println("E! PLEASE REPORT THIS PANIC ON GITHUB with " +
"stack trace, configuration, and OS information: " + "stack trace, configuration, and OS information: " +
"https://github.com/influxdata/telegraf/issues/new") "https://github.com/influxdata/telegraf/issues/new")
@ -103,19 +103,18 @@ func (a *Agent) gatherer(
input *models.RunningInput, input *models.RunningInput,
interval time.Duration, interval time.Duration,
metricC chan telegraf.Metric, metricC chan telegraf.Metric,
) error { ) {
defer panicRecover(input) defer panicRecover(input)
ticker := time.NewTicker(interval) ticker := time.NewTicker(interval)
defer ticker.Stop() defer ticker.Stop()
for { for {
var outerr error acc := NewAccumulator(input, metricC)
acc := NewAccumulator(input.Config, metricC)
acc.SetPrecision(a.Config.Agent.Precision.Duration, acc.SetPrecision(a.Config.Agent.Precision.Duration,
a.Config.Agent.Interval.Duration) a.Config.Agent.Interval.Duration)
acc.setDefaultTags(a.Config.Tags) input.SetDebug(a.Config.Agent.Debug)
input.SetDefaultTags(a.Config.Tags)
internal.RandomSleep(a.Config.Agent.CollectionJitter.Duration, shutdown) internal.RandomSleep(a.Config.Agent.CollectionJitter.Duration, shutdown)
@ -123,15 +122,12 @@ func (a *Agent) gatherer(
gatherWithTimeout(shutdown, input, acc, interval) gatherWithTimeout(shutdown, input, acc, interval)
elapsed := time.Since(start) elapsed := time.Since(start)
if outerr != nil {
return outerr
}
log.Printf("D! Input [%s] gathered metrics, (%s interval) in %s\n", log.Printf("D! Input [%s] gathered metrics, (%s interval) in %s\n",
input.Name, interval, elapsed) input.Name(), interval, elapsed)
select { select {
case <-shutdown: case <-shutdown:
return nil return
case <-ticker.C: case <-ticker.C:
continue continue
} }
@ -160,13 +156,13 @@ func gatherWithTimeout(
select { select {
case err := <-done: case err := <-done:
if err != nil { if err != nil {
log.Printf("E! ERROR in input [%s]: %s", input.Name, err) log.Printf("E! ERROR in input [%s]: %s", input.Name(), err)
} }
return return
case <-ticker.C: case <-ticker.C:
log.Printf("E! ERROR: input [%s] took longer to collect than "+ log.Printf("E! ERROR: input [%s] took longer to collect than "+
"collection interval (%s)", "collection interval (%s)",
input.Name, timeout) input.Name(), timeout)
continue continue
case <-shutdown: case <-shutdown:
return return
@ -194,13 +190,13 @@ func (a *Agent) Test() error {
}() }()
for _, input := range a.Config.Inputs { for _, input := range a.Config.Inputs {
acc := NewAccumulator(input.Config, metricC) acc := NewAccumulator(input, metricC)
acc.SetTrace(true)
acc.SetPrecision(a.Config.Agent.Precision.Duration, acc.SetPrecision(a.Config.Agent.Precision.Duration,
a.Config.Agent.Interval.Duration) a.Config.Agent.Interval.Duration)
acc.setDefaultTags(a.Config.Tags) input.SetTrace(true)
input.SetDefaultTags(a.Config.Tags)
fmt.Printf("* Plugin: %s, Collection 1\n", input.Name) fmt.Printf("* Plugin: %s, Collection 1\n", input.Name())
if input.Config.Interval != 0 { if input.Config.Interval != 0 {
fmt.Printf("* Internal: %s\n", input.Config.Interval) fmt.Printf("* Internal: %s\n", input.Config.Interval)
} }
@ -214,10 +210,10 @@ func (a *Agent) Test() error {
// Special instructions for some inputs. cpu, for example, needs to be // Special instructions for some inputs. cpu, for example, needs to be
// run twice in order to return cpu usage percentages. // run twice in order to return cpu usage percentages.
switch input.Name { switch input.Name() {
case "cpu", "mongodb", "procstat": case "cpu", "mongodb", "procstat":
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
fmt.Printf("* Plugin: %s, Collection 2\n", input.Name) fmt.Printf("* Plugin: %s, Collection 2\n", input.Name())
if err := input.Input.Gather(acc); err != nil { if err := input.Input.Gather(acc); err != nil {
return err return err
} }
@ -250,47 +246,73 @@ func (a *Agent) flush() {
func (a *Agent) flusher(shutdown chan struct{}, metricC chan telegraf.Metric) error { func (a *Agent) flusher(shutdown chan struct{}, metricC chan telegraf.Metric) error {
// Inelegant, but this sleep is to allow the Gather threads to run, so that // Inelegant, but this sleep is to allow the Gather threads to run, so that
// the flusher will flush after metrics are collected. // the flusher will flush after metrics are collected.
time.Sleep(time.Millisecond * 200) time.Sleep(time.Millisecond * 300)
// create an output metric channel and a gorouting that continously passes
// each metric onto the output plugins & aggregators.
outMetricC := make(chan telegraf.Metric, 100)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
for {
select {
case <-shutdown:
if len(outMetricC) > 0 {
// keep going until outMetricC is flushed
continue
}
return
case m := <-outMetricC:
// if dropOriginal is set to true, then we will only send this
// metric to the aggregators, not the outputs.
var dropOriginal bool
if !m.IsAggregate() {
for _, agg := range a.Config.Aggregators {
if ok := agg.Add(copyMetric(m)); ok {
dropOriginal = true
}
}
}
if !dropOriginal {
for i, o := range a.Config.Outputs {
if i == len(a.Config.Outputs)-1 {
o.AddMetric(m)
} else {
o.AddMetric(copyMetric(m))
}
}
}
}
}
}()
ticker := time.NewTicker(a.Config.Agent.FlushInterval.Duration) ticker := time.NewTicker(a.Config.Agent.FlushInterval.Duration)
for { for {
select { select {
case <-shutdown: case <-shutdown:
log.Println("I! Hang on, flushing any cached metrics before shutdown") log.Println("I! Hang on, flushing any cached metrics before shutdown")
// wait for outMetricC to get flushed before flushing outputs
wg.Wait()
a.flush() a.flush()
return nil return nil
case <-ticker.C: case <-ticker.C:
internal.RandomSleep(a.Config.Agent.FlushJitter.Duration, shutdown) internal.RandomSleep(a.Config.Agent.FlushJitter.Duration, shutdown)
a.flush() a.flush()
case m := <-metricC: case metric := <-metricC:
for i, o := range a.Config.Outputs { // NOTE potential bottleneck here as we put each metric through the
if i == len(a.Config.Outputs)-1 { // processors serially.
o.AddMetric(m) mS := []telegraf.Metric{metric}
} else { for _, processor := range a.Config.Processors {
o.AddMetric(copyMetric(m)) mS = processor.Apply(mS...)
} }
for _, m := range mS {
outMetricC <- m
} }
} }
} }
} }
func copyMetric(m telegraf.Metric) telegraf.Metric {
t := time.Time(m.Time())
tags := make(map[string]string)
fields := make(map[string]interface{})
for k, v := range m.Tags() {
tags[k] = v
}
for k, v := range m.Fields() {
fields[k] = v
}
out, _ := telegraf.NewMetric(m.Name(), tags, fields, t)
return out
}
// Run runs the agent daemon, gathering every Interval // Run runs the agent daemon, gathering every Interval
func (a *Agent) Run(shutdown chan struct{}) error { func (a *Agent) Run(shutdown chan struct{}) error {
var wg sync.WaitGroup var wg sync.WaitGroup
@ -301,20 +323,20 @@ func (a *Agent) Run(shutdown chan struct{}) error {
a.Config.Agent.Hostname, a.Config.Agent.FlushInterval.Duration) a.Config.Agent.Hostname, a.Config.Agent.FlushInterval.Duration)
// channel shared between all input threads for accumulating metrics // channel shared between all input threads for accumulating metrics
metricC := make(chan telegraf.Metric, 10000) metricC := make(chan telegraf.Metric, 100)
// Start all ServicePlugins
for _, input := range a.Config.Inputs { for _, input := range a.Config.Inputs {
// Start service of any ServicePlugins
switch p := input.Input.(type) { switch p := input.Input.(type) {
case telegraf.ServiceInput: case telegraf.ServiceInput:
acc := NewAccumulator(input.Config, metricC) acc := NewAccumulator(input, metricC)
// Service input plugins should set their own precision of their // Service input plugins should set their own precision of their
// metrics. // metrics.
acc.DisablePrecision() acc.SetPrecision(time.Nanosecond, 0)
acc.setDefaultTags(a.Config.Tags) input.SetDefaultTags(a.Config.Tags)
if err := p.Start(acc); err != nil { if err := p.Start(acc); err != nil {
log.Printf("E! Service for input %s failed to start, exiting\n%s\n", log.Printf("E! Service for input %s failed to start, exiting\n%s\n",
input.Name, err.Error()) input.Name(), err.Error())
return err return err
} }
defer p.Stop() defer p.Stop()
@ -336,6 +358,17 @@ func (a *Agent) Run(shutdown chan struct{}) error {
} }
}() }()
wg.Add(len(a.Config.Aggregators))
for _, aggregator := range a.Config.Aggregators {
go func(agg *models.RunningAggregator) {
defer wg.Done()
acc := NewAccumulator(agg, metricC)
acc.SetPrecision(a.Config.Agent.Precision.Duration,
a.Config.Agent.Interval.Duration)
agg.Run(acc, shutdown)
}(aggregator)
}
wg.Add(len(a.Config.Inputs)) wg.Add(len(a.Config.Inputs))
for _, input := range a.Config.Inputs { for _, input := range a.Config.Inputs {
interval := a.Config.Agent.Interval.Duration interval := a.Config.Agent.Interval.Duration
@ -345,12 +378,26 @@ func (a *Agent) Run(shutdown chan struct{}) error {
} }
go func(in *models.RunningInput, interv time.Duration) { go func(in *models.RunningInput, interv time.Duration) {
defer wg.Done() defer wg.Done()
if err := a.gatherer(shutdown, in, interv, metricC); err != nil { a.gatherer(shutdown, in, interv, metricC)
log.Printf("E! " + err.Error())
}
}(input, interval) }(input, interval)
} }
wg.Wait() wg.Wait()
return nil return nil
} }
func copyMetric(m telegraf.Metric) telegraf.Metric {
t := time.Time(m.Time())
tags := make(map[string]string)
fields := make(map[string]interface{})
for k, v := range m.Tags() {
tags[k] = v
}
for k, v := range m.Fields() {
fields[k] = v
}
out, _ := telegraf.NewMetric(m.Name(), tags, fields, t)
return out
}

22
aggregator.go Normal file
View File

@ -0,0 +1,22 @@
package telegraf
// Aggregator is an interface for implementing an Aggregator plugin.
// the RunningAggregator wraps this interface and guarantees that
// Add, Push, and Reset can not be called concurrently, so locking is not
// required when implementing an Aggregator plugin.
type Aggregator interface {
// SampleConfig returns the default configuration of the Input.
SampleConfig() string
// Description returns a one-sentence description on the Input.
Description() string
// Add the metric to the aggregator.
Add(in Metric)
// Push pushes the current aggregates to the accumulator.
Push(acc Accumulator)
// Reset resets the aggregators caches and aggregates.
Reset()
}

View File

@ -13,11 +13,12 @@ import (
"github.com/influxdata/telegraf/agent" "github.com/influxdata/telegraf/agent"
"github.com/influxdata/telegraf/internal/config" "github.com/influxdata/telegraf/internal/config"
"github.com/influxdata/telegraf/logger" "github.com/influxdata/telegraf/logger"
_ "github.com/influxdata/telegraf/plugins/aggregators/all"
"github.com/influxdata/telegraf/plugins/inputs" "github.com/influxdata/telegraf/plugins/inputs"
_ "github.com/influxdata/telegraf/plugins/inputs/all" _ "github.com/influxdata/telegraf/plugins/inputs/all"
"github.com/influxdata/telegraf/plugins/outputs" "github.com/influxdata/telegraf/plugins/outputs"
_ "github.com/influxdata/telegraf/plugins/outputs/all" _ "github.com/influxdata/telegraf/plugins/outputs/all"
_ "github.com/influxdata/telegraf/plugins/processors/all"
"github.com/kardianos/service" "github.com/kardianos/service"
) )
@ -41,6 +42,10 @@ var fOutputFilters = flag.String("output-filter", "",
"filter the outputs to enable, separator is :") "filter the outputs to enable, separator is :")
var fOutputList = flag.Bool("output-list", false, var fOutputList = flag.Bool("output-list", false,
"print available output plugins.") "print available output plugins.")
var fAggregatorFilters = flag.String("aggregator-filter", "",
"filter the aggregators to enable, separator is :")
var fProcessorFilters = flag.String("processor-filter", "",
"filter the processors to enable, separator is :")
var fUsage = flag.String("usage", "", var fUsage = flag.String("usage", "",
"print usage for a plugin, ie, 'telegraf -usage mysql'") "print usage for a plugin, ie, 'telegraf -usage mysql'")
var fService = flag.String("service", "", var fService = flag.String("service", "",
@ -68,47 +73,38 @@ const usage = `Telegraf, The plugin-driven server agent for collecting and repor
Usage: Usage:
telegraf <flags> telegraf [commands|flags]
The flags are: The commands & flags are:
-config <file> configuration file to load config print out full sample configuration to stdout
-test gather metrics once, print them to stdout, and exit version print the version to stdout
-sample-config print out full sample configuration to stdout
-config-directory directory containing additional *.conf files
-input-filter filter the input plugins to enable, separator is :
-input-list print all the plugins inputs
-output-filter filter the output plugins to enable, separator is :
-output-list print all the available outputs
-usage print usage for a plugin, ie, 'telegraf -usage mysql'
-debug print metrics as they're generated to stdout
-quiet run in quiet mode
-version print the version to stdout
-service Control the service, ie, 'telegraf -service install (windows only)'
In addition to the -config flag, telegraf will also load the config file from --config <file> configuration file to load
an environment variable or default location. Precedence is: --test gather metrics once, print them to stdout, and exit
1. -config flag --config-directory directory containing additional *.conf files
2. $TELEGRAF_CONFIG_PATH environment variable --input-filter filter the input plugins to enable, separator is :
3. $HOME/.telegraf/telegraf.conf --output-filter filter the output plugins to enable, separator is :
4. /etc/telegraf/telegraf.conf --usage print usage for a plugin, ie, 'telegraf --usage mysql'
--debug print metrics as they're generated to stdout
--quiet run in quiet mode
Examples: Examples:
# generate a telegraf config file: # generate a telegraf config file:
telegraf -sample-config > telegraf.conf telegraf config > telegraf.conf
# generate config with only cpu input & influxdb output plugins defined # generate config with only cpu input & influxdb output plugins defined
telegraf -sample-config -input-filter cpu -output-filter influxdb telegraf config -input-filter cpu -output-filter influxdb
# run a single telegraf collection, outputing metrics to stdout # run a single telegraf collection, outputing metrics to stdout
telegraf -config telegraf.conf -test telegraf --config telegraf.conf -test
# run telegraf with all plugins defined in config file # run telegraf with all plugins defined in config file
telegraf -config telegraf.conf telegraf --config telegraf.conf
# run telegraf, enabling the cpu & memory input, and influxdb output plugins # run telegraf, enabling the cpu & memory input, and influxdb output plugins
telegraf -config telegraf.conf -input-filter cpu:mem -output-filter influxdb telegraf --config telegraf.conf --input-filter cpu:mem --output-filter influxdb
` `
var stop chan struct{} var stop chan struct{}
@ -128,7 +124,6 @@ func reloadLoop(stop chan struct{}, s service.Service) {
reload <- true reload <- true
for <-reload { for <-reload {
reload <- false reload <- false
flag.Usage = func() { usageExit(0) }
flag.Parse() flag.Parse()
args := flag.Args() args := flag.Args()
@ -142,6 +137,16 @@ func reloadLoop(stop chan struct{}, s service.Service) {
outputFilter := strings.TrimSpace(*fOutputFilters) outputFilter := strings.TrimSpace(*fOutputFilters)
outputFilters = strings.Split(":"+outputFilter+":", ":") outputFilters = strings.Split(":"+outputFilter+":", ":")
} }
var aggregatorFilters []string
if *fAggregatorFilters != "" {
aggregatorFilter := strings.TrimSpace(*fAggregatorFilters)
aggregatorFilters = strings.Split(":"+aggregatorFilter+":", ":")
}
var processorFilters []string
if *fProcessorFilters != "" {
processorFilter := strings.TrimSpace(*fProcessorFilters)
processorFilters = strings.Split(":"+processorFilter+":", ":")
}
if len(args) > 0 { if len(args) > 0 {
switch args[0] { switch args[0] {
@ -149,7 +154,12 @@ func reloadLoop(stop chan struct{}, s service.Service) {
fmt.Printf("Telegraf v%s (git: %s %s)\n", version, branch, commit) fmt.Printf("Telegraf v%s (git: %s %s)\n", version, branch, commit)
return return
case "config": case "config":
config.PrintSampleConfig(inputFilters, outputFilters) config.PrintSampleConfig(
inputFilters,
outputFilters,
aggregatorFilters,
processorFilters,
)
return return
} }
} }
@ -172,12 +182,17 @@ func reloadLoop(stop chan struct{}, s service.Service) {
fmt.Printf("Telegraf v%s (git: %s %s)\n", version, branch, commit) fmt.Printf("Telegraf v%s (git: %s %s)\n", version, branch, commit)
return return
case *fSampleConfig: case *fSampleConfig:
config.PrintSampleConfig(inputFilters, outputFilters) config.PrintSampleConfig(
inputFilters,
outputFilters,
aggregatorFilters,
processorFilters,
)
return return
case *fUsage != "": case *fUsage != "":
if err := config.PrintInputConfig(*fUsage); err != nil { if err := config.PrintInputConfig(*fUsage); err != nil {
if err2 := config.PrintOutputConfig(*fUsage); err2 != nil { if err2 := config.PrintOutputConfig(*fUsage); err2 != nil {
log.Fatalf("%s and %s", err, err2) log.Fatalf("E! %s and %s", err, err2)
} }
} }
return return
@ -189,26 +204,25 @@ func reloadLoop(stop chan struct{}, s service.Service) {
c.InputFilters = inputFilters c.InputFilters = inputFilters
err := c.LoadConfig(*fConfig) err := c.LoadConfig(*fConfig)
if err != nil { if err != nil {
fmt.Println(err) log.Fatal("E! " + err.Error())
os.Exit(1)
} }
if *fConfigDirectory != "" { if *fConfigDirectory != "" {
err = c.LoadDirectory(*fConfigDirectory) err = c.LoadDirectory(*fConfigDirectory)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal("E! " + err.Error())
} }
} }
if len(c.Outputs) == 0 { if len(c.Outputs) == 0 {
log.Fatalf("Error: no outputs found, did you provide a valid config file?") log.Fatalf("E! Error: no outputs found, did you provide a valid config file?")
} }
if len(c.Inputs) == 0 { if len(c.Inputs) == 0 {
log.Fatalf("Error: no inputs found, did you provide a valid config file?") log.Fatalf("E! Error: no inputs found, did you provide a valid config file?")
} }
ag, err := agent.NewAgent(c) ag, err := agent.NewAgent(c)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal("E! " + err.Error())
} }
// Setup logging // Setup logging
@ -221,14 +235,14 @@ func reloadLoop(stop chan struct{}, s service.Service) {
if *fTest { if *fTest {
err = ag.Test() err = ag.Test()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal("E! " + err.Error())
} }
return return
} }
err = ag.Connect() err = ag.Connect()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal("E! " + err.Error())
} }
shutdown := make(chan struct{}) shutdown := make(chan struct{})
@ -259,7 +273,7 @@ func reloadLoop(stop chan struct{}, s service.Service) {
if *fPidfile != "" { if *fPidfile != "" {
f, err := os.Create(*fPidfile) f, err := os.Create(*fPidfile)
if err != nil { if err != nil {
log.Fatalf("Unable to create pidfile: %s", err) log.Fatalf("E! Unable to create pidfile: %s", err)
} }
fmt.Fprintf(f, "%d\n", os.Getpid()) fmt.Fprintf(f, "%d\n", os.Getpid())
@ -291,6 +305,7 @@ func (p *program) Stop(s service.Service) error {
} }
func main() { func main() {
flag.Usage = func() { usageExit(0) }
flag.Parse() flag.Parse()
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
svcConfig := &service.Config{ svcConfig := &service.Config{
@ -304,7 +319,7 @@ func main() {
prg := &program{} prg := &program{}
s, err := service.New(prg, svcConfig) s, err := service.New(prg, svcConfig)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal("E! " + err.Error())
} }
// Handle the -service flag here to prevent any issues with tooling that // Handle the -service flag here to prevent any issues with tooling that
// may not have an interactive session, e.g. installing from Ansible. // may not have an interactive session, e.g. installing from Ansible.
@ -314,7 +329,7 @@ func main() {
} }
err := service.Control(s, *fService) err := service.Control(s, *fService)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal("E! " + err.Error())
} }
} else { } else {
err = s.Run() err = s.Run()

View File

@ -1,38 +1,38 @@
# Telegraf Configuration # Telegraf Configuration
You can see the latest config file with all available plugins here:
[telegraf.conf](https://github.com/influxdata/telegraf/blob/master/etc/telegraf.conf)
## Generating a Configuration File ## Generating a Configuration File
A default Telegraf config file can be generated using the -sample-config flag: A default Telegraf config file can be auto-generated by telegraf:
``` ```
telegraf -sample-config > telegraf.conf telegraf config > telegraf.conf
``` ```
To generate a file with specific inputs and outputs, you can use the To generate a file with specific inputs and outputs, you can use the
-input-filter and -output-filter flags: -input-filter and -output-filter flags:
``` ```
telegraf -sample-config -input-filter cpu:mem:net:swap -output-filter influxdb:kafka telegraf config -input-filter cpu:mem:net:swap -output-filter influxdb:kafka
``` ```
You can see the latest config file with all available plugins here:
[telegraf.conf](https://github.com/influxdata/telegraf/blob/master/etc/telegraf.conf)
## Environment Variables ## Environment Variables
Environment variables can be used anywhere in the config file, simply prepend Environment variables can be used anywhere in the config file, simply prepend
them with $. For strings the variable must be within quotes (ie, "$STR_VAR"), them with $. For strings the variable must be within quotes (ie, "$STR_VAR"),
for numbers and booleans they should be plain (ie, $INT_VAR, $BOOL_VAR) for numbers and booleans they should be plain (ie, $INT_VAR, $BOOL_VAR)
## `[global_tags]` Configuration # Global Tags
Global tags can be specified in the `[global_tags]` section of the config file Global tags can be specified in the `[global_tags]` section of the config file
in key="value" format. All metrics being gathered on this host will be tagged in key="value" format. All metrics being gathered on this host will be tagged
with the tags specified here. with the tags specified here.
## `[agent]` Configuration ## Agent Configuration
Telegraf has a few options you can configure under the `agent` section of the Telegraf has a few options you can configure under the `[agent]` section of the
config. config.
* **interval**: Default data collection interval for all inputs * **interval**: Default data collection interval for all inputs
@ -56,13 +56,63 @@ interval. Maximum flush_interval will be flush_interval + flush_jitter
This is primarily to avoid This is primarily to avoid
large write spikes for users running a large number of telegraf instances. large write spikes for users running a large number of telegraf instances.
ie, a jitter of 5s and flush_interval 10s means flushes will happen every 10-15s. ie, a jitter of 5s and flush_interval 10s means flushes will happen every 10-15s.
* **precision**: By default, precision will be set to the same timestamp order
as the collection interval, with the maximum being 1s. Precision will NOT
be used for service inputs, such as logparser and statsd. Valid values are
"ns", "us" (or "µs"), "ms", "s".
* **logfile**: Specify the log file name. The empty string means to log to stdout.
* **debug**: Run telegraf in debug mode. * **debug**: Run telegraf in debug mode.
* **quiet**: Run telegraf in quiet mode. * **quiet**: Run telegraf in quiet mode (error messages only).
* **hostname**: Override default hostname, if empty use os.Hostname(). * **hostname**: Override default hostname, if empty use os.Hostname().
* **omit_hostname**: If true, do no set the "host" tag in the telegraf agent.
## Input Configuration
The following config parameters are available for all inputs:
* **interval**: How often to gather this metric. Normal plugins use a single
global interval, but if one particular input should be run less or more often,
you can configure that here.
* **name_override**: Override the base name of the measurement.
(Default is the name of the input).
* **name_prefix**: Specifies a prefix to attach to the measurement name.
* **name_suffix**: Specifies a suffix to attach to the measurement name.
* **tags**: A map of tags to apply to a specific input's measurements.
## Output Configuration
There are no generic configuration options available for all outputs.
## Aggregator Configuration
The following config parameters are available for all aggregators:
* **period**: The period on which to flush & clear each aggregator. All metrics
that are sent with timestamps outside of this period will be ignored by the
aggregator.
* **delay**: The delay before each aggregator is flushed. This is to control
how long for aggregators to wait before receiving metrics from input plugins,
in the case that aggregators are flushing and inputs are gathering on the
same interval.
* **drop_original**: If true, the original metric will be dropped by the
aggregator and will not get sent to the output plugins.
* **name_override**: Override the base name of the measurement.
(Default is the name of the input).
* **name_prefix**: Specifies a prefix to attach to the measurement name.
* **name_suffix**: Specifies a suffix to attach to the measurement name.
* **tags**: A map of tags to apply to a specific input's measurements.
## Processor Configuration
The following config parameters are available for all processors:
* **order**: This is the order in which the processor(s) get executed. If this
is not specified then processor execution order will be random.
#### Measurement Filtering #### Measurement Filtering
Filters can be configured per input or output, see below for examples. Filters can be configured per input, output, processor, or aggregator,
see below for examples.
* **namepass**: An array of strings that is used to filter metrics generated by the * **namepass**: An array of strings that is used to filter metrics generated by the
current input. Each string in the array is tested as a glob match against current input. Each string in the array is tested as a glob match against
@ -90,19 +140,6 @@ the tag keys in the final measurement.
the plugin definition, otherwise subsequent plugin config options will be the plugin definition, otherwise subsequent plugin config options will be
interpreted as part of the tagpass/tagdrop map. interpreted as part of the tagpass/tagdrop map.
## Input Configuration
Some configuration options are configurable per input:
* **name_override**: Override the base name of the measurement.
(Default is the name of the input).
* **name_prefix**: Specifies a prefix to attach to the measurement name.
* **name_suffix**: Specifies a suffix to attach to the measurement name.
* **tags**: A map of tags to apply to a specific input's measurements.
* **interval**: How often to gather this metric. Normal plugins use a single
global interval, but if one particular input should be run less or more often,
you can configure that here.
#### Input Configuration Examples #### Input Configuration Examples
This is a full working config that will output CPU data to an InfluxDB instance This is a full working config that will output CPU data to an InfluxDB instance
@ -254,11 +291,7 @@ to avoid measurement collisions:
fielddrop = ["cpu_time*"] fielddrop = ["cpu_time*"]
``` ```
## Output Configuration #### Output Configuration Examples:
Telegraf also supports specifying multiple output sinks to send data to,
configuring each output sink is different, but examples can be
found by running `telegraf -sample-config`.
```toml ```toml
[[outputs.influxdb]] [[outputs.influxdb]]
@ -283,3 +316,39 @@ found by running `telegraf -sample-config`.
[outputs.influxdb.tagpass] [outputs.influxdb.tagpass]
cpu = ["cpu0"] cpu = ["cpu0"]
``` ```
#### Aggregator Configuration Examples:
This will collect and emit the min/max of the system load1 metric every
30s, dropping the originals.
```toml
[[inputs.system]]
fieldpass = ["load1"] # collects system load1 metric.
[[aggregators.minmax]]
period = "30s" # send & clear the aggregate every 30s.
drop_original = true # drop the original metrics.
[[outputs.file]]
files = ["stdout"]
```
This will collect and emit the min/max of the swap metrics every
30s, dropping the originals. The aggregator will not be applied
to the system load metrics due to the `namepass` parameter.
```toml
[[inputs.swap]]
[[inputs.system]]
fieldpass = ["load1"] # collects system load1 metric.
[[aggregators.minmax]]
period = "30s" # send & clear the aggregate every 30s.
drop_original = true # drop the original metrics.
namepass = ["swap"] # only "pass" swap metrics through the aggregator.
[[outputs.file]]
files = ["stdout"]
```

View File

@ -232,6 +232,16 @@ us.west.cpu.load 100
=> cpu.load,region=us.west value=100 => cpu.load,region=us.west value=100
``` ```
Multiple templates can also be specified, but these should be differentiated
using _filters_ (see below for more details)
```toml
templates = [
"*.*.* region.region.measurement", # <- all 3-part measurements will match this one.
"*.*.*.* region.region.host.measurement", # <- all 4-part measurements will match this one.
]
```
#### Field Templates: #### Field Templates:
The field keyword tells Telegraf to give the metric that field name. The field keyword tells Telegraf to give the metric that field name.

View File

@ -11,15 +11,18 @@ import (
"regexp" "regexp"
"runtime" "runtime"
"sort" "sort"
"strconv"
"strings" "strings"
"time" "time"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal" "github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/internal/models" "github.com/influxdata/telegraf/internal/models"
"github.com/influxdata/telegraf/plugins/aggregators"
"github.com/influxdata/telegraf/plugins/inputs" "github.com/influxdata/telegraf/plugins/inputs"
"github.com/influxdata/telegraf/plugins/outputs" "github.com/influxdata/telegraf/plugins/outputs"
"github.com/influxdata/telegraf/plugins/parsers" "github.com/influxdata/telegraf/plugins/parsers"
"github.com/influxdata/telegraf/plugins/processors"
"github.com/influxdata/telegraf/plugins/serializers" "github.com/influxdata/telegraf/plugins/serializers"
"github.com/influxdata/config" "github.com/influxdata/config"
@ -47,9 +50,12 @@ type Config struct {
InputFilters []string InputFilters []string
OutputFilters []string OutputFilters []string
Agent *AgentConfig Agent *AgentConfig
Inputs []*models.RunningInput Inputs []*models.RunningInput
Outputs []*models.RunningOutput Outputs []*models.RunningOutput
Aggregators []*models.RunningAggregator
// Processors have a slice wrapper type because they need to be sorted
Processors models.RunningProcessors
} }
func NewConfig() *Config { func NewConfig() *Config {
@ -64,6 +70,7 @@ func NewConfig() *Config {
Tags: make(map[string]string), Tags: make(map[string]string),
Inputs: make([]*models.RunningInput, 0), Inputs: make([]*models.RunningInput, 0),
Outputs: make([]*models.RunningOutput, 0), Outputs: make([]*models.RunningOutput, 0),
Processors: make([]*models.RunningProcessor, 0),
InputFilters: make([]string, 0), InputFilters: make([]string, 0),
OutputFilters: make([]string, 0), OutputFilters: make([]string, 0),
} }
@ -138,7 +145,7 @@ type AgentConfig struct {
func (c *Config) InputNames() []string { func (c *Config) InputNames() []string {
var name []string var name []string
for _, input := range c.Inputs { for _, input := range c.Inputs {
name = append(name, input.Name) name = append(name, input.Name())
} }
return name return name
} }
@ -234,7 +241,7 @@ var header = `# Telegraf Configuration
debug = false debug = false
## Run telegraf in quiet mode (error log messages only). ## Run telegraf in quiet mode (error log messages only).
quiet = false quiet = false
## Specify the log file name. The empty string means to log to stdout. ## Specify the log file name. The empty string means to log to stderr.
logfile = "" logfile = ""
## Override default hostname, if empty use os.Hostname() ## Override default hostname, if empty use os.Hostname()
@ -248,6 +255,20 @@ var header = `# Telegraf Configuration
############################################################################### ###############################################################################
` `
var processorHeader = `
###############################################################################
# PROCESSOR PLUGINS #
###############################################################################
`
var aggregatorHeader = `
###############################################################################
# AGGREGATOR PLUGINS #
###############################################################################
`
var inputHeader = ` var inputHeader = `
############################################################################### ###############################################################################
@ -263,9 +284,15 @@ var serviceInputHeader = `
` `
// PrintSampleConfig prints the sample config // PrintSampleConfig prints the sample config
func PrintSampleConfig(inputFilters []string, outputFilters []string) { func PrintSampleConfig(
inputFilters []string,
outputFilters []string,
aggregatorFilters []string,
processorFilters []string,
) {
fmt.Printf(header) fmt.Printf(header)
// print output plugins
if len(outputFilters) != 0 { if len(outputFilters) != 0 {
printFilteredOutputs(outputFilters, false) printFilteredOutputs(outputFilters, false)
} else { } else {
@ -281,6 +308,33 @@ func PrintSampleConfig(inputFilters []string, outputFilters []string) {
printFilteredOutputs(pnames, true) printFilteredOutputs(pnames, true)
} }
// print processor plugins
fmt.Printf(processorHeader)
if len(processorFilters) != 0 {
printFilteredProcessors(processorFilters, false)
} else {
pnames := []string{}
for pname := range processors.Processors {
pnames = append(pnames, pname)
}
sort.Strings(pnames)
printFilteredProcessors(pnames, true)
}
// pring aggregator plugins
fmt.Printf(aggregatorHeader)
if len(aggregatorFilters) != 0 {
printFilteredAggregators(aggregatorFilters, false)
} else {
pnames := []string{}
for pname := range aggregators.Aggregators {
pnames = append(pnames, pname)
}
sort.Strings(pnames)
printFilteredAggregators(pnames, true)
}
// print input plugins
fmt.Printf(inputHeader) fmt.Printf(inputHeader)
if len(inputFilters) != 0 { if len(inputFilters) != 0 {
printFilteredInputs(inputFilters, false) printFilteredInputs(inputFilters, false)
@ -298,6 +352,42 @@ func PrintSampleConfig(inputFilters []string, outputFilters []string) {
} }
} }
func printFilteredProcessors(processorFilters []string, commented bool) {
// Filter processors
var pnames []string
for pname := range processors.Processors {
if sliceContains(pname, processorFilters) {
pnames = append(pnames, pname)
}
}
sort.Strings(pnames)
// Print Outputs
for _, pname := range pnames {
creator := processors.Processors[pname]
output := creator()
printConfig(pname, output, "processors", commented)
}
}
func printFilteredAggregators(aggregatorFilters []string, commented bool) {
// Filter outputs
var anames []string
for aname := range aggregators.Aggregators {
if sliceContains(aname, aggregatorFilters) {
anames = append(anames, aname)
}
}
sort.Strings(anames)
// Print Outputs
for _, aname := range anames {
creator := aggregators.Aggregators[aname]
output := creator()
printConfig(aname, output, "aggregators", commented)
}
}
func printFilteredInputs(inputFilters []string, commented bool) { func printFilteredInputs(inputFilters []string, commented bool) {
// Filter inputs // Filter inputs
var pnames []string var pnames []string
@ -507,6 +597,7 @@ func (c *Config) LoadConfig(path string) error {
case "outputs": case "outputs":
for pluginName, pluginVal := range subTable.Fields { for pluginName, pluginVal := range subTable.Fields {
switch pluginSubTable := pluginVal.(type) { switch pluginSubTable := pluginVal.(type) {
// legacy [outputs.influxdb] support
case *ast.Table: case *ast.Table:
if err = c.addOutput(pluginName, pluginSubTable); err != nil { if err = c.addOutput(pluginName, pluginSubTable); err != nil {
return fmt.Errorf("Error parsing %s, %s", path, err) return fmt.Errorf("Error parsing %s, %s", path, err)
@ -525,6 +616,7 @@ func (c *Config) LoadConfig(path string) error {
case "inputs", "plugins": case "inputs", "plugins":
for pluginName, pluginVal := range subTable.Fields { for pluginName, pluginVal := range subTable.Fields {
switch pluginSubTable := pluginVal.(type) { switch pluginSubTable := pluginVal.(type) {
// legacy [inputs.cpu] support
case *ast.Table: case *ast.Table:
if err = c.addInput(pluginName, pluginSubTable); err != nil { if err = c.addInput(pluginName, pluginSubTable); err != nil {
return fmt.Errorf("Error parsing %s, %s", path, err) return fmt.Errorf("Error parsing %s, %s", path, err)
@ -540,6 +632,34 @@ func (c *Config) LoadConfig(path string) error {
pluginName, path) pluginName, path)
} }
} }
case "processors":
for pluginName, pluginVal := range subTable.Fields {
switch pluginSubTable := pluginVal.(type) {
case []*ast.Table:
for _, t := range pluginSubTable {
if err = c.addProcessor(pluginName, t); err != nil {
return fmt.Errorf("Error parsing %s, %s", path, err)
}
}
default:
return fmt.Errorf("Unsupported config format: %s, file %s",
pluginName, path)
}
}
case "aggregators":
for pluginName, pluginVal := range subTable.Fields {
switch pluginSubTable := pluginVal.(type) {
case []*ast.Table:
for _, t := range pluginSubTable {
if err = c.addAggregator(pluginName, t); err != nil {
return fmt.Errorf("Error parsing %s, %s", path, err)
}
}
default:
return fmt.Errorf("Unsupported config format: %s, file %s",
pluginName, path)
}
}
// Assume it's an input input for legacy config file support if no other // Assume it's an input input for legacy config file support if no other
// identifiers are present // identifiers are present
default: default:
@ -548,6 +668,10 @@ func (c *Config) LoadConfig(path string) error {
} }
} }
} }
if len(c.Processors) > 1 {
sort.Sort(c.Processors)
}
return nil return nil
} }
@ -580,6 +704,52 @@ func parseFile(fpath string) (*ast.Table, error) {
return toml.Parse(contents) return toml.Parse(contents)
} }
func (c *Config) addAggregator(name string, table *ast.Table) error {
creator, ok := aggregators.Aggregators[name]
if !ok {
return fmt.Errorf("Undefined but requested aggregator: %s", name)
}
aggregator := creator()
conf, err := buildAggregator(name, table)
if err != nil {
return err
}
if err := config.UnmarshalTable(table, aggregator); err != nil {
return err
}
c.Aggregators = append(c.Aggregators, models.NewRunningAggregator(aggregator, conf))
return nil
}
func (c *Config) addProcessor(name string, table *ast.Table) error {
creator, ok := processors.Processors[name]
if !ok {
return fmt.Errorf("Undefined but requested processor: %s", name)
}
processor := creator()
processorConfig, err := buildProcessor(name, table)
if err != nil {
return err
}
if err := config.UnmarshalTable(table, processor); err != nil {
return err
}
rf := &models.RunningProcessor{
Name: name,
Processor: processor,
Config: processorConfig,
}
c.Processors = append(c.Processors, rf)
return nil
}
func (c *Config) addOutput(name string, table *ast.Table) error { func (c *Config) addOutput(name string, table *ast.Table) error {
if len(c.OutputFilters) > 0 && !sliceContains(name, c.OutputFilters) { if len(c.OutputFilters) > 0 && !sliceContains(name, c.OutputFilters) {
return nil return nil
@ -652,7 +822,6 @@ func (c *Config) addInput(name string, table *ast.Table) error {
} }
rp := &models.RunningInput{ rp := &models.RunningInput{
Name: name,
Input: input, Input: input,
Config: pluginConfig, Config: pluginConfig,
} }
@ -660,6 +829,144 @@ func (c *Config) addInput(name string, table *ast.Table) error {
return nil return nil
} }
// buildAggregator parses Aggregator specific items from the ast.Table,
// builds the filter and returns a
// models.AggregatorConfig to be inserted into models.RunningAggregator
func buildAggregator(name string, tbl *ast.Table) (*models.AggregatorConfig, error) {
unsupportedFields := []string{"tagexclude", "taginclude"}
for _, field := range unsupportedFields {
if _, ok := tbl.Fields[field]; ok {
return nil, fmt.Errorf("%s is not supported for aggregator plugins (%s).",
field, name)
}
}
conf := &models.AggregatorConfig{
Name: name,
Delay: time.Millisecond * 100,
Period: time.Second * 30,
}
if node, ok := tbl.Fields["period"]; ok {
if kv, ok := node.(*ast.KeyValue); ok {
if str, ok := kv.Value.(*ast.String); ok {
dur, err := time.ParseDuration(str.Value)
if err != nil {
return nil, err
}
conf.Period = dur
}
}
}
if node, ok := tbl.Fields["delay"]; ok {
if kv, ok := node.(*ast.KeyValue); ok {
if str, ok := kv.Value.(*ast.String); ok {
dur, err := time.ParseDuration(str.Value)
if err != nil {
return nil, err
}
conf.Delay = dur
}
}
}
if node, ok := tbl.Fields["drop_original"]; ok {
if kv, ok := node.(*ast.KeyValue); ok {
if b, ok := kv.Value.(*ast.Boolean); ok {
var err error
conf.DropOriginal, err = strconv.ParseBool(b.Value)
if err != nil {
log.Printf("Error parsing boolean value for %s: %s\n", name, err)
}
}
}
}
if node, ok := tbl.Fields["name_prefix"]; ok {
if kv, ok := node.(*ast.KeyValue); ok {
if str, ok := kv.Value.(*ast.String); ok {
conf.MeasurementPrefix = str.Value
}
}
}
if node, ok := tbl.Fields["name_suffix"]; ok {
if kv, ok := node.(*ast.KeyValue); ok {
if str, ok := kv.Value.(*ast.String); ok {
conf.MeasurementSuffix = str.Value
}
}
}
if node, ok := tbl.Fields["name_override"]; ok {
if kv, ok := node.(*ast.KeyValue); ok {
if str, ok := kv.Value.(*ast.String); ok {
conf.NameOverride = str.Value
}
}
}
conf.Tags = make(map[string]string)
if node, ok := tbl.Fields["tags"]; ok {
if subtbl, ok := node.(*ast.Table); ok {
if err := config.UnmarshalTable(subtbl, conf.Tags); err != nil {
log.Printf("Could not parse tags for input %s\n", name)
}
}
}
delete(tbl.Fields, "period")
delete(tbl.Fields, "delay")
delete(tbl.Fields, "drop_original")
delete(tbl.Fields, "name_prefix")
delete(tbl.Fields, "name_suffix")
delete(tbl.Fields, "name_override")
delete(tbl.Fields, "tags")
var err error
conf.Filter, err = buildFilter(tbl)
if err != nil {
return conf, err
}
return conf, nil
}
// buildProcessor parses Processor specific items from the ast.Table,
// builds the filter and returns a
// models.ProcessorConfig to be inserted into models.RunningProcessor
func buildProcessor(name string, tbl *ast.Table) (*models.ProcessorConfig, error) {
conf := &models.ProcessorConfig{Name: name}
unsupportedFields := []string{"tagexclude", "taginclude", "fielddrop", "fieldpass"}
for _, field := range unsupportedFields {
if _, ok := tbl.Fields[field]; ok {
return nil, fmt.Errorf("%s is not supported for processor plugins (%s).",
field, name)
}
}
if node, ok := tbl.Fields["order"]; ok {
if kv, ok := node.(*ast.KeyValue); ok {
if b, ok := kv.Value.(*ast.Integer); ok {
var err error
conf.Order, err = strconv.ParseInt(b.Value, 10, 64)
if err != nil {
log.Printf("Error parsing int value for %s: %s\n", name, err)
}
}
}
}
delete(tbl.Fields, "order")
var err error
conf.Filter, err = buildFilter(tbl)
if err != nil {
return conf, err
}
return conf, nil
}
// buildFilter builds a Filter // buildFilter builds a Filter
// (tagpass/tagdrop/namepass/namedrop/fieldpass/fielddrop) to // (tagpass/tagdrop/namepass/namedrop/fieldpass/fielddrop) to
// be inserted into the models.OutputConfig/models.InputConfig // be inserted into the models.OutputConfig/models.InputConfig

View File

@ -96,7 +96,7 @@ func (f *Filter) Compile() error {
// Apply applies the filter to the given measurement name, fields map, and // Apply applies the filter to the given measurement name, fields map, and
// tags map. It will return false if the metric should be "filtered out", and // tags map. It will return false if the metric should be "filtered out", and
// true if the metric should "pass". // true if the metric should "pass".
// It will modify tags in-place if they need to be deleted. // It will modify tags & fields in-place if they need to be deleted.
func (f *Filter) Apply( func (f *Filter) Apply(
measurement string, measurement string,
fields map[string]interface{}, fields map[string]interface{},

View File

@ -0,0 +1,154 @@
package models
import (
"log"
"math"
"time"
"github.com/influxdata/telegraf"
)
// makemetric is used by both RunningAggregator & RunningInput
// to make metrics.
// nameOverride: override the name of the measurement being made.
// namePrefix: add this prefix to each measurement name.
// nameSuffix: add this suffix to each measurement name.
// pluginTags: these are tags that are specific to this plugin.
// daemonTags: these are daemon-wide global tags, and get applied after pluginTags.
// filter: this is a filter to apply to each metric being made.
// applyFilter: if false, the above filter is not applied to each metric.
// This is used by Aggregators, because aggregators use filters
// on incoming metrics instead of on created metrics.
// TODO refactor this to not have such a huge func signature.
func makemetric(
measurement string,
fields map[string]interface{},
tags map[string]string,
nameOverride string,
namePrefix string,
nameSuffix string,
pluginTags map[string]string,
daemonTags map[string]string,
filter Filter,
applyFilter bool,
debug bool,
mType telegraf.ValueType,
t time.Time,
) telegraf.Metric {
if len(fields) == 0 || len(measurement) == 0 {
return nil
}
if tags == nil {
tags = make(map[string]string)
}
// Override measurement name if set
if len(nameOverride) != 0 {
measurement = nameOverride
}
// Apply measurement prefix and suffix if set
if len(namePrefix) != 0 {
measurement = namePrefix + measurement
}
if len(nameSuffix) != 0 {
measurement = measurement + nameSuffix
}
// Apply plugin-wide tags if set
for k, v := range pluginTags {
if _, ok := tags[k]; !ok {
tags[k] = v
}
}
// Apply daemon-wide tags if set
for k, v := range daemonTags {
if _, ok := tags[k]; !ok {
tags[k] = v
}
}
// Apply the metric filter(s)
// for aggregators, the filter does not get applied when the metric is made.
// instead, the filter is applied to metric incoming into the plugin.
// ie, it gets applied in the RunningAggregator.Apply function.
if applyFilter {
if ok := filter.Apply(measurement, fields, tags); !ok {
return nil
}
}
for k, v := range fields {
// Validate uint64 and float64 fields
// convert all int & uint types to int64
switch val := v.(type) {
case nil:
// delete nil fields
delete(fields, k)
case uint:
fields[k] = int64(val)
continue
case uint8:
fields[k] = int64(val)
continue
case uint16:
fields[k] = int64(val)
continue
case uint32:
fields[k] = int64(val)
continue
case int:
fields[k] = int64(val)
continue
case int8:
fields[k] = int64(val)
continue
case int16:
fields[k] = int64(val)
continue
case int32:
fields[k] = int64(val)
continue
case uint64:
// InfluxDB does not support writing uint64
if val < uint64(9223372036854775808) {
fields[k] = int64(val)
} else {
fields[k] = int64(9223372036854775807)
}
continue
case float32:
fields[k] = float64(val)
continue
case float64:
// NaNs are invalid values in influxdb, skip measurement
if math.IsNaN(val) || math.IsInf(val, 0) {
if debug {
log.Printf("Measurement [%s] field [%s] has a NaN or Inf "+
"field, skipping",
measurement, k)
}
delete(fields, k)
continue
}
default:
fields[k] = v
}
}
var m telegraf.Metric
var err error
switch mType {
case telegraf.Counter:
m, err = telegraf.NewCounterMetric(measurement, tags, fields, t)
case telegraf.Gauge:
m, err = telegraf.NewGaugeMetric(measurement, tags, fields, t)
default:
m, err = telegraf.NewMetric(measurement, tags, fields, t)
}
if err != nil {
log.Printf("Error adding point [%s]: %s\n", measurement, err.Error())
return nil
}
return m
}

View File

@ -0,0 +1,164 @@
package models
import (
"time"
"github.com/influxdata/telegraf"
)
type RunningAggregator struct {
a telegraf.Aggregator
Config *AggregatorConfig
metrics chan telegraf.Metric
periodStart time.Time
periodEnd time.Time
}
func NewRunningAggregator(
a telegraf.Aggregator,
conf *AggregatorConfig,
) *RunningAggregator {
return &RunningAggregator{
a: a,
Config: conf,
metrics: make(chan telegraf.Metric, 100),
}
}
// AggregatorConfig containing configuration parameters for the running
// aggregator plugin.
type AggregatorConfig struct {
Name string
DropOriginal bool
NameOverride string
MeasurementPrefix string
MeasurementSuffix string
Tags map[string]string
Filter Filter
Period time.Duration
Delay time.Duration
}
func (r *RunningAggregator) Name() string {
return "aggregators." + r.Config.Name
}
func (r *RunningAggregator) MakeMetric(
measurement string,
fields map[string]interface{},
tags map[string]string,
mType telegraf.ValueType,
t time.Time,
) telegraf.Metric {
m := makemetric(
measurement,
fields,
tags,
r.Config.NameOverride,
r.Config.MeasurementPrefix,
r.Config.MeasurementSuffix,
r.Config.Tags,
nil,
r.Config.Filter,
false,
false,
mType,
t,
)
m.SetAggregate(true)
return m
}
// Add applies the given metric to the aggregator.
// Before applying to the plugin, it will run any defined filters on the metric.
// Apply returns true if the original metric should be dropped.
func (r *RunningAggregator) Add(in telegraf.Metric) bool {
if r.Config.Filter.IsActive() {
// check if the aggregator should apply this metric
name := in.Name()
fields := in.Fields()
tags := in.Tags()
t := in.Time()
if ok := r.Config.Filter.Apply(name, fields, tags); !ok {
// aggregator should not apply this metric
return false
}
in, _ = telegraf.NewMetric(name, tags, fields, t)
}
r.metrics <- in
return r.Config.DropOriginal
}
func (r *RunningAggregator) add(in telegraf.Metric) {
r.a.Add(in)
}
func (r *RunningAggregator) push(acc telegraf.Accumulator) {
r.a.Push(acc)
}
func (r *RunningAggregator) reset() {
r.a.Reset()
}
// Run runs the running aggregator, listens for incoming metrics, and waits
// for period ticks to tell it when to push and reset the aggregator.
func (r *RunningAggregator) Run(
acc telegraf.Accumulator,
shutdown chan struct{},
) {
// The start of the period is truncated to the nearest second.
//
// Every metric then gets it's timestamp checked and is dropped if it
// is not within:
//
// start < t < end + truncation + delay
//
// So if we start at now = 00:00.2 with a 10s period and 0.3s delay:
// now = 00:00.2
// start = 00:00
// truncation = 00:00.2
// end = 00:10
// 1st interval: 00:00 - 00:10.5
// 2nd interval: 00:10 - 00:20.5
// etc.
//
now := time.Now()
r.periodStart = now.Truncate(time.Second)
truncation := now.Sub(r.periodStart)
r.periodEnd = r.periodStart.Add(r.Config.Period)
time.Sleep(r.Config.Delay)
periodT := time.NewTicker(r.Config.Period)
defer periodT.Stop()
for {
select {
case <-shutdown:
if len(r.metrics) > 0 {
// wait until metrics are flushed before exiting
continue
}
return
case m := <-r.metrics:
if m.Time().Before(r.periodStart) ||
m.Time().After(r.periodEnd.Add(truncation).Add(r.Config.Delay)) {
// the metric is outside the current aggregation period, so
// skip it.
continue
}
r.add(m)
case <-periodT.C:
r.periodStart = r.periodEnd
r.periodEnd = r.periodStart.Add(r.Config.Period)
r.push(acc)
r.reset()
}
}
}

View File

@ -0,0 +1,256 @@
package models
import (
"fmt"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/assert"
)
func TestAdd(t *testing.T) {
a := &TestAggregator{}
ra := NewRunningAggregator(a, &AggregatorConfig{
Name: "TestRunningAggregator",
Filter: Filter{
NamePass: []string{"*"},
},
Period: time.Millisecond * 500,
})
assert.NoError(t, ra.Config.Filter.Compile())
acc := testutil.Accumulator{}
go ra.Run(&acc, make(chan struct{}))
m := ra.MakeMetric(
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{},
telegraf.Untyped,
time.Now().Add(time.Millisecond*150),
)
assert.False(t, ra.Add(m))
for {
time.Sleep(time.Millisecond)
if atomic.LoadInt64(&a.sum) > 0 {
break
}
}
assert.Equal(t, int64(101), atomic.LoadInt64(&a.sum))
}
func TestAddMetricsOutsideCurrentPeriod(t *testing.T) {
a := &TestAggregator{}
ra := NewRunningAggregator(a, &AggregatorConfig{
Name: "TestRunningAggregator",
Filter: Filter{
NamePass: []string{"*"},
},
Period: time.Millisecond * 500,
})
assert.NoError(t, ra.Config.Filter.Compile())
acc := testutil.Accumulator{}
go ra.Run(&acc, make(chan struct{}))
// metric before current period
m := ra.MakeMetric(
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{},
telegraf.Untyped,
time.Now().Add(-time.Hour),
)
assert.False(t, ra.Add(m))
// metric after current period
m = ra.MakeMetric(
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{},
telegraf.Untyped,
time.Now().Add(time.Hour),
)
assert.False(t, ra.Add(m))
// "now" metric
m = ra.MakeMetric(
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{},
telegraf.Untyped,
time.Now().Add(time.Millisecond*50),
)
assert.False(t, ra.Add(m))
for {
time.Sleep(time.Millisecond)
if atomic.LoadInt64(&a.sum) > 0 {
break
}
}
assert.Equal(t, int64(101), atomic.LoadInt64(&a.sum))
}
func TestAddAndPushOnePeriod(t *testing.T) {
a := &TestAggregator{}
ra := NewRunningAggregator(a, &AggregatorConfig{
Name: "TestRunningAggregator",
Filter: Filter{
NamePass: []string{"*"},
},
Period: time.Millisecond * 500,
})
assert.NoError(t, ra.Config.Filter.Compile())
acc := testutil.Accumulator{}
shutdown := make(chan struct{})
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
ra.Run(&acc, shutdown)
}()
m := ra.MakeMetric(
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{},
telegraf.Untyped,
time.Now().Add(time.Millisecond*100),
)
assert.False(t, ra.Add(m))
for {
time.Sleep(time.Millisecond)
if acc.NMetrics() > 0 {
break
}
}
acc.AssertContainsFields(t, "TestMetric", map[string]interface{}{"sum": int64(101)})
close(shutdown)
wg.Wait()
}
func TestAddDropOriginal(t *testing.T) {
ra := NewRunningAggregator(&TestAggregator{}, &AggregatorConfig{
Name: "TestRunningAggregator",
Filter: Filter{
NamePass: []string{"RI*"},
},
DropOriginal: true,
})
assert.NoError(t, ra.Config.Filter.Compile())
m := ra.MakeMetric(
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{},
telegraf.Untyped,
time.Now(),
)
assert.True(t, ra.Add(m))
// this metric name doesn't match the filter, so Add will return false
m2 := ra.MakeMetric(
"foobar",
map[string]interface{}{"value": int(101)},
map[string]string{},
telegraf.Untyped,
time.Now(),
)
assert.False(t, ra.Add(m2))
}
// make an untyped, counter, & gauge metric
func TestMakeMetricA(t *testing.T) {
now := time.Now()
ra := NewRunningAggregator(&TestAggregator{}, &AggregatorConfig{
Name: "TestRunningAggregator",
})
assert.Equal(t, "aggregators.TestRunningAggregator", ra.Name())
m := ra.MakeMetric(
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{},
telegraf.Untyped,
now,
)
assert.Equal(
t,
m.String(),
fmt.Sprintf("RITest value=101i %d", now.UnixNano()),
)
assert.Equal(
t,
m.Type(),
telegraf.Untyped,
)
m = ra.MakeMetric(
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{},
telegraf.Counter,
now,
)
assert.Equal(
t,
m.String(),
fmt.Sprintf("RITest value=101i %d", now.UnixNano()),
)
assert.Equal(
t,
m.Type(),
telegraf.Counter,
)
m = ra.MakeMetric(
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{},
telegraf.Gauge,
now,
)
assert.Equal(
t,
m.String(),
fmt.Sprintf("RITest value=101i %d", now.UnixNano()),
)
assert.Equal(
t,
m.Type(),
telegraf.Gauge,
)
}
type TestAggregator struct {
sum int64
}
func (t *TestAggregator) Description() string { return "" }
func (t *TestAggregator) SampleConfig() string { return "" }
func (t *TestAggregator) Reset() {
atomic.StoreInt64(&t.sum, 0)
}
func (t *TestAggregator) Push(acc telegraf.Accumulator) {
acc.AddFields("TestMetric",
map[string]interface{}{"sum": t.sum},
map[string]string{},
)
}
func (t *TestAggregator) Add(in telegraf.Metric) {
for _, v := range in.Fields() {
if vi, ok := v.(int64); ok {
atomic.AddInt64(&t.sum, vi)
}
}
}

View File

@ -1,15 +1,19 @@
package models package models
import ( import (
"fmt"
"time" "time"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
) )
type RunningInput struct { type RunningInput struct {
Name string
Input telegraf.Input Input telegraf.Input
Config *InputConfig Config *InputConfig
trace bool
debug bool
defaultTags map[string]string
} }
// InputConfig containing a name, interval, and filter // InputConfig containing a name, interval, and filter
@ -22,3 +26,59 @@ type InputConfig struct {
Filter Filter Filter Filter
Interval time.Duration Interval time.Duration
} }
func (r *RunningInput) Name() string {
return "inputs." + r.Config.Name
}
// MakeMetric either returns a metric, or returns nil if the metric doesn't
// need to be created (because of filtering, an error, etc.)
func (r *RunningInput) MakeMetric(
measurement string,
fields map[string]interface{},
tags map[string]string,
mType telegraf.ValueType,
t time.Time,
) telegraf.Metric {
m := makemetric(
measurement,
fields,
tags,
r.Config.NameOverride,
r.Config.MeasurementPrefix,
r.Config.MeasurementSuffix,
r.Config.Tags,
r.defaultTags,
r.Config.Filter,
true,
r.debug,
mType,
t,
)
if r.trace && m != nil {
fmt.Println("> " + m.String())
}
return m
}
func (r *RunningInput) Debug() bool {
return r.debug
}
func (r *RunningInput) SetDebug(debug bool) {
r.debug = debug
}
func (r *RunningInput) Trace() bool {
return r.trace
}
func (r *RunningInput) SetTrace(trace bool) {
r.trace = trace
}
func (r *RunningInput) SetDefaultTags(tags map[string]string) {
r.defaultTags = tags
}

View File

@ -0,0 +1,352 @@
package models
import (
"fmt"
"math"
"testing"
"time"
"github.com/influxdata/telegraf"
"github.com/stretchr/testify/assert"
)
func TestMakeMetricNoFields(t *testing.T) {
now := time.Now()
ri := RunningInput{
Config: &InputConfig{
Name: "TestRunningInput",
},
}
m := ri.MakeMetric(
"RITest",
map[string]interface{}{},
map[string]string{},
telegraf.Untyped,
now,
)
assert.Nil(t, m)
}
// nil fields should get dropped
func TestMakeMetricNilFields(t *testing.T) {
now := time.Now()
ri := RunningInput{
Config: &InputConfig{
Name: "TestRunningInput",
},
}
m := ri.MakeMetric(
"RITest",
map[string]interface{}{
"value": int(101),
"nil": nil,
},
map[string]string{},
telegraf.Untyped,
now,
)
assert.Equal(
t,
fmt.Sprintf("RITest value=101i %d", now.UnixNano()),
m.String(),
)
}
// make an untyped, counter, & gauge metric
func TestMakeMetric(t *testing.T) {
now := time.Now()
ri := RunningInput{
Config: &InputConfig{
Name: "TestRunningInput",
},
}
ri.SetDebug(true)
assert.Equal(t, true, ri.Debug())
ri.SetTrace(true)
assert.Equal(t, true, ri.Trace())
assert.Equal(t, "inputs.TestRunningInput", ri.Name())
m := ri.MakeMetric(
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{},
telegraf.Untyped,
now,
)
assert.Equal(
t,
m.String(),
fmt.Sprintf("RITest value=101i %d", now.UnixNano()),
)
assert.Equal(
t,
m.Type(),
telegraf.Untyped,
)
m = ri.MakeMetric(
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{},
telegraf.Counter,
now,
)
assert.Equal(
t,
m.String(),
fmt.Sprintf("RITest value=101i %d", now.UnixNano()),
)
assert.Equal(
t,
m.Type(),
telegraf.Counter,
)
m = ri.MakeMetric(
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{},
telegraf.Gauge,
now,
)
assert.Equal(
t,
m.String(),
fmt.Sprintf("RITest value=101i %d", now.UnixNano()),
)
assert.Equal(
t,
m.Type(),
telegraf.Gauge,
)
}
func TestMakeMetricWithPluginTags(t *testing.T) {
now := time.Now()
ri := RunningInput{
Config: &InputConfig{
Name: "TestRunningInput",
Tags: map[string]string{
"foo": "bar",
},
},
}
ri.SetDebug(true)
assert.Equal(t, true, ri.Debug())
ri.SetTrace(true)
assert.Equal(t, true, ri.Trace())
m := ri.MakeMetric(
"RITest",
map[string]interface{}{"value": int(101)},
nil,
telegraf.Untyped,
now,
)
assert.Equal(
t,
m.String(),
fmt.Sprintf("RITest,foo=bar value=101i %d", now.UnixNano()),
)
}
func TestMakeMetricFilteredOut(t *testing.T) {
now := time.Now()
ri := RunningInput{
Config: &InputConfig{
Name: "TestRunningInput",
Tags: map[string]string{
"foo": "bar",
},
Filter: Filter{NamePass: []string{"foobar"}},
},
}
ri.SetDebug(true)
assert.Equal(t, true, ri.Debug())
ri.SetTrace(true)
assert.Equal(t, true, ri.Trace())
assert.NoError(t, ri.Config.Filter.Compile())
m := ri.MakeMetric(
"RITest",
map[string]interface{}{"value": int(101)},
nil,
telegraf.Untyped,
now,
)
assert.Nil(t, m)
}
func TestMakeMetricWithDaemonTags(t *testing.T) {
now := time.Now()
ri := RunningInput{
Config: &InputConfig{
Name: "TestRunningInput",
},
}
ri.SetDefaultTags(map[string]string{
"foo": "bar",
})
ri.SetDebug(true)
assert.Equal(t, true, ri.Debug())
ri.SetTrace(true)
assert.Equal(t, true, ri.Trace())
m := ri.MakeMetric(
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{},
telegraf.Untyped,
now,
)
assert.Equal(
t,
m.String(),
fmt.Sprintf("RITest,foo=bar value=101i %d", now.UnixNano()),
)
}
// make an untyped, counter, & gauge metric
func TestMakeMetricInfFields(t *testing.T) {
inf := math.Inf(1)
ninf := math.Inf(-1)
now := time.Now()
ri := RunningInput{
Config: &InputConfig{
Name: "TestRunningInput",
},
}
ri.SetDebug(true)
assert.Equal(t, true, ri.Debug())
ri.SetTrace(true)
assert.Equal(t, true, ri.Trace())
m := ri.MakeMetric(
"RITest",
map[string]interface{}{
"value": int(101),
"inf": inf,
"ninf": ninf,
},
map[string]string{},
telegraf.Untyped,
now,
)
assert.Equal(
t,
m.String(),
fmt.Sprintf("RITest value=101i %d", now.UnixNano()),
)
}
func TestMakeMetricAllFieldTypes(t *testing.T) {
now := time.Now()
ri := RunningInput{
Config: &InputConfig{
Name: "TestRunningInput",
},
}
ri.SetDebug(true)
assert.Equal(t, true, ri.Debug())
ri.SetTrace(true)
assert.Equal(t, true, ri.Trace())
m := ri.MakeMetric(
"RITest",
map[string]interface{}{
"a": int(10),
"b": int8(10),
"c": int16(10),
"d": int32(10),
"e": uint(10),
"f": uint8(10),
"g": uint16(10),
"h": uint32(10),
"i": uint64(10),
"j": float32(10),
"k": uint64(9223372036854775810),
"l": "foobar",
"m": true,
},
map[string]string{},
telegraf.Untyped,
now,
)
assert.Equal(
t,
fmt.Sprintf("RITest a=10i,b=10i,c=10i,d=10i,e=10i,f=10i,g=10i,h=10i,i=10i,j=10,k=9223372036854775807i,l=\"foobar\",m=true %d", now.UnixNano()),
m.String(),
)
}
func TestMakeMetricNameOverride(t *testing.T) {
now := time.Now()
ri := RunningInput{
Config: &InputConfig{
Name: "TestRunningInput",
NameOverride: "foobar",
},
}
m := ri.MakeMetric(
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{},
telegraf.Untyped,
now,
)
assert.Equal(
t,
m.String(),
fmt.Sprintf("foobar value=101i %d", now.UnixNano()),
)
}
func TestMakeMetricNamePrefix(t *testing.T) {
now := time.Now()
ri := RunningInput{
Config: &InputConfig{
Name: "TestRunningInput",
MeasurementPrefix: "foobar_",
},
}
m := ri.MakeMetric(
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{},
telegraf.Untyped,
now,
)
assert.Equal(
t,
m.String(),
fmt.Sprintf("foobar_RITest value=101i %d", now.UnixNano()),
)
}
func TestMakeMetricNameSuffix(t *testing.T) {
now := time.Now()
ri := RunningInput{
Config: &InputConfig{
Name: "TestRunningInput",
MeasurementSuffix: "_foobar",
},
}
m := ri.MakeMetric(
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{},
telegraf.Untyped,
now,
)
assert.Equal(
t,
m.String(),
fmt.Sprintf("RITest_foobar value=101i %d", now.UnixNano()),
)
}

View File

@ -132,7 +132,6 @@ func TestRunningOutput_PassFilter(t *testing.T) {
func TestRunningOutput_TagIncludeNoMatch(t *testing.T) { func TestRunningOutput_TagIncludeNoMatch(t *testing.T) {
conf := &OutputConfig{ conf := &OutputConfig{
Filter: Filter{ Filter: Filter{
TagInclude: []string{"nothing*"}, TagInclude: []string{"nothing*"},
}, },
} }
@ -154,7 +153,6 @@ func TestRunningOutput_TagIncludeNoMatch(t *testing.T) {
func TestRunningOutput_TagExcludeMatch(t *testing.T) { func TestRunningOutput_TagExcludeMatch(t *testing.T) {
conf := &OutputConfig{ conf := &OutputConfig{
Filter: Filter{ Filter: Filter{
TagExclude: []string{"tag*"}, TagExclude: []string{"tag*"},
}, },
} }
@ -176,7 +174,6 @@ func TestRunningOutput_TagExcludeMatch(t *testing.T) {
func TestRunningOutput_TagExcludeNoMatch(t *testing.T) { func TestRunningOutput_TagExcludeNoMatch(t *testing.T) {
conf := &OutputConfig{ conf := &OutputConfig{
Filter: Filter{ Filter: Filter{
TagExclude: []string{"nothing*"}, TagExclude: []string{"nothing*"},
}, },
} }
@ -198,7 +195,6 @@ func TestRunningOutput_TagExcludeNoMatch(t *testing.T) {
func TestRunningOutput_TagIncludeMatch(t *testing.T) { func TestRunningOutput_TagIncludeMatch(t *testing.T) {
conf := &OutputConfig{ conf := &OutputConfig{
Filter: Filter{ Filter: Filter{
TagInclude: []string{"tag*"}, TagInclude: []string{"tag*"},
}, },
} }

View File

@ -0,0 +1,44 @@
package models
import (
"github.com/influxdata/telegraf"
)
type RunningProcessor struct {
Name string
Processor telegraf.Processor
Config *ProcessorConfig
}
type RunningProcessors []*RunningProcessor
func (rp RunningProcessors) Len() int { return len(rp) }
func (rp RunningProcessors) Swap(i, j int) { rp[i], rp[j] = rp[j], rp[i] }
func (rp RunningProcessors) Less(i, j int) bool { return rp[i].Config.Order < rp[j].Config.Order }
// FilterConfig containing a name and filter
type ProcessorConfig struct {
Name string
Order int64
Filter Filter
}
func (rp *RunningProcessor) Apply(in ...telegraf.Metric) []telegraf.Metric {
ret := []telegraf.Metric{}
for _, metric := range in {
if rp.Config.Filter.IsActive() {
// check if the filter should be applied to this metric
if ok := rp.Config.Filter.Apply(metric.Name(), metric.Fields(), metric.Tags()); !ok {
// this means filter should not be applied
ret = append(ret, metric)
continue
}
}
// This metric should pass through the filter, so call the filter Apply
// function and append results to the output slice.
ret = append(ret, rp.Processor.Apply(metric)...)
}
return ret
}

View File

@ -0,0 +1,117 @@
package models
import (
"testing"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/assert"
)
type TestProcessor struct {
}
func (f *TestProcessor) SampleConfig() string { return "" }
func (f *TestProcessor) Description() string { return "" }
// Apply renames:
// "foo" to "fuz"
// "bar" to "baz"
// And it also drops measurements named "dropme"
func (f *TestProcessor) Apply(in ...telegraf.Metric) []telegraf.Metric {
out := make([]telegraf.Metric, 0)
for _, m := range in {
switch m.Name() {
case "foo":
out = append(out, testutil.TestMetric(1, "fuz"))
case "bar":
out = append(out, testutil.TestMetric(1, "baz"))
case "dropme":
// drop the metric!
default:
out = append(out, m)
}
}
return out
}
func NewTestRunningProcessor() *RunningProcessor {
out := &RunningProcessor{
Name: "test",
Processor: &TestProcessor{},
Config: &ProcessorConfig{Filter: Filter{}},
}
return out
}
func TestRunningProcessor(t *testing.T) {
inmetrics := []telegraf.Metric{
testutil.TestMetric(1, "foo"),
testutil.TestMetric(1, "bar"),
testutil.TestMetric(1, "baz"),
}
expectedNames := []string{
"fuz",
"baz",
"baz",
}
rfp := NewTestRunningProcessor()
filteredMetrics := rfp.Apply(inmetrics...)
actualNames := []string{
filteredMetrics[0].Name(),
filteredMetrics[1].Name(),
filteredMetrics[2].Name(),
}
assert.Equal(t, expectedNames, actualNames)
}
func TestRunningProcessor_WithNameDrop(t *testing.T) {
inmetrics := []telegraf.Metric{
testutil.TestMetric(1, "foo"),
testutil.TestMetric(1, "bar"),
testutil.TestMetric(1, "baz"),
}
expectedNames := []string{
"foo",
"baz",
"baz",
}
rfp := NewTestRunningProcessor()
rfp.Config.Filter.NameDrop = []string{"foo"}
assert.NoError(t, rfp.Config.Filter.Compile())
filteredMetrics := rfp.Apply(inmetrics...)
actualNames := []string{
filteredMetrics[0].Name(),
filteredMetrics[1].Name(),
filteredMetrics[2].Name(),
}
assert.Equal(t, expectedNames, actualNames)
}
func TestRunningProcessor_DroppedMetric(t *testing.T) {
inmetrics := []telegraf.Metric{
testutil.TestMetric(1, "dropme"),
testutil.TestMetric(1, "foo"),
testutil.TestMetric(1, "bar"),
}
expectedNames := []string{
"fuz",
"baz",
}
rfp := NewTestRunningProcessor()
filteredMetrics := rfp.Apply(inmetrics...)
actualNames := []string{
filteredMetrics[0].Name(),
filteredMetrics[1].Name(),
}
assert.Equal(t, expectedNames, actualNames)
}

View File

@ -27,8 +27,8 @@ func (t *telegrafLog) Write(p []byte) (n int, err error) {
// debug will set the log level to DEBUG // debug will set the log level to DEBUG
// quiet will set the log level to ERROR // quiet will set the log level to ERROR
// logfile will direct the logging output to a file. Empty string is // logfile will direct the logging output to a file. Empty string is
// interpreted as stdout. If there is an error opening the file the // interpreted as stderr. If there is an error opening the file the
// logger will fallback to stdout. // logger will fallback to stderr.
func SetupLogging(debug, quiet bool, logfile string) { func SetupLogging(debug, quiet bool, logfile string) {
if debug { if debug {
wlog.SetLevel(wlog.DEBUG) wlog.SetLevel(wlog.DEBUG)
@ -41,17 +41,17 @@ func SetupLogging(debug, quiet bool, logfile string) {
if logfile != "" { if logfile != "" {
if _, err := os.Stat(logfile); os.IsNotExist(err) { if _, err := os.Stat(logfile); os.IsNotExist(err) {
if oFile, err = os.Create(logfile); err != nil { if oFile, err = os.Create(logfile); err != nil {
log.Printf("E! Unable to create %s (%s), using stdout", logfile, err) log.Printf("E! Unable to create %s (%s), using stderr", logfile, err)
oFile = os.Stdout oFile = os.Stderr
} }
} else { } else {
if oFile, err = os.OpenFile(logfile, os.O_APPEND|os.O_WRONLY, os.ModeAppend); err != nil { if oFile, err = os.OpenFile(logfile, os.O_APPEND|os.O_WRONLY, os.ModeAppend); err != nil {
log.Printf("E! Unable to append to %s (%s), using stdout", logfile, err) log.Printf("E! Unable to append to %s (%s), using stderr", logfile, err)
oFile = os.Stdout oFile = os.Stderr
} }
} }
} else { } else {
oFile = os.Stdout oFile = os.Stderr
} }
log.SetOutput(newTelegrafWriter(oFile)) log.SetOutput(newTelegrafWriter(oFile))

View File

@ -4,6 +4,7 @@ import (
"time" "time"
"github.com/influxdata/influxdb/client/v2" "github.com/influxdata/influxdb/client/v2"
"github.com/influxdata/influxdb/models"
) )
// ValueType is an enumeration of metric types that represent a simple value. // ValueType is an enumeration of metric types that represent a simple value.
@ -33,6 +34,10 @@ type Metric interface {
// UnixNano returns the unix nano time of the metric // UnixNano returns the unix nano time of the metric
UnixNano() int64 UnixNano() int64
// HashID returns a non-cryptographic hash of the metric (name + tags)
// NOTE: do not persist & depend on this value to disk.
HashID() uint64
// Fields returns the fields for the metric // Fields returns the fields for the metric
Fields() map[string]interface{} Fields() map[string]interface{}
@ -44,13 +49,28 @@ type Metric interface {
// Point returns a influxdb client.Point object // Point returns a influxdb client.Point object
Point() *client.Point Point() *client.Point
// SetAggregate sets the metric's aggregate status
// This is so that aggregate metrics don't get re-sent to aggregator plugins
SetAggregate(bool)
// IsAggregate returns true if the metric is an aggregate
IsAggregate() bool
} }
// metric is a wrapper of the influxdb client.Point struct // metric is a wrapper of the influxdb client.Point struct
type metric struct { type metric struct {
pt *client.Point pt models.Point
mType ValueType mType ValueType
isaggregate bool
}
func NewMetricFromPoint(pt models.Point) Metric {
return &metric{
pt: pt,
mType: Untyped,
}
} }
// NewMetric returns an untyped metric. // NewMetric returns an untyped metric.
@ -60,7 +80,7 @@ func NewMetric(
fields map[string]interface{}, fields map[string]interface{},
t time.Time, t time.Time,
) (Metric, error) { ) (Metric, error) {
pt, err := client.NewPoint(name, tags, fields, t) pt, err := models.NewPoint(name, models.NewTags(tags), fields, t)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -79,7 +99,7 @@ func NewGaugeMetric(
fields map[string]interface{}, fields map[string]interface{},
t time.Time, t time.Time,
) (Metric, error) { ) (Metric, error) {
pt, err := client.NewPoint(name, tags, fields, t) pt, err := models.NewPoint(name, models.NewTags(tags), fields, t)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -98,7 +118,7 @@ func NewCounterMetric(
fields map[string]interface{}, fields map[string]interface{},
t time.Time, t time.Time,
) (Metric, error) { ) (Metric, error) {
pt, err := client.NewPoint(name, tags, fields, t) pt, err := models.NewPoint(name, models.NewTags(tags), fields, t)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -113,7 +133,7 @@ func (m *metric) Name() string {
} }
func (m *metric) Tags() map[string]string { func (m *metric) Tags() map[string]string {
return m.pt.Tags() return m.pt.Tags().Map()
} }
func (m *metric) Time() time.Time { func (m *metric) Time() time.Time {
@ -124,6 +144,10 @@ func (m *metric) Type() ValueType {
return m.mType return m.mType
} }
func (m *metric) HashID() uint64 {
return m.pt.HashID()
}
func (m *metric) UnixNano() int64 { func (m *metric) UnixNano() int64 {
return m.pt.UnixNano() return m.pt.UnixNano()
} }
@ -141,5 +165,13 @@ func (m *metric) PrecisionString(precison string) string {
} }
func (m *metric) Point() *client.Point { func (m *metric) Point() *client.Point {
return m.pt return client.NewPointFrom(m.pt)
}
func (m *metric) IsAggregate() bool {
return m.isaggregate
}
func (m *metric) SetAggregate(b bool) {
m.isaggregate = b
} }

View File

@ -0,0 +1,5 @@
package all
import (
_ "github.com/influxdata/telegraf/plugins/aggregators/minmax"
)

View File

@ -0,0 +1,119 @@
package minmax
import (
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/aggregators"
)
type MinMax struct {
cache map[uint64]aggregate
}
func NewMinMax() telegraf.Aggregator {
mm := &MinMax{}
mm.Reset()
return mm
}
type aggregate struct {
fields map[string]minmax
name string
tags map[string]string
}
type minmax struct {
min float64
max float64
}
var sampleConfig = `
## General Aggregator Arguments:
## The period on which to flush & clear the aggregator.
period = "30s"
## If true, the original metric will be dropped by the
## aggregator and will not get sent to the output plugins.
drop_original = false
`
func (m *MinMax) SampleConfig() string {
return sampleConfig
}
func (m *MinMax) Description() string {
return "Keep the aggregate min/max of each metric passing through."
}
func (m *MinMax) Add(in telegraf.Metric) {
id := in.HashID()
if _, ok := m.cache[id]; !ok {
// hit an uncached metric, create caches for first time:
a := aggregate{
name: in.Name(),
tags: in.Tags(),
fields: make(map[string]minmax),
}
for k, v := range in.Fields() {
if fv, ok := convert(v); ok {
a.fields[k] = minmax{
min: fv,
max: fv,
}
}
}
m.cache[id] = a
} else {
for k, v := range in.Fields() {
if fv, ok := convert(v); ok {
if _, ok := m.cache[id].fields[k]; !ok {
// hit an uncached field of a cached metric
m.cache[id].fields[k] = minmax{
min: fv,
max: fv,
}
continue
}
if fv < m.cache[id].fields[k].min {
tmp := m.cache[id].fields[k]
tmp.min = fv
m.cache[id].fields[k] = tmp
} else if fv > m.cache[id].fields[k].max {
tmp := m.cache[id].fields[k]
tmp.max = fv
m.cache[id].fields[k] = tmp
}
}
}
}
}
func (m *MinMax) Push(acc telegraf.Accumulator) {
for _, aggregate := range m.cache {
fields := map[string]interface{}{}
for k, v := range aggregate.fields {
fields[k+"_min"] = v.min
fields[k+"_max"] = v.max
}
acc.AddFields(aggregate.name, fields, aggregate.tags)
}
}
func (m *MinMax) Reset() {
m.cache = make(map[uint64]aggregate)
}
func convert(in interface{}) (float64, bool) {
switch v := in.(type) {
case float64:
return v, true
case int64:
return float64(v), true
default:
return 0, false
}
}
func init() {
aggregators.Add("minmax", func() telegraf.Aggregator {
return NewMinMax()
})
}

View File

@ -0,0 +1,162 @@
package minmax
import (
"testing"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/testutil"
)
var m1, _ = telegraf.NewMetric("m1",
map[string]string{"foo": "bar"},
map[string]interface{}{
"a": int64(1),
"b": int64(1),
"c": int64(1),
"d": int64(1),
"e": int64(1),
"f": float64(2),
"g": float64(2),
"h": float64(2),
"i": float64(2),
"j": float64(3),
},
time.Now(),
)
var m2, _ = telegraf.NewMetric("m1",
map[string]string{"foo": "bar"},
map[string]interface{}{
"a": int64(1),
"b": int64(3),
"c": int64(3),
"d": int64(3),
"e": int64(3),
"f": float64(1),
"g": float64(1),
"h": float64(1),
"i": float64(1),
"j": float64(1),
"k": float64(200),
"ignoreme": "string",
"andme": true,
},
time.Now(),
)
func BenchmarkApply(b *testing.B) {
minmax := NewMinMax()
for n := 0; n < b.N; n++ {
minmax.Add(m1)
minmax.Add(m2)
}
}
// Test two metrics getting added.
func TestMinMaxWithPeriod(t *testing.T) {
acc := testutil.Accumulator{}
minmax := NewMinMax()
minmax.Add(m1)
minmax.Add(m2)
minmax.Push(&acc)
expectedFields := map[string]interface{}{
"a_max": float64(1),
"a_min": float64(1),
"b_max": float64(3),
"b_min": float64(1),
"c_max": float64(3),
"c_min": float64(1),
"d_max": float64(3),
"d_min": float64(1),
"e_max": float64(3),
"e_min": float64(1),
"f_max": float64(2),
"f_min": float64(1),
"g_max": float64(2),
"g_min": float64(1),
"h_max": float64(2),
"h_min": float64(1),
"i_max": float64(2),
"i_min": float64(1),
"j_max": float64(3),
"j_min": float64(1),
"k_max": float64(200),
"k_min": float64(200),
}
expectedTags := map[string]string{
"foo": "bar",
}
acc.AssertContainsTaggedFields(t, "m1", expectedFields, expectedTags)
}
// Test two metrics getting added with a push/reset in between (simulates
// getting added in different periods.)
func TestMinMaxDifferentPeriods(t *testing.T) {
acc := testutil.Accumulator{}
minmax := NewMinMax()
minmax.Add(m1)
minmax.Push(&acc)
expectedFields := map[string]interface{}{
"a_max": float64(1),
"a_min": float64(1),
"b_max": float64(1),
"b_min": float64(1),
"c_max": float64(1),
"c_min": float64(1),
"d_max": float64(1),
"d_min": float64(1),
"e_max": float64(1),
"e_min": float64(1),
"f_max": float64(2),
"f_min": float64(2),
"g_max": float64(2),
"g_min": float64(2),
"h_max": float64(2),
"h_min": float64(2),
"i_max": float64(2),
"i_min": float64(2),
"j_max": float64(3),
"j_min": float64(3),
}
expectedTags := map[string]string{
"foo": "bar",
}
acc.AssertContainsTaggedFields(t, "m1", expectedFields, expectedTags)
acc.ClearMetrics()
minmax.Reset()
minmax.Add(m2)
minmax.Push(&acc)
expectedFields = map[string]interface{}{
"a_max": float64(1),
"a_min": float64(1),
"b_max": float64(3),
"b_min": float64(3),
"c_max": float64(3),
"c_min": float64(3),
"d_max": float64(3),
"d_min": float64(3),
"e_max": float64(3),
"e_min": float64(3),
"f_max": float64(1),
"f_min": float64(1),
"g_max": float64(1),
"g_min": float64(1),
"h_max": float64(1),
"h_min": float64(1),
"i_max": float64(1),
"i_min": float64(1),
"j_max": float64(1),
"j_min": float64(1),
"k_max": float64(200),
"k_min": float64(200),
}
expectedTags = map[string]string{
"foo": "bar",
}
acc.AssertContainsTaggedFields(t, "m1", expectedFields, expectedTags)
}

View File

@ -0,0 +1,11 @@
package aggregators
import "github.com/influxdata/telegraf"
type Creator func() telegraf.Aggregator
var Aggregators = map[string]Creator{}
func Add(name string, creator Creator) {
Aggregators[name] = creator
}

View File

@ -2,6 +2,10 @@
This input plugin will capture specific statistics per cgroup. This input plugin will capture specific statistics per cgroup.
Consider restricting paths to the set of cgroups you really
want to monitor if you have a large number of cgroups, to avoid
any cardinality issues.
Following file formats are supported: Following file formats are supported:
* Single value * Single value
@ -33,9 +37,8 @@ KEY1 VAL1\n
### Tags: ### Tags:
Measurements don't have any specific tags unless you define them at the telegraf level (defaults). We All measurements have the following tags:
used to have the path listed as a tag, but to keep cardinality in check it's easier to move this - path
value to a field. Thanks @sebito91!
### Configuration: ### Configuration:

View File

@ -11,15 +11,18 @@ type CGroup struct {
} }
var sampleConfig = ` var sampleConfig = `
## Directories in which to look for files, globs are supported. ## Directories in which to look for files, globs are supported.
# paths = [ ## Consider restricting paths to the set of cgroups you really
# "/cgroup/memory", ## want to monitor if you have a large number of cgroups, to avoid
# "/cgroup/memory/child1", ## any cardinality issues.
# "/cgroup/memory/child2/*", # paths = [
# ] # "/cgroup/memory",
## cgroup stat fields, as file names, globs are supported. # "/cgroup/memory/child1",
## these file names are appended to each path from above. # "/cgroup/memory/child2/*",
# files = ["memory.*usage*", "memory.limit_in_bytes"] # ]
## cgroup stat fields, as file names, globs are supported.
## these file names are appended to each path from above.
# files = ["memory.*usage*", "memory.limit_in_bytes"]
` `
func (g *CGroup) SampleConfig() string { func (g *CGroup) SampleConfig() string {

View File

@ -56,9 +56,10 @@ func (g *CGroup) gatherDir(dir string, acc telegraf.Accumulator) error {
return err return err
} }
} }
fields["path"] = dir
acc.AddFields(metricName, fields, nil) tags := map[string]string{"path": dir}
acc.AddFields(metricName, fields, tags)
return nil return nil
} }

View File

@ -3,13 +3,10 @@
package cgroup package cgroup
import ( import (
"fmt"
"testing" "testing"
"github.com/influxdata/telegraf/testutil" "github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"reflect"
) )
var cg1 = &CGroup{ var cg1 = &CGroup{
@ -24,32 +21,15 @@ var cg1 = &CGroup{
}, },
} }
func assertContainsFields(a *testutil.Accumulator, t *testing.T, measurement string, fieldSet []map[string]interface{}) {
a.Lock()
defer a.Unlock()
numEquals := 0
for _, p := range a.Metrics {
if p.Measurement == measurement {
for _, fields := range fieldSet {
if reflect.DeepEqual(fields, p.Fields) {
numEquals++
}
}
}
}
if numEquals != len(fieldSet) {
assert.Fail(t, fmt.Sprintf("only %d of %d are equal", numEquals, len(fieldSet)))
}
}
func TestCgroupStatistics_1(t *testing.T) { func TestCgroupStatistics_1(t *testing.T) {
var acc testutil.Accumulator var acc testutil.Accumulator
err := cg1.Gather(&acc) err := cg1.Gather(&acc)
require.NoError(t, err) require.NoError(t, err)
tags := map[string]string{
"path": "testdata/memory",
}
fields := map[string]interface{}{ fields := map[string]interface{}{
"memory.stat.cache": 1739362304123123123, "memory.stat.cache": 1739362304123123123,
"memory.stat.rss": 1775325184, "memory.stat.rss": 1775325184,
@ -62,9 +42,8 @@ func TestCgroupStatistics_1(t *testing.T) {
"memory.limit_in_bytes": 223372036854771712, "memory.limit_in_bytes": 223372036854771712,
"memory.use_hierarchy": "12-781", "memory.use_hierarchy": "12-781",
"notify_on_release": 0, "notify_on_release": 0,
"path": "testdata/memory",
} }
assertContainsFields(&acc, t, "cgroup", []map[string]interface{}{fields}) acc.AssertContainsTaggedFields(t, "cgroup", fields, tags)
} }
// ====================================================================== // ======================================================================
@ -80,14 +59,16 @@ func TestCgroupStatistics_2(t *testing.T) {
err := cg2.Gather(&acc) err := cg2.Gather(&acc)
require.NoError(t, err) require.NoError(t, err)
tags := map[string]string{
"path": "testdata/cpu",
}
fields := map[string]interface{}{ fields := map[string]interface{}{
"cpuacct.usage_percpu.0": -1452543795404, "cpuacct.usage_percpu.0": -1452543795404,
"cpuacct.usage_percpu.1": 1376681271659, "cpuacct.usage_percpu.1": 1376681271659,
"cpuacct.usage_percpu.2": 1450950799997, "cpuacct.usage_percpu.2": 1450950799997,
"cpuacct.usage_percpu.3": -1473113374257, "cpuacct.usage_percpu.3": -1473113374257,
"path": "testdata/cpu",
} }
assertContainsFields(&acc, t, "cgroup", []map[string]interface{}{fields}) acc.AssertContainsTaggedFields(t, "cgroup", fields, tags)
} }
// ====================================================================== // ======================================================================
@ -103,16 +84,18 @@ func TestCgroupStatistics_3(t *testing.T) {
err := cg3.Gather(&acc) err := cg3.Gather(&acc)
require.NoError(t, err) require.NoError(t, err)
tags := map[string]string{
"path": "testdata/memory/group_1",
}
fields := map[string]interface{}{ fields := map[string]interface{}{
"memory.limit_in_bytes": 223372036854771712, "memory.limit_in_bytes": 223372036854771712,
"path": "testdata/memory/group_1",
} }
acc.AssertContainsTaggedFields(t, "cgroup", fields, tags)
fieldsTwo := map[string]interface{}{ tags = map[string]string{
"memory.limit_in_bytes": 223372036854771712, "path": "testdata/memory/group_2",
"path": "testdata/memory/group_2",
} }
assertContainsFields(&acc, t, "cgroup", []map[string]interface{}{fields, fieldsTwo}) acc.AssertContainsTaggedFields(t, "cgroup", fields, tags)
} }
// ====================================================================== // ======================================================================
@ -128,22 +111,23 @@ func TestCgroupStatistics_4(t *testing.T) {
err := cg4.Gather(&acc) err := cg4.Gather(&acc)
require.NoError(t, err) require.NoError(t, err)
tags := map[string]string{
"path": "testdata/memory/group_1/group_1_1",
}
fields := map[string]interface{}{ fields := map[string]interface{}{
"memory.limit_in_bytes": 223372036854771712, "memory.limit_in_bytes": 223372036854771712,
"path": "testdata/memory/group_1/group_1_1",
} }
acc.AssertContainsTaggedFields(t, "cgroup", fields, tags)
fieldsTwo := map[string]interface{}{ tags = map[string]string{
"memory.limit_in_bytes": 223372036854771712, "path": "testdata/memory/group_1/group_1_2",
"path": "testdata/memory/group_1/group_1_2",
} }
acc.AssertContainsTaggedFields(t, "cgroup", fields, tags)
fieldsThree := map[string]interface{}{ tags = map[string]string{
"memory.limit_in_bytes": 223372036854771712, "path": "testdata/memory/group_2",
"path": "testdata/memory/group_2",
} }
acc.AssertContainsTaggedFields(t, "cgroup", fields, tags)
assertContainsFields(&acc, t, "cgroup", []map[string]interface{}{fields, fieldsTwo, fieldsThree})
} }
// ====================================================================== // ======================================================================
@ -159,16 +143,18 @@ func TestCgroupStatistics_5(t *testing.T) {
err := cg5.Gather(&acc) err := cg5.Gather(&acc)
require.NoError(t, err) require.NoError(t, err)
tags := map[string]string{
"path": "testdata/memory/group_1/group_1_1",
}
fields := map[string]interface{}{ fields := map[string]interface{}{
"memory.limit_in_bytes": 223372036854771712, "memory.limit_in_bytes": 223372036854771712,
"path": "testdata/memory/group_1/group_1_1",
} }
acc.AssertContainsTaggedFields(t, "cgroup", fields, tags)
fieldsTwo := map[string]interface{}{ tags = map[string]string{
"memory.limit_in_bytes": 223372036854771712, "path": "testdata/memory/group_2/group_1_1",
"path": "testdata/memory/group_2/group_1_1",
} }
assertContainsFields(&acc, t, "cgroup", []map[string]interface{}{fields, fieldsTwo}) acc.AssertContainsTaggedFields(t, "cgroup", fields, tags)
} }
// ====================================================================== // ======================================================================
@ -184,11 +170,13 @@ func TestCgroupStatistics_6(t *testing.T) {
err := cg6.Gather(&acc) err := cg6.Gather(&acc)
require.NoError(t, err) require.NoError(t, err)
tags := map[string]string{
"path": "testdata/memory",
}
fields := map[string]interface{}{ fields := map[string]interface{}{
"memory.usage_in_bytes": 3513667584, "memory.usage_in_bytes": 3513667584,
"memory.use_hierarchy": "12-781", "memory.use_hierarchy": "12-781",
"memory.kmem.limit_in_bytes": 9223372036854771712, "memory.kmem.limit_in_bytes": 9223372036854771712,
"path": "testdata/memory",
} }
assertContainsFields(&acc, t, "cgroup", []map[string]interface{}{fields}) acc.AssertContainsTaggedFields(t, "cgroup", fields, tags)
} }

View File

@ -99,14 +99,14 @@ func TestWriteHTTPHighTraffic(t *testing.T) {
var wg sync.WaitGroup var wg sync.WaitGroup
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
wg.Add(1) wg.Add(1)
go func() { go func(innerwg *sync.WaitGroup) {
defer innerwg.Done()
for i := 0; i < 500; i++ { for i := 0; i < 500; i++ {
resp, err := http.Post("http://localhost:8286/write?db=mydb", "", bytes.NewBuffer([]byte(testMsgs))) resp, err := http.Post("http://localhost:8286/write?db=mydb", "", bytes.NewBuffer([]byte(testMsgs)))
require.NoError(t, err) require.NoError(t, err)
require.EqualValues(t, 204, resp.StatusCode) require.EqualValues(t, 204, resp.StatusCode)
} }
wg.Done() }(&wg)
}()
} }
wg.Wait() wg.Wait()

View File

@ -4,16 +4,16 @@ import (
"bytes" "bytes"
"database/sql" "database/sql"
"fmt" "fmt"
"net/url"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
_ "github.com/go-sql-driver/mysql"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal/errchan" "github.com/influxdata/telegraf/internal/errchan"
"github.com/influxdata/telegraf/plugins/inputs" "github.com/influxdata/telegraf/plugins/inputs"
"github.com/go-sql-driver/mysql"
) )
type Mysql struct { type Mysql struct {
@ -398,27 +398,6 @@ var (
} }
) )
func dsnAddTimeout(dsn string) (string, error) {
// DSN "?timeout=5s" is not valid, but "/?timeout=5s" is valid ("" and "/"
// are the same DSN)
if dsn == "" {
dsn = "/"
}
u, err := url.Parse(dsn)
if err != nil {
return "", err
}
v := u.Query()
// Only override timeout if not already defined
if _, ok := v["timeout"]; ok == false {
v.Add("timeout", defaultTimeout.String())
u.RawQuery = v.Encode()
}
return u.String(), nil
}
// Math constants // Math constants
const ( const (
picoSeconds = 1e12 picoSeconds = 1e12
@ -682,10 +661,7 @@ func (m *Mysql) gatherGlobalVariables(db *sql.DB, serv string, acc telegraf.Accu
var val sql.RawBytes var val sql.RawBytes
// parse DSN and save server tag // parse DSN and save server tag
servtag, err := parseDSN(serv) servtag := getDSNTag(serv)
if err != nil {
servtag = "localhost"
}
tags := map[string]string{"server": servtag} tags := map[string]string{"server": servtag}
fields := make(map[string]interface{}) fields := make(map[string]interface{})
for rows.Next() { for rows.Next() {
@ -722,10 +698,7 @@ func (m *Mysql) gatherSlaveStatuses(db *sql.DB, serv string, acc telegraf.Accumu
} }
defer rows.Close() defer rows.Close()
servtag, err := parseDSN(serv) servtag := getDSNTag(serv)
if err != nil {
servtag = "localhost"
}
tags := map[string]string{"server": servtag} tags := map[string]string{"server": servtag}
fields := make(map[string]interface{}) fields := make(map[string]interface{})
@ -770,11 +743,7 @@ func (m *Mysql) gatherBinaryLogs(db *sql.DB, serv string, acc telegraf.Accumulat
defer rows.Close() defer rows.Close()
// parse DSN and save host as a tag // parse DSN and save host as a tag
var servtag string servtag := getDSNTag(serv)
servtag, err = parseDSN(serv)
if err != nil {
servtag = "localhost"
}
tags := map[string]string{"server": servtag} tags := map[string]string{"server": servtag}
var ( var (
size uint64 = 0 size uint64 = 0
@ -817,11 +786,7 @@ func (m *Mysql) gatherGlobalStatuses(db *sql.DB, serv string, acc telegraf.Accum
} }
// parse the DSN and save host name as a tag // parse the DSN and save host name as a tag
var servtag string servtag := getDSNTag(serv)
servtag, err = parseDSN(serv)
if err != nil {
servtag = "localhost"
}
tags := map[string]string{"server": servtag} tags := map[string]string{"server": servtag}
fields := make(map[string]interface{}) fields := make(map[string]interface{})
for rows.Next() { for rows.Next() {
@ -932,10 +897,7 @@ func (m *Mysql) GatherProcessListStatuses(db *sql.DB, serv string, acc telegraf.
var servtag string var servtag string
fields := make(map[string]interface{}) fields := make(map[string]interface{})
servtag, err = parseDSN(serv) servtag = getDSNTag(serv)
if err != nil {
servtag = "localhost"
}
// mapping of state with its counts // mapping of state with its counts
stateCounts := make(map[string]uint32, len(generalThreadStates)) stateCounts := make(map[string]uint32, len(generalThreadStates))
@ -978,10 +940,7 @@ func (m *Mysql) gatherPerfTableIOWaits(db *sql.DB, serv string, acc telegraf.Acc
timeFetch, timeInsert, timeUpdate, timeDelete float64 timeFetch, timeInsert, timeUpdate, timeDelete float64
) )
servtag, err = parseDSN(serv) servtag = getDSNTag(serv)
if err != nil {
servtag = "localhost"
}
for rows.Next() { for rows.Next() {
err = rows.Scan(&objSchema, &objName, err = rows.Scan(&objSchema, &objName,
@ -1030,10 +989,7 @@ func (m *Mysql) gatherPerfIndexIOWaits(db *sql.DB, serv string, acc telegraf.Acc
timeFetch, timeInsert, timeUpdate, timeDelete float64 timeFetch, timeInsert, timeUpdate, timeDelete float64
) )
servtag, err = parseDSN(serv) servtag = getDSNTag(serv)
if err != nil {
servtag = "localhost"
}
for rows.Next() { for rows.Next() {
err = rows.Scan(&objSchema, &objName, &indexName, err = rows.Scan(&objSchema, &objName, &indexName,
@ -1085,10 +1041,7 @@ func (m *Mysql) gatherInfoSchemaAutoIncStatuses(db *sql.DB, serv string, acc tel
incValue, maxInt uint64 incValue, maxInt uint64
) )
servtag, err := parseDSN(serv) servtag := getDSNTag(serv)
if err != nil {
servtag = "localhost"
}
for rows.Next() { for rows.Next() {
if err := rows.Scan(&schema, &table, &column, &incValue, &maxInt); err != nil { if err := rows.Scan(&schema, &table, &column, &incValue, &maxInt); err != nil {
@ -1132,10 +1085,7 @@ func (m *Mysql) gatherPerfTableLockWaits(db *sql.DB, serv string, acc telegraf.A
} }
defer rows.Close() defer rows.Close()
servtag, err := parseDSN(serv) servtag := getDSNTag(serv)
if err != nil {
servtag = "localhost"
}
var ( var (
objectSchema string objectSchema string
@ -1257,10 +1207,7 @@ func (m *Mysql) gatherPerfEventWaits(db *sql.DB, serv string, acc telegraf.Accum
starCount, timeWait float64 starCount, timeWait float64
) )
servtag, err := parseDSN(serv) servtag := getDSNTag(serv)
if err != nil {
servtag = "localhost"
}
tags := map[string]string{ tags := map[string]string{
"server": servtag, "server": servtag,
} }
@ -1295,10 +1242,7 @@ func (m *Mysql) gatherPerfFileEventsStatuses(db *sql.DB, serv string, acc telegr
sumNumBytesRead, sumNumBytesWrite float64 sumNumBytesRead, sumNumBytesWrite float64
) )
servtag, err := parseDSN(serv) servtag := getDSNTag(serv)
if err != nil {
servtag = "localhost"
}
tags := map[string]string{ tags := map[string]string{
"server": servtag, "server": servtag,
} }
@ -1365,10 +1309,7 @@ func (m *Mysql) gatherPerfEventsStatements(db *sql.DB, serv string, acc telegraf
noIndexUsed float64 noIndexUsed float64
) )
servtag, err := parseDSN(serv) servtag := getDSNTag(serv)
if err != nil {
servtag = "localhost"
}
tags := map[string]string{ tags := map[string]string{
"server": servtag, "server": servtag,
} }
@ -1412,14 +1353,8 @@ func (m *Mysql) gatherPerfEventsStatements(db *sql.DB, serv string, acc telegraf
// gatherTableSchema can be used to gather stats on each schema // gatherTableSchema can be used to gather stats on each schema
func (m *Mysql) gatherTableSchema(db *sql.DB, serv string, acc telegraf.Accumulator) error { func (m *Mysql) gatherTableSchema(db *sql.DB, serv string, acc telegraf.Accumulator) error {
var ( var dbList []string
dbList []string servtag := getDSNTag(serv)
servtag string
)
servtag, err := parseDSN(serv)
if err != nil {
servtag = "localhost"
}
// if the list of databases if empty, then get all databases // if the list of databases if empty, then get all databases
if len(m.TableSchemaDatabases) == 0 { if len(m.TableSchemaDatabases) == 0 {
@ -1575,6 +1510,27 @@ func copyTags(in map[string]string) map[string]string {
return out return out
} }
func dsnAddTimeout(dsn string) (string, error) {
conf, err := mysql.ParseDSN(dsn)
if err != nil {
return "", err
}
if conf.Timeout == 0 {
conf.Timeout = time.Second * 5
}
return conf.FormatDSN(), nil
}
func getDSNTag(dsn string) string {
conf, err := mysql.ParseDSN(dsn)
if err != nil {
return "127.0.0.1:3306"
}
return conf.Addr
}
func init() { func init() {
inputs.Add("mysql", func() telegraf.Input { inputs.Add("mysql", func() telegraf.Input {
return &Mysql{} return &Mysql{}

View File

@ -26,7 +26,7 @@ func TestMysqlDefaultsToLocal(t *testing.T) {
assert.True(t, acc.HasMeasurement("mysql")) assert.True(t, acc.HasMeasurement("mysql"))
} }
func TestMysqlParseDSN(t *testing.T) { func TestMysqlGetDSNTag(t *testing.T) {
tests := []struct { tests := []struct {
input string input string
output string output string
@ -78,9 +78,9 @@ func TestMysqlParseDSN(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
output, _ := parseDSN(test.input) output := getDSNTag(test.input)
if output != test.output { if output != test.output {
t.Errorf("Expected %s, got %s\n", test.output, output) t.Errorf("Input: %s Expected %s, got %s\n", test.input, test.output, output)
} }
} }
} }
@ -92,7 +92,7 @@ func TestMysqlDNSAddTimeout(t *testing.T) {
}{ }{
{ {
"", "",
"/?timeout=5s", "tcp(127.0.0.1:3306)/?timeout=5s",
}, },
{ {
"tcp(192.168.1.1:3306)/", "tcp(192.168.1.1:3306)/",
@ -104,7 +104,19 @@ func TestMysqlDNSAddTimeout(t *testing.T) {
}, },
{ {
"root:passwd@tcp(192.168.1.1:3306)/?tls=false&timeout=10s", "root:passwd@tcp(192.168.1.1:3306)/?tls=false&timeout=10s",
"root:passwd@tcp(192.168.1.1:3306)/?tls=false&timeout=10s", "root:passwd@tcp(192.168.1.1:3306)/?timeout=10s&tls=false",
},
{
"tcp(10.150.1.123:3306)/",
"tcp(10.150.1.123:3306)/?timeout=5s",
},
{
"root:@!~(*&$#%(&@#(@&#Password@tcp(10.150.1.123:3306)/",
"root:@!~(*&$#%(&@#(@&#Password@tcp(10.150.1.123:3306)/?timeout=5s",
},
{
"root:Test3a#@!@tcp(10.150.1.123:3306)/",
"root:Test3a#@!@tcp(10.150.1.123:3306)/?timeout=5s",
}, },
} }

View File

@ -1,85 +0,0 @@
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"errors"
"strings"
)
// parseDSN parses the DSN string to a config
func parseDSN(dsn string) (string, error) {
//var user, passwd string
var addr, net string
// [user[:password]@][net[(addr)]]/dbname[?param1=value1&paramN=valueN]
// Find the last '/' (since the password or the net addr might contain a '/')
for i := len(dsn) - 1; i >= 0; i-- {
if dsn[i] == '/' {
var j, k int
// left part is empty if i <= 0
if i > 0 {
// [username[:password]@][protocol[(address)]]
// Find the last '@' in dsn[:i]
for j = i; j >= 0; j-- {
if dsn[j] == '@' {
// username[:password]
// Find the first ':' in dsn[:j]
for k = 0; k < j; k++ {
if dsn[k] == ':' {
//passwd = dsn[k+1 : j]
break
}
}
//user = dsn[:k]
break
}
}
// [protocol[(address)]]
// Find the first '(' in dsn[j+1:i]
for k = j + 1; k < i; k++ {
if dsn[k] == '(' {
// dsn[i-1] must be == ')' if an address is specified
if dsn[i-1] != ')' {
if strings.ContainsRune(dsn[k+1:i], ')') {
return "", errors.New("Invalid DSN unescaped")
}
return "", errors.New("Invalid DSN Addr")
}
addr = dsn[k+1 : i-1]
break
}
}
net = dsn[j+1 : k]
}
break
}
}
// Set default network if empty
if net == "" {
net = "tcp"
}
// Set default address if empty
if addr == "" {
switch net {
case "tcp":
addr = "127.0.0.1:3306"
case "unix":
addr = "/tmp/mysql.sock"
default:
return "", errors.New("Default addr for network '" + net + "' unknown")
}
}
return addr, nil
}

View File

@ -122,6 +122,9 @@ func (g *phpfpm) gatherServer(addr string, acc telegraf.Accumulator) error {
fcgiIp := socketAddr[0] fcgiIp := socketAddr[0]
fcgiPort, _ := strconv.Atoi(socketAddr[1]) fcgiPort, _ := strconv.Atoi(socketAddr[1])
fcgi, err = newFcgiClient(fcgiIp, fcgiPort) fcgi, err = newFcgiClient(fcgiIp, fcgiPort)
if err != nil {
return err
}
if len(u.Path) > 1 { if len(u.Path) > 1 {
statusPath = strings.Trim(u.Path, "/") statusPath = strings.Trim(u.Path, "/")
} else { } else {

View File

@ -29,6 +29,7 @@ type Postgresql struct {
Tagvalue string Tagvalue string
Measurement string Measurement string
} }
Debug bool
} }
type query []struct { type query []struct {

View File

@ -178,13 +178,30 @@ type Table struct {
initialized bool initialized bool
} }
// init() populates Fields if a table OID is provided. // init() builds & initializes the nested fields.
func (t *Table) init() error { func (t *Table) init() error {
if t.initialized { if t.initialized {
return nil return nil
} }
if err := t.initBuild(); err != nil {
return err
}
// initialize all the nested fields
for i := range t.Fields {
if err := t.Fields[i].init(); err != nil {
return err
}
}
t.initialized = true
return nil
}
// init() populates Fields if a table OID is provided.
func (t *Table) initBuild() error {
if t.Oid == "" { if t.Oid == "" {
t.initialized = true
return nil return nil
} }
@ -242,14 +259,6 @@ func (t *Table) init() error {
t.Fields = append(t.Fields, Field{Name: col, Oid: mibPrefix + col, IsTag: isTag}) t.Fields = append(t.Fields, Field{Name: col, Oid: mibPrefix + col, IsTag: isTag})
} }
// initialize all the nested fields
for i := range t.Fields {
if err := t.Fields[i].init(); err != nil {
return err
}
}
t.initialized = true
return nil return nil
} }

View File

@ -21,6 +21,7 @@ var mockedCommands = [][]string{
{"snmptranslate", "-Td", "-Ob", "-m", "all", "1.0.0.1.1"}, {"snmptranslate", "-Td", "-Ob", "-m", "all", "1.0.0.1.1"},
{"snmptranslate", "-Td", "-Ob", "-m", "all", ".1.0.0.0.1.1"}, {"snmptranslate", "-Td", "-Ob", "-m", "all", ".1.0.0.0.1.1"},
{"snmptranslate", "-Td", "-Ob", "-m", "all", ".1.0.0.0.1.1.0"}, {"snmptranslate", "-Td", "-Ob", "-m", "all", ".1.0.0.0.1.1.0"},
{"snmptranslate", "-Td", "-Ob", "-m", "all", ".1.0.0.0.1.4"},
{"snmptranslate", "-Td", "-Ob", "-m", "all", ".1.2.3"}, {"snmptranslate", "-Td", "-Ob", "-m", "all", ".1.2.3"},
{"snmptranslate", "-Td", "-Ob", ".iso.2.3"}, {"snmptranslate", "-Td", "-Ob", ".iso.2.3"},
{"snmptranslate", "-Td", "-Ob", "-m", "all", ".999"}, {"snmptranslate", "-Td", "-Ob", "-m", "all", ".999"},

View File

@ -67,6 +67,7 @@ var mockedCommandResults = map[string]mockedCommandResult{
"snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x001.0.0.1.1": mockedCommandResult{stdout: "TEST::hostname\nhostname OBJECT-TYPE\n -- FROM\tTEST\n SYNTAX\tOCTET STRING\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) 1 1 }\n", stderr: "", exitError: false}, "snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x001.0.0.1.1": mockedCommandResult{stdout: "TEST::hostname\nhostname OBJECT-TYPE\n -- FROM\tTEST\n SYNTAX\tOCTET STRING\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) 1 1 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x00.1.0.0.0.1.1": mockedCommandResult{stdout: "TEST::server\nserver OBJECT-TYPE\n -- FROM\tTEST\n SYNTAX\tOCTET STRING\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) testTable(0) testTableEntry(1) 1 }\n", stderr: "", exitError: false}, "snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x00.1.0.0.0.1.1": mockedCommandResult{stdout: "TEST::server\nserver OBJECT-TYPE\n -- FROM\tTEST\n SYNTAX\tOCTET STRING\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) testTable(0) testTableEntry(1) 1 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x00.1.0.0.0.1.1.0": mockedCommandResult{stdout: "TEST::server.0\nserver OBJECT-TYPE\n -- FROM\tTEST\n SYNTAX\tOCTET STRING\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) testTable(0) testTableEntry(1) server(1) 0 }\n", stderr: "", exitError: false}, "snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x00.1.0.0.0.1.1.0": mockedCommandResult{stdout: "TEST::server.0\nserver OBJECT-TYPE\n -- FROM\tTEST\n SYNTAX\tOCTET STRING\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) testTable(0) testTableEntry(1) server(1) 0 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x00.1.0.0.0.1.4": mockedCommandResult{stdout: "TEST::testTableEntry.4\ntestTableEntry OBJECT-TYPE\n -- FROM\tTEST\n MAX-ACCESS\tnot-accessible\n STATUS\tcurrent\n INDEX\t\t{ server }\n::= { iso(1) 0 testOID(0) testTable(0) testTableEntry(1) 4 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x00.1.2.3": mockedCommandResult{stdout: "iso.2.3\niso OBJECT-TYPE\n -- FROM\t#-1\n::= { iso(1) 2 3 }\n", stderr: "", exitError: false}, "snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x00.1.2.3": mockedCommandResult{stdout: "iso.2.3\niso OBJECT-TYPE\n -- FROM\t#-1\n::= { iso(1) 2 3 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00.iso.2.3": mockedCommandResult{stdout: "iso.2.3\niso OBJECT-TYPE\n -- FROM\t#-1\n::= { iso(1) 2 3 }\n", stderr: "", exitError: false}, "snmptranslate\x00-Td\x00-Ob\x00.iso.2.3": mockedCommandResult{stdout: "iso.2.3\niso OBJECT-TYPE\n -- FROM\t#-1\n::= { iso(1) 2 3 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x00.999": mockedCommandResult{stdout: ".999\n [TRUNCATED]\n", stderr: "", exitError: false}, "snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x00.999": mockedCommandResult{stdout: ".999\n [TRUNCATED]\n", stderr: "", exitError: false},

View File

@ -57,38 +57,34 @@ func NewGraphiteParser(
defaultTemplate, _ := NewTemplate("measurement*", nil, p.Separator) defaultTemplate, _ := NewTemplate("measurement*", nil, p.Separator)
matcher.AddDefaultTemplate(defaultTemplate) matcher.AddDefaultTemplate(defaultTemplate)
tmplts := parsedTemplates{}
for _, pattern := range p.Templates { for _, pattern := range p.Templates {
template := pattern tmplt := parsedTemplate{}
filter := "" tmplt.template = pattern
// Format is [filter] <template> [tag1=value1,tag2=value2] // Format is [filter] <template> [tag1=value1,tag2=value2]
parts := strings.Fields(pattern) parts := strings.Fields(pattern)
if len(parts) < 1 { if len(parts) < 1 {
continue continue
} else if len(parts) >= 2 { } else if len(parts) >= 2 {
if strings.Contains(parts[1], "=") { if strings.Contains(parts[1], "=") {
template = parts[0] tmplt.template = parts[0]
tmplt.tagstring = parts[1]
} else { } else {
filter = parts[0] tmplt.filter = parts[0]
template = parts[1] tmplt.template = parts[1]
if len(parts) > 2 {
tmplt.tagstring = parts[2]
}
} }
} }
tmplts = append(tmplts, tmplt)
}
// Parse out the default tags specific to this template sort.Sort(tmplts)
tags := map[string]string{} for _, tmplt := range tmplts {
if strings.Contains(parts[len(parts)-1], "=") { if err := p.addToMatcher(tmplt); err != nil {
tagStrs := strings.Split(parts[len(parts)-1], ",") return nil, err
for _, kv := range tagStrs {
parts := strings.Split(kv, "=")
tags[parts[0]] = parts[1]
}
} }
tmpl, err1 := NewTemplate(template, tags, p.Separator)
if err1 != nil {
err = err1
break
}
matcher.Add(filter, tmpl)
} }
if err != nil { if err != nil {
@ -98,6 +94,24 @@ func NewGraphiteParser(
} }
} }
func (p *GraphiteParser) addToMatcher(tmplt parsedTemplate) error {
// Parse out the default tags specific to this template
tags := map[string]string{}
if tmplt.tagstring != "" {
for _, kv := range strings.Split(tmplt.tagstring, ",") {
parts := strings.Split(kv, "=")
tags[parts[0]] = parts[1]
}
}
tmpl, err := NewTemplate(tmplt.template, tags, p.Separator)
if err != nil {
return err
}
p.matcher.Add(tmplt.filter, tmpl)
return nil
}
func (p *GraphiteParser) Parse(buf []byte) ([]telegraf.Metric, error) { func (p *GraphiteParser) Parse(buf []byte) ([]telegraf.Metric, error) {
// parse even if the buffer begins with a newline // parse even if the buffer begins with a newline
buf = bytes.TrimPrefix(buf, []byte("\n")) buf = bytes.TrimPrefix(buf, []byte("\n"))
@ -465,3 +479,30 @@ func (n *nodes) Less(j, k int) bool {
func (n *nodes) Swap(i, j int) { (*n)[i], (*n)[j] = (*n)[j], (*n)[i] } func (n *nodes) Swap(i, j int) { (*n)[i], (*n)[j] = (*n)[j], (*n)[i] }
func (n *nodes) Len() int { return len(*n) } func (n *nodes) Len() int { return len(*n) }
type parsedTemplate struct {
template string
filter string
tagstring string
}
type parsedTemplates []parsedTemplate
func (e parsedTemplates) Less(j, k int) bool {
if len(e[j].filter) == 0 && len(e[k].filter) == 0 {
nj := len(strings.Split(e[j].template, "."))
nk := len(strings.Split(e[k].template, "."))
return nj < nk
}
if len(e[j].filter) == 0 {
return true
}
if len(e[k].filter) == 0 {
return false
}
nj := len(strings.Split(e[j].template, "."))
nk := len(strings.Split(e[k].template, "."))
return nj < nk
}
func (e parsedTemplates) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
func (e parsedTemplates) Len() int { return len(e) }

View File

@ -747,6 +747,48 @@ func TestApplyTemplateGreedyField(t *testing.T) {
} }
} }
func TestApplyTemplateOverSpecific(t *testing.T) {
p, err := NewGraphiteParser(
".",
[]string{
"measurement.host.metric.metric.metric",
},
nil,
)
assert.NoError(t, err)
measurement, tags, _, err := p.ApplyTemplate("net.server001.a.b 2")
assert.Equal(t, "net", measurement)
assert.Equal(t,
map[string]string{"host": "server001", "metric": "a.b"},
tags)
}
func TestApplyTemplateMostSpecificTemplate(t *testing.T) {
p, err := NewGraphiteParser(
".",
[]string{
"measurement.host.metric",
"measurement.host.metric.metric.metric",
"measurement.host.metric.metric",
},
nil,
)
assert.NoError(t, err)
measurement, tags, _, err := p.ApplyTemplate("net.server001.a.b.c 2")
assert.Equal(t, "net", measurement)
assert.Equal(t,
map[string]string{"host": "server001", "metric": "a.b.c"},
tags)
measurement, tags, _, err = p.ApplyTemplate("net.server001.a.b 2")
assert.Equal(t, "net", measurement)
assert.Equal(t,
map[string]string{"host": "server001", "metric": "a.b"},
tags)
}
// Test Helpers // Test Helpers
func errstr(err error) string { func errstr(err error) string {
if err != nil { if err != nil {

View File

@ -26,17 +26,15 @@ func (p *InfluxParser) Parse(buf []byte) ([]telegraf.Metric, error) {
points, err := models.ParsePoints(buf) points, err := models.ParsePoints(buf)
metrics := make([]telegraf.Metric, len(points)) metrics := make([]telegraf.Metric, len(points))
for i, point := range points { for i, point := range points {
tags := point.Tags()
for k, v := range p.DefaultTags { for k, v := range p.DefaultTags {
// Only set tags not in parsed metric // only set the default tag if it doesn't already exist:
if _, ok := tags[k]; !ok { if tmp := point.Tags().GetString(k); tmp == "" {
tags[k] = v point.AddTag(k, v)
} }
} }
// Ignore error here because it's impossible that a model.Point // Ignore error here because it's impossible that a model.Point
// wouldn't parse into client.Point properly // wouldn't parse into client.Point properly
metrics[i], _ = telegraf.NewMetric(point.Name(), tags, metrics[i] = telegraf.NewMetricFromPoint(point)
point.Fields(), point.Time())
} }
return metrics, err return metrics, err
} }

View File

@ -0,0 +1,5 @@
package all
import (
_ "github.com/influxdata/telegraf/plugins/processors/printer"
)

View File

@ -0,0 +1,35 @@
package printer
import (
"fmt"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/processors"
)
type Printer struct {
}
var sampleConfig = `
`
func (p *Printer) SampleConfig() string {
return sampleConfig
}
func (p *Printer) Description() string {
return "Print all metrics that pass through this filter."
}
func (p *Printer) Apply(in ...telegraf.Metric) []telegraf.Metric {
for _, metric := range in {
fmt.Println(metric.String())
}
return in
}
func init() {
processors.Add("printer", func() telegraf.Processor {
return &Printer{}
})
}

View File

@ -0,0 +1 @@
package printer

View File

@ -0,0 +1,11 @@
package processors
import "github.com/influxdata/telegraf"
type Creator func() telegraf.Processor
var Processors = map[string]Creator{}
func Add(name string, creator Creator) {
Processors[name] = creator
}

12
processor.go Normal file
View File

@ -0,0 +1,12 @@
package telegraf
type Processor interface {
// SampleConfig returns the default configuration of the Input
SampleConfig() string
// Description returns a one-sentence description on the Input
Description() string
// Apply the filter to the given metric
Apply(in ...Metric) []Metric
}

View File

@ -39,6 +39,13 @@ func (a *Accumulator) NMetrics() uint64 {
return atomic.LoadUint64(&a.nMetrics) return atomic.LoadUint64(&a.nMetrics)
} }
func (a *Accumulator) ClearMetrics() {
atomic.StoreUint64(&a.nMetrics, 0)
a.Lock()
defer a.Unlock()
a.Metrics = make([]*Metric, 0)
}
// AddFields adds a measurement point with a specified timestamp. // AddFields adds a measurement point with a specified timestamp.
func (a *Accumulator) AddFields( func (a *Accumulator) AddFields(
measurement string, measurement string,