Merge branch 'master' into dn-particle-plugin
This commit is contained in:
commit
43b8f19dce
|
@ -5,3 +5,4 @@ tivan
|
||||||
.idea
|
.idea
|
||||||
*~
|
*~
|
||||||
*#
|
*#
|
||||||
|
.DS_Store
|
||||||
|
|
53
CHANGELOG.md
53
CHANGELOG.md
|
@ -1,9 +1,12 @@
|
||||||
## v1.5 [unreleased]
|
## v1.5 [unreleased]
|
||||||
|
|
||||||
### New Plugins
|
### New Plugins
|
||||||
- [nginx_plus](./plugins/inputs/nginx_plus/README.md) - Thanks to @mplonka & @poblahblahblah
|
- [basicstats](./plugins/aggregators/basicstats/README.md) - Thanks to @toni-moreno
|
||||||
- [jolokia2](./plugins/inputs/jolokia2/README.md) - Thanks to @dylanmei
|
- [jolokia2](./plugins/inputs/jolokia2/README.md) - Thanks to @dylanmei
|
||||||
- [wavefront](./plugins/inputswavefront/README.md) - Thanks to @puckpuck
|
- [nginx_plus](./plugins/inputs/nginx_plus/README.md) - Thanks to @mplonka & @poblahblahblah
|
||||||
|
- [smart](./plugins/inputs/smart/README.md) - Thanks to @rickard-von-essen
|
||||||
|
- [teamspeak](./plugins/inputs/teamspeak/README.md) - Thanks to @p4ddy1
|
||||||
|
- [wavefront](./plugins/outputs/wavefront/README.md) - Thanks to @puckpuck
|
||||||
|
|
||||||
### Release Notes
|
### Release Notes
|
||||||
|
|
||||||
|
@ -21,11 +24,9 @@
|
||||||
- [#3170](https://github.com/influxdata/telegraf/pull/3170): Add support for sharding based on metric name.
|
- [#3170](https://github.com/influxdata/telegraf/pull/3170): Add support for sharding based on metric name.
|
||||||
- [#3196](https://github.com/influxdata/telegraf/pull/3196): Add Kafka output plugin topic_suffix option.
|
- [#3196](https://github.com/influxdata/telegraf/pull/3196): Add Kafka output plugin topic_suffix option.
|
||||||
- [#3027](https://github.com/influxdata/telegraf/pull/3027): Include mount mode option in disk metrics.
|
- [#3027](https://github.com/influxdata/telegraf/pull/3027): Include mount mode option in disk metrics.
|
||||||
- [#3212](https://github.com/influxdata/telegraf/pull/3212): Add support for standard proxy env vars in outputs.
|
|
||||||
- [#3191](https://github.com/influxdata/telegraf/pull/3191): TLS and MTLS enhancements to HTTPListener input plugin.
|
- [#3191](https://github.com/influxdata/telegraf/pull/3191): TLS and MTLS enhancements to HTTPListener input plugin.
|
||||||
- [#3213](https://github.com/influxdata/telegraf/pull/3213): Add polling method to logparser and tail inputs.
|
- [#3213](https://github.com/influxdata/telegraf/pull/3213): Add polling method to logparser and tail inputs.
|
||||||
- [#3211](https://github.com/influxdata/telegraf/pull/3211): Add timeout option for kubernetes input.
|
- [#3211](https://github.com/influxdata/telegraf/pull/3211): Add timeout option for kubernetes input.
|
||||||
- [#3224](https://github.com/influxdata/telegraf/pull/3224): Preserve url path prefix in influx output.
|
|
||||||
- [#3234](https://github.com/influxdata/telegraf/pull/3234): Add support for timing sums in statsd input.
|
- [#3234](https://github.com/influxdata/telegraf/pull/3234): Add support for timing sums in statsd input.
|
||||||
- [#2617](https://github.com/influxdata/telegraf/issues/2617): Add resource limit monitoring to procstat.
|
- [#2617](https://github.com/influxdata/telegraf/issues/2617): Add resource limit monitoring to procstat.
|
||||||
- [#3236](https://github.com/influxdata/telegraf/pull/3236): Add support for k8s service DNS discovery to prometheus input.
|
- [#3236](https://github.com/influxdata/telegraf/pull/3236): Add support for k8s service DNS discovery to prometheus input.
|
||||||
|
@ -36,13 +37,50 @@
|
||||||
- [#3106](https://github.com/influxdata/telegraf/pull/3106): Add configurable separator for metrics and fields in opentsdb output.
|
- [#3106](https://github.com/influxdata/telegraf/pull/3106): Add configurable separator for metrics and fields in opentsdb output.
|
||||||
- [#1692](https://github.com/influxdata/telegraf/pull/1692): Add support for the rollbar occurrence webhook event.
|
- [#1692](https://github.com/influxdata/telegraf/pull/1692): Add support for the rollbar occurrence webhook event.
|
||||||
- [#3160](https://github.com/influxdata/telegraf/pull/3160): Add Wavefront output plugin.
|
- [#3160](https://github.com/influxdata/telegraf/pull/3160): Add Wavefront output plugin.
|
||||||
|
- [#3281](https://github.com/influxdata/telegraf/pull/3281): Add extra wired tiger cache metrics to mongodb input.
|
||||||
|
- [#3141](https://github.com/influxdata/telegraf/pull/3141): Collect Docker Swarm service metrics in docker input plugin.
|
||||||
|
- [#2449](https://github.com/influxdata/telegraf/pull/2449): Add smart input plugin for collecting S.M.A.R.T. data.
|
||||||
|
- [#3269](https://github.com/influxdata/telegraf/pull/3269): Add cluster health level configuration to elasticsearch input.
|
||||||
|
- [#3304](https://github.com/influxdata/telegraf/pull/3304): Add ability to limit node stats in elasticsearch input.
|
||||||
|
- [#2167](https://github.com/influxdata/telegraf/pull/2167): Add new basicstats aggregator.
|
||||||
|
- [#3344](https://github.com/influxdata/telegraf/pull/3344): Add UDP IPv6 support to statsd input.
|
||||||
|
- [#3350](https://github.com/influxdata/telegraf/pull/3350): Use labels in prometheus output for string fields.
|
||||||
|
- [#3358](https://github.com/influxdata/telegraf/pull/3358): Add support for decimal timestamps to ts-epoch modifier.
|
||||||
|
- [#3337](https://github.com/influxdata/telegraf/pull/3337): Add histogram and summary types and use in prometheus plugins.
|
||||||
|
- [#3365](https://github.com/influxdata/telegraf/pull/3365): Gather concurrently from snmp agents.
|
||||||
|
- [#3333](https://github.com/influxdata/telegraf/issues/3333): Perform DNS lookup before ping and report result.
|
||||||
|
- [#3398](https://github.com/influxdata/telegraf/issues/3398): Add instance name option to varnish plugin.
|
||||||
|
- [#3406](https://github.com/influxdata/telegraf/pull/3406): Add support for SSL settings to ElasticSearch output plugin.
|
||||||
|
- [#3315](https://github.com/influxdata/telegraf/pull/3315): Add Teamspeak 3 input plugin.
|
||||||
|
|
||||||
### Bugfixes
|
### Bugfixes
|
||||||
|
|
||||||
- [#3136](https://github.com/influxdata/telegraf/issues/3136): Fix webhooks input address in use during reload.
|
- [#3136](https://github.com/influxdata/telegraf/issues/3136): Fix webhooks input address in use during reload.
|
||||||
- [#3258](https://github.com/influxdata/telegraf/issues/3258): Unlock Statsd when stopping to prevent deadlock.
|
- [#3258](https://github.com/influxdata/telegraf/issues/3258): Unlock Statsd when stopping to prevent deadlock.
|
||||||
|
- [#3319](https://github.com/influxdata/telegraf/issues/3319): Fix cloudwatch output requires unneeded permissions.
|
||||||
|
- [#3351](https://github.com/influxdata/telegraf/issues/3351): Fix prometheus passthrough for existing value types.
|
||||||
|
|
||||||
## v1.4.2 [unreleased]
|
## v1.4.4 [unreleased]
|
||||||
|
|
||||||
|
- [#3401](https://github.com/influxdata/telegraf/pull/3401): Use schema specified in mqtt_consumer input.
|
||||||
|
|
||||||
|
## v1.4.3 [2017-10-25]
|
||||||
|
|
||||||
|
### Bugfixes
|
||||||
|
|
||||||
|
- [#3327](https://github.com/influxdata/telegraf/issues/3327): Fix container name filters in docker input.
|
||||||
|
- [#3321](https://github.com/influxdata/telegraf/issues/3321): Fix snmpwalk address format in leofs input.
|
||||||
|
- [#3329](https://github.com/influxdata/telegraf/issues/3329): Fix case sensitivity issue in sqlserver query.
|
||||||
|
- [#3342](https://github.com/influxdata/telegraf/pull/3342): Fix CPU input plugin stuck after suspend on Linux.
|
||||||
|
- [#3013](https://github.com/influxdata/telegraf/issues/3013): Fix mongodb input panic when restarting mongodb.
|
||||||
|
- [#3224](https://github.com/influxdata/telegraf/pull/3224): Preserve url path prefix in influx output.
|
||||||
|
- [#3354](https://github.com/influxdata/telegraf/pull/3354): Fix TELEGRAF_OPTS expansion in systemd service unit.
|
||||||
|
- [#3357](https://github.com/influxdata/telegraf/issues/3357): Remove warning when JSON contains null value.
|
||||||
|
- [#3375](https://github.com/influxdata/telegraf/issues/3375): Fix ACL token usage in consul input plugin.
|
||||||
|
- [#3369](https://github.com/influxdata/telegraf/issues/3369): Fix unquoting error with Tomcat 6.
|
||||||
|
- [#3373](https://github.com/influxdata/telegraf/issues/3373): Fix syscall panic in diskio on some Linux systems.
|
||||||
|
|
||||||
|
## v1.4.2 [2017-10-10]
|
||||||
|
|
||||||
### Bugfixes
|
### Bugfixes
|
||||||
|
|
||||||
|
@ -50,6 +88,11 @@
|
||||||
- [#3265](https://github.com/influxdata/telegraf/issues/3265): Fix parsing of JSON with a UTF8 BOM in httpjson.
|
- [#3265](https://github.com/influxdata/telegraf/issues/3265): Fix parsing of JSON with a UTF8 BOM in httpjson.
|
||||||
- [#2887](https://github.com/influxdata/telegraf/issues/2887): Allow JSON data format to contain zero metrics.
|
- [#2887](https://github.com/influxdata/telegraf/issues/2887): Allow JSON data format to contain zero metrics.
|
||||||
- [#3284](https://github.com/influxdata/telegraf/issues/3284): Fix format of connection_timeout in mqtt_consumer.
|
- [#3284](https://github.com/influxdata/telegraf/issues/3284): Fix format of connection_timeout in mqtt_consumer.
|
||||||
|
- [#3081](https://github.com/influxdata/telegraf/issues/3081): Fix case sensitivity error in sqlserver input.
|
||||||
|
- [#3297](https://github.com/influxdata/telegraf/issues/3297): Add support for proxy environment variables to http_response.
|
||||||
|
- [#1588](https://github.com/influxdata/telegraf/issues/1588): Add support for standard proxy env vars in outputs.
|
||||||
|
- [#3282](https://github.com/influxdata/telegraf/issues/3282): Fix panic in cpu input if number of cpus changes.
|
||||||
|
- [#2854](https://github.com/influxdata/telegraf/issues/2854): Use chunked transfer encoding in InfluxDB output.
|
||||||
|
|
||||||
## v1.4.1 [2017-09-26]
|
## v1.4.1 [2017-09-26]
|
||||||
|
|
||||||
|
|
2
Godeps
2
Godeps
|
@ -40,6 +40,8 @@ github.com/kballard/go-shellquote d8ec1a69a250a17bb0e419c386eac1f3711dc142
|
||||||
github.com/matttproud/golang_protobuf_extensions c12348ce28de40eed0136aa2b644d0ee0650e56c
|
github.com/matttproud/golang_protobuf_extensions c12348ce28de40eed0136aa2b644d0ee0650e56c
|
||||||
github.com/Microsoft/go-winio ce2922f643c8fd76b46cadc7f404a06282678b34
|
github.com/Microsoft/go-winio ce2922f643c8fd76b46cadc7f404a06282678b34
|
||||||
github.com/miekg/dns 99f84ae56e75126dd77e5de4fae2ea034a468ca1
|
github.com/miekg/dns 99f84ae56e75126dd77e5de4fae2ea034a468ca1
|
||||||
|
github.com/mitchellh/mapstructure d0303fe809921458f417bcf828397a65db30a7e4
|
||||||
|
github.com/multiplay/go-ts3 07477f49b8dfa3ada231afc7b7b17617d42afe8e
|
||||||
github.com/naoina/go-stringutil 6b638e95a32d0c1131db0e7fe83775cbea4a0d0b
|
github.com/naoina/go-stringutil 6b638e95a32d0c1131db0e7fe83775cbea4a0d0b
|
||||||
github.com/nats-io/go-nats ea9585611a4ab58a205b9b125ebd74c389a6b898
|
github.com/nats-io/go-nats ea9585611a4ab58a205b9b125ebd74c389a6b898
|
||||||
github.com/nats-io/nats ea9585611a4ab58a205b9b125ebd74c389a6b898
|
github.com/nats-io/nats ea9585611a4ab58a205b9b125ebd74c389a6b898
|
||||||
|
|
|
@ -5,8 +5,7 @@ and writing metrics.
|
||||||
|
|
||||||
Design goals are to have a minimal memory footprint with a plugin system so
|
Design goals are to have a minimal memory footprint with a plugin system so
|
||||||
that developers in the community can easily add support for collecting metrics
|
that developers in the community can easily add support for collecting metrics
|
||||||
from well known services (like Hadoop, Postgres, or Redis) and third party
|
from local or remote services.
|
||||||
APIs (like Mailchimp, AWS CloudWatch, or Google Analytics).
|
|
||||||
|
|
||||||
Telegraf is plugin-driven and has the concept of 4 distinct plugins:
|
Telegraf is plugin-driven and has the concept of 4 distinct plugins:
|
||||||
|
|
||||||
|
@ -193,9 +192,11 @@ configuration options.
|
||||||
* [riak](./plugins/inputs/riak)
|
* [riak](./plugins/inputs/riak)
|
||||||
* [salesforce](./plugins/inputs/salesforce)
|
* [salesforce](./plugins/inputs/salesforce)
|
||||||
* [sensors](./plugins/inputs/sensors)
|
* [sensors](./plugins/inputs/sensors)
|
||||||
|
* [smart](./plugins/inputs/smart)
|
||||||
* [snmp](./plugins/inputs/snmp)
|
* [snmp](./plugins/inputs/snmp)
|
||||||
* [snmp_legacy](./plugins/inputs/snmp_legacy)
|
* [snmp_legacy](./plugins/inputs/snmp_legacy)
|
||||||
* [sql server](./plugins/inputs/sqlserver) (microsoft)
|
* [sql server](./plugins/inputs/sqlserver) (microsoft)
|
||||||
|
* [teamspeak](./plugins/inputs/teamspeak)
|
||||||
* [tomcat](./plugins/inputs/tomcat)
|
* [tomcat](./plugins/inputs/tomcat)
|
||||||
* [twemproxy](./plugins/inputs/twemproxy)
|
* [twemproxy](./plugins/inputs/twemproxy)
|
||||||
* [varnish](./plugins/inputs/varnish)
|
* [varnish](./plugins/inputs/varnish)
|
||||||
|
@ -254,6 +255,7 @@ formats may be used with input plugins supporting the `data_format` option:
|
||||||
|
|
||||||
## Aggregator Plugins
|
## Aggregator Plugins
|
||||||
|
|
||||||
|
* [basicstats](./plugins/aggregators/basicstats)
|
||||||
* [minmax](./plugins/aggregators/minmax)
|
* [minmax](./plugins/aggregators/minmax)
|
||||||
* [histogram](./plugins/aggregators/histogram)
|
* [histogram](./plugins/aggregators/histogram)
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,18 @@ type Accumulator interface {
|
||||||
tags map[string]string,
|
tags map[string]string,
|
||||||
t ...time.Time)
|
t ...time.Time)
|
||||||
|
|
||||||
|
// AddSummary is the same as AddFields, but will add the metric as a "Summary" type
|
||||||
|
AddSummary(measurement string,
|
||||||
|
fields map[string]interface{},
|
||||||
|
tags map[string]string,
|
||||||
|
t ...time.Time)
|
||||||
|
|
||||||
|
// AddHistogram is the same as AddFields, but will add the metric as a "Histogram" type
|
||||||
|
AddHistogram(measurement string,
|
||||||
|
fields map[string]interface{},
|
||||||
|
tags map[string]string,
|
||||||
|
t ...time.Time)
|
||||||
|
|
||||||
SetPrecision(precision, interval time.Duration)
|
SetPrecision(precision, interval time.Duration)
|
||||||
|
|
||||||
AddError(err error)
|
AddError(err error)
|
||||||
|
|
|
@ -76,6 +76,28 @@ func (ac *accumulator) AddCounter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ac *accumulator) AddSummary(
|
||||||
|
measurement string,
|
||||||
|
fields map[string]interface{},
|
||||||
|
tags map[string]string,
|
||||||
|
t ...time.Time,
|
||||||
|
) {
|
||||||
|
if m := ac.maker.MakeMetric(measurement, fields, tags, telegraf.Summary, ac.getTime(t)); m != nil {
|
||||||
|
ac.metrics <- m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *accumulator) AddHistogram(
|
||||||
|
measurement string,
|
||||||
|
fields map[string]interface{},
|
||||||
|
tags map[string]string,
|
||||||
|
t ...time.Time,
|
||||||
|
) {
|
||||||
|
if m := ac.maker.MakeMetric(measurement, fields, tags, telegraf.Histogram, ac.getTime(t)); m != nil {
|
||||||
|
ac.metrics <- 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) {
|
||||||
|
|
|
@ -252,7 +252,7 @@ func (a *Agent) flusher(shutdown chan struct{}, metricC chan telegraf.Metric, ag
|
||||||
// the flusher will flush after metrics are collected.
|
// the flusher will flush after metrics are collected.
|
||||||
time.Sleep(time.Millisecond * 300)
|
time.Sleep(time.Millisecond * 300)
|
||||||
|
|
||||||
// create an output metric channel and a gorouting that continously passes
|
// create an output metric channel and a gorouting that continuously passes
|
||||||
// each metric onto the output plugins & aggregators.
|
// each metric onto the output plugins & aggregators.
|
||||||
outMetricC := make(chan telegraf.Metric, 100)
|
outMetricC := make(chan telegraf.Metric, 100)
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
|
@ -6,8 +6,8 @@ machine:
|
||||||
- rabbitmq-server
|
- rabbitmq-server
|
||||||
post:
|
post:
|
||||||
- sudo rm -rf /usr/local/go
|
- sudo rm -rf /usr/local/go
|
||||||
- wget https://storage.googleapis.com/golang/go1.9.linux-amd64.tar.gz
|
- wget https://storage.googleapis.com/golang/go1.9.1.linux-amd64.tar.gz
|
||||||
- sudo tar -C /usr/local -xzf go1.9.linux-amd64.tar.gz
|
- sudo tar -C /usr/local -xzf go1.9.1.linux-amd64.tar.gz
|
||||||
- go version
|
- go version
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
|
@ -55,9 +55,6 @@ var fUsage = flag.String("usage", "",
|
||||||
var fService = flag.String("service", "",
|
var fService = flag.String("service", "",
|
||||||
"operate on the service")
|
"operate on the service")
|
||||||
|
|
||||||
// Telegraf version, populated linker.
|
|
||||||
// ie, -ldflags "-X main.version=`git describe --always --tags`"
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
nextVersion = "1.5.0"
|
nextVersion = "1.5.0"
|
||||||
version string
|
version string
|
||||||
|
|
|
@ -39,6 +39,11 @@ metrics as they pass through Telegraf:
|
||||||
|
|
||||||
Both Aggregators and Processors analyze metrics as they pass through Telegraf.
|
Both Aggregators and Processors analyze metrics as they pass through Telegraf.
|
||||||
|
|
||||||
|
Use [measurement filtering](CONFIGURATION.md#measurement-filtering)
|
||||||
|
to control which metrics are passed through a processor or aggregator. If a
|
||||||
|
metric is filtered out the metric bypasses the plugin and is passed downstream
|
||||||
|
to the next plugin.
|
||||||
|
|
||||||
**Processor** plugins process metrics as they pass through and immediately emit
|
**Processor** plugins process metrics as they pass through and immediately emit
|
||||||
results based on the values they process. For example, this could be printing
|
results based on the values they process. For example, this could be printing
|
||||||
all metrics or adding a tag to all metrics that pass through.
|
all metrics or adding a tag to all metrics that pass through.
|
||||||
|
|
|
@ -24,6 +24,9 @@ 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)
|
||||||
|
|
||||||
|
When using the `.deb` or `.rpm` packages, you can define environment variables
|
||||||
|
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
|
||||||
|
@ -95,9 +98,13 @@ 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
|
||||||
|
limit what metrics are emitted from the input plugin.
|
||||||
|
|
||||||
## Output Configuration
|
## Output Configuration
|
||||||
|
|
||||||
There are no generic configuration options available for all outputs.
|
The [measurement filtering](#measurement-filtering) parameters can be used to
|
||||||
|
limit what metrics are emitted from the output plugin.
|
||||||
|
|
||||||
## Aggregator Configuration
|
## Aggregator Configuration
|
||||||
|
|
||||||
|
@ -118,6 +125,10 @@ 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 be used to
|
||||||
|
limit what metrics are handled by the aggregator. Excluded metrics are passed
|
||||||
|
downstream to the next aggregator.
|
||||||
|
|
||||||
## Processor Configuration
|
## Processor Configuration
|
||||||
|
|
||||||
The following config parameters are available for all processors:
|
The following config parameters are available for all processors:
|
||||||
|
@ -125,6 +136,10 @@ 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) can parameters may be used
|
||||||
|
to limit what metrics are handled by the processor. Excluded metrics are
|
||||||
|
passed downstream to the next processor.
|
||||||
|
|
||||||
#### Measurement Filtering
|
#### Measurement Filtering
|
||||||
|
|
||||||
Filters can be configured per input, output, processor, or aggregator,
|
Filters can be configured per input, output, processor, or aggregator,
|
||||||
|
@ -374,3 +389,15 @@ to the system load metrics due to the `namepass` parameter.
|
||||||
[[outputs.file]]
|
[[outputs.file]]
|
||||||
files = ["stdout"]
|
files = ["stdout"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Processor Configuration Examples:
|
||||||
|
|
||||||
|
Print only the metrics with `cpu` as the measurement name, all metrics are
|
||||||
|
passed to the output:
|
||||||
|
```toml
|
||||||
|
[[processors.printer]]
|
||||||
|
namepass = "cpu"
|
||||||
|
|
||||||
|
[[outputs.file]]
|
||||||
|
files = ["/tmp/metrics.out"]
|
||||||
|
```
|
||||||
|
|
|
@ -20,3 +20,8 @@ If running as a service add the environment variable to `/etc/default/telegraf`:
|
||||||
```
|
```
|
||||||
GODEBUG=netdns=cgo
|
GODEBUG=netdns=cgo
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Q: When will the next version be released?
|
||||||
|
|
||||||
|
The latest release date estimate can be viewed on the
|
||||||
|
[milestones](https://github.com/influxdata/telegraf/milestones) page.
|
||||||
|
|
|
@ -82,6 +82,8 @@ following works:
|
||||||
- github.com/streadway/amqp [BSD](https://github.com/streadway/amqp/blob/master/LICENSE)
|
- github.com/streadway/amqp [BSD](https://github.com/streadway/amqp/blob/master/LICENSE)
|
||||||
- github.com/stretchr/objx [MIT](https://github.com/stretchr/objx/blob/master/LICENSE.md)
|
- github.com/stretchr/objx [MIT](https://github.com/stretchr/objx/blob/master/LICENSE.md)
|
||||||
- github.com/stretchr/testify [MIT](https://github.com/stretchr/testify/blob/master/LICENCE.txt)
|
- github.com/stretchr/testify [MIT](https://github.com/stretchr/testify/blob/master/LICENCE.txt)
|
||||||
|
- github.com/mitchellh/mapstructure [MIT](https://github.com/mitchellh/mapstructure/blob/master/LICENSE)
|
||||||
|
- github.com/multiplay/go-ts3 [BSD](https://github.com/multiplay/go-ts3/blob/master/LICENSE)
|
||||||
- github.com/vjeantet/grok [APACHE](https://github.com/vjeantet/grok/blob/master/LICENSE)
|
- github.com/vjeantet/grok [APACHE](https://github.com/vjeantet/grok/blob/master/LICENSE)
|
||||||
- github.com/wvanbergen/kafka [MIT](https://github.com/wvanbergen/kafka/blob/master/LICENSE)
|
- github.com/wvanbergen/kafka [MIT](https://github.com/wvanbergen/kafka/blob/master/LICENSE)
|
||||||
- github.com/wvanbergen/kazoo-go [MIT](https://github.com/wvanbergen/kazoo-go/blob/master/MIT-LICENSE)
|
- github.com/wvanbergen/kazoo-go [MIT](https://github.com/wvanbergen/kazoo-go/blob/master/MIT-LICENSE)
|
||||||
|
|
|
@ -38,7 +38,7 @@ Telegraf can manage its own service through the --service flag:
|
||||||
| `telegraf.exe --service stop` | Stop the telegraf service |
|
| `telegraf.exe --service stop` | Stop the telegraf service |
|
||||||
|
|
||||||
|
|
||||||
Trobleshooting common error #1067
|
Troubleshooting common error #1067
|
||||||
|
|
||||||
When installing as service in Windows, always double check to specify full path of the config file, otherwise windows service will fail to start
|
When installing as service in Windows, always double check to specify full path of the config file, otherwise windows service will fail to start
|
||||||
|
|
||||||
|
|
|
@ -1586,8 +1586,8 @@
|
||||||
# # Read metrics from a LeoFS Server via SNMP
|
# # Read metrics from a LeoFS Server via SNMP
|
||||||
# [[inputs.leofs]]
|
# [[inputs.leofs]]
|
||||||
# ## An array of URLs of the form:
|
# ## An array of URLs of the form:
|
||||||
# ## "udp://" host [ ":" port]
|
# ## host [ ":" port]
|
||||||
# servers = ["udp://127.0.0.1:4020"]
|
# servers = ["127.0.0.1:4020"]
|
||||||
|
|
||||||
|
|
||||||
# # Provides Linux sysctl fs metrics
|
# # Provides Linux sysctl fs metrics
|
||||||
|
|
|
@ -77,3 +77,40 @@ func compileFilterNoGlob(filters []string) Filter {
|
||||||
}
|
}
|
||||||
return &out
|
return &out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IncludeExcludeFilter struct {
|
||||||
|
include Filter
|
||||||
|
exclude Filter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIncludeExcludeFilter(
|
||||||
|
include []string,
|
||||||
|
exclude []string,
|
||||||
|
) (Filter, error) {
|
||||||
|
in, err := Compile(include)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ex, err := Compile(exclude)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &IncludeExcludeFilter{in, ex}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *IncludeExcludeFilter) Match(s string) bool {
|
||||||
|
if f.include != nil {
|
||||||
|
if !f.include.Match(s) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.exclude != nil {
|
||||||
|
if f.exclude.Match(s) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
|
@ -126,7 +126,7 @@ type AgentConfig struct {
|
||||||
|
|
||||||
// TODO(cam): Remove UTC and parameter, they are no longer
|
// TODO(cam): Remove UTC and parameter, they are no longer
|
||||||
// valid for the agent config. Leaving them here for now for backwards-
|
// valid for the agent config. Leaving them here for now for backwards-
|
||||||
// compatability
|
// compatibility
|
||||||
UTC bool `toml:"utc"`
|
UTC bool `toml:"utc"`
|
||||||
|
|
||||||
// Debug is the option for running in debug mode
|
// Debug is the option for running in debug mode
|
||||||
|
@ -683,7 +683,7 @@ func (c *Config) LoadConfig(path string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// trimBOM trims the Byte-Order-Marks from the beginning of the file.
|
// trimBOM trims the Byte-Order-Marks from the beginning of the file.
|
||||||
// this is for Windows compatability only.
|
// this is for Windows compatibility only.
|
||||||
// see https://github.com/influxdata/telegraf/issues/1378
|
// see https://github.com/influxdata/telegraf/issues/1378
|
||||||
func trimBOM(f []byte) []byte {
|
func trimBOM(f []byte) []byte {
|
||||||
return bytes.TrimPrefix(f, []byte("\xef\xbb\xbf"))
|
return bytes.TrimPrefix(f, []byte("\xef\xbb\xbf"))
|
||||||
|
|
|
@ -13,6 +13,8 @@ const (
|
||||||
Counter
|
Counter
|
||||||
Gauge
|
Gauge
|
||||||
Untyped
|
Untyped
|
||||||
|
Summary
|
||||||
|
Histogram
|
||||||
)
|
)
|
||||||
|
|
||||||
type Metric interface {
|
type Metric interface {
|
||||||
|
|
|
@ -647,7 +647,7 @@ func skipWhitespace(buf []byte, i int) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeError is a helper function for making a metric parsing error.
|
// makeError is a helper function for making a metric parsing error.
|
||||||
// reason is the reason that the error occured.
|
// reason is the reason why the error occurred.
|
||||||
// buf should be the current buffer we are parsing.
|
// buf should be the current buffer we are parsing.
|
||||||
// i is the current index, to give some context on where in the buffer we are.
|
// i is the current index, to give some context on where in the buffer we are.
|
||||||
func makeError(reason string, buf []byte, i int) error {
|
func makeError(reason string, buf []byte, i int) error {
|
||||||
|
|
|
@ -181,7 +181,7 @@ func TestMetricReader_SplitWithExactLengthSplit(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regresssion test for when a metric requires to be split and one of the
|
// Regression test for when a metric requires to be split and one of the
|
||||||
// split metrics is larger than the buffer.
|
// split metrics is larger than the buffer.
|
||||||
//
|
//
|
||||||
// Previously the metric index would be set incorrectly causing a panic.
|
// Previously the metric index would be set incorrectly causing a panic.
|
||||||
|
@ -218,7 +218,7 @@ func TestMetricReader_SplitOverflowOversized(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regresssion test for when a split metric exactly fits in the buffer.
|
// Regression test for when a split metric exactly fits in the buffer.
|
||||||
//
|
//
|
||||||
// Previously the metric would be overflow split when not required.
|
// Previously the metric would be overflow split when not required.
|
||||||
func TestMetricReader_SplitOverflowUneeded(t *testing.T) {
|
func TestMetricReader_SplitOverflowUneeded(t *testing.T) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package all
|
package all
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/aggregators/basicstats"
|
||||||
_ "github.com/influxdata/telegraf/plugins/aggregators/histogram"
|
_ "github.com/influxdata/telegraf/plugins/aggregators/histogram"
|
||||||
_ "github.com/influxdata/telegraf/plugins/aggregators/minmax"
|
_ "github.com/influxdata/telegraf/plugins/aggregators/minmax"
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
# BasicStats Aggregator Plugin
|
||||||
|
|
||||||
|
The BasicStats aggregator plugin give us count,max,min,mean,s2(variance), stdev for a set of values,
|
||||||
|
emitting the aggregate every `period` seconds.
|
||||||
|
|
||||||
|
### Configuration:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Keep the aggregate basicstats of each metric passing through.
|
||||||
|
[[aggregators.basicstats]]
|
||||||
|
## 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
|
||||||
|
```
|
||||||
|
|
||||||
|
### Measurements & Fields:
|
||||||
|
|
||||||
|
- measurement1
|
||||||
|
- field1_count
|
||||||
|
- field1_max
|
||||||
|
- field1_min
|
||||||
|
- field1_mean
|
||||||
|
- field1_s2 (variance)
|
||||||
|
- field1_stdev (standard deviation)
|
||||||
|
|
||||||
|
### Tags:
|
||||||
|
|
||||||
|
No tags are applied by this aggregator.
|
||||||
|
|
||||||
|
### Example Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ telegraf --config telegraf.conf --quiet
|
||||||
|
system,host=tars load1=1 1475583980000000000
|
||||||
|
system,host=tars load1=1 1475583990000000000
|
||||||
|
system,host=tars load1_count=2,load1_max=1,load1_min=1,load1_mean=1,load1_s2=0,load1_stdev=0 1475584010000000000
|
||||||
|
system,host=tars load1=1 1475584020000000000
|
||||||
|
system,host=tars load1=3 1475584030000000000
|
||||||
|
system,host=tars load1_count=2,load1_max=3,load1_min=1,load1_mean=2,load1_s2=2,load1_stdev=1.414162 1475584010000000000
|
||||||
|
```
|
|
@ -0,0 +1,155 @@
|
||||||
|
package basicstats
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/plugins/aggregators"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BasicStats struct {
|
||||||
|
cache map[uint64]aggregate
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBasicStats() telegraf.Aggregator {
|
||||||
|
mm := &BasicStats{}
|
||||||
|
mm.Reset()
|
||||||
|
return mm
|
||||||
|
}
|
||||||
|
|
||||||
|
type aggregate struct {
|
||||||
|
fields map[string]basicstats
|
||||||
|
name string
|
||||||
|
tags map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
type basicstats struct {
|
||||||
|
count float64
|
||||||
|
min float64
|
||||||
|
max float64
|
||||||
|
mean float64
|
||||||
|
M2 float64 //intermedia value for variance/stdev
|
||||||
|
}
|
||||||
|
|
||||||
|
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 *BasicStats) SampleConfig() string {
|
||||||
|
return sampleConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BasicStats) Description() string {
|
||||||
|
return "Keep the aggregate basicstats of each metric passing through."
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BasicStats) 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]basicstats),
|
||||||
|
}
|
||||||
|
for k, v := range in.Fields() {
|
||||||
|
if fv, ok := convert(v); ok {
|
||||||
|
a.fields[k] = basicstats{
|
||||||
|
count: 1,
|
||||||
|
min: fv,
|
||||||
|
max: fv,
|
||||||
|
mean: fv,
|
||||||
|
M2: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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] = basicstats{
|
||||||
|
count: 1,
|
||||||
|
min: fv,
|
||||||
|
max: fv,
|
||||||
|
mean: fv,
|
||||||
|
M2: 0.0,
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp := m.cache[id].fields[k]
|
||||||
|
//https://en.m.wikipedia.org/wiki/Algorithms_for_calculating_variance
|
||||||
|
//variable initialization
|
||||||
|
x := fv
|
||||||
|
mean := tmp.mean
|
||||||
|
M2 := tmp.M2
|
||||||
|
//counter compute
|
||||||
|
n := tmp.count + 1
|
||||||
|
tmp.count = n
|
||||||
|
//mean compute
|
||||||
|
delta := x - mean
|
||||||
|
mean = mean + delta/n
|
||||||
|
tmp.mean = mean
|
||||||
|
//variance/stdev compute
|
||||||
|
M2 = M2 + delta*(x-mean)
|
||||||
|
tmp.M2 = M2
|
||||||
|
//max/min compute
|
||||||
|
if fv < tmp.min {
|
||||||
|
tmp.min = fv
|
||||||
|
} else if fv > tmp.max {
|
||||||
|
tmp.max = fv
|
||||||
|
}
|
||||||
|
//store final data
|
||||||
|
m.cache[id].fields[k] = tmp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BasicStats) Push(acc telegraf.Accumulator) {
|
||||||
|
for _, aggregate := range m.cache {
|
||||||
|
fields := map[string]interface{}{}
|
||||||
|
for k, v := range aggregate.fields {
|
||||||
|
fields[k+"_count"] = v.count
|
||||||
|
fields[k+"_min"] = v.min
|
||||||
|
fields[k+"_max"] = v.max
|
||||||
|
fields[k+"_mean"] = v.mean
|
||||||
|
//v.count always >=1
|
||||||
|
if v.count > 1 {
|
||||||
|
variance := v.M2 / (v.count - 1)
|
||||||
|
fields[k+"_s2"] = variance
|
||||||
|
fields[k+"_stdev"] = math.Sqrt(variance)
|
||||||
|
}
|
||||||
|
//if count == 1 StdDev = infinite => so I won't send data
|
||||||
|
}
|
||||||
|
acc.AddFields(aggregate.name, fields, aggregate.tags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BasicStats) 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("basicstats", func() telegraf.Aggregator {
|
||||||
|
return NewBasicStats()
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,151 @@
|
||||||
|
package basicstats
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf/metric"
|
||||||
|
"github.com/influxdata/telegraf/testutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var m1, _ = metric.New("m1",
|
||||||
|
map[string]string{"foo": "bar"},
|
||||||
|
map[string]interface{}{
|
||||||
|
"a": int64(1),
|
||||||
|
"b": int64(1),
|
||||||
|
"c": float64(2),
|
||||||
|
"d": float64(2),
|
||||||
|
},
|
||||||
|
time.Now(),
|
||||||
|
)
|
||||||
|
var m2, _ = metric.New("m1",
|
||||||
|
map[string]string{"foo": "bar"},
|
||||||
|
map[string]interface{}{
|
||||||
|
"a": int64(1),
|
||||||
|
"b": int64(3),
|
||||||
|
"c": float64(4),
|
||||||
|
"d": float64(6),
|
||||||
|
"e": float64(200),
|
||||||
|
"ignoreme": "string",
|
||||||
|
"andme": true,
|
||||||
|
},
|
||||||
|
time.Now(),
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkApply(b *testing.B) {
|
||||||
|
minmax := NewBasicStats()
|
||||||
|
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
minmax.Add(m1)
|
||||||
|
minmax.Add(m2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test two metrics getting added.
|
||||||
|
func TestBasicStatsWithPeriod(t *testing.T) {
|
||||||
|
acc := testutil.Accumulator{}
|
||||||
|
minmax := NewBasicStats()
|
||||||
|
|
||||||
|
minmax.Add(m1)
|
||||||
|
minmax.Add(m2)
|
||||||
|
minmax.Push(&acc)
|
||||||
|
|
||||||
|
expectedFields := map[string]interface{}{
|
||||||
|
"a_count": float64(2), //a
|
||||||
|
"a_max": float64(1),
|
||||||
|
"a_min": float64(1),
|
||||||
|
"a_mean": float64(1),
|
||||||
|
"a_stdev": float64(0),
|
||||||
|
"a_s2": float64(0),
|
||||||
|
"b_count": float64(2), //b
|
||||||
|
"b_max": float64(3),
|
||||||
|
"b_min": float64(1),
|
||||||
|
"b_mean": float64(2),
|
||||||
|
"b_s2": float64(2),
|
||||||
|
"b_stdev": math.Sqrt(2),
|
||||||
|
"c_count": float64(2), //c
|
||||||
|
"c_max": float64(4),
|
||||||
|
"c_min": float64(2),
|
||||||
|
"c_mean": float64(3),
|
||||||
|
"c_s2": float64(2),
|
||||||
|
"c_stdev": math.Sqrt(2),
|
||||||
|
"d_count": float64(2), //d
|
||||||
|
"d_max": float64(6),
|
||||||
|
"d_min": float64(2),
|
||||||
|
"d_mean": float64(4),
|
||||||
|
"d_s2": float64(8),
|
||||||
|
"d_stdev": math.Sqrt(8),
|
||||||
|
"e_count": float64(1), //e
|
||||||
|
"e_max": float64(200),
|
||||||
|
"e_min": float64(200),
|
||||||
|
"e_mean": 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 TestBasicStatsDifferentPeriods(t *testing.T) {
|
||||||
|
acc := testutil.Accumulator{}
|
||||||
|
minmax := NewBasicStats()
|
||||||
|
|
||||||
|
minmax.Add(m1)
|
||||||
|
minmax.Push(&acc)
|
||||||
|
expectedFields := map[string]interface{}{
|
||||||
|
"a_count": float64(1), //a
|
||||||
|
"a_max": float64(1),
|
||||||
|
"a_min": float64(1),
|
||||||
|
"a_mean": float64(1),
|
||||||
|
"b_count": float64(1), //b
|
||||||
|
"b_max": float64(1),
|
||||||
|
"b_min": float64(1),
|
||||||
|
"b_mean": float64(1),
|
||||||
|
"c_count": float64(1), //c
|
||||||
|
"c_max": float64(2),
|
||||||
|
"c_min": float64(2),
|
||||||
|
"c_mean": float64(2),
|
||||||
|
"d_count": float64(1), //d
|
||||||
|
"d_max": float64(2),
|
||||||
|
"d_min": float64(2),
|
||||||
|
"d_mean": float64(2),
|
||||||
|
}
|
||||||
|
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_count": float64(1), //a
|
||||||
|
"a_max": float64(1),
|
||||||
|
"a_min": float64(1),
|
||||||
|
"a_mean": float64(1),
|
||||||
|
"b_count": float64(1), //b
|
||||||
|
"b_max": float64(3),
|
||||||
|
"b_min": float64(3),
|
||||||
|
"b_mean": float64(3),
|
||||||
|
"c_count": float64(1), //c
|
||||||
|
"c_max": float64(4),
|
||||||
|
"c_min": float64(4),
|
||||||
|
"c_mean": float64(4),
|
||||||
|
"d_count": float64(1), //d
|
||||||
|
"d_max": float64(6),
|
||||||
|
"d_min": float64(6),
|
||||||
|
"d_mean": float64(6),
|
||||||
|
"e_count": float64(1), //e
|
||||||
|
"e_max": float64(200),
|
||||||
|
"e_min": float64(200),
|
||||||
|
"e_mean": float64(200),
|
||||||
|
}
|
||||||
|
expectedTags = map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
}
|
||||||
|
acc.AssertContainsTaggedFields(t, "m1", expectedFields, expectedTags)
|
||||||
|
}
|
|
@ -76,6 +76,7 @@ import (
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/riak"
|
_ "github.com/influxdata/telegraf/plugins/inputs/riak"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/salesforce"
|
_ "github.com/influxdata/telegraf/plugins/inputs/salesforce"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/sensors"
|
_ "github.com/influxdata/telegraf/plugins/inputs/sensors"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/inputs/smart"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/snmp"
|
_ "github.com/influxdata/telegraf/plugins/inputs/snmp"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/snmp_legacy"
|
_ "github.com/influxdata/telegraf/plugins/inputs/snmp_legacy"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/socket_listener"
|
_ "github.com/influxdata/telegraf/plugins/inputs/socket_listener"
|
||||||
|
@ -85,6 +86,7 @@ import (
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/system"
|
_ "github.com/influxdata/telegraf/plugins/inputs/system"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/tail"
|
_ "github.com/influxdata/telegraf/plugins/inputs/tail"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/tcp_listener"
|
_ "github.com/influxdata/telegraf/plugins/inputs/tcp_listener"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/inputs/teamspeak"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/tomcat"
|
_ "github.com/influxdata/telegraf/plugins/inputs/tomcat"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/trig"
|
_ "github.com/influxdata/telegraf/plugins/inputs/trig"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/twemproxy"
|
_ "github.com/influxdata/telegraf/plugins/inputs/twemproxy"
|
||||||
|
|
|
@ -92,7 +92,7 @@ func (c *CloudWatch) SampleConfig() string {
|
||||||
## Collection Delay (required - must account for metrics availability via CloudWatch API)
|
## Collection Delay (required - must account for metrics availability via CloudWatch API)
|
||||||
delay = "5m"
|
delay = "5m"
|
||||||
|
|
||||||
## Recomended: use metric 'interval' that is a multiple of 'period' to avoid
|
## Recommended: use metric 'interval' that is a multiple of 'period' to avoid
|
||||||
## gaps or overlap in pulled data
|
## gaps or overlap in pulled data
|
||||||
interval = "5m"
|
interval = "5m"
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,10 @@ func (c *Consul) createAPIClient() (*api.Client, error) {
|
||||||
config.Datacenter = c.Datacentre
|
config.Datacenter = c.Datacentre
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.Token != "" {
|
||||||
|
config.Token = c.Token
|
||||||
|
}
|
||||||
|
|
||||||
if c.Username != "" {
|
if c.Username != "" {
|
||||||
config.HttpAuth = &api.HttpBasicAuth{
|
config.HttpAuth = &api.HttpBasicAuth{
|
||||||
Username: c.Username,
|
Username: c.Username,
|
||||||
|
|
|
@ -20,7 +20,7 @@ var sampleChecks = []*api.HealthCheck{
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGatherHealtCheck(t *testing.T) {
|
func TestGatherHealthCheck(t *testing.T) {
|
||||||
expectedFields := map[string]interface{}{
|
expectedFields := map[string]interface{}{
|
||||||
"check_name": "foo.health",
|
"check_name": "foo.health",
|
||||||
"status": "passing",
|
"status": "passing",
|
||||||
|
|
|
@ -21,7 +21,7 @@ var sampleConfig = `
|
||||||
## http://admin:secret@couchbase-0.example.com:8091/
|
## http://admin:secret@couchbase-0.example.com:8091/
|
||||||
##
|
##
|
||||||
## If no servers are specified, then localhost is used as the host.
|
## If no servers are specified, then localhost is used as the host.
|
||||||
## If no protocol is specifed, HTTP is used.
|
## If no protocol is specified, HTTP is used.
|
||||||
## If no port is specified, 8091 is used.
|
## If no port is specified, 8091 is used.
|
||||||
servers = ["http://localhost:8091"]
|
servers = ["http://localhost:8091"]
|
||||||
`
|
`
|
||||||
|
|
|
@ -17,7 +17,7 @@ type DnsQuery struct {
|
||||||
// Domains or subdomains to query
|
// Domains or subdomains to query
|
||||||
Domains []string
|
Domains []string
|
||||||
|
|
||||||
// Network protocl name
|
// Network protocol name
|
||||||
Network string
|
Network string
|
||||||
|
|
||||||
// Server to query
|
// Server to query
|
||||||
|
|
|
@ -17,6 +17,11 @@ to gather stats from the [Engine API](https://docs.docker.com/engine/api/v1.20/)
|
||||||
## To use environment variables (ie, docker-machine), set endpoint = "ENV"
|
## To use environment variables (ie, docker-machine), set endpoint = "ENV"
|
||||||
endpoint = "unix:///var/run/docker.sock"
|
endpoint = "unix:///var/run/docker.sock"
|
||||||
|
|
||||||
|
## Set to true to collect Swarm metrics(desired_replicas, running_replicas)
|
||||||
|
## Note: configure this in one of the manager nodes in a Swarm cluster.
|
||||||
|
## configuring in multiple Swarm managers results in duplication of metrics.
|
||||||
|
gather_services = false
|
||||||
|
|
||||||
## Only collect metrics for these containers. Values will be appended to
|
## Only collect metrics for these containers. Values will be appended to
|
||||||
## container_name_include.
|
## container_name_include.
|
||||||
## Deprecated (1.4.0), use container_name_include
|
## Deprecated (1.4.0), use container_name_include
|
||||||
|
@ -161,6 +166,9 @@ based on the availability of per-cpu stats on your system.
|
||||||
- available
|
- available
|
||||||
- total
|
- total
|
||||||
- used
|
- used
|
||||||
|
- docker_swarm
|
||||||
|
- tasks_desired
|
||||||
|
- tasks_running
|
||||||
|
|
||||||
|
|
||||||
### Tags:
|
### Tags:
|
||||||
|
@ -191,6 +199,10 @@ based on the availability of per-cpu stats on your system.
|
||||||
- network
|
- network
|
||||||
- docker_container_blkio specific:
|
- docker_container_blkio specific:
|
||||||
- device
|
- device
|
||||||
|
- docker_swarm specific:
|
||||||
|
- service_id
|
||||||
|
- service_name
|
||||||
|
- service_mode
|
||||||
|
|
||||||
### Example Output:
|
### Example Output:
|
||||||
|
|
||||||
|
@ -242,4 +254,7 @@ io_service_bytes_recursive_sync=77824i,io_service_bytes_recursive_total=80293888
|
||||||
io_service_bytes_recursive_write=368640i,io_serviced_recursive_async=6562i,\
|
io_service_bytes_recursive_write=368640i,io_serviced_recursive_async=6562i,\
|
||||||
io_serviced_recursive_read=6492i,io_serviced_recursive_sync=37i,\
|
io_serviced_recursive_read=6492i,io_serviced_recursive_sync=37i,\
|
||||||
io_serviced_recursive_total=6599i,io_serviced_recursive_write=107i 1453409536840126713
|
io_serviced_recursive_total=6599i,io_serviced_recursive_write=107i 1453409536840126713
|
||||||
|
>docker_swarm,
|
||||||
|
service_id=xaup2o9krw36j2dy1mjx1arjw,service_mode=replicated,service_name=test,\
|
||||||
|
tasks_desired=3,tasks_running=3 1508968160000000000
|
||||||
```
|
```
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/swarm"
|
||||||
docker "github.com/docker/docker/client"
|
docker "github.com/docker/docker/client"
|
||||||
"github.com/docker/go-connections/sockets"
|
"github.com/docker/go-connections/sockets"
|
||||||
)
|
)
|
||||||
|
@ -20,6 +21,9 @@ type Client interface {
|
||||||
ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error)
|
ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error)
|
||||||
ContainerStats(ctx context.Context, containerID string, stream bool) (types.ContainerStats, error)
|
ContainerStats(ctx context.Context, containerID string, stream bool) (types.ContainerStats, error)
|
||||||
ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error)
|
ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error)
|
||||||
|
ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error)
|
||||||
|
TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error)
|
||||||
|
NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEnvClient() (Client, error) {
|
func NewEnvClient() (Client, error) {
|
||||||
|
@ -65,3 +69,12 @@ func (c *SocketClient) ContainerStats(ctx context.Context, containerID string, s
|
||||||
func (c *SocketClient) ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error) {
|
func (c *SocketClient) ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error) {
|
||||||
return c.client.ContainerInspect(ctx, containerID)
|
return c.client.ContainerInspect(ctx, containerID)
|
||||||
}
|
}
|
||||||
|
func (c *SocketClient) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||||
|
return c.client.ServiceList(ctx, options)
|
||||||
|
}
|
||||||
|
func (c *SocketClient) TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) {
|
||||||
|
return c.client.TaskList(ctx, options)
|
||||||
|
}
|
||||||
|
func (c *SocketClient) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) {
|
||||||
|
return c.client.NodeList(ctx, options)
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -14,38 +15,29 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/swarm"
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
"github.com/influxdata/telegraf/filter"
|
"github.com/influxdata/telegraf/filter"
|
||||||
"github.com/influxdata/telegraf/internal"
|
"github.com/influxdata/telegraf/internal"
|
||||||
"github.com/influxdata/telegraf/plugins/inputs"
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DockerLabelFilter struct {
|
|
||||||
labelInclude filter.Filter
|
|
||||||
labelExclude filter.Filter
|
|
||||||
}
|
|
||||||
|
|
||||||
type DockerContainerFilter struct {
|
|
||||||
containerInclude filter.Filter
|
|
||||||
containerExclude filter.Filter
|
|
||||||
}
|
|
||||||
|
|
||||||
// Docker object
|
// Docker object
|
||||||
type Docker struct {
|
type Docker struct {
|
||||||
Endpoint string
|
Endpoint string
|
||||||
ContainerNames []string
|
ContainerNames []string
|
||||||
|
|
||||||
|
GatherServices bool `toml:"gather_services"`
|
||||||
|
|
||||||
Timeout internal.Duration
|
Timeout internal.Duration
|
||||||
PerDevice bool `toml:"perdevice"`
|
PerDevice bool `toml:"perdevice"`
|
||||||
Total bool `toml:"total"`
|
Total bool `toml:"total"`
|
||||||
TagEnvironment []string `toml:"tag_env"`
|
TagEnvironment []string `toml:"tag_env"`
|
||||||
LabelInclude []string `toml:"docker_label_include"`
|
LabelInclude []string `toml:"docker_label_include"`
|
||||||
LabelExclude []string `toml:"docker_label_exclude"`
|
LabelExclude []string `toml:"docker_label_exclude"`
|
||||||
LabelFilter DockerLabelFilter
|
|
||||||
|
|
||||||
ContainerInclude []string `toml:"container_name_include"`
|
ContainerInclude []string `toml:"container_name_include"`
|
||||||
ContainerExclude []string `toml:"container_name_exclude"`
|
ContainerExclude []string `toml:"container_name_exclude"`
|
||||||
ContainerFilter DockerContainerFilter
|
|
||||||
|
|
||||||
SSLCA string `toml:"ssl_ca"`
|
SSLCA string `toml:"ssl_ca"`
|
||||||
SSLCert string `toml:"ssl_cert"`
|
SSLCert string `toml:"ssl_cert"`
|
||||||
|
@ -55,10 +47,12 @@ type Docker struct {
|
||||||
newEnvClient func() (Client, error)
|
newEnvClient func() (Client, error)
|
||||||
newClient func(string, *tls.Config) (Client, error)
|
newClient func(string, *tls.Config) (Client, error)
|
||||||
|
|
||||||
client Client
|
client Client
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
engine_host string
|
engine_host string
|
||||||
filtersCreated bool
|
filtersCreated bool
|
||||||
|
labelFilter filter.Filter
|
||||||
|
containerFilter filter.Filter
|
||||||
}
|
}
|
||||||
|
|
||||||
// KB, MB, GB, TB, PB...human friendly
|
// KB, MB, GB, TB, PB...human friendly
|
||||||
|
@ -82,6 +76,9 @@ var sampleConfig = `
|
||||||
## To use environment variables (ie, docker-machine), set endpoint = "ENV"
|
## To use environment variables (ie, docker-machine), set endpoint = "ENV"
|
||||||
endpoint = "unix:///var/run/docker.sock"
|
endpoint = "unix:///var/run/docker.sock"
|
||||||
|
|
||||||
|
## Set to true to collect Swarm metrics(desired_replicas, running_replicas)
|
||||||
|
gather_services = false
|
||||||
|
|
||||||
## Only collect metrics for these containers, collect all if empty
|
## Only collect metrics for these containers, collect all if empty
|
||||||
container_names = []
|
container_names = []
|
||||||
|
|
||||||
|
@ -160,6 +157,13 @@ func (d *Docker) Gather(acc telegraf.Accumulator) error {
|
||||||
acc.AddError(err)
|
acc.AddError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if d.GatherServices {
|
||||||
|
err := d.gatherSwarmInfo(acc)
|
||||||
|
if err != nil {
|
||||||
|
acc.AddError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// List containers
|
// List containers
|
||||||
opts := types.ContainerListOptions{}
|
opts := types.ContainerListOptions{}
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), d.Timeout.Duration)
|
ctx, cancel := context.WithTimeout(context.Background(), d.Timeout.Duration)
|
||||||
|
@ -187,6 +191,75 @@ func (d *Docker) Gather(acc telegraf.Accumulator) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Docker) gatherSwarmInfo(acc telegraf.Accumulator) error {
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), d.Timeout.Duration)
|
||||||
|
defer cancel()
|
||||||
|
services, err := d.client.ServiceList(ctx, types.ServiceListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(services) > 0 {
|
||||||
|
|
||||||
|
tasks, err := d.client.TaskList(ctx, types.TaskListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes, err := d.client.NodeList(ctx, types.NodeListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
running := map[string]int{}
|
||||||
|
tasksNoShutdown := map[string]int{}
|
||||||
|
|
||||||
|
activeNodes := make(map[string]struct{})
|
||||||
|
for _, n := range nodes {
|
||||||
|
if n.Status.State != swarm.NodeStateDown {
|
||||||
|
activeNodes[n.ID] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, task := range tasks {
|
||||||
|
if task.DesiredState != swarm.TaskStateShutdown {
|
||||||
|
tasksNoShutdown[task.ServiceID]++
|
||||||
|
}
|
||||||
|
|
||||||
|
if task.Status.State == swarm.TaskStateRunning {
|
||||||
|
running[task.ServiceID]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, service := range services {
|
||||||
|
tags := map[string]string{}
|
||||||
|
fields := make(map[string]interface{})
|
||||||
|
now := time.Now()
|
||||||
|
tags["service_id"] = service.ID
|
||||||
|
tags["service_name"] = service.Spec.Name
|
||||||
|
if service.Spec.Mode.Replicated != nil && service.Spec.Mode.Replicated.Replicas != nil {
|
||||||
|
tags["service_mode"] = "replicated"
|
||||||
|
fields["tasks_running"] = running[service.ID]
|
||||||
|
fields["tasks_desired"] = *service.Spec.Mode.Replicated.Replicas
|
||||||
|
} else if service.Spec.Mode.Global != nil {
|
||||||
|
tags["service_mode"] = "global"
|
||||||
|
fields["tasks_running"] = running[service.ID]
|
||||||
|
fields["tasks_desired"] = tasksNoShutdown[service.ID]
|
||||||
|
} else {
|
||||||
|
log.Printf("E! Unknow Replicas Mode")
|
||||||
|
}
|
||||||
|
// Add metrics
|
||||||
|
acc.AddFields("docker_swarm",
|
||||||
|
fields,
|
||||||
|
tags,
|
||||||
|
now)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Docker) gatherInfo(acc telegraf.Accumulator) error {
|
func (d *Docker) gatherInfo(acc telegraf.Accumulator) error {
|
||||||
// Init vars
|
// Init vars
|
||||||
dataFields := make(map[string]interface{})
|
dataFields := make(map[string]interface{})
|
||||||
|
@ -291,12 +364,8 @@ func (d *Docker) gatherContainer(
|
||||||
"container_version": imageVersion,
|
"container_version": imageVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(d.ContainerInclude) > 0 || len(d.ContainerExclude) > 0 {
|
if !d.containerFilter.Match(cname) {
|
||||||
if len(d.ContainerInclude) == 0 || !d.ContainerFilter.containerInclude.Match(cname) {
|
return nil
|
||||||
if len(d.ContainerExclude) == 0 || d.ContainerFilter.containerExclude.Match(cname) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), d.Timeout.Duration)
|
ctx, cancel := context.WithTimeout(context.Background(), d.Timeout.Duration)
|
||||||
|
@ -317,10 +386,8 @@ func (d *Docker) gatherContainer(
|
||||||
|
|
||||||
// Add labels to tags
|
// Add labels to tags
|
||||||
for k, label := range container.Labels {
|
for k, label := range container.Labels {
|
||||||
if len(d.LabelInclude) == 0 || d.LabelFilter.labelInclude.Match(k) {
|
if d.labelFilter.Match(k) {
|
||||||
if len(d.LabelExclude) == 0 || !d.LabelFilter.labelExclude.Match(k) {
|
tags[k] = label
|
||||||
tags[k] = label
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -666,46 +733,25 @@ func parseSize(sizeStr string) (int64, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Docker) createContainerFilters() error {
|
func (d *Docker) createContainerFilters() error {
|
||||||
|
// Backwards compatibility for deprecated `container_names` parameter.
|
||||||
if len(d.ContainerNames) > 0 {
|
if len(d.ContainerNames) > 0 {
|
||||||
d.ContainerInclude = append(d.ContainerInclude, d.ContainerNames...)
|
d.ContainerInclude = append(d.ContainerInclude, d.ContainerNames...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(d.ContainerInclude) != 0 {
|
filter, err := filter.NewIncludeExcludeFilter(d.ContainerInclude, d.ContainerExclude)
|
||||||
var err error
|
if err != nil {
|
||||||
d.ContainerFilter.containerInclude, err = filter.Compile(d.ContainerInclude)
|
return err
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
d.containerFilter = filter
|
||||||
if len(d.ContainerExclude) != 0 {
|
|
||||||
var err error
|
|
||||||
d.ContainerFilter.containerExclude, err = filter.Compile(d.ContainerExclude)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Docker) createLabelFilters() error {
|
func (d *Docker) createLabelFilters() error {
|
||||||
if len(d.LabelInclude) != 0 {
|
filter, err := filter.NewIncludeExcludeFilter(d.LabelInclude, d.LabelExclude)
|
||||||
var err error
|
if err != nil {
|
||||||
d.LabelFilter.labelInclude, err = filter.Compile(d.LabelInclude)
|
return err
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
d.labelFilter = filter
|
||||||
if len(d.LabelExclude) != 0 {
|
|
||||||
var err error
|
|
||||||
d.LabelFilter.labelExclude, err = filter.Compile(d.LabelExclude)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/influxdata/telegraf/testutil"
|
"github.com/influxdata/telegraf/testutil"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/swarm"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,6 +17,9 @@ type MockClient struct {
|
||||||
ContainerListF func(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error)
|
ContainerListF func(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error)
|
||||||
ContainerStatsF func(ctx context.Context, containerID string, stream bool) (types.ContainerStats, error)
|
ContainerStatsF func(ctx context.Context, containerID string, stream bool) (types.ContainerStats, error)
|
||||||
ContainerInspectF func(ctx context.Context, containerID string) (types.ContainerJSON, error)
|
ContainerInspectF func(ctx context.Context, containerID string) (types.ContainerJSON, error)
|
||||||
|
ServiceListF func(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error)
|
||||||
|
TaskListF func(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error)
|
||||||
|
NodeListF func(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *MockClient) Info(ctx context.Context) (types.Info, error) {
|
func (c *MockClient) Info(ctx context.Context) (types.Info, error) {
|
||||||
|
@ -44,21 +48,53 @@ func (c *MockClient) ContainerInspect(
|
||||||
return c.ContainerInspectF(ctx, containerID)
|
return c.ContainerInspectF(ctx, containerID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *MockClient) ServiceList(
|
||||||
|
ctx context.Context,
|
||||||
|
options types.ServiceListOptions,
|
||||||
|
) ([]swarm.Service, error) {
|
||||||
|
return c.ServiceListF(ctx, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockClient) TaskList(
|
||||||
|
ctx context.Context,
|
||||||
|
options types.TaskListOptions,
|
||||||
|
) ([]swarm.Task, error) {
|
||||||
|
return c.TaskListF(ctx, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockClient) NodeList(
|
||||||
|
ctx context.Context,
|
||||||
|
options types.NodeListOptions,
|
||||||
|
) ([]swarm.Node, error) {
|
||||||
|
return c.NodeListF(ctx, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseClient = MockClient{
|
||||||
|
InfoF: func(context.Context) (types.Info, error) {
|
||||||
|
return info, nil
|
||||||
|
},
|
||||||
|
ContainerListF: func(context.Context, types.ContainerListOptions) ([]types.Container, error) {
|
||||||
|
return containerList, nil
|
||||||
|
},
|
||||||
|
ContainerStatsF: func(context.Context, string, bool) (types.ContainerStats, error) {
|
||||||
|
return containerStats(), nil
|
||||||
|
},
|
||||||
|
ContainerInspectF: func(context.Context, string) (types.ContainerJSON, error) {
|
||||||
|
return containerInspect, nil
|
||||||
|
},
|
||||||
|
ServiceListF: func(context.Context, types.ServiceListOptions) ([]swarm.Service, error) {
|
||||||
|
return ServiceList, nil
|
||||||
|
},
|
||||||
|
TaskListF: func(context.Context, types.TaskListOptions) ([]swarm.Task, error) {
|
||||||
|
return TaskList, nil
|
||||||
|
},
|
||||||
|
NodeListF: func(context.Context, types.NodeListOptions) ([]swarm.Node, error) {
|
||||||
|
return NodeList, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func newClient(host string, tlsConfig *tls.Config) (Client, error) {
|
func newClient(host string, tlsConfig *tls.Config) (Client, error) {
|
||||||
return &MockClient{
|
return &baseClient, nil
|
||||||
InfoF: func(context.Context) (types.Info, error) {
|
|
||||||
return info, nil
|
|
||||||
},
|
|
||||||
ContainerListF: func(context.Context, types.ContainerListOptions) ([]types.Container, error) {
|
|
||||||
return containerList, nil
|
|
||||||
},
|
|
||||||
ContainerStatsF: func(context.Context, string, bool) (types.ContainerStats, error) {
|
|
||||||
return containerStats(), nil
|
|
||||||
},
|
|
||||||
ContainerInspectF: func(context.Context, string) (types.ContainerJSON, error) {
|
|
||||||
return containerInspect, nil
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDockerGatherContainerStats(t *testing.T) {
|
func TestDockerGatherContainerStats(t *testing.T) {
|
||||||
|
@ -227,6 +263,15 @@ func TestDocker_WindowsMemoryContainerStats(t *testing.T) {
|
||||||
ContainerInspectF: func(ctx context.Context, containerID string) (types.ContainerJSON, error) {
|
ContainerInspectF: func(ctx context.Context, containerID string) (types.ContainerJSON, error) {
|
||||||
return containerInspect, nil
|
return containerInspect, nil
|
||||||
},
|
},
|
||||||
|
ServiceListF: func(context.Context, types.ServiceListOptions) ([]swarm.Service, error) {
|
||||||
|
return ServiceList, nil
|
||||||
|
},
|
||||||
|
TaskListF: func(context.Context, types.TaskListOptions) ([]swarm.Task, error) {
|
||||||
|
return TaskList, nil
|
||||||
|
},
|
||||||
|
NodeListF: func(context.Context, types.NodeListOptions) ([]swarm.Node, error) {
|
||||||
|
return NodeList, nil
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -234,82 +279,291 @@ func TestDocker_WindowsMemoryContainerStats(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDockerGatherLabels(t *testing.T) {
|
func TestContainerLabels(t *testing.T) {
|
||||||
var gatherLabelsTests = []struct {
|
var tests = []struct {
|
||||||
include []string
|
name string
|
||||||
exclude []string
|
container types.Container
|
||||||
expected []string
|
include []string
|
||||||
notexpected []string
|
exclude []string
|
||||||
|
expected map[string]string
|
||||||
}{
|
}{
|
||||||
{[]string{}, []string{}, []string{"label1", "label2"}, []string{}},
|
{
|
||||||
{[]string{"*"}, []string{}, []string{"label1", "label2"}, []string{}},
|
name: "Nil filters matches all",
|
||||||
{[]string{"lab*"}, []string{}, []string{"label1", "label2"}, []string{}},
|
container: types.Container{
|
||||||
{[]string{"label1"}, []string{}, []string{"label1"}, []string{"label2"}},
|
Labels: map[string]string{
|
||||||
{[]string{"label1*"}, []string{}, []string{"label1"}, []string{"label2"}},
|
"a": "x",
|
||||||
{[]string{}, []string{"*"}, []string{}, []string{"label1", "label2"}},
|
},
|
||||||
{[]string{}, []string{"lab*"}, []string{}, []string{"label1", "label2"}},
|
},
|
||||||
{[]string{}, []string{"label1"}, []string{"label2"}, []string{"label1"}},
|
include: nil,
|
||||||
{[]string{"*"}, []string{"*"}, []string{}, []string{"label1", "label2"}},
|
exclude: nil,
|
||||||
|
expected: map[string]string{
|
||||||
|
"a": "x",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty filters matches all",
|
||||||
|
container: types.Container{
|
||||||
|
Labels: map[string]string{
|
||||||
|
"a": "x",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: []string{},
|
||||||
|
exclude: []string{},
|
||||||
|
expected: map[string]string{
|
||||||
|
"a": "x",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Must match include",
|
||||||
|
container: types.Container{
|
||||||
|
Labels: map[string]string{
|
||||||
|
"a": "x",
|
||||||
|
"b": "y",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: []string{"a"},
|
||||||
|
exclude: []string{},
|
||||||
|
expected: map[string]string{
|
||||||
|
"a": "x",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Must not match exclude",
|
||||||
|
container: types.Container{
|
||||||
|
Labels: map[string]string{
|
||||||
|
"a": "x",
|
||||||
|
"b": "y",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: []string{},
|
||||||
|
exclude: []string{"b"},
|
||||||
|
expected: map[string]string{
|
||||||
|
"a": "x",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Include Glob",
|
||||||
|
container: types.Container{
|
||||||
|
Labels: map[string]string{
|
||||||
|
"aa": "x",
|
||||||
|
"ab": "y",
|
||||||
|
"bb": "z",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: []string{"a*"},
|
||||||
|
exclude: []string{},
|
||||||
|
expected: map[string]string{
|
||||||
|
"aa": "x",
|
||||||
|
"ab": "y",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Exclude Glob",
|
||||||
|
container: types.Container{
|
||||||
|
Labels: map[string]string{
|
||||||
|
"aa": "x",
|
||||||
|
"ab": "y",
|
||||||
|
"bb": "z",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: []string{},
|
||||||
|
exclude: []string{"a*"},
|
||||||
|
expected: map[string]string{
|
||||||
|
"bb": "z",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Excluded Includes",
|
||||||
|
container: types.Container{
|
||||||
|
Labels: map[string]string{
|
||||||
|
"aa": "x",
|
||||||
|
"ab": "y",
|
||||||
|
"bb": "z",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: []string{"a*"},
|
||||||
|
exclude: []string{"*b"},
|
||||||
|
expected: map[string]string{
|
||||||
|
"aa": "x",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
for _, tt := range gatherLabelsTests {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
t.Run("", func(t *testing.T) {
|
|
||||||
var acc testutil.Accumulator
|
var acc testutil.Accumulator
|
||||||
d := Docker{
|
|
||||||
newClient: newClient,
|
newClientFunc := func(host string, tlsConfig *tls.Config) (Client, error) {
|
||||||
|
client := baseClient
|
||||||
|
client.ContainerListF = func(context.Context, types.ContainerListOptions) ([]types.Container, error) {
|
||||||
|
return []types.Container{tt.container}, nil
|
||||||
|
}
|
||||||
|
return &client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, label := range tt.include {
|
d := Docker{
|
||||||
d.LabelInclude = append(d.LabelInclude, label)
|
newClient: newClientFunc,
|
||||||
}
|
LabelInclude: tt.include,
|
||||||
for _, label := range tt.exclude {
|
LabelExclude: tt.exclude,
|
||||||
d.LabelExclude = append(d.LabelExclude, label)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := d.Gather(&acc)
|
err := d.Gather(&acc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
for _, label := range tt.expected {
|
// Grab tags from a container metric
|
||||||
if !acc.HasTag("docker_container_cpu", label) {
|
var actual map[string]string
|
||||||
t.Errorf("Didn't get expected label of %s. Test was: Include: %s Exclude %s",
|
for _, metric := range acc.Metrics {
|
||||||
label, tt.include, tt.exclude)
|
if metric.Measurement == "docker_container_cpu" {
|
||||||
|
actual = metric.Tags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, label := range tt.notexpected {
|
for k, v := range tt.expected {
|
||||||
if acc.HasTag("docker_container_cpu", label) {
|
require.Equal(t, v, actual[k])
|
||||||
t.Errorf("Got unexpected label of %s. Test was: Include: %s Exclude %s",
|
|
||||||
label, tt.include, tt.exclude)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContainerNames(t *testing.T) {
|
func TestContainerNames(t *testing.T) {
|
||||||
var gatherContainerNames = []struct {
|
var tests = []struct {
|
||||||
include []string
|
name string
|
||||||
exclude []string
|
containers [][]string
|
||||||
expected []string
|
include []string
|
||||||
notexpected []string
|
exclude []string
|
||||||
|
expected []string
|
||||||
}{
|
}{
|
||||||
{[]string{}, []string{}, []string{"etcd", "etcd2"}, []string{}},
|
{
|
||||||
{[]string{"*"}, []string{}, []string{"etcd", "etcd2"}, []string{}},
|
name: "Nil filters matches all",
|
||||||
{[]string{"etc*"}, []string{}, []string{"etcd", "etcd2"}, []string{}},
|
containers: [][]string{
|
||||||
{[]string{"etcd"}, []string{}, []string{"etcd"}, []string{"etcd2"}},
|
{"/etcd"},
|
||||||
{[]string{"etcd2*"}, []string{}, []string{"etcd2"}, []string{"etcd"}},
|
{"/etcd2"},
|
||||||
{[]string{}, []string{"etc*"}, []string{}, []string{"etcd", "etcd2"}},
|
},
|
||||||
{[]string{}, []string{"etcd"}, []string{"etcd2"}, []string{"etcd"}},
|
include: nil,
|
||||||
{[]string{"*"}, []string{"*"}, []string{"etcd", "etcd2"}, []string{}},
|
exclude: nil,
|
||||||
{[]string{}, []string{"*"}, []string{""}, []string{"etcd", "etcd2"}},
|
expected: []string{"etcd", "etcd2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty filters matches all",
|
||||||
|
containers: [][]string{
|
||||||
|
{"/etcd"},
|
||||||
|
{"/etcd2"},
|
||||||
|
},
|
||||||
|
include: []string{},
|
||||||
|
exclude: []string{},
|
||||||
|
expected: []string{"etcd", "etcd2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Match all containers",
|
||||||
|
containers: [][]string{
|
||||||
|
{"/etcd"},
|
||||||
|
{"/etcd2"},
|
||||||
|
},
|
||||||
|
include: []string{"*"},
|
||||||
|
exclude: []string{},
|
||||||
|
expected: []string{"etcd", "etcd2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Include prefix match",
|
||||||
|
containers: [][]string{
|
||||||
|
{"/etcd"},
|
||||||
|
{"/etcd2"},
|
||||||
|
},
|
||||||
|
include: []string{"etc*"},
|
||||||
|
exclude: []string{},
|
||||||
|
expected: []string{"etcd", "etcd2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Exact match",
|
||||||
|
containers: [][]string{
|
||||||
|
{"/etcd"},
|
||||||
|
{"/etcd2"},
|
||||||
|
},
|
||||||
|
include: []string{"etcd"},
|
||||||
|
exclude: []string{},
|
||||||
|
expected: []string{"etcd"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Star matches zero length",
|
||||||
|
containers: [][]string{
|
||||||
|
{"/etcd"},
|
||||||
|
{"/etcd2"},
|
||||||
|
},
|
||||||
|
include: []string{"etcd2*"},
|
||||||
|
exclude: []string{},
|
||||||
|
expected: []string{"etcd2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Exclude matches all",
|
||||||
|
containers: [][]string{
|
||||||
|
{"/etcd"},
|
||||||
|
{"/etcd2"},
|
||||||
|
},
|
||||||
|
include: []string{},
|
||||||
|
exclude: []string{"etc*"},
|
||||||
|
expected: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Exclude single",
|
||||||
|
containers: [][]string{
|
||||||
|
{"/etcd"},
|
||||||
|
{"/etcd2"},
|
||||||
|
},
|
||||||
|
include: []string{},
|
||||||
|
exclude: []string{"etcd"},
|
||||||
|
expected: []string{"etcd2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Exclude all",
|
||||||
|
containers: [][]string{
|
||||||
|
{"/etcd"},
|
||||||
|
{"/etcd2"},
|
||||||
|
},
|
||||||
|
include: []string{"*"},
|
||||||
|
exclude: []string{"*"},
|
||||||
|
expected: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Exclude item matching include",
|
||||||
|
containers: [][]string{
|
||||||
|
{"acme"},
|
||||||
|
{"foo"},
|
||||||
|
{"acme-test"},
|
||||||
|
},
|
||||||
|
include: []string{"acme*"},
|
||||||
|
exclude: []string{"*test*"},
|
||||||
|
expected: []string{"acme"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Exclude item no wildcards",
|
||||||
|
containers: [][]string{
|
||||||
|
{"acme"},
|
||||||
|
{"acme-test"},
|
||||||
|
},
|
||||||
|
include: []string{"acme*"},
|
||||||
|
exclude: []string{"test"},
|
||||||
|
expected: []string{"acme", "acme-test"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
for _, tt := range gatherContainerNames {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
t.Run("", func(t *testing.T) {
|
|
||||||
var acc testutil.Accumulator
|
var acc testutil.Accumulator
|
||||||
|
|
||||||
|
newClientFunc := func(host string, tlsConfig *tls.Config) (Client, error) {
|
||||||
|
client := baseClient
|
||||||
|
client.ContainerListF = func(context.Context, types.ContainerListOptions) ([]types.Container, error) {
|
||||||
|
var containers []types.Container
|
||||||
|
for _, names := range tt.containers {
|
||||||
|
containers = append(containers, types.Container{
|
||||||
|
Names: names,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return containers, nil
|
||||||
|
}
|
||||||
|
return &client, nil
|
||||||
|
}
|
||||||
|
|
||||||
d := Docker{
|
d := Docker{
|
||||||
newClient: newClient,
|
newClient: newClientFunc,
|
||||||
ContainerInclude: tt.include,
|
ContainerInclude: tt.include,
|
||||||
ContainerExclude: tt.exclude,
|
ContainerExclude: tt.exclude,
|
||||||
}
|
}
|
||||||
|
@ -317,39 +571,21 @@ func TestContainerNames(t *testing.T) {
|
||||||
err := d.Gather(&acc)
|
err := d.Gather(&acc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Set of expected names
|
||||||
|
var expected = make(map[string]bool)
|
||||||
|
for _, v := range tt.expected {
|
||||||
|
expected[v] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set of actual names
|
||||||
|
var actual = make(map[string]bool)
|
||||||
for _, metric := range acc.Metrics {
|
for _, metric := range acc.Metrics {
|
||||||
if metric.Measurement == "docker_container_cpu" {
|
if name, ok := metric.Tags["container_name"]; ok {
|
||||||
if val, ok := metric.Tags["container_name"]; ok {
|
actual[name] = true
|
||||||
var found bool = false
|
|
||||||
for _, cname := range tt.expected {
|
|
||||||
if val == cname {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
t.Errorf("Got unexpected container of %s. Test was -> Include: %s, Exclude: %s", val, tt.include, tt.exclude)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, metric := range acc.Metrics {
|
require.Equal(t, expected, actual)
|
||||||
if metric.Measurement == "docker_container_cpu" {
|
|
||||||
if val, ok := metric.Tags["container_name"]; ok {
|
|
||||||
var found bool = false
|
|
||||||
for _, cname := range tt.notexpected {
|
|
||||||
if val == cname {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if found {
|
|
||||||
t.Errorf("Got unexpected container of %s. Test was -> Include: %s, Exclude: %s", val, tt.include, tt.exclude)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -436,3 +672,42 @@ func TestDockerGatherInfo(t *testing.T) {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDockerGatherSwarmInfo(t *testing.T) {
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
d := Docker{
|
||||||
|
newClient: newClient,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := acc.GatherError(d.Gather)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
d.gatherSwarmInfo(&acc)
|
||||||
|
|
||||||
|
// test docker_container_net measurement
|
||||||
|
acc.AssertContainsTaggedFields(t,
|
||||||
|
"docker_swarm",
|
||||||
|
map[string]interface{}{
|
||||||
|
"tasks_running": int(2),
|
||||||
|
"tasks_desired": uint64(2),
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"service_id": "qolkls9g5iasdiuihcyz9rnx2",
|
||||||
|
"service_name": "test1",
|
||||||
|
"service_mode": "replicated",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
acc.AssertContainsTaggedFields(t,
|
||||||
|
"docker_swarm",
|
||||||
|
map[string]interface{}{
|
||||||
|
"tasks_running": int(1),
|
||||||
|
"tasks_desired": int(1),
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"service_id": "qolkls9g5iasdiuihcyz9rn3",
|
||||||
|
"service_name": "test2",
|
||||||
|
"service_mode": "global",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/registry"
|
"github.com/docker/docker/api/types/registry"
|
||||||
|
"github.com/docker/docker/api/types/swarm"
|
||||||
)
|
)
|
||||||
|
|
||||||
var info = types.Info{
|
var info = types.Info{
|
||||||
|
@ -133,6 +134,79 @@ var containerList = []types.Container{
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var two = uint64(2)
|
||||||
|
var ServiceList = []swarm.Service{
|
||||||
|
swarm.Service{
|
||||||
|
ID: "qolkls9g5iasdiuihcyz9rnx2",
|
||||||
|
Spec: swarm.ServiceSpec{
|
||||||
|
Annotations: swarm.Annotations{
|
||||||
|
Name: "test1",
|
||||||
|
},
|
||||||
|
Mode: swarm.ServiceMode{
|
||||||
|
Replicated: &swarm.ReplicatedService{
|
||||||
|
Replicas: &two,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
swarm.Service{
|
||||||
|
ID: "qolkls9g5iasdiuihcyz9rn3",
|
||||||
|
Spec: swarm.ServiceSpec{
|
||||||
|
Annotations: swarm.Annotations{
|
||||||
|
Name: "test2",
|
||||||
|
},
|
||||||
|
Mode: swarm.ServiceMode{
|
||||||
|
Global: &swarm.GlobalService{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var TaskList = []swarm.Task{
|
||||||
|
swarm.Task{
|
||||||
|
ID: "kwh0lv7hwwbh",
|
||||||
|
ServiceID: "qolkls9g5iasdiuihcyz9rnx2",
|
||||||
|
NodeID: "0cl4jturcyd1ks3fwpd010kor",
|
||||||
|
Status: swarm.TaskStatus{
|
||||||
|
State: "running",
|
||||||
|
},
|
||||||
|
DesiredState: "running",
|
||||||
|
},
|
||||||
|
swarm.Task{
|
||||||
|
ID: "u78m5ojbivc3",
|
||||||
|
ServiceID: "qolkls9g5iasdiuihcyz9rnx2",
|
||||||
|
NodeID: "0cl4jturcyd1ks3fwpd010kor",
|
||||||
|
Status: swarm.TaskStatus{
|
||||||
|
State: "running",
|
||||||
|
},
|
||||||
|
DesiredState: "running",
|
||||||
|
},
|
||||||
|
swarm.Task{
|
||||||
|
ID: "1n1uilkhr98l",
|
||||||
|
ServiceID: "qolkls9g5iasdiuihcyz9rn3",
|
||||||
|
NodeID: "0cl4jturcyd1ks3fwpd010kor",
|
||||||
|
Status: swarm.TaskStatus{
|
||||||
|
State: "running",
|
||||||
|
},
|
||||||
|
DesiredState: "running",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var NodeList = []swarm.Node{
|
||||||
|
swarm.Node{
|
||||||
|
ID: "0cl4jturcyd1ks3fwpd010kor",
|
||||||
|
Status: swarm.NodeStatus{
|
||||||
|
State: "ready",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
swarm.Node{
|
||||||
|
ID: "0cl4jturcyd1ks3fwpd010kor",
|
||||||
|
Status: swarm.NodeStatus{
|
||||||
|
State: "ready",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func containerStats() types.ContainerStats {
|
func containerStats() types.ContainerStats {
|
||||||
var stat types.ContainerStats
|
var stat types.ContainerStats
|
||||||
jsonStat := `
|
jsonStat := `
|
||||||
|
|
|
@ -23,10 +23,21 @@ or [cluster-stats](https://www.elastic.co/guide/en/elasticsearch/reference/curre
|
||||||
## Set cluster_health to true when you want to also obtain cluster health stats
|
## Set cluster_health to true when you want to also obtain cluster health stats
|
||||||
cluster_health = false
|
cluster_health = false
|
||||||
|
|
||||||
## Set cluster_stats to true when you want to obtain cluster stats from the
|
## Adjust cluster_health_level when you want to also obtain detailed health stats
|
||||||
|
## The options are
|
||||||
|
## - indices (default)
|
||||||
|
## - cluster
|
||||||
|
# cluster_health_level = "indices"
|
||||||
|
|
||||||
|
## Set cluster_stats to true when you want to also obtain cluster stats from the
|
||||||
## Master node.
|
## Master node.
|
||||||
cluster_stats = false
|
cluster_stats = false
|
||||||
|
|
||||||
|
## node_stats is a list of sub-stats that you want to have gathered. Valid options
|
||||||
|
## are "indices", "os", "process", "jvm", "thread_pool", "fs", "transport", "http",
|
||||||
|
## "breakers". Per default, all stats are gathered.
|
||||||
|
# node_stats = ["jvm", "http"]
|
||||||
|
|
||||||
## Optional SSL Config
|
## Optional SSL Config
|
||||||
# ssl_ca = "/etc/telegraf/ca.pem"
|
# ssl_ca = "/etc/telegraf/ca.pem"
|
||||||
# ssl_cert = "/etc/telegraf/cert.pem"
|
# ssl_cert = "/etc/telegraf/cert.pem"
|
||||||
|
|
|
@ -3,17 +3,16 @@ package elasticsearch
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
"github.com/influxdata/telegraf/internal"
|
"github.com/influxdata/telegraf/internal"
|
||||||
"github.com/influxdata/telegraf/plugins/inputs"
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||||||
jsonparser "github.com/influxdata/telegraf/plugins/parsers/json"
|
jsonparser "github.com/influxdata/telegraf/plugins/parsers/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// mask for masking username/password from error messages
|
// mask for masking username/password from error messages
|
||||||
|
@ -94,10 +93,21 @@ const sampleConfig = `
|
||||||
## Set cluster_health to true when you want to also obtain cluster health stats
|
## Set cluster_health to true when you want to also obtain cluster health stats
|
||||||
cluster_health = false
|
cluster_health = false
|
||||||
|
|
||||||
|
## Adjust cluster_health_level when you want to also obtain detailed health stats
|
||||||
|
## The options are
|
||||||
|
## - indices (default)
|
||||||
|
## - cluster
|
||||||
|
# cluster_health_level = "indices"
|
||||||
|
|
||||||
## Set cluster_stats to true when you want to also obtain cluster stats from the
|
## Set cluster_stats to true when you want to also obtain cluster stats from the
|
||||||
## Master node.
|
## Master node.
|
||||||
cluster_stats = false
|
cluster_stats = false
|
||||||
|
|
||||||
|
## node_stats is a list of sub-stats that you want to have gathered. Valid options
|
||||||
|
## are "indices", "os", "process", "jvm", "thread_pool", "fs", "transport", "http",
|
||||||
|
## "breakers". Per default, all stats are gathered.
|
||||||
|
# node_stats = ["jvm", "http"]
|
||||||
|
|
||||||
## Optional SSL Config
|
## Optional SSL Config
|
||||||
# ssl_ca = "/etc/telegraf/ca.pem"
|
# ssl_ca = "/etc/telegraf/ca.pem"
|
||||||
# ssl_cert = "/etc/telegraf/cert.pem"
|
# ssl_cert = "/etc/telegraf/cert.pem"
|
||||||
|
@ -113,7 +123,9 @@ type Elasticsearch struct {
|
||||||
Servers []string
|
Servers []string
|
||||||
HttpTimeout internal.Duration
|
HttpTimeout internal.Duration
|
||||||
ClusterHealth bool
|
ClusterHealth bool
|
||||||
|
ClusterHealthLevel string
|
||||||
ClusterStats bool
|
ClusterStats bool
|
||||||
|
NodeStats []string
|
||||||
SSLCA string `toml:"ssl_ca"` // Path to CA file
|
SSLCA string `toml:"ssl_ca"` // Path to CA file
|
||||||
SSLCert string `toml:"ssl_cert"` // Path to host cert file
|
SSLCert string `toml:"ssl_cert"` // Path to host cert file
|
||||||
SSLKey string `toml:"ssl_key"` // Path to cert key file
|
SSLKey string `toml:"ssl_key"` // Path to cert key file
|
||||||
|
@ -126,7 +138,8 @@ type Elasticsearch struct {
|
||||||
// NewElasticsearch return a new instance of Elasticsearch
|
// NewElasticsearch return a new instance of Elasticsearch
|
||||||
func NewElasticsearch() *Elasticsearch {
|
func NewElasticsearch() *Elasticsearch {
|
||||||
return &Elasticsearch{
|
return &Elasticsearch{
|
||||||
HttpTimeout: internal.Duration{Duration: time.Second * 5},
|
HttpTimeout: internal.Duration{Duration: time.Second * 5},
|
||||||
|
ClusterHealthLevel: "indices",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,12 +171,7 @@ func (e *Elasticsearch) Gather(acc telegraf.Accumulator) error {
|
||||||
for _, serv := range e.Servers {
|
for _, serv := range e.Servers {
|
||||||
go func(s string, acc telegraf.Accumulator) {
|
go func(s string, acc telegraf.Accumulator) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
var url string
|
url := e.nodeStatsUrl(s)
|
||||||
if e.Local {
|
|
||||||
url = s + statsPathLocal
|
|
||||||
} else {
|
|
||||||
url = s + statsPath
|
|
||||||
}
|
|
||||||
e.isMaster = false
|
e.isMaster = false
|
||||||
|
|
||||||
if e.ClusterStats {
|
if e.ClusterStats {
|
||||||
|
@ -182,7 +190,10 @@ func (e *Elasticsearch) Gather(acc telegraf.Accumulator) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.ClusterHealth {
|
if e.ClusterHealth {
|
||||||
url = s + "/_cluster/health?level=indices"
|
url = s + "/_cluster/health"
|
||||||
|
if e.ClusterHealthLevel != "" {
|
||||||
|
url = url + "?level=" + e.ClusterHealthLevel
|
||||||
|
}
|
||||||
if err := e.gatherClusterHealth(url, acc); err != nil {
|
if err := e.gatherClusterHealth(url, acc); err != nil {
|
||||||
acc.AddError(fmt.Errorf(mask.ReplaceAllString(err.Error(), "http(s)://XXX:XXX@")))
|
acc.AddError(fmt.Errorf(mask.ReplaceAllString(err.Error(), "http(s)://XXX:XXX@")))
|
||||||
return
|
return
|
||||||
|
@ -219,6 +230,22 @@ func (e *Elasticsearch) createHttpClient() (*http.Client, error) {
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Elasticsearch) nodeStatsUrl(baseUrl string) string {
|
||||||
|
var url string
|
||||||
|
|
||||||
|
if e.Local {
|
||||||
|
url = baseUrl + statsPathLocal
|
||||||
|
} else {
|
||||||
|
url = baseUrl + statsPath
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(e.NodeStats) == 0 {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s/%s", url, strings.Join(e.NodeStats, ","))
|
||||||
|
}
|
||||||
|
|
||||||
func (e *Elasticsearch) gatherNodeStats(url string, acc telegraf.Accumulator) error {
|
func (e *Elasticsearch) gatherNodeStats(url string, acc telegraf.Accumulator) error {
|
||||||
nodeStats := &struct {
|
nodeStats := &struct {
|
||||||
ClusterName string `json:"cluster_name"`
|
ClusterName string `json:"cluster_name"`
|
||||||
|
@ -259,6 +286,11 @@ func (e *Elasticsearch) gatherNodeStats(url string, acc telegraf.Accumulator) er
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
for p, s := range stats {
|
for p, s := range stats {
|
||||||
|
// if one of the individual node stats is not even in the
|
||||||
|
// original result
|
||||||
|
if s == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
f := jsonparser.JSONFlattener{}
|
f := jsonparser.JSONFlattener{}
|
||||||
// parse Json, ignoring strings and bools
|
// parse Json, ignoring strings and bools
|
||||||
err := f.FlattenJSON("", s)
|
err := f.FlattenJSON("", s)
|
||||||
|
|
|
@ -13,6 +13,16 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func defaultTags() map[string]string {
|
||||||
|
return map[string]string{
|
||||||
|
"cluster_name": "es-testcluster",
|
||||||
|
"node_attribute_master": "true",
|
||||||
|
"node_id": "SDFsfSDFsdfFSDSDfSFDSDF",
|
||||||
|
"node_name": "test.host.com",
|
||||||
|
"node_host": "test",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type transportMock struct {
|
type transportMock struct {
|
||||||
statusCode int
|
statusCode int
|
||||||
body string
|
body string
|
||||||
|
@ -45,15 +55,9 @@ func checkIsMaster(es *Elasticsearch, expected bool, t *testing.T) {
|
||||||
assert.Fail(t, msg)
|
assert.Fail(t, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func checkNodeStatsResult(t *testing.T, acc *testutil.Accumulator) {
|
|
||||||
tags := map[string]string{
|
|
||||||
"cluster_name": "es-testcluster",
|
|
||||||
"node_attribute_master": "true",
|
|
||||||
"node_id": "SDFsfSDFsdfFSDSDfSFDSDF",
|
|
||||||
"node_name": "test.host.com",
|
|
||||||
"node_host": "test",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
func checkNodeStatsResult(t *testing.T, acc *testutil.Accumulator) {
|
||||||
|
tags := defaultTags()
|
||||||
acc.AssertContainsTaggedFields(t, "elasticsearch_indices", nodestatsIndicesExpected, tags)
|
acc.AssertContainsTaggedFields(t, "elasticsearch_indices", nodestatsIndicesExpected, tags)
|
||||||
acc.AssertContainsTaggedFields(t, "elasticsearch_os", nodestatsOsExpected, tags)
|
acc.AssertContainsTaggedFields(t, "elasticsearch_os", nodestatsOsExpected, tags)
|
||||||
acc.AssertContainsTaggedFields(t, "elasticsearch_process", nodestatsProcessExpected, tags)
|
acc.AssertContainsTaggedFields(t, "elasticsearch_process", nodestatsProcessExpected, tags)
|
||||||
|
@ -79,6 +83,31 @@ func TestGather(t *testing.T) {
|
||||||
checkNodeStatsResult(t, &acc)
|
checkNodeStatsResult(t, &acc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGatherIndividualStats(t *testing.T) {
|
||||||
|
es := newElasticsearchWithClient()
|
||||||
|
es.Servers = []string{"http://example.com:9200"}
|
||||||
|
es.NodeStats = []string{"jvm", "process"}
|
||||||
|
es.client.Transport = newTransportMock(http.StatusOK, nodeStatsResponseJVMProcess)
|
||||||
|
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
if err := acc.GatherError(es.Gather); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkIsMaster(es, false, t)
|
||||||
|
|
||||||
|
tags := defaultTags()
|
||||||
|
acc.AssertDoesNotContainsTaggedFields(t, "elasticsearch_indices", nodestatsIndicesExpected, tags)
|
||||||
|
acc.AssertDoesNotContainsTaggedFields(t, "elasticsearch_os", nodestatsOsExpected, tags)
|
||||||
|
acc.AssertContainsTaggedFields(t, "elasticsearch_process", nodestatsProcessExpected, tags)
|
||||||
|
acc.AssertContainsTaggedFields(t, "elasticsearch_jvm", nodestatsJvmExpected, tags)
|
||||||
|
acc.AssertDoesNotContainsTaggedFields(t, "elasticsearch_thread_pool", nodestatsThreadPoolExpected, tags)
|
||||||
|
acc.AssertDoesNotContainsTaggedFields(t, "elasticsearch_fs", nodestatsFsExpected, tags)
|
||||||
|
acc.AssertDoesNotContainsTaggedFields(t, "elasticsearch_transport", nodestatsTransportExpected, tags)
|
||||||
|
acc.AssertDoesNotContainsTaggedFields(t, "elasticsearch_http", nodestatsHttpExpected, tags)
|
||||||
|
acc.AssertDoesNotContainsTaggedFields(t, "elasticsearch_breakers", nodestatsBreakersExpected, tags)
|
||||||
|
}
|
||||||
|
|
||||||
func TestGatherNodeStats(t *testing.T) {
|
func TestGatherNodeStats(t *testing.T) {
|
||||||
es := newElasticsearchWithClient()
|
es := newElasticsearchWithClient()
|
||||||
es.Servers = []string{"http://example.com:9200"}
|
es.Servers = []string{"http://example.com:9200"}
|
||||||
|
@ -93,10 +122,11 @@ func TestGatherNodeStats(t *testing.T) {
|
||||||
checkNodeStatsResult(t, &acc)
|
checkNodeStatsResult(t, &acc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGatherClusterHealth(t *testing.T) {
|
func TestGatherClusterHealthEmptyClusterHealth(t *testing.T) {
|
||||||
es := newElasticsearchWithClient()
|
es := newElasticsearchWithClient()
|
||||||
es.Servers = []string{"http://example.com:9200"}
|
es.Servers = []string{"http://example.com:9200"}
|
||||||
es.ClusterHealth = true
|
es.ClusterHealth = true
|
||||||
|
es.ClusterHealthLevel = ""
|
||||||
es.client.Transport = newTransportMock(http.StatusOK, clusterHealthResponse)
|
es.client.Transport = newTransportMock(http.StatusOK, clusterHealthResponse)
|
||||||
|
|
||||||
var acc testutil.Accumulator
|
var acc testutil.Accumulator
|
||||||
|
@ -104,6 +134,56 @@ func TestGatherClusterHealth(t *testing.T) {
|
||||||
|
|
||||||
checkIsMaster(es, false, t)
|
checkIsMaster(es, false, t)
|
||||||
|
|
||||||
|
acc.AssertContainsTaggedFields(t, "elasticsearch_cluster_health",
|
||||||
|
clusterHealthExpected,
|
||||||
|
map[string]string{"name": "elasticsearch_telegraf"})
|
||||||
|
|
||||||
|
acc.AssertDoesNotContainsTaggedFields(t, "elasticsearch_indices",
|
||||||
|
v1IndexExpected,
|
||||||
|
map[string]string{"index": "v1"})
|
||||||
|
|
||||||
|
acc.AssertDoesNotContainsTaggedFields(t, "elasticsearch_indices",
|
||||||
|
v2IndexExpected,
|
||||||
|
map[string]string{"index": "v2"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGatherClusterHealthSpecificClusterHealth(t *testing.T) {
|
||||||
|
es := newElasticsearchWithClient()
|
||||||
|
es.Servers = []string{"http://example.com:9200"}
|
||||||
|
es.ClusterHealth = true
|
||||||
|
es.ClusterHealthLevel = "cluster"
|
||||||
|
es.client.Transport = newTransportMock(http.StatusOK, clusterHealthResponse)
|
||||||
|
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
require.NoError(t, es.gatherClusterHealth("junk", &acc))
|
||||||
|
|
||||||
|
checkIsMaster(es, false, t)
|
||||||
|
|
||||||
|
acc.AssertContainsTaggedFields(t, "elasticsearch_cluster_health",
|
||||||
|
clusterHealthExpected,
|
||||||
|
map[string]string{"name": "elasticsearch_telegraf"})
|
||||||
|
|
||||||
|
acc.AssertDoesNotContainsTaggedFields(t, "elasticsearch_indices",
|
||||||
|
v1IndexExpected,
|
||||||
|
map[string]string{"index": "v1"})
|
||||||
|
|
||||||
|
acc.AssertDoesNotContainsTaggedFields(t, "elasticsearch_indices",
|
||||||
|
v2IndexExpected,
|
||||||
|
map[string]string{"index": "v2"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGatherClusterHealthAlsoIndicesHealth(t *testing.T) {
|
||||||
|
es := newElasticsearchWithClient()
|
||||||
|
es.Servers = []string{"http://example.com:9200"}
|
||||||
|
es.ClusterHealth = true
|
||||||
|
es.ClusterHealthLevel = "indices"
|
||||||
|
es.client.Transport = newTransportMock(http.StatusOK, clusterHealthResponseWithIndices)
|
||||||
|
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
require.NoError(t, es.gatherClusterHealth("junk", &acc))
|
||||||
|
|
||||||
|
checkIsMaster(es, false, t)
|
||||||
|
|
||||||
acc.AssertContainsTaggedFields(t, "elasticsearch_cluster_health",
|
acc.AssertContainsTaggedFields(t, "elasticsearch_cluster_health",
|
||||||
clusterHealthExpected,
|
clusterHealthExpected,
|
||||||
map[string]string{"name": "elasticsearch_telegraf"})
|
map[string]string{"name": "elasticsearch_telegraf"})
|
||||||
|
@ -185,7 +265,6 @@ func TestGatherClusterStatsNonMaster(t *testing.T) {
|
||||||
// ensure flag is clear so Cluster Stats would not be done
|
// ensure flag is clear so Cluster Stats would not be done
|
||||||
checkIsMaster(es, false, t)
|
checkIsMaster(es, false, t)
|
||||||
checkNodeStatsResult(t, &acc)
|
checkNodeStatsResult(t, &acc)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newElasticsearchWithClient() *Elasticsearch {
|
func newElasticsearchWithClient() *Elasticsearch {
|
||||||
|
|
|
@ -1,6 +1,21 @@
|
||||||
package elasticsearch
|
package elasticsearch
|
||||||
|
|
||||||
const clusterHealthResponse = `
|
const clusterHealthResponse = `
|
||||||
|
{
|
||||||
|
"cluster_name": "elasticsearch_telegraf",
|
||||||
|
"status": "green",
|
||||||
|
"timed_out": false,
|
||||||
|
"number_of_nodes": 3,
|
||||||
|
"number_of_data_nodes": 3,
|
||||||
|
"active_primary_shards": 5,
|
||||||
|
"active_shards": 15,
|
||||||
|
"relocating_shards": 0,
|
||||||
|
"initializing_shards": 0,
|
||||||
|
"unassigned_shards": 0
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const clusterHealthResponseWithIndices = `
|
||||||
{
|
{
|
||||||
"cluster_name": "elasticsearch_telegraf",
|
"cluster_name": "elasticsearch_telegraf",
|
||||||
"status": "green",
|
"status": "green",
|
||||||
|
@ -489,6 +504,100 @@ const nodeStatsResponse = `
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const nodeStatsResponseJVMProcess = `
|
||||||
|
{
|
||||||
|
"cluster_name": "es-testcluster",
|
||||||
|
"nodes": {
|
||||||
|
"SDFsfSDFsdfFSDSDfSFDSDF": {
|
||||||
|
"timestamp": 1436365550135,
|
||||||
|
"name": "test.host.com",
|
||||||
|
"transport_address": "inet[/127.0.0.1:9300]",
|
||||||
|
"host": "test",
|
||||||
|
"ip": [
|
||||||
|
"inet[/127.0.0.1:9300]",
|
||||||
|
"NONE"
|
||||||
|
],
|
||||||
|
"attributes": {
|
||||||
|
"master": "true"
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"timestamp": 1436460392945,
|
||||||
|
"open_file_descriptors": 160,
|
||||||
|
"cpu": {
|
||||||
|
"percent": 2,
|
||||||
|
"sys_in_millis": 1870,
|
||||||
|
"user_in_millis": 13610,
|
||||||
|
"total_in_millis": 15480
|
||||||
|
},
|
||||||
|
"mem": {
|
||||||
|
"total_virtual_in_bytes": 4747890688
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jvm": {
|
||||||
|
"timestamp": 1436460392945,
|
||||||
|
"uptime_in_millis": 202245,
|
||||||
|
"mem": {
|
||||||
|
"heap_used_in_bytes": 52709568,
|
||||||
|
"heap_used_percent": 5,
|
||||||
|
"heap_committed_in_bytes": 259522560,
|
||||||
|
"heap_max_in_bytes": 1038876672,
|
||||||
|
"non_heap_used_in_bytes": 39634576,
|
||||||
|
"non_heap_committed_in_bytes": 40841216,
|
||||||
|
"pools": {
|
||||||
|
"young": {
|
||||||
|
"used_in_bytes": 32685760,
|
||||||
|
"max_in_bytes": 279183360,
|
||||||
|
"peak_used_in_bytes": 71630848,
|
||||||
|
"peak_max_in_bytes": 279183360
|
||||||
|
},
|
||||||
|
"survivor": {
|
||||||
|
"used_in_bytes": 8912880,
|
||||||
|
"max_in_bytes": 34865152,
|
||||||
|
"peak_used_in_bytes": 8912888,
|
||||||
|
"peak_max_in_bytes": 34865152
|
||||||
|
},
|
||||||
|
"old": {
|
||||||
|
"used_in_bytes": 11110928,
|
||||||
|
"max_in_bytes": 724828160,
|
||||||
|
"peak_used_in_bytes": 14354608,
|
||||||
|
"peak_max_in_bytes": 724828160
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"threads": {
|
||||||
|
"count": 44,
|
||||||
|
"peak_count": 45
|
||||||
|
},
|
||||||
|
"gc": {
|
||||||
|
"collectors": {
|
||||||
|
"young": {
|
||||||
|
"collection_count": 2,
|
||||||
|
"collection_time_in_millis": 98
|
||||||
|
},
|
||||||
|
"old": {
|
||||||
|
"collection_count": 1,
|
||||||
|
"collection_time_in_millis": 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"buffer_pools": {
|
||||||
|
"direct": {
|
||||||
|
"count": 40,
|
||||||
|
"used_in_bytes": 6304239,
|
||||||
|
"total_capacity_in_bytes": 6304239
|
||||||
|
},
|
||||||
|
"mapped": {
|
||||||
|
"count": 0,
|
||||||
|
"used_in_bytes": 0,
|
||||||
|
"total_capacity_in_bytes": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
var nodestatsIndicesExpected = map[string]interface{}{
|
var nodestatsIndicesExpected = map[string]interface{}{
|
||||||
"id_cache_memory_size_in_bytes": float64(0),
|
"id_cache_memory_size_in_bytes": float64(0),
|
||||||
"completion_size_in_bytes": float64(0),
|
"completion_size_in_bytes": float64(0),
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf/testutil"
|
"github.com/influxdata/telegraf/testutil"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
@ -24,28 +26,24 @@ func TestGatherNoMd5(t *testing.T) {
|
||||||
tags1 := map[string]string{
|
tags1 := map[string]string{
|
||||||
"file": dir + "log1.log",
|
"file": dir + "log1.log",
|
||||||
}
|
}
|
||||||
fields1 := map[string]interface{}{
|
require.True(t, acc.HasPoint("filestat", tags1,
|
||||||
"size_bytes": int64(0),
|
"size_bytes", int64(0)))
|
||||||
"exists": int64(1),
|
require.True(t, acc.HasPoint("filestat", tags1,
|
||||||
}
|
"exists", int64(1)))
|
||||||
acc.AssertContainsTaggedFields(t, "filestat", fields1, tags1)
|
|
||||||
|
|
||||||
tags2 := map[string]string{
|
tags2 := map[string]string{
|
||||||
"file": dir + "log2.log",
|
"file": dir + "log2.log",
|
||||||
}
|
}
|
||||||
fields2 := map[string]interface{}{
|
require.True(t, acc.HasPoint("filestat", tags2,
|
||||||
"size_bytes": int64(0),
|
"size_bytes", int64(0)))
|
||||||
"exists": int64(1),
|
require.True(t, acc.HasPoint("filestat", tags2,
|
||||||
}
|
"exists", int64(1)))
|
||||||
acc.AssertContainsTaggedFields(t, "filestat", fields2, tags2)
|
|
||||||
|
|
||||||
tags3 := map[string]string{
|
tags3 := map[string]string{
|
||||||
"file": "/non/existant/file",
|
"file": "/non/existant/file",
|
||||||
}
|
}
|
||||||
fields3 := map[string]interface{}{
|
require.True(t, acc.HasPoint("filestat", tags3,
|
||||||
"exists": int64(0),
|
"exists", int64(0)))
|
||||||
}
|
|
||||||
acc.AssertContainsTaggedFields(t, "filestat", fields3, tags3)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGatherExplicitFiles(t *testing.T) {
|
func TestGatherExplicitFiles(t *testing.T) {
|
||||||
|
@ -64,30 +62,28 @@ func TestGatherExplicitFiles(t *testing.T) {
|
||||||
tags1 := map[string]string{
|
tags1 := map[string]string{
|
||||||
"file": dir + "log1.log",
|
"file": dir + "log1.log",
|
||||||
}
|
}
|
||||||
fields1 := map[string]interface{}{
|
require.True(t, acc.HasPoint("filestat", tags1,
|
||||||
"size_bytes": int64(0),
|
"size_bytes", int64(0)))
|
||||||
"exists": int64(1),
|
require.True(t, acc.HasPoint("filestat", tags1,
|
||||||
"md5_sum": "d41d8cd98f00b204e9800998ecf8427e",
|
"exists", int64(1)))
|
||||||
}
|
require.True(t, acc.HasPoint("filestat", tags1,
|
||||||
acc.AssertContainsTaggedFields(t, "filestat", fields1, tags1)
|
"md5_sum", "d41d8cd98f00b204e9800998ecf8427e"))
|
||||||
|
|
||||||
tags2 := map[string]string{
|
tags2 := map[string]string{
|
||||||
"file": dir + "log2.log",
|
"file": dir + "log2.log",
|
||||||
}
|
}
|
||||||
fields2 := map[string]interface{}{
|
require.True(t, acc.HasPoint("filestat", tags2,
|
||||||
"size_bytes": int64(0),
|
"size_bytes", int64(0)))
|
||||||
"exists": int64(1),
|
require.True(t, acc.HasPoint("filestat", tags2,
|
||||||
"md5_sum": "d41d8cd98f00b204e9800998ecf8427e",
|
"exists", int64(1)))
|
||||||
}
|
require.True(t, acc.HasPoint("filestat", tags2,
|
||||||
acc.AssertContainsTaggedFields(t, "filestat", fields2, tags2)
|
"md5_sum", "d41d8cd98f00b204e9800998ecf8427e"))
|
||||||
|
|
||||||
tags3 := map[string]string{
|
tags3 := map[string]string{
|
||||||
"file": "/non/existant/file",
|
"file": "/non/existant/file",
|
||||||
}
|
}
|
||||||
fields3 := map[string]interface{}{
|
require.True(t, acc.HasPoint("filestat", tags3,
|
||||||
"exists": int64(0),
|
"exists", int64(0)))
|
||||||
}
|
|
||||||
acc.AssertContainsTaggedFields(t, "filestat", fields3, tags3)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGatherGlob(t *testing.T) {
|
func TestGatherGlob(t *testing.T) {
|
||||||
|
@ -136,32 +132,32 @@ func TestGatherSuperAsterisk(t *testing.T) {
|
||||||
tags1 := map[string]string{
|
tags1 := map[string]string{
|
||||||
"file": dir + "log1.log",
|
"file": dir + "log1.log",
|
||||||
}
|
}
|
||||||
fields1 := map[string]interface{}{
|
require.True(t, acc.HasPoint("filestat", tags1,
|
||||||
"size_bytes": int64(0),
|
"size_bytes", int64(0)))
|
||||||
"exists": int64(1),
|
require.True(t, acc.HasPoint("filestat", tags1,
|
||||||
"md5_sum": "d41d8cd98f00b204e9800998ecf8427e",
|
"exists", int64(1)))
|
||||||
}
|
require.True(t, acc.HasPoint("filestat", tags1,
|
||||||
acc.AssertContainsTaggedFields(t, "filestat", fields1, tags1)
|
"md5_sum", "d41d8cd98f00b204e9800998ecf8427e"))
|
||||||
|
|
||||||
tags2 := map[string]string{
|
tags2 := map[string]string{
|
||||||
"file": dir + "log2.log",
|
"file": dir + "log2.log",
|
||||||
}
|
}
|
||||||
fields2 := map[string]interface{}{
|
require.True(t, acc.HasPoint("filestat", tags2,
|
||||||
"size_bytes": int64(0),
|
"size_bytes", int64(0)))
|
||||||
"exists": int64(1),
|
require.True(t, acc.HasPoint("filestat", tags2,
|
||||||
"md5_sum": "d41d8cd98f00b204e9800998ecf8427e",
|
"exists", int64(1)))
|
||||||
}
|
require.True(t, acc.HasPoint("filestat", tags2,
|
||||||
acc.AssertContainsTaggedFields(t, "filestat", fields2, tags2)
|
"md5_sum", "d41d8cd98f00b204e9800998ecf8427e"))
|
||||||
|
|
||||||
tags3 := map[string]string{
|
tags3 := map[string]string{
|
||||||
"file": dir + "test.conf",
|
"file": dir + "test.conf",
|
||||||
}
|
}
|
||||||
fields3 := map[string]interface{}{
|
require.True(t, acc.HasPoint("filestat", tags3,
|
||||||
"size_bytes": int64(104),
|
"size_bytes", int64(104)))
|
||||||
"exists": int64(1),
|
require.True(t, acc.HasPoint("filestat", tags3,
|
||||||
"md5_sum": "5a7e9b77fa25e7bb411dbd17cf403c1f",
|
"exists", int64(1)))
|
||||||
}
|
require.True(t, acc.HasPoint("filestat", tags3,
|
||||||
acc.AssertContainsTaggedFields(t, "filestat", fields3, tags3)
|
"md5_sum", "5a7e9b77fa25e7bb411dbd17cf403c1f"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetMd5(t *testing.T) {
|
func TestGetMd5(t *testing.T) {
|
||||||
|
|
|
@ -34,137 +34,82 @@ cpu_load_short,host=server06 value=12.0 1422568543702900257
|
||||||
emptyMsg = ""
|
emptyMsg = ""
|
||||||
|
|
||||||
serviceRootPEM = `-----BEGIN CERTIFICATE-----
|
serviceRootPEM = `-----BEGIN CERTIFICATE-----
|
||||||
MIIDRTCCAi2gAwIBAgIUenakcvMDj2URxBvUHBe0Mfhac0cwDQYJKoZIhvcNAQEL
|
MIIBxzCCATCgAwIBAgIJAOLq2g9+9TVgMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNV
|
||||||
BQAwGzEZMBcGA1UEAxMQdGVsZWdyYWYtdGVzdC1jYTAeFw0xNzA4MzEwNTE5NDNa
|
BAMMC1RlbGVncmFmIENBMB4XDTE3MTAwMjIyNDMwOFoXDTE3MTEwMTIyNDMwOFow
|
||||||
Fw0yNzA4MjkwNTIwMTNaMBsxGTAXBgNVBAMTEHRlbGVncmFmLXRlc3QtY2EwggEi
|
FjEUMBIGA1UEAwwLVGVsZWdyYWYgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ
|
||||||
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDxpDlUEC6LNXQMhvTtlWKUekwa
|
AoGBALHtGXLKZz3HUA4E1H0mR3gAtgNwUSRArxylCjQwO/7tFEYDFVCCPFzAF7G8
|
||||||
xh2OaiR16WvO8iA+sYmjlpFXOe+V6YWT+daOGujCqlGdrfDjj3C3pqFPJ6Q4VXaA
|
hzHyBNgx5FwNrH3bMEol9iIxzoZNU0XTWS7DzN4S+89C2Tn+NaFko/SeFBMp4IK/
|
||||||
xQyd0Ena7kRtuQ/IUSpTWxyrpSIzKL3dAoV0NYpjFWznjVMP3Rq4l+4cHqviZSvK
|
55YAgcYGe2QbFnPITGYPT05VkbSBMD0PBITNSwsclGZGFVoHAgMBAAGjHTAbMAwG
|
||||||
bWUK5n0vBGpEw3A22V9urhlSNkSbECvzn9EFHyIeJX603zaKXYw5wiDwCp1swbXW
|
A1UdEwQFMAMBAf8wCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBCwUAA4GBAIJpAA+X
|
||||||
2WS2h45JeI5xrpKcFmLaqRNe0swi6bkGnmefyCv7nsbOLeKyEW9AExDSd6nSLdu9
|
QB57JhNxevUlFFLmGx7ASKrOeZLupzak4qUK718erafMAsXhydx1eKL/5Ne7ZcFa
|
||||||
TGzhAfnfodcajSmKiQ+7YL9JY1bQ9hlfXk1ULg4riSEMKF+trZFZUanaXeeBAgMB
|
Tf6dRPzCjv89WzYK/kJ59AgATkXNPvADRUKd0ViQw4Q4EcfuQrTMEym+gl1W2qQl
|
||||||
AAGjgYAwfjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
|
U9/eBDE341pcrfdHHGhS5LKv6KTmjyYmDLxl
|
||||||
FgQUiPkCD8gEsSgIiV8jzACMoUZcHaIwHwYDVR0jBBgwFoAUiPkCD8gEsSgIiV8j
|
|
||||||
zACMoUZcHaIwGwYDVR0RBBQwEoIQdGVsZWdyYWYtdGVzdC1jYTANBgkqhkiG9w0B
|
|
||||||
AQsFAAOCAQEAXeadR7ZVkb2C0F8OEd2CQxVt2/JOqM4G2N2O8uTwf+hIn+qm+jbb
|
|
||||||
Q6JokGhr5Ybhvtv3U9JnI6RVI+TOYNkDzs5e2DtntFQmcKb2c+y5Z+OpvWd13ObK
|
|
||||||
GMCs4bho6O7h1qo1Z+Ftd6sYQ7JL0MuTGWCNbXv2c1iC4zPT54n1vGZC5so08RO0
|
|
||||||
r7bqLLEnkSawabvSAeTxtweCXJUw3D576e0sb8oU0AP/Hn/2IC9E1vFZdjDswEfs
|
|
||||||
ARE4Oc5XnN6sqjtp0q5CqPpW6tYFwxdtZFk0VYPXyRnETVgry7Dc/iX6mktIYUx+
|
|
||||||
qWSyPEDKALyxx6yUyVDqgcY2VUm0rM/1Iw==
|
|
||||||
-----END CERTIFICATE-----`
|
-----END CERTIFICATE-----`
|
||||||
serviceCertPEM = `-----BEGIN CERTIFICATE-----
|
serviceCertPEM = `-----BEGIN CERTIFICATE-----
|
||||||
MIIDKjCCAhKgAwIBAgIUVYjQKruuFavlMZvV7X6RRF4OyBowDQYJKoZIhvcNAQEL
|
MIIBzzCCATigAwIBAgIBATANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtUZWxl
|
||||||
BQAwGzEZMBcGA1UEAxMQdGVsZWdyYWYtdGVzdC1jYTAeFw0xNzA4MzEwNTM3MjRa
|
Z3JhZiBDQTAeFw0xNzEwMDIyMjQzMDhaFw0yNzA5MzAyMjQzMDhaMBQxEjAQBgNV
|
||||||
Fw0xNzA5MzAwNTM3NTRaMBQxEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZI
|
BAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAoI/8ceps
|
||||||
hvcNAQEBBQADggEPADCCAQoCggEBANojLHm+4ttLfl8xo4orZ436/o36wdQ30sWz
|
DvvA3KUDViYwZcB+RvfT6XCPCT35mEzuXWP42JHk1VPNA41215U8CGoJF7+OzRcZ
|
||||||
xE8eGejhARvCSNIR1Tau41Towq/MQVQQejQJRgqBSz7UEfzJNJGKKKc560j6fmTM
|
an3b2WLfAph+bi4Vmpe8eolmPHjf57jJ2fdDeLtMA4T0WF8yR4fHxrrU2UFsgXod
|
||||||
FHpFNZcTrNrTb0r3blUWF1oswhTgg313OXbVsz+E9tHkT1p/s9uURy3TJ3O/CFHq
|
kpQNqa/R5+iEKNMQVQgD2HjP5BE1u+H6fscCAwEAAaMvMC0wCQYDVR0TBAIwADAL
|
||||||
2vTiTQMTq31v0FEN1E/d6uzMhnGy5+QuRu/0A2iPpgXgPopYZwG5t4hN1KklM//l
|
BgNVHQ8EBAMCBSAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQAD
|
||||||
j2gMlX6mAYalctFOkDbhIe4/4dQcfT0sWA49KInZmUeB1RdyiNfCoXnDRZHocPIj
|
gYEAV5vx8FHNlD6Z3e01/MiNSmYXn93CwlixYMRyW1Ri2P6hMtJiMRp59fNFzroa
|
||||||
ltYAK/Igda0fdlMisoqh2ZMrCt8yhws7ycc12cFi7ZMv8zvi5p8CAwEAAaNtMGsw
|
iv6djr30uuKYOiAvdKhNaYWERgrtjGVEuPoIMQfaAaKHQj6CKLBXeGZ5Gxhy+M6G
|
||||||
EwYDVR0lBAwwCgYIKwYBBQUHAwEwHQYDVR0OBBYEFCdE87Nz7vPpgRmj++6J8rQR
|
OE6g0E4ufHOqr1h1GDIiAq88zyJ2AupgLLUCMFtkq0v0mr0=
|
||||||
0F/TMB8GA1UdIwQYMBaAFIj5Ag/IBLEoCIlfI8wAjKFGXB2iMBQGA1UdEQQNMAuC
|
|
||||||
CWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAIPhMYCsCPvOcvLLkahaZVn2g
|
|
||||||
ZbzPDplFhEsH1cpc7vd3GCV2EYjNTbBTDs5NlovSbJLf1DFB+gwsfEjhlFVZB3UQ
|
|
||||||
6GtuA5CQh/Skv8ngCDiLP50BbKF0CLa4Ia0xrSTAyRsg2rt9APphbej0yKqJ7j8U
|
|
||||||
1KK6rjOSnuzrKseex26VVovjPFq0FgkghWRm0xrAeizGTBCSEStZEPhk3pBo2x95
|
|
||||||
a32VPpmhlQMDyiV6m1cc9/MfxMisnyeLqJl8E9nziNa4/BgwwN9DcOp63D9OOa6A
|
|
||||||
brtLz8OXqvV+7gKlq+nASFDimXwFKRyqRH6ECyHNTE2K14KZb7+JTa0AUm6Nlw==
|
|
||||||
-----END CERTIFICATE-----`
|
-----END CERTIFICATE-----`
|
||||||
serviceKeyPEM = `-----BEGIN RSA PRIVATE KEY-----
|
serviceKeyPEM = `-----BEGIN RSA PRIVATE KEY-----
|
||||||
MIIEowIBAAKCAQEA2iMseb7i20t+XzGjiitnjfr+jfrB1DfSxbPETx4Z6OEBG8JI
|
MIICXAIBAAKBgQCgj/xx6mwO+8DcpQNWJjBlwH5G99PpcI8JPfmYTO5dY/jYkeTV
|
||||||
0hHVNq7jVOjCr8xBVBB6NAlGCoFLPtQR/Mk0kYoopznrSPp+ZMwUekU1lxOs2tNv
|
U80DjXbXlTwIagkXv47NFxlqfdvZYt8CmH5uLhWal7x6iWY8eN/nuMnZ90N4u0wD
|
||||||
SvduVRYXWizCFOCDfXc5dtWzP4T20eRPWn+z25RHLdMnc78IUera9OJNAxOrfW/Q
|
hPRYXzJHh8fGutTZQWyBeh2SlA2pr9Hn6IQo0xBVCAPYeM/kETW74fp+xwIDAQAB
|
||||||
UQ3UT93q7MyGcbLn5C5G7/QDaI+mBeA+ilhnAbm3iE3UqSUz/+WPaAyVfqYBhqVy
|
AoGABiRb6NOp3Ize3NHnJcWCNnI9omNalOR8ZEMdqCjROXtYiphSI6L4BbnEoQyR
|
||||||
0U6QNuEh7j/h1Bx9PSxYDj0oidmZR4HVF3KI18KhecNFkehw8iOW1gAr8iB1rR92
|
ZlUAEgt+3/ORQlScM12n4EaLF4Zi4CTGmibRHUff/ybUDGMg2Lp/AL/ghP/3U37l
|
||||||
UyKyiqHZkysK3zKHCzvJxzXZwWLtky/zO+LmnwIDAQABAoIBABD8MidcrK9kpndl
|
C/oRjohK9Rqn28hf8xgL9Jz+KbQaVv5f+frLwL3EKreYtOkCQQDLe1s89rbxvTZr
|
||||||
FxXYIV0V0SJfBx6uJhRM1hlO/7d5ZauyqhbpWo/CeGMRKK+lmOShz9Ijcre4r5I5
|
PhtwYrnXC8KbBNPIzJbTXrphqr0H3xuDlTpd+4tvIlL6LoqANYXAmHHlKUuPcar6
|
||||||
0xi61gQLHPVAdkidcKAKoAGRSAX2ezwiwIS21Xl8md7ko0wa20I2uVu+chGdGdbo
|
QCj9xNwTAkEAygDRac8qewqIWhZOs0u8phC37dxzwVXslrgjO+kTLxN/Q1srK45T
|
||||||
DyG91dRgLFauHWFO26f9QIVW5aY6ifyjg1fyxR/9n2YZfkqbjvASW4Mmfv5GR1aT
|
gHDbJuCrBPkYrjAXWHd2rIkOWl0rk38A/QJADct4HQLw1iSous6EF7Npu+19LPs/
|
||||||
mffajgsquy78PKs86f879iG+cfCzPYdoK+h7fsm4EEqDwK8JCsUIY1qN+Tuj5RQY
|
zF4qX3wNkK99jzoN6HbGdTandkpSa8mZ9CUswyjSl+Gb0Ma4+6w72zBsZwJBAKn+
|
||||||
zuIuD34+wywe7Jd1vwjQ40Cyilgtnu8Q8s8J05bXrD3mqer5nrqIGOX0vKgs+EXx
|
Zj0VCjrhcj3d5/0bD3bxOtgBXaimFqP/8ibIzkwfrEmSv5G4BK1iTAs7prBYsFxm
|
||||||
1hV+6ZECgYEA+950L2u8oPzNXu9BAL8Y5Tl384qj1+Cj/g28MuZFoPf/KU0HRN6l
|
PD9GyagI7vs8zR8jEkECQD51jhM8DDPah/ECC31we54Y9dqBOupy1a8y6os1YFkv
|
||||||
PBlXKaGP9iX+749tdiNPk5keIwOL8xCVXOpMLOA/jOlGODydG9rX67WCL/R1RcJR
|
BV7zTVrpOzwUsrkMW+wFyQSX9eyyMfJHJihlobXA+QY=
|
||||||
+Pip8dxO1ZNpOKHud06XLMuuVz9qNq0Xwb1VCzNTOxnEDwtXNyDm6OkCgYEA3bcW
|
|
||||||
hMeDNn85UA4n0ljcdCmEu12WS7L//jaAOWuPPfM3GgKEIic6hqrPWEuZlZtQnybx
|
|
||||||
L6qQgaWyCfl/8z0+5WynQqkVPz1j1dSrSKnemxyeySrmUcOH5UJfAKaG5PUd7H3t
|
|
||||||
oPTCxkbW3Bi2QLlgd4nb7+OEk6w0V9Zzv4AFHkcCgYBL/aD2Ub4WoE9iLjNhg0aC
|
|
||||||
mmUrcI/gaSFxXDmE7d7iIxC0KE5iI/6cdFTM9bbWoD4bjx2KgDrZIGBsVfyaeE1o
|
|
||||||
PDSBcaMa46LRAtCv/8YXkqrVxx6+zlMnF/dGRp7uZ0xeztSA4JBR7p4KKtLj7jN1
|
|
||||||
u6b1+yVIdoylsVk+A8pHSQKBgQCUcsn5DTyleHl/SHsRM74naMUeToMbHDaalxMz
|
|
||||||
XvkBmZ8DIzwlQe7FzAgYLkYfDWblqMVEDQfERpT2aL9qtU8vfZhf4aYAObJmsYYd
|
|
||||||
mN8bLAaE2txrUmfi8JV7cgRPuG7YsVgxtK/U4glqRIGCxJv6bat86vERjvNc/JFz
|
|
||||||
XtwOcQKBgF83Ov+EA9pL0AFs+BMiO+0SX/OqLX0TDMSqUKg3jjVfgl+BSBEZIsOu
|
|
||||||
g5jqHBx3Om/UyrXdn+lnMhyEgCuNkeC6057B5iGcWucTlnINeejXk/pnbvMtGjD1
|
|
||||||
OGWmdXhgLtKg6Edqm+9fnH0UJN6DRxRRCUfzMfbY8TRmLzZG2W34
|
|
||||||
-----END RSA PRIVATE KEY-----`
|
-----END RSA PRIVATE KEY-----`
|
||||||
clientRootPEM = `-----BEGIN CERTIFICATE-----
|
clientRootPEM = `-----BEGIN CERTIFICATE-----
|
||||||
MIIDRTCCAi2gAwIBAgIUenakcvMDj2URxBvUHBe0Mfhac0cwDQYJKoZIhvcNAQEL
|
MIIBxzCCATCgAwIBAgIJAOLq2g9+9TVgMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNV
|
||||||
BQAwGzEZMBcGA1UEAxMQdGVsZWdyYWYtdGVzdC1jYTAeFw0xNzA4MzEwNTE5NDNa
|
BAMMC1RlbGVncmFmIENBMB4XDTE3MTAwMjIyNDMwOFoXDTE3MTEwMTIyNDMwOFow
|
||||||
Fw0yNzA4MjkwNTIwMTNaMBsxGTAXBgNVBAMTEHRlbGVncmFmLXRlc3QtY2EwggEi
|
FjEUMBIGA1UEAwwLVGVsZWdyYWYgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ
|
||||||
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDxpDlUEC6LNXQMhvTtlWKUekwa
|
AoGBALHtGXLKZz3HUA4E1H0mR3gAtgNwUSRArxylCjQwO/7tFEYDFVCCPFzAF7G8
|
||||||
xh2OaiR16WvO8iA+sYmjlpFXOe+V6YWT+daOGujCqlGdrfDjj3C3pqFPJ6Q4VXaA
|
hzHyBNgx5FwNrH3bMEol9iIxzoZNU0XTWS7DzN4S+89C2Tn+NaFko/SeFBMp4IK/
|
||||||
xQyd0Ena7kRtuQ/IUSpTWxyrpSIzKL3dAoV0NYpjFWznjVMP3Rq4l+4cHqviZSvK
|
55YAgcYGe2QbFnPITGYPT05VkbSBMD0PBITNSwsclGZGFVoHAgMBAAGjHTAbMAwG
|
||||||
bWUK5n0vBGpEw3A22V9urhlSNkSbECvzn9EFHyIeJX603zaKXYw5wiDwCp1swbXW
|
A1UdEwQFMAMBAf8wCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBCwUAA4GBAIJpAA+X
|
||||||
2WS2h45JeI5xrpKcFmLaqRNe0swi6bkGnmefyCv7nsbOLeKyEW9AExDSd6nSLdu9
|
QB57JhNxevUlFFLmGx7ASKrOeZLupzak4qUK718erafMAsXhydx1eKL/5Ne7ZcFa
|
||||||
TGzhAfnfodcajSmKiQ+7YL9JY1bQ9hlfXk1ULg4riSEMKF+trZFZUanaXeeBAgMB
|
Tf6dRPzCjv89WzYK/kJ59AgATkXNPvADRUKd0ViQw4Q4EcfuQrTMEym+gl1W2qQl
|
||||||
AAGjgYAwfjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
|
U9/eBDE341pcrfdHHGhS5LKv6KTmjyYmDLxl
|
||||||
FgQUiPkCD8gEsSgIiV8jzACMoUZcHaIwHwYDVR0jBBgwFoAUiPkCD8gEsSgIiV8j
|
|
||||||
zACMoUZcHaIwGwYDVR0RBBQwEoIQdGVsZWdyYWYtdGVzdC1jYTANBgkqhkiG9w0B
|
|
||||||
AQsFAAOCAQEAXeadR7ZVkb2C0F8OEd2CQxVt2/JOqM4G2N2O8uTwf+hIn+qm+jbb
|
|
||||||
Q6JokGhr5Ybhvtv3U9JnI6RVI+TOYNkDzs5e2DtntFQmcKb2c+y5Z+OpvWd13ObK
|
|
||||||
GMCs4bho6O7h1qo1Z+Ftd6sYQ7JL0MuTGWCNbXv2c1iC4zPT54n1vGZC5so08RO0
|
|
||||||
r7bqLLEnkSawabvSAeTxtweCXJUw3D576e0sb8oU0AP/Hn/2IC9E1vFZdjDswEfs
|
|
||||||
ARE4Oc5XnN6sqjtp0q5CqPpW6tYFwxdtZFk0VYPXyRnETVgry7Dc/iX6mktIYUx+
|
|
||||||
qWSyPEDKALyxx6yUyVDqgcY2VUm0rM/1Iw==
|
|
||||||
-----END CERTIFICATE-----`
|
-----END CERTIFICATE-----`
|
||||||
clientCertPEM = `-----BEGIN CERTIFICATE-----
|
clientCertPEM = `-----BEGIN CERTIFICATE-----
|
||||||
MIIDMDCCAhigAwIBAgIUIVOF5g2zH6+J/dbGdu4q18aSJoMwDQYJKoZIhvcNAQEL
|
MIIBzjCCATegAwIBAgIBAjANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtUZWxl
|
||||||
BQAwGzEZMBcGA1UEAxMQdGVsZWdyYWYtdGVzdC1jYTAeFw0xNzA4MzEwNTQ1MzJa
|
Z3JhZiBDQTAeFw0xNzEwMDIyMjQzMDhaFw0yNzA5MzAyMjQzMDhaMBMxETAPBgNV
|
||||||
Fw0yNzA4MjUwMTQ2MDJaMBcxFTATBgNVBAMTDGR1bW15LWNsaWVudDCCASIwDQYJ
|
BAMMCHRlbGVncmFmMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDIrPGv8Sm1
|
||||||
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAKok1HJ40buyjrS+DG9ORLzrWIJad2y/
|
6tI+vlATzWGOK1D40iNTiGj4FpcS2Tm4SdaDSfa3VL9N5l8aeuN4E8O2YXK3QcR8
|
||||||
6X2Bg9MSENfpEUgaS7nK2ML3m1e2poHqBSR+V8VECNs+MDCLSOeQ4FC1TdBKMLfw
|
NoeY87cWW06PtFc/ByS42VeWDKt28/DpGzbrzCVNOumS3X5QEyySYLpi0uqI9ZZ5
|
||||||
NxW88y5Gj6rTRcAXl092ba7stwbqJPBAZu1Eh1jXIp5nrFKh8Jq7kRxmMB5vC70V
|
O2sOJ2yVua8F3cwqPTveVmU3LeQfVrh7QwIDAQABoy8wLTAJBgNVHRMEAjAAMAsG
|
||||||
fOSPS0RZtEd7D+QZ6jgkFJWsZzn4gJr8nc/kmLcntLw+g/tz9/8lfaV306tLlhMH
|
A1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAjANBgkqhkiG9w0BAQsFAAOB
|
||||||
dv3Ka6Nt86j6/muOwvoeAkAnCEFAgDcXg4F37PFAiEHRw9DyTeWDuZqvnMZ3gosL
|
gQAVEfHePY9fumW8rkbbSbiuQ1dGIINbMGPO17eAjOxMT4Z1jDb8oTVHbaZM0rKo
|
||||||
kl15QhnP0yG2QCjSb1gaLcKB42cyxDnPc31WsVuuzQnajazcVf3lJW0CAwEAAaNw
|
wKx4dDp5mnLK+NuMZ1sNxKOf6IMmQ022ANOYM0dkwfg13bpC3BGW8Z7nOFK0xXh6
|
||||||
MG4wEwYDVR0lBAwwCgYIKwYBBQUHAwIwHQYDVR0OBBYEFCemMO+Qlj+YCLQ3ScAQ
|
4KTcXktBUtubmn6w7szvWY2OajPVoiGgcapwwhCrBEa6rg==
|
||||||
8XYJJJ5ZMB8GA1UdIwQYMBaAFIj5Ag/IBLEoCIlfI8wAjKFGXB2iMBcGA1UdEQQQ
|
|
||||||
MA6CDGR1bW15LWNsaWVudDANBgkqhkiG9w0BAQsFAAOCAQEARThbApKvvGDp7uSc
|
|
||||||
mINaqDOHe69F9PepV0/3+B5+X1b3yd2sbzZL/ZoHl27kajSHVrUF+09gcTosfuY3
|
|
||||||
omnIPw+NseqTJG+qTMRb3AarLNO46EJZLOowAEhnJyVmhK5uU0YqhV1X9eN+g4/o
|
|
||||||
BuyOPvHj6UJWviZFy6fDIj2N+ygN/CNP5X3iLDBUoyCEHAehLiQr0aRgsqe4JLlS
|
|
||||||
P+0l0btTUpcqUhsQy+sD2lv3MO1tZ/P4zhzu0J0LUeLBDdOPf/FIvTgkCNxN9GGy
|
|
||||||
SLmeBeCzsKmWbzE3Yuahw3h4IblVyyGc7ZDGIobDrZgFqshcZylU8wrsjUnjNSPA
|
|
||||||
G+LOWQ==
|
|
||||||
-----END CERTIFICATE-----`
|
-----END CERTIFICATE-----`
|
||||||
clientKeyPEM = `-----BEGIN RSA PRIVATE KEY-----
|
clientKeyPEM = `-----BEGIN RSA PRIVATE KEY-----
|
||||||
MIIEogIBAAKCAQEAqiTUcnjRu7KOtL4Mb05EvOtYglp3bL/pfYGD0xIQ1+kRSBpL
|
MIICXgIBAAKBgQDIrPGv8Sm16tI+vlATzWGOK1D40iNTiGj4FpcS2Tm4SdaDSfa3
|
||||||
ucrYwvebV7amgeoFJH5XxUQI2z4wMItI55DgULVN0Eowt/A3FbzzLkaPqtNFwBeX
|
VL9N5l8aeuN4E8O2YXK3QcR8NoeY87cWW06PtFc/ByS42VeWDKt28/DpGzbrzCVN
|
||||||
T3Ztruy3Buok8EBm7USHWNcinmesUqHwmruRHGYwHm8LvRV85I9LRFm0R3sP5Bnq
|
OumS3X5QEyySYLpi0uqI9ZZ5O2sOJ2yVua8F3cwqPTveVmU3LeQfVrh7QwIDAQAB
|
||||||
OCQUlaxnOfiAmvydz+SYtye0vD6D+3P3/yV9pXfTq0uWEwd2/cpro23zqPr+a47C
|
AoGAHtvpdqLhRSZNGnTtn33vyIsEsp6t7ASID855gN6Cr8I7CIlxNRQFLxeD/HB1
|
||||||
+h4CQCcIQUCANxeDgXfs8UCIQdHD0PJN5YO5mq+cxneCiwuSXXlCGc/TIbZAKNJv
|
VlvDtuIZX/DvJCLGi1C/EOMNm2nY7IT2gZgMpxvmfjfGhHKT1MWYu9cdyiOOacqD
|
||||||
WBotwoHjZzLEOc9zfVaxW67NCdqNrNxV/eUlbQIDAQABAoIBAAXZYEhTKPqn58oE
|
yRDAcKpubIPEIV3aczglv9sVApXwZcgePzDwweTVfP/Nv5ECQQDthIv5Y5k3UO8h
|
||||||
4o6NBUXtXUyV6ZcefdtnsW13KIcTpxlwdfv8IjmJo5h/WfgLYIPhqAjLDvbii2uP
|
Hht+27W8McFJ5eiF5OcLGOQ4nKGHkCOskfD4u/i+j+4dUeGBdpT8CzszgofBa6wh
|
||||||
zkDPtTZxFSy88DHSm0IvDbkgid3Yh4RUC0qbCqhB0QT21bBAtokfmvuN4c3KSJ1K
|
dJevQerVAkEA2Ep8PUfXRjel8NiLNL8iK/SR26y8wPduKam31SMUPq71+GROKkFz
|
||||||
nefj3Ng6Fxtku+WTMIj2+CJwZwcyAH47ZUngYs/77gA0hAJcbdL/bj8Bpmd+lH6C
|
yYYAbKORs+fS6LBT+M48cEu470o+g8eptwJBALzCEMeSOqp2XIRSPAG2NBiq5fSH
|
||||||
Ci22T2hrw+cpWMN6qwa3wxWIneCaqxkylSgpUzSNE0QO3mXkX+NYtL2BQ0w+wPqq
|
jSIThvYPwxemisyEZYV4uivCnu06zz5n2zIa/k3L0zGdc6vomPRBh2aVmT0CQQCY
|
||||||
lww3QJOFAX1qRLflglL9K+ruTQofm49vxv6apsoqdkrxEBoPzkljlqiPRmzUxau4
|
/B5ibfUbqnLKJzBXb7Xo50Vf3w9nYdvexjfMHtLL/47lUXVkOAWBDjIwpYWCfb/V
|
||||||
cvbApQECgYEAy5m5O3mQt6DBrDRJWSwpZh6DRNd5USEqXOIFtp+Qze2Jx1pYQfZt
|
bBsJCj7/ot+9CYOsTEaDAkEA4XAGFxx78JMVuJLjevkf0pGUPEocdoOAvpYWT5sR
|
||||||
NOXOrwy04o0+6yLzc4O4W5ta2KfTlALFzCa6Na3Ca4ZUAeteWprrdh8b1b2w/wUH
|
9FODrPEtW84ZevSmuByjzeqVzS3ElIxACopRJgSN20d9vg==
|
||||||
E3uQFkvH0zFdPsA3pTTZ0k/ydmHnu4zZqBnSeh0dIW8xFYgZZCgQusECgYEA1e7O
|
|
||||||
ujCUa8y49sY42D/Y/c8B96xVfJZO5hhY7eLgkzqUlmFl31Ld7AjlJcXpbMeW1vaa
|
|
||||||
0Mxbfx2qAVaZEkvdnXq3V8spe6qOGBdlKzey4DMEfmEXLFp5DRYCSwpXiqDZcGqc
|
|
||||||
jwI58wuzKoDgydN9bLdF8XYGtQXnHIE9WyTYMa0CgYBKYSBgb+rEir/2LyvUneOJ
|
|
||||||
4P/HuIgjcWBOimvX6bc2495/q6uufV4sAwBcxuGWGk+wCxaxTp+dJ8YqfDU5T0H/
|
|
||||||
cO56Cb6LFYm/IcNYilwWzQqYLTJqF+Yb4fojiw+3QcN01zf87K/eu0IyqVXFGJGz
|
|
||||||
bauM3PH1cu+VlCDijBiAgQKBgDOQ9YmRriTx2t+41fjiIvbC0BGYG58FSA1UbxMg
|
|
||||||
LcuvQiOhZIHZIp8DYeCh/Or4jRZRqO2NZLyWNOVPr2Pmn4uXCdyCnwQtD0UlVoB9
|
|
||||||
U4ORKJMh6gkJ4cXSuUjHPGSw8tiTChu6iKdZ+ZzUJdrgPIpY/uX98g3uV0/aoyR2
|
|
||||||
FBqdAoGAQIrcOsTpCe6l3ZDtQyNIeAj1s7sZyW8MBy95RXw3y/yzVEOAu4yWNobj
|
|
||||||
RReeHQEsrQq+sJ/cols8HfoOpGpL3U0IGDi5vr1JlOXmBhFX2xuFrfh3jvgXlUqb
|
|
||||||
fqxPcT3d7I/UEi0ueDh3osyTn46mDfRfF7HBLBNeyQbIFWBDDus=
|
|
||||||
-----END RSA PRIVATE KEY-----`
|
-----END RSA PRIVATE KEY-----`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -98,6 +98,7 @@ func (h *HTTPResponse) createHttpClient() (*http.Client, error) {
|
||||||
}
|
}
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
DisableKeepAlives: true,
|
DisableKeepAlives: true,
|
||||||
TLSClientConfig: tlsCfg,
|
TLSClientConfig: tlsCfg,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
# Telegraf ipmi plugin
|
# IPMI Sensor Input Plugin
|
||||||
|
|
||||||
Get bare metal metrics using the command line utility `ipmitool`
|
Get bare metal metrics using the command line utility
|
||||||
|
[`ipmitool`](https://sourceforge.net/projects/ipmitool/files/ipmitool/).
|
||||||
see ipmitool(https://sourceforge.net/projects/ipmitool/files/ipmitool/)
|
|
||||||
|
|
||||||
If no servers are specified, the plugin will query the local machine sensor stats via the following command:
|
If no servers are specified, the plugin will query the local machine sensor stats via the following command:
|
||||||
|
|
||||||
|
@ -16,18 +15,7 @@ When one or more servers are specified, the plugin will use the following comman
|
||||||
ipmitool -I lan -H SERVER -U USERID -P PASSW0RD sdr
|
ipmitool -I lan -H SERVER -U USERID -P PASSW0RD sdr
|
||||||
```
|
```
|
||||||
|
|
||||||
## Measurements
|
### Configuration
|
||||||
|
|
||||||
- ipmi_sensor:
|
|
||||||
|
|
||||||
* Tags: `name`, `unit`
|
|
||||||
* Fields:
|
|
||||||
- status
|
|
||||||
- value
|
|
||||||
|
|
||||||
The `server` tag will be made available when retrieving stats from remote server(s).
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
# Read metrics from the bare metal servers via IPMI
|
# Read metrics from the bare metal servers via IPMI
|
||||||
|
@ -52,26 +40,49 @@ The `server` tag will be made available when retrieving stats from remote server
|
||||||
timeout = "20s"
|
timeout = "20s"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Output
|
### Measurements
|
||||||
|
|
||||||
|
- ipmi_sensor:
|
||||||
|
- tags:
|
||||||
|
- name
|
||||||
|
- unit
|
||||||
|
- server (only when retrieving stats from remote servers)
|
||||||
|
- fields:
|
||||||
|
- status (int)
|
||||||
|
- value (float)
|
||||||
|
|
||||||
|
|
||||||
|
#### Permissions
|
||||||
|
|
||||||
|
When gathering from the local system, Telegraf will need permission to the
|
||||||
|
ipmi device node. When using udev you can create the device node giving
|
||||||
|
`rw` permissions to the `telegraf` user by adding the following rule to
|
||||||
|
`/etc/udev/rules.d/52-telegraf-ipmi.rules`:
|
||||||
|
|
||||||
|
```
|
||||||
|
KERNEL=="ipmi*", MODE="660", GROUP="telegraf"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example Output
|
||||||
|
|
||||||
When retrieving stats from a remote server:
|
When retrieving stats from a remote server:
|
||||||
```
|
```
|
||||||
> ipmi_sensor,server=10.20.2.203,unit=degrees_c,name=ambient_temp status=1i,value=20 1458488465012559455
|
ipmi_sensor,server=10.20.2.203,unit=degrees_c,name=ambient_temp status=1i,value=20 1458488465012559455
|
||||||
> ipmi_sensor,server=10.20.2.203,unit=feet,name=altitude status=1i,value=80 1458488465012688613
|
ipmi_sensor,server=10.20.2.203,unit=feet,name=altitude status=1i,value=80 1458488465012688613
|
||||||
> ipmi_sensor,server=10.20.2.203,unit=watts,name=avg_power status=1i,value=220 1458488465012776511
|
ipmi_sensor,server=10.20.2.203,unit=watts,name=avg_power status=1i,value=220 1458488465012776511
|
||||||
> ipmi_sensor,server=10.20.2.203,unit=volts,name=planar_3.3v status=1i,value=3.28 1458488465012861875
|
ipmi_sensor,server=10.20.2.203,unit=volts,name=planar_3.3v status=1i,value=3.28 1458488465012861875
|
||||||
> ipmi_sensor,server=10.20.2.203,unit=volts,name=planar_vbat status=1i,value=3.04 1458488465013072508
|
ipmi_sensor,server=10.20.2.203,unit=volts,name=planar_vbat status=1i,value=3.04 1458488465013072508
|
||||||
> ipmi_sensor,server=10.20.2.203,unit=rpm,name=fan_1a_tach status=1i,value=2610 1458488465013137932
|
ipmi_sensor,server=10.20.2.203,unit=rpm,name=fan_1a_tach status=1i,value=2610 1458488465013137932
|
||||||
> ipmi_sensor,server=10.20.2.203,unit=rpm,name=fan_1b_tach status=1i,value=1775 1458488465013279896
|
ipmi_sensor,server=10.20.2.203,unit=rpm,name=fan_1b_tach status=1i,value=1775 1458488465013279896
|
||||||
```
|
```
|
||||||
|
|
||||||
When retrieving stats from the local machine (no server specified):
|
When retrieving stats from the local machine (no server specified):
|
||||||
```
|
```
|
||||||
> ipmi_sensor,unit=degrees_c,name=ambient_temp status=1i,value=20 1458488465012559455
|
ipmi_sensor,unit=degrees_c,name=ambient_temp status=1i,value=20 1458488465012559455
|
||||||
> ipmi_sensor,unit=feet,name=altitude status=1i,value=80 1458488465012688613
|
ipmi_sensor,unit=feet,name=altitude status=1i,value=80 1458488465012688613
|
||||||
> ipmi_sensor,unit=watts,name=avg_power status=1i,value=220 1458488465012776511
|
ipmi_sensor,unit=watts,name=avg_power status=1i,value=220 1458488465012776511
|
||||||
> ipmi_sensor,unit=volts,name=planar_3.3v status=1i,value=3.28 1458488465012861875
|
ipmi_sensor,unit=volts,name=planar_3.3v status=1i,value=3.28 1458488465012861875
|
||||||
> ipmi_sensor,unit=volts,name=planar_vbat status=1i,value=3.04 1458488465013072508
|
ipmi_sensor,unit=volts,name=planar_vbat status=1i,value=3.04 1458488465013072508
|
||||||
> ipmi_sensor,unit=rpm,name=fan_1a_tach status=1i,value=2610 1458488465013137932
|
ipmi_sensor,unit=rpm,name=fan_1a_tach status=1i,value=2610 1458488465013137932
|
||||||
> ipmi_sensor,unit=rpm,name=fan_1b_tach status=1i,value=1775 1458488465013279896
|
ipmi_sensor,unit=rpm,name=fan_1b_tach status=1i,value=1775 1458488465013279896
|
||||||
```
|
```
|
||||||
|
|
|
@ -35,7 +35,7 @@ var sampleConfig = `
|
||||||
##
|
##
|
||||||
# servers = ["USERID:PASSW0RD@lan(192.168.1.1)"]
|
# servers = ["USERID:PASSW0RD@lan(192.168.1.1)"]
|
||||||
|
|
||||||
## Recomended: use metric 'interval' that is a multiple of 'timeout' to avoid
|
## Recommended: use metric 'interval' that is a multiple of 'timeout' to avoid
|
||||||
## gaps or overlap in pulled data
|
## gaps or overlap in pulled data
|
||||||
interval = "30s"
|
interval = "30s"
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,7 @@ func TestIptables_Gather(t *testing.T) {
|
||||||
K 4520 RETURN tcp -- * * 0.0.0.0/0 0.0.0.0/0
|
K 4520 RETURN tcp -- * * 0.0.0.0/0 0.0.0.0/0
|
||||||
`},
|
`},
|
||||||
},
|
},
|
||||||
{ // 8 - Multiple rows, multipe chains => no error
|
{ // 8 - Multiple rows, multiple chains => no error
|
||||||
table: "filter",
|
table: "filter",
|
||||||
chains: []string{"INPUT", "FORWARD"},
|
chains: []string{"INPUT", "FORWARD"},
|
||||||
values: []string{
|
values: []string{
|
||||||
|
|
|
@ -3,8 +3,6 @@ package leofs
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net/url"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -19,7 +17,7 @@ import (
|
||||||
const oid = ".1.3.6.1.4.1.35450"
|
const oid = ".1.3.6.1.4.1.35450"
|
||||||
|
|
||||||
// For Manager Master
|
// For Manager Master
|
||||||
const defaultEndpoint = "udp://127.0.0.1:4020"
|
const defaultEndpoint = "127.0.0.1:4020"
|
||||||
|
|
||||||
type ServerType int
|
type ServerType int
|
||||||
|
|
||||||
|
@ -137,8 +135,8 @@ var serverTypeMapping = map[string]ServerType{
|
||||||
|
|
||||||
var sampleConfig = `
|
var sampleConfig = `
|
||||||
## An array of URLs of the form:
|
## An array of URLs of the form:
|
||||||
## "udp://" host [ ":" port]
|
## host [ ":" port]
|
||||||
servers = ["udp://127.0.0.1:4020"]
|
servers = ["127.0.0.1:4020"]
|
||||||
`
|
`
|
||||||
|
|
||||||
func (l *LeoFS) SampleConfig() string {
|
func (l *LeoFS) SampleConfig() string {
|
||||||
|
@ -155,28 +153,22 @@ func (l *LeoFS) Gather(acc telegraf.Accumulator) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
for i, endpoint := range l.Servers {
|
for _, endpoint := range l.Servers {
|
||||||
if !strings.HasPrefix(endpoint, "udp://") {
|
results := strings.Split(endpoint, ":")
|
||||||
// Preserve backwards compatibility for hostnames without a
|
|
||||||
// scheme, broken in go 1.8. Remove in Telegraf 2.0
|
port := "4020"
|
||||||
endpoint = "udp://" + endpoint
|
if len(results) > 2 {
|
||||||
log.Printf("W! [inputs.mongodb] Using %q as connection URL; please update your configuration to use an URL", endpoint)
|
|
||||||
l.Servers[i] = endpoint
|
|
||||||
}
|
|
||||||
u, err := url.Parse(endpoint)
|
|
||||||
if err != nil {
|
|
||||||
acc.AddError(fmt.Errorf("Unable to parse address %q: %s", endpoint, err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if u.Host == "" {
|
|
||||||
acc.AddError(fmt.Errorf("Unable to parse address %q", endpoint))
|
acc.AddError(fmt.Errorf("Unable to parse address %q", endpoint))
|
||||||
continue
|
continue
|
||||||
|
} else if len(results) == 2 {
|
||||||
|
if _, err := strconv.Atoi(results[1]); err == nil {
|
||||||
|
port = results[1]
|
||||||
|
} else {
|
||||||
|
acc.AddError(fmt.Errorf("Unable to parse port from %q", endpoint))
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
port := u.Port()
|
|
||||||
if port == "" {
|
|
||||||
port = "4020"
|
|
||||||
}
|
|
||||||
st, ok := serverTypeMapping[port]
|
st, ok := serverTypeMapping[port]
|
||||||
if !ok {
|
if !ok {
|
||||||
st = ServerTypeStorage
|
st = ServerTypeStorage
|
||||||
|
@ -196,7 +188,7 @@ func (l *LeoFS) gatherServer(
|
||||||
serverType ServerType,
|
serverType ServerType,
|
||||||
acc telegraf.Accumulator,
|
acc telegraf.Accumulator,
|
||||||
) error {
|
) error {
|
||||||
cmd := exec.Command("snmpwalk", "-v2c", "-cpublic", endpoint, oid)
|
cmd := exec.Command("snmpwalk", "-v2c", "-cpublic", "-On", endpoint, oid)
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -16,21 +16,21 @@ package main
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
const output = ` + "`" + `iso.3.6.1.4.1.35450.15.1.0 = STRING: "manager_888@127.0.0.1"
|
const output = ` + "`" + `.1.3.6.1.4.1.35450.15.1.0 = STRING: "manager_888@127.0.0.1"
|
||||||
iso.3.6.1.4.1.35450.15.2.0 = Gauge32: 186
|
.1.3.6.1.4.1.35450.15.2.0 = Gauge32: 186
|
||||||
iso.3.6.1.4.1.35450.15.3.0 = Gauge32: 46235519
|
.1.3.6.1.4.1.35450.15.3.0 = Gauge32: 46235519
|
||||||
iso.3.6.1.4.1.35450.15.4.0 = Gauge32: 32168525
|
.1.3.6.1.4.1.35450.15.4.0 = Gauge32: 32168525
|
||||||
iso.3.6.1.4.1.35450.15.5.0 = Gauge32: 14066068
|
.1.3.6.1.4.1.35450.15.5.0 = Gauge32: 14066068
|
||||||
iso.3.6.1.4.1.35450.15.6.0 = Gauge32: 5512968
|
.1.3.6.1.4.1.35450.15.6.0 = Gauge32: 5512968
|
||||||
iso.3.6.1.4.1.35450.15.7.0 = Gauge32: 186
|
.1.3.6.1.4.1.35450.15.7.0 = Gauge32: 186
|
||||||
iso.3.6.1.4.1.35450.15.8.0 = Gauge32: 46269006
|
.1.3.6.1.4.1.35450.15.8.0 = Gauge32: 46269006
|
||||||
iso.3.6.1.4.1.35450.15.9.0 = Gauge32: 32202867
|
.1.3.6.1.4.1.35450.15.9.0 = Gauge32: 32202867
|
||||||
iso.3.6.1.4.1.35450.15.10.0 = Gauge32: 14064995
|
.1.3.6.1.4.1.35450.15.10.0 = Gauge32: 14064995
|
||||||
iso.3.6.1.4.1.35450.15.11.0 = Gauge32: 5492634
|
.1.3.6.1.4.1.35450.15.11.0 = Gauge32: 5492634
|
||||||
iso.3.6.1.4.1.35450.15.12.0 = Gauge32: 60
|
.1.3.6.1.4.1.35450.15.12.0 = Gauge32: 60
|
||||||
iso.3.6.1.4.1.35450.15.13.0 = Gauge32: 43515904
|
.1.3.6.1.4.1.35450.15.13.0 = Gauge32: 43515904
|
||||||
iso.3.6.1.4.1.35450.15.14.0 = Gauge32: 60
|
.1.3.6.1.4.1.35450.15.14.0 = Gauge32: 60
|
||||||
iso.3.6.1.4.1.35450.15.15.0 = Gauge32: 43533983` + "`" +
|
.1.3.6.1.4.1.35450.15.15.0 = Gauge32: 43533983` + "`" +
|
||||||
`
|
`
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Println(output)
|
fmt.Println(output)
|
||||||
|
@ -42,34 +42,34 @@ package main
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
const output = ` + "`" + `iso.3.6.1.4.1.35450.34.1.0 = STRING: "storage_0@127.0.0.1"
|
const output = ` + "`" + `.1.3.6.1.4.1.35450.34.1.0 = STRING: "storage_0@127.0.0.1"
|
||||||
iso.3.6.1.4.1.35450.34.2.0 = Gauge32: 512
|
.1.3.6.1.4.1.35450.34.2.0 = Gauge32: 512
|
||||||
iso.3.6.1.4.1.35450.34.3.0 = Gauge32: 38126307
|
.1.3.6.1.4.1.35450.34.3.0 = Gauge32: 38126307
|
||||||
iso.3.6.1.4.1.35450.34.4.0 = Gauge32: 22308716
|
.1.3.6.1.4.1.35450.34.4.0 = Gauge32: 22308716
|
||||||
iso.3.6.1.4.1.35450.34.5.0 = Gauge32: 15816448
|
.1.3.6.1.4.1.35450.34.5.0 = Gauge32: 15816448
|
||||||
iso.3.6.1.4.1.35450.34.6.0 = Gauge32: 5232008
|
.1.3.6.1.4.1.35450.34.6.0 = Gauge32: 5232008
|
||||||
iso.3.6.1.4.1.35450.34.7.0 = Gauge32: 512
|
.1.3.6.1.4.1.35450.34.7.0 = Gauge32: 512
|
||||||
iso.3.6.1.4.1.35450.34.8.0 = Gauge32: 38113176
|
.1.3.6.1.4.1.35450.34.8.0 = Gauge32: 38113176
|
||||||
iso.3.6.1.4.1.35450.34.9.0 = Gauge32: 22313398
|
.1.3.6.1.4.1.35450.34.9.0 = Gauge32: 22313398
|
||||||
iso.3.6.1.4.1.35450.34.10.0 = Gauge32: 15798779
|
.1.3.6.1.4.1.35450.34.10.0 = Gauge32: 15798779
|
||||||
iso.3.6.1.4.1.35450.34.11.0 = Gauge32: 5237315
|
.1.3.6.1.4.1.35450.34.11.0 = Gauge32: 5237315
|
||||||
iso.3.6.1.4.1.35450.34.12.0 = Gauge32: 191
|
.1.3.6.1.4.1.35450.34.12.0 = Gauge32: 191
|
||||||
iso.3.6.1.4.1.35450.34.13.0 = Gauge32: 824
|
.1.3.6.1.4.1.35450.34.13.0 = Gauge32: 824
|
||||||
iso.3.6.1.4.1.35450.34.14.0 = Gauge32: 0
|
.1.3.6.1.4.1.35450.34.14.0 = Gauge32: 0
|
||||||
iso.3.6.1.4.1.35450.34.15.0 = Gauge32: 50105
|
.1.3.6.1.4.1.35450.34.15.0 = Gauge32: 50105
|
||||||
iso.3.6.1.4.1.35450.34.16.0 = Gauge32: 196654
|
.1.3.6.1.4.1.35450.34.16.0 = Gauge32: 196654
|
||||||
iso.3.6.1.4.1.35450.34.17.0 = Gauge32: 0
|
.1.3.6.1.4.1.35450.34.17.0 = Gauge32: 0
|
||||||
iso.3.6.1.4.1.35450.34.18.0 = Gauge32: 2052
|
.1.3.6.1.4.1.35450.34.18.0 = Gauge32: 2052
|
||||||
iso.3.6.1.4.1.35450.34.19.0 = Gauge32: 50296
|
.1.3.6.1.4.1.35450.34.19.0 = Gauge32: 50296
|
||||||
iso.3.6.1.4.1.35450.34.20.0 = Gauge32: 35
|
.1.3.6.1.4.1.35450.34.20.0 = Gauge32: 35
|
||||||
iso.3.6.1.4.1.35450.34.21.0 = Gauge32: 898
|
.1.3.6.1.4.1.35450.34.21.0 = Gauge32: 898
|
||||||
iso.3.6.1.4.1.35450.34.22.0 = Gauge32: 0
|
.1.3.6.1.4.1.35450.34.22.0 = Gauge32: 0
|
||||||
iso.3.6.1.4.1.35450.34.23.0 = Gauge32: 0
|
.1.3.6.1.4.1.35450.34.23.0 = Gauge32: 0
|
||||||
iso.3.6.1.4.1.35450.34.24.0 = Gauge32: 0
|
.1.3.6.1.4.1.35450.34.24.0 = Gauge32: 0
|
||||||
iso.3.6.1.4.1.35450.34.31.0 = Gauge32: 51
|
.1.3.6.1.4.1.35450.34.31.0 = Gauge32: 51
|
||||||
iso.3.6.1.4.1.35450.34.32.0 = Gauge32: 53219328
|
.1.3.6.1.4.1.35450.34.32.0 = Gauge32: 53219328
|
||||||
iso.3.6.1.4.1.35450.34.33.0 = Gauge32: 51
|
.1.3.6.1.4.1.35450.34.33.0 = Gauge32: 51
|
||||||
iso.3.6.1.4.1.35450.34.34.0 = Gauge32: 53351083` + "`" +
|
.1.3.6.1.4.1.35450.34.34.0 = Gauge32: 53351083` + "`" +
|
||||||
`
|
`
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Println(output)
|
fmt.Println(output)
|
||||||
|
@ -81,31 +81,31 @@ package main
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
const output = ` + "`" + `iso.3.6.1.4.1.35450.34.1.0 = STRING: "gateway_0@127.0.0.1"
|
const output = ` + "`" + `.1.3.6.1.4.1.35450.34.1.0 = STRING: "gateway_0@127.0.0.1"
|
||||||
iso.3.6.1.4.1.35450.34.2.0 = Gauge32: 465
|
.1.3.6.1.4.1.35450.34.2.0 = Gauge32: 465
|
||||||
iso.3.6.1.4.1.35450.34.3.0 = Gauge32: 61676335
|
.1.3.6.1.4.1.35450.34.3.0 = Gauge32: 61676335
|
||||||
iso.3.6.1.4.1.35450.34.4.0 = Gauge32: 46890415
|
.1.3.6.1.4.1.35450.34.4.0 = Gauge32: 46890415
|
||||||
iso.3.6.1.4.1.35450.34.5.0 = Gauge32: 14785011
|
.1.3.6.1.4.1.35450.34.5.0 = Gauge32: 14785011
|
||||||
iso.3.6.1.4.1.35450.34.6.0 = Gauge32: 5578855
|
.1.3.6.1.4.1.35450.34.6.0 = Gauge32: 5578855
|
||||||
iso.3.6.1.4.1.35450.34.7.0 = Gauge32: 465
|
.1.3.6.1.4.1.35450.34.7.0 = Gauge32: 465
|
||||||
iso.3.6.1.4.1.35450.34.8.0 = Gauge32: 61644426
|
.1.3.6.1.4.1.35450.34.8.0 = Gauge32: 61644426
|
||||||
iso.3.6.1.4.1.35450.34.9.0 = Gauge32: 46880358
|
.1.3.6.1.4.1.35450.34.9.0 = Gauge32: 46880358
|
||||||
iso.3.6.1.4.1.35450.34.10.0 = Gauge32: 14763002
|
.1.3.6.1.4.1.35450.34.10.0 = Gauge32: 14763002
|
||||||
iso.3.6.1.4.1.35450.34.11.0 = Gauge32: 5582125
|
.1.3.6.1.4.1.35450.34.11.0 = Gauge32: 5582125
|
||||||
iso.3.6.1.4.1.35450.34.12.0 = Gauge32: 191
|
.1.3.6.1.4.1.35450.34.12.0 = Gauge32: 191
|
||||||
iso.3.6.1.4.1.35450.34.13.0 = Gauge32: 827
|
.1.3.6.1.4.1.35450.34.13.0 = Gauge32: 827
|
||||||
iso.3.6.1.4.1.35450.34.14.0 = Gauge32: 0
|
.1.3.6.1.4.1.35450.34.14.0 = Gauge32: 0
|
||||||
iso.3.6.1.4.1.35450.34.15.0 = Gauge32: 50105
|
.1.3.6.1.4.1.35450.34.15.0 = Gauge32: 50105
|
||||||
iso.3.6.1.4.1.35450.34.16.0 = Gauge32: 196650
|
.1.3.6.1.4.1.35450.34.16.0 = Gauge32: 196650
|
||||||
iso.3.6.1.4.1.35450.34.17.0 = Gauge32: 0
|
.1.3.6.1.4.1.35450.34.17.0 = Gauge32: 0
|
||||||
iso.3.6.1.4.1.35450.34.18.0 = Gauge32: 30256
|
.1.3.6.1.4.1.35450.34.18.0 = Gauge32: 30256
|
||||||
iso.3.6.1.4.1.35450.34.19.0 = Gauge32: 532158
|
.1.3.6.1.4.1.35450.34.19.0 = Gauge32: 532158
|
||||||
iso.3.6.1.4.1.35450.34.20.0 = Gauge32: 34
|
.1.3.6.1.4.1.35450.34.20.0 = Gauge32: 34
|
||||||
iso.3.6.1.4.1.35450.34.21.0 = Gauge32: 1
|
.1.3.6.1.4.1.35450.34.21.0 = Gauge32: 1
|
||||||
iso.3.6.1.4.1.35450.34.31.0 = Gauge32: 53
|
.1.3.6.1.4.1.35450.34.31.0 = Gauge32: 53
|
||||||
iso.3.6.1.4.1.35450.34.32.0 = Gauge32: 55050240
|
.1.3.6.1.4.1.35450.34.32.0 = Gauge32: 55050240
|
||||||
iso.3.6.1.4.1.35450.34.33.0 = Gauge32: 53
|
.1.3.6.1.4.1.35450.34.33.0 = Gauge32: 53
|
||||||
iso.3.6.1.4.1.35450.34.34.0 = Gauge32: 55186538` + "`" +
|
.1.3.6.1.4.1.35450.34.34.0 = Gauge32: 55186538` + "`" +
|
||||||
`
|
`
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Println(output)
|
fmt.Println(output)
|
||||||
|
|
|
@ -100,7 +100,7 @@ current time.
|
||||||
- ts-rfc3339 ("2006-01-02T15:04:05Z07:00")
|
- ts-rfc3339 ("2006-01-02T15:04:05Z07:00")
|
||||||
- ts-rfc3339nano ("2006-01-02T15:04:05.999999999Z07:00")
|
- ts-rfc3339nano ("2006-01-02T15:04:05.999999999Z07:00")
|
||||||
- ts-httpd ("02/Jan/2006:15:04:05 -0700")
|
- ts-httpd ("02/Jan/2006:15:04:05 -0700")
|
||||||
- ts-epoch (seconds since unix epoch)
|
- ts-epoch (seconds since unix epoch, may contain decimal)
|
||||||
- ts-epochnano (nanoseconds since unix epoch)
|
- ts-epochnano (nanoseconds since unix epoch)
|
||||||
- ts-"CUSTOM"
|
- ts-"CUSTOM"
|
||||||
|
|
||||||
|
@ -130,6 +130,19 @@ This example input and config parses a file using a custom timestamp conversion:
|
||||||
patterns = ['%{TIMESTAMP_ISO8601:timestamp:ts-"2006-01-02 15:04:05"} value=%{NUMBER:value:int}']
|
patterns = ['%{TIMESTAMP_ISO8601:timestamp:ts-"2006-01-02 15:04:05"} value=%{NUMBER:value:int}']
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This example input and config parses a file using a timestamp in unix time:
|
||||||
|
|
||||||
|
```
|
||||||
|
1466004605 value=42
|
||||||
|
1466004605.123456789 value=42
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[inputs.logparser]]
|
||||||
|
[inputs.logparser.grok]
|
||||||
|
patterns = ['%{NUMBER:timestamp:ts-epoch} value=%{NUMBER:value:int}']
|
||||||
|
```
|
||||||
|
|
||||||
This example parses a file using a built-in conversion and a custom pattern:
|
This example parses a file using a built-in conversion and a custom pattern:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -253,12 +253,30 @@ func (p *Parser) ParseLine(line string) (telegraf.Metric, error) {
|
||||||
case STRING:
|
case STRING:
|
||||||
fields[k] = strings.Trim(v, `"`)
|
fields[k] = strings.Trim(v, `"`)
|
||||||
case EPOCH:
|
case EPOCH:
|
||||||
iv, err := strconv.ParseInt(v, 10, 64)
|
parts := strings.SplitN(v, ".", 2)
|
||||||
if err != nil {
|
if len(parts) == 0 {
|
||||||
log.Printf("E! Error parsing %s to int: %s", v, err)
|
log.Printf("E! Error parsing %s to timestamp: %s", v, err)
|
||||||
} else {
|
break
|
||||||
timestamp = time.Unix(iv, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sec, err := strconv.ParseInt(parts[0], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("E! Error parsing %s to timestamp: %s", v, err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ts := time.Unix(sec, 0)
|
||||||
|
|
||||||
|
if len(parts) == 2 {
|
||||||
|
padded := fmt.Sprintf("%-9s", parts[1])
|
||||||
|
nsString := strings.Replace(padded[:9], " ", "0", -1)
|
||||||
|
nanosec, err := strconv.ParseInt(nsString, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("E! Error parsing %s to timestamp: %s", v, err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ts = ts.Add(time.Duration(nanosec) * time.Nanosecond)
|
||||||
|
}
|
||||||
|
timestamp = ts
|
||||||
case EPOCH_NANO:
|
case EPOCH_NANO:
|
||||||
iv, err := strconv.ParseInt(v, 10, 64)
|
iv, err := strconv.ParseInt(v, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -385,6 +385,77 @@ func TestParseEpoch(t *testing.T) {
|
||||||
assert.Equal(t, time.Unix(1466004605, 0), metricA.Time())
|
assert.Equal(t, time.Unix(1466004605, 0), metricA.Time())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseEpochDecimal(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
name string
|
||||||
|
line string
|
||||||
|
noMatch bool
|
||||||
|
err error
|
||||||
|
tags map[string]string
|
||||||
|
fields map[string]interface{}
|
||||||
|
time time.Time
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ns precision",
|
||||||
|
line: "1466004605.359052000 value=42",
|
||||||
|
tags: map[string]string{},
|
||||||
|
fields: map[string]interface{}{
|
||||||
|
"value": int64(42),
|
||||||
|
},
|
||||||
|
time: time.Unix(0, 1466004605359052000),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ms precision",
|
||||||
|
line: "1466004605.359 value=42",
|
||||||
|
tags: map[string]string{},
|
||||||
|
fields: map[string]interface{}{
|
||||||
|
"value": int64(42),
|
||||||
|
},
|
||||||
|
time: time.Unix(0, 1466004605359000000),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "second precision",
|
||||||
|
line: "1466004605 value=42",
|
||||||
|
tags: map[string]string{},
|
||||||
|
fields: map[string]interface{}{
|
||||||
|
"value": int64(42),
|
||||||
|
},
|
||||||
|
time: time.Unix(0, 1466004605000000000),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sub ns precision",
|
||||||
|
line: "1466004605.123456789123 value=42",
|
||||||
|
tags: map[string]string{},
|
||||||
|
fields: map[string]interface{}{
|
||||||
|
"value": int64(42),
|
||||||
|
},
|
||||||
|
time: time.Unix(0, 1466004605123456789),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
parser := &Parser{
|
||||||
|
Patterns: []string{"%{NUMBER:ts:ts-epoch} value=%{NUMBER:value:int}"},
|
||||||
|
}
|
||||||
|
assert.NoError(t, parser.Compile())
|
||||||
|
m, err := parser.ParseLine(tt.line)
|
||||||
|
|
||||||
|
if tt.noMatch {
|
||||||
|
require.Nil(t, m)
|
||||||
|
require.Nil(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, tt.err, err)
|
||||||
|
|
||||||
|
require.NotNil(t, m)
|
||||||
|
require.Equal(t, tt.tags, m.Tags())
|
||||||
|
require.Equal(t, tt.fields, m.Fields())
|
||||||
|
require.Equal(t, tt.time, m.Time())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseEpochErrors(t *testing.T) {
|
func TestParseEpochErrors(t *testing.T) {
|
||||||
p := &Parser{
|
p := &Parser{
|
||||||
Patterns: []string{"%{MYAPP}"},
|
Patterns: []string{"%{MYAPP}"},
|
||||||
|
|
|
@ -367,7 +367,7 @@ func getMetrics(role Role, group string) []string {
|
||||||
ret, ok := m[group]
|
ret, ok := m[group]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("I! [mesos] Unkown %s metrics group: %s\n", role, group)
|
log.Printf("I! [mesos] Unknown %s metrics group: %s\n", role, group)
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -197,7 +197,7 @@ func (c *Client) Send(typ int32, command string) (response *Packet, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient creates a new Client type, creating the connection
|
// NewClient creates a new Client type, creating the connection
|
||||||
// to the server specified by the host and port arguements. If
|
// to the server specified by the host and port arguments. If
|
||||||
// the connection fails, an error is returned.
|
// the connection fails, an error is returned.
|
||||||
func NewClient(host string, port int) (client *Client, err error) {
|
func NewClient(host string, port int) (client *Client, err error) {
|
||||||
client = new(Client)
|
client = new(Client)
|
||||||
|
|
|
@ -47,7 +47,7 @@ func (s *Minecraft) SampleConfig() string {
|
||||||
return sampleConfig
|
return sampleConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gather uses the RCON protocal to collect player and
|
// Gather uses the RCON protocol to collect player and
|
||||||
// scoreboard stats from a minecraft server.
|
// scoreboard stats from a minecraft server.
|
||||||
//var hasClient bool = false
|
//var hasClient bool = false
|
||||||
func (s *Minecraft) Gather(acc telegraf.Accumulator) error {
|
func (s *Minecraft) Gather(acc telegraf.Accumulator) error {
|
||||||
|
|
|
@ -76,7 +76,7 @@ func newClient(server, port string) (*rcon.Client, error) {
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gather recieves all player scoreboard information and returns it per player.
|
// Gather receives all player scoreboard information and returns it per player.
|
||||||
func (r *RCON) Gather(producer RCONClientProducer) ([]string, error) {
|
func (r *RCON) Gather(producer RCONClientProducer) ([]string, error) {
|
||||||
if r.client == nil {
|
if r.client == nil {
|
||||||
var err error
|
var err error
|
||||||
|
|
|
@ -77,6 +77,21 @@ var WiredTigerStats = map[string]string{
|
||||||
"percent_cache_used": "CacheUsedPercent",
|
"percent_cache_used": "CacheUsedPercent",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var WiredTigerExtStats = map[string]string{
|
||||||
|
"wtcache_tracked_dirty_bytes": "TrackedDirtyBytes",
|
||||||
|
"wtcache_current_bytes": "CurrentCachedBytes",
|
||||||
|
"wtcache_max_bytes_configured": "MaxBytesConfigured",
|
||||||
|
"wtcache_app_threads_page_read_count": "AppThreadsPageReadCount",
|
||||||
|
"wtcache_app_threads_page_read_time": "AppThreadsPageReadTime",
|
||||||
|
"wtcache_app_threads_page_write_count": "AppThreadsPageWriteCount",
|
||||||
|
"wtcache_bytes_written_from": "BytesWrittenFrom",
|
||||||
|
"wtcache_bytes_read_into": "BytesReadInto",
|
||||||
|
"wtcache_pages_evicted_by_app_thread": "PagesEvictedByAppThread",
|
||||||
|
"wtcache_pages_queued_for_eviction": "PagesQueuedForEviction",
|
||||||
|
"wtcache_server_evicting_pages": "ServerEvictingPages",
|
||||||
|
"wtcache_worker_thread_evictingpages": "WorkerThreadEvictingPages",
|
||||||
|
}
|
||||||
|
|
||||||
var DbDataStats = map[string]string{
|
var DbDataStats = map[string]string{
|
||||||
"collections": "Collections",
|
"collections": "Collections",
|
||||||
"objects": "Objects",
|
"objects": "Objects",
|
||||||
|
@ -121,13 +136,11 @@ func (d *MongodbData) AddDefaultStats() {
|
||||||
floatVal, _ := strconv.ParseFloat(percentVal, 64)
|
floatVal, _ := strconv.ParseFloat(percentVal, 64)
|
||||||
d.add(key, floatVal)
|
d.add(key, floatVal)
|
||||||
}
|
}
|
||||||
|
d.addStat(statLine, WiredTigerExtStats)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *MongodbData) addStat(
|
func (d *MongodbData) addStat(statLine reflect.Value, stats map[string]string) {
|
||||||
statLine reflect.Value,
|
|
||||||
stats map[string]string,
|
|
||||||
) {
|
|
||||||
for key, value := range stats {
|
for key, value := range stats {
|
||||||
val := statLine.FieldByName(value).Interface()
|
val := statLine.FieldByName(value).Interface()
|
||||||
d.add(key, val)
|
d.add(key, val)
|
||||||
|
|
|
@ -70,9 +70,21 @@ func TestAddReplStats(t *testing.T) {
|
||||||
func TestAddWiredTigerStats(t *testing.T) {
|
func TestAddWiredTigerStats(t *testing.T) {
|
||||||
d := NewMongodbData(
|
d := NewMongodbData(
|
||||||
&StatLine{
|
&StatLine{
|
||||||
StorageEngine: "wiredTiger",
|
StorageEngine: "wiredTiger",
|
||||||
CacheDirtyPercent: 0,
|
CacheDirtyPercent: 0,
|
||||||
CacheUsedPercent: 0,
|
CacheUsedPercent: 0,
|
||||||
|
TrackedDirtyBytes: 0,
|
||||||
|
CurrentCachedBytes: 0,
|
||||||
|
MaxBytesConfigured: 0,
|
||||||
|
AppThreadsPageReadCount: 0,
|
||||||
|
AppThreadsPageReadTime: 0,
|
||||||
|
AppThreadsPageWriteCount: 0,
|
||||||
|
BytesWrittenFrom: 0,
|
||||||
|
BytesReadInto: 0,
|
||||||
|
PagesEvictedByAppThread: 0,
|
||||||
|
PagesQueuedForEviction: 0,
|
||||||
|
ServerEvictingPages: 0,
|
||||||
|
WorkerThreadEvictingPages: 0,
|
||||||
},
|
},
|
||||||
tags,
|
tags,
|
||||||
)
|
)
|
||||||
|
|
|
@ -127,9 +127,19 @@ type ConcurrentTransStats struct {
|
||||||
|
|
||||||
// CacheStats stores cache statistics for WiredTiger.
|
// CacheStats stores cache statistics for WiredTiger.
|
||||||
type CacheStats struct {
|
type CacheStats struct {
|
||||||
TrackedDirtyBytes int64 `bson:"tracked dirty bytes in the cache"`
|
TrackedDirtyBytes int64 `bson:"tracked dirty bytes in the cache"`
|
||||||
CurrentCachedBytes int64 `bson:"bytes currently in the cache"`
|
CurrentCachedBytes int64 `bson:"bytes currently in the cache"`
|
||||||
MaxBytesConfigured int64 `bson:"maximum bytes configured"`
|
MaxBytesConfigured int64 `bson:"maximum bytes configured"`
|
||||||
|
AppThreadsPageReadCount int64 `bson:"application threads page read from disk to cache count"`
|
||||||
|
AppThreadsPageReadTime int64 `bson:"application threads page read from disk to cache time (usecs)"`
|
||||||
|
AppThreadsPageWriteCount int64 `bson:"application threads page write from cache to disk count"`
|
||||||
|
AppThreadsPageWriteTime int64 `bson:"application threads page write from cache to disk time (usecs)"`
|
||||||
|
BytesWrittenFrom int64 `bson:"bytes written from cache"`
|
||||||
|
BytesReadInto int64 `bson:"bytes read into cache"`
|
||||||
|
PagesEvictedByAppThread int64 `bson:"pages evicted by application threads"`
|
||||||
|
PagesQueuedForEviction int64 `bson:"pages queued for eviction"`
|
||||||
|
ServerEvictingPages int64 `bson:"eviction server evicting pages"`
|
||||||
|
WorkerThreadEvictingPages int64 `bson:"eviction worker thread evicting pages"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionStats stores transaction checkpoints in WiredTiger.
|
// TransactionStats stores transaction checkpoints in WiredTiger.
|
||||||
|
@ -406,6 +416,20 @@ type StatLine struct {
|
||||||
CacheDirtyPercent float64
|
CacheDirtyPercent float64
|
||||||
CacheUsedPercent float64
|
CacheUsedPercent float64
|
||||||
|
|
||||||
|
// Cache ultilization extended (wiredtiger only)
|
||||||
|
TrackedDirtyBytes int64
|
||||||
|
CurrentCachedBytes int64
|
||||||
|
MaxBytesConfigured int64
|
||||||
|
AppThreadsPageReadCount int64
|
||||||
|
AppThreadsPageReadTime int64
|
||||||
|
AppThreadsPageWriteCount int64
|
||||||
|
BytesWrittenFrom int64
|
||||||
|
BytesReadInto int64
|
||||||
|
PagesEvictedByAppThread int64
|
||||||
|
PagesQueuedForEviction int64
|
||||||
|
ServerEvictingPages int64
|
||||||
|
WorkerThreadEvictingPages int64
|
||||||
|
|
||||||
// Replicated Opcounter fields
|
// Replicated Opcounter fields
|
||||||
InsertR, QueryR, UpdateR, DeleteR, GetMoreR, CommandR int64
|
InsertR, QueryR, UpdateR, DeleteR, GetMoreR, CommandR int64
|
||||||
ReplLag int64
|
ReplLag int64
|
||||||
|
@ -514,7 +538,7 @@ func NewStatLine(oldMongo, newMongo MongoStatus, key string, all bool, sampleSec
|
||||||
returnVal.Command = diff(newStat.Opcounters.Command, oldStat.Opcounters.Command, sampleSecs)
|
returnVal.Command = diff(newStat.Opcounters.Command, oldStat.Opcounters.Command, sampleSecs)
|
||||||
}
|
}
|
||||||
|
|
||||||
if newStat.Metrics != nil && newStat.Metrics.TTL != nil && oldStat.Metrics.TTL != nil {
|
if newStat.Metrics != nil && newStat.Metrics.TTL != nil && oldStat.Metrics != nil && oldStat.Metrics.TTL != nil {
|
||||||
returnVal.Passes = diff(newStat.Metrics.TTL.Passes, oldStat.Metrics.TTL.Passes, sampleSecs)
|
returnVal.Passes = diff(newStat.Metrics.TTL.Passes, oldStat.Metrics.TTL.Passes, sampleSecs)
|
||||||
returnVal.DeletedDocuments = diff(newStat.Metrics.TTL.DeletedDocuments, oldStat.Metrics.TTL.DeletedDocuments, sampleSecs)
|
returnVal.DeletedDocuments = diff(newStat.Metrics.TTL.DeletedDocuments, oldStat.Metrics.TTL.DeletedDocuments, sampleSecs)
|
||||||
}
|
}
|
||||||
|
@ -534,6 +558,19 @@ func NewStatLine(oldMongo, newMongo MongoStatus, key string, all bool, sampleSec
|
||||||
returnVal.Flushes = newStat.WiredTiger.Transaction.TransCheckpoints - oldStat.WiredTiger.Transaction.TransCheckpoints
|
returnVal.Flushes = newStat.WiredTiger.Transaction.TransCheckpoints - oldStat.WiredTiger.Transaction.TransCheckpoints
|
||||||
returnVal.CacheDirtyPercent = float64(newStat.WiredTiger.Cache.TrackedDirtyBytes) / float64(newStat.WiredTiger.Cache.MaxBytesConfigured)
|
returnVal.CacheDirtyPercent = float64(newStat.WiredTiger.Cache.TrackedDirtyBytes) / float64(newStat.WiredTiger.Cache.MaxBytesConfigured)
|
||||||
returnVal.CacheUsedPercent = float64(newStat.WiredTiger.Cache.CurrentCachedBytes) / float64(newStat.WiredTiger.Cache.MaxBytesConfigured)
|
returnVal.CacheUsedPercent = float64(newStat.WiredTiger.Cache.CurrentCachedBytes) / float64(newStat.WiredTiger.Cache.MaxBytesConfigured)
|
||||||
|
|
||||||
|
returnVal.TrackedDirtyBytes = newStat.WiredTiger.Cache.TrackedDirtyBytes
|
||||||
|
returnVal.CurrentCachedBytes = newStat.WiredTiger.Cache.CurrentCachedBytes
|
||||||
|
returnVal.MaxBytesConfigured = newStat.WiredTiger.Cache.MaxBytesConfigured
|
||||||
|
returnVal.AppThreadsPageReadCount = newStat.WiredTiger.Cache.AppThreadsPageReadCount
|
||||||
|
returnVal.AppThreadsPageReadTime = newStat.WiredTiger.Cache.AppThreadsPageReadTime
|
||||||
|
returnVal.AppThreadsPageWriteCount = newStat.WiredTiger.Cache.AppThreadsPageWriteCount
|
||||||
|
returnVal.BytesWrittenFrom = newStat.WiredTiger.Cache.BytesWrittenFrom
|
||||||
|
returnVal.BytesReadInto = newStat.WiredTiger.Cache.BytesReadInto
|
||||||
|
returnVal.PagesEvictedByAppThread = newStat.WiredTiger.Cache.PagesEvictedByAppThread
|
||||||
|
returnVal.PagesQueuedForEviction = newStat.WiredTiger.Cache.PagesQueuedForEviction
|
||||||
|
returnVal.ServerEvictingPages = newStat.WiredTiger.Cache.ServerEvictingPages
|
||||||
|
returnVal.WorkerThreadEvictingPages = newStat.WiredTiger.Cache.WorkerThreadEvictingPages
|
||||||
} else if newStat.BackgroundFlushing != nil && oldStat.BackgroundFlushing != nil {
|
} else if newStat.BackgroundFlushing != nil && oldStat.BackgroundFlushing != nil {
|
||||||
returnVal.Flushes = newStat.BackgroundFlushing.Flushes - oldStat.BackgroundFlushing.Flushes
|
returnVal.Flushes = newStat.BackgroundFlushing.Flushes - oldStat.BackgroundFlushing.Flushes
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,9 @@ The plugin expects messages in the
|
||||||
```toml
|
```toml
|
||||||
# Read metrics from MQTT topic(s)
|
# Read metrics from MQTT topic(s)
|
||||||
[[inputs.mqtt_consumer]]
|
[[inputs.mqtt_consumer]]
|
||||||
servers = ["localhost:1883"]
|
## MQTT broker URLs to be used. The format should be scheme://host:port,
|
||||||
|
## schema can be tcp, ssl, or ws.
|
||||||
|
servers = ["tcp://localhost:1883"]
|
||||||
## MQTT QoS, must be 0, 1, or 2
|
## MQTT QoS, must be 0, 1, or 2
|
||||||
qos = 0
|
qos = 0
|
||||||
## Connection timeout for initial connection in seconds
|
## Connection timeout for initial connection in seconds
|
||||||
|
|
|
@ -56,7 +56,10 @@ type MQTTConsumer struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var sampleConfig = `
|
var sampleConfig = `
|
||||||
servers = ["localhost:1883"]
|
## MQTT broker URLs to be used. The format should be scheme://host:port,
|
||||||
|
## schema can be tcp, ssl, or ws.
|
||||||
|
servers = ["tcp://localhost:1883"]
|
||||||
|
|
||||||
## MQTT QoS, must be 0, 1, or 2
|
## MQTT QoS, must be 0, 1, or 2
|
||||||
qos = 0
|
qos = 0
|
||||||
## Connection timeout for initial connection in seconds
|
## Connection timeout for initial connection in seconds
|
||||||
|
@ -239,9 +242,7 @@ func (m *MQTTConsumer) createOpts() (*mqtt.ClientOptions, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
scheme := "tcp"
|
|
||||||
if tlsCfg != nil {
|
if tlsCfg != nil {
|
||||||
scheme = "ssl"
|
|
||||||
opts.SetTLSConfig(tlsCfg)
|
opts.SetTLSConfig(tlsCfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,8 +258,17 @@ func (m *MQTTConsumer) createOpts() (*mqtt.ClientOptions, error) {
|
||||||
if len(m.Servers) == 0 {
|
if len(m.Servers) == 0 {
|
||||||
return opts, fmt.Errorf("could not get host infomations")
|
return opts, fmt.Errorf("could not get host infomations")
|
||||||
}
|
}
|
||||||
for _, host := range m.Servers {
|
|
||||||
server := fmt.Sprintf("%s://%s", scheme, host)
|
for _, server := range m.Servers {
|
||||||
|
// Preserve support for host:port style servers; deprecated in Telegraf 1.4.4
|
||||||
|
if !strings.Contains(server, "://") {
|
||||||
|
log.Printf("W! mqtt_consumer server %q should be updated to use `scheme://host:port` format", server)
|
||||||
|
if tlsCfg == nil {
|
||||||
|
server = "tcp://" + server
|
||||||
|
} else {
|
||||||
|
server = "ssl://" + server
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
opts.AddBroker(server)
|
opts.AddBroker(server)
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,7 @@ func (n *Nginx) Gather(acc telegraf.Accumulator) error {
|
||||||
addr, err := url.Parse(u)
|
addr, err := url.Parse(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
acc.AddError(fmt.Errorf("Unable to parse address '%s': %s", u, err))
|
acc.AddError(fmt.Errorf("Unable to parse address '%s': %s", u, err))
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
|
|
|
@ -59,6 +59,7 @@ func (n *NginxPlus) Gather(acc telegraf.Accumulator) error {
|
||||||
addr, err := url.Parse(u)
|
addr, err := url.Parse(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
acc.AddError(fmt.Errorf("Unable to parse address '%s': %s", u, err))
|
acc.AddError(fmt.Errorf("Unable to parse address '%s': %s", u, err))
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
|
|
|
@ -59,7 +59,7 @@ func (client *conn) Request(
|
||||||
rec := &record{}
|
rec := &record{}
|
||||||
var err1 error
|
var err1 error
|
||||||
|
|
||||||
// recive untill EOF or FCGI_END_REQUEST
|
// recive until EOF or FCGI_END_REQUEST
|
||||||
READ_LOOP:
|
READ_LOOP:
|
||||||
for {
|
for {
|
||||||
err1 = rec.read(client.rwc)
|
err1 = rec.read(client.rwc)
|
||||||
|
|
|
@ -62,7 +62,7 @@ func TestPhpFpmGeneratesMetrics_From_Fcgi(t *testing.T) {
|
||||||
// Let OS find an available port
|
// Let OS find an available port
|
||||||
tcp, err := net.Listen("tcp", "127.0.0.1:0")
|
tcp, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Cannot initalize test server")
|
t.Fatal("Cannot initialize test server")
|
||||||
}
|
}
|
||||||
defer tcp.Close()
|
defer tcp.Close()
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ func TestPhpFpmGeneratesMetrics_From_Socket(t *testing.T) {
|
||||||
binary.Read(rand.Reader, binary.LittleEndian, &randomNumber)
|
binary.Read(rand.Reader, binary.LittleEndian, &randomNumber)
|
||||||
tcp, err := net.Listen("unix", fmt.Sprintf("/tmp/test-fpm%d.sock", randomNumber))
|
tcp, err := net.Listen("unix", fmt.Sprintf("/tmp/test-fpm%d.sock", randomNumber))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Cannot initalize server on port ")
|
t.Fatal("Cannot initialize server on port ")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer tcp.Close()
|
defer tcp.Close()
|
||||||
|
@ -150,7 +150,7 @@ func TestPhpFpmGeneratesMetrics_From_Socket_Custom_Status_Path(t *testing.T) {
|
||||||
binary.Read(rand.Reader, binary.LittleEndian, &randomNumber)
|
binary.Read(rand.Reader, binary.LittleEndian, &randomNumber)
|
||||||
tcp, err := net.Listen("unix", fmt.Sprintf("/tmp/test-fpm%d.sock", randomNumber))
|
tcp, err := net.Listen("unix", fmt.Sprintf("/tmp/test-fpm%d.sock", randomNumber))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Cannot initalize server on port ")
|
t.Fatal("Cannot initialize server on port ")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer tcp.Close()
|
defer tcp.Close()
|
||||||
|
|
|
@ -28,11 +28,14 @@ urls = ["www.google.com"] # required
|
||||||
- packets_received ( from ping output )
|
- packets_received ( from ping output )
|
||||||
- percent_reply_loss ( compute from packets_transmitted and reply_received )
|
- percent_reply_loss ( compute from packets_transmitted and reply_received )
|
||||||
- percent_packets_loss ( compute from packets_transmitted and packets_received )
|
- percent_packets_loss ( compute from packets_transmitted and packets_received )
|
||||||
- errors ( when host can not be found or wrong prameters is passed to application )
|
- errors ( when host can not be found or wrong parameters is passed to application )
|
||||||
- response time
|
- response time
|
||||||
- average_response_ms ( compute from minimum_response_ms and maximum_response_ms )
|
- average_response_ms ( compute from minimum_response_ms and maximum_response_ms )
|
||||||
- minimum_response_ms ( from ping output )
|
- minimum_response_ms ( from ping output )
|
||||||
- maximum_response_ms ( from ping output )
|
- maximum_response_ms ( from ping output )
|
||||||
|
- result_code
|
||||||
|
- 0: success
|
||||||
|
- 1: no such host
|
||||||
|
|
||||||
### Tags:
|
### Tags:
|
||||||
|
|
||||||
|
@ -44,5 +47,5 @@ urls = ["www.google.com"] # required
|
||||||
```
|
```
|
||||||
$ ./telegraf --config telegraf.conf --input-filter ping --test
|
$ ./telegraf --config telegraf.conf --input-filter ping --test
|
||||||
* Plugin: ping, Collection 1
|
* Plugin: ping, Collection 1
|
||||||
ping,host=WIN-PBAPLP511R7,url=www.google.com average_response_ms=7i,maximum_response_ms=9i,minimum_response_ms=7i,packets_received=4i,packets_transmitted=4i,percent_packet_loss=0,percent_reply_loss=0,reply_received=4i 1469879119000000000
|
ping,host=WIN-PBAPLP511R7,url=www.google.com result_code=0i,average_response_ms=7i,maximum_response_ms=9i,minimum_response_ms=7i,packets_received=4i,packets_transmitted=4i,percent_packet_loss=0,percent_reply_loss=0,reply_received=4i 1469879119000000000
|
||||||
```
|
```
|
||||||
|
|
|
@ -5,6 +5,7 @@ package ping
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -76,6 +77,17 @@ func (p *Ping) Gather(acc telegraf.Accumulator) error {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(u string) {
|
go func(u string) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
tags := map[string]string{"url": u}
|
||||||
|
fields := map[string]interface{}{"result_code": 0}
|
||||||
|
|
||||||
|
_, err := net.LookupHost(u)
|
||||||
|
if err != nil {
|
||||||
|
acc.AddError(err)
|
||||||
|
fields["result_code"] = 1
|
||||||
|
acc.AddFields("ping", fields, tags)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
args := p.args(u)
|
args := p.args(u)
|
||||||
totalTimeout := float64(p.Count)*p.Timeout + float64(p.Count-1)*p.PingInterval
|
totalTimeout := float64(p.Count)*p.Timeout + float64(p.Count-1)*p.PingInterval
|
||||||
|
|
||||||
|
@ -99,24 +111,23 @@ func (p *Ping) Gather(acc telegraf.Accumulator) error {
|
||||||
} else {
|
} else {
|
||||||
acc.AddError(err)
|
acc.AddError(err)
|
||||||
}
|
}
|
||||||
|
acc.AddFields("ping", fields, tags)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := map[string]string{"url": u}
|
|
||||||
trans, rec, min, avg, max, stddev, err := processPingOutput(out)
|
trans, rec, min, avg, max, stddev, err := processPingOutput(out)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// fatal error
|
// fatal error
|
||||||
acc.AddError(fmt.Errorf("%s: %s", err, u))
|
acc.AddError(fmt.Errorf("%s: %s", err, u))
|
||||||
|
acc.AddFields("ping", fields, tags)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Calculate packet loss percentage
|
// Calculate packet loss percentage
|
||||||
loss := float64(trans-rec) / float64(trans) * 100.0
|
loss := float64(trans-rec) / float64(trans) * 100.0
|
||||||
fields := map[string]interface{}{
|
fields["packets_transmitted"] = trans
|
||||||
"packets_transmitted": trans,
|
fields["packets_received"] = rec
|
||||||
"packets_received": rec,
|
fields["percent_packet_loss"] = loss
|
||||||
"percent_packet_loss": loss,
|
|
||||||
}
|
|
||||||
if min > 0 {
|
if min > 0 {
|
||||||
fields["minimum_response_ms"] = min
|
fields["minimum_response_ms"] = min
|
||||||
}
|
}
|
||||||
|
@ -145,7 +156,7 @@ func hostPinger(timeout float64, args ...string) (string, error) {
|
||||||
}
|
}
|
||||||
c := exec.Command(bin, args...)
|
c := exec.Command(bin, args...)
|
||||||
out, err := internal.CombinedOutputTimeout(c,
|
out, err := internal.CombinedOutputTimeout(c,
|
||||||
time.Second*time.Duration(timeout+1))
|
time.Second*time.Duration(timeout+5))
|
||||||
return string(out), err
|
return string(out), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,7 +205,6 @@ func processPingOutput(out string) (int, int, float64, float64, float64, float64
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
if strings.Contains(line, "transmitted") &&
|
if strings.Contains(line, "transmitted") &&
|
||||||
strings.Contains(line, "received") {
|
strings.Contains(line, "received") {
|
||||||
err = nil
|
|
||||||
stats := strings.Split(line, ", ")
|
stats := strings.Split(line, ", ")
|
||||||
// Transmitted packets
|
// Transmitted packets
|
||||||
trans, err = strconv.Atoi(strings.Split(stats[0], " ")[0])
|
trans, err = strconv.Atoi(strings.Split(stats[0], " ")[0])
|
||||||
|
@ -209,8 +219,17 @@ func processPingOutput(out string) (int, int, float64, float64, float64, float64
|
||||||
} else if strings.Contains(line, "min/avg/max") {
|
} else if strings.Contains(line, "min/avg/max") {
|
||||||
stats := strings.Split(line, " ")[3]
|
stats := strings.Split(line, " ")[3]
|
||||||
min, err = strconv.ParseFloat(strings.Split(stats, "/")[0], 64)
|
min, err = strconv.ParseFloat(strings.Split(stats, "/")[0], 64)
|
||||||
|
if err != nil {
|
||||||
|
return trans, recv, min, avg, max, stddev, err
|
||||||
|
}
|
||||||
avg, err = strconv.ParseFloat(strings.Split(stats, "/")[1], 64)
|
avg, err = strconv.ParseFloat(strings.Split(stats, "/")[1], 64)
|
||||||
|
if err != nil {
|
||||||
|
return trans, recv, min, avg, max, stddev, err
|
||||||
|
}
|
||||||
max, err = strconv.ParseFloat(strings.Split(stats, "/")[2], 64)
|
max, err = strconv.ParseFloat(strings.Split(stats, "/")[2], 64)
|
||||||
|
if err != nil {
|
||||||
|
return trans, recv, min, avg, max, stddev, err
|
||||||
|
}
|
||||||
stddev, err = strconv.ParseFloat(strings.Split(stats, "/")[3], 64)
|
stddev, err = strconv.ParseFloat(strings.Split(stats, "/")[3], 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return trans, recv, min, avg, max, stddev, err
|
return trans, recv, min, avg, max, stddev, err
|
||||||
|
|
|
@ -158,6 +158,7 @@ func TestPingGather(t *testing.T) {
|
||||||
"average_response_ms": 43.628,
|
"average_response_ms": 43.628,
|
||||||
"maximum_response_ms": 51.806,
|
"maximum_response_ms": 51.806,
|
||||||
"standard_deviation_ms": 5.325,
|
"standard_deviation_ms": 5.325,
|
||||||
|
"result_code": 0,
|
||||||
}
|
}
|
||||||
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
|
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
|
||||||
|
|
||||||
|
@ -198,6 +199,7 @@ func TestLossyPingGather(t *testing.T) {
|
||||||
"average_response_ms": 44.033,
|
"average_response_ms": 44.033,
|
||||||
"maximum_response_ms": 51.806,
|
"maximum_response_ms": 51.806,
|
||||||
"standard_deviation_ms": 5.325,
|
"standard_deviation_ms": 5.325,
|
||||||
|
"result_code": 0,
|
||||||
}
|
}
|
||||||
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
|
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
|
||||||
}
|
}
|
||||||
|
@ -230,6 +232,7 @@ func TestBadPingGather(t *testing.T) {
|
||||||
"packets_transmitted": 2,
|
"packets_transmitted": 2,
|
||||||
"packets_received": 0,
|
"packets_received": 0,
|
||||||
"percent_packet_loss": 100.0,
|
"percent_packet_loss": 100.0,
|
||||||
|
"result_code": 0,
|
||||||
}
|
}
|
||||||
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
|
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ package ping
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"net"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -158,16 +159,27 @@ func (p *Ping) Gather(acc telegraf.Accumulator) error {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(u string) {
|
go func(u string) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
|
tags := map[string]string{"url": u}
|
||||||
|
fields := map[string]interface{}{"result_code": 0}
|
||||||
|
|
||||||
|
_, err := net.LookupHost(u)
|
||||||
|
if err != nil {
|
||||||
|
errorChannel <- err
|
||||||
|
fields["result_code"] = 1
|
||||||
|
acc.AddFields("ping", fields, tags)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
args := p.args(u)
|
args := p.args(u)
|
||||||
totalTimeout := p.timeout() * float64(p.Count)
|
totalTimeout := p.timeout() * float64(p.Count)
|
||||||
out, err := p.pingHost(totalTimeout, args...)
|
out, err := p.pingHost(totalTimeout, args...)
|
||||||
// ping host return exitcode != 0 also when there was no response from host
|
// ping host return exitcode != 0 also when there was no response from host
|
||||||
// but command was execute succesfully
|
// but command was execute successfully
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Combine go err + stderr output
|
// Combine go err + stderr output
|
||||||
pendingError = errors.New(strings.TrimSpace(out) + ", " + err.Error())
|
pendingError = errors.New(strings.TrimSpace(out) + ", " + err.Error())
|
||||||
}
|
}
|
||||||
tags := map[string]string{"url": u}
|
|
||||||
trans, recReply, receivePacket, avg, min, max, err := processPingOutput(out)
|
trans, recReply, receivePacket, avg, min, max, err := processPingOutput(out)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// fatal error
|
// fatal error
|
||||||
|
@ -175,24 +187,20 @@ func (p *Ping) Gather(acc telegraf.Accumulator) error {
|
||||||
errorChannel <- pendingError
|
errorChannel <- pendingError
|
||||||
}
|
}
|
||||||
errorChannel <- err
|
errorChannel <- err
|
||||||
fields := map[string]interface{}{
|
|
||||||
"errors": 100.0,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
fields["errors"] = 100.0
|
||||||
acc.AddFields("ping", fields, tags)
|
acc.AddFields("ping", fields, tags)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Calculate packet loss percentage
|
// Calculate packet loss percentage
|
||||||
lossReply := float64(trans-recReply) / float64(trans) * 100.0
|
lossReply := float64(trans-recReply) / float64(trans) * 100.0
|
||||||
lossPackets := float64(trans-receivePacket) / float64(trans) * 100.0
|
lossPackets := float64(trans-receivePacket) / float64(trans) * 100.0
|
||||||
fields := map[string]interface{}{
|
|
||||||
"packets_transmitted": trans,
|
fields["packets_transmitted"] = trans
|
||||||
"reply_received": recReply,
|
fields["reply_received"] = recReply
|
||||||
"packets_received": receivePacket,
|
fields["packets_received"] = receivePacket
|
||||||
"percent_packet_loss": lossPackets,
|
fields["percent_packet_loss"] = lossPackets
|
||||||
"percent_reply_loss": lossReply,
|
fields["percent_reply_loss"] = lossReply
|
||||||
}
|
|
||||||
if avg > 0 {
|
if avg > 0 {
|
||||||
fields["average_response_ms"] = float64(avg)
|
fields["average_response_ms"] = float64(avg)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,10 @@ package ping
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf/testutil"
|
"github.com/influxdata/telegraf/testutil"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Windows ping format ( should support multilanguage ?)
|
// Windows ping format ( should support multilanguage ?)
|
||||||
|
@ -81,6 +82,7 @@ func TestPingGather(t *testing.T) {
|
||||||
"average_response_ms": 50.0,
|
"average_response_ms": 50.0,
|
||||||
"minimum_response_ms": 50.0,
|
"minimum_response_ms": 50.0,
|
||||||
"maximum_response_ms": 52.0,
|
"maximum_response_ms": 52.0,
|
||||||
|
"result_code": 0,
|
||||||
}
|
}
|
||||||
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
|
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
|
||||||
|
|
||||||
|
@ -121,6 +123,7 @@ func TestBadPingGather(t *testing.T) {
|
||||||
"reply_received": 0,
|
"reply_received": 0,
|
||||||
"percent_packet_loss": 100.0,
|
"percent_packet_loss": 100.0,
|
||||||
"percent_reply_loss": 100.0,
|
"percent_reply_loss": 100.0,
|
||||||
|
"result_code": 0,
|
||||||
}
|
}
|
||||||
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
|
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
|
||||||
}
|
}
|
||||||
|
@ -167,6 +170,7 @@ func TestLossyPingGather(t *testing.T) {
|
||||||
"average_response_ms": 115.0,
|
"average_response_ms": 115.0,
|
||||||
"minimum_response_ms": 114.0,
|
"minimum_response_ms": 114.0,
|
||||||
"maximum_response_ms": 119.0,
|
"maximum_response_ms": 119.0,
|
||||||
|
"result_code": 0,
|
||||||
}
|
}
|
||||||
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
|
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
|
||||||
}
|
}
|
||||||
|
@ -269,6 +273,7 @@ func TestUnreachablePingGather(t *testing.T) {
|
||||||
"reply_received": 0,
|
"reply_received": 0,
|
||||||
"percent_packet_loss": 75.0,
|
"percent_packet_loss": 75.0,
|
||||||
"percent_reply_loss": 100.0,
|
"percent_reply_loss": 100.0,
|
||||||
|
"result_code": 0,
|
||||||
}
|
}
|
||||||
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
|
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
|
||||||
|
|
||||||
|
@ -315,6 +320,7 @@ func TestTTLExpiredPingGather(t *testing.T) {
|
||||||
"reply_received": 0,
|
"reply_received": 0,
|
||||||
"percent_packet_loss": 75.0,
|
"percent_packet_loss": 75.0,
|
||||||
"percent_reply_loss": 100.0,
|
"percent_reply_loss": 100.0,
|
||||||
|
"result_code": 0,
|
||||||
}
|
}
|
||||||
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
|
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@ func TestMemcachedGeneratesMetrics(t *testing.T) {
|
||||||
binary.Read(rand.Reader, binary.LittleEndian, &randomNumber)
|
binary.Read(rand.Reader, binary.LittleEndian, &randomNumber)
|
||||||
socket, err := net.Listen("unix", fmt.Sprintf("/tmp/pdns%d.controlsocket", randomNumber))
|
socket, err := net.Listen("unix", fmt.Sprintf("/tmp/pdns%d.controlsocket", randomNumber))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Cannot initalize server on port ")
|
t.Fatal("Cannot initialize server on port ")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer socket.Close()
|
defer socket.Close()
|
||||||
|
|
|
@ -86,7 +86,7 @@ func Parse(buf []byte, header http.Header) ([]telegraf.Metric, error) {
|
||||||
} else {
|
} else {
|
||||||
t = time.Now()
|
t = time.Now()
|
||||||
}
|
}
|
||||||
metric, err := metric.New(metricName, tags, fields, t)
|
metric, err := metric.New(metricName, tags, fields, t, valueType(mf.GetType()))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
metrics = append(metrics, metric)
|
metrics = append(metrics, metric)
|
||||||
}
|
}
|
||||||
|
@ -97,6 +97,21 @@ func Parse(buf []byte, header http.Header) ([]telegraf.Metric, error) {
|
||||||
return metrics, err
|
return metrics, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func valueType(mt dto.MetricType) telegraf.ValueType {
|
||||||
|
switch mt {
|
||||||
|
case dto.MetricType_COUNTER:
|
||||||
|
return telegraf.Counter
|
||||||
|
case dto.MetricType_GAUGE:
|
||||||
|
return telegraf.Gauge
|
||||||
|
case dto.MetricType_SUMMARY:
|
||||||
|
return telegraf.Summary
|
||||||
|
case dto.MetricType_HISTOGRAM:
|
||||||
|
return telegraf.Histogram
|
||||||
|
default:
|
||||||
|
return telegraf.Untyped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get Quantiles from summary metric
|
// Get Quantiles from summary metric
|
||||||
func makeQuantiles(m *dto.Metric) map[string]interface{} {
|
func makeQuantiles(m *dto.Metric) map[string]interface{} {
|
||||||
fields := make(map[string]interface{})
|
fields := make(map[string]interface{})
|
||||||
|
@ -134,11 +149,11 @@ func getNameAndValue(m *dto.Metric) map[string]interface{} {
|
||||||
fields["gauge"] = float64(m.GetGauge().GetValue())
|
fields["gauge"] = float64(m.GetGauge().GetValue())
|
||||||
}
|
}
|
||||||
} else if m.Counter != nil {
|
} else if m.Counter != nil {
|
||||||
if !math.IsNaN(m.GetGauge().GetValue()) {
|
if !math.IsNaN(m.GetCounter().GetValue()) {
|
||||||
fields["counter"] = float64(m.GetCounter().GetValue())
|
fields["counter"] = float64(m.GetCounter().GetValue())
|
||||||
}
|
}
|
||||||
} else if m.Untyped != nil {
|
} else if m.Untyped != nil {
|
||||||
if !math.IsNaN(m.GetGauge().GetValue()) {
|
if !math.IsNaN(m.GetUntyped().GetValue()) {
|
||||||
fields["value"] = float64(m.GetUntyped().GetValue())
|
fields["value"] = float64(m.GetUntyped().GetValue())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -218,7 +218,19 @@ func (p *Prometheus) gatherURL(url UrlAndAddress, acc telegraf.Accumulator) erro
|
||||||
if url.Address != "" {
|
if url.Address != "" {
|
||||||
tags["address"] = url.Address
|
tags["address"] = url.Address
|
||||||
}
|
}
|
||||||
acc.AddFields(metric.Name(), metric.Fields(), tags, metric.Time())
|
|
||||||
|
switch metric.Type() {
|
||||||
|
case telegraf.Counter:
|
||||||
|
acc.AddCounter(metric.Name(), metric.Fields(), tags, metric.Time())
|
||||||
|
case telegraf.Gauge:
|
||||||
|
acc.AddGauge(metric.Name(), metric.Fields(), tags, metric.Time())
|
||||||
|
case telegraf.Summary:
|
||||||
|
acc.AddSummary(metric.Name(), metric.Fields(), tags, metric.Time())
|
||||||
|
case telegraf.Histogram:
|
||||||
|
acc.AddHistogram(metric.Name(), metric.Fields(), tags, metric.Time())
|
||||||
|
default:
|
||||||
|
acc.AddFields(metric.Name(), metric.Fields(), tags, metric.Time())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -0,0 +1,135 @@
|
||||||
|
# Telegraf S.M.A.R.T. plugin
|
||||||
|
|
||||||
|
Get metrics using the command line utility `smartctl` for S.M.A.R.T. (Self-Monitoring, Analysis and Reporting Technology) storage devices. SMART is a monitoring system included in computer hard disk drives (HDDs) and solid-state drives (SSDs)[1] that detects and reports on various indicators of drive reliability, with the intent of enabling the anticipation of hardware failures.
|
||||||
|
See smartmontools (https://www.smartmontools.org/).
|
||||||
|
|
||||||
|
If no devices are specified, the plugin will scan for SMART devices via the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
smartctl --scan
|
||||||
|
```
|
||||||
|
|
||||||
|
Metrics will be reported from the following `smartctl` command:
|
||||||
|
|
||||||
|
```
|
||||||
|
smartctl --info --attributes --health -n <nocheck> --format=brief <device>
|
||||||
|
```
|
||||||
|
|
||||||
|
This plugin supports _smartmontools_ version 5.41 and above, but v. 5.41 and v. 5.42
|
||||||
|
might require setting `nocheck`, see the comment in the sample configuration.
|
||||||
|
|
||||||
|
To enable SMART on a storage device run:
|
||||||
|
|
||||||
|
```
|
||||||
|
smartctl -s on <device>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Measurements
|
||||||
|
|
||||||
|
- smart_device:
|
||||||
|
|
||||||
|
* Tags:
|
||||||
|
- `capacity`
|
||||||
|
- `device`
|
||||||
|
- `device_model`
|
||||||
|
- `enabled`
|
||||||
|
- `health`
|
||||||
|
- `serial_no`
|
||||||
|
- `wwn`
|
||||||
|
* Fields:
|
||||||
|
- `exit_status`
|
||||||
|
- `health_ok`
|
||||||
|
- `read_error_rate`
|
||||||
|
- `seek_error`
|
||||||
|
- `temp_c`
|
||||||
|
- `udma_crc_errors`
|
||||||
|
|
||||||
|
- smart_attribute:
|
||||||
|
|
||||||
|
* Tags:
|
||||||
|
- `device`
|
||||||
|
- `fail`
|
||||||
|
- `flags`
|
||||||
|
- `id`
|
||||||
|
- `name`
|
||||||
|
- `serial_no`
|
||||||
|
- `wwn`
|
||||||
|
* Fields:
|
||||||
|
- `exit_status`
|
||||||
|
- `raw_value`
|
||||||
|
- `threshold`
|
||||||
|
- `value`
|
||||||
|
- `worst`
|
||||||
|
|
||||||
|
### Flags
|
||||||
|
|
||||||
|
The interpretation of the tag `flags` is:
|
||||||
|
- *K* auto-keep
|
||||||
|
- *C* event count
|
||||||
|
- *R* error rate
|
||||||
|
- *S* speed/performance
|
||||||
|
- *O* updated online
|
||||||
|
- *P* prefailure warning
|
||||||
|
|
||||||
|
### Exit Status
|
||||||
|
|
||||||
|
The `exit_status` field captures the exit status of the smartctl command which
|
||||||
|
is defined by a bitmask. For the interpretation of the bitmask see the man page for
|
||||||
|
smartctl.
|
||||||
|
|
||||||
|
### Device Names
|
||||||
|
|
||||||
|
Device names, e.g., `/dev/sda`, are *not persistent*, and may be
|
||||||
|
subject to change across reboots or system changes. Instead, you can the
|
||||||
|
*World Wide Name* (WWN) or serial number to identify devices. On Linux block
|
||||||
|
devices can be referenced by the WWN in the following location:
|
||||||
|
`/dev/disk/by-id/`.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Read metrics from storage devices supporting S.M.A.R.T.
|
||||||
|
[[inputs.smart]]
|
||||||
|
## Optionally specify the path to the smartctl executable
|
||||||
|
# path = "/usr/bin/smartctl"
|
||||||
|
#
|
||||||
|
## On most platforms smartctl requires root access.
|
||||||
|
## Setting 'use_sudo' to true will make use of sudo to run smartctl.
|
||||||
|
## Sudo must be configured to to allow the telegraf user to run smartctl
|
||||||
|
## with out password.
|
||||||
|
# use_sudo = false
|
||||||
|
#
|
||||||
|
## Skip checking disks in this power mode. Defaults to
|
||||||
|
## "standby" to not wake up disks that have stoped rotating.
|
||||||
|
## See --nockeck in the man pages for smartctl.
|
||||||
|
## smartctl version 5.41 and 5.42 have faulty detection of
|
||||||
|
## power mode and might require changing this value to
|
||||||
|
## "never" depending on your storage device.
|
||||||
|
# nocheck = "standby"
|
||||||
|
#
|
||||||
|
## Gather detailed metrics for each SMART Attribute.
|
||||||
|
## Defaults to "false"
|
||||||
|
##
|
||||||
|
# attributes = false
|
||||||
|
#
|
||||||
|
## Optionally specify devices to exclude from reporting.
|
||||||
|
# excludes = [ "/dev/pass6" ]
|
||||||
|
#
|
||||||
|
## Optionally specify devices and device type, if unset
|
||||||
|
## a scan (smartctl --scan) for S.M.A.R.T. devices will
|
||||||
|
## done and all found will be included except for the
|
||||||
|
## excluded in excludes.
|
||||||
|
# devices = [ "/dev/ada0 -d atacam" ]
|
||||||
|
```
|
||||||
|
|
||||||
|
To run `smartctl` with `sudo` create a wrapper script and use `path` in
|
||||||
|
the configuration to execute that.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
Example output from an _Apple SSD_:
|
||||||
|
```
|
||||||
|
> smart_attribute,serial_no=S1K5NYCD964433,wwn=5002538655584d30,id=199,name=UDMA_CRC_Error_Count,flags=-O-RC-,fail=-,host=mbpro.local,device=/dev/rdisk0 threshold=0i,raw_value=0i,exit_status=0i,value=200i,worst=200i 1502536854000000000
|
||||||
|
> smart_attribute,device=/dev/rdisk0,serial_no=S1K5NYCD964433,wwn=5002538655584d30,id=240,name=Unknown_SSD_Attribute,flags=-O---K,fail=-,host=mbpro.local exit_status=0i,value=100i,worst=100i,threshold=0i,raw_value=0i 1502536854000000000
|
||||||
|
> smart_device,enabled=Enabled,host=mbpro.local,device=/dev/rdisk0,model=APPLE\ SSD\ SM0512F,serial_no=S1K5NYCD964433,wwn=5002538655584d30,capacity=500277790720 udma_crc_errors=0i,exit_status=0i,health_ok=true,read_error_rate=0i,temp_c=40i 1502536854000000000
|
||||||
|
```
|
|
@ -0,0 +1,339 @@
|
||||||
|
package smart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/internal"
|
||||||
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
execCommand = exec.Command // execCommand is used to mock commands in tests.
|
||||||
|
|
||||||
|
// Device Model: APPLE SSD SM256E
|
||||||
|
modelInInfo = regexp.MustCompile("^Device Model:\\s+(.*)$")
|
||||||
|
// Serial Number: S0X5NZBC422720
|
||||||
|
serialInInfo = regexp.MustCompile("^Serial Number:\\s+(.*)$")
|
||||||
|
// LU WWN Device Id: 5 002538 655584d30
|
||||||
|
wwnInInfo = regexp.MustCompile("^LU WWN Device Id:\\s+(.*)$")
|
||||||
|
// User Capacity: 251,000,193,024 bytes [251 GB]
|
||||||
|
usercapacityInInfo = regexp.MustCompile("^User Capacity:\\s+([0-9,]+)\\s+bytes.*$")
|
||||||
|
// SMART support is: Enabled
|
||||||
|
smartEnabledInInfo = regexp.MustCompile("^SMART support is:\\s+(\\w+)$")
|
||||||
|
// SMART overall-health self-assessment test result: PASSED
|
||||||
|
// PASSED, FAILED, UNKNOWN
|
||||||
|
smartOverallHealth = regexp.MustCompile("^SMART overall-health self-assessment test result:\\s+(\\w+).*$")
|
||||||
|
|
||||||
|
// ID# ATTRIBUTE_NAME FLAGS VALUE WORST THRESH FAIL RAW_VALUE
|
||||||
|
// 1 Raw_Read_Error_Rate -O-RC- 200 200 000 - 0
|
||||||
|
// 5 Reallocated_Sector_Ct PO--CK 100 100 000 - 0
|
||||||
|
// 192 Power-Off_Retract_Count -O--C- 097 097 000 - 14716
|
||||||
|
attribute = regexp.MustCompile("^\\s*([0-9]+)\\s(\\S+)\\s+([-P][-O][-S][-R][-C][-K])\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([-\\w]+)\\s+([\\w\\+\\.]+).*$")
|
||||||
|
|
||||||
|
deviceFieldIds = map[string]string{
|
||||||
|
"1": "read_error_rate",
|
||||||
|
"7": "seek_error_rate",
|
||||||
|
"194": "temp_c",
|
||||||
|
"199": "udma_crc_errors",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type Smart struct {
|
||||||
|
Path string
|
||||||
|
Nocheck string
|
||||||
|
Attributes bool
|
||||||
|
Excludes []string
|
||||||
|
Devices []string
|
||||||
|
UseSudo bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var sampleConfig = `
|
||||||
|
## Optionally specify the path to the smartctl executable
|
||||||
|
# path = "/usr/bin/smartctl"
|
||||||
|
#
|
||||||
|
## On most platforms smartctl requires root access.
|
||||||
|
## Setting 'use_sudo' to true will make use of sudo to run smartctl.
|
||||||
|
## Sudo must be configured to to allow the telegraf user to run smartctl
|
||||||
|
## with out password.
|
||||||
|
# use_sudo = false
|
||||||
|
#
|
||||||
|
## Skip checking disks in this power mode. Defaults to
|
||||||
|
## "standby" to not wake up disks that have stoped rotating.
|
||||||
|
## See --nocheck in the man pages for smartctl.
|
||||||
|
## smartctl version 5.41 and 5.42 have faulty detection of
|
||||||
|
## power mode and might require changing this value to
|
||||||
|
## "never" depending on your disks.
|
||||||
|
# nocheck = "standby"
|
||||||
|
#
|
||||||
|
## Gather detailed metrics for each SMART Attribute.
|
||||||
|
## Defaults to "false"
|
||||||
|
##
|
||||||
|
# attributes = false
|
||||||
|
#
|
||||||
|
## Optionally specify devices to exclude from reporting.
|
||||||
|
# excludes = [ "/dev/pass6" ]
|
||||||
|
#
|
||||||
|
## Optionally specify devices and device type, if unset
|
||||||
|
## a scan (smartctl --scan) for S.M.A.R.T. devices will
|
||||||
|
## done and all found will be included except for the
|
||||||
|
## excluded in excludes.
|
||||||
|
# devices = [ "/dev/ada0 -d atacam" ]
|
||||||
|
`
|
||||||
|
|
||||||
|
func (m *Smart) SampleConfig() string {
|
||||||
|
return sampleConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Smart) Description() string {
|
||||||
|
return "Read metrics from storage devices supporting S.M.A.R.T."
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Smart) Gather(acc telegraf.Accumulator) error {
|
||||||
|
if len(m.Path) == 0 {
|
||||||
|
return fmt.Errorf("smartctl not found: verify that smartctl is installed and that smartctl is in your PATH")
|
||||||
|
}
|
||||||
|
|
||||||
|
devices := m.Devices
|
||||||
|
if len(devices) == 0 {
|
||||||
|
var err error
|
||||||
|
devices, err = m.scan()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.getAttributes(acc, devices)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap with sudo
|
||||||
|
func sudo(sudo bool, command string, args ...string) *exec.Cmd {
|
||||||
|
if sudo {
|
||||||
|
return execCommand("sudo", append([]string{"-n", command}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return execCommand(command, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan for S.M.A.R.T. devices
|
||||||
|
func (m *Smart) scan() ([]string, error) {
|
||||||
|
|
||||||
|
cmd := sudo(m.UseSudo, m.Path, "--scan")
|
||||||
|
out, err := internal.CombinedOutputTimeout(cmd, time.Second*5)
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, fmt.Errorf("failed to run command %s: %s - %s", strings.Join(cmd.Args, " "), err, string(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
devices := []string{}
|
||||||
|
for _, line := range strings.Split(string(out), "\n") {
|
||||||
|
dev := strings.Split(line, "#")
|
||||||
|
if len(dev) > 1 && !excludedDev(m.Excludes, strings.TrimSpace(dev[0])) {
|
||||||
|
devices = append(devices, strings.TrimSpace(dev[0]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return devices, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func excludedDev(excludes []string, deviceLine string) bool {
|
||||||
|
device := strings.Split(deviceLine, " ")
|
||||||
|
if len(device) != 0 {
|
||||||
|
for _, exclude := range excludes {
|
||||||
|
if device[0] == exclude {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get info and attributes for each S.M.A.R.T. device
|
||||||
|
func (m *Smart) getAttributes(acc telegraf.Accumulator, devices []string) {
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(len(devices))
|
||||||
|
|
||||||
|
for _, device := range devices {
|
||||||
|
go gatherDisk(acc, m.UseSudo, m.Attributes, m.Path, m.Nocheck, device, &wg)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command line parse errors are denoted by the exit code having the 0 bit set.
|
||||||
|
// All other errors are drive/communication errors and should be ignored.
|
||||||
|
func exitStatus(err error) (int, error) {
|
||||||
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||||
|
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||||
|
return status.ExitStatus(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func gatherDisk(acc telegraf.Accumulator, usesudo, attributes bool, path, nockeck, device string, wg *sync.WaitGroup) {
|
||||||
|
|
||||||
|
defer wg.Done()
|
||||||
|
// smartctl 5.41 & 5.42 have are broken regarding handling of --nocheck/-n
|
||||||
|
args := []string{"--info", "--health", "--attributes", "--tolerance=verypermissive", "-n", nockeck, "--format=brief"}
|
||||||
|
args = append(args, strings.Split(device, " ")...)
|
||||||
|
cmd := sudo(usesudo, path, args...)
|
||||||
|
out, e := internal.CombinedOutputTimeout(cmd, time.Second*5)
|
||||||
|
outStr := string(out)
|
||||||
|
|
||||||
|
// Ignore all exit statuses except if it is a command line parse error
|
||||||
|
exitStatus, er := exitStatus(e)
|
||||||
|
if er != nil {
|
||||||
|
acc.AddError(fmt.Errorf("failed to run command %s: %s - %s", strings.Join(cmd.Args, " "), e, outStr))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
device_tags := map[string]string{}
|
||||||
|
device_tags["device"] = strings.Split(device, " ")[0]
|
||||||
|
device_fields := make(map[string]interface{})
|
||||||
|
device_fields["exit_status"] = exitStatus
|
||||||
|
|
||||||
|
for _, line := range strings.Split(outStr, "\n") {
|
||||||
|
|
||||||
|
model := modelInInfo.FindStringSubmatch(line)
|
||||||
|
if len(model) > 1 {
|
||||||
|
device_tags["model"] = model[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
serial := serialInInfo.FindStringSubmatch(line)
|
||||||
|
if len(serial) > 1 {
|
||||||
|
device_tags["serial_no"] = serial[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
wwn := wwnInInfo.FindStringSubmatch(line)
|
||||||
|
if len(wwn) > 1 {
|
||||||
|
device_tags["wwn"] = strings.Replace(wwn[1], " ", "", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
capacity := usercapacityInInfo.FindStringSubmatch(line)
|
||||||
|
if len(capacity) > 1 {
|
||||||
|
device_tags["capacity"] = strings.Replace(capacity[1], ",", "", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
enabled := smartEnabledInInfo.FindStringSubmatch(line)
|
||||||
|
if len(enabled) > 1 {
|
||||||
|
device_tags["enabled"] = enabled[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
health := smartOverallHealth.FindStringSubmatch(line)
|
||||||
|
if len(health) > 1 {
|
||||||
|
device_fields["health_ok"] = (health[1] == "PASSED")
|
||||||
|
}
|
||||||
|
|
||||||
|
attr := attribute.FindStringSubmatch(line)
|
||||||
|
|
||||||
|
if len(attr) > 1 {
|
||||||
|
|
||||||
|
if attributes {
|
||||||
|
tags := map[string]string{}
|
||||||
|
fields := make(map[string]interface{})
|
||||||
|
|
||||||
|
tags["device"] = strings.Split(device, " ")[0]
|
||||||
|
|
||||||
|
if serial, ok := device_tags["serial_no"]; ok {
|
||||||
|
tags["serial_no"] = serial
|
||||||
|
}
|
||||||
|
if wwn, ok := device_tags["wwn"]; ok {
|
||||||
|
tags["wwn"] = wwn
|
||||||
|
}
|
||||||
|
tags["id"] = attr[1]
|
||||||
|
tags["name"] = attr[2]
|
||||||
|
tags["flags"] = attr[3]
|
||||||
|
|
||||||
|
fields["exit_status"] = exitStatus
|
||||||
|
if i, err := strconv.ParseInt(attr[4], 10, 64); err == nil {
|
||||||
|
fields["value"] = i
|
||||||
|
}
|
||||||
|
if i, err := strconv.ParseInt(attr[5], 10, 64); err == nil {
|
||||||
|
fields["worst"] = i
|
||||||
|
}
|
||||||
|
if i, err := strconv.ParseInt(attr[6], 10, 64); err == nil {
|
||||||
|
fields["threshold"] = i
|
||||||
|
}
|
||||||
|
|
||||||
|
tags["fail"] = attr[7]
|
||||||
|
if val, err := parseRawValue(attr[8]); err == nil {
|
||||||
|
fields["raw_value"] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
acc.AddFields("smart_attribute", fields, tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the attribute matches on the one in deviceFieldIds
|
||||||
|
// save the raw value to a field.
|
||||||
|
if field, ok := deviceFieldIds[attr[1]]; ok {
|
||||||
|
if val, err := parseRawValue(attr[8]); err == nil {
|
||||||
|
device_fields[field] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acc.AddFields("smart_device", device_fields, device_tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRawValue(rawVal string) (int64, error) {
|
||||||
|
|
||||||
|
// Integer
|
||||||
|
if i, err := strconv.ParseInt(rawVal, 10, 64); err == nil {
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duration: 65h+33m+09.259s
|
||||||
|
unit := regexp.MustCompile("^(.*)([hms])$")
|
||||||
|
parts := strings.Split(rawVal, "+")
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return 0, fmt.Errorf("Couldn't parse RAW_VALUE '%s'", rawVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
duration := int64(0)
|
||||||
|
for _, part := range parts {
|
||||||
|
timePart := unit.FindStringSubmatch(part)
|
||||||
|
if len(timePart) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch timePart[2] {
|
||||||
|
case "h":
|
||||||
|
duration += parseInt(timePart[1]) * int64(3600)
|
||||||
|
case "m":
|
||||||
|
duration += parseInt(timePart[1]) * int64(60)
|
||||||
|
case "s":
|
||||||
|
// drop fractions of seconds
|
||||||
|
duration += parseInt(strings.Split(timePart[1], ".")[0])
|
||||||
|
default:
|
||||||
|
// Unknown, ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return duration, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInt(str string) int64 {
|
||||||
|
if i, err := strconv.ParseInt(str, 10, 64); err == nil {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
m := Smart{}
|
||||||
|
path, _ := exec.LookPath("smartctl")
|
||||||
|
if len(path) > 0 {
|
||||||
|
m.Path = path
|
||||||
|
}
|
||||||
|
m.Nocheck = "standby"
|
||||||
|
|
||||||
|
inputs.Add("smart", func() telegraf.Input {
|
||||||
|
return &m
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,426 @@
|
||||||
|
package smart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf/testutil"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
mockScanData = `/dev/ada0 -d atacam # /dev/ada0, ATA device
|
||||||
|
`
|
||||||
|
mockInfoAttributeData = `smartctl 6.5 2016-05-07 r4318 [Darwin 16.4.0 x86_64] (local build)
|
||||||
|
Copyright (C) 2002-16, Bruce Allen, Christian Franke, www.smartmontools.org
|
||||||
|
|
||||||
|
CHECK POWER MODE not implemented, ignoring -n option
|
||||||
|
=== START OF INFORMATION SECTION ===
|
||||||
|
Model Family: Apple SD/SM/TS...E/F SSDs
|
||||||
|
Device Model: APPLE SSD SM256E
|
||||||
|
Serial Number: S0X5NZBC422720
|
||||||
|
LU WWN Device Id: 5 002538 043584d30
|
||||||
|
Firmware Version: CXM09A1Q
|
||||||
|
User Capacity: 251,000,193,024 bytes [251 GB]
|
||||||
|
Sector Sizes: 512 bytes logical, 4096 bytes physical
|
||||||
|
Rotation Rate: Solid State Device
|
||||||
|
Device is: In smartctl database [for details use: -P show]
|
||||||
|
ATA Version is: ATA8-ACS T13/1699-D revision 4c
|
||||||
|
SATA Version is: SATA 3.0, 6.0 Gb/s (current: 6.0 Gb/s)
|
||||||
|
Local Time is: Thu Feb 9 16:48:45 2017 CET
|
||||||
|
SMART support is: Available - device has SMART capability.
|
||||||
|
SMART support is: Enabled
|
||||||
|
|
||||||
|
=== START OF READ SMART DATA SECTION ===
|
||||||
|
SMART overall-health self-assessment test result: PASSED
|
||||||
|
|
||||||
|
=== START OF READ SMART DATA SECTION ===
|
||||||
|
SMART Attributes Data Structure revision number: 1
|
||||||
|
Vendor Specific SMART Attributes with Thresholds:
|
||||||
|
ID# ATTRIBUTE_NAME FLAGS VALUE WORST THRESH FAIL RAW_VALUE
|
||||||
|
1 Raw_Read_Error_Rate -O-RC- 200 200 000 - 0
|
||||||
|
5 Reallocated_Sector_Ct PO--CK 100 100 000 - 0
|
||||||
|
9 Power_On_Hours -O--CK 099 099 000 - 2988
|
||||||
|
12 Power_Cycle_Count -O--CK 085 085 000 - 14879
|
||||||
|
169 Unknown_Attribute PO--C- 253 253 010 - 2044932921600
|
||||||
|
173 Wear_Leveling_Count -O--CK 185 185 100 - 957808640337
|
||||||
|
190 Airflow_Temperature_Cel -O---K 055 040 045 Past 45 (Min/Max 43/57 #2689)
|
||||||
|
192 Power-Off_Retract_Count -O--C- 097 097 000 - 14716
|
||||||
|
194 Temperature_Celsius -O---K 066 021 000 - 34 (Min/Max 14/79)
|
||||||
|
197 Current_Pending_Sector -O---K 100 100 000 - 0
|
||||||
|
199 UDMA_CRC_Error_Count -O-RC- 200 200 000 - 0
|
||||||
|
240 Head_Flying_Hours ------ 100 253 000 - 6585h+55m+23.234s
|
||||||
|
||||||_ K auto-keep
|
||||||
|
|||||__ C event count
|
||||||
|
||||___ R error rate
|
||||||
|
|||____ S speed/performance
|
||||||
|
||_____ O updated online
|
||||||
|
|______ P prefailure warning
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGatherAttributes(t *testing.T) {
|
||||||
|
s := &Smart{
|
||||||
|
Path: "smartctl",
|
||||||
|
Attributes: true,
|
||||||
|
}
|
||||||
|
// overwriting exec commands with mock commands
|
||||||
|
execCommand = fakeExecCommand
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
|
||||||
|
err := s.Gather(&acc)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 65, acc.NFields(), "Wrong number of fields gathered")
|
||||||
|
|
||||||
|
var testsAda0Attributes = []struct {
|
||||||
|
fields map[string]interface{}
|
||||||
|
tags map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
map[string]interface{}{
|
||||||
|
"value": int64(200),
|
||||||
|
"worst": int64(200),
|
||||||
|
"threshold": int64(0),
|
||||||
|
"raw_value": int64(0),
|
||||||
|
"exit_status": int(0),
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"device": "/dev/ada0",
|
||||||
|
"serial_no": "S0X5NZBC422720",
|
||||||
|
"wwn": "5002538043584d30",
|
||||||
|
"id": "1",
|
||||||
|
"name": "Raw_Read_Error_Rate",
|
||||||
|
"flags": "-O-RC-",
|
||||||
|
"fail": "-",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
map[string]interface{}{
|
||||||
|
"value": int64(100),
|
||||||
|
"worst": int64(100),
|
||||||
|
"threshold": int64(0),
|
||||||
|
"raw_value": int64(0),
|
||||||
|
"exit_status": int(0),
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"device": "/dev/ada0",
|
||||||
|
"serial_no": "S0X5NZBC422720",
|
||||||
|
"wwn": "5002538043584d30",
|
||||||
|
"id": "5",
|
||||||
|
"name": "Reallocated_Sector_Ct",
|
||||||
|
"flags": "PO--CK",
|
||||||
|
"fail": "-",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
map[string]interface{}{
|
||||||
|
"value": int64(99),
|
||||||
|
"worst": int64(99),
|
||||||
|
"threshold": int64(0),
|
||||||
|
"raw_value": int64(2988),
|
||||||
|
"exit_status": int(0),
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"device": "/dev/ada0",
|
||||||
|
"serial_no": "S0X5NZBC422720",
|
||||||
|
"wwn": "5002538043584d30",
|
||||||
|
"id": "9",
|
||||||
|
"name": "Power_On_Hours",
|
||||||
|
"flags": "-O--CK",
|
||||||
|
"fail": "-",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
map[string]interface{}{
|
||||||
|
"value": int64(85),
|
||||||
|
"worst": int64(85),
|
||||||
|
"threshold": int64(0),
|
||||||
|
"raw_value": int64(14879),
|
||||||
|
"exit_status": int(0),
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"device": "/dev/ada0",
|
||||||
|
"serial_no": "S0X5NZBC422720",
|
||||||
|
"wwn": "5002538043584d30",
|
||||||
|
"id": "12",
|
||||||
|
"name": "Power_Cycle_Count",
|
||||||
|
"flags": "-O--CK",
|
||||||
|
"fail": "-",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
map[string]interface{}{
|
||||||
|
"value": int64(253),
|
||||||
|
"worst": int64(253),
|
||||||
|
"threshold": int64(10),
|
||||||
|
"raw_value": int64(2044932921600),
|
||||||
|
"exit_status": int(0),
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"device": "/dev/ada0",
|
||||||
|
"serial_no": "S0X5NZBC422720",
|
||||||
|
"wwn": "5002538043584d30",
|
||||||
|
"id": "169",
|
||||||
|
"name": "Unknown_Attribute",
|
||||||
|
"flags": "PO--C-",
|
||||||
|
"fail": "-",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
map[string]interface{}{
|
||||||
|
"value": int64(185),
|
||||||
|
"worst": int64(185),
|
||||||
|
"threshold": int64(100),
|
||||||
|
"raw_value": int64(957808640337),
|
||||||
|
"exit_status": int(0),
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"device": "/dev/ada0",
|
||||||
|
"serial_no": "S0X5NZBC422720",
|
||||||
|
"wwn": "5002538043584d30",
|
||||||
|
"id": "173",
|
||||||
|
"name": "Wear_Leveling_Count",
|
||||||
|
"flags": "-O--CK",
|
||||||
|
"fail": "-",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
map[string]interface{}{
|
||||||
|
"value": int64(55),
|
||||||
|
"worst": int64(40),
|
||||||
|
"threshold": int64(45),
|
||||||
|
"raw_value": int64(45),
|
||||||
|
"exit_status": int(0),
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"device": "/dev/ada0",
|
||||||
|
"serial_no": "S0X5NZBC422720",
|
||||||
|
"wwn": "5002538043584d30",
|
||||||
|
"id": "190",
|
||||||
|
"name": "Airflow_Temperature_Cel",
|
||||||
|
"flags": "-O---K",
|
||||||
|
"fail": "Past",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
map[string]interface{}{
|
||||||
|
"value": int64(97),
|
||||||
|
"worst": int64(97),
|
||||||
|
"threshold": int64(0),
|
||||||
|
"raw_value": int64(14716),
|
||||||
|
"exit_status": int(0),
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"device": "/dev/ada0",
|
||||||
|
"serial_no": "S0X5NZBC422720",
|
||||||
|
"wwn": "5002538043584d30",
|
||||||
|
"id": "192",
|
||||||
|
"name": "Power-Off_Retract_Count",
|
||||||
|
"flags": "-O--C-",
|
||||||
|
"fail": "-",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
map[string]interface{}{
|
||||||
|
"value": int64(66),
|
||||||
|
"worst": int64(21),
|
||||||
|
"threshold": int64(0),
|
||||||
|
"raw_value": int64(34),
|
||||||
|
"exit_status": int(0),
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"device": "/dev/ada0",
|
||||||
|
"serial_no": "S0X5NZBC422720",
|
||||||
|
"wwn": "5002538043584d30",
|
||||||
|
"id": "194",
|
||||||
|
"name": "Temperature_Celsius",
|
||||||
|
"flags": "-O---K",
|
||||||
|
"fail": "-",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
map[string]interface{}{
|
||||||
|
"value": int64(100),
|
||||||
|
"worst": int64(100),
|
||||||
|
"threshold": int64(0),
|
||||||
|
"raw_value": int64(0),
|
||||||
|
"exit_status": int(0),
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"device": "/dev/ada0",
|
||||||
|
"serial_no": "S0X5NZBC422720",
|
||||||
|
"wwn": "5002538043584d30",
|
||||||
|
"id": "197",
|
||||||
|
"name": "Current_Pending_Sector",
|
||||||
|
"flags": "-O---K",
|
||||||
|
"fail": "-",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
map[string]interface{}{
|
||||||
|
"value": int64(200),
|
||||||
|
"worst": int64(200),
|
||||||
|
"threshold": int64(0),
|
||||||
|
"raw_value": int64(0),
|
||||||
|
"exit_status": int(0),
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"device": "/dev/ada0",
|
||||||
|
"serial_no": "S0X5NZBC422720",
|
||||||
|
"wwn": "5002538043584d30",
|
||||||
|
"id": "199",
|
||||||
|
"name": "UDMA_CRC_Error_Count",
|
||||||
|
"flags": "-O-RC-",
|
||||||
|
"fail": "-",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
map[string]interface{}{
|
||||||
|
"value": int64(100),
|
||||||
|
"worst": int64(253),
|
||||||
|
"threshold": int64(0),
|
||||||
|
"raw_value": int64(23709323),
|
||||||
|
"exit_status": int(0),
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"device": "/dev/ada0",
|
||||||
|
"serial_no": "S0X5NZBC422720",
|
||||||
|
"wwn": "5002538043584d30",
|
||||||
|
"id": "240",
|
||||||
|
"name": "Head_Flying_Hours",
|
||||||
|
"flags": "------",
|
||||||
|
"fail": "-",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testsAda0Attributes {
|
||||||
|
acc.AssertContainsTaggedFields(t, "smart_attribute", test.fields, test.tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tags = map[string]string{}
|
||||||
|
|
||||||
|
var testsAda0Device = []struct {
|
||||||
|
fields map[string]interface{}
|
||||||
|
tags map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
map[string]interface{}{
|
||||||
|
"exit_status": int(0),
|
||||||
|
"health_ok": bool(true),
|
||||||
|
"read_error_rate": int64(0),
|
||||||
|
"temp_c": int64(34),
|
||||||
|
"udma_crc_errors": int64(0),
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"device": "/dev/ada0",
|
||||||
|
"model": "APPLE SSD SM256E",
|
||||||
|
"serial_no": "S0X5NZBC422720",
|
||||||
|
"wwn": "5002538043584d30",
|
||||||
|
"enabled": "Enabled",
|
||||||
|
"capacity": "251000193024",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testsAda0Device {
|
||||||
|
acc.AssertContainsTaggedFields(t, "smart_device", test.fields, test.tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGatherNoAttributes(t *testing.T) {
|
||||||
|
s := &Smart{
|
||||||
|
Path: "smartctl",
|
||||||
|
Attributes: false,
|
||||||
|
}
|
||||||
|
// overwriting exec commands with mock commands
|
||||||
|
execCommand = fakeExecCommand
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
|
||||||
|
err := s.Gather(&acc)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 5, acc.NFields(), "Wrong number of fields gathered")
|
||||||
|
acc.AssertDoesNotContainMeasurement(t, "smart_attribute")
|
||||||
|
|
||||||
|
// tags = map[string]string{}
|
||||||
|
|
||||||
|
var testsAda0Device = []struct {
|
||||||
|
fields map[string]interface{}
|
||||||
|
tags map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
map[string]interface{}{
|
||||||
|
"exit_status": int(0),
|
||||||
|
"health_ok": bool(true),
|
||||||
|
"read_error_rate": int64(0),
|
||||||
|
"temp_c": int64(34),
|
||||||
|
"udma_crc_errors": int64(0),
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"device": "/dev/ada0",
|
||||||
|
"model": "APPLE SSD SM256E",
|
||||||
|
"serial_no": "S0X5NZBC422720",
|
||||||
|
"wwn": "5002538043584d30",
|
||||||
|
"enabled": "Enabled",
|
||||||
|
"capacity": "251000193024",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testsAda0Device {
|
||||||
|
acc.AssertContainsTaggedFields(t, "smart_device", test.fields, test.tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExcludedDev(t *testing.T) {
|
||||||
|
assert.Equal(t, true, excludedDev([]string{"/dev/pass6"}, "/dev/pass6 -d atacam"), "Should be excluded.")
|
||||||
|
assert.Equal(t, false, excludedDev([]string{}, "/dev/pass6 -d atacam"), "Shouldn't be excluded.")
|
||||||
|
assert.Equal(t, false, excludedDev([]string{"/dev/pass6"}, "/dev/pass1 -d atacam"), "Shouldn't be excluded.")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// fackeExecCommand is a helper function that mock
|
||||||
|
// the exec.Command call (and call the test binary)
|
||||||
|
func fakeExecCommand(command string, args ...string) *exec.Cmd {
|
||||||
|
cs := []string{"-test.run=TestHelperProcess", "--", command}
|
||||||
|
cs = append(cs, args...)
|
||||||
|
cmd := exec.Command(os.Args[0], cs...)
|
||||||
|
cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHelperProcess isn't a real test. It's used to mock exec.Command
|
||||||
|
// For example, if you run:
|
||||||
|
// GO_WANT_HELPER_PROCESS=1 go test -test.run=TestHelperProcess -- --scan
|
||||||
|
// it returns below mockScanData.
|
||||||
|
func TestHelperProcess(t *testing.T) {
|
||||||
|
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
args := os.Args
|
||||||
|
|
||||||
|
// Previous arguments are tests stuff, that looks like :
|
||||||
|
// /tmp/go-build970079519/…/_test/integration.test -test.run=TestHelperProcess --
|
||||||
|
cmd, arg1, args := args[3], args[4], args[5:]
|
||||||
|
|
||||||
|
if cmd == "smartctl" {
|
||||||
|
if arg1 == "--scan" {
|
||||||
|
fmt.Fprint(os.Stdout, mockScanData)
|
||||||
|
}
|
||||||
|
if arg1 == "--info" {
|
||||||
|
fmt.Fprint(os.Stdout, mockInfoAttributeData)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Fprint(os.Stdout, "command not found")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
|
@ -135,7 +135,7 @@ type Snmp struct {
|
||||||
Name string
|
Name string
|
||||||
Fields []Field `toml:"field"`
|
Fields []Field `toml:"field"`
|
||||||
|
|
||||||
connectionCache map[string]snmpConnection
|
connectionCache []snmpConnection
|
||||||
initialized bool
|
initialized bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,6 +144,8 @@ func (s *Snmp) init() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.connectionCache = make([]snmpConnection, len(s.Agents))
|
||||||
|
|
||||||
for i := range s.Tables {
|
for i := range s.Tables {
|
||||||
if err := s.Tables[i].init(); err != nil {
|
if err := s.Tables[i].init(); err != nil {
|
||||||
return Errorf(err, "initializing table %s", s.Tables[i].Name)
|
return Errorf(err, "initializing table %s", s.Tables[i].Name)
|
||||||
|
@ -342,30 +344,36 @@ func (s *Snmp) Gather(acc telegraf.Accumulator) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, agent := range s.Agents {
|
var wg sync.WaitGroup
|
||||||
gs, err := s.getConnection(agent)
|
for i, agent := range s.Agents {
|
||||||
if err != nil {
|
wg.Add(1)
|
||||||
acc.AddError(Errorf(err, "agent %s", agent))
|
go func(i int, agent string) {
|
||||||
continue
|
defer wg.Done()
|
||||||
}
|
gs, err := s.getConnection(i)
|
||||||
|
if err != nil {
|
||||||
// First is the top-level fields. We treat the fields as table prefixes with an empty index.
|
acc.AddError(Errorf(err, "agent %s", agent))
|
||||||
t := Table{
|
return
|
||||||
Name: s.Name,
|
|
||||||
Fields: s.Fields,
|
|
||||||
}
|
|
||||||
topTags := map[string]string{}
|
|
||||||
if err := s.gatherTable(acc, gs, t, topTags, false); err != nil {
|
|
||||||
acc.AddError(Errorf(err, "agent %s", agent))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now is the real tables.
|
|
||||||
for _, t := range s.Tables {
|
|
||||||
if err := s.gatherTable(acc, gs, t, topTags, true); err != nil {
|
|
||||||
acc.AddError(Errorf(err, "agent %s: gathering table %s", agent, t.Name))
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// First is the top-level fields. We treat the fields as table prefixes with an empty index.
|
||||||
|
t := Table{
|
||||||
|
Name: s.Name,
|
||||||
|
Fields: s.Fields,
|
||||||
|
}
|
||||||
|
topTags := map[string]string{}
|
||||||
|
if err := s.gatherTable(acc, gs, t, topTags, false); err != nil {
|
||||||
|
acc.AddError(Errorf(err, "agent %s", agent))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now is the real tables.
|
||||||
|
for _, t := range s.Tables {
|
||||||
|
if err := s.gatherTable(acc, gs, t, topTags, true); err != nil {
|
||||||
|
acc.AddError(Errorf(err, "agent %s: gathering table %s", agent, t.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(i, agent)
|
||||||
}
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -568,16 +576,18 @@ func (gsw gosnmpWrapper) Get(oids []string) (*gosnmp.SnmpPacket, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// getConnection creates a snmpConnection (*gosnmp.GoSNMP) object and caches the
|
// getConnection creates a snmpConnection (*gosnmp.GoSNMP) object and caches the
|
||||||
// result using `agent` as the cache key.
|
// result using `agentIndex` as the cache key. This is done to allow multiple
|
||||||
func (s *Snmp) getConnection(agent string) (snmpConnection, error) {
|
// connections to a single address. It is an error to use a connection in
|
||||||
if s.connectionCache == nil {
|
// more than one goroutine.
|
||||||
s.connectionCache = map[string]snmpConnection{}
|
func (s *Snmp) getConnection(idx int) (snmpConnection, error) {
|
||||||
}
|
if gs := s.connectionCache[idx]; gs != nil {
|
||||||
if gs, ok := s.connectionCache[agent]; ok {
|
|
||||||
return gs, nil
|
return gs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
agent := s.Agents[idx]
|
||||||
|
|
||||||
gs := gosnmpWrapper{&gosnmp.GoSNMP{}}
|
gs := gosnmpWrapper{&gosnmp.GoSNMP{}}
|
||||||
|
s.connectionCache[idx] = gs
|
||||||
|
|
||||||
host, portStr, err := net.SplitHostPort(agent)
|
host, portStr, err := net.SplitHostPort(agent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -677,7 +687,6 @@ func (s *Snmp) getConnection(agent string) (snmpConnection, error) {
|
||||||
return nil, Errorf(err, "setting up connection")
|
return nil, Errorf(err, "setting up connection")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.connectionCache[agent] = gs
|
|
||||||
return gs, nil
|
return gs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -120,7 +120,7 @@ func TestSampleConfig(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
assert.Equal(t, s, *conf.Inputs.Snmp[0])
|
assert.Equal(t, &s, conf.Inputs.Snmp[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFieldInit(t *testing.T) {
|
func TestFieldInit(t *testing.T) {
|
||||||
|
@ -251,13 +251,16 @@ func TestSnmpInit_noTranslate(t *testing.T) {
|
||||||
|
|
||||||
func TestGetSNMPConnection_v2(t *testing.T) {
|
func TestGetSNMPConnection_v2(t *testing.T) {
|
||||||
s := &Snmp{
|
s := &Snmp{
|
||||||
|
Agents: []string{"1.2.3.4:567", "1.2.3.4"},
|
||||||
Timeout: internal.Duration{Duration: 3 * time.Second},
|
Timeout: internal.Duration{Duration: 3 * time.Second},
|
||||||
Retries: 4,
|
Retries: 4,
|
||||||
Version: 2,
|
Version: 2,
|
||||||
Community: "foo",
|
Community: "foo",
|
||||||
}
|
}
|
||||||
|
err := s.init()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
gsc, err := s.getConnection("1.2.3.4:567")
|
gsc, err := s.getConnection(0)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
gs := gsc.(gosnmpWrapper)
|
gs := gsc.(gosnmpWrapper)
|
||||||
assert.Equal(t, "1.2.3.4", gs.Target)
|
assert.Equal(t, "1.2.3.4", gs.Target)
|
||||||
|
@ -265,7 +268,7 @@ func TestGetSNMPConnection_v2(t *testing.T) {
|
||||||
assert.Equal(t, gosnmp.Version2c, gs.Version)
|
assert.Equal(t, gosnmp.Version2c, gs.Version)
|
||||||
assert.Equal(t, "foo", gs.Community)
|
assert.Equal(t, "foo", gs.Community)
|
||||||
|
|
||||||
gsc, err = s.getConnection("1.2.3.4")
|
gsc, err = s.getConnection(1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
gs = gsc.(gosnmpWrapper)
|
gs = gsc.(gosnmpWrapper)
|
||||||
assert.Equal(t, "1.2.3.4", gs.Target)
|
assert.Equal(t, "1.2.3.4", gs.Target)
|
||||||
|
@ -274,6 +277,7 @@ func TestGetSNMPConnection_v2(t *testing.T) {
|
||||||
|
|
||||||
func TestGetSNMPConnection_v3(t *testing.T) {
|
func TestGetSNMPConnection_v3(t *testing.T) {
|
||||||
s := &Snmp{
|
s := &Snmp{
|
||||||
|
Agents: []string{"1.2.3.4"},
|
||||||
Version: 3,
|
Version: 3,
|
||||||
MaxRepetitions: 20,
|
MaxRepetitions: 20,
|
||||||
ContextName: "mycontext",
|
ContextName: "mycontext",
|
||||||
|
@ -287,8 +291,10 @@ func TestGetSNMPConnection_v3(t *testing.T) {
|
||||||
EngineBoots: 1,
|
EngineBoots: 1,
|
||||||
EngineTime: 2,
|
EngineTime: 2,
|
||||||
}
|
}
|
||||||
|
err := s.init()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
gsc, err := s.getConnection("1.2.3.4")
|
gsc, err := s.getConnection(0)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
gs := gsc.(gosnmpWrapper)
|
gs := gsc.(gosnmpWrapper)
|
||||||
assert.Equal(t, gs.Version, gosnmp.Version3)
|
assert.Equal(t, gs.Version, gosnmp.Version3)
|
||||||
|
@ -308,15 +314,22 @@ func TestGetSNMPConnection_v3(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetSNMPConnection_caching(t *testing.T) {
|
func TestGetSNMPConnection_caching(t *testing.T) {
|
||||||
s := &Snmp{}
|
s := &Snmp{
|
||||||
gs1, err := s.getConnection("1.2.3.4")
|
Agents: []string{"1.2.3.4", "1.2.3.5", "1.2.3.5"},
|
||||||
|
}
|
||||||
|
err := s.init()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
gs2, err := s.getConnection("1.2.3.4")
|
gs1, err := s.getConnection(0)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
gs3, err := s.getConnection("1.2.3.5")
|
gs2, err := s.getConnection(0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
gs3, err := s.getConnection(1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
gs4, err := s.getConnection(2)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.True(t, gs1 == gs2)
|
assert.True(t, gs1 == gs2)
|
||||||
assert.False(t, gs2 == gs3)
|
assert.False(t, gs2 == gs3)
|
||||||
|
assert.False(t, gs3 == gs4)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGosnmpWrapper_walk_retry(t *testing.T) {
|
func TestGosnmpWrapper_walk_retry(t *testing.T) {
|
||||||
|
@ -560,11 +573,11 @@ func TestGather(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
connectionCache: map[string]snmpConnection{
|
connectionCache: []snmpConnection{
|
||||||
"TestGather": tsc,
|
tsc,
|
||||||
},
|
},
|
||||||
|
initialized: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
acc := &testutil.Accumulator{}
|
acc := &testutil.Accumulator{}
|
||||||
|
|
||||||
tstart := time.Now()
|
tstart := time.Now()
|
||||||
|
@ -607,9 +620,10 @@ func TestGather_host(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
connectionCache: map[string]snmpConnection{
|
connectionCache: []snmpConnection{
|
||||||
"TestGather": tsc,
|
tsc,
|
||||||
},
|
},
|
||||||
|
initialized: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
acc := &testutil.Accumulator{}
|
acc := &testutil.Accumulator{}
|
||||||
|
|
|
@ -2,11 +2,12 @@ package sqlserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"github.com/influxdata/telegraf"
|
|
||||||
"github.com/influxdata/telegraf/plugins/inputs"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||||||
|
|
||||||
// go-mssqldb initialization
|
// go-mssqldb initialization
|
||||||
_ "github.com/zensqlmonitor/go-mssqldb"
|
_ "github.com/zensqlmonitor/go-mssqldb"
|
||||||
)
|
)
|
||||||
|
@ -244,10 +245,10 @@ UNION ALL
|
||||||
SELECT 'Average pending disk IO', AveragePendingDiskIOCount = (SELECT AVG(pending_disk_io_count) FROM sys.dm_os_schedulers WITH (NOLOCK) WHERE scheduler_id < 255 )
|
SELECT 'Average pending disk IO', AveragePendingDiskIOCount = (SELECT AVG(pending_disk_io_count) FROM sys.dm_os_schedulers WITH (NOLOCK) WHERE scheduler_id < 255 )
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT 'Buffer pool rate (bytes/sec)', BufferPoolRate = (1.0*cntr_value * 8 * 1024) /
|
SELECT 'Buffer pool rate (bytes/sec)', BufferPoolRate = (1.0*cntr_value * 8 * 1024) /
|
||||||
(SELECT 1.0*cntr_value FROM sys.dm_os_performance_counters WHERE object_name like '%Buffer Manager%' AND lower(counter_name) = 'Page life expectancy')
|
(SELECT 1.0*cntr_value FROM sys.dm_os_performance_counters WHERE object_name like '%Buffer Manager%' AND counter_name = 'Page life expectancy')
|
||||||
FROM sys.dm_os_performance_counters
|
FROM sys.dm_os_performance_counters
|
||||||
WHERE object_name like '%Buffer Manager%'
|
WHERE object_name like '%Buffer Manager%'
|
||||||
AND counter_name = 'database pages'
|
AND counter_name = 'Database pages'
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT 'Memory grant pending', MemoryGrantPending = cntr_value
|
SELECT 'Memory grant pending', MemoryGrantPending = cntr_value
|
||||||
FROM sys.dm_os_performance_counters
|
FROM sys.dm_os_performance_counters
|
||||||
|
@ -1436,16 +1437,16 @@ SELECT
|
||||||
, type = 'Wait stats'
|
, type = 'Wait stats'
|
||||||
---- values
|
---- values
|
||||||
, [I/O] = SUM([I/O])
|
, [I/O] = SUM([I/O])
|
||||||
, [Latch] = SUM([Latch])
|
, [Latch] = SUM([LATCH])
|
||||||
, [Lock] = SUM([Lock])
|
, [Lock] = SUM([LOCK])
|
||||||
, [Network] = SUM([Network])
|
, [Network] = SUM([NETWORK])
|
||||||
, [Service broker] = SUM([Service broker])
|
, [Service broker] = SUM([SERVICE BROKER])
|
||||||
, [Memory] = SUM([Memory])
|
, [Memory] = SUM([MEMORY])
|
||||||
, [Buffer] = SUM([Buffer])
|
, [Buffer] = SUM([BUFFER])
|
||||||
, [CLR] = SUM([CLR])
|
, [CLR] = SUM([CLR])
|
||||||
, [SQLOS] = SUM([SQLOS])
|
, [SQLOS] = SUM([SQLOS])
|
||||||
, [XEvent] = SUM([XEvent])
|
, [XEvent] = SUM([XEVENT])
|
||||||
, [Other] = SUM([Other])
|
, [Other] = SUM([OTHER])
|
||||||
, [Total] = SUM([I/O]+[LATCH]+[LOCK]+[NETWORK]+[SERVICE BROKER]+[MEMORY]+[BUFFER]+[CLR]+[XEVENT]+[SQLOS]+[OTHER])
|
, [Total] = SUM([I/O]+[LATCH]+[LOCK]+[NETWORK]+[SERVICE BROKER]+[MEMORY]+[BUFFER]+[CLR]+[XEVENT]+[SQLOS]+[OTHER])
|
||||||
FROM
|
FROM
|
||||||
(
|
(
|
||||||
|
@ -1479,16 +1480,16 @@ SELECT
|
||||||
, type = 'Wait stats'
|
, type = 'Wait stats'
|
||||||
---- values
|
---- values
|
||||||
, [I/O] = SUM([I/O])
|
, [I/O] = SUM([I/O])
|
||||||
, [Latch] = SUM([Latch])
|
, [Latch] = SUM([LATCH])
|
||||||
, [Lock] = SUM([Lock])
|
, [Lock] = SUM([LOCK])
|
||||||
, [Network] = SUM([Network])
|
, [Network] = SUM([NETWORK])
|
||||||
, [Service broker] = SUM([Service broker])
|
, [Service broker] = SUM([SERVICE BROKER])
|
||||||
, [Memory] = SUM([Memory])
|
, [Memory] = SUM([MEMORY])
|
||||||
, [Buffer] = SUM([Buffer])
|
, [Buffer] = SUM([BUFFER])
|
||||||
, [CLR] = SUM([CLR])
|
, [CLR] = SUM([CLR])
|
||||||
, [SQLOS] = SUM([SQLOS])
|
, [SQLOS] = SUM([SQLOS])
|
||||||
, [XEvent] = SUM([XEvent])
|
, [XEvent] = SUM([XEVENT])
|
||||||
, [Other] = SUM([Other])
|
, [Other] = SUM([OTHER])
|
||||||
, [Total] = SUM([I/O]+[LATCH]+[LOCK]+[NETWORK]+[SERVICE BROKER]+[MEMORY]+[BUFFER]+[CLR]+[XEVENT]+[SQLOS]+[OTHER])
|
, [Total] = SUM([I/O]+[LATCH]+[LOCK]+[NETWORK]+[SERVICE BROKER]+[MEMORY]+[BUFFER]+[CLR]+[XEVENT]+[SQLOS]+[OTHER])
|
||||||
FROM
|
FROM
|
||||||
(
|
(
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
```toml
|
```toml
|
||||||
# Statsd Server
|
# Statsd Server
|
||||||
[[inputs.statsd]]
|
[[inputs.statsd]]
|
||||||
## Protocol, must be "tcp" or "udp" (default=udp)
|
## Protocol, must be "tcp", "udp4", "udp6" or "udp" (default=udp)
|
||||||
protocol = "udp"
|
protocol = "udp"
|
||||||
|
|
||||||
## MaxTCPConnection - applicable when protocol is set to tcp (default=250)
|
## MaxTCPConnection - applicable when protocol is set to tcp (default=250)
|
||||||
|
|
|
@ -66,7 +66,7 @@ type Statsd struct {
|
||||||
|
|
||||||
// MetricSeparator is the separator between parts of the metric name.
|
// MetricSeparator is the separator between parts of the metric name.
|
||||||
MetricSeparator string
|
MetricSeparator string
|
||||||
// This flag enables parsing of tags in the dogstatsd extention to the
|
// This flag enables parsing of tags in the dogstatsd extension to the
|
||||||
// statsd protocol (http://docs.datadoghq.com/guides/dogstatsd/)
|
// statsd protocol (http://docs.datadoghq.com/guides/dogstatsd/)
|
||||||
ParseDataDogTags bool
|
ParseDataDogTags bool
|
||||||
|
|
||||||
|
@ -171,7 +171,7 @@ func (_ *Statsd) Description() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
const sampleConfig = `
|
const sampleConfig = `
|
||||||
## Protocol, must be "tcp" or "udp" (default=udp)
|
## Protocol, must be "tcp", "udp", "udp4" or "udp6" (default=udp)
|
||||||
protocol = "udp"
|
protocol = "udp"
|
||||||
|
|
||||||
## MaxTCPConnection - applicable when protocol is set to tcp (default=250)
|
## MaxTCPConnection - applicable when protocol is set to tcp (default=250)
|
||||||
|
@ -327,10 +327,9 @@ func (s *Statsd) Start(_ telegraf.Accumulator) error {
|
||||||
|
|
||||||
s.wg.Add(2)
|
s.wg.Add(2)
|
||||||
// Start the UDP listener
|
// Start the UDP listener
|
||||||
switch s.Protocol {
|
if s.isUDP() {
|
||||||
case "udp":
|
|
||||||
go s.udpListen()
|
go s.udpListen()
|
||||||
case "tcp":
|
} else {
|
||||||
go s.tcpListen()
|
go s.tcpListen()
|
||||||
}
|
}
|
||||||
// Start the line parser
|
// Start the line parser
|
||||||
|
@ -382,8 +381,8 @@ func (s *Statsd) tcpListen() error {
|
||||||
func (s *Statsd) udpListen() error {
|
func (s *Statsd) udpListen() error {
|
||||||
defer s.wg.Done()
|
defer s.wg.Done()
|
||||||
var err error
|
var err error
|
||||||
address, _ := net.ResolveUDPAddr("udp", s.ServiceAddress)
|
address, _ := net.ResolveUDPAddr(s.Protocol, s.ServiceAddress)
|
||||||
s.UDPlistener, err = net.ListenUDP("udp", address)
|
s.UDPlistener, err = net.ListenUDP(s.Protocol, address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("ERROR: ListenUDP - %s", err)
|
log.Fatalf("ERROR: ListenUDP - %s", err)
|
||||||
}
|
}
|
||||||
|
@ -427,13 +426,13 @@ func (s *Statsd) parser() error {
|
||||||
return nil
|
return nil
|
||||||
case buf := <-s.in:
|
case buf := <-s.in:
|
||||||
lines := strings.Split(buf.String(), "\n")
|
lines := strings.Split(buf.String(), "\n")
|
||||||
|
s.bufPool.Put(buf)
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
line = strings.TrimSpace(line)
|
line = strings.TrimSpace(line)
|
||||||
if line != "" {
|
if line != "" {
|
||||||
s.parseStatsdLine(line)
|
s.parseStatsdLine(line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.bufPool.Put(buf)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -825,10 +824,9 @@ func (s *Statsd) Stop() {
|
||||||
s.Lock()
|
s.Lock()
|
||||||
log.Println("I! Stopping the statsd service")
|
log.Println("I! Stopping the statsd service")
|
||||||
close(s.done)
|
close(s.done)
|
||||||
switch s.Protocol {
|
if s.isUDP() {
|
||||||
case "udp":
|
|
||||||
s.UDPlistener.Close()
|
s.UDPlistener.Close()
|
||||||
case "tcp":
|
} else {
|
||||||
s.TCPlistener.Close()
|
s.TCPlistener.Close()
|
||||||
// Close all open TCP connections
|
// Close all open TCP connections
|
||||||
// - get all conns from the s.conns map and put into slice
|
// - get all conns from the s.conns map and put into slice
|
||||||
|
@ -843,8 +841,6 @@ func (s *Statsd) Stop() {
|
||||||
for _, conn := range conns {
|
for _, conn := range conns {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
s.UDPlistener.Close()
|
|
||||||
}
|
}
|
||||||
s.Unlock()
|
s.Unlock()
|
||||||
|
|
||||||
|
@ -856,6 +852,11 @@ func (s *Statsd) Stop() {
|
||||||
s.Unlock()
|
s.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsUDP returns true if the protocol is UDP, false otherwise.
|
||||||
|
func (s *Statsd) isUDP() bool {
|
||||||
|
return strings.HasPrefix(s.Protocol, "udp")
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
inputs.Add("statsd", func() telegraf.Input {
|
inputs.Add("statsd", func() telegraf.Input {
|
||||||
return &Statsd{
|
return &Statsd{
|
||||||
|
|
|
@ -100,7 +100,7 @@ var sampleConfig = `
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
## Options for the sadf command. The values on the left represent the sadf
|
## Options for the sadf command. The values on the left represent the sadf
|
||||||
## options and the values on the right their description (wich are used for
|
## options and the values on the right their description (which are used for
|
||||||
## grouping and prefixing metrics).
|
## grouping and prefixing metrics).
|
||||||
##
|
##
|
||||||
## Run 'sar -h' or 'man sar' to find out the supported options for your
|
## Run 'sar -h' or 'man sar' to find out the supported options for your
|
||||||
|
|
|
@ -140,7 +140,7 @@ SELECT derivative(last("io_time"),1ms) FROM "diskio" WHERE time > now() - 30m GR
|
||||||
#### Calculate average queue depth:
|
#### Calculate average queue depth:
|
||||||
`iops_in_progress` will give you an instantaneous value. This will give you the average between polling intervals.
|
`iops_in_progress` will give you an instantaneous value. This will give you the average between polling intervals.
|
||||||
```
|
```
|
||||||
SELECT derivative(last("weighted_io_time",1ms))/1000 from "diskio" WHERE time > now() - 30m GROUP BY "host","name",time(60s)
|
SELECT derivative(last("weighted_io_time",1ms)) from "diskio" WHERE time > now() - 30m GROUP BY "host","name",time(60s)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Example Output:
|
### Example Output:
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
|
|
||||||
type CPUStats struct {
|
type CPUStats struct {
|
||||||
ps PS
|
ps PS
|
||||||
lastStats []cpu.TimesStat
|
lastStats map[string]cpu.TimesStat
|
||||||
|
|
||||||
PerCPU bool `toml:"percpu"`
|
PerCPU bool `toml:"percpu"`
|
||||||
TotalCPU bool `toml:"totalcpu"`
|
TotalCPU bool `toml:"totalcpu"`
|
||||||
|
@ -53,7 +53,7 @@ func (s *CPUStats) Gather(acc telegraf.Accumulator) error {
|
||||||
}
|
}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
for i, cts := range times {
|
for _, cts := range times {
|
||||||
tags := map[string]string{
|
tags := map[string]string{
|
||||||
"cpu": cts.CPU,
|
"cpu": cts.CPU,
|
||||||
}
|
}
|
||||||
|
@ -86,14 +86,18 @@ func (s *CPUStats) Gather(acc telegraf.Accumulator) error {
|
||||||
// If it's the 1st gather, can't get CPU Usage stats yet
|
// If it's the 1st gather, can't get CPU Usage stats yet
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
lastCts := s.lastStats[i]
|
|
||||||
|
lastCts, ok := s.lastStats[cts.CPU]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
lastTotal := totalCpuTime(lastCts)
|
lastTotal := totalCpuTime(lastCts)
|
||||||
lastActive := activeCpuTime(lastCts)
|
lastActive := activeCpuTime(lastCts)
|
||||||
totalDelta := total - lastTotal
|
totalDelta := total - lastTotal
|
||||||
|
|
||||||
if totalDelta < 0 {
|
if totalDelta < 0 {
|
||||||
s.lastStats = times
|
err = fmt.Errorf("Error: current total CPU time is less than previous total CPU time")
|
||||||
return fmt.Errorf("Error: current total CPU time is less than previous total CPU time")
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if totalDelta == 0 {
|
if totalDelta == 0 {
|
||||||
|
@ -118,9 +122,12 @@ func (s *CPUStats) Gather(acc telegraf.Accumulator) error {
|
||||||
acc.AddGauge("cpu", fieldsG, tags, now)
|
acc.AddGauge("cpu", fieldsG, tags, now)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.lastStats = times
|
s.lastStats = make(map[string]cpu.TimesStat)
|
||||||
|
for _, cts := range times {
|
||||||
|
s.lastStats[cts.CPU] = cts
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func totalCpuTime(t cpu.TimesStat) float64 {
|
func totalCpuTime(t cpu.TimesStat) float64 {
|
||||||
|
|
|
@ -54,7 +54,7 @@ func TestCPUStats(t *testing.T) {
|
||||||
err := cs.Gather(&acc)
|
err := cs.Gather(&acc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Computed values are checked with delta > 0 becasue of floating point arithmatic
|
// Computed values are checked with delta > 0 because of floating point arithmatic
|
||||||
// imprecision
|
// imprecision
|
||||||
assertContainsTaggedFloat(t, &acc, "cpu", "time_user", 8.8, 0, cputags)
|
assertContainsTaggedFloat(t, &acc, "cpu", "time_user", 8.8, 0, cputags)
|
||||||
assertContainsTaggedFloat(t, &acc, "cpu", "time_system", 8.2, 0, cputags)
|
assertContainsTaggedFloat(t, &acc, "cpu", "time_system", 8.2, 0, cputags)
|
||||||
|
@ -105,7 +105,7 @@ func TestCPUStats(t *testing.T) {
|
||||||
// specific tags within a certain distance of a given expected value. Asserts a failure
|
// specific tags within a certain distance of a given expected value. Asserts a failure
|
||||||
// if the measurement is of the wrong type, or if no matching measurements are found
|
// if the measurement is of the wrong type, or if no matching measurements are found
|
||||||
//
|
//
|
||||||
// Paramaters:
|
// Parameters:
|
||||||
// t *testing.T : Testing object to use
|
// t *testing.T : Testing object to use
|
||||||
// acc testutil.Accumulator: Accumulator to examine
|
// acc testutil.Accumulator: Accumulator to examine
|
||||||
// measurement string : Name of the measurement to examine
|
// measurement string : Name of the measurement to examine
|
||||||
|
@ -149,3 +149,107 @@ func assertContainsTaggedFloat(
|
||||||
measurement, delta, expectedValue, actualValue)
|
measurement, delta, expectedValue, actualValue)
|
||||||
assert.Fail(t, msg)
|
assert.Fail(t, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCPUCountChange tests that no errors are encountered if the number of
|
||||||
|
// CPUs increases as reported with LXC.
|
||||||
|
func TestCPUCountIncrease(t *testing.T) {
|
||||||
|
var mps MockPS
|
||||||
|
var mps2 MockPS
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
var err error
|
||||||
|
|
||||||
|
cs := NewCPUStats(&mps)
|
||||||
|
|
||||||
|
mps.On("CPUTimes").Return(
|
||||||
|
[]cpu.TimesStat{
|
||||||
|
cpu.TimesStat{
|
||||||
|
CPU: "cpu0",
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
err = cs.Gather(&acc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
mps2.On("CPUTimes").Return(
|
||||||
|
[]cpu.TimesStat{
|
||||||
|
cpu.TimesStat{
|
||||||
|
CPU: "cpu0",
|
||||||
|
},
|
||||||
|
cpu.TimesStat{
|
||||||
|
CPU: "cpu1",
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
cs.ps = &mps2
|
||||||
|
|
||||||
|
err = cs.Gather(&acc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCPUTimesDecrease tests that telegraf continue to works after
|
||||||
|
// CPU times decrease, which seems to occur when Linux system is suspended.
|
||||||
|
func TestCPUTimesDecrease(t *testing.T) {
|
||||||
|
var mps MockPS
|
||||||
|
defer mps.AssertExpectations(t)
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
|
||||||
|
cts := cpu.TimesStat{
|
||||||
|
CPU: "cpu0",
|
||||||
|
User: 18,
|
||||||
|
Idle: 80,
|
||||||
|
Iowait: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
cts2 := cpu.TimesStat{
|
||||||
|
CPU: "cpu0",
|
||||||
|
User: 38, // increased by 20
|
||||||
|
Idle: 40, // decreased by 40
|
||||||
|
Iowait: 1, // decreased by 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cts3 := cpu.TimesStat{
|
||||||
|
CPU: "cpu0",
|
||||||
|
User: 56, // increased by 18
|
||||||
|
Idle: 120, // increased by 80
|
||||||
|
Iowait: 3, // increased by 2
|
||||||
|
}
|
||||||
|
|
||||||
|
mps.On("CPUTimes").Return([]cpu.TimesStat{cts}, nil)
|
||||||
|
|
||||||
|
cs := NewCPUStats(&mps)
|
||||||
|
|
||||||
|
cputags := map[string]string{
|
||||||
|
"cpu": "cpu0",
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cs.Gather(&acc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Computed values are checked with delta > 0 because of floating point arithmatic
|
||||||
|
// imprecision
|
||||||
|
assertContainsTaggedFloat(t, &acc, "cpu", "time_user", 18, 0, cputags)
|
||||||
|
assertContainsTaggedFloat(t, &acc, "cpu", "time_idle", 80, 0, cputags)
|
||||||
|
assertContainsTaggedFloat(t, &acc, "cpu", "time_iowait", 2, 0, cputags)
|
||||||
|
|
||||||
|
mps2 := MockPS{}
|
||||||
|
mps2.On("CPUTimes").Return([]cpu.TimesStat{cts2}, nil)
|
||||||
|
cs.ps = &mps2
|
||||||
|
|
||||||
|
// CPU times decreased. An error should be raised
|
||||||
|
err = cs.Gather(&acc)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
mps3 := MockPS{}
|
||||||
|
mps3.On("CPUTimes").Return([]cpu.TimesStat{cts3}, nil)
|
||||||
|
cs.ps = &mps3
|
||||||
|
|
||||||
|
err = cs.Gather(&acc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assertContainsTaggedFloat(t, &acc, "cpu", "time_user", 56, 0, cputags)
|
||||||
|
assertContainsTaggedFloat(t, &acc, "cpu", "time_idle", 120, 0, cputags)
|
||||||
|
assertContainsTaggedFloat(t, &acc, "cpu", "time_iowait", 3, 0, cputags)
|
||||||
|
|
||||||
|
assertContainsTaggedFloat(t, &acc, "cpu", "usage_user", 18, 0.0005, cputags)
|
||||||
|
assertContainsTaggedFloat(t, &acc, "cpu", "usage_idle", 80, 0.0005, cputags)
|
||||||
|
assertContainsTaggedFloat(t, &acc, "cpu", "usage_iowait", 2, 0.0005, cputags)
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -166,14 +167,13 @@ func (s *DiskIOStats) Gather(acc telegraf.Accumulator) error {
|
||||||
var varRegex = regexp.MustCompile(`\$(?:\w+|\{\w+\})`)
|
var varRegex = regexp.MustCompile(`\$(?:\w+|\{\w+\})`)
|
||||||
|
|
||||||
func (s *DiskIOStats) diskName(devName string) string {
|
func (s *DiskIOStats) diskName(devName string) string {
|
||||||
di, err := s.diskInfo(devName)
|
if len(s.NameTemplates) == 0 {
|
||||||
if err != nil {
|
|
||||||
// discard error :-(
|
|
||||||
// We can't return error because it's non-fatal to the Gather().
|
|
||||||
// And we have no logger, so we can't log it.
|
|
||||||
return devName
|
return devName
|
||||||
}
|
}
|
||||||
if di == nil {
|
|
||||||
|
di, err := s.diskInfo(devName)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("W! Error gathering disk info: %s", err)
|
||||||
return devName
|
return devName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,14 +200,13 @@ func (s *DiskIOStats) diskName(devName string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DiskIOStats) diskTags(devName string) map[string]string {
|
func (s *DiskIOStats) diskTags(devName string) map[string]string {
|
||||||
di, err := s.diskInfo(devName)
|
if len(s.DeviceTags) == 0 {
|
||||||
if err != nil {
|
|
||||||
// discard error :-(
|
|
||||||
// We can't return error because it's non-fatal to the Gather().
|
|
||||||
// And we have no logger, so we can't log it.
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if di == nil {
|
|
||||||
|
di, err := s.diskInfo(devName)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("W! Error gathering disk info: %s", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,25 +5,26 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
type diskInfoCache struct {
|
type diskInfoCache struct {
|
||||||
stat syscall.Stat_t
|
udevDataPath string
|
||||||
values map[string]string
|
values map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
var udevPath = "/run/udev/data"
|
var udevPath = "/run/udev/data"
|
||||||
|
|
||||||
func (s *DiskIOStats) diskInfo(devName string) (map[string]string, error) {
|
func (s *DiskIOStats) diskInfo(devName string) (map[string]string, error) {
|
||||||
fi, err := os.Stat("/dev/" + devName)
|
var err error
|
||||||
|
var stat unix.Stat_t
|
||||||
|
|
||||||
|
path := "/dev/" + devName
|
||||||
|
err = unix.Stat(path, &stat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
stat, ok := fi.Sys().(*syscall.Stat_t)
|
|
||||||
if !ok {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.infoCache == nil {
|
if s.infoCache == nil {
|
||||||
s.infoCache = map[string]diskInfoCache{}
|
s.infoCache = map[string]diskInfoCache{}
|
||||||
|
@ -31,25 +32,26 @@ func (s *DiskIOStats) diskInfo(devName string) (map[string]string, error) {
|
||||||
ic, ok := s.infoCache[devName]
|
ic, ok := s.infoCache[devName]
|
||||||
if ok {
|
if ok {
|
||||||
return ic.values, nil
|
return ic.values, nil
|
||||||
} else {
|
|
||||||
ic = diskInfoCache{
|
|
||||||
stat: *stat,
|
|
||||||
values: map[string]string{},
|
|
||||||
}
|
|
||||||
s.infoCache[devName] = ic
|
|
||||||
}
|
}
|
||||||
di := ic.values
|
|
||||||
|
|
||||||
major := stat.Rdev >> 8 & 0xff
|
major := stat.Rdev >> 8 & 0xff
|
||||||
minor := stat.Rdev & 0xff
|
minor := stat.Rdev & 0xff
|
||||||
|
udevDataPath := fmt.Sprintf("%s/b%d:%d", udevPath, major, minor)
|
||||||
|
|
||||||
f, err := os.Open(fmt.Sprintf("%s/b%d:%d", udevPath, major, minor))
|
di := map[string]string{}
|
||||||
|
|
||||||
|
s.infoCache[devName] = diskInfoCache{
|
||||||
|
udevDataPath: udevDataPath,
|
||||||
|
values: di,
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(udevDataPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
scnr := bufio.NewScanner(f)
|
|
||||||
|
|
||||||
|
scnr := bufio.NewScanner(f)
|
||||||
for scnr.Scan() {
|
for scnr.Scan() {
|
||||||
l := scnr.Text()
|
l := scnr.Text()
|
||||||
if len(l) < 4 || l[:2] != "E:" {
|
if len(l) < 4 || l[:2] != "E:" {
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Teamspeak 3 Input Plugin
|
||||||
|
|
||||||
|
This plugin uses the Teamspeak 3 ServerQuery interface of the Teamspeak server to collect statistics of one or more
|
||||||
|
virtual servers. If you are querying an external Teamspeak server, make sure to add the host which is running Telegraf
|
||||||
|
to query_ip_whitelist.txt in the Teamspeak Server directory. For information about how to configure the server take a look
|
||||||
|
the [Teamspeak 3 ServerQuery Manual](http://media.teamspeak.com/ts3_literature/TeamSpeak%203%20Server%20Query%20Manual.pdf)
|
||||||
|
|
||||||
|
### Configuration:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Reads metrics from a Teamspeak 3 Server via ServerQuery
|
||||||
|
[[inputs.teamspeak]]
|
||||||
|
## Server address for Teamspeak 3 ServerQuery
|
||||||
|
# server = "127.0.0.1:10011"
|
||||||
|
## Username for ServerQuery
|
||||||
|
username = "serverqueryuser"
|
||||||
|
## Password for ServerQuery
|
||||||
|
password = "secret"
|
||||||
|
## Array of virtual servers
|
||||||
|
# virtual_servers = [1]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Measurements:
|
||||||
|
|
||||||
|
- teamspeak
|
||||||
|
- uptime
|
||||||
|
- clients_online
|
||||||
|
- total_ping
|
||||||
|
- total_packet_loss
|
||||||
|
- packets_sent_total
|
||||||
|
- packets_received_total
|
||||||
|
- bytes_sent_total
|
||||||
|
- bytes_received_total
|
||||||
|
|
||||||
|
### Tags:
|
||||||
|
|
||||||
|
- The following tags are used:
|
||||||
|
- virtual_server
|
||||||
|
- name
|
||||||
|
|
||||||
|
### Example output:
|
||||||
|
|
||||||
|
```
|
||||||
|
teamspeak,virtual_server=1,name=LeopoldsServer,host=vm01 bytes_received_total=29638202639i,uptime=13567846i,total_ping=26.89,total_packet_loss=0,packets_sent_total=415821252i,packets_received_total=237069900i,bytes_sent_total=55309568252i,clients_online=11i 1507406561000000000
|
||||||
|
```
|
|
@ -0,0 +1,100 @@
|
||||||
|
package teamspeak
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/multiplay/go-ts3"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Teamspeak struct {
|
||||||
|
Server string
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
VirtualServers []int `toml:"virtual_servers"`
|
||||||
|
|
||||||
|
client *ts3.Client
|
||||||
|
connected bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *Teamspeak) Description() string {
|
||||||
|
return "Reads metrics from a Teamspeak 3 Server via ServerQuery"
|
||||||
|
}
|
||||||
|
|
||||||
|
const sampleConfig = `
|
||||||
|
## Server address for Teamspeak 3 ServerQuery
|
||||||
|
# server = "127.0.0.1:10011"
|
||||||
|
## Username for ServerQuery
|
||||||
|
username = "serverqueryuser"
|
||||||
|
## Password for ServerQuery
|
||||||
|
password = "secret"
|
||||||
|
## Array of virtual servers
|
||||||
|
# virtual_servers = [1]
|
||||||
|
`
|
||||||
|
|
||||||
|
func (ts *Teamspeak) SampleConfig() string {
|
||||||
|
return sampleConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *Teamspeak) Gather(acc telegraf.Accumulator) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if !ts.connected {
|
||||||
|
ts.client, err = ts3.NewClient(ts.Server)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ts.client.Login(ts.Username, ts.Password)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ts.connected = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, vserver := range ts.VirtualServers {
|
||||||
|
ts.client.Use(vserver)
|
||||||
|
|
||||||
|
sm, err := ts.client.Server.Info()
|
||||||
|
if err != nil {
|
||||||
|
ts.connected = false
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sc, err := ts.client.Server.ServerConnectionInfo()
|
||||||
|
if err != nil {
|
||||||
|
ts.connected = false
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tags := map[string]string{
|
||||||
|
"virtual_server": strconv.Itoa(sm.ID),
|
||||||
|
"name": sm.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"uptime": sm.Uptime,
|
||||||
|
"clients_online": sm.ClientsOnline,
|
||||||
|
"total_ping": sm.TotalPing,
|
||||||
|
"total_packet_loss": sm.TotalPacketLossTotal,
|
||||||
|
"packets_sent_total": sc.PacketsSentTotal,
|
||||||
|
"packets_received_total": sc.PacketsReceivedTotal,
|
||||||
|
"bytes_sent_total": sc.BytesSentTotal,
|
||||||
|
"bytes_received_total": sc.BytesReceivedTotal,
|
||||||
|
}
|
||||||
|
|
||||||
|
acc.AddFields("teamspeak", fields, tags)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
inputs.Add("teamspeak", func() telegraf.Input {
|
||||||
|
return &Teamspeak{
|
||||||
|
Server: "127.0.0.1:10011",
|
||||||
|
VirtualServers: []int{1},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
package teamspeak
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf/testutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
const welcome = `Welcome to the TeamSpeak 3 ServerQuery interface, type "help" for a list of commands and "help <command>" for information on a specific command.`
|
||||||
|
const ok = `error id=0 msg=ok`
|
||||||
|
const errorMsg = `error id=256 msg=command\snot\sfound`
|
||||||
|
|
||||||
|
var cmd = map[string]string{
|
||||||
|
"login": "",
|
||||||
|
"use": "",
|
||||||
|
"serverinfo": `virtualserver_unique_identifier=a1vn9PLF8CMIU virtualserver_name=Testserver virtualserver_welcomemessage=Test virtualserver_platform=Linux virtualserver_version=3.0.13.8\s[Build:\s1500452811] virtualserver_maxclients=32 virtualserver_password virtualserver_clientsonline=2 virtualserver_channelsonline=1 virtualserver_created=1507400243 virtualserver_uptime=148 virtualserver_codec_encryption_mode=0 virtualserver_hostmessage virtualserver_hostmessage_mode=0 virtualserver_filebase=files\/virtualserver_1 virtualserver_default_server_group=8 virtualserver_default_channel_group=8 virtualserver_flag_password=0 virtualserver_default_channel_admin_group=5 virtualserver_max_download_total_bandwidth=18446744073709551615 virtualserver_max_upload_total_bandwidth=18446744073709551615 virtualserver_hostbanner_url virtualserver_hostbanner_gfx_url virtualserver_hostbanner_gfx_interval=0 virtualserver_complain_autoban_count=5 virtualserver_complain_autoban_time=1200 virtualserver_complain_remove_time=3600 virtualserver_min_clients_in_channel_before_forced_silence=100 virtualserver_priority_speaker_dimm_modificator=-18.0000 virtualserver_id=1 virtualserver_antiflood_points_tick_reduce=5 virtualserver_antiflood_points_needed_command_block=150 virtualserver_antiflood_points_needed_ip_block=250 virtualserver_client_connections=1 virtualserver_query_client_connections=1 virtualserver_hostbutton_tooltip virtualserver_hostbutton_url virtualserver_hostbutton_gfx_url virtualserver_queryclientsonline=1 virtualserver_download_quota=18446744073709551615 virtualserver_upload_quota=18446744073709551615 virtualserver_month_bytes_downloaded=0 virtualserver_month_bytes_uploaded=0 virtualserver_total_bytes_downloaded=0 virtualserver_total_bytes_uploaded=0 virtualserver_port=9987 virtualserver_autostart=1 virtualserver_machine_id virtualserver_needed_identity_security_level=8 virtualserver_log_client=0 virtualserver_log_query=0 virtualserver_log_channel=0 virtualserver_log_permissions=1 virtualserver_log_server=0 virtualserver_log_filetransfer=0 virtualserver_min_client_version=1445512488 virtualserver_name_phonetic virtualserver_icon_id=0 virtualserver_reserved_slots=0 virtualserver_total_packetloss_speech=0.0000 virtualserver_total_packetloss_keepalive=0.0000 virtualserver_total_packetloss_control=0.0000 virtualserver_total_packetloss_total=0.0000 virtualserver_total_ping=1.0000 virtualserver_ip=0.0.0.0,\s:: virtualserver_weblist_enabled=1 virtualserver_ask_for_privilegekey=0 virtualserver_hostbanner_mode=0 virtualserver_channel_temp_delete_delay_default=0 virtualserver_min_android_version=1407159763 virtualserver_min_ios_version=1407159763 virtualserver_status=online connection_filetransfer_bandwidth_sent=0 connection_filetransfer_bandwidth_received=0 connection_filetransfer_bytes_sent_total=0 connection_filetransfer_bytes_received_total=0 connection_packets_sent_speech=0 connection_bytes_sent_speech=0 connection_packets_received_speech=0 connection_bytes_received_speech=0 connection_packets_sent_keepalive=261 connection_bytes_sent_keepalive=10701 connection_packets_received_keepalive=261 connection_bytes_received_keepalive=10961 connection_packets_sent_control=54 connection_bytes_sent_control=15143 connection_packets_received_control=55 connection_bytes_received_control=4239 connection_packets_sent_total=315 connection_bytes_sent_total=25844 connection_packets_received_total=316 connection_bytes_received_total=15200 connection_bandwidth_sent_last_second_total=81 connection_bandwidth_sent_last_minute_total=141 connection_bandwidth_received_last_second_total=83 connection_bandwidth_received_last_minute_total=98`,
|
||||||
|
"serverrequestconnectioninfo": `connection_filetransfer_bandwidth_sent=0 connection_filetransfer_bandwidth_received=0 connection_filetransfer_bytes_sent_total=0 connection_filetransfer_bytes_received_total=0 connection_packets_sent_total=369 connection_bytes_sent_total=28058 connection_packets_received_total=370 connection_bytes_received_total=17468 connection_bandwidth_sent_last_second_total=81 connection_bandwidth_sent_last_minute_total=109 connection_bandwidth_received_last_second_total=83 connection_bandwidth_received_last_minute_total=94 connection_connected_time=174 connection_packetloss_total=0.0000 connection_ping=1.0000`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGather(t *testing.T) {
|
||||||
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Initializing test server failed")
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
go handleRequest(l, t)
|
||||||
|
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
testConfig := Teamspeak{
|
||||||
|
Server: l.Addr().String(),
|
||||||
|
Username: "serveradmin",
|
||||||
|
Password: "test",
|
||||||
|
VirtualServers: []int{1},
|
||||||
|
}
|
||||||
|
err = testConfig.Gather(&acc)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Gather returned error. Error: %s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"uptime": int(148),
|
||||||
|
"clients_online": int(2),
|
||||||
|
"total_ping": float32(1.0000),
|
||||||
|
"total_packet_loss": float64(0.0000),
|
||||||
|
"packets_sent_total": uint64(369),
|
||||||
|
"packets_received_total": uint64(370),
|
||||||
|
"bytes_sent_total": uint64(28058),
|
||||||
|
"bytes_received_total": uint64(17468),
|
||||||
|
}
|
||||||
|
|
||||||
|
acc.AssertContainsFields(t, "teamspeak", fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleRequest(l net.Listener, t *testing.T) {
|
||||||
|
c, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Error accepting test connection")
|
||||||
|
}
|
||||||
|
c.Write([]byte("TS3\n\r" + welcome + "\n\r"))
|
||||||
|
for {
|
||||||
|
msg, _, err := bufio.NewReader(c).ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r, exists := cmd[strings.Split(string(msg), " ")[0]]
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
switch r {
|
||||||
|
case "":
|
||||||
|
c.Write([]byte(ok + "\n\r"))
|
||||||
|
case "quit":
|
||||||
|
c.Write([]byte(ok + "\n\r"))
|
||||||
|
c.Close()
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
c.Write([]byte(r + "\n\r" + ok + "\n\r"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.Write([]byte(errorMsg + "\n\r"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -165,7 +165,7 @@ func (s *Tomcat) Gather(acc telegraf.Accumulator) error {
|
||||||
for _, c := range status.TomcatConnectors {
|
for _, c := range status.TomcatConnectors {
|
||||||
name, err := strconv.Unquote(c.Name)
|
name, err := strconv.Unquote(c.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Unable to unquote name '%s': %s", c.Name, err)
|
name = c.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
tccTags := map[string]string{
|
tccTags := map[string]string{
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
var tomcatStatus = `<?xml version="1.0" encoding="UTF-8"?>
|
var tomcatStatus8 = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<?xml-stylesheet type="text/xsl" href="/manager/xform.xsl" ?>
|
<?xml-stylesheet type="text/xsl" href="/manager/xform.xsl" ?>
|
||||||
<status>
|
<status>
|
||||||
<jvm>
|
<jvm>
|
||||||
|
@ -37,10 +37,10 @@ var tomcatStatus = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
</connector>
|
</connector>
|
||||||
</status>`
|
</status>`
|
||||||
|
|
||||||
func TestHTTPTomcat(t *testing.T) {
|
func TestHTTPTomcat8(t *testing.T) {
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
fmt.Fprintln(w, tomcatStatus)
|
fmt.Fprintln(w, tomcatStatus8)
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
|
@ -91,5 +91,63 @@ func TestHTTPTomcat(t *testing.T) {
|
||||||
"name": "http-apr-8080",
|
"name": "http-apr-8080",
|
||||||
}
|
}
|
||||||
acc.AssertContainsTaggedFields(t, "tomcat_connector", connectorFields, connectorTags)
|
acc.AssertContainsTaggedFields(t, "tomcat_connector", connectorFields, connectorTags)
|
||||||
|
}
|
||||||
|
|
||||||
|
var tomcatStatus6 = `<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<?xml-stylesheet type="text/xsl" href="xform.xsl" ?>
|
||||||
|
<status>
|
||||||
|
<jvm>
|
||||||
|
<memory free="1942681600" total="2040070144" max="2040070144"/>
|
||||||
|
</jvm>
|
||||||
|
<connector name="http-8080">
|
||||||
|
<threadInfo maxThreads="150" currentThreadCount="2" currentThreadsBusy="2"/>
|
||||||
|
<requestInfo maxTime="1005" processingTime="2465" requestCount="436" errorCount="16" bytesReceived="0" bytesSent="550196"/>
|
||||||
|
<workers>
|
||||||
|
<worker stage="K" requestProcessingTime="526" requestBytesSent="0" requestBytesReceived="0" remoteAddr="127.0.0.1" virtualHost="?" method="?" currentUri="?" currentQueryString="?" protocol="?"/>
|
||||||
|
<worker stage="S" requestProcessingTime="1" requestBytesSent="0" requestBytesReceived="0" remoteAddr="127.0.0.1" virtualHost="127.0.0.1" method="GET" currentUri="/manager/status/all" currentQueryString="XML=true" protocol="HTTP/1.1"/>
|
||||||
|
</workers>
|
||||||
|
</connector>
|
||||||
|
</status>`
|
||||||
|
|
||||||
|
func TestHTTPTomcat6(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprintln(w, tomcatStatus6)
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
tc := Tomcat{
|
||||||
|
URL: ts.URL,
|
||||||
|
Username: "tomcat",
|
||||||
|
Password: "s3cret",
|
||||||
|
}
|
||||||
|
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
err := tc.Gather(&acc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// tomcat_jvm_memory
|
||||||
|
jvmMemoryFields := map[string]interface{}{
|
||||||
|
"free": int64(1942681600),
|
||||||
|
"total": int64(2040070144),
|
||||||
|
"max": int64(2040070144),
|
||||||
|
}
|
||||||
|
acc.AssertContainsFields(t, "tomcat_jvm_memory", jvmMemoryFields)
|
||||||
|
|
||||||
|
// tomcat_connector
|
||||||
|
connectorFields := map[string]interface{}{
|
||||||
|
"bytes_received": int64(0),
|
||||||
|
"bytes_sent": int64(550196),
|
||||||
|
"current_thread_count": int64(2),
|
||||||
|
"current_threads_busy": int64(2),
|
||||||
|
"error_count": int(16),
|
||||||
|
"max_threads": int64(150),
|
||||||
|
"max_time": int(1005),
|
||||||
|
"processing_time": int(2465),
|
||||||
|
"request_count": int(436),
|
||||||
|
}
|
||||||
|
connectorTags := map[string]string{
|
||||||
|
"name": "http-8080",
|
||||||
|
}
|
||||||
|
acc.AssertContainsTaggedFields(t, "tomcat_connector", connectorFields, connectorTags)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,10 @@ This plugin gathers stats from [Varnish HTTP Cache](https://varnish-cache.org/)
|
||||||
## Setting stats will override the defaults shown below.
|
## Setting stats will override the defaults shown below.
|
||||||
## stats may also be set to ["all"], which will collect all stats
|
## stats may also be set to ["all"], which will collect all stats
|
||||||
stats = ["MAIN.cache_hit", "MAIN.cache_miss", "MAIN.uptime"]
|
stats = ["MAIN.cache_hit", "MAIN.cache_miss", "MAIN.uptime"]
|
||||||
|
|
||||||
|
## Optional name for the varnish instance (or working directory) to query
|
||||||
|
## Usually appened after -n in varnish cli
|
||||||
|
#name = instanceName
|
||||||
```
|
```
|
||||||
|
|
||||||
### Measurements & Fields:
|
### Measurements & Fields:
|
||||||
|
|
|
@ -17,13 +17,14 @@ import (
|
||||||
"github.com/influxdata/telegraf/plugins/inputs"
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type runner func(cmdName string, UseSudo bool) (*bytes.Buffer, error)
|
type runner func(cmdName string, UseSudo bool, InstanceName string) (*bytes.Buffer, error)
|
||||||
|
|
||||||
// Varnish is used to store configuration values
|
// Varnish is used to store configuration values
|
||||||
type Varnish struct {
|
type Varnish struct {
|
||||||
Stats []string
|
Stats []string
|
||||||
Binary string
|
Binary string
|
||||||
UseSudo bool
|
UseSudo bool
|
||||||
|
InstanceName string
|
||||||
|
|
||||||
filter filter.Filter
|
filter filter.Filter
|
||||||
run runner
|
run runner
|
||||||
|
@ -44,6 +45,10 @@ var sampleConfig = `
|
||||||
## Glob matching can be used, ie, stats = ["MAIN.*"]
|
## Glob matching can be used, ie, stats = ["MAIN.*"]
|
||||||
## stats may also be set to ["*"], which will collect all stats
|
## stats may also be set to ["*"], which will collect all stats
|
||||||
stats = ["MAIN.cache_hit", "MAIN.cache_miss", "MAIN.uptime"]
|
stats = ["MAIN.cache_hit", "MAIN.cache_miss", "MAIN.uptime"]
|
||||||
|
|
||||||
|
## Optional name for the varnish instance (or working directory) to query
|
||||||
|
## Usually appened after -n in varnish cli
|
||||||
|
#name = instanceName
|
||||||
`
|
`
|
||||||
|
|
||||||
func (s *Varnish) Description() string {
|
func (s *Varnish) Description() string {
|
||||||
|
@ -56,8 +61,13 @@ func (s *Varnish) SampleConfig() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shell out to varnish_stat and return the output
|
// Shell out to varnish_stat and return the output
|
||||||
func varnishRunner(cmdName string, UseSudo bool) (*bytes.Buffer, error) {
|
func varnishRunner(cmdName string, UseSudo bool, InstanceName string) (*bytes.Buffer, error) {
|
||||||
cmdArgs := []string{"-1"}
|
cmdArgs := []string{"-1"}
|
||||||
|
|
||||||
|
if InstanceName != "" {
|
||||||
|
cmdArgs = append(cmdArgs, []string{"-n", InstanceName}...)
|
||||||
|
}
|
||||||
|
|
||||||
cmd := exec.Command(cmdName, cmdArgs...)
|
cmd := exec.Command(cmdName, cmdArgs...)
|
||||||
|
|
||||||
if UseSudo {
|
if UseSudo {
|
||||||
|
@ -99,7 +109,7 @@ func (s *Varnish) Gather(acc telegraf.Accumulator) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := s.run(s.Binary, s.UseSudo)
|
out, err := s.run(s.Binary, s.UseSudo, s.InstanceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error gathering metrics: %s", err)
|
return fmt.Errorf("error gathering metrics: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -155,10 +165,11 @@ func (s *Varnish) Gather(acc telegraf.Accumulator) error {
|
||||||
func init() {
|
func init() {
|
||||||
inputs.Add("varnish", func() telegraf.Input {
|
inputs.Add("varnish", func() telegraf.Input {
|
||||||
return &Varnish{
|
return &Varnish{
|
||||||
run: varnishRunner,
|
run: varnishRunner,
|
||||||
Stats: defaultStats,
|
Stats: defaultStats,
|
||||||
Binary: defaultBinary,
|
Binary: defaultBinary,
|
||||||
UseSudo: false,
|
UseSudo: false,
|
||||||
|
InstanceName: "",
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,14 +5,15 @@ package varnish
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/influxdata/telegraf/testutil"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf/testutil"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func fakeVarnishStat(output string, useSudo bool) func(string, bool) (*bytes.Buffer, error) {
|
func fakeVarnishStat(output string, useSudo bool, InstanceName string) func(string, bool, string) (*bytes.Buffer, error) {
|
||||||
return func(string, bool) (*bytes.Buffer, error) {
|
return func(string, bool, string) (*bytes.Buffer, error) {
|
||||||
return bytes.NewBuffer([]byte(output)), nil
|
return bytes.NewBuffer([]byte(output)), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +21,7 @@ func fakeVarnishStat(output string, useSudo bool) func(string, bool) (*bytes.Buf
|
||||||
func TestGather(t *testing.T) {
|
func TestGather(t *testing.T) {
|
||||||
acc := &testutil.Accumulator{}
|
acc := &testutil.Accumulator{}
|
||||||
v := &Varnish{
|
v := &Varnish{
|
||||||
run: fakeVarnishStat(smOutput, false),
|
run: fakeVarnishStat(smOutput, false, ""),
|
||||||
Stats: []string{"*"},
|
Stats: []string{"*"},
|
||||||
}
|
}
|
||||||
v.Gather(acc)
|
v.Gather(acc)
|
||||||
|
@ -36,7 +37,7 @@ func TestGather(t *testing.T) {
|
||||||
func TestParseFullOutput(t *testing.T) {
|
func TestParseFullOutput(t *testing.T) {
|
||||||
acc := &testutil.Accumulator{}
|
acc := &testutil.Accumulator{}
|
||||||
v := &Varnish{
|
v := &Varnish{
|
||||||
run: fakeVarnishStat(fullOutput, true),
|
run: fakeVarnishStat(fullOutput, true, ""),
|
||||||
Stats: []string{"*"},
|
Stats: []string{"*"},
|
||||||
}
|
}
|
||||||
err := v.Gather(acc)
|
err := v.Gather(acc)
|
||||||
|
@ -51,7 +52,7 @@ func TestParseFullOutput(t *testing.T) {
|
||||||
func TestFilterSomeStats(t *testing.T) {
|
func TestFilterSomeStats(t *testing.T) {
|
||||||
acc := &testutil.Accumulator{}
|
acc := &testutil.Accumulator{}
|
||||||
v := &Varnish{
|
v := &Varnish{
|
||||||
run: fakeVarnishStat(fullOutput, false),
|
run: fakeVarnishStat(fullOutput, false, ""),
|
||||||
Stats: []string{"MGT.*", "VBE.*"},
|
Stats: []string{"MGT.*", "VBE.*"},
|
||||||
}
|
}
|
||||||
err := v.Gather(acc)
|
err := v.Gather(acc)
|
||||||
|
@ -74,7 +75,7 @@ func TestFieldConfig(t *testing.T) {
|
||||||
for fieldCfg, expected := range expect {
|
for fieldCfg, expected := range expect {
|
||||||
acc := &testutil.Accumulator{}
|
acc := &testutil.Accumulator{}
|
||||||
v := &Varnish{
|
v := &Varnish{
|
||||||
run: fakeVarnishStat(fullOutput, true),
|
run: fakeVarnishStat(fullOutput, true, ""),
|
||||||
Stats: strings.Split(fieldCfg, ","),
|
Stats: strings.Split(fieldCfg, ","),
|
||||||
}
|
}
|
||||||
err := v.Gather(acc)
|
err := v.Gather(acc)
|
||||||
|
|
|
@ -1,6 +1,17 @@
|
||||||
# particle webhooks
|
# particle webhooks
|
||||||
|
|
||||||
You should configure your Rollbar's Webhooks to point at the `webhooks` service. To do this go to `https://console.particle.io/` and click `Settings > Notifications > Webhook`. In the resulting page set `URL` to `http://<my_ip>:1619/particle`, and click on `Enable Webhook Integration`.
|
|
||||||
|
You should configure your Particle.io's Webhooks to point at the `webhooks` service. To do this go to `(https://console.particle.io/)[https://console.particle.io]` and click `Integrations > New Integration > Webhook`. In the resulting page set `URL` to `http://<my_ip>:1619/particle`, and under `Advanced Settings` click on `JSON` and add:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"influx_db": "your_measurement_name"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If required, enter your username and password, etc. and then click `Save`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Events
|
## Events
|
||||||
|
|
||||||
|
@ -18,9 +29,11 @@ String data = String::format("{ \"tags\" : {
|
||||||
);
|
);
|
||||||
Particle.publish("event_name", data, PRIVATE);
|
Particle.publish("event_name", data, PRIVATE);
|
||||||
```
|
```
|
||||||
|
|
||||||
Escaping the "" is required in the source file.
|
Escaping the "" is required in the source file.
|
||||||
The number of tag values and field values is not restrictied so you can send as many values per webhook call as you'd like.
|
The number of tag values and field values is not restrictied so you can send as many values per webhook call as you'd like.
|
||||||
|
|
||||||
You will need to enable JSON messages in the Webhooks setup of Particle.io
|
You will need to enable JSON messages in the Webhooks setup of Particle.io
|
||||||
|
|
||||||
|
|
||||||
See [webhook doc](https://docs.particle.io/reference/webhooks/)
|
See [webhook doc](https://docs.particle.io/reference/webhooks/)
|
||||||
|
|
|
@ -347,7 +347,7 @@ func PdhGetFormattedCounterValueDouble(hCounter PDH_HCOUNTER, lpdwType *uint32,
|
||||||
//
|
//
|
||||||
// okPath := "\\Process(*)\\% Processor Time" // notice the wildcard * character
|
// okPath := "\\Process(*)\\% Processor Time" // notice the wildcard * character
|
||||||
//
|
//
|
||||||
// // ommitted all necessary stuff ...
|
// // omitted all necessary stuff ...
|
||||||
//
|
//
|
||||||
// var bufSize uint32
|
// var bufSize uint32
|
||||||
// var bufCount uint32
|
// var bufCount uint32
|
||||||
|
|
|
@ -110,7 +110,7 @@ func (m *Win_PerfCounters) AddItem(query string, objectName string, counter stri
|
||||||
ret = PdhAddEnglishCounter(handle, query, 0, &counterHandle)
|
ret = PdhAddEnglishCounter(handle, query, 0, &counterHandle)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call PdhCollectQueryData one time to check existance of the counter
|
// Call PdhCollectQueryData one time to check existence of the counter
|
||||||
ret = PdhCollectQueryData(handle)
|
ret = PdhCollectQueryData(handle)
|
||||||
if ret != ERROR_SUCCESS {
|
if ret != ERROR_SUCCESS {
|
||||||
PdhCloseQuery(handle)
|
PdhCloseQuery(handle)
|
||||||
|
|
|
@ -13,6 +13,8 @@ API endpoint. In the following order the plugin will attempt to authenticate.
|
||||||
5. [Shared Credentials](https://github.com/aws/aws-sdk-go/wiki/configuring-sdk#shared-credentials-file)
|
5. [Shared Credentials](https://github.com/aws/aws-sdk-go/wiki/configuring-sdk#shared-credentials-file)
|
||||||
6. [EC2 Instance Profile](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html)
|
6. [EC2 Instance Profile](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html)
|
||||||
|
|
||||||
|
The IAM user needs only the `cloudwatch:PutMetricData` permission.
|
||||||
|
|
||||||
## Config
|
## Config
|
||||||
|
|
||||||
For this output plugin to function correctly the following variables
|
For this output plugin to function correctly the following variables
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
||||||
|
"github.com/aws/aws-sdk-go/service/sts"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
internalaws "github.com/influxdata/telegraf/internal/config/aws"
|
internalaws "github.com/influxdata/telegraf/internal/config/aws"
|
||||||
|
@ -71,21 +72,20 @@ func (c *CloudWatch) Connect() error {
|
||||||
}
|
}
|
||||||
configProvider := credentialConfig.Credentials()
|
configProvider := credentialConfig.Credentials()
|
||||||
|
|
||||||
svc := cloudwatch.New(configProvider)
|
stsService := sts.New(configProvider)
|
||||||
|
|
||||||
params := &cloudwatch.ListMetricsInput{
|
params := &sts.GetSessionTokenInput{}
|
||||||
Namespace: aws.String(c.Namespace),
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := svc.ListMetrics(params) // Try a read-only call to test connection.
|
_, err := stsService.GetSessionToken(params)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("E! cloudwatch: Error in ListMetrics API call : %+v \n", err.Error())
|
log.Printf("E! cloudwatch: Cannot use credentials to connect to AWS : %+v \n", err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.svc = svc
|
c.svc = cloudwatch.New(configProvider)
|
||||||
|
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CloudWatch) Close() error {
|
func (c *CloudWatch) Close() error {
|
||||||
|
|
|
@ -174,6 +174,13 @@ This plugin will format the events in the following way:
|
||||||
# %H - hour (00..23)
|
# %H - hour (00..23)
|
||||||
index_name = "telegraf-%Y.%m.%d" # required.
|
index_name = "telegraf-%Y.%m.%d" # required.
|
||||||
|
|
||||||
|
## Optional SSL Config
|
||||||
|
# ssl_ca = "/etc/telegraf/ca.pem"
|
||||||
|
# ssl_cert = "/etc/telegraf/cert.pem"
|
||||||
|
# ssl_key = "/etc/telegraf/key.pem"
|
||||||
|
## Use SSL but skip chain & host verification
|
||||||
|
# insecure_skip_verify = false
|
||||||
|
|
||||||
## Template Config
|
## Template Config
|
||||||
## Set to true if you want telegraf to manage its index template.
|
## Set to true if you want telegraf to manage its index template.
|
||||||
## If enabled it will create a recommended index template for telegraf indexes
|
## If enabled it will create a recommended index template for telegraf indexes
|
||||||
|
|
|
@ -3,15 +3,15 @@ package elasticsearch
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
"github.com/influxdata/telegraf/internal"
|
"github.com/influxdata/telegraf/internal"
|
||||||
"github.com/influxdata/telegraf/plugins/outputs"
|
"github.com/influxdata/telegraf/plugins/outputs"
|
||||||
"gopkg.in/olivere/elastic.v5"
|
"gopkg.in/olivere/elastic.v5"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Elasticsearch struct {
|
type Elasticsearch struct {
|
||||||
|
@ -25,6 +25,10 @@ type Elasticsearch struct {
|
||||||
ManageTemplate bool
|
ManageTemplate bool
|
||||||
TemplateName string
|
TemplateName string
|
||||||
OverwriteTemplate bool
|
OverwriteTemplate bool
|
||||||
|
SSLCA string `toml:"ssl_ca"` // Path to CA file
|
||||||
|
SSLCert string `toml:"ssl_cert"` // Path to host cert file
|
||||||
|
SSLKey string `toml:"ssl_key"` // Path to cert key file
|
||||||
|
InsecureSkipVerify bool // Use SSL but skip chain & host verification
|
||||||
Client *elastic.Client
|
Client *elastic.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +60,13 @@ var sampleConfig = `
|
||||||
# %H - hour (00..23)
|
# %H - hour (00..23)
|
||||||
index_name = "telegraf-%Y.%m.%d" # required.
|
index_name = "telegraf-%Y.%m.%d" # required.
|
||||||
|
|
||||||
|
## Optional SSL Config
|
||||||
|
# ssl_ca = "/etc/telegraf/ca.pem"
|
||||||
|
# ssl_cert = "/etc/telegraf/cert.pem"
|
||||||
|
# ssl_key = "/etc/telegraf/key.pem"
|
||||||
|
## Use SSL but skip chain & host verification
|
||||||
|
# insecure_skip_verify = false
|
||||||
|
|
||||||
## Template Config
|
## Template Config
|
||||||
## Set to true if you want telegraf to manage its index template.
|
## Set to true if you want telegraf to manage its index template.
|
||||||
## If enabled it will create a recommended index template for telegraf indexes
|
## If enabled it will create a recommended index template for telegraf indexes
|
||||||
|
@ -76,7 +87,21 @@ func (a *Elasticsearch) Connect() error {
|
||||||
|
|
||||||
var clientOptions []elastic.ClientOptionFunc
|
var clientOptions []elastic.ClientOptionFunc
|
||||||
|
|
||||||
|
tlsCfg, err := internal.GetTLSConfig(a.SSLCert, a.SSLKey, a.SSLCA, a.InsecureSkipVerify)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: tlsCfg,
|
||||||
|
}
|
||||||
|
|
||||||
|
httpclient := &http.Client{
|
||||||
|
Transport: tr,
|
||||||
|
Timeout: a.Timeout.Duration,
|
||||||
|
}
|
||||||
|
|
||||||
clientOptions = append(clientOptions,
|
clientOptions = append(clientOptions,
|
||||||
|
elastic.SetHttpClient(httpclient),
|
||||||
elastic.SetSniff(a.EnableSniffer),
|
elastic.SetSniff(a.EnableSniffer),
|
||||||
elastic.SetURL(a.URLs...),
|
elastic.SetURL(a.URLs...),
|
||||||
elastic.SetHealthcheckInterval(a.HealthCheckInterval.Duration),
|
elastic.SetHealthcheckInterval(a.HealthCheckInterval.Duration),
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue