Compare commits
4 Commits
dgn-extern
...
kb-procsta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2eea71c1f | ||
|
|
da9b719889 | ||
|
|
07f57710df | ||
|
|
dc84cce95c |
74
CHANGELOG.md
74
CHANGELOG.md
@@ -1,57 +1,9 @@
|
|||||||
## v1.2 [unreleased]
|
## v1.1 [unreleased]
|
||||||
|
|
||||||
### Release Notes
|
### Release Notes
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- [#2123](https://github.com/influxdata/telegraf/pull/2123): Fix improper calculation of CPU percentages
|
|
||||||
- [#1564](https://github.com/influxdata/telegraf/issues/1564): Use RFC3339 timestamps in log output.
|
|
||||||
- [#1997](https://github.com/influxdata/telegraf/issues/1997): Non-default HTTP timeouts for RabbitMQ plugin.
|
|
||||||
- [#2074](https://github.com/influxdata/telegraf/pull/2074): "discard" output plugin added, primarily for testing purposes.
|
|
||||||
- [#1965](https://github.com/influxdata/telegraf/pull/1965): The JSON parser can now parse an array of objects using the same configuration.
|
|
||||||
- [#1807](https://github.com/influxdata/telegraf/pull/1807): Option to use device name rather than path for reporting disk stats.
|
|
||||||
- [#1348](https://github.com/influxdata/telegraf/issues/1348): Telegraf "internal" plugin for collecting stats on itself.
|
|
||||||
- [#2127](https://github.com/influxdata/telegraf/pull/2127): Update Go version to 1.7.4.
|
|
||||||
- [#2126](https://github.com/influxdata/telegraf/pull/2126): Support a metric.Split function.
|
|
||||||
- [#2026](https://github.com/influxdata/telegraf/pull/2065): elasticsearch "shield" (basic auth) support doc.
|
|
||||||
|
|
||||||
### Bugfixes
|
|
||||||
|
|
||||||
- [#2049](https://github.com/influxdata/telegraf/pull/2049): Fix the Value data format not trimming null characters from input.
|
|
||||||
- [#1949](https://github.com/influxdata/telegraf/issues/1949): Fix windows `net` plugin.
|
|
||||||
- [#1775](https://github.com/influxdata/telegraf/issues/1775): Cache & expire metrics for delivery to prometheus
|
|
||||||
|
|
||||||
## v1.1.2 [2016-12-12]
|
|
||||||
|
|
||||||
### Bugfixes
|
|
||||||
|
|
||||||
- [#2007](https://github.com/influxdata/telegraf/issues/2007): Make snmptranslate not required when using numeric OID.
|
|
||||||
- [#2104](https://github.com/influxdata/telegraf/issues/2104): Add a global snmp translation cache.
|
|
||||||
|
|
||||||
## v1.1.1 [2016-11-14]
|
|
||||||
|
|
||||||
### Bugfixes
|
|
||||||
|
|
||||||
- [#2023](https://github.com/influxdata/telegraf/issues/2023): Fix issue parsing toml durations with single quotes.
|
|
||||||
|
|
||||||
## v1.1.0 [2016-11-07]
|
|
||||||
|
|
||||||
### Release Notes
|
|
||||||
|
|
||||||
- Telegraf now supports two new types of plugins: processors & aggregators.
|
|
||||||
|
|
||||||
- On systemd Telegraf will no longer redirect it's stdout to /var/log/telegraf/telegraf.log.
|
|
||||||
On most systems, the logs will be directed to the systemd journal and can be
|
|
||||||
accessed by `journalctl -u telegraf.service`. Consult the systemd journal
|
|
||||||
documentation for configuring journald. There is also a [`logfile` config option](https://github.com/influxdata/telegraf/blob/master/etc/telegraf.conf#L70)
|
|
||||||
available in 1.1, which will allow users to easily configure telegraf to
|
|
||||||
continue sending logs to /var/log/telegraf/telegraf.log.
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- [#1726](https://github.com/influxdata/telegraf/issues/1726): Processor & Aggregator plugin support.
|
|
||||||
- [#1861](https://github.com/influxdata/telegraf/pull/1861): adding the tags in the graylog output plugin
|
|
||||||
- [#1732](https://github.com/influxdata/telegraf/pull/1732): Telegraf systemd service, log to journal.
|
|
||||||
- [#1782](https://github.com/influxdata/telegraf/pull/1782): Allow numeric and non-string values for tag_keys.
|
- [#1782](https://github.com/influxdata/telegraf/pull/1782): Allow numeric and non-string values for tag_keys.
|
||||||
- [#1694](https://github.com/influxdata/telegraf/pull/1694): Adding Gauge and Counter metric types.
|
- [#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
|
- [#1606](https://github.com/influxdata/telegraf/pull/1606): Remove carraige returns from exec plugin output on Windows
|
||||||
@@ -66,22 +18,18 @@ continue sending logs to /var/log/telegraf/telegraf.log.
|
|||||||
- [#1542](https://github.com/influxdata/telegraf/pull/1542): Add filestack webhook 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.
|
- [#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.
|
- [#1697](https://github.com/influxdata/telegraf/pull/1697): Add NATS output plugin.
|
||||||
- [#1407](https://github.com/influxdata/telegraf/pull/1407) & [#1915](https://github.com/influxdata/telegraf/pull/1915): HTTP service listener input 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
|
- [#1699](https://github.com/influxdata/telegraf/pull/1699): Add database blacklist option for Postgresql
|
||||||
- [#1791](https://github.com/influxdata/telegraf/pull/1791): Add Docker container state metrics to Docker input plugin output
|
- [#1791](https://github.com/influxdata/telegraf/pull/1791): Add Docker container state metrics to Docker input plugin output
|
||||||
- [#1755](https://github.com/influxdata/telegraf/issues/1755): Add support to SNMP for IP & MAC address conversion.
|
- [#1755](https://github.com/influxdata/telegraf/issues/1755): Add support to SNMP for IP & MAC address conversion.
|
||||||
- [#1729](https://github.com/influxdata/telegraf/issues/1729): Add support to SNMP for OID index suffixes.
|
- [#1729](https://github.com/influxdata/telegraf/issues/1729): Add support to SNMP for OID index suffixes.
|
||||||
- [#1813](https://github.com/influxdata/telegraf/pull/1813): Change default arguments for SNMP plugin.
|
- [#1813](https://github.com/influxdata/telegraf/pull/1813): Change default arguments for SNMP plugin.
|
||||||
- [#1686](https://github.com/influxdata/telegraf/pull/1686): Mesos input plugin: very high-cardinality mesos-task metrics removed.
|
- [#1686](https://github.com/influxdata/telegraf/pull/1686): Mesos input plugin: very high-cardinality mesos-task metrics removed.
|
||||||
|
- [#1839](https://github.com/influxdata/telegraf/pull/1839): Exact match with pgrep -x option in procstat
|
||||||
- [#1838](https://github.com/influxdata/telegraf/pull/1838): Logging overhaul to centralize the logger & log levels, & provide a logfile config option.
|
- [#1838](https://github.com/influxdata/telegraf/pull/1838): Logging overhaul to centralize the logger & log levels, & provide a logfile config option.
|
||||||
- [#1700](https://github.com/influxdata/telegraf/pull/1700): HAProxy plugin socket glob matching.
|
|
||||||
- [#1847](https://github.com/influxdata/telegraf/pull/1847): Add Kubernetes plugin for retrieving pod metrics.
|
|
||||||
|
|
||||||
### Bugfixes
|
### Bugfixes
|
||||||
|
|
||||||
- [#1955](https://github.com/influxdata/telegraf/issues/1955): Fix NATS plug-ins reconnection logic.
|
|
||||||
- [#1936](https://github.com/influxdata/telegraf/issues/1936): Set required default values in udp_listener & tcp_listener.
|
|
||||||
- [#1926](https://github.com/influxdata/telegraf/issues/1926): Fix toml unmarshal panic in Duration objects.
|
|
||||||
- [#1746](https://github.com/influxdata/telegraf/issues/1746): Fix handling of non-string values for JSON keys listed in tag_keys.
|
- [#1746](https://github.com/influxdata/telegraf/issues/1746): Fix handling of non-string values for JSON keys listed in tag_keys.
|
||||||
- [#1628](https://github.com/influxdata/telegraf/issues/1628): Fix mongodb input panic on version 2.2.
|
- [#1628](https://github.com/influxdata/telegraf/issues/1628): Fix mongodb input panic on version 2.2.
|
||||||
- [#1733](https://github.com/influxdata/telegraf/issues/1733): Fix statsd scientific notation parsing
|
- [#1733](https://github.com/influxdata/telegraf/issues/1733): Fix statsd scientific notation parsing
|
||||||
@@ -96,22 +44,8 @@ continue sending logs to /var/log/telegraf/telegraf.log.
|
|||||||
- [#1772](https://github.com/influxdata/telegraf/pull/1772): Windows remote management interactive service fix.
|
- [#1772](https://github.com/influxdata/telegraf/pull/1772): Windows remote management interactive service fix.
|
||||||
- [#1702](https://github.com/influxdata/telegraf/issues/1702): sqlserver, fix issue when case sensitive collation is activated.
|
- [#1702](https://github.com/influxdata/telegraf/issues/1702): sqlserver, fix issue when case sensitive collation is activated.
|
||||||
- [#1823](https://github.com/influxdata/telegraf/issues/1823): Fix huge allocations in http_listener when dealing with huge payloads.
|
- [#1823](https://github.com/influxdata/telegraf/issues/1823): Fix huge allocations in http_listener when dealing with huge payloads.
|
||||||
- [#1833](https://github.com/influxdata/telegraf/issues/1833): Fix translating SNMP fields not in MIB.
|
|
||||||
- [#1835](https://github.com/influxdata/telegraf/issues/1835): Fix SNMP emitting empty fields.
|
|
||||||
- [#1854](https://github.com/influxdata/telegraf/pull/1853): SQL Server waitstats truncation bug.
|
|
||||||
- [#1810](https://github.com/influxdata/telegraf/issues/1810): Fix logparser common log format: numbers in ident.
|
|
||||||
- [#1793](https://github.com/influxdata/telegraf/pull/1793): Fix JSON Serialization in OpenTSDB output.
|
|
||||||
- [#1731](https://github.com/influxdata/telegraf/issues/1731): Fix Graphite template ordering, use most specific.
|
|
||||||
- [#1836](https://github.com/influxdata/telegraf/pull/1836): Fix snmp table field initialization for non-automatic table.
|
|
||||||
- [#1724](https://github.com/influxdata/telegraf/issues/1724): cgroups path being parsed as metric.
|
|
||||||
- [#1886](https://github.com/influxdata/telegraf/issues/1886): Fix phpfpm fcgi client panic when URL does not exist.
|
|
||||||
- [#1344](https://github.com/influxdata/telegraf/issues/1344): Fix config file parse error logging.
|
|
||||||
- [#1771](https://github.com/influxdata/telegraf/issues/1771): Delete nil fields in the metric maker.
|
|
||||||
- [#870](https://github.com/influxdata/telegraf/issues/870): Fix MySQL special characters in DSN parsing.
|
|
||||||
- [#1742](https://github.com/influxdata/telegraf/issues/1742): Ping input odd timeout behavior.
|
|
||||||
- [#1950](https://github.com/influxdata/telegraf/pull/1950): Switch to github.com/kballard/go-shellquote.
|
|
||||||
|
|
||||||
## v1.0.1 [2016-09-26]
|
## v1.0.1 [unreleased]
|
||||||
|
|
||||||
### Bugfixes
|
### Bugfixes
|
||||||
|
|
||||||
|
|||||||
190
CONTRIBUTING.md
190
CONTRIBUTING.md
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
1. [Sign the CLA](http://influxdb.com/community/cla.html)
|
1. [Sign the CLA](http://influxdb.com/community/cla.html)
|
||||||
1. Make changes or write plugin (see below for details)
|
1. Make changes or write plugin (see below for details)
|
||||||
1. Add your plugin to one of: `plugins/{inputs,outputs,aggregators,processors}/all/all.go`
|
1. Add your plugin to `plugins/inputs/all/all.go` or `plugins/outputs/all/all.go`
|
||||||
1. If your plugin requires a new Go package,
|
1. If your plugin requires a new Go package,
|
||||||
[add it](https://github.com/influxdata/telegraf/blob/master/CONTRIBUTING.md#adding-a-dependency)
|
[add it](https://github.com/influxdata/telegraf/blob/master/CONTRIBUTING.md#adding-a-dependency)
|
||||||
1. Write a README for your plugin, if it's an input plugin, it should be structured
|
1. Write a README for your plugin, if it's an input plugin, it should be structured
|
||||||
@@ -16,8 +16,8 @@ for a good example.
|
|||||||
|
|
||||||
## GoDoc
|
## GoDoc
|
||||||
|
|
||||||
Public interfaces for inputs, outputs, processors, aggregators, metrics,
|
Public interfaces for inputs, outputs, metrics, and the accumulator can be found
|
||||||
and the accumulator can be found on the GoDoc
|
on the GoDoc
|
||||||
|
|
||||||
[](https://godoc.org/github.com/influxdata/telegraf)
|
[](https://godoc.org/github.com/influxdata/telegraf)
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ and submit new inputs.
|
|||||||
|
|
||||||
### Input Plugin Guidelines
|
### Input Plugin Guidelines
|
||||||
|
|
||||||
* A plugin must conform to the [`telegraf.Input`](https://godoc.org/github.com/influxdata/telegraf#Input) interface.
|
* A plugin must conform to the `telegraf.Input` interface.
|
||||||
* Input Plugins should call `inputs.Add` in their `init` function to register themselves.
|
* Input Plugins should call `inputs.Add` in their `init` function to register themselves.
|
||||||
See below for a quick example.
|
See below for a quick example.
|
||||||
* Input Plugins must be added to the
|
* Input Plugins must be added to the
|
||||||
@@ -177,7 +177,7 @@ similar constructs.
|
|||||||
|
|
||||||
### Output Plugin Guidelines
|
### Output Plugin Guidelines
|
||||||
|
|
||||||
* An output must conform to the [`telegraf.Output`](https://godoc.org/github.com/influxdata/telegraf#Output) interface.
|
* An output must conform to the `outputs.Output` interface.
|
||||||
* Outputs should call `outputs.Add` in their `init` function to register themselves.
|
* Outputs should call `outputs.Add` in their `init` function to register themselves.
|
||||||
See below for a quick example.
|
See below for a quick example.
|
||||||
* To be available within Telegraf itself, plugins must add themselves to the
|
* To be available within Telegraf itself, plugins must add themselves to the
|
||||||
@@ -275,186 +275,6 @@ and `Stop()` methods.
|
|||||||
* Same as the `Output` guidelines, except that they must conform to the
|
* Same as the `Output` guidelines, except that they must conform to the
|
||||||
`output.ServiceOutput` interface.
|
`output.ServiceOutput` interface.
|
||||||
|
|
||||||
## Processor Plugins
|
|
||||||
|
|
||||||
This section is for developers who want to create a new processor plugin.
|
|
||||||
|
|
||||||
### Processor Plugin Guidelines
|
|
||||||
|
|
||||||
* A processor must conform to the [`telegraf.Processor`](https://godoc.org/github.com/influxdata/telegraf#Processor) interface.
|
|
||||||
* Processors should call `processors.Add` in their `init` function to register themselves.
|
|
||||||
See below for a quick example.
|
|
||||||
* To be available within Telegraf itself, plugins must add themselves to the
|
|
||||||
`github.com/influxdata/telegraf/plugins/processors/all/all.go` file.
|
|
||||||
* The `SampleConfig` function should return valid toml that describes how the
|
|
||||||
processor can be configured. This is include in `telegraf -sample-config`.
|
|
||||||
* The `Description` function should say in one line what this processor does.
|
|
||||||
|
|
||||||
### Processor Example
|
|
||||||
|
|
||||||
```go
|
|
||||||
package printer
|
|
||||||
|
|
||||||
// printer.go
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
|
||||||
"github.com/influxdata/telegraf/plugins/processors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Printer struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
var sampleConfig = `
|
|
||||||
`
|
|
||||||
|
|
||||||
func (p *Printer) SampleConfig() string {
|
|
||||||
return sampleConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Printer) Description() string {
|
|
||||||
return "Print all metrics that pass through this filter."
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Printer) Apply(in ...telegraf.Metric) []telegraf.Metric {
|
|
||||||
for _, metric := range in {
|
|
||||||
fmt.Println(metric.String())
|
|
||||||
}
|
|
||||||
return in
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
processors.Add("printer", func() telegraf.Processor {
|
|
||||||
return &Printer{}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Aggregator Plugins
|
|
||||||
|
|
||||||
This section is for developers who want to create a new aggregator plugin.
|
|
||||||
|
|
||||||
### Aggregator Plugin Guidelines
|
|
||||||
|
|
||||||
* A aggregator must conform to the [`telegraf.Aggregator`](https://godoc.org/github.com/influxdata/telegraf#Aggregator) interface.
|
|
||||||
* Aggregators should call `aggregators.Add` in their `init` function to register themselves.
|
|
||||||
See below for a quick example.
|
|
||||||
* To be available within Telegraf itself, plugins must add themselves to the
|
|
||||||
`github.com/influxdata/telegraf/plugins/aggregators/all/all.go` file.
|
|
||||||
* The `SampleConfig` function should return valid toml that describes how the
|
|
||||||
aggregator can be configured. This is include in `telegraf -sample-config`.
|
|
||||||
* The `Description` function should say in one line what this aggregator does.
|
|
||||||
* The Aggregator plugin will need to keep caches of metrics that have passed
|
|
||||||
through it. This should be done using the builtin `HashID()` function of each
|
|
||||||
metric.
|
|
||||||
* When the `Reset()` function is called, all caches should be cleared.
|
|
||||||
|
|
||||||
### Aggregator Example
|
|
||||||
|
|
||||||
```go
|
|
||||||
package min
|
|
||||||
|
|
||||||
// min.go
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/influxdata/telegraf"
|
|
||||||
"github.com/influxdata/telegraf/plugins/aggregators"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Min struct {
|
|
||||||
// caches for metric fields, names, and tags
|
|
||||||
fieldCache map[uint64]map[string]float64
|
|
||||||
nameCache map[uint64]string
|
|
||||||
tagCache map[uint64]map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMin() telegraf.Aggregator {
|
|
||||||
m := &Min{}
|
|
||||||
m.Reset()
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
var sampleConfig = `
|
|
||||||
## period is the flush & clear interval of the aggregator.
|
|
||||||
period = "30s"
|
|
||||||
## If true drop_original will drop the original metrics and
|
|
||||||
## only send aggregates.
|
|
||||||
drop_original = false
|
|
||||||
`
|
|
||||||
|
|
||||||
func (m *Min) SampleConfig() string {
|
|
||||||
return sampleConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Min) Description() string {
|
|
||||||
return "Keep the aggregate min of each metric passing through."
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Min) Add(in telegraf.Metric) {
|
|
||||||
id := in.HashID()
|
|
||||||
if _, ok := m.nameCache[id]; !ok {
|
|
||||||
// hit an uncached metric, create caches for first time:
|
|
||||||
m.nameCache[id] = in.Name()
|
|
||||||
m.tagCache[id] = in.Tags()
|
|
||||||
m.fieldCache[id] = make(map[string]float64)
|
|
||||||
for k, v := range in.Fields() {
|
|
||||||
if fv, ok := convert(v); ok {
|
|
||||||
m.fieldCache[id][k] = fv
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for k, v := range in.Fields() {
|
|
||||||
if fv, ok := convert(v); ok {
|
|
||||||
if _, ok := m.fieldCache[id][k]; !ok {
|
|
||||||
// hit an uncached field of a cached metric
|
|
||||||
m.fieldCache[id][k] = fv
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if fv < m.fieldCache[id][k] {
|
|
||||||
// set new minimum
|
|
||||||
m.fieldCache[id][k] = fv
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Min) Push(acc telegraf.Accumulator) {
|
|
||||||
for id, _ := range m.nameCache {
|
|
||||||
fields := map[string]interface{}{}
|
|
||||||
for k, v := range m.fieldCache[id] {
|
|
||||||
fields[k+"_min"] = v
|
|
||||||
}
|
|
||||||
acc.AddFields(m.nameCache[id], fields, m.tagCache[id])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Min) Reset() {
|
|
||||||
m.fieldCache = make(map[uint64]map[string]float64)
|
|
||||||
m.nameCache = make(map[uint64]string)
|
|
||||||
m.tagCache = make(map[uint64]map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
func convert(in interface{}) (float64, bool) {
|
|
||||||
switch v := in.(type) {
|
|
||||||
case float64:
|
|
||||||
return v, true
|
|
||||||
case int64:
|
|
||||||
return float64(v), true
|
|
||||||
default:
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
aggregators.Add("min", func() telegraf.Aggregator {
|
|
||||||
return NewMin()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Unit Tests
|
## Unit Tests
|
||||||
|
|
||||||
### Execute short tests
|
### Execute short tests
|
||||||
|
|||||||
12
Godeps
12
Godeps
@@ -19,19 +19,19 @@ github.com/eclipse/paho.mqtt.golang 0f7a459f04f13a41b7ed752d47944528d4bf9a86
|
|||||||
github.com/go-sql-driver/mysql 1fca743146605a172a266e1654e01e5cd5669bee
|
github.com/go-sql-driver/mysql 1fca743146605a172a266e1654e01e5cd5669bee
|
||||||
github.com/gobwas/glob 49571a1557cd20e6a2410adc6421f85b66c730b5
|
github.com/gobwas/glob 49571a1557cd20e6a2410adc6421f85b66c730b5
|
||||||
github.com/golang/protobuf 552c7b9542c194800fd493123b3798ef0a832032
|
github.com/golang/protobuf 552c7b9542c194800fd493123b3798ef0a832032
|
||||||
github.com/golang/snappy d9eb7a3d35ec988b8585d4a0068e462c27d28380
|
github.com/golang/snappy 427fb6fc07997f43afa32f35e850833760e489a7
|
||||||
|
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/hashicorp/consul 5aa90455ce78d4d41578bafc86305e6e6b28d7d2
|
||||||
github.com/hpcloud/tail b2940955ab8b26e19d43a43c4da0475dd81bdb56
|
github.com/hpcloud/tail b2940955ab8b26e19d43a43c4da0475dd81bdb56
|
||||||
github.com/influxdata/config b79f6829346b8d6e78ba73544b1e1038f1f1c9da
|
github.com/influxdata/config b79f6829346b8d6e78ba73544b1e1038f1f1c9da
|
||||||
github.com/influxdata/influxdb fc57c0f7c635df3873f3d64f0ed2100ddc94d5ae
|
github.com/influxdata/influxdb e094138084855d444195b252314dfee9eae34cab
|
||||||
github.com/influxdata/toml af4df43894b16e3fd2b788d01bd27ad0776ef2d0
|
github.com/influxdata/toml af4df43894b16e3fd2b788d01bd27ad0776ef2d0
|
||||||
github.com/influxdata/wlog 7c63b0a71ef8300adc255344d275e10e5c3a71ec
|
github.com/influxdata/wlog 7c63b0a71ef8300adc255344d275e10e5c3a71ec
|
||||||
github.com/kardianos/osext 29ae4ffbc9a6fe9fb2bc5029050ce6996ea1d3bc
|
github.com/kardianos/osext 29ae4ffbc9a6fe9fb2bc5029050ce6996ea1d3bc
|
||||||
github.com/kardianos/service 5e335590050d6d00f3aa270217d288dda1c94d0a
|
github.com/kardianos/service 5e335590050d6d00f3aa270217d288dda1c94d0a
|
||||||
github.com/kballard/go-shellquote d8ec1a69a250a17bb0e419c386eac1f3711dc142
|
|
||||||
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
|
||||||
@@ -47,8 +47,8 @@ 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 1516eb9ddc5e61ba58874047a98f8b44b5e585e8
|
github.com/shirou/gopsutil 4d0c402af66c78735c5ccf820dc2ca7de5e4ff08
|
||||||
github.com/soniah/gosnmp 3fe3beb30fa9700988893c56a63b1df8e1b68c26
|
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/vjeantet/grok 83bfdfdfd1a8146795b28e547a8e3c8b28a466c2
|
||||||
@@ -56,7 +56,7 @@ 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/yuin/gopher-lua bf3808abd44b1e55143a2d7f08571aaa80db1808
|
||||||
github.com/zensqlmonitor/go-mssqldb ffe5510c6fa5e15e6d983210ab501c815b56b363
|
github.com/zensqlmonitor/go-mssqldb ffe5510c6fa5e15e6d983210ab501c815b56b363
|
||||||
golang.org/x/crypto c197bcf24cde29d3f73c7b4ac6fd41f4384e8af6
|
golang.org/x/crypto 5dc8cb4b8a8eb076cbb5a06bc3b8682c15bdbbd3
|
||||||
golang.org/x/net 6acef71eb69611914f7a30939ea9f6e194c78172
|
golang.org/x/net 6acef71eb69611914f7a30939ea9f6e194c78172
|
||||||
golang.org/x/text a71fd10341b064c10f4a81ceac72bcf70f26ea34
|
golang.org/x/text a71fd10341b064c10f4a81ceac72bcf70f26ea34
|
||||||
gopkg.in/dancannon/gorethink.v1 7d1af5be49cb5ecc7b177bf387d232050299d6ef
|
gopkg.in/dancannon/gorethink.v1 7d1af5be49cb5ecc7b177bf387d232050299d6ef
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -1,6 +1,6 @@
|
|||||||
VERSION := $(shell sh -c 'git describe --always --tags')
|
VERSION := $(shell sh -c 'git describe --always --tags')
|
||||||
BRANCH := $(shell sh -c 'git rev-parse --abbrev-ref HEAD')
|
BRANCH := $(shell sh -c 'git rev-parse --abbrev-ref HEAD')
|
||||||
COMMIT := $(shell sh -c 'git rev-parse --short HEAD')
|
COMMIT := $(shell sh -c 'git rev-parse HEAD')
|
||||||
ifdef GOBIN
|
ifdef GOBIN
|
||||||
PATH := $(GOBIN):$(PATH)
|
PATH := $(GOBIN):$(PATH)
|
||||||
else
|
else
|
||||||
|
|||||||
301
README.md
301
README.md
@@ -1,23 +1,15 @@
|
|||||||
# Telegraf [](https://circleci.com/gh/influxdata/telegraf) [](https://hub.docker.com/_/telegraf/)
|
# Telegraf [](https://circleci.com/gh/influxdata/telegraf) [](https://hub.docker.com/_/telegraf/)
|
||||||
|
|
||||||
Telegraf is an agent written in Go for collecting, processing, aggregating,
|
Telegraf is an agent written in Go for collecting metrics from the system it's
|
||||||
and writing metrics.
|
running on, or from other services, and writing them into InfluxDB or other
|
||||||
|
[outputs](https://github.com/influxdata/telegraf#supported-output-plugins).
|
||||||
|
|
||||||
Design goals are to have a minimal memory footprint with a plugin system so
|
Design goals are to have a minimal memory footprint with a plugin system so
|
||||||
that developers in the community can easily add support for collecting metrics
|
that developers in the community can easily add support for collecting metrics
|
||||||
from well known services (like Hadoop, Postgres, or Redis) and third party
|
from well known services (like Hadoop, Postgres, or Redis) and third party
|
||||||
APIs (like Mailchimp, AWS CloudWatch, or Google Analytics).
|
APIs (like Mailchimp, AWS CloudWatch, or Google Analytics).
|
||||||
|
|
||||||
Telegraf is plugin-driven and has the concept of 4 distinct plugins:
|
New input and output plugins are designed to be easy to contribute,
|
||||||
|
|
||||||
1. [Input Plugins](#input-plugins) collect metrics from the system, services, or 3rd party APIs
|
|
||||||
2. [Processor Plugins](#processor-plugins) transform, decorate, and/or filter metrics
|
|
||||||
3. [Aggregator Plugins](#aggregator-plugins) create aggregate metrics (e.g. mean, min, max, quantiles, etc.)
|
|
||||||
4. [Output Plugins](#output-plugins) write metrics to various destinations
|
|
||||||
|
|
||||||
For more information on Processor and Aggregator plugins please [read this](./docs/AGGREGATORS_AND_PROCESSORS.md).
|
|
||||||
|
|
||||||
New plugins are designed to be easy to contribute,
|
|
||||||
we'll eagerly accept pull
|
we'll eagerly accept pull
|
||||||
requests and will manage the set of plugins that Telegraf supports.
|
requests and will manage the set of plugins that Telegraf supports.
|
||||||
See the [contributing guide](CONTRIBUTING.md) for instructions on writing
|
See the [contributing guide](CONTRIBUTING.md) for instructions on writing
|
||||||
@@ -28,12 +20,12 @@ new plugins.
|
|||||||
### Linux deb and rpm Packages:
|
### Linux deb and rpm Packages:
|
||||||
|
|
||||||
Latest:
|
Latest:
|
||||||
* https://dl.influxdata.com/telegraf/releases/telegraf_1.1.1_amd64.deb
|
* https://dl.influxdata.com/telegraf/releases/telegraf_1.0.0_amd64.deb
|
||||||
* https://dl.influxdata.com/telegraf/releases/telegraf-1.1.1.x86_64.rpm
|
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0.x86_64.rpm
|
||||||
|
|
||||||
Latest (arm):
|
Latest (arm):
|
||||||
* https://dl.influxdata.com/telegraf/releases/telegraf_1.1.1_armhf.deb
|
* https://dl.influxdata.com/telegraf/releases/telegraf_1.0.0_armhf.deb
|
||||||
* https://dl.influxdata.com/telegraf/releases/telegraf-1.1.1.armhf.rpm
|
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0.armhf.rpm
|
||||||
|
|
||||||
##### Package Instructions:
|
##### Package Instructions:
|
||||||
|
|
||||||
@@ -47,21 +39,21 @@ controlled via `systemctl [action] telegraf`
|
|||||||
### yum/apt Repositories:
|
### yum/apt Repositories:
|
||||||
|
|
||||||
There is a yum/apt repo available for the whole InfluxData stack, see
|
There is a yum/apt repo available for the whole InfluxData stack, see
|
||||||
[here](https://docs.influxdata.com/influxdb/latest/introduction/installation/#installation)
|
[here](https://docs.influxdata.com/influxdb/v0.10/introduction/installation/#installation)
|
||||||
for instructions on setting up the repo. Once it is configured, you will be able
|
for instructions on setting up the repo. Once it is configured, you will be able
|
||||||
to use this repo to install & update telegraf.
|
to use this repo to install & update telegraf.
|
||||||
|
|
||||||
### Linux tarballs:
|
### Linux tarballs:
|
||||||
|
|
||||||
Latest:
|
Latest:
|
||||||
* https://dl.influxdata.com/telegraf/releases/telegraf-1.1.1_linux_amd64.tar.gz
|
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0_linux_amd64.tar.gz
|
||||||
* https://dl.influxdata.com/telegraf/releases/telegraf-1.1.1_linux_i386.tar.gz
|
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0_linux_i386.tar.gz
|
||||||
* https://dl.influxdata.com/telegraf/releases/telegraf-1.1.1_linux_armhf.tar.gz
|
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0_linux_armhf.tar.gz
|
||||||
|
|
||||||
### FreeBSD tarball:
|
### FreeBSD tarball:
|
||||||
|
|
||||||
Latest:
|
Latest:
|
||||||
* https://dl.influxdata.com/telegraf/releases/telegraf-1.1.1_freebsd_amd64.tar.gz
|
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0_freebsd_amd64.tar.gz
|
||||||
|
|
||||||
### Ansible Role:
|
### Ansible Role:
|
||||||
|
|
||||||
@@ -77,7 +69,7 @@ brew install telegraf
|
|||||||
### Windows Binaries (EXPERIMENTAL)
|
### Windows Binaries (EXPERIMENTAL)
|
||||||
|
|
||||||
Latest:
|
Latest:
|
||||||
* https://dl.influxdata.com/telegraf/releases/telegraf-1.1.1_windows_amd64.zip
|
* https://dl.influxdata.com/telegraf/releases/telegraf-1.0.0_windows_amd64.zip
|
||||||
|
|
||||||
### From Source:
|
### From Source:
|
||||||
|
|
||||||
@@ -93,114 +85,121 @@ if you don't have it already. You also must build with golang version 1.5+.
|
|||||||
|
|
||||||
## How to use it:
|
## How to use it:
|
||||||
|
|
||||||
See usage with:
|
```console
|
||||||
|
$ telegraf -help
|
||||||
|
Telegraf, The plugin-driven server agent for collecting and reporting metrics.
|
||||||
|
|
||||||
```
|
Usage:
|
||||||
telegraf --help
|
|
||||||
```
|
|
||||||
|
|
||||||
### Generate a telegraf config file:
|
telegraf <flags>
|
||||||
|
|
||||||
```
|
The flags are:
|
||||||
telegraf config > telegraf.conf
|
|
||||||
```
|
|
||||||
|
|
||||||
### Generate config with only cpu input & influxdb output plugins defined
|
-config <file> configuration file to load
|
||||||
|
-test gather metrics once, print them to stdout, and exit
|
||||||
|
-sample-config print out full sample configuration to stdout
|
||||||
|
-config-directory directory containing additional *.conf files
|
||||||
|
-input-filter filter the input plugins to enable, separator is :
|
||||||
|
-output-filter filter the output plugins to enable, separator is :
|
||||||
|
-usage print usage for a plugin, ie, 'telegraf -usage mysql'
|
||||||
|
-debug print metrics as they're generated to stdout
|
||||||
|
-quiet run in quiet mode
|
||||||
|
-version print the version to stdout
|
||||||
|
|
||||||
```
|
Examples:
|
||||||
telegraf --input-filter cpu --output-filter influxdb config
|
|
||||||
```
|
|
||||||
|
|
||||||
### Run a single telegraf collection, outputing metrics to stdout
|
# generate a telegraf config file:
|
||||||
|
telegraf -sample-config > telegraf.conf
|
||||||
|
|
||||||
```
|
# generate config with only cpu input & influxdb output plugins defined
|
||||||
telegraf --config telegraf.conf -test
|
telegraf -sample-config -input-filter cpu -output-filter influxdb
|
||||||
```
|
|
||||||
|
|
||||||
### Run telegraf with all plugins defined in config file
|
# run a single telegraf collection, outputing metrics to stdout
|
||||||
|
telegraf -config telegraf.conf -test
|
||||||
|
|
||||||
```
|
# run telegraf with all plugins defined in config file
|
||||||
telegraf --config telegraf.conf
|
telegraf -config telegraf.conf
|
||||||
```
|
|
||||||
|
|
||||||
### Run telegraf, enabling the cpu & memory input, and influxdb output plugins
|
|
||||||
|
|
||||||
|
# run telegraf, enabling the cpu & memory input, and influxdb output plugins
|
||||||
|
telegraf -config telegraf.conf -input-filter cpu:mem -output-filter influxdb
|
||||||
```
|
```
|
||||||
telegraf --config telegraf.conf -input-filter cpu:mem -output-filter influxdb
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
See the [configuration guide](docs/CONFIGURATION.md) for a rundown of the more advanced
|
See the [configuration guide](docs/CONFIGURATION.md) for a rundown of the more advanced
|
||||||
configuration options.
|
configuration options.
|
||||||
|
|
||||||
## Input Plugins
|
## Supported Input Plugins
|
||||||
|
|
||||||
* [aws cloudwatch](./plugins/inputs/cloudwatch)
|
Telegraf currently has support for collecting metrics from many sources. For
|
||||||
* [aerospike](./plugins/inputs/aerospike)
|
more information on each, please look at the directory of the same name in
|
||||||
* [apache](./plugins/inputs/apache)
|
`plugins/inputs`.
|
||||||
* [bcache](./plugins/inputs/bcache)
|
|
||||||
* [cassandra](./plugins/inputs/cassandra)
|
Currently implemented sources:
|
||||||
* [ceph](./plugins/inputs/ceph)
|
|
||||||
* [chrony](./plugins/inputs/chrony)
|
* [aws cloudwatch](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/cloudwatch)
|
||||||
* [consul](./plugins/inputs/consul)
|
* [aerospike](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/aerospike)
|
||||||
* [conntrack](./plugins/inputs/conntrack)
|
* [apache](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/apache)
|
||||||
* [couchbase](./plugins/inputs/couchbase)
|
* [bcache](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/bcache)
|
||||||
* [couchdb](./plugins/inputs/couchdb)
|
* [cassandra](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/cassandra)
|
||||||
* [disque](./plugins/inputs/disque)
|
* [ceph](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/ceph)
|
||||||
* [dns query time](./plugins/inputs/dns_query)
|
* [chrony](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/chrony)
|
||||||
* [docker](./plugins/inputs/docker)
|
* [consul](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/consul)
|
||||||
* [dovecot](./plugins/inputs/dovecot)
|
* [conntrack](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/conntrack)
|
||||||
* [elasticsearch](./plugins/inputs/elasticsearch)
|
* [couchbase](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/couchbase)
|
||||||
* [exec](./plugins/inputs/exec) (generic executable plugin, support JSON, influx, graphite and nagios)
|
* [couchdb](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/couchdb)
|
||||||
* [filestat](./plugins/inputs/filestat)
|
* [disque](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/disque)
|
||||||
* [haproxy](./plugins/inputs/haproxy)
|
* [dns query time](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/dns_query)
|
||||||
* [hddtemp](./plugins/inputs/hddtemp)
|
* [docker](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/docker)
|
||||||
* [http_response](./plugins/inputs/http_response)
|
* [dovecot](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/dovecot)
|
||||||
* [httpjson](./plugins/inputs/httpjson) (generic JSON-emitting http service plugin)
|
* [elasticsearch](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/elasticsearch)
|
||||||
* [internal](./plugins/inputs/internal)
|
* [exec](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/exec) (generic executable plugin, support JSON, influx, graphite and nagios)
|
||||||
* [influxdb](./plugins/inputs/influxdb)
|
* [filestat](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/filestat)
|
||||||
* [ipmi_sensor](./plugins/inputs/ipmi_sensor)
|
* [haproxy](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/haproxy)
|
||||||
* [iptables](./plugins/inputs/iptables)
|
* [hddtemp](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/hddtemp)
|
||||||
* [jolokia](./plugins/inputs/jolokia)
|
* [http_response](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/http_response)
|
||||||
* [leofs](./plugins/inputs/leofs)
|
* [httpjson](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/httpjson) (generic JSON-emitting http service plugin)
|
||||||
* [lustre2](./plugins/inputs/lustre2)
|
* [influxdb](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/influxdb)
|
||||||
* [mailchimp](./plugins/inputs/mailchimp)
|
* [ipmi_sensor](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/ipmi_sensor)
|
||||||
* [memcached](./plugins/inputs/memcached)
|
* [iptables](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/iptables)
|
||||||
* [mesos](./plugins/inputs/mesos)
|
* [jolokia](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/jolokia)
|
||||||
* [mongodb](./plugins/inputs/mongodb)
|
* [leofs](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/leofs)
|
||||||
* [mysql](./plugins/inputs/mysql)
|
* [lustre2](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/lustre2)
|
||||||
* [net_response](./plugins/inputs/net_response)
|
* [mailchimp](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/mailchimp)
|
||||||
* [nginx](./plugins/inputs/nginx)
|
* [memcached](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/memcached)
|
||||||
* [nsq](./plugins/inputs/nsq)
|
* [mesos](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/mesos)
|
||||||
* [nstat](./plugins/inputs/nstat)
|
* [mongodb](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/mongodb)
|
||||||
* [ntpq](./plugins/inputs/ntpq)
|
* [mysql](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/mysql)
|
||||||
* [phpfpm](./plugins/inputs/phpfpm)
|
* [net_response](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/net_response)
|
||||||
* [phusion passenger](./plugins/inputs/passenger)
|
* [nginx](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/nginx)
|
||||||
* [ping](./plugins/inputs/ping)
|
* [nsq](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/nsq)
|
||||||
* [postgresql](./plugins/inputs/postgresql)
|
* [nstat](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/nstat)
|
||||||
* [postgresql_extensible](./plugins/inputs/postgresql_extensible)
|
* [ntpq](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/ntpq)
|
||||||
* [powerdns](./plugins/inputs/powerdns)
|
* [phpfpm](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/phpfpm)
|
||||||
* [procstat](./plugins/inputs/procstat)
|
* [phusion passenger](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/passenger)
|
||||||
* [prometheus](./plugins/inputs/prometheus)
|
* [ping](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/ping)
|
||||||
* [puppetagent](./plugins/inputs/puppetagent)
|
* [postgresql](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/postgresql)
|
||||||
* [rabbitmq](./plugins/inputs/rabbitmq)
|
* [postgresql_extensible](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/postgresql_extensible)
|
||||||
* [raindrops](./plugins/inputs/raindrops)
|
* [powerdns](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/powerdns)
|
||||||
* [redis](./plugins/inputs/redis)
|
* [procstat](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/procstat)
|
||||||
* [rethinkdb](./plugins/inputs/rethinkdb)
|
* [prometheus](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/prometheus)
|
||||||
* [riak](./plugins/inputs/riak)
|
* [puppetagent](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/puppetagent)
|
||||||
* [sensors](./plugins/inputs/sensors)
|
* [rabbitmq](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/rabbitmq)
|
||||||
* [snmp](./plugins/inputs/snmp)
|
* [raindrops](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/raindrops)
|
||||||
* [snmp_legacy](./plugins/inputs/snmp_legacy)
|
* [redis](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/redis)
|
||||||
* [sql server](./plugins/inputs/sqlserver) (microsoft)
|
* [rethinkdb](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/rethinkdb)
|
||||||
* [twemproxy](./plugins/inputs/twemproxy)
|
* [riak](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/riak)
|
||||||
* [varnish](./plugins/inputs/varnish)
|
* [sensors](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/sensors)
|
||||||
* [zfs](./plugins/inputs/zfs)
|
* [snmp](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/snmp)
|
||||||
* [zookeeper](./plugins/inputs/zookeeper)
|
* [snmp_legacy](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/snmp_legacy)
|
||||||
* [win_perf_counters ](./plugins/inputs/win_perf_counters) (windows performance counters)
|
* [sql server](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/sqlserver) (microsoft)
|
||||||
* [sysstat](./plugins/inputs/sysstat)
|
* [twemproxy](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/twemproxy)
|
||||||
* [system](./plugins/inputs/system)
|
* [varnish](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/varnish)
|
||||||
|
* [zfs](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/zfs)
|
||||||
|
* [zookeeper](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/zookeeper)
|
||||||
|
* [win_perf_counters ](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/win_perf_counters) (windows performance counters)
|
||||||
|
* [sysstat](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/sysstat)
|
||||||
|
* [system](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/system)
|
||||||
* cpu
|
* cpu
|
||||||
* mem
|
* mem
|
||||||
* net
|
* net
|
||||||
@@ -214,51 +213,45 @@ configuration options.
|
|||||||
|
|
||||||
Telegraf can also collect metrics via the following service plugins:
|
Telegraf can also collect metrics via the following service plugins:
|
||||||
|
|
||||||
* [http_listener](./plugins/inputs/http_listener)
|
* [http_listener](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/http_listener)
|
||||||
* [kafka_consumer](./plugins/inputs/kafka_consumer)
|
* [kafka_consumer](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/kafka_consumer)
|
||||||
* [mqtt_consumer](./plugins/inputs/mqtt_consumer)
|
* [mqtt_consumer](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/mqtt_consumer)
|
||||||
* [nats_consumer](./plugins/inputs/nats_consumer)
|
* [nats_consumer](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/nats_consumer)
|
||||||
* [nsq_consumer](./plugins/inputs/nsq_consumer)
|
* [nsq_consumer](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/nsq_consumer)
|
||||||
* [logparser](./plugins/inputs/logparser)
|
* [logparser](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/logparser)
|
||||||
* [statsd](./plugins/inputs/statsd)
|
* [statsd](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/statsd)
|
||||||
* [tail](./plugins/inputs/tail)
|
* [tail](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/tail)
|
||||||
* [tcp_listener](./plugins/inputs/tcp_listener)
|
* [tcp_listener](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/tcp_listener)
|
||||||
* [udp_listener](./plugins/inputs/udp_listener)
|
* [udp_listener](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/udp_listener)
|
||||||
* [webhooks](./plugins/inputs/webhooks)
|
* [webhooks](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/webhooks)
|
||||||
* [filestack](./plugins/inputs/webhooks/filestack)
|
* [filestack](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/webhooks/filestack)
|
||||||
* [github](./plugins/inputs/webhooks/github)
|
* [github](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/webhooks/github)
|
||||||
* [mandrill](./plugins/inputs/webhooks/mandrill)
|
* [mandrill](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/webhooks/mandrill)
|
||||||
* [rollbar](./plugins/inputs/webhooks/rollbar)
|
* [rollbar](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/webhooks/rollbar)
|
||||||
|
|
||||||
## Processor Plugins
|
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.
|
||||||
|
|
||||||
* [printer](./plugins/processors/printer)
|
## Supported Output Plugins
|
||||||
|
|
||||||
## Aggregator Plugins
|
* [influxdb](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/influxdb)
|
||||||
|
* [amon](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/amon)
|
||||||
* [minmax](./plugins/aggregators/minmax)
|
* [amqp](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/amqp)
|
||||||
|
* [aws kinesis](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/kinesis)
|
||||||
## Output Plugins
|
* [aws cloudwatch](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/cloudwatch)
|
||||||
|
* [datadog](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/datadog)
|
||||||
* [influxdb](./plugins/outputs/influxdb)
|
* [file](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/file)
|
||||||
* [amon](./plugins/outputs/amon)
|
* [graphite](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/graphite)
|
||||||
* [amqp](./plugins/outputs/amqp)
|
* [graylog](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/graylog)
|
||||||
* [aws kinesis](./plugins/outputs/kinesis)
|
* [instrumental](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/instrumental)
|
||||||
* [aws cloudwatch](./plugins/outputs/cloudwatch)
|
* [kafka](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/kafka)
|
||||||
* [datadog](./plugins/outputs/datadog)
|
* [librato](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/librato)
|
||||||
* [discard](./plugins/outputs/discard)
|
* [mqtt](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/mqtt)
|
||||||
* [file](./plugins/outputs/file)
|
* [nats](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/nats)
|
||||||
* [graphite](./plugins/outputs/graphite)
|
* [nsq](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/nsq)
|
||||||
* [graylog](./plugins/outputs/graylog)
|
* [opentsdb](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/opentsdb)
|
||||||
* [instrumental](./plugins/outputs/instrumental)
|
* [prometheus](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/prometheus_client)
|
||||||
* [kafka](./plugins/outputs/kafka)
|
* [riemann](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/riemann)
|
||||||
* [librato](./plugins/outputs/librato)
|
|
||||||
* [mqtt](./plugins/outputs/mqtt)
|
|
||||||
* [nats](./plugins/outputs/nats)
|
|
||||||
* [nsq](./plugins/outputs/nsq)
|
|
||||||
* [opentsdb](./plugins/outputs/opentsdb)
|
|
||||||
* [prometheus](./plugins/outputs/prometheus_client)
|
|
||||||
* [riemann](./plugins/outputs/riemann)
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ package telegraf
|
|||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// Accumulator is an interface for "accumulating" metrics from plugin(s).
|
// Accumulator is an interface for "accumulating" metrics from input plugin(s).
|
||||||
// The metrics are sent down a channel shared between all plugins.
|
// 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
|
// AddFields adds a metric to the accumulator with the given measurement
|
||||||
// name, fields, and tags (and timestamp). If a timestamp is not provided,
|
// name, fields, and tags (and timestamp). If a timestamp is not provided,
|
||||||
@@ -28,7 +29,12 @@ type Accumulator interface {
|
|||||||
tags map[string]string,
|
tags map[string]string,
|
||||||
t ...time.Time)
|
t ...time.Time)
|
||||||
|
|
||||||
|
AddError(err error)
|
||||||
|
|
||||||
|
Debug() bool
|
||||||
|
SetDebug(enabled bool)
|
||||||
|
|
||||||
SetPrecision(precision, interval time.Duration)
|
SetPrecision(precision, interval time.Duration)
|
||||||
|
|
||||||
AddError(err error)
|
DisablePrecision()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +1,41 @@
|
|||||||
package agent
|
package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"math"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
"github.com/influxdata/telegraf/selfstat"
|
"github.com/influxdata/telegraf/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
NErrors = selfstat.Register("agent", "gather_errors", map[string]string{})
|
|
||||||
)
|
|
||||||
|
|
||||||
type MetricMaker interface {
|
|
||||||
Name() string
|
|
||||||
MakeMetric(
|
|
||||||
measurement string,
|
|
||||||
fields map[string]interface{},
|
|
||||||
tags map[string]string,
|
|
||||||
mType telegraf.ValueType,
|
|
||||||
t time.Time,
|
|
||||||
) telegraf.Metric
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAccumulator(
|
func NewAccumulator(
|
||||||
maker MetricMaker,
|
inputConfig *models.InputConfig,
|
||||||
metrics chan telegraf.Metric,
|
metrics chan telegraf.Metric,
|
||||||
) *accumulator {
|
) *accumulator {
|
||||||
acc := accumulator{
|
acc := accumulator{}
|
||||||
maker: maker,
|
acc.metrics = metrics
|
||||||
metrics: metrics,
|
acc.inputConfig = inputConfig
|
||||||
precision: time.Nanosecond,
|
acc.precision = time.Nanosecond
|
||||||
}
|
|
||||||
return &acc
|
return &acc
|
||||||
}
|
}
|
||||||
|
|
||||||
type accumulator struct {
|
type accumulator struct {
|
||||||
metrics chan telegraf.Metric
|
metrics chan telegraf.Metric
|
||||||
|
|
||||||
maker MetricMaker
|
defaultTags map[string]string
|
||||||
|
|
||||||
|
debug bool
|
||||||
|
// print every point added to the accumulator
|
||||||
|
trace bool
|
||||||
|
|
||||||
|
inputConfig *models.InputConfig
|
||||||
|
|
||||||
precision time.Duration
|
precision time.Duration
|
||||||
|
|
||||||
|
errCount uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ac *accumulator) AddFields(
|
func (ac *accumulator) AddFields(
|
||||||
@@ -49,7 +44,7 @@ func (ac *accumulator) AddFields(
|
|||||||
tags map[string]string,
|
tags map[string]string,
|
||||||
t ...time.Time,
|
t ...time.Time,
|
||||||
) {
|
) {
|
||||||
if m := ac.maker.MakeMetric(measurement, fields, tags, telegraf.Untyped, ac.getTime(t)); m != nil {
|
if m := ac.makeMetric(measurement, fields, tags, telegraf.Untyped, t...); m != nil {
|
||||||
ac.metrics <- m
|
ac.metrics <- m
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,7 +55,7 @@ func (ac *accumulator) AddGauge(
|
|||||||
tags map[string]string,
|
tags map[string]string,
|
||||||
t ...time.Time,
|
t ...time.Time,
|
||||||
) {
|
) {
|
||||||
if m := ac.maker.MakeMetric(measurement, fields, tags, telegraf.Gauge, ac.getTime(t)); m != nil {
|
if m := ac.makeMetric(measurement, fields, tags, telegraf.Gauge, t...); m != nil {
|
||||||
ac.metrics <- m
|
ac.metrics <- m
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,20 +66,139 @@ func (ac *accumulator) AddCounter(
|
|||||||
tags map[string]string,
|
tags map[string]string,
|
||||||
t ...time.Time,
|
t ...time.Time,
|
||||||
) {
|
) {
|
||||||
if m := ac.maker.MakeMetric(measurement, fields, tags, telegraf.Counter, ac.getTime(t)); m != nil {
|
if m := ac.makeMetric(measurement, fields, tags, telegraf.Counter, t...); m != nil {
|
||||||
ac.metrics <- m
|
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 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if tags == nil {
|
||||||
|
tags = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override measurement name if set
|
||||||
|
if len(ac.inputConfig.NameOverride) != 0 {
|
||||||
|
measurement = ac.inputConfig.NameOverride
|
||||||
|
}
|
||||||
|
// Apply measurement prefix and suffix if set
|
||||||
|
if len(ac.inputConfig.MeasurementPrefix) != 0 {
|
||||||
|
measurement = ac.inputConfig.MeasurementPrefix + measurement
|
||||||
|
}
|
||||||
|
if len(ac.inputConfig.MeasurementSuffix) != 0 {
|
||||||
|
measurement = measurement + ac.inputConfig.MeasurementSuffix
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply plugin-wide tags if set
|
||||||
|
for k, v := range ac.inputConfig.Tags {
|
||||||
|
if _, ok := tags[k]; !ok {
|
||||||
|
tags[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Apply daemon-wide tags if set
|
||||||
|
for k, v := range ac.defaultTags {
|
||||||
|
if _, ok := tags[k]; !ok {
|
||||||
|
tags[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the metric filter(s)
|
||||||
|
if ok := ac.inputConfig.Filter.Apply(measurement, fields, tags); !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range fields {
|
||||||
|
// Validate uint64 and float64 fields
|
||||||
|
switch val := v.(type) {
|
||||||
|
case uint64:
|
||||||
|
// InfluxDB does not support writing uint64
|
||||||
|
if val < uint64(9223372036854775808) {
|
||||||
|
fields[k] = int64(val)
|
||||||
|
} else {
|
||||||
|
fields[k] = int64(9223372036854775807)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
case float64:
|
||||||
|
// NaNs are invalid values in influxdb, skip measurement
|
||||||
|
if math.IsNaN(val) || math.IsInf(val, 0) {
|
||||||
|
if ac.debug {
|
||||||
|
log.Printf("I! Measurement [%s] field [%s] has a NaN or Inf "+
|
||||||
|
"field, skipping",
|
||||||
|
measurement, k)
|
||||||
|
}
|
||||||
|
delete(fields, k)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fields[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
var timestamp time.Time
|
||||||
|
if len(t) > 0 {
|
||||||
|
timestamp = t[0]
|
||||||
|
} else {
|
||||||
|
timestamp = time.Now()
|
||||||
|
}
|
||||||
|
timestamp = timestamp.Round(ac.precision)
|
||||||
|
|
||||||
|
var m telegraf.Metric
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("E! Error adding point [%s]: %s\n", measurement, err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if ac.trace {
|
||||||
|
fmt.Println("> " + m.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
// AddError passes a runtime error to the accumulator.
|
// AddError passes a runtime error to the accumulator.
|
||||||
// The error will be tagged with the plugin name and written to the log.
|
// The error will be tagged with the plugin name and written to the log.
|
||||||
func (ac *accumulator) AddError(err error) {
|
func (ac *accumulator) AddError(err error) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
NErrors.Incr(1)
|
atomic.AddUint64(&ac.errCount, 1)
|
||||||
//TODO suppress/throttle consecutive duplicate errors?
|
//TODO suppress/throttle consecutive duplicate errors?
|
||||||
log.Printf("E! Error in plugin [%s]: %s", ac.maker.Name(), err)
|
log.Printf("E! Error in input [%s]: %s", ac.inputConfig.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *accumulator) Debug() bool {
|
||||||
|
return ac.debug
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *accumulator) SetDebug(debug bool) {
|
||||||
|
ac.debug = debug
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *accumulator) Trace() bool {
|
||||||
|
return ac.trace
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *accumulator) SetTrace(trace bool) {
|
||||||
|
ac.trace = trace
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPrecision takes two time.Duration objects. If the first is non-zero,
|
// SetPrecision takes two time.Duration objects. If the first is non-zero,
|
||||||
@@ -108,12 +222,17 @@ func (ac *accumulator) SetPrecision(precision, interval time.Duration) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ac accumulator) getTime(t []time.Time) time.Time {
|
func (ac *accumulator) DisablePrecision() {
|
||||||
var timestamp time.Time
|
ac.precision = time.Nanosecond
|
||||||
if len(t) > 0 {
|
}
|
||||||
timestamp = t[0]
|
|
||||||
} else {
|
func (ac *accumulator) setDefaultTags(tags map[string]string) {
|
||||||
timestamp = time.Now()
|
ac.defaultTags = tags
|
||||||
}
|
}
|
||||||
return timestamp.Round(ac.precision)
|
|
||||||
|
func (ac *accumulator) addDefaultTag(key, value string) {
|
||||||
|
if ac.defaultTags == nil {
|
||||||
|
ac.defaultTags = make(map[string]string)
|
||||||
|
}
|
||||||
|
ac.defaultTags[key] = value
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,106 +4,24 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
"github.com/influxdata/telegraf/metric"
|
"github.com/influxdata/telegraf/internal/models"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAdd(t *testing.T) {
|
func TestAdd(t *testing.T) {
|
||||||
|
a := accumulator{}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
metrics := make(chan telegraf.Metric, 10)
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
defer close(metrics)
|
defer close(a.metrics)
|
||||||
a := NewAccumulator(&TestMetricMaker{}, metrics)
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
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 := <-metrics
|
|
||||||
actual := testm.String()
|
|
||||||
assert.Contains(t, actual, "acctest value=101")
|
|
||||||
|
|
||||||
testm = <-metrics
|
|
||||||
actual = testm.String()
|
|
||||||
assert.Contains(t, actual, "acctest,acc=test value=101")
|
|
||||||
|
|
||||||
testm = <-metrics
|
|
||||||
actual = testm.String()
|
|
||||||
assert.Equal(t,
|
|
||||||
fmt.Sprintf("acctest,acc=test value=101 %d\n", now.UnixNano()),
|
|
||||||
actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddFields(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
metrics := make(chan telegraf.Metric, 10)
|
|
||||||
defer close(metrics)
|
|
||||||
a := NewAccumulator(&TestMetricMaker{}, metrics)
|
|
||||||
|
|
||||||
fields := map[string]interface{}{
|
|
||||||
"usage": float64(99),
|
|
||||||
}
|
|
||||||
a.AddFields("acctest", fields, map[string]string{})
|
|
||||||
a.AddGauge("acctest", fields, map[string]string{"acc": "test"})
|
|
||||||
a.AddCounter("acctest", fields, map[string]string{"acc": "test"}, now)
|
|
||||||
|
|
||||||
testm := <-metrics
|
|
||||||
actual := testm.String()
|
|
||||||
assert.Contains(t, actual, "acctest usage=99")
|
|
||||||
|
|
||||||
testm = <-metrics
|
|
||||||
actual = testm.String()
|
|
||||||
assert.Contains(t, actual, "acctest,acc=test usage=99")
|
|
||||||
|
|
||||||
testm = <-metrics
|
|
||||||
actual = testm.String()
|
|
||||||
assert.Equal(t,
|
|
||||||
fmt.Sprintf("acctest,acc=test usage=99 %d\n", now.UnixNano()),
|
|
||||||
actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAccAddError(t *testing.T) {
|
|
||||||
errBuf := bytes.NewBuffer(nil)
|
|
||||||
log.SetOutput(errBuf)
|
|
||||||
defer log.SetOutput(os.Stderr)
|
|
||||||
|
|
||||||
metrics := make(chan telegraf.Metric, 10)
|
|
||||||
defer close(metrics)
|
|
||||||
a := NewAccumulator(&TestMetricMaker{}, metrics)
|
|
||||||
|
|
||||||
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, int64(3), NErrors.Get())
|
|
||||||
require.Len(t, errs, 4) // 4 because of trailing newline
|
|
||||||
assert.Contains(t, string(errs[0]), "TestPlugin")
|
|
||||||
assert.Contains(t, string(errs[0]), "foo")
|
|
||||||
assert.Contains(t, string(errs[1]), "TestPlugin")
|
|
||||||
assert.Contains(t, string(errs[1]), "bar")
|
|
||||||
assert.Contains(t, string(errs[2]), "TestPlugin")
|
|
||||||
assert.Contains(t, string(errs[2]), "baz")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddNoIntervalWithPrecision(t *testing.T) {
|
|
||||||
now := time.Date(2006, time.February, 10, 12, 0, 0, 82912748, time.UTC)
|
|
||||||
metrics := make(chan telegraf.Metric, 10)
|
|
||||||
defer close(metrics)
|
|
||||||
a := NewAccumulator(&TestMetricMaker{}, metrics)
|
|
||||||
a.SetPrecision(0, time.Second)
|
|
||||||
|
|
||||||
a.AddFields("acctest",
|
a.AddFields("acctest",
|
||||||
map[string]interface{}{"value": float64(101)},
|
map[string]interface{}{"value": float64(101)},
|
||||||
@@ -126,17 +44,155 @@ func TestAddNoIntervalWithPrecision(t *testing.T) {
|
|||||||
testm = <-a.metrics
|
testm = <-a.metrics
|
||||||
actual = testm.String()
|
actual = testm.String()
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
fmt.Sprintf("acctest,acc=test value=101 %d\n", int64(1139572800000000000)),
|
fmt.Sprintf("acctest,acc=test value=101 %d", now.UnixNano()),
|
||||||
|
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)
|
actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddDisablePrecision(t *testing.T) {
|
func TestAddDisablePrecision(t *testing.T) {
|
||||||
|
a := accumulator{}
|
||||||
now := time.Date(2006, time.February, 10, 12, 0, 0, 82912748, time.UTC)
|
now := time.Date(2006, time.February, 10, 12, 0, 0, 82912748, time.UTC)
|
||||||
metrics := make(chan telegraf.Metric, 10)
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
defer close(metrics)
|
defer close(a.metrics)
|
||||||
a := NewAccumulator(&TestMetricMaker{}, metrics)
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
a.SetPrecision(time.Nanosecond, 0)
|
a.SetPrecision(time.Second, time.Millisecond)
|
||||||
|
a.DisablePrecision()
|
||||||
a.AddFields("acctest",
|
a.AddFields("acctest",
|
||||||
map[string]interface{}{"value": float64(101)},
|
map[string]interface{}{"value": float64(101)},
|
||||||
map[string]string{})
|
map[string]string{})
|
||||||
@@ -158,47 +214,16 @@ func TestAddDisablePrecision(t *testing.T) {
|
|||||||
testm = <-a.metrics
|
testm = <-a.metrics
|
||||||
actual = testm.String()
|
actual = testm.String()
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
fmt.Sprintf("acctest,acc=test value=101 %d\n", int64(1139572800082912748)),
|
fmt.Sprintf("acctest,acc=test value=101 %d", int64(1139572800082912748)),
|
||||||
actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddNoPrecisionWithInterval(t *testing.T) {
|
|
||||||
now := time.Date(2006, time.February, 10, 12, 0, 0, 82912748, time.UTC)
|
|
||||||
metrics := make(chan telegraf.Metric, 10)
|
|
||||||
defer close(metrics)
|
|
||||||
a := NewAccumulator(&TestMetricMaker{}, metrics)
|
|
||||||
|
|
||||||
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\n", int64(1139572800000000000)),
|
|
||||||
actual)
|
actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDifferentPrecisions(t *testing.T) {
|
func TestDifferentPrecisions(t *testing.T) {
|
||||||
|
a := accumulator{}
|
||||||
now := time.Date(2006, time.February, 10, 12, 0, 0, 82912748, time.UTC)
|
now := time.Date(2006, time.February, 10, 12, 0, 0, 82912748, time.UTC)
|
||||||
metrics := make(chan telegraf.Metric, 10)
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
defer close(metrics)
|
defer close(a.metrics)
|
||||||
a := NewAccumulator(&TestMetricMaker{}, metrics)
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
a.SetPrecision(0, time.Second)
|
a.SetPrecision(0, time.Second)
|
||||||
a.AddFields("acctest",
|
a.AddFields("acctest",
|
||||||
@@ -207,7 +232,7 @@ func TestDifferentPrecisions(t *testing.T) {
|
|||||||
testm := <-a.metrics
|
testm := <-a.metrics
|
||||||
actual := testm.String()
|
actual := testm.String()
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
fmt.Sprintf("acctest,acc=test value=101 %d\n", int64(1139572800000000000)),
|
fmt.Sprintf("acctest,acc=test value=101 %d", int64(1139572800000000000)),
|
||||||
actual)
|
actual)
|
||||||
|
|
||||||
a.SetPrecision(0, time.Millisecond)
|
a.SetPrecision(0, time.Millisecond)
|
||||||
@@ -217,7 +242,7 @@ func TestDifferentPrecisions(t *testing.T) {
|
|||||||
testm = <-a.metrics
|
testm = <-a.metrics
|
||||||
actual = testm.String()
|
actual = testm.String()
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
fmt.Sprintf("acctest,acc=test value=101 %d\n", int64(1139572800083000000)),
|
fmt.Sprintf("acctest,acc=test value=101 %d", int64(1139572800083000000)),
|
||||||
actual)
|
actual)
|
||||||
|
|
||||||
a.SetPrecision(0, time.Microsecond)
|
a.SetPrecision(0, time.Microsecond)
|
||||||
@@ -227,7 +252,7 @@ func TestDifferentPrecisions(t *testing.T) {
|
|||||||
testm = <-a.metrics
|
testm = <-a.metrics
|
||||||
actual = testm.String()
|
actual = testm.String()
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
fmt.Sprintf("acctest,acc=test value=101 %d\n", int64(1139572800082913000)),
|
fmt.Sprintf("acctest,acc=test value=101 %d", int64(1139572800082913000)),
|
||||||
actual)
|
actual)
|
||||||
|
|
||||||
a.SetPrecision(0, time.Nanosecond)
|
a.SetPrecision(0, time.Nanosecond)
|
||||||
@@ -237,104 +262,353 @@ func TestDifferentPrecisions(t *testing.T) {
|
|||||||
testm = <-a.metrics
|
testm = <-a.metrics
|
||||||
actual = testm.String()
|
actual = testm.String()
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
fmt.Sprintf("acctest,acc=test value=101 %d\n", int64(1139572800082912748)),
|
fmt.Sprintf("acctest,acc=test value=101 %d", int64(1139572800082912748)),
|
||||||
actual)
|
actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddGauge(t *testing.T) {
|
func TestAddDefaultTags(t *testing.T) {
|
||||||
|
a := accumulator{}
|
||||||
|
a.addDefaultTag("default", "tag")
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
metrics := make(chan telegraf.Metric, 10)
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
defer close(metrics)
|
defer close(a.metrics)
|
||||||
a := NewAccumulator(&TestMetricMaker{}, metrics)
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
a.AddGauge("acctest",
|
a.AddFields("acctest",
|
||||||
map[string]interface{}{"value": float64(101)},
|
map[string]interface{}{"value": float64(101)},
|
||||||
map[string]string{})
|
map[string]string{})
|
||||||
a.AddGauge("acctest",
|
a.AddFields("acctest",
|
||||||
map[string]interface{}{"value": float64(101)},
|
map[string]interface{}{"value": float64(101)},
|
||||||
map[string]string{"acc": "test"})
|
map[string]string{"acc": "test"})
|
||||||
a.AddGauge("acctest",
|
a.AddFields("acctest",
|
||||||
map[string]interface{}{"value": float64(101)},
|
map[string]interface{}{"value": float64(101)},
|
||||||
map[string]string{"acc": "test"}, now)
|
map[string]string{"acc": "test"}, now)
|
||||||
|
|
||||||
testm := <-metrics
|
testm := <-a.metrics
|
||||||
actual := testm.String()
|
actual := testm.String()
|
||||||
assert.Contains(t, actual, "acctest value=101")
|
assert.Contains(t, actual, "acctest,default=tag value=101")
|
||||||
assert.Equal(t, testm.Type(), telegraf.Gauge)
|
|
||||||
|
|
||||||
testm = <-metrics
|
testm = <-a.metrics
|
||||||
actual = testm.String()
|
actual = testm.String()
|
||||||
assert.Contains(t, actual, "acctest,acc=test value=101")
|
assert.Contains(t, actual, "acctest,acc=test,default=tag value=101")
|
||||||
assert.Equal(t, testm.Type(), telegraf.Gauge)
|
|
||||||
|
|
||||||
testm = <-metrics
|
testm = <-a.metrics
|
||||||
actual = testm.String()
|
actual = testm.String()
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
fmt.Sprintf("acctest,acc=test value=101 %d\n", now.UnixNano()),
|
fmt.Sprintf("acctest,acc=test,default=tag value=101 %d", now.UnixNano()),
|
||||||
actual)
|
actual)
|
||||||
assert.Equal(t, testm.Type(), telegraf.Gauge)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddCounter(t *testing.T) {
|
func TestAddFields(t *testing.T) {
|
||||||
|
a := accumulator{}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
metrics := make(chan telegraf.Metric, 10)
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
defer close(metrics)
|
defer close(a.metrics)
|
||||||
a := NewAccumulator(&TestMetricMaker{}, metrics)
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
a.AddCounter("acctest",
|
fields := map[string]interface{}{
|
||||||
map[string]interface{}{"value": float64(101)},
|
"usage": float64(99),
|
||||||
|
}
|
||||||
|
a.AddFields("acctest", fields, map[string]string{})
|
||||||
|
a.AddFields("acctest", fields, map[string]string{"acc": "test"})
|
||||||
|
a.AddFields("acctest", fields, map[string]string{"acc": "test"}, now)
|
||||||
|
|
||||||
|
testm := <-a.metrics
|
||||||
|
actual := testm.String()
|
||||||
|
assert.Contains(t, actual, "acctest usage=99")
|
||||||
|
|
||||||
|
testm = <-a.metrics
|
||||||
|
actual = testm.String()
|
||||||
|
assert.Contains(t, actual, "acctest,acc=test usage=99")
|
||||||
|
|
||||||
|
testm = <-a.metrics
|
||||||
|
actual = testm.String()
|
||||||
|
assert.Equal(t,
|
||||||
|
fmt.Sprintf("acctest,acc=test usage=99 %d", now.UnixNano()),
|
||||||
|
actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that all Inf fields get dropped, and not added to metrics channel
|
||||||
|
func TestAddInfFields(t *testing.T) {
|
||||||
|
inf := math.Inf(1)
|
||||||
|
ninf := math.Inf(-1)
|
||||||
|
|
||||||
|
a := accumulator{}
|
||||||
|
now := time.Now()
|
||||||
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
|
defer close(a.metrics)
|
||||||
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"usage": inf,
|
||||||
|
"nusage": ninf,
|
||||||
|
}
|
||||||
|
a.AddFields("acctest", fields, map[string]string{})
|
||||||
|
a.AddFields("acctest", fields, map[string]string{"acc": "test"})
|
||||||
|
a.AddFields("acctest", fields, map[string]string{"acc": "test"}, now)
|
||||||
|
|
||||||
|
assert.Len(t, a.metrics, 0)
|
||||||
|
|
||||||
|
// test that non-inf fields are kept and not dropped
|
||||||
|
fields["notinf"] = float64(100)
|
||||||
|
a.AddFields("acctest", fields, map[string]string{})
|
||||||
|
testm := <-a.metrics
|
||||||
|
actual := testm.String()
|
||||||
|
assert.Contains(t, actual, "acctest notinf=100")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that nan fields are dropped and not added
|
||||||
|
func TestAddNaNFields(t *testing.T) {
|
||||||
|
nan := math.NaN()
|
||||||
|
|
||||||
|
a := accumulator{}
|
||||||
|
now := time.Now()
|
||||||
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
|
defer close(a.metrics)
|
||||||
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"usage": nan,
|
||||||
|
}
|
||||||
|
a.AddFields("acctest", fields, map[string]string{})
|
||||||
|
a.AddFields("acctest", fields, map[string]string{"acc": "test"})
|
||||||
|
a.AddFields("acctest", fields, map[string]string{"acc": "test"}, now)
|
||||||
|
|
||||||
|
assert.Len(t, a.metrics, 0)
|
||||||
|
|
||||||
|
// test that non-nan fields are kept and not dropped
|
||||||
|
fields["notnan"] = float64(100)
|
||||||
|
a.AddFields("acctest", fields, map[string]string{})
|
||||||
|
testm := <-a.metrics
|
||||||
|
actual := testm.String()
|
||||||
|
assert.Contains(t, actual, "acctest notnan=100")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddUint64Fields(t *testing.T) {
|
||||||
|
a := accumulator{}
|
||||||
|
now := time.Now()
|
||||||
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
|
defer close(a.metrics)
|
||||||
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"usage": uint64(99),
|
||||||
|
}
|
||||||
|
a.AddFields("acctest", fields, map[string]string{})
|
||||||
|
a.AddFields("acctest", fields, map[string]string{"acc": "test"})
|
||||||
|
a.AddFields("acctest", fields, map[string]string{"acc": "test"}, now)
|
||||||
|
|
||||||
|
testm := <-a.metrics
|
||||||
|
actual := testm.String()
|
||||||
|
assert.Contains(t, actual, "acctest usage=99i")
|
||||||
|
|
||||||
|
testm = <-a.metrics
|
||||||
|
actual = testm.String()
|
||||||
|
assert.Contains(t, actual, "acctest,acc=test usage=99i")
|
||||||
|
|
||||||
|
testm = <-a.metrics
|
||||||
|
actual = testm.String()
|
||||||
|
assert.Equal(t,
|
||||||
|
fmt.Sprintf("acctest,acc=test usage=99i %d", now.UnixNano()),
|
||||||
|
actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddUint64Overflow(t *testing.T) {
|
||||||
|
a := accumulator{}
|
||||||
|
now := time.Now()
|
||||||
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
|
defer close(a.metrics)
|
||||||
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"usage": uint64(9223372036854775808),
|
||||||
|
}
|
||||||
|
a.AddFields("acctest", fields, map[string]string{})
|
||||||
|
a.AddFields("acctest", fields, map[string]string{"acc": "test"})
|
||||||
|
a.AddFields("acctest", fields, map[string]string{"acc": "test"}, now)
|
||||||
|
|
||||||
|
testm := <-a.metrics
|
||||||
|
actual := testm.String()
|
||||||
|
assert.Contains(t, actual, "acctest usage=9223372036854775807i")
|
||||||
|
|
||||||
|
testm = <-a.metrics
|
||||||
|
actual = testm.String()
|
||||||
|
assert.Contains(t, actual, "acctest,acc=test usage=9223372036854775807i")
|
||||||
|
|
||||||
|
testm = <-a.metrics
|
||||||
|
actual = testm.String()
|
||||||
|
assert.Equal(t,
|
||||||
|
fmt.Sprintf("acctest,acc=test usage=9223372036854775807i %d", now.UnixNano()),
|
||||||
|
actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddInts(t *testing.T) {
|
||||||
|
a := accumulator{}
|
||||||
|
a.addDefaultTag("default", "tag")
|
||||||
|
now := time.Now()
|
||||||
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
|
defer close(a.metrics)
|
||||||
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
|
a.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": int(101)},
|
||||||
map[string]string{})
|
map[string]string{})
|
||||||
a.AddCounter("acctest",
|
a.AddFields("acctest",
|
||||||
map[string]interface{}{"value": float64(101)},
|
map[string]interface{}{"value": int32(101)},
|
||||||
map[string]string{"acc": "test"})
|
map[string]string{"acc": "test"})
|
||||||
a.AddCounter("acctest",
|
a.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": int64(101)},
|
||||||
|
map[string]string{"acc": "test"}, now)
|
||||||
|
|
||||||
|
testm := <-a.metrics
|
||||||
|
actual := testm.String()
|
||||||
|
assert.Contains(t, actual, "acctest,default=tag value=101i")
|
||||||
|
|
||||||
|
testm = <-a.metrics
|
||||||
|
actual = testm.String()
|
||||||
|
assert.Contains(t, actual, "acctest,acc=test,default=tag value=101i")
|
||||||
|
|
||||||
|
testm = <-a.metrics
|
||||||
|
actual = testm.String()
|
||||||
|
assert.Equal(t,
|
||||||
|
fmt.Sprintf("acctest,acc=test,default=tag value=101i %d", now.UnixNano()),
|
||||||
|
actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddFloats(t *testing.T) {
|
||||||
|
a := accumulator{}
|
||||||
|
a.addDefaultTag("default", "tag")
|
||||||
|
now := time.Now()
|
||||||
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
|
defer close(a.metrics)
|
||||||
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
|
a.AddFields("acctest",
|
||||||
|
map[string]interface{}{"value": float32(101)},
|
||||||
|
map[string]string{"acc": "test"})
|
||||||
|
a.AddFields("acctest",
|
||||||
map[string]interface{}{"value": float64(101)},
|
map[string]interface{}{"value": float64(101)},
|
||||||
map[string]string{"acc": "test"}, now)
|
map[string]string{"acc": "test"}, now)
|
||||||
|
|
||||||
testm := <-metrics
|
testm := <-a.metrics
|
||||||
actual := testm.String()
|
actual := testm.String()
|
||||||
assert.Contains(t, actual, "acctest value=101")
|
assert.Contains(t, actual, "acctest,acc=test,default=tag value=101")
|
||||||
assert.Equal(t, testm.Type(), telegraf.Counter)
|
|
||||||
|
|
||||||
testm = <-metrics
|
testm = <-a.metrics
|
||||||
actual = testm.String()
|
|
||||||
assert.Contains(t, actual, "acctest,acc=test value=101")
|
|
||||||
assert.Equal(t, testm.Type(), telegraf.Counter)
|
|
||||||
|
|
||||||
testm = <-metrics
|
|
||||||
actual = testm.String()
|
actual = testm.String()
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
fmt.Sprintf("acctest,acc=test value=101 %d\n", now.UnixNano()),
|
fmt.Sprintf("acctest,acc=test,default=tag value=101 %d", now.UnixNano()),
|
||||||
actual)
|
actual)
|
||||||
assert.Equal(t, testm.Type(), telegraf.Counter)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestMetricMaker struct {
|
func TestAddStrings(t *testing.T) {
|
||||||
|
a := accumulator{}
|
||||||
|
a.addDefaultTag("default", "tag")
|
||||||
|
now := time.Now()
|
||||||
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
|
defer close(a.metrics)
|
||||||
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
|
a.AddFields("acctest",
|
||||||
|
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
|
||||||
|
actual := testm.String()
|
||||||
|
assert.Contains(t, actual, "acctest,acc=test,default=tag value=\"test\"")
|
||||||
|
|
||||||
|
testm = <-a.metrics
|
||||||
|
actual = testm.String()
|
||||||
|
assert.Equal(t,
|
||||||
|
fmt.Sprintf("acctest,acc=test,default=tag value=\"foo\" %d", now.UnixNano()),
|
||||||
|
actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tm *TestMetricMaker) Name() string {
|
func TestAddBools(t *testing.T) {
|
||||||
return "TestPlugin"
|
a := accumulator{}
|
||||||
|
a.addDefaultTag("default", "tag")
|
||||||
|
now := time.Now()
|
||||||
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
|
defer close(a.metrics)
|
||||||
|
a.inputConfig = &models.InputConfig{}
|
||||||
|
|
||||||
|
a.AddFields("acctest",
|
||||||
|
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
|
||||||
|
actual := testm.String()
|
||||||
|
assert.Contains(t, actual, "acctest,acc=test,default=tag value=true")
|
||||||
|
|
||||||
|
testm = <-a.metrics
|
||||||
|
actual = testm.String()
|
||||||
|
assert.Equal(t,
|
||||||
|
fmt.Sprintf("acctest,acc=test,default=tag value=false %d", now.UnixNano()),
|
||||||
|
actual)
|
||||||
}
|
}
|
||||||
func (tm *TestMetricMaker) MakeMetric(
|
|
||||||
measurement string,
|
// Test that tag filters get applied to metrics.
|
||||||
fields map[string]interface{},
|
func TestAccFilterTags(t *testing.T) {
|
||||||
tags map[string]string,
|
a := accumulator{}
|
||||||
mType telegraf.ValueType,
|
now := time.Now()
|
||||||
t time.Time,
|
a.metrics = make(chan telegraf.Metric, 10)
|
||||||
) telegraf.Metric {
|
defer close(a.metrics)
|
||||||
switch mType {
|
filter := models.Filter{
|
||||||
case telegraf.Untyped:
|
TagExclude: []string{"acc"},
|
||||||
if m, err := metric.New(measurement, tags, fields, t); err == nil {
|
|
||||||
return m
|
|
||||||
}
|
}
|
||||||
case telegraf.Counter:
|
assert.NoError(t, filter.Compile())
|
||||||
if m, err := metric.New(measurement, tags, fields, t, telegraf.Counter); err == nil {
|
a.inputConfig = &models.InputConfig{}
|
||||||
return m
|
a.inputConfig.Filter = filter
|
||||||
}
|
|
||||||
case telegraf.Gauge:
|
a.AddFields("acctest",
|
||||||
if m, err := metric.New(measurement, tags, fields, t, telegraf.Gauge); err == nil {
|
map[string]interface{}{"value": float64(101)},
|
||||||
return m
|
map[string]string{})
|
||||||
}
|
a.AddFields("acctest",
|
||||||
}
|
map[string]interface{}{"value": float64(101)},
|
||||||
return nil
|
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")
|
||||||
}
|
}
|
||||||
|
|||||||
153
agent/agent.go
153
agent/agent.go
@@ -12,7 +12,6 @@ import (
|
|||||||
"github.com/influxdata/telegraf/internal"
|
"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"
|
||||||
"github.com/influxdata/telegraf/selfstat"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Agent runs telegraf and collects data based on the given config
|
// Agent runs telegraf and collects data based on the given config
|
||||||
@@ -45,6 +44,8 @@ func NewAgent(config *config.Config) (*Agent, error) {
|
|||||||
// Connect connects to all configured outputs
|
// Connect connects to all configured outputs
|
||||||
func (a *Agent) Connect() error {
|
func (a *Agent) Connect() error {
|
||||||
for _, o := range a.Config.Outputs {
|
for _, o := range a.Config.Outputs {
|
||||||
|
o.Quiet = a.Config.Agent.Quiet
|
||||||
|
|
||||||
switch ot := o.Output.(type) {
|
switch ot := o.Output.(type) {
|
||||||
case telegraf.ServiceOutput:
|
case telegraf.ServiceOutput:
|
||||||
if err := ot.Start(); err != nil {
|
if err := ot.Start(); err != nil {
|
||||||
@@ -88,7 +89,7 @@ func panicRecover(input *models.RunningInput) {
|
|||||||
trace := make([]byte, 2048)
|
trace := make([]byte, 2048)
|
||||||
runtime.Stack(trace, true)
|
runtime.Stack(trace, true)
|
||||||
log.Printf("E! FATAL: Input [%s] panicked: %s, Stack:\n%s\n",
|
log.Printf("E! FATAL: Input [%s] panicked: %s, Stack:\n%s\n",
|
||||||
input.Name(), err, trace)
|
input.Name, err, trace)
|
||||||
log.Println("E! PLEASE REPORT THIS PANIC ON GITHUB with " +
|
log.Println("E! PLEASE REPORT THIS PANIC ON GITHUB with " +
|
||||||
"stack trace, configuration, and OS information: " +
|
"stack trace, configuration, and OS information: " +
|
||||||
"https://github.com/influxdata/telegraf/issues/new")
|
"https://github.com/influxdata/telegraf/issues/new")
|
||||||
@@ -102,33 +103,35 @@ func (a *Agent) gatherer(
|
|||||||
input *models.RunningInput,
|
input *models.RunningInput,
|
||||||
interval time.Duration,
|
interval time.Duration,
|
||||||
metricC chan telegraf.Metric,
|
metricC chan telegraf.Metric,
|
||||||
) {
|
) error {
|
||||||
defer panicRecover(input)
|
defer panicRecover(input)
|
||||||
|
|
||||||
GatherTime := selfstat.RegisterTiming("gather",
|
|
||||||
"gather_time_ns",
|
|
||||||
map[string]string{"input": input.Config.Name},
|
|
||||||
)
|
|
||||||
|
|
||||||
acc := NewAccumulator(input, metricC)
|
|
||||||
acc.SetPrecision(a.Config.Agent.Precision.Duration,
|
|
||||||
a.Config.Agent.Interval.Duration)
|
|
||||||
|
|
||||||
ticker := time.NewTicker(interval)
|
ticker := time.NewTicker(interval)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
var outerr error
|
||||||
|
|
||||||
|
acc := NewAccumulator(input.Config, metricC)
|
||||||
|
acc.SetPrecision(a.Config.Agent.Precision.Duration,
|
||||||
|
a.Config.Agent.Interval.Duration)
|
||||||
|
acc.setDefaultTags(a.Config.Tags)
|
||||||
|
|
||||||
internal.RandomSleep(a.Config.Agent.CollectionJitter.Duration, shutdown)
|
internal.RandomSleep(a.Config.Agent.CollectionJitter.Duration, shutdown)
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
gatherWithTimeout(shutdown, input, acc, interval)
|
gatherWithTimeout(shutdown, input, acc, interval)
|
||||||
elapsed := time.Since(start)
|
elapsed := time.Since(start)
|
||||||
|
|
||||||
GatherTime.Incr(elapsed.Nanoseconds())
|
if outerr != nil {
|
||||||
|
return outerr
|
||||||
|
}
|
||||||
|
log.Printf("D! Input [%s] gathered metrics, (%s interval) in %s\n",
|
||||||
|
input.Name, interval, elapsed)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-shutdown:
|
case <-shutdown:
|
||||||
return
|
return nil
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -157,13 +160,13 @@ func gatherWithTimeout(
|
|||||||
select {
|
select {
|
||||||
case err := <-done:
|
case err := <-done:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("E! ERROR in input [%s]: %s", input.Name(), err)
|
log.Printf("E! ERROR in input [%s]: %s", input.Name, err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
log.Printf("E! ERROR: input [%s] took longer to collect than "+
|
log.Printf("E! ERROR: input [%s] took longer to collect than "+
|
||||||
"collection interval (%s)",
|
"collection interval (%s)",
|
||||||
input.Name(), timeout)
|
input.Name, timeout)
|
||||||
continue
|
continue
|
||||||
case <-shutdown:
|
case <-shutdown:
|
||||||
return
|
return
|
||||||
@@ -191,13 +194,13 @@ func (a *Agent) Test() error {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
for _, input := range a.Config.Inputs {
|
for _, input := range a.Config.Inputs {
|
||||||
acc := NewAccumulator(input, metricC)
|
acc := NewAccumulator(input.Config, metricC)
|
||||||
|
acc.SetTrace(true)
|
||||||
acc.SetPrecision(a.Config.Agent.Precision.Duration,
|
acc.SetPrecision(a.Config.Agent.Precision.Duration,
|
||||||
a.Config.Agent.Interval.Duration)
|
a.Config.Agent.Interval.Duration)
|
||||||
input.SetTrace(true)
|
acc.setDefaultTags(a.Config.Tags)
|
||||||
input.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 {
|
||||||
fmt.Printf("* Internal: %s\n", input.Config.Interval)
|
fmt.Printf("* Internal: %s\n", input.Config.Interval)
|
||||||
}
|
}
|
||||||
@@ -205,13 +208,16 @@ 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.
|
||||||
switch input.Name() {
|
switch input.Name {
|
||||||
case "cpu", "mongodb", "procstat":
|
case "cpu", "mongodb", "procstat":
|
||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
fmt.Printf("* Plugin: %s, Collection 2\n", input.Name())
|
fmt.Printf("* Plugin: %s, Collection 2\n", input.Name)
|
||||||
if err := input.Input.Gather(acc); err != nil {
|
if err := input.Input.Gather(acc); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -244,71 +250,45 @@ func (a *Agent) flush() {
|
|||||||
func (a *Agent) flusher(shutdown chan struct{}, metricC chan telegraf.Metric) error {
|
func (a *Agent) flusher(shutdown chan struct{}, metricC chan telegraf.Metric) error {
|
||||||
// Inelegant, but this sleep is to allow the Gather threads to run, so that
|
// Inelegant, but this sleep is to allow the Gather threads to run, so that
|
||||||
// the flusher will flush after metrics are collected.
|
// the flusher will flush after metrics are collected.
|
||||||
time.Sleep(time.Millisecond * 300)
|
time.Sleep(time.Millisecond * 200)
|
||||||
|
|
||||||
// create an output metric channel and a gorouting that continously passes
|
|
||||||
// each metric onto the output plugins & aggregators.
|
|
||||||
outMetricC := make(chan telegraf.Metric, 100)
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-shutdown:
|
|
||||||
if len(outMetricC) > 0 {
|
|
||||||
// keep going until outMetricC is flushed
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return
|
|
||||||
case m := <-outMetricC:
|
|
||||||
// if dropOriginal is set to true, then we will only send this
|
|
||||||
// metric to the aggregators, not the outputs.
|
|
||||||
var dropOriginal bool
|
|
||||||
if !m.IsAggregate() {
|
|
||||||
for _, agg := range a.Config.Aggregators {
|
|
||||||
if ok := agg.Add(m.Copy()); ok {
|
|
||||||
dropOriginal = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !dropOriginal {
|
|
||||||
for i, o := range a.Config.Outputs {
|
|
||||||
if i == len(a.Config.Outputs)-1 {
|
|
||||||
o.AddMetric(m)
|
|
||||||
} else {
|
|
||||||
o.AddMetric(m.Copy())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
ticker := time.NewTicker(a.Config.Agent.FlushInterval.Duration)
|
ticker := time.NewTicker(a.Config.Agent.FlushInterval.Duration)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-shutdown:
|
case <-shutdown:
|
||||||
log.Println("I! Hang on, flushing any cached metrics before shutdown")
|
log.Println("I! Hang on, flushing any cached metrics before shutdown")
|
||||||
// wait for outMetricC to get flushed before flushing outputs
|
|
||||||
wg.Wait()
|
|
||||||
a.flush()
|
a.flush()
|
||||||
return nil
|
return nil
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
internal.RandomSleep(a.Config.Agent.FlushJitter.Duration, shutdown)
|
internal.RandomSleep(a.Config.Agent.FlushJitter.Duration, shutdown)
|
||||||
a.flush()
|
a.flush()
|
||||||
case metric := <-metricC:
|
case m := <-metricC:
|
||||||
// NOTE potential bottleneck here as we put each metric through the
|
for i, o := range a.Config.Outputs {
|
||||||
// processors serially.
|
if i == len(a.Config.Outputs)-1 {
|
||||||
mS := []telegraf.Metric{metric}
|
o.AddMetric(m)
|
||||||
for _, processor := range a.Config.Processors {
|
} else {
|
||||||
mS = processor.Apply(mS...)
|
o.AddMetric(copyMetric(m))
|
||||||
}
|
|
||||||
for _, m := range mS {
|
|
||||||
outMetricC <- m
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyMetric(m telegraf.Metric) telegraf.Metric {
|
||||||
|
t := time.Time(m.Time())
|
||||||
|
|
||||||
|
tags := make(map[string]string)
|
||||||
|
fields := make(map[string]interface{})
|
||||||
|
for k, v := range m.Tags() {
|
||||||
|
tags[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range m.Fields() {
|
||||||
|
fields[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
out, _ := telegraf.NewMetric(m.Name(), tags, fields, t)
|
||||||
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run runs the agent daemon, gathering every Interval
|
// Run runs the agent daemon, gathering every Interval
|
||||||
@@ -321,20 +301,20 @@ func (a *Agent) Run(shutdown chan struct{}) error {
|
|||||||
a.Config.Agent.Hostname, a.Config.Agent.FlushInterval.Duration)
|
a.Config.Agent.Hostname, a.Config.Agent.FlushInterval.Duration)
|
||||||
|
|
||||||
// channel shared between all input threads for accumulating metrics
|
// channel shared between all input threads for accumulating metrics
|
||||||
metricC := make(chan telegraf.Metric, 100)
|
metricC := make(chan telegraf.Metric, 10000)
|
||||||
|
|
||||||
// Start all ServicePlugins
|
|
||||||
for _, input := range a.Config.Inputs {
|
for _, input := range a.Config.Inputs {
|
||||||
input.SetDefaultTags(a.Config.Tags)
|
// Start service of any ServicePlugins
|
||||||
switch p := input.Input.(type) {
|
switch p := input.Input.(type) {
|
||||||
case telegraf.ServiceInput:
|
case telegraf.ServiceInput:
|
||||||
acc := NewAccumulator(input, metricC)
|
acc := NewAccumulator(input.Config, metricC)
|
||||||
// Service input plugins should set their own precision of their
|
// Service input plugins should set their own precision of their
|
||||||
// metrics.
|
// metrics.
|
||||||
acc.SetPrecision(time.Nanosecond, 0)
|
acc.DisablePrecision()
|
||||||
|
acc.setDefaultTags(a.Config.Tags)
|
||||||
if err := p.Start(acc); err != nil {
|
if err := p.Start(acc); err != nil {
|
||||||
log.Printf("E! Service for input %s failed to start, exiting\n%s\n",
|
log.Printf("E! Service for input %s failed to start, exiting\n%s\n",
|
||||||
input.Name(), err.Error())
|
input.Name, err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer p.Stop()
|
defer p.Stop()
|
||||||
@@ -356,17 +336,6 @@ func (a *Agent) Run(shutdown chan struct{}) error {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
wg.Add(len(a.Config.Aggregators))
|
|
||||||
for _, aggregator := range a.Config.Aggregators {
|
|
||||||
go func(agg *models.RunningAggregator) {
|
|
||||||
defer wg.Done()
|
|
||||||
acc := NewAccumulator(agg, metricC)
|
|
||||||
acc.SetPrecision(a.Config.Agent.Precision.Duration,
|
|
||||||
a.Config.Agent.Interval.Duration)
|
|
||||||
agg.Run(acc, shutdown)
|
|
||||||
}(aggregator)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Add(len(a.Config.Inputs))
|
wg.Add(len(a.Config.Inputs))
|
||||||
for _, input := range a.Config.Inputs {
|
for _, input := range a.Config.Inputs {
|
||||||
interval := a.Config.Agent.Interval.Duration
|
interval := a.Config.Agent.Interval.Duration
|
||||||
@@ -376,7 +345,9 @@ func (a *Agent) Run(shutdown chan struct{}) error {
|
|||||||
}
|
}
|
||||||
go func(in *models.RunningInput, interv time.Duration) {
|
go func(in *models.RunningInput, interv time.Duration) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
a.gatherer(shutdown, in, interv, metricC)
|
if err := a.gatherer(shutdown, in, interv, metricC); err != nil {
|
||||||
|
log.Printf("E! " + err.Error())
|
||||||
|
}
|
||||||
}(input, interval)
|
}(input, interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
package telegraf
|
|
||||||
|
|
||||||
// Aggregator is an interface for implementing an Aggregator plugin.
|
|
||||||
// the RunningAggregator wraps this interface and guarantees that
|
|
||||||
// Add, Push, and Reset can not be called concurrently, so locking is not
|
|
||||||
// required when implementing an Aggregator plugin.
|
|
||||||
type Aggregator interface {
|
|
||||||
// SampleConfig returns the default configuration of the Input.
|
|
||||||
SampleConfig() string
|
|
||||||
|
|
||||||
// Description returns a one-sentence description on the Input.
|
|
||||||
Description() string
|
|
||||||
|
|
||||||
// Add the metric to the aggregator.
|
|
||||||
Add(in Metric)
|
|
||||||
|
|
||||||
// Push pushes the current aggregates to the accumulator.
|
|
||||||
Push(acc Accumulator)
|
|
||||||
|
|
||||||
// Reset resets the aggregators caches and aggregates.
|
|
||||||
Reset()
|
|
||||||
}
|
|
||||||
@@ -4,14 +4,17 @@ machine:
|
|||||||
post:
|
post:
|
||||||
- sudo service zookeeper stop
|
- sudo service zookeeper stop
|
||||||
- go version
|
- go version
|
||||||
- go version | grep 1.7.4 || 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.8beta1.linux-amd64.tar.gz
|
- wget https://storage.googleapis.com/golang/go1.7.1.linux-amd64.tar.gz
|
||||||
- sudo tar -C /usr/local -xzf go1.8beta1.linux-amd64.tar.gz
|
- sudo tar -C /usr/local -xzf go1.7.1.linux-amd64.tar.gz
|
||||||
- go version
|
- go version
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
override:
|
override:
|
||||||
- docker info
|
- docker info
|
||||||
|
post:
|
||||||
|
- gem install fpm
|
||||||
|
- sudo apt-get install -y rpm python-boto
|
||||||
|
|
||||||
test:
|
test:
|
||||||
override:
|
override:
|
||||||
|
|||||||
@@ -6,25 +6,18 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"plugin"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
|
||||||
"github.com/influxdata/telegraf/agent"
|
"github.com/influxdata/telegraf/agent"
|
||||||
"github.com/influxdata/telegraf/internal/config"
|
"github.com/influxdata/telegraf/internal/config"
|
||||||
"github.com/influxdata/telegraf/logger"
|
"github.com/influxdata/telegraf/logger"
|
||||||
"github.com/influxdata/telegraf/plugins/aggregators"
|
|
||||||
_ "github.com/influxdata/telegraf/plugins/aggregators/all"
|
|
||||||
"github.com/influxdata/telegraf/plugins/inputs"
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||||||
_ "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/influxdata/telegraf/plugins/processors"
|
|
||||||
_ "github.com/influxdata/telegraf/plugins/processors/all"
|
|
||||||
"github.com/kardianos/service"
|
"github.com/kardianos/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -48,16 +41,10 @@ var fOutputFilters = flag.String("output-filter", "",
|
|||||||
"filter the outputs to enable, separator is :")
|
"filter the outputs to enable, separator is :")
|
||||||
var fOutputList = flag.Bool("output-list", false,
|
var fOutputList = flag.Bool("output-list", false,
|
||||||
"print available output plugins.")
|
"print available output plugins.")
|
||||||
var fAggregatorFilters = flag.String("aggregator-filter", "",
|
|
||||||
"filter the aggregators to enable, separator is :")
|
|
||||||
var fProcessorFilters = flag.String("processor-filter", "",
|
|
||||||
"filter the processors to enable, separator is :")
|
|
||||||
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 fService = flag.String("service", "",
|
var fService = flag.String("service", "",
|
||||||
"operate on the service")
|
"operate on the service")
|
||||||
var fPlugins = flag.String("plugins", "",
|
|
||||||
"path to directory containing external plugins")
|
|
||||||
|
|
||||||
// Telegraf version, populated linker.
|
// Telegraf version, populated linker.
|
||||||
// ie, -ldflags "-X main.version=`git describe --always --tags`"
|
// ie, -ldflags "-X main.version=`git describe --always --tags`"
|
||||||
@@ -81,38 +68,47 @@ const usage = `Telegraf, The plugin-driven server agent for collecting and repor
|
|||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
telegraf [commands|flags]
|
telegraf <flags>
|
||||||
|
|
||||||
The commands & flags are:
|
The flags are:
|
||||||
|
|
||||||
config print out full sample configuration to stdout
|
-config <file> configuration file to load
|
||||||
version print the version to stdout
|
-test gather metrics once, print them to stdout, and exit
|
||||||
|
-sample-config print out full sample configuration to stdout
|
||||||
|
-config-directory directory containing additional *.conf files
|
||||||
|
-input-filter filter the input plugins to enable, separator is :
|
||||||
|
-input-list print all the plugins inputs
|
||||||
|
-output-filter filter the output plugins to enable, separator is :
|
||||||
|
-output-list print all the available outputs
|
||||||
|
-usage print usage for a plugin, ie, 'telegraf -usage mysql'
|
||||||
|
-debug print metrics as they're generated to stdout
|
||||||
|
-quiet run in quiet mode
|
||||||
|
-version print the version to stdout
|
||||||
|
-service Control the service, ie, 'telegraf -service install (windows only)'
|
||||||
|
|
||||||
--config <file> configuration file to load
|
In addition to the -config flag, telegraf will also load the config file from
|
||||||
--test gather metrics once, print them to stdout, and exit
|
an environment variable or default location. Precedence is:
|
||||||
--config-directory directory containing additional *.conf files
|
1. -config flag
|
||||||
--input-filter filter the input plugins to enable, separator is :
|
2. $TELEGRAF_CONFIG_PATH environment variable
|
||||||
--output-filter filter the output plugins to enable, separator is :
|
3. $HOME/.telegraf/telegraf.conf
|
||||||
--usage print usage for a plugin, ie, 'telegraf --usage mysql'
|
4. /etc/telegraf/telegraf.conf
|
||||||
--debug print metrics as they're generated to stdout
|
|
||||||
--quiet run in quiet mode
|
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
# generate a telegraf config file:
|
# generate a telegraf config file:
|
||||||
telegraf config > telegraf.conf
|
telegraf -sample-config > telegraf.conf
|
||||||
|
|
||||||
# generate config with only cpu input & influxdb output plugins defined
|
# generate config with only cpu input & influxdb output plugins defined
|
||||||
telegraf --input-filter cpu --output-filter influxdb config
|
telegraf -sample-config -input-filter cpu -output-filter influxdb
|
||||||
|
|
||||||
# run a single telegraf collection, outputing metrics to stdout
|
# run a single telegraf collection, outputing metrics to stdout
|
||||||
telegraf --config telegraf.conf -test
|
telegraf -config telegraf.conf -test
|
||||||
|
|
||||||
# run telegraf with all plugins defined in config file
|
# run telegraf with all plugins defined in config file
|
||||||
telegraf --config telegraf.conf
|
telegraf -config telegraf.conf
|
||||||
|
|
||||||
# run telegraf, enabling the cpu & memory input, and influxdb output plugins
|
# run telegraf, enabling the cpu & memory input, and influxdb output plugins
|
||||||
telegraf --config telegraf.conf --input-filter cpu:mem --output-filter influxdb
|
telegraf -config telegraf.conf -input-filter cpu:mem -output-filter influxdb
|
||||||
`
|
`
|
||||||
|
|
||||||
var stop chan struct{}
|
var stop chan struct{}
|
||||||
@@ -132,6 +128,7 @@ func reloadLoop(stop chan struct{}, s service.Service) {
|
|||||||
reload <- true
|
reload <- true
|
||||||
for <-reload {
|
for <-reload {
|
||||||
reload <- false
|
reload <- false
|
||||||
|
flag.Usage = func() { usageExit(0) }
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
args := flag.Args()
|
args := flag.Args()
|
||||||
|
|
||||||
@@ -145,16 +142,6 @@ func reloadLoop(stop chan struct{}, s service.Service) {
|
|||||||
outputFilter := strings.TrimSpace(*fOutputFilters)
|
outputFilter := strings.TrimSpace(*fOutputFilters)
|
||||||
outputFilters = strings.Split(":"+outputFilter+":", ":")
|
outputFilters = strings.Split(":"+outputFilter+":", ":")
|
||||||
}
|
}
|
||||||
var aggregatorFilters []string
|
|
||||||
if *fAggregatorFilters != "" {
|
|
||||||
aggregatorFilter := strings.TrimSpace(*fAggregatorFilters)
|
|
||||||
aggregatorFilters = strings.Split(":"+aggregatorFilter+":", ":")
|
|
||||||
}
|
|
||||||
var processorFilters []string
|
|
||||||
if *fProcessorFilters != "" {
|
|
||||||
processorFilter := strings.TrimSpace(*fProcessorFilters)
|
|
||||||
processorFilters = strings.Split(":"+processorFilter+":", ":")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
switch args[0] {
|
switch args[0] {
|
||||||
@@ -162,12 +149,7 @@ func reloadLoop(stop chan struct{}, s service.Service) {
|
|||||||
fmt.Printf("Telegraf v%s (git: %s %s)\n", version, branch, commit)
|
fmt.Printf("Telegraf v%s (git: %s %s)\n", version, branch, commit)
|
||||||
return
|
return
|
||||||
case "config":
|
case "config":
|
||||||
config.PrintSampleConfig(
|
config.PrintSampleConfig(inputFilters, outputFilters)
|
||||||
inputFilters,
|
|
||||||
outputFilters,
|
|
||||||
aggregatorFilters,
|
|
||||||
processorFilters,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -190,17 +172,12 @@ func reloadLoop(stop chan struct{}, s service.Service) {
|
|||||||
fmt.Printf("Telegraf v%s (git: %s %s)\n", version, branch, commit)
|
fmt.Printf("Telegraf v%s (git: %s %s)\n", version, branch, commit)
|
||||||
return
|
return
|
||||||
case *fSampleConfig:
|
case *fSampleConfig:
|
||||||
config.PrintSampleConfig(
|
config.PrintSampleConfig(inputFilters, outputFilters)
|
||||||
inputFilters,
|
|
||||||
outputFilters,
|
|
||||||
aggregatorFilters,
|
|
||||||
processorFilters,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
case *fUsage != "":
|
case *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("E! %s and %s", err, err2)
|
log.Fatalf("%s and %s", err, err2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -212,25 +189,26 @@ func reloadLoop(stop chan struct{}, s service.Service) {
|
|||||||
c.InputFilters = inputFilters
|
c.InputFilters = inputFilters
|
||||||
err := c.LoadConfig(*fConfig)
|
err := c.LoadConfig(*fConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("E! " + err.Error())
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *fConfigDirectory != "" {
|
if *fConfigDirectory != "" {
|
||||||
err = c.LoadDirectory(*fConfigDirectory)
|
err = c.LoadDirectory(*fConfigDirectory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("E! " + err.Error())
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(c.Outputs) == 0 {
|
if len(c.Outputs) == 0 {
|
||||||
log.Fatalf("E! Error: no outputs found, did you provide a valid config file?")
|
log.Fatalf("Error: no outputs found, did you provide a valid config file?")
|
||||||
}
|
}
|
||||||
if len(c.Inputs) == 0 {
|
if len(c.Inputs) == 0 {
|
||||||
log.Fatalf("E! Error: no inputs found, did you provide a valid config file?")
|
log.Fatalf("Error: no inputs found, did you provide a valid config file?")
|
||||||
}
|
}
|
||||||
|
|
||||||
ag, err := agent.NewAgent(c)
|
ag, err := agent.NewAgent(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("E! " + err.Error())
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup logging
|
// Setup logging
|
||||||
@@ -243,14 +221,14 @@ func reloadLoop(stop chan struct{}, s service.Service) {
|
|||||||
if *fTest {
|
if *fTest {
|
||||||
err = ag.Test()
|
err = ag.Test()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("E! " + err.Error())
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ag.Connect()
|
err = ag.Connect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("E! " + err.Error())
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
shutdown := make(chan struct{})
|
shutdown := make(chan struct{})
|
||||||
@@ -281,7 +259,7 @@ func reloadLoop(stop chan struct{}, s service.Service) {
|
|||||||
if *fPidfile != "" {
|
if *fPidfile != "" {
|
||||||
f, err := os.Create(*fPidfile)
|
f, err := os.Create(*fPidfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("E! Unable to create pidfile: %s", err)
|
log.Fatalf("Unable to create pidfile: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(f, "%d\n", os.Getpid())
|
fmt.Fprintf(f, "%d\n", os.Getpid())
|
||||||
@@ -312,93 +290,8 @@ func (p *program) Stop(s service.Service) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadExternalPlugins loads external plugins from shared libraries (.so, .dll, etc.)
|
|
||||||
// in the specified directory.
|
|
||||||
func loadExternalPlugins(dir string) error {
|
|
||||||
return filepath.Walk(dir, func(pth string, info os.FileInfo, err error) error {
|
|
||||||
// Stop if there was an error.
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore directories.
|
|
||||||
if info.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore files that aren't shared libraries.
|
|
||||||
ext := strings.ToLower(path.Ext(pth))
|
|
||||||
if ext != ".so" && ext != ".dll" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load plugin.
|
|
||||||
p, err := plugin.Open(pth)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register plugin.
|
|
||||||
if err := registerPlugin(dir, pth, p); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// registerPlugin registers an external plugin with telegraf.
|
|
||||||
func registerPlugin(pluginsDir, filePath string, p *plugin.Plugin) error {
|
|
||||||
// Clean the file path and make sure it's relative to the root plugins directory.
|
|
||||||
// This is done because plugin names are namespaced using the directory
|
|
||||||
// structure. E.g., if the root plugin directory, passed in the pluginsDir
|
|
||||||
// argument, is '/home/jdoe/bin/telegraf/plugins' and we're registering plugin
|
|
||||||
// '/home/jdoe/bin/telegraf/plugins/input/mysql.so'
|
|
||||||
pluginsDir = filepath.Clean(pluginsDir)
|
|
||||||
parentDir, _ := filepath.Split(pluginsDir)
|
|
||||||
var err error
|
|
||||||
if filePath, err = filepath.Rel(parentDir, filePath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Strip the file extension and save it.
|
|
||||||
ext := path.Ext(filePath)
|
|
||||||
filePath = strings.TrimSuffix(filePath, ext)
|
|
||||||
// Convert path separators to "." to generate a plugin name namespaced by directory names.
|
|
||||||
name := strings.Replace(filePath, string(os.PathSeparator), ".", -1)
|
|
||||||
|
|
||||||
if create, err := p.Lookup("NewInput"); err == nil {
|
|
||||||
inputs.Add(name, inputs.Creator(create.(func() telegraf.Input)))
|
|
||||||
} else if create, err := p.Lookup("NewOutput"); err == nil {
|
|
||||||
outputs.Add(name, outputs.Creator(create.(func() telegraf.Output)))
|
|
||||||
} else if create, err := p.Lookup("NewProcessor"); err == nil {
|
|
||||||
processors.Add(name, processors.Creator(create.(func() telegraf.Processor)))
|
|
||||||
} else if create, err := p.Lookup("NewAggregator"); err == nil {
|
|
||||||
aggregators.Add(name, aggregators.Creator(create.(func() telegraf.Aggregator)))
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("not a telegraf plugin: %s%s", filePath, ext)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("I! Registered: %s (from %s%s)\n", name, filePath, ext)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Usage = func() { usageExit(0) }
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
// Load external plugins, if requested.
|
|
||||||
if *fPlugins != "" {
|
|
||||||
pluginsDir, err := filepath.Abs(*fPlugins)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("E! " + err.Error())
|
|
||||||
}
|
|
||||||
log.Printf("I! Loading external plugins from: %s\n", pluginsDir)
|
|
||||||
if err := loadExternalPlugins(*fPlugins); err != nil {
|
|
||||||
log.Fatal("E! " + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
svcConfig := &service.Config{
|
svcConfig := &service.Config{
|
||||||
Name: "telegraf",
|
Name: "telegraf",
|
||||||
@@ -411,7 +304,7 @@ func main() {
|
|||||||
prg := &program{}
|
prg := &program{}
|
||||||
s, err := service.New(prg, svcConfig)
|
s, err := service.New(prg, svcConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("E! " + err.Error())
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
// Handle the -service flag here to prevent any issues with tooling that
|
// Handle the -service flag here to prevent any issues with tooling that
|
||||||
// may not have an interactive session, e.g. installing from Ansible.
|
// may not have an interactive session, e.g. installing from Ansible.
|
||||||
@@ -421,7 +314,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
err := service.Control(s, *fService)
|
err := service.Control(s, *fService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("E! " + err.Error())
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = s.Run()
|
err = s.Run()
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
# Telegraf Aggregator & Processor Plugins
|
|
||||||
|
|
||||||
As of release 1.1.0, Telegraf has the concept of Aggregator and Processor Plugins.
|
|
||||||
|
|
||||||
These plugins sit in-between Input & Output plugins, aggregating and processing
|
|
||||||
metrics as they pass through Telegraf:
|
|
||||||
|
|
||||||
```
|
|
||||||
┌───────────┐
|
|
||||||
│ │
|
|
||||||
│ CPU │───┐
|
|
||||||
│ │ │
|
|
||||||
└───────────┘ │
|
|
||||||
│
|
|
||||||
┌───────────┐ │ ┌───────────┐
|
|
||||||
│ │ │ │ │
|
|
||||||
│ Memory │───┤ ┌──▶│ InfluxDB │
|
|
||||||
│ │ │ │ │ │
|
|
||||||
└───────────┘ │ ┌─────────────┐ ┌─────────────┐ │ └───────────┘
|
|
||||||
│ │ │ │Aggregate │ │
|
|
||||||
┌───────────┐ │ │Process │ │ - mean │ │ ┌───────────┐
|
|
||||||
│ │ │ │ - transform │ │ - quantiles │ │ │ │
|
|
||||||
│ MySQL │───┼───▶│ - decorate │────▶│ - min/max │───┼──▶│ File │
|
|
||||||
│ │ │ │ - filter │ │ - count │ │ │ │
|
|
||||||
└───────────┘ │ │ │ │ │ │ └───────────┘
|
|
||||||
│ └─────────────┘ └─────────────┘ │
|
|
||||||
┌───────────┐ │ │ ┌───────────┐
|
|
||||||
│ │ │ │ │ │
|
|
||||||
│ SNMP │───┤ └──▶│ Kafka │
|
|
||||||
│ │ │ │ │
|
|
||||||
└───────────┘ │ └───────────┘
|
|
||||||
│
|
|
||||||
┌───────────┐ │
|
|
||||||
│ │ │
|
|
||||||
│ Docker │───┘
|
|
||||||
│ │
|
|
||||||
└───────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
Both Aggregators and Processors analyze metrics as they pass through Telegraf.
|
|
||||||
|
|
||||||
**Processor** plugins process metrics as they pass through and immediately emit
|
|
||||||
results based on the values they process. For example, this could be printing
|
|
||||||
all metrics or adding a tag to all metrics that pass through.
|
|
||||||
|
|
||||||
**Aggregator** plugins, on the other hand, are a bit more complicated. Aggregators
|
|
||||||
are typically for emitting new _aggregate_ metrics, such as a running mean,
|
|
||||||
minimum, maximum, quantiles, or standard deviation. For this reason, all _aggregator_
|
|
||||||
plugins are configured with a `period`. The `period` is the size of the window
|
|
||||||
of metrics that each _aggregate_ represents. In other words, the emitted
|
|
||||||
_aggregate_ metric will be the aggregated value of the past `period` seconds.
|
|
||||||
Since many users will only care about their aggregates and not every single metric
|
|
||||||
gathered, there is also a `drop_original` argument, which tells Telegraf to only
|
|
||||||
emit the aggregates and not the original metrics.
|
|
||||||
|
|
||||||
**NOTE** That since aggregators only aggregate metrics within their period, that
|
|
||||||
historical data is not supported. In other words, if your metric timestamp is more
|
|
||||||
than `now() - period` in the past, it will not be aggregated. If this is a feature
|
|
||||||
that you need, please comment on this [github issue](https://github.com/influxdata/telegraf/issues/1992)
|
|
||||||
@@ -1,38 +1,38 @@
|
|||||||
# Telegraf Configuration
|
# Telegraf Configuration
|
||||||
|
|
||||||
You can see the latest config file with all available plugins here:
|
|
||||||
[telegraf.conf](https://github.com/influxdata/telegraf/blob/master/etc/telegraf.conf)
|
|
||||||
|
|
||||||
## Generating a Configuration File
|
## Generating a Configuration File
|
||||||
|
|
||||||
A default Telegraf config file can be auto-generated by telegraf:
|
A default Telegraf config file can be generated using the -sample-config flag:
|
||||||
|
|
||||||
```
|
```
|
||||||
telegraf 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 --input-filter cpu:mem:net:swap --output-filter influxdb:kafka config
|
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
|
||||||
|
|
||||||
Environment variables can be used anywhere in the config file, simply prepend
|
Environment variables can be used anywhere in the config file, simply prepend
|
||||||
them with $. For strings the variable must be within quotes (ie, "$STR_VAR"),
|
them with $. For strings the variable must be within quotes (ie, "$STR_VAR"),
|
||||||
for numbers and booleans they should be plain (ie, $INT_VAR, $BOOL_VAR)
|
for numbers and booleans they should be plain (ie, $INT_VAR, $BOOL_VAR)
|
||||||
|
|
||||||
# Global Tags
|
## `[global_tags]` Configuration
|
||||||
|
|
||||||
Global tags can be specified in the `[global_tags]` section of the config file
|
Global tags can be specified in the `[global_tags]` section of the config file
|
||||||
in 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
|
||||||
|
|
||||||
Telegraf has a few options you can configure under the `[agent]` section of the
|
Telegraf has a few options you can configure under the `agent` section of the
|
||||||
config.
|
config.
|
||||||
|
|
||||||
* **interval**: Default data collection interval for all inputs
|
* **interval**: Default data collection interval for all inputs
|
||||||
@@ -56,63 +56,13 @@ interval. Maximum flush_interval will be flush_interval + flush_jitter
|
|||||||
This is primarily to avoid
|
This is primarily to avoid
|
||||||
large write spikes for users running a large number of telegraf instances.
|
large write spikes for users running a large number of telegraf instances.
|
||||||
ie, a jitter of 5s and flush_interval 10s means flushes will happen every 10-15s.
|
ie, a jitter of 5s and flush_interval 10s means flushes will happen every 10-15s.
|
||||||
* **precision**: 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".
|
|
||||||
* **logfile**: Specify the log file name. The empty string means to log to stdout.
|
|
||||||
* **debug**: Run telegraf in debug mode.
|
* **debug**: Run telegraf in debug mode.
|
||||||
* **quiet**: Run telegraf in quiet mode (error messages only).
|
* **quiet**: Run telegraf in quiet mode.
|
||||||
* **hostname**: Override default hostname, if empty use os.Hostname().
|
* **hostname**: Override default hostname, if empty use os.Hostname().
|
||||||
* **omit_hostname**: If true, do no set the "host" tag in the telegraf agent.
|
|
||||||
|
|
||||||
## Input Configuration
|
|
||||||
|
|
||||||
The following config parameters are available for all inputs:
|
|
||||||
|
|
||||||
* **interval**: How often to gather this metric. Normal plugins use a single
|
|
||||||
global interval, but if one particular input should be run less or more often,
|
|
||||||
you can configure that here.
|
|
||||||
* **name_override**: Override the base name of the measurement.
|
|
||||||
(Default is the name of the input).
|
|
||||||
* **name_prefix**: Specifies a prefix to attach to the measurement name.
|
|
||||||
* **name_suffix**: Specifies a suffix to attach to the measurement name.
|
|
||||||
* **tags**: A map of tags to apply to a specific input's measurements.
|
|
||||||
|
|
||||||
## Output Configuration
|
|
||||||
|
|
||||||
There are no generic configuration options available for all outputs.
|
|
||||||
|
|
||||||
## Aggregator Configuration
|
|
||||||
|
|
||||||
The following config parameters are available for all aggregators:
|
|
||||||
|
|
||||||
* **period**: The period on which to flush & clear each aggregator. All metrics
|
|
||||||
that are sent with timestamps outside of this period will be ignored by the
|
|
||||||
aggregator.
|
|
||||||
* **delay**: The delay before each aggregator is flushed. This is to control
|
|
||||||
how long for aggregators to wait before receiving metrics from input plugins,
|
|
||||||
in the case that aggregators are flushing and inputs are gathering on the
|
|
||||||
same interval.
|
|
||||||
* **drop_original**: If true, the original metric will be dropped by the
|
|
||||||
aggregator and will not get sent to the output plugins.
|
|
||||||
* **name_override**: Override the base name of the measurement.
|
|
||||||
(Default is the name of the input).
|
|
||||||
* **name_prefix**: Specifies a prefix to attach to the measurement name.
|
|
||||||
* **name_suffix**: Specifies a suffix to attach to the measurement name.
|
|
||||||
* **tags**: A map of tags to apply to a specific input's measurements.
|
|
||||||
|
|
||||||
## Processor Configuration
|
|
||||||
|
|
||||||
The following config parameters are available for all processors:
|
|
||||||
|
|
||||||
* **order**: This is the order in which the processor(s) get executed. If this
|
|
||||||
is not specified then processor execution order will be random.
|
|
||||||
|
|
||||||
#### Measurement Filtering
|
#### Measurement Filtering
|
||||||
|
|
||||||
Filters can be configured per input, output, processor, or aggregator,
|
Filters can be configured per input or output, see below for examples.
|
||||||
see below for examples.
|
|
||||||
|
|
||||||
* **namepass**: An array of strings that is used to filter metrics generated by the
|
* **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
|
current input. Each string in the array is tested as a glob match against
|
||||||
@@ -140,6 +90,19 @@ the tag keys in the final measurement.
|
|||||||
the plugin definition, otherwise subsequent plugin config options will be
|
the plugin definition, otherwise subsequent plugin config options will be
|
||||||
interpreted as part of the tagpass/tagdrop map.
|
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.
|
||||||
|
(Default is the name of the input).
|
||||||
|
* **name_prefix**: Specifies a prefix to attach to the measurement name.
|
||||||
|
* **name_suffix**: Specifies a suffix to attach to the measurement name.
|
||||||
|
* **tags**: A map of tags to apply to a specific input's measurements.
|
||||||
|
* **interval**: How often to gather this metric. Normal plugins use a single
|
||||||
|
global interval, but if one particular input should be run less or more often,
|
||||||
|
you can configure that here.
|
||||||
|
|
||||||
#### 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
|
||||||
@@ -291,7 +254,11 @@ to avoid measurement collisions:
|
|||||||
fielddrop = ["cpu_time*"]
|
fielddrop = ["cpu_time*"]
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Output Configuration Examples:
|
## Output Configuration
|
||||||
|
|
||||||
|
Telegraf also supports specifying multiple output sinks to send data to,
|
||||||
|
configuring each output sink is different, but examples can be
|
||||||
|
found by running `telegraf -sample-config`.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[[outputs.influxdb]]
|
[[outputs.influxdb]]
|
||||||
@@ -316,39 +283,3 @@ to avoid measurement collisions:
|
|||||||
[outputs.influxdb.tagpass]
|
[outputs.influxdb.tagpass]
|
||||||
cpu = ["cpu0"]
|
cpu = ["cpu0"]
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Aggregator Configuration Examples:
|
|
||||||
|
|
||||||
This will collect and emit the min/max of the system load1 metric every
|
|
||||||
30s, dropping the originals.
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[[inputs.system]]
|
|
||||||
fieldpass = ["load1"] # collects system load1 metric.
|
|
||||||
|
|
||||||
[[aggregators.minmax]]
|
|
||||||
period = "30s" # send & clear the aggregate every 30s.
|
|
||||||
drop_original = true # drop the original metrics.
|
|
||||||
|
|
||||||
[[outputs.file]]
|
|
||||||
files = ["stdout"]
|
|
||||||
```
|
|
||||||
|
|
||||||
This will collect and emit the min/max of the swap metrics every
|
|
||||||
30s, dropping the originals. The aggregator will not be applied
|
|
||||||
to the system load metrics due to the `namepass` parameter.
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[[inputs.swap]]
|
|
||||||
|
|
||||||
[[inputs.system]]
|
|
||||||
fieldpass = ["load1"] # collects system load1 metric.
|
|
||||||
|
|
||||||
[[aggregators.minmax]]
|
|
||||||
period = "30s" # send & clear the aggregate every 30s.
|
|
||||||
drop_original = true # drop the original metrics.
|
|
||||||
namepass = ["swap"] # only "pass" swap metrics through the aggregator.
|
|
||||||
|
|
||||||
[[outputs.file]]
|
|
||||||
files = ["stdout"]
|
|
||||||
```
|
|
||||||
@@ -147,62 +147,6 @@ Your Telegraf metrics would get tagged with "my_tag_1"
|
|||||||
exec_mycollector,my_tag_1=foo a=5,b_c=6
|
exec_mycollector,my_tag_1=foo a=5,b_c=6
|
||||||
```
|
```
|
||||||
|
|
||||||
If the JSON data is an array, then each element of the array is parsed with the configured settings.
|
|
||||||
Each resulting metric will be output with the same timestamp.
|
|
||||||
|
|
||||||
For example, if the following configuration:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[[inputs.exec]]
|
|
||||||
## Commands array
|
|
||||||
commands = ["/usr/bin/mycollector --foo=bar"]
|
|
||||||
|
|
||||||
## measurement name suffix (for separating different commands)
|
|
||||||
name_suffix = "_mycollector"
|
|
||||||
|
|
||||||
## Data format to consume.
|
|
||||||
## Each data format has it's own unique set of configuration options, read
|
|
||||||
## more about them here:
|
|
||||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md
|
|
||||||
data_format = "json"
|
|
||||||
|
|
||||||
## List of tag names to extract from top-level of JSON server response
|
|
||||||
tag_keys = [
|
|
||||||
"my_tag_1",
|
|
||||||
"my_tag_2"
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
with this JSON output from a command:
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"a": 5,
|
|
||||||
"b": {
|
|
||||||
"c": 6
|
|
||||||
},
|
|
||||||
"my_tag_1": "foo",
|
|
||||||
"my_tag_2": "baz"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"a": 7,
|
|
||||||
"b": {
|
|
||||||
"c": 8
|
|
||||||
},
|
|
||||||
"my_tag_1": "bar",
|
|
||||||
"my_tag_2": "baz"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
Your Telegraf metrics would get tagged with "my_tag_1" and "my_tag_2"
|
|
||||||
|
|
||||||
```
|
|
||||||
exec_mycollector,my_tag_1=foo,my_tag_2=baz a=5,b_c=6
|
|
||||||
exec_mycollector,my_tag_1=bar,my_tag_2=baz a=7,b_c=8
|
|
||||||
```
|
|
||||||
|
|
||||||
# Value:
|
# Value:
|
||||||
|
|
||||||
The "value" data format translates single values into Telegraf metrics. This
|
The "value" data format translates single values into Telegraf metrics. This
|
||||||
@@ -288,16 +232,6 @@ us.west.cpu.load 100
|
|||||||
=> cpu.load,region=us.west value=100
|
=> cpu.load,region=us.west value=100
|
||||||
```
|
```
|
||||||
|
|
||||||
Multiple templates can also be specified, but these should be differentiated
|
|
||||||
using _filters_ (see below for more details)
|
|
||||||
|
|
||||||
```toml
|
|
||||||
templates = [
|
|
||||||
"*.*.* region.region.measurement", # <- all 3-part measurements will match this one.
|
|
||||||
"*.*.*.* region.region.host.measurement", # <- all 4-part measurements will match this one.
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Field Templates:
|
#### Field Templates:
|
||||||
|
|
||||||
The field keyword tells Telegraf to give the metric that field name.
|
The field keyword tells Telegraf to give the metric that field name.
|
||||||
|
|||||||
@@ -12,11 +12,11 @@
|
|||||||
- github.com/gogo/protobuf [BSD LICENSE](https://github.com/gogo/protobuf/blob/master/LICENSE)
|
- github.com/gogo/protobuf [BSD LICENSE](https://github.com/gogo/protobuf/blob/master/LICENSE)
|
||||||
- github.com/golang/protobuf [BSD LICENSE](https://github.com/golang/protobuf/blob/master/LICENSE)
|
- github.com/golang/protobuf [BSD LICENSE](https://github.com/golang/protobuf/blob/master/LICENSE)
|
||||||
- github.com/golang/snappy [BSD LICENSE](https://github.com/golang/snappy/blob/master/LICENSE)
|
- github.com/golang/snappy [BSD LICENSE](https://github.com/golang/snappy/blob/master/LICENSE)
|
||||||
|
- github.com/gonuts/go-shellquote (No License, but the project it was forked from https://github.com/kballard/go-shellquote is [MIT](https://github.com/kballard/go-shellquote/blob/master/LICENSE)).
|
||||||
- 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/kardianos/service [ZLIB LICENSE](https://github.com/kardianos/service/blob/master/LICENSE) (License not named but matches word for word with ZLib)
|
||||||
- github.com/kballard/go-shellquote [MIT LICENSE](https://github.com/kballard/go-shellquote/blob/master/LICENSE)
|
|
||||||
- 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)
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
debug = false
|
debug = false
|
||||||
## Run telegraf in quiet mode (error log messages only).
|
## Run telegraf in quiet mode (error log messages only).
|
||||||
quiet = false
|
quiet = false
|
||||||
## Specify the log file name. The empty string means to log to stderr.
|
## Specify the log file name. The empty string means to log to stdout.
|
||||||
logfile = ""
|
logfile = ""
|
||||||
|
|
||||||
## Override default hostname, if empty use os.Hostname()
|
## Override default hostname, if empty use os.Hostname()
|
||||||
@@ -160,7 +160,7 @@
|
|||||||
# # Configuration for AWS CloudWatch output.
|
# # Configuration for AWS CloudWatch output.
|
||||||
# [[outputs.cloudwatch]]
|
# [[outputs.cloudwatch]]
|
||||||
# ## Amazon REGION
|
# ## Amazon REGION
|
||||||
# region = "us-east-1"
|
# region = 'us-east-1'
|
||||||
#
|
#
|
||||||
# ## Amazon Credentials
|
# ## Amazon Credentials
|
||||||
# ## Credentials are loaded in the following order
|
# ## Credentials are loaded in the following order
|
||||||
@@ -178,7 +178,7 @@
|
|||||||
# #shared_credential_file = ""
|
# #shared_credential_file = ""
|
||||||
#
|
#
|
||||||
# ## Namespace for the CloudWatch MetricDatums
|
# ## Namespace for the CloudWatch MetricDatums
|
||||||
# namespace = "InfluxData/Telegraf"
|
# namespace = 'InfluxData/Telegraf'
|
||||||
|
|
||||||
|
|
||||||
# # Configuration for DataDog API to send metrics to.
|
# # Configuration for DataDog API to send metrics to.
|
||||||
@@ -441,30 +441,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# PROCESSOR PLUGINS #
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
# # Print all metrics that pass through this filter.
|
|
||||||
# [[processors.printer]]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# AGGREGATOR PLUGINS #
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
# # Keep the aggregate min/max of each metric passing through.
|
|
||||||
# [[aggregators.minmax]]
|
|
||||||
# ## General Aggregator Arguments:
|
|
||||||
# ## The period on which to flush & clear the aggregator.
|
|
||||||
# period = "30s"
|
|
||||||
# ## If true, the original metric will be dropped by the
|
|
||||||
# ## aggregator and will not get sent to the output plugins.
|
|
||||||
# drop_original = false
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# INPUT PLUGINS #
|
# INPUT PLUGINS #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
@@ -607,9 +583,6 @@
|
|||||||
# # Read specific statistics per cgroup
|
# # Read specific statistics per cgroup
|
||||||
# [[inputs.cgroup]]
|
# [[inputs.cgroup]]
|
||||||
# ## Directories in which to look for files, globs are supported.
|
# ## Directories in which to look for files, globs are supported.
|
||||||
# ## Consider restricting paths to the set of cgroups you really
|
|
||||||
# ## want to monitor if you have a large number of cgroups, to avoid
|
|
||||||
# ## any cardinality issues.
|
|
||||||
# # paths = [
|
# # paths = [
|
||||||
# # "/cgroup/memory",
|
# # "/cgroup/memory",
|
||||||
# # "/cgroup/memory/child1",
|
# # "/cgroup/memory/child1",
|
||||||
@@ -623,7 +596,7 @@
|
|||||||
# # 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
|
# ## Amazon Credentials
|
||||||
# ## Credentials are loaded in the following order
|
# ## Credentials are loaded in the following order
|
||||||
@@ -641,21 +614,21 @@
|
|||||||
# #shared_credential_file = ""
|
# #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 = "5m"
|
# period = '1m'
|
||||||
#
|
#
|
||||||
# ## Collection Delay (required - must account for metrics availability via CloudWatch API)
|
# ## Collection Delay (required - must account for metrics availability via CloudWatch API)
|
||||||
# delay = "5m"
|
# delay = '1m'
|
||||||
#
|
#
|
||||||
# ## Recomended: use metric 'interval' that is a multiple of 'period' to avoid
|
# ## Recomended: use metric 'interval' that is a multiple of 'period' to avoid
|
||||||
# ## gaps or overlap in pulled data
|
# ## gaps or overlap in pulled data
|
||||||
# interval = "5m"
|
# interval = '1m'
|
||||||
#
|
#
|
||||||
# ## Configure the TTL for the internal cache of metrics.
|
# ## Configure the TTL for the internal cache of metrics.
|
||||||
# ## Defaults to 1 hr if not specified
|
# ## Defaults to 1 hr if not specified
|
||||||
# #cache_ttl = "10m"
|
# #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
|
# ## 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
|
# ## 10 reqs/sec, so if you define multiple namespaces, these should add up to a
|
||||||
@@ -666,12 +639,12 @@
|
|||||||
# ## 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
|
||||||
# #[[inputs.cloudwatch.metrics]]
|
# #[[inputs.cloudwatch.metrics]]
|
||||||
# # names = ["Latency", "RequestCount"]
|
# # names = ['Latency', 'RequestCount']
|
||||||
# #
|
# #
|
||||||
# # ## Dimension filters for Metric (optional)
|
# # ## Dimension filters for Metric (optional)
|
||||||
# # [[inputs.cloudwatch.metrics.dimensions]]
|
# # [[inputs.cloudwatch.metrics.dimensions]]
|
||||||
# # name = "LoadBalancerName"
|
# # name = 'LoadBalancerName'
|
||||||
# # value = "p-example"
|
# # value = 'p-example'
|
||||||
|
|
||||||
|
|
||||||
# # Gather health check statuses from services registered in Consul
|
# # Gather health check statuses from services registered in Consul
|
||||||
@@ -777,8 +750,6 @@
|
|||||||
# # Read stats from one or more Elasticsearch servers or clusters
|
# # Read stats from one or more Elasticsearch servers or clusters
|
||||||
# [[inputs.elasticsearch]]
|
# [[inputs.elasticsearch]]
|
||||||
# ## specify a list of one or more Elasticsearch servers
|
# ## specify a list of one or more Elasticsearch servers
|
||||||
# # you can add username and password to your url to use basic authentication:
|
|
||||||
# # servers = ["http://user:pass@localhost:9200"]
|
|
||||||
# servers = ["http://localhost:9200"]
|
# servers = ["http://localhost:9200"]
|
||||||
#
|
#
|
||||||
# ## Timeout for HTTP requests to the elastic search server(s)
|
# ## Timeout for HTTP requests to the elastic search server(s)
|
||||||
@@ -879,15 +850,12 @@
|
|||||||
# ## 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
|
# ## Make sure you specify the complete path to the stats endpoint
|
||||||
# ## including the protocol, ie http://10.10.3.33:1936/haproxy?stats
|
# ## ie 10.10.3.33:1936/haproxy?stats
|
||||||
# #
|
# #
|
||||||
# ## If no servers are specified, then default to 127.0.0.1:1936/haproxy?stats
|
# ## If no servers are specified, then default to 127.0.0.1:1936/haproxy?stats
|
||||||
# servers = ["http://myhaproxy.com:1936/haproxy?stats"]
|
# servers = ["http://myhaproxy.com:1936/haproxy?stats"]
|
||||||
# ##
|
# ## Or you can also use local socket
|
||||||
# ## You can also use local socket with standard wildcard globbing.
|
# ## servers = ["socket:/run/haproxy/admin.sock"]
|
||||||
# ## Server address not starting with 'http' will be treated as a possible
|
|
||||||
# ## socket, so both examples below are valid.
|
|
||||||
# ## servers = ["socket:/run/haproxy/admin.sock", "/run/haproxy/*.sock"]
|
|
||||||
|
|
||||||
|
|
||||||
# # HTTP/HTTPS request given an address a method and a timeout
|
# # HTTP/HTTPS request given an address a method and a timeout
|
||||||
@@ -1032,22 +1000,6 @@
|
|||||||
# attribute = "LoadedClassCount,UnloadedClassCount,TotalLoadedClassCount"
|
# attribute = "LoadedClassCount,UnloadedClassCount,TotalLoadedClassCount"
|
||||||
|
|
||||||
|
|
||||||
# # Read metrics from the kubernetes kubelet api
|
|
||||||
# [[inputs.kubernetes]]
|
|
||||||
# ## URL for the kubelet
|
|
||||||
# url = "http://1.1.1.1:10255"
|
|
||||||
#
|
|
||||||
# ## Use bearer token for authorization
|
|
||||||
# # 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
|
|
||||||
|
|
||||||
|
|
||||||
# # Read metrics from a LeoFS Server via SNMP
|
# # Read metrics from a LeoFS Server via SNMP
|
||||||
# [[inputs.leofs]]
|
# [[inputs.leofs]]
|
||||||
# ## An array of URI to gather stats about LeoFS.
|
# ## An array of URI to gather stats about LeoFS.
|
||||||
@@ -1167,13 +1119,13 @@
|
|||||||
# ## gather metrics from SHOW BINARY LOGS command output
|
# ## gather metrics from SHOW BINARY LOGS command output
|
||||||
# gather_binary_logs = false
|
# gather_binary_logs = false
|
||||||
# #
|
# #
|
||||||
# ## gather metrics from PERFORMANCE_SCHEMA.TABLE_IO_WAITS_SUMMARY_BY_TABLE
|
# ## gather metrics from PERFORMANCE_SCHEMA.TABLE_IO_WAITS_SUMMART_BY_TABLE
|
||||||
# gather_table_io_waits = false
|
# gather_table_io_waits = false
|
||||||
# #
|
# #
|
||||||
# ## gather metrics from PERFORMANCE_SCHEMA.TABLE_LOCK_WAITS
|
# ## gather metrics from PERFORMANCE_SCHEMA.TABLE_LOCK_WAITS
|
||||||
# gather_table_lock_waits = false
|
# gather_table_lock_waits = false
|
||||||
# #
|
# #
|
||||||
# ## gather metrics from PERFORMANCE_SCHEMA.TABLE_IO_WAITS_SUMMARY_BY_INDEX_USAGE
|
# ## gather metrics from PERFORMANCE_SCHEMA.TABLE_IO_WAITS_SUMMART_BY_INDEX_USAGE
|
||||||
# gather_index_io_waits = false
|
# gather_index_io_waits = false
|
||||||
# #
|
# #
|
||||||
# ## gather metrics from PERFORMANCE_SCHEMA.EVENT_WAITS
|
# ## gather metrics from PERFORMANCE_SCHEMA.EVENT_WAITS
|
||||||
@@ -1295,13 +1247,13 @@
|
|||||||
# ## urls to ping
|
# ## urls to ping
|
||||||
# urls = ["www.google.com"] # required
|
# urls = ["www.google.com"] # required
|
||||||
# ## number of pings to send per collection (ping -c <COUNT>)
|
# ## number of pings to send per collection (ping -c <COUNT>)
|
||||||
# # count = 1
|
# 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 = 1.0
|
# ping_interval = 0.0
|
||||||
# ## per-ping timeout, in s. 0 == no timeout (ping -W <TIMEOUT>)
|
# ## per-ping timeout, in s. 0 == no timeout (ping -W <TIMEOUT>)
|
||||||
# # timeout = 1.0
|
# timeout = 1.0
|
||||||
# ## interface to send ping from (ping -I <INTERFACE>)
|
# ## interface to send ping from (ping -I <INTERFACE>)
|
||||||
# # interface = ""
|
# interface = ""
|
||||||
|
|
||||||
|
|
||||||
# # Read metrics from one or many postgresql servers
|
# # Read metrics from one or many postgresql servers
|
||||||
@@ -1404,6 +1356,8 @@
|
|||||||
# # exe = "nginx"
|
# # exe = "nginx"
|
||||||
# ## pattern as argument for pgrep (ie, pgrep -f <pattern>)
|
# ## pattern as argument for pgrep (ie, pgrep -f <pattern>)
|
||||||
# # pattern = "nginx"
|
# # pattern = "nginx"
|
||||||
|
# ## match the exact name of the process (ie, pgrep -xf <pattern>)
|
||||||
|
# # exact = false
|
||||||
# ## user as argument for pgrep (ie, pgrep -u <user>)
|
# ## user as argument for pgrep (ie, pgrep -u <user>)
|
||||||
# # user = "nginx"
|
# # user = "nginx"
|
||||||
#
|
#
|
||||||
@@ -1729,18 +1683,9 @@
|
|||||||
# ## Address and port to host HTTP listener on
|
# ## Address and port to host HTTP listener on
|
||||||
# service_address = ":8186"
|
# service_address = ":8186"
|
||||||
#
|
#
|
||||||
# ## maximum duration before timing out read of the request
|
# ## timeouts
|
||||||
# read_timeout = "10s"
|
# read_timeout = "10s"
|
||||||
# ## maximum duration before timing out write of the response
|
|
||||||
# write_timeout = "10s"
|
# write_timeout = "10s"
|
||||||
#
|
|
||||||
# ## Maximum allowed http request body size in bytes.
|
|
||||||
# ## 0 means to use the default of 536,870,912 bytes (500 mebibytes)
|
|
||||||
# max_body_size = 0
|
|
||||||
#
|
|
||||||
# ## Maximum line size allowed to be sent in bytes.
|
|
||||||
# ## 0 means to use the default of 65536 bytes (64 kibibytes)
|
|
||||||
# max_line_size = 0
|
|
||||||
|
|
||||||
|
|
||||||
# # Read metrics from Kafka topic(s)
|
# # Read metrics from Kafka topic(s)
|
||||||
@@ -1835,18 +1780,13 @@
|
|||||||
# # Read metrics from NATS subject(s)
|
# # Read metrics from NATS subject(s)
|
||||||
# [[inputs.nats_consumer]]
|
# [[inputs.nats_consumer]]
|
||||||
# ## urls of NATS servers
|
# ## urls of NATS servers
|
||||||
# # servers = ["nats://localhost:4222"]
|
# servers = ["nats://localhost:4222"]
|
||||||
# ## Use Transport Layer Security
|
# ## Use Transport Layer Security
|
||||||
# # secure = false
|
# secure = false
|
||||||
# ## subject(s) to consume
|
# ## subject(s) to consume
|
||||||
# # subjects = ["telegraf"]
|
# subjects = ["telegraf"]
|
||||||
# ## name a queue group
|
# ## name a queue group
|
||||||
# # queue_group = "telegraf_consumers"
|
# queue_group = "telegraf_consumers"
|
||||||
#
|
|
||||||
# ## Sets the limits for pending msgs and bytes for each subscription
|
|
||||||
# ## These shouldn't need to be adjusted except in very high throughput scenarios
|
|
||||||
# # pending_message_limit = 65536
|
|
||||||
# # pending_bytes_limit = 67108864
|
|
||||||
#
|
#
|
||||||
# ## Data format to consume.
|
# ## Data format to consume.
|
||||||
# ## Each data format has it's own unique set of configuration options, read
|
# ## Each data format has it's own unique set of configuration options, read
|
||||||
@@ -1933,14 +1873,14 @@
|
|||||||
# # 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
|
||||||
# # service_address = ":8094"
|
# service_address = ":8094"
|
||||||
#
|
#
|
||||||
# ## Number of TCP messages allowed to queue up. Once filled, the
|
# ## Number of TCP messages allowed to queue up. Once filled, the
|
||||||
# ## TCP listener will start dropping packets.
|
# ## TCP listener will start dropping packets.
|
||||||
# # allowed_pending_messages = 10000
|
# allowed_pending_messages = 10000
|
||||||
#
|
#
|
||||||
# ## Maximum number of concurrent TCP connections to allow
|
# ## Maximum number of concurrent TCP connections to allow
|
||||||
# # max_tcp_connections = 250
|
# max_tcp_connections = 250
|
||||||
#
|
#
|
||||||
# ## Data format to consume.
|
# ## Data format to consume.
|
||||||
# ## Each data format has it's own unique set of configuration options, read
|
# ## Each data format has it's own unique set of configuration options, read
|
||||||
@@ -1952,11 +1892,11 @@
|
|||||||
# # Generic UDP listener
|
# # Generic UDP listener
|
||||||
# [[inputs.udp_listener]]
|
# [[inputs.udp_listener]]
|
||||||
# ## Address and port to host UDP listener on
|
# ## Address and port to host UDP listener on
|
||||||
# # service_address = ":8092"
|
# service_address = ":8092"
|
||||||
#
|
#
|
||||||
# ## Number of UDP messages allowed to queue up. Once filled, the
|
# ## Number of UDP messages allowed to queue up. Once filled, the
|
||||||
# ## UDP listener will start dropping packets.
|
# ## UDP listener will start dropping packets.
|
||||||
# # allowed_pending_messages = 10000
|
# allowed_pending_messages = 10000
|
||||||
#
|
#
|
||||||
# ## Data format to consume.
|
# ## Data format to consume.
|
||||||
# ## Each data format has it's own unique set of configuration options, read
|
# ## Each data format has it's own unique set of configuration options, read
|
||||||
@@ -1981,3 +1921,4 @@
|
|||||||
#
|
#
|
||||||
# [inputs.webhooks.rollbar]
|
# [inputs.webhooks.rollbar]
|
||||||
# path = "/rollbar"
|
# path = "/rollbar"
|
||||||
|
|
||||||
|
|||||||
@@ -130,7 +130,6 @@
|
|||||||
Counters = [
|
Counters = [
|
||||||
"Context Switches/sec",
|
"Context Switches/sec",
|
||||||
"System Calls/sec",
|
"System Calls/sec",
|
||||||
"Processor Queue Length",
|
|
||||||
]
|
]
|
||||||
Instances = ["------"]
|
Instances = ["------"]
|
||||||
Measurement = "win_system"
|
Measurement = "win_system"
|
||||||
|
|||||||
@@ -1,22 +1,16 @@
|
|||||||
package buffer
|
package buffer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
"github.com/influxdata/telegraf/selfstat"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
MetricsWritten = selfstat.Register("agent", "metrics_written", map[string]string{})
|
|
||||||
MetricsDropped = selfstat.Register("agent", "metrics_dropped", map[string]string{})
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Buffer is an object for storing metrics in a circular buffer.
|
// Buffer is an object for storing metrics in a circular buffer.
|
||||||
type Buffer struct {
|
type Buffer struct {
|
||||||
buf chan telegraf.Metric
|
buf chan telegraf.Metric
|
||||||
|
// total dropped metrics
|
||||||
mu sync.Mutex
|
drops int
|
||||||
|
// total metrics added
|
||||||
|
total int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBuffer returns a Buffer
|
// NewBuffer returns a Buffer
|
||||||
@@ -38,14 +32,25 @@ func (b *Buffer) Len() int {
|
|||||||
return len(b.buf)
|
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.
|
// Add adds metrics to the buffer.
|
||||||
func (b *Buffer) Add(metrics ...telegraf.Metric) {
|
func (b *Buffer) Add(metrics ...telegraf.Metric) {
|
||||||
for i, _ := range metrics {
|
for i, _ := range metrics {
|
||||||
MetricsWritten.Incr(1)
|
b.total++
|
||||||
select {
|
select {
|
||||||
case b.buf <- metrics[i]:
|
case b.buf <- metrics[i]:
|
||||||
default:
|
default:
|
||||||
MetricsDropped.Incr(1)
|
b.drops++
|
||||||
<-b.buf
|
<-b.buf
|
||||||
b.buf <- metrics[i]
|
b.buf <- metrics[i]
|
||||||
}
|
}
|
||||||
@@ -56,13 +61,11 @@ func (b *Buffer) Add(metrics ...telegraf.Metric) {
|
|||||||
// the batch will be of maximum length batchSize. It can be less than batchSize,
|
// the batch will be of maximum length batchSize. It can be less than batchSize,
|
||||||
// if the length of Buffer is less than batchSize.
|
// if the length of Buffer is less than batchSize.
|
||||||
func (b *Buffer) Batch(batchSize int) []telegraf.Metric {
|
func (b *Buffer) Batch(batchSize int) []telegraf.Metric {
|
||||||
b.mu.Lock()
|
|
||||||
n := min(len(b.buf), batchSize)
|
n := min(len(b.buf), batchSize)
|
||||||
out := make([]telegraf.Metric, n)
|
out := make([]telegraf.Metric, n)
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
out[i] = <-b.buf
|
out[i] = <-b.buf
|
||||||
}
|
}
|
||||||
b.mu.Unlock()
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,53 +27,47 @@ func BenchmarkAddMetrics(b *testing.B) {
|
|||||||
|
|
||||||
func TestNewBufferBasicFuncs(t *testing.T) {
|
func TestNewBufferBasicFuncs(t *testing.T) {
|
||||||
b := NewBuffer(10)
|
b := NewBuffer(10)
|
||||||
MetricsDropped.Set(0)
|
|
||||||
MetricsWritten.Set(0)
|
|
||||||
|
|
||||||
assert.True(t, b.IsEmpty())
|
assert.True(t, b.IsEmpty())
|
||||||
assert.Zero(t, b.Len())
|
assert.Zero(t, b.Len())
|
||||||
assert.Zero(t, MetricsDropped.Get())
|
assert.Zero(t, b.Drops())
|
||||||
assert.Zero(t, MetricsWritten.Get())
|
assert.Zero(t, b.Total())
|
||||||
|
|
||||||
m := testutil.TestMetric(1, "mymetric")
|
m := testutil.TestMetric(1, "mymetric")
|
||||||
b.Add(m)
|
b.Add(m)
|
||||||
assert.False(t, b.IsEmpty())
|
assert.False(t, b.IsEmpty())
|
||||||
assert.Equal(t, b.Len(), 1)
|
assert.Equal(t, b.Len(), 1)
|
||||||
assert.Equal(t, int64(0), MetricsDropped.Get())
|
assert.Equal(t, b.Drops(), 0)
|
||||||
assert.Equal(t, int64(1), MetricsWritten.Get())
|
assert.Equal(t, b.Total(), 1)
|
||||||
|
|
||||||
b.Add(metricList...)
|
b.Add(metricList...)
|
||||||
assert.False(t, b.IsEmpty())
|
assert.False(t, b.IsEmpty())
|
||||||
assert.Equal(t, b.Len(), 6)
|
assert.Equal(t, b.Len(), 6)
|
||||||
assert.Equal(t, int64(0), MetricsDropped.Get())
|
assert.Equal(t, b.Drops(), 0)
|
||||||
assert.Equal(t, int64(6), MetricsWritten.Get())
|
assert.Equal(t, b.Total(), 6)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDroppingMetrics(t *testing.T) {
|
func TestDroppingMetrics(t *testing.T) {
|
||||||
b := NewBuffer(10)
|
b := NewBuffer(10)
|
||||||
MetricsDropped.Set(0)
|
|
||||||
MetricsWritten.Set(0)
|
|
||||||
|
|
||||||
// Add up to the size of the buffer
|
// Add up to the size of the buffer
|
||||||
b.Add(metricList...)
|
b.Add(metricList...)
|
||||||
b.Add(metricList...)
|
b.Add(metricList...)
|
||||||
assert.False(t, b.IsEmpty())
|
assert.False(t, b.IsEmpty())
|
||||||
assert.Equal(t, b.Len(), 10)
|
assert.Equal(t, b.Len(), 10)
|
||||||
assert.Equal(t, int64(0), MetricsDropped.Get())
|
assert.Equal(t, b.Drops(), 0)
|
||||||
assert.Equal(t, int64(10), MetricsWritten.Get())
|
assert.Equal(t, b.Total(), 10)
|
||||||
|
|
||||||
// Add 5 more and verify they were dropped
|
// Add 5 more and verify they were dropped
|
||||||
b.Add(metricList...)
|
b.Add(metricList...)
|
||||||
assert.False(t, b.IsEmpty())
|
assert.False(t, b.IsEmpty())
|
||||||
assert.Equal(t, b.Len(), 10)
|
assert.Equal(t, b.Len(), 10)
|
||||||
assert.Equal(t, int64(5), MetricsDropped.Get())
|
assert.Equal(t, b.Drops(), 5)
|
||||||
assert.Equal(t, int64(15), MetricsWritten.Get())
|
assert.Equal(t, b.Total(), 15)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGettingBatches(t *testing.T) {
|
func TestGettingBatches(t *testing.T) {
|
||||||
b := NewBuffer(20)
|
b := NewBuffer(20)
|
||||||
MetricsDropped.Set(0)
|
|
||||||
MetricsWritten.Set(0)
|
|
||||||
|
|
||||||
// Verify that the buffer returned is smaller than requested when there are
|
// Verify that the buffer returned is smaller than requested when there are
|
||||||
// not as many items as requested.
|
// not as many items as requested.
|
||||||
@@ -84,8 +78,8 @@ func TestGettingBatches(t *testing.T) {
|
|||||||
// Verify that the buffer is now empty
|
// Verify that the buffer is now empty
|
||||||
assert.True(t, b.IsEmpty())
|
assert.True(t, b.IsEmpty())
|
||||||
assert.Zero(t, b.Len())
|
assert.Zero(t, b.Len())
|
||||||
assert.Zero(t, MetricsDropped.Get())
|
assert.Zero(t, b.Drops())
|
||||||
assert.Equal(t, int64(5), MetricsWritten.Get())
|
assert.Equal(t, b.Total(), 5)
|
||||||
|
|
||||||
// Verify that the buffer returned is not more than the size requested
|
// Verify that the buffer returned is not more than the size requested
|
||||||
b.Add(metricList...)
|
b.Add(metricList...)
|
||||||
@@ -95,6 +89,6 @@ func TestGettingBatches(t *testing.T) {
|
|||||||
// Verify that buffer is not empty
|
// Verify that buffer is not empty
|
||||||
assert.False(t, b.IsEmpty())
|
assert.False(t, b.IsEmpty())
|
||||||
assert.Equal(t, b.Len(), 2)
|
assert.Equal(t, b.Len(), 2)
|
||||||
assert.Equal(t, int64(0), MetricsDropped.Get())
|
assert.Equal(t, b.Drops(), 0)
|
||||||
assert.Equal(t, int64(10), MetricsWritten.Get())
|
assert.Equal(t, b.Total(), 10)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,18 +11,15 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
"github.com/influxdata/telegraf/internal"
|
"github.com/influxdata/telegraf/internal"
|
||||||
"github.com/influxdata/telegraf/internal/models"
|
"github.com/influxdata/telegraf/internal/models"
|
||||||
"github.com/influxdata/telegraf/plugins/aggregators"
|
|
||||||
"github.com/influxdata/telegraf/plugins/inputs"
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||||||
"github.com/influxdata/telegraf/plugins/outputs"
|
"github.com/influxdata/telegraf/plugins/outputs"
|
||||||
"github.com/influxdata/telegraf/plugins/parsers"
|
"github.com/influxdata/telegraf/plugins/parsers"
|
||||||
"github.com/influxdata/telegraf/plugins/processors"
|
|
||||||
"github.com/influxdata/telegraf/plugins/serializers"
|
"github.com/influxdata/telegraf/plugins/serializers"
|
||||||
|
|
||||||
"github.com/influxdata/config"
|
"github.com/influxdata/config"
|
||||||
@@ -53,9 +50,6 @@ type Config struct {
|
|||||||
Agent *AgentConfig
|
Agent *AgentConfig
|
||||||
Inputs []*models.RunningInput
|
Inputs []*models.RunningInput
|
||||||
Outputs []*models.RunningOutput
|
Outputs []*models.RunningOutput
|
||||||
Aggregators []*models.RunningAggregator
|
|
||||||
// Processors have a slice wrapper type because they need to be sorted
|
|
||||||
Processors models.RunningProcessors
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfig() *Config {
|
func NewConfig() *Config {
|
||||||
@@ -70,7 +64,6 @@ func NewConfig() *Config {
|
|||||||
Tags: make(map[string]string),
|
Tags: make(map[string]string),
|
||||||
Inputs: make([]*models.RunningInput, 0),
|
Inputs: make([]*models.RunningInput, 0),
|
||||||
Outputs: make([]*models.RunningOutput, 0),
|
Outputs: make([]*models.RunningOutput, 0),
|
||||||
Processors: make([]*models.RunningProcessor, 0),
|
|
||||||
InputFilters: make([]string, 0),
|
InputFilters: make([]string, 0),
|
||||||
OutputFilters: make([]string, 0),
|
OutputFilters: make([]string, 0),
|
||||||
}
|
}
|
||||||
@@ -145,7 +138,7 @@ type AgentConfig struct {
|
|||||||
func (c *Config) InputNames() []string {
|
func (c *Config) InputNames() []string {
|
||||||
var name []string
|
var name []string
|
||||||
for _, input := range c.Inputs {
|
for _, input := range c.Inputs {
|
||||||
name = append(name, input.Name())
|
name = append(name, input.Name)
|
||||||
}
|
}
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
@@ -241,7 +234,7 @@ var header = `# Telegraf Configuration
|
|||||||
debug = false
|
debug = false
|
||||||
## Run telegraf in quiet mode (error log messages only).
|
## Run telegraf in quiet mode (error log messages only).
|
||||||
quiet = false
|
quiet = false
|
||||||
## Specify the log file name. The empty string means to log to stderr.
|
## Specify the log file name. The empty string means to log to stdout.
|
||||||
logfile = ""
|
logfile = ""
|
||||||
|
|
||||||
## Override default hostname, if empty use os.Hostname()
|
## Override default hostname, if empty use os.Hostname()
|
||||||
@@ -255,20 +248,6 @@ var header = `# Telegraf Configuration
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
`
|
`
|
||||||
|
|
||||||
var processorHeader = `
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# PROCESSOR PLUGINS #
|
|
||||||
###############################################################################
|
|
||||||
`
|
|
||||||
|
|
||||||
var aggregatorHeader = `
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# AGGREGATOR PLUGINS #
|
|
||||||
###############################################################################
|
|
||||||
`
|
|
||||||
|
|
||||||
var inputHeader = `
|
var inputHeader = `
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
@@ -284,15 +263,9 @@ var serviceInputHeader = `
|
|||||||
`
|
`
|
||||||
|
|
||||||
// PrintSampleConfig prints the sample config
|
// PrintSampleConfig prints the sample config
|
||||||
func PrintSampleConfig(
|
func PrintSampleConfig(inputFilters []string, outputFilters []string) {
|
||||||
inputFilters []string,
|
|
||||||
outputFilters []string,
|
|
||||||
aggregatorFilters []string,
|
|
||||||
processorFilters []string,
|
|
||||||
) {
|
|
||||||
fmt.Printf(header)
|
fmt.Printf(header)
|
||||||
|
|
||||||
// print output plugins
|
|
||||||
if len(outputFilters) != 0 {
|
if len(outputFilters) != 0 {
|
||||||
printFilteredOutputs(outputFilters, false)
|
printFilteredOutputs(outputFilters, false)
|
||||||
} else {
|
} else {
|
||||||
@@ -308,33 +281,6 @@ func PrintSampleConfig(
|
|||||||
printFilteredOutputs(pnames, true)
|
printFilteredOutputs(pnames, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// print processor plugins
|
|
||||||
fmt.Printf(processorHeader)
|
|
||||||
if len(processorFilters) != 0 {
|
|
||||||
printFilteredProcessors(processorFilters, false)
|
|
||||||
} else {
|
|
||||||
pnames := []string{}
|
|
||||||
for pname := range processors.Processors {
|
|
||||||
pnames = append(pnames, pname)
|
|
||||||
}
|
|
||||||
sort.Strings(pnames)
|
|
||||||
printFilteredProcessors(pnames, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// pring aggregator plugins
|
|
||||||
fmt.Printf(aggregatorHeader)
|
|
||||||
if len(aggregatorFilters) != 0 {
|
|
||||||
printFilteredAggregators(aggregatorFilters, false)
|
|
||||||
} else {
|
|
||||||
pnames := []string{}
|
|
||||||
for pname := range aggregators.Aggregators {
|
|
||||||
pnames = append(pnames, pname)
|
|
||||||
}
|
|
||||||
sort.Strings(pnames)
|
|
||||||
printFilteredAggregators(pnames, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// print input plugins
|
|
||||||
fmt.Printf(inputHeader)
|
fmt.Printf(inputHeader)
|
||||||
if len(inputFilters) != 0 {
|
if len(inputFilters) != 0 {
|
||||||
printFilteredInputs(inputFilters, false)
|
printFilteredInputs(inputFilters, false)
|
||||||
@@ -352,42 +298,6 @@ func PrintSampleConfig(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func printFilteredProcessors(processorFilters []string, commented bool) {
|
|
||||||
// Filter processors
|
|
||||||
var pnames []string
|
|
||||||
for pname := range processors.Processors {
|
|
||||||
if sliceContains(pname, processorFilters) {
|
|
||||||
pnames = append(pnames, pname)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Strings(pnames)
|
|
||||||
|
|
||||||
// Print Outputs
|
|
||||||
for _, pname := range pnames {
|
|
||||||
creator := processors.Processors[pname]
|
|
||||||
output := creator()
|
|
||||||
printConfig(pname, output, "processors", commented)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func printFilteredAggregators(aggregatorFilters []string, commented bool) {
|
|
||||||
// Filter outputs
|
|
||||||
var anames []string
|
|
||||||
for aname := range aggregators.Aggregators {
|
|
||||||
if sliceContains(aname, aggregatorFilters) {
|
|
||||||
anames = append(anames, aname)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Strings(anames)
|
|
||||||
|
|
||||||
// Print Outputs
|
|
||||||
for _, aname := range anames {
|
|
||||||
creator := aggregators.Aggregators[aname]
|
|
||||||
output := creator()
|
|
||||||
printConfig(aname, output, "aggregators", commented)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func printFilteredInputs(inputFilters []string, commented bool) {
|
func printFilteredInputs(inputFilters []string, commented bool) {
|
||||||
// Filter inputs
|
// Filter inputs
|
||||||
var pnames []string
|
var pnames []string
|
||||||
@@ -597,7 +507,6 @@ func (c *Config) LoadConfig(path string) error {
|
|||||||
case "outputs":
|
case "outputs":
|
||||||
for pluginName, pluginVal := range subTable.Fields {
|
for pluginName, pluginVal := range subTable.Fields {
|
||||||
switch pluginSubTable := pluginVal.(type) {
|
switch pluginSubTable := pluginVal.(type) {
|
||||||
// legacy [outputs.influxdb] support
|
|
||||||
case *ast.Table:
|
case *ast.Table:
|
||||||
if err = c.addOutput(pluginName, pluginSubTable); err != nil {
|
if err = c.addOutput(pluginName, pluginSubTable); err != nil {
|
||||||
return fmt.Errorf("Error parsing %s, %s", path, err)
|
return fmt.Errorf("Error parsing %s, %s", path, err)
|
||||||
@@ -616,7 +525,6 @@ func (c *Config) LoadConfig(path string) error {
|
|||||||
case "inputs", "plugins":
|
case "inputs", "plugins":
|
||||||
for pluginName, pluginVal := range subTable.Fields {
|
for pluginName, pluginVal := range subTable.Fields {
|
||||||
switch pluginSubTable := pluginVal.(type) {
|
switch pluginSubTable := pluginVal.(type) {
|
||||||
// legacy [inputs.cpu] support
|
|
||||||
case *ast.Table:
|
case *ast.Table:
|
||||||
if err = c.addInput(pluginName, pluginSubTable); err != nil {
|
if err = c.addInput(pluginName, pluginSubTable); err != nil {
|
||||||
return fmt.Errorf("Error parsing %s, %s", path, err)
|
return fmt.Errorf("Error parsing %s, %s", path, err)
|
||||||
@@ -632,34 +540,6 @@ func (c *Config) LoadConfig(path string) error {
|
|||||||
pluginName, path)
|
pluginName, path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "processors":
|
|
||||||
for pluginName, pluginVal := range subTable.Fields {
|
|
||||||
switch pluginSubTable := pluginVal.(type) {
|
|
||||||
case []*ast.Table:
|
|
||||||
for _, t := range pluginSubTable {
|
|
||||||
if err = c.addProcessor(pluginName, t); err != nil {
|
|
||||||
return fmt.Errorf("Error parsing %s, %s", path, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("Unsupported config format: %s, file %s",
|
|
||||||
pluginName, path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "aggregators":
|
|
||||||
for pluginName, pluginVal := range subTable.Fields {
|
|
||||||
switch pluginSubTable := pluginVal.(type) {
|
|
||||||
case []*ast.Table:
|
|
||||||
for _, t := range pluginSubTable {
|
|
||||||
if err = c.addAggregator(pluginName, t); err != nil {
|
|
||||||
return fmt.Errorf("Error parsing %s, %s", path, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("Unsupported config format: %s, file %s",
|
|
||||||
pluginName, path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Assume it's an input input for legacy config file support if no other
|
// Assume it's an input input for legacy config file support if no other
|
||||||
// identifiers are present
|
// identifiers are present
|
||||||
default:
|
default:
|
||||||
@@ -668,10 +548,6 @@ func (c *Config) LoadConfig(path string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.Processors) > 1 {
|
|
||||||
sort.Sort(c.Processors)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -704,52 +580,6 @@ func parseFile(fpath string) (*ast.Table, error) {
|
|||||||
return toml.Parse(contents)
|
return toml.Parse(contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) addAggregator(name string, table *ast.Table) error {
|
|
||||||
creator, ok := aggregators.Aggregators[name]
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("Undefined but requested aggregator: %s", name)
|
|
||||||
}
|
|
||||||
aggregator := creator()
|
|
||||||
|
|
||||||
conf, err := buildAggregator(name, table)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := config.UnmarshalTable(table, aggregator); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Aggregators = append(c.Aggregators, models.NewRunningAggregator(aggregator, conf))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) addProcessor(name string, table *ast.Table) error {
|
|
||||||
creator, ok := processors.Processors[name]
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("Undefined but requested processor: %s", name)
|
|
||||||
}
|
|
||||||
processor := creator()
|
|
||||||
|
|
||||||
processorConfig, err := buildProcessor(name, table)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := config.UnmarshalTable(table, processor); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
rf := &models.RunningProcessor{
|
|
||||||
Name: name,
|
|
||||||
Processor: processor,
|
|
||||||
Config: processorConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Processors = append(c.Processors, rf)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) addOutput(name string, table *ast.Table) error {
|
func (c *Config) addOutput(name string, table *ast.Table) error {
|
||||||
if len(c.OutputFilters) > 0 && !sliceContains(name, c.OutputFilters) {
|
if len(c.OutputFilters) > 0 && !sliceContains(name, c.OutputFilters) {
|
||||||
return nil
|
return nil
|
||||||
@@ -821,149 +651,15 @@ func (c *Config) addInput(name string, table *ast.Table) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
rp := models.NewRunningInput(input, pluginConfig)
|
rp := &models.RunningInput{
|
||||||
|
Name: name,
|
||||||
|
Input: input,
|
||||||
|
Config: pluginConfig,
|
||||||
|
}
|
||||||
c.Inputs = append(c.Inputs, rp)
|
c.Inputs = append(c.Inputs, rp)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildAggregator parses Aggregator specific items from the ast.Table,
|
|
||||||
// builds the filter and returns a
|
|
||||||
// models.AggregatorConfig to be inserted into models.RunningAggregator
|
|
||||||
func buildAggregator(name string, tbl *ast.Table) (*models.AggregatorConfig, error) {
|
|
||||||
unsupportedFields := []string{"tagexclude", "taginclude"}
|
|
||||||
for _, field := range unsupportedFields {
|
|
||||||
if _, ok := tbl.Fields[field]; ok {
|
|
||||||
return nil, fmt.Errorf("%s is not supported for aggregator plugins (%s).",
|
|
||||||
field, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
conf := &models.AggregatorConfig{
|
|
||||||
Name: name,
|
|
||||||
Delay: time.Millisecond * 100,
|
|
||||||
Period: time.Second * 30,
|
|
||||||
}
|
|
||||||
|
|
||||||
if node, ok := tbl.Fields["period"]; ok {
|
|
||||||
if kv, ok := node.(*ast.KeyValue); ok {
|
|
||||||
if str, ok := kv.Value.(*ast.String); ok {
|
|
||||||
dur, err := time.ParseDuration(str.Value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
conf.Period = dur
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if node, ok := tbl.Fields["delay"]; ok {
|
|
||||||
if kv, ok := node.(*ast.KeyValue); ok {
|
|
||||||
if str, ok := kv.Value.(*ast.String); ok {
|
|
||||||
dur, err := time.ParseDuration(str.Value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
conf.Delay = dur
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if node, ok := tbl.Fields["drop_original"]; ok {
|
|
||||||
if kv, ok := node.(*ast.KeyValue); ok {
|
|
||||||
if b, ok := kv.Value.(*ast.Boolean); ok {
|
|
||||||
var err error
|
|
||||||
conf.DropOriginal, err = strconv.ParseBool(b.Value)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error parsing boolean value for %s: %s\n", name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if node, ok := tbl.Fields["name_prefix"]; ok {
|
|
||||||
if kv, ok := node.(*ast.KeyValue); ok {
|
|
||||||
if str, ok := kv.Value.(*ast.String); ok {
|
|
||||||
conf.MeasurementPrefix = str.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if node, ok := tbl.Fields["name_suffix"]; ok {
|
|
||||||
if kv, ok := node.(*ast.KeyValue); ok {
|
|
||||||
if str, ok := kv.Value.(*ast.String); ok {
|
|
||||||
conf.MeasurementSuffix = str.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if node, ok := tbl.Fields["name_override"]; ok {
|
|
||||||
if kv, ok := node.(*ast.KeyValue); ok {
|
|
||||||
if str, ok := kv.Value.(*ast.String); ok {
|
|
||||||
conf.NameOverride = str.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
conf.Tags = make(map[string]string)
|
|
||||||
if node, ok := tbl.Fields["tags"]; ok {
|
|
||||||
if subtbl, ok := node.(*ast.Table); ok {
|
|
||||||
if err := config.UnmarshalTable(subtbl, conf.Tags); err != nil {
|
|
||||||
log.Printf("Could not parse tags for input %s\n", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(tbl.Fields, "period")
|
|
||||||
delete(tbl.Fields, "delay")
|
|
||||||
delete(tbl.Fields, "drop_original")
|
|
||||||
delete(tbl.Fields, "name_prefix")
|
|
||||||
delete(tbl.Fields, "name_suffix")
|
|
||||||
delete(tbl.Fields, "name_override")
|
|
||||||
delete(tbl.Fields, "tags")
|
|
||||||
var err error
|
|
||||||
conf.Filter, err = buildFilter(tbl)
|
|
||||||
if err != nil {
|
|
||||||
return conf, err
|
|
||||||
}
|
|
||||||
return conf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildProcessor parses Processor specific items from the ast.Table,
|
|
||||||
// builds the filter and returns a
|
|
||||||
// models.ProcessorConfig to be inserted into models.RunningProcessor
|
|
||||||
func buildProcessor(name string, tbl *ast.Table) (*models.ProcessorConfig, error) {
|
|
||||||
conf := &models.ProcessorConfig{Name: name}
|
|
||||||
unsupportedFields := []string{"tagexclude", "taginclude", "fielddrop", "fieldpass"}
|
|
||||||
for _, field := range unsupportedFields {
|
|
||||||
if _, ok := tbl.Fields[field]; ok {
|
|
||||||
return nil, fmt.Errorf("%s is not supported for processor plugins (%s).",
|
|
||||||
field, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if node, ok := tbl.Fields["order"]; ok {
|
|
||||||
if kv, ok := node.(*ast.KeyValue); ok {
|
|
||||||
if b, ok := kv.Value.(*ast.Integer); ok {
|
|
||||||
var err error
|
|
||||||
conf.Order, err = strconv.ParseInt(b.Value, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error parsing int value for %s: %s\n", name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(tbl.Fields, "order")
|
|
||||||
var err error
|
|
||||||
conf.Filter, err = buildFilter(tbl)
|
|
||||||
if err != nil {
|
|
||||||
return conf, err
|
|
||||||
}
|
|
||||||
return conf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 models.OutputConfig/models.InputConfig
|
// be inserted into the models.OutputConfig/models.InputConfig
|
||||||
|
|||||||
@@ -35,21 +35,11 @@ type Duration struct {
|
|||||||
// UnmarshalTOML parses the duration from the TOML config file
|
// UnmarshalTOML parses the duration from the TOML config file
|
||||||
func (d *Duration) UnmarshalTOML(b []byte) error {
|
func (d *Duration) UnmarshalTOML(b []byte) error {
|
||||||
var err error
|
var err error
|
||||||
b = bytes.Trim(b, `'`)
|
|
||||||
|
|
||||||
// see if we can directly convert it
|
|
||||||
d.Duration, err = time.ParseDuration(string(b))
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse string duration, ie, "1s"
|
// Parse string duration, ie, "1s"
|
||||||
if uq, err := strconv.Unquote(string(b)); err == nil && len(uq) > 0 {
|
d.Duration, err = time.ParseDuration(string(b[1 : len(b)-1]))
|
||||||
d.Duration, err = time.ParseDuration(uq)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// First try parsing as integer seconds
|
// First try parsing as integer seconds
|
||||||
sI, err := strconv.ParseInt(string(b), 10, 64)
|
sI, err := strconv.ParseInt(string(b), 10, 64)
|
||||||
|
|||||||
@@ -131,26 +131,3 @@ func TestRandomSleep(t *testing.T) {
|
|||||||
elapsed = time.Since(s)
|
elapsed = time.Since(s)
|
||||||
assert.True(t, elapsed < time.Millisecond*150)
|
assert.True(t, elapsed < time.Millisecond*150)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDuration(t *testing.T) {
|
|
||||||
var d Duration
|
|
||||||
|
|
||||||
d.UnmarshalTOML([]byte(`"1s"`))
|
|
||||||
assert.Equal(t, time.Second, d.Duration)
|
|
||||||
|
|
||||||
d = Duration{}
|
|
||||||
d.UnmarshalTOML([]byte(`1s`))
|
|
||||||
assert.Equal(t, time.Second, d.Duration)
|
|
||||||
|
|
||||||
d = Duration{}
|
|
||||||
d.UnmarshalTOML([]byte(`'1s'`))
|
|
||||||
assert.Equal(t, time.Second, d.Duration)
|
|
||||||
|
|
||||||
d = Duration{}
|
|
||||||
d.UnmarshalTOML([]byte(`10`))
|
|
||||||
assert.Equal(t, 10*time.Second, d.Duration)
|
|
||||||
|
|
||||||
d = Duration{}
|
|
||||||
d.UnmarshalTOML([]byte(`1.5`))
|
|
||||||
assert.Equal(t, time.Second, d.Duration)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ func (f *Filter) Compile() error {
|
|||||||
// Apply applies the filter to the given measurement name, fields map, and
|
// 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
|
// tags map. It will return false if the metric should be "filtered out", and
|
||||||
// true if the metric should "pass".
|
// true if the metric should "pass".
|
||||||
// It will modify tags & fields in-place if they need to be deleted.
|
// It will modify tags in-place if they need to be deleted.
|
||||||
func (f *Filter) Apply(
|
func (f *Filter) Apply(
|
||||||
measurement string,
|
measurement string,
|
||||||
fields map[string]interface{},
|
fields map[string]interface{},
|
||||||
|
|||||||
@@ -1,143 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"math"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
|
||||||
"github.com/influxdata/telegraf/metric"
|
|
||||||
)
|
|
||||||
|
|
||||||
// makemetric is used by both RunningAggregator & RunningInput
|
|
||||||
// to make metrics.
|
|
||||||
// nameOverride: override the name of the measurement being made.
|
|
||||||
// namePrefix: add this prefix to each measurement name.
|
|
||||||
// nameSuffix: add this suffix to each measurement name.
|
|
||||||
// pluginTags: these are tags that are specific to this plugin.
|
|
||||||
// daemonTags: these are daemon-wide global tags, and get applied after pluginTags.
|
|
||||||
// filter: this is a filter to apply to each metric being made.
|
|
||||||
// applyFilter: if false, the above filter is not applied to each metric.
|
|
||||||
// This is used by Aggregators, because aggregators use filters
|
|
||||||
// on incoming metrics instead of on created metrics.
|
|
||||||
// TODO refactor this to not have such a huge func signature.
|
|
||||||
func makemetric(
|
|
||||||
measurement string,
|
|
||||||
fields map[string]interface{},
|
|
||||||
tags map[string]string,
|
|
||||||
nameOverride string,
|
|
||||||
namePrefix string,
|
|
||||||
nameSuffix string,
|
|
||||||
pluginTags map[string]string,
|
|
||||||
daemonTags map[string]string,
|
|
||||||
filter Filter,
|
|
||||||
applyFilter bool,
|
|
||||||
mType telegraf.ValueType,
|
|
||||||
t time.Time,
|
|
||||||
) telegraf.Metric {
|
|
||||||
if len(fields) == 0 || len(measurement) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if tags == nil {
|
|
||||||
tags = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Override measurement name if set
|
|
||||||
if len(nameOverride) != 0 {
|
|
||||||
measurement = nameOverride
|
|
||||||
}
|
|
||||||
// Apply measurement prefix and suffix if set
|
|
||||||
if len(namePrefix) != 0 {
|
|
||||||
measurement = namePrefix + measurement
|
|
||||||
}
|
|
||||||
if len(nameSuffix) != 0 {
|
|
||||||
measurement = measurement + nameSuffix
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply plugin-wide tags if set
|
|
||||||
for k, v := range pluginTags {
|
|
||||||
if _, ok := tags[k]; !ok {
|
|
||||||
tags[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Apply daemon-wide tags if set
|
|
||||||
for k, v := range daemonTags {
|
|
||||||
if _, ok := tags[k]; !ok {
|
|
||||||
tags[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply the metric filter(s)
|
|
||||||
// for aggregators, the filter does not get applied when the metric is made.
|
|
||||||
// instead, the filter is applied to metric incoming into the plugin.
|
|
||||||
// ie, it gets applied in the RunningAggregator.Apply function.
|
|
||||||
if applyFilter {
|
|
||||||
if ok := filter.Apply(measurement, fields, tags); !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range fields {
|
|
||||||
// Validate uint64 and float64 fields
|
|
||||||
// convert all int & uint types to int64
|
|
||||||
switch val := v.(type) {
|
|
||||||
case nil:
|
|
||||||
// delete nil fields
|
|
||||||
delete(fields, k)
|
|
||||||
case uint:
|
|
||||||
fields[k] = int64(val)
|
|
||||||
continue
|
|
||||||
case uint8:
|
|
||||||
fields[k] = int64(val)
|
|
||||||
continue
|
|
||||||
case uint16:
|
|
||||||
fields[k] = int64(val)
|
|
||||||
continue
|
|
||||||
case uint32:
|
|
||||||
fields[k] = int64(val)
|
|
||||||
continue
|
|
||||||
case int:
|
|
||||||
fields[k] = int64(val)
|
|
||||||
continue
|
|
||||||
case int8:
|
|
||||||
fields[k] = int64(val)
|
|
||||||
continue
|
|
||||||
case int16:
|
|
||||||
fields[k] = int64(val)
|
|
||||||
continue
|
|
||||||
case int32:
|
|
||||||
fields[k] = int64(val)
|
|
||||||
continue
|
|
||||||
case uint64:
|
|
||||||
// InfluxDB does not support writing uint64
|
|
||||||
if val < uint64(9223372036854775808) {
|
|
||||||
fields[k] = int64(val)
|
|
||||||
} else {
|
|
||||||
fields[k] = int64(9223372036854775807)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
case float32:
|
|
||||||
fields[k] = float64(val)
|
|
||||||
continue
|
|
||||||
case float64:
|
|
||||||
// NaNs are invalid values in influxdb, skip measurement
|
|
||||||
if math.IsNaN(val) || math.IsInf(val, 0) {
|
|
||||||
log.Printf("D! Measurement [%s] field [%s] has a NaN or Inf "+
|
|
||||||
"field, skipping",
|
|
||||||
measurement, k)
|
|
||||||
delete(fields, k)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
fields[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m, err := metric.New(measurement, tags, fields, t, mType)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error adding point [%s]: %s\n", measurement, err.Error())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
|
||||||
"github.com/influxdata/telegraf/metric"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RunningAggregator struct {
|
|
||||||
a telegraf.Aggregator
|
|
||||||
Config *AggregatorConfig
|
|
||||||
|
|
||||||
metrics chan telegraf.Metric
|
|
||||||
|
|
||||||
periodStart time.Time
|
|
||||||
periodEnd time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRunningAggregator(
|
|
||||||
a telegraf.Aggregator,
|
|
||||||
conf *AggregatorConfig,
|
|
||||||
) *RunningAggregator {
|
|
||||||
return &RunningAggregator{
|
|
||||||
a: a,
|
|
||||||
Config: conf,
|
|
||||||
metrics: make(chan telegraf.Metric, 100),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AggregatorConfig containing configuration parameters for the running
|
|
||||||
// aggregator plugin.
|
|
||||||
type AggregatorConfig struct {
|
|
||||||
Name string
|
|
||||||
|
|
||||||
DropOriginal bool
|
|
||||||
NameOverride string
|
|
||||||
MeasurementPrefix string
|
|
||||||
MeasurementSuffix string
|
|
||||||
Tags map[string]string
|
|
||||||
Filter Filter
|
|
||||||
|
|
||||||
Period time.Duration
|
|
||||||
Delay time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RunningAggregator) Name() string {
|
|
||||||
return "aggregators." + r.Config.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RunningAggregator) MakeMetric(
|
|
||||||
measurement string,
|
|
||||||
fields map[string]interface{},
|
|
||||||
tags map[string]string,
|
|
||||||
mType telegraf.ValueType,
|
|
||||||
t time.Time,
|
|
||||||
) telegraf.Metric {
|
|
||||||
m := makemetric(
|
|
||||||
measurement,
|
|
||||||
fields,
|
|
||||||
tags,
|
|
||||||
r.Config.NameOverride,
|
|
||||||
r.Config.MeasurementPrefix,
|
|
||||||
r.Config.MeasurementSuffix,
|
|
||||||
r.Config.Tags,
|
|
||||||
nil,
|
|
||||||
r.Config.Filter,
|
|
||||||
false,
|
|
||||||
mType,
|
|
||||||
t,
|
|
||||||
)
|
|
||||||
|
|
||||||
m.SetAggregate(true)
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add applies the given metric to the aggregator.
|
|
||||||
// Before applying to the plugin, it will run any defined filters on the metric.
|
|
||||||
// Apply returns true if the original metric should be dropped.
|
|
||||||
func (r *RunningAggregator) Add(in telegraf.Metric) bool {
|
|
||||||
if r.Config.Filter.IsActive() {
|
|
||||||
// check if the aggregator should apply this metric
|
|
||||||
name := in.Name()
|
|
||||||
fields := in.Fields()
|
|
||||||
tags := in.Tags()
|
|
||||||
t := in.Time()
|
|
||||||
if ok := r.Config.Filter.Apply(name, fields, tags); !ok {
|
|
||||||
// aggregator should not apply this metric
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
in, _ = metric.New(name, tags, fields, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.metrics <- in
|
|
||||||
return r.Config.DropOriginal
|
|
||||||
}
|
|
||||||
func (r *RunningAggregator) add(in telegraf.Metric) {
|
|
||||||
r.a.Add(in)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RunningAggregator) push(acc telegraf.Accumulator) {
|
|
||||||
r.a.Push(acc)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RunningAggregator) reset() {
|
|
||||||
r.a.Reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run runs the running aggregator, listens for incoming metrics, and waits
|
|
||||||
// for period ticks to tell it when to push and reset the aggregator.
|
|
||||||
func (r *RunningAggregator) Run(
|
|
||||||
acc telegraf.Accumulator,
|
|
||||||
shutdown chan struct{},
|
|
||||||
) {
|
|
||||||
// The start of the period is truncated to the nearest second.
|
|
||||||
//
|
|
||||||
// Every metric then gets it's timestamp checked and is dropped if it
|
|
||||||
// is not within:
|
|
||||||
//
|
|
||||||
// start < t < end + truncation + delay
|
|
||||||
//
|
|
||||||
// So if we start at now = 00:00.2 with a 10s period and 0.3s delay:
|
|
||||||
// now = 00:00.2
|
|
||||||
// start = 00:00
|
|
||||||
// truncation = 00:00.2
|
|
||||||
// end = 00:10
|
|
||||||
// 1st interval: 00:00 - 00:10.5
|
|
||||||
// 2nd interval: 00:10 - 00:20.5
|
|
||||||
// etc.
|
|
||||||
//
|
|
||||||
now := time.Now()
|
|
||||||
r.periodStart = now.Truncate(time.Second)
|
|
||||||
truncation := now.Sub(r.periodStart)
|
|
||||||
r.periodEnd = r.periodStart.Add(r.Config.Period)
|
|
||||||
time.Sleep(r.Config.Delay)
|
|
||||||
periodT := time.NewTicker(r.Config.Period)
|
|
||||||
defer periodT.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-shutdown:
|
|
||||||
if len(r.metrics) > 0 {
|
|
||||||
// wait until metrics are flushed before exiting
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return
|
|
||||||
case m := <-r.metrics:
|
|
||||||
if m.Time().Before(r.periodStart) ||
|
|
||||||
m.Time().After(r.periodEnd.Add(truncation).Add(r.Config.Delay)) {
|
|
||||||
// the metric is outside the current aggregation period, so
|
|
||||||
// skip it.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
r.add(m)
|
|
||||||
case <-periodT.C:
|
|
||||||
r.periodStart = r.periodEnd
|
|
||||||
r.periodEnd = r.periodStart.Add(r.Config.Period)
|
|
||||||
r.push(acc)
|
|
||||||
r.reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,256 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
|
||||||
"github.com/influxdata/telegraf/testutil"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAdd(t *testing.T) {
|
|
||||||
a := &TestAggregator{}
|
|
||||||
ra := NewRunningAggregator(a, &AggregatorConfig{
|
|
||||||
Name: "TestRunningAggregator",
|
|
||||||
Filter: Filter{
|
|
||||||
NamePass: []string{"*"},
|
|
||||||
},
|
|
||||||
Period: time.Millisecond * 500,
|
|
||||||
})
|
|
||||||
assert.NoError(t, ra.Config.Filter.Compile())
|
|
||||||
acc := testutil.Accumulator{}
|
|
||||||
go ra.Run(&acc, make(chan struct{}))
|
|
||||||
|
|
||||||
m := ra.MakeMetric(
|
|
||||||
"RITest",
|
|
||||||
map[string]interface{}{"value": int(101)},
|
|
||||||
map[string]string{},
|
|
||||||
telegraf.Untyped,
|
|
||||||
time.Now().Add(time.Millisecond*150),
|
|
||||||
)
|
|
||||||
assert.False(t, ra.Add(m))
|
|
||||||
|
|
||||||
for {
|
|
||||||
time.Sleep(time.Millisecond)
|
|
||||||
if atomic.LoadInt64(&a.sum) > 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert.Equal(t, int64(101), atomic.LoadInt64(&a.sum))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddMetricsOutsideCurrentPeriod(t *testing.T) {
|
|
||||||
a := &TestAggregator{}
|
|
||||||
ra := NewRunningAggregator(a, &AggregatorConfig{
|
|
||||||
Name: "TestRunningAggregator",
|
|
||||||
Filter: Filter{
|
|
||||||
NamePass: []string{"*"},
|
|
||||||
},
|
|
||||||
Period: time.Millisecond * 500,
|
|
||||||
})
|
|
||||||
assert.NoError(t, ra.Config.Filter.Compile())
|
|
||||||
acc := testutil.Accumulator{}
|
|
||||||
go ra.Run(&acc, make(chan struct{}))
|
|
||||||
|
|
||||||
// metric before current period
|
|
||||||
m := ra.MakeMetric(
|
|
||||||
"RITest",
|
|
||||||
map[string]interface{}{"value": int(101)},
|
|
||||||
map[string]string{},
|
|
||||||
telegraf.Untyped,
|
|
||||||
time.Now().Add(-time.Hour),
|
|
||||||
)
|
|
||||||
assert.False(t, ra.Add(m))
|
|
||||||
|
|
||||||
// metric after current period
|
|
||||||
m = ra.MakeMetric(
|
|
||||||
"RITest",
|
|
||||||
map[string]interface{}{"value": int(101)},
|
|
||||||
map[string]string{},
|
|
||||||
telegraf.Untyped,
|
|
||||||
time.Now().Add(time.Hour),
|
|
||||||
)
|
|
||||||
assert.False(t, ra.Add(m))
|
|
||||||
|
|
||||||
// "now" metric
|
|
||||||
m = ra.MakeMetric(
|
|
||||||
"RITest",
|
|
||||||
map[string]interface{}{"value": int(101)},
|
|
||||||
map[string]string{},
|
|
||||||
telegraf.Untyped,
|
|
||||||
time.Now().Add(time.Millisecond*50),
|
|
||||||
)
|
|
||||||
assert.False(t, ra.Add(m))
|
|
||||||
|
|
||||||
for {
|
|
||||||
time.Sleep(time.Millisecond)
|
|
||||||
if atomic.LoadInt64(&a.sum) > 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert.Equal(t, int64(101), atomic.LoadInt64(&a.sum))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddAndPushOnePeriod(t *testing.T) {
|
|
||||||
a := &TestAggregator{}
|
|
||||||
ra := NewRunningAggregator(a, &AggregatorConfig{
|
|
||||||
Name: "TestRunningAggregator",
|
|
||||||
Filter: Filter{
|
|
||||||
NamePass: []string{"*"},
|
|
||||||
},
|
|
||||||
Period: time.Millisecond * 500,
|
|
||||||
})
|
|
||||||
assert.NoError(t, ra.Config.Filter.Compile())
|
|
||||||
acc := testutil.Accumulator{}
|
|
||||||
shutdown := make(chan struct{})
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
ra.Run(&acc, shutdown)
|
|
||||||
}()
|
|
||||||
|
|
||||||
m := ra.MakeMetric(
|
|
||||||
"RITest",
|
|
||||||
map[string]interface{}{"value": int(101)},
|
|
||||||
map[string]string{},
|
|
||||||
telegraf.Untyped,
|
|
||||||
time.Now().Add(time.Millisecond*100),
|
|
||||||
)
|
|
||||||
assert.False(t, ra.Add(m))
|
|
||||||
|
|
||||||
for {
|
|
||||||
time.Sleep(time.Millisecond)
|
|
||||||
if acc.NMetrics() > 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
acc.AssertContainsFields(t, "TestMetric", map[string]interface{}{"sum": int64(101)})
|
|
||||||
|
|
||||||
close(shutdown)
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddDropOriginal(t *testing.T) {
|
|
||||||
ra := NewRunningAggregator(&TestAggregator{}, &AggregatorConfig{
|
|
||||||
Name: "TestRunningAggregator",
|
|
||||||
Filter: Filter{
|
|
||||||
NamePass: []string{"RI*"},
|
|
||||||
},
|
|
||||||
DropOriginal: true,
|
|
||||||
})
|
|
||||||
assert.NoError(t, ra.Config.Filter.Compile())
|
|
||||||
|
|
||||||
m := ra.MakeMetric(
|
|
||||||
"RITest",
|
|
||||||
map[string]interface{}{"value": int(101)},
|
|
||||||
map[string]string{},
|
|
||||||
telegraf.Untyped,
|
|
||||||
time.Now(),
|
|
||||||
)
|
|
||||||
assert.True(t, ra.Add(m))
|
|
||||||
|
|
||||||
// this metric name doesn't match the filter, so Add will return false
|
|
||||||
m2 := ra.MakeMetric(
|
|
||||||
"foobar",
|
|
||||||
map[string]interface{}{"value": int(101)},
|
|
||||||
map[string]string{},
|
|
||||||
telegraf.Untyped,
|
|
||||||
time.Now(),
|
|
||||||
)
|
|
||||||
assert.False(t, ra.Add(m2))
|
|
||||||
}
|
|
||||||
|
|
||||||
// make an untyped, counter, & gauge metric
|
|
||||||
func TestMakeMetricA(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
ra := NewRunningAggregator(&TestAggregator{}, &AggregatorConfig{
|
|
||||||
Name: "TestRunningAggregator",
|
|
||||||
})
|
|
||||||
assert.Equal(t, "aggregators.TestRunningAggregator", ra.Name())
|
|
||||||
|
|
||||||
m := ra.MakeMetric(
|
|
||||||
"RITest",
|
|
||||||
map[string]interface{}{"value": int(101)},
|
|
||||||
map[string]string{},
|
|
||||||
telegraf.Untyped,
|
|
||||||
now,
|
|
||||||
)
|
|
||||||
assert.Equal(
|
|
||||||
t,
|
|
||||||
fmt.Sprintf("RITest value=101i %d\n", now.UnixNano()),
|
|
||||||
m.String(),
|
|
||||||
)
|
|
||||||
assert.Equal(
|
|
||||||
t,
|
|
||||||
m.Type(),
|
|
||||||
telegraf.Untyped,
|
|
||||||
)
|
|
||||||
|
|
||||||
m = ra.MakeMetric(
|
|
||||||
"RITest",
|
|
||||||
map[string]interface{}{"value": int(101)},
|
|
||||||
map[string]string{},
|
|
||||||
telegraf.Counter,
|
|
||||||
now,
|
|
||||||
)
|
|
||||||
assert.Equal(
|
|
||||||
t,
|
|
||||||
fmt.Sprintf("RITest value=101i %d\n", now.UnixNano()),
|
|
||||||
m.String(),
|
|
||||||
)
|
|
||||||
assert.Equal(
|
|
||||||
t,
|
|
||||||
m.Type(),
|
|
||||||
telegraf.Counter,
|
|
||||||
)
|
|
||||||
|
|
||||||
m = ra.MakeMetric(
|
|
||||||
"RITest",
|
|
||||||
map[string]interface{}{"value": int(101)},
|
|
||||||
map[string]string{},
|
|
||||||
telegraf.Gauge,
|
|
||||||
now,
|
|
||||||
)
|
|
||||||
assert.Equal(
|
|
||||||
t,
|
|
||||||
fmt.Sprintf("RITest value=101i %d\n", now.UnixNano()),
|
|
||||||
m.String(),
|
|
||||||
)
|
|
||||||
assert.Equal(
|
|
||||||
t,
|
|
||||||
m.Type(),
|
|
||||||
telegraf.Gauge,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestAggregator struct {
|
|
||||||
sum int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TestAggregator) Description() string { return "" }
|
|
||||||
func (t *TestAggregator) SampleConfig() string { return "" }
|
|
||||||
func (t *TestAggregator) Reset() {
|
|
||||||
atomic.StoreInt64(&t.sum, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TestAggregator) Push(acc telegraf.Accumulator) {
|
|
||||||
acc.AddFields("TestMetric",
|
|
||||||
map[string]interface{}{"sum": t.sum},
|
|
||||||
map[string]string{},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TestAggregator) Add(in telegraf.Metric) {
|
|
||||||
for _, v := range in.Fields() {
|
|
||||||
if vi, ok := v.(int64); ok {
|
|
||||||
atomic.AddInt64(&t.sum, vi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +1,15 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
"github.com/influxdata/telegraf/selfstat"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var GlobalMetricsGathered = selfstat.Register("agent", "metrics_gathered", map[string]string{})
|
|
||||||
|
|
||||||
type RunningInput struct {
|
type RunningInput struct {
|
||||||
|
Name string
|
||||||
Input telegraf.Input
|
Input telegraf.Input
|
||||||
Config *InputConfig
|
Config *InputConfig
|
||||||
|
|
||||||
trace bool
|
|
||||||
defaultTags map[string]string
|
|
||||||
|
|
||||||
MetricsGathered selfstat.Stat
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRunningInput(
|
|
||||||
input telegraf.Input,
|
|
||||||
config *InputConfig,
|
|
||||||
) *RunningInput {
|
|
||||||
return &RunningInput{
|
|
||||||
Input: input,
|
|
||||||
Config: config,
|
|
||||||
MetricsGathered: selfstat.Register(
|
|
||||||
"gather",
|
|
||||||
"metrics_gathered",
|
|
||||||
map[string]string{"input": config.Name},
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// InputConfig containing a name, interval, and filter
|
// InputConfig containing a name, interval, and filter
|
||||||
@@ -45,52 +22,3 @@ type InputConfig struct {
|
|||||||
Filter Filter
|
Filter Filter
|
||||||
Interval time.Duration
|
Interval time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RunningInput) Name() string {
|
|
||||||
return "inputs." + r.Config.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeMetric either returns a metric, or returns nil if the metric doesn't
|
|
||||||
// need to be created (because of filtering, an error, etc.)
|
|
||||||
func (r *RunningInput) MakeMetric(
|
|
||||||
measurement string,
|
|
||||||
fields map[string]interface{},
|
|
||||||
tags map[string]string,
|
|
||||||
mType telegraf.ValueType,
|
|
||||||
t time.Time,
|
|
||||||
) telegraf.Metric {
|
|
||||||
m := makemetric(
|
|
||||||
measurement,
|
|
||||||
fields,
|
|
||||||
tags,
|
|
||||||
r.Config.NameOverride,
|
|
||||||
r.Config.MeasurementPrefix,
|
|
||||||
r.Config.MeasurementSuffix,
|
|
||||||
r.Config.Tags,
|
|
||||||
r.defaultTags,
|
|
||||||
r.Config.Filter,
|
|
||||||
true,
|
|
||||||
mType,
|
|
||||||
t,
|
|
||||||
)
|
|
||||||
|
|
||||||
if r.trace && m != nil {
|
|
||||||
fmt.Println("> " + m.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
r.MetricsGathered.Incr(1)
|
|
||||||
GlobalMetricsGathered.Incr(1)
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RunningInput) Trace() bool {
|
|
||||||
return r.trace
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RunningInput) SetTrace(trace bool) {
|
|
||||||
r.trace = trace
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RunningInput) SetDefaultTags(tags map[string]string) {
|
|
||||||
r.defaultTags = tags
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,339 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMakeMetricNoFields(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
ri := NewRunningInput(&testInput{}, &InputConfig{
|
|
||||||
Name: "TestRunningInput",
|
|
||||||
})
|
|
||||||
|
|
||||||
m := ri.MakeMetric(
|
|
||||||
"RITest",
|
|
||||||
map[string]interface{}{},
|
|
||||||
map[string]string{},
|
|
||||||
telegraf.Untyped,
|
|
||||||
now,
|
|
||||||
)
|
|
||||||
assert.Nil(t, m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// nil fields should get dropped
|
|
||||||
func TestMakeMetricNilFields(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
ri := NewRunningInput(&testInput{}, &InputConfig{
|
|
||||||
Name: "TestRunningInput",
|
|
||||||
})
|
|
||||||
|
|
||||||
m := ri.MakeMetric(
|
|
||||||
"RITest",
|
|
||||||
map[string]interface{}{
|
|
||||||
"value": int(101),
|
|
||||||
"nil": nil,
|
|
||||||
},
|
|
||||||
map[string]string{},
|
|
||||||
telegraf.Untyped,
|
|
||||||
now,
|
|
||||||
)
|
|
||||||
assert.Equal(
|
|
||||||
t,
|
|
||||||
fmt.Sprintf("RITest value=101i %d\n", now.UnixNano()),
|
|
||||||
m.String(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// make an untyped, counter, & gauge metric
|
|
||||||
func TestMakeMetric(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
ri := NewRunningInput(&testInput{}, &InputConfig{
|
|
||||||
Name: "TestRunningInput",
|
|
||||||
})
|
|
||||||
|
|
||||||
ri.SetTrace(true)
|
|
||||||
assert.Equal(t, true, ri.Trace())
|
|
||||||
assert.Equal(t, "inputs.TestRunningInput", ri.Name())
|
|
||||||
|
|
||||||
m := ri.MakeMetric(
|
|
||||||
"RITest",
|
|
||||||
map[string]interface{}{"value": int(101)},
|
|
||||||
map[string]string{},
|
|
||||||
telegraf.Untyped,
|
|
||||||
now,
|
|
||||||
)
|
|
||||||
assert.Equal(
|
|
||||||
t,
|
|
||||||
fmt.Sprintf("RITest value=101i %d\n", now.UnixNano()),
|
|
||||||
m.String(),
|
|
||||||
)
|
|
||||||
assert.Equal(
|
|
||||||
t,
|
|
||||||
m.Type(),
|
|
||||||
telegraf.Untyped,
|
|
||||||
)
|
|
||||||
|
|
||||||
m = ri.MakeMetric(
|
|
||||||
"RITest",
|
|
||||||
map[string]interface{}{"value": int(101)},
|
|
||||||
map[string]string{},
|
|
||||||
telegraf.Counter,
|
|
||||||
now,
|
|
||||||
)
|
|
||||||
assert.Equal(
|
|
||||||
t,
|
|
||||||
fmt.Sprintf("RITest value=101i %d\n", now.UnixNano()),
|
|
||||||
m.String(),
|
|
||||||
)
|
|
||||||
assert.Equal(
|
|
||||||
t,
|
|
||||||
m.Type(),
|
|
||||||
telegraf.Counter,
|
|
||||||
)
|
|
||||||
|
|
||||||
m = ri.MakeMetric(
|
|
||||||
"RITest",
|
|
||||||
map[string]interface{}{"value": int(101)},
|
|
||||||
map[string]string{},
|
|
||||||
telegraf.Gauge,
|
|
||||||
now,
|
|
||||||
)
|
|
||||||
assert.Equal(
|
|
||||||
t,
|
|
||||||
fmt.Sprintf("RITest value=101i %d\n", now.UnixNano()),
|
|
||||||
m.String(),
|
|
||||||
)
|
|
||||||
assert.Equal(
|
|
||||||
t,
|
|
||||||
m.Type(),
|
|
||||||
telegraf.Gauge,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMakeMetricWithPluginTags(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
ri := NewRunningInput(&testInput{}, &InputConfig{
|
|
||||||
Name: "TestRunningInput",
|
|
||||||
Tags: map[string]string{
|
|
||||||
"foo": "bar",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
ri.SetTrace(true)
|
|
||||||
assert.Equal(t, true, ri.Trace())
|
|
||||||
|
|
||||||
m := ri.MakeMetric(
|
|
||||||
"RITest",
|
|
||||||
map[string]interface{}{"value": int(101)},
|
|
||||||
nil,
|
|
||||||
telegraf.Untyped,
|
|
||||||
now,
|
|
||||||
)
|
|
||||||
assert.Equal(
|
|
||||||
t,
|
|
||||||
fmt.Sprintf("RITest,foo=bar value=101i %d\n", now.UnixNano()),
|
|
||||||
m.String(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMakeMetricFilteredOut(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
ri := NewRunningInput(&testInput{}, &InputConfig{
|
|
||||||
Name: "TestRunningInput",
|
|
||||||
Tags: map[string]string{
|
|
||||||
"foo": "bar",
|
|
||||||
},
|
|
||||||
Filter: Filter{NamePass: []string{"foobar"}},
|
|
||||||
})
|
|
||||||
|
|
||||||
ri.SetTrace(true)
|
|
||||||
assert.Equal(t, true, ri.Trace())
|
|
||||||
assert.NoError(t, ri.Config.Filter.Compile())
|
|
||||||
|
|
||||||
m := ri.MakeMetric(
|
|
||||||
"RITest",
|
|
||||||
map[string]interface{}{"value": int(101)},
|
|
||||||
nil,
|
|
||||||
telegraf.Untyped,
|
|
||||||
now,
|
|
||||||
)
|
|
||||||
assert.Nil(t, m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMakeMetricWithDaemonTags(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
ri := NewRunningInput(&testInput{}, &InputConfig{
|
|
||||||
Name: "TestRunningInput",
|
|
||||||
})
|
|
||||||
ri.SetDefaultTags(map[string]string{
|
|
||||||
"foo": "bar",
|
|
||||||
})
|
|
||||||
|
|
||||||
ri.SetTrace(true)
|
|
||||||
assert.Equal(t, true, ri.Trace())
|
|
||||||
|
|
||||||
m := ri.MakeMetric(
|
|
||||||
"RITest",
|
|
||||||
map[string]interface{}{"value": int(101)},
|
|
||||||
map[string]string{},
|
|
||||||
telegraf.Untyped,
|
|
||||||
now,
|
|
||||||
)
|
|
||||||
assert.Equal(
|
|
||||||
t,
|
|
||||||
fmt.Sprintf("RITest,foo=bar value=101i %d\n", now.UnixNano()),
|
|
||||||
m.String(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// make an untyped, counter, & gauge metric
|
|
||||||
func TestMakeMetricInfFields(t *testing.T) {
|
|
||||||
inf := math.Inf(1)
|
|
||||||
ninf := math.Inf(-1)
|
|
||||||
now := time.Now()
|
|
||||||
ri := NewRunningInput(&testInput{}, &InputConfig{
|
|
||||||
Name: "TestRunningInput",
|
|
||||||
})
|
|
||||||
|
|
||||||
ri.SetTrace(true)
|
|
||||||
assert.Equal(t, true, ri.Trace())
|
|
||||||
|
|
||||||
m := ri.MakeMetric(
|
|
||||||
"RITest",
|
|
||||||
map[string]interface{}{
|
|
||||||
"value": int(101),
|
|
||||||
"inf": inf,
|
|
||||||
"ninf": ninf,
|
|
||||||
},
|
|
||||||
map[string]string{},
|
|
||||||
telegraf.Untyped,
|
|
||||||
now,
|
|
||||||
)
|
|
||||||
assert.Equal(
|
|
||||||
t,
|
|
||||||
fmt.Sprintf("RITest value=101i %d\n", now.UnixNano()),
|
|
||||||
m.String(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMakeMetricAllFieldTypes(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
ri := NewRunningInput(&testInput{}, &InputConfig{
|
|
||||||
Name: "TestRunningInput",
|
|
||||||
})
|
|
||||||
|
|
||||||
ri.SetTrace(true)
|
|
||||||
assert.Equal(t, true, ri.Trace())
|
|
||||||
|
|
||||||
m := ri.MakeMetric(
|
|
||||||
"RITest",
|
|
||||||
map[string]interface{}{
|
|
||||||
"a": int(10),
|
|
||||||
"b": int8(10),
|
|
||||||
"c": int16(10),
|
|
||||||
"d": int32(10),
|
|
||||||
"e": uint(10),
|
|
||||||
"f": uint8(10),
|
|
||||||
"g": uint16(10),
|
|
||||||
"h": uint32(10),
|
|
||||||
"i": uint64(10),
|
|
||||||
"j": float32(10),
|
|
||||||
"k": uint64(9223372036854775810),
|
|
||||||
"l": "foobar",
|
|
||||||
"m": true,
|
|
||||||
},
|
|
||||||
map[string]string{},
|
|
||||||
telegraf.Untyped,
|
|
||||||
now,
|
|
||||||
)
|
|
||||||
assert.Contains(t, m.String(), "a=10i")
|
|
||||||
assert.Contains(t, m.String(), "b=10i")
|
|
||||||
assert.Contains(t, m.String(), "c=10i")
|
|
||||||
assert.Contains(t, m.String(), "d=10i")
|
|
||||||
assert.Contains(t, m.String(), "e=10i")
|
|
||||||
assert.Contains(t, m.String(), "f=10i")
|
|
||||||
assert.Contains(t, m.String(), "g=10i")
|
|
||||||
assert.Contains(t, m.String(), "h=10i")
|
|
||||||
assert.Contains(t, m.String(), "i=10i")
|
|
||||||
assert.Contains(t, m.String(), "j=10")
|
|
||||||
assert.NotContains(t, m.String(), "j=10i")
|
|
||||||
assert.Contains(t, m.String(), "k=9223372036854775807i")
|
|
||||||
assert.Contains(t, m.String(), "l=\"foobar\"")
|
|
||||||
assert.Contains(t, m.String(), "m=true")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMakeMetricNameOverride(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
ri := NewRunningInput(&testInput{}, &InputConfig{
|
|
||||||
Name: "TestRunningInput",
|
|
||||||
NameOverride: "foobar",
|
|
||||||
})
|
|
||||||
|
|
||||||
m := ri.MakeMetric(
|
|
||||||
"RITest",
|
|
||||||
map[string]interface{}{"value": int(101)},
|
|
||||||
map[string]string{},
|
|
||||||
telegraf.Untyped,
|
|
||||||
now,
|
|
||||||
)
|
|
||||||
assert.Equal(
|
|
||||||
t,
|
|
||||||
fmt.Sprintf("foobar value=101i %d\n", now.UnixNano()),
|
|
||||||
m.String(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMakeMetricNamePrefix(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
ri := NewRunningInput(&testInput{}, &InputConfig{
|
|
||||||
Name: "TestRunningInput",
|
|
||||||
MeasurementPrefix: "foobar_",
|
|
||||||
})
|
|
||||||
|
|
||||||
m := ri.MakeMetric(
|
|
||||||
"RITest",
|
|
||||||
map[string]interface{}{"value": int(101)},
|
|
||||||
map[string]string{},
|
|
||||||
telegraf.Untyped,
|
|
||||||
now,
|
|
||||||
)
|
|
||||||
assert.Equal(
|
|
||||||
t,
|
|
||||||
fmt.Sprintf("foobar_RITest value=101i %d\n", now.UnixNano()),
|
|
||||||
m.String(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMakeMetricNameSuffix(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
ri := NewRunningInput(&testInput{}, &InputConfig{
|
|
||||||
Name: "TestRunningInput",
|
|
||||||
MeasurementSuffix: "_foobar",
|
|
||||||
})
|
|
||||||
|
|
||||||
m := ri.MakeMetric(
|
|
||||||
"RITest",
|
|
||||||
map[string]interface{}{"value": int(101)},
|
|
||||||
map[string]string{},
|
|
||||||
telegraf.Untyped,
|
|
||||||
now,
|
|
||||||
)
|
|
||||||
assert.Equal(
|
|
||||||
t,
|
|
||||||
fmt.Sprintf("RITest_foobar value=101i %d\n", now.UnixNano()),
|
|
||||||
m.String(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
type testInput struct{}
|
|
||||||
|
|
||||||
func (t *testInput) Description() string { return "" }
|
|
||||||
func (t *testInput) SampleConfig() string { return "" }
|
|
||||||
func (t *testInput) Gather(acc telegraf.Accumulator) error { return nil }
|
|
||||||
@@ -6,8 +6,6 @@ import (
|
|||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
"github.com/influxdata/telegraf/internal/buffer"
|
"github.com/influxdata/telegraf/internal/buffer"
|
||||||
"github.com/influxdata/telegraf/metric"
|
|
||||||
"github.com/influxdata/telegraf/selfstat"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -23,15 +21,10 @@ type RunningOutput struct {
|
|||||||
Name string
|
Name string
|
||||||
Output telegraf.Output
|
Output telegraf.Output
|
||||||
Config *OutputConfig
|
Config *OutputConfig
|
||||||
|
Quiet bool
|
||||||
MetricBufferLimit int
|
MetricBufferLimit int
|
||||||
MetricBatchSize int
|
MetricBatchSize int
|
||||||
|
|
||||||
MetricsFiltered selfstat.Stat
|
|
||||||
MetricsWritten selfstat.Stat
|
|
||||||
BufferSize selfstat.Stat
|
|
||||||
BufferLimit selfstat.Stat
|
|
||||||
WriteTime selfstat.Stat
|
|
||||||
|
|
||||||
metrics *buffer.Buffer
|
metrics *buffer.Buffer
|
||||||
failMetrics *buffer.Buffer
|
failMetrics *buffer.Buffer
|
||||||
}
|
}
|
||||||
@@ -57,56 +50,29 @@ func NewRunningOutput(
|
|||||||
Config: conf,
|
Config: conf,
|
||||||
MetricBufferLimit: bufferLimit,
|
MetricBufferLimit: bufferLimit,
|
||||||
MetricBatchSize: batchSize,
|
MetricBatchSize: batchSize,
|
||||||
MetricsWritten: selfstat.Register(
|
|
||||||
"write",
|
|
||||||
"metrics_written",
|
|
||||||
map[string]string{"output": name},
|
|
||||||
),
|
|
||||||
MetricsFiltered: selfstat.Register(
|
|
||||||
"write",
|
|
||||||
"metrics_filtered",
|
|
||||||
map[string]string{"output": name},
|
|
||||||
),
|
|
||||||
BufferSize: selfstat.Register(
|
|
||||||
"write",
|
|
||||||
"buffer_size",
|
|
||||||
map[string]string{"output": name},
|
|
||||||
),
|
|
||||||
BufferLimit: selfstat.Register(
|
|
||||||
"write",
|
|
||||||
"buffer_limit",
|
|
||||||
map[string]string{"output": name},
|
|
||||||
),
|
|
||||||
WriteTime: selfstat.RegisterTiming(
|
|
||||||
"write",
|
|
||||||
"write_time_ns",
|
|
||||||
map[string]string{"output": name},
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
ro.BufferLimit.Incr(int64(ro.MetricBufferLimit))
|
|
||||||
return ro
|
return ro
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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(m telegraf.Metric) {
|
func (ro *RunningOutput) AddMetric(metric telegraf.Metric) {
|
||||||
// Filter any tagexclude/taginclude parameters before adding metric
|
// Filter any tagexclude/taginclude parameters before adding metric
|
||||||
if ro.Config.Filter.IsActive() {
|
if ro.Config.Filter.IsActive() {
|
||||||
// In order to filter out tags, we need to create a new metric, since
|
// In order to filter out tags, we need to create a new metric, since
|
||||||
// metrics are immutable once created.
|
// metrics are immutable once created.
|
||||||
name := m.Name()
|
name := metric.Name()
|
||||||
tags := m.Tags()
|
tags := metric.Tags()
|
||||||
fields := m.Fields()
|
fields := metric.Fields()
|
||||||
t := m.Time()
|
t := metric.Time()
|
||||||
if ok := ro.Config.Filter.Apply(name, fields, tags); !ok {
|
if ok := ro.Config.Filter.Apply(name, fields, tags); !ok {
|
||||||
ro.MetricsFiltered.Incr(1)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// error is not possible if creating from another metric, so ignore.
|
// error is not possible if creating from another metric, so ignore.
|
||||||
m, _ = metric.New(name, tags, fields, t)
|
metric, _ = telegraf.NewMetric(name, tags, fields, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
ro.metrics.Add(m)
|
ro.metrics.Add(metric)
|
||||||
if ro.metrics.Len() == ro.MetricBatchSize {
|
if ro.metrics.Len() == ro.MetricBatchSize {
|
||||||
batch := ro.metrics.Batch(ro.MetricBatchSize)
|
batch := ro.metrics.Batch(ro.MetricBatchSize)
|
||||||
err := ro.write(batch)
|
err := ro.write(batch)
|
||||||
@@ -118,21 +84,28 @@ func (ro *RunningOutput) AddMetric(m telegraf.Metric) {
|
|||||||
|
|
||||||
// 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 {
|
||||||
nFails, nMetrics := ro.failMetrics.Len(), ro.metrics.Len()
|
if !ro.Quiet {
|
||||||
log.Printf("D! Output [%s] buffer fullness: %d / %d metrics. ",
|
log.Printf("I! Output [%s] buffer fullness: %d / %d metrics. "+
|
||||||
ro.Name, nFails+nMetrics, ro.MetricBufferLimit)
|
"Total gathered metrics: %d. Total dropped metrics: %d.",
|
||||||
ro.BufferSize.Incr(int64(nFails + nMetrics))
|
ro.Name,
|
||||||
|
ro.failMetrics.Len()+ro.metrics.Len(),
|
||||||
|
ro.MetricBufferLimit,
|
||||||
|
ro.metrics.Total(),
|
||||||
|
ro.metrics.Drops()+ro.failMetrics.Drops())
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if !ro.failMetrics.IsEmpty() {
|
if !ro.failMetrics.IsEmpty() {
|
||||||
|
bufLen := ro.failMetrics.Len()
|
||||||
// how many batches of failed writes we need to write.
|
// how many batches of failed writes we need to write.
|
||||||
nBatches := nFails/ro.MetricBatchSize + 1
|
nBatches := bufLen/ro.MetricBatchSize + 1
|
||||||
batchSize := ro.MetricBatchSize
|
batchSize := ro.MetricBatchSize
|
||||||
|
|
||||||
for i := 0; i < nBatches; i++ {
|
for i := 0; i < nBatches; i++ {
|
||||||
// If it's the last batch, only grab the metrics that have not had
|
// If it's the last batch, only grab the metrics that have not had
|
||||||
// a write attempt already (this is primarily to preserve order).
|
// a write attempt already (this is primarily to preserve order).
|
||||||
if i == nBatches-1 {
|
if i == nBatches-1 {
|
||||||
batchSize = nFails % ro.MetricBatchSize
|
batchSize = bufLen % ro.MetricBatchSize
|
||||||
}
|
}
|
||||||
batch := ro.failMetrics.Batch(batchSize)
|
batch := ro.failMetrics.Batch(batchSize)
|
||||||
// If we've already failed previous writes, don't bother trying to
|
// If we've already failed previous writes, don't bother trying to
|
||||||
@@ -153,7 +126,6 @@ func (ro *RunningOutput) Write() error {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
err = ro.write(batch)
|
err = ro.write(batch)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ro.failMetrics.Add(batch...)
|
ro.failMetrics.Add(batch...)
|
||||||
return err
|
return err
|
||||||
@@ -162,19 +134,17 @@ func (ro *RunningOutput) Write() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ro *RunningOutput) write(metrics []telegraf.Metric) error {
|
func (ro *RunningOutput) write(metrics []telegraf.Metric) error {
|
||||||
nMetrics := len(metrics)
|
if metrics == nil || len(metrics) == 0 {
|
||||||
if nMetrics == 0 {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
err := ro.Output.Write(metrics)
|
err := ro.Output.Write(metrics)
|
||||||
elapsed := time.Since(start)
|
elapsed := time.Since(start)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
log.Printf("D! Output [%s] wrote batch of %d metrics in %s\n",
|
if !ro.Quiet {
|
||||||
ro.Name, nMetrics, elapsed)
|
log.Printf("I! Output [%s] wrote batch of %d metrics in %s\n",
|
||||||
ro.MetricsWritten.Incr(int64(nMetrics))
|
ro.Name, len(metrics), elapsed)
|
||||||
ro.BufferSize.Incr(-int64(nMetrics))
|
}
|
||||||
ro.WriteTime.Incr(elapsed.Nanoseconds())
|
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,9 +36,10 @@ func BenchmarkRunningOutputAddWrite(b *testing.B) {
|
|||||||
|
|
||||||
m := &perfOutput{}
|
m := &perfOutput{}
|
||||||
ro := NewRunningOutput("test", m, conf, 1000, 10000)
|
ro := NewRunningOutput("test", m, conf, 1000, 10000)
|
||||||
|
ro.Quiet = true
|
||||||
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
ro.AddMetric(testutil.TestMetric(101, "metric1"))
|
ro.AddMetric(first5[0])
|
||||||
ro.Write()
|
ro.Write()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,9 +52,10 @@ func BenchmarkRunningOutputAddWriteEvery100(b *testing.B) {
|
|||||||
|
|
||||||
m := &perfOutput{}
|
m := &perfOutput{}
|
||||||
ro := NewRunningOutput("test", m, conf, 1000, 10000)
|
ro := NewRunningOutput("test", m, conf, 1000, 10000)
|
||||||
|
ro.Quiet = true
|
||||||
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
ro.AddMetric(testutil.TestMetric(101, "metric1"))
|
ro.AddMetric(first5[0])
|
||||||
if n%100 == 0 {
|
if n%100 == 0 {
|
||||||
ro.Write()
|
ro.Write()
|
||||||
}
|
}
|
||||||
@@ -69,9 +71,10 @@ func BenchmarkRunningOutputAddFailWrites(b *testing.B) {
|
|||||||
m := &perfOutput{}
|
m := &perfOutput{}
|
||||||
m.failWrite = true
|
m.failWrite = true
|
||||||
ro := NewRunningOutput("test", m, conf, 1000, 10000)
|
ro := NewRunningOutput("test", m, conf, 1000, 10000)
|
||||||
|
ro.Quiet = true
|
||||||
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
ro.AddMetric(testutil.TestMetric(101, "metric1"))
|
ro.AddMetric(first5[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,6 +132,7 @@ func TestRunningOutput_PassFilter(t *testing.T) {
|
|||||||
func TestRunningOutput_TagIncludeNoMatch(t *testing.T) {
|
func TestRunningOutput_TagIncludeNoMatch(t *testing.T) {
|
||||||
conf := &OutputConfig{
|
conf := &OutputConfig{
|
||||||
Filter: Filter{
|
Filter: Filter{
|
||||||
|
|
||||||
TagInclude: []string{"nothing*"},
|
TagInclude: []string{"nothing*"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -137,7 +141,7 @@ func TestRunningOutput_TagIncludeNoMatch(t *testing.T) {
|
|||||||
m := &mockOutput{}
|
m := &mockOutput{}
|
||||||
ro := NewRunningOutput("test", m, conf, 1000, 10000)
|
ro := NewRunningOutput("test", m, conf, 1000, 10000)
|
||||||
|
|
||||||
ro.AddMetric(testutil.TestMetric(101, "metric1"))
|
ro.AddMetric(first5[0])
|
||||||
assert.Len(t, m.Metrics(), 0)
|
assert.Len(t, m.Metrics(), 0)
|
||||||
|
|
||||||
err := ro.Write()
|
err := ro.Write()
|
||||||
@@ -150,6 +154,7 @@ func TestRunningOutput_TagIncludeNoMatch(t *testing.T) {
|
|||||||
func TestRunningOutput_TagExcludeMatch(t *testing.T) {
|
func TestRunningOutput_TagExcludeMatch(t *testing.T) {
|
||||||
conf := &OutputConfig{
|
conf := &OutputConfig{
|
||||||
Filter: Filter{
|
Filter: Filter{
|
||||||
|
|
||||||
TagExclude: []string{"tag*"},
|
TagExclude: []string{"tag*"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -158,7 +163,7 @@ func TestRunningOutput_TagExcludeMatch(t *testing.T) {
|
|||||||
m := &mockOutput{}
|
m := &mockOutput{}
|
||||||
ro := NewRunningOutput("test", m, conf, 1000, 10000)
|
ro := NewRunningOutput("test", m, conf, 1000, 10000)
|
||||||
|
|
||||||
ro.AddMetric(testutil.TestMetric(101, "metric1"))
|
ro.AddMetric(first5[0])
|
||||||
assert.Len(t, m.Metrics(), 0)
|
assert.Len(t, m.Metrics(), 0)
|
||||||
|
|
||||||
err := ro.Write()
|
err := ro.Write()
|
||||||
@@ -171,6 +176,7 @@ func TestRunningOutput_TagExcludeMatch(t *testing.T) {
|
|||||||
func TestRunningOutput_TagExcludeNoMatch(t *testing.T) {
|
func TestRunningOutput_TagExcludeNoMatch(t *testing.T) {
|
||||||
conf := &OutputConfig{
|
conf := &OutputConfig{
|
||||||
Filter: Filter{
|
Filter: Filter{
|
||||||
|
|
||||||
TagExclude: []string{"nothing*"},
|
TagExclude: []string{"nothing*"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -179,7 +185,7 @@ func TestRunningOutput_TagExcludeNoMatch(t *testing.T) {
|
|||||||
m := &mockOutput{}
|
m := &mockOutput{}
|
||||||
ro := NewRunningOutput("test", m, conf, 1000, 10000)
|
ro := NewRunningOutput("test", m, conf, 1000, 10000)
|
||||||
|
|
||||||
ro.AddMetric(testutil.TestMetric(101, "metric1"))
|
ro.AddMetric(first5[0])
|
||||||
assert.Len(t, m.Metrics(), 0)
|
assert.Len(t, m.Metrics(), 0)
|
||||||
|
|
||||||
err := ro.Write()
|
err := ro.Write()
|
||||||
@@ -192,6 +198,7 @@ func TestRunningOutput_TagExcludeNoMatch(t *testing.T) {
|
|||||||
func TestRunningOutput_TagIncludeMatch(t *testing.T) {
|
func TestRunningOutput_TagIncludeMatch(t *testing.T) {
|
||||||
conf := &OutputConfig{
|
conf := &OutputConfig{
|
||||||
Filter: Filter{
|
Filter: Filter{
|
||||||
|
|
||||||
TagInclude: []string{"tag*"},
|
TagInclude: []string{"tag*"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -200,7 +207,7 @@ func TestRunningOutput_TagIncludeMatch(t *testing.T) {
|
|||||||
m := &mockOutput{}
|
m := &mockOutput{}
|
||||||
ro := NewRunningOutput("test", m, conf, 1000, 10000)
|
ro := NewRunningOutput("test", m, conf, 1000, 10000)
|
||||||
|
|
||||||
ro.AddMetric(testutil.TestMetric(101, "metric1"))
|
ro.AddMetric(first5[0])
|
||||||
assert.Len(t, m.Metrics(), 0)
|
assert.Len(t, m.Metrics(), 0)
|
||||||
|
|
||||||
err := ro.Write()
|
err := ro.Write()
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/influxdata/telegraf"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RunningProcessor struct {
|
|
||||||
Name string
|
|
||||||
Processor telegraf.Processor
|
|
||||||
Config *ProcessorConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
type RunningProcessors []*RunningProcessor
|
|
||||||
|
|
||||||
func (rp RunningProcessors) Len() int { return len(rp) }
|
|
||||||
func (rp RunningProcessors) Swap(i, j int) { rp[i], rp[j] = rp[j], rp[i] }
|
|
||||||
func (rp RunningProcessors) Less(i, j int) bool { return rp[i].Config.Order < rp[j].Config.Order }
|
|
||||||
|
|
||||||
// FilterConfig containing a name and filter
|
|
||||||
type ProcessorConfig struct {
|
|
||||||
Name string
|
|
||||||
Order int64
|
|
||||||
Filter Filter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rp *RunningProcessor) Apply(in ...telegraf.Metric) []telegraf.Metric {
|
|
||||||
ret := []telegraf.Metric{}
|
|
||||||
|
|
||||||
for _, metric := range in {
|
|
||||||
if rp.Config.Filter.IsActive() {
|
|
||||||
// check if the filter should be applied to this metric
|
|
||||||
if ok := rp.Config.Filter.Apply(metric.Name(), metric.Fields(), metric.Tags()); !ok {
|
|
||||||
// this means filter should not be applied
|
|
||||||
ret = append(ret, metric)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// This metric should pass through the filter, so call the filter Apply
|
|
||||||
// function and append results to the output slice.
|
|
||||||
ret = append(ret, rp.Processor.Apply(metric)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
|
||||||
"github.com/influxdata/telegraf/testutil"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TestProcessor struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TestProcessor) SampleConfig() string { return "" }
|
|
||||||
func (f *TestProcessor) Description() string { return "" }
|
|
||||||
|
|
||||||
// Apply renames:
|
|
||||||
// "foo" to "fuz"
|
|
||||||
// "bar" to "baz"
|
|
||||||
// And it also drops measurements named "dropme"
|
|
||||||
func (f *TestProcessor) Apply(in ...telegraf.Metric) []telegraf.Metric {
|
|
||||||
out := make([]telegraf.Metric, 0)
|
|
||||||
for _, m := range in {
|
|
||||||
switch m.Name() {
|
|
||||||
case "foo":
|
|
||||||
out = append(out, testutil.TestMetric(1, "fuz"))
|
|
||||||
case "bar":
|
|
||||||
out = append(out, testutil.TestMetric(1, "baz"))
|
|
||||||
case "dropme":
|
|
||||||
// drop the metric!
|
|
||||||
default:
|
|
||||||
out = append(out, m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTestRunningProcessor() *RunningProcessor {
|
|
||||||
out := &RunningProcessor{
|
|
||||||
Name: "test",
|
|
||||||
Processor: &TestProcessor{},
|
|
||||||
Config: &ProcessorConfig{Filter: Filter{}},
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRunningProcessor(t *testing.T) {
|
|
||||||
inmetrics := []telegraf.Metric{
|
|
||||||
testutil.TestMetric(1, "foo"),
|
|
||||||
testutil.TestMetric(1, "bar"),
|
|
||||||
testutil.TestMetric(1, "baz"),
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedNames := []string{
|
|
||||||
"fuz",
|
|
||||||
"baz",
|
|
||||||
"baz",
|
|
||||||
}
|
|
||||||
rfp := NewTestRunningProcessor()
|
|
||||||
filteredMetrics := rfp.Apply(inmetrics...)
|
|
||||||
|
|
||||||
actualNames := []string{
|
|
||||||
filteredMetrics[0].Name(),
|
|
||||||
filteredMetrics[1].Name(),
|
|
||||||
filteredMetrics[2].Name(),
|
|
||||||
}
|
|
||||||
assert.Equal(t, expectedNames, actualNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRunningProcessor_WithNameDrop(t *testing.T) {
|
|
||||||
inmetrics := []telegraf.Metric{
|
|
||||||
testutil.TestMetric(1, "foo"),
|
|
||||||
testutil.TestMetric(1, "bar"),
|
|
||||||
testutil.TestMetric(1, "baz"),
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedNames := []string{
|
|
||||||
"foo",
|
|
||||||
"baz",
|
|
||||||
"baz",
|
|
||||||
}
|
|
||||||
rfp := NewTestRunningProcessor()
|
|
||||||
|
|
||||||
rfp.Config.Filter.NameDrop = []string{"foo"}
|
|
||||||
assert.NoError(t, rfp.Config.Filter.Compile())
|
|
||||||
|
|
||||||
filteredMetrics := rfp.Apply(inmetrics...)
|
|
||||||
|
|
||||||
actualNames := []string{
|
|
||||||
filteredMetrics[0].Name(),
|
|
||||||
filteredMetrics[1].Name(),
|
|
||||||
filteredMetrics[2].Name(),
|
|
||||||
}
|
|
||||||
assert.Equal(t, expectedNames, actualNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRunningProcessor_DroppedMetric(t *testing.T) {
|
|
||||||
inmetrics := []telegraf.Metric{
|
|
||||||
testutil.TestMetric(1, "dropme"),
|
|
||||||
testutil.TestMetric(1, "foo"),
|
|
||||||
testutil.TestMetric(1, "bar"),
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedNames := []string{
|
|
||||||
"fuz",
|
|
||||||
"baz",
|
|
||||||
}
|
|
||||||
rfp := NewTestRunningProcessor()
|
|
||||||
filteredMetrics := rfp.Apply(inmetrics...)
|
|
||||||
|
|
||||||
actualNames := []string{
|
|
||||||
filteredMetrics[0].Name(),
|
|
||||||
filteredMetrics[1].Name(),
|
|
||||||
}
|
|
||||||
assert.Equal(t, expectedNames, actualNames)
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/influxdata/wlog"
|
"github.com/influxdata/wlog"
|
||||||
)
|
)
|
||||||
@@ -20,18 +19,17 @@ type telegrafLog struct {
|
|||||||
writer io.Writer
|
writer io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *telegrafLog) Write(b []byte) (n int, err error) {
|
func (t *telegrafLog) Write(p []byte) (n int, err error) {
|
||||||
return t.writer.Write(append([]byte(time.Now().UTC().Format(time.RFC3339)+" "), b...))
|
return t.writer.Write(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetupLogging configures the logging output.
|
// SetupLogging configures the logging output.
|
||||||
// debug will set the log level to DEBUG
|
// debug will set the log level to DEBUG
|
||||||
// quiet will set the log level to ERROR
|
// quiet will set the log level to ERROR
|
||||||
// logfile will direct the logging output to a file. Empty string is
|
// logfile will direct the logging output to a file. Empty string is
|
||||||
// interpreted as stderr. If there is an error opening the file the
|
// interpreted as stdout. If there is an error opening the file the
|
||||||
// logger will fallback to stderr.
|
// logger will fallback to stdout.
|
||||||
func SetupLogging(debug, quiet bool, logfile string) {
|
func SetupLogging(debug, quiet bool, logfile string) {
|
||||||
log.SetFlags(0)
|
|
||||||
if debug {
|
if debug {
|
||||||
wlog.SetLevel(wlog.DEBUG)
|
wlog.SetLevel(wlog.DEBUG)
|
||||||
}
|
}
|
||||||
@@ -43,17 +41,17 @@ func SetupLogging(debug, quiet bool, logfile string) {
|
|||||||
if logfile != "" {
|
if logfile != "" {
|
||||||
if _, err := os.Stat(logfile); os.IsNotExist(err) {
|
if _, err := os.Stat(logfile); os.IsNotExist(err) {
|
||||||
if oFile, err = os.Create(logfile); err != nil {
|
if oFile, err = os.Create(logfile); err != nil {
|
||||||
log.Printf("E! Unable to create %s (%s), using stderr", logfile, err)
|
log.Printf("E! Unable to create %s (%s), using stdout", logfile, err)
|
||||||
oFile = os.Stderr
|
oFile = os.Stdout
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if oFile, err = os.OpenFile(logfile, os.O_APPEND|os.O_WRONLY, os.ModeAppend); err != nil {
|
if oFile, err = os.OpenFile(logfile, os.O_APPEND|os.O_WRONLY, os.ModeAppend); err != nil {
|
||||||
log.Printf("E! Unable to append to %s (%s), using stderr", logfile, err)
|
log.Printf("E! Unable to append to %s (%s), using stdout", logfile, err)
|
||||||
oFile = os.Stderr
|
oFile = os.Stdout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
oFile = os.Stderr
|
oFile = os.Stdout
|
||||||
}
|
}
|
||||||
|
|
||||||
log.SetOutput(newTelegrafWriter(oFile))
|
log.SetOutput(newTelegrafWriter(oFile))
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
package logger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestWriteLogToFile(t *testing.T) {
|
|
||||||
tmpfile, err := ioutil.TempFile("", "")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
defer func() { os.Remove(tmpfile.Name()) }()
|
|
||||||
|
|
||||||
SetupLogging(false, false, tmpfile.Name())
|
|
||||||
log.Printf("I! TEST")
|
|
||||||
log.Printf("D! TEST") // <- should be ignored
|
|
||||||
|
|
||||||
f, err := ioutil.ReadFile(tmpfile.Name())
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, f[19:], []byte("Z I! TEST\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDebugWriteLogToFile(t *testing.T) {
|
|
||||||
tmpfile, err := ioutil.TempFile("", "")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
defer func() { os.Remove(tmpfile.Name()) }()
|
|
||||||
|
|
||||||
SetupLogging(true, false, tmpfile.Name())
|
|
||||||
log.Printf("D! TEST")
|
|
||||||
|
|
||||||
f, err := ioutil.ReadFile(tmpfile.Name())
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, f[19:], []byte("Z D! TEST\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestErrorWriteLogToFile(t *testing.T) {
|
|
||||||
tmpfile, err := ioutil.TempFile("", "")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
defer func() { os.Remove(tmpfile.Name()) }()
|
|
||||||
|
|
||||||
SetupLogging(false, true, tmpfile.Name())
|
|
||||||
log.Printf("E! TEST")
|
|
||||||
log.Printf("I! TEST") // <- should be ignored
|
|
||||||
|
|
||||||
f, err := ioutil.ReadFile(tmpfile.Name())
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, f[19:], []byte("Z E! TEST\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkTelegrafLogWrite(b *testing.B) {
|
|
||||||
var msg = []byte("test")
|
|
||||||
var buf bytes.Buffer
|
|
||||||
w := newTelegrafWriter(&buf)
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
buf.Reset()
|
|
||||||
w.Write(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
155
metric.go
155
metric.go
@@ -3,7 +3,6 @@ package telegraf
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
// TODO remove
|
|
||||||
"github.com/influxdata/influxdb/client/v2"
|
"github.com/influxdata/influxdb/client/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,44 +18,128 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Metric interface {
|
type Metric interface {
|
||||||
Serialize() []byte
|
// Name returns the measurement name of the metric
|
||||||
String() string // convenience function for string(Serialize())
|
|
||||||
Copy() Metric
|
|
||||||
// Split will attempt to return multiple metrics with the same timestamp
|
|
||||||
// whose string representations are no longer than maxSize.
|
|
||||||
// Metrics with a single field may exceed the requested size.
|
|
||||||
Split(maxSize int) []Metric
|
|
||||||
|
|
||||||
// Tag functions
|
|
||||||
HasTag(key string) bool
|
|
||||||
AddTag(key, value string)
|
|
||||||
RemoveTag(key string)
|
|
||||||
|
|
||||||
// Field functions
|
|
||||||
HasField(key string) bool
|
|
||||||
AddField(key string, value interface{})
|
|
||||||
RemoveField(key string) error
|
|
||||||
|
|
||||||
// Name functions
|
|
||||||
SetName(name string)
|
|
||||||
SetPrefix(prefix string)
|
|
||||||
SetSuffix(suffix string)
|
|
||||||
|
|
||||||
// Getting data structure functions
|
|
||||||
Name() string
|
Name() string
|
||||||
Tags() map[string]string
|
|
||||||
Fields() map[string]interface{}
|
|
||||||
Time() time.Time
|
|
||||||
UnixNano() int64
|
|
||||||
Type() ValueType
|
|
||||||
Len() int // returns the length of the serialized metric, including newline
|
|
||||||
HashID() uint64
|
|
||||||
|
|
||||||
// aggregator things:
|
// Name returns the tags associated with the metric
|
||||||
SetAggregate(bool)
|
Tags() map[string]string
|
||||||
IsAggregate() bool
|
|
||||||
|
// Time return the timestamp for the metric
|
||||||
|
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() int64
|
||||||
|
|
||||||
|
// Fields returns the fields for the metric
|
||||||
|
Fields() map[string]interface{}
|
||||||
|
|
||||||
|
// String returns a line-protocol string of the metric
|
||||||
|
String() string
|
||||||
|
|
||||||
|
// PrecisionString returns a line-protocol string of the metric, at precision
|
||||||
|
PrecisionString(precison string) string
|
||||||
|
|
||||||
// Point returns a influxdb client.Point object
|
// Point returns a influxdb client.Point object
|
||||||
// TODO remove this function
|
|
||||||
Point() *client.Point
|
Point() *client.Point
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// metric is a wrapper of the influxdb client.Point struct
|
||||||
|
type metric struct {
|
||||||
|
pt *client.Point
|
||||||
|
|
||||||
|
mType ValueType
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMetric returns an untyped metric.
|
||||||
|
func NewMetric(
|
||||||
|
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: 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *metric) Name() string {
|
||||||
|
return m.pt.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *metric) Tags() map[string]string {
|
||||||
|
return m.pt.Tags()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *metric) Time() time.Time {
|
||||||
|
return m.pt.Time()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *metric) Type() ValueType {
|
||||||
|
return m.mType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *metric) UnixNano() int64 {
|
||||||
|
return m.pt.UnixNano()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *metric) Fields() map[string]interface{} {
|
||||||
|
return m.pt.Fields()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *metric) String() string {
|
||||||
|
return m.pt.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *metric) PrecisionString(precison string) string {
|
||||||
|
return m.pt.PrecisionString(precison)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *metric) Point() *client.Point {
|
||||||
|
return m.pt
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
package metric
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// escaper is for escaping:
|
|
||||||
// - tag keys
|
|
||||||
// - tag values
|
|
||||||
// - field keys
|
|
||||||
// see https://docs.influxdata.com/influxdb/v1.0/write_protocols/line_protocol_tutorial/#special-characters-and-keywords
|
|
||||||
escaper = strings.NewReplacer(`,`, `\,`, `"`, `\"`, ` `, `\ `, `=`, `\=`)
|
|
||||||
unEscaper = strings.NewReplacer(`\,`, `,`, `\"`, `"`, `\ `, ` `, `\=`, `=`)
|
|
||||||
|
|
||||||
// nameEscaper is for escaping measurement names only.
|
|
||||||
// see https://docs.influxdata.com/influxdb/v1.0/write_protocols/line_protocol_tutorial/#special-characters-and-keywords
|
|
||||||
nameEscaper = strings.NewReplacer(`,`, `\,`, ` `, `\ `)
|
|
||||||
nameUnEscaper = strings.NewReplacer(`\,`, `,`, `\ `, ` `)
|
|
||||||
|
|
||||||
// stringFieldEscaper is for escaping string field values only.
|
|
||||||
// see https://docs.influxdata.com/influxdb/v1.0/write_protocols/line_protocol_tutorial/#special-characters-and-keywords
|
|
||||||
stringFieldEscaper = strings.NewReplacer(`"`, `\"`)
|
|
||||||
stringFieldUnEscaper = strings.NewReplacer(`\"`, `"`)
|
|
||||||
)
|
|
||||||
|
|
||||||
func escape(s string, t string) string {
|
|
||||||
switch t {
|
|
||||||
case "fieldkey", "tagkey", "tagval":
|
|
||||||
return escaper.Replace(s)
|
|
||||||
case "name":
|
|
||||||
return nameEscaper.Replace(s)
|
|
||||||
case "fieldval":
|
|
||||||
return stringFieldEscaper.Replace(s)
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func unescape(s string, t string) string {
|
|
||||||
switch t {
|
|
||||||
case "fieldkey", "tagkey", "tagval":
|
|
||||||
return unEscaper.Replace(s)
|
|
||||||
case "name":
|
|
||||||
return nameUnEscaper.Replace(s)
|
|
||||||
case "fieldval":
|
|
||||||
return stringFieldUnEscaper.Replace(s)
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package metric
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// parseIntBytes is a zero-alloc wrapper around strconv.ParseInt.
|
|
||||||
func parseIntBytes(b []byte, base int, bitSize int) (i int64, err error) {
|
|
||||||
s := unsafeBytesToString(b)
|
|
||||||
return strconv.ParseInt(s, base, bitSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseFloatBytes is a zero-alloc wrapper around strconv.ParseFloat.
|
|
||||||
func parseFloatBytes(b []byte, bitSize int) (float64, error) {
|
|
||||||
s := unsafeBytesToString(b)
|
|
||||||
return strconv.ParseFloat(s, bitSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseBoolBytes is a zero-alloc wrapper around strconv.ParseBool.
|
|
||||||
func parseBoolBytes(b []byte) (bool, error) {
|
|
||||||
return strconv.ParseBool(unsafeBytesToString(b))
|
|
||||||
}
|
|
||||||
|
|
||||||
// unsafeBytesToString converts a []byte to a string without a heap allocation.
|
|
||||||
//
|
|
||||||
// It is unsafe, and is intended to prepare input to short-lived functions
|
|
||||||
// that require strings.
|
|
||||||
func unsafeBytesToString(in []byte) string {
|
|
||||||
src := *(*reflect.SliceHeader)(unsafe.Pointer(&in))
|
|
||||||
dst := reflect.StringHeader{
|
|
||||||
Data: src.Data,
|
|
||||||
Len: src.Len,
|
|
||||||
}
|
|
||||||
s := *(*string)(unsafe.Pointer(&dst))
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
package metric
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"testing"
|
|
||||||
"testing/quick"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParseIntBytesEquivalenceFuzz(t *testing.T) {
|
|
||||||
f := func(b []byte, base int, bitSize int) bool {
|
|
||||||
exp, expErr := strconv.ParseInt(string(b), base, bitSize)
|
|
||||||
got, gotErr := parseIntBytes(b, base, bitSize)
|
|
||||||
|
|
||||||
return exp == got && checkErrs(expErr, gotErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &quick.Config{
|
|
||||||
MaxCount: 10000,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := quick.Check(f, cfg); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseIntBytesValid64bitBase10EquivalenceFuzz(t *testing.T) {
|
|
||||||
buf := []byte{}
|
|
||||||
f := func(n int64) bool {
|
|
||||||
buf = strconv.AppendInt(buf[:0], n, 10)
|
|
||||||
|
|
||||||
exp, expErr := strconv.ParseInt(string(buf), 10, 64)
|
|
||||||
got, gotErr := parseIntBytes(buf, 10, 64)
|
|
||||||
|
|
||||||
return exp == got && checkErrs(expErr, gotErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &quick.Config{
|
|
||||||
MaxCount: 10000,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := quick.Check(f, cfg); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseFloatBytesEquivalenceFuzz(t *testing.T) {
|
|
||||||
f := func(b []byte, bitSize int) bool {
|
|
||||||
exp, expErr := strconv.ParseFloat(string(b), bitSize)
|
|
||||||
got, gotErr := parseFloatBytes(b, bitSize)
|
|
||||||
|
|
||||||
return exp == got && checkErrs(expErr, gotErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &quick.Config{
|
|
||||||
MaxCount: 10000,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := quick.Check(f, cfg); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseFloatBytesValid64bitEquivalenceFuzz(t *testing.T) {
|
|
||||||
buf := []byte{}
|
|
||||||
f := func(n float64) bool {
|
|
||||||
buf = strconv.AppendFloat(buf[:0], n, 'f', -1, 64)
|
|
||||||
|
|
||||||
exp, expErr := strconv.ParseFloat(string(buf), 64)
|
|
||||||
got, gotErr := parseFloatBytes(buf, 64)
|
|
||||||
|
|
||||||
return exp == got && checkErrs(expErr, gotErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &quick.Config{
|
|
||||||
MaxCount: 10000,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := quick.Check(f, cfg); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseBoolBytesEquivalence(t *testing.T) {
|
|
||||||
var buf []byte
|
|
||||||
for _, s := range []string{"1", "t", "T", "TRUE", "true", "True", "0", "f", "F", "FALSE", "false", "False", "fail", "TrUe", "FAlSE", "numbers", ""} {
|
|
||||||
buf = append(buf[:0], s...)
|
|
||||||
|
|
||||||
exp, expErr := strconv.ParseBool(s)
|
|
||||||
got, gotErr := parseBoolBytes(buf)
|
|
||||||
|
|
||||||
if got != exp || !checkErrs(expErr, gotErr) {
|
|
||||||
t.Errorf("Failed to parse boolean value %q correctly: wanted (%t, %v), got (%t, %v)", s, exp, expErr, got, gotErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkErrs(a, b error) bool {
|
|
||||||
if (a == nil) != (b == nil) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return a == nil || a.Error() == b.Error()
|
|
||||||
}
|
|
||||||
546
metric/metric.go
546
metric/metric.go
@@ -1,546 +0,0 @@
|
|||||||
package metric
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"hash/fnv"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
|
||||||
|
|
||||||
// TODO remove
|
|
||||||
"github.com/influxdata/influxdb/client/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
const MaxInt = int(^uint(0) >> 1)
|
|
||||||
|
|
||||||
func New(
|
|
||||||
name string,
|
|
||||||
tags map[string]string,
|
|
||||||
fields map[string]interface{},
|
|
||||||
t time.Time,
|
|
||||||
mType ...telegraf.ValueType,
|
|
||||||
) (telegraf.Metric, error) {
|
|
||||||
if len(fields) == 0 {
|
|
||||||
return nil, fmt.Errorf("Metric cannot be made without any fields")
|
|
||||||
}
|
|
||||||
if len(name) == 0 {
|
|
||||||
return nil, fmt.Errorf("Metric cannot be made with an empty name")
|
|
||||||
}
|
|
||||||
|
|
||||||
var thisType telegraf.ValueType
|
|
||||||
if len(mType) > 0 {
|
|
||||||
thisType = mType[0]
|
|
||||||
} else {
|
|
||||||
thisType = telegraf.Untyped
|
|
||||||
}
|
|
||||||
|
|
||||||
m := &metric{
|
|
||||||
name: []byte(escape(name, "name")),
|
|
||||||
t: []byte(fmt.Sprint(t.UnixNano())),
|
|
||||||
nsec: t.UnixNano(),
|
|
||||||
mType: thisType,
|
|
||||||
}
|
|
||||||
|
|
||||||
// pre-allocate exact size of the tags slice
|
|
||||||
taglen := 0
|
|
||||||
for k, v := range tags {
|
|
||||||
// TODO check that length of tag key & value are > 0
|
|
||||||
taglen += 2 + len(escape(k, "tagkey")) + len(escape(v, "tagval"))
|
|
||||||
}
|
|
||||||
m.tags = make([]byte, taglen)
|
|
||||||
|
|
||||||
i := 0
|
|
||||||
for k, v := range tags {
|
|
||||||
m.tags[i] = ','
|
|
||||||
i++
|
|
||||||
i += copy(m.tags[i:], escape(k, "tagkey"))
|
|
||||||
m.tags[i] = '='
|
|
||||||
i++
|
|
||||||
i += copy(m.tags[i:], escape(v, "tagval"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// pre-allocate capacity of the fields slice
|
|
||||||
fieldlen := 0
|
|
||||||
for k, _ := range fields {
|
|
||||||
// 10 bytes is completely arbitrary, but will at least prevent some
|
|
||||||
// amount of allocations. There's a small possibility this will create
|
|
||||||
// slightly more allocations for a metric that has many short fields.
|
|
||||||
fieldlen += len(k) + 10
|
|
||||||
}
|
|
||||||
m.fields = make([]byte, 0, fieldlen)
|
|
||||||
|
|
||||||
i = 0
|
|
||||||
for k, v := range fields {
|
|
||||||
if i != 0 {
|
|
||||||
m.fields = append(m.fields, ',')
|
|
||||||
}
|
|
||||||
m.fields = appendField(m.fields, k, v)
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// indexUnescapedByte finds the index of the first byte equal to b in buf that
|
|
||||||
// is not escaped. Returns -1 if not found.
|
|
||||||
func indexUnescapedByte(buf []byte, b byte) int {
|
|
||||||
var keyi int
|
|
||||||
for {
|
|
||||||
i := bytes.IndexByte(buf[keyi:], b)
|
|
||||||
if i == -1 {
|
|
||||||
return -1
|
|
||||||
} else if i == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
keyi += i
|
|
||||||
if countBackslashes(buf, keyi-1)%2 == 0 {
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
keyi++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return keyi
|
|
||||||
}
|
|
||||||
|
|
||||||
// countBackslashes counts the number of preceding backslashes starting at
|
|
||||||
// the 'start' index.
|
|
||||||
func countBackslashes(buf []byte, index int) int {
|
|
||||||
var count int
|
|
||||||
for {
|
|
||||||
if index < 0 {
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
if buf[index] == '\\' {
|
|
||||||
count++
|
|
||||||
index--
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
type metric struct {
|
|
||||||
name []byte
|
|
||||||
tags []byte
|
|
||||||
fields []byte
|
|
||||||
t []byte
|
|
||||||
|
|
||||||
mType telegraf.ValueType
|
|
||||||
aggregate bool
|
|
||||||
|
|
||||||
// cached values for reuse in "get" functions
|
|
||||||
hashID uint64
|
|
||||||
nsec int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) Point() *client.Point {
|
|
||||||
c, _ := client.NewPoint(m.Name(), m.Tags(), m.Fields(), m.Time())
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) String() string {
|
|
||||||
return string(m.name) + string(m.tags) + " " + string(m.fields) + " " + string(m.t) + "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) SetAggregate(b bool) {
|
|
||||||
m.aggregate = b
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) IsAggregate() bool {
|
|
||||||
return m.aggregate
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) Type() telegraf.ValueType {
|
|
||||||
return m.mType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) Len() int {
|
|
||||||
// 3 is for 2 spaces surrounding the fields array + newline at the end.
|
|
||||||
return len(m.name) + len(m.tags) + len(m.fields) + len(m.t) + 3
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) Serialize() []byte {
|
|
||||||
tmp := make([]byte, m.Len())
|
|
||||||
i := 0
|
|
||||||
i += copy(tmp[i:], m.name)
|
|
||||||
i += copy(tmp[i:], m.tags)
|
|
||||||
tmp[i] = ' '
|
|
||||||
i++
|
|
||||||
i += copy(tmp[i:], m.fields)
|
|
||||||
tmp[i] = ' '
|
|
||||||
i++
|
|
||||||
i += copy(tmp[i:], m.t)
|
|
||||||
tmp[i] = '\n'
|
|
||||||
return tmp
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) Split(maxSize int) []telegraf.Metric {
|
|
||||||
if m.Len() < maxSize {
|
|
||||||
return []telegraf.Metric{m}
|
|
||||||
}
|
|
||||||
var out []telegraf.Metric
|
|
||||||
|
|
||||||
// constant number of bytes for each metric (in addition to field bytes)
|
|
||||||
constant := len(m.name) + len(m.tags) + len(m.t) + 3
|
|
||||||
// currently selected fields
|
|
||||||
fields := make([]byte, 0, maxSize)
|
|
||||||
|
|
||||||
i := 0
|
|
||||||
for {
|
|
||||||
if i >= len(m.fields) {
|
|
||||||
// hit the end of the field byte slice
|
|
||||||
if len(fields) > 0 {
|
|
||||||
out = append(out, copyWith(m.name, m.tags, fields, m.t))
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// find the end of the next field
|
|
||||||
j := indexUnescapedByte(m.fields[i:], ',')
|
|
||||||
if j == -1 {
|
|
||||||
j = len(m.fields)
|
|
||||||
} else {
|
|
||||||
j += i
|
|
||||||
}
|
|
||||||
|
|
||||||
// if true, then we need to create a metric _not_ including the currently
|
|
||||||
// selected field
|
|
||||||
if len(m.fields[i:j])+len(fields)+constant > maxSize {
|
|
||||||
// if false, then we'll create a metric including the currently
|
|
||||||
// selected field anyways. This means that the given maxSize is too
|
|
||||||
// small for a single field to fit.
|
|
||||||
if len(fields) > 0 {
|
|
||||||
out = append(out, copyWith(m.name, m.tags, fields, m.t))
|
|
||||||
}
|
|
||||||
|
|
||||||
fields = make([]byte, 0, maxSize)
|
|
||||||
}
|
|
||||||
if len(fields) > 0 {
|
|
||||||
fields = append(fields, ',')
|
|
||||||
}
|
|
||||||
fields = append(fields, m.fields[i:j]...)
|
|
||||||
|
|
||||||
i = j + 1
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) Fields() map[string]interface{} {
|
|
||||||
fieldMap := map[string]interface{}{}
|
|
||||||
i := 0
|
|
||||||
for {
|
|
||||||
if i >= len(m.fields) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// end index of field key
|
|
||||||
i1 := indexUnescapedByte(m.fields[i:], '=')
|
|
||||||
if i1 == -1 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// start index of field value
|
|
||||||
i2 := i1 + 1
|
|
||||||
|
|
||||||
// end index of field value
|
|
||||||
var i3 int
|
|
||||||
if m.fields[i:][i2] == '"' {
|
|
||||||
i3 = indexUnescapedByte(m.fields[i:][i2+1:], '"')
|
|
||||||
if i3 == -1 {
|
|
||||||
i3 = len(m.fields[i:])
|
|
||||||
}
|
|
||||||
i3 += i2 + 2 // increment index to the comma
|
|
||||||
} else {
|
|
||||||
i3 = indexUnescapedByte(m.fields[i:], ',')
|
|
||||||
if i3 == -1 {
|
|
||||||
i3 = len(m.fields[i:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch m.fields[i:][i2] {
|
|
||||||
case '"':
|
|
||||||
// string field
|
|
||||||
fieldMap[unescape(string(m.fields[i:][0:i1]), "fieldkey")] = unescape(string(m.fields[i:][i2+1:i3-1]), "fieldval")
|
|
||||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
|
||||||
// number field
|
|
||||||
switch m.fields[i:][i3-1] {
|
|
||||||
case 'i':
|
|
||||||
// integer field
|
|
||||||
n, err := parseIntBytes(m.fields[i:][i2:i3-1], 10, 64)
|
|
||||||
if err == nil {
|
|
||||||
fieldMap[unescape(string(m.fields[i:][0:i1]), "fieldkey")] = n
|
|
||||||
} else {
|
|
||||||
// TODO handle error or just ignore field silently?
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// float field
|
|
||||||
n, err := parseFloatBytes(m.fields[i:][i2:i3], 64)
|
|
||||||
if err == nil {
|
|
||||||
fieldMap[unescape(string(m.fields[i:][0:i1]), "fieldkey")] = n
|
|
||||||
} else {
|
|
||||||
// TODO handle error or just ignore field silently?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 'T', 't':
|
|
||||||
fieldMap[unescape(string(m.fields[i:][0:i1]), "fieldkey")] = true
|
|
||||||
case 'F', 'f':
|
|
||||||
fieldMap[unescape(string(m.fields[i:][0:i1]), "fieldkey")] = false
|
|
||||||
default:
|
|
||||||
// TODO handle unsupported field type
|
|
||||||
}
|
|
||||||
|
|
||||||
i += i3 + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return fieldMap
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) Tags() map[string]string {
|
|
||||||
tagMap := map[string]string{}
|
|
||||||
if len(m.tags) == 0 {
|
|
||||||
return tagMap
|
|
||||||
}
|
|
||||||
|
|
||||||
i := 0
|
|
||||||
for {
|
|
||||||
// start index of tag key
|
|
||||||
i0 := indexUnescapedByte(m.tags[i:], ',') + 1
|
|
||||||
if i0 == 0 {
|
|
||||||
// didn't find a tag start
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// end index of tag key
|
|
||||||
i1 := indexUnescapedByte(m.tags[i:], '=')
|
|
||||||
// start index of tag value
|
|
||||||
i2 := i1 + 1
|
|
||||||
// end index of tag value (starting from i2)
|
|
||||||
i3 := indexUnescapedByte(m.tags[i+i2:], ',')
|
|
||||||
if i3 == -1 {
|
|
||||||
tagMap[unescape(string(m.tags[i:][i0:i1]), "tagkey")] = unescape(string(m.tags[i:][i2:]), "tagval")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
tagMap[unescape(string(m.tags[i:][i0:i1]), "tagkey")] = unescape(string(m.tags[i:][i2:i2+i3]), "tagval")
|
|
||||||
// increment start index for the next tag
|
|
||||||
i += i2 + i3
|
|
||||||
}
|
|
||||||
|
|
||||||
return tagMap
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) Name() string {
|
|
||||||
return unescape(string(m.name), "name")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) Time() time.Time {
|
|
||||||
// assume metric has been verified already and ignore error:
|
|
||||||
if m.nsec == 0 {
|
|
||||||
m.nsec, _ = parseIntBytes(m.t, 10, 64)
|
|
||||||
}
|
|
||||||
return time.Unix(0, m.nsec)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) UnixNano() int64 {
|
|
||||||
// assume metric has been verified already and ignore error:
|
|
||||||
if m.nsec == 0 {
|
|
||||||
m.nsec, _ = parseIntBytes(m.t, 10, 64)
|
|
||||||
}
|
|
||||||
return m.nsec
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) SetName(name string) {
|
|
||||||
m.hashID = 0
|
|
||||||
m.name = []byte(nameEscaper.Replace(name))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) SetPrefix(prefix string) {
|
|
||||||
m.hashID = 0
|
|
||||||
m.name = append([]byte(nameEscaper.Replace(prefix)), m.name...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) SetSuffix(suffix string) {
|
|
||||||
m.hashID = 0
|
|
||||||
m.name = append(m.name, []byte(nameEscaper.Replace(suffix))...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) AddTag(key, value string) {
|
|
||||||
m.RemoveTag(key)
|
|
||||||
m.tags = append(m.tags, []byte(","+escape(key, "tagkey")+"="+escape(value, "tagval"))...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) HasTag(key string) bool {
|
|
||||||
i := bytes.Index(m.tags, []byte(escape(key, "tagkey")+"="))
|
|
||||||
if i == -1 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) RemoveTag(key string) {
|
|
||||||
m.hashID = 0
|
|
||||||
|
|
||||||
i := bytes.Index(m.tags, []byte(escape(key, "tagkey")+"="))
|
|
||||||
if i == -1 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tmp := m.tags[0 : i-1]
|
|
||||||
j := indexUnescapedByte(m.tags[i:], ',')
|
|
||||||
if j != -1 {
|
|
||||||
tmp = append(tmp, m.tags[i+j:]...)
|
|
||||||
}
|
|
||||||
m.tags = tmp
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) AddField(key string, value interface{}) {
|
|
||||||
m.fields = append(m.fields, ',')
|
|
||||||
m.fields = appendField(m.fields, key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) HasField(key string) bool {
|
|
||||||
i := bytes.Index(m.fields, []byte(escape(key, "tagkey")+"="))
|
|
||||||
if i == -1 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) RemoveField(key string) error {
|
|
||||||
i := bytes.Index(m.fields, []byte(escape(key, "tagkey")+"="))
|
|
||||||
if i == -1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var tmp []byte
|
|
||||||
if i != 0 {
|
|
||||||
tmp = m.fields[0 : i-1]
|
|
||||||
}
|
|
||||||
j := indexUnescapedByte(m.fields[i:], ',')
|
|
||||||
if j != -1 {
|
|
||||||
tmp = append(tmp, m.fields[i+j:]...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(tmp) == 0 {
|
|
||||||
return fmt.Errorf("Metric cannot remove final field: %s", m.fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.fields = tmp
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) Copy() telegraf.Metric {
|
|
||||||
return copyWith(m.name, m.tags, m.fields, m.t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyWith(name, tags, fields, t []byte) telegraf.Metric {
|
|
||||||
out := metric{
|
|
||||||
name: make([]byte, len(name)),
|
|
||||||
tags: make([]byte, len(tags)),
|
|
||||||
fields: make([]byte, len(fields)),
|
|
||||||
t: make([]byte, len(t)),
|
|
||||||
}
|
|
||||||
copy(out.name, name)
|
|
||||||
copy(out.tags, tags)
|
|
||||||
copy(out.fields, fields)
|
|
||||||
copy(out.t, t)
|
|
||||||
return &out
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *metric) HashID() uint64 {
|
|
||||||
if m.hashID == 0 {
|
|
||||||
h := fnv.New64a()
|
|
||||||
h.Write(m.name)
|
|
||||||
|
|
||||||
tags := m.Tags()
|
|
||||||
tmp := make([]string, len(tags))
|
|
||||||
i := 0
|
|
||||||
for k, v := range tags {
|
|
||||||
tmp[i] = k + v
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
sort.Strings(tmp)
|
|
||||||
|
|
||||||
for _, s := range tmp {
|
|
||||||
h.Write([]byte(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
m.hashID = h.Sum64()
|
|
||||||
}
|
|
||||||
return m.hashID
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendField(b []byte, k string, v interface{}) []byte {
|
|
||||||
if v == nil {
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
b = append(b, []byte(escape(k, "tagkey")+"=")...)
|
|
||||||
|
|
||||||
// check popular types first
|
|
||||||
switch v := v.(type) {
|
|
||||||
case float64:
|
|
||||||
b = strconv.AppendFloat(b, v, 'f', -1, 64)
|
|
||||||
case int64:
|
|
||||||
b = strconv.AppendInt(b, v, 10)
|
|
||||||
b = append(b, 'i')
|
|
||||||
case string:
|
|
||||||
b = append(b, '"')
|
|
||||||
b = append(b, []byte(escape(v, "fieldval"))...)
|
|
||||||
b = append(b, '"')
|
|
||||||
case bool:
|
|
||||||
b = strconv.AppendBool(b, v)
|
|
||||||
case int32:
|
|
||||||
b = strconv.AppendInt(b, int64(v), 10)
|
|
||||||
b = append(b, 'i')
|
|
||||||
case int16:
|
|
||||||
b = strconv.AppendInt(b, int64(v), 10)
|
|
||||||
b = append(b, 'i')
|
|
||||||
case int8:
|
|
||||||
b = strconv.AppendInt(b, int64(v), 10)
|
|
||||||
b = append(b, 'i')
|
|
||||||
case int:
|
|
||||||
b = strconv.AppendInt(b, int64(v), 10)
|
|
||||||
b = append(b, 'i')
|
|
||||||
case uint64:
|
|
||||||
// Cap uints above the maximum int value
|
|
||||||
var intv int64
|
|
||||||
if v <= uint64(MaxInt) {
|
|
||||||
intv = int64(v)
|
|
||||||
} else {
|
|
||||||
intv = int64(MaxInt)
|
|
||||||
}
|
|
||||||
b = strconv.AppendInt(b, intv, 10)
|
|
||||||
b = append(b, 'i')
|
|
||||||
case uint32:
|
|
||||||
b = strconv.AppendInt(b, int64(v), 10)
|
|
||||||
b = append(b, 'i')
|
|
||||||
case uint16:
|
|
||||||
b = strconv.AppendInt(b, int64(v), 10)
|
|
||||||
b = append(b, 'i')
|
|
||||||
case uint8:
|
|
||||||
b = strconv.AppendInt(b, int64(v), 10)
|
|
||||||
b = append(b, 'i')
|
|
||||||
case uint:
|
|
||||||
// Cap uints above the maximum int value
|
|
||||||
var intv int64
|
|
||||||
if v <= uint(MaxInt) {
|
|
||||||
intv = int64(v)
|
|
||||||
} else {
|
|
||||||
intv = int64(MaxInt)
|
|
||||||
}
|
|
||||||
b = strconv.AppendInt(b, intv, 10)
|
|
||||||
b = append(b, 'i')
|
|
||||||
case float32:
|
|
||||||
b = strconv.AppendFloat(b, float64(v), 'f', -1, 32)
|
|
||||||
case []byte:
|
|
||||||
b = append(b, v...)
|
|
||||||
default:
|
|
||||||
// Can't determine the type, so convert to string
|
|
||||||
b = append(b, '"')
|
|
||||||
b = append(b, []byte(escape(fmt.Sprintf("%v", v), "fieldval"))...)
|
|
||||||
b = append(b, '"')
|
|
||||||
}
|
|
||||||
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
package metric
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
|
||||||
)
|
|
||||||
|
|
||||||
// vars for making sure that the compiler doesnt optimize out the benchmarks:
|
|
||||||
var (
|
|
||||||
s string
|
|
||||||
I interface{}
|
|
||||||
tags map[string]string
|
|
||||||
fields map[string]interface{}
|
|
||||||
)
|
|
||||||
|
|
||||||
func BenchmarkNewMetric(b *testing.B) {
|
|
||||||
var mt telegraf.Metric
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
mt, _ = New("test_metric",
|
|
||||||
map[string]string{
|
|
||||||
"test_tag_1": "tag_value_1",
|
|
||||||
"test_tag_2": "tag_value_2",
|
|
||||||
"test_tag_3": "tag_value_3",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"string_field": "string",
|
|
||||||
"int_field": int64(1000),
|
|
||||||
"float_field": float64(2.1),
|
|
||||||
},
|
|
||||||
time.Now(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
s = string(mt.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkAddTag(b *testing.B) {
|
|
||||||
var mt telegraf.Metric
|
|
||||||
mt = &metric{
|
|
||||||
name: []byte("cpu"),
|
|
||||||
tags: []byte(",host=localhost"),
|
|
||||||
fields: []byte("a=101"),
|
|
||||||
t: []byte("1480614053000000000"),
|
|
||||||
}
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
mt.AddTag("foo", "bar")
|
|
||||||
}
|
|
||||||
s = string(mt.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkSplit(b *testing.B) {
|
|
||||||
var mt telegraf.Metric
|
|
||||||
mt = &metric{
|
|
||||||
name: []byte("cpu"),
|
|
||||||
tags: []byte(",host=localhost"),
|
|
||||||
fields: []byte("a=101,b=10i,c=10101,d=101010,e=42"),
|
|
||||||
t: []byte("1480614053000000000"),
|
|
||||||
}
|
|
||||||
var metrics []telegraf.Metric
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
metrics = mt.Split(60)
|
|
||||||
}
|
|
||||||
s = string(metrics[0].String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkTags(b *testing.B) {
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
var mt, _ = New("test_metric",
|
|
||||||
map[string]string{
|
|
||||||
"test_tag_1": "tag_value_1",
|
|
||||||
"test_tag_2": "tag_value_2",
|
|
||||||
"test_tag_3": "tag_value_3",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"string_field": "string",
|
|
||||||
"int_field": int64(1000),
|
|
||||||
"float_field": float64(2.1),
|
|
||||||
},
|
|
||||||
time.Now(),
|
|
||||||
)
|
|
||||||
tags = mt.Tags()
|
|
||||||
}
|
|
||||||
s = fmt.Sprint(tags)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkFields(b *testing.B) {
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
var mt, _ = New("test_metric",
|
|
||||||
map[string]string{
|
|
||||||
"test_tag_1": "tag_value_1",
|
|
||||||
"test_tag_2": "tag_value_2",
|
|
||||||
"test_tag_3": "tag_value_3",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"string_field": "string",
|
|
||||||
"int_field": int64(1000),
|
|
||||||
"float_field": float64(2.1),
|
|
||||||
},
|
|
||||||
time.Now(),
|
|
||||||
)
|
|
||||||
fields = mt.Fields()
|
|
||||||
}
|
|
||||||
s = fmt.Sprint(fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkString(b *testing.B) {
|
|
||||||
mt, _ := New("test_metric",
|
|
||||||
map[string]string{
|
|
||||||
"test_tag_1": "tag_value_1",
|
|
||||||
"test_tag_2": "tag_value_2",
|
|
||||||
"test_tag_3": "tag_value_3",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"string_field": "string",
|
|
||||||
"int_field": int64(1000),
|
|
||||||
"float_field": float64(2.1),
|
|
||||||
},
|
|
||||||
time.Now(),
|
|
||||||
)
|
|
||||||
var S string
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
S = mt.String()
|
|
||||||
}
|
|
||||||
s = S
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkSerialize(b *testing.B) {
|
|
||||||
mt, _ := New("test_metric",
|
|
||||||
map[string]string{
|
|
||||||
"test_tag_1": "tag_value_1",
|
|
||||||
"test_tag_2": "tag_value_2",
|
|
||||||
"test_tag_3": "tag_value_3",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"string_field": "string",
|
|
||||||
"int_field": int64(1000),
|
|
||||||
"float_field": float64(2.1),
|
|
||||||
},
|
|
||||||
time.Now(),
|
|
||||||
)
|
|
||||||
var B []byte
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
B = mt.Serialize()
|
|
||||||
}
|
|
||||||
s = string(B)
|
|
||||||
}
|
|
||||||
@@ -1,646 +0,0 @@
|
|||||||
package metric
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"regexp"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewMetric(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 := New("cpu", tags, fields, now)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, telegraf.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 TestNewErrors(t *testing.T) {
|
|
||||||
// creating a metric with an empty name produces an error:
|
|
||||||
m, err := New(
|
|
||||||
"",
|
|
||||||
map[string]string{
|
|
||||||
"datacenter": "us-east-1",
|
|
||||||
"mytag": "foo",
|
|
||||||
"another": "tag",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"value": float64(1),
|
|
||||||
},
|
|
||||||
time.Now(),
|
|
||||||
)
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Nil(t, m)
|
|
||||||
|
|
||||||
// creating a metric with empty fields produces an error:
|
|
||||||
m, err = New(
|
|
||||||
"foobar",
|
|
||||||
map[string]string{
|
|
||||||
"datacenter": "us-east-1",
|
|
||||||
"mytag": "foo",
|
|
||||||
"another": "tag",
|
|
||||||
},
|
|
||||||
map[string]interface{}{},
|
|
||||||
time.Now(),
|
|
||||||
)
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Nil(t, m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewMetric_Tags(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
tags := map[string]string{
|
|
||||||
"host": "localhost",
|
|
||||||
"datacenter": "us-east-1",
|
|
||||||
}
|
|
||||||
fields := map[string]interface{}{
|
|
||||||
"value": float64(1),
|
|
||||||
}
|
|
||||||
m, err := New("cpu", tags, fields, now)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.True(t, m.HasTag("host"))
|
|
||||||
assert.True(t, m.HasTag("datacenter"))
|
|
||||||
|
|
||||||
m.AddTag("newtag", "foo")
|
|
||||||
assert.True(t, m.HasTag("newtag"))
|
|
||||||
|
|
||||||
m.RemoveTag("host")
|
|
||||||
assert.False(t, m.HasTag("host"))
|
|
||||||
assert.True(t, m.HasTag("newtag"))
|
|
||||||
assert.True(t, m.HasTag("datacenter"))
|
|
||||||
|
|
||||||
m.RemoveTag("datacenter")
|
|
||||||
assert.False(t, m.HasTag("datacenter"))
|
|
||||||
assert.True(t, m.HasTag("newtag"))
|
|
||||||
assert.Equal(t, map[string]string{"newtag": "foo"}, m.Tags())
|
|
||||||
|
|
||||||
m.RemoveTag("newtag")
|
|
||||||
assert.False(t, m.HasTag("newtag"))
|
|
||||||
assert.Equal(t, map[string]string{}, m.Tags())
|
|
||||||
|
|
||||||
assert.Equal(t, "cpu value=1 "+fmt.Sprint(now.UnixNano())+"\n", m.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSerialize(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
tags := map[string]string{
|
|
||||||
"datacenter": "us-east-1",
|
|
||||||
}
|
|
||||||
fields := map[string]interface{}{
|
|
||||||
"value": float64(1),
|
|
||||||
}
|
|
||||||
m, err := New("cpu", tags, fields, now)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t,
|
|
||||||
[]byte("cpu,datacenter=us-east-1 value=1 "+fmt.Sprint(now.UnixNano())+"\n"),
|
|
||||||
m.Serialize())
|
|
||||||
|
|
||||||
m.RemoveTag("datacenter")
|
|
||||||
assert.Equal(t,
|
|
||||||
[]byte("cpu value=1 "+fmt.Sprint(now.UnixNano())+"\n"),
|
|
||||||
m.Serialize())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHashID(t *testing.T) {
|
|
||||||
m, _ := New(
|
|
||||||
"cpu",
|
|
||||||
map[string]string{
|
|
||||||
"datacenter": "us-east-1",
|
|
||||||
"mytag": "foo",
|
|
||||||
"another": "tag",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"value": float64(1),
|
|
||||||
},
|
|
||||||
time.Now(),
|
|
||||||
)
|
|
||||||
hash := m.HashID()
|
|
||||||
|
|
||||||
// adding a field doesn't change the hash:
|
|
||||||
m.AddField("foo", int64(100))
|
|
||||||
assert.Equal(t, hash, m.HashID())
|
|
||||||
|
|
||||||
// removing a non-existent tag doesn't change the hash:
|
|
||||||
m.RemoveTag("no-op")
|
|
||||||
assert.Equal(t, hash, m.HashID())
|
|
||||||
|
|
||||||
// adding a tag does change it:
|
|
||||||
m.AddTag("foo", "bar")
|
|
||||||
assert.NotEqual(t, hash, m.HashID())
|
|
||||||
hash = m.HashID()
|
|
||||||
|
|
||||||
// removing a tag also changes it:
|
|
||||||
m.RemoveTag("mytag")
|
|
||||||
assert.NotEqual(t, hash, m.HashID())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHashID_Consistency(t *testing.T) {
|
|
||||||
m, _ := New(
|
|
||||||
"cpu",
|
|
||||||
map[string]string{
|
|
||||||
"datacenter": "us-east-1",
|
|
||||||
"mytag": "foo",
|
|
||||||
"another": "tag",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"value": float64(1),
|
|
||||||
},
|
|
||||||
time.Now(),
|
|
||||||
)
|
|
||||||
hash := m.HashID()
|
|
||||||
|
|
||||||
for i := 0; i < 1000; i++ {
|
|
||||||
m2, _ := New(
|
|
||||||
"cpu",
|
|
||||||
map[string]string{
|
|
||||||
"datacenter": "us-east-1",
|
|
||||||
"mytag": "foo",
|
|
||||||
"another": "tag",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"value": float64(1),
|
|
||||||
},
|
|
||||||
time.Now(),
|
|
||||||
)
|
|
||||||
assert.Equal(t, hash, m2.HashID())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewMetric_NameModifiers(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
tags := map[string]string{}
|
|
||||||
fields := map[string]interface{}{
|
|
||||||
"value": float64(1),
|
|
||||||
}
|
|
||||||
m, err := New("cpu", tags, fields, now)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
hash := m.HashID()
|
|
||||||
suffix := fmt.Sprintf(" value=1 %d\n", now.UnixNano())
|
|
||||||
assert.Equal(t, "cpu"+suffix, m.String())
|
|
||||||
|
|
||||||
m.SetPrefix("pre_")
|
|
||||||
assert.NotEqual(t, hash, m.HashID())
|
|
||||||
hash = m.HashID()
|
|
||||||
assert.Equal(t, "pre_cpu"+suffix, m.String())
|
|
||||||
|
|
||||||
m.SetSuffix("_post")
|
|
||||||
assert.NotEqual(t, hash, m.HashID())
|
|
||||||
hash = m.HashID()
|
|
||||||
assert.Equal(t, "pre_cpu_post"+suffix, m.String())
|
|
||||||
|
|
||||||
m.SetName("mem")
|
|
||||||
assert.NotEqual(t, hash, m.HashID())
|
|
||||||
assert.Equal(t, "mem"+suffix, m.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewMetric_FieldModifiers(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
tags := map[string]string{
|
|
||||||
"host": "localhost",
|
|
||||||
}
|
|
||||||
fields := map[string]interface{}{
|
|
||||||
"value": float64(1),
|
|
||||||
}
|
|
||||||
m, err := New("cpu", tags, fields, now)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.True(t, m.HasField("value"))
|
|
||||||
assert.False(t, m.HasField("foo"))
|
|
||||||
|
|
||||||
m.AddField("newfield", "foo")
|
|
||||||
assert.True(t, m.HasField("newfield"))
|
|
||||||
|
|
||||||
assert.NoError(t, m.RemoveField("newfield"))
|
|
||||||
assert.False(t, m.HasField("newfield"))
|
|
||||||
|
|
||||||
// don't allow user to remove all fields:
|
|
||||||
assert.Error(t, m.RemoveField("value"))
|
|
||||||
|
|
||||||
m.AddField("value2", int64(101))
|
|
||||||
assert.NoError(t, m.RemoveField("value"))
|
|
||||||
assert.False(t, m.HasField("value"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewMetric_Fields(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
tags := map[string]string{
|
|
||||||
"host": "localhost",
|
|
||||||
}
|
|
||||||
fields := map[string]interface{}{
|
|
||||||
"float": float64(1),
|
|
||||||
"int": int64(1),
|
|
||||||
"bool": true,
|
|
||||||
"false": false,
|
|
||||||
"string": "test",
|
|
||||||
}
|
|
||||||
m, err := New("cpu", tags, fields, now)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, fields, m.Fields())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewMetric_Time(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
tags := map[string]string{
|
|
||||||
"host": "localhost",
|
|
||||||
}
|
|
||||||
fields := map[string]interface{}{
|
|
||||||
"float": float64(1),
|
|
||||||
"int": int64(1),
|
|
||||||
"bool": true,
|
|
||||||
"false": false,
|
|
||||||
"string": "test",
|
|
||||||
}
|
|
||||||
m, err := New("cpu", tags, fields, now)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
m = m.Copy()
|
|
||||||
m2 := m.Copy()
|
|
||||||
|
|
||||||
assert.Equal(t, now.UnixNano(), m.Time().UnixNano())
|
|
||||||
assert.Equal(t, now.UnixNano(), m2.UnixNano())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewMetric_Copy(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
tags := map[string]string{}
|
|
||||||
fields := map[string]interface{}{
|
|
||||||
"float": float64(1),
|
|
||||||
}
|
|
||||||
m, err := New("cpu", tags, fields, now)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
m2 := m.Copy()
|
|
||||||
|
|
||||||
assert.Equal(t,
|
|
||||||
fmt.Sprintf("cpu float=1 %d\n", now.UnixNano()),
|
|
||||||
m.String())
|
|
||||||
m.AddTag("host", "localhost")
|
|
||||||
assert.Equal(t,
|
|
||||||
fmt.Sprintf("cpu,host=localhost float=1 %d\n", now.UnixNano()),
|
|
||||||
m.String())
|
|
||||||
|
|
||||||
assert.Equal(t,
|
|
||||||
fmt.Sprintf("cpu float=1 %d\n", now.UnixNano()),
|
|
||||||
m2.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewMetric_AllTypes(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
tags := map[string]string{}
|
|
||||||
fields := map[string]interface{}{
|
|
||||||
"float64": float64(1),
|
|
||||||
"float32": float32(1),
|
|
||||||
"int64": int64(1),
|
|
||||||
"int32": int32(1),
|
|
||||||
"int16": int16(1),
|
|
||||||
"int8": int8(1),
|
|
||||||
"int": int(1),
|
|
||||||
"uint64": uint64(1),
|
|
||||||
"uint32": uint32(1),
|
|
||||||
"uint16": uint16(1),
|
|
||||||
"uint8": uint8(1),
|
|
||||||
"uint": uint(1),
|
|
||||||
"bytes": []byte("foo"),
|
|
||||||
"nil": nil,
|
|
||||||
"maxuint64": uint64(MaxInt) + 10,
|
|
||||||
"maxuint": uint(MaxInt) + 10,
|
|
||||||
"unsupported": []int{1, 2},
|
|
||||||
}
|
|
||||||
m, err := New("cpu", tags, fields, now)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Contains(t, m.String(), "float64=1")
|
|
||||||
assert.Contains(t, m.String(), "float32=1")
|
|
||||||
assert.Contains(t, m.String(), "int64=1i")
|
|
||||||
assert.Contains(t, m.String(), "int32=1i")
|
|
||||||
assert.Contains(t, m.String(), "int16=1i")
|
|
||||||
assert.Contains(t, m.String(), "int8=1i")
|
|
||||||
assert.Contains(t, m.String(), "int=1i")
|
|
||||||
assert.Contains(t, m.String(), "uint64=1i")
|
|
||||||
assert.Contains(t, m.String(), "uint32=1i")
|
|
||||||
assert.Contains(t, m.String(), "uint16=1i")
|
|
||||||
assert.Contains(t, m.String(), "uint8=1i")
|
|
||||||
assert.Contains(t, m.String(), "uint=1i")
|
|
||||||
assert.NotContains(t, m.String(), "nil")
|
|
||||||
assert.Contains(t, m.String(), fmt.Sprintf("maxuint64=%di", MaxInt))
|
|
||||||
assert.Contains(t, m.String(), fmt.Sprintf("maxuint=%di", MaxInt))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIndexUnescapedByte(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
in []byte
|
|
||||||
b byte
|
|
||||||
expected int
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
in: []byte(`foobar`),
|
|
||||||
b: 'b',
|
|
||||||
expected: 3,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: []byte(`foo\bar`),
|
|
||||||
b: 'b',
|
|
||||||
expected: -1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: []byte(`foo\\bar`),
|
|
||||||
b: 'b',
|
|
||||||
expected: 5,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: []byte(`foobar`),
|
|
||||||
b: 'f',
|
|
||||||
expected: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: []byte(`foobar`),
|
|
||||||
b: 'r',
|
|
||||||
expected: 5,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: []byte(`\foobar`),
|
|
||||||
b: 'f',
|
|
||||||
expected: -1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
got := indexUnescapedByte(test.in, test.b)
|
|
||||||
assert.Equal(t, test.expected, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 := New("cpu", tags, fields, now, telegraf.Gauge)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, telegraf.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 := New("cpu", tags, fields, now, telegraf.Counter)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, telegraf.Counter, 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())
|
|
||||||
}
|
|
||||||
|
|
||||||
// test splitting metric into various max lengths
|
|
||||||
func TestSplitMetric(t *testing.T) {
|
|
||||||
now := time.Unix(0, 1480940990034083306)
|
|
||||||
tags := map[string]string{
|
|
||||||
"host": "localhost",
|
|
||||||
}
|
|
||||||
fields := map[string]interface{}{
|
|
||||||
"float": float64(100001),
|
|
||||||
"int": int64(100001),
|
|
||||||
"bool": true,
|
|
||||||
"false": false,
|
|
||||||
"string": "test",
|
|
||||||
}
|
|
||||||
m, err := New("cpu", tags, fields, now)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
split80 := m.Split(80)
|
|
||||||
assert.Len(t, split80, 2)
|
|
||||||
|
|
||||||
split70 := m.Split(70)
|
|
||||||
assert.Len(t, split70, 3)
|
|
||||||
|
|
||||||
split60 := m.Split(60)
|
|
||||||
assert.Len(t, split60, 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
// test splitting metric into various max lengths
|
|
||||||
// use a simple regex check to verify that the split metrics are valid
|
|
||||||
func TestSplitMetric_RegexVerify(t *testing.T) {
|
|
||||||
now := time.Unix(0, 1480940990034083306)
|
|
||||||
tags := map[string]string{
|
|
||||||
"host": "localhost",
|
|
||||||
}
|
|
||||||
fields := map[string]interface{}{
|
|
||||||
"foo": float64(98934259085),
|
|
||||||
"bar": float64(19385292),
|
|
||||||
"number": float64(19385292),
|
|
||||||
"another": float64(19385292),
|
|
||||||
"n": float64(19385292),
|
|
||||||
}
|
|
||||||
m, err := New("cpu", tags, fields, now)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
// verification regex
|
|
||||||
re := regexp.MustCompile(`cpu,host=localhost \w+=\d+(,\w+=\d+)* 1480940990034083306`)
|
|
||||||
|
|
||||||
split90 := m.Split(90)
|
|
||||||
assert.Len(t, split90, 2)
|
|
||||||
for _, splitM := range split90 {
|
|
||||||
assert.True(t, re.Match(splitM.Serialize()), splitM.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
split70 := m.Split(70)
|
|
||||||
assert.Len(t, split70, 3)
|
|
||||||
for _, splitM := range split70 {
|
|
||||||
assert.True(t, re.Match(splitM.Serialize()), splitM.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
split20 := m.Split(20)
|
|
||||||
assert.Len(t, split20, 5)
|
|
||||||
for _, splitM := range split20 {
|
|
||||||
assert.True(t, re.Match(splitM.Serialize()), splitM.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// test splitting metric even when given length is shorter than
|
|
||||||
// shortest possible length
|
|
||||||
// Split should split metric as short as possible, ie, 1 field per metric
|
|
||||||
func TestSplitMetric_TooShort(t *testing.T) {
|
|
||||||
now := time.Unix(0, 1480940990034083306)
|
|
||||||
tags := map[string]string{
|
|
||||||
"host": "localhost",
|
|
||||||
}
|
|
||||||
fields := map[string]interface{}{
|
|
||||||
"float": float64(100001),
|
|
||||||
"int": int64(100001),
|
|
||||||
"bool": true,
|
|
||||||
"false": false,
|
|
||||||
"string": "test",
|
|
||||||
}
|
|
||||||
m, err := New("cpu", tags, fields, now)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
split := m.Split(10)
|
|
||||||
assert.Len(t, split, 5)
|
|
||||||
strings := make([]string, 5)
|
|
||||||
for i, splitM := range split {
|
|
||||||
strings[i] = splitM.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Contains(t, strings, "cpu,host=localhost float=100001 1480940990034083306\n")
|
|
||||||
assert.Contains(t, strings, "cpu,host=localhost int=100001i 1480940990034083306\n")
|
|
||||||
assert.Contains(t, strings, "cpu,host=localhost bool=true 1480940990034083306\n")
|
|
||||||
assert.Contains(t, strings, "cpu,host=localhost false=false 1480940990034083306\n")
|
|
||||||
assert.Contains(t, strings, "cpu,host=localhost string=\"test\" 1480940990034083306\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSplitMetric_NoOp(t *testing.T) {
|
|
||||||
now := time.Unix(0, 1480940990034083306)
|
|
||||||
tags := map[string]string{
|
|
||||||
"host": "localhost",
|
|
||||||
}
|
|
||||||
fields := map[string]interface{}{
|
|
||||||
"float": float64(100001),
|
|
||||||
"int": int64(100001),
|
|
||||||
"bool": true,
|
|
||||||
"false": false,
|
|
||||||
"string": "test",
|
|
||||||
}
|
|
||||||
m, err := New("cpu", tags, fields, now)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
split := m.Split(1000)
|
|
||||||
assert.Len(t, split, 1)
|
|
||||||
assert.Equal(t, m, split[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSplitMetric_OneField(t *testing.T) {
|
|
||||||
now := time.Unix(0, 1480940990034083306)
|
|
||||||
tags := map[string]string{
|
|
||||||
"host": "localhost",
|
|
||||||
}
|
|
||||||
fields := map[string]interface{}{
|
|
||||||
"float": float64(100001),
|
|
||||||
}
|
|
||||||
m, err := New("cpu", tags, fields, now)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, "cpu,host=localhost float=100001 1480940990034083306\n", m.String())
|
|
||||||
|
|
||||||
split := m.Split(1000)
|
|
||||||
assert.Len(t, split, 1)
|
|
||||||
assert.Equal(t, "cpu,host=localhost float=100001 1480940990034083306\n", split[0].String())
|
|
||||||
|
|
||||||
split = m.Split(1)
|
|
||||||
assert.Len(t, split, 1)
|
|
||||||
assert.Equal(t, "cpu,host=localhost float=100001 1480940990034083306\n", split[0].String())
|
|
||||||
|
|
||||||
split = m.Split(40)
|
|
||||||
assert.Len(t, split, 1)
|
|
||||||
assert.Equal(t, "cpu,host=localhost float=100001 1480940990034083306\n", split[0].String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewMetricAggregate(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
tags := map[string]string{
|
|
||||||
"host": "localhost",
|
|
||||||
}
|
|
||||||
fields := map[string]interface{}{
|
|
||||||
"usage_idle": float64(99),
|
|
||||||
}
|
|
||||||
m, err := New("cpu", tags, fields, now)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.False(t, m.IsAggregate())
|
|
||||||
m.SetAggregate(true)
|
|
||||||
assert.True(t, m.IsAggregate())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewMetricPoint(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
tags := map[string]string{
|
|
||||||
"host": "localhost",
|
|
||||||
}
|
|
||||||
fields := map[string]interface{}{
|
|
||||||
"usage_idle": float64(99),
|
|
||||||
}
|
|
||||||
m, err := New("cpu", tags, fields, now)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
p := m.Point()
|
|
||||||
|
|
||||||
assert.Equal(t, fields, m.Fields())
|
|
||||||
assert.Equal(t, fields, p.Fields())
|
|
||||||
assert.Equal(t, "cpu", p.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewMetricString(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
tags := map[string]string{
|
|
||||||
"host": "localhost",
|
|
||||||
}
|
|
||||||
fields := map[string]interface{}{
|
|
||||||
"usage_idle": float64(99),
|
|
||||||
}
|
|
||||||
m, err := New("cpu", tags, fields, now)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
lineProto := fmt.Sprintf("cpu,host=localhost usage_idle=99 %d\n",
|
|
||||||
now.UnixNano())
|
|
||||||
assert.Equal(t, lineProto, m.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewMetricFailNaN(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
tags := map[string]string{
|
|
||||||
"host": "localhost",
|
|
||||||
}
|
|
||||||
fields := map[string]interface{}{
|
|
||||||
"usage_idle": math.NaN(),
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := New("cpu", tags, fields, now)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
627
metric/parse.go
627
metric/parse.go
@@ -1,627 +0,0 @@
|
|||||||
package metric
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrInvalidNumber = errors.New("invalid number")
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// the number of characters for the largest possible int64 (9223372036854775807)
|
|
||||||
maxInt64Digits = 19
|
|
||||||
|
|
||||||
// the number of characters for the smallest possible int64 (-9223372036854775808)
|
|
||||||
minInt64Digits = 20
|
|
||||||
|
|
||||||
// the number of characters required for the largest float64 before a range check
|
|
||||||
// would occur during parsing
|
|
||||||
maxFloat64Digits = 25
|
|
||||||
|
|
||||||
// the number of characters required for smallest float64 before a range check occur
|
|
||||||
// would occur during parsing
|
|
||||||
minFloat64Digits = 27
|
|
||||||
|
|
||||||
MaxKeyLength = 65535
|
|
||||||
)
|
|
||||||
|
|
||||||
// The following constants allow us to specify which state to move to
|
|
||||||
// next, when scanning sections of a Point.
|
|
||||||
const (
|
|
||||||
tagKeyState = iota
|
|
||||||
tagValueState
|
|
||||||
fieldsState
|
|
||||||
)
|
|
||||||
|
|
||||||
func Parse(buf []byte) ([]telegraf.Metric, error) {
|
|
||||||
return ParseWithDefaultTime(buf, time.Now())
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseWithDefaultTime(buf []byte, t time.Time) ([]telegraf.Metric, error) {
|
|
||||||
if len(buf) <= 6 {
|
|
||||||
return []telegraf.Metric{}, makeError("buffer too short", buf, 0)
|
|
||||||
}
|
|
||||||
metrics := make([]telegraf.Metric, 0, bytes.Count(buf, []byte("\n"))+1)
|
|
||||||
var errStr string
|
|
||||||
i := 0
|
|
||||||
for {
|
|
||||||
j := bytes.IndexByte(buf[i:], '\n')
|
|
||||||
if j == -1 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if len(buf[i:i+j]) < 2 {
|
|
||||||
i += j + 1 // increment i past the previous newline
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
m, err := parseMetric(buf[i:i+j], t)
|
|
||||||
if err != nil {
|
|
||||||
i += j + 1 // increment i past the previous newline
|
|
||||||
errStr += " " + err.Error()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
i += j + 1 // increment i past the previous newline
|
|
||||||
|
|
||||||
metrics = append(metrics, m)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(errStr) > 0 {
|
|
||||||
return metrics, fmt.Errorf(errStr)
|
|
||||||
}
|
|
||||||
return metrics, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseMetric(buf []byte, defaultTime time.Time) (telegraf.Metric, error) {
|
|
||||||
var dTime string
|
|
||||||
// scan the first block which is measurement[,tag1=value1,tag2=value=2...]
|
|
||||||
pos, key, err := scanKey(buf, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// measurement name is required
|
|
||||||
if len(key) == 0 {
|
|
||||||
return nil, fmt.Errorf("missing measurement")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(key) > MaxKeyLength {
|
|
||||||
return nil, fmt.Errorf("max key length exceeded: %v > %v", len(key), MaxKeyLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
// scan the second block is which is field1=value1[,field2=value2,...]
|
|
||||||
pos, fields, err := scanFields(buf, pos)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// at least one field is required
|
|
||||||
if len(fields) == 0 {
|
|
||||||
return nil, fmt.Errorf("missing fields")
|
|
||||||
}
|
|
||||||
|
|
||||||
// scan the last block which is an optional integer timestamp
|
|
||||||
pos, ts, err := scanTime(buf, pos)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
m := &metric{
|
|
||||||
fields: fields,
|
|
||||||
t: ts,
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse out the measurement name
|
|
||||||
// namei is the index at which the "name" ends
|
|
||||||
namei := indexUnescapedByte(key, ',')
|
|
||||||
if namei < 1 {
|
|
||||||
// no tags
|
|
||||||
m.name = key
|
|
||||||
} else {
|
|
||||||
m.name = key[0:namei]
|
|
||||||
m.tags = key[namei:]
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(m.t) == 0 {
|
|
||||||
if len(dTime) == 0 {
|
|
||||||
dTime = fmt.Sprint(defaultTime.UnixNano())
|
|
||||||
}
|
|
||||||
// use default time
|
|
||||||
m.t = []byte(dTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
// here we copy on return because this allows us to later call
|
|
||||||
// AddTag, AddField, RemoveTag, RemoveField, etc. without worrying about
|
|
||||||
// modifying 'tag' bytes having an affect on 'field' bytes, for example.
|
|
||||||
return m.Copy(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// scanKey scans buf starting at i for the measurement and tag portion of the point.
|
|
||||||
// It returns the ending position and the byte slice of key within buf. If there
|
|
||||||
// are tags, they will be sorted if they are not already.
|
|
||||||
func scanKey(buf []byte, i int) (int, []byte, error) {
|
|
||||||
start := skipWhitespace(buf, i)
|
|
||||||
i = start
|
|
||||||
|
|
||||||
// First scan the Point's measurement.
|
|
||||||
state, i, err := scanMeasurement(buf, i)
|
|
||||||
if err != nil {
|
|
||||||
return i, buf[start:i], err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optionally scan tags if needed.
|
|
||||||
if state == tagKeyState {
|
|
||||||
i, err = scanTags(buf, i)
|
|
||||||
if err != nil {
|
|
||||||
return i, buf[start:i], err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return i, buf[start:i], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// scanMeasurement examines the measurement part of a Point, returning
|
|
||||||
// the next state to move to, and the current location in the buffer.
|
|
||||||
func scanMeasurement(buf []byte, i int) (int, int, error) {
|
|
||||||
// Check first byte of measurement, anything except a comma is fine.
|
|
||||||
// It can't be a space, since whitespace is stripped prior to this
|
|
||||||
// function call.
|
|
||||||
if i >= len(buf) || buf[i] == ',' {
|
|
||||||
return -1, i, makeError("missing measurement", buf, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
i++
|
|
||||||
if i >= len(buf) {
|
|
||||||
// cpu
|
|
||||||
return -1, i, makeError("missing fields", buf, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
if buf[i-1] == '\\' {
|
|
||||||
// Skip character (it's escaped).
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unescaped comma; move onto scanning the tags.
|
|
||||||
if buf[i] == ',' {
|
|
||||||
return tagKeyState, i + 1, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unescaped space; move onto scanning the fields.
|
|
||||||
if buf[i] == ' ' {
|
|
||||||
// cpu value=1.0
|
|
||||||
return fieldsState, i, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// scanTags examines all the tags in a Point, keeping track of and
|
|
||||||
// returning the updated indices slice, number of commas and location
|
|
||||||
// in buf where to start examining the Point fields.
|
|
||||||
func scanTags(buf []byte, i int) (int, error) {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
state = tagKeyState
|
|
||||||
)
|
|
||||||
|
|
||||||
for {
|
|
||||||
switch state {
|
|
||||||
case tagKeyState:
|
|
||||||
i, err = scanTagsKey(buf, i)
|
|
||||||
state = tagValueState // tag value always follows a tag key
|
|
||||||
case tagValueState:
|
|
||||||
state, i, err = scanTagsValue(buf, i)
|
|
||||||
case fieldsState:
|
|
||||||
return i, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return i, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// scanTagsKey scans each character in a tag key.
|
|
||||||
func scanTagsKey(buf []byte, i int) (int, error) {
|
|
||||||
// First character of the key.
|
|
||||||
if i >= len(buf) || buf[i] == ' ' || buf[i] == ',' || buf[i] == '=' {
|
|
||||||
// cpu,{'', ' ', ',', '='}
|
|
||||||
return i, makeError("missing tag key", buf, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Examine each character in the tag key until we hit an unescaped
|
|
||||||
// equals (the tag value), or we hit an error (i.e., unescaped
|
|
||||||
// space or comma).
|
|
||||||
for {
|
|
||||||
i++
|
|
||||||
|
|
||||||
// Either we reached the end of the buffer or we hit an
|
|
||||||
// unescaped comma or space.
|
|
||||||
if i >= len(buf) ||
|
|
||||||
((buf[i] == ' ' || buf[i] == ',') && buf[i-1] != '\\') {
|
|
||||||
// cpu,tag{'', ' ', ','}
|
|
||||||
return i, makeError("missing tag value", buf, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
if buf[i] == '=' && buf[i-1] != '\\' {
|
|
||||||
// cpu,tag=
|
|
||||||
return i + 1, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// scanTagsValue scans each character in a tag value.
|
|
||||||
func scanTagsValue(buf []byte, i int) (int, int, error) {
|
|
||||||
// Tag value cannot be empty.
|
|
||||||
if i >= len(buf) || buf[i] == ',' || buf[i] == ' ' {
|
|
||||||
// cpu,tag={',', ' '}
|
|
||||||
return -1, i, makeError("missing tag value", buf, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Examine each character in the tag value until we hit an unescaped
|
|
||||||
// comma (move onto next tag key), an unescaped space (move onto
|
|
||||||
// fields), or we error out.
|
|
||||||
for {
|
|
||||||
i++
|
|
||||||
if i >= len(buf) {
|
|
||||||
// cpu,tag=value
|
|
||||||
return -1, i, makeError("missing fields", buf, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
// An unescaped equals sign is an invalid tag value.
|
|
||||||
if buf[i] == '=' && buf[i-1] != '\\' {
|
|
||||||
// cpu,tag={'=', 'fo=o'}
|
|
||||||
return -1, i, makeError("invalid tag format", buf, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
if buf[i] == ',' && buf[i-1] != '\\' {
|
|
||||||
// cpu,tag=foo,
|
|
||||||
return tagKeyState, i + 1, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cpu,tag=foo value=1.0
|
|
||||||
// cpu, tag=foo\= value=1.0
|
|
||||||
if buf[i] == ' ' && buf[i-1] != '\\' {
|
|
||||||
return fieldsState, i, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// scanFields scans buf, starting at i for the fields section of a point. It returns
|
|
||||||
// the ending position and the byte slice of the fields within buf
|
|
||||||
func scanFields(buf []byte, i int) (int, []byte, error) {
|
|
||||||
start := skipWhitespace(buf, i)
|
|
||||||
i = start
|
|
||||||
quoted := false
|
|
||||||
|
|
||||||
// tracks how many '=' we've seen
|
|
||||||
equals := 0
|
|
||||||
|
|
||||||
// tracks how many commas we've seen
|
|
||||||
commas := 0
|
|
||||||
|
|
||||||
for {
|
|
||||||
// reached the end of buf?
|
|
||||||
if i >= len(buf) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// escaped characters?
|
|
||||||
if buf[i] == '\\' && i+1 < len(buf) {
|
|
||||||
i += 2
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the value is quoted, scan until we get to the end quote
|
|
||||||
// Only quote values in the field value since quotes are not significant
|
|
||||||
// in the field key
|
|
||||||
if buf[i] == '"' && equals > commas {
|
|
||||||
quoted = !quoted
|
|
||||||
i++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we see an =, ensure that there is at least on char before and after it
|
|
||||||
if buf[i] == '=' && !quoted {
|
|
||||||
equals++
|
|
||||||
|
|
||||||
// check for "... =123" but allow "a\ =123"
|
|
||||||
if buf[i-1] == ' ' && buf[i-2] != '\\' {
|
|
||||||
return i, buf[start:i], makeError("missing field key", buf, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for "...a=123,=456" but allow "a=123,a\,=456"
|
|
||||||
if buf[i-1] == ',' && buf[i-2] != '\\' {
|
|
||||||
return i, buf[start:i], makeError("missing field key", buf, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for "... value="
|
|
||||||
if i+1 >= len(buf) {
|
|
||||||
return i, buf[start:i], makeError("missing field value", buf, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for "... value=,value2=..."
|
|
||||||
if buf[i+1] == ',' || buf[i+1] == ' ' {
|
|
||||||
return i, buf[start:i], makeError("missing field value", buf, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
if isNumeric(buf[i+1]) || buf[i+1] == '-' || buf[i+1] == 'N' || buf[i+1] == 'n' {
|
|
||||||
var err error
|
|
||||||
i, err = scanNumber(buf, i+1)
|
|
||||||
if err != nil {
|
|
||||||
return i, buf[start:i], err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// If next byte is not a double-quote, the value must be a boolean
|
|
||||||
if buf[i+1] != '"' {
|
|
||||||
var err error
|
|
||||||
i, _, err = scanBoolean(buf, i+1)
|
|
||||||
if err != nil {
|
|
||||||
return i, buf[start:i], err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if buf[i] == ',' && !quoted {
|
|
||||||
commas++
|
|
||||||
}
|
|
||||||
|
|
||||||
// reached end of block?
|
|
||||||
if buf[i] == ' ' && !quoted {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
if quoted {
|
|
||||||
return i, buf[start:i], makeError("unbalanced quotes", buf, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check that all field sections had key and values (e.g. prevent "a=1,b"
|
|
||||||
if equals == 0 || commas != equals-1 {
|
|
||||||
return i, buf[start:i], makeError("invalid field format", buf, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
return i, buf[start:i], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// scanTime scans buf, starting at i for the time section of a point. It
|
|
||||||
// returns the ending position and the byte slice of the timestamp within buf
|
|
||||||
// and and error if the timestamp is not in the correct numeric format.
|
|
||||||
func scanTime(buf []byte, i int) (int, []byte, error) {
|
|
||||||
start := skipWhitespace(buf, i)
|
|
||||||
i = start
|
|
||||||
|
|
||||||
for {
|
|
||||||
// reached the end of buf?
|
|
||||||
if i >= len(buf) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reached end of block or trailing whitespace?
|
|
||||||
if buf[i] == '\n' || buf[i] == ' ' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle negative timestamps
|
|
||||||
if i == start && buf[i] == '-' {
|
|
||||||
i++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Timestamps should be integers, make sure they are so we don't need
|
|
||||||
// to actually parse the timestamp until needed.
|
|
||||||
if buf[i] < '0' || buf[i] > '9' {
|
|
||||||
return i, buf[start:i], makeError("invalid timestamp", buf, i)
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
return i, buf[start:i], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isNumeric(b byte) bool {
|
|
||||||
return (b >= '0' && b <= '9') || b == '.'
|
|
||||||
}
|
|
||||||
|
|
||||||
// scanNumber returns the end position within buf, start at i after
|
|
||||||
// scanning over buf for an integer, or float. It returns an
|
|
||||||
// error if a invalid number is scanned.
|
|
||||||
func scanNumber(buf []byte, i int) (int, error) {
|
|
||||||
start := i
|
|
||||||
var isInt bool
|
|
||||||
|
|
||||||
// Is negative number?
|
|
||||||
if i < len(buf) && buf[i] == '-' {
|
|
||||||
i++
|
|
||||||
// There must be more characters now, as just '-' is illegal.
|
|
||||||
if i == len(buf) {
|
|
||||||
return i, ErrInvalidNumber
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// how many decimal points we've see
|
|
||||||
decimal := false
|
|
||||||
|
|
||||||
// indicates the number is float in scientific notation
|
|
||||||
scientific := false
|
|
||||||
|
|
||||||
for {
|
|
||||||
if i >= len(buf) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if buf[i] == ',' || buf[i] == ' ' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if buf[i] == 'i' && i > start && !isInt {
|
|
||||||
isInt = true
|
|
||||||
i++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if buf[i] == '.' {
|
|
||||||
// Can't have more than 1 decimal (e.g. 1.1.1 should fail)
|
|
||||||
if decimal {
|
|
||||||
return i, ErrInvalidNumber
|
|
||||||
}
|
|
||||||
decimal = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// `e` is valid for floats but not as the first char
|
|
||||||
if i > start && (buf[i] == 'e' || buf[i] == 'E') {
|
|
||||||
scientific = true
|
|
||||||
i++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// + and - are only valid at this point if they follow an e (scientific notation)
|
|
||||||
if (buf[i] == '+' || buf[i] == '-') && (buf[i-1] == 'e' || buf[i-1] == 'E') {
|
|
||||||
i++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// NaN is an unsupported value
|
|
||||||
if i+2 < len(buf) && (buf[i] == 'N' || buf[i] == 'n') {
|
|
||||||
return i, ErrInvalidNumber
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isNumeric(buf[i]) {
|
|
||||||
return i, ErrInvalidNumber
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
if isInt && (decimal || scientific) {
|
|
||||||
return i, ErrInvalidNumber
|
|
||||||
}
|
|
||||||
|
|
||||||
numericDigits := i - start
|
|
||||||
if isInt {
|
|
||||||
numericDigits--
|
|
||||||
}
|
|
||||||
if decimal {
|
|
||||||
numericDigits--
|
|
||||||
}
|
|
||||||
if buf[start] == '-' {
|
|
||||||
numericDigits--
|
|
||||||
}
|
|
||||||
|
|
||||||
if numericDigits == 0 {
|
|
||||||
return i, ErrInvalidNumber
|
|
||||||
}
|
|
||||||
|
|
||||||
// It's more common that numbers will be within min/max range for their type but we need to prevent
|
|
||||||
// out or range numbers from being parsed successfully. This uses some simple heuristics to decide
|
|
||||||
// if we should parse the number to the actual type. It does not do it all the time because it incurs
|
|
||||||
// extra allocations and we end up converting the type again when writing points to disk.
|
|
||||||
if isInt {
|
|
||||||
// Make sure the last char is an 'i' for integers (e.g. 9i10 is not valid)
|
|
||||||
if buf[i-1] != 'i' {
|
|
||||||
return i, ErrInvalidNumber
|
|
||||||
}
|
|
||||||
// Parse the int to check bounds the number of digits could be larger than the max range
|
|
||||||
// We subtract 1 from the index to remove the `i` from our tests
|
|
||||||
if len(buf[start:i-1]) >= maxInt64Digits || len(buf[start:i-1]) >= minInt64Digits {
|
|
||||||
if _, err := parseIntBytes(buf[start:i-1], 10, 64); err != nil {
|
|
||||||
return i, makeError(fmt.Sprintf("unable to parse integer %s: %s", buf[start:i-1], err), buf, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Parse the float to check bounds if it's scientific or the number of digits could be larger than the max range
|
|
||||||
if scientific || len(buf[start:i]) >= maxFloat64Digits || len(buf[start:i]) >= minFloat64Digits {
|
|
||||||
if _, err := parseFloatBytes(buf[start:i], 10); err != nil {
|
|
||||||
return i, makeError("invalid float", buf, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return i, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// scanBoolean returns the end position within buf, start at i after
|
|
||||||
// scanning over buf for boolean. Valid values for a boolean are
|
|
||||||
// t, T, true, TRUE, f, F, false, FALSE. It returns an error if a invalid boolean
|
|
||||||
// is scanned.
|
|
||||||
func scanBoolean(buf []byte, i int) (int, []byte, error) {
|
|
||||||
start := i
|
|
||||||
|
|
||||||
if i < len(buf) && (buf[i] != 't' && buf[i] != 'f' && buf[i] != 'T' && buf[i] != 'F') {
|
|
||||||
return i, buf[start:i], makeError("invalid value", buf, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
i++
|
|
||||||
for {
|
|
||||||
if i >= len(buf) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if buf[i] == ',' || buf[i] == ' ' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Single char bool (t, T, f, F) is ok
|
|
||||||
if i-start == 1 {
|
|
||||||
return i, buf[start:i], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// length must be 4 for true or TRUE
|
|
||||||
if (buf[start] == 't' || buf[start] == 'T') && i-start != 4 {
|
|
||||||
return i, buf[start:i], makeError("invalid boolean", buf, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
// length must be 5 for false or FALSE
|
|
||||||
if (buf[start] == 'f' || buf[start] == 'F') && i-start != 5 {
|
|
||||||
return i, buf[start:i], makeError("invalid boolean", buf, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise
|
|
||||||
valid := false
|
|
||||||
switch buf[start] {
|
|
||||||
case 't':
|
|
||||||
valid = bytes.Equal(buf[start:i], []byte("true"))
|
|
||||||
case 'f':
|
|
||||||
valid = bytes.Equal(buf[start:i], []byte("false"))
|
|
||||||
case 'T':
|
|
||||||
valid = bytes.Equal(buf[start:i], []byte("TRUE")) || bytes.Equal(buf[start:i], []byte("True"))
|
|
||||||
case 'F':
|
|
||||||
valid = bytes.Equal(buf[start:i], []byte("FALSE")) || bytes.Equal(buf[start:i], []byte("False"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if !valid {
|
|
||||||
return i, buf[start:i], makeError("invalid boolean", buf, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
return i, buf[start:i], nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// skipWhitespace returns the end position within buf, starting at i after
|
|
||||||
// scanning over spaces in tags
|
|
||||||
func skipWhitespace(buf []byte, i int) int {
|
|
||||||
for i < len(buf) {
|
|
||||||
if buf[i] != ' ' && buf[i] != '\t' && buf[i] != 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeError is a helper function for making a metric parsing error.
|
|
||||||
// reason is the reason that the error occured.
|
|
||||||
// buf should be the current buffer we are parsing.
|
|
||||||
// i is the current index, to give some context on where in the buffer we are.
|
|
||||||
func makeError(reason string, buf []byte, i int) error {
|
|
||||||
return fmt.Errorf("metric parsing error, reason: [%s], buffer: [%s], index: [%d]",
|
|
||||||
reason, buf, i)
|
|
||||||
}
|
|
||||||
@@ -1,355 +0,0 @@
|
|||||||
package metric
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
const trues = `booltest b=T
|
|
||||||
booltest b=t
|
|
||||||
booltest b=True
|
|
||||||
booltest b=TRUE
|
|
||||||
booltest b=true
|
|
||||||
`
|
|
||||||
|
|
||||||
const falses = `booltest b=F
|
|
||||||
booltest b=f
|
|
||||||
booltest b=False
|
|
||||||
booltest b=FALSE
|
|
||||||
booltest b=false
|
|
||||||
`
|
|
||||||
|
|
||||||
const withEscapes = `w\,\ eather,host=local temp=99 1465839830100400200
|
|
||||||
w\,eather,host=local temp=99 1465839830100400200
|
|
||||||
weather,location=us\,midwest temperature=82 1465839830100400200
|
|
||||||
weather,location=us-midwest temp\=rature=82 1465839830100400200
|
|
||||||
weather,location\ place=us-midwest temperature=82 1465839830100400200
|
|
||||||
weather,location=us-midwest temperature="too\"hot\"" 1465839830100400200
|
|
||||||
`
|
|
||||||
|
|
||||||
const withTimestamps = `cpu usage=99 1480595849000000000
|
|
||||||
cpu usage=99 1480595850000000000
|
|
||||||
cpu usage=99 1480595851700030000
|
|
||||||
cpu usage=99 1480595852000000300
|
|
||||||
`
|
|
||||||
|
|
||||||
const sevenMetrics = `cpu,host=foo,datacenter=us-east idle=99,busy=1i,b=true,s="string"
|
|
||||||
cpu,host=foo,datacenter=us-east idle=99,busy=1i,b=true,s="string"
|
|
||||||
cpu,host=foo,datacenter=us-east idle=99,busy=1i,b=true,s="string"
|
|
||||||
cpu,host=foo,datacenter=us-east idle=99,busy=1i,b=true,s="string"
|
|
||||||
cpu,host=foo,datacenter=us-east idle=99,busy=1i,b=true,s="string"
|
|
||||||
cpu,host=foo,datacenter=us-east idle=99,busy=1i,b=true,s="string"
|
|
||||||
cpu,host=foo,datacenter=us-east idle=99,busy=1i,b=true,s="string"
|
|
||||||
`
|
|
||||||
|
|
||||||
// some metrics are invalid
|
|
||||||
const someInvalid = `cpu,host=foo,datacenter=us-east usage_idle=99,usage_busy=1
|
|
||||||
cpu,host=foo,datacenter=us-east usage_idle=99,usage_busy=1
|
|
||||||
cpu,host=foo,datacenter=us-east usage_idle=99,usage_busy=1
|
|
||||||
cpu,cpu=cpu3, host=foo,datacenter=us-east usage_idle=99,usage_busy=1
|
|
||||||
cpu,cpu=cpu4 , usage_idle=99,usage_busy=1
|
|
||||||
cpu 1480595852000000300
|
|
||||||
cpu usage=99 1480595852foobar300
|
|
||||||
cpu,host=foo,datacenter=us-east usage_idle=99,usage_busy=1
|
|
||||||
`
|
|
||||||
|
|
||||||
func TestParse(t *testing.T) {
|
|
||||||
start := time.Now()
|
|
||||||
metrics, err := Parse([]byte(sevenMetrics))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, metrics, 7)
|
|
||||||
|
|
||||||
// all metrics parsed together w/o a timestamp should have the same time.
|
|
||||||
firstTime := metrics[0].Time()
|
|
||||||
for _, m := range metrics {
|
|
||||||
assert.Equal(t,
|
|
||||||
map[string]interface{}{
|
|
||||||
"idle": float64(99),
|
|
||||||
"busy": int64(1),
|
|
||||||
"b": true,
|
|
||||||
"s": "string",
|
|
||||||
},
|
|
||||||
m.Fields(),
|
|
||||||
)
|
|
||||||
assert.Equal(t,
|
|
||||||
map[string]string{
|
|
||||||
"host": "foo",
|
|
||||||
"datacenter": "us-east",
|
|
||||||
},
|
|
||||||
m.Tags(),
|
|
||||||
)
|
|
||||||
assert.True(t, m.Time().After(start))
|
|
||||||
assert.True(t, m.Time().Equal(firstTime))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseErrors(t *testing.T) {
|
|
||||||
start := time.Now()
|
|
||||||
metrics, err := Parse([]byte(someInvalid))
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Len(t, metrics, 4)
|
|
||||||
|
|
||||||
// all metrics parsed together w/o a timestamp should have the same time.
|
|
||||||
firstTime := metrics[0].Time()
|
|
||||||
for _, m := range metrics {
|
|
||||||
assert.Equal(t,
|
|
||||||
map[string]interface{}{
|
|
||||||
"usage_idle": float64(99),
|
|
||||||
"usage_busy": float64(1),
|
|
||||||
},
|
|
||||||
m.Fields(),
|
|
||||||
)
|
|
||||||
assert.Equal(t,
|
|
||||||
map[string]string{
|
|
||||||
"host": "foo",
|
|
||||||
"datacenter": "us-east",
|
|
||||||
},
|
|
||||||
m.Tags(),
|
|
||||||
)
|
|
||||||
assert.True(t, m.Time().After(start))
|
|
||||||
assert.True(t, m.Time().Equal(firstTime))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseWithTimestamps(t *testing.T) {
|
|
||||||
metrics, err := Parse([]byte(withTimestamps))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, metrics, 4)
|
|
||||||
|
|
||||||
expectedTimestamps := []time.Time{
|
|
||||||
time.Unix(0, 1480595849000000000),
|
|
||||||
time.Unix(0, 1480595850000000000),
|
|
||||||
time.Unix(0, 1480595851700030000),
|
|
||||||
time.Unix(0, 1480595852000000300),
|
|
||||||
}
|
|
||||||
|
|
||||||
// all metrics parsed together w/o a timestamp should have the same time.
|
|
||||||
for i, m := range metrics {
|
|
||||||
assert.Equal(t,
|
|
||||||
map[string]interface{}{
|
|
||||||
"usage": float64(99),
|
|
||||||
},
|
|
||||||
m.Fields(),
|
|
||||||
)
|
|
||||||
assert.True(t, m.Time().Equal(expectedTimestamps[i]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseEscapes(t *testing.T) {
|
|
||||||
metrics, err := Parse([]byte(withEscapes))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, metrics, 6)
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields map[string]interface{}
|
|
||||||
tags map[string]string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: `w, eather`,
|
|
||||||
fields: map[string]interface{}{"temp": float64(99)},
|
|
||||||
tags: map[string]string{"host": "local"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: `w,eather`,
|
|
||||||
fields: map[string]interface{}{"temp": float64(99)},
|
|
||||||
tags: map[string]string{"host": "local"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: `weather`,
|
|
||||||
fields: map[string]interface{}{"temperature": float64(82)},
|
|
||||||
tags: map[string]string{"location": `us,midwest`},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: `weather`,
|
|
||||||
fields: map[string]interface{}{`temp=rature`: float64(82)},
|
|
||||||
tags: map[string]string{"location": `us-midwest`},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: `weather`,
|
|
||||||
fields: map[string]interface{}{"temperature": float64(82)},
|
|
||||||
tags: map[string]string{`location place`: `us-midwest`},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: `weather`,
|
|
||||||
fields: map[string]interface{}{`temperature`: `too"hot"`},
|
|
||||||
tags: map[string]string{"location": `us-midwest`},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range tests {
|
|
||||||
assert.Equal(t, test.name, metrics[i].Name())
|
|
||||||
assert.Equal(t, test.fields, metrics[i].Fields())
|
|
||||||
assert.Equal(t, test.tags, metrics[i].Tags())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseTrueBooleans(t *testing.T) {
|
|
||||||
metrics, err := Parse([]byte(trues))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, metrics, 5)
|
|
||||||
|
|
||||||
for _, metric := range metrics {
|
|
||||||
assert.Equal(t, "booltest", metric.Name())
|
|
||||||
assert.Equal(t, true, metric.Fields()["b"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseFalseBooleans(t *testing.T) {
|
|
||||||
metrics, err := Parse([]byte(falses))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, metrics, 5)
|
|
||||||
|
|
||||||
for _, metric := range metrics {
|
|
||||||
assert.Equal(t, "booltest", metric.Name())
|
|
||||||
assert.Equal(t, false, metric.Fields()["b"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParsePointBadNumber(t *testing.T) {
|
|
||||||
for _, tt := range []string{
|
|
||||||
"cpu v=- ",
|
|
||||||
"cpu v=-i ",
|
|
||||||
"cpu v=-. ",
|
|
||||||
"cpu v=. ",
|
|
||||||
"cpu v=1.0i ",
|
|
||||||
"cpu v=1ii ",
|
|
||||||
"cpu v=1a ",
|
|
||||||
"cpu v=-e-e-e ",
|
|
||||||
"cpu v=42+3 ",
|
|
||||||
"cpu v= ",
|
|
||||||
} {
|
|
||||||
_, err := Parse([]byte(tt + "\n"))
|
|
||||||
assert.Error(t, err, tt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseTagsMissingParts(t *testing.T) {
|
|
||||||
for _, tt := range []string{
|
|
||||||
`cpu,host`,
|
|
||||||
`cpu,host,`,
|
|
||||||
`cpu,host=`,
|
|
||||||
`cpu,f=oo=bar value=1`,
|
|
||||||
`cpu,host value=1i`,
|
|
||||||
`cpu,host=serverA,region value=1i`,
|
|
||||||
`cpu,host=serverA,region= value=1i`,
|
|
||||||
`cpu,host=serverA,region=,zone=us-west value=1i`,
|
|
||||||
`cpu, value=1`,
|
|
||||||
`cpu, ,,`,
|
|
||||||
`cpu,,,`,
|
|
||||||
`cpu,host=serverA,=us-east value=1i`,
|
|
||||||
`cpu,host=serverAa\,,=us-east value=1i`,
|
|
||||||
`cpu,host=serverA\,,=us-east value=1i`,
|
|
||||||
`cpu, =serverA value=1i`,
|
|
||||||
} {
|
|
||||||
_, err := Parse([]byte(tt + "\n"))
|
|
||||||
assert.Error(t, err, tt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParsePointWhitespace(t *testing.T) {
|
|
||||||
for _, tt := range []string{
|
|
||||||
`cpu value=1.0 1257894000000000000`,
|
|
||||||
`cpu value=1.0 1257894000000000000`,
|
|
||||||
`cpu value=1.0 1257894000000000000`,
|
|
||||||
`cpu value=1.0 1257894000000000000 `,
|
|
||||||
} {
|
|
||||||
m, err := Parse([]byte(tt + "\n"))
|
|
||||||
assert.NoError(t, err, tt)
|
|
||||||
assert.Equal(t, "cpu", m[0].Name())
|
|
||||||
assert.Equal(t, map[string]interface{}{"value": float64(1)}, m[0].Fields())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParsePointInvalidFields(t *testing.T) {
|
|
||||||
for _, tt := range []string{
|
|
||||||
"test,foo=bar a=101,=value",
|
|
||||||
"test,foo=bar =value",
|
|
||||||
"test,foo=bar a=101,key=",
|
|
||||||
"test,foo=bar key=",
|
|
||||||
`test,foo=bar a=101,b="foo`,
|
|
||||||
} {
|
|
||||||
_, err := Parse([]byte(tt + "\n"))
|
|
||||||
assert.Error(t, err, tt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParsePointNoFields(t *testing.T) {
|
|
||||||
for _, tt := range []string{
|
|
||||||
"cpu_load_short,host=server01,region=us-west",
|
|
||||||
"very_long_measurement_name",
|
|
||||||
"cpu,host==",
|
|
||||||
"============",
|
|
||||||
"cpu",
|
|
||||||
"cpu\n\n\n\n\n\n\n",
|
|
||||||
" ",
|
|
||||||
} {
|
|
||||||
_, err := Parse([]byte(tt + "\n"))
|
|
||||||
assert.Error(t, err, tt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// a b=1 << this is the shortest possible metric
|
|
||||||
// any shorter is just ignored
|
|
||||||
func TestParseBufTooShort(t *testing.T) {
|
|
||||||
for _, tt := range []string{
|
|
||||||
"",
|
|
||||||
"a",
|
|
||||||
"a ",
|
|
||||||
"a b=",
|
|
||||||
} {
|
|
||||||
_, err := Parse([]byte(tt + "\n"))
|
|
||||||
assert.Error(t, err, tt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseInvalidBooleans(t *testing.T) {
|
|
||||||
for _, tt := range []string{
|
|
||||||
"test b=tru",
|
|
||||||
"test b=fals",
|
|
||||||
"test b=faLse",
|
|
||||||
"test q=foo",
|
|
||||||
"test b=lambchops",
|
|
||||||
} {
|
|
||||||
_, err := Parse([]byte(tt + "\n"))
|
|
||||||
assert.Error(t, err, tt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseInvalidNumbers(t *testing.T) {
|
|
||||||
for _, tt := range []string{
|
|
||||||
"test b=-",
|
|
||||||
"test b=1.1.1",
|
|
||||||
"test b=nan",
|
|
||||||
"test b=9i10",
|
|
||||||
"test b=9999999999999999999i",
|
|
||||||
} {
|
|
||||||
_, err := Parse([]byte(tt + "\n"))
|
|
||||||
assert.Error(t, err, tt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseNegativeTimestamps(t *testing.T) {
|
|
||||||
for _, tt := range []string{
|
|
||||||
"test foo=101 -1257894000000000000",
|
|
||||||
} {
|
|
||||||
metrics, err := Parse([]byte(tt + "\n"))
|
|
||||||
assert.NoError(t, err, tt)
|
|
||||||
assert.True(t, metrics[0].Time().Equal(time.Unix(0, -1257894000000000000)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseMaxKeyLength(t *testing.T) {
|
|
||||||
key := ""
|
|
||||||
for {
|
|
||||||
if len(key) > MaxKeyLength {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
key += "test"
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := Parse([]byte(key + " value=1\n"))
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
||||||
111
metric_test.go
Normal file
111
metric_test.go
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
package telegraf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewMetric(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 := NewMetric("cpu", tags, fields, now)
|
||||||
|
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, fields, m.Fields())
|
||||||
|
assert.Equal(t, "cpu", m.Name())
|
||||||
|
assert.Equal(t, now, m.Time())
|
||||||
|
assert.Equal(t, now.UnixNano(), m.UnixNano())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewMetricString(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
tags := map[string]string{
|
||||||
|
"host": "localhost",
|
||||||
|
}
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"usage_idle": float64(99),
|
||||||
|
}
|
||||||
|
m, err := NewMetric("cpu", tags, fields, now)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
lineProto := fmt.Sprintf("cpu,host=localhost usage_idle=99 %d",
|
||||||
|
now.UnixNano())
|
||||||
|
assert.Equal(t, lineProto, m.String())
|
||||||
|
|
||||||
|
lineProtoPrecision := fmt.Sprintf("cpu,host=localhost usage_idle=99 %d",
|
||||||
|
now.Unix())
|
||||||
|
assert.Equal(t, lineProtoPrecision, m.PrecisionString("s"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewMetricFailNaN(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
tags := map[string]string{
|
||||||
|
"host": "localhost",
|
||||||
|
}
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"usage_idle": math.NaN(),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := NewMetric("cpu", tags, fields, now)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package all
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "github.com/influxdata/telegraf/plugins/aggregators/minmax"
|
|
||||||
)
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# MinMax Aggregator Plugin
|
|
||||||
|
|
||||||
The minmax aggregator plugin aggregates min & max values of each field it sees,
|
|
||||||
emitting the aggrate every `period` seconds.
|
|
||||||
|
|
||||||
### Configuration:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
# Keep the aggregate min/max of each metric passing through.
|
|
||||||
[[aggregators.minmax]]
|
|
||||||
## General Aggregator Arguments:
|
|
||||||
## The period on which to flush & clear the aggregator.
|
|
||||||
period = "30s"
|
|
||||||
## If true, the original metric will be dropped by the
|
|
||||||
## aggregator and will not get sent to the output plugins.
|
|
||||||
drop_original = false
|
|
||||||
```
|
|
||||||
|
|
||||||
### Measurements & Fields:
|
|
||||||
|
|
||||||
- measurement1
|
|
||||||
- field1_max
|
|
||||||
- field1_min
|
|
||||||
|
|
||||||
### Tags:
|
|
||||||
|
|
||||||
No tags are applied by this aggregator.
|
|
||||||
|
|
||||||
### Example Output:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ telegraf --config telegraf.conf --quiet
|
|
||||||
system,host=tars load1=1.72 1475583980000000000
|
|
||||||
system,host=tars load1=1.6 1475583990000000000
|
|
||||||
system,host=tars load1=1.66 1475584000000000000
|
|
||||||
system,host=tars load1=1.63 1475584010000000000
|
|
||||||
system,host=tars load1_max=1.72,load1_min=1.6 1475584010000000000
|
|
||||||
system,host=tars load1=1.46 1475584020000000000
|
|
||||||
system,host=tars load1=1.39 1475584030000000000
|
|
||||||
system,host=tars load1=1.41 1475584040000000000
|
|
||||||
system,host=tars load1_max=1.46,load1_min=1.39 1475584040000000000
|
|
||||||
```
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
package minmax
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/influxdata/telegraf"
|
|
||||||
"github.com/influxdata/telegraf/plugins/aggregators"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MinMax struct {
|
|
||||||
cache map[uint64]aggregate
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMinMax() telegraf.Aggregator {
|
|
||||||
mm := &MinMax{}
|
|
||||||
mm.Reset()
|
|
||||||
return mm
|
|
||||||
}
|
|
||||||
|
|
||||||
type aggregate struct {
|
|
||||||
fields map[string]minmax
|
|
||||||
name string
|
|
||||||
tags map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
type minmax struct {
|
|
||||||
min float64
|
|
||||||
max float64
|
|
||||||
}
|
|
||||||
|
|
||||||
var sampleConfig = `
|
|
||||||
## General Aggregator Arguments:
|
|
||||||
## The period on which to flush & clear the aggregator.
|
|
||||||
period = "30s"
|
|
||||||
## If true, the original metric will be dropped by the
|
|
||||||
## aggregator and will not get sent to the output plugins.
|
|
||||||
drop_original = false
|
|
||||||
`
|
|
||||||
|
|
||||||
func (m *MinMax) SampleConfig() string {
|
|
||||||
return sampleConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MinMax) Description() string {
|
|
||||||
return "Keep the aggregate min/max of each metric passing through."
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MinMax) Add(in telegraf.Metric) {
|
|
||||||
id := in.HashID()
|
|
||||||
if _, ok := m.cache[id]; !ok {
|
|
||||||
// hit an uncached metric, create caches for first time:
|
|
||||||
a := aggregate{
|
|
||||||
name: in.Name(),
|
|
||||||
tags: in.Tags(),
|
|
||||||
fields: make(map[string]minmax),
|
|
||||||
}
|
|
||||||
for k, v := range in.Fields() {
|
|
||||||
if fv, ok := convert(v); ok {
|
|
||||||
a.fields[k] = minmax{
|
|
||||||
min: fv,
|
|
||||||
max: fv,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m.cache[id] = a
|
|
||||||
} else {
|
|
||||||
for k, v := range in.Fields() {
|
|
||||||
if fv, ok := convert(v); ok {
|
|
||||||
if _, ok := m.cache[id].fields[k]; !ok {
|
|
||||||
// hit an uncached field of a cached metric
|
|
||||||
m.cache[id].fields[k] = minmax{
|
|
||||||
min: fv,
|
|
||||||
max: fv,
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if fv < m.cache[id].fields[k].min {
|
|
||||||
tmp := m.cache[id].fields[k]
|
|
||||||
tmp.min = fv
|
|
||||||
m.cache[id].fields[k] = tmp
|
|
||||||
} else if fv > m.cache[id].fields[k].max {
|
|
||||||
tmp := m.cache[id].fields[k]
|
|
||||||
tmp.max = fv
|
|
||||||
m.cache[id].fields[k] = tmp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MinMax) Push(acc telegraf.Accumulator) {
|
|
||||||
for _, aggregate := range m.cache {
|
|
||||||
fields := map[string]interface{}{}
|
|
||||||
for k, v := range aggregate.fields {
|
|
||||||
fields[k+"_min"] = v.min
|
|
||||||
fields[k+"_max"] = v.max
|
|
||||||
}
|
|
||||||
acc.AddFields(aggregate.name, fields, aggregate.tags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MinMax) Reset() {
|
|
||||||
m.cache = make(map[uint64]aggregate)
|
|
||||||
}
|
|
||||||
|
|
||||||
func convert(in interface{}) (float64, bool) {
|
|
||||||
switch v := in.(type) {
|
|
||||||
case float64:
|
|
||||||
return v, true
|
|
||||||
case int64:
|
|
||||||
return float64(v), true
|
|
||||||
default:
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
aggregators.Add("minmax", func() telegraf.Aggregator {
|
|
||||||
return NewMinMax()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
package minmax
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/influxdata/telegraf/metric"
|
|
||||||
"github.com/influxdata/telegraf/testutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
var m1, _ = metric.New("m1",
|
|
||||||
map[string]string{"foo": "bar"},
|
|
||||||
map[string]interface{}{
|
|
||||||
"a": int64(1),
|
|
||||||
"b": int64(1),
|
|
||||||
"c": int64(1),
|
|
||||||
"d": int64(1),
|
|
||||||
"e": int64(1),
|
|
||||||
"f": float64(2),
|
|
||||||
"g": float64(2),
|
|
||||||
"h": float64(2),
|
|
||||||
"i": float64(2),
|
|
||||||
"j": float64(3),
|
|
||||||
},
|
|
||||||
time.Now(),
|
|
||||||
)
|
|
||||||
var m2, _ = metric.New("m1",
|
|
||||||
map[string]string{"foo": "bar"},
|
|
||||||
map[string]interface{}{
|
|
||||||
"a": int64(1),
|
|
||||||
"b": int64(3),
|
|
||||||
"c": int64(3),
|
|
||||||
"d": int64(3),
|
|
||||||
"e": int64(3),
|
|
||||||
"f": float64(1),
|
|
||||||
"g": float64(1),
|
|
||||||
"h": float64(1),
|
|
||||||
"i": float64(1),
|
|
||||||
"j": float64(1),
|
|
||||||
"k": float64(200),
|
|
||||||
"ignoreme": "string",
|
|
||||||
"andme": true,
|
|
||||||
},
|
|
||||||
time.Now(),
|
|
||||||
)
|
|
||||||
|
|
||||||
func BenchmarkApply(b *testing.B) {
|
|
||||||
minmax := NewMinMax()
|
|
||||||
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
minmax.Add(m1)
|
|
||||||
minmax.Add(m2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test two metrics getting added.
|
|
||||||
func TestMinMaxWithPeriod(t *testing.T) {
|
|
||||||
acc := testutil.Accumulator{}
|
|
||||||
minmax := NewMinMax()
|
|
||||||
|
|
||||||
minmax.Add(m1)
|
|
||||||
minmax.Add(m2)
|
|
||||||
minmax.Push(&acc)
|
|
||||||
|
|
||||||
expectedFields := map[string]interface{}{
|
|
||||||
"a_max": float64(1),
|
|
||||||
"a_min": float64(1),
|
|
||||||
"b_max": float64(3),
|
|
||||||
"b_min": float64(1),
|
|
||||||
"c_max": float64(3),
|
|
||||||
"c_min": float64(1),
|
|
||||||
"d_max": float64(3),
|
|
||||||
"d_min": float64(1),
|
|
||||||
"e_max": float64(3),
|
|
||||||
"e_min": float64(1),
|
|
||||||
"f_max": float64(2),
|
|
||||||
"f_min": float64(1),
|
|
||||||
"g_max": float64(2),
|
|
||||||
"g_min": float64(1),
|
|
||||||
"h_max": float64(2),
|
|
||||||
"h_min": float64(1),
|
|
||||||
"i_max": float64(2),
|
|
||||||
"i_min": float64(1),
|
|
||||||
"j_max": float64(3),
|
|
||||||
"j_min": float64(1),
|
|
||||||
"k_max": float64(200),
|
|
||||||
"k_min": float64(200),
|
|
||||||
}
|
|
||||||
expectedTags := map[string]string{
|
|
||||||
"foo": "bar",
|
|
||||||
}
|
|
||||||
acc.AssertContainsTaggedFields(t, "m1", expectedFields, expectedTags)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test two metrics getting added with a push/reset in between (simulates
|
|
||||||
// getting added in different periods.)
|
|
||||||
func TestMinMaxDifferentPeriods(t *testing.T) {
|
|
||||||
acc := testutil.Accumulator{}
|
|
||||||
minmax := NewMinMax()
|
|
||||||
|
|
||||||
minmax.Add(m1)
|
|
||||||
minmax.Push(&acc)
|
|
||||||
expectedFields := map[string]interface{}{
|
|
||||||
"a_max": float64(1),
|
|
||||||
"a_min": float64(1),
|
|
||||||
"b_max": float64(1),
|
|
||||||
"b_min": float64(1),
|
|
||||||
"c_max": float64(1),
|
|
||||||
"c_min": float64(1),
|
|
||||||
"d_max": float64(1),
|
|
||||||
"d_min": float64(1),
|
|
||||||
"e_max": float64(1),
|
|
||||||
"e_min": float64(1),
|
|
||||||
"f_max": float64(2),
|
|
||||||
"f_min": float64(2),
|
|
||||||
"g_max": float64(2),
|
|
||||||
"g_min": float64(2),
|
|
||||||
"h_max": float64(2),
|
|
||||||
"h_min": float64(2),
|
|
||||||
"i_max": float64(2),
|
|
||||||
"i_min": float64(2),
|
|
||||||
"j_max": float64(3),
|
|
||||||
"j_min": float64(3),
|
|
||||||
}
|
|
||||||
expectedTags := map[string]string{
|
|
||||||
"foo": "bar",
|
|
||||||
}
|
|
||||||
acc.AssertContainsTaggedFields(t, "m1", expectedFields, expectedTags)
|
|
||||||
|
|
||||||
acc.ClearMetrics()
|
|
||||||
minmax.Reset()
|
|
||||||
minmax.Add(m2)
|
|
||||||
minmax.Push(&acc)
|
|
||||||
expectedFields = map[string]interface{}{
|
|
||||||
"a_max": float64(1),
|
|
||||||
"a_min": float64(1),
|
|
||||||
"b_max": float64(3),
|
|
||||||
"b_min": float64(3),
|
|
||||||
"c_max": float64(3),
|
|
||||||
"c_min": float64(3),
|
|
||||||
"d_max": float64(3),
|
|
||||||
"d_min": float64(3),
|
|
||||||
"e_max": float64(3),
|
|
||||||
"e_min": float64(3),
|
|
||||||
"f_max": float64(1),
|
|
||||||
"f_min": float64(1),
|
|
||||||
"g_max": float64(1),
|
|
||||||
"g_min": float64(1),
|
|
||||||
"h_max": float64(1),
|
|
||||||
"h_min": float64(1),
|
|
||||||
"i_max": float64(1),
|
|
||||||
"i_min": float64(1),
|
|
||||||
"j_max": float64(1),
|
|
||||||
"j_min": float64(1),
|
|
||||||
"k_max": float64(200),
|
|
||||||
"k_min": float64(200),
|
|
||||||
}
|
|
||||||
expectedTags = map[string]string{
|
|
||||||
"foo": "bar",
|
|
||||||
}
|
|
||||||
acc.AssertContainsTaggedFields(t, "m1", expectedFields, expectedTags)
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package aggregators
|
|
||||||
|
|
||||||
import "github.com/influxdata/telegraf"
|
|
||||||
|
|
||||||
type Creator func() telegraf.Aggregator
|
|
||||||
|
|
||||||
var Aggregators = map[string]Creator{}
|
|
||||||
|
|
||||||
func Add(name string, creator Creator) {
|
|
||||||
Aggregators[name] = creator
|
|
||||||
}
|
|
||||||
@@ -27,12 +27,10 @@ import (
|
|||||||
_ "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/internal"
|
|
||||||
_ "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/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/kubernetes"
|
|
||||||
_ "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/logparser"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/lustre2"
|
_ "github.com/influxdata/telegraf/plugins/inputs/lustre2"
|
||||||
|
|||||||
@@ -2,16 +2,6 @@
|
|||||||
|
|
||||||
#### Plugin arguments:
|
#### Plugin arguments:
|
||||||
- **urls** []string: List of apache-status URLs to collect from. Default is "http://localhost/server-status?auto".
|
- **urls** []string: List of apache-status URLs to collect from. Default is "http://localhost/server-status?auto".
|
||||||
- **username** string: Username for HTTP basic authentication
|
|
||||||
- **password** string: Password for HTTP basic authentication
|
|
||||||
- **timeout** duration: time that the HTTP connection will remain waiting for response. Defalt 4 seconds ("4s")
|
|
||||||
|
|
||||||
##### Optional SSL Config
|
|
||||||
|
|
||||||
- **ssl_ca** string: the full path for the SSL CA certicate
|
|
||||||
- **ssl_cert** string: the full path for the SSL certificate
|
|
||||||
- **ssl_key** string: the full path for the key file
|
|
||||||
- **insecure_skip_verify** bool: if true HTTP client will skip all SSL verifications related to peer and host. Default to false
|
|
||||||
|
|
||||||
#### Description
|
#### Description
|
||||||
|
|
||||||
|
|||||||
@@ -11,42 +11,17 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
"github.com/influxdata/telegraf/internal"
|
|
||||||
"github.com/influxdata/telegraf/plugins/inputs"
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Apache struct {
|
type Apache struct {
|
||||||
Urls []string
|
Urls []string
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
ResponseTimeout internal.Duration
|
|
||||||
// Path to CA file
|
|
||||||
SSLCA string `toml:"ssl_ca"`
|
|
||||||
// Path to host cert file
|
|
||||||
SSLCert string `toml:"ssl_cert"`
|
|
||||||
// Path to cert key file
|
|
||||||
SSLKey string `toml:"ssl_key"`
|
|
||||||
// Use SSL but skip chain & host verification
|
|
||||||
InsecureSkipVerify bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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".
|
## Default is "http://localhost/server-status?auto".
|
||||||
urls = ["http://localhost/server-status?auto"]
|
urls = ["http://localhost/server-status?auto"]
|
||||||
## user credentials for basic HTTP authentication
|
|
||||||
username = "myuser"
|
|
||||||
password = "mypassword"
|
|
||||||
|
|
||||||
## Timeout to the complete conection and reponse time in seconds
|
|
||||||
response_timeout = "25s" ## default to 5 seconds
|
|
||||||
|
|
||||||
## Optional SSL Config
|
|
||||||
# ssl_ca = "/etc/telegraf/ca.pem"
|
|
||||||
# ssl_cert = "/etc/telegraf/cert.pem"
|
|
||||||
# ssl_key = "/etc/telegraf/key.pem"
|
|
||||||
## Use SSL but skip chain & host verification
|
|
||||||
# insecure_skip_verify = false
|
|
||||||
`
|
`
|
||||||
|
|
||||||
func (n *Apache) SampleConfig() string {
|
func (n *Apache) SampleConfig() string {
|
||||||
@@ -61,9 +36,6 @@ func (n *Apache) Gather(acc telegraf.Accumulator) error {
|
|||||||
if len(n.Urls) == 0 {
|
if len(n.Urls) == 0 {
|
||||||
n.Urls = []string{"http://localhost/server-status?auto"}
|
n.Urls = []string{"http://localhost/server-status?auto"}
|
||||||
}
|
}
|
||||||
if n.ResponseTimeout.Duration < time.Second {
|
|
||||||
n.ResponseTimeout.Duration = time.Second * 5
|
|
||||||
}
|
|
||||||
|
|
||||||
var outerr error
|
var outerr error
|
||||||
var errch = make(chan error)
|
var errch = make(chan error)
|
||||||
@@ -89,46 +61,21 @@ func (n *Apache) Gather(acc telegraf.Accumulator) error {
|
|||||||
return outerr
|
return outerr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Apache) gatherUrl(addr *url.URL, acc telegraf.Accumulator) error {
|
var tr = &http.Transport{
|
||||||
|
|
||||||
var tr *http.Transport
|
|
||||||
|
|
||||||
if addr.Scheme == "https" {
|
|
||||||
tlsCfg, err := internal.GetTLSConfig(
|
|
||||||
n.SSLCert, n.SSLKey, n.SSLCA, n.InsecureSkipVerify)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tr = &http.Transport{
|
|
||||||
ResponseHeaderTimeout: time.Duration(3 * time.Second),
|
ResponseHeaderTimeout: time.Duration(3 * time.Second),
|
||||||
TLSClientConfig: tlsCfg,
|
}
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tr = &http.Transport{
|
|
||||||
ResponseHeaderTimeout: time.Duration(3 * time.Second),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &http.Client{
|
var client = &http.Client{
|
||||||
Transport: tr,
|
Transport: tr,
|
||||||
Timeout: n.ResponseTimeout.Duration,
|
Timeout: time.Duration(4 * time.Second),
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", addr.String(), nil)
|
func (n *Apache) gatherUrl(addr *url.URL, acc telegraf.Accumulator) error {
|
||||||
|
resp, err := client.Get(addr.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error on new request to %s : %s\n", addr.String(), err)
|
return fmt.Errorf("error making HTTP request to %s: %s", addr.String(), err)
|
||||||
}
|
|
||||||
|
|
||||||
if len(n.Username) != 0 && len(n.Password) != 0 {
|
|
||||||
req.SetBasicAuth(n.Username, n.Password)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error on request to %s : %s\n", addr.String(), err)
|
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return fmt.Errorf("%s returned HTTP status %s", addr.String(), resp.Status)
|
return fmt.Errorf("%s returned HTTP status %s", addr.String(), resp.Status)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
#### Description
|
#### Description
|
||||||
|
|
||||||
The Cassandra plugin collects Cassandra 3 / JVM metrics exposed as MBean's attributes through jolokia REST endpoint. All metrics are collected for each server configured.
|
The Cassandra plugin collects Cassandra/JVM metrics exposed as MBean's attributes through jolokia REST endpoint. All metrics are collected for each server configured.
|
||||||
|
|
||||||
See: https://jolokia.org/ and [Cassandra Documentation](http://docs.datastax.com/en/cassandra/3.x/cassandra/operations/monitoringCassandraTOC.html)
|
See: https://jolokia.org/ and [Cassandra Documentation](http://docs.datastax.com/en/cassandra/3.x/cassandra/operations/monitoringCassandraTOC.html)
|
||||||
|
|
||||||
@@ -50,12 +50,12 @@ Here is a list of metrics that might be useful to monitor your cassandra cluster
|
|||||||
|
|
||||||
####measurement = cassandraCache
|
####measurement = cassandraCache
|
||||||
|
|
||||||
- /org.apache.cassandra.metrics:type=Cache,scope=KeyCache,name=Hits
|
- /org.apache.cassandra.metrics:type=Cache,scope=KeyCache,name=Hit
|
||||||
- /org.apache.cassandra.metrics:type=Cache,scope=KeyCache,name=Requests
|
- /org.apache.cassandra.metrics:type=Cache,scope=KeyCache,name=Requests
|
||||||
- /org.apache.cassandra.metrics:type=Cache,scope=KeyCache,name=Entries
|
- /org.apache.cassandra.metrics:type=Cache,scope=KeyCache,name=Entries
|
||||||
- /org.apache.cassandra.metrics:type=Cache,scope=KeyCache,name=Size
|
- /org.apache.cassandra.metrics:type=Cache,scope=KeyCache,name=Size
|
||||||
- /org.apache.cassandra.metrics:type=Cache,scope=KeyCache,name=Capacity
|
- /org.apache.cassandra.metrics:type=Cache,scope=KeyCache,name=Capacity
|
||||||
- /org.apache.cassandra.metrics:type=Cache,scope=RowCache,name=Hits
|
- /org.apache.cassandra.metrics:type=Cache,scope=RowCache,name=Hit
|
||||||
- /org.apache.cassandra.metrics:type=Cache,scope=RowCache,name=Requests
|
- /org.apache.cassandra.metrics:type=Cache,scope=RowCache,name=Requests
|
||||||
- /org.apache.cassandra.metrics:type=Cache,scope=RowCache,name=Entries
|
- /org.apache.cassandra.metrics:type=Cache,scope=RowCache,name=Entries
|
||||||
- /org.apache.cassandra.metrics:type=Cache,scope=RowCache,name=Size
|
- /org.apache.cassandra.metrics:type=Cache,scope=RowCache,name=Size
|
||||||
@@ -85,7 +85,7 @@ Here is a list of metrics that might be useful to monitor your cassandra cluster
|
|||||||
|
|
||||||
####measurement = cassandraCompaction
|
####measurement = cassandraCompaction
|
||||||
|
|
||||||
- /org.apache.cassandra.metrics:type=Compaction,name=CompletedTasks
|
- /org.apache.cassandra.metrics:type=Compaction,name=CompletedTask
|
||||||
- /org.apache.cassandra.metrics:type=Compaction,name=PendingTasks
|
- /org.apache.cassandra.metrics:type=Compaction,name=PendingTasks
|
||||||
- /org.apache.cassandra.metrics:type=Compaction,name=TotalCompactionsCompleted
|
- /org.apache.cassandra.metrics:type=Compaction,name=TotalCompactionsCompleted
|
||||||
- /org.apache.cassandra.metrics:type=Compaction,name=BytesCompacted
|
- /org.apache.cassandra.metrics:type=Compaction,name=BytesCompacted
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
|
|
||||||
This input plugin will capture specific statistics per cgroup.
|
This input plugin will capture specific statistics per cgroup.
|
||||||
|
|
||||||
Consider restricting paths to the set of cgroups you really
|
|
||||||
want to monitor if you have a large number of cgroups, to avoid
|
|
||||||
any cardinality issues.
|
|
||||||
|
|
||||||
Following file formats are supported:
|
Following file formats are supported:
|
||||||
|
|
||||||
* Single value
|
* Single value
|
||||||
@@ -37,8 +33,9 @@ KEY1 VAL1\n
|
|||||||
|
|
||||||
### Tags:
|
### Tags:
|
||||||
|
|
||||||
All measurements have the following tags:
|
Measurements don't have any specific tags unless you define them at the telegraf level (defaults). We
|
||||||
- path
|
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:
|
### Configuration:
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ type CGroup struct {
|
|||||||
|
|
||||||
var sampleConfig = `
|
var sampleConfig = `
|
||||||
## Directories in which to look for files, globs are supported.
|
## Directories in which to look for files, globs are supported.
|
||||||
## Consider restricting paths to the set of cgroups you really
|
|
||||||
## want to monitor if you have a large number of cgroups, to avoid
|
|
||||||
## any cardinality issues.
|
|
||||||
# paths = [
|
# paths = [
|
||||||
# "/cgroup/memory",
|
# "/cgroup/memory",
|
||||||
# "/cgroup/memory/child1",
|
# "/cgroup/memory/child1",
|
||||||
|
|||||||
@@ -56,10 +56,9 @@ func (g *CGroup) gatherDir(dir string, acc telegraf.Accumulator) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fields["path"] = dir
|
||||||
|
|
||||||
tags := map[string]string{"path": dir}
|
acc.AddFields(metricName, fields, nil)
|
||||||
|
|
||||||
acc.AddFields(metricName, fields, tags)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,13 @@
|
|||||||
package cgroup
|
package cgroup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf/testutil"
|
"github.com/influxdata/telegraf/testutil"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cg1 = &CGroup{
|
var cg1 = &CGroup{
|
||||||
@@ -21,15 +24,32 @@ var cg1 = &CGroup{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
func TestCgroupStatistics_1(t *testing.T) {
|
||||||
var acc testutil.Accumulator
|
var acc testutil.Accumulator
|
||||||
|
|
||||||
err := cg1.Gather(&acc)
|
err := cg1.Gather(&acc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tags := map[string]string{
|
|
||||||
"path": "testdata/memory",
|
|
||||||
}
|
|
||||||
fields := map[string]interface{}{
|
fields := map[string]interface{}{
|
||||||
"memory.stat.cache": 1739362304123123123,
|
"memory.stat.cache": 1739362304123123123,
|
||||||
"memory.stat.rss": 1775325184,
|
"memory.stat.rss": 1775325184,
|
||||||
@@ -42,8 +62,9 @@ func TestCgroupStatistics_1(t *testing.T) {
|
|||||||
"memory.limit_in_bytes": 223372036854771712,
|
"memory.limit_in_bytes": 223372036854771712,
|
||||||
"memory.use_hierarchy": "12-781",
|
"memory.use_hierarchy": "12-781",
|
||||||
"notify_on_release": 0,
|
"notify_on_release": 0,
|
||||||
|
"path": "testdata/memory",
|
||||||
}
|
}
|
||||||
acc.AssertContainsTaggedFields(t, "cgroup", fields, tags)
|
assertContainsFields(&acc, t, "cgroup", []map[string]interface{}{fields})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
@@ -59,16 +80,14 @@ func TestCgroupStatistics_2(t *testing.T) {
|
|||||||
err := cg2.Gather(&acc)
|
err := cg2.Gather(&acc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tags := map[string]string{
|
|
||||||
"path": "testdata/cpu",
|
|
||||||
}
|
|
||||||
fields := map[string]interface{}{
|
fields := map[string]interface{}{
|
||||||
"cpuacct.usage_percpu.0": -1452543795404,
|
"cpuacct.usage_percpu.0": -1452543795404,
|
||||||
"cpuacct.usage_percpu.1": 1376681271659,
|
"cpuacct.usage_percpu.1": 1376681271659,
|
||||||
"cpuacct.usage_percpu.2": 1450950799997,
|
"cpuacct.usage_percpu.2": 1450950799997,
|
||||||
"cpuacct.usage_percpu.3": -1473113374257,
|
"cpuacct.usage_percpu.3": -1473113374257,
|
||||||
|
"path": "testdata/cpu",
|
||||||
}
|
}
|
||||||
acc.AssertContainsTaggedFields(t, "cgroup", fields, tags)
|
assertContainsFields(&acc, t, "cgroup", []map[string]interface{}{fields})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
@@ -84,18 +103,16 @@ func TestCgroupStatistics_3(t *testing.T) {
|
|||||||
err := cg3.Gather(&acc)
|
err := cg3.Gather(&acc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tags := map[string]string{
|
|
||||||
"path": "testdata/memory/group_1",
|
|
||||||
}
|
|
||||||
fields := map[string]interface{}{
|
fields := map[string]interface{}{
|
||||||
"memory.limit_in_bytes": 223372036854771712,
|
"memory.limit_in_bytes": 223372036854771712,
|
||||||
|
"path": "testdata/memory/group_1",
|
||||||
}
|
}
|
||||||
acc.AssertContainsTaggedFields(t, "cgroup", fields, tags)
|
|
||||||
|
|
||||||
tags = map[string]string{
|
fieldsTwo := map[string]interface{}{
|
||||||
|
"memory.limit_in_bytes": 223372036854771712,
|
||||||
"path": "testdata/memory/group_2",
|
"path": "testdata/memory/group_2",
|
||||||
}
|
}
|
||||||
acc.AssertContainsTaggedFields(t, "cgroup", fields, tags)
|
assertContainsFields(&acc, t, "cgroup", []map[string]interface{}{fields, fieldsTwo})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
@@ -111,23 +128,22 @@ func TestCgroupStatistics_4(t *testing.T) {
|
|||||||
err := cg4.Gather(&acc)
|
err := cg4.Gather(&acc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tags := map[string]string{
|
|
||||||
"path": "testdata/memory/group_1/group_1_1",
|
|
||||||
}
|
|
||||||
fields := map[string]interface{}{
|
fields := map[string]interface{}{
|
||||||
"memory.limit_in_bytes": 223372036854771712,
|
"memory.limit_in_bytes": 223372036854771712,
|
||||||
|
"path": "testdata/memory/group_1/group_1_1",
|
||||||
}
|
}
|
||||||
acc.AssertContainsTaggedFields(t, "cgroup", fields, tags)
|
|
||||||
|
|
||||||
tags = map[string]string{
|
fieldsTwo := map[string]interface{}{
|
||||||
|
"memory.limit_in_bytes": 223372036854771712,
|
||||||
"path": "testdata/memory/group_1/group_1_2",
|
"path": "testdata/memory/group_1/group_1_2",
|
||||||
}
|
}
|
||||||
acc.AssertContainsTaggedFields(t, "cgroup", fields, tags)
|
|
||||||
|
|
||||||
tags = map[string]string{
|
fieldsThree := map[string]interface{}{
|
||||||
|
"memory.limit_in_bytes": 223372036854771712,
|
||||||
"path": "testdata/memory/group_2",
|
"path": "testdata/memory/group_2",
|
||||||
}
|
}
|
||||||
acc.AssertContainsTaggedFields(t, "cgroup", fields, tags)
|
|
||||||
|
assertContainsFields(&acc, t, "cgroup", []map[string]interface{}{fields, fieldsTwo, fieldsThree})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
@@ -143,18 +159,16 @@ func TestCgroupStatistics_5(t *testing.T) {
|
|||||||
err := cg5.Gather(&acc)
|
err := cg5.Gather(&acc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tags := map[string]string{
|
|
||||||
"path": "testdata/memory/group_1/group_1_1",
|
|
||||||
}
|
|
||||||
fields := map[string]interface{}{
|
fields := map[string]interface{}{
|
||||||
"memory.limit_in_bytes": 223372036854771712,
|
"memory.limit_in_bytes": 223372036854771712,
|
||||||
|
"path": "testdata/memory/group_1/group_1_1",
|
||||||
}
|
}
|
||||||
acc.AssertContainsTaggedFields(t, "cgroup", fields, tags)
|
|
||||||
|
|
||||||
tags = map[string]string{
|
fieldsTwo := map[string]interface{}{
|
||||||
|
"memory.limit_in_bytes": 223372036854771712,
|
||||||
"path": "testdata/memory/group_2/group_1_1",
|
"path": "testdata/memory/group_2/group_1_1",
|
||||||
}
|
}
|
||||||
acc.AssertContainsTaggedFields(t, "cgroup", fields, tags)
|
assertContainsFields(&acc, t, "cgroup", []map[string]interface{}{fields, fieldsTwo})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
@@ -170,13 +184,11 @@ func TestCgroupStatistics_6(t *testing.T) {
|
|||||||
err := cg6.Gather(&acc)
|
err := cg6.Gather(&acc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tags := map[string]string{
|
|
||||||
"path": "testdata/memory",
|
|
||||||
}
|
|
||||||
fields := map[string]interface{}{
|
fields := map[string]interface{}{
|
||||||
"memory.usage_in_bytes": 3513667584,
|
"memory.usage_in_bytes": 3513667584,
|
||||||
"memory.use_hierarchy": "12-781",
|
"memory.use_hierarchy": "12-781",
|
||||||
"memory.kmem.limit_in_bytes": 9223372036854771712,
|
"memory.kmem.limit_in_bytes": 9223372036854771712,
|
||||||
|
"path": "testdata/memory",
|
||||||
}
|
}
|
||||||
acc.AssertContainsTaggedFields(t, "cgroup", fields, tags)
|
assertContainsFields(&acc, t, "cgroup", []map[string]interface{}{fields})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,13 +103,9 @@ func processChronycOutput(out string) (map[string]interface{}, map[string]string
|
|||||||
tags["stratum"] = valueFields[0]
|
tags["stratum"] = valueFields[0]
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.Contains(strings.ToLower(name), "reference_id") {
|
|
||||||
tags["reference_id"] = valueFields[0]
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
value, err := strconv.ParseFloat(valueFields[0], 64)
|
value, err := strconv.ParseFloat(valueFields[0], 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tags[name] = strings.ToLower(strings.Join(valueFields, " "))
|
tags[name] = strings.ToLower(valueFields[0])
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.Contains(stats[1], "slow") {
|
if strings.Contains(stats[1], "slow") {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func TestGather(t *testing.T) {
|
|||||||
|
|
||||||
tags := map[string]string{
|
tags := map[string]string{
|
||||||
"reference_id": "192.168.1.22",
|
"reference_id": "192.168.1.22",
|
||||||
"leap_status": "not synchronized",
|
"leap_status": "normal",
|
||||||
"stratum": "3",
|
"stratum": "3",
|
||||||
}
|
}
|
||||||
fields := map[string]interface{}{
|
fields := map[string]interface{}{
|
||||||
@@ -85,7 +85,7 @@ Skew : 0.006 ppm
|
|||||||
Root delay : 0.001655 seconds
|
Root delay : 0.001655 seconds
|
||||||
Root dispersion : 0.003307 seconds
|
Root dispersion : 0.003307 seconds
|
||||||
Update interval : 507.2 seconds
|
Update interval : 507.2 seconds
|
||||||
Leap status : Not synchronized
|
Leap status : Normal
|
||||||
`
|
`
|
||||||
|
|
||||||
args := os.Args
|
args := os.Args
|
||||||
|
|||||||
@@ -18,28 +18,21 @@ API endpoint. In the following order the plugin will attempt to authenticate.
|
|||||||
```toml
|
```toml
|
||||||
[[inputs.cloudwatch]]
|
[[inputs.cloudwatch]]
|
||||||
## Amazon Region (required)
|
## Amazon Region (required)
|
||||||
region = "us-east-1"
|
region = 'us-east-1'
|
||||||
|
|
||||||
# The minimum period for Cloudwatch metrics is 1 minute (60s). However not all
|
|
||||||
# metrics are made available to the 1 minute period. Some are collected at
|
|
||||||
# 3 minute and 5 minutes intervals. See https://aws.amazon.com/cloudwatch/faqs/#monitoring.
|
|
||||||
# Note that if a period is configured that is smaller than the minimum for a
|
|
||||||
# particular metric, that metric will not be returned by the Cloudwatch API
|
|
||||||
# and will not be collected by Telegraf.
|
|
||||||
#
|
|
||||||
## Requested CloudWatch aggregation Period (required - must be a multiple of 60s)
|
## Requested CloudWatch aggregation Period (required - must be a multiple of 60s)
|
||||||
period = "5m"
|
period = '1m'
|
||||||
|
|
||||||
## Collection Delay (required - must account for metrics availability via CloudWatch API)
|
## Collection Delay (required - must account for metrics availability via CloudWatch API)
|
||||||
delay = "5m"
|
delay = '1m'
|
||||||
|
|
||||||
## Override global run interval (optional - defaults to global interval)
|
## Override global run interval (optional - defaults to global interval)
|
||||||
## Recomended: use metric 'interval' that is a multiple of 'period' to avoid
|
## Recomended: use metric 'interval' that is a multiple of 'period' to avoid
|
||||||
## gaps or overlap in pulled data
|
## gaps or overlap in pulled data
|
||||||
interval = "5m"
|
interval = '1m'
|
||||||
|
|
||||||
## 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
|
## 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
|
## 10 reqs/sec, so if you define multiple namespaces, these should add up to a
|
||||||
@@ -50,16 +43,16 @@ API endpoint. In the following order the plugin will attempt to authenticate.
|
|||||||
## 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
|
||||||
[[inputs.cloudwatch.metrics]]
|
[[inputs.cloudwatch.metrics]]
|
||||||
names = ["Latency", "RequestCount"]
|
names = ['Latency', 'RequestCount']
|
||||||
|
|
||||||
## Dimension filters for Metric (optional)
|
## Dimension filters for Metric (optional)
|
||||||
[[inputs.cloudwatch.metrics.dimensions]]
|
[[inputs.cloudwatch.metrics.dimensions]]
|
||||||
name = "LoadBalancerName"
|
name = 'LoadBalancerName'
|
||||||
value = "p-example"
|
value = 'p-example'
|
||||||
|
|
||||||
[[inputs.cloudwatch.metrics.dimensions]]
|
[[inputs.cloudwatch.metrics.dimensions]]
|
||||||
name = "AvailabilityZone"
|
name = 'AvailabilityZone'
|
||||||
value = "*"
|
value = '*'
|
||||||
```
|
```
|
||||||
#### Requirements and Terminology
|
#### Requirements and Terminology
|
||||||
|
|
||||||
@@ -78,16 +71,16 @@ wildcard dimension is ignored.
|
|||||||
Example:
|
Example:
|
||||||
```
|
```
|
||||||
[[inputs.cloudwatch.metrics]]
|
[[inputs.cloudwatch.metrics]]
|
||||||
names = ["Latency"]
|
names = ['Latency']
|
||||||
|
|
||||||
## Dimension filters for Metric (optional)
|
## Dimension filters for Metric (optional)
|
||||||
[[inputs.cloudwatch.metrics.dimensions]]
|
[[inputs.cloudwatch.metrics.dimensions]]
|
||||||
name = "LoadBalancerName"
|
name = 'LoadBalancerName'
|
||||||
value = "p-example"
|
value = 'p-example'
|
||||||
|
|
||||||
[[inputs.cloudwatch.metrics.dimensions]]
|
[[inputs.cloudwatch.metrics.dimensions]]
|
||||||
name = "AvailabilityZone"
|
name = 'AvailabilityZone'
|
||||||
value = "*"
|
value = '*'
|
||||||
```
|
```
|
||||||
|
|
||||||
If the following ELBs are available:
|
If the following ELBs are available:
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ type (
|
|||||||
func (c *CloudWatch) SampleConfig() string {
|
func (c *CloudWatch) SampleConfig() string {
|
||||||
return `
|
return `
|
||||||
## Amazon Region
|
## Amazon Region
|
||||||
region = "us-east-1"
|
region = 'us-east-1'
|
||||||
|
|
||||||
## Amazon Credentials
|
## Amazon Credentials
|
||||||
## Credentials are loaded in the following order
|
## Credentials are loaded in the following order
|
||||||
@@ -80,29 +80,22 @@ func (c *CloudWatch) SampleConfig() string {
|
|||||||
#profile = ""
|
#profile = ""
|
||||||
#shared_credential_file = ""
|
#shared_credential_file = ""
|
||||||
|
|
||||||
# The minimum period for Cloudwatch metrics is 1 minute (60s). However not all
|
|
||||||
# metrics are made available to the 1 minute period. Some are collected at
|
|
||||||
# 3 minute and 5 minutes intervals. See https://aws.amazon.com/cloudwatch/faqs/#monitoring.
|
|
||||||
# Note that if a period is configured that is smaller than the minimum for a
|
|
||||||
# particular metric, that metric will not be returned by the Cloudwatch API
|
|
||||||
# and will not be collected by Telegraf.
|
|
||||||
#
|
|
||||||
## Requested CloudWatch aggregation Period (required - must be a multiple of 60s)
|
## Requested CloudWatch aggregation Period (required - must be a multiple of 60s)
|
||||||
period = "5m"
|
period = '1m'
|
||||||
|
|
||||||
## Collection Delay (required - must account for metrics availability via CloudWatch API)
|
## Collection Delay (required - must account for metrics availability via CloudWatch API)
|
||||||
delay = "5m"
|
delay = '1m'
|
||||||
|
|
||||||
## Recomended: use metric 'interval' that is a multiple of 'period' to avoid
|
## Recomended: use metric 'interval' that is a multiple of 'period' to avoid
|
||||||
## gaps or overlap in pulled data
|
## gaps or overlap in pulled data
|
||||||
interval = "5m"
|
interval = '1m'
|
||||||
|
|
||||||
## Configure the TTL for the internal cache of metrics.
|
## Configure the TTL for the internal cache of metrics.
|
||||||
## Defaults to 1 hr if not specified
|
## Defaults to 1 hr if not specified
|
||||||
#cache_ttl = "10m"
|
#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
|
## 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
|
## 10 reqs/sec, so if you define multiple namespaces, these should add up to a
|
||||||
@@ -113,12 +106,12 @@ func (c *CloudWatch) SampleConfig() string {
|
|||||||
## 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
|
||||||
#[[inputs.cloudwatch.metrics]]
|
#[[inputs.cloudwatch.metrics]]
|
||||||
# names = ["Latency", "RequestCount"]
|
# names = ['Latency', 'RequestCount']
|
||||||
#
|
#
|
||||||
# ## Dimension filters for Metric (optional)
|
# ## Dimension filters for Metric (optional)
|
||||||
# [[inputs.cloudwatch.metrics.dimensions]]
|
# [[inputs.cloudwatch.metrics.dimensions]]
|
||||||
# name = "LoadBalancerName"
|
# name = 'LoadBalancerName'
|
||||||
# value = "p-example"
|
# value = 'p-example'
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,6 +133,7 @@ func (c *CloudWatch) Gather(acc telegraf.Accumulator) error {
|
|||||||
if !hasWilcard(m.Dimensions) {
|
if !hasWilcard(m.Dimensions) {
|
||||||
dimensions := make([]*cloudwatch.Dimension, len(m.Dimensions))
|
dimensions := make([]*cloudwatch.Dimension, len(m.Dimensions))
|
||||||
for k, d := range m.Dimensions {
|
for k, d := range m.Dimensions {
|
||||||
|
fmt.Printf("Dimension [%s]:[%s]\n", d.Name, d.Value)
|
||||||
dimensions[k] = &cloudwatch.Dimension{
|
dimensions[k] = &cloudwatch.Dimension{
|
||||||
Name: aws.String(d.Name),
|
Name: aws.String(d.Name),
|
||||||
Value: aws.String(d.Value),
|
Value: aws.String(d.Value),
|
||||||
@@ -235,12 +229,13 @@ func (c *CloudWatch) initializeCloudWatch() error {
|
|||||||
/*
|
/*
|
||||||
* Fetch available metrics for given CloudWatch Namespace
|
* Fetch available metrics for given CloudWatch Namespace
|
||||||
*/
|
*/
|
||||||
func (c *CloudWatch) fetchNamespaceMetrics() ([]*cloudwatch.Metric, error) {
|
func (c *CloudWatch) fetchNamespaceMetrics() (metrics []*cloudwatch.Metric, err error) {
|
||||||
if c.metricCache != nil && c.metricCache.IsValid() {
|
if c.metricCache != nil && c.metricCache.IsValid() {
|
||||||
return c.metricCache.Metrics, nil
|
metrics = c.metricCache.Metrics
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
metrics := []*cloudwatch.Metric{}
|
metrics = []*cloudwatch.Metric{}
|
||||||
|
|
||||||
var token *string
|
var token *string
|
||||||
for more := true; more; {
|
for more := true; more; {
|
||||||
@@ -268,7 +263,7 @@ func (c *CloudWatch) fetchNamespaceMetrics() ([]*cloudwatch.Metric, error) {
|
|||||||
TTL: c.CacheTTL.Duration,
|
TTL: c.CacheTTL.Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
return metrics, nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -60,8 +60,6 @@ type indexHealth struct {
|
|||||||
|
|
||||||
const sampleConfig = `
|
const sampleConfig = `
|
||||||
## specify a list of one or more Elasticsearch servers
|
## specify a list of one or more Elasticsearch servers
|
||||||
# you can add username and password to your url to use basic authentication:
|
|
||||||
# servers = ["http://user:pass@localhost:9200"]
|
|
||||||
servers = ["http://localhost:9200"]
|
servers = ["http://localhost:9200"]
|
||||||
|
|
||||||
## Timeout for HTTP requests to the elastic search server(s)
|
## Timeout for HTTP requests to the elastic search server(s)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/kballard/go-shellquote"
|
"github.com/gonuts/go-shellquote"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
"github.com/influxdata/telegraf/internal"
|
"github.com/influxdata/telegraf/internal"
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const malformedJson = `
|
|||||||
"status": "green",
|
"status": "green",
|
||||||
`
|
`
|
||||||
|
|
||||||
const lineProtocol = "cpu,host=foo,datacenter=us-east usage_idle=99,usage_busy=1\n"
|
const lineProtocol = "cpu,host=foo,datacenter=us-east usage_idle=99,usage_busy=1"
|
||||||
|
|
||||||
const lineProtocolMulti = `
|
const lineProtocolMulti = `
|
||||||
cpu,cpu=cpu0,host=foo,datacenter=us-east usage_idle=99,usage_busy=1
|
cpu,cpu=cpu0,host=foo,datacenter=us-east usage_idle=99,usage_busy=1
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
# HAproxy Input Plugin
|
|
||||||
|
|
||||||
[HAproxy](http://www.haproxy.org/) input plugin gathers metrics directly from any running HAproxy instance. It can do so by using CSV generated by HAproxy status page or from admin socket(s).
|
|
||||||
|
|
||||||
### Configuration:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
# SampleConfig
|
|
||||||
[[inputs.haproxy]]
|
|
||||||
servers = ["http://1.2.3.4/haproxy?stats", "/var/run/haproxy*.sock"]
|
|
||||||
```
|
|
||||||
|
|
||||||
Server addresses need to explicitly start with 'http' if you wish to use HAproxy status page. Otherwise, address will be assumed to be an UNIX socket and protocol (if present) will be discarded.
|
|
||||||
|
|
||||||
Following examples will all resolve to the same socket:
|
|
||||||
```
|
|
||||||
socket:/var/run/haproxy.sock
|
|
||||||
unix:/var/run/haproxy.sock
|
|
||||||
foo:/var/run/haproxy.sock
|
|
||||||
/var/run/haproxy.sock
|
|
||||||
```
|
|
||||||
|
|
||||||
When using socket names, wildcard expansion is supported so plugin can gather stats from multiple sockets at once.
|
|
||||||
|
|
||||||
If no servers are specified, then the default address of `http://127.0.0.1:1936/haproxy?stats` will be used.
|
|
||||||
|
|
||||||
### Measurements & Fields:
|
|
||||||
|
|
||||||
Plugin will gather measurements outlined in [HAproxy CSV format documentation](https://cbonte.github.io/haproxy-dconv/1.5/configuration.html#9.1).
|
|
||||||
|
|
||||||
### Tags:
|
|
||||||
|
|
||||||
- All measurements have the following tags:
|
|
||||||
- server - address of server data is gathered from
|
|
||||||
- proxy - proxy name as reported in `pxname`
|
|
||||||
- sv - service name as reported in `svname`
|
|
||||||
|
|
||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -18,7 +17,7 @@ import (
|
|||||||
"github.com/influxdata/telegraf/plugins/inputs"
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||||||
)
|
)
|
||||||
|
|
||||||
//CSV format: https://cbonte.github.io/haproxy-dconv/1.5/configuration.html#9.1
|
//CSV format: https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#9.1
|
||||||
const (
|
const (
|
||||||
HF_PXNAME = 0 // 0. pxname [LFBS]: proxy name
|
HF_PXNAME = 0 // 0. pxname [LFBS]: proxy name
|
||||||
HF_SVNAME = 1 // 1. svname [LFBS]: service name (FRONTEND for frontend, BACKEND for backend, any name for server/listener)
|
HF_SVNAME = 1 // 1. svname [LFBS]: service name (FRONTEND for frontend, BACKEND for backend, any name for server/listener)
|
||||||
@@ -94,15 +93,12 @@ var sampleConfig = `
|
|||||||
## 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
|
## Make sure you specify the complete path to the stats endpoint
|
||||||
## including the protocol, ie http://10.10.3.33:1936/haproxy?stats
|
## ie 10.10.3.33:1936/haproxy?stats
|
||||||
#
|
#
|
||||||
## If no servers are specified, then default to 127.0.0.1:1936/haproxy?stats
|
## If no servers are specified, then default to 127.0.0.1:1936/haproxy?stats
|
||||||
servers = ["http://myhaproxy.com:1936/haproxy?stats"]
|
servers = ["http://myhaproxy.com:1936/haproxy?stats"]
|
||||||
##
|
## Or you can also use local socket
|
||||||
## You can also use local socket with standard wildcard globbing.
|
## servers = ["socket:/run/haproxy/admin.sock"]
|
||||||
## Server address not starting with 'http' will be treated as a possible
|
|
||||||
## socket, so both examples below are valid.
|
|
||||||
## servers = ["socket:/run/haproxy/admin.sock", "/run/haproxy/*.sock"]
|
|
||||||
`
|
`
|
||||||
|
|
||||||
func (r *haproxy) SampleConfig() string {
|
func (r *haproxy) SampleConfig() string {
|
||||||
@@ -120,36 +116,10 @@ func (g *haproxy) Gather(acc telegraf.Accumulator) error {
|
|||||||
return g.gatherServer("http://127.0.0.1:1936/haproxy?stats", acc)
|
return g.gatherServer("http://127.0.0.1:1936/haproxy?stats", acc)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints := make([]string, 0, len(g.Servers))
|
|
||||||
|
|
||||||
for _, endpoint := range g.Servers {
|
|
||||||
|
|
||||||
if strings.HasPrefix(endpoint, "http") {
|
|
||||||
endpoints = append(endpoints, endpoint)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
socketPath := getSocketAddr(endpoint)
|
|
||||||
|
|
||||||
matches, err := filepath.Glob(socketPath)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(matches) == 0 {
|
|
||||||
endpoints = append(endpoints, socketPath)
|
|
||||||
} else {
|
|
||||||
for _, match := range matches {
|
|
||||||
endpoints = append(endpoints, match)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
errChan := errchan.New(len(endpoints))
|
errChan := errchan.New(len(g.Servers))
|
||||||
wg.Add(len(endpoints))
|
wg.Add(len(g.Servers))
|
||||||
for _, server := range endpoints {
|
for _, server := range g.Servers {
|
||||||
go func(serv string) {
|
go func(serv string) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
errChan.C <- g.gatherServer(serv, acc)
|
errChan.C <- g.gatherServer(serv, acc)
|
||||||
@@ -161,7 +131,14 @@ func (g *haproxy) Gather(acc telegraf.Accumulator) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *haproxy) gatherServerSocket(addr string, acc telegraf.Accumulator) error {
|
func (g *haproxy) gatherServerSocket(addr string, acc telegraf.Accumulator) error {
|
||||||
socketPath := getSocketAddr(addr)
|
var socketPath string
|
||||||
|
socketAddr := strings.Split(addr, ":")
|
||||||
|
|
||||||
|
if len(socketAddr) >= 2 {
|
||||||
|
socketPath = socketAddr[1]
|
||||||
|
} else {
|
||||||
|
socketPath = socketAddr[0]
|
||||||
|
}
|
||||||
|
|
||||||
c, err := net.Dial("unix", socketPath)
|
c, err := net.Dial("unix", socketPath)
|
||||||
|
|
||||||
@@ -219,16 +196,6 @@ func (g *haproxy) gatherServer(addr string, acc telegraf.Accumulator) error {
|
|||||||
return importCsvResult(res.Body, acc, u.Host)
|
return importCsvResult(res.Body, acc, u.Host)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSocketAddr(sock string) string {
|
|
||||||
socketAddr := strings.Split(sock, ":")
|
|
||||||
|
|
||||||
if len(socketAddr) >= 2 {
|
|
||||||
return socketAddr[1]
|
|
||||||
} else {
|
|
||||||
return socketAddr[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func importCsvResult(r io.Reader, acc telegraf.Accumulator, host string) error {
|
func importCsvResult(r io.Reader, acc telegraf.Accumulator, host string) error {
|
||||||
csv := csv.NewReader(r)
|
csv := csv.NewReader(r)
|
||||||
result, err := csv.ReadAll()
|
result, err := csv.ReadAll()
|
||||||
|
|||||||
@@ -72,106 +72,6 @@ func TestHaproxyGeneratesMetricsWithAuthentication(t *testing.T) {
|
|||||||
"sv": "host0",
|
"sv": "host0",
|
||||||
}
|
}
|
||||||
|
|
||||||
fields := HaproxyGetFieldValues()
|
|
||||||
acc.AssertContainsTaggedFields(t, "haproxy", fields, tags)
|
|
||||||
|
|
||||||
//Here, we should get error because we don't pass authentication data
|
|
||||||
r = &haproxy{
|
|
||||||
Servers: []string{ts.URL},
|
|
||||||
}
|
|
||||||
|
|
||||||
err = r.Gather(&acc)
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHaproxyGeneratesMetricsWithoutAuthentication(t *testing.T) {
|
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
fmt.Fprint(w, csvOutputSample)
|
|
||||||
}))
|
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
r := &haproxy{
|
|
||||||
Servers: []string{ts.URL},
|
|
||||||
}
|
|
||||||
|
|
||||||
var acc testutil.Accumulator
|
|
||||||
|
|
||||||
err := r.Gather(&acc)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
tags := map[string]string{
|
|
||||||
"proxy": "be_app",
|
|
||||||
"server": ts.Listener.Addr().String(),
|
|
||||||
"sv": "host0",
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := HaproxyGetFieldValues()
|
|
||||||
acc.AssertContainsTaggedFields(t, "haproxy", fields, tags)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHaproxyGeneratesMetricsUsingSocket(t *testing.T) {
|
|
||||||
var randomNumber int64
|
|
||||||
var sockets [5]net.Listener
|
|
||||||
_globmask := "/tmp/test-haproxy*.sock"
|
|
||||||
_badmask := "/tmp/test-fail-haproxy*.sock"
|
|
||||||
|
|
||||||
for i := 0; i < 5; i++ {
|
|
||||||
binary.Read(rand.Reader, binary.LittleEndian, &randomNumber)
|
|
||||||
sockname := fmt.Sprintf("/tmp/test-haproxy%d.sock", randomNumber)
|
|
||||||
|
|
||||||
sock, err := net.Listen("unix", sockname)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Cannot initialize socket ")
|
|
||||||
}
|
|
||||||
|
|
||||||
sockets[i] = sock
|
|
||||||
defer sock.Close()
|
|
||||||
|
|
||||||
s := statServer{}
|
|
||||||
go s.serverSocket(sock)
|
|
||||||
}
|
|
||||||
|
|
||||||
r := &haproxy{
|
|
||||||
Servers: []string{_globmask},
|
|
||||||
}
|
|
||||||
|
|
||||||
var acc testutil.Accumulator
|
|
||||||
|
|
||||||
err := r.Gather(&acc)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
fields := HaproxyGetFieldValues()
|
|
||||||
|
|
||||||
for _, sock := range sockets {
|
|
||||||
tags := map[string]string{
|
|
||||||
"proxy": "be_app",
|
|
||||||
"server": sock.Addr().String(),
|
|
||||||
"sv": "host0",
|
|
||||||
}
|
|
||||||
|
|
||||||
acc.AssertContainsTaggedFields(t, "haproxy", fields, tags)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This mask should not match any socket
|
|
||||||
r.Servers = []string{_badmask}
|
|
||||||
|
|
||||||
err = r.Gather(&acc)
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
//When not passing server config, we default to localhost
|
|
||||||
//We just want to make sure we did request stat from localhost
|
|
||||||
func TestHaproxyDefaultGetFromLocalhost(t *testing.T) {
|
|
||||||
r := &haproxy{}
|
|
||||||
|
|
||||||
var acc testutil.Accumulator
|
|
||||||
|
|
||||||
err := r.Gather(&acc)
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.Contains(t, err.Error(), "127.0.0.1:1936/haproxy?stats/;csv")
|
|
||||||
}
|
|
||||||
|
|
||||||
func HaproxyGetFieldValues() map[string]interface{} {
|
|
||||||
fields := map[string]interface{}{
|
fields := map[string]interface{}{
|
||||||
"active_servers": uint64(1),
|
"active_servers": uint64(1),
|
||||||
"backup_servers": uint64(0),
|
"backup_servers": uint64(0),
|
||||||
@@ -204,7 +104,146 @@ func HaproxyGetFieldValues() map[string]interface{} {
|
|||||||
"wredis": uint64(0),
|
"wredis": uint64(0),
|
||||||
"wretr": uint64(1),
|
"wretr": uint64(1),
|
||||||
}
|
}
|
||||||
return fields
|
acc.AssertContainsTaggedFields(t, "haproxy", fields, tags)
|
||||||
|
|
||||||
|
//Here, we should get error because we don't pass authentication data
|
||||||
|
r = &haproxy{
|
||||||
|
Servers: []string{ts.URL},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.Gather(&acc)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHaproxyGeneratesMetricsWithoutAuthentication(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Fprint(w, csvOutputSample)
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
r := &haproxy{
|
||||||
|
Servers: []string{ts.URL},
|
||||||
|
}
|
||||||
|
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
|
||||||
|
err := r.Gather(&acc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tags := map[string]string{
|
||||||
|
"proxy": "be_app",
|
||||||
|
"server": ts.Listener.Addr().String(),
|
||||||
|
"sv": "host0",
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"active_servers": uint64(1),
|
||||||
|
"backup_servers": uint64(0),
|
||||||
|
"bin": uint64(510913516),
|
||||||
|
"bout": uint64(2193856571),
|
||||||
|
"check_duration": uint64(10),
|
||||||
|
"cli_abort": uint64(73),
|
||||||
|
"ctime": uint64(2),
|
||||||
|
"downtime": uint64(0),
|
||||||
|
"dresp": uint64(0),
|
||||||
|
"econ": uint64(0),
|
||||||
|
"eresp": uint64(1),
|
||||||
|
"http_response.1xx": uint64(0),
|
||||||
|
"http_response.2xx": uint64(119534),
|
||||||
|
"http_response.3xx": uint64(48051),
|
||||||
|
"http_response.4xx": uint64(2345),
|
||||||
|
"http_response.5xx": uint64(1056),
|
||||||
|
"lbtot": uint64(171013),
|
||||||
|
"qcur": uint64(0),
|
||||||
|
"qmax": uint64(0),
|
||||||
|
"qtime": uint64(0),
|
||||||
|
"rate": uint64(3),
|
||||||
|
"rate_max": uint64(12),
|
||||||
|
"rtime": uint64(312),
|
||||||
|
"scur": uint64(1),
|
||||||
|
"smax": uint64(32),
|
||||||
|
"srv_abort": uint64(1),
|
||||||
|
"stot": uint64(171014),
|
||||||
|
"ttime": uint64(2341),
|
||||||
|
"wredis": uint64(0),
|
||||||
|
"wretr": uint64(1),
|
||||||
|
}
|
||||||
|
acc.AssertContainsTaggedFields(t, "haproxy", fields, tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHaproxyGeneratesMetricsUsingSocket(t *testing.T) {
|
||||||
|
var randomNumber int64
|
||||||
|
binary.Read(rand.Reader, binary.LittleEndian, &randomNumber)
|
||||||
|
sock, err := net.Listen("unix", fmt.Sprintf("/tmp/test-haproxy%d.sock", randomNumber))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Cannot initialize socket ")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer sock.Close()
|
||||||
|
|
||||||
|
s := statServer{}
|
||||||
|
go s.serverSocket(sock)
|
||||||
|
|
||||||
|
r := &haproxy{
|
||||||
|
Servers: []string{sock.Addr().String()},
|
||||||
|
}
|
||||||
|
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
|
||||||
|
err = r.Gather(&acc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tags := map[string]string{
|
||||||
|
"proxy": "be_app",
|
||||||
|
"server": sock.Addr().String(),
|
||||||
|
"sv": "host0",
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"active_servers": uint64(1),
|
||||||
|
"backup_servers": uint64(0),
|
||||||
|
"bin": uint64(510913516),
|
||||||
|
"bout": uint64(2193856571),
|
||||||
|
"check_duration": uint64(10),
|
||||||
|
"cli_abort": uint64(73),
|
||||||
|
"ctime": uint64(2),
|
||||||
|
"downtime": uint64(0),
|
||||||
|
"dresp": uint64(0),
|
||||||
|
"econ": uint64(0),
|
||||||
|
"eresp": uint64(1),
|
||||||
|
"http_response.1xx": uint64(0),
|
||||||
|
"http_response.2xx": uint64(119534),
|
||||||
|
"http_response.3xx": uint64(48051),
|
||||||
|
"http_response.4xx": uint64(2345),
|
||||||
|
"http_response.5xx": uint64(1056),
|
||||||
|
"lbtot": uint64(171013),
|
||||||
|
"qcur": uint64(0),
|
||||||
|
"qmax": uint64(0),
|
||||||
|
"qtime": uint64(0),
|
||||||
|
"rate": uint64(3),
|
||||||
|
"rate_max": uint64(12),
|
||||||
|
"rtime": uint64(312),
|
||||||
|
"scur": uint64(1),
|
||||||
|
"smax": uint64(32),
|
||||||
|
"srv_abort": uint64(1),
|
||||||
|
"stot": uint64(171014),
|
||||||
|
"ttime": uint64(2341),
|
||||||
|
"wredis": uint64(0),
|
||||||
|
"wretr": uint64(1),
|
||||||
|
}
|
||||||
|
acc.AssertContainsTaggedFields(t, "haproxy", fields, tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
//When not passing server config, we default to localhost
|
||||||
|
//We just want to make sure we did request stat from localhost
|
||||||
|
func TestHaproxyDefaultGetFromLocalhost(t *testing.T) {
|
||||||
|
r := &haproxy{}
|
||||||
|
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
|
||||||
|
err := r.Gather(&acc)
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "127.0.0.1:1936/haproxy?stats/;csv")
|
||||||
}
|
}
|
||||||
|
|
||||||
const csvOutputSample = `
|
const csvOutputSample = `
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
package http_listener
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
type pool struct {
|
|
||||||
buffers chan []byte
|
|
||||||
size int
|
|
||||||
|
|
||||||
created int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPool returns a new pool object.
|
|
||||||
// n is the number of buffers
|
|
||||||
// bufSize is the size (in bytes) of each buffer
|
|
||||||
func NewPool(n, bufSize int) *pool {
|
|
||||||
return &pool{
|
|
||||||
buffers: make(chan []byte, n),
|
|
||||||
size: bufSize,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pool) get() []byte {
|
|
||||||
select {
|
|
||||||
case b := <-p.buffers:
|
|
||||||
return b
|
|
||||||
default:
|
|
||||||
atomic.AddInt64(&p.created, 1)
|
|
||||||
return make([]byte, p.size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pool) put(b []byte) {
|
|
||||||
select {
|
|
||||||
case p.buffers <- b:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pool) ncreated() int64 {
|
|
||||||
return atomic.LoadInt64(&p.created)
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package http_listener
|
package http_listener
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
"fmt"
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -13,172 +13,136 @@ import (
|
|||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
"github.com/influxdata/telegraf/internal"
|
"github.com/influxdata/telegraf/internal"
|
||||||
"github.com/influxdata/telegraf/plugins/inputs"
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||||||
"github.com/influxdata/telegraf/plugins/parsers/influx"
|
"github.com/influxdata/telegraf/plugins/inputs/http_listener/stoppableListener"
|
||||||
"github.com/influxdata/telegraf/selfstat"
|
"github.com/influxdata/telegraf/plugins/parsers"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
type HttpListener struct {
|
||||||
// DEFAULT_MAX_BODY_SIZE is the default maximum request body size, in bytes.
|
|
||||||
// if the request body is over this size, we will return an HTTP 413 error.
|
|
||||||
// 500 MB
|
|
||||||
DEFAULT_MAX_BODY_SIZE = 500 * 1024 * 1024
|
|
||||||
|
|
||||||
// MAX_LINE_SIZE is the maximum size, in bytes, that can be allocated for
|
|
||||||
// a single InfluxDB point.
|
|
||||||
// 64 KB
|
|
||||||
DEFAULT_MAX_LINE_SIZE = 64 * 1024
|
|
||||||
)
|
|
||||||
|
|
||||||
type HTTPListener struct {
|
|
||||||
ServiceAddress string
|
ServiceAddress string
|
||||||
ReadTimeout internal.Duration
|
ReadTimeout internal.Duration
|
||||||
WriteTimeout internal.Duration
|
WriteTimeout internal.Duration
|
||||||
MaxBodySize int64
|
|
||||||
MaxLineSize int
|
|
||||||
|
|
||||||
mu sync.Mutex
|
sync.Mutex
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
|
|
||||||
listener net.Listener
|
listener *stoppableListener.StoppableListener
|
||||||
|
|
||||||
parser influx.InfluxParser
|
parser parsers.Parser
|
||||||
acc telegraf.Accumulator
|
acc telegraf.Accumulator
|
||||||
pool *pool
|
|
||||||
|
|
||||||
BytesRecv selfstat.Stat
|
|
||||||
RequestsServed selfstat.Stat
|
|
||||||
WritesServed selfstat.Stat
|
|
||||||
QueriesServed selfstat.Stat
|
|
||||||
PingsServed selfstat.Stat
|
|
||||||
RequestsRecv selfstat.Stat
|
|
||||||
WritesRecv selfstat.Stat
|
|
||||||
QueriesRecv selfstat.Stat
|
|
||||||
PingsRecv selfstat.Stat
|
|
||||||
NotFoundsServed selfstat.Stat
|
|
||||||
BuffersCreated selfstat.Stat
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const sampleConfig = `
|
const sampleConfig = `
|
||||||
## Address and port to host HTTP listener on
|
## Address and port to host HTTP listener on
|
||||||
service_address = ":8186"
|
service_address = ":8186"
|
||||||
|
|
||||||
## maximum duration before timing out read of the request
|
## timeouts
|
||||||
read_timeout = "10s"
|
read_timeout = "10s"
|
||||||
## maximum duration before timing out write of the response
|
|
||||||
write_timeout = "10s"
|
write_timeout = "10s"
|
||||||
|
|
||||||
## Maximum allowed http request body size in bytes.
|
|
||||||
## 0 means to use the default of 536,870,912 bytes (500 mebibytes)
|
|
||||||
max_body_size = 0
|
|
||||||
|
|
||||||
## Maximum line size allowed to be sent in bytes.
|
|
||||||
## 0 means to use the default of 65536 bytes (64 kibibytes)
|
|
||||||
max_line_size = 0
|
|
||||||
`
|
`
|
||||||
|
|
||||||
func (h *HTTPListener) SampleConfig() string {
|
func (t *HttpListener) SampleConfig() string {
|
||||||
return sampleConfig
|
return sampleConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HTTPListener) Description() string {
|
func (t *HttpListener) Description() string {
|
||||||
return "Influx HTTP write listener"
|
return "Influx HTTP write listener"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HTTPListener) Gather(_ telegraf.Accumulator) error {
|
func (t *HttpListener) Gather(_ telegraf.Accumulator) error {
|
||||||
h.BuffersCreated.Set(h.pool.ncreated())
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *HttpListener) SetParser(parser parsers.Parser) {
|
||||||
|
t.parser = parser
|
||||||
|
}
|
||||||
|
|
||||||
// Start starts the http listener service.
|
// Start starts the http listener service.
|
||||||
func (h *HTTPListener) Start(acc telegraf.Accumulator) error {
|
func (t *HttpListener) Start(acc telegraf.Accumulator) error {
|
||||||
h.mu.Lock()
|
t.Lock()
|
||||||
defer h.mu.Unlock()
|
defer t.Unlock()
|
||||||
|
|
||||||
tags := map[string]string{
|
t.acc = acc
|
||||||
"address": h.ServiceAddress,
|
|
||||||
}
|
|
||||||
h.BytesRecv = selfstat.Register("http_listener", "bytes_received", tags)
|
|
||||||
h.RequestsServed = selfstat.Register("http_listener", "requests_served", tags)
|
|
||||||
h.WritesServed = selfstat.Register("http_listener", "writes_served", tags)
|
|
||||||
h.QueriesServed = selfstat.Register("http_listener", "queries_served", tags)
|
|
||||||
h.PingsServed = selfstat.Register("http_listener", "pings_served", tags)
|
|
||||||
h.RequestsRecv = selfstat.Register("http_listener", "requests_received", tags)
|
|
||||||
h.WritesRecv = selfstat.Register("http_listener", "writes_received", tags)
|
|
||||||
h.QueriesRecv = selfstat.Register("http_listener", "queries_received", tags)
|
|
||||||
h.PingsRecv = selfstat.Register("http_listener", "pings_received", tags)
|
|
||||||
h.NotFoundsServed = selfstat.Register("http_listener", "not_founds_served", tags)
|
|
||||||
h.BuffersCreated = selfstat.Register("http_listener", "buffers_created", tags)
|
|
||||||
|
|
||||||
if h.MaxBodySize == 0 {
|
var rawListener, err = net.Listen("tcp", t.ServiceAddress)
|
||||||
h.MaxBodySize = DEFAULT_MAX_BODY_SIZE
|
if err != nil {
|
||||||
}
|
return err
|
||||||
if h.MaxLineSize == 0 {
|
}
|
||||||
h.MaxLineSize = DEFAULT_MAX_LINE_SIZE
|
t.listener, err = stoppableListener.New(rawListener)
|
||||||
}
|
|
||||||
|
|
||||||
h.acc = acc
|
|
||||||
h.pool = NewPool(200, h.MaxLineSize)
|
|
||||||
|
|
||||||
var listener, err = net.Listen("tcp", h.ServiceAddress)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
h.listener = listener
|
|
||||||
|
|
||||||
h.wg.Add(1)
|
go t.httpListen()
|
||||||
go func() {
|
|
||||||
defer h.wg.Done()
|
|
||||||
h.httpListen()
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.Printf("I! Started HTTP listener service on %s\n", h.ServiceAddress)
|
log.Printf("I! Started HTTP listener service on %s\n", t.ServiceAddress)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop cleans up all resources
|
// Stop cleans up all resources
|
||||||
func (h *HTTPListener) Stop() {
|
func (t *HttpListener) Stop() {
|
||||||
h.mu.Lock()
|
t.Lock()
|
||||||
defer h.mu.Unlock()
|
defer t.Unlock()
|
||||||
|
|
||||||
h.listener.Close()
|
t.listener.Stop()
|
||||||
h.wg.Wait()
|
t.listener.Close()
|
||||||
|
|
||||||
log.Println("I! Stopped HTTP listener service on ", h.ServiceAddress)
|
t.wg.Wait()
|
||||||
|
|
||||||
|
log.Println("I! Stopped HTTP listener service on ", t.ServiceAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
// httpListen sets up an http.Server and calls server.Serve.
|
// httpListen listens for HTTP requests.
|
||||||
// like server.Serve, httpListen will always return a non-nil error, for this
|
func (t *HttpListener) httpListen() error {
|
||||||
// reason, the error returned should probably be ignored.
|
if t.ReadTimeout.Duration < time.Second {
|
||||||
// see https://golang.org/pkg/net/http/#Server.Serve
|
t.ReadTimeout.Duration = time.Second * 10
|
||||||
func (h *HTTPListener) httpListen() error {
|
|
||||||
if h.ReadTimeout.Duration < time.Second {
|
|
||||||
h.ReadTimeout.Duration = time.Second * 10
|
|
||||||
}
|
}
|
||||||
if h.WriteTimeout.Duration < time.Second {
|
if t.WriteTimeout.Duration < time.Second {
|
||||||
h.WriteTimeout.Duration = time.Second * 10
|
t.WriteTimeout.Duration = time.Second * 10
|
||||||
}
|
}
|
||||||
|
|
||||||
var server = http.Server{
|
var server = http.Server{
|
||||||
Handler: h,
|
Handler: t,
|
||||||
ReadTimeout: h.ReadTimeout.Duration,
|
ReadTimeout: t.ReadTimeout.Duration,
|
||||||
WriteTimeout: h.WriteTimeout.Duration,
|
WriteTimeout: t.WriteTimeout.Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
return server.Serve(h.listener)
|
return server.Serve(t.listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HTTPListener) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
func (t *HttpListener) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||||
h.RequestsRecv.Incr(1)
|
t.wg.Add(1)
|
||||||
defer h.RequestsServed.Incr(1)
|
defer t.wg.Done()
|
||||||
|
|
||||||
switch req.URL.Path {
|
switch req.URL.Path {
|
||||||
case "/write":
|
case "/write":
|
||||||
h.WritesRecv.Incr(1)
|
var http400msg bytes.Buffer
|
||||||
defer h.WritesServed.Incr(1)
|
var partial string
|
||||||
h.serveWrite(res, req)
|
scanner := bufio.NewScanner(req.Body)
|
||||||
|
scanner.Buffer([]byte(""), 128*1024)
|
||||||
|
for scanner.Scan() {
|
||||||
|
metrics, err := t.parser.Parse(scanner.Bytes())
|
||||||
|
if err == nil {
|
||||||
|
for _, m := range metrics {
|
||||||
|
t.acc.AddFields(m.Name(), m.Fields(), m.Tags(), m.Time())
|
||||||
|
}
|
||||||
|
partial = "partial write: "
|
||||||
|
} else {
|
||||||
|
http400msg.WriteString(err.Error() + " ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
http.Error(res, "Internal server error: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
} else if http400msg.Len() > 0 {
|
||||||
|
res.Header().Set("Content-Type", "application/json")
|
||||||
|
res.Header().Set("X-Influxdb-Version", "1.0")
|
||||||
|
res.WriteHeader(http.StatusBadRequest)
|
||||||
|
res.Write([]byte(fmt.Sprintf(`{"error":"%s%s"}`, partial, http400msg.String())))
|
||||||
|
} else {
|
||||||
|
res.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
case "/query":
|
case "/query":
|
||||||
h.QueriesRecv.Incr(1)
|
|
||||||
defer h.QueriesServed.Incr(1)
|
|
||||||
// Deliver a dummy response to the query endpoint, as some InfluxDB
|
// Deliver a dummy response to the query endpoint, as some InfluxDB
|
||||||
// clients test endpoint availability with a query
|
// clients test endpoint availability with a query
|
||||||
res.Header().Set("Content-Type", "application/json")
|
res.Header().Set("Content-Type", "application/json")
|
||||||
@@ -186,147 +150,16 @@ func (h *HTTPListener) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
|||||||
res.WriteHeader(http.StatusOK)
|
res.WriteHeader(http.StatusOK)
|
||||||
res.Write([]byte("{\"results\":[]}"))
|
res.Write([]byte("{\"results\":[]}"))
|
||||||
case "/ping":
|
case "/ping":
|
||||||
h.PingsRecv.Incr(1)
|
|
||||||
defer h.PingsServed.Incr(1)
|
|
||||||
// respond to ping requests
|
// respond to ping requests
|
||||||
res.WriteHeader(http.StatusNoContent)
|
res.WriteHeader(http.StatusNoContent)
|
||||||
default:
|
default:
|
||||||
defer h.NotFoundsServed.Incr(1)
|
|
||||||
// Don't know how to respond to calls to other endpoints
|
// Don't know how to respond to calls to other endpoints
|
||||||
http.NotFound(res, req)
|
http.NotFound(res, req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HTTPListener) serveWrite(res http.ResponseWriter, req *http.Request) {
|
|
||||||
// Check that the content length is not too large for us to handle.
|
|
||||||
if req.ContentLength > h.MaxBodySize {
|
|
||||||
tooLarge(res)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
// Handle gzip request bodies
|
|
||||||
body := req.Body
|
|
||||||
var err error
|
|
||||||
if req.Header.Get("Content-Encoding") == "gzip" {
|
|
||||||
body, err = gzip.NewReader(req.Body)
|
|
||||||
defer body.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("E! " + err.Error())
|
|
||||||
badRequest(res)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
body = http.MaxBytesReader(res, body, h.MaxBodySize)
|
|
||||||
|
|
||||||
var return400 bool
|
|
||||||
var hangingBytes bool
|
|
||||||
buf := h.pool.get()
|
|
||||||
defer h.pool.put(buf)
|
|
||||||
bufStart := 0
|
|
||||||
for {
|
|
||||||
n, err := io.ReadFull(body, buf[bufStart:])
|
|
||||||
if err != nil && err != io.ErrUnexpectedEOF && err != io.EOF {
|
|
||||||
log.Println("E! " + err.Error())
|
|
||||||
// problem reading the request body
|
|
||||||
badRequest(res)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h.BytesRecv.Incr(int64(n))
|
|
||||||
|
|
||||||
if err == io.EOF {
|
|
||||||
if return400 {
|
|
||||||
badRequest(res)
|
|
||||||
} else {
|
|
||||||
res.WriteHeader(http.StatusNoContent)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if hangingBytes {
|
|
||||||
i := bytes.IndexByte(buf, '\n')
|
|
||||||
if i == -1 {
|
|
||||||
// still didn't find a newline, keep scanning
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// rotate the bit remaining after the first newline to the front of the buffer
|
|
||||||
i++ // start copying after the newline
|
|
||||||
bufStart = len(buf) - i
|
|
||||||
if bufStart > 0 {
|
|
||||||
copy(buf, buf[i:])
|
|
||||||
}
|
|
||||||
hangingBytes = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == io.ErrUnexpectedEOF {
|
|
||||||
// finished reading the request body
|
|
||||||
if err := h.parse(buf[:n+bufStart], now); err != nil {
|
|
||||||
log.Println("E! " + err.Error())
|
|
||||||
return400 = true
|
|
||||||
}
|
|
||||||
if return400 {
|
|
||||||
badRequest(res)
|
|
||||||
} else {
|
|
||||||
res.WriteHeader(http.StatusNoContent)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we got down here it means that we filled our buffer, and there
|
|
||||||
// are still bytes remaining to be read. So we will parse up until the
|
|
||||||
// final newline, then push the rest of the bytes into the next buffer.
|
|
||||||
i := bytes.LastIndexByte(buf, '\n')
|
|
||||||
if i == -1 {
|
|
||||||
// drop any line longer than the max buffer size
|
|
||||||
log.Printf("E! http_listener received a single line longer than the maximum of %d bytes",
|
|
||||||
len(buf))
|
|
||||||
hangingBytes = true
|
|
||||||
return400 = true
|
|
||||||
bufStart = 0
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := h.parse(buf[:i+1], now); err != nil {
|
|
||||||
log.Println("E! " + err.Error())
|
|
||||||
return400 = true
|
|
||||||
}
|
|
||||||
// rotate the bit remaining after the last newline to the front of the buffer
|
|
||||||
i++ // start copying after the newline
|
|
||||||
bufStart = len(buf) - i
|
|
||||||
if bufStart > 0 {
|
|
||||||
copy(buf, buf[i:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *HTTPListener) parse(b []byte, t time.Time) error {
|
|
||||||
metrics, err := h.parser.ParseWithDefaultTime(b, t)
|
|
||||||
|
|
||||||
for _, m := range metrics {
|
|
||||||
h.acc.AddFields(m.Name(), m.Fields(), m.Tags(), m.Time())
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func tooLarge(res http.ResponseWriter) {
|
|
||||||
res.Header().Set("Content-Type", "application/json")
|
|
||||||
res.Header().Set("X-Influxdb-Version", "1.0")
|
|
||||||
res.WriteHeader(http.StatusRequestEntityTooLarge)
|
|
||||||
res.Write([]byte(`{"error":"http: request body too large"}`))
|
|
||||||
}
|
|
||||||
|
|
||||||
func badRequest(res http.ResponseWriter) {
|
|
||||||
res.Header().Set("Content-Type", "application/json")
|
|
||||||
res.Header().Set("X-Influxdb-Version", "1.0")
|
|
||||||
res.WriteHeader(http.StatusBadRequest)
|
|
||||||
res.Write([]byte(`{"error":"http: bad request"}`))
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
inputs.Add("http_listener", func() telegraf.Input {
|
inputs.Add("http_listener", func() telegraf.Input {
|
||||||
return &HTTPListener{
|
return &HttpListener{}
|
||||||
ServiceAddress: ":8186",
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
package http_listener
|
package http_listener
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf/plugins/parsers"
|
||||||
"github.com/influxdata/telegraf/testutil"
|
"github.com/influxdata/telegraf/testutil"
|
||||||
|
|
||||||
|
"bytes"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -27,15 +27,17 @@ cpu_load_short,host=server06 value=12.0 1422568543702900257
|
|||||||
emptyMsg = ""
|
emptyMsg = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
func newTestHTTPListener() *HTTPListener {
|
func newTestHttpListener() *HttpListener {
|
||||||
listener := &HTTPListener{
|
listener := &HttpListener{
|
||||||
ServiceAddress: ":8186",
|
ServiceAddress: ":8186",
|
||||||
}
|
}
|
||||||
return listener
|
return listener
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWriteHTTP(t *testing.T) {
|
func TestWriteHTTP(t *testing.T) {
|
||||||
listener := newTestHTTPListener()
|
listener := newTestHttpListener()
|
||||||
|
parser, _ := parsers.NewInfluxParser()
|
||||||
|
listener.SetParser(parser)
|
||||||
|
|
||||||
acc := &testutil.Accumulator{}
|
acc := &testutil.Accumulator{}
|
||||||
require.NoError(t, listener.Start(acc))
|
require.NoError(t, listener.Start(acc))
|
||||||
@@ -69,10 +71,10 @@ func TestWriteHTTP(t *testing.T) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post a gigantic metric to the listener and verify that an error is returned:
|
// Post a gigantic metric to the listener:
|
||||||
resp, err = http.Post("http://localhost:8186/write?db=mydb", "", bytes.NewBuffer([]byte(hugeMetric)))
|
resp, err = http.Post("http://localhost:8186/write?db=mydb", "", bytes.NewBuffer([]byte(hugeMetric)))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.EqualValues(t, 400, resp.StatusCode)
|
require.EqualValues(t, 204, resp.StatusCode)
|
||||||
|
|
||||||
time.Sleep(time.Millisecond * 15)
|
time.Sleep(time.Millisecond * 15)
|
||||||
acc.AssertContainsTaggedFields(t, "cpu_load_short",
|
acc.AssertContainsTaggedFields(t, "cpu_load_short",
|
||||||
@@ -81,133 +83,11 @@ func TestWriteHTTP(t *testing.T) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWriteHTTPMaxLineSizeIncrease(t *testing.T) {
|
|
||||||
listener := &HTTPListener{
|
|
||||||
ServiceAddress: ":8296",
|
|
||||||
MaxLineSize: 128 * 1000,
|
|
||||||
}
|
|
||||||
|
|
||||||
acc := &testutil.Accumulator{}
|
|
||||||
require.NoError(t, listener.Start(acc))
|
|
||||||
defer listener.Stop()
|
|
||||||
|
|
||||||
time.Sleep(time.Millisecond * 25)
|
|
||||||
|
|
||||||
// Post a gigantic metric to the listener and verify that it writes OK this time:
|
|
||||||
resp, err := http.Post("http://localhost:8296/write?db=mydb", "", bytes.NewBuffer([]byte(hugeMetric)))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, 204, resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWriteHTTPVerySmallMaxBody(t *testing.T) {
|
|
||||||
listener := &HTTPListener{
|
|
||||||
ServiceAddress: ":8297",
|
|
||||||
MaxBodySize: 4096,
|
|
||||||
}
|
|
||||||
|
|
||||||
acc := &testutil.Accumulator{}
|
|
||||||
require.NoError(t, listener.Start(acc))
|
|
||||||
defer listener.Stop()
|
|
||||||
|
|
||||||
time.Sleep(time.Millisecond * 25)
|
|
||||||
|
|
||||||
resp, err := http.Post("http://localhost:8297/write", "", bytes.NewBuffer([]byte(hugeMetric)))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, 413, resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWriteHTTPVerySmallMaxLineSize(t *testing.T) {
|
|
||||||
listener := &HTTPListener{
|
|
||||||
ServiceAddress: ":8298",
|
|
||||||
MaxLineSize: 70,
|
|
||||||
}
|
|
||||||
|
|
||||||
acc := &testutil.Accumulator{}
|
|
||||||
require.NoError(t, listener.Start(acc))
|
|
||||||
defer listener.Stop()
|
|
||||||
|
|
||||||
time.Sleep(time.Millisecond * 25)
|
|
||||||
|
|
||||||
resp, err := http.Post("http://localhost:8298/write", "", bytes.NewBuffer([]byte(testMsgs)))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, 204, resp.StatusCode)
|
|
||||||
|
|
||||||
time.Sleep(time.Millisecond * 15)
|
|
||||||
hostTags := []string{"server02", "server03",
|
|
||||||
"server04", "server05", "server06"}
|
|
||||||
for _, hostTag := range hostTags {
|
|
||||||
acc.AssertContainsTaggedFields(t, "cpu_load_short",
|
|
||||||
map[string]interface{}{"value": float64(12)},
|
|
||||||
map[string]string{"host": hostTag},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWriteHTTPLargeLinesSkipped(t *testing.T) {
|
|
||||||
listener := &HTTPListener{
|
|
||||||
ServiceAddress: ":8300",
|
|
||||||
MaxLineSize: 100,
|
|
||||||
}
|
|
||||||
|
|
||||||
acc := &testutil.Accumulator{}
|
|
||||||
require.NoError(t, listener.Start(acc))
|
|
||||||
defer listener.Stop()
|
|
||||||
|
|
||||||
time.Sleep(time.Millisecond * 25)
|
|
||||||
|
|
||||||
resp, err := http.Post("http://localhost:8300/write", "", bytes.NewBuffer([]byte(hugeMetric+testMsgs)))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, 400, resp.StatusCode)
|
|
||||||
|
|
||||||
time.Sleep(time.Millisecond * 15)
|
|
||||||
hostTags := []string{"server02", "server03",
|
|
||||||
"server04", "server05", "server06"}
|
|
||||||
for _, hostTag := range hostTags {
|
|
||||||
acc.AssertContainsTaggedFields(t, "cpu_load_short",
|
|
||||||
map[string]interface{}{"value": float64(12)},
|
|
||||||
map[string]string{"host": hostTag},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// test that writing gzipped data works
|
|
||||||
func TestWriteHTTPGzippedData(t *testing.T) {
|
|
||||||
listener := &HTTPListener{
|
|
||||||
ServiceAddress: ":8299",
|
|
||||||
}
|
|
||||||
|
|
||||||
acc := &testutil.Accumulator{}
|
|
||||||
require.NoError(t, listener.Start(acc))
|
|
||||||
defer listener.Stop()
|
|
||||||
|
|
||||||
time.Sleep(time.Millisecond * 25)
|
|
||||||
|
|
||||||
data, err := ioutil.ReadFile("./testdata/testmsgs.gz")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", "http://localhost:8299/write", bytes.NewBuffer(data))
|
|
||||||
require.NoError(t, err)
|
|
||||||
req.Header.Set("Content-Encoding", "gzip")
|
|
||||||
|
|
||||||
client := &http.Client{}
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, 204, resp.StatusCode)
|
|
||||||
|
|
||||||
time.Sleep(time.Millisecond * 50)
|
|
||||||
hostTags := []string{"server02", "server03",
|
|
||||||
"server04", "server05", "server06"}
|
|
||||||
for _, hostTag := range hostTags {
|
|
||||||
acc.AssertContainsTaggedFields(t, "cpu_load_short",
|
|
||||||
map[string]interface{}{"value": float64(12)},
|
|
||||||
map[string]string{"host": hostTag},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// writes 25,000 metrics to the listener with 10 different writers
|
// writes 25,000 metrics to the listener with 10 different writers
|
||||||
func TestWriteHTTPHighTraffic(t *testing.T) {
|
func TestWriteHTTPHighTraffic(t *testing.T) {
|
||||||
listener := &HTTPListener{ServiceAddress: ":8286"}
|
listener := &HttpListener{ServiceAddress: ":8286"}
|
||||||
|
parser, _ := parsers.NewInfluxParser()
|
||||||
|
listener.SetParser(parser)
|
||||||
|
|
||||||
acc := &testutil.Accumulator{}
|
acc := &testutil.Accumulator{}
|
||||||
require.NoError(t, listener.Start(acc))
|
require.NoError(t, listener.Start(acc))
|
||||||
@@ -219,25 +99,26 @@ func TestWriteHTTPHighTraffic(t *testing.T) {
|
|||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(innerwg *sync.WaitGroup) {
|
go func() {
|
||||||
defer innerwg.Done()
|
|
||||||
for i := 0; i < 500; i++ {
|
for i := 0; i < 500; i++ {
|
||||||
resp, err := http.Post("http://localhost:8286/write?db=mydb", "", bytes.NewBuffer([]byte(testMsgs)))
|
resp, err := http.Post("http://localhost:8286/write?db=mydb", "", bytes.NewBuffer([]byte(testMsgs)))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.EqualValues(t, 204, resp.StatusCode)
|
require.EqualValues(t, 204, resp.StatusCode)
|
||||||
}
|
}
|
||||||
}(&wg)
|
wg.Done()
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
time.Sleep(time.Millisecond * 250)
|
time.Sleep(time.Millisecond * 50)
|
||||||
listener.Gather(acc)
|
listener.Gather(acc)
|
||||||
|
|
||||||
require.Equal(t, int64(25000), int64(acc.NMetrics()))
|
require.Equal(t, int64(25000), int64(acc.NMetrics()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReceive404ForInvalidEndpoint(t *testing.T) {
|
func TestReceive404ForInvalidEndpoint(t *testing.T) {
|
||||||
listener := newTestHTTPListener()
|
listener := newTestHttpListener()
|
||||||
|
listener.parser, _ = parsers.NewInfluxParser()
|
||||||
|
|
||||||
acc := &testutil.Accumulator{}
|
acc := &testutil.Accumulator{}
|
||||||
require.NoError(t, listener.Start(acc))
|
require.NoError(t, listener.Start(acc))
|
||||||
@@ -254,7 +135,8 @@ func TestReceive404ForInvalidEndpoint(t *testing.T) {
|
|||||||
func TestWriteHTTPInvalid(t *testing.T) {
|
func TestWriteHTTPInvalid(t *testing.T) {
|
||||||
time.Sleep(time.Millisecond * 250)
|
time.Sleep(time.Millisecond * 250)
|
||||||
|
|
||||||
listener := newTestHTTPListener()
|
listener := newTestHttpListener()
|
||||||
|
listener.parser, _ = parsers.NewInfluxParser()
|
||||||
|
|
||||||
acc := &testutil.Accumulator{}
|
acc := &testutil.Accumulator{}
|
||||||
require.NoError(t, listener.Start(acc))
|
require.NoError(t, listener.Start(acc))
|
||||||
@@ -271,7 +153,8 @@ func TestWriteHTTPInvalid(t *testing.T) {
|
|||||||
func TestWriteHTTPEmpty(t *testing.T) {
|
func TestWriteHTTPEmpty(t *testing.T) {
|
||||||
time.Sleep(time.Millisecond * 250)
|
time.Sleep(time.Millisecond * 250)
|
||||||
|
|
||||||
listener := newTestHTTPListener()
|
listener := newTestHttpListener()
|
||||||
|
listener.parser, _ = parsers.NewInfluxParser()
|
||||||
|
|
||||||
acc := &testutil.Accumulator{}
|
acc := &testutil.Accumulator{}
|
||||||
require.NoError(t, listener.Start(acc))
|
require.NoError(t, listener.Start(acc))
|
||||||
@@ -288,7 +171,8 @@ func TestWriteHTTPEmpty(t *testing.T) {
|
|||||||
func TestQueryAndPingHTTP(t *testing.T) {
|
func TestQueryAndPingHTTP(t *testing.T) {
|
||||||
time.Sleep(time.Millisecond * 250)
|
time.Sleep(time.Millisecond * 250)
|
||||||
|
|
||||||
listener := newTestHTTPListener()
|
listener := newTestHttpListener()
|
||||||
|
listener.parser, _ = parsers.NewInfluxParser()
|
||||||
|
|
||||||
acc := &testutil.Accumulator{}
|
acc := &testutil.Accumulator{}
|
||||||
require.NoError(t, listener.Start(acc))
|
require.NoError(t, listener.Start(acc))
|
||||||
|
|||||||
10
plugins/inputs/http_listener/stoppableListener/LICENSE
Normal file
10
plugins/inputs/http_listener/stoppableListener/LICENSE
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
Copyright (c) 2014, Eric Urban
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
62
plugins/inputs/http_listener/stoppableListener/listener.go
Normal file
62
plugins/inputs/http_listener/stoppableListener/listener.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package stoppableListener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StoppableListener struct {
|
||||||
|
*net.TCPListener //Wrapped listener
|
||||||
|
stop chan int //Channel used only to indicate listener should shutdown
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(l net.Listener) (*StoppableListener, error) {
|
||||||
|
tcpL, ok := l.(*net.TCPListener)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("Cannot wrap listener")
|
||||||
|
}
|
||||||
|
|
||||||
|
retval := &StoppableListener{}
|
||||||
|
retval.TCPListener = tcpL
|
||||||
|
retval.stop = make(chan int)
|
||||||
|
|
||||||
|
return retval, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var StoppedError = errors.New("Listener stopped")
|
||||||
|
|
||||||
|
func (sl *StoppableListener) Accept() (net.Conn, error) {
|
||||||
|
|
||||||
|
for {
|
||||||
|
//Wait up to one second for a new connection
|
||||||
|
sl.SetDeadline(time.Now().Add(time.Second))
|
||||||
|
|
||||||
|
newConn, err := sl.TCPListener.Accept()
|
||||||
|
|
||||||
|
//Check for the channel being closed
|
||||||
|
select {
|
||||||
|
case <-sl.stop:
|
||||||
|
return nil, StoppedError
|
||||||
|
default:
|
||||||
|
//If the channel is still open, continue as normal
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
netErr, ok := err.(net.Error)
|
||||||
|
|
||||||
|
//If this is a timeout, then continue to wait for
|
||||||
|
//new connections
|
||||||
|
if ok && netErr.Timeout() && netErr.Temporary() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newConn, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl *StoppableListener) Stop() {
|
||||||
|
close(sl.stop)
|
||||||
|
}
|
||||||
BIN
plugins/inputs/http_listener/testdata/testmsgs.gz
vendored
BIN
plugins/inputs/http_listener/testdata/testmsgs.gz
vendored
Binary file not shown.
@@ -37,8 +37,6 @@ You can also specify which keys from server response should be considered tags:
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
If the JSON response is an array of objects, then each object will be parsed with the same configuration.
|
|
||||||
|
|
||||||
You can also specify additional request parameters for the service:
|
You can also specify additional request parameters for the service:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -152,53 +150,3 @@ httpjson_mycollector1_b_e,server='http://my.service.com/_stats' value=5
|
|||||||
httpjson_mycollector2_load,server='http://service.net/json/stats' value=100
|
httpjson_mycollector2_load,server='http://service.net/json/stats' value=100
|
||||||
httpjson_mycollector2_users,server='http://service.net/json/stats' value=1335
|
httpjson_mycollector2_users,server='http://service.net/json/stats' value=1335
|
||||||
```
|
```
|
||||||
|
|
||||||
# Example 3, Multiple Metrics in Response:
|
|
||||||
|
|
||||||
The response JSON can be treated as an array of data points that are all parsed with the same configuration.
|
|
||||||
|
|
||||||
```
|
|
||||||
[[inputs.httpjson]]
|
|
||||||
name = "mycollector"
|
|
||||||
servers = [
|
|
||||||
"http://my.service.com/_stats"
|
|
||||||
]
|
|
||||||
# HTTP method to use (case-sensitive)
|
|
||||||
method = "GET"
|
|
||||||
tag_keys = ["service"]
|
|
||||||
```
|
|
||||||
|
|
||||||
which responds with the following JSON:
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"service": "service01",
|
|
||||||
"a": 0.5,
|
|
||||||
"b": {
|
|
||||||
"c": "some text",
|
|
||||||
"d": 0.1,
|
|
||||||
"e": 5
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"service": "service02",
|
|
||||||
"a": 0.6,
|
|
||||||
"b": {
|
|
||||||
"c": "some text",
|
|
||||||
"d": 0.2,
|
|
||||||
"e": 6
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
The collected metrics will be:
|
|
||||||
```
|
|
||||||
httpjson_mycollector_a,service='service01',server='http://my.service.com/_stats' value=0.5
|
|
||||||
httpjson_mycollector_b_d,service='service01',server='http://my.service.com/_stats' value=0.1
|
|
||||||
httpjson_mycollector_b_e,service='service01',server='http://my.service.com/_stats' value=5
|
|
||||||
httpjson_mycollector_a,service='service02',server='http://my.service.com/_stats' value=0.6
|
|
||||||
httpjson_mycollector_b_d,service='service02',server='http://my.service.com/_stats' value=0.2
|
|
||||||
httpjson_mycollector_b_e,service='service02',server='http://my.service.com/_stats' value=6
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -511,52 +511,3 @@ func TestHttpJson200Tags(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const validJSONArrayTags = `
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"value": 15,
|
|
||||||
"role": "master",
|
|
||||||
"build": "123"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": 17,
|
|
||||||
"role": "slave",
|
|
||||||
"build": "456"
|
|
||||||
}
|
|
||||||
]`
|
|
||||||
|
|
||||||
// Test that array data is collected correctly
|
|
||||||
func TestHttpJsonArray200Tags(t *testing.T) {
|
|
||||||
httpjson := genMockHttpJson(validJSONArrayTags, 200)
|
|
||||||
|
|
||||||
for _, service := range httpjson {
|
|
||||||
if service.Name == "other_webapp" {
|
|
||||||
var acc testutil.Accumulator
|
|
||||||
err := service.Gather(&acc)
|
|
||||||
// Set responsetime
|
|
||||||
for _, p := range acc.Metrics {
|
|
||||||
p.Fields["response_time"] = 1.0
|
|
||||||
}
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, 8, acc.NFields())
|
|
||||||
assert.Equal(t, uint64(4), acc.NMetrics())
|
|
||||||
|
|
||||||
for _, m := range acc.Metrics {
|
|
||||||
if m.Tags["role"] == "master" {
|
|
||||||
assert.Equal(t, "123", m.Tags["build"])
|
|
||||||
assert.Equal(t, float64(15), m.Fields["value"])
|
|
||||||
assert.Equal(t, float64(1), m.Fields["response_time"])
|
|
||||||
assert.Equal(t, "httpjson_"+service.Name, m.Measurement)
|
|
||||||
} else if m.Tags["role"] == "slave" {
|
|
||||||
assert.Equal(t, "456", m.Tags["build"])
|
|
||||||
assert.Equal(t, float64(17), m.Fields["value"])
|
|
||||||
assert.Equal(t, float64(1), m.Fields["response_time"])
|
|
||||||
assert.Equal(t, "httpjson_"+service.Name, m.Measurement)
|
|
||||||
} else {
|
|
||||||
assert.FailNow(t, "unknown metric")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
# Internal Input Plugin
|
|
||||||
|
|
||||||
The `internal` plugin collects metrics about the telegraf agent itself.
|
|
||||||
|
|
||||||
Note that some metrics are aggregates across all instances of one type of
|
|
||||||
plugin.
|
|
||||||
|
|
||||||
### Configuration:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
# Collect statistics about itself
|
|
||||||
[[inputs.internal]]
|
|
||||||
## If true, collect telegraf memory stats.
|
|
||||||
# collect_memstats = true
|
|
||||||
```
|
|
||||||
|
|
||||||
### Measurements & Fields:
|
|
||||||
|
|
||||||
memstats are taken from the Go runtime: https://golang.org/pkg/runtime/#MemStats
|
|
||||||
|
|
||||||
- internal\_memstats
|
|
||||||
- alloc\_bytes
|
|
||||||
- frees
|
|
||||||
- heap\_alloc\_bytes
|
|
||||||
- heap\_idle\_bytes
|
|
||||||
- heap\_in\_use\_bytes
|
|
||||||
- heap\_objects\_bytes
|
|
||||||
- heap\_released\_bytes
|
|
||||||
- heap\_sys\_bytes
|
|
||||||
- mallocs
|
|
||||||
- num\_gc
|
|
||||||
- pointer\_lookups
|
|
||||||
- sys\_bytes
|
|
||||||
- total\_alloc\_bytes
|
|
||||||
|
|
||||||
agent stats collect aggregate stats on all telegraf plugins.
|
|
||||||
|
|
||||||
- internal\_agent
|
|
||||||
- gather\_errors
|
|
||||||
- metrics\_dropped
|
|
||||||
- metrics\_gathered
|
|
||||||
- metrics\_written
|
|
||||||
|
|
||||||
internal\_gather stats collect aggregate stats on all input plugins
|
|
||||||
that are of the same input type. They are tagged with `input=<plugin_name>`.
|
|
||||||
|
|
||||||
- internal\_gather
|
|
||||||
- gather\_time\_ns
|
|
||||||
- metrics\_gathered
|
|
||||||
|
|
||||||
internal\_write stats collect aggregate stats on all output plugins
|
|
||||||
that are of the same input type. They are tagged with `output=<plugin_name>`.
|
|
||||||
|
|
||||||
|
|
||||||
- internal\_write
|
|
||||||
- buffer\_limit
|
|
||||||
- buffer\_size
|
|
||||||
- metrics\_written
|
|
||||||
- metrics\_filtered
|
|
||||||
- write\_time\_ns
|
|
||||||
|
|
||||||
internal\_\<plugin\_name\> are metrics which are defined on a per-plugin basis, and
|
|
||||||
usually contain tags which differentiate each instance of a particular type of
|
|
||||||
plugin.
|
|
||||||
|
|
||||||
- internal\_\<plugin\_name\>
|
|
||||||
- individual plugin-specific fields, such as requests counts.
|
|
||||||
|
|
||||||
### Tags:
|
|
||||||
|
|
||||||
All measurements for specific plugins are tagged with information relevant
|
|
||||||
to each particular plugin.
|
|
||||||
|
|
||||||
### Example Output:
|
|
||||||
|
|
||||||
```
|
|
||||||
internal_memstats,host=tyrion alloc_bytes=4457408i,sys_bytes=10590456i,pointer_lookups=7i,mallocs=17642i,frees=7473i,heap_sys_bytes=6848512i,heap_idle_bytes=1368064i,heap_in_use_bytes=5480448i,heap_released_bytes=0i,total_alloc_bytes=6875560i,heap_alloc_bytes=4457408i,heap_objects_bytes=10169i,num_gc=2i 1480682800000000000
|
|
||||||
internal_agent,host=tyrion metrics_written=18i,metrics_dropped=0i,metrics_gathered=19i,gather_errors=0i 1480682800000000000
|
|
||||||
internal_write,output=file,host=tyrion buffer_limit=10000i,write_time_ns=636609i,metrics_written=18i,buffer_size=0i 1480682800000000000
|
|
||||||
internal_gather,input=internal,host=tyrion metrics_gathered=19i,gather_time_ns=442114i 1480682800000000000
|
|
||||||
internal_gather,input=http_listener,host=tyrion metrics_gathered=0i,gather_time_ns=167285i 1480682800000000000
|
|
||||||
internal_http_listener,address=:8186,host=tyrion queries_received=0i,writes_received=0i,requests_received=0i,buffers_created=0i,requests_served=0i,pings_received=0i,bytes_received=0i,not_founds_served=0i,pings_served=0i,queries_served=0i,writes_served=0i 1480682800000000000
|
|
||||||
```
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
|
||||||
"github.com/influxdata/telegraf/plugins/inputs"
|
|
||||||
"github.com/influxdata/telegraf/selfstat"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Self struct {
|
|
||||||
CollectMemstats bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSelf() telegraf.Input {
|
|
||||||
return &Self{
|
|
||||||
CollectMemstats: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var sampleConfig = `
|
|
||||||
## If true, collect telegraf memory stats.
|
|
||||||
# collect_memstats = true
|
|
||||||
`
|
|
||||||
|
|
||||||
func (s *Self) Description() string {
|
|
||||||
return "Collect statistics about itself"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Self) SampleConfig() string {
|
|
||||||
return sampleConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Self) Gather(acc telegraf.Accumulator) error {
|
|
||||||
if s.CollectMemstats {
|
|
||||||
m := &runtime.MemStats{}
|
|
||||||
runtime.ReadMemStats(m)
|
|
||||||
fields := map[string]interface{}{
|
|
||||||
"alloc_bytes": m.Alloc, // bytes allocated and not yet freed
|
|
||||||
"total_alloc_bytes": m.TotalAlloc, // bytes allocated (even if freed)
|
|
||||||
"sys_bytes": m.Sys, // bytes obtained from system (sum of XxxSys below)
|
|
||||||
"pointer_lookups": m.Lookups, // number of pointer lookups
|
|
||||||
"mallocs": m.Mallocs, // number of mallocs
|
|
||||||
"frees": m.Frees, // number of frees
|
|
||||||
// Main allocation heap statistics.
|
|
||||||
"heap_alloc_bytes": m.HeapAlloc, // bytes allocated and not yet freed (same as Alloc above)
|
|
||||||
"heap_sys_bytes": m.HeapSys, // bytes obtained from system
|
|
||||||
"heap_idle_bytes": m.HeapIdle, // bytes in idle spans
|
|
||||||
"heap_in_use_bytes": m.HeapInuse, // bytes in non-idle span
|
|
||||||
"heap_released_bytes": m.HeapReleased, // bytes released to the OS
|
|
||||||
"heap_objects_bytes": m.HeapObjects, // total number of allocated objects
|
|
||||||
"num_gc": m.NumGC,
|
|
||||||
}
|
|
||||||
acc.AddFields("internal_memstats", fields, map[string]string{})
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, m := range selfstat.Metrics() {
|
|
||||||
acc.AddFields(m.Name(), m.Fields(), m.Tags(), m.Time())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
inputs.Add("internal", NewSelf)
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/influxdata/telegraf/selfstat"
|
|
||||||
"github.com/influxdata/telegraf/testutil"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSelfPlugin(t *testing.T) {
|
|
||||||
s := NewSelf()
|
|
||||||
acc := &testutil.Accumulator{}
|
|
||||||
|
|
||||||
s.Gather(acc)
|
|
||||||
assert.True(t, acc.HasMeasurement("internal_memstats"))
|
|
||||||
|
|
||||||
// test that a registered stat is incremented
|
|
||||||
stat := selfstat.Register("mytest", "test", map[string]string{"test": "foo"})
|
|
||||||
stat.Incr(1)
|
|
||||||
stat.Incr(2)
|
|
||||||
s.Gather(acc)
|
|
||||||
acc.AssertContainsTaggedFields(t, "internal_mytest",
|
|
||||||
map[string]interface{}{
|
|
||||||
"test": int64(3),
|
|
||||||
},
|
|
||||||
map[string]string{
|
|
||||||
"test": "foo",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
acc.ClearMetrics()
|
|
||||||
|
|
||||||
// test that a registered stat is set properly
|
|
||||||
stat.Set(101)
|
|
||||||
s.Gather(acc)
|
|
||||||
acc.AssertContainsTaggedFields(t, "internal_mytest",
|
|
||||||
map[string]interface{}{
|
|
||||||
"test": int64(101),
|
|
||||||
},
|
|
||||||
map[string]string{
|
|
||||||
"test": "foo",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
acc.ClearMetrics()
|
|
||||||
|
|
||||||
// test that regular and timing stats can share the same measurement, and
|
|
||||||
// that timings are set properly.
|
|
||||||
timing := selfstat.RegisterTiming("mytest", "test_ns", map[string]string{"test": "foo"})
|
|
||||||
timing.Incr(100)
|
|
||||||
timing.Incr(200)
|
|
||||||
s.Gather(acc)
|
|
||||||
acc.AssertContainsTaggedFields(t, "internal_mytest",
|
|
||||||
map[string]interface{}{
|
|
||||||
"test": int64(101),
|
|
||||||
"test_ns": int64(150),
|
|
||||||
},
|
|
||||||
map[string]string{
|
|
||||||
"test": "foo",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -23,7 +23,7 @@ func TestReadsMetricsFromKafka(t *testing.T) {
|
|||||||
testTopic := fmt.Sprintf("telegraf_test_topic_%d", time.Now().Unix())
|
testTopic := fmt.Sprintf("telegraf_test_topic_%d", time.Now().Unix())
|
||||||
|
|
||||||
// Send a Kafka message to the kafka host
|
// Send a Kafka message to the kafka host
|
||||||
msg := "cpu_load_short,direction=in,host=server01,region=us-west value=23422.0 1422568543702900257\n"
|
msg := "cpu_load_short,direction=in,host=server01,region=us-west value=23422.0 1422568543702900257"
|
||||||
producer, err := sarama.NewSyncProducer(brokerPeers, nil)
|
producer, err := sarama.NewSyncProducer(brokerPeers, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
_, _, err = producer.SendMessage(
|
_, _, err = producer.SendMessage(
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
testMsg = "cpu_load_short,host=server01 value=23422.0 1422568543702900257\n"
|
testMsg = "cpu_load_short,host=server01 value=23422.0 1422568543702900257"
|
||||||
testMsgGraphite = "cpu.load.short.graphite 23422 1454780029"
|
testMsgGraphite = "cpu.load.short.graphite 23422 1454780029"
|
||||||
testMsgJSON = "{\"a\": 5, \"b\": {\"c\": 6}}\n"
|
testMsgJSON = "{\"a\": 5, \"b\": {\"c\": 6}}\n"
|
||||||
invalidMsg = "cpu_load_short,host=server01 1422568543702900257\n"
|
invalidMsg = "cpu_load_short,host=server01 1422568543702900257"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newTestKafka() (*Kafka, chan *sarama.ConsumerMessage) {
|
func newTestKafka() (*Kafka, chan *sarama.ConsumerMessage) {
|
||||||
|
|||||||
@@ -1,265 +0,0 @@
|
|||||||
# Kubernetes Input Plugin
|
|
||||||
|
|
||||||
**This plugin is experimental and may cause high cardinality issues with moderate to large Kubernetes deployments**
|
|
||||||
|
|
||||||
This input plugin talks to the kubelet api using the `/stats/summary` endpoint to gather metrics about the running pods and containers for a single host. It is assumed that this plugin is running as part of a `daemonset` within a kubernetes installation. This means that telegraf is running on every node within the cluster. Therefore, you should configure this plugin to talk to its locally running kubelet.
|
|
||||||
|
|
||||||
To find the ip address of the host you are running on you can issue a command like the following:
|
|
||||||
```
|
|
||||||
$ curl -s $API_URL/api/v1/namespaces/$POD_NAMESPACE/pods/$HOSTNAME --header "Authorization: Bearer $TOKEN" --insecure | jq -r '.status.hostIP'
|
|
||||||
```
|
|
||||||
In this case we used the downward API to pass in the `$POD_NAMESPACE` and `$HOSTNAME` is the hostname of the pod which is set by the kubernetes API.
|
|
||||||
|
|
||||||
## Summary Data
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"node": {
|
|
||||||
"nodeName": "node1",
|
|
||||||
"systemContainers": [
|
|
||||||
{
|
|
||||||
"name": "kubelet",
|
|
||||||
"startTime": "2016-08-25T18:46:52Z",
|
|
||||||
"cpu": {
|
|
||||||
"time": "2016-09-27T16:57:31Z",
|
|
||||||
"usageNanoCores": 56652446,
|
|
||||||
"usageCoreNanoSeconds": 101437561712262
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"time": "2016-09-27T16:57:31Z",
|
|
||||||
"usageBytes": 62529536,
|
|
||||||
"workingSetBytes": 62349312,
|
|
||||||
"rssBytes": 47509504,
|
|
||||||
"pageFaults": 4769397409,
|
|
||||||
"majorPageFaults": 13
|
|
||||||
},
|
|
||||||
"rootfs": {
|
|
||||||
"availableBytes": 84379979776,
|
|
||||||
"capacityBytes": 105553100800
|
|
||||||
},
|
|
||||||
"logs": {
|
|
||||||
"availableBytes": 84379979776,
|
|
||||||
"capacityBytes": 105553100800
|
|
||||||
},
|
|
||||||
"userDefinedMetrics": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "bar",
|
|
||||||
"startTime": "2016-08-25T18:46:52Z",
|
|
||||||
"cpu": {
|
|
||||||
"time": "2016-09-27T16:57:31Z",
|
|
||||||
"usageNanoCores": 56652446,
|
|
||||||
"usageCoreNanoSeconds": 101437561712262
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"time": "2016-09-27T16:57:31Z",
|
|
||||||
"usageBytes": 62529536,
|
|
||||||
"workingSetBytes": 62349312,
|
|
||||||
"rssBytes": 47509504,
|
|
||||||
"pageFaults": 4769397409,
|
|
||||||
"majorPageFaults": 13
|
|
||||||
},
|
|
||||||
"rootfs": {
|
|
||||||
"availableBytes": 84379979776,
|
|
||||||
"capacityBytes": 105553100800
|
|
||||||
},
|
|
||||||
"logs": {
|
|
||||||
"availableBytes": 84379979776,
|
|
||||||
"capacityBytes": 105553100800
|
|
||||||
},
|
|
||||||
"userDefinedMetrics": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"startTime": "2016-08-25T18:46:52Z",
|
|
||||||
"cpu": {
|
|
||||||
"time": "2016-09-27T16:57:41Z",
|
|
||||||
"usageNanoCores": 576996212,
|
|
||||||
"usageCoreNanoSeconds": 774129887054161
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"time": "2016-09-27T16:57:41Z",
|
|
||||||
"availableBytes": 10726387712,
|
|
||||||
"usageBytes": 12313182208,
|
|
||||||
"workingSetBytes": 5081538560,
|
|
||||||
"rssBytes": 35586048,
|
|
||||||
"pageFaults": 351742,
|
|
||||||
"majorPageFaults": 1236
|
|
||||||
},
|
|
||||||
"network": {
|
|
||||||
"time": "2016-09-27T16:57:41Z",
|
|
||||||
"rxBytes": 213281337459,
|
|
||||||
"rxErrors": 0,
|
|
||||||
"txBytes": 292869995684,
|
|
||||||
"txErrors": 0
|
|
||||||
},
|
|
||||||
"fs": {
|
|
||||||
"availableBytes": 84379979776,
|
|
||||||
"capacityBytes": 105553100800,
|
|
||||||
"usedBytes": 16754286592
|
|
||||||
},
|
|
||||||
"runtime": {
|
|
||||||
"imageFs": {
|
|
||||||
"availableBytes": 84379979776,
|
|
||||||
"capacityBytes": 105553100800,
|
|
||||||
"usedBytes": 5809371475
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pods": [
|
|
||||||
{
|
|
||||||
"podRef": {
|
|
||||||
"name": "foopod",
|
|
||||||
"namespace": "foons",
|
|
||||||
"uid": "6d305b06-8419-11e6-825c-42010af000ae"
|
|
||||||
},
|
|
||||||
"startTime": "2016-09-26T18:45:42Z",
|
|
||||||
"containers": [
|
|
||||||
{
|
|
||||||
"name": "foocontainer",
|
|
||||||
"startTime": "2016-09-26T18:46:43Z",
|
|
||||||
"cpu": {
|
|
||||||
"time": "2016-09-27T16:57:32Z",
|
|
||||||
"usageNanoCores": 846503,
|
|
||||||
"usageCoreNanoSeconds": 56507553554
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"time": "2016-09-27T16:57:32Z",
|
|
||||||
"usageBytes": 30789632,
|
|
||||||
"workingSetBytes": 30789632,
|
|
||||||
"rssBytes": 30695424,
|
|
||||||
"pageFaults": 10761,
|
|
||||||
"majorPageFaults": 0
|
|
||||||
},
|
|
||||||
"rootfs": {
|
|
||||||
"availableBytes": 84379979776,
|
|
||||||
"capacityBytes": 105553100800,
|
|
||||||
"usedBytes": 57344
|
|
||||||
},
|
|
||||||
"logs": {
|
|
||||||
"availableBytes": 84379979776,
|
|
||||||
"capacityBytes": 105553100800,
|
|
||||||
"usedBytes": 24576
|
|
||||||
},
|
|
||||||
"userDefinedMetrics": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"network": {
|
|
||||||
"time": "2016-09-27T16:57:34Z",
|
|
||||||
"rxBytes": 70749124,
|
|
||||||
"rxErrors": 0,
|
|
||||||
"txBytes": 47813506,
|
|
||||||
"txErrors": 0
|
|
||||||
},
|
|
||||||
"volume": [
|
|
||||||
{
|
|
||||||
"availableBytes": 7903948800,
|
|
||||||
"capacityBytes": 7903961088,
|
|
||||||
"usedBytes": 12288,
|
|
||||||
"name": "volume1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"availableBytes": 7903956992,
|
|
||||||
"capacityBytes": 7903961088,
|
|
||||||
"usedBytes": 4096,
|
|
||||||
"name": "volume2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"availableBytes": 7903948800,
|
|
||||||
"capacityBytes": 7903961088,
|
|
||||||
"usedBytes": 12288,
|
|
||||||
"name": "volume3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"availableBytes": 7903952896,
|
|
||||||
"capacityBytes": 7903961088,
|
|
||||||
"usedBytes": 8192,
|
|
||||||
"name": "volume4"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Daemonset YAML
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
apiVersion: extensions/v1beta1
|
|
||||||
kind: DaemonSet
|
|
||||||
metadata:
|
|
||||||
name: telegraf
|
|
||||||
namespace: telegraf
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: telegraf
|
|
||||||
spec:
|
|
||||||
serviceAccount: telegraf
|
|
||||||
containers:
|
|
||||||
- name: telegraf
|
|
||||||
image: quay.io/org/image:latest
|
|
||||||
imagePullPolicy: IfNotPresent
|
|
||||||
env:
|
|
||||||
- name: POD_NAMESPACE
|
|
||||||
valueFrom:
|
|
||||||
fieldRef:
|
|
||||||
fieldPath: metadata.namespace
|
|
||||||
- name: "HOST_PROC"
|
|
||||||
value: "/rootfs/proc"
|
|
||||||
- name: "HOST_SYS"
|
|
||||||
value: "/rootfs/sys"
|
|
||||||
volumeMounts:
|
|
||||||
- name: sysro
|
|
||||||
mountPath: /rootfs/sys
|
|
||||||
readOnly: true
|
|
||||||
- name: procro
|
|
||||||
mountPath: /rootfs/proc
|
|
||||||
readOnly: true
|
|
||||||
- name: varrunutmpro
|
|
||||||
mountPath: /var/run/utmp
|
|
||||||
readOnly: true
|
|
||||||
- name: logger-redis-creds
|
|
||||||
mountPath: /var/run/secrets/deis/redis/creds
|
|
||||||
volumes:
|
|
||||||
- name: sysro
|
|
||||||
hostPath:
|
|
||||||
path: /sys
|
|
||||||
- name: procro
|
|
||||||
hostPath:
|
|
||||||
path: /proc
|
|
||||||
- name: varrunutmpro
|
|
||||||
hostPath:
|
|
||||||
path: /var/run/utmp
|
|
||||||
```
|
|
||||||
|
|
||||||
### Line Protocol
|
|
||||||
|
|
||||||
#### kubernetes_pod_container
|
|
||||||
```
|
|
||||||
kubernetes_pod_container,host=ip-10-0-0-0.ec2.internal,
|
|
||||||
container_name=deis-controller,namespace=deis,
|
|
||||||
node_name=ip-10-0-0-0.ec2.internal, pod_name=deis-controller-3058870187-xazsr, cpu_usage_core_nanoseconds=2432835i,cpu_usage_nanocores=0i,
|
|
||||||
logsfs_avaialble_bytes=121128271872i,logsfs_capacity_bytes=153567944704i,
|
|
||||||
logsfs_used_bytes=20787200i,memory_major_page_faults=0i,
|
|
||||||
memory_page_faults=175i,memory_rss_bytes=0i,
|
|
||||||
memory_usage_bytes=0i,memory_working_set_bytes=0i,
|
|
||||||
rootfs_available_bytes=121128271872i,rootfs_capacity_bytes=153567944704i,
|
|
||||||
rootfs_used_bytes=1110016i 1476477530000000000
|
|
||||||
```
|
|
||||||
|
|
||||||
#### kubernetes_pod_volume
|
|
||||||
```
|
|
||||||
kubernetes_pod_volume,host=ip-10-0-0-0.ec2.internal,name=default-token-f7wts,
|
|
||||||
namespace=kube-system,node_name=ip-10-0-0-0.ec2.internal,
|
|
||||||
pod_name=kubernetes-dashboard-v1.1.1-t4x4t, available_bytes=8415240192i,
|
|
||||||
capacity_bytes=8415252480i,used_bytes=12288i 1476477530000000000
|
|
||||||
```
|
|
||||||
|
|
||||||
#### kubernetes_pod_network
|
|
||||||
```
|
|
||||||
kubernetes_pod_network,host=ip-10-0-0-0.ec2.internal,namespace=deis,
|
|
||||||
node_name=ip-10-0-0-0.ec2.internal,pod_name=deis-controller-3058870187-xazsr,
|
|
||||||
rx_bytes=120671099i,rx_errors=0i,
|
|
||||||
tx_bytes=102451983i,tx_errors=0i 1476477530000000000
|
|
||||||
```
|
|
||||||
@@ -1,242 +0,0 @@
|
|||||||
package kubernetes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
|
||||||
"github.com/influxdata/telegraf/internal"
|
|
||||||
"github.com/influxdata/telegraf/internal/errchan"
|
|
||||||
"github.com/influxdata/telegraf/plugins/inputs"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Kubernetes represents the config object for the plugin
|
|
||||||
type Kubernetes struct {
|
|
||||||
URL string
|
|
||||||
|
|
||||||
// Bearer Token authorization file path
|
|
||||||
BearerToken string `toml:"bearer_token"`
|
|
||||||
|
|
||||||
// Path to CA file
|
|
||||||
SSLCA string `toml:"ssl_ca"`
|
|
||||||
// Path to host cert file
|
|
||||||
SSLCert string `toml:"ssl_cert"`
|
|
||||||
// Path to cert key file
|
|
||||||
SSLKey string `toml:"ssl_key"`
|
|
||||||
// Use SSL but skip chain & host verification
|
|
||||||
InsecureSkipVerify bool
|
|
||||||
|
|
||||||
RoundTripper http.RoundTripper
|
|
||||||
}
|
|
||||||
|
|
||||||
var sampleConfig = `
|
|
||||||
## URL for the kubelet
|
|
||||||
url = "http://1.1.1.1:10255"
|
|
||||||
|
|
||||||
## Use bearer token for authorization
|
|
||||||
# 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
|
|
||||||
`
|
|
||||||
|
|
||||||
const (
|
|
||||||
summaryEndpoint = `%s/stats/summary`
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
inputs.Add("kubernetes", func() telegraf.Input {
|
|
||||||
return &Kubernetes{}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//SampleConfig returns a sample config
|
|
||||||
func (k *Kubernetes) SampleConfig() string {
|
|
||||||
return sampleConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
//Description returns the description of this plugin
|
|
||||||
func (k *Kubernetes) Description() string {
|
|
||||||
return "Read metrics from the kubernetes kubelet api"
|
|
||||||
}
|
|
||||||
|
|
||||||
//Gather collects kubernetes metrics from a given URL
|
|
||||||
func (k *Kubernetes) Gather(acc telegraf.Accumulator) error {
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
errChan := errchan.New(1)
|
|
||||||
wg.Add(1)
|
|
||||||
go func(k *Kubernetes) {
|
|
||||||
defer wg.Done()
|
|
||||||
errChan.C <- k.gatherSummary(k.URL, acc)
|
|
||||||
}(k)
|
|
||||||
wg.Wait()
|
|
||||||
return errChan.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildURL(endpoint string, base string) (*url.URL, error) {
|
|
||||||
u := fmt.Sprintf(endpoint, base)
|
|
||||||
addr, err := url.Parse(u)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Unable to parse address '%s': %s", u, err)
|
|
||||||
}
|
|
||||||
return addr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *Kubernetes) gatherSummary(baseURL string, acc telegraf.Accumulator) error {
|
|
||||||
url := fmt.Sprintf("%s/stats/summary", baseURL)
|
|
||||||
var req, err = http.NewRequest("GET", url, nil)
|
|
||||||
var token []byte
|
|
||||||
var resp *http.Response
|
|
||||||
|
|
||||||
tlsCfg, err := internal.GetTLSConfig(k.SSLCert, k.SSLKey, k.SSLCA, k.InsecureSkipVerify)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if k.RoundTripper == nil {
|
|
||||||
k.RoundTripper = &http.Transport{
|
|
||||||
TLSHandshakeTimeout: 5 * time.Second,
|
|
||||||
TLSClientConfig: tlsCfg,
|
|
||||||
ResponseHeaderTimeout: time.Duration(3 * time.Second),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if k.BearerToken != "" {
|
|
||||||
token, err = ioutil.ReadFile(k.BearerToken)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req.Header.Set("Authorization", "Bearer "+string(token))
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err = k.RoundTripper.RoundTrip(req)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error making HTTP request to %s: %s", url, err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return fmt.Errorf("%s returned HTTP status %s", url, resp.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
summaryMetrics := &SummaryMetrics{}
|
|
||||||
err = json.NewDecoder(resp.Body).Decode(summaryMetrics)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf(`Error parsing response: %s`, err)
|
|
||||||
}
|
|
||||||
buildSystemContainerMetrics(summaryMetrics, acc)
|
|
||||||
buildNodeMetrics(summaryMetrics, acc)
|
|
||||||
buildPodMetrics(summaryMetrics, acc)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildSystemContainerMetrics(summaryMetrics *SummaryMetrics, acc telegraf.Accumulator) {
|
|
||||||
for _, container := range summaryMetrics.Node.SystemContainers {
|
|
||||||
tags := map[string]string{
|
|
||||||
"node_name": summaryMetrics.Node.NodeName,
|
|
||||||
"container_name": container.Name,
|
|
||||||
}
|
|
||||||
fields := make(map[string]interface{})
|
|
||||||
fields["cpu_usage_nanocores"] = container.CPU.UsageNanoCores
|
|
||||||
fields["cpu_usage_core_nanoseconds"] = container.CPU.UsageCoreNanoSeconds
|
|
||||||
fields["memory_usage_bytes"] = container.Memory.UsageBytes
|
|
||||||
fields["memory_working_set_bytes"] = container.Memory.WorkingSetBytes
|
|
||||||
fields["memory_rss_bytes"] = container.Memory.RSSBytes
|
|
||||||
fields["memory_page_faults"] = container.Memory.PageFaults
|
|
||||||
fields["memory_major_page_faults"] = container.Memory.MajorPageFaults
|
|
||||||
fields["rootfs_available_bytes"] = container.RootFS.AvailableBytes
|
|
||||||
fields["rootfs_capacity_bytes"] = container.RootFS.CapacityBytes
|
|
||||||
fields["logsfs_avaialble_bytes"] = container.LogsFS.AvailableBytes
|
|
||||||
fields["logsfs_capacity_bytes"] = container.LogsFS.CapacityBytes
|
|
||||||
acc.AddFields("kubernetes_system_container", fields, tags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildNodeMetrics(summaryMetrics *SummaryMetrics, acc telegraf.Accumulator) {
|
|
||||||
tags := map[string]string{
|
|
||||||
"node_name": summaryMetrics.Node.NodeName,
|
|
||||||
}
|
|
||||||
fields := make(map[string]interface{})
|
|
||||||
fields["cpu_usage_nanocores"] = summaryMetrics.Node.CPU.UsageNanoCores
|
|
||||||
fields["cpu_usage_core_nanoseconds"] = summaryMetrics.Node.CPU.UsageCoreNanoSeconds
|
|
||||||
fields["memory_available_bytes"] = summaryMetrics.Node.Memory.AvailableBytes
|
|
||||||
fields["memory_usage_bytes"] = summaryMetrics.Node.Memory.UsageBytes
|
|
||||||
fields["memory_working_set_bytes"] = summaryMetrics.Node.Memory.WorkingSetBytes
|
|
||||||
fields["memory_rss_bytes"] = summaryMetrics.Node.Memory.RSSBytes
|
|
||||||
fields["memory_page_faults"] = summaryMetrics.Node.Memory.PageFaults
|
|
||||||
fields["memory_major_page_faults"] = summaryMetrics.Node.Memory.MajorPageFaults
|
|
||||||
fields["network_rx_bytes"] = summaryMetrics.Node.Network.RXBytes
|
|
||||||
fields["network_rx_errors"] = summaryMetrics.Node.Network.RXErrors
|
|
||||||
fields["network_tx_bytes"] = summaryMetrics.Node.Network.TXBytes
|
|
||||||
fields["network_tx_errors"] = summaryMetrics.Node.Network.TXErrors
|
|
||||||
fields["fs_available_bytes"] = summaryMetrics.Node.FileSystem.AvailableBytes
|
|
||||||
fields["fs_capacity_bytes"] = summaryMetrics.Node.FileSystem.CapacityBytes
|
|
||||||
fields["fs_used_bytes"] = summaryMetrics.Node.FileSystem.UsedBytes
|
|
||||||
fields["runtime_image_fs_available_bytes"] = summaryMetrics.Node.Runtime.ImageFileSystem.AvailableBytes
|
|
||||||
fields["runtime_image_fs_capacity_bytes"] = summaryMetrics.Node.Runtime.ImageFileSystem.CapacityBytes
|
|
||||||
fields["runtime_image_fs_used_bytes"] = summaryMetrics.Node.Runtime.ImageFileSystem.UsedBytes
|
|
||||||
acc.AddFields("kubernetes_node", fields, tags)
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildPodMetrics(summaryMetrics *SummaryMetrics, acc telegraf.Accumulator) {
|
|
||||||
for _, pod := range summaryMetrics.Pods {
|
|
||||||
for _, container := range pod.Containers {
|
|
||||||
tags := map[string]string{
|
|
||||||
"node_name": summaryMetrics.Node.NodeName,
|
|
||||||
"namespace": pod.PodRef.Namespace,
|
|
||||||
"container_name": container.Name,
|
|
||||||
"pod_name": pod.PodRef.Name,
|
|
||||||
}
|
|
||||||
fields := make(map[string]interface{})
|
|
||||||
fields["cpu_usage_nanocores"] = container.CPU.UsageNanoCores
|
|
||||||
fields["cpu_usage_core_nanoseconds"] = container.CPU.UsageCoreNanoSeconds
|
|
||||||
fields["memory_usage_bytes"] = container.Memory.UsageBytes
|
|
||||||
fields["memory_working_set_bytes"] = container.Memory.WorkingSetBytes
|
|
||||||
fields["memory_rss_bytes"] = container.Memory.RSSBytes
|
|
||||||
fields["memory_page_faults"] = container.Memory.PageFaults
|
|
||||||
fields["memory_major_page_faults"] = container.Memory.MajorPageFaults
|
|
||||||
fields["rootfs_available_bytes"] = container.RootFS.AvailableBytes
|
|
||||||
fields["rootfs_capacity_bytes"] = container.RootFS.CapacityBytes
|
|
||||||
fields["rootfs_used_bytes"] = container.RootFS.UsedBytes
|
|
||||||
fields["logsfs_avaialble_bytes"] = container.LogsFS.AvailableBytes
|
|
||||||
fields["logsfs_capacity_bytes"] = container.LogsFS.CapacityBytes
|
|
||||||
fields["logsfs_used_bytes"] = container.LogsFS.UsedBytes
|
|
||||||
acc.AddFields("kubernetes_pod_container", fields, tags)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, volume := range pod.Volumes {
|
|
||||||
tags := map[string]string{
|
|
||||||
"node_name": summaryMetrics.Node.NodeName,
|
|
||||||
"pod_name": pod.PodRef.Name,
|
|
||||||
"namespace": pod.PodRef.Namespace,
|
|
||||||
"volume_name": volume.Name,
|
|
||||||
}
|
|
||||||
fields := make(map[string]interface{})
|
|
||||||
fields["available_bytes"] = volume.AvailableBytes
|
|
||||||
fields["capacity_bytes"] = volume.CapacityBytes
|
|
||||||
fields["used_bytes"] = volume.UsedBytes
|
|
||||||
acc.AddFields("kubernetes_pod_volume", fields, tags)
|
|
||||||
}
|
|
||||||
|
|
||||||
tags := map[string]string{
|
|
||||||
"node_name": summaryMetrics.Node.NodeName,
|
|
||||||
"pod_name": pod.PodRef.Name,
|
|
||||||
"namespace": pod.PodRef.Namespace,
|
|
||||||
}
|
|
||||||
fields := make(map[string]interface{})
|
|
||||||
fields["rx_bytes"] = pod.Network.RXBytes
|
|
||||||
fields["rx_errors"] = pod.Network.RXErrors
|
|
||||||
fields["tx_bytes"] = pod.Network.TXBytes
|
|
||||||
fields["tx_errors"] = pod.Network.TXErrors
|
|
||||||
acc.AddFields("kubernetes_pod_network", fields, tags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
package kubernetes
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// SummaryMetrics represents all the summary data about a paritcular node retrieved from a kubelet
|
|
||||||
type SummaryMetrics struct {
|
|
||||||
Node NodeMetrics `json:"node"`
|
|
||||||
Pods []PodMetrics `json:"pods"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodeMetrics represents detailed information about a node
|
|
||||||
type NodeMetrics struct {
|
|
||||||
NodeName string `json:"nodeName"`
|
|
||||||
SystemContainers []ContainerMetrics `json:"systemContainers"`
|
|
||||||
StartTime time.Time `json:"startTime"`
|
|
||||||
CPU CPUMetrics `json:"cpu"`
|
|
||||||
Memory MemoryMetrics `json:"memory"`
|
|
||||||
Network NetworkMetrics `json:"network"`
|
|
||||||
FileSystem FileSystemMetrics `json:"fs"`
|
|
||||||
Runtime RuntimeMetrics `json:"runtime"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainerMetrics represents the metric data collect about a container from the kubelet
|
|
||||||
type ContainerMetrics struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
StartTime time.Time `json:"startTime"`
|
|
||||||
CPU CPUMetrics `json:"cpu"`
|
|
||||||
Memory MemoryMetrics `json:"memory"`
|
|
||||||
RootFS FileSystemMetrics `json:"rootfs"`
|
|
||||||
LogsFS FileSystemMetrics `json:"logs"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RuntimeMetrics contains metric data on the runtime of the system
|
|
||||||
type RuntimeMetrics struct {
|
|
||||||
ImageFileSystem FileSystemMetrics `json:"imageFs"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CPUMetrics represents the cpu usage data of a pod or node
|
|
||||||
type CPUMetrics struct {
|
|
||||||
Time time.Time `json:"time"`
|
|
||||||
UsageNanoCores int64 `json:"usageNanoCores"`
|
|
||||||
UsageCoreNanoSeconds int64 `json:"usageCoreNanoSeconds"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PodMetrics contains metric data on a given pod
|
|
||||||
type PodMetrics struct {
|
|
||||||
PodRef PodReference `json:"podRef"`
|
|
||||||
StartTime time.Time `json:"startTime"`
|
|
||||||
Containers []ContainerMetrics `json:"containers"`
|
|
||||||
Network NetworkMetrics `json:"network"`
|
|
||||||
Volumes []VolumeMetrics `json:"volume"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PodReference is how a pod is identified
|
|
||||||
type PodReference struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Namespace string `json:"namespace"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// MemoryMetrics represents the memory metrics for a pod or node
|
|
||||||
type MemoryMetrics struct {
|
|
||||||
Time time.Time `json:"time"`
|
|
||||||
AvailableBytes int64 `json:"availableBytes"`
|
|
||||||
UsageBytes int64 `json:"usageBytes"`
|
|
||||||
WorkingSetBytes int64 `json:"workingSetBytes"`
|
|
||||||
RSSBytes int64 `json:"rssBytes"`
|
|
||||||
PageFaults int64 `json:"pageFaults"`
|
|
||||||
MajorPageFaults int64 `json:"majorPageFaults"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileSystemMetrics represents disk usage metrics for a pod or node
|
|
||||||
type FileSystemMetrics struct {
|
|
||||||
AvailableBytes int64 `json:"availableBytes"`
|
|
||||||
CapacityBytes int64 `json:"capacityBytes"`
|
|
||||||
UsedBytes int64 `json:"usedBytes"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NetworkMetrics represents network usage data for a pod or node
|
|
||||||
type NetworkMetrics struct {
|
|
||||||
Time time.Time `json:"time"`
|
|
||||||
RXBytes int64 `json:"rxBytes"`
|
|
||||||
RXErrors int64 `json:"rxErrors"`
|
|
||||||
TXBytes int64 `json:"txBytes"`
|
|
||||||
TXErrors int64 `json:"txErrors"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// VolumeMetrics represents the disk usage data for a given volume
|
|
||||||
type VolumeMetrics struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
AvailableBytes int64 `json:"availableBytes"`
|
|
||||||
CapacityBytes int64 `json:"capacityBytes"`
|
|
||||||
UsedBytes int64 `json:"usedBytes"`
|
|
||||||
}
|
|
||||||
@@ -1,289 +0,0 @@
|
|||||||
package kubernetes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/influxdata/telegraf/testutil"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestKubernetesStats(t *testing.T) {
|
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
fmt.Fprintln(w, response)
|
|
||||||
}))
|
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
k := &Kubernetes{
|
|
||||||
URL: ts.URL,
|
|
||||||
}
|
|
||||||
|
|
||||||
var acc testutil.Accumulator
|
|
||||||
err := k.Gather(&acc)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
fields := map[string]interface{}{
|
|
||||||
"cpu_usage_nanocores": int64(56652446),
|
|
||||||
"cpu_usage_core_nanoseconds": int64(101437561712262),
|
|
||||||
"memory_usage_bytes": int64(62529536),
|
|
||||||
"memory_working_set_bytes": int64(62349312),
|
|
||||||
"memory_rss_bytes": int64(47509504),
|
|
||||||
"memory_page_faults": int64(4769397409),
|
|
||||||
"memory_major_page_faults": int64(13),
|
|
||||||
"rootfs_available_bytes": int64(84379979776),
|
|
||||||
"rootfs_capacity_bytes": int64(105553100800),
|
|
||||||
"logsfs_avaialble_bytes": int64(84379979776),
|
|
||||||
"logsfs_capacity_bytes": int64(105553100800),
|
|
||||||
}
|
|
||||||
tags := map[string]string{
|
|
||||||
"node_name": "node1",
|
|
||||||
"container_name": "kubelet",
|
|
||||||
}
|
|
||||||
acc.AssertContainsTaggedFields(t, "kubernetes_system_container", fields, tags)
|
|
||||||
|
|
||||||
fields = map[string]interface{}{
|
|
||||||
"cpu_usage_nanocores": int64(576996212),
|
|
||||||
"cpu_usage_core_nanoseconds": int64(774129887054161),
|
|
||||||
"memory_usage_bytes": int64(12313182208),
|
|
||||||
"memory_working_set_bytes": int64(5081538560),
|
|
||||||
"memory_rss_bytes": int64(35586048),
|
|
||||||
"memory_page_faults": int64(351742),
|
|
||||||
"memory_major_page_faults": int64(1236),
|
|
||||||
"memory_available_bytes": int64(10726387712),
|
|
||||||
"network_rx_bytes": int64(213281337459),
|
|
||||||
"network_rx_errors": int64(0),
|
|
||||||
"network_tx_bytes": int64(292869995684),
|
|
||||||
"network_tx_errors": int64(0),
|
|
||||||
"fs_available_bytes": int64(84379979776),
|
|
||||||
"fs_capacity_bytes": int64(105553100800),
|
|
||||||
"fs_used_bytes": int64(16754286592),
|
|
||||||
"runtime_image_fs_available_bytes": int64(84379979776),
|
|
||||||
"runtime_image_fs_capacity_bytes": int64(105553100800),
|
|
||||||
"runtime_image_fs_used_bytes": int64(5809371475),
|
|
||||||
}
|
|
||||||
tags = map[string]string{
|
|
||||||
"node_name": "node1",
|
|
||||||
}
|
|
||||||
acc.AssertContainsTaggedFields(t, "kubernetes_node", fields, tags)
|
|
||||||
|
|
||||||
fields = map[string]interface{}{
|
|
||||||
"cpu_usage_nanocores": int64(846503),
|
|
||||||
"cpu_usage_core_nanoseconds": int64(56507553554),
|
|
||||||
"memory_usage_bytes": int64(30789632),
|
|
||||||
"memory_working_set_bytes": int64(30789632),
|
|
||||||
"memory_rss_bytes": int64(30695424),
|
|
||||||
"memory_page_faults": int64(10761),
|
|
||||||
"memory_major_page_faults": int64(0),
|
|
||||||
"rootfs_available_bytes": int64(84379979776),
|
|
||||||
"rootfs_capacity_bytes": int64(105553100800),
|
|
||||||
"rootfs_used_bytes": int64(57344),
|
|
||||||
"logsfs_avaialble_bytes": int64(84379979776),
|
|
||||||
"logsfs_capacity_bytes": int64(105553100800),
|
|
||||||
"logsfs_used_bytes": int64(24576),
|
|
||||||
}
|
|
||||||
tags = map[string]string{
|
|
||||||
"node_name": "node1",
|
|
||||||
"container_name": "foocontainer",
|
|
||||||
"namespace": "foons",
|
|
||||||
"pod_name": "foopod",
|
|
||||||
}
|
|
||||||
acc.AssertContainsTaggedFields(t, "kubernetes_pod_container", fields, tags)
|
|
||||||
|
|
||||||
fields = map[string]interface{}{
|
|
||||||
"available_bytes": int64(7903948800),
|
|
||||||
"capacity_bytes": int64(7903961088),
|
|
||||||
"used_bytes": int64(12288),
|
|
||||||
}
|
|
||||||
tags = map[string]string{
|
|
||||||
"node_name": "node1",
|
|
||||||
"volume_name": "volume1",
|
|
||||||
"namespace": "foons",
|
|
||||||
"pod_name": "foopod",
|
|
||||||
}
|
|
||||||
acc.AssertContainsTaggedFields(t, "kubernetes_pod_volume", fields, tags)
|
|
||||||
|
|
||||||
fields = map[string]interface{}{
|
|
||||||
"rx_bytes": int64(70749124),
|
|
||||||
"rx_errors": int64(0),
|
|
||||||
"tx_bytes": int64(47813506),
|
|
||||||
"tx_errors": int64(0),
|
|
||||||
}
|
|
||||||
tags = map[string]string{
|
|
||||||
"node_name": "node1",
|
|
||||||
"namespace": "foons",
|
|
||||||
"pod_name": "foopod",
|
|
||||||
}
|
|
||||||
acc.AssertContainsTaggedFields(t, "kubernetes_pod_network", fields, tags)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var response = `
|
|
||||||
{
|
|
||||||
"node": {
|
|
||||||
"nodeName": "node1",
|
|
||||||
"systemContainers": [
|
|
||||||
{
|
|
||||||
"name": "kubelet",
|
|
||||||
"startTime": "2016-08-25T18:46:52Z",
|
|
||||||
"cpu": {
|
|
||||||
"time": "2016-09-27T16:57:31Z",
|
|
||||||
"usageNanoCores": 56652446,
|
|
||||||
"usageCoreNanoSeconds": 101437561712262
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"time": "2016-09-27T16:57:31Z",
|
|
||||||
"usageBytes": 62529536,
|
|
||||||
"workingSetBytes": 62349312,
|
|
||||||
"rssBytes": 47509504,
|
|
||||||
"pageFaults": 4769397409,
|
|
||||||
"majorPageFaults": 13
|
|
||||||
},
|
|
||||||
"rootfs": {
|
|
||||||
"availableBytes": 84379979776,
|
|
||||||
"capacityBytes": 105553100800
|
|
||||||
},
|
|
||||||
"logs": {
|
|
||||||
"availableBytes": 84379979776,
|
|
||||||
"capacityBytes": 105553100800
|
|
||||||
},
|
|
||||||
"userDefinedMetrics": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "bar",
|
|
||||||
"startTime": "2016-08-25T18:46:52Z",
|
|
||||||
"cpu": {
|
|
||||||
"time": "2016-09-27T16:57:31Z",
|
|
||||||
"usageNanoCores": 56652446,
|
|
||||||
"usageCoreNanoSeconds": 101437561712262
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"time": "2016-09-27T16:57:31Z",
|
|
||||||
"usageBytes": 62529536,
|
|
||||||
"workingSetBytes": 62349312,
|
|
||||||
"rssBytes": 47509504,
|
|
||||||
"pageFaults": 4769397409,
|
|
||||||
"majorPageFaults": 13
|
|
||||||
},
|
|
||||||
"rootfs": {
|
|
||||||
"availableBytes": 84379979776,
|
|
||||||
"capacityBytes": 105553100800
|
|
||||||
},
|
|
||||||
"logs": {
|
|
||||||
"availableBytes": 84379979776,
|
|
||||||
"capacityBytes": 105553100800
|
|
||||||
},
|
|
||||||
"userDefinedMetrics": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"startTime": "2016-08-25T18:46:52Z",
|
|
||||||
"cpu": {
|
|
||||||
"time": "2016-09-27T16:57:41Z",
|
|
||||||
"usageNanoCores": 576996212,
|
|
||||||
"usageCoreNanoSeconds": 774129887054161
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"time": "2016-09-27T16:57:41Z",
|
|
||||||
"availableBytes": 10726387712,
|
|
||||||
"usageBytes": 12313182208,
|
|
||||||
"workingSetBytes": 5081538560,
|
|
||||||
"rssBytes": 35586048,
|
|
||||||
"pageFaults": 351742,
|
|
||||||
"majorPageFaults": 1236
|
|
||||||
},
|
|
||||||
"network": {
|
|
||||||
"time": "2016-09-27T16:57:41Z",
|
|
||||||
"rxBytes": 213281337459,
|
|
||||||
"rxErrors": 0,
|
|
||||||
"txBytes": 292869995684,
|
|
||||||
"txErrors": 0
|
|
||||||
},
|
|
||||||
"fs": {
|
|
||||||
"availableBytes": 84379979776,
|
|
||||||
"capacityBytes": 105553100800,
|
|
||||||
"usedBytes": 16754286592
|
|
||||||
},
|
|
||||||
"runtime": {
|
|
||||||
"imageFs": {
|
|
||||||
"availableBytes": 84379979776,
|
|
||||||
"capacityBytes": 105553100800,
|
|
||||||
"usedBytes": 5809371475
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pods": [
|
|
||||||
{
|
|
||||||
"podRef": {
|
|
||||||
"name": "foopod",
|
|
||||||
"namespace": "foons",
|
|
||||||
"uid": "6d305b06-8419-11e6-825c-42010af000ae"
|
|
||||||
},
|
|
||||||
"startTime": "2016-09-26T18:45:42Z",
|
|
||||||
"containers": [
|
|
||||||
{
|
|
||||||
"name": "foocontainer",
|
|
||||||
"startTime": "2016-09-26T18:46:43Z",
|
|
||||||
"cpu": {
|
|
||||||
"time": "2016-09-27T16:57:32Z",
|
|
||||||
"usageNanoCores": 846503,
|
|
||||||
"usageCoreNanoSeconds": 56507553554
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"time": "2016-09-27T16:57:32Z",
|
|
||||||
"usageBytes": 30789632,
|
|
||||||
"workingSetBytes": 30789632,
|
|
||||||
"rssBytes": 30695424,
|
|
||||||
"pageFaults": 10761,
|
|
||||||
"majorPageFaults": 0
|
|
||||||
},
|
|
||||||
"rootfs": {
|
|
||||||
"availableBytes": 84379979776,
|
|
||||||
"capacityBytes": 105553100800,
|
|
||||||
"usedBytes": 57344
|
|
||||||
},
|
|
||||||
"logs": {
|
|
||||||
"availableBytes": 84379979776,
|
|
||||||
"capacityBytes": 105553100800,
|
|
||||||
"usedBytes": 24576
|
|
||||||
},
|
|
||||||
"userDefinedMetrics": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"network": {
|
|
||||||
"time": "2016-09-27T16:57:34Z",
|
|
||||||
"rxBytes": 70749124,
|
|
||||||
"rxErrors": 0,
|
|
||||||
"txBytes": 47813506,
|
|
||||||
"txErrors": 0
|
|
||||||
},
|
|
||||||
"volume": [
|
|
||||||
{
|
|
||||||
"availableBytes": 7903948800,
|
|
||||||
"capacityBytes": 7903961088,
|
|
||||||
"usedBytes": 12288,
|
|
||||||
"name": "volume1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"availableBytes": 7903956992,
|
|
||||||
"capacityBytes": 7903961088,
|
|
||||||
"usedBytes": 4096,
|
|
||||||
"name": "volume2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"availableBytes": 7903948800,
|
|
||||||
"capacityBytes": 7903961088,
|
|
||||||
"usedBytes": 12288,
|
|
||||||
"name": "volume3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"availableBytes": 7903952896,
|
|
||||||
"capacityBytes": 7903961088,
|
|
||||||
"usedBytes": 8192,
|
|
||||||
"name": "volume4"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}`
|
|
||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
"github.com/vjeantet/grok"
|
"github.com/vjeantet/grok"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
"github.com/influxdata/telegraf/metric"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var timeLayouts = map[string]string{
|
var timeLayouts = map[string]string{
|
||||||
@@ -281,7 +280,7 @@ func (p *Parser) ParseLine(line string) (telegraf.Metric, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return metric.New(p.Measurement, tags, fields, p.tsModder.tsMod(timestamp))
|
return telegraf.NewMetric(p.Measurement, tags, fields, p.tsModder.tsMod(timestamp))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) addCustomPatterns(scanner *bufio.Scanner) {
|
func (p *Parser) addCustomPatterns(scanner *bufio.Scanner) {
|
||||||
|
|||||||
@@ -152,31 +152,6 @@ func TestBuiltinCommonLogFormat(t *testing.T) {
|
|||||||
assert.Equal(t, map[string]string{"verb": "GET", "resp_code": "200"}, m.Tags())
|
assert.Equal(t, map[string]string{"verb": "GET", "resp_code": "200"}, m.Tags())
|
||||||
}
|
}
|
||||||
|
|
||||||
// common log format
|
|
||||||
// 127.0.0.1 user1234 frank1234 [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326
|
|
||||||
func TestBuiltinCommonLogFormatWithNumbers(t *testing.T) {
|
|
||||||
p := &Parser{
|
|
||||||
Patterns: []string{"%{COMMON_LOG_FORMAT}"},
|
|
||||||
}
|
|
||||||
assert.NoError(t, p.Compile())
|
|
||||||
|
|
||||||
// Parse an influxdb POST request
|
|
||||||
m, err := p.ParseLine(`127.0.0.1 user1234 frank1234 [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326`)
|
|
||||||
require.NotNil(t, m)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t,
|
|
||||||
map[string]interface{}{
|
|
||||||
"resp_bytes": int64(2326),
|
|
||||||
"auth": "frank1234",
|
|
||||||
"client_ip": "127.0.0.1",
|
|
||||||
"http_version": float64(1.0),
|
|
||||||
"ident": "user1234",
|
|
||||||
"request": "/apache_pb.gif",
|
|
||||||
},
|
|
||||||
m.Fields())
|
|
||||||
assert.Equal(t, map[string]string{"verb": "GET", "resp_code": "200"}, m.Tags())
|
|
||||||
}
|
|
||||||
|
|
||||||
// combined log format
|
// combined log format
|
||||||
// 127.0.0.1 user-identifier frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326 "-" "Mozilla"
|
// 127.0.0.1 user-identifier frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326 "-" "Mozilla"
|
||||||
func TestBuiltinCombinedLogFormat(t *testing.T) {
|
func TestBuiltinCombinedLogFormat(t *testing.T) {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ RESPONSE_TIME %{DURATION:response_time_ns:duration}
|
|||||||
EXAMPLE_LOG \[%{HTTPDATE:ts:ts-httpd}\] %{NUMBER:myfloat:float} %{RESPONSE_CODE} %{IPORHOST:clientip} %{RESPONSE_TIME}
|
EXAMPLE_LOG \[%{HTTPDATE:ts:ts-httpd}\] %{NUMBER:myfloat:float} %{RESPONSE_CODE} %{IPORHOST:clientip} %{RESPONSE_TIME}
|
||||||
|
|
||||||
# Wider-ranging username matching vs. logstash built-in %{USER}
|
# Wider-ranging username matching vs. logstash built-in %{USER}
|
||||||
NGUSERNAME [a-zA-Z0-9\.\@\-\+_%]+
|
NGUSERNAME [a-zA-Z\.\@\-\+_%]+
|
||||||
NGUSER %{NGUSERNAME}
|
NGUSER %{NGUSERNAME}
|
||||||
# Wider-ranging client IP matching
|
# Wider-ranging client IP matching
|
||||||
CLIENT (?:%{IPORHOST}|%{HOSTPORT}|::1)
|
CLIENT (?:%{IPORHOST}|%{HOSTPORT}|::1)
|
||||||
@@ -64,7 +64,7 @@ CLIENT (?:%{IPORHOST}|%{HOSTPORT}|::1)
|
|||||||
|
|
||||||
# apache & nginx logs, this is also known as the "common log format"
|
# apache & nginx logs, this is also known as the "common log format"
|
||||||
# see https://en.wikipedia.org/wiki/Common_Log_Format
|
# see https://en.wikipedia.org/wiki/Common_Log_Format
|
||||||
COMMON_LOG_FORMAT %{CLIENT:client_ip} %{NOTSPACE:ident} %{NOTSPACE:auth} \[%{HTTPDATE:ts:ts-httpd}\] "(?:%{WORD:verb:tag} %{NOTSPACE:request}(?: HTTP/%{NUMBER:http_version:float})?|%{DATA})" %{NUMBER:resp_code:tag} (?:%{NUMBER:resp_bytes:int}|-)
|
COMMON_LOG_FORMAT %{CLIENT:client_ip} %{NGUSER:ident} %{NGUSER:auth} \[%{HTTPDATE:ts:ts-httpd}\] "(?:%{WORD:verb:tag} %{NOTSPACE:request}(?: HTTP/%{NUMBER:http_version:float})?|%{DATA})" %{NUMBER:resp_code:tag} (?:%{NUMBER:resp_bytes:int}|-)
|
||||||
|
|
||||||
# Combined log format is the same as the common log format but with the addition
|
# Combined log format is the same as the common log format but with the addition
|
||||||
# of two quoted strings at the end for "referrer" and "agent"
|
# of two quoted strings at the end for "referrer" and "agent"
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ RESPONSE_TIME %{DURATION:response_time_ns:duration}
|
|||||||
EXAMPLE_LOG \[%{HTTPDATE:ts:ts-httpd}\] %{NUMBER:myfloat:float} %{RESPONSE_CODE} %{IPORHOST:clientip} %{RESPONSE_TIME}
|
EXAMPLE_LOG \[%{HTTPDATE:ts:ts-httpd}\] %{NUMBER:myfloat:float} %{RESPONSE_CODE} %{IPORHOST:clientip} %{RESPONSE_TIME}
|
||||||
|
|
||||||
# Wider-ranging username matching vs. logstash built-in %{USER}
|
# Wider-ranging username matching vs. logstash built-in %{USER}
|
||||||
NGUSERNAME [a-zA-Z0-9\.\@\-\+_%]+
|
NGUSERNAME [a-zA-Z\.\@\-\+_%]+
|
||||||
NGUSER %{NGUSERNAME}
|
NGUSER %{NGUSERNAME}
|
||||||
# Wider-ranging client IP matching
|
# Wider-ranging client IP matching
|
||||||
CLIENT (?:%{IPORHOST}|%{HOSTPORT}|::1)
|
CLIENT (?:%{IPORHOST}|%{HOSTPORT}|::1)
|
||||||
@@ -60,7 +60,7 @@ CLIENT (?:%{IPORHOST}|%{HOSTPORT}|::1)
|
|||||||
|
|
||||||
# apache & nginx logs, this is also known as the "common log format"
|
# apache & nginx logs, this is also known as the "common log format"
|
||||||
# see https://en.wikipedia.org/wiki/Common_Log_Format
|
# see https://en.wikipedia.org/wiki/Common_Log_Format
|
||||||
COMMON_LOG_FORMAT %{CLIENT:client_ip} %{NOTSPACE:ident} %{NOTSPACE:auth} \[%{HTTPDATE:ts:ts-httpd}\] "(?:%{WORD:verb:tag} %{NOTSPACE:request}(?: HTTP/%{NUMBER:http_version:float})?|%{DATA})" %{NUMBER:resp_code:tag} (?:%{NUMBER:resp_bytes:int}|-)
|
COMMON_LOG_FORMAT %{CLIENT:client_ip} %{NGUSER:ident} %{NGUSER:auth} \[%{HTTPDATE:ts:ts-httpd}\] "(?:%{WORD:verb:tag} %{NOTSPACE:request}(?: HTTP/%{NUMBER:http_version:float})?|%{DATA})" %{NUMBER:resp_code:tag} (?:%{NUMBER:resp_bytes:int}|-)
|
||||||
|
|
||||||
# Combined log format is the same as the common log format but with the addition
|
# Combined log format is the same as the common log format but with the addition
|
||||||
# of two quoted strings at the end for "referrer" and "agent"
|
# of two quoted strings at the end for "referrer" and "agent"
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
testMsg = "cpu_load_short,host=server01 value=23422.0 1422568543702900257\n"
|
testMsg = "cpu_load_short,host=server01 value=23422.0 1422568543702900257"
|
||||||
testMsgGraphite = "cpu.load.short.graphite 23422 1454780029"
|
testMsgGraphite = "cpu.load.short.graphite 23422 1454780029"
|
||||||
testMsgJSON = "{\"a\": 5, \"b\": {\"c\": 6}}\n"
|
testMsgJSON = "{\"a\": 5, \"b\": {\"c\": 6}}\n"
|
||||||
invalidMsg = "cpu_load_short,host=server01 1422568543702900257\n"
|
invalidMsg = "cpu_load_short,host=server01 1422568543702900257"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newTestMQTTConsumer() (*MQTTConsumer, chan mqtt.Message) {
|
func newTestMQTTConsumer() (*MQTTConsumer, chan mqtt.Message) {
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ The unit of fields varies by the tags.
|
|||||||
* file_events_total(float,number)
|
* file_events_total(float,number)
|
||||||
* file_events_seconds_total(float, milliseconds)
|
* file_events_seconds_total(float, milliseconds)
|
||||||
* file_events_bytes_total(float, bytes)
|
* file_events_bytes_total(float, bytes)
|
||||||
* Perf events statements - gathers attributes of each event
|
* Perf file events statements - gathers attributes of each event
|
||||||
* events_statements_total(float, number)
|
* events_statements_total(float, number)
|
||||||
* events_statements_seconds_total(float, millieconds)
|
* events_statements_seconds_total(float, millieconds)
|
||||||
* events_statements_errors_total(float, number)
|
* events_statements_errors_total(float, number)
|
||||||
|
|||||||
@@ -4,16 +4,16 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
"github.com/influxdata/telegraf/internal/errchan"
|
"github.com/influxdata/telegraf/internal/errchan"
|
||||||
"github.com/influxdata/telegraf/plugins/inputs"
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||||||
|
|
||||||
"github.com/go-sql-driver/mysql"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Mysql struct {
|
type Mysql struct {
|
||||||
@@ -69,13 +69,13 @@ var sampleConfig = `
|
|||||||
## gather metrics from SHOW BINARY LOGS command output
|
## gather metrics from SHOW BINARY LOGS command output
|
||||||
gather_binary_logs = false
|
gather_binary_logs = false
|
||||||
#
|
#
|
||||||
## gather metrics from PERFORMANCE_SCHEMA.TABLE_IO_WAITS_SUMMARY_BY_TABLE
|
## gather metrics from PERFORMANCE_SCHEMA.TABLE_IO_WAITS_SUMMART_BY_TABLE
|
||||||
gather_table_io_waits = false
|
gather_table_io_waits = false
|
||||||
#
|
#
|
||||||
## gather metrics from PERFORMANCE_SCHEMA.TABLE_LOCK_WAITS
|
## gather metrics from PERFORMANCE_SCHEMA.TABLE_LOCK_WAITS
|
||||||
gather_table_lock_waits = false
|
gather_table_lock_waits = false
|
||||||
#
|
#
|
||||||
## gather metrics from PERFORMANCE_SCHEMA.TABLE_IO_WAITS_SUMMARY_BY_INDEX_USAGE
|
## gather metrics from PERFORMANCE_SCHEMA.TABLE_IO_WAITS_SUMMART_BY_INDEX_USAGE
|
||||||
gather_index_io_waits = false
|
gather_index_io_waits = false
|
||||||
#
|
#
|
||||||
## gather metrics from PERFORMANCE_SCHEMA.EVENT_WAITS
|
## gather metrics from PERFORMANCE_SCHEMA.EVENT_WAITS
|
||||||
@@ -398,6 +398,27 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func dsnAddTimeout(dsn string) (string, error) {
|
||||||
|
|
||||||
|
// DSN "?timeout=5s" is not valid, but "/?timeout=5s" is valid ("" and "/"
|
||||||
|
// are the same DSN)
|
||||||
|
if dsn == "" {
|
||||||
|
dsn = "/"
|
||||||
|
}
|
||||||
|
u, err := url.Parse(dsn)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
v := u.Query()
|
||||||
|
|
||||||
|
// Only override timeout if not already defined
|
||||||
|
if _, ok := v["timeout"]; ok == false {
|
||||||
|
v.Add("timeout", defaultTimeout.String())
|
||||||
|
u.RawQuery = v.Encode()
|
||||||
|
}
|
||||||
|
return u.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Math constants
|
// Math constants
|
||||||
const (
|
const (
|
||||||
picoSeconds = 1e12
|
picoSeconds = 1e12
|
||||||
@@ -661,7 +682,10 @@ func (m *Mysql) gatherGlobalVariables(db *sql.DB, serv string, acc telegraf.Accu
|
|||||||
var val sql.RawBytes
|
var val sql.RawBytes
|
||||||
|
|
||||||
// parse DSN and save server tag
|
// parse DSN and save server tag
|
||||||
servtag := getDSNTag(serv)
|
servtag, err := parseDSN(serv)
|
||||||
|
if err != nil {
|
||||||
|
servtag = "localhost"
|
||||||
|
}
|
||||||
tags := map[string]string{"server": servtag}
|
tags := map[string]string{"server": servtag}
|
||||||
fields := make(map[string]interface{})
|
fields := make(map[string]interface{})
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
@@ -698,7 +722,10 @@ func (m *Mysql) gatherSlaveStatuses(db *sql.DB, serv string, acc telegraf.Accumu
|
|||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
servtag := getDSNTag(serv)
|
servtag, err := parseDSN(serv)
|
||||||
|
if err != nil {
|
||||||
|
servtag = "localhost"
|
||||||
|
}
|
||||||
|
|
||||||
tags := map[string]string{"server": servtag}
|
tags := map[string]string{"server": servtag}
|
||||||
fields := make(map[string]interface{})
|
fields := make(map[string]interface{})
|
||||||
@@ -743,7 +770,11 @@ func (m *Mysql) gatherBinaryLogs(db *sql.DB, serv string, acc telegraf.Accumulat
|
|||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
// parse DSN and save host as a tag
|
// parse DSN and save host as a tag
|
||||||
servtag := getDSNTag(serv)
|
var servtag string
|
||||||
|
servtag, err = parseDSN(serv)
|
||||||
|
if err != nil {
|
||||||
|
servtag = "localhost"
|
||||||
|
}
|
||||||
tags := map[string]string{"server": servtag}
|
tags := map[string]string{"server": servtag}
|
||||||
var (
|
var (
|
||||||
size uint64 = 0
|
size uint64 = 0
|
||||||
@@ -786,7 +817,11 @@ func (m *Mysql) gatherGlobalStatuses(db *sql.DB, serv string, acc telegraf.Accum
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse the DSN and save host name as a tag
|
// parse the DSN and save host name as a tag
|
||||||
servtag := getDSNTag(serv)
|
var servtag string
|
||||||
|
servtag, err = parseDSN(serv)
|
||||||
|
if err != nil {
|
||||||
|
servtag = "localhost"
|
||||||
|
}
|
||||||
tags := map[string]string{"server": servtag}
|
tags := map[string]string{"server": servtag}
|
||||||
fields := make(map[string]interface{})
|
fields := make(map[string]interface{})
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
@@ -897,7 +932,10 @@ func (m *Mysql) GatherProcessListStatuses(db *sql.DB, serv string, acc telegraf.
|
|||||||
|
|
||||||
var servtag string
|
var servtag string
|
||||||
fields := make(map[string]interface{})
|
fields := make(map[string]interface{})
|
||||||
servtag = getDSNTag(serv)
|
servtag, err = parseDSN(serv)
|
||||||
|
if err != nil {
|
||||||
|
servtag = "localhost"
|
||||||
|
}
|
||||||
|
|
||||||
// mapping of state with its counts
|
// mapping of state with its counts
|
||||||
stateCounts := make(map[string]uint32, len(generalThreadStates))
|
stateCounts := make(map[string]uint32, len(generalThreadStates))
|
||||||
@@ -940,7 +978,10 @@ func (m *Mysql) gatherPerfTableIOWaits(db *sql.DB, serv string, acc telegraf.Acc
|
|||||||
timeFetch, timeInsert, timeUpdate, timeDelete float64
|
timeFetch, timeInsert, timeUpdate, timeDelete float64
|
||||||
)
|
)
|
||||||
|
|
||||||
servtag = getDSNTag(serv)
|
servtag, err = parseDSN(serv)
|
||||||
|
if err != nil {
|
||||||
|
servtag = "localhost"
|
||||||
|
}
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
err = rows.Scan(&objSchema, &objName,
|
err = rows.Scan(&objSchema, &objName,
|
||||||
@@ -989,7 +1030,10 @@ func (m *Mysql) gatherPerfIndexIOWaits(db *sql.DB, serv string, acc telegraf.Acc
|
|||||||
timeFetch, timeInsert, timeUpdate, timeDelete float64
|
timeFetch, timeInsert, timeUpdate, timeDelete float64
|
||||||
)
|
)
|
||||||
|
|
||||||
servtag = getDSNTag(serv)
|
servtag, err = parseDSN(serv)
|
||||||
|
if err != nil {
|
||||||
|
servtag = "localhost"
|
||||||
|
}
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
err = rows.Scan(&objSchema, &objName, &indexName,
|
err = rows.Scan(&objSchema, &objName, &indexName,
|
||||||
@@ -1041,7 +1085,10 @@ func (m *Mysql) gatherInfoSchemaAutoIncStatuses(db *sql.DB, serv string, acc tel
|
|||||||
incValue, maxInt uint64
|
incValue, maxInt uint64
|
||||||
)
|
)
|
||||||
|
|
||||||
servtag := getDSNTag(serv)
|
servtag, err := parseDSN(serv)
|
||||||
|
if err != nil {
|
||||||
|
servtag = "localhost"
|
||||||
|
}
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
if err := rows.Scan(&schema, &table, &column, &incValue, &maxInt); err != nil {
|
if err := rows.Scan(&schema, &table, &column, &incValue, &maxInt); err != nil {
|
||||||
@@ -1085,7 +1132,10 @@ func (m *Mysql) gatherPerfTableLockWaits(db *sql.DB, serv string, acc telegraf.A
|
|||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
servtag := getDSNTag(serv)
|
servtag, err := parseDSN(serv)
|
||||||
|
if err != nil {
|
||||||
|
servtag = "localhost"
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
objectSchema string
|
objectSchema string
|
||||||
@@ -1207,7 +1257,10 @@ func (m *Mysql) gatherPerfEventWaits(db *sql.DB, serv string, acc telegraf.Accum
|
|||||||
starCount, timeWait float64
|
starCount, timeWait float64
|
||||||
)
|
)
|
||||||
|
|
||||||
servtag := getDSNTag(serv)
|
servtag, err := parseDSN(serv)
|
||||||
|
if err != nil {
|
||||||
|
servtag = "localhost"
|
||||||
|
}
|
||||||
tags := map[string]string{
|
tags := map[string]string{
|
||||||
"server": servtag,
|
"server": servtag,
|
||||||
}
|
}
|
||||||
@@ -1242,7 +1295,10 @@ func (m *Mysql) gatherPerfFileEventsStatuses(db *sql.DB, serv string, acc telegr
|
|||||||
sumNumBytesRead, sumNumBytesWrite float64
|
sumNumBytesRead, sumNumBytesWrite float64
|
||||||
)
|
)
|
||||||
|
|
||||||
servtag := getDSNTag(serv)
|
servtag, err := parseDSN(serv)
|
||||||
|
if err != nil {
|
||||||
|
servtag = "localhost"
|
||||||
|
}
|
||||||
tags := map[string]string{
|
tags := map[string]string{
|
||||||
"server": servtag,
|
"server": servtag,
|
||||||
}
|
}
|
||||||
@@ -1309,7 +1365,10 @@ func (m *Mysql) gatherPerfEventsStatements(db *sql.DB, serv string, acc telegraf
|
|||||||
noIndexUsed float64
|
noIndexUsed float64
|
||||||
)
|
)
|
||||||
|
|
||||||
servtag := getDSNTag(serv)
|
servtag, err := parseDSN(serv)
|
||||||
|
if err != nil {
|
||||||
|
servtag = "localhost"
|
||||||
|
}
|
||||||
tags := map[string]string{
|
tags := map[string]string{
|
||||||
"server": servtag,
|
"server": servtag,
|
||||||
}
|
}
|
||||||
@@ -1353,8 +1412,14 @@ func (m *Mysql) gatherPerfEventsStatements(db *sql.DB, serv string, acc telegraf
|
|||||||
|
|
||||||
// gatherTableSchema can be used to gather stats on each schema
|
// gatherTableSchema can be used to gather stats on each schema
|
||||||
func (m *Mysql) gatherTableSchema(db *sql.DB, serv string, acc telegraf.Accumulator) error {
|
func (m *Mysql) gatherTableSchema(db *sql.DB, serv string, acc telegraf.Accumulator) error {
|
||||||
var dbList []string
|
var (
|
||||||
servtag := getDSNTag(serv)
|
dbList []string
|
||||||
|
servtag string
|
||||||
|
)
|
||||||
|
servtag, err := parseDSN(serv)
|
||||||
|
if err != nil {
|
||||||
|
servtag = "localhost"
|
||||||
|
}
|
||||||
|
|
||||||
// if the list of databases if empty, then get all databases
|
// if the list of databases if empty, then get all databases
|
||||||
if len(m.TableSchemaDatabases) == 0 {
|
if len(m.TableSchemaDatabases) == 0 {
|
||||||
@@ -1510,27 +1575,6 @@ func copyTags(in map[string]string) map[string]string {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func dsnAddTimeout(dsn string) (string, error) {
|
|
||||||
conf, err := mysql.ParseDSN(dsn)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if conf.Timeout == 0 {
|
|
||||||
conf.Timeout = time.Second * 5
|
|
||||||
}
|
|
||||||
|
|
||||||
return conf.FormatDSN(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDSNTag(dsn string) string {
|
|
||||||
conf, err := mysql.ParseDSN(dsn)
|
|
||||||
if err != nil {
|
|
||||||
return "127.0.0.1:3306"
|
|
||||||
}
|
|
||||||
return conf.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
inputs.Add("mysql", func() telegraf.Input {
|
inputs.Add("mysql", func() telegraf.Input {
|
||||||
return &Mysql{}
|
return &Mysql{}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func TestMysqlDefaultsToLocal(t *testing.T) {
|
|||||||
assert.True(t, acc.HasMeasurement("mysql"))
|
assert.True(t, acc.HasMeasurement("mysql"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMysqlGetDSNTag(t *testing.T) {
|
func TestMysqlParseDSN(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input string
|
input string
|
||||||
output string
|
output string
|
||||||
@@ -78,9 +78,9 @@ func TestMysqlGetDSNTag(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
output := getDSNTag(test.input)
|
output, _ := parseDSN(test.input)
|
||||||
if output != test.output {
|
if output != test.output {
|
||||||
t.Errorf("Input: %s Expected %s, got %s\n", test.input, test.output, output)
|
t.Errorf("Expected %s, got %s\n", test.output, output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,7 +92,7 @@ func TestMysqlDNSAddTimeout(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"",
|
"",
|
||||||
"tcp(127.0.0.1:3306)/?timeout=5s",
|
"/?timeout=5s",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tcp(192.168.1.1:3306)/",
|
"tcp(192.168.1.1:3306)/",
|
||||||
@@ -104,19 +104,7 @@ func TestMysqlDNSAddTimeout(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"root:passwd@tcp(192.168.1.1:3306)/?tls=false&timeout=10s",
|
"root:passwd@tcp(192.168.1.1:3306)/?tls=false&timeout=10s",
|
||||||
"root:passwd@tcp(192.168.1.1:3306)/?timeout=10s&tls=false",
|
"root:passwd@tcp(192.168.1.1:3306)/?tls=false&timeout=10s",
|
||||||
},
|
|
||||||
{
|
|
||||||
"tcp(10.150.1.123:3306)/",
|
|
||||||
"tcp(10.150.1.123:3306)/?timeout=5s",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"root:@!~(*&$#%(&@#(@&#Password@tcp(10.150.1.123:3306)/",
|
|
||||||
"root:@!~(*&$#%(&@#(@&#Password@tcp(10.150.1.123:3306)/?timeout=5s",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"root:Test3a#@!@tcp(10.150.1.123:3306)/",
|
|
||||||
"root:Test3a#@!@tcp(10.150.1.123:3306)/?timeout=5s",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
85
plugins/inputs/mysql/parse_dsn.go
Normal file
85
plugins/inputs/mysql/parse_dsn.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// parseDSN parses the DSN string to a config
|
||||||
|
func parseDSN(dsn string) (string, error) {
|
||||||
|
//var user, passwd string
|
||||||
|
var addr, net string
|
||||||
|
|
||||||
|
// [user[:password]@][net[(addr)]]/dbname[?param1=value1¶mN=valueN]
|
||||||
|
// Find the last '/' (since the password or the net addr might contain a '/')
|
||||||
|
for i := len(dsn) - 1; i >= 0; i-- {
|
||||||
|
if dsn[i] == '/' {
|
||||||
|
var j, k int
|
||||||
|
|
||||||
|
// left part is empty if i <= 0
|
||||||
|
if i > 0 {
|
||||||
|
// [username[:password]@][protocol[(address)]]
|
||||||
|
// Find the last '@' in dsn[:i]
|
||||||
|
for j = i; j >= 0; j-- {
|
||||||
|
if dsn[j] == '@' {
|
||||||
|
// username[:password]
|
||||||
|
// Find the first ':' in dsn[:j]
|
||||||
|
for k = 0; k < j; k++ {
|
||||||
|
if dsn[k] == ':' {
|
||||||
|
//passwd = dsn[k+1 : j]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//user = dsn[:k]
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// [protocol[(address)]]
|
||||||
|
// Find the first '(' in dsn[j+1:i]
|
||||||
|
for k = j + 1; k < i; k++ {
|
||||||
|
if dsn[k] == '(' {
|
||||||
|
// dsn[i-1] must be == ')' if an address is specified
|
||||||
|
if dsn[i-1] != ')' {
|
||||||
|
if strings.ContainsRune(dsn[k+1:i], ')') {
|
||||||
|
return "", errors.New("Invalid DSN unescaped")
|
||||||
|
}
|
||||||
|
return "", errors.New("Invalid DSN Addr")
|
||||||
|
}
|
||||||
|
addr = dsn[k+1 : i-1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
net = dsn[j+1 : k]
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default network if empty
|
||||||
|
if net == "" {
|
||||||
|
net = "tcp"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default address if empty
|
||||||
|
if addr == "" {
|
||||||
|
switch net {
|
||||||
|
case "tcp":
|
||||||
|
addr = "127.0.0.1:3306"
|
||||||
|
case "unix":
|
||||||
|
addr = "/tmp/mysql.sock"
|
||||||
|
default:
|
||||||
|
return "", errors.New("Default addr for network '" + net + "' unknown")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr, nil
|
||||||
|
}
|
||||||
@@ -28,17 +28,12 @@ type natsConsumer struct {
|
|||||||
Servers []string
|
Servers []string
|
||||||
Secure bool
|
Secure bool
|
||||||
|
|
||||||
// Client pending limits:
|
|
||||||
PendingMessageLimit int
|
|
||||||
PendingBytesLimit int
|
|
||||||
|
|
||||||
// Legacy metric buffer support
|
// Legacy metric buffer support
|
||||||
MetricBuffer int
|
MetricBuffer int
|
||||||
|
|
||||||
parser parsers.Parser
|
parser parsers.Parser
|
||||||
|
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
wg sync.WaitGroup
|
|
||||||
Conn *nats.Conn
|
Conn *nats.Conn
|
||||||
Subs []*nats.Subscription
|
Subs []*nats.Subscription
|
||||||
|
|
||||||
@@ -52,18 +47,13 @@ type natsConsumer struct {
|
|||||||
|
|
||||||
var sampleConfig = `
|
var sampleConfig = `
|
||||||
## urls of NATS servers
|
## urls of NATS servers
|
||||||
# servers = ["nats://localhost:4222"]
|
servers = ["nats://localhost:4222"]
|
||||||
## Use Transport Layer Security
|
## Use Transport Layer Security
|
||||||
# secure = false
|
secure = false
|
||||||
## subject(s) to consume
|
## subject(s) to consume
|
||||||
# subjects = ["telegraf"]
|
subjects = ["telegraf"]
|
||||||
## name a queue group
|
## name a queue group
|
||||||
# queue_group = "telegraf_consumers"
|
queue_group = "telegraf_consumers"
|
||||||
|
|
||||||
## Sets the limits for pending msgs and bytes for each subscription
|
|
||||||
## These shouldn't need to be adjusted except in very high throughput scenarios
|
|
||||||
# pending_message_limit = 65536
|
|
||||||
# pending_bytes_limit = 67108864
|
|
||||||
|
|
||||||
## Data format to consume.
|
## Data format to consume.
|
||||||
## Each data format has it's own unique set of configuration options, read
|
## Each data format has it's own unique set of configuration options, read
|
||||||
@@ -101,15 +91,8 @@ func (n *natsConsumer) Start(acc telegraf.Accumulator) error {
|
|||||||
|
|
||||||
var connectErr error
|
var connectErr error
|
||||||
|
|
||||||
// set default NATS connection options
|
|
||||||
opts := nats.DefaultOptions
|
opts := nats.DefaultOptions
|
||||||
|
|
||||||
// override max reconnection tries
|
|
||||||
opts.MaxReconnect = -1
|
|
||||||
|
|
||||||
// override servers if any were specified
|
|
||||||
opts.Servers = n.Servers
|
opts.Servers = n.Servers
|
||||||
|
|
||||||
opts.Secure = n.Secure
|
opts.Secure = n.Secure
|
||||||
|
|
||||||
if n.Conn == nil || n.Conn.IsClosed() {
|
if n.Conn == nil || n.Conn.IsClosed() {
|
||||||
@@ -122,22 +105,12 @@ func (n *natsConsumer) Start(acc telegraf.Accumulator) error {
|
|||||||
n.errs = make(chan error)
|
n.errs = make(chan error)
|
||||||
n.Conn.SetErrorHandler(n.natsErrHandler)
|
n.Conn.SetErrorHandler(n.natsErrHandler)
|
||||||
|
|
||||||
n.in = make(chan *nats.Msg, 1000)
|
n.in = make(chan *nats.Msg)
|
||||||
for _, subj := range n.Subjects {
|
for _, subj := range n.Subjects {
|
||||||
sub, err := n.Conn.QueueSubscribe(subj, n.QueueGroup, func(m *nats.Msg) {
|
sub, err := n.Conn.ChanQueueSubscribe(subj, n.QueueGroup, n.in)
|
||||||
n.in <- m
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// ensure that the subscription has been processed by the server
|
|
||||||
if err = n.Conn.Flush(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// set the subscription pending limits
|
|
||||||
if err = sub.SetPendingLimits(n.PendingMessageLimit, n.PendingBytesLimit); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
n.Subs = append(n.Subs, sub)
|
n.Subs = append(n.Subs, sub)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,7 +118,6 @@ func (n *natsConsumer) Start(acc telegraf.Accumulator) error {
|
|||||||
n.done = make(chan struct{})
|
n.done = make(chan struct{})
|
||||||
|
|
||||||
// Start the message reader
|
// Start the message reader
|
||||||
n.wg.Add(1)
|
|
||||||
go n.receiver()
|
go n.receiver()
|
||||||
log.Printf("I! Started the NATS consumer service, nats: %v, subjects: %v, queue: %v\n",
|
log.Printf("I! Started the NATS consumer service, nats: %v, subjects: %v, queue: %v\n",
|
||||||
n.Conn.ConnectedUrl(), n.Subjects, n.QueueGroup)
|
n.Conn.ConnectedUrl(), n.Subjects, n.QueueGroup)
|
||||||
@@ -156,7 +128,7 @@ func (n *natsConsumer) Start(acc telegraf.Accumulator) error {
|
|||||||
// receiver() reads all incoming messages from NATS, and parses them into
|
// receiver() reads all incoming messages from NATS, and parses them into
|
||||||
// telegraf metrics.
|
// telegraf metrics.
|
||||||
func (n *natsConsumer) receiver() {
|
func (n *natsConsumer) receiver() {
|
||||||
defer n.wg.Done()
|
defer n.clean()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-n.done:
|
case <-n.done:
|
||||||
@@ -172,11 +144,17 @@ func (n *natsConsumer) receiver() {
|
|||||||
for _, metric := range metrics {
|
for _, metric := range metrics {
|
||||||
n.acc.AddFields(metric.Name(), metric.Fields(), metric.Tags(), metric.Time())
|
n.acc.AddFields(metric.Name(), metric.Fields(), metric.Tags(), metric.Time())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *natsConsumer) clean() {
|
func (n *natsConsumer) clean() {
|
||||||
|
n.Lock()
|
||||||
|
defer n.Unlock()
|
||||||
|
close(n.in)
|
||||||
|
close(n.errs)
|
||||||
|
|
||||||
for _, sub := range n.Subs {
|
for _, sub := range n.Subs {
|
||||||
if err := sub.Unsubscribe(); err != nil {
|
if err := sub.Unsubscribe(); err != nil {
|
||||||
log.Printf("E! Error unsubscribing from subject %s in queue %s: %s\n",
|
log.Printf("E! Error unsubscribing from subject %s in queue %s: %s\n",
|
||||||
@@ -192,8 +170,6 @@ func (n *natsConsumer) clean() {
|
|||||||
func (n *natsConsumer) Stop() {
|
func (n *natsConsumer) Stop() {
|
||||||
n.Lock()
|
n.Lock()
|
||||||
close(n.done)
|
close(n.done)
|
||||||
n.wg.Wait()
|
|
||||||
n.clean()
|
|
||||||
n.Unlock()
|
n.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,13 +179,6 @@ func (n *natsConsumer) Gather(acc telegraf.Accumulator) error {
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
inputs.Add("nats_consumer", func() telegraf.Input {
|
inputs.Add("nats_consumer", func() telegraf.Input {
|
||||||
return &natsConsumer{
|
return &natsConsumer{}
|
||||||
Servers: []string{"nats://localhost:4222"},
|
|
||||||
Secure: false,
|
|
||||||
Subjects: []string{"telegraf"},
|
|
||||||
QueueGroup: "telegraf_consumers",
|
|
||||||
PendingBytesLimit: nats.DefaultSubPendingBytesLimit,
|
|
||||||
PendingMessageLimit: nats.DefaultSubPendingMsgsLimit,
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
testMsg = "cpu_load_short,host=server01 value=23422.0 1422568543702900257\n"
|
testMsg = "cpu_load_short,host=server01 value=23422.0 1422568543702900257"
|
||||||
testMsgGraphite = "cpu.load.short.graphite 23422 1454780029"
|
testMsgGraphite = "cpu.load.short.graphite 23422 1454780029"
|
||||||
testMsgJSON = "{\"a\": 5, \"b\": {\"c\": 6}}\n"
|
testMsgJSON = "{\"a\": 5, \"b\": {\"c\": 6}}\n"
|
||||||
invalidMsg = "cpu_load_short,host=server01 1422568543702900257\n"
|
invalidMsg = "cpu_load_short,host=server01 1422568543702900257"
|
||||||
metricBuffer = 5
|
metricBuffer = 5
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -39,7 +39,6 @@ func TestRunParser(t *testing.T) {
|
|||||||
defer close(n.done)
|
defer close(n.done)
|
||||||
|
|
||||||
n.parser, _ = parsers.NewInfluxParser()
|
n.parser, _ = parsers.NewInfluxParser()
|
||||||
n.wg.Add(1)
|
|
||||||
go n.receiver()
|
go n.receiver()
|
||||||
in <- natsMsg(testMsg)
|
in <- natsMsg(testMsg)
|
||||||
time.Sleep(time.Millisecond * 25)
|
time.Sleep(time.Millisecond * 25)
|
||||||
@@ -57,7 +56,6 @@ func TestRunParserInvalidMsg(t *testing.T) {
|
|||||||
defer close(n.done)
|
defer close(n.done)
|
||||||
|
|
||||||
n.parser, _ = parsers.NewInfluxParser()
|
n.parser, _ = parsers.NewInfluxParser()
|
||||||
n.wg.Add(1)
|
|
||||||
go n.receiver()
|
go n.receiver()
|
||||||
in <- natsMsg(invalidMsg)
|
in <- natsMsg(invalidMsg)
|
||||||
time.Sleep(time.Millisecond * 25)
|
time.Sleep(time.Millisecond * 25)
|
||||||
@@ -75,7 +73,6 @@ func TestRunParserAndGather(t *testing.T) {
|
|||||||
defer close(n.done)
|
defer close(n.done)
|
||||||
|
|
||||||
n.parser, _ = parsers.NewInfluxParser()
|
n.parser, _ = parsers.NewInfluxParser()
|
||||||
n.wg.Add(1)
|
|
||||||
go n.receiver()
|
go n.receiver()
|
||||||
in <- natsMsg(testMsg)
|
in <- natsMsg(testMsg)
|
||||||
time.Sleep(time.Millisecond * 25)
|
time.Sleep(time.Millisecond * 25)
|
||||||
@@ -94,7 +91,6 @@ func TestRunParserAndGatherGraphite(t *testing.T) {
|
|||||||
defer close(n.done)
|
defer close(n.done)
|
||||||
|
|
||||||
n.parser, _ = parsers.NewGraphiteParser("_", []string{}, nil)
|
n.parser, _ = parsers.NewGraphiteParser("_", []string{}, nil)
|
||||||
n.wg.Add(1)
|
|
||||||
go n.receiver()
|
go n.receiver()
|
||||||
in <- natsMsg(testMsgGraphite)
|
in <- natsMsg(testMsgGraphite)
|
||||||
time.Sleep(time.Millisecond * 25)
|
time.Sleep(time.Millisecond * 25)
|
||||||
@@ -113,7 +109,6 @@ func TestRunParserAndGatherJSON(t *testing.T) {
|
|||||||
defer close(n.done)
|
defer close(n.done)
|
||||||
|
|
||||||
n.parser, _ = parsers.NewJSONParser("nats_json_test", []string{}, nil)
|
n.parser, _ = parsers.NewJSONParser("nats_json_test", []string{}, nil)
|
||||||
n.wg.Add(1)
|
|
||||||
go n.receiver()
|
go n.receiver()
|
||||||
in <- natsMsg(testMsgJSON)
|
in <- natsMsg(testMsgJSON)
|
||||||
time.Sleep(time.Millisecond * 25)
|
time.Sleep(time.Millisecond * 25)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import (
|
|||||||
// This test is modeled after the kafka consumer integration test
|
// This test is modeled after the kafka consumer integration test
|
||||||
func TestReadsMetricsFromNSQ(t *testing.T) {
|
func TestReadsMetricsFromNSQ(t *testing.T) {
|
||||||
msgID := nsq.MessageID{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 's', 'd', 'f', 'g', 'h'}
|
msgID := nsq.MessageID{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 's', 'd', 'f', 'g', 'h'}
|
||||||
msg := nsq.NewMessage(msgID, []byte("cpu_load_short,direction=in,host=server01,region=us-west value=23422.0 1422568543702900257\n"))
|
msg := nsq.NewMessage(msgID, []byte("cpu_load_short,direction=in,host=server01,region=us-west value=23422.0 1422568543702900257"))
|
||||||
|
|
||||||
script := []instruction{
|
script := []instruction{
|
||||||
// SUB
|
// SUB
|
||||||
|
|||||||
@@ -122,9 +122,6 @@ func (g *phpfpm) gatherServer(addr string, acc telegraf.Accumulator) error {
|
|||||||
fcgiIp := socketAddr[0]
|
fcgiIp := socketAddr[0]
|
||||||
fcgiPort, _ := strconv.Atoi(socketAddr[1])
|
fcgiPort, _ := strconv.Atoi(socketAddr[1])
|
||||||
fcgi, err = newFcgiClient(fcgiIp, fcgiPort)
|
fcgi, err = newFcgiClient(fcgiIp, fcgiPort)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(u.Path) > 1 {
|
if len(u.Path) > 1 {
|
||||||
statusPath = strings.Trim(u.Path, "/")
|
statusPath = strings.Trim(u.Path, "/")
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -52,13 +52,13 @@ const sampleConfig = `
|
|||||||
## urls to ping
|
## urls to ping
|
||||||
urls = ["www.google.com"] # required
|
urls = ["www.google.com"] # required
|
||||||
## number of pings to send per collection (ping -c <COUNT>)
|
## number of pings to send per collection (ping -c <COUNT>)
|
||||||
# count = 1
|
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 = 1.0
|
ping_interval = 0.0
|
||||||
## per-ping timeout, in s. 0 == no timeout (ping -W <TIMEOUT>)
|
## per-ping timeout, in s. 0 == no timeout (ping -W <TIMEOUT>)
|
||||||
# timeout = 1.0
|
timeout = 1.0
|
||||||
## interface to send ping from (ping -I <INTERFACE>)
|
## interface to send ping from (ping -I <INTERFACE>)
|
||||||
# interface = ""
|
interface = ""
|
||||||
`
|
`
|
||||||
|
|
||||||
func (_ *Ping) SampleConfig() string {
|
func (_ *Ping) SampleConfig() string {
|
||||||
@@ -200,11 +200,6 @@ func processPingOutput(out string) (int, int, float64, error) {
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
inputs.Add("ping", func() telegraf.Input {
|
inputs.Add("ping", func() telegraf.Input {
|
||||||
return &Ping{
|
return &Ping{pingHost: hostPinger}
|
||||||
pingHost: hostPinger,
|
|
||||||
PingInterval: 1.0,
|
|
||||||
Count: 1,
|
|
||||||
Timeout: 1.0,
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ type Postgresql struct {
|
|||||||
Tagvalue string
|
Tagvalue string
|
||||||
Measurement string
|
Measurement string
|
||||||
}
|
}
|
||||||
Debug bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type query []struct {
|
type query []struct {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user