Skip to content

Commit acf7f68

Browse files
committed
Add contract test for runtime metrics
1 parent 965841c commit acf7f68

File tree

4 files changed

+118
-3
lines changed

4 files changed

+118
-3
lines changed

contract-tests/images/mock-collector/mock_collector_client.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def wait_condition(exported: List[ExportTraceServiceRequest], current: List[Expo
8787
spans.append(ResourceScopeSpan(resource_span, scope_span, span))
8888
return spans
8989

90-
def get_metrics(self, present_metrics: Set[str]) -> List[ResourceScopeMetric]:
90+
def get_metrics(self, present_metrics: Set[str], exact_match=True) -> List[ResourceScopeMetric]:
9191
"""Get all metrics that are currently stored in the mock collector.
9292
9393
Returns:
@@ -111,7 +111,9 @@ def wait_condition(
111111
for scope_metric in resource_metric.scope_metrics:
112112
for metric in scope_metric.metrics:
113113
received_metrics.add(metric.name.lower())
114-
return 0 < len(exported) == len(current) and present_metrics_lower.issubset(received_metrics)
114+
if exact_match:
115+
return 0 < len(exported) == len(current) and present_metrics_lower.issubset(received_metrics)
116+
return present_metrics_lower.issubset(received_metrics)
115117

116118
exported_metrics: List[ExportMetricsServiceRequest] = _wait_for_content(get_export, wait_condition)
117119
metrics: List[ResourceScopeMetric] = []

contract-tests/tests/test/amazon/base/contract_test_base.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def setUp(self) -> None:
9090
.with_exposed_ports(self.get_application_port())
9191
.with_env("OTEL_METRIC_EXPORT_INTERVAL", "50")
9292
.with_env("OTEL_AWS_APPLICATION_SIGNALS_ENABLED", "true")
93-
.with_env("OTEL_AWS_APPLICATION_SIGNALS_RUNTIME_ENABLED", "false")
93+
.with_env("OTEL_AWS_APPLICATION_SIGNALS_RUNTIME_ENABLED", self.is_runtime_enabled())
9494
.with_env("OTEL_METRICS_EXPORTER", "none")
9595
.with_env("OTEL_EXPORTER_OTLP_PROTOCOL", "grpc")
9696
.with_env("OTEL_BSP_SCHEDULE_DELAY", "1")
@@ -217,6 +217,9 @@ def get_application_otel_service_name(self) -> str:
217217
def get_application_otel_resource_attributes(self) -> str:
218218
return "service.name=" + self.get_application_otel_service_name()
219219

220+
def is_runtime_enabled(self) -> str:
221+
return "false"
222+
220223
def _assert_aws_span_attributes(self, resource_scope_spans: List[ResourceScopeSpan], path: str, **kwargs):
221224
self.fail("Tests must implement this function")
222225

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
from typing import Dict, List
4+
5+
from mock_collector_client import ResourceScopeMetric
6+
from requests import Response
7+
from typing_extensions import override
8+
9+
import amazon.utils.application_signals_constants as constants
10+
from amazon.base.contract_test_base import ContractTestBase
11+
from opentelemetry.proto.common.v1.common_pb2 import AnyValue
12+
from opentelemetry.proto.metrics.v1.metrics_pb2 import Metric, NumberDataPoint
13+
14+
15+
class RuntimeMetricsTest(ContractTestBase):
16+
@override
17+
def is_runtime_enabled(self) -> str:
18+
return "true"
19+
20+
@override
21+
@staticmethod
22+
def get_application_image_name() -> str:
23+
return "aws-application-signals-tests-django-app"
24+
25+
@override
26+
def get_application_wait_pattern(self) -> str:
27+
return "Quit the server with CONTROL-C."
28+
29+
@override
30+
def get_application_extra_environment_variables(self):
31+
return {"DJANGO_SETTINGS_MODULE": "django_server.settings"}
32+
33+
def test_runtime_succeeds(self) -> None:
34+
self.mock_collector_client.clear_signals()
35+
response: Response = self.send_request("GET", "success")
36+
self.assertEqual(200, response.status_code)
37+
38+
metrics: List[ResourceScopeMetric] = self.mock_collector_client.get_metrics(
39+
{
40+
constants.LATENCY_METRIC,
41+
constants.ERROR_METRIC,
42+
constants.FAULT_METRIC,
43+
constants.PYTHON_PROCESS_CPU_TIME,
44+
constants.PYTHON_PROCESS_CPU_UTILIZATION,
45+
constants.PYTHON_PROCESS_GC_COUNT,
46+
constants.PYTHON_PROCESS_MEMORY_USED,
47+
constants.PYTHON_PROCESS_THREAD_COUNT,
48+
},
49+
False,
50+
)
51+
self._assert_resource_attributes(metrics)
52+
self._assert_counter_attribute_exists(metrics, constants.PYTHON_PROCESS_CPU_TIME, "")
53+
self._assert_gauge_attribute_exists(metrics, constants.PYTHON_PROCESS_CPU_UTILIZATION, "")
54+
self._assert_gauge_attribute_exists(metrics, constants.PYTHON_PROCESS_GC_COUNT, "count")
55+
self._assert_gauge_attribute_exists(metrics, constants.PYTHON_PROCESS_MEMORY_USED, "type")
56+
self._assert_gauge_attribute_exists(metrics, constants.PYTHON_PROCESS_THREAD_COUNT, "")
57+
58+
def _assert_resource_attributes(
59+
self,
60+
resource_scope_metrics: List[ResourceScopeMetric],
61+
) -> None:
62+
for metric in resource_scope_metrics:
63+
attribute_dict: Dict[str, AnyValue] = self._get_attributes_dict(metric.resource_metrics.resource.attributes)
64+
self._assert_str_attribute(
65+
attribute_dict, constants.AWS_LOCAL_SERVICE, self.get_application_otel_service_name()
66+
)
67+
68+
def _assert_gauge_attribute_exists(
69+
self,
70+
resource_scope_metrics: List[ResourceScopeMetric],
71+
metric_name: str,
72+
attribute_key: str,
73+
) -> None:
74+
target_metrics: List[Metric] = []
75+
for resource_scope_metric in resource_scope_metrics:
76+
if resource_scope_metric.metric.name.lower() == metric_name.lower():
77+
target_metrics.append(resource_scope_metric.metric)
78+
self.assertTrue(len(target_metrics) > 0)
79+
80+
for target_metric in target_metrics:
81+
dp_list: List[NumberDataPoint] = target_metric.gauge.data_points
82+
self.assertTrue(len(dp_list) > 0)
83+
if attribute_key != "":
84+
attribute_dict: Dict[str, AnyValue] = self._get_attributes_dict(dp_list[0].attributes)
85+
self.assertIsNotNone(attribute_dict.get(attribute_key))
86+
87+
def _assert_counter_attribute_exists(
88+
self,
89+
resource_scope_metrics: List[ResourceScopeMetric],
90+
metric_name: str,
91+
attribute_key: str,
92+
) -> None:
93+
target_metrics: List[Metric] = []
94+
for resource_scope_metric in resource_scope_metrics:
95+
if resource_scope_metric.metric.name.lower() == metric_name.lower():
96+
target_metrics.append(resource_scope_metric.metric)
97+
self.assertTrue(len(target_metrics) > 0)
98+
99+
for target_metric in target_metrics:
100+
dp_list: List[NumberDataPoint] = target_metric.sum.data_points
101+
self.assertTrue(len(dp_list) > 0)
102+
if attribute_key != "":
103+
attribute_dict: Dict[str, AnyValue] = self._get_attributes_dict(dp_list[0].attributes)
104+
self.assertIsNotNone(attribute_dict.get(attribute_key))

contract-tests/tests/test/amazon/utils/application_signals_constants.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99
ERROR_METRIC: str = "error"
1010
FAULT_METRIC: str = "fault"
1111

12+
PYTHON_PROCESS_GC_COUNT = "process.runtime.cpython.gc_count"
13+
PYTHON_PROCESS_MEMORY_USED = "process.runtime.cpython.memory"
14+
PYTHON_PROCESS_THREAD_COUNT = "process.runtime.cpython.thread_count"
15+
PYTHON_PROCESS_CPU_TIME = "process.runtime.cpython.cpu_time"
16+
PYTHON_PROCESS_CPU_UTILIZATION = "process.runtime.cpython.cpu.utilization"
17+
1218
# Attribute names
1319
AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER: str = "aws.remote.resource.cfn.primary.identifier"
1420
AWS_LOCAL_SERVICE: str = "aws.local.service"

0 commit comments

Comments
 (0)