Remove metric recreation when filtering (#4767)

This commit is contained in:
Daniel Nelson 2018-09-28 14:48:20 -07:00 committed by GitHub
parent cc64b14ab4
commit 7553c8fd13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 635 additions and 606 deletions

View File

@ -5,6 +5,7 @@ import (
"time" "time"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/metric"
"github.com/influxdata/telegraf/selfstat" "github.com/influxdata/telegraf/selfstat"
) )
@ -14,13 +15,13 @@ var (
type MetricMaker interface { type MetricMaker interface {
Name() string Name() string
MakeMetric( MakeMetric(metric telegraf.Metric) telegraf.Metric
measurement string, }
fields map[string]interface{},
tags map[string]string, type accumulator struct {
mType telegraf.ValueType, maker MetricMaker
t time.Time, metrics chan telegraf.Metric
) telegraf.Metric precision time.Duration
} }
func NewAccumulator( func NewAccumulator(
@ -35,23 +36,13 @@ func NewAccumulator(
return &acc return &acc
} }
type accumulator struct {
metrics chan telegraf.Metric
maker MetricMaker
precision time.Duration
}
func (ac *accumulator) AddFields( func (ac *accumulator) AddFields(
measurement string, measurement string,
fields map[string]interface{}, fields map[string]interface{},
tags map[string]string, tags map[string]string,
t ...time.Time, t ...time.Time,
) { ) {
if m := ac.maker.MakeMetric(measurement, fields, tags, telegraf.Untyped, ac.getTime(t)); m != nil { ac.addMetric(measurement, tags, fields, telegraf.Untyped, t...)
ac.metrics <- m
}
} }
func (ac *accumulator) AddGauge( func (ac *accumulator) AddGauge(
@ -60,9 +51,7 @@ func (ac *accumulator) AddGauge(
tags map[string]string, tags map[string]string,
t ...time.Time, t ...time.Time,
) { ) {
if m := ac.maker.MakeMetric(measurement, fields, tags, telegraf.Gauge, ac.getTime(t)); m != nil { ac.addMetric(measurement, tags, fields, telegraf.Gauge, t...)
ac.metrics <- m
}
} }
func (ac *accumulator) AddCounter( func (ac *accumulator) AddCounter(
@ -71,9 +60,7 @@ func (ac *accumulator) AddCounter(
tags map[string]string, tags map[string]string,
t ...time.Time, t ...time.Time,
) { ) {
if m := ac.maker.MakeMetric(measurement, fields, tags, telegraf.Counter, ac.getTime(t)); m != nil { ac.addMetric(measurement, tags, fields, telegraf.Counter, t...)
ac.metrics <- m
}
} }
func (ac *accumulator) AddSummary( func (ac *accumulator) AddSummary(
@ -82,9 +69,7 @@ func (ac *accumulator) AddSummary(
tags map[string]string, tags map[string]string,
t ...time.Time, t ...time.Time,
) { ) {
if m := ac.maker.MakeMetric(measurement, fields, tags, telegraf.Summary, ac.getTime(t)); m != nil { ac.addMetric(measurement, tags, fields, telegraf.Summary, t...)
ac.metrics <- m
}
} }
func (ac *accumulator) AddHistogram( func (ac *accumulator) AddHistogram(
@ -93,7 +78,21 @@ func (ac *accumulator) AddHistogram(
tags map[string]string, tags map[string]string,
t ...time.Time, t ...time.Time,
) { ) {
if m := ac.maker.MakeMetric(measurement, fields, tags, telegraf.Histogram, ac.getTime(t)); m != nil { ac.addMetric(measurement, tags, fields, telegraf.Histogram, t...)
}
func (ac *accumulator) addMetric(
measurement string,
tags map[string]string,
fields map[string]interface{},
tp telegraf.ValueType,
t ...time.Time,
) {
m, err := metric.New(measurement, tags, fields, ac.getTime(t), tp)
if err != nil {
return
}
if m := ac.maker.MakeMetric(m); m != nil {
ac.metrics <- m ac.metrics <- m
} }
} }
@ -105,7 +104,6 @@ func (ac *accumulator) AddError(err error) {
return return
} }
NErrors.Incr(1) NErrors.Incr(1)
//TODO suppress/throttle consecutive duplicate errors?
log.Printf("E! Error in plugin [%s]: %s", ac.maker.Name(), err) log.Printf("E! Error in plugin [%s]: %s", ac.maker.Name(), err)
} }

View File

@ -9,7 +9,6 @@ import (
"time" "time"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/metric"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -134,26 +133,7 @@ type TestMetricMaker struct {
func (tm *TestMetricMaker) Name() string { func (tm *TestMetricMaker) Name() string {
return "TestPlugin" return "TestPlugin"
} }
func (tm *TestMetricMaker) MakeMetric(
measurement string, func (tm *TestMetricMaker) MakeMetric(metric telegraf.Metric) telegraf.Metric {
fields map[string]interface{}, return metric
tags map[string]string,
mType telegraf.ValueType,
t time.Time,
) telegraf.Metric {
switch mType {
case telegraf.Untyped:
if m, err := metric.New(measurement, tags, fields, t); err == nil {
return m
}
case telegraf.Counter:
if m, err := metric.New(measurement, tags, fields, t, telegraf.Counter); err == nil {
return m
}
case telegraf.Gauge:
if m, err := metric.New(measurement, tags, fields, t, telegraf.Gauge); err == nil {
return m
}
}
return nil
} }

View File

@ -1,11 +1,14 @@
# Telegraf Configuration # Configuration
You can see the latest config file with all available plugins here: Telegraf's configuration file is written using
[telegraf.conf](https://github.com/influxdata/telegraf/blob/master/etc/telegraf.conf) [TOML](https://github.com/toml-lang/toml#toml).
[View the telegraf.conf config file with all available
plugins](/etc/telegraf.conf).
## Generating a Configuration File ## Generating a Configuration File
A default Telegraf config file can be auto-generated by telegraf: A default config file can be generated by telegraf:
``` ```
telegraf config > telegraf.conf telegraf config > telegraf.conf
@ -18,7 +21,7 @@ To generate a file with specific inputs and outputs, you can use the
telegraf --input-filter cpu:mem:net:swap --output-filter influxdb:kafka config telegraf --input-filter cpu:mem:net:swap --output-filter influxdb:kafka config
``` ```
## 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"),
@ -27,7 +30,7 @@ for numbers and booleans they should be plain (ie, $INT_VAR, $BOOL_VAR)
When using the `.deb` or `.rpm` packages, you can define environment variables When using the `.deb` or `.rpm` packages, you can define environment variables
in the `/etc/default/telegraf` file. in the `/etc/default/telegraf` file.
## Configuration file locations ### Configuration file locations
The location of the configuration file can be set via the `--config` command The location of the configuration file can be set via the `--config` command
line flag. line flag.
@ -40,13 +43,13 @@ On most systems, the default locations are `/etc/telegraf/telegraf.conf` for
the main configuration file and `/etc/telegraf/telegraf.d` for the directory of the main configuration file and `/etc/telegraf/telegraf.d` for the directory of
configuration files. configuration files.
# Global Tags ### 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.
@ -85,7 +88,7 @@ ie, a jitter of 5s and flush_interval 10s means flushes will happen every 10-15s
* **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. * **omit_hostname**: If true, do no set the "host" tag in the telegraf agent.
## Input Configuration ### Input Configuration
The following config parameters are available for all inputs: The following config parameters are available for all inputs:
@ -98,15 +101,15 @@ you can configure that here.
* **name_suffix**: Specifies a suffix 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. * **tags**: A map of tags to apply to a specific input's measurements.
The [measurement filtering](#measurement-filtering) parameters can be used to The [metric filtering](#metric-filtering) parameters can be used to limit what metrics are
limit what metrics are emitted from the input plugin. emitted from the input plugin.
## Output Configuration ### Output Configuration
The [measurement filtering](#measurement-filtering) parameters can be used to The [metric filtering](#metric-filtering) parameters can be used to limit what metrics are
limit what metrics are emitted from the output plugin. emitted from the output plugin.
## Aggregator Configuration ### Aggregator Configuration
The following config parameters are available for all aggregators: The following config parameters are available for all aggregators:
@ -125,63 +128,77 @@ aggregator and will not get sent to the output plugins.
* **name_suffix**: Specifies a suffix 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. * **tags**: A map of tags to apply to a specific input's measurements.
The [measurement filtering](#measurement-filtering) parameters can be used to The [metric filtering](#metric-filtering) parameters can be used to limit what metrics are
limit what metrics are handled by the aggregator. Excluded metrics are passed handled by the aggregator. Excluded metrics are passed downstream to the next
downstream to the next aggregator. aggregator.
## Processor Configuration ### Processor Configuration
The following config parameters are available for all processors: The following config parameters are available for all processors:
* **order**: This is the order in which the processor(s) get executed. If this * **order**: This is the order in which the processor(s) get executed. If this
is not specified then processor execution order will be random. is not specified then processor execution order will be random.
The [measurement filtering](#measurement-filtering) parameters can be used The [metric filtering](#metric-filtering) parameters can be used to limit what metrics are
to limit what metrics are handled by the processor. Excluded metrics are handled by the processor. Excluded metrics are passed downstream to the next
passed downstream to the next processor. processor.
#### Measurement Filtering <a id="measurement-filtering"></a>
### Metric Filtering
Filters can be configured per input, output, processor, or aggregator, Metric filtering can be configured per plugin on any input, output, processor,
see below for examples. and aggregator plugin. Filters fall under two categories: Selectors and
Modifiers.
* **namepass**: #### Selectors
An array of glob pattern strings. Only points whose measurement name matches
Selector filters include or exclude entire metrics. When a metric is excluded
from a Input or an Output plugin, the metric is dropped. If a metric is
excluded from a Processor or Aggregator plugin, it is skips the plugin and is
sent onwards to the next stage of processing.
- **namepass**:
An array of glob pattern strings. Only metrics whose measurement name matches
a pattern in this list are emitted. a pattern in this list are emitted.
* **namedrop**:
The inverse of `namepass`. If a match is found the point is discarded. This - **namedrop**:
is tested on points after they have passed the `namepass` test. The inverse of `namepass`. If a match is found the metric is discarded. This
* **fieldpass**: is tested on metrics after they have passed the `namepass` test.
An array of glob pattern strings. Only fields whose field key matches a
pattern in this list are emitted. - **tagpass**:
* **fielddrop**: A table mapping tag keys to arrays of glob pattern strings. Only metrics
The inverse of `fieldpass`. Fields with a field key matching one of the
patterns will be discarded from the point. This is tested on points after
they have passed the `fieldpass` test.
* **tagpass**:
A table mapping tag keys to arrays of glob pattern strings. Only points
that contain a tag key in the table and a tag value matching one of its that contain a tag key in the table and a tag value matching one of its
patterns is emitted. patterns is emitted.
* **tagdrop**:
The inverse of `tagpass`. If a match is found the point is discarded. This - **tagdrop**:
is tested on points after they have passed the `tagpass` test. The inverse of `tagpass`. If a match is found the metric is discarded. This
* **taginclude**: is tested on metrics after they have passed the `tagpass` test.
#### Modifiers
Modifier filters remove tags and fields from a metric. If all fields are
removed the metric is removed.
- **fieldpass**:
An array of glob pattern strings. Only fields whose field key matches a
pattern in this list are emitted.
- **fielddrop**:
The inverse of `fieldpass`. Fields with a field key matching one of the
patterns will be discarded from the metric. This is tested on metrics after
they have passed the `fieldpass` test.
- **taginclude**:
An array of glob pattern strings. Only tags with a tag key matching one of An array of glob pattern strings. Only tags with a tag key matching one of
the patterns are emitted. In contrast to `tagpass`, which will pass an entire the patterns are emitted. In contrast to `tagpass`, which will pass an entire
point based on its tag, `taginclude` removes all non matching tags from the metric based on its tag, `taginclude` removes all non matching tags from the
point. This filter can be used on both inputs & outputs, but it is metric.
_recommended_ to be used on inputs, as it is more efficient to filter out tags
at the ingestion point. - **tagexclude**:
* **tagexclude**:
The inverse of `taginclude`. Tags with a tag key matching one of the patterns The inverse of `taginclude`. Tags with a tag key matching one of the patterns
will be discarded from the point. will be discarded from the metric.
**NOTE** Due to the way TOML is parsed, `tagpass` and `tagdrop` parameters ### Input Configuration Examples
must be defined at the _end_ of the plugin definition, otherwise subsequent
plugin config options will be interpreted as part of the tagpass/tagdrop
tables.
#### 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
at 192.168.59.103:8086, tagging measurements with dc="denver-1". It will output at 192.168.59.103:8086, tagging measurements with dc="denver-1". It will output

View File

@ -884,14 +884,6 @@ func (c *Config) addInput(name string, table *ast.Table) error {
// builds the filter and returns a // builds the filter and returns a
// models.AggregatorConfig to be inserted into models.RunningAggregator // models.AggregatorConfig to be inserted into models.RunningAggregator
func buildAggregator(name string, tbl *ast.Table) (*models.AggregatorConfig, error) { 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{ conf := &models.AggregatorConfig{
Name: name, Name: name,
Delay: time.Millisecond * 100, Delay: time.Millisecond * 100,
@ -989,13 +981,6 @@ func buildAggregator(name string, tbl *ast.Table) (*models.AggregatorConfig, err
// models.ProcessorConfig to be inserted into models.RunningProcessor // models.ProcessorConfig to be inserted into models.RunningProcessor
func buildProcessor(name string, tbl *ast.Table) (*models.ProcessorConfig, error) { func buildProcessor(name string, tbl *ast.Table) (*models.ProcessorConfig, error) {
conf := &models.ProcessorConfig{Name: name} 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 node, ok := tbl.Fields["order"]; ok {
if kv, ok := node.(*ast.KeyValue); ok { if kv, ok := node.(*ast.KeyValue); ok {

View File

@ -3,6 +3,7 @@ package models
import ( import (
"fmt" "fmt"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/filter" "github.com/influxdata/telegraf/filter"
) )
@ -93,45 +94,35 @@ func (f *Filter) Compile() error {
return nil return nil
} }
// Apply applies the filter to the given measurement name, fields map, and // Select returns true if the metric matches according to the
// tags map. It will return false if the metric should be "filtered out", and // namepass/namedrop and tagpass/tagdrop filters. The metric is not modified.
// true if the metric should "pass". func (f *Filter) Select(metric telegraf.Metric) bool {
// It will modify tags & fields in-place if they need to be deleted.
func (f *Filter) Apply(
measurement string,
fields map[string]interface{},
tags map[string]string,
) bool {
if !f.isActive { if !f.isActive {
return true return true
} }
// check if the measurement name should pass if !f.shouldNamePass(metric.Name()) {
if !f.shouldNamePass(measurement) {
return false return false
} }
// check if the tags should pass if !f.shouldTagsPass(metric.TagList()) {
if !f.shouldTagsPass(tags) {
return false return false
} }
// filter fields
for fieldkey, _ := range fields {
if !f.shouldFieldPass(fieldkey) {
delete(fields, fieldkey)
}
}
if len(fields) == 0 {
return false
}
// filter tags
f.filterTags(tags)
return true return true
} }
// Modify removes any tags and fields from the metric according to the
// fieldpass/fielddrop and taginclude/tagexclude filters.
func (f *Filter) Modify(metric telegraf.Metric) {
if !f.isActive {
return
}
f.filterFields(metric)
f.filterTags(metric)
}
// IsActive checking if filter is active // IsActive checking if filter is active
func (f *Filter) IsActive() bool { func (f *Filter) IsActive() bool {
return f.isActive return f.isActive
@ -140,7 +131,6 @@ func (f *Filter) IsActive() bool {
// shouldNamePass returns true if the metric should pass, false if should drop // shouldNamePass returns true if the metric should pass, false if should drop
// based on the drop/pass filter parameters // based on the drop/pass filter parameters
func (f *Filter) shouldNamePass(key string) bool { func (f *Filter) shouldNamePass(key string) bool {
pass := func(f *Filter) bool { pass := func(f *Filter) bool {
if f.namePass.Match(key) { if f.namePass.Match(key) {
return true return true
@ -169,47 +159,32 @@ func (f *Filter) shouldNamePass(key string) bool {
// shouldFieldPass returns true if the metric should pass, false if should drop // shouldFieldPass returns true if the metric should pass, false if should drop
// based on the drop/pass filter parameters // based on the drop/pass filter parameters
func (f *Filter) shouldFieldPass(key string) bool { func (f *Filter) shouldFieldPass(key string) bool {
pass := func(f *Filter) bool {
if f.fieldPass.Match(key) {
return true
}
return false
}
drop := func(f *Filter) bool {
if f.fieldDrop.Match(key) {
return false
}
return true
}
if f.fieldPass != nil && f.fieldDrop != nil { if f.fieldPass != nil && f.fieldDrop != nil {
return pass(f) && drop(f) return f.fieldPass.Match(key) && !f.fieldDrop.Match(key)
} else if f.fieldPass != nil { } else if f.fieldPass != nil {
return pass(f) return f.fieldPass.Match(key)
} else if f.fieldDrop != nil { } else if f.fieldDrop != nil {
return drop(f) return !f.fieldDrop.Match(key)
} }
return true return true
} }
// shouldTagsPass returns true if the metric should pass, false if should drop // shouldTagsPass returns true if the metric should pass, false if should drop
// based on the tagdrop/tagpass filter parameters // based on the tagdrop/tagpass filter parameters
func (f *Filter) shouldTagsPass(tags map[string]string) bool { func (f *Filter) shouldTagsPass(tags []*telegraf.Tag) bool {
pass := func(f *Filter) bool { pass := func(f *Filter) bool {
for _, pat := range f.TagPass { for _, pat := range f.TagPass {
if pat.filter == nil { if pat.filter == nil {
continue continue
} }
if tagval, ok := tags[pat.Name]; ok { for _, tag := range tags {
if pat.filter.Match(tagval) { if tag.Key == pat.Name {
if pat.filter.Match(tag.Value) {
return true return true
} }
} }
} }
}
return false return false
} }
@ -218,12 +193,14 @@ func (f *Filter) shouldTagsPass(tags map[string]string) bool {
if pat.filter == nil { if pat.filter == nil {
continue continue
} }
if tagval, ok := tags[pat.Name]; ok { for _, tag := range tags {
if pat.filter.Match(tagval) { if tag.Key == pat.Name {
if pat.filter.Match(tag.Value) {
return false return false
} }
} }
} }
}
return true return true
} }
@ -242,22 +219,42 @@ func (f *Filter) shouldTagsPass(tags map[string]string) bool {
return true return true
} }
// Apply TagInclude and TagExclude filters. // filterFields removes fields according to fieldpass/fielddrop.
// modifies the tags map in-place. func (f *Filter) filterFields(metric telegraf.Metric) {
func (f *Filter) filterTags(tags map[string]string) { filterKeys := []string{}
if f.tagInclude != nil { for _, field := range metric.FieldList() {
for k, _ := range tags { if !f.shouldFieldPass(field.Key) {
if !f.tagInclude.Match(k) { filterKeys = append(filterKeys, field.Key)
delete(tags, k)
}
} }
} }
if f.tagExclude != nil { for _, key := range filterKeys {
for k, _ := range tags { metric.RemoveField(key)
if f.tagExclude.Match(k) { }
delete(tags, k) }
}
} // filterTags removes tags according to taginclude/tagexclude.
func (f *Filter) filterTags(metric telegraf.Metric) {
filterKeys := []string{}
if f.tagInclude != nil {
for _, tag := range metric.TagList() {
if !f.tagInclude.Match(tag.Key) {
filterKeys = append(filterKeys, tag.Key)
}
}
}
for _, key := range filterKeys {
metric.RemoveTag(key)
}
if f.tagExclude != nil {
for _, tag := range metric.TagList() {
if f.tagExclude.Match(tag.Key) {
filterKeys = append(filterKeys, tag.Key)
}
}
}
for _, key := range filterKeys {
metric.RemoveTag(key)
} }
} }

View File

@ -2,17 +2,24 @@ package models
import ( import (
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/metric"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestFilter_ApplyEmpty(t *testing.T) { func TestFilter_ApplyEmpty(t *testing.T) {
f := Filter{} f := Filter{}
require.NoError(t, f.Compile()) require.NoError(t, f.Compile())
assert.False(t, f.IsActive()) require.False(t, f.IsActive())
assert.True(t, f.Apply("m", map[string]interface{}{"value": int64(1)}, map[string]string{})) m, err := metric.New("m",
map[string]string{},
map[string]interface{}{"value": int64(1)},
time.Now())
require.NoError(t, err)
require.True(t, f.Select(m))
} }
func TestFilter_ApplyTagsDontPass(t *testing.T) { func TestFilter_ApplyTagsDontPass(t *testing.T) {
@ -27,11 +34,14 @@ func TestFilter_ApplyTagsDontPass(t *testing.T) {
} }
require.NoError(t, f.Compile()) require.NoError(t, f.Compile())
require.NoError(t, f.Compile()) require.NoError(t, f.Compile())
assert.True(t, f.IsActive()) require.True(t, f.IsActive())
assert.False(t, f.Apply("m", m, err := metric.New("m",
map[string]string{"cpu": "cpu-total"},
map[string]interface{}{"value": int64(1)}, map[string]interface{}{"value": int64(1)},
map[string]string{"cpu": "cpu-total"})) time.Now())
require.NoError(t, err)
require.False(t, f.Select(m))
} }
func TestFilter_ApplyDeleteFields(t *testing.T) { func TestFilter_ApplyDeleteFields(t *testing.T) {
@ -40,11 +50,19 @@ func TestFilter_ApplyDeleteFields(t *testing.T) {
} }
require.NoError(t, f.Compile()) require.NoError(t, f.Compile())
require.NoError(t, f.Compile()) require.NoError(t, f.Compile())
assert.True(t, f.IsActive()) require.True(t, f.IsActive())
fields := map[string]interface{}{"value": int64(1), "value2": int64(2)} m, err := metric.New("m",
assert.True(t, f.Apply("m", fields, nil)) map[string]string{},
assert.Equal(t, map[string]interface{}{"value2": int64(2)}, fields) map[string]interface{}{
"value": int64(1),
"value2": int64(2),
},
time.Now())
require.NoError(t, err)
require.True(t, f.Select(m))
f.Modify(m)
require.Equal(t, map[string]interface{}{"value2": int64(2)}, m.Fields())
} }
func TestFilter_ApplyDeleteAllFields(t *testing.T) { func TestFilter_ApplyDeleteAllFields(t *testing.T) {
@ -53,10 +71,19 @@ func TestFilter_ApplyDeleteAllFields(t *testing.T) {
} }
require.NoError(t, f.Compile()) require.NoError(t, f.Compile())
require.NoError(t, f.Compile()) require.NoError(t, f.Compile())
assert.True(t, f.IsActive()) require.True(t, f.IsActive())
fields := map[string]interface{}{"value": int64(1), "value2": int64(2)} m, err := metric.New("m",
assert.False(t, f.Apply("m", fields, nil)) map[string]string{},
map[string]interface{}{
"value": int64(1),
"value2": int64(2),
},
time.Now())
require.NoError(t, err)
require.True(t, f.Select(m))
f.Modify(m)
require.Len(t, m.FieldList(), 0)
} }
func TestFilter_Empty(t *testing.T) { func TestFilter_Empty(t *testing.T) {
@ -230,20 +257,20 @@ func TestFilter_TagPass(t *testing.T) {
} }
require.NoError(t, f.Compile()) require.NoError(t, f.Compile())
passes := []map[string]string{ passes := [][]*telegraf.Tag{
{"cpu": "cpu-total"}, []*telegraf.Tag{&telegraf.Tag{Key: "cpu", Value: "cpu-total"}},
{"cpu": "cpu-0"}, []*telegraf.Tag{&telegraf.Tag{Key: "cpu", Value: "cpu-0"}},
{"cpu": "cpu-1"}, []*telegraf.Tag{&telegraf.Tag{Key: "cpu", Value: "cpu-1"}},
{"cpu": "cpu-2"}, []*telegraf.Tag{&telegraf.Tag{Key: "cpu", Value: "cpu-2"}},
{"mem": "mem_free"}, []*telegraf.Tag{&telegraf.Tag{Key: "mem", Value: "mem_free"}},
} }
drops := []map[string]string{ drops := [][]*telegraf.Tag{
{"cpu": "cputotal"}, []*telegraf.Tag{&telegraf.Tag{Key: "cpu", Value: "cputotal"}},
{"cpu": "cpu0"}, []*telegraf.Tag{&telegraf.Tag{Key: "cpu", Value: "cpu0"}},
{"cpu": "cpu1"}, []*telegraf.Tag{&telegraf.Tag{Key: "cpu", Value: "cpu1"}},
{"cpu": "cpu2"}, []*telegraf.Tag{&telegraf.Tag{Key: "cpu", Value: "cpu2"}},
{"mem": "mem_used"}, []*telegraf.Tag{&telegraf.Tag{Key: "mem", Value: "mem_used"}},
} }
for _, tags := range passes { for _, tags := range passes {
@ -274,20 +301,20 @@ func TestFilter_TagDrop(t *testing.T) {
} }
require.NoError(t, f.Compile()) require.NoError(t, f.Compile())
drops := []map[string]string{ drops := [][]*telegraf.Tag{
{"cpu": "cpu-total"}, []*telegraf.Tag{&telegraf.Tag{Key: "cpu", Value: "cpu-total"}},
{"cpu": "cpu-0"}, []*telegraf.Tag{&telegraf.Tag{Key: "cpu", Value: "cpu-0"}},
{"cpu": "cpu-1"}, []*telegraf.Tag{&telegraf.Tag{Key: "cpu", Value: "cpu-1"}},
{"cpu": "cpu-2"}, []*telegraf.Tag{&telegraf.Tag{Key: "cpu", Value: "cpu-2"}},
{"mem": "mem_free"}, []*telegraf.Tag{&telegraf.Tag{Key: "mem", Value: "mem_free"}},
} }
passes := []map[string]string{ passes := [][]*telegraf.Tag{
{"cpu": "cputotal"}, []*telegraf.Tag{&telegraf.Tag{Key: "cpu", Value: "cputotal"}},
{"cpu": "cpu0"}, []*telegraf.Tag{&telegraf.Tag{Key: "cpu", Value: "cpu0"}},
{"cpu": "cpu1"}, []*telegraf.Tag{&telegraf.Tag{Key: "cpu", Value: "cpu1"}},
{"cpu": "cpu2"}, []*telegraf.Tag{&telegraf.Tag{Key: "cpu", Value: "cpu2"}},
{"mem": "mem_used"}, []*telegraf.Tag{&telegraf.Tag{Key: "mem", Value: "mem_used"}},
} }
for _, tags := range passes { for _, tags := range passes {
@ -304,58 +331,70 @@ func TestFilter_TagDrop(t *testing.T) {
} }
func TestFilter_FilterTagsNoMatches(t *testing.T) { func TestFilter_FilterTagsNoMatches(t *testing.T) {
pretags := map[string]string{ m, err := metric.New("m",
map[string]string{
"host": "localhost", "host": "localhost",
"mytag": "foobar", "mytag": "foobar",
} },
map[string]interface{}{"value": int64(1)},
time.Now())
require.NoError(t, err)
f := Filter{ f := Filter{
TagExclude: []string{"nomatch"}, TagExclude: []string{"nomatch"},
} }
require.NoError(t, f.Compile()) require.NoError(t, f.Compile())
f.filterTags(pretags) f.filterTags(m)
assert.Equal(t, map[string]string{ require.Equal(t, map[string]string{
"host": "localhost", "host": "localhost",
"mytag": "foobar", "mytag": "foobar",
}, pretags) }, m.Tags())
f = Filter{ f = Filter{
TagInclude: []string{"nomatch"}, TagInclude: []string{"nomatch"},
} }
require.NoError(t, f.Compile()) require.NoError(t, f.Compile())
f.filterTags(pretags) f.filterTags(m)
assert.Equal(t, map[string]string{}, pretags) require.Equal(t, map[string]string{}, m.Tags())
} }
func TestFilter_FilterTagsMatches(t *testing.T) { func TestFilter_FilterTagsMatches(t *testing.T) {
pretags := map[string]string{ m, err := metric.New("m",
map[string]string{
"host": "localhost", "host": "localhost",
"mytag": "foobar", "mytag": "foobar",
} },
map[string]interface{}{"value": int64(1)},
time.Now())
require.NoError(t, err)
f := Filter{ f := Filter{
TagExclude: []string{"ho*"}, TagExclude: []string{"ho*"},
} }
require.NoError(t, f.Compile()) require.NoError(t, f.Compile())
f.filterTags(pretags) f.filterTags(m)
assert.Equal(t, map[string]string{ require.Equal(t, map[string]string{
"mytag": "foobar", "mytag": "foobar",
}, pretags) }, m.Tags())
pretags = map[string]string{ m, err = metric.New("m",
map[string]string{
"host": "localhost", "host": "localhost",
"mytag": "foobar", "mytag": "foobar",
} },
map[string]interface{}{"value": int64(1)},
time.Now())
require.NoError(t, err)
f = Filter{ f = Filter{
TagInclude: []string{"my*"}, TagInclude: []string{"my*"},
} }
require.NoError(t, f.Compile()) require.NoError(t, f.Compile())
f.filterTags(pretags) f.filterTags(m)
assert.Equal(t, map[string]string{ require.Equal(t, map[string]string{
"mytag": "foobar", "mytag": "foobar",
}, pretags) }, m.Tags())
} }
// TestFilter_FilterNamePassAndDrop used for check case when // TestFilter_FilterNamePassAndDrop used for check case when
@ -374,7 +413,7 @@ func TestFilter_FilterNamePassAndDrop(t *testing.T) {
require.NoError(t, f.Compile()) require.NoError(t, f.Compile())
for i, name := range inputData { for i, name := range inputData {
assert.Equal(t, f.shouldNamePass(name), expectedResult[i]) require.Equal(t, f.shouldNamePass(name), expectedResult[i])
} }
} }
@ -394,7 +433,7 @@ func TestFilter_FilterFieldPassAndDrop(t *testing.T) {
require.NoError(t, f.Compile()) require.NoError(t, f.Compile())
for i, field := range inputData { for i, field := range inputData {
assert.Equal(t, f.shouldFieldPass(field), expectedResult[i]) require.Equal(t, f.shouldFieldPass(field), expectedResult[i])
} }
} }
@ -402,12 +441,11 @@ func TestFilter_FilterFieldPassAndDrop(t *testing.T) {
// both parameters were defined // both parameters were defined
// see: https://github.com/influxdata/telegraf/issues/2860 // see: https://github.com/influxdata/telegraf/issues/2860
func TestFilter_FilterTagsPassAndDrop(t *testing.T) { func TestFilter_FilterTagsPassAndDrop(t *testing.T) {
inputData := [][]*telegraf.Tag{
inputData := []map[string]string{ []*telegraf.Tag{&telegraf.Tag{Key: "tag1", Value: "1"}, &telegraf.Tag{Key: "tag2", Value: "3"}},
{"tag1": "1", "tag2": "3"}, []*telegraf.Tag{&telegraf.Tag{Key: "tag1", Value: "1"}, &telegraf.Tag{Key: "tag2", Value: "2"}},
{"tag1": "1", "tag2": "2"}, []*telegraf.Tag{&telegraf.Tag{Key: "tag1", Value: "2"}, &telegraf.Tag{Key: "tag2", Value: "1"}},
{"tag1": "2", "tag2": "1"}, []*telegraf.Tag{&telegraf.Tag{Key: "tag1", Value: "4"}, &telegraf.Tag{Key: "tag2", Value: "1"}},
{"tag1": "4", "tag2": "1"},
} }
expectedResult := []bool{false, true, false, false} expectedResult := []bool{false, true, false, false}
@ -438,7 +476,7 @@ func TestFilter_FilterTagsPassAndDrop(t *testing.T) {
require.NoError(t, f.Compile()) require.NoError(t, f.Compile())
for i, tag := range inputData { for i, tag := range inputData {
assert.Equal(t, f.shouldTagsPass(tag), expectedResult[i]) require.Equal(t, f.shouldTagsPass(tag), expectedResult[i])
} }
} }

View File

@ -1,86 +1,42 @@
package models package models
import ( import (
"log"
"time"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/metric"
) )
// makemetric is used by both RunningAggregator & RunningInput // Makemetric applies new metric plugin and agent measurement and tag
// to make metrics. // settings.
// 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( func makemetric(
measurement string, metric telegraf.Metric,
fields map[string]interface{},
tags map[string]string,
nameOverride string, nameOverride string,
namePrefix string, namePrefix string,
nameSuffix string, nameSuffix string,
pluginTags map[string]string, tags map[string]string,
daemonTags map[string]string, globalTags map[string]string,
filter Filter,
applyFilter bool,
mType telegraf.ValueType,
t time.Time,
) telegraf.Metric { ) telegraf.Metric {
if len(fields) == 0 || len(measurement) == 0 { if len(nameOverride) != 0 {
return nil metric.SetName(nameOverride)
}
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 { if len(namePrefix) != 0 {
measurement = namePrefix + measurement metric.AddPrefix(namePrefix)
} }
if len(nameSuffix) != 0 { if len(nameSuffix) != 0 {
measurement = measurement + nameSuffix metric.AddSuffix(nameSuffix)
} }
// Apply plugin-wide tags if set // Apply plugin-wide tags
for k, v := range pluginTags { for k, v := range tags {
if _, ok := tags[k]; !ok { if _, ok := metric.GetTag(k); !ok {
tags[k] = v metric.AddTag(k, v)
} }
} }
// Apply daemon-wide tags if set // Apply global tags
for k, v := range daemonTags { for k, v := range globalTags {
if _, ok := tags[k]; !ok { if _, ok := metric.GetTag(k); !ok {
tags[k] = v metric.AddTag(k, v)
} }
} }
// Apply the metric filter(s) return metric
// 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
}
}
m, err := metric.New(measurement, tags, fields, t, mType)
if err != nil {
log.Printf("Error adding point [%s]: %s\n", measurement, err.Error())
return nil
}
return m
} }

View File

@ -5,7 +5,6 @@ import (
"time" "time"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/metric"
) )
type RunningAggregator struct { type RunningAggregator struct {
@ -29,47 +28,32 @@ func NewRunningAggregator(
} }
} }
// AggregatorConfig containing configuration parameters for the running // AggregatorConfig is the common config for all aggregators.
// aggregator plugin.
type AggregatorConfig struct { type AggregatorConfig struct {
Name string Name string
DropOriginal bool DropOriginal bool
Period time.Duration
Delay time.Duration
NameOverride string NameOverride string
MeasurementPrefix string MeasurementPrefix string
MeasurementSuffix string MeasurementSuffix string
Tags map[string]string Tags map[string]string
Filter Filter Filter Filter
Period time.Duration
Delay time.Duration
} }
func (r *RunningAggregator) Name() string { func (r *RunningAggregator) Name() string {
return "aggregators." + r.Config.Name return "aggregators." + r.Config.Name
} }
func (r *RunningAggregator) MakeMetric( func (r *RunningAggregator) MakeMetric(metric telegraf.Metric) telegraf.Metric {
measurement string,
fields map[string]interface{},
tags map[string]string,
mType telegraf.ValueType,
t time.Time,
) telegraf.Metric {
m := makemetric( m := makemetric(
measurement, metric,
fields,
tags,
r.Config.NameOverride, r.Config.NameOverride,
r.Config.MeasurementPrefix, r.Config.MeasurementPrefix,
r.Config.MeasurementSuffix, r.Config.MeasurementSuffix,
r.Config.Tags, r.Config.Tags,
nil, nil)
r.Config.Filter,
false,
mType,
t,
)
if m != nil { if m != nil {
m.SetAggregate(true) m.SetAggregate(true)
@ -78,27 +62,23 @@ func (r *RunningAggregator) MakeMetric(
return m return m
} }
// Add applies the given metric to the aggregator. // Add a metric to the aggregator and return true if the original metric
// Before applying to the plugin, it will run any defined filters on the metric. // should be dropped.
// Apply returns true if the original metric should be dropped. func (r *RunningAggregator) Add(metric telegraf.Metric) bool {
func (r *RunningAggregator) Add(in telegraf.Metric) bool { if ok := r.Config.Filter.Select(metric); !ok {
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 return false
} }
in, _ = metric.New(name, tags, fields, t) r.Config.Filter.Modify(metric)
if len(metric.FieldList()) == 0 {
return r.Config.DropOriginal
} }
r.metrics <- in r.metrics <- metric
return r.Config.DropOriginal return r.Config.DropOriginal
} }
func (r *RunningAggregator) add(in telegraf.Metric) { func (r *RunningAggregator) add(in telegraf.Metric) {
r.a.Add(in) r.a.Add(in)
} }

View File

@ -7,9 +7,11 @@ import (
"time" "time"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/metric"
"github.com/influxdata/telegraf/testutil" "github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestAdd(t *testing.T) { func TestAdd(t *testing.T) {
@ -25,13 +27,15 @@ func TestAdd(t *testing.T) {
acc := testutil.Accumulator{} acc := testutil.Accumulator{}
go ra.Run(&acc, make(chan struct{})) go ra.Run(&acc, make(chan struct{}))
m := ra.MakeMetric( m, err := metric.New("RITest",
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{}, map[string]string{},
telegraf.Untyped, map[string]interface{}{
"value": int64(101),
},
time.Now().Add(time.Millisecond*150), time.Now().Add(time.Millisecond*150),
) telegraf.Untyped)
require.NoError(t, err)
assert.False(t, ra.Add(m)) assert.False(t, ra.Add(m))
for { for {
@ -56,34 +60,37 @@ func TestAddMetricsOutsideCurrentPeriod(t *testing.T) {
acc := testutil.Accumulator{} acc := testutil.Accumulator{}
go ra.Run(&acc, make(chan struct{})) go ra.Run(&acc, make(chan struct{}))
// metric before current period m, err := metric.New("RITest",
m := ra.MakeMetric(
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{}, map[string]string{},
telegraf.Untyped, map[string]interface{}{
"value": int64(101),
},
time.Now().Add(-time.Hour), time.Now().Add(-time.Hour),
) telegraf.Untyped)
require.NoError(t, err)
assert.False(t, ra.Add(m)) assert.False(t, ra.Add(m))
// metric after current period // metric after current period
m = ra.MakeMetric( m, err = metric.New("RITest",
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{}, map[string]string{},
telegraf.Untyped, map[string]interface{}{
"value": int64(101),
},
time.Now().Add(time.Hour), time.Now().Add(time.Hour),
) telegraf.Untyped)
require.NoError(t, err)
assert.False(t, ra.Add(m)) assert.False(t, ra.Add(m))
// "now" metric // "now" metric
m = ra.MakeMetric( m, err = metric.New("RITest",
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{}, map[string]string{},
telegraf.Untyped, map[string]interface{}{
"value": int64(101),
},
time.Now().Add(time.Millisecond*50), time.Now().Add(time.Millisecond*50),
) telegraf.Untyped)
require.NoError(t, err)
assert.False(t, ra.Add(m)) assert.False(t, ra.Add(m))
for { for {
@ -115,13 +122,14 @@ func TestAddAndPushOnePeriod(t *testing.T) {
ra.Run(&acc, shutdown) ra.Run(&acc, shutdown)
}() }()
m := ra.MakeMetric( m, err := metric.New("RITest",
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{}, map[string]string{},
telegraf.Untyped, map[string]interface{}{
"value": int64(101),
},
time.Now().Add(time.Millisecond*100), time.Now().Add(time.Millisecond*100),
) telegraf.Untyped)
require.NoError(t, err)
assert.False(t, ra.Add(m)) assert.False(t, ra.Add(m))
for { for {
@ -146,23 +154,25 @@ func TestAddDropOriginal(t *testing.T) {
}) })
assert.NoError(t, ra.Config.Filter.Compile()) assert.NoError(t, ra.Config.Filter.Compile())
m := ra.MakeMetric( m, err := metric.New("RITest",
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{}, map[string]string{},
telegraf.Untyped, map[string]interface{}{
"value": int64(101),
},
time.Now(), time.Now(),
) telegraf.Untyped)
require.NoError(t, err)
assert.True(t, ra.Add(m)) assert.True(t, ra.Add(m))
// this metric name doesn't match the filter, so Add will return false // this metric name doesn't match the filter, so Add will return false
m2 := ra.MakeMetric( m2, err := metric.New("foobar",
"foobar",
map[string]interface{}{"value": int(101)},
map[string]string{}, map[string]string{},
telegraf.Untyped, map[string]interface{}{
"value": int64(101),
},
time.Now(), time.Now(),
) telegraf.Untyped)
require.NoError(t, err)
assert.False(t, ra.Add(m2)) assert.False(t, ra.Add(m2))
} }

View File

@ -36,44 +36,39 @@ func NewRunningInput(
} }
} }
// InputConfig containing a name, interval, and filter // InputConfig is the common config for all inputs.
type InputConfig struct { type InputConfig struct {
Name string Name string
Interval time.Duration
NameOverride string NameOverride string
MeasurementPrefix string MeasurementPrefix string
MeasurementSuffix string MeasurementSuffix string
Tags map[string]string Tags map[string]string
Filter Filter Filter Filter
Interval time.Duration
} }
func (r *RunningInput) Name() string { func (r *RunningInput) Name() string {
return "inputs." + r.Config.Name return "inputs." + r.Config.Name
} }
// MakeMetric either returns a metric, or returns nil if the metric doesn't func (r *RunningInput) MakeMetric(metric telegraf.Metric) telegraf.Metric {
// need to be created (because of filtering, an error, etc.) if ok := r.Config.Filter.Select(metric); !ok {
func (r *RunningInput) MakeMetric( return nil
measurement string, }
fields map[string]interface{},
tags map[string]string, r.Config.Filter.Modify(metric)
mType telegraf.ValueType, if len(metric.FieldList()) == 0 {
t time.Time, return nil
) telegraf.Metric { }
m := makemetric( m := makemetric(
measurement, metric,
fields,
tags,
r.Config.NameOverride, r.Config.NameOverride,
r.Config.MeasurementPrefix, r.Config.MeasurementPrefix,
r.Config.MeasurementSuffix, r.Config.MeasurementSuffix,
r.Config.Tags, r.Config.Tags,
r.defaultTags, r.defaultTags)
r.Config.Filter,
true,
mType,
t,
)
if r.trace && m != nil { if r.trace && m != nil {
s := influx.NewSerializer() s := influx.NewSerializer()

View File

@ -17,13 +17,13 @@ func TestMakeMetricNoFields(t *testing.T) {
Name: "TestRunningInput", Name: "TestRunningInput",
}) })
m := ri.MakeMetric( m, err := metric.New("RITest",
"RITest",
map[string]interface{}{},
map[string]string{}, map[string]string{},
telegraf.Untyped, map[string]interface{}{},
now, now,
) telegraf.Untyped)
m = ri.MakeMetric(m)
require.NoError(t, err)
assert.Nil(t, m) assert.Nil(t, m)
} }
@ -34,16 +34,16 @@ func TestMakeMetricNilFields(t *testing.T) {
Name: "TestRunningInput", Name: "TestRunningInput",
}) })
m := ri.MakeMetric( m, err := metric.New("RITest",
"RITest", map[string]string{},
map[string]interface{}{ map[string]interface{}{
"value": int(101), "value": int64(101),
"nil": nil, "nil": nil,
}, },
map[string]string{},
telegraf.Untyped,
now, now,
) telegraf.Untyped)
require.NoError(t, err)
m = ri.MakeMetric(m)
expected, err := metric.New("RITest", expected, err := metric.New("RITest",
map[string]string{}, map[string]string{},
@ -69,13 +69,15 @@ func TestMakeMetricWithPluginTags(t *testing.T) {
ri.SetTrace(true) ri.SetTrace(true)
assert.Equal(t, true, ri.Trace()) assert.Equal(t, true, ri.Trace())
m := ri.MakeMetric( m, err := metric.New("RITest",
"RITest", map[string]string{},
map[string]interface{}{"value": int(101)}, map[string]interface{}{
nil, "value": int64(101),
telegraf.Untyped, },
now, now,
) telegraf.Untyped)
require.NoError(t, err)
m = ri.MakeMetric(m)
expected, err := metric.New("RITest", expected, err := metric.New("RITest",
map[string]string{ map[string]string{
@ -104,13 +106,15 @@ func TestMakeMetricFilteredOut(t *testing.T) {
assert.Equal(t, true, ri.Trace()) assert.Equal(t, true, ri.Trace())
assert.NoError(t, ri.Config.Filter.Compile()) assert.NoError(t, ri.Config.Filter.Compile())
m := ri.MakeMetric( m, err := metric.New("RITest",
"RITest", map[string]string{},
map[string]interface{}{"value": int(101)}, map[string]interface{}{
nil, "value": int64(101),
telegraf.Untyped, },
now, now,
) telegraf.Untyped)
m = ri.MakeMetric(m)
require.NoError(t, err)
assert.Nil(t, m) assert.Nil(t, m)
} }
@ -126,13 +130,15 @@ func TestMakeMetricWithDaemonTags(t *testing.T) {
ri.SetTrace(true) ri.SetTrace(true)
assert.Equal(t, true, ri.Trace()) assert.Equal(t, true, ri.Trace())
m := ri.MakeMetric( m, err := metric.New("RITest",
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{}, map[string]string{},
telegraf.Untyped, map[string]interface{}{
"value": int64(101),
},
now, now,
) telegraf.Untyped)
require.NoError(t, err)
m = ri.MakeMetric(m)
expected, err := metric.New("RITest", expected, err := metric.New("RITest",
map[string]string{ map[string]string{
"foo": "bar", "foo": "bar",
@ -153,13 +159,15 @@ func TestMakeMetricNameOverride(t *testing.T) {
NameOverride: "foobar", NameOverride: "foobar",
}) })
m := ri.MakeMetric( m, err := metric.New("RITest",
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{}, map[string]string{},
telegraf.Untyped, map[string]interface{}{
"value": int64(101),
},
now, now,
) telegraf.Untyped)
require.NoError(t, err)
m = ri.MakeMetric(m)
expected, err := metric.New("foobar", expected, err := metric.New("foobar",
nil, nil,
map[string]interface{}{ map[string]interface{}{
@ -178,13 +186,15 @@ func TestMakeMetricNamePrefix(t *testing.T) {
MeasurementPrefix: "foobar_", MeasurementPrefix: "foobar_",
}) })
m := ri.MakeMetric( m, err := metric.New("RITest",
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{}, map[string]string{},
telegraf.Untyped, map[string]interface{}{
"value": int64(101),
},
now, now,
) telegraf.Untyped)
require.NoError(t, err)
m = ri.MakeMetric(m)
expected, err := metric.New("foobar_RITest", expected, err := metric.New("foobar_RITest",
nil, nil,
map[string]interface{}{ map[string]interface{}{
@ -203,13 +213,15 @@ func TestMakeMetricNameSuffix(t *testing.T) {
MeasurementSuffix: "_foobar", MeasurementSuffix: "_foobar",
}) })
m := ri.MakeMetric( m, err := metric.New("RITest",
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{}, map[string]string{},
telegraf.Untyped, map[string]interface{}{
"value": int64(101),
},
now, now,
) telegraf.Untyped)
require.NoError(t, err)
m = ri.MakeMetric(m)
expected, err := metric.New("RITest_foobar", expected, err := metric.New("RITest_foobar",
nil, nil,
map[string]interface{}{ map[string]interface{}{

View File

@ -7,7 +7,6 @@ import (
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal/buffer" "github.com/influxdata/telegraf/internal/buffer"
"github.com/influxdata/telegraf/metric"
"github.com/influxdata/telegraf/selfstat" "github.com/influxdata/telegraf/selfstat"
) )
@ -42,6 +41,12 @@ type RunningOutput struct {
writeMutex sync.Mutex writeMutex sync.Mutex
} }
// OutputConfig containing name and filter
type OutputConfig struct {
Name string
Filter Filter
}
func NewRunningOutput( func NewRunningOutput(
name string, name string,
output telegraf.Output, output telegraf.Output,
@ -95,36 +100,25 @@ func NewRunningOutput(
// AddMetric adds a metric to the output. This function can also write cached // AddMetric adds a metric to the output. This function can also write cached
// points if FlushBufferWhenFull is true. // points if FlushBufferWhenFull is true.
func (ro *RunningOutput) AddMetric(m telegraf.Metric) { func (ro *RunningOutput) AddMetric(metric telegraf.Metric) {
if ok := ro.Config.Filter.Select(metric); !ok {
if m == nil {
return
}
// Filter any tagexclude/taginclude parameters before adding metric
if ro.Config.Filter.IsActive() {
// In order to filter out tags, we need to create a new metric, since
// metrics are immutable once created.
name := m.Name()
tags := m.Tags()
fields := m.Fields()
t := m.Time()
tp := m.Type()
if ok := ro.Config.Filter.Apply(name, fields, tags); !ok {
ro.MetricsFiltered.Incr(1) ro.MetricsFiltered.Incr(1)
return return
} }
// error is not possible if creating from another metric, so ignore.
m, _ = metric.New(name, tags, fields, t, tp) ro.Config.Filter.Modify(metric)
if len(metric.FieldList()) == 0 {
return
} }
if output, ok := ro.Output.(telegraf.AggregatingOutput); ok { if output, ok := ro.Output.(telegraf.AggregatingOutput); ok {
ro.aggMutex.Lock() ro.aggMutex.Lock()
output.Add(m) output.Add(metric)
ro.aggMutex.Unlock() ro.aggMutex.Unlock()
return return
} }
ro.metrics.Add(m) ro.metrics.Add(metric)
if ro.metrics.Len() == ro.MetricBatchSize { if ro.metrics.Len() == ro.MetricBatchSize {
batch := ro.metrics.Batch(ro.MetricBatchSize) batch := ro.metrics.Batch(ro.MetricBatchSize)
err := ro.write(batch) err := ro.write(batch)
@ -206,9 +200,3 @@ func (ro *RunningOutput) write(metrics []telegraf.Metric) error {
} }
return err return err
} }
// OutputConfig containing name and filter
type OutputConfig struct {
Name string
Filter Filter
}

View File

@ -75,23 +75,6 @@ func BenchmarkRunningOutputAddFailWrites(b *testing.B) {
} }
} }
func TestAddingNilMetric(t *testing.T) {
conf := &OutputConfig{
Filter: Filter{},
}
m := &mockOutput{}
ro := NewRunningOutput("test", m, conf, 1000, 10000)
ro.AddMetric(nil)
ro.AddMetric(nil)
ro.AddMetric(nil)
err := ro.Write()
assert.NoError(t, err)
assert.Len(t, m.Metrics(), 0)
}
// Test that NameDrop filters ger properly applied. // Test that NameDrop filters ger properly applied.
func TestRunningOutput_DropFilter(t *testing.T) { func TestRunningOutput_DropFilter(t *testing.T) {
conf := &OutputConfig{ conf := &OutputConfig{

View File

@ -34,14 +34,18 @@ func (rp *RunningProcessor) Apply(in ...telegraf.Metric) []telegraf.Metric {
ret := []telegraf.Metric{} ret := []telegraf.Metric{}
for _, metric := range in { for _, metric := range in {
if rp.Config.Filter.IsActive() { // In processors when a filter selects a metric it is sent through the
// check if the filter should be applied to this metric // processor. Otherwise the metric continues downstream unmodified.
if ok := rp.Config.Filter.Apply(metric.Name(), metric.Fields(), metric.Tags()); !ok { if ok := rp.Config.Filter.Select(metric); !ok {
// this means filter should not be applied
ret = append(ret, metric) ret = append(ret, metric)
continue continue
} }
rp.Config.Filter.Modify(metric)
if len(metric.FieldList()) == 0 {
continue
} }
// This metric should pass through the filter, so call the filter Apply // This metric should pass through the filter, so call the filter Apply
// function and append results to the output slice. // function and append results to the output slice.
ret = append(ret, rp.Processor.Apply(metric)...) ret = append(ret, rp.Processor.Apply(metric)...)

View File

@ -1,117 +1,203 @@
package models package models
import ( import (
"sort"
"testing" "testing"
"time"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/testutil" "github.com/influxdata/telegraf/metric"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/require"
) )
type TestProcessor struct { // MockProcessor is a Processor with an overrideable Apply implementation.
type MockProcessor struct {
ApplyF func(in ...telegraf.Metric) []telegraf.Metric
} }
func (f *TestProcessor) SampleConfig() string { return "" } func (p *MockProcessor) SampleConfig() string {
func (f *TestProcessor) Description() string { return "" } return ""
}
// Apply renames: func (p *MockProcessor) Description() string {
// "foo" to "fuz" return ""
// "bar" to "baz" }
// And it also drops measurements named "dropme"
func (f *TestProcessor) Apply(in ...telegraf.Metric) []telegraf.Metric { func (p *MockProcessor) Apply(in ...telegraf.Metric) []telegraf.Metric {
out := make([]telegraf.Metric, 0) return p.ApplyF(in...)
}
// TagProcessor returns a Processor whose Apply function adds the tag and
// value.
func TagProcessor(key, value string) *MockProcessor {
return &MockProcessor{
ApplyF: func(in ...telegraf.Metric) []telegraf.Metric {
for _, m := range in { for _, m := range in {
switch m.Name() { m.AddTag(key, value)
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 in
},
} }
return out
} }
func NewTestRunningProcessor() *RunningProcessor { func Metric(
out := &RunningProcessor{ name string,
Name: "test", tags map[string]string,
Processor: &TestProcessor{}, fields map[string]interface{},
Config: &ProcessorConfig{Filter: Filter{}}, tm time.Time,
tp ...telegraf.ValueType,
) telegraf.Metric {
m, err := metric.New(name, tags, fields, tm, tp...)
if err != nil {
panic(err)
} }
return out return m
} }
func TestRunningProcessor(t *testing.T) { func TestRunningProcessor_Apply(t *testing.T) {
inmetrics := []telegraf.Metric{ type args struct {
testutil.TestMetric(1, "foo"), Processor telegraf.Processor
testutil.TestMetric(1, "bar"), Config *ProcessorConfig
testutil.TestMetric(1, "baz"),
} }
expectedNames := []string{ tests := []struct {
"fuz", name string
"baz", args args
"baz", input []telegraf.Metric
expected []telegraf.Metric
}{
{
name: "inactive filter applies metrics",
args: args{
Processor: TagProcessor("apply", "true"),
Config: &ProcessorConfig{
Filter: Filter{},
},
},
input: []telegraf.Metric{
Metric(
"cpu",
map[string]string{},
map[string]interface{}{
"value": 42.0,
},
time.Unix(0, 0),
),
},
expected: []telegraf.Metric{
Metric(
"cpu",
map[string]string{
"apply": "true",
},
map[string]interface{}{
"value": 42.0,
},
time.Unix(0, 0),
),
},
},
{
name: "filter applies",
args: args{
Processor: TagProcessor("apply", "true"),
Config: &ProcessorConfig{
Filter: Filter{
NamePass: []string{"cpu"},
},
},
},
input: []telegraf.Metric{
Metric(
"cpu",
map[string]string{},
map[string]interface{}{
"value": 42.0,
},
time.Unix(0, 0),
),
},
expected: []telegraf.Metric{
Metric(
"cpu",
map[string]string{
"apply": "true",
},
map[string]interface{}{
"value": 42.0,
},
time.Unix(0, 0),
),
},
},
{
name: "filter doesn't apply",
args: args{
Processor: TagProcessor("apply", "true"),
Config: &ProcessorConfig{
Filter: Filter{
NameDrop: []string{"cpu"},
},
},
},
input: []telegraf.Metric{
Metric(
"cpu",
map[string]string{},
map[string]interface{}{
"value": 42.0,
},
time.Unix(0, 0),
),
},
expected: []telegraf.Metric{
Metric(
"cpu",
map[string]string{},
map[string]interface{}{
"value": 42.0,
},
time.Unix(0, 0),
),
},
},
} }
rfp := NewTestRunningProcessor()
filteredMetrics := rfp.Apply(inmetrics...)
actualNames := []string{ for _, tt := range tests {
filteredMetrics[0].Name(), t.Run(tt.name, func(t *testing.T) {
filteredMetrics[1].Name(), rp := &RunningProcessor{
filteredMetrics[2].Name(), Processor: tt.args.Processor,
Config: tt.args.Config,
}
rp.Config.Filter.Compile()
actual := rp.Apply(tt.input...)
require.Equal(t, tt.expected, actual)
})
} }
assert.Equal(t, expectedNames, actualNames)
} }
func TestRunningProcessor_WithNameDrop(t *testing.T) { func TestRunningProcessor_Order(t *testing.T) {
inmetrics := []telegraf.Metric{ rp1 := &RunningProcessor{
testutil.TestMetric(1, "foo"), Config: &ProcessorConfig{
testutil.TestMetric(1, "bar"), Order: 1,
testutil.TestMetric(1, "baz"), },
}
rp2 := &RunningProcessor{
Config: &ProcessorConfig{
Order: 2,
},
}
rp3 := &RunningProcessor{
Config: &ProcessorConfig{
Order: 3,
},
} }
expectedNames := []string{ procs := RunningProcessors{rp2, rp3, rp1}
"foo", sort.Sort(procs)
"baz", require.Equal(t,
"baz", RunningProcessors{rp1, rp2, rp3},
} procs)
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)
} }