Compare commits
109 Commits
0.13.0
...
1.0.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2beef21231 | ||
|
|
cb3c54a1ae | ||
|
|
d50a1e83ac | ||
|
|
1f10639222 | ||
|
|
af0979cce5 | ||
|
|
5b43901bd8 | ||
|
|
d7efb7a71d | ||
|
|
4d242836ee | ||
|
|
06cb5a041e | ||
|
|
ea2521bf27 | ||
|
|
4cd1f7a104 | ||
|
|
137843b2f6 | ||
|
|
008ed17a79 | ||
|
|
75e6cb9064 | ||
|
|
ad88a9421a | ||
|
|
346deb30a3 | ||
|
|
8c3d7cd145 | ||
|
|
821b30eb92 | ||
|
|
a362352587 | ||
|
|
94f952787f | ||
|
|
3ff184c061 | ||
|
|
80368e3936 | ||
|
|
2c448e22e1 | ||
|
|
1aabd38eb2 | ||
|
|
675457873a | ||
|
|
8173338f8a | ||
|
|
c4841843a9 | ||
|
|
f08a27be5d | ||
|
|
a4b36d12dd | ||
|
|
c842724b61 | ||
|
|
fb5f40319e | ||
|
|
52b9fc837c | ||
|
|
6f991ec78a | ||
|
|
7921d87a45 | ||
|
|
9f7a758bf9 | ||
|
|
0aff7a0bc1 | ||
|
|
c4cfdb8a25 | ||
|
|
342cfc4087 | ||
|
|
bd1282eddf | ||
|
|
892abec025 | ||
|
|
e809c4e445 | ||
|
|
9ff536d94d | ||
|
|
4f27315720 | ||
|
|
958ef2f872 | ||
|
|
069764f05e | ||
|
|
eeeab5192b | ||
|
|
a7dfbce3d3 | ||
|
|
ed2d1d9bb7 | ||
|
|
0fb2d2ffae | ||
|
|
3af65e7abb | ||
|
|
984b6cb0fb | ||
|
|
ca504a19ec | ||
|
|
c2797c85d1 | ||
|
|
d5add07c0b | ||
|
|
0ebf1c1ad7 | ||
|
|
42d7fc5e16 | ||
|
|
6828fc48e1 | ||
|
|
98d91b1c89 | ||
|
|
9bbdb2d562 | ||
|
|
a8334c3261 | ||
|
|
9144f9630b | ||
|
|
3e4a19539a | ||
|
|
5fe7e6e40e | ||
|
|
58f2ba1247 | ||
|
|
5f3a91bffd | ||
|
|
6351aa5167 | ||
|
|
9966099d1a | ||
|
|
1ef5599361 | ||
|
|
c78b6cdb4e | ||
|
|
d736c7235a | ||
|
|
475252d873 | ||
|
|
e103923430 | ||
|
|
cb59517ceb | ||
|
|
1248934f3e | ||
|
|
204ebf6bf6 | ||
|
|
52d5b19219 | ||
|
|
8e92d3a4a0 | ||
|
|
c44ecf54a5 | ||
|
|
c6699c36d3 | ||
|
|
d6ceae7005 | ||
|
|
4dcb82bf08 | ||
|
|
4f5d5926d9 | ||
|
|
3c5c3b98df | ||
|
|
56aee1ceee | ||
|
|
f176c28a56 | ||
|
|
2e68bd1412 | ||
|
|
35eb65460d | ||
|
|
ab54064689 | ||
|
|
debf7bf149 | ||
|
|
1dbe3b8231 | ||
|
|
b065573e23 | ||
|
|
e94e50181c | ||
|
|
69dfe63809 | ||
|
|
f32916a5bd | ||
|
|
be7ca56872 | ||
|
|
33cacc71b8 | ||
|
|
c292e3931a | ||
|
|
a87d6f0545 | ||
|
|
3a01b6d5b7 | ||
|
|
39df2635bd | ||
|
|
08ecfb8a67 | ||
|
|
a59bf7246a | ||
|
|
281296cd3f | ||
|
|
61d190b1ae | ||
|
|
dc89f029ad | ||
|
|
7557056a31 | ||
|
|
20c45a150c | ||
|
|
46bf0ef271 | ||
|
|
a7b632eb5e |
4
.gitattributes
vendored
4
.gitattributes
vendored
@@ -1,2 +1,4 @@
|
||||
CHANGELOG.md merge=union
|
||||
|
||||
README.md merge=union
|
||||
plugins/inputs/all/all.go merge=union
|
||||
plugins/outputs/all/all.go merge=union
|
||||
|
||||
44
.github/ISSUE_TEMPLATE.md
vendored
Normal file
44
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
## Directions
|
||||
|
||||
GitHub Issues are reserved for actionable bug reports and feature requests.
|
||||
General questions should be sent to the [InfluxDB mailing list](https://groups.google.com/forum/#!forum/influxdb).
|
||||
|
||||
Before opening an issue, search for similar bug reports or feature requests on GitHub Issues.
|
||||
If no similar issue can be found, fill out either the "Bug Report" or the "Feature Request" section below.
|
||||
Erase the other section and everything on and above this line.
|
||||
|
||||
*Please note, the quickest way to fix a bug is to open a Pull Request.*
|
||||
|
||||
## Bug report
|
||||
|
||||
### Relevant telegraf.conf:
|
||||
|
||||
### System info:
|
||||
|
||||
[Include Telegraf version, operating system name, and other relevant details]
|
||||
|
||||
### Steps to reproduce:
|
||||
|
||||
1. ...
|
||||
2. ...
|
||||
|
||||
### Expected behavior:
|
||||
|
||||
### Actual behavior:
|
||||
|
||||
### Additional info:
|
||||
|
||||
[Include gist of relevant config, logs, etc.]
|
||||
|
||||
|
||||
## Feature Request
|
||||
|
||||
Opening a feature request kicks off a discussion.
|
||||
|
||||
### Proposal:
|
||||
|
||||
### Current behavior:
|
||||
|
||||
### Desired behavior:
|
||||
|
||||
### Use case: [Why is this important (helps with prioritizing requests)]
|
||||
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
### Required for all PRs:
|
||||
|
||||
- [ ] CHANGELOG.md updated
|
||||
- [ ] Sign [CLA](https://influxdata.com/community/cla/) (if not already signed)
|
||||
- [ ] README.md updated (if adding a new plugin)
|
||||
111
CHANGELOG.md
111
CHANGELOG.md
@@ -1,3 +1,114 @@
|
||||
## v1.0
|
||||
|
||||
### Features
|
||||
|
||||
### Bugfixes
|
||||
|
||||
## v1.0 beta 2 [2016-06-21]
|
||||
|
||||
### Features
|
||||
|
||||
- [#1340](https://github.com/influxdata/telegraf/issues/1340): statsd: do not log every dropped metric.
|
||||
- [#1368](https://github.com/influxdata/telegraf/pull/1368): Add precision rounding to all metrics on collection.
|
||||
- [#1390](https://github.com/influxdata/telegraf/pull/1390): Add support for Tengine
|
||||
- [#1320](https://github.com/influxdata/telegraf/pull/1320): Logparser input plugin for parsing grok-style log patterns.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- [#1330](https://github.com/influxdata/telegraf/issues/1330): Fix exec plugin panic when using single binary.
|
||||
- [#1336](https://github.com/influxdata/telegraf/issues/1336): Fixed incorrect prometheus metrics source selection.
|
||||
- [#1112](https://github.com/influxdata/telegraf/issues/1112): Set default Zookeeper chroot to empty string.
|
||||
- [#1335](https://github.com/influxdata/telegraf/issues/1335): Fix overall ping timeout to be calculated based on per-ping timeout.
|
||||
- [#1374](https://github.com/influxdata/telegraf/pull/1374): Change "default" retention policy to "".
|
||||
- [#1377](https://github.com/influxdata/telegraf/issues/1377): Graphite output mangling '%' character.
|
||||
|
||||
## v1.0 beta 1 [2016-06-07]
|
||||
|
||||
### Release Notes
|
||||
|
||||
- `flush_jitter` behavior has been changed. The random jitter will now be
|
||||
evaluated at every flush interval, rather than once at startup. This makes it
|
||||
consistent with the behavior of `collection_jitter`.
|
||||
|
||||
- All AWS plugins now utilize a standard mechanism for evaluating credentials.
|
||||
This allows all AWS plugins to support environment variables, shared credential
|
||||
files & profiles, and role assumptions. See the specific plugin README for
|
||||
details.
|
||||
|
||||
- The AWS CloudWatch input plugin can now declare a wildcard value for a metric
|
||||
dimension. This causes the plugin to read all metrics that contain the specified
|
||||
dimension key regardless of value. This is used to export collections of metrics
|
||||
without having to know the dimension values ahead of time.
|
||||
|
||||
- The AWS CloudWatch input plugin can now be configured with the `cache_ttl`
|
||||
attribute. This configures the TTL of the internal metric cache. This is useful
|
||||
in conjunction with wildcard dimension values as it will control the amount of
|
||||
time before a new metric is included by the plugin.
|
||||
|
||||
### Features
|
||||
- [#1262](https://github.com/influxdata/telegraf/pull/1261): Add graylog input pluging.
|
||||
- [#1294](https://github.com/influxdata/telegraf/pull/1294): consul input plugin. Thanks @harnash
|
||||
- [#1164](https://github.com/influxdata/telegraf/pull/1164): conntrack input plugin. Thanks @robinpercy!
|
||||
- [#1165](https://github.com/influxdata/telegraf/pull/1165): vmstat input plugin. Thanks @jshim-xm!
|
||||
- [#1247](https://github.com/influxdata/telegraf/pull/1247): rollbar input plugin. Thanks @francois2metz and @cduez!
|
||||
- [#1208](https://github.com/influxdata/telegraf/pull/1208): Standardized AWS credentials evaluation & wildcard CloudWatch dimensions. Thanks @johnrengelman!
|
||||
- [#1264](https://github.com/influxdata/telegraf/pull/1264): Add SSL config options to http_response plugin.
|
||||
- [#1272](https://github.com/influxdata/telegraf/pull/1272): graphite parser: add ability to specify multiple tag keys, for consistency with influxdb parser.
|
||||
- [#1265](https://github.com/influxdata/telegraf/pull/1265): Make dns lookups for chrony configurable. Thanks @zbindenren!
|
||||
- [#1275](https://github.com/influxdata/telegraf/pull/1275): Allow wildcard filtering of varnish stats.
|
||||
- [#1142](https://github.com/influxdata/telegraf/pull/1142): Support for glob patterns in exec plugin commands configuration.
|
||||
- [#1278](https://github.com/influxdata/telegraf/pull/1278): RabbitMQ input: made url parameter optional by using DefaultURL (http://localhost:15672) if not specified
|
||||
- [#1197](https://github.com/influxdata/telegraf/pull/1197): Limit AWS GetMetricStatistics requests to 10 per second.
|
||||
- [#1278](https://github.com/influxdata/telegraf/pull/1278) & [#1288](https://github.com/influxdata/telegraf/pull/1288) & [#1295](https://github.com/influxdata/telegraf/pull/1295): RabbitMQ/Apache/InfluxDB inputs: made url(s) parameter optional by using reasonable input defaults if not specified
|
||||
- [#1296](https://github.com/influxdata/telegraf/issues/1296): Refactor of flush_jitter argument.
|
||||
- [#1213](https://github.com/influxdata/telegraf/issues/1213): Add inactive & active memory to mem plugin.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- [#1252](https://github.com/influxdata/telegraf/pull/1252) & [#1279](https://github.com/influxdata/telegraf/pull/1279): Fix systemd service. Thanks @zbindenren & @PierreF!
|
||||
- [#1221](https://github.com/influxdata/telegraf/pull/1221): Fix influxdb n_shards counter.
|
||||
- [#1258](https://github.com/influxdata/telegraf/pull/1258): Fix potential kernel plugin integer parse error.
|
||||
- [#1268](https://github.com/influxdata/telegraf/pull/1268): Fix potential influxdb input type assertion panic.
|
||||
- [#1283](https://github.com/influxdata/telegraf/pull/1283): Still send processes metrics if a process exited during metric collection.
|
||||
- [#1297](https://github.com/influxdata/telegraf/issues/1297): disk plugin panic when usage grab fails.
|
||||
- [#1316](https://github.com/influxdata/telegraf/pull/1316): Removed leaked "database" tag on redis metrics. Thanks @PierreF!
|
||||
- [#1323](https://github.com/influxdata/telegraf/issues/1323): Processes plugin: fix potential error with /proc/net/stat directory.
|
||||
- [#1322](https://github.com/influxdata/telegraf/issues/1322): Fix rare RHEL 5.2 panic in gopsutil diskio gathering function.
|
||||
|
||||
## v0.13.1 [2016-05-24]
|
||||
|
||||
### Release Notes
|
||||
|
||||
- net_response and http_response plugins timeouts will now accept duration
|
||||
strings, ie, "2s" or "500ms".
|
||||
- Input plugin Gathers will no longer be logged by default, but a Gather for
|
||||
_each_ plugin will be logged in Debug mode.
|
||||
- Debug mode will no longer print every point added to the accumulator. This
|
||||
functionality can be duplicated using the `file` output plugin and printing
|
||||
to "stdout".
|
||||
|
||||
### Features
|
||||
|
||||
- [#1173](https://github.com/influxdata/telegraf/pull/1173): varnish input plugin. Thanks @sfox-xmatters!
|
||||
- [#1138](https://github.com/influxdata/telegraf/pull/1138): nstat input plugin. Thanks @Maksadbek!
|
||||
- [#1139](https://github.com/influxdata/telegraf/pull/1139): instrumental output plugin. Thanks @jasonroelofs!
|
||||
- [#1172](https://github.com/influxdata/telegraf/pull/1172): Ceph storage stats. Thanks @robinpercy!
|
||||
- [#1233](https://github.com/influxdata/telegraf/pull/1233): Updated golint gopsutil dependency.
|
||||
- [#1238](https://github.com/influxdata/telegraf/pull/1238): chrony input plugin. Thanks @zbindenren!
|
||||
- [#479](https://github.com/influxdata/telegraf/issues/479): per-plugin execution time added to debug output.
|
||||
- [#1249](https://github.com/influxdata/telegraf/issues/1249): influxdb output: added write_consistency argument.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- [#1195](https://github.com/influxdata/telegraf/pull/1195): Docker panic on timeout. Thanks @zstyblik!
|
||||
- [#1211](https://github.com/influxdata/telegraf/pull/1211): mongodb input. Fix possible panic. Thanks @kols!
|
||||
- [#1215](https://github.com/influxdata/telegraf/pull/1215): Fix for possible gopsutil-dependent plugin hangs.
|
||||
- [#1228](https://github.com/influxdata/telegraf/pull/1228): Fix service plugin host tag overwrite.
|
||||
- [#1198](https://github.com/influxdata/telegraf/pull/1198): http_response: override request Host header properly
|
||||
- [#1230](https://github.com/influxdata/telegraf/issues/1230): Fix Telegraf process hangup due to a single plugin hanging.
|
||||
- [#1214](https://github.com/influxdata/telegraf/issues/1214): Use TCP timeout argument in net_response plugin.
|
||||
- [#1243](https://github.com/influxdata/telegraf/pull/1243): Logfile not created on systemd.
|
||||
|
||||
## v0.13 [2016-05-11]
|
||||
|
||||
### Release Notes
|
||||
|
||||
@@ -212,8 +212,8 @@ func (s *Simple) Close() error {
|
||||
}
|
||||
|
||||
func (s *Simple) Write(metrics []telegraf.Metric) error {
|
||||
for _, pt := range points {
|
||||
// write `pt` to the output sink here
|
||||
for _, metric := range metrics {
|
||||
// write `metric` to the output sink here
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
6
Godeps
6
Godeps
@@ -16,13 +16,14 @@ github.com/eapache/go-resiliency b86b1ec0dd4209a588dc1285cdd471e73525c0b3
|
||||
github.com/eapache/queue ded5959c0d4e360646dc9e9908cff48666781367
|
||||
github.com/eclipse/paho.mqtt.golang 0f7a459f04f13a41b7ed752d47944528d4bf9a86
|
||||
github.com/go-sql-driver/mysql 1fca743146605a172a266e1654e01e5cd5669bee
|
||||
github.com/gobwas/glob d877f6352135181470c40c73ebb81aefa22115fa
|
||||
github.com/gobwas/glob 49571a1557cd20e6a2410adc6421f85b66c730b5
|
||||
github.com/golang/protobuf 552c7b9542c194800fd493123b3798ef0a832032
|
||||
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/hashicorp/consul 5aa90455ce78d4d41578bafc86305e6e6b28d7d2
|
||||
github.com/hpcloud/tail b2940955ab8b26e19d43a43c4da0475dd81bdb56
|
||||
github.com/influxdata/config b79f6829346b8d6e78ba73544b1e1038f1f1c9da
|
||||
github.com/influxdata/influxdb e094138084855d444195b252314dfee9eae34cab
|
||||
@@ -42,10 +43,11 @@ github.com/prometheus/client_model fa8ad6fec33561be4280a8f0514318c79d7f6cb6
|
||||
github.com/prometheus/common e8eabff8812b05acf522b45fdcd725a785188e37
|
||||
github.com/prometheus/procfs 406e5b7bfd8201a36e2bb5f7bdae0b03380c2ce8
|
||||
github.com/samuel/go-zookeeper 218e9c81c0dd8b3b18172b2bbfad92cc7d6db55f
|
||||
github.com/shirou/gopsutil 37d89088411de59a4ef9fc340afa0e89dfcb4ea9
|
||||
github.com/shirou/gopsutil 586bb697f3ec9f8ec08ffefe18f521a64534037c
|
||||
github.com/soniah/gosnmp b1b4f885b12c5dcbd021c5cee1c904110de6db7d
|
||||
github.com/streadway/amqp b4f3ceab0337f013208d31348b578d83c0064744
|
||||
github.com/stretchr/testify 1f4a1643a57e798696635ea4c126e9127adb7d3c
|
||||
github.com/vjeantet/grok 83bfdfdfd1a8146795b28e547a8e3c8b28a466c2
|
||||
github.com/wvanbergen/kafka 46f9a1cf3f670edec492029fadded9c2d9e18866
|
||||
github.com/wvanbergen/kazoo-go 0f768712ae6f76454f987c3356177e138df258f8
|
||||
github.com/zensqlmonitor/go-mssqldb ffe5510c6fa5e15e6d983210ab501c815b56b363
|
||||
|
||||
6
Makefile
6
Makefile
@@ -64,7 +64,6 @@ endif
|
||||
docker run --name memcached -p "11211:11211" -d memcached
|
||||
docker run --name postgres -p "5432:5432" -d postgres
|
||||
docker run --name rabbitmq -p "15672:15672" -p "5672:5672" -d rabbitmq:3-management
|
||||
docker run --name opentsdb -p "4242:4242" -d petergrace/opentsdb-docker
|
||||
docker run --name redis -p "6379:6379" -d redis
|
||||
docker run --name aerospike -p "3000:3000" -d aerospike
|
||||
docker run --name nsq -p "4150:4150" -d nsqio/nsq /nsqd
|
||||
@@ -79,7 +78,6 @@ docker-run-circle:
|
||||
-e ADVERTISED_PORT=9092 \
|
||||
-p "2181:2181" -p "9092:9092" \
|
||||
-d spotify/kafka
|
||||
docker run --name opentsdb -p "4242:4242" -d petergrace/opentsdb-docker
|
||||
docker run --name aerospike -p "3000:3000" -d aerospike
|
||||
docker run --name nsq -p "4150:4150" -d nsqio/nsq /nsqd
|
||||
docker run --name mqtt -p "1883:1883" -d ncarlier/mqtt
|
||||
@@ -88,8 +86,8 @@ docker-run-circle:
|
||||
|
||||
# Kill all docker containers, ignore errors
|
||||
docker-kill:
|
||||
-docker kill nsq aerospike redis opentsdb rabbitmq postgres memcached mysql kafka mqtt riemann snmp
|
||||
-docker rm nsq aerospike redis opentsdb rabbitmq postgres memcached mysql kafka mqtt riemann snmp
|
||||
-docker kill nsq aerospike redis rabbitmq postgres memcached mysql kafka mqtt riemann snmp
|
||||
-docker rm nsq aerospike redis rabbitmq postgres memcached mysql kafka mqtt riemann snmp
|
||||
|
||||
# Run full unit tests using docker containers (includes setup and teardown)
|
||||
test: vet docker-kill docker-run
|
||||
|
||||
50
README.md
50
README.md
@@ -1,4 +1,4 @@
|
||||
# Telegraf [](https://circleci.com/gh/influxdata/telegraf)
|
||||
# Telegraf [](https://circleci.com/gh/influxdata/telegraf) [](https://hub.docker.com/_/telegraf/)
|
||||
|
||||
Telegraf is an agent written in Go for collecting metrics from the system it's
|
||||
running on, or from other services, and writing them into InfluxDB or other
|
||||
@@ -20,12 +20,12 @@ new plugins.
|
||||
### Linux deb and rpm Packages:
|
||||
|
||||
Latest:
|
||||
* https://dl.influxdata.com/telegraf/releases/telegraf_0.13.0-1_amd64.deb
|
||||
* https://dl.influxdata.com/telegraf/releases/telegraf-0.13.0-1.x86_64.rpm
|
||||
* https://dl.influxdata.com/telegraf/releases/telegraf_1.0.0-beta2_amd64.deb
|
||||
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0_beta2.x86_64.rpm
|
||||
|
||||
Latest (arm):
|
||||
* https://dl.influxdata.com/telegraf/releases/telegraf_0.13.0-1_armhf.deb
|
||||
* https://dl.influxdata.com/telegraf/releases/telegraf-0.13.0-1.armhf.rpm
|
||||
* https://dl.influxdata.com/telegraf/releases/telegraf_1.0.0-beta2_armhf.deb
|
||||
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0_beta2.armhf.rpm
|
||||
|
||||
##### Package Instructions:
|
||||
|
||||
@@ -46,32 +46,14 @@ to use this repo to install & update telegraf.
|
||||
### Linux tarballs:
|
||||
|
||||
Latest:
|
||||
* https://dl.influxdata.com/telegraf/releases/telegraf-0.13.0-1_linux_amd64.tar.gz
|
||||
* https://dl.influxdata.com/telegraf/releases/telegraf-0.13.0-1_linux_i386.tar.gz
|
||||
* https://dl.influxdata.com/telegraf/releases/telegraf-0.13.0-1_linux_armhf.tar.gz
|
||||
|
||||
##### tarball Instructions:
|
||||
|
||||
To install the full directory structure with config file, run:
|
||||
|
||||
```
|
||||
sudo tar -C / -zxvf ./telegraf-0.13.0-1_linux_amd64.tar.gz
|
||||
```
|
||||
|
||||
To extract only the binary, run:
|
||||
|
||||
```
|
||||
tar -zxvf telegraf-0.13.0-1_linux_amd64.tar.gz --strip-components=3 ./usr/bin/telegraf
|
||||
```
|
||||
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0-beta2_linux_amd64.tar.gz
|
||||
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0-beta2_linux_i386.tar.gz
|
||||
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0-beta2_linux_armhf.tar.gz
|
||||
|
||||
### FreeBSD tarball:
|
||||
|
||||
Latest:
|
||||
* https://dl.influxdata.com/telegraf/releases/telegraf-0.13.0-1_freebsd_amd64.tar.gz
|
||||
|
||||
##### tarball Instructions:
|
||||
|
||||
See linux instructions above.
|
||||
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0-beta2_freebsd_amd64.tar.gz
|
||||
|
||||
### Ansible Role:
|
||||
|
||||
@@ -87,8 +69,7 @@ brew install telegraf
|
||||
### Windows Binaries (EXPERIMENTAL)
|
||||
|
||||
Latest:
|
||||
* https://dl.influxdata.com/telegraf/releases/telegraf-0.13.0-1_windows_amd64.zip
|
||||
* https://dl.influxdata.com/telegraf/releases/telegraf-0.13.0-1_windows_i386.zip
|
||||
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0-beta2_windows_amd64.zip
|
||||
|
||||
### From Source:
|
||||
|
||||
@@ -161,6 +142,10 @@ Currently implemented sources:
|
||||
* [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)
|
||||
* [ceph](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/ceph)
|
||||
* [chrony](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/chrony)
|
||||
* [consul](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/consul)
|
||||
* [conntrack](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/conntrack)
|
||||
* [couchbase](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/couchbase)
|
||||
* [couchdb](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/couchdb)
|
||||
* [disque](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/disque)
|
||||
@@ -186,6 +171,7 @@ Currently implemented sources:
|
||||
* [net_response](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/net_response)
|
||||
* [nginx](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/nginx)
|
||||
* [nsq](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/nsq)
|
||||
* [nstat](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/nstat)
|
||||
* [ntpq](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/ntpq)
|
||||
* [phpfpm](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/phpfpm)
|
||||
* [phusion passenger](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/passenger)
|
||||
@@ -205,6 +191,7 @@ Currently implemented sources:
|
||||
* [snmp](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/snmp)
|
||||
* [sql server](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/sqlserver) (microsoft)
|
||||
* [twemproxy](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/twemproxy)
|
||||
* [varnish](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/varnish)
|
||||
* [zfs](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/zfs)
|
||||
* [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)
|
||||
@@ -219,16 +206,19 @@ Currently implemented sources:
|
||||
* swap
|
||||
* processes
|
||||
* kernel (/proc/stat)
|
||||
* kernel (/proc/vmstat)
|
||||
|
||||
Telegraf can also collect metrics via the following service plugins:
|
||||
|
||||
* [statsd](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/statsd)
|
||||
* [tail](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/tail)
|
||||
* [udp_listener](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/udp_listener)
|
||||
* [tcp_listener](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/tcp_listener)
|
||||
* [mqtt_consumer](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/mqtt_consumer)
|
||||
* [kafka_consumer](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/kafka_consumer)
|
||||
* [nats_consumer](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/nats_consumer)
|
||||
* [github_webhooks](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/github_webhooks)
|
||||
* [rollbar_webhooks](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/rollbar_webhooks)
|
||||
|
||||
We'll be adding support for many more over the coming months. Read on if you
|
||||
want to add support for another service or third-party API.
|
||||
@@ -243,6 +233,8 @@ want to add support for another service or third-party API.
|
||||
* [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)
|
||||
* [graylog](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/graylog)
|
||||
* [instrumental](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/instrumental)
|
||||
* [kafka](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/kafka)
|
||||
* [librato](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/librato)
|
||||
* [mqtt](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/mqtt)
|
||||
|
||||
@@ -18,4 +18,8 @@ type Accumulator interface {
|
||||
|
||||
Debug() bool
|
||||
SetDebug(enabled bool)
|
||||
|
||||
SetPrecision(precision, interval time.Duration)
|
||||
|
||||
DisablePrecision()
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
@@ -18,21 +17,24 @@ func NewAccumulator(
|
||||
acc := accumulator{}
|
||||
acc.metrics = metrics
|
||||
acc.inputConfig = inputConfig
|
||||
acc.precision = time.Nanosecond
|
||||
return &acc
|
||||
}
|
||||
|
||||
type accumulator struct {
|
||||
sync.Mutex
|
||||
|
||||
metrics chan telegraf.Metric
|
||||
|
||||
defaultTags map[string]string
|
||||
|
||||
debug bool
|
||||
// print every point added to the accumulator
|
||||
trace bool
|
||||
|
||||
inputConfig *internal_models.InputConfig
|
||||
|
||||
prefix string
|
||||
|
||||
precision time.Duration
|
||||
}
|
||||
|
||||
func (ac *accumulator) Add(
|
||||
@@ -84,13 +86,17 @@ func (ac *accumulator) AddFields(
|
||||
if tags == nil {
|
||||
tags = make(map[string]string)
|
||||
}
|
||||
// Apply daemon-wide tags if set
|
||||
for k, v := range ac.defaultTags {
|
||||
tags[k] = v
|
||||
}
|
||||
// Apply plugin-wide tags if set
|
||||
for k, v := range ac.inputConfig.Tags {
|
||||
tags[k] = v
|
||||
if _, ok := tags[k]; !ok {
|
||||
tags[k] = v
|
||||
}
|
||||
}
|
||||
// Apply daemon-wide tags if set
|
||||
for k, v := range ac.defaultTags {
|
||||
if _, ok := tags[k]; !ok {
|
||||
tags[k] = v
|
||||
}
|
||||
}
|
||||
ac.inputConfig.Filter.FilterTags(tags)
|
||||
|
||||
@@ -138,6 +144,7 @@ func (ac *accumulator) AddFields(
|
||||
} else {
|
||||
timestamp = time.Now()
|
||||
}
|
||||
timestamp = timestamp.Round(ac.precision)
|
||||
|
||||
if ac.prefix != "" {
|
||||
measurement = ac.prefix + measurement
|
||||
@@ -148,7 +155,7 @@ func (ac *accumulator) AddFields(
|
||||
log.Printf("Error adding point [%s]: %s\n", measurement, err.Error())
|
||||
return
|
||||
}
|
||||
if ac.debug {
|
||||
if ac.trace {
|
||||
fmt.Println("> " + m.String())
|
||||
}
|
||||
ac.metrics <- m
|
||||
@@ -162,6 +169,39 @@ func (ac *accumulator) SetDebug(debug bool) {
|
||||
ac.debug = debug
|
||||
}
|
||||
|
||||
func (ac *accumulator) Trace() bool {
|
||||
return ac.trace
|
||||
}
|
||||
|
||||
func (ac *accumulator) SetTrace(trace bool) {
|
||||
ac.trace = trace
|
||||
}
|
||||
|
||||
// SetPrecision takes two time.Duration objects. If the first is non-zero,
|
||||
// it sets that as the precision. Otherwise, it takes the second argument
|
||||
// as the order of time that the metrics should be rounded to, with the
|
||||
// maximum being 1s.
|
||||
func (ac *accumulator) SetPrecision(precision, interval time.Duration) {
|
||||
if precision > 0 {
|
||||
ac.precision = precision
|
||||
return
|
||||
}
|
||||
switch {
|
||||
case interval >= time.Second:
|
||||
ac.precision = time.Second
|
||||
case interval >= time.Millisecond:
|
||||
ac.precision = time.Millisecond
|
||||
case interval >= time.Microsecond:
|
||||
ac.precision = time.Microsecond
|
||||
default:
|
||||
ac.precision = time.Nanosecond
|
||||
}
|
||||
}
|
||||
|
||||
func (ac *accumulator) DisablePrecision() {
|
||||
ac.precision = time.Nanosecond
|
||||
}
|
||||
|
||||
func (ac *accumulator) setDefaultTags(tags map[string]string) {
|
||||
ac.defaultTags = tags
|
||||
}
|
||||
|
||||
@@ -38,6 +38,128 @@ func TestAdd(t *testing.T) {
|
||||
actual)
|
||||
}
|
||||
|
||||
func TestAddNoPrecisionWithInterval(t *testing.T) {
|
||||
a := accumulator{}
|
||||
now := time.Date(2006, time.February, 10, 12, 0, 0, 82912748, time.UTC)
|
||||
a.metrics = make(chan telegraf.Metric, 10)
|
||||
defer close(a.metrics)
|
||||
a.inputConfig = &internal_models.InputConfig{}
|
||||
|
||||
a.SetPrecision(0, time.Second)
|
||||
a.Add("acctest", float64(101), map[string]string{})
|
||||
a.Add("acctest", float64(101), map[string]string{"acc": "test"})
|
||||
a.Add("acctest", float64(101), map[string]string{"acc": "test"}, now)
|
||||
|
||||
testm := <-a.metrics
|
||||
actual := testm.String()
|
||||
assert.Contains(t, actual, "acctest value=101")
|
||||
|
||||
testm = <-a.metrics
|
||||
actual = testm.String()
|
||||
assert.Contains(t, actual, "acctest,acc=test value=101")
|
||||
|
||||
testm = <-a.metrics
|
||||
actual = testm.String()
|
||||
assert.Equal(t,
|
||||
fmt.Sprintf("acctest,acc=test value=101 %d", int64(1139572800000000000)),
|
||||
actual)
|
||||
}
|
||||
|
||||
func TestAddNoIntervalWithPrecision(t *testing.T) {
|
||||
a := accumulator{}
|
||||
now := time.Date(2006, time.February, 10, 12, 0, 0, 82912748, time.UTC)
|
||||
a.metrics = make(chan telegraf.Metric, 10)
|
||||
defer close(a.metrics)
|
||||
a.inputConfig = &internal_models.InputConfig{}
|
||||
|
||||
a.SetPrecision(time.Second, time.Millisecond)
|
||||
a.Add("acctest", float64(101), map[string]string{})
|
||||
a.Add("acctest", float64(101), map[string]string{"acc": "test"})
|
||||
a.Add("acctest", float64(101), map[string]string{"acc": "test"}, now)
|
||||
|
||||
testm := <-a.metrics
|
||||
actual := testm.String()
|
||||
assert.Contains(t, actual, "acctest value=101")
|
||||
|
||||
testm = <-a.metrics
|
||||
actual = testm.String()
|
||||
assert.Contains(t, actual, "acctest,acc=test value=101")
|
||||
|
||||
testm = <-a.metrics
|
||||
actual = testm.String()
|
||||
assert.Equal(t,
|
||||
fmt.Sprintf("acctest,acc=test value=101 %d", int64(1139572800000000000)),
|
||||
actual)
|
||||
}
|
||||
|
||||
func TestAddDisablePrecision(t *testing.T) {
|
||||
a := accumulator{}
|
||||
now := time.Date(2006, time.February, 10, 12, 0, 0, 82912748, time.UTC)
|
||||
a.metrics = make(chan telegraf.Metric, 10)
|
||||
defer close(a.metrics)
|
||||
a.inputConfig = &internal_models.InputConfig{}
|
||||
|
||||
a.SetPrecision(time.Second, time.Millisecond)
|
||||
a.DisablePrecision()
|
||||
a.Add("acctest", float64(101), map[string]string{})
|
||||
a.Add("acctest", float64(101), map[string]string{"acc": "test"})
|
||||
a.Add("acctest", float64(101), map[string]string{"acc": "test"}, now)
|
||||
|
||||
testm := <-a.metrics
|
||||
actual := testm.String()
|
||||
assert.Contains(t, actual, "acctest value=101")
|
||||
|
||||
testm = <-a.metrics
|
||||
actual = testm.String()
|
||||
assert.Contains(t, actual, "acctest,acc=test value=101")
|
||||
|
||||
testm = <-a.metrics
|
||||
actual = testm.String()
|
||||
assert.Equal(t,
|
||||
fmt.Sprintf("acctest,acc=test value=101 %d", int64(1139572800082912748)),
|
||||
actual)
|
||||
}
|
||||
|
||||
func TestDifferentPrecisions(t *testing.T) {
|
||||
a := accumulator{}
|
||||
now := time.Date(2006, time.February, 10, 12, 0, 0, 82912748, time.UTC)
|
||||
a.metrics = make(chan telegraf.Metric, 10)
|
||||
defer close(a.metrics)
|
||||
a.inputConfig = &internal_models.InputConfig{}
|
||||
|
||||
a.SetPrecision(0, time.Second)
|
||||
a.Add("acctest", float64(101), map[string]string{"acc": "test"}, now)
|
||||
testm := <-a.metrics
|
||||
actual := testm.String()
|
||||
assert.Equal(t,
|
||||
fmt.Sprintf("acctest,acc=test value=101 %d", int64(1139572800000000000)),
|
||||
actual)
|
||||
|
||||
a.SetPrecision(0, time.Millisecond)
|
||||
a.Add("acctest", float64(101), map[string]string{"acc": "test"}, now)
|
||||
testm = <-a.metrics
|
||||
actual = testm.String()
|
||||
assert.Equal(t,
|
||||
fmt.Sprintf("acctest,acc=test value=101 %d", int64(1139572800083000000)),
|
||||
actual)
|
||||
|
||||
a.SetPrecision(0, time.Microsecond)
|
||||
a.Add("acctest", float64(101), map[string]string{"acc": "test"}, now)
|
||||
testm = <-a.metrics
|
||||
actual = testm.String()
|
||||
assert.Equal(t,
|
||||
fmt.Sprintf("acctest,acc=test value=101 %d", int64(1139572800082913000)),
|
||||
actual)
|
||||
|
||||
a.SetPrecision(0, time.Nanosecond)
|
||||
a.Add("acctest", float64(101), map[string]string{"acc": "test"}, now)
|
||||
testm = <-a.metrics
|
||||
actual = testm.String()
|
||||
assert.Equal(t,
|
||||
fmt.Sprintf("acctest,acc=test value=101 %d", int64(1139572800082912748)),
|
||||
actual)
|
||||
}
|
||||
|
||||
func TestAddDefaultTags(t *testing.T) {
|
||||
a := accumulator{}
|
||||
a.addDefaultTag("default", "tag")
|
||||
|
||||
189
agent/agent.go
189
agent/agent.go
@@ -1,17 +1,15 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
cryptorand "crypto/rand"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/internal"
|
||||
"github.com/influxdata/telegraf/internal/config"
|
||||
"github.com/influxdata/telegraf/internal/models"
|
||||
)
|
||||
@@ -102,93 +100,41 @@ func panicRecover(input *internal_models.RunningInput) {
|
||||
}
|
||||
}
|
||||
|
||||
// gatherParallel runs the inputs that are using the same reporting interval
|
||||
// as the telegraf agent.
|
||||
func (a *Agent) gatherParallel(metricC chan telegraf.Metric) error {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
start := time.Now()
|
||||
counter := 0
|
||||
jitter := a.Config.Agent.CollectionJitter.Duration.Nanoseconds()
|
||||
for _, input := range a.Config.Inputs {
|
||||
if input.Config.Interval != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
counter++
|
||||
go func(input *internal_models.RunningInput) {
|
||||
defer panicRecover(input)
|
||||
defer wg.Done()
|
||||
|
||||
acc := NewAccumulator(input.Config, metricC)
|
||||
acc.SetDebug(a.Config.Agent.Debug)
|
||||
acc.setDefaultTags(a.Config.Tags)
|
||||
|
||||
if jitter != 0 {
|
||||
nanoSleep := rand.Int63n(jitter)
|
||||
d, err := time.ParseDuration(fmt.Sprintf("%dns", nanoSleep))
|
||||
if err != nil {
|
||||
log.Printf("Jittering collection interval failed for plugin %s",
|
||||
input.Name)
|
||||
} else {
|
||||
time.Sleep(d)
|
||||
}
|
||||
}
|
||||
|
||||
if err := input.Input.Gather(acc); err != nil {
|
||||
log.Printf("Error in input [%s]: %s", input.Name, err)
|
||||
}
|
||||
|
||||
}(input)
|
||||
}
|
||||
|
||||
if counter == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
elapsed := time.Since(start)
|
||||
if !a.Config.Agent.Quiet {
|
||||
log.Printf("Gathered metrics, (%s interval), from %d inputs in %s\n",
|
||||
a.Config.Agent.Interval.Duration, counter, elapsed)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// gatherSeparate runs the inputs that have been configured with their own
|
||||
// gatherer runs the inputs that have been configured with their own
|
||||
// reporting interval.
|
||||
func (a *Agent) gatherSeparate(
|
||||
func (a *Agent) gatherer(
|
||||
shutdown chan struct{},
|
||||
input *internal_models.RunningInput,
|
||||
interval time.Duration,
|
||||
metricC chan telegraf.Metric,
|
||||
) error {
|
||||
defer panicRecover(input)
|
||||
|
||||
ticker := time.NewTicker(input.Config.Interval)
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
var outerr error
|
||||
start := time.Now()
|
||||
|
||||
acc := NewAccumulator(input.Config, metricC)
|
||||
acc.SetDebug(a.Config.Agent.Debug)
|
||||
acc.SetPrecision(a.Config.Agent.Precision.Duration,
|
||||
a.Config.Agent.Interval.Duration)
|
||||
acc.setDefaultTags(a.Config.Tags)
|
||||
|
||||
if err := input.Input.Gather(acc); err != nil {
|
||||
log.Printf("Error in input [%s]: %s", input.Name, err)
|
||||
}
|
||||
internal.RandomSleep(a.Config.Agent.CollectionJitter.Duration, shutdown)
|
||||
|
||||
start := time.Now()
|
||||
gatherWithTimeout(shutdown, input, acc, interval)
|
||||
elapsed := time.Since(start)
|
||||
if !a.Config.Agent.Quiet {
|
||||
log.Printf("Gathered metrics, (separate %s interval), from %s in %s\n",
|
||||
input.Config.Interval, input.Name, elapsed)
|
||||
}
|
||||
|
||||
if outerr != nil {
|
||||
return outerr
|
||||
}
|
||||
if a.Config.Agent.Debug {
|
||||
log.Printf("Input [%s] gathered metrics, (%s interval) in %s\n",
|
||||
input.Name, interval, elapsed)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-shutdown:
|
||||
@@ -199,6 +145,42 @@ func (a *Agent) gatherSeparate(
|
||||
}
|
||||
}
|
||||
|
||||
// gatherWithTimeout gathers from the given input, with the given timeout.
|
||||
// when the given timeout is reached, gatherWithTimeout logs an error message
|
||||
// but continues waiting for it to return. This is to avoid leaving behind
|
||||
// hung processes, and to prevent re-calling the same hung process over and
|
||||
// over.
|
||||
func gatherWithTimeout(
|
||||
shutdown chan struct{},
|
||||
input *internal_models.RunningInput,
|
||||
acc *accumulator,
|
||||
timeout time.Duration,
|
||||
) {
|
||||
ticker := time.NewTicker(timeout)
|
||||
defer ticker.Stop()
|
||||
done := make(chan error)
|
||||
go func() {
|
||||
done <- input.Input.Gather(acc)
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case err := <-done:
|
||||
if err != nil {
|
||||
log.Printf("ERROR in input [%s]: %s", input.Name, err)
|
||||
}
|
||||
return
|
||||
case <-ticker.C:
|
||||
log.Printf("ERROR: input [%s] took longer to collect than "+
|
||||
"collection interval (%s)",
|
||||
input.Name, timeout)
|
||||
continue
|
||||
case <-shutdown:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test verifies that we can 'Gather' from all inputs with their configured
|
||||
// Config struct
|
||||
func (a *Agent) Test() error {
|
||||
@@ -220,7 +202,9 @@ func (a *Agent) Test() error {
|
||||
|
||||
for _, input := range a.Config.Inputs {
|
||||
acc := NewAccumulator(input.Config, metricC)
|
||||
acc.SetDebug(true)
|
||||
acc.SetTrace(true)
|
||||
acc.SetPrecision(a.Config.Agent.Precision.Duration,
|
||||
a.Config.Agent.Interval.Duration)
|
||||
acc.setDefaultTags(a.Config.Tags)
|
||||
|
||||
fmt.Printf("* Plugin: %s, Collection 1\n", input.Name)
|
||||
@@ -281,6 +265,7 @@ func (a *Agent) flusher(shutdown chan struct{}, metricC chan telegraf.Metric) er
|
||||
a.flush()
|
||||
return nil
|
||||
case <-ticker.C:
|
||||
internal.RandomSleep(a.Config.Agent.FlushJitter.Duration, shutdown)
|
||||
a.flush()
|
||||
case m := <-metricC:
|
||||
for _, o := range a.Config.Outputs {
|
||||
@@ -290,35 +275,10 @@ func (a *Agent) flusher(shutdown chan struct{}, metricC chan telegraf.Metric) er
|
||||
}
|
||||
}
|
||||
|
||||
// jitterInterval applies the the interval jitter to the flush interval using
|
||||
// crypto/rand number generator
|
||||
func jitterInterval(ininterval, injitter time.Duration) time.Duration {
|
||||
var jitter int64
|
||||
outinterval := ininterval
|
||||
if injitter.Nanoseconds() != 0 {
|
||||
maxjitter := big.NewInt(injitter.Nanoseconds())
|
||||
if j, err := cryptorand.Int(cryptorand.Reader, maxjitter); err == nil {
|
||||
jitter = j.Int64()
|
||||
}
|
||||
outinterval = time.Duration(jitter + ininterval.Nanoseconds())
|
||||
}
|
||||
|
||||
if outinterval.Nanoseconds() < time.Duration(500*time.Millisecond).Nanoseconds() {
|
||||
log.Printf("Flush interval %s too low, setting to 500ms\n", outinterval)
|
||||
outinterval = time.Duration(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
return outinterval
|
||||
}
|
||||
|
||||
// Run runs the agent daemon, gathering every Interval
|
||||
func (a *Agent) Run(shutdown chan struct{}) error {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
a.Config.Agent.FlushInterval.Duration = jitterInterval(
|
||||
a.Config.Agent.FlushInterval.Duration,
|
||||
a.Config.Agent.FlushJitter.Duration)
|
||||
|
||||
log.Printf("Agent Config: Interval:%s, Debug:%#v, Quiet:%#v, Hostname:%#v, "+
|
||||
"Flush Interval:%s \n",
|
||||
a.Config.Agent.Interval.Duration, a.Config.Agent.Debug, a.Config.Agent.Quiet,
|
||||
@@ -333,6 +293,9 @@ func (a *Agent) Run(shutdown chan struct{}) error {
|
||||
case telegraf.ServiceInput:
|
||||
acc := NewAccumulator(input.Config, metricC)
|
||||
acc.SetDebug(a.Config.Agent.Debug)
|
||||
// Service input plugins should set their own precision of their
|
||||
// metrics.
|
||||
acc.DisablePrecision()
|
||||
acc.setDefaultTags(a.Config.Tags)
|
||||
if err := p.Start(acc); err != nil {
|
||||
log.Printf("Service for input %s failed to start, exiting\n%s\n",
|
||||
@@ -348,7 +311,6 @@ func (a *Agent) Run(shutdown chan struct{}) error {
|
||||
i := int64(a.Config.Agent.Interval.Duration)
|
||||
time.Sleep(time.Duration(i - (time.Now().UnixNano() % i)))
|
||||
}
|
||||
ticker := time.NewTicker(a.Config.Agent.Interval.Duration)
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
@@ -359,32 +321,21 @@ func (a *Agent) Run(shutdown chan struct{}) error {
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Add(len(a.Config.Inputs))
|
||||
for _, input := range a.Config.Inputs {
|
||||
// Special handling for inputs that have their own collection interval
|
||||
// configured. Default intervals are handled below with gatherParallel
|
||||
interval := a.Config.Agent.Interval.Duration
|
||||
// overwrite global interval if this plugin has it's own.
|
||||
if input.Config.Interval != 0 {
|
||||
wg.Add(1)
|
||||
go func(input *internal_models.RunningInput) {
|
||||
defer wg.Done()
|
||||
if err := a.gatherSeparate(shutdown, input, metricC); err != nil {
|
||||
log.Printf(err.Error())
|
||||
}
|
||||
}(input)
|
||||
interval = input.Config.Interval
|
||||
}
|
||||
go func(in *internal_models.RunningInput, interv time.Duration) {
|
||||
defer wg.Done()
|
||||
if err := a.gatherer(shutdown, in, interv, metricC); err != nil {
|
||||
log.Printf(err.Error())
|
||||
}
|
||||
}(input, interval)
|
||||
}
|
||||
|
||||
defer wg.Wait()
|
||||
|
||||
for {
|
||||
if err := a.gatherParallel(metricC); err != nil {
|
||||
log.Printf(err.Error())
|
||||
}
|
||||
|
||||
select {
|
||||
case <-shutdown:
|
||||
return nil
|
||||
case <-ticker.C:
|
||||
continue
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package agent
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf/internal/config"
|
||||
|
||||
@@ -110,75 +109,3 @@ func TestAgent_LoadOutput(t *testing.T) {
|
||||
a, _ = NewAgent(c)
|
||||
assert.Equal(t, 3, len(a.Config.Outputs))
|
||||
}
|
||||
|
||||
func TestAgent_ZeroJitter(t *testing.T) {
|
||||
flushinterval := jitterInterval(time.Duration(10*time.Second),
|
||||
time.Duration(0*time.Second))
|
||||
|
||||
actual := flushinterval.Nanoseconds()
|
||||
exp := time.Duration(10 * time.Second).Nanoseconds()
|
||||
|
||||
if actual != exp {
|
||||
t.Errorf("Actual %v, expected %v", actual, exp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_ZeroInterval(t *testing.T) {
|
||||
min := time.Duration(500 * time.Millisecond).Nanoseconds()
|
||||
max := time.Duration(5 * time.Second).Nanoseconds()
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
flushinterval := jitterInterval(time.Duration(0*time.Second),
|
||||
time.Duration(5*time.Second))
|
||||
actual := flushinterval.Nanoseconds()
|
||||
|
||||
if actual > max {
|
||||
t.Errorf("Didn't expect interval %d to be > %d", actual, max)
|
||||
break
|
||||
}
|
||||
if actual < min {
|
||||
t.Errorf("Didn't expect interval %d to be < %d", actual, min)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_ZeroBoth(t *testing.T) {
|
||||
flushinterval := jitterInterval(time.Duration(0*time.Second),
|
||||
time.Duration(0*time.Second))
|
||||
|
||||
actual := flushinterval
|
||||
exp := time.Duration(500 * time.Millisecond)
|
||||
|
||||
if actual != exp {
|
||||
t.Errorf("Actual %v, expected %v", actual, exp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_JitterMax(t *testing.T) {
|
||||
max := time.Duration(32 * time.Second).Nanoseconds()
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
flushinterval := jitterInterval(time.Duration(30*time.Second),
|
||||
time.Duration(2*time.Second))
|
||||
actual := flushinterval.Nanoseconds()
|
||||
if actual > max {
|
||||
t.Errorf("Didn't expect interval %d to be > %d", actual, max)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_JitterMin(t *testing.T) {
|
||||
min := time.Duration(30 * time.Second).Nanoseconds()
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
flushinterval := jitterInterval(time.Duration(30*time.Second),
|
||||
time.Duration(2*time.Second))
|
||||
actual := flushinterval.Nanoseconds()
|
||||
if actual < min {
|
||||
t.Errorf("Didn't expect interval %d to be < %d", actual, min)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,49 +186,59 @@ name of the plugin.
|
||||
# Graphite:
|
||||
|
||||
The Graphite data format translates graphite _dot_ buckets directly into
|
||||
telegraf measurement names, with a single value field, and without any tags. For
|
||||
more advanced options, Telegraf supports specifying "templates" to translate
|
||||
telegraf measurement names, with a single value field, and without any tags.
|
||||
By default, the separator is left as ".", but this can be changed using the
|
||||
"separator" argument. For more advanced options,
|
||||
Telegraf supports specifying "templates" to translate
|
||||
graphite buckets into Telegraf metrics.
|
||||
|
||||
#### Separator:
|
||||
|
||||
You can specify a separator to use for the parsed metrics.
|
||||
By default, it will leave the metrics with a "." separator.
|
||||
Setting `separator = "_"` will translate:
|
||||
Templates are of the form:
|
||||
|
||||
```
|
||||
cpu.usage.idle 99
|
||||
=> cpu_usage_idle value=99
|
||||
"host.mytag.mytag.measurement.measurement.field*"
|
||||
```
|
||||
|
||||
#### Measurement/Tag Templates:
|
||||
Where the following keywords exist:
|
||||
|
||||
1. `measurement`: specifies that this section of the graphite bucket corresponds
|
||||
to the measurement name. This can be specified multiple times.
|
||||
2. `field`: specifies that this section of the graphite bucket corresponds
|
||||
to the field name. This can be specified multiple times.
|
||||
3. `measurement*`: specifies that all remaining elements of the graphite bucket
|
||||
correspond to the measurement name.
|
||||
4. `field*`: specifies that all remaining elements of the graphite bucket
|
||||
correspond to the field name.
|
||||
|
||||
Any part of the template that is not a keyword is treated as a tag key. This
|
||||
can also be specified multiple times.
|
||||
|
||||
NOTE: `field*` cannot be used in conjunction with `measurement*`!
|
||||
|
||||
#### Measurement & Tag Templates:
|
||||
|
||||
The most basic template is to specify a single transformation to apply to all
|
||||
incoming metrics. _measurement_ is a special keyword that tells Telegraf which
|
||||
parts of the graphite bucket to combine into the measurement name. It can have a
|
||||
trailing `*` to indicate that the remainder of the metric should be used.
|
||||
Other words are considered tag keys. So the following template:
|
||||
incoming metrics. So the following template:
|
||||
|
||||
```toml
|
||||
templates = [
|
||||
"region.measurement*"
|
||||
"region.region.measurement*"
|
||||
]
|
||||
```
|
||||
|
||||
would result in the following Graphite -> Telegraf transformation.
|
||||
|
||||
```
|
||||
us-west.cpu.load 100
|
||||
=> cpu.load,region=us-west value=100
|
||||
us.west.cpu.load 100
|
||||
=> cpu.load,region=us.west value=100
|
||||
```
|
||||
|
||||
#### Field Templates:
|
||||
|
||||
There is also a _field_ keyword, which can only be specified once.
|
||||
The field keyword tells Telegraf to give the metric that field name.
|
||||
So the following template:
|
||||
|
||||
```toml
|
||||
separator = "_"
|
||||
templates = [
|
||||
"measurement.measurement.field.field.region"
|
||||
]
|
||||
@@ -237,24 +247,26 @@ templates = [
|
||||
would result in the following Graphite -> Telegraf transformation.
|
||||
|
||||
```
|
||||
cpu.usage.idle.percent.us-west 100
|
||||
=> cpu_usage,region=us-west idle_percent=100
|
||||
cpu.usage.idle.percent.eu-east 100
|
||||
=> cpu_usage,region=eu-east idle_percent=100
|
||||
```
|
||||
|
||||
The field key can also be derived from the second "half" of the input metric-name by specifying ```field*```:
|
||||
The field key can also be derived from all remaining elements of the graphite
|
||||
bucket by specifying `field*`:
|
||||
|
||||
```toml
|
||||
separator = "_"
|
||||
templates = [
|
||||
"measurement.measurement.region.field*"
|
||||
]
|
||||
```
|
||||
|
||||
would result in the following Graphite -> Telegraf transformation.
|
||||
which would result in the following Graphite -> Telegraf transformation.
|
||||
|
||||
```
|
||||
cpu.usage.us-west.idle.percentage 100
|
||||
=> cpu_usage,region=us-west idle_percentage=100
|
||||
cpu.usage.eu-east.idle.percentage 100
|
||||
=> cpu_usage,region=eu-east idle_percentage=100
|
||||
```
|
||||
(This cannot be used in conjunction with "measurement*"!)
|
||||
|
||||
#### Filter Templates:
|
||||
|
||||
@@ -271,8 +283,8 @@ templates = [
|
||||
which would result in the following transformation:
|
||||
|
||||
```
|
||||
cpu.load.us-west 100
|
||||
=> cpu_load,region=us-west value=100
|
||||
cpu.load.eu-east 100
|
||||
=> cpu_load,region=eu-east value=100
|
||||
|
||||
mem.cached.localhost 256
|
||||
=> mem_cached,host=localhost value=256
|
||||
@@ -294,8 +306,8 @@ templates = [
|
||||
would result in the following Graphite -> Telegraf transformation.
|
||||
|
||||
```
|
||||
cpu.usage.idle.us-west 100
|
||||
=> cpu_usage,region=us-west,datacenter=1a idle=100
|
||||
cpu.usage.idle.eu-east 100
|
||||
=> cpu_usage,region=eu-east,datacenter=1a idle=100
|
||||
```
|
||||
|
||||
There are many more options available,
|
||||
@@ -326,12 +338,12 @@ There are many more options available,
|
||||
## similar to the line protocol format. There can be only one default template.
|
||||
## Templates support below format:
|
||||
## 1. filter + template
|
||||
## 2. filter + template + extra tag
|
||||
## 2. filter + template + extra tag(s)
|
||||
## 3. filter + template with field key
|
||||
## 4. default template
|
||||
templates = [
|
||||
"*.app env.service.resource.measurement",
|
||||
"stats.* .host.measurement* region=us-west,agent=sensu",
|
||||
"stats.* .host.measurement* region=eu-east,agent=sensu",
|
||||
"stats2.* .host.measurement.field",
|
||||
"measurement*"
|
||||
]
|
||||
|
||||
@@ -52,6 +52,11 @@
|
||||
## ie, a jitter of 5s and interval 10s means flushes will happen every 10-15s
|
||||
flush_jitter = "0s"
|
||||
|
||||
## By default, precision will be set to the same timestamp order as the
|
||||
## collection interval, with the maximum being 1s.
|
||||
## Precision will NOT be used for service inputs, such as logparser and statsd.
|
||||
## Valid values are "Nns", "Nus" (or "Nµs"), "Nms", "Ns".
|
||||
precision = ""
|
||||
## Run telegraf in debug mode
|
||||
debug = false
|
||||
## Run telegraf in quiet mode
|
||||
@@ -75,11 +80,11 @@
|
||||
urls = ["http://localhost:8086"] # required
|
||||
## The target database for metrics (telegraf will create it if not exists).
|
||||
database = "telegraf" # required
|
||||
## Retention policy to write to.
|
||||
retention_policy = "default"
|
||||
## Precision of writes, valid values are "ns", "us" (or "µs"), "ms", "s", "m", "h".
|
||||
## note: using "s" precision greatly improves InfluxDB compression.
|
||||
precision = "s"
|
||||
|
||||
## Retention policy to write to. Empty string writes to the default rp.
|
||||
retention_policy = ""
|
||||
## Write consistency (clusters only), can be: "any", "one", "quorom", "all"
|
||||
write_consistency = "any"
|
||||
|
||||
## Write timeout (for the InfluxDB client), formatted as a string.
|
||||
## If not provided, will default to 5s. 0s means no timeout (not recommended).
|
||||
@@ -103,10 +108,10 @@
|
||||
# [[outputs.amon]]
|
||||
# ## Amon Server Key
|
||||
# server_key = "my-server-key" # required.
|
||||
#
|
||||
#
|
||||
# ## Amon Instance URL
|
||||
# amon_instance = "https://youramoninstance" # required
|
||||
#
|
||||
#
|
||||
# ## Connection timeout.
|
||||
# # timeout = "5s"
|
||||
|
||||
@@ -122,21 +127,21 @@
|
||||
# ## Telegraf tag to use as a routing key
|
||||
# ## ie, if this tag exists, it's value will be used as the routing key
|
||||
# routing_tag = "host"
|
||||
#
|
||||
#
|
||||
# ## InfluxDB retention policy
|
||||
# # retention_policy = "default"
|
||||
# ## InfluxDB database
|
||||
# # database = "telegraf"
|
||||
# ## InfluxDB precision
|
||||
# # precision = "s"
|
||||
#
|
||||
#
|
||||
# ## Optional SSL Config
|
||||
# # ssl_ca = "/etc/telegraf/ca.pem"
|
||||
# # ssl_cert = "/etc/telegraf/cert.pem"
|
||||
# # ssl_key = "/etc/telegraf/key.pem"
|
||||
# ## Use SSL but skip chain & host verification
|
||||
# # insecure_skip_verify = false
|
||||
#
|
||||
#
|
||||
# ## Data format to output.
|
||||
# ## Each data format has it's own unique set of configuration options, read
|
||||
# ## more about them here:
|
||||
@@ -148,16 +153,22 @@
|
||||
# [[outputs.cloudwatch]]
|
||||
# ## Amazon REGION
|
||||
# region = 'us-east-1'
|
||||
#
|
||||
#
|
||||
# ## Amazon Credentials
|
||||
# ## Credentials are loaded in the following order
|
||||
# ## 1) explicit credentials from 'access_key' and 'secret_key'
|
||||
# ## 2) environment variables
|
||||
# ## 3) shared credentials file
|
||||
# ## 4) EC2 Instance Profile
|
||||
# ## 1) Assumed credentials via STS if role_arn is specified
|
||||
# ## 2) explicit credentials from 'access_key' and 'secret_key'
|
||||
# ## 3) shared profile from 'profile'
|
||||
# ## 4) environment variables
|
||||
# ## 5) shared credentials file
|
||||
# ## 6) EC2 Instance Profile
|
||||
# #access_key = ""
|
||||
# #secret_key = ""
|
||||
#
|
||||
# #token = ""
|
||||
# #role_arn = ""
|
||||
# #profile = ""
|
||||
# #shared_credential_file = ""
|
||||
#
|
||||
# ## Namespace for the CloudWatch MetricDatums
|
||||
# namespace = 'InfluxData/Telegraf'
|
||||
|
||||
@@ -166,7 +177,7 @@
|
||||
# [[outputs.datadog]]
|
||||
# ## Datadog API key
|
||||
# apikey = "my-secret-key" # required.
|
||||
#
|
||||
#
|
||||
# ## Connection timeout.
|
||||
# # timeout = "5s"
|
||||
|
||||
@@ -175,7 +186,7 @@
|
||||
# [[outputs.file]]
|
||||
# ## Files to write to, "stdout" is a specially handled file.
|
||||
# files = ["stdout", "/tmp/metrics.out"]
|
||||
#
|
||||
#
|
||||
# ## Data format to output.
|
||||
# ## Each data format has it's own unique set of configuration options, read
|
||||
# ## more about them here:
|
||||
@@ -196,6 +207,27 @@
|
||||
# timeout = 2
|
||||
|
||||
|
||||
# # Send telegraf metrics to graylog(s)
|
||||
# [[outputs.graylog]]
|
||||
# ## Udp endpoint for your graylog instance.
|
||||
# servers = ["127.0.0.1:12201", "192.168.1.1:12201"]
|
||||
|
||||
|
||||
# # Configuration for sending metrics to an Instrumental project
|
||||
# [[outputs.instrumental]]
|
||||
# ## Project API Token (required)
|
||||
# api_token = "API Token" # required
|
||||
# ## Prefix the metrics with a given name
|
||||
# prefix = ""
|
||||
# ## Stats output template (Graphite formatting)
|
||||
# ## see https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md#graphite
|
||||
# template = "host.tags.measurement.field"
|
||||
# ## Timeout in seconds to connect
|
||||
# timeout = "2s"
|
||||
# ## Display Communcation to Instrumental
|
||||
# debug = false
|
||||
|
||||
|
||||
# # Configuration for the Kafka server to send metrics to
|
||||
# [[outputs.kafka]]
|
||||
# ## URLs of kafka brokers
|
||||
@@ -205,14 +237,14 @@
|
||||
# ## Telegraf tag to use as a routing key
|
||||
# ## ie, if this tag exists, it's value will be used as the routing key
|
||||
# routing_tag = "host"
|
||||
#
|
||||
#
|
||||
# ## CompressionCodec represents the various compression codecs recognized by
|
||||
# ## Kafka in messages.
|
||||
# ## 0 : No compression
|
||||
# ## 1 : Gzip compression
|
||||
# ## 2 : Snappy compression
|
||||
# compression_codec = 0
|
||||
#
|
||||
#
|
||||
# ## RequiredAcks is used in Produce Requests to tell the broker how many
|
||||
# ## replica acknowledgements it must see before responding
|
||||
# ## 0 : the producer never waits for an acknowledgement from the broker.
|
||||
@@ -228,17 +260,17 @@
|
||||
# ## guarantee that no messages will be lost as long as at least one in
|
||||
# ## sync replica remains.
|
||||
# required_acks = -1
|
||||
#
|
||||
#
|
||||
# ## The total number of times to retry sending a message
|
||||
# max_retry = 3
|
||||
#
|
||||
#
|
||||
# ## Optional SSL Config
|
||||
# # ssl_ca = "/etc/telegraf/ca.pem"
|
||||
# # ssl_cert = "/etc/telegraf/cert.pem"
|
||||
# # ssl_key = "/etc/telegraf/key.pem"
|
||||
# ## Use SSL but skip chain & host verification
|
||||
# # insecure_skip_verify = false
|
||||
#
|
||||
#
|
||||
# ## Data format to output.
|
||||
# ## Each data format has it's own unique set of configuration options, read
|
||||
# ## more about them here:
|
||||
@@ -250,16 +282,22 @@
|
||||
# [[outputs.kinesis]]
|
||||
# ## Amazon REGION of kinesis endpoint.
|
||||
# region = "ap-southeast-2"
|
||||
#
|
||||
#
|
||||
# ## Amazon Credentials
|
||||
# ## Credentials are loaded in the following order
|
||||
# ## 1) explicit credentials from 'access_key' and 'secret_key'
|
||||
# ## 2) environment variables
|
||||
# ## 3) shared credentials file
|
||||
# ## 4) EC2 Instance Profile
|
||||
# ## 1) Assumed credentials via STS if role_arn is specified
|
||||
# ## 2) explicit credentials from 'access_key' and 'secret_key'
|
||||
# ## 3) shared profile from 'profile'
|
||||
# ## 4) environment variables
|
||||
# ## 5) shared credentials file
|
||||
# ## 6) EC2 Instance Profile
|
||||
# #access_key = ""
|
||||
# #secret_key = ""
|
||||
#
|
||||
# #token = ""
|
||||
# #role_arn = ""
|
||||
# #profile = ""
|
||||
# #shared_credential_file = ""
|
||||
#
|
||||
# ## Kinesis StreamName must exist prior to starting telegraf.
|
||||
# streamname = "StreamName"
|
||||
# ## PartitionKey as used for sharding data.
|
||||
@@ -294,23 +332,23 @@
|
||||
# # Configuration for MQTT server to send metrics to
|
||||
# [[outputs.mqtt]]
|
||||
# servers = ["localhost:1883"] # required.
|
||||
#
|
||||
#
|
||||
# ## MQTT outputs send metrics to this topic format
|
||||
# ## "<topic_prefix>/<hostname>/<pluginname>/"
|
||||
# ## ex: prefix/web01.example.com/mem
|
||||
# topic_prefix = "telegraf"
|
||||
#
|
||||
#
|
||||
# ## username and password to connect MQTT server.
|
||||
# # username = "telegraf"
|
||||
# # password = "metricsmetricsmetricsmetrics"
|
||||
#
|
||||
#
|
||||
# ## Optional SSL Config
|
||||
# # ssl_ca = "/etc/telegraf/ca.pem"
|
||||
# # ssl_cert = "/etc/telegraf/cert.pem"
|
||||
# # ssl_key = "/etc/telegraf/key.pem"
|
||||
# ## Use SSL but skip chain & host verification
|
||||
# # insecure_skip_verify = false
|
||||
#
|
||||
#
|
||||
# ## Data format to output.
|
||||
# ## Each data format has it's own unique set of configuration options, read
|
||||
# ## more about them here:
|
||||
@@ -324,7 +362,7 @@
|
||||
# server = "localhost:4150"
|
||||
# ## NSQ topic for producer messages
|
||||
# topic = "telegraf"
|
||||
#
|
||||
#
|
||||
# ## Data format to output.
|
||||
# ## Each data format has it's own unique set of configuration options, read
|
||||
# ## more about them here:
|
||||
@@ -336,14 +374,14 @@
|
||||
# [[outputs.opentsdb]]
|
||||
# ## prefix for metrics keys
|
||||
# prefix = "my.specific.prefix."
|
||||
#
|
||||
#
|
||||
# ## Telnet Mode ##
|
||||
# ## DNS name of the OpenTSDB server in telnet mode
|
||||
# host = "opentsdb.example.com"
|
||||
#
|
||||
#
|
||||
# ## Port of the OpenTSDB server in telnet mode
|
||||
# port = 4242
|
||||
#
|
||||
#
|
||||
# ## Debug true - Prints OpenTSDB communication
|
||||
# debug = false
|
||||
|
||||
@@ -436,6 +474,7 @@
|
||||
# # Read Apache status information (mod_status)
|
||||
# [[inputs.apache]]
|
||||
# ## An array of Apache status URI to gather stats.
|
||||
# ## Default is "http://localhost/server-status?auto".
|
||||
# urls = ["http://localhost/server-status?auto"]
|
||||
|
||||
|
||||
@@ -444,7 +483,7 @@
|
||||
# ## Bcache sets path
|
||||
# ## If not specified, then default is:
|
||||
# bcachePath = "/sys/fs/bcache"
|
||||
#
|
||||
#
|
||||
# ## By default, telegraf gather stats for all bcache devices
|
||||
# ## Setting devices will restrict the stats to the specified
|
||||
# ## bcache devices.
|
||||
@@ -469,33 +508,61 @@
|
||||
# ]
|
||||
|
||||
|
||||
# # Collects performance metrics from the MON and OSD nodes in a Ceph storage cluster.
|
||||
# [[inputs.ceph]]
|
||||
# ## All configuration values are optional, defaults are shown below
|
||||
#
|
||||
# ## location of ceph binary
|
||||
# ceph_binary = "/usr/bin/ceph"
|
||||
#
|
||||
# ## directory in which to look for socket files
|
||||
# socket_dir = "/var/run/ceph"
|
||||
#
|
||||
# ## prefix of MON and OSD socket files, used to determine socket type
|
||||
# mon_prefix = "ceph-mon"
|
||||
# osd_prefix = "ceph-osd"
|
||||
#
|
||||
# ## suffix used to identify socket files
|
||||
# socket_suffix = "asok"
|
||||
|
||||
|
||||
# # Pull Metric Statistics from Amazon CloudWatch
|
||||
# [[inputs.cloudwatch]]
|
||||
# ## Amazon Region
|
||||
# region = 'us-east-1'
|
||||
#
|
||||
#
|
||||
# ## Amazon Credentials
|
||||
# ## Credentials are loaded in the following order
|
||||
# ## 1) explicit credentials from 'access_key' and 'secret_key'
|
||||
# ## 2) environment variables
|
||||
# ## 3) shared credentials file
|
||||
# ## 4) EC2 Instance Profile
|
||||
# ## 1) Assumed credentials via STS if role_arn is specified
|
||||
# ## 2) explicit credentials from 'access_key' and 'secret_key'
|
||||
# ## 3) shared profile from 'profile'
|
||||
# ## 4) environment variables
|
||||
# ## 5) shared credentials file
|
||||
# ## 6) EC2 Instance Profile
|
||||
# #access_key = ""
|
||||
# #secret_key = ""
|
||||
#
|
||||
# #token = ""
|
||||
# #role_arn = ""
|
||||
# #profile = ""
|
||||
# #shared_credential_file = ""
|
||||
#
|
||||
# ## 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'
|
||||
#
|
||||
#
|
||||
# ## Configure the TTL for the internal cache of metrics.
|
||||
# ## Defaults to 1 hr if not specified
|
||||
# #cache_ttl = '10m'
|
||||
#
|
||||
# ## 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
|
||||
@@ -508,6 +575,23 @@
|
||||
# # value = 'p-example'
|
||||
|
||||
|
||||
# # Gather health check statuses from services registered in Consul
|
||||
# [[inputs.consul]]
|
||||
# ## Most of these values defaults to the one configured on a Consul's agent level.
|
||||
# ## Optional Consul server address (default: "localhost")
|
||||
# # address = "localhost"
|
||||
# ## Optional URI scheme for the Consul server (default: "http")
|
||||
# # scheme = "http"
|
||||
# ## Optional ACL token used in every request (default: "")
|
||||
# # token = ""
|
||||
# ## Optional username used for request HTTP Basic Authentication (default: "")
|
||||
# # username = ""
|
||||
# ## Optional password used for HTTP Basic Authentication (default: "")
|
||||
# # password = ""
|
||||
# ## Optional data centre to query the health checks from (default: "")
|
||||
# # datacentre = ""
|
||||
|
||||
|
||||
# # Read metrics from one or many couchbase clusters
|
||||
# [[inputs.couchbase]]
|
||||
# ## specify servers via a url matching:
|
||||
@@ -542,17 +626,17 @@
|
||||
# [[inputs.dns_query]]
|
||||
# ## servers to query
|
||||
# servers = ["8.8.8.8"] # required
|
||||
#
|
||||
#
|
||||
# ## Domains or subdomains to query. "."(root) is default
|
||||
# domains = ["."] # optional
|
||||
#
|
||||
#
|
||||
# ## Query record type. Default is "A"
|
||||
# ## Posible values: A, AAAA, CNAME, MX, NS, PTR, TXT, SOA, SPF, SRV.
|
||||
# record_type = "A" # optional
|
||||
#
|
||||
#
|
||||
# ## Dns server port. 53 is default
|
||||
# port = 53 # optional
|
||||
#
|
||||
#
|
||||
# ## Query timeout in seconds. Default is 2 seconds
|
||||
# timeout = 2 # optional
|
||||
|
||||
@@ -588,11 +672,11 @@
|
||||
# [[inputs.elasticsearch]]
|
||||
# ## specify a list of one or more Elasticsearch servers
|
||||
# servers = ["http://localhost:9200"]
|
||||
#
|
||||
#
|
||||
# ## set local to false when you want to read the indices stats from all nodes
|
||||
# ## within the cluster
|
||||
# local = true
|
||||
#
|
||||
#
|
||||
# ## set cluster_health to true when you want to also obtain cluster level stats
|
||||
# cluster_health = false
|
||||
|
||||
@@ -600,14 +684,18 @@
|
||||
# # Read metrics from one or more commands that can output to stdout
|
||||
# [[inputs.exec]]
|
||||
# ## Commands array
|
||||
# commands = ["/tmp/test.sh", "/usr/bin/mycollector --foo=bar"]
|
||||
#
|
||||
# commands = [
|
||||
# "/tmp/test.sh",
|
||||
# "/usr/bin/mycollector --foo=bar",
|
||||
# "/tmp/collect_*.sh"
|
||||
# ]
|
||||
#
|
||||
# ## Timeout for each command to complete.
|
||||
# timeout = "5s"
|
||||
#
|
||||
#
|
||||
# ## measurement name suffix (for separating different commands)
|
||||
# name_suffix = "_mycollector"
|
||||
#
|
||||
#
|
||||
# ## Data format to consume.
|
||||
# ## Each data format has it's own unique set of configuration options, read
|
||||
# ## more about them here:
|
||||
@@ -631,11 +719,48 @@
|
||||
# md5 = false
|
||||
|
||||
|
||||
# # Read flattened metrics from one or more GrayLog HTTP endpoints
|
||||
# [[inputs.graylog]]
|
||||
# ## API endpoint, currently supported API:
|
||||
# ##
|
||||
# ## - multiple (Ex http://<host>:12900/system/metrics/multiple)
|
||||
# ## - namespace (Ex http://<host>:12900/system/metrics/namespace/{namespace})
|
||||
# ##
|
||||
# ## For namespace endpoint, the metrics array will be ignored for that call.
|
||||
# ## Endpoint can contain namespace and multiple type calls.
|
||||
# ##
|
||||
# ## Please check http://[graylog-server-ip]:12900/api-browser for full list
|
||||
# ## of endpoints
|
||||
# servers = [
|
||||
# "http://[graylog-server-ip]:12900/system/metrics/multiple",
|
||||
# ]
|
||||
#
|
||||
# ## Metrics list
|
||||
# ## List of metrics can be found on Graylog webservice documentation.
|
||||
# ## Or by hitting the the web service api at:
|
||||
# ## http://[graylog-host]:12900/system/metrics
|
||||
# metrics = [
|
||||
# "jvm.cl.loaded",
|
||||
# "jvm.memory.pools.Metaspace.committed"
|
||||
# ]
|
||||
#
|
||||
# ## Username and password
|
||||
# username = ""
|
||||
# password = ""
|
||||
#
|
||||
# ## Optional SSL Config
|
||||
# # ssl_ca = "/etc/telegraf/ca.pem"
|
||||
# # ssl_cert = "/etc/telegraf/cert.pem"
|
||||
# # ssl_key = "/etc/telegraf/key.pem"
|
||||
# ## Use SSL but skip chain & host verification
|
||||
# # insecure_skip_verify = false
|
||||
|
||||
|
||||
# # Read metrics of haproxy, via socket or csv stats page
|
||||
# [[inputs.haproxy]]
|
||||
# ## An array of address to gather stats about. Specify an ip on hostname
|
||||
# ## with optional port. ie localhost, 10.10.3.33:1936, etc.
|
||||
#
|
||||
#
|
||||
# ## If no servers are specified, then default to 127.0.0.1:1936
|
||||
# servers = ["http://myhaproxy.com:1936", "http://anotherhaproxy.com:1936"]
|
||||
# ## Or you can also use local socket
|
||||
@@ -647,7 +772,7 @@
|
||||
# ## Server address (default http://localhost)
|
||||
# address = "http://github.com"
|
||||
# ## Set response_timeout (default 5 seconds)
|
||||
# response_timeout = 5
|
||||
# response_timeout = "5s"
|
||||
# ## HTTP Request Method
|
||||
# method = "GET"
|
||||
# ## Whether to follow redirects from the server (defaults to false)
|
||||
@@ -659,41 +784,48 @@
|
||||
# # body = '''
|
||||
# # {'fake':'data'}
|
||||
# # '''
|
||||
#
|
||||
# ## Optional SSL Config
|
||||
# # ssl_ca = "/etc/telegraf/ca.pem"
|
||||
# # ssl_cert = "/etc/telegraf/cert.pem"
|
||||
# # ssl_key = "/etc/telegraf/key.pem"
|
||||
# ## Use SSL but skip chain & host verification
|
||||
# # insecure_skip_verify = false
|
||||
|
||||
|
||||
# # Read flattened metrics from one or more JSON HTTP endpoints
|
||||
# [[inputs.httpjson]]
|
||||
# ## NOTE This plugin only reads numerical measurements, strings and booleans
|
||||
# ## will be ignored.
|
||||
#
|
||||
#
|
||||
# ## a name for the service being polled
|
||||
# name = "webserver_stats"
|
||||
#
|
||||
#
|
||||
# ## URL of each server in the service's cluster
|
||||
# servers = [
|
||||
# "http://localhost:9999/stats/",
|
||||
# "http://localhost:9998/stats/",
|
||||
# ]
|
||||
#
|
||||
#
|
||||
# ## HTTP method to use: GET or POST (case-sensitive)
|
||||
# method = "GET"
|
||||
#
|
||||
#
|
||||
# ## List of tag names to extract from top-level of JSON server response
|
||||
# # tag_keys = [
|
||||
# # "my_tag_1",
|
||||
# # "my_tag_2"
|
||||
# # ]
|
||||
#
|
||||
#
|
||||
# ## HTTP parameters (all values must be strings)
|
||||
# [inputs.httpjson.parameters]
|
||||
# event_type = "cpu_spike"
|
||||
# threshold = "0.75"
|
||||
#
|
||||
#
|
||||
# ## HTTP Header parameters (all values must be strings)
|
||||
# # [inputs.httpjson.headers]
|
||||
# # X-Auth-Token = "my-xauth-token"
|
||||
# # apiVersion = "v1"
|
||||
#
|
||||
#
|
||||
# ## Optional SSL Config
|
||||
# # ssl_ca = "/etc/telegraf/ca.pem"
|
||||
# # ssl_cert = "/etc/telegraf/cert.pem"
|
||||
@@ -707,8 +839,9 @@
|
||||
# ## 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
|
||||
# ## Default is "http://localhost:8086/debug/vars".
|
||||
# urls = [
|
||||
# "http://localhost:8086/debug/vars"
|
||||
# ]
|
||||
@@ -728,7 +861,7 @@
|
||||
# [[inputs.jolokia]]
|
||||
# ## This is the context root used to compose the jolokia url
|
||||
# context = "/jolokia"
|
||||
#
|
||||
#
|
||||
# ## This specifies the mode used
|
||||
# # mode = "proxy"
|
||||
# #
|
||||
@@ -738,8 +871,8 @@
|
||||
# # [inputs.jolokia.proxy]
|
||||
# # host = "127.0.0.1"
|
||||
# # port = "8080"
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
# ## List of servers exposing jolokia read service
|
||||
# [[inputs.jolokia.servers]]
|
||||
# name = "as-server-01"
|
||||
@@ -747,7 +880,7 @@
|
||||
# port = "8080"
|
||||
# # 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.
|
||||
@@ -756,13 +889,13 @@
|
||||
# name = "heap_memory_usage"
|
||||
# mbean = "java.lang:type=Memory"
|
||||
# attribute = "HeapMemoryUsage"
|
||||
#
|
||||
#
|
||||
# ## This collect thread counts metrics.
|
||||
# [[inputs.jolokia.metrics]]
|
||||
# name = "thread_count"
|
||||
# mbean = "java.lang:type=Threading"
|
||||
# attribute = "TotalStartedThreadCount,ThreadCount,DaemonThreadCount,PeakThreadCount"
|
||||
#
|
||||
#
|
||||
# ## This collect number of class loaded/unloaded counts metrics.
|
||||
# [[inputs.jolokia.metrics]]
|
||||
# name = "class_count"
|
||||
@@ -848,8 +981,8 @@
|
||||
# ## [username[:password]@][protocol[(address)]]/[?tls=[true|false|skip-verify]]
|
||||
# ## see https://github.com/go-sql-driver/mysql#dsn-data-source-name
|
||||
# ## e.g.
|
||||
# ## root:passwd@tcp(127.0.0.1:3306)/?tls=false
|
||||
# ## root@tcp(127.0.0.1:3306)/?tls=false
|
||||
# ## db_user:passwd@tcp(127.0.0.1:3306)/?tls=false
|
||||
# ## db_user@tcp(127.0.0.1:3306)/?tls=false
|
||||
# #
|
||||
# ## If no servers are specified, then localhost is used as the host.
|
||||
# servers = ["tcp(127.0.0.1:3306)/"]
|
||||
@@ -913,14 +1046,15 @@
|
||||
# protocol = "tcp"
|
||||
# ## Server address (default localhost)
|
||||
# address = "github.com:80"
|
||||
# ## Set timeout (default 1.0 seconds)
|
||||
# timeout = 1.0
|
||||
# ## Set read timeout (default 1.0 seconds)
|
||||
# read_timeout = 1.0
|
||||
# ## Set timeout
|
||||
# timeout = "1s"
|
||||
#
|
||||
# ## Optional string sent to the server
|
||||
# # send = "ssh"
|
||||
# ## Optional expected string in answer
|
||||
# # expect = "ssh"
|
||||
# ## Set read timeout (only used if expecting a response)
|
||||
# read_timeout = "1s"
|
||||
|
||||
|
||||
# # Read TCP metrics such as established, time wait and sockets counts.
|
||||
@@ -940,6 +1074,18 @@
|
||||
# endpoints = ["http://localhost:4151"]
|
||||
|
||||
|
||||
# # Collect kernel snmp counters and network interface statistics
|
||||
# [[inputs.nstat]]
|
||||
# ## file paths for proc files. If empty default paths will be used:
|
||||
# ## /proc/net/netstat, /proc/net/snmp, /proc/net/snmp6
|
||||
# ## These can also be overridden with env variables, see README.
|
||||
# proc_net_netstat = ""
|
||||
# proc_net_snmp = ""
|
||||
# proc_net_snmp6 = ""
|
||||
# ## dump metrics with 0 values too
|
||||
# dump_zeros = true
|
||||
|
||||
|
||||
# # Get standard NTP query metrics, requires ntpq executable.
|
||||
# [[inputs.ntpq]]
|
||||
# ## If false, set the -n ntpq flag. Can reduce metric gather time.
|
||||
@@ -994,7 +1140,7 @@
|
||||
# count = 1 # required
|
||||
# ## interval, in s, at which to ping. 0 == default (ping -i <PING_INTERVAL>)
|
||||
# ping_interval = 0.0
|
||||
# ## ping timeout, in s. 0 == no timeout (ping -W <TIMEOUT>)
|
||||
# ## per-ping timeout, in s. 0 == no timeout (ping -W <TIMEOUT>)
|
||||
# timeout = 1.0
|
||||
# ## interface to send ping from (ping -I <INTERFACE>)
|
||||
# interface = ""
|
||||
@@ -1016,7 +1162,7 @@
|
||||
# ## to grab metrics for.
|
||||
# ##
|
||||
# address = "host=localhost user=postgres sslmode=disable"
|
||||
#
|
||||
#
|
||||
# ## A list of databases to pull metrics about. If not specified, metrics for all
|
||||
# ## databases are gathered.
|
||||
# # databases = ["app_production", "testing"]
|
||||
@@ -1098,7 +1244,10 @@
|
||||
# # pattern = "nginx"
|
||||
# ## user as argument for pgrep (ie, pgrep -u <user>)
|
||||
# # user = "nginx"
|
||||
#
|
||||
#
|
||||
# ## override for process_name
|
||||
# ## This is optional; default is sourced from /proc/<pid>/status
|
||||
# # process_name = "bar"
|
||||
# ## Field name prefix
|
||||
# prefix = ""
|
||||
# ## comment this out if you want raw cpu_time stats
|
||||
@@ -1109,7 +1258,7 @@
|
||||
# [[inputs.prometheus]]
|
||||
# ## An array of urls to scrape metrics from.
|
||||
# urls = ["http://localhost:9100/metrics"]
|
||||
#
|
||||
#
|
||||
# ## Use SSL but skip chain & host verification
|
||||
# # insecure_skip_verify = false
|
||||
# ## Use bearer token for authorization
|
||||
@@ -1124,11 +1273,11 @@
|
||||
|
||||
# # Read metrics from one or many RabbitMQ servers via the management API
|
||||
# [[inputs.rabbitmq]]
|
||||
# url = "http://localhost:15672" # required
|
||||
# # url = "http://localhost:15672"
|
||||
# # name = "rmq-server-1" # optional tag
|
||||
# # username = "guest"
|
||||
# # password = "guest"
|
||||
#
|
||||
#
|
||||
# ## A list of nodes to pull metrics about. If not specified, metrics for
|
||||
# ## all nodes are gathered.
|
||||
# # nodes = ["rabbit@node1", "rabbit@node2"]
|
||||
@@ -1192,7 +1341,7 @@
|
||||
# collect = ["mybulk", "sysservices", "sysdescr"]
|
||||
# # Simple list of OIDs to get, in addition to "collect"
|
||||
# get_oids = []
|
||||
#
|
||||
#
|
||||
# [[inputs.snmp.host]]
|
||||
# address = "192.168.2.3:161"
|
||||
# community = "public"
|
||||
@@ -1204,31 +1353,31 @@
|
||||
# "ifNumber",
|
||||
# ".1.3.6.1.2.1.1.3.0",
|
||||
# ]
|
||||
#
|
||||
#
|
||||
# [[inputs.snmp.get]]
|
||||
# name = "ifnumber"
|
||||
# oid = "ifNumber"
|
||||
#
|
||||
#
|
||||
# [[inputs.snmp.get]]
|
||||
# name = "interface_speed"
|
||||
# oid = "ifSpeed"
|
||||
# instance = "0"
|
||||
#
|
||||
#
|
||||
# [[inputs.snmp.get]]
|
||||
# name = "sysuptime"
|
||||
# oid = ".1.3.6.1.2.1.1.3.0"
|
||||
# unit = "second"
|
||||
#
|
||||
#
|
||||
# [[inputs.snmp.bulk]]
|
||||
# name = "mybulk"
|
||||
# max_repetition = 127
|
||||
# oid = ".1.3.6.1.2.1.1"
|
||||
#
|
||||
#
|
||||
# [[inputs.snmp.bulk]]
|
||||
# name = "ifoutoctets"
|
||||
# max_repetition = 127
|
||||
# oid = "ifOutOctets"
|
||||
#
|
||||
#
|
||||
# [[inputs.snmp.host]]
|
||||
# address = "192.168.2.13:161"
|
||||
# #address = "127.0.0.1:161"
|
||||
@@ -1241,19 +1390,19 @@
|
||||
# [[inputs.snmp.host.table]]
|
||||
# name = "iftable3"
|
||||
# include_instances = ["enp5s0", "eth1"]
|
||||
#
|
||||
#
|
||||
# # SNMP TABLEs
|
||||
# # table without mapping neither subtables
|
||||
# [[inputs.snmp.table]]
|
||||
# name = "iftable1"
|
||||
# oid = ".1.3.6.1.2.1.31.1.1.1"
|
||||
#
|
||||
#
|
||||
# # table without mapping but with subtables
|
||||
# [[inputs.snmp.table]]
|
||||
# name = "iftable2"
|
||||
# oid = ".1.3.6.1.2.1.31.1.1.1"
|
||||
# sub_tables = [".1.3.6.1.2.1.2.2.1.13"]
|
||||
#
|
||||
#
|
||||
# # table with mapping but without subtables
|
||||
# [[inputs.snmp.table]]
|
||||
# name = "iftable3"
|
||||
@@ -1261,7 +1410,7 @@
|
||||
# # if empty. get all instances
|
||||
# mapping_table = ".1.3.6.1.2.1.31.1.1.1.1"
|
||||
# # if empty, get all subtables
|
||||
#
|
||||
#
|
||||
# # table with both mapping and subtables
|
||||
# [[inputs.snmp.table]]
|
||||
# name = "iftable4"
|
||||
@@ -1300,25 +1449,37 @@
|
||||
# pools = ["redis_pool", "mc_pool"]
|
||||
|
||||
|
||||
# # Read metrics of ZFS from arcstats, zfetchstats and vdev_cache_stats
|
||||
# # A plugin to collect stats from Varnish HTTP Cache
|
||||
# [[inputs.varnish]]
|
||||
# ## The default location of the varnishstat binary can be overridden with:
|
||||
# binary = "/usr/bin/varnishstat"
|
||||
#
|
||||
# ## By default, telegraf gather stats for 3 metric points.
|
||||
# ## Setting stats will override the defaults shown below.
|
||||
# ## Glob matching can be used, ie, stats = ["MAIN.*"]
|
||||
# ## stats may also be set to ["*"], which will collect all stats
|
||||
# stats = ["MAIN.cache_hit", "MAIN.cache_miss", "MAIN.uptime"]
|
||||
|
||||
|
||||
# # Read metrics of ZFS from arcstats, zfetchstats, vdev_cache_stats, and pools
|
||||
# [[inputs.zfs]]
|
||||
# ## ZFS kstat path
|
||||
# ## ZFS kstat path. Ignored on FreeBSD
|
||||
# ## If not specified, then default is:
|
||||
# kstatPath = "/proc/spl/kstat/zfs"
|
||||
#
|
||||
# # kstatPath = "/proc/spl/kstat/zfs"
|
||||
#
|
||||
# ## By default, telegraf gather all zfs stats
|
||||
# ## If not specified, then default is:
|
||||
# kstatMetrics = ["arcstats", "zfetchstats", "vdev_cache_stats"]
|
||||
#
|
||||
# # kstatMetrics = ["arcstats", "zfetchstats", "vdev_cache_stats"]
|
||||
#
|
||||
# ## By default, don't gather zpool stats
|
||||
# poolMetrics = false
|
||||
# # poolMetrics = false
|
||||
|
||||
|
||||
# # Reads 'mntr' stats from one or many zookeeper servers
|
||||
# [[inputs.zookeeper]]
|
||||
# ## An array of address to gather stats about. Specify an ip or hostname
|
||||
# ## with port. ie localhost:2181, 10.0.0.1:2181, etc.
|
||||
#
|
||||
#
|
||||
# ## If no servers are specified, then localhost is used as the host.
|
||||
# ## If no port is specified, 2181 is used
|
||||
# servers = [":2181"]
|
||||
@@ -1342,12 +1503,12 @@
|
||||
# ## an array of Zookeeper connection strings
|
||||
# zookeeper_peers = ["localhost:2181"]
|
||||
# ## Zookeeper Chroot
|
||||
# zookeeper_chroot = "/"
|
||||
# zookeeper_chroot = ""
|
||||
# ## the name of the consumer group
|
||||
# consumer_group = "telegraf_metrics_consumers"
|
||||
# ## Offset (must be either "oldest" or "newest")
|
||||
# offset = "oldest"
|
||||
#
|
||||
#
|
||||
# ## Data format to consume.
|
||||
# ## Each data format has it's own unique set of configuration options, read
|
||||
# ## more about them here:
|
||||
@@ -1355,37 +1516,66 @@
|
||||
# data_format = "influx"
|
||||
|
||||
|
||||
# # Stream and parse log file(s).
|
||||
# [[inputs.logparser]]
|
||||
# ## Log files to parse.
|
||||
# ## These accept standard unix glob matching rules, but with the addition of
|
||||
# ## ** as a "super asterisk". ie:
|
||||
# ## /var/log/**.log -> recursively find all .log files in /var/log
|
||||
# ## /var/log/*/*.log -> find all .log files with a parent dir in /var/log
|
||||
# ## /var/log/apache.log -> only tail the apache log file
|
||||
# files = ["/var/log/influxdb/influxdb.log"]
|
||||
# ## Read file from beginning.
|
||||
# from_beginning = false
|
||||
#
|
||||
# ## Parse logstash-style "grok" patterns:
|
||||
# ## Telegraf built-in parsing patterns: https://goo.gl/dkay10
|
||||
# [inputs.logparser.grok]
|
||||
# ## This is a list of patterns to check the given log file(s) for.
|
||||
# ## Note that adding patterns here increases processing time. The most
|
||||
# ## efficient configuration is to have one pattern per logparser.
|
||||
# ## Other common built-in patterns are:
|
||||
# ## %{COMMON_LOG_FORMAT} (plain apache & nginx access logs)
|
||||
# ## %{COMBINED_LOG_FORMAT} (access logs + referrer & agent)
|
||||
# patterns = ["%{INFLUXDB_HTTPD_LOG}"]
|
||||
# ## Full path(s) to custom pattern files.
|
||||
# custom_pattern_files = []
|
||||
# ## Custom patterns can also be defined here. Put one pattern per line.
|
||||
# custom_patterns = '''
|
||||
# '''
|
||||
|
||||
|
||||
# # Read metrics from MQTT topic(s)
|
||||
# [[inputs.mqtt_consumer]]
|
||||
# servers = ["localhost:1883"]
|
||||
# ## MQTT QoS, must be 0, 1, or 2
|
||||
# qos = 0
|
||||
#
|
||||
#
|
||||
# ## Topics to subscribe to
|
||||
# topics = [
|
||||
# "telegraf/host01/cpu",
|
||||
# "telegraf/+/mem",
|
||||
# "sensors/#",
|
||||
# ]
|
||||
#
|
||||
#
|
||||
# # if true, messages that can't be delivered while the subscriber is offline
|
||||
# # will be delivered when it comes back (such as on service restart).
|
||||
# # NOTE: if true, client_id MUST be set
|
||||
# persistent_session = false
|
||||
# # If empty, a random client ID will be generated.
|
||||
# client_id = ""
|
||||
#
|
||||
#
|
||||
# ## username and password to connect MQTT server.
|
||||
# # username = "telegraf"
|
||||
# # password = "metricsmetricsmetricsmetrics"
|
||||
#
|
||||
#
|
||||
# ## Optional SSL Config
|
||||
# # ssl_ca = "/etc/telegraf/ca.pem"
|
||||
# # ssl_cert = "/etc/telegraf/cert.pem"
|
||||
# # ssl_key = "/etc/telegraf/key.pem"
|
||||
# ## Use SSL but skip chain & host verification
|
||||
# # insecure_skip_verify = false
|
||||
#
|
||||
#
|
||||
# ## Data format to consume.
|
||||
# ## Each data format has it's own unique set of configuration options, read
|
||||
# ## more about them here:
|
||||
@@ -1403,7 +1593,7 @@
|
||||
# subjects = ["telegraf"]
|
||||
# ## name a queue group
|
||||
# queue_group = "telegraf_consumers"
|
||||
#
|
||||
#
|
||||
# ## Data format to consume.
|
||||
# ## Each data format has it's own unique set of configuration options, read
|
||||
# ## more about them here:
|
||||
@@ -1411,6 +1601,12 @@
|
||||
# data_format = "influx"
|
||||
|
||||
|
||||
# # A Rollbar Webhook Event collector
|
||||
# [[inputs.rollbar_webhooks]]
|
||||
# ## Address and port to host Webhook listener on
|
||||
# service_address = ":1619"
|
||||
|
||||
|
||||
# # Statsd Server
|
||||
# [[inputs.statsd]]
|
||||
# ## Address and port to host UDP listener on
|
||||
@@ -1425,24 +1621,24 @@
|
||||
# delete_timings = true
|
||||
# ## Percentiles to calculate for timing & histogram stats
|
||||
# percentiles = [90]
|
||||
#
|
||||
#
|
||||
# ## separator to use between elements of a statsd metric
|
||||
# metric_separator = "_"
|
||||
#
|
||||
#
|
||||
# ## Parses tags in the datadog statsd format
|
||||
# ## http://docs.datadoghq.com/guides/dogstatsd/
|
||||
# parse_data_dog_tags = false
|
||||
#
|
||||
#
|
||||
# ## Statsd data translation templates, more info can be read here:
|
||||
# ## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md#graphite
|
||||
# # templates = [
|
||||
# # "cpu.* measurement*"
|
||||
# # ]
|
||||
#
|
||||
#
|
||||
# ## Number of UDP messages allowed to queue up, once filled,
|
||||
# ## the statsd server will start dropping packets
|
||||
# allowed_pending_messages = 10000
|
||||
#
|
||||
#
|
||||
# ## Number of timing/histogram values to track per-measurement in the
|
||||
# ## calculation of percentiles. Raising this limit increases the accuracy
|
||||
# ## of percentiles but also increases the memory usage and cpu time.
|
||||
@@ -1463,7 +1659,7 @@
|
||||
# files = ["/var/mymetrics.out"]
|
||||
# ## Read file from beginning.
|
||||
# from_beginning = false
|
||||
#
|
||||
#
|
||||
# ## Data format to consume.
|
||||
# ## Each data format has it's own unique set of configuration options, read
|
||||
# ## more about them here:
|
||||
@@ -1475,14 +1671,14 @@
|
||||
# [[inputs.tcp_listener]]
|
||||
# ## Address and port to host TCP listener on
|
||||
# service_address = ":8094"
|
||||
#
|
||||
#
|
||||
# ## Number of TCP messages allowed to queue up. Once filled, the
|
||||
# ## TCP listener will start dropping packets.
|
||||
# allowed_pending_messages = 10000
|
||||
#
|
||||
#
|
||||
# ## Maximum number of concurrent TCP connections to allow
|
||||
# max_tcp_connections = 250
|
||||
#
|
||||
#
|
||||
# ## Data format to consume.
|
||||
# ## Each data format has it's own unique set of configuration options, read
|
||||
# ## more about them here:
|
||||
@@ -1494,11 +1690,11 @@
|
||||
# [[inputs.udp_listener]]
|
||||
# ## Address and port to host UDP listener on
|
||||
# service_address = ":8092"
|
||||
#
|
||||
#
|
||||
# ## Number of UDP messages allowed to queue up. Once filled, the
|
||||
# ## UDP listener will start dropping packets.
|
||||
# allowed_pending_messages = 10000
|
||||
#
|
||||
#
|
||||
# ## Data format to consume.
|
||||
# ## Each data format has it's own unique set of configuration options, read
|
||||
# ## more about them here:
|
||||
|
||||
79
filter/filter.go
Normal file
79
filter/filter.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package filter
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
)
|
||||
|
||||
type Filter interface {
|
||||
Match(string) bool
|
||||
}
|
||||
|
||||
// CompileFilter takes a list of string filters and returns a Filter interface
|
||||
// for matching a given string against the filter list. The filter list
|
||||
// supports glob matching too, ie:
|
||||
//
|
||||
// f, _ := CompileFilter([]string{"cpu", "mem", "net*"})
|
||||
// f.Match("cpu") // true
|
||||
// f.Match("network") // true
|
||||
// f.Match("memory") // false
|
||||
//
|
||||
func CompileFilter(filters []string) (Filter, error) {
|
||||
// return if there is nothing to compile
|
||||
if len(filters) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// check if we can compile a non-glob filter
|
||||
noGlob := true
|
||||
for _, filter := range filters {
|
||||
if hasMeta(filter) {
|
||||
noGlob = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case noGlob:
|
||||
// return non-globbing filter if not needed.
|
||||
return compileFilterNoGlob(filters), nil
|
||||
case len(filters) == 1:
|
||||
return glob.Compile(filters[0])
|
||||
default:
|
||||
return glob.Compile("{" + strings.Join(filters, ",") + "}")
|
||||
}
|
||||
}
|
||||
|
||||
// hasMeta reports whether path contains any magic glob characters.
|
||||
func hasMeta(s string) bool {
|
||||
return strings.IndexAny(s, "*?[") >= 0
|
||||
}
|
||||
|
||||
type filter struct {
|
||||
m map[string]struct{}
|
||||
}
|
||||
|
||||
func (f *filter) Match(s string) bool {
|
||||
_, ok := f.m[s]
|
||||
return ok
|
||||
}
|
||||
|
||||
type filtersingle struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (f *filtersingle) Match(s string) bool {
|
||||
return f.s == s
|
||||
}
|
||||
|
||||
func compileFilterNoGlob(filters []string) Filter {
|
||||
if len(filters) == 1 {
|
||||
return &filtersingle{s: filters[0]}
|
||||
}
|
||||
out := filter{m: make(map[string]struct{})}
|
||||
for _, filter := range filters {
|
||||
out.m[filter] = struct{}{}
|
||||
}
|
||||
return &out
|
||||
}
|
||||
96
filter/filter_test.go
Normal file
96
filter/filter_test.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package filter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCompileFilter(t *testing.T) {
|
||||
f, err := CompileFilter([]string{})
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, f)
|
||||
|
||||
f, err = CompileFilter([]string{"cpu"})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, f.Match("cpu"))
|
||||
assert.False(t, f.Match("cpu0"))
|
||||
assert.False(t, f.Match("mem"))
|
||||
|
||||
f, err = CompileFilter([]string{"cpu*"})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, f.Match("cpu"))
|
||||
assert.True(t, f.Match("cpu0"))
|
||||
assert.False(t, f.Match("mem"))
|
||||
|
||||
f, err = CompileFilter([]string{"cpu", "mem"})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, f.Match("cpu"))
|
||||
assert.False(t, f.Match("cpu0"))
|
||||
assert.True(t, f.Match("mem"))
|
||||
|
||||
f, err = CompileFilter([]string{"cpu", "mem", "net*"})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, f.Match("cpu"))
|
||||
assert.False(t, f.Match("cpu0"))
|
||||
assert.True(t, f.Match("mem"))
|
||||
assert.True(t, f.Match("network"))
|
||||
}
|
||||
|
||||
var benchbool bool
|
||||
|
||||
func BenchmarkFilterSingleNoGlobFalse(b *testing.B) {
|
||||
f, _ := CompileFilter([]string{"cpu"})
|
||||
var tmp bool
|
||||
for n := 0; n < b.N; n++ {
|
||||
tmp = f.Match("network")
|
||||
}
|
||||
benchbool = tmp
|
||||
}
|
||||
|
||||
func BenchmarkFilterSingleNoGlobTrue(b *testing.B) {
|
||||
f, _ := CompileFilter([]string{"cpu"})
|
||||
var tmp bool
|
||||
for n := 0; n < b.N; n++ {
|
||||
tmp = f.Match("cpu")
|
||||
}
|
||||
benchbool = tmp
|
||||
}
|
||||
|
||||
func BenchmarkFilter(b *testing.B) {
|
||||
f, _ := CompileFilter([]string{"cpu", "mem", "net*"})
|
||||
var tmp bool
|
||||
for n := 0; n < b.N; n++ {
|
||||
tmp = f.Match("network")
|
||||
}
|
||||
benchbool = tmp
|
||||
}
|
||||
|
||||
func BenchmarkFilterNoGlob(b *testing.B) {
|
||||
f, _ := CompileFilter([]string{"cpu", "mem", "net"})
|
||||
var tmp bool
|
||||
for n := 0; n < b.N; n++ {
|
||||
tmp = f.Match("net")
|
||||
}
|
||||
benchbool = tmp
|
||||
}
|
||||
|
||||
func BenchmarkFilter2(b *testing.B) {
|
||||
f, _ := CompileFilter([]string{"aa", "bb", "c", "ad", "ar", "at", "aq",
|
||||
"aw", "az", "axxx", "ab", "cpu", "mem", "net*"})
|
||||
var tmp bool
|
||||
for n := 0; n < b.N; n++ {
|
||||
tmp = f.Match("network")
|
||||
}
|
||||
benchbool = tmp
|
||||
}
|
||||
|
||||
func BenchmarkFilter2NoGlob(b *testing.B) {
|
||||
f, _ := CompileFilter([]string{"aa", "bb", "c", "ad", "ar", "at", "aq",
|
||||
"aw", "az", "axxx", "ab", "cpu", "mem", "net"})
|
||||
var tmp bool
|
||||
for n := 0; n < b.N; n++ {
|
||||
tmp = f.Match("net")
|
||||
}
|
||||
benchbool = tmp
|
||||
}
|
||||
49
internal/config/aws/credentials.go
Normal file
49
internal/config/aws/credentials.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/client"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
)
|
||||
|
||||
type CredentialConfig struct {
|
||||
Region string
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
RoleARN string
|
||||
Profile string
|
||||
Filename string
|
||||
Token string
|
||||
}
|
||||
|
||||
func (c *CredentialConfig) Credentials() client.ConfigProvider {
|
||||
if c.RoleARN != "" {
|
||||
return c.assumeCredentials()
|
||||
} else {
|
||||
return c.rootCredentials()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CredentialConfig) rootCredentials() client.ConfigProvider {
|
||||
config := &aws.Config{
|
||||
Region: aws.String(c.Region),
|
||||
}
|
||||
if c.AccessKey != "" || c.SecretKey != "" {
|
||||
config.Credentials = credentials.NewStaticCredentials(c.AccessKey, c.SecretKey, c.Token)
|
||||
} else if c.Profile != "" || c.Filename != "" {
|
||||
config.Credentials = credentials.NewSharedCredentials(c.Filename, c.Profile)
|
||||
}
|
||||
|
||||
return session.New(config)
|
||||
}
|
||||
|
||||
func (c *CredentialConfig) assumeCredentials() client.ConfigProvider {
|
||||
rootCredentials := c.rootCredentials()
|
||||
config := &aws.Config{
|
||||
Region: aws.String(c.Region),
|
||||
}
|
||||
config.Credentials = stscreds.NewCredentials(rootCredentials, c.RoleARN)
|
||||
return session.New(config)
|
||||
}
|
||||
@@ -58,7 +58,6 @@ func NewConfig() *Config {
|
||||
Interval: internal.Duration{Duration: 10 * time.Second},
|
||||
RoundInterval: true,
|
||||
FlushInterval: internal.Duration{Duration: 10 * time.Second},
|
||||
FlushJitter: internal.Duration{Duration: 5 * time.Second},
|
||||
},
|
||||
|
||||
Tags: make(map[string]string),
|
||||
@@ -78,6 +77,14 @@ type AgentConfig struct {
|
||||
// ie, if Interval=10s then always collect on :00, :10, :20, etc.
|
||||
RoundInterval bool
|
||||
|
||||
// By default, precision will be set to the same timestamp order as the
|
||||
// collection interval, with the maximum being 1s.
|
||||
// ie, when interval = "10s", precision will be "1s"
|
||||
// when interval = "250ms", precision will be "1ms"
|
||||
// Precision will NOT be used for service inputs. It is up to each individual
|
||||
// service input to set the timestamp at the appropriate precision.
|
||||
Precision internal.Duration
|
||||
|
||||
// CollectionJitter is used to jitter the collection by a random amount.
|
||||
// Each plugin will sleep for a random time within jitter before collecting.
|
||||
// This can be used to avoid many plugins querying things like sysfs at the
|
||||
@@ -109,11 +116,10 @@ type AgentConfig struct {
|
||||
// does _not_ deactivate FlushInterval.
|
||||
FlushBufferWhenFull bool
|
||||
|
||||
// TODO(cam): Remove UTC and Precision parameters, they are no longer
|
||||
// TODO(cam): Remove UTC and parameter, they are no longer
|
||||
// valid for the agent config. Leaving them here for now for backwards-
|
||||
// compatability
|
||||
UTC bool `toml:"utc"`
|
||||
Precision string
|
||||
UTC bool `toml:"utc"`
|
||||
|
||||
// Debug is the option for running in debug mode
|
||||
Debug bool
|
||||
@@ -210,6 +216,11 @@ var header = `# Telegraf Configuration
|
||||
## ie, a jitter of 5s and interval 10s means flushes will happen every 10-15s
|
||||
flush_jitter = "0s"
|
||||
|
||||
## By default, precision will be set to the same timestamp order as the
|
||||
## collection interval, with the maximum being 1s.
|
||||
## Precision will NOT be used for service inputs, such as logparser and statsd.
|
||||
## Valid values are "Nns", "Nus" (or "Nµs"), "Nms", "Ns".
|
||||
precision = ""
|
||||
## Run telegraf in debug mode
|
||||
debug = false
|
||||
## Run telegraf in quiet mode
|
||||
@@ -357,7 +368,7 @@ func printConfig(name string, p printer, op string, commented bool) {
|
||||
fmt.Print("\n")
|
||||
continue
|
||||
}
|
||||
fmt.Print(comment + line + "\n")
|
||||
fmt.Print(strings.TrimRight(comment+line, " ") + "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
37
internal/errchan/errchan.go
Normal file
37
internal/errchan/errchan.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package errchan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ErrChan struct {
|
||||
C chan error
|
||||
}
|
||||
|
||||
// New returns an error channel of max length 'n'
|
||||
// errors can be sent to the ErrChan.C channel, and will be returned when
|
||||
// ErrChan.Error() is called.
|
||||
func New(n int) *ErrChan {
|
||||
return &ErrChan{
|
||||
C: make(chan error, n),
|
||||
}
|
||||
}
|
||||
|
||||
// Error closes the ErrChan.C channel and returns an error if there are any
|
||||
// non-nil errors, otherwise returns nil.
|
||||
func (e *ErrChan) Error() error {
|
||||
close(e.C)
|
||||
|
||||
var out string
|
||||
for err := range e.C {
|
||||
if err != nil {
|
||||
out += "[" + err.Error() + "], "
|
||||
}
|
||||
}
|
||||
|
||||
if out != "" {
|
||||
return fmt.Errorf("Errors encountered: " + strings.TrimRight(out, ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -10,8 +10,10 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/big"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
@@ -32,12 +34,25 @@ type Duration struct {
|
||||
|
||||
// UnmarshalTOML parses the duration from the TOML config file
|
||||
func (d *Duration) UnmarshalTOML(b []byte) error {
|
||||
dur, err := time.ParseDuration(string(b[1 : len(b)-1]))
|
||||
if err != nil {
|
||||
return err
|
||||
var err error
|
||||
// Parse string duration, ie, "1s"
|
||||
d.Duration, err = time.ParseDuration(string(b[1 : len(b)-1]))
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
d.Duration = dur
|
||||
// First try parsing as integer seconds
|
||||
sI, err := strconv.ParseInt(string(b), 10, 64)
|
||||
if err == nil {
|
||||
d.Duration = time.Second * time.Duration(sI)
|
||||
return nil
|
||||
}
|
||||
// Second try parsing as float seconds
|
||||
sF, err := strconv.ParseFloat(string(b), 64)
|
||||
if err == nil {
|
||||
d.Duration = time.Second * time.Duration(sF)
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -191,3 +206,27 @@ func WaitTimeout(c *exec.Cmd, timeout time.Duration) error {
|
||||
return TimeoutErr
|
||||
}
|
||||
}
|
||||
|
||||
// RandomSleep will sleep for a random amount of time up to max.
|
||||
// If the shutdown channel is closed, it will return before it has finished
|
||||
// sleeping.
|
||||
func RandomSleep(max time.Duration, shutdown chan struct{}) {
|
||||
if max == 0 {
|
||||
return
|
||||
}
|
||||
maxSleep := big.NewInt(max.Nanoseconds())
|
||||
|
||||
var sleepns int64
|
||||
if j, err := rand.Int(rand.Reader, maxSleep); err == nil {
|
||||
sleepns = j.Int64()
|
||||
}
|
||||
|
||||
t := time.NewTimer(time.Nanosecond * time.Duration(sleepns))
|
||||
select {
|
||||
case <-t.C:
|
||||
return
|
||||
case <-shutdown:
|
||||
t.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,3 +106,28 @@ func TestRunError(t *testing.T) {
|
||||
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRandomSleep(t *testing.T) {
|
||||
// test that zero max returns immediately
|
||||
s := time.Now()
|
||||
RandomSleep(time.Duration(0), make(chan struct{}))
|
||||
elapsed := time.Since(s)
|
||||
assert.True(t, elapsed < time.Millisecond)
|
||||
|
||||
// test that max sleep is respected
|
||||
s = time.Now()
|
||||
RandomSleep(time.Millisecond*50, make(chan struct{}))
|
||||
elapsed = time.Since(s)
|
||||
assert.True(t, elapsed < time.Millisecond*50)
|
||||
|
||||
// test that shutdown is respected
|
||||
s = time.Now()
|
||||
shutdown := make(chan struct{})
|
||||
go func() {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
close(shutdown)
|
||||
}()
|
||||
RandomSleep(time.Second, shutdown)
|
||||
elapsed = time.Since(s)
|
||||
assert.True(t, elapsed < time.Millisecond*150)
|
||||
}
|
||||
|
||||
59
internal/limiter/limiter.go
Normal file
59
internal/limiter/limiter.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NewRateLimiter returns a rate limiter that will will emit from the C
|
||||
// channel only 'n' times every 'rate' seconds.
|
||||
func NewRateLimiter(n int, rate time.Duration) *rateLimiter {
|
||||
r := &rateLimiter{
|
||||
C: make(chan bool),
|
||||
rate: rate,
|
||||
n: n,
|
||||
shutdown: make(chan bool),
|
||||
}
|
||||
r.wg.Add(1)
|
||||
go r.limiter()
|
||||
return r
|
||||
}
|
||||
|
||||
type rateLimiter struct {
|
||||
C chan bool
|
||||
rate time.Duration
|
||||
n int
|
||||
|
||||
shutdown chan bool
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
func (r *rateLimiter) Stop() {
|
||||
close(r.shutdown)
|
||||
r.wg.Wait()
|
||||
close(r.C)
|
||||
}
|
||||
|
||||
func (r *rateLimiter) limiter() {
|
||||
defer r.wg.Done()
|
||||
ticker := time.NewTicker(r.rate)
|
||||
defer ticker.Stop()
|
||||
counter := 0
|
||||
for {
|
||||
select {
|
||||
case <-r.shutdown:
|
||||
return
|
||||
case <-ticker.C:
|
||||
counter = 0
|
||||
default:
|
||||
if counter < r.n {
|
||||
select {
|
||||
case r.C <- true:
|
||||
counter++
|
||||
case <-r.shutdown:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
54
internal/limiter/limiter_test.go
Normal file
54
internal/limiter/limiter_test.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRateLimiter(t *testing.T) {
|
||||
r := NewRateLimiter(5, time.Second)
|
||||
ticker := time.NewTicker(time.Millisecond * 75)
|
||||
|
||||
// test that we can only get 5 receives from the rate limiter
|
||||
counter := 0
|
||||
outer:
|
||||
for {
|
||||
select {
|
||||
case <-r.C:
|
||||
counter++
|
||||
case <-ticker.C:
|
||||
break outer
|
||||
}
|
||||
}
|
||||
|
||||
assert.Equal(t, 5, counter)
|
||||
r.Stop()
|
||||
// verify that the Stop function closes the channel.
|
||||
_, ok := <-r.C
|
||||
assert.False(t, ok)
|
||||
}
|
||||
|
||||
func TestRateLimiterMultipleIterations(t *testing.T) {
|
||||
r := NewRateLimiter(5, time.Millisecond*50)
|
||||
ticker := time.NewTicker(time.Millisecond * 250)
|
||||
|
||||
// test that we can get 15 receives from the rate limiter
|
||||
counter := 0
|
||||
outer:
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
break outer
|
||||
case <-r.C:
|
||||
counter++
|
||||
}
|
||||
}
|
||||
|
||||
assert.True(t, counter > 10)
|
||||
r.Stop()
|
||||
// verify that the Stop function closes the channel.
|
||||
_, ok := <-r.C
|
||||
assert.False(t, ok)
|
||||
}
|
||||
@@ -2,81 +2,79 @@ package internal_models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/filter"
|
||||
)
|
||||
|
||||
// TagFilter is the name of a tag, and the values on which to filter
|
||||
type TagFilter struct {
|
||||
Name string
|
||||
Filter []string
|
||||
filter glob.Glob
|
||||
filter filter.Filter
|
||||
}
|
||||
|
||||
// Filter containing drop/pass and tagdrop/tagpass rules
|
||||
type Filter struct {
|
||||
NameDrop []string
|
||||
nameDrop glob.Glob
|
||||
nameDrop filter.Filter
|
||||
NamePass []string
|
||||
namePass glob.Glob
|
||||
namePass filter.Filter
|
||||
|
||||
FieldDrop []string
|
||||
fieldDrop glob.Glob
|
||||
fieldDrop filter.Filter
|
||||
FieldPass []string
|
||||
fieldPass glob.Glob
|
||||
fieldPass filter.Filter
|
||||
|
||||
TagDrop []TagFilter
|
||||
TagPass []TagFilter
|
||||
|
||||
TagExclude []string
|
||||
tagExclude glob.Glob
|
||||
tagExclude filter.Filter
|
||||
TagInclude []string
|
||||
tagInclude glob.Glob
|
||||
tagInclude filter.Filter
|
||||
|
||||
IsActive bool
|
||||
}
|
||||
|
||||
// Compile all Filter lists into glob.Glob objects.
|
||||
// Compile all Filter lists into filter.Filter objects.
|
||||
func (f *Filter) CompileFilter() error {
|
||||
var err error
|
||||
f.nameDrop, err = compileFilter(f.NameDrop)
|
||||
f.nameDrop, err = filter.CompileFilter(f.NameDrop)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error compiling 'namedrop', %s", err)
|
||||
}
|
||||
f.namePass, err = compileFilter(f.NamePass)
|
||||
f.namePass, err = filter.CompileFilter(f.NamePass)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error compiling 'namepass', %s", err)
|
||||
}
|
||||
|
||||
f.fieldDrop, err = compileFilter(f.FieldDrop)
|
||||
f.fieldDrop, err = filter.CompileFilter(f.FieldDrop)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error compiling 'fielddrop', %s", err)
|
||||
}
|
||||
f.fieldPass, err = compileFilter(f.FieldPass)
|
||||
f.fieldPass, err = filter.CompileFilter(f.FieldPass)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error compiling 'fieldpass', %s", err)
|
||||
}
|
||||
|
||||
f.tagExclude, err = compileFilter(f.TagExclude)
|
||||
f.tagExclude, err = filter.CompileFilter(f.TagExclude)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error compiling 'tagexclude', %s", err)
|
||||
}
|
||||
f.tagInclude, err = compileFilter(f.TagInclude)
|
||||
f.tagInclude, err = filter.CompileFilter(f.TagInclude)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error compiling 'taginclude', %s", err)
|
||||
}
|
||||
|
||||
for i, _ := range f.TagDrop {
|
||||
f.TagDrop[i].filter, err = compileFilter(f.TagDrop[i].Filter)
|
||||
f.TagDrop[i].filter, err = filter.CompileFilter(f.TagDrop[i].Filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error compiling 'tagdrop', %s", err)
|
||||
}
|
||||
}
|
||||
for i, _ := range f.TagPass {
|
||||
f.TagPass[i].filter, err = compileFilter(f.TagPass[i].Filter)
|
||||
f.TagPass[i].filter, err = filter.CompileFilter(f.TagPass[i].Filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error compiling 'tagpass', %s", err)
|
||||
}
|
||||
@@ -84,20 +82,6 @@ func (f *Filter) CompileFilter() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func compileFilter(filter []string) (glob.Glob, error) {
|
||||
if len(filter) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var g glob.Glob
|
||||
var err error
|
||||
if len(filter) == 1 {
|
||||
g, err = glob.Compile(filter[0])
|
||||
} else {
|
||||
g, err = glob.Compile("{" + strings.Join(filter, ",") + "}")
|
||||
}
|
||||
return g, err
|
||||
}
|
||||
|
||||
func (f *Filter) ShouldMetricPass(metric telegraf.Metric) bool {
|
||||
if f.ShouldNamePass(metric.Name()) && f.ShouldTagsPass(metric.Tags()) {
|
||||
return true
|
||||
|
||||
@@ -253,51 +253,6 @@ func TestFilter_TagDrop(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilter_CompileFilterError(t *testing.T) {
|
||||
f := Filter{
|
||||
NameDrop: []string{"", ""},
|
||||
}
|
||||
assert.Error(t, f.CompileFilter())
|
||||
f = Filter{
|
||||
NamePass: []string{"", ""},
|
||||
}
|
||||
assert.Error(t, f.CompileFilter())
|
||||
f = Filter{
|
||||
FieldDrop: []string{"", ""},
|
||||
}
|
||||
assert.Error(t, f.CompileFilter())
|
||||
f = Filter{
|
||||
FieldPass: []string{"", ""},
|
||||
}
|
||||
assert.Error(t, f.CompileFilter())
|
||||
f = Filter{
|
||||
TagExclude: []string{"", ""},
|
||||
}
|
||||
assert.Error(t, f.CompileFilter())
|
||||
f = Filter{
|
||||
TagInclude: []string{"", ""},
|
||||
}
|
||||
assert.Error(t, f.CompileFilter())
|
||||
filters := []TagFilter{
|
||||
TagFilter{
|
||||
Name: "cpu",
|
||||
Filter: []string{"{foobar}"},
|
||||
}}
|
||||
f = Filter{
|
||||
TagDrop: filters,
|
||||
}
|
||||
require.Error(t, f.CompileFilter())
|
||||
filters = []TagFilter{
|
||||
TagFilter{
|
||||
Name: "cpu",
|
||||
Filter: []string{"{foobar}"},
|
||||
}}
|
||||
f = Filter{
|
||||
TagPass: filters,
|
||||
}
|
||||
require.Error(t, f.CompileFilter())
|
||||
}
|
||||
|
||||
func TestFilter_ShouldMetricsPass(t *testing.T) {
|
||||
m := testutil.TestMetric(1, "testmetric")
|
||||
f := Filter{
|
||||
|
||||
@@ -45,14 +45,9 @@ func NewMetric(
|
||||
name string,
|
||||
tags map[string]string,
|
||||
fields map[string]interface{},
|
||||
t ...time.Time,
|
||||
t time.Time,
|
||||
) (Metric, error) {
|
||||
var T time.Time
|
||||
if len(t) > 0 {
|
||||
T = t[0]
|
||||
}
|
||||
|
||||
pt, err := client.NewPoint(name, tags, fields, T)
|
||||
pt, err := client.NewPoint(name, tags, fields, t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -51,23 +51,6 @@ func TestNewMetricString(t *testing.T) {
|
||||
assert.Equal(t, lineProtoPrecision, m.PrecisionString("s"))
|
||||
}
|
||||
|
||||
func TestNewMetricStringNoTime(t *testing.T) {
|
||||
tags := map[string]string{
|
||||
"host": "localhost",
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
"usage_idle": float64(99),
|
||||
}
|
||||
m, err := NewMetric("cpu", tags, fields)
|
||||
assert.NoError(t, err)
|
||||
|
||||
lineProto := fmt.Sprintf("cpu,host=localhost usage_idle=99")
|
||||
assert.Equal(t, lineProto, m.String())
|
||||
|
||||
lineProtoPrecision := fmt.Sprintf("cpu,host=localhost usage_idle=99")
|
||||
assert.Equal(t, lineProtoPrecision, m.PrecisionString("s"))
|
||||
}
|
||||
|
||||
func TestNewMetricFailNaN(t *testing.T) {
|
||||
now := time.Now()
|
||||
|
||||
|
||||
@@ -5,7 +5,11 @@ import (
|
||||
_ "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/ceph"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/chrony"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/cloudwatch"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/conntrack"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/consul"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/couchbase"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/couchdb"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/disque"
|
||||
@@ -16,6 +20,7 @@ import (
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/exec"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/filestat"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/github_webhooks"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/graylog"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/haproxy"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/http_response"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/httpjson"
|
||||
@@ -24,6 +29,7 @@ import (
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/jolokia"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/kafka_consumer"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/leofs"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/logparser"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/lustre2"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/mailchimp"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/memcached"
|
||||
@@ -35,6 +41,7 @@ import (
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/net_response"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/nginx"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/nsq"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/nstat"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/ntpq"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/passenger"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/phpfpm"
|
||||
@@ -50,6 +57,7 @@ import (
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/redis"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/rethinkdb"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/riak"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/rollbar_webhooks"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/sensors"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/snmp"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/sqlserver"
|
||||
@@ -61,6 +69,7 @@ import (
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/trig"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/twemproxy"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/udp_listener"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/varnish"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/win_perf_counters"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/zfs"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/zookeeper"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Telegraf plugin: Apache
|
||||
|
||||
#### Plugin arguments:
|
||||
- **urls** []string: List of apache-status URLs to collect from.
|
||||
- **urls** []string: List of apache-status URLs to collect from. Default is "http://localhost/server-status?auto".
|
||||
|
||||
#### Description
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ type Apache struct {
|
||||
|
||||
var sampleConfig = `
|
||||
## An array of Apache status URI to gather stats.
|
||||
## Default is "http://localhost/server-status?auto".
|
||||
urls = ["http://localhost/server-status?auto"]
|
||||
`
|
||||
|
||||
@@ -33,6 +34,10 @@ func (n *Apache) Description() string {
|
||||
}
|
||||
|
||||
func (n *Apache) Gather(acc telegraf.Accumulator) error {
|
||||
if len(n.Urls) == 0 {
|
||||
n.Urls = []string{"http://localhost/server-status?auto"}
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var outerr error
|
||||
|
||||
|
||||
109
plugins/inputs/ceph/README.md
Normal file
109
plugins/inputs/ceph/README.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# Ceph Storage Input Plugin
|
||||
|
||||
Collects performance metrics from the MON and OSD nodes in a Ceph storage cluster.
|
||||
|
||||
The plugin works by scanning the configured SocketDir for OSD and MON socket files. When it finds
|
||||
a MON socket, it runs **ceph --admin-daemon $file perfcounters_dump**. For OSDs it runs **ceph --admin-daemon $file perf dump**
|
||||
|
||||
The resulting JSON is parsed and grouped into collections, based on top-level key. Top-level keys are
|
||||
used as collection tags, and all sub-keys are flattened. For example:
|
||||
|
||||
```
|
||||
{
|
||||
"paxos": {
|
||||
"refresh": 9363435,
|
||||
"refresh_latency": {
|
||||
"avgcount": 9363435,
|
||||
"sum": 5378.794002000
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Would be parsed into the following metrics, all of which would be tagged with collection=paxos:
|
||||
|
||||
- refresh = 9363435
|
||||
- refresh_latency.avgcount: 9363435
|
||||
- refresh_latency.sum: 5378.794002000
|
||||
|
||||
|
||||
### Configuration:
|
||||
|
||||
```
|
||||
# Collects performance metrics from the MON and OSD nodes in a Ceph storage cluster.
|
||||
[[inputs.ceph]]
|
||||
## All configuration values are optional, defaults are shown below
|
||||
|
||||
## location of ceph binary
|
||||
ceph_binary = "/usr/bin/ceph"
|
||||
|
||||
## directory in which to look for socket files
|
||||
socket_dir = "/var/run/ceph"
|
||||
|
||||
## prefix of MON and OSD socket files, used to determine socket type
|
||||
mon_prefix = "ceph-mon"
|
||||
osd_prefix = "ceph-osd"
|
||||
|
||||
## suffix used to identify socket files
|
||||
socket_suffix = "asok"
|
||||
```
|
||||
|
||||
### Measurements & Fields:
|
||||
|
||||
All fields are collected under the **ceph** measurement and stored as float64s. For a full list of fields, see the sample perf dumps in ceph_test.go.
|
||||
|
||||
|
||||
### Tags:
|
||||
|
||||
All measurements will have the following tags:
|
||||
|
||||
- type: either 'osd' or 'mon' to indicate which type of node was queried
|
||||
- id: a unique string identifier, parsed from the socket file name for the node
|
||||
- collection: the top-level key under which these fields were reported. Possible values are:
|
||||
- for MON nodes:
|
||||
- cluster
|
||||
- leveldb
|
||||
- mon
|
||||
- paxos
|
||||
- throttle-mon_client_bytes
|
||||
- throttle-mon_daemon_bytes
|
||||
- throttle-msgr_dispatch_throttler-mon
|
||||
- for OSD nodes:
|
||||
- WBThrottle
|
||||
- filestore
|
||||
- leveldb
|
||||
- mutex-FileJournal::completions_lock
|
||||
- mutex-FileJournal::finisher_lock
|
||||
- mutex-FileJournal::write_lock
|
||||
- mutex-FileJournal::writeq_lock
|
||||
- mutex-JOS::ApplyManager::apply_lock
|
||||
- mutex-JOS::ApplyManager::com_lock
|
||||
- mutex-JOS::SubmitManager::lock
|
||||
- mutex-WBThrottle::lock
|
||||
- objecter
|
||||
- osd
|
||||
- recoverystate_perf
|
||||
- throttle-filestore_bytes
|
||||
- throttle-filestore_ops
|
||||
- throttle-msgr_dispatch_throttler-client
|
||||
- throttle-msgr_dispatch_throttler-cluster
|
||||
- throttle-msgr_dispatch_throttler-hb_back_server
|
||||
- throttle-msgr_dispatch_throttler-hb_front_serve
|
||||
- throttle-msgr_dispatch_throttler-hbclient
|
||||
- throttle-msgr_dispatch_throttler-ms_objecter
|
||||
- throttle-objecter_bytes
|
||||
- throttle-objecter_ops
|
||||
- throttle-osd_client_bytes
|
||||
- throttle-osd_client_messages
|
||||
|
||||
|
||||
### Example Output:
|
||||
|
||||
<pre>
|
||||
telegraf -test -config /etc/telegraf/telegraf.conf -config-directory /etc/telegraf/telegraf.d -input-filter ceph
|
||||
* Plugin: ceph, Collection 1
|
||||
> ceph,collection=paxos, id=node-2,role=openstack,type=mon accept_timeout=0,begin=14931264,begin_bytes.avgcount=14931264,begin_bytes.sum=180309683362,begin_keys.avgcount=0,begin_keys.sum=0,begin_latency.avgcount=14931264,begin_latency.sum=9293.29589,collect=1,collect_bytes.avgcount=1,collect_bytes.sum=24,collect_keys.avgcount=1,collect_keys.sum=1,collect_latency.avgcount=1,collect_latency.sum=0.00028,collect_timeout=0,collect_uncommitted=0,commit=14931264,commit_bytes.avgcount=0,commit_bytes.sum=0,commit_keys.avgcount=0,commit_keys.sum=0,commit_latency.avgcount=0,commit_latency.sum=0,lease_ack_timeout=0,lease_timeout=0,new_pn=0,new_pn_latency.avgcount=0,new_pn_latency.sum=0,refresh=14931264,refresh_latency.avgcount=14931264,refresh_latency.sum=8706.98498,restart=4,share_state=0,share_state_bytes.avgcount=0,share_state_bytes.sum=0,share_state_keys.avgcount=0,share_state_keys.sum=0,start_leader=0,start_peon=1,store_state=14931264,store_state_bytes.avgcount=14931264,store_state_bytes.sum=353119959211,store_state_keys.avgcount=14931264,store_state_keys.sum=289807523,store_state_latency.avgcount=14931264,store_state_latency.sum=10952.835724 1462821234814535148
|
||||
> ceph,collection=throttle-mon_client_bytes,id=node-2,type=mon get=1413017,get_or_fail_fail=0,get_or_fail_success=0,get_sum=71211705,max=104857600,put=1413013,put_sum=71211459,take=0,take_sum=0,val=246,wait.avgcount=0,wait.sum=0 1462821234814737219
|
||||
> ceph,collection=throttle-mon_daemon_bytes,id=node-2,type=mon get=4058121,get_or_fail_fail=0,get_or_fail_success=0,get_sum=6027348117,max=419430400,put=4058121,put_sum=6027348117,take=0,take_sum=0,val=0,wait.avgcount=0,wait.sum=0 1462821234814815661
|
||||
> ceph,collection=throttle-msgr_dispatch_throttler-mon,id=node-2,type=mon get=54276277,get_or_fail_fail=0,get_or_fail_success=0,get_sum=370232877040,max=104857600,put=54276277,put_sum=370232877040,take=0,take_sum=0,val=0,wait.avgcount=0,wait.sum=0 1462821234814872064
|
||||
</pre>
|
||||
249
plugins/inputs/ceph/ceph.go
Normal file
249
plugins/inputs/ceph/ceph.go
Normal file
@@ -0,0 +1,249 @@
|
||||
package ceph
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
measurement = "ceph"
|
||||
typeMon = "monitor"
|
||||
typeOsd = "osd"
|
||||
osdPrefix = "ceph-osd"
|
||||
monPrefix = "ceph-mon"
|
||||
sockSuffix = "asok"
|
||||
)
|
||||
|
||||
type Ceph struct {
|
||||
CephBinary string
|
||||
OsdPrefix string
|
||||
MonPrefix string
|
||||
SocketDir string
|
||||
SocketSuffix string
|
||||
}
|
||||
|
||||
func (c *Ceph) setDefaults() {
|
||||
if c.CephBinary == "" {
|
||||
c.CephBinary = "/usr/bin/ceph"
|
||||
}
|
||||
|
||||
if c.OsdPrefix == "" {
|
||||
c.OsdPrefix = osdPrefix
|
||||
}
|
||||
|
||||
if c.MonPrefix == "" {
|
||||
c.MonPrefix = monPrefix
|
||||
}
|
||||
|
||||
if c.SocketDir == "" {
|
||||
c.SocketDir = "/var/run/ceph"
|
||||
}
|
||||
|
||||
if c.SocketSuffix == "" {
|
||||
c.SocketSuffix = sockSuffix
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Ceph) Description() string {
|
||||
return "Collects performance metrics from the MON and OSD nodes in a Ceph storage cluster."
|
||||
}
|
||||
|
||||
var sampleConfig = `
|
||||
## All configuration values are optional, defaults are shown below
|
||||
|
||||
## location of ceph binary
|
||||
ceph_binary = "/usr/bin/ceph"
|
||||
|
||||
## directory in which to look for socket files
|
||||
socket_dir = "/var/run/ceph"
|
||||
|
||||
## prefix of MON and OSD socket files, used to determine socket type
|
||||
mon_prefix = "ceph-mon"
|
||||
osd_prefix = "ceph-osd"
|
||||
|
||||
## suffix used to identify socket files
|
||||
socket_suffix = "asok"
|
||||
`
|
||||
|
||||
func (c *Ceph) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func (c *Ceph) Gather(acc telegraf.Accumulator) error {
|
||||
c.setDefaults()
|
||||
sockets, err := findSockets(c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find sockets at path '%s': %v", c.SocketDir, err)
|
||||
}
|
||||
|
||||
for _, s := range sockets {
|
||||
dump, err := perfDump(c.CephBinary, s)
|
||||
if err != nil {
|
||||
log.Printf("error reading from socket '%s': %v", s.socket, err)
|
||||
continue
|
||||
}
|
||||
data, err := parseDump(dump)
|
||||
if err != nil {
|
||||
log.Printf("error parsing dump from socket '%s': %v", s.socket, err)
|
||||
continue
|
||||
}
|
||||
for tag, metrics := range *data {
|
||||
acc.AddFields(measurement,
|
||||
map[string]interface{}(metrics),
|
||||
map[string]string{"type": s.sockType, "id": s.sockId, "collection": tag})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
inputs.Add(measurement, func() telegraf.Input { return &Ceph{} })
|
||||
}
|
||||
|
||||
var perfDump = func(binary string, socket *socket) (string, error) {
|
||||
cmdArgs := []string{"--admin-daemon", socket.socket}
|
||||
if socket.sockType == typeOsd {
|
||||
cmdArgs = append(cmdArgs, "perf", "dump")
|
||||
} else if socket.sockType == typeMon {
|
||||
cmdArgs = append(cmdArgs, "perfcounters_dump")
|
||||
} else {
|
||||
return "", fmt.Errorf("ignoring unknown socket type: %s", socket.sockType)
|
||||
}
|
||||
|
||||
cmd := exec.Command(binary, cmdArgs...)
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error running ceph dump: %s", err)
|
||||
}
|
||||
|
||||
return out.String(), nil
|
||||
}
|
||||
|
||||
var findSockets = func(c *Ceph) ([]*socket, error) {
|
||||
listing, err := ioutil.ReadDir(c.SocketDir)
|
||||
if err != nil {
|
||||
return []*socket{}, fmt.Errorf("Failed to read socket directory '%s': %v", c.SocketDir, err)
|
||||
}
|
||||
sockets := make([]*socket, 0, len(listing))
|
||||
for _, info := range listing {
|
||||
f := info.Name()
|
||||
var sockType string
|
||||
var sockPrefix string
|
||||
if strings.HasPrefix(f, c.MonPrefix) {
|
||||
sockType = typeMon
|
||||
sockPrefix = monPrefix
|
||||
}
|
||||
if strings.HasPrefix(f, c.OsdPrefix) {
|
||||
sockType = typeOsd
|
||||
sockPrefix = osdPrefix
|
||||
|
||||
}
|
||||
if sockType == typeOsd || sockType == typeMon {
|
||||
path := filepath.Join(c.SocketDir, f)
|
||||
sockets = append(sockets, &socket{parseSockId(f, sockPrefix, c.SocketSuffix), sockType, path})
|
||||
}
|
||||
}
|
||||
return sockets, nil
|
||||
}
|
||||
|
||||
func parseSockId(fname, prefix, suffix string) string {
|
||||
s := fname
|
||||
s = strings.TrimPrefix(s, prefix)
|
||||
s = strings.TrimSuffix(s, suffix)
|
||||
s = strings.Trim(s, ".-_")
|
||||
return s
|
||||
}
|
||||
|
||||
type socket struct {
|
||||
sockId string
|
||||
sockType string
|
||||
socket string
|
||||
}
|
||||
|
||||
type metric struct {
|
||||
pathStack []string // lifo stack of name components
|
||||
value float64
|
||||
}
|
||||
|
||||
// Pops names of pathStack to build the flattened name for a metric
|
||||
func (m *metric) name() string {
|
||||
buf := bytes.Buffer{}
|
||||
for i := len(m.pathStack) - 1; i >= 0; i-- {
|
||||
if buf.Len() > 0 {
|
||||
buf.WriteString(".")
|
||||
}
|
||||
buf.WriteString(m.pathStack[i])
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
type metricMap map[string]interface{}
|
||||
|
||||
type taggedMetricMap map[string]metricMap
|
||||
|
||||
// Parses a raw JSON string into a taggedMetricMap
|
||||
// Delegates the actual parsing to newTaggedMetricMap(..)
|
||||
func parseDump(dump string) (*taggedMetricMap, error) {
|
||||
data := make(map[string]interface{})
|
||||
err := json.Unmarshal([]byte(dump), &data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse json: '%s': %v", dump, err)
|
||||
}
|
||||
|
||||
tmm := newTaggedMetricMap(data)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to tag dataset: '%v': %v", tmm, err)
|
||||
}
|
||||
|
||||
return tmm, nil
|
||||
}
|
||||
|
||||
// Builds a TaggedMetricMap out of a generic string map.
|
||||
// The top-level key is used as a tag and all sub-keys are flattened into metrics
|
||||
func newTaggedMetricMap(data map[string]interface{}) *taggedMetricMap {
|
||||
tmm := make(taggedMetricMap)
|
||||
for tag, datapoints := range data {
|
||||
mm := make(metricMap)
|
||||
for _, m := range flatten(datapoints) {
|
||||
mm[m.name()] = m.value
|
||||
}
|
||||
tmm[tag] = mm
|
||||
}
|
||||
return &tmm
|
||||
}
|
||||
|
||||
// Recursively flattens any k-v hierarchy present in data.
|
||||
// Nested keys are flattened into ordered slices associated with a metric value.
|
||||
// The key slices are treated as stacks, and are expected to be reversed and concatenated
|
||||
// when passed as metrics to the accumulator. (see (*metric).name())
|
||||
func flatten(data interface{}) []*metric {
|
||||
var metrics []*metric
|
||||
|
||||
switch val := data.(type) {
|
||||
case float64:
|
||||
metrics = []*metric{&metric{make([]string, 0, 1), val}}
|
||||
case map[string]interface{}:
|
||||
metrics = make([]*metric, 0, len(val))
|
||||
for k, v := range val {
|
||||
for _, m := range flatten(v) {
|
||||
m.pathStack = append(m.pathStack, k)
|
||||
metrics = append(metrics, m)
|
||||
}
|
||||
}
|
||||
default:
|
||||
log.Printf("Ignoring unexpected type '%T' for value %v", val, val)
|
||||
}
|
||||
|
||||
return metrics
|
||||
}
|
||||
682
plugins/inputs/ceph/ceph_test.go
Normal file
682
plugins/inputs/ceph/ceph_test.go
Normal file
@@ -0,0 +1,682 @@
|
||||
package ceph
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
epsilon = float64(0.00000001)
|
||||
)
|
||||
|
||||
func TestParseSockId(t *testing.T) {
|
||||
s := parseSockId(sockFile(osdPrefix, 1), osdPrefix, sockSuffix)
|
||||
assert.Equal(t, s, "1")
|
||||
}
|
||||
|
||||
func TestParseMonDump(t *testing.T) {
|
||||
dump, err := parseDump(monPerfDump)
|
||||
assert.NoError(t, err)
|
||||
assert.InEpsilon(t, 5678670180, (*dump)["cluster"]["osd_kb_used"], epsilon)
|
||||
assert.InEpsilon(t, 6866.540527000, (*dump)["paxos"]["store_state_latency.sum"], epsilon)
|
||||
}
|
||||
|
||||
func TestParseOsdDump(t *testing.T) {
|
||||
dump, err := parseDump(osdPerfDump)
|
||||
assert.NoError(t, err)
|
||||
assert.InEpsilon(t, 552132.109360000, (*dump)["filestore"]["commitcycle_interval.sum"], epsilon)
|
||||
assert.Equal(t, float64(0), (*dump)["mutex-FileJournal::finisher_lock"]["wait.avgcount"])
|
||||
}
|
||||
|
||||
func TestGather(t *testing.T) {
|
||||
saveFind := findSockets
|
||||
saveDump := perfDump
|
||||
defer func() {
|
||||
findSockets = saveFind
|
||||
perfDump = saveDump
|
||||
}()
|
||||
|
||||
findSockets = func(c *Ceph) ([]*socket, error) {
|
||||
return []*socket{&socket{"osd.1", typeOsd, ""}}, nil
|
||||
}
|
||||
|
||||
perfDump = func(binary string, s *socket) (string, error) {
|
||||
return osdPerfDump, nil
|
||||
}
|
||||
|
||||
acc := &testutil.Accumulator{}
|
||||
c := &Ceph{}
|
||||
c.Gather(acc)
|
||||
|
||||
}
|
||||
|
||||
func TestFindSockets(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "socktest")
|
||||
assert.NoError(t, err)
|
||||
defer func() {
|
||||
err := os.Remove(tmpdir)
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
c := &Ceph{
|
||||
CephBinary: "foo",
|
||||
SocketDir: tmpdir,
|
||||
}
|
||||
|
||||
c.setDefaults()
|
||||
|
||||
for _, st := range sockTestParams {
|
||||
createTestFiles(tmpdir, st)
|
||||
|
||||
sockets, err := findSockets(c)
|
||||
assert.NoError(t, err)
|
||||
|
||||
for i := 1; i <= st.osds; i++ {
|
||||
assertFoundSocket(t, tmpdir, typeOsd, i, sockets)
|
||||
}
|
||||
|
||||
for i := 1; i <= st.mons; i++ {
|
||||
assertFoundSocket(t, tmpdir, typeMon, i, sockets)
|
||||
}
|
||||
cleanupTestFiles(tmpdir, st)
|
||||
}
|
||||
}
|
||||
|
||||
func assertFoundSocket(t *testing.T, dir, sockType string, i int, sockets []*socket) {
|
||||
var prefix string
|
||||
if sockType == typeOsd {
|
||||
prefix = osdPrefix
|
||||
} else {
|
||||
prefix = monPrefix
|
||||
}
|
||||
expected := path.Join(dir, sockFile(prefix, i))
|
||||
found := false
|
||||
for _, s := range sockets {
|
||||
fmt.Printf("Checking %s\n", s.socket)
|
||||
if s.socket == expected {
|
||||
found = true
|
||||
assert.Equal(t, s.sockType, sockType, "Unexpected socket type for '%s'", s)
|
||||
assert.Equal(t, s.sockId, strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
assert.True(t, found, "Did not find socket: %s", expected)
|
||||
}
|
||||
|
||||
func sockFile(prefix string, i int) string {
|
||||
return strings.Join([]string{prefix, strconv.Itoa(i), sockSuffix}, ".")
|
||||
}
|
||||
|
||||
func createTestFiles(dir string, st *SockTest) {
|
||||
writeFile := func(prefix string, i int) {
|
||||
f := sockFile(prefix, i)
|
||||
fpath := path.Join(dir, f)
|
||||
ioutil.WriteFile(fpath, []byte(""), 0777)
|
||||
}
|
||||
tstFileApply(st, writeFile)
|
||||
}
|
||||
|
||||
func cleanupTestFiles(dir string, st *SockTest) {
|
||||
rmFile := func(prefix string, i int) {
|
||||
f := sockFile(prefix, i)
|
||||
fpath := path.Join(dir, f)
|
||||
err := os.Remove(fpath)
|
||||
if err != nil {
|
||||
fmt.Printf("Error removing test file %s: %v\n", fpath, err)
|
||||
}
|
||||
}
|
||||
tstFileApply(st, rmFile)
|
||||
}
|
||||
|
||||
func tstFileApply(st *SockTest, fn func(prefix string, i int)) {
|
||||
for i := 1; i <= st.osds; i++ {
|
||||
fn(osdPrefix, i)
|
||||
}
|
||||
for i := 1; i <= st.mons; i++ {
|
||||
fn(monPrefix, i)
|
||||
}
|
||||
}
|
||||
|
||||
type SockTest struct {
|
||||
osds int
|
||||
mons int
|
||||
}
|
||||
|
||||
var sockTestParams = []*SockTest{
|
||||
&SockTest{
|
||||
osds: 2,
|
||||
mons: 2,
|
||||
},
|
||||
&SockTest{
|
||||
mons: 1,
|
||||
},
|
||||
&SockTest{
|
||||
osds: 1,
|
||||
},
|
||||
&SockTest{},
|
||||
}
|
||||
|
||||
var monPerfDump = `
|
||||
{ "cluster": { "num_mon": 2,
|
||||
"num_mon_quorum": 2,
|
||||
"num_osd": 26,
|
||||
"num_osd_up": 26,
|
||||
"num_osd_in": 26,
|
||||
"osd_epoch": 3306,
|
||||
"osd_kb": 11487846448,
|
||||
"osd_kb_used": 5678670180,
|
||||
"osd_kb_avail": 5809176268,
|
||||
"num_pool": 12,
|
||||
"num_pg": 768,
|
||||
"num_pg_active_clean": 768,
|
||||
"num_pg_active": 768,
|
||||
"num_pg_peering": 0,
|
||||
"num_object": 397616,
|
||||
"num_object_degraded": 0,
|
||||
"num_object_unfound": 0,
|
||||
"num_bytes": 2917848227467,
|
||||
"num_mds_up": 0,
|
||||
"num_mds_in": 0,
|
||||
"num_mds_failed": 0,
|
||||
"mds_epoch": 1},
|
||||
"leveldb": { "leveldb_get": 321950312,
|
||||
"leveldb_transaction": 18729922,
|
||||
"leveldb_compact": 0,
|
||||
"leveldb_compact_range": 74141,
|
||||
"leveldb_compact_queue_merge": 0,
|
||||
"leveldb_compact_queue_len": 0},
|
||||
"mon": {},
|
||||
"paxos": { "start_leader": 0,
|
||||
"start_peon": 1,
|
||||
"restart": 4,
|
||||
"refresh": 9363435,
|
||||
"refresh_latency": { "avgcount": 9363435,
|
||||
"sum": 5378.794002000},
|
||||
"begin": 9363435,
|
||||
"begin_keys": { "avgcount": 0,
|
||||
"sum": 0},
|
||||
"begin_bytes": { "avgcount": 9363435,
|
||||
"sum": 110468605489},
|
||||
"begin_latency": { "avgcount": 9363435,
|
||||
"sum": 5850.060682000},
|
||||
"commit": 9363435,
|
||||
"commit_keys": { "avgcount": 0,
|
||||
"sum": 0},
|
||||
"commit_bytes": { "avgcount": 0,
|
||||
"sum": 0},
|
||||
"commit_latency": { "avgcount": 0,
|
||||
"sum": 0.000000000},
|
||||
"collect": 1,
|
||||
"collect_keys": { "avgcount": 1,
|
||||
"sum": 1},
|
||||
"collect_bytes": { "avgcount": 1,
|
||||
"sum": 24},
|
||||
"collect_latency": { "avgcount": 1,
|
||||
"sum": 0.000280000},
|
||||
"collect_uncommitted": 0,
|
||||
"collect_timeout": 0,
|
||||
"accept_timeout": 0,
|
||||
"lease_ack_timeout": 0,
|
||||
"lease_timeout": 0,
|
||||
"store_state": 9363435,
|
||||
"store_state_keys": { "avgcount": 9363435,
|
||||
"sum": 176572789},
|
||||
"store_state_bytes": { "avgcount": 9363435,
|
||||
"sum": 216355887217},
|
||||
"store_state_latency": { "avgcount": 9363435,
|
||||
"sum": 6866.540527000},
|
||||
"share_state": 0,
|
||||
"share_state_keys": { "avgcount": 0,
|
||||
"sum": 0},
|
||||
"share_state_bytes": { "avgcount": 0,
|
||||
"sum": 0},
|
||||
"new_pn": 0,
|
||||
"new_pn_latency": { "avgcount": 0,
|
||||
"sum": 0.000000000}},
|
||||
"throttle-mon_client_bytes": { "val": 246,
|
||||
"max": 104857600,
|
||||
"get": 896030,
|
||||
"get_sum": 45854374,
|
||||
"get_or_fail_fail": 0,
|
||||
"get_or_fail_success": 0,
|
||||
"take": 0,
|
||||
"take_sum": 0,
|
||||
"put": 896026,
|
||||
"put_sum": 45854128,
|
||||
"wait": { "avgcount": 0,
|
||||
"sum": 0.000000000}},
|
||||
"throttle-mon_daemon_bytes": { "val": 0,
|
||||
"max": 419430400,
|
||||
"get": 2773768,
|
||||
"get_sum": 3627676976,
|
||||
"get_or_fail_fail": 0,
|
||||
"get_or_fail_success": 0,
|
||||
"take": 0,
|
||||
"take_sum": 0,
|
||||
"put": 2773768,
|
||||
"put_sum": 3627676976,
|
||||
"wait": { "avgcount": 0,
|
||||
"sum": 0.000000000}},
|
||||
"throttle-msgr_dispatch_throttler-mon": { "val": 0,
|
||||
"max": 104857600,
|
||||
"get": 34504949,
|
||||
"get_sum": 226860281124,
|
||||
"get_or_fail_fail": 0,
|
||||
"get_or_fail_success": 0,
|
||||
"take": 0,
|
||||
"take_sum": 0,
|
||||
"put": 34504949,
|
||||
"put_sum": 226860281124,
|
||||
"wait": { "avgcount": 0,
|
||||
"sum": 0.000000000}}}
|
||||
`
|
||||
|
||||
var osdPerfDump = `
|
||||
{ "WBThrottle": { "bytes_dirtied": 28405539,
|
||||
"bytes_wb": 0,
|
||||
"ios_dirtied": 93,
|
||||
"ios_wb": 0,
|
||||
"inodes_dirtied": 86,
|
||||
"inodes_wb": 0},
|
||||
"filestore": { "journal_queue_max_ops": 0,
|
||||
"journal_queue_ops": 0,
|
||||
"journal_ops": 1108008,
|
||||
"journal_queue_max_bytes": 0,
|
||||
"journal_queue_bytes": 0,
|
||||
"journal_bytes": 73233416196,
|
||||
"journal_latency": { "avgcount": 1108008,
|
||||
"sum": 290.981036000},
|
||||
"journal_wr": 1091866,
|
||||
"journal_wr_bytes": { "avgcount": 1091866,
|
||||
"sum": 74925682688},
|
||||
"journal_full": 0,
|
||||
"committing": 0,
|
||||
"commitcycle": 110389,
|
||||
"commitcycle_interval": { "avgcount": 110389,
|
||||
"sum": 552132.109360000},
|
||||
"commitcycle_latency": { "avgcount": 110389,
|
||||
"sum": 178.657804000},
|
||||
"op_queue_max_ops": 50,
|
||||
"op_queue_ops": 0,
|
||||
"ops": 1108008,
|
||||
"op_queue_max_bytes": 104857600,
|
||||
"op_queue_bytes": 0,
|
||||
"bytes": 73226768148,
|
||||
"apply_latency": { "avgcount": 1108008,
|
||||
"sum": 947.742722000},
|
||||
"queue_transaction_latency_avg": { "avgcount": 1108008,
|
||||
"sum": 0.511327000}},
|
||||
"leveldb": { "leveldb_get": 4361221,
|
||||
"leveldb_transaction": 4351276,
|
||||
"leveldb_compact": 0,
|
||||
"leveldb_compact_range": 0,
|
||||
"leveldb_compact_queue_merge": 0,
|
||||
"leveldb_compact_queue_len": 0},
|
||||
"mutex-FileJournal::completions_lock": { "wait": { "avgcount": 0,
|
||||
"sum": 0.000000000}},
|
||||
"mutex-FileJournal::finisher_lock": { "wait": { "avgcount": 0,
|
||||
"sum": 0.000000000}},
|
||||
"mutex-FileJournal::write_lock": { "wait": { "avgcount": 0,
|
||||
"sum": 0.000000000}},
|
||||
"mutex-FileJournal::writeq_lock": { "wait": { "avgcount": 0,
|
||||
"sum": 0.000000000}},
|
||||
"mutex-JOS::ApplyManager::apply_lock": { "wait": { "avgcount": 0,
|
||||
"sum": 0.000000000}},
|
||||
"mutex-JOS::ApplyManager::com_lock": { "wait": { "avgcount": 0,
|
||||
"sum": 0.000000000}},
|
||||
"mutex-JOS::SubmitManager::lock": { "wait": { "avgcount": 0,
|
||||
"sum": 0.000000000}},
|
||||
"mutex-WBThrottle::lock": { "wait": { "avgcount": 0,
|
||||
"sum": 0.000000000}},
|
||||
"objecter": { "op_active": 0,
|
||||
"op_laggy": 0,
|
||||
"op_send": 0,
|
||||
"op_send_bytes": 0,
|
||||
"op_resend": 0,
|
||||
"op_ack": 0,
|
||||
"op_commit": 0,
|
||||
"op": 0,
|
||||
"op_r": 0,
|
||||
"op_w": 0,
|
||||
"op_rmw": 0,
|
||||
"op_pg": 0,
|
||||
"osdop_stat": 0,
|
||||
"osdop_create": 0,
|
||||
"osdop_read": 0,
|
||||
"osdop_write": 0,
|
||||
"osdop_writefull": 0,
|
||||
"osdop_append": 0,
|
||||
"osdop_zero": 0,
|
||||
"osdop_truncate": 0,
|
||||
"osdop_delete": 0,
|
||||
"osdop_mapext": 0,
|
||||
"osdop_sparse_read": 0,
|
||||
"osdop_clonerange": 0,
|
||||
"osdop_getxattr": 0,
|
||||
"osdop_setxattr": 0,
|
||||
"osdop_cmpxattr": 0,
|
||||
"osdop_rmxattr": 0,
|
||||
"osdop_resetxattrs": 0,
|
||||
"osdop_tmap_up": 0,
|
||||
"osdop_tmap_put": 0,
|
||||
"osdop_tmap_get": 0,
|
||||
"osdop_call": 0,
|
||||
"osdop_watch": 0,
|
||||
"osdop_notify": 0,
|
||||
"osdop_src_cmpxattr": 0,
|
||||
"osdop_pgls": 0,
|
||||
"osdop_pgls_filter": 0,
|
||||
"osdop_other": 0,
|
||||
"linger_active": 0,
|
||||
"linger_send": 0,
|
||||
"linger_resend": 0,
|
||||
"poolop_active": 0,
|
||||
"poolop_send": 0,
|
||||
"poolop_resend": 0,
|
||||
"poolstat_active": 0,
|
||||
"poolstat_send": 0,
|
||||
"poolstat_resend": 0,
|
||||
"statfs_active": 0,
|
||||
"statfs_send": 0,
|
||||
"statfs_resend": 0,
|
||||
"command_active": 0,
|
||||
"command_send": 0,
|
||||
"command_resend": 0,
|
||||
"map_epoch": 3300,
|
||||
"map_full": 0,
|
||||
"map_inc": 3293,
|
||||
"osd_sessions": 0,
|
||||
"osd_session_open": 0,
|
||||
"osd_session_close": 0,
|
||||
"osd_laggy": 0},
|
||||
"osd": { "opq": 0,
|
||||
"op_wip": 0,
|
||||
"op": 23939,
|
||||
"op_in_bytes": 1245903961,
|
||||
"op_out_bytes": 29103083856,
|
||||
"op_latency": { "avgcount": 23939,
|
||||
"sum": 440.192015000},
|
||||
"op_process_latency": { "avgcount": 23939,
|
||||
"sum": 30.170685000},
|
||||
"op_r": 23112,
|
||||
"op_r_out_bytes": 29103056146,
|
||||
"op_r_latency": { "avgcount": 23112,
|
||||
"sum": 19.373526000},
|
||||
"op_r_process_latency": { "avgcount": 23112,
|
||||
"sum": 14.625928000},
|
||||
"op_w": 549,
|
||||
"op_w_in_bytes": 1245804358,
|
||||
"op_w_rlat": { "avgcount": 549,
|
||||
"sum": 17.022299000},
|
||||
"op_w_latency": { "avgcount": 549,
|
||||
"sum": 418.494610000},
|
||||
"op_w_process_latency": { "avgcount": 549,
|
||||
"sum": 13.316555000},
|
||||
"op_rw": 278,
|
||||
"op_rw_in_bytes": 99603,
|
||||
"op_rw_out_bytes": 27710,
|
||||
"op_rw_rlat": { "avgcount": 278,
|
||||
"sum": 2.213785000},
|
||||
"op_rw_latency": { "avgcount": 278,
|
||||
"sum": 2.323879000},
|
||||
"op_rw_process_latency": { "avgcount": 278,
|
||||
"sum": 2.228202000},
|
||||
"subop": 1074774,
|
||||
"subop_in_bytes": 26841811636,
|
||||
"subop_latency": { "avgcount": 1074774,
|
||||
"sum": 745.509160000},
|
||||
"subop_w": 0,
|
||||
"subop_w_in_bytes": 26841811636,
|
||||
"subop_w_latency": { "avgcount": 1074774,
|
||||
"sum": 745.509160000},
|
||||
"subop_pull": 0,
|
||||
"subop_pull_latency": { "avgcount": 0,
|
||||
"sum": 0.000000000},
|
||||
"subop_push": 0,
|
||||
"subop_push_in_bytes": 0,
|
||||
"subop_push_latency": { "avgcount": 0,
|
||||
"sum": 0.000000000},
|
||||
"pull": 0,
|
||||
"push": 28,
|
||||
"push_out_bytes": 103483392,
|
||||
"push_in": 0,
|
||||
"push_in_bytes": 0,
|
||||
"recovery_ops": 15,
|
||||
"loadavg": 202,
|
||||
"buffer_bytes": 0,
|
||||
"numpg": 18,
|
||||
"numpg_primary": 8,
|
||||
"numpg_replica": 10,
|
||||
"numpg_stray": 0,
|
||||
"heartbeat_to_peers": 10,
|
||||
"heartbeat_from_peers": 0,
|
||||
"map_messages": 7413,
|
||||
"map_message_epochs": 9792,
|
||||
"map_message_epoch_dups": 10105,
|
||||
"messages_delayed_for_map": 83,
|
||||
"stat_bytes": 102123175936,
|
||||
"stat_bytes_used": 49961820160,
|
||||
"stat_bytes_avail": 52161355776,
|
||||
"copyfrom": 0,
|
||||
"tier_promote": 0,
|
||||
"tier_flush": 0,
|
||||
"tier_flush_fail": 0,
|
||||
"tier_try_flush": 0,
|
||||
"tier_try_flush_fail": 0,
|
||||
"tier_evict": 0,
|
||||
"tier_whiteout": 0,
|
||||
"tier_dirty": 230,
|
||||
"tier_clean": 0,
|
||||
"tier_delay": 0,
|
||||
"agent_wake": 0,
|
||||
"agent_skip": 0,
|
||||
"agent_flush": 0,
|
||||
"agent_evict": 0},
|
||||
"recoverystate_perf": { "initial_latency": { "avgcount": 473,
|
||||
"sum": 0.027207000},
|
||||
"started_latency": { "avgcount": 1480,
|
||||
"sum": 9854902.397648000},
|
||||
"reset_latency": { "avgcount": 1953,
|
||||
"sum": 0.096206000},
|
||||
"start_latency": { "avgcount": 1953,
|
||||
"sum": 0.059947000},
|
||||
"primary_latency": { "avgcount": 765,
|
||||
"sum": 4688922.186935000},
|
||||
"peering_latency": { "avgcount": 704,
|
||||
"sum": 1668.652135000},
|
||||
"backfilling_latency": { "avgcount": 0,
|
||||
"sum": 0.000000000},
|
||||
"waitremotebackfillreserved_latency": { "avgcount": 0,
|
||||
"sum": 0.000000000},
|
||||
"waitlocalbackfillreserved_latency": { "avgcount": 0,
|
||||
"sum": 0.000000000},
|
||||
"notbackfilling_latency": { "avgcount": 0,
|
||||
"sum": 0.000000000},
|
||||
"repnotrecovering_latency": { "avgcount": 462,
|
||||
"sum": 5158922.114600000},
|
||||
"repwaitrecoveryreserved_latency": { "avgcount": 15,
|
||||
"sum": 0.008275000},
|
||||
"repwaitbackfillreserved_latency": { "avgcount": 1,
|
||||
"sum": 0.000095000},
|
||||
"RepRecovering_latency": { "avgcount": 16,
|
||||
"sum": 2274.944727000},
|
||||
"activating_latency": { "avgcount": 514,
|
||||
"sum": 261.008520000},
|
||||
"waitlocalrecoveryreserved_latency": { "avgcount": 20,
|
||||
"sum": 0.175422000},
|
||||
"waitremoterecoveryreserved_latency": { "avgcount": 20,
|
||||
"sum": 0.682778000},
|
||||
"recovering_latency": { "avgcount": 20,
|
||||
"sum": 0.697551000},
|
||||
"recovered_latency": { "avgcount": 511,
|
||||
"sum": 0.011038000},
|
||||
"clean_latency": { "avgcount": 503,
|
||||
"sum": 4686961.154278000},
|
||||
"active_latency": { "avgcount": 506,
|
||||
"sum": 4687223.640464000},
|
||||
"replicaactive_latency": { "avgcount": 446,
|
||||
"sum": 5161197.078966000},
|
||||
"stray_latency": { "avgcount": 794,
|
||||
"sum": 4805.105128000},
|
||||
"getinfo_latency": { "avgcount": 704,
|
||||
"sum": 1138.477937000},
|
||||
"getlog_latency": { "avgcount": 678,
|
||||
"sum": 0.036393000},
|
||||
"waitactingchange_latency": { "avgcount": 69,
|
||||
"sum": 59.172893000},
|
||||
"incomplete_latency": { "avgcount": 0,
|
||||
"sum": 0.000000000},
|
||||
"getmissing_latency": { "avgcount": 609,
|
||||
"sum": 0.012288000},
|
||||
"waitupthru_latency": { "avgcount": 576,
|
||||
"sum": 530.106999000}},
|
||||
"throttle-filestore_bytes": { "val": 0,
|
||||
"max": 0,
|
||||
"get": 0,
|
||||
"get_sum": 0,
|
||||
"get_or_fail_fail": 0,
|
||||
"get_or_fail_success": 0,
|
||||
"take": 0,
|
||||
"take_sum": 0,
|
||||
"put": 0,
|
||||
"put_sum": 0,
|
||||
"wait": { "avgcount": 0,
|
||||
"sum": 0.000000000}},
|
||||
"throttle-filestore_ops": { "val": 0,
|
||||
"max": 0,
|
||||
"get": 0,
|
||||
"get_sum": 0,
|
||||
"get_or_fail_fail": 0,
|
||||
"get_or_fail_success": 0,
|
||||
"take": 0,
|
||||
"take_sum": 0,
|
||||
"put": 0,
|
||||
"put_sum": 0,
|
||||
"wait": { "avgcount": 0,
|
||||
"sum": 0.000000000}},
|
||||
"throttle-msgr_dispatch_throttler-client": { "val": 0,
|
||||
"max": 104857600,
|
||||
"get": 130730,
|
||||
"get_sum": 1246039872,
|
||||
"get_or_fail_fail": 0,
|
||||
"get_or_fail_success": 0,
|
||||
"take": 0,
|
||||
"take_sum": 0,
|
||||
"put": 130730,
|
||||
"put_sum": 1246039872,
|
||||
"wait": { "avgcount": 0,
|
||||
"sum": 0.000000000}},
|
||||
"throttle-msgr_dispatch_throttler-cluster": { "val": 0,
|
||||
"max": 104857600,
|
||||
"get": 1108033,
|
||||
"get_sum": 71277949992,
|
||||
"get_or_fail_fail": 0,
|
||||
"get_or_fail_success": 0,
|
||||
"take": 0,
|
||||
"take_sum": 0,
|
||||
"put": 1108033,
|
||||
"put_sum": 71277949992,
|
||||
"wait": { "avgcount": 0,
|
||||
"sum": 0.000000000}},
|
||||
"throttle-msgr_dispatch_throttler-hb_back_server": { "val": 0,
|
||||
"max": 104857600,
|
||||
"get": 18320575,
|
||||
"get_sum": 861067025,
|
||||
"get_or_fail_fail": 0,
|
||||
"get_or_fail_success": 0,
|
||||
"take": 0,
|
||||
"take_sum": 0,
|
||||
"put": 18320575,
|
||||
"put_sum": 861067025,
|
||||
"wait": { "avgcount": 0,
|
||||
"sum": 0.000000000}},
|
||||
"throttle-msgr_dispatch_throttler-hb_front_server": { "val": 0,
|
||||
"max": 104857600,
|
||||
"get": 18320575,
|
||||
"get_sum": 861067025,
|
||||
"get_or_fail_fail": 0,
|
||||
"get_or_fail_success": 0,
|
||||
"take": 0,
|
||||
"take_sum": 0,
|
||||
"put": 18320575,
|
||||
"put_sum": 861067025,
|
||||
"wait": { "avgcount": 0,
|
||||
"sum": 0.000000000}},
|
||||
"throttle-msgr_dispatch_throttler-hbclient": { "val": 0,
|
||||
"max": 104857600,
|
||||
"get": 40479394,
|
||||
"get_sum": 1902531518,
|
||||
"get_or_fail_fail": 0,
|
||||
"get_or_fail_success": 0,
|
||||
"take": 0,
|
||||
"take_sum": 0,
|
||||
"put": 40479394,
|
||||
"put_sum": 1902531518,
|
||||
"wait": { "avgcount": 0,
|
||||
"sum": 0.000000000}},
|
||||
"throttle-msgr_dispatch_throttler-ms_objecter": { "val": 0,
|
||||
"max": 104857600,
|
||||
"get": 0,
|
||||
"get_sum": 0,
|
||||
"get_or_fail_fail": 0,
|
||||
"get_or_fail_success": 0,
|
||||
"take": 0,
|
||||
"take_sum": 0,
|
||||
"put": 0,
|
||||
"put_sum": 0,
|
||||
"wait": { "avgcount": 0,
|
||||
"sum": 0.000000000}},
|
||||
"throttle-objecter_bytes": { "val": 0,
|
||||
"max": 104857600,
|
||||
"get": 0,
|
||||
"get_sum": 0,
|
||||
"get_or_fail_fail": 0,
|
||||
"get_or_fail_success": 0,
|
||||
"take": 0,
|
||||
"take_sum": 0,
|
||||
"put": 0,
|
||||
"put_sum": 0,
|
||||
"wait": { "avgcount": 0,
|
||||
"sum": 0.000000000}},
|
||||
"throttle-objecter_ops": { "val": 0,
|
||||
"max": 1024,
|
||||
"get": 0,
|
||||
"get_sum": 0,
|
||||
"get_or_fail_fail": 0,
|
||||
"get_or_fail_success": 0,
|
||||
"take": 0,
|
||||
"take_sum": 0,
|
||||
"put": 0,
|
||||
"put_sum": 0,
|
||||
"wait": { "avgcount": 0,
|
||||
"sum": 0.000000000}},
|
||||
"throttle-osd_client_bytes": { "val": 0,
|
||||
"max": 524288000,
|
||||
"get": 24241,
|
||||
"get_sum": 1241992581,
|
||||
"get_or_fail_fail": 0,
|
||||
"get_or_fail_success": 0,
|
||||
"take": 0,
|
||||
"take_sum": 0,
|
||||
"put": 25958,
|
||||
"put_sum": 1241992581,
|
||||
"wait": { "avgcount": 0,
|
||||
"sum": 0.000000000}},
|
||||
"throttle-osd_client_messages": { "val": 0,
|
||||
"max": 100,
|
||||
"get": 49214,
|
||||
"get_sum": 49214,
|
||||
"get_or_fail_fail": 0,
|
||||
"get_or_fail_success": 0,
|
||||
"take": 0,
|
||||
"take_sum": 0,
|
||||
"put": 49214,
|
||||
"put_sum": 49214,
|
||||
"wait": { "avgcount": 0,
|
||||
"sum": 0.000000000}}}
|
||||
`
|
||||
92
plugins/inputs/chrony/README.md
Normal file
92
plugins/inputs/chrony/README.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# chrony Input Plugin
|
||||
|
||||
Get standard chrony metrics, requires chronyc executable.
|
||||
|
||||
Below is the documentation of the various headers returned by `chronyc tracking`.
|
||||
|
||||
- Reference ID - This is the refid and name (or IP address) if available, of the
|
||||
server to which the computer is currently synchronised. If this is 127.127.1.1
|
||||
it means the computer is not synchronised to any external source and that you
|
||||
have the ‘local’ mode operating (via the local command in chronyc (see section local),
|
||||
or the local directive in the ‘/etc/chrony.conf’ file (see section local)).
|
||||
- Stratum - The stratum indicates how many hops away from a computer with an attached
|
||||
reference clock we are. Such a computer is a stratum-1 computer, so the computer in the
|
||||
example is two hops away (i.e. a.b.c is a stratum-2 and is synchronised from a stratum-1).
|
||||
- Ref time - This is the time (UTC) at which the last measurement from the reference
|
||||
source was processed.
|
||||
- System time - In normal operation, chronyd never steps the system clock, because any
|
||||
jump in the timescale can have adverse consequences for certain application programs.
|
||||
Instead, any error in the system clock is corrected by slightly speeding up or slowing
|
||||
down the system clock until the error has been removed, and then returning to the system
|
||||
clock’s normal speed. A consequence of this is that there will be a period when the
|
||||
system clock (as read by other programs using the gettimeofday() system call, or by the
|
||||
date command in the shell) will be different from chronyd's estimate of the current true
|
||||
time (which it reports to NTP clients when it is operating in server mode). The value
|
||||
reported on this line is the difference due to this effect.
|
||||
- Last offset - This is the estimated local offset on the last clock update.
|
||||
- RMS offset - This is a long-term average of the offset value.
|
||||
- Frequency - The ‘frequency’ is the rate by which the system’s clock would be
|
||||
wrong if chronyd was not correcting it. It is expressed in ppm (parts per million).
|
||||
For example, a value of 1ppm would mean that when the system’s clock thinks it has
|
||||
advanced 1 second, it has actually advanced by 1.000001 seconds relative to true time.
|
||||
- Residual freq - This shows the ‘residual frequency’ for the currently selected
|
||||
reference source. This reflects any difference between what the measurements from the
|
||||
reference source indicate the frequency should be and the frequency currently being used.
|
||||
The reason this is not always zero is that a smoothing procedure is applied to the
|
||||
frequency. Each time a measurement from the reference source is obtained and a new
|
||||
residual frequency computed, the estimated accuracy of this residual is compared with the
|
||||
estimated accuracy (see ‘skew’ next) of the existing frequency value. A weighted average
|
||||
is computed for the new frequency, with weights depending on these accuracies. If the
|
||||
measurements from the reference source follow a consistent trend, the residual will be
|
||||
driven to zero over time.
|
||||
- Skew - This is the estimated error bound on the frequency.
|
||||
- Root delay - This is the total of the network path delays to the stratum-1 computer
|
||||
from which the computer is ultimately synchronised. In certain extreme situations, this
|
||||
value can be negative. (This can arise in a symmetric peer arrangement where the computers’
|
||||
frequencies are not tracking each other and the network delay is very short relative to the
|
||||
turn-around time at each computer.)
|
||||
- Root dispersion - This is the total dispersion accumulated through all the computers
|
||||
back to the stratum-1 computer from which the computer is ultimately synchronised.
|
||||
Dispersion is due to system clock resolution, statistical measurement variations etc.
|
||||
- Leap status - This is the leap status, which can be Normal, Insert second,
|
||||
Delete second or Not synchronised.
|
||||
|
||||
### Configuration:
|
||||
|
||||
```toml
|
||||
# Get standard chrony metrics, requires chronyc executable.
|
||||
[[inputs.chrony]]
|
||||
## If true, chronyc tries to perform a DNS lookup for the time server.
|
||||
# dns_lookup = false
|
||||
```
|
||||
|
||||
### Measurements & Fields:
|
||||
|
||||
- chrony
|
||||
- last_offset (float, seconds)
|
||||
- rms_offset (float, seconds)
|
||||
- frequency (float, ppm)
|
||||
- residual_freq (float, ppm)
|
||||
- skew (float, ppm)
|
||||
- root_delay (float, seconds)
|
||||
- root_dispersion (float, seconds)
|
||||
- update_interval (float, seconds)
|
||||
|
||||
### Tags:
|
||||
|
||||
- All measurements have the following tags:
|
||||
- reference_id
|
||||
- stratum
|
||||
- leap_status
|
||||
|
||||
### Example Output:
|
||||
|
||||
```
|
||||
$ telegraf -config telegraf.conf -input-filter chrony -test
|
||||
* Plugin: chrony, Collection 1
|
||||
> chrony,leap_status=normal,reference_id=192.168.1.1,stratum=3 frequency=-35.657,last_offset=-0.000013616,residual_freq=-0,rms_offset=0.000027073,root_delay=0.000644,root_dispersion=0.003444,skew=0.001,update_interval=1031.2 1463750789687639161
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
129
plugins/inputs/chrony/chrony.go
Normal file
129
plugins/inputs/chrony/chrony.go
Normal file
@@ -0,0 +1,129 @@
|
||||
// +build linux
|
||||
|
||||
package chrony
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/internal"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
var (
|
||||
execCommand = exec.Command // execCommand is used to mock commands in tests.
|
||||
)
|
||||
|
||||
type Chrony struct {
|
||||
DNSLookup bool `toml:"dns_lookup"`
|
||||
path string
|
||||
}
|
||||
|
||||
func (*Chrony) Description() string {
|
||||
return "Get standard chrony metrics, requires chronyc executable."
|
||||
}
|
||||
|
||||
func (*Chrony) SampleConfig() string {
|
||||
return `
|
||||
## If true, chronyc tries to perform a DNS lookup for the time server.
|
||||
# dns_lookup = false
|
||||
`
|
||||
}
|
||||
|
||||
func (c *Chrony) Gather(acc telegraf.Accumulator) error {
|
||||
if len(c.path) == 0 {
|
||||
return errors.New("chronyc not found: verify that chrony is installed and that chronyc is in your PATH")
|
||||
}
|
||||
|
||||
flags := []string{}
|
||||
if !c.DNSLookup {
|
||||
flags = append(flags, "-n")
|
||||
}
|
||||
flags = append(flags, "tracking")
|
||||
|
||||
cmd := execCommand(c.path, flags...)
|
||||
out, err := internal.CombinedOutputTimeout(cmd, time.Second*5)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to run command %s: %s - %s", strings.Join(cmd.Args, " "), err, string(out))
|
||||
}
|
||||
fields, tags, err := processChronycOutput(string(out))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
acc.AddFields("chrony", fields, tags)
|
||||
return nil
|
||||
}
|
||||
|
||||
// processChronycOutput takes in a string output from the chronyc command, like:
|
||||
//
|
||||
// Reference ID : 192.168.1.22 (ntp.example.com)
|
||||
// Stratum : 3
|
||||
// Ref time (UTC) : Thu May 12 14:27:07 2016
|
||||
// System time : 0.000020390 seconds fast of NTP time
|
||||
// Last offset : +0.000012651 seconds
|
||||
// RMS offset : 0.000025577 seconds
|
||||
// Frequency : 16.001 ppm slow
|
||||
// Residual freq : -0.000 ppm
|
||||
// Skew : 0.006 ppm
|
||||
// Root delay : 0.001655 seconds
|
||||
// Root dispersion : 0.003307 seconds
|
||||
// Update interval : 507.2 seconds
|
||||
// Leap status : Normal
|
||||
//
|
||||
// The value on the left side of the colon is used as field name, if the first field on
|
||||
// the right side is a float. If it cannot be parsed as float, it is a tag name.
|
||||
//
|
||||
// Ref time is ignored and all names are converted to snake case.
|
||||
//
|
||||
// It returns (<fields>, <tags>)
|
||||
func processChronycOutput(out string) (map[string]interface{}, map[string]string, error) {
|
||||
tags := map[string]string{}
|
||||
fields := map[string]interface{}{}
|
||||
lines := strings.Split(strings.TrimSpace(out), "\n")
|
||||
for _, line := range lines {
|
||||
stats := strings.Split(line, ":")
|
||||
if len(stats) < 2 {
|
||||
return nil, nil, fmt.Errorf("unexpected output from chronyc, expected ':' in %s", out)
|
||||
}
|
||||
name := strings.ToLower(strings.Replace(strings.TrimSpace(stats[0]), " ", "_", -1))
|
||||
// ignore reference time
|
||||
if strings.Contains(name, "time") {
|
||||
continue
|
||||
}
|
||||
valueFields := strings.Fields(stats[1])
|
||||
if len(valueFields) == 0 {
|
||||
return nil, nil, fmt.Errorf("unexpected output from chronyc: %s", out)
|
||||
}
|
||||
if strings.Contains(strings.ToLower(name), "stratum") {
|
||||
tags["stratum"] = valueFields[0]
|
||||
continue
|
||||
}
|
||||
value, err := strconv.ParseFloat(valueFields[0], 64)
|
||||
if err != nil {
|
||||
tags[name] = strings.ToLower(valueFields[0])
|
||||
continue
|
||||
}
|
||||
if strings.Contains(stats[1], "slow") {
|
||||
value = -value
|
||||
}
|
||||
fields[name] = value
|
||||
}
|
||||
|
||||
return fields, tags, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
c := Chrony{}
|
||||
path, _ := exec.LookPath("chronyc")
|
||||
if len(path) > 0 {
|
||||
c.path = path
|
||||
}
|
||||
inputs.Add("chrony", func() telegraf.Input {
|
||||
return &c
|
||||
})
|
||||
}
|
||||
3
plugins/inputs/chrony/chrony_notlinux.go
Normal file
3
plugins/inputs/chrony/chrony_notlinux.go
Normal file
@@ -0,0 +1,3 @@
|
||||
// +build !linux
|
||||
|
||||
package chrony
|
||||
109
plugins/inputs/chrony/chrony_test.go
Normal file
109
plugins/inputs/chrony/chrony_test.go
Normal file
@@ -0,0 +1,109 @@
|
||||
// +build linux
|
||||
|
||||
package chrony
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
)
|
||||
|
||||
func TestGather(t *testing.T) {
|
||||
c := Chrony{
|
||||
path: "chronyc",
|
||||
}
|
||||
// overwriting exec commands with mock commands
|
||||
execCommand = fakeExecCommand
|
||||
defer func() { execCommand = exec.Command }()
|
||||
var acc testutil.Accumulator
|
||||
|
||||
err := c.Gather(&acc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"reference_id": "192.168.1.22",
|
||||
"leap_status": "normal",
|
||||
"stratum": "3",
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
"last_offset": 0.000012651,
|
||||
"rms_offset": 0.000025577,
|
||||
"frequency": -16.001,
|
||||
"residual_freq": 0.0,
|
||||
"skew": 0.006,
|
||||
"root_delay": 0.001655,
|
||||
"root_dispersion": 0.003307,
|
||||
"update_interval": 507.2,
|
||||
}
|
||||
|
||||
acc.AssertContainsTaggedFields(t, "chrony", fields, tags)
|
||||
|
||||
// test with dns lookup
|
||||
c.DNSLookup = true
|
||||
err = c.Gather(&acc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
acc.AssertContainsTaggedFields(t, "chrony", fields, tags)
|
||||
|
||||
}
|
||||
|
||||
// fackeExecCommand is a helper function that mock
|
||||
// the exec.Command call (and call the test binary)
|
||||
func fakeExecCommand(command string, args ...string) *exec.Cmd {
|
||||
cs := []string{"-test.run=TestHelperProcess", "--", command}
|
||||
cs = append(cs, args...)
|
||||
cmd := exec.Command(os.Args[0], cs...)
|
||||
cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TestHelperProcess isn't a real test. It's used to mock exec.Command
|
||||
// For example, if you run:
|
||||
// GO_WANT_HELPER_PROCESS=1 go test -test.run=TestHelperProcess -- chrony tracking
|
||||
// it returns below mockData.
|
||||
func TestHelperProcess(t *testing.T) {
|
||||
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
|
||||
return
|
||||
}
|
||||
|
||||
lookup := "Reference ID : 192.168.1.22 (ntp.example.com)\n"
|
||||
noLookup := "Reference ID : 192.168.1.22 (192.168.1.22)\n"
|
||||
mockData := `Stratum : 3
|
||||
Ref time (UTC) : Thu May 12 14:27:07 2016
|
||||
System time : 0.000020390 seconds fast of NTP time
|
||||
Last offset : +0.000012651 seconds
|
||||
RMS offset : 0.000025577 seconds
|
||||
Frequency : 16.001 ppm slow
|
||||
Residual freq : -0.000 ppm
|
||||
Skew : 0.006 ppm
|
||||
Root delay : 0.001655 seconds
|
||||
Root dispersion : 0.003307 seconds
|
||||
Update interval : 507.2 seconds
|
||||
Leap status : Normal
|
||||
`
|
||||
|
||||
args := os.Args
|
||||
|
||||
// Previous arguments are tests stuff, that looks like :
|
||||
// /tmp/go-build970079519/…/_test/integration.test -test.run=TestHelperProcess --
|
||||
cmd, args := args[3], args[4:]
|
||||
|
||||
if cmd == "chronyc" {
|
||||
if args[0] == "tracking" {
|
||||
fmt.Fprint(os.Stdout, lookup+mockData)
|
||||
} else {
|
||||
fmt.Fprint(os.Stdout, noLookup+mockData)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprint(os.Stdout, "command not found")
|
||||
os.Exit(1)
|
||||
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
@@ -6,9 +6,12 @@ This plugin will pull Metric Statistics from Amazon CloudWatch.
|
||||
|
||||
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)
|
||||
1. Assumed credentials via STS if `role_arn` attribute is specified (source credentials are evaluated from subsequent rules)
|
||||
2. Explicit credentials from `access_key`, `secret_key`, and `token` attributes
|
||||
3. Shared profile from `profile` attribute
|
||||
4. [Environment Variables](https://github.com/aws/aws-sdk-go/wiki/configuring-sdk#environment-variables)
|
||||
5. [Shared Credentials](https://github.com/aws/aws-sdk-go/wiki/configuring-sdk#shared-credentials-file)
|
||||
6. [EC2 Instance Profile](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html)
|
||||
|
||||
### Configuration:
|
||||
|
||||
@@ -24,7 +27,7 @@ API endpoint. In the following order the plugin will attempt to authenticate.
|
||||
delay = '1m'
|
||||
|
||||
## Override global run interval (optional - defaults to global interval)
|
||||
## Recomended: use metric 'interval' that is a multiple of 'period' to avoid
|
||||
## Recomended: use metric 'interval' that is a multiple of 'period' to avoid
|
||||
## gaps or overlap in pulled data
|
||||
interval = '1m'
|
||||
|
||||
@@ -36,11 +39,15 @@ API endpoint. In the following order the plugin will attempt to authenticate.
|
||||
## 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'
|
||||
|
||||
[[inputs.cloudwatch.metrics.dimensions]]
|
||||
name = 'AvailabilityZone'
|
||||
value = '*'
|
||||
```
|
||||
#### Requirements and Terminology
|
||||
|
||||
@@ -52,6 +59,39 @@ Plugin Configuration utilizes [CloudWatch concepts](http://docs.aws.amazon.com/A
|
||||
- `names` must be valid CloudWatch [Metric](http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/cloudwatch_concepts.html#Metric) names
|
||||
- `dimensions` must be valid CloudWatch [Dimension](http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/cloudwatch_concepts.html#Dimension) name/value pairs
|
||||
|
||||
Omitting or specifying a value of `'*'` for a dimension value configures all available metrics that contain a dimension with the specified name
|
||||
to be retrieved. If specifying >1 dimension, then the metric must contain *all* the configured dimensions where the the value of the
|
||||
wildcard dimension is ignored.
|
||||
|
||||
Example:
|
||||
```
|
||||
[[inputs.cloudwatch.metrics]]
|
||||
names = ['Latency']
|
||||
|
||||
## Dimension filters for Metric (optional)
|
||||
[[inputs.cloudwatch.metrics.dimensions]]
|
||||
name = 'LoadBalancerName'
|
||||
value = 'p-example'
|
||||
|
||||
[[inputs.cloudwatch.metrics.dimensions]]
|
||||
name = 'AvailabilityZone'
|
||||
value = '*'
|
||||
```
|
||||
|
||||
If the following ELBs are available:
|
||||
- name: `p-example`, availabilityZone: `us-east-1a`
|
||||
- name: `p-example`, availabilityZone: `us-east-1b`
|
||||
- name: `q-example`, availabilityZone: `us-east-1a`
|
||||
- name: `q-example`, availabilityZone: `us-east-1b`
|
||||
|
||||
|
||||
Then 2 metrics will be output:
|
||||
- name: `p-example`, availabilityZone: `us-east-1a`
|
||||
- name: `p-example`, availabilityZone: `us-east-1b`
|
||||
|
||||
If the `AvailabilityZone` wildcard dimension was omitted, then a single metric (name: `p-example`)
|
||||
would be exported containing the aggregate values of the ELB across availability zones.
|
||||
|
||||
#### Restrictions and Limitations
|
||||
- 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/)
|
||||
|
||||
@@ -3,28 +3,36 @@ package cloudwatch
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/internal"
|
||||
internalaws "github.com/influxdata/telegraf/internal/config/aws"
|
||||
"github.com/influxdata/telegraf/internal/errchan"
|
||||
"github.com/influxdata/telegraf/internal/limiter"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
type (
|
||||
CloudWatch struct {
|
||||
Region string `toml:"region"`
|
||||
AccessKey string `toml:"access_key"`
|
||||
SecretKey string `toml:"secret_key"`
|
||||
Region string `toml:"region"`
|
||||
AccessKey string `toml:"access_key"`
|
||||
SecretKey string `toml:"secret_key"`
|
||||
RoleARN string `toml:"role_arn"`
|
||||
Profile string `toml:"profile"`
|
||||
Filename string `toml:"shared_credential_file"`
|
||||
Token string `toml:"token"`
|
||||
|
||||
Period internal.Duration `toml:"period"`
|
||||
Delay internal.Duration `toml:"delay"`
|
||||
Namespace string `toml:"namespace"`
|
||||
Metrics []*Metric `toml:"metrics"`
|
||||
CacheTTL internal.Duration `toml:"cache_ttl"`
|
||||
client cloudwatchClient
|
||||
metricCache *MetricCache
|
||||
}
|
||||
@@ -58,12 +66,18 @@ func (c *CloudWatch) SampleConfig() string {
|
||||
|
||||
## Amazon Credentials
|
||||
## Credentials are loaded in the following order
|
||||
## 1) explicit credentials from 'access_key' and 'secret_key'
|
||||
## 2) environment variables
|
||||
## 3) shared credentials file
|
||||
## 4) EC2 Instance Profile
|
||||
## 1) Assumed credentials via STS if role_arn is specified
|
||||
## 2) explicit credentials from 'access_key' and 'secret_key'
|
||||
## 3) shared profile from 'profile'
|
||||
## 4) environment variables
|
||||
## 5) shared credentials file
|
||||
## 6) EC2 Instance Profile
|
||||
#access_key = ""
|
||||
#secret_key = ""
|
||||
#token = ""
|
||||
#role_arn = ""
|
||||
#profile = ""
|
||||
#shared_credential_file = ""
|
||||
|
||||
## Requested CloudWatch aggregation Period (required - must be a multiple of 60s)
|
||||
period = '1m'
|
||||
@@ -75,6 +89,10 @@ func (c *CloudWatch) SampleConfig() string {
|
||||
## gaps or overlap in pulled data
|
||||
interval = '1m'
|
||||
|
||||
## Configure the TTL for the internal cache of metrics.
|
||||
## Defaults to 1 hr if not specified
|
||||
#cache_ttl = '10m'
|
||||
|
||||
## Metric Statistic Namespace (required)
|
||||
namespace = 'AWS/ELB'
|
||||
|
||||
@@ -106,20 +124,40 @@ func (c *CloudWatch) Gather(acc telegraf.Accumulator) error {
|
||||
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),
|
||||
if !hasWilcard(m.Dimensions) {
|
||||
dimensions := make([]*cloudwatch.Dimension, len(m.Dimensions))
|
||||
for k, d := range m.Dimensions {
|
||||
fmt.Printf("Dimension [%s]:[%s]\n", d.Name, d.Value)
|
||||
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 {
|
||||
allMetrics, err := c.fetchNamespaceMetrics()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, name := range m.MetricNames {
|
||||
for _, metric := range allMetrics {
|
||||
if isSelected(metric, m.Dimensions) {
|
||||
metrics = append(metrics, &cloudwatch.Metric{
|
||||
Namespace: aws.String(c.Namespace),
|
||||
MetricName: aws.String(name),
|
||||
Dimensions: metric.Dimensions,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, name := range m.MetricNames {
|
||||
metrics = append(metrics, &cloudwatch.Metric{
|
||||
Namespace: aws.String(c.Namespace),
|
||||
MetricName: aws.String(name),
|
||||
Dimensions: dimensions,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
@@ -130,30 +168,35 @@ func (c *CloudWatch) Gather(acc telegraf.Accumulator) error {
|
||||
}
|
||||
|
||||
metricCount := len(metrics)
|
||||
var errChan = make(chan error, metricCount)
|
||||
errChan := errchan.New(metricCount)
|
||||
|
||||
now := time.Now()
|
||||
|
||||
// limit concurrency or we can easily exhaust user connection limit
|
||||
semaphore := make(chan byte, 64)
|
||||
|
||||
// see cloudwatch API request limits:
|
||||
// http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/cloudwatch_limits.html
|
||||
lmtr := limiter.NewRateLimiter(10, time.Second)
|
||||
defer lmtr.Stop()
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(metrics))
|
||||
for _, m := range metrics {
|
||||
semaphore <- 0x1
|
||||
go c.gatherMetric(acc, m, now, semaphore, errChan)
|
||||
<-lmtr.C
|
||||
go func(inm *cloudwatch.Metric) {
|
||||
defer wg.Done()
|
||||
c.gatherMetric(acc, inm, now, errChan.C)
|
||||
}(m)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
for i := 1; i <= metricCount; i++ {
|
||||
err := <-errChan
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return errChan.Error()
|
||||
}
|
||||
|
||||
func init() {
|
||||
inputs.Add("cloudwatch", func() telegraf.Input {
|
||||
return &CloudWatch{}
|
||||
ttl, _ := time.ParseDuration("1hr")
|
||||
return &CloudWatch{
|
||||
CacheTTL: internal.Duration{Duration: ttl},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -161,14 +204,18 @@ func init() {
|
||||
* Initialize CloudWatch client
|
||||
*/
|
||||
func (c *CloudWatch) initializeCloudWatch() error {
|
||||
config := &aws.Config{
|
||||
Region: aws.String(c.Region),
|
||||
}
|
||||
if c.AccessKey != "" || c.SecretKey != "" {
|
||||
config.Credentials = credentials.NewStaticCredentials(c.AccessKey, c.SecretKey, "")
|
||||
credentialConfig := &internalaws.CredentialConfig{
|
||||
Region: c.Region,
|
||||
AccessKey: c.AccessKey,
|
||||
SecretKey: c.SecretKey,
|
||||
RoleARN: c.RoleARN,
|
||||
Profile: c.Profile,
|
||||
Filename: c.Filename,
|
||||
Token: c.Token,
|
||||
}
|
||||
configProvider := credentialConfig.Credentials()
|
||||
|
||||
c.client = cloudwatch.New(session.New(config))
|
||||
c.client = cloudwatch.New(configProvider)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -203,11 +250,10 @@ func (c *CloudWatch) fetchNamespaceMetrics() (metrics []*cloudwatch.Metric, err
|
||||
more = token != nil
|
||||
}
|
||||
|
||||
cacheTTL, _ := time.ParseDuration("1hr")
|
||||
c.metricCache = &MetricCache{
|
||||
Metrics: metrics,
|
||||
Fetched: time.Now(),
|
||||
TTL: cacheTTL,
|
||||
TTL: c.CacheTTL.Duration,
|
||||
}
|
||||
|
||||
return
|
||||
@@ -216,12 +262,16 @@ func (c *CloudWatch) fetchNamespaceMetrics() (metrics []*cloudwatch.Metric, err
|
||||
/*
|
||||
* 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) {
|
||||
func (c *CloudWatch) gatherMetric(
|
||||
acc telegraf.Accumulator,
|
||||
metric *cloudwatch.Metric,
|
||||
now time.Time,
|
||||
errChan chan error,
|
||||
) {
|
||||
params := c.getStatisticsInput(metric, now)
|
||||
resp, err := c.client.GetMetricStatistics(params)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
<-semaphore
|
||||
return
|
||||
}
|
||||
|
||||
@@ -258,7 +308,6 @@ func (c *CloudWatch) gatherMetric(acc telegraf.Accumulator, metric *cloudwatch.M
|
||||
}
|
||||
|
||||
errChan <- nil
|
||||
<-semaphore
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -309,3 +358,32 @@ func (c *CloudWatch) getStatisticsInput(metric *cloudwatch.Metric, now time.Time
|
||||
func (c *MetricCache) IsValid() bool {
|
||||
return c.Metrics != nil && time.Since(c.Fetched) < c.TTL
|
||||
}
|
||||
|
||||
func hasWilcard(dimensions []*Dimension) bool {
|
||||
for _, d := range dimensions {
|
||||
if d.Value == "" || d.Value == "*" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isSelected(metric *cloudwatch.Metric, dimensions []*Dimension) bool {
|
||||
if len(metric.Dimensions) != len(dimensions) {
|
||||
return false
|
||||
}
|
||||
for _, d := range dimensions {
|
||||
selected := false
|
||||
for _, d2 := range metric.Dimensions {
|
||||
if d.Name == *d2.Name {
|
||||
if d.Value == "" || d.Value == "*" || d.Value == *d2.Value {
|
||||
selected = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !selected {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
56
plugins/inputs/conntrack/README.md
Normal file
56
plugins/inputs/conntrack/README.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Conntrack Plugin
|
||||
|
||||
Collects stats from Netfilter's conntrack-tools.
|
||||
|
||||
The conntrack-tools provide a mechanism for tracking various aspects of
|
||||
network connections as they are processed by netfilter. At runtime,
|
||||
conntrack exposes many of those connection statistics within /proc/sys/net.
|
||||
Depending on your kernel version, these files can be found in either
|
||||
/proc/sys/net/ipv4/netfilter or /proc/sys/net/netfilter and will be
|
||||
prefixed with either ip_ or nf_. This plugin reads the files specified
|
||||
in its configuration and publishes each one as a field, with the prefix
|
||||
normalized to ip_.
|
||||
|
||||
In order to simplify configuration in a heterogeneous environment, a superset
|
||||
of directory and filenames can be specified. Any locations that don't exist
|
||||
will be ignored.
|
||||
|
||||
For more information on conntrack-tools, see the
|
||||
[Netfilter Documentation](http://conntrack-tools.netfilter.org/).
|
||||
|
||||
|
||||
### Configuration:
|
||||
|
||||
```toml
|
||||
# Collects conntrack stats from the configured directories and files.
|
||||
[[inputs.conntrack]]
|
||||
## The following defaults would work with multiple versions of conntrack.
|
||||
## Note the nf_ and ip_ filename prefixes are mutually exclusive across
|
||||
## kernel versions, as are the directory locations.
|
||||
|
||||
## Superset of filenames to look for within the conntrack dirs.
|
||||
## Missing files will be ignored.
|
||||
files = ["ip_conntrack_count","ip_conntrack_max",
|
||||
"nf_conntrack_count","nf_conntrack_max"]
|
||||
|
||||
## Directories to search within for the conntrack files above.
|
||||
## Missing directrories will be ignored.
|
||||
dirs = ["/proc/sys/net/ipv4/netfilter","/proc/sys/net/netfilter"]
|
||||
```
|
||||
|
||||
### Measurements & Fields:
|
||||
|
||||
- conntrack
|
||||
- ip_conntrack_count (int, count): the number of entries in the conntrack table
|
||||
- ip_conntrack_max (int, size): the max capacity of the conntrack table
|
||||
|
||||
### Tags:
|
||||
|
||||
This input does not use tags.
|
||||
|
||||
### Example Output:
|
||||
|
||||
```
|
||||
$ ./telegraf -config telegraf.conf -input-filter conntrack -test
|
||||
conntrack,host=myhost ip_conntrack_count=2,ip_conntrack_max=262144 1461620427667995735
|
||||
```
|
||||
119
plugins/inputs/conntrack/conntrack.go
Normal file
119
plugins/inputs/conntrack/conntrack.go
Normal file
@@ -0,0 +1,119 @@
|
||||
// +build linux
|
||||
|
||||
package conntrack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
"log"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Conntrack struct {
|
||||
Path string
|
||||
Dirs []string
|
||||
Files []string
|
||||
}
|
||||
|
||||
const (
|
||||
inputName = "conntrack"
|
||||
)
|
||||
|
||||
var dfltDirs = []string{
|
||||
"/proc/sys/net/ipv4/netfilter",
|
||||
"/proc/sys/net/netfilter",
|
||||
}
|
||||
|
||||
var dfltFiles = []string{
|
||||
"ip_conntrack_count",
|
||||
"ip_conntrack_max",
|
||||
"nf_conntrack_count",
|
||||
"nf_conntrack_max",
|
||||
}
|
||||
|
||||
func (c *Conntrack) setDefaults() {
|
||||
if len(c.Dirs) == 0 {
|
||||
c.Dirs = dfltDirs
|
||||
}
|
||||
|
||||
if len(c.Files) == 0 {
|
||||
c.Files = dfltFiles
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conntrack) Description() string {
|
||||
return "Collects conntrack stats from the configured directories and files."
|
||||
}
|
||||
|
||||
var sampleConfig = `
|
||||
## The following defaults would work with multiple versions of conntrack.
|
||||
## Note the nf_ and ip_ filename prefixes are mutually exclusive across
|
||||
## kernel versions, as are the directory locations.
|
||||
|
||||
## Superset of filenames to look for within the conntrack dirs.
|
||||
## Missing files will be ignored.
|
||||
files = ["ip_conntrack_count","ip_conntrack_max",
|
||||
"nf_conntrack_count","nf_conntrack_max"]
|
||||
|
||||
## Directories to search within for the conntrack files above.
|
||||
## Missing directrories will be ignored.
|
||||
dirs = ["/proc/sys/net/ipv4/netfilter","/proc/sys/net/netfilter"]
|
||||
`
|
||||
|
||||
func (c *Conntrack) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func (c *Conntrack) Gather(acc telegraf.Accumulator) error {
|
||||
c.setDefaults()
|
||||
|
||||
var metricKey string
|
||||
fields := make(map[string]interface{})
|
||||
|
||||
for _, dir := range c.Dirs {
|
||||
for _, file := range c.Files {
|
||||
// NOTE: no system will have both nf_ and ip_ prefixes,
|
||||
// so we're safe to branch on suffix only.
|
||||
parts := strings.SplitN(file, "_", 2)
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
metricKey = "ip_" + parts[1]
|
||||
|
||||
fName := filepath.Join(dir, file)
|
||||
if _, err := os.Stat(fName); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
contents, err := ioutil.ReadFile(fName)
|
||||
if err != nil {
|
||||
log.Printf("failed to read file '%s': %v", fName, err)
|
||||
}
|
||||
|
||||
v := strings.TrimSpace(string(contents))
|
||||
fields[metricKey], err = strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
log.Printf("failed to parse metric, expected number but "+
|
||||
" found '%s': %v", v, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(fields) == 0 {
|
||||
return fmt.Errorf("Conntrack input failed to collect metrics. " +
|
||||
"Is the conntrack kernel module loaded?")
|
||||
}
|
||||
|
||||
acc.AddFields(inputName, fields, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
inputs.Add(inputName, func() telegraf.Input { return &Conntrack{} })
|
||||
}
|
||||
3
plugins/inputs/conntrack/conntrack_notlinux.go
Normal file
3
plugins/inputs/conntrack/conntrack_notlinux.go
Normal file
@@ -0,0 +1,3 @@
|
||||
// +build !linux
|
||||
|
||||
package conntrack
|
||||
90
plugins/inputs/conntrack/conntrack_test.go
Normal file
90
plugins/inputs/conntrack/conntrack_test.go
Normal file
@@ -0,0 +1,90 @@
|
||||
// +build linux
|
||||
|
||||
package conntrack
|
||||
|
||||
import (
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func restoreDflts(savedFiles, savedDirs []string) {
|
||||
dfltFiles = savedFiles
|
||||
dfltDirs = savedDirs
|
||||
}
|
||||
|
||||
func TestNoFilesFound(t *testing.T) {
|
||||
defer restoreDflts(dfltFiles, dfltDirs)
|
||||
|
||||
dfltFiles = []string{"baz.txt"}
|
||||
dfltDirs = []string{"./foo/bar"}
|
||||
c := &Conntrack{}
|
||||
acc := &testutil.Accumulator{}
|
||||
err := c.Gather(acc)
|
||||
|
||||
assert.EqualError(t, err, "Conntrack input failed to collect metrics. "+
|
||||
"Is the conntrack kernel module loaded?")
|
||||
}
|
||||
|
||||
func TestDefaultsUsed(t *testing.T) {
|
||||
defer restoreDflts(dfltFiles, dfltDirs)
|
||||
tmpdir, err := ioutil.TempDir("", "tmp1")
|
||||
assert.NoError(t, err)
|
||||
defer os.Remove(tmpdir)
|
||||
|
||||
tmpFile, err := ioutil.TempFile(tmpdir, "ip_conntrack_count")
|
||||
assert.NoError(t, err)
|
||||
|
||||
dfltDirs = []string{tmpdir}
|
||||
fname := path.Base(tmpFile.Name())
|
||||
dfltFiles = []string{fname}
|
||||
|
||||
count := 1234321
|
||||
ioutil.WriteFile(tmpFile.Name(), []byte(strconv.Itoa(count)), 0660)
|
||||
c := &Conntrack{}
|
||||
acc := &testutil.Accumulator{}
|
||||
|
||||
c.Gather(acc)
|
||||
acc.AssertContainsFields(t, inputName, map[string]interface{}{
|
||||
fname: float64(count)})
|
||||
}
|
||||
|
||||
func TestConfigsUsed(t *testing.T) {
|
||||
defer restoreDflts(dfltFiles, dfltDirs)
|
||||
tmpdir, err := ioutil.TempDir("", "tmp1")
|
||||
assert.NoError(t, err)
|
||||
defer os.Remove(tmpdir)
|
||||
|
||||
cntFile, err := ioutil.TempFile(tmpdir, "nf_conntrack_count")
|
||||
maxFile, err := ioutil.TempFile(tmpdir, "nf_conntrack_max")
|
||||
assert.NoError(t, err)
|
||||
|
||||
dfltDirs = []string{tmpdir}
|
||||
cntFname := path.Base(cntFile.Name())
|
||||
maxFname := path.Base(maxFile.Name())
|
||||
dfltFiles = []string{cntFname, maxFname}
|
||||
|
||||
count := 1234321
|
||||
max := 9999999
|
||||
ioutil.WriteFile(cntFile.Name(), []byte(strconv.Itoa(count)), 0660)
|
||||
ioutil.WriteFile(maxFile.Name(), []byte(strconv.Itoa(max)), 0660)
|
||||
c := &Conntrack{}
|
||||
acc := &testutil.Accumulator{}
|
||||
|
||||
c.Gather(acc)
|
||||
|
||||
fix := func(s string) string {
|
||||
return strings.Replace(s, "nf_", "ip_", 1)
|
||||
}
|
||||
|
||||
acc.AssertContainsFields(t, inputName,
|
||||
map[string]interface{}{
|
||||
fix(cntFname): float64(count),
|
||||
fix(maxFname): float64(max),
|
||||
})
|
||||
}
|
||||
46
plugins/inputs/consul/README.md
Normal file
46
plugins/inputs/consul/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Telegraf Input Plugin: Consul
|
||||
|
||||
This plugin will collect statistics about all helath checks registered in the Consul. It uses [Consul API](https://www.consul.io/docs/agent/http/health.html#health_state)
|
||||
to query the data. It will not report the [telemetry](https://www.consul.io/docs/agent/telemetry.html) but Consul can report those stats already using StatsD protocol if needed.
|
||||
|
||||
## Configuration:
|
||||
|
||||
```
|
||||
# Gather health check statuses from services registered in Consul
|
||||
[[inputs.consul]]
|
||||
## Most of these values defaults to the one configured on a Consul's agent level.
|
||||
## Optional Consul server address (default: "")
|
||||
# address = ""
|
||||
## Optional URI scheme for the Consul server (default: "")
|
||||
# scheme = ""
|
||||
## Optional ACL token used in every request (default: "")
|
||||
# token = ""
|
||||
## Optional username used for request HTTP Basic Authentication (default: "")
|
||||
# username = ""
|
||||
## Optional password used for HTTP Basic Authentication (default: "")
|
||||
# password = ""
|
||||
## Optional data centre to query the health checks from (default: "")
|
||||
# datacentre = ""
|
||||
```
|
||||
|
||||
## Measurements:
|
||||
|
||||
### Consul:
|
||||
Tags:
|
||||
- node: on which node check/service is registered on
|
||||
- service_name: name of the service (this is the service name not the service ID)
|
||||
|
||||
Fields:
|
||||
- check_id
|
||||
- check_name
|
||||
- service_id
|
||||
- status
|
||||
|
||||
## Example output
|
||||
|
||||
```
|
||||
$ telegraf --config ./telegraf.conf -input-filter consul -test
|
||||
* Plugin: consul, Collection 1
|
||||
> consul_health_checks,host=wolfpit,node=consul-server-node check_id="serfHealth",check_name="Serf Health Status",service_id="",status="passing" 1464698464486439902
|
||||
> consul_health_checks,host=wolfpit,node=consul-server-node,service_name=www.example.com check_id="service:www-example-com.test01",check_name="Service 'www.example.com' check",service_id="www-example-com.test01",status="critical" 1464698464486519036
|
||||
```
|
||||
136
plugins/inputs/consul/consul.go
Normal file
136
plugins/inputs/consul/consul.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package consul
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/internal"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
type Consul struct {
|
||||
Address string
|
||||
Scheme string
|
||||
Token string
|
||||
Username string
|
||||
Password string
|
||||
Datacentre string
|
||||
|
||||
// Path to CA file
|
||||
SSLCA string `toml:"ssl_ca"`
|
||||
// Path to host cert file
|
||||
SSLCert string `toml:"ssl_cert"`
|
||||
// Path to cert key file
|
||||
SSLKey string `toml:"ssl_key"`
|
||||
// Use SSL but skip chain & host verification
|
||||
InsecureSkipVerify bool
|
||||
|
||||
// client used to connect to Consul agnet
|
||||
client *api.Client
|
||||
}
|
||||
|
||||
var sampleConfig = `
|
||||
## Most of these values defaults to the one configured on a Consul's agent level.
|
||||
## Optional Consul server address (default: "localhost")
|
||||
# address = "localhost"
|
||||
## Optional URI scheme for the Consul server (default: "http")
|
||||
# scheme = "http"
|
||||
## Optional ACL token used in every request (default: "")
|
||||
# token = ""
|
||||
## Optional username used for request HTTP Basic Authentication (default: "")
|
||||
# username = ""
|
||||
## Optional password used for HTTP Basic Authentication (default: "")
|
||||
# password = ""
|
||||
## Optional data centre to query the health checks from (default: "")
|
||||
# datacentre = ""
|
||||
`
|
||||
|
||||
func (c *Consul) Description() string {
|
||||
return "Gather health check statuses from services registered in Consul"
|
||||
}
|
||||
|
||||
func (c *Consul) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func (c *Consul) createAPIClient() (*api.Client, error) {
|
||||
config := api.DefaultConfig()
|
||||
|
||||
if c.Address != "" {
|
||||
config.Address = c.Address
|
||||
}
|
||||
|
||||
if c.Scheme != "" {
|
||||
config.Scheme = c.Scheme
|
||||
}
|
||||
|
||||
if c.Datacentre != "" {
|
||||
config.Datacenter = c.Datacentre
|
||||
}
|
||||
|
||||
if c.Username != "" {
|
||||
config.HttpAuth = &api.HttpBasicAuth{
|
||||
Username: c.Username,
|
||||
Password: c.Password,
|
||||
}
|
||||
}
|
||||
|
||||
tlsCfg, err := internal.GetTLSConfig(
|
||||
c.SSLCert, c.SSLKey, c.SSLCA, c.InsecureSkipVerify)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config.HttpClient.Transport = &http.Transport{
|
||||
TLSClientConfig: tlsCfg,
|
||||
}
|
||||
|
||||
return api.NewClient(config)
|
||||
}
|
||||
|
||||
func (c *Consul) GatherHealthCheck(acc telegraf.Accumulator, checks []*api.HealthCheck) {
|
||||
for _, check := range checks {
|
||||
record := make(map[string]interface{})
|
||||
tags := make(map[string]string)
|
||||
|
||||
record["check_id"] = check.CheckID
|
||||
record["check_name"] = check.Name
|
||||
record["service_id"] = check.ServiceID
|
||||
record["status"] = check.Status
|
||||
|
||||
tags["node"] = check.Node
|
||||
tags["service_name"] = check.ServiceName
|
||||
|
||||
acc.AddFields("consul_health_checks", record, tags)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Consul) Gather(acc telegraf.Accumulator) error {
|
||||
if c.client == nil {
|
||||
newClient, err := c.createAPIClient()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.client = newClient
|
||||
}
|
||||
|
||||
checks, _, err := c.client.Health().State("any", nil)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.GatherHealthCheck(acc, checks)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
inputs.Add("consul", func() telegraf.Input {
|
||||
return &Consul{}
|
||||
})
|
||||
}
|
||||
42
plugins/inputs/consul/consul_test.go
Normal file
42
plugins/inputs/consul/consul_test.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package consul
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
)
|
||||
|
||||
var sampleChecks = []*api.HealthCheck{
|
||||
&api.HealthCheck{
|
||||
Node: "localhost",
|
||||
CheckID: "foo.health123",
|
||||
Name: "foo.health",
|
||||
Status: "passing",
|
||||
Notes: "lorem ipsum",
|
||||
Output: "OK",
|
||||
ServiceID: "foo.123",
|
||||
ServiceName: "foo",
|
||||
},
|
||||
}
|
||||
|
||||
func TestGatherHealtCheck(t *testing.T) {
|
||||
expectedFields := map[string]interface{}{
|
||||
"check_id": "foo.health123",
|
||||
"check_name": "foo.health",
|
||||
"status": "passing",
|
||||
"service_id": "foo.123",
|
||||
}
|
||||
|
||||
expectedTags := map[string]string{
|
||||
"node": "localhost",
|
||||
"service_name": "foo",
|
||||
}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
|
||||
consul := &Consul{}
|
||||
consul.GatherHealthCheck(&acc, sampleChecks)
|
||||
|
||||
acc.AssertContainsTaggedFields(t, "consul_health_checks", expectedFields, expectedTags)
|
||||
}
|
||||
@@ -221,7 +221,7 @@ func (d *Docker) gatherContainer(
|
||||
defer cancel()
|
||||
r, err := d.client.ContainerStats(ctx, container.ID, false)
|
||||
if err != nil {
|
||||
log.Printf("Error getting docker stats: %s\n", err.Error())
|
||||
return fmt.Errorf("Error getting docker stats: %s", err.Error())
|
||||
}
|
||||
defer r.Close()
|
||||
dec := json.NewDecoder(r)
|
||||
|
||||
@@ -1,320 +1,307 @@
|
||||
# Elasticsearch plugin
|
||||
|
||||
#### Plugin arguments:
|
||||
- **servers** []string: list of one or more Elasticsearch servers
|
||||
- **local** boolean: If false, it will read the indices stats from all nodes
|
||||
- **cluster_health** boolean: If true, it will also obtain cluster level stats
|
||||
|
||||
#### Description
|
||||
# Elasticsearch input plugin
|
||||
|
||||
The [elasticsearch](https://www.elastic.co/) plugin queries endpoints to obtain
|
||||
[node](https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-nodes-stats.html)
|
||||
and optionally [cluster](https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-health.html) stats.
|
||||
|
||||
Example:
|
||||
### Configuration:
|
||||
|
||||
```
|
||||
[elasticsearch]
|
||||
|
||||
servers = ["http://localhost:9200"]
|
||||
|
||||
local = true
|
||||
|
||||
cluster_health = true
|
||||
[[inputs.elasticsearch]]
|
||||
servers = ["http://localhost:9200"]
|
||||
local = true
|
||||
cluster_health = true
|
||||
```
|
||||
|
||||
# Measurements
|
||||
#### cluster measurements (utilizes fields instead of single values):
|
||||
|
||||
contains `status`, `timed_out`, `number_of_nodes`, `number_of_data_nodes`,
|
||||
`active_primary_shards`, `active_shards`, `relocating_shards`,
|
||||
`initializing_shards`, `unassigned_shards` fields
|
||||
- elasticsearch_cluster_health
|
||||
|
||||
contains `status`, `number_of_shards`, `number_of_replicas`,
|
||||
`active_primary_shards`, `active_shards`, `relocating_shards`,
|
||||
`initializing_shards`, `unassigned_shards` fields
|
||||
- elasticsearch_indices
|
||||
|
||||
#### node measurements:
|
||||
### Measurements & Fields:
|
||||
|
||||
field data circuit breaker measurement names:
|
||||
- elasticsearch_breakers_fielddata_estimated_size_in_bytes value=0
|
||||
- elasticsearch_breakers_fielddata_overhead value=1.03
|
||||
- elasticsearch_breakers_fielddata_tripped value=0
|
||||
- elasticsearch_breakers_fielddata_limit_size_in_bytes value=623326003
|
||||
- elasticsearch_breakers_request_estimated_size_in_bytes value=0
|
||||
- elasticsearch_breakers_request_overhead value=1.0
|
||||
- elasticsearch_breakers_request_tripped value=0
|
||||
- elasticsearch_breakers_request_limit_size_in_bytes value=415550668
|
||||
- elasticsearch_breakers_parent_overhead value=1.0
|
||||
- elasticsearch_breakers_parent_tripped value=0
|
||||
- elasticsearch_breakers_parent_limit_size_in_bytes value=727213670
|
||||
- elasticsearch_breakers_parent_estimated_size_in_bytes value=0
|
||||
- elasticsearch_breakers
|
||||
- fielddata_estimated_size_in_bytes value=0
|
||||
- fielddata_overhead value=1.03
|
||||
- fielddata_tripped value=0
|
||||
- fielddata_limit_size_in_bytes value=623326003
|
||||
- request_estimated_size_in_bytes value=0
|
||||
- request_overhead value=1.0
|
||||
- request_tripped value=0
|
||||
- request_limit_size_in_bytes value=415550668
|
||||
- parent_overhead value=1.0
|
||||
- parent_tripped value=0
|
||||
- parent_limit_size_in_bytes value=727213670
|
||||
- parent_estimated_size_in_bytes value=0
|
||||
|
||||
File system information, data path, free disk space, read/write measurement names:
|
||||
- elasticsearch_fs_timestamp value=1436460392946
|
||||
- elasticsearch_fs_total_free_in_bytes value=16909316096
|
||||
- elasticsearch_fs_total_available_in_bytes value=15894814720
|
||||
- elasticsearch_fs_total_total_in_bytes value=19507089408
|
||||
- elasticsearch_fs
|
||||
- timestamp value=1436460392946
|
||||
- total_free_in_bytes value=16909316096
|
||||
- total_available_in_bytes value=15894814720
|
||||
- total_total_in_bytes value=19507089408
|
||||
|
||||
indices size, document count, indexing and deletion times, search times,
|
||||
field cache size, merges and flushes measurement names:
|
||||
- elasticsearch_indices_id_cache_memory_size_in_bytes value=0
|
||||
- elasticsearch_indices_completion_size_in_bytes value=0
|
||||
- elasticsearch_indices_suggest_total value=0
|
||||
- elasticsearch_indices_suggest_time_in_millis value=0
|
||||
- elasticsearch_indices_suggest_current value=0
|
||||
- elasticsearch_indices_query_cache_memory_size_in_bytes value=0
|
||||
- elasticsearch_indices_query_cache_evictions value=0
|
||||
- elasticsearch_indices_query_cache_hit_count value=0
|
||||
- elasticsearch_indices_query_cache_miss_count value=0
|
||||
- elasticsearch_indices_store_size_in_bytes value=37715234
|
||||
- elasticsearch_indices_store_throttle_time_in_millis value=215
|
||||
- elasticsearch_indices_merges_current_docs value=0
|
||||
- elasticsearch_indices_merges_current_size_in_bytes value=0
|
||||
- elasticsearch_indices_merges_total value=133
|
||||
- elasticsearch_indices_merges_total_time_in_millis value=21060
|
||||
- elasticsearch_indices_merges_total_docs value=203672
|
||||
- elasticsearch_indices_merges_total_size_in_bytes value=142900226
|
||||
- elasticsearch_indices_merges_current value=0
|
||||
- elasticsearch_indices_filter_cache_memory_size_in_bytes value=7384
|
||||
- elasticsearch_indices_filter_cache_evictions value=0
|
||||
- elasticsearch_indices_indexing_index_total value=84790
|
||||
- elasticsearch_indices_indexing_index_time_in_millis value=29680
|
||||
- elasticsearch_indices_indexing_index_current value=0
|
||||
- elasticsearch_indices_indexing_noop_update_total value=0
|
||||
- elasticsearch_indices_indexing_throttle_time_in_millis value=0
|
||||
- elasticsearch_indices_indexing_delete_tota value=13879
|
||||
- elasticsearch_indices_indexing_delete_time_in_millis value=1139
|
||||
- elasticsearch_indices_indexing_delete_current value=0
|
||||
- elasticsearch_indices_get_exists_time_in_millis value=0
|
||||
- elasticsearch_indices_get_missing_total value=1
|
||||
- elasticsearch_indices_get_missing_time_in_millis value=2
|
||||
- elasticsearch_indices_get_current value=0
|
||||
- elasticsearch_indices_get_total value=1
|
||||
- elasticsearch_indices_get_time_in_millis value=2
|
||||
- elasticsearch_indices_get_exists_total value=0
|
||||
- elasticsearch_indices_refresh_total value=1076
|
||||
- elasticsearch_indices_refresh_total_time_in_millis value=20078
|
||||
- elasticsearch_indices_percolate_current value=0
|
||||
- elasticsearch_indices_percolate_memory_size_in_bytes value=-1
|
||||
- elasticsearch_indices_percolate_queries value=0
|
||||
- elasticsearch_indices_percolate_total value=0
|
||||
- elasticsearch_indices_percolate_time_in_millis value=0
|
||||
- elasticsearch_indices_translog_operations value=17702
|
||||
- elasticsearch_indices_translog_size_in_bytes value=17
|
||||
- elasticsearch_indices_recovery_current_as_source value=0
|
||||
- elasticsearch_indices_recovery_current_as_target value=0
|
||||
- elasticsearch_indices_recovery_throttle_time_in_millis value=0
|
||||
- elasticsearch_indices_docs_count value=29652
|
||||
- elasticsearch_indices_docs_deleted value=5229
|
||||
- elasticsearch_indices_flush_total_time_in_millis value=2401
|
||||
- elasticsearch_indices_flush_total value=115
|
||||
- elasticsearch_indices_fielddata_memory_size_in_bytes value=12996
|
||||
- elasticsearch_indices_fielddata_evictions value=0
|
||||
- elasticsearch_indices_search_fetch_current value=0
|
||||
- elasticsearch_indices_search_open_contexts value=0
|
||||
- elasticsearch_indices_search_query_total value=1452
|
||||
- elasticsearch_indices_search_query_time_in_millis value=5695
|
||||
- elasticsearch_indices_search_query_current value=0
|
||||
- elasticsearch_indices_search_fetch_total value=414
|
||||
- elasticsearch_indices_search_fetch_time_in_millis value=146
|
||||
- elasticsearch_indices_warmer_current value=0
|
||||
- elasticsearch_indices_warmer_total value=2319
|
||||
- elasticsearch_indices_warmer_total_time_in_millis value=448
|
||||
- elasticsearch_indices_segments_count value=134
|
||||
- elasticsearch_indices_segments_memory_in_bytes value=1285212
|
||||
- elasticsearch_indices_segments_index_writer_memory_in_bytes value=0
|
||||
- elasticsearch_indices_segments_index_writer_max_memory_in_bytes value=172368955
|
||||
- elasticsearch_indices_segments_version_map_memory_in_bytes value=611844
|
||||
- elasticsearch_indices_segments_fixed_bit_set_memory_in_bytes value=0
|
||||
- elasticsearch_indices
|
||||
- id_cache_memory_size_in_bytes value=0
|
||||
- completion_size_in_bytes value=0
|
||||
- suggest_total value=0
|
||||
- suggest_time_in_millis value=0
|
||||
- suggest_current value=0
|
||||
- query_cache_memory_size_in_bytes value=0
|
||||
- query_cache_evictions value=0
|
||||
- query_cache_hit_count value=0
|
||||
- query_cache_miss_count value=0
|
||||
- store_size_in_bytes value=37715234
|
||||
- store_throttle_time_in_millis value=215
|
||||
- merges_current_docs value=0
|
||||
- merges_current_size_in_bytes value=0
|
||||
- merges_total value=133
|
||||
- merges_total_time_in_millis value=21060
|
||||
- merges_total_docs value=203672
|
||||
- merges_total_size_in_bytes value=142900226
|
||||
- merges_current value=0
|
||||
- filter_cache_memory_size_in_bytes value=7384
|
||||
- filter_cache_evictions value=0
|
||||
- indexing_index_total value=84790
|
||||
- indexing_index_time_in_millis value=29680
|
||||
- indexing_index_current value=0
|
||||
- indexing_noop_update_total value=0
|
||||
- indexing_throttle_time_in_millis value=0
|
||||
- indexing_delete_tota value=13879
|
||||
- indexing_delete_time_in_millis value=1139
|
||||
- indexing_delete_current value=0
|
||||
- get_exists_time_in_millis value=0
|
||||
- get_missing_total value=1
|
||||
- get_missing_time_in_millis value=2
|
||||
- get_current value=0
|
||||
- get_total value=1
|
||||
- get_time_in_millis value=2
|
||||
- get_exists_total value=0
|
||||
- refresh_total value=1076
|
||||
- refresh_total_time_in_millis value=20078
|
||||
- percolate_current value=0
|
||||
- percolate_memory_size_in_bytes value=-1
|
||||
- percolate_queries value=0
|
||||
- percolate_total value=0
|
||||
- percolate_time_in_millis value=0
|
||||
- translog_operations value=17702
|
||||
- translog_size_in_bytes value=17
|
||||
- recovery_current_as_source value=0
|
||||
- recovery_current_as_target value=0
|
||||
- recovery_throttle_time_in_millis value=0
|
||||
- docs_count value=29652
|
||||
- docs_deleted value=5229
|
||||
- flush_total_time_in_millis value=2401
|
||||
- flush_total value=115
|
||||
- fielddata_memory_size_in_bytes value=12996
|
||||
- fielddata_evictions value=0
|
||||
- search_fetch_current value=0
|
||||
- search_open_contexts value=0
|
||||
- search_query_total value=1452
|
||||
- search_query_time_in_millis value=5695
|
||||
- search_query_current value=0
|
||||
- search_fetch_total value=414
|
||||
- search_fetch_time_in_millis value=146
|
||||
- warmer_current value=0
|
||||
- warmer_total value=2319
|
||||
- warmer_total_time_in_millis value=448
|
||||
- segments_count value=134
|
||||
- segments_memory_in_bytes value=1285212
|
||||
- segments_index_writer_memory_in_bytes value=0
|
||||
- segments_index_writer_max_memory_in_bytes value=172368955
|
||||
- segments_version_map_memory_in_bytes value=611844
|
||||
- segments_fixed_bit_set_memory_in_bytes value=0
|
||||
|
||||
HTTP connection measurement names:
|
||||
- elasticsearch_http_current_open value=3
|
||||
- elasticsearch_http_total_opened value=3
|
||||
- elasticsearch_http
|
||||
- current_open value=3
|
||||
- total_opened value=3
|
||||
|
||||
JVM stats, memory pool information, garbage collection, buffer pools measurement names:
|
||||
- elasticsearch_jvm_timestamp value=1436460392945
|
||||
- elasticsearch_jvm_uptime_in_millis value=202245
|
||||
- elasticsearch_jvm_mem_non_heap_used_in_bytes value=39634576
|
||||
- elasticsearch_jvm_mem_non_heap_committed_in_bytes value=40841216
|
||||
- elasticsearch_jvm_mem_pools_young_max_in_bytes value=279183360
|
||||
- elasticsearch_jvm_mem_pools_young_peak_used_in_bytes value=71630848
|
||||
- elasticsearch_jvm_mem_pools_young_peak_max_in_bytes value=279183360
|
||||
- elasticsearch_jvm_mem_pools_young_used_in_bytes value=32685760
|
||||
- elasticsearch_jvm_mem_pools_survivor_peak_used_in_bytes value=8912888
|
||||
- elasticsearch_jvm_mem_pools_survivor_peak_max_in_bytes value=34865152
|
||||
- elasticsearch_jvm_mem_pools_survivor_used_in_bytes value=8912880
|
||||
- elasticsearch_jvm_mem_pools_survivor_max_in_bytes value=34865152
|
||||
- elasticsearch_jvm_mem_pools_old_peak_max_in_bytes value=724828160
|
||||
- elasticsearch_jvm_mem_pools_old_used_in_bytes value=11110928
|
||||
- elasticsearch_jvm_mem_pools_old_max_in_bytes value=724828160
|
||||
- elasticsearch_jvm_mem_pools_old_peak_used_in_bytes value=14354608
|
||||
- elasticsearch_jvm_mem_heap_used_in_bytes value=52709568
|
||||
- elasticsearch_jvm_mem_heap_used_percent value=5
|
||||
- elasticsearch_jvm_mem_heap_committed_in_bytes value=259522560
|
||||
- elasticsearch_jvm_mem_heap_max_in_bytes value=1038876672
|
||||
- elasticsearch_jvm_threads_peak_count value=45
|
||||
- elasticsearch_jvm_threads_count value=44
|
||||
- elasticsearch_jvm_gc_collectors_young_collection_count value=2
|
||||
- elasticsearch_jvm_gc_collectors_young_collection_time_in_millis value=98
|
||||
- elasticsearch_jvm_gc_collectors_old_collection_count value=1
|
||||
- elasticsearch_jvm_gc_collectors_old_collection_time_in_millis value=24
|
||||
- elasticsearch_jvm_buffer_pools_direct_count value=40
|
||||
- elasticsearch_jvm_buffer_pools_direct_used_in_bytes value=6304239
|
||||
- elasticsearch_jvm_buffer_pools_direct_total_capacity_in_bytes value=6304239
|
||||
- elasticsearch_jvm_buffer_pools_mapped_count value=0
|
||||
- elasticsearch_jvm_buffer_pools_mapped_used_in_bytes value=0
|
||||
- elasticsearch_jvm_buffer_pools_mapped_total_capacity_in_bytes value=0
|
||||
- elasticsearch_jvm
|
||||
- timestamp value=1436460392945
|
||||
- uptime_in_millis value=202245
|
||||
- mem_non_heap_used_in_bytes value=39634576
|
||||
- mem_non_heap_committed_in_bytes value=40841216
|
||||
- mem_pools_young_max_in_bytes value=279183360
|
||||
- mem_pools_young_peak_used_in_bytes value=71630848
|
||||
- mem_pools_young_peak_max_in_bytes value=279183360
|
||||
- mem_pools_young_used_in_bytes value=32685760
|
||||
- mem_pools_survivor_peak_used_in_bytes value=8912888
|
||||
- mem_pools_survivor_peak_max_in_bytes value=34865152
|
||||
- mem_pools_survivor_used_in_bytes value=8912880
|
||||
- mem_pools_survivor_max_in_bytes value=34865152
|
||||
- mem_pools_old_peak_max_in_bytes value=724828160
|
||||
- mem_pools_old_used_in_bytes value=11110928
|
||||
- mem_pools_old_max_in_bytes value=724828160
|
||||
- mem_pools_old_peak_used_in_bytes value=14354608
|
||||
- mem_heap_used_in_bytes value=52709568
|
||||
- mem_heap_used_percent value=5
|
||||
- mem_heap_committed_in_bytes value=259522560
|
||||
- mem_heap_max_in_bytes value=1038876672
|
||||
- threads_peak_count value=45
|
||||
- threads_count value=44
|
||||
- gc_collectors_young_collection_count value=2
|
||||
- gc_collectors_young_collection_time_in_millis value=98
|
||||
- gc_collectors_old_collection_count value=1
|
||||
- gc_collectors_old_collection_time_in_millis value=24
|
||||
- buffer_pools_direct_count value=40
|
||||
- buffer_pools_direct_used_in_bytes value=6304239
|
||||
- buffer_pools_direct_total_capacity_in_bytes value=6304239
|
||||
- buffer_pools_mapped_count value=0
|
||||
- buffer_pools_mapped_used_in_bytes value=0
|
||||
- buffer_pools_mapped_total_capacity_in_bytes value=0
|
||||
|
||||
TCP information measurement names:
|
||||
- elasticsearch_network_tcp_in_errs value=0
|
||||
- elasticsearch_network_tcp_passive_opens value=16
|
||||
- elasticsearch_network_tcp_curr_estab value=29
|
||||
- elasticsearch_network_tcp_in_segs value=113
|
||||
- elasticsearch_network_tcp_out_segs value=97
|
||||
- elasticsearch_network_tcp_retrans_segs value=0
|
||||
- elasticsearch_network_tcp_attempt_fails value=0
|
||||
- elasticsearch_network_tcp_active_opens value=13
|
||||
- elasticsearch_network_tcp_estab_resets value=0
|
||||
- elasticsearch_network_tcp_out_rsts value=0
|
||||
- elasticsearch_network
|
||||
- tcp_in_errs value=0
|
||||
- tcp_passive_opens value=16
|
||||
- tcp_curr_estab value=29
|
||||
- tcp_in_segs value=113
|
||||
- tcp_out_segs value=97
|
||||
- tcp_retrans_segs value=0
|
||||
- tcp_attempt_fails value=0
|
||||
- tcp_active_opens value=13
|
||||
- tcp_estab_resets value=0
|
||||
- tcp_out_rsts value=0
|
||||
|
||||
Operating system stats, load average, cpu, mem, swap measurement names:
|
||||
- elasticsearch_os_swap_used_in_bytes value=0
|
||||
- elasticsearch_os_swap_free_in_bytes value=487997440
|
||||
- elasticsearch_os_timestamp value=1436460392944
|
||||
- elasticsearch_os_uptime_in_millis value=25092
|
||||
- elasticsearch_os_cpu_sys value=0
|
||||
- elasticsearch_os_cpu_user value=0
|
||||
- elasticsearch_os_cpu_idle value=99
|
||||
- elasticsearch_os_cpu_usage value=0
|
||||
- elasticsearch_os_cpu_stolen value=0
|
||||
- elasticsearch_os_mem_free_percent value=74
|
||||
- elasticsearch_os_mem_used_percent value=25
|
||||
- elasticsearch_os_mem_actual_free_in_bytes value=1565470720
|
||||
- elasticsearch_os_mem_actual_used_in_bytes value=534159360
|
||||
- elasticsearch_os_mem_free_in_bytes value=477761536
|
||||
- elasticsearch_os_mem_used_in_bytes value=1621868544
|
||||
- elasticsearch_os
|
||||
- swap_used_in_bytes value=0
|
||||
- swap_free_in_bytes value=487997440
|
||||
- timestamp value=1436460392944
|
||||
- uptime_in_millis value=25092
|
||||
- cpu_sys value=0
|
||||
- cpu_user value=0
|
||||
- cpu_idle value=99
|
||||
- cpu_usage value=0
|
||||
- cpu_stolen value=0
|
||||
- mem_free_percent value=74
|
||||
- mem_used_percent value=25
|
||||
- mem_actual_free_in_bytes value=1565470720
|
||||
- mem_actual_used_in_bytes value=534159360
|
||||
- mem_free_in_bytes value=477761536
|
||||
- mem_used_in_bytes value=1621868544
|
||||
|
||||
Process statistics, memory consumption, cpu usage, open file descriptors measurement names:
|
||||
- elasticsearch_process_mem_resident_in_bytes value=246382592
|
||||
- elasticsearch_process_mem_share_in_bytes value=18747392
|
||||
- elasticsearch_process_mem_total_virtual_in_bytes value=4747890688
|
||||
- elasticsearch_process_timestamp value=1436460392945
|
||||
- elasticsearch_process_open_file_descriptors value=160
|
||||
- elasticsearch_process_cpu_total_in_millis value=15480
|
||||
- elasticsearch_process_cpu_percent value=2
|
||||
- elasticsearch_process_cpu_sys_in_millis value=1870
|
||||
- elasticsearch_process_cpu_user_in_millis value=13610
|
||||
- elasticsearch_process
|
||||
- mem_resident_in_bytes value=246382592
|
||||
- mem_share_in_bytes value=18747392
|
||||
- mem_total_virtual_in_bytes value=4747890688
|
||||
- timestamp value=1436460392945
|
||||
- open_file_descriptors value=160
|
||||
- cpu_total_in_millis value=15480
|
||||
- cpu_percent value=2
|
||||
- cpu_sys_in_millis value=1870
|
||||
- cpu_user_in_millis value=13610
|
||||
|
||||
Statistics about each thread pool, including current size, queue and rejected tasks measurement names:
|
||||
- elasticsearch_thread_pool_merge_threads value=6
|
||||
- elasticsearch_thread_pool_merge_queue value=4
|
||||
- elasticsearch_thread_pool_merge_active value=5
|
||||
- elasticsearch_thread_pool_merge_rejected value=2
|
||||
- elasticsearch_thread_pool_merge_largest value=5
|
||||
- elasticsearch_thread_pool_merge_completed value=1
|
||||
- elasticsearch_thread_pool_bulk_threads value=4
|
||||
- elasticsearch_thread_pool_bulk_queue value=5
|
||||
- elasticsearch_thread_pool_bulk_active value=7
|
||||
- elasticsearch_thread_pool_bulk_rejected value=3
|
||||
- elasticsearch_thread_pool_bulk_largest value=1
|
||||
- elasticsearch_thread_pool_bulk_completed value=4
|
||||
- elasticsearch_thread_pool_warmer_threads value=2
|
||||
- elasticsearch_thread_pool_warmer_queue value=7
|
||||
- elasticsearch_thread_pool_warmer_active value=3
|
||||
- elasticsearch_thread_pool_warmer_rejected value=2
|
||||
- elasticsearch_thread_pool_warmer_largest value=3
|
||||
- elasticsearch_thread_pool_warmer_completed value=1
|
||||
- elasticsearch_thread_pool_get_largest value=2
|
||||
- elasticsearch_thread_pool_get_completed value=1
|
||||
- elasticsearch_thread_pool_get_threads value=1
|
||||
- elasticsearch_thread_pool_get_queue value=8
|
||||
- elasticsearch_thread_pool_get_active value=4
|
||||
- elasticsearch_thread_pool_get_rejected value=3
|
||||
- elasticsearch_thread_pool_index_threads value=6
|
||||
- elasticsearch_thread_pool_index_queue value=8
|
||||
- elasticsearch_thread_pool_index_active value=4
|
||||
- elasticsearch_thread_pool_index_rejected value=2
|
||||
- elasticsearch_thread_pool_index_largest value=3
|
||||
- elasticsearch_thread_pool_index_completed value=6
|
||||
- elasticsearch_thread_pool_suggest_threads value=2
|
||||
- elasticsearch_thread_pool_suggest_queue value=7
|
||||
- elasticsearch_thread_pool_suggest_active value=2
|
||||
- elasticsearch_thread_pool_suggest_rejected value=1
|
||||
- elasticsearch_thread_pool_suggest_largest value=8
|
||||
- elasticsearch_thread_pool_suggest_completed value=3
|
||||
- elasticsearch_thread_pool_fetch_shard_store_queue value=7
|
||||
- elasticsearch_thread_pool_fetch_shard_store_active value=4
|
||||
- elasticsearch_thread_pool_fetch_shard_store_rejected value=2
|
||||
- elasticsearch_thread_pool_fetch_shard_store_largest value=4
|
||||
- elasticsearch_thread_pool_fetch_shard_store_completed value=1
|
||||
- elasticsearch_thread_pool_fetch_shard_store_threads value=1
|
||||
- elasticsearch_thread_pool_management_threads value=2
|
||||
- elasticsearch_thread_pool_management_queue value=3
|
||||
- elasticsearch_thread_pool_management_active value=1
|
||||
- elasticsearch_thread_pool_management_rejected value=6
|
||||
- elasticsearch_thread_pool_management_largest value=2
|
||||
- elasticsearch_thread_pool_management_completed value=22
|
||||
- elasticsearch_thread_pool_percolate_queue value=23
|
||||
- elasticsearch_thread_pool_percolate_active value=13
|
||||
- elasticsearch_thread_pool_percolate_rejected value=235
|
||||
- elasticsearch_thread_pool_percolate_largest value=23
|
||||
- elasticsearch_thread_pool_percolate_completed value=33
|
||||
- elasticsearch_thread_pool_percolate_threads value=123
|
||||
- elasticsearch_thread_pool_listener_active value=4
|
||||
- elasticsearch_thread_pool_listener_rejected value=8
|
||||
- elasticsearch_thread_pool_listener_largest value=1
|
||||
- elasticsearch_thread_pool_listener_completed value=1
|
||||
- elasticsearch_thread_pool_listener_threads value=1
|
||||
- elasticsearch_thread_pool_listener_queue value=2
|
||||
- elasticsearch_thread_pool_search_rejected value=7
|
||||
- elasticsearch_thread_pool_search_largest value=2
|
||||
- elasticsearch_thread_pool_search_completed value=4
|
||||
- elasticsearch_thread_pool_search_threads value=5
|
||||
- elasticsearch_thread_pool_search_queue value=7
|
||||
- elasticsearch_thread_pool_search_active value=2
|
||||
- elasticsearch_thread_pool_fetch_shard_started_threads value=3
|
||||
- elasticsearch_thread_pool_fetch_shard_started_queue value=1
|
||||
- elasticsearch_thread_pool_fetch_shard_started_active value=5
|
||||
- elasticsearch_thread_pool_fetch_shard_started_rejected value=6
|
||||
- elasticsearch_thread_pool_fetch_shard_started_largest value=4
|
||||
- elasticsearch_thread_pool_fetch_shard_started_completed value=54
|
||||
- elasticsearch_thread_pool_refresh_rejected value=4
|
||||
- elasticsearch_thread_pool_refresh_largest value=8
|
||||
- elasticsearch_thread_pool_refresh_completed value=3
|
||||
- elasticsearch_thread_pool_refresh_threads value=23
|
||||
- elasticsearch_thread_pool_refresh_queue value=7
|
||||
- elasticsearch_thread_pool_refresh_active value=3
|
||||
- elasticsearch_thread_pool_optimize_threads value=3
|
||||
- elasticsearch_thread_pool_optimize_queue value=4
|
||||
- elasticsearch_thread_pool_optimize_active value=1
|
||||
- elasticsearch_thread_pool_optimize_rejected value=2
|
||||
- elasticsearch_thread_pool_optimize_largest value=7
|
||||
- elasticsearch_thread_pool_optimize_completed value=3
|
||||
- elasticsearch_thread_pool_snapshot_largest value=1
|
||||
- elasticsearch_thread_pool_snapshot_completed value=0
|
||||
- elasticsearch_thread_pool_snapshot_threads value=8
|
||||
- elasticsearch_thread_pool_snapshot_queue value=5
|
||||
- elasticsearch_thread_pool_snapshot_active value=6
|
||||
- elasticsearch_thread_pool_snapshot_rejected value=2
|
||||
- elasticsearch_thread_pool_generic_threads value=1
|
||||
- elasticsearch_thread_pool_generic_queue value=4
|
||||
- elasticsearch_thread_pool_generic_active value=6
|
||||
- elasticsearch_thread_pool_generic_rejected value=3
|
||||
- elasticsearch_thread_pool_generic_largest value=2
|
||||
- elasticsearch_thread_pool_generic_completed value=27
|
||||
- elasticsearch_thread_pool_flush_threads value=3
|
||||
- elasticsearch_thread_pool_flush_queue value=8
|
||||
- elasticsearch_thread_pool_flush_active value=0
|
||||
- elasticsearch_thread_pool_flush_rejected value=1
|
||||
- elasticsearch_thread_pool_flush_largest value=5
|
||||
- elasticsearch_thread_pool_flush_completed value=3
|
||||
- elasticsearch_thread_pool
|
||||
- merge_threads value=6
|
||||
- merge_queue value=4
|
||||
- merge_active value=5
|
||||
- merge_rejected value=2
|
||||
- merge_largest value=5
|
||||
- merge_completed value=1
|
||||
- bulk_threads value=4
|
||||
- bulk_queue value=5
|
||||
- bulk_active value=7
|
||||
- bulk_rejected value=3
|
||||
- bulk_largest value=1
|
||||
- bulk_completed value=4
|
||||
- warmer_threads value=2
|
||||
- warmer_queue value=7
|
||||
- warmer_active value=3
|
||||
- warmer_rejected value=2
|
||||
- warmer_largest value=3
|
||||
- warmer_completed value=1
|
||||
- get_largest value=2
|
||||
- get_completed value=1
|
||||
- get_threads value=1
|
||||
- get_queue value=8
|
||||
- get_active value=4
|
||||
- get_rejected value=3
|
||||
- index_threads value=6
|
||||
- index_queue value=8
|
||||
- index_active value=4
|
||||
- index_rejected value=2
|
||||
- index_largest value=3
|
||||
- index_completed value=6
|
||||
- suggest_threads value=2
|
||||
- suggest_queue value=7
|
||||
- suggest_active value=2
|
||||
- suggest_rejected value=1
|
||||
- suggest_largest value=8
|
||||
- suggest_completed value=3
|
||||
- fetch_shard_store_queue value=7
|
||||
- fetch_shard_store_active value=4
|
||||
- fetch_shard_store_rejected value=2
|
||||
- fetch_shard_store_largest value=4
|
||||
- fetch_shard_store_completed value=1
|
||||
- fetch_shard_store_threads value=1
|
||||
- management_threads value=2
|
||||
- management_queue value=3
|
||||
- management_active value=1
|
||||
- management_rejected value=6
|
||||
- management_largest value=2
|
||||
- management_completed value=22
|
||||
- percolate_queue value=23
|
||||
- percolate_active value=13
|
||||
- percolate_rejected value=235
|
||||
- percolate_largest value=23
|
||||
- percolate_completed value=33
|
||||
- percolate_threads value=123
|
||||
- listener_active value=4
|
||||
- listener_rejected value=8
|
||||
- listener_largest value=1
|
||||
- listener_completed value=1
|
||||
- listener_threads value=1
|
||||
- listener_queue value=2
|
||||
- search_rejected value=7
|
||||
- search_largest value=2
|
||||
- search_completed value=4
|
||||
- search_threads value=5
|
||||
- search_queue value=7
|
||||
- search_active value=2
|
||||
- fetch_shard_started_threads value=3
|
||||
- fetch_shard_started_queue value=1
|
||||
- fetch_shard_started_active value=5
|
||||
- fetch_shard_started_rejected value=6
|
||||
- fetch_shard_started_largest value=4
|
||||
- fetch_shard_started_completed value=54
|
||||
- refresh_rejected value=4
|
||||
- refresh_largest value=8
|
||||
- refresh_completed value=3
|
||||
- refresh_threads value=23
|
||||
- refresh_queue value=7
|
||||
- refresh_active value=3
|
||||
- optimize_threads value=3
|
||||
- optimize_queue value=4
|
||||
- optimize_active value=1
|
||||
- optimize_rejected value=2
|
||||
- optimize_largest value=7
|
||||
- optimize_completed value=3
|
||||
- snapshot_largest value=1
|
||||
- snapshot_completed value=0
|
||||
- snapshot_threads value=8
|
||||
- snapshot_queue value=5
|
||||
- snapshot_active value=6
|
||||
- snapshot_rejected value=2
|
||||
- generic_threads value=1
|
||||
- generic_queue value=4
|
||||
- generic_active value=6
|
||||
- generic_rejected value=3
|
||||
- generic_largest value=2
|
||||
- generic_completed value=27
|
||||
- flush_threads value=3
|
||||
- flush_queue value=8
|
||||
- flush_active value=0
|
||||
- flush_rejected value=1
|
||||
- flush_largest value=5
|
||||
- flush_completed value=3
|
||||
|
||||
Transport statistics about sent and received bytes in cluster communication measurement names:
|
||||
- elasticsearch_transport_server_open value=13
|
||||
- elasticsearch_transport_rx_count value=6
|
||||
- elasticsearch_transport_rx_size_in_bytes value=1380
|
||||
- elasticsearch_transport_tx_count value=6
|
||||
- elasticsearch_transport_tx_size_in_bytes value=1380
|
||||
- elasticsearch_transport
|
||||
- server_open value=13
|
||||
- rx_count value=6
|
||||
- rx_size_in_bytes value=1380
|
||||
- tx_count value=6
|
||||
- tx_size_in_bytes value=1380
|
||||
|
||||
@@ -2,14 +2,13 @@ package elasticsearch
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/internal/errchan"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
jsonparser "github.com/influxdata/telegraf/plugins/parsers/json"
|
||||
)
|
||||
@@ -102,7 +101,7 @@ func (e *Elasticsearch) Description() string {
|
||||
// Gather reads the stats from Elasticsearch and writes it to the
|
||||
// Accumulator.
|
||||
func (e *Elasticsearch) Gather(acc telegraf.Accumulator) error {
|
||||
errChan := make(chan error, len(e.Servers))
|
||||
errChan := errchan.New(len(e.Servers))
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(e.Servers))
|
||||
|
||||
@@ -116,7 +115,7 @@ func (e *Elasticsearch) Gather(acc telegraf.Accumulator) error {
|
||||
url = s + statsPath
|
||||
}
|
||||
if err := e.gatherNodeStats(url, acc); err != nil {
|
||||
errChan <- err
|
||||
errChan.C <- err
|
||||
return
|
||||
}
|
||||
if e.ClusterHealth {
|
||||
@@ -126,17 +125,7 @@ func (e *Elasticsearch) Gather(acc telegraf.Accumulator) error {
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errChan)
|
||||
// Get all errors and return them as one giant error
|
||||
errStrings := []string{}
|
||||
for err := range errChan {
|
||||
errStrings = append(errStrings, err.Error())
|
||||
}
|
||||
|
||||
if len(errStrings) == 0 {
|
||||
return nil
|
||||
}
|
||||
return errors.New(strings.Join(errStrings, "\n"))
|
||||
return errChan.Error()
|
||||
}
|
||||
|
||||
func (e *Elasticsearch) gatherNodeStats(url string, acc telegraf.Accumulator) error {
|
||||
|
||||
@@ -6,14 +6,20 @@ Please also see: [Telegraf Input Data Formats](https://github.com/influxdata/tel
|
||||
|
||||
#### Configuration
|
||||
|
||||
In this example a script called ```/tmp/test.sh``` and a script called ```/tmp/test2.sh```
|
||||
are configured for ```[[inputs.exec]]``` in JSON format.
|
||||
In this example a script called ```/tmp/test.sh```, a script called ```/tmp/test2.sh```, and
|
||||
all scripts matching glob pattern ```/tmp/collect_*.sh``` are configured for ```[[inputs.exec]]```
|
||||
in JSON format. Glob patterns are matched on every run, so adding new scripts that match the pattern
|
||||
will cause them to be picked up immediately.
|
||||
|
||||
```
|
||||
```toml
|
||||
# Read flattened metrics from one or more commands that output JSON to stdout
|
||||
[[inputs.exec]]
|
||||
# Shell/commands array
|
||||
commands = ["/tmp/test.sh", "/tmp/test2.sh"]
|
||||
# Full command line to executable with parameters, or a glob pattern to run all matching files.
|
||||
commands = ["/tmp/test.sh", "/tmp/test2.sh", "/tmp/collect_*.sh"]
|
||||
|
||||
## Timeout for each command to complete.
|
||||
timeout = "5s"
|
||||
|
||||
# Data format to consume.
|
||||
# NOTE json only reads numerical measurements, strings and booleans are ignored.
|
||||
@@ -21,26 +27,6 @@ are configured for ```[[inputs.exec]]``` in JSON format.
|
||||
|
||||
# measurement name suffix (for separating different commands)
|
||||
name_suffix = "_mycollector"
|
||||
|
||||
## Below configuration will be used for data_format = "graphite", can be ignored for other data_format
|
||||
## If matching multiple measurement files, this string will be used to join the matched values.
|
||||
#separator = "."
|
||||
|
||||
## Each template line requires a template pattern. It can have an optional
|
||||
## filter before the template and separated by spaces. It can also have optional extra
|
||||
## tags following the template. Multiple tags should be separated by commas and no spaces
|
||||
## similar to the line protocol format. The can be only one default template.
|
||||
## Templates support below format:
|
||||
## 1. filter + template
|
||||
## 2. filter + template + extra tag
|
||||
## 3. filter + template with field key
|
||||
## 4. default template
|
||||
#templates = [
|
||||
# "*.app env.service.resource.measurement",
|
||||
# "stats.* .host.measurement* region=us-west,agent=sensu",
|
||||
# "stats2.* .host.measurement.field",
|
||||
# "measurement*"
|
||||
#]
|
||||
```
|
||||
|
||||
Other options for modifying the measurement names are:
|
||||
@@ -79,7 +65,7 @@ in influx line-protocol format.
|
||||
|
||||
#### Configuration
|
||||
|
||||
```
|
||||
```toml
|
||||
[[inputs.exec]]
|
||||
# Shell/commands array
|
||||
# compatible with old version
|
||||
@@ -87,6 +73,9 @@ in influx line-protocol format.
|
||||
# command = "/usr/bin/line_protocol_collector"
|
||||
commands = ["/usr/bin/line_protocol_collector","/tmp/test2.sh"]
|
||||
|
||||
## Timeout for each command to complete.
|
||||
timeout = "5s"
|
||||
|
||||
# Data format to consume.
|
||||
# NOTE json only reads numerical measurements, strings and booleans are ignored.
|
||||
data_format = "influx"
|
||||
@@ -120,12 +109,16 @@ We can also change the data_format to "graphite" to use the metrics collecting s
|
||||
In this example a script called /tmp/test.sh and a script called /tmp/test2.sh are configured for [[inputs.exec]] in graphite format.
|
||||
|
||||
#### Configuration
|
||||
```
|
||||
|
||||
```toml
|
||||
# Read flattened metrics from one or more commands that output JSON to stdout
|
||||
[[inputs.exec]]
|
||||
# Shell/commands array
|
||||
commands = ["/tmp/test.sh","/tmp/test2.sh"]
|
||||
|
||||
## Timeout for each command to complete.
|
||||
timeout = "5s"
|
||||
|
||||
# Data format to consume.
|
||||
# NOTE json only reads numerical measurements, strings and booleans are ignored.
|
||||
data_format = "graphite"
|
||||
@@ -180,4 +173,3 @@ 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)
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
@@ -12,6 +14,7 @@ import (
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/internal"
|
||||
"github.com/influxdata/telegraf/internal/errchan"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
"github.com/influxdata/telegraf/plugins/parsers"
|
||||
"github.com/influxdata/telegraf/plugins/parsers/nagios"
|
||||
@@ -19,7 +22,11 @@ import (
|
||||
|
||||
const sampleConfig = `
|
||||
## Commands array
|
||||
commands = ["/tmp/test.sh", "/usr/bin/mycollector --foo=bar"]
|
||||
commands = [
|
||||
"/tmp/test.sh",
|
||||
"/usr/bin/mycollector --foo=bar",
|
||||
"/tmp/collect_*.sh"
|
||||
]
|
||||
|
||||
## Timeout for each command to complete.
|
||||
timeout = "5s"
|
||||
@@ -150,23 +157,45 @@ func (e *Exec) Gather(acc telegraf.Accumulator) error {
|
||||
e.Command = ""
|
||||
}
|
||||
|
||||
e.errChan = make(chan error, len(e.Commands))
|
||||
commands := make([]string, 0, len(e.Commands))
|
||||
for _, pattern := range e.Commands {
|
||||
cmdAndArgs := strings.SplitN(pattern, " ", 2)
|
||||
if len(cmdAndArgs) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
e.wg.Add(len(e.Commands))
|
||||
for _, command := range e.Commands {
|
||||
matches, err := filepath.Glob(cmdAndArgs[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(matches) == 0 {
|
||||
// There were no matches with the glob pattern, so let's assume
|
||||
// that the command is in PATH and just run it as it is
|
||||
commands = append(commands, pattern)
|
||||
} else {
|
||||
// There were matches, so we'll append each match together with
|
||||
// the arguments to the commands slice
|
||||
for _, match := range matches {
|
||||
if len(cmdAndArgs) == 1 {
|
||||
commands = append(commands, match)
|
||||
} else {
|
||||
commands = append(commands,
|
||||
strings.Join([]string{match, cmdAndArgs[1]}, " "))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errChan := errchan.New(len(commands))
|
||||
e.errChan = errChan.C
|
||||
|
||||
e.wg.Add(len(commands))
|
||||
for _, command := range commands {
|
||||
go e.ProcessCommand(command, acc)
|
||||
}
|
||||
e.wg.Wait()
|
||||
|
||||
select {
|
||||
default:
|
||||
close(e.errChan)
|
||||
return nil
|
||||
case err := <-e.errChan:
|
||||
close(e.errChan)
|
||||
return err
|
||||
}
|
||||
|
||||
return errChan.Error()
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -169,3 +169,51 @@ func TestLineProtocolParseMultiple(t *testing.T) {
|
||||
acc.AssertContainsTaggedFields(t, "cpu", fields, tags)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecCommandWithGlob(t *testing.T) {
|
||||
parser, _ := parsers.NewValueParser("metric", "string", nil)
|
||||
e := NewExec()
|
||||
e.Commands = []string{"/bin/ech* metric_value"}
|
||||
e.SetParser(parser)
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err := e.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
fields := map[string]interface{}{
|
||||
"value": "metric_value",
|
||||
}
|
||||
acc.AssertContainsFields(t, "metric", fields)
|
||||
}
|
||||
|
||||
func TestExecCommandWithoutGlob(t *testing.T) {
|
||||
parser, _ := parsers.NewValueParser("metric", "string", nil)
|
||||
e := NewExec()
|
||||
e.Commands = []string{"/bin/echo metric_value"}
|
||||
e.SetParser(parser)
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err := e.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
fields := map[string]interface{}{
|
||||
"value": "metric_value",
|
||||
}
|
||||
acc.AssertContainsFields(t, "metric", fields)
|
||||
}
|
||||
|
||||
func TestExecCommandWithoutGlobAndPath(t *testing.T) {
|
||||
parser, _ := parsers.NewValueParser("metric", "string", nil)
|
||||
e := NewExec()
|
||||
e.Commands = []string{"echo metric_value"}
|
||||
e.SetParser(parser)
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err := e.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
fields := map[string]interface{}{
|
||||
"value": "metric_value",
|
||||
}
|
||||
acc.AssertContainsFields(t, "metric", fields)
|
||||
}
|
||||
|
||||
@@ -91,193 +91,12 @@ func (gh *GithubWebhooks) eventHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func newCommitComment(data []byte) (Event, error) {
|
||||
commitCommentStruct := CommitCommentEvent{}
|
||||
err := json.Unmarshal(data, &commitCommentStruct)
|
||||
func generateEvent(data []byte, event Event) (Event, error) {
|
||||
err := json.Unmarshal(data, event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return commitCommentStruct, nil
|
||||
}
|
||||
|
||||
func newCreate(data []byte) (Event, error) {
|
||||
createStruct := CreateEvent{}
|
||||
err := json.Unmarshal(data, &createStruct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return createStruct, nil
|
||||
}
|
||||
|
||||
func newDelete(data []byte) (Event, error) {
|
||||
deleteStruct := DeleteEvent{}
|
||||
err := json.Unmarshal(data, &deleteStruct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return deleteStruct, nil
|
||||
}
|
||||
|
||||
func newDeployment(data []byte) (Event, error) {
|
||||
deploymentStruct := DeploymentEvent{}
|
||||
err := json.Unmarshal(data, &deploymentStruct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return deploymentStruct, nil
|
||||
}
|
||||
|
||||
func newDeploymentStatus(data []byte) (Event, error) {
|
||||
deploymentStatusStruct := DeploymentStatusEvent{}
|
||||
err := json.Unmarshal(data, &deploymentStatusStruct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return deploymentStatusStruct, nil
|
||||
}
|
||||
|
||||
func newFork(data []byte) (Event, error) {
|
||||
forkStruct := ForkEvent{}
|
||||
err := json.Unmarshal(data, &forkStruct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return forkStruct, nil
|
||||
}
|
||||
|
||||
func newGollum(data []byte) (Event, error) {
|
||||
gollumStruct := GollumEvent{}
|
||||
err := json.Unmarshal(data, &gollumStruct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return gollumStruct, nil
|
||||
}
|
||||
|
||||
func newIssueComment(data []byte) (Event, error) {
|
||||
issueCommentStruct := IssueCommentEvent{}
|
||||
err := json.Unmarshal(data, &issueCommentStruct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return issueCommentStruct, nil
|
||||
}
|
||||
|
||||
func newIssues(data []byte) (Event, error) {
|
||||
issuesStruct := IssuesEvent{}
|
||||
err := json.Unmarshal(data, &issuesStruct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return issuesStruct, nil
|
||||
}
|
||||
|
||||
func newMember(data []byte) (Event, error) {
|
||||
memberStruct := MemberEvent{}
|
||||
err := json.Unmarshal(data, &memberStruct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return memberStruct, nil
|
||||
}
|
||||
|
||||
func newMembership(data []byte) (Event, error) {
|
||||
membershipStruct := MembershipEvent{}
|
||||
err := json.Unmarshal(data, &membershipStruct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return membershipStruct, nil
|
||||
}
|
||||
|
||||
func newPageBuild(data []byte) (Event, error) {
|
||||
pageBuildEvent := PageBuildEvent{}
|
||||
err := json.Unmarshal(data, &pageBuildEvent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pageBuildEvent, nil
|
||||
}
|
||||
|
||||
func newPublic(data []byte) (Event, error) {
|
||||
publicEvent := PublicEvent{}
|
||||
err := json.Unmarshal(data, &publicEvent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return publicEvent, nil
|
||||
}
|
||||
|
||||
func newPullRequest(data []byte) (Event, error) {
|
||||
pullRequestStruct := PullRequestEvent{}
|
||||
err := json.Unmarshal(data, &pullRequestStruct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pullRequestStruct, nil
|
||||
}
|
||||
|
||||
func newPullRequestReviewComment(data []byte) (Event, error) {
|
||||
pullRequestReviewCommentStruct := PullRequestReviewCommentEvent{}
|
||||
err := json.Unmarshal(data, &pullRequestReviewCommentStruct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pullRequestReviewCommentStruct, nil
|
||||
}
|
||||
|
||||
func newPush(data []byte) (Event, error) {
|
||||
pushStruct := PushEvent{}
|
||||
err := json.Unmarshal(data, &pushStruct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pushStruct, nil
|
||||
}
|
||||
|
||||
func newRelease(data []byte) (Event, error) {
|
||||
releaseStruct := ReleaseEvent{}
|
||||
err := json.Unmarshal(data, &releaseStruct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return releaseStruct, nil
|
||||
}
|
||||
|
||||
func newRepository(data []byte) (Event, error) {
|
||||
repositoryStruct := RepositoryEvent{}
|
||||
err := json.Unmarshal(data, &repositoryStruct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return repositoryStruct, nil
|
||||
}
|
||||
|
||||
func newStatus(data []byte) (Event, error) {
|
||||
statusStruct := StatusEvent{}
|
||||
err := json.Unmarshal(data, &statusStruct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return statusStruct, nil
|
||||
}
|
||||
|
||||
func newTeamAdd(data []byte) (Event, error) {
|
||||
teamAddStruct := TeamAddEvent{}
|
||||
err := json.Unmarshal(data, &teamAddStruct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return teamAddStruct, nil
|
||||
}
|
||||
|
||||
func newWatch(data []byte) (Event, error) {
|
||||
watchStruct := WatchEvent{}
|
||||
err := json.Unmarshal(data, &watchStruct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return watchStruct, nil
|
||||
return event, nil
|
||||
}
|
||||
|
||||
type newEventError struct {
|
||||
@@ -288,51 +107,51 @@ func (e *newEventError) Error() string {
|
||||
return e.s
|
||||
}
|
||||
|
||||
func NewEvent(r []byte, t string) (Event, error) {
|
||||
log.Printf("New %v event recieved", t)
|
||||
switch t {
|
||||
func NewEvent(data []byte, name string) (Event, error) {
|
||||
log.Printf("New %v event received", name)
|
||||
switch name {
|
||||
case "commit_comment":
|
||||
return newCommitComment(r)
|
||||
return generateEvent(data, &CommitCommentEvent{})
|
||||
case "create":
|
||||
return newCreate(r)
|
||||
return generateEvent(data, &CreateEvent{})
|
||||
case "delete":
|
||||
return newDelete(r)
|
||||
return generateEvent(data, &DeleteEvent{})
|
||||
case "deployment":
|
||||
return newDeployment(r)
|
||||
return generateEvent(data, &DeploymentEvent{})
|
||||
case "deployment_status":
|
||||
return newDeploymentStatus(r)
|
||||
return generateEvent(data, &DeploymentStatusEvent{})
|
||||
case "fork":
|
||||
return newFork(r)
|
||||
return generateEvent(data, &ForkEvent{})
|
||||
case "gollum":
|
||||
return newGollum(r)
|
||||
return generateEvent(data, &GollumEvent{})
|
||||
case "issue_comment":
|
||||
return newIssueComment(r)
|
||||
return generateEvent(data, &IssueCommentEvent{})
|
||||
case "issues":
|
||||
return newIssues(r)
|
||||
return generateEvent(data, &IssuesEvent{})
|
||||
case "member":
|
||||
return newMember(r)
|
||||
return generateEvent(data, &MemberEvent{})
|
||||
case "membership":
|
||||
return newMembership(r)
|
||||
return generateEvent(data, &MembershipEvent{})
|
||||
case "page_build":
|
||||
return newPageBuild(r)
|
||||
return generateEvent(data, &PageBuildEvent{})
|
||||
case "public":
|
||||
return newPublic(r)
|
||||
return generateEvent(data, &PublicEvent{})
|
||||
case "pull_request":
|
||||
return newPullRequest(r)
|
||||
return generateEvent(data, &PullRequestEvent{})
|
||||
case "pull_request_review_comment":
|
||||
return newPullRequestReviewComment(r)
|
||||
return generateEvent(data, &PullRequestReviewCommentEvent{})
|
||||
case "push":
|
||||
return newPush(r)
|
||||
return generateEvent(data, &PushEvent{})
|
||||
case "release":
|
||||
return newRelease(r)
|
||||
return generateEvent(data, &ReleaseEvent{})
|
||||
case "repository":
|
||||
return newRepository(r)
|
||||
return generateEvent(data, &RepositoryEvent{})
|
||||
case "status":
|
||||
return newStatus(r)
|
||||
return generateEvent(data, &StatusEvent{})
|
||||
case "team_add":
|
||||
return newTeamAdd(r)
|
||||
return generateEvent(data, &TeamAddEvent{})
|
||||
case "watch":
|
||||
return newWatch(r)
|
||||
return generateEvent(data, &WatchEvent{})
|
||||
}
|
||||
return nil, &newEventError{"Not a recgonized event type"}
|
||||
return nil, &newEventError{"Not a recognized event type"}
|
||||
}
|
||||
|
||||
@@ -7,231 +7,89 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCommitCommentEvent(t *testing.T) {
|
||||
func GithubWebhookRequest(event string, jsonString string, t *testing.T) {
|
||||
gh := NewGithubWebhooks()
|
||||
jsonString := CommitCommentEventJSON()
|
||||
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
|
||||
req.Header.Add("X-Github-Event", "commit_comment")
|
||||
req.Header.Add("X-Github-Event", event)
|
||||
w := httptest.NewRecorder()
|
||||
gh.eventHandler(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
|
||||
t.Errorf("POST "+event+" returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitCommentEvent(t *testing.T) {
|
||||
GithubWebhookRequest("commit_comment", CommitCommentEventJSON(), t)
|
||||
}
|
||||
|
||||
func TestDeleteEvent(t *testing.T) {
|
||||
gh := NewGithubWebhooks()
|
||||
jsonString := DeleteEventJSON()
|
||||
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
|
||||
req.Header.Add("X-Github-Event", "delete")
|
||||
w := httptest.NewRecorder()
|
||||
gh.eventHandler(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
|
||||
}
|
||||
GithubWebhookRequest("delete", DeleteEventJSON(), t)
|
||||
}
|
||||
|
||||
func TestDeploymentEvent(t *testing.T) {
|
||||
gh := NewGithubWebhooks()
|
||||
jsonString := DeploymentEventJSON()
|
||||
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
|
||||
req.Header.Add("X-Github-Event", "deployment")
|
||||
w := httptest.NewRecorder()
|
||||
gh.eventHandler(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
|
||||
}
|
||||
GithubWebhookRequest("deployment", DeploymentEventJSON(), t)
|
||||
}
|
||||
|
||||
func TestDeploymentStatusEvent(t *testing.T) {
|
||||
gh := NewGithubWebhooks()
|
||||
jsonString := DeploymentStatusEventJSON()
|
||||
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
|
||||
req.Header.Add("X-Github-Event", "deployment_status")
|
||||
w := httptest.NewRecorder()
|
||||
gh.eventHandler(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
|
||||
}
|
||||
GithubWebhookRequest("deployment_status", DeploymentStatusEventJSON(), t)
|
||||
}
|
||||
|
||||
func TestForkEvent(t *testing.T) {
|
||||
gh := NewGithubWebhooks()
|
||||
jsonString := ForkEventJSON()
|
||||
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
|
||||
req.Header.Add("X-Github-Event", "fork")
|
||||
w := httptest.NewRecorder()
|
||||
gh.eventHandler(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
|
||||
}
|
||||
GithubWebhookRequest("fork", ForkEventJSON(), t)
|
||||
}
|
||||
|
||||
func TestGollumEvent(t *testing.T) {
|
||||
gh := NewGithubWebhooks()
|
||||
jsonString := GollumEventJSON()
|
||||
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
|
||||
req.Header.Add("X-Github-Event", "gollum")
|
||||
w := httptest.NewRecorder()
|
||||
gh.eventHandler(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
|
||||
}
|
||||
GithubWebhookRequest("gollum", GollumEventJSON(), t)
|
||||
}
|
||||
|
||||
func TestIssueCommentEvent(t *testing.T) {
|
||||
gh := NewGithubWebhooks()
|
||||
jsonString := IssueCommentEventJSON()
|
||||
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
|
||||
req.Header.Add("X-Github-Event", "issue_comment")
|
||||
w := httptest.NewRecorder()
|
||||
gh.eventHandler(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
|
||||
}
|
||||
GithubWebhookRequest("issue_comment", IssueCommentEventJSON(), t)
|
||||
}
|
||||
|
||||
func TestIssuesEvent(t *testing.T) {
|
||||
gh := NewGithubWebhooks()
|
||||
jsonString := IssuesEventJSON()
|
||||
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
|
||||
req.Header.Add("X-Github-Event", "issues")
|
||||
w := httptest.NewRecorder()
|
||||
gh.eventHandler(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
|
||||
}
|
||||
GithubWebhookRequest("issues", IssuesEventJSON(), t)
|
||||
}
|
||||
|
||||
func TestMemberEvent(t *testing.T) {
|
||||
gh := NewGithubWebhooks()
|
||||
jsonString := MemberEventJSON()
|
||||
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
|
||||
req.Header.Add("X-Github-Event", "member")
|
||||
w := httptest.NewRecorder()
|
||||
gh.eventHandler(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
|
||||
}
|
||||
GithubWebhookRequest("member", MemberEventJSON(), t)
|
||||
}
|
||||
|
||||
func TestMembershipEvent(t *testing.T) {
|
||||
gh := NewGithubWebhooks()
|
||||
jsonString := MembershipEventJSON()
|
||||
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
|
||||
req.Header.Add("X-Github-Event", "membership")
|
||||
w := httptest.NewRecorder()
|
||||
gh.eventHandler(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
|
||||
}
|
||||
GithubWebhookRequest("membership", MembershipEventJSON(), t)
|
||||
}
|
||||
|
||||
func TestPageBuildEvent(t *testing.T) {
|
||||
gh := NewGithubWebhooks()
|
||||
jsonString := PageBuildEventJSON()
|
||||
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
|
||||
req.Header.Add("X-Github-Event", "page_build")
|
||||
w := httptest.NewRecorder()
|
||||
gh.eventHandler(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
|
||||
}
|
||||
GithubWebhookRequest("page_build", PageBuildEventJSON(), t)
|
||||
}
|
||||
|
||||
func TestPublicEvent(t *testing.T) {
|
||||
gh := NewGithubWebhooks()
|
||||
jsonString := PublicEventJSON()
|
||||
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
|
||||
req.Header.Add("X-Github-Event", "public")
|
||||
w := httptest.NewRecorder()
|
||||
gh.eventHandler(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
|
||||
}
|
||||
GithubWebhookRequest("public", PublicEventJSON(), t)
|
||||
}
|
||||
|
||||
func TestPullRequestReviewCommentEvent(t *testing.T) {
|
||||
gh := NewGithubWebhooks()
|
||||
jsonString := PullRequestReviewCommentEventJSON()
|
||||
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
|
||||
req.Header.Add("X-Github-Event", "pull_request_review_comment")
|
||||
w := httptest.NewRecorder()
|
||||
gh.eventHandler(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
|
||||
}
|
||||
GithubWebhookRequest("pull_request_review_comment", PullRequestReviewCommentEventJSON(), t)
|
||||
}
|
||||
|
||||
func TestPushEvent(t *testing.T) {
|
||||
gh := NewGithubWebhooks()
|
||||
jsonString := PushEventJSON()
|
||||
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
|
||||
req.Header.Add("X-Github-Event", "push")
|
||||
w := httptest.NewRecorder()
|
||||
gh.eventHandler(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
|
||||
}
|
||||
GithubWebhookRequest("push", PushEventJSON(), t)
|
||||
}
|
||||
|
||||
func TestReleaseEvent(t *testing.T) {
|
||||
gh := NewGithubWebhooks()
|
||||
jsonString := ReleaseEventJSON()
|
||||
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
|
||||
req.Header.Add("X-Github-Event", "release")
|
||||
w := httptest.NewRecorder()
|
||||
gh.eventHandler(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
|
||||
}
|
||||
GithubWebhookRequest("release", ReleaseEventJSON(), t)
|
||||
}
|
||||
|
||||
func TestRepositoryEvent(t *testing.T) {
|
||||
gh := NewGithubWebhooks()
|
||||
jsonString := RepositoryEventJSON()
|
||||
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
|
||||
req.Header.Add("X-Github-Event", "repository")
|
||||
w := httptest.NewRecorder()
|
||||
gh.eventHandler(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
|
||||
}
|
||||
GithubWebhookRequest("repository", RepositoryEventJSON(), t)
|
||||
}
|
||||
|
||||
func TestStatusEvent(t *testing.T) {
|
||||
gh := NewGithubWebhooks()
|
||||
|
||||
jsonString := StatusEventJSON()
|
||||
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
|
||||
req.Header.Add("X-Github-Event", "status")
|
||||
w := httptest.NewRecorder()
|
||||
gh.eventHandler(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
|
||||
}
|
||||
GithubWebhookRequest("status", StatusEventJSON(), t)
|
||||
}
|
||||
|
||||
func TestTeamAddEvent(t *testing.T) {
|
||||
gh := NewGithubWebhooks()
|
||||
jsonString := TeamAddEventJSON()
|
||||
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
|
||||
req.Header.Add("X-Github-Event", "team_add")
|
||||
w := httptest.NewRecorder()
|
||||
gh.eventHandler(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
|
||||
}
|
||||
GithubWebhookRequest("team_add", TeamAddEventJSON(), t)
|
||||
}
|
||||
|
||||
func TestWatchEvent(t *testing.T) {
|
||||
gh := NewGithubWebhooks()
|
||||
jsonString := WatchEventJSON()
|
||||
req, _ := http.NewRequest("POST", "/", strings.NewReader(jsonString))
|
||||
req.Header.Add("X-Github-Event", "watch")
|
||||
w := httptest.NewRecorder()
|
||||
gh.eventHandler(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("POST commit_comment returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
|
||||
}
|
||||
GithubWebhookRequest("watch", WatchEventJSON(), t)
|
||||
}
|
||||
|
||||
55
plugins/inputs/graylog/README.md
Normal file
55
plugins/inputs/graylog/README.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# GrayLog plugin
|
||||
|
||||
The Graylog plugin can collect data from remote Graylog service URLs.
|
||||
|
||||
Plugin currently support two type of end points:-
|
||||
|
||||
- multiple (Ex http://[graylog-server-ip]:12900/system/metrics/multiple)
|
||||
- namespace (Ex http://[graylog-server-ip]:12900/system/metrics/namespace/{namespace})
|
||||
|
||||
End Point can be a mixe of one multiple end point and several namespaces end points
|
||||
|
||||
|
||||
Note: if namespace end point specified metrics array will be ignored for that call.
|
||||
|
||||
### Configuration:
|
||||
|
||||
```toml
|
||||
# Read flattened metrics from one or more GrayLog HTTP endpoints
|
||||
[[inputs.graylog]]
|
||||
## API endpoint, currently supported API:
|
||||
##
|
||||
## - multiple (Ex http://<host>:12900/system/metrics/multiple)
|
||||
## - namespace (Ex http://<host>:12900/system/metrics/namespace/{namespace})
|
||||
##
|
||||
## For namespace endpoint, the metrics array will be ignored for that call.
|
||||
## Endpoint can contain namespace and multiple type calls.
|
||||
##
|
||||
## Please check http://[graylog-server-ip]:12900/api-browser for full list
|
||||
## of endpoints
|
||||
servers = [
|
||||
"http://[graylog-server-ip]:12900/system/metrics/multiple",
|
||||
]
|
||||
|
||||
## Metrics list
|
||||
## List of metrics can be found on Graylog webservice documentation.
|
||||
## Or by hitting the the web service api at:
|
||||
## http://[graylog-host]:12900/system/metrics
|
||||
metrics = [
|
||||
"jvm.cl.loaded",
|
||||
"jvm.memory.pools.Metaspace.committed"
|
||||
]
|
||||
|
||||
## Username and password
|
||||
username = ""
|
||||
password = ""
|
||||
|
||||
## Optional SSL Config
|
||||
# ssl_ca = "/etc/telegraf/ca.pem"
|
||||
# ssl_cert = "/etc/telegraf/cert.pem"
|
||||
# ssl_key = "/etc/telegraf/key.pem"
|
||||
## Use SSL but skip chain & host verification
|
||||
# insecure_skip_verify = false
|
||||
```
|
||||
|
||||
Please refer to GrayLog metrics api browser for full metric end points http://host:12900/api-browser
|
||||
312
plugins/inputs/graylog/graylog.go
Normal file
312
plugins/inputs/graylog/graylog.go
Normal file
@@ -0,0 +1,312 @@
|
||||
package graylog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/internal"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
type ResponseMetrics struct {
|
||||
total int
|
||||
Metrics []Metric `json:"metrics"`
|
||||
}
|
||||
|
||||
type Metric struct {
|
||||
FullName string `json:"full_name"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Fields map[string]interface{} `json:"metric"`
|
||||
}
|
||||
|
||||
type GrayLog struct {
|
||||
Servers []string
|
||||
Metrics []string
|
||||
Username string
|
||||
Password string
|
||||
|
||||
// Path to CA file
|
||||
SSLCA string `toml:"ssl_ca"`
|
||||
// Path to host cert file
|
||||
SSLCert string `toml:"ssl_cert"`
|
||||
// Path to cert key file
|
||||
SSLKey string `toml:"ssl_key"`
|
||||
// Use SSL but skip chain & host verification
|
||||
InsecureSkipVerify bool
|
||||
|
||||
client HTTPClient
|
||||
}
|
||||
|
||||
type HTTPClient interface {
|
||||
// Returns the result of an http request
|
||||
//
|
||||
// Parameters:
|
||||
// req: HTTP request object
|
||||
//
|
||||
// Returns:
|
||||
// http.Response: HTTP respons object
|
||||
// error : Any error that may have occurred
|
||||
MakeRequest(req *http.Request) (*http.Response, error)
|
||||
|
||||
SetHTTPClient(client *http.Client)
|
||||
HTTPClient() *http.Client
|
||||
}
|
||||
|
||||
type Messagebody struct {
|
||||
Metrics []string `json:"metrics"`
|
||||
}
|
||||
|
||||
type RealHTTPClient struct {
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func (c *RealHTTPClient) MakeRequest(req *http.Request) (*http.Response, error) {
|
||||
return c.client.Do(req)
|
||||
}
|
||||
|
||||
func (c *RealHTTPClient) SetHTTPClient(client *http.Client) {
|
||||
c.client = client
|
||||
}
|
||||
|
||||
func (c *RealHTTPClient) HTTPClient() *http.Client {
|
||||
return c.client
|
||||
}
|
||||
|
||||
var sampleConfig = `
|
||||
## API endpoint, currently supported API:
|
||||
##
|
||||
## - multiple (Ex http://<host>:12900/system/metrics/multiple)
|
||||
## - namespace (Ex http://<host>:12900/system/metrics/namespace/{namespace})
|
||||
##
|
||||
## For namespace endpoint, the metrics array will be ignored for that call.
|
||||
## Endpoint can contain namespace and multiple type calls.
|
||||
##
|
||||
## Please check http://[graylog-server-ip]:12900/api-browser for full list
|
||||
## of endpoints
|
||||
servers = [
|
||||
"http://[graylog-server-ip]:12900/system/metrics/multiple",
|
||||
]
|
||||
|
||||
## Metrics list
|
||||
## List of metrics can be found on Graylog webservice documentation.
|
||||
## Or by hitting the the web service api at:
|
||||
## http://[graylog-host]:12900/system/metrics
|
||||
metrics = [
|
||||
"jvm.cl.loaded",
|
||||
"jvm.memory.pools.Metaspace.committed"
|
||||
]
|
||||
|
||||
## Username and password
|
||||
username = ""
|
||||
password = ""
|
||||
|
||||
## Optional SSL Config
|
||||
# ssl_ca = "/etc/telegraf/ca.pem"
|
||||
# ssl_cert = "/etc/telegraf/cert.pem"
|
||||
# ssl_key = "/etc/telegraf/key.pem"
|
||||
## Use SSL but skip chain & host verification
|
||||
# insecure_skip_verify = false
|
||||
`
|
||||
|
||||
func (h *GrayLog) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func (h *GrayLog) Description() string {
|
||||
return "Read flattened metrics from one or more GrayLog HTTP endpoints"
|
||||
}
|
||||
|
||||
// Gathers data for all servers.
|
||||
func (h *GrayLog) Gather(acc telegraf.Accumulator) error {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
if h.client.HTTPClient() == nil {
|
||||
tlsCfg, err := internal.GetTLSConfig(
|
||||
h.SSLCert, h.SSLKey, h.SSLCA, h.InsecureSkipVerify)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tr := &http.Transport{
|
||||
ResponseHeaderTimeout: time.Duration(3 * time.Second),
|
||||
TLSClientConfig: tlsCfg,
|
||||
}
|
||||
client := &http.Client{
|
||||
Transport: tr,
|
||||
Timeout: time.Duration(4 * time.Second),
|
||||
}
|
||||
h.client.SetHTTPClient(client)
|
||||
}
|
||||
|
||||
errorChannel := make(chan error, len(h.Servers))
|
||||
|
||||
for _, server := range h.Servers {
|
||||
wg.Add(1)
|
||||
go func(server string) {
|
||||
defer wg.Done()
|
||||
if err := h.gatherServer(acc, server); err != nil {
|
||||
errorChannel <- err
|
||||
}
|
||||
}(server)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errorChannel)
|
||||
|
||||
// Get all errors and return them as one giant error
|
||||
errorStrings := []string{}
|
||||
for err := range errorChannel {
|
||||
errorStrings = append(errorStrings, err.Error())
|
||||
}
|
||||
|
||||
if len(errorStrings) == 0 {
|
||||
return nil
|
||||
}
|
||||
return errors.New(strings.Join(errorStrings, "\n"))
|
||||
}
|
||||
|
||||
// Gathers data from a particular server
|
||||
// Parameters:
|
||||
// acc : The telegraf Accumulator to use
|
||||
// serverURL: endpoint to send request to
|
||||
// service : the service being queried
|
||||
//
|
||||
// Returns:
|
||||
// error: Any error that may have occurred
|
||||
func (h *GrayLog) gatherServer(
|
||||
acc telegraf.Accumulator,
|
||||
serverURL string,
|
||||
) error {
|
||||
resp, _, err := h.sendRequest(serverURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
requestURL, err := url.Parse(serverURL)
|
||||
host, port, _ := net.SplitHostPort(requestURL.Host)
|
||||
var dat ResponseMetrics
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal([]byte(resp), &dat); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, m_item := range dat.Metrics {
|
||||
fields := make(map[string]interface{})
|
||||
tags := map[string]string{
|
||||
"server": host,
|
||||
"port": port,
|
||||
"name": m_item.Name,
|
||||
"type": m_item.Type,
|
||||
}
|
||||
h.flatten(m_item.Fields, fields, "")
|
||||
acc.AddFields(m_item.FullName, fields, tags)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flatten JSON hierarchy to produce field name and field value
|
||||
// Parameters:
|
||||
// item: Item map to flatten
|
||||
// fields: Map to store generated fields.
|
||||
// id: Prefix for top level metric (empty string "")
|
||||
// Returns:
|
||||
// void
|
||||
func (h *GrayLog) flatten(item map[string]interface{}, fields map[string]interface{}, id string) {
|
||||
if id != "" {
|
||||
id = id + "_"
|
||||
}
|
||||
for k, i := range item {
|
||||
switch i.(type) {
|
||||
case int:
|
||||
fields[id+k] = i.(float64)
|
||||
case float64:
|
||||
fields[id+k] = i.(float64)
|
||||
case map[string]interface{}:
|
||||
h.flatten(i.(map[string]interface{}), fields, id+k)
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sends an HTTP request to the server using the GrayLog object's HTTPClient.
|
||||
// Parameters:
|
||||
// serverURL: endpoint to send request to
|
||||
//
|
||||
// Returns:
|
||||
// string: body of the response
|
||||
// error : Any error that may have occurred
|
||||
func (h *GrayLog) sendRequest(serverURL string) (string, float64, error) {
|
||||
headers := map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
}
|
||||
method := "GET"
|
||||
content := bytes.NewBufferString("")
|
||||
headers["Authorization"] = "Basic " + base64.URLEncoding.EncodeToString([]byte(h.Username+":"+h.Password))
|
||||
// Prepare URL
|
||||
requestURL, err := url.Parse(serverURL)
|
||||
if err != nil {
|
||||
return "", -1, fmt.Errorf("Invalid server URL \"%s\"", serverURL)
|
||||
}
|
||||
if strings.Contains(requestURL.String(), "multiple") {
|
||||
m := &Messagebody{Metrics: h.Metrics}
|
||||
http_body, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return "", -1, fmt.Errorf("Invalid list of Metrics %s", h.Metrics)
|
||||
}
|
||||
method = "POST"
|
||||
content = bytes.NewBuffer(http_body)
|
||||
}
|
||||
req, err := http.NewRequest(method, requestURL.String(), content)
|
||||
if err != nil {
|
||||
return "", -1, err
|
||||
}
|
||||
// Add header parameters
|
||||
for k, v := range headers {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
start := time.Now()
|
||||
resp, err := h.client.MakeRequest(req)
|
||||
if err != nil {
|
||||
return "", -1, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
responseTime := time.Since(start).Seconds()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return string(body), responseTime, err
|
||||
}
|
||||
|
||||
// Process response
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
err = fmt.Errorf("Response from url \"%s\" has status code %d (%s), expected %d (%s)",
|
||||
requestURL.String(),
|
||||
resp.StatusCode,
|
||||
http.StatusText(resp.StatusCode),
|
||||
http.StatusOK,
|
||||
http.StatusText(http.StatusOK))
|
||||
return string(body), responseTime, err
|
||||
}
|
||||
return string(body), responseTime, err
|
||||
}
|
||||
|
||||
func init() {
|
||||
inputs.Add("graylog", func() telegraf.Input {
|
||||
return &GrayLog{
|
||||
client: &RealHTTPClient{},
|
||||
}
|
||||
})
|
||||
}
|
||||
199
plugins/inputs/graylog/graylog_test.go
Normal file
199
plugins/inputs/graylog/graylog_test.go
Normal file
@@ -0,0 +1,199 @@
|
||||
package graylog
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const validJSON = `
|
||||
{
|
||||
"total": 3,
|
||||
"metrics": [
|
||||
{
|
||||
"full_name": "jvm.cl.loaded",
|
||||
"metric": {
|
||||
"value": 18910
|
||||
},
|
||||
"name": "loaded",
|
||||
"type": "gauge"
|
||||
},
|
||||
{
|
||||
"full_name": "jvm.memory.pools.Metaspace.committed",
|
||||
"metric": {
|
||||
"value": 108040192
|
||||
},
|
||||
"name": "committed",
|
||||
"type": "gauge"
|
||||
},
|
||||
{
|
||||
"full_name": "org.graylog2.shared.journal.KafkaJournal.writeTime",
|
||||
"metric": {
|
||||
"time": {
|
||||
"min": 99
|
||||
},
|
||||
"rate": {
|
||||
"total": 10,
|
||||
"mean": 2
|
||||
},
|
||||
"duration_unit": "microseconds",
|
||||
"rate_unit": "events/second"
|
||||
},
|
||||
"name": "writeTime",
|
||||
"type": "hdrtimer"
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
var validTags = map[string]map[string]string{
|
||||
"jvm.cl.loaded": {
|
||||
"name": "loaded",
|
||||
"type": "gauge",
|
||||
"port": "12900",
|
||||
"server": "localhost",
|
||||
},
|
||||
"jvm.memory.pools.Metaspace.committed": {
|
||||
"name": "committed",
|
||||
"type": "gauge",
|
||||
"port": "12900",
|
||||
"server": "localhost",
|
||||
},
|
||||
"org.graylog2.shared.journal.KafkaJournal.writeTime": {
|
||||
"name": "writeTime",
|
||||
"type": "hdrtimer",
|
||||
"port": "12900",
|
||||
"server": "localhost",
|
||||
},
|
||||
}
|
||||
|
||||
var expectedFields = map[string]map[string]interface{}{
|
||||
"jvm.cl.loaded": {
|
||||
"value": float64(18910),
|
||||
},
|
||||
"jvm.memory.pools.Metaspace.committed": {
|
||||
"value": float64(108040192),
|
||||
},
|
||||
"org.graylog2.shared.journal.KafkaJournal.writeTime": {
|
||||
"time_min": float64(99),
|
||||
"rate_total": float64(10),
|
||||
"rate_mean": float64(2),
|
||||
},
|
||||
}
|
||||
|
||||
const invalidJSON = "I don't think this is JSON"
|
||||
|
||||
const empty = ""
|
||||
|
||||
type mockHTTPClient struct {
|
||||
responseBody string
|
||||
statusCode int
|
||||
}
|
||||
|
||||
// Mock implementation of MakeRequest. Usually returns an http.Response with
|
||||
// hard-coded responseBody and statusCode. However, if the request uses a
|
||||
// nonstandard method, it uses status code 405 (method not allowed)
|
||||
func (c *mockHTTPClient) MakeRequest(req *http.Request) (*http.Response, error) {
|
||||
resp := http.Response{}
|
||||
resp.StatusCode = c.statusCode
|
||||
|
||||
// basic error checking on request method
|
||||
allowedMethods := []string{"GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT"}
|
||||
methodValid := false
|
||||
for _, method := range allowedMethods {
|
||||
if req.Method == method {
|
||||
methodValid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !methodValid {
|
||||
resp.StatusCode = 405 // Method not allowed
|
||||
}
|
||||
|
||||
resp.Body = ioutil.NopCloser(strings.NewReader(c.responseBody))
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (c *mockHTTPClient) SetHTTPClient(_ *http.Client) {
|
||||
}
|
||||
|
||||
func (c *mockHTTPClient) HTTPClient() *http.Client {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Generates a pointer to an HttpJson object that uses a mock HTTP client.
|
||||
// Parameters:
|
||||
// response : Body of the response that the mock HTTP client should return
|
||||
// statusCode: HTTP status code the mock HTTP client should return
|
||||
//
|
||||
// Returns:
|
||||
// *HttpJson: Pointer to an HttpJson object that uses the generated mock HTTP client
|
||||
func genMockGrayLog(response string, statusCode int) []*GrayLog {
|
||||
return []*GrayLog{
|
||||
&GrayLog{
|
||||
client: &mockHTTPClient{responseBody: response, statusCode: statusCode},
|
||||
Servers: []string{
|
||||
"http://localhost:12900/system/metrics/multiple",
|
||||
},
|
||||
Metrics: []string{
|
||||
"jvm.memory.pools.Metaspace.committed",
|
||||
"jvm.cl.loaded",
|
||||
"org.graylog2.shared.journal.KafkaJournal.writeTime",
|
||||
},
|
||||
Username: "test",
|
||||
Password: "test",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Test that the proper values are ignored or collected
|
||||
func TestNormalResponse(t *testing.T) {
|
||||
graylog := genMockGrayLog(validJSON, 200)
|
||||
|
||||
for _, service := range graylog {
|
||||
var acc testutil.Accumulator
|
||||
err := service.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
for k, v := range expectedFields {
|
||||
acc.AssertContainsTaggedFields(t, k, v, validTags[k])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test response to HTTP 500
|
||||
func TestHttpJson500(t *testing.T) {
|
||||
graylog := genMockGrayLog(validJSON, 500)
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err := graylog[0].Gather(&acc)
|
||||
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, 0, acc.NFields())
|
||||
}
|
||||
|
||||
// Test response to malformed JSON
|
||||
func TestHttpJsonBadJson(t *testing.T) {
|
||||
graylog := genMockGrayLog(invalidJSON, 200)
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err := graylog[0].Gather(&acc)
|
||||
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, 0, acc.NFields())
|
||||
}
|
||||
|
||||
// Test response to empty string as response objectgT
|
||||
func TestHttpJsonEmptyResponse(t *testing.T) {
|
||||
graylog := genMockGrayLog(empty, 200)
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err := graylog[0].Gather(&acc)
|
||||
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, 0, acc.NFields())
|
||||
}
|
||||
@@ -3,8 +3,6 @@ package haproxy
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -13,6 +11,10 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/internal/errchan"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
//CSV format: https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#9.1
|
||||
@@ -113,20 +115,17 @@ func (g *haproxy) Gather(acc telegraf.Accumulator) error {
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
var outerr error
|
||||
|
||||
for _, serv := range g.Servers {
|
||||
wg.Add(1)
|
||||
errChan := errchan.New(len(g.Servers))
|
||||
wg.Add(len(g.Servers))
|
||||
for _, server := range g.Servers {
|
||||
go func(serv string) {
|
||||
defer wg.Done()
|
||||
outerr = g.gatherServer(serv, acc)
|
||||
}(serv)
|
||||
errChan.C <- g.gatherServer(serv, acc)
|
||||
}(server)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return outerr
|
||||
return errChan.Error()
|
||||
}
|
||||
|
||||
func (g *haproxy) gatherServerSocket(addr string, acc telegraf.Accumulator) error {
|
||||
|
||||
@@ -5,23 +5,30 @@ This input plugin will test HTTP/HTTPS connections.
|
||||
### Configuration:
|
||||
|
||||
```
|
||||
# List of UDP/TCP connections you want to check
|
||||
# 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
|
||||
response_timeout = "5s"
|
||||
## 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
|
||||
## HTTP Request Headers (all values must be strings)
|
||||
# [inputs.http_response.headers]
|
||||
# Host = "github.com"
|
||||
## Optional HTTP Request Body
|
||||
body = '''
|
||||
{'fake':'data'}
|
||||
'''
|
||||
# body = '''
|
||||
# {'fake':'data'}
|
||||
# '''
|
||||
|
||||
## Optional SSL Config
|
||||
# ssl_ca = "/etc/telegraf/ca.pem"
|
||||
# ssl_cert = "/etc/telegraf/cert.pem"
|
||||
# ssl_key = "/etc/telegraf/key.pem"
|
||||
## Use SSL but skip chain & host verification
|
||||
# insecure_skip_verify = false
|
||||
```
|
||||
|
||||
### Measurements & Fields:
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/internal"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
@@ -17,9 +18,18 @@ type HTTPResponse struct {
|
||||
Address string
|
||||
Body string
|
||||
Method string
|
||||
ResponseTimeout int
|
||||
ResponseTimeout internal.Duration
|
||||
Headers map[string]string
|
||||
FollowRedirects bool
|
||||
|
||||
// Path to CA file
|
||||
SSLCA string `toml:"ssl_ca"`
|
||||
// Path to host cert file
|
||||
SSLCert string `toml:"ssl_cert"`
|
||||
// Path to cert key file
|
||||
SSLKey string `toml:"ssl_key"`
|
||||
// Use SSL but skip chain & host verification
|
||||
InsecureSkipVerify bool
|
||||
}
|
||||
|
||||
// Description returns the plugin Description
|
||||
@@ -31,7 +41,7 @@ var sampleConfig = `
|
||||
## Server address (default http://localhost)
|
||||
address = "http://github.com"
|
||||
## Set response_timeout (default 5 seconds)
|
||||
response_timeout = 5
|
||||
response_timeout = "5s"
|
||||
## HTTP Request Method
|
||||
method = "GET"
|
||||
## Whether to follow redirects from the server (defaults to false)
|
||||
@@ -43,6 +53,13 @@ var sampleConfig = `
|
||||
# body = '''
|
||||
# {'fake':'data'}
|
||||
# '''
|
||||
|
||||
## Optional SSL Config
|
||||
# ssl_ca = "/etc/telegraf/ca.pem"
|
||||
# ssl_cert = "/etc/telegraf/cert.pem"
|
||||
# ssl_key = "/etc/telegraf/key.pem"
|
||||
## Use SSL but skip chain & host verification
|
||||
# insecure_skip_verify = false
|
||||
`
|
||||
|
||||
// SampleConfig returns the plugin SampleConfig
|
||||
@@ -55,27 +72,27 @@ 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 {
|
||||
func (h *HTTPResponse) createHttpClient() (*http.Client, error) {
|
||||
tlsCfg, err := internal.GetTLSConfig(
|
||||
h.SSLCert, h.SSLKey, h.SSLCA, h.InsecureSkipVerify)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tr := &http.Transport{
|
||||
ResponseHeaderTimeout: h.ResponseTimeout.Duration,
|
||||
TLSClientConfig: tlsCfg,
|
||||
}
|
||||
client := &http.Client{
|
||||
Timeout: time.Second * ResponseTimeout,
|
||||
Transport: tr,
|
||||
Timeout: h.ResponseTimeout.Duration,
|
||||
}
|
||||
|
||||
if followRedirects == false {
|
||||
if h.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
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// HTTPGather gathers all fields and returns any errors it encounters
|
||||
@@ -83,7 +100,10 @@ func (h *HTTPResponse) HTTPGather() (map[string]interface{}, error) {
|
||||
// Prepare fields
|
||||
fields := make(map[string]interface{})
|
||||
|
||||
client := CreateHttpClient(h.FollowRedirects, time.Duration(h.ResponseTimeout))
|
||||
client, err := h.createHttpClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var body io.Reader
|
||||
if h.Body != "" {
|
||||
@@ -93,7 +113,13 @@ func (h *HTTPResponse) HTTPGather() (map[string]interface{}, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request.Header = CreateHeaders(h.Headers)
|
||||
|
||||
for key, val := range h.Headers {
|
||||
request.Header.Add(key, val)
|
||||
if key == "Host" {
|
||||
request.Host = val
|
||||
}
|
||||
}
|
||||
|
||||
// Start Timer
|
||||
start := time.Now()
|
||||
@@ -117,8 +143,8 @@ func (h *HTTPResponse) HTTPGather() (map[string]interface{}, error) {
|
||||
// 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
|
||||
if h.ResponseTimeout.Duration < time.Second {
|
||||
h.ResponseTimeout.Duration = time.Second * 5
|
||||
}
|
||||
// Check send and expected string
|
||||
if h.Method == "" {
|
||||
|
||||
@@ -2,28 +2,17 @@ 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)
|
||||
}
|
||||
"github.com/influxdata/telegraf/internal"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func setUpTestMux() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
@@ -63,6 +52,33 @@ func setUpTestMux() http.Handler {
|
||||
return mux
|
||||
}
|
||||
|
||||
func TestHeaders(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
cHeader := r.Header.Get("Content-Type")
|
||||
assert.Equal(t, "Hello", r.Host)
|
||||
assert.Equal(t, "application/json", cHeader)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
h := &HTTPResponse{
|
||||
Address: ts.URL,
|
||||
Method: "GET",
|
||||
ResponseTimeout: internal.Duration{Duration: time.Second * 2},
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
"Host": "Hello",
|
||||
},
|
||||
}
|
||||
fields, err := h.HTTPGather()
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, fields)
|
||||
if assert.NotNil(t, fields["http_response_code"]) {
|
||||
assert.Equal(t, http.StatusOK, fields["http_response_code"])
|
||||
}
|
||||
assert.NotNil(t, fields["response_time"])
|
||||
}
|
||||
|
||||
func TestFields(t *testing.T) {
|
||||
mux := setUpTestMux()
|
||||
ts := httptest.NewServer(mux)
|
||||
@@ -72,7 +88,7 @@ func TestFields(t *testing.T) {
|
||||
Address: ts.URL + "/good",
|
||||
Body: "{ 'test': 'data'}",
|
||||
Method: "GET",
|
||||
ResponseTimeout: 20,
|
||||
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
@@ -85,7 +101,6 @@ func TestFields(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, fields["http_response_code"])
|
||||
}
|
||||
assert.NotNil(t, fields["response_time"])
|
||||
|
||||
}
|
||||
|
||||
func TestRedirects(t *testing.T) {
|
||||
@@ -97,7 +112,7 @@ func TestRedirects(t *testing.T) {
|
||||
Address: ts.URL + "/redirect",
|
||||
Body: "{ 'test': 'data'}",
|
||||
Method: "GET",
|
||||
ResponseTimeout: 20,
|
||||
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
@@ -114,7 +129,7 @@ func TestRedirects(t *testing.T) {
|
||||
Address: ts.URL + "/badredirect",
|
||||
Body: "{ 'test': 'data'}",
|
||||
Method: "GET",
|
||||
ResponseTimeout: 20,
|
||||
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
@@ -133,7 +148,7 @@ func TestMethod(t *testing.T) {
|
||||
Address: ts.URL + "/mustbepostmethod",
|
||||
Body: "{ 'test': 'data'}",
|
||||
Method: "POST",
|
||||
ResponseTimeout: 20,
|
||||
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
@@ -150,7 +165,7 @@ func TestMethod(t *testing.T) {
|
||||
Address: ts.URL + "/mustbepostmethod",
|
||||
Body: "{ 'test': 'data'}",
|
||||
Method: "GET",
|
||||
ResponseTimeout: 20,
|
||||
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
@@ -168,7 +183,7 @@ func TestMethod(t *testing.T) {
|
||||
Address: ts.URL + "/mustbepostmethod",
|
||||
Body: "{ 'test': 'data'}",
|
||||
Method: "head",
|
||||
ResponseTimeout: 20,
|
||||
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
@@ -191,7 +206,7 @@ func TestBody(t *testing.T) {
|
||||
Address: ts.URL + "/musthaveabody",
|
||||
Body: "{ 'test': 'data'}",
|
||||
Method: "GET",
|
||||
ResponseTimeout: 20,
|
||||
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
@@ -207,7 +222,7 @@ func TestBody(t *testing.T) {
|
||||
h = &HTTPResponse{
|
||||
Address: ts.URL + "/musthaveabody",
|
||||
Method: "GET",
|
||||
ResponseTimeout: 20,
|
||||
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
@@ -230,7 +245,7 @@ func TestTimeout(t *testing.T) {
|
||||
Address: ts.URL + "/twosecondnap",
|
||||
Body: "{ 'test': 'data'}",
|
||||
Method: "GET",
|
||||
ResponseTimeout: 1,
|
||||
ResponseTimeout: internal.Duration{Duration: time.Second * 1},
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
|
||||
@@ -15,6 +15,7 @@ InfluxDB-formatted endpoints. See below for more information.
|
||||
## See the influxdb plugin's README for more details.
|
||||
|
||||
## Multiple URLs from which to read InfluxDB-formatted JSON
|
||||
## Default is "http://localhost:8086/debug/vars".
|
||||
urls = [
|
||||
"http://localhost:8086/debug/vars"
|
||||
]
|
||||
@@ -22,16 +23,78 @@ InfluxDB-formatted endpoints. See below for more information.
|
||||
|
||||
### Measurements & Fields
|
||||
|
||||
- influxdb
|
||||
- n_shards
|
||||
- influxdb_database
|
||||
- influxdb_httpd
|
||||
- influxdb_measurement
|
||||
- influxdb_memstats
|
||||
- heap_inuse
|
||||
- heap_released
|
||||
- mspan_inuse
|
||||
- total_alloc
|
||||
- sys
|
||||
- mallocs
|
||||
- frees
|
||||
- heap_idle
|
||||
- pause_total_ns
|
||||
- lookups
|
||||
- heap_sys
|
||||
- mcache_sys
|
||||
- next_gc
|
||||
- gcc_pu_fraction
|
||||
- other_sys
|
||||
- alloc
|
||||
- stack_inuse
|
||||
- stack_sys
|
||||
- buck_hash_sys
|
||||
- gc_sys
|
||||
- num_gc
|
||||
- heap_alloc
|
||||
- heap_objects
|
||||
- mspan_sys
|
||||
- mcache_inuse
|
||||
- last_gc
|
||||
- influxdb_shard
|
||||
- influxdb_subscriber
|
||||
- influxdb_tsm1_cache
|
||||
- influxdb_tsm1_wal
|
||||
- influxdb_write
|
||||
|
||||
### Example Output:
|
||||
|
||||
```
|
||||
telegraf -config ~/ws/telegraf.conf -input-filter influxdb -test
|
||||
* Plugin: influxdb, Collection 1
|
||||
> influxdb_database,database=_internal,host=tyrion,url=http://localhost:8086/debug/vars numMeasurements=10,numSeries=29 1463590500247354636
|
||||
> influxdb_httpd,bind=:8086,host=tyrion,url=http://localhost:8086/debug/vars req=7,reqActive=1,reqDurationNs=14227734 1463590500247354636
|
||||
> influxdb_measurement,database=_internal,host=tyrion,measurement=database,url=http://localhost:8086/debug/vars numSeries=1 1463590500247354636
|
||||
> influxdb_measurement,database=_internal,host=tyrion,measurement=httpd,url=http://localhost:8086/debug/vars numSeries=1 1463590500247354636
|
||||
> influxdb_measurement,database=_internal,host=tyrion,measurement=measurement,url=http://localhost:8086/debug/vars numSeries=10 1463590500247354636
|
||||
> influxdb_measurement,database=_internal,host=tyrion,measurement=runtime,url=http://localhost:8086/debug/vars numSeries=1 1463590500247354636
|
||||
> influxdb_measurement,database=_internal,host=tyrion,measurement=shard,url=http://localhost:8086/debug/vars numSeries=4 1463590500247354636
|
||||
> influxdb_measurement,database=_internal,host=tyrion,measurement=subscriber,url=http://localhost:8086/debug/vars numSeries=1 1463590500247354636
|
||||
> influxdb_measurement,database=_internal,host=tyrion,measurement=tsm1_cache,url=http://localhost:8086/debug/vars numSeries=4 1463590500247354636
|
||||
> influxdb_measurement,database=_internal,host=tyrion,measurement=tsm1_filestore,url=http://localhost:8086/debug/vars numSeries=2 1463590500247354636
|
||||
> influxdb_measurement,database=_internal,host=tyrion,measurement=tsm1_wal,url=http://localhost:8086/debug/vars numSeries=4 1463590500247354636
|
||||
> influxdb_measurement,database=_internal,host=tyrion,measurement=write,url=http://localhost:8086/debug/vars numSeries=1 1463590500247354636
|
||||
> influxdb_memstats,host=tyrion,url=http://localhost:8086/debug/vars alloc=7642384i,buck_hash_sys=1463471i,frees=1169558i,gc_sys=653312i,gcc_pu_fraction=0.00003825652361068311,heap_alloc=7642384i,heap_idle=9912320i,heap_inuse=9125888i,heap_objects=48276i,heap_released=0i,heap_sys=19038208i,last_gc=1463590480877651621i,lookups=90i,mallocs=1217834i,mcache_inuse=4800i,mcache_sys=16384i,mspan_inuse=70920i,mspan_sys=81920i,next_gc=11679787i,num_gc=141i,other_sys=1244233i,pause_total_ns=24034027i,stack_inuse=884736i,stack_sys=884736i,sys=23382264i,total_alloc=679012200i 1463590500277918755
|
||||
> influxdb_shard,database=_internal,engine=tsm1,host=tyrion,id=4,path=/Users/sparrc/.influxdb/data/_internal/monitor/4,retentionPolicy=monitor,url=http://localhost:8086/debug/vars fieldsCreate=65,seriesCreate=26,writePointsOk=7274,writeReq=280 1463590500247354636
|
||||
> influxdb_subscriber,host=tyrion,url=http://localhost:8086/debug/vars pointsWritten=7274 1463590500247354636
|
||||
> influxdb_tsm1_cache,database=_internal,host=tyrion,path=/Users/sparrc/.influxdb/data/_internal/monitor/1,retentionPolicy=monitor,url=http://localhost:8086/debug/vars WALCompactionTimeMs=0,cacheAgeMs=2809192,cachedBytes=0,diskBytes=0,memBytes=0,snapshotCount=0 1463590500247354636
|
||||
> influxdb_tsm1_cache,database=_internal,host=tyrion,path=/Users/sparrc/.influxdb/data/_internal/monitor/2,retentionPolicy=monitor,url=http://localhost:8086/debug/vars WALCompactionTimeMs=0,cacheAgeMs=2809184,cachedBytes=0,diskBytes=0,memBytes=0,snapshotCount=0 1463590500247354636
|
||||
> influxdb_tsm1_cache,database=_internal,host=tyrion,path=/Users/sparrc/.influxdb/data/_internal/monitor/3,retentionPolicy=monitor,url=http://localhost:8086/debug/vars WALCompactionTimeMs=0,cacheAgeMs=2809180,cachedBytes=0,diskBytes=0,memBytes=42368,snapshotCount=0 1463590500247354636
|
||||
> influxdb_tsm1_cache,database=_internal,host=tyrion,path=/Users/sparrc/.influxdb/data/_internal/monitor/4,retentionPolicy=monitor,url=http://localhost:8086/debug/vars WALCompactionTimeMs=0,cacheAgeMs=2799155,cachedBytes=0,diskBytes=0,memBytes=331216,snapshotCount=0 1463590500247354636
|
||||
> influxdb_tsm1_filestore,database=_internal,host=tyrion,path=/Users/sparrc/.influxdb/data/_internal/monitor/1,retentionPolicy=monitor,url=http://localhost:8086/debug/vars diskBytes=37892 1463590500247354636
|
||||
> influxdb_tsm1_filestore,database=_internal,host=tyrion,path=/Users/sparrc/.influxdb/data/_internal/monitor/2,retentionPolicy=monitor,url=http://localhost:8086/debug/vars diskBytes=52907 1463590500247354636
|
||||
> influxdb_tsm1_wal,database=_internal,host=tyrion,path=/Users/sparrc/.influxdb/wal/_internal/monitor/1,retentionPolicy=monitor,url=http://localhost:8086/debug/vars currentSegmentDiskBytes=0,oldSegmentsDiskBytes=0 1463590500247354636
|
||||
> influxdb_tsm1_wal,database=_internal,host=tyrion,path=/Users/sparrc/.influxdb/wal/_internal/monitor/2,retentionPolicy=monitor,url=http://localhost:8086/debug/vars currentSegmentDiskBytes=0,oldSegmentsDiskBytes=0 1463590500247354636
|
||||
> influxdb_tsm1_wal,database=_internal,host=tyrion,path=/Users/sparrc/.influxdb/wal/_internal/monitor/3,retentionPolicy=monitor,url=http://localhost:8086/debug/vars currentSegmentDiskBytes=0,oldSegmentsDiskBytes=65651 1463590500247354636
|
||||
> influxdb_tsm1_wal,database=_internal,host=tyrion,path=/Users/sparrc/.influxdb/wal/_internal/monitor/4,retentionPolicy=monitor,url=http://localhost:8086/debug/vars currentSegmentDiskBytes=495687,oldSegmentsDiskBytes=0 1463590500247354636
|
||||
> influxdb_write,host=tyrion,url=http://localhost:8086/debug/vars pointReq=7274,pointReqLocal=7274,req=280,subWriteOk=280,writeOk=280 1463590500247354636
|
||||
> influxdb_shard,host=tyrion n_shards=4i 1463590500247354636
|
||||
```
|
||||
|
||||
### InfluxDB-formatted endpoints
|
||||
|
||||
The influxdb plugin can collect InfluxDB-formatted data from JSON endpoints.
|
||||
@@ -46,65 +109,3 @@ With a configuration of:
|
||||
"http://192.168.2.1:8086/debug/vars"
|
||||
]
|
||||
```
|
||||
|
||||
And if 127.0.0.1 responds with this JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"k1": {
|
||||
"name": "fruit",
|
||||
"tags": {
|
||||
"kind": "apple"
|
||||
},
|
||||
"values": {
|
||||
"inventory": 371,
|
||||
"sold": 112
|
||||
}
|
||||
},
|
||||
"k2": {
|
||||
"name": "fruit",
|
||||
"tags": {
|
||||
"kind": "banana"
|
||||
},
|
||||
"values": {
|
||||
"inventory": 1000,
|
||||
"sold": 403
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And if 192.168.2.1 responds like so:
|
||||
|
||||
```json
|
||||
{
|
||||
"k3": {
|
||||
"name": "transactions",
|
||||
"tags": {},
|
||||
"values": {
|
||||
"total": 100,
|
||||
"balance": 184.75
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then the collected metrics will be:
|
||||
|
||||
```
|
||||
influxdb_fruit,url='http://127.0.0.1:8086/debug/vars',kind='apple' inventory=371.0,sold=112.0
|
||||
influxdb_fruit,url='http://127.0.0.1:8086/debug/vars',kind='banana' inventory=1000.0,sold=403.0
|
||||
|
||||
influxdb_transactions,url='http://192.168.2.1:8086/debug/vars' total=100.0,balance=184.75
|
||||
```
|
||||
|
||||
There are two important details to note about the collected metrics:
|
||||
|
||||
1. Even though the values in JSON are being displayed as integers,
|
||||
the metrics are reported as floats.
|
||||
JSON encoders usually don't print the fractional part for round floats.
|
||||
Because you cannot change the type of an existing field in InfluxDB,
|
||||
we assume all numbers are floats.
|
||||
|
||||
2. The top-level keys' names (in the example above, `"k1"`, `"k2"`, and `"k3"`)
|
||||
are not considered when recording the metrics.
|
||||
|
||||
@@ -28,6 +28,7 @@ func (*InfluxDB) SampleConfig() string {
|
||||
## See the influxdb plugin's README for more details.
|
||||
|
||||
## Multiple URLs from which to read InfluxDB-formatted JSON
|
||||
## Default is "http://localhost:8086/debug/vars".
|
||||
urls = [
|
||||
"http://localhost:8086/debug/vars"
|
||||
]
|
||||
@@ -35,6 +36,9 @@ func (*InfluxDB) SampleConfig() string {
|
||||
}
|
||||
|
||||
func (i *InfluxDB) Gather(acc telegraf.Accumulator) error {
|
||||
if len(i.URLs) == 0 {
|
||||
i.URLs = []string{"http://localhost:8086/debug/vars"}
|
||||
}
|
||||
errorChannel := make(chan error, len(i.URLs))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
@@ -120,6 +124,9 @@ func (i *InfluxDB) gatherURL(
|
||||
acc telegraf.Accumulator,
|
||||
url string,
|
||||
) error {
|
||||
shardCounter := 0
|
||||
now := time.Now()
|
||||
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -154,43 +161,45 @@ func (i *InfluxDB) gatherURL(
|
||||
return err
|
||||
}
|
||||
|
||||
if key.(string) == "memstats" {
|
||||
var m memstats
|
||||
if err := dec.Decode(&m); err != nil {
|
||||
continue
|
||||
if keyStr, ok := key.(string); ok {
|
||||
if keyStr == "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,
|
||||
})
|
||||
}
|
||||
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.
|
||||
@@ -207,6 +216,10 @@ func (i *InfluxDB) gatherURL(
|
||||
continue
|
||||
}
|
||||
|
||||
if p.Name == "shard" {
|
||||
shardCounter++
|
||||
}
|
||||
|
||||
// Add a tag to indicate the source of the data.
|
||||
p.Tags["url"] = url
|
||||
|
||||
@@ -214,9 +227,18 @@ func (i *InfluxDB) gatherURL(
|
||||
"influxdb_"+p.Name,
|
||||
p.Values,
|
||||
p.Tags,
|
||||
now,
|
||||
)
|
||||
}
|
||||
|
||||
acc.AddFields("influxdb",
|
||||
map[string]interface{}{
|
||||
"n_shards": shardCounter,
|
||||
},
|
||||
nil,
|
||||
now,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ func TestBasic(t *testing.T) {
|
||||
var acc testutil.Accumulator
|
||||
require.NoError(t, plugin.Gather(&acc))
|
||||
|
||||
require.Len(t, acc.Metrics, 2)
|
||||
require.Len(t, acc.Metrics, 3)
|
||||
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.
|
||||
@@ -50,6 +50,11 @@ func TestBasic(t *testing.T) {
|
||||
"url": fakeServer.URL + "/endpoint",
|
||||
}
|
||||
acc.AssertContainsTaggedFields(t, "influxdb_bar", fields, tags)
|
||||
|
||||
acc.AssertContainsTaggedFields(t, "influxdb",
|
||||
map[string]interface{}{
|
||||
"n_shards": 0,
|
||||
}, map[string]string{})
|
||||
}
|
||||
|
||||
func TestInfluxDB(t *testing.T) {
|
||||
@@ -69,7 +74,7 @@ func TestInfluxDB(t *testing.T) {
|
||||
var acc testutil.Accumulator
|
||||
require.NoError(t, plugin.Gather(&acc))
|
||||
|
||||
require.Len(t, acc.Metrics, 33)
|
||||
require.Len(t, acc.Metrics, 34)
|
||||
|
||||
fields := map[string]interface{}{
|
||||
"heap_inuse": int64(18046976),
|
||||
@@ -104,6 +109,11 @@ func TestInfluxDB(t *testing.T) {
|
||||
"url": fakeInfluxServer.URL + "/endpoint",
|
||||
}
|
||||
acc.AssertContainsTaggedFields(t, "influxdb_memstats", fields, tags)
|
||||
|
||||
acc.AssertContainsTaggedFields(t, "influxdb",
|
||||
map[string]interface{}{
|
||||
"n_shards": 1,
|
||||
}, map[string]string{})
|
||||
}
|
||||
|
||||
func TestErrorHandling(t *testing.T) {
|
||||
|
||||
@@ -50,7 +50,7 @@ var sampleConfig = `
|
||||
## an array of Zookeeper connection strings
|
||||
zookeeper_peers = ["localhost:2181"]
|
||||
## Zookeeper Chroot
|
||||
zookeeper_chroot = "/"
|
||||
zookeeper_chroot = ""
|
||||
## the name of the consumer group
|
||||
consumer_group = "telegraf_metrics_consumers"
|
||||
## Offset (must be either "oldest" or "newest")
|
||||
|
||||
89
plugins/inputs/logparser/README.md
Normal file
89
plugins/inputs/logparser/README.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# logparser Input Plugin
|
||||
|
||||
The logparser plugin streams and parses the given logfiles. Currently it only
|
||||
has the capability of parsing "grok" patterns from logfiles, which also supports
|
||||
regex patterns.
|
||||
|
||||
### Configuration:
|
||||
|
||||
```toml
|
||||
[[inputs.logparser]]
|
||||
## Log files to parse.
|
||||
## These accept standard unix glob matching rules, but with the addition of
|
||||
## ** as a "super asterisk". ie:
|
||||
## /var/log/**.log -> recursively find all .log files in /var/log
|
||||
## /var/log/*/*.log -> find all .log files with a parent dir in /var/log
|
||||
## /var/log/apache.log -> only tail the apache log file
|
||||
files = ["/var/log/influxdb/influxdb.log"]
|
||||
## Read file from beginning.
|
||||
from_beginning = false
|
||||
|
||||
## Parse logstash-style "grok" patterns:
|
||||
## Telegraf builtin parsing patterns: https://goo.gl/dkay10
|
||||
[inputs.logparser.grok]
|
||||
## This is a list of patterns to check the given log file(s) for.
|
||||
## Note that adding patterns here increases processing time. The most
|
||||
## efficient configuration is to have one file & pattern per logparser.
|
||||
patterns = ["%{INFLUXDB_HTTPD_LOG}"]
|
||||
## Full path(s) to custom pattern files.
|
||||
custom_pattern_files = []
|
||||
## Custom patterns can also be defined here. Put one pattern per line.
|
||||
custom_patterns = '''
|
||||
'''
|
||||
```
|
||||
|
||||
## Grok Parser
|
||||
|
||||
The grok parser uses a slightly modified version of logstash "grok" patterns,
|
||||
with the format `%{<capture_syntax>[:<semantic_name>][:<modifier>]}`
|
||||
|
||||
|
||||
Telegraf has many of it's own
|
||||
[built-in patterns](https://github.com/influxdata/telegraf/blob/master/plugins/inputs/logparser/grok/patterns/influx-patterns),
|
||||
as well as supporting
|
||||
[logstash's builtin patterns](https://github.com/logstash-plugins/logstash-patterns-core/blob/master/patterns/grok-patterns).
|
||||
|
||||
|
||||
The best way to get acquainted with grok patterns is to read the logstash docs,
|
||||
which are available here:
|
||||
https://www.elastic.co/guide/en/logstash/current/plugins-filters-grok.html
|
||||
|
||||
|
||||
If you need help building patterns to match your logs,
|
||||
you will find the http://grokdebug.herokuapp.com application quite useful!
|
||||
|
||||
|
||||
By default all named captures are converted into string fields.
|
||||
Modifiers can be used to convert captures to other types or tags.
|
||||
Timestamp modifiers can be used to convert captures to the timestamp of the
|
||||
parsed metric.
|
||||
|
||||
|
||||
- Available modifiers:
|
||||
- string (default if nothing is specified)
|
||||
- int
|
||||
- float
|
||||
- duration (ie, 5.23ms gets converted to int nanoseconds)
|
||||
- tag (converts the field into a tag)
|
||||
- drop (drops the field completely)
|
||||
- Timestamp modifiers:
|
||||
- ts-ansic ("Mon Jan _2 15:04:05 2006")
|
||||
- ts-unix ("Mon Jan _2 15:04:05 MST 2006")
|
||||
- ts-ruby ("Mon Jan 02 15:04:05 -0700 2006")
|
||||
- ts-rfc822 ("02 Jan 06 15:04 MST")
|
||||
- ts-rfc822z ("02 Jan 06 15:04 -0700")
|
||||
- ts-rfc850 ("Monday, 02-Jan-06 15:04:05 MST")
|
||||
- ts-rfc1123 ("Mon, 02 Jan 2006 15:04:05 MST")
|
||||
- ts-rfc1123z ("Mon, 02 Jan 2006 15:04:05 -0700")
|
||||
- ts-rfc3339 ("2006-01-02T15:04:05Z07:00")
|
||||
- ts-rfc3339nano ("2006-01-02T15:04:05.999999999Z07:00")
|
||||
- ts-httpd ("02/Jan/2006:15:04:05 -0700")
|
||||
- ts-epoch (seconds since unix epoch)
|
||||
- ts-epochnano (nanoseconds since unix epoch)
|
||||
- ts-"CUSTOM"
|
||||
|
||||
|
||||
CUSTOM time layouts must be within quotes and be the representation of the
|
||||
"reference time", which is `Mon Jan 2 15:04:05 -0700 MST 2006`
|
||||
See https://golang.org/pkg/time/#Parse for more details.
|
||||
|
||||
373
plugins/inputs/logparser/grok/grok.go
Normal file
373
plugins/inputs/logparser/grok/grok.go
Normal file
@@ -0,0 +1,373 @@
|
||||
package grok
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/vjeantet/grok"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
)
|
||||
|
||||
var timeFormats = map[string]string{
|
||||
"ts-ansic": "Mon Jan _2 15:04:05 2006",
|
||||
"ts-unix": "Mon Jan _2 15:04:05 MST 2006",
|
||||
"ts-ruby": "Mon Jan 02 15:04:05 -0700 2006",
|
||||
"ts-rfc822": "02 Jan 06 15:04 MST",
|
||||
"ts-rfc822z": "02 Jan 06 15:04 -0700", // RFC822 with numeric zone
|
||||
"ts-rfc850": "Monday, 02-Jan-06 15:04:05 MST",
|
||||
"ts-rfc1123": "Mon, 02 Jan 2006 15:04:05 MST",
|
||||
"ts-rfc1123z": "Mon, 02 Jan 2006 15:04:05 -0700", // RFC1123 with numeric zone
|
||||
"ts-rfc3339": "2006-01-02T15:04:05Z07:00",
|
||||
"ts-rfc3339nano": "2006-01-02T15:04:05.999999999Z07:00",
|
||||
"ts-httpd": "02/Jan/2006:15:04:05 -0700",
|
||||
"ts-epoch": "EPOCH",
|
||||
"ts-epochnano": "EPOCH_NANO",
|
||||
}
|
||||
|
||||
const (
|
||||
INT = "int"
|
||||
TAG = "tag"
|
||||
FLOAT = "float"
|
||||
STRING = "string"
|
||||
DURATION = "duration"
|
||||
DROP = "drop"
|
||||
)
|
||||
|
||||
var (
|
||||
// matches named captures that contain a type.
|
||||
// ie,
|
||||
// %{NUMBER:bytes:int}
|
||||
// %{IPORHOST:clientip:tag}
|
||||
// %{HTTPDATE:ts1:ts-http}
|
||||
// %{HTTPDATE:ts2:ts-"02 Jan 06 15:04"}
|
||||
typedRe = regexp.MustCompile(`%{\w+:(\w+):(ts-".+"|t?s?-?\w+)}`)
|
||||
// matches a plain pattern name. ie, %{NUMBER}
|
||||
patternOnlyRe = regexp.MustCompile(`%{(\w+)}`)
|
||||
)
|
||||
|
||||
type Parser struct {
|
||||
Patterns []string
|
||||
CustomPatterns string
|
||||
CustomPatternFiles []string
|
||||
|
||||
// typeMap is a map of patterns -> capture name -> modifier,
|
||||
// ie, {
|
||||
// "%{TESTLOG}":
|
||||
// {
|
||||
// "bytes": "int",
|
||||
// "clientip": "tag"
|
||||
// }
|
||||
// }
|
||||
typeMap map[string]map[string]string
|
||||
// tsMap is a map of patterns -> capture name -> timestamp layout.
|
||||
// ie, {
|
||||
// "%{TESTLOG}":
|
||||
// {
|
||||
// "httptime": "02/Jan/2006:15:04:05 -0700"
|
||||
// }
|
||||
// }
|
||||
tsMap map[string]map[string]string
|
||||
// patterns is a map of all of the parsed patterns from CustomPatterns
|
||||
// and CustomPatternFiles.
|
||||
// ie, {
|
||||
// "DURATION": "%{NUMBER}[nuµm]?s"
|
||||
// "RESPONSE_CODE": "%{NUMBER:rc:tag}"
|
||||
// }
|
||||
patterns map[string]string
|
||||
|
||||
g *grok.Grok
|
||||
tsModder *tsModder
|
||||
}
|
||||
|
||||
func (p *Parser) Compile() error {
|
||||
p.typeMap = make(map[string]map[string]string)
|
||||
p.tsMap = make(map[string]map[string]string)
|
||||
p.patterns = make(map[string]string)
|
||||
p.tsModder = &tsModder{}
|
||||
var err error
|
||||
p.g, err = grok.NewWithConfig(&grok.Config{NamedCapturesOnly: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.CustomPatterns = DEFAULT_PATTERNS + p.CustomPatterns
|
||||
|
||||
if len(p.CustomPatterns) != 0 {
|
||||
scanner := bufio.NewScanner(strings.NewReader(p.CustomPatterns))
|
||||
p.addCustomPatterns(scanner)
|
||||
}
|
||||
|
||||
for _, filename := range p.CustomPatternFiles {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(bufio.NewReader(file))
|
||||
p.addCustomPatterns(scanner)
|
||||
}
|
||||
|
||||
return p.compileCustomPatterns()
|
||||
}
|
||||
|
||||
func (p *Parser) ParseLine(line string) (telegraf.Metric, error) {
|
||||
var err error
|
||||
var values map[string]string
|
||||
// the matching pattern string
|
||||
var patternName string
|
||||
for _, pattern := range p.Patterns {
|
||||
if values, err = p.g.Parse(pattern, line); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(values) != 0 {
|
||||
patternName = pattern
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(values) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
fields := make(map[string]interface{})
|
||||
tags := make(map[string]string)
|
||||
timestamp := time.Now()
|
||||
for k, v := range values {
|
||||
if k == "" || v == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var t string
|
||||
// check if pattern has some modifiers
|
||||
if types, ok := p.typeMap[patternName]; ok {
|
||||
t = types[k]
|
||||
}
|
||||
// if we didn't find a modifier, check if we have a timestamp layout
|
||||
if t == "" {
|
||||
if ts, ok := p.tsMap[patternName]; ok {
|
||||
// check if the modifier is a timestamp layout
|
||||
if layout, ok := ts[k]; ok {
|
||||
t = layout
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we didn't find a type OR timestamp modifier, assume string
|
||||
if t == "" {
|
||||
t = STRING
|
||||
}
|
||||
|
||||
switch t {
|
||||
case INT:
|
||||
iv, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
log.Printf("ERROR parsing %s to int: %s", v, err)
|
||||
} else {
|
||||
fields[k] = iv
|
||||
}
|
||||
case FLOAT:
|
||||
fv, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
log.Printf("ERROR parsing %s to float: %s", v, err)
|
||||
} else {
|
||||
fields[k] = fv
|
||||
}
|
||||
case DURATION:
|
||||
d, err := time.ParseDuration(v)
|
||||
if err != nil {
|
||||
log.Printf("ERROR parsing %s to duration: %s", v, err)
|
||||
} else {
|
||||
fields[k] = int64(d)
|
||||
}
|
||||
case TAG:
|
||||
tags[k] = v
|
||||
case STRING:
|
||||
fields[k] = strings.Trim(v, `"`)
|
||||
case "EPOCH":
|
||||
iv, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
log.Printf("ERROR parsing %s to int: %s", v, err)
|
||||
} else {
|
||||
timestamp = time.Unix(iv, 0)
|
||||
}
|
||||
case "EPOCH_NANO":
|
||||
iv, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
log.Printf("ERROR parsing %s to int: %s", v, err)
|
||||
} else {
|
||||
timestamp = time.Unix(0, iv)
|
||||
}
|
||||
case DROP:
|
||||
// goodbye!
|
||||
default:
|
||||
ts, err := time.Parse(t, v)
|
||||
if err == nil {
|
||||
timestamp = ts
|
||||
} else {
|
||||
log.Printf("ERROR parsing %s to time layout [%s]: %s", v, t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return telegraf.NewMetric("logparser_grok", tags, fields, p.tsModder.tsMod(timestamp))
|
||||
}
|
||||
|
||||
func (p *Parser) addCustomPatterns(scanner *bufio.Scanner) {
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if len(line) > 0 && line[0] != '#' {
|
||||
names := strings.SplitN(line, " ", 2)
|
||||
p.patterns[names[0]] = names[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) compileCustomPatterns() error {
|
||||
var err error
|
||||
// check if the pattern contains a subpattern that is already defined
|
||||
// replace it with the subpattern for modifier inheritance.
|
||||
for i := 0; i < 2; i++ {
|
||||
for name, pattern := range p.patterns {
|
||||
subNames := patternOnlyRe.FindAllStringSubmatch(pattern, -1)
|
||||
for _, subName := range subNames {
|
||||
if subPattern, ok := p.patterns[subName[1]]; ok {
|
||||
pattern = strings.Replace(pattern, subName[0], subPattern, 1)
|
||||
}
|
||||
}
|
||||
p.patterns[name] = pattern
|
||||
}
|
||||
}
|
||||
|
||||
// check if pattern contains modifiers. Parse them out if it does.
|
||||
for name, pattern := range p.patterns {
|
||||
if typedRe.MatchString(pattern) {
|
||||
// this pattern has modifiers, so parse out the modifiers
|
||||
pattern, err = p.parseTypedCaptures(name, pattern)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.patterns[name] = pattern
|
||||
}
|
||||
}
|
||||
|
||||
return p.g.AddPatternsFromMap(p.patterns)
|
||||
}
|
||||
|
||||
// parseTypedCaptures parses the capture types, and then deletes the type from
|
||||
// the line so that it is a valid "grok" pattern again.
|
||||
// ie,
|
||||
// %{NUMBER:bytes:int} => %{NUMBER:bytes} (stores %{NUMBER}->bytes->int)
|
||||
// %{IPORHOST:clientip:tag} => %{IPORHOST:clientip} (stores %{IPORHOST}->clientip->tag)
|
||||
func (p *Parser) parseTypedCaptures(name, pattern string) (string, error) {
|
||||
matches := typedRe.FindAllStringSubmatch(pattern, -1)
|
||||
|
||||
// grab the name of the capture pattern
|
||||
patternName := "%{" + name + "}"
|
||||
// create type map for this pattern
|
||||
p.typeMap[patternName] = make(map[string]string)
|
||||
p.tsMap[patternName] = make(map[string]string)
|
||||
|
||||
// boolean to verify that each pattern only has a single ts- data type.
|
||||
hasTimestamp := false
|
||||
for _, match := range matches {
|
||||
// regex capture 1 is the name of the capture
|
||||
// regex capture 2 is the type of the capture
|
||||
if strings.HasPrefix(match[2], "ts-") {
|
||||
if hasTimestamp {
|
||||
return pattern, fmt.Errorf("logparser pattern compile error: "+
|
||||
"Each pattern is allowed only one named "+
|
||||
"timestamp data type. pattern: %s", pattern)
|
||||
}
|
||||
if f, ok := timeFormats[match[2]]; ok {
|
||||
p.tsMap[patternName][match[1]] = f
|
||||
} else {
|
||||
p.tsMap[patternName][match[1]] = strings.TrimSuffix(strings.TrimPrefix(match[2], `ts-"`), `"`)
|
||||
}
|
||||
hasTimestamp = true
|
||||
} else {
|
||||
p.typeMap[patternName][match[1]] = match[2]
|
||||
}
|
||||
|
||||
// the modifier is not a valid part of a "grok" pattern, so remove it
|
||||
// from the pattern.
|
||||
pattern = strings.Replace(pattern, ":"+match[2]+"}", "}", 1)
|
||||
}
|
||||
|
||||
return pattern, nil
|
||||
}
|
||||
|
||||
// tsModder is a struct for incrementing identical timestamps of log lines
|
||||
// so that we don't push identical metrics that will get overwritten.
|
||||
type tsModder struct {
|
||||
dupe time.Time
|
||||
last time.Time
|
||||
incr time.Duration
|
||||
incrn time.Duration
|
||||
rollover time.Duration
|
||||
}
|
||||
|
||||
// tsMod increments the given timestamp one unit more from the previous
|
||||
// duplicate timestamp.
|
||||
// the increment unit is determined as the next smallest time unit below the
|
||||
// most significant time unit of ts.
|
||||
// ie, if the input is at ms precision, it will increment it 1µs.
|
||||
func (t *tsModder) tsMod(ts time.Time) time.Time {
|
||||
defer func() { t.last = ts }()
|
||||
// don't mod the time if we don't need to
|
||||
if t.last.IsZero() || ts.IsZero() {
|
||||
t.incrn = 0
|
||||
t.rollover = 0
|
||||
return ts
|
||||
}
|
||||
if !ts.Equal(t.last) && !ts.Equal(t.dupe) {
|
||||
t.incr = 0
|
||||
t.incrn = 0
|
||||
t.rollover = 0
|
||||
return ts
|
||||
}
|
||||
|
||||
if ts.Equal(t.last) {
|
||||
t.dupe = ts
|
||||
}
|
||||
|
||||
if ts.Equal(t.dupe) && t.incr == time.Duration(0) {
|
||||
tsNano := ts.UnixNano()
|
||||
|
||||
d := int64(10)
|
||||
counter := 1
|
||||
for {
|
||||
a := tsNano % d
|
||||
if a > 0 {
|
||||
break
|
||||
}
|
||||
d = d * 10
|
||||
counter++
|
||||
}
|
||||
|
||||
switch {
|
||||
case counter <= 6:
|
||||
t.incr = time.Nanosecond
|
||||
case counter <= 9:
|
||||
t.incr = time.Microsecond
|
||||
case counter > 9:
|
||||
t.incr = time.Millisecond
|
||||
}
|
||||
}
|
||||
|
||||
t.incrn++
|
||||
if t.incrn == 999 && t.incr > time.Nanosecond {
|
||||
t.rollover = t.incr * t.incrn
|
||||
t.incrn = 1
|
||||
t.incr = t.incr / 1000
|
||||
if t.incr < time.Nanosecond {
|
||||
t.incr = time.Nanosecond
|
||||
}
|
||||
}
|
||||
return ts.Add(t.incr*t.incrn + t.rollover)
|
||||
}
|
||||
508
plugins/inputs/logparser/grok/grok_test.go
Normal file
508
plugins/inputs/logparser/grok/grok_test.go
Normal file
@@ -0,0 +1,508 @@
|
||||
package grok
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var benchM telegraf.Metric
|
||||
|
||||
func Benchmark_ParseLine_CommonLogFormat(b *testing.B) {
|
||||
p := &Parser{
|
||||
Patterns: []string{"%{COMMON_LOG_FORMAT}"},
|
||||
}
|
||||
p.Compile()
|
||||
|
||||
var m telegraf.Metric
|
||||
for n := 0; n < b.N; n++ {
|
||||
m, _ = p.ParseLine(`127.0.0.1 user-identifier frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326`)
|
||||
}
|
||||
benchM = m
|
||||
}
|
||||
|
||||
func Benchmark_ParseLine_CombinedLogFormat(b *testing.B) {
|
||||
p := &Parser{
|
||||
Patterns: []string{"%{COMBINED_LOG_FORMAT}"},
|
||||
}
|
||||
p.Compile()
|
||||
|
||||
var m telegraf.Metric
|
||||
for n := 0; n < b.N; n++ {
|
||||
m, _ = p.ParseLine(`127.0.0.1 user-identifier frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326 "-" "Mozilla"`)
|
||||
}
|
||||
benchM = m
|
||||
}
|
||||
|
||||
func Benchmark_ParseLine_InfluxLog(b *testing.B) {
|
||||
p := &Parser{
|
||||
Patterns: []string{"%{INFLUXDB_HTTPD_LOG}"},
|
||||
}
|
||||
p.Compile()
|
||||
|
||||
var m telegraf.Metric
|
||||
for n := 0; n < b.N; n++ {
|
||||
m, _ = p.ParseLine(`[httpd] 192.168.1.1 - - [14/Jun/2016:11:33:29 +0100] "POST /write?consistency=any&db=telegraf&precision=ns&rp= HTTP/1.1" 204 0 "-" "InfluxDBClient" 6f61bc44-321b-11e6-8050-000000000000 2513`)
|
||||
}
|
||||
benchM = m
|
||||
}
|
||||
|
||||
func Benchmark_ParseLine_InfluxLog_NoMatch(b *testing.B) {
|
||||
p := &Parser{
|
||||
Patterns: []string{"%{INFLUXDB_HTTPD_LOG}"},
|
||||
}
|
||||
p.Compile()
|
||||
|
||||
var m telegraf.Metric
|
||||
for n := 0; n < b.N; n++ {
|
||||
m, _ = p.ParseLine(`[retention] 2016/06/14 14:38:24 retention policy shard deletion check commencing`)
|
||||
}
|
||||
benchM = m
|
||||
}
|
||||
|
||||
func Benchmark_ParseLine_CustomPattern(b *testing.B) {
|
||||
p := &Parser{
|
||||
Patterns: []string{"%{TEST_LOG_A}", "%{TEST_LOG_B}"},
|
||||
CustomPatterns: `
|
||||
DURATION %{NUMBER}[nuµm]?s
|
||||
RESPONSE_CODE %{NUMBER:response_code:tag}
|
||||
RESPONSE_TIME %{DURATION:response_time:duration}
|
||||
TEST_LOG_A %{NUMBER:myfloat:float} %{RESPONSE_CODE} %{IPORHOST:clientip} %{RESPONSE_TIME}
|
||||
`,
|
||||
}
|
||||
p.Compile()
|
||||
|
||||
var m telegraf.Metric
|
||||
for n := 0; n < b.N; n++ {
|
||||
m, _ = p.ParseLine(`[04/Jun/2016:12:41:45 +0100] 1.25 200 192.168.1.1 5.432µs 101`)
|
||||
}
|
||||
benchM = m
|
||||
}
|
||||
|
||||
func TestBuiltinInfluxdbHttpd(t *testing.T) {
|
||||
p := &Parser{
|
||||
Patterns: []string{"%{INFLUXDB_HTTPD_LOG}"},
|
||||
}
|
||||
assert.NoError(t, p.Compile())
|
||||
|
||||
// Parse an influxdb POST request
|
||||
m, err := p.ParseLine(`[httpd] ::1 - - [14/Jun/2016:11:33:29 +0100] "POST /write?consistency=any&db=telegraf&precision=ns&rp= HTTP/1.1" 204 0 "-" "InfluxDBClient" 6f61bc44-321b-11e6-8050-000000000000 2513`)
|
||||
require.NotNil(t, m)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t,
|
||||
map[string]interface{}{
|
||||
"resp_bytes": int64(0),
|
||||
"auth": "-",
|
||||
"client_ip": "::1",
|
||||
"resp_code": int64(204),
|
||||
"http_version": float64(1.1),
|
||||
"ident": "-",
|
||||
"referrer": "-",
|
||||
"request": "/write?consistency=any&db=telegraf&precision=ns&rp=",
|
||||
"response_time_us": int64(2513),
|
||||
"agent": "InfluxDBClient",
|
||||
},
|
||||
m.Fields())
|
||||
assert.Equal(t, map[string]string{"verb": "POST"}, m.Tags())
|
||||
|
||||
// Parse an influxdb GET request
|
||||
m, err = p.ParseLine(`[httpd] ::1 - - [14/Jun/2016:12:10:02 +0100] "GET /query?db=telegraf&q=SELECT+bytes%2Cresponse_time_us+FROM+logparser_grok+WHERE+http_method+%3D+%27GET%27+AND+response_time_us+%3E+0+AND+time+%3E+now%28%29+-+1h HTTP/1.1" 200 578 "http://localhost:8083/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.84 Safari/537.36" 8a3806f1-3220-11e6-8006-000000000000 988`)
|
||||
require.NotNil(t, m)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t,
|
||||
map[string]interface{}{
|
||||
"resp_bytes": int64(578),
|
||||
"auth": "-",
|
||||
"client_ip": "::1",
|
||||
"resp_code": int64(200),
|
||||
"http_version": float64(1.1),
|
||||
"ident": "-",
|
||||
"referrer": "http://localhost:8083/",
|
||||
"request": "/query?db=telegraf&q=SELECT+bytes%2Cresponse_time_us+FROM+logparser_grok+WHERE+http_method+%3D+%27GET%27+AND+response_time_us+%3E+0+AND+time+%3E+now%28%29+-+1h",
|
||||
"response_time_us": int64(988),
|
||||
"agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.84 Safari/537.36",
|
||||
},
|
||||
m.Fields())
|
||||
assert.Equal(t, map[string]string{"verb": "GET"}, m.Tags())
|
||||
}
|
||||
|
||||
// common log format
|
||||
// 127.0.0.1 user-identifier frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326
|
||||
func TestBuiltinCommonLogFormat(t *testing.T) {
|
||||
p := &Parser{
|
||||
Patterns: []string{"%{COMMON_LOG_FORMAT}"},
|
||||
}
|
||||
assert.NoError(t, p.Compile())
|
||||
|
||||
// Parse an influxdb POST request
|
||||
m, err := p.ParseLine(`127.0.0.1 user-identifier frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326`)
|
||||
require.NotNil(t, m)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t,
|
||||
map[string]interface{}{
|
||||
"resp_bytes": int64(2326),
|
||||
"auth": "frank",
|
||||
"client_ip": "127.0.0.1",
|
||||
"resp_code": int64(200),
|
||||
"http_version": float64(1.0),
|
||||
"ident": "user-identifier",
|
||||
"request": "/apache_pb.gif",
|
||||
},
|
||||
m.Fields())
|
||||
assert.Equal(t, map[string]string{"verb": "GET"}, m.Tags())
|
||||
}
|
||||
|
||||
// combined log format
|
||||
// 127.0.0.1 user-identifier frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326 "-" "Mozilla"
|
||||
func TestBuiltinCombinedLogFormat(t *testing.T) {
|
||||
p := &Parser{
|
||||
Patterns: []string{"%{COMBINED_LOG_FORMAT}"},
|
||||
}
|
||||
assert.NoError(t, p.Compile())
|
||||
|
||||
// Parse an influxdb POST request
|
||||
m, err := p.ParseLine(`127.0.0.1 user-identifier frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326 "-" "Mozilla"`)
|
||||
require.NotNil(t, m)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t,
|
||||
map[string]interface{}{
|
||||
"resp_bytes": int64(2326),
|
||||
"auth": "frank",
|
||||
"client_ip": "127.0.0.1",
|
||||
"resp_code": int64(200),
|
||||
"http_version": float64(1.0),
|
||||
"ident": "user-identifier",
|
||||
"request": "/apache_pb.gif",
|
||||
"referrer": "-",
|
||||
"agent": "Mozilla",
|
||||
},
|
||||
m.Fields())
|
||||
assert.Equal(t, map[string]string{"verb": "GET"}, m.Tags())
|
||||
}
|
||||
|
||||
func TestCompileStringAndParse(t *testing.T) {
|
||||
p := &Parser{
|
||||
Patterns: []string{"%{TEST_LOG_A}", "%{TEST_LOG_B}"},
|
||||
CustomPatterns: `
|
||||
DURATION %{NUMBER}[nuµm]?s
|
||||
RESPONSE_CODE %{NUMBER:response_code:tag}
|
||||
RESPONSE_TIME %{DURATION:response_time:duration}
|
||||
TEST_LOG_A %{NUMBER:myfloat:float} %{RESPONSE_CODE} %{IPORHOST:clientip} %{RESPONSE_TIME}
|
||||
`,
|
||||
}
|
||||
assert.NoError(t, p.Compile())
|
||||
|
||||
metricA, err := p.ParseLine(`1.25 200 192.168.1.1 5.432µs`)
|
||||
require.NotNil(t, metricA)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t,
|
||||
map[string]interface{}{
|
||||
"clientip": "192.168.1.1",
|
||||
"myfloat": float64(1.25),
|
||||
"response_time": int64(5432),
|
||||
},
|
||||
metricA.Fields())
|
||||
assert.Equal(t, map[string]string{"response_code": "200"}, metricA.Tags())
|
||||
}
|
||||
|
||||
func TestParseEpochNano(t *testing.T) {
|
||||
p := &Parser{
|
||||
Patterns: []string{"%{MYAPP}"},
|
||||
CustomPatterns: `
|
||||
MYAPP %{POSINT:ts:ts-epochnano} response_time=%{POSINT:response_time:int} mymetric=%{NUMBER:metric:float}
|
||||
`,
|
||||
}
|
||||
assert.NoError(t, p.Compile())
|
||||
|
||||
metricA, err := p.ParseLine(`1466004605359052000 response_time=20821 mymetric=10890.645`)
|
||||
require.NotNil(t, metricA)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t,
|
||||
map[string]interface{}{
|
||||
"response_time": int64(20821),
|
||||
"metric": float64(10890.645),
|
||||
},
|
||||
metricA.Fields())
|
||||
assert.Equal(t, map[string]string{}, metricA.Tags())
|
||||
assert.Equal(t, time.Unix(0, 1466004605359052000), metricA.Time())
|
||||
}
|
||||
|
||||
func TestParseEpoch(t *testing.T) {
|
||||
p := &Parser{
|
||||
Patterns: []string{"%{MYAPP}"},
|
||||
CustomPatterns: `
|
||||
MYAPP %{POSINT:ts:ts-epoch} response_time=%{POSINT:response_time:int} mymetric=%{NUMBER:metric:float}
|
||||
`,
|
||||
}
|
||||
assert.NoError(t, p.Compile())
|
||||
|
||||
metricA, err := p.ParseLine(`1466004605 response_time=20821 mymetric=10890.645`)
|
||||
require.NotNil(t, metricA)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t,
|
||||
map[string]interface{}{
|
||||
"response_time": int64(20821),
|
||||
"metric": float64(10890.645),
|
||||
},
|
||||
metricA.Fields())
|
||||
assert.Equal(t, map[string]string{}, metricA.Tags())
|
||||
assert.Equal(t, time.Unix(1466004605, 0), metricA.Time())
|
||||
}
|
||||
|
||||
func TestParseEpochErrors(t *testing.T) {
|
||||
p := &Parser{
|
||||
Patterns: []string{"%{MYAPP}"},
|
||||
CustomPatterns: `
|
||||
MYAPP %{WORD:ts:ts-epoch} response_time=%{POSINT:response_time:int} mymetric=%{NUMBER:metric:float}
|
||||
`,
|
||||
}
|
||||
assert.NoError(t, p.Compile())
|
||||
|
||||
_, err := p.ParseLine(`foobar response_time=20821 mymetric=10890.645`)
|
||||
assert.NoError(t, err)
|
||||
|
||||
p = &Parser{
|
||||
Patterns: []string{"%{MYAPP}"},
|
||||
CustomPatterns: `
|
||||
MYAPP %{WORD:ts:ts-epochnano} response_time=%{POSINT:response_time:int} mymetric=%{NUMBER:metric:float}
|
||||
`,
|
||||
}
|
||||
assert.NoError(t, p.Compile())
|
||||
|
||||
_, err = p.ParseLine(`foobar response_time=20821 mymetric=10890.645`)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCompileFileAndParse(t *testing.T) {
|
||||
p := &Parser{
|
||||
Patterns: []string{"%{TEST_LOG_A}", "%{TEST_LOG_B}"},
|
||||
CustomPatternFiles: []string{"./testdata/test-patterns"},
|
||||
}
|
||||
assert.NoError(t, p.Compile())
|
||||
|
||||
metricA, err := p.ParseLine(`[04/Jun/2016:12:41:45 +0100] 1.25 200 192.168.1.1 5.432µs 101`)
|
||||
require.NotNil(t, metricA)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t,
|
||||
map[string]interface{}{
|
||||
"clientip": "192.168.1.1",
|
||||
"myfloat": float64(1.25),
|
||||
"response_time": int64(5432),
|
||||
"myint": int64(101),
|
||||
},
|
||||
metricA.Fields())
|
||||
assert.Equal(t, map[string]string{"response_code": "200"}, metricA.Tags())
|
||||
assert.Equal(t,
|
||||
time.Date(2016, time.June, 4, 12, 41, 45, 0, time.FixedZone("foo", 60*60)).Nanosecond(),
|
||||
metricA.Time().Nanosecond())
|
||||
|
||||
metricB, err := p.ParseLine(`[04/06/2016--12:41:45] 1.25 mystring dropme nomodifier`)
|
||||
require.NotNil(t, metricB)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t,
|
||||
map[string]interface{}{
|
||||
"myfloat": 1.25,
|
||||
"mystring": "mystring",
|
||||
"nomodifier": "nomodifier",
|
||||
},
|
||||
metricB.Fields())
|
||||
assert.Equal(t, map[string]string{}, metricB.Tags())
|
||||
assert.Equal(t,
|
||||
time.Date(2016, time.June, 4, 12, 41, 45, 0, time.FixedZone("foo", 60*60)).Nanosecond(),
|
||||
metricB.Time().Nanosecond())
|
||||
}
|
||||
|
||||
func TestCompileNoModifiersAndParse(t *testing.T) {
|
||||
p := &Parser{
|
||||
Patterns: []string{"%{TEST_LOG_C}"},
|
||||
CustomPatterns: `
|
||||
DURATION %{NUMBER}[nuµm]?s
|
||||
TEST_LOG_C %{NUMBER:myfloat} %{NUMBER} %{IPORHOST:clientip} %{DURATION:rt}
|
||||
`,
|
||||
}
|
||||
assert.NoError(t, p.Compile())
|
||||
|
||||
metricA, err := p.ParseLine(`1.25 200 192.168.1.1 5.432µs`)
|
||||
require.NotNil(t, metricA)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t,
|
||||
map[string]interface{}{
|
||||
"clientip": "192.168.1.1",
|
||||
"myfloat": "1.25",
|
||||
"rt": "5.432µs",
|
||||
},
|
||||
metricA.Fields())
|
||||
assert.Equal(t, map[string]string{}, metricA.Tags())
|
||||
}
|
||||
|
||||
func TestCompileNoNamesAndParse(t *testing.T) {
|
||||
p := &Parser{
|
||||
Patterns: []string{"%{TEST_LOG_C}"},
|
||||
CustomPatterns: `
|
||||
DURATION %{NUMBER}[nuµm]?s
|
||||
TEST_LOG_C %{NUMBER} %{NUMBER} %{IPORHOST} %{DURATION}
|
||||
`,
|
||||
}
|
||||
assert.NoError(t, p.Compile())
|
||||
|
||||
metricA, err := p.ParseLine(`1.25 200 192.168.1.1 5.432µs`)
|
||||
require.Nil(t, metricA)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestParseNoMatch(t *testing.T) {
|
||||
p := &Parser{
|
||||
Patterns: []string{"%{TEST_LOG_A}", "%{TEST_LOG_B}"},
|
||||
CustomPatternFiles: []string{"./testdata/test-patterns"},
|
||||
}
|
||||
assert.NoError(t, p.Compile())
|
||||
|
||||
metricA, err := p.ParseLine(`[04/Jun/2016:12:41:45 +0100] notnumber 200 192.168.1.1 5.432µs 101`)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, metricA)
|
||||
}
|
||||
|
||||
func TestCompileErrors(t *testing.T) {
|
||||
// Compile fails because there are multiple timestamps:
|
||||
p := &Parser{
|
||||
Patterns: []string{"%{TEST_LOG_A}", "%{TEST_LOG_B}"},
|
||||
CustomPatterns: `
|
||||
TEST_LOG_A %{HTTPDATE:ts1:ts-httpd} %{HTTPDATE:ts2:ts-httpd} %{NUMBER:mynum:int}
|
||||
`,
|
||||
}
|
||||
assert.Error(t, p.Compile())
|
||||
|
||||
// Compile fails because file doesn't exist:
|
||||
p = &Parser{
|
||||
Patterns: []string{"%{TEST_LOG_A}", "%{TEST_LOG_B}"},
|
||||
CustomPatternFiles: []string{"/tmp/foo/bar/baz"},
|
||||
}
|
||||
assert.Error(t, p.Compile())
|
||||
}
|
||||
|
||||
func TestParseErrors(t *testing.T) {
|
||||
// Parse fails because the pattern doesn't exist
|
||||
p := &Parser{
|
||||
Patterns: []string{"%{TEST_LOG_B}"},
|
||||
CustomPatterns: `
|
||||
TEST_LOG_A %{HTTPDATE:ts:ts-httpd} %{WORD:myword:int} %{}
|
||||
`,
|
||||
}
|
||||
assert.NoError(t, p.Compile())
|
||||
_, err := p.ParseLine(`[04/Jun/2016:12:41:45 +0100] notnumber 200 192.168.1.1 5.432µs 101`)
|
||||
assert.Error(t, err)
|
||||
|
||||
// Parse fails because myword is not an int
|
||||
p = &Parser{
|
||||
Patterns: []string{"%{TEST_LOG_A}"},
|
||||
CustomPatterns: `
|
||||
TEST_LOG_A %{HTTPDATE:ts:ts-httpd} %{WORD:myword:int}
|
||||
`,
|
||||
}
|
||||
assert.NoError(t, p.Compile())
|
||||
_, err = p.ParseLine(`04/Jun/2016:12:41:45 +0100 notnumber`)
|
||||
assert.Error(t, err)
|
||||
|
||||
// Parse fails because myword is not a float
|
||||
p = &Parser{
|
||||
Patterns: []string{"%{TEST_LOG_A}"},
|
||||
CustomPatterns: `
|
||||
TEST_LOG_A %{HTTPDATE:ts:ts-httpd} %{WORD:myword:float}
|
||||
`,
|
||||
}
|
||||
assert.NoError(t, p.Compile())
|
||||
_, err = p.ParseLine(`04/Jun/2016:12:41:45 +0100 notnumber`)
|
||||
assert.Error(t, err)
|
||||
|
||||
// Parse fails because myword is not a duration
|
||||
p = &Parser{
|
||||
Patterns: []string{"%{TEST_LOG_A}"},
|
||||
CustomPatterns: `
|
||||
TEST_LOG_A %{HTTPDATE:ts:ts-httpd} %{WORD:myword:duration}
|
||||
`,
|
||||
}
|
||||
assert.NoError(t, p.Compile())
|
||||
_, err = p.ParseLine(`04/Jun/2016:12:41:45 +0100 notnumber`)
|
||||
assert.Error(t, err)
|
||||
|
||||
// Parse fails because the time layout is wrong.
|
||||
p = &Parser{
|
||||
Patterns: []string{"%{TEST_LOG_A}"},
|
||||
CustomPatterns: `
|
||||
TEST_LOG_A %{HTTPDATE:ts:ts-unix} %{WORD:myword:duration}
|
||||
`,
|
||||
}
|
||||
assert.NoError(t, p.Compile())
|
||||
_, err = p.ParseLine(`04/Jun/2016:12:41:45 +0100 notnumber`)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestTsModder(t *testing.T) {
|
||||
tsm := &tsModder{}
|
||||
|
||||
reftime := time.Date(2006, time.December, 1, 1, 1, 1, int(time.Millisecond), time.UTC)
|
||||
modt := tsm.tsMod(reftime)
|
||||
assert.Equal(t, reftime, modt)
|
||||
modt = tsm.tsMod(reftime)
|
||||
assert.Equal(t, reftime.Add(time.Microsecond*1), modt)
|
||||
modt = tsm.tsMod(reftime)
|
||||
assert.Equal(t, reftime.Add(time.Microsecond*2), modt)
|
||||
modt = tsm.tsMod(reftime)
|
||||
assert.Equal(t, reftime.Add(time.Microsecond*3), modt)
|
||||
|
||||
reftime = time.Date(2006, time.December, 1, 1, 1, 1, int(time.Microsecond), time.UTC)
|
||||
modt = tsm.tsMod(reftime)
|
||||
assert.Equal(t, reftime, modt)
|
||||
modt = tsm.tsMod(reftime)
|
||||
assert.Equal(t, reftime.Add(time.Nanosecond*1), modt)
|
||||
modt = tsm.tsMod(reftime)
|
||||
assert.Equal(t, reftime.Add(time.Nanosecond*2), modt)
|
||||
modt = tsm.tsMod(reftime)
|
||||
assert.Equal(t, reftime.Add(time.Nanosecond*3), modt)
|
||||
|
||||
reftime = time.Date(2006, time.December, 1, 1, 1, 1, int(time.Microsecond)*999, time.UTC)
|
||||
modt = tsm.tsMod(reftime)
|
||||
assert.Equal(t, reftime, modt)
|
||||
modt = tsm.tsMod(reftime)
|
||||
assert.Equal(t, reftime.Add(time.Nanosecond*1), modt)
|
||||
modt = tsm.tsMod(reftime)
|
||||
assert.Equal(t, reftime.Add(time.Nanosecond*2), modt)
|
||||
modt = tsm.tsMod(reftime)
|
||||
assert.Equal(t, reftime.Add(time.Nanosecond*3), modt)
|
||||
|
||||
reftime = time.Date(2006, time.December, 1, 1, 1, 1, 0, time.UTC)
|
||||
modt = tsm.tsMod(reftime)
|
||||
assert.Equal(t, reftime, modt)
|
||||
modt = tsm.tsMod(reftime)
|
||||
assert.Equal(t, reftime.Add(time.Millisecond*1), modt)
|
||||
modt = tsm.tsMod(reftime)
|
||||
assert.Equal(t, reftime.Add(time.Millisecond*2), modt)
|
||||
modt = tsm.tsMod(reftime)
|
||||
assert.Equal(t, reftime.Add(time.Millisecond*3), modt)
|
||||
|
||||
reftime = time.Time{}
|
||||
modt = tsm.tsMod(reftime)
|
||||
assert.Equal(t, reftime, modt)
|
||||
}
|
||||
|
||||
func TestTsModder_Rollover(t *testing.T) {
|
||||
tsm := &tsModder{}
|
||||
|
||||
reftime := time.Date(2006, time.December, 1, 1, 1, 1, int(time.Millisecond), time.UTC)
|
||||
modt := tsm.tsMod(reftime)
|
||||
for i := 1; i < 1000; i++ {
|
||||
modt = tsm.tsMod(reftime)
|
||||
}
|
||||
assert.Equal(t, reftime.Add(time.Microsecond*999+time.Nanosecond), modt)
|
||||
|
||||
reftime = time.Date(2006, time.December, 1, 1, 1, 1, int(time.Microsecond), time.UTC)
|
||||
modt = tsm.tsMod(reftime)
|
||||
for i := 1; i < 1001; i++ {
|
||||
modt = tsm.tsMod(reftime)
|
||||
}
|
||||
assert.Equal(t, reftime.Add(time.Nanosecond*1000), modt)
|
||||
}
|
||||
80
plugins/inputs/logparser/grok/influx_patterns.go
Normal file
80
plugins/inputs/logparser/grok/influx_patterns.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package grok
|
||||
|
||||
// THIS SHOULD BE KEPT IN-SYNC WITH patterns/influx-patterns
|
||||
const DEFAULT_PATTERNS = `
|
||||
# Captures are a slightly modified version of logstash "grok" patterns, with
|
||||
# the format %{<capture syntax>[:<semantic name>][:<modifier>]}
|
||||
# By default all named captures are converted into string fields.
|
||||
# Modifiers can be used to convert captures to other types or tags.
|
||||
# Timestamp modifiers can be used to convert captures to the timestamp of the
|
||||
# parsed metric.
|
||||
|
||||
# View logstash grok pattern docs here:
|
||||
# https://www.elastic.co/guide/en/logstash/current/plugins-filters-grok.html
|
||||
# All default logstash patterns are supported, these can be viewed here:
|
||||
# https://github.com/logstash-plugins/logstash-patterns-core/blob/master/patterns/grok-patterns
|
||||
|
||||
# Available modifiers:
|
||||
# string (default if nothing is specified)
|
||||
# int
|
||||
# float
|
||||
# duration (ie, 5.23ms gets converted to int nanoseconds)
|
||||
# tag (converts the field into a tag)
|
||||
# drop (drops the field completely)
|
||||
# Timestamp modifiers:
|
||||
# ts-ansic ("Mon Jan _2 15:04:05 2006")
|
||||
# ts-unix ("Mon Jan _2 15:04:05 MST 2006")
|
||||
# ts-ruby ("Mon Jan 02 15:04:05 -0700 2006")
|
||||
# ts-rfc822 ("02 Jan 06 15:04 MST")
|
||||
# ts-rfc822z ("02 Jan 06 15:04 -0700")
|
||||
# ts-rfc850 ("Monday, 02-Jan-06 15:04:05 MST")
|
||||
# ts-rfc1123 ("Mon, 02 Jan 2006 15:04:05 MST")
|
||||
# ts-rfc1123z ("Mon, 02 Jan 2006 15:04:05 -0700")
|
||||
# ts-rfc3339 ("2006-01-02T15:04:05Z07:00")
|
||||
# ts-rfc3339nano ("2006-01-02T15:04:05.999999999Z07:00")
|
||||
# ts-httpd ("02/Jan/2006:15:04:05 -0700")
|
||||
# ts-epoch (seconds since unix epoch)
|
||||
# ts-epochnano (nanoseconds since unix epoch)
|
||||
# ts-"CUSTOM"
|
||||
# CUSTOM time layouts must be within quotes and be the representation of the
|
||||
# "reference time", which is Mon Jan 2 15:04:05 -0700 MST 2006
|
||||
# See https://golang.org/pkg/time/#Parse for more details.
|
||||
|
||||
# Example log file pattern, example log looks like this:
|
||||
# [04/Jun/2016:12:41:45 +0100] 1.25 200 192.168.1.1 5.432µs
|
||||
# Breakdown of the DURATION pattern below:
|
||||
# NUMBER is a builtin logstash grok pattern matching float & int numbers.
|
||||
# [nuµm]? is a regex specifying 0 or 1 of the characters within brackets.
|
||||
# s is also regex, this pattern must end in "s".
|
||||
# so DURATION will match something like '5.324ms' or '6.1µs' or '10s'
|
||||
DURATION %{NUMBER}[nuµm]?s
|
||||
RESPONSE_CODE %{NUMBER:response_code:tag}
|
||||
RESPONSE_TIME %{DURATION:response_time_ns:duration}
|
||||
EXAMPLE_LOG \[%{HTTPDATE:ts:ts-httpd}\] %{NUMBER:myfloat:float} %{RESPONSE_CODE} %{IPORHOST:clientip} %{RESPONSE_TIME}
|
||||
|
||||
# Wider-ranging username matching vs. logstash built-in %{USER}
|
||||
NGUSERNAME [a-zA-Z\.\@\-\+_%]+
|
||||
NGUSER %{NGUSERNAME}
|
||||
|
||||
##
|
||||
## COMMON LOG PATTERNS
|
||||
##
|
||||
|
||||
# InfluxDB log patterns
|
||||
CLIENT (?:%{IPORHOST}|%{HOSTPORT}|::1)
|
||||
INFLUXDB_HTTPD_LOG \[httpd\] %{COMBINED_LOG_FORMAT} %{UUID:uuid:drop} %{NUMBER:response_time_us:int}
|
||||
|
||||
# apache & nginx logs, this is also known as the "common log format"
|
||||
# see https://en.wikipedia.org/wiki/Common_Log_Format
|
||||
COMMON_LOG_FORMAT %{CLIENT:client_ip} %{NGUSER:ident} %{NGUSER:auth} \[%{HTTPDATE:ts:ts-httpd}\] "(?:%{WORD:verb:tag} %{NOTSPACE:request}(?: HTTP/%{NUMBER:http_version:float})?|%{DATA})" %{NUMBER:resp_code:int} (?:%{NUMBER:resp_bytes:int}|-)
|
||||
|
||||
# Combined log format is the same as the common log format but with the addition
|
||||
# of two quoted strings at the end for "referrer" and "agent"
|
||||
# See Examples at http://httpd.apache.org/docs/current/mod/mod_log_config.html
|
||||
COMBINED_LOG_FORMAT %{COMMON_LOG_FORMAT} %{QS:referrer} %{QS:agent}
|
||||
|
||||
# HTTPD log formats
|
||||
HTTPD20_ERRORLOG \[%{HTTPDERROR_DATE:timestamp}\] \[%{LOGLEVEL:loglevel:tag}\] (?:\[client %{IPORHOST:clientip}\] ){0,1}%{GREEDYDATA:errormsg}
|
||||
HTTPD24_ERRORLOG \[%{HTTPDERROR_DATE:timestamp}\] \[%{WORD:module}:%{LOGLEVEL:loglevel:tag}\] \[pid %{POSINT:pid:int}:tid %{NUMBER:tid:int}\]( \(%{POSINT:proxy_errorcode:int}\)%{DATA:proxy_errormessage}:)?( \[client %{IPORHOST:client}:%{POSINT:clientport}\])? %{DATA:errorcode}: %{GREEDYDATA:message}
|
||||
HTTPD_ERRORLOG %{HTTPD20_ERRORLOG}|%{HTTPD24_ERRORLOG}
|
||||
`
|
||||
75
plugins/inputs/logparser/grok/patterns/influx-patterns
Normal file
75
plugins/inputs/logparser/grok/patterns/influx-patterns
Normal file
@@ -0,0 +1,75 @@
|
||||
# Captures are a slightly modified version of logstash "grok" patterns, with
|
||||
# the format %{<capture syntax>[:<semantic name>][:<modifier>]}
|
||||
# By default all named captures are converted into string fields.
|
||||
# Modifiers can be used to convert captures to other types or tags.
|
||||
# Timestamp modifiers can be used to convert captures to the timestamp of the
|
||||
# parsed metric.
|
||||
|
||||
# View logstash grok pattern docs here:
|
||||
# https://www.elastic.co/guide/en/logstash/current/plugins-filters-grok.html
|
||||
# All default logstash patterns are supported, these can be viewed here:
|
||||
# https://github.com/logstash-plugins/logstash-patterns-core/blob/master/patterns/grok-patterns
|
||||
|
||||
# Available modifiers:
|
||||
# string (default if nothing is specified)
|
||||
# int
|
||||
# float
|
||||
# duration (ie, 5.23ms gets converted to int nanoseconds)
|
||||
# tag (converts the field into a tag)
|
||||
# drop (drops the field completely)
|
||||
# Timestamp modifiers:
|
||||
# ts-ansic ("Mon Jan _2 15:04:05 2006")
|
||||
# ts-unix ("Mon Jan _2 15:04:05 MST 2006")
|
||||
# ts-ruby ("Mon Jan 02 15:04:05 -0700 2006")
|
||||
# ts-rfc822 ("02 Jan 06 15:04 MST")
|
||||
# ts-rfc822z ("02 Jan 06 15:04 -0700")
|
||||
# ts-rfc850 ("Monday, 02-Jan-06 15:04:05 MST")
|
||||
# ts-rfc1123 ("Mon, 02 Jan 2006 15:04:05 MST")
|
||||
# ts-rfc1123z ("Mon, 02 Jan 2006 15:04:05 -0700")
|
||||
# ts-rfc3339 ("2006-01-02T15:04:05Z07:00")
|
||||
# ts-rfc3339nano ("2006-01-02T15:04:05.999999999Z07:00")
|
||||
# ts-httpd ("02/Jan/2006:15:04:05 -0700")
|
||||
# ts-epoch (seconds since unix epoch)
|
||||
# ts-epochnano (nanoseconds since unix epoch)
|
||||
# ts-"CUSTOM"
|
||||
# CUSTOM time layouts must be within quotes and be the representation of the
|
||||
# "reference time", which is Mon Jan 2 15:04:05 -0700 MST 2006
|
||||
# See https://golang.org/pkg/time/#Parse for more details.
|
||||
|
||||
# Example log file pattern, example log looks like this:
|
||||
# [04/Jun/2016:12:41:45 +0100] 1.25 200 192.168.1.1 5.432µs
|
||||
# Breakdown of the DURATION pattern below:
|
||||
# NUMBER is a builtin logstash grok pattern matching float & int numbers.
|
||||
# [nuµm]? is a regex specifying 0 or 1 of the characters within brackets.
|
||||
# s is also regex, this pattern must end in "s".
|
||||
# so DURATION will match something like '5.324ms' or '6.1µs' or '10s'
|
||||
DURATION %{NUMBER}[nuµm]?s
|
||||
RESPONSE_CODE %{NUMBER:response_code:tag}
|
||||
RESPONSE_TIME %{DURATION:response_time_ns:duration}
|
||||
EXAMPLE_LOG \[%{HTTPDATE:ts:ts-httpd}\] %{NUMBER:myfloat:float} %{RESPONSE_CODE} %{IPORHOST:clientip} %{RESPONSE_TIME}
|
||||
|
||||
# Wider-ranging username matching vs. logstash built-in %{USER}
|
||||
NGUSERNAME [a-zA-Z\.\@\-\+_%]+
|
||||
NGUSER %{NGUSERNAME}
|
||||
|
||||
##
|
||||
## COMMON LOG PATTERNS
|
||||
##
|
||||
|
||||
# InfluxDB log patterns
|
||||
CLIENT (?:%{IPORHOST}|%{HOSTPORT}|::1)
|
||||
INFLUXDB_HTTPD_LOG \[httpd\] %{COMBINED_LOG_FORMAT} %{UUID:uuid:drop} %{NUMBER:response_time_us:int}
|
||||
|
||||
# apache & nginx logs, this is also known as the "common log format"
|
||||
# see https://en.wikipedia.org/wiki/Common_Log_Format
|
||||
COMMON_LOG_FORMAT %{CLIENT:client_ip} %{NGUSER:ident} %{NGUSER:auth} \[%{HTTPDATE:ts:ts-httpd}\] "(?:%{WORD:verb:tag} %{NOTSPACE:request}(?: HTTP/%{NUMBER:http_version:float})?|%{DATA})" %{NUMBER:resp_code:int} (?:%{NUMBER:resp_bytes:int}|-)
|
||||
|
||||
# Combined log format is the same as the common log format but with the addition
|
||||
# of two quoted strings at the end for "referrer" and "agent"
|
||||
# See Examples at http://httpd.apache.org/docs/current/mod/mod_log_config.html
|
||||
COMBINED_LOG_FORMAT %{COMMON_LOG_FORMAT} %{QS:referrer} %{QS:agent}
|
||||
|
||||
# HTTPD log formats
|
||||
HTTPD20_ERRORLOG \[%{HTTPDERROR_DATE:timestamp}\] \[%{LOGLEVEL:loglevel:tag}\] (?:\[client %{IPORHOST:clientip}\] ){0,1}%{GREEDYDATA:errormsg}
|
||||
HTTPD24_ERRORLOG \[%{HTTPDERROR_DATE:timestamp}\] \[%{WORD:module}:%{LOGLEVEL:loglevel:tag}\] \[pid %{POSINT:pid:int}:tid %{NUMBER:tid:int}\]( \(%{POSINT:proxy_errorcode:int}\)%{DATA:proxy_errormessage}:)?( \[client %{IPORHOST:client}:%{POSINT:clientport}\])? %{DATA:errorcode}: %{GREEDYDATA:message}
|
||||
HTTPD_ERRORLOG %{HTTPD20_ERRORLOG}|%{HTTPD24_ERRORLOG}
|
||||
14
plugins/inputs/logparser/grok/testdata/test-patterns
vendored
Normal file
14
plugins/inputs/logparser/grok/testdata/test-patterns
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# Test A log line:
|
||||
# [04/Jun/2016:12:41:45 +0100] 1.25 200 192.168.1.1 5.432µs 101
|
||||
DURATION %{NUMBER}[nuµm]?s
|
||||
RESPONSE_CODE %{NUMBER:response_code:tag}
|
||||
RESPONSE_TIME %{DURATION:response_time:duration}
|
||||
TEST_LOG_A \[%{HTTPDATE:timestamp:ts-httpd}\] %{NUMBER:myfloat:float} %{RESPONSE_CODE} %{IPORHOST:clientip} %{RESPONSE_TIME} %{NUMBER:myint:int}
|
||||
|
||||
# Test B log line:
|
||||
# [04/06/2016--12:41:45] 1.25 mystring dropme nomodifier
|
||||
TEST_TIMESTAMP %{MONTHDAY}/%{MONTHNUM}/%{YEAR}--%{TIME}
|
||||
TEST_LOG_B \[%{TEST_TIMESTAMP:timestamp:ts-"02/01/2006--15:04:05"}\] %{NUMBER:myfloat:float} %{WORD:mystring:string} %{WORD:dropme:drop} %{WORD:nomodifier}
|
||||
|
||||
TEST_TIMESTAMP %{MONTHDAY}/%{MONTHNUM}/%{YEAR}--%{TIME}
|
||||
TEST_LOG_BAD \[%{TEST_TIMESTAMP:timestamp:ts-"02/01/2006--15:04:05"}\] %{NUMBER:myfloat:float} %{WORD:mystring:int} %{WORD:dropme:drop} %{WORD:nomodifier}
|
||||
1
plugins/inputs/logparser/grok/testdata/test_a.log
vendored
Normal file
1
plugins/inputs/logparser/grok/testdata/test_a.log
vendored
Normal file
@@ -0,0 +1 @@
|
||||
[04/Jun/2016:12:41:45 +0100] 1.25 200 192.168.1.1 5.432µs 101
|
||||
1
plugins/inputs/logparser/grok/testdata/test_b.log
vendored
Normal file
1
plugins/inputs/logparser/grok/testdata/test_b.log
vendored
Normal file
@@ -0,0 +1 @@
|
||||
[04/06/2016--12:41:45] 1.25 mystring dropme nomodifier
|
||||
228
plugins/inputs/logparser/logparser.go
Normal file
228
plugins/inputs/logparser/logparser.go
Normal file
@@ -0,0 +1,228 @@
|
||||
package logparser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/hpcloud/tail"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/internal/globpath"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
|
||||
// Parsers
|
||||
"github.com/influxdata/telegraf/plugins/inputs/logparser/grok"
|
||||
)
|
||||
|
||||
type LogParser interface {
|
||||
ParseLine(line string) (telegraf.Metric, error)
|
||||
Compile() error
|
||||
}
|
||||
|
||||
type LogParserPlugin struct {
|
||||
Files []string
|
||||
FromBeginning bool
|
||||
|
||||
tailers []*tail.Tail
|
||||
lines chan string
|
||||
done chan struct{}
|
||||
wg sync.WaitGroup
|
||||
acc telegraf.Accumulator
|
||||
parsers []LogParser
|
||||
|
||||
sync.Mutex
|
||||
|
||||
GrokParser *grok.Parser `toml:"grok"`
|
||||
}
|
||||
|
||||
const sampleConfig = `
|
||||
## Log files to parse.
|
||||
## These accept standard unix glob matching rules, but with the addition of
|
||||
## ** as a "super asterisk". ie:
|
||||
## /var/log/**.log -> recursively find all .log files in /var/log
|
||||
## /var/log/*/*.log -> find all .log files with a parent dir in /var/log
|
||||
## /var/log/apache.log -> only tail the apache log file
|
||||
files = ["/var/log/influxdb/influxdb.log"]
|
||||
## Read file from beginning.
|
||||
from_beginning = false
|
||||
|
||||
## Parse logstash-style "grok" patterns:
|
||||
## Telegraf built-in parsing patterns: https://goo.gl/dkay10
|
||||
[inputs.logparser.grok]
|
||||
## This is a list of patterns to check the given log file(s) for.
|
||||
## Note that adding patterns here increases processing time. The most
|
||||
## efficient configuration is to have one pattern per logparser.
|
||||
## Other common built-in patterns are:
|
||||
## %{COMMON_LOG_FORMAT} (plain apache & nginx access logs)
|
||||
## %{COMBINED_LOG_FORMAT} (access logs + referrer & agent)
|
||||
patterns = ["%{INFLUXDB_HTTPD_LOG}"]
|
||||
## Full path(s) to custom pattern files.
|
||||
custom_pattern_files = []
|
||||
## Custom patterns can also be defined here. Put one pattern per line.
|
||||
custom_patterns = '''
|
||||
'''
|
||||
`
|
||||
|
||||
func (l *LogParserPlugin) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func (l *LogParserPlugin) Description() string {
|
||||
return "Stream and parse log file(s)."
|
||||
}
|
||||
|
||||
func (l *LogParserPlugin) Gather(acc telegraf.Accumulator) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LogParserPlugin) Start(acc telegraf.Accumulator) error {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
l.acc = acc
|
||||
l.lines = make(chan string, 1000)
|
||||
l.done = make(chan struct{})
|
||||
|
||||
// Looks for fields which implement LogParser interface
|
||||
l.parsers = []LogParser{}
|
||||
s := reflect.ValueOf(l).Elem()
|
||||
for i := 0; i < s.NumField(); i++ {
|
||||
f := s.Field(i)
|
||||
|
||||
if !f.CanInterface() {
|
||||
continue
|
||||
}
|
||||
|
||||
if lpPlugin, ok := f.Interface().(LogParser); ok {
|
||||
if reflect.ValueOf(lpPlugin).IsNil() {
|
||||
continue
|
||||
}
|
||||
l.parsers = append(l.parsers, lpPlugin)
|
||||
}
|
||||
}
|
||||
|
||||
if len(l.parsers) == 0 {
|
||||
return fmt.Errorf("ERROR: logparser input plugin: no parser defined.")
|
||||
}
|
||||
|
||||
// compile log parser patterns:
|
||||
for _, parser := range l.parsers {
|
||||
if err := parser.Compile(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var seek tail.SeekInfo
|
||||
if !l.FromBeginning {
|
||||
seek.Whence = 2
|
||||
seek.Offset = 0
|
||||
}
|
||||
|
||||
l.wg.Add(1)
|
||||
go l.parser()
|
||||
|
||||
var errS string
|
||||
// Create a "tailer" for each file
|
||||
for _, filepath := range l.Files {
|
||||
g, err := globpath.Compile(filepath)
|
||||
if err != nil {
|
||||
log.Printf("ERROR Glob %s failed to compile, %s", filepath, err)
|
||||
}
|
||||
for file, _ := range g.Match() {
|
||||
tailer, err := tail.TailFile(file,
|
||||
tail.Config{
|
||||
ReOpen: true,
|
||||
Follow: true,
|
||||
Location: &seek,
|
||||
})
|
||||
if err != nil {
|
||||
errS += err.Error() + " "
|
||||
continue
|
||||
}
|
||||
// create a goroutine for each "tailer"
|
||||
l.wg.Add(1)
|
||||
go l.receiver(tailer)
|
||||
l.tailers = append(l.tailers, tailer)
|
||||
}
|
||||
}
|
||||
|
||||
if errS != "" {
|
||||
return fmt.Errorf(errS)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// receiver is launched as a goroutine to continuously watch a tailed logfile
|
||||
// for changes and send any log lines down the l.lines channel.
|
||||
func (l *LogParserPlugin) receiver(tailer *tail.Tail) {
|
||||
defer l.wg.Done()
|
||||
|
||||
var line *tail.Line
|
||||
for line = range tailer.Lines {
|
||||
if line.Err != nil {
|
||||
log.Printf("ERROR tailing file %s, Error: %s\n",
|
||||
tailer.Filename, line.Err)
|
||||
continue
|
||||
}
|
||||
|
||||
select {
|
||||
case <-l.done:
|
||||
case l.lines <- line.Text:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parser is launched as a goroutine to watch the l.lines channel.
|
||||
// when a line is available, parser parses it and adds the metric(s) to the
|
||||
// accumulator.
|
||||
func (l *LogParserPlugin) parser() {
|
||||
defer l.wg.Done()
|
||||
|
||||
var m telegraf.Metric
|
||||
var err error
|
||||
var line string
|
||||
for {
|
||||
select {
|
||||
case <-l.done:
|
||||
return
|
||||
case line = <-l.lines:
|
||||
if line == "" || line == "\n" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
for _, parser := range l.parsers {
|
||||
m, err = parser.ParseLine(line)
|
||||
if err == nil {
|
||||
if m != nil {
|
||||
l.acc.AddFields(m.Name(), m.Fields(), m.Tags(), m.Time())
|
||||
}
|
||||
} else {
|
||||
log.Printf("Malformed log line in [%s], Error: %s\n", line, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LogParserPlugin) Stop() {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
for _, t := range l.tailers {
|
||||
err := t.Stop()
|
||||
if err != nil {
|
||||
log.Printf("ERROR stopping tail on file %s\n", t.Filename)
|
||||
}
|
||||
t.Cleanup()
|
||||
}
|
||||
close(l.done)
|
||||
l.wg.Wait()
|
||||
}
|
||||
|
||||
func init() {
|
||||
inputs.Add("logparser", func() telegraf.Input {
|
||||
return &LogParserPlugin{}
|
||||
})
|
||||
}
|
||||
116
plugins/inputs/logparser/logparser_test.go
Normal file
116
plugins/inputs/logparser/logparser_test.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package logparser
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
|
||||
"github.com/influxdata/telegraf/plugins/inputs/logparser/grok"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStartNoParsers(t *testing.T) {
|
||||
logparser := &LogParserPlugin{
|
||||
FromBeginning: true,
|
||||
Files: []string{"grok/testdata/*.log"},
|
||||
}
|
||||
|
||||
acc := testutil.Accumulator{}
|
||||
assert.Error(t, logparser.Start(&acc))
|
||||
}
|
||||
|
||||
func TestGrokParseLogFilesNonExistPattern(t *testing.T) {
|
||||
thisdir := getCurrentDir()
|
||||
p := &grok.Parser{
|
||||
Patterns: []string{"%{FOOBAR}"},
|
||||
CustomPatternFiles: []string{thisdir + "grok/testdata/test-patterns"},
|
||||
}
|
||||
|
||||
logparser := &LogParserPlugin{
|
||||
FromBeginning: true,
|
||||
Files: []string{thisdir + "grok/testdata/*.log"},
|
||||
GrokParser: p,
|
||||
}
|
||||
|
||||
acc := testutil.Accumulator{}
|
||||
assert.NoError(t, logparser.Start(&acc))
|
||||
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
logparser.Stop()
|
||||
}
|
||||
|
||||
func TestGrokParseLogFiles(t *testing.T) {
|
||||
thisdir := getCurrentDir()
|
||||
p := &grok.Parser{
|
||||
Patterns: []string{"%{TEST_LOG_A}", "%{TEST_LOG_B}"},
|
||||
CustomPatternFiles: []string{thisdir + "grok/testdata/test-patterns"},
|
||||
}
|
||||
|
||||
logparser := &LogParserPlugin{
|
||||
FromBeginning: true,
|
||||
Files: []string{thisdir + "grok/testdata/*.log"},
|
||||
GrokParser: p,
|
||||
}
|
||||
|
||||
acc := testutil.Accumulator{}
|
||||
assert.NoError(t, logparser.Start(&acc))
|
||||
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
logparser.Stop()
|
||||
|
||||
acc.AssertContainsTaggedFields(t, "logparser_grok",
|
||||
map[string]interface{}{
|
||||
"clientip": "192.168.1.1",
|
||||
"myfloat": float64(1.25),
|
||||
"response_time": int64(5432),
|
||||
"myint": int64(101),
|
||||
},
|
||||
map[string]string{"response_code": "200"})
|
||||
|
||||
acc.AssertContainsTaggedFields(t, "logparser_grok",
|
||||
map[string]interface{}{
|
||||
"myfloat": 1.25,
|
||||
"mystring": "mystring",
|
||||
"nomodifier": "nomodifier",
|
||||
},
|
||||
map[string]string{})
|
||||
}
|
||||
|
||||
func TestGrokParseLogFilesOneBad(t *testing.T) {
|
||||
thisdir := getCurrentDir()
|
||||
p := &grok.Parser{
|
||||
Patterns: []string{"%{TEST_LOG_A}", "%{TEST_LOG_BAD}"},
|
||||
CustomPatternFiles: []string{thisdir + "grok/testdata/test-patterns"},
|
||||
}
|
||||
assert.NoError(t, p.Compile())
|
||||
|
||||
logparser := &LogParserPlugin{
|
||||
FromBeginning: true,
|
||||
Files: []string{thisdir + "grok/testdata/*.log"},
|
||||
GrokParser: p,
|
||||
}
|
||||
|
||||
acc := testutil.Accumulator{}
|
||||
assert.NoError(t, logparser.Start(&acc))
|
||||
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
logparser.Stop()
|
||||
|
||||
acc.AssertContainsTaggedFields(t, "logparser_grok",
|
||||
map[string]interface{}{
|
||||
"clientip": "192.168.1.1",
|
||||
"myfloat": float64(1.25),
|
||||
"response_time": int64(5432),
|
||||
"myint": int64(101),
|
||||
},
|
||||
map[string]string{"response_code": "200"})
|
||||
}
|
||||
|
||||
func getCurrentDir() string {
|
||||
_, filename, _, _ := runtime.Caller(1)
|
||||
return strings.Replace(filename, "logparser_test.go", "", 1)
|
||||
}
|
||||
@@ -69,10 +69,10 @@ func (m *MongoDB) Gather(acc telegraf.Accumulator) error {
|
||||
}
|
||||
}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
go func(srv *Server) {
|
||||
defer wg.Done()
|
||||
outerr = m.gatherServer(m.getMongoServer(u), acc)
|
||||
}()
|
||||
outerr = m.gatherServer(srv, acc)
|
||||
}(m.getMongoServer(u))
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
@@ -33,7 +33,7 @@ func (s *Server) gatherData(acc telegraf.Accumulator) error {
|
||||
result_repl := &ReplSetStatus{}
|
||||
err = s.Session.DB("admin").Run(bson.D{{"replSetGetStatus", 1}}, result_repl)
|
||||
if err != nil {
|
||||
log.Println("Not gathering replica set status, member not in replica set")
|
||||
log.Println("Not gathering replica set status, member not in replica set (" + err.Error() + ")")
|
||||
}
|
||||
|
||||
jumbo_chunks, _ := s.Session.DB("config").C("chunks").Find(bson.M{"jumbo": true}).Count()
|
||||
|
||||
@@ -78,9 +78,9 @@ type ReplSetStatus struct {
|
||||
|
||||
// ReplSetMember stores information related to a replica set member
|
||||
type ReplSetMember struct {
|
||||
Name string `bson:"name"`
|
||||
State int64 `bson:"state"`
|
||||
Optime *bson.MongoTimestamp `bson:"optime"`
|
||||
Name string `bson:"name"`
|
||||
State int64 `bson:"state"`
|
||||
OptimeDate *bson.MongoTimestamp `bson:"optimeDate"`
|
||||
}
|
||||
|
||||
// WiredTiger stores information related to the WiredTiger storage engine.
|
||||
@@ -663,9 +663,9 @@ func NewStatLine(oldMongo, newMongo MongoStatus, key string, all bool, sampleSec
|
||||
}
|
||||
}
|
||||
|
||||
if me.Optime != nil && master.Optime != nil && me.State == 2 {
|
||||
if me.OptimeDate != nil && master.OptimeDate != nil && me.State == 2 {
|
||||
// MongoTimestamp type is int64 where the first 32bits are the unix timestamp
|
||||
lag := int64(*master.Optime>>32 - *me.Optime>>32)
|
||||
lag := int64(*master.OptimeDate>>32 - *me.OptimeDate>>32)
|
||||
if lag < 0 {
|
||||
returnVal.ReplLag = 0
|
||||
} else {
|
||||
|
||||
@@ -25,8 +25,8 @@ This plugin gathers the statistic data from MySQL server
|
||||
## [username[:password]@][protocol[(address)]]/[?tls=[true|false|skip-verify]]
|
||||
## see https://github.com/go-sql-driver/mysql#dsn-data-source-name
|
||||
## e.g.
|
||||
## root:passwd@tcp(127.0.0.1:3306)/?tls=false
|
||||
## root@tcp(127.0.0.1:3306)/?tls=false
|
||||
## db_user:passwd@tcp(127.0.0.1:3306)/?tls=false
|
||||
## db_user@tcp(127.0.0.1:3306)/?tls=false
|
||||
#
|
||||
## If no servers are specified, then localhost is used as the host.
|
||||
servers = ["tcp(127.0.0.1:3306)/"]
|
||||
@@ -53,13 +53,13 @@ This plugin gathers the statistic data from MySQL server
|
||||
## gather metrics from SHOW BINARY LOGS command output
|
||||
gather_binary_logs = false
|
||||
#
|
||||
## gather metrics from PERFORMANCE_SCHEMA.TABLE_IO_WAITS_SUMMART_BY_TABLE
|
||||
## gather metrics from PERFORMANCE_SCHEMA.TABLE_IO_WAITS_SUMMARY_BY_TABLE
|
||||
gather_table_io_waits = false
|
||||
#
|
||||
## gather metrics from PERFORMANCE_SCHEMA.TABLE_LOCK_WAITS
|
||||
gather_table_lock_waits = false
|
||||
#
|
||||
## gather metrics from PERFORMANCE_SCHEMA.TABLE_IO_WAITS_SUMMART_BY_INDEX_USAGE
|
||||
## gather metrics from PERFORMANCE_SCHEMA.TABLE_IO_WAITS_SUMMARY_BY_INDEX_USAGE
|
||||
gather_index_io_waits = false
|
||||
#
|
||||
## gather metrics from PERFORMANCE_SCHEMA.EVENT_WAITS
|
||||
|
||||
@@ -39,8 +39,8 @@ var sampleConfig = `
|
||||
## [username[:password]@][protocol[(address)]]/[?tls=[true|false|skip-verify]]
|
||||
## see https://github.com/go-sql-driver/mysql#dsn-data-source-name
|
||||
## e.g.
|
||||
## root:passwd@tcp(127.0.0.1:3306)/?tls=false
|
||||
## root@tcp(127.0.0.1:3306)/?tls=false
|
||||
## db_user:passwd@tcp(127.0.0.1:3306)/?tls=false
|
||||
## db_user@tcp(127.0.0.1:3306)/?tls=false
|
||||
#
|
||||
## If no servers are specified, then localhost is used as the host.
|
||||
servers = ["tcp(127.0.0.1:3306)/"]
|
||||
|
||||
@@ -6,41 +6,30 @@ It can also check response text.
|
||||
### Configuration:
|
||||
|
||||
```
|
||||
# List of UDP/TCP connections you want to check
|
||||
[[inputs.net_response]]
|
||||
protocol = "tcp"
|
||||
# Server address (default IP localhost)
|
||||
address = "github.com:80"
|
||||
# Set timeout (default 1.0)
|
||||
timeout = 1.0
|
||||
# Set read timeout (default 1.0)
|
||||
read_timeout = 1.0
|
||||
# String sent to the server
|
||||
send = "ssh"
|
||||
# Expected string in answer
|
||||
expect = "ssh"
|
||||
|
||||
[[inputs.net_response]]
|
||||
protocol = "tcp"
|
||||
address = ":80"
|
||||
|
||||
# TCP or UDP 'ping' given url and collect response time in seconds
|
||||
[[inputs.net_response]]
|
||||
protocol = "udp"
|
||||
# Server address (default IP localhost)
|
||||
## Protocol, must be "tcp" or "udp"
|
||||
protocol = "tcp"
|
||||
## Server address (default localhost)
|
||||
address = "github.com:80"
|
||||
# Set timeout (default 1.0)
|
||||
timeout = 1.0
|
||||
# Set read timeout (default 1.0)
|
||||
read_timeout = 1.0
|
||||
# String sent to the server
|
||||
## Set timeout
|
||||
timeout = "1s"
|
||||
|
||||
## Optional string sent to the server
|
||||
send = "ssh"
|
||||
# Expected string in answer
|
||||
## Optional expected string in answer
|
||||
expect = "ssh"
|
||||
## Set read timeout (only used if expecting a response)
|
||||
read_timeout = "1s"
|
||||
|
||||
[[inputs.net_response]]
|
||||
protocol = "udp"
|
||||
address = "localhost:161"
|
||||
timeout = 2.0
|
||||
timeout = "2s"
|
||||
```
|
||||
|
||||
### Measurements & Fields:
|
||||
|
||||
@@ -9,14 +9,15 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/internal"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
// NetResponses struct
|
||||
type NetResponse struct {
|
||||
Address string
|
||||
Timeout float64
|
||||
ReadTimeout float64
|
||||
Timeout internal.Duration
|
||||
ReadTimeout internal.Duration
|
||||
Send string
|
||||
Expect string
|
||||
Protocol string
|
||||
@@ -31,29 +32,28 @@ var sampleConfig = `
|
||||
protocol = "tcp"
|
||||
## Server address (default localhost)
|
||||
address = "github.com:80"
|
||||
## Set timeout (default 1.0 seconds)
|
||||
timeout = 1.0
|
||||
## Set read timeout (default 1.0 seconds)
|
||||
read_timeout = 1.0
|
||||
## Set timeout
|
||||
timeout = "1s"
|
||||
|
||||
## Optional string sent to the server
|
||||
# send = "ssh"
|
||||
## Optional expected string in answer
|
||||
# expect = "ssh"
|
||||
## Set read timeout (only used if expecting a response)
|
||||
read_timeout = "1s"
|
||||
`
|
||||
|
||||
func (_ *NetResponse) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func (t *NetResponse) TcpGather() (map[string]interface{}, error) {
|
||||
func (n *NetResponse) TcpGather() (map[string]interface{}, error) {
|
||||
// Prepare fields
|
||||
fields := make(map[string]interface{})
|
||||
// Start Timer
|
||||
start := time.Now()
|
||||
// Resolving
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", t.Address)
|
||||
// Connecting
|
||||
conn, err := net.DialTCP("tcp", nil, tcpAddr)
|
||||
conn, err := net.DialTimeout("tcp", n.Address, n.Timeout.Duration)
|
||||
// Stop timer
|
||||
responseTime := time.Since(start).Seconds()
|
||||
// Handle error
|
||||
@@ -62,17 +62,16 @@ func (t *NetResponse) TcpGather() (map[string]interface{}, error) {
|
||||
}
|
||||
defer conn.Close()
|
||||
// Send string if needed
|
||||
if t.Send != "" {
|
||||
msg := []byte(t.Send)
|
||||
if n.Send != "" {
|
||||
msg := []byte(n.Send)
|
||||
conn.Write(msg)
|
||||
conn.CloseWrite()
|
||||
// Stop timer
|
||||
responseTime = time.Since(start).Seconds()
|
||||
}
|
||||
// Read string if needed
|
||||
if t.Expect != "" {
|
||||
if n.Expect != "" {
|
||||
// Set read timeout
|
||||
conn.SetReadDeadline(time.Now().Add(time.Duration(t.ReadTimeout) * time.Second))
|
||||
conn.SetReadDeadline(time.Now().Add(n.ReadTimeout.Duration))
|
||||
// Prepare reader
|
||||
reader := bufio.NewReader(conn)
|
||||
tp := textproto.NewReader(reader)
|
||||
@@ -85,7 +84,7 @@ func (t *NetResponse) TcpGather() (map[string]interface{}, error) {
|
||||
fields["string_found"] = false
|
||||
} else {
|
||||
// Looking for string in answer
|
||||
RegEx := regexp.MustCompile(`.*` + t.Expect + `.*`)
|
||||
RegEx := regexp.MustCompile(`.*` + n.Expect + `.*`)
|
||||
find := RegEx.FindString(string(data))
|
||||
if find != "" {
|
||||
fields["string_found"] = true
|
||||
@@ -99,13 +98,13 @@ func (t *NetResponse) TcpGather() (map[string]interface{}, error) {
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
func (u *NetResponse) UdpGather() (map[string]interface{}, error) {
|
||||
func (n *NetResponse) UdpGather() (map[string]interface{}, error) {
|
||||
// Prepare fields
|
||||
fields := make(map[string]interface{})
|
||||
// Start Timer
|
||||
start := time.Now()
|
||||
// Resolving
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", u.Address)
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", n.Address)
|
||||
LocalAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
|
||||
// Connecting
|
||||
conn, err := net.DialUDP("udp", LocalAddr, udpAddr)
|
||||
@@ -115,11 +114,11 @@ func (u *NetResponse) UdpGather() (map[string]interface{}, error) {
|
||||
return nil, err
|
||||
}
|
||||
// Send string
|
||||
msg := []byte(u.Send)
|
||||
msg := []byte(n.Send)
|
||||
conn.Write(msg)
|
||||
// Read string
|
||||
// Set read timeout
|
||||
conn.SetReadDeadline(time.Now().Add(time.Duration(u.ReadTimeout) * time.Second))
|
||||
conn.SetReadDeadline(time.Now().Add(n.ReadTimeout.Duration))
|
||||
// Read
|
||||
buf := make([]byte, 1024)
|
||||
_, _, err = conn.ReadFromUDP(buf)
|
||||
@@ -130,7 +129,7 @@ func (u *NetResponse) UdpGather() (map[string]interface{}, error) {
|
||||
return nil, err
|
||||
} else {
|
||||
// Looking for string in answer
|
||||
RegEx := regexp.MustCompile(`.*` + u.Expect + `.*`)
|
||||
RegEx := regexp.MustCompile(`.*` + n.Expect + `.*`)
|
||||
find := RegEx.FindString(string(buf))
|
||||
if find != "" {
|
||||
fields["string_found"] = true
|
||||
@@ -142,28 +141,28 @@ func (u *NetResponse) UdpGather() (map[string]interface{}, error) {
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
func (c *NetResponse) Gather(acc telegraf.Accumulator) error {
|
||||
func (n *NetResponse) Gather(acc telegraf.Accumulator) error {
|
||||
// Set default values
|
||||
if c.Timeout == 0 {
|
||||
c.Timeout = 1.0
|
||||
if n.Timeout.Duration == 0 {
|
||||
n.Timeout.Duration = time.Second
|
||||
}
|
||||
if c.ReadTimeout == 0 {
|
||||
c.ReadTimeout = 1.0
|
||||
if n.ReadTimeout.Duration == 0 {
|
||||
n.ReadTimeout.Duration = time.Second
|
||||
}
|
||||
// Check send and expected string
|
||||
if c.Protocol == "udp" && c.Send == "" {
|
||||
if n.Protocol == "udp" && n.Send == "" {
|
||||
return errors.New("Send string cannot be empty")
|
||||
}
|
||||
if c.Protocol == "udp" && c.Expect == "" {
|
||||
if n.Protocol == "udp" && n.Expect == "" {
|
||||
return errors.New("Expected string cannot be empty")
|
||||
}
|
||||
// Prepare host and port
|
||||
host, port, err := net.SplitHostPort(c.Address)
|
||||
host, port, err := net.SplitHostPort(n.Address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if host == "" {
|
||||
c.Address = "localhost:" + port
|
||||
n.Address = "localhost:" + port
|
||||
}
|
||||
if port == "" {
|
||||
return errors.New("Bad port")
|
||||
@@ -172,11 +171,11 @@ func (c *NetResponse) Gather(acc telegraf.Accumulator) error {
|
||||
tags := map[string]string{"server": host, "port": port}
|
||||
var fields map[string]interface{}
|
||||
// Gather data
|
||||
if c.Protocol == "tcp" {
|
||||
fields, err = c.TcpGather()
|
||||
if n.Protocol == "tcp" {
|
||||
fields, err = n.TcpGather()
|
||||
tags["protocol"] = "tcp"
|
||||
} else if c.Protocol == "udp" {
|
||||
fields, err = c.UdpGather()
|
||||
} else if n.Protocol == "udp" {
|
||||
fields, err = n.UdpGather()
|
||||
tags["protocol"] = "udp"
|
||||
} else {
|
||||
return errors.New("Bad protocol")
|
||||
|
||||
@@ -5,7 +5,9 @@ import (
|
||||
"regexp"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf/internal"
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -35,7 +37,7 @@ func TestTCPError(t *testing.T) {
|
||||
// Error
|
||||
err1 := c.Gather(&acc)
|
||||
require.Error(t, err1)
|
||||
assert.Equal(t, "dial tcp 127.0.0.1:9999: getsockopt: connection refused", err1.Error())
|
||||
assert.Contains(t, err1.Error(), "getsockopt: connection refused")
|
||||
}
|
||||
|
||||
func TestTCPOK1(t *testing.T) {
|
||||
@@ -46,8 +48,8 @@ func TestTCPOK1(t *testing.T) {
|
||||
Address: "127.0.0.1:2004",
|
||||
Send: "test",
|
||||
Expect: "test",
|
||||
ReadTimeout: 3.0,
|
||||
Timeout: 1.0,
|
||||
ReadTimeout: internal.Duration{Duration: time.Second * 3},
|
||||
Timeout: internal.Duration{Duration: time.Second},
|
||||
Protocol: "tcp",
|
||||
}
|
||||
// Start TCP server
|
||||
@@ -86,8 +88,8 @@ func TestTCPOK2(t *testing.T) {
|
||||
Address: "127.0.0.1:2004",
|
||||
Send: "test",
|
||||
Expect: "test2",
|
||||
ReadTimeout: 3.0,
|
||||
Timeout: 1.0,
|
||||
ReadTimeout: internal.Duration{Duration: time.Second * 3},
|
||||
Timeout: internal.Duration{Duration: time.Second},
|
||||
Protocol: "tcp",
|
||||
}
|
||||
// Start TCP server
|
||||
@@ -141,8 +143,8 @@ func TestUDPOK1(t *testing.T) {
|
||||
Address: "127.0.0.1:2004",
|
||||
Send: "test",
|
||||
Expect: "test",
|
||||
ReadTimeout: 3.0,
|
||||
Timeout: 1.0,
|
||||
ReadTimeout: internal.Duration{Duration: time.Second * 3},
|
||||
Timeout: internal.Duration{Duration: time.Second},
|
||||
Protocol: "udp",
|
||||
}
|
||||
// Start UDP server
|
||||
|
||||
@@ -97,11 +97,12 @@ func (n *Nginx) gatherUrl(addr *url.URL, acc telegraf.Accumulator) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := strings.SplitN(strings.TrimSpace(line), " ", 3)
|
||||
data := strings.Fields(line)
|
||||
accepts, err := strconv.ParseUint(data[0], 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
handled, err := strconv.ParseUint(data[1], 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -116,7 +117,7 @@ func (n *Nginx) gatherUrl(addr *url.URL, acc telegraf.Accumulator) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data = strings.SplitN(strings.TrimSpace(line), " ", 6)
|
||||
data = strings.Fields(line)
|
||||
reading, err := strconv.ParseUint(data[1], 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -13,12 +13,18 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const sampleResponse = `
|
||||
const nginxSampleResponse = `
|
||||
Active connections: 585
|
||||
server accepts handled requests
|
||||
85340 85340 35085
|
||||
Reading: 4 Writing: 135 Waiting: 446
|
||||
`
|
||||
const tengineSampleResponse = `
|
||||
Active connections: 403
|
||||
server accepts handled requests request_time
|
||||
853 8533 3502 1546565864
|
||||
Reading: 8 Writing: 125 Waiting: 946
|
||||
`
|
||||
|
||||
// Verify that nginx tags are properly parsed based on the server
|
||||
func TestNginxTags(t *testing.T) {
|
||||
@@ -36,7 +42,9 @@ func TestNginxGeneratesMetrics(t *testing.T) {
|
||||
var rsp string
|
||||
|
||||
if r.URL.Path == "/stub_status" {
|
||||
rsp = sampleResponse
|
||||
rsp = nginxSampleResponse
|
||||
} else if r.URL.Path == "/tengine_status" {
|
||||
rsp = tengineSampleResponse
|
||||
} else {
|
||||
panic("Cannot handle request")
|
||||
}
|
||||
@@ -49,12 +57,20 @@ func TestNginxGeneratesMetrics(t *testing.T) {
|
||||
Urls: []string{fmt.Sprintf("%s/stub_status", ts.URL)},
|
||||
}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
nt := &Nginx{
|
||||
Urls: []string{fmt.Sprintf("%s/tengine_status", ts.URL)},
|
||||
}
|
||||
|
||||
err := n.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
var acc_nginx testutil.Accumulator
|
||||
var acc_tengine testutil.Accumulator
|
||||
|
||||
fields := map[string]interface{}{
|
||||
err_nginx := n.Gather(&acc_nginx)
|
||||
err_tengine := nt.Gather(&acc_tengine)
|
||||
|
||||
require.NoError(t, err_nginx)
|
||||
require.NoError(t, err_tengine)
|
||||
|
||||
fields_nginx := map[string]interface{}{
|
||||
"active": uint64(585),
|
||||
"accepts": uint64(85340),
|
||||
"handled": uint64(85340),
|
||||
@@ -63,6 +79,17 @@ func TestNginxGeneratesMetrics(t *testing.T) {
|
||||
"writing": uint64(135),
|
||||
"waiting": uint64(446),
|
||||
}
|
||||
|
||||
fields_tengine := map[string]interface{}{
|
||||
"active": uint64(403),
|
||||
"accepts": uint64(853),
|
||||
"handled": uint64(8533),
|
||||
"requests": uint64(3502),
|
||||
"reading": uint64(8),
|
||||
"writing": uint64(125),
|
||||
"waiting": uint64(946),
|
||||
}
|
||||
|
||||
addr, err := url.Parse(ts.URL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -81,5 +108,6 @@ func TestNginxGeneratesMetrics(t *testing.T) {
|
||||
}
|
||||
|
||||
tags := map[string]string{"server": host, "port": port}
|
||||
acc.AssertContainsTaggedFields(t, "nginx", fields, tags)
|
||||
acc_nginx.AssertContainsTaggedFields(t, "nginx", fields_nginx, tags)
|
||||
acc_tengine.AssertContainsTaggedFields(t, "nginx", fields_tengine, tags)
|
||||
}
|
||||
|
||||
343
plugins/inputs/nstat/README.md
Normal file
343
plugins/inputs/nstat/README.md
Normal file
@@ -0,0 +1,343 @@
|
||||
## Nstat input plugin
|
||||
|
||||
Plugin collects network metrics from `/proc/net/netstat`, `/proc/net/snmp` and `/proc/net/snmp6` files
|
||||
|
||||
### Configuration
|
||||
|
||||
The plugin firstly tries to read file paths from config values
|
||||
if it is empty, then it reads from env variables.
|
||||
* `PROC_NET_NETSTAT`
|
||||
* `PROC_NET_SNMP`
|
||||
* `PROC_NET_SNMP6`
|
||||
|
||||
If these variables are also not set,
|
||||
then it tries to read the proc root from env - `PROC_ROOT`,
|
||||
and sets `/proc` as a root path if `PROC_ROOT` is also empty.
|
||||
|
||||
Then appends default file paths:
|
||||
* `/net/netstat`
|
||||
* `/net/snmp`
|
||||
* `/net/snmp6`
|
||||
|
||||
So if nothing is given, no paths in config and in env vars, the plugin takes the default paths.
|
||||
* `/proc/net/netstat`
|
||||
* `/proc/net/snmp`
|
||||
* `/proc/net/snmp6`
|
||||
|
||||
The sample config file
|
||||
```toml
|
||||
[[inputs.nstat]]
|
||||
## file paths
|
||||
## e.g: /proc/net/netstat, /proc/net/snmp, /proc/net/snmp6
|
||||
# proc_net_netstat = ""
|
||||
# proc_net_snmp = ""
|
||||
# proc_net_snmp6 = ""
|
||||
## dump metrics with 0 values too
|
||||
# dump_zeros = true
|
||||
```
|
||||
|
||||
### Measurements & Fields
|
||||
|
||||
- nstat
|
||||
- Icmp6InCsumErrors
|
||||
- Icmp6InDestUnreachs
|
||||
- Icmp6InEchoReplies
|
||||
- Icmp6InEchos
|
||||
- Icmp6InErrors
|
||||
- Icmp6InGroupMembQueries
|
||||
- Icmp6InGroupMembReductions
|
||||
- Icmp6InGroupMembResponses
|
||||
- Icmp6InMLDv2Reports
|
||||
- Icmp6InMsgs
|
||||
- Icmp6InNeighborAdvertisements
|
||||
- Icmp6InNeighborSolicits
|
||||
- Icmp6InParmProblems
|
||||
- Icmp6InPktTooBigs
|
||||
- Icmp6InRedirects
|
||||
- Icmp6InRouterAdvertisements
|
||||
- Icmp6InRouterSolicits
|
||||
- Icmp6InTimeExcds
|
||||
- Icmp6OutDestUnreachs
|
||||
- Icmp6OutEchoReplies
|
||||
- Icmp6OutEchos
|
||||
- Icmp6OutErrors
|
||||
- Icmp6OutGroupMembQueries
|
||||
- Icmp6OutGroupMembReductions
|
||||
- Icmp6OutGroupMembResponses
|
||||
- Icmp6OutMLDv2Reports
|
||||
- Icmp6OutMsgs
|
||||
- Icmp6OutNeighborAdvertisements
|
||||
- Icmp6OutNeighborSolicits
|
||||
- Icmp6OutParmProblems
|
||||
- Icmp6OutPktTooBigs
|
||||
- Icmp6OutRedirects
|
||||
- Icmp6OutRouterAdvertisements
|
||||
- Icmp6OutRouterSolicits
|
||||
- Icmp6OutTimeExcds
|
||||
- Icmp6OutType133
|
||||
- Icmp6OutType135
|
||||
- Icmp6OutType143
|
||||
- IcmpInAddrMaskReps
|
||||
- IcmpInAddrMasks
|
||||
- IcmpInCsumErrors
|
||||
- IcmpInDestUnreachs
|
||||
- IcmpInEchoReps
|
||||
- IcmpInEchos
|
||||
- IcmpInErrors
|
||||
- IcmpInMsgs
|
||||
- IcmpInParmProbs
|
||||
- IcmpInRedirects
|
||||
- IcmpInSrcQuenchs
|
||||
- IcmpInTimeExcds
|
||||
- IcmpInTimestampReps
|
||||
- IcmpInTimestamps
|
||||
- IcmpMsgInType3
|
||||
- IcmpMsgOutType3
|
||||
- IcmpOutAddrMaskReps
|
||||
- IcmpOutAddrMasks
|
||||
- IcmpOutDestUnreachs
|
||||
- IcmpOutEchoReps
|
||||
- IcmpOutEchos
|
||||
- IcmpOutErrors
|
||||
- IcmpOutMsgs
|
||||
- IcmpOutParmProbs
|
||||
- IcmpOutRedirects
|
||||
- IcmpOutSrcQuenchs
|
||||
- IcmpOutTimeExcds
|
||||
- IcmpOutTimestampReps
|
||||
- IcmpOutTimestamps
|
||||
- Ip6FragCreates
|
||||
- Ip6FragFails
|
||||
- Ip6FragOKs
|
||||
- Ip6InAddrErrors
|
||||
- Ip6InBcastOctets
|
||||
- Ip6InCEPkts
|
||||
- Ip6InDelivers
|
||||
- Ip6InDiscards
|
||||
- Ip6InECT0Pkts
|
||||
- Ip6InECT1Pkts
|
||||
- Ip6InHdrErrors
|
||||
- Ip6InMcastOctets
|
||||
- Ip6InMcastPkts
|
||||
- Ip6InNoECTPkts
|
||||
- Ip6InNoRoutes
|
||||
- Ip6InOctets
|
||||
- Ip6InReceives
|
||||
- Ip6InTooBigErrors
|
||||
- Ip6InTruncatedPkts
|
||||
- Ip6InUnknownProtos
|
||||
- Ip6OutBcastOctets
|
||||
- Ip6OutDiscards
|
||||
- Ip6OutForwDatagrams
|
||||
- Ip6OutMcastOctets
|
||||
- Ip6OutMcastPkts
|
||||
- Ip6OutNoRoutes
|
||||
- Ip6OutOctets
|
||||
- Ip6OutRequests
|
||||
- Ip6ReasmFails
|
||||
- Ip6ReasmOKs
|
||||
- Ip6ReasmReqds
|
||||
- Ip6ReasmTimeout
|
||||
- IpDefaultTTL
|
||||
- IpExtInBcastOctets
|
||||
- IpExtInBcastPkts
|
||||
- IpExtInCEPkts
|
||||
- IpExtInCsumErrors
|
||||
- IpExtInECT0Pkts
|
||||
- IpExtInECT1Pkts
|
||||
- IpExtInMcastOctets
|
||||
- IpExtInMcastPkts
|
||||
- IpExtInNoECTPkts
|
||||
- IpExtInNoRoutes
|
||||
- IpExtInOctets
|
||||
- IpExtInTruncatedPkts
|
||||
- IpExtOutBcastOctets
|
||||
- IpExtOutBcastPkts
|
||||
- IpExtOutMcastOctets
|
||||
- IpExtOutMcastPkts
|
||||
- IpExtOutOctets
|
||||
- IpForwDatagrams
|
||||
- IpForwarding
|
||||
- IpFragCreates
|
||||
- IpFragFails
|
||||
- IpFragOKs
|
||||
- IpInAddrErrors
|
||||
- IpInDelivers
|
||||
- IpInDiscards
|
||||
- IpInHdrErrors
|
||||
- IpInReceives
|
||||
- IpInUnknownProtos
|
||||
- IpOutDiscards
|
||||
- IpOutNoRoutes
|
||||
- IpOutRequests
|
||||
- IpReasmFails
|
||||
- IpReasmOKs
|
||||
- IpReasmReqds
|
||||
- IpReasmTimeout
|
||||
- TcpActiveOpens
|
||||
- TcpAttemptFails
|
||||
- TcpCurrEstab
|
||||
- TcpEstabResets
|
||||
- TcpExtArpFilter
|
||||
- TcpExtBusyPollRxPackets
|
||||
- TcpExtDelayedACKLocked
|
||||
- TcpExtDelayedACKLost
|
||||
- TcpExtDelayedACKs
|
||||
- TcpExtEmbryonicRsts
|
||||
- TcpExtIPReversePathFilter
|
||||
- TcpExtListenDrops
|
||||
- TcpExtListenOverflows
|
||||
- TcpExtLockDroppedIcmps
|
||||
- TcpExtOfoPruned
|
||||
- TcpExtOutOfWindowIcmps
|
||||
- TcpExtPAWSActive
|
||||
- TcpExtPAWSEstab
|
||||
- TcpExtPAWSPassive
|
||||
- TcpExtPruneCalled
|
||||
- TcpExtRcvPruned
|
||||
- TcpExtSyncookiesFailed
|
||||
- TcpExtSyncookiesRecv
|
||||
- TcpExtSyncookiesSent
|
||||
- TcpExtTCPACKSkippedChallenge
|
||||
- TcpExtTCPACKSkippedFinWait2
|
||||
- TcpExtTCPACKSkippedPAWS
|
||||
- TcpExtTCPACKSkippedSeq
|
||||
- TcpExtTCPACKSkippedSynRecv
|
||||
- TcpExtTCPACKSkippedTimeWait
|
||||
- TcpExtTCPAbortFailed
|
||||
- TcpExtTCPAbortOnClose
|
||||
- TcpExtTCPAbortOnData
|
||||
- TcpExtTCPAbortOnLinger
|
||||
- TcpExtTCPAbortOnMemory
|
||||
- TcpExtTCPAbortOnTimeout
|
||||
- TcpExtTCPAutoCorking
|
||||
- TcpExtTCPBacklogDrop
|
||||
- TcpExtTCPChallengeACK
|
||||
- TcpExtTCPDSACKIgnoredNoUndo
|
||||
- TcpExtTCPDSACKIgnoredOld
|
||||
- TcpExtTCPDSACKOfoRecv
|
||||
- TcpExtTCPDSACKOfoSent
|
||||
- TcpExtTCPDSACKOldSent
|
||||
- TcpExtTCPDSACKRecv
|
||||
- TcpExtTCPDSACKUndo
|
||||
- TcpExtTCPDeferAcceptDrop
|
||||
- TcpExtTCPDirectCopyFromBacklog
|
||||
- TcpExtTCPDirectCopyFromPrequeue
|
||||
- TcpExtTCPFACKReorder
|
||||
- TcpExtTCPFastOpenActive
|
||||
- TcpExtTCPFastOpenActiveFail
|
||||
- TcpExtTCPFastOpenCookieReqd
|
||||
- TcpExtTCPFastOpenListenOverflow
|
||||
- TcpExtTCPFastOpenPassive
|
||||
- TcpExtTCPFastOpenPassiveFail
|
||||
- TcpExtTCPFastRetrans
|
||||
- TcpExtTCPForwardRetrans
|
||||
- TcpExtTCPFromZeroWindowAdv
|
||||
- TcpExtTCPFullUndo
|
||||
- TcpExtTCPHPAcks
|
||||
- TcpExtTCPHPHits
|
||||
- TcpExtTCPHPHitsToUser
|
||||
- TcpExtTCPHystartDelayCwnd
|
||||
- TcpExtTCPHystartDelayDetect
|
||||
- TcpExtTCPHystartTrainCwnd
|
||||
- TcpExtTCPHystartTrainDetect
|
||||
- TcpExtTCPKeepAlive
|
||||
- TcpExtTCPLossFailures
|
||||
- TcpExtTCPLossProbeRecovery
|
||||
- TcpExtTCPLossProbes
|
||||
- TcpExtTCPLossUndo
|
||||
- TcpExtTCPLostRetransmit
|
||||
- TcpExtTCPMD5NotFound
|
||||
- TcpExtTCPMD5Unexpected
|
||||
- TcpExtTCPMTUPFail
|
||||
- TcpExtTCPMTUPSuccess
|
||||
- TcpExtTCPMemoryPressures
|
||||
- TcpExtTCPMinTTLDrop
|
||||
- TcpExtTCPOFODrop
|
||||
- TcpExtTCPOFOMerge
|
||||
- TcpExtTCPOFOQueue
|
||||
- TcpExtTCPOrigDataSent
|
||||
- TcpExtTCPPartialUndo
|
||||
- TcpExtTCPPrequeueDropped
|
||||
- TcpExtTCPPrequeued
|
||||
- TcpExtTCPPureAcks
|
||||
- TcpExtTCPRcvCoalesce
|
||||
- TcpExtTCPRcvCollapsed
|
||||
- TcpExtTCPRenoFailures
|
||||
- TcpExtTCPRenoRecovery
|
||||
- TcpExtTCPRenoRecoveryFail
|
||||
- TcpExtTCPRenoReorder
|
||||
- TcpExtTCPReqQFullDoCookies
|
||||
- TcpExtTCPReqQFullDrop
|
||||
- TcpExtTCPRetransFail
|
||||
- TcpExtTCPSACKDiscard
|
||||
- TcpExtTCPSACKReneging
|
||||
- TcpExtTCPSACKReorder
|
||||
- TcpExtTCPSYNChallenge
|
||||
- TcpExtTCPSackFailures
|
||||
- TcpExtTCPSackMerged
|
||||
- TcpExtTCPSackRecovery
|
||||
- TcpExtTCPSackRecoveryFail
|
||||
- TcpExtTCPSackShiftFallback
|
||||
- TcpExtTCPSackShifted
|
||||
- TcpExtTCPSchedulerFailed
|
||||
- TcpExtTCPSlowStartRetrans
|
||||
- TcpExtTCPSpuriousRTOs
|
||||
- TcpExtTCPSpuriousRtxHostQueues
|
||||
- TcpExtTCPSynRetrans
|
||||
- TcpExtTCPTSReorder
|
||||
- TcpExtTCPTimeWaitOverflow
|
||||
- TcpExtTCPTimeouts
|
||||
- TcpExtTCPToZeroWindowAdv
|
||||
- TcpExtTCPWantZeroWindowAdv
|
||||
- TcpExtTCPWinProbe
|
||||
- TcpExtTW
|
||||
- TcpExtTWKilled
|
||||
- TcpExtTWRecycled
|
||||
- TcpInCsumErrors
|
||||
- TcpInErrs
|
||||
- TcpInSegs
|
||||
- TcpMaxConn
|
||||
- TcpOutRsts
|
||||
- TcpOutSegs
|
||||
- TcpPassiveOpens
|
||||
- TcpRetransSegs
|
||||
- TcpRtoAlgorithm
|
||||
- TcpRtoMax
|
||||
- TcpRtoMin
|
||||
- Udp6IgnoredMulti
|
||||
- Udp6InCsumErrors
|
||||
- Udp6InDatagrams
|
||||
- Udp6InErrors
|
||||
- Udp6NoPorts
|
||||
- Udp6OutDatagrams
|
||||
- Udp6RcvbufErrors
|
||||
- Udp6SndbufErrors
|
||||
- UdpIgnoredMulti
|
||||
- UdpInCsumErrors
|
||||
- UdpInDatagrams
|
||||
- UdpInErrors
|
||||
- UdpLite6InCsumErrors
|
||||
- UdpLite6InDatagrams
|
||||
- UdpLite6InErrors
|
||||
- UdpLite6NoPorts
|
||||
- UdpLite6OutDatagrams
|
||||
- UdpLite6RcvbufErrors
|
||||
- UdpLite6SndbufErrors
|
||||
- UdpLiteIgnoredMulti
|
||||
- UdpLiteInCsumErrors
|
||||
- UdpLiteInDatagrams
|
||||
- UdpLiteInErrors
|
||||
- UdpLiteNoPorts
|
||||
- UdpLiteOutDatagrams
|
||||
- UdpLiteRcvbufErrors
|
||||
- UdpLiteSndbufErrors
|
||||
- UdpNoPorts
|
||||
- UdpOutDatagrams
|
||||
- UdpRcvbufErrors
|
||||
- UdpSndbufErrors
|
||||
|
||||
### Tags
|
||||
- All measurements have the following tags
|
||||
- host (host of the system)
|
||||
- name (the type of the metric: snmp, snmp6 or netstat)
|
||||
233
plugins/inputs/nstat/nstat.go
Normal file
233
plugins/inputs/nstat/nstat.go
Normal file
@@ -0,0 +1,233 @@
|
||||
package nstat
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
var (
|
||||
zeroByte = []byte("0")
|
||||
newLineByte = []byte("\n")
|
||||
colonByte = []byte(":")
|
||||
)
|
||||
|
||||
// default file paths
|
||||
const (
|
||||
NET_NETSTAT = "/net/netstat"
|
||||
NET_SNMP = "/net/snmp"
|
||||
NET_SNMP6 = "/net/snmp6"
|
||||
NET_PROC = "/proc"
|
||||
)
|
||||
|
||||
// env variable names
|
||||
const (
|
||||
ENV_NETSTAT = "PROC_NET_NETSTAT"
|
||||
ENV_SNMP = "PROC_NET_SNMP"
|
||||
ENV_SNMP6 = "PROC_NET_SNMP6"
|
||||
ENV_ROOT = "PROC_ROOT"
|
||||
)
|
||||
|
||||
type Nstat struct {
|
||||
ProcNetNetstat string `toml:"proc_net_netstat"`
|
||||
ProcNetSNMP string `toml:"proc_net_snmp"`
|
||||
ProcNetSNMP6 string `toml:"proc_net_snmp6"`
|
||||
DumpZeros bool `toml:"dump_zeros"`
|
||||
}
|
||||
|
||||
var sampleConfig = `
|
||||
## file paths for proc files. If empty default paths will be used:
|
||||
## /proc/net/netstat, /proc/net/snmp, /proc/net/snmp6
|
||||
## These can also be overridden with env variables, see README.
|
||||
proc_net_netstat = ""
|
||||
proc_net_snmp = ""
|
||||
proc_net_snmp6 = ""
|
||||
## dump metrics with 0 values too
|
||||
dump_zeros = true
|
||||
`
|
||||
|
||||
func (ns *Nstat) Description() string {
|
||||
return "Collect kernel snmp counters and network interface statistics"
|
||||
}
|
||||
|
||||
func (ns *Nstat) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func (ns *Nstat) Gather(acc telegraf.Accumulator) error {
|
||||
// load paths, get from env if config values are empty
|
||||
ns.loadPaths()
|
||||
|
||||
netstat, err := ioutil.ReadFile(ns.ProcNetNetstat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// collect netstat data
|
||||
err = ns.gatherNetstat(netstat, acc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// collect SNMP data
|
||||
snmp, err := ioutil.ReadFile(ns.ProcNetSNMP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ns.gatherSNMP(snmp, acc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// collect SNMP6 data
|
||||
snmp6, err := ioutil.ReadFile(ns.ProcNetSNMP6)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ns.gatherSNMP6(snmp6, acc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *Nstat) gatherNetstat(data []byte, acc telegraf.Accumulator) error {
|
||||
metrics, err := loadUglyTable(data, ns.DumpZeros)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tags := map[string]string{
|
||||
"name": "netstat",
|
||||
}
|
||||
acc.AddFields("nstat", metrics, tags)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *Nstat) gatherSNMP(data []byte, acc telegraf.Accumulator) error {
|
||||
metrics, err := loadUglyTable(data, ns.DumpZeros)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tags := map[string]string{
|
||||
"name": "snmp",
|
||||
}
|
||||
acc.AddFields("nstat", metrics, tags)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *Nstat) gatherSNMP6(data []byte, acc telegraf.Accumulator) error {
|
||||
metrics, err := loadGoodTable(data, ns.DumpZeros)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tags := map[string]string{
|
||||
"name": "snmp6",
|
||||
}
|
||||
acc.AddFields("nstat", metrics, tags)
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadPaths can be used to read paths firstly from config
|
||||
// if it is empty then try read from env variables
|
||||
func (ns *Nstat) loadPaths() {
|
||||
if ns.ProcNetNetstat == "" {
|
||||
ns.ProcNetNetstat = proc(ENV_NETSTAT, NET_NETSTAT)
|
||||
}
|
||||
if ns.ProcNetSNMP == "" {
|
||||
ns.ProcNetSNMP = proc(ENV_SNMP, NET_SNMP)
|
||||
}
|
||||
if ns.ProcNetSNMP6 == "" {
|
||||
ns.ProcNetSNMP = proc(ENV_SNMP6, NET_SNMP6)
|
||||
}
|
||||
}
|
||||
|
||||
// loadGoodTable can be used to parse string heap that
|
||||
// headers and values are arranged in right order
|
||||
func loadGoodTable(table []byte, dumpZeros bool) (map[string]interface{}, error) {
|
||||
entries := map[string]interface{}{}
|
||||
fields := bytes.Fields(table)
|
||||
var value int64
|
||||
var err error
|
||||
// iterate over two values each time
|
||||
// first value is header, second is value
|
||||
for i := 0; i < len(fields); i = i + 2 {
|
||||
// counter is zero
|
||||
if bytes.Equal(fields[i+1], zeroByte) {
|
||||
if !dumpZeros {
|
||||
continue
|
||||
} else {
|
||||
entries[string(fields[i])] = int64(0)
|
||||
continue
|
||||
}
|
||||
}
|
||||
// the counter is not zero, so parse it.
|
||||
value, err = strconv.ParseInt(string(fields[i+1]), 10, 64)
|
||||
if err == nil {
|
||||
entries[string(fields[i])] = value
|
||||
}
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// loadUglyTable can be used to parse string heap that
|
||||
// the headers and values are splitted with a newline
|
||||
func loadUglyTable(table []byte, dumpZeros bool) (map[string]interface{}, error) {
|
||||
entries := map[string]interface{}{}
|
||||
// split the lines by newline
|
||||
lines := bytes.Split(table, newLineByte)
|
||||
var value int64
|
||||
var err error
|
||||
// iterate over lines, take 2 lines each time
|
||||
// first line contains header names
|
||||
// second line contains values
|
||||
for i := 0; i < len(lines); i = i + 2 {
|
||||
if len(lines[i]) == 0 {
|
||||
continue
|
||||
}
|
||||
headers := bytes.Fields(lines[i])
|
||||
prefix := bytes.TrimSuffix(headers[0], colonByte)
|
||||
metrics := bytes.Fields(lines[i+1])
|
||||
|
||||
for j := 1; j < len(headers); j++ {
|
||||
// counter is zero
|
||||
if bytes.Equal(metrics[j], zeroByte) {
|
||||
if !dumpZeros {
|
||||
continue
|
||||
} else {
|
||||
entries[string(append(prefix, headers[j]...))] = int64(0)
|
||||
continue
|
||||
}
|
||||
}
|
||||
// the counter is not zero, so parse it.
|
||||
value, err = strconv.ParseInt(string(metrics[j]), 10, 64)
|
||||
if err == nil {
|
||||
entries[string(append(prefix, headers[j]...))] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// proc can be used to read file paths from env
|
||||
func proc(env, path string) string {
|
||||
// try to read full file path
|
||||
if p := os.Getenv(env); p != "" {
|
||||
return p
|
||||
}
|
||||
// try to read root path, or use default root path
|
||||
root := os.Getenv(ENV_ROOT)
|
||||
if root == "" {
|
||||
root = NET_PROC
|
||||
}
|
||||
return root + path
|
||||
}
|
||||
|
||||
func init() {
|
||||
inputs.Add("nstat", func() telegraf.Input {
|
||||
return &Nstat{}
|
||||
})
|
||||
}
|
||||
56
plugins/inputs/nstat/nstat_test.go
Normal file
56
plugins/inputs/nstat/nstat_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package nstat
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestLoadUglyTable(t *testing.T) {
|
||||
uglyStr := `IpExt: InNoRoutes InTruncatedPkts InMcastPkts InCEPkts
|
||||
IpExt: 332 433718 0 2660494435`
|
||||
parsed := map[string]interface{}{
|
||||
"IpExtInNoRoutes": int64(332),
|
||||
"IpExtInTruncatedPkts": int64(433718),
|
||||
"IpExtInMcastPkts": int64(0),
|
||||
"IpExtInCEPkts": int64(2660494435),
|
||||
}
|
||||
|
||||
got, err := loadUglyTable([]byte(uglyStr), true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(got) == 0 {
|
||||
t.Fatalf("want %+v, got %+v", parsed, got)
|
||||
}
|
||||
|
||||
for key := range parsed {
|
||||
if parsed[key].(int64) != got[key].(int64) {
|
||||
t.Fatalf("want %+v, got %+v", parsed[key], got[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadGoodTable(t *testing.T) {
|
||||
goodStr := `Ip6InReceives 11707
|
||||
Ip6InTooBigErrors 0
|
||||
Ip6InDelivers 62
|
||||
Ip6InMcastOctets 1242966`
|
||||
|
||||
parsed := map[string]interface{}{
|
||||
"Ip6InReceives": int64(11707),
|
||||
"Ip6InTooBigErrors": int64(0),
|
||||
"Ip6InDelivers": int64(62),
|
||||
"Ip6InMcastOctets": int64(1242966),
|
||||
}
|
||||
got, err := loadGoodTable([]byte(goodStr), true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(got) == 0 {
|
||||
t.Fatalf("want %+v, got %+v", parsed, got)
|
||||
}
|
||||
|
||||
for key := range parsed {
|
||||
if parsed[key].(int64) != got[key].(int64) {
|
||||
t.Fatalf("want %+v, got %+v", parsed[key], got[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,17 @@ func (n *NTPQ) Gather(acc telegraf.Accumulator) error {
|
||||
lineCounter := 0
|
||||
scanner := bufio.NewScanner(bytes.NewReader(out))
|
||||
for scanner.Scan() {
|
||||
fields := strings.Fields(scanner.Text())
|
||||
line := scanner.Text()
|
||||
|
||||
tags := make(map[string]string)
|
||||
// if there is an ntpq state prefix, remove it and make it it's own tag
|
||||
// see https://github.com/influxdata/telegraf/issues/1161
|
||||
if strings.ContainsAny(string(line[0]), "*#o+x.-") {
|
||||
tags["state_prefix"] = string(line[0])
|
||||
line = strings.TrimLeft(line, "*#o+x.-")
|
||||
}
|
||||
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
@@ -97,7 +107,6 @@ func (n *NTPQ) Gather(acc telegraf.Accumulator) error {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tags := make(map[string]string)
|
||||
mFields := make(map[string]interface{})
|
||||
|
||||
// Get tags from output
|
||||
@@ -113,6 +122,9 @@ func (n *NTPQ) Gather(acc telegraf.Accumulator) error {
|
||||
if index == -1 {
|
||||
continue
|
||||
}
|
||||
if fields[index] == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
if key == "when" {
|
||||
when := fields[index]
|
||||
@@ -160,6 +172,9 @@ func (n *NTPQ) Gather(acc telegraf.Accumulator) error {
|
||||
if index == -1 {
|
||||
continue
|
||||
}
|
||||
if fields[index] == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
m, err := strconv.ParseFloat(fields[index], 64)
|
||||
if err != nil {
|
||||
|
||||
@@ -32,10 +32,11 @@ func TestSingleNTPQ(t *testing.T) {
|
||||
"jitter": float64(17.462),
|
||||
}
|
||||
tags := map[string]string{
|
||||
"remote": "*uschi5-ntp-002.",
|
||||
"refid": "10.177.80.46",
|
||||
"stratum": "2",
|
||||
"type": "u",
|
||||
"remote": "uschi5-ntp-002.",
|
||||
"state_prefix": "*",
|
||||
"refid": "10.177.80.46",
|
||||
"stratum": "2",
|
||||
"type": "u",
|
||||
}
|
||||
acc.AssertContainsTaggedFields(t, "ntpq", fields, tags)
|
||||
}
|
||||
@@ -60,10 +61,11 @@ func TestBadIntNTPQ(t *testing.T) {
|
||||
"jitter": float64(17.462),
|
||||
}
|
||||
tags := map[string]string{
|
||||
"remote": "*uschi5-ntp-002.",
|
||||
"refid": "10.177.80.46",
|
||||
"stratum": "2",
|
||||
"type": "u",
|
||||
"remote": "uschi5-ntp-002.",
|
||||
"state_prefix": "*",
|
||||
"refid": "10.177.80.46",
|
||||
"stratum": "2",
|
||||
"type": "u",
|
||||
}
|
||||
acc.AssertContainsTaggedFields(t, "ntpq", fields, tags)
|
||||
}
|
||||
@@ -88,10 +90,11 @@ func TestBadFloatNTPQ(t *testing.T) {
|
||||
"jitter": float64(17.462),
|
||||
}
|
||||
tags := map[string]string{
|
||||
"remote": "*uschi5-ntp-002.",
|
||||
"refid": "10.177.80.46",
|
||||
"stratum": "2",
|
||||
"type": "u",
|
||||
"remote": "uschi5-ntp-002.",
|
||||
"state_prefix": "*",
|
||||
"refid": "10.177.80.46",
|
||||
"stratum": "2",
|
||||
"type": "u",
|
||||
}
|
||||
acc.AssertContainsTaggedFields(t, "ntpq", fields, tags)
|
||||
}
|
||||
@@ -117,10 +120,11 @@ func TestDaysNTPQ(t *testing.T) {
|
||||
"jitter": float64(17.462),
|
||||
}
|
||||
tags := map[string]string{
|
||||
"remote": "*uschi5-ntp-002.",
|
||||
"refid": "10.177.80.46",
|
||||
"stratum": "2",
|
||||
"type": "u",
|
||||
"remote": "uschi5-ntp-002.",
|
||||
"state_prefix": "*",
|
||||
"refid": "10.177.80.46",
|
||||
"stratum": "2",
|
||||
"type": "u",
|
||||
}
|
||||
acc.AssertContainsTaggedFields(t, "ntpq", fields, tags)
|
||||
}
|
||||
@@ -146,10 +150,11 @@ func TestHoursNTPQ(t *testing.T) {
|
||||
"jitter": float64(17.462),
|
||||
}
|
||||
tags := map[string]string{
|
||||
"remote": "*uschi5-ntp-002.",
|
||||
"refid": "10.177.80.46",
|
||||
"stratum": "2",
|
||||
"type": "u",
|
||||
"remote": "uschi5-ntp-002.",
|
||||
"state_prefix": "*",
|
||||
"refid": "10.177.80.46",
|
||||
"stratum": "2",
|
||||
"type": "u",
|
||||
}
|
||||
acc.AssertContainsTaggedFields(t, "ntpq", fields, tags)
|
||||
}
|
||||
@@ -175,10 +180,11 @@ func TestMinutesNTPQ(t *testing.T) {
|
||||
"jitter": float64(17.462),
|
||||
}
|
||||
tags := map[string]string{
|
||||
"remote": "*uschi5-ntp-002.",
|
||||
"refid": "10.177.80.46",
|
||||
"stratum": "2",
|
||||
"type": "u",
|
||||
"remote": "uschi5-ntp-002.",
|
||||
"state_prefix": "*",
|
||||
"refid": "10.177.80.46",
|
||||
"stratum": "2",
|
||||
"type": "u",
|
||||
}
|
||||
acc.AssertContainsTaggedFields(t, "ntpq", fields, tags)
|
||||
}
|
||||
@@ -203,10 +209,11 @@ func TestBadWhenNTPQ(t *testing.T) {
|
||||
"jitter": float64(17.462),
|
||||
}
|
||||
tags := map[string]string{
|
||||
"remote": "*uschi5-ntp-002.",
|
||||
"refid": "10.177.80.46",
|
||||
"stratum": "2",
|
||||
"type": "u",
|
||||
"remote": "uschi5-ntp-002.",
|
||||
"state_prefix": "*",
|
||||
"refid": "10.177.80.46",
|
||||
"stratum": "2",
|
||||
"type": "u",
|
||||
}
|
||||
acc.AssertContainsTaggedFields(t, "ntpq", fields, tags)
|
||||
}
|
||||
@@ -278,9 +285,10 @@ func TestBadHeaderNTPQ(t *testing.T) {
|
||||
"jitter": float64(17.462),
|
||||
}
|
||||
tags := map[string]string{
|
||||
"remote": "*uschi5-ntp-002.",
|
||||
"refid": "10.177.80.46",
|
||||
"type": "u",
|
||||
"remote": "uschi5-ntp-002.",
|
||||
"state_prefix": "*",
|
||||
"refid": "10.177.80.46",
|
||||
"type": "u",
|
||||
}
|
||||
acc.AssertContainsTaggedFields(t, "ntpq", fields, tags)
|
||||
}
|
||||
@@ -306,9 +314,10 @@ func TestMissingDelayColumnNTPQ(t *testing.T) {
|
||||
"jitter": float64(17.462),
|
||||
}
|
||||
tags := map[string]string{
|
||||
"remote": "*uschi5-ntp-002.",
|
||||
"refid": "10.177.80.46",
|
||||
"type": "u",
|
||||
"remote": "uschi5-ntp-002.",
|
||||
"state_prefix": "*",
|
||||
"refid": "10.177.80.46",
|
||||
"type": "u",
|
||||
}
|
||||
acc.AssertContainsTaggedFields(t, "ntpq", fields, tags)
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ type Ping struct {
|
||||
// Number of pings to send (ping -c <COUNT>)
|
||||
Count int
|
||||
|
||||
// Ping timeout, in seconds. 0 means no timeout (ping -t <TIMEOUT>)
|
||||
// Ping timeout, in seconds. 0 means no timeout (ping -W <TIMEOUT>)
|
||||
Timeout float64
|
||||
|
||||
// Interface to send ping from (ping -I <INTERFACE>)
|
||||
@@ -55,7 +55,7 @@ const sampleConfig = `
|
||||
count = 1 # required
|
||||
## interval, in s, at which to ping. 0 == default (ping -i <PING_INTERVAL>)
|
||||
ping_interval = 0.0
|
||||
## ping timeout, in s. 0 == no timeout (ping -W <TIMEOUT>)
|
||||
## per-ping timeout, in s. 0 == no timeout (ping -W <TIMEOUT>)
|
||||
timeout = 1.0
|
||||
## interface to send ping from (ping -I <INTERFACE>)
|
||||
interface = ""
|
||||
@@ -76,7 +76,8 @@ func (p *Ping) Gather(acc telegraf.Accumulator) error {
|
||||
go func(u string) {
|
||||
defer wg.Done()
|
||||
args := p.args(u)
|
||||
out, err := p.pingHost(p.Timeout, args...)
|
||||
totalTimeout := float64(p.Count)*p.Timeout + float64(p.Count-1)*p.PingInterval
|
||||
out, err := p.pingHost(totalTimeout, args...)
|
||||
if err != nil {
|
||||
// Combine go err + stderr output
|
||||
errorChannel <- errors.New(
|
||||
@@ -138,8 +139,8 @@ func (p *Ping) args(url string) []string {
|
||||
}
|
||||
if p.Timeout > 0 {
|
||||
switch runtime.GOOS {
|
||||
case "darwin", "freebsd":
|
||||
args = append(args, "-t", strconv.FormatFloat(p.Timeout, 'f', 1, 64))
|
||||
case "darwin":
|
||||
args = append(args, "-W", strconv.FormatFloat(p.Timeout/1000, 'f', 1, 64))
|
||||
case "linux":
|
||||
args = append(args, "-W", strconv.FormatFloat(p.Timeout, 'f', 1, 64))
|
||||
default:
|
||||
|
||||
@@ -15,11 +15,12 @@ import (
|
||||
)
|
||||
|
||||
type Procstat struct {
|
||||
PidFile string `toml:"pid_file"`
|
||||
Exe string
|
||||
Pattern string
|
||||
Prefix string
|
||||
User string
|
||||
PidFile string `toml:"pid_file"`
|
||||
Exe string
|
||||
Pattern string
|
||||
Prefix string
|
||||
ProcessName string
|
||||
User string
|
||||
|
||||
// pidmap maps a pid to a process object, so we don't recreate every gather
|
||||
pidmap map[int32]*process.Process
|
||||
@@ -45,6 +46,9 @@ var sampleConfig = `
|
||||
## user as argument for pgrep (ie, pgrep -u <user>)
|
||||
# user = "nginx"
|
||||
|
||||
## override for process_name
|
||||
## This is optional; default is sourced from /proc/<pid>/status
|
||||
# process_name = "bar"
|
||||
## Field name prefix
|
||||
prefix = ""
|
||||
## comment this out if you want raw cpu_time stats
|
||||
@@ -66,7 +70,7 @@ func (p *Procstat) Gather(acc telegraf.Accumulator) error {
|
||||
p.Exe, p.PidFile, p.Pattern, p.User, err.Error())
|
||||
} else {
|
||||
for pid, proc := range p.pidmap {
|
||||
p := NewSpecProcessor(p.Prefix, acc, proc, p.tagmap[pid])
|
||||
p := NewSpecProcessor(p.ProcessName, p.Prefix, acc, proc, p.tagmap[pid])
|
||||
p.pushMetrics()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,13 +17,19 @@ type SpecProcessor struct {
|
||||
}
|
||||
|
||||
func NewSpecProcessor(
|
||||
processName string,
|
||||
prefix string,
|
||||
acc telegraf.Accumulator,
|
||||
p *process.Process,
|
||||
tags map[string]string,
|
||||
) *SpecProcessor {
|
||||
if name, err := p.Name(); err == nil {
|
||||
tags["process_name"] = name
|
||||
if processName != "" {
|
||||
tags["process_name"] = processName
|
||||
} else {
|
||||
name, err := p.Name()
|
||||
if err == nil {
|
||||
tags["process_name"] = name
|
||||
}
|
||||
}
|
||||
return &SpecProcessor{
|
||||
Prefix: prefix,
|
||||
@@ -65,7 +71,7 @@ func (p *SpecProcessor) pushMetrics() {
|
||||
fields[prefix+"write_bytes"] = io.WriteCount
|
||||
}
|
||||
|
||||
cpu_time, err := p.proc.CPUTimes()
|
||||
cpu_time, err := p.proc.Times()
|
||||
if err == nil {
|
||||
fields[prefix+"cpu_time_user"] = cpu_time.User
|
||||
fields[prefix+"cpu_time_system"] = cpu_time.System
|
||||
@@ -80,7 +86,7 @@ func (p *SpecProcessor) pushMetrics() {
|
||||
fields[prefix+"cpu_time_guest_nice"] = cpu_time.GuestNice
|
||||
}
|
||||
|
||||
cpu_perc, err := p.proc.CPUPercent(time.Duration(0))
|
||||
cpu_perc, err := p.proc.Percent(time.Duration(0))
|
||||
if err == nil && cpu_perc != 0 {
|
||||
fields[prefix+"cpu_usage"] = cpu_perc
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"io"
|
||||
"math"
|
||||
"mime"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
|
||||
@@ -74,13 +75,13 @@ func (p *PrometheusParser) Parse(buf []byte) ([]telegraf.Metric, error) {
|
||||
if mf.GetType() == dto.MetricType_SUMMARY {
|
||||
// summary metric
|
||||
fields = makeQuantiles(m)
|
||||
fields["count"] = float64(m.GetHistogram().GetSampleCount())
|
||||
fields["count"] = float64(m.GetSummary().GetSampleCount())
|
||||
fields["sum"] = float64(m.GetSummary().GetSampleSum())
|
||||
} else if mf.GetType() == dto.MetricType_HISTOGRAM {
|
||||
// historgram metric
|
||||
fields = makeBuckets(m)
|
||||
fields["count"] = float64(m.GetHistogram().GetSampleCount())
|
||||
fields["sum"] = float64(m.GetSummary().GetSampleSum())
|
||||
fields["sum"] = float64(m.GetHistogram().GetSampleSum())
|
||||
|
||||
} else {
|
||||
// standard metric
|
||||
@@ -88,7 +89,13 @@ func (p *PrometheusParser) Parse(buf []byte) ([]telegraf.Metric, error) {
|
||||
}
|
||||
// converting to telegraf metric
|
||||
if len(fields) > 0 {
|
||||
metric, err := telegraf.NewMetric(metricName, tags, fields)
|
||||
var t time.Time
|
||||
if m.TimestampMs != nil && *m.TimestampMs > 0 {
|
||||
t = time.Unix(0, *m.TimestampMs*1000000)
|
||||
} else {
|
||||
t = time.Now()
|
||||
}
|
||||
metric, err := telegraf.NewMetric(metricName, tags, fields, t)
|
||||
if err == nil {
|
||||
metrics = append(metrics, metric)
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ func TestParseValidPrometheus(t *testing.T) {
|
||||
"0.5": 552048.506,
|
||||
"0.9": 5.876804288e+06,
|
||||
"0.99": 5.876804288e+06,
|
||||
"count": 0.0,
|
||||
"count": 9.0,
|
||||
"sum": 1.8909097205e+07,
|
||||
}, metrics[0].Fields())
|
||||
assert.Equal(t, map[string]string{"handler": "prometheus"}, metrics[0].Tags())
|
||||
@@ -151,7 +151,7 @@ func TestParseValidPrometheus(t *testing.T) {
|
||||
assert.Equal(t, map[string]interface{}{
|
||||
"500000": 2000.0,
|
||||
"count": 2025.0,
|
||||
"sum": 0.0,
|
||||
"sum": 1.02726334e+08,
|
||||
"250000": 1997.0,
|
||||
"2e+06": 2012.0,
|
||||
"4e+06": 2017.0,
|
||||
|
||||
@@ -5,9 +5,11 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/internal/errchan"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
@@ -102,7 +104,7 @@ type gatherFunc func(r *RabbitMQ, acc telegraf.Accumulator, errChan chan error)
|
||||
var gatherFunctions = []gatherFunc{gatherOverview, gatherNodes, gatherQueues}
|
||||
|
||||
var sampleConfig = `
|
||||
url = "http://localhost:15672" # required
|
||||
# url = "http://localhost:15672"
|
||||
# name = "rmq-server-1" # optional tag
|
||||
# username = "guest"
|
||||
# password = "guest"
|
||||
@@ -129,23 +131,24 @@ func (r *RabbitMQ) Gather(acc telegraf.Accumulator) error {
|
||||
}
|
||||
}
|
||||
|
||||
var errChan = make(chan error, len(gatherFunctions))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(gatherFunctions))
|
||||
errChan := errchan.New(len(gatherFunctions))
|
||||
for _, f := range gatherFunctions {
|
||||
go f(r, acc, errChan)
|
||||
go func(gf gatherFunc) {
|
||||
defer wg.Done()
|
||||
gf(r, acc, errChan.C)
|
||||
}(f)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
for i := 1; i <= len(gatherFunctions); i++ {
|
||||
err := <-errChan
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return errChan.Error()
|
||||
}
|
||||
|
||||
func (r *RabbitMQ) requestJSON(u string, target interface{}) error {
|
||||
if r.URL == "" {
|
||||
r.URL = DefaultURL
|
||||
}
|
||||
u = fmt.Sprintf("%s%s", r.URL, u)
|
||||
|
||||
req, err := http.NewRequest("GET", u, nil)
|
||||
|
||||
@@ -74,6 +74,7 @@ var Tracking = map[string]string{
|
||||
"used_cpu_user": "used_cpu_user",
|
||||
"used_cpu_sys_children": "used_cpu_sys_children",
|
||||
"used_cpu_user_children": "used_cpu_user_children",
|
||||
"role": "role",
|
||||
}
|
||||
|
||||
var ErrProtocolError = errors.New("redis protocol error")
|
||||
@@ -206,6 +207,11 @@ func gatherInfoOutput(
|
||||
keyspace_misses = ival
|
||||
}
|
||||
|
||||
if name == "role" {
|
||||
tags["role"] = val
|
||||
continue
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
fields[metric] = ival
|
||||
continue
|
||||
@@ -235,10 +241,14 @@ func gatherKeyspaceLine(
|
||||
name string,
|
||||
line string,
|
||||
acc telegraf.Accumulator,
|
||||
tags map[string]string,
|
||||
global_tags map[string]string,
|
||||
) {
|
||||
if strings.Contains(line, "keys=") {
|
||||
fields := make(map[string]interface{})
|
||||
tags := make(map[string]string)
|
||||
for k, v := range global_tags {
|
||||
tags[k] = v
|
||||
}
|
||||
tags["database"] = name
|
||||
dbparts := strings.Split(line, ",")
|
||||
for _, dbp := range dbparts {
|
||||
|
||||
@@ -35,6 +35,7 @@ func TestRedis_ParseMetrics(t *testing.T) {
|
||||
err := gatherInfoOutput(rdr, &acc, tags)
|
||||
require.NoError(t, err)
|
||||
|
||||
tags = map[string]string{"host": "redis.net", "role": "master"}
|
||||
fields := map[string]interface{}{
|
||||
"uptime": uint64(238),
|
||||
"clients": uint64(1),
|
||||
@@ -70,13 +71,14 @@ func TestRedis_ParseMetrics(t *testing.T) {
|
||||
"used_cpu_user_children": float64(0.00),
|
||||
"keyspace_hitrate": float64(0.50),
|
||||
}
|
||||
keyspaceTags := map[string]string{"host": "redis.net", "role": "master", "database": "db0"}
|
||||
keyspaceFields := map[string]interface{}{
|
||||
"avg_ttl": uint64(0),
|
||||
"expires": uint64(0),
|
||||
"keys": uint64(2),
|
||||
}
|
||||
acc.AssertContainsTaggedFields(t, "redis", fields, tags)
|
||||
acc.AssertContainsTaggedFields(t, "redis_keyspace", keyspaceFields, tags)
|
||||
acc.AssertContainsTaggedFields(t, "redis_keyspace", keyspaceFields, keyspaceTags)
|
||||
}
|
||||
|
||||
const testOutput = `# Server
|
||||
|
||||
47
plugins/inputs/rollbar_webhooks/README.md
Normal file
47
plugins/inputs/rollbar_webhooks/README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# rollbar_webhooks
|
||||
|
||||
This is a Telegraf service plugin that listens for events kicked off by Rollbar Webhooks service and persists data from them into configured outputs. To set up the listener first generate the proper configuration:
|
||||
```sh
|
||||
$ telegraf -sample-config -input-filter rollbar_webhooks -output-filter influxdb > config.conf.new
|
||||
```
|
||||
Change the config file to point to the InfluxDB server you are using and adjust the settings to match your environment. Once that is complete:
|
||||
```sh
|
||||
$ cp config.conf.new /etc/telegraf/telegraf.conf
|
||||
$ sudo service telegraf start
|
||||
```
|
||||
Once the server is running you should configure your Rollbar's Webhooks to point at the `rollbar_webhooks` service. To do this go to `rollbar.com/` and click `Settings > Notifications > Webhook`. In the resulting page set `URL` to `http://<my_ip>:1619`, and click on `Enable Webhook Integration`.
|
||||
|
||||
## Events
|
||||
|
||||
The titles of the following sections are links to the full payloads and details for each event. The body contains what information from the event is persisted. The format is as follows:
|
||||
```
|
||||
# TAGS
|
||||
* 'tagKey' = `tagValue` type
|
||||
# FIELDS
|
||||
* 'fieldKey' = `fieldValue` type
|
||||
```
|
||||
The tag values and field values show the place on the incoming JSON object where the data is sourced from.
|
||||
|
||||
See [webhook doc](https://rollbar.com/docs/webhooks/)
|
||||
|
||||
#### `new_item` event
|
||||
|
||||
**Tags:**
|
||||
* 'event' = `event.event_name` string
|
||||
* 'environment' = `event.data.item.environment` string
|
||||
* 'project_id = `event.data.item.project_id` int
|
||||
* 'language' = `event.data.item.last_occurence.language` string
|
||||
* 'level' = `event.data.item.last_occurence.level` string
|
||||
|
||||
**Fields:**
|
||||
* 'id' = `event.data.item.id` int
|
||||
|
||||
#### `deploy` event
|
||||
|
||||
**Tags:**
|
||||
* 'event' = `event.event_name` string
|
||||
* 'environment' = `event.data.deploy.environment` string
|
||||
* 'project_id = `event.data.deploy.project_id` int
|
||||
|
||||
**Fields:**
|
||||
* 'id' = `event.data.item.id` int
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user