Compare commits

..

1 Commits

Author SHA1 Message Date
Cameron Sparr
37ae3956c1 Space Igloo 2016-05-04 11:16:18 -06:00
166 changed files with 2605 additions and 10772 deletions

4
.gitattributes vendored
View File

@@ -1,4 +1,2 @@
CHANGELOG.md merge=union CHANGELOG.md merge=union
README.md merge=union
plugins/inputs/all/all.go merge=union
plugins/outputs/all/all.go merge=union

View File

@@ -1,44 +0,0 @@
## Directions
GitHub Issues are reserved for actionable bug reports and feature requests.
General questions should be sent to the [InfluxDB mailing list](https://groups.google.com/forum/#!forum/influxdb).
Before opening an issue, search for similar bug reports or feature requests on GitHub Issues.
If no similar issue can be found, fill out either the "Bug Report" or the "Feature Request" section below.
Erase the other section and everything on and above this line.
*Please note, the quickest way to fix a bug is to open a Pull Request.*
## Bug report
### Relevant telegraf.conf:
### System info:
[Include Telegraf version, operating system name, and other relevant details]
### Steps to reproduce:
1. ...
2. ...
### Expected behavior:
### Actual behavior:
### Additional info:
[Include gist of relevant config, logs, etc.]
## Feature Request
Opening a feature request kicks off a discussion.
### Proposal:
### Current behavior:
### Desired behavior:
### Use case: [Why is this important (helps with prioritizing requests)]

View File

@@ -1,5 +0,0 @@
### Required for all PRs:
- [ ] CHANGELOG.md updated
- [ ] Sign [CLA](https://influxdata.com/community/cla/) (if not already signed)
- [ ] README.md updated (if adding a new plugin)

View File

@@ -1,115 +1,4 @@
## v1.0 ## v0.13 [unreleased]
### Features
### Bugfixes
## v1.0 beta 2 [2016-06-21]
### Features
- [#1340](https://github.com/influxdata/telegraf/issues/1340): statsd: do not log every dropped metric.
- [#1368](https://github.com/influxdata/telegraf/pull/1368): Add precision rounding to all metrics on collection.
- [#1390](https://github.com/influxdata/telegraf/pull/1390): Add support for Tengine
- [#1320](https://github.com/influxdata/telegraf/pull/1320): Logparser input plugin for parsing grok-style log patterns.
### Bugfixes
- [#1330](https://github.com/influxdata/telegraf/issues/1330): Fix exec plugin panic when using single binary.
- [#1336](https://github.com/influxdata/telegraf/issues/1336): Fixed incorrect prometheus metrics source selection.
- [#1112](https://github.com/influxdata/telegraf/issues/1112): Set default Zookeeper chroot to empty string.
- [#1335](https://github.com/influxdata/telegraf/issues/1335): Fix overall ping timeout to be calculated based on per-ping timeout.
- [#1374](https://github.com/influxdata/telegraf/pull/1374): Change "default" retention policy to "".
- [#1377](https://github.com/influxdata/telegraf/issues/1377): Graphite output mangling '%' character.
## v1.0 beta 1 [2016-06-07]
### Release Notes
- `flush_jitter` behavior has been changed. The random jitter will now be
evaluated at every flush interval, rather than once at startup. This makes it
consistent with the behavior of `collection_jitter`.
- All AWS plugins now utilize a standard mechanism for evaluating credentials.
This allows all AWS plugins to support environment variables, shared credential
files & profiles, and role assumptions. See the specific plugin README for
details.
- The AWS CloudWatch input plugin can now declare a wildcard value for a metric
dimension. This causes the plugin to read all metrics that contain the specified
dimension key regardless of value. This is used to export collections of metrics
without having to know the dimension values ahead of time.
- The AWS CloudWatch input plugin can now be configured with the `cache_ttl`
attribute. This configures the TTL of the internal metric cache. This is useful
in conjunction with wildcard dimension values as it will control the amount of
time before a new metric is included by the plugin.
### Features
- [#1262](https://github.com/influxdata/telegraf/pull/1261): Add graylog input pluging.
- [#1294](https://github.com/influxdata/telegraf/pull/1294): consul input plugin. Thanks @harnash
- [#1164](https://github.com/influxdata/telegraf/pull/1164): conntrack input plugin. Thanks @robinpercy!
- [#1165](https://github.com/influxdata/telegraf/pull/1165): vmstat input plugin. Thanks @jshim-xm!
- [#1247](https://github.com/influxdata/telegraf/pull/1247): rollbar input plugin. Thanks @francois2metz and @cduez!
- [#1208](https://github.com/influxdata/telegraf/pull/1208): Standardized AWS credentials evaluation & wildcard CloudWatch dimensions. Thanks @johnrengelman!
- [#1264](https://github.com/influxdata/telegraf/pull/1264): Add SSL config options to http_response plugin.
- [#1272](https://github.com/influxdata/telegraf/pull/1272): graphite parser: add ability to specify multiple tag keys, for consistency with influxdb parser.
- [#1265](https://github.com/influxdata/telegraf/pull/1265): Make dns lookups for chrony configurable. Thanks @zbindenren!
- [#1275](https://github.com/influxdata/telegraf/pull/1275): Allow wildcard filtering of varnish stats.
- [#1142](https://github.com/influxdata/telegraf/pull/1142): Support for glob patterns in exec plugin commands configuration.
- [#1278](https://github.com/influxdata/telegraf/pull/1278): RabbitMQ input: made url parameter optional by using DefaultURL (http://localhost:15672) if not specified
- [#1197](https://github.com/influxdata/telegraf/pull/1197): Limit AWS GetMetricStatistics requests to 10 per second.
- [#1278](https://github.com/influxdata/telegraf/pull/1278) & [#1288](https://github.com/influxdata/telegraf/pull/1288) & [#1295](https://github.com/influxdata/telegraf/pull/1295): RabbitMQ/Apache/InfluxDB inputs: made url(s) parameter optional by using reasonable input defaults if not specified
- [#1296](https://github.com/influxdata/telegraf/issues/1296): Refactor of flush_jitter argument.
- [#1213](https://github.com/influxdata/telegraf/issues/1213): Add inactive & active memory to mem plugin.
### Bugfixes
- [#1252](https://github.com/influxdata/telegraf/pull/1252) & [#1279](https://github.com/influxdata/telegraf/pull/1279): Fix systemd service. Thanks @zbindenren & @PierreF!
- [#1221](https://github.com/influxdata/telegraf/pull/1221): Fix influxdb n_shards counter.
- [#1258](https://github.com/influxdata/telegraf/pull/1258): Fix potential kernel plugin integer parse error.
- [#1268](https://github.com/influxdata/telegraf/pull/1268): Fix potential influxdb input type assertion panic.
- [#1283](https://github.com/influxdata/telegraf/pull/1283): Still send processes metrics if a process exited during metric collection.
- [#1297](https://github.com/influxdata/telegraf/issues/1297): disk plugin panic when usage grab fails.
- [#1316](https://github.com/influxdata/telegraf/pull/1316): Removed leaked "database" tag on redis metrics. Thanks @PierreF!
- [#1323](https://github.com/influxdata/telegraf/issues/1323): Processes plugin: fix potential error with /proc/net/stat directory.
- [#1322](https://github.com/influxdata/telegraf/issues/1322): Fix rare RHEL 5.2 panic in gopsutil diskio gathering function.
## v0.13.1 [2016-05-24]
### Release Notes
- net_response and http_response plugins timeouts will now accept duration
strings, ie, "2s" or "500ms".
- Input plugin Gathers will no longer be logged by default, but a Gather for
_each_ plugin will be logged in Debug mode.
- Debug mode will no longer print every point added to the accumulator. This
functionality can be duplicated using the `file` output plugin and printing
to "stdout".
### Features
- [#1173](https://github.com/influxdata/telegraf/pull/1173): varnish input plugin. Thanks @sfox-xmatters!
- [#1138](https://github.com/influxdata/telegraf/pull/1138): nstat input plugin. Thanks @Maksadbek!
- [#1139](https://github.com/influxdata/telegraf/pull/1139): instrumental output plugin. Thanks @jasonroelofs!
- [#1172](https://github.com/influxdata/telegraf/pull/1172): Ceph storage stats. Thanks @robinpercy!
- [#1233](https://github.com/influxdata/telegraf/pull/1233): Updated golint gopsutil dependency.
- [#1238](https://github.com/influxdata/telegraf/pull/1238): chrony input plugin. Thanks @zbindenren!
- [#479](https://github.com/influxdata/telegraf/issues/479): per-plugin execution time added to debug output.
- [#1249](https://github.com/influxdata/telegraf/issues/1249): influxdb output: added write_consistency argument.
### Bugfixes
- [#1195](https://github.com/influxdata/telegraf/pull/1195): Docker panic on timeout. Thanks @zstyblik!
- [#1211](https://github.com/influxdata/telegraf/pull/1211): mongodb input. Fix possible panic. Thanks @kols!
- [#1215](https://github.com/influxdata/telegraf/pull/1215): Fix for possible gopsutil-dependent plugin hangs.
- [#1228](https://github.com/influxdata/telegraf/pull/1228): Fix service plugin host tag overwrite.
- [#1198](https://github.com/influxdata/telegraf/pull/1198): http_response: override request Host header properly
- [#1230](https://github.com/influxdata/telegraf/issues/1230): Fix Telegraf process hangup due to a single plugin hanging.
- [#1214](https://github.com/influxdata/telegraf/issues/1214): Use TCP timeout argument in net_response plugin.
- [#1243](https://github.com/influxdata/telegraf/pull/1243): Logfile not created on systemd.
## v0.13 [2016-05-11]
### Release Notes ### Release Notes
@@ -159,15 +48,7 @@ based on _prefix_ in addition to globs. This means that a filter like
- disque: `host -> disque_host` - disque: `host -> disque_host`
- rethinkdb: `host -> rethinkdb_host` - rethinkdb: `host -> rethinkdb_host`
- **Breaking Change**: The `win_perf_counters` input has been changed to - **Breaking Change**: The `win_perf_counters` input has been changed to sanitize field names, replacing `/Sec` and `/sec` with `_persec`, as well as spaces with underscores. This is needed because Graphite doesn't like slashes and spaces, and was failing to accept metrics that had them. The `/[sS]ec` -> `_persec` is just to make things clearer and uniform.
sanitize field names, replacing `/Sec` and `/sec` with `_persec`, as well as
spaces with underscores. This is needed because Graphite doesn't like slashes
and spaces, and was failing to accept metrics that had them.
The `/[sS]ec` -> `_persec` is just to make things clearer and uniform.
- **Breaking Change**: snmp plugin. The `host` tag of the snmp plugin has been
changed to the `snmp_host` tag.
- The `disk` input plugin can now be configured with the `HOST_MOUNT_PREFIX` environment variable. - The `disk` input plugin can now be configured with the `HOST_MOUNT_PREFIX` environment variable.
This value is prepended to any mountpaths discovered before retrieving stats. This value is prepended to any mountpaths discovered before retrieving stats.
It is not included on the report path. This is necessary for reporting host disk stats when running from within a container. It is not included on the report path. This is necessary for reporting host disk stats when running from within a container.

View File

@@ -212,8 +212,8 @@ func (s *Simple) Close() error {
} }
func (s *Simple) Write(metrics []telegraf.Metric) error { func (s *Simple) Write(metrics []telegraf.Metric) error {
for _, metric := range metrics { for _, pt := range points {
// write `metric` to the output sink here // write `pt` to the output sink here
} }
return nil return nil
} }

8
Godeps
View File

@@ -16,17 +16,16 @@ github.com/eapache/go-resiliency b86b1ec0dd4209a588dc1285cdd471e73525c0b3
github.com/eapache/queue ded5959c0d4e360646dc9e9908cff48666781367 github.com/eapache/queue ded5959c0d4e360646dc9e9908cff48666781367
github.com/eclipse/paho.mqtt.golang 0f7a459f04f13a41b7ed752d47944528d4bf9a86 github.com/eclipse/paho.mqtt.golang 0f7a459f04f13a41b7ed752d47944528d4bf9a86
github.com/go-sql-driver/mysql 1fca743146605a172a266e1654e01e5cd5669bee github.com/go-sql-driver/mysql 1fca743146605a172a266e1654e01e5cd5669bee
github.com/gobwas/glob 49571a1557cd20e6a2410adc6421f85b66c730b5 github.com/gobwas/glob d877f6352135181470c40c73ebb81aefa22115fa
github.com/golang/protobuf 552c7b9542c194800fd493123b3798ef0a832032 github.com/golang/protobuf 552c7b9542c194800fd493123b3798ef0a832032
github.com/golang/snappy 427fb6fc07997f43afa32f35e850833760e489a7 github.com/golang/snappy 427fb6fc07997f43afa32f35e850833760e489a7
github.com/gonuts/go-shellquote e842a11b24c6abfb3dd27af69a17f482e4b483c2 github.com/gonuts/go-shellquote e842a11b24c6abfb3dd27af69a17f482e4b483c2
github.com/gorilla/context 1ea25387ff6f684839d82767c1733ff4d4d15d0a github.com/gorilla/context 1ea25387ff6f684839d82767c1733ff4d4d15d0a
github.com/gorilla/mux c9e326e2bdec29039a3761c07bece13133863e1e github.com/gorilla/mux c9e326e2bdec29039a3761c07bece13133863e1e
github.com/hailocab/go-hostpool e80d13ce29ede4452c43dea11e79b9bc8a15b478 github.com/hailocab/go-hostpool e80d13ce29ede4452c43dea11e79b9bc8a15b478
github.com/hashicorp/consul 5aa90455ce78d4d41578bafc86305e6e6b28d7d2
github.com/hpcloud/tail b2940955ab8b26e19d43a43c4da0475dd81bdb56 github.com/hpcloud/tail b2940955ab8b26e19d43a43c4da0475dd81bdb56
github.com/influxdata/config b79f6829346b8d6e78ba73544b1e1038f1f1c9da github.com/influxdata/config b79f6829346b8d6e78ba73544b1e1038f1f1c9da
github.com/influxdata/influxdb e094138084855d444195b252314dfee9eae34cab github.com/influxdata/influxdb 21db76b3374c733f37ed16ad93f3484020034351
github.com/influxdata/toml af4df43894b16e3fd2b788d01bd27ad0776ef2d0 github.com/influxdata/toml af4df43894b16e3fd2b788d01bd27ad0776ef2d0
github.com/klauspost/crc32 19b0b332c9e4516a6370a0456e6182c3b5036720 github.com/klauspost/crc32 19b0b332c9e4516a6370a0456e6182c3b5036720
github.com/lib/pq e182dc4027e2ded4b19396d638610f2653295f36 github.com/lib/pq e182dc4027e2ded4b19396d638610f2653295f36
@@ -43,11 +42,10 @@ github.com/prometheus/client_model fa8ad6fec33561be4280a8f0514318c79d7f6cb6
github.com/prometheus/common e8eabff8812b05acf522b45fdcd725a785188e37 github.com/prometheus/common e8eabff8812b05acf522b45fdcd725a785188e37
github.com/prometheus/procfs 406e5b7bfd8201a36e2bb5f7bdae0b03380c2ce8 github.com/prometheus/procfs 406e5b7bfd8201a36e2bb5f7bdae0b03380c2ce8
github.com/samuel/go-zookeeper 218e9c81c0dd8b3b18172b2bbfad92cc7d6db55f github.com/samuel/go-zookeeper 218e9c81c0dd8b3b18172b2bbfad92cc7d6db55f
github.com/shirou/gopsutil 586bb697f3ec9f8ec08ffefe18f521a64534037c github.com/shirou/gopsutil 1f32ce1bb380845be7f5d174ac641a2c592c0c42
github.com/soniah/gosnmp b1b4f885b12c5dcbd021c5cee1c904110de6db7d github.com/soniah/gosnmp b1b4f885b12c5dcbd021c5cee1c904110de6db7d
github.com/streadway/amqp b4f3ceab0337f013208d31348b578d83c0064744 github.com/streadway/amqp b4f3ceab0337f013208d31348b578d83c0064744
github.com/stretchr/testify 1f4a1643a57e798696635ea4c126e9127adb7d3c github.com/stretchr/testify 1f4a1643a57e798696635ea4c126e9127adb7d3c
github.com/vjeantet/grok 83bfdfdfd1a8146795b28e547a8e3c8b28a466c2
github.com/wvanbergen/kafka 46f9a1cf3f670edec492029fadded9c2d9e18866 github.com/wvanbergen/kafka 46f9a1cf3f670edec492029fadded9c2d9e18866
github.com/wvanbergen/kazoo-go 0f768712ae6f76454f987c3356177e138df258f8 github.com/wvanbergen/kazoo-go 0f768712ae6f76454f987c3356177e138df258f8
github.com/zensqlmonitor/go-mssqldb ffe5510c6fa5e15e6d983210ab501c815b56b363 github.com/zensqlmonitor/go-mssqldb ffe5510c6fa5e15e6d983210ab501c815b56b363

View File

@@ -14,21 +14,21 @@ windows: prepare-windows build-windows
# Only run the build (no dependency grabbing) # Only run the build (no dependency grabbing)
build: build:
go install -ldflags "-X main.version=$(VERSION)" ./... go install -ldflags "-X main.Version=$(VERSION)" ./...
build-windows: build-windows:
go build -o telegraf.exe -ldflags \ go build -o telegraf.exe -ldflags \
"-X main.version=$(VERSION)" \ "-X main.Version=$(VERSION)" \
./cmd/telegraf/telegraf.go ./cmd/telegraf/telegraf.go
build-for-docker: build-for-docker:
CGO_ENABLED=0 GOOS=linux go build -installsuffix cgo -o telegraf -ldflags \ CGO_ENABLED=0 GOOS=linux go build -installsuffix cgo -o telegraf -ldflags \
"-s -X main.version=$(VERSION)" \ "-s -X main.Version=$(VERSION)" \
./cmd/telegraf/telegraf.go ./cmd/telegraf/telegraf.go
# Build with race detector # Build with race detector
dev: prepare dev: prepare
go build -race -ldflags "-X main.version=$(VERSION)" ./... go build -race -ldflags "-X main.Version=$(VERSION)" ./...
# run package script # run package script
package: package:
@@ -64,6 +64,7 @@ endif
docker run --name memcached -p "11211:11211" -d memcached docker run --name memcached -p "11211:11211" -d memcached
docker run --name postgres -p "5432:5432" -d postgres docker run --name postgres -p "5432:5432" -d postgres
docker run --name rabbitmq -p "15672:15672" -p "5672:5672" -d rabbitmq:3-management docker run --name rabbitmq -p "15672:15672" -p "5672:5672" -d rabbitmq:3-management
docker run --name opentsdb -p "4242:4242" -d petergrace/opentsdb-docker
docker run --name redis -p "6379:6379" -d redis docker run --name redis -p "6379:6379" -d redis
docker run --name aerospike -p "3000:3000" -d aerospike docker run --name aerospike -p "3000:3000" -d aerospike
docker run --name nsq -p "4150:4150" -d nsqio/nsq /nsqd docker run --name nsq -p "4150:4150" -d nsqio/nsq /nsqd
@@ -78,6 +79,7 @@ docker-run-circle:
-e ADVERTISED_PORT=9092 \ -e ADVERTISED_PORT=9092 \
-p "2181:2181" -p "9092:9092" \ -p "2181:2181" -p "9092:9092" \
-d spotify/kafka -d spotify/kafka
docker run --name opentsdb -p "4242:4242" -d petergrace/opentsdb-docker
docker run --name aerospike -p "3000:3000" -d aerospike docker run --name aerospike -p "3000:3000" -d aerospike
docker run --name nsq -p "4150:4150" -d nsqio/nsq /nsqd docker run --name nsq -p "4150:4150" -d nsqio/nsq /nsqd
docker run --name mqtt -p "1883:1883" -d ncarlier/mqtt docker run --name mqtt -p "1883:1883" -d ncarlier/mqtt
@@ -86,8 +88,8 @@ docker-run-circle:
# Kill all docker containers, ignore errors # Kill all docker containers, ignore errors
docker-kill: docker-kill:
-docker kill nsq aerospike redis rabbitmq postgres memcached mysql kafka mqtt riemann snmp -docker kill nsq aerospike redis opentsdb rabbitmq postgres memcached mysql kafka mqtt riemann snmp
-docker rm nsq aerospike redis rabbitmq postgres memcached mysql kafka mqtt riemann snmp -docker rm nsq aerospike redis opentsdb rabbitmq postgres memcached mysql kafka mqtt riemann snmp
# Run full unit tests using docker containers (includes setup and teardown) # Run full unit tests using docker containers (includes setup and teardown)
test: vet docker-kill docker-run test: vet docker-kill docker-run

View File

@@ -1,4 +1,4 @@
# Telegraf [![Circle CI](https://circleci.com/gh/influxdata/telegraf.svg?style=svg)](https://circleci.com/gh/influxdata/telegraf) [![Docker pulls](https://img.shields.io/docker/pulls/library/telegraf.svg)](https://hub.docker.com/_/telegraf/) # Telegraf [![Circle CI](https://circleci.com/gh/influxdata/telegraf.svg?style=svg)](https://circleci.com/gh/influxdata/telegraf)
Telegraf is an agent written in Go for collecting metrics from the system it's Telegraf is an agent written in Go for collecting metrics from the system it's
running on, or from other services, and writing them into InfluxDB or other running on, or from other services, and writing them into InfluxDB or other
@@ -20,12 +20,12 @@ new plugins.
### Linux deb and rpm Packages: ### Linux deb and rpm Packages:
Latest: Latest:
* https://dl.influxdata.com/telegraf/releases/telegraf_1.0.0-beta2_amd64.deb * http://get.influxdb.org/telegraf/telegraf_0.12.1-1_amd64.deb
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0_beta2.x86_64.rpm * http://get.influxdb.org/telegraf/telegraf-0.12.1-1.x86_64.rpm
Latest (arm): Latest (arm):
* https://dl.influxdata.com/telegraf/releases/telegraf_1.0.0-beta2_armhf.deb * http://get.influxdb.org/telegraf/telegraf_0.12.1-1_armhf.deb
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0_beta2.armhf.rpm * http://get.influxdb.org/telegraf/telegraf-0.12.1-1.armhf.rpm
##### Package Instructions: ##### Package Instructions:
@@ -46,14 +46,32 @@ to use this repo to install & update telegraf.
### Linux tarballs: ### Linux tarballs:
Latest: Latest:
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0-beta2_linux_amd64.tar.gz * http://get.influxdb.org/telegraf/telegraf-0.12.1-1_linux_amd64.tar.gz
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0-beta2_linux_i386.tar.gz * http://get.influxdb.org/telegraf/telegraf-0.12.1-1_linux_i386.tar.gz
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0-beta2_linux_armhf.tar.gz * http://get.influxdb.org/telegraf/telegraf-0.12.1-1_linux_armhf.tar.gz
##### tarball Instructions:
To install the full directory structure with config file, run:
```
sudo tar -C / -zxvf ./telegraf-0.12.1-1_linux_amd64.tar.gz
```
To extract only the binary, run:
```
tar -zxvf telegraf-0.12.1-1_linux_amd64.tar.gz --strip-components=3 ./usr/bin/telegraf
```
### FreeBSD tarball: ### FreeBSD tarball:
Latest: Latest:
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0-beta2_freebsd_amd64.tar.gz * http://get.influxdb.org/telegraf/telegraf-0.12.1-1_freebsd_amd64.tar.gz
##### tarball Instructions:
See linux instructions above.
### Ansible Role: ### Ansible Role:
@@ -69,7 +87,8 @@ brew install telegraf
### Windows Binaries (EXPERIMENTAL) ### Windows Binaries (EXPERIMENTAL)
Latest: Latest:
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0-beta2_windows_amd64.zip * http://get.influxdb.org/telegraf/telegraf-0.12.1-1_windows_amd64.zip
* http://get.influxdb.org/telegraf/telegraf-0.12.1-1_windows_i386.zip
### From Source: ### From Source:
@@ -142,10 +161,6 @@ Currently implemented sources:
* [apache](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/apache) * [apache](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/apache)
* [bcache](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/bcache) * [bcache](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/bcache)
* [cassandra](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/cassandra) * [cassandra](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/cassandra)
* [ceph](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/ceph)
* [chrony](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/chrony)
* [consul](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/consul)
* [conntrack](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/conntrack)
* [couchbase](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/couchbase) * [couchbase](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/couchbase)
* [couchdb](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/couchdb) * [couchdb](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/couchdb)
* [disque](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/disque) * [disque](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/disque)
@@ -171,7 +186,6 @@ Currently implemented sources:
* [net_response](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/net_response) * [net_response](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/net_response)
* [nginx](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/nginx) * [nginx](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/nginx)
* [nsq](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/nsq) * [nsq](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/nsq)
* [nstat](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/nstat)
* [ntpq](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/ntpq) * [ntpq](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/ntpq)
* [phpfpm](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/phpfpm) * [phpfpm](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/phpfpm)
* [phusion passenger](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/passenger) * [phusion passenger](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/passenger)
@@ -191,7 +205,6 @@ Currently implemented sources:
* [snmp](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/snmp) * [snmp](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/snmp)
* [sql server](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/sqlserver) (microsoft) * [sql server](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/sqlserver) (microsoft)
* [twemproxy](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/twemproxy) * [twemproxy](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/twemproxy)
* [varnish](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/varnish)
* [zfs](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/zfs) * [zfs](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/zfs)
* [zookeeper](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/zookeeper) * [zookeeper](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/zookeeper)
* [win_perf_counters ](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/win_perf_counters) (windows performance counters) * [win_perf_counters ](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/win_perf_counters) (windows performance counters)
@@ -206,19 +219,16 @@ Currently implemented sources:
* swap * swap
* processes * processes
* kernel (/proc/stat) * kernel (/proc/stat)
* kernel (/proc/vmstat)
Telegraf can also collect metrics via the following service plugins: Telegraf can also collect metrics via the following service plugins:
* [statsd](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/statsd) * [statsd](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/statsd)
* [tail](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/tail)
* [udp_listener](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/udp_listener) * [udp_listener](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/udp_listener)
* [tcp_listener](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/tcp_listener) * [tcp_listener](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/tcp_listener)
* [mqtt_consumer](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/mqtt_consumer) * [mqtt_consumer](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/mqtt_consumer)
* [kafka_consumer](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/kafka_consumer) * [kafka_consumer](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/kafka_consumer)
* [nats_consumer](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/nats_consumer) * [nats_consumer](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/nats_consumer)
* [github_webhooks](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/github_webhooks) * [github_webhooks](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/github_webhooks)
* [rollbar_webhooks](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/rollbar_webhooks)
We'll be adding support for many more over the coming months. Read on if you We'll be adding support for many more over the coming months. Read on if you
want to add support for another service or third-party API. want to add support for another service or third-party API.
@@ -233,8 +243,6 @@ want to add support for another service or third-party API.
* [datadog](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/datadog) * [datadog](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/datadog)
* [file](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/file) * [file](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/file)
* [graphite](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/graphite) * [graphite](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/graphite)
* [graylog](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/graylog)
* [instrumental](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/instrumental)
* [kafka](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/kafka) * [kafka](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/kafka)
* [librato](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/librato) * [librato](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/librato)
* [mqtt](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/mqtt) * [mqtt](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/mqtt)

View File

@@ -18,8 +18,4 @@ type Accumulator interface {
Debug() bool Debug() bool
SetDebug(enabled bool) SetDebug(enabled bool)
SetPrecision(precision, interval time.Duration)
DisablePrecision()
} }

View File

@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"log" "log"
"math" "math"
"sync"
"time" "time"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
@@ -17,24 +18,21 @@ func NewAccumulator(
acc := accumulator{} acc := accumulator{}
acc.metrics = metrics acc.metrics = metrics
acc.inputConfig = inputConfig acc.inputConfig = inputConfig
acc.precision = time.Nanosecond
return &acc return &acc
} }
type accumulator struct { type accumulator struct {
sync.Mutex
metrics chan telegraf.Metric metrics chan telegraf.Metric
defaultTags map[string]string defaultTags map[string]string
debug bool debug bool
// print every point added to the accumulator
trace bool
inputConfig *internal_models.InputConfig inputConfig *internal_models.InputConfig
prefix string prefix string
precision time.Duration
} }
func (ac *accumulator) Add( func (ac *accumulator) Add(
@@ -86,17 +84,13 @@ func (ac *accumulator) AddFields(
if tags == nil { if tags == nil {
tags = make(map[string]string) tags = make(map[string]string)
} }
// Apply plugin-wide tags if set
for k, v := range ac.inputConfig.Tags {
if _, ok := tags[k]; !ok {
tags[k] = v
}
}
// Apply daemon-wide tags if set // Apply daemon-wide tags if set
for k, v := range ac.defaultTags { for k, v := range ac.defaultTags {
if _, ok := tags[k]; !ok { tags[k] = v
tags[k] = v }
} // Apply plugin-wide tags if set
for k, v := range ac.inputConfig.Tags {
tags[k] = v
} }
ac.inputConfig.Filter.FilterTags(tags) ac.inputConfig.Filter.FilterTags(tags)
@@ -144,7 +138,6 @@ func (ac *accumulator) AddFields(
} else { } else {
timestamp = time.Now() timestamp = time.Now()
} }
timestamp = timestamp.Round(ac.precision)
if ac.prefix != "" { if ac.prefix != "" {
measurement = ac.prefix + measurement measurement = ac.prefix + measurement
@@ -155,7 +148,7 @@ func (ac *accumulator) AddFields(
log.Printf("Error adding point [%s]: %s\n", measurement, err.Error()) log.Printf("Error adding point [%s]: %s\n", measurement, err.Error())
return return
} }
if ac.trace { if ac.debug {
fmt.Println("> " + m.String()) fmt.Println("> " + m.String())
} }
ac.metrics <- m ac.metrics <- m
@@ -169,39 +162,6 @@ func (ac *accumulator) SetDebug(debug bool) {
ac.debug = debug ac.debug = debug
} }
func (ac *accumulator) Trace() bool {
return ac.trace
}
func (ac *accumulator) SetTrace(trace bool) {
ac.trace = trace
}
// SetPrecision takes two time.Duration objects. If the first is non-zero,
// it sets that as the precision. Otherwise, it takes the second argument
// as the order of time that the metrics should be rounded to, with the
// maximum being 1s.
func (ac *accumulator) SetPrecision(precision, interval time.Duration) {
if precision > 0 {
ac.precision = precision
return
}
switch {
case interval >= time.Second:
ac.precision = time.Second
case interval >= time.Millisecond:
ac.precision = time.Millisecond
case interval >= time.Microsecond:
ac.precision = time.Microsecond
default:
ac.precision = time.Nanosecond
}
}
func (ac *accumulator) DisablePrecision() {
ac.precision = time.Nanosecond
}
func (ac *accumulator) setDefaultTags(tags map[string]string) { func (ac *accumulator) setDefaultTags(tags map[string]string) {
ac.defaultTags = tags ac.defaultTags = tags
} }

View File

@@ -38,128 +38,6 @@ func TestAdd(t *testing.T) {
actual) actual)
} }
func TestAddNoPrecisionWithInterval(t *testing.T) {
a := accumulator{}
now := time.Date(2006, time.February, 10, 12, 0, 0, 82912748, time.UTC)
a.metrics = make(chan telegraf.Metric, 10)
defer close(a.metrics)
a.inputConfig = &internal_models.InputConfig{}
a.SetPrecision(0, time.Second)
a.Add("acctest", float64(101), map[string]string{})
a.Add("acctest", float64(101), map[string]string{"acc": "test"})
a.Add("acctest", float64(101), map[string]string{"acc": "test"}, now)
testm := <-a.metrics
actual := testm.String()
assert.Contains(t, actual, "acctest value=101")
testm = <-a.metrics
actual = testm.String()
assert.Contains(t, actual, "acctest,acc=test value=101")
testm = <-a.metrics
actual = testm.String()
assert.Equal(t,
fmt.Sprintf("acctest,acc=test value=101 %d", int64(1139572800000000000)),
actual)
}
func TestAddNoIntervalWithPrecision(t *testing.T) {
a := accumulator{}
now := time.Date(2006, time.February, 10, 12, 0, 0, 82912748, time.UTC)
a.metrics = make(chan telegraf.Metric, 10)
defer close(a.metrics)
a.inputConfig = &internal_models.InputConfig{}
a.SetPrecision(time.Second, time.Millisecond)
a.Add("acctest", float64(101), map[string]string{})
a.Add("acctest", float64(101), map[string]string{"acc": "test"})
a.Add("acctest", float64(101), map[string]string{"acc": "test"}, now)
testm := <-a.metrics
actual := testm.String()
assert.Contains(t, actual, "acctest value=101")
testm = <-a.metrics
actual = testm.String()
assert.Contains(t, actual, "acctest,acc=test value=101")
testm = <-a.metrics
actual = testm.String()
assert.Equal(t,
fmt.Sprintf("acctest,acc=test value=101 %d", int64(1139572800000000000)),
actual)
}
func TestAddDisablePrecision(t *testing.T) {
a := accumulator{}
now := time.Date(2006, time.February, 10, 12, 0, 0, 82912748, time.UTC)
a.metrics = make(chan telegraf.Metric, 10)
defer close(a.metrics)
a.inputConfig = &internal_models.InputConfig{}
a.SetPrecision(time.Second, time.Millisecond)
a.DisablePrecision()
a.Add("acctest", float64(101), map[string]string{})
a.Add("acctest", float64(101), map[string]string{"acc": "test"})
a.Add("acctest", float64(101), map[string]string{"acc": "test"}, now)
testm := <-a.metrics
actual := testm.String()
assert.Contains(t, actual, "acctest value=101")
testm = <-a.metrics
actual = testm.String()
assert.Contains(t, actual, "acctest,acc=test value=101")
testm = <-a.metrics
actual = testm.String()
assert.Equal(t,
fmt.Sprintf("acctest,acc=test value=101 %d", int64(1139572800082912748)),
actual)
}
func TestDifferentPrecisions(t *testing.T) {
a := accumulator{}
now := time.Date(2006, time.February, 10, 12, 0, 0, 82912748, time.UTC)
a.metrics = make(chan telegraf.Metric, 10)
defer close(a.metrics)
a.inputConfig = &internal_models.InputConfig{}
a.SetPrecision(0, time.Second)
a.Add("acctest", float64(101), map[string]string{"acc": "test"}, now)
testm := <-a.metrics
actual := testm.String()
assert.Equal(t,
fmt.Sprintf("acctest,acc=test value=101 %d", int64(1139572800000000000)),
actual)
a.SetPrecision(0, time.Millisecond)
a.Add("acctest", float64(101), map[string]string{"acc": "test"}, now)
testm = <-a.metrics
actual = testm.String()
assert.Equal(t,
fmt.Sprintf("acctest,acc=test value=101 %d", int64(1139572800083000000)),
actual)
a.SetPrecision(0, time.Microsecond)
a.Add("acctest", float64(101), map[string]string{"acc": "test"}, now)
testm = <-a.metrics
actual = testm.String()
assert.Equal(t,
fmt.Sprintf("acctest,acc=test value=101 %d", int64(1139572800082913000)),
actual)
a.SetPrecision(0, time.Nanosecond)
a.Add("acctest", float64(101), map[string]string{"acc": "test"}, now)
testm = <-a.metrics
actual = testm.String()
assert.Equal(t,
fmt.Sprintf("acctest,acc=test value=101 %d", int64(1139572800082912748)),
actual)
}
func TestAddDefaultTags(t *testing.T) { func TestAddDefaultTags(t *testing.T) {
a := accumulator{} a := accumulator{}
a.addDefaultTag("default", "tag") a.addDefaultTag("default", "tag")

View File

@@ -1,15 +1,17 @@
package agent package agent
import ( import (
cryptorand "crypto/rand"
"fmt" "fmt"
"log" "log"
"math/big"
"math/rand"
"os" "os"
"runtime" "runtime"
"sync" "sync"
"time" "time"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/internal/config" "github.com/influxdata/telegraf/internal/config"
"github.com/influxdata/telegraf/internal/models" "github.com/influxdata/telegraf/internal/models"
) )
@@ -100,41 +102,93 @@ func panicRecover(input *internal_models.RunningInput) {
} }
} }
// gatherer runs the inputs that have been configured with their own // gatherParallel runs the inputs that are using the same reporting interval
// as the telegraf agent.
func (a *Agent) gatherParallel(metricC chan telegraf.Metric) error {
var wg sync.WaitGroup
start := time.Now()
counter := 0
jitter := a.Config.Agent.CollectionJitter.Duration.Nanoseconds()
for _, input := range a.Config.Inputs {
if input.Config.Interval != 0 {
continue
}
wg.Add(1)
counter++
go func(input *internal_models.RunningInput) {
defer panicRecover(input)
defer wg.Done()
acc := NewAccumulator(input.Config, metricC)
acc.SetDebug(a.Config.Agent.Debug)
acc.setDefaultTags(a.Config.Tags)
if jitter != 0 {
nanoSleep := rand.Int63n(jitter)
d, err := time.ParseDuration(fmt.Sprintf("%dns", nanoSleep))
if err != nil {
log.Printf("Jittering collection interval failed for plugin %s",
input.Name)
} else {
time.Sleep(d)
}
}
if err := input.Input.Gather(acc); err != nil {
log.Printf("Error in input [%s]: %s", input.Name, err)
}
}(input)
}
if counter == 0 {
return nil
}
wg.Wait()
elapsed := time.Since(start)
if !a.Config.Agent.Quiet {
log.Printf("Gathered metrics, (%s interval), from %d inputs in %s\n",
a.Config.Agent.Interval.Duration, counter, elapsed)
}
return nil
}
// gatherSeparate runs the inputs that have been configured with their own
// reporting interval. // reporting interval.
func (a *Agent) gatherer( func (a *Agent) gatherSeparate(
shutdown chan struct{}, shutdown chan struct{},
input *internal_models.RunningInput, input *internal_models.RunningInput,
interval time.Duration,
metricC chan telegraf.Metric, metricC chan telegraf.Metric,
) error { ) error {
defer panicRecover(input) defer panicRecover(input)
ticker := time.NewTicker(interval) ticker := time.NewTicker(input.Config.Interval)
defer ticker.Stop()
for { for {
var outerr error var outerr error
start := time.Now()
acc := NewAccumulator(input.Config, metricC) acc := NewAccumulator(input.Config, metricC)
acc.SetDebug(a.Config.Agent.Debug) acc.SetDebug(a.Config.Agent.Debug)
acc.SetPrecision(a.Config.Agent.Precision.Duration,
a.Config.Agent.Interval.Duration)
acc.setDefaultTags(a.Config.Tags) acc.setDefaultTags(a.Config.Tags)
internal.RandomSleep(a.Config.Agent.CollectionJitter.Duration, shutdown) if err := input.Input.Gather(acc); err != nil {
log.Printf("Error in input [%s]: %s", input.Name, err)
}
start := time.Now()
gatherWithTimeout(shutdown, input, acc, interval)
elapsed := time.Since(start) elapsed := time.Since(start)
if !a.Config.Agent.Quiet {
log.Printf("Gathered metrics, (separate %s interval), from %s in %s\n",
input.Config.Interval, input.Name, elapsed)
}
if outerr != nil { if outerr != nil {
return outerr return outerr
} }
if a.Config.Agent.Debug {
log.Printf("Input [%s] gathered metrics, (%s interval) in %s\n",
input.Name, interval, elapsed)
}
select { select {
case <-shutdown: case <-shutdown:
@@ -145,42 +199,6 @@ func (a *Agent) gatherer(
} }
} }
// gatherWithTimeout gathers from the given input, with the given timeout.
// when the given timeout is reached, gatherWithTimeout logs an error message
// but continues waiting for it to return. This is to avoid leaving behind
// hung processes, and to prevent re-calling the same hung process over and
// over.
func gatherWithTimeout(
shutdown chan struct{},
input *internal_models.RunningInput,
acc *accumulator,
timeout time.Duration,
) {
ticker := time.NewTicker(timeout)
defer ticker.Stop()
done := make(chan error)
go func() {
done <- input.Input.Gather(acc)
}()
for {
select {
case err := <-done:
if err != nil {
log.Printf("ERROR in input [%s]: %s", input.Name, err)
}
return
case <-ticker.C:
log.Printf("ERROR: input [%s] took longer to collect than "+
"collection interval (%s)",
input.Name, timeout)
continue
case <-shutdown:
return
}
}
}
// Test verifies that we can 'Gather' from all inputs with their configured // Test verifies that we can 'Gather' from all inputs with their configured
// Config struct // Config struct
func (a *Agent) Test() error { func (a *Agent) Test() error {
@@ -202,9 +220,7 @@ func (a *Agent) Test() error {
for _, input := range a.Config.Inputs { for _, input := range a.Config.Inputs {
acc := NewAccumulator(input.Config, metricC) acc := NewAccumulator(input.Config, metricC)
acc.SetTrace(true) acc.SetDebug(true)
acc.SetPrecision(a.Config.Agent.Precision.Duration,
a.Config.Agent.Interval.Duration)
acc.setDefaultTags(a.Config.Tags) acc.setDefaultTags(a.Config.Tags)
fmt.Printf("* Plugin: %s, Collection 1\n", input.Name) fmt.Printf("* Plugin: %s, Collection 1\n", input.Name)
@@ -265,7 +281,6 @@ func (a *Agent) flusher(shutdown chan struct{}, metricC chan telegraf.Metric) er
a.flush() a.flush()
return nil return nil
case <-ticker.C: case <-ticker.C:
internal.RandomSleep(a.Config.Agent.FlushJitter.Duration, shutdown)
a.flush() a.flush()
case m := <-metricC: case m := <-metricC:
for _, o := range a.Config.Outputs { for _, o := range a.Config.Outputs {
@@ -275,10 +290,35 @@ func (a *Agent) flusher(shutdown chan struct{}, metricC chan telegraf.Metric) er
} }
} }
// jitterInterval applies the the interval jitter to the flush interval using
// crypto/rand number generator
func jitterInterval(ininterval, injitter time.Duration) time.Duration {
var jitter int64
outinterval := ininterval
if injitter.Nanoseconds() != 0 {
maxjitter := big.NewInt(injitter.Nanoseconds())
if j, err := cryptorand.Int(cryptorand.Reader, maxjitter); err == nil {
jitter = j.Int64()
}
outinterval = time.Duration(jitter + ininterval.Nanoseconds())
}
if outinterval.Nanoseconds() < time.Duration(500*time.Millisecond).Nanoseconds() {
log.Printf("Flush interval %s too low, setting to 500ms\n", outinterval)
outinterval = time.Duration(500 * time.Millisecond)
}
return outinterval
}
// Run runs the agent daemon, gathering every Interval // Run runs the agent daemon, gathering every Interval
func (a *Agent) Run(shutdown chan struct{}) error { func (a *Agent) Run(shutdown chan struct{}) error {
var wg sync.WaitGroup var wg sync.WaitGroup
a.Config.Agent.FlushInterval.Duration = jitterInterval(
a.Config.Agent.FlushInterval.Duration,
a.Config.Agent.FlushJitter.Duration)
log.Printf("Agent Config: Interval:%s, Debug:%#v, Quiet:%#v, Hostname:%#v, "+ log.Printf("Agent Config: Interval:%s, Debug:%#v, Quiet:%#v, Hostname:%#v, "+
"Flush Interval:%s \n", "Flush Interval:%s \n",
a.Config.Agent.Interval.Duration, a.Config.Agent.Debug, a.Config.Agent.Quiet, a.Config.Agent.Interval.Duration, a.Config.Agent.Debug, a.Config.Agent.Quiet,
@@ -293,9 +333,6 @@ func (a *Agent) Run(shutdown chan struct{}) error {
case telegraf.ServiceInput: case telegraf.ServiceInput:
acc := NewAccumulator(input.Config, metricC) acc := NewAccumulator(input.Config, metricC)
acc.SetDebug(a.Config.Agent.Debug) acc.SetDebug(a.Config.Agent.Debug)
// Service input plugins should set their own precision of their
// metrics.
acc.DisablePrecision()
acc.setDefaultTags(a.Config.Tags) acc.setDefaultTags(a.Config.Tags)
if err := p.Start(acc); err != nil { if err := p.Start(acc); err != nil {
log.Printf("Service for input %s failed to start, exiting\n%s\n", log.Printf("Service for input %s failed to start, exiting\n%s\n",
@@ -311,6 +348,7 @@ func (a *Agent) Run(shutdown chan struct{}) error {
i := int64(a.Config.Agent.Interval.Duration) i := int64(a.Config.Agent.Interval.Duration)
time.Sleep(time.Duration(i - (time.Now().UnixNano() % i))) time.Sleep(time.Duration(i - (time.Now().UnixNano() % i)))
} }
ticker := time.NewTicker(a.Config.Agent.Interval.Duration)
wg.Add(1) wg.Add(1)
go func() { go func() {
@@ -321,21 +359,32 @@ func (a *Agent) Run(shutdown chan struct{}) error {
} }
}() }()
wg.Add(len(a.Config.Inputs))
for _, input := range a.Config.Inputs { for _, input := range a.Config.Inputs {
interval := a.Config.Agent.Interval.Duration // Special handling for inputs that have their own collection interval
// overwrite global interval if this plugin has it's own. // configured. Default intervals are handled below with gatherParallel
if input.Config.Interval != 0 { if input.Config.Interval != 0 {
interval = input.Config.Interval wg.Add(1)
go func(input *internal_models.RunningInput) {
defer wg.Done()
if err := a.gatherSeparate(shutdown, input, metricC); err != nil {
log.Printf(err.Error())
}
}(input)
} }
go func(in *internal_models.RunningInput, interv time.Duration) {
defer wg.Done()
if err := a.gatherer(shutdown, in, interv, metricC); err != nil {
log.Printf(err.Error())
}
}(input, interval)
} }
wg.Wait() defer wg.Wait()
return nil
for {
if err := a.gatherParallel(metricC); err != nil {
log.Printf(err.Error())
}
select {
case <-shutdown:
return nil
case <-ticker.C:
continue
}
}
} }

View File

@@ -2,6 +2,7 @@ package agent
import ( import (
"testing" "testing"
"time"
"github.com/influxdata/telegraf/internal/config" "github.com/influxdata/telegraf/internal/config"
@@ -109,3 +110,75 @@ func TestAgent_LoadOutput(t *testing.T) {
a, _ = NewAgent(c) a, _ = NewAgent(c)
assert.Equal(t, 3, len(a.Config.Outputs)) assert.Equal(t, 3, len(a.Config.Outputs))
} }
func TestAgent_ZeroJitter(t *testing.T) {
flushinterval := jitterInterval(time.Duration(10*time.Second),
time.Duration(0*time.Second))
actual := flushinterval.Nanoseconds()
exp := time.Duration(10 * time.Second).Nanoseconds()
if actual != exp {
t.Errorf("Actual %v, expected %v", actual, exp)
}
}
func TestAgent_ZeroInterval(t *testing.T) {
min := time.Duration(500 * time.Millisecond).Nanoseconds()
max := time.Duration(5 * time.Second).Nanoseconds()
for i := 0; i < 1000; i++ {
flushinterval := jitterInterval(time.Duration(0*time.Second),
time.Duration(5*time.Second))
actual := flushinterval.Nanoseconds()
if actual > max {
t.Errorf("Didn't expect interval %d to be > %d", actual, max)
break
}
if actual < min {
t.Errorf("Didn't expect interval %d to be < %d", actual, min)
break
}
}
}
func TestAgent_ZeroBoth(t *testing.T) {
flushinterval := jitterInterval(time.Duration(0*time.Second),
time.Duration(0*time.Second))
actual := flushinterval
exp := time.Duration(500 * time.Millisecond)
if actual != exp {
t.Errorf("Actual %v, expected %v", actual, exp)
}
}
func TestAgent_JitterMax(t *testing.T) {
max := time.Duration(32 * time.Second).Nanoseconds()
for i := 0; i < 1000; i++ {
flushinterval := jitterInterval(time.Duration(30*time.Second),
time.Duration(2*time.Second))
actual := flushinterval.Nanoseconds()
if actual > max {
t.Errorf("Didn't expect interval %d to be > %d", actual, max)
break
}
}
}
func TestAgent_JitterMin(t *testing.T) {
min := time.Duration(30 * time.Second).Nanoseconds()
for i := 0; i < 1000; i++ {
flushinterval := jitterInterval(time.Duration(30*time.Second),
time.Duration(2*time.Second))
actual := flushinterval.Nanoseconds()
if actual < min {
t.Errorf("Didn't expect interval %d to be < %d", actual, min)
break
}
}
}

View File

@@ -46,13 +46,9 @@ var fOutputFiltersLegacy = flag.String("outputfilter", "",
var fConfigDirectoryLegacy = flag.String("configdirectory", "", var fConfigDirectoryLegacy = flag.String("configdirectory", "",
"directory containing additional *.conf files") "directory containing additional *.conf files")
// Telegraf version, populated linker. // Telegraf version
// ie, -ldflags "-X main.version=`git describe --always --tags`" // -ldflags "-X main.Version=`git describe --always --tags`"
var ( var Version string
version string
commit string
branch string
)
const usage = `Telegraf, The plugin-driven server agent for collecting and reporting metrics. const usage = `Telegraf, The plugin-driven server agent for collecting and reporting metrics.
@@ -136,7 +132,7 @@ func main() {
if len(args) > 0 { if len(args) > 0 {
switch args[0] { switch args[0] {
case "version": case "version":
v := fmt.Sprintf("Telegraf - version %s", version) v := fmt.Sprintf("Telegraf - Version %s", Version)
fmt.Println(v) fmt.Println(v)
return return
case "config": case "config":
@@ -162,7 +158,7 @@ func main() {
} }
if *fVersion { if *fVersion {
v := fmt.Sprintf("Telegraf - version %s", version) v := fmt.Sprintf("Telegraf - Version %s", Version)
fmt.Println(v) fmt.Println(v)
return return
} }
@@ -255,7 +251,7 @@ func main() {
} }
}() }()
log.Printf("Starting Telegraf (version %s)\n", version) log.Printf("Starting Telegraf (version %s)\n", Version)
log.Printf("Loaded outputs: %s", strings.Join(c.OutputNames(), " ")) log.Printf("Loaded outputs: %s", strings.Join(c.OutputNames(), " "))
log.Printf("Loaded inputs: %s", strings.Join(c.InputNames(), " ")) log.Printf("Loaded inputs: %s", strings.Join(c.InputNames(), " "))
log.Printf("Tags enabled: %s", c.ListTags()) log.Printf("Tags enabled: %s", c.ListTags())

View File

@@ -186,59 +186,49 @@ name of the plugin.
# Graphite: # Graphite:
The Graphite data format translates graphite _dot_ buckets directly into The Graphite data format translates graphite _dot_ buckets directly into
telegraf measurement names, with a single value field, and without any tags. telegraf measurement names, with a single value field, and without any tags. For
By default, the separator is left as ".", but this can be changed using the more advanced options, Telegraf supports specifying "templates" to translate
"separator" argument. For more advanced options,
Telegraf supports specifying "templates" to translate
graphite buckets into Telegraf metrics. graphite buckets into Telegraf metrics.
Templates are of the form: #### Separator:
You can specify a separator to use for the parsed metrics.
By default, it will leave the metrics with a "." separator.
Setting `separator = "_"` will translate:
``` ```
"host.mytag.mytag.measurement.measurement.field*" cpu.usage.idle 99
=> cpu_usage_idle value=99
``` ```
Where the following keywords exist: #### Measurement/Tag Templates:
1. `measurement`: specifies that this section of the graphite bucket corresponds
to the measurement name. This can be specified multiple times.
2. `field`: specifies that this section of the graphite bucket corresponds
to the field name. This can be specified multiple times.
3. `measurement*`: specifies that all remaining elements of the graphite bucket
correspond to the measurement name.
4. `field*`: specifies that all remaining elements of the graphite bucket
correspond to the field name.
Any part of the template that is not a keyword is treated as a tag key. This
can also be specified multiple times.
NOTE: `field*` cannot be used in conjunction with `measurement*`!
#### Measurement & Tag Templates:
The most basic template is to specify a single transformation to apply to all The most basic template is to specify a single transformation to apply to all
incoming metrics. So the following template: incoming metrics. _measurement_ is a special keyword that tells Telegraf which
parts of the graphite bucket to combine into the measurement name. It can have a
trailing `*` to indicate that the remainder of the metric should be used.
Other words are considered tag keys. So the following template:
```toml ```toml
templates = [ templates = [
"region.region.measurement*" "region.measurement*"
] ]
``` ```
would result in the following Graphite -> Telegraf transformation. would result in the following Graphite -> Telegraf transformation.
``` ```
us.west.cpu.load 100 us-west.cpu.load 100
=> cpu.load,region=us.west value=100 => cpu.load,region=us-west value=100
``` ```
#### Field Templates: #### Field Templates:
There is also a _field_ keyword, which can only be specified once.
The field keyword tells Telegraf to give the metric that field name. The field keyword tells Telegraf to give the metric that field name.
So the following template: So the following template:
```toml ```toml
separator = "_"
templates = [ templates = [
"measurement.measurement.field.field.region" "measurement.measurement.field.field.region"
] ]
@@ -247,26 +237,24 @@ templates = [
would result in the following Graphite -> Telegraf transformation. would result in the following Graphite -> Telegraf transformation.
``` ```
cpu.usage.idle.percent.eu-east 100 cpu.usage.idle.percent.us-west 100
=> cpu_usage,region=eu-east idle_percent=100 => cpu_usage,region=us-west idle_percent=100
``` ```
The field key can also be derived from all remaining elements of the graphite The field key can also be derived from the second "half" of the input metric-name by specifying ```field*```:
bucket by specifying `field*`:
```toml ```toml
separator = "_"
templates = [ templates = [
"measurement.measurement.region.field*" "measurement.measurement.region.field*"
] ]
``` ```
which would result in the following Graphite -> Telegraf transformation. would result in the following Graphite -> Telegraf transformation.
``` ```
cpu.usage.eu-east.idle.percentage 100 cpu.usage.us-west.idle.percentage 100
=> cpu_usage,region=eu-east idle_percentage=100 => cpu_usage,region=us-west idle_percentage=100
``` ```
(This cannot be used in conjunction with "measurement*"!)
#### Filter Templates: #### Filter Templates:
@@ -283,8 +271,8 @@ templates = [
which would result in the following transformation: which would result in the following transformation:
``` ```
cpu.load.eu-east 100 cpu.load.us-west 100
=> cpu_load,region=eu-east value=100 => cpu_load,region=us-west value=100
mem.cached.localhost 256 mem.cached.localhost 256
=> mem_cached,host=localhost value=256 => mem_cached,host=localhost value=256
@@ -306,8 +294,8 @@ templates = [
would result in the following Graphite -> Telegraf transformation. would result in the following Graphite -> Telegraf transformation.
``` ```
cpu.usage.idle.eu-east 100 cpu.usage.idle.us-west 100
=> cpu_usage,region=eu-east,datacenter=1a idle=100 => cpu_usage,region=us-west,datacenter=1a idle=100
``` ```
There are many more options available, There are many more options available,
@@ -338,12 +326,12 @@ There are many more options available,
## similar to the line protocol format. There can be only one default template. ## similar to the line protocol format. There can be only one default template.
## Templates support below format: ## Templates support below format:
## 1. filter + template ## 1. filter + template
## 2. filter + template + extra tag(s) ## 2. filter + template + extra tag
## 3. filter + template with field key ## 3. filter + template with field key
## 4. default template ## 4. default template
templates = [ templates = [
"*.app env.service.resource.measurement", "*.app env.service.resource.measurement",
"stats.* .host.measurement* region=eu-east,agent=sensu", "stats.* .host.measurement* region=us-west,agent=sensu",
"stats2.* .host.measurement.field", "stats2.* .host.measurement.field",
"measurement*" "measurement*"
] ]

View File

@@ -52,11 +52,6 @@
## ie, a jitter of 5s and interval 10s means flushes will happen every 10-15s ## ie, a jitter of 5s and interval 10s means flushes will happen every 10-15s
flush_jitter = "0s" flush_jitter = "0s"
## By default, precision will be set to the same timestamp order as the
## collection interval, with the maximum being 1s.
## Precision will NOT be used for service inputs, such as logparser and statsd.
## Valid values are "Nns", "Nus" (or "Nµs"), "Nms", "Ns".
precision = ""
## Run telegraf in debug mode ## Run telegraf in debug mode
debug = false debug = false
## Run telegraf in quiet mode ## Run telegraf in quiet mode
@@ -80,11 +75,11 @@
urls = ["http://localhost:8086"] # required urls = ["http://localhost:8086"] # required
## The target database for metrics (telegraf will create it if not exists). ## The target database for metrics (telegraf will create it if not exists).
database = "telegraf" # required database = "telegraf" # required
## Retention policy to write to.
## Retention policy to write to. Empty string writes to the default rp. retention_policy = "default"
retention_policy = "" ## Precision of writes, valid values are "ns", "us" (or "µs"), "ms", "s", "m", "h".
## Write consistency (clusters only), can be: "any", "one", "quorom", "all" ## note: using "s" precision greatly improves InfluxDB compression.
write_consistency = "any" precision = "s"
## Write timeout (for the InfluxDB client), formatted as a string. ## Write timeout (for the InfluxDB client), formatted as a string.
## If not provided, will default to 5s. 0s means no timeout (not recommended). ## If not provided, will default to 5s. 0s means no timeout (not recommended).
@@ -156,18 +151,12 @@
# #
# ## Amazon Credentials # ## Amazon Credentials
# ## Credentials are loaded in the following order # ## Credentials are loaded in the following order
# ## 1) Assumed credentials via STS if role_arn is specified # ## 1) explicit credentials from 'access_key' and 'secret_key'
# ## 2) explicit credentials from 'access_key' and 'secret_key' # ## 2) environment variables
# ## 3) shared profile from 'profile' # ## 3) shared credentials file
# ## 4) environment variables # ## 4) EC2 Instance Profile
# ## 5) shared credentials file
# ## 6) EC2 Instance Profile
# #access_key = "" # #access_key = ""
# #secret_key = "" # #secret_key = ""
# #token = ""
# #role_arn = ""
# #profile = ""
# #shared_credential_file = ""
# #
# ## Namespace for the CloudWatch MetricDatums # ## Namespace for the CloudWatch MetricDatums
# namespace = 'InfluxData/Telegraf' # namespace = 'InfluxData/Telegraf'
@@ -207,27 +196,6 @@
# timeout = 2 # timeout = 2
# # Send telegraf metrics to graylog(s)
# [[outputs.graylog]]
# ## Udp endpoint for your graylog instance.
# servers = ["127.0.0.1:12201", "192.168.1.1:12201"]
# # Configuration for sending metrics to an Instrumental project
# [[outputs.instrumental]]
# ## Project API Token (required)
# api_token = "API Token" # required
# ## Prefix the metrics with a given name
# prefix = ""
# ## Stats output template (Graphite formatting)
# ## see https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md#graphite
# template = "host.tags.measurement.field"
# ## Timeout in seconds to connect
# timeout = "2s"
# ## Display Communcation to Instrumental
# debug = false
# # Configuration for the Kafka server to send metrics to # # Configuration for the Kafka server to send metrics to
# [[outputs.kafka]] # [[outputs.kafka]]
# ## URLs of kafka brokers # ## URLs of kafka brokers
@@ -285,18 +253,12 @@
# #
# ## Amazon Credentials # ## Amazon Credentials
# ## Credentials are loaded in the following order # ## Credentials are loaded in the following order
# ## 1) Assumed credentials via STS if role_arn is specified # ## 1) explicit credentials from 'access_key' and 'secret_key'
# ## 2) explicit credentials from 'access_key' and 'secret_key' # ## 2) environment variables
# ## 3) shared profile from 'profile' # ## 3) shared credentials file
# ## 4) environment variables # ## 4) EC2 Instance Profile
# ## 5) shared credentials file
# ## 6) EC2 Instance Profile
# #access_key = "" # #access_key = ""
# #secret_key = "" # #secret_key = ""
# #token = ""
# #role_arn = ""
# #profile = ""
# #shared_credential_file = ""
# #
# ## Kinesis StreamName must exist prior to starting telegraf. # ## Kinesis StreamName must exist prior to starting telegraf.
# streamname = "StreamName" # streamname = "StreamName"
@@ -474,7 +436,6 @@
# # Read Apache status information (mod_status) # # Read Apache status information (mod_status)
# [[inputs.apache]] # [[inputs.apache]]
# ## An array of Apache status URI to gather stats. # ## An array of Apache status URI to gather stats.
# ## Default is "http://localhost/server-status?auto".
# urls = ["http://localhost/server-status?auto"] # urls = ["http://localhost/server-status?auto"]
@@ -508,24 +469,6 @@
# ] # ]
# # Collects performance metrics from the MON and OSD nodes in a Ceph storage cluster.
# [[inputs.ceph]]
# ## All configuration values are optional, defaults are shown below
#
# ## location of ceph binary
# ceph_binary = "/usr/bin/ceph"
#
# ## directory in which to look for socket files
# socket_dir = "/var/run/ceph"
#
# ## prefix of MON and OSD socket files, used to determine socket type
# mon_prefix = "ceph-mon"
# osd_prefix = "ceph-osd"
#
# ## suffix used to identify socket files
# socket_suffix = "asok"
# # Pull Metric Statistics from Amazon CloudWatch # # Pull Metric Statistics from Amazon CloudWatch
# [[inputs.cloudwatch]] # [[inputs.cloudwatch]]
# ## Amazon Region # ## Amazon Region
@@ -533,18 +476,12 @@
# #
# ## Amazon Credentials # ## Amazon Credentials
# ## Credentials are loaded in the following order # ## Credentials are loaded in the following order
# ## 1) Assumed credentials via STS if role_arn is specified # ## 1) explicit credentials from 'access_key' and 'secret_key'
# ## 2) explicit credentials from 'access_key' and 'secret_key' # ## 2) environment variables
# ## 3) shared profile from 'profile' # ## 3) shared credentials file
# ## 4) environment variables # ## 4) EC2 Instance Profile
# ## 5) shared credentials file
# ## 6) EC2 Instance Profile
# #access_key = "" # #access_key = ""
# #secret_key = "" # #secret_key = ""
# #token = ""
# #role_arn = ""
# #profile = ""
# #shared_credential_file = ""
# #
# ## Requested CloudWatch aggregation Period (required - must be a multiple of 60s) # ## Requested CloudWatch aggregation Period (required - must be a multiple of 60s)
# period = '1m' # period = '1m'
@@ -556,10 +493,6 @@
# ## gaps or overlap in pulled data # ## gaps or overlap in pulled data
# interval = '1m' # interval = '1m'
# #
# ## Configure the TTL for the internal cache of metrics.
# ## Defaults to 1 hr if not specified
# #cache_ttl = '10m'
#
# ## Metric Statistic Namespace (required) # ## Metric Statistic Namespace (required)
# namespace = 'AWS/ELB' # namespace = 'AWS/ELB'
# #
@@ -575,23 +508,6 @@
# # value = 'p-example' # # value = 'p-example'
# # Gather health check statuses from services registered in Consul
# [[inputs.consul]]
# ## Most of these values defaults to the one configured on a Consul's agent level.
# ## Optional Consul server address (default: "localhost")
# # address = "localhost"
# ## Optional URI scheme for the Consul server (default: "http")
# # scheme = "http"
# ## Optional ACL token used in every request (default: "")
# # token = ""
# ## Optional username used for request HTTP Basic Authentication (default: "")
# # username = ""
# ## Optional password used for HTTP Basic Authentication (default: "")
# # password = ""
# ## Optional data centre to query the health checks from (default: "")
# # datacentre = ""
# # Read metrics from one or many couchbase clusters # # Read metrics from one or many couchbase clusters
# [[inputs.couchbase]] # [[inputs.couchbase]]
# ## specify servers via a url matching: # ## specify servers via a url matching:
@@ -684,11 +600,7 @@
# # Read metrics from one or more commands that can output to stdout # # Read metrics from one or more commands that can output to stdout
# [[inputs.exec]] # [[inputs.exec]]
# ## Commands array # ## Commands array
# commands = [ # commands = ["/tmp/test.sh", "/usr/bin/mycollector --foo=bar"]
# "/tmp/test.sh",
# "/usr/bin/mycollector --foo=bar",
# "/tmp/collect_*.sh"
# ]
# #
# ## Timeout for each command to complete. # ## Timeout for each command to complete.
# timeout = "5s" # timeout = "5s"
@@ -719,43 +631,6 @@
# md5 = false # md5 = false
# # Read flattened metrics from one or more GrayLog HTTP endpoints
# [[inputs.graylog]]
# ## API endpoint, currently supported API:
# ##
# ## - multiple (Ex http://<host>:12900/system/metrics/multiple)
# ## - namespace (Ex http://<host>:12900/system/metrics/namespace/{namespace})
# ##
# ## For namespace endpoint, the metrics array will be ignored for that call.
# ## Endpoint can contain namespace and multiple type calls.
# ##
# ## Please check http://[graylog-server-ip]:12900/api-browser for full list
# ## of endpoints
# servers = [
# "http://[graylog-server-ip]:12900/system/metrics/multiple",
# ]
#
# ## Metrics list
# ## List of metrics can be found on Graylog webservice documentation.
# ## Or by hitting the the web service api at:
# ## http://[graylog-host]:12900/system/metrics
# metrics = [
# "jvm.cl.loaded",
# "jvm.memory.pools.Metaspace.committed"
# ]
#
# ## Username and password
# username = ""
# password = ""
#
# ## 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
# # Read metrics of haproxy, via socket or csv stats page # # Read metrics of haproxy, via socket or csv stats page
# [[inputs.haproxy]] # [[inputs.haproxy]]
# ## An array of address to gather stats about. Specify an ip on hostname # ## An array of address to gather stats about. Specify an ip on hostname
@@ -763,8 +638,8 @@
# #
# ## If no servers are specified, then default to 127.0.0.1:1936 # ## If no servers are specified, then default to 127.0.0.1:1936
# servers = ["http://myhaproxy.com:1936", "http://anotherhaproxy.com:1936"] # servers = ["http://myhaproxy.com:1936", "http://anotherhaproxy.com:1936"]
# ## Or you can also use local socket # ## Or you can also use local socket(not work yet)
# ## servers = ["socket:/run/haproxy/admin.sock"] # ## servers = ["socket://run/haproxy/admin.sock"]
# # HTTP/HTTPS request given an address a method and a timeout # # HTTP/HTTPS request given an address a method and a timeout
@@ -772,7 +647,7 @@
# ## Server address (default http://localhost) # ## Server address (default http://localhost)
# address = "http://github.com" # address = "http://github.com"
# ## Set response_timeout (default 5 seconds) # ## Set response_timeout (default 5 seconds)
# response_timeout = "5s" # response_timeout = 5
# ## HTTP Request Method # ## HTTP Request Method
# method = "GET" # method = "GET"
# ## Whether to follow redirects from the server (defaults to false) # ## Whether to follow redirects from the server (defaults to false)
@@ -784,13 +659,6 @@
# # body = ''' # # body = '''
# # {'fake':'data'} # # {'fake':'data'}
# # ''' # # '''
#
# ## 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
# # Read flattened metrics from one or more JSON HTTP endpoints # # Read flattened metrics from one or more JSON HTTP endpoints
@@ -841,7 +709,6 @@
# ## See the influxdb plugin's README for more details. # ## See the influxdb plugin's README for more details.
# #
# ## Multiple URLs from which to read InfluxDB-formatted JSON # ## Multiple URLs from which to read InfluxDB-formatted JSON
# ## Default is "http://localhost:8086/debug/vars".
# urls = [ # urls = [
# "http://localhost:8086/debug/vars" # "http://localhost:8086/debug/vars"
# ] # ]
@@ -981,8 +848,8 @@
# ## [username[:password]@][protocol[(address)]]/[?tls=[true|false|skip-verify]] # ## [username[:password]@][protocol[(address)]]/[?tls=[true|false|skip-verify]]
# ## see https://github.com/go-sql-driver/mysql#dsn-data-source-name # ## see https://github.com/go-sql-driver/mysql#dsn-data-source-name
# ## e.g. # ## e.g.
# ## db_user:passwd@tcp(127.0.0.1:3306)/?tls=false # ## root:passwd@tcp(127.0.0.1:3306)/?tls=false
# ## db_user@tcp(127.0.0.1:3306)/?tls=false # ## root@tcp(127.0.0.1:3306)/?tls=false
# # # #
# ## If no servers are specified, then localhost is used as the host. # ## If no servers are specified, then localhost is used as the host.
# servers = ["tcp(127.0.0.1:3306)/"] # servers = ["tcp(127.0.0.1:3306)/"]
@@ -1046,15 +913,14 @@
# protocol = "tcp" # protocol = "tcp"
# ## Server address (default localhost) # ## Server address (default localhost)
# address = "github.com:80" # address = "github.com:80"
# ## Set timeout # ## Set timeout (default 1.0 seconds)
# timeout = "1s" # timeout = 1.0
# # ## Set read timeout (default 1.0 seconds)
# read_timeout = 1.0
# ## Optional string sent to the server # ## Optional string sent to the server
# # send = "ssh" # # send = "ssh"
# ## Optional expected string in answer # ## Optional expected string in answer
# # expect = "ssh" # # expect = "ssh"
# ## Set read timeout (only used if expecting a response)
# read_timeout = "1s"
# # Read TCP metrics such as established, time wait and sockets counts. # # Read TCP metrics such as established, time wait and sockets counts.
@@ -1074,18 +940,6 @@
# endpoints = ["http://localhost:4151"] # endpoints = ["http://localhost:4151"]
# # Collect kernel snmp counters and network interface statistics
# [[inputs.nstat]]
# ## file paths for proc files. If empty default paths will be used:
# ## /proc/net/netstat, /proc/net/snmp, /proc/net/snmp6
# ## These can also be overridden with env variables, see README.
# proc_net_netstat = ""
# proc_net_snmp = ""
# proc_net_snmp6 = ""
# ## dump metrics with 0 values too
# dump_zeros = true
# # Get standard NTP query metrics, requires ntpq executable. # # Get standard NTP query metrics, requires ntpq executable.
# [[inputs.ntpq]] # [[inputs.ntpq]]
# ## If false, set the -n ntpq flag. Can reduce metric gather time. # ## If false, set the -n ntpq flag. Can reduce metric gather time.
@@ -1140,7 +994,7 @@
# count = 1 # required # count = 1 # required
# ## interval, in s, at which to ping. 0 == default (ping -i <PING_INTERVAL>) # ## interval, in s, at which to ping. 0 == default (ping -i <PING_INTERVAL>)
# ping_interval = 0.0 # ping_interval = 0.0
# ## per-ping timeout, in s. 0 == no timeout (ping -W <TIMEOUT>) # ## ping timeout, in s. 0 == no timeout (ping -W <TIMEOUT>)
# timeout = 1.0 # timeout = 1.0
# ## interface to send ping from (ping -I <INTERFACE>) # ## interface to send ping from (ping -I <INTERFACE>)
# interface = "" # interface = ""
@@ -1245,9 +1099,6 @@
# ## user as argument for pgrep (ie, pgrep -u <user>) # ## user as argument for pgrep (ie, pgrep -u <user>)
# # user = "nginx" # # user = "nginx"
# #
# ## override for process_name
# ## This is optional; default is sourced from /proc/<pid>/status
# # process_name = "bar"
# ## Field name prefix # ## Field name prefix
# prefix = "" # prefix = ""
# ## comment this out if you want raw cpu_time stats # ## comment this out if you want raw cpu_time stats
@@ -1273,7 +1124,7 @@
# # Read metrics from one or many RabbitMQ servers via the management API # # Read metrics from one or many RabbitMQ servers via the management API
# [[inputs.rabbitmq]] # [[inputs.rabbitmq]]
# # url = "http://localhost:15672" # url = "http://localhost:15672" # required
# # name = "rmq-server-1" # optional tag # # name = "rmq-server-1" # optional tag
# # username = "guest" # # username = "guest"
# # password = "guest" # # password = "guest"
@@ -1449,30 +1300,18 @@
# pools = ["redis_pool", "mc_pool"] # pools = ["redis_pool", "mc_pool"]
# # A plugin to collect stats from Varnish HTTP Cache # # Read metrics of ZFS from arcstats, zfetchstats and vdev_cache_stats
# [[inputs.varnish]]
# ## The default location of the varnishstat binary can be overridden with:
# binary = "/usr/bin/varnishstat"
#
# ## By default, telegraf gather stats for 3 metric points.
# ## Setting stats will override the defaults shown below.
# ## Glob matching can be used, ie, stats = ["MAIN.*"]
# ## stats may also be set to ["*"], which will collect all stats
# stats = ["MAIN.cache_hit", "MAIN.cache_miss", "MAIN.uptime"]
# # Read metrics of ZFS from arcstats, zfetchstats, vdev_cache_stats, and pools
# [[inputs.zfs]] # [[inputs.zfs]]
# ## ZFS kstat path. Ignored on FreeBSD # ## ZFS kstat path
# ## If not specified, then default is: # ## If not specified, then default is:
# # kstatPath = "/proc/spl/kstat/zfs" # kstatPath = "/proc/spl/kstat/zfs"
# #
# ## By default, telegraf gather all zfs stats # ## By default, telegraf gather all zfs stats
# ## If not specified, then default is: # ## If not specified, then default is:
# # kstatMetrics = ["arcstats", "zfetchstats", "vdev_cache_stats"] # kstatMetrics = ["arcstats", "zfetchstats", "vdev_cache_stats"]
# #
# ## By default, don't gather zpool stats # ## By default, don't gather zpool stats
# # poolMetrics = false # poolMetrics = false
# # Reads 'mntr' stats from one or many zookeeper servers # # Reads 'mntr' stats from one or many zookeeper servers
@@ -1503,7 +1342,7 @@
# ## an array of Zookeeper connection strings # ## an array of Zookeeper connection strings
# zookeeper_peers = ["localhost:2181"] # zookeeper_peers = ["localhost:2181"]
# ## Zookeeper Chroot # ## Zookeeper Chroot
# zookeeper_chroot = "" # zookeeper_chroot = "/"
# ## the name of the consumer group # ## the name of the consumer group
# consumer_group = "telegraf_metrics_consumers" # consumer_group = "telegraf_metrics_consumers"
# ## Offset (must be either "oldest" or "newest") # ## Offset (must be either "oldest" or "newest")
@@ -1516,35 +1355,6 @@
# data_format = "influx" # data_format = "influx"
# # Stream and parse log file(s).
# [[inputs.logparser]]
# ## Log files to parse.
# ## These accept standard unix glob matching rules, but with the addition of
# ## ** as a "super asterisk". ie:
# ## /var/log/**.log -> recursively find all .log files in /var/log
# ## /var/log/*/*.log -> find all .log files with a parent dir in /var/log
# ## /var/log/apache.log -> only tail the apache log file
# files = ["/var/log/influxdb/influxdb.log"]
# ## Read file from beginning.
# from_beginning = false
#
# ## Parse logstash-style "grok" patterns:
# ## Telegraf built-in parsing patterns: https://goo.gl/dkay10
# [inputs.logparser.grok]
# ## This is a list of patterns to check the given log file(s) for.
# ## Note that adding patterns here increases processing time. The most
# ## efficient configuration is to have one pattern per logparser.
# ## Other common built-in patterns are:
# ## %{COMMON_LOG_FORMAT} (plain apache & nginx access logs)
# ## %{COMBINED_LOG_FORMAT} (access logs + referrer & agent)
# patterns = ["%{INFLUXDB_HTTPD_LOG}"]
# ## Full path(s) to custom pattern files.
# custom_pattern_files = []
# ## Custom patterns can also be defined here. Put one pattern per line.
# custom_patterns = '''
# '''
# # Read metrics from MQTT topic(s) # # Read metrics from MQTT topic(s)
# [[inputs.mqtt_consumer]] # [[inputs.mqtt_consumer]]
# servers = ["localhost:1883"] # servers = ["localhost:1883"]
@@ -1601,12 +1411,6 @@
# data_format = "influx" # data_format = "influx"
# # A Rollbar Webhook Event collector
# [[inputs.rollbar_webhooks]]
# ## Address and port to host Webhook listener on
# service_address = ":1619"
# # Statsd Server # # Statsd Server
# [[inputs.statsd]] # [[inputs.statsd]]
# ## Address and port to host UDP listener on # ## Address and port to host UDP listener on

View File

@@ -1,79 +0,0 @@
package filter
import (
"strings"
"github.com/gobwas/glob"
)
type Filter interface {
Match(string) bool
}
// CompileFilter takes a list of string filters and returns a Filter interface
// for matching a given string against the filter list. The filter list
// supports glob matching too, ie:
//
// f, _ := CompileFilter([]string{"cpu", "mem", "net*"})
// f.Match("cpu") // true
// f.Match("network") // true
// f.Match("memory") // false
//
func CompileFilter(filters []string) (Filter, error) {
// return if there is nothing to compile
if len(filters) == 0 {
return nil, nil
}
// check if we can compile a non-glob filter
noGlob := true
for _, filter := range filters {
if hasMeta(filter) {
noGlob = false
break
}
}
switch {
case noGlob:
// return non-globbing filter if not needed.
return compileFilterNoGlob(filters), nil
case len(filters) == 1:
return glob.Compile(filters[0])
default:
return glob.Compile("{" + strings.Join(filters, ",") + "}")
}
}
// hasMeta reports whether path contains any magic glob characters.
func hasMeta(s string) bool {
return strings.IndexAny(s, "*?[") >= 0
}
type filter struct {
m map[string]struct{}
}
func (f *filter) Match(s string) bool {
_, ok := f.m[s]
return ok
}
type filtersingle struct {
s string
}
func (f *filtersingle) Match(s string) bool {
return f.s == s
}
func compileFilterNoGlob(filters []string) Filter {
if len(filters) == 1 {
return &filtersingle{s: filters[0]}
}
out := filter{m: make(map[string]struct{})}
for _, filter := range filters {
out.m[filter] = struct{}{}
}
return &out
}

View File

@@ -1,96 +0,0 @@
package filter
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCompileFilter(t *testing.T) {
f, err := CompileFilter([]string{})
assert.NoError(t, err)
assert.Nil(t, f)
f, err = CompileFilter([]string{"cpu"})
assert.NoError(t, err)
assert.True(t, f.Match("cpu"))
assert.False(t, f.Match("cpu0"))
assert.False(t, f.Match("mem"))
f, err = CompileFilter([]string{"cpu*"})
assert.NoError(t, err)
assert.True(t, f.Match("cpu"))
assert.True(t, f.Match("cpu0"))
assert.False(t, f.Match("mem"))
f, err = CompileFilter([]string{"cpu", "mem"})
assert.NoError(t, err)
assert.True(t, f.Match("cpu"))
assert.False(t, f.Match("cpu0"))
assert.True(t, f.Match("mem"))
f, err = CompileFilter([]string{"cpu", "mem", "net*"})
assert.NoError(t, err)
assert.True(t, f.Match("cpu"))
assert.False(t, f.Match("cpu0"))
assert.True(t, f.Match("mem"))
assert.True(t, f.Match("network"))
}
var benchbool bool
func BenchmarkFilterSingleNoGlobFalse(b *testing.B) {
f, _ := CompileFilter([]string{"cpu"})
var tmp bool
for n := 0; n < b.N; n++ {
tmp = f.Match("network")
}
benchbool = tmp
}
func BenchmarkFilterSingleNoGlobTrue(b *testing.B) {
f, _ := CompileFilter([]string{"cpu"})
var tmp bool
for n := 0; n < b.N; n++ {
tmp = f.Match("cpu")
}
benchbool = tmp
}
func BenchmarkFilter(b *testing.B) {
f, _ := CompileFilter([]string{"cpu", "mem", "net*"})
var tmp bool
for n := 0; n < b.N; n++ {
tmp = f.Match("network")
}
benchbool = tmp
}
func BenchmarkFilterNoGlob(b *testing.B) {
f, _ := CompileFilter([]string{"cpu", "mem", "net"})
var tmp bool
for n := 0; n < b.N; n++ {
tmp = f.Match("net")
}
benchbool = tmp
}
func BenchmarkFilter2(b *testing.B) {
f, _ := CompileFilter([]string{"aa", "bb", "c", "ad", "ar", "at", "aq",
"aw", "az", "axxx", "ab", "cpu", "mem", "net*"})
var tmp bool
for n := 0; n < b.N; n++ {
tmp = f.Match("network")
}
benchbool = tmp
}
func BenchmarkFilter2NoGlob(b *testing.B) {
f, _ := CompileFilter([]string{"aa", "bb", "c", "ad", "ar", "at", "aq",
"aw", "az", "axxx", "ab", "cpu", "mem", "net"})
var tmp bool
for n := 0; n < b.N; n++ {
tmp = f.Match("net")
}
benchbool = tmp
}

View File

@@ -1,49 +0,0 @@
package aws
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/session"
)
type CredentialConfig struct {
Region string
AccessKey string
SecretKey string
RoleARN string
Profile string
Filename string
Token string
}
func (c *CredentialConfig) Credentials() client.ConfigProvider {
if c.RoleARN != "" {
return c.assumeCredentials()
} else {
return c.rootCredentials()
}
}
func (c *CredentialConfig) rootCredentials() client.ConfigProvider {
config := &aws.Config{
Region: aws.String(c.Region),
}
if c.AccessKey != "" || c.SecretKey != "" {
config.Credentials = credentials.NewStaticCredentials(c.AccessKey, c.SecretKey, c.Token)
} else if c.Profile != "" || c.Filename != "" {
config.Credentials = credentials.NewSharedCredentials(c.Filename, c.Profile)
}
return session.New(config)
}
func (c *CredentialConfig) assumeCredentials() client.ConfigProvider {
rootCredentials := c.rootCredentials()
config := &aws.Config{
Region: aws.String(c.Region),
}
config.Credentials = stscreds.NewCredentials(rootCredentials, c.RoleARN)
return session.New(config)
}

View File

@@ -58,6 +58,7 @@ func NewConfig() *Config {
Interval: internal.Duration{Duration: 10 * time.Second}, Interval: internal.Duration{Duration: 10 * time.Second},
RoundInterval: true, RoundInterval: true,
FlushInterval: internal.Duration{Duration: 10 * time.Second}, FlushInterval: internal.Duration{Duration: 10 * time.Second},
FlushJitter: internal.Duration{Duration: 5 * time.Second},
}, },
Tags: make(map[string]string), Tags: make(map[string]string),
@@ -77,14 +78,6 @@ type AgentConfig struct {
// ie, if Interval=10s then always collect on :00, :10, :20, etc. // ie, if Interval=10s then always collect on :00, :10, :20, etc.
RoundInterval bool RoundInterval bool
// By default, precision will be set to the same timestamp order as the
// collection interval, with the maximum being 1s.
// ie, when interval = "10s", precision will be "1s"
// when interval = "250ms", precision will be "1ms"
// Precision will NOT be used for service inputs. It is up to each individual
// service input to set the timestamp at the appropriate precision.
Precision internal.Duration
// CollectionJitter is used to jitter the collection by a random amount. // CollectionJitter is used to jitter the collection by a random amount.
// Each plugin will sleep for a random time within jitter before collecting. // Each plugin will sleep for a random time within jitter before collecting.
// This can be used to avoid many plugins querying things like sysfs at the // This can be used to avoid many plugins querying things like sysfs at the
@@ -116,10 +109,11 @@ type AgentConfig struct {
// does _not_ deactivate FlushInterval. // does _not_ deactivate FlushInterval.
FlushBufferWhenFull bool FlushBufferWhenFull bool
// TODO(cam): Remove UTC and parameter, they are no longer // TODO(cam): Remove UTC and Precision parameters, 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 // compatability
UTC bool `toml:"utc"` UTC bool `toml:"utc"`
Precision string
// Debug is the option for running in debug mode // Debug is the option for running in debug mode
Debug bool Debug bool
@@ -216,11 +210,6 @@ var header = `# Telegraf Configuration
## ie, a jitter of 5s and interval 10s means flushes will happen every 10-15s ## ie, a jitter of 5s and interval 10s means flushes will happen every 10-15s
flush_jitter = "0s" flush_jitter = "0s"
## By default, precision will be set to the same timestamp order as the
## collection interval, with the maximum being 1s.
## Precision will NOT be used for service inputs, such as logparser and statsd.
## Valid values are "Nns", "Nus" (or "Nµs"), "Nms", "Ns".
precision = ""
## Run telegraf in debug mode ## Run telegraf in debug mode
debug = false debug = false
## Run telegraf in quiet mode ## Run telegraf in quiet mode
@@ -368,7 +357,7 @@ func printConfig(name string, p printer, op string, commented bool) {
fmt.Print("\n") fmt.Print("\n")
continue continue
} }
fmt.Print(strings.TrimRight(comment+line, " ") + "\n") fmt.Print(comment + line + "\n")
} }
} }
} }

View File

@@ -1,37 +0,0 @@
package errchan
import (
"fmt"
"strings"
)
type ErrChan struct {
C chan error
}
// New returns an error channel of max length 'n'
// errors can be sent to the ErrChan.C channel, and will be returned when
// ErrChan.Error() is called.
func New(n int) *ErrChan {
return &ErrChan{
C: make(chan error, n),
}
}
// Error closes the ErrChan.C channel and returns an error if there are any
// non-nil errors, otherwise returns nil.
func (e *ErrChan) Error() error {
close(e.C)
var out string
for err := range e.C {
if err != nil {
out += "[" + err.Error() + "], "
}
}
if out != "" {
return fmt.Errorf("Errors encountered: " + strings.TrimRight(out, ", "))
}
return nil
}

View File

@@ -10,10 +10,8 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"math/big"
"os" "os"
"os/exec" "os/exec"
"strconv"
"strings" "strings"
"time" "time"
"unicode" "unicode"
@@ -34,25 +32,12 @@ type Duration struct {
// UnmarshalTOML parses the duration from the TOML config file // UnmarshalTOML parses the duration from the TOML config file
func (d *Duration) UnmarshalTOML(b []byte) error { func (d *Duration) UnmarshalTOML(b []byte) error {
var err error dur, err := time.ParseDuration(string(b[1 : len(b)-1]))
// Parse string duration, ie, "1s" if err != nil {
d.Duration, err = time.ParseDuration(string(b[1 : len(b)-1])) return err
if err == nil {
return nil
} }
// First try parsing as integer seconds d.Duration = dur
sI, err := strconv.ParseInt(string(b), 10, 64)
if err == nil {
d.Duration = time.Second * time.Duration(sI)
return nil
}
// Second try parsing as float seconds
sF, err := strconv.ParseFloat(string(b), 64)
if err == nil {
d.Duration = time.Second * time.Duration(sF)
return nil
}
return nil return nil
} }
@@ -206,27 +191,3 @@ func WaitTimeout(c *exec.Cmd, timeout time.Duration) error {
return TimeoutErr return TimeoutErr
} }
} }
// RandomSleep will sleep for a random amount of time up to max.
// If the shutdown channel is closed, it will return before it has finished
// sleeping.
func RandomSleep(max time.Duration, shutdown chan struct{}) {
if max == 0 {
return
}
maxSleep := big.NewInt(max.Nanoseconds())
var sleepns int64
if j, err := rand.Int(rand.Reader, maxSleep); err == nil {
sleepns = j.Int64()
}
t := time.NewTimer(time.Nanosecond * time.Duration(sleepns))
select {
case <-t.C:
return
case <-shutdown:
t.Stop()
return
}
}

View File

@@ -106,28 +106,3 @@ func TestRunError(t *testing.T) {
assert.Error(t, err) assert.Error(t, err)
} }
func TestRandomSleep(t *testing.T) {
// test that zero max returns immediately
s := time.Now()
RandomSleep(time.Duration(0), make(chan struct{}))
elapsed := time.Since(s)
assert.True(t, elapsed < time.Millisecond)
// test that max sleep is respected
s = time.Now()
RandomSleep(time.Millisecond*50, make(chan struct{}))
elapsed = time.Since(s)
assert.True(t, elapsed < time.Millisecond*50)
// test that shutdown is respected
s = time.Now()
shutdown := make(chan struct{})
go func() {
time.Sleep(time.Millisecond * 100)
close(shutdown)
}()
RandomSleep(time.Second, shutdown)
elapsed = time.Since(s)
assert.True(t, elapsed < time.Millisecond*150)
}

View File

@@ -1,59 +0,0 @@
package limiter
import (
"sync"
"time"
)
// NewRateLimiter returns a rate limiter that will will emit from the C
// channel only 'n' times every 'rate' seconds.
func NewRateLimiter(n int, rate time.Duration) *rateLimiter {
r := &rateLimiter{
C: make(chan bool),
rate: rate,
n: n,
shutdown: make(chan bool),
}
r.wg.Add(1)
go r.limiter()
return r
}
type rateLimiter struct {
C chan bool
rate time.Duration
n int
shutdown chan bool
wg sync.WaitGroup
}
func (r *rateLimiter) Stop() {
close(r.shutdown)
r.wg.Wait()
close(r.C)
}
func (r *rateLimiter) limiter() {
defer r.wg.Done()
ticker := time.NewTicker(r.rate)
defer ticker.Stop()
counter := 0
for {
select {
case <-r.shutdown:
return
case <-ticker.C:
counter = 0
default:
if counter < r.n {
select {
case r.C <- true:
counter++
case <-r.shutdown:
return
}
}
}
}
}

View File

@@ -1,54 +0,0 @@
package limiter
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestRateLimiter(t *testing.T) {
r := NewRateLimiter(5, time.Second)
ticker := time.NewTicker(time.Millisecond * 75)
// test that we can only get 5 receives from the rate limiter
counter := 0
outer:
for {
select {
case <-r.C:
counter++
case <-ticker.C:
break outer
}
}
assert.Equal(t, 5, counter)
r.Stop()
// verify that the Stop function closes the channel.
_, ok := <-r.C
assert.False(t, ok)
}
func TestRateLimiterMultipleIterations(t *testing.T) {
r := NewRateLimiter(5, time.Millisecond*50)
ticker := time.NewTicker(time.Millisecond * 250)
// test that we can get 15 receives from the rate limiter
counter := 0
outer:
for {
select {
case <-ticker.C:
break outer
case <-r.C:
counter++
}
}
assert.True(t, counter > 10)
r.Stop()
// verify that the Stop function closes the channel.
_, ok := <-r.C
assert.False(t, ok)
}

View File

@@ -2,79 +2,81 @@ package internal_models
import ( import (
"fmt" "fmt"
"strings"
"github.com/gobwas/glob"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/filter"
) )
// TagFilter is the name of a tag, and the values on which to filter // TagFilter is the name of a tag, and the values on which to filter
type TagFilter struct { type TagFilter struct {
Name string Name string
Filter []string Filter []string
filter filter.Filter filter glob.Glob
} }
// Filter containing drop/pass and tagdrop/tagpass rules // Filter containing drop/pass and tagdrop/tagpass rules
type Filter struct { type Filter struct {
NameDrop []string NameDrop []string
nameDrop filter.Filter nameDrop glob.Glob
NamePass []string NamePass []string
namePass filter.Filter namePass glob.Glob
FieldDrop []string FieldDrop []string
fieldDrop filter.Filter fieldDrop glob.Glob
FieldPass []string FieldPass []string
fieldPass filter.Filter fieldPass glob.Glob
TagDrop []TagFilter TagDrop []TagFilter
TagPass []TagFilter TagPass []TagFilter
TagExclude []string TagExclude []string
tagExclude filter.Filter tagExclude glob.Glob
TagInclude []string TagInclude []string
tagInclude filter.Filter tagInclude glob.Glob
IsActive bool IsActive bool
} }
// Compile all Filter lists into filter.Filter objects. // Compile all Filter lists into glob.Glob objects.
func (f *Filter) CompileFilter() error { func (f *Filter) CompileFilter() error {
var err error var err error
f.nameDrop, err = filter.CompileFilter(f.NameDrop) f.nameDrop, err = compileFilter(f.NameDrop)
if err != nil { if err != nil {
return fmt.Errorf("Error compiling 'namedrop', %s", err) return fmt.Errorf("Error compiling 'namedrop', %s", err)
} }
f.namePass, err = filter.CompileFilter(f.NamePass) f.namePass, err = compileFilter(f.NamePass)
if err != nil { if err != nil {
return fmt.Errorf("Error compiling 'namepass', %s", err) return fmt.Errorf("Error compiling 'namepass', %s", err)
} }
f.fieldDrop, err = filter.CompileFilter(f.FieldDrop) f.fieldDrop, err = compileFilter(f.FieldDrop)
if err != nil { if err != nil {
return fmt.Errorf("Error compiling 'fielddrop', %s", err) return fmt.Errorf("Error compiling 'fielddrop', %s", err)
} }
f.fieldPass, err = filter.CompileFilter(f.FieldPass) f.fieldPass, err = compileFilter(f.FieldPass)
if err != nil { if err != nil {
return fmt.Errorf("Error compiling 'fieldpass', %s", err) return fmt.Errorf("Error compiling 'fieldpass', %s", err)
} }
f.tagExclude, err = filter.CompileFilter(f.TagExclude) f.tagExclude, err = compileFilter(f.TagExclude)
if err != nil { if err != nil {
return fmt.Errorf("Error compiling 'tagexclude', %s", err) return fmt.Errorf("Error compiling 'tagexclude', %s", err)
} }
f.tagInclude, err = filter.CompileFilter(f.TagInclude) f.tagInclude, err = compileFilter(f.TagInclude)
if err != nil { if err != nil {
return fmt.Errorf("Error compiling 'taginclude', %s", err) return fmt.Errorf("Error compiling 'taginclude', %s", err)
} }
for i, _ := range f.TagDrop { for i, _ := range f.TagDrop {
f.TagDrop[i].filter, err = filter.CompileFilter(f.TagDrop[i].Filter) f.TagDrop[i].filter, err = compileFilter(f.TagDrop[i].Filter)
if err != nil { if err != nil {
return fmt.Errorf("Error compiling 'tagdrop', %s", err) return fmt.Errorf("Error compiling 'tagdrop', %s", err)
} }
} }
for i, _ := range f.TagPass { for i, _ := range f.TagPass {
f.TagPass[i].filter, err = filter.CompileFilter(f.TagPass[i].Filter) f.TagPass[i].filter, err = compileFilter(f.TagPass[i].Filter)
if err != nil { if err != nil {
return fmt.Errorf("Error compiling 'tagpass', %s", err) return fmt.Errorf("Error compiling 'tagpass', %s", err)
} }
@@ -82,6 +84,20 @@ func (f *Filter) CompileFilter() error {
return nil return nil
} }
func compileFilter(filter []string) (glob.Glob, error) {
if len(filter) == 0 {
return nil, nil
}
var g glob.Glob
var err error
if len(filter) == 1 {
g, err = glob.Compile(filter[0])
} else {
g, err = glob.Compile("{" + strings.Join(filter, ",") + "}")
}
return g, err
}
func (f *Filter) ShouldMetricPass(metric telegraf.Metric) bool { func (f *Filter) ShouldMetricPass(metric telegraf.Metric) bool {
if f.ShouldNamePass(metric.Name()) && f.ShouldTagsPass(metric.Tags()) { if f.ShouldNamePass(metric.Name()) && f.ShouldTagsPass(metric.Tags()) {
return true return true

View File

@@ -253,6 +253,51 @@ func TestFilter_TagDrop(t *testing.T) {
} }
} }
func TestFilter_CompileFilterError(t *testing.T) {
f := Filter{
NameDrop: []string{"", ""},
}
assert.Error(t, f.CompileFilter())
f = Filter{
NamePass: []string{"", ""},
}
assert.Error(t, f.CompileFilter())
f = Filter{
FieldDrop: []string{"", ""},
}
assert.Error(t, f.CompileFilter())
f = Filter{
FieldPass: []string{"", ""},
}
assert.Error(t, f.CompileFilter())
f = Filter{
TagExclude: []string{"", ""},
}
assert.Error(t, f.CompileFilter())
f = Filter{
TagInclude: []string{"", ""},
}
assert.Error(t, f.CompileFilter())
filters := []TagFilter{
TagFilter{
Name: "cpu",
Filter: []string{"{foobar}"},
}}
f = Filter{
TagDrop: filters,
}
require.Error(t, f.CompileFilter())
filters = []TagFilter{
TagFilter{
Name: "cpu",
Filter: []string{"{foobar}"},
}}
f = Filter{
TagPass: filters,
}
require.Error(t, f.CompileFilter())
}
func TestFilter_ShouldMetricsPass(t *testing.T) { func TestFilter_ShouldMetricsPass(t *testing.T) {
m := testutil.TestMetric(1, "testmetric") m := testutil.TestMetric(1, "testmetric")
f := Filter{ f := Filter{

View File

@@ -45,9 +45,14 @@ func NewMetric(
name string, name string,
tags map[string]string, tags map[string]string,
fields map[string]interface{}, fields map[string]interface{},
t time.Time, t ...time.Time,
) (Metric, error) { ) (Metric, error) {
pt, err := client.NewPoint(name, tags, fields, t) var T time.Time
if len(t) > 0 {
T = t[0]
}
pt, err := client.NewPoint(name, tags, fields, T)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -51,6 +51,23 @@ func TestNewMetricString(t *testing.T) {
assert.Equal(t, lineProtoPrecision, m.PrecisionString("s")) assert.Equal(t, lineProtoPrecision, m.PrecisionString("s"))
} }
func TestNewMetricStringNoTime(t *testing.T) {
tags := map[string]string{
"host": "localhost",
}
fields := map[string]interface{}{
"usage_idle": float64(99),
}
m, err := NewMetric("cpu", tags, fields)
assert.NoError(t, err)
lineProto := fmt.Sprintf("cpu,host=localhost usage_idle=99")
assert.Equal(t, lineProto, m.String())
lineProtoPrecision := fmt.Sprintf("cpu,host=localhost usage_idle=99")
assert.Equal(t, lineProtoPrecision, m.PrecisionString("s"))
}
func TestNewMetricFailNaN(t *testing.T) { func TestNewMetricFailNaN(t *testing.T) {
now := time.Now() now := time.Now()

View File

@@ -5,11 +5,7 @@ import (
_ "github.com/influxdata/telegraf/plugins/inputs/apache" _ "github.com/influxdata/telegraf/plugins/inputs/apache"
_ "github.com/influxdata/telegraf/plugins/inputs/bcache" _ "github.com/influxdata/telegraf/plugins/inputs/bcache"
_ "github.com/influxdata/telegraf/plugins/inputs/cassandra" _ "github.com/influxdata/telegraf/plugins/inputs/cassandra"
_ "github.com/influxdata/telegraf/plugins/inputs/ceph"
_ "github.com/influxdata/telegraf/plugins/inputs/chrony"
_ "github.com/influxdata/telegraf/plugins/inputs/cloudwatch" _ "github.com/influxdata/telegraf/plugins/inputs/cloudwatch"
_ "github.com/influxdata/telegraf/plugins/inputs/conntrack"
_ "github.com/influxdata/telegraf/plugins/inputs/consul"
_ "github.com/influxdata/telegraf/plugins/inputs/couchbase" _ "github.com/influxdata/telegraf/plugins/inputs/couchbase"
_ "github.com/influxdata/telegraf/plugins/inputs/couchdb" _ "github.com/influxdata/telegraf/plugins/inputs/couchdb"
_ "github.com/influxdata/telegraf/plugins/inputs/disque" _ "github.com/influxdata/telegraf/plugins/inputs/disque"
@@ -20,16 +16,15 @@ import (
_ "github.com/influxdata/telegraf/plugins/inputs/exec" _ "github.com/influxdata/telegraf/plugins/inputs/exec"
_ "github.com/influxdata/telegraf/plugins/inputs/filestat" _ "github.com/influxdata/telegraf/plugins/inputs/filestat"
_ "github.com/influxdata/telegraf/plugins/inputs/github_webhooks" _ "github.com/influxdata/telegraf/plugins/inputs/github_webhooks"
_ "github.com/influxdata/telegraf/plugins/inputs/graylog"
_ "github.com/influxdata/telegraf/plugins/inputs/haproxy" _ "github.com/influxdata/telegraf/plugins/inputs/haproxy"
_ "github.com/influxdata/telegraf/plugins/inputs/http_response" _ "github.com/influxdata/telegraf/plugins/inputs/http_response"
_ "github.com/influxdata/telegraf/plugins/inputs/httpjson" _ "github.com/influxdata/telegraf/plugins/inputs/httpjson"
_ "github.com/influxdata/telegraf/plugins/inputs/igloo"
_ "github.com/influxdata/telegraf/plugins/inputs/influxdb" _ "github.com/influxdata/telegraf/plugins/inputs/influxdb"
_ "github.com/influxdata/telegraf/plugins/inputs/ipmi_sensor" _ "github.com/influxdata/telegraf/plugins/inputs/ipmi_sensor"
_ "github.com/influxdata/telegraf/plugins/inputs/jolokia" _ "github.com/influxdata/telegraf/plugins/inputs/jolokia"
_ "github.com/influxdata/telegraf/plugins/inputs/kafka_consumer" _ "github.com/influxdata/telegraf/plugins/inputs/kafka_consumer"
_ "github.com/influxdata/telegraf/plugins/inputs/leofs" _ "github.com/influxdata/telegraf/plugins/inputs/leofs"
_ "github.com/influxdata/telegraf/plugins/inputs/logparser"
_ "github.com/influxdata/telegraf/plugins/inputs/lustre2" _ "github.com/influxdata/telegraf/plugins/inputs/lustre2"
_ "github.com/influxdata/telegraf/plugins/inputs/mailchimp" _ "github.com/influxdata/telegraf/plugins/inputs/mailchimp"
_ "github.com/influxdata/telegraf/plugins/inputs/memcached" _ "github.com/influxdata/telegraf/plugins/inputs/memcached"
@@ -41,7 +36,6 @@ import (
_ "github.com/influxdata/telegraf/plugins/inputs/net_response" _ "github.com/influxdata/telegraf/plugins/inputs/net_response"
_ "github.com/influxdata/telegraf/plugins/inputs/nginx" _ "github.com/influxdata/telegraf/plugins/inputs/nginx"
_ "github.com/influxdata/telegraf/plugins/inputs/nsq" _ "github.com/influxdata/telegraf/plugins/inputs/nsq"
_ "github.com/influxdata/telegraf/plugins/inputs/nstat"
_ "github.com/influxdata/telegraf/plugins/inputs/ntpq" _ "github.com/influxdata/telegraf/plugins/inputs/ntpq"
_ "github.com/influxdata/telegraf/plugins/inputs/passenger" _ "github.com/influxdata/telegraf/plugins/inputs/passenger"
_ "github.com/influxdata/telegraf/plugins/inputs/phpfpm" _ "github.com/influxdata/telegraf/plugins/inputs/phpfpm"
@@ -57,7 +51,6 @@ import (
_ "github.com/influxdata/telegraf/plugins/inputs/redis" _ "github.com/influxdata/telegraf/plugins/inputs/redis"
_ "github.com/influxdata/telegraf/plugins/inputs/rethinkdb" _ "github.com/influxdata/telegraf/plugins/inputs/rethinkdb"
_ "github.com/influxdata/telegraf/plugins/inputs/riak" _ "github.com/influxdata/telegraf/plugins/inputs/riak"
_ "github.com/influxdata/telegraf/plugins/inputs/rollbar_webhooks"
_ "github.com/influxdata/telegraf/plugins/inputs/sensors" _ "github.com/influxdata/telegraf/plugins/inputs/sensors"
_ "github.com/influxdata/telegraf/plugins/inputs/snmp" _ "github.com/influxdata/telegraf/plugins/inputs/snmp"
_ "github.com/influxdata/telegraf/plugins/inputs/sqlserver" _ "github.com/influxdata/telegraf/plugins/inputs/sqlserver"
@@ -69,7 +62,6 @@ import (
_ "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"
_ "github.com/influxdata/telegraf/plugins/inputs/udp_listener" _ "github.com/influxdata/telegraf/plugins/inputs/udp_listener"
_ "github.com/influxdata/telegraf/plugins/inputs/varnish"
_ "github.com/influxdata/telegraf/plugins/inputs/win_perf_counters" _ "github.com/influxdata/telegraf/plugins/inputs/win_perf_counters"
_ "github.com/influxdata/telegraf/plugins/inputs/zfs" _ "github.com/influxdata/telegraf/plugins/inputs/zfs"
_ "github.com/influxdata/telegraf/plugins/inputs/zookeeper" _ "github.com/influxdata/telegraf/plugins/inputs/zookeeper"

View File

@@ -1,7 +1,7 @@
# Telegraf plugin: Apache # Telegraf plugin: Apache
#### Plugin arguments: #### Plugin arguments:
- **urls** []string: List of apache-status URLs to collect from. Default is "http://localhost/server-status?auto". - **urls** []string: List of apache-status URLs to collect from.
#### Description #### Description

View File

@@ -21,7 +21,6 @@ type Apache struct {
var sampleConfig = ` var sampleConfig = `
## An array of Apache status URI to gather stats. ## An array of Apache status URI to gather stats.
## Default is "http://localhost/server-status?auto".
urls = ["http://localhost/server-status?auto"] urls = ["http://localhost/server-status?auto"]
` `
@@ -34,10 +33,6 @@ func (n *Apache) Description() string {
} }
func (n *Apache) Gather(acc telegraf.Accumulator) error { func (n *Apache) Gather(acc telegraf.Accumulator) error {
if len(n.Urls) == 0 {
n.Urls = []string{"http://localhost/server-status?auto"}
}
var wg sync.WaitGroup var wg sync.WaitGroup
var outerr error var outerr error

View File

@@ -1,109 +0,0 @@
# Ceph Storage Input Plugin
Collects performance metrics from the MON and OSD nodes in a Ceph storage cluster.
The plugin works by scanning the configured SocketDir for OSD and MON socket files. When it finds
a MON socket, it runs **ceph --admin-daemon $file perfcounters_dump**. For OSDs it runs **ceph --admin-daemon $file perf dump**
The resulting JSON is parsed and grouped into collections, based on top-level key. Top-level keys are
used as collection tags, and all sub-keys are flattened. For example:
```
{
"paxos": {
"refresh": 9363435,
"refresh_latency": {
"avgcount": 9363435,
"sum": 5378.794002000
}
}
}
```
Would be parsed into the following metrics, all of which would be tagged with collection=paxos:
- refresh = 9363435
- refresh_latency.avgcount: 9363435
- refresh_latency.sum: 5378.794002000
### Configuration:
```
# Collects performance metrics from the MON and OSD nodes in a Ceph storage cluster.
[[inputs.ceph]]
## All configuration values are optional, defaults are shown below
## location of ceph binary
ceph_binary = "/usr/bin/ceph"
## directory in which to look for socket files
socket_dir = "/var/run/ceph"
## prefix of MON and OSD socket files, used to determine socket type
mon_prefix = "ceph-mon"
osd_prefix = "ceph-osd"
## suffix used to identify socket files
socket_suffix = "asok"
```
### Measurements & Fields:
All fields are collected under the **ceph** measurement and stored as float64s. For a full list of fields, see the sample perf dumps in ceph_test.go.
### Tags:
All measurements will have the following tags:
- type: either 'osd' or 'mon' to indicate which type of node was queried
- id: a unique string identifier, parsed from the socket file name for the node
- collection: the top-level key under which these fields were reported. Possible values are:
- for MON nodes:
- cluster
- leveldb
- mon
- paxos
- throttle-mon_client_bytes
- throttle-mon_daemon_bytes
- throttle-msgr_dispatch_throttler-mon
- for OSD nodes:
- WBThrottle
- filestore
- leveldb
- mutex-FileJournal::completions_lock
- mutex-FileJournal::finisher_lock
- mutex-FileJournal::write_lock
- mutex-FileJournal::writeq_lock
- mutex-JOS::ApplyManager::apply_lock
- mutex-JOS::ApplyManager::com_lock
- mutex-JOS::SubmitManager::lock
- mutex-WBThrottle::lock
- objecter
- osd
- recoverystate_perf
- throttle-filestore_bytes
- throttle-filestore_ops
- throttle-msgr_dispatch_throttler-client
- throttle-msgr_dispatch_throttler-cluster
- throttle-msgr_dispatch_throttler-hb_back_server
- throttle-msgr_dispatch_throttler-hb_front_serve
- throttle-msgr_dispatch_throttler-hbclient
- throttle-msgr_dispatch_throttler-ms_objecter
- throttle-objecter_bytes
- throttle-objecter_ops
- throttle-osd_client_bytes
- throttle-osd_client_messages
### Example Output:
<pre>
telegraf -test -config /etc/telegraf/telegraf.conf -config-directory /etc/telegraf/telegraf.d -input-filter ceph
* Plugin: ceph, Collection 1
> ceph,collection=paxos, id=node-2,role=openstack,type=mon accept_timeout=0,begin=14931264,begin_bytes.avgcount=14931264,begin_bytes.sum=180309683362,begin_keys.avgcount=0,begin_keys.sum=0,begin_latency.avgcount=14931264,begin_latency.sum=9293.29589,collect=1,collect_bytes.avgcount=1,collect_bytes.sum=24,collect_keys.avgcount=1,collect_keys.sum=1,collect_latency.avgcount=1,collect_latency.sum=0.00028,collect_timeout=0,collect_uncommitted=0,commit=14931264,commit_bytes.avgcount=0,commit_bytes.sum=0,commit_keys.avgcount=0,commit_keys.sum=0,commit_latency.avgcount=0,commit_latency.sum=0,lease_ack_timeout=0,lease_timeout=0,new_pn=0,new_pn_latency.avgcount=0,new_pn_latency.sum=0,refresh=14931264,refresh_latency.avgcount=14931264,refresh_latency.sum=8706.98498,restart=4,share_state=0,share_state_bytes.avgcount=0,share_state_bytes.sum=0,share_state_keys.avgcount=0,share_state_keys.sum=0,start_leader=0,start_peon=1,store_state=14931264,store_state_bytes.avgcount=14931264,store_state_bytes.sum=353119959211,store_state_keys.avgcount=14931264,store_state_keys.sum=289807523,store_state_latency.avgcount=14931264,store_state_latency.sum=10952.835724 1462821234814535148
> ceph,collection=throttle-mon_client_bytes,id=node-2,type=mon get=1413017,get_or_fail_fail=0,get_or_fail_success=0,get_sum=71211705,max=104857600,put=1413013,put_sum=71211459,take=0,take_sum=0,val=246,wait.avgcount=0,wait.sum=0 1462821234814737219
> ceph,collection=throttle-mon_daemon_bytes,id=node-2,type=mon get=4058121,get_or_fail_fail=0,get_or_fail_success=0,get_sum=6027348117,max=419430400,put=4058121,put_sum=6027348117,take=0,take_sum=0,val=0,wait.avgcount=0,wait.sum=0 1462821234814815661
> ceph,collection=throttle-msgr_dispatch_throttler-mon,id=node-2,type=mon get=54276277,get_or_fail_fail=0,get_or_fail_success=0,get_sum=370232877040,max=104857600,put=54276277,put_sum=370232877040,take=0,take_sum=0,val=0,wait.avgcount=0,wait.sum=0 1462821234814872064
</pre>

View File

@@ -1,249 +0,0 @@
package ceph
import (
"bytes"
"encoding/json"
"fmt"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
"io/ioutil"
"log"
"os/exec"
"path/filepath"
"strings"
)
const (
measurement = "ceph"
typeMon = "monitor"
typeOsd = "osd"
osdPrefix = "ceph-osd"
monPrefix = "ceph-mon"
sockSuffix = "asok"
)
type Ceph struct {
CephBinary string
OsdPrefix string
MonPrefix string
SocketDir string
SocketSuffix string
}
func (c *Ceph) setDefaults() {
if c.CephBinary == "" {
c.CephBinary = "/usr/bin/ceph"
}
if c.OsdPrefix == "" {
c.OsdPrefix = osdPrefix
}
if c.MonPrefix == "" {
c.MonPrefix = monPrefix
}
if c.SocketDir == "" {
c.SocketDir = "/var/run/ceph"
}
if c.SocketSuffix == "" {
c.SocketSuffix = sockSuffix
}
}
func (c *Ceph) Description() string {
return "Collects performance metrics from the MON and OSD nodes in a Ceph storage cluster."
}
var sampleConfig = `
## All configuration values are optional, defaults are shown below
## location of ceph binary
ceph_binary = "/usr/bin/ceph"
## directory in which to look for socket files
socket_dir = "/var/run/ceph"
## prefix of MON and OSD socket files, used to determine socket type
mon_prefix = "ceph-mon"
osd_prefix = "ceph-osd"
## suffix used to identify socket files
socket_suffix = "asok"
`
func (c *Ceph) SampleConfig() string {
return sampleConfig
}
func (c *Ceph) Gather(acc telegraf.Accumulator) error {
c.setDefaults()
sockets, err := findSockets(c)
if err != nil {
return fmt.Errorf("failed to find sockets at path '%s': %v", c.SocketDir, err)
}
for _, s := range sockets {
dump, err := perfDump(c.CephBinary, s)
if err != nil {
log.Printf("error reading from socket '%s': %v", s.socket, err)
continue
}
data, err := parseDump(dump)
if err != nil {
log.Printf("error parsing dump from socket '%s': %v", s.socket, err)
continue
}
for tag, metrics := range *data {
acc.AddFields(measurement,
map[string]interface{}(metrics),
map[string]string{"type": s.sockType, "id": s.sockId, "collection": tag})
}
}
return nil
}
func init() {
inputs.Add(measurement, func() telegraf.Input { return &Ceph{} })
}
var perfDump = func(binary string, socket *socket) (string, error) {
cmdArgs := []string{"--admin-daemon", socket.socket}
if socket.sockType == typeOsd {
cmdArgs = append(cmdArgs, "perf", "dump")
} else if socket.sockType == typeMon {
cmdArgs = append(cmdArgs, "perfcounters_dump")
} else {
return "", fmt.Errorf("ignoring unknown socket type: %s", socket.sockType)
}
cmd := exec.Command(binary, cmdArgs...)
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return "", fmt.Errorf("error running ceph dump: %s", err)
}
return out.String(), nil
}
var findSockets = func(c *Ceph) ([]*socket, error) {
listing, err := ioutil.ReadDir(c.SocketDir)
if err != nil {
return []*socket{}, fmt.Errorf("Failed to read socket directory '%s': %v", c.SocketDir, err)
}
sockets := make([]*socket, 0, len(listing))
for _, info := range listing {
f := info.Name()
var sockType string
var sockPrefix string
if strings.HasPrefix(f, c.MonPrefix) {
sockType = typeMon
sockPrefix = monPrefix
}
if strings.HasPrefix(f, c.OsdPrefix) {
sockType = typeOsd
sockPrefix = osdPrefix
}
if sockType == typeOsd || sockType == typeMon {
path := filepath.Join(c.SocketDir, f)
sockets = append(sockets, &socket{parseSockId(f, sockPrefix, c.SocketSuffix), sockType, path})
}
}
return sockets, nil
}
func parseSockId(fname, prefix, suffix string) string {
s := fname
s = strings.TrimPrefix(s, prefix)
s = strings.TrimSuffix(s, suffix)
s = strings.Trim(s, ".-_")
return s
}
type socket struct {
sockId string
sockType string
socket string
}
type metric struct {
pathStack []string // lifo stack of name components
value float64
}
// Pops names of pathStack to build the flattened name for a metric
func (m *metric) name() string {
buf := bytes.Buffer{}
for i := len(m.pathStack) - 1; i >= 0; i-- {
if buf.Len() > 0 {
buf.WriteString(".")
}
buf.WriteString(m.pathStack[i])
}
return buf.String()
}
type metricMap map[string]interface{}
type taggedMetricMap map[string]metricMap
// Parses a raw JSON string into a taggedMetricMap
// Delegates the actual parsing to newTaggedMetricMap(..)
func parseDump(dump string) (*taggedMetricMap, error) {
data := make(map[string]interface{})
err := json.Unmarshal([]byte(dump), &data)
if err != nil {
return nil, fmt.Errorf("failed to parse json: '%s': %v", dump, err)
}
tmm := newTaggedMetricMap(data)
if err != nil {
return nil, fmt.Errorf("failed to tag dataset: '%v': %v", tmm, err)
}
return tmm, nil
}
// Builds a TaggedMetricMap out of a generic string map.
// The top-level key is used as a tag and all sub-keys are flattened into metrics
func newTaggedMetricMap(data map[string]interface{}) *taggedMetricMap {
tmm := make(taggedMetricMap)
for tag, datapoints := range data {
mm := make(metricMap)
for _, m := range flatten(datapoints) {
mm[m.name()] = m.value
}
tmm[tag] = mm
}
return &tmm
}
// Recursively flattens any k-v hierarchy present in data.
// Nested keys are flattened into ordered slices associated with a metric value.
// The key slices are treated as stacks, and are expected to be reversed and concatenated
// when passed as metrics to the accumulator. (see (*metric).name())
func flatten(data interface{}) []*metric {
var metrics []*metric
switch val := data.(type) {
case float64:
metrics = []*metric{&metric{make([]string, 0, 1), val}}
case map[string]interface{}:
metrics = make([]*metric, 0, len(val))
for k, v := range val {
for _, m := range flatten(v) {
m.pathStack = append(m.pathStack, k)
metrics = append(metrics, m)
}
}
default:
log.Printf("Ignoring unexpected type '%T' for value %v", val, val)
}
return metrics
}

View File

@@ -1,682 +0,0 @@
package ceph
import (
"fmt"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/assert"
"io/ioutil"
"os"
"path"
"strconv"
"strings"
"testing"
)
const (
epsilon = float64(0.00000001)
)
func TestParseSockId(t *testing.T) {
s := parseSockId(sockFile(osdPrefix, 1), osdPrefix, sockSuffix)
assert.Equal(t, s, "1")
}
func TestParseMonDump(t *testing.T) {
dump, err := parseDump(monPerfDump)
assert.NoError(t, err)
assert.InEpsilon(t, 5678670180, (*dump)["cluster"]["osd_kb_used"], epsilon)
assert.InEpsilon(t, 6866.540527000, (*dump)["paxos"]["store_state_latency.sum"], epsilon)
}
func TestParseOsdDump(t *testing.T) {
dump, err := parseDump(osdPerfDump)
assert.NoError(t, err)
assert.InEpsilon(t, 552132.109360000, (*dump)["filestore"]["commitcycle_interval.sum"], epsilon)
assert.Equal(t, float64(0), (*dump)["mutex-FileJournal::finisher_lock"]["wait.avgcount"])
}
func TestGather(t *testing.T) {
saveFind := findSockets
saveDump := perfDump
defer func() {
findSockets = saveFind
perfDump = saveDump
}()
findSockets = func(c *Ceph) ([]*socket, error) {
return []*socket{&socket{"osd.1", typeOsd, ""}}, nil
}
perfDump = func(binary string, s *socket) (string, error) {
return osdPerfDump, nil
}
acc := &testutil.Accumulator{}
c := &Ceph{}
c.Gather(acc)
}
func TestFindSockets(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "socktest")
assert.NoError(t, err)
defer func() {
err := os.Remove(tmpdir)
assert.NoError(t, err)
}()
c := &Ceph{
CephBinary: "foo",
SocketDir: tmpdir,
}
c.setDefaults()
for _, st := range sockTestParams {
createTestFiles(tmpdir, st)
sockets, err := findSockets(c)
assert.NoError(t, err)
for i := 1; i <= st.osds; i++ {
assertFoundSocket(t, tmpdir, typeOsd, i, sockets)
}
for i := 1; i <= st.mons; i++ {
assertFoundSocket(t, tmpdir, typeMon, i, sockets)
}
cleanupTestFiles(tmpdir, st)
}
}
func assertFoundSocket(t *testing.T, dir, sockType string, i int, sockets []*socket) {
var prefix string
if sockType == typeOsd {
prefix = osdPrefix
} else {
prefix = monPrefix
}
expected := path.Join(dir, sockFile(prefix, i))
found := false
for _, s := range sockets {
fmt.Printf("Checking %s\n", s.socket)
if s.socket == expected {
found = true
assert.Equal(t, s.sockType, sockType, "Unexpected socket type for '%s'", s)
assert.Equal(t, s.sockId, strconv.Itoa(i))
}
}
assert.True(t, found, "Did not find socket: %s", expected)
}
func sockFile(prefix string, i int) string {
return strings.Join([]string{prefix, strconv.Itoa(i), sockSuffix}, ".")
}
func createTestFiles(dir string, st *SockTest) {
writeFile := func(prefix string, i int) {
f := sockFile(prefix, i)
fpath := path.Join(dir, f)
ioutil.WriteFile(fpath, []byte(""), 0777)
}
tstFileApply(st, writeFile)
}
func cleanupTestFiles(dir string, st *SockTest) {
rmFile := func(prefix string, i int) {
f := sockFile(prefix, i)
fpath := path.Join(dir, f)
err := os.Remove(fpath)
if err != nil {
fmt.Printf("Error removing test file %s: %v\n", fpath, err)
}
}
tstFileApply(st, rmFile)
}
func tstFileApply(st *SockTest, fn func(prefix string, i int)) {
for i := 1; i <= st.osds; i++ {
fn(osdPrefix, i)
}
for i := 1; i <= st.mons; i++ {
fn(monPrefix, i)
}
}
type SockTest struct {
osds int
mons int
}
var sockTestParams = []*SockTest{
&SockTest{
osds: 2,
mons: 2,
},
&SockTest{
mons: 1,
},
&SockTest{
osds: 1,
},
&SockTest{},
}
var monPerfDump = `
{ "cluster": { "num_mon": 2,
"num_mon_quorum": 2,
"num_osd": 26,
"num_osd_up": 26,
"num_osd_in": 26,
"osd_epoch": 3306,
"osd_kb": 11487846448,
"osd_kb_used": 5678670180,
"osd_kb_avail": 5809176268,
"num_pool": 12,
"num_pg": 768,
"num_pg_active_clean": 768,
"num_pg_active": 768,
"num_pg_peering": 0,
"num_object": 397616,
"num_object_degraded": 0,
"num_object_unfound": 0,
"num_bytes": 2917848227467,
"num_mds_up": 0,
"num_mds_in": 0,
"num_mds_failed": 0,
"mds_epoch": 1},
"leveldb": { "leveldb_get": 321950312,
"leveldb_transaction": 18729922,
"leveldb_compact": 0,
"leveldb_compact_range": 74141,
"leveldb_compact_queue_merge": 0,
"leveldb_compact_queue_len": 0},
"mon": {},
"paxos": { "start_leader": 0,
"start_peon": 1,
"restart": 4,
"refresh": 9363435,
"refresh_latency": { "avgcount": 9363435,
"sum": 5378.794002000},
"begin": 9363435,
"begin_keys": { "avgcount": 0,
"sum": 0},
"begin_bytes": { "avgcount": 9363435,
"sum": 110468605489},
"begin_latency": { "avgcount": 9363435,
"sum": 5850.060682000},
"commit": 9363435,
"commit_keys": { "avgcount": 0,
"sum": 0},
"commit_bytes": { "avgcount": 0,
"sum": 0},
"commit_latency": { "avgcount": 0,
"sum": 0.000000000},
"collect": 1,
"collect_keys": { "avgcount": 1,
"sum": 1},
"collect_bytes": { "avgcount": 1,
"sum": 24},
"collect_latency": { "avgcount": 1,
"sum": 0.000280000},
"collect_uncommitted": 0,
"collect_timeout": 0,
"accept_timeout": 0,
"lease_ack_timeout": 0,
"lease_timeout": 0,
"store_state": 9363435,
"store_state_keys": { "avgcount": 9363435,
"sum": 176572789},
"store_state_bytes": { "avgcount": 9363435,
"sum": 216355887217},
"store_state_latency": { "avgcount": 9363435,
"sum": 6866.540527000},
"share_state": 0,
"share_state_keys": { "avgcount": 0,
"sum": 0},
"share_state_bytes": { "avgcount": 0,
"sum": 0},
"new_pn": 0,
"new_pn_latency": { "avgcount": 0,
"sum": 0.000000000}},
"throttle-mon_client_bytes": { "val": 246,
"max": 104857600,
"get": 896030,
"get_sum": 45854374,
"get_or_fail_fail": 0,
"get_or_fail_success": 0,
"take": 0,
"take_sum": 0,
"put": 896026,
"put_sum": 45854128,
"wait": { "avgcount": 0,
"sum": 0.000000000}},
"throttle-mon_daemon_bytes": { "val": 0,
"max": 419430400,
"get": 2773768,
"get_sum": 3627676976,
"get_or_fail_fail": 0,
"get_or_fail_success": 0,
"take": 0,
"take_sum": 0,
"put": 2773768,
"put_sum": 3627676976,
"wait": { "avgcount": 0,
"sum": 0.000000000}},
"throttle-msgr_dispatch_throttler-mon": { "val": 0,
"max": 104857600,
"get": 34504949,
"get_sum": 226860281124,
"get_or_fail_fail": 0,
"get_or_fail_success": 0,
"take": 0,
"take_sum": 0,
"put": 34504949,
"put_sum": 226860281124,
"wait": { "avgcount": 0,
"sum": 0.000000000}}}
`
var osdPerfDump = `
{ "WBThrottle": { "bytes_dirtied": 28405539,
"bytes_wb": 0,
"ios_dirtied": 93,
"ios_wb": 0,
"inodes_dirtied": 86,
"inodes_wb": 0},
"filestore": { "journal_queue_max_ops": 0,
"journal_queue_ops": 0,
"journal_ops": 1108008,
"journal_queue_max_bytes": 0,
"journal_queue_bytes": 0,
"journal_bytes": 73233416196,
"journal_latency": { "avgcount": 1108008,
"sum": 290.981036000},
"journal_wr": 1091866,
"journal_wr_bytes": { "avgcount": 1091866,
"sum": 74925682688},
"journal_full": 0,
"committing": 0,
"commitcycle": 110389,
"commitcycle_interval": { "avgcount": 110389,
"sum": 552132.109360000},
"commitcycle_latency": { "avgcount": 110389,
"sum": 178.657804000},
"op_queue_max_ops": 50,
"op_queue_ops": 0,
"ops": 1108008,
"op_queue_max_bytes": 104857600,
"op_queue_bytes": 0,
"bytes": 73226768148,
"apply_latency": { "avgcount": 1108008,
"sum": 947.742722000},
"queue_transaction_latency_avg": { "avgcount": 1108008,
"sum": 0.511327000}},
"leveldb": { "leveldb_get": 4361221,
"leveldb_transaction": 4351276,
"leveldb_compact": 0,
"leveldb_compact_range": 0,
"leveldb_compact_queue_merge": 0,
"leveldb_compact_queue_len": 0},
"mutex-FileJournal::completions_lock": { "wait": { "avgcount": 0,
"sum": 0.000000000}},
"mutex-FileJournal::finisher_lock": { "wait": { "avgcount": 0,
"sum": 0.000000000}},
"mutex-FileJournal::write_lock": { "wait": { "avgcount": 0,
"sum": 0.000000000}},
"mutex-FileJournal::writeq_lock": { "wait": { "avgcount": 0,
"sum": 0.000000000}},
"mutex-JOS::ApplyManager::apply_lock": { "wait": { "avgcount": 0,
"sum": 0.000000000}},
"mutex-JOS::ApplyManager::com_lock": { "wait": { "avgcount": 0,
"sum": 0.000000000}},
"mutex-JOS::SubmitManager::lock": { "wait": { "avgcount": 0,
"sum": 0.000000000}},
"mutex-WBThrottle::lock": { "wait": { "avgcount": 0,
"sum": 0.000000000}},
"objecter": { "op_active": 0,
"op_laggy": 0,
"op_send": 0,
"op_send_bytes": 0,
"op_resend": 0,
"op_ack": 0,
"op_commit": 0,
"op": 0,
"op_r": 0,
"op_w": 0,
"op_rmw": 0,
"op_pg": 0,
"osdop_stat": 0,
"osdop_create": 0,
"osdop_read": 0,
"osdop_write": 0,
"osdop_writefull": 0,
"osdop_append": 0,
"osdop_zero": 0,
"osdop_truncate": 0,
"osdop_delete": 0,
"osdop_mapext": 0,
"osdop_sparse_read": 0,
"osdop_clonerange": 0,
"osdop_getxattr": 0,
"osdop_setxattr": 0,
"osdop_cmpxattr": 0,
"osdop_rmxattr": 0,
"osdop_resetxattrs": 0,
"osdop_tmap_up": 0,
"osdop_tmap_put": 0,
"osdop_tmap_get": 0,
"osdop_call": 0,
"osdop_watch": 0,
"osdop_notify": 0,
"osdop_src_cmpxattr": 0,
"osdop_pgls": 0,
"osdop_pgls_filter": 0,
"osdop_other": 0,
"linger_active": 0,
"linger_send": 0,
"linger_resend": 0,
"poolop_active": 0,
"poolop_send": 0,
"poolop_resend": 0,
"poolstat_active": 0,
"poolstat_send": 0,
"poolstat_resend": 0,
"statfs_active": 0,
"statfs_send": 0,
"statfs_resend": 0,
"command_active": 0,
"command_send": 0,
"command_resend": 0,
"map_epoch": 3300,
"map_full": 0,
"map_inc": 3293,
"osd_sessions": 0,
"osd_session_open": 0,
"osd_session_close": 0,
"osd_laggy": 0},
"osd": { "opq": 0,
"op_wip": 0,
"op": 23939,
"op_in_bytes": 1245903961,
"op_out_bytes": 29103083856,
"op_latency": { "avgcount": 23939,
"sum": 440.192015000},
"op_process_latency": { "avgcount": 23939,
"sum": 30.170685000},
"op_r": 23112,
"op_r_out_bytes": 29103056146,
"op_r_latency": { "avgcount": 23112,
"sum": 19.373526000},
"op_r_process_latency": { "avgcount": 23112,
"sum": 14.625928000},
"op_w": 549,
"op_w_in_bytes": 1245804358,
"op_w_rlat": { "avgcount": 549,
"sum": 17.022299000},
"op_w_latency": { "avgcount": 549,
"sum": 418.494610000},
"op_w_process_latency": { "avgcount": 549,
"sum": 13.316555000},
"op_rw": 278,
"op_rw_in_bytes": 99603,
"op_rw_out_bytes": 27710,
"op_rw_rlat": { "avgcount": 278,
"sum": 2.213785000},
"op_rw_latency": { "avgcount": 278,
"sum": 2.323879000},
"op_rw_process_latency": { "avgcount": 278,
"sum": 2.228202000},
"subop": 1074774,
"subop_in_bytes": 26841811636,
"subop_latency": { "avgcount": 1074774,
"sum": 745.509160000},
"subop_w": 0,
"subop_w_in_bytes": 26841811636,
"subop_w_latency": { "avgcount": 1074774,
"sum": 745.509160000},
"subop_pull": 0,
"subop_pull_latency": { "avgcount": 0,
"sum": 0.000000000},
"subop_push": 0,
"subop_push_in_bytes": 0,
"subop_push_latency": { "avgcount": 0,
"sum": 0.000000000},
"pull": 0,
"push": 28,
"push_out_bytes": 103483392,
"push_in": 0,
"push_in_bytes": 0,
"recovery_ops": 15,
"loadavg": 202,
"buffer_bytes": 0,
"numpg": 18,
"numpg_primary": 8,
"numpg_replica": 10,
"numpg_stray": 0,
"heartbeat_to_peers": 10,
"heartbeat_from_peers": 0,
"map_messages": 7413,
"map_message_epochs": 9792,
"map_message_epoch_dups": 10105,
"messages_delayed_for_map": 83,
"stat_bytes": 102123175936,
"stat_bytes_used": 49961820160,
"stat_bytes_avail": 52161355776,
"copyfrom": 0,
"tier_promote": 0,
"tier_flush": 0,
"tier_flush_fail": 0,
"tier_try_flush": 0,
"tier_try_flush_fail": 0,
"tier_evict": 0,
"tier_whiteout": 0,
"tier_dirty": 230,
"tier_clean": 0,
"tier_delay": 0,
"agent_wake": 0,
"agent_skip": 0,
"agent_flush": 0,
"agent_evict": 0},
"recoverystate_perf": { "initial_latency": { "avgcount": 473,
"sum": 0.027207000},
"started_latency": { "avgcount": 1480,
"sum": 9854902.397648000},
"reset_latency": { "avgcount": 1953,
"sum": 0.096206000},
"start_latency": { "avgcount": 1953,
"sum": 0.059947000},
"primary_latency": { "avgcount": 765,
"sum": 4688922.186935000},
"peering_latency": { "avgcount": 704,
"sum": 1668.652135000},
"backfilling_latency": { "avgcount": 0,
"sum": 0.000000000},
"waitremotebackfillreserved_latency": { "avgcount": 0,
"sum": 0.000000000},
"waitlocalbackfillreserved_latency": { "avgcount": 0,
"sum": 0.000000000},
"notbackfilling_latency": { "avgcount": 0,
"sum": 0.000000000},
"repnotrecovering_latency": { "avgcount": 462,
"sum": 5158922.114600000},
"repwaitrecoveryreserved_latency": { "avgcount": 15,
"sum": 0.008275000},
"repwaitbackfillreserved_latency": { "avgcount": 1,
"sum": 0.000095000},
"RepRecovering_latency": { "avgcount": 16,
"sum": 2274.944727000},
"activating_latency": { "avgcount": 514,
"sum": 261.008520000},
"waitlocalrecoveryreserved_latency": { "avgcount": 20,
"sum": 0.175422000},
"waitremoterecoveryreserved_latency": { "avgcount": 20,
"sum": 0.682778000},
"recovering_latency": { "avgcount": 20,
"sum": 0.697551000},
"recovered_latency": { "avgcount": 511,
"sum": 0.011038000},
"clean_latency": { "avgcount": 503,
"sum": 4686961.154278000},
"active_latency": { "avgcount": 506,
"sum": 4687223.640464000},
"replicaactive_latency": { "avgcount": 446,
"sum": 5161197.078966000},
"stray_latency": { "avgcount": 794,
"sum": 4805.105128000},
"getinfo_latency": { "avgcount": 704,
"sum": 1138.477937000},
"getlog_latency": { "avgcount": 678,
"sum": 0.036393000},
"waitactingchange_latency": { "avgcount": 69,
"sum": 59.172893000},
"incomplete_latency": { "avgcount": 0,
"sum": 0.000000000},
"getmissing_latency": { "avgcount": 609,
"sum": 0.012288000},
"waitupthru_latency": { "avgcount": 576,
"sum": 530.106999000}},
"throttle-filestore_bytes": { "val": 0,
"max": 0,
"get": 0,
"get_sum": 0,
"get_or_fail_fail": 0,
"get_or_fail_success": 0,
"take": 0,
"take_sum": 0,
"put": 0,
"put_sum": 0,
"wait": { "avgcount": 0,
"sum": 0.000000000}},
"throttle-filestore_ops": { "val": 0,
"max": 0,
"get": 0,
"get_sum": 0,
"get_or_fail_fail": 0,
"get_or_fail_success": 0,
"take": 0,
"take_sum": 0,
"put": 0,
"put_sum": 0,
"wait": { "avgcount": 0,
"sum": 0.000000000}},
"throttle-msgr_dispatch_throttler-client": { "val": 0,
"max": 104857600,
"get": 130730,
"get_sum": 1246039872,
"get_or_fail_fail": 0,
"get_or_fail_success": 0,
"take": 0,
"take_sum": 0,
"put": 130730,
"put_sum": 1246039872,
"wait": { "avgcount": 0,
"sum": 0.000000000}},
"throttle-msgr_dispatch_throttler-cluster": { "val": 0,
"max": 104857600,
"get": 1108033,
"get_sum": 71277949992,
"get_or_fail_fail": 0,
"get_or_fail_success": 0,
"take": 0,
"take_sum": 0,
"put": 1108033,
"put_sum": 71277949992,
"wait": { "avgcount": 0,
"sum": 0.000000000}},
"throttle-msgr_dispatch_throttler-hb_back_server": { "val": 0,
"max": 104857600,
"get": 18320575,
"get_sum": 861067025,
"get_or_fail_fail": 0,
"get_or_fail_success": 0,
"take": 0,
"take_sum": 0,
"put": 18320575,
"put_sum": 861067025,
"wait": { "avgcount": 0,
"sum": 0.000000000}},
"throttle-msgr_dispatch_throttler-hb_front_server": { "val": 0,
"max": 104857600,
"get": 18320575,
"get_sum": 861067025,
"get_or_fail_fail": 0,
"get_or_fail_success": 0,
"take": 0,
"take_sum": 0,
"put": 18320575,
"put_sum": 861067025,
"wait": { "avgcount": 0,
"sum": 0.000000000}},
"throttle-msgr_dispatch_throttler-hbclient": { "val": 0,
"max": 104857600,
"get": 40479394,
"get_sum": 1902531518,
"get_or_fail_fail": 0,
"get_or_fail_success": 0,
"take": 0,
"take_sum": 0,
"put": 40479394,
"put_sum": 1902531518,
"wait": { "avgcount": 0,
"sum": 0.000000000}},
"throttle-msgr_dispatch_throttler-ms_objecter": { "val": 0,
"max": 104857600,
"get": 0,
"get_sum": 0,
"get_or_fail_fail": 0,
"get_or_fail_success": 0,
"take": 0,
"take_sum": 0,
"put": 0,
"put_sum": 0,
"wait": { "avgcount": 0,
"sum": 0.000000000}},
"throttle-objecter_bytes": { "val": 0,
"max": 104857600,
"get": 0,
"get_sum": 0,
"get_or_fail_fail": 0,
"get_or_fail_success": 0,
"take": 0,
"take_sum": 0,
"put": 0,
"put_sum": 0,
"wait": { "avgcount": 0,
"sum": 0.000000000}},
"throttle-objecter_ops": { "val": 0,
"max": 1024,
"get": 0,
"get_sum": 0,
"get_or_fail_fail": 0,
"get_or_fail_success": 0,
"take": 0,
"take_sum": 0,
"put": 0,
"put_sum": 0,
"wait": { "avgcount": 0,
"sum": 0.000000000}},
"throttle-osd_client_bytes": { "val": 0,
"max": 524288000,
"get": 24241,
"get_sum": 1241992581,
"get_or_fail_fail": 0,
"get_or_fail_success": 0,
"take": 0,
"take_sum": 0,
"put": 25958,
"put_sum": 1241992581,
"wait": { "avgcount": 0,
"sum": 0.000000000}},
"throttle-osd_client_messages": { "val": 0,
"max": 100,
"get": 49214,
"get_sum": 49214,
"get_or_fail_fail": 0,
"get_or_fail_success": 0,
"take": 0,
"take_sum": 0,
"put": 49214,
"put_sum": 49214,
"wait": { "avgcount": 0,
"sum": 0.000000000}}}
`

View File

@@ -1,92 +0,0 @@
# chrony Input Plugin
Get standard chrony metrics, requires chronyc executable.
Below is the documentation of the various headers returned by `chronyc tracking`.
- Reference ID - This is the refid and name (or IP address) if available, of the
server to which the computer is currently synchronised. If this is 127.127.1.1
it means the computer is not synchronised to any external source and that you
have the local mode operating (via the local command in chronyc (see section local),
or the local directive in the /etc/chrony.conf file (see section local)).
- Stratum - The stratum indicates how many hops away from a computer with an attached
reference clock we are. Such a computer is a stratum-1 computer, so the computer in the
example is two hops away (i.e. a.b.c is a stratum-2 and is synchronised from a stratum-1).
- Ref time - This is the time (UTC) at which the last measurement from the reference
source was processed.
- System time - In normal operation, chronyd never steps the system clock, because any
jump in the timescale can have adverse consequences for certain application programs.
Instead, any error in the system clock is corrected by slightly speeding up or slowing
down the system clock until the error has been removed, and then returning to the system
clocks normal speed. A consequence of this is that there will be a period when the
system clock (as read by other programs using the gettimeofday() system call, or by the
date command in the shell) will be different from chronyd's estimate of the current true
time (which it reports to NTP clients when it is operating in server mode). The value
reported on this line is the difference due to this effect.
- Last offset - This is the estimated local offset on the last clock update.
- RMS offset - This is a long-term average of the offset value.
- Frequency - The frequency is the rate by which the systems clock would be
wrong if chronyd was not correcting it. It is expressed in ppm (parts per million).
For example, a value of 1ppm would mean that when the systems clock thinks it has
advanced 1 second, it has actually advanced by 1.000001 seconds relative to true time.
- Residual freq - This shows the residual frequency for the currently selected
reference source. This reflects any difference between what the measurements from the
reference source indicate the frequency should be and the frequency currently being used.
The reason this is not always zero is that a smoothing procedure is applied to the
frequency. Each time a measurement from the reference source is obtained and a new
residual frequency computed, the estimated accuracy of this residual is compared with the
estimated accuracy (see skew next) of the existing frequency value. A weighted average
is computed for the new frequency, with weights depending on these accuracies. If the
measurements from the reference source follow a consistent trend, the residual will be
driven to zero over time.
- Skew - This is the estimated error bound on the frequency.
- Root delay - This is the total of the network path delays to the stratum-1 computer
from which the computer is ultimately synchronised. In certain extreme situations, this
value can be negative. (This can arise in a symmetric peer arrangement where the computers
frequencies are not tracking each other and the network delay is very short relative to the
turn-around time at each computer.)
- Root dispersion - This is the total dispersion accumulated through all the computers
back to the stratum-1 computer from which the computer is ultimately synchronised.
Dispersion is due to system clock resolution, statistical measurement variations etc.
- Leap status - This is the leap status, which can be Normal, Insert second,
Delete second or Not synchronised.
### Configuration:
```toml
# Get standard chrony metrics, requires chronyc executable.
[[inputs.chrony]]
## If true, chronyc tries to perform a DNS lookup for the time server.
# dns_lookup = false
```
### Measurements & Fields:
- chrony
- last_offset (float, seconds)
- rms_offset (float, seconds)
- frequency (float, ppm)
- residual_freq (float, ppm)
- skew (float, ppm)
- root_delay (float, seconds)
- root_dispersion (float, seconds)
- update_interval (float, seconds)
### Tags:
- All measurements have the following tags:
- reference_id
- stratum
- leap_status
### Example Output:
```
$ telegraf -config telegraf.conf -input-filter chrony -test
* Plugin: chrony, Collection 1
> chrony,leap_status=normal,reference_id=192.168.1.1,stratum=3 frequency=-35.657,last_offset=-0.000013616,residual_freq=-0,rms_offset=0.000027073,root_delay=0.000644,root_dispersion=0.003444,skew=0.001,update_interval=1031.2 1463750789687639161
```

View File

@@ -1,129 +0,0 @@
// +build linux
package chrony
import (
"errors"
"fmt"
"os/exec"
"strconv"
"strings"
"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.
)
type Chrony struct {
DNSLookup bool `toml:"dns_lookup"`
path string
}
func (*Chrony) Description() string {
return "Get standard chrony metrics, requires chronyc executable."
}
func (*Chrony) SampleConfig() string {
return `
## If true, chronyc tries to perform a DNS lookup for the time server.
# dns_lookup = false
`
}
func (c *Chrony) Gather(acc telegraf.Accumulator) error {
if len(c.path) == 0 {
return errors.New("chronyc not found: verify that chrony is installed and that chronyc is in your PATH")
}
flags := []string{}
if !c.DNSLookup {
flags = append(flags, "-n")
}
flags = append(flags, "tracking")
cmd := execCommand(c.path, flags...)
out, err := internal.CombinedOutputTimeout(cmd, time.Second*5)
if err != nil {
return fmt.Errorf("failed to run command %s: %s - %s", strings.Join(cmd.Args, " "), err, string(out))
}
fields, tags, err := processChronycOutput(string(out))
if err != nil {
return err
}
acc.AddFields("chrony", fields, tags)
return nil
}
// processChronycOutput takes in a string output from the chronyc command, like:
//
// Reference ID : 192.168.1.22 (ntp.example.com)
// Stratum : 3
// Ref time (UTC) : Thu May 12 14:27:07 2016
// System time : 0.000020390 seconds fast of NTP time
// Last offset : +0.000012651 seconds
// RMS offset : 0.000025577 seconds
// Frequency : 16.001 ppm slow
// Residual freq : -0.000 ppm
// Skew : 0.006 ppm
// Root delay : 0.001655 seconds
// Root dispersion : 0.003307 seconds
// Update interval : 507.2 seconds
// Leap status : Normal
//
// The value on the left side of the colon is used as field name, if the first field on
// the right side is a float. If it cannot be parsed as float, it is a tag name.
//
// Ref time is ignored and all names are converted to snake case.
//
// It returns (<fields>, <tags>)
func processChronycOutput(out string) (map[string]interface{}, map[string]string, error) {
tags := map[string]string{}
fields := map[string]interface{}{}
lines := strings.Split(strings.TrimSpace(out), "\n")
for _, line := range lines {
stats := strings.Split(line, ":")
if len(stats) < 2 {
return nil, nil, fmt.Errorf("unexpected output from chronyc, expected ':' in %s", out)
}
name := strings.ToLower(strings.Replace(strings.TrimSpace(stats[0]), " ", "_", -1))
// ignore reference time
if strings.Contains(name, "time") {
continue
}
valueFields := strings.Fields(stats[1])
if len(valueFields) == 0 {
return nil, nil, fmt.Errorf("unexpected output from chronyc: %s", out)
}
if strings.Contains(strings.ToLower(name), "stratum") {
tags["stratum"] = valueFields[0]
continue
}
value, err := strconv.ParseFloat(valueFields[0], 64)
if err != nil {
tags[name] = strings.ToLower(valueFields[0])
continue
}
if strings.Contains(stats[1], "slow") {
value = -value
}
fields[name] = value
}
return fields, tags, nil
}
func init() {
c := Chrony{}
path, _ := exec.LookPath("chronyc")
if len(path) > 0 {
c.path = path
}
inputs.Add("chrony", func() telegraf.Input {
return &c
})
}

View File

@@ -1,3 +0,0 @@
// +build !linux
package chrony

View File

@@ -1,109 +0,0 @@
// +build linux
package chrony
import (
"fmt"
"os"
"os/exec"
"testing"
"github.com/influxdata/telegraf/testutil"
)
func TestGather(t *testing.T) {
c := Chrony{
path: "chronyc",
}
// overwriting exec commands with mock commands
execCommand = fakeExecCommand
defer func() { execCommand = exec.Command }()
var acc testutil.Accumulator
err := c.Gather(&acc)
if err != nil {
t.Fatal(err)
}
tags := map[string]string{
"reference_id": "192.168.1.22",
"leap_status": "normal",
"stratum": "3",
}
fields := map[string]interface{}{
"last_offset": 0.000012651,
"rms_offset": 0.000025577,
"frequency": -16.001,
"residual_freq": 0.0,
"skew": 0.006,
"root_delay": 0.001655,
"root_dispersion": 0.003307,
"update_interval": 507.2,
}
acc.AssertContainsTaggedFields(t, "chrony", fields, tags)
// test with dns lookup
c.DNSLookup = true
err = c.Gather(&acc)
if err != nil {
t.Fatal(err)
}
acc.AssertContainsTaggedFields(t, "chrony", fields, tags)
}
// 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 -- chrony tracking
// it returns below mockData.
func TestHelperProcess(t *testing.T) {
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
return
}
lookup := "Reference ID : 192.168.1.22 (ntp.example.com)\n"
noLookup := "Reference ID : 192.168.1.22 (192.168.1.22)\n"
mockData := `Stratum : 3
Ref time (UTC) : Thu May 12 14:27:07 2016
System time : 0.000020390 seconds fast of NTP time
Last offset : +0.000012651 seconds
RMS offset : 0.000025577 seconds
Frequency : 16.001 ppm slow
Residual freq : -0.000 ppm
Skew : 0.006 ppm
Root delay : 0.001655 seconds
Root dispersion : 0.003307 seconds
Update interval : 507.2 seconds
Leap status : Normal
`
args := os.Args
// Previous arguments are tests stuff, that looks like :
// /tmp/go-build970079519/…/_test/integration.test -test.run=TestHelperProcess --
cmd, args := args[3], args[4:]
if cmd == "chronyc" {
if args[0] == "tracking" {
fmt.Fprint(os.Stdout, lookup+mockData)
} else {
fmt.Fprint(os.Stdout, noLookup+mockData)
}
} else {
fmt.Fprint(os.Stdout, "command not found")
os.Exit(1)
}
os.Exit(0)
}

View File

@@ -6,12 +6,9 @@ This plugin will pull Metric Statistics from Amazon CloudWatch.
This plugin uses a credential chain for Authentication with the CloudWatch This plugin uses a credential chain for Authentication with the CloudWatch
API endpoint. In the following order the plugin will attempt to authenticate. API endpoint. In the following order the plugin will attempt to authenticate.
1. Assumed credentials via STS if `role_arn` attribute is specified (source credentials are evaluated from subsequent rules) 1. [IAMS Role](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html)
2. Explicit credentials from `access_key`, `secret_key`, and `token` attributes 2. [Environment Variables](https://github.com/aws/aws-sdk-go/wiki/configuring-sdk#environment-variables)
3. Shared profile from `profile` attribute 3. [Shared Credentials](https://github.com/aws/aws-sdk-go/wiki/configuring-sdk#shared-credentials-file)
4. [Environment Variables](https://github.com/aws/aws-sdk-go/wiki/configuring-sdk#environment-variables)
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)
### Configuration: ### Configuration:
@@ -44,10 +41,6 @@ API endpoint. In the following order the plugin will attempt to authenticate.
[[inputs.cloudwatch.metrics.dimensions]] [[inputs.cloudwatch.metrics.dimensions]]
name = 'LoadBalancerName' name = 'LoadBalancerName'
value = 'p-example' value = 'p-example'
[[inputs.cloudwatch.metrics.dimensions]]
name = 'AvailabilityZone'
value = '*'
``` ```
#### Requirements and Terminology #### Requirements and Terminology
@@ -59,39 +52,6 @@ Plugin Configuration utilizes [CloudWatch concepts](http://docs.aws.amazon.com/A
- `names` must be valid CloudWatch [Metric](http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/cloudwatch_concepts.html#Metric) names - `names` must be valid CloudWatch [Metric](http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/cloudwatch_concepts.html#Metric) names
- `dimensions` must be valid CloudWatch [Dimension](http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/cloudwatch_concepts.html#Dimension) name/value pairs - `dimensions` must be valid CloudWatch [Dimension](http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/cloudwatch_concepts.html#Dimension) name/value pairs
Omitting or specifying a value of `'*'` for a dimension value configures all available metrics that contain a dimension with the specified name
to be retrieved. If specifying >1 dimension, then the metric must contain *all* the configured dimensions where the the value of the
wildcard dimension is ignored.
Example:
```
[[inputs.cloudwatch.metrics]]
names = ['Latency']
## Dimension filters for Metric (optional)
[[inputs.cloudwatch.metrics.dimensions]]
name = 'LoadBalancerName'
value = 'p-example'
[[inputs.cloudwatch.metrics.dimensions]]
name = 'AvailabilityZone'
value = '*'
```
If the following ELBs are available:
- name: `p-example`, availabilityZone: `us-east-1a`
- name: `p-example`, availabilityZone: `us-east-1b`
- name: `q-example`, availabilityZone: `us-east-1a`
- name: `q-example`, availabilityZone: `us-east-1b`
Then 2 metrics will be output:
- name: `p-example`, availabilityZone: `us-east-1a`
- name: `p-example`, availabilityZone: `us-east-1b`
If the `AvailabilityZone` wildcard dimension was omitted, then a single metric (name: `p-example`)
would be exported containing the aggregate values of the ELB across availability zones.
#### Restrictions and Limitations #### Restrictions and Limitations
- CloudWatch metrics are not available instantly via the CloudWatch API. You should adjust your collection `delay` to account for this lag in metrics availability based on your [monitoring subscription level](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-cloudwatch-new.html) - CloudWatch metrics are not available instantly via the CloudWatch API. You should adjust your collection `delay` to account for this lag in metrics availability based on your [monitoring subscription level](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-cloudwatch-new.html)
- CloudWatch API usage incurs cost - see [GetMetricStatistics Pricing](https://aws.amazon.com/cloudwatch/pricing/) - CloudWatch API usage incurs cost - see [GetMetricStatistics Pricing](https://aws.amazon.com/cloudwatch/pricing/)

View File

@@ -3,36 +3,28 @@ package cloudwatch
import ( import (
"fmt" "fmt"
"strings" "strings"
"sync"
"time" "time"
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/cloudwatch" "github.com/aws/aws-sdk-go/service/cloudwatch"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal" "github.com/influxdata/telegraf/internal"
internalaws "github.com/influxdata/telegraf/internal/config/aws"
"github.com/influxdata/telegraf/internal/errchan"
"github.com/influxdata/telegraf/internal/limiter"
"github.com/influxdata/telegraf/plugins/inputs" "github.com/influxdata/telegraf/plugins/inputs"
) )
type ( type (
CloudWatch struct { CloudWatch struct {
Region string `toml:"region"` Region string `toml:"region"`
AccessKey string `toml:"access_key"` AccessKey string `toml:"access_key"`
SecretKey string `toml:"secret_key"` SecretKey string `toml:"secret_key"`
RoleARN string `toml:"role_arn"`
Profile string `toml:"profile"`
Filename string `toml:"shared_credential_file"`
Token string `toml:"token"`
Period internal.Duration `toml:"period"` Period internal.Duration `toml:"period"`
Delay internal.Duration `toml:"delay"` Delay internal.Duration `toml:"delay"`
Namespace string `toml:"namespace"` Namespace string `toml:"namespace"`
Metrics []*Metric `toml:"metrics"` Metrics []*Metric `toml:"metrics"`
CacheTTL internal.Duration `toml:"cache_ttl"`
client cloudwatchClient client cloudwatchClient
metricCache *MetricCache metricCache *MetricCache
} }
@@ -66,18 +58,12 @@ func (c *CloudWatch) SampleConfig() string {
## Amazon Credentials ## Amazon Credentials
## Credentials are loaded in the following order ## Credentials are loaded in the following order
## 1) Assumed credentials via STS if role_arn is specified ## 1) explicit credentials from 'access_key' and 'secret_key'
## 2) explicit credentials from 'access_key' and 'secret_key' ## 2) environment variables
## 3) shared profile from 'profile' ## 3) shared credentials file
## 4) environment variables ## 4) EC2 Instance Profile
## 5) shared credentials file
## 6) EC2 Instance Profile
#access_key = "" #access_key = ""
#secret_key = "" #secret_key = ""
#token = ""
#role_arn = ""
#profile = ""
#shared_credential_file = ""
## Requested CloudWatch aggregation Period (required - must be a multiple of 60s) ## Requested CloudWatch aggregation Period (required - must be a multiple of 60s)
period = '1m' period = '1m'
@@ -89,10 +75,6 @@ func (c *CloudWatch) SampleConfig() string {
## gaps or overlap in pulled data ## gaps or overlap in pulled data
interval = '1m' interval = '1m'
## Configure the TTL for the internal cache of metrics.
## Defaults to 1 hr if not specified
#cache_ttl = '10m'
## Metric Statistic Namespace (required) ## Metric Statistic Namespace (required)
namespace = 'AWS/ELB' namespace = 'AWS/ELB'
@@ -124,40 +106,20 @@ func (c *CloudWatch) Gather(acc telegraf.Accumulator) error {
if c.Metrics != nil { if c.Metrics != nil {
metrics = []*cloudwatch.Metric{} metrics = []*cloudwatch.Metric{}
for _, m := range c.Metrics { for _, m := range c.Metrics {
if !hasWilcard(m.Dimensions) { dimensions := make([]*cloudwatch.Dimension, len(m.Dimensions))
dimensions := make([]*cloudwatch.Dimension, len(m.Dimensions)) for k, d := range m.Dimensions {
for k, d := range m.Dimensions { dimensions[k] = &cloudwatch.Dimension{
fmt.Printf("Dimension [%s]:[%s]\n", d.Name, d.Value) Name: aws.String(d.Name),
dimensions[k] = &cloudwatch.Dimension{ Value: aws.String(d.Value),
Name: aws.String(d.Name),
Value: aws.String(d.Value),
}
}
for _, name := range m.MetricNames {
metrics = append(metrics, &cloudwatch.Metric{
Namespace: aws.String(c.Namespace),
MetricName: aws.String(name),
Dimensions: dimensions,
})
}
} else {
allMetrics, err := c.fetchNamespaceMetrics()
if err != nil {
return err
}
for _, name := range m.MetricNames {
for _, metric := range allMetrics {
if isSelected(metric, m.Dimensions) {
metrics = append(metrics, &cloudwatch.Metric{
Namespace: aws.String(c.Namespace),
MetricName: aws.String(name),
Dimensions: metric.Dimensions,
})
}
}
} }
} }
for _, name := range m.MetricNames {
metrics = append(metrics, &cloudwatch.Metric{
Namespace: aws.String(c.Namespace),
MetricName: aws.String(name),
Dimensions: dimensions,
})
}
} }
} else { } else {
var err error var err error
@@ -168,35 +130,30 @@ func (c *CloudWatch) Gather(acc telegraf.Accumulator) error {
} }
metricCount := len(metrics) metricCount := len(metrics)
errChan := errchan.New(metricCount) var errChan = make(chan error, metricCount)
now := time.Now() now := time.Now()
// limit concurrency or we can easily exhaust user connection limit // limit concurrency or we can easily exhaust user connection limit
// see cloudwatch API request limits: semaphore := make(chan byte, 64)
// http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/cloudwatch_limits.html
lmtr := limiter.NewRateLimiter(10, time.Second)
defer lmtr.Stop()
var wg sync.WaitGroup
wg.Add(len(metrics))
for _, m := range metrics {
<-lmtr.C
go func(inm *cloudwatch.Metric) {
defer wg.Done()
c.gatherMetric(acc, inm, now, errChan.C)
}(m)
}
wg.Wait()
return errChan.Error() for _, m := range metrics {
semaphore <- 0x1
go c.gatherMetric(acc, m, now, semaphore, errChan)
}
for i := 1; i <= metricCount; i++ {
err := <-errChan
if err != nil {
return err
}
}
return nil
} }
func init() { func init() {
inputs.Add("cloudwatch", func() telegraf.Input { inputs.Add("cloudwatch", func() telegraf.Input {
ttl, _ := time.ParseDuration("1hr") return &CloudWatch{}
return &CloudWatch{
CacheTTL: internal.Duration{Duration: ttl},
}
}) })
} }
@@ -204,18 +161,14 @@ func init() {
* Initialize CloudWatch client * Initialize CloudWatch client
*/ */
func (c *CloudWatch) initializeCloudWatch() error { func (c *CloudWatch) initializeCloudWatch() error {
credentialConfig := &internalaws.CredentialConfig{ config := &aws.Config{
Region: c.Region, Region: aws.String(c.Region),
AccessKey: c.AccessKey, }
SecretKey: c.SecretKey, if c.AccessKey != "" || c.SecretKey != "" {
RoleARN: c.RoleARN, config.Credentials = credentials.NewStaticCredentials(c.AccessKey, c.SecretKey, "")
Profile: c.Profile,
Filename: c.Filename,
Token: c.Token,
} }
configProvider := credentialConfig.Credentials()
c.client = cloudwatch.New(configProvider) c.client = cloudwatch.New(session.New(config))
return nil return nil
} }
@@ -250,10 +203,11 @@ func (c *CloudWatch) fetchNamespaceMetrics() (metrics []*cloudwatch.Metric, err
more = token != nil more = token != nil
} }
cacheTTL, _ := time.ParseDuration("1hr")
c.metricCache = &MetricCache{ c.metricCache = &MetricCache{
Metrics: metrics, Metrics: metrics,
Fetched: time.Now(), Fetched: time.Now(),
TTL: c.CacheTTL.Duration, TTL: cacheTTL,
} }
return return
@@ -262,16 +216,12 @@ func (c *CloudWatch) fetchNamespaceMetrics() (metrics []*cloudwatch.Metric, err
/* /*
* Gather given Metric and emit any error * Gather given Metric and emit any error
*/ */
func (c *CloudWatch) gatherMetric( func (c *CloudWatch) gatherMetric(acc telegraf.Accumulator, metric *cloudwatch.Metric, now time.Time, semaphore chan byte, errChan chan error) {
acc telegraf.Accumulator,
metric *cloudwatch.Metric,
now time.Time,
errChan chan error,
) {
params := c.getStatisticsInput(metric, now) params := c.getStatisticsInput(metric, now)
resp, err := c.client.GetMetricStatistics(params) resp, err := c.client.GetMetricStatistics(params)
if err != nil { if err != nil {
errChan <- err errChan <- err
<-semaphore
return return
} }
@@ -308,6 +258,7 @@ func (c *CloudWatch) gatherMetric(
} }
errChan <- nil errChan <- nil
<-semaphore
} }
/* /*
@@ -358,32 +309,3 @@ func (c *CloudWatch) getStatisticsInput(metric *cloudwatch.Metric, now time.Time
func (c *MetricCache) IsValid() bool { func (c *MetricCache) IsValid() bool {
return c.Metrics != nil && time.Since(c.Fetched) < c.TTL return c.Metrics != nil && time.Since(c.Fetched) < c.TTL
} }
func hasWilcard(dimensions []*Dimension) bool {
for _, d := range dimensions {
if d.Value == "" || d.Value == "*" {
return true
}
}
return false
}
func isSelected(metric *cloudwatch.Metric, dimensions []*Dimension) bool {
if len(metric.Dimensions) != len(dimensions) {
return false
}
for _, d := range dimensions {
selected := false
for _, d2 := range metric.Dimensions {
if d.Name == *d2.Name {
if d.Value == "" || d.Value == "*" || d.Value == *d2.Value {
selected = true
}
}
}
if !selected {
return false
}
}
return true
}

View File

@@ -1,56 +0,0 @@
# Conntrack Plugin
Collects stats from Netfilter's conntrack-tools.
The conntrack-tools provide a mechanism for tracking various aspects of
network connections as they are processed by netfilter. At runtime,
conntrack exposes many of those connection statistics within /proc/sys/net.
Depending on your kernel version, these files can be found in either
/proc/sys/net/ipv4/netfilter or /proc/sys/net/netfilter and will be
prefixed with either ip_ or nf_. This plugin reads the files specified
in its configuration and publishes each one as a field, with the prefix
normalized to ip_.
In order to simplify configuration in a heterogeneous environment, a superset
of directory and filenames can be specified. Any locations that don't exist
will be ignored.
For more information on conntrack-tools, see the
[Netfilter Documentation](http://conntrack-tools.netfilter.org/).
### Configuration:
```toml
# Collects conntrack stats from the configured directories and files.
[[inputs.conntrack]]
## The following defaults would work with multiple versions of conntrack.
## Note the nf_ and ip_ filename prefixes are mutually exclusive across
## kernel versions, as are the directory locations.
## Superset of filenames to look for within the conntrack dirs.
## Missing files will be ignored.
files = ["ip_conntrack_count","ip_conntrack_max",
"nf_conntrack_count","nf_conntrack_max"]
## Directories to search within for the conntrack files above.
## Missing directrories will be ignored.
dirs = ["/proc/sys/net/ipv4/netfilter","/proc/sys/net/netfilter"]
```
### Measurements & Fields:
- conntrack
- ip_conntrack_count (int, count): the number of entries in the conntrack table
- ip_conntrack_max (int, size): the max capacity of the conntrack table
### Tags:
This input does not use tags.
### Example Output:
```
$ ./telegraf -config telegraf.conf -input-filter conntrack -test
conntrack,host=myhost ip_conntrack_count=2,ip_conntrack_max=262144 1461620427667995735
```

View File

@@ -1,119 +0,0 @@
// +build linux
package conntrack
import (
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
"log"
"path/filepath"
)
type Conntrack struct {
Path string
Dirs []string
Files []string
}
const (
inputName = "conntrack"
)
var dfltDirs = []string{
"/proc/sys/net/ipv4/netfilter",
"/proc/sys/net/netfilter",
}
var dfltFiles = []string{
"ip_conntrack_count",
"ip_conntrack_max",
"nf_conntrack_count",
"nf_conntrack_max",
}
func (c *Conntrack) setDefaults() {
if len(c.Dirs) == 0 {
c.Dirs = dfltDirs
}
if len(c.Files) == 0 {
c.Files = dfltFiles
}
}
func (c *Conntrack) Description() string {
return "Collects conntrack stats from the configured directories and files."
}
var sampleConfig = `
## The following defaults would work with multiple versions of conntrack.
## Note the nf_ and ip_ filename prefixes are mutually exclusive across
## kernel versions, as are the directory locations.
## Superset of filenames to look for within the conntrack dirs.
## Missing files will be ignored.
files = ["ip_conntrack_count","ip_conntrack_max",
"nf_conntrack_count","nf_conntrack_max"]
## Directories to search within for the conntrack files above.
## Missing directrories will be ignored.
dirs = ["/proc/sys/net/ipv4/netfilter","/proc/sys/net/netfilter"]
`
func (c *Conntrack) SampleConfig() string {
return sampleConfig
}
func (c *Conntrack) Gather(acc telegraf.Accumulator) error {
c.setDefaults()
var metricKey string
fields := make(map[string]interface{})
for _, dir := range c.Dirs {
for _, file := range c.Files {
// NOTE: no system will have both nf_ and ip_ prefixes,
// so we're safe to branch on suffix only.
parts := strings.SplitN(file, "_", 2)
if len(parts) < 2 {
continue
}
metricKey = "ip_" + parts[1]
fName := filepath.Join(dir, file)
if _, err := os.Stat(fName); err != nil {
continue
}
contents, err := ioutil.ReadFile(fName)
if err != nil {
log.Printf("failed to read file '%s': %v", fName, err)
}
v := strings.TrimSpace(string(contents))
fields[metricKey], err = strconv.ParseFloat(v, 64)
if err != nil {
log.Printf("failed to parse metric, expected number but "+
" found '%s': %v", v, err)
}
}
}
if len(fields) == 0 {
return fmt.Errorf("Conntrack input failed to collect metrics. " +
"Is the conntrack kernel module loaded?")
}
acc.AddFields(inputName, fields, nil)
return nil
}
func init() {
inputs.Add(inputName, func() telegraf.Input { return &Conntrack{} })
}

View File

@@ -1,3 +0,0 @@
// +build !linux
package conntrack

View File

@@ -1,90 +0,0 @@
// +build linux
package conntrack
import (
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/assert"
"io/ioutil"
"os"
"path"
"strconv"
"strings"
"testing"
)
func restoreDflts(savedFiles, savedDirs []string) {
dfltFiles = savedFiles
dfltDirs = savedDirs
}
func TestNoFilesFound(t *testing.T) {
defer restoreDflts(dfltFiles, dfltDirs)
dfltFiles = []string{"baz.txt"}
dfltDirs = []string{"./foo/bar"}
c := &Conntrack{}
acc := &testutil.Accumulator{}
err := c.Gather(acc)
assert.EqualError(t, err, "Conntrack input failed to collect metrics. "+
"Is the conntrack kernel module loaded?")
}
func TestDefaultsUsed(t *testing.T) {
defer restoreDflts(dfltFiles, dfltDirs)
tmpdir, err := ioutil.TempDir("", "tmp1")
assert.NoError(t, err)
defer os.Remove(tmpdir)
tmpFile, err := ioutil.TempFile(tmpdir, "ip_conntrack_count")
assert.NoError(t, err)
dfltDirs = []string{tmpdir}
fname := path.Base(tmpFile.Name())
dfltFiles = []string{fname}
count := 1234321
ioutil.WriteFile(tmpFile.Name(), []byte(strconv.Itoa(count)), 0660)
c := &Conntrack{}
acc := &testutil.Accumulator{}
c.Gather(acc)
acc.AssertContainsFields(t, inputName, map[string]interface{}{
fname: float64(count)})
}
func TestConfigsUsed(t *testing.T) {
defer restoreDflts(dfltFiles, dfltDirs)
tmpdir, err := ioutil.TempDir("", "tmp1")
assert.NoError(t, err)
defer os.Remove(tmpdir)
cntFile, err := ioutil.TempFile(tmpdir, "nf_conntrack_count")
maxFile, err := ioutil.TempFile(tmpdir, "nf_conntrack_max")
assert.NoError(t, err)
dfltDirs = []string{tmpdir}
cntFname := path.Base(cntFile.Name())
maxFname := path.Base(maxFile.Name())
dfltFiles = []string{cntFname, maxFname}
count := 1234321
max := 9999999
ioutil.WriteFile(cntFile.Name(), []byte(strconv.Itoa(count)), 0660)
ioutil.WriteFile(maxFile.Name(), []byte(strconv.Itoa(max)), 0660)
c := &Conntrack{}
acc := &testutil.Accumulator{}
c.Gather(acc)
fix := func(s string) string {
return strings.Replace(s, "nf_", "ip_", 1)
}
acc.AssertContainsFields(t, inputName,
map[string]interface{}{
fix(cntFname): float64(count),
fix(maxFname): float64(max),
})
}

View File

@@ -1,46 +0,0 @@
# Telegraf Input Plugin: Consul
This plugin will collect statistics about all helath checks registered in the Consul. It uses [Consul API](https://www.consul.io/docs/agent/http/health.html#health_state)
to query the data. It will not report the [telemetry](https://www.consul.io/docs/agent/telemetry.html) but Consul can report those stats already using StatsD protocol if needed.
## Configuration:
```
# Gather health check statuses from services registered in Consul
[[inputs.consul]]
## Most of these values defaults to the one configured on a Consul's agent level.
## Optional Consul server address (default: "")
# address = ""
## Optional URI scheme for the Consul server (default: "")
# scheme = ""
## Optional ACL token used in every request (default: "")
# token = ""
## Optional username used for request HTTP Basic Authentication (default: "")
# username = ""
## Optional password used for HTTP Basic Authentication (default: "")
# password = ""
## Optional data centre to query the health checks from (default: "")
# datacentre = ""
```
## Measurements:
### Consul:
Tags:
- node: on which node check/service is registered on
- service_name: name of the service (this is the service name not the service ID)
Fields:
- check_id
- check_name
- service_id
- status
## Example output
```
$ telegraf --config ./telegraf.conf -input-filter consul -test
* Plugin: consul, Collection 1
> consul_health_checks,host=wolfpit,node=consul-server-node check_id="serfHealth",check_name="Serf Health Status",service_id="",status="passing" 1464698464486439902
> consul_health_checks,host=wolfpit,node=consul-server-node,service_name=www.example.com check_id="service:www-example-com.test01",check_name="Service 'www.example.com' check",service_id="www-example-com.test01",status="critical" 1464698464486519036
```

View File

@@ -1,136 +0,0 @@
package consul
import (
"net/http"
"github.com/hashicorp/consul/api"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/plugins/inputs"
)
type Consul struct {
Address string
Scheme string
Token string
Username string
Password string
Datacentre string
// Path to CA file
SSLCA string `toml:"ssl_ca"`
// Path to host cert file
SSLCert string `toml:"ssl_cert"`
// Path to cert key file
SSLKey string `toml:"ssl_key"`
// Use SSL but skip chain & host verification
InsecureSkipVerify bool
// client used to connect to Consul agnet
client *api.Client
}
var sampleConfig = `
## Most of these values defaults to the one configured on a Consul's agent level.
## Optional Consul server address (default: "localhost")
# address = "localhost"
## Optional URI scheme for the Consul server (default: "http")
# scheme = "http"
## Optional ACL token used in every request (default: "")
# token = ""
## Optional username used for request HTTP Basic Authentication (default: "")
# username = ""
## Optional password used for HTTP Basic Authentication (default: "")
# password = ""
## Optional data centre to query the health checks from (default: "")
# datacentre = ""
`
func (c *Consul) Description() string {
return "Gather health check statuses from services registered in Consul"
}
func (c *Consul) SampleConfig() string {
return sampleConfig
}
func (c *Consul) createAPIClient() (*api.Client, error) {
config := api.DefaultConfig()
if c.Address != "" {
config.Address = c.Address
}
if c.Scheme != "" {
config.Scheme = c.Scheme
}
if c.Datacentre != "" {
config.Datacenter = c.Datacentre
}
if c.Username != "" {
config.HttpAuth = &api.HttpBasicAuth{
Username: c.Username,
Password: c.Password,
}
}
tlsCfg, err := internal.GetTLSConfig(
c.SSLCert, c.SSLKey, c.SSLCA, c.InsecureSkipVerify)
if err != nil {
return nil, err
}
config.HttpClient.Transport = &http.Transport{
TLSClientConfig: tlsCfg,
}
return api.NewClient(config)
}
func (c *Consul) GatherHealthCheck(acc telegraf.Accumulator, checks []*api.HealthCheck) {
for _, check := range checks {
record := make(map[string]interface{})
tags := make(map[string]string)
record["check_id"] = check.CheckID
record["check_name"] = check.Name
record["service_id"] = check.ServiceID
record["status"] = check.Status
tags["node"] = check.Node
tags["service_name"] = check.ServiceName
acc.AddFields("consul_health_checks", record, tags)
}
}
func (c *Consul) Gather(acc telegraf.Accumulator) error {
if c.client == nil {
newClient, err := c.createAPIClient()
if err != nil {
return err
}
c.client = newClient
}
checks, _, err := c.client.Health().State("any", nil)
if err != nil {
return err
}
c.GatherHealthCheck(acc, checks)
return nil
}
func init() {
inputs.Add("consul", func() telegraf.Input {
return &Consul{}
})
}

View File

@@ -1,42 +0,0 @@
package consul
import (
"testing"
"github.com/hashicorp/consul/api"
"github.com/influxdata/telegraf/testutil"
)
var sampleChecks = []*api.HealthCheck{
&api.HealthCheck{
Node: "localhost",
CheckID: "foo.health123",
Name: "foo.health",
Status: "passing",
Notes: "lorem ipsum",
Output: "OK",
ServiceID: "foo.123",
ServiceName: "foo",
},
}
func TestGatherHealtCheck(t *testing.T) {
expectedFields := map[string]interface{}{
"check_id": "foo.health123",
"check_name": "foo.health",
"status": "passing",
"service_id": "foo.123",
}
expectedTags := map[string]string{
"node": "localhost",
"service_name": "foo",
}
var acc testutil.Accumulator
consul := &Consul{}
consul.GatherHealthCheck(&acc, sampleChecks)
acc.AssertContainsTaggedFields(t, "consul_health_checks", expectedFields, expectedTags)
}

View File

@@ -221,7 +221,7 @@ func (d *Docker) gatherContainer(
defer cancel() defer cancel()
r, err := d.client.ContainerStats(ctx, container.ID, false) r, err := d.client.ContainerStats(ctx, container.ID, false)
if err != nil { if err != nil {
return fmt.Errorf("Error getting docker stats: %s", err.Error()) log.Printf("Error getting docker stats: %s\n", err.Error())
} }
defer r.Close() defer r.Close()
dec := json.NewDecoder(r) dec := json.NewDecoder(r)
@@ -307,11 +307,7 @@ func gatherContainerStats(
for i, percpu := range stat.CPUStats.CPUUsage.PercpuUsage { for i, percpu := range stat.CPUStats.CPUUsage.PercpuUsage {
percputags := copyTags(tags) percputags := copyTags(tags)
percputags["cpu"] = fmt.Sprintf("cpu%d", i) percputags["cpu"] = fmt.Sprintf("cpu%d", i)
fields := map[string]interface{}{ acc.AddFields("docker_container_cpu", map[string]interface{}{"usage_total": percpu}, percputags, now)
"usage_total": percpu,
"container_id": id,
}
acc.AddFields("docker_container_cpu", fields, percputags, now)
} }
for network, netstats := range stat.Networks { for network, netstats := range stat.Networks {
@@ -470,8 +466,6 @@ func parseSize(sizeStr string) (int64, error) {
func init() { func init() {
inputs.Add("docker", func() telegraf.Input { inputs.Add("docker", func() telegraf.Input {
return &Docker{ return &Docker{}
Timeout: internal.Duration{Duration: time.Second * 5},
}
}) })
} }

View File

@@ -111,15 +111,13 @@ func TestDockerGatherContainerStats(t *testing.T) {
cputags["cpu"] = "cpu0" cputags["cpu"] = "cpu0"
cpu0fields := map[string]interface{}{ cpu0fields := map[string]interface{}{
"usage_total": uint64(1), "usage_total": uint64(1),
"container_id": "123456789",
} }
acc.AssertContainsTaggedFields(t, "docker_container_cpu", cpu0fields, cputags) acc.AssertContainsTaggedFields(t, "docker_container_cpu", cpu0fields, cputags)
cputags["cpu"] = "cpu1" cputags["cpu"] = "cpu1"
cpu1fields := map[string]interface{}{ cpu1fields := map[string]interface{}{
"usage_total": uint64(1002), "usage_total": uint64(1002),
"container_id": "123456789",
} }
acc.AssertContainsTaggedFields(t, "docker_container_cpu", cpu1fields, cputags) acc.AssertContainsTaggedFields(t, "docker_container_cpu", cpu1fields, cputags)
} }
@@ -374,8 +372,7 @@ func TestDockerGatherInfo(t *testing.T) {
acc.AssertContainsTaggedFields(t, acc.AssertContainsTaggedFields(t,
"docker_container_cpu", "docker_container_cpu",
map[string]interface{}{ map[string]interface{}{
"usage_total": uint64(1231652), "usage_total": uint64(1231652),
"container_id": "b7dfbb9478a6ae55e237d4d74f8bbb753f0817192b5081334dc78476296e2173",
}, },
map[string]string{ map[string]string{
"container_name": "etcd2", "container_name": "etcd2",

View File

@@ -1,307 +1,320 @@
# Elasticsearch input plugin # Elasticsearch plugin
#### Plugin arguments:
- **servers** []string: list of one or more Elasticsearch servers
- **local** boolean: If false, it will read the indices stats from all nodes
- **cluster_health** boolean: If true, it will also obtain cluster level stats
#### Description
The [elasticsearch](https://www.elastic.co/) plugin queries endpoints to obtain The [elasticsearch](https://www.elastic.co/) plugin queries endpoints to obtain
[node](https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-nodes-stats.html) [node](https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-nodes-stats.html)
and optionally [cluster](https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-health.html) stats. and optionally [cluster](https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-health.html) stats.
### Configuration: Example:
``` ```
[[inputs.elasticsearch]] [elasticsearch]
servers = ["http://localhost:9200"]
local = true servers = ["http://localhost:9200"]
cluster_health = true
local = true
cluster_health = true
``` ```
### Measurements & Fields: # Measurements
#### cluster measurements (utilizes fields instead of single values):
contains `status`, `timed_out`, `number_of_nodes`, `number_of_data_nodes`,
`active_primary_shards`, `active_shards`, `relocating_shards`,
`initializing_shards`, `unassigned_shards` fields
- elasticsearch_cluster_health
contains `status`, `number_of_shards`, `number_of_replicas`,
`active_primary_shards`, `active_shards`, `relocating_shards`,
`initializing_shards`, `unassigned_shards` fields
- elasticsearch_indices
#### node measurements:
field data circuit breaker measurement names: field data circuit breaker measurement names:
- elasticsearch_breakers - elasticsearch_breakers_fielddata_estimated_size_in_bytes value=0
- fielddata_estimated_size_in_bytes value=0 - elasticsearch_breakers_fielddata_overhead value=1.03
- fielddata_overhead value=1.03 - elasticsearch_breakers_fielddata_tripped value=0
- fielddata_tripped value=0 - elasticsearch_breakers_fielddata_limit_size_in_bytes value=623326003
- fielddata_limit_size_in_bytes value=623326003 - elasticsearch_breakers_request_estimated_size_in_bytes value=0
- request_estimated_size_in_bytes value=0 - elasticsearch_breakers_request_overhead value=1.0
- request_overhead value=1.0 - elasticsearch_breakers_request_tripped value=0
- request_tripped value=0 - elasticsearch_breakers_request_limit_size_in_bytes value=415550668
- request_limit_size_in_bytes value=415550668 - elasticsearch_breakers_parent_overhead value=1.0
- parent_overhead value=1.0 - elasticsearch_breakers_parent_tripped value=0
- parent_tripped value=0 - elasticsearch_breakers_parent_limit_size_in_bytes value=727213670
- parent_limit_size_in_bytes value=727213670 - elasticsearch_breakers_parent_estimated_size_in_bytes value=0
- parent_estimated_size_in_bytes value=0
File system information, data path, free disk space, read/write measurement names: File system information, data path, free disk space, read/write measurement names:
- elasticsearch_fs - elasticsearch_fs_timestamp value=1436460392946
- timestamp value=1436460392946 - elasticsearch_fs_total_free_in_bytes value=16909316096
- total_free_in_bytes value=16909316096 - elasticsearch_fs_total_available_in_bytes value=15894814720
- total_available_in_bytes value=15894814720 - elasticsearch_fs_total_total_in_bytes value=19507089408
- total_total_in_bytes value=19507089408
indices size, document count, indexing and deletion times, search times, indices size, document count, indexing and deletion times, search times,
field cache size, merges and flushes measurement names: field cache size, merges and flushes measurement names:
- elasticsearch_indices - elasticsearch_indices_id_cache_memory_size_in_bytes value=0
- id_cache_memory_size_in_bytes value=0 - elasticsearch_indices_completion_size_in_bytes value=0
- completion_size_in_bytes value=0 - elasticsearch_indices_suggest_total value=0
- suggest_total value=0 - elasticsearch_indices_suggest_time_in_millis value=0
- suggest_time_in_millis value=0 - elasticsearch_indices_suggest_current value=0
- suggest_current value=0 - elasticsearch_indices_query_cache_memory_size_in_bytes value=0
- query_cache_memory_size_in_bytes value=0 - elasticsearch_indices_query_cache_evictions value=0
- query_cache_evictions value=0 - elasticsearch_indices_query_cache_hit_count value=0
- query_cache_hit_count value=0 - elasticsearch_indices_query_cache_miss_count value=0
- query_cache_miss_count value=0 - elasticsearch_indices_store_size_in_bytes value=37715234
- store_size_in_bytes value=37715234 - elasticsearch_indices_store_throttle_time_in_millis value=215
- store_throttle_time_in_millis value=215 - elasticsearch_indices_merges_current_docs value=0
- merges_current_docs value=0 - elasticsearch_indices_merges_current_size_in_bytes value=0
- merges_current_size_in_bytes value=0 - elasticsearch_indices_merges_total value=133
- merges_total value=133 - elasticsearch_indices_merges_total_time_in_millis value=21060
- merges_total_time_in_millis value=21060 - elasticsearch_indices_merges_total_docs value=203672
- merges_total_docs value=203672 - elasticsearch_indices_merges_total_size_in_bytes value=142900226
- merges_total_size_in_bytes value=142900226 - elasticsearch_indices_merges_current value=0
- merges_current value=0 - elasticsearch_indices_filter_cache_memory_size_in_bytes value=7384
- filter_cache_memory_size_in_bytes value=7384 - elasticsearch_indices_filter_cache_evictions value=0
- filter_cache_evictions value=0 - elasticsearch_indices_indexing_index_total value=84790
- indexing_index_total value=84790 - elasticsearch_indices_indexing_index_time_in_millis value=29680
- indexing_index_time_in_millis value=29680 - elasticsearch_indices_indexing_index_current value=0
- indexing_index_current value=0 - elasticsearch_indices_indexing_noop_update_total value=0
- indexing_noop_update_total value=0 - elasticsearch_indices_indexing_throttle_time_in_millis value=0
- indexing_throttle_time_in_millis value=0 - elasticsearch_indices_indexing_delete_tota value=13879
- indexing_delete_tota value=13879 - elasticsearch_indices_indexing_delete_time_in_millis value=1139
- indexing_delete_time_in_millis value=1139 - elasticsearch_indices_indexing_delete_current value=0
- indexing_delete_current value=0 - elasticsearch_indices_get_exists_time_in_millis value=0
- get_exists_time_in_millis value=0 - elasticsearch_indices_get_missing_total value=1
- get_missing_total value=1 - elasticsearch_indices_get_missing_time_in_millis value=2
- get_missing_time_in_millis value=2 - elasticsearch_indices_get_current value=0
- get_current value=0 - elasticsearch_indices_get_total value=1
- get_total value=1 - elasticsearch_indices_get_time_in_millis value=2
- get_time_in_millis value=2 - elasticsearch_indices_get_exists_total value=0
- get_exists_total value=0 - elasticsearch_indices_refresh_total value=1076
- refresh_total value=1076 - elasticsearch_indices_refresh_total_time_in_millis value=20078
- refresh_total_time_in_millis value=20078 - elasticsearch_indices_percolate_current value=0
- percolate_current value=0 - elasticsearch_indices_percolate_memory_size_in_bytes value=-1
- percolate_memory_size_in_bytes value=-1 - elasticsearch_indices_percolate_queries value=0
- percolate_queries value=0 - elasticsearch_indices_percolate_total value=0
- percolate_total value=0 - elasticsearch_indices_percolate_time_in_millis value=0
- percolate_time_in_millis value=0 - elasticsearch_indices_translog_operations value=17702
- translog_operations value=17702 - elasticsearch_indices_translog_size_in_bytes value=17
- translog_size_in_bytes value=17 - elasticsearch_indices_recovery_current_as_source value=0
- recovery_current_as_source value=0 - elasticsearch_indices_recovery_current_as_target value=0
- recovery_current_as_target value=0 - elasticsearch_indices_recovery_throttle_time_in_millis value=0
- recovery_throttle_time_in_millis value=0 - elasticsearch_indices_docs_count value=29652
- docs_count value=29652 - elasticsearch_indices_docs_deleted value=5229
- docs_deleted value=5229 - elasticsearch_indices_flush_total_time_in_millis value=2401
- flush_total_time_in_millis value=2401 - elasticsearch_indices_flush_total value=115
- flush_total value=115 - elasticsearch_indices_fielddata_memory_size_in_bytes value=12996
- fielddata_memory_size_in_bytes value=12996 - elasticsearch_indices_fielddata_evictions value=0
- fielddata_evictions value=0 - elasticsearch_indices_search_fetch_current value=0
- search_fetch_current value=0 - elasticsearch_indices_search_open_contexts value=0
- search_open_contexts value=0 - elasticsearch_indices_search_query_total value=1452
- search_query_total value=1452 - elasticsearch_indices_search_query_time_in_millis value=5695
- search_query_time_in_millis value=5695 - elasticsearch_indices_search_query_current value=0
- search_query_current value=0 - elasticsearch_indices_search_fetch_total value=414
- search_fetch_total value=414 - elasticsearch_indices_search_fetch_time_in_millis value=146
- search_fetch_time_in_millis value=146 - elasticsearch_indices_warmer_current value=0
- warmer_current value=0 - elasticsearch_indices_warmer_total value=2319
- warmer_total value=2319 - elasticsearch_indices_warmer_total_time_in_millis value=448
- warmer_total_time_in_millis value=448 - elasticsearch_indices_segments_count value=134
- segments_count value=134 - elasticsearch_indices_segments_memory_in_bytes value=1285212
- segments_memory_in_bytes value=1285212 - elasticsearch_indices_segments_index_writer_memory_in_bytes value=0
- segments_index_writer_memory_in_bytes value=0 - elasticsearch_indices_segments_index_writer_max_memory_in_bytes value=172368955
- segments_index_writer_max_memory_in_bytes value=172368955 - elasticsearch_indices_segments_version_map_memory_in_bytes value=611844
- segments_version_map_memory_in_bytes value=611844 - elasticsearch_indices_segments_fixed_bit_set_memory_in_bytes value=0
- segments_fixed_bit_set_memory_in_bytes value=0
HTTP connection measurement names: HTTP connection measurement names:
- elasticsearch_http - elasticsearch_http_current_open value=3
- current_open value=3 - elasticsearch_http_total_opened value=3
- total_opened value=3
JVM stats, memory pool information, garbage collection, buffer pools measurement names: JVM stats, memory pool information, garbage collection, buffer pools measurement names:
- elasticsearch_jvm - elasticsearch_jvm_timestamp value=1436460392945
- timestamp value=1436460392945 - elasticsearch_jvm_uptime_in_millis value=202245
- uptime_in_millis value=202245 - elasticsearch_jvm_mem_non_heap_used_in_bytes value=39634576
- mem_non_heap_used_in_bytes value=39634576 - elasticsearch_jvm_mem_non_heap_committed_in_bytes value=40841216
- mem_non_heap_committed_in_bytes value=40841216 - elasticsearch_jvm_mem_pools_young_max_in_bytes value=279183360
- mem_pools_young_max_in_bytes value=279183360 - elasticsearch_jvm_mem_pools_young_peak_used_in_bytes value=71630848
- mem_pools_young_peak_used_in_bytes value=71630848 - elasticsearch_jvm_mem_pools_young_peak_max_in_bytes value=279183360
- mem_pools_young_peak_max_in_bytes value=279183360 - elasticsearch_jvm_mem_pools_young_used_in_bytes value=32685760
- mem_pools_young_used_in_bytes value=32685760 - elasticsearch_jvm_mem_pools_survivor_peak_used_in_bytes value=8912888
- mem_pools_survivor_peak_used_in_bytes value=8912888 - elasticsearch_jvm_mem_pools_survivor_peak_max_in_bytes value=34865152
- mem_pools_survivor_peak_max_in_bytes value=34865152 - elasticsearch_jvm_mem_pools_survivor_used_in_bytes value=8912880
- mem_pools_survivor_used_in_bytes value=8912880 - elasticsearch_jvm_mem_pools_survivor_max_in_bytes value=34865152
- mem_pools_survivor_max_in_bytes value=34865152 - elasticsearch_jvm_mem_pools_old_peak_max_in_bytes value=724828160
- mem_pools_old_peak_max_in_bytes value=724828160 - elasticsearch_jvm_mem_pools_old_used_in_bytes value=11110928
- mem_pools_old_used_in_bytes value=11110928 - elasticsearch_jvm_mem_pools_old_max_in_bytes value=724828160
- mem_pools_old_max_in_bytes value=724828160 - elasticsearch_jvm_mem_pools_old_peak_used_in_bytes value=14354608
- mem_pools_old_peak_used_in_bytes value=14354608 - elasticsearch_jvm_mem_heap_used_in_bytes value=52709568
- mem_heap_used_in_bytes value=52709568 - elasticsearch_jvm_mem_heap_used_percent value=5
- mem_heap_used_percent value=5 - elasticsearch_jvm_mem_heap_committed_in_bytes value=259522560
- mem_heap_committed_in_bytes value=259522560 - elasticsearch_jvm_mem_heap_max_in_bytes value=1038876672
- mem_heap_max_in_bytes value=1038876672 - elasticsearch_jvm_threads_peak_count value=45
- threads_peak_count value=45 - elasticsearch_jvm_threads_count value=44
- threads_count value=44 - elasticsearch_jvm_gc_collectors_young_collection_count value=2
- gc_collectors_young_collection_count value=2 - elasticsearch_jvm_gc_collectors_young_collection_time_in_millis value=98
- gc_collectors_young_collection_time_in_millis value=98 - elasticsearch_jvm_gc_collectors_old_collection_count value=1
- gc_collectors_old_collection_count value=1 - elasticsearch_jvm_gc_collectors_old_collection_time_in_millis value=24
- gc_collectors_old_collection_time_in_millis value=24 - elasticsearch_jvm_buffer_pools_direct_count value=40
- buffer_pools_direct_count value=40 - elasticsearch_jvm_buffer_pools_direct_used_in_bytes value=6304239
- buffer_pools_direct_used_in_bytes value=6304239 - elasticsearch_jvm_buffer_pools_direct_total_capacity_in_bytes value=6304239
- buffer_pools_direct_total_capacity_in_bytes value=6304239 - elasticsearch_jvm_buffer_pools_mapped_count value=0
- buffer_pools_mapped_count value=0 - elasticsearch_jvm_buffer_pools_mapped_used_in_bytes value=0
- buffer_pools_mapped_used_in_bytes value=0 - elasticsearch_jvm_buffer_pools_mapped_total_capacity_in_bytes value=0
- buffer_pools_mapped_total_capacity_in_bytes value=0
TCP information measurement names: TCP information measurement names:
- elasticsearch_network - elasticsearch_network_tcp_in_errs value=0
- tcp_in_errs value=0 - elasticsearch_network_tcp_passive_opens value=16
- tcp_passive_opens value=16 - elasticsearch_network_tcp_curr_estab value=29
- tcp_curr_estab value=29 - elasticsearch_network_tcp_in_segs value=113
- tcp_in_segs value=113 - elasticsearch_network_tcp_out_segs value=97
- tcp_out_segs value=97 - elasticsearch_network_tcp_retrans_segs value=0
- tcp_retrans_segs value=0 - elasticsearch_network_tcp_attempt_fails value=0
- tcp_attempt_fails value=0 - elasticsearch_network_tcp_active_opens value=13
- tcp_active_opens value=13 - elasticsearch_network_tcp_estab_resets value=0
- tcp_estab_resets value=0 - elasticsearch_network_tcp_out_rsts value=0
- tcp_out_rsts value=0
Operating system stats, load average, cpu, mem, swap measurement names: Operating system stats, load average, cpu, mem, swap measurement names:
- elasticsearch_os - elasticsearch_os_swap_used_in_bytes value=0
- swap_used_in_bytes value=0 - elasticsearch_os_swap_free_in_bytes value=487997440
- swap_free_in_bytes value=487997440 - elasticsearch_os_timestamp value=1436460392944
- timestamp value=1436460392944 - elasticsearch_os_uptime_in_millis value=25092
- uptime_in_millis value=25092 - elasticsearch_os_cpu_sys value=0
- cpu_sys value=0 - elasticsearch_os_cpu_user value=0
- cpu_user value=0 - elasticsearch_os_cpu_idle value=99
- cpu_idle value=99 - elasticsearch_os_cpu_usage value=0
- cpu_usage value=0 - elasticsearch_os_cpu_stolen value=0
- cpu_stolen value=0 - elasticsearch_os_mem_free_percent value=74
- mem_free_percent value=74 - elasticsearch_os_mem_used_percent value=25
- mem_used_percent value=25 - elasticsearch_os_mem_actual_free_in_bytes value=1565470720
- mem_actual_free_in_bytes value=1565470720 - elasticsearch_os_mem_actual_used_in_bytes value=534159360
- mem_actual_used_in_bytes value=534159360 - elasticsearch_os_mem_free_in_bytes value=477761536
- mem_free_in_bytes value=477761536 - elasticsearch_os_mem_used_in_bytes value=1621868544
- mem_used_in_bytes value=1621868544
Process statistics, memory consumption, cpu usage, open file descriptors measurement names: Process statistics, memory consumption, cpu usage, open file descriptors measurement names:
- elasticsearch_process - elasticsearch_process_mem_resident_in_bytes value=246382592
- mem_resident_in_bytes value=246382592 - elasticsearch_process_mem_share_in_bytes value=18747392
- mem_share_in_bytes value=18747392 - elasticsearch_process_mem_total_virtual_in_bytes value=4747890688
- mem_total_virtual_in_bytes value=4747890688 - elasticsearch_process_timestamp value=1436460392945
- timestamp value=1436460392945 - elasticsearch_process_open_file_descriptors value=160
- open_file_descriptors value=160 - elasticsearch_process_cpu_total_in_millis value=15480
- cpu_total_in_millis value=15480 - elasticsearch_process_cpu_percent value=2
- cpu_percent value=2 - elasticsearch_process_cpu_sys_in_millis value=1870
- cpu_sys_in_millis value=1870 - elasticsearch_process_cpu_user_in_millis value=13610
- cpu_user_in_millis value=13610
Statistics about each thread pool, including current size, queue and rejected tasks measurement names: Statistics about each thread pool, including current size, queue and rejected tasks measurement names:
- elasticsearch_thread_pool - elasticsearch_thread_pool_merge_threads value=6
- merge_threads value=6 - elasticsearch_thread_pool_merge_queue value=4
- merge_queue value=4 - elasticsearch_thread_pool_merge_active value=5
- merge_active value=5 - elasticsearch_thread_pool_merge_rejected value=2
- merge_rejected value=2 - elasticsearch_thread_pool_merge_largest value=5
- merge_largest value=5 - elasticsearch_thread_pool_merge_completed value=1
- merge_completed value=1 - elasticsearch_thread_pool_bulk_threads value=4
- bulk_threads value=4 - elasticsearch_thread_pool_bulk_queue value=5
- bulk_queue value=5 - elasticsearch_thread_pool_bulk_active value=7
- bulk_active value=7 - elasticsearch_thread_pool_bulk_rejected value=3
- bulk_rejected value=3 - elasticsearch_thread_pool_bulk_largest value=1
- bulk_largest value=1 - elasticsearch_thread_pool_bulk_completed value=4
- bulk_completed value=4 - elasticsearch_thread_pool_warmer_threads value=2
- warmer_threads value=2 - elasticsearch_thread_pool_warmer_queue value=7
- warmer_queue value=7 - elasticsearch_thread_pool_warmer_active value=3
- warmer_active value=3 - elasticsearch_thread_pool_warmer_rejected value=2
- warmer_rejected value=2 - elasticsearch_thread_pool_warmer_largest value=3
- warmer_largest value=3 - elasticsearch_thread_pool_warmer_completed value=1
- warmer_completed value=1 - elasticsearch_thread_pool_get_largest value=2
- get_largest value=2 - elasticsearch_thread_pool_get_completed value=1
- get_completed value=1 - elasticsearch_thread_pool_get_threads value=1
- get_threads value=1 - elasticsearch_thread_pool_get_queue value=8
- get_queue value=8 - elasticsearch_thread_pool_get_active value=4
- get_active value=4 - elasticsearch_thread_pool_get_rejected value=3
- get_rejected value=3 - elasticsearch_thread_pool_index_threads value=6
- index_threads value=6 - elasticsearch_thread_pool_index_queue value=8
- index_queue value=8 - elasticsearch_thread_pool_index_active value=4
- index_active value=4 - elasticsearch_thread_pool_index_rejected value=2
- index_rejected value=2 - elasticsearch_thread_pool_index_largest value=3
- index_largest value=3 - elasticsearch_thread_pool_index_completed value=6
- index_completed value=6 - elasticsearch_thread_pool_suggest_threads value=2
- suggest_threads value=2 - elasticsearch_thread_pool_suggest_queue value=7
- suggest_queue value=7 - elasticsearch_thread_pool_suggest_active value=2
- suggest_active value=2 - elasticsearch_thread_pool_suggest_rejected value=1
- suggest_rejected value=1 - elasticsearch_thread_pool_suggest_largest value=8
- suggest_largest value=8 - elasticsearch_thread_pool_suggest_completed value=3
- suggest_completed value=3 - elasticsearch_thread_pool_fetch_shard_store_queue value=7
- fetch_shard_store_queue value=7 - elasticsearch_thread_pool_fetch_shard_store_active value=4
- fetch_shard_store_active value=4 - elasticsearch_thread_pool_fetch_shard_store_rejected value=2
- fetch_shard_store_rejected value=2 - elasticsearch_thread_pool_fetch_shard_store_largest value=4
- fetch_shard_store_largest value=4 - elasticsearch_thread_pool_fetch_shard_store_completed value=1
- fetch_shard_store_completed value=1 - elasticsearch_thread_pool_fetch_shard_store_threads value=1
- fetch_shard_store_threads value=1 - elasticsearch_thread_pool_management_threads value=2
- management_threads value=2 - elasticsearch_thread_pool_management_queue value=3
- management_queue value=3 - elasticsearch_thread_pool_management_active value=1
- management_active value=1 - elasticsearch_thread_pool_management_rejected value=6
- management_rejected value=6 - elasticsearch_thread_pool_management_largest value=2
- management_largest value=2 - elasticsearch_thread_pool_management_completed value=22
- management_completed value=22 - elasticsearch_thread_pool_percolate_queue value=23
- percolate_queue value=23 - elasticsearch_thread_pool_percolate_active value=13
- percolate_active value=13 - elasticsearch_thread_pool_percolate_rejected value=235
- percolate_rejected value=235 - elasticsearch_thread_pool_percolate_largest value=23
- percolate_largest value=23 - elasticsearch_thread_pool_percolate_completed value=33
- percolate_completed value=33 - elasticsearch_thread_pool_percolate_threads value=123
- percolate_threads value=123 - elasticsearch_thread_pool_listener_active value=4
- listener_active value=4 - elasticsearch_thread_pool_listener_rejected value=8
- listener_rejected value=8 - elasticsearch_thread_pool_listener_largest value=1
- listener_largest value=1 - elasticsearch_thread_pool_listener_completed value=1
- listener_completed value=1 - elasticsearch_thread_pool_listener_threads value=1
- listener_threads value=1 - elasticsearch_thread_pool_listener_queue value=2
- listener_queue value=2 - elasticsearch_thread_pool_search_rejected value=7
- search_rejected value=7 - elasticsearch_thread_pool_search_largest value=2
- search_largest value=2 - elasticsearch_thread_pool_search_completed value=4
- search_completed value=4 - elasticsearch_thread_pool_search_threads value=5
- search_threads value=5 - elasticsearch_thread_pool_search_queue value=7
- search_queue value=7 - elasticsearch_thread_pool_search_active value=2
- search_active value=2 - elasticsearch_thread_pool_fetch_shard_started_threads value=3
- fetch_shard_started_threads value=3 - elasticsearch_thread_pool_fetch_shard_started_queue value=1
- fetch_shard_started_queue value=1 - elasticsearch_thread_pool_fetch_shard_started_active value=5
- fetch_shard_started_active value=5 - elasticsearch_thread_pool_fetch_shard_started_rejected value=6
- fetch_shard_started_rejected value=6 - elasticsearch_thread_pool_fetch_shard_started_largest value=4
- fetch_shard_started_largest value=4 - elasticsearch_thread_pool_fetch_shard_started_completed value=54
- fetch_shard_started_completed value=54 - elasticsearch_thread_pool_refresh_rejected value=4
- refresh_rejected value=4 - elasticsearch_thread_pool_refresh_largest value=8
- refresh_largest value=8 - elasticsearch_thread_pool_refresh_completed value=3
- refresh_completed value=3 - elasticsearch_thread_pool_refresh_threads value=23
- refresh_threads value=23 - elasticsearch_thread_pool_refresh_queue value=7
- refresh_queue value=7 - elasticsearch_thread_pool_refresh_active value=3
- refresh_active value=3 - elasticsearch_thread_pool_optimize_threads value=3
- optimize_threads value=3 - elasticsearch_thread_pool_optimize_queue value=4
- optimize_queue value=4 - elasticsearch_thread_pool_optimize_active value=1
- optimize_active value=1 - elasticsearch_thread_pool_optimize_rejected value=2
- optimize_rejected value=2 - elasticsearch_thread_pool_optimize_largest value=7
- optimize_largest value=7 - elasticsearch_thread_pool_optimize_completed value=3
- optimize_completed value=3 - elasticsearch_thread_pool_snapshot_largest value=1
- snapshot_largest value=1 - elasticsearch_thread_pool_snapshot_completed value=0
- snapshot_completed value=0 - elasticsearch_thread_pool_snapshot_threads value=8
- snapshot_threads value=8 - elasticsearch_thread_pool_snapshot_queue value=5
- snapshot_queue value=5 - elasticsearch_thread_pool_snapshot_active value=6
- snapshot_active value=6 - elasticsearch_thread_pool_snapshot_rejected value=2
- snapshot_rejected value=2 - elasticsearch_thread_pool_generic_threads value=1
- generic_threads value=1 - elasticsearch_thread_pool_generic_queue value=4
- generic_queue value=4 - elasticsearch_thread_pool_generic_active value=6
- generic_active value=6 - elasticsearch_thread_pool_generic_rejected value=3
- generic_rejected value=3 - elasticsearch_thread_pool_generic_largest value=2
- generic_largest value=2 - elasticsearch_thread_pool_generic_completed value=27
- generic_completed value=27 - elasticsearch_thread_pool_flush_threads value=3
- flush_threads value=3 - elasticsearch_thread_pool_flush_queue value=8
- flush_queue value=8 - elasticsearch_thread_pool_flush_active value=0
- flush_active value=0 - elasticsearch_thread_pool_flush_rejected value=1
- flush_rejected value=1 - elasticsearch_thread_pool_flush_largest value=5
- flush_largest value=5 - elasticsearch_thread_pool_flush_completed value=3
- flush_completed value=3
Transport statistics about sent and received bytes in cluster communication measurement names: Transport statistics about sent and received bytes in cluster communication measurement names:
- elasticsearch_transport - elasticsearch_transport_server_open value=13
- server_open value=13 - elasticsearch_transport_rx_count value=6
- rx_count value=6 - elasticsearch_transport_rx_size_in_bytes value=1380
- rx_size_in_bytes value=1380 - elasticsearch_transport_tx_count value=6
- tx_count value=6 - elasticsearch_transport_tx_size_in_bytes value=1380
- tx_size_in_bytes value=1380

View File

@@ -2,13 +2,14 @@ package elasticsearch
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"strings"
"sync" "sync"
"time" "time"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal/errchan"
"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"
) )
@@ -101,7 +102,7 @@ func (e *Elasticsearch) Description() string {
// Gather reads the stats from Elasticsearch and writes it to the // Gather reads the stats from Elasticsearch and writes it to the
// Accumulator. // Accumulator.
func (e *Elasticsearch) Gather(acc telegraf.Accumulator) error { func (e *Elasticsearch) Gather(acc telegraf.Accumulator) error {
errChan := errchan.New(len(e.Servers)) errChan := make(chan error, len(e.Servers))
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(len(e.Servers)) wg.Add(len(e.Servers))
@@ -115,7 +116,7 @@ func (e *Elasticsearch) Gather(acc telegraf.Accumulator) error {
url = s + statsPath url = s + statsPath
} }
if err := e.gatherNodeStats(url, acc); err != nil { if err := e.gatherNodeStats(url, acc); err != nil {
errChan.C <- err errChan <- err
return return
} }
if e.ClusterHealth { if e.ClusterHealth {
@@ -125,7 +126,17 @@ func (e *Elasticsearch) Gather(acc telegraf.Accumulator) error {
} }
wg.Wait() wg.Wait()
return errChan.Error() close(errChan)
// Get all errors and return them as one giant error
errStrings := []string{}
for err := range errChan {
errStrings = append(errStrings, err.Error())
}
if len(errStrings) == 0 {
return nil
}
return errors.New(strings.Join(errStrings, "\n"))
} }
func (e *Elasticsearch) gatherNodeStats(url string, acc telegraf.Accumulator) error { func (e *Elasticsearch) gatherNodeStats(url string, acc telegraf.Accumulator) error {

View File

@@ -6,20 +6,14 @@ Please also see: [Telegraf Input Data Formats](https://github.com/influxdata/tel
#### Configuration #### Configuration
In this example a script called ```/tmp/test.sh```, a script called ```/tmp/test2.sh```, and In this example a script called ```/tmp/test.sh``` and a script called ```/tmp/test2.sh```
all scripts matching glob pattern ```/tmp/collect_*.sh``` are configured for ```[[inputs.exec]]``` are configured for ```[[inputs.exec]]``` in JSON format.
in JSON format. Glob patterns are matched on every run, so adding new scripts that match the pattern
will cause them to be picked up immediately.
```toml ```
# Read flattened metrics from one or more commands that output JSON to stdout # Read flattened metrics from one or more commands that output JSON to stdout
[[inputs.exec]] [[inputs.exec]]
# Shell/commands array # Shell/commands array
# Full command line to executable with parameters, or a glob pattern to run all matching files. commands = ["/tmp/test.sh", "/tmp/test2.sh"]
commands = ["/tmp/test.sh", "/tmp/test2.sh", "/tmp/collect_*.sh"]
## Timeout for each command to complete.
timeout = "5s"
# Data format to consume. # Data format to consume.
# NOTE json only reads numerical measurements, strings and booleans are ignored. # NOTE json only reads numerical measurements, strings and booleans are ignored.
@@ -27,6 +21,26 @@ will cause them to be picked up immediately.
# measurement name suffix (for separating different commands) # measurement name suffix (for separating different commands)
name_suffix = "_mycollector" name_suffix = "_mycollector"
## Below configuration will be used for data_format = "graphite", can be ignored for other data_format
## If matching multiple measurement files, this string will be used to join the matched values.
#separator = "."
## Each template line requires a template pattern. It can have an optional
## filter before the template and separated by spaces. It can also have optional extra
## tags following the template. Multiple tags should be separated by commas and no spaces
## similar to the line protocol format. The can be only one default template.
## Templates support below format:
## 1. filter + template
## 2. filter + template + extra tag
## 3. filter + template with field key
## 4. default template
#templates = [
# "*.app env.service.resource.measurement",
# "stats.* .host.measurement* region=us-west,agent=sensu",
# "stats2.* .host.measurement.field",
# "measurement*"
#]
``` ```
Other options for modifying the measurement names are: Other options for modifying the measurement names are:
@@ -65,7 +79,7 @@ in influx line-protocol format.
#### Configuration #### Configuration
```toml ```
[[inputs.exec]] [[inputs.exec]]
# Shell/commands array # Shell/commands array
# compatible with old version # compatible with old version
@@ -73,9 +87,6 @@ in influx line-protocol format.
# command = "/usr/bin/line_protocol_collector" # command = "/usr/bin/line_protocol_collector"
commands = ["/usr/bin/line_protocol_collector","/tmp/test2.sh"] commands = ["/usr/bin/line_protocol_collector","/tmp/test2.sh"]
## Timeout for each command to complete.
timeout = "5s"
# Data format to consume. # Data format to consume.
# NOTE json only reads numerical measurements, strings and booleans are ignored. # NOTE json only reads numerical measurements, strings and booleans are ignored.
data_format = "influx" data_format = "influx"
@@ -109,16 +120,12 @@ We can also change the data_format to "graphite" to use the metrics collecting s
In this example a script called /tmp/test.sh and a script called /tmp/test2.sh are configured for [[inputs.exec]] in graphite format. In this example a script called /tmp/test.sh and a script called /tmp/test2.sh are configured for [[inputs.exec]] in graphite format.
#### Configuration #### Configuration
```
```toml
# Read flattened metrics from one or more commands that output JSON to stdout # Read flattened metrics from one or more commands that output JSON to stdout
[[inputs.exec]] [[inputs.exec]]
# Shell/commands array # Shell/commands array
commands = ["/tmp/test.sh","/tmp/test2.sh"] commands = ["/tmp/test.sh","/tmp/test2.sh"]
## Timeout for each command to complete.
timeout = "5s"
# Data format to consume. # Data format to consume.
# NOTE json only reads numerical measurements, strings and booleans are ignored. # NOTE json only reads numerical measurements, strings and booleans are ignored.
data_format = "graphite" data_format = "graphite"
@@ -173,3 +180,4 @@ sensu.metric.net.server0.eth0.rx_dropped 0 1444234982
The templates configuration will be used to parse the graphite metrics to support influxdb/opentsdb tagging store engines. The templates configuration will be used to parse the graphite metrics to support influxdb/opentsdb tagging store engines.
More detail information about templates, please refer to [The graphite Input](https://github.com/influxdata/influxdb/blob/master/services/graphite/README.md) More detail information about templates, please refer to [The graphite Input](https://github.com/influxdata/influxdb/blob/master/services/graphite/README.md)

View File

@@ -4,8 +4,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"os/exec" "os/exec"
"path/filepath"
"strings"
"sync" "sync"
"syscall" "syscall"
"time" "time"
@@ -14,7 +12,6 @@ import (
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal" "github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/internal/errchan"
"github.com/influxdata/telegraf/plugins/inputs" "github.com/influxdata/telegraf/plugins/inputs"
"github.com/influxdata/telegraf/plugins/parsers" "github.com/influxdata/telegraf/plugins/parsers"
"github.com/influxdata/telegraf/plugins/parsers/nagios" "github.com/influxdata/telegraf/plugins/parsers/nagios"
@@ -22,11 +19,7 @@ import (
const sampleConfig = ` const sampleConfig = `
## Commands array ## Commands array
commands = [ commands = ["/tmp/test.sh", "/usr/bin/mycollector --foo=bar"]
"/tmp/test.sh",
"/usr/bin/mycollector --foo=bar",
"/tmp/collect_*.sh"
]
## Timeout for each command to complete. ## Timeout for each command to complete.
timeout = "5s" timeout = "5s"
@@ -157,45 +150,23 @@ func (e *Exec) Gather(acc telegraf.Accumulator) error {
e.Command = "" e.Command = ""
} }
commands := make([]string, 0, len(e.Commands)) e.errChan = make(chan error, len(e.Commands))
for _, pattern := range e.Commands {
cmdAndArgs := strings.SplitN(pattern, " ", 2)
if len(cmdAndArgs) == 0 {
continue
}
matches, err := filepath.Glob(cmdAndArgs[0]) e.wg.Add(len(e.Commands))
if err != nil { for _, command := range e.Commands {
return err
}
if len(matches) == 0 {
// There were no matches with the glob pattern, so let's assume
// that the command is in PATH and just run it as it is
commands = append(commands, pattern)
} else {
// There were matches, so we'll append each match together with
// the arguments to the commands slice
for _, match := range matches {
if len(cmdAndArgs) == 1 {
commands = append(commands, match)
} else {
commands = append(commands,
strings.Join([]string{match, cmdAndArgs[1]}, " "))
}
}
}
}
errChan := errchan.New(len(commands))
e.errChan = errChan.C
e.wg.Add(len(commands))
for _, command := range commands {
go e.ProcessCommand(command, acc) go e.ProcessCommand(command, acc)
} }
e.wg.Wait() e.wg.Wait()
return errChan.Error()
select {
default:
close(e.errChan)
return nil
case err := <-e.errChan:
close(e.errChan)
return err
}
} }
func init() { func init() {

View File

@@ -169,51 +169,3 @@ func TestLineProtocolParseMultiple(t *testing.T) {
acc.AssertContainsTaggedFields(t, "cpu", fields, tags) acc.AssertContainsTaggedFields(t, "cpu", fields, tags)
} }
} }
func TestExecCommandWithGlob(t *testing.T) {
parser, _ := parsers.NewValueParser("metric", "string", nil)
e := NewExec()
e.Commands = []string{"/bin/ech* metric_value"}
e.SetParser(parser)
var acc testutil.Accumulator
err := e.Gather(&acc)
require.NoError(t, err)
fields := map[string]interface{}{
"value": "metric_value",
}
acc.AssertContainsFields(t, "metric", fields)
}
func TestExecCommandWithoutGlob(t *testing.T) {
parser, _ := parsers.NewValueParser("metric", "string", nil)
e := NewExec()
e.Commands = []string{"/bin/echo metric_value"}
e.SetParser(parser)
var acc testutil.Accumulator
err := e.Gather(&acc)
require.NoError(t, err)
fields := map[string]interface{}{
"value": "metric_value",
}
acc.AssertContainsFields(t, "metric", fields)
}
func TestExecCommandWithoutGlobAndPath(t *testing.T) {
parser, _ := parsers.NewValueParser("metric", "string", nil)
e := NewExec()
e.Commands = []string{"echo metric_value"}
e.SetParser(parser)
var acc testutil.Accumulator
err := e.Gather(&acc)
require.NoError(t, err)
fields := map[string]interface{}{
"value": "metric_value",
}
acc.AssertContainsFields(t, "metric", fields)
}

View File

@@ -91,12 +91,193 @@ func (gh *GithubWebhooks) eventHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }
func generateEvent(data []byte, event Event) (Event, error) { func newCommitComment(data []byte) (Event, error) {
err := json.Unmarshal(data, event) commitCommentStruct := CommitCommentEvent{}
err := json.Unmarshal(data, &commitCommentStruct)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return event, nil return commitCommentStruct, nil
}
func newCreate(data []byte) (Event, error) {
createStruct := CreateEvent{}
err := json.Unmarshal(data, &createStruct)
if err != nil {
return nil, err
}
return createStruct, nil
}
func newDelete(data []byte) (Event, error) {
deleteStruct := DeleteEvent{}
err := json.Unmarshal(data, &deleteStruct)
if err != nil {
return nil, err
}
return deleteStruct, nil
}
func newDeployment(data []byte) (Event, error) {
deploymentStruct := DeploymentEvent{}
err := json.Unmarshal(data, &deploymentStruct)
if err != nil {
return nil, err
}
return deploymentStruct, nil
}
func newDeploymentStatus(data []byte) (Event, error) {
deploymentStatusStruct := DeploymentStatusEvent{}
err := json.Unmarshal(data, &deploymentStatusStruct)
if err != nil {
return nil, err
}
return deploymentStatusStruct, nil
}
func newFork(data []byte) (Event, error) {
forkStruct := ForkEvent{}
err := json.Unmarshal(data, &forkStruct)
if err != nil {
return nil, err
}
return forkStruct, nil
}
func newGollum(data []byte) (Event, error) {
gollumStruct := GollumEvent{}
err := json.Unmarshal(data, &gollumStruct)
if err != nil {
return nil, err
}
return gollumStruct, nil
}
func newIssueComment(data []byte) (Event, error) {
issueCommentStruct := IssueCommentEvent{}
err := json.Unmarshal(data, &issueCommentStruct)
if err != nil {
return nil, err
}
return issueCommentStruct, nil
}
func newIssues(data []byte) (Event, error) {
issuesStruct := IssuesEvent{}
err := json.Unmarshal(data, &issuesStruct)
if err != nil {
return nil, err
}
return issuesStruct, nil
}
func newMember(data []byte) (Event, error) {
memberStruct := MemberEvent{}
err := json.Unmarshal(data, &memberStruct)
if err != nil {
return nil, err
}
return memberStruct, nil
}
func newMembership(data []byte) (Event, error) {
membershipStruct := MembershipEvent{}
err := json.Unmarshal(data, &membershipStruct)
if err != nil {
return nil, err
}
return membershipStruct, nil
}
func newPageBuild(data []byte) (Event, error) {
pageBuildEvent := PageBuildEvent{}
err := json.Unmarshal(data, &pageBuildEvent)
if err != nil {
return nil, err
}
return pageBuildEvent, nil
}
func newPublic(data []byte) (Event, error) {
publicEvent := PublicEvent{}
err := json.Unmarshal(data, &publicEvent)
if err != nil {
return nil, err
}
return publicEvent, nil
}
func newPullRequest(data []byte) (Event, error) {
pullRequestStruct := PullRequestEvent{}
err := json.Unmarshal(data, &pullRequestStruct)
if err != nil {
return nil, err
}
return pullRequestStruct, nil
}
func newPullRequestReviewComment(data []byte) (Event, error) {
pullRequestReviewCommentStruct := PullRequestReviewCommentEvent{}
err := json.Unmarshal(data, &pullRequestReviewCommentStruct)
if err != nil {
return nil, err
}
return pullRequestReviewCommentStruct, nil
}
func newPush(data []byte) (Event, error) {
pushStruct := PushEvent{}
err := json.Unmarshal(data, &pushStruct)
if err != nil {
return nil, err
}
return pushStruct, nil
}
func newRelease(data []byte) (Event, error) {
releaseStruct := ReleaseEvent{}
err := json.Unmarshal(data, &releaseStruct)
if err != nil {
return nil, err
}
return releaseStruct, nil
}
func newRepository(data []byte) (Event, error) {
repositoryStruct := RepositoryEvent{}
err := json.Unmarshal(data, &repositoryStruct)
if err != nil {
return nil, err
}
return repositoryStruct, nil
}
func newStatus(data []byte) (Event, error) {
statusStruct := StatusEvent{}
err := json.Unmarshal(data, &statusStruct)
if err != nil {
return nil, err
}
return statusStruct, nil
}
func newTeamAdd(data []byte) (Event, error) {
teamAddStruct := TeamAddEvent{}
err := json.Unmarshal(data, &teamAddStruct)
if err != nil {
return nil, err
}
return teamAddStruct, nil
}
func newWatch(data []byte) (Event, error) {
watchStruct := WatchEvent{}
err := json.Unmarshal(data, &watchStruct)
if err != nil {
return nil, err
}
return watchStruct, nil
} }
type newEventError struct { type newEventError struct {
@@ -107,51 +288,51 @@ func (e *newEventError) Error() string {
return e.s return e.s
} }
func NewEvent(data []byte, name string) (Event, error) { func NewEvent(r []byte, t string) (Event, error) {
log.Printf("New %v event received", name) log.Printf("New %v event recieved", t)
switch name { switch t {
case "commit_comment": case "commit_comment":
return generateEvent(data, &CommitCommentEvent{}) return newCommitComment(r)
case "create": case "create":
return generateEvent(data, &CreateEvent{}) return newCreate(r)
case "delete": case "delete":
return generateEvent(data, &DeleteEvent{}) return newDelete(r)
case "deployment": case "deployment":
return generateEvent(data, &DeploymentEvent{}) return newDeployment(r)
case "deployment_status": case "deployment_status":
return generateEvent(data, &DeploymentStatusEvent{}) return newDeploymentStatus(r)
case "fork": case "fork":
return generateEvent(data, &ForkEvent{}) return newFork(r)
case "gollum": case "gollum":
return generateEvent(data, &GollumEvent{}) return newGollum(r)
case "issue_comment": case "issue_comment":
return generateEvent(data, &IssueCommentEvent{}) return newIssueComment(r)
case "issues": case "issues":
return generateEvent(data, &IssuesEvent{}) return newIssues(r)
case "member": case "member":
return generateEvent(data, &MemberEvent{}) return newMember(r)
case "membership": case "membership":
return generateEvent(data, &MembershipEvent{}) return newMembership(r)
case "page_build": case "page_build":
return generateEvent(data, &PageBuildEvent{}) return newPageBuild(r)
case "public": case "public":
return generateEvent(data, &PublicEvent{}) return newPublic(r)
case "pull_request": case "pull_request":
return generateEvent(data, &PullRequestEvent{}) return newPullRequest(r)
case "pull_request_review_comment": case "pull_request_review_comment":
return generateEvent(data, &PullRequestReviewCommentEvent{}) return newPullRequestReviewComment(r)
case "push": case "push":
return generateEvent(data, &PushEvent{}) return newPush(r)
case "release": case "release":
return generateEvent(data, &ReleaseEvent{}) return newRelease(r)
case "repository": case "repository":
return generateEvent(data, &RepositoryEvent{}) return newRepository(r)
case "status": case "status":
return generateEvent(data, &StatusEvent{}) return newStatus(r)
case "team_add": case "team_add":
return generateEvent(data, &TeamAddEvent{}) return newTeamAdd(r)
case "watch": case "watch":
return generateEvent(data, &WatchEvent{}) return newWatch(r)
} }
return nil, &newEventError{"Not a recognized event type"} return nil, &newEventError{"Not a recgonized event type"}
} }

View File

@@ -7,89 +7,231 @@ import (
"testing" "testing"
) )
func GithubWebhookRequest(event string, jsonString string, t *testing.T) { func TestCommitCommentEvent(t *testing.T) {
gh := NewGithubWebhooks() gh := NewGithubWebhooks()
jsonString := CommitCommentEventJSON()
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString)) req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
req.Header.Add("X-Github-Event", event) req.Header.Add("X-Github-Event", "commit_comment")
w := httptest.NewRecorder() w := httptest.NewRecorder()
gh.eventHandler(w, req) gh.eventHandler(w, req)
if w.Code != http.StatusOK { if w.Code != http.StatusOK {
t.Errorf("POST "+event+" returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK) t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
} }
} }
func TestCommitCommentEvent(t *testing.T) {
GithubWebhookRequest("commit_comment", CommitCommentEventJSON(), t)
}
func TestDeleteEvent(t *testing.T) { func TestDeleteEvent(t *testing.T) {
GithubWebhookRequest("delete", DeleteEventJSON(), t) gh := NewGithubWebhooks()
jsonString := DeleteEventJSON()
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
req.Header.Add("X-Github-Event", "delete")
w := httptest.NewRecorder()
gh.eventHandler(w, req)
if w.Code != http.StatusOK {
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
}
} }
func TestDeploymentEvent(t *testing.T) { func TestDeploymentEvent(t *testing.T) {
GithubWebhookRequest("deployment", DeploymentEventJSON(), t) gh := NewGithubWebhooks()
jsonString := DeploymentEventJSON()
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
req.Header.Add("X-Github-Event", "deployment")
w := httptest.NewRecorder()
gh.eventHandler(w, req)
if w.Code != http.StatusOK {
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
}
} }
func TestDeploymentStatusEvent(t *testing.T) { func TestDeploymentStatusEvent(t *testing.T) {
GithubWebhookRequest("deployment_status", DeploymentStatusEventJSON(), t) gh := NewGithubWebhooks()
jsonString := DeploymentStatusEventJSON()
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
req.Header.Add("X-Github-Event", "deployment_status")
w := httptest.NewRecorder()
gh.eventHandler(w, req)
if w.Code != http.StatusOK {
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
}
} }
func TestForkEvent(t *testing.T) { func TestForkEvent(t *testing.T) {
GithubWebhookRequest("fork", ForkEventJSON(), t) gh := NewGithubWebhooks()
jsonString := ForkEventJSON()
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
req.Header.Add("X-Github-Event", "fork")
w := httptest.NewRecorder()
gh.eventHandler(w, req)
if w.Code != http.StatusOK {
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
}
} }
func TestGollumEvent(t *testing.T) { func TestGollumEvent(t *testing.T) {
GithubWebhookRequest("gollum", GollumEventJSON(), t) gh := NewGithubWebhooks()
jsonString := GollumEventJSON()
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
req.Header.Add("X-Github-Event", "gollum")
w := httptest.NewRecorder()
gh.eventHandler(w, req)
if w.Code != http.StatusOK {
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
}
} }
func TestIssueCommentEvent(t *testing.T) { func TestIssueCommentEvent(t *testing.T) {
GithubWebhookRequest("issue_comment", IssueCommentEventJSON(), t) gh := NewGithubWebhooks()
jsonString := IssueCommentEventJSON()
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
req.Header.Add("X-Github-Event", "issue_comment")
w := httptest.NewRecorder()
gh.eventHandler(w, req)
if w.Code != http.StatusOK {
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
}
} }
func TestIssuesEvent(t *testing.T) { func TestIssuesEvent(t *testing.T) {
GithubWebhookRequest("issues", IssuesEventJSON(), t) gh := NewGithubWebhooks()
jsonString := IssuesEventJSON()
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
req.Header.Add("X-Github-Event", "issues")
w := httptest.NewRecorder()
gh.eventHandler(w, req)
if w.Code != http.StatusOK {
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
}
} }
func TestMemberEvent(t *testing.T) { func TestMemberEvent(t *testing.T) {
GithubWebhookRequest("member", MemberEventJSON(), t) gh := NewGithubWebhooks()
jsonString := MemberEventJSON()
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
req.Header.Add("X-Github-Event", "member")
w := httptest.NewRecorder()
gh.eventHandler(w, req)
if w.Code != http.StatusOK {
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
}
} }
func TestMembershipEvent(t *testing.T) { func TestMembershipEvent(t *testing.T) {
GithubWebhookRequest("membership", MembershipEventJSON(), t) gh := NewGithubWebhooks()
jsonString := MembershipEventJSON()
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
req.Header.Add("X-Github-Event", "membership")
w := httptest.NewRecorder()
gh.eventHandler(w, req)
if w.Code != http.StatusOK {
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
}
} }
func TestPageBuildEvent(t *testing.T) { func TestPageBuildEvent(t *testing.T) {
GithubWebhookRequest("page_build", PageBuildEventJSON(), t) gh := NewGithubWebhooks()
jsonString := PageBuildEventJSON()
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
req.Header.Add("X-Github-Event", "page_build")
w := httptest.NewRecorder()
gh.eventHandler(w, req)
if w.Code != http.StatusOK {
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
}
} }
func TestPublicEvent(t *testing.T) { func TestPublicEvent(t *testing.T) {
GithubWebhookRequest("public", PublicEventJSON(), t) gh := NewGithubWebhooks()
jsonString := PublicEventJSON()
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
req.Header.Add("X-Github-Event", "public")
w := httptest.NewRecorder()
gh.eventHandler(w, req)
if w.Code != http.StatusOK {
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
}
} }
func TestPullRequestReviewCommentEvent(t *testing.T) { func TestPullRequestReviewCommentEvent(t *testing.T) {
GithubWebhookRequest("pull_request_review_comment", PullRequestReviewCommentEventJSON(), t) gh := NewGithubWebhooks()
jsonString := PullRequestReviewCommentEventJSON()
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
req.Header.Add("X-Github-Event", "pull_request_review_comment")
w := httptest.NewRecorder()
gh.eventHandler(w, req)
if w.Code != http.StatusOK {
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
}
} }
func TestPushEvent(t *testing.T) { func TestPushEvent(t *testing.T) {
GithubWebhookRequest("push", PushEventJSON(), t) gh := NewGithubWebhooks()
jsonString := PushEventJSON()
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
req.Header.Add("X-Github-Event", "push")
w := httptest.NewRecorder()
gh.eventHandler(w, req)
if w.Code != http.StatusOK {
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
}
} }
func TestReleaseEvent(t *testing.T) { func TestReleaseEvent(t *testing.T) {
GithubWebhookRequest("release", ReleaseEventJSON(), t) gh := NewGithubWebhooks()
jsonString := ReleaseEventJSON()
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
req.Header.Add("X-Github-Event", "release")
w := httptest.NewRecorder()
gh.eventHandler(w, req)
if w.Code != http.StatusOK {
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
}
} }
func TestRepositoryEvent(t *testing.T) { func TestRepositoryEvent(t *testing.T) {
GithubWebhookRequest("repository", RepositoryEventJSON(), t) gh := NewGithubWebhooks()
jsonString := RepositoryEventJSON()
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
req.Header.Add("X-Github-Event", "repository")
w := httptest.NewRecorder()
gh.eventHandler(w, req)
if w.Code != http.StatusOK {
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
}
} }
func TestStatusEvent(t *testing.T) { func TestStatusEvent(t *testing.T) {
GithubWebhookRequest("status", StatusEventJSON(), t) gh := NewGithubWebhooks()
jsonString := StatusEventJSON()
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
req.Header.Add("X-Github-Event", "status")
w := httptest.NewRecorder()
gh.eventHandler(w, req)
if w.Code != http.StatusOK {
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
}
} }
func TestTeamAddEvent(t *testing.T) { func TestTeamAddEvent(t *testing.T) {
GithubWebhookRequest("team_add", TeamAddEventJSON(), t) gh := NewGithubWebhooks()
jsonString := TeamAddEventJSON()
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
req.Header.Add("X-Github-Event", "team_add")
w := httptest.NewRecorder()
gh.eventHandler(w, req)
if w.Code != http.StatusOK {
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
}
} }
func TestWatchEvent(t *testing.T) { func TestWatchEvent(t *testing.T) {
GithubWebhookRequest("watch", WatchEventJSON(), t) gh := NewGithubWebhooks()
jsonString := WatchEventJSON()
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
req.Header.Add("X-Github-Event", "watch")
w := httptest.NewRecorder()
gh.eventHandler(w, req)
if w.Code != http.StatusOK {
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
}
} }

View File

@@ -1,55 +0,0 @@
# GrayLog plugin
The Graylog plugin can collect data from remote Graylog service URLs.
Plugin currently support two type of end points:-
- multiple (Ex http://[graylog-server-ip]:12900/system/metrics/multiple)
- namespace (Ex http://[graylog-server-ip]:12900/system/metrics/namespace/{namespace})
End Point can be a mixe of one multiple end point and several namespaces end points
Note: if namespace end point specified metrics array will be ignored for that call.
### Configuration:
```toml
# Read flattened metrics from one or more GrayLog HTTP endpoints
[[inputs.graylog]]
## API endpoint, currently supported API:
##
## - multiple (Ex http://<host>:12900/system/metrics/multiple)
## - namespace (Ex http://<host>:12900/system/metrics/namespace/{namespace})
##
## For namespace endpoint, the metrics array will be ignored for that call.
## Endpoint can contain namespace and multiple type calls.
##
## Please check http://[graylog-server-ip]:12900/api-browser for full list
## of endpoints
servers = [
"http://[graylog-server-ip]:12900/system/metrics/multiple",
]
## Metrics list
## List of metrics can be found on Graylog webservice documentation.
## Or by hitting the the web service api at:
## http://[graylog-host]:12900/system/metrics
metrics = [
"jvm.cl.loaded",
"jvm.memory.pools.Metaspace.committed"
]
## Username and password
username = ""
password = ""
## 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
```
Please refer to GrayLog metrics api browser for full metric end points http://host:12900/api-browser

View File

@@ -1,312 +0,0 @@
package graylog
import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"strings"
"sync"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/plugins/inputs"
)
type ResponseMetrics struct {
total int
Metrics []Metric `json:"metrics"`
}
type Metric struct {
FullName string `json:"full_name"`
Name string `json:"name"`
Type string `json:"type"`
Fields map[string]interface{} `json:"metric"`
}
type GrayLog struct {
Servers []string
Metrics []string
Username string
Password string
// Path to CA file
SSLCA string `toml:"ssl_ca"`
// Path to host cert file
SSLCert string `toml:"ssl_cert"`
// Path to cert key file
SSLKey string `toml:"ssl_key"`
// Use SSL but skip chain & host verification
InsecureSkipVerify bool
client HTTPClient
}
type HTTPClient interface {
// Returns the result of an http request
//
// Parameters:
// req: HTTP request object
//
// Returns:
// http.Response: HTTP respons object
// error : Any error that may have occurred
MakeRequest(req *http.Request) (*http.Response, error)
SetHTTPClient(client *http.Client)
HTTPClient() *http.Client
}
type Messagebody struct {
Metrics []string `json:"metrics"`
}
type RealHTTPClient struct {
client *http.Client
}
func (c *RealHTTPClient) MakeRequest(req *http.Request) (*http.Response, error) {
return c.client.Do(req)
}
func (c *RealHTTPClient) SetHTTPClient(client *http.Client) {
c.client = client
}
func (c *RealHTTPClient) HTTPClient() *http.Client {
return c.client
}
var sampleConfig = `
## API endpoint, currently supported API:
##
## - multiple (Ex http://<host>:12900/system/metrics/multiple)
## - namespace (Ex http://<host>:12900/system/metrics/namespace/{namespace})
##
## For namespace endpoint, the metrics array will be ignored for that call.
## Endpoint can contain namespace and multiple type calls.
##
## Please check http://[graylog-server-ip]:12900/api-browser for full list
## of endpoints
servers = [
"http://[graylog-server-ip]:12900/system/metrics/multiple",
]
## Metrics list
## List of metrics can be found on Graylog webservice documentation.
## Or by hitting the the web service api at:
## http://[graylog-host]:12900/system/metrics
metrics = [
"jvm.cl.loaded",
"jvm.memory.pools.Metaspace.committed"
]
## Username and password
username = ""
password = ""
## 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
`
func (h *GrayLog) SampleConfig() string {
return sampleConfig
}
func (h *GrayLog) Description() string {
return "Read flattened metrics from one or more GrayLog HTTP endpoints"
}
// Gathers data for all servers.
func (h *GrayLog) Gather(acc telegraf.Accumulator) error {
var wg sync.WaitGroup
if h.client.HTTPClient() == nil {
tlsCfg, err := internal.GetTLSConfig(
h.SSLCert, h.SSLKey, h.SSLCA, h.InsecureSkipVerify)
if err != nil {
return err
}
tr := &http.Transport{
ResponseHeaderTimeout: time.Duration(3 * time.Second),
TLSClientConfig: tlsCfg,
}
client := &http.Client{
Transport: tr,
Timeout: time.Duration(4 * time.Second),
}
h.client.SetHTTPClient(client)
}
errorChannel := make(chan error, len(h.Servers))
for _, server := range h.Servers {
wg.Add(1)
go func(server string) {
defer wg.Done()
if err := h.gatherServer(acc, server); err != nil {
errorChannel <- err
}
}(server)
}
wg.Wait()
close(errorChannel)
// Get all errors and return them as one giant error
errorStrings := []string{}
for err := range errorChannel {
errorStrings = append(errorStrings, err.Error())
}
if len(errorStrings) == 0 {
return nil
}
return errors.New(strings.Join(errorStrings, "\n"))
}
// Gathers data from a particular server
// Parameters:
// acc : The telegraf Accumulator to use
// serverURL: endpoint to send request to
// service : the service being queried
//
// Returns:
// error: Any error that may have occurred
func (h *GrayLog) gatherServer(
acc telegraf.Accumulator,
serverURL string,
) error {
resp, _, err := h.sendRequest(serverURL)
if err != nil {
return err
}
requestURL, err := url.Parse(serverURL)
host, port, _ := net.SplitHostPort(requestURL.Host)
var dat ResponseMetrics
if err != nil {
return err
}
if err := json.Unmarshal([]byte(resp), &dat); err != nil {
return err
}
for _, m_item := range dat.Metrics {
fields := make(map[string]interface{})
tags := map[string]string{
"server": host,
"port": port,
"name": m_item.Name,
"type": m_item.Type,
}
h.flatten(m_item.Fields, fields, "")
acc.AddFields(m_item.FullName, fields, tags)
}
return nil
}
// Flatten JSON hierarchy to produce field name and field value
// Parameters:
// item: Item map to flatten
// fields: Map to store generated fields.
// id: Prefix for top level metric (empty string "")
// Returns:
// void
func (h *GrayLog) flatten(item map[string]interface{}, fields map[string]interface{}, id string) {
if id != "" {
id = id + "_"
}
for k, i := range item {
switch i.(type) {
case int:
fields[id+k] = i.(float64)
case float64:
fields[id+k] = i.(float64)
case map[string]interface{}:
h.flatten(i.(map[string]interface{}), fields, id+k)
default:
}
}
}
// Sends an HTTP request to the server using the GrayLog object's HTTPClient.
// Parameters:
// serverURL: endpoint to send request to
//
// Returns:
// string: body of the response
// error : Any error that may have occurred
func (h *GrayLog) sendRequest(serverURL string) (string, float64, error) {
headers := map[string]string{
"Content-Type": "application/json",
"Accept": "application/json",
}
method := "GET"
content := bytes.NewBufferString("")
headers["Authorization"] = "Basic " + base64.URLEncoding.EncodeToString([]byte(h.Username+":"+h.Password))
// Prepare URL
requestURL, err := url.Parse(serverURL)
if err != nil {
return "", -1, fmt.Errorf("Invalid server URL \"%s\"", serverURL)
}
if strings.Contains(requestURL.String(), "multiple") {
m := &Messagebody{Metrics: h.Metrics}
http_body, err := json.Marshal(m)
if err != nil {
return "", -1, fmt.Errorf("Invalid list of Metrics %s", h.Metrics)
}
method = "POST"
content = bytes.NewBuffer(http_body)
}
req, err := http.NewRequest(method, requestURL.String(), content)
if err != nil {
return "", -1, err
}
// Add header parameters
for k, v := range headers {
req.Header.Add(k, v)
}
start := time.Now()
resp, err := h.client.MakeRequest(req)
if err != nil {
return "", -1, err
}
defer resp.Body.Close()
responseTime := time.Since(start).Seconds()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return string(body), responseTime, err
}
// Process response
if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("Response from url \"%s\" has status code %d (%s), expected %d (%s)",
requestURL.String(),
resp.StatusCode,
http.StatusText(resp.StatusCode),
http.StatusOK,
http.StatusText(http.StatusOK))
return string(body), responseTime, err
}
return string(body), responseTime, err
}
func init() {
inputs.Add("graylog", func() telegraf.Input {
return &GrayLog{
client: &RealHTTPClient{},
}
})
}

View File

@@ -1,199 +0,0 @@
package graylog
import (
"io/ioutil"
"net/http"
"strings"
"testing"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const validJSON = `
{
"total": 3,
"metrics": [
{
"full_name": "jvm.cl.loaded",
"metric": {
"value": 18910
},
"name": "loaded",
"type": "gauge"
},
{
"full_name": "jvm.memory.pools.Metaspace.committed",
"metric": {
"value": 108040192
},
"name": "committed",
"type": "gauge"
},
{
"full_name": "org.graylog2.shared.journal.KafkaJournal.writeTime",
"metric": {
"time": {
"min": 99
},
"rate": {
"total": 10,
"mean": 2
},
"duration_unit": "microseconds",
"rate_unit": "events/second"
},
"name": "writeTime",
"type": "hdrtimer"
}
]
}`
var validTags = map[string]map[string]string{
"jvm.cl.loaded": {
"name": "loaded",
"type": "gauge",
"port": "12900",
"server": "localhost",
},
"jvm.memory.pools.Metaspace.committed": {
"name": "committed",
"type": "gauge",
"port": "12900",
"server": "localhost",
},
"org.graylog2.shared.journal.KafkaJournal.writeTime": {
"name": "writeTime",
"type": "hdrtimer",
"port": "12900",
"server": "localhost",
},
}
var expectedFields = map[string]map[string]interface{}{
"jvm.cl.loaded": {
"value": float64(18910),
},
"jvm.memory.pools.Metaspace.committed": {
"value": float64(108040192),
},
"org.graylog2.shared.journal.KafkaJournal.writeTime": {
"time_min": float64(99),
"rate_total": float64(10),
"rate_mean": float64(2),
},
}
const invalidJSON = "I don't think this is JSON"
const empty = ""
type mockHTTPClient struct {
responseBody string
statusCode int
}
// Mock implementation of MakeRequest. Usually returns an http.Response with
// hard-coded responseBody and statusCode. However, if the request uses a
// nonstandard method, it uses status code 405 (method not allowed)
func (c *mockHTTPClient) MakeRequest(req *http.Request) (*http.Response, error) {
resp := http.Response{}
resp.StatusCode = c.statusCode
// basic error checking on request method
allowedMethods := []string{"GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT"}
methodValid := false
for _, method := range allowedMethods {
if req.Method == method {
methodValid = true
break
}
}
if !methodValid {
resp.StatusCode = 405 // Method not allowed
}
resp.Body = ioutil.NopCloser(strings.NewReader(c.responseBody))
return &resp, nil
}
func (c *mockHTTPClient) SetHTTPClient(_ *http.Client) {
}
func (c *mockHTTPClient) HTTPClient() *http.Client {
return nil
}
// Generates a pointer to an HttpJson object that uses a mock HTTP client.
// Parameters:
// response : Body of the response that the mock HTTP client should return
// statusCode: HTTP status code the mock HTTP client should return
//
// Returns:
// *HttpJson: Pointer to an HttpJson object that uses the generated mock HTTP client
func genMockGrayLog(response string, statusCode int) []*GrayLog {
return []*GrayLog{
&GrayLog{
client: &mockHTTPClient{responseBody: response, statusCode: statusCode},
Servers: []string{
"http://localhost:12900/system/metrics/multiple",
},
Metrics: []string{
"jvm.memory.pools.Metaspace.committed",
"jvm.cl.loaded",
"org.graylog2.shared.journal.KafkaJournal.writeTime",
},
Username: "test",
Password: "test",
},
}
}
// Test that the proper values are ignored or collected
func TestNormalResponse(t *testing.T) {
graylog := genMockGrayLog(validJSON, 200)
for _, service := range graylog {
var acc testutil.Accumulator
err := service.Gather(&acc)
require.NoError(t, err)
for k, v := range expectedFields {
acc.AssertContainsTaggedFields(t, k, v, validTags[k])
}
}
}
// Test response to HTTP 500
func TestHttpJson500(t *testing.T) {
graylog := genMockGrayLog(validJSON, 500)
var acc testutil.Accumulator
err := graylog[0].Gather(&acc)
assert.NotNil(t, err)
assert.Equal(t, 0, acc.NFields())
}
// Test response to malformed JSON
func TestHttpJsonBadJson(t *testing.T) {
graylog := genMockGrayLog(invalidJSON, 200)
var acc testutil.Accumulator
err := graylog[0].Gather(&acc)
assert.NotNil(t, err)
assert.Equal(t, 0, acc.NFields())
}
// Test response to empty string as response objectgT
func TestHttpJsonEmptyResponse(t *testing.T) {
graylog := genMockGrayLog(empty, 200)
var acc testutil.Accumulator
err := graylog[0].Gather(&acc)
assert.NotNil(t, err)
assert.Equal(t, 0, acc.NFields())
}

View File

@@ -3,6 +3,8 @@ package haproxy
import ( import (
"encoding/csv" "encoding/csv"
"fmt" "fmt"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
"io" "io"
"net" "net"
"net/http" "net/http"
@@ -11,10 +13,6 @@ import (
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal/errchan"
"github.com/influxdata/telegraf/plugins/inputs"
) )
//CSV format: https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#9.1 //CSV format: https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#9.1
@@ -115,17 +113,20 @@ func (g *haproxy) Gather(acc telegraf.Accumulator) error {
} }
var wg sync.WaitGroup var wg sync.WaitGroup
errChan := errchan.New(len(g.Servers))
wg.Add(len(g.Servers)) var outerr error
for _, server := range g.Servers {
for _, serv := range g.Servers {
wg.Add(1)
go func(serv string) { go func(serv string) {
defer wg.Done() defer wg.Done()
errChan.C <- g.gatherServer(serv, acc) outerr = g.gatherServer(serv, acc)
}(server) }(serv)
} }
wg.Wait() wg.Wait()
return errChan.Error()
return outerr
} }
func (g *haproxy) gatherServerSocket(addr string, acc telegraf.Accumulator) error { func (g *haproxy) gatherServerSocket(addr string, acc telegraf.Accumulator) error {

View File

@@ -5,30 +5,23 @@ This input plugin will test HTTP/HTTPS connections.
### Configuration: ### Configuration:
``` ```
# HTTP/HTTPS request given an address a method and a timeout # List of UDP/TCP connections you want to check
[[inputs.http_response]] [[inputs.http_response]]
## Server address (default http://localhost) ## Server address (default http://localhost)
address = "http://github.com" address = "http://github.com"
## Set response_timeout (default 5 seconds) ## Set response_timeout (default 5 seconds)
response_timeout = "5s" response_timeout = 5
## HTTP Request Method ## HTTP Request Method
method = "GET" method = "GET"
## HTTP Request Headers
[inputs.http_response.headers]
Host = github.com
## Whether to follow redirects from the server (defaults to false) ## Whether to follow redirects from the server (defaults to false)
follow_redirects = true follow_redirects = true
## HTTP Request Headers (all values must be strings)
# [inputs.http_response.headers]
# Host = "github.com"
## Optional HTTP Request Body ## Optional HTTP Request Body
# body = ''' body = '''
# {'fake':'data'} {'fake':'data'}
# ''' '''
## 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
``` ```
### Measurements & Fields: ### Measurements & Fields:

View File

@@ -9,7 +9,6 @@ import (
"time" "time"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/plugins/inputs" "github.com/influxdata/telegraf/plugins/inputs"
) )
@@ -18,18 +17,9 @@ type HTTPResponse struct {
Address string Address string
Body string Body string
Method string Method string
ResponseTimeout internal.Duration ResponseTimeout int
Headers map[string]string Headers map[string]string
FollowRedirects bool FollowRedirects bool
// Path to CA file
SSLCA string `toml:"ssl_ca"`
// Path to host cert file
SSLCert string `toml:"ssl_cert"`
// Path to cert key file
SSLKey string `toml:"ssl_key"`
// Use SSL but skip chain & host verification
InsecureSkipVerify bool
} }
// Description returns the plugin Description // Description returns the plugin Description
@@ -41,7 +31,7 @@ var sampleConfig = `
## Server address (default http://localhost) ## Server address (default http://localhost)
address = "http://github.com" address = "http://github.com"
## Set response_timeout (default 5 seconds) ## Set response_timeout (default 5 seconds)
response_timeout = "5s" response_timeout = 5
## HTTP Request Method ## HTTP Request Method
method = "GET" method = "GET"
## Whether to follow redirects from the server (defaults to false) ## Whether to follow redirects from the server (defaults to false)
@@ -53,13 +43,6 @@ var sampleConfig = `
# body = ''' # body = '''
# {'fake':'data'} # {'fake':'data'}
# ''' # '''
## 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
` `
// SampleConfig returns the plugin SampleConfig // SampleConfig returns the plugin SampleConfig
@@ -72,27 +55,27 @@ var ErrRedirectAttempted = errors.New("redirect")
// CreateHttpClient creates an http client which will timeout at the specified // CreateHttpClient creates an http client which will timeout at the specified
// timeout period and can follow redirects if specified // timeout period and can follow redirects if specified
func (h *HTTPResponse) createHttpClient() (*http.Client, error) { func CreateHttpClient(followRedirects bool, ResponseTimeout time.Duration) *http.Client {
tlsCfg, err := internal.GetTLSConfig(
h.SSLCert, h.SSLKey, h.SSLCA, h.InsecureSkipVerify)
if err != nil {
return nil, err
}
tr := &http.Transport{
ResponseHeaderTimeout: h.ResponseTimeout.Duration,
TLSClientConfig: tlsCfg,
}
client := &http.Client{ client := &http.Client{
Transport: tr, Timeout: time.Second * ResponseTimeout,
Timeout: h.ResponseTimeout.Duration,
} }
if h.FollowRedirects == false { if followRedirects == false {
client.CheckRedirect = func(req *http.Request, via []*http.Request) error { client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return ErrRedirectAttempted return ErrRedirectAttempted
} }
} }
return client, nil return client
}
// CreateHeaders takes a map of header strings and puts them
// into a http.Header Object
func CreateHeaders(headers map[string]string) http.Header {
httpHeaders := make(http.Header)
for key := range headers {
httpHeaders.Add(key, headers[key])
}
return httpHeaders
} }
// HTTPGather gathers all fields and returns any errors it encounters // HTTPGather gathers all fields and returns any errors it encounters
@@ -100,10 +83,7 @@ func (h *HTTPResponse) HTTPGather() (map[string]interface{}, error) {
// Prepare fields // Prepare fields
fields := make(map[string]interface{}) fields := make(map[string]interface{})
client, err := h.createHttpClient() client := CreateHttpClient(h.FollowRedirects, time.Duration(h.ResponseTimeout))
if err != nil {
return nil, err
}
var body io.Reader var body io.Reader
if h.Body != "" { if h.Body != "" {
@@ -113,13 +93,7 @@ func (h *HTTPResponse) HTTPGather() (map[string]interface{}, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
request.Header = CreateHeaders(h.Headers)
for key, val := range h.Headers {
request.Header.Add(key, val)
if key == "Host" {
request.Host = val
}
}
// Start Timer // Start Timer
start := time.Now() start := time.Now()
@@ -143,8 +117,8 @@ func (h *HTTPResponse) HTTPGather() (map[string]interface{}, error) {
// Gather gets all metric fields and tags and returns any errors it encounters // Gather gets all metric fields and tags and returns any errors it encounters
func (h *HTTPResponse) Gather(acc telegraf.Accumulator) error { func (h *HTTPResponse) Gather(acc telegraf.Accumulator) error {
// Set default values // Set default values
if h.ResponseTimeout.Duration < time.Second { if h.ResponseTimeout < 1 {
h.ResponseTimeout.Duration = time.Second * 5 h.ResponseTimeout = 5
} }
// Check send and expected string // Check send and expected string
if h.Method == "" { if h.Method == "" {

View File

@@ -2,18 +2,29 @@ package http_response
import ( import (
"fmt" "fmt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"time" "time"
"github.com/influxdata/telegraf/internal"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestCreateHeaders(t *testing.T) {
fakeHeaders := map[string]string{
"Accept": "text/plain",
"Content-Type": "application/json",
"Cache-Control": "no-cache",
}
headers := CreateHeaders(fakeHeaders)
testHeaders := make(http.Header)
testHeaders.Add("Accept", "text/plain")
testHeaders.Add("Content-Type", "application/json")
testHeaders.Add("Cache-Control", "no-cache")
assert.Equal(t, testHeaders, headers)
}
func setUpTestMux() http.Handler { func setUpTestMux() http.Handler {
mux := http.NewServeMux() mux := http.NewServeMux()
mux.HandleFunc("/redirect", func(w http.ResponseWriter, req *http.Request) { mux.HandleFunc("/redirect", func(w http.ResponseWriter, req *http.Request) {
@@ -52,33 +63,6 @@ func setUpTestMux() http.Handler {
return mux return mux
} }
func TestHeaders(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cHeader := r.Header.Get("Content-Type")
assert.Equal(t, "Hello", r.Host)
assert.Equal(t, "application/json", cHeader)
w.WriteHeader(http.StatusOK)
}))
defer ts.Close()
h := &HTTPResponse{
Address: ts.URL,
Method: "GET",
ResponseTimeout: internal.Duration{Duration: time.Second * 2},
Headers: map[string]string{
"Content-Type": "application/json",
"Host": "Hello",
},
}
fields, err := h.HTTPGather()
require.NoError(t, err)
assert.NotEmpty(t, fields)
if assert.NotNil(t, fields["http_response_code"]) {
assert.Equal(t, http.StatusOK, fields["http_response_code"])
}
assert.NotNil(t, fields["response_time"])
}
func TestFields(t *testing.T) { func TestFields(t *testing.T) {
mux := setUpTestMux() mux := setUpTestMux()
ts := httptest.NewServer(mux) ts := httptest.NewServer(mux)
@@ -88,7 +72,7 @@ func TestFields(t *testing.T) {
Address: ts.URL + "/good", Address: ts.URL + "/good",
Body: "{ 'test': 'data'}", Body: "{ 'test': 'data'}",
Method: "GET", Method: "GET",
ResponseTimeout: internal.Duration{Duration: time.Second * 20}, ResponseTimeout: 20,
Headers: map[string]string{ Headers: map[string]string{
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
@@ -101,6 +85,7 @@ func TestFields(t *testing.T) {
assert.Equal(t, http.StatusOK, fields["http_response_code"]) assert.Equal(t, http.StatusOK, fields["http_response_code"])
} }
assert.NotNil(t, fields["response_time"]) assert.NotNil(t, fields["response_time"])
} }
func TestRedirects(t *testing.T) { func TestRedirects(t *testing.T) {
@@ -112,7 +97,7 @@ func TestRedirects(t *testing.T) {
Address: ts.URL + "/redirect", Address: ts.URL + "/redirect",
Body: "{ 'test': 'data'}", Body: "{ 'test': 'data'}",
Method: "GET", Method: "GET",
ResponseTimeout: internal.Duration{Duration: time.Second * 20}, ResponseTimeout: 20,
Headers: map[string]string{ Headers: map[string]string{
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
@@ -129,7 +114,7 @@ func TestRedirects(t *testing.T) {
Address: ts.URL + "/badredirect", Address: ts.URL + "/badredirect",
Body: "{ 'test': 'data'}", Body: "{ 'test': 'data'}",
Method: "GET", Method: "GET",
ResponseTimeout: internal.Duration{Duration: time.Second * 20}, ResponseTimeout: 20,
Headers: map[string]string{ Headers: map[string]string{
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
@@ -148,7 +133,7 @@ func TestMethod(t *testing.T) {
Address: ts.URL + "/mustbepostmethod", Address: ts.URL + "/mustbepostmethod",
Body: "{ 'test': 'data'}", Body: "{ 'test': 'data'}",
Method: "POST", Method: "POST",
ResponseTimeout: internal.Duration{Duration: time.Second * 20}, ResponseTimeout: 20,
Headers: map[string]string{ Headers: map[string]string{
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
@@ -165,7 +150,7 @@ func TestMethod(t *testing.T) {
Address: ts.URL + "/mustbepostmethod", Address: ts.URL + "/mustbepostmethod",
Body: "{ 'test': 'data'}", Body: "{ 'test': 'data'}",
Method: "GET", Method: "GET",
ResponseTimeout: internal.Duration{Duration: time.Second * 20}, ResponseTimeout: 20,
Headers: map[string]string{ Headers: map[string]string{
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
@@ -183,7 +168,7 @@ func TestMethod(t *testing.T) {
Address: ts.URL + "/mustbepostmethod", Address: ts.URL + "/mustbepostmethod",
Body: "{ 'test': 'data'}", Body: "{ 'test': 'data'}",
Method: "head", Method: "head",
ResponseTimeout: internal.Duration{Duration: time.Second * 20}, ResponseTimeout: 20,
Headers: map[string]string{ Headers: map[string]string{
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
@@ -206,7 +191,7 @@ func TestBody(t *testing.T) {
Address: ts.URL + "/musthaveabody", Address: ts.URL + "/musthaveabody",
Body: "{ 'test': 'data'}", Body: "{ 'test': 'data'}",
Method: "GET", Method: "GET",
ResponseTimeout: internal.Duration{Duration: time.Second * 20}, ResponseTimeout: 20,
Headers: map[string]string{ Headers: map[string]string{
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
@@ -222,7 +207,7 @@ func TestBody(t *testing.T) {
h = &HTTPResponse{ h = &HTTPResponse{
Address: ts.URL + "/musthaveabody", Address: ts.URL + "/musthaveabody",
Method: "GET", Method: "GET",
ResponseTimeout: internal.Duration{Duration: time.Second * 20}, ResponseTimeout: 20,
Headers: map[string]string{ Headers: map[string]string{
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
@@ -245,7 +230,7 @@ func TestTimeout(t *testing.T) {
Address: ts.URL + "/twosecondnap", Address: ts.URL + "/twosecondnap",
Body: "{ 'test': 'data'}", Body: "{ 'test': 'data'}",
Method: "GET", Method: "GET",
ResponseTimeout: internal.Duration{Duration: time.Second * 1}, ResponseTimeout: 1,
Headers: map[string]string{ Headers: map[string]string{
"Content-Type": "application/json", "Content-Type": "application/json",
}, },

View File

@@ -0,0 +1,23 @@
# igloo Input Plugin
The igloo plugin "tails" a logfile and parses each log message.
By default, the igloo plugin acts like the following unix tail command:
```
tail -F --lines=0 myfile.log
```
- `-F` means that it will follow the _name_ of the given file, so
that it will be compatible with log-rotated files, and that it will retry on
inaccessible files.
- `--lines=0` means that it will start at the end of the file (unless
the `from_beginning` option is set).
see http://man7.org/linux/man-pages/man1/tail.1.html for more details.
### Configuration:
```toml
```

View File

@@ -0,0 +1,331 @@
package igloo
import (
"fmt"
"log"
"regexp"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/hpcloud/tail"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal/globpath"
"github.com/influxdata/telegraf/plugins/inputs"
)
// format of timestamps
const (
rfcFormat string = "%s-%s-%sT%s:%s:%s.%sZ"
)
var (
// regex for finding timestamps
tRe = regexp.MustCompile(`Timestamp=((\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2}),(\d+))`)
)
type Tail struct {
Files []string
FromBeginning bool
TagKeys []string
Counters []string
NumFields []string
StrFields []string
numfieldsRe map[string]*regexp.Regexp
strfieldsRe map[string]*regexp.Regexp
countersRe map[string]*regexp.Regexp
tagsRe map[string]*regexp.Regexp
counters map[string]map[string]int64
tailers []*tail.Tail
wg sync.WaitGroup
acc telegraf.Accumulator
sync.Mutex
}
func NewTail() *Tail {
return &Tail{
FromBeginning: false,
}
}
const sampleConfig = `
## logfiles to parse.
##
## These accept standard unix glob matching rules, but with the addition of
## ** as a "super asterisk". ie:
## "/var/log/**.log" -> recursively find all .log files in /var/log
## "/var/log/*/*.log" -> find all .log files with a parent dir in /var/log
## "/var/log/apache.log" -> just tail the apache log file
##
## See https://github.com/gobwas/glob for more examples
##
files = ["$HOME/sample.log"]
## Read file from beginning.
from_beginning = false
## Each log message is searched for these tag keys in TagKey=Value format.
## Any that are found will be tagged on the resulting influx measurements.
tag_keys = [
"HostLocal",
"ProductName",
"OperationName",
]
## counters are keys which are treated as counters.
## so if counters = ["Result"], then this means that the following ocurrence
## on a log line:
## Result=Success
## would be treated as a counter: Result_Success, and it will be incremented
## for every occurrence, until Telegraf is restarted.
counters = ["Result"]
## num_fields are log line occurrences that are translated into numerical
## fields. ie:
## Duration=1
num_fields = ["Duration", "Attempt"]
## str_fields are log line occurences that are translated into string fields,
## ie:
## ActivityGUID=0bb03bf4-ae1d-4487-bb6f-311653b35760
str_fields = ["ActivityGUID"]
`
func (t *Tail) SampleConfig() string {
return sampleConfig
}
func (t *Tail) Description() string {
return "Stream an igloo file, like the tail -f command"
}
func (t *Tail) Gather(acc telegraf.Accumulator) error {
return nil
}
func (t *Tail) buildRegexes() error {
t.numfieldsRe = make(map[string]*regexp.Regexp)
t.strfieldsRe = make(map[string]*regexp.Regexp)
t.tagsRe = make(map[string]*regexp.Regexp)
t.countersRe = make(map[string]*regexp.Regexp)
t.counters = make(map[string]map[string]int64)
for _, field := range t.NumFields {
re, err := regexp.Compile(field + `=([0-9\.]+)`)
if err != nil {
return err
}
t.numfieldsRe[field] = re
}
for _, field := range t.StrFields {
re, err := regexp.Compile(field + `=([0-9a-zA-Z\.\-]+)`)
if err != nil {
return err
}
t.strfieldsRe[field] = re
}
for _, field := range t.TagKeys {
re, err := regexp.Compile(field + `=([0-9a-zA-Z\.\-]+)`)
if err != nil {
return err
}
t.tagsRe[field] = re
}
for _, field := range t.Counters {
re, err := regexp.Compile("(" + field + ")" + `=([0-9a-zA-Z\.\-]+)`)
if err != nil {
return err
}
t.countersRe[field] = re
}
return nil
}
func (t *Tail) Start(acc telegraf.Accumulator) error {
t.Lock()
defer t.Unlock()
t.acc = acc
if err := t.buildRegexes(); err != nil {
return err
}
var seek tail.SeekInfo
if !t.FromBeginning {
seek.Whence = 2
seek.Offset = 0
}
var errS string
// Create a "tailer" for each file
for _, filepath := range t.Files {
g, err := globpath.Compile(filepath)
if err != nil {
log.Printf("ERROR Glob %s failed to compile, %s", filepath, err)
}
for file, _ := range g.Match() {
tailer, err := tail.TailFile(file,
tail.Config{
ReOpen: true,
Follow: true,
Location: &seek,
})
if err != nil {
errS += err.Error() + " "
continue
}
// create a goroutine for each "tailer"
go t.receiver(tailer)
t.tailers = append(t.tailers, tailer)
}
}
if errS != "" {
return fmt.Errorf(errS)
}
return nil
}
// this is launched as a goroutine to continuously watch a tailed logfile
// for changes, parse any incoming msgs, and add to the accumulator.
func (t *Tail) receiver(tailer *tail.Tail) {
t.wg.Add(1)
defer t.wg.Done()
var err error
var line *tail.Line
for line = range tailer.Lines {
if line.Err != nil {
log.Printf("ERROR tailing file %s, Error: %s\n",
tailer.Filename, err)
continue
}
err = t.Parse(line.Text)
if err != nil {
log.Printf("ERROR: %s", err)
}
}
}
func (t *Tail) Parse(line string) error {
// find the timestamp:
match := tRe.FindAllStringSubmatch(line, -1)
if len(match) < 1 {
return nil
}
if len(match[0]) < 9 {
return nil
}
// make an rfc3339 timestamp and parse it:
ts, err := time.Parse(time.RFC3339Nano,
fmt.Sprintf(rfcFormat, match[0][2], match[0][3], match[0][4], match[0][5], match[0][6], match[0][7], match[0][8]))
if err != nil {
return nil
}
fields := make(map[string]interface{})
tags := make(map[string]string)
// parse numerical fields:
for name, re := range t.numfieldsRe {
match := re.FindAllStringSubmatch(line, -1)
if len(match) < 1 {
continue
}
if len(match[0]) < 2 {
continue
}
num, err := strconv.ParseFloat(match[0][1], 64)
if err == nil {
fields[name] = num
}
}
// parse string fields:
for name, re := range t.strfieldsRe {
match := re.FindAllStringSubmatch(line, -1)
if len(match) < 1 {
continue
}
if len(match[0]) < 2 {
continue
}
fields[name] = match[0][1]
}
// parse tags:
for name, re := range t.tagsRe {
match := re.FindAllStringSubmatch(line, -1)
if len(match) < 1 {
continue
}
if len(match[0]) < 2 {
continue
}
tags[name] = match[0][1]
}
if len(t.countersRe) > 0 {
// Make a unique key for the measurement name/tags
var tg []string
for k, v := range tags {
tg = append(tg, fmt.Sprintf("%s=%s", k, v))
}
sort.Strings(tg)
hash := fmt.Sprintf("%s%s", strings.Join(tg, ""), "igloo")
// check if this hash already has a counter map
_, ok := t.counters[hash]
if !ok {
// doesnt have counter map, so make one
t.counters[hash] = make(map[string]int64)
}
// search for counter matches:
for _, re := range t.countersRe {
match := re.FindAllStringSubmatch(line, -1)
if len(match) < 1 {
continue
}
if len(match[0]) < 3 {
continue
}
counterName := match[0][1] + "_" + match[0][2]
// increment this counter
t.counters[hash][counterName] += 1
// add this counter to the output fields
fields[counterName] = t.counters[hash][counterName]
}
}
t.acc.AddFields("igloo", fields, tags, ts)
return nil
}
func (t *Tail) Stop() {
t.Lock()
defer t.Unlock()
for _, t := range t.tailers {
err := t.Stop()
if err != nil {
log.Printf("ERROR stopping tail on file %s\n", t.Filename)
}
t.Cleanup()
}
t.wg.Wait()
}
func init() {
inputs.Add("igloo", func() telegraf.Input {
return NewTail()
})
}

View File

@@ -15,7 +15,6 @@ InfluxDB-formatted endpoints. See below for more information.
## See the influxdb plugin's README for more details. ## See the influxdb plugin's README for more details.
## Multiple URLs from which to read InfluxDB-formatted JSON ## Multiple URLs from which to read InfluxDB-formatted JSON
## Default is "http://localhost:8086/debug/vars".
urls = [ urls = [
"http://localhost:8086/debug/vars" "http://localhost:8086/debug/vars"
] ]
@@ -23,78 +22,16 @@ InfluxDB-formatted endpoints. See below for more information.
### Measurements & Fields ### Measurements & Fields
- influxdb
- n_shards
- influxdb_database - influxdb_database
- influxdb_httpd - influxdb_httpd
- influxdb_measurement - influxdb_measurement
- influxdb_memstats - influxdb_memstats
- heap_inuse
- heap_released
- mspan_inuse
- total_alloc
- sys
- mallocs
- frees
- heap_idle
- pause_total_ns
- lookups
- heap_sys
- mcache_sys
- next_gc
- gcc_pu_fraction
- other_sys
- alloc
- stack_inuse
- stack_sys
- buck_hash_sys
- gc_sys
- num_gc
- heap_alloc
- heap_objects
- mspan_sys
- mcache_inuse
- last_gc
- influxdb_shard - influxdb_shard
- influxdb_subscriber - influxdb_subscriber
- influxdb_tsm1_cache - influxdb_tsm1_cache
- influxdb_tsm1_wal - influxdb_tsm1_wal
- influxdb_write - influxdb_write
### Example Output:
```
telegraf -config ~/ws/telegraf.conf -input-filter influxdb -test
* Plugin: influxdb, Collection 1
> influxdb_database,database=_internal,host=tyrion,url=http://localhost:8086/debug/vars numMeasurements=10,numSeries=29 1463590500247354636
> influxdb_httpd,bind=:8086,host=tyrion,url=http://localhost:8086/debug/vars req=7,reqActive=1,reqDurationNs=14227734 1463590500247354636
> influxdb_measurement,database=_internal,host=tyrion,measurement=database,url=http://localhost:8086/debug/vars numSeries=1 1463590500247354636
> influxdb_measurement,database=_internal,host=tyrion,measurement=httpd,url=http://localhost:8086/debug/vars numSeries=1 1463590500247354636
> influxdb_measurement,database=_internal,host=tyrion,measurement=measurement,url=http://localhost:8086/debug/vars numSeries=10 1463590500247354636
> influxdb_measurement,database=_internal,host=tyrion,measurement=runtime,url=http://localhost:8086/debug/vars numSeries=1 1463590500247354636
> influxdb_measurement,database=_internal,host=tyrion,measurement=shard,url=http://localhost:8086/debug/vars numSeries=4 1463590500247354636
> influxdb_measurement,database=_internal,host=tyrion,measurement=subscriber,url=http://localhost:8086/debug/vars numSeries=1 1463590500247354636
> influxdb_measurement,database=_internal,host=tyrion,measurement=tsm1_cache,url=http://localhost:8086/debug/vars numSeries=4 1463590500247354636
> influxdb_measurement,database=_internal,host=tyrion,measurement=tsm1_filestore,url=http://localhost:8086/debug/vars numSeries=2 1463590500247354636
> influxdb_measurement,database=_internal,host=tyrion,measurement=tsm1_wal,url=http://localhost:8086/debug/vars numSeries=4 1463590500247354636
> influxdb_measurement,database=_internal,host=tyrion,measurement=write,url=http://localhost:8086/debug/vars numSeries=1 1463590500247354636
> influxdb_memstats,host=tyrion,url=http://localhost:8086/debug/vars alloc=7642384i,buck_hash_sys=1463471i,frees=1169558i,gc_sys=653312i,gcc_pu_fraction=0.00003825652361068311,heap_alloc=7642384i,heap_idle=9912320i,heap_inuse=9125888i,heap_objects=48276i,heap_released=0i,heap_sys=19038208i,last_gc=1463590480877651621i,lookups=90i,mallocs=1217834i,mcache_inuse=4800i,mcache_sys=16384i,mspan_inuse=70920i,mspan_sys=81920i,next_gc=11679787i,num_gc=141i,other_sys=1244233i,pause_total_ns=24034027i,stack_inuse=884736i,stack_sys=884736i,sys=23382264i,total_alloc=679012200i 1463590500277918755
> influxdb_shard,database=_internal,engine=tsm1,host=tyrion,id=4,path=/Users/sparrc/.influxdb/data/_internal/monitor/4,retentionPolicy=monitor,url=http://localhost:8086/debug/vars fieldsCreate=65,seriesCreate=26,writePointsOk=7274,writeReq=280 1463590500247354636
> influxdb_subscriber,host=tyrion,url=http://localhost:8086/debug/vars pointsWritten=7274 1463590500247354636
> influxdb_tsm1_cache,database=_internal,host=tyrion,path=/Users/sparrc/.influxdb/data/_internal/monitor/1,retentionPolicy=monitor,url=http://localhost:8086/debug/vars WALCompactionTimeMs=0,cacheAgeMs=2809192,cachedBytes=0,diskBytes=0,memBytes=0,snapshotCount=0 1463590500247354636
> influxdb_tsm1_cache,database=_internal,host=tyrion,path=/Users/sparrc/.influxdb/data/_internal/monitor/2,retentionPolicy=monitor,url=http://localhost:8086/debug/vars WALCompactionTimeMs=0,cacheAgeMs=2809184,cachedBytes=0,diskBytes=0,memBytes=0,snapshotCount=0 1463590500247354636
> influxdb_tsm1_cache,database=_internal,host=tyrion,path=/Users/sparrc/.influxdb/data/_internal/monitor/3,retentionPolicy=monitor,url=http://localhost:8086/debug/vars WALCompactionTimeMs=0,cacheAgeMs=2809180,cachedBytes=0,diskBytes=0,memBytes=42368,snapshotCount=0 1463590500247354636
> influxdb_tsm1_cache,database=_internal,host=tyrion,path=/Users/sparrc/.influxdb/data/_internal/monitor/4,retentionPolicy=monitor,url=http://localhost:8086/debug/vars WALCompactionTimeMs=0,cacheAgeMs=2799155,cachedBytes=0,diskBytes=0,memBytes=331216,snapshotCount=0 1463590500247354636
> influxdb_tsm1_filestore,database=_internal,host=tyrion,path=/Users/sparrc/.influxdb/data/_internal/monitor/1,retentionPolicy=monitor,url=http://localhost:8086/debug/vars diskBytes=37892 1463590500247354636
> influxdb_tsm1_filestore,database=_internal,host=tyrion,path=/Users/sparrc/.influxdb/data/_internal/monitor/2,retentionPolicy=monitor,url=http://localhost:8086/debug/vars diskBytes=52907 1463590500247354636
> influxdb_tsm1_wal,database=_internal,host=tyrion,path=/Users/sparrc/.influxdb/wal/_internal/monitor/1,retentionPolicy=monitor,url=http://localhost:8086/debug/vars currentSegmentDiskBytes=0,oldSegmentsDiskBytes=0 1463590500247354636
> influxdb_tsm1_wal,database=_internal,host=tyrion,path=/Users/sparrc/.influxdb/wal/_internal/monitor/2,retentionPolicy=monitor,url=http://localhost:8086/debug/vars currentSegmentDiskBytes=0,oldSegmentsDiskBytes=0 1463590500247354636
> influxdb_tsm1_wal,database=_internal,host=tyrion,path=/Users/sparrc/.influxdb/wal/_internal/monitor/3,retentionPolicy=monitor,url=http://localhost:8086/debug/vars currentSegmentDiskBytes=0,oldSegmentsDiskBytes=65651 1463590500247354636
> influxdb_tsm1_wal,database=_internal,host=tyrion,path=/Users/sparrc/.influxdb/wal/_internal/monitor/4,retentionPolicy=monitor,url=http://localhost:8086/debug/vars currentSegmentDiskBytes=495687,oldSegmentsDiskBytes=0 1463590500247354636
> influxdb_write,host=tyrion,url=http://localhost:8086/debug/vars pointReq=7274,pointReqLocal=7274,req=280,subWriteOk=280,writeOk=280 1463590500247354636
> influxdb_shard,host=tyrion n_shards=4i 1463590500247354636
```
### InfluxDB-formatted endpoints ### InfluxDB-formatted endpoints
The influxdb plugin can collect InfluxDB-formatted data from JSON endpoints. The influxdb plugin can collect InfluxDB-formatted data from JSON endpoints.
@@ -109,3 +46,65 @@ With a configuration of:
"http://192.168.2.1:8086/debug/vars" "http://192.168.2.1:8086/debug/vars"
] ]
``` ```
And if 127.0.0.1 responds with this JSON:
```json
{
"k1": {
"name": "fruit",
"tags": {
"kind": "apple"
},
"values": {
"inventory": 371,
"sold": 112
}
},
"k2": {
"name": "fruit",
"tags": {
"kind": "banana"
},
"values": {
"inventory": 1000,
"sold": 403
}
}
}
```
And if 192.168.2.1 responds like so:
```json
{
"k3": {
"name": "transactions",
"tags": {},
"values": {
"total": 100,
"balance": 184.75
}
}
}
```
Then the collected metrics will be:
```
influxdb_fruit,url='http://127.0.0.1:8086/debug/vars',kind='apple' inventory=371.0,sold=112.0
influxdb_fruit,url='http://127.0.0.1:8086/debug/vars',kind='banana' inventory=1000.0,sold=403.0
influxdb_transactions,url='http://192.168.2.1:8086/debug/vars' total=100.0,balance=184.75
```
There are two important details to note about the collected metrics:
1. Even though the values in JSON are being displayed as integers,
the metrics are reported as floats.
JSON encoders usually don't print the fractional part for round floats.
Because you cannot change the type of an existing field in InfluxDB,
we assume all numbers are floats.
2. The top-level keys' names (in the example above, `"k1"`, `"k2"`, and `"k3"`)
are not considered when recording the metrics.

View File

@@ -28,7 +28,6 @@ func (*InfluxDB) SampleConfig() string {
## See the influxdb plugin's README for more details. ## See the influxdb plugin's README for more details.
## Multiple URLs from which to read InfluxDB-formatted JSON ## Multiple URLs from which to read InfluxDB-formatted JSON
## Default is "http://localhost:8086/debug/vars".
urls = [ urls = [
"http://localhost:8086/debug/vars" "http://localhost:8086/debug/vars"
] ]
@@ -36,9 +35,6 @@ func (*InfluxDB) SampleConfig() string {
} }
func (i *InfluxDB) Gather(acc telegraf.Accumulator) error { func (i *InfluxDB) Gather(acc telegraf.Accumulator) error {
if len(i.URLs) == 0 {
i.URLs = []string{"http://localhost:8086/debug/vars"}
}
errorChannel := make(chan error, len(i.URLs)) errorChannel := make(chan error, len(i.URLs))
var wg sync.WaitGroup var wg sync.WaitGroup
@@ -124,9 +120,6 @@ func (i *InfluxDB) gatherURL(
acc telegraf.Accumulator, acc telegraf.Accumulator,
url string, url string,
) error { ) error {
shardCounter := 0
now := time.Now()
resp, err := client.Get(url) resp, err := client.Get(url)
if err != nil { if err != nil {
return err return err
@@ -161,45 +154,43 @@ func (i *InfluxDB) gatherURL(
return err return err
} }
if keyStr, ok := key.(string); ok { if key.(string) == "memstats" {
if keyStr == "memstats" { var m memstats
var m memstats if err := dec.Decode(&m); err != nil {
if err := dec.Decode(&m); err != nil { continue
continue
}
acc.AddFields("influxdb_memstats",
map[string]interface{}{
"alloc": m.Alloc,
"total_alloc": m.TotalAlloc,
"sys": m.Sys,
"lookups": m.Lookups,
"mallocs": m.Mallocs,
"frees": m.Frees,
"heap_alloc": m.HeapAlloc,
"heap_sys": m.HeapSys,
"heap_idle": m.HeapIdle,
"heap_inuse": m.HeapInuse,
"heap_released": m.HeapReleased,
"heap_objects": m.HeapObjects,
"stack_inuse": m.StackInuse,
"stack_sys": m.StackSys,
"mspan_inuse": m.MSpanInuse,
"mspan_sys": m.MSpanSys,
"mcache_inuse": m.MCacheInuse,
"mcache_sys": m.MCacheSys,
"buck_hash_sys": m.BuckHashSys,
"gc_sys": m.GCSys,
"other_sys": m.OtherSys,
"next_gc": m.NextGC,
"last_gc": m.LastGC,
"pause_total_ns": m.PauseTotalNs,
"num_gc": m.NumGC,
"gcc_pu_fraction": m.GCCPUFraction,
},
map[string]string{
"url": url,
})
} }
acc.AddFields("influxdb_memstats",
map[string]interface{}{
"alloc": m.Alloc,
"total_alloc": m.TotalAlloc,
"sys": m.Sys,
"lookups": m.Lookups,
"mallocs": m.Mallocs,
"frees": m.Frees,
"heap_alloc": m.HeapAlloc,
"heap_sys": m.HeapSys,
"heap_idle": m.HeapIdle,
"heap_inuse": m.HeapInuse,
"heap_released": m.HeapReleased,
"heap_objects": m.HeapObjects,
"stack_inuse": m.StackInuse,
"stack_sys": m.StackSys,
"mspan_inuse": m.MSpanInuse,
"mspan_sys": m.MSpanSys,
"mcache_inuse": m.MCacheInuse,
"mcache_sys": m.MCacheSys,
"buck_hash_sys": m.BuckHashSys,
"gc_sys": m.GCSys,
"other_sys": m.OtherSys,
"next_gc": m.NextGC,
"last_gc": m.LastGC,
"pause_total_ns": m.PauseTotalNs,
"num_gc": m.NumGC,
"gcc_pu_fraction": m.GCCPUFraction,
},
map[string]string{
"url": url,
})
} }
// Attempt to parse a whole object into a point. // Attempt to parse a whole object into a point.
@@ -216,10 +207,6 @@ func (i *InfluxDB) gatherURL(
continue continue
} }
if p.Name == "shard" {
shardCounter++
}
// Add a tag to indicate the source of the data. // Add a tag to indicate the source of the data.
p.Tags["url"] = url p.Tags["url"] = url
@@ -227,18 +214,9 @@ func (i *InfluxDB) gatherURL(
"influxdb_"+p.Name, "influxdb_"+p.Name,
p.Values, p.Values,
p.Tags, p.Tags,
now,
) )
} }
acc.AddFields("influxdb",
map[string]interface{}{
"n_shards": shardCounter,
},
nil,
now,
)
return nil return nil
} }

View File

@@ -27,7 +27,7 @@ func TestBasic(t *testing.T) {
var acc testutil.Accumulator var acc testutil.Accumulator
require.NoError(t, plugin.Gather(&acc)) require.NoError(t, plugin.Gather(&acc))
require.Len(t, acc.Metrics, 3) require.Len(t, acc.Metrics, 2)
fields := map[string]interface{}{ fields := map[string]interface{}{
// JSON will truncate floats to integer representations. // JSON will truncate floats to integer representations.
// Since there's no distinction in JSON, we can't assume it's an int. // Since there's no distinction in JSON, we can't assume it's an int.
@@ -50,11 +50,6 @@ func TestBasic(t *testing.T) {
"url": fakeServer.URL + "/endpoint", "url": fakeServer.URL + "/endpoint",
} }
acc.AssertContainsTaggedFields(t, "influxdb_bar", fields, tags) acc.AssertContainsTaggedFields(t, "influxdb_bar", fields, tags)
acc.AssertContainsTaggedFields(t, "influxdb",
map[string]interface{}{
"n_shards": 0,
}, map[string]string{})
} }
func TestInfluxDB(t *testing.T) { func TestInfluxDB(t *testing.T) {
@@ -74,7 +69,7 @@ func TestInfluxDB(t *testing.T) {
var acc testutil.Accumulator var acc testutil.Accumulator
require.NoError(t, plugin.Gather(&acc)) require.NoError(t, plugin.Gather(&acc))
require.Len(t, acc.Metrics, 34) require.Len(t, acc.Metrics, 33)
fields := map[string]interface{}{ fields := map[string]interface{}{
"heap_inuse": int64(18046976), "heap_inuse": int64(18046976),
@@ -109,11 +104,6 @@ func TestInfluxDB(t *testing.T) {
"url": fakeInfluxServer.URL + "/endpoint", "url": fakeInfluxServer.URL + "/endpoint",
} }
acc.AssertContainsTaggedFields(t, "influxdb_memstats", fields, tags) acc.AssertContainsTaggedFields(t, "influxdb_memstats", fields, tags)
acc.AssertContainsTaggedFields(t, "influxdb",
map[string]interface{}{
"n_shards": 1,
}, map[string]string{})
} }
func TestErrorHandling(t *testing.T) { func TestErrorHandling(t *testing.T) {

View File

@@ -50,7 +50,7 @@ var sampleConfig = `
## an array of Zookeeper connection strings ## an array of Zookeeper connection strings
zookeeper_peers = ["localhost:2181"] zookeeper_peers = ["localhost:2181"]
## Zookeeper Chroot ## Zookeeper Chroot
zookeeper_chroot = "" zookeeper_chroot = "/"
## the name of the consumer group ## the name of the consumer group
consumer_group = "telegraf_metrics_consumers" consumer_group = "telegraf_metrics_consumers"
## Offset (must be either "oldest" or "newest") ## Offset (must be either "oldest" or "newest")

View File

@@ -1,89 +0,0 @@
# logparser Input Plugin
The logparser plugin streams and parses the given logfiles. Currently it only
has the capability of parsing "grok" patterns from logfiles, which also supports
regex patterns.
### Configuration:
```toml
[[inputs.logparser]]
## Log files to parse.
## These accept standard unix glob matching rules, but with the addition of
## ** as a "super asterisk". ie:
## /var/log/**.log -> recursively find all .log files in /var/log
## /var/log/*/*.log -> find all .log files with a parent dir in /var/log
## /var/log/apache.log -> only tail the apache log file
files = ["/var/log/influxdb/influxdb.log"]
## Read file from beginning.
from_beginning = false
## Parse logstash-style "grok" patterns:
## Telegraf builtin parsing patterns: https://goo.gl/dkay10
[inputs.logparser.grok]
## This is a list of patterns to check the given log file(s) for.
## Note that adding patterns here increases processing time. The most
## efficient configuration is to have one file & pattern per logparser.
patterns = ["%{INFLUXDB_HTTPD_LOG}"]
## Full path(s) to custom pattern files.
custom_pattern_files = []
## Custom patterns can also be defined here. Put one pattern per line.
custom_patterns = '''
'''
```
## Grok Parser
The grok parser uses a slightly modified version of logstash "grok" patterns,
with the format `%{<capture_syntax>[:<semantic_name>][:<modifier>]}`
Telegraf has many of it's own
[built-in patterns](https://github.com/influxdata/telegraf/blob/master/plugins/inputs/logparser/grok/patterns/influx-patterns),
as well as supporting
[logstash's builtin patterns](https://github.com/logstash-plugins/logstash-patterns-core/blob/master/patterns/grok-patterns).
The best way to get acquainted with grok patterns is to read the logstash docs,
which are available here:
https://www.elastic.co/guide/en/logstash/current/plugins-filters-grok.html
If you need help building patterns to match your logs,
you will find the http://grokdebug.herokuapp.com application quite useful!
By default all named captures are converted into string fields.
Modifiers can be used to convert captures to other types or tags.
Timestamp modifiers can be used to convert captures to the timestamp of the
parsed metric.
- Available modifiers:
- string (default if nothing is specified)
- int
- float
- duration (ie, 5.23ms gets converted to int nanoseconds)
- tag (converts the field into a tag)
- drop (drops the field completely)
- Timestamp modifiers:
- ts-ansic ("Mon Jan _2 15:04:05 2006")
- ts-unix ("Mon Jan _2 15:04:05 MST 2006")
- ts-ruby ("Mon Jan 02 15:04:05 -0700 2006")
- ts-rfc822 ("02 Jan 06 15:04 MST")
- ts-rfc822z ("02 Jan 06 15:04 -0700")
- ts-rfc850 ("Monday, 02-Jan-06 15:04:05 MST")
- ts-rfc1123 ("Mon, 02 Jan 2006 15:04:05 MST")
- ts-rfc1123z ("Mon, 02 Jan 2006 15:04:05 -0700")
- ts-rfc3339 ("2006-01-02T15:04:05Z07:00")
- ts-rfc3339nano ("2006-01-02T15:04:05.999999999Z07:00")
- ts-httpd ("02/Jan/2006:15:04:05 -0700")
- ts-epoch (seconds since unix epoch)
- ts-epochnano (nanoseconds since unix epoch)
- ts-"CUSTOM"
CUSTOM time layouts must be within quotes and be the representation of the
"reference time", which is `Mon Jan 2 15:04:05 -0700 MST 2006`
See https://golang.org/pkg/time/#Parse for more details.

View File

@@ -1,373 +0,0 @@
package grok
import (
"bufio"
"fmt"
"log"
"os"
"regexp"
"strconv"
"strings"
"time"
"github.com/vjeantet/grok"
"github.com/influxdata/telegraf"
)
var timeFormats = map[string]string{
"ts-ansic": "Mon Jan _2 15:04:05 2006",
"ts-unix": "Mon Jan _2 15:04:05 MST 2006",
"ts-ruby": "Mon Jan 02 15:04:05 -0700 2006",
"ts-rfc822": "02 Jan 06 15:04 MST",
"ts-rfc822z": "02 Jan 06 15:04 -0700", // RFC822 with numeric zone
"ts-rfc850": "Monday, 02-Jan-06 15:04:05 MST",
"ts-rfc1123": "Mon, 02 Jan 2006 15:04:05 MST",
"ts-rfc1123z": "Mon, 02 Jan 2006 15:04:05 -0700", // RFC1123 with numeric zone
"ts-rfc3339": "2006-01-02T15:04:05Z07:00",
"ts-rfc3339nano": "2006-01-02T15:04:05.999999999Z07:00",
"ts-httpd": "02/Jan/2006:15:04:05 -0700",
"ts-epoch": "EPOCH",
"ts-epochnano": "EPOCH_NANO",
}
const (
INT = "int"
TAG = "tag"
FLOAT = "float"
STRING = "string"
DURATION = "duration"
DROP = "drop"
)
var (
// matches named captures that contain a type.
// ie,
// %{NUMBER:bytes:int}
// %{IPORHOST:clientip:tag}
// %{HTTPDATE:ts1:ts-http}
// %{HTTPDATE:ts2:ts-"02 Jan 06 15:04"}
typedRe = regexp.MustCompile(`%{\w+:(\w+):(ts-".+"|t?s?-?\w+)}`)
// matches a plain pattern name. ie, %{NUMBER}
patternOnlyRe = regexp.MustCompile(`%{(\w+)}`)
)
type Parser struct {
Patterns []string
CustomPatterns string
CustomPatternFiles []string
// typeMap is a map of patterns -> capture name -> modifier,
// ie, {
// "%{TESTLOG}":
// {
// "bytes": "int",
// "clientip": "tag"
// }
// }
typeMap map[string]map[string]string
// tsMap is a map of patterns -> capture name -> timestamp layout.
// ie, {
// "%{TESTLOG}":
// {
// "httptime": "02/Jan/2006:15:04:05 -0700"
// }
// }
tsMap map[string]map[string]string
// patterns is a map of all of the parsed patterns from CustomPatterns
// and CustomPatternFiles.
// ie, {
// "DURATION": "%{NUMBER}[nuµm]?s"
// "RESPONSE_CODE": "%{NUMBER:rc:tag}"
// }
patterns map[string]string
g *grok.Grok
tsModder *tsModder
}
func (p *Parser) Compile() error {
p.typeMap = make(map[string]map[string]string)
p.tsMap = make(map[string]map[string]string)
p.patterns = make(map[string]string)
p.tsModder = &tsModder{}
var err error
p.g, err = grok.NewWithConfig(&grok.Config{NamedCapturesOnly: true})
if err != nil {
return err
}
p.CustomPatterns = DEFAULT_PATTERNS + p.CustomPatterns
if len(p.CustomPatterns) != 0 {
scanner := bufio.NewScanner(strings.NewReader(p.CustomPatterns))
p.addCustomPatterns(scanner)
}
for _, filename := range p.CustomPatternFiles {
file, err := os.Open(filename)
if err != nil {
return err
}
scanner := bufio.NewScanner(bufio.NewReader(file))
p.addCustomPatterns(scanner)
}
return p.compileCustomPatterns()
}
func (p *Parser) ParseLine(line string) (telegraf.Metric, error) {
var err error
var values map[string]string
// the matching pattern string
var patternName string
for _, pattern := range p.Patterns {
if values, err = p.g.Parse(pattern, line); err != nil {
return nil, err
}
if len(values) != 0 {
patternName = pattern
break
}
}
if len(values) == 0 {
return nil, nil
}
fields := make(map[string]interface{})
tags := make(map[string]string)
timestamp := time.Now()
for k, v := range values {
if k == "" || v == "" {
continue
}
var t string
// check if pattern has some modifiers
if types, ok := p.typeMap[patternName]; ok {
t = types[k]
}
// if we didn't find a modifier, check if we have a timestamp layout
if t == "" {
if ts, ok := p.tsMap[patternName]; ok {
// check if the modifier is a timestamp layout
if layout, ok := ts[k]; ok {
t = layout
}
}
}
// if we didn't find a type OR timestamp modifier, assume string
if t == "" {
t = STRING
}
switch t {
case INT:
iv, err := strconv.ParseInt(v, 10, 64)
if err != nil {
log.Printf("ERROR parsing %s to int: %s", v, err)
} else {
fields[k] = iv
}
case FLOAT:
fv, err := strconv.ParseFloat(v, 64)
if err != nil {
log.Printf("ERROR parsing %s to float: %s", v, err)
} else {
fields[k] = fv
}
case DURATION:
d, err := time.ParseDuration(v)
if err != nil {
log.Printf("ERROR parsing %s to duration: %s", v, err)
} else {
fields[k] = int64(d)
}
case TAG:
tags[k] = v
case STRING:
fields[k] = strings.Trim(v, `"`)
case "EPOCH":
iv, err := strconv.ParseInt(v, 10, 64)
if err != nil {
log.Printf("ERROR parsing %s to int: %s", v, err)
} else {
timestamp = time.Unix(iv, 0)
}
case "EPOCH_NANO":
iv, err := strconv.ParseInt(v, 10, 64)
if err != nil {
log.Printf("ERROR parsing %s to int: %s", v, err)
} else {
timestamp = time.Unix(0, iv)
}
case DROP:
// goodbye!
default:
ts, err := time.Parse(t, v)
if err == nil {
timestamp = ts
} else {
log.Printf("ERROR parsing %s to time layout [%s]: %s", v, t, err)
}
}
}
return telegraf.NewMetric("logparser_grok", tags, fields, p.tsModder.tsMod(timestamp))
}
func (p *Parser) addCustomPatterns(scanner *bufio.Scanner) {
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if len(line) > 0 && line[0] != '#' {
names := strings.SplitN(line, " ", 2)
p.patterns[names[0]] = names[1]
}
}
}
func (p *Parser) compileCustomPatterns() error {
var err error
// check if the pattern contains a subpattern that is already defined
// replace it with the subpattern for modifier inheritance.
for i := 0; i < 2; i++ {
for name, pattern := range p.patterns {
subNames := patternOnlyRe.FindAllStringSubmatch(pattern, -1)
for _, subName := range subNames {
if subPattern, ok := p.patterns[subName[1]]; ok {
pattern = strings.Replace(pattern, subName[0], subPattern, 1)
}
}
p.patterns[name] = pattern
}
}
// check if pattern contains modifiers. Parse them out if it does.
for name, pattern := range p.patterns {
if typedRe.MatchString(pattern) {
// this pattern has modifiers, so parse out the modifiers
pattern, err = p.parseTypedCaptures(name, pattern)
if err != nil {
return err
}
p.patterns[name] = pattern
}
}
return p.g.AddPatternsFromMap(p.patterns)
}
// parseTypedCaptures parses the capture types, and then deletes the type from
// the line so that it is a valid "grok" pattern again.
// ie,
// %{NUMBER:bytes:int} => %{NUMBER:bytes} (stores %{NUMBER}->bytes->int)
// %{IPORHOST:clientip:tag} => %{IPORHOST:clientip} (stores %{IPORHOST}->clientip->tag)
func (p *Parser) parseTypedCaptures(name, pattern string) (string, error) {
matches := typedRe.FindAllStringSubmatch(pattern, -1)
// grab the name of the capture pattern
patternName := "%{" + name + "}"
// create type map for this pattern
p.typeMap[patternName] = make(map[string]string)
p.tsMap[patternName] = make(map[string]string)
// boolean to verify that each pattern only has a single ts- data type.
hasTimestamp := false
for _, match := range matches {
// regex capture 1 is the name of the capture
// regex capture 2 is the type of the capture
if strings.HasPrefix(match[2], "ts-") {
if hasTimestamp {
return pattern, fmt.Errorf("logparser pattern compile error: "+
"Each pattern is allowed only one named "+
"timestamp data type. pattern: %s", pattern)
}
if f, ok := timeFormats[match[2]]; ok {
p.tsMap[patternName][match[1]] = f
} else {
p.tsMap[patternName][match[1]] = strings.TrimSuffix(strings.TrimPrefix(match[2], `ts-"`), `"`)
}
hasTimestamp = true
} else {
p.typeMap[patternName][match[1]] = match[2]
}
// the modifier is not a valid part of a "grok" pattern, so remove it
// from the pattern.
pattern = strings.Replace(pattern, ":"+match[2]+"}", "}", 1)
}
return pattern, nil
}
// tsModder is a struct for incrementing identical timestamps of log lines
// so that we don't push identical metrics that will get overwritten.
type tsModder struct {
dupe time.Time
last time.Time
incr time.Duration
incrn time.Duration
rollover time.Duration
}
// tsMod increments the given timestamp one unit more from the previous
// duplicate timestamp.
// the increment unit is determined as the next smallest time unit below the
// most significant time unit of ts.
// ie, if the input is at ms precision, it will increment it 1µs.
func (t *tsModder) tsMod(ts time.Time) time.Time {
defer func() { t.last = ts }()
// don't mod the time if we don't need to
if t.last.IsZero() || ts.IsZero() {
t.incrn = 0
t.rollover = 0
return ts
}
if !ts.Equal(t.last) && !ts.Equal(t.dupe) {
t.incr = 0
t.incrn = 0
t.rollover = 0
return ts
}
if ts.Equal(t.last) {
t.dupe = ts
}
if ts.Equal(t.dupe) && t.incr == time.Duration(0) {
tsNano := ts.UnixNano()
d := int64(10)
counter := 1
for {
a := tsNano % d
if a > 0 {
break
}
d = d * 10
counter++
}
switch {
case counter <= 6:
t.incr = time.Nanosecond
case counter <= 9:
t.incr = time.Microsecond
case counter > 9:
t.incr = time.Millisecond
}
}
t.incrn++
if t.incrn == 999 && t.incr > time.Nanosecond {
t.rollover = t.incr * t.incrn
t.incrn = 1
t.incr = t.incr / 1000
if t.incr < time.Nanosecond {
t.incr = time.Nanosecond
}
}
return ts.Add(t.incr*t.incrn + t.rollover)
}

View File

@@ -1,508 +0,0 @@
package grok
import (
"testing"
"time"
"github.com/influxdata/telegraf"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var benchM telegraf.Metric
func Benchmark_ParseLine_CommonLogFormat(b *testing.B) {
p := &Parser{
Patterns: []string{"%{COMMON_LOG_FORMAT}"},
}
p.Compile()
var m telegraf.Metric
for n := 0; n < b.N; n++ {
m, _ = p.ParseLine(`127.0.0.1 user-identifier frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326`)
}
benchM = m
}
func Benchmark_ParseLine_CombinedLogFormat(b *testing.B) {
p := &Parser{
Patterns: []string{"%{COMBINED_LOG_FORMAT}"},
}
p.Compile()
var m telegraf.Metric
for n := 0; n < b.N; n++ {
m, _ = p.ParseLine(`127.0.0.1 user-identifier frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326 "-" "Mozilla"`)
}
benchM = m
}
func Benchmark_ParseLine_InfluxLog(b *testing.B) {
p := &Parser{
Patterns: []string{"%{INFLUXDB_HTTPD_LOG}"},
}
p.Compile()
var m telegraf.Metric
for n := 0; n < b.N; n++ {
m, _ = p.ParseLine(`[httpd] 192.168.1.1 - - [14/Jun/2016:11:33:29 +0100] "POST /write?consistency=any&db=telegraf&precision=ns&rp= HTTP/1.1" 204 0 "-" "InfluxDBClient" 6f61bc44-321b-11e6-8050-000000000000 2513`)
}
benchM = m
}
func Benchmark_ParseLine_InfluxLog_NoMatch(b *testing.B) {
p := &Parser{
Patterns: []string{"%{INFLUXDB_HTTPD_LOG}"},
}
p.Compile()
var m telegraf.Metric
for n := 0; n < b.N; n++ {
m, _ = p.ParseLine(`[retention] 2016/06/14 14:38:24 retention policy shard deletion check commencing`)
}
benchM = m
}
func Benchmark_ParseLine_CustomPattern(b *testing.B) {
p := &Parser{
Patterns: []string{"%{TEST_LOG_A}", "%{TEST_LOG_B}"},
CustomPatterns: `
DURATION %{NUMBER}[nuµm]?s
RESPONSE_CODE %{NUMBER:response_code:tag}
RESPONSE_TIME %{DURATION:response_time:duration}
TEST_LOG_A %{NUMBER:myfloat:float} %{RESPONSE_CODE} %{IPORHOST:clientip} %{RESPONSE_TIME}
`,
}
p.Compile()
var m telegraf.Metric
for n := 0; n < b.N; n++ {
m, _ = p.ParseLine(`[04/Jun/2016:12:41:45 +0100] 1.25 200 192.168.1.1 5.432µs 101`)
}
benchM = m
}
func TestBuiltinInfluxdbHttpd(t *testing.T) {
p := &Parser{
Patterns: []string{"%{INFLUXDB_HTTPD_LOG}"},
}
assert.NoError(t, p.Compile())
// Parse an influxdb POST request
m, err := p.ParseLine(`[httpd] ::1 - - [14/Jun/2016:11:33:29 +0100] "POST /write?consistency=any&db=telegraf&precision=ns&rp= HTTP/1.1" 204 0 "-" "InfluxDBClient" 6f61bc44-321b-11e6-8050-000000000000 2513`)
require.NotNil(t, m)
assert.NoError(t, err)
assert.Equal(t,
map[string]interface{}{
"resp_bytes": int64(0),
"auth": "-",
"client_ip": "::1",
"resp_code": int64(204),
"http_version": float64(1.1),
"ident": "-",
"referrer": "-",
"request": "/write?consistency=any&db=telegraf&precision=ns&rp=",
"response_time_us": int64(2513),
"agent": "InfluxDBClient",
},
m.Fields())
assert.Equal(t, map[string]string{"verb": "POST"}, m.Tags())
// Parse an influxdb GET request
m, err = p.ParseLine(`[httpd] ::1 - - [14/Jun/2016:12:10:02 +0100] "GET /query?db=telegraf&q=SELECT+bytes%2Cresponse_time_us+FROM+logparser_grok+WHERE+http_method+%3D+%27GET%27+AND+response_time_us+%3E+0+AND+time+%3E+now%28%29+-+1h HTTP/1.1" 200 578 "http://localhost:8083/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.84 Safari/537.36" 8a3806f1-3220-11e6-8006-000000000000 988`)
require.NotNil(t, m)
assert.NoError(t, err)
assert.Equal(t,
map[string]interface{}{
"resp_bytes": int64(578),
"auth": "-",
"client_ip": "::1",
"resp_code": int64(200),
"http_version": float64(1.1),
"ident": "-",
"referrer": "http://localhost:8083/",
"request": "/query?db=telegraf&q=SELECT+bytes%2Cresponse_time_us+FROM+logparser_grok+WHERE+http_method+%3D+%27GET%27+AND+response_time_us+%3E+0+AND+time+%3E+now%28%29+-+1h",
"response_time_us": int64(988),
"agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.84 Safari/537.36",
},
m.Fields())
assert.Equal(t, map[string]string{"verb": "GET"}, m.Tags())
}
// common log format
// 127.0.0.1 user-identifier frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326
func TestBuiltinCommonLogFormat(t *testing.T) {
p := &Parser{
Patterns: []string{"%{COMMON_LOG_FORMAT}"},
}
assert.NoError(t, p.Compile())
// Parse an influxdb POST request
m, err := p.ParseLine(`127.0.0.1 user-identifier frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326`)
require.NotNil(t, m)
assert.NoError(t, err)
assert.Equal(t,
map[string]interface{}{
"resp_bytes": int64(2326),
"auth": "frank",
"client_ip": "127.0.0.1",
"resp_code": int64(200),
"http_version": float64(1.0),
"ident": "user-identifier",
"request": "/apache_pb.gif",
},
m.Fields())
assert.Equal(t, map[string]string{"verb": "GET"}, m.Tags())
}
// combined log format
// 127.0.0.1 user-identifier frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326 "-" "Mozilla"
func TestBuiltinCombinedLogFormat(t *testing.T) {
p := &Parser{
Patterns: []string{"%{COMBINED_LOG_FORMAT}"},
}
assert.NoError(t, p.Compile())
// Parse an influxdb POST request
m, err := p.ParseLine(`127.0.0.1 user-identifier frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326 "-" "Mozilla"`)
require.NotNil(t, m)
assert.NoError(t, err)
assert.Equal(t,
map[string]interface{}{
"resp_bytes": int64(2326),
"auth": "frank",
"client_ip": "127.0.0.1",
"resp_code": int64(200),
"http_version": float64(1.0),
"ident": "user-identifier",
"request": "/apache_pb.gif",
"referrer": "-",
"agent": "Mozilla",
},
m.Fields())
assert.Equal(t, map[string]string{"verb": "GET"}, m.Tags())
}
func TestCompileStringAndParse(t *testing.T) {
p := &Parser{
Patterns: []string{"%{TEST_LOG_A}", "%{TEST_LOG_B}"},
CustomPatterns: `
DURATION %{NUMBER}[nuµm]?s
RESPONSE_CODE %{NUMBER:response_code:tag}
RESPONSE_TIME %{DURATION:response_time:duration}
TEST_LOG_A %{NUMBER:myfloat:float} %{RESPONSE_CODE} %{IPORHOST:clientip} %{RESPONSE_TIME}
`,
}
assert.NoError(t, p.Compile())
metricA, err := p.ParseLine(`1.25 200 192.168.1.1 5.432µs`)
require.NotNil(t, metricA)
assert.NoError(t, err)
assert.Equal(t,
map[string]interface{}{
"clientip": "192.168.1.1",
"myfloat": float64(1.25),
"response_time": int64(5432),
},
metricA.Fields())
assert.Equal(t, map[string]string{"response_code": "200"}, metricA.Tags())
}
func TestParseEpochNano(t *testing.T) {
p := &Parser{
Patterns: []string{"%{MYAPP}"},
CustomPatterns: `
MYAPP %{POSINT:ts:ts-epochnano} response_time=%{POSINT:response_time:int} mymetric=%{NUMBER:metric:float}
`,
}
assert.NoError(t, p.Compile())
metricA, err := p.ParseLine(`1466004605359052000 response_time=20821 mymetric=10890.645`)
require.NotNil(t, metricA)
assert.NoError(t, err)
assert.Equal(t,
map[string]interface{}{
"response_time": int64(20821),
"metric": float64(10890.645),
},
metricA.Fields())
assert.Equal(t, map[string]string{}, metricA.Tags())
assert.Equal(t, time.Unix(0, 1466004605359052000), metricA.Time())
}
func TestParseEpoch(t *testing.T) {
p := &Parser{
Patterns: []string{"%{MYAPP}"},
CustomPatterns: `
MYAPP %{POSINT:ts:ts-epoch} response_time=%{POSINT:response_time:int} mymetric=%{NUMBER:metric:float}
`,
}
assert.NoError(t, p.Compile())
metricA, err := p.ParseLine(`1466004605 response_time=20821 mymetric=10890.645`)
require.NotNil(t, metricA)
assert.NoError(t, err)
assert.Equal(t,
map[string]interface{}{
"response_time": int64(20821),
"metric": float64(10890.645),
},
metricA.Fields())
assert.Equal(t, map[string]string{}, metricA.Tags())
assert.Equal(t, time.Unix(1466004605, 0), metricA.Time())
}
func TestParseEpochErrors(t *testing.T) {
p := &Parser{
Patterns: []string{"%{MYAPP}"},
CustomPatterns: `
MYAPP %{WORD:ts:ts-epoch} response_time=%{POSINT:response_time:int} mymetric=%{NUMBER:metric:float}
`,
}
assert.NoError(t, p.Compile())
_, err := p.ParseLine(`foobar response_time=20821 mymetric=10890.645`)
assert.NoError(t, err)
p = &Parser{
Patterns: []string{"%{MYAPP}"},
CustomPatterns: `
MYAPP %{WORD:ts:ts-epochnano} response_time=%{POSINT:response_time:int} mymetric=%{NUMBER:metric:float}
`,
}
assert.NoError(t, p.Compile())
_, err = p.ParseLine(`foobar response_time=20821 mymetric=10890.645`)
assert.NoError(t, err)
}
func TestCompileFileAndParse(t *testing.T) {
p := &Parser{
Patterns: []string{"%{TEST_LOG_A}", "%{TEST_LOG_B}"},
CustomPatternFiles: []string{"./testdata/test-patterns"},
}
assert.NoError(t, p.Compile())
metricA, err := p.ParseLine(`[04/Jun/2016:12:41:45 +0100] 1.25 200 192.168.1.1 5.432µs 101`)
require.NotNil(t, metricA)
assert.NoError(t, err)
assert.Equal(t,
map[string]interface{}{
"clientip": "192.168.1.1",
"myfloat": float64(1.25),
"response_time": int64(5432),
"myint": int64(101),
},
metricA.Fields())
assert.Equal(t, map[string]string{"response_code": "200"}, metricA.Tags())
assert.Equal(t,
time.Date(2016, time.June, 4, 12, 41, 45, 0, time.FixedZone("foo", 60*60)).Nanosecond(),
metricA.Time().Nanosecond())
metricB, err := p.ParseLine(`[04/06/2016--12:41:45] 1.25 mystring dropme nomodifier`)
require.NotNil(t, metricB)
assert.NoError(t, err)
assert.Equal(t,
map[string]interface{}{
"myfloat": 1.25,
"mystring": "mystring",
"nomodifier": "nomodifier",
},
metricB.Fields())
assert.Equal(t, map[string]string{}, metricB.Tags())
assert.Equal(t,
time.Date(2016, time.June, 4, 12, 41, 45, 0, time.FixedZone("foo", 60*60)).Nanosecond(),
metricB.Time().Nanosecond())
}
func TestCompileNoModifiersAndParse(t *testing.T) {
p := &Parser{
Patterns: []string{"%{TEST_LOG_C}"},
CustomPatterns: `
DURATION %{NUMBER}[nuµm]?s
TEST_LOG_C %{NUMBER:myfloat} %{NUMBER} %{IPORHOST:clientip} %{DURATION:rt}
`,
}
assert.NoError(t, p.Compile())
metricA, err := p.ParseLine(`1.25 200 192.168.1.1 5.432µs`)
require.NotNil(t, metricA)
assert.NoError(t, err)
assert.Equal(t,
map[string]interface{}{
"clientip": "192.168.1.1",
"myfloat": "1.25",
"rt": "5.432µs",
},
metricA.Fields())
assert.Equal(t, map[string]string{}, metricA.Tags())
}
func TestCompileNoNamesAndParse(t *testing.T) {
p := &Parser{
Patterns: []string{"%{TEST_LOG_C}"},
CustomPatterns: `
DURATION %{NUMBER}[nuµm]?s
TEST_LOG_C %{NUMBER} %{NUMBER} %{IPORHOST} %{DURATION}
`,
}
assert.NoError(t, p.Compile())
metricA, err := p.ParseLine(`1.25 200 192.168.1.1 5.432µs`)
require.Nil(t, metricA)
assert.NoError(t, err)
}
func TestParseNoMatch(t *testing.T) {
p := &Parser{
Patterns: []string{"%{TEST_LOG_A}", "%{TEST_LOG_B}"},
CustomPatternFiles: []string{"./testdata/test-patterns"},
}
assert.NoError(t, p.Compile())
metricA, err := p.ParseLine(`[04/Jun/2016:12:41:45 +0100] notnumber 200 192.168.1.1 5.432µs 101`)
assert.NoError(t, err)
assert.Nil(t, metricA)
}
func TestCompileErrors(t *testing.T) {
// Compile fails because there are multiple timestamps:
p := &Parser{
Patterns: []string{"%{TEST_LOG_A}", "%{TEST_LOG_B}"},
CustomPatterns: `
TEST_LOG_A %{HTTPDATE:ts1:ts-httpd} %{HTTPDATE:ts2:ts-httpd} %{NUMBER:mynum:int}
`,
}
assert.Error(t, p.Compile())
// Compile fails because file doesn't exist:
p = &Parser{
Patterns: []string{"%{TEST_LOG_A}", "%{TEST_LOG_B}"},
CustomPatternFiles: []string{"/tmp/foo/bar/baz"},
}
assert.Error(t, p.Compile())
}
func TestParseErrors(t *testing.T) {
// Parse fails because the pattern doesn't exist
p := &Parser{
Patterns: []string{"%{TEST_LOG_B}"},
CustomPatterns: `
TEST_LOG_A %{HTTPDATE:ts:ts-httpd} %{WORD:myword:int} %{}
`,
}
assert.NoError(t, p.Compile())
_, err := p.ParseLine(`[04/Jun/2016:12:41:45 +0100] notnumber 200 192.168.1.1 5.432µs 101`)
assert.Error(t, err)
// Parse fails because myword is not an int
p = &Parser{
Patterns: []string{"%{TEST_LOG_A}"},
CustomPatterns: `
TEST_LOG_A %{HTTPDATE:ts:ts-httpd} %{WORD:myword:int}
`,
}
assert.NoError(t, p.Compile())
_, err = p.ParseLine(`04/Jun/2016:12:41:45 +0100 notnumber`)
assert.Error(t, err)
// Parse fails because myword is not a float
p = &Parser{
Patterns: []string{"%{TEST_LOG_A}"},
CustomPatterns: `
TEST_LOG_A %{HTTPDATE:ts:ts-httpd} %{WORD:myword:float}
`,
}
assert.NoError(t, p.Compile())
_, err = p.ParseLine(`04/Jun/2016:12:41:45 +0100 notnumber`)
assert.Error(t, err)
// Parse fails because myword is not a duration
p = &Parser{
Patterns: []string{"%{TEST_LOG_A}"},
CustomPatterns: `
TEST_LOG_A %{HTTPDATE:ts:ts-httpd} %{WORD:myword:duration}
`,
}
assert.NoError(t, p.Compile())
_, err = p.ParseLine(`04/Jun/2016:12:41:45 +0100 notnumber`)
assert.Error(t, err)
// Parse fails because the time layout is wrong.
p = &Parser{
Patterns: []string{"%{TEST_LOG_A}"},
CustomPatterns: `
TEST_LOG_A %{HTTPDATE:ts:ts-unix} %{WORD:myword:duration}
`,
}
assert.NoError(t, p.Compile())
_, err = p.ParseLine(`04/Jun/2016:12:41:45 +0100 notnumber`)
assert.Error(t, err)
}
func TestTsModder(t *testing.T) {
tsm := &tsModder{}
reftime := time.Date(2006, time.December, 1, 1, 1, 1, int(time.Millisecond), time.UTC)
modt := tsm.tsMod(reftime)
assert.Equal(t, reftime, modt)
modt = tsm.tsMod(reftime)
assert.Equal(t, reftime.Add(time.Microsecond*1), modt)
modt = tsm.tsMod(reftime)
assert.Equal(t, reftime.Add(time.Microsecond*2), modt)
modt = tsm.tsMod(reftime)
assert.Equal(t, reftime.Add(time.Microsecond*3), modt)
reftime = time.Date(2006, time.December, 1, 1, 1, 1, int(time.Microsecond), time.UTC)
modt = tsm.tsMod(reftime)
assert.Equal(t, reftime, modt)
modt = tsm.tsMod(reftime)
assert.Equal(t, reftime.Add(time.Nanosecond*1), modt)
modt = tsm.tsMod(reftime)
assert.Equal(t, reftime.Add(time.Nanosecond*2), modt)
modt = tsm.tsMod(reftime)
assert.Equal(t, reftime.Add(time.Nanosecond*3), modt)
reftime = time.Date(2006, time.December, 1, 1, 1, 1, int(time.Microsecond)*999, time.UTC)
modt = tsm.tsMod(reftime)
assert.Equal(t, reftime, modt)
modt = tsm.tsMod(reftime)
assert.Equal(t, reftime.Add(time.Nanosecond*1), modt)
modt = tsm.tsMod(reftime)
assert.Equal(t, reftime.Add(time.Nanosecond*2), modt)
modt = tsm.tsMod(reftime)
assert.Equal(t, reftime.Add(time.Nanosecond*3), modt)
reftime = time.Date(2006, time.December, 1, 1, 1, 1, 0, time.UTC)
modt = tsm.tsMod(reftime)
assert.Equal(t, reftime, modt)
modt = tsm.tsMod(reftime)
assert.Equal(t, reftime.Add(time.Millisecond*1), modt)
modt = tsm.tsMod(reftime)
assert.Equal(t, reftime.Add(time.Millisecond*2), modt)
modt = tsm.tsMod(reftime)
assert.Equal(t, reftime.Add(time.Millisecond*3), modt)
reftime = time.Time{}
modt = tsm.tsMod(reftime)
assert.Equal(t, reftime, modt)
}
func TestTsModder_Rollover(t *testing.T) {
tsm := &tsModder{}
reftime := time.Date(2006, time.December, 1, 1, 1, 1, int(time.Millisecond), time.UTC)
modt := tsm.tsMod(reftime)
for i := 1; i < 1000; i++ {
modt = tsm.tsMod(reftime)
}
assert.Equal(t, reftime.Add(time.Microsecond*999+time.Nanosecond), modt)
reftime = time.Date(2006, time.December, 1, 1, 1, 1, int(time.Microsecond), time.UTC)
modt = tsm.tsMod(reftime)
for i := 1; i < 1001; i++ {
modt = tsm.tsMod(reftime)
}
assert.Equal(t, reftime.Add(time.Nanosecond*1000), modt)
}

View File

@@ -1,80 +0,0 @@
package grok
// THIS SHOULD BE KEPT IN-SYNC WITH patterns/influx-patterns
const DEFAULT_PATTERNS = `
# Captures are a slightly modified version of logstash "grok" patterns, with
# the format %{<capture syntax>[:<semantic name>][:<modifier>]}
# By default all named captures are converted into string fields.
# Modifiers can be used to convert captures to other types or tags.
# Timestamp modifiers can be used to convert captures to the timestamp of the
# parsed metric.
# View logstash grok pattern docs here:
# https://www.elastic.co/guide/en/logstash/current/plugins-filters-grok.html
# All default logstash patterns are supported, these can be viewed here:
# https://github.com/logstash-plugins/logstash-patterns-core/blob/master/patterns/grok-patterns
# Available modifiers:
# string (default if nothing is specified)
# int
# float
# duration (ie, 5.23ms gets converted to int nanoseconds)
# tag (converts the field into a tag)
# drop (drops the field completely)
# Timestamp modifiers:
# ts-ansic ("Mon Jan _2 15:04:05 2006")
# ts-unix ("Mon Jan _2 15:04:05 MST 2006")
# ts-ruby ("Mon Jan 02 15:04:05 -0700 2006")
# ts-rfc822 ("02 Jan 06 15:04 MST")
# ts-rfc822z ("02 Jan 06 15:04 -0700")
# ts-rfc850 ("Monday, 02-Jan-06 15:04:05 MST")
# ts-rfc1123 ("Mon, 02 Jan 2006 15:04:05 MST")
# ts-rfc1123z ("Mon, 02 Jan 2006 15:04:05 -0700")
# ts-rfc3339 ("2006-01-02T15:04:05Z07:00")
# ts-rfc3339nano ("2006-01-02T15:04:05.999999999Z07:00")
# ts-httpd ("02/Jan/2006:15:04:05 -0700")
# ts-epoch (seconds since unix epoch)
# ts-epochnano (nanoseconds since unix epoch)
# ts-"CUSTOM"
# CUSTOM time layouts must be within quotes and be the representation of the
# "reference time", which is Mon Jan 2 15:04:05 -0700 MST 2006
# See https://golang.org/pkg/time/#Parse for more details.
# Example log file pattern, example log looks like this:
# [04/Jun/2016:12:41:45 +0100] 1.25 200 192.168.1.1 5.432µs
# Breakdown of the DURATION pattern below:
# NUMBER is a builtin logstash grok pattern matching float & int numbers.
# [nuµm]? is a regex specifying 0 or 1 of the characters within brackets.
# s is also regex, this pattern must end in "s".
# so DURATION will match something like '5.324ms' or '6.1µs' or '10s'
DURATION %{NUMBER}[nuµm]?s
RESPONSE_CODE %{NUMBER:response_code:tag}
RESPONSE_TIME %{DURATION:response_time_ns:duration}
EXAMPLE_LOG \[%{HTTPDATE:ts:ts-httpd}\] %{NUMBER:myfloat:float} %{RESPONSE_CODE} %{IPORHOST:clientip} %{RESPONSE_TIME}
# Wider-ranging username matching vs. logstash built-in %{USER}
NGUSERNAME [a-zA-Z\.\@\-\+_%]+
NGUSER %{NGUSERNAME}
##
## COMMON LOG PATTERNS
##
# InfluxDB log patterns
CLIENT (?:%{IPORHOST}|%{HOSTPORT}|::1)
INFLUXDB_HTTPD_LOG \[httpd\] %{COMBINED_LOG_FORMAT} %{UUID:uuid:drop} %{NUMBER:response_time_us:int}
# apache & nginx logs, this is also known as the "common log format"
# see https://en.wikipedia.org/wiki/Common_Log_Format
COMMON_LOG_FORMAT %{CLIENT:client_ip} %{NGUSER:ident} %{NGUSER:auth} \[%{HTTPDATE:ts:ts-httpd}\] "(?:%{WORD:verb:tag} %{NOTSPACE:request}(?: HTTP/%{NUMBER:http_version:float})?|%{DATA})" %{NUMBER:resp_code:int} (?:%{NUMBER:resp_bytes:int}|-)
# Combined log format is the same as the common log format but with the addition
# of two quoted strings at the end for "referrer" and "agent"
# See Examples at http://httpd.apache.org/docs/current/mod/mod_log_config.html
COMBINED_LOG_FORMAT %{COMMON_LOG_FORMAT} %{QS:referrer} %{QS:agent}
# HTTPD log formats
HTTPD20_ERRORLOG \[%{HTTPDERROR_DATE:timestamp}\] \[%{LOGLEVEL:loglevel:tag}\] (?:\[client %{IPORHOST:clientip}\] ){0,1}%{GREEDYDATA:errormsg}
HTTPD24_ERRORLOG \[%{HTTPDERROR_DATE:timestamp}\] \[%{WORD:module}:%{LOGLEVEL:loglevel:tag}\] \[pid %{POSINT:pid:int}:tid %{NUMBER:tid:int}\]( \(%{POSINT:proxy_errorcode:int}\)%{DATA:proxy_errormessage}:)?( \[client %{IPORHOST:client}:%{POSINT:clientport}\])? %{DATA:errorcode}: %{GREEDYDATA:message}
HTTPD_ERRORLOG %{HTTPD20_ERRORLOG}|%{HTTPD24_ERRORLOG}
`

View File

@@ -1,75 +0,0 @@
# Captures are a slightly modified version of logstash "grok" patterns, with
# the format %{<capture syntax>[:<semantic name>][:<modifier>]}
# By default all named captures are converted into string fields.
# Modifiers can be used to convert captures to other types or tags.
# Timestamp modifiers can be used to convert captures to the timestamp of the
# parsed metric.
# View logstash grok pattern docs here:
# https://www.elastic.co/guide/en/logstash/current/plugins-filters-grok.html
# All default logstash patterns are supported, these can be viewed here:
# https://github.com/logstash-plugins/logstash-patterns-core/blob/master/patterns/grok-patterns
# Available modifiers:
# string (default if nothing is specified)
# int
# float
# duration (ie, 5.23ms gets converted to int nanoseconds)
# tag (converts the field into a tag)
# drop (drops the field completely)
# Timestamp modifiers:
# ts-ansic ("Mon Jan _2 15:04:05 2006")
# ts-unix ("Mon Jan _2 15:04:05 MST 2006")
# ts-ruby ("Mon Jan 02 15:04:05 -0700 2006")
# ts-rfc822 ("02 Jan 06 15:04 MST")
# ts-rfc822z ("02 Jan 06 15:04 -0700")
# ts-rfc850 ("Monday, 02-Jan-06 15:04:05 MST")
# ts-rfc1123 ("Mon, 02 Jan 2006 15:04:05 MST")
# ts-rfc1123z ("Mon, 02 Jan 2006 15:04:05 -0700")
# ts-rfc3339 ("2006-01-02T15:04:05Z07:00")
# ts-rfc3339nano ("2006-01-02T15:04:05.999999999Z07:00")
# ts-httpd ("02/Jan/2006:15:04:05 -0700")
# ts-epoch (seconds since unix epoch)
# ts-epochnano (nanoseconds since unix epoch)
# ts-"CUSTOM"
# CUSTOM time layouts must be within quotes and be the representation of the
# "reference time", which is Mon Jan 2 15:04:05 -0700 MST 2006
# See https://golang.org/pkg/time/#Parse for more details.
# Example log file pattern, example log looks like this:
# [04/Jun/2016:12:41:45 +0100] 1.25 200 192.168.1.1 5.432µs
# Breakdown of the DURATION pattern below:
# NUMBER is a builtin logstash grok pattern matching float & int numbers.
# [nuµm]? is a regex specifying 0 or 1 of the characters within brackets.
# s is also regex, this pattern must end in "s".
# so DURATION will match something like '5.324ms' or '6.1µs' or '10s'
DURATION %{NUMBER}[nuµm]?s
RESPONSE_CODE %{NUMBER:response_code:tag}
RESPONSE_TIME %{DURATION:response_time_ns:duration}
EXAMPLE_LOG \[%{HTTPDATE:ts:ts-httpd}\] %{NUMBER:myfloat:float} %{RESPONSE_CODE} %{IPORHOST:clientip} %{RESPONSE_TIME}
# Wider-ranging username matching vs. logstash built-in %{USER}
NGUSERNAME [a-zA-Z\.\@\-\+_%]+
NGUSER %{NGUSERNAME}
##
## COMMON LOG PATTERNS
##
# InfluxDB log patterns
CLIENT (?:%{IPORHOST}|%{HOSTPORT}|::1)
INFLUXDB_HTTPD_LOG \[httpd\] %{COMBINED_LOG_FORMAT} %{UUID:uuid:drop} %{NUMBER:response_time_us:int}
# apache & nginx logs, this is also known as the "common log format"
# see https://en.wikipedia.org/wiki/Common_Log_Format
COMMON_LOG_FORMAT %{CLIENT:client_ip} %{NGUSER:ident} %{NGUSER:auth} \[%{HTTPDATE:ts:ts-httpd}\] "(?:%{WORD:verb:tag} %{NOTSPACE:request}(?: HTTP/%{NUMBER:http_version:float})?|%{DATA})" %{NUMBER:resp_code:int} (?:%{NUMBER:resp_bytes:int}|-)
# Combined log format is the same as the common log format but with the addition
# of two quoted strings at the end for "referrer" and "agent"
# See Examples at http://httpd.apache.org/docs/current/mod/mod_log_config.html
COMBINED_LOG_FORMAT %{COMMON_LOG_FORMAT} %{QS:referrer} %{QS:agent}
# HTTPD log formats
HTTPD20_ERRORLOG \[%{HTTPDERROR_DATE:timestamp}\] \[%{LOGLEVEL:loglevel:tag}\] (?:\[client %{IPORHOST:clientip}\] ){0,1}%{GREEDYDATA:errormsg}
HTTPD24_ERRORLOG \[%{HTTPDERROR_DATE:timestamp}\] \[%{WORD:module}:%{LOGLEVEL:loglevel:tag}\] \[pid %{POSINT:pid:int}:tid %{NUMBER:tid:int}\]( \(%{POSINT:proxy_errorcode:int}\)%{DATA:proxy_errormessage}:)?( \[client %{IPORHOST:client}:%{POSINT:clientport}\])? %{DATA:errorcode}: %{GREEDYDATA:message}
HTTPD_ERRORLOG %{HTTPD20_ERRORLOG}|%{HTTPD24_ERRORLOG}

View File

@@ -1,14 +0,0 @@
# Test A log line:
# [04/Jun/2016:12:41:45 +0100] 1.25 200 192.168.1.1 5.432µs 101
DURATION %{NUMBER}[nuµm]?s
RESPONSE_CODE %{NUMBER:response_code:tag}
RESPONSE_TIME %{DURATION:response_time:duration}
TEST_LOG_A \[%{HTTPDATE:timestamp:ts-httpd}\] %{NUMBER:myfloat:float} %{RESPONSE_CODE} %{IPORHOST:clientip} %{RESPONSE_TIME} %{NUMBER:myint:int}
# Test B log line:
# [04/06/2016--12:41:45] 1.25 mystring dropme nomodifier
TEST_TIMESTAMP %{MONTHDAY}/%{MONTHNUM}/%{YEAR}--%{TIME}
TEST_LOG_B \[%{TEST_TIMESTAMP:timestamp:ts-"02/01/2006--15:04:05"}\] %{NUMBER:myfloat:float} %{WORD:mystring:string} %{WORD:dropme:drop} %{WORD:nomodifier}
TEST_TIMESTAMP %{MONTHDAY}/%{MONTHNUM}/%{YEAR}--%{TIME}
TEST_LOG_BAD \[%{TEST_TIMESTAMP:timestamp:ts-"02/01/2006--15:04:05"}\] %{NUMBER:myfloat:float} %{WORD:mystring:int} %{WORD:dropme:drop} %{WORD:nomodifier}

View File

@@ -1 +0,0 @@
[04/Jun/2016:12:41:45 +0100] 1.25 200 192.168.1.1 5.432µs 101

View File

@@ -1 +0,0 @@
[04/06/2016--12:41:45] 1.25 mystring dropme nomodifier

View File

@@ -1,228 +0,0 @@
package logparser
import (
"fmt"
"log"
"reflect"
"sync"
"github.com/hpcloud/tail"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal/globpath"
"github.com/influxdata/telegraf/plugins/inputs"
// Parsers
"github.com/influxdata/telegraf/plugins/inputs/logparser/grok"
)
type LogParser interface {
ParseLine(line string) (telegraf.Metric, error)
Compile() error
}
type LogParserPlugin struct {
Files []string
FromBeginning bool
tailers []*tail.Tail
lines chan string
done chan struct{}
wg sync.WaitGroup
acc telegraf.Accumulator
parsers []LogParser
sync.Mutex
GrokParser *grok.Parser `toml:"grok"`
}
const sampleConfig = `
## Log files to parse.
## These accept standard unix glob matching rules, but with the addition of
## ** as a "super asterisk". ie:
## /var/log/**.log -> recursively find all .log files in /var/log
## /var/log/*/*.log -> find all .log files with a parent dir in /var/log
## /var/log/apache.log -> only tail the apache log file
files = ["/var/log/influxdb/influxdb.log"]
## Read file from beginning.
from_beginning = false
## Parse logstash-style "grok" patterns:
## Telegraf built-in parsing patterns: https://goo.gl/dkay10
[inputs.logparser.grok]
## This is a list of patterns to check the given log file(s) for.
## Note that adding patterns here increases processing time. The most
## efficient configuration is to have one pattern per logparser.
## Other common built-in patterns are:
## %{COMMON_LOG_FORMAT} (plain apache & nginx access logs)
## %{COMBINED_LOG_FORMAT} (access logs + referrer & agent)
patterns = ["%{INFLUXDB_HTTPD_LOG}"]
## Full path(s) to custom pattern files.
custom_pattern_files = []
## Custom patterns can also be defined here. Put one pattern per line.
custom_patterns = '''
'''
`
func (l *LogParserPlugin) SampleConfig() string {
return sampleConfig
}
func (l *LogParserPlugin) Description() string {
return "Stream and parse log file(s)."
}
func (l *LogParserPlugin) Gather(acc telegraf.Accumulator) error {
return nil
}
func (l *LogParserPlugin) Start(acc telegraf.Accumulator) error {
l.Lock()
defer l.Unlock()
l.acc = acc
l.lines = make(chan string, 1000)
l.done = make(chan struct{})
// Looks for fields which implement LogParser interface
l.parsers = []LogParser{}
s := reflect.ValueOf(l).Elem()
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
if !f.CanInterface() {
continue
}
if lpPlugin, ok := f.Interface().(LogParser); ok {
if reflect.ValueOf(lpPlugin).IsNil() {
continue
}
l.parsers = append(l.parsers, lpPlugin)
}
}
if len(l.parsers) == 0 {
return fmt.Errorf("ERROR: logparser input plugin: no parser defined.")
}
// compile log parser patterns:
for _, parser := range l.parsers {
if err := parser.Compile(); err != nil {
return err
}
}
var seek tail.SeekInfo
if !l.FromBeginning {
seek.Whence = 2
seek.Offset = 0
}
l.wg.Add(1)
go l.parser()
var errS string
// Create a "tailer" for each file
for _, filepath := range l.Files {
g, err := globpath.Compile(filepath)
if err != nil {
log.Printf("ERROR Glob %s failed to compile, %s", filepath, err)
}
for file, _ := range g.Match() {
tailer, err := tail.TailFile(file,
tail.Config{
ReOpen: true,
Follow: true,
Location: &seek,
})
if err != nil {
errS += err.Error() + " "
continue
}
// create a goroutine for each "tailer"
l.wg.Add(1)
go l.receiver(tailer)
l.tailers = append(l.tailers, tailer)
}
}
if errS != "" {
return fmt.Errorf(errS)
}
return nil
}
// receiver is launched as a goroutine to continuously watch a tailed logfile
// for changes and send any log lines down the l.lines channel.
func (l *LogParserPlugin) receiver(tailer *tail.Tail) {
defer l.wg.Done()
var line *tail.Line
for line = range tailer.Lines {
if line.Err != nil {
log.Printf("ERROR tailing file %s, Error: %s\n",
tailer.Filename, line.Err)
continue
}
select {
case <-l.done:
case l.lines <- line.Text:
}
}
}
// parser is launched as a goroutine to watch the l.lines channel.
// when a line is available, parser parses it and adds the metric(s) to the
// accumulator.
func (l *LogParserPlugin) parser() {
defer l.wg.Done()
var m telegraf.Metric
var err error
var line string
for {
select {
case <-l.done:
return
case line = <-l.lines:
if line == "" || line == "\n" {
continue
}
}
for _, parser := range l.parsers {
m, err = parser.ParseLine(line)
if err == nil {
if m != nil {
l.acc.AddFields(m.Name(), m.Fields(), m.Tags(), m.Time())
}
} else {
log.Printf("Malformed log line in [%s], Error: %s\n", line, err)
}
}
}
}
func (l *LogParserPlugin) Stop() {
l.Lock()
defer l.Unlock()
for _, t := range l.tailers {
err := t.Stop()
if err != nil {
log.Printf("ERROR stopping tail on file %s\n", t.Filename)
}
t.Cleanup()
}
close(l.done)
l.wg.Wait()
}
func init() {
inputs.Add("logparser", func() telegraf.Input {
return &LogParserPlugin{}
})
}

View File

@@ -1,116 +0,0 @@
package logparser
import (
"runtime"
"strings"
"testing"
"time"
"github.com/influxdata/telegraf/testutil"
"github.com/influxdata/telegraf/plugins/inputs/logparser/grok"
"github.com/stretchr/testify/assert"
)
func TestStartNoParsers(t *testing.T) {
logparser := &LogParserPlugin{
FromBeginning: true,
Files: []string{"grok/testdata/*.log"},
}
acc := testutil.Accumulator{}
assert.Error(t, logparser.Start(&acc))
}
func TestGrokParseLogFilesNonExistPattern(t *testing.T) {
thisdir := getCurrentDir()
p := &grok.Parser{
Patterns: []string{"%{FOOBAR}"},
CustomPatternFiles: []string{thisdir + "grok/testdata/test-patterns"},
}
logparser := &LogParserPlugin{
FromBeginning: true,
Files: []string{thisdir + "grok/testdata/*.log"},
GrokParser: p,
}
acc := testutil.Accumulator{}
assert.NoError(t, logparser.Start(&acc))
time.Sleep(time.Millisecond * 500)
logparser.Stop()
}
func TestGrokParseLogFiles(t *testing.T) {
thisdir := getCurrentDir()
p := &grok.Parser{
Patterns: []string{"%{TEST_LOG_A}", "%{TEST_LOG_B}"},
CustomPatternFiles: []string{thisdir + "grok/testdata/test-patterns"},
}
logparser := &LogParserPlugin{
FromBeginning: true,
Files: []string{thisdir + "grok/testdata/*.log"},
GrokParser: p,
}
acc := testutil.Accumulator{}
assert.NoError(t, logparser.Start(&acc))
time.Sleep(time.Millisecond * 500)
logparser.Stop()
acc.AssertContainsTaggedFields(t, "logparser_grok",
map[string]interface{}{
"clientip": "192.168.1.1",
"myfloat": float64(1.25),
"response_time": int64(5432),
"myint": int64(101),
},
map[string]string{"response_code": "200"})
acc.AssertContainsTaggedFields(t, "logparser_grok",
map[string]interface{}{
"myfloat": 1.25,
"mystring": "mystring",
"nomodifier": "nomodifier",
},
map[string]string{})
}
func TestGrokParseLogFilesOneBad(t *testing.T) {
thisdir := getCurrentDir()
p := &grok.Parser{
Patterns: []string{"%{TEST_LOG_A}", "%{TEST_LOG_BAD}"},
CustomPatternFiles: []string{thisdir + "grok/testdata/test-patterns"},
}
assert.NoError(t, p.Compile())
logparser := &LogParserPlugin{
FromBeginning: true,
Files: []string{thisdir + "grok/testdata/*.log"},
GrokParser: p,
}
acc := testutil.Accumulator{}
assert.NoError(t, logparser.Start(&acc))
time.Sleep(time.Millisecond * 500)
logparser.Stop()
acc.AssertContainsTaggedFields(t, "logparser_grok",
map[string]interface{}{
"clientip": "192.168.1.1",
"myfloat": float64(1.25),
"response_time": int64(5432),
"myint": int64(101),
},
map[string]string{"response_code": "200"})
}
func getCurrentDir() string {
_, filename, _, _ := runtime.Caller(1)
return strings.Replace(filename, "logparser_test.go", "", 1)
}

View File

@@ -69,10 +69,10 @@ func (m *MongoDB) Gather(acc telegraf.Accumulator) error {
} }
} }
wg.Add(1) wg.Add(1)
go func(srv *Server) { go func() {
defer wg.Done() defer wg.Done()
outerr = m.gatherServer(srv, acc) outerr = m.gatherServer(m.getMongoServer(u), acc)
}(m.getMongoServer(u)) }()
} }
wg.Wait() wg.Wait()

View File

@@ -33,7 +33,7 @@ func (s *Server) gatherData(acc telegraf.Accumulator) error {
result_repl := &ReplSetStatus{} result_repl := &ReplSetStatus{}
err = s.Session.DB("admin").Run(bson.D{{"replSetGetStatus", 1}}, result_repl) err = s.Session.DB("admin").Run(bson.D{{"replSetGetStatus", 1}}, result_repl)
if err != nil { if err != nil {
log.Println("Not gathering replica set status, member not in replica set (" + err.Error() + ")") log.Println("Not gathering replica set status, member not in replica set")
} }
jumbo_chunks, _ := s.Session.DB("config").C("chunks").Find(bson.M{"jumbo": true}).Count() jumbo_chunks, _ := s.Session.DB("config").C("chunks").Find(bson.M{"jumbo": true}).Count()

View File

@@ -78,9 +78,9 @@ type ReplSetStatus struct {
// ReplSetMember stores information related to a replica set member // ReplSetMember stores information related to a replica set member
type ReplSetMember struct { type ReplSetMember struct {
Name string `bson:"name"` Name string `bson:"name"`
State int64 `bson:"state"` State int64 `bson:"state"`
OptimeDate *bson.MongoTimestamp `bson:"optimeDate"` Optime *bson.MongoTimestamp `bson:"optime"`
} }
// WiredTiger stores information related to the WiredTiger storage engine. // WiredTiger stores information related to the WiredTiger storage engine.
@@ -663,9 +663,9 @@ func NewStatLine(oldMongo, newMongo MongoStatus, key string, all bool, sampleSec
} }
} }
if me.OptimeDate != nil && master.OptimeDate != nil && me.State == 2 { if me.Optime != nil && master.Optime != nil && me.State == 2 {
// MongoTimestamp type is int64 where the first 32bits are the unix timestamp // MongoTimestamp type is int64 where the first 32bits are the unix timestamp
lag := int64(*master.OptimeDate>>32 - *me.OptimeDate>>32) lag := int64(*master.Optime>>32 - *me.Optime>>32)
if lag < 0 { if lag < 0 {
returnVal.ReplLag = 0 returnVal.ReplLag = 0
} else { } else {

View File

@@ -21,12 +21,8 @@ The plugin expects messages in the
"sensors/#", "sensors/#",
] ]
# if true, messages that can't be delivered while the subscriber is offline ## Maximum number of metrics to buffer between collection intervals
# will be delivered when it comes back (such as on service restart). metric_buffer = 100000
# NOTE: if true, client_id MUST be set
persistent_session = false
# If empty, a random client ID will be generated.
client_id = ""
## username and password to connect MQTT server. ## username and password to connect MQTT server.
# username = "telegraf" # username = "telegraf"

View File

@@ -25,8 +25,8 @@ This plugin gathers the statistic data from MySQL server
## [username[:password]@][protocol[(address)]]/[?tls=[true|false|skip-verify]] ## [username[:password]@][protocol[(address)]]/[?tls=[true|false|skip-verify]]
## see https://github.com/go-sql-driver/mysql#dsn-data-source-name ## see https://github.com/go-sql-driver/mysql#dsn-data-source-name
## e.g. ## e.g.
## db_user:passwd@tcp(127.0.0.1:3306)/?tls=false ## root:passwd@tcp(127.0.0.1:3306)/?tls=false
## db_user@tcp(127.0.0.1:3306)/?tls=false ## root@tcp(127.0.0.1:3306)/?tls=false
# #
## If no servers are specified, then localhost is used as the host. ## If no servers are specified, then localhost is used as the host.
servers = ["tcp(127.0.0.1:3306)/"] servers = ["tcp(127.0.0.1:3306)/"]
@@ -53,13 +53,13 @@ This plugin gathers the statistic data from MySQL server
## gather metrics from SHOW BINARY LOGS command output ## gather metrics from SHOW BINARY LOGS command output
gather_binary_logs = false gather_binary_logs = false
# #
## gather metrics from PERFORMANCE_SCHEMA.TABLE_IO_WAITS_SUMMARY_BY_TABLE ## gather metrics from PERFORMANCE_SCHEMA.TABLE_IO_WAITS_SUMMART_BY_TABLE
gather_table_io_waits = false gather_table_io_waits = false
# #
## gather metrics from PERFORMANCE_SCHEMA.TABLE_LOCK_WAITS ## gather metrics from PERFORMANCE_SCHEMA.TABLE_LOCK_WAITS
gather_table_lock_waits = false gather_table_lock_waits = false
# #
## gather metrics from PERFORMANCE_SCHEMA.TABLE_IO_WAITS_SUMMARY_BY_INDEX_USAGE ## gather metrics from PERFORMANCE_SCHEMA.TABLE_IO_WAITS_SUMMART_BY_INDEX_USAGE
gather_index_io_waits = false gather_index_io_waits = false
# #
## gather metrics from PERFORMANCE_SCHEMA.EVENT_WAITS ## gather metrics from PERFORMANCE_SCHEMA.EVENT_WAITS

View File

@@ -39,8 +39,8 @@ var sampleConfig = `
## [username[:password]@][protocol[(address)]]/[?tls=[true|false|skip-verify]] ## [username[:password]@][protocol[(address)]]/[?tls=[true|false|skip-verify]]
## see https://github.com/go-sql-driver/mysql#dsn-data-source-name ## see https://github.com/go-sql-driver/mysql#dsn-data-source-name
## e.g. ## e.g.
## db_user:passwd@tcp(127.0.0.1:3306)/?tls=false ## root:passwd@tcp(127.0.0.1:3306)/?tls=false
## db_user@tcp(127.0.0.1:3306)/?tls=false ## root@tcp(127.0.0.1:3306)/?tls=false
# #
## If no servers are specified, then localhost is used as the host. ## If no servers are specified, then localhost is used as the host.
servers = ["tcp(127.0.0.1:3306)/"] servers = ["tcp(127.0.0.1:3306)/"]

View File

@@ -6,30 +6,41 @@ It can also check response text.
### Configuration: ### Configuration:
``` ```
# List of UDP/TCP connections you want to check
[[inputs.net_response]]
protocol = "tcp"
# Server address (default IP localhost)
address = "github.com:80"
# Set timeout (default 1.0)
timeout = 1.0
# Set read timeout (default 1.0)
read_timeout = 1.0
# String sent to the server
send = "ssh"
# Expected string in answer
expect = "ssh"
[[inputs.net_response]] [[inputs.net_response]]
protocol = "tcp" protocol = "tcp"
address = ":80" address = ":80"
# TCP or UDP 'ping' given url and collect response time in seconds
[[inputs.net_response]] [[inputs.net_response]]
## Protocol, must be "tcp" or "udp" protocol = "udp"
protocol = "tcp" # Server address (default IP localhost)
## Server address (default localhost)
address = "github.com:80" address = "github.com:80"
## Set timeout # Set timeout (default 1.0)
timeout = "1s" timeout = 1.0
# Set read timeout (default 1.0)
## Optional string sent to the server read_timeout = 1.0
# String sent to the server
send = "ssh" send = "ssh"
## Optional expected string in answer # Expected string in answer
expect = "ssh" expect = "ssh"
## Set read timeout (only used if expecting a response)
read_timeout = "1s"
[[inputs.net_response]] [[inputs.net_response]]
protocol = "udp" protocol = "udp"
address = "localhost:161" address = "localhost:161"
timeout = "2s" timeout = 2.0
``` ```
### Measurements & Fields: ### Measurements & Fields:

View File

@@ -9,15 +9,14 @@ import (
"time" "time"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/plugins/inputs" "github.com/influxdata/telegraf/plugins/inputs"
) )
// NetResponses struct // NetResponses struct
type NetResponse struct { type NetResponse struct {
Address string Address string
Timeout internal.Duration Timeout float64
ReadTimeout internal.Duration ReadTimeout float64
Send string Send string
Expect string Expect string
Protocol string Protocol string
@@ -32,28 +31,29 @@ var sampleConfig = `
protocol = "tcp" protocol = "tcp"
## Server address (default localhost) ## Server address (default localhost)
address = "github.com:80" address = "github.com:80"
## Set timeout ## Set timeout (default 1.0 seconds)
timeout = "1s" timeout = 1.0
## Set read timeout (default 1.0 seconds)
read_timeout = 1.0
## Optional string sent to the server ## Optional string sent to the server
# send = "ssh" # send = "ssh"
## Optional expected string in answer ## Optional expected string in answer
# expect = "ssh" # expect = "ssh"
## Set read timeout (only used if expecting a response)
read_timeout = "1s"
` `
func (_ *NetResponse) SampleConfig() string { func (_ *NetResponse) SampleConfig() string {
return sampleConfig return sampleConfig
} }
func (n *NetResponse) TcpGather() (map[string]interface{}, error) { func (t *NetResponse) TcpGather() (map[string]interface{}, error) {
// Prepare fields // Prepare fields
fields := make(map[string]interface{}) fields := make(map[string]interface{})
// Start Timer // Start Timer
start := time.Now() start := time.Now()
// Resolving
tcpAddr, err := net.ResolveTCPAddr("tcp", t.Address)
// Connecting // Connecting
conn, err := net.DialTimeout("tcp", n.Address, n.Timeout.Duration) conn, err := net.DialTCP("tcp", nil, tcpAddr)
// Stop timer // Stop timer
responseTime := time.Since(start).Seconds() responseTime := time.Since(start).Seconds()
// Handle error // Handle error
@@ -62,16 +62,17 @@ func (n *NetResponse) TcpGather() (map[string]interface{}, error) {
} }
defer conn.Close() defer conn.Close()
// Send string if needed // Send string if needed
if n.Send != "" { if t.Send != "" {
msg := []byte(n.Send) msg := []byte(t.Send)
conn.Write(msg) conn.Write(msg)
conn.CloseWrite()
// Stop timer // Stop timer
responseTime = time.Since(start).Seconds() responseTime = time.Since(start).Seconds()
} }
// Read string if needed // Read string if needed
if n.Expect != "" { if t.Expect != "" {
// Set read timeout // Set read timeout
conn.SetReadDeadline(time.Now().Add(n.ReadTimeout.Duration)) conn.SetReadDeadline(time.Now().Add(time.Duration(t.ReadTimeout) * time.Second))
// Prepare reader // Prepare reader
reader := bufio.NewReader(conn) reader := bufio.NewReader(conn)
tp := textproto.NewReader(reader) tp := textproto.NewReader(reader)
@@ -84,7 +85,7 @@ func (n *NetResponse) TcpGather() (map[string]interface{}, error) {
fields["string_found"] = false fields["string_found"] = false
} else { } else {
// Looking for string in answer // Looking for string in answer
RegEx := regexp.MustCompile(`.*` + n.Expect + `.*`) RegEx := regexp.MustCompile(`.*` + t.Expect + `.*`)
find := RegEx.FindString(string(data)) find := RegEx.FindString(string(data))
if find != "" { if find != "" {
fields["string_found"] = true fields["string_found"] = true
@@ -98,13 +99,13 @@ func (n *NetResponse) TcpGather() (map[string]interface{}, error) {
return fields, nil return fields, nil
} }
func (n *NetResponse) UdpGather() (map[string]interface{}, error) { func (u *NetResponse) UdpGather() (map[string]interface{}, error) {
// Prepare fields // Prepare fields
fields := make(map[string]interface{}) fields := make(map[string]interface{})
// Start Timer // Start Timer
start := time.Now() start := time.Now()
// Resolving // Resolving
udpAddr, err := net.ResolveUDPAddr("udp", n.Address) udpAddr, err := net.ResolveUDPAddr("udp", u.Address)
LocalAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0") LocalAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
// Connecting // Connecting
conn, err := net.DialUDP("udp", LocalAddr, udpAddr) conn, err := net.DialUDP("udp", LocalAddr, udpAddr)
@@ -114,11 +115,11 @@ func (n *NetResponse) UdpGather() (map[string]interface{}, error) {
return nil, err return nil, err
} }
// Send string // Send string
msg := []byte(n.Send) msg := []byte(u.Send)
conn.Write(msg) conn.Write(msg)
// Read string // Read string
// Set read timeout // Set read timeout
conn.SetReadDeadline(time.Now().Add(n.ReadTimeout.Duration)) conn.SetReadDeadline(time.Now().Add(time.Duration(u.ReadTimeout) * time.Second))
// Read // Read
buf := make([]byte, 1024) buf := make([]byte, 1024)
_, _, err = conn.ReadFromUDP(buf) _, _, err = conn.ReadFromUDP(buf)
@@ -129,7 +130,7 @@ func (n *NetResponse) UdpGather() (map[string]interface{}, error) {
return nil, err return nil, err
} else { } else {
// Looking for string in answer // Looking for string in answer
RegEx := regexp.MustCompile(`.*` + n.Expect + `.*`) RegEx := regexp.MustCompile(`.*` + u.Expect + `.*`)
find := RegEx.FindString(string(buf)) find := RegEx.FindString(string(buf))
if find != "" { if find != "" {
fields["string_found"] = true fields["string_found"] = true
@@ -141,28 +142,28 @@ func (n *NetResponse) UdpGather() (map[string]interface{}, error) {
return fields, nil return fields, nil
} }
func (n *NetResponse) Gather(acc telegraf.Accumulator) error { func (c *NetResponse) Gather(acc telegraf.Accumulator) error {
// Set default values // Set default values
if n.Timeout.Duration == 0 { if c.Timeout == 0 {
n.Timeout.Duration = time.Second c.Timeout = 1.0
} }
if n.ReadTimeout.Duration == 0 { if c.ReadTimeout == 0 {
n.ReadTimeout.Duration = time.Second c.ReadTimeout = 1.0
} }
// Check send and expected string // Check send and expected string
if n.Protocol == "udp" && n.Send == "" { if c.Protocol == "udp" && c.Send == "" {
return errors.New("Send string cannot be empty") return errors.New("Send string cannot be empty")
} }
if n.Protocol == "udp" && n.Expect == "" { if c.Protocol == "udp" && c.Expect == "" {
return errors.New("Expected string cannot be empty") return errors.New("Expected string cannot be empty")
} }
// Prepare host and port // Prepare host and port
host, port, err := net.SplitHostPort(n.Address) host, port, err := net.SplitHostPort(c.Address)
if err != nil { if err != nil {
return err return err
} }
if host == "" { if host == "" {
n.Address = "localhost:" + port c.Address = "localhost:" + port
} }
if port == "" { if port == "" {
return errors.New("Bad port") return errors.New("Bad port")
@@ -171,11 +172,11 @@ func (n *NetResponse) Gather(acc telegraf.Accumulator) error {
tags := map[string]string{"server": host, "port": port} tags := map[string]string{"server": host, "port": port}
var fields map[string]interface{} var fields map[string]interface{}
// Gather data // Gather data
if n.Protocol == "tcp" { if c.Protocol == "tcp" {
fields, err = n.TcpGather() fields, err = c.TcpGather()
tags["protocol"] = "tcp" tags["protocol"] = "tcp"
} else if n.Protocol == "udp" { } else if c.Protocol == "udp" {
fields, err = n.UdpGather() fields, err = c.UdpGather()
tags["protocol"] = "udp" tags["protocol"] = "udp"
} else { } else {
return errors.New("Bad protocol") return errors.New("Bad protocol")

View File

@@ -5,9 +5,7 @@ import (
"regexp" "regexp"
"sync" "sync"
"testing" "testing"
"time"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/testutil" "github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -37,7 +35,7 @@ func TestTCPError(t *testing.T) {
// Error // Error
err1 := c.Gather(&acc) err1 := c.Gather(&acc)
require.Error(t, err1) require.Error(t, err1)
assert.Contains(t, err1.Error(), "getsockopt: connection refused") assert.Equal(t, "dial tcp 127.0.0.1:9999: getsockopt: connection refused", err1.Error())
} }
func TestTCPOK1(t *testing.T) { func TestTCPOK1(t *testing.T) {
@@ -48,8 +46,8 @@ func TestTCPOK1(t *testing.T) {
Address: "127.0.0.1:2004", Address: "127.0.0.1:2004",
Send: "test", Send: "test",
Expect: "test", Expect: "test",
ReadTimeout: internal.Duration{Duration: time.Second * 3}, ReadTimeout: 3.0,
Timeout: internal.Duration{Duration: time.Second}, Timeout: 1.0,
Protocol: "tcp", Protocol: "tcp",
} }
// Start TCP server // Start TCP server
@@ -88,8 +86,8 @@ func TestTCPOK2(t *testing.T) {
Address: "127.0.0.1:2004", Address: "127.0.0.1:2004",
Send: "test", Send: "test",
Expect: "test2", Expect: "test2",
ReadTimeout: internal.Duration{Duration: time.Second * 3}, ReadTimeout: 3.0,
Timeout: internal.Duration{Duration: time.Second}, Timeout: 1.0,
Protocol: "tcp", Protocol: "tcp",
} }
// Start TCP server // Start TCP server
@@ -143,8 +141,8 @@ func TestUDPOK1(t *testing.T) {
Address: "127.0.0.1:2004", Address: "127.0.0.1:2004",
Send: "test", Send: "test",
Expect: "test", Expect: "test",
ReadTimeout: internal.Duration{Duration: time.Second * 3}, ReadTimeout: 3.0,
Timeout: internal.Duration{Duration: time.Second}, Timeout: 1.0,
Protocol: "udp", Protocol: "udp",
} }
// Start UDP server // Start UDP server

View File

@@ -97,12 +97,11 @@ func (n *Nginx) gatherUrl(addr *url.URL, acc telegraf.Accumulator) error {
if err != nil { if err != nil {
return err return err
} }
data := strings.Fields(line) data := strings.SplitN(strings.TrimSpace(line), " ", 3)
accepts, err := strconv.ParseUint(data[0], 10, 64) accepts, err := strconv.ParseUint(data[0], 10, 64)
if err != nil { if err != nil {
return err return err
} }
handled, err := strconv.ParseUint(data[1], 10, 64) handled, err := strconv.ParseUint(data[1], 10, 64)
if err != nil { if err != nil {
return err return err
@@ -117,7 +116,7 @@ func (n *Nginx) gatherUrl(addr *url.URL, acc telegraf.Accumulator) error {
if err != nil { if err != nil {
return err return err
} }
data = strings.Fields(line) data = strings.SplitN(strings.TrimSpace(line), " ", 6)
reading, err := strconv.ParseUint(data[1], 10, 64) reading, err := strconv.ParseUint(data[1], 10, 64)
if err != nil { if err != nil {
return err return err

View File

@@ -13,18 +13,12 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
const nginxSampleResponse = ` const sampleResponse = `
Active connections: 585 Active connections: 585
server accepts handled requests server accepts handled requests
85340 85340 35085 85340 85340 35085
Reading: 4 Writing: 135 Waiting: 446 Reading: 4 Writing: 135 Waiting: 446
` `
const tengineSampleResponse = `
Active connections: 403
server accepts handled requests request_time
853 8533 3502 1546565864
Reading: 8 Writing: 125 Waiting: 946
`
// Verify that nginx tags are properly parsed based on the server // Verify that nginx tags are properly parsed based on the server
func TestNginxTags(t *testing.T) { func TestNginxTags(t *testing.T) {
@@ -42,9 +36,7 @@ func TestNginxGeneratesMetrics(t *testing.T) {
var rsp string var rsp string
if r.URL.Path == "/stub_status" { if r.URL.Path == "/stub_status" {
rsp = nginxSampleResponse rsp = sampleResponse
} else if r.URL.Path == "/tengine_status" {
rsp = tengineSampleResponse
} else { } else {
panic("Cannot handle request") panic("Cannot handle request")
} }
@@ -57,20 +49,12 @@ func TestNginxGeneratesMetrics(t *testing.T) {
Urls: []string{fmt.Sprintf("%s/stub_status", ts.URL)}, Urls: []string{fmt.Sprintf("%s/stub_status", ts.URL)},
} }
nt := &Nginx{ var acc testutil.Accumulator
Urls: []string{fmt.Sprintf("%s/tengine_status", ts.URL)},
}
var acc_nginx testutil.Accumulator err := n.Gather(&acc)
var acc_tengine testutil.Accumulator require.NoError(t, err)
err_nginx := n.Gather(&acc_nginx) fields := map[string]interface{}{
err_tengine := nt.Gather(&acc_tengine)
require.NoError(t, err_nginx)
require.NoError(t, err_tengine)
fields_nginx := map[string]interface{}{
"active": uint64(585), "active": uint64(585),
"accepts": uint64(85340), "accepts": uint64(85340),
"handled": uint64(85340), "handled": uint64(85340),
@@ -79,17 +63,6 @@ func TestNginxGeneratesMetrics(t *testing.T) {
"writing": uint64(135), "writing": uint64(135),
"waiting": uint64(446), "waiting": uint64(446),
} }
fields_tengine := map[string]interface{}{
"active": uint64(403),
"accepts": uint64(853),
"handled": uint64(8533),
"requests": uint64(3502),
"reading": uint64(8),
"writing": uint64(125),
"waiting": uint64(946),
}
addr, err := url.Parse(ts.URL) addr, err := url.Parse(ts.URL)
if err != nil { if err != nil {
panic(err) panic(err)
@@ -108,6 +81,5 @@ func TestNginxGeneratesMetrics(t *testing.T) {
} }
tags := map[string]string{"server": host, "port": port} tags := map[string]string{"server": host, "port": port}
acc_nginx.AssertContainsTaggedFields(t, "nginx", fields_nginx, tags) acc.AssertContainsTaggedFields(t, "nginx", fields, tags)
acc_tengine.AssertContainsTaggedFields(t, "nginx", fields_tengine, tags)
} }

View File

@@ -1,343 +0,0 @@
## Nstat input plugin
Plugin collects network metrics from `/proc/net/netstat`, `/proc/net/snmp` and `/proc/net/snmp6` files
### Configuration
The plugin firstly tries to read file paths from config values
if it is empty, then it reads from env variables.
* `PROC_NET_NETSTAT`
* `PROC_NET_SNMP`
* `PROC_NET_SNMP6`
If these variables are also not set,
then it tries to read the proc root from env - `PROC_ROOT`,
and sets `/proc` as a root path if `PROC_ROOT` is also empty.
Then appends default file paths:
* `/net/netstat`
* `/net/snmp`
* `/net/snmp6`
So if nothing is given, no paths in config and in env vars, the plugin takes the default paths.
* `/proc/net/netstat`
* `/proc/net/snmp`
* `/proc/net/snmp6`
The sample config file
```toml
[[inputs.nstat]]
## file paths
## e.g: /proc/net/netstat, /proc/net/snmp, /proc/net/snmp6
# proc_net_netstat = ""
# proc_net_snmp = ""
# proc_net_snmp6 = ""
## dump metrics with 0 values too
# dump_zeros = true
```
### Measurements & Fields
- nstat
- Icmp6InCsumErrors
- Icmp6InDestUnreachs
- Icmp6InEchoReplies
- Icmp6InEchos
- Icmp6InErrors
- Icmp6InGroupMembQueries
- Icmp6InGroupMembReductions
- Icmp6InGroupMembResponses
- Icmp6InMLDv2Reports
- Icmp6InMsgs
- Icmp6InNeighborAdvertisements
- Icmp6InNeighborSolicits
- Icmp6InParmProblems
- Icmp6InPktTooBigs
- Icmp6InRedirects
- Icmp6InRouterAdvertisements
- Icmp6InRouterSolicits
- Icmp6InTimeExcds
- Icmp6OutDestUnreachs
- Icmp6OutEchoReplies
- Icmp6OutEchos
- Icmp6OutErrors
- Icmp6OutGroupMembQueries
- Icmp6OutGroupMembReductions
- Icmp6OutGroupMembResponses
- Icmp6OutMLDv2Reports
- Icmp6OutMsgs
- Icmp6OutNeighborAdvertisements
- Icmp6OutNeighborSolicits
- Icmp6OutParmProblems
- Icmp6OutPktTooBigs
- Icmp6OutRedirects
- Icmp6OutRouterAdvertisements
- Icmp6OutRouterSolicits
- Icmp6OutTimeExcds
- Icmp6OutType133
- Icmp6OutType135
- Icmp6OutType143
- IcmpInAddrMaskReps
- IcmpInAddrMasks
- IcmpInCsumErrors
- IcmpInDestUnreachs
- IcmpInEchoReps
- IcmpInEchos
- IcmpInErrors
- IcmpInMsgs
- IcmpInParmProbs
- IcmpInRedirects
- IcmpInSrcQuenchs
- IcmpInTimeExcds
- IcmpInTimestampReps
- IcmpInTimestamps
- IcmpMsgInType3
- IcmpMsgOutType3
- IcmpOutAddrMaskReps
- IcmpOutAddrMasks
- IcmpOutDestUnreachs
- IcmpOutEchoReps
- IcmpOutEchos
- IcmpOutErrors
- IcmpOutMsgs
- IcmpOutParmProbs
- IcmpOutRedirects
- IcmpOutSrcQuenchs
- IcmpOutTimeExcds
- IcmpOutTimestampReps
- IcmpOutTimestamps
- Ip6FragCreates
- Ip6FragFails
- Ip6FragOKs
- Ip6InAddrErrors
- Ip6InBcastOctets
- Ip6InCEPkts
- Ip6InDelivers
- Ip6InDiscards
- Ip6InECT0Pkts
- Ip6InECT1Pkts
- Ip6InHdrErrors
- Ip6InMcastOctets
- Ip6InMcastPkts
- Ip6InNoECTPkts
- Ip6InNoRoutes
- Ip6InOctets
- Ip6InReceives
- Ip6InTooBigErrors
- Ip6InTruncatedPkts
- Ip6InUnknownProtos
- Ip6OutBcastOctets
- Ip6OutDiscards
- Ip6OutForwDatagrams
- Ip6OutMcastOctets
- Ip6OutMcastPkts
- Ip6OutNoRoutes
- Ip6OutOctets
- Ip6OutRequests
- Ip6ReasmFails
- Ip6ReasmOKs
- Ip6ReasmReqds
- Ip6ReasmTimeout
- IpDefaultTTL
- IpExtInBcastOctets
- IpExtInBcastPkts
- IpExtInCEPkts
- IpExtInCsumErrors
- IpExtInECT0Pkts
- IpExtInECT1Pkts
- IpExtInMcastOctets
- IpExtInMcastPkts
- IpExtInNoECTPkts
- IpExtInNoRoutes
- IpExtInOctets
- IpExtInTruncatedPkts
- IpExtOutBcastOctets
- IpExtOutBcastPkts
- IpExtOutMcastOctets
- IpExtOutMcastPkts
- IpExtOutOctets
- IpForwDatagrams
- IpForwarding
- IpFragCreates
- IpFragFails
- IpFragOKs
- IpInAddrErrors
- IpInDelivers
- IpInDiscards
- IpInHdrErrors
- IpInReceives
- IpInUnknownProtos
- IpOutDiscards
- IpOutNoRoutes
- IpOutRequests
- IpReasmFails
- IpReasmOKs
- IpReasmReqds
- IpReasmTimeout
- TcpActiveOpens
- TcpAttemptFails
- TcpCurrEstab
- TcpEstabResets
- TcpExtArpFilter
- TcpExtBusyPollRxPackets
- TcpExtDelayedACKLocked
- TcpExtDelayedACKLost
- TcpExtDelayedACKs
- TcpExtEmbryonicRsts
- TcpExtIPReversePathFilter
- TcpExtListenDrops
- TcpExtListenOverflows
- TcpExtLockDroppedIcmps
- TcpExtOfoPruned
- TcpExtOutOfWindowIcmps
- TcpExtPAWSActive
- TcpExtPAWSEstab
- TcpExtPAWSPassive
- TcpExtPruneCalled
- TcpExtRcvPruned
- TcpExtSyncookiesFailed
- TcpExtSyncookiesRecv
- TcpExtSyncookiesSent
- TcpExtTCPACKSkippedChallenge
- TcpExtTCPACKSkippedFinWait2
- TcpExtTCPACKSkippedPAWS
- TcpExtTCPACKSkippedSeq
- TcpExtTCPACKSkippedSynRecv
- TcpExtTCPACKSkippedTimeWait
- TcpExtTCPAbortFailed
- TcpExtTCPAbortOnClose
- TcpExtTCPAbortOnData
- TcpExtTCPAbortOnLinger
- TcpExtTCPAbortOnMemory
- TcpExtTCPAbortOnTimeout
- TcpExtTCPAutoCorking
- TcpExtTCPBacklogDrop
- TcpExtTCPChallengeACK
- TcpExtTCPDSACKIgnoredNoUndo
- TcpExtTCPDSACKIgnoredOld
- TcpExtTCPDSACKOfoRecv
- TcpExtTCPDSACKOfoSent
- TcpExtTCPDSACKOldSent
- TcpExtTCPDSACKRecv
- TcpExtTCPDSACKUndo
- TcpExtTCPDeferAcceptDrop
- TcpExtTCPDirectCopyFromBacklog
- TcpExtTCPDirectCopyFromPrequeue
- TcpExtTCPFACKReorder
- TcpExtTCPFastOpenActive
- TcpExtTCPFastOpenActiveFail
- TcpExtTCPFastOpenCookieReqd
- TcpExtTCPFastOpenListenOverflow
- TcpExtTCPFastOpenPassive
- TcpExtTCPFastOpenPassiveFail
- TcpExtTCPFastRetrans
- TcpExtTCPForwardRetrans
- TcpExtTCPFromZeroWindowAdv
- TcpExtTCPFullUndo
- TcpExtTCPHPAcks
- TcpExtTCPHPHits
- TcpExtTCPHPHitsToUser
- TcpExtTCPHystartDelayCwnd
- TcpExtTCPHystartDelayDetect
- TcpExtTCPHystartTrainCwnd
- TcpExtTCPHystartTrainDetect
- TcpExtTCPKeepAlive
- TcpExtTCPLossFailures
- TcpExtTCPLossProbeRecovery
- TcpExtTCPLossProbes
- TcpExtTCPLossUndo
- TcpExtTCPLostRetransmit
- TcpExtTCPMD5NotFound
- TcpExtTCPMD5Unexpected
- TcpExtTCPMTUPFail
- TcpExtTCPMTUPSuccess
- TcpExtTCPMemoryPressures
- TcpExtTCPMinTTLDrop
- TcpExtTCPOFODrop
- TcpExtTCPOFOMerge
- TcpExtTCPOFOQueue
- TcpExtTCPOrigDataSent
- TcpExtTCPPartialUndo
- TcpExtTCPPrequeueDropped
- TcpExtTCPPrequeued
- TcpExtTCPPureAcks
- TcpExtTCPRcvCoalesce
- TcpExtTCPRcvCollapsed
- TcpExtTCPRenoFailures
- TcpExtTCPRenoRecovery
- TcpExtTCPRenoRecoveryFail
- TcpExtTCPRenoReorder
- TcpExtTCPReqQFullDoCookies
- TcpExtTCPReqQFullDrop
- TcpExtTCPRetransFail
- TcpExtTCPSACKDiscard
- TcpExtTCPSACKReneging
- TcpExtTCPSACKReorder
- TcpExtTCPSYNChallenge
- TcpExtTCPSackFailures
- TcpExtTCPSackMerged
- TcpExtTCPSackRecovery
- TcpExtTCPSackRecoveryFail
- TcpExtTCPSackShiftFallback
- TcpExtTCPSackShifted
- TcpExtTCPSchedulerFailed
- TcpExtTCPSlowStartRetrans
- TcpExtTCPSpuriousRTOs
- TcpExtTCPSpuriousRtxHostQueues
- TcpExtTCPSynRetrans
- TcpExtTCPTSReorder
- TcpExtTCPTimeWaitOverflow
- TcpExtTCPTimeouts
- TcpExtTCPToZeroWindowAdv
- TcpExtTCPWantZeroWindowAdv
- TcpExtTCPWinProbe
- TcpExtTW
- TcpExtTWKilled
- TcpExtTWRecycled
- TcpInCsumErrors
- TcpInErrs
- TcpInSegs
- TcpMaxConn
- TcpOutRsts
- TcpOutSegs
- TcpPassiveOpens
- TcpRetransSegs
- TcpRtoAlgorithm
- TcpRtoMax
- TcpRtoMin
- Udp6IgnoredMulti
- Udp6InCsumErrors
- Udp6InDatagrams
- Udp6InErrors
- Udp6NoPorts
- Udp6OutDatagrams
- Udp6RcvbufErrors
- Udp6SndbufErrors
- UdpIgnoredMulti
- UdpInCsumErrors
- UdpInDatagrams
- UdpInErrors
- UdpLite6InCsumErrors
- UdpLite6InDatagrams
- UdpLite6InErrors
- UdpLite6NoPorts
- UdpLite6OutDatagrams
- UdpLite6RcvbufErrors
- UdpLite6SndbufErrors
- UdpLiteIgnoredMulti
- UdpLiteInCsumErrors
- UdpLiteInDatagrams
- UdpLiteInErrors
- UdpLiteNoPorts
- UdpLiteOutDatagrams
- UdpLiteRcvbufErrors
- UdpLiteSndbufErrors
- UdpNoPorts
- UdpOutDatagrams
- UdpRcvbufErrors
- UdpSndbufErrors
### Tags
- All measurements have the following tags
- host (host of the system)
- name (the type of the metric: snmp, snmp6 or netstat)

View File

@@ -1,233 +0,0 @@
package nstat
import (
"bytes"
"io/ioutil"
"os"
"strconv"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
)
var (
zeroByte = []byte("0")
newLineByte = []byte("\n")
colonByte = []byte(":")
)
// default file paths
const (
NET_NETSTAT = "/net/netstat"
NET_SNMP = "/net/snmp"
NET_SNMP6 = "/net/snmp6"
NET_PROC = "/proc"
)
// env variable names
const (
ENV_NETSTAT = "PROC_NET_NETSTAT"
ENV_SNMP = "PROC_NET_SNMP"
ENV_SNMP6 = "PROC_NET_SNMP6"
ENV_ROOT = "PROC_ROOT"
)
type Nstat struct {
ProcNetNetstat string `toml:"proc_net_netstat"`
ProcNetSNMP string `toml:"proc_net_snmp"`
ProcNetSNMP6 string `toml:"proc_net_snmp6"`
DumpZeros bool `toml:"dump_zeros"`
}
var sampleConfig = `
## file paths for proc files. If empty default paths will be used:
## /proc/net/netstat, /proc/net/snmp, /proc/net/snmp6
## These can also be overridden with env variables, see README.
proc_net_netstat = ""
proc_net_snmp = ""
proc_net_snmp6 = ""
## dump metrics with 0 values too
dump_zeros = true
`
func (ns *Nstat) Description() string {
return "Collect kernel snmp counters and network interface statistics"
}
func (ns *Nstat) SampleConfig() string {
return sampleConfig
}
func (ns *Nstat) Gather(acc telegraf.Accumulator) error {
// load paths, get from env if config values are empty
ns.loadPaths()
netstat, err := ioutil.ReadFile(ns.ProcNetNetstat)
if err != nil {
return err
}
// collect netstat data
err = ns.gatherNetstat(netstat, acc)
if err != nil {
return err
}
// collect SNMP data
snmp, err := ioutil.ReadFile(ns.ProcNetSNMP)
if err != nil {
return err
}
err = ns.gatherSNMP(snmp, acc)
if err != nil {
return err
}
// collect SNMP6 data
snmp6, err := ioutil.ReadFile(ns.ProcNetSNMP6)
if err != nil {
return err
}
err = ns.gatherSNMP6(snmp6, acc)
if err != nil {
return err
}
return nil
}
func (ns *Nstat) gatherNetstat(data []byte, acc telegraf.Accumulator) error {
metrics, err := loadUglyTable(data, ns.DumpZeros)
if err != nil {
return err
}
tags := map[string]string{
"name": "netstat",
}
acc.AddFields("nstat", metrics, tags)
return nil
}
func (ns *Nstat) gatherSNMP(data []byte, acc telegraf.Accumulator) error {
metrics, err := loadUglyTable(data, ns.DumpZeros)
if err != nil {
return err
}
tags := map[string]string{
"name": "snmp",
}
acc.AddFields("nstat", metrics, tags)
return nil
}
func (ns *Nstat) gatherSNMP6(data []byte, acc telegraf.Accumulator) error {
metrics, err := loadGoodTable(data, ns.DumpZeros)
if err != nil {
return err
}
tags := map[string]string{
"name": "snmp6",
}
acc.AddFields("nstat", metrics, tags)
return nil
}
// loadPaths can be used to read paths firstly from config
// if it is empty then try read from env variables
func (ns *Nstat) loadPaths() {
if ns.ProcNetNetstat == "" {
ns.ProcNetNetstat = proc(ENV_NETSTAT, NET_NETSTAT)
}
if ns.ProcNetSNMP == "" {
ns.ProcNetSNMP = proc(ENV_SNMP, NET_SNMP)
}
if ns.ProcNetSNMP6 == "" {
ns.ProcNetSNMP = proc(ENV_SNMP6, NET_SNMP6)
}
}
// loadGoodTable can be used to parse string heap that
// headers and values are arranged in right order
func loadGoodTable(table []byte, dumpZeros bool) (map[string]interface{}, error) {
entries := map[string]interface{}{}
fields := bytes.Fields(table)
var value int64
var err error
// iterate over two values each time
// first value is header, second is value
for i := 0; i < len(fields); i = i + 2 {
// counter is zero
if bytes.Equal(fields[i+1], zeroByte) {
if !dumpZeros {
continue
} else {
entries[string(fields[i])] = int64(0)
continue
}
}
// the counter is not zero, so parse it.
value, err = strconv.ParseInt(string(fields[i+1]), 10, 64)
if err == nil {
entries[string(fields[i])] = value
}
}
return entries, nil
}
// loadUglyTable can be used to parse string heap that
// the headers and values are splitted with a newline
func loadUglyTable(table []byte, dumpZeros bool) (map[string]interface{}, error) {
entries := map[string]interface{}{}
// split the lines by newline
lines := bytes.Split(table, newLineByte)
var value int64
var err error
// iterate over lines, take 2 lines each time
// first line contains header names
// second line contains values
for i := 0; i < len(lines); i = i + 2 {
if len(lines[i]) == 0 {
continue
}
headers := bytes.Fields(lines[i])
prefix := bytes.TrimSuffix(headers[0], colonByte)
metrics := bytes.Fields(lines[i+1])
for j := 1; j < len(headers); j++ {
// counter is zero
if bytes.Equal(metrics[j], zeroByte) {
if !dumpZeros {
continue
} else {
entries[string(append(prefix, headers[j]...))] = int64(0)
continue
}
}
// the counter is not zero, so parse it.
value, err = strconv.ParseInt(string(metrics[j]), 10, 64)
if err == nil {
entries[string(append(prefix, headers[j]...))] = value
}
}
}
return entries, nil
}
// proc can be used to read file paths from env
func proc(env, path string) string {
// try to read full file path
if p := os.Getenv(env); p != "" {
return p
}
// try to read root path, or use default root path
root := os.Getenv(ENV_ROOT)
if root == "" {
root = NET_PROC
}
return root + path
}
func init() {
inputs.Add("nstat", func() telegraf.Input {
return &Nstat{}
})
}

View File

@@ -1,56 +0,0 @@
package nstat
import "testing"
func TestLoadUglyTable(t *testing.T) {
uglyStr := `IpExt: InNoRoutes InTruncatedPkts InMcastPkts InCEPkts
IpExt: 332 433718 0 2660494435`
parsed := map[string]interface{}{
"IpExtInNoRoutes": int64(332),
"IpExtInTruncatedPkts": int64(433718),
"IpExtInMcastPkts": int64(0),
"IpExtInCEPkts": int64(2660494435),
}
got, err := loadUglyTable([]byte(uglyStr), true)
if err != nil {
t.Fatal(err)
}
if len(got) == 0 {
t.Fatalf("want %+v, got %+v", parsed, got)
}
for key := range parsed {
if parsed[key].(int64) != got[key].(int64) {
t.Fatalf("want %+v, got %+v", parsed[key], got[key])
}
}
}
func TestLoadGoodTable(t *testing.T) {
goodStr := `Ip6InReceives 11707
Ip6InTooBigErrors 0
Ip6InDelivers 62
Ip6InMcastOctets 1242966`
parsed := map[string]interface{}{
"Ip6InReceives": int64(11707),
"Ip6InTooBigErrors": int64(0),
"Ip6InDelivers": int64(62),
"Ip6InMcastOctets": int64(1242966),
}
got, err := loadGoodTable([]byte(goodStr), true)
if err != nil {
t.Fatal(err)
}
if len(got) == 0 {
t.Fatalf("want %+v, got %+v", parsed, got)
}
for key := range parsed {
if parsed[key].(int64) != got[key].(int64) {
t.Fatalf("want %+v, got %+v", parsed[key], got[key])
}
}
}

View File

@@ -70,17 +70,7 @@ func (n *NTPQ) Gather(acc telegraf.Accumulator) error {
lineCounter := 0 lineCounter := 0
scanner := bufio.NewScanner(bytes.NewReader(out)) scanner := bufio.NewScanner(bytes.NewReader(out))
for scanner.Scan() { for scanner.Scan() {
line := scanner.Text() fields := strings.Fields(scanner.Text())
tags := make(map[string]string)
// if there is an ntpq state prefix, remove it and make it it's own tag
// see https://github.com/influxdata/telegraf/issues/1161
if strings.ContainsAny(string(line[0]), "*#o+x.-") {
tags["state_prefix"] = string(line[0])
line = strings.TrimLeft(line, "*#o+x.-")
}
fields := strings.Fields(line)
if len(fields) < 2 { if len(fields) < 2 {
continue continue
} }
@@ -107,6 +97,7 @@ func (n *NTPQ) Gather(acc telegraf.Accumulator) error {
} }
} }
} else { } else {
tags := make(map[string]string)
mFields := make(map[string]interface{}) mFields := make(map[string]interface{})
// Get tags from output // Get tags from output
@@ -122,9 +113,6 @@ func (n *NTPQ) Gather(acc telegraf.Accumulator) error {
if index == -1 { if index == -1 {
continue continue
} }
if fields[index] == "-" {
continue
}
if key == "when" { if key == "when" {
when := fields[index] when := fields[index]
@@ -172,9 +160,6 @@ func (n *NTPQ) Gather(acc telegraf.Accumulator) error {
if index == -1 { if index == -1 {
continue continue
} }
if fields[index] == "-" {
continue
}
m, err := strconv.ParseFloat(fields[index], 64) m, err := strconv.ParseFloat(fields[index], 64)
if err != nil { if err != nil {

View File

@@ -32,11 +32,10 @@ func TestSingleNTPQ(t *testing.T) {
"jitter": float64(17.462), "jitter": float64(17.462),
} }
tags := map[string]string{ tags := map[string]string{
"remote": "uschi5-ntp-002.", "remote": "*uschi5-ntp-002.",
"state_prefix": "*", "refid": "10.177.80.46",
"refid": "10.177.80.46", "stratum": "2",
"stratum": "2", "type": "u",
"type": "u",
} }
acc.AssertContainsTaggedFields(t, "ntpq", fields, tags) acc.AssertContainsTaggedFields(t, "ntpq", fields, tags)
} }
@@ -61,11 +60,10 @@ func TestBadIntNTPQ(t *testing.T) {
"jitter": float64(17.462), "jitter": float64(17.462),
} }
tags := map[string]string{ tags := map[string]string{
"remote": "uschi5-ntp-002.", "remote": "*uschi5-ntp-002.",
"state_prefix": "*", "refid": "10.177.80.46",
"refid": "10.177.80.46", "stratum": "2",
"stratum": "2", "type": "u",
"type": "u",
} }
acc.AssertContainsTaggedFields(t, "ntpq", fields, tags) acc.AssertContainsTaggedFields(t, "ntpq", fields, tags)
} }
@@ -90,11 +88,10 @@ func TestBadFloatNTPQ(t *testing.T) {
"jitter": float64(17.462), "jitter": float64(17.462),
} }
tags := map[string]string{ tags := map[string]string{
"remote": "uschi5-ntp-002.", "remote": "*uschi5-ntp-002.",
"state_prefix": "*", "refid": "10.177.80.46",
"refid": "10.177.80.46", "stratum": "2",
"stratum": "2", "type": "u",
"type": "u",
} }
acc.AssertContainsTaggedFields(t, "ntpq", fields, tags) acc.AssertContainsTaggedFields(t, "ntpq", fields, tags)
} }
@@ -120,11 +117,10 @@ func TestDaysNTPQ(t *testing.T) {
"jitter": float64(17.462), "jitter": float64(17.462),
} }
tags := map[string]string{ tags := map[string]string{
"remote": "uschi5-ntp-002.", "remote": "*uschi5-ntp-002.",
"state_prefix": "*", "refid": "10.177.80.46",
"refid": "10.177.80.46", "stratum": "2",
"stratum": "2", "type": "u",
"type": "u",
} }
acc.AssertContainsTaggedFields(t, "ntpq", fields, tags) acc.AssertContainsTaggedFields(t, "ntpq", fields, tags)
} }
@@ -150,11 +146,10 @@ func TestHoursNTPQ(t *testing.T) {
"jitter": float64(17.462), "jitter": float64(17.462),
} }
tags := map[string]string{ tags := map[string]string{
"remote": "uschi5-ntp-002.", "remote": "*uschi5-ntp-002.",
"state_prefix": "*", "refid": "10.177.80.46",
"refid": "10.177.80.46", "stratum": "2",
"stratum": "2", "type": "u",
"type": "u",
} }
acc.AssertContainsTaggedFields(t, "ntpq", fields, tags) acc.AssertContainsTaggedFields(t, "ntpq", fields, tags)
} }
@@ -180,11 +175,10 @@ func TestMinutesNTPQ(t *testing.T) {
"jitter": float64(17.462), "jitter": float64(17.462),
} }
tags := map[string]string{ tags := map[string]string{
"remote": "uschi5-ntp-002.", "remote": "*uschi5-ntp-002.",
"state_prefix": "*", "refid": "10.177.80.46",
"refid": "10.177.80.46", "stratum": "2",
"stratum": "2", "type": "u",
"type": "u",
} }
acc.AssertContainsTaggedFields(t, "ntpq", fields, tags) acc.AssertContainsTaggedFields(t, "ntpq", fields, tags)
} }
@@ -209,11 +203,10 @@ func TestBadWhenNTPQ(t *testing.T) {
"jitter": float64(17.462), "jitter": float64(17.462),
} }
tags := map[string]string{ tags := map[string]string{
"remote": "uschi5-ntp-002.", "remote": "*uschi5-ntp-002.",
"state_prefix": "*", "refid": "10.177.80.46",
"refid": "10.177.80.46", "stratum": "2",
"stratum": "2", "type": "u",
"type": "u",
} }
acc.AssertContainsTaggedFields(t, "ntpq", fields, tags) acc.AssertContainsTaggedFields(t, "ntpq", fields, tags)
} }
@@ -285,10 +278,9 @@ func TestBadHeaderNTPQ(t *testing.T) {
"jitter": float64(17.462), "jitter": float64(17.462),
} }
tags := map[string]string{ tags := map[string]string{
"remote": "uschi5-ntp-002.", "remote": "*uschi5-ntp-002.",
"state_prefix": "*", "refid": "10.177.80.46",
"refid": "10.177.80.46", "type": "u",
"type": "u",
} }
acc.AssertContainsTaggedFields(t, "ntpq", fields, tags) acc.AssertContainsTaggedFields(t, "ntpq", fields, tags)
} }
@@ -314,10 +306,9 @@ func TestMissingDelayColumnNTPQ(t *testing.T) {
"jitter": float64(17.462), "jitter": float64(17.462),
} }
tags := map[string]string{ tags := map[string]string{
"remote": "uschi5-ntp-002.", "remote": "*uschi5-ntp-002.",
"state_prefix": "*", "refid": "10.177.80.46",
"refid": "10.177.80.46", "type": "u",
"type": "u",
} }
acc.AssertContainsTaggedFields(t, "ntpq", fields, tags) acc.AssertContainsTaggedFields(t, "ntpq", fields, tags)
} }

View File

@@ -28,7 +28,7 @@ type Ping struct {
// Number of pings to send (ping -c <COUNT>) // Number of pings to send (ping -c <COUNT>)
Count int Count int
// Ping timeout, in seconds. 0 means no timeout (ping -W <TIMEOUT>) // Ping timeout, in seconds. 0 means no timeout (ping -t <TIMEOUT>)
Timeout float64 Timeout float64
// Interface to send ping from (ping -I <INTERFACE>) // Interface to send ping from (ping -I <INTERFACE>)
@@ -55,7 +55,7 @@ const sampleConfig = `
count = 1 # required count = 1 # required
## interval, in s, at which to ping. 0 == default (ping -i <PING_INTERVAL>) ## interval, in s, at which to ping. 0 == default (ping -i <PING_INTERVAL>)
ping_interval = 0.0 ping_interval = 0.0
## per-ping timeout, in s. 0 == no timeout (ping -W <TIMEOUT>) ## ping timeout, in s. 0 == no timeout (ping -W <TIMEOUT>)
timeout = 1.0 timeout = 1.0
## interface to send ping from (ping -I <INTERFACE>) ## interface to send ping from (ping -I <INTERFACE>)
interface = "" interface = ""
@@ -76,8 +76,7 @@ func (p *Ping) Gather(acc telegraf.Accumulator) error {
go func(u string) { go func(u string) {
defer wg.Done() defer wg.Done()
args := p.args(u) args := p.args(u)
totalTimeout := float64(p.Count)*p.Timeout + float64(p.Count-1)*p.PingInterval out, err := p.pingHost(p.Timeout, args...)
out, err := p.pingHost(totalTimeout, args...)
if err != nil { if err != nil {
// Combine go err + stderr output // Combine go err + stderr output
errorChannel <- errors.New( errorChannel <- errors.New(
@@ -139,8 +138,8 @@ func (p *Ping) args(url string) []string {
} }
if p.Timeout > 0 { if p.Timeout > 0 {
switch runtime.GOOS { switch runtime.GOOS {
case "darwin": case "darwin", "freebsd":
args = append(args, "-W", strconv.FormatFloat(p.Timeout/1000, 'f', 1, 64)) args = append(args, "-t", strconv.FormatFloat(p.Timeout, 'f', 1, 64))
case "linux": case "linux":
args = append(args, "-W", strconv.FormatFloat(p.Timeout, 'f', 1, 64)) args = append(args, "-W", strconv.FormatFloat(p.Timeout, 'f', 1, 64))
default: default:

View File

@@ -15,12 +15,11 @@ import (
) )
type Procstat struct { type Procstat struct {
PidFile string `toml:"pid_file"` PidFile string `toml:"pid_file"`
Exe string Exe string
Pattern string Pattern string
Prefix string Prefix string
ProcessName string User string
User string
// pidmap maps a pid to a process object, so we don't recreate every gather // pidmap maps a pid to a process object, so we don't recreate every gather
pidmap map[int32]*process.Process pidmap map[int32]*process.Process
@@ -46,9 +45,6 @@ var sampleConfig = `
## user as argument for pgrep (ie, pgrep -u <user>) ## user as argument for pgrep (ie, pgrep -u <user>)
# user = "nginx" # user = "nginx"
## override for process_name
## This is optional; default is sourced from /proc/<pid>/status
# process_name = "bar"
## Field name prefix ## Field name prefix
prefix = "" prefix = ""
## comment this out if you want raw cpu_time stats ## comment this out if you want raw cpu_time stats
@@ -70,7 +66,7 @@ func (p *Procstat) Gather(acc telegraf.Accumulator) error {
p.Exe, p.PidFile, p.Pattern, p.User, err.Error()) p.Exe, p.PidFile, p.Pattern, p.User, err.Error())
} else { } else {
for pid, proc := range p.pidmap { for pid, proc := range p.pidmap {
p := NewSpecProcessor(p.ProcessName, p.Prefix, acc, proc, p.tagmap[pid]) p := NewSpecProcessor(p.Prefix, acc, proc, p.tagmap[pid])
p.pushMetrics() p.pushMetrics()
} }
} }

View File

@@ -17,19 +17,13 @@ type SpecProcessor struct {
} }
func NewSpecProcessor( func NewSpecProcessor(
processName string,
prefix string, prefix string,
acc telegraf.Accumulator, acc telegraf.Accumulator,
p *process.Process, p *process.Process,
tags map[string]string, tags map[string]string,
) *SpecProcessor { ) *SpecProcessor {
if processName != "" { if name, err := p.Name(); err == nil {
tags["process_name"] = processName tags["process_name"] = name
} else {
name, err := p.Name()
if err == nil {
tags["process_name"] = name
}
} }
return &SpecProcessor{ return &SpecProcessor{
Prefix: prefix, Prefix: prefix,
@@ -71,7 +65,7 @@ func (p *SpecProcessor) pushMetrics() {
fields[prefix+"write_bytes"] = io.WriteCount fields[prefix+"write_bytes"] = io.WriteCount
} }
cpu_time, err := p.proc.Times() cpu_time, err := p.proc.CPUTimes()
if err == nil { if err == nil {
fields[prefix+"cpu_time_user"] = cpu_time.User fields[prefix+"cpu_time_user"] = cpu_time.User
fields[prefix+"cpu_time_system"] = cpu_time.System fields[prefix+"cpu_time_system"] = cpu_time.System
@@ -86,7 +80,7 @@ func (p *SpecProcessor) pushMetrics() {
fields[prefix+"cpu_time_guest_nice"] = cpu_time.GuestNice fields[prefix+"cpu_time_guest_nice"] = cpu_time.GuestNice
} }
cpu_perc, err := p.proc.Percent(time.Duration(0)) cpu_perc, err := p.proc.CPUPercent(time.Duration(0))
if err == nil && cpu_perc != 0 { if err == nil && cpu_perc != 0 {
fields[prefix+"cpu_usage"] = cpu_perc fields[prefix+"cpu_usage"] = cpu_perc
} }

View File

@@ -10,7 +10,6 @@ import (
"io" "io"
"math" "math"
"mime" "mime"
"time"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
@@ -75,13 +74,13 @@ func (p *PrometheusParser) Parse(buf []byte) ([]telegraf.Metric, error) {
if mf.GetType() == dto.MetricType_SUMMARY { if mf.GetType() == dto.MetricType_SUMMARY {
// summary metric // summary metric
fields = makeQuantiles(m) fields = makeQuantiles(m)
fields["count"] = float64(m.GetSummary().GetSampleCount()) fields["count"] = float64(m.GetHistogram().GetSampleCount())
fields["sum"] = float64(m.GetSummary().GetSampleSum()) fields["sum"] = float64(m.GetSummary().GetSampleSum())
} else if mf.GetType() == dto.MetricType_HISTOGRAM { } else if mf.GetType() == dto.MetricType_HISTOGRAM {
// historgram metric // historgram metric
fields = makeBuckets(m) fields = makeBuckets(m)
fields["count"] = float64(m.GetHistogram().GetSampleCount()) fields["count"] = float64(m.GetHistogram().GetSampleCount())
fields["sum"] = float64(m.GetHistogram().GetSampleSum()) fields["sum"] = float64(m.GetSummary().GetSampleSum())
} else { } else {
// standard metric // standard metric
@@ -89,13 +88,7 @@ func (p *PrometheusParser) Parse(buf []byte) ([]telegraf.Metric, error) {
} }
// converting to telegraf metric // converting to telegraf metric
if len(fields) > 0 { if len(fields) > 0 {
var t time.Time metric, err := telegraf.NewMetric(metricName, tags, fields)
if m.TimestampMs != nil && *m.TimestampMs > 0 {
t = time.Unix(0, *m.TimestampMs*1000000)
} else {
t = time.Now()
}
metric, err := telegraf.NewMetric(metricName, tags, fields, t)
if err == nil { if err == nil {
metrics = append(metrics, metric) metrics = append(metrics, metric)
} }

Some files were not shown because too many files have changed in this diff Show More