diff --git a/aws_embedded_metrics/logger/metrics_context.py b/aws_embedded_metrics/logger/metrics_context.py index 47ed1dd..32fe682 100644 --- a/aws_embedded_metrics/logger/metrics_context.py +++ b/aws_embedded_metrics/logger/metrics_context.py @@ -17,7 +17,7 @@ from aws_embedded_metrics.constants import MAX_DIMENSION_SET_SIZE from aws_embedded_metrics.exceptions import DimensionSetExceededError from aws_embedded_metrics.logger.metric import Metric -from typing import List, Dict, Any +from typing import List, Dict, Any, Set class MetricsContext(object): @@ -66,20 +66,26 @@ def validate_dimension_set(dimensions: Dict[str, str]) -> None: raise DimensionSetExceededError( f"Maximum number of dimensions per dimension set allowed are {MAX_DIMENSION_SET_SIZE}") - def put_dimensions(self, dimensions: Dict[str, str]) -> None: + def put_dimensions(self, dimension_set: Dict[str, str]) -> None: """ Adds dimensions to the context. ``` context.put_dimensions({ "k1": "v1", "k2": "v2" }) ``` """ - if dimensions is None: + if dimension_set is None: # TODO add ability to define failure strategy return - self.validate_dimension_set(dimensions) + self.validate_dimension_set(dimension_set) - self.dimensions.append(dimensions) + # Duplicate dimension sets are removed before being added to the end of the collection. + # This ensures only latest dimension value is used as a target member on the root EMF node. + # This operation is O(n^2), but acceptable given sets are capped at 30 dimensions + incoming_keys: Set = set(dimension_set.keys()) + self.dimensions = list(filter(lambda dim: (set(dim.keys()) != incoming_keys), self.dimensions)) + + self.dimensions.append(dimension_set) def set_dimensions(self, dimensionSets: List[Dict[str, str]]) -> None: """ diff --git a/tests/logger/test_metrics_context.py b/tests/logger/test_metrics_context.py index d1f8b0c..375bff9 100644 --- a/tests/logger/test_metrics_context.py +++ b/tests/logger/test_metrics_context.py @@ -54,6 +54,107 @@ def test_put_dimension_adds_to_dimensions(): assert context.dimensions == [dimension_set] +def test_put_dimensions_accept_multiple_unique_dimensions(): + # arrange + context = MetricsContext() + dimension1 = {fake.word(): fake.word()} + dimension2 = {fake.word(): fake.word()} + + # act + context.put_dimensions(dimension1) + context.put_dimensions(dimension2) + + # assert + assert len(context.get_dimensions()) == 2 + assert context.get_dimensions()[0] == dimension1 + assert context.get_dimensions()[1] == dimension2 + + +def test_put_dimensions_prevent_duplicate_dimensions(): + # arrange + context = MetricsContext() + pair1 = [fake.word(), fake.word()] + pair2 = [fake.word(), fake.word()] + + dimension1 = {pair1[0]: pair1[1]} + dimension2 = {pair2[0]: pair2[1]} + dimension3 = {pair1[0]: pair1[1], pair2[0]: pair2[1]} + + # act + context.put_dimensions(dimension1) + context.put_dimensions(dimension2) + context.put_dimensions(dimension1) + context.put_dimensions(dimension3) + context.put_dimensions(dimension2) + context.put_dimensions(dimension3) + + # assert + assert len(context.get_dimensions()) == 3 + assert context.get_dimensions()[0] == dimension1 + assert context.get_dimensions()[1] == dimension2 + assert context.get_dimensions()[2] == dimension3 + + +def test_put_dimensions_use_most_recent_dimension_value(): + # arrange + context = MetricsContext() + key1 = fake.word() + key2 = fake.word() + val1 = fake.word() + val2 = fake.word() + + dimension1 = {key1: val1} + dimension2 = {key2: val2} + dimension3 = {key1: val2} + dimension4 = {key2: val1} + dimension5 = {key1: val1, key2: val2} + dimension6 = {key1: val2, key2: val1} + + # act + context.put_dimensions(dimension1) + context.put_dimensions(dimension2) + context.put_dimensions(dimension5) + context.put_dimensions(dimension3) + context.put_dimensions(dimension4) + context.put_dimensions(dimension6) + + # assert + assert len(context.get_dimensions()) == 3 + assert context.get_dimensions()[0] == dimension3 + assert context.get_dimensions()[1] == dimension4 + assert context.get_dimensions()[2] == dimension6 + + +def test_put_dimensions_with_set_dimensions(): + # arrange + context = MetricsContext() + key1 = fake.word() + key2 = fake.word() + val1 = fake.word() + val2 = fake.word() + + dimension1 = {key1: val1} + dimension2 = {key2: val2} + dimension3 = {key1: val2} + dimension4 = {key2: val1} + dimension5 = {key1: val1, key2: val2} + dimension6 = {key1: val2, key2: val1} + + # act + context.put_dimensions(dimension1) + context.put_dimensions(dimension2) + context.set_dimensions([dimension3]) + context.put_dimensions(dimension4) + context.put_dimensions(dimension5) + context.put_dimensions(dimension6) + + # assert + assert len(context.get_dimensions()) == 3 + assert context.get_dimensions()[0] == dimension3 + assert context.get_dimensions()[1] == dimension4 + assert context.get_dimensions()[2] == dimension6 + + def test_get_dimensions_returns_only_custom_dimensions_if_no_default_dimensions_not_set(): # arrange context = MetricsContext()