Compare commits

..

7 Commits

Author SHA1 Message Date
Gunnar Aasen
4b8d0ad35d Additional code cleanup 2018-05-24 09:45:45 -07:00
Gunnar Aasen
78e5f52966 Revert Godeps changes 2018-05-24 09:45:45 -07:00
Gunnar Aasen
79b6edadd2 Refactor to use AggregatingOutput 2018-05-24 09:44:16 -07:00
Gunnar Aasen
9490a22aeb Output: Azure Monitor: Cleanup and add README 2018-05-24 09:44:16 -07:00
Gunnar Aasen
17093efad5 Output: Azure Monitor: Initial aggregated metric implementation 2018-05-24 09:44:16 -07:00
Mark Simms
d077f5dbc7 Starting on azure monitor metrics integration with MSI auth 2018-05-24 09:42:31 -07:00
Daniel Nelson
6cea487bfc Add idea for an output that aggregates before adding to metric buffer 2018-05-24 09:42:31 -07:00
67 changed files with 1878 additions and 5067 deletions

View File

@@ -1,9 +1,3 @@
## v1.8 [unreleased]
### Features
- [#4236](https://github.com/influxdata/telegraf/pull/4236): Add SSL/TLS support to redis input.
## v1.7 [unreleased]
### Release Notes
@@ -25,7 +19,6 @@
- [jti_openconfig_telemetry](./plugins/inputs/jti_openconfig_telemetry/README.md) - Contributed by @ajhai
- [mcrouter](./plugins/inputs/mcrouter/README.md) - Contributed by @cthayer
- [nvidia_smi](./plugins/inputs/nvidia_smi/README.md) - Contributed by @jackzampolin
- [syslog](./plugins/inputs/syslog/README.md) - Contributed by @influxdata
### New Processors
@@ -63,12 +56,6 @@
- [#3489](https://github.com/influxdata/telegraf/pull/3489): Add burrow input plugin.
- [#3969](https://github.com/influxdata/telegraf/pull/3969): Add option to unbound module to use threads as tags.
- [#4183](https://github.com/influxdata/telegraf/pull/4183): Add support for TLS and username/password auth to aerospike input.
- [#4190](https://github.com/influxdata/telegraf/pull/4190): Add special syslog timestamp parser to grok parser that uses current year.
- [#4181](https://github.com/influxdata/telegraf/pull/4181): Add syslog input plugin.
- [#4212](https://github.com/influxdata/telegraf/pull/4212): Print the enabled aggregator and processor plugins on startup.
- [#3994](https://github.com/influxdata/telegraf/pull/3994): Add static routing_key option to amqp output.
- [#3995](https://github.com/influxdata/telegraf/pull/3995): Add passive mode exchange declaration option to amqp consumer input.
- [#4216](https://github.com/influxdata/telegraf/pull/4216): Add counter fields to pf input.
### Bugfixes
@@ -76,18 +63,6 @@
- [#4036](https://github.com/influxdata/telegraf/pull/4036): Add all win_perf_counters fields for a series in a single metric.
- [#4118](https://github.com/influxdata/telegraf/pull/4118): Report results of dns_query instead of 0ms on timeout.
- [#4155](https://github.com/influxdata/telegraf/pull/4155): Add consul service tags to metric.
- [#2879](https://github.com/influxdata/telegraf/issues/2879): Fix wildcards and multi instance processes in win_perf_counters.
- [#2468](https://github.com/influxdata/telegraf/issues/2468): Fix crash on 32-bit Windows in win_perf_counters.
- [#4198](https://github.com/influxdata/telegraf/issues/4198): Fix win_perf_counters not collecting at every interval.
## v1.6.4 [2018-06-05]
### Bugfixes
- [#4203](https://github.com/influxdata/telegraf/issues/4203): Fix snmp overriding of auto-configured table fields.
- [#4218](https://github.com/influxdata/telegraf/issues/4218): Fix uint support in cloudwatch output.
- [#4188](https://github.com/influxdata/telegraf/pull/4188): Fix documentation of instance_name option in varnish input.
- [#4195](https://github.com/influxdata/telegraf/pull/4195): Revert to previous aerospike library version due to memory leak.
## v1.6.3 [2018-05-21]

6
Godeps
View File

@@ -1,6 +1,7 @@
code.cloudfoundry.org/clock e9dc86bbf0e5bbe6bf7ff5a6f71e048959b61f71
collectd.org 2ce144541b8903101fb8f1483cc0497a68798122
github.com/aerospike/aerospike-client-go 95e1ad7791bdbca44707fedbb29be42024900d9c
github.com/aerospike/aerospike-client-go 9701404f4c60a6ea256595d24bf318f721a7e8b8
github.com/Azure/go-autorest 9ad9326b278af8fa5cc67c30c0ce9a58cc0862b2
github.com/amir/raidman c74861fe6a7bb8ede0a010ce4485bdbb4fc4c985
github.com/apache/thrift 4aaa92ece8503a6da9bc6701604f69acf2b99d07
github.com/aws/aws-sdk-go c861d27d0304a79f727e9a8a4e2ac1e74602fdc0
@@ -28,11 +29,10 @@ github.com/golang/snappy 7db9049039a047d955fe8c19b83c8ff5abd765c7
github.com/go-ole/go-ole be49f7c07711fcb603cff39e1de7c67926dc0ba7
github.com/google/go-cmp f94e52cad91c65a63acc1e75d4be223ea22e99bc
github.com/gorilla/mux 53c1911da2b537f792e7cafcb446b05ffe33b996
github.com/go-redis/redis 83fb42932f6145ce52df09860384a4653d2d332a
github.com/go-redis/redis 73b70592cdaa9e6abdfcfbf97b4a90d80728c836
github.com/go-sql-driver/mysql 2e00b5cd70399450106cec6431c2e2ce3cae5034
github.com/hailocab/go-hostpool e80d13ce29ede4452c43dea11e79b9bc8a15b478
github.com/hashicorp/consul 5174058f0d2bda63fa5198ab96c33d9a909c58ed
github.com/influxdata/go-syslog 84f3b60009444d298f97454feb1f20cf91d1fa6e
github.com/influxdata/tail c43482518d410361b6c383d7aebce33d0471d7bc
github.com/influxdata/toml 5d1d907f22ead1cd47adde17ceec5bda9cacaf8f
github.com/influxdata/wlog 7c63b0a71ef8300adc255344d275e10e5c3a71ec

View File

@@ -211,7 +211,6 @@ configuration options.
* [snmp_legacy](./plugins/inputs/snmp_legacy)
* [solr](./plugins/inputs/solr)
* [sql server](./plugins/inputs/sqlserver) (microsoft)
* [syslog](./plugins/inputs/syslog)
* [teamspeak](./plugins/inputs/teamspeak)
* [tomcat](./plugins/inputs/tomcat)
* [twemproxy](./plugins/inputs/twemproxy)

View File

@@ -362,6 +362,24 @@ func (a *Agent) Run(shutdown chan struct{}) error {
metricC := make(chan telegraf.Metric, 100)
aggC := make(chan telegraf.Metric, 100)
// Start all ServicePlugins
for _, input := range a.Config.Inputs {
input.SetDefaultTags(a.Config.Tags)
switch p := input.Input.(type) {
case telegraf.ServiceInput:
acc := NewAccumulator(input, metricC)
// Service input plugins should set their own precision of their
// metrics.
acc.SetPrecision(time.Nanosecond, 0)
if err := p.Start(acc); err != nil {
log.Printf("E! Service for input %s failed to start, exiting\n%s\n",
input.Name(), err.Error())
return err
}
defer p.Stop()
}
}
// Round collection to nearest interval by sleeping
if a.Config.Agent.RoundInterval {
i := int64(a.Config.Agent.Interval.Duration)
@@ -401,25 +419,6 @@ func (a *Agent) Run(shutdown chan struct{}) error {
}(input, interval)
}
// Start all ServicePlugins inputs after all other
// plugins are loaded so that no metrics get dropped
for _, input := range a.Config.Inputs {
input.SetDefaultTags(a.Config.Tags)
switch p := input.Input.(type) {
case telegraf.ServiceInput:
acc := NewAccumulator(input, metricC)
// Service input plugins should set their own precision of their
// metrics.
acc.SetPrecision(time.Nanosecond, 0)
if err := p.Start(acc); err != nil {
log.Printf("E! Service for input %s failed to start, exiting\n%s\n",
input.Name(), err.Error())
return err
}
defer p.Stop()
}
}
wg.Wait()
a.Close()
return nil

View File

@@ -58,7 +58,7 @@ var fService = flag.String("service", "",
var fRunAsConsole = flag.Bool("console", false, "run as console application (windows only)")
var (
nextVersion = "1.8.0"
nextVersion = "1.7.0"
version string
commit string
branch string
@@ -166,10 +166,8 @@ func reloadLoop(
}()
log.Printf("I! Starting Telegraf %s\n", displayVersion())
log.Printf("I! Loaded inputs: %s", strings.Join(c.InputNames(), " "))
log.Printf("I! Loaded aggregators: %s", strings.Join(c.AggregatorNames(), " "))
log.Printf("I! Loaded processors: %s", strings.Join(c.ProcessorNames(), " "))
log.Printf("I! Loaded outputs: %s", strings.Join(c.OutputNames(), " "))
log.Printf("I! Loaded inputs: %s", strings.Join(c.InputNames(), " "))
log.Printf("I! Tags enabled: %s", c.ListTags())
if *fPidfile != "" {

View File

@@ -44,7 +44,6 @@ following works:
- github.com/hashicorp/raft [MPL](https://github.com/hashicorp/raft/blob/master/LICENSE)
- github.com/influxdata/tail [MIT](https://github.com/influxdata/tail/blob/master/LICENSE.txt)
- github.com/influxdata/toml [MIT](https://github.com/influxdata/toml/blob/master/LICENSE)
- github.com/influxdata/go-syslog [MIT](https://github.com/influxdata/go-syslog/blob/develop/LICENSE)
- github.com/influxdata/wlog [MIT](https://github.com/influxdata/wlog/blob/master/LICENSE)
- github.com/jackc/pgx [MIT](https://github.com/jackc/pgx/blob/master/LICENSE)
- github.com/jmespath/go-jmespath [APACHE](https://github.com/jmespath/go-jmespath/blob/master/LICENSE)

View File

@@ -158,74 +158,30 @@
# # timeout = "5s"
# # Publishes metrics to an AMQP broker
# # Configuration for the AMQP server to send metrics to
# [[outputs.amqp]]
# ## Broker to publish to.
# ## deprecated in 1.7; use the brokers option
# # url = "amqp://localhost:5672/influxdb"
#
# ## Brokers to publish to. If multiple brokers are specified a random broker
# ## will be selected anytime a connection is established. This can be
# ## helpful for load balancing when not using a dedicated load balancer.
# brokers = ["amqp://localhost:5672/influxdb"]
#
# ## Maximum messages to send over a connection. Once this is reached, the
# ## connection is closed and a new connection is made. This can be helpful for
# ## load balancing when not using a dedicated load balancer.
# # max_messages = 0
#
# ## Exchange to declare and publish to.
# ## AMQP url
# url = "amqp://localhost:5672/influxdb"
# ## AMQP exchange
# exchange = "telegraf"
#
# ## Exchange type; common types are "direct", "fanout", "topic", "header", "x-consistent-hash".
# # exchange_type = "topic"
#
# ## If true, exchange will be passively declared.
# # exchange_declare_passive = false
#
# ## If true, exchange will be created as a durable exchange.
# # exchange_durable = true
#
# ## Additional exchange arguments.
# # exchange_arguments = { }
# # exchange_arguments = {"hash_propery" = "timestamp"}
#
# ## Authentication credentials for the PLAIN auth_method.
# # username = ""
# # password = ""
#
# ## Auth method. PLAIN and EXTERNAL are supported
# ## Using EXTERNAL requires enabling the rabbitmq_auth_mechanism_ssl plugin as
# ## described here: https://www.rabbitmq.com/plugins.html
# # auth_method = "PLAIN"
# ## Telegraf tag to use as a routing key
# ## ie, if this tag exists, its value will be used as the routing key
# routing_tag = "host"
# ## Delivery Mode controls if a published message is persistent
# ## Valid options are "transient" and "persistent". default: "transient"
# delivery_mode = "transient"
#
# ## Metric tag to use as a routing key.
# ## ie, if this tag exists, its value will be used as the routing key
# # routing_tag = "host"
#
# ## Static routing key. Used when no routing_tag is set or as a fallback
# ## when the tag specified in routing tag is not found.
# # routing_key = ""
# # routing_key = "telegraf"
#
# ## Delivery Mode controls if a published message is persistent.
# ## One of "transient" or "persistent".
# # delivery_mode = "transient"
#
# ## InfluxDB database added as a message header.
# ## deprecated in 1.7; use the headers option
# ## InfluxDB retention policy
# # retention_policy = "default"
# ## InfluxDB database
# # database = "telegraf"
#
# ## InfluxDB retention policy added as a message header
# ## deprecated in 1.7; use the headers option
# # retention_policy = "default"
#
# ## Static headers added to each published message.
# # headers = { }
# # headers = {"database" = "telegraf", "retention_policy" = "default"}
#
# ## Connection timeout. If not provided, will default to 5s. 0s means no
# ## timeout (not recommended).
# ## Write timeout, formatted as a string. If not provided, will default
# ## to 5s. 0s means no timeout (not recommended).
# # timeout = "5s"
#
# ## Optional TLS Config
@@ -235,16 +191,11 @@
# ## Use TLS but skip chain & host verification
# # insecure_skip_verify = false
#
# ## If true use batch serialization format instead of line based delimiting.
# ## Only applies to data formats which are not line based such as JSON.
# ## Recommended to set to true.
# # use_batch_format = false
#
# ## Data format to output.
# ## Each data format has its own unique set of configuration options, read
# ## more about them here:
# ## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md
# # data_format = "influx"
# data_format = "influx"
# # Send metrics to Azure Application Insights
@@ -393,10 +344,6 @@
# ## Graphite output template
# ## see https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md
# template = "host.tags.measurement.field"
#
# ## Enable Graphite tags support
# # graphite_tag_support = false
#
# ## timeout in seconds for the write connection to graphite
# timeout = 2
#
@@ -429,6 +376,11 @@
# # username = "username"
# # password = "pa$$word"
#
# ## Additional HTTP headers
# # [outputs.http.headers]
# # # Should be set to "application/json" for json data_format
# # Content-Type = "text/plain; charset=utf-8"
#
# ## Optional TLS Config
# # tls_ca = "/etc/telegraf/ca.pem"
# # tls_cert = "/etc/telegraf/cert.pem"
@@ -441,11 +393,6 @@
# ## more about them here:
# ## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md
# # data_format = "influx"
#
# ## Additional HTTP headers
# # [outputs.http.headers]
# # # Should be set manually to "application/json" for json data_format
# # Content-Type = "text/plain; charset=utf-8"
# # Configuration for sending metrics to an Instrumental project
@@ -878,34 +825,6 @@
# PROCESSOR PLUGINS #
###############################################################################
# # Convert values to another metric value type
# [[processors.converter]]
# ## Tags to convert
# ##
# ## The table key determines the target type, and the array of key-values
# ## select the keys to convert. The array may contain globs.
# ## <target-type> = [<tag-key>...]
# [processors.converter.tags]
# string = []
# integer = []
# unsigned = []
# boolean = []
# float = []
#
# ## Fields to convert
# ##
# ## The table key determines the target type, and the array of key-values
# ## select the keys to convert. The array may contain globs.
# ## <target-type> = [<field-key>...]
# [processors.converter.fields]
# tag = []
# string = []
# integer = []
# unsigned = []
# boolean = []
# float = []
# # Apply metric modifications using override semantics.
# [[processors.override]]
# ## All modifications on inputs and aggregators can be overridden:
@@ -922,36 +841,6 @@
# [[processors.printer]]
# # Transforms tag and field values with regex pattern
# [[processors.regex]]
# ## Tag and field conversions defined in a separate sub-tables
# # [[processors.regex.tags]]
# # ## Tag to change
# # key = "resp_code"
# # ## Regular expression to match on a tag value
# # pattern = "^(\\d)\\d\\d$"
# # ## Pattern for constructing a new value (${1} represents first subgroup)
# # replacement = "${1}xx"
#
# # [[processors.regex.fields]]
# # key = "request"
# # ## All the power of the Go regular expressions available here
# # ## For example, named subgroups
# # pattern = "^/api(?P<method>/[\\w/]+)\\S*"
# # replacement = "${method}"
# # ## If result_key is present, a new field will be created
# # ## instead of changing existing field
# # result_key = "method"
#
# ## Multiple conversions may be applied for one field sequentially
# ## Let's extract one more value
# # [[processors.regex.fields]]
# # key = "request"
# # pattern = ".*category=(\\w+).*"
# # replacement = "${1}"
# # result_key = "search_category"
# # Print all metrics that pass through this filter.
# [[processors.topk]]
# ## How many seconds between aggregations
@@ -1138,17 +1027,6 @@
# ## This plugin will query all namespaces the aerospike
# ## server has configured and get stats for them.
# servers = ["localhost:3000"]
#
# # username = "telegraf"
# # password = "pa$$word"
#
# ## Optional TLS Config
# # enable_tls = false
# # tls_ca = "/etc/telegraf/ca.pem"
# # tls_cert = "/etc/telegraf/cert.pem"
# # tls_key = "/etc/telegraf/key.pem"
# ## If false, skip chain & host verification
# # insecure_skip_verify = true
# # Read Apache status information (mod_status)
@@ -1173,32 +1051,6 @@
# # insecure_skip_verify = false
# # Gather metrics from Apache Aurora schedulers
# [[inputs.aurora]]
# ## Schedulers are the base addresses of your Aurora Schedulers
# schedulers = ["http://127.0.0.1:8081"]
#
# ## Set of role types to collect metrics from.
# ##
# ## The scheduler roles are checked each interval by contacting the
# ## scheduler nodes; zookeeper is not contacted.
# # roles = ["leader", "follower"]
#
# ## Timeout is the max time for total network operations.
# # timeout = "5s"
#
# ## Username and password are sent using HTTP Basic Auth.
# # username = "username"
# # password = "pa$$word"
#
# ## Optional TLS Config
# # tls_ca = "/etc/telegraf/ca.pem"
# # tls_cert = "/etc/telegraf/cert.pem"
# # tls_key = "/etc/telegraf/key.pem"
# ## Use TLS but skip chain & host verification
# # insecure_skip_verify = false
# # Read metrics of bcache from stats_total and dirty_data
# [[inputs.bcache]]
# ## Bcache sets path
@@ -1223,49 +1075,6 @@
# # bond_interfaces = ["bond0"]
# # Collect Kafka topics and consumers status from Burrow HTTP API.
# [[inputs.burrow]]
# ## Burrow API endpoints in format "schema://host:port".
# ## Default is "http://localhost:8000".
# servers = ["http://localhost:8000"]
#
# ## Override Burrow API prefix.
# ## Useful when Burrow is behind reverse-proxy.
# # api_prefix = "/v3/kafka"
#
# ## Maximum time to receive response.
# # response_timeout = "5s"
#
# ## Limit per-server concurrent connections.
# ## Useful in case of large number of topics or consumer groups.
# # concurrent_connections = 20
#
# ## Filter clusters, default is no filtering.
# ## Values can be specified as glob patterns.
# # clusters_include = []
# # clusters_exclude = []
#
# ## Filter consumer groups, default is no filtering.
# ## Values can be specified as glob patterns.
# # groups_include = []
# # groups_exclude = []
#
# ## Filter topics, default is no filtering.
# ## Values can be specified as glob patterns.
# # topics_include = []
# # topics_exclude = []
#
# ## Credentials for basic HTTP authentication.
# # username = ""
# # password = ""
#
# ## Optional SSL config
# # ssl_ca = "/etc/telegraf/ca.pem"
# # ssl_cert = "/etc/telegraf/cert.pem"
# # ssl_key = "/etc/telegraf/key.pem"
# # insecure_skip_verify = false
# # Collects performance metrics from the MON and OSD nodes in a Ceph storage cluster.
# [[inputs.ceph]]
# ## This is the recommended interval to poll. Too frequent and you will lose
@@ -2787,9 +2596,6 @@
# ## Remove numbers from field names.
# ## If true, a field name like 'temp1_input' will be changed to 'temp_input'.
# # remove_numbers = true
#
# ## Timeout is the maximum amount of time that the sensors command can run.
# # timeout = "5s"
# # Read metrics from storage devices supporting S.M.A.R.T.
@@ -3141,27 +2947,23 @@
# pools = ["redis_pool", "mc_pool"]
# # A plugin to collect stats from the Unbound DNS resolver
# # A plugin to collect stats from Unbound - a validating, recursive, and caching DNS resolver
# [[inputs.unbound]]
# ## Address of server to connect to, read from unbound conf default, optionally ':port'
# ## Will lookup IP if given a hostname
# server = "127.0.0.1:8953"
#
# ## If running as a restricted user you can prepend sudo for additional access:
# # use_sudo = false
# #use_sudo = false
#
# ## The default location of the unbound-control binary can be overridden with:
# # binary = "/usr/sbin/unbound-control"
# binary = "/usr/sbin/unbound-control"
#
# ## The default timeout of 1s can be overriden with:
# # timeout = "1s"
# timeout = "1s"
#
# ## When set to true, thread metrics are tagged with the thread id.
# ##
# ## The default is false for backwards compatibility, and will be change to
# ## true in a future version. It is recommended to set to true on new
# ## deployments.
# thread_as_tag = false
# ## Use the builtin fielddrop/fieldpass telegraf filters in order to keep/remove specific fields
# fieldpass = ["total_*", "num_*","time_up", "mem_*"]
#
# ## IP of server to connect to, read from unbound conf default, optionally ':port'
# ## Will lookup IP if given a hostname
# server = "127.0.0.1:8953"
# # A plugin to collect stats from Varnish HTTP Cache
@@ -3180,7 +2982,7 @@
#
# ## Optional name for the varnish instance (or working directory) to query
# ## Usually appened after -n in varnish cli
# # instance_name = instanceName
# #name = instanceName
# # Read metrics of ZFS from arcstats, zfetchstats, vdev_cache_stats, and pools
@@ -3227,43 +3029,17 @@
# # AMQP consumer plugin
# [[inputs.amqp_consumer]]
# ## Broker to consume from.
# ## deprecated in 1.7; use the brokers option
# # url = "amqp://localhost:5672/influxdb"
#
# ## Brokers to consume from. If multiple brokers are specified a random broker
# ## will be selected anytime a connection is established. This can be
# ## helpful for load balancing when not using a dedicated load balancer.
# brokers = ["amqp://localhost:5672/influxdb"]
#
# ## Authentication credentials for the PLAIN auth_method.
# # username = ""
# # password = ""
#
# ## Exchange to declare and consume from.
# ## AMQP url
# url = "amqp://localhost:5672/influxdb"
# ## AMQP exchange
# exchange = "telegraf"
#
# ## Exchange type; common types are "direct", "fanout", "topic", "header", "x-consistent-hash".
# # exchange_type = "topic"
#
# ## If true, exchange will be passively declared.
# # exchange_passive = false
#
# ## Exchange durability can be either "transient" or "durable".
# # exchange_durability = "durable"
#
# ## Additional exchange arguments.
# # exchange_arguments = { }
# # exchange_arguments = {"hash_propery" = "timestamp"}
#
# ## AMQP queue name
# queue = "telegraf"
#
# ## Binding Key
# binding_key = "#"
#
# ## Maximum number of messages server should give to the worker.
# # prefetch_count = 50
# prefetch_count = 50
#
# ## Auth method. PLAIN and EXTERNAL are supported
# ## Using EXTERNAL requires enabling the rabbitmq_auth_mechanism_ssl plugin as
@@ -3784,46 +3560,6 @@
# percentile_limit = 1000
# # Accepts syslog messages per RFC5425
# [[inputs.syslog]]
# ## Specify an ip or hostname with port - eg., tcp://localhost:6514, tcp://10.0.0.1:6514
# ## Protocol, address and port to host the syslog receiver.
# ## If no host is specified, then localhost is used.
# ## If no port is specified, 6514 is used (RFC5425#section-4.1).
# server = "tcp://:6514"
#
# ## TLS Config
# # tls_allowed_cacerts = ["/etc/telegraf/ca.pem"]
# # tls_cert = "/etc/telegraf/cert.pem"
# # tls_key = "/etc/telegraf/key.pem"
#
# ## Period between keep alive probes.
# ## 0 disables keep alive probes.
# ## Defaults to the OS configuration.
# ## Only applies to stream sockets (e.g. TCP).
# # keep_alive_period = "5m"
#
# ## Maximum number of concurrent connections (default = 0).
# ## 0 means unlimited.
# ## Only applies to stream sockets (e.g. TCP).
# # max_connections = 1024
#
# ## Read timeout (default = 500ms).
# ## 0 means unlimited.
# # read_timeout = 500ms
#
# ## Whether to parse in best effort mode or not (default = false).
# ## By default best effort parsing is off.
# # best_effort = false
#
# ## Character to prepend to SD-PARAMs (default = "_").
# ## A syslog message can contain multiple parameters and multiple identifiers within structured data section.
# ## Eg., [id1 name1="val1" name2="val2"][id2 name1="val1" nameA="valA"]
# ## For each combination a field is created.
# ## Its name is created concatenating identifier, sdparam_separator, and parameter name.
# # sdparam_separator = "_"
# # Stream a log file, like the tail -f command
# [[inputs.tail]]
# ## files to tail.

View File

@@ -156,24 +156,6 @@ func (c *Config) InputNames() []string {
return name
}
// Outputs returns a list of strings of the configured aggregators.
func (c *Config) AggregatorNames() []string {
var name []string
for _, aggregator := range c.Aggregators {
name = append(name, aggregator.Name())
}
return name
}
// Outputs returns a list of strings of the configured processors.
func (c *Config) ProcessorNames() []string {
var name []string
for _, processor := range c.Processors {
name = append(name, processor.Name)
}
return name
}
// Outputs returns a list of strings of the configured outputs.
func (c *Config) OutputNames() []string {
var name []string

View File

@@ -1,7 +1,6 @@
package models
import (
"log"
"time"
"github.com/influxdata/telegraf"
@@ -154,7 +153,6 @@ func (r *RunningAggregator) Run(
m.Time().After(r.periodEnd.Add(truncation).Add(r.Config.Delay)) {
// the metric is outside the current aggregation period, so
// skip it.
log.Printf("D! aggregator: metric \"%s\" is not in the current timewindow, skipping", m.Name())
continue
}
r.add(m)

View File

@@ -113,6 +113,11 @@ func (ro *RunningOutput) AddMetric(m telegraf.Metric) {
m, _ = metric.New(name, tags, fields, t)
}
if output, ok := ro.Output.(telegraf.AggregatingOutput); ok {
output.Add(m)
return
}
ro.metrics.Add(m)
if ro.metrics.Len() == ro.MetricBatchSize {
batch := ro.metrics.Batch(ro.MetricBatchSize)
@@ -125,6 +130,12 @@ func (ro *RunningOutput) AddMetric(m telegraf.Metric) {
// Write writes all cached points to this output.
func (ro *RunningOutput) Write() error {
if output, ok := ro.Output.(telegraf.AggregatingOutput); ok {
metrics := output.Push()
ro.metrics.Add(metrics...)
output.Reset()
}
nFails, nMetrics := ro.failMetrics.Len(), ro.metrics.Len()
ro.BufferSize.Set(int64(nFails + nMetrics))
log.Printf("D! Output [%s] buffer fullness: %d / %d metrics. ",

View File

@@ -13,6 +13,12 @@ type Output interface {
Write(metrics []Metric) error
}
type AggregatingOutput interface {
Add(in Metric)
Push() []Metric
Reset()
}
type ServiceOutput interface {
// Connect to the Output
Connect() error

View File

@@ -97,7 +97,6 @@ import (
_ "github.com/influxdata/telegraf/plugins/inputs/solr"
_ "github.com/influxdata/telegraf/plugins/inputs/sqlserver"
_ "github.com/influxdata/telegraf/plugins/inputs/statsd"
_ "github.com/influxdata/telegraf/plugins/inputs/syslog"
_ "github.com/influxdata/telegraf/plugins/inputs/sysstat"
_ "github.com/influxdata/telegraf/plugins/inputs/system"
_ "github.com/influxdata/telegraf/plugins/inputs/tail"

View File

@@ -15,48 +15,23 @@ The following defaults are known to work with RabbitMQ:
```toml
# AMQP consumer plugin
[[inputs.amqp_consumer]]
## Broker to consume from.
## deprecated in 1.7; use the brokers option
# url = "amqp://localhost:5672/influxdb"
## Brokers to consume from. If multiple brokers are specified a random broker
## will be selected anytime a connection is established. This can be
## helpful for load balancing when not using a dedicated load balancer.
brokers = ["amqp://localhost:5672/influxdb"]
## Authentication credentials for the PLAIN auth_method.
# username = ""
# password = ""
## Exchange to declare and consume from.
## AMQP url
url = "amqp://localhost:5672/influxdb"
## AMQP exchange
exchange = "telegraf"
## Exchange type; common types are "direct", "fanout", "topic", "header", "x-consistent-hash".
# exchange_type = "topic"
## If true, exchange will be passively declared.
# exchange_passive = false
## Exchange durability can be either "transient" or "durable".
# exchange_durability = "durable"
## Additional exchange arguments.
# exchange_arguments = { }
# exchange_arguments = {"hash_propery" = "timestamp"}
## AMQP queue name
queue = "telegraf"
## Binding Key
binding_key = "#"
## Maximum number of messages server should give to the worker.
# prefetch_count = 50
## Controls how many messages the server will try to keep on the network
## for consumers before receiving delivery acks.
#prefetch_count = 50
## Auth method. PLAIN and EXTERNAL are supported
## Auth method. PLAIN and EXTERNAL are supported.
## Using EXTERNAL requires enabling the rabbitmq_auth_mechanism_ssl plugin as
## described here: https://www.rabbitmq.com/plugins.html
# auth_method = "PLAIN"
## Optional TLS Config
# tls_ca = "/etc/telegraf/ca.pem"
# tls_cert = "/etc/telegraf/cert.pem"

View File

@@ -1,10 +1,8 @@
package amqp_consumer
import (
"errors"
"fmt"
"log"
"math/rand"
"strings"
"sync"
"time"
@@ -19,16 +17,9 @@ import (
// AMQPConsumer is the top level struct for this plugin
type AMQPConsumer struct {
URL string `toml:"url"` // deprecated in 1.7; use brokers
Brokers []string `toml:"brokers"`
Username string `toml:"username"`
Password string `toml:"password"`
Exchange string `toml:"exchange"`
ExchangeType string `toml:"exchange_type"`
ExchangeDurability string `toml:"exchange_durability"`
ExchangePassive bool `toml:"exchange_passive"`
ExchangeArguments map[string]string `toml:"exchange_arguments"`
URL string
// AMQP exchange
Exchange string
// Queue Name
Queue string
// Binding Key
@@ -57,55 +48,23 @@ func (a *externalAuth) Response() string {
}
const (
DefaultAuthMethod = "PLAIN"
DefaultBroker = "amqp://localhost:5672/influxdb"
DefaultExchangeType = "topic"
DefaultExchangeDurability = "durable"
DefaultAuthMethod = "PLAIN"
DefaultPrefetchCount = 50
)
func (a *AMQPConsumer) SampleConfig() string {
return `
## Broker to consume from.
## deprecated in 1.7; use the brokers option
# url = "amqp://localhost:5672/influxdb"
## Brokers to consume from. If multiple brokers are specified a random broker
## will be selected anytime a connection is established. This can be
## helpful for load balancing when not using a dedicated load balancer.
brokers = ["amqp://localhost:5672/influxdb"]
## Authentication credentials for the PLAIN auth_method.
# username = ""
# password = ""
## Exchange to declare and consume from.
## AMQP url
url = "amqp://localhost:5672/influxdb"
## AMQP exchange
exchange = "telegraf"
## Exchange type; common types are "direct", "fanout", "topic", "header", "x-consistent-hash".
# exchange_type = "topic"
## If true, exchange will be passively declared.
# exchange_passive = false
## Exchange durability can be either "transient" or "durable".
# exchange_durability = "durable"
## Additional exchange arguments.
# exchange_arguments = { }
# exchange_arguments = {"hash_propery" = "timestamp"}
## AMQP queue name
queue = "telegraf"
## Binding Key
binding_key = "#"
## Maximum number of messages server should give to the worker.
# prefetch_count = 50
prefetch_count = 50
## Auth method. PLAIN and EXTERNAL are supported
## Using EXTERNAL requires enabling the rabbitmq_auth_mechanism_ssl plugin as
@@ -147,21 +106,16 @@ func (a *AMQPConsumer) createConfig() (*amqp.Config, error) {
return nil, err
}
var auth []amqp.Authentication
// parse auth method
var sasl []amqp.Authentication // nil by default
if strings.ToUpper(a.AuthMethod) == "EXTERNAL" {
auth = []amqp.Authentication{&externalAuth{}}
} else if a.Username != "" || a.Password != "" {
auth = []amqp.Authentication{
&amqp.PlainAuth{
Username: a.Username,
Password: a.Password,
},
}
sasl = []amqp.Authentication{&externalAuth{}}
}
config := amqp.Config{
TLSClientConfig: tls,
SASL: auth, // if nil, it will be PLAIN
SASL: sasl, // if nil, it will be PLAIN
}
return &config, nil
}
@@ -209,55 +163,28 @@ func (a *AMQPConsumer) Start(acc telegraf.Accumulator) error {
}
func (a *AMQPConsumer) connect(amqpConf *amqp.Config) (<-chan amqp.Delivery, error) {
brokers := a.Brokers
if len(brokers) == 0 {
brokers = []string{a.URL}
conn, err := amqp.DialConfig(a.URL, *amqpConf)
if err != nil {
return nil, err
}
a.conn = conn
p := rand.Perm(len(brokers))
for _, n := range p {
broker := brokers[n]
log.Printf("D! [amqp_consumer] connecting to %q", broker)
conn, err := amqp.DialConfig(broker, *amqpConf)
if err == nil {
a.conn = conn
log.Printf("D! [amqp_consumer] connected to %q", broker)
break
}
log.Printf("D! [amqp_consumer] error connecting to %q", broker)
}
if a.conn == nil {
return nil, errors.New("could not connect to any broker")
}
ch, err := a.conn.Channel()
ch, err := conn.Channel()
if err != nil {
return nil, fmt.Errorf("Failed to open a channel: %s", err)
}
var exchangeDurable = true
switch a.ExchangeDurability {
case "transient":
exchangeDurable = false
default:
exchangeDurable = true
}
exchangeArgs := make(amqp.Table, len(a.ExchangeArguments))
for k, v := range a.ExchangeArguments {
exchangeArgs[k] = v
}
err = declareExchange(
ch,
a.Exchange,
a.ExchangeType,
a.ExchangePassive,
exchangeDurable,
exchangeArgs)
err = ch.ExchangeDeclare(
a.Exchange, // name
"topic", // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
if err != nil {
return nil, err
return nil, fmt.Errorf("Failed to declare an exchange: %s", err)
}
q, err := ch.QueueDeclare(
@@ -309,42 +236,6 @@ func (a *AMQPConsumer) connect(amqpConf *amqp.Config) (<-chan amqp.Delivery, err
return msgs, err
}
func declareExchange(
channel *amqp.Channel,
exchangeName string,
exchangeType string,
exchangePassive bool,
exchangeDurable bool,
exchangeArguments amqp.Table,
) error {
var err error
if exchangePassive {
err = channel.ExchangeDeclarePassive(
exchangeName,
exchangeType,
exchangeDurable,
false, // delete when unused
false, // internal
false, // no-wait
exchangeArguments,
)
} else {
err = channel.ExchangeDeclare(
exchangeName,
exchangeType,
exchangeDurable,
false, // delete when unused
false, // internal
false, // no-wait
exchangeArguments,
)
}
if err != nil {
return fmt.Errorf("error declaring exchange: %v", err)
}
return nil
}
// Read messages from queue and add them to the Accumulator
func (a *AMQPConsumer) process(msgs <-chan amqp.Delivery, acc telegraf.Accumulator) {
defer a.wg.Done()
@@ -376,11 +267,8 @@ func (a *AMQPConsumer) Stop() {
func init() {
inputs.Add("amqp_consumer", func() telegraf.Input {
return &AMQPConsumer{
URL: DefaultBroker,
AuthMethod: DefaultAuthMethod,
ExchangeType: DefaultExchangeType,
ExchangeDurability: DefaultExchangeDurability,
PrefetchCount: DefaultPrefetchCount,
AuthMethod: DefaultAuthMethod,
PrefetchCount: DefaultPrefetchCount,
}
})
}

View File

@@ -8,7 +8,6 @@ Supported Burrow version: `1.x`
### Configuration
```
[[inputs.burrow]]
## Burrow API endpoints in format "schema://host:port".
## Default is "http://localhost:8000".
servers = ["http://localhost:8000"]

View File

@@ -3,15 +3,14 @@
package conntrack
import (
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/assert"
"io/ioutil"
"os"
"path"
"strconv"
"strings"
"testing"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/assert"
)
func restoreDflts(savedFiles, savedDirs []string) {
@@ -40,7 +39,6 @@ func TestDefaultsUsed(t *testing.T) {
tmpFile, err := ioutil.TempFile(tmpdir, "ip_conntrack_count")
assert.NoError(t, err)
defer os.Remove(tmpFile.Name())
dfltDirs = []string{tmpdir}
fname := path.Base(tmpFile.Name())
@@ -65,8 +63,6 @@ func TestConfigsUsed(t *testing.T) {
cntFile, err := ioutil.TempFile(tmpdir, "nf_conntrack_count")
maxFile, err := ioutil.TempFile(tmpdir, "nf_conntrack_max")
assert.NoError(t, err)
defer os.Remove(cntFile.Name())
defer os.Remove(maxFile.Name())
dfltDirs = []string{tmpdir}
cntFname := path.Base(cntFile.Name())

View File

@@ -172,14 +172,8 @@ Both `jolokia2_agent` and `jolokia2_proxy` plugins support default configuration
### Example Configurations:
- [ActiveMQ](/plugins/inputs/jolokia2/examples/activemq.conf)
- [BitBucket](/plugins/inputs/jolokia2/examples/bitbucket.conf)
- [Cassandra](/plugins/inputs/jolokia2/examples/cassandra.conf)
- [Hadoop-HDFS](/plugins/inputs/jolokia2/examples/hadoop-hdfs.conf)
- [Java JVM](/plugins/inputs/jolokia2/examples/java.conf)
- [JBoss](/plugins/inputs/jolokia2/examples/jboss.conf)
- [Kafka](/plugins/inputs/jolokia2/examples/kafka.conf)
- [Tomcat](/plugins/inputs/jolokia2/examples/tomcat.conf)
- [Weblogic](/plugins/inputs/jolokia2/examples/weblogic.conf)
- [Cassandra](/plugins/inputs/jolokia2/examples/cassandra.conf)
Please help improve this list and contribute new configuration files by opening an issue or pull request.

View File

@@ -1,57 +0,0 @@
## Jolokia is bundled with ActiveMQ
[[inputs.jolokia2_agent]]
urls = ["http://localhost:8161/api/jolokia"]
name_prefix = "activemq."
username = "admin"
password = "admin"
### JVM Generic
[[inputs.jolokia2_agent.metric]]
name = "OperatingSystem"
mbean = "java.lang:type=OperatingSystem"
paths = ["ProcessCpuLoad","SystemLoadAverage","SystemCpuLoad"]
[[inputs.jolokia2_agent.metric]]
name = "jvm_runtime"
mbean = "java.lang:type=Runtime"
paths = ["Uptime"]
[[inputs.jolokia2_agent.metric]]
name = "jvm_memory"
mbean = "java.lang:type=Memory"
paths = ["HeapMemoryUsage", "NonHeapMemoryUsage", "ObjectPendingFinalizationCount"]
[[inputs.jolokia2_agent.metric]]
name = "jvm_garbage_collector"
mbean = "java.lang:name=*,type=GarbageCollector"
paths = ["CollectionTime", "CollectionCount"]
tag_keys = ["name"]
[[inputs.jolokia2_agent.metric]]
name = "jvm_memory_pool"
mbean = "java.lang:name=*,type=MemoryPool"
paths = ["Usage", "PeakUsage", "CollectionUsage"]
tag_keys = ["name"]
tag_prefix = "pool_"
### ACTIVEMQ
[[inputs.jolokia2_agent.metric]]
name = "queue"
mbean = "org.apache.activemq:brokerName=*,destinationName=*,destinationType=Queue,type=Broker"
paths = ["QueueSize","EnqueueCount","ConsumerCount","DispatchCount","DequeueCount","ProducerCount","InFlightCount"]
tag_keys = ["brokerName","destinationName"]
[[inputs.jolokia2_agent.metric]]
name = "topic"
mbean = "org.apache.activemq:brokerName=*,destinationName=*,destinationType=Topic,type=Broker"
paths = ["ProducerCount","DequeueCount","ConsumerCount","QueueSize","EnqueueCount"]
tag_keys = ["brokerName","destinationName"]
[[inputs.jolokia2_agent.metric]]
name = "broker"
mbean = "org.apache.activemq:brokerName=*,type=Broker"
paths = ["TotalConsumerCount","TotalMessageCount","TotalEnqueueCount","TotalDequeueCount","MemoryLimit","MemoryPercentUsage","StoreLimit","StorePercentUsage","TempPercentUsage","TempLimit"]
tag_keys = ["brokerName"]

View File

@@ -1,39 +0,0 @@
[[inputs.jolokia2_agent]]
urls = ["http://localhost:8778/jolokia"]
name_prefix = "bitbucket."
[[inputs.jolokia2_agent.metric]]
name = "jvm_operatingsystem"
mbean = "java.lang:type=OperatingSystem"
[[inputs.jolokia2_agent.metric]]
name = "jvm_runtime"
mbean = "java.lang:type=Runtime"
[[inputs.jolokia2_agent.metric]]
name = "jvm_thread"
mbean = "java.lang:type=Threading"
[[inputs.jolokia2_agent.metric]]
name = "jvm_memory"
mbean = "java.lang:type=Memory"
[[inputs.jolokia2_agent.metric]]
name = "jvm_class_loading"
mbean = "java.lang:type=ClassLoading"
[[inputs.jolokia2_agent.metric]]
name = "jvm_memory_pool"
mbean = "java.lang:type=MemoryPool,name=*"
[[inputs.jolokia2_agent.metric]]
name = "webhooks"
mbean = "com.atlassian.webhooks:name=*"
[[inputs.jolokia2_agent.metric]]
name = "atlassian"
mbean = "com.atlassian.bitbucket:name=*"
[[inputs.jolokia2_agent.metric]]
name = "thread_pools"
mbean = "com.atlassian.bitbucket.thread-pools:name=*"

View File

@@ -1,85 +0,0 @@
################
# NAMENODE #
################
[[inputs.jolokia2_agent]]
urls = ["http://localhost:8778/jolokia"]
name_prefix = "hadoop.hdfs.namenode."
[[inputs.jolokia2_agent.metric]]
name = "FSNamesystem"
mbean = "Hadoop:name=FSNamesystem,service=NameNode"
paths = ["CapacityTotal", "CapacityRemaining", "CapacityUsedNonDFS", "NumLiveDataNodes", "NumDeadDataNodes", "NumInMaintenanceDeadDataNodes", "NumDecomDeadDataNodes"]
[[inputs.jolokia2_agent.metric]]
name = "FSNamesystemState"
mbean = "Hadoop:name=FSNamesystemState,service=NameNode"
paths = ["VolumeFailuresTotal", "UnderReplicatedBlocks", "BlocksTotal"]
[[inputs.jolokia2_agent.metric]]
name = "OperatingSystem"
mbean = "java.lang:type=OperatingSystem"
paths = ["ProcessCpuLoad", "SystemLoadAverage", "SystemCpuLoad"]
[[inputs.jolokia2_agent.metric]]
name = "jvm_runtime"
mbean = "java.lang:type=Runtime"
paths = ["Uptime"]
[[inputs.jolokia2_agent.metric]]
name = "jvm_memory"
mbean = "java.lang:type=Memory"
paths = ["HeapMemoryUsage", "NonHeapMemoryUsage", "ObjectPendingFinalizationCount"]
[[inputs.jolokia2_agent.metric]]
name = "jvm_garbage_collector"
mbean = "java.lang:name=*,type=GarbageCollector"
paths = ["CollectionTime", "CollectionCount"]
tag_keys = ["name"]
[[inputs.jolokia2_agent.metric]]
name = "jvm_memory_pool"
mbean = "java.lang:name=*,type=MemoryPool"
paths = ["Usage", "PeakUsage", "CollectionUsage"]
tag_keys = ["name"]
tag_prefix = "pool_"
################
# DATANODE #
################
[[inputs.jolokia2_agent]]
urls = ["http://localhost:7778/jolokia"]
name_prefix = "hadoop.hdfs.datanode."
[[inputs.jolokia2_agent.metric]]
name = "FSDatasetState"
mbean = "Hadoop:name=FSDatasetState,service=DataNode"
paths = ["Capacity", "DfsUsed", "Remaining", "NumBlocksFailedToUnCache", "NumBlocksFailedToCache", "NumBlocksCached"]
[[inputs.jolokia2_agent.metric]]
name = "OperatingSystem"
mbean = "java.lang:type=OperatingSystem"
paths = ["ProcessCpuLoad", "SystemLoadAverage", "SystemCpuLoad"]
[[inputs.jolokia2_agent.metric]]
name = "jvm_runtime"
mbean = "java.lang:type=Runtime"
paths = ["Uptime"]
[[inputs.jolokia2_agent.metric]]
name = "jvm_memory"
mbean = "java.lang:type=Memory"
paths = ["HeapMemoryUsage", "NonHeapMemoryUsage", "ObjectPendingFinalizationCount"]
[[inputs.jolokia2_agent.metric]]
name = "jvm_garbage_collector"
mbean = "java.lang:name=*,type=GarbageCollector"
paths = ["CollectionTime", "CollectionCount"]
tag_keys = ["name"]
[[inputs.jolokia2_agent.metric]]
name = "jvm_memory_pool"
mbean = "java.lang:name=*,type=MemoryPool"
paths = ["Usage", "PeakUsage", "CollectionUsage"]
tag_keys = ["name"]
tag_prefix = "pool_"

View File

@@ -1,59 +0,0 @@
[[inputs.jolokia2_agent]]
urls = ["http://localhost:8080/jolokia"]
name_prefix = "jboss."
### JVM Generic
[[inputs.jolokia2_agent.metric]]
name = "OperatingSystem"
mbean = "java.lang:type=OperatingSystem"
paths = ["ProcessCpuLoad","SystemLoadAverage","SystemCpuLoad"]
[[inputs.jolokia2_agent.metric]]
name = "jvm_runtime"
mbean = "java.lang:type=Runtime"
paths = ["Uptime"]
[[inputs.jolokia2_agent.metric]]
name = "jvm_memory"
mbean = "java.lang:type=Memory"
paths = ["HeapMemoryUsage", "NonHeapMemoryUsage", "ObjectPendingFinalizationCount"]
[[inputs.jolokia2_agent.metric]]
name = "jvm_garbage_collector"
mbean = "java.lang:name=*,type=GarbageCollector"
paths = ["CollectionTime", "CollectionCount"]
tag_keys = ["name"]
[[inputs.jolokia2_agent.metric]]
name = "jvm_memory_pool"
mbean = "java.lang:name=*,type=MemoryPool"
paths = ["Usage", "PeakUsage", "CollectionUsage"]
tag_keys = ["name"]
tag_prefix = "pool_"
### JBOSS
[[inputs.jolokia2_agent.metric]]
name = "connectors.http"
mbean = "jboss.as:https-listener=*,server=*,subsystem=undertow"
paths = ["bytesReceived","bytesSent","errorCount","requestCount"]
tag_keys = ["server","https-listener"]
[[inputs.jolokia2_agent.metric]]
name = "connectors.http"
mbean = "jboss.as:http-listener=*,server=*,subsystem=undertow"
paths = ["bytesReceived","bytesSent","errorCount","requestCount"]
tag_keys = ["server","http-listener"]
[[inputs.jolokia2_agent.metric]]
name = "datasource.jdbc"
mbean = "jboss.as:data-source=*,statistics=jdbc,subsystem=datasources"
paths = ["PreparedStatementCacheAccessCount","PreparedStatementCacheHitCount","PreparedStatementCacheMissCount"]
tag_keys = ["data-source"]
[[inputs.jolokia2_agent.metric]]
name = "datasource.pool"
mbean = "jboss.as:data-source=*,statistics=pool,subsystem=datasources"
paths = ["AvailableCount","ActiveCount","MaxUsedCount"]
tag_keys = ["data-source"]

View File

@@ -1,65 +0,0 @@
[[inputs.jolokia2_agent]]
urls = ["http://localhost:8080/jolokia"]
name_prefix = "tomcat."
### JVM Generic
[[inputs.jolokia2_agent.metric]]
name = "OperatingSystem"
mbean = "java.lang:type=OperatingSystem"
paths = ["ProcessCpuLoad","SystemLoadAverage","SystemCpuLoad"]
[[inputs.jolokia2_agent.metric]]
name = "jvm_runtime"
mbean = "java.lang:type=Runtime"
paths = ["Uptime"]
[[inputs.jolokia2_agent.metric]]
name = "jvm_memory"
mbean = "java.lang:type=Memory"
paths = ["HeapMemoryUsage", "NonHeapMemoryUsage", "ObjectPendingFinalizationCount"]
[[inputs.jolokia2_agent.metric]]
name = "jvm_garbage_collector"
mbean = "java.lang:name=*,type=GarbageCollector"
paths = ["CollectionTime", "CollectionCount"]
tag_keys = ["name"]
[[inputs.jolokia2_agent.metric]]
name = "jvm_memory_pool"
mbean = "java.lang:name=*,type=MemoryPool"
paths = ["Usage", "PeakUsage", "CollectionUsage"]
tag_keys = ["name"]
tag_prefix = "pool_"
### TOMCAT
[[inputs.jolokia2_agent.metric]]
name = "GlobalRequestProcessor"
mbean = "Catalina:name=*,type=GlobalRequestProcessor"
paths = ["requestCount","bytesReceived","bytesSent","processingTime","errorCount"]
tag_keys = ["name"]
[[inputs.jolokia2_agent.metric]]
name = "JspMonitor"
mbean = "Catalina:J2EEApplication=*,J2EEServer=*,WebModule=*,name=jsp,type=JspMonitor"
paths = ["jspReloadCount","jspCount","jspUnloadCount"]
tag_keys = ["J2EEApplication","J2EEServer","WebModule"]
[[inputs.jolokia2_agent.metric]]
name = "ThreadPool"
mbean = "Catalina:name=*,type=ThreadPool"
paths = ["maxThreads","currentThreadCount","currentThreadsBusy"]
tag_keys = ["name"]
[[inputs.jolokia2_agent.metric]]
name = "Servlet"
mbean = "Catalina:J2EEApplication=*,J2EEServer=*,WebModule=*,j2eeType=Servlet,name=*"
paths = ["processingTime","errorCount","requestCount"]
tag_keys = ["name","J2EEApplication","J2EEServer","WebModule"]
[[inputs.jolokia2_agent.metric]]
name = "Cache"
mbean = "Catalina:context=*,host=*,name=Cache,type=WebResourceRoot"
paths = ["hitCount","lookupCount"]
tag_keys = ["context","host"]

View File

@@ -1,56 +0,0 @@
[[inputs.jolokia2_agent]]
urls = ["http://localhost:8080/jolokia"]
name_prefix = "weblogic."
### JVM Generic
[[inputs.jolokia2_agent.metric]]
name = "OperatingSystem"
mbean = "java.lang:type=OperatingSystem"
paths = ["ProcessCpuLoad","SystemLoadAverage","SystemCpuLoad"]
[[inputs.jolokia2_agent.metric]]
name = "jvm_runtime"
mbean = "java.lang:type=Runtime"
paths = ["Uptime"]
[[inputs.jolokia2_agent.metric]]
name = "jvm_memory"
mbean = "java.lang:type=Memory"
paths = ["HeapMemoryUsage", "NonHeapMemoryUsage", "ObjectPendingFinalizationCount"]
[[inputs.jolokia2_agent.metric]]
name = "jvm_garbage_collector"
mbean = "java.lang:name=*,type=GarbageCollector"
paths = ["CollectionTime", "CollectionCount"]
tag_keys = ["name"]
[[inputs.jolokia2_agent.metric]]
name = "jvm_memory_pool"
mbean = "java.lang:name=*,type=MemoryPool"
paths = ["Usage", "PeakUsage", "CollectionUsage"]
tag_keys = ["name"]
tag_prefix = "pool_"
### WLS
[[inputs.jolokia2_agent.metric]]
name = "JTARuntime"
mbean = "com.bea:Name=JTARuntime,ServerRuntime=*,Type=JTARuntime"
paths = ["SecondsActiveTotalCount","TransactionRolledBackTotalCount","TransactionRolledBackSystemTotalCount","TransactionRolledBackAppTotalCount","TransactionRolledBackResourceTotalCount","TransactionHeuristicsTotalCount","TransactionAbandonedTotalCount","TransactionTotalCount","TransactionRolledBackTimeoutTotalCount","ActiveTransactionsTotalCount","TransactionCommittedTotalCount"]
tag_keys = ["ServerRuntime"]
tag_prefix = "wls_"
[[inputs.jolokia2_agent.metric]]
name = "ThreadPoolRuntime"
mbean = "com.bea:Name=ThreadPoolRuntime,ServerRuntime=*,Type=ThreadPoolRuntime"
paths = ["StuckThreadCount","CompletedRequestCount","ExecuteThreadTotalCount","ExecuteThreadIdleCount","StandbyThreadCount","Throughput","HoggingThreadCount","PendingUserRequestCount"]
tag_keys = ["ServerRuntime"]
tag_prefix = "wls_"
[[inputs.jolokia2_agent.metric]]
name = "JMSRuntime"
mbean = "com.bea:Name=*.jms,ServerRuntime=*,Type=JMSRuntime"
paths = ["ConnectionsCurrentCount","ConnectionsHighCount","ConnectionsTotalCount","JMSServersCurrentCount","JMSServersHighCount","JMSServersTotalCount"]
tag_keys = ["name","ServerRuntime"]
tag_prefix = "wls_"

View File

@@ -31,21 +31,6 @@ telegraf ALL=(root) NOPASSWD: /sbin/pfctl -s info
- searches (integer, count)
- inserts (integer, count)
- removals (integer, count)
- match (integer, count)
- bad-offset (integer, count)
- fragment (integer, count)
- short (integer, count)
- normalize (integer, count)
- memory (integer, count)
- bad-timestamp (integer, count)
- congestion (integer, count)
- ip-option (integer, count)
- proto-cksum (integer, count)
- state-mismatch (integer, count)
- state-insert (integer, count)
- state-limit (integer, count)
- src-limit (integer, count)
- synproxy (integer, count)
### Example Output:

View File

@@ -67,7 +67,7 @@ func errMissingData(tag string) error {
type pfctlOutputStanza struct {
HeaderRE *regexp.Regexp
ParseFunc func([]string, map[string]interface{}) error
ParseFunc func([]string, telegraf.Accumulator) error
Found bool
}
@@ -76,16 +76,11 @@ var pfctlOutputStanzas = []*pfctlOutputStanza{
HeaderRE: regexp.MustCompile("^State Table"),
ParseFunc: parseStateTable,
},
&pfctlOutputStanza{
HeaderRE: regexp.MustCompile("^Counters"),
ParseFunc: parseCounterTable,
},
}
var anyTableHeaderRE = regexp.MustCompile("^[A-Z]")
func (pf *PF) parsePfctlOutput(pfoutput string, acc telegraf.Accumulator) error {
fields := make(map[string]interface{})
scanner := bufio.NewScanner(strings.NewReader(pfoutput))
for scanner.Scan() {
line := scanner.Text()
@@ -96,14 +91,10 @@ func (pf *PF) parsePfctlOutput(pfoutput string, acc telegraf.Accumulator) error
line = scanner.Text()
for !anyTableHeaderRE.MatchString(line) {
stanzaLines = append(stanzaLines, line)
more := scanner.Scan()
if more {
line = scanner.Text()
} else {
break
}
scanner.Scan()
line = scanner.Text()
}
if perr := s.ParseFunc(stanzaLines, fields); perr != nil {
if perr := s.ParseFunc(stanzaLines, acc); perr != nil {
return perr
}
s.Found = true
@@ -115,8 +106,6 @@ func (pf *PF) parsePfctlOutput(pfoutput string, acc telegraf.Accumulator) error
return errParseHeader
}
}
acc.AddFields(measurement, fields, make(map[string]string))
return nil
}
@@ -135,40 +124,11 @@ var StateTable = []*Entry{
var stateTableRE = regexp.MustCompile(`^ (.*?)\s+(\d+)`)
func parseStateTable(lines []string, fields map[string]interface{}) error {
return storeFieldValues(lines, stateTableRE, fields, StateTable)
}
var CounterTable = []*Entry{
&Entry{"match", "match", -1},
&Entry{"bad-offset", "bad-offset", -1},
&Entry{"fragment", "fragment", -1},
&Entry{"short", "short", -1},
&Entry{"normalize", "normalize", -1},
&Entry{"memory", "memory", -1},
&Entry{"bad-timestamp", "bad-timestamp", -1},
&Entry{"congestion", "congestion", -1},
&Entry{"ip-option", "ip-option", -1},
&Entry{"proto-cksum", "proto-cksum", -1},
&Entry{"state-mismatch", "state-mismatch", -1},
&Entry{"state-insert", "state-insert", -1},
&Entry{"state-limit", "state-limit", -1},
&Entry{"src-limit", "src-limit", -1},
&Entry{"synproxy", "synproxy", -1},
}
var counterTableRE = regexp.MustCompile(`^ (.*?)\s+(\d+)`)
func parseCounterTable(lines []string, fields map[string]interface{}) error {
return storeFieldValues(lines, counterTableRE, fields, CounterTable)
}
func storeFieldValues(lines []string, regex *regexp.Regexp, fields map[string]interface{}, entryTable []*Entry) error {
func parseStateTable(lines []string, acc telegraf.Accumulator) error {
for _, v := range lines {
entries := regex.FindStringSubmatch(v)
entries := stateTableRE.FindStringSubmatch(v)
if entries != nil {
for _, f := range entryTable {
for _, f := range StateTable {
if f.PfctlTitle == entries[1] {
var err error
if f.Value, err = strconv.ParseInt(entries[2], 10, 64); err != nil {
@@ -179,13 +139,15 @@ func storeFieldValues(lines []string, regex *regexp.Regexp, fields map[string]in
}
}
for _, v := range entryTable {
fields := make(map[string]interface{})
for _, v := range StateTable {
if v.Value == -1 {
return errMissingData(v.PfctlTitle)
}
fields[v.Field] = v.Value
}
acc.AddFields(measurement, fields, make(map[string]string))
return nil
}

View File

@@ -152,25 +152,10 @@ Counters
measurements: []measurementResult{
measurementResult{
fields: map[string]interface{}{
"entries": int64(2),
"searches": int64(11325),
"inserts": int64(5),
"removals": int64(3),
"match": int64(11226),
"bad-offset": int64(0),
"fragment": int64(0),
"short": int64(0),
"normalize": int64(0),
"memory": int64(0),
"bad-timestamp": int64(0),
"congestion": int64(0),
"ip-option": int64(0),
"proto-cksum": int64(0),
"state-mismatch": int64(0),
"state-insert": int64(0),
"state-limit": int64(0),
"src-limit": int64(0),
"synproxy": int64(0)},
"entries": int64(2),
"searches": int64(11325),
"inserts": int64(5),
"removals": int64(3)},
tags: map[string]string{},
},
},
@@ -212,25 +197,10 @@ Counters
measurements: []measurementResult{
measurementResult{
fields: map[string]interface{}{
"entries": int64(649),
"searches": int64(18421725761),
"inserts": int64(156762508),
"removals": int64(156761859),
"match": int64(473002784),
"bad-offset": int64(0),
"fragment": int64(2729),
"short": int64(107),
"normalize": int64(1685),
"memory": int64(101),
"bad-timestamp": int64(0),
"congestion": int64(0),
"ip-option": int64(152301),
"proto-cksum": int64(108),
"state-mismatch": int64(24393),
"state-insert": int64(92),
"state-limit": int64(0),
"src-limit": int64(0),
"synproxy": int64(0)},
"entries": int64(649),
"searches": int64(18421725761),
"inserts": int64(156762508),
"removals": int64(156761859)},
tags: map[string]string{},
},
},

View File

@@ -14,13 +14,6 @@
## If no servers are specified, then localhost is used as the host.
## If no port is specified, 6379 is used
servers = ["tcp://localhost:6379"]
## Optional TLS Config
# tls_ca = "/etc/telegraf/ca.pem"
# tls_cert = "/etc/telegraf/cert.pem"
# tls_key = "/etc/telegraf/key.pem"
## Use TLS but skip chain & host verification
# insecure_skip_verify = true
```
### Measurements & Fields:

View File

@@ -13,13 +13,11 @@ import (
"github.com/go-redis/redis"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal/tls"
"github.com/influxdata/telegraf/plugins/inputs"
)
type Redis struct {
Servers []string
tls.ClientConfig
clients []Client
initialized bool
@@ -58,13 +56,6 @@ var sampleConfig = `
## If no servers are specified, then localhost is used as the host.
## If no port is specified, 6379 is used
servers = ["tcp://localhost:6379"]
## Optional TLS Config
# tls_ca = "/etc/telegraf/ca.pem"
# tls_cert = "/etc/telegraf/cert.pem"
# tls_key = "/etc/telegraf/key.pem"
## Use TLS but skip chain & host verification
# insecure_skip_verify = true
`
func (r *Redis) SampleConfig() string {
@@ -118,18 +109,12 @@ func (r *Redis) init(acc telegraf.Accumulator) error {
address = u.Host
}
tlsConfig, err := r.ClientConfig.TLSConfig()
if err != nil {
return err
}
client := redis.NewClient(
&redis.Options{
Addr: address,
Password: password,
Network: u.Scheme,
PoolSize: 1,
TLSConfig: tlsConfig,
Addr: address,
Password: password,
Network: u.Scheme,
PoolSize: 1,
},
)

View File

@@ -218,20 +218,10 @@ func (t *Table) initBuild() error {
if err != nil {
return err
}
if t.Name == "" {
t.Name = oidText
}
knownOIDs := map[string]bool{}
for _, f := range t.Fields {
knownOIDs[f.Oid] = true
}
for _, f := range fields {
if !knownOIDs[f.Oid] {
t.Fields = append(t.Fields, f)
}
}
t.Fields = append(t.Fields, fields...)
return nil
}

View File

@@ -21,7 +21,7 @@ var mockedCommands = [][]string{
{"snmptranslate", "-Td", "-Ob", "-m", "all", "1.0.0.1.1"},
{"snmptranslate", "-Td", "-Ob", "-m", "all", ".1.0.0.0.1.1"},
{"snmptranslate", "-Td", "-Ob", "-m", "all", ".1.0.0.0.1.1.0"},
{"snmptranslate", "-Td", "-Ob", "-m", "all", ".1.0.0.0.1.5"},
{"snmptranslate", "-Td", "-Ob", "-m", "all", ".1.0.0.0.1.4"},
{"snmptranslate", "-Td", "-Ob", "-m", "all", ".1.2.3"},
{"snmptranslate", "-Td", "-Ob", ".iso.2.3"},
{"snmptranslate", "-Td", "-Ob", "-m", "all", ".999"},
@@ -30,7 +30,6 @@ var mockedCommands = [][]string{
{"snmptranslate", "-Td", "-Ob", "TEST::testTable"},
{"snmptranslate", "-Td", "-Ob", "TEST::connections"},
{"snmptranslate", "-Td", "-Ob", "TEST::latency"},
{"snmptranslate", "-Td", "-Ob", "TEST::description"},
{"snmptranslate", "-Td", "-Ob", "TEST::hostname"},
{"snmptranslate", "-Td", "-Ob", "IF-MIB::ifPhysAddress.1"},
{"snmptranslate", "-Td", "-Ob", "BRIDGE-MIB::dot1dTpFdbAddress.1"},

View File

@@ -67,7 +67,7 @@ var mockedCommandResults = map[string]mockedCommandResult{
"snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x001.0.0.1.1": mockedCommandResult{stdout: "TEST::hostname\nhostname OBJECT-TYPE\n -- FROM\tTEST\n SYNTAX\tOCTET STRING\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) 1 1 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x00.1.0.0.0.1.1": mockedCommandResult{stdout: "TEST::server\nserver OBJECT-TYPE\n -- FROM\tTEST\n SYNTAX\tOCTET STRING\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) testTable(0) testTableEntry(1) 1 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x00.1.0.0.0.1.1.0": mockedCommandResult{stdout: "TEST::server.0\nserver OBJECT-TYPE\n -- FROM\tTEST\n SYNTAX\tOCTET STRING\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) testTable(0) testTableEntry(1) server(1) 0 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x00.1.0.0.0.1.5": mockedCommandResult{stdout: "TEST::testTableEntry.5\ntestTableEntry OBJECT-TYPE\n -- FROM\tTEST\n MAX-ACCESS\tnot-accessible\n STATUS\tcurrent\n INDEX\t\t{ server }\n::= { iso(1) 0 testOID(0) testTable(0) testTableEntry(1) 5 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x00.1.0.0.0.1.4": mockedCommandResult{stdout: "TEST::testTableEntry.4\ntestTableEntry OBJECT-TYPE\n -- FROM\tTEST\n MAX-ACCESS\tnot-accessible\n STATUS\tcurrent\n INDEX\t\t{ server }\n::= { iso(1) 0 testOID(0) testTable(0) testTableEntry(1) 4 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x00.1.2.3": mockedCommandResult{stdout: "iso.2.3\niso OBJECT-TYPE\n -- FROM\t#-1\n::= { iso(1) 2 3 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00.iso.2.3": mockedCommandResult{stdout: "iso.2.3\niso OBJECT-TYPE\n -- FROM\t#-1\n::= { iso(1) 2 3 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00-m\x00all\x00.999": mockedCommandResult{stdout: ".999\n [TRUNCATED]\n", stderr: "", exitError: false},
@@ -76,11 +76,10 @@ var mockedCommandResults = map[string]mockedCommandResult{
"snmptranslate\x00-Td\x00-Ob\x00TEST::testTable": mockedCommandResult{stdout: "TEST::testTable\ntestTable OBJECT-TYPE\n -- FROM\tTEST\n MAX-ACCESS\tnot-accessible\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) 0 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00TEST::connections": mockedCommandResult{stdout: "TEST::connections\nconnections OBJECT-TYPE\n -- FROM\tTEST\n SYNTAX\tINTEGER\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) testTable(0) testTableEntry(1) 2 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00TEST::latency": mockedCommandResult{stdout: "TEST::latency\nlatency OBJECT-TYPE\n -- FROM\tTEST\n SYNTAX\tOCTET STRING\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) testTable(0) testTableEntry(1) 3 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00TEST::description": mockedCommandResult{stdout: "TEST::description\ndescription OBJECT-TYPE\n -- FROM\tTEST\n SYNTAX\tOCTET STRING\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) testTable(0) testTableEntry(1) 4 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00TEST::hostname": mockedCommandResult{stdout: "TEST::hostname\nhostname OBJECT-TYPE\n -- FROM\tTEST\n SYNTAX\tOCTET STRING\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n::= { iso(1) 0 testOID(0) 1 1 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00IF-MIB::ifPhysAddress.1": mockedCommandResult{stdout: "IF-MIB::ifPhysAddress.1\nifPhysAddress OBJECT-TYPE\n -- FROM\tIF-MIB\n -- TEXTUAL CONVENTION PhysAddress\n SYNTAX\tOCTET STRING\n DISPLAY-HINT\t\"1x:\"\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n DESCRIPTION\t\"The interface's address at its protocol sub-layer. For\n example, for an 802.x interface, this object normally\n contains a MAC address. The interface's media-specific MIB\n must define the bit and byte ordering and the format of the\n value of this object. For interfaces which do not have such\n an address (e.g., a serial line), this object should contain\n an octet string of zero length.\"\n::= { iso(1) org(3) dod(6) internet(1) mgmt(2) mib-2(1) interfaces(2) ifTable(2) ifEntry(1) ifPhysAddress(6) 1 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00BRIDGE-MIB::dot1dTpFdbAddress.1": mockedCommandResult{stdout: "BRIDGE-MIB::dot1dTpFdbAddress.1\ndot1dTpFdbAddress OBJECT-TYPE\n -- FROM\tBRIDGE-MIB\n -- TEXTUAL CONVENTION MacAddress\n SYNTAX\tOCTET STRING (6) \n DISPLAY-HINT\t\"1x:\"\n MAX-ACCESS\tread-only\n STATUS\tcurrent\n DESCRIPTION\t\"A unicast MAC address for which the bridge has\n forwarding and/or filtering information.\"\n::= { iso(1) org(3) dod(6) internet(1) mgmt(2) mib-2(1) dot1dBridge(17) dot1dTp(4) dot1dTpFdbTable(3) dot1dTpFdbEntry(1) dot1dTpFdbAddress(1) 1 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00-Ob\x00TCP-MIB::tcpConnectionLocalAddress.1": mockedCommandResult{stdout: "TCP-MIB::tcpConnectionLocalAddress.1\ntcpConnectionLocalAddress OBJECT-TYPE\n -- FROM\tTCP-MIB\n -- TEXTUAL CONVENTION InetAddress\n SYNTAX\tOCTET STRING (0..255) \n MAX-ACCESS\tnot-accessible\n STATUS\tcurrent\n DESCRIPTION\t\"The local IP address for this TCP connection. The type\n of this address is determined by the value of\n tcpConnectionLocalAddressType.\n\n As this object is used in the index for the\n tcpConnectionTable, implementors should be\n careful not to create entries that would result in OIDs\n with more than 128 subidentifiers; otherwise the information\n cannot be accessed by using SNMPv1, SNMPv2c, or SNMPv3.\"\n::= { iso(1) org(3) dod(6) internet(1) mgmt(2) mib-2(1) tcp(6) tcpConnectionTable(19) tcpConnectionEntry(1) tcpConnectionLocalAddress(2) 1 }\n", stderr: "", exitError: false},
"snmptranslate\x00-Td\x00TEST::testTable.1": mockedCommandResult{stdout: "TEST::testTableEntry\ntestTableEntry OBJECT-TYPE\n -- FROM\tTEST\n MAX-ACCESS\tnot-accessible\n STATUS\tcurrent\n INDEX\t\t{ server }\n::= { iso(1) 0 testOID(0) testTable(0) 1 }\n", stderr: "", exitError: false},
"snmptable\x00-Ch\x00-Cl\x00-c\x00public\x00127.0.0.1\x00TEST::testTable": mockedCommandResult{stdout: "server connections latency description \nTEST::testTable: No entries\n", stderr: "", exitError: false},
"snmptable\x00-Ch\x00-Cl\x00-c\x00public\x00127.0.0.1\x00TEST::testTable": mockedCommandResult{stdout: "server connections latency \nTEST::testTable: No entries\n", stderr: "", exitError: false},
}

View File

@@ -72,7 +72,7 @@ var tsc = &testSNMPConnection{
".1.0.0.0.1.3.1": "0.456",
".1.0.0.0.1.3.2": "0.000",
".1.0.0.0.1.3.3": "9.999",
".1.0.0.0.1.5.0": 123456,
".1.0.0.0.1.4.0": 123456,
".1.0.0.1.1": "baz",
".1.0.0.1.2": 234,
".1.0.0.1.3": []byte("byte slice"),
@@ -159,23 +159,19 @@ func TestFieldInit(t *testing.T) {
func TestTableInit(t *testing.T) {
tbl := Table{
Oid: ".1.0.0.0",
Fields: []Field{
{Oid: ".999", Name: "foo"},
{Oid: "TEST::description", Name: "description", IsTag: true},
},
Oid: ".1.0.0.0",
Fields: []Field{{Oid: ".999", Name: "foo"}},
}
err := tbl.init()
require.NoError(t, err)
assert.Equal(t, "testTable", tbl.Name)
assert.Len(t, tbl.Fields, 5)
assert.Len(t, tbl.Fields, 4)
assert.Contains(t, tbl.Fields, Field{Oid: ".999", Name: "foo", initialized: true})
assert.Contains(t, tbl.Fields, Field{Oid: ".1.0.0.0.1.1", Name: "server", IsTag: true, initialized: true})
assert.Contains(t, tbl.Fields, Field{Oid: ".1.0.0.0.1.2", Name: "connections", initialized: true})
assert.Contains(t, tbl.Fields, Field{Oid: ".1.0.0.0.1.3", Name: "latency", initialized: true})
assert.Contains(t, tbl.Fields, Field{Oid: ".1.0.0.0.1.4", Name: "description", IsTag: true, initialized: true})
}
func TestSnmpInit(t *testing.T) {
@@ -191,11 +187,10 @@ func TestSnmpInit(t *testing.T) {
err := s.init()
require.NoError(t, err)
assert.Len(t, s.Tables[0].Fields, 4)
assert.Len(t, s.Tables[0].Fields, 3)
assert.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.0.0.0.1.1", Name: "server", IsTag: true, initialized: true})
assert.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.0.0.0.1.2", Name: "connections", initialized: true})
assert.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.0.0.0.1.3", Name: "latency", initialized: true})
assert.Contains(t, s.Tables[0].Fields, Field{Oid: ".1.0.0.0.1.4", Name: "description", initialized: true})
assert.Equal(t, Field{
Oid: ".1.0.0.1.1",
@@ -584,7 +579,7 @@ func TestGather(t *testing.T) {
Fields: []Field{
{
Name: "myOtherField",
Oid: ".1.0.0.0.1.5",
Oid: ".1.0.0.0.1.4",
},
},
},

View File

@@ -22,7 +22,6 @@ TestTableEntry ::=
server OCTET STRING,
connections INTEGER,
latency OCTET STRING,
description OCTET STRING,
}
server OBJECT-TYPE
@@ -43,12 +42,6 @@ latency OBJECT-TYPE
STATUS current
::= { testTableEntry 3 }
description OBJECT-TYPE
SYNTAX OCTET STRING
MAX-ACCESS read-only
STATUS current
::= { testTableEntry 4 }
hostname OBJECT-TYPE
SYNTAX OCTET STRING
MAX-ACCESS read-only

View File

@@ -3,11 +3,9 @@ package socket_listener
import (
"bytes"
"crypto/tls"
"io/ioutil"
"log"
"net"
"os"
"path/filepath"
"testing"
"time"
@@ -52,17 +50,14 @@ func TestSocketListener_tcp_tls(t *testing.T) {
}
func TestSocketListener_unix_tls(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "telegraf")
require.NoError(t, err)
defer os.RemoveAll(tmpdir)
sock := filepath.Join(tmpdir, "socket_listener.TestSocketListener_unix_tls.sock")
defer testEmptyLog(t)()
sl := newSocketListener()
sl.ServiceAddress = "unix://" + sock
sl.ServiceAddress = "unix:///tmp/telegraf_test.sock"
sl.ServerConfig = *pki.TLSServerConfig()
acc := &testutil.Accumulator{}
err = sl.Start(acc)
err := sl.Start(acc)
require.NoError(t, err)
defer sl.Stop()
@@ -70,7 +65,7 @@ func TestSocketListener_unix_tls(t *testing.T) {
tlsCfg.InsecureSkipVerify = true
require.NoError(t, err)
secureClient, err := tls.Dial("unix", sock, tlsCfg)
secureClient, err := tls.Dial("unix", "/tmp/telegraf_test.sock", tlsCfg)
require.NoError(t, err)
testSocketListener(t, sl, secureClient)
@@ -113,48 +108,38 @@ func TestSocketListener_udp(t *testing.T) {
}
func TestSocketListener_unix(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "telegraf")
require.NoError(t, err)
defer os.RemoveAll(tmpdir)
sock := filepath.Join(tmpdir, "socket_listener.TestSocketListener_unix.sock")
defer testEmptyLog(t)()
os.Create(sock)
os.Create("/tmp/telegraf_test.sock")
sl := newSocketListener()
sl.ServiceAddress = "unix://" + sock
sl.ServiceAddress = "unix:///tmp/telegraf_test.sock"
sl.ReadBufferSize = 1024
acc := &testutil.Accumulator{}
err = sl.Start(acc)
err := sl.Start(acc)
require.NoError(t, err)
defer sl.Stop()
client, err := net.Dial("unix", sock)
client, err := net.Dial("unix", "/tmp/telegraf_test.sock")
require.NoError(t, err)
testSocketListener(t, sl, client)
}
func TestSocketListener_unixgram(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "telegraf")
require.NoError(t, err)
defer os.RemoveAll(tmpdir)
sock := filepath.Join(tmpdir, "socket_listener.TestSocketListener_unixgram.sock")
defer testEmptyLog(t)()
os.Create(sock)
os.Create("/tmp/telegraf_test.sock")
sl := newSocketListener()
sl.ServiceAddress = "unixgram://" + sock
sl.ServiceAddress = "unixgram:///tmp/telegraf_test.sock"
sl.ReadBufferSize = 1024
acc := &testutil.Accumulator{}
err = sl.Start(acc)
err := sl.Start(acc)
require.NoError(t, err)
defer sl.Stop()
client, err := net.Dial("unixgram", sock)
client, err := net.Dial("unixgram", "/tmp/telegraf_test.sock")
require.NoError(t, err)
testSocketListener(t, sl, client)

View File

@@ -1,98 +0,0 @@
# Syslog Input Plugin
The syslog plugin listens for syslog messages transmitted over
[UDP](https://tools.ietf.org/html/rfc5426) or
[TCP](https://tools.ietf.org/html/rfc5425).
Syslog messages should be formatted according to
[RFC 5424](https://tools.ietf.org/html/rfc5424).
### Configuration
```toml
[[inputs.syslog]]
## Specify an ip or hostname with port - eg., tcp://localhost:6514, tcp://10.0.0.1:6514
## Protocol, address and port to host the syslog receiver.
## If no host is specified, then localhost is used.
## If no port is specified, 6514 is used (RFC5425#section-4.1).
server = "tcp://:6514"
## TLS Config
# tls_allowed_cacerts = ["/etc/telegraf/ca.pem"]
# tls_cert = "/etc/telegraf/cert.pem"
# tls_key = "/etc/telegraf/key.pem"
## Period between keep alive probes.
## 0 disables keep alive probes.
## Defaults to the OS configuration.
## Only applies to stream sockets (e.g. TCP).
# keep_alive_period = "5m"
## Maximum number of concurrent connections (default = 0).
## 0 means unlimited.
## Only applies to stream sockets (e.g. TCP).
# max_connections = 1024
## Read timeout (default = 500ms).
## 0 means unlimited.
# read_timeout = 500ms
## Whether to parse in best effort mode or not (default = false).
## By default best effort parsing is off.
# best_effort = false
## Character to prepend to SD-PARAMs (default = "_").
## A syslog message can contain multiple parameters and multiple identifiers within structured data section.
## Eg., [id1 name1="val1" name2="val2"][id2 name1="val1" nameA="valA"]
## For each combination a field is created.
## Its name is created concatenating identifier, sdparam_separator, and parameter name.
# sdparam_separator = "_"
```
#### Best Effort
The [`best_effort`](https://github.com/influxdata/go-syslog#best-effort-mode)
option instructs the parser to extract partial but valid info from syslog
messages. If unset only full messages will be collected.
### Metrics
- syslog
- tags
- severity (string)
- facility (string)
- hostname (string)
- appname (string)
- fields
- version (integer)
- severity_code (integer)
- facility_code (integer)
- timestamp (integer)
- procid (string)
- msgid (string)
- sdid (bool)
- *Structured Data* (string)
### Rsyslog Integration
Rsyslog can be configured to forward logging messages to Telegraf by configuring
[remote logging](https://www.rsyslog.com/doc/v8-stable/configuration/actions.html#remote-machine).
Most system are setup with a configuration split between `/etc/rsyslog.conf`
and the files in the `/etc/rsyslog.d/` directory, it is recommended to add the
new configuration into the config directory to simplify updates to the main
config file.
Add the following lines to `/etc/rsyslog.d/50-telegraf.conf` making
adjustments to the target address as needed:
```
$ActionQueueType LinkedList # use asynchronous processing
$ActionQueueFileName srvrfwd # set file name, also enables disk mode
$ActionResumeRetryCount -1 # infinite retries on insert failure
$ActionQueueSaveOnShutdown on # save in-memory data if rsyslog shuts down
# forward over tcp with octet framing according to RFC 5425
*.* @@(o)127.0.0.1:6514;RSYSLOG_SyslogProtocol23Format
```
To complete TLS setup please refer to [rsyslog docs](https://www.rsyslog.com/doc/v8-stable/tutorials/tls.html).

View File

@@ -1,539 +0,0 @@
package syslog
import (
"crypto/tls"
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/require"
)
var (
pki = testutil.NewPKI("../../../testutil/pki")
)
type testCase5425 struct {
name string
data []byte
wantBestEffort []testutil.Metric
wantStrict []testutil.Metric
werr int // how many errors we expect in the strict mode?
}
func getTestCasesForRFC5425() []testCase5425 {
testCases := []testCase5425{
{
name: "1st/avg/ok",
data: []byte(`188 <29>1 2016-02-21T04:32:57+00:00 web1 someservice 2341 2 [origin][meta sequence="14125553" service="someservice"] "GET /v1/ok HTTP/1.1" 200 145 "-" "hacheck 0.9.0" 24306 127.0.0.1:40124 575`),
wantStrict: []testutil.Metric{
testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(1),
"timestamp": time.Unix(1456029177, 0).UnixNano(),
"procid": "2341",
"msgid": "2",
"message": `"GET /v1/ok HTTP/1.1" 200 145 "-" "hacheck 0.9.0" 24306 127.0.0.1:40124 575`,
"origin": true,
"meta_sequence": "14125553",
"meta_service": "someservice",
"severity_code": 5,
"facility_code": 3,
},
Tags: map[string]string{
"severity": "notice",
"facility": "daemon",
"hostname": "web1",
"appname": "someservice",
},
Time: defaultTime,
},
},
wantBestEffort: []testutil.Metric{
testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(1),
"timestamp": time.Unix(1456029177, 0).UnixNano(),
"procid": "2341",
"msgid": "2",
"message": `"GET /v1/ok HTTP/1.1" 200 145 "-" "hacheck 0.9.0" 24306 127.0.0.1:40124 575`,
"origin": true,
"meta_sequence": "14125553",
"meta_service": "someservice",
"severity_code": 5,
"facility_code": 3,
},
Tags: map[string]string{
"severity": "notice",
"facility": "daemon",
"hostname": "web1",
"appname": "someservice",
},
Time: defaultTime,
},
},
},
{
name: "1st/min/ok//2nd/min/ok",
data: []byte("16 <1>2 - - - - - -17 <4>11 - - - - - -"),
wantStrict: []testutil.Metric{
testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(2),
"severity_code": 1,
"facility_code": 0,
},
Tags: map[string]string{
"severity": "alert",
"facility": "kern",
},
Time: defaultTime,
},
testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(11),
"severity_code": 4,
"facility_code": 0,
},
Tags: map[string]string{
"severity": "warning",
"facility": "kern",
},
Time: defaultTime.Add(time.Nanosecond),
},
},
wantBestEffort: []testutil.Metric{
testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(2),
"severity_code": 1,
"facility_code": 0,
},
Tags: map[string]string{
"severity": "alert",
"facility": "kern",
},
Time: defaultTime,
},
testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(11),
"severity_code": 4,
"facility_code": 0,
},
Tags: map[string]string{
"severity": "warning",
"facility": "kern",
},
Time: defaultTime.Add(time.Nanosecond),
},
},
},
{
name: "1st/utf8/ok",
data: []byte("23 <1>1 - - - - - - hellø"),
wantStrict: []testutil.Metric{
testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(1),
"message": "hellø",
"severity_code": 1,
"facility_code": 0,
},
Tags: map[string]string{
"severity": "alert",
"facility": "kern",
},
Time: defaultTime,
},
},
wantBestEffort: []testutil.Metric{
testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(1),
"message": "hellø",
"severity_code": 1,
"facility_code": 0,
},
Tags: map[string]string{
"severity": "alert",
"facility": "kern",
},
Time: defaultTime,
},
},
},
{
name: "1st/nl/ok", // newline
data: []byte("28 <1>3 - - - - - - hello\nworld"),
wantStrict: []testutil.Metric{
testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(3),
"message": "hello\nworld",
"severity_code": 1,
"facility_code": 0,
},
Tags: map[string]string{
"severity": "alert",
"facility": "kern",
},
Time: defaultTime,
},
},
wantBestEffort: []testutil.Metric{
testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(3),
"message": "hello\nworld",
"severity_code": 1,
"facility_code": 0,
},
Tags: map[string]string{
"severity": "alert",
"facility": "kern",
},
Time: defaultTime,
},
},
},
{
name: "1st/uf/ko", // underflow (msglen less than provided octets)
data: []byte("16 <1>2"),
wantStrict: nil,
wantBestEffort: []testutil.Metric{
testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(2),
"severity_code": 1,
"facility_code": 0,
},
Tags: map[string]string{
"severity": "alert",
"facility": "kern",
},
Time: defaultTime,
},
},
werr: 1,
},
{
name: "1st/min/ok",
data: []byte("16 <1>1 - - - - - -"),
wantStrict: []testutil.Metric{
testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(1),
"severity_code": 1,
"facility_code": 0,
},
Tags: map[string]string{
"severity": "alert",
"facility": "kern",
},
Time: defaultTime,
},
},
wantBestEffort: []testutil.Metric{
testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(1),
"severity_code": 1,
"facility_code": 0,
},
Tags: map[string]string{
"severity": "alert",
"facility": "kern",
},
Time: defaultTime,
},
},
},
{
name: "1st/uf/mf", // The first "underflow" message breaks also the second one
data: []byte("16 <1>217 <11>1 - - - - - -"),
wantStrict: nil,
wantBestEffort: []testutil.Metric{
testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(217),
"severity_code": 1,
"facility_code": 0,
},
Tags: map[string]string{
"severity": "alert",
"facility": "kern",
},
Time: defaultTime,
},
},
werr: 1,
},
// {
// name: "1st/of/ko", // overflow (msglen greather then max allowed octets)
// data: []byte(fmt.Sprintf("8193 <%d>%d %s %s %s %s %s 12 %s", maxP, maxV, maxTS, maxH, maxA, maxPID, maxMID, message7681)),
// want: []testutil.Metric{},
// },
{
name: "1st/max/ok",
data: []byte(fmt.Sprintf("8192 <%d>%d %s %s %s %s %s - %s", maxP, maxV, maxTS, maxH, maxA, maxPID, maxMID, message7681)),
wantStrict: []testutil.Metric{
testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": maxV,
"timestamp": time.Unix(1514764799, 999999000).UnixNano(),
"message": message7681,
"procid": maxPID,
"msgid": maxMID,
"facility_code": 23,
"severity_code": 7,
},
Tags: map[string]string{
"severity": "debug",
"facility": "local7",
"hostname": maxH,
"appname": maxA,
},
Time: defaultTime,
},
},
wantBestEffort: []testutil.Metric{
testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": maxV,
"timestamp": time.Unix(1514764799, 999999000).UnixNano(),
"message": message7681,
"procid": maxPID,
"msgid": maxMID,
"facility_code": 23,
"severity_code": 7,
},
Tags: map[string]string{
"severity": "debug",
"facility": "local7",
"hostname": maxH,
"appname": maxA,
},
Time: defaultTime,
},
},
},
}
return testCases
}
func newTCPSyslogReceiver(address string, keepAlive *internal.Duration, maxConn int, bestEffort bool) *Syslog {
d := &internal.Duration{
Duration: defaultReadTimeout,
}
s := &Syslog{
Address: address,
now: func() time.Time {
return defaultTime
},
ReadTimeout: d,
BestEffort: bestEffort,
Separator: "_",
}
if keepAlive != nil {
s.KeepAlivePeriod = keepAlive
}
if maxConn > 0 {
s.MaxConnections = maxConn
}
return s
}
func testStrictRFC5425(t *testing.T, protocol string, address string, wantTLS bool, keepAlive *internal.Duration) {
for _, tc := range getTestCasesForRFC5425() {
t.Run(tc.name, func(t *testing.T) {
// Creation of a strict mode receiver
receiver := newTCPSyslogReceiver(protocol+"://"+address, keepAlive, 0, false)
require.NotNil(t, receiver)
if wantTLS {
receiver.ServerConfig = *pki.TLSServerConfig()
}
require.Equal(t, receiver.KeepAlivePeriod, keepAlive)
acc := &testutil.Accumulator{}
require.NoError(t, receiver.Start(acc))
defer receiver.Stop()
// Connect
var conn net.Conn
var err error
if wantTLS {
config, e := pki.TLSClientConfig().TLSConfig()
require.NoError(t, e)
config.ServerName = "localhost"
conn, err = tls.Dial(protocol, address, config)
} else {
conn, err = net.Dial(protocol, address)
defer conn.Close()
}
require.NotNil(t, conn)
require.NoError(t, err)
// Clear
acc.ClearMetrics()
acc.Errors = make([]error, 0)
// Write
conn.Write(tc.data)
// Wait that the the number of data points is accumulated
// Since the receiver is running concurrently
if tc.wantStrict != nil {
acc.Wait(len(tc.wantStrict))
}
// Wait the parsing error
acc.WaitError(tc.werr)
// Verify
if len(acc.Errors) != tc.werr {
t.Fatalf("Got unexpected errors. want error = %v, errors = %v\n", tc.werr, acc.Errors)
}
var got []testutil.Metric
for _, metric := range acc.Metrics {
got = append(got, *metric)
}
if !cmp.Equal(tc.wantStrict, got) {
t.Fatalf("Got (+) / Want (-)\n %s", cmp.Diff(tc.wantStrict, got))
}
})
}
}
func testBestEffortRFC5425(t *testing.T, protocol string, address string, wantTLS bool, keepAlive *internal.Duration) {
for _, tc := range getTestCasesForRFC5425() {
t.Run(tc.name, func(t *testing.T) {
// Creation of a best effort mode receiver
receiver := newTCPSyslogReceiver(protocol+"://"+address, keepAlive, 0, true)
require.NotNil(t, receiver)
if wantTLS {
receiver.ServerConfig = *pki.TLSServerConfig()
}
require.Equal(t, receiver.KeepAlivePeriod, keepAlive)
acc := &testutil.Accumulator{}
require.NoError(t, receiver.Start(acc))
defer receiver.Stop()
// Connect
var conn net.Conn
var err error
if wantTLS {
config, e := pki.TLSClientConfig().TLSConfig()
require.NoError(t, e)
config.ServerName = "localhost"
conn, err = tls.Dial(protocol, address, config)
} else {
conn, err = net.Dial(protocol, address)
defer conn.Close()
}
require.NotNil(t, conn)
require.NoError(t, err)
// Clear
acc.ClearMetrics()
acc.Errors = make([]error, 0)
// Write
conn.Write(tc.data)
// Wait that the the number of data points is accumulated
// Since the receiver is running concurrently
if tc.wantBestEffort != nil {
acc.Wait(len(tc.wantBestEffort))
}
// Verify
var got []testutil.Metric
for _, metric := range acc.Metrics {
got = append(got, *metric)
}
if !cmp.Equal(tc.wantBestEffort, got) {
t.Fatalf("Got (+) / Want (-)\n %s", cmp.Diff(tc.wantBestEffort, got))
}
})
}
}
func TestStrict_tcp(t *testing.T) {
testStrictRFC5425(t, "tcp", address, false, nil)
}
func TestBestEffort_tcp(t *testing.T) {
testBestEffortRFC5425(t, "tcp", address, false, nil)
}
func TestStrict_tcp_tls(t *testing.T) {
testStrictRFC5425(t, "tcp", address, true, nil)
}
func TestBestEffort_tcp_tls(t *testing.T) {
testBestEffortRFC5425(t, "tcp", address, true, nil)
}
func TestStrictWithKeepAlive_tcp_tls(t *testing.T) {
testStrictRFC5425(t, "tcp", address, true, &internal.Duration{Duration: time.Minute})
}
func TestStrictWithZeroKeepAlive_tcp_tls(t *testing.T) {
testStrictRFC5425(t, "tcp", address, true, &internal.Duration{Duration: 0})
}
func TestStrict_unix(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "telegraf")
require.NoError(t, err)
defer os.RemoveAll(tmpdir)
sock := filepath.Join(tmpdir, "syslog.TestStrict_unix.sock")
testStrictRFC5425(t, "unix", sock, false, nil)
}
func TestBestEffort_unix(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "telegraf")
require.NoError(t, err)
defer os.RemoveAll(tmpdir)
sock := filepath.Join(tmpdir, "syslog.TestBestEffort_unix.sock")
testBestEffortRFC5425(t, "unix", sock, false, nil)
}
func TestStrict_unix_tls(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "telegraf")
require.NoError(t, err)
defer os.RemoveAll(tmpdir)
sock := filepath.Join(tmpdir, "syslog.TestStrict_unix_tls.sock")
testStrictRFC5425(t, "unix", sock, true, nil)
}
func TestBestEffort_unix_tls(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "telegraf")
require.NoError(t, err)
defer os.RemoveAll(tmpdir)
sock := filepath.Join(tmpdir, "syslog.TestBestEffort_unix_tls.sock")
testBestEffortRFC5425(t, "unix", sock, true, nil)
}

View File

@@ -1,408 +0,0 @@
package syslog
import (
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"sync/atomic"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/require"
)
type testCase5426 struct {
name string
data []byte
wantBestEffort *testutil.Metric
wantStrict *testutil.Metric
werr bool
}
func getTestCasesForRFC5426() []testCase5426 {
testCases := []testCase5426{
{
name: "empty",
data: []byte(""),
werr: true,
},
{
name: "complete",
data: []byte("<1>1 - - - - - - A"),
wantBestEffort: &testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(1),
"message": "A",
"facility_code": 0,
"severity_code": 1,
},
Tags: map[string]string{
"severity": "alert",
"facility": "kern",
},
Time: defaultTime,
},
wantStrict: &testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(1),
"message": "A",
"facility_code": 0,
"severity_code": 1,
},
Tags: map[string]string{
"severity": "alert",
"facility": "kern",
},
Time: defaultTime,
},
},
{
name: "one/per/packet",
data: []byte("<1>3 - - - - - - A<1>4 - - - - - - B"),
wantBestEffort: &testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(3),
"message": "A<1>4 - - - - - - B",
"severity_code": 1,
"facility_code": 0,
},
Tags: map[string]string{
"severity": "alert",
"facility": "kern",
},
Time: defaultTime,
},
wantStrict: &testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(3),
"message": "A<1>4 - - - - - - B",
"severity_code": 1,
"facility_code": 0,
},
Tags: map[string]string{
"severity": "alert",
"facility": "kern",
},
Time: defaultTime,
},
},
{
name: "average",
data: []byte(`<29>1 2016-02-21T04:32:57+00:00 web1 someservice 2341 2 [origin][meta sequence="14125553" service="someservice"] "GET /v1/ok HTTP/1.1" 200 145 "-" "hacheck 0.9.0" 24306 127.0.0.1:40124 575`),
wantBestEffort: &testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(1),
"timestamp": time.Unix(1456029177, 0).UnixNano(),
"procid": "2341",
"msgid": "2",
"message": `"GET /v1/ok HTTP/1.1" 200 145 "-" "hacheck 0.9.0" 24306 127.0.0.1:40124 575`,
"origin": true,
"meta_sequence": "14125553",
"meta_service": "someservice",
"severity_code": 5,
"facility_code": 3,
},
Tags: map[string]string{
"severity": "notice",
"facility": "daemon",
"hostname": "web1",
"appname": "someservice",
},
Time: defaultTime,
},
wantStrict: &testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(1),
"timestamp": time.Unix(1456029177, 0).UnixNano(),
"procid": "2341",
"msgid": "2",
"message": `"GET /v1/ok HTTP/1.1" 200 145 "-" "hacheck 0.9.0" 24306 127.0.0.1:40124 575`,
"origin": true,
"meta_sequence": "14125553",
"meta_service": "someservice",
"severity_code": 5,
"facility_code": 3,
},
Tags: map[string]string{
"severity": "notice",
"facility": "daemon",
"hostname": "web1",
"appname": "someservice",
},
Time: defaultTime,
},
},
{
name: "max",
data: []byte(fmt.Sprintf("<%d>%d %s %s %s %s %s - %s", maxP, maxV, maxTS, maxH, maxA, maxPID, maxMID, message7681)),
wantBestEffort: &testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": maxV,
"timestamp": time.Unix(1514764799, 999999000).UnixNano(),
"message": message7681,
"procid": maxPID,
"msgid": maxMID,
"severity_code": 7,
"facility_code": 23,
},
Tags: map[string]string{
"severity": "debug",
"facility": "local7",
"hostname": maxH,
"appname": maxA,
},
Time: defaultTime,
},
wantStrict: &testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": maxV,
"timestamp": time.Unix(1514764799, 999999000).UnixNano(),
"message": message7681,
"procid": maxPID,
"msgid": maxMID,
"severity_code": 7,
"facility_code": 23,
},
Tags: map[string]string{
"severity": "debug",
"facility": "local7",
"hostname": maxH,
"appname": maxA,
},
Time: defaultTime,
},
},
{
name: "minimal/incomplete",
data: []byte("<1>2"),
wantBestEffort: &testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(2),
"facility_code": 0,
"severity_code": 1,
},
Tags: map[string]string{
"severity": "alert",
"facility": "kern",
},
Time: defaultTime,
},
werr: true,
},
}
return testCases
}
func newUDPSyslogReceiver(address string, bestEffort bool) *Syslog {
return &Syslog{
Address: address,
now: func() time.Time {
return defaultTime
},
BestEffort: bestEffort,
Separator: "_",
}
}
func testRFC5426(t *testing.T, protocol string, address string, bestEffort bool) {
for _, tc := range getTestCasesForRFC5426() {
t.Run(tc.name, func(t *testing.T) {
// Create receiver
receiver := newUDPSyslogReceiver(protocol+"://"+address, bestEffort)
acc := &testutil.Accumulator{}
require.NoError(t, receiver.Start(acc))
defer receiver.Stop()
// Clear
acc.ClearMetrics()
acc.Errors = make([]error, 0)
// Connect
conn, err := net.Dial(protocol, address)
require.NotNil(t, conn)
defer conn.Close()
require.Nil(t, err)
// Write
_, e := conn.Write(tc.data)
require.Nil(t, e)
// Waiting ...
if tc.wantStrict == nil && tc.werr || bestEffort && tc.werr {
acc.WaitError(1)
}
if tc.wantBestEffort != nil && bestEffort || tc.wantStrict != nil && !bestEffort {
acc.Wait(1) // RFC5426 mandates a syslog message per UDP packet
}
// Compare
var got *testutil.Metric
var want *testutil.Metric
if len(acc.Metrics) > 0 {
got = acc.Metrics[0]
}
if bestEffort {
want = tc.wantBestEffort
} else {
want = tc.wantStrict
}
if !cmp.Equal(want, got) {
t.Fatalf("Got (+) / Want (-)\n %s", cmp.Diff(want, got))
}
})
}
}
func TestBestEffort_udp(t *testing.T) {
testRFC5426(t, "udp", address, true)
}
func TestStrict_udp(t *testing.T) {
testRFC5426(t, "udp", address, false)
}
func TestBestEffort_unixgram(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "telegraf")
require.NoError(t, err)
defer os.RemoveAll(tmpdir)
sock := filepath.Join(tmpdir, "syslog.TestBestEffort_unixgram.sock")
os.Create(sock)
testRFC5426(t, "unixgram", sock, true)
}
func TestStrict_unixgram(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "telegraf")
require.NoError(t, err)
defer os.RemoveAll(tmpdir)
sock := filepath.Join(tmpdir, "syslog.TestStrict_unixgram.sock")
os.Create(sock)
testRFC5426(t, "unixgram", sock, false)
}
func TestTimeIncrement_udp(t *testing.T) {
var i int64
atomic.StoreInt64(&i, 0)
getNow := func() time.Time {
if atomic.LoadInt64(&i)%2 == 0 {
return time.Unix(1, 0)
}
return time.Unix(1, 1)
}
// Create receiver
receiver := &Syslog{
Address: "udp://" + address,
now: getNow,
BestEffort: false,
Separator: "_",
}
acc := &testutil.Accumulator{}
require.NoError(t, receiver.Start(acc))
defer receiver.Stop()
// Connect
conn, err := net.Dial("udp", address)
require.NotNil(t, conn)
defer conn.Close()
require.Nil(t, err)
// Write
_, e := conn.Write([]byte("<1>1 - - - - - -"))
require.Nil(t, e)
// Wait
acc.Wait(1)
want := &testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(1),
"facility_code": 0,
"severity_code": 1,
},
Tags: map[string]string{
"severity": "alert",
"facility": "kern",
},
Time: getNow(),
}
if !cmp.Equal(want, acc.Metrics[0]) {
t.Fatalf("Got (+) / Want (-)\n %s", cmp.Diff(want, acc.Metrics[0]))
}
// New one with different time
atomic.StoreInt64(&i, atomic.LoadInt64(&i)+1)
// Clear
acc.ClearMetrics()
// Write
_, e = conn.Write([]byte("<1>1 - - - - - -"))
require.Nil(t, e)
// Wait
acc.Wait(1)
want = &testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(1),
"facility_code": 0,
"severity_code": 1,
},
Tags: map[string]string{
"severity": "alert",
"facility": "kern",
},
Time: getNow(),
}
if !cmp.Equal(want, acc.Metrics[0]) {
t.Fatalf("Got (+) / Want (-)\n %s", cmp.Diff(want, acc.Metrics[0]))
}
// New one with same time as previous one
// Clear
acc.ClearMetrics()
// Write
_, e = conn.Write([]byte("<1>1 - - - - - -"))
require.Nil(t, e)
// Wait
acc.Wait(1)
want = &testutil.Metric{
Measurement: "syslog",
Fields: map[string]interface{}{
"version": uint16(1),
"facility_code": 0,
"severity_code": 1,
},
Tags: map[string]string{
"severity": "alert",
"facility": "kern",
},
Time: getNow().Add(time.Nanosecond),
}
if !cmp.Equal(want, acc.Metrics[0]) {
t.Fatalf("Got (+) / Want (-)\n %s", cmp.Diff(want, acc.Metrics[0]))
}
}

View File

@@ -1,419 +0,0 @@
package syslog
import (
"crypto/tls"
"fmt"
"io"
"net"
"net/url"
"os"
"strings"
"sync"
"time"
"github.com/influxdata/go-syslog/rfc5424"
"github.com/influxdata/go-syslog/rfc5425"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal"
tlsConfig "github.com/influxdata/telegraf/internal/tls"
"github.com/influxdata/telegraf/plugins/inputs"
)
const defaultReadTimeout = time.Millisecond * 500
const ipMaxPacketSize = 64 * 1024
// Syslog is a syslog plugin
type Syslog struct {
tlsConfig.ServerConfig
Address string `toml:"server"`
KeepAlivePeriod *internal.Duration
ReadTimeout *internal.Duration
MaxConnections int
BestEffort bool
Separator string `toml:"sdparam_separator"`
now func() time.Time
lastTime time.Time
mu sync.Mutex
wg sync.WaitGroup
io.Closer
isStream bool
tcpListener net.Listener
tlsConfig *tls.Config
connections map[string]net.Conn
connectionsMu sync.Mutex
udpListener net.PacketConn
}
var sampleConfig = `
## Specify an ip or hostname with port - eg., tcp://localhost:6514, tcp://10.0.0.1:6514
## Protocol, address and port to host the syslog receiver.
## If no host is specified, then localhost is used.
## If no port is specified, 6514 is used (RFC5425#section-4.1).
server = "tcp://:6514"
## TLS Config
# tls_allowed_cacerts = ["/etc/telegraf/ca.pem"]
# tls_cert = "/etc/telegraf/cert.pem"
# tls_key = "/etc/telegraf/key.pem"
## Period between keep alive probes.
## 0 disables keep alive probes.
## Defaults to the OS configuration.
## Only applies to stream sockets (e.g. TCP).
# keep_alive_period = "5m"
## Maximum number of concurrent connections (default = 0).
## 0 means unlimited.
## Only applies to stream sockets (e.g. TCP).
# max_connections = 1024
## Read timeout (default = 500ms).
## 0 means unlimited.
# read_timeout = 500ms
## Whether to parse in best effort mode or not (default = false).
## By default best effort parsing is off.
# best_effort = false
## Character to prepend to SD-PARAMs (default = "_").
## A syslog message can contain multiple parameters and multiple identifiers within structured data section.
## Eg., [id1 name1="val1" name2="val2"][id2 name1="val1" nameA="valA"]
## For each combination a field is created.
## Its name is created concatenating identifier, sdparam_separator, and parameter name.
# sdparam_separator = "_"
`
// SampleConfig returns sample configuration message
func (s *Syslog) SampleConfig() string {
return sampleConfig
}
// Description returns the plugin description
func (s *Syslog) Description() string {
return "Accepts syslog messages per RFC5425"
}
// Gather ...
func (s *Syslog) Gather(_ telegraf.Accumulator) error {
return nil
}
// Start starts the service.
func (s *Syslog) Start(acc telegraf.Accumulator) error {
s.mu.Lock()
defer s.mu.Unlock()
scheme, host, err := getAddressParts(s.Address)
if err != nil {
return err
}
s.Address = host
switch scheme {
case "tcp", "tcp4", "tcp6", "unix", "unixpacket":
s.isStream = true
case "udp", "udp4", "udp6", "ip", "ip4", "ip6", "unixgram":
s.isStream = false
default:
return fmt.Errorf("unknown protocol '%s' in '%s'", scheme, s.Address)
}
if scheme == "unix" || scheme == "unixpacket" || scheme == "unixgram" {
os.Remove(s.Address)
}
if s.isStream {
l, err := net.Listen(scheme, s.Address)
if err != nil {
return err
}
s.Closer = l
s.tcpListener = l
s.tlsConfig, err = s.TLSConfig()
if err != nil {
return err
}
s.wg.Add(1)
go s.listenStream(acc)
} else {
l, err := net.ListenPacket(scheme, s.Address)
if err != nil {
return err
}
s.Closer = l
s.udpListener = l
s.wg.Add(1)
go s.listenPacket(acc)
}
if scheme == "unix" || scheme == "unixpacket" || scheme == "unixgram" {
s.Closer = unixCloser{path: s.Address, closer: s.Closer}
}
return nil
}
// Stop cleans up all resources
func (s *Syslog) Stop() {
s.mu.Lock()
defer s.mu.Unlock()
if s.Closer != nil {
s.Close()
}
s.wg.Wait()
}
// getAddressParts returns the address scheme and host
// it also sets defaults for them when missing
// when the input address does not specify the protocol it returns an error
func getAddressParts(a string) (string, string, error) {
parts := strings.SplitN(a, "://", 2)
if len(parts) != 2 {
return "", "", fmt.Errorf("missing protocol within address '%s'", a)
}
u, _ := url.Parse(a)
switch u.Scheme {
case "unix", "unixpacket", "unixgram":
return parts[0], parts[1], nil
}
var host string
if u.Hostname() != "" {
host = u.Hostname()
}
host += ":"
if u.Port() == "" {
host += "6514"
} else {
host += u.Port()
}
return u.Scheme, host, nil
}
func (s *Syslog) listenPacket(acc telegraf.Accumulator) {
defer s.wg.Done()
b := make([]byte, ipMaxPacketSize)
p := rfc5424.NewParser()
for {
n, _, err := s.udpListener.ReadFrom(b)
if err != nil {
if !strings.HasSuffix(err.Error(), ": use of closed network connection") {
acc.AddError(err)
}
break
}
if s.ReadTimeout != nil && s.ReadTimeout.Duration > 0 {
s.udpListener.SetReadDeadline(time.Now().Add(s.ReadTimeout.Duration))
}
message, err := p.Parse(b[:n], &s.BestEffort)
if message != nil {
acc.AddFields("syslog", fields(*message, s), tags(*message), s.time())
}
if err != nil {
acc.AddError(err)
}
}
}
func (s *Syslog) listenStream(acc telegraf.Accumulator) {
defer s.wg.Done()
s.connections = map[string]net.Conn{}
for {
conn, err := s.tcpListener.Accept()
if err != nil {
if !strings.HasSuffix(err.Error(), ": use of closed network connection") {
acc.AddError(err)
}
break
}
var tcpConn, _ = conn.(*net.TCPConn)
if s.tlsConfig != nil {
conn = tls.Server(conn, s.tlsConfig)
}
s.connectionsMu.Lock()
if s.MaxConnections > 0 && len(s.connections) >= s.MaxConnections {
s.connectionsMu.Unlock()
conn.Close()
continue
}
s.connections[conn.RemoteAddr().String()] = conn
s.connectionsMu.Unlock()
if err := s.setKeepAlive(tcpConn); err != nil {
acc.AddError(fmt.Errorf("unable to configure keep alive (%s): %s", s.Address, err))
}
go s.handle(conn, acc)
}
s.connectionsMu.Lock()
for _, c := range s.connections {
c.Close()
}
s.connectionsMu.Unlock()
}
func (s *Syslog) removeConnection(c net.Conn) {
s.connectionsMu.Lock()
delete(s.connections, c.RemoteAddr().String())
s.connectionsMu.Unlock()
}
func (s *Syslog) handle(conn net.Conn, acc telegraf.Accumulator) {
defer func() {
s.removeConnection(conn)
conn.Close()
}()
if s.ReadTimeout != nil && s.ReadTimeout.Duration > 0 {
conn.SetReadDeadline(time.Now().Add(s.ReadTimeout.Duration))
}
var p *rfc5425.Parser
if s.BestEffort {
p = rfc5425.NewParser(conn, rfc5425.WithBestEffort())
} else {
p = rfc5425.NewParser(conn)
}
p.ParseExecuting(func(r *rfc5425.Result) {
s.store(*r, acc)
})
}
func (s *Syslog) setKeepAlive(c *net.TCPConn) error {
if s.KeepAlivePeriod == nil {
return nil
}
if s.KeepAlivePeriod.Duration == 0 {
return c.SetKeepAlive(false)
}
if err := c.SetKeepAlive(true); err != nil {
return err
}
return c.SetKeepAlivePeriod(s.KeepAlivePeriod.Duration)
}
func (s *Syslog) store(res rfc5425.Result, acc telegraf.Accumulator) {
if res.Error != nil {
acc.AddError(res.Error)
}
if res.MessageError != nil {
acc.AddError(res.MessageError)
}
if res.Message != nil {
msg := *res.Message
acc.AddFields("syslog", fields(msg, s), tags(msg), s.time())
}
}
func tags(msg rfc5424.SyslogMessage) map[string]string {
ts := map[string]string{}
// Not checking assuming a minimally valid message
ts["severity"] = *msg.SeverityShortLevel()
ts["facility"] = *msg.FacilityLevel()
if msg.Hostname() != nil {
ts["hostname"] = *msg.Hostname()
}
if msg.Appname() != nil {
ts["appname"] = *msg.Appname()
}
return ts
}
func fields(msg rfc5424.SyslogMessage, s *Syslog) map[string]interface{} {
// Not checking assuming a minimally valid message
flds := map[string]interface{}{
"version": msg.Version(),
}
flds["severity_code"] = int(*msg.Severity())
flds["facility_code"] = int(*msg.Facility())
if msg.Timestamp() != nil {
flds["timestamp"] = (*msg.Timestamp()).UnixNano()
}
if msg.ProcID() != nil {
flds["procid"] = *msg.ProcID()
}
if msg.MsgID() != nil {
flds["msgid"] = *msg.MsgID()
}
if msg.Message() != nil {
flds["message"] = *msg.Message()
}
if msg.StructuredData() != nil {
for sdid, sdparams := range *msg.StructuredData() {
if len(sdparams) == 0 {
// When SD-ID does not have params we indicate its presence with a bool
flds[sdid] = true
continue
}
for name, value := range sdparams {
// Using whitespace as separator since it is not allowed by the grammar within SDID
flds[sdid+s.Separator+name] = value
}
}
}
return flds
}
type unixCloser struct {
path string
closer io.Closer
}
func (uc unixCloser) Close() error {
err := uc.closer.Close()
os.Remove(uc.path) // ignore error
return err
}
func (s *Syslog) time() time.Time {
t := s.now()
if t == s.lastTime {
t = t.Add(time.Nanosecond)
}
s.lastTime = t
return t
}
func getNanoNow() time.Time {
return time.Unix(0, time.Now().UnixNano())
}
func init() {
receiver := &Syslog{
Address: ":6514",
now: getNanoNow,
ReadTimeout: &internal.Duration{
Duration: defaultReadTimeout,
},
Separator: "_",
}
inputs.Add("syslog", func() telegraf.Input { return receiver })
}

View File

@@ -1,68 +0,0 @@
package syslog
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/require"
)
const (
address = ":6514"
)
var defaultTime = time.Unix(0, 0)
var maxP = uint8(191)
var maxV = uint16(999)
var maxTS = "2017-12-31T23:59:59.999999+00:00"
var maxH = "abcdefghilmnopqrstuvzabcdefghilmnopqrstuvzabcdefghilmnopqrstuvzabcdefghilmnopqrstuvzabcdefghilmnopqrstuvzabcdefghilmnopqrstuvzabcdefghilmnopqrstuvzabcdefghilmnopqrstuvzabcdefghilmnopqrstuvzabcdefghilmnopqrstuvzabcdefghilmnopqrstuvzabcdefghilmnopqrstuvzabc"
var maxA = "abcdefghilmnopqrstuvzabcdefghilmnopqrstuvzabcdef"
var maxPID = "abcdefghilmnopqrstuvzabcdefghilmnopqrstuvzabcdefghilmnopqrstuvzabcdefghilmnopqrstuvzabcdefghilmnopqrstuvzabcdefghilmnopqrstuvzab"
var maxMID = "abcdefghilmnopqrstuvzabcdefghilm"
var message7681 = strings.Repeat("l", 7681)
func TestAddress(t *testing.T) {
var err error
var rec *Syslog
rec = &Syslog{
Address: "localhost:6514",
}
err = rec.Start(&testutil.Accumulator{})
require.EqualError(t, err, "missing protocol within address 'localhost:6514'")
require.Error(t, err)
rec = &Syslog{
Address: "unsupported://example.com:6514",
}
err = rec.Start(&testutil.Accumulator{})
require.EqualError(t, err, "unknown protocol 'unsupported' in 'example.com:6514'")
require.Error(t, err)
tmpdir, err := ioutil.TempDir("", "telegraf")
defer os.RemoveAll(tmpdir)
require.NoError(t, err)
sock := filepath.Join(tmpdir, "syslog.TestAddress.sock")
rec = &Syslog{
Address: "unixgram://" + sock,
}
err = rec.Start(&testutil.Accumulator{})
require.NoError(t, err)
require.Equal(t, sock, rec.Address)
rec.Stop()
// Default port is 6514
rec = &Syslog{
Address: "tcp://localhost",
}
err = rec.Start(&testutil.Accumulator{})
require.NoError(t, err)
require.Equal(t, "localhost:6514", rec.Address)
rec.Stop()
}

View File

@@ -20,7 +20,7 @@ This plugin gathers stats from [Varnish HTTP Cache](https://varnish-cache.org/)
## Optional name for the varnish instance (or working directory) to query
## Usually appened after -n in varnish cli
# instance_name = instanceName
#name = instanceName
```
### Measurements & Fields:

View File

@@ -48,7 +48,7 @@ var sampleConfig = `
## Optional name for the varnish instance (or working directory) to query
## Usually appened after -n in varnish cli
# instance_name = instanceName
#name = instanceName
`
func (s *Varnish) Description() string {

View File

@@ -1,24 +1,19 @@
# win_perf_counters readme
Input plugin to read Performance Counters on Windows operating systems.
Configuration is parsed and then tested for validity such as
whether the Object, Instance and Counter exist on Telegraf startup.
Counter paths are refreshed periodically, see [CountersRefreshInterval](#countersrefreshinterval)
configuration parameter for more info.
Wildcards can be used in instance and counter names. Partial wildcards are supported only
in instance names on Windows Vista and newer.
In case of query for all instances `["*"]`, the plugin does not return the instance `_Total`
by default. See [IncludeTotal](#includetotal) for more info.
The way this plugin works is that on load of Telegraf,
the plugin will be handed configuration from Telegraf.
This configuration is parsed and then tested for validity such as
if the Object, Instance and Counter existing.
If it does not match at startup, it will not be fetched.
Exceptions to this are in cases where you query for all instances "*".
By default the plugin does not return _Total
when it is querying for all (*) as this is redundant.
## Basics
The examples contained in this file have been found on the internet
as counters used when performance monitoring
Active Directory and IIS in particular.
Active Directory and IIS in perticular.
There are a lot other good objects to monitor, if you know what to look for.
This file is likely to be updated in the future with more examples for
useful configurations for separate scenarios.
@@ -34,23 +29,9 @@ Bool, if set to `true` will print out all matching performance objects.
Example:
`PrintValid=true`
#### CountersRefreshInterval
Configured counters are matched against available counters at the interval
specified by the `CountersRefreshInterval` parameter. Default value is `1m` (1 minute).
If wildcards are used in instance or counter names, they are expanded at this point.
Setting `CountersRefreshInterval` too low (order of seconds) can cause Telegraf to create
a high CPU load.
Set to `0s` to disable periodic refreshing.
#### PreVistaSupport
_Deprecated. Necessary features on Windows Vista and newer are checked dynamically_
Bool, if set to `true` will use the localized PerfCounter interface that has been present since before Vista for backwards compatability.
Bool, if set to `true` will use the localized PerfCounter interface that is present before Vista for backwards compatability.
It is recommended NOT to use this on OSes starting with Vista and newer because it requires more configuration to use this than the newer interface present since Vista.
@@ -64,10 +45,10 @@ See Entry below.
### Entry
A new configuration entry consists of the TOML header to start with,
`[[inputs.win_perf_counters.object]]`.
This must follow before other plugin configurations,
This must follow before other plugins configuration,
beneath the main win_perf_counters entry, `[[inputs.win_perf_counters]]`.
Following this are 3 required key/value pairs and the three optional parameters and their usage.
Following this is 3 required key/value pairs and the three optional parameters and their usage.
#### ObjectName
**Required**
@@ -79,39 +60,37 @@ Example: `ObjectName = "LogicalDisk"`
#### Instances
**Required**
Instances key (this is an array) is the instances of a counter you would like returned,
Instances (this is an array) is the instances of a counter you would like returned,
it can be one or more values.
Example, `Instances = ["C:","D:","E:"]` will return only for the instances
C:, D: and E: where relevant. To get all instances of a Counter, use `["*"]` only.
By default any results containing `_Total` are stripped,
C:, D: and E: where relevant. To get all instances of a Counter, use ["*"] only.
By default any results containing _Total are stripped,
unless this is specified as the wanted instance.
Alternatively see the option `IncludeTotal` below.
Alternatively see the option IncludeTotal below.
It is also possible to set partial wildcards, eg. `["chrome*"]`
Some Objects do not have instances to select from at all.
Here only one option is valid if you want data back,
Some Objects does not have instances to select from at all,
here only one option is valid if you want data back,
and that is to specify `Instances = ["------"]`.
#### Counters
**Required**
Counters key (this is an array) is the counters of the ObjectName
Counters (this is an array) is the counters of the ObjectName
you would like returned, it can also be one or more values.
Example: `Counters = ["% Idle Time", "% Disk Read Time", "% Disk Write Time"]`
This must be specified for every counter you want the results of,
or use `["*"]` for all the counters for object.
it is not possible to ask for all counters in the ObjectName.
#### Measurement
*Optional*
This key is optional, if it is not set it will be `win_perf_counters`.
This key is optional, if it is not set it will be win_perf_counters.
In InfluxDB this is the key by which the returned data is stored underneath,
so for ordering your data in a good manner,
this is a good key to set with a value when you want your IIS and Disk results stored
separately from Processor results.
this is a good key to set with where you want your IIS and Disk results stored,
separate from Processor results.
Example: `Measurement = "win_disk"
@@ -120,9 +99,9 @@ Example: `Measurement = "win_disk"
This key is optional, it is a simple bool.
If it is not set to true or included it is treated as false.
This key only has an effect if the Instances key is set to `["*"]`
and you would also like all instances containing `_Total` returned,
like `_Total`, `0,_Total` and so on where applicable
This key only has an effect if Instances is set to "*"
and you would also like all instances containg _Total returned,
like "_Total", "0,_Total" and so on where applicable
(Processor Information is one example).
#### WarnOnMissing
@@ -132,13 +111,13 @@ This key is optional, it is a simple bool.
If it is not set to true or included it is treated as false.
This only has an effect on the first execution of the plugin,
it will print out any ObjectName/Instance/Counter combinations
asked for that do not match. Useful when debugging new configurations.
asked for that does not match. Useful when debugging new configurations.
#### FailOnMissing
*Internal*
This key should not be used. It is for testing purposes only.
It is a simple bool. If it is not set to true or included this is treated as false.
This key should not be used, it is for testing purposes only.
It is a simple bool, if it is not set to true or included this is treaded as false.
If this is set to true, the plugin will abort and end prematurely
if any of the combinations of ObjectName/Instances/Counters are invalid.
@@ -358,14 +337,10 @@ if any of the combinations of ObjectName/Instances/Counters are invalid.
## Troubleshooting
If you are getting an error about an invalid counter, use the `typeperf` command to check the counter path
on the command line.
E.g. `typeperf "Process(chrome*)\% Processor Time"`
If no metrics are emitted even with the default config, you may need to repair
your performance counters.
1. Launch the Command Prompt as Administrator (right click Runs As Administrator).
1. Launch Command Prompt as Administrator (right click Runs As Administrator).
1. Drop into the C:\WINDOWS\System32 directory by typing `C:` then `cd \Windows\System32`
1. Rebuild your counter values, which may take a few moments so please be
patient, by running:

View File

@@ -161,6 +161,43 @@ type (
PDH_HCOUNTER HANDLE // counter handle
)
// Union specialization for double values
type PDH_FMT_COUNTERVALUE_DOUBLE struct {
CStatus uint32
DoubleValue float64
}
// Union specialization for 64 bit integer values
type PDH_FMT_COUNTERVALUE_LARGE struct {
CStatus uint32
LargeValue int64
}
// Union specialization for long values
type PDH_FMT_COUNTERVALUE_LONG struct {
CStatus uint32
LongValue int32
padding [4]byte
}
// Union specialization for double values, used by PdhGetFormattedCounterArrayDouble()
type PDH_FMT_COUNTERVALUE_ITEM_DOUBLE struct {
SzName *uint16 // pointer to a string
FmtValue PDH_FMT_COUNTERVALUE_DOUBLE
}
// Union specialization for 'large' values, used by PdhGetFormattedCounterArrayLarge()
type PDH_FMT_COUNTERVALUE_ITEM_LARGE struct {
SzName *uint16 // pointer to a string
FmtValue PDH_FMT_COUNTERVALUE_LARGE
}
// Union specialization for long values, used by PdhGetFormattedCounterArrayLong()
type PDH_FMT_COUNTERVALUE_ITEM_LONG struct {
SzName *uint16 // pointer to a string
FmtValue PDH_FMT_COUNTERVALUE_LONG
}
var (
// Library
libpdhDll *syscall.DLL
@@ -174,8 +211,6 @@ var (
pdh_GetFormattedCounterArrayW *syscall.Proc
pdh_OpenQuery *syscall.Proc
pdh_ValidatePathW *syscall.Proc
pdh_ExpandWildCardPathW *syscall.Proc
pdh_GetCounterInfoW *syscall.Proc
)
func init() {
@@ -191,11 +226,9 @@ func init() {
pdh_GetFormattedCounterArrayW = libpdhDll.MustFindProc("PdhGetFormattedCounterArrayW")
pdh_OpenQuery = libpdhDll.MustFindProc("PdhOpenQuery")
pdh_ValidatePathW = libpdhDll.MustFindProc("PdhValidatePathW")
pdh_ExpandWildCardPathW = libpdhDll.MustFindProc("PdhExpandWildCardPathW")
pdh_GetCounterInfoW = libpdhDll.MustFindProc("PdhGetCounterInfoW")
}
// PdhAddCounter adds the specified counter to the query. This is the internationalized version. Preferably, use the
// Adds the specified counter to the query. This is the internationalized version. Preferably, use the
// function PdhAddEnglishCounter instead. hQuery is the query handle, which has been fetched by PdhOpenQuery.
// szFullCounterPath is a full, internationalized counter path (this will differ per Windows language version).
// dwUserData is a 'user-defined value', which becomes part of the counter information. To retrieve this value
@@ -244,14 +277,7 @@ func PdhAddCounter(hQuery PDH_HQUERY, szFullCounterPath string, dwUserData uintp
return uint32(ret)
}
// PdhAddEnglishCounterSupported returns true if PdhAddEnglishCounterW Win API function was found in pdh.dll.
// PdhAddEnglishCounterW function is not supported on pre-Windows Vista systems
func PdhAddEnglishCounterSupported() bool {
return pdh_AddEnglishCounterW != nil
}
// PdhAddEnglishCounter adds the specified language-neutral counter to the query. See the PdhAddCounter function. This function only exists on
// Adds the specified language-neutral counter to the query. See the PdhAddCounter function. This function only exists on
// Windows versions higher than Vista.
func PdhAddEnglishCounter(hQuery PDH_HQUERY, szFullCounterPath string, dwUserData uintptr, phCounter *PDH_HCOUNTER) uint32 {
if pdh_AddEnglishCounterW == nil {
@@ -268,7 +294,7 @@ func PdhAddEnglishCounter(hQuery PDH_HQUERY, szFullCounterPath string, dwUserDat
return uint32(ret)
}
// PdhCloseQuery closes all counters contained in the specified query, closes all handles related to the query,
// Closes all counters contained in the specified query, closes all handles related to the query,
// and frees all memory associated with the query.
func PdhCloseQuery(hQuery PDH_HQUERY) uint32 {
ret, _, _ := pdh_CloseQuery.Call(uintptr(hQuery))
@@ -303,7 +329,7 @@ func PdhCollectQueryData(hQuery PDH_HQUERY) uint32 {
return uint32(ret)
}
// PdhGetFormattedCounterValueDouble formats the given hCounter using a 'double'. The result is set into the specialized union struct pValue.
// Formats the given hCounter using a 'double'. The result is set into the specialized union struct pValue.
// This function does not directly translate to a Windows counterpart due to union specialization tricks.
func PdhGetFormattedCounterValueDouble(hCounter PDH_HCOUNTER, lpdwType *uint32, pValue *PDH_FMT_COUNTERVALUE_DOUBLE) uint32 {
ret, _, _ := pdh_GetFormattedCounterValue.Call(
@@ -315,7 +341,7 @@ func PdhGetFormattedCounterValueDouble(hCounter PDH_HCOUNTER, lpdwType *uint32,
return uint32(ret)
}
// PdhGetFormattedCounterArrayDouble returns an array of formatted counter values. Use this function when you want to format the counter values of a
// Returns an array of formatted counter values. Use this function when you want to format the counter values of a
// counter that contains a wildcard character for the instance name. The itemBuffer must a slice of type PDH_FMT_COUNTERVALUE_ITEM_DOUBLE.
// An example of how this function can be used:
//
@@ -363,7 +389,7 @@ func PdhGetFormattedCounterArrayDouble(hCounter PDH_HCOUNTER, lpdwBufferSize *ui
return uint32(ret)
}
// PdhOpenQuery creates a new query that is used to manage the collection of performance data.
// Creates a new query that is used to manage the collection of performance data.
// szDataSource is a null terminated string that specifies the name of the log file from which to
// retrieve the performance data. If 0, performance data is collected from a real-time data source.
// dwUserData is a user-defined value to associate with this query. To retrieve the user data later,
@@ -379,51 +405,7 @@ func PdhOpenQuery(szDataSource uintptr, dwUserData uintptr, phQuery *PDH_HQUERY)
return uint32(ret)
}
//PdhExpandWildCardPath examines the specified computer or log file and returns those counter paths that match the given counter path which contains wildcard characters.
//The general counter path format is as follows:
//
//\\computer\object(parent/instance#index)\counter
//
//The parent, instance, index, and counter components of the counter path may contain either a valid name or a wildcard character. The computer, parent, instance,
// and index components are not necessary for all counters.
//
//The following is a list of the possible formats:
//
//\\computer\object(parent/instance#index)\counter
//\\computer\object(parent/instance)\counter
//\\computer\object(instance#index)\counter
//\\computer\object(instance)\counter
//\\computer\object\counter
//\object(parent/instance#index)\counter
//\object(parent/instance)\counter
//\object(instance#index)\counter
//\object(instance)\counter
//\object\counter
//Use an asterisk (*) as the wildcard character, for example, \object(*)\counter.
//
//If a wildcard character is specified in the parent name, all instances of the specified object that match the specified instance and counter fields will be returned.
// For example, \object(*/instance)\counter.
//
//If a wildcard character is specified in the instance name, all instances of the specified object and parent object will be returned if all instance names
// corresponding to the specified index match the wildcard character. For example, \object(parent/*)\counter. If the object does not contain an instance, an error occurs.
//
//If a wildcard character is specified in the counter name, all counters of the specified object are returned.
//
//Partial counter path string matches (for example, "pro*") are supported.
func PdhExpandWildCardPath(szWildCardPath string, mszExpandedPathList *uint16, pcchPathListLength *uint32) uint32 {
ptxt, _ := syscall.UTF16PtrFromString(szWildCardPath)
flags := uint32(0) // expand instances and counters
ret, _, _ := pdh_ExpandWildCardPathW.Call(
uintptr(unsafe.Pointer(nil)), // search counters on local computer
uintptr(unsafe.Pointer(ptxt)),
uintptr(unsafe.Pointer(mszExpandedPathList)),
uintptr(unsafe.Pointer(pcchPathListLength)),
uintptr(unsafe.Pointer(&flags)))
return uint32(ret)
}
// PdhValidatePath validates a path. Will return ERROR_SUCCESS when ok, or PDH_CSTATUS_BAD_COUNTERNAME when the path is
// Validates a path. Will return ERROR_SUCCESS when ok, or PDH_CSTATUS_BAD_COUNTERNAME when the path is
// erroneous.
func PdhValidatePath(path string) uint32 {
ptxt, _ := syscall.UTF16PtrFromString(path)
@@ -432,6 +414,13 @@ func PdhValidatePath(path string) uint32 {
return uint32(ret)
}
func UTF16PtrToString(s *uint16) string {
if s == nil {
return ""
}
return syscall.UTF16ToString((*[1 << 29]uint16)(unsafe.Pointer(s))[0:])
}
func PdhFormatError(msgId uint32) string {
var flags uint32 = windows.FORMAT_MESSAGE_FROM_HMODULE | windows.FORMAT_MESSAGE_ARGUMENT_ARRAY | windows.FORMAT_MESSAGE_IGNORE_INSERTS
buf := make([]uint16, 300)
@@ -441,25 +430,3 @@ func PdhFormatError(msgId uint32) string {
}
return fmt.Sprintf("(pdhErr=%d) %s", msgId, err.Error())
}
//Retrieves information about a counter, such as data size, counter type, path, and user-supplied data values
//hCounter [in]
//Handle of the counter from which you want to retrieve information. The PdhAddCounter function returns this handle.
//
//bRetrieveExplainText [in]
//Determines whether explain text is retrieved. If you set this parameter to TRUE, the explain text for the counter is retrieved. If you set this parameter to FALSE, the field in the returned buffer is NULL.
//
//pdwBufferSize [in, out]
//Size of the lpBuffer buffer, in bytes. If zero on input, the function returns PDH_MORE_DATA and sets this parameter to the required buffer size. If the buffer is larger than the required size, the function sets this parameter to the actual size of the buffer that was used. If the specified size on input is greater than zero but less than the required size, you should not rely on the returned size to reallocate the buffer.
//
//lpBuffer [out]
//Caller-allocated buffer that receives a PDH_COUNTER_INFO structure. The structure is variable-length, because the string data is appended to the end of the fixed-format portion of the structure. This is done so that all data is returned in a single buffer allocated by the caller. Set to NULL if pdwBufferSize is zero.
func PdhGetCounterInfo(hCounter PDH_HCOUNTER, bRetrieveExplainText int, pdwBufferSize *uint32, lpBuffer *byte) uint32 {
ret, _, _ := pdh_GetCounterInfoW.Call(
uintptr(hCounter),
uintptr(bRetrieveExplainText),
uintptr(unsafe.Pointer(pdwBufferSize)),
uintptr(unsafe.Pointer(lpBuffer)))
return uint32(ret)
}

View File

@@ -1,121 +0,0 @@
// Copyright (c) 2010 The win Authors. 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.
// 3. The names of the authors may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 AUTHORS 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.
//
// This is the official list of 'win' authors for copyright purposes.
//
// Alexander Neumann <an2048@googlemail.com>
// Joseph Watson <jtwatson@linux-consulting.us>
// Kevin Pors <krpors@gmail.com>
// +build windows
package win_perf_counters
// Union specialization for double values
type PDH_FMT_COUNTERVALUE_DOUBLE struct {
CStatus uint32
padding [4]byte
DoubleValue float64
}
// Union specialization for 64 bit integer values
type PDH_FMT_COUNTERVALUE_LARGE struct {
CStatus uint32
padding [4]byte
LargeValue int64
}
// Union specialization for long values
type PDH_FMT_COUNTERVALUE_LONG struct {
CStatus uint32
LongValue int32
padding [4]byte
}
type PDH_FMT_COUNTERVALUE_ITEM_DOUBLE struct {
SzName *uint16
padding [4]byte
FmtValue PDH_FMT_COUNTERVALUE_DOUBLE
}
// Union specialization for 'large' values, used by PdhGetFormattedCounterArrayLarge()
type PDH_FMT_COUNTERVALUE_ITEM_LARGE struct {
SzName *uint16 // pointer to a string
padding [4]byte
FmtValue PDH_FMT_COUNTERVALUE_LARGE
}
// Union specialization for long values, used by PdhGetFormattedCounterArrayLong()
type PDH_FMT_COUNTERVALUE_ITEM_LONG struct {
SzName *uint16 // pointer to a string
padding [4]byte
FmtValue PDH_FMT_COUNTERVALUE_LONG
}
//PDH_COUNTER_INFO structure contains information describing the properties of a counter. This information also includes the counter path.
type PDH_COUNTER_INFO struct {
//Size of the structure, including the appended strings, in bytes.
DwLength uint32
//Counter type. For a list of counter types, see the Counter Types section of the <a "href=http://go.microsoft.com/fwlink/p/?linkid=84422">Windows Server 2003 Deployment Kit</a>.
//The counter type constants are defined in Winperf.h.
DwType uint32
//Counter version information. Not used.
CVersion uint32
//Counter status that indicates if the counter value is valid. For a list of possible values,
//see <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa371894(v=vs.85).aspx">Checking PDH Interface Return Values</a>.
CStatus uint32
//Scale factor to use when computing the displayable value of the counter. The scale factor is a power of ten.
//The valid range of this parameter is PDH_MIN_SCALE (7) (the returned value is the actual value times 10⁷) to
//PDH_MAX_SCALE (+7) (the returned value is the actual value times 10⁺⁷). A value of zero will set the scale to one, so that the actual value is returned
LScale int32
//Default scale factor as suggested by the counter's provider.
LDefaultScale int32
//The value passed in the dwUserData parameter when calling PdhAddCounter.
DwUserData *uint32
//The value passed in the dwUserData parameter when calling PdhOpenQuery.
DwQueryUserData *uint32
//Null-terminated string that specifies the full counter path. The string follows this structure in memory.
SzFullPath *uint16 // pointer to a string
//Null-terminated string that contains the name of the computer specified in the counter path. Is NULL, if the path does not specify a computer.
//The string follows this structure in memory.
SzMachineName *uint16 // pointer to a string
//Null-terminated string that contains the name of the performance object specified in the counter path. The string follows this structure in memory.
SzObjectName *uint16 // pointer to a string
//Null-terminated string that contains the name of the object instance specified in the counter path. Is NULL, if the path does not specify an instance.
//The string follows this structure in memory.
SzInstanceName *uint16 // pointer to a string
//Null-terminated string that contains the name of the parent instance specified in the counter path. Is NULL, if the path does not specify a parent instance.
//The string follows this structure in memory.
SzParentInstance *uint16 // pointer to a string
//Instance index specified in the counter path. Is 0, if the path does not specify an instance index.
DwInstanceIndex uint32 // pointer to a string
//Null-terminated string that contains the counter name. The string follows this structure in memory.
SzCounterName *uint16 // pointer to a string
//padding
Padding [4]byte
//Help text that describes the counter. Is NULL if the source is a log file.
SzExplainText *uint16 // pointer to a string
//Start of the string data that is appended to the structure.
DataBuffer [1]uint32 // pointer to an extra space
}

View File

@@ -1,114 +0,0 @@
// Copyright (c) 2010 The win Authors. 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.
// 3. The names of the authors may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 AUTHORS 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.
//
// This is the official list of 'win' authors for copyright purposes.
//
// Alexander Neumann <an2048@googlemail.com>
// Joseph Watson <jtwatson@linux-consulting.us>
// Kevin Pors <krpors@gmail.com>
// +build windows
package win_perf_counters
// Union specialization for double values
type PDH_FMT_COUNTERVALUE_DOUBLE struct {
CStatus uint32
DoubleValue float64
}
// Union specialization for 64 bit integer values
type PDH_FMT_COUNTERVALUE_LARGE struct {
CStatus uint32
LargeValue int64
}
// Union specialization for long values
type PDH_FMT_COUNTERVALUE_LONG struct {
CStatus uint32
LongValue int32
padding [4]byte
}
type PDH_FMT_COUNTERVALUE_ITEM_DOUBLE struct {
SzName *uint16
FmtValue PDH_FMT_COUNTERVALUE_DOUBLE
}
// Union specialization for 'large' values, used by PdhGetFormattedCounterArrayLarge()
type PDH_FMT_COUNTERVALUE_ITEM_LARGE struct {
SzName *uint16 // pointer to a string
FmtValue PDH_FMT_COUNTERVALUE_LARGE
}
// Union specialization for long values, used by PdhGetFormattedCounterArrayLong()
type PDH_FMT_COUNTERVALUE_ITEM_LONG struct {
SzName *uint16 // pointer to a string
FmtValue PDH_FMT_COUNTERVALUE_LONG
}
//PDH_COUNTER_INFO structure contains information describing the properties of a counter. This information also includes the counter path.
type PDH_COUNTER_INFO struct {
//Size of the structure, including the appended strings, in bytes.
DwLength uint32
//Counter type. For a list of counter types, see the Counter Types section of the <a "href=http://go.microsoft.com/fwlink/p/?linkid=84422">Windows Server 2003 Deployment Kit</a>.
//The counter type constants are defined in Winperf.h.
DwType uint32
//Counter version information. Not used.
CVersion uint32
//Counter status that indicates if the counter value is valid. For a list of possible values,
//see <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa371894(v=vs.85).aspx">Checking PDH Interface Return Values</a>.
CStatus uint32
//Scale factor to use when computing the displayable value of the counter. The scale factor is a power of ten.
//The valid range of this parameter is PDH_MIN_SCALE (7) (the returned value is the actual value times 10⁷) to
//PDH_MAX_SCALE (+7) (the returned value is the actual value times 10⁺⁷). A value of zero will set the scale to one, so that the actual value is returned
LScale int32
//Default scale factor as suggested by the counter's provider.
LDefaultScale int32
//The value passed in the dwUserData parameter when calling PdhAddCounter.
DwUserData *uint32
//The value passed in the dwUserData parameter when calling PdhOpenQuery.
DwQueryUserData *uint32
//Null-terminated string that specifies the full counter path. The string follows this structure in memory.
SzFullPath *uint16 // pointer to a string
//Null-terminated string that contains the name of the computer specified in the counter path. Is NULL, if the path does not specify a computer.
//The string follows this structure in memory.
SzMachineName *uint16 // pointer to a string
//Null-terminated string that contains the name of the performance object specified in the counter path. The string follows this structure in memory.
SzObjectName *uint16 // pointer to a string
//Null-terminated string that contains the name of the object instance specified in the counter path. Is NULL, if the path does not specify an instance.
//The string follows this structure in memory.
SzInstanceName *uint16 // pointer to a string
//Null-terminated string that contains the name of the parent instance specified in the counter path. Is NULL, if the path does not specify a parent instance.
//The string follows this structure in memory.
SzParentInstance *uint16 // pointer to a string
//Instance index specified in the counter path. Is 0, if the path does not specify an instance index.
DwInstanceIndex uint32 // pointer to a string
//Null-terminated string that contains the counter name. The string follows this structure in memory.
SzCounterName *uint16 // pointer to a string
//Help text that describes the counter. Is NULL if the source is a log file.
SzExplainText *uint16 // pointer to a string
//Start of the string data that is appended to the structure.
DataBuffer [1]uint32 // pointer to an extra space
}

View File

@@ -1,189 +0,0 @@
// Go API over pdh syscalls
// +build windows
package win_perf_counters
import (
"errors"
"syscall"
"unsafe"
)
//PerformanceQuery provides wrappers around Windows performance counters API for easy usage in GO
type PerformanceQuery interface {
Open() error
Close() error
AddCounterToQuery(counterPath string) (PDH_HCOUNTER, error)
AddEnglishCounterToQuery(counterPath string) (PDH_HCOUNTER, error)
GetCounterPath(counterHandle PDH_HCOUNTER) (string, error)
ExpandWildCardPath(counterPath string) ([]string, error)
GetFormattedCounterValueDouble(hCounter PDH_HCOUNTER) (float64, error)
CollectData() error
AddEnglishCounterSupported() bool
}
//PdhError represents error returned from Performance Counters API
type PdhError struct {
ErrorCode uint32
errorText string
}
func (m *PdhError) Error() string {
return m.errorText
}
func NewPdhError(code uint32) error {
return &PdhError{
ErrorCode: code,
errorText: PdhFormatError(code),
}
}
//PerformanceQueryImpl is implementation of PerformanceQuery interface, which calls phd.dll functions
type PerformanceQueryImpl struct {
query PDH_HQUERY
}
// Open creates a new counterPath that is used to manage the collection of performance data.
// It returns counterPath handle used for subsequent calls for adding counters and querying data
func (m *PerformanceQueryImpl) Open() error {
if m.query != 0 {
err := m.Close()
if err != nil {
return err
}
}
var handle PDH_HQUERY
ret := PdhOpenQuery(0, 0, &handle)
if ret != ERROR_SUCCESS {
return NewPdhError(ret)
}
m.query = handle
return nil
}
// Close closes the counterPath, releases associated counter handles and frees resources
func (m *PerformanceQueryImpl) Close() error {
if m.query == 0 {
return errors.New("uninitialised query")
}
ret := PdhCloseQuery(m.query)
if ret != ERROR_SUCCESS {
return NewPdhError(ret)
}
m.query = 0
return nil
}
func (m *PerformanceQueryImpl) AddCounterToQuery(counterPath string) (PDH_HCOUNTER, error) {
var counterHandle PDH_HCOUNTER
if m.query == 0 {
return 0, errors.New("uninitialised query")
}
ret := PdhAddCounter(m.query, counterPath, 0, &counterHandle)
if ret != ERROR_SUCCESS {
return 0, NewPdhError(ret)
}
return counterHandle, nil
}
func (m *PerformanceQueryImpl) AddEnglishCounterToQuery(counterPath string) (PDH_HCOUNTER, error) {
var counterHandle PDH_HCOUNTER
if m.query == 0 {
return 0, errors.New("uninitialised query")
}
ret := PdhAddEnglishCounter(m.query, counterPath, 0, &counterHandle)
if ret != ERROR_SUCCESS {
return 0, NewPdhError(ret)
}
return counterHandle, nil
}
//GetCounterPath return counter information for given handle
func (m *PerformanceQueryImpl) GetCounterPath(counterHandle PDH_HCOUNTER) (string, error) {
var bufSize uint32
var buff []byte
ret := PdhGetCounterInfo(counterHandle, 0, &bufSize, nil)
if ret == PDH_MORE_DATA {
buff = make([]byte, bufSize)
bufSize = uint32(len(buff))
ret = PdhGetCounterInfo(counterHandle, 0, &bufSize, &buff[0])
if ret == ERROR_SUCCESS {
ci := (*PDH_COUNTER_INFO)(unsafe.Pointer(&buff[0]))
return UTF16PtrToString(ci.SzFullPath), nil
}
}
return "", NewPdhError(ret)
}
// ExpandWildCardPath examines local computer and returns those counter paths that match the given counter path which contains wildcard characters.
func (m *PerformanceQueryImpl) ExpandWildCardPath(counterPath string) ([]string, error) {
var bufSize uint32
var buff []uint16
ret := PdhExpandWildCardPath(counterPath, nil, &bufSize)
if ret == PDH_MORE_DATA {
buff = make([]uint16, bufSize)
bufSize = uint32(len(buff))
ret = PdhExpandWildCardPath(counterPath, &buff[0], &bufSize)
if ret == ERROR_SUCCESS {
list := UTF16ToStringArray(buff)
return list, nil
}
}
return nil, NewPdhError(ret)
}
//GetFormattedCounterValueDouble computes a displayable value for the specified counter
func (m *PerformanceQueryImpl) GetFormattedCounterValueDouble(hCounter PDH_HCOUNTER) (float64, error) {
var counterType uint32
var value PDH_FMT_COUNTERVALUE_DOUBLE
ret := PdhGetFormattedCounterValueDouble(hCounter, &counterType, &value)
if ret == ERROR_SUCCESS {
if value.CStatus == PDH_CSTATUS_VALID_DATA || value.CStatus == PDH_CSTATUS_NEW_DATA {
return value.DoubleValue, nil
} else {
return 0, NewPdhError(value.CStatus)
}
} else {
return 0, NewPdhError(ret)
}
}
func (m *PerformanceQueryImpl) CollectData() error {
if m.query == 0 {
return errors.New("uninitialised query")
}
ret := PdhCollectQueryData(m.query)
if ret != ERROR_SUCCESS {
return NewPdhError(ret)
}
return nil
}
func (m *PerformanceQueryImpl) AddEnglishCounterSupported() bool {
return PdhAddEnglishCounterSupported()
}
// UTF16PtrToString converts Windows API LPTSTR (pointer to string) to go string
func UTF16PtrToString(s *uint16) string {
if s == nil {
return ""
}
return syscall.UTF16ToString((*[1 << 29]uint16)(unsafe.Pointer(s))[0:])
}
// UTF16ToStringArray converts list of Windows API NULL terminated strings to go string array
func UTF16ToStringArray(buf []uint16) []string {
var strings []string
nextLineStart := 0
stringLine := UTF16PtrToString(&buf[0])
for stringLine != "" {
strings = append(strings, stringLine)
nextLineStart += len(stringLine) + 1
remainingBuf := buf[nextLineStart:]
stringLine = UTF16PtrToString(&remainingBuf[0])
}
return strings
}

View File

@@ -5,13 +5,11 @@ package win_perf_counters
import (
"errors"
"fmt"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/plugins/inputs"
"log"
"regexp"
"strings"
"time"
"unsafe"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
)
var sampleConfig = `
@@ -22,8 +20,6 @@ var sampleConfig = `
## agent, it will not be gathered.
## Settings:
# PrintValid = false # Print All matching performance counters
# Period after which counters will be reread from configuration and wildcards in counter paths expanded
CountersRefreshInterval="1m"
[[inputs.win_perf_counters.object]]
# Processor usage, alternative to native, reports on a per core.
@@ -57,7 +53,7 @@ var sampleConfig = `
Measurement = "win_system"
[[inputs.win_perf_counters.object]]
# Example counterPath where the Instance portion must be removed to get data back,
# Example query where the Instance portion must be removed to get data back,
# such as from the Memory object.
ObjectName = "Memory"
Counters = [
@@ -65,20 +61,17 @@ var sampleConfig = `
"Page Faults/sec", "Pages/sec", "Transition Faults/sec",
"Pool Nonpaged Bytes", "Pool Paged Bytes"
]
Instances = ["------"] # Use 6 x - to remove the Instance bit from the counterPath.
Instances = ["------"] # Use 6 x - to remove the Instance bit from the query.
Measurement = "win_mem"
`
type Win_PerfCounters struct {
PrintValid bool
//deprecated: determined dynamically
PreVistaSupport bool
Object []perfobject
CountersRefreshInterval internal.Duration
PrintValid bool
PreVistaSupport bool
Object []perfobject
lastRefreshed time.Time
counters []*counter
query PerformanceQuery
configParsed bool
itemCache []*item
}
type perfobject struct {
@@ -91,101 +84,56 @@ type perfobject struct {
IncludeTotal bool
}
type counter struct {
counterPath string
type item struct {
query string
objectName string
counter string
instance string
measurement string
includeTotal bool
include_total bool
handle PDH_HQUERY
counterHandle PDH_HCOUNTER
}
var sanitizedChars = strings.NewReplacer("/sec", "_persec", "/Sec", "_persec",
" ", "_", "%", "Percent", `\`, "")
//General Counter path pattern is: \\computer\object(parent/instance#index)\counter
//parent/instance#index part is skipped in single instance objects (e.g. Memory): \\computer\object\counter
func (m *Win_PerfCounters) AddItem(query string, objectName string, counter string, instance string,
measurement string, include_total bool) error {
var counterPathRE = regexp.MustCompile(`.*\\(.*)\\(.*)`)
var objectInstanceRE = regexp.MustCompile(`(.*)\((.*)\)`)
//extractObjectInstanceCounterFromQuery gets object name, instance name (if available) and counter name from counter path
func extractObjectInstanceCounterFromQuery(query string) (object string, instance string, counter string, err error) {
pathParts := counterPathRE.FindAllStringSubmatch(query, -1)
if pathParts == nil || len(pathParts[0]) != 3 {
err = errors.New("Could not extract counter info from: " + query)
return
}
counter = pathParts[0][2]
//try to get instance name
objectInstanceParts := objectInstanceRE.FindAllStringSubmatch(pathParts[0][1], -1)
if objectInstanceParts == nil || len(objectInstanceParts[0]) != 3 {
object = pathParts[0][1]
var handle PDH_HQUERY
var counterHandle PDH_HCOUNTER
ret := PdhOpenQuery(0, 0, &handle)
if m.PreVistaSupport {
ret = PdhAddCounter(handle, query, 0, &counterHandle)
} else {
object = objectInstanceParts[0][1]
instance = objectInstanceParts[0][2]
ret = PdhAddEnglishCounter(handle, query, 0, &counterHandle)
}
return
// Call PdhCollectQueryData one time to check existence of the counter
ret = PdhCollectQueryData(handle)
if ret != ERROR_SUCCESS {
PdhCloseQuery(handle)
return errors.New(PdhFormatError(ret))
}
newItem := &item{query, objectName, counter, instance, measurement,
include_total, handle, counterHandle}
m.itemCache = append(m.itemCache, newItem)
return nil
}
func (m *Win_PerfCounters) Description() string {
return "Input plugin to counterPath Performance Counters on Windows operating systems"
return "Input plugin to query Performance Counters on Windows operating systems"
}
func (m *Win_PerfCounters) SampleConfig() string {
return sampleConfig
}
func (m *Win_PerfCounters) AddItem(counterPath string, instance string, measurement string, includeTotal bool) error {
if !m.query.AddEnglishCounterSupported() {
_, err := m.query.AddCounterToQuery(counterPath)
if err != nil {
return err
}
} else {
counterHandle, err := m.query.AddEnglishCounterToQuery(counterPath)
if err != nil {
return err
}
counterPath, err = m.query.GetCounterPath(counterHandle)
if err != nil {
return err
}
}
counters, err := m.query.ExpandWildCardPath(counterPath)
if err != nil {
return err
}
for _, counterPath := range counters {
var err error
counterHandle, err := m.query.AddCounterToQuery(counterPath)
parsedObjectName, parsedInstance, parsedCounter, err := extractObjectInstanceCounterFromQuery(counterPath)
if err != nil {
return err
}
if parsedInstance == "_Total" && instance == "*" && !includeTotal {
continue
}
newItem := &counter{counterPath, parsedObjectName, parsedCounter, parsedInstance, measurement,
includeTotal, counterHandle}
m.counters = append(m.counters, newItem)
if m.PrintValid {
log.Printf("Valid: %s\n", counterPath)
}
}
return nil
}
func (m *Win_PerfCounters) ParseConfig() error {
var counterPath string
var query string
if len(m.Object) > 0 {
for _, PerfObject := range m.Object {
@@ -194,16 +142,21 @@ func (m *Win_PerfCounters) ParseConfig() error {
objectname := PerfObject.ObjectName
if instance == "------" {
counterPath = "\\" + objectname + "\\" + counter
query = "\\" + objectname + "\\" + counter
} else {
counterPath = "\\" + objectname + "(" + instance + ")\\" + counter
query = "\\" + objectname + "(" + instance + ")\\" + counter
}
err := m.AddItem(counterPath, instance, PerfObject.Measurement, PerfObject.IncludeTotal)
err := m.AddItem(query, objectname, counter, instance,
PerfObject.Measurement, PerfObject.IncludeTotal)
if err != nil {
if err == nil {
if m.PrintValid {
fmt.Printf("Valid: %s\n", query)
}
} else {
if PerfObject.FailOnMissing || PerfObject.WarnOnMissing {
log.Printf("Invalid counterPath: '%s'. Error: %s\n", counterPath, err.Error())
fmt.Printf("Invalid query: '%s'. Error: %s", query, err.Error())
}
if PerfObject.FailOnMissing {
return err
@@ -212,40 +165,33 @@ func (m *Win_PerfCounters) ParseConfig() error {
}
}
}
return nil
} else {
err := errors.New("no performance objects configured")
err := errors.New("No performance objects configured!")
return err
}
}
func (m *Win_PerfCounters) GetParsedItemsForTesting() []*item {
return m.itemCache
}
func (m *Win_PerfCounters) Gather(acc telegraf.Accumulator) error {
// Parse the config once
var err error
if m.lastRefreshed.IsZero() || (m.CountersRefreshInterval.Duration.Nanoseconds() > 0 && m.lastRefreshed.Add(m.CountersRefreshInterval.Duration).Before(time.Now())) {
m.counters = m.counters[:0]
err = m.query.Open()
if !m.configParsed {
err := m.ParseConfig()
m.configParsed = true
if err != nil {
return err
}
err = m.ParseConfig()
if err != nil {
return err
}
//some counters need two data samples before computing a value
err = m.query.CollectData()
if err != nil {
return err
}
m.lastRefreshed = time.Now()
time.Sleep(time.Second)
}
var bufSize uint32
var bufCount uint32
var size uint32 = uint32(unsafe.Sizeof(PDH_FMT_COUNTERVALUE_ITEM_DOUBLE{}))
var emptyBuf [1]PDH_FMT_COUNTERVALUE_ITEM_DOUBLE // need at least 1 addressable null ptr.
type InstanceGrouping struct {
name string
instance string
@@ -254,40 +200,78 @@ func (m *Win_PerfCounters) Gather(acc telegraf.Accumulator) error {
var collectFields = make(map[InstanceGrouping]map[string]interface{})
err = m.query.CollectData()
if err != nil {
return err
}
// For iterate over the known metrics and get the samples.
for _, metric := range m.counters {
for _, metric := range m.itemCache {
// collect
value, err := m.query.GetFormattedCounterValueDouble(metric.counterHandle)
if err == nil {
measurement := sanitizedChars.Replace(metric.measurement)
if measurement == "" {
measurement = "win_perf_counters"
}
ret := PdhCollectQueryData(metric.handle)
if ret == ERROR_SUCCESS {
ret = PdhGetFormattedCounterArrayDouble(metric.counterHandle, &bufSize,
&bufCount, &emptyBuf[0]) // uses null ptr here according to MSDN.
if ret == PDH_MORE_DATA {
filledBuf := make([]PDH_FMT_COUNTERVALUE_ITEM_DOUBLE, bufCount*size)
if len(filledBuf) == 0 {
continue
}
ret = PdhGetFormattedCounterArrayDouble(metric.counterHandle,
&bufSize, &bufCount, &filledBuf[0])
for i := 0; i < int(bufCount); i++ {
c := filledBuf[i]
var s string = UTF16PtrToString(c.SzName)
var instance = InstanceGrouping{measurement, metric.instance, metric.objectName}
if collectFields[instance] == nil {
collectFields[instance] = make(map[string]interface{})
}
collectFields[instance][sanitizedChars.Replace(metric.counter)] = float32(value)
} else {
//ignore invalid data from as some counters from process instances returns this sometimes
if phderr, ok := err.(*PdhError); ok && phderr.ErrorCode != PDH_INVALID_DATA && phderr.ErrorCode != PDH_CALC_NEGATIVE_VALUE {
return fmt.Errorf("error while getting value for counter %s: %v", metric.counterPath, err)
var add bool
if metric.include_total {
// If IncludeTotal is set, include all.
add = true
} else if metric.instance == "*" && !strings.Contains(s, "_Total") {
// Catch if set to * and that it is not a '*_Total*' instance.
add = true
} else if metric.instance == s {
// Catch if we set it to total or some form of it
add = true
} else if strings.Contains(metric.instance, "#") && strings.HasPrefix(metric.instance, s) {
// If you are using a multiple instance identifier such as "w3wp#1"
// phd.dll returns only the first 2 characters of the identifier.
add = true
s = metric.instance
} else if metric.instance == "------" {
add = true
}
if add {
tags := make(map[string]string)
if s != "" {
tags["instance"] = s
}
tags["objectname"] = metric.objectName
measurement := sanitizedChars.Replace(metric.measurement)
if measurement == "" {
measurement = "win_perf_counters"
}
var instance = InstanceGrouping{measurement, s, metric.objectName}
if collectFields[instance] == nil {
collectFields[instance] = make(map[string]interface{})
}
collectFields[instance][sanitizedChars.Replace(metric.counter)] = float32(c.FmtValue.DoubleValue)
}
}
filledBuf = nil
// Need to at least set bufSize to zero, because if not, the function will not
// return PDH_MORE_DATA and will not set the bufSize.
bufCount = 0
bufSize = 0
}
}
}
for instance, fields := range collectFields {
var tags = map[string]string{
"instance": instance.instance,
"objectname": instance.objectname,
}
if len(instance.instance) > 0 {
tags["instance"] = instance.instance
}
acc.AddFields(instance.name, fields, tags)
}
@@ -295,7 +279,5 @@ func (m *Win_PerfCounters) Gather(acc telegraf.Accumulator) error {
}
func init() {
inputs.Add("win_perf_counters", func() telegraf.Input {
return &Win_PerfCounters{query: &PerformanceQueryImpl{}, CountersRefreshInterval: internal.Duration{Duration: time.Second * 60}}
})
inputs.Add("win_perf_counters", func() telegraf.Input { return &Win_PerfCounters{} })
}

View File

@@ -1,592 +0,0 @@
// +build windows
package win_perf_counters
import (
"errors"
"testing"
"time"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"strings"
)
func TestWinPerformanceQueryImpl(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
var query PerformanceQuery
var hCounter PDH_HCOUNTER
var err error
query = &PerformanceQueryImpl{}
err = query.Close()
require.Error(t, err, "uninitialized query must return errors")
_, err = query.AddCounterToQuery("")
require.Error(t, err, "uninitialized query must return errors")
assert.True(t, strings.Contains(err.Error(), "uninitialised"))
_, err = query.AddEnglishCounterToQuery("")
require.Error(t, err, "uninitialized query must return errors")
assert.True(t, strings.Contains(err.Error(), "uninitialised"))
err = query.CollectData()
require.Error(t, err, "uninitialized query must return errors")
assert.True(t, strings.Contains(err.Error(), "uninitialised"))
err = query.Open()
require.NoError(t, err)
counterPath := "\\Processor Information(_Total)\\% Processor Time"
hCounter, err = query.AddCounterToQuery(counterPath)
require.NoError(t, err)
assert.NotEqual(t, 0, hCounter)
err = query.Close()
require.NoError(t, err)
err = query.Open()
require.NoError(t, err)
hCounter, err = query.AddEnglishCounterToQuery(counterPath)
require.NoError(t, err)
assert.NotEqual(t, 0, hCounter)
cp, err := query.GetCounterPath(hCounter)
require.NoError(t, err)
assert.True(t, strings.HasSuffix(cp, counterPath))
err = query.CollectData()
require.NoError(t, err)
time.Sleep(time.Second)
err = query.CollectData()
require.NoError(t, err)
_, err = query.GetFormattedCounterValueDouble(hCounter)
require.NoError(t, err)
counterPath = "\\Process(*)\\% Processor Time"
paths, err := query.ExpandWildCardPath(counterPath)
require.NoError(t, err)
require.NotNil(t, paths)
assert.True(t, len(paths) > 1)
counterPath = "\\Process(_Total)\\*"
paths, err = query.ExpandWildCardPath(counterPath)
require.NoError(t, err)
require.NotNil(t, paths)
assert.True(t, len(paths) > 1)
}
func TestWinPerfcountersConfigGet1(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
var instances = make([]string, 1)
var counters = make([]string, 1)
var perfobjects = make([]perfobject, 1)
objectname := "Processor Information"
instances[0] = "_Total"
counters[0] = "% Processor Time"
var measurement = "test"
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: false,
FailOnMissing: true,
IncludeTotal: false,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}}
m.query.Open()
err := m.ParseConfig()
require.NoError(t, err)
}
func TestWinPerfcountersConfigGet2(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
var instances = make([]string, 1)
var counters = make([]string, 1)
var perfobjects = make([]perfobject, 1)
objectname := "Processor Information"
instances[0] = "_Total"
counters[0] = "% Processor Time"
var measurement = "test"
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: false,
FailOnMissing: true,
IncludeTotal: false,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}}
m.query.Open()
err := m.ParseConfig()
require.NoError(t, err)
if len(m.counters) == 1 {
require.NoError(t, nil)
} else if len(m.counters) == 0 {
var errorstring1 = "No results returned from the counterPath: " + string(len(m.counters))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
} else if len(m.counters) > 1 {
var errorstring1 = "Too many results returned from the counterPath: " + string(len(m.counters))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
}
}
func TestWinPerfcountersConfigGet3(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
var instances = make([]string, 1)
var counters = make([]string, 2)
var perfobjects = make([]perfobject, 1)
objectname := "Processor Information"
instances[0] = "_Total"
counters[0] = "% Processor Time"
counters[1] = "% Idle Time"
var measurement = "test"
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: false,
FailOnMissing: true,
IncludeTotal: false,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}}
m.query.Open()
err := m.ParseConfig()
require.NoError(t, err)
if len(m.counters) == 2 {
require.NoError(t, nil)
} else if len(m.counters) < 2 {
var errorstring1 = "Too few results returned from the counterPath. " + string(len(m.counters))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
} else if len(m.counters) > 2 {
var errorstring1 = "Too many results returned from the counterPath: " + string(len(m.counters))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
}
}
func TestWinPerfcountersConfigGet4(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
var instances = make([]string, 2)
var counters = make([]string, 1)
var perfobjects = make([]perfobject, 1)
objectname := "Processor Information"
instances[0] = "_Total"
instances[1] = "0,1"
counters[0] = "% Processor Time"
var measurement = "test"
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: false,
FailOnMissing: true,
IncludeTotal: false,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}}
m.query.Open()
err := m.ParseConfig()
require.NoError(t, err)
if len(m.counters) == 2 {
require.NoError(t, nil)
} else if len(m.counters) < 2 {
var errorstring1 = "Too few results returned from the counterPath: " + string(len(m.counters))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
} else if len(m.counters) > 2 {
var errorstring1 = "Too many results returned from the counterPath: " + string(len(m.counters))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
}
}
func TestWinPerfcountersConfigGet5(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
var instances = make([]string, 2)
var counters = make([]string, 2)
var perfobjects = make([]perfobject, 1)
objectname := "Processor Information"
instances[0] = "_Total"
instances[1] = "0,1"
counters[0] = "% Processor Time"
counters[1] = "% Idle Time"
var measurement = "test"
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: false,
FailOnMissing: true,
IncludeTotal: false,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}}
m.query.Open()
err := m.ParseConfig()
require.NoError(t, err)
if len(m.counters) == 4 {
require.NoError(t, nil)
} else if len(m.counters) < 4 {
var errorstring1 = "Too few results returned from the counterPath: " +
string(len(m.counters))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
} else if len(m.counters) > 4 {
var errorstring1 = "Too many results returned from the counterPath: " +
string(len(m.counters))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
}
}
func TestWinPerfcountersConfigGet6(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
var instances = make([]string, 1)
var counters = make([]string, 1)
var perfobjects = make([]perfobject, 1)
objectname := "System"
instances[0] = "------"
counters[0] = "Context Switches/sec"
var measurement = "test"
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: false,
FailOnMissing: true,
IncludeTotal: false,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}}
m.query.Open()
err := m.ParseConfig()
require.NoError(t, err)
}
func TestWinPerfcountersConfigGet7(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
var instances = make([]string, 1)
var counters = make([]string, 3)
var perfobjects = make([]perfobject, 1)
objectname := "Processor Information"
instances[0] = "_Total"
counters[0] = "% Processor Time"
counters[1] = "% Processor TimeERROR"
counters[2] = "% Idle Time"
var measurement = "test"
PerfObject := perfobject{
objectname,
counters,
instances,
measurement,
false,
false,
false,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}}
m.query.Open()
err := m.ParseConfig()
require.NoError(t, err)
if len(m.counters) == 2 {
require.NoError(t, nil)
} else if len(m.counters) < 2 {
var errorstring1 = "Too few results returned from the counterPath: " +
string(len(m.counters))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
} else if len(m.counters) > 2 {
var errorstring1 = "Too many results returned from the counterPath: " +
string(len(m.counters))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
}
}
func TestWinPerfcountersConfigError1(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
var instances = make([]string, 1)
var counters = make([]string, 1)
var perfobjects = make([]perfobject, 1)
objectname := "Processor InformationERROR"
instances[0] = "_Total"
counters[0] = "% Processor Time"
var measurement = "test"
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: false,
FailOnMissing: true,
IncludeTotal: false,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}}
m.query.Open()
err := m.ParseConfig()
require.Error(t, err)
}
func TestWinPerfcountersConfigError2(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
var instances = make([]string, 1)
var counters = make([]string, 1)
var perfobjects = make([]perfobject, 1)
objectname := "Processor"
instances[0] = "SuperERROR"
counters[0] = "% C1 Time"
var measurement = "test"
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: false,
FailOnMissing: true,
IncludeTotal: false,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}}
m.query.Open()
err := m.ParseConfig()
var acc testutil.Accumulator
err = m.Gather(&acc)
require.Error(t, err)
}
func TestWinPerfcountersConfigError3(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
var instances = make([]string, 1)
var counters = make([]string, 1)
var perfobjects = make([]perfobject, 1)
objectname := "Processor Information"
instances[0] = "_Total"
counters[0] = "% Processor TimeERROR"
var measurement = "test"
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: false,
FailOnMissing: true,
IncludeTotal: false,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}}
m.query.Open()
err := m.ParseConfig()
require.Error(t, err)
}
func TestWinPerfcountersCollect1(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
var instances = make([]string, 1)
var counters = make([]string, 1)
var perfobjects = make([]perfobject, 1)
objectname := "Processor Information"
instances[0] = "_Total"
counters[0] = "Parking Status"
var expectedCounter = "Parking_Status"
var measurement = "test"
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: false,
FailOnMissing: true,
IncludeTotal: false,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}}
var acc testutil.Accumulator
err := m.Gather(&acc)
require.NoError(t, err)
time.Sleep(2000 * time.Millisecond)
err = m.Gather(&acc)
require.NoError(t, err)
assert.Len(t, acc.Metrics, 2)
for _, metric := range acc.Metrics {
_, ok := metric.Fields[expectedCounter]
assert.True(t, ok)
}
}
func TestWinPerfcountersCollect2(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
var instances = make([]string, 2)
var counters = make([]string, 1)
var perfobjects = make([]perfobject, 1)
objectname := "Processor Information"
instances[0] = "_Total"
instances[1] = "0,0"
counters[0] = "Performance Limit Flags"
var expectedCounter = "Performance_Limit_Flags"
var measurement = "test"
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: false,
FailOnMissing: true,
IncludeTotal: false,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}}
var acc testutil.Accumulator
err := m.Gather(&acc)
require.NoError(t, err)
time.Sleep(2000 * time.Millisecond)
err = m.Gather(&acc)
require.NoError(t, err)
assert.Len(t, acc.Metrics, 4)
for _, metric := range acc.Metrics {
_, ok := metric.Fields[expectedCounter]
assert.True(t, ok)
}
}

View File

@@ -4,462 +4,528 @@ package win_perf_counters
import (
"errors"
"fmt"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
"time"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/require"
)
type testCounter struct {
handle PDH_HCOUNTER
path string
value float64
}
type FakePerformanceQuery struct {
counters map[string]testCounter
addEnglishSupported bool
expandPaths map[string][]string
openCalled bool
}
func TestWinPerfcountersConfigGet1(t *testing.T) {
func (m *FakePerformanceQuery) Open() error {
if m.openCalled {
err := m.Close()
if err != nil {
return err
}
}
m.openCalled = true
return nil
}
var instances = make([]string, 1)
var counters = make([]string, 1)
var perfobjects = make([]perfobject, 1)
func (m *FakePerformanceQuery) Close() error {
if !m.openCalled {
return errors.New("CloSe: uninitialised query")
}
m.openCalled = false
return nil
}
objectname := "Processor Information"
instances[0] = "_Total"
counters[0] = "% Processor Time"
func (m *FakePerformanceQuery) AddCounterToQuery(counterPath string) (PDH_HCOUNTER, error) {
if !m.openCalled {
return 0, errors.New("AddCounterToQuery: uninitialised query")
}
if c, ok := m.counters[counterPath]; ok {
return c.handle, nil
} else {
return 0, errors.New(fmt.Sprintf("AddCounterToQuery: invalid counter path: %s", counterPath))
}
}
var measurement string = "test"
var warnonmissing bool = false
var failonmissing bool = true
var includetotal bool = false
func (m *FakePerformanceQuery) AddEnglishCounterToQuery(counterPath string) (PDH_HCOUNTER, error) {
if !m.openCalled {
return 0, errors.New("AddEnglishCounterToQuery: uninitialised query")
}
if c, ok := m.counters[counterPath]; ok {
return c.handle, nil
} else {
return 0, fmt.Errorf("AddEnglishCounterToQuery: invalid counter path: %s", counterPath)
}
}
func (m *FakePerformanceQuery) GetCounterPath(counterHandle PDH_HCOUNTER) (string, error) {
for _, counter := range m.counters {
if counter.handle == counterHandle {
return counter.path, nil
}
}
return "", fmt.Errorf("GetCounterPath: invalid handle: %d", counterHandle)
}
func (m *FakePerformanceQuery) ExpandWildCardPath(counterPath string) ([]string, error) {
if e, ok := m.expandPaths[counterPath]; ok {
return e, nil
} else {
return []string{}, fmt.Errorf("ExpandWildCardPath: invalid counter path: %s", counterPath)
}
}
func (m *FakePerformanceQuery) GetFormattedCounterValueDouble(counterHandle PDH_HCOUNTER) (float64, error) {
if !m.openCalled {
return 0, errors.New("GetFormattedCounterValueDouble: uninitialised query")
}
for _, counter := range m.counters {
if counter.handle == counterHandle {
if counter.value > 0 {
return counter.value, nil
} else {
if counter.value == 0 {
return 0, NewPdhError(PDH_INVALID_DATA)
} else {
return 0, NewPdhError(PDH_CALC_NEGATIVE_VALUE)
}
}
}
}
return 0, fmt.Errorf("GetFormattedCounterValueDouble: invalid handle: %d", counterHandle)
}
func (m *FakePerformanceQuery) CollectData() error {
if !m.openCalled {
return errors.New("CollectData: uninitialised query")
}
return nil
}
func (m *FakePerformanceQuery) AddEnglishCounterSupported() bool {
return m.addEnglishSupported
}
func createPerfObject(measurement string, object string, instances []string, counters []string, failOnMissing bool, includeTotal bool) []perfobject {
PerfObject := perfobject{
ObjectName: object,
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: false,
FailOnMissing: failOnMissing,
IncludeTotal: includeTotal,
WarnOnMissing: warnonmissing,
FailOnMissing: failonmissing,
IncludeTotal: includetotal,
}
perfobjects := []perfobject{PerfObject}
return perfobjects
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects}
err := m.ParseConfig()
require.NoError(t, err)
}
func createCounterMap(counterPaths []string, values []float64) map[string]testCounter {
counters := make(map[string]testCounter)
for i, cp := range counterPaths {
counters[cp] = testCounter{
PDH_HCOUNTER(i),
cp,
values[i],
}
func TestWinPerfcountersConfigGet2(t *testing.T) {
var instances = make([]string, 1)
var counters = make([]string, 1)
var perfobjects = make([]perfobject, 1)
objectname := "Processor Information"
instances[0] = "_Total"
counters[0] = "% Processor Time"
var measurement string = "test"
var warnonmissing bool = false
var failonmissing bool = true
var includetotal bool = false
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: warnonmissing,
FailOnMissing: failonmissing,
IncludeTotal: includetotal,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects}
err := m.ParseConfig()
require.NoError(t, err)
var parsedItems = m.GetParsedItemsForTesting()
if len(parsedItems) == 1 {
require.NoError(t, nil)
} else if len(parsedItems) == 0 {
var errorstring1 string = "No results returned from the query: " + string(len(parsedItems))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
} else if len(parsedItems) > 1 {
var errorstring1 string = "Too many results returned from the query: " + string(len(parsedItems))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
}
return counters
}
func TestAddItemSimple(t *testing.T) {
var err error
cps1 := []string{"\\O(I)\\C"}
m := Win_PerfCounters{PrintValid: false, Object: nil, query: &FakePerformanceQuery{
counters: createCounterMap(cps1, []float64{1.1}),
expandPaths: map[string][]string{
cps1[0]: cps1,
},
addEnglishSupported: true,
}}
err = m.query.Open()
func TestWinPerfcountersConfigGet3(t *testing.T) {
var instances = make([]string, 1)
var counters = make([]string, 2)
var perfobjects = make([]perfobject, 1)
objectname := "Processor Information"
instances[0] = "_Total"
counters[0] = "% Processor Time"
counters[1] = "% Idle Time"
var measurement string = "test"
var warnonmissing bool = false
var failonmissing bool = true
var includetotal bool = false
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: warnonmissing,
FailOnMissing: failonmissing,
IncludeTotal: includetotal,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects}
err := m.ParseConfig()
require.NoError(t, err)
err = m.AddItem(cps1[0], "I", "test", false)
var parsedItems = m.GetParsedItemsForTesting()
if len(parsedItems) == 2 {
require.NoError(t, nil)
} else if len(parsedItems) < 2 {
var errorstring1 string = "Too few results returned from the query. " + string(len(parsedItems))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
} else if len(parsedItems) > 2 {
var errorstring1 string = "Too many results returned from the query: " + string(len(parsedItems))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
}
}
func TestWinPerfcountersConfigGet4(t *testing.T) {
var instances = make([]string, 2)
var counters = make([]string, 1)
var perfobjects = make([]perfobject, 1)
objectname := "Processor Information"
instances[0] = "_Total"
instances[1] = "0,1"
counters[0] = "% Processor Time"
var measurement string = "test"
var warnonmissing bool = false
var failonmissing bool = true
var includetotal bool = false
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: warnonmissing,
FailOnMissing: failonmissing,
IncludeTotal: includetotal,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects}
err := m.ParseConfig()
require.NoError(t, err)
err = m.query.Close()
var parsedItems = m.GetParsedItemsForTesting()
if len(parsedItems) == 2 {
require.NoError(t, nil)
} else if len(parsedItems) < 2 {
var errorstring1 string = "Too few results returned from the query: " + string(len(parsedItems))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
} else if len(parsedItems) > 2 {
var errorstring1 string = "Too many results returned from the query: " + string(len(parsedItems))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
}
}
func TestWinPerfcountersConfigGet5(t *testing.T) {
var instances = make([]string, 2)
var counters = make([]string, 2)
var perfobjects = make([]perfobject, 1)
objectname := "Processor Information"
instances[0] = "_Total"
instances[1] = "0,1"
counters[0] = "% Processor Time"
counters[1] = "% Idle Time"
var measurement string = "test"
var warnonmissing bool = false
var failonmissing bool = true
var includetotal bool = false
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: warnonmissing,
FailOnMissing: failonmissing,
IncludeTotal: includetotal,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects}
err := m.ParseConfig()
require.NoError(t, err)
var parsedItems = m.GetParsedItemsForTesting()
if len(parsedItems) == 4 {
require.NoError(t, nil)
} else if len(parsedItems) < 4 {
var errorstring1 string = "Too few results returned from the query: " +
string(len(parsedItems))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
} else if len(parsedItems) > 4 {
var errorstring1 string = "Too many results returned from the query: " +
string(len(parsedItems))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
}
}
func TestWinPerfcountersConfigGet6(t *testing.T) {
var instances = make([]string, 1)
var counters = make([]string, 1)
var perfobjects = make([]perfobject, 1)
objectname := "System"
instances[0] = "------"
counters[0] = "Context Switches/sec"
var measurement string = "test"
var warnonmissing bool = false
var failonmissing bool = true
var includetotal bool = false
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: warnonmissing,
FailOnMissing: failonmissing,
IncludeTotal: includetotal,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects}
err := m.ParseConfig()
require.NoError(t, err)
}
func TestAddItemInvalidCountPath(t *testing.T) {
var err error
cps1 := []string{"\\O\\C"}
m := Win_PerfCounters{PrintValid: false, Object: nil, query: &FakePerformanceQuery{
counters: createCounterMap(cps1, []float64{1.1}),
expandPaths: map[string][]string{
cps1[0]: {"\\O/C"},
},
addEnglishSupported: true,
}}
err = m.query.Open()
func TestWinPerfcountersConfigGet7(t *testing.T) {
var instances = make([]string, 1)
var counters = make([]string, 3)
var perfobjects = make([]perfobject, 1)
objectname := "Processor Information"
instances[0] = "_Total"
counters[0] = "% Processor Time"
counters[1] = "% Processor TimeERROR"
counters[2] = "% Idle Time"
var measurement string = "test"
var warnonmissing bool = false
var failonmissing bool = false
var includetotal bool = false
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: warnonmissing,
FailOnMissing: failonmissing,
IncludeTotal: includetotal,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects}
err := m.ParseConfig()
require.NoError(t, err)
err = m.AddItem("\\O\\C", "*", "test", false)
var parsedItems = m.GetParsedItemsForTesting()
if len(parsedItems) == 2 {
require.NoError(t, nil)
} else if len(parsedItems) < 2 {
var errorstring1 string = "Too few results returned from the query: " +
string(len(parsedItems))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
} else if len(parsedItems) > 2 {
var errorstring1 string = "Too many results returned from the query: " +
string(len(parsedItems))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
}
}
func TestWinPerfcountersConfigError1(t *testing.T) {
var instances = make([]string, 1)
var counters = make([]string, 1)
var perfobjects = make([]perfobject, 1)
objectname := "Processor InformationERROR"
instances[0] = "_Total"
counters[0] = "% Processor Time"
var measurement string = "test"
var warnonmissing bool = false
var failonmissing bool = true
var includetotal bool = false
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: warnonmissing,
FailOnMissing: failonmissing,
IncludeTotal: includetotal,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects}
err := m.ParseConfig()
require.Error(t, err)
err = m.query.Close()
require.NoError(t, err)
}
func TestParseConfigBasic(t *testing.T) {
var err error
perfObjects := createPerfObject("m", "O", []string{"I1", "I2"}, []string{"C1", "C2"}, false, false)
cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2"}
m := Win_PerfCounters{PrintValid: false, Object: perfObjects, query: &FakePerformanceQuery{
counters: createCounterMap(cps1, []float64{1.1, 1.2, 1.3, 1.4}),
expandPaths: map[string][]string{
cps1[0]: {cps1[0]},
cps1[1]: {cps1[1]},
cps1[2]: {cps1[2]},
cps1[3]: {cps1[3]},
},
addEnglishSupported: true,
}}
err = m.query.Open()
require.NoError(t, err)
err = m.ParseConfig()
require.NoError(t, err)
assert.Len(t, m.counters, 4)
err = m.query.Close()
require.NoError(t, err)
}
func TestWinPerfcountersConfigError2(t *testing.T) {
func TestParseConfigNoInstance(t *testing.T) {
var err error
perfObjects := createPerfObject("m", "O", []string{"------"}, []string{"C1", "C2"}, false, false)
cps1 := []string{"\\O\\C1", "\\O\\C2"}
m := Win_PerfCounters{PrintValid: false, Object: perfObjects, query: &FakePerformanceQuery{
counters: createCounterMap(cps1, []float64{1.1, 1.2}),
expandPaths: map[string][]string{
cps1[0]: {cps1[0]},
cps1[1]: {cps1[1]},
},
addEnglishSupported: true,
}}
err = m.query.Open()
require.NoError(t, err)
err = m.ParseConfig()
require.NoError(t, err)
assert.Len(t, m.counters, 2)
err = m.query.Close()
require.NoError(t, err)
}
var instances = make([]string, 1)
var counters = make([]string, 1)
var perfobjects = make([]perfobject, 1)
func TestParseConfigInvalidCounterError(t *testing.T) {
var err error
perfObjects := createPerfObject("m", "O", []string{"I1", "I2"}, []string{"C1", "C2"}, true, false)
cps1 := []string{"\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2"}
m := Win_PerfCounters{PrintValid: false, Object: perfObjects, query: &FakePerformanceQuery{
counters: createCounterMap(cps1, []float64{1.1, 1.2, 1.3}),
expandPaths: map[string][]string{
cps1[0]: {cps1[0]},
cps1[1]: {cps1[1]},
cps1[2]: {cps1[2]},
},
addEnglishSupported: true,
}}
err = m.query.Open()
require.NoError(t, err)
err = m.ParseConfig()
objectname := "Processor"
instances[0] = "SuperERROR"
counters[0] = "% C1 Time"
var measurement string = "test"
var warnonmissing bool = false
var failonmissing bool = true
var includetotal bool = false
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: warnonmissing,
FailOnMissing: failonmissing,
IncludeTotal: includetotal,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects}
err := m.ParseConfig()
require.Error(t, err)
err = m.query.Close()
require.NoError(t, err)
}
func TestParseConfigInvalidCounterNoError(t *testing.T) {
var err error
perfObjects := createPerfObject("m", "O", []string{"I1", "I2"}, []string{"C1", "C2"}, false, false)
cps1 := []string{"\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2"}
m := Win_PerfCounters{PrintValid: false, Object: perfObjects, query: &FakePerformanceQuery{
counters: createCounterMap(cps1, []float64{1.1, 1.2, 1.3}),
expandPaths: map[string][]string{
cps1[0]: {cps1[0]},
cps1[1]: {cps1[1]},
cps1[2]: {cps1[2]},
},
addEnglishSupported: true,
}}
err = m.query.Open()
require.NoError(t, err)
err = m.ParseConfig()
require.NoError(t, err)
err = m.query.Close()
require.NoError(t, err)
func TestWinPerfcountersConfigError3(t *testing.T) {
var instances = make([]string, 1)
var counters = make([]string, 1)
var perfobjects = make([]perfobject, 1)
objectname := "Processor Information"
instances[0] = "_Total"
counters[0] = "% Processor TimeERROR"
var measurement string = "test"
var warnonmissing bool = false
var failonmissing bool = true
var includetotal bool = false
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: warnonmissing,
FailOnMissing: failonmissing,
IncludeTotal: includetotal,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects}
err := m.ParseConfig()
require.Error(t, err)
}
func TestParseConfigTotal(t *testing.T) {
var err error
perfObjects := createPerfObject("m", "O", []string{"*"}, []string{"*"}, true, true)
cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(_Total)\\C1", "\\O(_Total)\\C2"}
m := Win_PerfCounters{PrintValid: false, Object: perfObjects, query: &FakePerformanceQuery{
counters: createCounterMap(append(cps1, "\\O(*)\\*"), []float64{1.1, 1.2, 1.3, 1.4, 0}),
expandPaths: map[string][]string{
"\\O(*)\\*": cps1,
},
addEnglishSupported: true,
}}
err = m.query.Open()
require.NoError(t, err)
err = m.ParseConfig()
require.NoError(t, err)
assert.Len(t, m.counters, 4)
err = m.query.Close()
func TestWinPerfcountersCollect1(t *testing.T) {
var instances = make([]string, 1)
var counters = make([]string, 1)
var perfobjects = make([]perfobject, 1)
objectname := "Processor Information"
instances[0] = "_Total"
counters[0] = "Parking Status"
var expectedCounter string = "Parking_Status"
var measurement string = "test"
var warnonmissing bool = false
var failonmissing bool = true
var includetotal bool = false
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: warnonmissing,
FailOnMissing: failonmissing,
IncludeTotal: includetotal,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects}
var acc testutil.Accumulator
err := m.Gather(&acc)
require.NoError(t, err)
perfObjects[0].IncludeTotal = false
time.Sleep(2000 * time.Millisecond)
err = m.Gather(&acc)
m = Win_PerfCounters{PrintValid: false, Object: perfObjects, query: &FakePerformanceQuery{
counters: createCounterMap(append(cps1, "\\O(*)\\*"), []float64{1.1, 1.2, 1.3, 1.4, 0}),
expandPaths: map[string][]string{
"\\O(*)\\*": cps1,
},
addEnglishSupported: true,
}}
err = m.query.Open()
require.NoError(t, err)
err = m.ParseConfig()
require.NoError(t, err)
assert.Len(t, m.counters, 2)
err = m.query.Close()
require.NoError(t, err)
}
func TestParseConfigExpand(t *testing.T) {
var err error
perfObjects := createPerfObject("m", "O", []string{"*"}, []string{"*"}, false, false)
cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2"}
m := Win_PerfCounters{PrintValid: false, Object: perfObjects, query: &FakePerformanceQuery{
counters: createCounterMap(append(cps1, "\\O(*)\\*"), []float64{1.1, 1.2, 1.3, 1.4, 0}),
expandPaths: map[string][]string{
"\\O(*)\\*": cps1,
},
addEnglishSupported: true,
}}
err = m.query.Open()
require.NoError(t, err)
err = m.ParseConfig()
require.NoError(t, err)
assert.Len(t, m.counters, 4)
err = m.query.Close()
require.NoError(t, err)
}
func TestSimpleGather(t *testing.T) {
var err error
if testing.Short() {
t.Skip("Skipping long taking test in short mode")
tags := map[string]string{
"instance": instances[0],
"objectname": objectname,
}
measurement := "test"
perfObjects := createPerfObject(measurement, "O", []string{"I"}, []string{"C"}, false, false)
cp1 := "\\O(I)\\C"
m := Win_PerfCounters{PrintValid: false, Object: perfObjects, query: &FakePerformanceQuery{
counters: createCounterMap([]string{cp1}, []float64{1.2}),
expandPaths: map[string][]string{
cp1: {cp1},
},
addEnglishSupported: false,
}}
var acc1 testutil.Accumulator
err = m.Gather(&acc1)
require.NoError(t, err)
fields1 := map[string]interface{}{
"C": float32(1.2),
fields := map[string]interface{}{
expectedCounter: float32(0),
}
tags1 := map[string]string{
"instance": "I",
"objectname": "O",
}
acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1)
}
func TestGatherInvalidDataIgnore(t *testing.T) {
var err error
if testing.Short() {
t.Skip("Skipping long taking test in short mode")
}
measurement := "test"
perfObjects := createPerfObject(measurement, "O", []string{"I"}, []string{"C1", "C2", "C3"}, false, false)
cps1 := []string{"\\O(I)\\C1", "\\O(I)\\C2", "\\O(I)\\C3"}
m := Win_PerfCounters{PrintValid: false, Object: perfObjects, query: &FakePerformanceQuery{
counters: createCounterMap(cps1, []float64{1.2, -1, 0}),
expandPaths: map[string][]string{
cps1[0]: {cps1[0]},
cps1[1]: {cps1[1]},
cps1[2]: {cps1[2]},
},
addEnglishSupported: false,
}}
var acc1 testutil.Accumulator
err = m.Gather(&acc1)
require.NoError(t, err)
fields1 := map[string]interface{}{
"C1": float32(1.2),
}
tags1 := map[string]string{
"instance": "I",
"objectname": "O",
}
acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1)
}
func TestGatherRefreshing(t *testing.T) {
var err error
if testing.Short() {
t.Skip("Skipping long taking test in short mode")
}
measurement := "test"
perfObjects := createPerfObject(measurement, "O", []string{"*"}, []string{"*"}, false, false)
cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2"}
fpm := &FakePerformanceQuery{
counters: createCounterMap(append(cps1, "\\O(*)\\*"), []float64{1.1, 1.2, 1.3, 1.4, 0}),
expandPaths: map[string][]string{
"\\O(*)\\*": cps1,
},
addEnglishSupported: true,
}
m := Win_PerfCounters{PrintValid: false, Object: perfObjects, query: fpm, CountersRefreshInterval: internal.Duration{Duration: time.Second * 10}}
var acc1 testutil.Accumulator
err = m.Gather(&acc1)
assert.Len(t, m.counters, 4)
require.NoError(t, err)
assert.Len(t, acc1.Metrics, 2)
fields1 := map[string]interface{}{
"C1": float32(1.1),
"C2": float32(1.2),
}
tags1 := map[string]string{
"instance": "I1",
"objectname": "O",
}
acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1)
fields2 := map[string]interface{}{
"C1": float32(1.3),
"C2": float32(1.4),
}
tags2 := map[string]string{
"instance": "I2",
"objectname": "O",
}
acc1.AssertContainsTaggedFields(t, measurement, fields2, tags2)
cps2 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2", "\\O(I3)\\C1", "\\O(I3)\\C2"}
fpm = &FakePerformanceQuery{
counters: createCounterMap(append(cps2, "\\O(*)\\*"), []float64{1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 0}),
expandPaths: map[string][]string{
"\\O(*)\\*": cps2,
},
addEnglishSupported: true,
}
m.query = fpm
fpm.Open()
var acc2 testutil.Accumulator
fields3 := map[string]interface{}{
"C1": float32(1.5),
"C2": float32(1.6),
}
tags3 := map[string]string{
"instance": "I3",
"objectname": "O",
}
//test before elapsing CounterRefreshRate counters are not refreshed
err = m.Gather(&acc2)
require.NoError(t, err)
assert.Len(t, m.counters, 4)
assert.Len(t, acc2.Metrics, 2)
acc2.AssertContainsTaggedFields(t, measurement, fields1, tags1)
acc2.AssertContainsTaggedFields(t, measurement, fields2, tags2)
acc2.AssertDoesNotContainsTaggedFields(t, measurement, fields3, tags3)
time.Sleep(m.CountersRefreshInterval.Duration)
var acc3 testutil.Accumulator
err = m.Gather(&acc3)
require.NoError(t, err)
assert.Len(t, acc3.Metrics, 3)
acc3.AssertContainsTaggedFields(t, measurement, fields1, tags1)
acc3.AssertContainsTaggedFields(t, measurement, fields2, tags2)
acc3.AssertContainsTaggedFields(t, measurement, fields3, tags3)
acc.AssertContainsTaggedFields(t, measurement, fields, tags)
}
func TestWinPerfcountersCollect2(t *testing.T) {
var instances = make([]string, 2)
var counters = make([]string, 1)
var perfobjects = make([]perfobject, 1)
objectname := "Processor Information"
instances[0] = "_Total"
instances[1] = "0,0"
counters[0] = "Performance Limit Flags"
var expectedCounter string = "Performance_Limit_Flags"
var measurement string = "test"
var warnonmissing bool = false
var failonmissing bool = true
var includetotal bool = false
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: warnonmissing,
FailOnMissing: failonmissing,
IncludeTotal: includetotal,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects}
var acc testutil.Accumulator
err := m.Gather(&acc)
require.NoError(t, err)
time.Sleep(2000 * time.Millisecond)
err = m.Gather(&acc)
tags := map[string]string{
"instance": instances[0],
"objectname": objectname,
}
fields := map[string]interface{}{
expectedCounter: float32(0),
}
acc.AssertContainsTaggedFields(t, measurement, fields, tags)
tags = map[string]string{
"instance": instances[1],
"objectname": objectname,
}
fields = map[string]interface{}{
expectedCounter: float32(0),
}
acc.AssertContainsTaggedFields(t, measurement, fields, tags)
}

View File

@@ -4,6 +4,7 @@ import (
_ "github.com/influxdata/telegraf/plugins/outputs/amon"
_ "github.com/influxdata/telegraf/plugins/outputs/amqp"
_ "github.com/influxdata/telegraf/plugins/outputs/application_insights"
_ "github.com/influxdata/telegraf/plugins/outputs/azuremonitor"
_ "github.com/influxdata/telegraf/plugins/outputs/cloudwatch"
_ "github.com/influxdata/telegraf/plugins/outputs/cratedb"
_ "github.com/influxdata/telegraf/plugins/outputs/datadog"

View File

@@ -2,82 +2,44 @@
This plugin writes to a AMQP 0-9-1 Exchange, a promenent implementation of this protocol being [RabbitMQ](https://www.rabbitmq.com/).
This plugin does not bind the exchange to a queue.
Metrics are written to a topic exchange using tag, defined in configuration file as RoutingTag, as a routing key.
If RoutingTag is empty, then empty routing key will be used.
Metrics are grouped in batches by RoutingTag.
This plugin doesn't bind exchange to a queue, so it should be done by consumer.
For an introduction to AMQP see:
- https://www.rabbitmq.com/tutorials/amqp-concepts.html
- https://www.rabbitmq.com/getstarted.html
### Configuration:
```toml
# Publishes metrics to an AMQP broker
```
# Configuration for the AMQP server to send metrics to
[[outputs.amqp]]
## Broker to publish to.
## deprecated in 1.7; use the brokers option
# url = "amqp://localhost:5672/influxdb"
## Brokers to publish to. If multiple brokers are specified a random broker
## will be selected anytime a connection is established. This can be
## helpful for load balancing when not using a dedicated load balancer.
brokers = ["amqp://localhost:5672/influxdb"]
## Maximum messages to send over a connection. Once this is reached, the
## connection is closed and a new connection is made. This can be helpful for
## load balancing when not using a dedicated load balancer.
# max_messages = 0
## Exchange to declare and publish to.
## AMQP url
url = "amqp://localhost:5672/influxdb"
## AMQP exchange
exchange = "telegraf"
## Exchange type; common types are "direct", "fanout", "topic", "header", "x-consistent-hash".
# exchange_type = "topic"
## If true, exchange will be passively declared.
# exchange_declare_passive = false
## If true, exchange will be created as a durable exchange.
# exchange_durable = true
## Additional exchange arguments.
# exchange_arguments = { }
# exchange_arguments = {"hash_propery" = "timestamp"}
## Authentication credentials for the PLAIN auth_method.
# username = ""
# password = ""
## Auth method. PLAIN and EXTERNAL are supported
## Using EXTERNAL requires enabling the rabbitmq_auth_mechanism_ssl plugin as
## described here: https://www.rabbitmq.com/plugins.html
# auth_method = "PLAIN"
## Metric tag to use as a routing key.
## ie, if this tag exists, its value will be used as the routing key
# routing_tag = "host"
## Static routing key. Used when no routing_tag is set or as a fallback
## when the tag specified in routing tag is not found.
# routing_key = ""
# routing_key = "telegraf"
## Delivery Mode controls if a published message is persistent.
## One of "transient" or "persistent".
## Telegraf tag to use as a routing key
## ie, if this tag exists, its value will be used as the routing key
routing_tag = "host"
## Delivery Mode controls if a published message is persistent
## Valid options are "transient" and "persistent". default: "transient"
# delivery_mode = "transient"
## InfluxDB database added as a message header.
## deprecated in 1.7; use the headers option
## InfluxDB retention policy
# retention_policy = "default"
## InfluxDB database
# database = "telegraf"
## InfluxDB retention policy added as a message header
## deprecated in 1.7; use the headers option
# retention_policy = "default"
## Static headers added to each published message.
# headers = { }
# headers = {"database" = "telegraf", "retention_policy" = "default"}
## Connection timeout. If not provided, will default to 5s. 0s means no
## timeout (not recommended).
## Write timeout, formatted as a string. If not provided, will default
## to 5s. 0s means no timeout (not recommended).
# timeout = "5s"
## Optional TLS Config
@@ -87,25 +49,9 @@ For an introduction to AMQP see:
## Use TLS but skip chain & host verification
# insecure_skip_verify = false
## If true use batch serialization format instead of line based delimiting.
## Only applies to data formats which are not line based such as JSON.
## Recommended to set to true.
# use_batch_format = false
## Data format to output.
## Each data format has its own unique set of configuration options, read
## more about them here:
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md
# data_format = "influx"
data_format = "influx"
```
#### Routing
If `routing_tag` is set, and the tag is defined on the metric, the value of
the tag is used as the routing key. Otherwise the value of `routing_key` is
used directly. If both are unset the empty string is used.
Exchange types that do not use a routing key, `direct` and `header`, always
use the empty string as the routing key.
Metrics are published in batches based on the final routing key.

View File

@@ -1,10 +1,11 @@
package amqp
import (
"bytes"
"fmt"
"log"
"net"
"strings"
"sync"
"time"
"github.com/influxdata/telegraf"
@@ -16,126 +17,80 @@ import (
"github.com/streadway/amqp"
)
const (
DefaultURL = "amqp://localhost:5672/influxdb"
DefaultAuthMethod = "PLAIN"
DefaultExchangeType = "topic"
DefaultRetentionPolicy = "default"
DefaultDatabase = "telegraf"
)
type client struct {
conn *amqp.Connection
channel *amqp.Channel
headers amqp.Table
}
type AMQP struct {
// AMQP brokers to send metrics to
URL string
// AMQP exchange
Exchange string
// AMQP Auth method
AuthMethod string
// Routing Key Tag
RoutingTag string `toml:"routing_tag"`
// InfluxDB database
Database string
// InfluxDB retention policy
RetentionPolicy string
// InfluxDB precision (DEPRECATED)
Precision string
// Connection timeout
Timeout internal.Duration
// Delivery Mode controls if a published message is persistent
// Valid options are "transient" and "persistent". default: "transient"
DeliveryMode string
tls.ClientConfig
sync.Mutex
c *client
deliveryMode uint8
serializer serializers.Serializer
}
type externalAuth struct{}
func (a *externalAuth) Mechanism() string {
return "EXTERNAL"
}
func (a *externalAuth) Response() string {
return fmt.Sprintf("\000")
}
type AMQP struct {
URL string `toml:"url"` // deprecated in 1.7; use brokers
Brokers []string `toml:"brokers"`
Exchange string `toml:"exchange"`
ExchangeType string `toml:"exchange_type"`
ExchangePassive bool `toml:"exchange_passive"`
ExchangeDurability string `toml:"exchange_durability"`
ExchangeArguments map[string]string `toml:"exchange_arguments"`
Username string `toml:"username"`
Password string `toml:"password"`
MaxMessages int `toml:"max_messages"`
AuthMethod string `toml:"auth_method"`
RoutingTag string `toml:"routing_tag"`
RoutingKey string `toml:"routing_key"`
DeliveryMode string `toml:"delivery_mode"`
Database string `toml:"database"` // deprecated in 1.7; use headers
RetentionPolicy string `toml:"retention_policy"` // deprecated in 1.7; use headers
Precision string `toml:"precision"` // deprecated; has no effect
Headers map[string]string `toml:"headers"`
Timeout internal.Duration `toml:"timeout"`
UseBatchFormat bool `toml:"use_batch_format"`
tls.ClientConfig
serializer serializers.Serializer
connect func(*ClientConfig) (Client, error)
client Client
config *ClientConfig
sentMessages int
}
type Client interface {
Publish(key string, body []byte) error
Close() error
}
const (
DefaultAuthMethod = "PLAIN"
DefaultRetentionPolicy = "default"
DefaultDatabase = "telegraf"
)
var sampleConfig = `
## Broker to publish to.
## deprecated in 1.7; use the brokers option
# url = "amqp://localhost:5672/influxdb"
## Brokers to publish to. If multiple brokers are specified a random broker
## will be selected anytime a connection is established. This can be
## helpful for load balancing when not using a dedicated load balancer.
brokers = ["amqp://localhost:5672/influxdb"]
## Maximum messages to send over a connection. Once this is reached, the
## connection is closed and a new connection is made. This can be helpful for
## load balancing when not using a dedicated load balancer.
# max_messages = 0
## Exchange to declare and publish to.
## AMQP url
url = "amqp://localhost:5672/influxdb"
## AMQP exchange
exchange = "telegraf"
## Exchange type; common types are "direct", "fanout", "topic", "header", "x-consistent-hash".
# exchange_type = "topic"
## If true, exchange will be passively declared.
# exchange_declare_passive = false
## If true, exchange will be created as a durable exchange.
# exchange_durable = true
## Additional exchange arguments.
# exchange_arguments = { }
# exchange_arguments = {"hash_propery" = "timestamp"}
## Authentication credentials for the PLAIN auth_method.
# username = ""
# password = ""
## Auth method. PLAIN and EXTERNAL are supported
## Using EXTERNAL requires enabling the rabbitmq_auth_mechanism_ssl plugin as
## described here: https://www.rabbitmq.com/plugins.html
# auth_method = "PLAIN"
## Telegraf tag to use as a routing key
## ie, if this tag exists, its value will be used as the routing key
routing_tag = "host"
## Delivery Mode controls if a published message is persistent
## Valid options are "transient" and "persistent". default: "transient"
delivery_mode = "transient"
## Metric tag to use as a routing key.
## ie, if this tag exists, its value will be used as the routing key
# routing_tag = "host"
## Static routing key. Used when no routing_tag is set or as a fallback
## when the tag specified in routing tag is not found.
# routing_key = ""
# routing_key = "telegraf"
## Delivery Mode controls if a published message is persistent.
## One of "transient" or "persistent".
# delivery_mode = "transient"
## InfluxDB database added as a message header.
## deprecated in 1.7; use the headers option
## InfluxDB retention policy
# retention_policy = "default"
## InfluxDB database
# database = "telegraf"
## InfluxDB retention policy added as a message header
## deprecated in 1.7; use the headers option
# retention_policy = "default"
## Static headers added to each published message.
# headers = { }
# headers = {"database" = "telegraf", "retention_policy" = "default"}
## Connection timeout. If not provided, will default to 5s. 0s means no
## timeout (not recommended).
## Write timeout, formatted as a string. If not provided, will default
## to 5s. 0s means no timeout (not recommended).
# timeout = "5s"
## Optional TLS Config
@@ -145,239 +100,194 @@ var sampleConfig = `
## Use TLS but skip chain & host verification
# insecure_skip_verify = false
## If true use batch serialization format instead of line based delimiting.
## Only applies to data formats which are not line based such as JSON.
## Recommended to set to true.
# use_batch_format = false
## Data format to output.
## Each data format has its own unique set of configuration options, read
## more about them here:
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md
# data_format = "influx"
data_format = "influx"
`
func (a *AMQP) SetSerializer(serializer serializers.Serializer) {
a.serializer = serializer
}
func (q *AMQP) Connect() error {
switch q.DeliveryMode {
case "transient":
q.deliveryMode = amqp.Transient
break
case "persistent":
q.deliveryMode = amqp.Persistent
break
default:
q.deliveryMode = amqp.Transient
break
}
headers := amqp.Table{
"database": q.Database,
"retention_policy": q.RetentionPolicy,
}
var connection *amqp.Connection
// make new tls config
tls, err := q.ClientConfig.TLSConfig()
if err != nil {
return err
}
// parse auth method
var sasl []amqp.Authentication // nil by default
if strings.ToUpper(q.AuthMethod) == "EXTERNAL" {
sasl = []amqp.Authentication{&externalAuth{}}
}
amqpConf := amqp.Config{
TLSClientConfig: tls,
SASL: sasl, // if nil, it will be PLAIN
Dial: func(network, addr string) (net.Conn, error) {
return net.DialTimeout(network, addr, q.Timeout.Duration)
},
}
connection, err = amqp.DialConfig(q.URL, amqpConf)
if err != nil {
return err
}
channel, err := connection.Channel()
if err != nil {
return fmt.Errorf("Failed to open a channel: %s", err)
}
err = channel.ExchangeDeclare(
q.Exchange, // name
"topic", // type
true, // durable
false, // delete when unused
false, // internal
false, // no-wait
nil, // arguments
)
if err != nil {
return fmt.Errorf("Failed to declare an exchange: %s", err)
}
q.setClient(&client{
conn: connection,
channel: channel,
headers: headers,
})
go func() {
err := <-connection.NotifyClose(make(chan *amqp.Error))
if err == nil {
return
}
q.setClient(nil)
log.Printf("I! Closing: %s", err)
log.Printf("I! Trying to reconnect")
for err := q.Connect(); err != nil; err = q.Connect() {
log.Println("E! ", err.Error())
time.Sleep(10 * time.Second)
}
}()
return nil
}
func (q *AMQP) Close() error {
c := q.getClient()
if c == nil {
return nil
}
err := c.conn.Close()
if err != nil && err != amqp.ErrClosed {
log.Printf("E! Error closing AMQP connection: %s", err)
return err
}
return nil
}
func (q *AMQP) SampleConfig() string {
return sampleConfig
}
func (q *AMQP) Description() string {
return "Publishes metrics to an AMQP broker"
}
func (q *AMQP) SetSerializer(serializer serializers.Serializer) {
q.serializer = serializer
}
func (q *AMQP) Connect() error {
if q.config == nil {
config, err := q.makeClientConfig()
if err != nil {
return err
}
q.config = config
}
client, err := q.connect(q.config)
if err != nil {
return err
}
q.client = client
return nil
}
func (q *AMQP) Close() error {
if q.client != nil {
return q.client.Close()
}
return nil
}
func (q *AMQP) routingKey(metric telegraf.Metric) string {
if q.RoutingTag != "" {
key, ok := metric.GetTag(q.RoutingTag)
if ok {
return key
}
}
return q.RoutingKey
return "Configuration for the AMQP server to send metrics to"
}
func (q *AMQP) Write(metrics []telegraf.Metric) error {
batches := make(map[string][]telegraf.Metric)
if q.ExchangeType == "direct" || q.ExchangeType == "header" {
// Since the routing_key is ignored for these exchange types send as a
// single batch.
batches[""] = metrics
} else {
for _, metric := range metrics {
routingKey := q.routingKey(metric)
if _, ok := batches[routingKey]; !ok {
batches[routingKey] = make([]telegraf.Metric, 0)
}
batches[routingKey] = append(batches[routingKey], metric)
}
if len(metrics) == 0 {
return nil
}
first := true
for key, metrics := range batches {
body, err := q.serialize(metrics)
c := q.getClient()
if c == nil {
return fmt.Errorf("connection is not open")
}
outbuf := make(map[string][]byte)
for _, metric := range metrics {
var key string
if q.RoutingTag != "" {
if h, ok := metric.Tags()[q.RoutingTag]; ok {
key = h
}
}
buf, err := q.serializer.Serialize(metric)
if err != nil {
return err
}
err = q.publish(key, body)
outbuf[key] = append(outbuf[key], buf...)
}
for key, buf := range outbuf {
// Note that since the channel is not in confirm mode, the absence of
// an error does not indicate successful delivery.
err := c.channel.Publish(
q.Exchange, // exchange
key, // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
Headers: c.headers,
ContentType: "text/plain",
Body: buf,
DeliveryMode: q.deliveryMode,
})
if err != nil {
// If this is the first attempt to publish and the connection is
// closed, try to reconnect and retry once.
if aerr, ok := err.(*amqp.Error); first && ok && aerr == amqp.ErrClosed {
first = false
q.client = nil
err := q.publish(key, body)
if err != nil {
return err
}
} else {
q.client = nil
return err
}
return fmt.Errorf("Failed to send AMQP message: %s", err)
}
first = false
}
if q.sentMessages >= q.MaxMessages && q.MaxMessages > 0 {
log.Printf("D! Output [amqp] sent MaxMessages; closing connection")
q.client = nil
}
return nil
}
func (q *AMQP) publish(key string, body []byte) error {
if q.client == nil {
client, err := q.connect(q.config)
if err != nil {
return err
}
q.sentMessages = 0
q.client = client
}
err := q.client.Publish(key, body)
if err != nil {
return err
}
q.sentMessages++
return nil
func (q *AMQP) getClient() *client {
q.Lock()
defer q.Unlock()
return q.c
}
func (q *AMQP) serialize(metrics []telegraf.Metric) ([]byte, error) {
if q.UseBatchFormat {
return q.serializer.SerializeBatch(metrics)
} else {
var buf bytes.Buffer
for _, metric := range metrics {
octets, err := q.serializer.Serialize(metric)
if err != nil {
return nil, err
}
_, err = buf.Write(octets)
if err != nil {
return nil, err
}
}
body := buf.Bytes()
return body, nil
}
}
func (q *AMQP) makeClientConfig() (*ClientConfig, error) {
config := &ClientConfig{
exchange: q.Exchange,
exchangeType: q.ExchangeType,
exchangePassive: q.ExchangePassive,
timeout: q.Timeout.Duration,
}
switch q.ExchangeDurability {
case "transient":
config.exchangeDurable = false
default:
config.exchangeDurable = true
}
config.brokers = q.Brokers
if len(config.brokers) == 0 {
config.brokers = []string{q.URL}
}
switch q.DeliveryMode {
case "transient":
config.deliveryMode = amqp.Transient
case "persistent":
config.deliveryMode = amqp.Persistent
default:
config.deliveryMode = amqp.Transient
}
if len(q.Headers) > 0 {
config.headers = make(amqp.Table, len(q.Headers))
for k, v := range q.Headers {
config.headers[k] = v
}
} else {
// Copy deprecated fields into message header
config.headers = amqp.Table{
"database": q.Database,
"retention_policy": q.RetentionPolicy,
}
}
if len(q.ExchangeArguments) > 0 {
config.exchangeArguments = make(amqp.Table, len(q.ExchangeArguments))
for k, v := range q.ExchangeArguments {
config.exchangeArguments[k] = v
}
}
tlsConfig, err := q.ClientConfig.TLSConfig()
if err != nil {
return nil, err
}
config.tlsConfig = tlsConfig
var auth []amqp.Authentication
if strings.ToUpper(q.AuthMethod) == "EXTERNAL" {
auth = []amqp.Authentication{&externalAuth{}}
} else if q.Username != "" || q.Password != "" {
auth = []amqp.Authentication{
&amqp.PlainAuth{
Username: q.Username,
Password: q.Password,
},
}
}
config.auth = auth
return config, nil
}
func connect(config *ClientConfig) (Client, error) {
return Connect(config)
func (q *AMQP) setClient(c *client) {
q.Lock()
q.c = c
q.Unlock()
}
func init() {
outputs.Add("amqp", func() telegraf.Output {
return &AMQP{
URL: DefaultURL,
ExchangeType: DefaultExchangeType,
AuthMethod: DefaultAuthMethod,
Database: DefaultDatabase,
RetentionPolicy: DefaultRetentionPolicy,
Timeout: internal.Duration{Duration: time.Second * 5},
connect: connect,
}
})
}

View File

@@ -2,161 +2,30 @@ package amqp
import (
"testing"
"time"
"github.com/influxdata/telegraf/internal"
"github.com/streadway/amqp"
"github.com/influxdata/telegraf/plugins/serializers"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/require"
)
type MockClient struct {
PublishF func(key string, body []byte) error
CloseF func() error
PublishCallCount int
CloseCallCount int
t *testing.T
}
func (c *MockClient) Publish(key string, body []byte) error {
c.PublishCallCount++
return c.PublishF(key, body)
}
func (c *MockClient) Close() error {
c.CloseCallCount++
return c.CloseF()
}
func MockConnect(config *ClientConfig) (Client, error) {
return &MockClient{}, nil
}
func NewMockClient() Client {
return &MockClient{
PublishF: func(key string, body []byte) error {
return nil
},
CloseF: func() error {
return nil
},
func TestConnectAndWrite(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
}
func TestConnect(t *testing.T) {
tests := []struct {
name string
output *AMQP
errFunc func(t *testing.T, output *AMQP, err error)
}{
{
name: "defaults",
output: &AMQP{
Brokers: []string{DefaultURL},
ExchangeType: DefaultExchangeType,
ExchangeDurability: "durable",
AuthMethod: DefaultAuthMethod,
Database: DefaultDatabase,
RetentionPolicy: DefaultRetentionPolicy,
Timeout: internal.Duration{Duration: time.Second * 5},
connect: func(config *ClientConfig) (Client, error) {
return NewMockClient(), nil
},
},
errFunc: func(t *testing.T, output *AMQP, err error) {
config := output.config
require.Equal(t, []string{DefaultURL}, config.brokers)
require.Equal(t, "", config.exchange)
require.Equal(t, "topic", config.exchangeType)
require.Equal(t, false, config.exchangePassive)
require.Equal(t, true, config.exchangeDurable)
require.Equal(t, amqp.Table(nil), config.exchangeArguments)
require.Equal(t, amqp.Table{
"database": DefaultDatabase,
"retention_policy": DefaultRetentionPolicy,
}, config.headers)
require.Equal(t, amqp.Transient, config.deliveryMode)
require.NoError(t, err)
},
},
{
name: "headers overrides deprecated dbrp",
output: &AMQP{
Headers: map[string]string{
"foo": "bar",
},
connect: func(config *ClientConfig) (Client, error) {
return NewMockClient(), nil
},
},
errFunc: func(t *testing.T, output *AMQP, err error) {
config := output.config
require.Equal(t, amqp.Table{
"foo": "bar",
}, config.headers)
require.NoError(t, err)
},
},
{
name: "exchange args",
output: &AMQP{
ExchangeArguments: map[string]string{
"foo": "bar",
},
connect: func(config *ClientConfig) (Client, error) {
return NewMockClient(), nil
},
},
errFunc: func(t *testing.T, output *AMQP, err error) {
config := output.config
require.Equal(t, amqp.Table{
"foo": "bar",
}, config.exchangeArguments)
require.NoError(t, err)
},
},
{
name: "username password",
output: &AMQP{
URL: "amqp://foo:bar@localhost",
Username: "telegraf",
Password: "pa$$word",
connect: func(config *ClientConfig) (Client, error) {
return NewMockClient(), nil
},
},
errFunc: func(t *testing.T, output *AMQP, err error) {
config := output.config
require.Equal(t, []amqp.Authentication{
&amqp.PlainAuth{
Username: "telegraf",
Password: "pa$$word",
},
}, config.auth)
var url = "amqp://" + testutil.GetLocalHost() + ":5672/"
s, _ := serializers.NewInfluxSerializer()
q := &AMQP{
URL: url,
Exchange: "telegraf_test",
serializer: s,
}
require.NoError(t, err)
},
},
{
name: "url support",
output: &AMQP{
URL: DefaultURL,
connect: func(config *ClientConfig) (Client, error) {
return NewMockClient(), nil
},
},
errFunc: func(t *testing.T, output *AMQP, err error) {
config := output.config
require.Equal(t, []string{DefaultURL}, config.brokers)
require.NoError(t, err)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.output.Connect()
tt.errFunc(t, tt.output, err)
})
}
// Verify that we can connect to the AMQP broker
err := q.Connect()
require.NoError(t, err)
// Verify that we can successfully write data to the amqp broker
err = q.Write(testutil.MockMetrics())
require.NoError(t, err)
}

View File

@@ -1,134 +0,0 @@
package amqp
import (
"crypto/tls"
"errors"
"fmt"
"log"
"math/rand"
"net"
"time"
"github.com/streadway/amqp"
)
type ClientConfig struct {
brokers []string
exchange string
exchangeType string
exchangePassive bool
exchangeDurable bool
exchangeArguments amqp.Table
headers amqp.Table
deliveryMode uint8
tlsConfig *tls.Config
timeout time.Duration
auth []amqp.Authentication
}
type client struct {
conn *amqp.Connection
channel *amqp.Channel
config *ClientConfig
}
// Connect opens a connection to one of the brokers at random
func Connect(config *ClientConfig) (*client, error) {
client := &client{
config: config,
}
p := rand.Perm(len(config.brokers))
for _, n := range p {
broker := config.brokers[n]
log.Printf("D! Output [amqp] connecting to %q", broker)
conn, err := amqp.DialConfig(
broker, amqp.Config{
TLSClientConfig: config.tlsConfig,
SASL: config.auth, // if nil, it will be PLAIN taken from url
Dial: func(network, addr string) (net.Conn, error) {
return net.DialTimeout(network, addr, config.timeout)
},
})
if err == nil {
client.conn = conn
log.Printf("D! Output [amqp] connected to %q", broker)
break
}
log.Printf("D! Output [amqp] error connecting to %q", broker)
}
if client.conn == nil {
return nil, errors.New("could not connect to any broker")
}
channel, err := client.conn.Channel()
if err != nil {
return nil, fmt.Errorf("error opening channel: %v", err)
}
client.channel = channel
err = client.DeclareExchange()
if err != nil {
return nil, err
}
return client, nil
}
func (c *client) DeclareExchange() error {
var err error
if c.config.exchangePassive {
err = c.channel.ExchangeDeclarePassive(
c.config.exchange,
c.config.exchangeType,
c.config.exchangeDurable,
false, // delete when unused
false, // internal
false, // no-wait
c.config.exchangeArguments,
)
} else {
err = c.channel.ExchangeDeclare(
c.config.exchange,
c.config.exchangeType,
c.config.exchangeDurable,
false, // delete when unused
false, // internal
false, // no-wait
c.config.exchangeArguments,
)
}
if err != nil {
return fmt.Errorf("error declaring exchange: %v", err)
}
return nil
}
func (c *client) Publish(key string, body []byte) error {
// Note that since the channel is not in confirm mode, the absence of
// an error does not indicate successful delivery.
return c.channel.Publish(
c.config.exchange, // exchange
key, // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
Headers: c.config.headers,
ContentType: "text/plain",
Body: body,
DeliveryMode: c.config.deliveryMode,
})
}
func (c *client) Close() error {
if c.conn == nil {
return nil
}
err := c.conn.Close()
if err != nil && err != amqp.ErrClosed {
return err
}
return nil
}

View File

@@ -0,0 +1,74 @@
## Azure Monitor Custom Metrics Output for Telegraf
This plugin will send custom metrics to Azure Monitor.
All metrics are written as summarized values: min, max, sum, count. The Telegraf field name is appended to the metric name. All Telegraf tags are set as the metric dimensions.
## Azure Authentication
This plugin can use one of several different types of credentials to authenticate
with the Azure Monitor Custom Metrics ingestion API endpoint. In the following
order the plugin will attempt to authenticate.
1. Managed Service Identity (MSI) token
- This is the prefered authentication method.
- Note: MSI is only available to ARM-based resources.
2. AAD Application Tokens (Service Principals)
- Primarily useful if Telegraf is writing metrics for other resources. [More information](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-application-objects).
- A Service Principal or User Principal needs to be assigned the `Monitoring Contributor` roles.
3. AAD User Tokens (User Principals)
- Allows Telegraf to authenticate like a user. It is best to use this method for development.
## Config
For this output plugin to function correctly the following variables
must be configured.
* resourceId
* region
### region
The region is the Azure region that you wish to connect to.
Examples include but are not limited to:
* useast
* centralus
* westcentralus
* westeurope
* southeastasia
### resourceId
The resourceId used for Azure Monitor metrics.
### Configuration:
```
# Configuration for sending aggregate metrics to Azure Monitor
[[outputs.azuremonitor]]
## The resource ID against which metric will be logged. If not
## specified, the plugin will attempt to retrieve the resource ID
## of the VM via the instance metadata service (optional if running
## on an Azure VM with MSI)
#resource_id = "/subscriptions/<subscription_id>/resourceGroups/<resource_group>/providers/Microsoft.Compute/virtualMachines/<vm_name>"
## Azure region to publish metrics against. Defaults to eastus.
## Leave blank to automatically query the region via MSI.
#region = "useast"
## Write HTTP timeout, formatted as a string. If not provided, will default
## to 5s. 0s means no timeout (not recommended).
# timeout = "5s"
## Whether or not to use managed service identity.
#useManagedServiceIdentity = true
## Fill in the following values if using Active Directory Service
## Principal or User Principal for authentication.
## Subscription ID
#azureSubscription = ""
## Tenant ID
#azureTenant = ""
## Client ID
#azureClientId = ""
## Client secrete
#azureClientSecret = ""
```

View File

@@ -0,0 +1,186 @@
package azuremonitor
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"time"
)
// VirtualMachineMetadata contains information about a VM from the metadata service
type VirtualMachineMetadata struct {
Raw string
AzureResourceID string
Compute struct {
Location string `json:"location"`
Name string `json:"name"`
Offer string `json:"offer"`
OsType string `json:"osType"`
PlacementGroupID string `json:"placementGroupId"`
PlatformFaultDomain string `json:"platformFaultDomain"`
PlatformUpdateDomain string `json:"platformUpdateDomain"`
Publisher string `json:"publisher"`
ResourceGroupName string `json:"resourceGroupName"`
Sku string `json:"sku"`
SubscriptionID string `json:"subscriptionId"`
Tags string `json:"tags"`
Version string `json:"version"`
VMID string `json:"vmId"`
VMScaleSetName string `json:"vmScaleSetName"`
VMSize string `json:"vmSize"`
Zone string `json:"zone"`
} `json:"compute"`
Network struct {
Interface []struct {
Ipv4 struct {
IPAddress []struct {
PrivateIPAddress string `json:"privateIpAddress"`
PublicIPAddress string `json:"publicIpAddress"`
} `json:"ipAddress"`
Subnet []struct {
Address string `json:"address"`
Prefix string `json:"prefix"`
} `json:"subnet"`
} `json:"ipv4"`
Ipv6 struct {
IPAddress []interface{} `json:"ipAddress"`
} `json:"ipv6"`
MacAddress string `json:"macAddress"`
} `json:"interface"`
} `json:"network"`
}
// msiToken is the managed service identity token
type msiToken struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresIn string `json:"expires_in"`
ExpiresOn string `json:"expires_on"`
NotBefore string `json:"not_before"`
Resource string `json:"resource"`
TokenType string `json:"token_type"`
expiresAt time.Time
notBefore time.Time
raw string
}
func (m *msiToken) parseTimes() {
val, err := strconv.ParseInt(m.ExpiresOn, 10, 64)
if err == nil {
m.expiresAt = time.Unix(val, 0)
}
val, err = strconv.ParseInt(m.NotBefore, 10, 64)
if err == nil {
m.notBefore = time.Unix(val, 0)
}
}
// ExpiresInDuration returns the duration until the token expires
func (m *msiToken) expiresInDuration() time.Duration {
expiresDuration := m.expiresAt.Sub(time.Now().UTC())
return expiresDuration
}
// GetMsiToken retrieves a managed service identity token from the specified port on the local VM
func (a *AzureMonitor) getMsiToken(clientID string) (*msiToken, error) {
// Acquire an MSI token. Documented at:
// https://docs.microsoft.com/en-us/azure/active-directory/managed-service-identity/how-to-use-vm-token
//
//GET http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fmanagement.azure.com%2F&client_id=712eac09-e943-418c-9be6-9fd5c91078bl HTTP/1.1 Metadata: true
// Create HTTP request for MSI token to access Azure Resource Manager
var msiEndpoint *url.URL
msiEndpoint, err := url.Parse(msiInstanceMetadataURL)
if err != nil {
return nil, err
}
msiParameters := url.Values{}
// Resource ID defaults to https://management.azure.com
msiParameters.Add("resource", defaultMSIResource)
msiParameters.Add("api-version", "2018-02-01")
// Client id is optional
if clientID != "" {
msiParameters.Add("client_id", clientID)
}
msiEndpoint.RawQuery = msiParameters.Encode()
req, err := http.NewRequest("GET", msiEndpoint.String(), nil)
if err != nil {
return nil, err
}
req.Header.Add("Metadata", "true")
resp, err := a.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
reply, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode >= 300 || resp.StatusCode < 200 {
return nil, fmt.Errorf("E! Get Error. %d HTTP response: %s response body: %s",
resp.StatusCode, resp.Status, reply)
}
var token msiToken
if err := json.Unmarshal(reply, &token); err != nil {
return nil, err
}
token.parseTimes()
token.raw = string(reply)
return &token, nil
}
// GetInstanceMetadata retrieves metadata about the current Azure VM
func (a *AzureMonitor) GetInstanceMetadata() error {
req, err := http.NewRequest("GET", vmInstanceMetadataURL, nil)
if err != nil {
return fmt.Errorf("Error creating HTTP request")
}
req.Header.Set("Metadata", "true")
resp, err := a.client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
reply, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode >= 300 || resp.StatusCode < 200 {
return fmt.Errorf("Post Error. HTTP response code:%d message:%s reply:\n%s",
resp.StatusCode, resp.Status, reply)
}
var metadata VirtualMachineMetadata
if err := json.Unmarshal(reply, &metadata); err != nil {
return err
}
if a.ResourceID == "" {
a.ResourceID = fmt.Sprintf(resourceIDTemplate,
metadata.Compute.SubscriptionID, metadata.Compute.ResourceGroupName, metadata.Compute.Name)
}
if a.Region == "" {
a.Region = metadata.Compute.Location
}
a.url = fmt.Sprintf(urlTemplate, a.Region, a.ResourceID)
return nil
}

View File

@@ -0,0 +1,445 @@
package azuremonitor
import (
"bytes"
"encoding/binary"
"encoding/json"
"fmt"
"hash/fnv"
"io/ioutil"
"log"
"net/http"
"regexp"
"time"
"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/metric"
"github.com/influxdata/telegraf/plugins/outputs"
)
var _ telegraf.AggregatingOutput = (*AzureMonitor)(nil)
var _ telegraf.Output = (*AzureMonitor)(nil)
// AzureMonitor allows publishing of metrics to the Azure Monitor custom metrics service
type AzureMonitor struct {
useMsi bool `toml:"use_managed_service_identity"`
ResourceID string `toml:"resource_id"`
Region string `toml:"region"`
Timeout internal.Duration `toml:"Timeout"`
AzureSubscriptionID string `toml:"azure_subscription"`
AzureTenantID string `toml:"azure_tenant"`
AzureClientID string `toml:"azure_client_id"`
AzureClientSecret string `toml:"azure_client_secret"`
StringAsDimension bool `toml:"string_as_dimension"`
url string
msiToken *msiToken
oauthConfig *adal.OAuthConfig
adalToken adal.OAuthTokenProvider
client *http.Client
cache map[time.Time]map[uint64]*aggregate
}
type aggregate struct {
telegraf.Metric
updated bool
}
const (
defaultRegion string = "eastus"
defaultMSIResource string = "https://monitoring.azure.com/"
urlTemplate string = "https://%s.monitoring.azure.com%s/metrics"
resourceIDTemplate string = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/virtualMachines/%s"
vmInstanceMetadataURL string = "http://169.254.169.254/metadata/instance?api-version=2017-12-01"
msiInstanceMetadataURL string = "http://169.254.169.254/metadata/identity/oauth2/token"
)
var sampleConfig = `
## The resource ID against which metric will be logged. If not
## specified, the plugin will attempt to retrieve the resource ID
## of the VM via the instance metadata service (optional if running
## on an Azure VM with MSI)
#resource_id = "/subscriptions/<subscription_id>/resourceGroups/<resource_group>/providers/Microsoft.Compute/virtualMachines/<vm_name>"
## Azure region to publish metrics against. Defaults to eastus.
## Leave blank to automatically query the region via MSI.
#region = "useast"
## Write HTTP timeout, formatted as a string. If not provided, will default
## to 5s. 0s means no timeout (not recommended).
# timeout = "5s"
## Whether or not to use managed service identity.
#use_managed_service_identity = true
## Fill in the following values if using Active Directory Service
## Principal or User Principal for authentication.
## Subscription ID
#azure_subscription = ""
## Tenant ID
#azure_tenant = ""
## Client ID
#azure_client_id = ""
## Client secrete
#azure_client_secret = ""
`
// Description provides a description of the plugin
func (a *AzureMonitor) Description() string {
return "Configuration for sending aggregate metrics to Azure Monitor"
}
// SampleConfig provides a sample configuration for the plugin
func (a *AzureMonitor) SampleConfig() string {
return sampleConfig
}
// Connect initializes the plugin and validates connectivity
func (a *AzureMonitor) Connect() error {
a.client = &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
},
Timeout: a.Timeout.Duration,
}
// If no direct AD values provided, fall back to MSI
if a.AzureSubscriptionID == "" && a.AzureTenantID == "" && a.AzureClientID == "" && a.AzureClientSecret == "" {
a.useMsi = true
} else if a.AzureSubscriptionID == "" || a.AzureTenantID == "" || a.AzureClientID == "" || a.AzureClientSecret == "" {
return fmt.Errorf("E! Must provide values for azure_subscription, azure_tenant, azure_client and azure_client_secret, or leave all blank to default to MSI")
}
if !a.useMsi {
// If using direct AD authentication create the AD access client
oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, a.AzureTenantID)
if err != nil {
return fmt.Errorf("E! Could not initialize AD client: %s", err)
}
a.oauthConfig = oauthConfig
}
// Pull region and resource identifier
err := a.GetInstanceMetadata()
if err != nil && a.ResourceID == "" && a.Region == "" {
return fmt.Errorf("E! No resource id specified, and Azure Instance metadata service not available. If not running on an Azure VM, provide a value for resource_id")
}
err = a.validateCredentials()
if err != nil {
return fmt.Errorf("E! Unable to fetch authentication credentials: %v", err)
}
a.Reset()
return nil
}
func (a *AzureMonitor) validateCredentials() error {
if a.useMsi {
// Check expiry on the token
if a.msiToken == nil || a.msiToken.expiresInDuration() < time.Minute {
msiToken, err := a.getMsiToken(a.AzureClientID)
if err != nil {
return err
}
a.msiToken = msiToken
}
return nil
}
adToken, err := adal.NewServicePrincipalToken(
*(a.oauthConfig), a.AzureClientID, a.AzureClientSecret,
azure.PublicCloud.ActiveDirectoryEndpoint)
if err != nil {
return fmt.Errorf("Could not acquire ADAL token: %s", err)
}
a.adalToken = adToken
return nil
}
// Close shuts down an any active connections
func (a *AzureMonitor) Close() error {
a.client = nil
return nil
}
type azureMonitorMetric struct {
Time time.Time `json:"time"`
Data *azureMonitorData `json:"data"`
}
type azureMonitorData struct {
BaseData *azureMonitorBaseData `json:"baseData"`
}
type azureMonitorBaseData struct {
Metric string `json:"metric"`
Namespace string `json:"namespace"`
DimensionNames []string `json:"dimNames"`
Series []*azureMonitorSeries `json:"series"`
}
type azureMonitorSeries struct {
DimensionValues []string `json:"dimValues"`
Min float64 `json:"min"`
Max float64 `json:"max"`
Sum float64 `json:"sum"`
Count int64 `json:"count"`
}
// Write writes metrics to the remote endpoint
func (a *AzureMonitor) Write(metrics []telegraf.Metric) error {
azmetrics := make(map[uint64]*azureMonitorMetric, len(metrics))
for _, m := range metrics {
id := hashIDWithTagKeysOnly(m)
if azm, ok := azmetrics[id]; !ok {
azmetrics[id] = translate(m)
} else {
azmetrics[id].Data.BaseData.Series = append(
azm.Data.BaseData.Series,
translate(m).Data.BaseData.Series...,
)
}
}
var body []byte
for _, m := range azmetrics {
// Azure Monitor accepts new batches of points in new-line delimited
// JSON, following RFC 4288 (see https://github.com/ndjson/ndjson-spec).
jsonBytes, err := json.Marshal(&m)
if err != nil {
return err
}
body = append(body, jsonBytes...)
body = append(body, '\n')
}
if err := a.validateCredentials(); err != nil {
return fmt.Errorf("E! Unable to fetch authentication credentials: %v", err)
}
req, err := http.NewRequest("POST", a.url, bytes.NewBuffer(body))
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+a.msiToken.AccessToken)
req.Header.Set("Content-Type", "application/x-ndjson")
resp, err := a.client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 300 || resp.StatusCode < 200 {
reply, err := ioutil.ReadAll(resp.Body)
if err != nil {
reply = nil
}
return fmt.Errorf("E! Get Error. %d HTTP response: %s response body: %s",
resp.StatusCode, resp.Status, reply)
}
return nil
}
func hashIDWithTagKeysOnly(m telegraf.Metric) uint64 {
h := fnv.New64a()
h.Write([]byte(m.Name()))
h.Write([]byte("\n"))
for _, tag := range m.TagList() {
h.Write([]byte(tag.Key))
h.Write([]byte("\n"))
}
b := make([]byte, binary.MaxVarintLen64)
n := binary.PutUvarint(b, uint64(m.Time().UnixNano()))
h.Write(b[:n])
h.Write([]byte("\n"))
return h.Sum64()
}
func translate(m telegraf.Metric) *azureMonitorMetric {
var dimensionNames []string
var dimensionValues []string
for i, tag := range m.TagList() {
// Azure custom metrics service supports up to 10 dimensions
if i > 10 {
log.Printf("W! [outputs.azuremonitor] metric [%s] exceeds 10 dimensions", m.Name())
continue
}
dimensionNames = append(dimensionNames, tag.Key)
dimensionValues = append(dimensionValues, tag.Value)
}
min, _ := m.GetField("min")
max, _ := m.GetField("max")
sum, _ := m.GetField("sum")
count, _ := m.GetField("count")
return &azureMonitorMetric{
Time: m.Time(),
Data: &azureMonitorData{
BaseData: &azureMonitorBaseData{
Metric: m.Name(),
Namespace: "default",
DimensionNames: dimensionNames,
Series: []*azureMonitorSeries{
&azureMonitorSeries{
DimensionValues: dimensionValues,
Min: min.(float64),
Max: max.(float64),
Sum: sum.(float64),
Count: count.(int64),
},
},
},
},
}
}
// Add will append a metric to the output aggregate
func (a *AzureMonitor) Add(m telegraf.Metric) {
// Azure Monitor only supports aggregates 30 minutes into the past
// and 4 minutes into the future. Future metrics are dropped when pushed.
t := m.Time()
tbucket := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), 0, 0, t.Location())
if tbucket.Before(time.Now().Add(-time.Minute * 30)) {
log.Printf("W! attempted to aggregate metric over 30 minutes old: %v, %v", t, tbucket)
return
}
// Azure Monitor doesn't have a string value type, so convert string
// fields to dimensions (a.k.a. tags) if enabled.
if a.StringAsDimension {
for fk, fv := range m.Fields() {
if v, ok := fv.(string); ok {
m.AddTag(fk, v)
}
}
}
for _, f := range m.FieldList() {
fv, ok := convert(f.Value)
if !ok {
continue
}
// Azure Monitor does not support fields so the field
// name is appended to the metric name.
name := m.Name() + "_" + sanitize(f.Key)
id := hashIDWithField(m.HashID(), f.Key)
_, ok = a.cache[tbucket]
if !ok {
// Time bucket does not exist and needs to be created.
a.cache[tbucket] = make(map[uint64]*aggregate)
}
nf := make(map[string]interface{}, 4)
nf["min"] = fv
nf["max"] = fv
nf["sum"] = fv
nf["count"] = 1
// Fetch existing aggregate
agg, ok := a.cache[tbucket][id]
if ok {
aggfields := agg.Fields()
if fv > aggfields["min"].(float64) {
nf["min"] = aggfields["min"]
}
if fv < aggfields["max"].(float64) {
nf["max"] = aggfields["max"]
}
nf["sum"] = fv + aggfields["sum"].(float64)
nf["count"] = aggfields["count"].(int64) + 1
}
na, _ := metric.New(name, m.Tags(), nf, tbucket)
a.cache[tbucket][id] = &aggregate{na, true}
}
}
func convert(in interface{}) (float64, bool) {
switch v := in.(type) {
case int64:
return float64(v), true
case uint64:
return float64(v), true
case float64:
return v, true
case bool:
if v {
return 1, true
}
return 0, true
default:
return 0, false
}
}
var invalidNameCharRE = regexp.MustCompile(`[^a-zA-Z0-9_]`)
func sanitize(value string) string {
return invalidNameCharRE.ReplaceAllString(value, "_")
}
func hashIDWithField(id uint64, fk string) uint64 {
h := fnv.New64a()
b := make([]byte, binary.MaxVarintLen64)
n := binary.PutUvarint(b, id)
h.Write(b[:n])
h.Write([]byte("\n"))
h.Write([]byte(fk))
h.Write([]byte("\n"))
return h.Sum64()
}
// Push sends metrics to the output metric buffer
func (a *AzureMonitor) Push() []telegraf.Metric {
var metrics []telegraf.Metric
for tbucket, aggs := range a.cache {
// Do not send metrics early
if tbucket.After(time.Now().Add(-time.Minute)) {
continue
}
for _, agg := range aggs {
// Only send aggregates that have had an update since
// the last push.
if !agg.updated {
continue
}
metrics = append(metrics, agg.Metric)
}
}
return metrics
}
// Reset clears the cache of aggregate metrics
func (a *AzureMonitor) Reset() {
for tbucket := range a.cache {
// Remove aggregates older than 30 minutes
if tbucket.Before(time.Now().Add(-time.Minute * 30)) {
delete(a.cache, tbucket)
continue
}
for id := range a.cache[tbucket] {
a.cache[tbucket][id].updated = false
}
}
}
func init() {
outputs.Add("azuremonitor", func() telegraf.Output {
return &AzureMonitor{
StringAsDimension: false,
Timeout: internal.Duration{Duration: time.Second * 5},
Region: defaultRegion,
cache: make(map[time.Time]map[uint64]*aggregate, 36),
}
})
}

View File

@@ -0,0 +1,40 @@
package azuremonitor
import (
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/metric"
)
// MockMetrics returns a mock []telegraf.Metric object for using in unit tests
// of telegraf output sinks.
func getMockMetrics() []telegraf.Metric {
metrics := make([]telegraf.Metric, 0)
// Create a new point batch
metrics = append(metrics, getTestMetric(1.0))
return metrics
}
// TestMetric Returns a simple test point:
// measurement -> "test1" or name
// tags -> "tag1":"value1"
// value -> value
// time -> time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
func getTestMetric(value interface{}, name ...string) telegraf.Metric {
if value == nil {
panic("Cannot use a nil value")
}
measurement := "test1"
if len(name) > 0 {
measurement = name[0]
}
tags := map[string]string{"tag1": "value1"}
pt, _ := metric.New(
measurement,
tags,
map[string]interface{}{"value": value},
time.Now().UTC(),
)
return pt
}

View File

@@ -177,8 +177,6 @@ func BuildMetricDatum(point telegraf.Metric) []*cloudwatch.MetricDatum {
value = float64(t)
case int64:
value = float64(t)
case uint64:
value = float64(t)
case float64:
value = t
case bool:

View File

@@ -83,6 +83,10 @@ func (f *File) Description() string {
}
func (f *File) Write(metrics []telegraf.Metric) error {
if len(metrics) == 0 {
return nil
}
var writeErr error = nil
for _, metric := range metrics {
b, err := f.serializer.Serialize(metric)

View File

@@ -22,7 +22,6 @@ const (
func TestFileExistingFile(t *testing.T) {
fh := createFile()
defer os.Remove(fh.Name())
s, _ := serializers.NewInfluxSerializer()
f := File{
Files: []string{fh.Name()},
@@ -44,7 +43,6 @@ func TestFileExistingFile(t *testing.T) {
func TestFileNewFile(t *testing.T) {
s, _ := serializers.NewInfluxSerializer()
fh := tmpFile()
defer os.Remove(fh)
f := File{
Files: []string{fh},
serializer: s,
@@ -64,11 +62,8 @@ func TestFileNewFile(t *testing.T) {
func TestFileExistingFiles(t *testing.T) {
fh1 := createFile()
defer os.Remove(fh1.Name())
fh2 := createFile()
defer os.Remove(fh2.Name())
fh3 := createFile()
defer os.Remove(fh3.Name())
s, _ := serializers.NewInfluxSerializer()
f := File{
@@ -93,11 +88,8 @@ func TestFileExistingFiles(t *testing.T) {
func TestFileNewFiles(t *testing.T) {
s, _ := serializers.NewInfluxSerializer()
fh1 := tmpFile()
defer os.Remove(fh1)
fh2 := tmpFile()
defer os.Remove(fh2)
fh3 := tmpFile()
defer os.Remove(fh3)
f := File{
Files: []string{fh1, fh2, fh3},
serializer: s,
@@ -119,9 +111,7 @@ func TestFileNewFiles(t *testing.T) {
func TestFileBoth(t *testing.T) {
fh1 := createFile()
defer os.Remove(fh1.Name())
fh2 := tmpFile()
defer os.Remove(fh2)
s, _ := serializers.NewInfluxSerializer()
f := File{

View File

@@ -21,6 +21,11 @@ data formats. For data_formats that support batching, metrics are sent in batch
# username = "username"
# password = "pa$$word"
## Additional HTTP headers
# [outputs.http.headers]
# # Should be set manually to "application/json" for json data_format
# Content-Type = "text/plain; charset=utf-8"
## Optional TLS Config
# tls_ca = "/etc/telegraf/ca.pem"
# tls_cert = "/etc/telegraf/cert.pem"
@@ -33,9 +38,4 @@ data formats. For data_formats that support batching, metrics are sent in batch
## more about them here:
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md
# data_format = "influx"
## Additional HTTP headers
# [outputs.http.headers]
# # Should be set manually to "application/json" for json data_format
# Content-Type = "text/plain; charset=utf-8"
```

View File

@@ -29,6 +29,11 @@ var sampleConfig = `
# username = "username"
# password = "pa$$word"
## Additional HTTP headers
# [outputs.http.headers]
# # Should be set to "application/json" for json data_format
# Content-Type = "text/plain; charset=utf-8"
## Optional TLS Config
# tls_ca = "/etc/telegraf/ca.pem"
# tls_cert = "/etc/telegraf/cert.pem"
@@ -41,11 +46,6 @@ var sampleConfig = `
## more about them here:
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md
# data_format = "influx"
## Additional HTTP headers
# [outputs.http.headers]
# # Should be set manually to "application/json" for json data_format
# Content-Type = "text/plain; charset=utf-8"
`
const (

View File

@@ -140,17 +140,17 @@ func (i *InfluxDB) Connect() error {
i.serializer.SetFieldTypeSupport(influx.UintSupport)
}
for _, loc := range urls {
u, err := url.Parse(loc)
for _, u := range urls {
u, err := url.Parse(u)
if err != nil {
return fmt.Errorf("error parsing url [%q]: %v", loc, err)
return fmt.Errorf("error parsing url [%s]: %v", u, err)
}
var proxy *url.URL
if len(i.HTTPProxy) > 0 {
proxy, err = url.Parse(i.HTTPProxy)
if err != nil {
return fmt.Errorf("error parsing proxy_url [%q]: %v", proxy, err)
return fmt.Errorf("error parsing proxy_url [%s]: %v", proxy, err)
}
}
@@ -170,7 +170,7 @@ func (i *InfluxDB) Connect() error {
i.clients = append(i.clients, c)
default:
return fmt.Errorf("unsupported scheme [%q]: %q", u, u.Scheme)
return fmt.Errorf("unsupported scheme [%s]: %q", u, u.Scheme)
}
}
@@ -209,14 +209,14 @@ func (i *InfluxDB) Write(metrics []telegraf.Metric) error {
if apiError.Type == DatabaseNotFound {
err := client.CreateDatabase(ctx)
if err != nil {
log.Printf("E! [outputs.influxdb] when writing to [%q]: database %q not found and failed to recreate",
log.Printf("E! [outputs.influxdb] when writing to [%s]: database %q not found and failed to recreate",
client.URL(), client.Database())
}
}
}
}
log.Printf("E! [outputs.influxdb]: when writing to [%q]: %v", client.URL(), err)
log.Printf("E! [outputs.influxdb]: when writing to [%s]: %v", client.URL(), err)
}
return errors.New("could not write any address")
@@ -231,7 +231,7 @@ func (i *InfluxDB) udpClient(url *url.URL) (Client, error) {
c, err := i.CreateUDPClientF(config)
if err != nil {
return nil, fmt.Errorf("error creating UDP client [%q]: %v", url, err)
return nil, fmt.Errorf("error creating UDP client [%s]: %v", url, err)
}
return c, nil
@@ -261,13 +261,13 @@ func (i *InfluxDB) httpClient(ctx context.Context, url *url.URL, proxy *url.URL)
c, err := i.CreateHTTPClientF(config)
if err != nil {
return nil, fmt.Errorf("error creating HTTP client [%q]: %v", url, err)
return nil, fmt.Errorf("error creating HTTP client [%s]: %v", url, err)
}
if !i.SkipDatabaseCreation {
err = c.CreateDatabase(ctx)
if err != nil {
log.Printf("W! [outputs.influxdb] when writing to [%q]: database %q creation failed: %v",
log.Printf("W! [outputs.influxdb] when writing to [%s]: database %q creation failed: %v",
c.URL(), c.Database(), err)
}
}

View File

@@ -137,7 +137,6 @@ func (sw *SocketWriter) Write(metrics []telegraf.Metric) error {
// permanent error. close the connection
sw.Close()
sw.Conn = nil
return fmt.Errorf("closing connection: %v", err)
}
return err
}

View File

@@ -3,10 +3,8 @@ package socket_writer
import (
"bufio"
"bytes"
"io/ioutil"
"net"
"os"
"path/filepath"
"sync"
"testing"
@@ -46,16 +44,13 @@ func TestSocketWriter_udp(t *testing.T) {
}
func TestSocketWriter_unix(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "telegraf")
require.NoError(t, err)
defer os.RemoveAll(tmpdir)
sock := filepath.Join(tmpdir, "socket_writer.TestSocketWriter_unix.sock")
listener, err := net.Listen("unix", sock)
os.Remove("/tmp/telegraf_test.sock")
defer os.Remove("/tmp/telegraf_test.sock")
listener, err := net.Listen("unix", "/tmp/telegraf_test.sock")
require.NoError(t, err)
sw := newSocketWriter()
sw.Address = "unix://" + sock
sw.Address = "unix:///tmp/telegraf_test.sock"
err = sw.Connect()
require.NoError(t, err)
@@ -67,16 +62,13 @@ func TestSocketWriter_unix(t *testing.T) {
}
func TestSocketWriter_unixgram(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "telegraf")
require.NoError(t, err)
defer os.RemoveAll(tmpdir)
sock := filepath.Join(tmpdir, "socket_writer.TestSocketWriter_unixgram.sock")
listener, err := net.ListenPacket("unixgram", sock)
os.Remove("/tmp/telegraf_test.sock")
defer os.Remove("/tmp/telegraf_test.sock")
listener, err := net.ListenPacket("unixgram", "/tmp/telegraf_test.sock")
require.NoError(t, err)
sw := newSocketWriter()
sw.Address = "unixgram://" + sock
sw.Address = "unixgram:///tmp/telegraf_test.sock"
err = sw.Connect()
require.NoError(t, err)

View File

@@ -95,7 +95,7 @@ supported_packages = {
"freebsd": [ "tar" ]
}
next_version = '1.8.0'
next_version = '1.7.0'
################
#### Telegraf Functions