diff --git a/envoy/changelog.d/16313.added b/envoy/changelog.d/16313.added new file mode 100644 index 0000000000000..674b23d56098c --- /dev/null +++ b/envoy/changelog.d/16313.added @@ -0,0 +1 @@ +Add local rate limit metrics \ No newline at end of file diff --git a/envoy/datadog_checks/envoy/check.py b/envoy/datadog_checks/envoy/check.py index 67876bc283c5d..7b7ebbb1e5e45 100644 --- a/envoy/datadog_checks/envoy/check.py +++ b/envoy/datadog_checks/envoy/check.py @@ -16,6 +16,7 @@ LABEL_MAP = { 'cluster_name': 'envoy_cluster', 'envoy_cluster_name': 'envoy_cluster', + 'envoy_local_http_ratelimit_prefix': 'stat_prefix', # local rate limit 'envoy_http_conn_manager_prefix': 'stat_prefix', # tracing 'envoy_listener_address': 'address', # listener 'envoy_virtual_cluster': 'virtual_envoy_cluster', # vhost @@ -89,6 +90,26 @@ 'metric_type': 'monotonic_count', 'new_name': 'listener.downstream_cx.count', }, + r'envoy_(.+)_http_local_rate_limit_enabled$': { + 'label_name': 'stat_prefix', + 'metric_type': 'monotonic_count', + 'new_name': 'http.local_rate_limit_enabled.count', + }, + r'envoy_(.+)_http_local_rate_limit_enforced$': { + 'label_name': 'stat_prefix', + 'metric_type': 'monotonic_count', + 'new_name': 'http.local_rate_limit_enforced.count', + }, + r'envoy_(.+)_http_local_rate_limit_ok$': { + 'label_name': 'stat_prefix', + 'metric_type': 'monotonic_count', + 'new_name': 'http.local_rate_limit_ok.count', + }, + r'envoy_(.+)_http_local_rate_limit_rate_limited$': { + 'label_name': 'stat_prefix', + 'metric_type': 'monotonic_count', + 'new_name': 'http.local_rate_limit_rate_limited.count', + }, } diff --git a/envoy/datadog_checks/envoy/metrics.py b/envoy/datadog_checks/envoy/metrics.py index 6ff1cdfc6c1c7..9d6d5d73b0705 100644 --- a/envoy/datadog_checks/envoy/metrics.py +++ b/envoy/datadog_checks/envoy/metrics.py @@ -1,7 +1,7 @@ # (C) Datadog, Inc. 2018-present # All rights reserved # Licensed under a 3-clause BSD style license (see LICENSE) -from .utils import make_metric_tree +from .utils import make_metric_tree, modify_metrics_dict METRIC_PREFIX = 'envoy.' @@ -372,6 +372,10 @@ 'envoy_http_rbac_denied': 'http.rbac_denied', 'envoy_http_rbac_shadow_allowed': 'http.rbac_shadow_allowed', 'envoy_http_rbac_shadow_denied': 'http.rbac_shadow_denied', + 'envoy_http_local_rate_limit_enabled': 'http.local_rate_limit_enabled', + 'envoy_http_local_rate_limit_enforced': 'http.local_rate_limit_enforced', + 'envoy_http_local_rate_limit_rate_limited': 'http.local_rate_limit_rate_limited', + 'envoy_http_local_rate_limit_ok': 'http.local_rate_limit_ok', } # fmt: off @@ -3857,7 +3861,41 @@ ), 'method': 'monotonic_count', }, + # "*." to match at the beginning of raw metric if it doesn't have a standard name + '*.http_local_rate_limit.enabled': { + 'tags': ( + ('stat_prefix',), + (), + (), + ), + 'method': 'monotonic_count', + }, + '*.http_local_rate_limit.enforced': { + 'tags': ( + ('stat_prefix',), + (), + (), + ), + 'method': 'monotonic_count', + }, + '*.http_local_rate_limit.rate_limited': { + 'tags': ( + ('stat_prefix',), + (), + (), + ), + 'method': 'monotonic_count', + }, + '*.http_local_rate_limit.ok': { + 'tags': ( + ('stat_prefix',), + (), + (), + ), + 'method': 'monotonic_count', + }, } # fmt: on +MOD_METRICS = modify_metrics_dict(METRICS) METRIC_TREE = make_metric_tree(METRICS) diff --git a/envoy/datadog_checks/envoy/parser.py b/envoy/datadog_checks/envoy/parser.py index df9f0d8f5846f..105858a7d16da 100644 --- a/envoy/datadog_checks/envoy/parser.py +++ b/envoy/datadog_checks/envoy/parser.py @@ -9,7 +9,7 @@ from six.moves import range, zip from .errors import UnknownMetric, UnknownTags -from .metrics import METRIC_PREFIX, METRIC_TREE, METRICS +from .metrics import METRIC_PREFIX, METRIC_TREE, MOD_METRICS HISTOGRAM = re.compile(r'([P0-9.]+)\(([^,]+)') PERCENTILE_SUFFIX = { @@ -65,6 +65,10 @@ def _parse_metric(metric, metric_mapping, skip_part=None): else: tag_value_builder.append(metric_part) tags_to_build += 1 + # Allows for the wildcard usage for the scenario in which the raw metric starts with a configurable name. + # E.g.: stat_prefix.http_local_rate_limit.ok + if "*" in metric_mapping: + metric_mapping = metric_mapping["*"] return metric_parts, tag_value_builder, tag_names, tag_values, unknown_tags, tags_to_build, metric_mapping @@ -78,11 +82,12 @@ def parse_metric(metric, retry=False, metric_mapping=METRIC_TREE, disable_legacy 'listener.0.0.0.0_80.downstream_cx_total' -> ('listener.downstream_cx_total', ['address:0.0.0.0_80'], 'count') """ + metric_parts, tag_value_builder, tag_names, tag_values, unknown_tags, tags_to_build, last_mapping = _parse_metric( metric, metric_mapping ) parsed_metric = '.'.join(metric_parts) - if parsed_metric not in METRICS: + if parsed_metric not in MOD_METRICS: if retry: skip_parts = [] # Retry parsing for metrics by skipping the last matched metric part @@ -102,7 +107,7 @@ def parse_metric(metric, retry=False, metric_mapping=METRIC_TREE, disable_legacy last_mapping, ) = _parse_metric(metric, metric_mapping, skip_part) parsed_metric = '.'.join(metric_parts) - if parsed_metric in METRICS: + if parsed_metric in MOD_METRICS: break else: raise UnknownMetric @@ -133,7 +138,7 @@ def parse_metric(metric, retry=False, metric_mapping=METRIC_TREE, disable_legacy tags = ['{}:{}'.format(tag_name, tag_value) for tag_name, tag_value in zip(tag_names, tag_values)] - return METRIC_PREFIX + parsed_metric, tags, METRICS[parsed_metric]['method'] + return METRIC_PREFIX + parsed_metric, tags, MOD_METRICS[parsed_metric]['method'] def construct_tag_values(tag_builder, num_tags): diff --git a/envoy/datadog_checks/envoy/utils.py b/envoy/datadog_checks/envoy/utils.py index 74566a594d09b..88b7f4233e7c9 100644 --- a/envoy/datadog_checks/envoy/utils.py +++ b/envoy/datadog_checks/envoy/utils.py @@ -85,3 +85,20 @@ def _get_server_info(server_info_url, log, http): return None return raw_version + + +def modify_metrics_dict(metrics): + # This function removes the wildcard from the metric list defined in metrics.py. Parser.py compares the compiled + # metric with the metrics lists and if the entry is not found, it will raise an UnknowMetric error. Since the "*." + # is used for wildcard matching, the comparison will always be false. E.g.: + # "*.http_local_rate_limit.enabled" =/= "http_local_rate_limit.enabled + # This is needed for metrics that start with a configurable namespace such as: + # `.http_local_rate_limit.enabled` and parsed as http_local_rate_limit.enabled with tag + # `stat_prefix=` in the parser.py + mod_metrics_dict = {} + + for key, value in metrics.items(): + new_key = key.replace('*.', '') + mod_metrics_dict[new_key] = value + + return mod_metrics_dict diff --git a/envoy/metadata.csv b/envoy/metadata.csv index 14dac077194c6..4dff22a33558f 100644 --- a/envoy/metadata.csv +++ b/envoy/metadata.csv @@ -28,7 +28,7 @@ envoy.cluster.http2.keepalive_timeout.count,count,,,,[OpenMetrics V2],0,envoy,, envoy.cluster.http2.metadata_empty_frames.count,count,,,,[OpenMetrics V2],0,envoy,, envoy.cluster.http2.outbound_control_flood.count,count,,connection,,[OpenMetrics V2] Total number of connections terminated for exceeding the limit on outbound frames of types PING/SETTINGS/RST_STREAM,-1,envoy,, envoy.cluster.http2.outbound_flood.count,count,,connection,,[OpenMetrics V2] Total number of connections terminated for exceeding the limit on outbound frames of all types,-1,envoy,, -envoy.cluster.http2.pending_send_bytes,gauge,,byte,,[OpenMetrics V2]Currently buffered body data in bytes waiting to be written when stream/connection window is opened. ,0,envoy,, +envoy.cluster.http2.pending_send_bytes,gauge,,byte,,[OpenMetrics V2]Currently buffered body data in bytes waiting to be written when stream/connection window is opened.,0,envoy,, envoy.cluster.http2.requests_rejected_with_underscores_in_headers.count,count,,request,,[OpenMetrics V2] Total numbers of rejected requests due to header names containing underscores. This action is configured by setting the headers_with_underscores_action config setting.,0,envoy,, envoy.cluster.http2.rx_messaging_error.count,count,,error,,[OpenMetrics V2] Total number of invalid received frames that violated section 8 of the HTTP/2 spec,-1,envoy,, envoy.cluster.http2.rx_reset.count,count,,message,,[OpenMetrics V2] Total number of reset stream frames received by Envoy,0,envoy,, @@ -534,6 +534,10 @@ envoy.http.rbac_allowed.count,count,,request,,[OpenMetrics V2] Total requests th envoy.http.rbac_denied.count,count,,request,,[OpenMetrics V2] Total requests that were denied access,-1,envoy,, envoy.http.rbac_shadow_allowed.count,count,,request,,[OpenMetrics V2] Total requests that would be allowed access by the filter's shadow rules,-1,envoy,, envoy.http.rbac_shadow_denied.count,count,,request,,[OpenMetrics V2] Total requests that would be denied access by the filter's shadow rules,-1,envoy,, +envoy.http.local_rate_limit_enabled.count,count,,request,,[OpenMetrics V2] Total number of requests for which the rate limiter was consulted,-1,envoy,, +envoy.http.local_rate_limit_enforced.count,count,,request,,[OpenMetrics V2] Total number of requests for which rate limiting was applied (e.g.: 429 returned),-1,envoy,, +envoy.http.local_rate_limit_rate_limited.count,count,,request,,[OpenMetrics V2] Total responses without an available token (but not necessarily enforced),-1,envoy,, +envoy.http.local_rate_limit_ok.count,count,,request,,[OpenMetrics V2] Total under limit responses from the token bucket,-1,envoy,, envoy.http.rs_too_large,count,,error,,[Legacy] Total response errors due to buffering an overly large body,-1,envoy,, envoy.http.user_agent.downstream_cx_total,count,,connection,,[Legacy] Total connections,0,envoy,, envoy.http.user_agent.downstream_cx_destroy_remote_active_rq,count,,connection,,[Legacy] Total connections destroyed remotely with active requests,-1,envoy,, @@ -965,3 +969,7 @@ envoy.access_logs.grpc_access_log.logs_dropped,count,,,,[Legacy] Number of GRPC envoy.access_logs.grpc_access_log.logs_written,count,,,,[Legacy] Number of GRPC Access Logs written,-1,envoy,, envoy.access_logs.grpc_access_log.logs_dropped.count,count,,,,[OpenMetrics V2] Count of GRPC Access Logs dropped,-1,envoy,, envoy.access_logs.grpc_access_log.logs_written.count,count,,,,[OpenMetrics V2] Count of GRPC Access Logs written,-1,envoy,, +envoy.http_local_rate_limit.enabled,count,,request,,[Legacy] Total number of requests for which the rate limiter was consulted,-1,envoy,, +envoy.http_local_rate_limit.enforced,count,,request,,[Legacy] Total number of requests for which rate limiting was applied (e.g.: 429 returned),-1,envoy,, +envoy.http_local_rate_limit.rate_limited,count,,request,,[Legacy] Total number of responses without an available token (but not necessarily enforced),-1,envoy,, +envoy.http_local_rate_limit.ok,count,,request,,[Legacy] Total number of under the limit responses from the token bucket,-1,envoy,, diff --git a/envoy/tests/common.py b/envoy/tests/common.py index ab77c9c9959ee..749cde119afe5 100644 --- a/envoy/tests/common.py +++ b/envoy/tests/common.py @@ -374,7 +374,16 @@ "vhost.vcluster.upstream_rq.count", ] -FLAKY_METRICS = { +LOCAL_RATE_LIMIT_METRICS = [ + "http.local_rate_limit_enabled.count", + "http.local_rate_limit_enforced.count", + "http.local_rate_limit_rate_limited.count", + "http.local_rate_limit_ok.count", +] + +RATE_LIMIT_STAT_PREFIX_TAG = 'stat_prefix:http_local_rate_limiter' + +FLAKY_METRICS = [ "listener.downstream_cx_active", "listener.downstream_cx_destroy.count", "cluster.internal.upstream_rq.count", @@ -384,9 +393,9 @@ "cluster.upstream_rq_xx.count", "access_logs.grpc_access_log.logs_written.count", "access_logs.grpc_access_log.logs_dropped.count", -} +] -MOCKED_PROMETHEUS_METRICS = { +MOCKED_PROMETHEUS_METRICS = [ "cluster.assignment_stale.count", "cluster.assignment_timeout_received.count", "cluster.bind_errors.count", @@ -602,6 +611,10 @@ "http.tracing.not_traceable.count", "http.tracing.random_sampling.count", "http.tracing.service_forced.count", + "http.local_rate_limit_enabled.count", + "http.local_rate_limit_enforced.count", + "http.local_rate_limit_rate_limited.count", + "http.local_rate_limit_ok.count", "listener.admin.downstream_cx.count", "listener.admin.downstream_cx_active", "listener.admin.downstream_cx_destroy.count", @@ -696,7 +709,7 @@ "tcp.on_demand_cluster_timeout.count", "tcp.upstream_flush.count", "tcp.upstream_flush_active", -} +] def get_fixture_path(filename): diff --git a/envoy/tests/docker/api_v3/front-envoy.yaml b/envoy/tests/docker/api_v3/front-envoy.yaml index e3664ad836f1a..d3d78d4baaff5 100644 --- a/envoy/tests/docker/api_v3/front-envoy.yaml +++ b/envoy/tests/docker/api_v3/front-envoy.yaml @@ -51,7 +51,33 @@ static_resources: envoy.filters.http.ext_authz: "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute disabled: true + - match: + prefix: "/ratelimit/service/1" + route: + cluster: service1 + typed_per_filter_config: + envoy.filters.http.local_ratelimit: + "@type": type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit + stat_prefix: http_local_rate_limiter + token_bucket: + max_tokens: 10000 + tokens_per_fill: 1000 + fill_interval: 1s + filter_enabled: + runtime_key: local_rate_limit_enabled + default_value: + numerator: 100 + denominator: HUNDRED + filter_enforced: + runtime_key: local_rate_limit_enforced + default_value: + numerator: 100 + denominator: HUNDRED http_filters: + - name: envoy.filters.http.local_ratelimit + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit + stat_prefix: http_local_rate_limiter - name: envoy.filters.http.rbac typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC diff --git a/envoy/tests/fixtures/legacy/local_rate_limit.txt b/envoy/tests/fixtures/legacy/local_rate_limit.txt new file mode 100644 index 0000000000000..1d15491b9d19c --- /dev/null +++ b/envoy/tests/fixtures/legacy/local_rate_limit.txt @@ -0,0 +1,8 @@ +http_local_rate_limiter.http_local_rate_limit.enabled: 0 +http_local_rate_limiter.http_local_rate_limit.enforced: 0 +http_local_rate_limiter.http_local_rate_limit.ok: 0 +http_local_rate_limiter.http_local_rate_limit.rate_limited: 0 +foo.http_local_rate_limit.enabled: 0 +foo.http_local_rate_limit.enforced: 0 +foo.http_local_rate_limit.ok: 0 +foo.http_local_rate_limit.rate_limited: 0 \ No newline at end of file diff --git a/envoy/tests/fixtures/multiple_services b/envoy/tests/fixtures/legacy/multiple_services similarity index 100% rename from envoy/tests/fixtures/multiple_services rename to envoy/tests/fixtures/legacy/multiple_services diff --git a/envoy/tests/fixtures/server_info_api_v2 b/envoy/tests/fixtures/legacy/server_info_api_v2 similarity index 100% rename from envoy/tests/fixtures/server_info_api_v2 rename to envoy/tests/fixtures/legacy/server_info_api_v2 diff --git a/envoy/tests/fixtures/server_info_api_v3 b/envoy/tests/fixtures/legacy/server_info_api_v3 similarity index 100% rename from envoy/tests/fixtures/server_info_api_v3 rename to envoy/tests/fixtures/legacy/server_info_api_v3 diff --git a/envoy/tests/fixtures/server_info_before_1_9 b/envoy/tests/fixtures/legacy/server_info_before_1_9 similarity index 100% rename from envoy/tests/fixtures/server_info_before_1_9 rename to envoy/tests/fixtures/legacy/server_info_before_1_9 diff --git a/envoy/tests/fixtures/server_info_invalid b/envoy/tests/fixtures/legacy/server_info_invalid similarity index 100% rename from envoy/tests/fixtures/server_info_invalid rename to envoy/tests/fixtures/legacy/server_info_invalid diff --git a/envoy/tests/fixtures/stat_prefix b/envoy/tests/fixtures/legacy/stat_prefix similarity index 100% rename from envoy/tests/fixtures/stat_prefix rename to envoy/tests/fixtures/legacy/stat_prefix diff --git a/envoy/tests/fixtures/unknown_metrics b/envoy/tests/fixtures/legacy/unknown_metrics similarity index 100% rename from envoy/tests/fixtures/unknown_metrics rename to envoy/tests/fixtures/legacy/unknown_metrics diff --git a/envoy/tests/fixtures/openmetrics.txt b/envoy/tests/fixtures/openmetrics/openmetrics.txt similarity index 99% rename from envoy/tests/fixtures/openmetrics.txt rename to envoy/tests/fixtures/openmetrics/openmetrics.txt index 806b0dd67d08f..e6d87498dcd21 100644 --- a/envoy/tests/fixtures/openmetrics.txt +++ b/envoy/tests/fixtures/openmetrics/openmetrics.txt @@ -639,6 +639,14 @@ envoy_cluster_manager_cds_control_plane_rate_limit_enforced{} 0 envoy_cluster_manager_cds_update_attempt{} 13 # TYPE envoy_cluster_manager_cds_update_failure counter envoy_cluster_manager_cds_update_failure{} 12 +# TYPE envoy_http_local_rate_limiter_http_local_rate_limit_enabled counter +envoy_http_local_rate_limiter_http_local_rate_limit_enabled{} 0 +# TYPE envoy_http_local_rate_limiter_http_local_rate_limit_enforced counter +envoy_http_local_rate_limiter_http_local_rate_limit_enforced{} 0 +# TYPE envoy_http_local_rate_limiter_http_local_rate_limit_ok counter +envoy_http_local_rate_limiter_http_local_rate_limit_ok{} 0 +# TYPE envoy_http_local_rate_limiter_http_local_rate_limit_rate_limited counter +envoy_http_local_rate_limiter_http_local_rate_limit_rate_limited{} 0 # TYPE envoy_listener_admin_downstream_cx_destroy counter envoy_listener_admin_downstream_cx_destroy{} 7 # TYPE envoy_listener_admin_http_downstream_rq_xx counter diff --git a/envoy/tests/fixtures/openmetrics/openmetrics_1_28.txt b/envoy/tests/fixtures/openmetrics/openmetrics_1_28.txt new file mode 100644 index 0000000000000..76bfd438fa54f --- /dev/null +++ b/envoy/tests/fixtures/openmetrics/openmetrics_1_28.txt @@ -0,0 +1,8 @@ +# TYPE envoy_http_local_rate_limit_enabled counter +envoy_http_local_rate_limit_enabled{envoy_local_http_ratelimit_prefix="http_local_rate_limiter"} 0 +# TYPE envoy_http_local_rate_limit_enforced counter +envoy_http_local_rate_limit_enforced{envoy_local_http_ratelimit_prefix="http_local_rate_limiter"} 0 +# TYPE envoy_http_local_rate_limit_ok counter +envoy_http_local_rate_limit_ok{envoy_local_http_ratelimit_prefix="http_local_rate_limiter"} 0 +# TYPE envoy_http_local_rate_limit_rate_limited counter +envoy_http_local_rate_limit_rate_limited{envoy_local_http_ratelimit_prefix="http_local_rate_limiter"} 0 \ No newline at end of file diff --git a/envoy/tests/legacy/common.py b/envoy/tests/legacy/common.py index 312f5cfae2867..99e0bed659c1a 100644 --- a/envoy/tests/legacy/common.py +++ b/envoy/tests/legacy/common.py @@ -44,3 +44,12 @@ "envoy.cluster.ext_authz.failure_mode_allowed", "envoy.cluster.ext_authz.ok", ] + +LOCAL_RATE_LIMIT_METRICS = [ + "envoy.http_local_rate_limit.enabled", + "envoy.http_local_rate_limit.enforced", + "envoy.http_local_rate_limit.rate_limited", + "envoy.http_local_rate_limit.ok", +] + +RATE_LIMIT_STAT_PREFIX_TAG = ['stat_prefix:http_local_rate_limiter', 'stat_prefix:foo'] diff --git a/envoy/tests/legacy/test_unit.py b/envoy/tests/legacy/test_unit.py index 14639f8bd7915..2f48085c12b5e 100644 --- a/envoy/tests/legacy/test_unit.py +++ b/envoy/tests/legacy/test_unit.py @@ -7,10 +7,19 @@ import pytest import requests +from datadog_checks.dev.utils import get_metadata_metrics from datadog_checks.envoy import Envoy from datadog_checks.envoy.metrics import METRIC_PREFIX, METRICS -from .common import ENVOY_VERSION, EXT_METRICS, FLAVOR, HOST, INSTANCES +from .common import ( + ENVOY_VERSION, + EXT_METRICS, + FLAVOR, + HOST, + INSTANCES, + LOCAL_RATE_LIMIT_METRICS, + RATE_LIMIT_STAT_PREFIX_TAG, +) CHECK_NAME = 'envoy' @@ -21,7 +30,7 @@ def test_success_fixture(aggregator, fixture_path, mock_http_response, check, dd instance = INSTANCES['main'] c = check(instance) - response = mock_http_response(file_path=fixture_path('multiple_services')).return_value + response = mock_http_response(file_path=fixture_path('./legacy/multiple_services')).return_value dd_run_check(c) metrics_collected = 0 @@ -59,7 +68,7 @@ def test_success_fixture_included_metrics(aggregator, fixture_path, mock_http_re instance = INSTANCES['included_metrics'] c = check(instance) - mock_http_response(file_path=fixture_path('multiple_services')) + mock_http_response(file_path=fixture_path('./legacy/multiple_services')) dd_run_check(c) for metric in aggregator.metric_names: @@ -70,7 +79,7 @@ def test_success_fixture_excluded_metrics(aggregator, fixture_path, mock_http_re instance = INSTANCES['excluded_metrics'] c = check(instance) - mock_http_response(file_path=fixture_path('multiple_services')) + mock_http_response(file_path=fixture_path('./legacy/multiple_services')) dd_run_check(c) for metric in aggregator.metric_names: @@ -83,7 +92,7 @@ def test_success_fixture_inclued_and_excluded_metrics( instance = INSTANCES['included_excluded_metrics'] c = check(instance) - mock_http_response(file_path=fixture_path('multiple_services')) + mock_http_response(file_path=fixture_path('./legacy/multiple_services')) dd_run_check(c) for metric in aggregator.metric_names: @@ -94,7 +103,7 @@ def test_service_check(aggregator, fixture_path, mock_http_response, check, dd_r instance = INSTANCES['main'] c = check(instance) - mock_http_response(file_path=fixture_path('multiple_services')) + mock_http_response(file_path=fixture_path('./legacy/multiple_services')) dd_run_check(c) assert aggregator.service_checks(Envoy.SERVICE_CHECK_NAME)[0].status == Envoy.OK @@ -104,7 +113,7 @@ def test_unknown(fixture_path, mock_http_response, dd_run_check, check): instance = INSTANCES['main'] c = check(instance) - mock_http_response(file_path=fixture_path('unknown_metrics')) + mock_http_response(file_path=fixture_path('./legacy/unknown_metrics')) dd_run_check(c) assert sum(c.unknown_metrics.values()) == 5 @@ -201,7 +210,7 @@ def test_metadata(datadog_agent, fixture_path, mock_http_response, check, fixtur check.check_id = 'test:123' check.log = mock.MagicMock() - mock_http_response(file_path=fixture_path(fixture_file)) + mock_http_response(file_path=fixture_path('./legacy/{}'.format(fixture_file))) check._collect_metadata() @@ -224,7 +233,7 @@ def test_metadata_invalid(datadog_agent, fixture_path, mock_http_response, check check.check_id = 'test:123' check.log = mock.MagicMock() - mock_http_response(file_path=fixture_path('server_info_invalid')) + mock_http_response(file_path=fixture_path('./legacy/server_info_invalid')) check._collect_metadata() datadog_agent.assert_metadata('test:123', {}) @@ -248,7 +257,7 @@ def test_stats_prefix_ext_auth(aggregator, fixture_path, mock_http_response, che tags = ['cluster_name:foo', 'envoy_cluster:foo'] tags_prefix = tags + ['stat_prefix:bar'] c = check(instance) - mock_http_response(file_path=fixture_path('stat_prefix')).return_value + mock_http_response(file_path=fixture_path('./legacy/stat_prefix')).return_value dd_run_check(c) # To ensure that this change didn't break the old behavior, both the value and the tags are asserted. @@ -261,3 +270,18 @@ def test_stats_prefix_ext_auth(aggregator, fixture_path, mock_http_response, che tags=tags_prefix, ) aggregator.assert_metric(metric, value=index, tags=tags) + + +def test_local_rate_limit_metrics(aggregator, fixture_path, mock_http_response, check, dd_run_check): + instance = INSTANCES['main'] + c = check(instance) + + mock_http_response(file_path=fixture_path('./legacy/local_rate_limit.txt')) + dd_run_check(c) + + for metric in LOCAL_RATE_LIMIT_METRICS: + aggregator.assert_metric(metric) + for tag in RATE_LIMIT_STAT_PREFIX_TAG: + aggregator.assert_metric_has_tag(metric, tag, count=1) + + aggregator.assert_metrics_using_metadata(get_metadata_metrics()) diff --git a/envoy/tests/legacy/test_utils.py b/envoy/tests/legacy/test_utils.py index 253ad85406424..8d8ebe6730d83 100644 --- a/envoy/tests/legacy/test_utils.py +++ b/envoy/tests/legacy/test_utils.py @@ -3,7 +3,7 @@ # Licensed under a 3-clause BSD style license (see LICENSE) import pytest -from datadog_checks.envoy.utils import make_metric_tree +from datadog_checks.envoy.utils import make_metric_tree, modify_metrics_dict pytestmark = [pytest.mark.unit] @@ -54,3 +54,37 @@ def test_make_metric_tree(): }, } # fmt: on + + +def test_wildcard_removal_tree(): + # fmt: off + metrics = { + "*.http_local_rate_limit.enabled": { + "tags": ( + ("stat_prefix",), + (), + () + ), + "method": "monotonic_count", + }, + "*.http_local_rate_limit.enforced": { + "tags": ( + ("stat_prefix",), + (), + () + ), + "method": "monotonic_count", + } + } + + assert modify_metrics_dict(metrics) == { + "http_local_rate_limit.enabled": { + "tags": (("stat_prefix",), (), ()), + "method": "monotonic_count", + }, + "http_local_rate_limit.enforced": { + "tags": (("stat_prefix",), (), ()), + "method": "monotonic_count", + } + } + # fmt: on diff --git a/envoy/tests/test_e2e.py b/envoy/tests/test_e2e.py index fdac444ca513e..63ece2e38524e 100644 --- a/envoy/tests/test_e2e.py +++ b/envoy/tests/test_e2e.py @@ -7,7 +7,13 @@ from datadog_checks.dev.utils import get_metadata_metrics from datadog_checks.envoy import Envoy -from .common import DEFAULT_INSTANCE, FLAKY_METRICS, PROMETHEUS_METRICS, requires_new_environment +from .common import ( + DEFAULT_INSTANCE, + FLAKY_METRICS, + LOCAL_RATE_LIMIT_METRICS, + PROMETHEUS_METRICS, + requires_new_environment, +) pytestmark = [requires_new_environment] @@ -16,7 +22,7 @@ def test_e2e(dd_agent_check): aggregator = dd_agent_check(DEFAULT_INSTANCE, rate=True) - for metric in PROMETHEUS_METRICS: + for metric in PROMETHEUS_METRICS + LOCAL_RATE_LIMIT_METRICS: formatted_metric = "envoy.{}".format(metric) if metric in FLAKY_METRICS: aggregator.assert_metric(formatted_metric, at_least=0) diff --git a/envoy/tests/test_integration.py b/envoy/tests/test_integration.py index 18de8975d9669..4020349d4baa9 100644 --- a/envoy/tests/test_integration.py +++ b/envoy/tests/test_integration.py @@ -8,7 +8,14 @@ from datadog_checks.dev.utils import get_metadata_metrics from datadog_checks.envoy.metrics import METRIC_PREFIX, METRICS -from .common import DEFAULT_INSTANCE, ENVOY_VERSION, FLAKY_METRICS, PROMETHEUS_METRICS, requires_new_environment +from .common import ( + DEFAULT_INSTANCE, + ENVOY_VERSION, + FLAKY_METRICS, + LOCAL_RATE_LIMIT_METRICS, + PROMETHEUS_METRICS, + requires_new_environment, +) pytestmark = [requires_new_environment, pytest.mark.integration, pytest.mark.usefixtures('dd_environment')] @@ -24,7 +31,7 @@ def test_check(aggregator, dd_run_check, check): dd_run_check(c) dd_run_check(c) - for metric in PROMETHEUS_METRICS: + for metric in PROMETHEUS_METRICS + LOCAL_RATE_LIMIT_METRICS: formatted_metric = "envoy.{}".format(metric) if metric in FLAKY_METRICS: aggregator.assert_metric(formatted_metric, at_least=0) diff --git a/envoy/tests/test_unit.py b/envoy/tests/test_unit.py index b3c525ce57468..1d77d74af1703 100644 --- a/envoy/tests/test_unit.py +++ b/envoy/tests/test_unit.py @@ -9,7 +9,13 @@ from datadog_checks.dev.utils import get_metadata_metrics from datadog_checks.envoy.metrics import PROMETHEUS_METRICS_MAP -from .common import DEFAULT_INSTANCE, MOCKED_PROMETHEUS_METRICS, get_fixture_path +from .common import ( + DEFAULT_INSTANCE, + LOCAL_RATE_LIMIT_METRICS, + MOCKED_PROMETHEUS_METRICS, + RATE_LIMIT_STAT_PREFIX_TAG, + get_fixture_path, +) pytestmark = [pytest.mark.unit] @@ -32,13 +38,13 @@ def test_check_with_py2(aggregator, dd_run_check, check, mock_http_response): @requires_py3 def test_check(aggregator, dd_run_check, check, mock_http_response): - mock_http_response(file_path=get_fixture_path('openmetrics.txt')) + mock_http_response(file_path=get_fixture_path('./openmetrics/openmetrics.txt')) c = check(DEFAULT_INSTANCE) dd_run_check(c) - for metric in MOCKED_PROMETHEUS_METRICS: + for metric in MOCKED_PROMETHEUS_METRICS + LOCAL_RATE_LIMIT_METRICS: aggregator.assert_metric("envoy.{}".format(metric)) aggregator.assert_service_check( @@ -56,7 +62,7 @@ def test_collect_metadata(datadog_agent, fixture_path, mock_http_response, check c.check_id = 'test:123' c.log = mock.MagicMock() - mock_http_response(file_path=fixture_path('server_info_api_v3')) + mock_http_response(file_path=fixture_path('./legacy/server_info_api_v3')) c._collect_metadata() @@ -86,6 +92,39 @@ def test_collect_metadata_with_invalid_base_url( c.log.debug.assert_called_with('Skipping server info collection due to malformed url: %s', b'') +@requires_py3 +@pytest.mark.parametrize( + 'fixture_file', + [ + 'openmetrics.txt', + 'openmetrics_1_28.txt', + ], + ids=[ + "Envoy < 1.28", + "Envoy >= 1.28", + ], +) +def test_local_rate_limit_metrics(aggregator, dd_run_check, check, mock_http_response, fixture_file): + # Envoy 1.28+ fixed this metric by moving the variable stat_prefix into a label which follows the normal + # OpenMetrics convention. However older versions still have the stat_prefix inside the metric name. + mock_http_response(file_path=get_fixture_path('./openmetrics/{}'.format(fixture_file))) + + c = check(DEFAULT_INSTANCE) + + dd_run_check(c) + + for metric in LOCAL_RATE_LIMIT_METRICS: + aggregator.assert_metric('envoy.{}'.format(metric)) + aggregator.assert_metric_has_tag('envoy.{}'.format(metric), RATE_LIMIT_STAT_PREFIX_TAG) + + aggregator.assert_service_check( + "envoy.openmetrics.health", status=AgentCheck.OK, tags=['endpoint:http://localhost:8001/stats/prometheus'] + ) + + aggregator.assert_no_duplicate_metrics() + aggregator.assert_metrics_using_metadata(get_metadata_metrics()) + + @requires_py3 def test_collect_metadata_with_disabled_collect_server_info( datadog_agent, fixture_path, mock_http_response, check, default_instance