Compare commits
377 Commits
0.12.1
...
ga-version
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9480b28681 | ||
|
|
c3aa43a6bd | ||
|
|
b2ea39077e | ||
|
|
811567a2f4 | ||
|
|
ca8fb440cc | ||
|
|
ac58a6bb3c | ||
|
|
9757d39240 | ||
|
|
5a9e7d77b8 | ||
|
|
e963b7f01b | ||
|
|
e7899d4dc5 | ||
|
|
301c79e57c | ||
|
|
67c288abda | ||
|
|
8dd2a8527a | ||
|
|
2fe427b3b3 | ||
|
|
6b1cc67664 | ||
|
|
1271f9d71a | ||
|
|
49ea4e9f39 | ||
|
|
50ef3282b6 | ||
|
|
b63dedb74d | ||
|
|
5628049440 | ||
|
|
54c9ba7639 | ||
|
|
b18d375d6c | ||
|
|
6dbbe65897 | ||
|
|
03d8abccdd | ||
|
|
0f6d317a8e | ||
|
|
792682590c | ||
|
|
2d3da343b3 | ||
|
|
094eda22c0 | ||
|
|
4886109d9c | ||
|
|
2dc47285bd | ||
|
|
6e33a6d62f | ||
|
|
a8f9eb23cc | ||
|
|
41a5ee6571 | ||
|
|
7d8de4b8e1 | ||
|
|
cc2b53abf4 | ||
|
|
32aa1cc814 | ||
|
|
38d877165a | ||
|
|
5c5984bfe1 | ||
|
|
30cdc31a27 | ||
|
|
602a36e241 | ||
|
|
b863ee1d65 | ||
|
|
ca49babf3a | ||
|
|
cf37b5cdcf | ||
|
|
969f388ef2 | ||
|
|
0589a1d0a5 | ||
|
|
4e019a176d | ||
|
|
a0e23d30fe | ||
|
|
e931706249 | ||
|
|
2457d95262 | ||
|
|
e9d33726a9 | ||
|
|
2462e04bf2 | ||
|
|
7fac74919c | ||
|
|
b022b5567d | ||
|
|
dbf6380e4b | ||
|
|
a0e42f8a61 | ||
|
|
94e673fe85 | ||
|
|
7600757f16 | ||
|
|
4ce8dd5f9a | ||
|
|
26315bfbea | ||
|
|
a282fb8524 | ||
|
|
dee98612e2 | ||
|
|
69e4e862a3 | ||
|
|
c0e895c3a7 | ||
|
|
fec9760f72 | ||
|
|
1989a5855d | ||
|
|
abcd19493e | ||
|
|
e457b7a8df | ||
|
|
3853d0d065 | ||
|
|
53e31cf1b5 | ||
|
|
c99c22534b | ||
|
|
8e22526756 | ||
|
|
7b6713b094 | ||
|
|
b0ef506a88 | ||
|
|
22c293de62 | ||
|
|
d3bb1e7010 | ||
|
|
49988b15a3 | ||
|
|
f0357b7a12 | ||
|
|
9d3ad6309e | ||
|
|
b55e9e78e3 | ||
|
|
4bc6fdb09e | ||
|
|
2b43b385de | ||
|
|
13865f9e04 | ||
|
|
497353e586 | ||
|
|
2d86dfba8b | ||
|
|
30dbfd9af8 | ||
|
|
c991b579d2 | ||
|
|
841729c0f9 | ||
|
|
412f5b5acb | ||
|
|
0b3958d3cd | ||
|
|
e68f251df7 | ||
|
|
986735234b | ||
|
|
4363eebc1b | ||
|
|
1be6ea5696 | ||
|
|
8acda0da8f | ||
|
|
ee240a5599 | ||
|
|
29ea433763 | ||
|
|
0462af164e | ||
|
|
1c24665b29 | ||
|
|
0af0fa7c2e | ||
|
|
191608041f | ||
|
|
42d9d5d237 | ||
|
|
d54b169d67 | ||
|
|
82166a36d0 | ||
|
|
cbf5a55c7d | ||
|
|
5f14ad9fa1 | ||
|
|
0be69b8a44 | ||
|
|
375710488d | ||
|
|
03d02fa67a | ||
|
|
b58cd78c79 | ||
|
|
dabb6f5466 | ||
|
|
281a4d5500 | ||
|
|
1c2965703d | ||
|
|
5dc4cce157 | ||
|
|
8c7edeb53b | ||
|
|
1d9745ee98 | ||
|
|
2d6c8767f7 | ||
|
|
b4a6d9c647 | ||
|
|
6afe9ceef1 | ||
|
|
704d9ad76c | ||
|
|
300d9adbd0 | ||
|
|
207c5498e7 | ||
|
|
d5e7439343 | ||
|
|
21add2c799 | ||
|
|
4651ab88ad | ||
|
|
53f40063b3 | ||
|
|
97d92bba67 | ||
|
|
bfdd665435 | ||
|
|
821d3fafa6 | ||
|
|
7c9b312cee | ||
|
|
69ab8a645c | ||
|
|
7b550c11cb | ||
|
|
bb4f18ca88 | ||
|
|
6efe91ea9c | ||
|
|
5f0a63f554 | ||
|
|
d14e7536ab | ||
|
|
c873937356 | ||
|
|
e1c3800cd9 | ||
|
|
c046232425 | ||
|
|
2d4864e126 | ||
|
|
048448aa93 | ||
|
|
755b2ec953 | ||
|
|
f62c493c77 | ||
|
|
a6365a6086 | ||
|
|
f7e057ec55 | ||
|
|
30cc00d11b | ||
|
|
d641c42029 | ||
|
|
9c2ca805da | ||
|
|
b0484d8a0c | ||
|
|
5ddd61d2e2 | ||
|
|
50ea7f4a9d | ||
|
|
b18134a4e3 | ||
|
|
7825df4771 | ||
|
|
d6951dacdc | ||
|
|
e603825e37 | ||
|
|
e3448153e1 | ||
|
|
25848c545a | ||
|
|
3098564896 | ||
|
|
4b6f9b93dd | ||
|
|
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 | ||
|
|
90a98c76a0 | ||
|
|
12357ee8c5 | ||
|
|
bb254fc2b9 | ||
|
|
aeadc2c43a | ||
|
|
ed492fe950 | ||
|
|
775daba8f5 | ||
|
|
677dd7ad53 | ||
|
|
85dee02a3b | ||
|
|
afdebbc3a2 | ||
|
|
5deb22a539 | ||
|
|
36b9e2e077 | ||
|
|
5348937c3d | ||
|
|
72fcacbbc7 | ||
|
|
4c28f15b35 | ||
|
|
095ef04c04 | ||
|
|
7d49979658 | ||
|
|
7a36695a21 | ||
|
|
5865587bd0 | ||
|
|
219bf93566 | ||
|
|
8371546a66 | ||
|
|
0b9b7bddd7 | ||
|
|
4c8449f4bc | ||
|
|
36d7b5c9ab | ||
|
|
f2b0ea6722 | ||
|
|
46f4be88a6 | ||
|
|
6381efa7ce | ||
|
|
85ee66efb9 | ||
|
|
40dccf5b29 | ||
|
|
c114849a31 | ||
|
|
4e9798d0e6 | ||
|
|
a30b1a394f | ||
|
|
91460436cf | ||
|
|
3f807a9432 | ||
|
|
cbe32c7482 | ||
|
|
5d3c582ecf | ||
|
|
3ed006d216 | ||
|
|
3e1026286b | ||
|
|
b59266249d | ||
|
|
015261a524 | ||
|
|
024e1088eb | ||
|
|
08f4b1ae8a | ||
|
|
1390c22004 | ||
|
|
8742ead585 | ||
|
|
59a297abe6 | ||
|
|
18636ea628 | ||
|
|
cf5980ace2 | ||
|
|
a7b0861436 | ||
|
|
89f2c0b0a4 | ||
|
|
ee4f4d7800 | ||
|
|
4de75ce621 | ||
|
|
1c4043ab39 | ||
|
|
44c945b9f5 | ||
|
|
c7719ac365 | ||
|
|
b9c24189e4 | ||
|
|
411d8d7439 | ||
|
|
671b40df2a | ||
|
|
249a860c6f | ||
|
|
0367a39e1f | ||
|
|
1a7340bb02 | ||
|
|
ce7d852d22 | ||
|
|
01b01c5969 | ||
|
|
c159460b2c | ||
|
|
07728d7425 | ||
|
|
d3a25e4dc1 | ||
|
|
1751c35f69 | ||
|
|
93f5b8cc4a | ||
|
|
5b1e59a48c | ||
|
|
7b27cad1ba | ||
|
|
1b083d63ab | ||
|
|
23f2b47531 | ||
|
|
194288c00e | ||
|
|
f9c8ed0dc3 | ||
|
|
88def9b71b | ||
|
|
f818f44693 | ||
|
|
8a395fdb4a | ||
|
|
c0588926b8 | ||
|
|
f1b7ecb2a2 | ||
|
|
4bcf157d88 | ||
|
|
2f7da03cce | ||
|
|
f1c995dcb8 | ||
|
|
9aec58c6b8 | ||
|
|
46aaaa9b70 | ||
|
|
46543d6323 | ||
|
|
a585119a67 | ||
|
|
8cc72368ca | ||
|
|
92e57ee06c | ||
|
|
c737a19d9f | ||
|
|
708a97d773 | ||
|
|
b95a90dbd6 | ||
|
|
a2d1ee08d4 | ||
|
|
7e64dc380f | ||
|
|
046cb6a564 | ||
|
|
644ce9edab | ||
|
|
059b601b13 | ||
|
|
d59999f510 | ||
|
|
c5d31e7527 | ||
|
|
c121e38da6 | ||
|
|
b16bc3d2e3 | ||
|
|
c732abbda2 | ||
|
|
61d681a7c8 | ||
|
|
7828bc09cf | ||
|
|
36d330fea0 | ||
|
|
4d46589d39 | ||
|
|
93f57edd3a | ||
|
|
8ec8ae0587 | ||
|
|
ce94e636bb | ||
|
|
21c7378b61 | ||
|
|
75a9845d20 | ||
|
|
d638f6e411 | ||
|
|
81d0a64d46 |
4
.gitattributes
vendored
Normal file
4
.gitattributes
vendored
Normal file
@@ -0,0 +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 (we recommend not updating this until the PR has been approved by a maintainer)
|
||||||
|
- [ ] Sign [CLA](https://influxdata.com/community/cla/) (if not already signed)
|
||||||
|
- [ ] README.md updated (if adding a new plugin)
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
|
build
|
||||||
tivan
|
tivan
|
||||||
.vagrant
|
.vagrant
|
||||||
/telegraf
|
/telegraf
|
||||||
|
|||||||
315
CHANGELOG.md
315
CHANGELOG.md
@@ -1,4 +1,317 @@
|
|||||||
## v0.13 [unreleased]
|
## v1.1 [unreleased]
|
||||||
|
|
||||||
|
### Release Notes
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- [#1694](https://github.com/influxdata/telegraf/pull/1694): Adding Gauge and Counter metric types.
|
||||||
|
- [#1606](https://github.com/influxdata/telegraf/pull/1606): Remove carraige returns from exec plugin output on Windows
|
||||||
|
- [#1674](https://github.com/influxdata/telegraf/issues/1674): elasticsearch input: configurable timeout.
|
||||||
|
- [#1607](https://github.com/influxdata/telegraf/pull/1607): Massage metric names in Instrumental output plugin
|
||||||
|
- [#1572](https://github.com/influxdata/telegraf/pull/1572): mesos improvements.
|
||||||
|
- [#1513](https://github.com/influxdata/telegraf/issues/1513): Add Ceph Cluster Performance Statistics
|
||||||
|
- [#1650](https://github.com/influxdata/telegraf/issues/1650): Ability to configure response_timeout in httpjson input.
|
||||||
|
- [#1685](https://github.com/influxdata/telegraf/issues/1685): Add additional redis metrics.
|
||||||
|
- [#1539](https://github.com/influxdata/telegraf/pull/1539): Added capability to send metrics through Http API for OpenTSDB.
|
||||||
|
- [#1471](https://github.com/influxdata/telegraf/pull/1471): iptables input plugin.
|
||||||
|
- [#1542](https://github.com/influxdata/telegraf/pull/1542): Add filestack webhook plugin.
|
||||||
|
- [#1599](https://github.com/influxdata/telegraf/pull/1599): Add server hostname for each docker measurements.
|
||||||
|
- [#1697](https://github.com/influxdata/telegraf/pull/1697): Add NATS output plugin.
|
||||||
|
- [#1407](https://github.com/influxdata/telegraf/pull/1407): HTTP service listener input plugin.
|
||||||
|
- [#1699](https://github.com/influxdata/telegraf/pull/1699): Add database blacklist option for Postgresql
|
||||||
|
|
||||||
|
### Bugfixes
|
||||||
|
|
||||||
|
- [#1628](https://github.com/influxdata/telegraf/issues/1628): Fix mongodb input panic on version 2.2.
|
||||||
|
- [#1738](https://github.com/influxdata/telegraf/issues/1738): Fix unmarshal of influxdb metrics with null tags
|
||||||
|
- [#1733](https://github.com/influxdata/telegraf/issues/1733): Fix statsd scientific notation parsing
|
||||||
|
- [#1716](https://github.com/influxdata/telegraf/issues/1716): Sensors plugin strconv.ParseFloat: parsing "": invalid syntax
|
||||||
|
- [#1530](https://github.com/influxdata/telegraf/issues/1530): Fix prometheus_client reload panic
|
||||||
|
|
||||||
|
## v1.0 [2016-09-08]
|
||||||
|
|
||||||
|
### Release Notes
|
||||||
|
|
||||||
|
**Breaking Change** The SNMP plugin is being deprecated in it's current form.
|
||||||
|
There is a [new SNMP plugin](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/snmp)
|
||||||
|
which fixes many of the issues and confusions
|
||||||
|
of its predecessor. For users wanting to continue to use the deprecated SNMP
|
||||||
|
plugin, you will need to change your config file from `[[inputs.snmp]]` to
|
||||||
|
`[[inputs.snmp_legacy]]`. The configuration of the new SNMP plugin is _not_
|
||||||
|
backwards-compatible.
|
||||||
|
|
||||||
|
**Breaking Change**: Aerospike main server node measurements have been renamed
|
||||||
|
aerospike_node. Aerospike namespace measurements have been renamed to
|
||||||
|
aerospike_namespace. They will also now be tagged with the node_name
|
||||||
|
that they correspond to. This has been done to differentiate measurements
|
||||||
|
that pertain to node vs. namespace statistics.
|
||||||
|
|
||||||
|
**Breaking Change**: users of github_webhooks must change to the new
|
||||||
|
`[[inputs.webhooks]]` plugin.
|
||||||
|
|
||||||
|
This means that the default github_webhooks config:
|
||||||
|
|
||||||
|
```
|
||||||
|
# A Github Webhook Event collector
|
||||||
|
[[inputs.github_webhooks]]
|
||||||
|
## Address and port to host Webhook listener on
|
||||||
|
service_address = ":1618"
|
||||||
|
```
|
||||||
|
|
||||||
|
should now look like:
|
||||||
|
|
||||||
|
```
|
||||||
|
# A Webhooks Event collector
|
||||||
|
[[inputs.webhooks]]
|
||||||
|
## Address and port to host Webhook listener on
|
||||||
|
service_address = ":1618"
|
||||||
|
|
||||||
|
[inputs.webhooks.github]
|
||||||
|
path = "/"
|
||||||
|
```
|
||||||
|
|
||||||
|
- Telegraf now supports being installed as an official windows service,
|
||||||
|
which can be installed via
|
||||||
|
`> C:\Program Files\Telegraf\telegraf.exe --service install`
|
||||||
|
|
||||||
|
- `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`.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- [#1413](https://github.com/influxdata/telegraf/issues/1413): Separate container_version from container_image tag.
|
||||||
|
- [#1525](https://github.com/influxdata/telegraf/pull/1525): Support setting per-device and total metrics for Docker network and blockio.
|
||||||
|
- [#1466](https://github.com/influxdata/telegraf/pull/1466): MongoDB input plugin: adding per DB stats from db.stats()
|
||||||
|
- [#1503](https://github.com/influxdata/telegraf/pull/1503): Add tls support for certs to RabbitMQ input plugin
|
||||||
|
- [#1289](https://github.com/influxdata/telegraf/pull/1289): webhooks input plugin. Thanks @francois2metz and @cduez!
|
||||||
|
- [#1247](https://github.com/influxdata/telegraf/pull/1247): rollbar webhook plugin.
|
||||||
|
- [#1408](https://github.com/influxdata/telegraf/pull/1408): mandrill webhook plugin.
|
||||||
|
- [#1402](https://github.com/influxdata/telegraf/pull/1402): docker-machine/boot2docker no longer required for unit tests.
|
||||||
|
- [#1350](https://github.com/influxdata/telegraf/pull/1350): cgroup input plugin.
|
||||||
|
- [#1369](https://github.com/influxdata/telegraf/pull/1369): Add input plugin for consuming metrics from NSQD.
|
||||||
|
- [#1369](https://github.com/influxdata/telegraf/pull/1480): add ability to read redis from a socket.
|
||||||
|
- [#1387](https://github.com/influxdata/telegraf/pull/1387): **Breaking Change** - Redis `role` tag renamed to `replication_role` to avoid global_tags override
|
||||||
|
- [#1437](https://github.com/influxdata/telegraf/pull/1437): Fetching Galera status metrics in MySQL
|
||||||
|
- [#1500](https://github.com/influxdata/telegraf/pull/1500): Aerospike plugin refactored to use official client lib.
|
||||||
|
- [#1434](https://github.com/influxdata/telegraf/pull/1434): Add measurement name arg to logparser plugin.
|
||||||
|
- [#1479](https://github.com/influxdata/telegraf/pull/1479): logparser: change resp_code from a field to a tag.
|
||||||
|
- [#1411](https://github.com/influxdata/telegraf/pull/1411): Implement support for fetching hddtemp data
|
||||||
|
- [#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.
|
||||||
|
- [#1397](https://github.com/influxdata/telegraf/issues/1397): ElasticSearch: now supports connecting to ElasticSearch via SSL
|
||||||
|
- [#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!
|
||||||
|
- [#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.
|
||||||
|
- [#1543](https://github.com/influxdata/telegraf/pull/1543): Official Windows service.
|
||||||
|
- [#1414](https://github.com/influxdata/telegraf/pull/1414): Forking sensors command to remove C package dependency.
|
||||||
|
- [#1389](https://github.com/influxdata/telegraf/pull/1389): Add a new SNMP plugin.
|
||||||
|
|
||||||
|
### Bugfixes
|
||||||
|
|
||||||
|
- [#1619](https://github.com/influxdata/telegraf/issues/1619): Fix `make windows` build target
|
||||||
|
- [#1519](https://github.com/influxdata/telegraf/pull/1519): Fix error race conditions and partial failures.
|
||||||
|
- [#1477](https://github.com/influxdata/telegraf/issues/1477): nstat: fix inaccurate config panic.
|
||||||
|
- [#1481](https://github.com/influxdata/telegraf/issues/1481): jolokia: fix handling multiple multi-dimensional attributes.
|
||||||
|
- [#1430](https://github.com/influxdata/telegraf/issues/1430): Fix prometheus character sanitizing. Sanitize more win_perf_counters characters.
|
||||||
|
- [#1534](https://github.com/influxdata/telegraf/pull/1534): Add diskio io_time to FreeBSD & report timing metrics as ms (as linux does).
|
||||||
|
- [#1379](https://github.com/influxdata/telegraf/issues/1379): Fix covering Amazon Linux for post remove flow.
|
||||||
|
- [#1584](https://github.com/influxdata/telegraf/issues/1584): procstat missing fields: read/write bytes & count
|
||||||
|
- [#1472](https://github.com/influxdata/telegraf/pull/1472): diskio input plugin: set 'skip_serial_number = true' by default to avoid high cardinality.
|
||||||
|
- [#1426](https://github.com/influxdata/telegraf/pull/1426): nil metrics panic fix.
|
||||||
|
- [#1384](https://github.com/influxdata/telegraf/pull/1384): Fix datarace in apache input plugin.
|
||||||
|
- [#1399](https://github.com/influxdata/telegraf/issues/1399): Add `read_repairs` statistics to riak plugin.
|
||||||
|
- [#1405](https://github.com/influxdata/telegraf/issues/1405): Fix memory/connection leak in prometheus input plugin.
|
||||||
|
- [#1378](https://github.com/influxdata/telegraf/issues/1378): Trim BOM from config file for Windows support.
|
||||||
|
- [#1339](https://github.com/influxdata/telegraf/issues/1339): Prometheus client output panic on service reload.
|
||||||
|
- [#1461](https://github.com/influxdata/telegraf/pull/1461): Prometheus parser, protobuf format header fix.
|
||||||
|
- [#1334](https://github.com/influxdata/telegraf/issues/1334): Prometheus output, metric refresh and caching fixes.
|
||||||
|
- [#1432](https://github.com/influxdata/telegraf/issues/1432): Panic fix for multiple graphite outputs under very high load.
|
||||||
|
- [#1412](https://github.com/influxdata/telegraf/pull/1412): Instrumental output has better reconnect behavior
|
||||||
|
- [#1460](https://github.com/influxdata/telegraf/issues/1460): Remove PID from procstat plugin to fix cardinality issues.
|
||||||
|
- [#1427](https://github.com/influxdata/telegraf/issues/1427): Cassandra input: version 2.x "column family" fix.
|
||||||
|
- [#1463](https://github.com/influxdata/telegraf/issues/1463): Shared WaitGroup in Exec plugin
|
||||||
|
- [#1436](https://github.com/influxdata/telegraf/issues/1436): logparser: honor modifiers in "pattern" config.
|
||||||
|
- [#1418](https://github.com/influxdata/telegraf/issues/1418): logparser: error and exit on file permissions/missing errors.
|
||||||
|
- [#1499](https://github.com/influxdata/telegraf/pull/1499): Make the user able to specify full path for HAproxy stats
|
||||||
|
- [#1521](https://github.com/influxdata/telegraf/pull/1521): Fix Redis url, an extra "tcp://" was added.
|
||||||
|
- [#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.
|
||||||
|
- [#1396](https://github.com/influxdata/telegraf/pull/1396): Prometheus input plugin now supports x509 certs authentication
|
||||||
|
- [#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.
|
||||||
|
- [#1586](https://github.com/influxdata/telegraf/pull/1586): Remove IF NOT EXISTS from influxdb output database creation.
|
||||||
|
- [#1600](https://github.com/influxdata/telegraf/issues/1600): Fix quoting with text values in postgresql_extensible plugin.
|
||||||
|
- [#1425](https://github.com/influxdata/telegraf/issues/1425): Fix win_perf_counter "index out of range" panic.
|
||||||
|
- [#1634](https://github.com/influxdata/telegraf/issues/1634): Fix ntpq panic when field is missing.
|
||||||
|
- [#1637](https://github.com/influxdata/telegraf/issues/1637): Sanitize graphite output field names.
|
||||||
|
- [#1695](https://github.com/influxdata/telegraf/pull/1695): Fix MySQL plugin not sending 0 value fields.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
- **Breaking change** in jolokia plugin. See
|
||||||
|
https://github.com/influxdata/telegraf/blob/master/plugins/inputs/jolokia/README.md
|
||||||
|
for updated configuration. The plugin will now support proxy mode and will make
|
||||||
|
POST requests.
|
||||||
|
|
||||||
|
- New [agent] configuration option: `metric_batch_size`. This option tells
|
||||||
|
telegraf the maximum batch size to allow to accumulate before sending a flush
|
||||||
|
to the configured outputs. `metric_buffer_limit` now refers to the absolute
|
||||||
|
maximum number of metrics that will accumulate before metrics are dropped.
|
||||||
|
|
||||||
|
- There is no longer an option to
|
||||||
|
`flush_buffer_when_full`, this is now the default and only behavior of telegraf.
|
||||||
|
|
||||||
|
- **Breaking Change**: docker plugin tags. The cont_id tag no longer exists, it
|
||||||
|
will now be a field, and be called container_id. Additionally, cont_image and
|
||||||
|
cont_name are being renamed to container_image and container_name.
|
||||||
|
|
||||||
|
- **Breaking Change**: docker plugin measurements. The `docker_cpu`, `docker_mem`,
|
||||||
|
`docker_blkio` and `docker_net` measurements are being renamed to
|
||||||
|
`docker_container_cpu`, `docker_container_mem`, `docker_container_blkio` and
|
||||||
|
`docker_container_net`. Why? Because these metrics are
|
||||||
|
specifically tracking per-container stats. The problem with per-container stats,
|
||||||
|
in some use-cases, is that if containers are short-lived AND names are not
|
||||||
|
kept consistent, then the series cardinality will balloon very quickly.
|
||||||
|
So adding "container" to each metric will:
|
||||||
|
(1) make it more clear that these metrics are per-container, and
|
||||||
|
(2) allow users to easily drop per-container metrics if cardinality is an
|
||||||
|
issue (`namedrop = ["docker_container_*"]`)
|
||||||
|
|
||||||
|
- `tagexclude` and `taginclude` are now available, which can be used to remove
|
||||||
|
tags from measurements on inputs and outputs. See
|
||||||
|
[the configuration doc](https://github.com/influxdata/telegraf/blob/master/docs/CONFIGURATION.md)
|
||||||
|
for more details.
|
||||||
|
|
||||||
|
- **Measurement filtering:** All measurement filters now match based on glob
|
||||||
|
only. Previously there was an undocumented behavior where filters would match
|
||||||
|
based on _prefix_ in addition to globs. This means that a filter like
|
||||||
|
`fielddrop = ["time_"]` will need to be changed to `fielddrop = ["time_*"]`
|
||||||
|
|
||||||
|
- **datadog**: measurement and field names will no longer have `_` replaced by `.`
|
||||||
|
|
||||||
|
- The following plugins have changed their tags to _not_ overwrite the host tag:
|
||||||
|
- cassandra: `host -> cassandra_host`
|
||||||
|
- disque: `host -> disque_host`
|
||||||
|
- rethinkdb: `host -> rethinkdb_host`
|
||||||
|
|
||||||
|
- **Breaking Change**: The `win_perf_counters` input has been changed to
|
||||||
|
sanitize field names, replacing `/Sec` and `/sec` with `_persec`, as well as
|
||||||
|
spaces with underscores. This is needed because Graphite doesn't like slashes
|
||||||
|
and spaces, and was failing to accept metrics that had them.
|
||||||
|
The `/[sS]ec` -> `_persec` is just to make things clearer and uniform.
|
||||||
|
|
||||||
|
- **Breaking Change**: snmp plugin. The `host` tag of the snmp plugin has been
|
||||||
|
changed to the `snmp_host` tag.
|
||||||
|
|
||||||
|
- The `disk` input plugin can now be configured with the `HOST_MOUNT_PREFIX` environment variable.
|
||||||
|
This value is prepended to any mountpaths discovered before retrieving stats.
|
||||||
|
It is not included on the report path. This is necessary for reporting host disk stats when running from within a container.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- [#1031](https://github.com/influxdata/telegraf/pull/1031): Jolokia plugin proxy mode. Thanks @saiello!
|
||||||
|
- [#1017](https://github.com/influxdata/telegraf/pull/1017): taginclude and tagexclude arguments.
|
||||||
|
- [#1015](https://github.com/influxdata/telegraf/pull/1015): Docker plugin schema refactor.
|
||||||
|
- [#889](https://github.com/influxdata/telegraf/pull/889): Improved MySQL plugin. Thanks @maksadbek!
|
||||||
|
- [#1060](https://github.com/influxdata/telegraf/pull/1060): TTL metrics added to MongoDB input plugin
|
||||||
|
- [#1056](https://github.com/influxdata/telegraf/pull/1056): Don't allow inputs to overwrite host tags.
|
||||||
|
- [#1035](https://github.com/influxdata/telegraf/issues/1035): Add `user`, `exe`, `pidfile` tags to procstat plugin.
|
||||||
|
- [#1041](https://github.com/influxdata/telegraf/issues/1041): Add `n_cpus` field to the system plugin.
|
||||||
|
- [#1072](https://github.com/influxdata/telegraf/pull/1072): New Input Plugin: filestat.
|
||||||
|
- [#1066](https://github.com/influxdata/telegraf/pull/1066): Replication lag metrics for MongoDB input plugin
|
||||||
|
- [#1086](https://github.com/influxdata/telegraf/pull/1086): Ability to specify AWS keys in config file. Thanks @johnrengleman!
|
||||||
|
- [#1096](https://github.com/influxdata/telegraf/pull/1096): Performance refactor of running output buffers.
|
||||||
|
- [#967](https://github.com/influxdata/telegraf/issues/967): Buffer logging improvements.
|
||||||
|
- [#1107](https://github.com/influxdata/telegraf/issues/1107): Support lustre2 job stats. Thanks @hanleyja!
|
||||||
|
- [#1122](https://github.com/influxdata/telegraf/pull/1122): Support setting config path through env variable and default paths.
|
||||||
|
- [#1128](https://github.com/influxdata/telegraf/pull/1128): MongoDB jumbo chunks metric for MongoDB input plugin
|
||||||
|
- [#1146](https://github.com/influxdata/telegraf/pull/1146): HAProxy socket support. Thanks weshmashian!
|
||||||
|
|
||||||
|
### Bugfixes
|
||||||
|
|
||||||
|
- [#1050](https://github.com/influxdata/telegraf/issues/1050): jolokia plugin - do not overwrite host tag. Thanks @saiello!
|
||||||
|
- [#921](https://github.com/influxdata/telegraf/pull/921): mqtt_consumer stops gathering metrics. Thanks @chaton78!
|
||||||
|
- [#1013](https://github.com/influxdata/telegraf/pull/1013): Close dead riemann output connections. Thanks @echupriyanov!
|
||||||
|
- [#1012](https://github.com/influxdata/telegraf/pull/1012): Set default tags in test accumulator.
|
||||||
|
- [#1024](https://github.com/influxdata/telegraf/issues/1024): Don't replace `.` with `_` in datadog output.
|
||||||
|
- [#1058](https://github.com/influxdata/telegraf/issues/1058): Fix possible leaky TCP connections in influxdb output.
|
||||||
|
- [#1044](https://github.com/influxdata/telegraf/pull/1044): Fix SNMP OID possible collisions. Thanks @relip
|
||||||
|
- [#1022](https://github.com/influxdata/telegraf/issues/1022): Dont error deb/rpm install on systemd errors.
|
||||||
|
- [#1078](https://github.com/influxdata/telegraf/issues/1078): Use default AWS credential chain.
|
||||||
|
- [#1070](https://github.com/influxdata/telegraf/issues/1070): SQL Server input. Fix datatype conversion.
|
||||||
|
- [#1089](https://github.com/influxdata/telegraf/issues/1089): Fix leaky TCP connections in phpfpm plugin.
|
||||||
|
- [#914](https://github.com/influxdata/telegraf/issues/914): Telegraf can drop metrics on full buffers.
|
||||||
|
- [#1098](https://github.com/influxdata/telegraf/issues/1098): Sanitize invalid OpenTSDB characters.
|
||||||
|
- [#1110](https://github.com/influxdata/telegraf/pull/1110): Sanitize * to - in graphite serializer. Thanks @goodeggs!
|
||||||
|
- [#1118](https://github.com/influxdata/telegraf/pull/1118): Sanitize Counter names for `win_perf_counters` input.
|
||||||
|
- [#1125](https://github.com/influxdata/telegraf/pull/1125): Wrap all exec command runners with a timeout, so hung os processes don't halt Telegraf.
|
||||||
|
- [#1113](https://github.com/influxdata/telegraf/pull/1113): Set MaxRetry and RequiredAcks defaults in Kafka output.
|
||||||
|
- [#1090](https://github.com/influxdata/telegraf/issues/1090): [agent] and [global_tags] config sometimes not getting applied.
|
||||||
|
- [#1133](https://github.com/influxdata/telegraf/issues/1133): Use a timeout for docker list & stat cmds.
|
||||||
|
- [#1052](https://github.com/influxdata/telegraf/issues/1052): Docker panic fix when decode fails.
|
||||||
|
- [#1136](https://github.com/influxdata/telegraf/pull/1136): "DELAYED" Inserts were deprecated in MySQL 5.6.6. Thanks @PierreF
|
||||||
|
|
||||||
## v0.12.1 [2016-04-14]
|
## v0.12.1 [2016-04-14]
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ Output plugins READMEs are less structured,
|
|||||||
but any information you can provide on how the data will look is appreciated.
|
but any information you can provide on how the data will look is appreciated.
|
||||||
See the [OpenTSDB output](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/opentsdb)
|
See the [OpenTSDB output](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/opentsdb)
|
||||||
for a good example.
|
for a good example.
|
||||||
|
1. **Optional:** Help users of your plugin by including example queries for populating dashboards. Include these sample queries in the `README.md` for the plugin.
|
||||||
|
1. **Optional:** Write a [tickscript](https://docs.influxdata.com/kapacitor/v1.0/tick/syntax/) for your plugin and add it to [Kapacitor](https://github.com/influxdata/kapacitor/tree/master/examples/telegraf). Or mention @jackzampolin in a PR comment with some common queries that you would want to alert on and he will write one for you.
|
||||||
|
|
||||||
## GoDoc
|
## GoDoc
|
||||||
|
|
||||||
@@ -30,7 +32,7 @@ Assuming you can already build the project, run these in the telegraf directory:
|
|||||||
|
|
||||||
1. `go get github.com/sparrc/gdm`
|
1. `go get github.com/sparrc/gdm`
|
||||||
1. `gdm restore`
|
1. `gdm restore`
|
||||||
1. `gdm save`
|
1. `GOOS=linux gdm save`
|
||||||
|
|
||||||
## Input Plugins
|
## Input Plugins
|
||||||
|
|
||||||
@@ -82,9 +84,9 @@ func (s *Simple) SampleConfig() string {
|
|||||||
|
|
||||||
func (s *Simple) Gather(acc telegraf.Accumulator) error {
|
func (s *Simple) Gather(acc telegraf.Accumulator) error {
|
||||||
if s.Ok {
|
if s.Ok {
|
||||||
acc.Add("state", "pretty good", nil)
|
acc.AddFields("state", map[string]interface{}{"value": "pretty good"}, nil)
|
||||||
} else {
|
} else {
|
||||||
acc.Add("state", "not great", nil)
|
acc.AddFields("state", map[string]interface{}{"value": "not great"}, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -95,6 +97,13 @@ func init() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Adding Typed Metrics
|
||||||
|
|
||||||
|
In addition the the `AddFields` function, the accumulator also supports an
|
||||||
|
`AddGauge` and `AddCounter` function. These functions are for adding _typed_
|
||||||
|
metrics. Metric types are ignored for the InfluxDB output, but can be used
|
||||||
|
for other outputs, such as [prometheus](https://prometheus.io/docs/concepts/metric_types/).
|
||||||
|
|
||||||
## Input Plugins Accepting Arbitrary Data Formats
|
## Input Plugins Accepting Arbitrary Data Formats
|
||||||
|
|
||||||
Some input plugins (such as
|
Some input plugins (such as
|
||||||
@@ -212,8 +221,8 @@ func (s *Simple) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Simple) Write(metrics []telegraf.Metric) error {
|
func (s *Simple) Write(metrics []telegraf.Metric) error {
|
||||||
for _, pt := range points {
|
for _, metric := range metrics {
|
||||||
// write `pt` to the output sink here
|
// write `metric` to the output sink here
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -290,10 +299,6 @@ To execute Telegraf tests follow these simple steps:
|
|||||||
instructions
|
instructions
|
||||||
- execute `make test`
|
- execute `make test`
|
||||||
|
|
||||||
**OSX users**: you will need to install `boot2docker` or `docker-machine`.
|
|
||||||
The Makefile will assume that you have a `docker-machine` box called `default` to
|
|
||||||
get the IP address.
|
|
||||||
|
|
||||||
### Unit test troubleshooting
|
### Unit test troubleshooting
|
||||||
|
|
||||||
Try cleaning up your test environment by executing `make docker-kill` and
|
Try cleaning up your test environment by executing `make docker-kill` and
|
||||||
|
|||||||
18
Godeps
18
Godeps
@@ -1,5 +1,6 @@
|
|||||||
github.com/Shopify/sarama 8aadb476e66ca998f2f6bb3c993e9a2daa3666b9
|
github.com/Shopify/sarama 8aadb476e66ca998f2f6bb3c993e9a2daa3666b9
|
||||||
github.com/Sirupsen/logrus 219c8cb75c258c552e999735be6df753ffc7afdc
|
github.com/Sirupsen/logrus 219c8cb75c258c552e999735be6df753ffc7afdc
|
||||||
|
github.com/aerospike/aerospike-client-go 7f3a312c3b2a60ac083ec6da296091c52c795c63
|
||||||
github.com/amir/raidman 53c1b967405155bfc8758557863bf2e14f814687
|
github.com/amir/raidman 53c1b967405155bfc8758557863bf2e14f814687
|
||||||
github.com/aws/aws-sdk-go 13a12060f716145019378a10e2806c174356b857
|
github.com/aws/aws-sdk-go 13a12060f716145019378a10e2806c174356b857
|
||||||
github.com/beorn7/perks 3ac7bf7a47d159a033b107610db8a1b6575507a4
|
github.com/beorn7/perks 3ac7bf7a47d159a033b107610db8a1b6575507a4
|
||||||
@@ -16,23 +17,28 @@ github.com/eapache/go-resiliency b86b1ec0dd4209a588dc1285cdd471e73525c0b3
|
|||||||
github.com/eapache/queue ded5959c0d4e360646dc9e9908cff48666781367
|
github.com/eapache/queue ded5959c0d4e360646dc9e9908cff48666781367
|
||||||
github.com/eclipse/paho.mqtt.golang 0f7a459f04f13a41b7ed752d47944528d4bf9a86
|
github.com/eclipse/paho.mqtt.golang 0f7a459f04f13a41b7ed752d47944528d4bf9a86
|
||||||
github.com/go-sql-driver/mysql 1fca743146605a172a266e1654e01e5cd5669bee
|
github.com/go-sql-driver/mysql 1fca743146605a172a266e1654e01e5cd5669bee
|
||||||
|
github.com/gobwas/glob 49571a1557cd20e6a2410adc6421f85b66c730b5
|
||||||
github.com/golang/protobuf 552c7b9542c194800fd493123b3798ef0a832032
|
github.com/golang/protobuf 552c7b9542c194800fd493123b3798ef0a832032
|
||||||
github.com/golang/snappy 427fb6fc07997f43afa32f35e850833760e489a7
|
github.com/golang/snappy 427fb6fc07997f43afa32f35e850833760e489a7
|
||||||
github.com/gonuts/go-shellquote e842a11b24c6abfb3dd27af69a17f482e4b483c2
|
github.com/gonuts/go-shellquote e842a11b24c6abfb3dd27af69a17f482e4b483c2
|
||||||
github.com/gorilla/context 1ea25387ff6f684839d82767c1733ff4d4d15d0a
|
github.com/gorilla/context 1ea25387ff6f684839d82767c1733ff4d4d15d0a
|
||||||
github.com/gorilla/mux c9e326e2bdec29039a3761c07bece13133863e1e
|
github.com/gorilla/mux c9e326e2bdec29039a3761c07bece13133863e1e
|
||||||
github.com/hailocab/go-hostpool e80d13ce29ede4452c43dea11e79b9bc8a15b478
|
github.com/hailocab/go-hostpool e80d13ce29ede4452c43dea11e79b9bc8a15b478
|
||||||
|
github.com/hashicorp/consul 5aa90455ce78d4d41578bafc86305e6e6b28d7d2
|
||||||
|
github.com/hpcloud/tail b2940955ab8b26e19d43a43c4da0475dd81bdb56
|
||||||
github.com/influxdata/config b79f6829346b8d6e78ba73544b1e1038f1f1c9da
|
github.com/influxdata/config b79f6829346b8d6e78ba73544b1e1038f1f1c9da
|
||||||
github.com/influxdata/influxdb e3fef5593c21644f2b43af55d6e17e70910b0e48
|
github.com/influxdata/influxdb e094138084855d444195b252314dfee9eae34cab
|
||||||
github.com/influxdata/toml af4df43894b16e3fd2b788d01bd27ad0776ef2d0
|
github.com/influxdata/toml af4df43894b16e3fd2b788d01bd27ad0776ef2d0
|
||||||
|
github.com/kardianos/osext 29ae4ffbc9a6fe9fb2bc5029050ce6996ea1d3bc
|
||||||
|
github.com/kardianos/service 5e335590050d6d00f3aa270217d288dda1c94d0a
|
||||||
github.com/klauspost/crc32 19b0b332c9e4516a6370a0456e6182c3b5036720
|
github.com/klauspost/crc32 19b0b332c9e4516a6370a0456e6182c3b5036720
|
||||||
github.com/lib/pq e182dc4027e2ded4b19396d638610f2653295f36
|
github.com/lib/pq e182dc4027e2ded4b19396d638610f2653295f36
|
||||||
github.com/matttproud/golang_protobuf_extensions d0c3fe89de86839aecf2e0579c40ba3bb336a453
|
github.com/matttproud/golang_protobuf_extensions d0c3fe89de86839aecf2e0579c40ba3bb336a453
|
||||||
github.com/miekg/dns cce6c130cdb92c752850880fd285bea1d64439dd
|
github.com/miekg/dns cce6c130cdb92c752850880fd285bea1d64439dd
|
||||||
github.com/mreiferson/go-snappystream 028eae7ab5c4c9e2d1cb4c4ca1e53259bbe7e504
|
github.com/mreiferson/go-snappystream 028eae7ab5c4c9e2d1cb4c4ca1e53259bbe7e504
|
||||||
github.com/naoina/go-stringutil 6b638e95a32d0c1131db0e7fe83775cbea4a0d0b
|
github.com/naoina/go-stringutil 6b638e95a32d0c1131db0e7fe83775cbea4a0d0b
|
||||||
github.com/nats-io/nats b13fc9d12b0b123ebc374e6b808c6228ae4234a3
|
github.com/nats-io/nats ea8b4fd12ebb823073c0004b9f09ac8748f4f165
|
||||||
github.com/nats-io/nuid 4f84f5f3b2786224e336af2e13dba0a0a80b76fa
|
github.com/nats-io/nuid a5152d67cf63cbfb5d992a395458722a45194715
|
||||||
github.com/nsqio/go-nsq 0b80d6f05e15ca1930e0c5e1d540ed627e299980
|
github.com/nsqio/go-nsq 0b80d6f05e15ca1930e0c5e1d540ed627e299980
|
||||||
github.com/opencontainers/runc 89ab7f2ccc1e45ddf6485eaa802c35dcf321dfc8
|
github.com/opencontainers/runc 89ab7f2ccc1e45ddf6485eaa802c35dcf321dfc8
|
||||||
github.com/prometheus/client_golang 18acf9993a863f4c4b40612e19cdd243e7c86831
|
github.com/prometheus/client_golang 18acf9993a863f4c4b40612e19cdd243e7c86831
|
||||||
@@ -40,12 +46,14 @@ github.com/prometheus/client_model fa8ad6fec33561be4280a8f0514318c79d7f6cb6
|
|||||||
github.com/prometheus/common e8eabff8812b05acf522b45fdcd725a785188e37
|
github.com/prometheus/common e8eabff8812b05acf522b45fdcd725a785188e37
|
||||||
github.com/prometheus/procfs 406e5b7bfd8201a36e2bb5f7bdae0b03380c2ce8
|
github.com/prometheus/procfs 406e5b7bfd8201a36e2bb5f7bdae0b03380c2ce8
|
||||||
github.com/samuel/go-zookeeper 218e9c81c0dd8b3b18172b2bbfad92cc7d6db55f
|
github.com/samuel/go-zookeeper 218e9c81c0dd8b3b18172b2bbfad92cc7d6db55f
|
||||||
github.com/shirou/gopsutil 1f32ce1bb380845be7f5d174ac641a2c592c0c42
|
github.com/shirou/gopsutil 4d0c402af66c78735c5ccf820dc2ca7de5e4ff08
|
||||||
github.com/soniah/gosnmp b1b4f885b12c5dcbd021c5cee1c904110de6db7d
|
github.com/soniah/gosnmp eb32571c2410868d85849ad67d1e51d01273eb84
|
||||||
github.com/streadway/amqp b4f3ceab0337f013208d31348b578d83c0064744
|
github.com/streadway/amqp b4f3ceab0337f013208d31348b578d83c0064744
|
||||||
github.com/stretchr/testify 1f4a1643a57e798696635ea4c126e9127adb7d3c
|
github.com/stretchr/testify 1f4a1643a57e798696635ea4c126e9127adb7d3c
|
||||||
|
github.com/vjeantet/grok 83bfdfdfd1a8146795b28e547a8e3c8b28a466c2
|
||||||
github.com/wvanbergen/kafka 46f9a1cf3f670edec492029fadded9c2d9e18866
|
github.com/wvanbergen/kafka 46f9a1cf3f670edec492029fadded9c2d9e18866
|
||||||
github.com/wvanbergen/kazoo-go 0f768712ae6f76454f987c3356177e138df258f8
|
github.com/wvanbergen/kazoo-go 0f768712ae6f76454f987c3356177e138df258f8
|
||||||
|
github.com/yuin/gopher-lua bf3808abd44b1e55143a2d7f08571aaa80db1808
|
||||||
github.com/zensqlmonitor/go-mssqldb ffe5510c6fa5e15e6d983210ab501c815b56b363
|
github.com/zensqlmonitor/go-mssqldb ffe5510c6fa5e15e6d983210ab501c815b56b363
|
||||||
golang.org/x/crypto 5dc8cb4b8a8eb076cbb5a06bc3b8682c15bdbbd3
|
golang.org/x/crypto 5dc8cb4b8a8eb076cbb5a06bc3b8682c15bdbbd3
|
||||||
golang.org/x/net 6acef71eb69611914f7a30939ea9f6e194c78172
|
golang.org/x/net 6acef71eb69611914f7a30939ea9f6e194c78172
|
||||||
|
|||||||
@@ -1,59 +1,12 @@
|
|||||||
github.com/Microsoft/go-winio 9f57cbbcbcb41dea496528872a4f0e37a4f7ae98
|
github.com/Microsoft/go-winio ce2922f643c8fd76b46cadc7f404a06282678b34
|
||||||
github.com/Shopify/sarama 8aadb476e66ca998f2f6bb3c993e9a2daa3666b9
|
|
||||||
github.com/Sirupsen/logrus 219c8cb75c258c552e999735be6df753ffc7afdc
|
|
||||||
github.com/StackExchange/wmi f3e2bae1e0cb5aef83e319133eabfee30013a4a5
|
github.com/StackExchange/wmi f3e2bae1e0cb5aef83e319133eabfee30013a4a5
|
||||||
github.com/amir/raidman 53c1b967405155bfc8758557863bf2e14f814687
|
github.com/go-ole/go-ole be49f7c07711fcb603cff39e1de7c67926dc0ba7
|
||||||
github.com/aws/aws-sdk-go 13a12060f716145019378a10e2806c174356b857
|
github.com/lxn/win 950a0e81e7678e63d8e6cd32412bdecb325ccd88
|
||||||
github.com/beorn7/perks 3ac7bf7a47d159a033b107610db8a1b6575507a4
|
github.com/shirou/w32 3c9377fc6748f222729a8270fe2775d149a249ad
|
||||||
github.com/cenkalti/backoff 4dc77674aceaabba2c7e3da25d4c823edfb73f99
|
golang.org/x/sys a646d33e2ee3172a661fc09bca23bb4889a41bc8
|
||||||
github.com/couchbase/go-couchbase cb664315a324d87d19c879d9cc67fda6be8c2ac1
|
github.com/go-ini/ini 9144852efba7c4daf409943ee90767da62d55438
|
||||||
github.com/couchbase/gomemcached a5ea6356f648fec6ab89add00edd09151455b4b2
|
github.com/jmespath/go-jmespath bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d
|
||||||
github.com/couchbase/goutils 5823a0cbaaa9008406021dc5daf80125ea30bba6
|
github.com/pmezard/go-difflib/difflib 792786c7400a136282c1664665ae0a8db921c6c2
|
||||||
github.com/dancannon/gorethink e7cac92ea2bc52638791a021f212145acfedb1fc
|
github.com/stretchr/objx 1a9d0bb9f541897e62256577b352fdbc1fb4fd94
|
||||||
github.com/davecgh/go-spew 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
|
gopkg.in/fsnotify.v1 a8a77c9133d2d6fd8334f3260d06f60e8d80a5fb
|
||||||
github.com/docker/engine-api 8924d6900370b4c7e7984be5adc61f50a80d7537
|
gopkg.in/tomb.v1 dd632973f1e7218eb1089048e0798ec9ae7dceb8
|
||||||
github.com/docker/go-connections f549a9393d05688dff0992ef3efd8bbe6c628aeb
|
|
||||||
github.com/docker/go-units 5d2041e26a699eaca682e2ea41c8f891e1060444
|
|
||||||
github.com/eapache/go-resiliency b86b1ec0dd4209a588dc1285cdd471e73525c0b3
|
|
||||||
github.com/eapache/queue ded5959c0d4e360646dc9e9908cff48666781367
|
|
||||||
github.com/eclipse/paho.mqtt.golang 0f7a459f04f13a41b7ed752d47944528d4bf9a86
|
|
||||||
github.com/go-ole/go-ole 50055884d646dd9434f16bbb5c9801749b9bafe4
|
|
||||||
github.com/go-sql-driver/mysql 1fca743146605a172a266e1654e01e5cd5669bee
|
|
||||||
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/influxdata/config b79f6829346b8d6e78ba73544b1e1038f1f1c9da
|
|
||||||
github.com/influxdata/influxdb e3fef5593c21644f2b43af55d6e17e70910b0e48
|
|
||||||
github.com/influxdata/toml af4df43894b16e3fd2b788d01bd27ad0776ef2d0
|
|
||||||
github.com/klauspost/crc32 19b0b332c9e4516a6370a0456e6182c3b5036720
|
|
||||||
github.com/lib/pq e182dc4027e2ded4b19396d638610f2653295f36
|
|
||||||
github.com/lxn/win 9a7734ea4db26bc593d52f6a8a957afdad39c5c1
|
|
||||||
github.com/matttproud/golang_protobuf_extensions d0c3fe89de86839aecf2e0579c40ba3bb336a453
|
|
||||||
github.com/miekg/dns cce6c130cdb92c752850880fd285bea1d64439dd
|
|
||||||
github.com/mreiferson/go-snappystream 028eae7ab5c4c9e2d1cb4c4ca1e53259bbe7e504
|
|
||||||
github.com/naoina/go-stringutil 6b638e95a32d0c1131db0e7fe83775cbea4a0d0b
|
|
||||||
github.com/nats-io/nats b13fc9d12b0b123ebc374e6b808c6228ae4234a3
|
|
||||||
github.com/nats-io/nuid 4f84f5f3b2786224e336af2e13dba0a0a80b76fa
|
|
||||||
github.com/nsqio/go-nsq 0b80d6f05e15ca1930e0c5e1d540ed627e299980
|
|
||||||
github.com/prometheus/client_golang 18acf9993a863f4c4b40612e19cdd243e7c86831
|
|
||||||
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 1f32ce1bb380845be7f5d174ac641a2c592c0c42
|
|
||||||
github.com/shirou/w32 ada3ba68f000aa1b58580e45c9d308fe0b7fc5c5
|
|
||||||
github.com/soniah/gosnmp b1b4f885b12c5dcbd021c5cee1c904110de6db7d
|
|
||||||
github.com/streadway/amqp b4f3ceab0337f013208d31348b578d83c0064744
|
|
||||||
github.com/stretchr/testify 1f4a1643a57e798696635ea4c126e9127adb7d3c
|
|
||||||
github.com/wvanbergen/kafka 46f9a1cf3f670edec492029fadded9c2d9e18866
|
|
||||||
github.com/wvanbergen/kazoo-go 0f768712ae6f76454f987c3356177e138df258f8
|
|
||||||
github.com/zensqlmonitor/go-mssqldb ffe5510c6fa5e15e6d983210ab501c815b56b363
|
|
||||||
golang.org/x/net 6acef71eb69611914f7a30939ea9f6e194c78172
|
|
||||||
golang.org/x/text a71fd10341b064c10f4a81ceac72bcf70f26ea34
|
|
||||||
gopkg.in/dancannon/gorethink.v1 7d1af5be49cb5ecc7b177bf387d232050299d6ef
|
|
||||||
gopkg.in/fatih/pool.v2 cba550ebf9bce999a02e963296d4bc7a486cb715
|
|
||||||
gopkg.in/mgo.v2 d90005c5262a3463800497ea5a89aed5fe22c886
|
|
||||||
gopkg.in/yaml.v2 a83829b6f1293c91addabc89d0571c246397bbf4
|
|
||||||
|
|||||||
37
Makefile
37
Makefile
@@ -1,4 +1,3 @@
|
|||||||
UNAME := $(shell sh -c 'uname')
|
|
||||||
VERSION := $(shell sh -c 'git describe --always --tags')
|
VERSION := $(shell sh -c 'git describe --always --tags')
|
||||||
ifdef GOBIN
|
ifdef GOBIN
|
||||||
PATH := $(GOBIN):$(PATH)
|
PATH := $(GOBIN):$(PATH)
|
||||||
@@ -14,22 +13,18 @@ windows: prepare-windows build-windows
|
|||||||
|
|
||||||
# Only run the build (no dependency grabbing)
|
# Only run the build (no dependency grabbing)
|
||||||
build:
|
build:
|
||||||
go install -ldflags "-X main.Version=$(VERSION)" ./...
|
go install -ldflags "-X main.version=$(VERSION)" ./...
|
||||||
|
|
||||||
build-windows:
|
build-windows:
|
||||||
go build -o telegraf.exe -ldflags \
|
GOOS=windows GOARCH=amd64 go build -o telegraf.exe -ldflags \
|
||||||
"-X main.Version=$(VERSION)" \
|
"-X main.version=$(VERSION)" \
|
||||||
./cmd/telegraf/telegraf.go
|
./cmd/telegraf/telegraf.go
|
||||||
|
|
||||||
build-for-docker:
|
build-for-docker:
|
||||||
CGO_ENABLED=0 GOOS=linux go build -installsuffix cgo -o telegraf -ldflags \
|
CGO_ENABLED=0 GOOS=linux go build -installsuffix cgo -o telegraf -ldflags \
|
||||||
"-s -X main.Version=$(VERSION)" \
|
"-s -X main.version=$(VERSION)" \
|
||||||
./cmd/telegraf/telegraf.go
|
./cmd/telegraf/telegraf.go
|
||||||
|
|
||||||
# Build with race detector
|
|
||||||
dev: prepare
|
|
||||||
go build -race -ldflags "-X main.Version=$(VERSION)" ./...
|
|
||||||
|
|
||||||
# run package script
|
# run package script
|
||||||
package:
|
package:
|
||||||
./scripts/build.py --package --version="$(VERSION)" --platform=linux --arch=all --upload
|
./scripts/build.py --package --version="$(VERSION)" --platform=linux --arch=all --upload
|
||||||
@@ -42,54 +37,44 @@ prepare:
|
|||||||
# Use the windows godeps file to prepare dependencies
|
# Use the windows godeps file to prepare dependencies
|
||||||
prepare-windows:
|
prepare-windows:
|
||||||
go get github.com/sparrc/gdm
|
go get github.com/sparrc/gdm
|
||||||
|
gdm restore
|
||||||
gdm restore -f Godeps_windows
|
gdm restore -f Godeps_windows
|
||||||
|
|
||||||
# Run all docker containers necessary for unit tests
|
# Run all docker containers necessary for unit tests
|
||||||
docker-run:
|
docker-run:
|
||||||
ifeq ($(UNAME), Darwin)
|
docker run --name aerospike -p "3000:3000" -d aerospike/aerospike-server:3.9.0
|
||||||
docker run --name kafka \
|
|
||||||
-e ADVERTISED_HOST=$(shell sh -c 'boot2docker ip || docker-machine ip default') \
|
|
||||||
-e ADVERTISED_PORT=9092 \
|
|
||||||
-p "2181:2181" -p "9092:9092" \
|
|
||||||
-d spotify/kafka
|
|
||||||
endif
|
|
||||||
ifeq ($(UNAME), Linux)
|
|
||||||
docker run --name kafka \
|
docker run --name kafka \
|
||||||
-e ADVERTISED_HOST=localhost \
|
-e ADVERTISED_HOST=localhost \
|
||||||
-e ADVERTISED_PORT=9092 \
|
-e ADVERTISED_PORT=9092 \
|
||||||
-p "2181:2181" -p "9092:9092" \
|
-p "2181:2181" -p "9092:9092" \
|
||||||
-d spotify/kafka
|
-d spotify/kafka
|
||||||
endif
|
|
||||||
docker run --name mysql -p "3306:3306" -e MYSQL_ALLOW_EMPTY_PASSWORD=yes -d mysql
|
docker run --name mysql -p "3306:3306" -e MYSQL_ALLOW_EMPTY_PASSWORD=yes -d mysql
|
||||||
docker run --name memcached -p "11211:11211" -d memcached
|
docker run --name memcached -p "11211:11211" -d memcached
|
||||||
docker run --name postgres -p "5432:5432" -d postgres
|
docker run --name postgres -p "5432:5432" -d postgres
|
||||||
docker run --name rabbitmq -p "15672:15672" -p "5672:5672" -d rabbitmq:3-management
|
docker run --name rabbitmq -p "15672:15672" -p "5672:5672" -d rabbitmq:3-management
|
||||||
docker run --name opentsdb -p "4242:4242" -d petergrace/opentsdb-docker
|
|
||||||
docker run --name redis -p "6379:6379" -d redis
|
docker run --name redis -p "6379:6379" -d redis
|
||||||
docker run --name aerospike -p "3000:3000" -d aerospike
|
|
||||||
docker run --name nsq -p "4150:4150" -d nsqio/nsq /nsqd
|
docker run --name nsq -p "4150:4150" -d nsqio/nsq /nsqd
|
||||||
docker run --name mqtt -p "1883:1883" -d ncarlier/mqtt
|
docker run --name mqtt -p "1883:1883" -d ncarlier/mqtt
|
||||||
docker run --name riemann -p "5555:5555" -d blalor/riemann
|
docker run --name riemann -p "5555:5555" -d blalor/riemann
|
||||||
docker run --name snmp -p "31161:31161/udp" -d titilambert/snmpsim
|
docker run --name nats -p "4222:4222" -d nats
|
||||||
|
|
||||||
# Run docker containers necessary for CircleCI unit tests
|
# Run docker containers necessary for CircleCI unit tests
|
||||||
docker-run-circle:
|
docker-run-circle:
|
||||||
|
docker run --name aerospike -p "3000:3000" -d aerospike/aerospike-server:3.9.0
|
||||||
docker run --name kafka \
|
docker run --name kafka \
|
||||||
-e ADVERTISED_HOST=localhost \
|
-e ADVERTISED_HOST=localhost \
|
||||||
-e ADVERTISED_PORT=9092 \
|
-e ADVERTISED_PORT=9092 \
|
||||||
-p "2181:2181" -p "9092:9092" \
|
-p "2181:2181" -p "9092:9092" \
|
||||||
-d spotify/kafka
|
-d spotify/kafka
|
||||||
docker run --name opentsdb -p "4242:4242" -d petergrace/opentsdb-docker
|
|
||||||
docker run --name aerospike -p "3000:3000" -d aerospike
|
|
||||||
docker run --name nsq -p "4150:4150" -d nsqio/nsq /nsqd
|
docker run --name nsq -p "4150:4150" -d nsqio/nsq /nsqd
|
||||||
docker run --name mqtt -p "1883:1883" -d ncarlier/mqtt
|
docker run --name mqtt -p "1883:1883" -d ncarlier/mqtt
|
||||||
docker run --name riemann -p "5555:5555" -d blalor/riemann
|
docker run --name riemann -p "5555:5555" -d blalor/riemann
|
||||||
docker run --name snmp -p "31161:31161/udp" -d titilambert/snmpsim
|
docker run --name nats -p "4222:4222" -d nats
|
||||||
|
|
||||||
# Kill all docker containers, ignore errors
|
# Kill all docker containers, ignore errors
|
||||||
docker-kill:
|
docker-kill:
|
||||||
-docker kill nsq aerospike redis opentsdb rabbitmq postgres memcached mysql kafka mqtt riemann snmp
|
-docker kill nsq aerospike redis rabbitmq postgres memcached mysql kafka mqtt riemann nats
|
||||||
-docker rm nsq aerospike redis opentsdb rabbitmq postgres memcached mysql kafka mqtt riemann snmp
|
-docker rm nsq aerospike redis rabbitmq postgres memcached mysql kafka mqtt riemann nats
|
||||||
|
|
||||||
# Run full unit tests using docker containers (includes setup and teardown)
|
# Run full unit tests using docker containers (includes setup and teardown)
|
||||||
test: vet docker-kill docker-run
|
test: vet docker-kill docker-run
|
||||||
|
|||||||
75
README.md
75
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
|
Telegraf is an agent written in Go for collecting metrics from the system it's
|
||||||
running on, or from other services, and writing them into InfluxDB or other
|
running on, or from other services, and writing them into InfluxDB or other
|
||||||
@@ -20,12 +20,12 @@ new plugins.
|
|||||||
### Linux deb and rpm Packages:
|
### Linux deb and rpm Packages:
|
||||||
|
|
||||||
Latest:
|
Latest:
|
||||||
* http://get.influxdb.org/telegraf/telegraf_0.12.1-1_amd64.deb
|
* https://dl.influxdata.com/telegraf/releases/telegraf_1.0.0_amd64.deb
|
||||||
* http://get.influxdb.org/telegraf/telegraf-0.12.1-1.x86_64.rpm
|
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0.x86_64.rpm
|
||||||
|
|
||||||
Latest (arm):
|
Latest (arm):
|
||||||
* http://get.influxdb.org/telegraf/telegraf_0.12.1-1_armhf.deb
|
* https://dl.influxdata.com/telegraf/releases/telegraf_1.0.0_armhf.deb
|
||||||
* http://get.influxdb.org/telegraf/telegraf-0.12.1-1.armhf.rpm
|
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0.armhf.rpm
|
||||||
|
|
||||||
##### Package Instructions:
|
##### Package Instructions:
|
||||||
|
|
||||||
@@ -46,32 +46,14 @@ to use this repo to install & update telegraf.
|
|||||||
### Linux tarballs:
|
### Linux tarballs:
|
||||||
|
|
||||||
Latest:
|
Latest:
|
||||||
* http://get.influxdb.org/telegraf/telegraf-0.12.1-1_linux_amd64.tar.gz
|
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0_linux_amd64.tar.gz
|
||||||
* http://get.influxdb.org/telegraf/telegraf-0.12.1-1_linux_i386.tar.gz
|
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0_linux_i386.tar.gz
|
||||||
* http://get.influxdb.org/telegraf/telegraf-0.12.1-1_linux_armhf.tar.gz
|
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0_linux_armhf.tar.gz
|
||||||
|
|
||||||
##### tarball Instructions:
|
|
||||||
|
|
||||||
To install the full directory structure with config file, run:
|
|
||||||
|
|
||||||
```
|
|
||||||
sudo tar -C / -zxvf ./telegraf-0.12.1-1_linux_amd64.tar.gz
|
|
||||||
```
|
|
||||||
|
|
||||||
To extract only the binary, run:
|
|
||||||
|
|
||||||
```
|
|
||||||
tar -zxvf telegraf-0.12.1-1_linux_amd64.tar.gz --strip-components=3 ./usr/bin/telegraf
|
|
||||||
```
|
|
||||||
|
|
||||||
### FreeBSD tarball:
|
### FreeBSD tarball:
|
||||||
|
|
||||||
Latest:
|
Latest:
|
||||||
* http://get.influxdb.org/telegraf/telegraf-0.12.1-1_freebsd_amd64.tar.gz
|
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0_freebsd_amd64.tar.gz
|
||||||
|
|
||||||
##### tarball Instructions:
|
|
||||||
|
|
||||||
See linux instructions above.
|
|
||||||
|
|
||||||
### Ansible Role:
|
### Ansible Role:
|
||||||
|
|
||||||
@@ -87,8 +69,7 @@ brew install telegraf
|
|||||||
### Windows Binaries (EXPERIMENTAL)
|
### Windows Binaries (EXPERIMENTAL)
|
||||||
|
|
||||||
Latest:
|
Latest:
|
||||||
* http://get.influxdb.org/telegraf/telegraf-0.12.1-1_windows_amd64.zip
|
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0_windows_amd64.zip
|
||||||
* http://get.influxdb.org/telegraf/telegraf-0.12.1-1_windows_i386.zip
|
|
||||||
|
|
||||||
### From Source:
|
### From Source:
|
||||||
|
|
||||||
@@ -161,6 +142,10 @@ Currently implemented sources:
|
|||||||
* [apache](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/apache)
|
* [apache](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/apache)
|
||||||
* [bcache](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/bcache)
|
* [bcache](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/bcache)
|
||||||
* [cassandra](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/cassandra)
|
* [cassandra](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/cassandra)
|
||||||
|
* [ceph](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/ceph)
|
||||||
|
* [chrony](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/chrony)
|
||||||
|
* [consul](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/consul)
|
||||||
|
* [conntrack](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/conntrack)
|
||||||
* [couchbase](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/couchbase)
|
* [couchbase](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/couchbase)
|
||||||
* [couchdb](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/couchdb)
|
* [couchdb](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/couchdb)
|
||||||
* [disque](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/disque)
|
* [disque](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/disque)
|
||||||
@@ -168,12 +153,15 @@ Currently implemented sources:
|
|||||||
* [docker](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/docker)
|
* [docker](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/docker)
|
||||||
* [dovecot](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/dovecot)
|
* [dovecot](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/dovecot)
|
||||||
* [elasticsearch](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/elasticsearch)
|
* [elasticsearch](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/elasticsearch)
|
||||||
* [exec](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/exec ) (generic executable plugin, support JSON, influx, graphite and nagios)
|
* [exec](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/exec) (generic executable plugin, support JSON, influx, graphite and nagios)
|
||||||
|
* [filestat](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/filestat)
|
||||||
* [haproxy](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/haproxy)
|
* [haproxy](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/haproxy)
|
||||||
|
* [hddtemp](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/hddtemp)
|
||||||
* [http_response](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/http_response)
|
* [http_response](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/http_response)
|
||||||
* [httpjson](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/httpjson) (generic JSON-emitting http service plugin)
|
* [httpjson](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/httpjson) (generic JSON-emitting http service plugin)
|
||||||
* [influxdb](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/influxdb)
|
* [influxdb](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/influxdb)
|
||||||
* [ipmi_sensor](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/ipmi_sensor)
|
* [ipmi_sensor](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/ipmi_sensor)
|
||||||
|
* [iptables](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/iptables)
|
||||||
* [jolokia](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/jolokia)
|
* [jolokia](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/jolokia)
|
||||||
* [leofs](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/leofs)
|
* [leofs](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/leofs)
|
||||||
* [lustre2](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/lustre2)
|
* [lustre2](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/lustre2)
|
||||||
@@ -185,6 +173,7 @@ Currently implemented sources:
|
|||||||
* [net_response](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/net_response)
|
* [net_response](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/net_response)
|
||||||
* [nginx](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/nginx)
|
* [nginx](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/nginx)
|
||||||
* [nsq](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/nsq)
|
* [nsq](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/nsq)
|
||||||
|
* [nstat](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/nstat)
|
||||||
* [ntpq](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/ntpq)
|
* [ntpq](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/ntpq)
|
||||||
* [phpfpm](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/phpfpm)
|
* [phpfpm](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/phpfpm)
|
||||||
* [phusion passenger](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/passenger)
|
* [phusion passenger](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/passenger)
|
||||||
@@ -200,10 +189,12 @@ Currently implemented sources:
|
|||||||
* [redis](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/redis)
|
* [redis](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/redis)
|
||||||
* [rethinkdb](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/rethinkdb)
|
* [rethinkdb](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/rethinkdb)
|
||||||
* [riak](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/riak)
|
* [riak](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/riak)
|
||||||
* [sensors ](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/sensors) (only available if built from source)
|
* [sensors](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/sensors)
|
||||||
* [snmp](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/snmp)
|
* [snmp](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/snmp)
|
||||||
|
* [snmp_legacy](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/snmp_legacy)
|
||||||
* [sql server](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/sqlserver) (microsoft)
|
* [sql server](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/sqlserver) (microsoft)
|
||||||
* [twemproxy](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/twemproxy)
|
* [twemproxy](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/twemproxy)
|
||||||
|
* [varnish](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/varnish)
|
||||||
* [zfs](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/zfs)
|
* [zfs](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/zfs)
|
||||||
* [zookeeper](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/zookeeper)
|
* [zookeeper](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/zookeeper)
|
||||||
* [win_perf_counters ](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/win_perf_counters) (windows performance counters)
|
* [win_perf_counters ](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/win_perf_counters) (windows performance counters)
|
||||||
@@ -218,16 +209,25 @@ Currently implemented sources:
|
|||||||
* swap
|
* swap
|
||||||
* processes
|
* processes
|
||||||
* kernel (/proc/stat)
|
* kernel (/proc/stat)
|
||||||
|
* kernel (/proc/vmstat)
|
||||||
|
|
||||||
Telegraf can also collect metrics via the following service plugins:
|
Telegraf can also collect metrics via the following service plugins:
|
||||||
|
|
||||||
* [statsd](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/statsd)
|
* [http_listener](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/http_listener)
|
||||||
* [udp_listener](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/udp_listener)
|
|
||||||
* [tcp_listener](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/tcp_listener)
|
|
||||||
* [mqtt_consumer](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/mqtt_consumer)
|
|
||||||
* [kafka_consumer](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/kafka_consumer)
|
* [kafka_consumer](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/kafka_consumer)
|
||||||
|
* [mqtt_consumer](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/mqtt_consumer)
|
||||||
* [nats_consumer](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/nats_consumer)
|
* [nats_consumer](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/nats_consumer)
|
||||||
* [github_webhooks](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/github_webhooks)
|
* [nsq_consumer](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/nsq_consumer)
|
||||||
|
* [logparser](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/logparser)
|
||||||
|
* [statsd](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/statsd)
|
||||||
|
* [tail](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/tail)
|
||||||
|
* [tcp_listener](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/tcp_listener)
|
||||||
|
* [udp_listener](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/udp_listener)
|
||||||
|
* [webhooks](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/webhooks)
|
||||||
|
* [filestack](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/webhooks/filestack)
|
||||||
|
* [github](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/webhooks/github)
|
||||||
|
* [mandrill](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/webhooks/mandrill)
|
||||||
|
* [rollbar](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/webhooks/rollbar)
|
||||||
|
|
||||||
We'll be adding support for many more over the coming months. Read on if you
|
We'll be adding support for many more over the coming months. Read on if you
|
||||||
want to add support for another service or third-party API.
|
want to add support for another service or third-party API.
|
||||||
@@ -242,9 +242,12 @@ want to add support for another service or third-party API.
|
|||||||
* [datadog](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/datadog)
|
* [datadog](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/datadog)
|
||||||
* [file](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/file)
|
* [file](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/file)
|
||||||
* [graphite](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/graphite)
|
* [graphite](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/graphite)
|
||||||
|
* [graylog](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/graylog)
|
||||||
|
* [instrumental](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/instrumental)
|
||||||
* [kafka](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/kafka)
|
* [kafka](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/kafka)
|
||||||
* [librato](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/librato)
|
* [librato](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/librato)
|
||||||
* [mqtt](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/mqtt)
|
* [mqtt](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/mqtt)
|
||||||
|
* [nats](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/nats)
|
||||||
* [nsq](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/nsq)
|
* [nsq](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/nsq)
|
||||||
* [opentsdb](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/opentsdb)
|
* [opentsdb](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/opentsdb)
|
||||||
* [prometheus](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/prometheus_client)
|
* [prometheus](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/prometheus_client)
|
||||||
|
|||||||
@@ -2,20 +2,39 @@ package telegraf
|
|||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
// Accumulator is an interface for "accumulating" metrics from input plugin(s).
|
||||||
|
// The metrics are sent down a channel shared between all input plugins and then
|
||||||
|
// flushed on the configured flush_interval.
|
||||||
type Accumulator interface {
|
type Accumulator interface {
|
||||||
|
// AddFields adds a metric to the accumulator with the given measurement
|
||||||
|
// name, fields, and tags (and timestamp). If a timestamp is not provided,
|
||||||
|
// then the accumulator sets it to "now".
|
||||||
// Create a point with a value, decorating it with tags
|
// Create a point with a value, decorating it with tags
|
||||||
// NOTE: tags is expected to be owned by the caller, don't mutate
|
// NOTE: tags is expected to be owned by the caller, don't mutate
|
||||||
// it after passing to Add.
|
// it after passing to Add.
|
||||||
Add(measurement string,
|
|
||||||
value interface{},
|
|
||||||
tags map[string]string,
|
|
||||||
t ...time.Time)
|
|
||||||
|
|
||||||
AddFields(measurement string,
|
AddFields(measurement string,
|
||||||
fields map[string]interface{},
|
fields map[string]interface{},
|
||||||
tags map[string]string,
|
tags map[string]string,
|
||||||
t ...time.Time)
|
t ...time.Time)
|
||||||
|
|
||||||
|
// AddGauge is the same as AddFields, but will add the metric as a "Gauge" type
|
||||||
|
AddGauge(measurement string,
|
||||||
|
fields map[string]interface{},
|
||||||
|
tags map[string]string,
|
||||||
|
t ...time.Time)
|
||||||
|
|
||||||
|
// AddCounter is the same as AddFields, but will add the metric as a "Counter" type
|
||||||
|
AddCounter(measurement string,
|
||||||
|
fields map[string]interface{},
|
||||||
|
tags map[string]string,
|
||||||
|
t ...time.Time)
|
||||||
|
|
||||||
|
AddError(err error)
|
||||||
|
|
||||||
Debug() bool
|
Debug() bool
|
||||||
SetDebug(enabled bool)
|
SetDebug(enabled bool)
|
||||||
|
|
||||||
|
SetPrecision(precision, interval time.Duration)
|
||||||
|
|
||||||
|
DisablePrecision()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"sync"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
@@ -12,43 +12,30 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func NewAccumulator(
|
func NewAccumulator(
|
||||||
inputConfig *internal_models.InputConfig,
|
inputConfig *models.InputConfig,
|
||||||
metrics chan telegraf.Metric,
|
metrics chan telegraf.Metric,
|
||||||
) *accumulator {
|
) *accumulator {
|
||||||
acc := accumulator{}
|
acc := accumulator{}
|
||||||
acc.metrics = metrics
|
acc.metrics = metrics
|
||||||
acc.inputConfig = inputConfig
|
acc.inputConfig = inputConfig
|
||||||
|
acc.precision = time.Nanosecond
|
||||||
return &acc
|
return &acc
|
||||||
}
|
}
|
||||||
|
|
||||||
type accumulator struct {
|
type accumulator struct {
|
||||||
sync.Mutex
|
|
||||||
|
|
||||||
metrics chan telegraf.Metric
|
metrics chan telegraf.Metric
|
||||||
|
|
||||||
defaultTags map[string]string
|
defaultTags map[string]string
|
||||||
|
|
||||||
debug bool
|
debug bool
|
||||||
|
// print every point added to the accumulator
|
||||||
|
trace bool
|
||||||
|
|
||||||
inputConfig *internal_models.InputConfig
|
inputConfig *models.InputConfig
|
||||||
|
|
||||||
prefix string
|
precision time.Duration
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *accumulator) Add(
|
errCount uint64
|
||||||
measurement string,
|
|
||||||
value interface{},
|
|
||||||
tags map[string]string,
|
|
||||||
t ...time.Time,
|
|
||||||
) {
|
|
||||||
fields := make(map[string]interface{})
|
|
||||||
fields["value"] = value
|
|
||||||
|
|
||||||
if !ac.inputConfig.Filter.ShouldNamePass(measurement) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ac.AddFields(measurement, fields, tags, t...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ac *accumulator) AddFields(
|
func (ac *accumulator) AddFields(
|
||||||
@@ -57,16 +44,47 @@ func (ac *accumulator) AddFields(
|
|||||||
tags map[string]string,
|
tags map[string]string,
|
||||||
t ...time.Time,
|
t ...time.Time,
|
||||||
) {
|
) {
|
||||||
|
if m := ac.makeMetric(measurement, fields, tags, telegraf.Untyped, t...); m != nil {
|
||||||
|
ac.metrics <- m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *accumulator) AddGauge(
|
||||||
|
measurement string,
|
||||||
|
fields map[string]interface{},
|
||||||
|
tags map[string]string,
|
||||||
|
t ...time.Time,
|
||||||
|
) {
|
||||||
|
if m := ac.makeMetric(measurement, fields, tags, telegraf.Gauge, t...); m != nil {
|
||||||
|
ac.metrics <- m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *accumulator) AddCounter(
|
||||||
|
measurement string,
|
||||||
|
fields map[string]interface{},
|
||||||
|
tags map[string]string,
|
||||||
|
t ...time.Time,
|
||||||
|
) {
|
||||||
|
if m := ac.makeMetric(measurement, fields, tags, telegraf.Counter, t...); m != nil {
|
||||||
|
ac.metrics <- m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeMetric either returns a metric, or returns nil if the metric doesn't
|
||||||
|
// need to be created (because of filtering, an error, etc.)
|
||||||
|
func (ac *accumulator) makeMetric(
|
||||||
|
measurement string,
|
||||||
|
fields map[string]interface{},
|
||||||
|
tags map[string]string,
|
||||||
|
mType telegraf.ValueType,
|
||||||
|
t ...time.Time,
|
||||||
|
) telegraf.Metric {
|
||||||
if len(fields) == 0 || len(measurement) == 0 {
|
if len(fields) == 0 || len(measurement) == 0 {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
if tags == nil {
|
||||||
if !ac.inputConfig.Filter.ShouldNamePass(measurement) {
|
tags = make(map[string]string)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ac.inputConfig.Filter.ShouldTagsPass(tags) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override measurement name if set
|
// Override measurement name if set
|
||||||
@@ -81,9 +99,6 @@ func (ac *accumulator) AddFields(
|
|||||||
measurement = measurement + ac.inputConfig.MeasurementSuffix
|
measurement = measurement + ac.inputConfig.MeasurementSuffix
|
||||||
}
|
}
|
||||||
|
|
||||||
if tags == nil {
|
|
||||||
tags = make(map[string]string)
|
|
||||||
}
|
|
||||||
// Apply plugin-wide tags if set
|
// Apply plugin-wide tags if set
|
||||||
for k, v := range ac.inputConfig.Tags {
|
for k, v := range ac.inputConfig.Tags {
|
||||||
if _, ok := tags[k]; !ok {
|
if _, ok := tags[k]; !ok {
|
||||||
@@ -97,23 +112,20 @@ func (ac *accumulator) AddFields(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make(map[string]interface{})
|
// Apply the metric filter(s)
|
||||||
for k, v := range fields {
|
if ok := ac.inputConfig.Filter.Apply(measurement, fields, tags); !ok {
|
||||||
// Filter out any filtered fields
|
return nil
|
||||||
if ac.inputConfig != nil {
|
}
|
||||||
if !ac.inputConfig.Filter.ShouldFieldsPass(k) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for k, v := range fields {
|
||||||
// Validate uint64 and float64 fields
|
// Validate uint64 and float64 fields
|
||||||
switch val := v.(type) {
|
switch val := v.(type) {
|
||||||
case uint64:
|
case uint64:
|
||||||
// InfluxDB does not support writing uint64
|
// InfluxDB does not support writing uint64
|
||||||
if val < uint64(9223372036854775808) {
|
if val < uint64(9223372036854775808) {
|
||||||
result[k] = int64(val)
|
fields[k] = int64(val)
|
||||||
} else {
|
} else {
|
||||||
result[k] = int64(9223372036854775807)
|
fields[k] = int64(9223372036854775807)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
case float64:
|
case float64:
|
||||||
@@ -124,15 +136,12 @@ func (ac *accumulator) AddFields(
|
|||||||
"field, skipping",
|
"field, skipping",
|
||||||
measurement, k)
|
measurement, k)
|
||||||
}
|
}
|
||||||
|
delete(fields, k)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result[k] = v
|
fields[k] = v
|
||||||
}
|
|
||||||
fields = nil
|
|
||||||
if len(result) == 0 {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var timestamp time.Time
|
var timestamp time.Time
|
||||||
@@ -141,20 +150,39 @@ func (ac *accumulator) AddFields(
|
|||||||
} else {
|
} else {
|
||||||
timestamp = time.Now()
|
timestamp = time.Now()
|
||||||
}
|
}
|
||||||
|
timestamp = timestamp.Round(ac.precision)
|
||||||
|
|
||||||
if ac.prefix != "" {
|
var m telegraf.Metric
|
||||||
measurement = ac.prefix + measurement
|
var err error
|
||||||
|
switch mType {
|
||||||
|
case telegraf.Counter:
|
||||||
|
m, err = telegraf.NewCounterMetric(measurement, tags, fields, timestamp)
|
||||||
|
case telegraf.Gauge:
|
||||||
|
m, err = telegraf.NewGaugeMetric(measurement, tags, fields, timestamp)
|
||||||
|
default:
|
||||||
|
m, err = telegraf.NewMetric(measurement, tags, fields, timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
m, err := telegraf.NewMetric(measurement, tags, result, timestamp)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error adding point [%s]: %s\n", measurement, err.Error())
|
log.Printf("Error adding point [%s]: %s\n", measurement, err.Error())
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
if ac.debug {
|
|
||||||
|
if ac.trace {
|
||||||
fmt.Println("> " + m.String())
|
fmt.Println("> " + m.String())
|
||||||
}
|
}
|
||||||
ac.metrics <- m
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddError passes a runtime error to the accumulator.
|
||||||
|
// The error will be tagged with the plugin name and written to the log.
|
||||||
|
func (ac *accumulator) AddError(err error) {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
atomic.AddUint64(&ac.errCount, 1)
|
||||||
|
//TODO suppress/throttle consecutive duplicate errors?
|
||||||
|
log.Printf("ERROR in input [%s]: %s", ac.inputConfig.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ac *accumulator) Debug() bool {
|
func (ac *accumulator) Debug() bool {
|
||||||
@@ -165,6 +193,39 @@ func (ac *accumulator) SetDebug(debug bool) {
|
|||||||
ac.debug = debug
|
ac.debug = debug
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ac *accumulator) Trace() bool {
|
||||||
|
return ac.trace
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *accumulator) SetTrace(trace bool) {
|
||||||
|
ac.trace = trace
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPrecision takes two time.Duration objects. If the first is non-zero,
|
||||||
|
// it sets that as the precision. Otherwise, it takes the second argument
|
||||||
|
// as the order of time that the metrics should be rounded to, with the
|
||||||
|
// maximum being 1s.
|
||||||
|
func (ac *accumulator) SetPrecision(precision, interval time.Duration) {
|
||||||
|
if precision > 0 {
|
||||||
|
ac.precision = precision
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case interval >= time.Second:
|
||||||
|
ac.precision = time.Second
|
||||||
|
case interval >= time.Millisecond:
|
||||||
|
ac.precision = time.Millisecond
|
||||||
|
case interval >= time.Microsecond:
|
||||||
|
ac.precision = time.Microsecond
|
||||||
|
default:
|
||||||
|
ac.precision = time.Nanosecond
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *accumulator) DisablePrecision() {
|
||||||
|
ac.precision = time.Nanosecond
|
||||||
|
}
|
||||||
|
|
||||||
func (ac *accumulator) setDefaultTags(tags map[string]string) {
|
func (ac *accumulator) setDefaultTags(tags map[string]string) {
|
||||||
ac.defaultTags = tags
|
ac.defaultTags = tags
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package agent
|
package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"math"
|
"math"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -10,6 +13,7 @@ import (
|
|||||||
"github.com/influxdata/telegraf/internal/models"
|
"github.com/influxdata/telegraf/internal/models"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAdd(t *testing.T) {
|
func TestAdd(t *testing.T) {
|
||||||
@@ -17,11 +21,17 @@ func TestAdd(t *testing.T) {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
a.metrics = make(chan telegraf.Metric, 10)
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
defer close(a.metrics)
|
defer close(a.metrics)
|
||||||
a.inputConfig = &internal_models.InputConfig{}
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
a.Add("acctest", float64(101), map[string]string{})
|
a.AddFields("acctest",
|
||||||
a.Add("acctest", float64(101), map[string]string{"acc": "test"})
|
map[string]interface{}{"value": float64(101)},
|
||||||
a.Add("acctest", float64(101), map[string]string{"acc": "test"}, now)
|
map[string]string{})
|
||||||
|
a.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": float64(101)},
|
||||||
|
map[string]string{"acc": "test"})
|
||||||
|
a.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": float64(101)},
|
||||||
|
map[string]string{"acc": "test"}, now)
|
||||||
|
|
||||||
testm := <-a.metrics
|
testm := <-a.metrics
|
||||||
actual := testm.String()
|
actual := testm.String()
|
||||||
@@ -38,17 +48,241 @@ func TestAdd(t *testing.T) {
|
|||||||
actual)
|
actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAddGauge(t *testing.T) {
|
||||||
|
a := accumulator{}
|
||||||
|
now := time.Now()
|
||||||
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
|
defer close(a.metrics)
|
||||||
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
|
a.AddGauge("acctest",
|
||||||
|
map[string]interface{}{"value": float64(101)},
|
||||||
|
map[string]string{})
|
||||||
|
a.AddGauge("acctest",
|
||||||
|
map[string]interface{}{"value": float64(101)},
|
||||||
|
map[string]string{"acc": "test"})
|
||||||
|
a.AddGauge("acctest",
|
||||||
|
map[string]interface{}{"value": float64(101)},
|
||||||
|
map[string]string{"acc": "test"}, now)
|
||||||
|
|
||||||
|
testm := <-a.metrics
|
||||||
|
actual := testm.String()
|
||||||
|
assert.Contains(t, actual, "acctest value=101")
|
||||||
|
assert.Equal(t, testm.Type(), telegraf.Gauge)
|
||||||
|
|
||||||
|
testm = <-a.metrics
|
||||||
|
actual = testm.String()
|
||||||
|
assert.Contains(t, actual, "acctest,acc=test value=101")
|
||||||
|
assert.Equal(t, testm.Type(), telegraf.Gauge)
|
||||||
|
|
||||||
|
testm = <-a.metrics
|
||||||
|
actual = testm.String()
|
||||||
|
assert.Equal(t,
|
||||||
|
fmt.Sprintf("acctest,acc=test value=101 %d", now.UnixNano()),
|
||||||
|
actual)
|
||||||
|
assert.Equal(t, testm.Type(), telegraf.Gauge)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddCounter(t *testing.T) {
|
||||||
|
a := accumulator{}
|
||||||
|
now := time.Now()
|
||||||
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
|
defer close(a.metrics)
|
||||||
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
|
a.AddCounter("acctest",
|
||||||
|
map[string]interface{}{"value": float64(101)},
|
||||||
|
map[string]string{})
|
||||||
|
a.AddCounter("acctest",
|
||||||
|
map[string]interface{}{"value": float64(101)},
|
||||||
|
map[string]string{"acc": "test"})
|
||||||
|
a.AddCounter("acctest",
|
||||||
|
map[string]interface{}{"value": float64(101)},
|
||||||
|
map[string]string{"acc": "test"}, now)
|
||||||
|
|
||||||
|
testm := <-a.metrics
|
||||||
|
actual := testm.String()
|
||||||
|
assert.Contains(t, actual, "acctest value=101")
|
||||||
|
assert.Equal(t, testm.Type(), telegraf.Counter)
|
||||||
|
|
||||||
|
testm = <-a.metrics
|
||||||
|
actual = testm.String()
|
||||||
|
assert.Contains(t, actual, "acctest,acc=test value=101")
|
||||||
|
assert.Equal(t, testm.Type(), telegraf.Counter)
|
||||||
|
|
||||||
|
testm = <-a.metrics
|
||||||
|
actual = testm.String()
|
||||||
|
assert.Equal(t,
|
||||||
|
fmt.Sprintf("acctest,acc=test value=101 %d", now.UnixNano()),
|
||||||
|
actual)
|
||||||
|
assert.Equal(t, testm.Type(), telegraf.Counter)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = &models.InputConfig{}
|
||||||
|
|
||||||
|
a.SetPrecision(0, time.Second)
|
||||||
|
a.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": float64(101)},
|
||||||
|
map[string]string{})
|
||||||
|
a.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": float64(101)},
|
||||||
|
map[string]string{"acc": "test"})
|
||||||
|
a.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": 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 = &models.InputConfig{}
|
||||||
|
|
||||||
|
a.SetPrecision(time.Second, time.Millisecond)
|
||||||
|
a.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": float64(101)},
|
||||||
|
map[string]string{})
|
||||||
|
a.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": float64(101)},
|
||||||
|
map[string]string{"acc": "test"})
|
||||||
|
a.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": 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 = &models.InputConfig{}
|
||||||
|
|
||||||
|
a.SetPrecision(time.Second, time.Millisecond)
|
||||||
|
a.DisablePrecision()
|
||||||
|
a.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": float64(101)},
|
||||||
|
map[string]string{})
|
||||||
|
a.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": float64(101)},
|
||||||
|
map[string]string{"acc": "test"})
|
||||||
|
a.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": 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 = &models.InputConfig{}
|
||||||
|
|
||||||
|
a.SetPrecision(0, time.Second)
|
||||||
|
a.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": 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.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": 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.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": 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.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": float64(101)},
|
||||||
|
map[string]string{"acc": "test"}, now)
|
||||||
|
testm = <-a.metrics
|
||||||
|
actual = testm.String()
|
||||||
|
assert.Equal(t,
|
||||||
|
fmt.Sprintf("acctest,acc=test value=101 %d", int64(1139572800082912748)),
|
||||||
|
actual)
|
||||||
|
}
|
||||||
|
|
||||||
func TestAddDefaultTags(t *testing.T) {
|
func TestAddDefaultTags(t *testing.T) {
|
||||||
a := accumulator{}
|
a := accumulator{}
|
||||||
a.addDefaultTag("default", "tag")
|
a.addDefaultTag("default", "tag")
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
a.metrics = make(chan telegraf.Metric, 10)
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
defer close(a.metrics)
|
defer close(a.metrics)
|
||||||
a.inputConfig = &internal_models.InputConfig{}
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
a.Add("acctest", float64(101), map[string]string{})
|
a.AddFields("acctest",
|
||||||
a.Add("acctest", float64(101), map[string]string{"acc": "test"})
|
map[string]interface{}{"value": float64(101)},
|
||||||
a.Add("acctest", float64(101), map[string]string{"acc": "test"}, now)
|
map[string]string{})
|
||||||
|
a.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": float64(101)},
|
||||||
|
map[string]string{"acc": "test"})
|
||||||
|
a.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": float64(101)},
|
||||||
|
map[string]string{"acc": "test"}, now)
|
||||||
|
|
||||||
testm := <-a.metrics
|
testm := <-a.metrics
|
||||||
actual := testm.String()
|
actual := testm.String()
|
||||||
@@ -70,7 +304,7 @@ func TestAddFields(t *testing.T) {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
a.metrics = make(chan telegraf.Metric, 10)
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
defer close(a.metrics)
|
defer close(a.metrics)
|
||||||
a.inputConfig = &internal_models.InputConfig{}
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
fields := map[string]interface{}{
|
fields := map[string]interface{}{
|
||||||
"usage": float64(99),
|
"usage": float64(99),
|
||||||
@@ -103,7 +337,7 @@ func TestAddInfFields(t *testing.T) {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
a.metrics = make(chan telegraf.Metric, 10)
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
defer close(a.metrics)
|
defer close(a.metrics)
|
||||||
a.inputConfig = &internal_models.InputConfig{}
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
fields := map[string]interface{}{
|
fields := map[string]interface{}{
|
||||||
"usage": inf,
|
"usage": inf,
|
||||||
@@ -131,7 +365,7 @@ func TestAddNaNFields(t *testing.T) {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
a.metrics = make(chan telegraf.Metric, 10)
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
defer close(a.metrics)
|
defer close(a.metrics)
|
||||||
a.inputConfig = &internal_models.InputConfig{}
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
fields := map[string]interface{}{
|
fields := map[string]interface{}{
|
||||||
"usage": nan,
|
"usage": nan,
|
||||||
@@ -155,7 +389,7 @@ func TestAddUint64Fields(t *testing.T) {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
a.metrics = make(chan telegraf.Metric, 10)
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
defer close(a.metrics)
|
defer close(a.metrics)
|
||||||
a.inputConfig = &internal_models.InputConfig{}
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
fields := map[string]interface{}{
|
fields := map[string]interface{}{
|
||||||
"usage": uint64(99),
|
"usage": uint64(99),
|
||||||
@@ -184,7 +418,7 @@ func TestAddUint64Overflow(t *testing.T) {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
a.metrics = make(chan telegraf.Metric, 10)
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
defer close(a.metrics)
|
defer close(a.metrics)
|
||||||
a.inputConfig = &internal_models.InputConfig{}
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
fields := map[string]interface{}{
|
fields := map[string]interface{}{
|
||||||
"usage": uint64(9223372036854775808),
|
"usage": uint64(9223372036854775808),
|
||||||
@@ -214,11 +448,17 @@ func TestAddInts(t *testing.T) {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
a.metrics = make(chan telegraf.Metric, 10)
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
defer close(a.metrics)
|
defer close(a.metrics)
|
||||||
a.inputConfig = &internal_models.InputConfig{}
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
a.Add("acctest", int(101), map[string]string{})
|
a.AddFields("acctest",
|
||||||
a.Add("acctest", int32(101), map[string]string{"acc": "test"})
|
map[string]interface{}{"value": int(101)},
|
||||||
a.Add("acctest", int64(101), map[string]string{"acc": "test"}, now)
|
map[string]string{})
|
||||||
|
a.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": int32(101)},
|
||||||
|
map[string]string{"acc": "test"})
|
||||||
|
a.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": int64(101)},
|
||||||
|
map[string]string{"acc": "test"}, now)
|
||||||
|
|
||||||
testm := <-a.metrics
|
testm := <-a.metrics
|
||||||
actual := testm.String()
|
actual := testm.String()
|
||||||
@@ -241,10 +481,14 @@ func TestAddFloats(t *testing.T) {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
a.metrics = make(chan telegraf.Metric, 10)
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
defer close(a.metrics)
|
defer close(a.metrics)
|
||||||
a.inputConfig = &internal_models.InputConfig{}
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
a.Add("acctest", float32(101), map[string]string{"acc": "test"})
|
a.AddFields("acctest",
|
||||||
a.Add("acctest", float64(101), map[string]string{"acc": "test"}, now)
|
map[string]interface{}{"value": float32(101)},
|
||||||
|
map[string]string{"acc": "test"})
|
||||||
|
a.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": float64(101)},
|
||||||
|
map[string]string{"acc": "test"}, now)
|
||||||
|
|
||||||
testm := <-a.metrics
|
testm := <-a.metrics
|
||||||
actual := testm.String()
|
actual := testm.String()
|
||||||
@@ -263,10 +507,14 @@ func TestAddStrings(t *testing.T) {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
a.metrics = make(chan telegraf.Metric, 10)
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
defer close(a.metrics)
|
defer close(a.metrics)
|
||||||
a.inputConfig = &internal_models.InputConfig{}
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
a.Add("acctest", "test", map[string]string{"acc": "test"})
|
a.AddFields("acctest",
|
||||||
a.Add("acctest", "foo", map[string]string{"acc": "test"}, now)
|
map[string]interface{}{"value": "test"},
|
||||||
|
map[string]string{"acc": "test"})
|
||||||
|
a.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": "foo"},
|
||||||
|
map[string]string{"acc": "test"}, now)
|
||||||
|
|
||||||
testm := <-a.metrics
|
testm := <-a.metrics
|
||||||
actual := testm.String()
|
actual := testm.String()
|
||||||
@@ -285,10 +533,12 @@ func TestAddBools(t *testing.T) {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
a.metrics = make(chan telegraf.Metric, 10)
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
defer close(a.metrics)
|
defer close(a.metrics)
|
||||||
a.inputConfig = &internal_models.InputConfig{}
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
a.Add("acctest", true, map[string]string{"acc": "test"})
|
a.AddFields("acctest",
|
||||||
a.Add("acctest", false, map[string]string{"acc": "test"}, now)
|
map[string]interface{}{"value": true}, map[string]string{"acc": "test"})
|
||||||
|
a.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": false}, map[string]string{"acc": "test"}, now)
|
||||||
|
|
||||||
testm := <-a.metrics
|
testm := <-a.metrics
|
||||||
actual := testm.String()
|
actual := testm.String()
|
||||||
@@ -300,3 +550,65 @@ func TestAddBools(t *testing.T) {
|
|||||||
fmt.Sprintf("acctest,acc=test,default=tag value=false %d", now.UnixNano()),
|
fmt.Sprintf("acctest,acc=test,default=tag value=false %d", now.UnixNano()),
|
||||||
actual)
|
actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test that tag filters get applied to metrics.
|
||||||
|
func TestAccFilterTags(t *testing.T) {
|
||||||
|
a := accumulator{}
|
||||||
|
now := time.Now()
|
||||||
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
|
defer close(a.metrics)
|
||||||
|
filter := models.Filter{
|
||||||
|
TagExclude: []string{"acc"},
|
||||||
|
}
|
||||||
|
assert.NoError(t, filter.Compile())
|
||||||
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
a.inputConfig.Filter = filter
|
||||||
|
|
||||||
|
a.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": float64(101)},
|
||||||
|
map[string]string{})
|
||||||
|
a.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": float64(101)},
|
||||||
|
map[string]string{"acc": "test"})
|
||||||
|
a.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": 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 value=101")
|
||||||
|
|
||||||
|
testm = <-a.metrics
|
||||||
|
actual = testm.String()
|
||||||
|
assert.Equal(t,
|
||||||
|
fmt.Sprintf("acctest value=101 %d", now.UnixNano()),
|
||||||
|
actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccAddError(t *testing.T) {
|
||||||
|
errBuf := bytes.NewBuffer(nil)
|
||||||
|
log.SetOutput(errBuf)
|
||||||
|
defer log.SetOutput(os.Stderr)
|
||||||
|
|
||||||
|
a := accumulator{}
|
||||||
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
a.inputConfig.Name = "mock_plugin"
|
||||||
|
|
||||||
|
a.AddError(fmt.Errorf("foo"))
|
||||||
|
a.AddError(fmt.Errorf("bar"))
|
||||||
|
a.AddError(fmt.Errorf("baz"))
|
||||||
|
|
||||||
|
errs := bytes.Split(errBuf.Bytes(), []byte{'\n'})
|
||||||
|
assert.EqualValues(t, 3, a.errCount)
|
||||||
|
require.Len(t, errs, 4) // 4 because of trailing newline
|
||||||
|
assert.Contains(t, string(errs[0]), "mock_plugin")
|
||||||
|
assert.Contains(t, string(errs[0]), "foo")
|
||||||
|
assert.Contains(t, string(errs[1]), "mock_plugin")
|
||||||
|
assert.Contains(t, string(errs[1]), "bar")
|
||||||
|
assert.Contains(t, string(errs[2]), "mock_plugin")
|
||||||
|
assert.Contains(t, string(errs[2]), "baz")
|
||||||
|
}
|
||||||
|
|||||||
215
agent/agent.go
215
agent/agent.go
@@ -1,17 +1,15 @@
|
|||||||
package agent
|
package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
cryptorand "crypto/rand"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"math/big"
|
|
||||||
"math/rand"
|
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/internal"
|
||||||
"github.com/influxdata/telegraf/internal/config"
|
"github.com/influxdata/telegraf/internal/config"
|
||||||
"github.com/influxdata/telegraf/internal/models"
|
"github.com/influxdata/telegraf/internal/models"
|
||||||
)
|
)
|
||||||
@@ -90,7 +88,7 @@ func (a *Agent) Close() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func panicRecover(input *internal_models.RunningInput) {
|
func panicRecover(input *models.RunningInput) {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
trace := make([]byte, 2048)
|
trace := make([]byte, 2048)
|
||||||
runtime.Stack(trace, true)
|
runtime.Stack(trace, true)
|
||||||
@@ -102,93 +100,41 @@ func panicRecover(input *internal_models.RunningInput) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// gatherParallel runs the inputs that are using the same reporting interval
|
// gatherer runs the inputs that have been configured with their own
|
||||||
// as the telegraf agent.
|
|
||||||
func (a *Agent) gatherParallel(metricC chan telegraf.Metric) error {
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
counter := 0
|
|
||||||
jitter := a.Config.Agent.CollectionJitter.Duration.Nanoseconds()
|
|
||||||
for _, input := range a.Config.Inputs {
|
|
||||||
if input.Config.Interval != 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Add(1)
|
|
||||||
counter++
|
|
||||||
go func(input *internal_models.RunningInput) {
|
|
||||||
defer panicRecover(input)
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
acc := NewAccumulator(input.Config, metricC)
|
|
||||||
acc.SetDebug(a.Config.Agent.Debug)
|
|
||||||
acc.setDefaultTags(a.Config.Tags)
|
|
||||||
|
|
||||||
if jitter != 0 {
|
|
||||||
nanoSleep := rand.Int63n(jitter)
|
|
||||||
d, err := time.ParseDuration(fmt.Sprintf("%dns", nanoSleep))
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Jittering collection interval failed for plugin %s",
|
|
||||||
input.Name)
|
|
||||||
} else {
|
|
||||||
time.Sleep(d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := input.Input.Gather(acc); err != nil {
|
|
||||||
log.Printf("Error in input [%s]: %s", input.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
}(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
if counter == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
elapsed := time.Since(start)
|
|
||||||
if !a.Config.Agent.Quiet {
|
|
||||||
log.Printf("Gathered metrics, (%s interval), from %d inputs in %s\n",
|
|
||||||
a.Config.Agent.Interval.Duration, counter, elapsed)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// gatherSeparate runs the inputs that have been configured with their own
|
|
||||||
// reporting interval.
|
// reporting interval.
|
||||||
func (a *Agent) gatherSeparate(
|
func (a *Agent) gatherer(
|
||||||
shutdown chan struct{},
|
shutdown chan struct{},
|
||||||
input *internal_models.RunningInput,
|
input *models.RunningInput,
|
||||||
|
interval time.Duration,
|
||||||
metricC chan telegraf.Metric,
|
metricC chan telegraf.Metric,
|
||||||
) error {
|
) error {
|
||||||
defer panicRecover(input)
|
defer panicRecover(input)
|
||||||
|
|
||||||
ticker := time.NewTicker(input.Config.Interval)
|
ticker := time.NewTicker(interval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
var outerr error
|
var outerr error
|
||||||
start := time.Now()
|
|
||||||
|
|
||||||
acc := NewAccumulator(input.Config, metricC)
|
acc := NewAccumulator(input.Config, metricC)
|
||||||
acc.SetDebug(a.Config.Agent.Debug)
|
acc.SetDebug(a.Config.Agent.Debug)
|
||||||
|
acc.SetPrecision(a.Config.Agent.Precision.Duration,
|
||||||
|
a.Config.Agent.Interval.Duration)
|
||||||
acc.setDefaultTags(a.Config.Tags)
|
acc.setDefaultTags(a.Config.Tags)
|
||||||
|
|
||||||
if err := input.Input.Gather(acc); err != nil {
|
internal.RandomSleep(a.Config.Agent.CollectionJitter.Duration, shutdown)
|
||||||
log.Printf("Error in input [%s]: %s", input.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
gatherWithTimeout(shutdown, input, acc, interval)
|
||||||
elapsed := time.Since(start)
|
elapsed := time.Since(start)
|
||||||
if !a.Config.Agent.Quiet {
|
|
||||||
log.Printf("Gathered metrics, (separate %s interval), from %s in %s\n",
|
|
||||||
input.Config.Interval, input.Name, elapsed)
|
|
||||||
}
|
|
||||||
|
|
||||||
if outerr != nil {
|
if outerr != nil {
|
||||||
return outerr
|
return outerr
|
||||||
}
|
}
|
||||||
|
if a.Config.Agent.Debug {
|
||||||
|
log.Printf("Input [%s] gathered metrics, (%s interval) in %s\n",
|
||||||
|
input.Name, interval, elapsed)
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-shutdown:
|
case <-shutdown:
|
||||||
@@ -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 *models.RunningInput,
|
||||||
|
acc *accumulator,
|
||||||
|
timeout time.Duration,
|
||||||
|
) {
|
||||||
|
ticker := time.NewTicker(timeout)
|
||||||
|
defer ticker.Stop()
|
||||||
|
done := make(chan error)
|
||||||
|
go func() {
|
||||||
|
done <- input.Input.Gather(acc)
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case err := <-done:
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("ERROR in input [%s]: %s", input.Name, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
log.Printf("ERROR: input [%s] took longer to collect than "+
|
||||||
|
"collection interval (%s)",
|
||||||
|
input.Name, timeout)
|
||||||
|
continue
|
||||||
|
case <-shutdown:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Test verifies that we can 'Gather' from all inputs with their configured
|
// Test verifies that we can 'Gather' from all inputs with their configured
|
||||||
// Config struct
|
// Config struct
|
||||||
func (a *Agent) Test() error {
|
func (a *Agent) Test() error {
|
||||||
@@ -220,7 +202,10 @@ func (a *Agent) Test() error {
|
|||||||
|
|
||||||
for _, input := range a.Config.Inputs {
|
for _, input := range a.Config.Inputs {
|
||||||
acc := NewAccumulator(input.Config, metricC)
|
acc := NewAccumulator(input.Config, metricC)
|
||||||
acc.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)
|
fmt.Printf("* Plugin: %s, Collection 1\n", input.Name)
|
||||||
if input.Config.Interval != 0 {
|
if input.Config.Interval != 0 {
|
||||||
@@ -230,6 +215,9 @@ func (a *Agent) Test() error {
|
|||||||
if err := input.Input.Gather(acc); err != nil {
|
if err := input.Input.Gather(acc); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if acc.errCount > 0 {
|
||||||
|
return fmt.Errorf("Errors encountered during processing")
|
||||||
|
}
|
||||||
|
|
||||||
// Special instructions for some inputs. cpu, for example, needs to be
|
// Special instructions for some inputs. cpu, for example, needs to be
|
||||||
// run twice in order to return cpu usage percentages.
|
// run twice in order to return cpu usage percentages.
|
||||||
@@ -252,7 +240,7 @@ func (a *Agent) flush() {
|
|||||||
|
|
||||||
wg.Add(len(a.Config.Outputs))
|
wg.Add(len(a.Config.Outputs))
|
||||||
for _, o := range a.Config.Outputs {
|
for _, o := range a.Config.Outputs {
|
||||||
go func(output *internal_models.RunningOutput) {
|
go func(output *models.RunningOutput) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
err := output.Write()
|
err := output.Write()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -280,44 +268,40 @@ func (a *Agent) flusher(shutdown chan struct{}, metricC chan telegraf.Metric) er
|
|||||||
a.flush()
|
a.flush()
|
||||||
return nil
|
return nil
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
|
internal.RandomSleep(a.Config.Agent.FlushJitter.Duration, shutdown)
|
||||||
a.flush()
|
a.flush()
|
||||||
case m := <-metricC:
|
case m := <-metricC:
|
||||||
for _, o := range a.Config.Outputs {
|
for i, o := range a.Config.Outputs {
|
||||||
o.AddMetric(m)
|
if i == len(a.Config.Outputs)-1 {
|
||||||
|
o.AddMetric(m)
|
||||||
|
} else {
|
||||||
|
o.AddMetric(copyMetric(m))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// jitterInterval applies the the interval jitter to the flush interval using
|
func copyMetric(m telegraf.Metric) telegraf.Metric {
|
||||||
// crypto/rand number generator
|
t := time.Time(m.Time())
|
||||||
func jitterInterval(ininterval, injitter time.Duration) time.Duration {
|
|
||||||
var jitter int64
|
tags := make(map[string]string)
|
||||||
outinterval := ininterval
|
fields := make(map[string]interface{})
|
||||||
if injitter.Nanoseconds() != 0 {
|
for k, v := range m.Tags() {
|
||||||
maxjitter := big.NewInt(injitter.Nanoseconds())
|
tags[k] = v
|
||||||
if j, err := cryptorand.Int(cryptorand.Reader, maxjitter); err == nil {
|
}
|
||||||
jitter = j.Int64()
|
for k, v := range m.Fields() {
|
||||||
}
|
fields[k] = v
|
||||||
outinterval = time.Duration(jitter + ininterval.Nanoseconds())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if outinterval.Nanoseconds() < time.Duration(500*time.Millisecond).Nanoseconds() {
|
out, _ := telegraf.NewMetric(m.Name(), tags, fields, t)
|
||||||
log.Printf("Flush interval %s too low, setting to 500ms\n", outinterval)
|
return out
|
||||||
outinterval = time.Duration(500 * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
return outinterval
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run runs the agent daemon, gathering every Interval
|
// Run runs the agent daemon, gathering every Interval
|
||||||
func (a *Agent) Run(shutdown chan struct{}) error {
|
func (a *Agent) Run(shutdown chan struct{}) error {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
a.Config.Agent.FlushInterval.Duration = jitterInterval(
|
|
||||||
a.Config.Agent.FlushInterval.Duration,
|
|
||||||
a.Config.Agent.FlushJitter.Duration)
|
|
||||||
|
|
||||||
log.Printf("Agent Config: Interval:%s, Debug:%#v, Quiet:%#v, Hostname:%#v, "+
|
log.Printf("Agent Config: Interval:%s, Debug:%#v, Quiet:%#v, Hostname:%#v, "+
|
||||||
"Flush Interval:%s \n",
|
"Flush Interval:%s \n",
|
||||||
a.Config.Agent.Interval.Duration, a.Config.Agent.Debug, a.Config.Agent.Quiet,
|
a.Config.Agent.Interval.Duration, a.Config.Agent.Debug, a.Config.Agent.Quiet,
|
||||||
@@ -332,6 +316,9 @@ func (a *Agent) Run(shutdown chan struct{}) error {
|
|||||||
case telegraf.ServiceInput:
|
case telegraf.ServiceInput:
|
||||||
acc := NewAccumulator(input.Config, metricC)
|
acc := NewAccumulator(input.Config, metricC)
|
||||||
acc.SetDebug(a.Config.Agent.Debug)
|
acc.SetDebug(a.Config.Agent.Debug)
|
||||||
|
// Service input plugins should set their own precision of their
|
||||||
|
// metrics.
|
||||||
|
acc.DisablePrecision()
|
||||||
acc.setDefaultTags(a.Config.Tags)
|
acc.setDefaultTags(a.Config.Tags)
|
||||||
if err := p.Start(acc); err != nil {
|
if err := p.Start(acc); err != nil {
|
||||||
log.Printf("Service for input %s failed to start, exiting\n%s\n",
|
log.Printf("Service for input %s failed to start, exiting\n%s\n",
|
||||||
@@ -347,7 +334,6 @@ func (a *Agent) Run(shutdown chan struct{}) error {
|
|||||||
i := int64(a.Config.Agent.Interval.Duration)
|
i := int64(a.Config.Agent.Interval.Duration)
|
||||||
time.Sleep(time.Duration(i - (time.Now().UnixNano() % i)))
|
time.Sleep(time.Duration(i - (time.Now().UnixNano() % i)))
|
||||||
}
|
}
|
||||||
ticker := time.NewTicker(a.Config.Agent.Interval.Duration)
|
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
@@ -358,32 +344,21 @@ func (a *Agent) Run(shutdown chan struct{}) error {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
wg.Add(len(a.Config.Inputs))
|
||||||
for _, input := range a.Config.Inputs {
|
for _, input := range a.Config.Inputs {
|
||||||
// Special handling for inputs that have their own collection interval
|
interval := a.Config.Agent.Interval.Duration
|
||||||
// configured. Default intervals are handled below with gatherParallel
|
// overwrite global interval if this plugin has it's own.
|
||||||
if input.Config.Interval != 0 {
|
if input.Config.Interval != 0 {
|
||||||
wg.Add(1)
|
interval = input.Config.Interval
|
||||||
go func(input *internal_models.RunningInput) {
|
|
||||||
defer wg.Done()
|
|
||||||
if err := a.gatherSeparate(shutdown, input, metricC); err != nil {
|
|
||||||
log.Printf(err.Error())
|
|
||||||
}
|
|
||||||
}(input)
|
|
||||||
}
|
}
|
||||||
|
go func(in *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()
|
wg.Wait()
|
||||||
|
return nil
|
||||||
for {
|
|
||||||
if err := a.gatherParallel(metricC); err != nil {
|
|
||||||
log.Printf(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-shutdown:
|
|
||||||
return nil
|
|
||||||
case <-ticker.C:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package agent
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/influxdata/telegraf/internal/config"
|
"github.com/influxdata/telegraf/internal/config"
|
||||||
|
|
||||||
@@ -110,75 +109,3 @@ func TestAgent_LoadOutput(t *testing.T) {
|
|||||||
a, _ = NewAgent(c)
|
a, _ = NewAgent(c)
|
||||||
assert.Equal(t, 3, len(a.Config.Outputs))
|
assert.Equal(t, 3, len(a.Config.Outputs))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAgent_ZeroJitter(t *testing.T) {
|
|
||||||
flushinterval := jitterInterval(time.Duration(10*time.Second),
|
|
||||||
time.Duration(0*time.Second))
|
|
||||||
|
|
||||||
actual := flushinterval.Nanoseconds()
|
|
||||||
exp := time.Duration(10 * time.Second).Nanoseconds()
|
|
||||||
|
|
||||||
if actual != exp {
|
|
||||||
t.Errorf("Actual %v, expected %v", actual, exp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAgent_ZeroInterval(t *testing.T) {
|
|
||||||
min := time.Duration(500 * time.Millisecond).Nanoseconds()
|
|
||||||
max := time.Duration(5 * time.Second).Nanoseconds()
|
|
||||||
|
|
||||||
for i := 0; i < 1000; i++ {
|
|
||||||
flushinterval := jitterInterval(time.Duration(0*time.Second),
|
|
||||||
time.Duration(5*time.Second))
|
|
||||||
actual := flushinterval.Nanoseconds()
|
|
||||||
|
|
||||||
if actual > max {
|
|
||||||
t.Errorf("Didn't expect interval %d to be > %d", actual, max)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if actual < min {
|
|
||||||
t.Errorf("Didn't expect interval %d to be < %d", actual, min)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAgent_ZeroBoth(t *testing.T) {
|
|
||||||
flushinterval := jitterInterval(time.Duration(0*time.Second),
|
|
||||||
time.Duration(0*time.Second))
|
|
||||||
|
|
||||||
actual := flushinterval
|
|
||||||
exp := time.Duration(500 * time.Millisecond)
|
|
||||||
|
|
||||||
if actual != exp {
|
|
||||||
t.Errorf("Actual %v, expected %v", actual, exp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAgent_JitterMax(t *testing.T) {
|
|
||||||
max := time.Duration(32 * time.Second).Nanoseconds()
|
|
||||||
|
|
||||||
for i := 0; i < 1000; i++ {
|
|
||||||
flushinterval := jitterInterval(time.Duration(30*time.Second),
|
|
||||||
time.Duration(2*time.Second))
|
|
||||||
actual := flushinterval.Nanoseconds()
|
|
||||||
if actual > max {
|
|
||||||
t.Errorf("Didn't expect interval %d to be > %d", actual, max)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAgent_JitterMin(t *testing.T) {
|
|
||||||
min := time.Duration(30 * time.Second).Nanoseconds()
|
|
||||||
|
|
||||||
for i := 0; i < 1000; i++ {
|
|
||||||
flushinterval := jitterInterval(time.Duration(30*time.Second),
|
|
||||||
time.Duration(2*time.Second))
|
|
||||||
actual := flushinterval.Nanoseconds()
|
|
||||||
if actual < min {
|
|
||||||
t.Errorf("Didn't expect interval %d to be < %d", actual, min)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ machine:
|
|||||||
post:
|
post:
|
||||||
- sudo service zookeeper stop
|
- sudo service zookeeper stop
|
||||||
- go version
|
- go version
|
||||||
- go version | grep 1.6.1 || sudo rm -rf /usr/local/go
|
- go version | grep 1.7.1 || sudo rm -rf /usr/local/go
|
||||||
- wget https://storage.googleapis.com/golang/go1.6.1.linux-amd64.tar.gz
|
- wget https://storage.googleapis.com/golang/go1.7.1.linux-amd64.tar.gz
|
||||||
- sudo tar -C /usr/local -xzf go1.6.1.linux-amd64.tar.gz
|
- sudo tar -C /usr/local -xzf go1.7.1.linux-amd64.tar.gz
|
||||||
- go version
|
- go version
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ import (
|
|||||||
_ "github.com/influxdata/telegraf/plugins/inputs/all"
|
_ "github.com/influxdata/telegraf/plugins/inputs/all"
|
||||||
"github.com/influxdata/telegraf/plugins/outputs"
|
"github.com/influxdata/telegraf/plugins/outputs"
|
||||||
_ "github.com/influxdata/telegraf/plugins/outputs/all"
|
_ "github.com/influxdata/telegraf/plugins/outputs/all"
|
||||||
|
"github.com/kardianos/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
var fDebug = flag.Bool("debug", false,
|
var fDebug = flag.Bool("debug", false,
|
||||||
@@ -39,16 +41,26 @@ var fOutputList = flag.Bool("output-list", false,
|
|||||||
"print available output plugins.")
|
"print available output plugins.")
|
||||||
var fUsage = flag.String("usage", "",
|
var fUsage = flag.String("usage", "",
|
||||||
"print usage for a plugin, ie, 'telegraf -usage mysql'")
|
"print usage for a plugin, ie, 'telegraf -usage mysql'")
|
||||||
var fInputFiltersLegacy = flag.String("filter", "",
|
var fService = flag.String("service", "",
|
||||||
"filter the inputs to enable, separator is :")
|
"operate on the service")
|
||||||
var fOutputFiltersLegacy = flag.String("outputfilter", "",
|
|
||||||
"filter the outputs to enable, separator is :")
|
|
||||||
var fConfigDirectoryLegacy = flag.String("configdirectory", "",
|
|
||||||
"directory containing additional *.conf files")
|
|
||||||
|
|
||||||
// Telegraf version
|
// Telegraf version, populated linker.
|
||||||
// -ldflags "-X main.Version=`git describe --always --tags`"
|
// ie, -ldflags "-X main.version=`git describe --always --tags`"
|
||||||
var Version string
|
var (
|
||||||
|
version string
|
||||||
|
commit string
|
||||||
|
branch string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// If commit or branch are not set, make that clear.
|
||||||
|
if commit == "" {
|
||||||
|
commit = "unknown"
|
||||||
|
}
|
||||||
|
if branch == "" {
|
||||||
|
branch = "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const usage = `Telegraf, The plugin-driven server agent for collecting and reporting metrics.
|
const usage = `Telegraf, The plugin-driven server agent for collecting and reporting metrics.
|
||||||
|
|
||||||
@@ -70,6 +82,14 @@ The flags are:
|
|||||||
-debug print metrics as they're generated to stdout
|
-debug print metrics as they're generated to stdout
|
||||||
-quiet run in quiet mode
|
-quiet run in quiet mode
|
||||||
-version print the version to stdout
|
-version print the version to stdout
|
||||||
|
-service Control the service, ie, 'telegraf -service install (windows only)'
|
||||||
|
|
||||||
|
In addition to the -config flag, telegraf will also load the config file from
|
||||||
|
an environment variable or default location. Precedence is:
|
||||||
|
1. -config flag
|
||||||
|
2. $TELEGRAF_CONFIG_PATH environment variable
|
||||||
|
3. $HOME/.telegraf/telegraf.conf
|
||||||
|
4. /etc/telegraf/telegraf.conf
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
@@ -89,7 +109,22 @@ Examples:
|
|||||||
telegraf -config telegraf.conf -input-filter cpu:mem -output-filter influxdb
|
telegraf -config telegraf.conf -input-filter cpu:mem -output-filter influxdb
|
||||||
`
|
`
|
||||||
|
|
||||||
func main() {
|
var logger service.Logger
|
||||||
|
|
||||||
|
var stop chan struct{}
|
||||||
|
|
||||||
|
var srvc service.Service
|
||||||
|
var svcConfig *service.Config
|
||||||
|
|
||||||
|
type program struct{}
|
||||||
|
|
||||||
|
func reloadLoop(stop chan struct{}, s service.Service) {
|
||||||
|
defer func() {
|
||||||
|
if service.Interactive() {
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}()
|
||||||
reload := make(chan bool, 1)
|
reload := make(chan bool, 1)
|
||||||
reload <- true
|
reload <- true
|
||||||
for <-reload {
|
for <-reload {
|
||||||
@@ -98,25 +133,12 @@ func main() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
args := flag.Args()
|
args := flag.Args()
|
||||||
|
|
||||||
if flag.NFlag() == 0 && len(args) == 0 {
|
|
||||||
usageExit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
var inputFilters []string
|
var inputFilters []string
|
||||||
if *fInputFiltersLegacy != "" {
|
|
||||||
inputFilter := strings.TrimSpace(*fInputFiltersLegacy)
|
|
||||||
inputFilters = strings.Split(":"+inputFilter+":", ":")
|
|
||||||
}
|
|
||||||
if *fInputFilters != "" {
|
if *fInputFilters != "" {
|
||||||
inputFilter := strings.TrimSpace(*fInputFilters)
|
inputFilter := strings.TrimSpace(*fInputFilters)
|
||||||
inputFilters = strings.Split(":"+inputFilter+":", ":")
|
inputFilters = strings.Split(":"+inputFilter+":", ":")
|
||||||
}
|
}
|
||||||
|
|
||||||
var outputFilters []string
|
var outputFilters []string
|
||||||
if *fOutputFiltersLegacy != "" {
|
|
||||||
outputFilter := strings.TrimSpace(*fOutputFiltersLegacy)
|
|
||||||
outputFilters = strings.Split(":"+outputFilter+":", ":")
|
|
||||||
}
|
|
||||||
if *fOutputFilters != "" {
|
if *fOutputFilters != "" {
|
||||||
outputFilter := strings.TrimSpace(*fOutputFilters)
|
outputFilter := strings.TrimSpace(*fOutputFilters)
|
||||||
outputFilters = strings.Split(":"+outputFilter+":", ":")
|
outputFilters = strings.Split(":"+outputFilter+":", ":")
|
||||||
@@ -125,8 +147,7 @@ func main() {
|
|||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
switch args[0] {
|
switch args[0] {
|
||||||
case "version":
|
case "version":
|
||||||
v := fmt.Sprintf("Telegraf - Version %s", Version)
|
fmt.Printf("Telegraf v%s (git: %s %s)\n", version, branch, commit)
|
||||||
fmt.Println(v)
|
|
||||||
return
|
return
|
||||||
case "config":
|
case "config":
|
||||||
config.PrintSampleConfig(inputFilters, outputFilters)
|
config.PrintSampleConfig(inputFilters, outputFilters)
|
||||||
@@ -134,67 +155,55 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if *fOutputList {
|
// switch for flags which just do something and exit immediately
|
||||||
|
switch {
|
||||||
|
case *fOutputList:
|
||||||
fmt.Println("Available Output Plugins:")
|
fmt.Println("Available Output Plugins:")
|
||||||
for k, _ := range outputs.Outputs {
|
for k, _ := range outputs.Outputs {
|
||||||
fmt.Printf(" %s\n", k)
|
fmt.Printf(" %s\n", k)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
case *fInputList:
|
||||||
|
|
||||||
if *fInputList {
|
|
||||||
fmt.Println("Available Input Plugins:")
|
fmt.Println("Available Input Plugins:")
|
||||||
for k, _ := range inputs.Inputs {
|
for k, _ := range inputs.Inputs {
|
||||||
fmt.Printf(" %s\n", k)
|
fmt.Printf(" %s\n", k)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
case *fVersion:
|
||||||
|
v := fmt.Sprintf("Telegraf - version %s", version)
|
||||||
if *fVersion {
|
|
||||||
v := fmt.Sprintf("Telegraf - Version %s", Version)
|
|
||||||
fmt.Println(v)
|
fmt.Println(v)
|
||||||
return
|
return
|
||||||
}
|
case *fSampleConfig:
|
||||||
|
|
||||||
if *fSampleConfig {
|
|
||||||
config.PrintSampleConfig(inputFilters, outputFilters)
|
config.PrintSampleConfig(inputFilters, outputFilters)
|
||||||
return
|
return
|
||||||
}
|
case *fUsage != "":
|
||||||
|
|
||||||
if *fUsage != "" {
|
|
||||||
if err := config.PrintInputConfig(*fUsage); err != nil {
|
if err := config.PrintInputConfig(*fUsage); err != nil {
|
||||||
if err2 := config.PrintOutputConfig(*fUsage); err2 != nil {
|
if err2 := config.PrintOutputConfig(*fUsage); err2 != nil {
|
||||||
log.Fatalf("%s and %s", err, err2)
|
log.Fatalf("%s and %s", err, err2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
case *fService != "" && runtime.GOOS == "windows":
|
||||||
|
if *fConfig != "" {
|
||||||
var (
|
(*svcConfig).Arguments = []string{"-config", *fConfig}
|
||||||
c *config.Config
|
}
|
||||||
err error
|
err := service.Control(s, *fService)
|
||||||
)
|
|
||||||
|
|
||||||
if *fConfig != "" {
|
|
||||||
c = config.NewConfig()
|
|
||||||
c.OutputFilters = outputFilters
|
|
||||||
c.InputFilters = inputFilters
|
|
||||||
err = c.LoadConfig(*fConfig)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
} else {
|
return
|
||||||
fmt.Println("You must specify a config file. See telegraf --help")
|
}
|
||||||
|
|
||||||
|
// If no other options are specified, load the config file and run.
|
||||||
|
c := config.NewConfig()
|
||||||
|
c.OutputFilters = outputFilters
|
||||||
|
c.InputFilters = inputFilters
|
||||||
|
err := c.LoadConfig(*fConfig)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *fConfigDirectoryLegacy != "" {
|
|
||||||
err = c.LoadDirectory(*fConfigDirectoryLegacy)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if *fConfigDirectory != "" {
|
if *fConfigDirectory != "" {
|
||||||
err = c.LoadDirectory(*fConfigDirectory)
|
err = c.LoadDirectory(*fConfigDirectory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -238,19 +247,23 @@ func main() {
|
|||||||
signals := make(chan os.Signal)
|
signals := make(chan os.Signal)
|
||||||
signal.Notify(signals, os.Interrupt, syscall.SIGHUP)
|
signal.Notify(signals, os.Interrupt, syscall.SIGHUP)
|
||||||
go func() {
|
go func() {
|
||||||
sig := <-signals
|
select {
|
||||||
if sig == os.Interrupt {
|
case sig := <-signals:
|
||||||
close(shutdown)
|
if sig == os.Interrupt {
|
||||||
}
|
close(shutdown)
|
||||||
if sig == syscall.SIGHUP {
|
}
|
||||||
log.Printf("Reloading Telegraf config\n")
|
if sig == syscall.SIGHUP {
|
||||||
<-reload
|
log.Printf("Reloading Telegraf config\n")
|
||||||
reload <- true
|
<-reload
|
||||||
|
reload <- true
|
||||||
|
close(shutdown)
|
||||||
|
}
|
||||||
|
case <-stop:
|
||||||
close(shutdown)
|
close(shutdown)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
log.Printf("Starting Telegraf (version %s)\n", Version)
|
log.Printf("Starting Telegraf (version %s)\n", version)
|
||||||
log.Printf("Loaded outputs: %s", strings.Join(c.OutputNames(), " "))
|
log.Printf("Loaded outputs: %s", strings.Join(c.OutputNames(), " "))
|
||||||
log.Printf("Loaded inputs: %s", strings.Join(c.InputNames(), " "))
|
log.Printf("Loaded inputs: %s", strings.Join(c.InputNames(), " "))
|
||||||
log.Printf("Tags enabled: %s", c.ListTags())
|
log.Printf("Tags enabled: %s", c.ListTags())
|
||||||
@@ -274,3 +287,46 @@ func usageExit(rc int) {
|
|||||||
fmt.Println(usage)
|
fmt.Println(usage)
|
||||||
os.Exit(rc)
|
os.Exit(rc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *program) Start(s service.Service) error {
|
||||||
|
srvc = s
|
||||||
|
go p.run()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (p *program) run() {
|
||||||
|
stop = make(chan struct{})
|
||||||
|
reloadLoop(stop, srvc)
|
||||||
|
}
|
||||||
|
func (p *program) Stop(s service.Service) error {
|
||||||
|
close(stop)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
svcConfig = &service.Config{
|
||||||
|
Name: "telegraf",
|
||||||
|
DisplayName: "Telegraf Data Collector Service",
|
||||||
|
Description: "Collects data using a series of plugins and publishes it to" +
|
||||||
|
"another series of plugins.",
|
||||||
|
Arguments: []string{"-config", "C:\\Program Files\\Telegraf\\telegraf.conf"},
|
||||||
|
}
|
||||||
|
|
||||||
|
prg := &program{}
|
||||||
|
s, err := service.New(prg, svcConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
logger, err = s.Logger(nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
err = s.Run()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stop = make(chan struct{})
|
||||||
|
reloadLoop(stop, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,11 +3,20 @@
|
|||||||
## Generating a Configuration File
|
## Generating a Configuration File
|
||||||
|
|
||||||
A default Telegraf config file can be generated using the -sample-config flag:
|
A default Telegraf config file can be generated using the -sample-config flag:
|
||||||
`telegraf -sample-config > telegraf.conf`
|
|
||||||
|
```
|
||||||
|
telegraf -sample-config > telegraf.conf
|
||||||
|
```
|
||||||
|
|
||||||
To generate a file with specific inputs and outputs, you can use the
|
To generate a file with specific inputs and outputs, you can use the
|
||||||
-input-filter and -output-filter flags:
|
-input-filter and -output-filter flags:
|
||||||
`telegraf -sample-config -input-filter cpu:mem:net:swap -output-filter influxdb:kafka`
|
|
||||||
|
```
|
||||||
|
telegraf -sample-config -input-filter cpu:mem:net:swap -output-filter influxdb:kafka
|
||||||
|
```
|
||||||
|
|
||||||
|
You can see the latest config file with all available plugins here:
|
||||||
|
[telegraf.conf](https://github.com/influxdata/telegraf/blob/master/etc/telegraf.conf)
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
@@ -17,8 +26,8 @@ for numbers and booleans they should be plain (ie, $INT_VAR, $BOOL_VAR)
|
|||||||
|
|
||||||
## `[global_tags]` Configuration
|
## `[global_tags]` Configuration
|
||||||
|
|
||||||
Global tags can be specific in the `[global_tags]` section of the config file in
|
Global tags can be specified in the `[global_tags]` section of the config file
|
||||||
key="value" format. All metrics being gathered on this host will be tagged
|
in key="value" format. All metrics being gathered on this host will be tagged
|
||||||
with the tags specified here.
|
with the tags specified here.
|
||||||
|
|
||||||
## `[agent]` Configuration
|
## `[agent]` Configuration
|
||||||
@@ -29,8 +38,12 @@ config.
|
|||||||
* **interval**: Default data collection interval for all inputs
|
* **interval**: Default data collection interval for all inputs
|
||||||
* **round_interval**: Rounds collection interval to 'interval'
|
* **round_interval**: Rounds collection interval to 'interval'
|
||||||
ie, if interval="10s" then always collect on :00, :10, :20, etc.
|
ie, if interval="10s" then always collect on :00, :10, :20, etc.
|
||||||
|
* **metric_batch_size**: Telegraf will send metrics to output in batch of at
|
||||||
|
most metric_batch_size metrics.
|
||||||
* **metric_buffer_limit**: Telegraf will cache metric_buffer_limit metrics
|
* **metric_buffer_limit**: Telegraf will cache metric_buffer_limit metrics
|
||||||
for each output, and will flush this buffer on a successful write.
|
for each output, and will flush this buffer on a successful write.
|
||||||
|
This should be a multiple of metric_batch_size and could not be less
|
||||||
|
than 2 times metric_batch_size.
|
||||||
* **collection_jitter**: Collection jitter is used to jitter
|
* **collection_jitter**: Collection jitter is used to jitter
|
||||||
the collection by a random amount.
|
the collection by a random amount.
|
||||||
Each plugin will sleep for a random time within jitter before collecting.
|
Each plugin will sleep for a random time within jitter before collecting.
|
||||||
@@ -47,9 +60,39 @@ ie, a jitter of 5s and flush_interval 10s means flushes will happen every 10-15s
|
|||||||
* **quiet**: Run telegraf in quiet mode.
|
* **quiet**: Run telegraf in quiet mode.
|
||||||
* **hostname**: Override default hostname, if empty use os.Hostname().
|
* **hostname**: Override default hostname, if empty use os.Hostname().
|
||||||
|
|
||||||
## `[inputs.xxx]` Configuration
|
#### Measurement Filtering
|
||||||
|
|
||||||
There are some configuration options that are configurable per input:
|
Filters can be configured per input or output, see below for examples.
|
||||||
|
|
||||||
|
* **namepass**: An array of strings that is used to filter metrics generated by the
|
||||||
|
current input. Each string in the array is tested as a glob match against
|
||||||
|
measurement names and if it matches, the field is emitted.
|
||||||
|
* **namedrop**: The inverse of pass, if a measurement name matches, it is not emitted.
|
||||||
|
* **fieldpass**: An array of strings that is used to filter metrics generated by the
|
||||||
|
current input. Each string in the array is tested as a glob match against field names
|
||||||
|
and if it matches, the field is emitted. fieldpass is not available for outputs.
|
||||||
|
* **fielddrop**: The inverse of pass, if a field name matches, it is not emitted.
|
||||||
|
fielddrop is not available for outputs.
|
||||||
|
* **tagpass**: tag names and arrays of strings that are used to filter
|
||||||
|
measurements by the current input. Each string in the array is tested as a glob
|
||||||
|
match against the tag name, and if it matches the measurement is emitted.
|
||||||
|
* **tagdrop**: The inverse of tagpass. If a tag matches, the measurement is not
|
||||||
|
emitted. This is tested on measurements that have passed the tagpass test.
|
||||||
|
* **tagexclude**: tagexclude can be used to exclude a tag from measurement(s).
|
||||||
|
As opposed to tagdrop, which will drop an entire measurement based on it's
|
||||||
|
tags, tagexclude simply strips the given tag keys from the measurement. This
|
||||||
|
can be used on inputs & outputs, but it is _recommended_ to be used on inputs,
|
||||||
|
as it is more efficient to filter out tags at the ingestion point.
|
||||||
|
* **taginclude**: taginclude is the inverse of tagexclude. It will only include
|
||||||
|
the tag keys in the final measurement.
|
||||||
|
|
||||||
|
**NOTE** `tagpass` and `tagdrop` parameters must be defined at the _end_ of
|
||||||
|
the plugin definition, otherwise subsequent plugin config options will be
|
||||||
|
interpreted as part of the tagpass/tagdrop map.
|
||||||
|
|
||||||
|
## Input Configuration
|
||||||
|
|
||||||
|
Some configuration options are configurable per input:
|
||||||
|
|
||||||
* **name_override**: Override the base name of the measurement.
|
* **name_override**: Override the base name of the measurement.
|
||||||
(Default is the name of the input).
|
(Default is the name of the input).
|
||||||
@@ -60,24 +103,6 @@ There are some configuration options that are configurable per input:
|
|||||||
global interval, but if one particular input should be run less or more often,
|
global interval, but if one particular input should be run less or more often,
|
||||||
you can configure that here.
|
you can configure that here.
|
||||||
|
|
||||||
#### Input Filters
|
|
||||||
|
|
||||||
There are also filters that can be configured per input:
|
|
||||||
|
|
||||||
* **namepass**: An array of strings that is used to filter metrics generated by the
|
|
||||||
current input. Each string in the array is tested as a glob match against
|
|
||||||
measurement names and if it matches, the field is emitted.
|
|
||||||
* **namedrop**: The inverse of pass, if a measurement name matches, it is not emitted.
|
|
||||||
* **fieldpass**: An array of strings that is used to filter metrics generated by the
|
|
||||||
current input. Each string in the array is tested as a glob match against field names
|
|
||||||
and if it matches, the field is emitted.
|
|
||||||
* **fielddrop**: The inverse of pass, if a field name matches, it is not emitted.
|
|
||||||
* **tagpass**: tag names and arrays of strings that are used to filter
|
|
||||||
measurements by the current input. Each string in the array is tested as a glob
|
|
||||||
match against the tag name, and if it matches the measurement is emitted.
|
|
||||||
* **tagdrop**: The inverse of tagpass. If a tag matches, the measurement is not
|
|
||||||
emitted. This is tested on measurements that have passed the tagpass test.
|
|
||||||
|
|
||||||
#### Input Configuration Examples
|
#### Input Configuration Examples
|
||||||
|
|
||||||
This is a full working config that will output CPU data to an InfluxDB instance
|
This is a full working config that will output CPU data to an InfluxDB instance
|
||||||
@@ -108,6 +133,10 @@ fields which begin with `time_`.
|
|||||||
|
|
||||||
#### Input Config: tagpass and tagdrop
|
#### Input Config: tagpass and tagdrop
|
||||||
|
|
||||||
|
**NOTE** `tagpass` and `tagdrop` parameters must be defined at the _end_ of
|
||||||
|
the plugin definition, otherwise subsequent plugin config options will be
|
||||||
|
interpreted as part of the tagpass/tagdrop map.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[[inputs.cpu]]
|
[[inputs.cpu]]
|
||||||
percpu = true
|
percpu = true
|
||||||
@@ -155,6 +184,20 @@ fields which begin with `time_`.
|
|||||||
namepass = ["rest_client_*"]
|
namepass = ["rest_client_*"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Input Config: taginclude and tagexclude
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Only include the "cpu" tag in the measurements for the cpu plugin.
|
||||||
|
[[inputs.cpu]]
|
||||||
|
percpu = true
|
||||||
|
totalcpu = true
|
||||||
|
taginclude = ["cpu"]
|
||||||
|
|
||||||
|
# Exclude the "fstype" tag from the measurements for the disk plugin.
|
||||||
|
[[inputs.disk]]
|
||||||
|
tagexclude = ["fstype"]
|
||||||
|
```
|
||||||
|
|
||||||
#### Input config: prefix, suffix, and override
|
#### Input config: prefix, suffix, and override
|
||||||
|
|
||||||
This plugin will emit measurements with the name `cpu_total`
|
This plugin will emit measurements with the name `cpu_total`
|
||||||
@@ -180,6 +223,9 @@ This will emit measurements with the name `foobar`
|
|||||||
This plugin will emit measurements with two additional tags: `tag1=foo` and
|
This plugin will emit measurements with two additional tags: `tag1=foo` and
|
||||||
`tag2=bar`
|
`tag2=bar`
|
||||||
|
|
||||||
|
NOTE: Order matters, the `[inputs.cpu.tags]` table must be at the _end_ of the
|
||||||
|
plugin definition.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[[inputs.cpu]]
|
[[inputs.cpu]]
|
||||||
percpu = false
|
percpu = false
|
||||||
@@ -208,15 +254,12 @@ to avoid measurement collisions:
|
|||||||
fielddrop = ["cpu_time*"]
|
fielddrop = ["cpu_time*"]
|
||||||
```
|
```
|
||||||
|
|
||||||
## `[outputs.xxx]` Configuration
|
## Output Configuration
|
||||||
|
|
||||||
Telegraf also supports specifying multiple output sinks to send data to,
|
Telegraf also supports specifying multiple output sinks to send data to,
|
||||||
configuring each output sink is different, but examples can be
|
configuring each output sink is different, but examples can be
|
||||||
found by running `telegraf -sample-config`.
|
found by running `telegraf -sample-config`.
|
||||||
|
|
||||||
Outputs also support the same configurable options as inputs
|
|
||||||
(namepass, namedrop, tagpass, tagdrop)
|
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[[outputs.influxdb]]
|
[[outputs.influxdb]]
|
||||||
urls = [ "http://localhost:8086" ]
|
urls = [ "http://localhost:8086" ]
|
||||||
|
|||||||
@@ -75,14 +75,19 @@ metrics are parsed directly into Telegraf metrics.
|
|||||||
|
|
||||||
# JSON:
|
# JSON:
|
||||||
|
|
||||||
The JSON data format flattens JSON into metric _fields_. For example, this JSON:
|
The JSON data format flattens JSON into metric _fields_.
|
||||||
|
NOTE: Only numerical values are converted to fields, and they are converted
|
||||||
|
into a float. strings are ignored unless specified as a tag_key (see below).
|
||||||
|
|
||||||
|
So for example, this JSON:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"a": 5,
|
"a": 5,
|
||||||
"b": {
|
"b": {
|
||||||
"c": 6
|
"c": 6
|
||||||
}
|
},
|
||||||
|
"ignored": "I'm a string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -151,7 +156,12 @@ as the parsed metric.
|
|||||||
#### Value Configuration:
|
#### Value Configuration:
|
||||||
|
|
||||||
You **must** tell Telegraf what type of metric to collect by using the
|
You **must** tell Telegraf what type of metric to collect by using the
|
||||||
`data_type` configuration option.
|
`data_type` configuration option. Available options are:
|
||||||
|
|
||||||
|
1. integer
|
||||||
|
2. float or long
|
||||||
|
3. string
|
||||||
|
4. boolean
|
||||||
|
|
||||||
**Note:** It is also recommended that you set `name_override` to a measurement
|
**Note:** It is also recommended that you set `name_override` to a measurement
|
||||||
name that makes sense for your metric, otherwise it will just be set to the
|
name that makes sense for your metric, otherwise it will just be set to the
|
||||||
@@ -176,49 +186,59 @@ name of the plugin.
|
|||||||
# Graphite:
|
# Graphite:
|
||||||
|
|
||||||
The Graphite data format translates graphite _dot_ buckets directly into
|
The Graphite data format translates graphite _dot_ buckets directly into
|
||||||
telegraf measurement names, with a single value field, and without any tags. For
|
telegraf measurement names, with a single value field, and without any tags.
|
||||||
more advanced options, Telegraf supports specifying "templates" to translate
|
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.
|
graphite buckets into Telegraf metrics.
|
||||||
|
|
||||||
#### Separator:
|
Templates are of the form:
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
cpu.usage.idle 99
|
"host.mytag.mytag.measurement.measurement.field*"
|
||||||
=> cpu_usage_idle value=99
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 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
|
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
|
incoming metrics. So the following template:
|
||||||
parts of the graphite bucket to combine into the measurement name. It can have a
|
|
||||||
trailing `*` to indicate that the remainder of the metric should be used.
|
|
||||||
Other words are considered tag keys. So the following template:
|
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
templates = [
|
templates = [
|
||||||
"region.measurement*"
|
"region.region.measurement*"
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
would result in the following Graphite -> Telegraf transformation.
|
would result in the following Graphite -> Telegraf transformation.
|
||||||
|
|
||||||
```
|
```
|
||||||
us-west.cpu.load 100
|
us.west.cpu.load 100
|
||||||
=> cpu.load,region=us-west value=100
|
=> cpu.load,region=us.west value=100
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Field Templates:
|
#### Field Templates:
|
||||||
|
|
||||||
There is also a _field_ keyword, which can only be specified once.
|
|
||||||
The field keyword tells Telegraf to give the metric that field name.
|
The field keyword tells Telegraf to give the metric that field name.
|
||||||
So the following template:
|
So the following template:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
|
separator = "_"
|
||||||
templates = [
|
templates = [
|
||||||
"measurement.measurement.field.field.region"
|
"measurement.measurement.field.field.region"
|
||||||
]
|
]
|
||||||
@@ -227,24 +247,26 @@ templates = [
|
|||||||
would result in the following Graphite -> Telegraf transformation.
|
would result in the following Graphite -> Telegraf transformation.
|
||||||
|
|
||||||
```
|
```
|
||||||
cpu.usage.idle.percent.us-west 100
|
cpu.usage.idle.percent.eu-east 100
|
||||||
=> cpu_usage,region=us-west idle_percent=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
|
```toml
|
||||||
|
separator = "_"
|
||||||
templates = [
|
templates = [
|
||||||
"measurement.measurement.region.field*"
|
"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.eu-east.idle.percentage 100
|
||||||
=> cpu_usage,region=us-west idle_percentage=100
|
=> cpu_usage,region=eu-east idle_percentage=100
|
||||||
```
|
```
|
||||||
(This cannot be used in conjunction with "measurement*"!)
|
|
||||||
|
|
||||||
#### Filter Templates:
|
#### Filter Templates:
|
||||||
|
|
||||||
@@ -261,8 +283,8 @@ templates = [
|
|||||||
which would result in the following transformation:
|
which would result in the following transformation:
|
||||||
|
|
||||||
```
|
```
|
||||||
cpu.load.us-west 100
|
cpu.load.eu-east 100
|
||||||
=> cpu_load,region=us-west value=100
|
=> cpu_load,region=eu-east value=100
|
||||||
|
|
||||||
mem.cached.localhost 256
|
mem.cached.localhost 256
|
||||||
=> mem_cached,host=localhost value=256
|
=> mem_cached,host=localhost value=256
|
||||||
@@ -284,8 +306,8 @@ templates = [
|
|||||||
would result in the following Graphite -> Telegraf transformation.
|
would result in the following Graphite -> Telegraf transformation.
|
||||||
|
|
||||||
```
|
```
|
||||||
cpu.usage.idle.us-west 100
|
cpu.usage.idle.eu-east 100
|
||||||
=> cpu_usage,region=us-west,datacenter=1a idle=100
|
=> cpu_usage,region=eu-east,datacenter=1a idle=100
|
||||||
```
|
```
|
||||||
|
|
||||||
There are many more options available,
|
There are many more options available,
|
||||||
@@ -316,12 +338,12 @@ There are many more options available,
|
|||||||
## similar to the line protocol format. There can be only one default template.
|
## similar to the line protocol format. There can be only one default template.
|
||||||
## Templates support below format:
|
## Templates support below format:
|
||||||
## 1. filter + template
|
## 1. filter + template
|
||||||
## 2. filter + template + extra tag
|
## 2. filter + template + extra tag(s)
|
||||||
## 3. filter + template with field key
|
## 3. filter + template with field key
|
||||||
## 4. default template
|
## 4. default template
|
||||||
templates = [
|
templates = [
|
||||||
"*.app env.service.resource.measurement",
|
"*.app env.service.resource.measurement",
|
||||||
"stats.* .host.measurement* region=us-west,agent=sensu",
|
"stats.* .host.measurement* region=eu-east,agent=sensu",
|
||||||
"stats2.* .host.measurement.field",
|
"stats2.* .host.measurement.field",
|
||||||
"measurement*"
|
"measurement*"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
- github.com/hashicorp/go-msgpack [BSD LICENSE](https://github.com/hashicorp/go-msgpack/blob/master/LICENSE)
|
- github.com/hashicorp/go-msgpack [BSD LICENSE](https://github.com/hashicorp/go-msgpack/blob/master/LICENSE)
|
||||||
- github.com/hashicorp/raft [MPL LICENSE](https://github.com/hashicorp/raft/blob/master/LICENSE)
|
- github.com/hashicorp/raft [MPL LICENSE](https://github.com/hashicorp/raft/blob/master/LICENSE)
|
||||||
- github.com/hashicorp/raft-boltdb [MPL LICENSE](https://github.com/hashicorp/raft-boltdb/blob/master/LICENSE)
|
- github.com/hashicorp/raft-boltdb [MPL LICENSE](https://github.com/hashicorp/raft-boltdb/blob/master/LICENSE)
|
||||||
|
- github.com/kardianos/service [ZLIB LICENSE](https://github.com/kardianos/service/blob/master/LICENSE) (License not named but matches word for word with ZLib)
|
||||||
- github.com/lib/pq [MIT LICENSE](https://github.com/lib/pq/blob/master/LICENSE.md)
|
- github.com/lib/pq [MIT LICENSE](https://github.com/lib/pq/blob/master/LICENSE.md)
|
||||||
- github.com/matttproud/golang_protobuf_extensions [APACHE LICENSE](https://github.com/matttproud/golang_protobuf_extensions/blob/master/LICENSE)
|
- github.com/matttproud/golang_protobuf_extensions [APACHE LICENSE](https://github.com/matttproud/golang_protobuf_extensions/blob/master/LICENSE)
|
||||||
- github.com/naoina/go-stringutil [MIT LICENSE](https://github.com/naoina/go-stringutil/blob/master/LICENSE)
|
- github.com/naoina/go-stringutil [MIT LICENSE](https://github.com/naoina/go-stringutil/blob/master/LICENSE)
|
||||||
@@ -28,6 +29,5 @@
|
|||||||
- github.com/wvanbergen/kazoo-go [MIT LICENSE](https://github.com/wvanbergen/kazoo-go/blob/master/MIT-LICENSE)
|
- github.com/wvanbergen/kazoo-go [MIT LICENSE](https://github.com/wvanbergen/kazoo-go/blob/master/MIT-LICENSE)
|
||||||
- gopkg.in/dancannon/gorethink.v1 [APACHE LICENSE](https://github.com/dancannon/gorethink/blob/v1.1.2/LICENSE)
|
- gopkg.in/dancannon/gorethink.v1 [APACHE LICENSE](https://github.com/dancannon/gorethink/blob/v1.1.2/LICENSE)
|
||||||
- gopkg.in/mgo.v2 [BSD LICENSE](https://github.com/go-mgo/mgo/blob/v2/LICENSE)
|
- gopkg.in/mgo.v2 [BSD LICENSE](https://github.com/go-mgo/mgo/blob/v2/LICENSE)
|
||||||
- golang.org/x/crypto/* [BSD LICENSE](https://github.com/golang/crypto/blob/master/LICENSE)
|
- golang.org/x/crypto/ [BSD LICENSE](https://github.com/golang/crypto/blob/master/LICENSE)
|
||||||
- internal Glob function [MIT LICENSE](https://github.com/ryanuber/go-glob/blob/master/LICENSE)
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,36 +1,39 @@
|
|||||||
# Running Telegraf as a Windows Service
|
# Running Telegraf as a Windows Service
|
||||||
|
|
||||||
If you have tried to install Go binaries as Windows Services with the **sc.exe**
|
Telegraf natively supports running as a Windows Service. Outlined below is are
|
||||||
tool you may have seen that the service errors and stops running after a while.
|
the general steps to set it up.
|
||||||
|
|
||||||
**NSSM** (the Non-Sucking Service Manager) is a tool that helps you in a
|
1. Obtain the telegraf windows distribution
|
||||||
[number of scenarios](http://nssm.cc/scenarios) including running Go binaries
|
2. Create the directory `C:\Program Files\Telegraf` (if you install in a different
|
||||||
that were not specifically designed to run only in Windows platforms.
|
location simply specify the `-config` parameter with the desired location)
|
||||||
|
3. Place the telegraf.exe and the telegraf.conf config file into `C:\Program Files\Telegraf`
|
||||||
|
4. To install the service into the Windows Service Manager, run the following in PowerShell as an administrator (If necessary, you can wrap any spaces in the file paths in double quotes ""):
|
||||||
|
|
||||||
## NSSM Installation via Chocolatey
|
```
|
||||||
|
> C:\"Program Files"\Telegraf\telegraf.exe --service install
|
||||||
|
```
|
||||||
|
|
||||||
You can install [Chocolatey](https://chocolatey.org/) and [NSSM](http://nssm.cc/)
|
5. Edit the configuration file to meet your needs
|
||||||
with these commands
|
6. To check that it works, run:
|
||||||
|
|
||||||
```powershell
|
```
|
||||||
iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))
|
> C:\"Program Files"\Telegraf\telegraf.exe --config C:\"Program Files"\Telegraf\telegraf.conf --test
|
||||||
choco install -y nssm
|
```
|
||||||
```
|
|
||||||
|
|
||||||
## Installing Telegraf as a Windows Service with NSSM
|
7. To start collecting data, run:
|
||||||
|
|
||||||
You can download the latest Telegraf Windows binaries (still Experimental at
|
```
|
||||||
the moment) from [the Telegraf Github repo](https://github.com/influxdata/telegraf).
|
> net start telegraf
|
||||||
|
```
|
||||||
|
|
||||||
Then you can create a C:\telegraf folder, unzip the binary there and modify the
|
## Other supported operations
|
||||||
**telegraf.conf** sample to allocate the metrics you want to send to **InfluxDB**.
|
|
||||||
|
|
||||||
Once you have NSSM installed in your system, the process is quite straightforward.
|
Telegraf can manage its own service through the --service flag:
|
||||||
You only need to type this command in your Windows shell
|
|
||||||
|
|
||||||
```powershell
|
| Command | Effect |
|
||||||
nssm install Telegraf c:\telegraf\telegraf.exe -config c:\telegraf\telegraf.config
|
|------------------------------------|-------------------------------|
|
||||||
```
|
| `telegraf.exe --service install` | Install telegraf as a service |
|
||||||
|
| `telegraf.exe --service uninstall` | Remove the telegraf service |
|
||||||
|
| `telegraf.exe --service start` | Start the telegraf service |
|
||||||
|
| `telegraf.exe --service stop` | Stop the telegraf service |
|
||||||
|
|
||||||
And now your service will be installed in Windows and you will be able to start and
|
|
||||||
stop it gracefully
|
|
||||||
@@ -30,11 +30,13 @@
|
|||||||
## ie, if interval="10s" then always collect on :00, :10, :20, etc.
|
## ie, if interval="10s" then always collect on :00, :10, :20, etc.
|
||||||
round_interval = true
|
round_interval = true
|
||||||
|
|
||||||
## Telegraf will cache metric_buffer_limit metrics for each output, and will
|
## Telegraf will send metrics to outputs in batches of at
|
||||||
## flush this buffer on a successful write.
|
## most metric_batch_size metrics.
|
||||||
metric_buffer_limit = 1000
|
metric_batch_size = 1000
|
||||||
## Flush the buffer whenever full, regardless of flush_interval.
|
## For failed writes, telegraf will cache metric_buffer_limit metrics for each
|
||||||
flush_buffer_when_full = true
|
## output, and will flush this buffer on a successful write. Oldest metrics
|
||||||
|
## are dropped first when this buffer fills.
|
||||||
|
metric_buffer_limit = 10000
|
||||||
|
|
||||||
## Collection jitter is used to jitter the collection by a random amount.
|
## Collection jitter is used to jitter the collection by a random amount.
|
||||||
## Each plugin will sleep for a random time within jitter before collecting.
|
## Each plugin will sleep for a random time within jitter before collecting.
|
||||||
@@ -50,6 +52,11 @@
|
|||||||
## ie, a jitter of 5s and interval 10s means flushes will happen every 10-15s
|
## ie, a jitter of 5s and interval 10s means flushes will happen every 10-15s
|
||||||
flush_jitter = "0s"
|
flush_jitter = "0s"
|
||||||
|
|
||||||
|
## By default, precision will be set to the same timestamp order as the
|
||||||
|
## collection interval, with the maximum being 1s.
|
||||||
|
## Precision will NOT be used for service inputs, such as logparser and statsd.
|
||||||
|
## Valid values are "ns", "us" (or "µs"), "ms", "s".
|
||||||
|
precision = ""
|
||||||
## Run telegraf in debug mode
|
## Run telegraf in debug mode
|
||||||
debug = false
|
debug = false
|
||||||
## Run telegraf in quiet mode
|
## Run telegraf in quiet mode
|
||||||
@@ -73,11 +80,11 @@
|
|||||||
urls = ["http://localhost:8086"] # required
|
urls = ["http://localhost:8086"] # required
|
||||||
## The target database for metrics (telegraf will create it if not exists).
|
## The target database for metrics (telegraf will create it if not exists).
|
||||||
database = "telegraf" # required
|
database = "telegraf" # required
|
||||||
## Retention policy to write to.
|
|
||||||
retention_policy = "default"
|
## Retention policy to write to. Empty string writes to the default rp.
|
||||||
## Precision of writes, valid values are "ns", "us" (or "µs"), "ms", "s", "m", "h".
|
retention_policy = ""
|
||||||
## note: using "s" precision greatly improves InfluxDB compression.
|
## Write consistency (clusters only), can be: "any", "one", "quorum", "all"
|
||||||
precision = "s"
|
write_consistency = "any"
|
||||||
|
|
||||||
## Write timeout (for the InfluxDB client), formatted as a string.
|
## Write timeout (for the InfluxDB client), formatted as a string.
|
||||||
## If not provided, will default to 5s. 0s means no timeout (not recommended).
|
## If not provided, will default to 5s. 0s means no timeout (not recommended).
|
||||||
@@ -147,6 +154,21 @@
|
|||||||
# ## Amazon REGION
|
# ## Amazon REGION
|
||||||
# region = 'us-east-1'
|
# region = 'us-east-1'
|
||||||
#
|
#
|
||||||
|
# ## Amazon Credentials
|
||||||
|
# ## Credentials are loaded in the following order
|
||||||
|
# ## 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 for the CloudWatch MetricDatums
|
||||||
# namespace = 'InfluxData/Telegraf'
|
# namespace = 'InfluxData/Telegraf'
|
||||||
|
|
||||||
@@ -175,6 +197,8 @@
|
|||||||
# # Configuration for Graphite server to send metrics to
|
# # Configuration for Graphite server to send metrics to
|
||||||
# [[outputs.graphite]]
|
# [[outputs.graphite]]
|
||||||
# ## TCP endpoint for your graphite instance.
|
# ## TCP endpoint for your graphite instance.
|
||||||
|
# ## If multiple endpoints are configured, output will be load balanced.
|
||||||
|
# ## Only one of the endpoints will be written to with each iteration.
|
||||||
# servers = ["localhost:2003"]
|
# servers = ["localhost:2003"]
|
||||||
# ## Prefix metrics name
|
# ## Prefix metrics name
|
||||||
# prefix = ""
|
# prefix = ""
|
||||||
@@ -185,6 +209,27 @@
|
|||||||
# timeout = 2
|
# timeout = 2
|
||||||
|
|
||||||
|
|
||||||
|
# # Send telegraf metrics to graylog(s)
|
||||||
|
# [[outputs.graylog]]
|
||||||
|
# ## Udp endpoint for your graylog instance.
|
||||||
|
# servers = ["127.0.0.1:12201", "192.168.1.1:12201"]
|
||||||
|
|
||||||
|
|
||||||
|
# # Configuration for sending metrics to an Instrumental project
|
||||||
|
# [[outputs.instrumental]]
|
||||||
|
# ## Project API Token (required)
|
||||||
|
# api_token = "API Token" # required
|
||||||
|
# ## Prefix the metrics with a given name
|
||||||
|
# prefix = ""
|
||||||
|
# ## Stats output template (Graphite formatting)
|
||||||
|
# ## see https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md#graphite
|
||||||
|
# template = "host.tags.measurement.field"
|
||||||
|
# ## Timeout in seconds to connect
|
||||||
|
# timeout = "2s"
|
||||||
|
# ## Display Communcation to Instrumental
|
||||||
|
# debug = false
|
||||||
|
|
||||||
|
|
||||||
# # Configuration for the Kafka server to send metrics to
|
# # Configuration for the Kafka server to send metrics to
|
||||||
# [[outputs.kafka]]
|
# [[outputs.kafka]]
|
||||||
# ## URLs of kafka brokers
|
# ## URLs of kafka brokers
|
||||||
@@ -239,6 +284,22 @@
|
|||||||
# [[outputs.kinesis]]
|
# [[outputs.kinesis]]
|
||||||
# ## Amazon REGION of kinesis endpoint.
|
# ## Amazon REGION of kinesis endpoint.
|
||||||
# region = "ap-southeast-2"
|
# region = "ap-southeast-2"
|
||||||
|
#
|
||||||
|
# ## Amazon Credentials
|
||||||
|
# ## Credentials are loaded in the following order
|
||||||
|
# ## 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.
|
# ## Kinesis StreamName must exist prior to starting telegraf.
|
||||||
# streamname = "StreamName"
|
# streamname = "StreamName"
|
||||||
# ## PartitionKey as used for sharding data.
|
# ## PartitionKey as used for sharding data.
|
||||||
@@ -260,14 +321,13 @@
|
|||||||
# api_token = "my-secret-token" # required.
|
# api_token = "my-secret-token" # required.
|
||||||
# ## Debug
|
# ## Debug
|
||||||
# # debug = false
|
# # debug = false
|
||||||
# ## Tag Field to populate source attribute (optional)
|
|
||||||
# ## This is typically the _hostname_ from which the metric was obtained.
|
|
||||||
# source_tag = "host"
|
|
||||||
# ## Connection timeout.
|
# ## Connection timeout.
|
||||||
# # timeout = "5s"
|
# # timeout = "5s"
|
||||||
# ## Output Name Template (same as graphite buckets)
|
# ## Output source Template (same as graphite buckets)
|
||||||
# ## see https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md#graphite
|
# ## see https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md#graphite
|
||||||
# template = "host.tags.measurement.field"
|
# ## This template is used in librato's source (not metric's name)
|
||||||
|
# template = "host"
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
# # Configuration for MQTT server to send metrics to
|
# # Configuration for MQTT server to send metrics to
|
||||||
@@ -375,8 +435,8 @@
|
|||||||
## disk partitions.
|
## disk partitions.
|
||||||
## Setting devices will restrict the stats to the specified devices.
|
## Setting devices will restrict the stats to the specified devices.
|
||||||
# devices = ["sda", "sdb"]
|
# devices = ["sda", "sdb"]
|
||||||
## Uncomment the following line if you do not need disk serial numbers.
|
## Uncomment the following line if you need disk serial numbers.
|
||||||
# skip_serial_number = true
|
# skip_serial_number = false
|
||||||
|
|
||||||
|
|
||||||
# Get kernel statistics from /proc/stat
|
# Get kernel statistics from /proc/stat
|
||||||
@@ -404,7 +464,7 @@
|
|||||||
# no configuration
|
# no configuration
|
||||||
|
|
||||||
|
|
||||||
# # Read stats from an aerospike server
|
# # Read stats from aerospike server(s)
|
||||||
# [[inputs.aerospike]]
|
# [[inputs.aerospike]]
|
||||||
# ## Aerospike servers to connect to (with port)
|
# ## Aerospike servers to connect to (with port)
|
||||||
# ## This plugin will query all namespaces the aerospike
|
# ## This plugin will query all namespaces the aerospike
|
||||||
@@ -415,6 +475,7 @@
|
|||||||
# # Read Apache status information (mod_status)
|
# # Read Apache status information (mod_status)
|
||||||
# [[inputs.apache]]
|
# [[inputs.apache]]
|
||||||
# ## An array of Apache status URI to gather stats.
|
# ## An array of Apache status URI to gather stats.
|
||||||
|
# ## Default is "http://localhost/server-status?auto".
|
||||||
# urls = ["http://localhost/server-status?auto"]
|
# urls = ["http://localhost/server-status?auto"]
|
||||||
|
|
||||||
|
|
||||||
@@ -448,11 +509,81 @@
|
|||||||
# ]
|
# ]
|
||||||
|
|
||||||
|
|
||||||
|
# # Collects performance metrics from the MON and OSD nodes in a Ceph storage cluster.
|
||||||
|
# [[inputs.ceph]]
|
||||||
|
# ## This is the recommended interval to poll. Too frequent and you will lose
|
||||||
|
# ## data points due to timeouts during rebalancing and recovery
|
||||||
|
# interval = '1m'
|
||||||
|
#
|
||||||
|
# ## 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"
|
||||||
|
#
|
||||||
|
# ## Ceph user to authenticate as, ceph will search for the corresponding keyring
|
||||||
|
# ## e.g. client.admin.keyring in /etc/ceph, or the explicit path defined in the
|
||||||
|
# ## client section of ceph.conf for example:
|
||||||
|
# ##
|
||||||
|
# ## [client.telegraf]
|
||||||
|
# ## keyring = /etc/ceph/client.telegraf.keyring
|
||||||
|
# ##
|
||||||
|
# ## Consult the ceph documentation for more detail on keyring generation.
|
||||||
|
# ceph_user = "client.admin"
|
||||||
|
#
|
||||||
|
# ## Ceph configuration to use to locate the cluster
|
||||||
|
# ceph_config = "/etc/ceph/ceph.conf"
|
||||||
|
#
|
||||||
|
# ## Whether to gather statistics via the admin socket
|
||||||
|
# gather_admin_socket_stats = true
|
||||||
|
#
|
||||||
|
# ## Whether to gather statistics via ceph commands, requires ceph_user and ceph_config
|
||||||
|
# ## to be specified
|
||||||
|
# gather_cluster_stats = true
|
||||||
|
|
||||||
|
|
||||||
|
# # Read specific statistics per cgroup
|
||||||
|
# [[inputs.cgroup]]
|
||||||
|
# ## Directories in which to look for files, globs are supported.
|
||||||
|
# # paths = [
|
||||||
|
# # "/cgroup/memory",
|
||||||
|
# # "/cgroup/memory/child1",
|
||||||
|
# # "/cgroup/memory/child2/*",
|
||||||
|
# # ]
|
||||||
|
# ## cgroup stat fields, as file names, globs are supported.
|
||||||
|
# ## these file names are appended to each path from above.
|
||||||
|
# # files = ["memory.*usage*", "memory.limit_in_bytes"]
|
||||||
|
|
||||||
|
|
||||||
# # Pull Metric Statistics from Amazon CloudWatch
|
# # Pull Metric Statistics from Amazon CloudWatch
|
||||||
# [[inputs.cloudwatch]]
|
# [[inputs.cloudwatch]]
|
||||||
# ## Amazon Region
|
# ## Amazon Region
|
||||||
# region = 'us-east-1'
|
# region = 'us-east-1'
|
||||||
#
|
#
|
||||||
|
# ## Amazon Credentials
|
||||||
|
# ## Credentials are loaded in the following order
|
||||||
|
# ## 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)
|
# ## Requested CloudWatch aggregation Period (required - must be a multiple of 60s)
|
||||||
# period = '1m'
|
# period = '1m'
|
||||||
#
|
#
|
||||||
@@ -463,6 +594,10 @@
|
|||||||
# ## gaps or overlap in pulled data
|
# ## gaps or overlap in pulled data
|
||||||
# interval = '1m'
|
# interval = '1m'
|
||||||
#
|
#
|
||||||
|
# ## Configure the TTL for the internal cache of metrics.
|
||||||
|
# ## Defaults to 1 hr if not specified
|
||||||
|
# #cache_ttl = '10m'
|
||||||
|
#
|
||||||
# ## Metric Statistic Namespace (required)
|
# ## Metric Statistic Namespace (required)
|
||||||
# namespace = 'AWS/ELB'
|
# namespace = 'AWS/ELB'
|
||||||
#
|
#
|
||||||
@@ -478,6 +613,23 @@
|
|||||||
# # value = 'p-example'
|
# # value = 'p-example'
|
||||||
|
|
||||||
|
|
||||||
|
# # Gather health check statuses from services registered in Consul
|
||||||
|
# [[inputs.consul]]
|
||||||
|
# ## Most of these values defaults to the one configured on a Consul's agent level.
|
||||||
|
# ## Optional Consul server address (default: "localhost")
|
||||||
|
# # address = "localhost"
|
||||||
|
# ## Optional URI scheme for the Consul server (default: "http")
|
||||||
|
# # scheme = "http"
|
||||||
|
# ## Optional ACL token used in every request (default: "")
|
||||||
|
# # token = ""
|
||||||
|
# ## Optional username used for request HTTP Basic Authentication (default: "")
|
||||||
|
# # username = ""
|
||||||
|
# ## Optional password used for HTTP Basic Authentication (default: "")
|
||||||
|
# # password = ""
|
||||||
|
# ## Optional data centre to query the health checks from (default: "")
|
||||||
|
# # datacentre = ""
|
||||||
|
|
||||||
|
|
||||||
# # Read metrics from one or many couchbase clusters
|
# # Read metrics from one or many couchbase clusters
|
||||||
# [[inputs.couchbase]]
|
# [[inputs.couchbase]]
|
||||||
# ## specify servers via a url matching:
|
# ## specify servers via a url matching:
|
||||||
@@ -535,6 +687,15 @@
|
|||||||
# endpoint = "unix:///var/run/docker.sock"
|
# endpoint = "unix:///var/run/docker.sock"
|
||||||
# ## Only collect metrics for these containers, collect all if empty
|
# ## Only collect metrics for these containers, collect all if empty
|
||||||
# container_names = []
|
# container_names = []
|
||||||
|
# ## Timeout for docker list, info, and stats commands
|
||||||
|
# timeout = "5s"
|
||||||
|
#
|
||||||
|
# ## Whether to report for each container per-device blkio (8:0, 8:1...) and
|
||||||
|
# ## network (eth0, eth1, ...) stats or not
|
||||||
|
# perdevice = true
|
||||||
|
# ## Whether to report for each container total blkio and network stats or not
|
||||||
|
# total = false
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
# # Read statistics from one or many dovecot servers
|
# # Read statistics from one or many dovecot servers
|
||||||
@@ -563,12 +724,26 @@
|
|||||||
#
|
#
|
||||||
# ## set cluster_health to true when you want to also obtain cluster level stats
|
# ## set cluster_health to true when you want to also obtain cluster level stats
|
||||||
# cluster_health = false
|
# cluster_health = false
|
||||||
|
#
|
||||||
|
# ## 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 from one or more commands that can output to stdout
|
# # Read metrics from one or more commands that can output to stdout
|
||||||
# [[inputs.exec]]
|
# [[inputs.exec]]
|
||||||
# ## Commands array
|
# ## Commands array
|
||||||
# commands = ["/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)
|
# ## measurement name suffix (for separating different commands)
|
||||||
# name_suffix = "_mycollector"
|
# name_suffix = "_mycollector"
|
||||||
@@ -580,15 +755,70 @@
|
|||||||
# data_format = "influx"
|
# data_format = "influx"
|
||||||
|
|
||||||
|
|
||||||
|
# # Read stats about given file(s)
|
||||||
|
# [[inputs.filestat]]
|
||||||
|
# ## Files to gather stats about.
|
||||||
|
# ## These accept standard unix glob matching rules, but with the addition of
|
||||||
|
# ## ** as a "super asterisk". ie:
|
||||||
|
# ## "/var/log/**.log" -> recursively find all .log files in /var/log
|
||||||
|
# ## "/var/log/*/*.log" -> find all .log files with a parent dir in /var/log
|
||||||
|
# ## "/var/log/apache.log" -> just tail the apache log file
|
||||||
|
# ##
|
||||||
|
# ## See https://github.com/gobwas/glob for more examples
|
||||||
|
# ##
|
||||||
|
# files = ["/var/log/**.log"]
|
||||||
|
# ## If true, read the entire file and calculate an md5 checksum.
|
||||||
|
# md5 = false
|
||||||
|
|
||||||
|
|
||||||
|
# # Read flattened metrics from one or more GrayLog HTTP endpoints
|
||||||
|
# [[inputs.graylog]]
|
||||||
|
# ## API endpoint, currently supported API:
|
||||||
|
# ##
|
||||||
|
# ## - multiple (Ex http://<host>:12900/system/metrics/multiple)
|
||||||
|
# ## - namespace (Ex http://<host>:12900/system/metrics/namespace/{namespace})
|
||||||
|
# ##
|
||||||
|
# ## For namespace endpoint, the metrics array will be ignored for that call.
|
||||||
|
# ## Endpoint can contain namespace and multiple type calls.
|
||||||
|
# ##
|
||||||
|
# ## Please check http://[graylog-server-ip]:12900/api-browser for full list
|
||||||
|
# ## of endpoints
|
||||||
|
# servers = [
|
||||||
|
# "http://[graylog-server-ip]:12900/system/metrics/multiple",
|
||||||
|
# ]
|
||||||
|
#
|
||||||
|
# ## Metrics list
|
||||||
|
# ## List of metrics can be found on Graylog webservice documentation.
|
||||||
|
# ## Or by hitting the the web service api at:
|
||||||
|
# ## http://[graylog-host]:12900/system/metrics
|
||||||
|
# metrics = [
|
||||||
|
# "jvm.cl.loaded",
|
||||||
|
# "jvm.memory.pools.Metaspace.committed"
|
||||||
|
# ]
|
||||||
|
#
|
||||||
|
# ## Username and password
|
||||||
|
# username = ""
|
||||||
|
# password = ""
|
||||||
|
#
|
||||||
|
# ## Optional SSL Config
|
||||||
|
# # ssl_ca = "/etc/telegraf/ca.pem"
|
||||||
|
# # ssl_cert = "/etc/telegraf/cert.pem"
|
||||||
|
# # ssl_key = "/etc/telegraf/key.pem"
|
||||||
|
# ## Use SSL but skip chain & host verification
|
||||||
|
# # insecure_skip_verify = false
|
||||||
|
|
||||||
|
|
||||||
# # Read metrics of haproxy, via socket or csv stats page
|
# # Read metrics of haproxy, via socket or csv stats page
|
||||||
# [[inputs.haproxy]]
|
# [[inputs.haproxy]]
|
||||||
# ## An array of address to gather stats about. Specify an ip on hostname
|
# ## An array of address to gather stats about. Specify an ip on hostname
|
||||||
# ## with optional port. ie localhost, 10.10.3.33:1936, etc.
|
# ## with optional port. ie localhost, 10.10.3.33:1936, etc.
|
||||||
#
|
# ## Make sure you specify the complete path to the stats endpoint
|
||||||
# ## If no servers are specified, then default to 127.0.0.1:1936
|
# ## ie 10.10.3.33:1936/haproxy?stats
|
||||||
# servers = ["http://myhaproxy.com:1936", "http://anotherhaproxy.com:1936"]
|
# #
|
||||||
# ## Or you can also use local socket(not work yet)
|
# ## If no servers are specified, then default to 127.0.0.1:1936/haproxy?stats
|
||||||
# ## servers = ["socket://run/haproxy/admin.sock"]
|
# servers = ["http://myhaproxy.com:1936/haproxy?stats"]
|
||||||
|
# ## Or you can also use local socket
|
||||||
|
# ## servers = ["socket:/run/haproxy/admin.sock"]
|
||||||
|
|
||||||
|
|
||||||
# # HTTP/HTTPS request given an address a method and a timeout
|
# # HTTP/HTTPS request given an address a method and a timeout
|
||||||
@@ -596,7 +826,7 @@
|
|||||||
# ## Server address (default http://localhost)
|
# ## Server address (default http://localhost)
|
||||||
# address = "http://github.com"
|
# address = "http://github.com"
|
||||||
# ## Set response_timeout (default 5 seconds)
|
# ## Set response_timeout (default 5 seconds)
|
||||||
# response_timeout = 5
|
# response_timeout = "5s"
|
||||||
# ## HTTP Request Method
|
# ## HTTP Request Method
|
||||||
# method = "GET"
|
# method = "GET"
|
||||||
# ## Whether to follow redirects from the server (defaults to false)
|
# ## Whether to follow redirects from the server (defaults to false)
|
||||||
@@ -608,6 +838,13 @@
|
|||||||
# # body = '''
|
# # body = '''
|
||||||
# # {'fake':'data'}
|
# # {'fake':'data'}
|
||||||
# # '''
|
# # '''
|
||||||
|
#
|
||||||
|
# ## Optional SSL Config
|
||||||
|
# # ssl_ca = "/etc/telegraf/ca.pem"
|
||||||
|
# # ssl_cert = "/etc/telegraf/cert.pem"
|
||||||
|
# # ssl_key = "/etc/telegraf/key.pem"
|
||||||
|
# ## Use SSL but skip chain & host verification
|
||||||
|
# # insecure_skip_verify = false
|
||||||
|
|
||||||
|
|
||||||
# # Read flattened metrics from one or more JSON HTTP endpoints
|
# # Read flattened metrics from one or more JSON HTTP endpoints
|
||||||
@@ -658,6 +895,7 @@
|
|||||||
# ## See the influxdb plugin's README for more details.
|
# ## See the influxdb plugin's README for more details.
|
||||||
#
|
#
|
||||||
# ## Multiple URLs from which to read InfluxDB-formatted JSON
|
# ## Multiple URLs from which to read InfluxDB-formatted JSON
|
||||||
|
# ## Default is "http://localhost:8086/debug/vars".
|
||||||
# urls = [
|
# urls = [
|
||||||
# "http://localhost:8086/debug/vars"
|
# "http://localhost:8086/debug/vars"
|
||||||
# ]
|
# ]
|
||||||
@@ -672,17 +910,40 @@
|
|||||||
# ##
|
# ##
|
||||||
# servers = ["USERID:PASSW0RD@lan(192.168.1.1)"]
|
# servers = ["USERID:PASSW0RD@lan(192.168.1.1)"]
|
||||||
|
|
||||||
|
# # Gather packets and bytes throughput from iptables
|
||||||
|
# [[inputs.iptables]]
|
||||||
|
# ## iptables require root access on most systems.
|
||||||
|
# ## Setting 'use_sudo' to true will make use of sudo to run iptables.
|
||||||
|
# ## Users must configure sudo to allow telegraf user to run iptables.
|
||||||
|
# ## iptables can be restricted to only use list command "iptables -nvL"
|
||||||
|
# use_sudo = false
|
||||||
|
# ## define the table to monitor:
|
||||||
|
# table = "filter"
|
||||||
|
# ## Defines the chains to monitor:
|
||||||
|
# chains = [ "INPUT" ]
|
||||||
|
|
||||||
|
|
||||||
# # Read JMX metrics through Jolokia
|
# # Read JMX metrics through Jolokia
|
||||||
# [[inputs.jolokia]]
|
# [[inputs.jolokia]]
|
||||||
# ## This is the context root used to compose the jolokia url
|
# ## This is the context root used to compose the jolokia url
|
||||||
# context = "/jolokia/read"
|
# context = "/jolokia"
|
||||||
|
#
|
||||||
|
# ## This specifies the mode used
|
||||||
|
# # mode = "proxy"
|
||||||
|
# #
|
||||||
|
# ## When in proxy mode this section is used to specify further
|
||||||
|
# ## proxy address configurations.
|
||||||
|
# ## Remember to change host address to fit your environment.
|
||||||
|
# # [inputs.jolokia.proxy]
|
||||||
|
# # host = "127.0.0.1"
|
||||||
|
# # port = "8080"
|
||||||
|
#
|
||||||
#
|
#
|
||||||
# ## List of servers exposing jolokia read service
|
# ## List of servers exposing jolokia read service
|
||||||
# [[inputs.jolokia.servers]]
|
# [[inputs.jolokia.servers]]
|
||||||
# name = "stable"
|
# name = "as-server-01"
|
||||||
# host = "192.168.103.2"
|
# host = "127.0.0.1"
|
||||||
# port = "8180"
|
# port = "8080"
|
||||||
# # username = "myuser"
|
# # username = "myuser"
|
||||||
# # password = "mypassword"
|
# # password = "mypassword"
|
||||||
#
|
#
|
||||||
@@ -692,17 +953,20 @@
|
|||||||
# ## This collect all heap memory usage metrics.
|
# ## This collect all heap memory usage metrics.
|
||||||
# [[inputs.jolokia.metrics]]
|
# [[inputs.jolokia.metrics]]
|
||||||
# name = "heap_memory_usage"
|
# name = "heap_memory_usage"
|
||||||
# jmx = "/java.lang:type=Memory/HeapMemoryUsage"
|
# mbean = "java.lang:type=Memory"
|
||||||
|
# attribute = "HeapMemoryUsage"
|
||||||
#
|
#
|
||||||
# ## This collect thread counts metrics.
|
# ## This collect thread counts metrics.
|
||||||
# [[inputs.jolokia.metrics]]
|
# [[inputs.jolokia.metrics]]
|
||||||
# name = "thread_count"
|
# name = "thread_count"
|
||||||
# jmx = "/java.lang:type=Threading/TotalStartedThreadCount,ThreadCount,DaemonThreadCount,PeakThreadCount"
|
# mbean = "java.lang:type=Threading"
|
||||||
|
# attribute = "TotalStartedThreadCount,ThreadCount,DaemonThreadCount,PeakThreadCount"
|
||||||
#
|
#
|
||||||
# ## This collect number of class loaded/unloaded counts metrics.
|
# ## This collect number of class loaded/unloaded counts metrics.
|
||||||
# [[inputs.jolokia.metrics]]
|
# [[inputs.jolokia.metrics]]
|
||||||
# name = "class_count"
|
# name = "class_count"
|
||||||
# jmx = "/java.lang:type=ClassLoading/LoadedClassCount,UnloadedClassCount,TotalLoadedClassCount"
|
# mbean = "java.lang:type=ClassLoading"
|
||||||
|
# attribute = "LoadedClassCount,UnloadedClassCount,TotalLoadedClassCount"
|
||||||
|
|
||||||
|
|
||||||
# # Read metrics from a LeoFS Server via SNMP
|
# # Read metrics from a LeoFS Server via SNMP
|
||||||
@@ -719,9 +983,13 @@
|
|||||||
# ##
|
# ##
|
||||||
# # ost_procfiles = [
|
# # ost_procfiles = [
|
||||||
# # "/proc/fs/lustre/obdfilter/*/stats",
|
# # "/proc/fs/lustre/obdfilter/*/stats",
|
||||||
# # "/proc/fs/lustre/osd-ldiskfs/*/stats"
|
# # "/proc/fs/lustre/osd-ldiskfs/*/stats",
|
||||||
|
# # "/proc/fs/lustre/obdfilter/*/job_stats",
|
||||||
|
# # ]
|
||||||
|
# # mds_procfiles = [
|
||||||
|
# # "/proc/fs/lustre/mdt/*/md_stats",
|
||||||
|
# # "/proc/fs/lustre/mdt/*/job_stats",
|
||||||
# # ]
|
# # ]
|
||||||
# # mds_procfiles = ["/proc/fs/lustre/mdt/*/md_stats"]
|
|
||||||
|
|
||||||
|
|
||||||
# # Gathers metrics from the /3.0/reports MailChimp API
|
# # Gathers metrics from the /3.0/reports MailChimp API
|
||||||
@@ -746,21 +1014,35 @@
|
|||||||
|
|
||||||
# # Telegraf plugin for gathering metrics from N Mesos masters
|
# # Telegraf plugin for gathering metrics from N Mesos masters
|
||||||
# [[inputs.mesos]]
|
# [[inputs.mesos]]
|
||||||
# # Timeout, in ms.
|
# ## Timeout, in ms.
|
||||||
# timeout = 100
|
# timeout = 100
|
||||||
# # A list of Mesos masters, default value is localhost:5050.
|
# ## A list of Mesos masters.
|
||||||
# masters = ["localhost:5050"]
|
# masters = ["localhost:5050"]
|
||||||
# # Metrics groups to be collected, by default, all enabled.
|
# ## Master metrics groups to be collected, by default, all enabled.
|
||||||
# master_collections = [
|
# master_collections = [
|
||||||
# "resources",
|
# "resources",
|
||||||
# "master",
|
# "master",
|
||||||
# "system",
|
# "system",
|
||||||
# "slaves",
|
# "agents",
|
||||||
# "frameworks",
|
# "frameworks",
|
||||||
|
# "tasks",
|
||||||
# "messages",
|
# "messages",
|
||||||
# "evqueue",
|
# "evqueue",
|
||||||
# "registrar",
|
# "registrar",
|
||||||
# ]
|
# ]
|
||||||
|
# ## A list of Mesos slaves, default is []
|
||||||
|
# # slaves = []
|
||||||
|
# ## Slave metrics groups to be collected, by default, all enabled.
|
||||||
|
# # slave_collections = [
|
||||||
|
# # "resources",
|
||||||
|
# # "agent",
|
||||||
|
# # "system",
|
||||||
|
# # "executors",
|
||||||
|
# # "tasks",
|
||||||
|
# # "messages",
|
||||||
|
# # ]
|
||||||
|
# ## Include mesos tasks statistics, default is false
|
||||||
|
# # slave_tasks = true
|
||||||
|
|
||||||
|
|
||||||
# # Read metrics from one or many MongoDB servers
|
# # Read metrics from one or many MongoDB servers
|
||||||
@@ -771,6 +1053,7 @@
|
|||||||
# ## mongodb://10.10.3.33:18832,
|
# ## mongodb://10.10.3.33:18832,
|
||||||
# ## 10.0.0.1:10000, etc.
|
# ## 10.0.0.1:10000, etc.
|
||||||
# servers = ["127.0.0.1:27017"]
|
# servers = ["127.0.0.1:27017"]
|
||||||
|
# gather_perdb_stats = false
|
||||||
|
|
||||||
|
|
||||||
# # Read metrics from one or many mysql servers
|
# # Read metrics from one or many mysql servers
|
||||||
@@ -779,11 +1062,54 @@
|
|||||||
# ## [username[:password]@][protocol[(address)]]/[?tls=[true|false|skip-verify]]
|
# ## [username[:password]@][protocol[(address)]]/[?tls=[true|false|skip-verify]]
|
||||||
# ## see https://github.com/go-sql-driver/mysql#dsn-data-source-name
|
# ## see https://github.com/go-sql-driver/mysql#dsn-data-source-name
|
||||||
# ## e.g.
|
# ## e.g.
|
||||||
# ## root:passwd@tcp(127.0.0.1:3306)/?tls=false
|
# ## db_user:passwd@tcp(127.0.0.1:3306)/?tls=false
|
||||||
# ## root@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.
|
# ## If no servers are specified, then localhost is used as the host.
|
||||||
# servers = ["tcp(127.0.0.1:3306)/"]
|
# servers = ["tcp(127.0.0.1:3306)/"]
|
||||||
|
# ## the limits for metrics form perf_events_statements
|
||||||
|
# perf_events_statements_digest_text_limit = 120
|
||||||
|
# perf_events_statements_limit = 250
|
||||||
|
# perf_events_statements_time_limit = 86400
|
||||||
|
# #
|
||||||
|
# ## if the list is empty, then metrics are gathered from all databasee tables
|
||||||
|
# table_schema_databases = []
|
||||||
|
# #
|
||||||
|
# ## gather metrics from INFORMATION_SCHEMA.TABLES for databases provided above list
|
||||||
|
# gather_table_schema = false
|
||||||
|
# #
|
||||||
|
# ## gather thread state counts from INFORMATION_SCHEMA.PROCESSLIST
|
||||||
|
# gather_process_list = true
|
||||||
|
# #
|
||||||
|
# ## gather auto_increment columns and max values from information schema
|
||||||
|
# gather_info_schema_auto_inc = true
|
||||||
|
# #
|
||||||
|
# ## gather metrics from SHOW SLAVE STATUS command output
|
||||||
|
# gather_slave_status = true
|
||||||
|
# #
|
||||||
|
# ## gather metrics from SHOW BINARY LOGS command output
|
||||||
|
# gather_binary_logs = false
|
||||||
|
# #
|
||||||
|
# ## gather metrics from PERFORMANCE_SCHEMA.TABLE_IO_WAITS_SUMMART_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_index_io_waits = false
|
||||||
|
# #
|
||||||
|
# ## gather metrics from PERFORMANCE_SCHEMA.EVENT_WAITS
|
||||||
|
# gather_event_waits = false
|
||||||
|
# #
|
||||||
|
# ## gather metrics from PERFORMANCE_SCHEMA.FILE_SUMMARY_BY_EVENT_NAME
|
||||||
|
# gather_file_events_stats = false
|
||||||
|
# #
|
||||||
|
# ## gather metrics from PERFORMANCE_SCHEMA.EVENTS_STATEMENTS_SUMMARY_BY_DIGEST
|
||||||
|
# gather_perf_events_statements = false
|
||||||
|
# #
|
||||||
|
# ## Some queries we may want to run less often (such as SHOW GLOBAL VARIABLES)
|
||||||
|
# interval_slow = "30m"
|
||||||
|
|
||||||
|
|
||||||
# # Read metrics about network interface usage
|
# # Read metrics about network interface usage
|
||||||
@@ -801,14 +1127,15 @@
|
|||||||
# protocol = "tcp"
|
# protocol = "tcp"
|
||||||
# ## Server address (default localhost)
|
# ## Server address (default localhost)
|
||||||
# address = "github.com:80"
|
# address = "github.com:80"
|
||||||
# ## Set timeout (default 1.0 seconds)
|
# ## Set timeout
|
||||||
# timeout = 1.0
|
# timeout = "1s"
|
||||||
# ## Set read timeout (default 1.0 seconds)
|
#
|
||||||
# read_timeout = 1.0
|
|
||||||
# ## Optional string sent to the server
|
# ## Optional string sent to the server
|
||||||
# # send = "ssh"
|
# # send = "ssh"
|
||||||
# ## Optional expected string in answer
|
# ## Optional expected string in answer
|
||||||
# # expect = "ssh"
|
# # expect = "ssh"
|
||||||
|
# ## Set read timeout (only used if expecting a response)
|
||||||
|
# read_timeout = "1s"
|
||||||
|
|
||||||
|
|
||||||
# # Read TCP metrics such as established, time wait and sockets counts.
|
# # Read TCP metrics such as established, time wait and sockets counts.
|
||||||
@@ -828,6 +1155,18 @@
|
|||||||
# endpoints = ["http://localhost:4151"]
|
# endpoints = ["http://localhost:4151"]
|
||||||
|
|
||||||
|
|
||||||
|
# # Collect kernel snmp counters and network interface statistics
|
||||||
|
# [[inputs.nstat]]
|
||||||
|
# ## file paths for proc files. If empty default paths will be used:
|
||||||
|
# ## /proc/net/netstat, /proc/net/snmp, /proc/net/snmp6
|
||||||
|
# ## These can also be overridden with env variables, see README.
|
||||||
|
# proc_net_netstat = "/proc/net/netstat"
|
||||||
|
# proc_net_snmp = "/proc/net/snmp"
|
||||||
|
# proc_net_snmp6 = "/proc/net/snmp6"
|
||||||
|
# ## dump metrics with 0 values too
|
||||||
|
# dump_zeros = true
|
||||||
|
|
||||||
|
|
||||||
# # Get standard NTP query metrics, requires ntpq executable.
|
# # Get standard NTP query metrics, requires ntpq executable.
|
||||||
# [[inputs.ntpq]]
|
# [[inputs.ntpq]]
|
||||||
# ## If false, set the -n ntpq flag. Can reduce metric gather time.
|
# ## If false, set the -n ntpq flag. Can reduce metric gather time.
|
||||||
@@ -847,6 +1186,23 @@
|
|||||||
# command = "passenger-status -v --show=xml"
|
# command = "passenger-status -v --show=xml"
|
||||||
|
|
||||||
|
|
||||||
|
# # Read metrics from one or many pgbouncer servers
|
||||||
|
# [[inputs.pgbouncer]]
|
||||||
|
# ## specify address via a url matching:
|
||||||
|
# ## postgres://[pqgotest[:password]]@localhost:port[/dbname]\
|
||||||
|
# ## ?sslmode=[disable|verify-ca|verify-full]
|
||||||
|
# ## or a simple string:
|
||||||
|
# ## host=localhost user=pqotest port=6432 password=... sslmode=... dbname=pgbouncer
|
||||||
|
# ##
|
||||||
|
# ## All connection parameters are optional, except for dbname,
|
||||||
|
# ## you need to set it always as pgbouncer.
|
||||||
|
# address = "host=localhost user=postgres port=6432 sslmode=disable dbname=pgbouncer"
|
||||||
|
#
|
||||||
|
# ## A list of databases to pull metrics about. If not specified, metrics for all
|
||||||
|
# ## databases are gathered.
|
||||||
|
# # databases = ["app_production", "testing"]
|
||||||
|
|
||||||
|
|
||||||
# # Read metrics of phpfpm, via HTTP status page or socket
|
# # Read metrics of phpfpm, via HTTP status page or socket
|
||||||
# [[inputs.phpfpm]]
|
# [[inputs.phpfpm]]
|
||||||
# ## An array of addresses to gather stats about. Specify an ip or hostname
|
# ## An array of addresses to gather stats about. Specify an ip or hostname
|
||||||
@@ -875,15 +1231,15 @@
|
|||||||
# [[inputs.ping]]
|
# [[inputs.ping]]
|
||||||
# ## NOTE: this plugin forks the ping command. You may need to set capabilities
|
# ## NOTE: this plugin forks the ping command. You may need to set capabilities
|
||||||
# ## via setcap cap_net_raw+p /bin/ping
|
# ## via setcap cap_net_raw+p /bin/ping
|
||||||
#
|
# #
|
||||||
# ## urls to ping
|
# ## urls to ping
|
||||||
# urls = ["www.google.com"] # required
|
# urls = ["www.google.com"] # required
|
||||||
# ## number of pings to send (ping -c <COUNT>)
|
# ## number of pings to send per collection (ping -c <COUNT>)
|
||||||
# count = 1 # required
|
# count = 1 # required
|
||||||
# ## interval, in s, at which to ping. 0 == default (ping -i <PING_INTERVAL>)
|
# ## interval, in s, at which to ping. 0 == default (ping -i <PING_INTERVAL>)
|
||||||
# ping_interval = 0.0
|
# ping_interval = 0.0
|
||||||
# ## ping timeout, in s. 0 == no timeout (ping -t <TIMEOUT>)
|
# ## per-ping timeout, in s. 0 == no timeout (ping -W <TIMEOUT>)
|
||||||
# timeout = 0.0
|
# timeout = 1.0
|
||||||
# ## interface to send ping from (ping -I <INTERFACE>)
|
# ## interface to send ping from (ping -I <INTERFACE>)
|
||||||
# interface = ""
|
# interface = ""
|
||||||
|
|
||||||
@@ -929,6 +1285,11 @@
|
|||||||
# ## databases are gathered.
|
# ## databases are gathered.
|
||||||
# ## databases = ["app_production", "testing"]
|
# ## databases = ["app_production", "testing"]
|
||||||
# #
|
# #
|
||||||
|
# # outputaddress = "db01"
|
||||||
|
# ## A custom name for the database that will be used as the "server" tag in the
|
||||||
|
# ## measurement output. If not specified, a default one generated from
|
||||||
|
# ## the connection address is used.
|
||||||
|
# #
|
||||||
# ## Define the toml config where the sql queries are stored
|
# ## Define the toml config where the sql queries are stored
|
||||||
# ## New queries can be added, if the withdbname is set to true and there is no
|
# ## New queries can be added, if the withdbname is set to true and there is no
|
||||||
# ## databases defined in the 'databases field', the sql query is ended by a
|
# ## databases defined in the 'databases field', the sql query is ended by a
|
||||||
@@ -939,24 +1300,28 @@
|
|||||||
# ## because the databases variable was set to ['postgres', 'pgbench' ] and the
|
# ## because the databases variable was set to ['postgres', 'pgbench' ] and the
|
||||||
# ## withdbname was true. Be careful that if the withdbname is set to false you
|
# ## withdbname was true. Be careful that if the withdbname is set to false you
|
||||||
# ## don't have to define the where clause (aka with the dbname) the tagvalue
|
# ## don't have to define the where clause (aka with the dbname) the tagvalue
|
||||||
# ## field is used to define custom tags (separated by comas)
|
# ## field is used to define custom tags (separated by commas)
|
||||||
|
# ## The optional "measurement" value can be used to override the default
|
||||||
|
# ## output measurement name ("postgresql").
|
||||||
# #
|
# #
|
||||||
# ## Structure :
|
# ## Structure :
|
||||||
# ## [[inputs.postgresql_extensible.query]]
|
# ## [[inputs.postgresql_extensible.query]]
|
||||||
# ## sqlquery string
|
# ## sqlquery string
|
||||||
# ## version string
|
# ## version string
|
||||||
# ## withdbname boolean
|
# ## withdbname boolean
|
||||||
# ## tagvalue string (coma separated)
|
# ## tagvalue string (comma separated)
|
||||||
|
# ## measurement string
|
||||||
# [[inputs.postgresql_extensible.query]]
|
# [[inputs.postgresql_extensible.query]]
|
||||||
# sqlquery="SELECT * FROM pg_stat_database"
|
# sqlquery="SELECT * FROM pg_stat_database"
|
||||||
# version=901
|
# version=901
|
||||||
# withdbname=false
|
# withdbname=false
|
||||||
# tagvalue=""
|
# tagvalue=""
|
||||||
|
# measurement=""
|
||||||
# [[inputs.postgresql_extensible.query]]
|
# [[inputs.postgresql_extensible.query]]
|
||||||
# sqlquery="SELECT * FROM pg_stat_bgwriter"
|
# sqlquery="SELECT * FROM pg_stat_bgwriter"
|
||||||
# version=901
|
# version=901
|
||||||
# withdbname=false
|
# withdbname=false
|
||||||
# tagvalue=""
|
# tagvalue="postgresql.stats"
|
||||||
|
|
||||||
|
|
||||||
# # Read metrics from one or many PowerDNS servers
|
# # Read metrics from one or many PowerDNS servers
|
||||||
@@ -978,6 +1343,9 @@
|
|||||||
# ## user as argument for pgrep (ie, pgrep -u <user>)
|
# ## user as argument for pgrep (ie, pgrep -u <user>)
|
||||||
# # user = "nginx"
|
# # user = "nginx"
|
||||||
#
|
#
|
||||||
|
# ## override for process_name
|
||||||
|
# ## This is optional; default is sourced from /proc/<pid>/status
|
||||||
|
# # process_name = "bar"
|
||||||
# ## Field name prefix
|
# ## Field name prefix
|
||||||
# prefix = ""
|
# prefix = ""
|
||||||
# ## comment this out if you want raw cpu_time stats
|
# ## comment this out if you want raw cpu_time stats
|
||||||
@@ -989,10 +1357,15 @@
|
|||||||
# ## An array of urls to scrape metrics from.
|
# ## An array of urls to scrape metrics from.
|
||||||
# urls = ["http://localhost:9100/metrics"]
|
# urls = ["http://localhost:9100/metrics"]
|
||||||
#
|
#
|
||||||
# ## Use SSL but skip chain & host verification
|
|
||||||
# # insecure_skip_verify = false
|
|
||||||
# ## Use bearer token for authorization
|
# ## Use bearer token for authorization
|
||||||
# # bearer_token = /path/to/bearer/token
|
# # bearer_token = /path/to/bearer/token
|
||||||
|
#
|
||||||
|
# ## Optional SSL Config
|
||||||
|
# # ssl_ca = /path/to/cafile
|
||||||
|
# # ssl_cert = /path/to/certfile
|
||||||
|
# # ssl_key = /path/to/keyfile
|
||||||
|
# ## Use SSL but skip chain & host verification
|
||||||
|
# # insecure_skip_verify = false
|
||||||
|
|
||||||
|
|
||||||
# # Reads last_run_summary.yaml file and converts to measurments
|
# # Reads last_run_summary.yaml file and converts to measurments
|
||||||
@@ -1003,11 +1376,18 @@
|
|||||||
|
|
||||||
# # Read metrics from one or many RabbitMQ servers via the management API
|
# # Read metrics from one or many RabbitMQ servers via the management API
|
||||||
# [[inputs.rabbitmq]]
|
# [[inputs.rabbitmq]]
|
||||||
# url = "http://localhost:15672" # required
|
# # url = "http://localhost:15672"
|
||||||
# # name = "rmq-server-1" # optional tag
|
# # name = "rmq-server-1" # optional tag
|
||||||
# # username = "guest"
|
# # username = "guest"
|
||||||
# # password = "guest"
|
# # password = "guest"
|
||||||
#
|
#
|
||||||
|
# ## 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
|
||||||
|
#
|
||||||
# ## A list of nodes to pull metrics about. If not specified, metrics for
|
# ## A list of nodes to pull metrics about. If not specified, metrics for
|
||||||
# ## all nodes are gathered.
|
# ## all nodes are gathered.
|
||||||
# # nodes = ["rabbit@node1", "rabbit@node2"]
|
# # nodes = ["rabbit@node1", "rabbit@node2"]
|
||||||
@@ -1026,6 +1406,7 @@
|
|||||||
# ## e.g.
|
# ## e.g.
|
||||||
# ## tcp://localhost:6379
|
# ## tcp://localhost:6379
|
||||||
# ## tcp://:password@192.168.99.100
|
# ## tcp://:password@192.168.99.100
|
||||||
|
# ## unix:///var/run/redis.sock
|
||||||
# ##
|
# ##
|
||||||
# ## If no servers are specified, then localhost is used as the host.
|
# ## If no servers are specified, then localhost is used as the host.
|
||||||
# ## If no port is specified, 6379 is used
|
# ## If no port is specified, 6379 is used
|
||||||
@@ -1048,8 +1429,8 @@
|
|||||||
# servers = ["http://localhost:8098"]
|
# servers = ["http://localhost:8098"]
|
||||||
|
|
||||||
|
|
||||||
# # Reads oids value from one or many snmp agents
|
# # DEPRECATED! PLEASE USE inputs.snmp INSTEAD.
|
||||||
# [[inputs.snmp]]
|
# [[inputs.snmp_legacy]]
|
||||||
# ## Use 'oids.txt' file to translate oids to names
|
# ## Use 'oids.txt' file to translate oids to names
|
||||||
# ## To generate 'oids.txt' you need to run:
|
# ## To generate 'oids.txt' you need to run:
|
||||||
# ## snmptranslate -m all -Tz -On | sed -e 's/"//g' > /tmp/oids.txt
|
# ## snmptranslate -m all -Tz -On | sed -e 's/"//g' > /tmp/oids.txt
|
||||||
@@ -1179,18 +1560,30 @@
|
|||||||
# pools = ["redis_pool", "mc_pool"]
|
# 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]]
|
# [[inputs.zfs]]
|
||||||
# ## ZFS kstat path
|
# ## ZFS kstat path. Ignored on FreeBSD
|
||||||
# ## If not specified, then default is:
|
# ## If not specified, then default is:
|
||||||
# kstatPath = "/proc/spl/kstat/zfs"
|
# # kstatPath = "/proc/spl/kstat/zfs"
|
||||||
#
|
#
|
||||||
# ## By default, telegraf gather all zfs stats
|
# ## By default, telegraf gather all zfs stats
|
||||||
# ## If not specified, then default is:
|
# ## If not specified, then default is:
|
||||||
# kstatMetrics = ["arcstats", "zfetchstats", "vdev_cache_stats"]
|
# # kstatMetrics = ["arcstats", "zfetchstats", "vdev_cache_stats"]
|
||||||
#
|
#
|
||||||
# ## By default, don't gather zpool stats
|
# ## By default, don't gather zpool stats
|
||||||
# poolMetrics = false
|
# # poolMetrics = false
|
||||||
|
|
||||||
|
|
||||||
# # Reads 'mntr' stats from one or many zookeeper servers
|
# # Reads 'mntr' stats from one or many zookeeper servers
|
||||||
@@ -1208,12 +1601,6 @@
|
|||||||
# SERVICE INPUT PLUGINS #
|
# SERVICE INPUT PLUGINS #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
# # A Github Webhook Event collector
|
|
||||||
# [[inputs.github_webhooks]]
|
|
||||||
# ## Address and port to host Webhook listener on
|
|
||||||
# service_address = ":1618"
|
|
||||||
|
|
||||||
|
|
||||||
# # Read metrics from Kafka topic(s)
|
# # Read metrics from Kafka topic(s)
|
||||||
# [[inputs.kafka_consumer]]
|
# [[inputs.kafka_consumer]]
|
||||||
# ## topic(s) to consume
|
# ## topic(s) to consume
|
||||||
@@ -1221,7 +1608,7 @@
|
|||||||
# ## an array of Zookeeper connection strings
|
# ## an array of Zookeeper connection strings
|
||||||
# zookeeper_peers = ["localhost:2181"]
|
# zookeeper_peers = ["localhost:2181"]
|
||||||
# ## Zookeeper Chroot
|
# ## Zookeeper Chroot
|
||||||
# zookeeper_chroot = "/"
|
# zookeeper_chroot = ""
|
||||||
# ## the name of the consumer group
|
# ## the name of the consumer group
|
||||||
# consumer_group = "telegraf_metrics_consumers"
|
# consumer_group = "telegraf_metrics_consumers"
|
||||||
# ## Offset (must be either "oldest" or "newest")
|
# ## Offset (must be either "oldest" or "newest")
|
||||||
@@ -1234,6 +1621,37 @@
|
|||||||
# data_format = "influx"
|
# data_format = "influx"
|
||||||
|
|
||||||
|
|
||||||
|
# # Stream and parse log file(s).
|
||||||
|
# [[inputs.logparser]]
|
||||||
|
# ## Log files to parse.
|
||||||
|
# ## These accept standard unix glob matching rules, but with the addition of
|
||||||
|
# ## ** as a "super asterisk". ie:
|
||||||
|
# ## /var/log/**.log -> recursively find all .log files in /var/log
|
||||||
|
# ## /var/log/*/*.log -> find all .log files with a parent dir in /var/log
|
||||||
|
# ## /var/log/apache.log -> only tail the apache log file
|
||||||
|
# files = ["/var/log/apache/access.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 = ["%{COMBINED_LOG_FORMAT}"]
|
||||||
|
# ## Name of the outputted measurement name.
|
||||||
|
# measurement = "apache_access_log"
|
||||||
|
# ## Full path(s) to custom pattern files.
|
||||||
|
# custom_pattern_files = []
|
||||||
|
# ## Custom patterns can also be defined here. Put one pattern per line.
|
||||||
|
# custom_patterns = '''
|
||||||
|
# '''
|
||||||
|
|
||||||
|
|
||||||
# # Read metrics from MQTT topic(s)
|
# # Read metrics from MQTT topic(s)
|
||||||
# [[inputs.mqtt_consumer]]
|
# [[inputs.mqtt_consumer]]
|
||||||
# servers = ["localhost:1883"]
|
# servers = ["localhost:1883"]
|
||||||
@@ -1290,6 +1708,21 @@
|
|||||||
# data_format = "influx"
|
# data_format = "influx"
|
||||||
|
|
||||||
|
|
||||||
|
# # Read NSQ topic for metrics.
|
||||||
|
# [[inputs.nsq_consumer]]
|
||||||
|
# ## An string representing the NSQD TCP Endpoint
|
||||||
|
# server = "localhost:4150"
|
||||||
|
# topic = "telegraf"
|
||||||
|
# channel = "consumer"
|
||||||
|
# max_in_flight = 100
|
||||||
|
#
|
||||||
|
# ## Data format to consume.
|
||||||
|
# ## Each data format has it's own unique set of configuration options, read
|
||||||
|
# ## more about them here:
|
||||||
|
# ## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md
|
||||||
|
# data_format = "influx"
|
||||||
|
|
||||||
|
|
||||||
# # Statsd Server
|
# # Statsd Server
|
||||||
# [[inputs.statsd]]
|
# [[inputs.statsd]]
|
||||||
# ## Address and port to host UDP listener on
|
# ## Address and port to host UDP listener on
|
||||||
@@ -1328,6 +1761,28 @@
|
|||||||
# percentile_limit = 1000
|
# percentile_limit = 1000
|
||||||
|
|
||||||
|
|
||||||
|
# # Stream a log file, like the tail -f command
|
||||||
|
# [[inputs.tail]]
|
||||||
|
# ## files to tail.
|
||||||
|
# ## These accept standard unix glob matching rules, but with the addition of
|
||||||
|
# ## ** as a "super asterisk". ie:
|
||||||
|
# ## "/var/log/**.log" -> recursively find all .log files in /var/log
|
||||||
|
# ## "/var/log/*/*.log" -> find all .log files with a parent dir in /var/log
|
||||||
|
# ## "/var/log/apache.log" -> just tail the apache log file
|
||||||
|
# ##
|
||||||
|
# ## See https://github.com/gobwas/glob for more examples
|
||||||
|
# ##
|
||||||
|
# files = ["/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:
|
||||||
|
# ## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md
|
||||||
|
# data_format = "influx"
|
||||||
|
|
||||||
|
|
||||||
# # Generic TCP listener
|
# # Generic TCP listener
|
||||||
# [[inputs.tcp_listener]]
|
# [[inputs.tcp_listener]]
|
||||||
# ## Address and port to host TCP listener on
|
# ## Address and port to host TCP listener on
|
||||||
@@ -1362,3 +1817,18 @@
|
|||||||
# ## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md
|
# ## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md
|
||||||
# data_format = "influx"
|
# data_format = "influx"
|
||||||
|
|
||||||
|
|
||||||
|
# # A Webhooks Event collector
|
||||||
|
# [[inputs.webhooks]]
|
||||||
|
# ## Address and port to host Webhook listener on
|
||||||
|
# service_address = ":1619"
|
||||||
|
#
|
||||||
|
# [inputs.webhooks.github]
|
||||||
|
# path = "/github"
|
||||||
|
#
|
||||||
|
# [inputs.webhooks.mandrill]
|
||||||
|
# path = "/mandrill"
|
||||||
|
#
|
||||||
|
# [inputs.webhooks.rollbar]
|
||||||
|
# path = "/rollbar"
|
||||||
|
|
||||||
|
|||||||
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile 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, _ := Compile([]string{"cpu", "mem", "net*"})
|
||||||
|
// f.Match("cpu") // true
|
||||||
|
// f.Match("network") // true
|
||||||
|
// f.Match("memory") // false
|
||||||
|
//
|
||||||
|
func Compile(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 TestCompile(t *testing.T) {
|
||||||
|
f, err := Compile([]string{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Nil(t, f)
|
||||||
|
|
||||||
|
f, err = Compile([]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 = Compile([]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 = Compile([]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 = Compile([]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, _ := Compile([]string{"cpu"})
|
||||||
|
var tmp bool
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
tmp = f.Match("network")
|
||||||
|
}
|
||||||
|
benchbool = tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkFilterSingleNoGlobTrue(b *testing.B) {
|
||||||
|
f, _ := Compile([]string{"cpu"})
|
||||||
|
var tmp bool
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
tmp = f.Match("cpu")
|
||||||
|
}
|
||||||
|
benchbool = tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkFilter(b *testing.B) {
|
||||||
|
f, _ := Compile([]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, _ := Compile([]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, _ := Compile([]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, _ := Compile([]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
|
||||||
|
}
|
||||||
77
internal/buffer/buffer.go
Normal file
77
internal/buffer/buffer.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package buffer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Buffer is an object for storing metrics in a circular buffer.
|
||||||
|
type Buffer struct {
|
||||||
|
buf chan telegraf.Metric
|
||||||
|
// total dropped metrics
|
||||||
|
drops int
|
||||||
|
// total metrics added
|
||||||
|
total int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBuffer returns a Buffer
|
||||||
|
// size is the maximum number of metrics that Buffer will cache. If Add is
|
||||||
|
// called when the buffer is full, then the oldest metric(s) will be dropped.
|
||||||
|
func NewBuffer(size int) *Buffer {
|
||||||
|
return &Buffer{
|
||||||
|
buf: make(chan telegraf.Metric, size),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty returns true if Buffer is empty.
|
||||||
|
func (b *Buffer) IsEmpty() bool {
|
||||||
|
return len(b.buf) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the current length of the buffer.
|
||||||
|
func (b *Buffer) Len() int {
|
||||||
|
return len(b.buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drops returns the total number of dropped metrics that have occured in this
|
||||||
|
// buffer since instantiation.
|
||||||
|
func (b *Buffer) Drops() int {
|
||||||
|
return b.drops
|
||||||
|
}
|
||||||
|
|
||||||
|
// Total returns the total number of metrics that have been added to this buffer.
|
||||||
|
func (b *Buffer) Total() int {
|
||||||
|
return b.total
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds metrics to the buffer.
|
||||||
|
func (b *Buffer) Add(metrics ...telegraf.Metric) {
|
||||||
|
for i, _ := range metrics {
|
||||||
|
b.total++
|
||||||
|
select {
|
||||||
|
case b.buf <- metrics[i]:
|
||||||
|
default:
|
||||||
|
b.drops++
|
||||||
|
<-b.buf
|
||||||
|
b.buf <- metrics[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batch returns a batch of metrics of size batchSize.
|
||||||
|
// the batch will be of maximum length batchSize. It can be less than batchSize,
|
||||||
|
// if the length of Buffer is less than batchSize.
|
||||||
|
func (b *Buffer) Batch(batchSize int) []telegraf.Metric {
|
||||||
|
n := min(len(b.buf), batchSize)
|
||||||
|
out := make([]telegraf.Metric, n)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
out[i] = <-b.buf
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func min(a, b int) int {
|
||||||
|
if b < a {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
94
internal/buffer/buffer_test.go
Normal file
94
internal/buffer/buffer_test.go
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
package buffer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/testutil"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var metricList = []telegraf.Metric{
|
||||||
|
testutil.TestMetric(2, "mymetric1"),
|
||||||
|
testutil.TestMetric(1, "mymetric2"),
|
||||||
|
testutil.TestMetric(11, "mymetric3"),
|
||||||
|
testutil.TestMetric(15, "mymetric4"),
|
||||||
|
testutil.TestMetric(8, "mymetric5"),
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAddMetrics(b *testing.B) {
|
||||||
|
buf := NewBuffer(10000)
|
||||||
|
m := testutil.TestMetric(1, "mymetric")
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
buf.Add(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewBufferBasicFuncs(t *testing.T) {
|
||||||
|
b := NewBuffer(10)
|
||||||
|
|
||||||
|
assert.True(t, b.IsEmpty())
|
||||||
|
assert.Zero(t, b.Len())
|
||||||
|
assert.Zero(t, b.Drops())
|
||||||
|
assert.Zero(t, b.Total())
|
||||||
|
|
||||||
|
m := testutil.TestMetric(1, "mymetric")
|
||||||
|
b.Add(m)
|
||||||
|
assert.False(t, b.IsEmpty())
|
||||||
|
assert.Equal(t, b.Len(), 1)
|
||||||
|
assert.Equal(t, b.Drops(), 0)
|
||||||
|
assert.Equal(t, b.Total(), 1)
|
||||||
|
|
||||||
|
b.Add(metricList...)
|
||||||
|
assert.False(t, b.IsEmpty())
|
||||||
|
assert.Equal(t, b.Len(), 6)
|
||||||
|
assert.Equal(t, b.Drops(), 0)
|
||||||
|
assert.Equal(t, b.Total(), 6)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDroppingMetrics(t *testing.T) {
|
||||||
|
b := NewBuffer(10)
|
||||||
|
|
||||||
|
// Add up to the size of the buffer
|
||||||
|
b.Add(metricList...)
|
||||||
|
b.Add(metricList...)
|
||||||
|
assert.False(t, b.IsEmpty())
|
||||||
|
assert.Equal(t, b.Len(), 10)
|
||||||
|
assert.Equal(t, b.Drops(), 0)
|
||||||
|
assert.Equal(t, b.Total(), 10)
|
||||||
|
|
||||||
|
// Add 5 more and verify they were dropped
|
||||||
|
b.Add(metricList...)
|
||||||
|
assert.False(t, b.IsEmpty())
|
||||||
|
assert.Equal(t, b.Len(), 10)
|
||||||
|
assert.Equal(t, b.Drops(), 5)
|
||||||
|
assert.Equal(t, b.Total(), 15)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGettingBatches(t *testing.T) {
|
||||||
|
b := NewBuffer(20)
|
||||||
|
|
||||||
|
// Verify that the buffer returned is smaller than requested when there are
|
||||||
|
// not as many items as requested.
|
||||||
|
b.Add(metricList...)
|
||||||
|
batch := b.Batch(10)
|
||||||
|
assert.Len(t, batch, 5)
|
||||||
|
|
||||||
|
// Verify that the buffer is now empty
|
||||||
|
assert.True(t, b.IsEmpty())
|
||||||
|
assert.Zero(t, b.Len())
|
||||||
|
assert.Zero(t, b.Drops())
|
||||||
|
assert.Equal(t, b.Total(), 5)
|
||||||
|
|
||||||
|
// Verify that the buffer returned is not more than the size requested
|
||||||
|
b.Add(metricList...)
|
||||||
|
batch = b.Batch(3)
|
||||||
|
assert.Len(t, batch, 3)
|
||||||
|
|
||||||
|
// Verify that buffer is not empty
|
||||||
|
assert.False(t, b.IsEmpty())
|
||||||
|
assert.Equal(t, b.Len(), 2)
|
||||||
|
assert.Equal(t, b.Drops(), 0)
|
||||||
|
assert.Equal(t, b.Total(), 10)
|
||||||
|
}
|
||||||
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)
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -47,8 +48,8 @@ type Config struct {
|
|||||||
OutputFilters []string
|
OutputFilters []string
|
||||||
|
|
||||||
Agent *AgentConfig
|
Agent *AgentConfig
|
||||||
Inputs []*internal_models.RunningInput
|
Inputs []*models.RunningInput
|
||||||
Outputs []*internal_models.RunningOutput
|
Outputs []*models.RunningOutput
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfig() *Config {
|
func NewConfig() *Config {
|
||||||
@@ -58,12 +59,11 @@ func NewConfig() *Config {
|
|||||||
Interval: internal.Duration{Duration: 10 * time.Second},
|
Interval: internal.Duration{Duration: 10 * time.Second},
|
||||||
RoundInterval: true,
|
RoundInterval: true,
|
||||||
FlushInterval: internal.Duration{Duration: 10 * time.Second},
|
FlushInterval: internal.Duration{Duration: 10 * time.Second},
|
||||||
FlushJitter: internal.Duration{Duration: 5 * time.Second},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Tags: make(map[string]string),
|
Tags: make(map[string]string),
|
||||||
Inputs: make([]*internal_models.RunningInput, 0),
|
Inputs: make([]*models.RunningInput, 0),
|
||||||
Outputs: make([]*internal_models.RunningOutput, 0),
|
Outputs: make([]*models.RunningOutput, 0),
|
||||||
InputFilters: make([]string, 0),
|
InputFilters: make([]string, 0),
|
||||||
OutputFilters: make([]string, 0),
|
OutputFilters: make([]string, 0),
|
||||||
}
|
}
|
||||||
@@ -78,6 +78,14 @@ type AgentConfig struct {
|
|||||||
// ie, if Interval=10s then always collect on :00, :10, :20, etc.
|
// ie, if Interval=10s then always collect on :00, :10, :20, etc.
|
||||||
RoundInterval bool
|
RoundInterval bool
|
||||||
|
|
||||||
|
// By default, precision will be set to the same timestamp order as the
|
||||||
|
// collection interval, with the maximum being 1s.
|
||||||
|
// ie, when interval = "10s", precision will be "1s"
|
||||||
|
// when interval = "250ms", precision will be "1ms"
|
||||||
|
// Precision will NOT be used for service inputs. It is up to each individual
|
||||||
|
// service input to set the timestamp at the appropriate precision.
|
||||||
|
Precision internal.Duration
|
||||||
|
|
||||||
// CollectionJitter is used to jitter the collection by a random amount.
|
// CollectionJitter is used to jitter the collection by a random amount.
|
||||||
// Each plugin will sleep for a random time within jitter before collecting.
|
// Each plugin will sleep for a random time within jitter before collecting.
|
||||||
// This can be used to avoid many plugins querying things like sysfs at the
|
// This can be used to avoid many plugins querying things like sysfs at the
|
||||||
@@ -93,9 +101,15 @@ type AgentConfig struct {
|
|||||||
// ie, a jitter of 5s and interval 10s means flushes will happen every 10-15s
|
// ie, a jitter of 5s and interval 10s means flushes will happen every 10-15s
|
||||||
FlushJitter internal.Duration
|
FlushJitter internal.Duration
|
||||||
|
|
||||||
|
// MetricBatchSize is the maximum number of metrics that is wrote to an
|
||||||
|
// output plugin in one call.
|
||||||
|
MetricBatchSize int
|
||||||
|
|
||||||
// MetricBufferLimit is the max number of metrics that each output plugin
|
// MetricBufferLimit is the max number of metrics that each output plugin
|
||||||
// will cache. The buffer is cleared when a successful write occurs. When
|
// will cache. The buffer is cleared when a successful write occurs. When
|
||||||
// full, the oldest metrics will be overwritten.
|
// full, the oldest metrics will be overwritten. This number should be a
|
||||||
|
// multiple of MetricBatchSize. Due to current implementation, this could
|
||||||
|
// not be less than 2 times MetricBatchSize.
|
||||||
MetricBufferLimit int
|
MetricBufferLimit int
|
||||||
|
|
||||||
// FlushBufferWhenFull tells Telegraf to flush the metric buffer whenever
|
// FlushBufferWhenFull tells Telegraf to flush the metric buffer whenever
|
||||||
@@ -103,11 +117,10 @@ type AgentConfig struct {
|
|||||||
// does _not_ deactivate FlushInterval.
|
// does _not_ deactivate FlushInterval.
|
||||||
FlushBufferWhenFull bool
|
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-
|
// valid for the agent config. Leaving them here for now for backwards-
|
||||||
// compatability
|
// compatability
|
||||||
UTC bool `toml:"utc"`
|
UTC bool `toml:"utc"`
|
||||||
Precision string
|
|
||||||
|
|
||||||
// Debug is the option for running in debug mode
|
// Debug is the option for running in debug mode
|
||||||
Debug bool
|
Debug bool
|
||||||
@@ -127,7 +140,7 @@ func (c *Config) InputNames() []string {
|
|||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
// Outputs returns a list of strings of the configured inputs.
|
// Outputs returns a list of strings of the configured outputs.
|
||||||
func (c *Config) OutputNames() []string {
|
func (c *Config) OutputNames() []string {
|
||||||
var name []string
|
var name []string
|
||||||
for _, output := range c.Outputs {
|
for _, output := range c.Outputs {
|
||||||
@@ -182,11 +195,13 @@ var header = `# Telegraf Configuration
|
|||||||
## ie, if interval="10s" then always collect on :00, :10, :20, etc.
|
## ie, if interval="10s" then always collect on :00, :10, :20, etc.
|
||||||
round_interval = true
|
round_interval = true
|
||||||
|
|
||||||
## Telegraf will cache metric_buffer_limit metrics for each output, and will
|
## Telegraf will send metrics to outputs in batches of at
|
||||||
## flush this buffer on a successful write.
|
## most metric_batch_size metrics.
|
||||||
metric_buffer_limit = 1000
|
metric_batch_size = 1000
|
||||||
## Flush the buffer whenever full, regardless of flush_interval.
|
## For failed writes, telegraf will cache metric_buffer_limit metrics for each
|
||||||
flush_buffer_when_full = true
|
## output, and will flush this buffer on a successful write. Oldest metrics
|
||||||
|
## are dropped first when this buffer fills.
|
||||||
|
metric_buffer_limit = 10000
|
||||||
|
|
||||||
## Collection jitter is used to jitter the collection by a random amount.
|
## Collection jitter is used to jitter the collection by a random amount.
|
||||||
## Each plugin will sleep for a random time within jitter before collecting.
|
## Each plugin will sleep for a random time within jitter before collecting.
|
||||||
@@ -202,6 +217,11 @@ var header = `# Telegraf Configuration
|
|||||||
## ie, a jitter of 5s and interval 10s means flushes will happen every 10-15s
|
## ie, a jitter of 5s and interval 10s means flushes will happen every 10-15s
|
||||||
flush_jitter = "0s"
|
flush_jitter = "0s"
|
||||||
|
|
||||||
|
## By default, precision will be set to the same timestamp order as the
|
||||||
|
## collection interval, with the maximum being 1s.
|
||||||
|
## Precision will NOT be used for service inputs, such as logparser and statsd.
|
||||||
|
## Valid values are "ns", "us" (or "µs"), "ms", "s".
|
||||||
|
precision = ""
|
||||||
## Run telegraf in debug mode
|
## Run telegraf in debug mode
|
||||||
debug = false
|
debug = false
|
||||||
## Run telegraf in quiet mode
|
## Run telegraf in quiet mode
|
||||||
@@ -349,7 +369,7 @@ func printConfig(name string, p printer, op string, commented bool) {
|
|||||||
fmt.Print("\n")
|
fmt.Print("\n")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fmt.Print(comment + line + "\n")
|
fmt.Print(strings.TrimRight(comment+line, " ") + "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -404,13 +424,70 @@ func (c *Config) LoadDirectory(path string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to find a default config file at these locations (in order):
|
||||||
|
// 1. $TELEGRAF_CONFIG_PATH
|
||||||
|
// 2. $HOME/.telegraf/telegraf.conf
|
||||||
|
// 3. /etc/telegraf/telegraf.conf
|
||||||
|
//
|
||||||
|
func getDefaultConfigPath() (string, error) {
|
||||||
|
envfile := os.Getenv("TELEGRAF_CONFIG_PATH")
|
||||||
|
homefile := os.ExpandEnv("${HOME}/.telegraf/telegraf.conf")
|
||||||
|
etcfile := "/etc/telegraf/telegraf.conf"
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
etcfile = `C:\Program Files\Telegraf\telegraf.conf`
|
||||||
|
}
|
||||||
|
for _, path := range []string{envfile, homefile, etcfile} {
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
log.Printf("Using config file: %s", path)
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we got here, we didn't find a file in a default location
|
||||||
|
return "", fmt.Errorf("No config file specified, and could not find one"+
|
||||||
|
" in $TELEGRAF_CONFIG_PATH, %s, or %s", homefile, etcfile)
|
||||||
|
}
|
||||||
|
|
||||||
// LoadConfig loads the given config file and applies it to c
|
// LoadConfig loads the given config file and applies it to c
|
||||||
func (c *Config) LoadConfig(path string) error {
|
func (c *Config) LoadConfig(path string) error {
|
||||||
|
var err error
|
||||||
|
if path == "" {
|
||||||
|
if path, err = getDefaultConfigPath(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
tbl, err := parseFile(path)
|
tbl, err := parseFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error parsing %s, %s", path, err)
|
return fmt.Errorf("Error parsing %s, %s", path, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse tags tables first:
|
||||||
|
for _, tableName := range []string{"tags", "global_tags"} {
|
||||||
|
if val, ok := tbl.Fields[tableName]; ok {
|
||||||
|
subTable, ok := val.(*ast.Table)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%s: invalid configuration", path)
|
||||||
|
}
|
||||||
|
if err = config.UnmarshalTable(subTable, c.Tags); err != nil {
|
||||||
|
log.Printf("Could not parse [global_tags] config\n")
|
||||||
|
return fmt.Errorf("Error parsing %s, %s", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse agent table:
|
||||||
|
if val, ok := tbl.Fields["agent"]; ok {
|
||||||
|
subTable, ok := val.(*ast.Table)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%s: invalid configuration", path)
|
||||||
|
}
|
||||||
|
if err = config.UnmarshalTable(subTable, c.Agent); err != nil {
|
||||||
|
log.Printf("Could not parse [agent] config\n")
|
||||||
|
return fmt.Errorf("Error parsing %s, %s", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse all the rest of the plugins:
|
||||||
for name, val := range tbl.Fields {
|
for name, val := range tbl.Fields {
|
||||||
subTable, ok := val.(*ast.Table)
|
subTable, ok := val.(*ast.Table)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -418,16 +495,7 @@ func (c *Config) LoadConfig(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch name {
|
switch name {
|
||||||
case "agent":
|
case "agent", "global_tags", "tags":
|
||||||
if err = config.UnmarshalTable(subTable, c.Agent); err != nil {
|
|
||||||
log.Printf("Could not parse [agent] config\n")
|
|
||||||
return fmt.Errorf("Error parsing %s, %s", path, err)
|
|
||||||
}
|
|
||||||
case "global_tags", "tags":
|
|
||||||
if err = config.UnmarshalTable(subTable, c.Tags); err != nil {
|
|
||||||
log.Printf("Could not parse [global_tags] config\n")
|
|
||||||
return fmt.Errorf("Error parsing %s, %s", path, err)
|
|
||||||
}
|
|
||||||
case "outputs":
|
case "outputs":
|
||||||
for pluginName, pluginVal := range subTable.Fields {
|
for pluginName, pluginVal := range subTable.Fields {
|
||||||
switch pluginSubTable := pluginVal.(type) {
|
switch pluginSubTable := pluginVal.(type) {
|
||||||
@@ -475,6 +543,13 @@ func (c *Config) LoadConfig(path string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// trimBOM trims the Byte-Order-Marks from the beginning of the file.
|
||||||
|
// this is for Windows compatability only.
|
||||||
|
// see https://github.com/influxdata/telegraf/issues/1378
|
||||||
|
func trimBOM(f []byte) []byte {
|
||||||
|
return bytes.TrimPrefix(f, []byte("\xef\xbb\xbf"))
|
||||||
|
}
|
||||||
|
|
||||||
// parseFile loads a TOML configuration from a provided path and
|
// parseFile loads a TOML configuration from a provided path and
|
||||||
// returns the AST produced from the TOML parser. When loading the file, it
|
// returns the AST produced from the TOML parser. When loading the file, it
|
||||||
// will find environment variables and replace them.
|
// will find environment variables and replace them.
|
||||||
@@ -483,6 +558,8 @@ func parseFile(fpath string) (*ast.Table, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// ugh windows why
|
||||||
|
contents = trimBOM(contents)
|
||||||
|
|
||||||
env_vars := envVarRe.FindAll(contents, -1)
|
env_vars := envVarRe.FindAll(contents, -1)
|
||||||
for _, env_var := range env_vars {
|
for _, env_var := range env_vars {
|
||||||
@@ -525,11 +602,8 @@ func (c *Config) addOutput(name string, table *ast.Table) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ro := internal_models.NewRunningOutput(name, output, outputConfig)
|
ro := models.NewRunningOutput(name, output, outputConfig,
|
||||||
if c.Agent.MetricBufferLimit > 0 {
|
c.Agent.MetricBatchSize, c.Agent.MetricBufferLimit)
|
||||||
ro.MetricBufferLimit = c.Agent.MetricBufferLimit
|
|
||||||
}
|
|
||||||
ro.FlushBufferWhenFull = c.Agent.FlushBufferWhenFull
|
|
||||||
c.Outputs = append(c.Outputs, ro)
|
c.Outputs = append(c.Outputs, ro)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -569,7 +643,7 @@ func (c *Config) addInput(name string, table *ast.Table) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
rp := &internal_models.RunningInput{
|
rp := &models.RunningInput{
|
||||||
Name: name,
|
Name: name,
|
||||||
Input: input,
|
Input: input,
|
||||||
Config: pluginConfig,
|
Config: pluginConfig,
|
||||||
@@ -580,10 +654,10 @@ func (c *Config) addInput(name string, table *ast.Table) error {
|
|||||||
|
|
||||||
// buildFilter builds a Filter
|
// buildFilter builds a Filter
|
||||||
// (tagpass/tagdrop/namepass/namedrop/fieldpass/fielddrop) to
|
// (tagpass/tagdrop/namepass/namedrop/fieldpass/fielddrop) to
|
||||||
// be inserted into the internal_models.OutputConfig/internal_models.InputConfig to be used for prefix
|
// be inserted into the models.OutputConfig/models.InputConfig
|
||||||
// filtering on tags and measurements
|
// to be used for glob filtering on tags and measurements
|
||||||
func buildFilter(tbl *ast.Table) internal_models.Filter {
|
func buildFilter(tbl *ast.Table) (models.Filter, error) {
|
||||||
f := internal_models.Filter{}
|
f := models.Filter{}
|
||||||
|
|
||||||
if node, ok := tbl.Fields["namepass"]; ok {
|
if node, ok := tbl.Fields["namepass"]; ok {
|
||||||
if kv, ok := node.(*ast.KeyValue); ok {
|
if kv, ok := node.(*ast.KeyValue); ok {
|
||||||
@@ -591,7 +665,6 @@ func buildFilter(tbl *ast.Table) internal_models.Filter {
|
|||||||
for _, elem := range ary.Value {
|
for _, elem := range ary.Value {
|
||||||
if str, ok := elem.(*ast.String); ok {
|
if str, ok := elem.(*ast.String); ok {
|
||||||
f.NamePass = append(f.NamePass, str.Value)
|
f.NamePass = append(f.NamePass, str.Value)
|
||||||
f.IsActive = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -604,7 +677,6 @@ func buildFilter(tbl *ast.Table) internal_models.Filter {
|
|||||||
for _, elem := range ary.Value {
|
for _, elem := range ary.Value {
|
||||||
if str, ok := elem.(*ast.String); ok {
|
if str, ok := elem.(*ast.String); ok {
|
||||||
f.NameDrop = append(f.NameDrop, str.Value)
|
f.NameDrop = append(f.NameDrop, str.Value)
|
||||||
f.IsActive = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -619,7 +691,6 @@ func buildFilter(tbl *ast.Table) internal_models.Filter {
|
|||||||
for _, elem := range ary.Value {
|
for _, elem := range ary.Value {
|
||||||
if str, ok := elem.(*ast.String); ok {
|
if str, ok := elem.(*ast.String); ok {
|
||||||
f.FieldPass = append(f.FieldPass, str.Value)
|
f.FieldPass = append(f.FieldPass, str.Value)
|
||||||
f.IsActive = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -635,7 +706,6 @@ func buildFilter(tbl *ast.Table) internal_models.Filter {
|
|||||||
for _, elem := range ary.Value {
|
for _, elem := range ary.Value {
|
||||||
if str, ok := elem.(*ast.String); ok {
|
if str, ok := elem.(*ast.String); ok {
|
||||||
f.FieldDrop = append(f.FieldDrop, str.Value)
|
f.FieldDrop = append(f.FieldDrop, str.Value)
|
||||||
f.IsActive = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -647,7 +717,7 @@ func buildFilter(tbl *ast.Table) internal_models.Filter {
|
|||||||
if subtbl, ok := node.(*ast.Table); ok {
|
if subtbl, ok := node.(*ast.Table); ok {
|
||||||
for name, val := range subtbl.Fields {
|
for name, val := range subtbl.Fields {
|
||||||
if kv, ok := val.(*ast.KeyValue); ok {
|
if kv, ok := val.(*ast.KeyValue); ok {
|
||||||
tagfilter := &internal_models.TagFilter{Name: name}
|
tagfilter := &models.TagFilter{Name: name}
|
||||||
if ary, ok := kv.Value.(*ast.Array); ok {
|
if ary, ok := kv.Value.(*ast.Array); ok {
|
||||||
for _, elem := range ary.Value {
|
for _, elem := range ary.Value {
|
||||||
if str, ok := elem.(*ast.String); ok {
|
if str, ok := elem.(*ast.String); ok {
|
||||||
@@ -656,7 +726,6 @@ func buildFilter(tbl *ast.Table) internal_models.Filter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
f.TagPass = append(f.TagPass, *tagfilter)
|
f.TagPass = append(f.TagPass, *tagfilter)
|
||||||
f.IsActive = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -666,7 +735,7 @@ func buildFilter(tbl *ast.Table) internal_models.Filter {
|
|||||||
if subtbl, ok := node.(*ast.Table); ok {
|
if subtbl, ok := node.(*ast.Table); ok {
|
||||||
for name, val := range subtbl.Fields {
|
for name, val := range subtbl.Fields {
|
||||||
if kv, ok := val.(*ast.KeyValue); ok {
|
if kv, ok := val.(*ast.KeyValue); ok {
|
||||||
tagfilter := &internal_models.TagFilter{Name: name}
|
tagfilter := &models.TagFilter{Name: name}
|
||||||
if ary, ok := kv.Value.(*ast.Array); ok {
|
if ary, ok := kv.Value.(*ast.Array); ok {
|
||||||
for _, elem := range ary.Value {
|
for _, elem := range ary.Value {
|
||||||
if str, ok := elem.(*ast.String); ok {
|
if str, ok := elem.(*ast.String); ok {
|
||||||
@@ -675,12 +744,38 @@ func buildFilter(tbl *ast.Table) internal_models.Filter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
f.TagDrop = append(f.TagDrop, *tagfilter)
|
f.TagDrop = append(f.TagDrop, *tagfilter)
|
||||||
f.IsActive = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if node, ok := tbl.Fields["tagexclude"]; ok {
|
||||||
|
if kv, ok := node.(*ast.KeyValue); ok {
|
||||||
|
if ary, ok := kv.Value.(*ast.Array); ok {
|
||||||
|
for _, elem := range ary.Value {
|
||||||
|
if str, ok := elem.(*ast.String); ok {
|
||||||
|
f.TagExclude = append(f.TagExclude, str.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if node, ok := tbl.Fields["taginclude"]; ok {
|
||||||
|
if kv, ok := node.(*ast.KeyValue); ok {
|
||||||
|
if ary, ok := kv.Value.(*ast.Array); ok {
|
||||||
|
for _, elem := range ary.Value {
|
||||||
|
if str, ok := elem.(*ast.String); ok {
|
||||||
|
f.TagInclude = append(f.TagInclude, str.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := f.Compile(); err != nil {
|
||||||
|
return f, err
|
||||||
|
}
|
||||||
|
|
||||||
delete(tbl.Fields, "namedrop")
|
delete(tbl.Fields, "namedrop")
|
||||||
delete(tbl.Fields, "namepass")
|
delete(tbl.Fields, "namepass")
|
||||||
delete(tbl.Fields, "fielddrop")
|
delete(tbl.Fields, "fielddrop")
|
||||||
@@ -689,14 +784,16 @@ func buildFilter(tbl *ast.Table) internal_models.Filter {
|
|||||||
delete(tbl.Fields, "pass")
|
delete(tbl.Fields, "pass")
|
||||||
delete(tbl.Fields, "tagdrop")
|
delete(tbl.Fields, "tagdrop")
|
||||||
delete(tbl.Fields, "tagpass")
|
delete(tbl.Fields, "tagpass")
|
||||||
return f
|
delete(tbl.Fields, "tagexclude")
|
||||||
|
delete(tbl.Fields, "taginclude")
|
||||||
|
return f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildInput parses input specific items from the ast.Table,
|
// buildInput parses input specific items from the ast.Table,
|
||||||
// builds the filter and returns a
|
// builds the filter and returns a
|
||||||
// internal_models.InputConfig to be inserted into internal_models.RunningInput
|
// models.InputConfig to be inserted into models.RunningInput
|
||||||
func buildInput(name string, tbl *ast.Table) (*internal_models.InputConfig, error) {
|
func buildInput(name string, tbl *ast.Table) (*models.InputConfig, error) {
|
||||||
cp := &internal_models.InputConfig{Name: name}
|
cp := &models.InputConfig{Name: name}
|
||||||
if node, ok := tbl.Fields["interval"]; ok {
|
if node, ok := tbl.Fields["interval"]; ok {
|
||||||
if kv, ok := node.(*ast.KeyValue); ok {
|
if kv, ok := node.(*ast.KeyValue); ok {
|
||||||
if str, ok := kv.Value.(*ast.String); ok {
|
if str, ok := kv.Value.(*ast.String); ok {
|
||||||
@@ -748,7 +845,11 @@ func buildInput(name string, tbl *ast.Table) (*internal_models.InputConfig, erro
|
|||||||
delete(tbl.Fields, "name_override")
|
delete(tbl.Fields, "name_override")
|
||||||
delete(tbl.Fields, "interval")
|
delete(tbl.Fields, "interval")
|
||||||
delete(tbl.Fields, "tags")
|
delete(tbl.Fields, "tags")
|
||||||
cp.Filter = buildFilter(tbl)
|
var err error
|
||||||
|
cp.Filter, err = buildFilter(tbl)
|
||||||
|
if err != nil {
|
||||||
|
return cp, err
|
||||||
|
}
|
||||||
return cp, nil
|
return cp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -864,13 +965,18 @@ func buildSerializer(name string, tbl *ast.Table) (serializers.Serializer, error
|
|||||||
return serializers.NewSerializer(c)
|
return serializers.NewSerializer(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildOutput parses output specific items from the ast.Table, builds the filter and returns an
|
// buildOutput parses output specific items from the ast.Table,
|
||||||
// internal_models.OutputConfig to be inserted into internal_models.RunningInput
|
// builds the filter and returns an
|
||||||
|
// models.OutputConfig to be inserted into models.RunningInput
|
||||||
// Note: error exists in the return for future calls that might require error
|
// Note: error exists in the return for future calls that might require error
|
||||||
func buildOutput(name string, tbl *ast.Table) (*internal_models.OutputConfig, error) {
|
func buildOutput(name string, tbl *ast.Table) (*models.OutputConfig, error) {
|
||||||
oc := &internal_models.OutputConfig{
|
filter, err := buildFilter(tbl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
oc := &models.OutputConfig{
|
||||||
Name: name,
|
Name: name,
|
||||||
Filter: buildFilter(tbl),
|
Filter: filter,
|
||||||
}
|
}
|
||||||
// Outputs don't support FieldDrop/FieldPass, so set to NameDrop/NamePass
|
// Outputs don't support FieldDrop/FieldPass, so set to NameDrop/NamePass
|
||||||
if len(oc.Filter.FieldDrop) > 0 {
|
if len(oc.Filter.FieldDrop) > 0 {
|
||||||
|
|||||||
@@ -26,27 +26,28 @@ func TestConfig_LoadSingleInputWithEnvVars(t *testing.T) {
|
|||||||
memcached := inputs.Inputs["memcached"]().(*memcached.Memcached)
|
memcached := inputs.Inputs["memcached"]().(*memcached.Memcached)
|
||||||
memcached.Servers = []string{"192.168.1.1"}
|
memcached.Servers = []string{"192.168.1.1"}
|
||||||
|
|
||||||
mConfig := &internal_models.InputConfig{
|
filter := models.Filter{
|
||||||
Name: "memcached",
|
NameDrop: []string{"metricname2"},
|
||||||
Filter: internal_models.Filter{
|
NamePass: []string{"metricname1"},
|
||||||
NameDrop: []string{"metricname2"},
|
FieldDrop: []string{"other", "stuff"},
|
||||||
NamePass: []string{"metricname1"},
|
FieldPass: []string{"some", "strings"},
|
||||||
FieldDrop: []string{"other", "stuff"},
|
TagDrop: []models.TagFilter{
|
||||||
FieldPass: []string{"some", "strings"},
|
models.TagFilter{
|
||||||
TagDrop: []internal_models.TagFilter{
|
Name: "badtag",
|
||||||
internal_models.TagFilter{
|
Filter: []string{"othertag"},
|
||||||
Name: "badtag",
|
|
||||||
Filter: []string{"othertag"},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
TagPass: []internal_models.TagFilter{
|
|
||||||
internal_models.TagFilter{
|
|
||||||
Name: "goodtag",
|
|
||||||
Filter: []string{"mytag"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
IsActive: true,
|
|
||||||
},
|
},
|
||||||
|
TagPass: []models.TagFilter{
|
||||||
|
models.TagFilter{
|
||||||
|
Name: "goodtag",
|
||||||
|
Filter: []string{"mytag"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.NoError(t, filter.Compile())
|
||||||
|
mConfig := &models.InputConfig{
|
||||||
|
Name: "memcached",
|
||||||
|
Filter: filter,
|
||||||
Interval: 10 * time.Second,
|
Interval: 10 * time.Second,
|
||||||
}
|
}
|
||||||
mConfig.Tags = make(map[string]string)
|
mConfig.Tags = make(map[string]string)
|
||||||
@@ -64,27 +65,28 @@ func TestConfig_LoadSingleInput(t *testing.T) {
|
|||||||
memcached := inputs.Inputs["memcached"]().(*memcached.Memcached)
|
memcached := inputs.Inputs["memcached"]().(*memcached.Memcached)
|
||||||
memcached.Servers = []string{"localhost"}
|
memcached.Servers = []string{"localhost"}
|
||||||
|
|
||||||
mConfig := &internal_models.InputConfig{
|
filter := models.Filter{
|
||||||
Name: "memcached",
|
NameDrop: []string{"metricname2"},
|
||||||
Filter: internal_models.Filter{
|
NamePass: []string{"metricname1"},
|
||||||
NameDrop: []string{"metricname2"},
|
FieldDrop: []string{"other", "stuff"},
|
||||||
NamePass: []string{"metricname1"},
|
FieldPass: []string{"some", "strings"},
|
||||||
FieldDrop: []string{"other", "stuff"},
|
TagDrop: []models.TagFilter{
|
||||||
FieldPass: []string{"some", "strings"},
|
models.TagFilter{
|
||||||
TagDrop: []internal_models.TagFilter{
|
Name: "badtag",
|
||||||
internal_models.TagFilter{
|
Filter: []string{"othertag"},
|
||||||
Name: "badtag",
|
|
||||||
Filter: []string{"othertag"},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
TagPass: []internal_models.TagFilter{
|
|
||||||
internal_models.TagFilter{
|
|
||||||
Name: "goodtag",
|
|
||||||
Filter: []string{"mytag"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
IsActive: true,
|
|
||||||
},
|
},
|
||||||
|
TagPass: []models.TagFilter{
|
||||||
|
models.TagFilter{
|
||||||
|
Name: "goodtag",
|
||||||
|
Filter: []string{"mytag"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.NoError(t, filter.Compile())
|
||||||
|
mConfig := &models.InputConfig{
|
||||||
|
Name: "memcached",
|
||||||
|
Filter: filter,
|
||||||
Interval: 5 * time.Second,
|
Interval: 5 * time.Second,
|
||||||
}
|
}
|
||||||
mConfig.Tags = make(map[string]string)
|
mConfig.Tags = make(map[string]string)
|
||||||
@@ -109,27 +111,28 @@ func TestConfig_LoadDirectory(t *testing.T) {
|
|||||||
memcached := inputs.Inputs["memcached"]().(*memcached.Memcached)
|
memcached := inputs.Inputs["memcached"]().(*memcached.Memcached)
|
||||||
memcached.Servers = []string{"localhost"}
|
memcached.Servers = []string{"localhost"}
|
||||||
|
|
||||||
mConfig := &internal_models.InputConfig{
|
filter := models.Filter{
|
||||||
Name: "memcached",
|
NameDrop: []string{"metricname2"},
|
||||||
Filter: internal_models.Filter{
|
NamePass: []string{"metricname1"},
|
||||||
NameDrop: []string{"metricname2"},
|
FieldDrop: []string{"other", "stuff"},
|
||||||
NamePass: []string{"metricname1"},
|
FieldPass: []string{"some", "strings"},
|
||||||
FieldDrop: []string{"other", "stuff"},
|
TagDrop: []models.TagFilter{
|
||||||
FieldPass: []string{"some", "strings"},
|
models.TagFilter{
|
||||||
TagDrop: []internal_models.TagFilter{
|
Name: "badtag",
|
||||||
internal_models.TagFilter{
|
Filter: []string{"othertag"},
|
||||||
Name: "badtag",
|
|
||||||
Filter: []string{"othertag"},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
TagPass: []internal_models.TagFilter{
|
|
||||||
internal_models.TagFilter{
|
|
||||||
Name: "goodtag",
|
|
||||||
Filter: []string{"mytag"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
IsActive: true,
|
|
||||||
},
|
},
|
||||||
|
TagPass: []models.TagFilter{
|
||||||
|
models.TagFilter{
|
||||||
|
Name: "goodtag",
|
||||||
|
Filter: []string{"mytag"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.NoError(t, filter.Compile())
|
||||||
|
mConfig := &models.InputConfig{
|
||||||
|
Name: "memcached",
|
||||||
|
Filter: filter,
|
||||||
Interval: 5 * time.Second,
|
Interval: 5 * time.Second,
|
||||||
}
|
}
|
||||||
mConfig.Tags = make(map[string]string)
|
mConfig.Tags = make(map[string]string)
|
||||||
@@ -144,7 +147,7 @@ func TestConfig_LoadDirectory(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
ex.SetParser(p)
|
ex.SetParser(p)
|
||||||
ex.Command = "/usr/bin/myothercollector --foo=bar"
|
ex.Command = "/usr/bin/myothercollector --foo=bar"
|
||||||
eConfig := &internal_models.InputConfig{
|
eConfig := &models.InputConfig{
|
||||||
Name: "exec",
|
Name: "exec",
|
||||||
MeasurementSuffix: "_myothercollector",
|
MeasurementSuffix: "_myothercollector",
|
||||||
}
|
}
|
||||||
@@ -163,7 +166,7 @@ func TestConfig_LoadDirectory(t *testing.T) {
|
|||||||
pstat := inputs.Inputs["procstat"]().(*procstat.Procstat)
|
pstat := inputs.Inputs["procstat"]().(*procstat.Procstat)
|
||||||
pstat.PidFile = "/var/run/grafana-server.pid"
|
pstat.PidFile = "/var/run/grafana-server.pid"
|
||||||
|
|
||||||
pConfig := &internal_models.InputConfig{Name: "procstat"}
|
pConfig := &models.InputConfig{Name: "procstat"}
|
||||||
pConfig.Tags = make(map[string]string)
|
pConfig.Tags = make(map[string]string)
|
||||||
|
|
||||||
assert.Equal(t, pstat, c.Inputs[3].Input,
|
assert.Equal(t, pstat, c.Inputs[3].Input,
|
||||||
|
|||||||
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
|
||||||
|
}
|
||||||
98
internal/globpath/globpath.go
Normal file
98
internal/globpath/globpath.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package globpath
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gobwas/glob"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sepStr = fmt.Sprintf("%v", string(os.PathSeparator))
|
||||||
|
|
||||||
|
type GlobPath struct {
|
||||||
|
path string
|
||||||
|
hasMeta bool
|
||||||
|
g glob.Glob
|
||||||
|
root string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Compile(path string) (*GlobPath, error) {
|
||||||
|
out := GlobPath{
|
||||||
|
hasMeta: hasMeta(path),
|
||||||
|
path: path,
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there are no glob meta characters in the path, don't bother compiling
|
||||||
|
// a glob object or finding the root directory. (see short-circuit in Match)
|
||||||
|
if !out.hasMeta {
|
||||||
|
return &out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if out.g, err = glob.Compile(path, os.PathSeparator); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Get the root directory for this filepath
|
||||||
|
out.root = findRootDir(path)
|
||||||
|
return &out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GlobPath) Match() map[string]os.FileInfo {
|
||||||
|
if !g.hasMeta {
|
||||||
|
out := make(map[string]os.FileInfo)
|
||||||
|
info, err := os.Stat(g.path)
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
out[g.path] = info
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
return walkFilePath(g.root, g.g)
|
||||||
|
}
|
||||||
|
|
||||||
|
// walk the filepath from the given root and return a list of files that match
|
||||||
|
// the given glob.
|
||||||
|
func walkFilePath(root string, g glob.Glob) map[string]os.FileInfo {
|
||||||
|
matchedFiles := make(map[string]os.FileInfo)
|
||||||
|
walkfn := func(path string, info os.FileInfo, _ error) error {
|
||||||
|
if g.Match(path) {
|
||||||
|
matchedFiles[path] = info
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
filepath.Walk(root, walkfn)
|
||||||
|
return matchedFiles
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the root dir of the given path (could include globs).
|
||||||
|
// ie:
|
||||||
|
// /var/log/telegraf.conf -> /var/log
|
||||||
|
// /home/** -> /home
|
||||||
|
// /home/*/** -> /home
|
||||||
|
// /lib/share/*/*/**.txt -> /lib/share
|
||||||
|
func findRootDir(path string) string {
|
||||||
|
pathItems := strings.Split(path, sepStr)
|
||||||
|
out := sepStr
|
||||||
|
for i, item := range pathItems {
|
||||||
|
if i == len(pathItems)-1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if item == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if hasMeta(item) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
out += item + sepStr
|
||||||
|
}
|
||||||
|
if out != "/" {
|
||||||
|
out = strings.TrimSuffix(out, "/")
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasMeta reports whether path contains any magic glob characters.
|
||||||
|
func hasMeta(path string) bool {
|
||||||
|
return strings.IndexAny(path, "*?[") >= 0
|
||||||
|
}
|
||||||
62
internal/globpath/globpath_test.go
Normal file
62
internal/globpath/globpath_test.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package globpath
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCompileAndMatch(t *testing.T) {
|
||||||
|
dir := getTestdataDir()
|
||||||
|
// test super asterisk
|
||||||
|
g1, err := Compile(dir + "/**")
|
||||||
|
require.NoError(t, err)
|
||||||
|
// test single asterisk
|
||||||
|
g2, err := Compile(dir + "/*.log")
|
||||||
|
require.NoError(t, err)
|
||||||
|
// test no meta characters (file exists)
|
||||||
|
g3, err := Compile(dir + "/log1.log")
|
||||||
|
require.NoError(t, err)
|
||||||
|
// test file that doesn't exist
|
||||||
|
g4, err := Compile(dir + "/i_dont_exist.log")
|
||||||
|
require.NoError(t, err)
|
||||||
|
// test super asterisk that doesn't exist
|
||||||
|
g5, err := Compile(dir + "/dir_doesnt_exist/**")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
matches := g1.Match()
|
||||||
|
assert.Len(t, matches, 3)
|
||||||
|
matches = g2.Match()
|
||||||
|
assert.Len(t, matches, 2)
|
||||||
|
matches = g3.Match()
|
||||||
|
assert.Len(t, matches, 1)
|
||||||
|
matches = g4.Match()
|
||||||
|
assert.Len(t, matches, 0)
|
||||||
|
matches = g5.Match()
|
||||||
|
assert.Len(t, matches, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindRootDir(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
output string
|
||||||
|
}{
|
||||||
|
{"/var/log/telegraf.conf", "/var/log"},
|
||||||
|
{"/home/**", "/home"},
|
||||||
|
{"/home/*/**", "/home"},
|
||||||
|
{"/lib/share/*/*/**.txt", "/lib/share"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
actual := findRootDir(test.input)
|
||||||
|
assert.Equal(t, test.output, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTestdataDir() string {
|
||||||
|
_, filename, _, _ := runtime.Caller(1)
|
||||||
|
return strings.Replace(filename, "globpath_test.go", "testdata", 1)
|
||||||
|
}
|
||||||
0
internal/globpath/testdata/log1.log
vendored
Normal file
0
internal/globpath/testdata/log1.log
vendored
Normal file
0
internal/globpath/testdata/log2.log
vendored
Normal file
0
internal/globpath/testdata/log2.log
vendored
Normal file
5
internal/globpath/testdata/test.conf
vendored
Normal file
5
internal/globpath/testdata/test.conf
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# this is a fake testing config file
|
||||||
|
# for testing the filestat plugin
|
||||||
|
|
||||||
|
option1 = "foo"
|
||||||
|
option2 = "bar"
|
||||||
@@ -2,13 +2,18 @@ package internal
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
@@ -16,6 +21,12 @@ import (
|
|||||||
|
|
||||||
const alphanum string = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
const alphanum string = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||||
|
|
||||||
|
var (
|
||||||
|
TimeoutErr = errors.New("Command timed out.")
|
||||||
|
|
||||||
|
NotImplementedError = errors.New("not implemented yet")
|
||||||
|
)
|
||||||
|
|
||||||
// Duration just wraps time.Duration
|
// Duration just wraps time.Duration
|
||||||
type Duration struct {
|
type Duration struct {
|
||||||
Duration time.Duration
|
Duration time.Duration
|
||||||
@@ -23,18 +34,29 @@ type Duration struct {
|
|||||||
|
|
||||||
// UnmarshalTOML parses the duration from the TOML config file
|
// UnmarshalTOML parses the duration from the TOML config file
|
||||||
func (d *Duration) UnmarshalTOML(b []byte) error {
|
func (d *Duration) UnmarshalTOML(b []byte) error {
|
||||||
dur, err := time.ParseDuration(string(b[1 : len(b)-1]))
|
var err error
|
||||||
if err != nil {
|
// Parse string duration, ie, "1s"
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var NotImplementedError = errors.New("not implemented yet")
|
|
||||||
|
|
||||||
// ReadLines reads contents from a file and splits them by new lines.
|
// ReadLines reads contents from a file and splits them by new lines.
|
||||||
// A convenience wrapper to ReadLinesOffsetN(filename, 0, -1).
|
// A convenience wrapper to ReadLinesOffsetN(filename, 0, -1).
|
||||||
func ReadLines(filename string) ([]string, error) {
|
func ReadLines(filename string) ([]string, error) {
|
||||||
@@ -111,8 +133,8 @@ func GetTLSConfig(
|
|||||||
cert, err := tls.LoadX509KeyPair(SSLCert, SSLKey)
|
cert, err := tls.LoadX509KeyPair(SSLCert, SSLKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New(fmt.Sprintf(
|
return nil, errors.New(fmt.Sprintf(
|
||||||
"Could not load TLS client key/certificate: %s",
|
"Could not load TLS client key/certificate from %s:%s: %s",
|
||||||
err))
|
SSLKey, SSLCert, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Certificates = []tls.Certificate{cert}
|
t.Certificates = []tls.Certificate{cert}
|
||||||
@@ -140,58 +162,71 @@ func SnakeCase(in string) string {
|
|||||||
return string(out)
|
return string(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Glob will test a string pattern, potentially containing globs, against a
|
// CombinedOutputTimeout runs the given command with the given timeout and
|
||||||
// subject string. The result is a simple true/false, determining whether or
|
// returns the combined output of stdout and stderr.
|
||||||
// not the glob pattern matched the subject text.
|
// If the command times out, it attempts to kill the process.
|
||||||
//
|
func CombinedOutputTimeout(c *exec.Cmd, timeout time.Duration) ([]byte, error) {
|
||||||
// Adapted from https://github.com/ryanuber/go-glob/blob/master/glob.go
|
var b bytes.Buffer
|
||||||
// thanks Ryan Uber!
|
c.Stdout = &b
|
||||||
func Glob(pattern, measurement string) bool {
|
c.Stderr = &b
|
||||||
// Empty pattern can only match empty subject
|
if err := c.Start(); err != nil {
|
||||||
if pattern == "" {
|
return nil, err
|
||||||
return measurement == pattern
|
}
|
||||||
|
err := WaitTimeout(c, timeout)
|
||||||
|
return b.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunTimeout runs the given command with the given timeout.
|
||||||
|
// If the command times out, it attempts to kill the process.
|
||||||
|
func RunTimeout(c *exec.Cmd, timeout time.Duration) error {
|
||||||
|
if err := c.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return WaitTimeout(c, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitTimeout waits for the given command to finish with a timeout.
|
||||||
|
// It assumes the command has already been started.
|
||||||
|
// If the command times out, it attempts to kill the process.
|
||||||
|
func WaitTimeout(c *exec.Cmd, timeout time.Duration) error {
|
||||||
|
timer := time.NewTimer(timeout)
|
||||||
|
done := make(chan error)
|
||||||
|
go func() { done <- c.Wait() }()
|
||||||
|
select {
|
||||||
|
case err := <-done:
|
||||||
|
timer.Stop()
|
||||||
|
return err
|
||||||
|
case <-timer.C:
|
||||||
|
if err := c.Process.Kill(); err != nil {
|
||||||
|
log.Printf("FATAL error killing process: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// wait for the command to return after killing it
|
||||||
|
<-done
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the pattern _is_ a glob, it matches everything
|
|
||||||
if pattern == "*" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := strings.Split(pattern, "*")
|
|
||||||
|
|
||||||
if len(parts) == 1 {
|
|
||||||
// No globs in pattern, so test for match
|
|
||||||
return pattern == measurement
|
|
||||||
}
|
|
||||||
|
|
||||||
leadingGlob := strings.HasPrefix(pattern, "*")
|
|
||||||
trailingGlob := strings.HasSuffix(pattern, "*")
|
|
||||||
end := len(parts) - 1
|
|
||||||
|
|
||||||
for i, part := range parts {
|
|
||||||
switch i {
|
|
||||||
case 0:
|
|
||||||
if leadingGlob {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !strings.HasPrefix(measurement, part) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case end:
|
|
||||||
if len(measurement) > 0 {
|
|
||||||
return trailingGlob || strings.HasSuffix(measurement, part)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if !strings.Contains(measurement, part) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trim evaluated text from measurement as we loop over the pattern.
|
|
||||||
idx := strings.Index(measurement, part) + len(part)
|
|
||||||
measurement = measurement[idx:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// All parts of the pattern matched
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +1,12 @@
|
|||||||
package internal
|
package internal
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"os/exec"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
func testGlobMatch(t *testing.T, pattern, subj string) {
|
"github.com/stretchr/testify/assert"
|
||||||
if !Glob(pattern, subj) {
|
)
|
||||||
t.Errorf("%s should match %s", pattern, subj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testGlobNoMatch(t *testing.T, pattern, subj string) {
|
|
||||||
if Glob(pattern, subj) {
|
|
||||||
t.Errorf("%s should not match %s", pattern, subj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmptyPattern(t *testing.T) {
|
|
||||||
testGlobMatch(t, "", "")
|
|
||||||
testGlobNoMatch(t, "", "test")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPatternWithoutGlobs(t *testing.T) {
|
|
||||||
testGlobMatch(t, "test", "test")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGlob(t *testing.T) {
|
|
||||||
for _, pattern := range []string{
|
|
||||||
"*test", // Leading glob
|
|
||||||
"this*", // Trailing glob
|
|
||||||
"*is*a*", // Lots of globs
|
|
||||||
"**test**", // Double glob characters
|
|
||||||
"**is**a***test*", // Varying number of globs
|
|
||||||
} {
|
|
||||||
testGlobMatch(t, pattern, "this_is_a_test")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, pattern := range []string{
|
|
||||||
"test*", // Implicit substring match should fail
|
|
||||||
"*is", // Partial match should fail
|
|
||||||
"*no*", // Globs without a match between them should fail
|
|
||||||
} {
|
|
||||||
testGlobNoMatch(t, pattern, "this_is_a_test")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type SnakeTest struct {
|
type SnakeTest struct {
|
||||||
input string
|
input string
|
||||||
@@ -71,3 +36,98 @@ func TestSnakeCase(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
sleepbin, _ = exec.LookPath("sleep")
|
||||||
|
echobin, _ = exec.LookPath("echo")
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRunTimeout(t *testing.T) {
|
||||||
|
if sleepbin == "" {
|
||||||
|
t.Skip("'sleep' binary not available on OS, skipping.")
|
||||||
|
}
|
||||||
|
cmd := exec.Command(sleepbin, "10")
|
||||||
|
start := time.Now()
|
||||||
|
err := RunTimeout(cmd, time.Millisecond*20)
|
||||||
|
elapsed := time.Since(start)
|
||||||
|
|
||||||
|
assert.Equal(t, TimeoutErr, err)
|
||||||
|
// Verify that command gets killed in 20ms, with some breathing room
|
||||||
|
assert.True(t, elapsed < time.Millisecond*75)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCombinedOutputTimeout(t *testing.T) {
|
||||||
|
if sleepbin == "" {
|
||||||
|
t.Skip("'sleep' binary not available on OS, skipping.")
|
||||||
|
}
|
||||||
|
cmd := exec.Command(sleepbin, "10")
|
||||||
|
start := time.Now()
|
||||||
|
_, err := CombinedOutputTimeout(cmd, time.Millisecond*20)
|
||||||
|
elapsed := time.Since(start)
|
||||||
|
|
||||||
|
assert.Equal(t, TimeoutErr, err)
|
||||||
|
// Verify that command gets killed in 20ms, with some breathing room
|
||||||
|
assert.True(t, elapsed < time.Millisecond*75)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCombinedOutput(t *testing.T) {
|
||||||
|
if echobin == "" {
|
||||||
|
t.Skip("'echo' binary not available on OS, skipping.")
|
||||||
|
}
|
||||||
|
cmd := exec.Command(echobin, "foo")
|
||||||
|
out, err := CombinedOutputTimeout(cmd, time.Second)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "foo\n", string(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
// test that CombinedOutputTimeout and exec.Cmd.CombinedOutput return
|
||||||
|
// the same output from a failed command.
|
||||||
|
func TestCombinedOutputError(t *testing.T) {
|
||||||
|
if sleepbin == "" {
|
||||||
|
t.Skip("'sleep' binary not available on OS, skipping.")
|
||||||
|
}
|
||||||
|
cmd := exec.Command(sleepbin, "foo")
|
||||||
|
expected, err := cmd.CombinedOutput()
|
||||||
|
|
||||||
|
cmd2 := exec.Command(sleepbin, "foo")
|
||||||
|
actual, err := CombinedOutputTimeout(cmd2, time.Second)
|
||||||
|
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunError(t *testing.T) {
|
||||||
|
if sleepbin == "" {
|
||||||
|
t.Skip("'sleep' binary not available on OS, skipping.")
|
||||||
|
}
|
||||||
|
cmd := exec.Command(sleepbin, "foo")
|
||||||
|
err := RunTimeout(cmd, time.Second)
|
||||||
|
|
||||||
|
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*100)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
@@ -1,105 +1,188 @@
|
|||||||
package internal_models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"fmt"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf/filter"
|
||||||
"github.com/influxdata/telegraf/internal"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TagFilter is the name of a tag, and the values on which to filter
|
// TagFilter is the name of a tag, and the values on which to filter
|
||||||
type TagFilter struct {
|
type TagFilter struct {
|
||||||
Name string
|
Name string
|
||||||
Filter []string
|
Filter []string
|
||||||
|
filter filter.Filter
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter containing drop/pass and tagdrop/tagpass rules
|
// Filter containing drop/pass and tagdrop/tagpass rules
|
||||||
type Filter struct {
|
type Filter struct {
|
||||||
NameDrop []string
|
NameDrop []string
|
||||||
|
nameDrop filter.Filter
|
||||||
NamePass []string
|
NamePass []string
|
||||||
|
namePass filter.Filter
|
||||||
|
|
||||||
FieldDrop []string
|
FieldDrop []string
|
||||||
|
fieldDrop filter.Filter
|
||||||
FieldPass []string
|
FieldPass []string
|
||||||
|
fieldPass filter.Filter
|
||||||
|
|
||||||
TagDrop []TagFilter
|
TagDrop []TagFilter
|
||||||
TagPass []TagFilter
|
TagPass []TagFilter
|
||||||
|
|
||||||
IsActive bool
|
TagExclude []string
|
||||||
|
tagExclude filter.Filter
|
||||||
|
TagInclude []string
|
||||||
|
tagInclude filter.Filter
|
||||||
|
|
||||||
|
isActive bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Filter) ShouldMetricPass(metric telegraf.Metric) bool {
|
// Compile all Filter lists into filter.Filter objects.
|
||||||
if f.ShouldNamePass(metric.Name()) && f.ShouldTagsPass(metric.Tags()) {
|
func (f *Filter) Compile() error {
|
||||||
|
if len(f.NameDrop) == 0 &&
|
||||||
|
len(f.NamePass) == 0 &&
|
||||||
|
len(f.FieldDrop) == 0 &&
|
||||||
|
len(f.FieldPass) == 0 &&
|
||||||
|
len(f.TagInclude) == 0 &&
|
||||||
|
len(f.TagExclude) == 0 &&
|
||||||
|
len(f.TagPass) == 0 &&
|
||||||
|
len(f.TagDrop) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f.isActive = true
|
||||||
|
var err error
|
||||||
|
f.nameDrop, err = filter.Compile(f.NameDrop)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error compiling 'namedrop', %s", err)
|
||||||
|
}
|
||||||
|
f.namePass, err = filter.Compile(f.NamePass)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error compiling 'namepass', %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.fieldDrop, err = filter.Compile(f.FieldDrop)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error compiling 'fielddrop', %s", err)
|
||||||
|
}
|
||||||
|
f.fieldPass, err = filter.Compile(f.FieldPass)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error compiling 'fieldpass', %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.tagExclude, err = filter.Compile(f.TagExclude)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error compiling 'tagexclude', %s", err)
|
||||||
|
}
|
||||||
|
f.tagInclude, err = filter.Compile(f.TagInclude)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error compiling 'taginclude', %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, _ := range f.TagDrop {
|
||||||
|
f.TagDrop[i].filter, err = filter.Compile(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 = filter.Compile(f.TagPass[i].Filter)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error compiling 'tagpass', %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies the filter to the given measurement name, fields map, and
|
||||||
|
// tags map. It will return false if the metric should be "filtered out", and
|
||||||
|
// true if the metric should "pass".
|
||||||
|
// It will modify tags in-place if they need to be deleted.
|
||||||
|
func (f *Filter) Apply(
|
||||||
|
measurement string,
|
||||||
|
fields map[string]interface{},
|
||||||
|
tags map[string]string,
|
||||||
|
) bool {
|
||||||
|
if !f.isActive {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
|
// check if the measurement name should pass
|
||||||
|
if !f.shouldNamePass(measurement) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the tags should pass
|
||||||
|
if !f.shouldTagsPass(tags) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter fields
|
||||||
|
for fieldkey, _ := range fields {
|
||||||
|
if !f.shouldFieldPass(fieldkey) {
|
||||||
|
delete(fields, fieldkey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(fields) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter tags
|
||||||
|
f.filterTags(tags)
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShouldFieldsPass returns true if the metric should pass, false if should drop
|
func (f *Filter) IsActive() bool {
|
||||||
|
return f.isActive
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldNamePass returns true if the metric should pass, false if should drop
|
||||||
// based on the drop/pass filter parameters
|
// based on the drop/pass filter parameters
|
||||||
func (f Filter) ShouldNamePass(key string) bool {
|
func (f *Filter) shouldNamePass(key string) bool {
|
||||||
if f.NamePass != nil {
|
if f.namePass != nil {
|
||||||
for _, pat := range f.NamePass {
|
if f.namePass.Match(key) {
|
||||||
// TODO remove HasPrefix check, leaving it for now for legacy support.
|
return true
|
||||||
// Cam, 2015-12-07
|
|
||||||
if strings.HasPrefix(key, pat) || internal.Glob(pat, key) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.NameDrop != nil {
|
if f.nameDrop != nil {
|
||||||
for _, pat := range f.NameDrop {
|
if f.nameDrop.Match(key) {
|
||||||
// TODO remove HasPrefix check, leaving it for now for legacy support.
|
return false
|
||||||
// Cam, 2015-12-07
|
|
||||||
if strings.HasPrefix(key, pat) || internal.Glob(pat, key) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShouldFieldsPass returns true if the metric should pass, false if should drop
|
// shouldFieldPass returns true if the metric should pass, false if should drop
|
||||||
// based on the drop/pass filter parameters
|
// based on the drop/pass filter parameters
|
||||||
func (f Filter) ShouldFieldsPass(key string) bool {
|
func (f *Filter) shouldFieldPass(key string) bool {
|
||||||
if f.FieldPass != nil {
|
if f.fieldPass != nil {
|
||||||
for _, pat := range f.FieldPass {
|
if f.fieldPass.Match(key) {
|
||||||
// TODO remove HasPrefix check, leaving it for now for legacy support.
|
return true
|
||||||
// Cam, 2015-12-07
|
|
||||||
if strings.HasPrefix(key, pat) || internal.Glob(pat, key) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.FieldDrop != nil {
|
if f.fieldDrop != nil {
|
||||||
for _, pat := range f.FieldDrop {
|
if f.fieldDrop.Match(key) {
|
||||||
// TODO remove HasPrefix check, leaving it for now for legacy support.
|
return false
|
||||||
// Cam, 2015-12-07
|
|
||||||
if strings.HasPrefix(key, pat) || internal.Glob(pat, key) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShouldTagsPass returns true if the metric should pass, false if should drop
|
// shouldTagsPass returns true if the metric should pass, false if should drop
|
||||||
// based on the tagdrop/tagpass filter parameters
|
// based on the tagdrop/tagpass filter parameters
|
||||||
func (f Filter) ShouldTagsPass(tags map[string]string) bool {
|
func (f *Filter) shouldTagsPass(tags map[string]string) bool {
|
||||||
if f.TagPass != nil {
|
if f.TagPass != nil {
|
||||||
for _, pat := range f.TagPass {
|
for _, pat := range f.TagPass {
|
||||||
|
if pat.filter == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if tagval, ok := tags[pat.Name]; ok {
|
if tagval, ok := tags[pat.Name]; ok {
|
||||||
for _, filter := range pat.Filter {
|
if pat.filter.Match(tagval) {
|
||||||
if internal.Glob(filter, tagval) {
|
return true
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,11 +191,12 @@ func (f Filter) ShouldTagsPass(tags map[string]string) bool {
|
|||||||
|
|
||||||
if f.TagDrop != nil {
|
if f.TagDrop != nil {
|
||||||
for _, pat := range f.TagDrop {
|
for _, pat := range f.TagDrop {
|
||||||
|
if pat.filter == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if tagval, ok := tags[pat.Name]; ok {
|
if tagval, ok := tags[pat.Name]; ok {
|
||||||
for _, filter := range pat.Filter {
|
if pat.filter.Match(tagval) {
|
||||||
if internal.Glob(filter, tagval) {
|
return false
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,3 +205,23 @@ func (f Filter) ShouldTagsPass(tags map[string]string) bool {
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply TagInclude and TagExclude filters.
|
||||||
|
// modifies the tags map in-place.
|
||||||
|
func (f *Filter) filterTags(tags map[string]string) {
|
||||||
|
if f.tagInclude != nil {
|
||||||
|
for k, _ := range tags {
|
||||||
|
if !f.tagInclude.Match(k) {
|
||||||
|
delete(tags, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.tagExclude != nil {
|
||||||
|
for k, _ := range tags {
|
||||||
|
if f.tagExclude.Match(k) {
|
||||||
|
delete(tags, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,64 @@
|
|||||||
package internal_models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestFilter_ApplyEmpty(t *testing.T) {
|
||||||
|
f := Filter{}
|
||||||
|
require.NoError(t, f.Compile())
|
||||||
|
assert.False(t, f.IsActive())
|
||||||
|
|
||||||
|
assert.True(t, f.Apply("m", map[string]interface{}{"value": int64(1)}, map[string]string{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilter_ApplyTagsDontPass(t *testing.T) {
|
||||||
|
filters := []TagFilter{
|
||||||
|
TagFilter{
|
||||||
|
Name: "cpu",
|
||||||
|
Filter: []string{"cpu-*"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
f := Filter{
|
||||||
|
TagDrop: filters,
|
||||||
|
}
|
||||||
|
require.NoError(t, f.Compile())
|
||||||
|
require.NoError(t, f.Compile())
|
||||||
|
assert.True(t, f.IsActive())
|
||||||
|
|
||||||
|
assert.False(t, f.Apply("m",
|
||||||
|
map[string]interface{}{"value": int64(1)},
|
||||||
|
map[string]string{"cpu": "cpu-total"}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilter_ApplyDeleteFields(t *testing.T) {
|
||||||
|
f := Filter{
|
||||||
|
FieldDrop: []string{"value"},
|
||||||
|
}
|
||||||
|
require.NoError(t, f.Compile())
|
||||||
|
require.NoError(t, f.Compile())
|
||||||
|
assert.True(t, f.IsActive())
|
||||||
|
|
||||||
|
fields := map[string]interface{}{"value": int64(1), "value2": int64(2)}
|
||||||
|
assert.True(t, f.Apply("m", fields, nil))
|
||||||
|
assert.Equal(t, map[string]interface{}{"value2": int64(2)}, fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilter_ApplyDeleteAllFields(t *testing.T) {
|
||||||
|
f := Filter{
|
||||||
|
FieldDrop: []string{"value*"},
|
||||||
|
}
|
||||||
|
require.NoError(t, f.Compile())
|
||||||
|
require.NoError(t, f.Compile())
|
||||||
|
assert.True(t, f.IsActive())
|
||||||
|
|
||||||
|
fields := map[string]interface{}{"value": int64(1), "value2": int64(2)}
|
||||||
|
assert.False(t, f.Apply("m", fields, nil))
|
||||||
|
}
|
||||||
|
|
||||||
func TestFilter_Empty(t *testing.T) {
|
func TestFilter_Empty(t *testing.T) {
|
||||||
f := Filter{}
|
f := Filter{}
|
||||||
|
|
||||||
@@ -18,7 +73,7 @@ func TestFilter_Empty(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, measurement := range measurements {
|
for _, measurement := range measurements {
|
||||||
if !f.ShouldFieldsPass(measurement) {
|
if !f.shouldFieldPass(measurement) {
|
||||||
t.Errorf("Expected measurement %s to pass", measurement)
|
t.Errorf("Expected measurement %s to pass", measurement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -28,6 +83,7 @@ func TestFilter_NamePass(t *testing.T) {
|
|||||||
f := Filter{
|
f := Filter{
|
||||||
NamePass: []string{"foo*", "cpu_usage_idle"},
|
NamePass: []string{"foo*", "cpu_usage_idle"},
|
||||||
}
|
}
|
||||||
|
require.NoError(t, f.Compile())
|
||||||
|
|
||||||
passes := []string{
|
passes := []string{
|
||||||
"foo",
|
"foo",
|
||||||
@@ -45,13 +101,13 @@ func TestFilter_NamePass(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, measurement := range passes {
|
for _, measurement := range passes {
|
||||||
if !f.ShouldNamePass(measurement) {
|
if !f.shouldNamePass(measurement) {
|
||||||
t.Errorf("Expected measurement %s to pass", measurement)
|
t.Errorf("Expected measurement %s to pass", measurement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, measurement := range drops {
|
for _, measurement := range drops {
|
||||||
if f.ShouldNamePass(measurement) {
|
if f.shouldNamePass(measurement) {
|
||||||
t.Errorf("Expected measurement %s to drop", measurement)
|
t.Errorf("Expected measurement %s to drop", measurement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,6 +117,7 @@ func TestFilter_NameDrop(t *testing.T) {
|
|||||||
f := Filter{
|
f := Filter{
|
||||||
NameDrop: []string{"foo*", "cpu_usage_idle"},
|
NameDrop: []string{"foo*", "cpu_usage_idle"},
|
||||||
}
|
}
|
||||||
|
require.NoError(t, f.Compile())
|
||||||
|
|
||||||
drops := []string{
|
drops := []string{
|
||||||
"foo",
|
"foo",
|
||||||
@@ -78,13 +135,13 @@ func TestFilter_NameDrop(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, measurement := range passes {
|
for _, measurement := range passes {
|
||||||
if !f.ShouldNamePass(measurement) {
|
if !f.shouldNamePass(measurement) {
|
||||||
t.Errorf("Expected measurement %s to pass", measurement)
|
t.Errorf("Expected measurement %s to pass", measurement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, measurement := range drops {
|
for _, measurement := range drops {
|
||||||
if f.ShouldNamePass(measurement) {
|
if f.shouldNamePass(measurement) {
|
||||||
t.Errorf("Expected measurement %s to drop", measurement)
|
t.Errorf("Expected measurement %s to drop", measurement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,6 +151,7 @@ func TestFilter_FieldPass(t *testing.T) {
|
|||||||
f := Filter{
|
f := Filter{
|
||||||
FieldPass: []string{"foo*", "cpu_usage_idle"},
|
FieldPass: []string{"foo*", "cpu_usage_idle"},
|
||||||
}
|
}
|
||||||
|
require.NoError(t, f.Compile())
|
||||||
|
|
||||||
passes := []string{
|
passes := []string{
|
||||||
"foo",
|
"foo",
|
||||||
@@ -111,13 +169,13 @@ func TestFilter_FieldPass(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, measurement := range passes {
|
for _, measurement := range passes {
|
||||||
if !f.ShouldFieldsPass(measurement) {
|
if !f.shouldFieldPass(measurement) {
|
||||||
t.Errorf("Expected measurement %s to pass", measurement)
|
t.Errorf("Expected measurement %s to pass", measurement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, measurement := range drops {
|
for _, measurement := range drops {
|
||||||
if f.ShouldFieldsPass(measurement) {
|
if f.shouldFieldPass(measurement) {
|
||||||
t.Errorf("Expected measurement %s to drop", measurement)
|
t.Errorf("Expected measurement %s to drop", measurement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,6 +185,7 @@ func TestFilter_FieldDrop(t *testing.T) {
|
|||||||
f := Filter{
|
f := Filter{
|
||||||
FieldDrop: []string{"foo*", "cpu_usage_idle"},
|
FieldDrop: []string{"foo*", "cpu_usage_idle"},
|
||||||
}
|
}
|
||||||
|
require.NoError(t, f.Compile())
|
||||||
|
|
||||||
drops := []string{
|
drops := []string{
|
||||||
"foo",
|
"foo",
|
||||||
@@ -144,13 +203,13 @@ func TestFilter_FieldDrop(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, measurement := range passes {
|
for _, measurement := range passes {
|
||||||
if !f.ShouldFieldsPass(measurement) {
|
if !f.shouldFieldPass(measurement) {
|
||||||
t.Errorf("Expected measurement %s to pass", measurement)
|
t.Errorf("Expected measurement %s to pass", measurement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, measurement := range drops {
|
for _, measurement := range drops {
|
||||||
if f.ShouldFieldsPass(measurement) {
|
if f.shouldFieldPass(measurement) {
|
||||||
t.Errorf("Expected measurement %s to drop", measurement)
|
t.Errorf("Expected measurement %s to drop", measurement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,6 +228,7 @@ func TestFilter_TagPass(t *testing.T) {
|
|||||||
f := Filter{
|
f := Filter{
|
||||||
TagPass: filters,
|
TagPass: filters,
|
||||||
}
|
}
|
||||||
|
require.NoError(t, f.Compile())
|
||||||
|
|
||||||
passes := []map[string]string{
|
passes := []map[string]string{
|
||||||
{"cpu": "cpu-total"},
|
{"cpu": "cpu-total"},
|
||||||
@@ -187,13 +247,13 @@ func TestFilter_TagPass(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tags := range passes {
|
for _, tags := range passes {
|
||||||
if !f.ShouldTagsPass(tags) {
|
if !f.shouldTagsPass(tags) {
|
||||||
t.Errorf("Expected tags %v to pass", tags)
|
t.Errorf("Expected tags %v to pass", tags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tags := range drops {
|
for _, tags := range drops {
|
||||||
if f.ShouldTagsPass(tags) {
|
if f.shouldTagsPass(tags) {
|
||||||
t.Errorf("Expected tags %v to drop", tags)
|
t.Errorf("Expected tags %v to drop", tags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -212,6 +272,7 @@ func TestFilter_TagDrop(t *testing.T) {
|
|||||||
f := Filter{
|
f := Filter{
|
||||||
TagDrop: filters,
|
TagDrop: filters,
|
||||||
}
|
}
|
||||||
|
require.NoError(t, f.Compile())
|
||||||
|
|
||||||
drops := []map[string]string{
|
drops := []map[string]string{
|
||||||
{"cpu": "cpu-total"},
|
{"cpu": "cpu-total"},
|
||||||
@@ -230,14 +291,69 @@ func TestFilter_TagDrop(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tags := range passes {
|
for _, tags := range passes {
|
||||||
if !f.ShouldTagsPass(tags) {
|
if !f.shouldTagsPass(tags) {
|
||||||
t.Errorf("Expected tags %v to pass", tags)
|
t.Errorf("Expected tags %v to pass", tags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tags := range drops {
|
for _, tags := range drops {
|
||||||
if f.ShouldTagsPass(tags) {
|
if f.shouldTagsPass(tags) {
|
||||||
t.Errorf("Expected tags %v to drop", tags)
|
t.Errorf("Expected tags %v to drop", tags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFilter_FilterTagsNoMatches(t *testing.T) {
|
||||||
|
pretags := map[string]string{
|
||||||
|
"host": "localhost",
|
||||||
|
"mytag": "foobar",
|
||||||
|
}
|
||||||
|
f := Filter{
|
||||||
|
TagExclude: []string{"nomatch"},
|
||||||
|
}
|
||||||
|
require.NoError(t, f.Compile())
|
||||||
|
|
||||||
|
f.filterTags(pretags)
|
||||||
|
assert.Equal(t, map[string]string{
|
||||||
|
"host": "localhost",
|
||||||
|
"mytag": "foobar",
|
||||||
|
}, pretags)
|
||||||
|
|
||||||
|
f = Filter{
|
||||||
|
TagInclude: []string{"nomatch"},
|
||||||
|
}
|
||||||
|
require.NoError(t, f.Compile())
|
||||||
|
|
||||||
|
f.filterTags(pretags)
|
||||||
|
assert.Equal(t, map[string]string{}, pretags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilter_FilterTagsMatches(t *testing.T) {
|
||||||
|
pretags := map[string]string{
|
||||||
|
"host": "localhost",
|
||||||
|
"mytag": "foobar",
|
||||||
|
}
|
||||||
|
f := Filter{
|
||||||
|
TagExclude: []string{"ho*"},
|
||||||
|
}
|
||||||
|
require.NoError(t, f.Compile())
|
||||||
|
|
||||||
|
f.filterTags(pretags)
|
||||||
|
assert.Equal(t, map[string]string{
|
||||||
|
"mytag": "foobar",
|
||||||
|
}, pretags)
|
||||||
|
|
||||||
|
pretags = map[string]string{
|
||||||
|
"host": "localhost",
|
||||||
|
"mytag": "foobar",
|
||||||
|
}
|
||||||
|
f = Filter{
|
||||||
|
TagInclude: []string{"my*"},
|
||||||
|
}
|
||||||
|
require.NoError(t, f.Compile())
|
||||||
|
|
||||||
|
f.filterTags(pretags)
|
||||||
|
assert.Equal(t, map[string]string{
|
||||||
|
"mytag": "foobar",
|
||||||
|
}, pretags)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package internal_models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|||||||
@@ -1,49 +1,55 @@
|
|||||||
package internal_models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/internal/buffer"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Default number of metrics kept between flushes.
|
// Default size of metrics batch size.
|
||||||
DEFAULT_METRIC_BUFFER_LIMIT = 1000
|
DEFAULT_METRIC_BATCH_SIZE = 1000
|
||||||
|
|
||||||
// Limit how many full metric buffers are kept due to failed writes.
|
// Default number of metrics kept. It should be a multiple of batch size.
|
||||||
FULL_METRIC_BUFFERS_LIMIT = 100
|
DEFAULT_METRIC_BUFFER_LIMIT = 10000
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// RunningOutput contains the output configuration
|
||||||
type RunningOutput struct {
|
type RunningOutput struct {
|
||||||
Name string
|
Name string
|
||||||
Output telegraf.Output
|
Output telegraf.Output
|
||||||
Config *OutputConfig
|
Config *OutputConfig
|
||||||
Quiet bool
|
Quiet bool
|
||||||
MetricBufferLimit int
|
MetricBufferLimit int
|
||||||
FlushBufferWhenFull bool
|
MetricBatchSize int
|
||||||
|
|
||||||
metrics []telegraf.Metric
|
metrics *buffer.Buffer
|
||||||
tmpmetrics map[int][]telegraf.Metric
|
failMetrics *buffer.Buffer
|
||||||
overwriteI int
|
|
||||||
mapI int
|
|
||||||
|
|
||||||
sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRunningOutput(
|
func NewRunningOutput(
|
||||||
name string,
|
name string,
|
||||||
output telegraf.Output,
|
output telegraf.Output,
|
||||||
conf *OutputConfig,
|
conf *OutputConfig,
|
||||||
|
batchSize int,
|
||||||
|
bufferLimit int,
|
||||||
) *RunningOutput {
|
) *RunningOutput {
|
||||||
|
if bufferLimit == 0 {
|
||||||
|
bufferLimit = DEFAULT_METRIC_BUFFER_LIMIT
|
||||||
|
}
|
||||||
|
if batchSize == 0 {
|
||||||
|
batchSize = DEFAULT_METRIC_BATCH_SIZE
|
||||||
|
}
|
||||||
ro := &RunningOutput{
|
ro := &RunningOutput{
|
||||||
Name: name,
|
Name: name,
|
||||||
metrics: make([]telegraf.Metric, 0),
|
metrics: buffer.NewBuffer(batchSize),
|
||||||
tmpmetrics: make(map[int][]telegraf.Metric),
|
failMetrics: buffer.NewBuffer(bufferLimit),
|
||||||
Output: output,
|
Output: output,
|
||||||
Config: conf,
|
Config: conf,
|
||||||
MetricBufferLimit: DEFAULT_METRIC_BUFFER_LIMIT,
|
MetricBufferLimit: bufferLimit,
|
||||||
|
MetricBatchSize: batchSize,
|
||||||
}
|
}
|
||||||
return ro
|
return ro
|
||||||
}
|
}
|
||||||
@@ -51,77 +57,84 @@ func NewRunningOutput(
|
|||||||
// AddMetric adds a metric to the output. This function can also write cached
|
// AddMetric adds a metric to the output. This function can also write cached
|
||||||
// points if FlushBufferWhenFull is true.
|
// points if FlushBufferWhenFull is true.
|
||||||
func (ro *RunningOutput) AddMetric(metric telegraf.Metric) {
|
func (ro *RunningOutput) AddMetric(metric telegraf.Metric) {
|
||||||
if ro.Config.Filter.IsActive {
|
// Filter any tagexclude/taginclude parameters before adding metric
|
||||||
if !ro.Config.Filter.ShouldMetricPass(metric) {
|
if ro.Config.Filter.IsActive() {
|
||||||
|
// In order to filter out tags, we need to create a new metric, since
|
||||||
|
// metrics are immutable once created.
|
||||||
|
name := metric.Name()
|
||||||
|
tags := metric.Tags()
|
||||||
|
fields := metric.Fields()
|
||||||
|
t := metric.Time()
|
||||||
|
if ok := ro.Config.Filter.Apply(name, fields, tags); !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// error is not possible if creating from another metric, so ignore.
|
||||||
|
metric, _ = telegraf.NewMetric(name, tags, fields, t)
|
||||||
}
|
}
|
||||||
ro.Lock()
|
|
||||||
defer ro.Unlock()
|
|
||||||
|
|
||||||
if len(ro.metrics) < ro.MetricBufferLimit {
|
ro.metrics.Add(metric)
|
||||||
ro.metrics = append(ro.metrics, metric)
|
if ro.metrics.Len() == ro.MetricBatchSize {
|
||||||
} else {
|
batch := ro.metrics.Batch(ro.MetricBatchSize)
|
||||||
if ro.FlushBufferWhenFull {
|
err := ro.write(batch)
|
||||||
ro.metrics = append(ro.metrics, metric)
|
if err != nil {
|
||||||
tmpmetrics := make([]telegraf.Metric, len(ro.metrics))
|
ro.failMetrics.Add(batch...)
|
||||||
copy(tmpmetrics, ro.metrics)
|
|
||||||
ro.metrics = make([]telegraf.Metric, 0)
|
|
||||||
err := ro.write(tmpmetrics)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("ERROR writing full metric buffer to output %s, %s",
|
|
||||||
ro.Name, err)
|
|
||||||
if len(ro.tmpmetrics) == FULL_METRIC_BUFFERS_LIMIT {
|
|
||||||
ro.mapI = 0
|
|
||||||
// overwrite one
|
|
||||||
ro.tmpmetrics[ro.mapI] = tmpmetrics
|
|
||||||
ro.mapI++
|
|
||||||
} else {
|
|
||||||
ro.tmpmetrics[ro.mapI] = tmpmetrics
|
|
||||||
ro.mapI++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ro.overwriteI == 0 {
|
|
||||||
log.Printf("WARNING: overwriting cached metrics, you may want to " +
|
|
||||||
"increase the metric_buffer_limit setting in your [agent] " +
|
|
||||||
"config if you do not wish to overwrite metrics.\n")
|
|
||||||
}
|
|
||||||
if ro.overwriteI == len(ro.metrics) {
|
|
||||||
ro.overwriteI = 0
|
|
||||||
}
|
|
||||||
ro.metrics[ro.overwriteI] = metric
|
|
||||||
ro.overwriteI++
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write writes all cached points to this output.
|
// Write writes all cached points to this output.
|
||||||
func (ro *RunningOutput) Write() error {
|
func (ro *RunningOutput) Write() error {
|
||||||
ro.Lock()
|
if !ro.Quiet {
|
||||||
defer ro.Unlock()
|
log.Printf("Output [%s] buffer fullness: %d / %d metrics. "+
|
||||||
err := ro.write(ro.metrics)
|
"Total gathered metrics: %d. Total dropped metrics: %d.",
|
||||||
if err != nil {
|
ro.Name,
|
||||||
return err
|
ro.failMetrics.Len()+ro.metrics.Len(),
|
||||||
} else {
|
ro.MetricBufferLimit,
|
||||||
ro.metrics = make([]telegraf.Metric, 0)
|
ro.metrics.Total(),
|
||||||
ro.overwriteI = 0
|
ro.metrics.Drops()+ro.failMetrics.Drops())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write any cached metric buffers that failed previously
|
var err error
|
||||||
for i, tmpmetrics := range ro.tmpmetrics {
|
if !ro.failMetrics.IsEmpty() {
|
||||||
if err := ro.write(tmpmetrics); err != nil {
|
bufLen := ro.failMetrics.Len()
|
||||||
return err
|
// how many batches of failed writes we need to write.
|
||||||
} else {
|
nBatches := bufLen/ro.MetricBatchSize + 1
|
||||||
delete(ro.tmpmetrics, i)
|
batchSize := ro.MetricBatchSize
|
||||||
|
|
||||||
|
for i := 0; i < nBatches; i++ {
|
||||||
|
// If it's the last batch, only grab the metrics that have not had
|
||||||
|
// a write attempt already (this is primarily to preserve order).
|
||||||
|
if i == nBatches-1 {
|
||||||
|
batchSize = bufLen % ro.MetricBatchSize
|
||||||
|
}
|
||||||
|
batch := ro.failMetrics.Batch(batchSize)
|
||||||
|
// If we've already failed previous writes, don't bother trying to
|
||||||
|
// write to this output again. We are not exiting the loop just so
|
||||||
|
// that we can rotate the metrics to preserve order.
|
||||||
|
if err == nil {
|
||||||
|
err = ro.write(batch)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
ro.failMetrics.Add(batch...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
batch := ro.metrics.Batch(ro.MetricBatchSize)
|
||||||
|
// see comment above about not trying to write to an already failed output.
|
||||||
|
// if ro.failMetrics is empty then err will always be nil at this point.
|
||||||
|
if err == nil {
|
||||||
|
err = ro.write(batch)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
ro.failMetrics.Add(batch...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ro *RunningOutput) write(metrics []telegraf.Metric) error {
|
func (ro *RunningOutput) write(metrics []telegraf.Metric) error {
|
||||||
if len(metrics) == 0 {
|
if metrics == nil || len(metrics) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
@@ -129,8 +142,8 @@ func (ro *RunningOutput) write(metrics []telegraf.Metric) error {
|
|||||||
elapsed := time.Since(start)
|
elapsed := time.Since(start)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if !ro.Quiet {
|
if !ro.Quiet {
|
||||||
log.Printf("Wrote %d metrics to output %s in %s\n",
|
log.Printf("Output [%s] wrote batch of %d metrics in %s\n",
|
||||||
len(metrics), ro.Name, elapsed)
|
ro.Name, len(metrics), elapsed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
package internal_models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -29,16 +28,92 @@ var next5 = []telegraf.Metric{
|
|||||||
testutil.TestMetric(101, "metric10"),
|
testutil.TestMetric(101, "metric10"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that we can write metrics with simple default setup.
|
// Benchmark adding metrics.
|
||||||
func TestRunningOutputDefault(t *testing.T) {
|
func BenchmarkRunningOutputAddWrite(b *testing.B) {
|
||||||
conf := &OutputConfig{
|
conf := &OutputConfig{
|
||||||
Filter: Filter{
|
Filter: Filter{},
|
||||||
IsActive: false,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m := &perfOutput{}
|
||||||
|
ro := NewRunningOutput("test", m, conf, 1000, 10000)
|
||||||
|
ro.Quiet = true
|
||||||
|
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
ro.AddMetric(first5[0])
|
||||||
|
ro.Write()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmark adding metrics.
|
||||||
|
func BenchmarkRunningOutputAddWriteEvery100(b *testing.B) {
|
||||||
|
conf := &OutputConfig{
|
||||||
|
Filter: Filter{},
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &perfOutput{}
|
||||||
|
ro := NewRunningOutput("test", m, conf, 1000, 10000)
|
||||||
|
ro.Quiet = true
|
||||||
|
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
ro.AddMetric(first5[0])
|
||||||
|
if n%100 == 0 {
|
||||||
|
ro.Write()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmark adding metrics.
|
||||||
|
func BenchmarkRunningOutputAddFailWrites(b *testing.B) {
|
||||||
|
conf := &OutputConfig{
|
||||||
|
Filter: Filter{},
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &perfOutput{}
|
||||||
|
m.failWrite = true
|
||||||
|
ro := NewRunningOutput("test", m, conf, 1000, 10000)
|
||||||
|
ro.Quiet = true
|
||||||
|
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
ro.AddMetric(first5[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that NameDrop filters ger properly applied.
|
||||||
|
func TestRunningOutput_DropFilter(t *testing.T) {
|
||||||
|
conf := &OutputConfig{
|
||||||
|
Filter: Filter{
|
||||||
|
NameDrop: []string{"metric1", "metric2"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.NoError(t, conf.Filter.Compile())
|
||||||
|
|
||||||
m := &mockOutput{}
|
m := &mockOutput{}
|
||||||
ro := NewRunningOutput("test", m, conf)
|
ro := NewRunningOutput("test", m, conf, 1000, 10000)
|
||||||
|
|
||||||
|
for _, metric := range first5 {
|
||||||
|
ro.AddMetric(metric)
|
||||||
|
}
|
||||||
|
for _, metric := range next5 {
|
||||||
|
ro.AddMetric(metric)
|
||||||
|
}
|
||||||
|
assert.Len(t, m.Metrics(), 0)
|
||||||
|
|
||||||
|
err := ro.Write()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, m.Metrics(), 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that NameDrop filters without a match do nothing.
|
||||||
|
func TestRunningOutput_PassFilter(t *testing.T) {
|
||||||
|
conf := &OutputConfig{
|
||||||
|
Filter: Filter{
|
||||||
|
NameDrop: []string{"metric1000", "foo*"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.NoError(t, conf.Filter.Compile())
|
||||||
|
|
||||||
|
m := &mockOutput{}
|
||||||
|
ro := NewRunningOutput("test", m, conf, 1000, 10000)
|
||||||
|
|
||||||
for _, metric := range first5 {
|
for _, metric := range first5 {
|
||||||
ro.AddMetric(metric)
|
ro.AddMetric(metric)
|
||||||
@@ -53,50 +128,102 @@ func TestRunningOutputDefault(t *testing.T) {
|
|||||||
assert.Len(t, m.Metrics(), 10)
|
assert.Len(t, m.Metrics(), 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that the first metric gets overwritten if there is a buffer overflow.
|
// Test that tags are properly included
|
||||||
func TestRunningOutputOverwrite(t *testing.T) {
|
func TestRunningOutput_TagIncludeNoMatch(t *testing.T) {
|
||||||
conf := &OutputConfig{
|
conf := &OutputConfig{
|
||||||
Filter: Filter{
|
Filter: Filter{
|
||||||
IsActive: false,
|
|
||||||
|
TagInclude: []string{"nothing*"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
assert.NoError(t, conf.Filter.Compile())
|
||||||
|
|
||||||
m := &mockOutput{}
|
m := &mockOutput{}
|
||||||
ro := NewRunningOutput("test", m, conf)
|
ro := NewRunningOutput("test", m, conf, 1000, 10000)
|
||||||
ro.MetricBufferLimit = 4
|
|
||||||
|
|
||||||
for _, metric := range first5 {
|
ro.AddMetric(first5[0])
|
||||||
ro.AddMetric(metric)
|
assert.Len(t, m.Metrics(), 0)
|
||||||
}
|
|
||||||
require.Len(t, m.Metrics(), 0)
|
|
||||||
|
|
||||||
err := ro.Write()
|
err := ro.Write()
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
require.Len(t, m.Metrics(), 4)
|
assert.Len(t, m.Metrics(), 1)
|
||||||
|
assert.Empty(t, m.Metrics()[0].Tags())
|
||||||
var expected, actual []string
|
|
||||||
for i, exp := range first5[1:] {
|
|
||||||
expected = append(expected, exp.String())
|
|
||||||
actual = append(actual, m.Metrics()[i].String())
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(expected)
|
|
||||||
sort.Strings(actual)
|
|
||||||
|
|
||||||
assert.Equal(t, expected, actual)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that multiple buffer overflows are handled properly.
|
// Test that tags are properly excluded
|
||||||
func TestRunningOutputMultiOverwrite(t *testing.T) {
|
func TestRunningOutput_TagExcludeMatch(t *testing.T) {
|
||||||
conf := &OutputConfig{
|
conf := &OutputConfig{
|
||||||
Filter: Filter{
|
Filter: Filter{
|
||||||
IsActive: false,
|
|
||||||
|
TagExclude: []string{"tag*"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
assert.NoError(t, conf.Filter.Compile())
|
||||||
|
|
||||||
|
m := &mockOutput{}
|
||||||
|
ro := NewRunningOutput("test", m, conf, 1000, 10000)
|
||||||
|
|
||||||
|
ro.AddMetric(first5[0])
|
||||||
|
assert.Len(t, m.Metrics(), 0)
|
||||||
|
|
||||||
|
err := ro.Write()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, m.Metrics(), 1)
|
||||||
|
assert.Len(t, m.Metrics()[0].Tags(), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that tags are properly Excluded
|
||||||
|
func TestRunningOutput_TagExcludeNoMatch(t *testing.T) {
|
||||||
|
conf := &OutputConfig{
|
||||||
|
Filter: Filter{
|
||||||
|
|
||||||
|
TagExclude: []string{"nothing*"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.NoError(t, conf.Filter.Compile())
|
||||||
|
|
||||||
|
m := &mockOutput{}
|
||||||
|
ro := NewRunningOutput("test", m, conf, 1000, 10000)
|
||||||
|
|
||||||
|
ro.AddMetric(first5[0])
|
||||||
|
assert.Len(t, m.Metrics(), 0)
|
||||||
|
|
||||||
|
err := ro.Write()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, m.Metrics(), 1)
|
||||||
|
assert.Len(t, m.Metrics()[0].Tags(), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that tags are properly included
|
||||||
|
func TestRunningOutput_TagIncludeMatch(t *testing.T) {
|
||||||
|
conf := &OutputConfig{
|
||||||
|
Filter: Filter{
|
||||||
|
|
||||||
|
TagInclude: []string{"tag*"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.NoError(t, conf.Filter.Compile())
|
||||||
|
|
||||||
|
m := &mockOutput{}
|
||||||
|
ro := NewRunningOutput("test", m, conf, 1000, 10000)
|
||||||
|
|
||||||
|
ro.AddMetric(first5[0])
|
||||||
|
assert.Len(t, m.Metrics(), 0)
|
||||||
|
|
||||||
|
err := ro.Write()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, m.Metrics(), 1)
|
||||||
|
assert.Len(t, m.Metrics()[0].Tags(), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that we can write metrics with simple default setup.
|
||||||
|
func TestRunningOutputDefault(t *testing.T) {
|
||||||
|
conf := &OutputConfig{
|
||||||
|
Filter: Filter{},
|
||||||
|
}
|
||||||
|
|
||||||
m := &mockOutput{}
|
m := &mockOutput{}
|
||||||
ro := NewRunningOutput("test", m, conf)
|
ro := NewRunningOutput("test", m, conf, 1000, 10000)
|
||||||
ro.MetricBufferLimit = 3
|
|
||||||
|
|
||||||
for _, metric := range first5 {
|
for _, metric := range first5 {
|
||||||
ro.AddMetric(metric)
|
ro.AddMetric(metric)
|
||||||
@@ -104,39 +231,24 @@ func TestRunningOutputMultiOverwrite(t *testing.T) {
|
|||||||
for _, metric := range next5 {
|
for _, metric := range next5 {
|
||||||
ro.AddMetric(metric)
|
ro.AddMetric(metric)
|
||||||
}
|
}
|
||||||
require.Len(t, m.Metrics(), 0)
|
assert.Len(t, m.Metrics(), 0)
|
||||||
|
|
||||||
err := ro.Write()
|
err := ro.Write()
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
require.Len(t, m.Metrics(), 3)
|
assert.Len(t, m.Metrics(), 10)
|
||||||
|
|
||||||
var expected, actual []string
|
|
||||||
for i, exp := range next5[2:] {
|
|
||||||
expected = append(expected, exp.String())
|
|
||||||
actual = append(actual, m.Metrics()[i].String())
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(expected)
|
|
||||||
sort.Strings(actual)
|
|
||||||
|
|
||||||
assert.Equal(t, expected, actual)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that running output doesn't flush until it's full when
|
// Test that running output doesn't flush until it's full when
|
||||||
// FlushBufferWhenFull is set.
|
// FlushBufferWhenFull is set.
|
||||||
func TestRunningOutputFlushWhenFull(t *testing.T) {
|
func TestRunningOutputFlushWhenFull(t *testing.T) {
|
||||||
conf := &OutputConfig{
|
conf := &OutputConfig{
|
||||||
Filter: Filter{
|
Filter: Filter{},
|
||||||
IsActive: false,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m := &mockOutput{}
|
m := &mockOutput{}
|
||||||
ro := NewRunningOutput("test", m, conf)
|
ro := NewRunningOutput("test", m, conf, 6, 10)
|
||||||
ro.FlushBufferWhenFull = true
|
|
||||||
ro.MetricBufferLimit = 5
|
|
||||||
|
|
||||||
// Fill buffer to limit
|
// Fill buffer to 1 under limit
|
||||||
for _, metric := range first5 {
|
for _, metric := range first5 {
|
||||||
ro.AddMetric(metric)
|
ro.AddMetric(metric)
|
||||||
}
|
}
|
||||||
@@ -159,15 +271,11 @@ func TestRunningOutputFlushWhenFull(t *testing.T) {
|
|||||||
// FlushBufferWhenFull is set, twice.
|
// FlushBufferWhenFull is set, twice.
|
||||||
func TestRunningOutputMultiFlushWhenFull(t *testing.T) {
|
func TestRunningOutputMultiFlushWhenFull(t *testing.T) {
|
||||||
conf := &OutputConfig{
|
conf := &OutputConfig{
|
||||||
Filter: Filter{
|
Filter: Filter{},
|
||||||
IsActive: false,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m := &mockOutput{}
|
m := &mockOutput{}
|
||||||
ro := NewRunningOutput("test", m, conf)
|
ro := NewRunningOutput("test", m, conf, 4, 12)
|
||||||
ro.FlushBufferWhenFull = true
|
|
||||||
ro.MetricBufferLimit = 4
|
|
||||||
|
|
||||||
// Fill buffer past limit twive
|
// Fill buffer past limit twive
|
||||||
for _, metric := range first5 {
|
for _, metric := range first5 {
|
||||||
@@ -177,23 +285,19 @@ func TestRunningOutputMultiFlushWhenFull(t *testing.T) {
|
|||||||
ro.AddMetric(metric)
|
ro.AddMetric(metric)
|
||||||
}
|
}
|
||||||
// flushed twice
|
// flushed twice
|
||||||
assert.Len(t, m.Metrics(), 10)
|
assert.Len(t, m.Metrics(), 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunningOutputWriteFail(t *testing.T) {
|
func TestRunningOutputWriteFail(t *testing.T) {
|
||||||
conf := &OutputConfig{
|
conf := &OutputConfig{
|
||||||
Filter: Filter{
|
Filter: Filter{},
|
||||||
IsActive: false,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m := &mockOutput{}
|
m := &mockOutput{}
|
||||||
m.failWrite = true
|
m.failWrite = true
|
||||||
ro := NewRunningOutput("test", m, conf)
|
ro := NewRunningOutput("test", m, conf, 4, 12)
|
||||||
ro.FlushBufferWhenFull = true
|
|
||||||
ro.MetricBufferLimit = 4
|
|
||||||
|
|
||||||
// Fill buffer past limit twice
|
// Fill buffer to limit twice
|
||||||
for _, metric := range first5 {
|
for _, metric := range first5 {
|
||||||
ro.AddMetric(metric)
|
ro.AddMetric(metric)
|
||||||
}
|
}
|
||||||
@@ -216,6 +320,155 @@ func TestRunningOutputWriteFail(t *testing.T) {
|
|||||||
assert.Len(t, m.Metrics(), 10)
|
assert.Len(t, m.Metrics(), 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify that the order of points is preserved during a write failure.
|
||||||
|
func TestRunningOutputWriteFailOrder(t *testing.T) {
|
||||||
|
conf := &OutputConfig{
|
||||||
|
Filter: Filter{},
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &mockOutput{}
|
||||||
|
m.failWrite = true
|
||||||
|
ro := NewRunningOutput("test", m, conf, 100, 1000)
|
||||||
|
|
||||||
|
// add 5 metrics
|
||||||
|
for _, metric := range first5 {
|
||||||
|
ro.AddMetric(metric)
|
||||||
|
}
|
||||||
|
// no successful flush yet
|
||||||
|
assert.Len(t, m.Metrics(), 0)
|
||||||
|
|
||||||
|
// Write fails
|
||||||
|
err := ro.Write()
|
||||||
|
require.Error(t, err)
|
||||||
|
// no successful flush yet
|
||||||
|
assert.Len(t, m.Metrics(), 0)
|
||||||
|
|
||||||
|
m.failWrite = false
|
||||||
|
// add 5 more metrics
|
||||||
|
for _, metric := range next5 {
|
||||||
|
ro.AddMetric(metric)
|
||||||
|
}
|
||||||
|
err = ro.Write()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Verify that 10 metrics were written
|
||||||
|
assert.Len(t, m.Metrics(), 10)
|
||||||
|
// Verify that they are in order
|
||||||
|
expected := append(first5, next5...)
|
||||||
|
assert.Equal(t, expected, m.Metrics())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the order of points is preserved during many write failures.
|
||||||
|
func TestRunningOutputWriteFailOrder2(t *testing.T) {
|
||||||
|
conf := &OutputConfig{
|
||||||
|
Filter: Filter{},
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &mockOutput{}
|
||||||
|
m.failWrite = true
|
||||||
|
ro := NewRunningOutput("test", m, conf, 5, 100)
|
||||||
|
|
||||||
|
// add 5 metrics
|
||||||
|
for _, metric := range first5 {
|
||||||
|
ro.AddMetric(metric)
|
||||||
|
}
|
||||||
|
// Write fails
|
||||||
|
err := ro.Write()
|
||||||
|
require.Error(t, err)
|
||||||
|
// no successful flush yet
|
||||||
|
assert.Len(t, m.Metrics(), 0)
|
||||||
|
|
||||||
|
// add 5 metrics
|
||||||
|
for _, metric := range next5 {
|
||||||
|
ro.AddMetric(metric)
|
||||||
|
}
|
||||||
|
// Write fails
|
||||||
|
err = ro.Write()
|
||||||
|
require.Error(t, err)
|
||||||
|
// no successful flush yet
|
||||||
|
assert.Len(t, m.Metrics(), 0)
|
||||||
|
|
||||||
|
// add 5 metrics
|
||||||
|
for _, metric := range first5 {
|
||||||
|
ro.AddMetric(metric)
|
||||||
|
}
|
||||||
|
// Write fails
|
||||||
|
err = ro.Write()
|
||||||
|
require.Error(t, err)
|
||||||
|
// no successful flush yet
|
||||||
|
assert.Len(t, m.Metrics(), 0)
|
||||||
|
|
||||||
|
// add 5 metrics
|
||||||
|
for _, metric := range next5 {
|
||||||
|
ro.AddMetric(metric)
|
||||||
|
}
|
||||||
|
// Write fails
|
||||||
|
err = ro.Write()
|
||||||
|
require.Error(t, err)
|
||||||
|
// no successful flush yet
|
||||||
|
assert.Len(t, m.Metrics(), 0)
|
||||||
|
|
||||||
|
m.failWrite = false
|
||||||
|
err = ro.Write()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Verify that 10 metrics were written
|
||||||
|
assert.Len(t, m.Metrics(), 20)
|
||||||
|
// Verify that they are in order
|
||||||
|
expected := append(first5, next5...)
|
||||||
|
expected = append(expected, first5...)
|
||||||
|
expected = append(expected, next5...)
|
||||||
|
assert.Equal(t, expected, m.Metrics())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the order of points is preserved when there is a remainder
|
||||||
|
// of points for the batch.
|
||||||
|
//
|
||||||
|
// ie, with a batch size of 5:
|
||||||
|
//
|
||||||
|
// 1 2 3 4 5 6 <-- order, failed points
|
||||||
|
// 6 1 2 3 4 5 <-- order, after 1st write failure (1 2 3 4 5 was batch)
|
||||||
|
// 1 2 3 4 5 6 <-- order, after 2nd write failure, (6 was batch)
|
||||||
|
//
|
||||||
|
func TestRunningOutputWriteFailOrder3(t *testing.T) {
|
||||||
|
conf := &OutputConfig{
|
||||||
|
Filter: Filter{},
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &mockOutput{}
|
||||||
|
m.failWrite = true
|
||||||
|
ro := NewRunningOutput("test", m, conf, 5, 1000)
|
||||||
|
|
||||||
|
// add 5 metrics
|
||||||
|
for _, metric := range first5 {
|
||||||
|
ro.AddMetric(metric)
|
||||||
|
}
|
||||||
|
// no successful flush yet
|
||||||
|
assert.Len(t, m.Metrics(), 0)
|
||||||
|
|
||||||
|
// Write fails
|
||||||
|
err := ro.Write()
|
||||||
|
require.Error(t, err)
|
||||||
|
// no successful flush yet
|
||||||
|
assert.Len(t, m.Metrics(), 0)
|
||||||
|
|
||||||
|
// add and attempt to write a single metric:
|
||||||
|
ro.AddMetric(next5[0])
|
||||||
|
err = ro.Write()
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
// unset fail and write metrics
|
||||||
|
m.failWrite = false
|
||||||
|
err = ro.Write()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Verify that 6 metrics were written
|
||||||
|
assert.Len(t, m.Metrics(), 6)
|
||||||
|
// Verify that they are in order
|
||||||
|
expected := append(first5, next5[0])
|
||||||
|
assert.Equal(t, expected, m.Metrics())
|
||||||
|
}
|
||||||
|
|
||||||
type mockOutput struct {
|
type mockOutput struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
|
|
||||||
@@ -263,3 +516,31 @@ func (m *mockOutput) Metrics() []telegraf.Metric {
|
|||||||
defer m.Unlock()
|
defer m.Unlock()
|
||||||
return m.metrics
|
return m.metrics
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type perfOutput struct {
|
||||||
|
// if true, mock a write failure
|
||||||
|
failWrite bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *perfOutput) Connect() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *perfOutput) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *perfOutput) Description() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *perfOutput) SampleConfig() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *perfOutput) Write(metrics []telegraf.Metric) error {
|
||||||
|
if m.failWrite {
|
||||||
|
return fmt.Errorf("Failed Write!")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
75
metric.go
75
metric.go
@@ -6,6 +6,17 @@ import (
|
|||||||
"github.com/influxdata/influxdb/client/v2"
|
"github.com/influxdata/influxdb/client/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ValueType is an enumeration of metric types that represent a simple value.
|
||||||
|
type ValueType int
|
||||||
|
|
||||||
|
// Possible values for the ValueType enum.
|
||||||
|
const (
|
||||||
|
_ ValueType = iota
|
||||||
|
Counter
|
||||||
|
Gauge
|
||||||
|
Untyped
|
||||||
|
)
|
||||||
|
|
||||||
type Metric interface {
|
type Metric interface {
|
||||||
// Name returns the measurement name of the metric
|
// Name returns the measurement name of the metric
|
||||||
Name() string
|
Name() string
|
||||||
@@ -16,6 +27,9 @@ type Metric interface {
|
|||||||
// Time return the timestamp for the metric
|
// Time return the timestamp for the metric
|
||||||
Time() time.Time
|
Time() time.Time
|
||||||
|
|
||||||
|
// Type returns the metric type. Can be either telegraf.Gauge or telegraf.Counter
|
||||||
|
Type() ValueType
|
||||||
|
|
||||||
// UnixNano returns the unix nano time of the metric
|
// UnixNano returns the unix nano time of the metric
|
||||||
UnixNano() int64
|
UnixNano() int64
|
||||||
|
|
||||||
@@ -35,29 +49,62 @@ type Metric interface {
|
|||||||
// metric is a wrapper of the influxdb client.Point struct
|
// metric is a wrapper of the influxdb client.Point struct
|
||||||
type metric struct {
|
type metric struct {
|
||||||
pt *client.Point
|
pt *client.Point
|
||||||
|
|
||||||
|
mType ValueType
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMetric returns a metric with the given timestamp. If a timestamp is not
|
// NewMetric returns an untyped metric.
|
||||||
// given, then data is sent to the database without a timestamp, in which case
|
|
||||||
// the server will assign local time upon reception. NOTE: it is recommended to
|
|
||||||
// send data with a timestamp.
|
|
||||||
func NewMetric(
|
func NewMetric(
|
||||||
name string,
|
name string,
|
||||||
tags map[string]string,
|
tags map[string]string,
|
||||||
fields map[string]interface{},
|
fields map[string]interface{},
|
||||||
t ...time.Time,
|
t time.Time,
|
||||||
) (Metric, error) {
|
) (Metric, error) {
|
||||||
var T time.Time
|
pt, err := client.NewPoint(name, tags, fields, t)
|
||||||
if len(t) > 0 {
|
|
||||||
T = t[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
pt, err := client.NewPoint(name, tags, fields, T)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &metric{
|
return &metric{
|
||||||
pt: pt,
|
pt: pt,
|
||||||
|
mType: Untyped,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGaugeMetric returns a gauge metric.
|
||||||
|
// Gauge metrics should be used when the metric is can arbitrarily go up and
|
||||||
|
// down. ie, temperature, memory usage, cpu usage, etc.
|
||||||
|
func NewGaugeMetric(
|
||||||
|
name string,
|
||||||
|
tags map[string]string,
|
||||||
|
fields map[string]interface{},
|
||||||
|
t time.Time,
|
||||||
|
) (Metric, error) {
|
||||||
|
pt, err := client.NewPoint(name, tags, fields, t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &metric{
|
||||||
|
pt: pt,
|
||||||
|
mType: Gauge,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCounterMetric returns a Counter metric.
|
||||||
|
// Counter metrics should be used when the metric being created is an
|
||||||
|
// always-increasing counter. ie, net bytes received, requests served, errors, etc.
|
||||||
|
func NewCounterMetric(
|
||||||
|
name string,
|
||||||
|
tags map[string]string,
|
||||||
|
fields map[string]interface{},
|
||||||
|
t time.Time,
|
||||||
|
) (Metric, error) {
|
||||||
|
pt, err := client.NewPoint(name, tags, fields, t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &metric{
|
||||||
|
pt: pt,
|
||||||
|
mType: Counter,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +120,10 @@ func (m *metric) Time() time.Time {
|
|||||||
return m.pt.Time()
|
return m.pt.Time()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *metric) Type() ValueType {
|
||||||
|
return m.mType
|
||||||
|
}
|
||||||
|
|
||||||
func (m *metric) UnixNano() int64 {
|
func (m *metric) UnixNano() int64 {
|
||||||
return m.pt.UnixNano()
|
return m.pt.UnixNano()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,51 @@ func TestNewMetric(t *testing.T) {
|
|||||||
m, err := NewMetric("cpu", tags, fields, now)
|
m, err := NewMetric("cpu", tags, fields, now)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, Untyped, m.Type())
|
||||||
|
assert.Equal(t, tags, m.Tags())
|
||||||
|
assert.Equal(t, fields, m.Fields())
|
||||||
|
assert.Equal(t, "cpu", m.Name())
|
||||||
|
assert.Equal(t, now, m.Time())
|
||||||
|
assert.Equal(t, now.UnixNano(), m.UnixNano())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewGaugeMetric(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
tags := map[string]string{
|
||||||
|
"host": "localhost",
|
||||||
|
"datacenter": "us-east-1",
|
||||||
|
}
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"usage_idle": float64(99),
|
||||||
|
"usage_busy": float64(1),
|
||||||
|
}
|
||||||
|
m, err := NewGaugeMetric("cpu", tags, fields, now)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, Gauge, m.Type())
|
||||||
|
assert.Equal(t, tags, m.Tags())
|
||||||
|
assert.Equal(t, fields, m.Fields())
|
||||||
|
assert.Equal(t, "cpu", m.Name())
|
||||||
|
assert.Equal(t, now, m.Time())
|
||||||
|
assert.Equal(t, now.UnixNano(), m.UnixNano())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewCounterMetric(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
tags := map[string]string{
|
||||||
|
"host": "localhost",
|
||||||
|
"datacenter": "us-east-1",
|
||||||
|
}
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"usage_idle": float64(99),
|
||||||
|
"usage_busy": float64(1),
|
||||||
|
}
|
||||||
|
m, err := NewCounterMetric("cpu", tags, fields, now)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, Counter, m.Type())
|
||||||
assert.Equal(t, tags, m.Tags())
|
assert.Equal(t, tags, m.Tags())
|
||||||
assert.Equal(t, fields, m.Fields())
|
assert.Equal(t, fields, m.Fields())
|
||||||
assert.Equal(t, "cpu", m.Name())
|
assert.Equal(t, "cpu", m.Name())
|
||||||
@@ -51,23 +96,6 @@ func TestNewMetricString(t *testing.T) {
|
|||||||
assert.Equal(t, lineProtoPrecision, m.PrecisionString("s"))
|
assert.Equal(t, lineProtoPrecision, m.PrecisionString("s"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewMetricStringNoTime(t *testing.T) {
|
|
||||||
tags := map[string]string{
|
|
||||||
"host": "localhost",
|
|
||||||
}
|
|
||||||
fields := map[string]interface{}{
|
|
||||||
"usage_idle": float64(99),
|
|
||||||
}
|
|
||||||
m, err := NewMetric("cpu", tags, fields)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
lineProto := fmt.Sprintf("cpu,host=localhost usage_idle=99")
|
|
||||||
assert.Equal(t, lineProto, m.String())
|
|
||||||
|
|
||||||
lineProtoPrecision := fmt.Sprintf("cpu,host=localhost usage_idle=99")
|
|
||||||
assert.Equal(t, lineProtoPrecision, m.PrecisionString("s"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewMetricFailNaN(t *testing.T) {
|
func TestNewMetricFailNaN(t *testing.T) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,14 @@ The example plugin gathers metrics about example things
|
|||||||
- measurement2 has the following tags:
|
- measurement2 has the following tags:
|
||||||
- tag3
|
- tag3
|
||||||
|
|
||||||
|
### Sample Queries:
|
||||||
|
|
||||||
|
These are some useful queries (to generate dashboards or other) to run against data from this plugin:
|
||||||
|
|
||||||
|
```
|
||||||
|
SELECT max(field1), mean(field1), min(field1) FROM measurement1 WHERE tag1=bar AND time > now() - 1h GROUP BY tag
|
||||||
|
```
|
||||||
|
|
||||||
### Example Output:
|
### Example Output:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,104 +1,21 @@
|
|||||||
package aerospike
|
package aerospike
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"errors"
|
||||||
"encoding/binary"
|
"log"
|
||||||
"fmt"
|
|
||||||
"github.com/influxdata/telegraf"
|
|
||||||
"github.com/influxdata/telegraf/plugins/inputs"
|
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/internal/errchan"
|
||||||
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||||||
|
|
||||||
|
as "github.com/aerospike/aerospike-client-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
MSG_HEADER_SIZE = 8
|
|
||||||
MSG_TYPE = 1 // Info is 1
|
|
||||||
MSG_VERSION = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
STATISTICS_COMMAND = []byte("statistics\n")
|
|
||||||
NAMESPACES_COMMAND = []byte("namespaces\n")
|
|
||||||
)
|
|
||||||
|
|
||||||
type aerospikeMessageHeader struct {
|
|
||||||
Version uint8
|
|
||||||
Type uint8
|
|
||||||
DataLen [6]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type aerospikeMessage struct {
|
|
||||||
aerospikeMessageHeader
|
|
||||||
Data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Taken from aerospike-client-go/types/message.go
|
|
||||||
func (msg *aerospikeMessage) Serialize() []byte {
|
|
||||||
msg.DataLen = msgLenToBytes(int64(len(msg.Data)))
|
|
||||||
buf := bytes.NewBuffer([]byte{})
|
|
||||||
binary.Write(buf, binary.BigEndian, msg.aerospikeMessageHeader)
|
|
||||||
binary.Write(buf, binary.BigEndian, msg.Data[:])
|
|
||||||
return buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type aerospikeInfoCommand struct {
|
|
||||||
msg *aerospikeMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
// Taken from aerospike-client-go/info.go
|
|
||||||
func (nfo *aerospikeInfoCommand) parseMultiResponse() (map[string]string, error) {
|
|
||||||
responses := make(map[string]string)
|
|
||||||
offset := int64(0)
|
|
||||||
begin := int64(0)
|
|
||||||
|
|
||||||
dataLen := int64(len(nfo.msg.Data))
|
|
||||||
|
|
||||||
// Create reusable StringBuilder for performance.
|
|
||||||
for offset < dataLen {
|
|
||||||
b := nfo.msg.Data[offset]
|
|
||||||
|
|
||||||
if b == '\t' {
|
|
||||||
name := nfo.msg.Data[begin:offset]
|
|
||||||
offset++
|
|
||||||
begin = offset
|
|
||||||
|
|
||||||
// Parse field value.
|
|
||||||
for offset < dataLen {
|
|
||||||
if nfo.msg.Data[offset] == '\n' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
offset++
|
|
||||||
}
|
|
||||||
|
|
||||||
if offset > begin {
|
|
||||||
value := nfo.msg.Data[begin:offset]
|
|
||||||
responses[string(name)] = string(value)
|
|
||||||
} else {
|
|
||||||
responses[string(name)] = ""
|
|
||||||
}
|
|
||||||
offset++
|
|
||||||
begin = offset
|
|
||||||
} else if b == '\n' {
|
|
||||||
if offset > begin {
|
|
||||||
name := nfo.msg.Data[begin:offset]
|
|
||||||
responses[string(name)] = ""
|
|
||||||
}
|
|
||||||
offset++
|
|
||||||
begin = offset
|
|
||||||
} else {
|
|
||||||
offset++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if offset > begin {
|
|
||||||
name := nfo.msg.Data[begin:offset]
|
|
||||||
responses[string(name)] = ""
|
|
||||||
}
|
|
||||||
return responses, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Aerospike struct {
|
type Aerospike struct {
|
||||||
Servers []string
|
Servers []string
|
||||||
}
|
}
|
||||||
@@ -115,7 +32,7 @@ func (a *Aerospike) SampleConfig() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Aerospike) Description() string {
|
func (a *Aerospike) Description() string {
|
||||||
return "Read stats from an aerospike server"
|
return "Read stats from aerospike server(s)"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Aerospike) Gather(acc telegraf.Accumulator) error {
|
func (a *Aerospike) Gather(acc telegraf.Accumulator) error {
|
||||||
@@ -124,214 +41,114 @@ func (a *Aerospike) Gather(acc telegraf.Accumulator) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
errChan := errchan.New(len(a.Servers))
|
||||||
var outerr error
|
wg.Add(len(a.Servers))
|
||||||
|
|
||||||
for _, server := range a.Servers {
|
for _, server := range a.Servers {
|
||||||
wg.Add(1)
|
go func(serv string) {
|
||||||
go func(server string) {
|
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
outerr = a.gatherServer(server, acc)
|
errChan.C <- a.gatherServer(serv, acc)
|
||||||
}(server)
|
}(server)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
return outerr
|
return errChan.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Aerospike) gatherServer(host string, acc telegraf.Accumulator) error {
|
func (a *Aerospike) gatherServer(hostport string, acc telegraf.Accumulator) error {
|
||||||
aerospikeInfo, err := getMap(STATISTICS_COMMAND, host)
|
host, port, err := net.SplitHostPort(hostport)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Aerospike info failed: %s", err)
|
return err
|
||||||
}
|
}
|
||||||
readAerospikeStats(aerospikeInfo, acc, host, "")
|
|
||||||
namespaces, err := getList(NAMESPACES_COMMAND, host)
|
iport, err := strconv.Atoi(port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Aerospike namespace list failed: %s", err)
|
iport = 3000
|
||||||
}
|
}
|
||||||
for ix := range namespaces {
|
|
||||||
nsInfo, err := getMap([]byte("namespace/"+namespaces[ix]+"\n"), host)
|
c, err := as.NewClient(host, iport)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Aerospike namespace '%s' query failed: %s", namespaces[ix], err)
|
return err
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
nodes := c.GetNodes()
|
||||||
|
for _, n := range nodes {
|
||||||
|
tags := map[string]string{
|
||||||
|
"aerospike_host": hostport,
|
||||||
|
}
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"node_name": n.GetName(),
|
||||||
|
}
|
||||||
|
stats, err := as.RequestNodeStats(n)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for k, v := range stats {
|
||||||
|
val, err := parseValue(v)
|
||||||
|
if err == nil {
|
||||||
|
fields[strings.Replace(k, "-", "_", -1)] = val
|
||||||
|
} else {
|
||||||
|
log.Printf("skipping aerospike field %v with int64 overflow", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acc.AddFields("aerospike_node", fields, tags, time.Now())
|
||||||
|
|
||||||
|
info, err := as.RequestNodeInfo(n, "namespaces")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
namespaces := strings.Split(info["namespaces"], ";")
|
||||||
|
|
||||||
|
for _, namespace := range namespaces {
|
||||||
|
nTags := map[string]string{
|
||||||
|
"aerospike_host": hostport,
|
||||||
|
}
|
||||||
|
nTags["namespace"] = namespace
|
||||||
|
nFields := map[string]interface{}{
|
||||||
|
"node_name": n.GetName(),
|
||||||
|
}
|
||||||
|
info, err := as.RequestNodeInfo(n, "namespace/"+namespace)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
stats := strings.Split(info["namespace/"+namespace], ";")
|
||||||
|
for _, stat := range stats {
|
||||||
|
parts := strings.Split(stat, "=")
|
||||||
|
if len(parts) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val, err := parseValue(parts[1])
|
||||||
|
if err == nil {
|
||||||
|
nFields[strings.Replace(parts[0], "-", "_", -1)] = val
|
||||||
|
} else {
|
||||||
|
log.Printf("skipping aerospike field %v with int64 overflow", parts[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acc.AddFields("aerospike_namespace", nFields, nTags, time.Now())
|
||||||
}
|
}
|
||||||
readAerospikeStats(nsInfo, acc, host, namespaces[ix])
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMap(key []byte, host string) (map[string]string, error) {
|
func parseValue(v string) (interface{}, error) {
|
||||||
data, err := get(key, host)
|
if parsed, err := strconv.ParseInt(v, 10, 64); err == nil {
|
||||||
if err != nil {
|
return parsed, nil
|
||||||
return nil, fmt.Errorf("Failed to get data: %s", err)
|
} else if _, err := strconv.ParseUint(v, 10, 64); err == nil {
|
||||||
|
// int64 overflow, yet valid uint64
|
||||||
|
return nil, errors.New("Number is too large")
|
||||||
|
} else if parsed, err := strconv.ParseBool(v); err == nil {
|
||||||
|
return parsed, nil
|
||||||
|
} else {
|
||||||
|
return v, nil
|
||||||
}
|
}
|
||||||
parsed, err := unmarshalMapInfo(data, string(key))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Failed to unmarshal data: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsed, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getList(key []byte, host string) ([]string, error) {
|
func copyTags(m map[string]string) map[string]string {
|
||||||
data, err := get(key, host)
|
out := make(map[string]string)
|
||||||
if err != nil {
|
for k, v := range m {
|
||||||
return nil, fmt.Errorf("Failed to get data: %s", err)
|
out[k] = v
|
||||||
}
|
}
|
||||||
parsed, err := unmarshalListInfo(data, string(key))
|
return out
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Failed to unmarshal data: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsed, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func get(key []byte, host string) (map[string]string, error) {
|
|
||||||
var err error
|
|
||||||
var data map[string]string
|
|
||||||
|
|
||||||
asInfo := &aerospikeInfoCommand{
|
|
||||||
msg: &aerospikeMessage{
|
|
||||||
aerospikeMessageHeader: aerospikeMessageHeader{
|
|
||||||
Version: uint8(MSG_VERSION),
|
|
||||||
Type: uint8(MSG_TYPE),
|
|
||||||
DataLen: msgLenToBytes(int64(len(key))),
|
|
||||||
},
|
|
||||||
Data: key,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := asInfo.msg.Serialize()
|
|
||||||
addr, err := net.ResolveTCPAddr("tcp", host)
|
|
||||||
if err != nil {
|
|
||||||
return data, fmt.Errorf("Lookup failed for '%s': %s", host, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := net.DialTCP("tcp", nil, addr)
|
|
||||||
if err != nil {
|
|
||||||
return data, fmt.Errorf("Connection failed for '%s': %s", host, err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
_, err = conn.Write(cmd)
|
|
||||||
if err != nil {
|
|
||||||
return data, fmt.Errorf("Failed to send to '%s': %s", host, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
msgHeader := bytes.NewBuffer(make([]byte, MSG_HEADER_SIZE))
|
|
||||||
_, err = readLenFromConn(conn, msgHeader.Bytes(), MSG_HEADER_SIZE)
|
|
||||||
if err != nil {
|
|
||||||
return data, fmt.Errorf("Failed to read header: %s", err)
|
|
||||||
}
|
|
||||||
err = binary.Read(msgHeader, binary.BigEndian, &asInfo.msg.aerospikeMessageHeader)
|
|
||||||
if err != nil {
|
|
||||||
return data, fmt.Errorf("Failed to unmarshal header: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
msgLen := msgLenFromBytes(asInfo.msg.aerospikeMessageHeader.DataLen)
|
|
||||||
|
|
||||||
if int64(len(asInfo.msg.Data)) != msgLen {
|
|
||||||
asInfo.msg.Data = make([]byte, msgLen)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = readLenFromConn(conn, asInfo.msg.Data, len(asInfo.msg.Data))
|
|
||||||
if err != nil {
|
|
||||||
return data, fmt.Errorf("Failed to read from connection to '%s': %s", host, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err = asInfo.parseMultiResponse()
|
|
||||||
if err != nil {
|
|
||||||
return data, fmt.Errorf("Failed to parse response from '%s': %s", host, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func readAerospikeStats(
|
|
||||||
stats map[string]string,
|
|
||||||
acc telegraf.Accumulator,
|
|
||||||
host string,
|
|
||||||
namespace string,
|
|
||||||
) {
|
|
||||||
fields := make(map[string]interface{})
|
|
||||||
tags := map[string]string{
|
|
||||||
"aerospike_host": host,
|
|
||||||
"namespace": "_service",
|
|
||||||
}
|
|
||||||
|
|
||||||
if namespace != "" {
|
|
||||||
tags["namespace"] = namespace
|
|
||||||
}
|
|
||||||
for key, value := range stats {
|
|
||||||
// We are going to ignore all string based keys
|
|
||||||
val, err := strconv.ParseInt(value, 10, 64)
|
|
||||||
if err == nil {
|
|
||||||
if strings.Contains(key, "-") {
|
|
||||||
key = strings.Replace(key, "-", "_", -1)
|
|
||||||
}
|
|
||||||
fields[key] = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
acc.AddFields("aerospike", fields, tags)
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalMapInfo(infoMap map[string]string, key string) (map[string]string, error) {
|
|
||||||
key = strings.TrimSuffix(key, "\n")
|
|
||||||
res := map[string]string{}
|
|
||||||
|
|
||||||
v, exists := infoMap[key]
|
|
||||||
if !exists {
|
|
||||||
return res, fmt.Errorf("Key '%s' missing from info", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
values := strings.Split(v, ";")
|
|
||||||
for i := range values {
|
|
||||||
kv := strings.Split(values[i], "=")
|
|
||||||
if len(kv) > 1 {
|
|
||||||
res[kv[0]] = kv[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalListInfo(infoMap map[string]string, key string) ([]string, error) {
|
|
||||||
key = strings.TrimSuffix(key, "\n")
|
|
||||||
|
|
||||||
v, exists := infoMap[key]
|
|
||||||
if !exists {
|
|
||||||
return []string{}, fmt.Errorf("Key '%s' missing from info", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
values := strings.Split(v, ";")
|
|
||||||
return values, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readLenFromConn(c net.Conn, buffer []byte, length int) (total int, err error) {
|
|
||||||
var r int
|
|
||||||
for total < length {
|
|
||||||
r, err = c.Read(buffer[total:length])
|
|
||||||
total += r
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Taken from aerospike-client-go/types/message.go
|
|
||||||
func msgLenToBytes(DataLen int64) [6]byte {
|
|
||||||
b := make([]byte, 8)
|
|
||||||
binary.BigEndian.PutUint64(b, uint64(DataLen))
|
|
||||||
res := [6]byte{}
|
|
||||||
copy(res[:], b[2:])
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Taken from aerospike-client-go/types/message.go
|
|
||||||
func msgLenFromBytes(buf [6]byte) int64 {
|
|
||||||
nbytes := append([]byte{0, 0}, buf[:]...)
|
|
||||||
DataLen := binary.BigEndian.Uint64(nbytes)
|
|
||||||
return int64(DataLen)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package aerospike
|
package aerospike
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf/testutil"
|
"github.com/influxdata/telegraf/testutil"
|
||||||
@@ -11,7 +10,7 @@ import (
|
|||||||
|
|
||||||
func TestAerospikeStatistics(t *testing.T) {
|
func TestAerospikeStatistics(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("Skipping integration test in short mode")
|
t.Skip("Skipping aerospike integration tests.")
|
||||||
}
|
}
|
||||||
|
|
||||||
a := &Aerospike{
|
a := &Aerospike{
|
||||||
@@ -23,96 +22,46 @@ func TestAerospikeStatistics(t *testing.T) {
|
|||||||
err := a.Gather(&acc)
|
err := a.Gather(&acc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Only use a few of the metrics
|
assert.True(t, acc.HasMeasurement("aerospike_node"))
|
||||||
asMetrics := []string{
|
assert.True(t, acc.HasMeasurement("aerospike_namespace"))
|
||||||
"transactions",
|
assert.True(t, acc.HasIntField("aerospike_node", "batch_error"))
|
||||||
"stat_write_errs",
|
|
||||||
"stat_read_reqs",
|
|
||||||
"stat_write_reqs",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, metric := range asMetrics {
|
|
||||||
assert.True(t, acc.HasIntField("aerospike", metric), metric)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAerospikeMsgLenFromToBytes(t *testing.T) {
|
func TestAerospikeStatisticsPartialErr(t *testing.T) {
|
||||||
var i int64 = 8
|
if testing.Short() {
|
||||||
assert.True(t, i == msgLenFromBytes(msgLenToBytes(i)))
|
t.Skip("Skipping aerospike integration tests.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a := &Aerospike{
|
||||||
|
Servers: []string{
|
||||||
|
testutil.GetLocalHost() + ":3000",
|
||||||
|
testutil.GetLocalHost() + ":9999",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func TestReadAerospikeStatsNoNamespace(t *testing.T) {
|
|
||||||
// Also test for re-writing
|
|
||||||
var acc testutil.Accumulator
|
var acc testutil.Accumulator
|
||||||
stats := map[string]string{
|
|
||||||
"stat-write-errs": "12345",
|
|
||||||
"stat_read_reqs": "12345",
|
|
||||||
}
|
|
||||||
readAerospikeStats(stats, &acc, "host1", "")
|
|
||||||
|
|
||||||
fields := map[string]interface{}{
|
err := a.Gather(&acc)
|
||||||
"stat_write_errs": int64(12345),
|
require.Error(t, err)
|
||||||
"stat_read_reqs": int64(12345),
|
|
||||||
}
|
assert.True(t, acc.HasMeasurement("aerospike_node"))
|
||||||
tags := map[string]string{
|
assert.True(t, acc.HasMeasurement("aerospike_namespace"))
|
||||||
"aerospike_host": "host1",
|
assert.True(t, acc.HasIntField("aerospike_node", "batch_error"))
|
||||||
"namespace": "_service",
|
|
||||||
}
|
|
||||||
acc.AssertContainsTaggedFields(t, "aerospike", fields, tags)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadAerospikeStatsNamespace(t *testing.T) {
|
func TestAerospikeParseValue(t *testing.T) {
|
||||||
var acc testutil.Accumulator
|
// uint64 with value bigger than int64 max
|
||||||
stats := map[string]string{
|
val, err := parseValue("18446744041841121751")
|
||||||
"stat_write_errs": "12345",
|
assert.Nil(t, val)
|
||||||
"stat_read_reqs": "12345",
|
assert.Error(t, err)
|
||||||
}
|
|
||||||
readAerospikeStats(stats, &acc, "host1", "test")
|
|
||||||
|
|
||||||
fields := map[string]interface{}{
|
// int values
|
||||||
"stat_write_errs": int64(12345),
|
val, err = parseValue("42")
|
||||||
"stat_read_reqs": int64(12345),
|
assert.NoError(t, err)
|
||||||
}
|
assert.Equal(t, val, int64(42), "must be parsed as int")
|
||||||
tags := map[string]string{
|
|
||||||
"aerospike_host": "host1",
|
// string values
|
||||||
"namespace": "test",
|
val, err = parseValue("BB977942A2CA502")
|
||||||
}
|
assert.NoError(t, err)
|
||||||
acc.AssertContainsTaggedFields(t, "aerospike", fields, tags)
|
assert.Equal(t, val, `BB977942A2CA502`, "must be left as string")
|
||||||
}
|
|
||||||
|
|
||||||
func TestAerospikeUnmarshalList(t *testing.T) {
|
|
||||||
i := map[string]string{
|
|
||||||
"test": "one;two;three",
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := []string{"one", "two", "three"}
|
|
||||||
|
|
||||||
list, err := unmarshalListInfo(i, "test2")
|
|
||||||
assert.True(t, err != nil)
|
|
||||||
|
|
||||||
list, err = unmarshalListInfo(i, "test")
|
|
||||||
assert.True(t, err == nil)
|
|
||||||
equal := true
|
|
||||||
for ix := range expected {
|
|
||||||
if list[ix] != expected[ix] {
|
|
||||||
equal = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert.True(t, equal)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAerospikeUnmarshalMap(t *testing.T) {
|
|
||||||
i := map[string]string{
|
|
||||||
"test": "key1=value1;key2=value2",
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := map[string]string{
|
|
||||||
"key1": "value1",
|
|
||||||
"key2": "value2",
|
|
||||||
}
|
|
||||||
m, err := unmarshalMapInfo(i, "test")
|
|
||||||
assert.True(t, err == nil)
|
|
||||||
assert.True(t, reflect.DeepEqual(m, expected))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,12 @@ import (
|
|||||||
_ "github.com/influxdata/telegraf/plugins/inputs/apache"
|
_ "github.com/influxdata/telegraf/plugins/inputs/apache"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/bcache"
|
_ "github.com/influxdata/telegraf/plugins/inputs/bcache"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/cassandra"
|
_ "github.com/influxdata/telegraf/plugins/inputs/cassandra"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/inputs/ceph"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/inputs/cgroup"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/inputs/chrony"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/cloudwatch"
|
_ "github.com/influxdata/telegraf/plugins/inputs/cloudwatch"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/inputs/conntrack"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/inputs/consul"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/couchbase"
|
_ "github.com/influxdata/telegraf/plugins/inputs/couchbase"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/couchdb"
|
_ "github.com/influxdata/telegraf/plugins/inputs/couchdb"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/disque"
|
_ "github.com/influxdata/telegraf/plugins/inputs/disque"
|
||||||
@@ -14,15 +19,20 @@ import (
|
|||||||
_ "github.com/influxdata/telegraf/plugins/inputs/dovecot"
|
_ "github.com/influxdata/telegraf/plugins/inputs/dovecot"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/elasticsearch"
|
_ "github.com/influxdata/telegraf/plugins/inputs/elasticsearch"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/exec"
|
_ "github.com/influxdata/telegraf/plugins/inputs/exec"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/github_webhooks"
|
_ "github.com/influxdata/telegraf/plugins/inputs/filestat"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/inputs/graylog"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/haproxy"
|
_ "github.com/influxdata/telegraf/plugins/inputs/haproxy"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/inputs/hddtemp"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/inputs/http_listener"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/http_response"
|
_ "github.com/influxdata/telegraf/plugins/inputs/http_response"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/httpjson"
|
_ "github.com/influxdata/telegraf/plugins/inputs/httpjson"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/influxdb"
|
_ "github.com/influxdata/telegraf/plugins/inputs/influxdb"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/ipmi_sensor"
|
_ "github.com/influxdata/telegraf/plugins/inputs/ipmi_sensor"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/inputs/iptables"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/jolokia"
|
_ "github.com/influxdata/telegraf/plugins/inputs/jolokia"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/kafka_consumer"
|
_ "github.com/influxdata/telegraf/plugins/inputs/kafka_consumer"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/leofs"
|
_ "github.com/influxdata/telegraf/plugins/inputs/leofs"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/inputs/logparser"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/lustre2"
|
_ "github.com/influxdata/telegraf/plugins/inputs/lustre2"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/mailchimp"
|
_ "github.com/influxdata/telegraf/plugins/inputs/mailchimp"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/memcached"
|
_ "github.com/influxdata/telegraf/plugins/inputs/memcached"
|
||||||
@@ -34,6 +44,8 @@ import (
|
|||||||
_ "github.com/influxdata/telegraf/plugins/inputs/net_response"
|
_ "github.com/influxdata/telegraf/plugins/inputs/net_response"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/nginx"
|
_ "github.com/influxdata/telegraf/plugins/inputs/nginx"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/nsq"
|
_ "github.com/influxdata/telegraf/plugins/inputs/nsq"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/inputs/nsq_consumer"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/inputs/nstat"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/ntpq"
|
_ "github.com/influxdata/telegraf/plugins/inputs/ntpq"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/passenger"
|
_ "github.com/influxdata/telegraf/plugins/inputs/passenger"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/phpfpm"
|
_ "github.com/influxdata/telegraf/plugins/inputs/phpfpm"
|
||||||
@@ -51,14 +63,18 @@ import (
|
|||||||
_ "github.com/influxdata/telegraf/plugins/inputs/riak"
|
_ "github.com/influxdata/telegraf/plugins/inputs/riak"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/sensors"
|
_ "github.com/influxdata/telegraf/plugins/inputs/sensors"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/snmp"
|
_ "github.com/influxdata/telegraf/plugins/inputs/snmp"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/inputs/snmp_legacy"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/sqlserver"
|
_ "github.com/influxdata/telegraf/plugins/inputs/sqlserver"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/statsd"
|
_ "github.com/influxdata/telegraf/plugins/inputs/statsd"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/sysstat"
|
_ "github.com/influxdata/telegraf/plugins/inputs/sysstat"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/system"
|
_ "github.com/influxdata/telegraf/plugins/inputs/system"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/inputs/tail"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/tcp_listener"
|
_ "github.com/influxdata/telegraf/plugins/inputs/tcp_listener"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/trig"
|
_ "github.com/influxdata/telegraf/plugins/inputs/trig"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/twemproxy"
|
_ "github.com/influxdata/telegraf/plugins/inputs/twemproxy"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/udp_listener"
|
_ "github.com/influxdata/telegraf/plugins/inputs/udp_listener"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/inputs/varnish"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/inputs/webhooks"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/win_perf_counters"
|
_ "github.com/influxdata/telegraf/plugins/inputs/win_perf_counters"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/zfs"
|
_ "github.com/influxdata/telegraf/plugins/inputs/zfs"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/zookeeper"
|
_ "github.com/influxdata/telegraf/plugins/inputs/zookeeper"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Telegraf plugin: Apache
|
# Telegraf plugin: Apache
|
||||||
|
|
||||||
#### Plugin arguments:
|
#### 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
|
#### Description
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
@@ -21,6 +20,7 @@ type Apache struct {
|
|||||||
|
|
||||||
var sampleConfig = `
|
var sampleConfig = `
|
||||||
## An array of Apache status URI to gather stats.
|
## An array of Apache status URI to gather stats.
|
||||||
|
## Default is "http://localhost/server-status?auto".
|
||||||
urls = ["http://localhost/server-status?auto"]
|
urls = ["http://localhost/server-status?auto"]
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -33,8 +33,12 @@ func (n *Apache) Description() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *Apache) Gather(acc telegraf.Accumulator) error {
|
func (n *Apache) Gather(acc telegraf.Accumulator) error {
|
||||||
var wg sync.WaitGroup
|
if len(n.Urls) == 0 {
|
||||||
|
n.Urls = []string{"http://localhost/server-status?auto"}
|
||||||
|
}
|
||||||
|
|
||||||
var outerr error
|
var outerr error
|
||||||
|
var errch = make(chan error)
|
||||||
|
|
||||||
for _, u := range n.Urls {
|
for _, u := range n.Urls {
|
||||||
addr, err := url.Parse(u)
|
addr, err := url.Parse(u)
|
||||||
@@ -42,14 +46,17 @@ func (n *Apache) Gather(acc telegraf.Accumulator) error {
|
|||||||
return fmt.Errorf("Unable to parse address '%s': %s", u, err)
|
return fmt.Errorf("Unable to parse address '%s': %s", u, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Add(1)
|
|
||||||
go func(addr *url.URL) {
|
go func(addr *url.URL) {
|
||||||
defer wg.Done()
|
errch <- n.gatherUrl(addr, acc)
|
||||||
outerr = n.gatherUrl(addr, acc)
|
|
||||||
}(addr)
|
}(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
// Drain channel, waiting for all requests to finish and save last error.
|
||||||
|
for range n.Urls {
|
||||||
|
if err := <-errch; err != nil {
|
||||||
|
outerr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return outerr
|
return outerr
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ func TestHTTPApache(t *testing.T) {
|
|||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
a := Apache{
|
a := Apache{
|
||||||
Urls: []string{ts.URL},
|
// Fetch it 2 times to catch possible data races.
|
||||||
|
Urls: []string{ts.URL, ts.URL},
|
||||||
}
|
}
|
||||||
|
|
||||||
var acc testutil.Accumulator
|
var acc testutil.Accumulator
|
||||||
|
|||||||
@@ -7,19 +7,12 @@ import (
|
|||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
"github.com/influxdata/telegraf/plugins/inputs"
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
//"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*type Server struct {
|
|
||||||
Host string
|
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
Port string
|
|
||||||
}*/
|
|
||||||
|
|
||||||
type JolokiaClient interface {
|
type JolokiaClient interface {
|
||||||
MakeRequest(req *http.Request) (*http.Response, error)
|
MakeRequest(req *http.Request) (*http.Response, error)
|
||||||
}
|
}
|
||||||
@@ -55,12 +48,6 @@ type jmxMetric interface {
|
|||||||
addTagsFields(out map[string]interface{})
|
addTagsFields(out map[string]interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func addServerTags(host string, tags map[string]string) {
|
|
||||||
if host != "" && host != "localhost" && host != "127.0.0.1" {
|
|
||||||
tags["host"] = host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newJavaMetric(host string, metric string,
|
func newJavaMetric(host string, metric string,
|
||||||
acc telegraf.Accumulator) *javaMetric {
|
acc telegraf.Accumulator) *javaMetric {
|
||||||
return &javaMetric{host: host, metric: metric, acc: acc}
|
return &javaMetric{host: host, metric: metric, acc: acc}
|
||||||
@@ -120,7 +107,7 @@ func (j javaMetric) addTagsFields(out map[string]interface{}) {
|
|||||||
|
|
||||||
tokens := parseJmxMetricRequest(mbean)
|
tokens := parseJmxMetricRequest(mbean)
|
||||||
addTokensToTags(tokens, tags)
|
addTokensToTags(tokens, tags)
|
||||||
addServerTags(j.host, tags)
|
tags["cassandra_host"] = j.host
|
||||||
|
|
||||||
if _, ok := tags["mname"]; !ok {
|
if _, ok := tags["mname"]; !ok {
|
||||||
//Queries for a single value will not return a "name" tag in the response.
|
//Queries for a single value will not return a "name" tag in the response.
|
||||||
@@ -148,7 +135,7 @@ func addCassandraMetric(mbean string, c cassandraMetric,
|
|||||||
fields := make(map[string]interface{})
|
fields := make(map[string]interface{})
|
||||||
tokens := parseJmxMetricRequest(mbean)
|
tokens := parseJmxMetricRequest(mbean)
|
||||||
addTokensToTags(tokens, tags)
|
addTokensToTags(tokens, tags)
|
||||||
addServerTags(c.host, tags)
|
tags["cassandra_host"] = c.host
|
||||||
addValuesAsFields(values, fields, tags["mname"])
|
addValuesAsFields(values, fields, tags["mname"])
|
||||||
c.acc.AddFields(tokens["class"]+tokens["type"], fields, tags)
|
c.acc.AddFields(tokens["class"]+tokens["type"], fields, tags)
|
||||||
|
|
||||||
@@ -161,7 +148,7 @@ func (c cassandraMetric) addTagsFields(out map[string]interface{}) {
|
|||||||
tokens := parseJmxMetricRequest(r.(map[string]interface{})["mbean"].(string))
|
tokens := parseJmxMetricRequest(r.(map[string]interface{})["mbean"].(string))
|
||||||
// Requests with wildcards for keyspace or table names will return nested
|
// Requests with wildcards for keyspace or table names will return nested
|
||||||
// maps in the json response
|
// maps in the json response
|
||||||
if tokens["type"] == "Table" && (tokens["keyspace"] == "*" ||
|
if (tokens["type"] == "Table" || tokens["type"] == "ColumnFamily") && (tokens["keyspace"] == "*" ||
|
||||||
tokens["scope"] == "*") {
|
tokens["scope"] == "*") {
|
||||||
if valuesMap, ok := out["value"]; ok {
|
if valuesMap, ok := out["value"]; ok {
|
||||||
for k, v := range valuesMap.(map[string]interface{}) {
|
for k, v := range valuesMap.(map[string]interface{}) {
|
||||||
@@ -277,15 +264,19 @@ func (c *Cassandra) Gather(acc telegraf.Accumulator) error {
|
|||||||
|
|
||||||
for _, server := range servers {
|
for _, server := range servers {
|
||||||
for _, metric := range metrics {
|
for _, metric := range metrics {
|
||||||
var m jmxMetric
|
|
||||||
|
|
||||||
serverTokens := parseServerTokens(server)
|
serverTokens := parseServerTokens(server)
|
||||||
|
|
||||||
|
var m jmxMetric
|
||||||
if strings.HasPrefix(metric, "/java.lang:") {
|
if strings.HasPrefix(metric, "/java.lang:") {
|
||||||
m = newJavaMetric(serverTokens["host"], metric, acc)
|
m = newJavaMetric(serverTokens["host"], metric, acc)
|
||||||
} else if strings.HasPrefix(metric,
|
} else if strings.HasPrefix(metric,
|
||||||
"/org.apache.cassandra.metrics:") {
|
"/org.apache.cassandra.metrics:") {
|
||||||
m = newCassandraMetric(serverTokens["host"], metric, acc)
|
m = newCassandraMetric(serverTokens["host"], metric, acc)
|
||||||
|
} else {
|
||||||
|
// unsupported metric type
|
||||||
|
log.Printf("Unsupported Cassandra metric [%s], skipping",
|
||||||
|
metric)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare URL
|
// Prepare URL
|
||||||
|
|||||||
@@ -163,13 +163,13 @@ func TestHttpJsonJavaMultiValue(t *testing.T) {
|
|||||||
"HeapMemoryUsage_used": 203288528.0,
|
"HeapMemoryUsage_used": 203288528.0,
|
||||||
}
|
}
|
||||||
tags1 := map[string]string{
|
tags1 := map[string]string{
|
||||||
"host": "10.10.10.10",
|
"cassandra_host": "10.10.10.10",
|
||||||
"mname": "HeapMemoryUsage",
|
"mname": "HeapMemoryUsage",
|
||||||
}
|
}
|
||||||
|
|
||||||
tags2 := map[string]string{
|
tags2 := map[string]string{
|
||||||
"host": "10.10.10.11",
|
"cassandra_host": "10.10.10.11",
|
||||||
"mname": "HeapMemoryUsage",
|
"mname": "HeapMemoryUsage",
|
||||||
}
|
}
|
||||||
acc.AssertContainsTaggedFields(t, "javaMemory", fields, tags1)
|
acc.AssertContainsTaggedFields(t, "javaMemory", fields, tags1)
|
||||||
acc.AssertContainsTaggedFields(t, "javaMemory", fields, tags2)
|
acc.AssertContainsTaggedFields(t, "javaMemory", fields, tags2)
|
||||||
@@ -190,8 +190,8 @@ func TestHttpJsonJavaMultiType(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tags := map[string]string{
|
tags := map[string]string{
|
||||||
"host": "10.10.10.10",
|
"cassandra_host": "10.10.10.10",
|
||||||
"mname": "ConcurrentMarkSweep",
|
"mname": "ConcurrentMarkSweep",
|
||||||
}
|
}
|
||||||
acc.AssertContainsTaggedFields(t, "javaGarbageCollector", fields, tags)
|
acc.AssertContainsTaggedFields(t, "javaGarbageCollector", fields, tags)
|
||||||
}
|
}
|
||||||
@@ -231,10 +231,10 @@ func TestHttpJsonCassandraMultiValue(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tags := map[string]string{
|
tags := map[string]string{
|
||||||
"host": "10.10.10.10",
|
"cassandra_host": "10.10.10.10",
|
||||||
"mname": "ReadLatency",
|
"mname": "ReadLatency",
|
||||||
"keyspace": "test_keyspace1",
|
"keyspace": "test_keyspace1",
|
||||||
"scope": "test_table",
|
"scope": "test_table",
|
||||||
}
|
}
|
||||||
acc.AssertContainsTaggedFields(t, "cassandraTable", fields, tags)
|
acc.AssertContainsTaggedFields(t, "cassandraTable", fields, tags)
|
||||||
}
|
}
|
||||||
@@ -268,17 +268,17 @@ func TestHttpJsonCassandraNestedMultiValue(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tags1 := map[string]string{
|
tags1 := map[string]string{
|
||||||
"host": "10.10.10.10",
|
"cassandra_host": "10.10.10.10",
|
||||||
"mname": "ReadLatency",
|
"mname": "ReadLatency",
|
||||||
"keyspace": "test_keyspace1",
|
"keyspace": "test_keyspace1",
|
||||||
"scope": "test_table1",
|
"scope": "test_table1",
|
||||||
}
|
}
|
||||||
|
|
||||||
tags2 := map[string]string{
|
tags2 := map[string]string{
|
||||||
"host": "10.10.10.10",
|
"cassandra_host": "10.10.10.10",
|
||||||
"mname": "ReadLatency",
|
"mname": "ReadLatency",
|
||||||
"keyspace": "test_keyspace2",
|
"keyspace": "test_keyspace2",
|
||||||
"scope": "test_table2",
|
"scope": "test_table2",
|
||||||
}
|
}
|
||||||
|
|
||||||
acc.AssertContainsTaggedFields(t, "cassandraTable", fields1, tags1)
|
acc.AssertContainsTaggedFields(t, "cassandraTable", fields1, tags1)
|
||||||
|
|||||||
222
plugins/inputs/ceph/README.md
Normal file
222
plugins/inputs/ceph/README.md
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
# Ceph Storage Input Plugin
|
||||||
|
|
||||||
|
Collects performance metrics from the MON and OSD nodes in a Ceph storage cluster.
|
||||||
|
|
||||||
|
*Admin Socket Stats*
|
||||||
|
|
||||||
|
This gatherer 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
|
||||||
|
|
||||||
|
|
||||||
|
*Cluster Stats*
|
||||||
|
|
||||||
|
This gatherer works by invoking ceph commands against the cluster thus only requires the ceph client, valid
|
||||||
|
ceph configuration and an access key to function (the ceph_config and ceph_user configuration variables work
|
||||||
|
in conjunction to specify these prerequisites). It may be run on any server you wish which has access to
|
||||||
|
the cluster. The currently supported commands are:
|
||||||
|
|
||||||
|
* ceph status
|
||||||
|
* ceph df
|
||||||
|
* ceph osd pool stats
|
||||||
|
|
||||||
|
### Configuration:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Collects performance metrics from the MON and OSD nodes in a Ceph storage cluster.
|
||||||
|
[[inputs.ceph]]
|
||||||
|
## This is the recommended interval to poll. Too frequent and you will lose
|
||||||
|
## data points due to timeouts during rebalancing and recovery
|
||||||
|
interval = '1m'
|
||||||
|
|
||||||
|
## 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"
|
||||||
|
|
||||||
|
## Ceph user to authenticate as, ceph will search for the corresponding keyring
|
||||||
|
## e.g. client.admin.keyring in /etc/ceph, or the explicit path defined in the
|
||||||
|
## client section of ceph.conf for example:
|
||||||
|
##
|
||||||
|
## [client.telegraf]
|
||||||
|
## keyring = /etc/ceph/client.telegraf.keyring
|
||||||
|
##
|
||||||
|
## Consult the ceph documentation for more detail on keyring generation.
|
||||||
|
ceph_user = "client.admin"
|
||||||
|
|
||||||
|
## Ceph configuration to use to locate the cluster
|
||||||
|
ceph_config = "/etc/ceph/ceph.conf"
|
||||||
|
|
||||||
|
## Whether to gather statistics via the admin socket
|
||||||
|
gather_admin_socket_stats = true
|
||||||
|
|
||||||
|
## Whether to gather statistics via ceph commands, requires ceph_user and ceph_config
|
||||||
|
## to be specified
|
||||||
|
gather_cluster_stats = true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Measurements & Fields:
|
||||||
|
|
||||||
|
*Admin Socket Stats*
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
*Cluster Stats*
|
||||||
|
|
||||||
|
* ceph\_osdmap
|
||||||
|
* epoch (float)
|
||||||
|
* full (boolean)
|
||||||
|
* nearfull (boolean)
|
||||||
|
* num\_in\_osds (float)
|
||||||
|
* num\_osds (float)
|
||||||
|
* num\_remremapped\_pgs (float)
|
||||||
|
* num\_up\_osds (float)
|
||||||
|
|
||||||
|
* ceph\_pgmap
|
||||||
|
* bytes\_avail (float)
|
||||||
|
* bytes\_total (float)
|
||||||
|
* bytes\_used (float)
|
||||||
|
* data\_bytes (float)
|
||||||
|
* num\_pgs (float)
|
||||||
|
* op\_per\_sec (float)
|
||||||
|
* read\_bytes\_sec (float)
|
||||||
|
* version (float)
|
||||||
|
* write\_bytes\_sec (float)
|
||||||
|
* recovering\_bytes\_per\_sec (float)
|
||||||
|
* recovering\_keys\_per\_sec (float)
|
||||||
|
* recovering\_objects\_per\_sec (float)
|
||||||
|
|
||||||
|
* ceph\_pgmap\_state
|
||||||
|
* state name e.g. active+clean (float)
|
||||||
|
|
||||||
|
* ceph\_usage
|
||||||
|
* bytes\_used (float)
|
||||||
|
* kb\_used (float)
|
||||||
|
* max\_avail (float)
|
||||||
|
* objects (float)
|
||||||
|
|
||||||
|
* ceph\_pool\_usage
|
||||||
|
* bytes\_used (float)
|
||||||
|
* kb\_used (float)
|
||||||
|
* max\_avail (float)
|
||||||
|
* objects (float)
|
||||||
|
|
||||||
|
* ceph\_pool\_stats
|
||||||
|
* op\_per\_sec (float)
|
||||||
|
* read\_bytes\_sec (float)
|
||||||
|
* write\_bytes\_sec (float)
|
||||||
|
* recovering\_object\_per\_sec (float)
|
||||||
|
* recovering\_bytes\_per\_sec (float)
|
||||||
|
* recovering\_keys\_per\_sec (float)
|
||||||
|
|
||||||
|
### Tags:
|
||||||
|
|
||||||
|
*Admin Socket Stats*
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
*Cluster Stats*
|
||||||
|
|
||||||
|
* ceph\_pg\_state has the following tags:
|
||||||
|
* state (state for which the value applies e.g. active+clean, active+remapped+backfill)
|
||||||
|
* ceph\_pool\_usage has the following tags:
|
||||||
|
* id
|
||||||
|
* name
|
||||||
|
* ceph\_pool\_stats has the following tags:
|
||||||
|
* id
|
||||||
|
* name
|
||||||
|
|
||||||
|
### Example Output:
|
||||||
|
|
||||||
|
*Admin Socket Stats*
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
*Cluster Stats*
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
> ceph_osdmap,host=ceph-mon-0 epoch=170772,full=false,nearfull=false,num_in_osds=340,num_osds=340,num_remapped_pgs=0,num_up_osds=340 1468841037000000000
|
||||||
|
> ceph_pgmap,host=ceph-mon-0 bytes_avail=634895531270144,bytes_total=812117151809536,bytes_used=177221620539392,data_bytes=56979991615058,num_pgs=22952,op_per_sec=15869,read_bytes_sec=43956026,version=39387592,write_bytes_sec=165344818 1468841037000000000
|
||||||
|
> ceph_pgmap_state,host=ceph-mon-0 active+clean=22952 1468928660000000000
|
||||||
|
> ceph_usage,host=ceph-mon-0 total_avail_bytes=634895514791936,total_bytes=812117151809536,total_used_bytes=177221637017600 1468841037000000000
|
||||||
|
> ceph_pool_usage,host=ceph-mon-0,id=150,name=cinder.volumes bytes_used=12648553794802,kb_used=12352103316,max_avail=154342562489244,objects=3026295 1468841037000000000
|
||||||
|
> ceph_pool_usage,host=ceph-mon-0,id=182,name=cinder.volumes.flash bytes_used=8541308223964,kb_used=8341121313,max_avail=39388593563936,objects=2075066 1468841037000000000
|
||||||
|
> ceph_pool_stats,host=ceph-mon-0,id=150,name=cinder.volumes op_per_sec=1706,read_bytes_sec=28671674,write_bytes_sec=29994541 1468841037000000000
|
||||||
|
> ceph_pool_stats,host=ceph-mon-0,id=182,name=cinder.volumes.flash op_per_sec=9748,read_bytes_sec=9605524,write_bytes_sec=45593310 1468841037000000000
|
||||||
|
</pre>
|
||||||
489
plugins/inputs/ceph/ceph.go
Normal file
489
plugins/inputs/ceph/ceph.go
Normal file
@@ -0,0 +1,489 @@
|
|||||||
|
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
|
||||||
|
CephUser string
|
||||||
|
CephConfig string
|
||||||
|
GatherAdminSocketStats bool
|
||||||
|
GatherClusterStats bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ceph) Description() string {
|
||||||
|
return "Collects performance metrics from the MON and OSD nodes in a Ceph storage cluster."
|
||||||
|
}
|
||||||
|
|
||||||
|
var sampleConfig = `
|
||||||
|
## This is the recommended interval to poll. Too frequent and you will lose
|
||||||
|
## data points due to timeouts during rebalancing and recovery
|
||||||
|
interval = '1m'
|
||||||
|
|
||||||
|
## 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"
|
||||||
|
|
||||||
|
## Ceph user to authenticate as
|
||||||
|
ceph_user = "client.admin"
|
||||||
|
|
||||||
|
## Ceph configuration to use to locate the cluster
|
||||||
|
ceph_config = "/etc/ceph/ceph.conf"
|
||||||
|
|
||||||
|
## Whether to gather statistics via the admin socket
|
||||||
|
gather_admin_socket_stats = true
|
||||||
|
|
||||||
|
## Whether to gather statistics via ceph commands
|
||||||
|
gather_cluster_stats = true
|
||||||
|
`
|
||||||
|
|
||||||
|
func (c *Ceph) SampleConfig() string {
|
||||||
|
return sampleConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ceph) Gather(acc telegraf.Accumulator) error {
|
||||||
|
if c.GatherAdminSocketStats {
|
||||||
|
if err := c.gatherAdminSocketStats(acc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.GatherClusterStats {
|
||||||
|
if err := c.gatherClusterStats(acc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ceph) gatherAdminSocketStats(acc telegraf.Accumulator) error {
|
||||||
|
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 (c *Ceph) gatherClusterStats(acc telegraf.Accumulator) error {
|
||||||
|
jobs := []struct {
|
||||||
|
command string
|
||||||
|
parser func(telegraf.Accumulator, string) error
|
||||||
|
}{
|
||||||
|
{"status", decodeStatus},
|
||||||
|
{"df", decodeDf},
|
||||||
|
{"osd pool stats", decodeOsdPoolStats},
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each job, execute against the cluster, parse and accumulate the data points
|
||||||
|
for _, job := range jobs {
|
||||||
|
output, err := c.exec(job.command)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error executing command: %v", err)
|
||||||
|
}
|
||||||
|
err = job.parser(acc, output)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error parsing output: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
c := Ceph{
|
||||||
|
CephBinary: "/usr/bin/ceph",
|
||||||
|
OsdPrefix: osdPrefix,
|
||||||
|
MonPrefix: monPrefix,
|
||||||
|
SocketDir: "/var/run/ceph",
|
||||||
|
SocketSuffix: sockSuffix,
|
||||||
|
CephUser: "client.admin",
|
||||||
|
CephConfig: "/etc/ceph/ceph.conf",
|
||||||
|
GatherAdminSocketStats: true,
|
||||||
|
GatherClusterStats: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs.Add(measurement, func() telegraf.Input { return &c })
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ceph) exec(command string) (string, error) {
|
||||||
|
cmdArgs := []string{"--conf", c.CephConfig, "--name", c.CephUser, "--format", "json"}
|
||||||
|
cmdArgs = append(cmdArgs, strings.Split(command, " ")...)
|
||||||
|
|
||||||
|
cmd := exec.Command(c.CephBinary, cmdArgs...)
|
||||||
|
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error running ceph %v: %s", command, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
output := out.String()
|
||||||
|
|
||||||
|
// Ceph doesn't sanitize its output, and may return invalid JSON. Patch this
|
||||||
|
// up for them, as having some inaccurate data is better than none.
|
||||||
|
output = strings.Replace(output, "-inf", "0", -1)
|
||||||
|
output = strings.Replace(output, "inf", "0", -1)
|
||||||
|
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeStatus(acc telegraf.Accumulator, input string) error {
|
||||||
|
data := make(map[string]interface{})
|
||||||
|
err := json.Unmarshal([]byte(input), &data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse json: '%s': %v", input, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = decodeStatusOsdmap(acc, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = decodeStatusPgmap(acc, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = decodeStatusPgmapState(acc, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeStatusOsdmap(acc telegraf.Accumulator, data map[string]interface{}) error {
|
||||||
|
osdmap, ok := data["osdmap"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("WARNING %s - unable to decode osdmap", measurement)
|
||||||
|
}
|
||||||
|
fields, ok := osdmap["osdmap"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("WARNING %s - unable to decode osdmap", measurement)
|
||||||
|
}
|
||||||
|
acc.AddFields("ceph_osdmap", fields, map[string]string{})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeStatusPgmap(acc telegraf.Accumulator, data map[string]interface{}) error {
|
||||||
|
pgmap, ok := data["pgmap"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("WARNING %s - unable to decode pgmap", measurement)
|
||||||
|
}
|
||||||
|
fields := make(map[string]interface{})
|
||||||
|
for key, value := range pgmap {
|
||||||
|
switch value.(type) {
|
||||||
|
case float64:
|
||||||
|
fields[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acc.AddFields("ceph_pgmap", fields, map[string]string{})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeStatusPgmapState(acc telegraf.Accumulator, data map[string]interface{}) error {
|
||||||
|
pgmap, ok := data["pgmap"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("WARNING %s - unable to decode pgmap", measurement)
|
||||||
|
}
|
||||||
|
fields := make(map[string]interface{})
|
||||||
|
for key, value := range pgmap {
|
||||||
|
switch value.(type) {
|
||||||
|
case []interface{}:
|
||||||
|
if key != "pgs_by_state" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, state := range value.([]interface{}) {
|
||||||
|
state_map, ok := state.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("WARNING %s - unable to decode pg state", measurement)
|
||||||
|
}
|
||||||
|
state_name, ok := state_map["state_name"].(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("WARNING %s - unable to decode pg state name", measurement)
|
||||||
|
}
|
||||||
|
state_count, ok := state_map["count"].(float64)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("WARNING %s - unable to decode pg state count", measurement)
|
||||||
|
}
|
||||||
|
fields[state_name] = state_count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acc.AddFields("ceph_pgmap_state", fields, map[string]string{})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeDf(acc telegraf.Accumulator, input string) error {
|
||||||
|
data := make(map[string]interface{})
|
||||||
|
err := json.Unmarshal([]byte(input), &data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse json: '%s': %v", input, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ceph.usage: records global utilization and number of objects
|
||||||
|
stats_fields, ok := data["stats"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("WARNING %s - unable to decode df stats", measurement)
|
||||||
|
}
|
||||||
|
acc.AddFields("ceph_usage", stats_fields, map[string]string{})
|
||||||
|
|
||||||
|
// ceph.pool.usage: records per pool utilization and number of objects
|
||||||
|
pools, ok := data["pools"].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("WARNING %s - unable to decode df pools", measurement)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pool := range pools {
|
||||||
|
pool_map, ok := pool.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("WARNING %s - unable to decode df pool", measurement)
|
||||||
|
}
|
||||||
|
pool_name, ok := pool_map["name"].(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("WARNING %s - unable to decode df pool name", measurement)
|
||||||
|
}
|
||||||
|
fields, ok := pool_map["stats"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("WARNING %s - unable to decode df pool stats", measurement)
|
||||||
|
}
|
||||||
|
tags := map[string]string{
|
||||||
|
"name": pool_name,
|
||||||
|
}
|
||||||
|
acc.AddFields("ceph_pool_usage", fields, tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeOsdPoolStats(acc telegraf.Accumulator, input string) error {
|
||||||
|
data := make([]map[string]interface{}, 0)
|
||||||
|
err := json.Unmarshal([]byte(input), &data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse json: '%s': %v", input, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ceph.pool.stats: records pre pool IO and recovery throughput
|
||||||
|
for _, pool := range data {
|
||||||
|
pool_name, ok := pool["pool_name"].(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("WARNING %s - unable to decode osd pool stats name", measurement)
|
||||||
|
}
|
||||||
|
// Note: the 'recovery' object looks broken (in hammer), so it's omitted
|
||||||
|
objects := []string{
|
||||||
|
"client_io_rate",
|
||||||
|
"recovery_rate",
|
||||||
|
}
|
||||||
|
fields := make(map[string]interface{})
|
||||||
|
for _, object := range objects {
|
||||||
|
perfdata, ok := pool[object].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("WARNING %s - unable to decode osd pool stats", measurement)
|
||||||
|
}
|
||||||
|
for key, value := range perfdata {
|
||||||
|
fields[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tags := map[string]string{
|
||||||
|
"name": pool_name,
|
||||||
|
}
|
||||||
|
acc.AddFields("ceph_pool_stats", fields, tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
687
plugins/inputs/ceph/ceph_test.go
Normal file
687
plugins/inputs/ceph/ceph_test.go
Normal file
@@ -0,0 +1,687 @@
|
|||||||
|
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",
|
||||||
|
OsdPrefix: "ceph-osd",
|
||||||
|
MonPrefix: "ceph-mon",
|
||||||
|
SocketDir: tmpdir,
|
||||||
|
SocketSuffix: "asok",
|
||||||
|
CephUser: "client.admin",
|
||||||
|
CephConfig: "/etc/ceph/ceph.conf",
|
||||||
|
GatherAdminSocketStats: true,
|
||||||
|
GatherClusterStats: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
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}}}
|
||||||
|
`
|
||||||
59
plugins/inputs/cgroup/README.md
Normal file
59
plugins/inputs/cgroup/README.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# CGroup Input Plugin For Telegraf Agent
|
||||||
|
|
||||||
|
This input plugin will capture specific statistics per cgroup.
|
||||||
|
|
||||||
|
Following file formats are supported:
|
||||||
|
|
||||||
|
* Single value
|
||||||
|
|
||||||
|
```
|
||||||
|
VAL\n
|
||||||
|
```
|
||||||
|
|
||||||
|
* New line separated values
|
||||||
|
|
||||||
|
```
|
||||||
|
VAL0\n
|
||||||
|
VAL1\n
|
||||||
|
```
|
||||||
|
|
||||||
|
* Space separated values
|
||||||
|
|
||||||
|
```
|
||||||
|
VAL0 VAL1 ...\n
|
||||||
|
```
|
||||||
|
|
||||||
|
* New line separated key-space-value's
|
||||||
|
|
||||||
|
```
|
||||||
|
KEY0 VAL0\n
|
||||||
|
KEY1 VAL1\n
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Tags:
|
||||||
|
|
||||||
|
Measurements don't have any specific tags unless you define them at the telegraf level (defaults). We
|
||||||
|
used to have the path listed as a tag, but to keep cardinality in check it's easier to move this
|
||||||
|
value to a field. Thanks @sebito91!
|
||||||
|
|
||||||
|
|
||||||
|
### Configuration:
|
||||||
|
|
||||||
|
```
|
||||||
|
# [[inputs.cgroup]]
|
||||||
|
# paths = [
|
||||||
|
# "/cgroup/memory", # root cgroup
|
||||||
|
# "/cgroup/memory/child1", # container cgroup
|
||||||
|
# "/cgroup/memory/child2/*", # all children cgroups under child2, but not child2 itself
|
||||||
|
# ]
|
||||||
|
# files = ["memory.*usage*", "memory.limit_in_bytes"]
|
||||||
|
|
||||||
|
# [[inputs.cgroup]]
|
||||||
|
# paths = [
|
||||||
|
# "/cgroup/cpu", # root cgroup
|
||||||
|
# "/cgroup/cpu/*", # all container cgroups
|
||||||
|
# "/cgroup/cpu/*/*", # all children cgroups under each container cgroup
|
||||||
|
# ]
|
||||||
|
# files = ["cpuacct.usage", "cpu.cfs_period_us", "cpu.cfs_quota_us"]
|
||||||
|
```
|
||||||
35
plugins/inputs/cgroup/cgroup.go
Normal file
35
plugins/inputs/cgroup/cgroup.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package cgroup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CGroup struct {
|
||||||
|
Paths []string `toml:"paths"`
|
||||||
|
Files []string `toml:"files"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var sampleConfig = `
|
||||||
|
## Directories in which to look for files, globs are supported.
|
||||||
|
# paths = [
|
||||||
|
# "/cgroup/memory",
|
||||||
|
# "/cgroup/memory/child1",
|
||||||
|
# "/cgroup/memory/child2/*",
|
||||||
|
# ]
|
||||||
|
## cgroup stat fields, as file names, globs are supported.
|
||||||
|
## these file names are appended to each path from above.
|
||||||
|
# files = ["memory.*usage*", "memory.limit_in_bytes"]
|
||||||
|
`
|
||||||
|
|
||||||
|
func (g *CGroup) SampleConfig() string {
|
||||||
|
return sampleConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *CGroup) Description() string {
|
||||||
|
return "Read specific statistics per cgroup"
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
inputs.Add("cgroup", func() telegraf.Input { return &CGroup{} })
|
||||||
|
}
|
||||||
243
plugins/inputs/cgroup/cgroup_linux.go
Normal file
243
plugins/inputs/cgroup/cgroup_linux.go
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
// +build linux
|
||||||
|
|
||||||
|
package cgroup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
)
|
||||||
|
|
||||||
|
const metricName = "cgroup"
|
||||||
|
|
||||||
|
func (g *CGroup) Gather(acc telegraf.Accumulator) error {
|
||||||
|
list := make(chan pathInfo)
|
||||||
|
go g.generateDirs(list)
|
||||||
|
|
||||||
|
for dir := range list {
|
||||||
|
if dir.err != nil {
|
||||||
|
return dir.err
|
||||||
|
}
|
||||||
|
if err := g.gatherDir(dir.path, acc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *CGroup) gatherDir(dir string, acc telegraf.Accumulator) error {
|
||||||
|
fields := make(map[string]interface{})
|
||||||
|
|
||||||
|
list := make(chan pathInfo)
|
||||||
|
go g.generateFiles(dir, list)
|
||||||
|
|
||||||
|
for file := range list {
|
||||||
|
if file.err != nil {
|
||||||
|
return file.err
|
||||||
|
}
|
||||||
|
|
||||||
|
raw, err := ioutil.ReadFile(file.path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(raw) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fd := fileData{data: raw, path: file.path}
|
||||||
|
if err := fd.parse(fields); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fields["path"] = dir
|
||||||
|
|
||||||
|
acc.AddFields(metricName, fields, nil)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================================================================
|
||||||
|
|
||||||
|
type pathInfo struct {
|
||||||
|
path string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDir(path string) (bool, error) {
|
||||||
|
result, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return result.IsDir(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *CGroup) generateDirs(list chan<- pathInfo) {
|
||||||
|
for _, dir := range g.Paths {
|
||||||
|
// getting all dirs that match the pattern 'dir'
|
||||||
|
items, err := filepath.Glob(dir)
|
||||||
|
if err != nil {
|
||||||
|
list <- pathInfo{err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range items {
|
||||||
|
ok, err := isDir(item)
|
||||||
|
if err != nil {
|
||||||
|
list <- pathInfo{err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// supply only dirs
|
||||||
|
if ok {
|
||||||
|
list <- pathInfo{path: item}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *CGroup) generateFiles(dir string, list chan<- pathInfo) {
|
||||||
|
for _, file := range g.Files {
|
||||||
|
// getting all file paths that match the pattern 'dir + file'
|
||||||
|
// path.Base make sure that file variable does not contains part of path
|
||||||
|
items, err := filepath.Glob(path.Join(dir, path.Base(file)))
|
||||||
|
if err != nil {
|
||||||
|
list <- pathInfo{err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range items {
|
||||||
|
ok, err := isDir(item)
|
||||||
|
if err != nil {
|
||||||
|
list <- pathInfo{err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// supply only files not dirs
|
||||||
|
if !ok {
|
||||||
|
list <- pathInfo{path: item}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================================================================
|
||||||
|
|
||||||
|
type fileData struct {
|
||||||
|
data []byte
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fd *fileData) format() (*fileFormat, error) {
|
||||||
|
for _, ff := range fileFormats {
|
||||||
|
ok, err := ff.match(fd.data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
return &ff, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("%v: unknown file format", fd.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fd *fileData) parse(fields map[string]interface{}) error {
|
||||||
|
format, err := fd.format()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
format.parser(filepath.Base(fd.path), fields, fd.data)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================================================================
|
||||||
|
|
||||||
|
type fileFormat struct {
|
||||||
|
name string
|
||||||
|
pattern string
|
||||||
|
parser func(measurement string, fields map[string]interface{}, b []byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyPattern = "[[:alpha:]_]+"
|
||||||
|
const valuePattern = "[\\d-]+"
|
||||||
|
|
||||||
|
var fileFormats = [...]fileFormat{
|
||||||
|
// VAL\n
|
||||||
|
fileFormat{
|
||||||
|
name: "Single value",
|
||||||
|
pattern: "^" + valuePattern + "\n$",
|
||||||
|
parser: func(measurement string, fields map[string]interface{}, b []byte) {
|
||||||
|
re := regexp.MustCompile("^(" + valuePattern + ")\n$")
|
||||||
|
matches := re.FindAllStringSubmatch(string(b), -1)
|
||||||
|
fields[measurement] = numberOrString(matches[0][1])
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// VAL0\n
|
||||||
|
// VAL1\n
|
||||||
|
// ...
|
||||||
|
fileFormat{
|
||||||
|
name: "New line separated values",
|
||||||
|
pattern: "^(" + valuePattern + "\n){2,}$",
|
||||||
|
parser: func(measurement string, fields map[string]interface{}, b []byte) {
|
||||||
|
re := regexp.MustCompile("(" + valuePattern + ")\n")
|
||||||
|
matches := re.FindAllStringSubmatch(string(b), -1)
|
||||||
|
for i, v := range matches {
|
||||||
|
fields[measurement+"."+strconv.Itoa(i)] = numberOrString(v[1])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// VAL0 VAL1 ...\n
|
||||||
|
fileFormat{
|
||||||
|
name: "Space separated values",
|
||||||
|
pattern: "^(" + valuePattern + " )+\n$",
|
||||||
|
parser: func(measurement string, fields map[string]interface{}, b []byte) {
|
||||||
|
re := regexp.MustCompile("(" + valuePattern + ") ")
|
||||||
|
matches := re.FindAllStringSubmatch(string(b), -1)
|
||||||
|
for i, v := range matches {
|
||||||
|
fields[measurement+"."+strconv.Itoa(i)] = numberOrString(v[1])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// KEY0 VAL0\n
|
||||||
|
// KEY1 VAL1\n
|
||||||
|
// ...
|
||||||
|
fileFormat{
|
||||||
|
name: "New line separated key-space-value's",
|
||||||
|
pattern: "^(" + keyPattern + " " + valuePattern + "\n)+$",
|
||||||
|
parser: func(measurement string, fields map[string]interface{}, b []byte) {
|
||||||
|
re := regexp.MustCompile("(" + keyPattern + ") (" + valuePattern + ")\n")
|
||||||
|
matches := re.FindAllStringSubmatch(string(b), -1)
|
||||||
|
for _, v := range matches {
|
||||||
|
fields[measurement+"."+v[1]] = numberOrString(v[2])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func numberOrString(s string) interface{} {
|
||||||
|
i, err := strconv.Atoi(s)
|
||||||
|
if err == nil {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fileFormat) match(b []byte) (bool, error) {
|
||||||
|
ok, err := regexp.Match(f.pattern, b)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
11
plugins/inputs/cgroup/cgroup_notlinux.go
Normal file
11
plugins/inputs/cgroup/cgroup_notlinux.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package cgroup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (g *CGroup) Gather(acc telegraf.Accumulator) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
194
plugins/inputs/cgroup/cgroup_test.go
Normal file
194
plugins/inputs/cgroup/cgroup_test.go
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
// +build linux
|
||||||
|
|
||||||
|
package cgroup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf/testutil"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cg1 = &CGroup{
|
||||||
|
Paths: []string{"testdata/memory"},
|
||||||
|
Files: []string{
|
||||||
|
"memory.empty",
|
||||||
|
"memory.max_usage_in_bytes",
|
||||||
|
"memory.limit_in_bytes",
|
||||||
|
"memory.stat",
|
||||||
|
"memory.use_hierarchy",
|
||||||
|
"notify_on_release",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertContainsFields(a *testutil.Accumulator, t *testing.T, measurement string, fieldSet []map[string]interface{}) {
|
||||||
|
a.Lock()
|
||||||
|
defer a.Unlock()
|
||||||
|
|
||||||
|
numEquals := 0
|
||||||
|
for _, p := range a.Metrics {
|
||||||
|
if p.Measurement == measurement {
|
||||||
|
for _, fields := range fieldSet {
|
||||||
|
if reflect.DeepEqual(fields, p.Fields) {
|
||||||
|
numEquals++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if numEquals != len(fieldSet) {
|
||||||
|
assert.Fail(t, fmt.Sprintf("only %d of %d are equal", numEquals, len(fieldSet)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCgroupStatistics_1(t *testing.T) {
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
|
||||||
|
err := cg1.Gather(&acc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"memory.stat.cache": 1739362304123123123,
|
||||||
|
"memory.stat.rss": 1775325184,
|
||||||
|
"memory.stat.rss_huge": 778043392,
|
||||||
|
"memory.stat.mapped_file": 421036032,
|
||||||
|
"memory.stat.dirty": -307200,
|
||||||
|
"memory.max_usage_in_bytes.0": 0,
|
||||||
|
"memory.max_usage_in_bytes.1": -1,
|
||||||
|
"memory.max_usage_in_bytes.2": 2,
|
||||||
|
"memory.limit_in_bytes": 223372036854771712,
|
||||||
|
"memory.use_hierarchy": "12-781",
|
||||||
|
"notify_on_release": 0,
|
||||||
|
"path": "testdata/memory",
|
||||||
|
}
|
||||||
|
assertContainsFields(&acc, t, "cgroup", []map[string]interface{}{fields})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================================================================
|
||||||
|
|
||||||
|
var cg2 = &CGroup{
|
||||||
|
Paths: []string{"testdata/cpu"},
|
||||||
|
Files: []string{"cpuacct.usage_percpu"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCgroupStatistics_2(t *testing.T) {
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
|
||||||
|
err := cg2.Gather(&acc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"cpuacct.usage_percpu.0": -1452543795404,
|
||||||
|
"cpuacct.usage_percpu.1": 1376681271659,
|
||||||
|
"cpuacct.usage_percpu.2": 1450950799997,
|
||||||
|
"cpuacct.usage_percpu.3": -1473113374257,
|
||||||
|
"path": "testdata/cpu",
|
||||||
|
}
|
||||||
|
assertContainsFields(&acc, t, "cgroup", []map[string]interface{}{fields})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================================================================
|
||||||
|
|
||||||
|
var cg3 = &CGroup{
|
||||||
|
Paths: []string{"testdata/memory/*"},
|
||||||
|
Files: []string{"memory.limit_in_bytes"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCgroupStatistics_3(t *testing.T) {
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
|
||||||
|
err := cg3.Gather(&acc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"memory.limit_in_bytes": 223372036854771712,
|
||||||
|
"path": "testdata/memory/group_1",
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldsTwo := map[string]interface{}{
|
||||||
|
"memory.limit_in_bytes": 223372036854771712,
|
||||||
|
"path": "testdata/memory/group_2",
|
||||||
|
}
|
||||||
|
assertContainsFields(&acc, t, "cgroup", []map[string]interface{}{fields, fieldsTwo})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================================================================
|
||||||
|
|
||||||
|
var cg4 = &CGroup{
|
||||||
|
Paths: []string{"testdata/memory/*/*", "testdata/memory/group_2"},
|
||||||
|
Files: []string{"memory.limit_in_bytes"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCgroupStatistics_4(t *testing.T) {
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
|
||||||
|
err := cg4.Gather(&acc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"memory.limit_in_bytes": 223372036854771712,
|
||||||
|
"path": "testdata/memory/group_1/group_1_1",
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldsTwo := map[string]interface{}{
|
||||||
|
"memory.limit_in_bytes": 223372036854771712,
|
||||||
|
"path": "testdata/memory/group_1/group_1_2",
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldsThree := map[string]interface{}{
|
||||||
|
"memory.limit_in_bytes": 223372036854771712,
|
||||||
|
"path": "testdata/memory/group_2",
|
||||||
|
}
|
||||||
|
|
||||||
|
assertContainsFields(&acc, t, "cgroup", []map[string]interface{}{fields, fieldsTwo, fieldsThree})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================================================================
|
||||||
|
|
||||||
|
var cg5 = &CGroup{
|
||||||
|
Paths: []string{"testdata/memory/*/group_1_1"},
|
||||||
|
Files: []string{"memory.limit_in_bytes"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCgroupStatistics_5(t *testing.T) {
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
|
||||||
|
err := cg5.Gather(&acc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"memory.limit_in_bytes": 223372036854771712,
|
||||||
|
"path": "testdata/memory/group_1/group_1_1",
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldsTwo := map[string]interface{}{
|
||||||
|
"memory.limit_in_bytes": 223372036854771712,
|
||||||
|
"path": "testdata/memory/group_2/group_1_1",
|
||||||
|
}
|
||||||
|
assertContainsFields(&acc, t, "cgroup", []map[string]interface{}{fields, fieldsTwo})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================================================================
|
||||||
|
|
||||||
|
var cg6 = &CGroup{
|
||||||
|
Paths: []string{"testdata/memory"},
|
||||||
|
Files: []string{"memory.us*", "*/memory.kmem.*"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCgroupStatistics_6(t *testing.T) {
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
|
||||||
|
err := cg6.Gather(&acc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"memory.usage_in_bytes": 3513667584,
|
||||||
|
"memory.use_hierarchy": "12-781",
|
||||||
|
"memory.kmem.limit_in_bytes": 9223372036854771712,
|
||||||
|
"path": "testdata/memory",
|
||||||
|
}
|
||||||
|
assertContainsFields(&acc, t, "cgroup", []map[string]interface{}{fields})
|
||||||
|
}
|
||||||
1
plugins/inputs/cgroup/testdata/blkio/blkio.io_serviced
vendored
Normal file
1
plugins/inputs/cgroup/testdata/blkio/blkio.io_serviced
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Total 0
|
||||||
131
plugins/inputs/cgroup/testdata/blkio/blkio.throttle.io_serviced
vendored
Normal file
131
plugins/inputs/cgroup/testdata/blkio/blkio.throttle.io_serviced
vendored
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
11:0 Read 0
|
||||||
|
11:0 Write 0
|
||||||
|
11:0 Sync 0
|
||||||
|
11:0 Async 0
|
||||||
|
11:0 Total 0
|
||||||
|
8:0 Read 49134
|
||||||
|
8:0 Write 216703
|
||||||
|
8:0 Sync 177906
|
||||||
|
8:0 Async 87931
|
||||||
|
8:0 Total 265837
|
||||||
|
7:7 Read 0
|
||||||
|
7:7 Write 0
|
||||||
|
7:7 Sync 0
|
||||||
|
7:7 Async 0
|
||||||
|
7:7 Total 0
|
||||||
|
7:6 Read 0
|
||||||
|
7:6 Write 0
|
||||||
|
7:6 Sync 0
|
||||||
|
7:6 Async 0
|
||||||
|
7:6 Total 0
|
||||||
|
7:5 Read 0
|
||||||
|
7:5 Write 0
|
||||||
|
7:5 Sync 0
|
||||||
|
7:5 Async 0
|
||||||
|
7:5 Total 0
|
||||||
|
7:4 Read 0
|
||||||
|
7:4 Write 0
|
||||||
|
7:4 Sync 0
|
||||||
|
7:4 Async 0
|
||||||
|
7:4 Total 0
|
||||||
|
7:3 Read 0
|
||||||
|
7:3 Write 0
|
||||||
|
7:3 Sync 0
|
||||||
|
7:3 Async 0
|
||||||
|
7:3 Total 0
|
||||||
|
7:2 Read 0
|
||||||
|
7:2 Write 0
|
||||||
|
7:2 Sync 0
|
||||||
|
7:2 Async 0
|
||||||
|
7:2 Total 0
|
||||||
|
7:1 Read 0
|
||||||
|
7:1 Write 0
|
||||||
|
7:1 Sync 0
|
||||||
|
7:1 Async 0
|
||||||
|
7:1 Total 0
|
||||||
|
7:0 Read 0
|
||||||
|
7:0 Write 0
|
||||||
|
7:0 Sync 0
|
||||||
|
7:0 Async 0
|
||||||
|
7:0 Total 0
|
||||||
|
1:15 Read 3
|
||||||
|
1:15 Write 0
|
||||||
|
1:15 Sync 0
|
||||||
|
1:15 Async 3
|
||||||
|
1:15 Total 3
|
||||||
|
1:14 Read 3
|
||||||
|
1:14 Write 0
|
||||||
|
1:14 Sync 0
|
||||||
|
1:14 Async 3
|
||||||
|
1:14 Total 3
|
||||||
|
1:13 Read 3
|
||||||
|
1:13 Write 0
|
||||||
|
1:13 Sync 0
|
||||||
|
1:13 Async 3
|
||||||
|
1:13 Total 3
|
||||||
|
1:12 Read 3
|
||||||
|
1:12 Write 0
|
||||||
|
1:12 Sync 0
|
||||||
|
1:12 Async 3
|
||||||
|
1:12 Total 3
|
||||||
|
1:11 Read 3
|
||||||
|
1:11 Write 0
|
||||||
|
1:11 Sync 0
|
||||||
|
1:11 Async 3
|
||||||
|
1:11 Total 3
|
||||||
|
1:10 Read 3
|
||||||
|
1:10 Write 0
|
||||||
|
1:10 Sync 0
|
||||||
|
1:10 Async 3
|
||||||
|
1:10 Total 3
|
||||||
|
1:9 Read 3
|
||||||
|
1:9 Write 0
|
||||||
|
1:9 Sync 0
|
||||||
|
1:9 Async 3
|
||||||
|
1:9 Total 3
|
||||||
|
1:8 Read 3
|
||||||
|
1:8 Write 0
|
||||||
|
1:8 Sync 0
|
||||||
|
1:8 Async 3
|
||||||
|
1:8 Total 3
|
||||||
|
1:7 Read 3
|
||||||
|
1:7 Write 0
|
||||||
|
1:7 Sync 0
|
||||||
|
1:7 Async 3
|
||||||
|
1:7 Total 3
|
||||||
|
1:6 Read 3
|
||||||
|
1:6 Write 0
|
||||||
|
1:6 Sync 0
|
||||||
|
1:6 Async 3
|
||||||
|
1:6 Total 3
|
||||||
|
1:5 Read 3
|
||||||
|
1:5 Write 0
|
||||||
|
1:5 Sync 0
|
||||||
|
1:5 Async 3
|
||||||
|
1:5 Total 3
|
||||||
|
1:4 Read 3
|
||||||
|
1:4 Write 0
|
||||||
|
1:4 Sync 0
|
||||||
|
1:4 Async 3
|
||||||
|
1:4 Total 3
|
||||||
|
1:3 Read 3
|
||||||
|
1:3 Write 0
|
||||||
|
1:3 Sync 0
|
||||||
|
1:3 Async 3
|
||||||
|
1:3 Total 3
|
||||||
|
1:2 Read 3
|
||||||
|
1:2 Write 0
|
||||||
|
1:2 Sync 0
|
||||||
|
1:2 Async 3
|
||||||
|
1:2 Total 3
|
||||||
|
1:1 Read 3
|
||||||
|
1:1 Write 0
|
||||||
|
1:1 Sync 0
|
||||||
|
1:1 Async 3
|
||||||
|
1:1 Total 3
|
||||||
|
1:0 Read 3
|
||||||
|
1:0 Write 0
|
||||||
|
1:0 Sync 0
|
||||||
|
1:0 Async 3
|
||||||
|
1:0 Total 3
|
||||||
|
Total 265885
|
||||||
1
plugins/inputs/cgroup/testdata/cpu/cpu.cfs_quota_us
vendored
Normal file
1
plugins/inputs/cgroup/testdata/cpu/cpu.cfs_quota_us
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-1
|
||||||
1
plugins/inputs/cgroup/testdata/cpu/cpuacct.usage_percpu
vendored
Normal file
1
plugins/inputs/cgroup/testdata/cpu/cpuacct.usage_percpu
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-1452543795404 1376681271659 1450950799997 -1473113374257
|
||||||
1
plugins/inputs/cgroup/testdata/memory/group_1/group_1_1/memory.limit_in_bytes
vendored
Normal file
1
plugins/inputs/cgroup/testdata/memory/group_1/group_1_1/memory.limit_in_bytes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
223372036854771712
|
||||||
5
plugins/inputs/cgroup/testdata/memory/group_1/group_1_1/memory.stat
vendored
Normal file
5
plugins/inputs/cgroup/testdata/memory/group_1/group_1_1/memory.stat
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
cache 1739362304123123123
|
||||||
|
rss 1775325184
|
||||||
|
rss_huge 778043392
|
||||||
|
mapped_file 421036032
|
||||||
|
dirty -307200
|
||||||
1
plugins/inputs/cgroup/testdata/memory/group_1/group_1_2/memory.limit_in_bytes
vendored
Normal file
1
plugins/inputs/cgroup/testdata/memory/group_1/group_1_2/memory.limit_in_bytes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
223372036854771712
|
||||||
5
plugins/inputs/cgroup/testdata/memory/group_1/group_1_2/memory.stat
vendored
Normal file
5
plugins/inputs/cgroup/testdata/memory/group_1/group_1_2/memory.stat
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
cache 1739362304123123123
|
||||||
|
rss 1775325184
|
||||||
|
rss_huge 778043392
|
||||||
|
mapped_file 421036032
|
||||||
|
dirty -307200
|
||||||
1
plugins/inputs/cgroup/testdata/memory/group_1/memory.kmem.limit_in_bytes
vendored
Normal file
1
plugins/inputs/cgroup/testdata/memory/group_1/memory.kmem.limit_in_bytes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
9223372036854771712
|
||||||
1
plugins/inputs/cgroup/testdata/memory/group_1/memory.kmem.max_usage_in_bytes
vendored
Normal file
1
plugins/inputs/cgroup/testdata/memory/group_1/memory.kmem.max_usage_in_bytes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
1
plugins/inputs/cgroup/testdata/memory/group_1/memory.limit_in_bytes
vendored
Normal file
1
plugins/inputs/cgroup/testdata/memory/group_1/memory.limit_in_bytes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
223372036854771712
|
||||||
5
plugins/inputs/cgroup/testdata/memory/group_1/memory.stat
vendored
Normal file
5
plugins/inputs/cgroup/testdata/memory/group_1/memory.stat
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
cache 1739362304123123123
|
||||||
|
rss 1775325184
|
||||||
|
rss_huge 778043392
|
||||||
|
mapped_file 421036032
|
||||||
|
dirty -307200
|
||||||
1
plugins/inputs/cgroup/testdata/memory/group_2/group_1_1/memory.limit_in_bytes
vendored
Normal file
1
plugins/inputs/cgroup/testdata/memory/group_2/group_1_1/memory.limit_in_bytes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
223372036854771712
|
||||||
5
plugins/inputs/cgroup/testdata/memory/group_2/group_1_1/memory.stat
vendored
Normal file
5
plugins/inputs/cgroup/testdata/memory/group_2/group_1_1/memory.stat
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
cache 1739362304123123123
|
||||||
|
rss 1775325184
|
||||||
|
rss_huge 778043392
|
||||||
|
mapped_file 421036032
|
||||||
|
dirty -307200
|
||||||
1
plugins/inputs/cgroup/testdata/memory/group_2/memory.limit_in_bytes
vendored
Normal file
1
plugins/inputs/cgroup/testdata/memory/group_2/memory.limit_in_bytes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
223372036854771712
|
||||||
5
plugins/inputs/cgroup/testdata/memory/group_2/memory.stat
vendored
Normal file
5
plugins/inputs/cgroup/testdata/memory/group_2/memory.stat
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
cache 1739362304123123123
|
||||||
|
rss 1775325184
|
||||||
|
rss_huge 778043392
|
||||||
|
mapped_file 421036032
|
||||||
|
dirty -307200
|
||||||
0
plugins/inputs/cgroup/testdata/memory/memory.empty
vendored
Normal file
0
plugins/inputs/cgroup/testdata/memory/memory.empty
vendored
Normal file
1
plugins/inputs/cgroup/testdata/memory/memory.kmem.limit_in_bytes
vendored
Normal file
1
plugins/inputs/cgroup/testdata/memory/memory.kmem.limit_in_bytes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
9223372036854771712
|
||||||
1
plugins/inputs/cgroup/testdata/memory/memory.limit_in_bytes
vendored
Normal file
1
plugins/inputs/cgroup/testdata/memory/memory.limit_in_bytes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
223372036854771712
|
||||||
3
plugins/inputs/cgroup/testdata/memory/memory.max_usage_in_bytes
vendored
Normal file
3
plugins/inputs/cgroup/testdata/memory/memory.max_usage_in_bytes
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
0
|
||||||
|
-1
|
||||||
|
2
|
||||||
8
plugins/inputs/cgroup/testdata/memory/memory.numa_stat
vendored
Normal file
8
plugins/inputs/cgroup/testdata/memory/memory.numa_stat
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
total=858067 N0=858067
|
||||||
|
file=406254 N0=406254
|
||||||
|
anon=451792 N0=451792
|
||||||
|
unevictable=21 N0=21
|
||||||
|
hierarchical_total=858067 N0=858067
|
||||||
|
hierarchical_file=406254 N0=406254
|
||||||
|
hierarchical_anon=451792 N0=451792
|
||||||
|
hierarchical_unevictable=21 N0=21
|
||||||
5
plugins/inputs/cgroup/testdata/memory/memory.stat
vendored
Normal file
5
plugins/inputs/cgroup/testdata/memory/memory.stat
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
cache 1739362304123123123
|
||||||
|
rss 1775325184
|
||||||
|
rss_huge 778043392
|
||||||
|
mapped_file 421036032
|
||||||
|
dirty -307200
|
||||||
1
plugins/inputs/cgroup/testdata/memory/memory.usage_in_bytes
vendored
Normal file
1
plugins/inputs/cgroup/testdata/memory/memory.usage_in_bytes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3513667584
|
||||||
1
plugins/inputs/cgroup/testdata/memory/memory.use_hierarchy
vendored
Normal file
1
plugins/inputs/cgroup/testdata/memory/memory.use_hierarchy
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
12-781
|
||||||
1
plugins/inputs/cgroup/testdata/memory/notify_on_release
vendored
Normal file
1
plugins/inputs/cgroup/testdata/memory/notify_on_release
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
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
|
This plugin uses a credential chain for Authentication with the CloudWatch
|
||||||
API endpoint. In the following order the plugin will attempt to authenticate.
|
API endpoint. In the following order the plugin will attempt to authenticate.
|
||||||
1. [IAMS Role](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html)
|
1. Assumed credentials via STS if `role_arn` attribute is specified (source credentials are evaluated from subsequent rules)
|
||||||
2. [Environment Variables](https://github.com/aws/aws-sdk-go/wiki/configuring-sdk#environment-variables)
|
2. Explicit credentials from `access_key`, `secret_key`, and `token` attributes
|
||||||
3. [Shared Credentials](https://github.com/aws/aws-sdk-go/wiki/configuring-sdk#shared-credentials-file)
|
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:
|
### Configuration:
|
||||||
|
|
||||||
@@ -31,6 +34,11 @@ API endpoint. In the following order the plugin will attempt to authenticate.
|
|||||||
## Metric Statistic Namespace (required)
|
## Metric Statistic Namespace (required)
|
||||||
namespace = 'AWS/ELB'
|
namespace = 'AWS/ELB'
|
||||||
|
|
||||||
|
## Maximum requests per second. Note that the global default AWS rate limit is
|
||||||
|
## 10 reqs/sec, so if you define multiple namespaces, these should add up to a
|
||||||
|
## maximum of 10. Optional - default value is 10.
|
||||||
|
ratelimit = 10
|
||||||
|
|
||||||
## Metrics to Pull (optional)
|
## Metrics to Pull (optional)
|
||||||
## Defaults to all Metrics in Namespace if nothing is provided
|
## Defaults to all Metrics in Namespace if nothing is provided
|
||||||
## Refreshes Namespace available metrics every 1h
|
## Refreshes Namespace available metrics every 1h
|
||||||
@@ -41,6 +49,10 @@ API endpoint. In the following order the plugin will attempt to authenticate.
|
|||||||
[[inputs.cloudwatch.metrics.dimensions]]
|
[[inputs.cloudwatch.metrics.dimensions]]
|
||||||
name = 'LoadBalancerName'
|
name = 'LoadBalancerName'
|
||||||
value = 'p-example'
|
value = 'p-example'
|
||||||
|
|
||||||
|
[[inputs.cloudwatch.metrics.dimensions]]
|
||||||
|
name = 'AvailabilityZone'
|
||||||
|
value = '*'
|
||||||
```
|
```
|
||||||
#### Requirements and Terminology
|
#### Requirements and Terminology
|
||||||
|
|
||||||
@@ -52,6 +64,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
|
- `names` must be valid CloudWatch [Metric](http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/cloudwatch_concepts.html#Metric) names
|
||||||
- `dimensions` must be valid CloudWatch [Dimension](http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/cloudwatch_concepts.html#Dimension) name/value pairs
|
- `dimensions` must be valid CloudWatch [Dimension](http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/cloudwatch_concepts.html#Dimension) name/value pairs
|
||||||
|
|
||||||
|
Omitting or specifying a value of `'*'` for a dimension value configures all available metrics that contain a dimension with the specified name
|
||||||
|
to be retrieved. If specifying >1 dimension, then the metric must contain *all* the configured dimensions where the the value of the
|
||||||
|
wildcard dimension is ignored.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
[[inputs.cloudwatch.metrics]]
|
||||||
|
names = ['Latency']
|
||||||
|
|
||||||
|
## Dimension filters for Metric (optional)
|
||||||
|
[[inputs.cloudwatch.metrics.dimensions]]
|
||||||
|
name = 'LoadBalancerName'
|
||||||
|
value = 'p-example'
|
||||||
|
|
||||||
|
[[inputs.cloudwatch.metrics.dimensions]]
|
||||||
|
name = 'AvailabilityZone'
|
||||||
|
value = '*'
|
||||||
|
```
|
||||||
|
|
||||||
|
If the following ELBs are available:
|
||||||
|
- name: `p-example`, availabilityZone: `us-east-1a`
|
||||||
|
- name: `p-example`, availabilityZone: `us-east-1b`
|
||||||
|
- name: `q-example`, availabilityZone: `us-east-1a`
|
||||||
|
- name: `q-example`, availabilityZone: `us-east-1b`
|
||||||
|
|
||||||
|
|
||||||
|
Then 2 metrics will be output:
|
||||||
|
- name: `p-example`, availabilityZone: `us-east-1a`
|
||||||
|
- name: `p-example`, availabilityZone: `us-east-1b`
|
||||||
|
|
||||||
|
If the `AvailabilityZone` wildcard dimension was omitted, then a single metric (name: `p-example`)
|
||||||
|
would be exported containing the aggregate values of the ELB across availability zones.
|
||||||
|
|
||||||
#### Restrictions and Limitations
|
#### Restrictions and Limitations
|
||||||
- CloudWatch metrics are not available instantly via the CloudWatch API. You should adjust your collection `delay` to account for this lag in metrics availability based on your [monitoring subscription level](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-cloudwatch-new.html)
|
- CloudWatch metrics are not available instantly via the CloudWatch API. You should adjust your collection `delay` to account for this lag in metrics availability based on your [monitoring subscription level](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-cloudwatch-new.html)
|
||||||
- CloudWatch API usage incurs cost - see [GetMetricStatistics Pricing](https://aws.amazon.com/cloudwatch/pricing/)
|
- CloudWatch API usage incurs cost - see [GetMetricStatistics Pricing](https://aws.amazon.com/cloudwatch/pricing/)
|
||||||
|
|||||||
@@ -3,28 +3,37 @@ package cloudwatch
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/session"
|
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
"github.com/influxdata/telegraf/internal"
|
"github.com/influxdata/telegraf/internal"
|
||||||
|
internalaws "github.com/influxdata/telegraf/internal/config/aws"
|
||||||
|
"github.com/influxdata/telegraf/internal/errchan"
|
||||||
|
"github.com/influxdata/telegraf/internal/limiter"
|
||||||
"github.com/influxdata/telegraf/plugins/inputs"
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
CloudWatch struct {
|
CloudWatch struct {
|
||||||
Region string `toml:"region"`
|
Region string `toml:"region"`
|
||||||
|
AccessKey string `toml:"access_key"`
|
||||||
|
SecretKey string `toml:"secret_key"`
|
||||||
|
RoleARN string `toml:"role_arn"`
|
||||||
|
Profile string `toml:"profile"`
|
||||||
|
Filename string `toml:"shared_credential_file"`
|
||||||
|
Token string `toml:"token"`
|
||||||
|
|
||||||
Period internal.Duration `toml:"period"`
|
Period internal.Duration `toml:"period"`
|
||||||
Delay internal.Duration `toml:"delay"`
|
Delay internal.Duration `toml:"delay"`
|
||||||
Namespace string `toml:"namespace"`
|
Namespace string `toml:"namespace"`
|
||||||
Metrics []*Metric `toml:"metrics"`
|
Metrics []*Metric `toml:"metrics"`
|
||||||
|
CacheTTL internal.Duration `toml:"cache_ttl"`
|
||||||
|
RateLimit int `toml:"ratelimit"`
|
||||||
client cloudwatchClient
|
client cloudwatchClient
|
||||||
metricCache *MetricCache
|
metricCache *MetricCache
|
||||||
}
|
}
|
||||||
@@ -56,6 +65,21 @@ func (c *CloudWatch) SampleConfig() string {
|
|||||||
## Amazon Region
|
## Amazon Region
|
||||||
region = 'us-east-1'
|
region = 'us-east-1'
|
||||||
|
|
||||||
|
## Amazon Credentials
|
||||||
|
## Credentials are loaded in the following order
|
||||||
|
## 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)
|
## Requested CloudWatch aggregation Period (required - must be a multiple of 60s)
|
||||||
period = '1m'
|
period = '1m'
|
||||||
|
|
||||||
@@ -66,9 +90,18 @@ func (c *CloudWatch) SampleConfig() string {
|
|||||||
## gaps or overlap in pulled data
|
## gaps or overlap in pulled data
|
||||||
interval = '1m'
|
interval = '1m'
|
||||||
|
|
||||||
|
## Configure the TTL for the internal cache of metrics.
|
||||||
|
## Defaults to 1 hr if not specified
|
||||||
|
#cache_ttl = '10m'
|
||||||
|
|
||||||
## Metric Statistic Namespace (required)
|
## Metric Statistic Namespace (required)
|
||||||
namespace = 'AWS/ELB'
|
namespace = 'AWS/ELB'
|
||||||
|
|
||||||
|
## Maximum requests per second. Note that the global default AWS rate limit is
|
||||||
|
## 10 reqs/sec, so if you define multiple namespaces, these should add up to a
|
||||||
|
## maximum of 10. Optional - default value is 10.
|
||||||
|
ratelimit = 10
|
||||||
|
|
||||||
## Metrics to Pull (optional)
|
## Metrics to Pull (optional)
|
||||||
## Defaults to all Metrics in Namespace if nothing is provided
|
## Defaults to all Metrics in Namespace if nothing is provided
|
||||||
## Refreshes Namespace available metrics every 1h
|
## Refreshes Namespace available metrics every 1h
|
||||||
@@ -97,20 +130,40 @@ func (c *CloudWatch) Gather(acc telegraf.Accumulator) error {
|
|||||||
if c.Metrics != nil {
|
if c.Metrics != nil {
|
||||||
metrics = []*cloudwatch.Metric{}
|
metrics = []*cloudwatch.Metric{}
|
||||||
for _, m := range c.Metrics {
|
for _, m := range c.Metrics {
|
||||||
dimensions := make([]*cloudwatch.Dimension, len(m.Dimensions))
|
if !hasWilcard(m.Dimensions) {
|
||||||
for k, d := range m.Dimensions {
|
dimensions := make([]*cloudwatch.Dimension, len(m.Dimensions))
|
||||||
dimensions[k] = &cloudwatch.Dimension{
|
for k, d := range m.Dimensions {
|
||||||
Name: aws.String(d.Name),
|
fmt.Printf("Dimension [%s]:[%s]\n", d.Name, d.Value)
|
||||||
Value: aws.String(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 {
|
} else {
|
||||||
var err error
|
var err error
|
||||||
@@ -121,30 +174,36 @@ func (c *CloudWatch) Gather(acc telegraf.Accumulator) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
metricCount := len(metrics)
|
metricCount := len(metrics)
|
||||||
var errChan = make(chan error, metricCount)
|
errChan := errchan.New(metricCount)
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
// limit concurrency or we can easily exhaust user connection limit
|
// limit concurrency or we can easily exhaust user connection limit
|
||||||
semaphore := make(chan byte, 64)
|
// see cloudwatch API request limits:
|
||||||
|
// http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/cloudwatch_limits.html
|
||||||
|
lmtr := limiter.NewRateLimiter(c.RateLimit, time.Second)
|
||||||
|
defer lmtr.Stop()
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(len(metrics))
|
||||||
for _, m := range metrics {
|
for _, m := range metrics {
|
||||||
semaphore <- 0x1
|
<-lmtr.C
|
||||||
go c.gatherMetric(acc, m, now, semaphore, errChan)
|
go func(inm *cloudwatch.Metric) {
|
||||||
|
defer wg.Done()
|
||||||
|
c.gatherMetric(acc, inm, now, errChan.C)
|
||||||
|
}(m)
|
||||||
}
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
for i := 1; i <= metricCount; i++ {
|
return errChan.Error()
|
||||||
err := <-errChan
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
inputs.Add("cloudwatch", func() telegraf.Input {
|
inputs.Add("cloudwatch", func() telegraf.Input {
|
||||||
return &CloudWatch{}
|
ttl, _ := time.ParseDuration("1hr")
|
||||||
|
return &CloudWatch{
|
||||||
|
CacheTTL: internal.Duration{Duration: ttl},
|
||||||
|
RateLimit: 10,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,17 +211,18 @@ func init() {
|
|||||||
* Initialize CloudWatch client
|
* Initialize CloudWatch client
|
||||||
*/
|
*/
|
||||||
func (c *CloudWatch) initializeCloudWatch() error {
|
func (c *CloudWatch) initializeCloudWatch() error {
|
||||||
config := &aws.Config{
|
credentialConfig := &internalaws.CredentialConfig{
|
||||||
Region: aws.String(c.Region),
|
Region: c.Region,
|
||||||
Credentials: credentials.NewChainCredentials(
|
AccessKey: c.AccessKey,
|
||||||
[]credentials.Provider{
|
SecretKey: c.SecretKey,
|
||||||
&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(session.New())},
|
RoleARN: c.RoleARN,
|
||||||
&credentials.EnvProvider{},
|
Profile: c.Profile,
|
||||||
&credentials.SharedCredentialsProvider{},
|
Filename: c.Filename,
|
||||||
}),
|
Token: c.Token,
|
||||||
}
|
}
|
||||||
|
configProvider := credentialConfig.Credentials()
|
||||||
|
|
||||||
c.client = cloudwatch.New(session.New(config))
|
c.client = cloudwatch.New(configProvider)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,11 +257,10 @@ func (c *CloudWatch) fetchNamespaceMetrics() (metrics []*cloudwatch.Metric, err
|
|||||||
more = token != nil
|
more = token != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheTTL, _ := time.ParseDuration("1hr")
|
|
||||||
c.metricCache = &MetricCache{
|
c.metricCache = &MetricCache{
|
||||||
Metrics: metrics,
|
Metrics: metrics,
|
||||||
Fetched: time.Now(),
|
Fetched: time.Now(),
|
||||||
TTL: cacheTTL,
|
TTL: c.CacheTTL.Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -210,12 +269,16 @@ func (c *CloudWatch) fetchNamespaceMetrics() (metrics []*cloudwatch.Metric, err
|
|||||||
/*
|
/*
|
||||||
* Gather given Metric and emit any error
|
* Gather given Metric and emit any error
|
||||||
*/
|
*/
|
||||||
func (c *CloudWatch) gatherMetric(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)
|
params := c.getStatisticsInput(metric, now)
|
||||||
resp, err := c.client.GetMetricStatistics(params)
|
resp, err := c.client.GetMetricStatistics(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errChan <- err
|
errChan <- err
|
||||||
<-semaphore
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,7 +315,6 @@ func (c *CloudWatch) gatherMetric(acc telegraf.Accumulator, metric *cloudwatch.M
|
|||||||
}
|
}
|
||||||
|
|
||||||
errChan <- nil
|
errChan <- nil
|
||||||
<-semaphore
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -303,3 +365,32 @@ func (c *CloudWatch) getStatisticsInput(metric *cloudwatch.Metric, now time.Time
|
|||||||
func (c *MetricCache) IsValid() bool {
|
func (c *MetricCache) IsValid() bool {
|
||||||
return c.Metrics != nil && time.Since(c.Fetched) < c.TTL
|
return c.Metrics != nil && time.Since(c.Fetched) < c.TTL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hasWilcard(dimensions []*Dimension) bool {
|
||||||
|
for _, d := range dimensions {
|
||||||
|
if d.Value == "" || d.Value == "*" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSelected(metric *cloudwatch.Metric, dimensions []*Dimension) bool {
|
||||||
|
if len(metric.Dimensions) != len(dimensions) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, d := range dimensions {
|
||||||
|
selected := false
|
||||||
|
for _, d2 := range metric.Dimensions {
|
||||||
|
if d.Name == *d2.Name {
|
||||||
|
if d.Value == "" || d.Value == "*" || d.Value == *d2.Value {
|
||||||
|
selected = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !selected {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ func TestGather(t *testing.T) {
|
|||||||
Namespace: "AWS/ELB",
|
Namespace: "AWS/ELB",
|
||||||
Delay: internalDuration,
|
Delay: internalDuration,
|
||||||
Period: internalDuration,
|
Period: internalDuration,
|
||||||
|
RateLimit: 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
var acc testutil.Accumulator
|
var acc testutil.Accumulator
|
||||||
|
|||||||
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),
|
||||||
|
})
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user