Skip to content

Commit 5e874ca

Browse files
committed
Add contract test for runtime metrics
1 parent 5d4a9c5 commit 5e874ca

File tree

4 files changed

+104
-4
lines changed

4 files changed

+104
-4
lines changed

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
3+
import json
34
from datetime import datetime, timedelta
45
from logging import Logger, getLogger
56
from time import sleep
@@ -87,7 +88,7 @@ def wait_condition(exported: List[ExportTraceServiceRequest], current: List[Expo
8788
spans.append(ResourceScopeSpan(resource_span, scope_span, span))
8889
return spans
8990

90-
def get_metrics(self, present_metrics: Set[str]) -> List[ResourceScopeMetric]:
91+
def get_metrics(self, present_metrics: Set[str], exact_match: bool = True) -> List[ResourceScopeMetric]:
9192
"""Get all metrics that are currently stored in the mock collector.
9293
9394
Returns:
@@ -111,7 +112,10 @@ def wait_condition(
111112
for scope_metric in resource_metric.scope_metrics:
112113
for metric in scope_metric.metrics:
113114
received_metrics.add(metric.name.lower())
114-
return 0 < len(exported) == len(current) and present_metrics_lower.issubset(received_metrics)
115+
if exact_match:
116+
return 0 < len(exported) == len(current) and present_metrics_lower.issubset(received_metrics)
117+
else:
118+
return present_metrics_lower.issubset(received_metrics)
115119

116120
exported_metrics: List[ExportMetricsServiceRequest] = _wait_for_content(get_export, wait_condition)
117121
metrics: List[ResourceScopeMetric] = []
@@ -140,4 +144,6 @@ def _wait_for_content(get_export: Callable[[], List[T]], wait_condition: Callabl
140144
except Exception:
141145
_logger.exception("Error while reading content")
142146

143-
raise RuntimeError("Timeout waiting for content")
147+
actual_exported: List[T] = get_export()
148+
actual_exported_value = json.dumps(actual_exported)
149+
raise RuntimeError(f"Timeout waiting for content {actual_exported_value}")

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def setUp(self) -> None:
8989
.with_exposed_ports(self.get_application_port())
9090
.with_env("OTEL_METRIC_EXPORT_INTERVAL", "50")
9191
.with_env("OTEL_AWS_APPLICATION_SIGNALS_ENABLED", "true")
92-
.with_env("OTEL_AWS_APPLICATION_SIGNALS_RUNTIME_ENABLED", "false")
92+
.with_env("OTEL_AWS_APPLICATION_SIGNALS_RUNTIME_ENABLED", self.is_runtime_enabled())
9393
.with_env("OTEL_METRICS_EXPORTER", "none")
9494
.with_env("OTEL_EXPORTER_OTLP_PROTOCOL", "grpc")
9595
.with_env("OTEL_BSP_SCHEDULE_DELAY", "1")
@@ -210,6 +210,9 @@ def get_application_otel_service_name(self) -> str:
210210
def get_application_otel_resource_attributes(self) -> str:
211211
return "service.name=" + self.get_application_otel_service_name()
212212

213+
def is_runtime_enabled(self) -> str:
214+
return "false"
215+
213216
def _assert_aws_span_attributes(self, resource_scope_spans: List[ResourceScopeSpan], path: str, **kwargs):
214217
self.fail("Tests must implement this function")
215218

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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+
8+
import amazon.utils.application_signals_constants as constants
9+
from amazon.django.django_test import DjangoTest
10+
from opentelemetry.proto.common.v1.common_pb2 import AnyValue
11+
from opentelemetry.proto.metrics.v1.metrics_pb2 import Metric, NumberDataPoint
12+
13+
14+
class RuntimeMetricsTest(DjangoTest):
15+
16+
def test_runtime_succeeds(self) -> None:
17+
self.mock_collector_client.clear_signals()
18+
response: Response = self.send_request("GET", "success")
19+
self.assertEqual(200, response.status_code)
20+
21+
metrics: List[ResourceScopeMetric] = self.mock_collector_client.get_metrics(
22+
{
23+
constants.LATENCY_METRIC,
24+
constants.ERROR_METRIC,
25+
constants.FAULT_METRIC,
26+
constants.PYTHON_PROCESS_CPU_TIME,
27+
constants.PYTHON_PROCESS_CPU_UTILIZATION,
28+
constants.PYTHON_PROCESS_GC_COUNT,
29+
constants.PYTHON_PROCESS_MEMORY_USED,
30+
constants.PYTHON_PROCESS_THREAD_COUNT,
31+
},
32+
False,
33+
)
34+
self._assert_resource_attributes(metrics)
35+
self._assert_counter_attribute_exists(metrics, constants.PYTHON_PROCESS_CPU_TIME, "")
36+
self._assert_gauge_attribute_exists(metrics, constants.PYTHON_PROCESS_CPU_UTILIZATION, "")
37+
self._assert_gauge_attribute_exists(metrics, constants.PYTHON_PROCESS_GC_COUNT, "count")
38+
self._assert_gauge_attribute_exists(metrics, constants.PYTHON_PROCESS_MEMORY_USED, "type")
39+
self._assert_gauge_attribute_exists(metrics, constants.PYTHON_PROCESS_THREAD_COUNT, "")
40+
41+
def _assert_resource_attributes(
42+
self,
43+
resource_scope_metrics: List[ResourceScopeMetric],
44+
) -> None:
45+
for metric in resource_scope_metrics:
46+
attribute_dict: Dict[str, AnyValue] = self._get_attributes_dict(metric.resource_metrics.resource.attributes)
47+
self._assert_str_attribute(
48+
attribute_dict, constants.AWS_LOCAL_SERVICE, self.get_application_otel_service_name()
49+
)
50+
51+
def _assert_gauge_attribute_exists(
52+
self,
53+
resource_scope_metrics: List[ResourceScopeMetric],
54+
metric_name: str,
55+
attribute_key: str,
56+
) -> None:
57+
target_metrics: List[Metric] = []
58+
for resource_scope_metric in resource_scope_metrics:
59+
if resource_scope_metric.metric.name.lower() == metric_name.lower():
60+
target_metrics.append(resource_scope_metric.metric)
61+
self.assertTrue(len(target_metrics) > 0)
62+
63+
if attribute_key != "":
64+
for target_metric in target_metrics:
65+
dp_list: List[NumberDataPoint] = target_metric.gauge.data_points
66+
attribute_dict: Dict[str, AnyValue] = self._get_attributes_dict(dp_list[0].attributes)
67+
self.assertIsNotNone(attribute_dict.get(attribute_key))
68+
69+
def _assert_counter_attribute_exists(
70+
self,
71+
resource_scope_metrics: List[ResourceScopeMetric],
72+
metric_name: str,
73+
attribute_key: str,
74+
) -> None:
75+
target_metrics: List[Metric] = []
76+
for resource_scope_metric in resource_scope_metrics:
77+
if resource_scope_metric.metric.name.lower() == metric_name.lower():
78+
target_metrics.append(resource_scope_metric.metric)
79+
self.assertTrue(len(target_metrics) > 0)
80+
81+
if attribute_key != "":
82+
for target_metric in target_metrics:
83+
dp_list: List[NumberDataPoint] = target_metric.sum.data_points
84+
attribute_dict: Dict[str, AnyValue] = self._get_attributes_dict(dp_list[0].attributes)
85+
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_LOCAL_SERVICE: str = "aws.local.service"
1420
AWS_LOCAL_OPERATION: str = "aws.local.operation"

0 commit comments

Comments
 (0)