Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f76739cb1b | ||
|
|
7f992fd321 | ||
|
|
e428d11add | ||
|
|
0ed5b75a14 | ||
|
|
b1b4adec74 | ||
|
|
1934cc2e62 | ||
|
|
ae8cf8c35e | ||
|
|
f5878eafb9 | ||
|
|
27fe4f7062 | ||
|
|
7ad8b26297 | ||
|
|
1a383b7d90 | ||
|
|
445946792e | ||
|
|
de82c7d5ac | ||
|
|
07f0d561dc | ||
|
|
be379f3dac | ||
|
|
1bf904fe60 | ||
|
|
c6faf005cb | ||
|
|
b534b58542 | ||
|
|
920711533e | ||
|
|
194110433e | ||
|
|
7926396d2a | ||
|
|
797522e8ca | ||
|
|
64b5d1a269 | ||
|
|
d00d3802c9 | ||
|
|
46fff13341 | ||
|
|
be3374a3ef | ||
|
|
264ac0b017 | ||
|
|
17033b3c6c | ||
|
|
c4ea122d66 | ||
|
|
90185dc6b3 | ||
|
|
377b030d88 | ||
|
|
437bd87d7c | ||
|
|
73a7916ce3 | ||
|
|
f947fa86e3 | ||
|
|
7219efbdb7 | ||
|
|
b7435b9cd1 | ||
|
|
207ab5a0d1 | ||
|
|
70aa0ef85d | ||
|
|
dfbe231a51 | ||
|
|
cce35da366 | ||
|
|
1a612bcae9 | ||
|
|
d5b9e003fe | ||
|
|
e19c474a92 | ||
|
|
8274798499 | ||
|
|
9f68a32934 | ||
|
|
5c688daff1 | ||
|
|
741fb1181f | ||
|
|
fd1f05c8e0 | ||
|
|
708cbf937f | ||
|
|
32213cad01 | ||
|
|
9320a6e115 | ||
|
|
0f16c0f4cf | ||
|
|
30464396d9 | ||
|
|
64066c4ea8 | ||
|
|
7e97787d9d | ||
|
|
40f2dd8c6c | ||
|
|
4dd364e1c3 | ||
|
|
03f2a35b31 | ||
|
|
73bd98df57 | ||
|
|
bcf1fc658d | ||
|
|
863cbe512d |
30
CHANGELOG.md
30
CHANGELOG.md
@@ -1,3 +1,33 @@
|
||||
## v0.13 [unreleased]
|
||||
|
||||
## v0.12.1 [2016-04-14]
|
||||
|
||||
### Release Notes
|
||||
- Breaking change in the dovecot input plugin. See Features section below.
|
||||
- Graphite output templates are now supported. See
|
||||
https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md#graphite
|
||||
- Possible breaking change for the librato and graphite outputs. Telegraf will
|
||||
no longer insert field names when the field is simply named `value`. This is
|
||||
because the `value` field is redundant in the graphite/librato context.
|
||||
|
||||
### Features
|
||||
- [#1009](https://github.com/influxdata/telegraf/pull/1009): Cassandra input plugin. Thanks @subhachandrachandra!
|
||||
- [#976](https://github.com/influxdata/telegraf/pull/976): Reduce allocations in the UDP and statsd inputs.
|
||||
- [#979](https://github.com/influxdata/telegraf/pull/979): Reduce allocations in the TCP listener.
|
||||
- [#992](https://github.com/influxdata/telegraf/pull/992): Refactor allocations in TCP/UDP listeners.
|
||||
- [#935](https://github.com/influxdata/telegraf/pull/935): AWS Cloudwatch input plugin. Thanks @joshhardy & @ljosa!
|
||||
- [#943](https://github.com/influxdata/telegraf/pull/943): http_response input plugin. Thanks @Lswith!
|
||||
- [#939](https://github.com/influxdata/telegraf/pull/939): sysstat input plugin. Thanks @zbindenren!
|
||||
- [#998](https://github.com/influxdata/telegraf/pull/998): **breaking change** enabled global, user and ip queries in dovecot plugin. Thanks @mikif70!
|
||||
- [#1001](https://github.com/influxdata/telegraf/pull/1001): Graphite serializer templates.
|
||||
- [#1008](https://github.com/influxdata/telegraf/pull/1008): Adding memstats metrics to the influxdb plugin.
|
||||
|
||||
### Bugfixes
|
||||
- [#968](https://github.com/influxdata/telegraf/issues/968): Processes plugin gets unknown state when spaces are in (command name)
|
||||
- [#969](https://github.com/influxdata/telegraf/pull/969): ipmi_sensors: allow : in password. Thanks @awaw!
|
||||
- [#972](https://github.com/influxdata/telegraf/pull/972): dovecot: remove extra newline in dovecot command. Thanks @mrannanj!
|
||||
- [#645](https://github.com/influxdata/telegraf/issues/645): docker plugin i/o error on closed pipe. Thanks @tripledes!
|
||||
|
||||
## v0.12.0 [2016-04-05]
|
||||
|
||||
### Features
|
||||
|
||||
@@ -114,7 +114,7 @@ creating the `Parser` object.
|
||||
You should also add the following to your SampleConfig() return:
|
||||
|
||||
```toml
|
||||
## Data format to consume. This can be "json", "influx" or "graphite"
|
||||
## Data format to consume.
|
||||
## Each data format has it's own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md
|
||||
@@ -244,7 +244,7 @@ instantiating and creating the `Serializer` object.
|
||||
You should also add the following to your SampleConfig() return:
|
||||
|
||||
```toml
|
||||
## Data format to output. This can be "influx" or "graphite"
|
||||
## Data format to output.
|
||||
## Each data format has it's own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md
|
||||
|
||||
7
Godeps
7
Godeps
@@ -9,10 +9,12 @@ github.com/couchbase/gomemcached a5ea6356f648fec6ab89add00edd09151455b4b2
|
||||
github.com/couchbase/goutils 5823a0cbaaa9008406021dc5daf80125ea30bba6
|
||||
github.com/dancannon/gorethink e7cac92ea2bc52638791a021f212145acfedb1fc
|
||||
github.com/davecgh/go-spew 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
|
||||
github.com/docker/engine-api 8924d6900370b4c7e7984be5adc61f50a80d7537
|
||||
github.com/docker/go-connections f549a9393d05688dff0992ef3efd8bbe6c628aeb
|
||||
github.com/docker/go-units 5d2041e26a699eaca682e2ea41c8f891e1060444
|
||||
github.com/eapache/go-resiliency b86b1ec0dd4209a588dc1285cdd471e73525c0b3
|
||||
github.com/eapache/queue ded5959c0d4e360646dc9e9908cff48666781367
|
||||
github.com/eclipse/paho.mqtt.golang 4ab3e867810d1ec5f35157c59e965054dbf43a0d
|
||||
github.com/fsouza/go-dockerclient a49c8269a6899cae30da1f8a4b82e0ce945f9967
|
||||
github.com/eclipse/paho.mqtt.golang 0f7a459f04f13a41b7ed752d47944528d4bf9a86
|
||||
github.com/go-sql-driver/mysql 1fca743146605a172a266e1654e01e5cd5669bee
|
||||
github.com/golang/protobuf 552c7b9542c194800fd493123b3798ef0a832032
|
||||
github.com/golang/snappy 427fb6fc07997f43afa32f35e850833760e489a7
|
||||
@@ -32,6 +34,7 @@ github.com/naoina/go-stringutil 6b638e95a32d0c1131db0e7fe83775cbea4a0d0b
|
||||
github.com/nats-io/nats b13fc9d12b0b123ebc374e6b808c6228ae4234a3
|
||||
github.com/nats-io/nuid 4f84f5f3b2786224e336af2e13dba0a0a80b76fa
|
||||
github.com/nsqio/go-nsq 0b80d6f05e15ca1930e0c5e1d540ed627e299980
|
||||
github.com/opencontainers/runc 89ab7f2ccc1e45ddf6485eaa802c35dcf321dfc8
|
||||
github.com/prometheus/client_golang 18acf9993a863f4c4b40612e19cdd243e7c86831
|
||||
github.com/prometheus/client_model fa8ad6fec33561be4280a8f0514318c79d7f6cb6
|
||||
github.com/prometheus/common e8eabff8812b05acf522b45fdcd725a785188e37
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
github.com/Microsoft/go-winio 9f57cbbcbcb41dea496528872a4f0e37a4f7ae98
|
||||
github.com/Shopify/sarama 8aadb476e66ca998f2f6bb3c993e9a2daa3666b9
|
||||
github.com/Sirupsen/logrus 219c8cb75c258c552e999735be6df753ffc7afdc
|
||||
github.com/StackExchange/wmi f3e2bae1e0cb5aef83e319133eabfee30013a4a5
|
||||
@@ -9,24 +10,24 @@ github.com/couchbase/go-couchbase cb664315a324d87d19c879d9cc67fda6be8c2ac1
|
||||
github.com/couchbase/gomemcached a5ea6356f648fec6ab89add00edd09151455b4b2
|
||||
github.com/couchbase/goutils 5823a0cbaaa9008406021dc5daf80125ea30bba6
|
||||
github.com/dancannon/gorethink e7cac92ea2bc52638791a021f212145acfedb1fc
|
||||
github.com/davecgh/go-spew fc32781af5e85e548d3f1abaf0fa3dbe8a72495c
|
||||
github.com/davecgh/go-spew 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
|
||||
github.com/docker/engine-api 8924d6900370b4c7e7984be5adc61f50a80d7537
|
||||
github.com/docker/go-connections f549a9393d05688dff0992ef3efd8bbe6c628aeb
|
||||
github.com/docker/go-units 5d2041e26a699eaca682e2ea41c8f891e1060444
|
||||
github.com/eapache/go-resiliency b86b1ec0dd4209a588dc1285cdd471e73525c0b3
|
||||
github.com/eapache/queue ded5959c0d4e360646dc9e9908cff48666781367
|
||||
github.com/eclipse/paho.mqtt.golang 4ab3e867810d1ec5f35157c59e965054dbf43a0d
|
||||
github.com/fsouza/go-dockerclient a49c8269a6899cae30da1f8a4b82e0ce945f9967
|
||||
github.com/go-ini/ini 776aa739ce9373377cd16f526cdf06cb4c89b40f
|
||||
github.com/eclipse/paho.mqtt.golang 0f7a459f04f13a41b7ed752d47944528d4bf9a86
|
||||
github.com/go-ole/go-ole 50055884d646dd9434f16bbb5c9801749b9bafe4
|
||||
github.com/go-sql-driver/mysql 1fca743146605a172a266e1654e01e5cd5669bee
|
||||
github.com/golang/protobuf 552c7b9542c194800fd493123b3798ef0a832032
|
||||
github.com/golang/snappy 5979233c5d6225d4a8e438cdd0b411888449ddab
|
||||
github.com/golang/snappy 427fb6fc07997f43afa32f35e850833760e489a7
|
||||
github.com/gonuts/go-shellquote e842a11b24c6abfb3dd27af69a17f482e4b483c2
|
||||
github.com/gorilla/context 1ea25387ff6f684839d82767c1733ff4d4d15d0a
|
||||
github.com/gorilla/mux c9e326e2bdec29039a3761c07bece13133863e1e
|
||||
github.com/hailocab/go-hostpool e80d13ce29ede4452c43dea11e79b9bc8a15b478
|
||||
github.com/influxdata/config b79f6829346b8d6e78ba73544b1e1038f1f1c9da
|
||||
github.com/influxdata/influxdb c190778997f4154294e6160c41b90140641ac915
|
||||
github.com/influxdata/influxdb e3fef5593c21644f2b43af55d6e17e70910b0e48
|
||||
github.com/influxdata/toml af4df43894b16e3fd2b788d01bd27ad0776ef2d0
|
||||
github.com/jmespath/go-jmespath 0b12d6b521d83fc7f755e7cfc1b1fbdd35a01a74
|
||||
github.com/klauspost/crc32 19b0b332c9e4516a6370a0456e6182c3b5036720
|
||||
github.com/lib/pq e182dc4027e2ded4b19396d638610f2653295f36
|
||||
github.com/lxn/win 9a7734ea4db26bc593d52f6a8a957afdad39c5c1
|
||||
@@ -37,7 +38,6 @@ github.com/naoina/go-stringutil 6b638e95a32d0c1131db0e7fe83775cbea4a0d0b
|
||||
github.com/nats-io/nats b13fc9d12b0b123ebc374e6b808c6228ae4234a3
|
||||
github.com/nats-io/nuid 4f84f5f3b2786224e336af2e13dba0a0a80b76fa
|
||||
github.com/nsqio/go-nsq 0b80d6f05e15ca1930e0c5e1d540ed627e299980
|
||||
github.com/pmezard/go-difflib 792786c7400a136282c1664665ae0a8db921c6c2
|
||||
github.com/prometheus/client_golang 18acf9993a863f4c4b40612e19cdd243e7c86831
|
||||
github.com/prometheus/client_model fa8ad6fec33561be4280a8f0514318c79d7f6cb6
|
||||
github.com/prometheus/common e8eabff8812b05acf522b45fdcd725a785188e37
|
||||
@@ -47,9 +47,8 @@ github.com/shirou/gopsutil 1f32ce1bb380845be7f5d174ac641a2c592c0c42
|
||||
github.com/shirou/w32 ada3ba68f000aa1b58580e45c9d308fe0b7fc5c5
|
||||
github.com/soniah/gosnmp b1b4f885b12c5dcbd021c5cee1c904110de6db7d
|
||||
github.com/streadway/amqp b4f3ceab0337f013208d31348b578d83c0064744
|
||||
github.com/stretchr/objx 1a9d0bb9f541897e62256577b352fdbc1fb4fd94
|
||||
github.com/stretchr/testify 1f4a1643a57e798696635ea4c126e9127adb7d3c
|
||||
github.com/wvanbergen/kafka 1a8639a45164fcc245d5c7b4bd3ccfbd1a0ffbf3
|
||||
github.com/wvanbergen/kafka 46f9a1cf3f670edec492029fadded9c2d9e18866
|
||||
github.com/wvanbergen/kazoo-go 0f768712ae6f76454f987c3356177e138df258f8
|
||||
github.com/zensqlmonitor/go-mssqldb ffe5510c6fa5e15e6d983210ab501c815b56b363
|
||||
golang.org/x/net 6acef71eb69611914f7a30939ea9f6e194c78172
|
||||
|
||||
31
README.md
31
README.md
@@ -20,12 +20,12 @@ new plugins.
|
||||
### Linux deb and rpm Packages:
|
||||
|
||||
Latest:
|
||||
* http://get.influxdb.org/telegraf/telegraf_0.12.0-1_amd64.deb
|
||||
* http://get.influxdb.org/telegraf/telegraf-0.12.0-1.x86_64.rpm
|
||||
* http://get.influxdb.org/telegraf/telegraf_0.12.1-1_amd64.deb
|
||||
* http://get.influxdb.org/telegraf/telegraf-0.12.1-1.x86_64.rpm
|
||||
|
||||
Latest (arm):
|
||||
* http://get.influxdb.org/telegraf/telegraf_0.12.0-1_armhf.deb
|
||||
* http://get.influxdb.org/telegraf/telegraf-0.12.0-1.armhf.rpm
|
||||
* http://get.influxdb.org/telegraf/telegraf_0.12.1-1_armhf.deb
|
||||
* http://get.influxdb.org/telegraf/telegraf-0.12.1-1.armhf.rpm
|
||||
|
||||
##### Package Instructions:
|
||||
|
||||
@@ -46,28 +46,28 @@ to use this repo to install & update telegraf.
|
||||
### Linux tarballs:
|
||||
|
||||
Latest:
|
||||
* http://get.influxdb.org/telegraf/telegraf-0.12.0-1_linux_amd64.tar.gz
|
||||
* http://get.influxdb.org/telegraf/telegraf-0.12.0-1_linux_i386.tar.gz
|
||||
* http://get.influxdb.org/telegraf/telegraf-0.12.0-1_linux_armhf.tar.gz
|
||||
* http://get.influxdb.org/telegraf/telegraf-0.12.1-1_linux_amd64.tar.gz
|
||||
* http://get.influxdb.org/telegraf/telegraf-0.12.1-1_linux_i386.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.0-1_linux_amd64.tar.gz
|
||||
sudo tar -C / -zxvf ./telegraf-0.12.1-1_linux_amd64.tar.gz
|
||||
```
|
||||
|
||||
To extract only the binary, run:
|
||||
|
||||
```
|
||||
tar -zxvf telegraf-0.12.0-1_linux_amd64.tar.gz --strip-components=3 ./usr/bin/telegraf
|
||||
tar -zxvf telegraf-0.12.1-1_linux_amd64.tar.gz --strip-components=3 ./usr/bin/telegraf
|
||||
```
|
||||
|
||||
### FreeBSD tarball:
|
||||
|
||||
Latest:
|
||||
* http://get.influxdb.org/telegraf/telegraf-0.12.0-1_freebsd_amd64.tar.gz
|
||||
* http://get.influxdb.org/telegraf/telegraf-0.12.1-1_freebsd_amd64.tar.gz
|
||||
|
||||
##### tarball Instructions:
|
||||
|
||||
@@ -87,8 +87,8 @@ brew install telegraf
|
||||
### Windows Binaries (EXPERIMENTAL)
|
||||
|
||||
Latest:
|
||||
* http://get.influxdb.org/telegraf/telegraf-0.12.0-1_windows_amd64.zip
|
||||
* http://get.influxdb.org/telegraf/telegraf-0.12.0-1_windows_i386.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:
|
||||
|
||||
@@ -156,9 +156,11 @@ more information on each, please look at the directory of the same name in
|
||||
|
||||
Currently implemented sources:
|
||||
|
||||
* [aws cloudwatch](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/cloudwatch)
|
||||
* [aerospike](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/aerospike)
|
||||
* [apache](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/apache)
|
||||
* [bcache](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/bcache)
|
||||
* [cassandra](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/cassandra)
|
||||
* [couchbase](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/couchbase)
|
||||
* [couchdb](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/couchdb)
|
||||
* [disque](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/disque)
|
||||
@@ -168,7 +170,8 @@ Currently implemented sources:
|
||||
* [elasticsearch](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/elasticsearch)
|
||||
* [exec](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/exec ) (generic executable plugin, support JSON, influx, graphite and nagios)
|
||||
* [haproxy](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/haproxy)
|
||||
* [httpjson ](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/httpjson ) (generic JSON-emitting http service plugin)
|
||||
* [http_response](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/http_response)
|
||||
* [httpjson](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/httpjson) (generic JSON-emitting http service plugin)
|
||||
* [influxdb](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/influxdb)
|
||||
* [ipmi_sensor](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/ipmi_sensor)
|
||||
* [jolokia](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/jolokia)
|
||||
@@ -204,6 +207,7 @@ Currently implemented sources:
|
||||
* [zfs](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/zfs)
|
||||
* [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)
|
||||
* [sysstat](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/sysstat)
|
||||
* [system](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/system)
|
||||
* cpu
|
||||
* mem
|
||||
@@ -236,6 +240,7 @@ want to add support for another service or third-party API.
|
||||
* [aws kinesis](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/kinesis)
|
||||
* [aws cloudwatch](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/cloudwatch)
|
||||
* [datadog](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/datadog)
|
||||
* [file](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/file)
|
||||
* [graphite](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/graphite)
|
||||
* [kafka](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/kafka)
|
||||
* [librato](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/librato)
|
||||
|
||||
@@ -4,9 +4,9 @@ machine:
|
||||
post:
|
||||
- sudo service zookeeper stop
|
||||
- go version
|
||||
- go version | grep 1.6 || sudo rm -rf /usr/local/go
|
||||
- wget https://storage.googleapis.com/golang/go1.6.linux-amd64.tar.gz
|
||||
- sudo tar -C /usr/local -xzf go1.6.linux-amd64.tar.gz
|
||||
- go version | grep 1.6.1 || sudo rm -rf /usr/local/go
|
||||
- wget https://storage.googleapis.com/golang/go1.6.1.linux-amd64.tar.gz
|
||||
- sudo tar -C /usr/local -xzf go1.6.1.linux-amd64.tar.gz
|
||||
- go version
|
||||
|
||||
dependencies:
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
Telegraf is able to parse the following input data formats into metrics:
|
||||
|
||||
1. InfluxDB Line Protocol
|
||||
1. JSON
|
||||
1. Graphite
|
||||
1. Value, ie 45 or "booyah"
|
||||
1. [InfluxDB Line Protocol](https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md#influx)
|
||||
1. [JSON](https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md#json)
|
||||
1. [Graphite](https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md#graphite)
|
||||
1. [Value](https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md#value), ie: 45 or "booyah"
|
||||
1. [Nagios](https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md#nagios) (exec input only)
|
||||
|
||||
Telegraf metrics, like InfluxDB
|
||||
[points](https://docs.influxdata.com/influxdb/v0.10/write_protocols/line/),
|
||||
@@ -38,7 +39,7 @@ example, in the exec plugin:
|
||||
## measurement name suffix (for separating different commands)
|
||||
name_suffix = "_mycollector"
|
||||
|
||||
## Data format to consume. This can be "json", "influx" or "graphite"
|
||||
## Data format to consume.
|
||||
## Each data format has it's own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md
|
||||
@@ -50,7 +51,7 @@ example, in the exec plugin:
|
||||
Each data_format has an additional set of configuration options available, which
|
||||
I'll go over below.
|
||||
|
||||
## Influx:
|
||||
# Influx:
|
||||
|
||||
There are no additional configuration options for InfluxDB line-protocol. The
|
||||
metrics are parsed directly into Telegraf metrics.
|
||||
@@ -65,14 +66,14 @@ metrics are parsed directly into Telegraf metrics.
|
||||
## measurement name suffix (for separating different commands)
|
||||
name_suffix = "_mycollector"
|
||||
|
||||
## Data format to consume. This can be "json", "influx" or "graphite"
|
||||
## Data format to consume.
|
||||
## Each data format has it's own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md
|
||||
data_format = "influx"
|
||||
```
|
||||
|
||||
## JSON:
|
||||
# JSON:
|
||||
|
||||
The JSON data format flattens JSON into metric _fields_. For example, this JSON:
|
||||
|
||||
@@ -110,7 +111,7 @@ For example, if you had this configuration:
|
||||
## measurement name suffix (for separating different commands)
|
||||
name_suffix = "_mycollector"
|
||||
|
||||
## Data format to consume. This can be "json", "influx" or "graphite"
|
||||
## Data format to consume.
|
||||
## Each data format has it's own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md
|
||||
@@ -141,21 +142,20 @@ Your Telegraf metrics would get tagged with "my_tag_1"
|
||||
exec_mycollector,my_tag_1=foo a=5,b_c=6
|
||||
```
|
||||
|
||||
## Value:
|
||||
# Value:
|
||||
|
||||
The "value" data format translates single values into Telegraf metrics. This
|
||||
is done by assigning a measurement name (which can be overridden using the
|
||||
`name_override` config option), and setting a single field ("value") as the
|
||||
parsed metric.
|
||||
is done by assigning a measurement name and setting a single field ("value")
|
||||
as the parsed metric.
|
||||
|
||||
#### Value Configuration:
|
||||
|
||||
You can tell Telegraf what type of metric to collect by using the `data_type`
|
||||
configuration option.
|
||||
You **must** tell Telegraf what type of metric to collect by using the
|
||||
`data_type` configuration option.
|
||||
|
||||
It is also recommended that you set `name_override` to a measurement name that
|
||||
makes sense for your metric, otherwise it will just be set to the name of the
|
||||
plugin.
|
||||
**Note:** It is also recommended that you set `name_override` to a measurement
|
||||
name that makes sense for your metric, otherwise it will just be set to the
|
||||
name of the plugin.
|
||||
|
||||
```toml
|
||||
[[inputs.exec]]
|
||||
@@ -165,15 +165,15 @@ plugin.
|
||||
## override the default metric name of "exec"
|
||||
name_override = "entropy_available"
|
||||
|
||||
## Data format to consume. This can be "json", "value", influx" or "graphite"
|
||||
## Data format to consume.
|
||||
## Each data format has it's own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md
|
||||
data_format = "value"
|
||||
data_type = "integer"
|
||||
data_type = "integer" # required
|
||||
```
|
||||
|
||||
## Graphite:
|
||||
# Graphite:
|
||||
|
||||
The Graphite data format translates graphite _dot_ buckets directly into
|
||||
telegraf measurement names, with a single value field, and without any tags. For
|
||||
@@ -301,7 +301,7 @@ There are many more options available,
|
||||
## measurement name suffix (for separating different commands)
|
||||
name_suffix = "_mycollector"
|
||||
|
||||
## Data format to consume. This can be "json", "influx" or "graphite" (line-protocol)
|
||||
## Data format to consume.
|
||||
## Each data format has it's own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md
|
||||
@@ -327,7 +327,7 @@ There are many more options available,
|
||||
]
|
||||
```
|
||||
|
||||
## Nagios:
|
||||
# Nagios:
|
||||
|
||||
There are no additional configuration options for Nagios line-protocol. The
|
||||
metrics are parsed directly into Telegraf metrics.
|
||||
@@ -344,7 +344,7 @@ Note: Nagios Input Data Formats is only supported in `exec` input plugin.
|
||||
## measurement name suffix (for separating different commands)
|
||||
name_suffix = "_mycollector"
|
||||
|
||||
## Data format to consume. This can be "json", "influx", "graphite" or "nagios"
|
||||
## Data format to consume.
|
||||
## Each data format has it's own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Telegraf Output Data Formats
|
||||
|
||||
Telegraf is able to serialize metrics into the following output data formats:
|
||||
|
||||
1. [InfluxDB Line Protocol](https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md#influx)
|
||||
1. [JSON](https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md#json)
|
||||
1. [Graphite](https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md#graphite)
|
||||
|
||||
Telegraf metrics, like InfluxDB
|
||||
[points](https://docs.influxdata.com/influxdb/v0.10/write_protocols/line/),
|
||||
are a combination of four basic parts:
|
||||
@@ -29,7 +35,7 @@ config option, for example, in the `file` output plugin:
|
||||
## Files to write to, "stdout" is a specially handled file.
|
||||
files = ["stdout"]
|
||||
|
||||
## Data format to output. This can be "influx" or "graphite"
|
||||
## Data format to output.
|
||||
## Each data format has it's own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md
|
||||
@@ -41,34 +47,46 @@ config option, for example, in the `file` output plugin:
|
||||
Each data_format has an additional set of configuration options available, which
|
||||
I'll go over below.
|
||||
|
||||
## Influx:
|
||||
# Influx:
|
||||
|
||||
There are no additional configuration options for InfluxDB line-protocol. The
|
||||
metrics are serialized directly into InfluxDB line-protocol.
|
||||
|
||||
#### Influx Configuration:
|
||||
### Influx Configuration:
|
||||
|
||||
```toml
|
||||
[[outputs.file]]
|
||||
## Files to write to, "stdout" is a specially handled file.
|
||||
files = ["stdout", "/tmp/metrics.out"]
|
||||
|
||||
## Data format to output. This can be "influx", "json" or "graphite"
|
||||
## Data format to output.
|
||||
## Each data format has it's own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md
|
||||
data_format = "influx"
|
||||
```
|
||||
|
||||
## Graphite:
|
||||
# Graphite:
|
||||
|
||||
The Graphite data format translates Telegraf metrics into _dot_ buckets.
|
||||
The format is:
|
||||
The Graphite data format translates Telegraf metrics into _dot_ buckets. A
|
||||
template can be specified for the output of Telegraf metrics into Graphite
|
||||
buckets. The default template is:
|
||||
|
||||
```
|
||||
[prefix].[host tag].[all tags (alphabetical)].[measurement name].[field name] value timestamp
|
||||
template = "host.tags.measurement.field"
|
||||
```
|
||||
|
||||
In the above template, we have four parts:
|
||||
|
||||
1. _host_ is a tag key. This can be any tag key that is in the Telegraf
|
||||
metric(s). If the key doesn't exist, it will be ignored. If it does exist, the
|
||||
tag value will be filled in.
|
||||
1. _tags_ is a special keyword that outputs all remaining tag values, separated
|
||||
by dots and in alphabetical order (by tag key). These will be filled after all
|
||||
tag keys are filled.
|
||||
1. _measurement_ is a special keyword that outputs the measurement name.
|
||||
1. _field_ is a special keyword that outputs the field name.
|
||||
|
||||
Which means the following influx metric -> graphite conversion would happen:
|
||||
|
||||
```
|
||||
@@ -78,27 +96,28 @@ tars.cpu-total.us-east-1.cpu.usage_user 0.89 1455320690
|
||||
tars.cpu-total.us-east-1.cpu.usage_idle 98.09 1455320690
|
||||
```
|
||||
|
||||
`prefix` is a configuration option when using the graphite output data format.
|
||||
|
||||
#### Graphite Configuration:
|
||||
### Graphite Configuration:
|
||||
|
||||
```toml
|
||||
[[outputs.file]]
|
||||
## Files to write to, "stdout" is a specially handled file.
|
||||
files = ["stdout", "/tmp/metrics.out"]
|
||||
|
||||
## Data format to output. This can be "influx", "json" or "graphite"
|
||||
## Data format to output.
|
||||
## Each data format has it's own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md
|
||||
data_format = "influx"
|
||||
data_format = "graphite"
|
||||
|
||||
# prefix each graphite bucket
|
||||
prefix = "telegraf"
|
||||
# graphite template
|
||||
template = "host.tags.measurement.field"
|
||||
```
|
||||
|
||||
## Json:
|
||||
# JSON:
|
||||
|
||||
The Json data format serialized Telegraf metrics in json format. The format is:
|
||||
The JSON data format serialized Telegraf metrics in json format. The format is:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -116,14 +135,14 @@ The Json data format serialized Telegraf metrics in json format. The format is:
|
||||
}
|
||||
```
|
||||
|
||||
#### Json Configuration:
|
||||
### JSON Configuration:
|
||||
|
||||
```toml
|
||||
[[outputs.file]]
|
||||
## Files to write to, "stdout" is a specially handled file.
|
||||
files = ["stdout", "/tmp/metrics.out"]
|
||||
|
||||
## Data format to output. This can be "influx", "json" or "graphite"
|
||||
## Data format to output.
|
||||
## Each data format has it's own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md
|
||||
|
||||
36
docs/WINDOWS_SERVICE.md
Normal file
36
docs/WINDOWS_SERVICE.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Running Telegraf as a Windows Service
|
||||
|
||||
If you have tried to install Go binaries as Windows Services with the **sc.exe**
|
||||
tool you may have seen that the service errors and stops running after a while.
|
||||
|
||||
**NSSM** (the Non-Sucking Service Manager) is a tool that helps you in a
|
||||
[number of scenarios](http://nssm.cc/scenarios) including running Go binaries
|
||||
that were not specifically designed to run only in Windows platforms.
|
||||
|
||||
## NSSM Installation via Chocolatey
|
||||
|
||||
You can install [Chocolatey](https://chocolatey.org/) and [NSSM](http://nssm.cc/)
|
||||
with these commands
|
||||
|
||||
```powershell
|
||||
iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))
|
||||
choco install -y nssm
|
||||
```
|
||||
|
||||
## Installing Telegraf as a Windows Service with NSSM
|
||||
|
||||
You can download the latest Telegraf Windows binaries (still Experimental at
|
||||
the moment) from [the Telegraf Github repo](https://github.com/influxdata/telegraf).
|
||||
|
||||
Then you can create a C:\telegraf folder, unzip the binary there and modify the
|
||||
**telegraf.conf** sample to allocate the metrics you want to send to **InfluxDB**.
|
||||
|
||||
Once you have NSSM installed in your system, the process is quite straightforward.
|
||||
You only need to type this command in your Windows shell
|
||||
|
||||
```powershell
|
||||
nssm install Telegraf c:\telegraf\telegraf.exe -config c:\telegraf\telegraf.config
|
||||
```
|
||||
|
||||
And now your service will be installed in Windows and you will be able to start and
|
||||
stop it gracefully
|
||||
@@ -178,6 +178,9 @@
|
||||
# servers = ["localhost:2003"]
|
||||
# ## Prefix metrics name
|
||||
# prefix = ""
|
||||
# ## Graphite output template
|
||||
# ## see https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md
|
||||
# template = "host.tags.measurement.field"
|
||||
# ## timeout in seconds for the write connection to graphite
|
||||
# timeout = 2
|
||||
|
||||
@@ -251,22 +254,20 @@
|
||||
# [[outputs.librato]]
|
||||
# ## Librator API Docs
|
||||
# ## http://dev.librato.com/v1/metrics-authentication
|
||||
#
|
||||
# ## Librato API user
|
||||
# api_user = "telegraf@influxdb.com" # required.
|
||||
#
|
||||
# ## Librato API token
|
||||
# api_token = "my-secret-token" # required.
|
||||
#
|
||||
# ### Debug
|
||||
# ## Debug
|
||||
# # debug = false
|
||||
#
|
||||
# ### Tag Field to populate source attribute (optional)
|
||||
# ### This is typically the _hostname_ from which the metric was obtained.
|
||||
# ## Tag Field to populate source attribute (optional)
|
||||
# ## This is typically the _hostname_ from which the metric was obtained.
|
||||
# source_tag = "host"
|
||||
#
|
||||
# ## Connection timeout.
|
||||
# # timeout = "5s"
|
||||
# ## Output Name Template (same as graphite buckets)
|
||||
# ## see https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md#graphite
|
||||
# template = "host.tags.measurement.field"
|
||||
|
||||
|
||||
# # Configuration for MQTT server to send metrics to
|
||||
@@ -429,6 +430,54 @@
|
||||
# bcacheDevs = ["bcache0"]
|
||||
|
||||
|
||||
# # Read Cassandra metrics through Jolokia
|
||||
# [[inputs.cassandra]]
|
||||
# # This is the context root used to compose the jolokia url
|
||||
# context = "/jolokia/read"
|
||||
# ## List of cassandra servers exposing jolokia read service
|
||||
# servers = ["myuser:mypassword@10.10.10.1:8778","10.10.10.2:8778",":8778"]
|
||||
# ## List of metrics collected on above servers
|
||||
# ## Each metric consists of a jmx path.
|
||||
# ## This will collect all heap memory usage metrics from the jvm and
|
||||
# ## ReadLatency metrics for all keyspaces and tables.
|
||||
# ## "type=Table" in the query works with Cassandra3.0. Older versions might
|
||||
# ## need to use "type=ColumnFamily"
|
||||
# metrics = [
|
||||
# "/java.lang:type=Memory/HeapMemoryUsage",
|
||||
# "/org.apache.cassandra.metrics:type=Table,keyspace=*,scope=*,name=ReadLatency"
|
||||
# ]
|
||||
|
||||
|
||||
# # Pull Metric Statistics from Amazon CloudWatch
|
||||
# [[inputs.cloudwatch]]
|
||||
# ## Amazon Region
|
||||
# region = 'us-east-1'
|
||||
#
|
||||
# ## Requested CloudWatch aggregation Period (required - must be a multiple of 60s)
|
||||
# period = '1m'
|
||||
#
|
||||
# ## Collection Delay (required - must account for metrics availability via CloudWatch API)
|
||||
# delay = '1m'
|
||||
#
|
||||
# ## Recomended: use metric 'interval' that is a multiple of 'period' to avoid
|
||||
# ## gaps or overlap in pulled data
|
||||
# interval = '1m'
|
||||
#
|
||||
# ## Metric Statistic Namespace (required)
|
||||
# namespace = 'AWS/ELB'
|
||||
#
|
||||
# ## Metrics to Pull (optional)
|
||||
# ## Defaults to all Metrics in Namespace if nothing is provided
|
||||
# ## Refreshes Namespace available metrics every 1h
|
||||
# #[[inputs.cloudwatch.metrics]]
|
||||
# # names = ['Latency', 'RequestCount']
|
||||
# #
|
||||
# # ## Dimension filters for Metric (optional)
|
||||
# # [[inputs.cloudwatch.metrics.dimensions]]
|
||||
# # name = 'LoadBalancerName'
|
||||
# # value = 'p-example'
|
||||
|
||||
|
||||
# # Read metrics from one or many couchbase clusters
|
||||
# [[inputs.couchbase]]
|
||||
# ## specify servers via a url matching:
|
||||
@@ -496,8 +545,11 @@
|
||||
# ##
|
||||
# ## If no servers are specified, then localhost is used as the host.
|
||||
# servers = ["localhost:24242"]
|
||||
# ## Only collect metrics for these domains, collect all if empty
|
||||
# domains = []
|
||||
# ## Type is one of "user", "domain", "ip", or "global"
|
||||
# type = "global"
|
||||
# ## Wildcard matches like "*.com". An empty string "" is same as "*"
|
||||
# ## If type = "ip" filters should be <IP/network>
|
||||
# filters = [""]
|
||||
|
||||
|
||||
# # Read stats from one or more Elasticsearch servers or clusters
|
||||
@@ -539,6 +591,25 @@
|
||||
# ## servers = ["socket://run/haproxy/admin.sock"]
|
||||
|
||||
|
||||
# # HTTP/HTTPS request given an address a method and a timeout
|
||||
# [[inputs.http_response]]
|
||||
# ## Server address (default http://localhost)
|
||||
# address = "http://github.com"
|
||||
# ## Set response_timeout (default 5 seconds)
|
||||
# response_timeout = 5
|
||||
# ## HTTP Request Method
|
||||
# method = "GET"
|
||||
# ## Whether to follow redirects from the server (defaults to false)
|
||||
# follow_redirects = true
|
||||
# ## HTTP Request Headers (all values must be strings)
|
||||
# # [inputs.http_response.headers]
|
||||
# # Host = "github.com"
|
||||
# ## Optional HTTP Request Body
|
||||
# # body = '''
|
||||
# # {'fake':'data'}
|
||||
# # '''
|
||||
|
||||
|
||||
# # Read flattened metrics from one or more JSON HTTP endpoints
|
||||
# [[inputs.httpjson]]
|
||||
# ## NOTE This plugin only reads numerical measurements, strings and booleans
|
||||
@@ -622,6 +693,16 @@
|
||||
# [[inputs.jolokia.metrics]]
|
||||
# name = "heap_memory_usage"
|
||||
# jmx = "/java.lang:type=Memory/HeapMemoryUsage"
|
||||
#
|
||||
# ## This collect thread counts metrics.
|
||||
# [[inputs.jolokia.metrics]]
|
||||
# name = "thread_count"
|
||||
# jmx = "/java.lang:type=Threading/TotalStartedThreadCount,ThreadCount,DaemonThreadCount,PeakThreadCount"
|
||||
#
|
||||
# ## This collect number of class loaded/unloaded counts metrics.
|
||||
# [[inputs.jolokia.metrics]]
|
||||
# name = "class_count"
|
||||
# jmx = "/java.lang:type=ClassLoading/LoadedClassCount,UnloadedClassCount,TotalLoadedClassCount"
|
||||
|
||||
|
||||
# # Read metrics from a LeoFS Server via SNMP
|
||||
@@ -1245,10 +1326,6 @@
|
||||
# ## calculation of percentiles. Raising this limit increases the accuracy
|
||||
# ## of percentiles but also increases the memory usage and cpu time.
|
||||
# percentile_limit = 1000
|
||||
#
|
||||
# ## UDP packet size for the server to listen for. This will depend on the size
|
||||
# ## of the packets that the client is sending, which is usually 1500 bytes.
|
||||
# udp_packet_size = 1500
|
||||
|
||||
|
||||
# # Generic TCP listener
|
||||
@@ -1279,11 +1356,6 @@
|
||||
# ## UDP listener will start dropping packets.
|
||||
# allowed_pending_messages = 10000
|
||||
#
|
||||
# ## UDP packet size for the server to listen for. This will depend
|
||||
# ## on the size of the packets that the client is sending, which is
|
||||
# ## usually 1500 bytes, but can be as large as 65,535 bytes.
|
||||
# udp_packet_size = 1500
|
||||
#
|
||||
# ## Data format to consume.
|
||||
# ## Each data format has it's own unique set of configuration options, read
|
||||
# ## more about them here:
|
||||
|
||||
@@ -850,8 +850,17 @@ func buildSerializer(name string, tbl *ast.Table) (serializers.Serializer, error
|
||||
}
|
||||
}
|
||||
|
||||
if node, ok := tbl.Fields["template"]; ok {
|
||||
if kv, ok := node.(*ast.KeyValue); ok {
|
||||
if str, ok := kv.Value.(*ast.String); ok {
|
||||
c.Template = str.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete(tbl.Fields, "data_format")
|
||||
delete(tbl.Fields, "prefix")
|
||||
delete(tbl.Fields, "template")
|
||||
return serializers.NewSerializer(c)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/aerospike"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/apache"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/bcache"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/cassandra"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/cloudwatch"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/couchbase"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/couchdb"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/disque"
|
||||
@@ -14,6 +16,7 @@ import (
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/exec"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/github_webhooks"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/haproxy"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/http_response"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/httpjson"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/influxdb"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/ipmi_sensor"
|
||||
@@ -50,6 +53,7 @@ import (
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/snmp"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/sqlserver"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/statsd"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/sysstat"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/system"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/tcp_listener"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/trig"
|
||||
|
||||
125
plugins/inputs/cassandra/README.md
Normal file
125
plugins/inputs/cassandra/README.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# Telegraf plugin: Cassandra
|
||||
|
||||
#### Plugin arguments:
|
||||
- **context** string: Context root used for jolokia url
|
||||
- **servers** []string: List of servers with the format "<user:passwd@><host>:port"
|
||||
- **metrics** []string: List of Jmx paths that identify mbeans attributes
|
||||
|
||||
#### Description
|
||||
|
||||
The Cassandra plugin collects Cassandra/JVM metrics exposed as MBean's attributes through jolokia REST endpoint. All metrics are collected for each server configured.
|
||||
|
||||
See: https://jolokia.org/ and [Cassandra Documentation](http://docs.datastax.com/en/cassandra/3.x/cassandra/operations/monitoringCassandraTOC.html)
|
||||
|
||||
# Measurements:
|
||||
Cassandra plugin produces one or more measurements for each metric configured, adding Server's name as `host` tag. More than one measurement is generated when querying table metrics with a wildcard for the keyspace or table name.
|
||||
|
||||
Given a configuration like:
|
||||
|
||||
```toml
|
||||
[[inputs.cassandra]]
|
||||
context = "/jolokia/read"
|
||||
servers = [":8778"]
|
||||
metrics = ["/java.lang:type=Memory/HeapMemoryUsage"]
|
||||
```
|
||||
|
||||
The collected metrics will be:
|
||||
|
||||
```
|
||||
javaMemory,host=myHost,mname=HeapMemoryUsage HeapMemoryUsage_committed=1040187392,HeapMemoryUsage_init=1050673152,HeapMemoryUsage_max=1040187392,HeapMemoryUsage_used=368155000 1459551767230567084
|
||||
```
|
||||
|
||||
# Useful Metrics:
|
||||
|
||||
Here is a list of metrics that might be useful to monitor your cassandra cluster. This was put together from multiple sources on the web.
|
||||
|
||||
- [How to monitor Cassandra performance metrics](https://www.datadoghq.com/blog/how-to-monitor-cassandra-performance-metrics)
|
||||
- [Cassandra Documentation](http://docs.datastax.com/en/cassandra/3.x/cassandra/operations/monitoringCassandraTOC.html)
|
||||
|
||||
####measurement = javaGarbageCollector
|
||||
|
||||
- /java.lang:type=GarbageCollector,name=ConcurrentMarkSweep/CollectionTime
|
||||
- /java.lang:type=GarbageCollector,name=ConcurrentMarkSweep/CollectionCount
|
||||
- /java.lang:type=GarbageCollector,name=ParNew/CollectionTime
|
||||
- /java.lang:type=GarbageCollector,name=ParNew/CollectionCount
|
||||
|
||||
####measurement = javaMemory
|
||||
|
||||
- /java.lang:type=Memory/HeapMemoryUsage
|
||||
- /java.lang:type=Memory/NonHeapMemoryUsage
|
||||
|
||||
####measurement = cassandraCache
|
||||
|
||||
- /org.apache.cassandra.metrics:type=Cache,scope=KeyCache,name=Hit
|
||||
- /org.apache.cassandra.metrics:type=Cache,scope=KeyCache,name=Requests
|
||||
- /org.apache.cassandra.metrics:type=Cache,scope=KeyCache,name=Entries
|
||||
- /org.apache.cassandra.metrics:type=Cache,scope=KeyCache,name=Size
|
||||
- /org.apache.cassandra.metrics:type=Cache,scope=KeyCache,name=Capacity
|
||||
- /org.apache.cassandra.metrics:type=Cache,scope=RowCache,name=Hit
|
||||
- /org.apache.cassandra.metrics:type=Cache,scope=RowCache,name=Requests
|
||||
- /org.apache.cassandra.metrics:type=Cache,scope=RowCache,name=Entries
|
||||
- /org.apache.cassandra.metrics:type=Cache,scope=RowCache,name=Size
|
||||
- /org.apache.cassandra.metrics:type=Cache,scope=RowCache,name=Capacity
|
||||
|
||||
####measurement = cassandraClient
|
||||
|
||||
- /org.apache.cassandra.metrics:type=Client,name=connectedNativeClients
|
||||
|
||||
####measurement = cassandraClientRequest
|
||||
|
||||
- /org.apache.cassandra.metrics:type=ClientRequest,scope=Read,name=TotalLatency
|
||||
- /org.apache.cassandra.metrics:type=ClientRequest,scope=Write,name=TotalLatency
|
||||
- /org.apache.cassandra.metrics:type=ClientRequest,scope=Read,name=Latency
|
||||
- /org.apache.cassandra.metrics:type=ClientRequest,scope=Write,name=Latency
|
||||
- /org.apache.cassandra.metrics:type=ClientRequest,scope=Read,name=Timeouts
|
||||
- /org.apache.cassandra.metrics:type=ClientRequest,scope=Write,name=Timeouts
|
||||
- /org.apache.cassandra.metrics:type=ClientRequest,scope=Read,name=Unavailables
|
||||
- /org.apache.cassandra.metrics:type=ClientRequest,scope=Write,name=Unavailables
|
||||
- /org.apache.cassandra.metrics:type=ClientRequest,scope=Read,name=Failures
|
||||
- /org.apache.cassandra.metrics:type=ClientRequest,scope=Write,name=Failures
|
||||
|
||||
####measurement = cassandraCommitLog
|
||||
|
||||
- /org.apache.cassandra.metrics:type=CommitLog,name=PendingTasks
|
||||
- /org.apache.cassandra.metrics:type=CommitLog,name=TotalCommitLogSize
|
||||
|
||||
####measurement = cassandraCompaction
|
||||
|
||||
- /org.apache.cassandra.metrics:type=Compaction,name=CompletedTask
|
||||
- /org.apache.cassandra.metrics:type=Compaction,name=PendingTasks
|
||||
- /org.apache.cassandra.metrics:type=Compaction,name=TotalCompactionsCompleted
|
||||
- /org.apache.cassandra.metrics:type=Compaction,name=BytesCompacted
|
||||
|
||||
####measurement = cassandraStorage
|
||||
|
||||
- /org.apache.cassandra.metrics:type=Storage,name=Load
|
||||
- /org.apache.cassandra.metrics:type=Storage,name=Exceptions
|
||||
|
||||
####measurement = cassandraTable
|
||||
Using wildcards for "keyspace" and "scope" can create a lot of series as metrics will be reported for every table and keyspace including internal system tables. Specify a keyspace name and/or a table name to limit them.
|
||||
|
||||
- /org.apache.cassandra.metrics:type=Table,keyspace=\*,scope=\*,name=LiveDiskSpaceUsed
|
||||
- /org.apache.cassandra.metrics:type=Table,keyspace=\*,scope=\*,name=TotalDiskSpaceUsed
|
||||
- /org.apache.cassandra.metrics:type=Table,keyspace=\*,scope=\*,name=ReadLatency
|
||||
- /org.apache.cassandra.metrics:type=Table,keyspace=\*,scope=\*,name=CoordinatorReadLatency
|
||||
- /org.apache.cassandra.metrics:type=Table,keyspace=\*,scope=\*,name=WriteLatency
|
||||
- /org.apache.cassandra.metrics:type=Table,keyspace=\*,scope=\*,name=ReadTotalLatency
|
||||
- /org.apache.cassandra.metrics:type=Table,keyspace=\*,scope=\*,name=WriteTotalLatency
|
||||
|
||||
|
||||
####measurement = cassandraThreadPools
|
||||
|
||||
- /org.apache.cassandra.metrics:type=ThreadPools,path=internal,scope=CompactionExecutor,name=ActiveTasks
|
||||
- /org.apache.cassandra.metrics:type=ThreadPools,path=internal,scope=AntiEntropyStage,name=ActiveTasks
|
||||
- /org.apache.cassandra.metrics:type=ThreadPools,path=request,scope=CounterMutationStage,name=PendingTasks
|
||||
- /org.apache.cassandra.metrics:type=ThreadPools,path=request,scope=CounterMutationStage,name=CurrentlyBlockedTasks
|
||||
- /org.apache.cassandra.metrics:type=ThreadPools,path=request,scope=MutationStage,name=PendingTasks
|
||||
- /org.apache.cassandra.metrics:type=ThreadPools,path=request,scope=MutationStage,name=CurrentlyBlockedTasks
|
||||
- /org.apache.cassandra.metrics:type=ThreadPools,path=request,scope=ReadRepairStage,name=PendingTasks
|
||||
- /org.apache.cassandra.metrics:type=ThreadPools,path=request,scope=ReadRepairStage,name=CurrentlyBlockedTasks
|
||||
- /org.apache.cassandra.metrics:type=ThreadPools,path=request,scope=ReadStage,name=PendingTasks
|
||||
- /org.apache.cassandra.metrics:type=ThreadPools,path=request,scope=ReadStage,name=CurrentlyBlockedTasks
|
||||
- /org.apache.cassandra.metrics:type=ThreadPools,path=request,scope=RequestResponseStage,name=PendingTasks
|
||||
- /org.apache.cassandra.metrics:type=ThreadPools,path=request,scope=RequestResponseStage,name=CurrentlyBlockedTasks
|
||||
|
||||
|
||||
318
plugins/inputs/cassandra/cassandra.go
Normal file
318
plugins/inputs/cassandra/cassandra.go
Normal file
@@ -0,0 +1,318 @@
|
||||
package cassandra
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
//"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*type Server struct {
|
||||
Host string
|
||||
Username string
|
||||
Password string
|
||||
Port string
|
||||
}*/
|
||||
|
||||
type JolokiaClient interface {
|
||||
MakeRequest(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
type JolokiaClientImpl struct {
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func (c JolokiaClientImpl) MakeRequest(req *http.Request) (*http.Response, error) {
|
||||
return c.client.Do(req)
|
||||
}
|
||||
|
||||
type Cassandra struct {
|
||||
jClient JolokiaClient
|
||||
Context string
|
||||
Servers []string
|
||||
Metrics []string
|
||||
}
|
||||
|
||||
type javaMetric struct {
|
||||
host string
|
||||
metric string
|
||||
acc telegraf.Accumulator
|
||||
}
|
||||
|
||||
type cassandraMetric struct {
|
||||
host string
|
||||
metric string
|
||||
acc telegraf.Accumulator
|
||||
}
|
||||
|
||||
type jmxMetric interface {
|
||||
addTagsFields(out map[string]interface{})
|
||||
}
|
||||
|
||||
func addServerTags(host string, tags map[string]string) {
|
||||
if host != "" && host != "localhost" && host != "127.0.0.1" {
|
||||
tags["host"] = host
|
||||
}
|
||||
}
|
||||
|
||||
func newJavaMetric(host string, metric string,
|
||||
acc telegraf.Accumulator) *javaMetric {
|
||||
return &javaMetric{host: host, metric: metric, acc: acc}
|
||||
}
|
||||
|
||||
func newCassandraMetric(host string, metric string,
|
||||
acc telegraf.Accumulator) *cassandraMetric {
|
||||
return &cassandraMetric{host: host, metric: metric, acc: acc}
|
||||
}
|
||||
|
||||
func addValuesAsFields(values map[string]interface{}, fields map[string]interface{},
|
||||
mname string) {
|
||||
for k, v := range values {
|
||||
if v != nil {
|
||||
fields[mname+"_"+k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseJmxMetricRequest(mbean string) map[string]string {
|
||||
tokens := make(map[string]string)
|
||||
classAndPairs := strings.Split(mbean, ":")
|
||||
if classAndPairs[0] == "org.apache.cassandra.metrics" {
|
||||
tokens["class"] = "cassandra"
|
||||
} else if classAndPairs[0] == "java.lang" {
|
||||
tokens["class"] = "java"
|
||||
} else {
|
||||
return tokens
|
||||
}
|
||||
pairs := strings.Split(classAndPairs[1], ",")
|
||||
for _, pair := range pairs {
|
||||
p := strings.Split(pair, "=")
|
||||
tokens[p[0]] = p[1]
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
|
||||
func addTokensToTags(tokens map[string]string, tags map[string]string) {
|
||||
for k, v := range tokens {
|
||||
if k == "name" {
|
||||
tags["mname"] = v // name seems to a reserved word in influxdb
|
||||
} else if k == "class" || k == "type" {
|
||||
continue // class and type are used in the metric name
|
||||
} else {
|
||||
tags[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (j javaMetric) addTagsFields(out map[string]interface{}) {
|
||||
tags := make(map[string]string)
|
||||
fields := make(map[string]interface{})
|
||||
|
||||
a := out["request"].(map[string]interface{})
|
||||
attribute := a["attribute"].(string)
|
||||
mbean := a["mbean"].(string)
|
||||
|
||||
tokens := parseJmxMetricRequest(mbean)
|
||||
addTokensToTags(tokens, tags)
|
||||
addServerTags(j.host, tags)
|
||||
|
||||
if _, ok := tags["mname"]; !ok {
|
||||
//Queries for a single value will not return a "name" tag in the response.
|
||||
tags["mname"] = attribute
|
||||
}
|
||||
|
||||
if values, ok := out["value"]; ok {
|
||||
switch t := values.(type) {
|
||||
case map[string]interface{}:
|
||||
addValuesAsFields(values.(map[string]interface{}), fields, attribute)
|
||||
case interface{}:
|
||||
fields[attribute] = t
|
||||
}
|
||||
j.acc.AddFields(tokens["class"]+tokens["type"], fields, tags)
|
||||
} else {
|
||||
fmt.Printf("Missing key 'value' in '%s' output response\n%v\n",
|
||||
j.metric, out)
|
||||
}
|
||||
}
|
||||
|
||||
func addCassandraMetric(mbean string, c cassandraMetric,
|
||||
values map[string]interface{}) {
|
||||
|
||||
tags := make(map[string]string)
|
||||
fields := make(map[string]interface{})
|
||||
tokens := parseJmxMetricRequest(mbean)
|
||||
addTokensToTags(tokens, tags)
|
||||
addServerTags(c.host, tags)
|
||||
addValuesAsFields(values, fields, tags["mname"])
|
||||
c.acc.AddFields(tokens["class"]+tokens["type"], fields, tags)
|
||||
|
||||
}
|
||||
|
||||
func (c cassandraMetric) addTagsFields(out map[string]interface{}) {
|
||||
|
||||
r := out["request"]
|
||||
|
||||
tokens := parseJmxMetricRequest(r.(map[string]interface{})["mbean"].(string))
|
||||
// Requests with wildcards for keyspace or table names will return nested
|
||||
// maps in the json response
|
||||
if tokens["type"] == "Table" && (tokens["keyspace"] == "*" ||
|
||||
tokens["scope"] == "*") {
|
||||
if valuesMap, ok := out["value"]; ok {
|
||||
for k, v := range valuesMap.(map[string]interface{}) {
|
||||
addCassandraMetric(k, c, v.(map[string]interface{}))
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Missing key 'value' in '%s' output response\n%v\n",
|
||||
c.metric, out)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if values, ok := out["value"]; ok {
|
||||
addCassandraMetric(r.(map[string]interface{})["mbean"].(string),
|
||||
c, values.(map[string]interface{}))
|
||||
} else {
|
||||
fmt.Printf("Missing key 'value' in '%s' output response\n%v\n",
|
||||
c.metric, out)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (j *Cassandra) SampleConfig() string {
|
||||
return `
|
||||
# This is the context root used to compose the jolokia url
|
||||
context = "/jolokia/read"
|
||||
## List of cassandra servers exposing jolokia read service
|
||||
servers = ["myuser:mypassword@10.10.10.1:8778","10.10.10.2:8778",":8778"]
|
||||
## List of metrics collected on above servers
|
||||
## Each metric consists of a jmx path.
|
||||
## This will collect all heap memory usage metrics from the jvm and
|
||||
## ReadLatency metrics for all keyspaces and tables.
|
||||
## "type=Table" in the query works with Cassandra3.0. Older versions might
|
||||
## need to use "type=ColumnFamily"
|
||||
metrics = [
|
||||
"/java.lang:type=Memory/HeapMemoryUsage",
|
||||
"/org.apache.cassandra.metrics:type=Table,keyspace=*,scope=*,name=ReadLatency"
|
||||
]
|
||||
`
|
||||
}
|
||||
|
||||
func (j *Cassandra) Description() string {
|
||||
return "Read Cassandra metrics through Jolokia"
|
||||
}
|
||||
|
||||
func (j *Cassandra) getAttr(requestUrl *url.URL) (map[string]interface{}, error) {
|
||||
// Create + send request
|
||||
req, err := http.NewRequest("GET", requestUrl.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := j.jClient.MakeRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Process response
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
err = fmt.Errorf("Response from url \"%s\" has status code %d (%s), expected %d (%s)",
|
||||
requestUrl,
|
||||
resp.StatusCode,
|
||||
http.StatusText(resp.StatusCode),
|
||||
http.StatusOK,
|
||||
http.StatusText(http.StatusOK))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// read body
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Unmarshal json
|
||||
var jsonOut map[string]interface{}
|
||||
if err = json.Unmarshal([]byte(body), &jsonOut); err != nil {
|
||||
return nil, errors.New("Error decoding JSON response")
|
||||
}
|
||||
|
||||
return jsonOut, nil
|
||||
}
|
||||
|
||||
func parseServerTokens(server string) map[string]string {
|
||||
serverTokens := make(map[string]string)
|
||||
|
||||
hostAndUser := strings.Split(server, "@")
|
||||
hostPort := ""
|
||||
userPasswd := ""
|
||||
if len(hostAndUser) == 2 {
|
||||
hostPort = hostAndUser[1]
|
||||
userPasswd = hostAndUser[0]
|
||||
} else {
|
||||
hostPort = hostAndUser[0]
|
||||
}
|
||||
hostTokens := strings.Split(hostPort, ":")
|
||||
serverTokens["host"] = hostTokens[0]
|
||||
serverTokens["port"] = hostTokens[1]
|
||||
|
||||
if userPasswd != "" {
|
||||
userTokens := strings.Split(userPasswd, ":")
|
||||
serverTokens["user"] = userTokens[0]
|
||||
serverTokens["passwd"] = userTokens[1]
|
||||
}
|
||||
return serverTokens
|
||||
}
|
||||
|
||||
func (c *Cassandra) Gather(acc telegraf.Accumulator) error {
|
||||
context := c.Context
|
||||
servers := c.Servers
|
||||
metrics := c.Metrics
|
||||
|
||||
for _, server := range servers {
|
||||
for _, metric := range metrics {
|
||||
var m jmxMetric
|
||||
|
||||
serverTokens := parseServerTokens(server)
|
||||
|
||||
if strings.HasPrefix(metric, "/java.lang:") {
|
||||
m = newJavaMetric(serverTokens["host"], metric, acc)
|
||||
} else if strings.HasPrefix(metric,
|
||||
"/org.apache.cassandra.metrics:") {
|
||||
m = newCassandraMetric(serverTokens["host"], metric, acc)
|
||||
}
|
||||
|
||||
// Prepare URL
|
||||
requestUrl, err := url.Parse("http://" + serverTokens["host"] + ":" +
|
||||
serverTokens["port"] + context + metric)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if serverTokens["user"] != "" && serverTokens["passwd"] != "" {
|
||||
requestUrl.User = url.UserPassword(serverTokens["user"],
|
||||
serverTokens["passwd"])
|
||||
}
|
||||
fmt.Printf("host %s url %s\n", serverTokens["host"], requestUrl)
|
||||
|
||||
out, err := c.getAttr(requestUrl)
|
||||
if out["status"] != 200.0 {
|
||||
fmt.Printf("URL returned with status %v\n", out["status"])
|
||||
continue
|
||||
}
|
||||
m.addTagsFields(out)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
inputs.Add("cassandra", func() telegraf.Input {
|
||||
return &Cassandra{jClient: &JolokiaClientImpl{client: &http.Client{}}}
|
||||
})
|
||||
}
|
||||
286
plugins/inputs/cassandra/cassandra_test.go
Normal file
286
plugins/inputs/cassandra/cassandra_test.go
Normal file
@@ -0,0 +1,286 @@
|
||||
package cassandra
|
||||
|
||||
import (
|
||||
_ "fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
_ "github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const validJavaMultiValueJSON = `
|
||||
{
|
||||
"request":{
|
||||
"mbean":"java.lang:type=Memory",
|
||||
"attribute":"HeapMemoryUsage",
|
||||
"type":"read"
|
||||
},
|
||||
"value":{
|
||||
"init":67108864,
|
||||
"committed":456130560,
|
||||
"max":477626368,
|
||||
"used":203288528
|
||||
},
|
||||
"timestamp":1446129191,
|
||||
"status":200
|
||||
}`
|
||||
|
||||
const validCassandraMultiValueJSON = `
|
||||
{
|
||||
"request": {
|
||||
"mbean": "org.apache.cassandra.metrics:keyspace=test_keyspace1,name=ReadLatency,scope=test_table,type=Table",
|
||||
"type": "read"},
|
||||
"status": 200,
|
||||
"timestamp": 1458089229,
|
||||
"value": {
|
||||
"999thPercentile": 20.0,
|
||||
"99thPercentile": 10.0,
|
||||
"Count": 400,
|
||||
"DurationUnit": "microseconds",
|
||||
"Max": 30.0,
|
||||
"Mean": null,
|
||||
"MeanRate": 3.0,
|
||||
"Min": 1.0,
|
||||
"RateUnit": "events/second",
|
||||
"StdDev": null
|
||||
}
|
||||
}`
|
||||
|
||||
const validCassandraNestedMultiValueJSON = `
|
||||
{
|
||||
"request": {
|
||||
"mbean": "org.apache.cassandra.metrics:keyspace=test_keyspace1,name=ReadLatency,scope=*,type=Table",
|
||||
"type": "read"},
|
||||
"status": 200,
|
||||
"timestamp": 1458089184,
|
||||
"value": {
|
||||
"org.apache.cassandra.metrics:keyspace=test_keyspace1,name=ReadLatency,scope=test_table1,type=Table":
|
||||
{ "999thPercentile": 1.0,
|
||||
"Count": 100,
|
||||
"DurationUnit": "microseconds",
|
||||
"OneMinuteRate": 1.0,
|
||||
"RateUnit": "events/second",
|
||||
"StdDev": null
|
||||
},
|
||||
"org.apache.cassandra.metrics:keyspace=test_keyspace2,name=ReadLatency,scope=test_table2,type=Table":
|
||||
{ "999thPercentile": 2.0,
|
||||
"Count": 200,
|
||||
"DurationUnit": "microseconds",
|
||||
"OneMinuteRate": 2.0,
|
||||
"RateUnit": "events/second",
|
||||
"StdDev": null
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
const validSingleValueJSON = `
|
||||
{
|
||||
"request":{
|
||||
"path":"used",
|
||||
"mbean":"java.lang:type=Memory",
|
||||
"attribute":"HeapMemoryUsage",
|
||||
"type":"read"
|
||||
},
|
||||
"value":209274376,
|
||||
"timestamp":1446129256,
|
||||
"status":200
|
||||
}`
|
||||
|
||||
const validJavaMultiTypeJSON = `
|
||||
{
|
||||
"request":{
|
||||
"mbean":"java.lang:name=ConcurrentMarkSweep,type=GarbageCollector",
|
||||
"attribute":"CollectionCount",
|
||||
"type":"read"
|
||||
},
|
||||
"value":1,
|
||||
"timestamp":1459316570,
|
||||
"status":200
|
||||
}`
|
||||
|
||||
const invalidJSON = "I don't think this is JSON"
|
||||
|
||||
const empty = ""
|
||||
|
||||
var Servers = []string{"10.10.10.10:8778"}
|
||||
var AuthServers = []string{"user:passwd@10.10.10.10:8778"}
|
||||
var MultipleServers = []string{"10.10.10.10:8778", "10.10.10.11:8778"}
|
||||
var HeapMetric = "/java.lang:type=Memory/HeapMemoryUsage"
|
||||
var ReadLatencyMetric = "/org.apache.cassandra.metrics:type=Table,keyspace=test_keyspace1,scope=test_table,name=ReadLatency"
|
||||
var NestedReadLatencyMetric = "/org.apache.cassandra.metrics:type=Table,keyspace=test_keyspace1,scope=*,name=ReadLatency"
|
||||
var GarbageCollectorMetric1 = "/java.lang:type=GarbageCollector,name=ConcurrentMarkSweep/CollectionCount"
|
||||
var GarbageCollectorMetric2 = "/java.lang:type=GarbageCollector,name=ConcurrentMarkSweep/CollectionTime"
|
||||
var Context = "/jolokia/read"
|
||||
|
||||
type jolokiaClientStub struct {
|
||||
responseBody string
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func (c jolokiaClientStub) MakeRequest(req *http.Request) (*http.Response, error) {
|
||||
resp := http.Response{}
|
||||
resp.StatusCode = c.statusCode
|
||||
resp.Body = ioutil.NopCloser(strings.NewReader(c.responseBody))
|
||||
return &resp, 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 genJolokiaClientStub(response string, statusCode int, servers []string, metrics []string) *Cassandra {
|
||||
return &Cassandra{
|
||||
jClient: jolokiaClientStub{responseBody: response, statusCode: statusCode},
|
||||
Context: Context,
|
||||
Servers: servers,
|
||||
Metrics: metrics,
|
||||
}
|
||||
}
|
||||
|
||||
// Test that the proper values are ignored or collected for class=Java
|
||||
func TestHttpJsonJavaMultiValue(t *testing.T) {
|
||||
cassandra := genJolokiaClientStub(validJavaMultiValueJSON, 200,
|
||||
MultipleServers, []string{HeapMetric})
|
||||
|
||||
var acc testutil.Accumulator
|
||||
acc.SetDebug(true)
|
||||
err := cassandra.Gather(&acc)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(acc.Metrics))
|
||||
|
||||
fields := map[string]interface{}{
|
||||
"HeapMemoryUsage_init": 67108864.0,
|
||||
"HeapMemoryUsage_committed": 456130560.0,
|
||||
"HeapMemoryUsage_max": 477626368.0,
|
||||
"HeapMemoryUsage_used": 203288528.0,
|
||||
}
|
||||
tags1 := map[string]string{
|
||||
"host": "10.10.10.10",
|
||||
"mname": "HeapMemoryUsage",
|
||||
}
|
||||
|
||||
tags2 := map[string]string{
|
||||
"host": "10.10.10.11",
|
||||
"mname": "HeapMemoryUsage",
|
||||
}
|
||||
acc.AssertContainsTaggedFields(t, "javaMemory", fields, tags1)
|
||||
acc.AssertContainsTaggedFields(t, "javaMemory", fields, tags2)
|
||||
}
|
||||
|
||||
func TestHttpJsonJavaMultiType(t *testing.T) {
|
||||
cassandra := genJolokiaClientStub(validJavaMultiTypeJSON, 200, AuthServers, []string{GarbageCollectorMetric1, GarbageCollectorMetric2})
|
||||
|
||||
var acc testutil.Accumulator
|
||||
acc.SetDebug(true)
|
||||
err := cassandra.Gather(&acc)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(acc.Metrics))
|
||||
|
||||
fields := map[string]interface{}{
|
||||
"CollectionCount": 1.0,
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"host": "10.10.10.10",
|
||||
"mname": "ConcurrentMarkSweep",
|
||||
}
|
||||
acc.AssertContainsTaggedFields(t, "javaGarbageCollector", fields, tags)
|
||||
}
|
||||
|
||||
// Test that the proper values are ignored or collected
|
||||
func TestHttpJsonOn404(t *testing.T) {
|
||||
|
||||
jolokia := genJolokiaClientStub(validJavaMultiValueJSON, 404, Servers,
|
||||
[]string{HeapMetric})
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err := jolokia.Gather(&acc)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(acc.Metrics))
|
||||
}
|
||||
|
||||
// Test that the proper values are ignored or collected for class=Cassandra
|
||||
func TestHttpJsonCassandraMultiValue(t *testing.T) {
|
||||
cassandra := genJolokiaClientStub(validCassandraMultiValueJSON, 200, Servers, []string{ReadLatencyMetric})
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err := cassandra.Gather(&acc)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(acc.Metrics))
|
||||
|
||||
fields := map[string]interface{}{
|
||||
"ReadLatency_999thPercentile": 20.0,
|
||||
"ReadLatency_99thPercentile": 10.0,
|
||||
"ReadLatency_Count": 400.0,
|
||||
"ReadLatency_DurationUnit": "microseconds",
|
||||
"ReadLatency_Max": 30.0,
|
||||
"ReadLatency_MeanRate": 3.0,
|
||||
"ReadLatency_Min": 1.0,
|
||||
"ReadLatency_RateUnit": "events/second",
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"host": "10.10.10.10",
|
||||
"mname": "ReadLatency",
|
||||
"keyspace": "test_keyspace1",
|
||||
"scope": "test_table",
|
||||
}
|
||||
acc.AssertContainsTaggedFields(t, "cassandraTable", fields, tags)
|
||||
}
|
||||
|
||||
// Test that the proper values are ignored or collected for class=Cassandra with
|
||||
// nested values
|
||||
func TestHttpJsonCassandraNestedMultiValue(t *testing.T) {
|
||||
cassandra := genJolokiaClientStub(validCassandraNestedMultiValueJSON, 200, Servers, []string{NestedReadLatencyMetric})
|
||||
|
||||
var acc testutil.Accumulator
|
||||
acc.SetDebug(true)
|
||||
err := cassandra.Gather(&acc)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(acc.Metrics))
|
||||
|
||||
fields1 := map[string]interface{}{
|
||||
"ReadLatency_999thPercentile": 1.0,
|
||||
"ReadLatency_Count": 100.0,
|
||||
"ReadLatency_DurationUnit": "microseconds",
|
||||
"ReadLatency_OneMinuteRate": 1.0,
|
||||
"ReadLatency_RateUnit": "events/second",
|
||||
}
|
||||
|
||||
fields2 := map[string]interface{}{
|
||||
"ReadLatency_999thPercentile": 2.0,
|
||||
"ReadLatency_Count": 200.0,
|
||||
"ReadLatency_DurationUnit": "microseconds",
|
||||
"ReadLatency_OneMinuteRate": 2.0,
|
||||
"ReadLatency_RateUnit": "events/second",
|
||||
}
|
||||
|
||||
tags1 := map[string]string{
|
||||
"host": "10.10.10.10",
|
||||
"mname": "ReadLatency",
|
||||
"keyspace": "test_keyspace1",
|
||||
"scope": "test_table1",
|
||||
}
|
||||
|
||||
tags2 := map[string]string{
|
||||
"host": "10.10.10.10",
|
||||
"mname": "ReadLatency",
|
||||
"keyspace": "test_keyspace2",
|
||||
"scope": "test_table2",
|
||||
}
|
||||
|
||||
acc.AssertContainsTaggedFields(t, "cassandraTable", fields1, tags1)
|
||||
acc.AssertContainsTaggedFields(t, "cassandraTable", fields2, tags2)
|
||||
}
|
||||
86
plugins/inputs/cloudwatch/README.md
Normal file
86
plugins/inputs/cloudwatch/README.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Amazon CloudWatch Statistics Input
|
||||
|
||||
This plugin will pull Metric Statistics from Amazon CloudWatch.
|
||||
|
||||
### Amazon Authentication
|
||||
|
||||
This plugin uses a credential chain for Authentication with the CloudWatch
|
||||
API endpoint. In the following order the plugin will attempt to authenticate.
|
||||
1. [IAMS Role](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html)
|
||||
2. [Environment Variables](https://github.com/aws/aws-sdk-go/wiki/configuring-sdk#environment-variables)
|
||||
3. [Shared Credentials](https://github.com/aws/aws-sdk-go/wiki/configuring-sdk#shared-credentials-file)
|
||||
|
||||
### Configuration:
|
||||
|
||||
```toml
|
||||
[[inputs.cloudwatch]]
|
||||
## Amazon Region (required)
|
||||
region = 'us-east-1'
|
||||
|
||||
## Requested CloudWatch aggregation Period (required - must be a multiple of 60s)
|
||||
period = '1m'
|
||||
|
||||
## Collection Delay (required - must account for metrics availability via CloudWatch API)
|
||||
delay = '1m'
|
||||
|
||||
## Override global run interval (optional - defaults to global interval)
|
||||
## Recomended: use metric 'interval' that is a multiple of 'period' to avoid
|
||||
## gaps or overlap in pulled data
|
||||
interval = '1m'
|
||||
|
||||
## Metric Statistic Namespace (required)
|
||||
namespace = 'AWS/ELB'
|
||||
|
||||
## Metrics to Pull (optional)
|
||||
## Defaults to all Metrics in Namespace if nothing is provided
|
||||
## Refreshes Namespace available metrics every 1h
|
||||
[[inputs.cloudwatch.metrics]]
|
||||
names = ['Latency', 'RequestCount']
|
||||
|
||||
## Dimension filters for Metric (optional)
|
||||
[[inputs.cloudwatch.metrics.dimensions]]
|
||||
name = 'LoadBalancerName'
|
||||
value = 'p-example'
|
||||
```
|
||||
#### Requirements and Terminology
|
||||
|
||||
Plugin Configuration utilizes [CloudWatch concepts](http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/cloudwatch_concepts.html) and access pattern to allow monitoring of any CloudWatch Metric.
|
||||
|
||||
- `region` must be a valid AWS [Region](http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/cloudwatch_concepts.html#CloudWatchRegions) value
|
||||
- `period` must be a valid CloudWatch [Period](http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/cloudwatch_concepts.html#CloudWatchPeriods) value
|
||||
- `namespace` must be a valid CloudWatch [Namespace](http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/cloudwatch_concepts.html#Namespace) value
|
||||
- `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
|
||||
|
||||
#### 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 API usage incurs cost - see [GetMetricStatistics Pricing](https://aws.amazon.com/cloudwatch/pricing/)
|
||||
|
||||
### Measurements & Fields:
|
||||
|
||||
Each CloudWatch Namespace monitored records a measurement with fields for each available Metric Statistic
|
||||
Namespace and Metrics are represented in [snake case](https://en.wikipedia.org/wiki/Snake_case)
|
||||
|
||||
- cloudwatch_{namespace}
|
||||
- {metric}_sum (metric Sum value)
|
||||
- {metric}_average (metric Average value)
|
||||
- {metric}_minimum (metric Minimum value)
|
||||
- {metric}_maximum (metric Maximum value)
|
||||
- {metric}_sample_count (metric SampleCount value)
|
||||
|
||||
|
||||
### Tags:
|
||||
Each measurement is tagged with the following identifiers to uniquely identify the associated metric
|
||||
Tag Dimension names are represented in [snake case](https://en.wikipedia.org/wiki/Snake_case)
|
||||
|
||||
- All measurements have the following tags:
|
||||
- region (CloudWatch Region)
|
||||
- unit (CloudWatch Metric Unit)
|
||||
- {dimension-name} (Cloudwatch Dimension value - one for each metric dimension)
|
||||
|
||||
### Example Output:
|
||||
|
||||
```
|
||||
$ ./telegraf -config telegraf.conf -input-filter cloudwatch -test
|
||||
> cloudwatch_aws_elb,load_balancer_name=p-example,region=us-east-1,unit=seconds latency_average=0.004810798017284538,latency_maximum=0.1100282669067383,latency_minimum=0.0006084442138671875,latency_sample_count=4029,latency_sum=19.382705211639404 1459542420000000000
|
||||
```
|
||||
305
plugins/inputs/cloudwatch/cloudwatch.go
Normal file
305
plugins/inputs/cloudwatch/cloudwatch.go
Normal file
@@ -0,0 +1,305 @@
|
||||
package cloudwatch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
|
||||
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/internal"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
type (
|
||||
CloudWatch struct {
|
||||
Region string `toml:"region"`
|
||||
Period internal.Duration `toml:"period"`
|
||||
Delay internal.Duration `toml:"delay"`
|
||||
Namespace string `toml:"namespace"`
|
||||
Metrics []*Metric `toml:"metrics"`
|
||||
client cloudwatchClient
|
||||
metricCache *MetricCache
|
||||
}
|
||||
|
||||
Metric struct {
|
||||
MetricNames []string `toml:"names"`
|
||||
Dimensions []*Dimension `toml:"dimensions"`
|
||||
}
|
||||
|
||||
Dimension struct {
|
||||
Name string `toml:"name"`
|
||||
Value string `toml:"value"`
|
||||
}
|
||||
|
||||
MetricCache struct {
|
||||
TTL time.Duration
|
||||
Fetched time.Time
|
||||
Metrics []*cloudwatch.Metric
|
||||
}
|
||||
|
||||
cloudwatchClient interface {
|
||||
ListMetrics(*cloudwatch.ListMetricsInput) (*cloudwatch.ListMetricsOutput, error)
|
||||
GetMetricStatistics(*cloudwatch.GetMetricStatisticsInput) (*cloudwatch.GetMetricStatisticsOutput, error)
|
||||
}
|
||||
)
|
||||
|
||||
func (c *CloudWatch) SampleConfig() string {
|
||||
return `
|
||||
## Amazon Region
|
||||
region = 'us-east-1'
|
||||
|
||||
## Requested CloudWatch aggregation Period (required - must be a multiple of 60s)
|
||||
period = '1m'
|
||||
|
||||
## Collection Delay (required - must account for metrics availability via CloudWatch API)
|
||||
delay = '1m'
|
||||
|
||||
## Recomended: use metric 'interval' that is a multiple of 'period' to avoid
|
||||
## gaps or overlap in pulled data
|
||||
interval = '1m'
|
||||
|
||||
## Metric Statistic Namespace (required)
|
||||
namespace = 'AWS/ELB'
|
||||
|
||||
## Metrics to Pull (optional)
|
||||
## Defaults to all Metrics in Namespace if nothing is provided
|
||||
## Refreshes Namespace available metrics every 1h
|
||||
#[[inputs.cloudwatch.metrics]]
|
||||
# names = ['Latency', 'RequestCount']
|
||||
#
|
||||
# ## Dimension filters for Metric (optional)
|
||||
# [[inputs.cloudwatch.metrics.dimensions]]
|
||||
# name = 'LoadBalancerName'
|
||||
# value = 'p-example'
|
||||
`
|
||||
}
|
||||
|
||||
func (c *CloudWatch) Description() string {
|
||||
return "Pull Metric Statistics from Amazon CloudWatch"
|
||||
}
|
||||
|
||||
func (c *CloudWatch) Gather(acc telegraf.Accumulator) error {
|
||||
if c.client == nil {
|
||||
c.initializeCloudWatch()
|
||||
}
|
||||
|
||||
var metrics []*cloudwatch.Metric
|
||||
|
||||
// check for provided metric filter
|
||||
if c.Metrics != nil {
|
||||
metrics = []*cloudwatch.Metric{}
|
||||
for _, m := range c.Metrics {
|
||||
dimensions := make([]*cloudwatch.Dimension, len(m.Dimensions))
|
||||
for k, d := range m.Dimensions {
|
||||
dimensions[k] = &cloudwatch.Dimension{
|
||||
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 {
|
||||
var err error
|
||||
metrics, err = c.fetchNamespaceMetrics()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
metricCount := len(metrics)
|
||||
var errChan = make(chan error, metricCount)
|
||||
|
||||
now := time.Now()
|
||||
|
||||
// limit concurrency or we can easily exhaust user connection limit
|
||||
semaphore := make(chan byte, 64)
|
||||
|
||||
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() {
|
||||
inputs.Add("cloudwatch", func() telegraf.Input {
|
||||
return &CloudWatch{}
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize CloudWatch client
|
||||
*/
|
||||
func (c *CloudWatch) initializeCloudWatch() error {
|
||||
config := &aws.Config{
|
||||
Region: aws.String(c.Region),
|
||||
Credentials: credentials.NewChainCredentials(
|
||||
[]credentials.Provider{
|
||||
&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(session.New())},
|
||||
&credentials.EnvProvider{},
|
||||
&credentials.SharedCredentialsProvider{},
|
||||
}),
|
||||
}
|
||||
|
||||
c.client = cloudwatch.New(session.New(config))
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
* Fetch available metrics for given CloudWatch Namespace
|
||||
*/
|
||||
func (c *CloudWatch) fetchNamespaceMetrics() (metrics []*cloudwatch.Metric, err error) {
|
||||
if c.metricCache != nil && c.metricCache.IsValid() {
|
||||
metrics = c.metricCache.Metrics
|
||||
return
|
||||
}
|
||||
|
||||
metrics = []*cloudwatch.Metric{}
|
||||
|
||||
var token *string
|
||||
for more := true; more; {
|
||||
params := &cloudwatch.ListMetricsInput{
|
||||
Namespace: aws.String(c.Namespace),
|
||||
Dimensions: []*cloudwatch.DimensionFilter{},
|
||||
NextToken: token,
|
||||
MetricName: nil,
|
||||
}
|
||||
|
||||
resp, err := c.client.ListMetrics(params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metrics = append(metrics, resp.Metrics...)
|
||||
|
||||
token = resp.NextToken
|
||||
more = token != nil
|
||||
}
|
||||
|
||||
cacheTTL, _ := time.ParseDuration("1hr")
|
||||
c.metricCache = &MetricCache{
|
||||
Metrics: metrics,
|
||||
Fetched: time.Now(),
|
||||
TTL: cacheTTL,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
* Gather given Metric and emit any error
|
||||
*/
|
||||
func (c *CloudWatch) gatherMetric(acc telegraf.Accumulator, metric *cloudwatch.Metric, now time.Time, semaphore chan byte, errChan chan error) {
|
||||
params := c.getStatisticsInput(metric, now)
|
||||
resp, err := c.client.GetMetricStatistics(params)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
<-semaphore
|
||||
return
|
||||
}
|
||||
|
||||
for _, point := range resp.Datapoints {
|
||||
tags := map[string]string{
|
||||
"region": c.Region,
|
||||
"unit": snakeCase(*point.Unit),
|
||||
}
|
||||
|
||||
for _, d := range metric.Dimensions {
|
||||
tags[snakeCase(*d.Name)] = *d.Value
|
||||
}
|
||||
|
||||
// record field for each statistic
|
||||
fields := map[string]interface{}{}
|
||||
|
||||
if point.Average != nil {
|
||||
fields[formatField(*metric.MetricName, cloudwatch.StatisticAverage)] = *point.Average
|
||||
}
|
||||
if point.Maximum != nil {
|
||||
fields[formatField(*metric.MetricName, cloudwatch.StatisticMaximum)] = *point.Maximum
|
||||
}
|
||||
if point.Minimum != nil {
|
||||
fields[formatField(*metric.MetricName, cloudwatch.StatisticMinimum)] = *point.Minimum
|
||||
}
|
||||
if point.SampleCount != nil {
|
||||
fields[formatField(*metric.MetricName, cloudwatch.StatisticSampleCount)] = *point.SampleCount
|
||||
}
|
||||
if point.Sum != nil {
|
||||
fields[formatField(*metric.MetricName, cloudwatch.StatisticSum)] = *point.Sum
|
||||
}
|
||||
|
||||
acc.AddFields(formatMeasurement(c.Namespace), fields, tags, *point.Timestamp)
|
||||
}
|
||||
|
||||
errChan <- nil
|
||||
<-semaphore
|
||||
}
|
||||
|
||||
/*
|
||||
* Formatting helpers
|
||||
*/
|
||||
func formatField(metricName string, statistic string) string {
|
||||
return fmt.Sprintf("%s_%s", snakeCase(metricName), snakeCase(statistic))
|
||||
}
|
||||
|
||||
func formatMeasurement(namespace string) string {
|
||||
namespace = strings.Replace(namespace, "/", "_", -1)
|
||||
namespace = snakeCase(namespace)
|
||||
return fmt.Sprintf("cloudwatch_%s", namespace)
|
||||
}
|
||||
|
||||
func snakeCase(s string) string {
|
||||
s = internal.SnakeCase(s)
|
||||
s = strings.Replace(s, "__", "_", -1)
|
||||
return s
|
||||
}
|
||||
|
||||
/*
|
||||
* Map Metric to *cloudwatch.GetMetricStatisticsInput for given timeframe
|
||||
*/
|
||||
func (c *CloudWatch) getStatisticsInput(metric *cloudwatch.Metric, now time.Time) *cloudwatch.GetMetricStatisticsInput {
|
||||
end := now.Add(-c.Delay.Duration)
|
||||
|
||||
input := &cloudwatch.GetMetricStatisticsInput{
|
||||
StartTime: aws.Time(end.Add(-c.Period.Duration)),
|
||||
EndTime: aws.Time(end),
|
||||
MetricName: metric.MetricName,
|
||||
Namespace: metric.Namespace,
|
||||
Period: aws.Int64(int64(c.Period.Duration.Seconds())),
|
||||
Dimensions: metric.Dimensions,
|
||||
Statistics: []*string{
|
||||
aws.String(cloudwatch.StatisticAverage),
|
||||
aws.String(cloudwatch.StatisticMaximum),
|
||||
aws.String(cloudwatch.StatisticMinimum),
|
||||
aws.String(cloudwatch.StatisticSum),
|
||||
aws.String(cloudwatch.StatisticSampleCount)},
|
||||
}
|
||||
return input
|
||||
}
|
||||
|
||||
/*
|
||||
* Check Metric Cache validity
|
||||
*/
|
||||
func (c *MetricCache) IsValid() bool {
|
||||
return c.Metrics != nil && time.Since(c.Fetched) < c.TTL
|
||||
}
|
||||
131
plugins/inputs/cloudwatch/cloudwatch_test.go
Normal file
131
plugins/inputs/cloudwatch/cloudwatch_test.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package cloudwatch
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
||||
"github.com/influxdata/telegraf/internal"
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type mockCloudWatchClient struct{}
|
||||
|
||||
func (m *mockCloudWatchClient) ListMetrics(params *cloudwatch.ListMetricsInput) (*cloudwatch.ListMetricsOutput, error) {
|
||||
metric := &cloudwatch.Metric{
|
||||
Namespace: params.Namespace,
|
||||
MetricName: aws.String("Latency"),
|
||||
Dimensions: []*cloudwatch.Dimension{
|
||||
&cloudwatch.Dimension{
|
||||
Name: aws.String("LoadBalancerName"),
|
||||
Value: aws.String("p-example"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result := &cloudwatch.ListMetricsOutput{
|
||||
Metrics: []*cloudwatch.Metric{metric},
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (m *mockCloudWatchClient) GetMetricStatistics(params *cloudwatch.GetMetricStatisticsInput) (*cloudwatch.GetMetricStatisticsOutput, error) {
|
||||
dataPoint := &cloudwatch.Datapoint{
|
||||
Timestamp: params.EndTime,
|
||||
Minimum: aws.Float64(0.1),
|
||||
Maximum: aws.Float64(0.3),
|
||||
Average: aws.Float64(0.2),
|
||||
Sum: aws.Float64(123),
|
||||
SampleCount: aws.Float64(100),
|
||||
Unit: aws.String("Seconds"),
|
||||
}
|
||||
result := &cloudwatch.GetMetricStatisticsOutput{
|
||||
Label: aws.String("Latency"),
|
||||
Datapoints: []*cloudwatch.Datapoint{dataPoint},
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func TestGather(t *testing.T) {
|
||||
duration, _ := time.ParseDuration("1m")
|
||||
internalDuration := internal.Duration{
|
||||
Duration: duration,
|
||||
}
|
||||
c := &CloudWatch{
|
||||
Region: "us-east-1",
|
||||
Namespace: "AWS/ELB",
|
||||
Delay: internalDuration,
|
||||
Period: internalDuration,
|
||||
}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
c.client = &mockCloudWatchClient{}
|
||||
|
||||
c.Gather(&acc)
|
||||
|
||||
fields := map[string]interface{}{}
|
||||
fields["latency_minimum"] = 0.1
|
||||
fields["latency_maximum"] = 0.3
|
||||
fields["latency_average"] = 0.2
|
||||
fields["latency_sum"] = 123.0
|
||||
fields["latency_sample_count"] = 100.0
|
||||
|
||||
tags := map[string]string{}
|
||||
tags["unit"] = "seconds"
|
||||
tags["region"] = "us-east-1"
|
||||
tags["load_balancer_name"] = "p-example"
|
||||
|
||||
assert.True(t, acc.HasMeasurement("cloudwatch_aws_elb"))
|
||||
acc.AssertContainsTaggedFields(t, "cloudwatch_aws_elb", fields, tags)
|
||||
|
||||
}
|
||||
|
||||
func TestGenerateStatisticsInputParams(t *testing.T) {
|
||||
d := &cloudwatch.Dimension{
|
||||
Name: aws.String("LoadBalancerName"),
|
||||
Value: aws.String("p-example"),
|
||||
}
|
||||
|
||||
m := &cloudwatch.Metric{
|
||||
MetricName: aws.String("Latency"),
|
||||
Dimensions: []*cloudwatch.Dimension{d},
|
||||
}
|
||||
|
||||
duration, _ := time.ParseDuration("1m")
|
||||
internalDuration := internal.Duration{
|
||||
Duration: duration,
|
||||
}
|
||||
|
||||
c := &CloudWatch{
|
||||
Namespace: "AWS/ELB",
|
||||
Delay: internalDuration,
|
||||
Period: internalDuration,
|
||||
}
|
||||
|
||||
c.initializeCloudWatch()
|
||||
|
||||
now := time.Now()
|
||||
|
||||
params := c.getStatisticsInput(m, now)
|
||||
|
||||
assert.EqualValues(t, *params.EndTime, now.Add(-c.Delay.Duration))
|
||||
assert.EqualValues(t, *params.StartTime, now.Add(-c.Period.Duration).Add(-c.Delay.Duration))
|
||||
assert.Len(t, params.Dimensions, 1)
|
||||
assert.Len(t, params.Statistics, 5)
|
||||
assert.EqualValues(t, *params.Period, 60)
|
||||
}
|
||||
|
||||
func TestMetricsCacheTimeout(t *testing.T) {
|
||||
ttl, _ := time.ParseDuration("5ms")
|
||||
cache := &MetricCache{
|
||||
Metrics: []*cloudwatch.Metric{},
|
||||
Fetched: time.Now(),
|
||||
TTL: ttl,
|
||||
}
|
||||
|
||||
assert.True(t, cache.IsValid())
|
||||
time.Sleep(ttl)
|
||||
assert.False(t, cache.IsValid())
|
||||
}
|
||||
@@ -5,11 +5,11 @@ docker containers. You can read Docker's documentation for their remote API
|
||||
[here](https://docs.docker.com/engine/reference/api/docker_remote_api_v1.20/#get-container-stats-based-on-resource-usage)
|
||||
|
||||
The docker plugin uses the excellent
|
||||
[fsouza go-dockerclient](https://github.com/fsouza/go-dockerclient) library to
|
||||
[docker engine-api](https://github.com/docker/engine-api) library to
|
||||
gather stats. Documentation for the library can be found
|
||||
[here](https://godoc.org/github.com/fsouza/go-dockerclient) and documentation
|
||||
[here](https://godoc.org/github.com/docker/engine-api) and documentation
|
||||
for the stat structure can be found
|
||||
[here](https://godoc.org/github.com/fsouza/go-dockerclient#Stats)
|
||||
[here](https://godoc.org/github.com/docker/engine-api/types#Stats)
|
||||
|
||||
### Configuration:
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package system
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"regexp"
|
||||
"strconv"
|
||||
@@ -10,12 +11,15 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/engine-api/client"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
)
|
||||
|
||||
// Docker object
|
||||
type Docker struct {
|
||||
Endpoint string
|
||||
ContainerNames []string
|
||||
@@ -23,14 +27,14 @@ type Docker struct {
|
||||
client DockerClient
|
||||
}
|
||||
|
||||
// DockerClient interface, useful for testing
|
||||
type DockerClient interface {
|
||||
// Docker Client wrapper
|
||||
// Useful for test
|
||||
Info() (*docker.Env, error)
|
||||
ListContainers(opts docker.ListContainersOptions) ([]docker.APIContainers, error)
|
||||
Stats(opts docker.StatsOptions) error
|
||||
Info(ctx context.Context) (types.Info, error)
|
||||
ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error)
|
||||
ContainerStats(ctx context.Context, containerID string, stream bool) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
// KB, MB, GB, TB, PB...human friendly
|
||||
const (
|
||||
KB = 1000
|
||||
MB = 1000 * KB
|
||||
@@ -52,28 +56,32 @@ var sampleConfig = `
|
||||
container_names = []
|
||||
`
|
||||
|
||||
// Description returns input description
|
||||
func (d *Docker) Description() string {
|
||||
return "Read metrics about docker containers"
|
||||
}
|
||||
|
||||
// SampleConfig prints sampleConfig
|
||||
func (d *Docker) SampleConfig() string { return sampleConfig }
|
||||
|
||||
// Gather starts stats collection
|
||||
func (d *Docker) Gather(acc telegraf.Accumulator) error {
|
||||
if d.client == nil {
|
||||
var c *docker.Client
|
||||
var c *client.Client
|
||||
var err error
|
||||
defaultHeaders := map[string]string{"User-Agent": "engine-api-cli-1.0"}
|
||||
if d.Endpoint == "ENV" {
|
||||
c, err = docker.NewClientFromEnv()
|
||||
c, err = client.NewEnvClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if d.Endpoint == "" {
|
||||
c, err = docker.NewClient("unix:///var/run/docker.sock")
|
||||
c, err = client.NewClient("unix:///var/run/docker.sock", "", nil, defaultHeaders)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
c, err = docker.NewClient(d.Endpoint)
|
||||
c, err = client.NewClient(d.Endpoint, "", nil, defaultHeaders)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -88,8 +96,8 @@ func (d *Docker) Gather(acc telegraf.Accumulator) error {
|
||||
}
|
||||
|
||||
// List containers
|
||||
opts := docker.ListContainersOptions{}
|
||||
containers, err := d.client.ListContainers(opts)
|
||||
opts := types.ContainerListOptions{}
|
||||
containers, err := d.client.ContainerList(context.Background(), opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -99,7 +107,7 @@ func (d *Docker) Gather(acc telegraf.Accumulator) error {
|
||||
wg.Add(len(containers))
|
||||
for _, container := range containers {
|
||||
|
||||
go func(c docker.APIContainers) {
|
||||
go func(c types.Container) {
|
||||
defer wg.Done()
|
||||
err := d.gatherContainer(c, acc)
|
||||
if err != nil {
|
||||
@@ -114,23 +122,22 @@ func (d *Docker) Gather(acc telegraf.Accumulator) error {
|
||||
|
||||
func (d *Docker) gatherInfo(acc telegraf.Accumulator) error {
|
||||
// Init vars
|
||||
var driverStatus [][]string
|
||||
dataFields := make(map[string]interface{})
|
||||
metadataFields := make(map[string]interface{})
|
||||
now := time.Now()
|
||||
// Get info from docker daemon
|
||||
info, err := d.client.Info()
|
||||
info, err := d.client.Info(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fields := map[string]interface{}{
|
||||
"n_cpus": info.GetInt64("NCPU"),
|
||||
"n_used_file_descriptors": info.GetInt64("NFd"),
|
||||
"n_containers": info.GetInt64("Containers"),
|
||||
"n_images": info.GetInt64("Images"),
|
||||
"n_goroutines": info.GetInt64("NGoroutines"),
|
||||
"n_listener_events": info.GetInt64("NEventsListener"),
|
||||
"n_cpus": info.NCPU,
|
||||
"n_used_file_descriptors": info.NFd,
|
||||
"n_containers": info.Containers,
|
||||
"n_images": info.Images,
|
||||
"n_goroutines": info.NGoroutines,
|
||||
"n_listener_events": info.NEventsListener,
|
||||
}
|
||||
// Add metrics
|
||||
acc.AddFields("docker",
|
||||
@@ -138,13 +145,11 @@ func (d *Docker) gatherInfo(acc telegraf.Accumulator) error {
|
||||
nil,
|
||||
now)
|
||||
acc.AddFields("docker",
|
||||
map[string]interface{}{"memory_total": info.GetInt64("MemTotal")},
|
||||
map[string]interface{}{"memory_total": info.MemTotal},
|
||||
map[string]string{"unit": "bytes"},
|
||||
now)
|
||||
// Get storage metrics
|
||||
driverStatusRaw := []byte(info.Get("DriverStatus"))
|
||||
json.Unmarshal(driverStatusRaw, &driverStatus)
|
||||
for _, rawData := range driverStatus {
|
||||
for _, rawData := range info.DriverStatus {
|
||||
// Try to convert string to int (bytes)
|
||||
value, err := parseSize(rawData[1])
|
||||
if err != nil {
|
||||
@@ -159,12 +164,12 @@ func (d *Docker) gatherInfo(acc telegraf.Accumulator) error {
|
||||
now)
|
||||
} else if strings.HasPrefix(name, "data_space_") {
|
||||
// data space
|
||||
field_name := strings.TrimPrefix(name, "data_space_")
|
||||
dataFields[field_name] = value
|
||||
fieldName := strings.TrimPrefix(name, "data_space_")
|
||||
dataFields[fieldName] = value
|
||||
} else if strings.HasPrefix(name, "metadata_space_") {
|
||||
// metadata space
|
||||
field_name := strings.TrimPrefix(name, "metadata_space_")
|
||||
metadataFields[field_name] = value
|
||||
fieldName := strings.TrimPrefix(name, "metadata_space_")
|
||||
metadataFields[fieldName] = value
|
||||
}
|
||||
}
|
||||
if len(dataFields) > 0 {
|
||||
@@ -183,9 +188,10 @@ func (d *Docker) gatherInfo(acc telegraf.Accumulator) error {
|
||||
}
|
||||
|
||||
func (d *Docker) gatherContainer(
|
||||
container docker.APIContainers,
|
||||
container types.Container,
|
||||
acc telegraf.Accumulator,
|
||||
) error {
|
||||
var v *types.StatsJSON
|
||||
// Parse container name
|
||||
cname := "unknown"
|
||||
if len(container.Names) > 0 {
|
||||
@@ -204,28 +210,14 @@ func (d *Docker) gatherContainer(
|
||||
}
|
||||
}
|
||||
|
||||
statChan := make(chan *docker.Stats)
|
||||
done := make(chan bool)
|
||||
statOpts := docker.StatsOptions{
|
||||
Stream: false,
|
||||
ID: container.ID,
|
||||
Stats: statChan,
|
||||
Done: done,
|
||||
Timeout: time.Duration(time.Second * 5),
|
||||
r, err := d.client.ContainerStats(context.Background(), container.ID, false)
|
||||
if err != nil {
|
||||
log.Printf("Error getting docker stats: %s\n", err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := d.client.Stats(statOpts)
|
||||
if err != nil {
|
||||
log.Printf("Error getting docker stats: %s\n", err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
stat := <-statChan
|
||||
close(done)
|
||||
|
||||
if stat == nil {
|
||||
return nil
|
||||
defer r.Close()
|
||||
dec := json.NewDecoder(r)
|
||||
if err = dec.Decode(&v); err != nil {
|
||||
log.Printf("Error decoding: %s\n", err.Error())
|
||||
}
|
||||
|
||||
// Add labels to tags
|
||||
@@ -233,13 +225,13 @@ func (d *Docker) gatherContainer(
|
||||
tags[k] = v
|
||||
}
|
||||
|
||||
gatherContainerStats(stat, acc, tags)
|
||||
gatherContainerStats(v, acc, tags)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func gatherContainerStats(
|
||||
stat *docker.Stats,
|
||||
stat *types.StatsJSON,
|
||||
acc telegraf.Accumulator,
|
||||
tags map[string]string,
|
||||
) {
|
||||
@@ -250,35 +242,35 @@ func gatherContainerStats(
|
||||
"usage": stat.MemoryStats.Usage,
|
||||
"fail_count": stat.MemoryStats.Failcnt,
|
||||
"limit": stat.MemoryStats.Limit,
|
||||
"total_pgmafault": stat.MemoryStats.Stats.TotalPgmafault,
|
||||
"cache": stat.MemoryStats.Stats.Cache,
|
||||
"mapped_file": stat.MemoryStats.Stats.MappedFile,
|
||||
"total_inactive_file": stat.MemoryStats.Stats.TotalInactiveFile,
|
||||
"pgpgout": stat.MemoryStats.Stats.Pgpgout,
|
||||
"rss": stat.MemoryStats.Stats.Rss,
|
||||
"total_mapped_file": stat.MemoryStats.Stats.TotalMappedFile,
|
||||
"writeback": stat.MemoryStats.Stats.Writeback,
|
||||
"unevictable": stat.MemoryStats.Stats.Unevictable,
|
||||
"pgpgin": stat.MemoryStats.Stats.Pgpgin,
|
||||
"total_unevictable": stat.MemoryStats.Stats.TotalUnevictable,
|
||||
"pgmajfault": stat.MemoryStats.Stats.Pgmajfault,
|
||||
"total_rss": stat.MemoryStats.Stats.TotalRss,
|
||||
"total_rss_huge": stat.MemoryStats.Stats.TotalRssHuge,
|
||||
"total_writeback": stat.MemoryStats.Stats.TotalWriteback,
|
||||
"total_inactive_anon": stat.MemoryStats.Stats.TotalInactiveAnon,
|
||||
"rss_huge": stat.MemoryStats.Stats.RssHuge,
|
||||
"hierarchical_memory_limit": stat.MemoryStats.Stats.HierarchicalMemoryLimit,
|
||||
"total_pgfault": stat.MemoryStats.Stats.TotalPgfault,
|
||||
"total_active_file": stat.MemoryStats.Stats.TotalActiveFile,
|
||||
"active_anon": stat.MemoryStats.Stats.ActiveAnon,
|
||||
"total_active_anon": stat.MemoryStats.Stats.TotalActiveAnon,
|
||||
"total_pgpgout": stat.MemoryStats.Stats.TotalPgpgout,
|
||||
"total_cache": stat.MemoryStats.Stats.TotalCache,
|
||||
"inactive_anon": stat.MemoryStats.Stats.InactiveAnon,
|
||||
"active_file": stat.MemoryStats.Stats.ActiveFile,
|
||||
"pgfault": stat.MemoryStats.Stats.Pgfault,
|
||||
"inactive_file": stat.MemoryStats.Stats.InactiveFile,
|
||||
"total_pgpgin": stat.MemoryStats.Stats.TotalPgpgin,
|
||||
"total_pgmafault": stat.MemoryStats.Stats["total_pgmajfault"],
|
||||
"cache": stat.MemoryStats.Stats["cache"],
|
||||
"mapped_file": stat.MemoryStats.Stats["mapped_file"],
|
||||
"total_inactive_file": stat.MemoryStats.Stats["total_inactive_file"],
|
||||
"pgpgout": stat.MemoryStats.Stats["pagpgout"],
|
||||
"rss": stat.MemoryStats.Stats["rss"],
|
||||
"total_mapped_file": stat.MemoryStats.Stats["total_mapped_file"],
|
||||
"writeback": stat.MemoryStats.Stats["writeback"],
|
||||
"unevictable": stat.MemoryStats.Stats["unevictable"],
|
||||
"pgpgin": stat.MemoryStats.Stats["pgpgin"],
|
||||
"total_unevictable": stat.MemoryStats.Stats["total_unevictable"],
|
||||
"pgmajfault": stat.MemoryStats.Stats["pgmajfault"],
|
||||
"total_rss": stat.MemoryStats.Stats["total_rss"],
|
||||
"total_rss_huge": stat.MemoryStats.Stats["total_rss_huge"],
|
||||
"total_writeback": stat.MemoryStats.Stats["total_write_back"],
|
||||
"total_inactive_anon": stat.MemoryStats.Stats["total_inactive_anon"],
|
||||
"rss_huge": stat.MemoryStats.Stats["rss_huge"],
|
||||
"hierarchical_memory_limit": stat.MemoryStats.Stats["hierarchical_memory_limit"],
|
||||
"total_pgfault": stat.MemoryStats.Stats["total_pgfault"],
|
||||
"total_active_file": stat.MemoryStats.Stats["total_active_file"],
|
||||
"active_anon": stat.MemoryStats.Stats["active_anon"],
|
||||
"total_active_anon": stat.MemoryStats.Stats["total_active_anon"],
|
||||
"total_pgpgout": stat.MemoryStats.Stats["total_pgpgout"],
|
||||
"total_cache": stat.MemoryStats.Stats["total_cache"],
|
||||
"inactive_anon": stat.MemoryStats.Stats["inactive_anon"],
|
||||
"active_file": stat.MemoryStats.Stats["active_file"],
|
||||
"pgfault": stat.MemoryStats.Stats["pgfault"],
|
||||
"inactive_file": stat.MemoryStats.Stats["inactive_file"],
|
||||
"total_pgpgin": stat.MemoryStats.Stats["total_pgpgin"],
|
||||
"usage_percent": calculateMemPercent(stat),
|
||||
}
|
||||
acc.AddFields("docker_mem", memfields, tags, now)
|
||||
@@ -287,7 +279,7 @@ func gatherContainerStats(
|
||||
"usage_total": stat.CPUStats.CPUUsage.TotalUsage,
|
||||
"usage_in_usermode": stat.CPUStats.CPUUsage.UsageInUsermode,
|
||||
"usage_in_kernelmode": stat.CPUStats.CPUUsage.UsageInKernelmode,
|
||||
"usage_system": stat.CPUStats.SystemCPUUsage,
|
||||
"usage_system": stat.CPUStats.SystemUsage,
|
||||
"throttling_periods": stat.CPUStats.ThrottlingData.Periods,
|
||||
"throttling_throttled_periods": stat.CPUStats.ThrottlingData.ThrottledPeriods,
|
||||
"throttling_throttled_time": stat.CPUStats.ThrottlingData.ThrottledTime,
|
||||
@@ -323,7 +315,7 @@ func gatherContainerStats(
|
||||
gatherBlockIOMetrics(stat, acc, tags, now)
|
||||
}
|
||||
|
||||
func calculateMemPercent(stat *docker.Stats) float64 {
|
||||
func calculateMemPercent(stat *types.StatsJSON) float64 {
|
||||
var memPercent = 0.0
|
||||
if stat.MemoryStats.Limit > 0 {
|
||||
memPercent = float64(stat.MemoryStats.Usage) / float64(stat.MemoryStats.Limit) * 100.0
|
||||
@@ -331,11 +323,11 @@ func calculateMemPercent(stat *docker.Stats) float64 {
|
||||
return memPercent
|
||||
}
|
||||
|
||||
func calculateCPUPercent(stat *docker.Stats) float64 {
|
||||
func calculateCPUPercent(stat *types.StatsJSON) float64 {
|
||||
var cpuPercent = 0.0
|
||||
// calculate the change for the cpu and system usage of the container in between readings
|
||||
cpuDelta := float64(stat.CPUStats.CPUUsage.TotalUsage) - float64(stat.PreCPUStats.CPUUsage.TotalUsage)
|
||||
systemDelta := float64(stat.CPUStats.SystemCPUUsage) - float64(stat.PreCPUStats.SystemCPUUsage)
|
||||
systemDelta := float64(stat.CPUStats.SystemUsage) - float64(stat.PreCPUStats.SystemUsage)
|
||||
|
||||
if systemDelta > 0.0 && cpuDelta > 0.0 {
|
||||
cpuPercent = (cpuDelta / systemDelta) * float64(len(stat.CPUStats.CPUUsage.PercpuUsage)) * 100.0
|
||||
@@ -344,7 +336,7 @@ func calculateCPUPercent(stat *docker.Stats) float64 {
|
||||
}
|
||||
|
||||
func gatherBlockIOMetrics(
|
||||
stat *docker.Stats,
|
||||
stat *types.StatsJSON,
|
||||
acc telegraf.Accumulator,
|
||||
tags map[string]string,
|
||||
now time.Time,
|
||||
@@ -353,7 +345,7 @@ func gatherBlockIOMetrics(
|
||||
// Make a map of devices to their block io stats
|
||||
deviceStatMap := make(map[string]map[string]interface{})
|
||||
|
||||
for _, metric := range blkioStats.IOServiceBytesRecursive {
|
||||
for _, metric := range blkioStats.IoServiceBytesRecursive {
|
||||
device := fmt.Sprintf("%d:%d", metric.Major, metric.Minor)
|
||||
_, ok := deviceStatMap[device]
|
||||
if !ok {
|
||||
@@ -364,7 +356,7 @@ func gatherBlockIOMetrics(
|
||||
deviceStatMap[device][field] = metric.Value
|
||||
}
|
||||
|
||||
for _, metric := range blkioStats.IOServicedRecursive {
|
||||
for _, metric := range blkioStats.IoServicedRecursive {
|
||||
device := fmt.Sprintf("%d:%d", metric.Major, metric.Minor)
|
||||
_, ok := deviceStatMap[device]
|
||||
if !ok {
|
||||
@@ -375,40 +367,38 @@ func gatherBlockIOMetrics(
|
||||
deviceStatMap[device][field] = metric.Value
|
||||
}
|
||||
|
||||
for _, metric := range blkioStats.IOQueueRecursive {
|
||||
for _, metric := range blkioStats.IoQueuedRecursive {
|
||||
device := fmt.Sprintf("%d:%d", metric.Major, metric.Minor)
|
||||
field := fmt.Sprintf("io_queue_recursive_%s", strings.ToLower(metric.Op))
|
||||
deviceStatMap[device][field] = metric.Value
|
||||
}
|
||||
|
||||
for _, metric := range blkioStats.IOServiceTimeRecursive {
|
||||
for _, metric := range blkioStats.IoServiceTimeRecursive {
|
||||
device := fmt.Sprintf("%d:%d", metric.Major, metric.Minor)
|
||||
field := fmt.Sprintf("io_service_time_recursive_%s", strings.ToLower(metric.Op))
|
||||
deviceStatMap[device][field] = metric.Value
|
||||
}
|
||||
|
||||
for _, metric := range blkioStats.IOWaitTimeRecursive {
|
||||
for _, metric := range blkioStats.IoWaitTimeRecursive {
|
||||
device := fmt.Sprintf("%d:%d", metric.Major, metric.Minor)
|
||||
field := fmt.Sprintf("io_wait_time_%s", strings.ToLower(metric.Op))
|
||||
deviceStatMap[device][field] = metric.Value
|
||||
}
|
||||
|
||||
for _, metric := range blkioStats.IOMergedRecursive {
|
||||
for _, metric := range blkioStats.IoMergedRecursive {
|
||||
device := fmt.Sprintf("%d:%d", metric.Major, metric.Minor)
|
||||
field := fmt.Sprintf("io_merged_recursive_%s", strings.ToLower(metric.Op))
|
||||
deviceStatMap[device][field] = metric.Value
|
||||
}
|
||||
|
||||
for _, metric := range blkioStats.IOTimeRecursive {
|
||||
for _, metric := range blkioStats.IoTimeRecursive {
|
||||
device := fmt.Sprintf("%d:%d", metric.Major, metric.Minor)
|
||||
field := fmt.Sprintf("io_time_recursive_%s", strings.ToLower(metric.Op))
|
||||
deviceStatMap[device][field] = metric.Value
|
||||
deviceStatMap[device]["io_time_recursive"] = metric.Value
|
||||
}
|
||||
|
||||
for _, metric := range blkioStats.SectorsRecursive {
|
||||
device := fmt.Sprintf("%d:%d", metric.Major, metric.Minor)
|
||||
field := fmt.Sprintf("sectors_recursive_%s", strings.ToLower(metric.Op))
|
||||
deviceStatMap[device][field] = metric.Value
|
||||
deviceStatMap[device]["sectors_recursive"] = metric.Value
|
||||
}
|
||||
|
||||
for device, fields := range deviceStatMap {
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/registry"
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -114,58 +119,58 @@ func TestDockerGatherContainerStats(t *testing.T) {
|
||||
acc.AssertContainsTaggedFields(t, "docker_cpu", cpu1fields, cputags)
|
||||
}
|
||||
|
||||
func testStats() *docker.Stats {
|
||||
stats := &docker.Stats{
|
||||
Read: time.Now(),
|
||||
Networks: make(map[string]docker.NetworkStats),
|
||||
}
|
||||
func testStats() *types.StatsJSON {
|
||||
stats := &types.StatsJSON{}
|
||||
stats.Read = time.Now()
|
||||
stats.Networks = make(map[string]types.NetworkStats)
|
||||
|
||||
stats.CPUStats.CPUUsage.PercpuUsage = []uint64{1, 1002}
|
||||
stats.CPUStats.CPUUsage.UsageInUsermode = 100
|
||||
stats.CPUStats.CPUUsage.TotalUsage = 500
|
||||
stats.CPUStats.CPUUsage.UsageInKernelmode = 200
|
||||
stats.CPUStats.SystemCPUUsage = 100
|
||||
stats.CPUStats.SystemUsage = 100
|
||||
stats.CPUStats.ThrottlingData.Periods = 1
|
||||
|
||||
stats.PreCPUStats.CPUUsage.TotalUsage = 400
|
||||
stats.PreCPUStats.SystemCPUUsage = 50
|
||||
stats.PreCPUStats.SystemUsage = 50
|
||||
|
||||
stats.MemoryStats.Stats.TotalPgmafault = 0
|
||||
stats.MemoryStats.Stats.Cache = 0
|
||||
stats.MemoryStats.Stats.MappedFile = 0
|
||||
stats.MemoryStats.Stats.TotalInactiveFile = 0
|
||||
stats.MemoryStats.Stats.Pgpgout = 0
|
||||
stats.MemoryStats.Stats.Rss = 0
|
||||
stats.MemoryStats.Stats.TotalMappedFile = 0
|
||||
stats.MemoryStats.Stats.Writeback = 0
|
||||
stats.MemoryStats.Stats.Unevictable = 0
|
||||
stats.MemoryStats.Stats.Pgpgin = 0
|
||||
stats.MemoryStats.Stats.TotalUnevictable = 0
|
||||
stats.MemoryStats.Stats.Pgmajfault = 0
|
||||
stats.MemoryStats.Stats.TotalRss = 44
|
||||
stats.MemoryStats.Stats.TotalRssHuge = 444
|
||||
stats.MemoryStats.Stats.TotalWriteback = 55
|
||||
stats.MemoryStats.Stats.TotalInactiveAnon = 0
|
||||
stats.MemoryStats.Stats.RssHuge = 0
|
||||
stats.MemoryStats.Stats.HierarchicalMemoryLimit = 0
|
||||
stats.MemoryStats.Stats.TotalPgfault = 0
|
||||
stats.MemoryStats.Stats.TotalActiveFile = 0
|
||||
stats.MemoryStats.Stats.ActiveAnon = 0
|
||||
stats.MemoryStats.Stats.TotalActiveAnon = 0
|
||||
stats.MemoryStats.Stats.TotalPgpgout = 0
|
||||
stats.MemoryStats.Stats.TotalCache = 0
|
||||
stats.MemoryStats.Stats.InactiveAnon = 0
|
||||
stats.MemoryStats.Stats.ActiveFile = 1
|
||||
stats.MemoryStats.Stats.Pgfault = 2
|
||||
stats.MemoryStats.Stats.InactiveFile = 3
|
||||
stats.MemoryStats.Stats.TotalPgpgin = 4
|
||||
stats.MemoryStats.Stats = make(map[string]uint64)
|
||||
stats.MemoryStats.Stats["total_pgmajfault"] = 0
|
||||
stats.MemoryStats.Stats["cache"] = 0
|
||||
stats.MemoryStats.Stats["mapped_file"] = 0
|
||||
stats.MemoryStats.Stats["total_inactive_file"] = 0
|
||||
stats.MemoryStats.Stats["pagpgout"] = 0
|
||||
stats.MemoryStats.Stats["rss"] = 0
|
||||
stats.MemoryStats.Stats["total_mapped_file"] = 0
|
||||
stats.MemoryStats.Stats["writeback"] = 0
|
||||
stats.MemoryStats.Stats["unevictable"] = 0
|
||||
stats.MemoryStats.Stats["pgpgin"] = 0
|
||||
stats.MemoryStats.Stats["total_unevictable"] = 0
|
||||
stats.MemoryStats.Stats["pgmajfault"] = 0
|
||||
stats.MemoryStats.Stats["total_rss"] = 44
|
||||
stats.MemoryStats.Stats["total_rss_huge"] = 444
|
||||
stats.MemoryStats.Stats["total_write_back"] = 55
|
||||
stats.MemoryStats.Stats["total_inactive_anon"] = 0
|
||||
stats.MemoryStats.Stats["rss_huge"] = 0
|
||||
stats.MemoryStats.Stats["hierarchical_memory_limit"] = 0
|
||||
stats.MemoryStats.Stats["total_pgfault"] = 0
|
||||
stats.MemoryStats.Stats["total_active_file"] = 0
|
||||
stats.MemoryStats.Stats["active_anon"] = 0
|
||||
stats.MemoryStats.Stats["total_active_anon"] = 0
|
||||
stats.MemoryStats.Stats["total_pgpgout"] = 0
|
||||
stats.MemoryStats.Stats["total_cache"] = 0
|
||||
stats.MemoryStats.Stats["inactive_anon"] = 0
|
||||
stats.MemoryStats.Stats["active_file"] = 1
|
||||
stats.MemoryStats.Stats["pgfault"] = 2
|
||||
stats.MemoryStats.Stats["inactive_file"] = 3
|
||||
stats.MemoryStats.Stats["total_pgpgin"] = 4
|
||||
|
||||
stats.MemoryStats.MaxUsage = 1001
|
||||
stats.MemoryStats.Usage = 1111
|
||||
stats.MemoryStats.Failcnt = 1
|
||||
stats.MemoryStats.Limit = 2000
|
||||
|
||||
stats.Networks["eth0"] = docker.NetworkStats{
|
||||
stats.Networks["eth0"] = types.NetworkStats{
|
||||
RxDropped: 1,
|
||||
RxBytes: 2,
|
||||
RxErrors: 3,
|
||||
@@ -176,23 +181,23 @@ func testStats() *docker.Stats {
|
||||
TxBytes: 4,
|
||||
}
|
||||
|
||||
sbr := docker.BlkioStatsEntry{
|
||||
sbr := types.BlkioStatEntry{
|
||||
Major: 6,
|
||||
Minor: 0,
|
||||
Op: "read",
|
||||
Value: 100,
|
||||
}
|
||||
sr := docker.BlkioStatsEntry{
|
||||
sr := types.BlkioStatEntry{
|
||||
Major: 6,
|
||||
Minor: 0,
|
||||
Op: "write",
|
||||
Value: 101,
|
||||
}
|
||||
|
||||
stats.BlkioStats.IOServiceBytesRecursive = append(
|
||||
stats.BlkioStats.IOServiceBytesRecursive, sbr)
|
||||
stats.BlkioStats.IOServicedRecursive = append(
|
||||
stats.BlkioStats.IOServicedRecursive, sr)
|
||||
stats.BlkioStats.IoServiceBytesRecursive = append(
|
||||
stats.BlkioStats.IoServiceBytesRecursive, sbr)
|
||||
stats.BlkioStats.IoServicedRecursive = append(
|
||||
stats.BlkioStats.IoServicedRecursive, sr)
|
||||
|
||||
return stats
|
||||
}
|
||||
@@ -200,35 +205,78 @@ func testStats() *docker.Stats {
|
||||
type FakeDockerClient struct {
|
||||
}
|
||||
|
||||
func (d FakeDockerClient) Info() (*docker.Env, error) {
|
||||
env := docker.Env{"Containers=108", "OomKillDisable=false", "SystemTime=2016-02-24T00:55:09.15073105-05:00", "NEventsListener=0", "ID=5WQQ:TFWR:FDNG:OKQ3:37Y4:FJWG:QIKK:623T:R3ME:QTKB:A7F7:OLHD", "Debug=false", "LoggingDriver=json-file", "KernelVersion=4.3.0-1-amd64", "IndexServerAddress=https://index.docker.io/v1/", "MemTotal=3840757760", "Images=199", "CpuCfsQuota=true", "Name=absol", "SwapLimit=false", "IPv4Forwarding=true", "ExecutionDriver=native-0.2", "InitSha1=23a51f3c916d2b5a3bbb31caf301fd2d14edd518", "ExperimentalBuild=false", "CpuCfsPeriod=true", "RegistryConfig={\"IndexConfigs\":{\"docker.io\":{\"Mirrors\":null,\"Name\":\"docker.io\",\"Official\":true,\"Secure\":true}},\"InsecureRegistryCIDRs\":[\"127.0.0.0/8\"],\"Mirrors\":null}", "OperatingSystem=Linux Mint LMDE (containerized)", "BridgeNfIptables=true", "HttpsProxy=", "Labels=null", "MemoryLimit=false", "DriverStatus=[[\"Pool Name\",\"docker-8:1-1182287-pool\"],[\"Pool Blocksize\",\"65.54 kB\"],[\"Backing Filesystem\",\"extfs\"],[\"Data file\",\"/dev/loop0\"],[\"Metadata file\",\"/dev/loop1\"],[\"Data Space Used\",\"17.3 GB\"],[\"Data Space Total\",\"107.4 GB\"],[\"Data Space Available\",\"36.53 GB\"],[\"Metadata Space Used\",\"20.97 MB\"],[\"Metadata Space Total\",\"2.147 GB\"],[\"Metadata Space Available\",\"2.127 GB\"],[\"Udev Sync Supported\",\"true\"],[\"Deferred Removal Enabled\",\"false\"],[\"Data loop file\",\"/var/lib/docker/devicemapper/devicemapper/data\"],[\"Metadata loop file\",\"/var/lib/docker/devicemapper/devicemapper/metadata\"],[\"Library Version\",\"1.02.115 (2016-01-25)\"]]", "NFd=19", "HttpProxy=", "Driver=devicemapper", "NGoroutines=39", "InitPath=/usr/lib/docker.io/dockerinit", "NCPU=4", "DockerRootDir=/var/lib/docker", "NoProxy=", "BridgeNfIp6tables=true"}
|
||||
return &env, nil
|
||||
func (d FakeDockerClient) Info(ctx context.Context) (types.Info, error) {
|
||||
env := types.Info{
|
||||
Containers: 108,
|
||||
OomKillDisable: false,
|
||||
SystemTime: "2016-02-24T00:55:09.15073105-05:00",
|
||||
NEventsListener: 0,
|
||||
ID: "5WQQ:TFWR:FDNG:OKQ3:37Y4:FJWG:QIKK:623T:R3ME:QTKB:A7F7:OLHD",
|
||||
Debug: false,
|
||||
LoggingDriver: "json-file",
|
||||
KernelVersion: "4.3.0-1-amd64",
|
||||
IndexServerAddress: "https://index.docker.io/v1/",
|
||||
MemTotal: 3840757760,
|
||||
Images: 199,
|
||||
CPUCfsQuota: true,
|
||||
Name: "absol",
|
||||
SwapLimit: false,
|
||||
IPv4Forwarding: true,
|
||||
ExecutionDriver: "native-0.2",
|
||||
ExperimentalBuild: false,
|
||||
CPUCfsPeriod: true,
|
||||
RegistryConfig: ®istry.ServiceConfig{
|
||||
IndexConfigs: map[string]*registry.IndexInfo{
|
||||
"docker.io": {
|
||||
Name: "docker.io",
|
||||
Mirrors: []string{},
|
||||
Official: true,
|
||||
Secure: true,
|
||||
},
|
||||
}, InsecureRegistryCIDRs: []*registry.NetIPNet{{IP: []byte{127, 0, 0, 0}, Mask: []byte{255, 0, 0, 0}}}, Mirrors: []string{}},
|
||||
OperatingSystem: "Linux Mint LMDE (containerized)",
|
||||
BridgeNfIptables: true,
|
||||
HTTPSProxy: "",
|
||||
Labels: []string{},
|
||||
MemoryLimit: false,
|
||||
DriverStatus: [][2]string{{"Pool Name", "docker-8:1-1182287-pool"}, {"Pool Blocksize", "65.54 kB"}, {"Backing Filesystem", "extfs"}, {"Data file", "/dev/loop0"}, {"Metadata file", "/dev/loop1"}, {"Data Space Used", "17.3 GB"}, {"Data Space Total", "107.4 GB"}, {"Data Space Available", "36.53 GB"}, {"Metadata Space Used", "20.97 MB"}, {"Metadata Space Total", "2.147 GB"}, {"Metadata Space Available", "2.127 GB"}, {"Udev Sync Supported", "true"}, {"Deferred Removal Enabled", "false"}, {"Data loop file", "/var/lib/docker/devicemapper/devicemapper/data"}, {"Metadata loop file", "/var/lib/docker/devicemapper/devicemapper/metadata"}, {"Library Version", "1.02.115 (2016-01-25)"}},
|
||||
NFd: 19,
|
||||
HTTPProxy: "",
|
||||
Driver: "devicemapper",
|
||||
NGoroutines: 39,
|
||||
NCPU: 4,
|
||||
DockerRootDir: "/var/lib/docker",
|
||||
NoProxy: "",
|
||||
BridgeNfIP6tables: true,
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
||||
func (d FakeDockerClient) ListContainers(opts docker.ListContainersOptions) ([]docker.APIContainers, error) {
|
||||
container1 := docker.APIContainers{
|
||||
func (d FakeDockerClient) ContainerList(octx context.Context, options types.ContainerListOptions) ([]types.Container, error) {
|
||||
container1 := types.Container{
|
||||
ID: "e2173b9478a6ae55e237d4d74f8bbb753f0817192b5081334dc78476296b7dfb",
|
||||
Names: []string{"/etcd"},
|
||||
Image: "quay.io/coreos/etcd:v2.2.2",
|
||||
Command: "/etcd -name etcd0 -advertise-client-urls http://localhost:2379 -listen-client-urls http://0.0.0.0:2379",
|
||||
Created: 1455941930,
|
||||
Status: "Up 4 hours",
|
||||
Ports: []docker.APIPort{
|
||||
docker.APIPort{
|
||||
Ports: []types.Port{
|
||||
types.Port{
|
||||
PrivatePort: 7001,
|
||||
PublicPort: 0,
|
||||
Type: "tcp",
|
||||
},
|
||||
docker.APIPort{
|
||||
types.Port{
|
||||
PrivatePort: 4001,
|
||||
PublicPort: 0,
|
||||
Type: "tcp",
|
||||
},
|
||||
docker.APIPort{
|
||||
types.Port{
|
||||
PrivatePort: 2380,
|
||||
PublicPort: 0,
|
||||
Type: "tcp",
|
||||
},
|
||||
docker.APIPort{
|
||||
types.Port{
|
||||
PrivatePort: 2379,
|
||||
PublicPort: 2379,
|
||||
Type: "tcp",
|
||||
@@ -237,31 +285,31 @@ func (d FakeDockerClient) ListContainers(opts docker.ListContainersOptions) ([]d
|
||||
},
|
||||
SizeRw: 0,
|
||||
SizeRootFs: 0,
|
||||
Names: []string{"/etcd"},
|
||||
}
|
||||
container2 := docker.APIContainers{
|
||||
container2 := types.Container{
|
||||
ID: "b7dfbb9478a6ae55e237d4d74f8bbb753f0817192b5081334dc78476296e2173",
|
||||
Names: []string{"/etcd2"},
|
||||
Image: "quay.io/coreos/etcd:v2.2.2",
|
||||
Command: "/etcd -name etcd2 -advertise-client-urls http://localhost:2379 -listen-client-urls http://0.0.0.0:2379",
|
||||
Created: 1455941933,
|
||||
Status: "Up 4 hours",
|
||||
Ports: []docker.APIPort{
|
||||
docker.APIPort{
|
||||
Ports: []types.Port{
|
||||
types.Port{
|
||||
PrivatePort: 7002,
|
||||
PublicPort: 0,
|
||||
Type: "tcp",
|
||||
},
|
||||
docker.APIPort{
|
||||
types.Port{
|
||||
PrivatePort: 4002,
|
||||
PublicPort: 0,
|
||||
Type: "tcp",
|
||||
},
|
||||
docker.APIPort{
|
||||
types.Port{
|
||||
PrivatePort: 2381,
|
||||
PublicPort: 0,
|
||||
Type: "tcp",
|
||||
},
|
||||
docker.APIPort{
|
||||
types.Port{
|
||||
PrivatePort: 2382,
|
||||
PublicPort: 2382,
|
||||
Type: "tcp",
|
||||
@@ -270,21 +318,19 @@ func (d FakeDockerClient) ListContainers(opts docker.ListContainersOptions) ([]d
|
||||
},
|
||||
SizeRw: 0,
|
||||
SizeRootFs: 0,
|
||||
Names: []string{"/etcd2"},
|
||||
}
|
||||
|
||||
containers := []docker.APIContainers{container1, container2}
|
||||
containers := []types.Container{container1, container2}
|
||||
return containers, nil
|
||||
|
||||
//#{e6a96c84ca91a5258b7cb752579fb68826b68b49ff957487695cd4d13c343b44 titilambert/snmpsim /bin/sh -c 'snmpsimd --agent-udpv4-endpoint=0.0.0.0:31161 --process-user=root --process-group=user' 1455724831 Up 4 hours [{31161 31161 udp 0.0.0.0}] 0 0 [/snmp] map[]}]2016/02/24 01:05:01 Gathered metrics, (3s interval), from 1 inputs in 1.233836656s
|
||||
}
|
||||
|
||||
func (d FakeDockerClient) Stats(opts docker.StatsOptions) error {
|
||||
func (d FakeDockerClient) ContainerStats(ctx context.Context, containerID string, stream bool) (io.ReadCloser, error) {
|
||||
var stat io.ReadCloser
|
||||
jsonStat := `{"read":"2016-02-24T11:42:27.472459608-05:00","memory_stats":{"stats":{},"limit":18935443456},"blkio_stats":{"io_service_bytes_recursive":[{"major":252,"minor":1,"op":"Read","value":753664},{"major":252,"minor":1,"op":"Write"},{"major":252,"minor":1,"op":"Sync"},{"major":252,"minor":1,"op":"Async","value":753664},{"major":252,"minor":1,"op":"Total","value":753664}],"io_serviced_recursive":[{"major":252,"minor":1,"op":"Read","value":26},{"major":252,"minor":1,"op":"Write"},{"major":252,"minor":1,"op":"Sync"},{"major":252,"minor":1,"op":"Async","value":26},{"major":252,"minor":1,"op":"Total","value":26}]},"cpu_stats":{"cpu_usage":{"percpu_usage":[17871,4959158,1646137,1231652,11829401,244656,369972,0],"usage_in_usermode":10000000,"total_usage":20298847},"system_cpu_usage":24052607520000000,"throttling_data":{}},"precpu_stats":{"cpu_usage":{"percpu_usage":[17871,4959158,1646137,1231652,11829401,244656,369972,0],"usage_in_usermode":10000000,"total_usage":20298847},"system_cpu_usage":24052599550000000,"throttling_data":{}}}`
|
||||
var stat docker.Stats
|
||||
json.Unmarshal([]byte(jsonStat), &stat)
|
||||
opts.Stats <- &stat
|
||||
return nil
|
||||
stat = ioutil.NopCloser(strings.NewReader(jsonStat))
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
func TestDockerGatherInfo(t *testing.T) {
|
||||
@@ -299,12 +345,12 @@ func TestDockerGatherInfo(t *testing.T) {
|
||||
acc.AssertContainsTaggedFields(t,
|
||||
"docker",
|
||||
map[string]interface{}{
|
||||
"n_listener_events": int64(0),
|
||||
"n_cpus": int64(4),
|
||||
"n_used_file_descriptors": int64(19),
|
||||
"n_containers": int64(108),
|
||||
"n_images": int64(199),
|
||||
"n_goroutines": int64(39),
|
||||
"n_listener_events": int(0),
|
||||
"n_cpus": int(4),
|
||||
"n_used_file_descriptors": int(19),
|
||||
"n_containers": int(108),
|
||||
"n_images": int(199),
|
||||
"n_goroutines": int(39),
|
||||
},
|
||||
map[string]string{},
|
||||
)
|
||||
|
||||
@@ -10,20 +10,25 @@ domains. You can read Dovecot's documentation
|
||||
```
|
||||
# Read metrics about dovecot servers
|
||||
[[inputs.dovecot]]
|
||||
# Dovecot servers
|
||||
# specify dovecot servers via an address:port list
|
||||
# e.g.
|
||||
# localhost:24242
|
||||
#
|
||||
# If no servers are specified, then localhost is used as the host.
|
||||
## specify dovecot servers via an address:port list
|
||||
## e.g.
|
||||
## localhost:24242
|
||||
##
|
||||
## If no servers are specified, then localhost is used as the host.
|
||||
servers = ["localhost:24242"]
|
||||
# Only collect metrics for these domains, collect all if empty
|
||||
domains = []
|
||||
## Type is one of "user", "domain", "ip", or "global"
|
||||
type = "global"
|
||||
## Wildcard matches like "*.com". An empty string "" is same as "*"
|
||||
## If type = "ip" filters should be <IP/network>
|
||||
filters = [""]
|
||||
```
|
||||
|
||||
|
||||
### Tags:
|
||||
server: hostname
|
||||
type: query type
|
||||
ip: ip addr
|
||||
user: username
|
||||
domain: domain name
|
||||
|
||||
|
||||
@@ -33,7 +38,7 @@ domains. You can read Dovecot's documentation
|
||||
last_update time.Time
|
||||
num_logins int64
|
||||
num_cmds int64
|
||||
num_connected_sessions int64
|
||||
num_connected_sessions int64 ## not in <user> type
|
||||
user_cpu float32
|
||||
sys_cpu float32
|
||||
clock_time float64
|
||||
@@ -57,11 +62,13 @@ domains. You can read Dovecot's documentation
|
||||
### Example Output:
|
||||
|
||||
```
|
||||
telegraf -config telegraf.cfg -input-filter dovecot -test
|
||||
telegraf -config t.cfg -input-filter dovecot -test
|
||||
* Plugin: dovecot, Collection 1
|
||||
> dovecot,domain=xxxxx.it,server=dovecot--1.mail.sys clock_time=12105746411632.5,disk_input=115285225472i,disk_output=4885067755520i,invol_cs=169701886i,last_update="2016-02-09 08:49:47.000014113 +0100 CET",mail_cache_hits=441828i,mail_lookup_attr=0i,mail_lookup_path=25323i,mail_read_bytes=241188145i,mail_read_count=11719i,maj_faults=3168i,min_faults=321438988i,num_cmds=51635i,num_connected_sessions=2i,num_logins=17149i,read_bytes=7939026951110i,read_count=3716991752i,reset_timestamp="2016-01-28 09:34:36 +0100 CET",sys_cpu=222595.288,user_cpu=267468.08,vol_cs=3288715920i,write_bytes=4483648967059i,write_count=1640646952i 1455004219924838345
|
||||
> dovecot,domain=yyyyy.com,server=dovecot-1.mail.sys clock_time=6650794455331782,disk_input=61957695569920i,disk_output=2638244004487168i,invol_cs=2004805041i,last_update="2016-02-09 08:49:49.000251296 +0100 CET",mail_cache_hits=2499112513i,mail_lookup_attr=506730i,mail_lookup_path=39128227i,mail_read_bytes=1076496874501i,mail_read_count=32615262i,maj_faults=1643304i,min_faults=4216116325i,num_cmds=85785559i,num_connected_sessions=1177i,num_logins=11658255i,read_bytes=4289150974554145i,read_count=1112000703i,reset_timestamp="2016-01-28 09:31:26 +0100 CET",sys_cpu=121125923.032,user_cpu=145561336.428,vol_cs=205451885i,write_bytes=2420130526835796i,write_count=2991367252i 1455004219925152529
|
||||
> dovecot,domain=xxxxx.it,server=dovecot-2.mail.sys clock_time=10710826586999.143,disk_input=79792410624i,disk_output=4496066158592i,invol_cs=150426876i,last_update="2016-02-09 08:48:19.000209134 +0100 CET",mail_cache_hits=5480869i,mail_lookup_attr=0i,mail_lookup_path=122563i,mail_read_bytes=340746273i,mail_read_count=44275i,maj_faults=1722i,min_faults=288071875i,num_cmds=50098i,num_connected_sessions=0i,num_logins=16389i,read_bytes=7259551999517i,read_count=3396625369i,reset_timestamp="2016-01-28 09:31:29 +0100 CET",sys_cpu=200762.792,user_cpu=242477.664,vol_cs=2996657358i,write_bytes=4133381575263i,write_count=1497242759i 1455004219924888283
|
||||
> dovecot,domain=yyyyy.com,server=dovecot-2.mail.sys clock_time=6522131245483702,disk_input=48259150004224i,disk_output=2754333359087616i,invol_cs=2294595260i,last_update="2016-02-09 08:49:49.000251919 +0100 CET",mail_cache_hits=2139113611i,mail_lookup_attr=520276i,mail_lookup_path=37940318i,mail_read_bytes=1088002215022i,mail_read_count=31350271i,maj_faults=994420i,min_faults=1486260543i,num_cmds=40414997i,num_connected_sessions=978i,num_logins=11259672i,read_bytes=4445546612487315i,read_count=1763534543i,reset_timestamp="2016-01-28 09:31:24 +0100 CET",sys_cpu=123655962.668,user_cpu=149259327.032,vol_cs=4215130546i,write_bytes=2531186030222761i,write_count=2186579650i 1455004219925398372
|
||||
```
|
||||
|
||||
> dovecot,ip=192.168.0.1,server=dovecot-1.domain.test,type=ip clock_time=0,disk_input=0i,disk_output=0i,invol_cs=0i,last_update="2016-04-08 10:59:47.000208479 +0200 CEST",mail_cache_hits=0i,mail_lookup_attr=0i,mail_lookup_path=0i,mail_read_bytes=0i,mail_read_count=0i,maj_faults=0i,min_faults=0i,num_cmds=12i,num_connected_sessions=0i,num_logins=6i,read_bytes=0i,read_count=0i,reset_timestamp="2016-04-08 10:33:34 +0200 CEST",sys_cpu=0,user_cpu=0,vol_cs=0i,write_bytes=0i,write_count=0i 1460106251633824223
|
||||
* Plugin: dovecot, Collection 1
|
||||
> dovecot,server=dovecot-1.domain.test,type=user,user=user-1@domain.test clock_time=0.00006,disk_input=405504i,disk_output=77824i,invol_cs=67i,last_update="2016-04-08 11:02:55.000111634 +0200 CEST",mail_cache_hits=26i,mail_lookup_attr=0i,mail_lookup_path=6i,mail_read_bytes=86233i,mail_read_count=5i,maj_faults=0i,min_faults=975i,num_cmds=41i,num_logins=3i,read_bytes=368833i,read_count=394i,reset_timestamp="2016-04-08 11:01:32 +0200 CEST",sys_cpu=0.008,user_cpu=0.004,vol_cs=323i,write_bytes=105086i,write_count=176i 1460106256637049167
|
||||
* Plugin: dovecot, Collection 1
|
||||
> dovecot,domain=domain.test,server=dovecot-1.domain.test,type=domain clock_time=100896189179847.7,disk_input=6467588263936i,disk_output=17933680439296i,invol_cs=1194808498i,last_update="2016-04-08 11:04:08.000377367 +0200 CEST",mail_cache_hits=46455781i,mail_lookup_attr=0i,mail_lookup_path=571490i,mail_read_bytes=79287033067i,mail_read_count=491243i,maj_faults=16992i,min_faults=1278442541i,num_cmds=606005i,num_connected_sessions=6597i,num_logins=166381i,read_bytes=30231409780721i,read_count=1624912080i,reset_timestamp="2016-04-08 10:28:45 +0200 CEST",sys_cpu=156440.372,user_cpu=216676.476,vol_cs=2749291157i,write_bytes=17097106707594i,write_count=944448998i 1460106261639672622
|
||||
* Plugin: dovecot, Collection 1
|
||||
> dovecot,server=dovecot-1.domain.test,type=global clock_time=101196971074203.94,disk_input=6493168218112i,disk_output=17978638815232i,invol_cs=1198855447i,last_update="2016-04-08 11:04:13.000379245 +0200 CEST",mail_cache_hits=68192209i,mail_lookup_attr=0i,mail_lookup_path=653861i,mail_read_bytes=86705151847i,mail_read_count=566125i,maj_faults=17208i,min_faults=1286179702i,num_cmds=917469i,num_connected_sessions=8896i,num_logins=174827i,read_bytes=30327690466186i,read_count=1772396430i,reset_timestamp="2016-04-08 10:28:45 +0200 CEST",sys_cpu=157965.692,user_cpu=219337.48,vol_cs=2827615787i,write_bytes=17150837661940i,write_count=992653220i 1460106266642153907
|
||||
```
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
// "log"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -15,8 +16,9 @@ import (
|
||||
)
|
||||
|
||||
type Dovecot struct {
|
||||
Type string
|
||||
Filters []string
|
||||
Servers []string
|
||||
Domains []string
|
||||
}
|
||||
|
||||
func (d *Dovecot) Description() string {
|
||||
@@ -30,12 +32,19 @@ var sampleConfig = `
|
||||
##
|
||||
## If no servers are specified, then localhost is used as the host.
|
||||
servers = ["localhost:24242"]
|
||||
## Only collect metrics for these domains, collect all if empty
|
||||
domains = []
|
||||
## Type is one of "user", "domain", "ip", or "global"
|
||||
type = "global"
|
||||
## Wildcard matches like "*.com". An empty string "" is same as "*"
|
||||
## If type = "ip" filters should be <IP/network>
|
||||
filters = [""]
|
||||
`
|
||||
|
||||
var defaultTimeout = time.Second * time.Duration(5)
|
||||
|
||||
var validQuery = map[string]bool{
|
||||
"user": true, "domain": true, "global": true, "ip": true,
|
||||
}
|
||||
|
||||
func (d *Dovecot) SampleConfig() string { return sampleConfig }
|
||||
|
||||
const defaultPort = "24242"
|
||||
@@ -43,6 +52,11 @@ const defaultPort = "24242"
|
||||
// Reads stats from all configured servers.
|
||||
func (d *Dovecot) Gather(acc telegraf.Accumulator) error {
|
||||
|
||||
if !validQuery[d.Type] {
|
||||
return fmt.Errorf("Error: %s is not a valid query type\n",
|
||||
d.Type)
|
||||
}
|
||||
|
||||
if len(d.Servers) == 0 {
|
||||
d.Servers = append(d.Servers, "127.0.0.1:24242")
|
||||
}
|
||||
@@ -51,18 +65,18 @@ func (d *Dovecot) Gather(acc telegraf.Accumulator) error {
|
||||
|
||||
var outerr error
|
||||
|
||||
var domains = make(map[string]bool)
|
||||
|
||||
for _, dom := range d.Domains {
|
||||
domains[dom] = true
|
||||
if len(d.Filters) <= 0 {
|
||||
d.Filters = append(d.Filters, "")
|
||||
}
|
||||
|
||||
for _, serv := range d.Servers {
|
||||
wg.Add(1)
|
||||
go func(serv string) {
|
||||
defer wg.Done()
|
||||
outerr = d.gatherServer(serv, acc, domains)
|
||||
}(serv)
|
||||
for _, filter := range d.Filters {
|
||||
wg.Add(1)
|
||||
go func(serv string, filter string) {
|
||||
defer wg.Done()
|
||||
outerr = d.gatherServer(serv, acc, d.Type, filter)
|
||||
}(serv, filter)
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
@@ -70,7 +84,8 @@ func (d *Dovecot) Gather(acc telegraf.Accumulator) error {
|
||||
return outerr
|
||||
}
|
||||
|
||||
func (d *Dovecot) gatherServer(addr string, acc telegraf.Accumulator, doms map[string]bool) error {
|
||||
func (d *Dovecot) gatherServer(addr string, acc telegraf.Accumulator, qtype string, filter string) error {
|
||||
|
||||
_, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error: %s on url %s\n", err, addr)
|
||||
@@ -85,17 +100,22 @@ func (d *Dovecot) gatherServer(addr string, acc telegraf.Accumulator, doms map[s
|
||||
// Extend connection
|
||||
c.SetDeadline(time.Now().Add(defaultTimeout))
|
||||
|
||||
c.Write([]byte("EXPORT\tdomain\n\n"))
|
||||
msg := fmt.Sprintf("EXPORT\t%s", qtype)
|
||||
if len(filter) > 0 {
|
||||
msg += fmt.Sprintf("\t%s=%s", qtype, filter)
|
||||
}
|
||||
msg += "\n"
|
||||
|
||||
c.Write([]byte(msg))
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, c)
|
||||
// buf := bufio.NewReader(c)
|
||||
|
||||
host, _, _ := net.SplitHostPort(addr)
|
||||
|
||||
return gatherStats(&buf, acc, doms, host)
|
||||
return gatherStats(&buf, acc, host, qtype)
|
||||
}
|
||||
|
||||
func gatherStats(buf *bytes.Buffer, acc telegraf.Accumulator, doms map[string]bool, host string) error {
|
||||
func gatherStats(buf *bytes.Buffer, acc telegraf.Accumulator, host string, qtype string) error {
|
||||
|
||||
lines := strings.Split(buf.String(), "\n")
|
||||
head := strings.Split(lines[0], "\t")
|
||||
@@ -106,16 +126,18 @@ func gatherStats(buf *bytes.Buffer, acc telegraf.Accumulator, doms map[string]bo
|
||||
continue
|
||||
}
|
||||
val := strings.Split(vals[i], "\t")
|
||||
|
||||
fields := make(map[string]interface{})
|
||||
if len(doms) > 0 && !doms[val[0]] {
|
||||
continue
|
||||
tags := map[string]string{"server": host, "type": qtype}
|
||||
|
||||
if qtype != "global" {
|
||||
tags[qtype] = val[0]
|
||||
}
|
||||
tags := map[string]string{"server": host, "domain": val[0]}
|
||||
|
||||
for n := range val {
|
||||
switch head[n] {
|
||||
case "domain":
|
||||
case qtype:
|
||||
continue
|
||||
// fields[head[n]] = val[n]
|
||||
case "user_cpu", "sys_cpu", "clock_time":
|
||||
fields[head[n]] = secParser(val[n])
|
||||
case "reset_timestamp", "last_update":
|
||||
|
||||
@@ -15,17 +15,6 @@ func TestDovecot(t *testing.T) {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
tags := map[string]string{"server": "dovecot.test", "domain": "domain.test"}
|
||||
buf := bytes.NewBufferString(sampleStats)
|
||||
|
||||
var doms = map[string]bool{
|
||||
"domain.test": true,
|
||||
}
|
||||
|
||||
err := gatherStats(buf, &acc, doms, "dovecot.test")
|
||||
require.NoError(t, err)
|
||||
|
||||
fields := map[string]interface{}{
|
||||
"reset_timestamp": time.Unix(1453969886, 0),
|
||||
"last_update": time.Unix(1454603963, 39864),
|
||||
@@ -52,10 +41,79 @@ func TestDovecot(t *testing.T) {
|
||||
"mail_cache_hits": int64(1557255080),
|
||||
}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
|
||||
// Test type=global
|
||||
tags := map[string]string{"server": "dovecot.test", "type": "global"}
|
||||
buf := bytes.NewBufferString(sampleGlobal)
|
||||
|
||||
err := gatherStats(buf, &acc, "dovecot.test", "global")
|
||||
require.NoError(t, err)
|
||||
|
||||
acc.AssertContainsTaggedFields(t, "dovecot", fields, tags)
|
||||
|
||||
// Test type=domain
|
||||
tags = map[string]string{"server": "dovecot.test", "type": "domain", "domain": "domain.test"}
|
||||
buf = bytes.NewBufferString(sampleDomain)
|
||||
|
||||
err = gatherStats(buf, &acc, "dovecot.test", "domain")
|
||||
require.NoError(t, err)
|
||||
|
||||
acc.AssertContainsTaggedFields(t, "dovecot", fields, tags)
|
||||
|
||||
// Test type=ip
|
||||
tags = map[string]string{"server": "dovecot.test", "type": "ip", "ip": "192.168.0.100"}
|
||||
buf = bytes.NewBufferString(sampleIp)
|
||||
|
||||
err = gatherStats(buf, &acc, "dovecot.test", "ip")
|
||||
require.NoError(t, err)
|
||||
|
||||
acc.AssertContainsTaggedFields(t, "dovecot", fields, tags)
|
||||
|
||||
// Test type=user
|
||||
fields = map[string]interface{}{
|
||||
"reset_timestamp": time.Unix(1453969886, 0),
|
||||
"last_update": time.Unix(1454603963, 39864),
|
||||
"num_logins": int64(7503897),
|
||||
"num_cmds": int64(52595715),
|
||||
"user_cpu": 1.00831175372e+08,
|
||||
"sys_cpu": 8.3849071112e+07,
|
||||
"clock_time": 4.3260019315281835e+15,
|
||||
"min_faults": int64(763950011),
|
||||
"maj_faults": int64(1112443),
|
||||
"vol_cs": int64(4120386897),
|
||||
"invol_cs": int64(3685239306),
|
||||
"disk_input": int64(41679480946688),
|
||||
"disk_output": int64(1819070669176832),
|
||||
"read_count": int64(2368906465),
|
||||
"read_bytes": int64(2957928122981169),
|
||||
"write_count": int64(3545389615),
|
||||
"write_bytes": int64(1666822498251286),
|
||||
"mail_lookup_path": int64(24396105),
|
||||
"mail_lookup_attr": int64(302845),
|
||||
"mail_read_count": int64(20155768),
|
||||
"mail_read_bytes": int64(669946617705),
|
||||
"mail_cache_hits": int64(1557255080),
|
||||
}
|
||||
|
||||
tags = map[string]string{"server": "dovecot.test", "type": "user", "user": "user.1@domain.test"}
|
||||
buf = bytes.NewBufferString(sampleUser)
|
||||
|
||||
err = gatherStats(buf, &acc, "dovecot.test", "user")
|
||||
require.NoError(t, err)
|
||||
|
||||
acc.AssertContainsTaggedFields(t, "dovecot", fields, tags)
|
||||
|
||||
}
|
||||
|
||||
const sampleStats = `domain reset_timestamp last_update num_logins num_cmds num_connected_sessions user_cpu sys_cpu clock_time min_faults maj_faults vol_cs invol_cs disk_input disk_output read_count read_bytes write_count write_bytes mail_lookup_path mail_lookup_attr mail_read_count mail_read_bytes mail_cache_hits
|
||||
domain.bad 1453970076 1454603947.383029 10749 33828 0 177988.524000 148071.772000 7531838964717.193706 212491179 2125 2190386067 112779200 74487934976 3221808119808 2469948401 5237602841760 1091171292 2951966459802 15363 0 2922 136403379 334372
|
||||
const sampleGlobal = `reset_timestamp last_update num_logins num_cmds num_connected_sessions user_cpu sys_cpu clock_time min_faults maj_faults vol_cs invol_cs disk_input disk_output read_count read_bytes write_count write_bytes mail_lookup_path mail_lookup_attr mail_read_count mail_read_bytes mail_cache_hits
|
||||
1453969886 1454603963.039864 7503897 52595715 1204 100831175.372000 83849071.112000 4326001931528183.495762 763950011 1112443 4120386897 3685239306 41679480946688 1819070669176832 2368906465 2957928122981169 3545389615 1666822498251286 24396105 302845 20155768 669946617705 1557255080`
|
||||
|
||||
const sampleDomain = `domain reset_timestamp last_update num_logins num_cmds num_connected_sessions user_cpu sys_cpu clock_time min_faults maj_faults vol_cs invol_cs disk_input disk_output read_count read_bytes write_count write_bytes mail_lookup_path mail_lookup_attr mail_read_count mail_read_bytes mail_cache_hits
|
||||
domain.test 1453969886 1454603963.039864 7503897 52595715 1204 100831175.372000 83849071.112000 4326001931528183.495762 763950011 1112443 4120386897 3685239306 41679480946688 1819070669176832 2368906465 2957928122981169 3545389615 1666822498251286 24396105 302845 20155768 669946617705 1557255080`
|
||||
|
||||
const sampleIp = `ip reset_timestamp last_update num_logins num_cmds num_connected_sessions user_cpu sys_cpu clock_time min_faults maj_faults vol_cs invol_cs disk_input disk_output read_count read_bytes write_count write_bytes mail_lookup_path mail_lookup_attr mail_read_count mail_read_bytes mail_cache_hits
|
||||
192.168.0.100 1453969886 1454603963.039864 7503897 52595715 1204 100831175.372000 83849071.112000 4326001931528183.495762 763950011 1112443 4120386897 3685239306 41679480946688 1819070669176832 2368906465 2957928122981169 3545389615 1666822498251286 24396105 302845 20155768 669946617705 1557255080`
|
||||
|
||||
const sampleUser = `user reset_timestamp last_update num_logins num_cmds user_cpu sys_cpu clock_time min_faults maj_faults vol_cs invol_cs disk_input disk_output read_count read_bytes write_count write_bytes mail_lookup_path mail_lookup_attr mail_read_count mail_read_bytes mail_cache_hits
|
||||
user.1@domain.test 1453969886 1454603963.039864 7503897 52595715 100831175.372000 83849071.112000 4326001931528183.495762 763950011 1112443 4120386897 3685239306 41679480946688 1819070669176832 2368906465 2957928122981169 3545389615 1666822498251286 24396105 302845 20155768 669946617705 1557255080`
|
||||
|
||||
@@ -2,18 +2,11 @@
|
||||
|
||||
Please also see: [Telegraf Input Data Formats](https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md)
|
||||
|
||||
The exec input plugin can execute arbitrary commands which output:
|
||||
|
||||
* JSON [javascript object notation](http://www.json.org/)
|
||||
* InfluxDB [line-protocol](https://docs.influxdata.com/influxdb/v0.10/write_protocols/line/)
|
||||
* Graphite [graphite-protocol](http://graphite.readthedocs.org/en/latest/feeding-carbon.html)
|
||||
|
||||
|
||||
### Example 1 - JSON
|
||||
|
||||
#### Configuration
|
||||
|
||||
In this example a script called ```/tmp/test.sh``` and a script called ```/tmp/test2.sh```
|
||||
In this example a script called ```/tmp/test.sh``` and a script called ```/tmp/test2.sh```
|
||||
are configured for ```[[inputs.exec]]``` in JSON format.
|
||||
|
||||
```
|
||||
@@ -22,7 +15,7 @@ are configured for ```[[inputs.exec]]``` in JSON format.
|
||||
# Shell/commands array
|
||||
commands = ["/tmp/test.sh", "/tmp/test2.sh"]
|
||||
|
||||
# Data format to consume. This can be "json", "influx" or "graphite" (line-protocol)
|
||||
# Data format to consume.
|
||||
# NOTE json only reads numerical measurements, strings and booleans are ignored.
|
||||
data_format = "json"
|
||||
|
||||
@@ -81,7 +74,7 @@ and strings will be ignored.
|
||||
### Example 2 - Influx Line-Protocol
|
||||
|
||||
In this example an application called ```/usr/bin/line_protocol_collector```
|
||||
and a script called ```/tmp/test2.sh``` are configured for ```[[inputs.exec]]```
|
||||
and a script called ```/tmp/test2.sh``` are configured for ```[[inputs.exec]]```
|
||||
in influx line-protocol format.
|
||||
|
||||
#### Configuration
|
||||
@@ -94,7 +87,7 @@ in influx line-protocol format.
|
||||
# command = "/usr/bin/line_protocol_collector"
|
||||
commands = ["/usr/bin/line_protocol_collector","/tmp/test2.sh"]
|
||||
|
||||
# Data format to consume. This can be "json" or "influx" (line-protocol)
|
||||
# Data format to consume.
|
||||
# NOTE json only reads numerical measurements, strings and booleans are ignored.
|
||||
data_format = "influx"
|
||||
```
|
||||
@@ -113,7 +106,7 @@ cpu,cpu=cpu6,host=foo,datacenter=us-east usage_idle=99,usage_busy=1
|
||||
|
||||
You will get data in InfluxDB exactly as it is defined above,
|
||||
tags are cpu=cpuN, host=foo, and datacenter=us-east with fields usage_idle
|
||||
and usage_busy. They will receive a timestamp at collection time.
|
||||
and usage_busy. They will receive a timestamp at collection time.
|
||||
Each line must end in \n, just as the Influx line protocol does.
|
||||
|
||||
|
||||
@@ -121,8 +114,8 @@ Each line must end in \n, just as the Influx line protocol does.
|
||||
|
||||
We can also change the data_format to "graphite" to use the metrics collecting scripts such as (compatible with graphite):
|
||||
|
||||
* Nagios [Mertics Plugins] (https://exchange.nagios.org/directory/Plugins)
|
||||
* Sensu [Mertics Plugins] (https://github.com/sensu-plugins)
|
||||
* Nagios [Metrics Plugins](https://exchange.nagios.org/directory/Plugins)
|
||||
* Sensu [Metrics Plugins](https://github.com/sensu-plugins)
|
||||
|
||||
In this example a script called /tmp/test.sh and a script called /tmp/test2.sh are configured for [[inputs.exec]] in graphite format.
|
||||
|
||||
@@ -133,7 +126,7 @@ In this example a script called /tmp/test.sh and a script called /tmp/test2.sh a
|
||||
# Shell/commands array
|
||||
commands = ["/tmp/test.sh","/tmp/test2.sh"]
|
||||
|
||||
# Data format to consume. This can be "json", "influx" or "graphite" (line-protocol)
|
||||
# Data format to consume.
|
||||
# NOTE json only reads numerical measurements, strings and booleans are ignored.
|
||||
data_format = "graphite"
|
||||
|
||||
@@ -186,5 +179,5 @@ 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.
|
||||
|
||||
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)
|
||||
|
||||
|
||||
44
plugins/inputs/http_response/README.md
Normal file
44
plugins/inputs/http_response/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Example Input Plugin
|
||||
|
||||
This input plugin will test HTTP/HTTPS connections.
|
||||
|
||||
### Configuration:
|
||||
|
||||
```
|
||||
# List of UDP/TCP connections you want to check
|
||||
[[inputs.http_response]]
|
||||
## Server address (default http://localhost)
|
||||
address = "http://github.com"
|
||||
## Set response_timeout (default 5 seconds)
|
||||
response_timeout = 5
|
||||
## HTTP Request Method
|
||||
method = "GET"
|
||||
## HTTP Request Headers
|
||||
[inputs.http_response.headers]
|
||||
Host = github.com
|
||||
## Whether to follow redirects from the server (defaults to false)
|
||||
follow_redirects = true
|
||||
## Optional HTTP Request Body
|
||||
body = '''
|
||||
{'fake':'data'}
|
||||
'''
|
||||
```
|
||||
|
||||
### Measurements & Fields:
|
||||
|
||||
- http_response
|
||||
- response_time (float, seconds)
|
||||
- http_response_code (int) #The code received
|
||||
|
||||
### Tags:
|
||||
|
||||
- All measurements have the following tags:
|
||||
- server
|
||||
- method
|
||||
|
||||
### Example Output:
|
||||
|
||||
```
|
||||
$ ./telegraf -config telegraf.conf -input-filter http_response -test
|
||||
http_response,method=GET,server=http://www.github.com http_response_code=200i,response_time=6.223266528 1459419354977857955
|
||||
```
|
||||
154
plugins/inputs/http_response/http_response.go
Normal file
154
plugins/inputs/http_response/http_response.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package http_response
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
// HTTPResponse struct
|
||||
type HTTPResponse struct {
|
||||
Address string
|
||||
Body string
|
||||
Method string
|
||||
ResponseTimeout int
|
||||
Headers map[string]string
|
||||
FollowRedirects bool
|
||||
}
|
||||
|
||||
// Description returns the plugin Description
|
||||
func (h *HTTPResponse) Description() string {
|
||||
return "HTTP/HTTPS request given an address a method and a timeout"
|
||||
}
|
||||
|
||||
var sampleConfig = `
|
||||
## Server address (default http://localhost)
|
||||
address = "http://github.com"
|
||||
## Set response_timeout (default 5 seconds)
|
||||
response_timeout = 5
|
||||
## HTTP Request Method
|
||||
method = "GET"
|
||||
## Whether to follow redirects from the server (defaults to false)
|
||||
follow_redirects = true
|
||||
## HTTP Request Headers (all values must be strings)
|
||||
# [inputs.http_response.headers]
|
||||
# Host = "github.com"
|
||||
## Optional HTTP Request Body
|
||||
# body = '''
|
||||
# {'fake':'data'}
|
||||
# '''
|
||||
`
|
||||
|
||||
// SampleConfig returns the plugin SampleConfig
|
||||
func (h *HTTPResponse) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
// ErrRedirectAttempted indicates that a redirect occurred
|
||||
var ErrRedirectAttempted = errors.New("redirect")
|
||||
|
||||
// CreateHttpClient creates an http client which will timeout at the specified
|
||||
// timeout period and can follow redirects if specified
|
||||
func CreateHttpClient(followRedirects bool, ResponseTimeout time.Duration) *http.Client {
|
||||
client := &http.Client{
|
||||
Timeout: time.Second * ResponseTimeout,
|
||||
}
|
||||
|
||||
if followRedirects == false {
|
||||
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return ErrRedirectAttempted
|
||||
}
|
||||
}
|
||||
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
|
||||
func (h *HTTPResponse) HTTPGather() (map[string]interface{}, error) {
|
||||
// Prepare fields
|
||||
fields := make(map[string]interface{})
|
||||
|
||||
client := CreateHttpClient(h.FollowRedirects, time.Duration(h.ResponseTimeout))
|
||||
|
||||
var body io.Reader
|
||||
if h.Body != "" {
|
||||
body = strings.NewReader(h.Body)
|
||||
}
|
||||
request, err := http.NewRequest(h.Method, h.Address, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request.Header = CreateHeaders(h.Headers)
|
||||
|
||||
// Start Timer
|
||||
start := time.Now()
|
||||
resp, err := client.Do(request)
|
||||
if err != nil {
|
||||
if h.FollowRedirects {
|
||||
return nil, err
|
||||
}
|
||||
if urlError, ok := err.(*url.Error); ok &&
|
||||
urlError.Err == ErrRedirectAttempted {
|
||||
err = nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
fields["response_time"] = time.Since(start).Seconds()
|
||||
fields["http_response_code"] = resp.StatusCode
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
// Gather gets all metric fields and tags and returns any errors it encounters
|
||||
func (h *HTTPResponse) Gather(acc telegraf.Accumulator) error {
|
||||
// Set default values
|
||||
if h.ResponseTimeout < 1 {
|
||||
h.ResponseTimeout = 5
|
||||
}
|
||||
// Check send and expected string
|
||||
if h.Method == "" {
|
||||
h.Method = "GET"
|
||||
}
|
||||
if h.Address == "" {
|
||||
h.Address = "http://localhost"
|
||||
}
|
||||
addr, err := url.Parse(h.Address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if addr.Scheme != "http" && addr.Scheme != "https" {
|
||||
return errors.New("Only http and https are supported")
|
||||
}
|
||||
// Prepare data
|
||||
tags := map[string]string{"server": h.Address, "method": h.Method}
|
||||
var fields map[string]interface{}
|
||||
// Gather data
|
||||
fields, err = h.HTTPGather()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Add metrics
|
||||
acc.AddFields("http_response", fields, tags)
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
inputs.Add("http_response", func() telegraf.Input {
|
||||
return &HTTPResponse{}
|
||||
})
|
||||
}
|
||||
241
plugins/inputs/http_response/http_response_test.go
Normal file
241
plugins/inputs/http_response/http_response_test.go
Normal file
@@ -0,0 +1,241 @@
|
||||
package http_response
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
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 {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/redirect", func(w http.ResponseWriter, req *http.Request) {
|
||||
http.Redirect(w, req, "/good", http.StatusMovedPermanently)
|
||||
})
|
||||
mux.HandleFunc("/good", func(w http.ResponseWriter, req *http.Request) {
|
||||
fmt.Fprintf(w, "hit the good page!")
|
||||
})
|
||||
mux.HandleFunc("/badredirect", func(w http.ResponseWriter, req *http.Request) {
|
||||
http.Redirect(w, req, "/badredirect", http.StatusMovedPermanently)
|
||||
})
|
||||
mux.HandleFunc("/mustbepostmethod", func(w http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != "POST" {
|
||||
http.Error(w, "method wasn't post", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "used post correctly!")
|
||||
})
|
||||
mux.HandleFunc("/musthaveabody", func(w http.ResponseWriter, req *http.Request) {
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
req.Body.Close()
|
||||
if err != nil {
|
||||
http.Error(w, "couldn't read request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if string(body) == "" {
|
||||
http.Error(w, "body was empty", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "sent a body!")
|
||||
})
|
||||
mux.HandleFunc("/twosecondnap", func(w http.ResponseWriter, req *http.Request) {
|
||||
time.Sleep(time.Second * 2)
|
||||
return
|
||||
})
|
||||
return mux
|
||||
}
|
||||
|
||||
func TestFields(t *testing.T) {
|
||||
mux := setUpTestMux()
|
||||
ts := httptest.NewServer(mux)
|
||||
defer ts.Close()
|
||||
|
||||
h := &HTTPResponse{
|
||||
Address: ts.URL + "/good",
|
||||
Body: "{ 'test': 'data'}",
|
||||
Method: "GET",
|
||||
ResponseTimeout: 20,
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
FollowRedirects: true,
|
||||
}
|
||||
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 TestRedirects(t *testing.T) {
|
||||
mux := setUpTestMux()
|
||||
ts := httptest.NewServer(mux)
|
||||
defer ts.Close()
|
||||
|
||||
h := &HTTPResponse{
|
||||
Address: ts.URL + "/redirect",
|
||||
Body: "{ 'test': 'data'}",
|
||||
Method: "GET",
|
||||
ResponseTimeout: 20,
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
FollowRedirects: true,
|
||||
}
|
||||
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"])
|
||||
}
|
||||
|
||||
h = &HTTPResponse{
|
||||
Address: ts.URL + "/badredirect",
|
||||
Body: "{ 'test': 'data'}",
|
||||
Method: "GET",
|
||||
ResponseTimeout: 20,
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
FollowRedirects: true,
|
||||
}
|
||||
fields, err = h.HTTPGather()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMethod(t *testing.T) {
|
||||
mux := setUpTestMux()
|
||||
ts := httptest.NewServer(mux)
|
||||
defer ts.Close()
|
||||
|
||||
h := &HTTPResponse{
|
||||
Address: ts.URL + "/mustbepostmethod",
|
||||
Body: "{ 'test': 'data'}",
|
||||
Method: "POST",
|
||||
ResponseTimeout: 20,
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
FollowRedirects: true,
|
||||
}
|
||||
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"])
|
||||
}
|
||||
|
||||
h = &HTTPResponse{
|
||||
Address: ts.URL + "/mustbepostmethod",
|
||||
Body: "{ 'test': 'data'}",
|
||||
Method: "GET",
|
||||
ResponseTimeout: 20,
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
FollowRedirects: true,
|
||||
}
|
||||
fields, err = h.HTTPGather()
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, fields)
|
||||
if assert.NotNil(t, fields["http_response_code"]) {
|
||||
assert.Equal(t, http.StatusMethodNotAllowed, fields["http_response_code"])
|
||||
}
|
||||
|
||||
//check that lowercase methods work correctly
|
||||
h = &HTTPResponse{
|
||||
Address: ts.URL + "/mustbepostmethod",
|
||||
Body: "{ 'test': 'data'}",
|
||||
Method: "head",
|
||||
ResponseTimeout: 20,
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
FollowRedirects: true,
|
||||
}
|
||||
fields, err = h.HTTPGather()
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, fields)
|
||||
if assert.NotNil(t, fields["http_response_code"]) {
|
||||
assert.Equal(t, http.StatusMethodNotAllowed, fields["http_response_code"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestBody(t *testing.T) {
|
||||
mux := setUpTestMux()
|
||||
ts := httptest.NewServer(mux)
|
||||
defer ts.Close()
|
||||
|
||||
h := &HTTPResponse{
|
||||
Address: ts.URL + "/musthaveabody",
|
||||
Body: "{ 'test': 'data'}",
|
||||
Method: "GET",
|
||||
ResponseTimeout: 20,
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
FollowRedirects: true,
|
||||
}
|
||||
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"])
|
||||
}
|
||||
|
||||
h = &HTTPResponse{
|
||||
Address: ts.URL + "/musthaveabody",
|
||||
Method: "GET",
|
||||
ResponseTimeout: 20,
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
FollowRedirects: true,
|
||||
}
|
||||
fields, err = h.HTTPGather()
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, fields)
|
||||
if assert.NotNil(t, fields["http_response_code"]) {
|
||||
assert.Equal(t, http.StatusBadRequest, fields["http_response_code"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeout(t *testing.T) {
|
||||
mux := setUpTestMux()
|
||||
ts := httptest.NewServer(mux)
|
||||
defer ts.Close()
|
||||
|
||||
h := &HTTPResponse{
|
||||
Address: ts.URL + "/twosecondnap",
|
||||
Body: "{ 'test': 'data'}",
|
||||
Method: "GET",
|
||||
ResponseTimeout: 1,
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
FollowRedirects: true,
|
||||
}
|
||||
_, err := h.HTTPGather()
|
||||
require.Error(t, err)
|
||||
}
|
||||
@@ -1,6 +1,41 @@
|
||||
# influxdb plugin
|
||||
|
||||
The influxdb plugin collects InfluxDB-formatted data from JSON endpoints.
|
||||
The InfluxDB plugin will collect metrics on the given InfluxDB servers.
|
||||
|
||||
This plugin can also gather metrics from endpoints that expose
|
||||
InfluxDB-formatted endpoints. See below for more information.
|
||||
|
||||
### Configuration:
|
||||
|
||||
```toml
|
||||
# Read InfluxDB-formatted JSON metrics from one or more HTTP endpoints
|
||||
[[inputs.influxdb]]
|
||||
## Works with InfluxDB debug endpoints out of the box,
|
||||
## but other services can use this format too.
|
||||
## See the influxdb plugin's README for more details.
|
||||
|
||||
## Multiple URLs from which to read InfluxDB-formatted JSON
|
||||
urls = [
|
||||
"http://localhost:8086/debug/vars"
|
||||
]
|
||||
```
|
||||
|
||||
### Measurements & Fields
|
||||
|
||||
- influxdb_database
|
||||
- influxdb_httpd
|
||||
- influxdb_measurement
|
||||
- influxdb_memstats
|
||||
- influxdb_shard
|
||||
- influxdb_subscriber
|
||||
- influxdb_tsm1_cache
|
||||
- influxdb_tsm1_wal
|
||||
- influxdb_write
|
||||
|
||||
### InfluxDB-formatted endpoints
|
||||
|
||||
The influxdb plugin can collect InfluxDB-formatted data from JSON endpoints.
|
||||
Whether associated with an Influx database or not.
|
||||
|
||||
With a configuration of:
|
||||
|
||||
@@ -65,8 +100,11 @@ influxdb_transactions,url='http://192.168.2.1:8086/debug/vars' total=100.0,balan
|
||||
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
2. The top-level keys' names (in the example above, `"k1"`, `"k2"`, and `"k3"`)
|
||||
are not considered when recording the metrics.
|
||||
|
||||
@@ -71,6 +71,35 @@ type point struct {
|
||||
Values map[string]interface{} `json:"values"`
|
||||
}
|
||||
|
||||
type memstats struct {
|
||||
Alloc int64 `json:"Alloc"`
|
||||
TotalAlloc int64 `json:"TotalAlloc"`
|
||||
Sys int64 `json:"Sys"`
|
||||
Lookups int64 `json:"Lookups"`
|
||||
Mallocs int64 `json:"Mallocs"`
|
||||
Frees int64 `json:"Frees"`
|
||||
HeapAlloc int64 `json:"HeapAlloc"`
|
||||
HeapSys int64 `json:"HeapSys"`
|
||||
HeapIdle int64 `json:"HeapIdle"`
|
||||
HeapInuse int64 `json:"HeapInuse"`
|
||||
HeapReleased int64 `json:"HeapReleased"`
|
||||
HeapObjects int64 `json:"HeapObjects"`
|
||||
StackInuse int64 `json:"StackInuse"`
|
||||
StackSys int64 `json:"StackSys"`
|
||||
MSpanInuse int64 `json:"MSpanInuse"`
|
||||
MSpanSys int64 `json:"MSpanSys"`
|
||||
MCacheInuse int64 `json:"MCacheInuse"`
|
||||
MCacheSys int64 `json:"MCacheSys"`
|
||||
BuckHashSys int64 `json:"BuckHashSys"`
|
||||
GCSys int64 `json:"GCSys"`
|
||||
OtherSys int64 `json:"OtherSys"`
|
||||
NextGC int64 `json:"NextGC"`
|
||||
LastGC int64 `json:"LastGC"`
|
||||
PauseTotalNs int64 `json:"PauseTotalNs"`
|
||||
NumGC int64 `json:"NumGC"`
|
||||
GCCPUFraction float64 `json:"GCCPUFraction"`
|
||||
}
|
||||
|
||||
var tr = &http.Transport{
|
||||
ResponseHeaderTimeout: time.Duration(3 * time.Second),
|
||||
}
|
||||
@@ -118,12 +147,52 @@ func (i *InfluxDB) gatherURL(
|
||||
break
|
||||
}
|
||||
|
||||
// Read in a string key. We don't do anything with the top-level keys, so it's discarded.
|
||||
_, err := dec.Token()
|
||||
// Read in a string key. We don't do anything with the top-level keys,
|
||||
// so it's discarded.
|
||||
key, err := dec.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if key.(string) == "memstats" {
|
||||
var m memstats
|
||||
if err := dec.Decode(&m); err != nil {
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
// Attempt to parse a whole object into a point.
|
||||
// It might be a non-object, like a string or array.
|
||||
// If we fail to decode it into a point, ignore it and move on.
|
||||
@@ -132,7 +201,8 @@ func (i *InfluxDB) gatherURL(
|
||||
continue
|
||||
}
|
||||
|
||||
// If the object was a point, but was not fully initialized, ignore it and move on.
|
||||
// If the object was a point, but was not fully initialized,
|
||||
// ignore it and move on.
|
||||
if p.Name == "" || p.Tags == nil || p.Values == nil || len(p.Values) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -11,7 +11,138 @@ import (
|
||||
)
|
||||
|
||||
func TestBasic(t *testing.T) {
|
||||
js := `
|
||||
fakeServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/endpoint" {
|
||||
_, _ = w.Write([]byte(basicJSON))
|
||||
} else {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
defer fakeServer.Close()
|
||||
|
||||
plugin := &influxdb.InfluxDB{
|
||||
URLs: []string{fakeServer.URL + "/endpoint"},
|
||||
}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
require.NoError(t, plugin.Gather(&acc))
|
||||
|
||||
require.Len(t, acc.Metrics, 2)
|
||||
fields := map[string]interface{}{
|
||||
// JSON will truncate floats to integer representations.
|
||||
// Since there's no distinction in JSON, we can't assume it's an int.
|
||||
"i": -1.0,
|
||||
"f": 0.5,
|
||||
"b": true,
|
||||
"s": "string",
|
||||
}
|
||||
tags := map[string]string{
|
||||
"id": "ex1",
|
||||
"url": fakeServer.URL + "/endpoint",
|
||||
}
|
||||
acc.AssertContainsTaggedFields(t, "influxdb_foo", fields, tags)
|
||||
|
||||
fields = map[string]interface{}{
|
||||
"x": "x",
|
||||
}
|
||||
tags = map[string]string{
|
||||
"id": "ex2",
|
||||
"url": fakeServer.URL + "/endpoint",
|
||||
}
|
||||
acc.AssertContainsTaggedFields(t, "influxdb_bar", fields, tags)
|
||||
}
|
||||
|
||||
func TestInfluxDB(t *testing.T) {
|
||||
fakeInfluxServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/endpoint" {
|
||||
_, _ = w.Write([]byte(influxReturn))
|
||||
} else {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
defer fakeInfluxServer.Close()
|
||||
|
||||
plugin := &influxdb.InfluxDB{
|
||||
URLs: []string{fakeInfluxServer.URL + "/endpoint"},
|
||||
}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
require.NoError(t, plugin.Gather(&acc))
|
||||
|
||||
require.Len(t, acc.Metrics, 33)
|
||||
|
||||
fields := map[string]interface{}{
|
||||
"heap_inuse": int64(18046976),
|
||||
"heap_released": int64(3473408),
|
||||
"mspan_inuse": int64(97440),
|
||||
"total_alloc": int64(201739016),
|
||||
"sys": int64(38537464),
|
||||
"mallocs": int64(570251),
|
||||
"frees": int64(381008),
|
||||
"heap_idle": int64(15802368),
|
||||
"pause_total_ns": int64(5132914),
|
||||
"lookups": int64(77),
|
||||
"heap_sys": int64(33849344),
|
||||
"mcache_sys": int64(16384),
|
||||
"next_gc": int64(20843042),
|
||||
"gcc_pu_fraction": float64(4.287178819113636e-05),
|
||||
"other_sys": int64(1229737),
|
||||
"alloc": int64(17034016),
|
||||
"stack_inuse": int64(753664),
|
||||
"stack_sys": int64(753664),
|
||||
"buck_hash_sys": int64(1461583),
|
||||
"gc_sys": int64(1112064),
|
||||
"num_gc": int64(27),
|
||||
"heap_alloc": int64(17034016),
|
||||
"heap_objects": int64(189243),
|
||||
"mspan_sys": int64(114688),
|
||||
"mcache_inuse": int64(4800),
|
||||
"last_gc": int64(1460434886475114239),
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"url": fakeInfluxServer.URL + "/endpoint",
|
||||
}
|
||||
acc.AssertContainsTaggedFields(t, "influxdb_memstats", fields, tags)
|
||||
}
|
||||
|
||||
func TestErrorHandling(t *testing.T) {
|
||||
badServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/endpoint" {
|
||||
_, _ = w.Write([]byte("not json"))
|
||||
} else {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
defer badServer.Close()
|
||||
|
||||
plugin := &influxdb.InfluxDB{
|
||||
URLs: []string{badServer.URL + "/endpoint"},
|
||||
}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
require.Error(t, plugin.Gather(&acc))
|
||||
}
|
||||
|
||||
func TestErrorHandling404(t *testing.T) {
|
||||
badServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/endpoint" {
|
||||
_, _ = w.Write([]byte(basicJSON))
|
||||
} else {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
defer badServer.Close()
|
||||
|
||||
plugin := &influxdb.InfluxDB{
|
||||
URLs: []string{badServer.URL},
|
||||
}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
require.Error(t, plugin.Gather(&acc))
|
||||
}
|
||||
|
||||
const basicJSON = `
|
||||
{
|
||||
"_1": {
|
||||
"name": "foo",
|
||||
@@ -55,43 +186,48 @@ func TestBasic(t *testing.T) {
|
||||
}
|
||||
}
|
||||
`
|
||||
fakeServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/endpoint" {
|
||||
_, _ = w.Write([]byte(js))
|
||||
} else {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
defer fakeServer.Close()
|
||||
|
||||
plugin := &influxdb.InfluxDB{
|
||||
URLs: []string{fakeServer.URL + "/endpoint"},
|
||||
}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
require.NoError(t, plugin.Gather(&acc))
|
||||
|
||||
require.Len(t, acc.Metrics, 2)
|
||||
fields := map[string]interface{}{
|
||||
// JSON will truncate floats to integer representations.
|
||||
// Since there's no distinction in JSON, we can't assume it's an int.
|
||||
"i": -1.0,
|
||||
"f": 0.5,
|
||||
"b": true,
|
||||
"s": "string",
|
||||
}
|
||||
tags := map[string]string{
|
||||
"id": "ex1",
|
||||
"url": fakeServer.URL + "/endpoint",
|
||||
}
|
||||
acc.AssertContainsTaggedFields(t, "influxdb_foo", fields, tags)
|
||||
|
||||
fields = map[string]interface{}{
|
||||
"x": "x",
|
||||
}
|
||||
tags = map[string]string{
|
||||
"id": "ex2",
|
||||
"url": fakeServer.URL + "/endpoint",
|
||||
}
|
||||
acc.AssertContainsTaggedFields(t, "influxdb_bar", fields, tags)
|
||||
}
|
||||
const influxReturn = `
|
||||
{
|
||||
"cluster": {"name": "cluster", "tags": {}, "values": {}},
|
||||
"cmdline": ["influxd"],
|
||||
"cq": {"name": "cq", "tags": {}, "values": {}},
|
||||
"database:_internal": {"name": "database", "tags": {"database": "_internal"}, "values": {"numMeasurements": 8, "numSeries": 12}},
|
||||
"database:udp": {"name": "database", "tags": {"database": "udp"}, "values": {"numMeasurements": 14, "numSeries": 38}},
|
||||
"hh:/Users/csparr/.influxdb/hh": {"name": "hh", "tags": {"path": "/Users/csparr/.influxdb/hh"}, "values": {}},
|
||||
"httpd::8086": {"name": "httpd", "tags": {"bind": ":8086"}, "values": {"req": 7, "reqActive": 1, "reqDurationNs": 4488799}},
|
||||
"measurement:cpu_idle.udp": {"name": "measurement", "tags": {"database": "udp", "measurement": "cpu_idle"}, "values": {"numSeries": 1}},
|
||||
"measurement:cpu_usage.udp": {"name": "measurement", "tags": {"database": "udp", "measurement": "cpu_usage"}, "values": {"numSeries": 1}},
|
||||
"measurement:database._internal": {"name": "measurement", "tags": {"database": "_internal", "measurement": "database"}, "values": {"numSeries": 2}},
|
||||
"measurement:database.udp": {"name": "measurement", "tags": {"database": "udp", "measurement": "database"}, "values": {"numSeries": 2}},
|
||||
"measurement:httpd.udp": {"name": "measurement", "tags": {"database": "udp", "measurement": "httpd"}, "values": {"numSeries": 1}},
|
||||
"measurement:measurement.udp": {"name": "measurement", "tags": {"database": "udp", "measurement": "measurement"}, "values": {"numSeries": 22}},
|
||||
"measurement:mem.udp": {"name": "measurement", "tags": {"database": "udp", "measurement": "mem"}, "values": {"numSeries": 1}},
|
||||
"measurement:net.udp": {"name": "measurement", "tags": {"database": "udp", "measurement": "net"}, "values": {"numSeries": 1}},
|
||||
"measurement:runtime._internal": {"name": "measurement", "tags": {"database": "_internal", "measurement": "runtime"}, "values": {"numSeries": 1}},
|
||||
"measurement:runtime.udp": {"name": "measurement", "tags": {"database": "udp", "measurement": "runtime"}, "values": {"numSeries": 1}},
|
||||
"measurement:shard._internal": {"name": "measurement", "tags": {"database": "_internal", "measurement": "shard"}, "values": {"numSeries": 2}},
|
||||
"measurement:shard.udp": {"name": "measurement", "tags": {"database": "udp", "measurement": "shard"}, "values": {"numSeries": 1}},
|
||||
"measurement:subscriber._internal": {"name": "measurement", "tags": {"database": "_internal", "measurement": "subscriber"}, "values": {"numSeries": 1}},
|
||||
"measurement:subscriber.udp": {"name": "measurement", "tags": {"database": "udp", "measurement": "subscriber"}, "values": {"numSeries": 1}},
|
||||
"measurement:swap_used.udp": {"name": "measurement", "tags": {"database": "udp", "measurement": "swap_used"}, "values": {"numSeries": 1}},
|
||||
"measurement:tsm1_cache._internal": {"name": "measurement", "tags": {"database": "_internal", "measurement": "tsm1_cache"}, "values": {"numSeries": 2}},
|
||||
"measurement:tsm1_cache.udp": {"name": "measurement", "tags": {"database": "udp", "measurement": "tsm1_cache"}, "values": {"numSeries": 2}},
|
||||
"measurement:tsm1_wal._internal": {"name": "measurement", "tags": {"database": "_internal", "measurement": "tsm1_wal"}, "values": {"numSeries": 2}},
|
||||
"measurement:tsm1_wal.udp": {"name": "measurement", "tags": {"database": "udp", "measurement": "tsm1_wal"}, "values": {"numSeries": 2}},
|
||||
"measurement:udp._internal": {"name": "measurement", "tags": {"database": "_internal", "measurement": "udp"}, "values": {"numSeries": 1}},
|
||||
"measurement:write._internal": {"name": "measurement", "tags": {"database": "_internal", "measurement": "write"}, "values": {"numSeries": 1}},
|
||||
"measurement:write.udp": {"name": "measurement", "tags": {"database": "udp", "measurement": "write"}, "values": {"numSeries": 1}},
|
||||
"memstats": {"Alloc":17034016,"TotalAlloc":201739016,"Sys":38537464,"Lookups":77,"Mallocs":570251,"Frees":381008,"HeapAlloc":17034016,"HeapSys":33849344,"HeapIdle":15802368,"HeapInuse":18046976,"HeapReleased":3473408,"HeapObjects":189243,"StackInuse":753664,"StackSys":753664,"MSpanInuse":97440,"MSpanSys":114688,"MCacheInuse":4800,"MCacheSys":16384,"BuckHashSys":1461583,"GCSys":1112064,"OtherSys":1229737,"NextGC":20843042,"LastGC":1460434886475114239,"PauseTotalNs":5132914,"PauseNs":[195052,117751,139370,156933,263089,165249,713747,103904,122015,294408,213753,170864,175845,114221,121563,122409,113098,162219,229257,126726,250774,254235,117206,293588,144279,124306,127053,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"PauseEnd":[1460433856394860455,1460433856398162739,1460433856405888337,1460433856411784017,1460433856417924684,1460433856428385687,1460433856443782908,1460433856456522851,1460433857392743223,1460433866484394564,1460433866494076235,1460433896472438632,1460433957839825106,1460433976473440328,1460434016473413006,1460434096471892794,1460434126470792929,1460434246480428250,1460434366554468369,1460434396471249528,1460434456471205885,1460434476479487292,1460434536471435965,1460434616469784776,1460434736482078216,1460434856544251733,1460434886475114239,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"NumGC":27,"GCCPUFraction":4.287178819113636e-05,"EnableGC":true,"DebugGC":false,"BySize":[{"Size":0,"Mallocs":0,"Frees":0},{"Size":8,"Mallocs":1031,"Frees":955},{"Size":16,"Mallocs":308485,"Frees":142064},{"Size":32,"Mallocs":64937,"Frees":54321},{"Size":48,"Mallocs":33012,"Frees":29754},{"Size":64,"Mallocs":20299,"Frees":18173},{"Size":80,"Mallocs":8186,"Frees":7597},{"Size":96,"Mallocs":9806,"Frees":8982},{"Size":112,"Mallocs":5671,"Frees":4850},{"Size":128,"Mallocs":2972,"Frees":2684},{"Size":144,"Mallocs":4106,"Frees":3719},{"Size":160,"Mallocs":1324,"Frees":911},{"Size":176,"Mallocs":2574,"Frees":2391},{"Size":192,"Mallocs":4053,"Frees":3863},{"Size":208,"Mallocs":442,"Frees":307},{"Size":224,"Mallocs":336,"Frees":172},{"Size":240,"Mallocs":143,"Frees":125},{"Size":256,"Mallocs":542,"Frees":497},{"Size":288,"Mallocs":15971,"Frees":14761},{"Size":320,"Mallocs":245,"Frees":30},{"Size":352,"Mallocs":1299,"Frees":1065},{"Size":384,"Mallocs":138,"Frees":2},{"Size":416,"Mallocs":54,"Frees":47},{"Size":448,"Mallocs":75,"Frees":29},{"Size":480,"Mallocs":6,"Frees":4},{"Size":512,"Mallocs":452,"Frees":422},{"Size":576,"Mallocs":486,"Frees":395},{"Size":640,"Mallocs":81,"Frees":67},{"Size":704,"Mallocs":421,"Frees":397},{"Size":768,"Mallocs":469,"Frees":468},{"Size":896,"Mallocs":1049,"Frees":1010},{"Size":1024,"Mallocs":1078,"Frees":960},{"Size":1152,"Mallocs":750,"Frees":498},{"Size":1280,"Mallocs":84,"Frees":72},{"Size":1408,"Mallocs":218,"Frees":187},{"Size":1536,"Mallocs":73,"Frees":48},{"Size":1664,"Mallocs":43,"Frees":30},{"Size":2048,"Mallocs":153,"Frees":57},{"Size":2304,"Mallocs":41,"Frees":30},{"Size":2560,"Mallocs":18,"Frees":15},{"Size":2816,"Mallocs":164,"Frees":157},{"Size":3072,"Mallocs":0,"Frees":0},{"Size":3328,"Mallocs":13,"Frees":6},{"Size":4096,"Mallocs":101,"Frees":82},{"Size":4608,"Mallocs":32,"Frees":26},{"Size":5376,"Mallocs":165,"Frees":151},{"Size":6144,"Mallocs":15,"Frees":9},{"Size":6400,"Mallocs":1,"Frees":1},{"Size":6656,"Mallocs":1,"Frees":0},{"Size":6912,"Mallocs":0,"Frees":0},{"Size":8192,"Mallocs":13,"Frees":13},{"Size":8448,"Mallocs":0,"Frees":0},{"Size":8704,"Mallocs":1,"Frees":1},{"Size":9472,"Mallocs":6,"Frees":4},{"Size":10496,"Mallocs":0,"Frees":0},{"Size":12288,"Mallocs":41,"Frees":35},{"Size":13568,"Mallocs":0,"Frees":0},{"Size":14080,"Mallocs":0,"Frees":0},{"Size":16384,"Mallocs":4,"Frees":4},{"Size":16640,"Mallocs":0,"Frees":0},{"Size":17664,"Mallocs":0,"Frees":0}]},
|
||||
"queryExecutor": {"name": "queryExecutor", "tags": {}, "values": {}},
|
||||
"shard:/Users/csparr/.influxdb/data/_internal/monitor/2:2": {"name": "shard", "tags": {"database": "_internal", "engine": "tsm1", "id": "2", "path": "/Users/csparr/.influxdb/data/_internal/monitor/2", "retentionPolicy": "monitor"}, "values": {}},
|
||||
"shard:/Users/csparr/.influxdb/data/udp/default/1:1": {"name": "shard", "tags": {"database": "udp", "engine": "tsm1", "id": "1", "path": "/Users/csparr/.influxdb/data/udp/default/1", "retentionPolicy": "default"}, "values": {"fieldsCreate": 61, "seriesCreate": 33, "writePointsOk": 3613, "writeReq": 110}},
|
||||
"subscriber": {"name": "subscriber", "tags": {}, "values": {"pointsWritten": 3613}},
|
||||
"tsm1_cache:/Users/csparr/.influxdb/data/_internal/monitor/2": {"name": "tsm1_cache", "tags": {"database": "_internal", "path": "/Users/csparr/.influxdb/data/_internal/monitor/2", "retentionPolicy": "monitor"}, "values": {"WALCompactionTimeMs": 0, "cacheAgeMs": 1103932, "cachedBytes": 0, "diskBytes": 0, "memBytes": 40480, "snapshotCount": 0}},
|
||||
"tsm1_cache:/Users/csparr/.influxdb/data/udp/default/1": {"name": "tsm1_cache", "tags": {"database": "udp", "path": "/Users/csparr/.influxdb/data/udp/default/1", "retentionPolicy": "default"}, "values": {"WALCompactionTimeMs": 0, "cacheAgeMs": 1103029, "cachedBytes": 0, "diskBytes": 0, "memBytes": 2359472, "snapshotCount": 0}},
|
||||
"tsm1_filestore:/Users/csparr/.influxdb/data/_internal/monitor/2": {"name": "tsm1_filestore", "tags": {"database": "_internal", "path": "/Users/csparr/.influxdb/data/_internal/monitor/2", "retentionPolicy": "monitor"}, "values": {}},
|
||||
"tsm1_filestore:/Users/csparr/.influxdb/data/udp/default/1": {"name": "tsm1_filestore", "tags": {"database": "udp", "path": "/Users/csparr/.influxdb/data/udp/default/1", "retentionPolicy": "default"}, "values": {}},
|
||||
"tsm1_wal:/Users/csparr/.influxdb/wal/_internal/monitor/2": {"name": "tsm1_wal", "tags": {"database": "_internal", "path": "/Users/csparr/.influxdb/wal/_internal/monitor/2", "retentionPolicy": "monitor"}, "values": {"currentSegmentDiskBytes": 0, "oldSegmentsDiskBytes": 69532}},
|
||||
"tsm1_wal:/Users/csparr/.influxdb/wal/udp/default/1": {"name": "tsm1_wal", "tags": {"database": "udp", "path": "/Users/csparr/.influxdb/wal/udp/default/1", "retentionPolicy": "default"}, "values": {"currentSegmentDiskBytes": 193728, "oldSegmentsDiskBytes": 1008330}},
|
||||
"write": {"name": "write", "tags": {}, "values": {"pointReq": 3613, "pointReqLocal": 3613, "req": 110, "subWriteOk": 110, "writeOk": 110}}
|
||||
}`
|
||||
|
||||
@@ -20,7 +20,7 @@ ipmitool -I lan -H 192.168.1.1 -U USERID -P PASSW0RD sdr
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
[[inputs.ipmi]]
|
||||
[[inputs.ipmi_sensor]]
|
||||
## specify servers via a url matching:
|
||||
## [username[:password]@][protocol[(address)]]
|
||||
## e.g.
|
||||
|
||||
@@ -28,7 +28,7 @@ func NewConnection(server string) *Connection {
|
||||
if inx1 > 0 {
|
||||
security := server[0:inx1]
|
||||
connstr = server[inx1+1 : len(server)]
|
||||
up := strings.Split(security, ":")
|
||||
up := strings.SplitN(security, ":", 2)
|
||||
conn.Username = up[0]
|
||||
conn.Password = up[1]
|
||||
}
|
||||
|
||||
@@ -1,16 +1,38 @@
|
||||
# Telegraf plugin: Jolokia
|
||||
|
||||
#### Plugin arguments:
|
||||
- **context** string: Context root used of jolokia url
|
||||
- **servers** []Server: List of servers
|
||||
+ **name** string: Server's logical name
|
||||
+ **host** string: Server's ip address or hostname
|
||||
+ **port** string: Server's listening port
|
||||
- **metrics** []Metric
|
||||
+ **name** string: Name of the measure
|
||||
+ **jmx** string: Jmx path that identifies mbeans attributes
|
||||
+ **pass** []string: Attributes to retain when collecting values
|
||||
+ **drop** []string: Attributes to drop when collecting values
|
||||
#### Configuration
|
||||
|
||||
```toml
|
||||
[[inputs.jolokia]]
|
||||
## This is the context root used to compose the jolokia url
|
||||
context = "/jolokia/read"
|
||||
|
||||
## List of servers exposing jolokia read service
|
||||
[[inputs.jolokia.servers]]
|
||||
name = "stable"
|
||||
host = "192.168.103.2"
|
||||
port = "8180"
|
||||
# username = "myuser"
|
||||
# password = "mypassword"
|
||||
|
||||
## List of metrics collected on above servers
|
||||
## Each metric consists in a name, a jmx path and either
|
||||
## a pass or drop slice attribute.
|
||||
## This collect all heap memory usage metrics.
|
||||
[[inputs.jolokia.metrics]]
|
||||
name = "heap_memory_usage"
|
||||
jmx = "/java.lang:type=Memory/HeapMemoryUsage"
|
||||
|
||||
## This collect thread counts metrics.
|
||||
[[inputs.jolokia.metrics]]
|
||||
name = "thread_count"
|
||||
jmx = "/java.lang:type=Threading/TotalStartedThreadCount,ThreadCount,DaemonThreadCount,PeakThreadCount"
|
||||
|
||||
## This collect number of class loaded/unloaded counts metrics.
|
||||
[[inputs.jolokia.metrics]]
|
||||
name = "class_count"
|
||||
jmx = "/java.lang:type=ClassLoading/LoadedClassCount,UnloadedClassCount,TotalLoadedClassCount"
|
||||
```
|
||||
|
||||
#### Description
|
||||
|
||||
@@ -21,31 +43,3 @@ See: https://jolokia.org/
|
||||
|
||||
# Measurements:
|
||||
Jolokia plugin produces one measure for each metric configured, adding Server's `name`, `host` and `port` as tags.
|
||||
|
||||
Given a configuration like:
|
||||
|
||||
```ini
|
||||
[jolokia]
|
||||
|
||||
[[jolokia.servers]]
|
||||
name = "as-service-1"
|
||||
host = "127.0.0.1"
|
||||
port = "8080"
|
||||
|
||||
[[jolokia.servers]]
|
||||
name = "as-service-2"
|
||||
host = "127.0.0.1"
|
||||
port = "8180"
|
||||
|
||||
[[jolokia.metrics]]
|
||||
name = "heap_memory_usage"
|
||||
jmx = "/java.lang:type=Memory/HeapMemoryUsage"
|
||||
pass = ["used", "max"]
|
||||
```
|
||||
|
||||
The collected metrics will be:
|
||||
|
||||
```
|
||||
jolokia_heap_memory_usage name=as-service-1,host=127.0.0.1,port=8080 used=xxx,max=yyy
|
||||
jolokia_heap_memory_usage name=as-service-2,host=127.0.0.1,port=8180 used=vvv,max=zzz
|
||||
```
|
||||
|
||||
@@ -65,6 +65,16 @@ func (j *Jolokia) SampleConfig() string {
|
||||
[[inputs.jolokia.metrics]]
|
||||
name = "heap_memory_usage"
|
||||
jmx = "/java.lang:type=Memory/HeapMemoryUsage"
|
||||
|
||||
## This collect thread counts metrics.
|
||||
[[inputs.jolokia.metrics]]
|
||||
name = "thread_count"
|
||||
jmx = "/java.lang:type=Threading/TotalStartedThreadCount,ThreadCount,DaemonThreadCount,PeakThreadCount"
|
||||
|
||||
## This collect number of class loaded/unloaded counts metrics.
|
||||
[[inputs.jolokia.metrics]]
|
||||
name = "class_count"
|
||||
jmx = "/java.lang:type=ClassLoading/LoadedClassCount,UnloadedClassCount,TotalLoadedClassCount"
|
||||
`
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ from the same topic in parallel.
|
||||
## Offset (must be either "oldest" or "newest")
|
||||
offset = "oldest"
|
||||
|
||||
## Data format to consume. This can be "json", "influx" or "graphite"
|
||||
## Data format to consume.
|
||||
|
||||
## Each data format has it's own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md
|
||||
|
||||
@@ -35,7 +35,7 @@ The plugin expects messages in the
|
||||
## Use SSL but skip chain & host verification
|
||||
# insecure_skip_verify = false
|
||||
|
||||
## Data format to consume. This can be "json", "influx" or "graphite"
|
||||
## Data format to consume.
|
||||
## Each data format has it's own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md
|
||||
|
||||
@@ -23,7 +23,8 @@ from a NATS cluster in parallel.
|
||||
## Maximum number of metrics to buffer between collection intervals
|
||||
metric_buffer = 100000
|
||||
|
||||
## Data format to consume. This can be "json", "influx" or "graphite"
|
||||
## Data format to consume.
|
||||
|
||||
## Each data format has it's own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md
|
||||
|
||||
@@ -18,7 +18,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
UDP_PACKET_SIZE int = 1500
|
||||
// UDP packet limit, see
|
||||
// https://en.wikipedia.org/wiki/User_Datagram_Protocol#Packet_structure
|
||||
UDP_MAX_PACKET_SIZE int = 64 * 1024
|
||||
|
||||
defaultFieldName = "value"
|
||||
|
||||
@@ -55,8 +57,10 @@ type Statsd struct {
|
||||
// statsd protocol (http://docs.datadoghq.com/guides/dogstatsd/)
|
||||
ParseDataDogTags bool
|
||||
|
||||
// UDPPacketSize is the size of the read packets for the server listening
|
||||
// for statsd UDP packets. This will default to 1500 bytes.
|
||||
// UDPPacketSize is deprecated, it's only here for legacy support
|
||||
// we now always create 1 max size buffer and then copy only what we need
|
||||
// into the in channel
|
||||
// see https://github.com/influxdata/telegraf/pull/992
|
||||
UDPPacketSize int `toml:"udp_packet_size"`
|
||||
|
||||
sync.Mutex
|
||||
@@ -157,10 +161,6 @@ const sampleConfig = `
|
||||
## calculation of percentiles. Raising this limit increases the accuracy
|
||||
## of percentiles but also increases the memory usage and cpu time.
|
||||
percentile_limit = 1000
|
||||
|
||||
## UDP packet size for the server to listen for. This will depend on the size
|
||||
## of the packets that the client is sending, which is usually 1500 bytes.
|
||||
udp_packet_size = 1500
|
||||
`
|
||||
|
||||
func (_ *Statsd) SampleConfig() string {
|
||||
@@ -274,20 +274,22 @@ func (s *Statsd) udpListen() error {
|
||||
}
|
||||
log.Println("Statsd listener listening on: ", s.listener.LocalAddr().String())
|
||||
|
||||
buf := make([]byte, UDP_MAX_PACKET_SIZE)
|
||||
for {
|
||||
select {
|
||||
case <-s.done:
|
||||
return nil
|
||||
default:
|
||||
buf := make([]byte, s.UDPPacketSize)
|
||||
n, _, err := s.listener.ReadFromUDP(buf)
|
||||
if err != nil && !strings.Contains(err.Error(), "closed network") {
|
||||
log.Printf("ERROR READ: %s\n", err.Error())
|
||||
continue
|
||||
}
|
||||
bufCopy := make([]byte, n)
|
||||
copy(bufCopy, buf[:n])
|
||||
|
||||
select {
|
||||
case s.in <- buf[:n]:
|
||||
case s.in <- bufCopy:
|
||||
default:
|
||||
log.Printf(dropwarn, string(buf[:n]))
|
||||
}
|
||||
@@ -300,11 +302,12 @@ func (s *Statsd) udpListen() error {
|
||||
// single statsd metric into a struct.
|
||||
func (s *Statsd) parser() error {
|
||||
defer s.wg.Done()
|
||||
var packet []byte
|
||||
for {
|
||||
select {
|
||||
case <-s.done:
|
||||
return nil
|
||||
case packet := <-s.in:
|
||||
case packet = <-s.in:
|
||||
lines := strings.Split(string(packet), "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
@@ -631,8 +634,7 @@ func (s *Statsd) Stop() {
|
||||
func init() {
|
||||
inputs.Add("statsd", func() telegraf.Input {
|
||||
return &Statsd{
|
||||
ConvertNames: true,
|
||||
UDPPacketSize: UDP_PACKET_SIZE,
|
||||
MetricSeparator: "_",
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ func NewTestStatsd() *Statsd {
|
||||
s.timings = make(map[string]cachedtimings)
|
||||
|
||||
s.MetricSeparator = "_"
|
||||
s.UDPPacketSize = UDP_PACKET_SIZE
|
||||
|
||||
return &s
|
||||
}
|
||||
|
||||
447
plugins/inputs/sysstat/README.md
Normal file
447
plugins/inputs/sysstat/README.md
Normal file
@@ -0,0 +1,447 @@
|
||||
# sysstat Input Plugin
|
||||
|
||||
Collect [sysstat](https://github.com/sysstat/sysstat) metrics - requires the sysstat
|
||||
package installed.
|
||||
|
||||
This plugin collects system metrics with the sysstat collector utility `sadc` and parses
|
||||
the created binary data file with the `sadf` utility.
|
||||
|
||||
### Configuration:
|
||||
|
||||
```toml
|
||||
# Sysstat metrics collector
|
||||
[[inputs.sysstat]]
|
||||
## Path to the sadc command.
|
||||
#
|
||||
## On Debian and Arch Linux the default path is /usr/lib/sa/sadc whereas
|
||||
## on RHEL and CentOS the default path is /usr/lib64/sa/sadc
|
||||
sadc_path = "/usr/lib/sa/sadc" # required
|
||||
#
|
||||
#
|
||||
## Path to the sadf command, if it is not in PATH
|
||||
# sadf_path = "/usr/bin/sadf"
|
||||
#
|
||||
#
|
||||
## Activities is a list of activities, that are passed as argument to the
|
||||
## sadc collector utility (e.g: DISK, SNMP etc...)
|
||||
## The more activities that are added, the more data is collected.
|
||||
# activities = ["DISK"]
|
||||
#
|
||||
#
|
||||
## Group metrics to measurements.
|
||||
##
|
||||
## If group is false each metric will be prefixed with a description
|
||||
## and represents itself a measurement.
|
||||
##
|
||||
## If Group is true, corresponding metrics are grouped to a single measurement.
|
||||
# group = true
|
||||
#
|
||||
#
|
||||
## Options for the sadf command. The values on the left represent the sadf options and
|
||||
## the values on the right their description (wich are used for grouping and prefixing metrics).
|
||||
##
|
||||
## Run 'sar -h' or 'man sar' to find out the supported options for your sysstat version.
|
||||
[inputs.sysstat.options]
|
||||
-C = "cpu"
|
||||
-B = "paging"
|
||||
-b = "io"
|
||||
-d = "disk" # requires DISK activity
|
||||
"-n ALL" = "network"
|
||||
"-P ALL" = "per_cpu"
|
||||
-q = "queue"
|
||||
-R = "mem"
|
||||
-r = "mem_util"
|
||||
-S = "swap_util"
|
||||
-u = "cpu_util"
|
||||
-v = "inode"
|
||||
-W = "swap"
|
||||
-w = "task"
|
||||
# -H = "hugepages" # only available for newer linux distributions
|
||||
# "-I ALL" = "interrupts" # requires INT activity
|
||||
#
|
||||
#
|
||||
## Device tags can be used to add additional tags for devices. For example the configuration below
|
||||
## adds a tag vg with value rootvg for all metrics with sda devices.
|
||||
# [[inputs.sysstat.device_tags.sda]]
|
||||
# vg = "rootvg"
|
||||
```
|
||||
|
||||
### Measurements & Fields:
|
||||
#### If group=true
|
||||
- cpu
|
||||
- pct_idle (float)
|
||||
- pct_iowait (float)
|
||||
- pct_nice (float)
|
||||
- pct_steal (float)
|
||||
- pct_system (float)
|
||||
- pct_user (float)
|
||||
|
||||
- disk
|
||||
- avgqu-sz (float)
|
||||
- avgrq-sz (float)
|
||||
- await (float)
|
||||
- pct_util (float)
|
||||
- rd_sec_pers (float)
|
||||
- svctm (float)
|
||||
- tps (float)
|
||||
|
||||
And much more, depending on the options you configure.
|
||||
|
||||
#### If group=false
|
||||
- cpu_pct_idle
|
||||
- value (float)
|
||||
- cpu_pct_iowait
|
||||
- value (float)
|
||||
- cpu_pct_nice
|
||||
- value (float)
|
||||
- cpu_pct_steal
|
||||
- value (float)
|
||||
- cpu_pct_system
|
||||
- value (float)
|
||||
- cpu_pct_user
|
||||
- value (float)
|
||||
- disk_avgqu-sz
|
||||
- value (float)
|
||||
- disk_avgrq-sz
|
||||
- value (float)
|
||||
- disk_await
|
||||
- value (float)
|
||||
- disk_pct_util
|
||||
- value (float)
|
||||
- disk_rd_sec_per_s
|
||||
- value (float)
|
||||
- disk_svctm
|
||||
- value (float)
|
||||
- disk_tps
|
||||
- value (float)
|
||||
|
||||
And much more, depending on the options you configure.
|
||||
|
||||
### Tags:
|
||||
|
||||
- All measurements have the following tags:
|
||||
- device
|
||||
|
||||
And more if you define some `device_tags`.
|
||||
### Example Output:
|
||||
|
||||
With the configuration below:
|
||||
```toml
|
||||
[[inputs.sysstat]]
|
||||
sadc_path = "/usr/lib/sa/sadc" # required
|
||||
activities = ["DISK", "SNMP", "INT"]
|
||||
group = true
|
||||
[inputs.sysstat.options]
|
||||
-C = "cpu"
|
||||
-B = "paging"
|
||||
-b = "io"
|
||||
-d = "disk" # requires DISK activity
|
||||
-H = "hugepages"
|
||||
"-I ALL" = "interrupts" # requires INT activity
|
||||
"-n ALL" = "network"
|
||||
"-P ALL" = "per_cpu"
|
||||
-q = "queue"
|
||||
-R = "mem"
|
||||
"-r ALL" = "mem_util"
|
||||
-S = "swap_util"
|
||||
-u = "cpu_util"
|
||||
-v = "inode"
|
||||
-W = "swap"
|
||||
-w = "task"
|
||||
[[inputs.sysstat.device_tags.sda]]
|
||||
vg = "rootvg"
|
||||
```
|
||||
|
||||
you get the following output:
|
||||
```
|
||||
$ telegraf -config telegraf.conf -input-filter sysstat -test
|
||||
* Plugin: sysstat, Collection 1
|
||||
> cpu_util,device=all pct_idle=98.85,pct_iowait=0,pct_nice=0.38,pct_steal=0,pct_system=0.64,pct_user=0.13 1459255626657883725
|
||||
> swap pswpin_per_s=0,pswpout_per_s=0 1459255626658387650
|
||||
> per_cpu,device=cpu1 pct_idle=98.98,pct_iowait=0,pct_nice=0.26,pct_steal=0,pct_system=0.51,pct_user=0.26 1459255626659630437
|
||||
> per_cpu,device=all pct_idle=98.85,pct_iowait=0,pct_nice=0.38,pct_steal=0,pct_system=0.64,pct_user=0.13 1459255626659670744
|
||||
> per_cpu,device=cpu0 pct_idle=98.73,pct_iowait=0,pct_nice=0.76,pct_steal=0,pct_system=0.51,pct_user=0 1459255626659697515
|
||||
> hugepages kbhugfree=0,kbhugused=0,pct_hugused=0 1459255626660057517
|
||||
> network,device=lo coll_per_s=0,pct_ifutil=0,rxcmp_per_s=0,rxdrop_per_s=0,rxerr_per_s=0,rxfifo_per_s=0,rxfram_per_s=0,rxkB_per_s=0.81,rxmcst_per_s=0,rxpck_per_s=16,txcarr_per_s=0,txcmp_per_s=0,txdrop_per_s=0,txerr_per_s=0,txfifo_per_s=0,txkB_per_s=0.81,txpck_per_s=16 1459255626661197666
|
||||
> network access_per_s=0,active_per_s=0,asmf_per_s=0,asmok_per_s=0,asmrq_per_s=0,atmptf_per_s=0,badcall_per_s=0,call_per_s=0,estres_per_s=0,fragcrt_per_s=0,fragf_per_s=0,fragok_per_s=0,fwddgm_per_s=0,getatt_per_s=0,hit_per_s=0,iadrerr_per_s=0,iadrmk_per_s=0,iadrmkr_per_s=0,idel_per_s=16,idgm_per_s=0,idgmerr_per_s=0,idisc_per_s=0,idstunr_per_s=0,iech_per_s=0,iechr_per_s=0,ierr_per_s=0,ihdrerr_per_s=0,imsg_per_s=0,ip-frag=0,iparmpb_per_s=0,irec_per_s=16,iredir_per_s=0,iseg_per_s=16,isegerr_per_s=0,isrcq_per_s=0,itm_per_s=0,itmex_per_s=0,itmr_per_s=0,iukwnpr_per_s=0,miss_per_s=0,noport_per_s=0,oadrmk_per_s=0,oadrmkr_per_s=0,odgm_per_s=0,odisc_per_s=0,odstunr_per_s=0,oech_per_s=0,oechr_per_s=0,oerr_per_s=0,omsg_per_s=0,onort_per_s=0,oparmpb_per_s=0,oredir_per_s=0,orq_per_s=16,orsts_per_s=0,oseg_per_s=16,osrcq_per_s=0,otm_per_s=0,otmex_per_s=0,otmr_per_s=0,packet_per_s=0,passive_per_s=0,rawsck=0,read_per_s=0,retrans_per_s=0,saccess_per_s=0,scall_per_s=0,sgetatt_per_s=0,sread_per_s=0,swrite_per_s=0,tcp-tw=7,tcp_per_s=0,tcpsck=1543,totsck=4052,udp_per_s=0,udpsck=2,write_per_s=0 1459255626661381788
|
||||
> network,device=ens33 coll_per_s=0,pct_ifutil=0,rxcmp_per_s=0,rxdrop_per_s=0,rxerr_per_s=0,rxfifo_per_s=0,rxfram_per_s=0,rxkB_per_s=0,rxmcst_per_s=0,rxpck_per_s=0,txcarr_per_s=0,txcmp_per_s=0,txdrop_per_s=0,txerr_per_s=0,txfifo_per_s=0,txkB_per_s=0,txpck_per_s=0 1459255626661533072
|
||||
> disk,device=sda,vg=rootvg avgqu-sz=0.01,avgrq-sz=8.5,await=3.31,pct_util=0.1,rd_sec_per_s=0,svctm=0.25,tps=4,wr_sec_per_s=34 1459255626663974389
|
||||
> queue blocked=0,ldavg-1=1.61,ldavg-15=1.34,ldavg-5=1.67,plist-sz=1415,runq-sz=0 1459255626664159054
|
||||
> paging fault_per_s=0.25,majflt_per_s=0,pct_vmeff=0,pgfree_per_s=19,pgpgin_per_s=0,pgpgout_per_s=17,pgscand_per_s=0,pgscank_per_s=0,pgsteal_per_s=0 1459255626664304249
|
||||
> mem_util kbactive=2206568,kbanonpg=1472208,kbbuffers=118020,kbcached=1035252,kbcommit=8717200,kbdirty=156,kbinact=418912,kbkstack=24672,kbmemfree=1744868,kbmemused=3610272,kbpgtbl=87116,kbslab=233804,kbvmused=0,pct_commit=136.13,pct_memused=67.42 1459255626664554981
|
||||
> io bread_per_s=0,bwrtn_per_s=34,rtps=0,tps=4,wtps=4 1459255626664596198
|
||||
> inode dentunusd=235039,file-nr=17120,inode-nr=94505,pty-nr=14 1459255626664663693
|
||||
> interrupts,device=i000 intr_per_s=0 1459255626664800109
|
||||
> interrupts,device=i003 intr_per_s=0 1459255626665255145
|
||||
> interrupts,device=i004 intr_per_s=0 1459255626665281776
|
||||
> interrupts,device=i006 intr_per_s=0 1459255626665297416
|
||||
> interrupts,device=i007 intr_per_s=0 1459255626665321008
|
||||
> interrupts,device=i010 intr_per_s=0 1459255626665339413
|
||||
> interrupts,device=i012 intr_per_s=0 1459255626665361510
|
||||
> interrupts,device=i013 intr_per_s=0 1459255626665381327
|
||||
> interrupts,device=i015 intr_per_s=1 1459255626665397313
|
||||
> interrupts,device=i001 intr_per_s=0.25 1459255626665412985
|
||||
> interrupts,device=i002 intr_per_s=0 1459255626665430475
|
||||
> interrupts,device=i005 intr_per_s=0 1459255626665453944
|
||||
> interrupts,device=i008 intr_per_s=0 1459255626665470650
|
||||
> interrupts,device=i011 intr_per_s=0 1459255626665486069
|
||||
> interrupts,device=i009 intr_per_s=0 1459255626665502913
|
||||
> interrupts,device=i014 intr_per_s=0 1459255626665518152
|
||||
> task cswch_per_s=722.25,proc_per_s=0 1459255626665849646
|
||||
> cpu,device=all pct_idle=98.85,pct_iowait=0,pct_nice=0.38,pct_steal=0,pct_system=0.64,pct_user=0.13 1459255626666639715
|
||||
> mem bufpg_per_s=0,campg_per_s=1.75,frmpg_per_s=-8.25 1459255626666770205
|
||||
> swap_util kbswpcad=0,kbswpfree=1048572,kbswpused=0,pct_swpcad=0,pct_swpused=0 1459255626667313276
|
||||
```
|
||||
|
||||
If you change the group value to false like below:
|
||||
```toml
|
||||
[[inputs.sysstat]]
|
||||
sadc_path = "/usr/lib/sa/sadc" # required
|
||||
activities = ["DISK", "SNMP", "INT"]
|
||||
group = false
|
||||
[inputs.sysstat.options]
|
||||
-C = "cpu"
|
||||
-B = "paging"
|
||||
-b = "io"
|
||||
-d = "disk" # requires DISK activity
|
||||
-H = "hugepages"
|
||||
"-I ALL" = "interrupts" # requires INT activity
|
||||
"-n ALL" = "network"
|
||||
"-P ALL" = "per_cpu"
|
||||
-q = "queue"
|
||||
-R = "mem"
|
||||
"-r ALL" = "mem_util"
|
||||
-S = "swap_util"
|
||||
-u = "cpu_util"
|
||||
-v = "inode"
|
||||
-W = "swap"
|
||||
-w = "task"
|
||||
[[inputs.sysstat.device_tags.sda]]
|
||||
vg = "rootvg"
|
||||
```
|
||||
|
||||
you get the following output:
|
||||
```
|
||||
$ telegraf -config telegraf.conf -input-filter sysstat -test
|
||||
* Plugin: sysstat, Collection 1
|
||||
> io_tps value=0.5 1459255780126025822
|
||||
> io_rtps value=0 1459255780126025822
|
||||
> io_wtps value=0.5 1459255780126025822
|
||||
> io_bread_per_s value=0 1459255780126025822
|
||||
> io_bwrtn_per_s value=38 1459255780126025822
|
||||
> cpu_util_pct_user,device=all value=39.07 1459255780126025822
|
||||
> cpu_util_pct_nice,device=all value=0 1459255780126025822
|
||||
> cpu_util_pct_system,device=all value=47.94 1459255780126025822
|
||||
> cpu_util_pct_iowait,device=all value=0 1459255780126025822
|
||||
> cpu_util_pct_steal,device=all value=0 1459255780126025822
|
||||
> cpu_util_pct_idle,device=all value=12.98 1459255780126025822
|
||||
> swap_pswpin_per_s value=0 1459255780126025822
|
||||
> cpu_pct_user,device=all value=39.07 1459255780126025822
|
||||
> cpu_pct_nice,device=all value=0 1459255780126025822
|
||||
> cpu_pct_system,device=all value=47.94 1459255780126025822
|
||||
> cpu_pct_iowait,device=all value=0 1459255780126025822
|
||||
> cpu_pct_steal,device=all value=0 1459255780126025822
|
||||
> cpu_pct_idle,device=all value=12.98 1459255780126025822
|
||||
> per_cpu_pct_user,device=all value=39.07 1459255780126025822
|
||||
> per_cpu_pct_nice,device=all value=0 1459255780126025822
|
||||
> per_cpu_pct_system,device=all value=47.94 1459255780126025822
|
||||
> per_cpu_pct_iowait,device=all value=0 1459255780126025822
|
||||
> per_cpu_pct_steal,device=all value=0 1459255780126025822
|
||||
> per_cpu_pct_idle,device=all value=12.98 1459255780126025822
|
||||
> per_cpu_pct_user,device=cpu0 value=33.5 1459255780126025822
|
||||
> per_cpu_pct_nice,device=cpu0 value=0 1459255780126025822
|
||||
> per_cpu_pct_system,device=cpu0 value=65.25 1459255780126025822
|
||||
> per_cpu_pct_iowait,device=cpu0 value=0 1459255780126025822
|
||||
> per_cpu_pct_steal,device=cpu0 value=0 1459255780126025822
|
||||
> per_cpu_pct_idle,device=cpu0 value=1.25 1459255780126025822
|
||||
> per_cpu_pct_user,device=cpu1 value=44.85 1459255780126025822
|
||||
> per_cpu_pct_nice,device=cpu1 value=0 1459255780126025822
|
||||
> per_cpu_pct_system,device=cpu1 value=29.55 1459255780126025822
|
||||
> per_cpu_pct_iowait,device=cpu1 value=0 1459255780126025822
|
||||
> per_cpu_pct_steal,device=cpu1 value=0 1459255780126025822
|
||||
> per_cpu_pct_idle,device=cpu1 value=25.59 1459255780126025822
|
||||
> hugepages_kbhugfree value=0 1459255780126025822
|
||||
> hugepages_kbhugused value=0 1459255780126025822
|
||||
> hugepages_pct_hugused value=0 1459255780126025822
|
||||
> interrupts_intr_per_s,device=i000 value=0 1459255780126025822
|
||||
> inode_dentunusd value=252876 1459255780126025822
|
||||
> mem_util_kbmemfree value=1613612 1459255780126025822
|
||||
> disk_tps,device=sda,vg=rootvg value=0.5 1459255780126025822
|
||||
> swap_pswpout_per_s value=0 1459255780126025822
|
||||
> network_rxpck_per_s,device=ens33 value=0 1459255780126025822
|
||||
> queue_runq-sz value=4 1459255780126025822
|
||||
> task_proc_per_s value=0 1459255780126025822
|
||||
> task_cswch_per_s value=2019 1459255780126025822
|
||||
> mem_frmpg_per_s value=0 1459255780126025822
|
||||
> mem_bufpg_per_s value=0.5 1459255780126025822
|
||||
> mem_campg_per_s value=1.25 1459255780126025822
|
||||
> interrupts_intr_per_s,device=i001 value=0 1459255780126025822
|
||||
> inode_file-nr value=19104 1459255780126025822
|
||||
> mem_util_kbmemused value=3741528 1459255780126025822
|
||||
> disk_rd_sec_per_s,device=sda,vg=rootvg value=0 1459255780126025822
|
||||
> network_txpck_per_s,device=ens33 value=0 1459255780126025822
|
||||
> queue_plist-sz value=1512 1459255780126025822
|
||||
> paging_pgpgin_per_s value=0 1459255780126025822
|
||||
> paging_pgpgout_per_s value=19 1459255780126025822
|
||||
> paging_fault_per_s value=0.25 1459255780126025822
|
||||
> paging_majflt_per_s value=0 1459255780126025822
|
||||
> paging_pgfree_per_s value=34.25 1459255780126025822
|
||||
> paging_pgscank_per_s value=0 1459255780126025822
|
||||
> paging_pgscand_per_s value=0 1459255780126025822
|
||||
> paging_pgsteal_per_s value=0 1459255780126025822
|
||||
> paging_pct_vmeff value=0 1459255780126025822
|
||||
> interrupts_intr_per_s,device=i002 value=0 1459255780126025822
|
||||
> interrupts_intr_per_s,device=i003 value=0 1459255780126025822
|
||||
> interrupts_intr_per_s,device=i004 value=0 1459255780126025822
|
||||
> interrupts_intr_per_s,device=i005 value=0 1459255780126025822
|
||||
> interrupts_intr_per_s,device=i006 value=0 1459255780126025822
|
||||
> interrupts_intr_per_s,device=i007 value=0 1459255780126025822
|
||||
> interrupts_intr_per_s,device=i008 value=0 1459255780126025822
|
||||
> interrupts_intr_per_s,device=i009 value=0 1459255780126025822
|
||||
> interrupts_intr_per_s,device=i010 value=0 1459255780126025822
|
||||
> interrupts_intr_per_s,device=i011 value=0 1459255780126025822
|
||||
> interrupts_intr_per_s,device=i012 value=0 1459255780126025822
|
||||
> interrupts_intr_per_s,device=i013 value=0 1459255780126025822
|
||||
> interrupts_intr_per_s,device=i014 value=0 1459255780126025822
|
||||
> interrupts_intr_per_s,device=i015 value=1 1459255780126025822
|
||||
> inode_inode-nr value=94709 1459255780126025822
|
||||
> inode_pty-nr value=14 1459255780126025822
|
||||
> mem_util_pct_memused value=69.87 1459255780126025822
|
||||
> mem_util_kbbuffers value=118252 1459255780126025822
|
||||
> mem_util_kbcached value=1045240 1459255780126025822
|
||||
> mem_util_kbcommit value=9628152 1459255780126025822
|
||||
> mem_util_pct_commit value=150.35 1459255780126025822
|
||||
> mem_util_kbactive value=2303752 1459255780126025822
|
||||
> mem_util_kbinact value=428340 1459255780126025822
|
||||
> mem_util_kbdirty value=104 1459255780126025822
|
||||
> mem_util_kbanonpg value=1568676 1459255780126025822
|
||||
> mem_util_kbslab value=240032 1459255780126025822
|
||||
> mem_util_kbkstack value=26224 1459255780126025822
|
||||
> mem_util_kbpgtbl value=98056 1459255780126025822
|
||||
> mem_util_kbvmused value=0 1459255780126025822
|
||||
> disk_wr_sec_per_s,device=sda,vg=rootvg value=38 1459255780126025822
|
||||
> disk_avgrq-sz,device=sda,vg=rootvg value=76 1459255780126025822
|
||||
> disk_avgqu-sz,device=sda,vg=rootvg value=0 1459255780126025822
|
||||
> disk_await,device=sda,vg=rootvg value=2 1459255780126025822
|
||||
> disk_svctm,device=sda,vg=rootvg value=2 1459255780126025822
|
||||
> disk_pct_util,device=sda,vg=rootvg value=0.1 1459255780126025822
|
||||
> network_rxkB_per_s,device=ens33 value=0 1459255780126025822
|
||||
> network_txkB_per_s,device=ens33 value=0 1459255780126025822
|
||||
> network_rxcmp_per_s,device=ens33 value=0 1459255780126025822
|
||||
> network_txcmp_per_s,device=ens33 value=0 1459255780126025822
|
||||
> network_rxmcst_per_s,device=ens33 value=0 1459255780126025822
|
||||
> network_pct_ifutil,device=ens33 value=0 1459255780126025822
|
||||
> network_rxpck_per_s,device=lo value=10.75 1459255780126025822
|
||||
> network_txpck_per_s,device=lo value=10.75 1459255780126025822
|
||||
> network_rxkB_per_s,device=lo value=0.77 1459255780126025822
|
||||
> network_txkB_per_s,device=lo value=0.77 1459255780126025822
|
||||
> network_rxcmp_per_s,device=lo value=0 1459255780126025822
|
||||
> network_txcmp_per_s,device=lo value=0 1459255780126025822
|
||||
> network_rxmcst_per_s,device=lo value=0 1459255780126025822
|
||||
> network_pct_ifutil,device=lo value=0 1459255780126025822
|
||||
> network_rxerr_per_s,device=ens33 value=0 1459255780126025822
|
||||
> network_txerr_per_s,device=ens33 value=0 1459255780126025822
|
||||
> network_coll_per_s,device=ens33 value=0 1459255780126025822
|
||||
> network_rxdrop_per_s,device=ens33 value=0 1459255780126025822
|
||||
> network_txdrop_per_s,device=ens33 value=0 1459255780126025822
|
||||
> network_txcarr_per_s,device=ens33 value=0 1459255780126025822
|
||||
> network_rxfram_per_s,device=ens33 value=0 1459255780126025822
|
||||
> network_rxfifo_per_s,device=ens33 value=0 1459255780126025822
|
||||
> network_txfifo_per_s,device=ens33 value=0 1459255780126025822
|
||||
> network_rxerr_per_s,device=lo value=0 1459255780126025822
|
||||
> network_txerr_per_s,device=lo value=0 1459255780126025822
|
||||
> network_coll_per_s,device=lo value=0 1459255780126025822
|
||||
> network_rxdrop_per_s,device=lo value=0 1459255780126025822
|
||||
> network_txdrop_per_s,device=lo value=0 1459255780126025822
|
||||
> network_txcarr_per_s,device=lo value=0 1459255780126025822
|
||||
> network_rxfram_per_s,device=lo value=0 1459255780126025822
|
||||
> network_rxfifo_per_s,device=lo value=0 1459255780126025822
|
||||
> network_txfifo_per_s,device=lo value=0 1459255780126025822
|
||||
> network_call_per_s value=0 1459255780126025822
|
||||
> network_retrans_per_s value=0 1459255780126025822
|
||||
> network_read_per_s value=0 1459255780126025822
|
||||
> network_write_per_s value=0 1459255780126025822
|
||||
> network_access_per_s value=0 1459255780126025822
|
||||
> network_getatt_per_s value=0 1459255780126025822
|
||||
> network_scall_per_s value=0 1459255780126025822
|
||||
> network_badcall_per_s value=0 1459255780126025822
|
||||
> network_packet_per_s value=0 1459255780126025822
|
||||
> network_udp_per_s value=0 1459255780126025822
|
||||
> network_tcp_per_s value=0 1459255780126025822
|
||||
> network_hit_per_s value=0 1459255780126025822
|
||||
> network_miss_per_s value=0 1459255780126025822
|
||||
> network_sread_per_s value=0 1459255780126025822
|
||||
> network_swrite_per_s value=0 1459255780126025822
|
||||
> network_saccess_per_s value=0 1459255780126025822
|
||||
> network_sgetatt_per_s value=0 1459255780126025822
|
||||
> network_totsck value=4234 1459255780126025822
|
||||
> network_tcpsck value=1637 1459255780126025822
|
||||
> network_udpsck value=2 1459255780126025822
|
||||
> network_rawsck value=0 1459255780126025822
|
||||
> network_ip-frag value=0 1459255780126025822
|
||||
> network_tcp-tw value=4 1459255780126025822
|
||||
> network_irec_per_s value=10.75 1459255780126025822
|
||||
> network_fwddgm_per_s value=0 1459255780126025822
|
||||
> network_idel_per_s value=10.75 1459255780126025822
|
||||
> network_orq_per_s value=10.75 1459255780126025822
|
||||
> network_asmrq_per_s value=0 1459255780126025822
|
||||
> network_asmok_per_s value=0 1459255780126025822
|
||||
> network_fragok_per_s value=0 1459255780126025822
|
||||
> network_fragcrt_per_s value=0 1459255780126025822
|
||||
> network_ihdrerr_per_s value=0 1459255780126025822
|
||||
> network_iadrerr_per_s value=0 1459255780126025822
|
||||
> network_iukwnpr_per_s value=0 1459255780126025822
|
||||
> network_idisc_per_s value=0 1459255780126025822
|
||||
> network_odisc_per_s value=0 1459255780126025822
|
||||
> network_onort_per_s value=0 1459255780126025822
|
||||
> network_asmf_per_s value=0 1459255780126025822
|
||||
> network_fragf_per_s value=0 1459255780126025822
|
||||
> network_imsg_per_s value=0 1459255780126025822
|
||||
> network_omsg_per_s value=0 1459255780126025822
|
||||
> network_iech_per_s value=0 1459255780126025822
|
||||
> network_iechr_per_s value=0 1459255780126025822
|
||||
> network_oech_per_s value=0 1459255780126025822
|
||||
> network_oechr_per_s value=0 1459255780126025822
|
||||
> network_itm_per_s value=0 1459255780126025822
|
||||
> network_itmr_per_s value=0 1459255780126025822
|
||||
> network_otm_per_s value=0 1459255780126025822
|
||||
> network_otmr_per_s value=0 1459255780126025822
|
||||
> network_iadrmk_per_s value=0 1459255780126025822
|
||||
> network_iadrmkr_per_s value=0 1459255780126025822
|
||||
> network_oadrmk_per_s value=0 1459255780126025822
|
||||
> network_oadrmkr_per_s value=0 1459255780126025822
|
||||
> network_ierr_per_s value=0 1459255780126025822
|
||||
> network_oerr_per_s value=0 1459255780126025822
|
||||
> network_idstunr_per_s value=0 1459255780126025822
|
||||
> network_odstunr_per_s value=0 1459255780126025822
|
||||
> network_itmex_per_s value=0 1459255780126025822
|
||||
> network_otmex_per_s value=0 1459255780126025822
|
||||
> network_iparmpb_per_s value=0 1459255780126025822
|
||||
> network_oparmpb_per_s value=0 1459255780126025822
|
||||
> network_isrcq_per_s value=0 1459255780126025822
|
||||
> network_osrcq_per_s value=0 1459255780126025822
|
||||
> network_iredir_per_s value=0 1459255780126025822
|
||||
> network_oredir_per_s value=0 1459255780126025822
|
||||
> network_active_per_s value=0 1459255780126025822
|
||||
> network_passive_per_s value=0 1459255780126025822
|
||||
> network_iseg_per_s value=10.75 1459255780126025822
|
||||
> network_oseg_per_s value=9.5 1459255780126025822
|
||||
> network_atmptf_per_s value=0 1459255780126025822
|
||||
> network_estres_per_s value=0 1459255780126025822
|
||||
> network_retrans_per_s value=1.5 1459255780126025822
|
||||
> network_isegerr_per_s value=0.25 1459255780126025822
|
||||
> network_orsts_per_s value=0 1459255780126025822
|
||||
> network_idgm_per_s value=0 1459255780126025822
|
||||
> network_odgm_per_s value=0 1459255780126025822
|
||||
> network_noport_per_s value=0 1459255780126025822
|
||||
> network_idgmerr_per_s value=0 1459255780126025822
|
||||
> queue_ldavg-1 value=2.1 1459255780126025822
|
||||
> queue_ldavg-5 value=1.82 1459255780126025822
|
||||
> queue_ldavg-15 value=1.44 1459255780126025822
|
||||
> queue_blocked value=0 1459255780126025822
|
||||
> swap_util_kbswpfree value=1048572 1459255780126025822
|
||||
> swap_util_kbswpused value=0 1459255780126025822
|
||||
> swap_util_pct_swpused value=0 1459255780126025822
|
||||
> swap_util_kbswpcad value=0 1459255780126025822
|
||||
> swap_util_pct_swpcad value=0 1459255780126025822
|
||||
```
|
||||
324
plugins/inputs/sysstat/sysstat.go
Normal file
324
plugins/inputs/sysstat/sysstat.go
Normal file
@@ -0,0 +1,324 @@
|
||||
// +build linux
|
||||
|
||||
package sysstat
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
var (
|
||||
firstTimestamp time.Time
|
||||
execCommand = exec.Command // execCommand is used to mock commands in tests.
|
||||
dfltActivities = []string{"DISK"}
|
||||
)
|
||||
|
||||
const parseInterval = 1 // parseInterval is the interval (in seconds) where the parsing of the binary file takes place.
|
||||
|
||||
type Sysstat struct {
|
||||
// Sadc represents the path to the sadc collector utility.
|
||||
Sadc string `toml:"sadc_path"`
|
||||
|
||||
// Sadf represents the path to the sadf cmd.
|
||||
Sadf string `toml:"sadf_path"`
|
||||
|
||||
// Activities is a list of activities that are passed as argument to the
|
||||
// collector utility (e.g: DISK, SNMP etc...)
|
||||
// The more activities that are added, the more data is collected.
|
||||
Activities []string
|
||||
|
||||
// Options is a map of options.
|
||||
//
|
||||
// The key represents the actual option that the Sadf command is called with and
|
||||
// the value represents the description for that option.
|
||||
//
|
||||
// For example, if you have the following options map:
|
||||
// map[string]string{"-C": "cpu", "-d": "disk"}
|
||||
// The Sadf command is run with the options -C and -d to extract cpu and
|
||||
// disk metrics from the collected binary file.
|
||||
//
|
||||
// If Group is false (see below), each metric will be prefixed with the corresponding description
|
||||
// and represents itself a measurement.
|
||||
//
|
||||
// If Group is true, metrics are grouped to a single measurement with the corresponding description as name.
|
||||
Options map[string]string
|
||||
|
||||
// Group determines if metrics are grouped or not.
|
||||
Group bool
|
||||
|
||||
// DeviceTags adds the possibility to add additional tags for devices.
|
||||
DeviceTags map[string][]map[string]string `toml:"device_tags"`
|
||||
tmpFile string
|
||||
interval int
|
||||
}
|
||||
|
||||
func (*Sysstat) Description() string {
|
||||
return "Sysstat metrics collector"
|
||||
}
|
||||
|
||||
var sampleConfig = `
|
||||
## Path to the sadc command.
|
||||
#
|
||||
## Common Defaults:
|
||||
## Debian/Ubuntu: /usr/lib/sysstat/sadc
|
||||
## Arch: /usr/lib/sa/sadc
|
||||
## RHEL/CentOS: /usr/lib64/sa/sadc
|
||||
sadc_path = "/usr/lib/sa/sadc" # required
|
||||
#
|
||||
#
|
||||
## Path to the sadf command, if it is not in PATH
|
||||
# sadf_path = "/usr/bin/sadf"
|
||||
#
|
||||
#
|
||||
## Activities is a list of activities, that are passed as argument to the
|
||||
## sadc collector utility (e.g: DISK, SNMP etc...)
|
||||
## The more activities that are added, the more data is collected.
|
||||
# activities = ["DISK"]
|
||||
#
|
||||
#
|
||||
## Group metrics to measurements.
|
||||
##
|
||||
## If group is false each metric will be prefixed with a description
|
||||
## and represents itself a measurement.
|
||||
##
|
||||
## If Group is true, corresponding metrics are grouped to a single measurement.
|
||||
# group = true
|
||||
#
|
||||
#
|
||||
## Options for the sadf command. The values on the left represent the sadf options and
|
||||
## the values on the right their description (wich are used for grouping and prefixing metrics).
|
||||
##
|
||||
## Run 'sar -h' or 'man sar' to find out the supported options for your sysstat version.
|
||||
[inputs.sysstat.options]
|
||||
-C = "cpu"
|
||||
-B = "paging"
|
||||
-b = "io"
|
||||
-d = "disk" # requires DISK activity
|
||||
"-n ALL" = "network"
|
||||
"-P ALL" = "per_cpu"
|
||||
-q = "queue"
|
||||
-R = "mem"
|
||||
-r = "mem_util"
|
||||
-S = "swap_util"
|
||||
-u = "cpu_util"
|
||||
-v = "inode"
|
||||
-W = "swap"
|
||||
-w = "task"
|
||||
# -H = "hugepages" # only available for newer linux distributions
|
||||
# "-I ALL" = "interrupts" # requires INT activity
|
||||
#
|
||||
#
|
||||
## Device tags can be used to add additional tags for devices. For example the configuration below
|
||||
## adds a tag vg with value rootvg for all metrics with sda devices.
|
||||
# [[inputs.sysstat.device_tags.sda]]
|
||||
# vg = "rootvg"
|
||||
`
|
||||
|
||||
func (*Sysstat) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func (s *Sysstat) Gather(acc telegraf.Accumulator) error {
|
||||
if s.interval == 0 {
|
||||
if firstTimestamp.IsZero() {
|
||||
firstTimestamp = time.Now()
|
||||
} else {
|
||||
s.interval = int(time.Since(firstTimestamp).Seconds())
|
||||
}
|
||||
}
|
||||
ts := time.Now().Add(time.Duration(s.interval) * time.Second)
|
||||
if err := s.collect(); err != nil {
|
||||
return err
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
errorChannel := make(chan error, len(s.Options)*2)
|
||||
for option := range s.Options {
|
||||
wg.Add(1)
|
||||
go func(acc telegraf.Accumulator, option string) {
|
||||
defer wg.Done()
|
||||
if err := s.parse(acc, option, ts); err != nil {
|
||||
errorChannel <- err
|
||||
}
|
||||
}(acc, option)
|
||||
}
|
||||
wg.Wait()
|
||||
close(errorChannel)
|
||||
|
||||
errorStrings := []string{}
|
||||
for err := range errorChannel {
|
||||
errorStrings = append(errorStrings, err.Error())
|
||||
}
|
||||
|
||||
if _, err := os.Stat(s.tmpFile); err == nil {
|
||||
if err := os.Remove(s.tmpFile); err != nil {
|
||||
errorStrings = append(errorStrings, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if len(errorStrings) == 0 {
|
||||
return nil
|
||||
}
|
||||
return errors.New(strings.Join(errorStrings, "\n"))
|
||||
}
|
||||
|
||||
// collect collects sysstat data with the collector utility sadc. It runs the following command:
|
||||
// Sadc -S <Activity1> -S <Activity2> ... <collectInterval> 2 tmpFile
|
||||
// The above command collects system metrics during <collectInterval> and saves it in binary form to tmpFile.
|
||||
func (s *Sysstat) collect() error {
|
||||
options := []string{}
|
||||
for _, act := range s.Activities {
|
||||
options = append(options, "-S", act)
|
||||
}
|
||||
s.tmpFile = path.Join("/tmp", fmt.Sprintf("sysstat-%d", time.Now().Unix()))
|
||||
collectInterval := s.interval - parseInterval // collectInterval has to be smaller than the telegraf data collection interval
|
||||
|
||||
if collectInterval < 0 { // If true, interval is not defined yet and Gather is run for the first time.
|
||||
collectInterval = 1 // In that case we only collect for 1 second.
|
||||
}
|
||||
|
||||
options = append(options, strconv.Itoa(collectInterval), "2", s.tmpFile)
|
||||
cmd := execCommand(s.Sadc, options...)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to run command %s: %s", strings.Join(cmd.Args, " "), string(out))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// parse runs Sadf on the previously saved tmpFile:
|
||||
// Sadf -p -- -p <option> tmpFile
|
||||
// and parses the output to add it to the telegraf.Accumulator acc.
|
||||
func (s *Sysstat) parse(acc telegraf.Accumulator, option string, ts time.Time) error {
|
||||
cmd := execCommand(s.Sadf, s.sadfOptions(option)...)
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("running command '%s' failed: %s", strings.Join(cmd.Args, " "), err)
|
||||
}
|
||||
|
||||
r := bufio.NewReader(stdout)
|
||||
csv := csv.NewReader(r)
|
||||
csv.Comma = '\t'
|
||||
csv.FieldsPerRecord = 6
|
||||
var measurement string
|
||||
// groupData to accumulate data when Group=true
|
||||
type groupData struct {
|
||||
tags map[string]string
|
||||
fields map[string]interface{}
|
||||
}
|
||||
m := make(map[string]groupData)
|
||||
for {
|
||||
record, err := csv.Read()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
device := record[3]
|
||||
value, err := strconv.ParseFloat(record[5], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tags := map[string]string{}
|
||||
if device != "-" {
|
||||
tags["device"] = device
|
||||
if addTags, ok := s.DeviceTags[device]; ok {
|
||||
for _, tag := range addTags {
|
||||
for k, v := range tag {
|
||||
tags[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if s.Group {
|
||||
measurement = s.Options[option]
|
||||
if _, ok := m[device]; !ok {
|
||||
m[device] = groupData{
|
||||
fields: make(map[string]interface{}),
|
||||
tags: make(map[string]string),
|
||||
}
|
||||
}
|
||||
g, _ := m[device]
|
||||
if len(g.tags) == 0 {
|
||||
for k, v := range tags {
|
||||
g.tags[k] = v
|
||||
}
|
||||
}
|
||||
g.fields[escape(record[4])] = value
|
||||
} else {
|
||||
measurement = s.Options[option] + "_" + escape(record[4])
|
||||
fields := map[string]interface{}{
|
||||
"value": value,
|
||||
}
|
||||
acc.AddFields(measurement, fields, tags, ts)
|
||||
}
|
||||
|
||||
}
|
||||
if s.Group {
|
||||
for _, v := range m {
|
||||
acc.AddFields(measurement, v.fields, v.tags, ts)
|
||||
}
|
||||
}
|
||||
if err := cmd.Wait(); err != nil {
|
||||
return fmt.Errorf("command %s failed with %s", strings.Join(cmd.Args, " "), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// sadfOptions creates the correct options for the sadf utility.
|
||||
func (s *Sysstat) sadfOptions(activityOption string) []string {
|
||||
options := []string{
|
||||
"-p",
|
||||
"--",
|
||||
"-p",
|
||||
}
|
||||
|
||||
opts := strings.Split(activityOption, " ")
|
||||
options = append(options, opts...)
|
||||
options = append(options, s.tmpFile)
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
// escape removes % and / chars in field names
|
||||
func escape(dirty string) string {
|
||||
var fieldEscaper = strings.NewReplacer(
|
||||
`%`, "pct_",
|
||||
`/`, "_per_",
|
||||
)
|
||||
return fieldEscaper.Replace(dirty)
|
||||
}
|
||||
|
||||
func init() {
|
||||
s := Sysstat{
|
||||
Group: true,
|
||||
Activities: dfltActivities,
|
||||
}
|
||||
sadf, _ := exec.LookPath("sadf")
|
||||
if len(sadf) > 0 {
|
||||
s.Sadf = sadf
|
||||
}
|
||||
inputs.Add("sysstat", func() telegraf.Input {
|
||||
return &s
|
||||
})
|
||||
}
|
||||
41
plugins/inputs/sysstat/sysstat_interval_test.go
Normal file
41
plugins/inputs/sysstat/sysstat_interval_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
// +build !race
|
||||
// +build linux
|
||||
|
||||
package sysstat
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
)
|
||||
|
||||
// TestInterval verifies that the correct interval is created. It is not
|
||||
// run with -race option, because in that scenario interval between the two
|
||||
// Gather calls is greater than wantedInterval.
|
||||
func TestInterval(t *testing.T) {
|
||||
// overwriting exec commands with mock commands
|
||||
execCommand = fakeExecCommand
|
||||
defer func() { execCommand = exec.Command }()
|
||||
var acc testutil.Accumulator
|
||||
|
||||
s.interval = 0
|
||||
wantedInterval := 3
|
||||
|
||||
err := s.Gather(&acc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
time.Sleep(time.Duration(wantedInterval) * time.Second)
|
||||
|
||||
err = s.Gather(&acc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if s.interval != wantedInterval {
|
||||
t.Errorf("wrong interval: got %d, want %d", s.interval, wantedInterval)
|
||||
}
|
||||
}
|
||||
3
plugins/inputs/sysstat/sysstat_notlinux.go
Normal file
3
plugins/inputs/sysstat/sysstat_notlinux.go
Normal file
@@ -0,0 +1,3 @@
|
||||
// +build !linux
|
||||
|
||||
package sysstat
|
||||
306
plugins/inputs/sysstat/sysstat_test.go
Normal file
306
plugins/inputs/sysstat/sysstat_test.go
Normal file
@@ -0,0 +1,306 @@
|
||||
// +build linux
|
||||
|
||||
package sysstat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
)
|
||||
|
||||
var s = Sysstat{
|
||||
interval: 10,
|
||||
Sadc: "/usr/lib/sa/sadc",
|
||||
Sadf: "/usr/bin/sadf",
|
||||
Group: false,
|
||||
Activities: []string{"DISK", "SNMP"},
|
||||
Options: map[string]string{
|
||||
"C": "cpu",
|
||||
"d": "disk",
|
||||
},
|
||||
DeviceTags: map[string][]map[string]string{
|
||||
"sda": {
|
||||
{
|
||||
"vg": "rootvg",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestGather(t *testing.T) {
|
||||
// overwriting exec commands with mock commands
|
||||
execCommand = fakeExecCommand
|
||||
defer func() { execCommand = exec.Command }()
|
||||
var acc testutil.Accumulator
|
||||
|
||||
err := s.Gather(&acc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cpuTags := map[string]string{"device": "all"}
|
||||
diskTags := map[string]string{"device": "sda", "vg": "rootvg"}
|
||||
tests := []struct {
|
||||
measurement string
|
||||
fields map[string]interface{}
|
||||
tags map[string]string
|
||||
}{
|
||||
{
|
||||
"cpu_pct_user",
|
||||
map[string]interface{}{
|
||||
"value": 0.65,
|
||||
},
|
||||
cpuTags,
|
||||
},
|
||||
{
|
||||
"cpu_pct_nice",
|
||||
map[string]interface{}{
|
||||
"value": 0.0,
|
||||
},
|
||||
cpuTags,
|
||||
},
|
||||
{
|
||||
"cpu_pct_system",
|
||||
map[string]interface{}{
|
||||
"value": 0.10,
|
||||
},
|
||||
cpuTags,
|
||||
},
|
||||
{
|
||||
"cpu_pct_iowait",
|
||||
map[string]interface{}{
|
||||
"value": 0.15,
|
||||
},
|
||||
cpuTags,
|
||||
},
|
||||
{
|
||||
"cpu_pct_steal",
|
||||
map[string]interface{}{
|
||||
"value": 0.0,
|
||||
},
|
||||
cpuTags,
|
||||
},
|
||||
{
|
||||
"cpu_pct_idle",
|
||||
map[string]interface{}{
|
||||
"value": 99.1,
|
||||
},
|
||||
cpuTags,
|
||||
},
|
||||
{
|
||||
"disk_tps",
|
||||
map[string]interface{}{
|
||||
"value": 0.00,
|
||||
},
|
||||
diskTags,
|
||||
},
|
||||
{
|
||||
"disk_rd_sec_per_s",
|
||||
map[string]interface{}{
|
||||
"value": 0.00,
|
||||
},
|
||||
diskTags,
|
||||
},
|
||||
{
|
||||
"disk_wr_sec_per_s",
|
||||
map[string]interface{}{
|
||||
"value": 0.00,
|
||||
},
|
||||
diskTags,
|
||||
},
|
||||
{
|
||||
"disk_avgrq-sz",
|
||||
map[string]interface{}{
|
||||
"value": 0.00,
|
||||
},
|
||||
diskTags,
|
||||
},
|
||||
{
|
||||
"disk_avgqu-sz",
|
||||
map[string]interface{}{
|
||||
"value": 0.00,
|
||||
},
|
||||
diskTags,
|
||||
},
|
||||
{
|
||||
"disk_await",
|
||||
map[string]interface{}{
|
||||
"value": 0.00,
|
||||
},
|
||||
diskTags,
|
||||
},
|
||||
{
|
||||
"disk_svctm",
|
||||
map[string]interface{}{
|
||||
"value": 0.00,
|
||||
},
|
||||
diskTags,
|
||||
},
|
||||
{
|
||||
"disk_pct_util",
|
||||
map[string]interface{}{
|
||||
"value": 0.00,
|
||||
},
|
||||
diskTags,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
acc.AssertContainsTaggedFields(t, test.measurement, test.fields, test.tags)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGatherGrouped(t *testing.T) {
|
||||
s.Group = true
|
||||
// overwriting exec commands with mock commands
|
||||
execCommand = fakeExecCommand
|
||||
defer func() { execCommand = exec.Command }()
|
||||
var acc testutil.Accumulator
|
||||
|
||||
err := s.Gather(&acc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
measurement string
|
||||
fields map[string]interface{}
|
||||
tags map[string]string
|
||||
}{
|
||||
{
|
||||
"cpu",
|
||||
map[string]interface{}{
|
||||
"pct_user": 0.65,
|
||||
"pct_nice": 0.0,
|
||||
"pct_system": 0.10,
|
||||
"pct_iowait": 0.15,
|
||||
"pct_steal": 0.0,
|
||||
"pct_idle": 99.1,
|
||||
},
|
||||
map[string]string{"device": "all"},
|
||||
},
|
||||
{
|
||||
"disk",
|
||||
map[string]interface{}{
|
||||
"tps": 0.00,
|
||||
"rd_sec_per_s": 0.00,
|
||||
"wr_sec_per_s": 0.00,
|
||||
"avgrq-sz": 0.00,
|
||||
"avgqu-sz": 0.00,
|
||||
"await": 0.00,
|
||||
"svctm": 0.00,
|
||||
"pct_util": 0.00,
|
||||
},
|
||||
map[string]string{"device": "sda", "vg": "rootvg"},
|
||||
},
|
||||
{
|
||||
"disk",
|
||||
map[string]interface{}{
|
||||
"tps": 2.01,
|
||||
"rd_sec_per_s": 1.0,
|
||||
"wr_sec_per_s": 0.00,
|
||||
"avgrq-sz": 0.30,
|
||||
"avgqu-sz": 0.60,
|
||||
"await": 0.70,
|
||||
"svctm": 0.20,
|
||||
"pct_util": 0.30,
|
||||
},
|
||||
map[string]string{"device": "sdb"},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
acc.AssertContainsTaggedFields(t, test.measurement, test.fields, test.tags)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEscape(t *testing.T) {
|
||||
var tests = []struct {
|
||||
input string
|
||||
escaped string
|
||||
}{
|
||||
{
|
||||
"%util",
|
||||
"pct_util",
|
||||
},
|
||||
{
|
||||
"bread/s",
|
||||
"bread_per_s",
|
||||
},
|
||||
{
|
||||
"%nice",
|
||||
"pct_nice",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
if test.escaped != escape(test.input) {
|
||||
t.Errorf("wrong escape, got %s, wanted %s", escape(test.input), test.escaped)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 -- sadf -p -- -p -C tmpFile
|
||||
// it returns mockData["C"] output.
|
||||
func TestHelperProcess(t *testing.T) {
|
||||
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
|
||||
return
|
||||
}
|
||||
|
||||
mockData := map[string]string{
|
||||
|
||||
"C": `dell-xps 5 2016-03-25 16:18:10 UTC all %user 0.65
|
||||
dell-xps 5 2016-03-25 16:18:10 UTC all %nice 0.00
|
||||
dell-xps 5 2016-03-25 16:18:10 UTC all %system 0.10
|
||||
dell-xps 5 2016-03-25 16:18:10 UTC all %iowait 0.15
|
||||
dell-xps 5 2016-03-25 16:18:10 UTC all %steal 0.00
|
||||
dell-xps 5 2016-03-25 16:18:10 UTC all %idle 99.10
|
||||
`,
|
||||
|
||||
"d": `dell-xps 5 2016-03-25 16:18:10 UTC sda tps 0.00
|
||||
dell-xps 5 2016-03-25 16:18:10 UTC sda rd_sec/s 0.00
|
||||
dell-xps 5 2016-03-25 16:18:10 UTC sda wr_sec/s 0.00
|
||||
dell-xps 5 2016-03-25 16:18:10 UTC sda avgrq-sz 0.00
|
||||
dell-xps 5 2016-03-25 16:18:10 UTC sda avgqu-sz 0.00
|
||||
dell-xps 5 2016-03-25 16:18:10 UTC sda await 0.00
|
||||
dell-xps 5 2016-03-25 16:18:10 UTC sda svctm 0.00
|
||||
dell-xps 5 2016-03-25 16:18:10 UTC sda %util 0.00
|
||||
dell-xps 5 2016-03-25 16:18:10 UTC sdb tps 2.01
|
||||
dell-xps 5 2016-03-25 16:18:10 UTC sdb rd_sec/s 1.00
|
||||
dell-xps 5 2016-03-25 16:18:10 UTC sdb wr_sec/s 0.00
|
||||
dell-xps 5 2016-03-25 16:18:10 UTC sdb avgrq-sz 0.30
|
||||
dell-xps 5 2016-03-25 16:18:10 UTC sdb avgqu-sz 0.60
|
||||
dell-xps 5 2016-03-25 16:18:10 UTC sdb await 0.70
|
||||
dell-xps 5 2016-03-25 16:18:10 UTC sdb svctm 0.20
|
||||
dell-xps 5 2016-03-25 16:18:10 UTC sdb %util 0.30
|
||||
`,
|
||||
}
|
||||
|
||||
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:]
|
||||
// Handle the case where args[0] is dir:...
|
||||
|
||||
switch path.Base(cmd) {
|
||||
case "sadf":
|
||||
fmt.Fprint(os.Stdout, mockData[args[3]])
|
||||
default:
|
||||
}
|
||||
// some code here to check arguments perhaps?
|
||||
os.Exit(0)
|
||||
}
|
||||
@@ -144,11 +144,18 @@ func (p *Processes) gatherFromProc(fields map[string]interface{}) error {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse out data after (<cmd name>)
|
||||
i := bytes.LastIndex(data, []byte(")"))
|
||||
if i == -1 {
|
||||
continue
|
||||
}
|
||||
data = data[i+2:]
|
||||
|
||||
stats := bytes.Fields(data)
|
||||
if len(stats) < 3 {
|
||||
return fmt.Errorf("Something is terribly wrong with %s", statFile)
|
||||
}
|
||||
switch stats[2][0] {
|
||||
switch stats[0][0] {
|
||||
case 'R':
|
||||
fields["running"] = fields["running"].(int64) + int64(1)
|
||||
case 'S':
|
||||
@@ -163,11 +170,11 @@ func (p *Processes) gatherFromProc(fields map[string]interface{}) error {
|
||||
fields["paging"] = fields["paging"].(int64) + int64(1)
|
||||
default:
|
||||
log.Printf("processes: Unknown state [ %s ] in file %s",
|
||||
string(stats[2][0]), statFile)
|
||||
string(stats[0][0]), statFile)
|
||||
}
|
||||
fields["total"] = fields["total"].(int64) + int64(1)
|
||||
|
||||
threads, err := strconv.Atoi(string(stats[19]))
|
||||
threads, err := strconv.Atoi(string(stats[17]))
|
||||
if err != nil {
|
||||
log.Printf("processes: Error parsing thread count: %s", err)
|
||||
continue
|
||||
|
||||
@@ -82,6 +82,28 @@ func TestFromProcFiles(t *testing.T) {
|
||||
acc.AssertContainsTaggedFields(t, "processes", fields, map[string]string{})
|
||||
}
|
||||
|
||||
func TestFromProcFilesWithSpaceInCmd(t *testing.T) {
|
||||
if runtime.GOOS != "linux" {
|
||||
t.Skip("This test only runs on linux")
|
||||
}
|
||||
tester := tester{}
|
||||
processes := &Processes{
|
||||
readProcFile: tester.testProcFile2,
|
||||
forceProc: true,
|
||||
}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err := processes.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
fields := getEmptyFields()
|
||||
fields["sleeping"] = tester.calls
|
||||
fields["total_threads"] = tester.calls * 2
|
||||
fields["total"] = tester.calls
|
||||
|
||||
acc.AssertContainsTaggedFields(t, "processes", fields, map[string]string{})
|
||||
}
|
||||
|
||||
func testExecPS() ([]byte, error) {
|
||||
return []byte(testPSOut), nil
|
||||
}
|
||||
@@ -96,6 +118,11 @@ func (t *tester) testProcFile(_ string) ([]byte, error) {
|
||||
return []byte(fmt.Sprintf(testProcStat, "S", "2")), nil
|
||||
}
|
||||
|
||||
func (t *tester) testProcFile2(_ string) ([]byte, error) {
|
||||
t.calls++
|
||||
return []byte(fmt.Sprintf(testProcStat2, "S", "2")), nil
|
||||
}
|
||||
|
||||
func testExecPSError() ([]byte, error) {
|
||||
return []byte(testPSOut), fmt.Errorf("ERROR!")
|
||||
}
|
||||
@@ -149,3 +176,6 @@ S+
|
||||
|
||||
const testProcStat = `10 (rcuob/0) %s 2 0 0 0 -1 2129984 0 0 0 0 0 0 0 0 20 0 %s 0 11 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744073709551615 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
`
|
||||
|
||||
const testProcStat2 = `10 (rcuob 0) %s 2 0 0 0 -1 2129984 0 0 0 0 0 0 0 0 20 0 %s 0 11 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744073709551615 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
`
|
||||
|
||||
@@ -22,7 +22,8 @@ This is a sample configuration for the plugin.
|
||||
## Maximum number of concurrent TCP connections to allow
|
||||
max_tcp_connections = 250
|
||||
|
||||
## Data format to consume. This can be "json", "influx" or "graphite"
|
||||
## Data format to consume.
|
||||
|
||||
## Each data format has it's own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md
|
||||
|
||||
@@ -39,7 +39,7 @@ type TcpListener struct {
|
||||
acc telegraf.Accumulator
|
||||
}
|
||||
|
||||
var dropwarn = "ERROR: Message queue full. Discarding line [%s] " +
|
||||
var dropwarn = "ERROR: Message queue full. Discarding metric [%s], " +
|
||||
"You may want to increase allowed_pending_messages in the config\n"
|
||||
|
||||
const sampleConfig = `
|
||||
@@ -193,6 +193,7 @@ func (t *TcpListener) handler(conn *net.TCPConn, id string) {
|
||||
t.forget(id)
|
||||
}()
|
||||
|
||||
var n int
|
||||
scanner := bufio.NewScanner(conn)
|
||||
for {
|
||||
select {
|
||||
@@ -202,11 +203,17 @@ func (t *TcpListener) handler(conn *net.TCPConn, id string) {
|
||||
if !scanner.Scan() {
|
||||
return
|
||||
}
|
||||
buf := scanner.Bytes()
|
||||
n = len(scanner.Bytes())
|
||||
if n == 0 {
|
||||
continue
|
||||
}
|
||||
bufCopy := make([]byte, n)
|
||||
copy(bufCopy, scanner.Bytes())
|
||||
|
||||
select {
|
||||
case t.in <- buf:
|
||||
case t.in <- bufCopy:
|
||||
default:
|
||||
log.Printf(dropwarn, string(buf))
|
||||
log.Printf(dropwarn, scanner.Text())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -215,11 +222,12 @@ func (t *TcpListener) handler(conn *net.TCPConn, id string) {
|
||||
// tcpParser parses the incoming tcp byte packets
|
||||
func (t *TcpListener) tcpParser() error {
|
||||
defer t.wg.Done()
|
||||
var packet []byte
|
||||
for {
|
||||
select {
|
||||
case <-t.done:
|
||||
return nil
|
||||
case packet := <-t.in:
|
||||
case packet = <-t.in:
|
||||
if len(packet) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -23,7 +23,8 @@ This is a sample configuration for the plugin.
|
||||
## usually 1500 bytes.
|
||||
udp_packet_size = 1500
|
||||
|
||||
## Data format to consume. This can be "json", "influx" or "graphite"
|
||||
## Data format to consume.
|
||||
|
||||
## Each data format has it's own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md
|
||||
|
||||
@@ -12,7 +12,11 @@ import (
|
||||
)
|
||||
|
||||
type UdpListener struct {
|
||||
ServiceAddress string
|
||||
ServiceAddress string
|
||||
// UDPPacketSize is deprecated, it's only here for legacy support
|
||||
// we now always create 1 max size buffer and then copy only what we need
|
||||
// into the in channel
|
||||
// see https://github.com/influxdata/telegraf/pull/992
|
||||
UDPPacketSize int `toml:"udp_packet_size"`
|
||||
AllowedPendingMessages int
|
||||
|
||||
@@ -30,7 +34,9 @@ type UdpListener struct {
|
||||
listener *net.UDPConn
|
||||
}
|
||||
|
||||
const UDP_PACKET_SIZE int = 1500
|
||||
// UDP packet limit, see
|
||||
// https://en.wikipedia.org/wiki/User_Datagram_Protocol#Packet_structure
|
||||
const UDP_MAX_PACKET_SIZE int = 64 * 1024
|
||||
|
||||
var dropwarn = "ERROR: Message queue full. Discarding line [%s] " +
|
||||
"You may want to increase allowed_pending_messages in the config\n"
|
||||
@@ -43,11 +49,6 @@ const sampleConfig = `
|
||||
## UDP listener will start dropping packets.
|
||||
allowed_pending_messages = 10000
|
||||
|
||||
## UDP packet size for the server to listen for. This will depend
|
||||
## on the size of the packets that the client is sending, which is
|
||||
## usually 1500 bytes, but can be as large as 65,535 bytes.
|
||||
udp_packet_size = 1500
|
||||
|
||||
## Data format to consume.
|
||||
## Each data format has it's own unique set of configuration options, read
|
||||
## more about them here:
|
||||
@@ -107,22 +108,24 @@ func (u *UdpListener) udpListen() error {
|
||||
}
|
||||
log.Println("UDP server listening on: ", u.listener.LocalAddr().String())
|
||||
|
||||
buf := make([]byte, UDP_MAX_PACKET_SIZE)
|
||||
for {
|
||||
select {
|
||||
case <-u.done:
|
||||
return nil
|
||||
default:
|
||||
buf := make([]byte, u.UDPPacketSize)
|
||||
n, _, err := u.listener.ReadFromUDP(buf)
|
||||
if err != nil && !strings.Contains(err.Error(), "closed network") {
|
||||
log.Printf("ERROR: %s\n", err.Error())
|
||||
continue
|
||||
}
|
||||
bufCopy := make([]byte, n)
|
||||
copy(bufCopy, buf[:n])
|
||||
|
||||
select {
|
||||
case u.in <- buf[:n]:
|
||||
case u.in <- bufCopy:
|
||||
default:
|
||||
log.Printf(dropwarn, string(buf[:n]))
|
||||
log.Printf(dropwarn, string(bufCopy))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,11 +133,13 @@ func (u *UdpListener) udpListen() error {
|
||||
|
||||
func (u *UdpListener) udpParser() error {
|
||||
defer u.wg.Done()
|
||||
|
||||
var packet []byte
|
||||
for {
|
||||
select {
|
||||
case <-u.done:
|
||||
return nil
|
||||
case packet := <-u.in:
|
||||
case packet = <-u.in:
|
||||
metrics, err := u.parser.Parse(packet)
|
||||
if err == nil {
|
||||
u.storeMetrics(metrics)
|
||||
@@ -156,8 +161,6 @@ func (u *UdpListener) storeMetrics(metrics []telegraf.Metric) error {
|
||||
|
||||
func init() {
|
||||
inputs.Add("udp_listener", func() telegraf.Input {
|
||||
return &UdpListener{
|
||||
UDPPacketSize: UDP_PACKET_SIZE,
|
||||
}
|
||||
return &UdpListener{}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,13 +1,34 @@
|
||||
# Graphite Output Plugin
|
||||
|
||||
This plugin writes to [Graphite](http://graphite.readthedocs.org/en/latest/index.html) via raw TCP.
|
||||
This plugin writes to [Graphite](http://graphite.readthedocs.org/en/latest/index.html)
|
||||
via raw TCP.
|
||||
|
||||
## Configuration:
|
||||
|
||||
```toml
|
||||
# Configuration for Graphite server to send metrics to
|
||||
[[outputs.graphite]]
|
||||
## TCP endpoint for your graphite instance.
|
||||
servers = ["localhost:2003"]
|
||||
## Prefix metrics name
|
||||
prefix = ""
|
||||
## Graphite output template
|
||||
## see https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md
|
||||
template = "host.tags.measurement.field"
|
||||
## timeout in seconds for the write connection to graphite
|
||||
timeout = 2
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
Servers []string
|
||||
Prefix string
|
||||
Timeout int
|
||||
Servers []string
|
||||
Prefix string
|
||||
Timeout int
|
||||
Template string
|
||||
|
||||
* `servers`: List of strings, ["mygraphiteserver:2003"].
|
||||
* `prefix`: String use to prefix all sent metrics.
|
||||
* `timeout`: Connection timeout in second.
|
||||
* `timeout`: Connection timeout in seconds.
|
||||
* `template`: Template for graphite output format, see
|
||||
https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md
|
||||
for more details.
|
||||
|
||||
@@ -16,10 +16,11 @@ import (
|
||||
|
||||
type Graphite struct {
|
||||
// URL is only for backwards compatability
|
||||
Servers []string
|
||||
Prefix string
|
||||
Timeout int
|
||||
conns []net.Conn
|
||||
Servers []string
|
||||
Prefix string
|
||||
Template string
|
||||
Timeout int
|
||||
conns []net.Conn
|
||||
}
|
||||
|
||||
var sampleConfig = `
|
||||
@@ -27,6 +28,9 @@ var sampleConfig = `
|
||||
servers = ["localhost:2003"]
|
||||
## Prefix metrics name
|
||||
prefix = ""
|
||||
## Graphite output template
|
||||
## see https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md
|
||||
template = "host.tags.measurement.field"
|
||||
## timeout in seconds for the write connection to graphite
|
||||
timeout = 2
|
||||
`
|
||||
@@ -72,7 +76,7 @@ func (g *Graphite) Description() string {
|
||||
func (g *Graphite) Write(metrics []telegraf.Metric) error {
|
||||
// Prepare data
|
||||
var bp []string
|
||||
s, err := serializers.NewGraphiteSerializer(g.Prefix)
|
||||
s, err := serializers.NewGraphiteSerializer(g.Prefix, g.Template)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ func TestGraphiteOK(t *testing.T) {
|
||||
m1, _ := telegraf.NewMetric(
|
||||
"mymeasurement",
|
||||
map[string]string{"host": "192.168.0.1"},
|
||||
map[string]interface{}{"mymeasurement": float64(3.14)},
|
||||
map[string]interface{}{"myfield": float64(3.14)},
|
||||
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
|
||||
)
|
||||
m2, _ := telegraf.NewMetric(
|
||||
@@ -90,10 +90,10 @@ func TCPServer(t *testing.T, wg *sync.WaitGroup) {
|
||||
reader := bufio.NewReader(conn)
|
||||
tp := textproto.NewReader(reader)
|
||||
data1, _ := tp.ReadLine()
|
||||
assert.Equal(t, "my.prefix.192_168_0_1.mymeasurement 3.14 1289430000", data1)
|
||||
assert.Equal(t, "my.prefix.192_168_0_1.mymeasurement.myfield 3.14 1289430000", data1)
|
||||
data2, _ := tp.ReadLine()
|
||||
assert.Equal(t, "my.prefix.192_168_0_1.mymeasurement.value 3.14 1289430000", data2)
|
||||
assert.Equal(t, "my.prefix.192_168_0_1.mymeasurement 3.14 1289430000", data2)
|
||||
data3, _ := tp.ReadLine()
|
||||
assert.Equal(t, "my.prefix.192_168_0_1.my_measurement.value 3.14 1289430000", data3)
|
||||
assert.Equal(t, "my.prefix.192_168_0_1.my_measurement 3.14 1289430000", data3)
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ type Librato struct {
|
||||
NameFromTags bool
|
||||
SourceTag string
|
||||
Timeout internal.Duration
|
||||
Template string
|
||||
|
||||
apiUrl string
|
||||
client *http.Client
|
||||
@@ -29,22 +30,20 @@ type Librato struct {
|
||||
var sampleConfig = `
|
||||
## Librator API Docs
|
||||
## http://dev.librato.com/v1/metrics-authentication
|
||||
|
||||
## Librato API user
|
||||
api_user = "telegraf@influxdb.com" # required.
|
||||
|
||||
## Librato API token
|
||||
api_token = "my-secret-token" # required.
|
||||
|
||||
### Debug
|
||||
## Debug
|
||||
# debug = false
|
||||
|
||||
### Tag Field to populate source attribute (optional)
|
||||
### This is typically the _hostname_ from which the metric was obtained.
|
||||
## Tag Field to populate source attribute (optional)
|
||||
## This is typically the _hostname_ from which the metric was obtained.
|
||||
source_tag = "host"
|
||||
|
||||
## Connection timeout.
|
||||
# timeout = "5s"
|
||||
## Output Name Template (same as graphite buckets)
|
||||
## see https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md#graphite
|
||||
template = "host.tags.measurement.field"
|
||||
`
|
||||
|
||||
type LMetrics struct {
|
||||
@@ -152,17 +151,13 @@ func (l *Librato) Description() string {
|
||||
return "Configuration for Librato API to send metrics to."
|
||||
}
|
||||
|
||||
func (l *Librato) buildGaugeName(m telegraf.Metric, fieldName string) string {
|
||||
// Use the GraphiteSerializer
|
||||
graphiteSerializer := graphite.GraphiteSerializer{}
|
||||
return graphiteSerializer.SerializeBucketName(m, fieldName)
|
||||
}
|
||||
|
||||
func (l *Librato) buildGauges(m telegraf.Metric) ([]*Gauge, error) {
|
||||
gauges := []*Gauge{}
|
||||
serializer := graphite.GraphiteSerializer{Template: l.Template}
|
||||
bucket := serializer.SerializeBucketName(m.Name(), m.Tags())
|
||||
for fieldName, value := range m.Fields() {
|
||||
gauge := &Gauge{
|
||||
Name: l.buildGaugeName(m, fieldName),
|
||||
Name: graphite.InsertField(bucket, fieldName),
|
||||
MeasureTime: m.Time().Unix(),
|
||||
}
|
||||
if !gauge.verifyValue(value) {
|
||||
|
||||
@@ -86,7 +86,7 @@ func TestBuildGauge(t *testing.T) {
|
||||
{
|
||||
testutil.TestMetric(0.0, "test1"),
|
||||
&Gauge{
|
||||
Name: "value1.test1.value",
|
||||
Name: "value1.test1",
|
||||
MeasureTime: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).Unix(),
|
||||
Value: 0.0,
|
||||
},
|
||||
@@ -95,7 +95,7 @@ func TestBuildGauge(t *testing.T) {
|
||||
{
|
||||
testutil.TestMetric(1.0, "test2"),
|
||||
&Gauge{
|
||||
Name: "value1.test2.value",
|
||||
Name: "value1.test2",
|
||||
MeasureTime: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).Unix(),
|
||||
Value: 1.0,
|
||||
},
|
||||
@@ -104,7 +104,7 @@ func TestBuildGauge(t *testing.T) {
|
||||
{
|
||||
testutil.TestMetric(10, "test3"),
|
||||
&Gauge{
|
||||
Name: "value1.test3.value",
|
||||
Name: "value1.test3",
|
||||
MeasureTime: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).Unix(),
|
||||
Value: 10.0,
|
||||
},
|
||||
@@ -113,7 +113,7 @@ func TestBuildGauge(t *testing.T) {
|
||||
{
|
||||
testutil.TestMetric(int32(112345), "test4"),
|
||||
&Gauge{
|
||||
Name: "value1.test4.value",
|
||||
Name: "value1.test4",
|
||||
MeasureTime: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).Unix(),
|
||||
Value: 112345.0,
|
||||
},
|
||||
@@ -122,7 +122,7 @@ func TestBuildGauge(t *testing.T) {
|
||||
{
|
||||
testutil.TestMetric(int64(112345), "test5"),
|
||||
&Gauge{
|
||||
Name: "value1.test5.value",
|
||||
Name: "value1.test5",
|
||||
MeasureTime: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).Unix(),
|
||||
Value: 112345.0,
|
||||
},
|
||||
@@ -131,7 +131,7 @@ func TestBuildGauge(t *testing.T) {
|
||||
{
|
||||
testutil.TestMetric(float32(11234.5), "test6"),
|
||||
&Gauge{
|
||||
Name: "value1.test6.value",
|
||||
Name: "value1.test6",
|
||||
MeasureTime: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).Unix(),
|
||||
Value: 11234.5,
|
||||
},
|
||||
@@ -189,7 +189,7 @@ func TestBuildGaugeWithSource(t *testing.T) {
|
||||
{
|
||||
pt1,
|
||||
&Gauge{
|
||||
Name: "192_168_0_1.value1.test1.value",
|
||||
Name: "192_168_0_1.value1.test1",
|
||||
MeasureTime: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC).Unix(),
|
||||
Value: 0.0,
|
||||
Source: "192.168.0.1",
|
||||
@@ -199,7 +199,7 @@ func TestBuildGaugeWithSource(t *testing.T) {
|
||||
{
|
||||
pt2,
|
||||
&Gauge{
|
||||
Name: "192_168_0_1.value1.test1.value",
|
||||
Name: "192_168_0_1.value1.test1",
|
||||
MeasureTime: time.Date(2010, time.December, 10, 23, 0, 0, 0, time.UTC).Unix(),
|
||||
Value: 1.0,
|
||||
},
|
||||
|
||||
@@ -8,11 +8,16 @@ import (
|
||||
"github.com/influxdata/telegraf"
|
||||
)
|
||||
|
||||
const DEFAULT_TEMPLATE = "host.tags.measurement.field"
|
||||
|
||||
var fieldDeleter = strings.NewReplacer(".FIELDNAME", "", "FIELDNAME.", "")
|
||||
|
||||
type GraphiteSerializer struct {
|
||||
Prefix string
|
||||
Prefix string
|
||||
Template string
|
||||
}
|
||||
|
||||
var sanitizedChars = strings.NewReplacer("/", "-", "@", "-", " ", "_")
|
||||
var sanitizedChars = strings.NewReplacer("/", "-", "@", "-", " ", "_", "..", ".")
|
||||
|
||||
func (s *GraphiteSerializer) Serialize(metric telegraf.Metric) ([]string, error) {
|
||||
out := []string{}
|
||||
@@ -20,65 +25,95 @@ func (s *GraphiteSerializer) Serialize(metric telegraf.Metric) ([]string, error)
|
||||
// Convert UnixNano to Unix timestamps
|
||||
timestamp := metric.UnixNano() / 1000000000
|
||||
|
||||
for field_name, value := range metric.Fields() {
|
||||
// Convert value
|
||||
value_str := fmt.Sprintf("%#v", value)
|
||||
// Write graphite metric
|
||||
var graphitePoint string
|
||||
graphitePoint = fmt.Sprintf("%s %s %d",
|
||||
s.SerializeBucketName(metric, field_name),
|
||||
value_str,
|
||||
bucket := s.SerializeBucketName(metric.Name(), metric.Tags())
|
||||
|
||||
for fieldName, value := range metric.Fields() {
|
||||
// Convert value to string
|
||||
valueS := fmt.Sprintf("%#v", value)
|
||||
point := fmt.Sprintf("%s %s %d",
|
||||
// insert "field" section of template
|
||||
InsertField(bucket, fieldName),
|
||||
valueS,
|
||||
timestamp)
|
||||
out = append(out, graphitePoint)
|
||||
out = append(out, point)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *GraphiteSerializer) SerializeBucketName(metric telegraf.Metric, field_name string) string {
|
||||
// Get the metric name
|
||||
name := metric.Name()
|
||||
|
||||
// Convert UnixNano to Unix timestamps
|
||||
tag_str := buildTags(metric)
|
||||
|
||||
// Write graphite metric
|
||||
var serializedBucketName string
|
||||
if name == field_name {
|
||||
serializedBucketName = fmt.Sprintf("%s.%s",
|
||||
tag_str,
|
||||
strings.Replace(name, ".", "_", -1))
|
||||
} else {
|
||||
serializedBucketName = fmt.Sprintf("%s.%s.%s",
|
||||
tag_str,
|
||||
strings.Replace(name, ".", "_", -1),
|
||||
strings.Replace(field_name, ".", "_", -1))
|
||||
// SerializeBucketName will take the given measurement name and tags and
|
||||
// produce a graphite bucket. It will use the GraphiteSerializer.Template
|
||||
// to generate this, or DEFAULT_TEMPLATE.
|
||||
//
|
||||
// NOTE: SerializeBucketName replaces the "field" portion of the template with
|
||||
// FIELDNAME. It is up to the user to replace this. This is so that
|
||||
// SerializeBucketName can be called just once per measurement, rather than
|
||||
// once per field. See GraphiteSerializer.InsertField() function.
|
||||
func (s *GraphiteSerializer) SerializeBucketName(
|
||||
measurement string,
|
||||
tags map[string]string,
|
||||
) string {
|
||||
if s.Template == "" {
|
||||
s.Template = DEFAULT_TEMPLATE
|
||||
}
|
||||
if s.Prefix != "" {
|
||||
serializedBucketName = fmt.Sprintf("%s.%s", s.Prefix, serializedBucketName)
|
||||
tagsCopy := make(map[string]string)
|
||||
for k, v := range tags {
|
||||
tagsCopy[k] = v
|
||||
}
|
||||
return serializedBucketName
|
||||
|
||||
var out []string
|
||||
templateParts := strings.Split(s.Template, ".")
|
||||
for _, templatePart := range templateParts {
|
||||
switch templatePart {
|
||||
case "measurement":
|
||||
out = append(out, measurement)
|
||||
case "tags":
|
||||
// we will replace this later
|
||||
out = append(out, "TAGS")
|
||||
case "field":
|
||||
// user of SerializeBucketName needs to replace this
|
||||
out = append(out, "FIELDNAME")
|
||||
default:
|
||||
// This is a tag being applied
|
||||
if tagvalue, ok := tagsCopy[templatePart]; ok {
|
||||
out = append(out, strings.Replace(tagvalue, ".", "_", -1))
|
||||
delete(tagsCopy, templatePart)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// insert remaining tags into output name
|
||||
for i, templatePart := range out {
|
||||
if templatePart == "TAGS" {
|
||||
out[i] = buildTags(tagsCopy)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if s.Prefix == "" {
|
||||
return sanitizedChars.Replace(strings.Join(out, "."))
|
||||
}
|
||||
return sanitizedChars.Replace(s.Prefix + "." + strings.Join(out, "."))
|
||||
}
|
||||
|
||||
func buildTags(metric telegraf.Metric) string {
|
||||
// InsertField takes the bucket string from SerializeBucketName and replaces the
|
||||
// FIELDNAME portion. If fieldName == "value", it will simply delete the
|
||||
// FIELDNAME portion.
|
||||
func InsertField(bucket, fieldName string) string {
|
||||
// if the field name is "value", then dont use it
|
||||
if fieldName == "value" {
|
||||
return fieldDeleter.Replace(bucket)
|
||||
}
|
||||
return strings.Replace(bucket, "FIELDNAME", fieldName, 1)
|
||||
}
|
||||
|
||||
func buildTags(tags map[string]string) string {
|
||||
var keys []string
|
||||
tags := metric.Tags()
|
||||
for k := range tags {
|
||||
if k == "host" {
|
||||
continue
|
||||
}
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
var tag_str string
|
||||
if host, ok := tags["host"]; ok {
|
||||
if len(keys) > 0 {
|
||||
tag_str = strings.Replace(host, ".", "_", -1) + "."
|
||||
} else {
|
||||
tag_str = strings.Replace(host, ".", "_", -1)
|
||||
}
|
||||
}
|
||||
|
||||
for i, k := range keys {
|
||||
tag_value := strings.Replace(tags[k], ".", "_", -1)
|
||||
if i == 0 {
|
||||
@@ -87,5 +122,5 @@ func buildTags(metric telegraf.Metric) string {
|
||||
tag_str += "." + tag_value
|
||||
}
|
||||
}
|
||||
return sanitizedChars.Replace(tag_str)
|
||||
return tag_str
|
||||
}
|
||||
|
||||
@@ -11,6 +11,23 @@ import (
|
||||
"github.com/influxdata/telegraf"
|
||||
)
|
||||
|
||||
var defaultTags = map[string]string{
|
||||
"host": "localhost",
|
||||
"cpu": "cpu0",
|
||||
"datacenter": "us-west-2",
|
||||
}
|
||||
|
||||
const (
|
||||
template1 = "tags.measurement.field"
|
||||
template2 = "host.measurement.field"
|
||||
template3 = "host.tags.field"
|
||||
template4 = "host.tags.measurement"
|
||||
// this template explicitly uses all tag keys, so "tags" should be empty
|
||||
template5 = "host.datacenter.cpu.tags.measurement.field"
|
||||
// this template has non-existent tag keys
|
||||
template6 = "foo.host.cpu.bar.tags.measurement.field"
|
||||
)
|
||||
|
||||
func TestGraphiteTags(t *testing.T) {
|
||||
m1, _ := telegraf.NewMetric(
|
||||
"mymeasurement",
|
||||
@@ -31,12 +48,12 @@ func TestGraphiteTags(t *testing.T) {
|
||||
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
|
||||
)
|
||||
|
||||
tags1 := buildTags(m1)
|
||||
tags2 := buildTags(m2)
|
||||
tags3 := buildTags(m3)
|
||||
tags1 := buildTags(m1.Tags())
|
||||
tags2 := buildTags(m2.Tags())
|
||||
tags3 := buildTags(m3.Tags())
|
||||
|
||||
assert.Equal(t, "192_168_0_1", tags1)
|
||||
assert.Equal(t, "192_168_0_1.first.second", tags2)
|
||||
assert.Equal(t, "first.second.192_168_0_1", tags2)
|
||||
assert.Equal(t, "first.second", tags3)
|
||||
}
|
||||
|
||||
@@ -93,6 +110,82 @@ func TestSerializeMetricHost(t *testing.T) {
|
||||
assert.Equal(t, expS, mS)
|
||||
}
|
||||
|
||||
// test that a field named "value" gets ignored.
|
||||
func TestSerializeValueField(t *testing.T) {
|
||||
now := time.Now()
|
||||
tags := map[string]string{
|
||||
"host": "localhost",
|
||||
"cpu": "cpu0",
|
||||
"datacenter": "us-west-2",
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
"value": float64(91.5),
|
||||
}
|
||||
m, err := telegraf.NewMetric("cpu", tags, fields, now)
|
||||
assert.NoError(t, err)
|
||||
|
||||
s := GraphiteSerializer{}
|
||||
mS, err := s.Serialize(m)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expS := []string{
|
||||
fmt.Sprintf("localhost.cpu0.us-west-2.cpu 91.5 %d", now.Unix()),
|
||||
}
|
||||
assert.Equal(t, expS, mS)
|
||||
}
|
||||
|
||||
// test that a field named "value" gets ignored in middle of template.
|
||||
func TestSerializeValueField2(t *testing.T) {
|
||||
now := time.Now()
|
||||
tags := map[string]string{
|
||||
"host": "localhost",
|
||||
"cpu": "cpu0",
|
||||
"datacenter": "us-west-2",
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
"value": float64(91.5),
|
||||
}
|
||||
m, err := telegraf.NewMetric("cpu", tags, fields, now)
|
||||
assert.NoError(t, err)
|
||||
|
||||
s := GraphiteSerializer{
|
||||
Template: "host.field.tags.measurement",
|
||||
}
|
||||
mS, err := s.Serialize(m)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expS := []string{
|
||||
fmt.Sprintf("localhost.cpu0.us-west-2.cpu 91.5 %d", now.Unix()),
|
||||
}
|
||||
assert.Equal(t, expS, mS)
|
||||
}
|
||||
|
||||
// test that a field named "value" gets ignored at beginning of template.
|
||||
func TestSerializeValueField3(t *testing.T) {
|
||||
now := time.Now()
|
||||
tags := map[string]string{
|
||||
"host": "localhost",
|
||||
"cpu": "cpu0",
|
||||
"datacenter": "us-west-2",
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
"value": float64(91.5),
|
||||
}
|
||||
m, err := telegraf.NewMetric("cpu", tags, fields, now)
|
||||
assert.NoError(t, err)
|
||||
|
||||
s := GraphiteSerializer{
|
||||
Template: "field.host.tags.measurement",
|
||||
}
|
||||
mS, err := s.Serialize(m)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expS := []string{
|
||||
fmt.Sprintf("localhost.cpu0.us-west-2.cpu 91.5 %d", now.Unix()),
|
||||
}
|
||||
assert.Equal(t, expS, mS)
|
||||
}
|
||||
|
||||
func TestSerializeMetricPrefix(t *testing.T) {
|
||||
now := time.Now()
|
||||
tags := map[string]string{
|
||||
@@ -133,48 +226,128 @@ func TestSerializeBucketNameNoHost(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
s := GraphiteSerializer{}
|
||||
mS := s.SerializeBucketName(m, "usage_idle")
|
||||
mS := s.SerializeBucketName(m.Name(), m.Tags())
|
||||
|
||||
expS := fmt.Sprintf("cpu0.us-west-2.cpu.usage_idle")
|
||||
expS := "cpu0.us-west-2.cpu.FIELDNAME"
|
||||
assert.Equal(t, expS, mS)
|
||||
}
|
||||
|
||||
func TestSerializeBucketNameHost(t *testing.T) {
|
||||
now := time.Now()
|
||||
tags := map[string]string{
|
||||
"host": "localhost",
|
||||
"cpu": "cpu0",
|
||||
"datacenter": "us-west-2",
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
"usage_idle": float64(91.5),
|
||||
}
|
||||
m, err := telegraf.NewMetric("cpu", tags, fields, now)
|
||||
m, err := telegraf.NewMetric("cpu", defaultTags, fields, now)
|
||||
assert.NoError(t, err)
|
||||
|
||||
s := GraphiteSerializer{}
|
||||
mS := s.SerializeBucketName(m, "usage_idle")
|
||||
mS := s.SerializeBucketName(m.Name(), m.Tags())
|
||||
|
||||
expS := fmt.Sprintf("localhost.cpu0.us-west-2.cpu.usage_idle")
|
||||
expS := "localhost.cpu0.us-west-2.cpu.FIELDNAME"
|
||||
assert.Equal(t, expS, mS)
|
||||
}
|
||||
|
||||
func TestSerializeBucketNamePrefix(t *testing.T) {
|
||||
now := time.Now()
|
||||
tags := map[string]string{
|
||||
"host": "localhost",
|
||||
"cpu": "cpu0",
|
||||
"datacenter": "us-west-2",
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
"usage_idle": float64(91.5),
|
||||
}
|
||||
m, err := telegraf.NewMetric("cpu", tags, fields, now)
|
||||
m, err := telegraf.NewMetric("cpu", defaultTags, fields, now)
|
||||
assert.NoError(t, err)
|
||||
|
||||
s := GraphiteSerializer{Prefix: "prefix"}
|
||||
mS := s.SerializeBucketName(m, "usage_idle")
|
||||
mS := s.SerializeBucketName(m.Name(), m.Tags())
|
||||
|
||||
expS := fmt.Sprintf("prefix.localhost.cpu0.us-west-2.cpu.usage_idle")
|
||||
expS := "prefix.localhost.cpu0.us-west-2.cpu.FIELDNAME"
|
||||
assert.Equal(t, expS, mS)
|
||||
}
|
||||
|
||||
func TestTemplate1(t *testing.T) {
|
||||
now := time.Now()
|
||||
fields := map[string]interface{}{
|
||||
"usage_idle": float64(91.5),
|
||||
}
|
||||
m, err := telegraf.NewMetric("cpu", defaultTags, fields, now)
|
||||
assert.NoError(t, err)
|
||||
|
||||
s := GraphiteSerializer{Template: template1}
|
||||
mS := s.SerializeBucketName(m.Name(), m.Tags())
|
||||
|
||||
expS := "cpu0.us-west-2.localhost.cpu.FIELDNAME"
|
||||
assert.Equal(t, expS, mS)
|
||||
}
|
||||
|
||||
func TestTemplate2(t *testing.T) {
|
||||
now := time.Now()
|
||||
fields := map[string]interface{}{
|
||||
"usage_idle": float64(91.5),
|
||||
}
|
||||
m, err := telegraf.NewMetric("cpu", defaultTags, fields, now)
|
||||
assert.NoError(t, err)
|
||||
|
||||
s := GraphiteSerializer{Template: template2}
|
||||
mS := s.SerializeBucketName(m.Name(), m.Tags())
|
||||
|
||||
expS := "localhost.cpu.FIELDNAME"
|
||||
assert.Equal(t, expS, mS)
|
||||
}
|
||||
|
||||
func TestTemplate3(t *testing.T) {
|
||||
now := time.Now()
|
||||
fields := map[string]interface{}{
|
||||
"usage_idle": float64(91.5),
|
||||
}
|
||||
m, err := telegraf.NewMetric("cpu", defaultTags, fields, now)
|
||||
assert.NoError(t, err)
|
||||
|
||||
s := GraphiteSerializer{Template: template3}
|
||||
mS := s.SerializeBucketName(m.Name(), m.Tags())
|
||||
|
||||
expS := "localhost.cpu0.us-west-2.FIELDNAME"
|
||||
assert.Equal(t, expS, mS)
|
||||
}
|
||||
|
||||
func TestTemplate4(t *testing.T) {
|
||||
now := time.Now()
|
||||
fields := map[string]interface{}{
|
||||
"usage_idle": float64(91.5),
|
||||
}
|
||||
m, err := telegraf.NewMetric("cpu", defaultTags, fields, now)
|
||||
assert.NoError(t, err)
|
||||
|
||||
s := GraphiteSerializer{Template: template4}
|
||||
mS := s.SerializeBucketName(m.Name(), m.Tags())
|
||||
|
||||
expS := "localhost.cpu0.us-west-2.cpu"
|
||||
assert.Equal(t, expS, mS)
|
||||
}
|
||||
|
||||
func TestTemplate5(t *testing.T) {
|
||||
now := time.Now()
|
||||
fields := map[string]interface{}{
|
||||
"usage_idle": float64(91.5),
|
||||
}
|
||||
m, err := telegraf.NewMetric("cpu", defaultTags, fields, now)
|
||||
assert.NoError(t, err)
|
||||
|
||||
s := GraphiteSerializer{Template: template5}
|
||||
mS := s.SerializeBucketName(m.Name(), m.Tags())
|
||||
|
||||
expS := "localhost.us-west-2.cpu0.cpu.FIELDNAME"
|
||||
assert.Equal(t, expS, mS)
|
||||
}
|
||||
|
||||
func TestTemplate6(t *testing.T) {
|
||||
now := time.Now()
|
||||
fields := map[string]interface{}{
|
||||
"usage_idle": float64(91.5),
|
||||
}
|
||||
m, err := telegraf.NewMetric("cpu", defaultTags, fields, now)
|
||||
assert.NoError(t, err)
|
||||
|
||||
s := GraphiteSerializer{Template: template6}
|
||||
mS := s.SerializeBucketName(m.Name(), m.Tags())
|
||||
|
||||
expS := "localhost.cpu0.us-west-2.cpu.FIELDNAME"
|
||||
assert.Equal(t, expS, mS)
|
||||
}
|
||||
|
||||
@@ -30,6 +30,10 @@ type Config struct {
|
||||
|
||||
// Prefix to add to all measurements, only supports Graphite
|
||||
Prefix string
|
||||
|
||||
// Template for converting telegraf metrics into Graphite
|
||||
// only supports Graphite
|
||||
Template string
|
||||
}
|
||||
|
||||
// NewSerializer a Serializer interface based on the given config.
|
||||
@@ -40,7 +44,7 @@ func NewSerializer(config *Config) (Serializer, error) {
|
||||
case "influx":
|
||||
serializer, err = NewInfluxSerializer()
|
||||
case "graphite":
|
||||
serializer, err = NewGraphiteSerializer(config.Prefix)
|
||||
serializer, err = NewGraphiteSerializer(config.Prefix, config.Template)
|
||||
case "json":
|
||||
serializer, err = NewJsonSerializer()
|
||||
}
|
||||
@@ -55,8 +59,9 @@ func NewInfluxSerializer() (Serializer, error) {
|
||||
return &influx.InfluxSerializer{}, nil
|
||||
}
|
||||
|
||||
func NewGraphiteSerializer(prefix string) (Serializer, error) {
|
||||
func NewGraphiteSerializer(prefix, template string) (Serializer, error) {
|
||||
return &graphite.GraphiteSerializer{
|
||||
Prefix: prefix,
|
||||
Prefix: prefix,
|
||||
Template: template,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -495,7 +495,7 @@ def build_packages(build_output, version, nightly=False, rc=None, iteration=1):
|
||||
fr = os.path.join(current_location, binary)
|
||||
# Where the binary should go in the package filesystem
|
||||
to = os.path.join(build_root, INSTALL_ROOT_DIR[1:], binary)
|
||||
|
||||
|
||||
if debug:
|
||||
print("[{}][{}] - Moving from '{}' to '{}'".format(platform,
|
||||
arch,
|
||||
@@ -560,7 +560,6 @@ def build_packages(build_output, version, nightly=False, rc=None, iteration=1):
|
||||
fpm_command += "--verbose "
|
||||
if package_type == "rpm":
|
||||
fpm_command += "--depends coreutils "
|
||||
fpm_command += "--depends lsof "
|
||||
out = run(fpm_command, shell=True)
|
||||
matches = re.search(':path=>"(.*)"', out)
|
||||
outfile = None
|
||||
|
||||
@@ -138,7 +138,7 @@ case $1 in
|
||||
if which start-stop-daemon > /dev/null 2>&1; then
|
||||
start-stop-daemon --chuid $USER:$GROUP --start --quiet --pidfile $pidfile --exec $daemon -- -pidfile $pidfile -config $config -config-directory $confdir $TELEGRAF_OPTS >>$STDOUT 2>>$STDERR &
|
||||
else
|
||||
nohup $daemon -pidfile $pidfile -config $config -config-directory $confdir $TELEGRAF_OPTS >>$STDOUT 2>>$STDERR &
|
||||
nohup sudo -u $USER $daemon -pidfile $pidfile -config $config -config-directory $confdir $TELEGRAF_OPTS >>$STDOUT 2>>$STDERR &
|
||||
fi
|
||||
log_success_msg "$name process was started"
|
||||
;;
|
||||
|
||||
Reference in New Issue
Block a user