diff --git a/envoy/CHANGELOG.md b/envoy/CHANGELOG.md new file mode 100644 index 0000000000000..433a07a873e2e --- /dev/null +++ b/envoy/CHANGELOG.md @@ -0,0 +1,10 @@ +# CHANGELOG - Envoy + +1.0.0 / Unreleased +================== + +### Changes + +* [FEATURE] add Envoy integration. See #1156 + + diff --git a/envoy/MANIFEST.in b/envoy/MANIFEST.in new file mode 100644 index 0000000000000..03fd6943f6ea7 --- /dev/null +++ b/envoy/MANIFEST.in @@ -0,0 +1,6 @@ +include README.md +include requirements.in +include requirements.txt +include requirements-dev.txt +graft datadog_checks +graft tests diff --git a/envoy/README.md b/envoy/README.md new file mode 100644 index 0000000000000..f8078da5c6194 --- /dev/null +++ b/envoy/README.md @@ -0,0 +1,59 @@ +# Agent Check: Envoy +## Overview + +This check collects distributed system observability metrics from [Envoy](https://www.envoyproxy.io). + +## Setup +### Installation + +The Envoy check is packaged with the Agent, so simply [install the Agent](https://app.datadoghq.com/account/settings#agent) on your server. + +If you need the newest version of the Envoy check, install the `dd-check-envoy` package; this package's check overrides the one packaged with the Agent. See the [integrations-core repository README.md for more details](https://docs.datadoghq.com/agent/faq/install-core-extra/). + +### Configuration + +Create a file `envoy.yaml` in the Datadog Agent's `conf.d` directory. See the [sample envoy.yaml](https://github.com/DataDog/integrations-core/blob/master/envoy/conf.yaml.example) for all available configuration options: + +### Validation + +[Run the Agent's `status` subcommand](https://docs.datadoghq.com/agent/faq/agent-commands/#agent-status-and-information) and look for `envoy` under the Checks section: + +``` + Checks + ====== + [...] + + envoy + ----- + - instance #0 [OK] + - Collected 244 metrics, 0 events & 1 service check + + [...] +``` + +## Compatibility + +The Envoy check is compatible with all platforms. + +## Data Collected +### Metrics + +See [metadata.csv](https://github.com/DataDog/integrations-core/blob/master/envoy/metadata.csv) for a list of metrics provided by this check. +See [metrics.py](https://github.com/DataDog/integrations-core/blob/master/envoy/datadog_checks/envoy/metrics.py) for a list of tags sent by each metric. + +### Events + +The Envoy check does not include any events at this time. + +### Service Checks + +`envoy.can_connect`: + +Returns CRITICAL if the Agent cannot connect to Envoy to collect metrics, otherwise OK. + +## Troubleshooting + +Need help? Contact [Datadog Support](http://docs.datadoghq.com/help/). + +## Further Reading +Learn more about infrastructure monitoring and all our integrations on [our blog](https://www.datadoghq.com/blog/) diff --git a/envoy/conf.yaml.example b/envoy/conf.yaml.example new file mode 100644 index 0000000000000..34b55943f5e4c --- /dev/null +++ b/envoy/conf.yaml.example @@ -0,0 +1,33 @@ +# This file is overwritten upon Agent upgrade. +# To make modifications to the check configuration, please copy this file +# to `envoy.yaml` and make your changes on that file. + +init_config: + +instances: + # For every instance, you need a `stats_url` and can optionally + # supply a list of tags. The admin endpoint must be accessible. + # https://www.envoyproxy.io/docs/envoy/latest/operations/admin + + - stats_url: http://localhost:80/stats + + # tags: + # - instance:foo + + # If the stats page is behind basic auth: + # username: USERNAME + # password: PASSWORD + + # The (optional) verify_ssl parameter will instruct the check to validate SSL + # certificates when connecting to Envoy. Defaulting to true, set to false if + # you want to disable SSL certificate validation. + # + # verify_ssl: true + + # The (optional) skip_proxy parameter will bypass any proxy + # settings enabled and attempt to reach Envoy directly. + # + # skip_proxy: false + + # If you need to specify a custom timeout in seconds (default is 20): + # timeout: 20 diff --git a/envoy/datadog_checks/__init__.py b/envoy/datadog_checks/__init__.py new file mode 100644 index 0000000000000..c5254131af1f1 --- /dev/null +++ b/envoy/datadog_checks/__init__.py @@ -0,0 +1,4 @@ +# (C) Datadog, Inc. 2018 +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) +__path__ = __import__('pkgutil').extend_path(__path__, __name__) diff --git a/envoy/datadog_checks/envoy/__about__.py b/envoy/datadog_checks/envoy/__about__.py new file mode 100644 index 0000000000000..eabee830b2b1e --- /dev/null +++ b/envoy/datadog_checks/envoy/__about__.py @@ -0,0 +1,5 @@ +# (C) Datadog, Inc. 2018 +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) + +__version__ = '1.0.0' diff --git a/envoy/datadog_checks/envoy/__init__.py b/envoy/datadog_checks/envoy/__init__.py new file mode 100644 index 0000000000000..8ce2c7c03d70a --- /dev/null +++ b/envoy/datadog_checks/envoy/__init__.py @@ -0,0 +1,10 @@ +# (C) Datadog, Inc. 2018 +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) +from .__about__ import __version__ +from .envoy import Envoy + +__all__ = [ + '__version__', + 'Envoy' +] diff --git a/envoy/datadog_checks/envoy/envoy.py b/envoy/datadog_checks/envoy/envoy.py new file mode 100644 index 0000000000000..57a7959627ead --- /dev/null +++ b/envoy/datadog_checks/envoy/envoy.py @@ -0,0 +1,72 @@ +# (C) Datadog, Inc. 2018 +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) +import requests + +from datadog_checks.checks import AgentCheck + +from .errors import UnknownMetric +from .parser import parse_metric + + +class Envoy(AgentCheck): + SERVICE_CHECK_NAME = 'envoy.can_connect' + + def check(self, instance): + custom_tags = instance.get('tags', []) + + try: + stats_url = instance['stats_url'] + except KeyError: + msg = 'Envoy configuration setting `stats_url` is required' + self.service_check(self.SERVICE_CHECK_NAME, AgentCheck.CRITICAL, message=msg, tags=custom_tags) + self.log.error(msg) + return + + username = instance.get('username', None) + password = instance.get('password', None) + auth = (username, password) if username and password else None + verify_ssl = instance.get('verify_ssl', True) + proxies = self.get_instance_proxy(instance, stats_url) + timeout = int(instance.get('timeout', 20)) + + try: + request = requests.get( + stats_url, auth=auth, verify=verify_ssl, proxies=proxies, timeout=timeout + ) + except requests.exceptions.Timeout: + msg = 'Envoy endpoint `{}` timed out after {} seconds'.format(stats_url, timeout) + self.service_check(self.SERVICE_CHECK_NAME, AgentCheck.CRITICAL, message=msg, tags=custom_tags) + self.log.exception(msg) + return + except (requests.exceptions.RequestException, requests.exceptions.ConnectionError): + msg = 'Error accessing Envoy endpoint `{}`'.format(stats_url) + self.service_check(self.SERVICE_CHECK_NAME, AgentCheck.CRITICAL, message=msg, tags=custom_tags) + self.log.exception(msg) + return + + if request.status_code != 200: + msg = 'Envoy endpoint `{}` responded with HTTP status code {}'.format(stats_url, request.status_code) + self.service_check(self.SERVICE_CHECK_NAME, AgentCheck.CRITICAL, message=msg, tags=custom_tags) + self.log.warning(msg) + return + + get_method = getattr + for line in request.content.decode().splitlines(): + try: + envoy_metric, value = line.split(': ') + except ValueError: + continue + + value = int(value) + + try: + metric, tags, method = parse_metric(envoy_metric) + except UnknownMetric: + self.log.warning('Unknown metric `{}`'.format(envoy_metric)) + continue + + tags.extend(custom_tags) + get_method(self, method)(metric, value, tags=tags) + + self.service_check(self.SERVICE_CHECK_NAME, AgentCheck.OK, tags=custom_tags) diff --git a/envoy/datadog_checks/envoy/errors.py b/envoy/datadog_checks/envoy/errors.py new file mode 100644 index 0000000000000..926ca9eccdcf8 --- /dev/null +++ b/envoy/datadog_checks/envoy/errors.py @@ -0,0 +1,2 @@ +class UnknownMetric(Exception): + pass diff --git a/envoy/datadog_checks/envoy/metrics.py b/envoy/datadog_checks/envoy/metrics.py new file mode 100644 index 0000000000000..976943d8fd277 --- /dev/null +++ b/envoy/datadog_checks/envoy/metrics.py @@ -0,0 +1,1228 @@ +from .utils import make_metric_tree + +METRIC_PREFIX = 'envoy.' + +METRICS = { + 'runtime.load_error': { + 'tags': (), + 'method': 'count', + }, + 'runtime.override_dir_not_exists': { + 'tags': (), + 'method': 'count', + }, + 'runtime.override_dir_exists': { + 'tags': (), + 'method': 'count', + }, + 'runtime.load_success': { + 'tags': (), + 'method': 'count', + }, + 'runtime.num_keys': { + 'tags': (), + 'method': 'gauge', + }, + 'cluster_manager.cds.config_reload': { + 'tags': (), + 'method': 'count', + }, + 'cluster_manager.cds.update_attempt': { + 'tags': (), + 'method': 'count', + }, + 'cluster_manager.cds.update_success': { + 'tags': (), + 'method': 'count', + }, + 'cluster_manager.cds.update_failure': { + 'tags': (), + 'method': 'count', + }, + 'cluster_manager.cds.version': { + 'tags': (), + 'method': 'gauge', + }, + 'http.no_route': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.no_cluster': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.rq_redirect': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.rq_total': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'vhost.vcluster.upstream_rq_1xx': { + 'tags': ('virtual_host_name', 'virtual_cluster_name', ), + 'method': 'count', + }, + 'vhost.vcluster.upstream_rq_2xx': { + 'tags': ('virtual_host_name', 'virtual_cluster_name', ), + 'method': 'count', + }, + 'vhost.vcluster.upstream_rq_3xx': { + 'tags': ('virtual_host_name', 'virtual_cluster_name', ), + 'method': 'count', + }, + 'vhost.vcluster.upstream_rq_4xx': { + 'tags': ('virtual_host_name', 'virtual_cluster_name', ), + 'method': 'count', + }, + 'vhost.vcluster.upstream_rq_5xx': { + 'tags': ('virtual_host_name', 'virtual_cluster_name', ), + 'method': 'count', + }, + 'vhost.vcluster.upstream_rq_time': { + 'tags': ('virtual_host_name', 'virtual_cluster_name', ), + 'method': 'histogram', + }, + 'cluster.ratelimit.ok': { + 'tags': ('route_target_cluster', ), + 'method': 'count', + }, + 'cluster.ratelimit.error': { + 'tags': ('route_target_cluster', ), + 'method': 'count', + }, + 'cluster.ratelimit.over_limit': { + 'tags': ('route_target_cluster', ), + 'method': 'count', + }, + 'http.ip_tagging.hit': { + 'tags': ('stat_prefix', 'tag_name', ), + 'method': 'count', + }, + 'http.ip_tagging.no_hit': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.ip_tagging.total': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'cluster.grpc.success': { + 'tags': ('route_target_cluster', 'grpc_service', 'grpc_method', ), + 'method': 'count', + }, + 'cluster.grpc.failure': { + 'tags': ('route_target_cluster', 'grpc_service', 'grpc_method', ), + 'method': 'count', + }, + 'cluster.grpc.total': { + 'tags': ('route_target_cluster', 'grpc_service', 'grpc_method', ), + 'method': 'count', + }, + 'http.dynamodb.operation.upstream_rq_total': { + 'tags': ('stat_prefix', 'operation_name', ), + 'method': 'count', + }, + 'http.dynamodb.operation.upstream_rq_time': { + 'tags': ('stat_prefix', 'operation_name', ), + 'method': 'histogram', + }, + 'http.dynamodb.table.upstream_rq_total': { + 'tags': ('stat_prefix', 'table_name', ), + 'method': 'count', + }, + 'http.dynamodb.table.upstream_rq_time': { + 'tags': ('stat_prefix', 'table_name', ), + 'method': 'histogram', + }, + 'http.dynamodb.error': { + 'tags': ('stat_prefix', 'table_name', 'error_type', ), + 'method': 'count', + }, + 'http.dynamodb.error.BatchFailureUnprocessedKeys': { + 'tags': ('stat_prefix', 'table_name', ), + 'method': 'count', + }, + 'http.buffer.rq_timeout': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.rds.config_reload': { + 'tags': ('stat_prefix', 'route_config_name', ), + 'method': 'count', + }, + 'http.rds.update_attempt': { + 'tags': ('stat_prefix', 'route_config_name', ), + 'method': 'count', + }, + 'http.rds.update_success': { + 'tags': ('stat_prefix', 'route_config_name', ), + 'method': 'count', + }, + 'http.rds.update_failure': { + 'tags': ('stat_prefix', 'route_config_name', ), + 'method': 'count', + }, + 'http.rds.version': { + 'tags': ('stat_prefix', 'route_config_name', ), + 'method': 'gauge', + }, + 'tcp.downstream_cx_total': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'tcp.downstream_cx_no_route': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'tcp.downstream_cx_tx_bytes_total': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'tcp.downstream_cx_tx_bytes_buffered': { + 'tags': ('stat_prefix', ), + 'method': 'gauge', + }, + 'tcp.downstream_flow_control_paused_reading_total': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'tcp.downstream_flow_control_resumed_reading_total': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'auth.clientssl.update_success': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'auth.clientssl.update_failure': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'auth.clientssl.auth_no_ssl': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'auth.clientssl.auth_ip_white_list': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'auth.clientssl.auth_digest_match': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'auth.clientssl.auth_digest_no_match': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'auth.clientssl.total_principals': { + 'tags': ('stat_prefix', ), + 'method': 'gauge', + }, + 'ratelimit.total': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'ratelimit.error': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'ratelimit.over_limit': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'ratelimit.ok': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'ratelimit.cx_closed': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'ratelimit.active': { + 'tags': ('stat_prefix', ), + 'method': 'gauge', + }, + 'redis.downstream_cx_active': { + 'tags': ('stat_prefix', ), + 'method': 'gauge', + }, + 'redis.downstream_cx_protocol_error': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'redis.downstream_cx_rx_bytes_buffered': { + 'tags': ('stat_prefix', ), + 'method': 'gauge', + }, + 'redis.downstream_cx_rx_bytes_total': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'redis.downstream_cx_total': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'redis.downstream_cx_tx_bytes_buffered': { + 'tags': ('stat_prefix', ), + 'method': 'gauge', + }, + 'redis.downstream_cx_tx_bytes_total': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'redis.downstream_cx_drain_close': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'redis.downstream_rq_active': { + 'tags': ('stat_prefix', ), + 'method': 'gauge', + }, + 'redis.downstream_rq_total': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'redis.splitter.invalid_request': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'redis.splitter.unsupported_command': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'redis.command.total': { + 'tags': ('stat_prefix', 'command', ), + 'method': 'count', + }, + 'mongo.decoding_error': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'mongo.delay_injected': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'mongo.op_get_more': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'mongo.op_insert': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'mongo.op_kill_cursors': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'mongo.op_query': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'mongo.op_query_tailable_cursor': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'mongo.op_query_no_cursor_timeout': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'mongo.op_query_await_data': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'mongo.op_query_exhaust': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'mongo.op_query_no_max_time': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'mongo.op_query_scatter_get': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'mongo.op_query_multi_get': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'mongo.op_query_active': { + 'tags': ('stat_prefix', ), + 'method': 'gauge', + }, + 'mongo.op_reply': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'mongo.op_reply_cursor_not_found': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'mongo.op_reply_query_failure': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'mongo.op_reply_valid_cursor': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'mongo.cx_destroy_local_with_active_rq': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'mongo.cx_destroy_remote_with_active_rq': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'mongo.cx_drain_close': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'mongo.cmd.total': { + 'tags': ('stat_prefix', 'cmd', ), + 'method': 'count', + }, + 'mongo.cmd.reply_num_docs': { + 'tags': ('stat_prefix', 'cmd', ), + 'method': 'histogram', + }, + 'mongo.cmd.reply_size': { + 'tags': ('stat_prefix', 'cmd', ), + 'method': 'histogram', + }, + 'mongo.cmd.reply_time_ms': { + 'tags': ('stat_prefix', 'cmd', ), + 'method': 'histogram', + }, + 'mongo.collection.query.total': { + 'tags': ('stat_prefix', 'collection', ), + 'method': 'count', + }, + 'mongo.collection.query.scatter_get': { + 'tags': ('stat_prefix', 'collection', ), + 'method': 'count', + }, + 'mongo.collection.query.multi_get': { + 'tags': ('stat_prefix', 'collection', ), + 'method': 'count', + }, + 'mongo.collection.query.reply_num_docs': { + 'tags': ('stat_prefix', 'collection', ), + 'method': 'histogram', + }, + 'mongo.collection.query.reply_size': { + 'tags': ('stat_prefix', 'collection', ), + 'method': 'histogram', + }, + 'mongo.collection.query.reply_time_ms': { + 'tags': ('stat_prefix', 'collection', ), + 'method': 'histogram', + }, + 'mongo.collection.callsite.query.total': { + 'tags': ('stat_prefix', 'collection', 'callsite', ), + 'method': 'count', + }, + 'mongo.collection.callsite.query.scatter_get': { + 'tags': ('stat_prefix', 'collection', 'callsite', ), + 'method': 'count', + }, + 'mongo.collection.callsite.query.multi_get': { + 'tags': ('stat_prefix', 'collection', 'callsite', ), + 'method': 'count', + }, + 'mongo.collection.callsite.query.reply_num_docs': { + 'tags': ('stat_prefix', 'collection', 'callsite', ), + 'method': 'histogram', + }, + 'mongo.collection.callsite.query.reply_size': { + 'tags': ('stat_prefix', 'collection', 'callsite', ), + 'method': 'histogram', + }, + 'mongo.collection.callsite.query.reply_time_ms': { + 'tags': ('stat_prefix', 'collection', 'callsite', ), + 'method': 'histogram', + }, + 'listener.downstream_cx_total': { + 'tags': ('address', ), + 'method': 'count', + }, + 'listener.downstream_cx_destroy': { + 'tags': ('address', ), + 'method': 'count', + }, + 'listener.downstream_cx_active': { + 'tags': ('address', ), + 'method': 'gauge', + }, + 'listener.downstream_cx_length_ms': { + 'tags': ('address', ), + 'method': 'histogram', + }, + 'listener.ssl.connection_error': { + 'tags': ('address', ), + 'method': 'count', + }, + 'listener.ssl.handshake': { + 'tags': ('address', ), + 'method': 'count', + }, + 'listener.ssl.session_reused': { + 'tags': ('address', ), + 'method': 'count', + }, + 'listener.ssl.no_certificate': { + 'tags': ('address', ), + 'method': 'count', + }, + 'listener.ssl.fail_no_sni_match': { + 'tags': ('address', ), + 'method': 'count', + }, + 'listener.ssl.fail_verify_no_cert': { + 'tags': ('address', ), + 'method': 'count', + }, + 'listener.ssl.fail_verify_error': { + 'tags': ('address', ), + 'method': 'count', + }, + 'listener.ssl.fail_verify_san': { + 'tags': ('address', ), + 'method': 'count', + }, + 'listener.ssl.fail_verify_cert_hash': { + 'tags': ('address', ), + 'method': 'count', + }, + 'listener.ssl.cipher': { + 'tags': ('address', 'cipher', ), + 'method': 'count', + }, + 'listener_manager.listener_added': { + 'tags': (), + 'method': 'count', + }, + 'listener_manager.listener_modified': { + 'tags': (), + 'method': 'count', + }, + 'listener_manager.listener_removed': { + 'tags': (), + 'method': 'count', + }, + 'listener_manager.listener_create_success': { + 'tags': (), + 'method': 'count', + }, + 'listener_manager.listener_create_failure': { + 'tags': (), + 'method': 'count', + }, + 'listener_manager.total_listeners_warming': { + 'tags': (), + 'method': 'gauge', + }, + 'listener_manager.total_listeners_active': { + 'tags': (), + 'method': 'gauge', + }, + 'listener_manager.total_listeners_draining': { + 'tags': (), + 'method': 'gauge', + }, + 'http.downstream_cx_total': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_cx_ssl_total': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_cx_http1_total': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_cx_websocket_total': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_cx_http2_total': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_cx_destroy': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_cx_destroy_remote': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_cx_destroy_local': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_cx_destroy_active_rq': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_cx_destroy_local_active_rq': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_cx_destroy_remote_active_rq': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_cx_active': { + 'tags': ('stat_prefix', ), + 'method': 'gauge', + }, + 'http.downstream_cx_ssl_active': { + 'tags': ('stat_prefix', ), + 'method': 'gauge', + }, + 'http.downstream_cx_http1_active': { + 'tags': ('stat_prefix', ), + 'method': 'gauge', + }, + 'http.downstream_cx_websocket_active': { + 'tags': ('stat_prefix', ), + 'method': 'gauge', + }, + 'http.downstream_cx_http2_active': { + 'tags': ('stat_prefix', ), + 'method': 'gauge', + }, + 'http.downstream_cx_protocol_error': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_cx_length_ms': { + 'tags': ('stat_prefix', ), + 'method': 'histogram', + }, + 'http.downstream_cx_rx_bytes_total': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_cx_rx_bytes_buffered': { + 'tags': ('stat_prefix', ), + 'method': 'gauge', + }, + 'http.downstream_cx_tx_bytes_total': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_cx_tx_bytes_buffered': { + 'tags': ('stat_prefix', ), + 'method': 'gauge', + }, + 'http.downstream_cx_drain_close': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_cx_idle_timeout': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_flow_control_paused_reading_total': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_flow_control_resumed_reading_total': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_rq_total': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_rq_http1_total': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_rq_http2_total': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_rq_active': { + 'tags': ('stat_prefix', ), + 'method': 'gauge', + }, + 'http.downstream_rq_response_before_rq_complete': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_rq_rx_reset': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_rq_tx_reset': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_rq_non_relative_path': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_rq_too_large': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_rq_1xx': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_rq_2xx': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_rq_3xx': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_rq_4xx': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_rq_5xx': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_rq_ws_on_non_ws_route': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.downstream_rq_time': { + 'tags': ('stat_prefix', ), + 'method': 'histogram', + }, + 'http.rs_too_large': { + 'tags': ('stat_prefix', ), + 'method': 'count', + }, + 'http.user_agent.downstream_cx_total': { + 'tags': ('stat_prefix', 'user_agent', ), + 'method': 'count', + }, + 'http.user_agent.downstream_cx_destroy_remote_active_rq': { + 'tags': ('stat_prefix', 'user_agent', ), + 'method': 'count', + }, + 'http.user_agent.downstream_rq_total': { + 'tags': ('stat_prefix', 'user_agent', ), + 'method': 'count', + }, + 'listener.http.downstream_rq_1xx': { + 'tags': ('address', 'stat_prefix', ), + 'method': 'count', + }, + 'listener.http.downstream_rq_2xx': { + 'tags': ('address', 'stat_prefix', ), + 'method': 'count', + }, + 'listener.http.downstream_rq_3xx': { + 'tags': ('address', 'stat_prefix', ), + 'method': 'count', + }, + 'listener.http.downstream_rq_4xx': { + 'tags': ('address', 'stat_prefix', ), + 'method': 'count', + }, + 'listener.http.downstream_rq_5xx': { + 'tags': ('address', 'stat_prefix', ), + 'method': 'count', + }, + 'http2.rx_reset': { + 'tags': (), + 'method': 'count', + }, + 'http2.tx_reset': { + 'tags': (), + 'method': 'count', + }, + 'http2.header_overflow': { + 'tags': (), + 'method': 'count', + }, + 'http2.trailers': { + 'tags': (), + 'method': 'count', + }, + 'http2.headers_cb_no_stream': { + 'tags': (), + 'method': 'count', + }, + 'http2.too_many_header_frames': { + 'tags': (), + 'method': 'count', + }, + 'cluster_manager.cluster_added': { + 'tags': (), + 'method': 'count', + }, + 'cluster_manager.cluster_modified': { + 'tags': (), + 'method': 'count', + }, + 'cluster_manager.cluster_removed': { + 'tags': (), + 'method': 'count', + }, + 'cluster_manager.total_clusters': { + 'tags': (), + 'method': 'gauge', + }, + 'cluster.upstream_cx_total': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_cx_active': { + 'tags': ('cluster_name', ), + 'method': 'gauge', + }, + 'cluster.upstream_cx_http1_total': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_cx_http2_total': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_cx_connect_fail': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_cx_connect_timeout': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_cx_connect_attempts_exceeded': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_cx_overflow': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_cx_connect_ms': { + 'tags': ('cluster_name', ), + 'method': 'histogram', + }, + 'cluster.upstream_cx_length_ms': { + 'tags': ('cluster_name', ), + 'method': 'histogram', + }, + 'cluster.upstream_cx_destroy': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_cx_destroy_local': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_cx_destroy_remote': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_cx_destroy_with_active_rq': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_cx_destroy_local_with_active_rq': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_cx_destroy_remote_with_active_rq': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_cx_close_notify': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_cx_rx_bytes_total': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_cx_rx_bytes_buffered': { + 'tags': ('cluster_name', ), + 'method': 'gauge', + }, + 'cluster.upstream_cx_tx_bytes_total': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_cx_tx_bytes_buffered': { + 'tags': ('cluster_name', ), + 'method': 'gauge', + }, + 'cluster.upstream_cx_protocol_error': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_cx_max_requests': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_cx_none_healthy': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_rq_total': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_rq_active': { + 'tags': ('cluster_name', ), + 'method': 'gauge', + }, + 'cluster.upstream_rq_pending_total': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_rq_pending_overflow': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_rq_pending_failure_eject': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_rq_pending_active': { + 'tags': ('cluster_name', ), + 'method': 'gauge', + }, + 'cluster.upstream_rq_cancelled': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_rq_maintenance_mode': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_rq_timeout': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_rq_per_try_timeout': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_rq_rx_reset': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_rq_tx_reset': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_rq_retry': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_rq_retry_success': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_rq_retry_overflow': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_flow_control_paused_reading_total': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_flow_control_resumed_reading_total': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_flow_control_backed_up_total': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_flow_control_drained_total': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.membership_change': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.membership_healthy': { + 'tags': ('cluster_name', ), + 'method': 'gauge', + }, + 'cluster.membership_total': { + 'tags': ('cluster_name', ), + 'method': 'gauge', + }, + 'cluster.retry_or_shadow_abandoned': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.config_reload': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.update_attempt': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.update_success': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.update_failure': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.version': { + 'tags': ('cluster_name', ), + 'method': 'gauge', + }, + 'cluster.max_host_weight': { + 'tags': ('cluster_name', ), + 'method': 'gauge', + }, + 'cluster.bind_errors': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.health_check.attempt': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.health_check.success': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.health_check.failure': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.health_check.passive_failure': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.health_check.network_failure': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.health_check.verify_cluster': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.health_check.healthy': { + 'tags': ('cluster_name', ), + 'method': 'gauge', + }, + 'cluster.outlier_detection.ejections_enforced_total': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.outlier_detection.ejections_active': { + 'tags': ('cluster_name', ), + 'method': 'gauge', + }, + 'cluster.outlier_detection.ejections_overflow': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.outlier_detection.ejections_enforced_consecutive_5xx': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.outlier_detection.ejections_detected_consecutive_5xx': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.outlier_detection.ejections_enforced_success_rate': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.outlier_detection.ejections_detected_success_rate': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.outlier_detection.ejections_enforced_consecutive_gateway_failure': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.outlier_detection.ejections_detected_consecutive_gateway_failure': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_rq_1xx': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_rq_2xx': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_rq_3xx': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_rq_4xx': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_rq_5xx': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.upstream_rq_time': { + 'tags': ('cluster_name', ), + 'method': 'histogram', + }, + 'cluster.canary.upstream_rq_1xx': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.canary.upstream_rq_2xx': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.canary.upstream_rq_3xx': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.canary.upstream_rq_4xx': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.canary.upstream_rq_5xx': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.canary.upstream_rq_time': { + 'tags': ('cluster_name', ), + 'method': 'histogram', + }, + 'cluster.internal.upstream_rq_1xx': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.internal.upstream_rq_2xx': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.internal.upstream_rq_3xx': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.internal.upstream_rq_4xx': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.internal.upstream_rq_5xx': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.internal.upstream_rq_time': { + 'tags': ('cluster_name', ), + 'method': 'histogram', + }, + 'cluster.external.upstream_rq_1xx': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.external.upstream_rq_2xx': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.external.upstream_rq_3xx': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.external.upstream_rq_4xx': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.external.upstream_rq_5xx': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.external.upstream_rq_time': { + 'tags': ('cluster_name', ), + 'method': 'histogram', + }, + 'cluster.zone.upstream_rq_1xx': { + 'tags': ('cluster_name', 'from_zone', 'to_zone', ), + 'method': 'count', + }, + 'cluster.zone.upstream_rq_2xx': { + 'tags': ('cluster_name', 'from_zone', 'to_zone', ), + 'method': 'count', + }, + 'cluster.zone.upstream_rq_3xx': { + 'tags': ('cluster_name', 'from_zone', 'to_zone', ), + 'method': 'count', + }, + 'cluster.zone.upstream_rq_4xx': { + 'tags': ('cluster_name', 'from_zone', 'to_zone', ), + 'method': 'count', + }, + 'cluster.zone.upstream_rq_5xx': { + 'tags': ('cluster_name', 'from_zone', 'to_zone', ), + 'method': 'count', + }, + 'cluster.zone.upstream_rq_time': { + 'tags': ('cluster_name', 'from_zone', 'to_zone', ), + 'method': 'histogram', + }, + 'cluster.lb_healthy_panic': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.lb_zone_cluster_too_small': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.lb_zone_routing_all_directly': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.lb_zone_routing_sampled': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.lb_zone_routing_cross_zone': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.lb_local_cluster_not_ok': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.lb_zone_number_differs': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.lb_subsets_active': { + 'tags': ('cluster_name', ), + 'method': 'gauge', + }, + 'cluster.lb_subsets_created': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.lb_subsets_removed': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.lb_subsets_selected': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, + 'cluster.lb_subsets_fallback': { + 'tags': ('cluster_name', ), + 'method': 'count', + }, +} + +METRIC_TREE = make_metric_tree(METRICS) diff --git a/envoy/datadog_checks/envoy/parser.py b/envoy/datadog_checks/envoy/parser.py new file mode 100644 index 0000000000000..359a83e0718b1 --- /dev/null +++ b/envoy/datadog_checks/envoy/parser.py @@ -0,0 +1,58 @@ +from .errors import UnknownMetric +from .metrics import METRIC_PREFIX, METRIC_TREE, METRICS + + +def parse_metric(metric): + metric_parts = [] + tag_values = [] + mapping = METRIC_TREE + + for metric_part in metric.split('.'): + if metric_part in mapping: + metric_parts.append(metric_part) + mapping = mapping[metric_part] + else: + tag_values.append(metric_part) + + metric = '.'.join(metric_parts) + if metric not in METRICS: + raise UnknownMetric + + tag_names = METRICS[metric]['tags'] + if len(tag_values) != len(tag_names): + tag_values = reassemble_addresses(tag_values) + + if len(tag_values) != len(tag_names): + raise UnknownMetric + + tags = [ + '{}:{}'.format(tag_name, tag_value) + for tag_name, tag_value in zip(tag_names, tag_values) + ] + + return METRIC_PREFIX + metric, tags, METRICS[metric]['method'] + + +def reassemble_addresses(seq): + reassembled = [] + prev = '' + + for s in seq: + if prev.isdigit(): + try: + end, port = s.split('_') + except ValueError: + end, port = '', '' + + if s.isdigit(): + reassembled[-1] += '.{}'.format(s) + elif end.isdigit() and port.isdigit(): + reassembled[-1] += '.{}:{}'.format(end, port) + else: + reassembled.append(s) + else: + reassembled.append(s) + + prev = s + + return reassembled diff --git a/envoy/datadog_checks/envoy/utils.py b/envoy/datadog_checks/envoy/utils.py new file mode 100644 index 0000000000000..9ec6dfdac0c0e --- /dev/null +++ b/envoy/datadog_checks/envoy/utils.py @@ -0,0 +1,20 @@ +from collections import defaultdict + + +def tree(): + return defaultdict(tree) + + +def make_metric_tree(metrics): + metric_tree = tree() + + for metric in metrics: + parts = metric.split('.') + mapping = metric_tree + + # We'll create keys as they are referenced. See: + # https://en.wikipedia.org/wiki/Autovivification + for part in parts: + mapping = mapping[part] + + return metric_tree diff --git a/envoy/manifest.json b/envoy/manifest.json new file mode 100644 index 0000000000000..5b6dc1b7875a2 --- /dev/null +++ b/envoy/manifest.json @@ -0,0 +1,20 @@ +{ + "maintainer": "help@datadoghq.com", + "manifest_version": "1.0.0", + "name": "envoy", + "short_description": "Gain insight into your distributed systems using Envoy.", + "guid": "007f4e6c-ac88-411e-ad81-f0272539b5ff", + "support": "core", + "supported_os": [ + "linux", + "mac_os", + "windows" + ], + "version": "1.0.0", + "public_title": "Datadog-Envoy Integration", + "categories":["cloud", "web"], + "type":"check", + "doc_link": "https://docs.datadoghq.com/integrations/envoy/", + "is_public": true, + "has_logo": true +} diff --git a/envoy/metadata.csv b/envoy/metadata.csv new file mode 100644 index 0000000000000..1e94ee2940025 --- /dev/null +++ b/envoy/metadata.csv @@ -0,0 +1,306 @@ +metric_name,metric_type,interval,unit_name,per_unit_name,description,orientation,integration,short_name +envoy.runtime.load_error,count,,error,,Total number of load attempts that resulted in an error,-1,envoy,failed loads +envoy.runtime.override_dir_not_exists,count,,occurrence,,Total number of loads that did not use an override directory,0,envoy,loads without override directory +envoy.runtime.override_dir_exists,count,,occurrence,,Total number of loads that did use an override directory,0,envoy,loads with override directory +envoy.runtime.load_success,count,,success,,Total number of load attempts that were successful,1,envoy,successful loads +envoy.runtime.num_keys,gauge,,location,,Number of keys currently loaded,0,envoy,keys loaded +envoy.cluster_manager.cds.config_reload,count,,request,,Total API fetches that resulted in a config reload due to a different config,0,envoy,cds config reloads +envoy.cluster_manager.cds.update_attempt,count,,request,,Total API fetches attempted,0,envoy,cds total api accesses +envoy.cluster_manager.cds.update_success,count,,request,,Total API fetches completed successfully,1,envoy,cds successful api accesses +envoy.cluster_manager.cds.update_failure,count,,request,,Total API fetches that failed (either network or schema errors),-1,envoy,cds failed api accesses +envoy.cluster_manager.cds.version,gauge,,item,,Hash of the contents from the last successful API fetch,0,envoy, +envoy.http.no_route,count,,request,,Total requests that had no route and resulted in a 404,-1,envoy, +envoy.http.no_cluster,count,,request,,Total requests in which the target cluster did not exist and resulted in a 404,-1,envoy, +envoy.http.rq_redirect,count,,request,,Total requests that resulted in a redirect response,0,envoy, +envoy.http.rq_total,count,,request,,Total routed requests,0,envoy, +envoy.vhost.vcluster.upstream_rq_1xx,count,,response,,Aggregate HTTP 1xx response codes,0,envoy,vhost 1xx response codes +envoy.vhost.vcluster.upstream_rq_2xx,count,,response,,Aggregate HTTP 2xx response codes,1,envoy,vhost 2xx response codes +envoy.vhost.vcluster.upstream_rq_3xx,count,,response,,Aggregate HTTP 3xx response codes,0,envoy,vhost 3xx response codes +envoy.vhost.vcluster.upstream_rq_4xx,count,,response,,Aggregate HTTP 4xx response codes,-1,envoy,vhost 4xx response codes +envoy.vhost.vcluster.upstream_rq_5xx,count,,response,,Aggregate HTTP 5xx response codes,-1,envoy,vhost 5xx response codes +envoy.vhost.vcluster.upstream_rq_time,gauge,,millisecond,,Request time milliseconds,-1,envoy,vhost request time ms +envoy.cluster.ratelimit.ok,count,,response,,Total under limit responses from the rate limit service,1,envoy, +envoy.cluster.ratelimit.error,count,,response,,Total errors contacting the rate limit service,-1,envoy, +envoy.cluster.ratelimit.over_limit,count,,response,,Total over limit responses from the rate limit service,-1,envoy, +envoy.http.ip_tagging.hit,count,,request,,Total number of requests that have the tag_name tag applied to it,0,envoy, +envoy.http.ip_tagging.no_hit,count,,request,,Total number of requests with no applicable IP tags,0,envoy, +envoy.http.ip_tagging.total,count,,request,,Total number of requests the IP Tagging Filter operated on,0,envoy, +envoy.cluster.grpc.success,count,,operation,,Total successful service/method calls,1,envoy, +envoy.cluster.grpc.failure,count,,operation,,Total failed service/method calls,-1,envoy, +envoy.cluster.grpc.total,count,,operation,,Total service/method calls,0,envoy, +envoy.http.dynamodb.operation.upstream_rq_total,count,,request,,Total number of requests with operation_name tag,0,envoy, +envoy.http.dynamodb.operation.upstream_rq_time,gauge,,millisecond,,Time spent on operation_name tag,-1,envoy, +envoy.http.dynamodb.table.upstream_rq_total,count,,request,,Total number of requests on table_name tag table,0,envoy, +envoy.http.dynamodb.table.upstream_rq_time,gauge,,millisecond,,Time spent on table_name tag table,-1,envoy, +envoy.http.dynamodb.error,count,,error,,Total number of specific error_type tag for a given table_name tag,-1,envoy, +envoy.http.dynamodb.error.BatchFailureUnprocessedKeys,count,,error,,Total number of partial batch failures for a given table_name tag,-1,envoy, +envoy.http.buffer.rq_timeout,count,,timeout,,Total requests that timed out waiting for a full request,-1,envoy, +envoy.http.rds.config_reload,count,,request,,Total API fetches that resulted in a config reload due to a different config,0,envoy,rds config reloads +envoy.http.rds.update_attempt,count,,request,,Total API fetches attempted,0,envoy,rds total api accesses +envoy.http.rds.update_success,count,,request,,Total API fetches completed successfully,1,envoy,rds successful api accesses +envoy.http.rds.update_failure,count,,request,,Total API fetches that failed (either network or schema errors),-1,envoy,rds failed api accesses +envoy.http.rds.version,gauge,,item,,Hash of the contents from the last successful API fetch,0,envoy, +envoy.tcp.downstream_cx_total,count,,connection,,Total number of connections handled by the filter,0,envoy, +envoy.tcp.downstream_cx_no_route,count,,connection,,Number of connections for which no matching route was found,-1,envoy, +envoy.tcp.downstream_cx_tx_bytes_total,count,,byte,,Total bytes written to the downstream connection,0,envoy, +envoy.tcp.downstream_cx_tx_bytes_buffered,gauge,,byte,,Total bytes currently buffered to the downstream connection,0,envoy, +envoy.tcp.downstream_flow_control_paused_reading_total,count,,occurrence,,Total number of times flow control paused reading from downstream,0,envoy, +envoy.tcp.downstream_flow_control_resumed_reading_total,count,,occurrence,,Total number of times flow control resumed reading from downstream,0,envoy, +envoy.auth.clientssl.update_success,count,,success,,Total principal update successes,1,envoy, +envoy.auth.clientssl.update_failure,count,,error,,Total principal update failures,-1,envoy, +envoy.auth.clientssl.auth_no_ssl,count,,connection,,Total connections ignored due to no TLS,-1,envoy, +envoy.auth.clientssl.auth_ip_white_list,count,,connection,,Total connections allowed due to the IP white list,1,envoy, +envoy.auth.clientssl.auth_digest_match,count,,connection,,Total connections allowed due to certificate match,1,envoy, +envoy.auth.clientssl.auth_digest_no_match,count,,connection,,Total connections denied due to no certificate match,-1,envoy, +envoy.auth.clientssl.total_principals,gauge,,item,,Total loaded principals,0,envoy, +envoy.ratelimit.total,count,,response,,Total requests to the rate limit service,0,envoy, +envoy.ratelimit.error,count,,response,,Total errors contacting the rate limit service,-1,envoy, +envoy.ratelimit.over_limit,count,,response,,Total over limit responses from the rate limit service,-1,envoy, +envoy.ratelimit.ok,count,,response,,Total under limit responses from the rate limit service,1,envoy, +envoy.ratelimit.cx_closed,count,,connection,,Total connections closed due to an over limit response from the rate limit service,-1,envoy, +envoy.ratelimit.active,gauge,,request,,Total active requests to the rate limit service,0,envoy, +envoy.redis.downstream_cx_active,gauge,,connection,,Total active connections,0,envoy, +envoy.redis.downstream_cx_protocol_error,count,,error,,Total protocol errors,-1,envoy, +envoy.redis.downstream_cx_rx_bytes_buffered,gauge,,byte,,Total received bytes currently buffered,0,envoy, +envoy.redis.downstream_cx_rx_bytes_total,count,,byte,,Total bytes received,0,envoy, +envoy.redis.downstream_cx_total,count,,connection,,Total connections,0,envoy, +envoy.redis.downstream_cx_tx_bytes_buffered,gauge,,byte,,Total sent bytes currently buffered,0,envoy, +envoy.redis.downstream_cx_tx_bytes_total,count,,byte,,Total bytes sent,0,envoy, +envoy.redis.downstream_cx_drain_close,count,,connection,,Number of connections closed due to draining,0,envoy, +envoy.redis.downstream_rq_active,gauge,,request,,Total active requests,0,envoy, +envoy.redis.downstream_rq_total,count,,request,,Total requests,0,envoy, +envoy.redis.splitter.invalid_request,count,,request,,Number of requests with an incorrect number of arguments,-1,envoy, +envoy.redis.splitter.unsupported_command,count,,operation,,Number of commands issued which are not recognized by the command splitter,-1,envoy, +envoy.redis.command.total,count,,operation,,Number of commands,0,envoy, +envoy.mongo.decoding_error,count,,error,,Number of MongoDB protocol decoding errors,-1,envoy, +envoy.mongo.delay_injected,count,,occurrence,,Number of times the delay is injected,0,envoy, +envoy.mongo.op_get_more,count,,message,,Number of OP_GET_MORE messages,0,envoy, +envoy.mongo.op_insert,count,,message,,Number of OP_INSERT messages,0,envoy, +envoy.mongo.op_kill_cursors,count,,message,,Number of OP_KILL_CURSORS messages,0,envoy, +envoy.mongo.op_query,count,,message,,Number of OP_QUERY messages,0,envoy, +envoy.mongo.op_query_tailable_cursor,count,,message,,Number of OP_QUERY with tailable cursor flag set,0,envoy, +envoy.mongo.op_query_no_cursor_timeout,count,,message,,Number of OP_QUERY with no cursor timeout flag set,0,envoy, +envoy.mongo.op_query_await_data,count,,message,,Number of OP_QUERY with await data flag set,0,envoy, +envoy.mongo.op_query_exhaust,count,,message,,Number of OP_QUERY with exhaust flag set,0,envoy, +envoy.mongo.op_query_no_max_time,count,,query,,Number of queries without maxTimeMS set,0,envoy, +envoy.mongo.op_query_scatter_get,count,,query,,Number of scatter get queries,0,envoy, +envoy.mongo.op_query_multi_get,count,,query,,Number of multi get queries,0,envoy, +envoy.mongo.op_query_active,gauge,,query,,Number of active queries,0,envoy, +envoy.mongo.op_reply,count,,message,,Number of OP_REPLY messages,0,envoy, +envoy.mongo.op_reply_cursor_not_found,count,,message,,Number of OP_REPLY with cursor not found flag set,0,envoy, +envoy.mongo.op_reply_query_failure,count,,message,,Number of OP_REPLY with query failure flag set,0,envoy, +envoy.mongo.op_reply_valid_cursor,count,,message,,Number of OP_REPLY with a valid cursor,0,envoy, +envoy.mongo.cx_destroy_local_with_active_rq,count,,connection,,Connections destroyed locally with an active query,-1,envoy, +envoy.mongo.cx_destroy_remote_with_active_rq,count,,connection,,Connections destroyed remotely with an active query,-1,envoy, +envoy.mongo.cx_drain_close,count,,connection,,Connections gracefully closed on reply boundaries during server drain,0,envoy, +envoy.mongo.cmd.total,count,,command,,Number of commands,0,envoy, +envoy.mongo.cmd.reply_num_docs,gauge,,document,,Number of documents in reply,0,envoy, +envoy.mongo.cmd.reply_size,gauge,,byte,,Size of the reply in bytes,0,envoy, +envoy.mongo.cmd.reply_time_ms,gauge,,millisecond,,Command time in milliseconds,-1,envoy, +envoy.mongo.collection.query.total,count,,query,,Number of queries,0,envoy, +envoy.mongo.collection.query.scatter_get,count,,query,,Number of scatter gets,0,envoy, +envoy.mongo.collection.query.multi_get,count,,query,,Number of multi gets,0,envoy, +envoy.mongo.collection.query.reply_num_docs,gauge,,document,,Number of documents in reply,0,envoy, +envoy.mongo.collection.query.reply_size,gauge,,byte,,Size of the reply in bytes,0,envoy, +envoy.mongo.collection.query.reply_time_ms,gauge,,millisecond,,Query time in milliseconds,-1,envoy, +envoy.mongo.collection.callsite.query.total,count,,query,,Number of queries for the callsite tag,0,envoy, +envoy.mongo.collection.callsite.query.scatter_get,count,,query,,Number of scatter gets for the callsite tag,0,envoy, +envoy.mongo.collection.callsite.query.multi_get,count,,query,,Number of multi gets for the callsite tag,0,envoy, +envoy.mongo.collection.callsite.query.reply_num_docs,gauge,,document,,Number of documents in reply for the callsite tag,0,envoy, +envoy.mongo.collection.callsite.query.reply_size,gauge,,byte,,Size of the reply in bytes for the callsite tag,0,envoy, +envoy.mongo.collection.callsite.query.reply_time_ms,gauge,,millisecond,,Query time in milliseconds for the callsite tag,-1,envoy, +envoy.listener.downstream_cx_total,count,,connection,,Total connections,0,envoy, +envoy.listener.downstream_cx_destroy,count,,connection,,Total destroyed connections,0,envoy, +envoy.listener.downstream_cx_active,gauge,,connection,,Total active connections,0,envoy, +envoy.listener.downstream_cx_length_ms,gauge,,millisecond,,Connection length milliseconds,-1,envoy, +envoy.listener.ssl.connection_error,count,,error,,Total TLS connection errors not including failed certificate verifications,-1,envoy, +envoy.listener.ssl.handshake,count,,success,,Total successful TLS connection handshakes,1,envoy, +envoy.listener.ssl.session_reused,count,,success,,Total successful TLS session resumptions,1,envoy, +envoy.listener.ssl.no_certificate,count,,success,,Total successful TLS connections with no client certificate,1,envoy, +envoy.listener.ssl.fail_no_sni_match,count,,connection,,Total TLS connections that were rejected because of missing SNI match,-1,envoy, +envoy.listener.ssl.fail_verify_no_cert,count,,connection,,Total TLS connections that failed because of missing client certificate,-1,envoy, +envoy.listener.ssl.fail_verify_error,count,,connection,,Total TLS connections that failed CA verification,-1,envoy, +envoy.listener.ssl.fail_verify_san,count,,connection,,Total TLS connections that failed SAN verification,-1,envoy, +envoy.listener.ssl.fail_verify_cert_hash,count,,connection,,Total TLS connections that failed certificate pinning verification,-1,envoy, +envoy.listener.ssl.cipher,count,,connection,,Total TLS connections that used cipher tag,0,envoy, +envoy.listener_manager.listener_added,count,,host,,Total listeners added (either via static config or LDS),0,envoy, +envoy.listener_manager.listener_modified,count,,host,,Total listeners modified (via LDS),0,envoy, +envoy.listener_manager.listener_removed,count,,host,,Total listeners removed (via LDS),0,envoy, +envoy.listener_manager.listener_create_success,count,,host,,Total listener objects successfully added to workers,1,envoy, +envoy.listener_manager.listener_create_failure,count,,host,,Total failed listener object additions to workers,-1,envoy, +envoy.listener_manager.total_listeners_warming,gauge,,host,,Number of currently warming listeners,0,envoy, +envoy.listener_manager.total_listeners_active,gauge,,host,,Number of currently active listeners,0,envoy, +envoy.listener_manager.total_listeners_draining,gauge,,host,,Number of currently draining listeners,0,envoy, +envoy.http.downstream_cx_total,count,,connection,,Total connections,0,envoy, +envoy.http.downstream_cx_ssl_total,count,,connection,,Total TLS connections,0,envoy, +envoy.http.downstream_cx_http1_total,count,,connection,,Total HTTP/1.1 connections,0,envoy, +envoy.http.downstream_cx_websocket_total,count,,connection,,Total WebSocket connections,0,envoy, +envoy.http.downstream_cx_http2_total,count,,connection,,Total HTTP/2 connections,0,envoy, +envoy.http.downstream_cx_destroy,count,,connection,,Total connections destroyed,0,envoy, +envoy.http.downstream_cx_destroy_remote,count,,connection,,Total connections destroyed due to remote close,0,envoy, +envoy.http.downstream_cx_destroy_local,count,,connection,,Total connections destroyed due to local close,0,envoy, +envoy.http.downstream_cx_destroy_active_rq,count,,connection,,Total connections destroyed with active requests,-1,envoy, +envoy.http.downstream_cx_destroy_local_active_rq,count,,connection,,Total connections destroyed locally with active requests,-1,envoy, +envoy.http.downstream_cx_destroy_remote_active_rq,count,,connection,,Total connections destroyed remotely with active requests,-1,envoy, +envoy.http.downstream_cx_active,gauge,,connection,,Total active connections,0,envoy, +envoy.http.downstream_cx_ssl_active,gauge,,connection,,Total active TLS connections,0,envoy, +envoy.http.downstream_cx_http1_active,gauge,,connection,,Total active HTTP/1.1 connections,0,envoy, +envoy.http.downstream_cx_websocket_active,gauge,,connection,,Total active WebSocket connections,0,envoy, +envoy.http.downstream_cx_http2_active,gauge,,connection,,Total active HTTP/2 connections,0,envoy, +envoy.http.downstream_cx_protocol_error,count,,error,,Total protocol errors,-1,envoy, +envoy.http.downstream_cx_length_ms,gauge,,millisecond,,Connection length milliseconds,0,envoy, +envoy.http.downstream_cx_rx_bytes_total,count,,byte,,Total bytes received,0,envoy, +envoy.http.downstream_cx_rx_bytes_buffered,gauge,,byte,,Total received bytes currently buffered,0,envoy, +envoy.http.downstream_cx_tx_bytes_total,count,,byte,,Total bytes sent,0,envoy, +envoy.http.downstream_cx_tx_bytes_buffered,gauge,,byte,,Total sent bytes currently buffered,0,envoy, +envoy.http.downstream_cx_drain_close,count,,connection,,Total connections closed due to draining,0,envoy, +envoy.http.downstream_cx_idle_timeout,count,,connection,,Total connections closed due to idle timeout,0,envoy, +envoy.http.downstream_flow_control_paused_reading_total,count,,occurrence,,Total number of times reads were disabled due to flow control,0,envoy, +envoy.http.downstream_flow_control_resumed_reading_total,count,,occurrence,,Total number of times reads were enabled on the connection due to flow control,0,envoy, +envoy.http.downstream_rq_total,count,,request,,Total requests,0,envoy, +envoy.http.downstream_rq_http1_total,count,,request,,Total HTTP/1.1 requests,0,envoy, +envoy.http.downstream_rq_http2_total,count,,request,,Total HTTP/2 requests,0,envoy, +envoy.http.downstream_rq_active,gauge,,request,,Total active requests,0,envoy, +envoy.http.downstream_rq_response_before_rq_complete,count,,response,,Total responses sent before the request was complete,0,envoy, +envoy.http.downstream_rq_rx_reset,count,,request,,Total request resets received,0,envoy, +envoy.http.downstream_rq_tx_reset,count,,request,,Total request resets sent,0,envoy, +envoy.http.downstream_rq_non_relative_path,count,,request,,Total requests with a non-relative HTTP path,0,envoy, +envoy.http.downstream_rq_too_large,count,,request,,Total requests resulting in a 413 due to buffering an overly large body,-1,envoy, +envoy.http.downstream_rq_1xx,count,,response,,Total 1xx responses,0,envoy, +envoy.http.downstream_rq_2xx,count,,response,,Total 2xx responses,1,envoy, +envoy.http.downstream_rq_3xx,count,,response,,Total 3xx responses,0,envoy, +envoy.http.downstream_rq_4xx,count,,response,,Total 4xx responses,-1,envoy, +envoy.http.downstream_rq_5xx,count,,response,,Total 5xx responses,-1,envoy, +envoy.http.downstream_rq_ws_on_non_ws_route,count,,request,,Total WebSocket upgrade requests rejected by non WebSocket routes,0,envoy, +envoy.http.downstream_rq_time,gauge,,millisecond,,Request time milliseconds,-1,envoy, +envoy.http.rs_too_large,count,,error,,Total response errors due to buffering an overly large body,-1,envoy, +envoy.http.user_agent.downstream_cx_total,count,,connection,,Total connections,0,envoy, +envoy.http.user_agent.downstream_cx_destroy_remote_active_rq,count,,connection,,Total connections destroyed remotely with active requests,-1,envoy, +envoy.http.user_agent.downstream_rq_total,count,,request,,Total requests,0,envoy, +envoy.listener.http.downstream_rq_1xx,count,,response,,Total 1xx responses,0,envoy, +envoy.listener.http.downstream_rq_2xx,count,,response,,Total 2xx responses,1,envoy, +envoy.listener.http.downstream_rq_3xx,count,,response,,Total 3xx responses,0,envoy, +envoy.listener.http.downstream_rq_4xx,count,,response,,Total 4xx responses,-1,envoy, +envoy.listener.http.downstream_rq_5xx,count,,response,,Total 5xx responses,-1,envoy, +envoy.http2.rx_reset,count,,message,,Total number of reset stream frames received by Envoy,0,envoy, +envoy.http2.tx_reset,count,,message,,Total number of reset stream frames transmitted by Envoy,0,envoy, +envoy.http2.header_overflow,count,,connection,,Total number of connections reset due to the headers being larger than 63 K,-1,envoy, +envoy.http2.trailers,count,,item,,Total number of trailers seen on requests coming from downstream,0,envoy, +envoy.http2.headers_cb_no_stream,count,,error,,Total number of errors where a header callback is called without an associated stream. This tracks an unexpected occurrence due to an as yet undiagnosed bug.,-1,envoy, +envoy.http2.too_many_header_frames,count,,occurrence,,Total number of times an HTTP2 connection is reset due to receiving too many headers frames. Envoy currently supports proxying at most one header frame for 100-Continue one non-100 response code header frame and one frame with trailers.,-1,envoy, +envoy.cluster_manager.cluster_added,count,,node,,Total clusters added (either via static config or CDS),0,envoy, +envoy.cluster_manager.cluster_modified,count,,node,,Total clusters modified (via CDS),0,envoy, +envoy.cluster_manager.cluster_removed,count,,node,,Total clusters removed (via CDS),0,envoy, +envoy.cluster_manager.total_clusters,gauge,,node,,Number of currently loaded clusters,0,envoy, +envoy.cluster.upstream_cx_total,count,,connection,,Total connections,0,envoy, +envoy.cluster.upstream_cx_active,gauge,,connection,,Total active connections,0,envoy, +envoy.cluster.upstream_cx_http1_total,count,,connection,,Total HTTP/1.1 connections,0,envoy, +envoy.cluster.upstream_cx_http2_total,count,,connection,,Total HTTP/2 connections,0,envoy, +envoy.cluster.upstream_cx_connect_fail,count,,error,,Total connection failures,-1,envoy, +envoy.cluster.upstream_cx_connect_timeout,count,,timeout,,Total connection timeouts,-1,envoy, +envoy.cluster.upstream_cx_connect_attempts_exceeded,count,,error,,Total consecutive connection failures exceeding configured connection attempts,-1,envoy, +envoy.cluster.upstream_cx_overflow,count,,occurrence,,Total times that the cluster’s connection circuit breaker overflowed,-1,envoy, +envoy.cluster.upstream_cx_connect_ms,gauge,,millisecond,,Connection establishment milliseconds,-1,envoy, +envoy.cluster.upstream_cx_length_ms,gauge,,millisecond,,Connection length milliseconds,0,envoy, +envoy.cluster.upstream_cx_destroy,count,,connection,,Total destroyed connections,0,envoy, +envoy.cluster.upstream_cx_destroy_local,count,,connection,,Total connections destroyed locally,0,envoy, +envoy.cluster.upstream_cx_destroy_remote,count,,connection,,Total connections destroyed remotely,0,envoy, +envoy.cluster.upstream_cx_destroy_with_active_rq,count,,connection,,Total connections destroyed with active requests,-1,envoy, +envoy.cluster.upstream_cx_destroy_local_with_active_rq,count,,connection,,Total connections destroyed locally with active requests,-1,envoy, +envoy.cluster.upstream_cx_destroy_remote_with_active_rq,count,,connection,,Total connections destroyed remotely with active requests,-1,envoy, +envoy.cluster.upstream_cx_close_notify,count,,connection,,Total connections closed via HTTP/1.1 connection close header or HTTP/2 GOAWAY,0,envoy, +envoy.cluster.upstream_cx_rx_bytes_total,count,,byte,,Total received connection bytes,0,envoy, +envoy.cluster.upstream_cx_rx_bytes_buffered,gauge,,byte,,Received connection bytes currently buffered,0,envoy, +envoy.cluster.upstream_cx_tx_bytes_total,count,,byte,,Total sent connection bytes,0,envoy, +envoy.cluster.upstream_cx_tx_bytes_buffered,gauge,,byte,,Send connection bytes currently buffered,0,envoy, +envoy.cluster.upstream_cx_protocol_error,count,,error,,Total connection protocol errors,-1,envoy, +envoy.cluster.upstream_cx_max_requests,count,,connection,,Total connections closed due to maximum requests,-1,envoy, +envoy.cluster.upstream_cx_none_healthy,count,,connection,,Total times connection not established due to no healthy hosts,-1,envoy, +envoy.cluster.upstream_rq_total,count,,request,,Total requests,0,envoy, +envoy.cluster.upstream_rq_active,gauge,,request,,Total active requests,0,envoy, +envoy.cluster.upstream_rq_pending_total,count,,request,,Total requests pending a connection pool connection,0,envoy, +envoy.cluster.upstream_rq_pending_overflow,count,,request,,Total requests that overflowed connection pool circuit breaking and were failed,-1,envoy, +envoy.cluster.upstream_rq_pending_failure_eject,count,,request,,Total requests that were failed due to a connection pool connection failure,-1,envoy, +envoy.cluster.upstream_rq_pending_active,gauge,,request,,Total active requests pending a connection pool connection,-1,envoy, +envoy.cluster.upstream_rq_cancelled,count,,request,,Total requests cancelled before obtaining a connection pool connection,-1,envoy, +envoy.cluster.upstream_rq_maintenance_mode,count,,request,,Total requests that resulted in an immediate 503 due to maintenance mode,-1,envoy, +envoy.cluster.upstream_rq_timeout,count,,request,,Total requests that timed out waiting for a response,-1,envoy, +envoy.cluster.upstream_rq_per_try_timeout,count,,request,,Total requests that hit the per try timeout,-1,envoy, +envoy.cluster.upstream_rq_rx_reset,count,,request,,Total requests that were reset remotely,0,envoy, +envoy.cluster.upstream_rq_tx_reset,count,,request,,Total requests that were reset locally,0,envoy, +envoy.cluster.upstream_rq_retry,count,,request,,Total request retries,0,envoy, +envoy.cluster.upstream_rq_retry_success,count,,request,,Total request retry successes,1,envoy, +envoy.cluster.upstream_rq_retry_overflow,count,,request,,Total requests not retried due to circuit breaking,-1,envoy, +envoy.cluster.upstream_flow_control_paused_reading_total,count,,occurrence,,Total number of times flow control paused reading from upstream,0,envoy, +envoy.cluster.upstream_flow_control_resumed_reading_total,count,,occurrence,,Total number of times flow control resumed reading from upstream,0,envoy, +envoy.cluster.upstream_flow_control_backed_up_total,count,,occurrence,,Total number of times the upstream connection backed up and paused reads from downstream,0,envoy, +envoy.cluster.upstream_flow_control_drained_total,count,,occurrence,,Total number of times the upstream connection drained and resumed reads from downstream,0,envoy, +envoy.cluster.membership_change,count,,event,,Total cluster membership changes,0,envoy, +envoy.cluster.membership_healthy,gauge,,node,,Current cluster healthy total (inclusive of both health checking and outlier detection),1,envoy, +envoy.cluster.membership_total,gauge,,node,,Current cluster membership total,0,envoy, +envoy.cluster.retry_or_shadow_abandoned,count,,occurrence,,Total number of times shadowing or retry buffering was canceled due to buffer limits,-1,envoy, +envoy.cluster.config_reload,count,,request,,Total API fetches that resulted in a config reload due to a different config,0,envoy, +envoy.cluster.update_attempt,count,,occurrence,,Total cluster membership update attempts,0,envoy, +envoy.cluster.update_success,count,,success,,Total cluster membership update successes,1,envoy, +envoy.cluster.update_failure,count,,error,,Total cluster membership update failures,-1,envoy, +envoy.cluster.version,gauge,,item,,Hash of the contents from the last successful API fetch,0,envoy, +envoy.cluster.max_host_weight,gauge,,item,,Maximum weight of any host in the cluster,0,envoy, +envoy.cluster.bind_errors,count,,error,,Total errors binding the socket to the configured source address,-1,envoy, +envoy.cluster.health_check.attempt,count,,check,,Number of health checks,0,envoy, +envoy.cluster.health_check.success,count,,check,,Number of successful health checks,1,envoy, +envoy.cluster.health_check.failure,count,,check,,Number of immediately failed health checks (e.g. HTTP 503) as well as network failures,-1,envoy, +envoy.cluster.health_check.passive_failure,count,,check,,Number of health check failures due to passive events (e.g. x-envoy-immediate-health-check-fail),-1,envoy, +envoy.cluster.health_check.network_failure,count,,check,,Number of health check failures due to network error,-1,envoy, +envoy.cluster.health_check.verify_cluster,count,,check,,Number of health checks that attempted cluster name verification,0,envoy, +envoy.cluster.health_check.healthy,gauge,,check,,Number of healthy members,1,envoy, +envoy.cluster.outlier_detection.ejections_enforced_total,count,,,,Number of enforced ejections due to any outlier type,-1,envoy, +envoy.cluster.outlier_detection.ejections_active,gauge,,,,Number of currently ejected hosts,-1,envoy, +envoy.cluster.outlier_detection.ejections_overflow,count,,,,Number of ejections aborted due to the max ejection %,-1,envoy, +envoy.cluster.outlier_detection.ejections_enforced_consecutive_5xx,count,,,,Number of enforced consecutive 5xx ejections,-1,envoy, +envoy.cluster.outlier_detection.ejections_detected_consecutive_5xx,count,,,,Number of detected consecutive 5xx ejections (even if unenforced),-1,envoy, +envoy.cluster.outlier_detection.ejections_enforced_success_rate,count,,,,Number of enforced success rate outlier ejections,-1,envoy, +envoy.cluster.outlier_detection.ejections_detected_success_rate,count,,,,Number of detected success rate outlier ejections (even if unenforced),-1,envoy, +envoy.cluster.outlier_detection.ejections_enforced_consecutive_gateway_failure,count,,,,Number of enforced consecutive gateway failure ejections,-1,envoy, +envoy.cluster.outlier_detection.ejections_detected_consecutive_gateway_failure,count,,,,Number of detected consecutive gateway failure ejections (even if unenforced),-1,envoy, +envoy.cluster.upstream_rq_1xx,count,,response,,Aggregate HTTP 1xx response codes,0,envoy, +envoy.cluster.upstream_rq_2xx,count,,response,,Aggregate HTTP 2xx response codes,1,envoy, +envoy.cluster.upstream_rq_3xx,count,,response,,Aggregate HTTP 3xx response codes,0,envoy, +envoy.cluster.upstream_rq_4xx,count,,response,,Aggregate HTTP 4xx response codes,-1,envoy, +envoy.cluster.upstream_rq_5xx,count,,response,,Aggregate HTTP 5xx response codes,-1,envoy, +envoy.cluster.upstream_rq_time,gauge,,millisecond,,Request time milliseconds,-1,envoy, +envoy.cluster.canary.upstream_rq_1xx,count,,response,,Upstream canary aggregate HTTP 1xx response codes,0,envoy, +envoy.cluster.canary.upstream_rq_2xx,count,,response,,Upstream canary aggregate HTTP 2xx response codes,1,envoy, +envoy.cluster.canary.upstream_rq_3xx,count,,response,,Upstream canary aggregate HTTP 3xx response codes,0,envoy, +envoy.cluster.canary.upstream_rq_4xx,count,,response,,Upstream canary aggregate HTTP 4xx response codes,-1,envoy, +envoy.cluster.canary.upstream_rq_5xx,count,,response,,Upstream canary aggregate HTTP 5xx response codes,-1,envoy, +envoy.cluster.canary.upstream_rq_time,gauge,,millisecond,,Upstream canary request time milliseconds,-1,envoy, +envoy.cluster.internal.upstream_rq_1xx,count,,response,,Internal origin aggregate HTTP 1xx response codes,0,envoy, +envoy.cluster.internal.upstream_rq_2xx,count,,response,,Internal origin aggregate HTTP 2xx response codes,1,envoy, +envoy.cluster.internal.upstream_rq_3xx,count,,response,,Internal origin aggregate HTTP 3xx response codes,0,envoy, +envoy.cluster.internal.upstream_rq_4xx,count,,response,,Internal origin aggregate HTTP 4xx response codes,-1,envoy, +envoy.cluster.internal.upstream_rq_5xx,count,,response,,Internal origin aggregate HTTP 5xx response codes,-1,envoy, +envoy.cluster.internal.upstream_rq_time,gauge,,millisecond,,Internal origin request time milliseconds,-1,envoy, +envoy.cluster.external.upstream_rq_1xx,count,,response,,External origin aggregate HTTP 1xx response codes,0,envoy, +envoy.cluster.external.upstream_rq_2xx,count,,response,,External origin aggregate HTTP 2xx response codes,1,envoy, +envoy.cluster.external.upstream_rq_3xx,count,,response,,External origin aggregate HTTP 3xx response codes,0,envoy, +envoy.cluster.external.upstream_rq_4xx,count,,response,,External origin aggregate HTTP 4xx response codes,-1,envoy, +envoy.cluster.external.upstream_rq_5xx,count,,response,,External origin aggregate HTTP 5xx response codes,-1,envoy, +envoy.cluster.external.upstream_rq_time,gauge,,millisecond,,External origin request time milliseconds,-1,envoy, +envoy.cluster.zone.upstream_rq_1xx,count,,response,,Aggregate HTTP 1xx response codes,0,envoy, +envoy.cluster.zone.upstream_rq_2xx,count,,response,,Aggregate HTTP 2xx response codes,1,envoy, +envoy.cluster.zone.upstream_rq_3xx,count,,response,,Aggregate HTTP 3xx response codes,0,envoy, +envoy.cluster.zone.upstream_rq_4xx,count,,response,,Aggregate HTTP 4xx response codes,-1,envoy, +envoy.cluster.zone.upstream_rq_5xx,count,,response,,Aggregate HTTP 5xx response codes,-1,envoy, +envoy.cluster.zone.upstream_rq_time,gauge,,millisecond,,Request time milliseconds,-1,envoy, +envoy.cluster.lb_healthy_panic,count,,request,,Total requests load balanced with the load balancer in panic mode,-1,envoy, +envoy.cluster.lb_zone_cluster_too_small,count,,,,No zone aware routing because of small upstream cluster size,0,envoy, +envoy.cluster.lb_zone_routing_all_directly,count,,,,Sending all requests directly to the same zone,0,envoy, +envoy.cluster.lb_zone_routing_sampled,count,,,,Sending some requests to the same zone,0,envoy, +envoy.cluster.lb_zone_routing_cross_zone,count,,,,Zone aware routing mode but have to send cross zone,0,envoy, +envoy.cluster.lb_local_cluster_not_ok,count,,,,Local host set is not set or it is panic mode for local cluster,0,envoy, +envoy.cluster.lb_zone_number_differs,count,,,,Number of zones in local and upstream cluster different,0,envoy, +envoy.cluster.lb_subsets_active,gauge,,,,Number of currently available subsets,0,envoy, +envoy.cluster.lb_subsets_created,count,,,,Number of subsets created,0,envoy, +envoy.cluster.lb_subsets_removed,count,,,,Number of subsets removed due to no hosts,0,envoy, +envoy.cluster.lb_subsets_selected,count,,occurrence,,Number of times any subset was selected for load balancing,0,envoy, +envoy.cluster.lb_subsets_fallback,count,,occurrence,,Number of times the fallback policy was invoked,0,envoy, diff --git a/envoy/requirements-dev.txt b/envoy/requirements-dev.txt new file mode 100644 index 0000000000000..14adc5680b2d4 --- /dev/null +++ b/envoy/requirements-dev.txt @@ -0,0 +1,3 @@ +backports.functools_lru_cache==1.5.0 +mock==2.0.0 +pytest diff --git a/envoy/requirements.in b/envoy/requirements.in new file mode 100644 index 0000000000000..271baf7e23f16 --- /dev/null +++ b/envoy/requirements.in @@ -0,0 +1 @@ +requests==2.18.4 diff --git a/envoy/requirements.txt b/envoy/requirements.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/envoy/setup.py b/envoy/setup.py new file mode 100644 index 0000000000000..27023ee2839d6 --- /dev/null +++ b/envoy/setup.py @@ -0,0 +1,69 @@ +# (C) Datadog, Inc. 2018 +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) +from setuptools import setup +from codecs import open # To use a consistent encoding +from os import path + +HERE = path.dirname(path.abspath(__file__)) + +# Get version info +ABOUT = {} +with open(path.join(HERE, "datadog_checks", "envoy", "__about__.py")) as f: + exec(f.read(), ABOUT) + +# Get the long description from the README file +with open(path.join(HERE, 'README.md'), encoding='utf-8') as f: + long_description = f.read() + + +# Parse requirements +def get_requirements(fpath): + with open(path.join(HERE, fpath), encoding='utf-8') as f: + return f.readlines() + + +setup( + name='datadog-envoy', + version=ABOUT["__version__"], + description='The Envoy check', + long_description=long_description, + keywords='datadog agent envoy check', + + # The project's main homepage. + url='https://github.com/DataDog/integrations-core', + + # Author details + author='Datadog', + author_email='packages@datadoghq.com', + + # License + license='BSD', + + # See https://pypi.python.org/pypi?%3Aaction=list_classifiers + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Intended Audience :: System Administrators', + 'Topic :: System :: Monitoring', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + ], + + # The package we're going to ship + packages=['datadog_checks.envoy'], + + # Run-time dependencies + install_requires=get_requirements('requirements.in')+[ + 'datadog-checks-base', + ], + + # Testing setup and dependencies + setup_requires=['pytest-runner'], + tests_require=get_requirements('requirements-dev.txt'), + + # Extra files to ship with the wheel package + package_data={'datadog_checks.envoy': ['conf.yaml.example']}, + include_package_data=True, +) diff --git a/envoy/tests/__init__.py b/envoy/tests/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/envoy/tests/fixtures/multiple_services b/envoy/tests/fixtures/multiple_services new file mode 100644 index 0000000000000..361996575cfce --- /dev/null +++ b/envoy/tests/fixtures/multiple_services @@ -0,0 +1,278 @@ +cluster.service1.bind_errors: 0 +cluster.service1.lb_healthy_panic: 0 +cluster.service1.lb_local_cluster_not_ok: 0 +cluster.service1.lb_recalculate_zone_structures: 0 +cluster.service1.lb_subsets_active: 0 +cluster.service1.lb_subsets_created: 0 +cluster.service1.lb_subsets_fallback: 0 +cluster.service1.lb_subsets_removed: 0 +cluster.service1.lb_subsets_selected: 0 +cluster.service1.lb_zone_cluster_too_small: 0 +cluster.service1.lb_zone_no_capacity_left: 0 +cluster.service1.lb_zone_number_differs: 0 +cluster.service1.lb_zone_routing_all_directly: 0 +cluster.service1.lb_zone_routing_cross_zone: 0 +cluster.service1.lb_zone_routing_sampled: 0 +cluster.service1.max_host_weight: 1 +cluster.service1.membership_change: 1 +cluster.service1.membership_healthy: 1 +cluster.service1.membership_total: 1 +cluster.service1.retry_or_shadow_abandoned: 0 +cluster.service1.update_attempt: 136 +cluster.service1.update_empty: 0 +cluster.service1.update_failure: 0 +cluster.service1.update_success: 136 +cluster.service1.upstream_cx_active: 0 +cluster.service1.upstream_cx_close_notify: 0 +cluster.service1.upstream_cx_connect_attempts_exceeded: 0 +cluster.service1.upstream_cx_connect_fail: 0 +cluster.service1.upstream_cx_connect_timeout: 0 +cluster.service1.upstream_cx_destroy: 0 +cluster.service1.upstream_cx_destroy_local: 0 +cluster.service1.upstream_cx_destroy_local_with_active_rq: 0 +cluster.service1.upstream_cx_destroy_remote: 0 +cluster.service1.upstream_cx_destroy_remote_with_active_rq: 0 +cluster.service1.upstream_cx_destroy_with_active_rq: 0 +cluster.service1.upstream_cx_http1_total: 0 +cluster.service1.upstream_cx_http2_total: 0 +cluster.service1.upstream_cx_max_requests: 0 +cluster.service1.upstream_cx_none_healthy: 0 +cluster.service1.upstream_cx_overflow: 0 +cluster.service1.upstream_cx_protocol_error: 0 +cluster.service1.upstream_cx_rx_bytes_buffered: 0 +cluster.service1.upstream_cx_rx_bytes_total: 0 +cluster.service1.upstream_cx_total: 0 +cluster.service1.upstream_cx_tx_bytes_buffered: 0 +cluster.service1.upstream_cx_tx_bytes_total: 0 +cluster.service1.upstream_flow_control_backed_up_total: 0 +cluster.service1.upstream_flow_control_drained_total: 0 +cluster.service1.upstream_flow_control_paused_reading_total: 0 +cluster.service1.upstream_flow_control_resumed_reading_total: 0 +cluster.service1.upstream_rq_active: 0 +cluster.service1.upstream_rq_cancelled: 0 +cluster.service1.upstream_rq_maintenance_mode: 0 +cluster.service1.upstream_rq_pending_active: 0 +cluster.service1.upstream_rq_pending_failure_eject: 0 +cluster.service1.upstream_rq_pending_overflow: 0 +cluster.service1.upstream_rq_pending_total: 0 +cluster.service1.upstream_rq_per_try_timeout: 0 +cluster.service1.upstream_rq_retry: 0 +cluster.service1.upstream_rq_retry_overflow: 0 +cluster.service1.upstream_rq_retry_success: 0 +cluster.service1.upstream_rq_rx_reset: 0 +cluster.service1.upstream_rq_timeout: 0 +cluster.service1.upstream_rq_total: 0 +cluster.service1.upstream_rq_tx_reset: 0 +cluster.service1.version: 0 +cluster.service2.bind_errors: 0 +cluster.service2.lb_healthy_panic: 0 +cluster.service2.lb_local_cluster_not_ok: 0 +cluster.service2.lb_recalculate_zone_structures: 0 +cluster.service2.lb_subsets_active: 0 +cluster.service2.lb_subsets_created: 0 +cluster.service2.lb_subsets_fallback: 0 +cluster.service2.lb_subsets_removed: 0 +cluster.service2.lb_subsets_selected: 0 +cluster.service2.lb_zone_cluster_too_small: 0 +cluster.service2.lb_zone_no_capacity_left: 0 +cluster.service2.lb_zone_number_differs: 0 +cluster.service2.lb_zone_routing_all_directly: 0 +cluster.service2.lb_zone_routing_cross_zone: 0 +cluster.service2.lb_zone_routing_sampled: 0 +cluster.service2.max_host_weight: 1 +cluster.service2.membership_change: 1 +cluster.service2.membership_healthy: 1 +cluster.service2.membership_total: 1 +cluster.service2.retry_or_shadow_abandoned: 0 +cluster.service2.update_attempt: 136 +cluster.service2.update_empty: 0 +cluster.service2.update_failure: 0 +cluster.service2.update_success: 136 +cluster.service2.upstream_cx_active: 0 +cluster.service2.upstream_cx_close_notify: 0 +cluster.service2.upstream_cx_connect_attempts_exceeded: 0 +cluster.service2.upstream_cx_connect_fail: 0 +cluster.service2.upstream_cx_connect_timeout: 0 +cluster.service2.upstream_cx_destroy: 0 +cluster.service2.upstream_cx_destroy_local: 0 +cluster.service2.upstream_cx_destroy_local_with_active_rq: 0 +cluster.service2.upstream_cx_destroy_remote: 0 +cluster.service2.upstream_cx_destroy_remote_with_active_rq: 0 +cluster.service2.upstream_cx_destroy_with_active_rq: 0 +cluster.service2.upstream_cx_http1_total: 0 +cluster.service2.upstream_cx_http2_total: 0 +cluster.service2.upstream_cx_max_requests: 0 +cluster.service2.upstream_cx_none_healthy: 0 +cluster.service2.upstream_cx_overflow: 0 +cluster.service2.upstream_cx_protocol_error: 0 +cluster.service2.upstream_cx_rx_bytes_buffered: 0 +cluster.service2.upstream_cx_rx_bytes_total: 0 +cluster.service2.upstream_cx_total: 0 +cluster.service2.upstream_cx_tx_bytes_buffered: 0 +cluster.service2.upstream_cx_tx_bytes_total: 0 +cluster.service2.upstream_flow_control_backed_up_total: 0 +cluster.service2.upstream_flow_control_drained_total: 0 +cluster.service2.upstream_flow_control_paused_reading_total: 0 +cluster.service2.upstream_flow_control_resumed_reading_total: 0 +cluster.service2.upstream_rq_active: 0 +cluster.service2.upstream_rq_cancelled: 0 +cluster.service2.upstream_rq_maintenance_mode: 0 +cluster.service2.upstream_rq_pending_active: 0 +cluster.service2.upstream_rq_pending_failure_eject: 0 +cluster.service2.upstream_rq_pending_overflow: 0 +cluster.service2.upstream_rq_pending_total: 0 +cluster.service2.upstream_rq_per_try_timeout: 0 +cluster.service2.upstream_rq_retry: 0 +cluster.service2.upstream_rq_retry_overflow: 0 +cluster.service2.upstream_rq_retry_success: 0 +cluster.service2.upstream_rq_rx_reset: 0 +cluster.service2.upstream_rq_timeout: 0 +cluster.service2.upstream_rq_total: 0 +cluster.service2.upstream_rq_tx_reset: 0 +cluster.service2.version: 0 +cluster_manager.cluster_added: 2 +cluster_manager.cluster_modified: 0 +cluster_manager.cluster_removed: 0 +cluster_manager.total_clusters: 2 +filesystem.flushed_by_timer: 34 +filesystem.reopen_failed: 0 +filesystem.write_buffered: 1 +filesystem.write_completed: 1 +filesystem.write_total_buffered: 0 +http.admin.downstream_cx_active: 1 +http.admin.downstream_cx_destroy: 1 +http.admin.downstream_cx_destroy_active_rq: 0 +http.admin.downstream_cx_destroy_local: 0 +http.admin.downstream_cx_destroy_local_active_rq: 0 +http.admin.downstream_cx_destroy_remote: 1 +http.admin.downstream_cx_destroy_remote_active_rq: 0 +http.admin.downstream_cx_drain_close: 0 +http.admin.downstream_cx_http1_active: 1 +http.admin.downstream_cx_http1_total: 2 +http.admin.downstream_cx_http2_active: 0 +http.admin.downstream_cx_http2_total: 0 +http.admin.downstream_cx_idle_timeout: 0 +http.admin.downstream_cx_protocol_error: 0 +http.admin.downstream_cx_rx_bytes_buffered: 83 +http.admin.downstream_cx_rx_bytes_total: 172 +http.admin.downstream_cx_ssl_active: 0 +http.admin.downstream_cx_ssl_total: 0 +http.admin.downstream_cx_total: 2 +http.admin.downstream_cx_tx_bytes_buffered: 0 +http.admin.downstream_cx_tx_bytes_total: 306 +http.admin.downstream_cx_websocket_active: 0 +http.admin.downstream_cx_websocket_total: 0 +http.admin.downstream_flow_control_paused_reading_total: 0 +http.admin.downstream_flow_control_resumed_reading_total: 0 +http.admin.downstream_rq_1xx: 0 +http.admin.downstream_rq_2xx: 1 +http.admin.downstream_rq_3xx: 0 +http.admin.downstream_rq_4xx: 0 +http.admin.downstream_rq_5xx: 0 +http.admin.downstream_rq_active: 1 +http.admin.downstream_rq_http1_total: 2 +http.admin.downstream_rq_http2_total: 0 +http.admin.downstream_rq_non_relative_path: 0 +http.admin.downstream_rq_response_before_rq_complete: 0 +http.admin.downstream_rq_rx_reset: 0 +http.admin.downstream_rq_too_large: 0 +http.admin.downstream_rq_total: 2 +http.admin.downstream_rq_tx_reset: 0 +http.admin.downstream_rq_ws_on_non_ws_route: 0 +http.admin.rs_too_large: 0 +http.admin.tracing.tracing.client_enabled: 0 +http.admin.tracing.tracing.health_check: 0 +http.admin.tracing.tracing.not_traceable: 0 +http.admin.tracing.tracing.random_sampling: 0 +http.admin.tracing.tracing.service_forced: 0 +http.async-client.no_cluster: 0 +http.async-client.no_route: 0 +http.async-client.rq_direct_response: 0 +http.async-client.rq_redirect: 0 +http.async-client.rq_total: 0 +http.ingress_http.downstream_cx_active: 0 +http.ingress_http.downstream_cx_destroy: 0 +http.ingress_http.downstream_cx_destroy_active_rq: 0 +http.ingress_http.downstream_cx_destroy_local: 0 +http.ingress_http.downstream_cx_destroy_local_active_rq: 0 +http.ingress_http.downstream_cx_destroy_remote: 0 +http.ingress_http.downstream_cx_destroy_remote_active_rq: 0 +http.ingress_http.downstream_cx_drain_close: 0 +http.ingress_http.downstream_cx_http1_active: 0 +http.ingress_http.downstream_cx_http1_total: 0 +http.ingress_http.downstream_cx_http2_active: 0 +http.ingress_http.downstream_cx_http2_total: 0 +http.ingress_http.downstream_cx_idle_timeout: 0 +http.ingress_http.downstream_cx_protocol_error: 0 +http.ingress_http.downstream_cx_rx_bytes_buffered: 0 +http.ingress_http.downstream_cx_rx_bytes_total: 0 +http.ingress_http.downstream_cx_ssl_active: 0 +http.ingress_http.downstream_cx_ssl_total: 0 +http.ingress_http.downstream_cx_total: 0 +http.ingress_http.downstream_cx_tx_bytes_buffered: 0 +http.ingress_http.downstream_cx_tx_bytes_total: 0 +http.ingress_http.downstream_cx_websocket_active: 0 +http.ingress_http.downstream_cx_websocket_total: 0 +http.ingress_http.downstream_flow_control_paused_reading_total: 0 +http.ingress_http.downstream_flow_control_resumed_reading_total: 0 +http.ingress_http.downstream_rq_1xx: 0 +http.ingress_http.downstream_rq_2xx: 0 +http.ingress_http.downstream_rq_3xx: 0 +http.ingress_http.downstream_rq_4xx: 0 +http.ingress_http.downstream_rq_5xx: 0 +http.ingress_http.downstream_rq_active: 0 +http.ingress_http.downstream_rq_http1_total: 0 +http.ingress_http.downstream_rq_http2_total: 0 +http.ingress_http.downstream_rq_non_relative_path: 0 +http.ingress_http.downstream_rq_response_before_rq_complete: 0 +http.ingress_http.downstream_rq_rx_reset: 0 +http.ingress_http.downstream_rq_too_large: 0 +http.ingress_http.downstream_rq_total: 0 +http.ingress_http.downstream_rq_tx_reset: 0 +http.ingress_http.downstream_rq_ws_on_non_ws_route: 0 +http.ingress_http.no_cluster: 0 +http.ingress_http.no_route: 0 +http.ingress_http.rq_direct_response: 0 +http.ingress_http.rq_redirect: 0 +http.ingress_http.rq_total: 0 +http.ingress_http.rs_too_large: 0 +http.ingress_http.tracing.client_enabled: 0 +http.ingress_http.tracing.health_check: 0 +http.ingress_http.tracing.not_traceable: 0 +http.ingress_http.tracing.random_sampling: 0 +http.ingress_http.tracing.service_forced: 0 +listener.0.0.0.0_80.downstream_cx_active: 0 +listener.0.0.0.0_80.downstream_cx_destroy: 0 +listener.0.0.0.0_80.downstream_cx_total: 0 +listener.0.0.0.0_80.http.ingress_http.downstream_rq_1xx: 0 +listener.0.0.0.0_80.http.ingress_http.downstream_rq_2xx: 0 +listener.0.0.0.0_80.http.ingress_http.downstream_rq_3xx: 0 +listener.0.0.0.0_80.http.ingress_http.downstream_rq_4xx: 0 +listener.0.0.0.0_80.http.ingress_http.downstream_rq_5xx: 0 +listener.admin.downstream_cx_active: 1 +listener.admin.downstream_cx_destroy: 1 +listener.admin.downstream_cx_total: 2 +listener.admin.http.admin.downstream_rq_1xx: 0 +listener.admin.http.admin.downstream_rq_2xx: 1 +listener.admin.http.admin.downstream_rq_3xx: 0 +listener.admin.http.admin.downstream_rq_4xx: 0 +listener.admin.http.admin.downstream_rq_5xx: 0 +listener_manager.listener_added: 1 +listener_manager.listener_create_failure: 0 +listener_manager.listener_create_success: 1 +listener_manager.listener_modified: 0 +listener_manager.listener_removed: 0 +listener_manager.total_listeners_active: 1 +listener_manager.total_listeners_draining: 0 +listener_manager.total_listeners_warming: 0 +server.days_until_first_cert_expiring: 2147483647 +server.live: 1 +server.memory_allocated: 3142656 +server.memory_heap_size: 4194304 +server.parent_connections: 0 +server.total_connections: 0 +server.uptime: 676 +server.version: 6100182 +server.watchdog_mega_miss: 0 +server.watchdog_miss: 0 +stats.overflow: 0 diff --git a/envoy/tests/test_envoy.py b/envoy/tests/test_envoy.py new file mode 100644 index 0000000000000..086a912025971 --- /dev/null +++ b/envoy/tests/test_envoy.py @@ -0,0 +1,66 @@ +# (C) Datadog, Inc. 2018 +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) +import os +try: + from functools import lru_cache +except ImportError: + from backports.functools_lru_cache import lru_cache + +import mock +import pytest +from datadog_checks.stubs import aggregator as _aggregator + +from datadog_checks.envoy import Envoy +from datadog_checks.envoy.metrics import METRIC_PREFIX, METRICS + +FIXTURE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'fixtures') + + +class MockResponse: + def __init__(self, content, status_code): + self.content = content + self.status_code = status_code + + +@lru_cache(maxsize=None) +def response(kind): + if kind == 'bad': + return MockResponse(b'', 500) + else: + file_path = os.path.join(FIXTURE_DIR, kind) + if os.path.isfile(file_path): + with open(file_path, 'rb') as f: + return MockResponse(f.read(), 200) + else: + raise IOError('File `{}` does not exist.'.format(file_path)) + + +@pytest.fixture +def aggregator(): + _aggregator.reset() + return _aggregator + + +class TestEnvoy: + CHECK_NAME = 'envoy' + INSTANCES = { + 'main': { + 'stats_url': 'http://localhost:80/stats', + }, + } + + def test_success(self, aggregator): + instance = self.INSTANCES['main'] + c = Envoy(self.CHECK_NAME, None, {}, [instance]) + + with mock.patch('requests.get', return_value=response('multiple_services')): + c.check(instance) + + metrics_collected = 0 + for metric in METRICS.keys(): + metrics_collected += len(aggregator.metrics(METRIC_PREFIX + metric)) + + # The 244 is how many metrics are collected from our + # particular example fixture in the first release. + assert metrics_collected >= 244 diff --git a/envoy/tests/test_metrics.py b/envoy/tests/test_metrics.py new file mode 100644 index 0000000000000..ce10a97666105 --- /dev/null +++ b/envoy/tests/test_metrics.py @@ -0,0 +1,10 @@ +from datadog_checks.envoy.metrics import METRIC_PREFIX, METRIC_TREE, METRICS +from datadog_checks.envoy.utils import make_metric_tree + + +def test_metric_prefix(): + assert METRIC_PREFIX == 'envoy.' + + +def test_metric_tree(): + assert METRIC_TREE == make_metric_tree(METRICS) diff --git a/envoy/tests/test_parser.py b/envoy/tests/test_parser.py new file mode 100644 index 0000000000000..595da322224e0 --- /dev/null +++ b/envoy/tests/test_parser.py @@ -0,0 +1,479 @@ +import pytest + +from datadog_checks.envoy.errors import UnknownMetric +from datadog_checks.envoy.metrics import METRIC_PREFIX, METRICS +from datadog_checks.envoy.parser import parse_metric, reassemble_addresses + + +class TestReassembleAddresses: + def test_correct(self): + seq = ['0', '0', '0', '0_80', 'ingress_http'] + assert reassemble_addresses(seq) == ['0.0.0.0:80', 'ingress_http'] + + def test_reassemble_addresses_empty(self): + assert reassemble_addresses([]) == [] + + +class TestParseMetric: + def test_unknown(self): + with pytest.raises(UnknownMetric): + parse_metric('foo.bar') + + def test_runtime(self): + metric = 'runtime.num_keys' + tags = METRICS[metric]['tags'] + + assert parse_metric(metric) == ( + METRIC_PREFIX + metric, + list(tags), + METRICS[metric]['method'] + ) + + def test_cds(self): + metric = 'cluster_manager.cds.config_reload' + tags = METRICS[metric]['tags'] + + assert parse_metric(metric) == ( + METRIC_PREFIX + metric, + list(tags), + METRICS[metric]['method'] + ) + + def test_http_router_filter(self): + metric = 'http{}.rq_total' + untagged_metric = metric.format('') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_stat_prefix' + tagged_metric = metric.format('.{}'.format(tag0)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0)], + METRICS[untagged_metric]['method'] + ) + + def test_http_router_filter_vhost(self): + metric = 'vhost{}.vcluster{}.upstream_rq_time' + untagged_metric = metric.format('', '') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_vhost_name' + tag1 = 'some_vcluster_name' + tagged_metric = metric.format('.{}'.format(tag0), '.{}'.format(tag1)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0), '{}:{}'.format(tags[1], tag1)], + METRICS[untagged_metric]['method'] + ) + + def test_http_rate_limit(self): + metric = 'cluster{}.ratelimit.ok' + untagged_metric = metric.format('') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_route_target_cluster' + tagged_metric = metric.format('.{}'.format(tag0)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0)], + METRICS[untagged_metric]['method'] + ) + + def test_ip_tagging(self): + metric = 'http{}.ip_tagging{}.hit' + untagged_metric = metric.format('', '') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_stat_prefix' + tag1 = 'some_tag_name' + tagged_metric = metric.format('.{}'.format(tag0), '.{}'.format(tag1)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0), '{}:{}'.format(tags[1], tag1)], + METRICS[untagged_metric]['method'] + ) + + def test_grpc(self): + metric = 'cluster{}.grpc{}{}.total' + untagged_metric = metric.format('', '', '') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_route_target_cluster' + tag1 = 'some_grpc_service' + tag2 = 'some_grpc_method' + tagged_metric = metric.format('.{}'.format(tag0), '.{}'.format(tag1), '.{}'.format(tag2)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0), '{}:{}'.format(tags[1], tag1), '{}:{}'.format(tags[2], tag2)], + METRICS[untagged_metric]['method'] + ) + + def test_dynamodb_operation(self): + metric = 'http{}.dynamodb.operation{}.upstream_rq_total' + untagged_metric = metric.format('', '') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_stat_prefix' + tag1 = 'some_operation_name' + tagged_metric = metric.format('.{}'.format(tag0), '.{}'.format(tag1)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0), '{}:{}'.format(tags[1], tag1)], + METRICS[untagged_metric]['method'] + ) + + def test_dynamodb_table(self): + metric = 'http{}.dynamodb.table{}.upstream_rq_total' + untagged_metric = metric.format('', '') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_stat_prefix' + tag1 = 'some_table_name' + tagged_metric = metric.format('.{}'.format(tag0), '.{}'.format(tag1)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0), '{}:{}'.format(tags[1], tag1)], + METRICS[untagged_metric]['method'] + ) + + def test_dynamodb_error(self): + metric = 'http{}.dynamodb.error{}{}' + untagged_metric = metric.format('', '', '') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_stat_prefix' + tag1 = 'some_table_name' + tag2 = 'error_type' + tagged_metric = metric.format('.{}'.format(tag0), '.{}'.format(tag1), '.{}'.format(tag2)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0), '{}:{}'.format(tags[1], tag1), '{}:{}'.format(tags[2], tag2)], + METRICS[untagged_metric]['method'] + ) + + def test_http_buffer_filter(self): + metric = 'http{}.buffer.rq_timeout' + untagged_metric = metric.format('') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_stat_prefix' + tagged_metric = metric.format('.{}'.format(tag0)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0)], + METRICS[untagged_metric]['method'] + ) + + def test_rds(self): + metric = 'http{}.rds{}.config_reload' + untagged_metric = metric.format('', '') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_stat_prefix' + tag1 = 'some_route_config_name' + tagged_metric = metric.format('.{}'.format(tag0), '.{}'.format(tag1)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0), '{}:{}'.format(tags[1], tag1)], + METRICS[untagged_metric]['method'] + ) + + def test_tcp_proxy(self): + metric = 'tcp{}.downstream_cx_total' + untagged_metric = metric.format('') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_stat_prefix' + tagged_metric = metric.format('.{}'.format(tag0)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0)], + METRICS[untagged_metric]['method'] + ) + + def test_tls(self): + metric = 'auth.clientssl{}.update_success' + untagged_metric = metric.format('') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_stat_prefix' + tagged_metric = metric.format('.{}'.format(tag0)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0)], + METRICS[untagged_metric]['method'] + ) + + def test_network_rate_limit(self): + metric = 'ratelimit{}.total' + untagged_metric = metric.format('') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_stat_prefix' + tagged_metric = metric.format('.{}'.format(tag0)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0)], + METRICS[untagged_metric]['method'] + ) + + def test_redis(self): + metric = 'redis{}.downstream_rq_total' + untagged_metric = metric.format('') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_stat_prefix' + tagged_metric = metric.format('.{}'.format(tag0)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0)], + METRICS[untagged_metric]['method'] + ) + + def test_redis_splitter(self): + metric = 'redis{}.splitter.invalid_request' + untagged_metric = metric.format('') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_stat_prefix' + tagged_metric = metric.format('.{}'.format(tag0)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0)], + METRICS[untagged_metric]['method'] + ) + + def test_redis_command(self): + metric = 'redis{}.command{}.total' + untagged_metric = metric.format('', '') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_stat_prefix' + tag1 = 'some_command' + tagged_metric = metric.format('.{}'.format(tag0), '.{}'.format(tag1)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0), '{}:{}'.format(tags[1], tag1)], + METRICS[untagged_metric]['method'] + ) + + def test_mongo(self): + metric = 'mongo{}.op_insert' + untagged_metric = metric.format('') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_stat_prefix' + tagged_metric = metric.format('.{}'.format(tag0)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0)], + METRICS[untagged_metric]['method'] + ) + + def test_mongo_command(self): + metric = 'mongo{}.cmd{}.total' + untagged_metric = metric.format('', '') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_stat_prefix' + tag1 = 'some_command' + tagged_metric = metric.format('.{}'.format(tag0), '.{}'.format(tag1)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0), '{}:{}'.format(tags[1], tag1)], + METRICS[untagged_metric]['method'] + ) + + def test_mongo_collection(self): + metric = 'mongo{}.collection{}.query.total' + untagged_metric = metric.format('', '') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_stat_prefix' + tag1 = 'some_collection' + tagged_metric = metric.format('.{}'.format(tag0), '.{}'.format(tag1)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0), '{}:{}'.format(tags[1], tag1)], + METRICS[untagged_metric]['method'] + ) + + def test_listener(self): + metric = 'listener{}.ssl.cipher{}' + untagged_metric = metric.format('', '') + tags = METRICS[untagged_metric]['tags'] + tag0 = '0.0.0.0_80' + tag0_reassembled = tag0.replace('_', ':') + tag1 = 'some_cipher' + tagged_metric = metric.format('.{}'.format(tag0), '.{}'.format(tag1)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0_reassembled), '{}:{}'.format(tags[1], tag1)], + METRICS[untagged_metric]['method'] + ) + + def test_listener_manager(self): + metric = 'listener_manager.listener_added' + tags = METRICS[metric]['tags'] + + assert parse_metric(metric) == ( + METRIC_PREFIX + metric, + list(tags), + METRICS[metric]['method'] + ) + + def test_http(self): + metric = 'http{}.downstream_cx_total' + untagged_metric = metric.format('') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_stat_prefix' + tagged_metric = metric.format('.{}'.format(tag0)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0)], + METRICS[untagged_metric]['method'] + ) + + def test_http_user_agent(self): + metric = 'http{}.user_agent{}.downstream_cx_total' + untagged_metric = metric.format('', '') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_stat_prefix' + tag1 = 'some_user_agent' + tagged_metric = metric.format('.{}'.format(tag0), '.{}'.format(tag1)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0), '{}:{}'.format(tags[1], tag1)], + METRICS[untagged_metric]['method'] + ) + + def test_http_listener(self): + metric = 'listener{}.http{}.downstream_rq_2xx' + untagged_metric = metric.format('', '') + tags = METRICS[untagged_metric]['tags'] + tag0 = '0.0.0.0_80' + tag0_reassembled = tag0.replace('_', ':') + tag1 = 'some_stat_prefix' + tagged_metric = metric.format('.{}'.format(tag0), '.{}'.format(tag1)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0_reassembled), '{}:{}'.format(tags[1], tag1)], + METRICS[untagged_metric]['method'] + ) + + def test_http2(self): + metric = 'http2.rx_reset' + tags = METRICS[metric]['tags'] + + assert parse_metric(metric) == ( + METRIC_PREFIX + metric, + list(tags), + METRICS[metric]['method'] + ) + + def test_cluster_manager(self): + metric = 'cluster_manager.cluster_added' + tags = METRICS[metric]['tags'] + + assert parse_metric(metric) == ( + METRIC_PREFIX + metric, + list(tags), + METRICS[metric]['method'] + ) + + def test_cluster(self): + metric = 'cluster{}.upstream_cx_total' + untagged_metric = metric.format('') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_name' + tagged_metric = metric.format('.{}'.format(tag0)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0)], + METRICS[untagged_metric]['method'] + ) + + def test_cluster_health_check(self): + metric = 'cluster{}.health_check.healthy' + untagged_metric = metric.format('') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_name' + tagged_metric = metric.format('.{}'.format(tag0)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0)], + METRICS[untagged_metric]['method'] + ) + + def test_cluster_outlier_detection(self): + metric = 'cluster{}.outlier_detection.ejections_enforced_total' + untagged_metric = metric.format('') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_name' + tagged_metric = metric.format('.{}'.format(tag0)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0)], + METRICS[untagged_metric]['method'] + ) + + def test_cluster_dynamic_http(self): + metric = 'cluster{}.upstream_rq_time' + untagged_metric = metric.format('') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_name' + tagged_metric = metric.format('.{}'.format(tag0)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0)], + METRICS[untagged_metric]['method'] + ) + + def test_cluster_dynamic_http_zones(self): + metric = 'cluster{}.zone{}{}.upstream_rq_time' + untagged_metric = metric.format('', '', '') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_name' + tag1 = 'some_table_name' + tag2 = 'some_to_zone' + tagged_metric = metric.format('.{}'.format(tag0), '.{}'.format(tag1), '.{}'.format(tag2)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0), '{}:{}'.format(tags[1], tag1), '{}:{}'.format(tags[2], tag2)], + METRICS[untagged_metric]['method'] + ) + + def test_cluster_load_balancer(self): + metric = 'cluster{}.lb_healthy_panic' + untagged_metric = metric.format('') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_name' + tagged_metric = metric.format('.{}'.format(tag0)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0)], + METRICS[untagged_metric]['method'] + ) + + def test_cluster_load_balancer_subsets(self): + metric = 'cluster{}.lb_subsets_active' + untagged_metric = metric.format('') + tags = METRICS[untagged_metric]['tags'] + tag0 = 'some_name' + tagged_metric = metric.format('.{}'.format(tag0)) + + assert parse_metric(tagged_metric) == ( + METRIC_PREFIX + untagged_metric, + ['{}:{}'.format(tags[0], tag0)], + METRICS[untagged_metric]['method'] + ) diff --git a/envoy/tests/test_utils.py b/envoy/tests/test_utils.py new file mode 100644 index 0000000000000..bb368f0dee8ff --- /dev/null +++ b/envoy/tests/test_utils.py @@ -0,0 +1,17 @@ +from datadog_checks.envoy.utils import make_metric_tree + + +def test_make_metric_tree(): + metrics = { + 'http.admin.downstream_cx_total': '', + 'http.admin.rs_too_large': '', + } + + assert make_metric_tree(metrics) == { + 'http': { + 'admin': { + 'downstream_cx_total': {}, + 'rs_too_large': {}, + }, + }, + } diff --git a/envoy/tox.ini b/envoy/tox.ini new file mode 100644 index 0000000000000..ac52606e476da --- /dev/null +++ b/envoy/tox.ini @@ -0,0 +1,26 @@ +[tox] +minversion = 2.0 +basepython = py27 +envlist = + envoy + flake8 + +[testenv] +platform = linux|darwin|win32 + +[testenv:envoy] +deps = + ../datadog-checks-base + -rrequirements-dev.txt +commands = + pip install --require-hashes -r requirements.txt + pytest + +[testenv:flake8] +skip_install = true +deps = flake8 +commands = flake8 . + +[flake8] +exclude = .eggs,.tox,build +max-line-length = 120