Compare commits
8 Commits
master
...
release-1.
Author | SHA1 | Date |
---|---|---|
Patrick Hemmer | 3da3a22791 | |
Patrick Hemmer | bed09e0a9c | |
Cameron Sparr | 94de9dca1f | |
John Engelman | 8ecfe13bf8 | |
John Engelman | a90a687d89 | |
Cameron Sparr | 5ef6fe1d85 | |
Cameron Sparr | f9aef06a3c | |
Cameron Sparr | 105bb65f73 |
25
CHANGELOG.md
25
CHANGELOG.md
|
@ -1,4 +1,29 @@
|
||||||
|
## v1.2 [unreleased]
|
||||||
|
|
||||||
|
### Release Notes
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
### Bugfixes
|
||||||
|
|
||||||
## v1.1 [unreleased]
|
## v1.1 [unreleased]
|
||||||
|
- [#1949](https://github.com/influxdata/telegraf/issues/1949): Fix windows `net` plugin.
|
||||||
|
|
||||||
|
## v1.1.1 [unreleased]
|
||||||
|
## 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
|
### Release Notes
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
@ -623,7 +623,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 +641,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 = '1m'
|
# period = "5m"
|
||||||
#
|
#
|
||||||
# ## Collection Delay (required - must account for metrics availability via CloudWatch API)
|
# ## Collection Delay (required - must account for metrics availability via CloudWatch API)
|
||||||
# delay = '1m'
|
# delay = "5m"
|
||||||
#
|
#
|
||||||
# ## 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 = '1m'
|
# interval = "5m"
|
||||||
#
|
#
|
||||||
# ## 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 +666,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
|
||||||
|
@ -1979,4 +1979,3 @@
|
||||||
#
|
#
|
||||||
# [inputs.webhooks.rollbar]
|
# [inputs.webhooks.rollbar]
|
||||||
# path = "/rollbar"
|
# path = "/rollbar"
|
||||||
|
|
||||||
|
|
|
@ -35,8 +35,9 @@ 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 straight convert it
|
// see if we can directly convert it
|
||||||
d.Duration, err = time.ParseDuration(string(b))
|
d.Duration, err = time.ParseDuration(string(b))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -142,6 +142,10 @@ func TestDuration(t *testing.T) {
|
||||||
d.UnmarshalTOML([]byte(`1s`))
|
d.UnmarshalTOML([]byte(`1s`))
|
||||||
assert.Equal(t, time.Second, d.Duration)
|
assert.Equal(t, time.Second, d.Duration)
|
||||||
|
|
||||||
|
d = Duration{}
|
||||||
|
d.UnmarshalTOML([]byte(`'1s'`))
|
||||||
|
assert.Equal(t, time.Second, d.Duration)
|
||||||
|
|
||||||
d = Duration{}
|
d = Duration{}
|
||||||
d.UnmarshalTOML([]byte(`10`))
|
d.UnmarshalTOML([]byte(`10`))
|
||||||
assert.Equal(t, 10*time.Second, d.Duration)
|
assert.Equal(t, 10*time.Second, d.Duration)
|
||||||
|
|
|
@ -18,21 +18,28 @@ 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 = '1m'
|
period = "5m"
|
||||||
|
|
||||||
## Collection Delay (required - must account for metrics availability via CloudWatch API)
|
## Collection Delay (required - must account for metrics availability via CloudWatch API)
|
||||||
delay = '1m'
|
delay = "5m"
|
||||||
|
|
||||||
## 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 = '1m'
|
interval = "5m"
|
||||||
|
|
||||||
## 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
|
||||||
|
@ -43,16 +50,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
|
||||||
|
|
||||||
|
@ -71,16 +78,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,22 +80,29 @@ 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 = '1m'
|
period = "5m"
|
||||||
|
|
||||||
## Collection Delay (required - must account for metrics availability via CloudWatch API)
|
## Collection Delay (required - must account for metrics availability via CloudWatch API)
|
||||||
delay = '1m'
|
delay = "5m"
|
||||||
|
|
||||||
## 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 = '1m'
|
interval = "5m"
|
||||||
|
|
||||||
## 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
|
||||||
|
@ -106,12 +113,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"
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +140,6 @@ 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),
|
||||||
|
@ -229,13 +235,12 @@ func (c *CloudWatch) initializeCloudWatch() error {
|
||||||
/*
|
/*
|
||||||
* Fetch available metrics for given CloudWatch Namespace
|
* Fetch available metrics for given CloudWatch Namespace
|
||||||
*/
|
*/
|
||||||
func (c *CloudWatch) fetchNamespaceMetrics() (metrics []*cloudwatch.Metric, err error) {
|
func (c *CloudWatch) fetchNamespaceMetrics() ([]*cloudwatch.Metric, error) {
|
||||||
if c.metricCache != nil && c.metricCache.IsValid() {
|
if c.metricCache != nil && c.metricCache.IsValid() {
|
||||||
metrics = c.metricCache.Metrics
|
return c.metricCache.Metrics, nil
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
metrics = []*cloudwatch.Metric{}
|
metrics := []*cloudwatch.Metric{}
|
||||||
|
|
||||||
var token *string
|
var token *string
|
||||||
for more := true; more; {
|
for more := true; more; {
|
||||||
|
@ -263,7 +268,7 @@ func (c *CloudWatch) fetchNamespaceMetrics() (metrics []*cloudwatch.Metric, err
|
||||||
TTL: c.CacheTTL.Duration,
|
TTL: c.CacheTTL.Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return metrics, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
|
@ -199,65 +200,22 @@ func (t *Table) init() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// init() populates Fields if a table OID is provided.
|
// initBuild initializes the table if it has an OID configured. If so, the
|
||||||
|
// net-snmp tools will be used to look up the OID and auto-populate the table's
|
||||||
|
// fields.
|
||||||
func (t *Table) initBuild() error {
|
func (t *Table) initBuild() error {
|
||||||
if t.Oid == "" {
|
if t.Oid == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
mibName, _, oidText, _, err := snmpTranslate(t.Oid)
|
_, _, oidText, fields, err := snmpTable(t.Oid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Errorf(err, "translating %s", t.Oid)
|
return Errorf(err, "initializing table %s", t.Oid)
|
||||||
}
|
}
|
||||||
if t.Name == "" {
|
if t.Name == "" {
|
||||||
t.Name = oidText
|
t.Name = oidText
|
||||||
}
|
}
|
||||||
mibPrefix := mibName + "::"
|
t.Fields = append(t.Fields, fields...)
|
||||||
oidFullName := mibPrefix + oidText
|
|
||||||
|
|
||||||
// first attempt to get the table's tags
|
|
||||||
tagOids := map[string]struct{}{}
|
|
||||||
// We have to guess that the "entry" oid is `t.Oid+".1"`. snmptable and snmptranslate don't seem to have a way to provide the info.
|
|
||||||
if out, err := execCmd("snmptranslate", "-Td", oidFullName+".1"); err == nil {
|
|
||||||
lines := bytes.Split(out, []byte{'\n'})
|
|
||||||
for _, line := range lines {
|
|
||||||
if !bytes.HasPrefix(line, []byte(" INDEX")) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
i := bytes.Index(line, []byte("{ "))
|
|
||||||
if i == -1 { // parse error
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
line = line[i+2:]
|
|
||||||
i = bytes.Index(line, []byte(" }"))
|
|
||||||
if i == -1 { // parse error
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
line = line[:i]
|
|
||||||
for _, col := range bytes.Split(line, []byte(", ")) {
|
|
||||||
tagOids[mibPrefix+string(col)] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// this won't actually try to run a query. The `-Ch` will just cause it to dump headers.
|
|
||||||
out, err := execCmd("snmptable", "-Ch", "-Cl", "-c", "public", "127.0.0.1", oidFullName)
|
|
||||||
if err != nil {
|
|
||||||
return Errorf(err, "getting table columns for %s", t.Oid)
|
|
||||||
}
|
|
||||||
cols := bytes.SplitN(out, []byte{'\n'}, 2)[0]
|
|
||||||
if len(cols) == 0 {
|
|
||||||
return fmt.Errorf("unable to get columns for table %s", t.Oid)
|
|
||||||
}
|
|
||||||
for _, col := range bytes.Split(cols, []byte{' '}) {
|
|
||||||
if len(col) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
col := string(col)
|
|
||||||
_, isTag := tagOids[mibPrefix+col]
|
|
||||||
t.Fields = append(t.Fields, Field{Name: col, Oid: mibPrefix + col, IsTag: isTag})
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -841,13 +799,141 @@ func fieldConvert(conv string, v interface{}) (interface{}, error) {
|
||||||
return nil, fmt.Errorf("invalid conversion type '%s'", conv)
|
return nil, fmt.Errorf("invalid conversion type '%s'", conv)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type snmpTableCache struct {
|
||||||
|
mibName string
|
||||||
|
oidNum string
|
||||||
|
oidText string
|
||||||
|
fields []Field
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
var snmpTableCaches map[string]snmpTableCache
|
||||||
|
var snmpTableCachesLock sync.Mutex
|
||||||
|
|
||||||
|
// snmpTable resolves the given OID as a table, providing information about the
|
||||||
|
// table and fields within.
|
||||||
|
func snmpTable(oid string) (mibName string, oidNum string, oidText string, fields []Field, err error) {
|
||||||
|
snmpTableCachesLock.Lock()
|
||||||
|
if snmpTableCaches == nil {
|
||||||
|
snmpTableCaches = map[string]snmpTableCache{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var stc snmpTableCache
|
||||||
|
var ok bool
|
||||||
|
if stc, ok = snmpTableCaches[oid]; !ok {
|
||||||
|
stc.mibName, stc.oidNum, stc.oidText, stc.fields, stc.err = snmpTableCall(oid)
|
||||||
|
snmpTableCaches[oid] = stc
|
||||||
|
}
|
||||||
|
|
||||||
|
snmpTableCachesLock.Unlock()
|
||||||
|
return stc.mibName, stc.oidNum, stc.oidText, stc.fields, stc.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func snmpTableCall(oid string) (mibName string, oidNum string, oidText string, fields []Field, err error) {
|
||||||
|
mibName, oidNum, oidText, _, err = snmpTranslate(oid)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", nil, Errorf(err, "translating")
|
||||||
|
}
|
||||||
|
|
||||||
|
mibPrefix := mibName + "::"
|
||||||
|
oidFullName := mibPrefix + oidText
|
||||||
|
|
||||||
|
// first attempt to get the table's tags
|
||||||
|
tagOids := map[string]struct{}{}
|
||||||
|
// We have to guess that the "entry" oid is `oid+".1"`. snmptable and snmptranslate don't seem to have a way to provide the info.
|
||||||
|
if out, err := execCmd("snmptranslate", "-Td", oidFullName+".1"); err == nil {
|
||||||
|
lines := bytes.Split(out, []byte{'\n'})
|
||||||
|
for _, line := range lines {
|
||||||
|
if !bytes.HasPrefix(line, []byte(" INDEX")) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
i := bytes.Index(line, []byte("{ "))
|
||||||
|
if i == -1 { // parse error
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
line = line[i+2:]
|
||||||
|
i = bytes.Index(line, []byte(" }"))
|
||||||
|
if i == -1 { // parse error
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
line = line[:i]
|
||||||
|
for _, col := range bytes.Split(line, []byte(", ")) {
|
||||||
|
tagOids[mibPrefix+string(col)] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this won't actually try to run a query. The `-Ch` will just cause it to dump headers.
|
||||||
|
out, err := execCmd("snmptable", "-Ch", "-Cl", "-c", "public", "127.0.0.1", oidFullName)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", nil, Errorf(err, "getting table columns")
|
||||||
|
}
|
||||||
|
cols := bytes.SplitN(out, []byte{'\n'}, 2)[0]
|
||||||
|
if len(cols) == 0 {
|
||||||
|
return "", "", "", nil, fmt.Errorf("could not find any columns in table")
|
||||||
|
}
|
||||||
|
for _, col := range bytes.Split(cols, []byte{' '}) {
|
||||||
|
if len(col) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
col := string(col)
|
||||||
|
_, isTag := tagOids[mibPrefix+col]
|
||||||
|
fields = append(fields, Field{Name: col, Oid: mibPrefix + col, IsTag: isTag})
|
||||||
|
}
|
||||||
|
|
||||||
|
return mibName, oidNum, oidText, fields, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type snmpTranslateCache struct {
|
||||||
|
mibName string
|
||||||
|
oidNum string
|
||||||
|
oidText string
|
||||||
|
conversion string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
var snmpTranslateCachesLock sync.Mutex
|
||||||
|
var snmpTranslateCaches map[string]snmpTranslateCache
|
||||||
|
|
||||||
// snmpTranslate resolves the given OID.
|
// snmpTranslate resolves the given OID.
|
||||||
func snmpTranslate(oid string) (mibName string, oidNum string, oidText string, conversion string, err error) {
|
func snmpTranslate(oid string) (mibName string, oidNum string, oidText string, conversion string, err error) {
|
||||||
|
snmpTranslateCachesLock.Lock()
|
||||||
|
if snmpTranslateCaches == nil {
|
||||||
|
snmpTranslateCaches = map[string]snmpTranslateCache{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var stc snmpTranslateCache
|
||||||
|
var ok bool
|
||||||
|
if stc, ok = snmpTranslateCaches[oid]; !ok {
|
||||||
|
// This will result in only one call to snmptranslate running at a time.
|
||||||
|
// We could speed it up by putting a lock in snmpTranslateCache and then
|
||||||
|
// returning it immediately, and multiple callers would then release the
|
||||||
|
// snmpTranslateCachesLock and instead wait on the individual
|
||||||
|
// snmpTranlsation.Lock to release. But I don't know that the extra complexity
|
||||||
|
// is worth it. Especially when it would slam the system pretty hard if lots
|
||||||
|
// of lookups are being perfomed.
|
||||||
|
|
||||||
|
stc.mibName, stc.oidNum, stc.oidText, stc.conversion, stc.err = snmpTranslateCall(oid)
|
||||||
|
snmpTranslateCaches[oid] = stc
|
||||||
|
}
|
||||||
|
|
||||||
|
snmpTranslateCachesLock.Unlock()
|
||||||
|
|
||||||
|
return stc.mibName, stc.oidNum, stc.oidText, stc.conversion, stc.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func snmpTranslateCall(oid string) (mibName string, oidNum string, oidText string, conversion string, err error) {
|
||||||
var out []byte
|
var out []byte
|
||||||
if strings.ContainsAny(oid, ":abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") {
|
if strings.ContainsAny(oid, ":abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") {
|
||||||
out, err = execCmd("snmptranslate", "-Td", "-Ob", oid)
|
out, err = execCmd("snmptranslate", "-Td", "-Ob", oid)
|
||||||
} else {
|
} else {
|
||||||
out, err = execCmd("snmptranslate", "-Td", "-Ob", "-m", "all", oid)
|
out, err = execCmd("snmptranslate", "-Td", "-Ob", "-m", "all", oid)
|
||||||
|
if err, ok := err.(*exec.Error); ok && err.Err == exec.ErrNotFound {
|
||||||
|
// Silently discard error if snmptranslate not found and we have a numeric OID.
|
||||||
|
// Meaning we can get by without the lookup.
|
||||||
|
return "", oid, oid, "", nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", "", err
|
return "", "", "", "", err
|
||||||
|
|
|
@ -4,6 +4,7 @@ package snmp
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os/exec"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -198,6 +199,56 @@ func TestSnmpInit(t *testing.T) {
|
||||||
}, s.Fields[0])
|
}, s.Fields[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSnmpInit_noTranslate(t *testing.T) {
|
||||||
|
// override execCommand so it returns exec.ErrNotFound
|
||||||
|
defer func(ec func(string, ...string) *exec.Cmd) { execCommand = ec }(execCommand)
|
||||||
|
execCommand = func(_ string, _ ...string) *exec.Cmd {
|
||||||
|
return exec.Command("snmptranslateExecErrNotFound")
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &Snmp{
|
||||||
|
Fields: []Field{
|
||||||
|
{Oid: ".1.1.1.1", Name: "one", IsTag: true},
|
||||||
|
{Oid: ".1.1.1.2", Name: "two"},
|
||||||
|
{Oid: ".1.1.1.3"},
|
||||||
|
},
|
||||||
|
Tables: []Table{
|
||||||
|
{Fields: []Field{
|
||||||
|
{Oid: ".1.1.1.4", Name: "four", IsTag: true},
|
||||||
|
{Oid: ".1.1.1.5", Name: "five"},
|
||||||
|
{Oid: ".1.1.1.6"},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.init()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, ".1.1.1.1", s.Fields[0].Oid)
|
||||||
|
assert.Equal(t, "one", s.Fields[0].Name)
|
||||||
|
assert.Equal(t, true, s.Fields[0].IsTag)
|
||||||
|
|
||||||
|
assert.Equal(t, ".1.1.1.2", s.Fields[1].Oid)
|
||||||
|
assert.Equal(t, "two", s.Fields[1].Name)
|
||||||
|
assert.Equal(t, false, s.Fields[1].IsTag)
|
||||||
|
|
||||||
|
assert.Equal(t, ".1.1.1.3", s.Fields[2].Oid)
|
||||||
|
assert.Equal(t, ".1.1.1.3", s.Fields[2].Name)
|
||||||
|
assert.Equal(t, false, s.Fields[2].IsTag)
|
||||||
|
|
||||||
|
assert.Equal(t, ".1.1.1.4", s.Tables[0].Fields[0].Oid)
|
||||||
|
assert.Equal(t, "four", s.Tables[0].Fields[0].Name)
|
||||||
|
assert.Equal(t, true, s.Tables[0].Fields[0].IsTag)
|
||||||
|
|
||||||
|
assert.Equal(t, ".1.1.1.5", s.Tables[0].Fields[1].Oid)
|
||||||
|
assert.Equal(t, "five", s.Tables[0].Fields[1].Name)
|
||||||
|
assert.Equal(t, false, s.Tables[0].Fields[1].IsTag)
|
||||||
|
|
||||||
|
assert.Equal(t, ".1.1.1.6", s.Tables[0].Fields[2].Oid)
|
||||||
|
assert.Equal(t, ".1.1.1.6", s.Tables[0].Fields[2].Name)
|
||||||
|
assert.Equal(t, false, s.Tables[0].Fields[2].IsTag)
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetSNMPConnection_v2(t *testing.T) {
|
func TestGetSNMPConnection_v2(t *testing.T) {
|
||||||
s := &Snmp{
|
s := &Snmp{
|
||||||
Timeout: internal.Duration{Duration: 3 * time.Second},
|
Timeout: internal.Duration{Duration: 3 * time.Second},
|
||||||
|
@ -597,6 +648,71 @@ func TestFieldConvert(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSnmpTranslateCache_miss(t *testing.T) {
|
||||||
|
snmpTranslateCaches = nil
|
||||||
|
oid := "IF-MIB::ifPhysAddress.1"
|
||||||
|
mibName, oidNum, oidText, conversion, err := snmpTranslate(oid)
|
||||||
|
assert.Len(t, snmpTranslateCaches, 1)
|
||||||
|
stc := snmpTranslateCaches[oid]
|
||||||
|
require.NotNil(t, stc)
|
||||||
|
assert.Equal(t, mibName, stc.mibName)
|
||||||
|
assert.Equal(t, oidNum, stc.oidNum)
|
||||||
|
assert.Equal(t, oidText, stc.oidText)
|
||||||
|
assert.Equal(t, conversion, stc.conversion)
|
||||||
|
assert.Equal(t, err, stc.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSnmpTranslateCache_hit(t *testing.T) {
|
||||||
|
snmpTranslateCaches = map[string]snmpTranslateCache{
|
||||||
|
"foo": snmpTranslateCache{
|
||||||
|
mibName: "a",
|
||||||
|
oidNum: "b",
|
||||||
|
oidText: "c",
|
||||||
|
conversion: "d",
|
||||||
|
err: fmt.Errorf("e"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mibName, oidNum, oidText, conversion, err := snmpTranslate("foo")
|
||||||
|
assert.Equal(t, "a", mibName)
|
||||||
|
assert.Equal(t, "b", oidNum)
|
||||||
|
assert.Equal(t, "c", oidText)
|
||||||
|
assert.Equal(t, "d", conversion)
|
||||||
|
assert.Equal(t, fmt.Errorf("e"), err)
|
||||||
|
snmpTranslateCaches = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSnmpTableCache_miss(t *testing.T) {
|
||||||
|
snmpTableCaches = nil
|
||||||
|
oid := ".1.0.0.0"
|
||||||
|
mibName, oidNum, oidText, fields, err := snmpTable(oid)
|
||||||
|
assert.Len(t, snmpTableCaches, 1)
|
||||||
|
stc := snmpTableCaches[oid]
|
||||||
|
require.NotNil(t, stc)
|
||||||
|
assert.Equal(t, mibName, stc.mibName)
|
||||||
|
assert.Equal(t, oidNum, stc.oidNum)
|
||||||
|
assert.Equal(t, oidText, stc.oidText)
|
||||||
|
assert.Equal(t, fields, stc.fields)
|
||||||
|
assert.Equal(t, err, stc.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSnmpTableCache_hit(t *testing.T) {
|
||||||
|
snmpTableCaches = map[string]snmpTableCache{
|
||||||
|
"foo": snmpTableCache{
|
||||||
|
mibName: "a",
|
||||||
|
oidNum: "b",
|
||||||
|
oidText: "c",
|
||||||
|
fields: []Field{{Name: "d"}},
|
||||||
|
err: fmt.Errorf("e"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mibName, oidNum, oidText, fields, err := snmpTable("foo")
|
||||||
|
assert.Equal(t, "a", mibName)
|
||||||
|
assert.Equal(t, "b", oidNum)
|
||||||
|
assert.Equal(t, "c", oidText)
|
||||||
|
assert.Equal(t, []Field{{Name: "d"}}, fields)
|
||||||
|
assert.Equal(t, fmt.Errorf("e"), err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestError(t *testing.T) {
|
func TestError(t *testing.T) {
|
||||||
e := fmt.Errorf("nested error")
|
e := fmt.Errorf("nested error")
|
||||||
err := Errorf(e, "top error %d", 123)
|
err := Errorf(e, "top error %d", 123)
|
||||||
|
|
|
@ -30,7 +30,7 @@ type CloudWatch struct {
|
||||||
|
|
||||||
var sampleConfig = `
|
var sampleConfig = `
|
||||||
## 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
|
||||||
|
@ -48,7 +48,7 @@ var sampleConfig = `
|
||||||
#shared_credential_file = ""
|
#shared_credential_file = ""
|
||||||
|
|
||||||
## Namespace for the CloudWatch MetricDatums
|
## Namespace for the CloudWatch MetricDatums
|
||||||
namespace = 'InfluxData/Telegraf'
|
namespace = "InfluxData/Telegraf"
|
||||||
`
|
`
|
||||||
|
|
||||||
func (c *CloudWatch) SampleConfig() string {
|
func (c *CloudWatch) SampleConfig() string {
|
||||||
|
|
|
@ -82,6 +82,6 @@ if [ $? -eq 0 ]; then
|
||||||
unset GOGC
|
unset GOGC
|
||||||
tag=$(git describe --exact-match HEAD)
|
tag=$(git describe --exact-match HEAD)
|
||||||
echo $tag
|
echo $tag
|
||||||
exit_if_fail ./scripts/build.py --release --package --version=$tag --platform=all --arch=all --upload --bucket=dl.influxdata.com/telegraf/releases
|
exit_if_fail ./scripts/build.py --release --package --platform=all --arch=all --upload --bucket=dl.influxdata.com/telegraf/releases
|
||||||
mv build $CIRCLE_ARTIFACTS
|
mv build $CIRCLE_ARTIFACTS
|
||||||
fi
|
fi
|
||||||
|
|
Loading…
Reference in New Issue