From 9b3b06743bbe7fec78c1e2c80ec323b0dfa63ee5 Mon Sep 17 00:00:00 2001 From: Diego Hurtado Date: Thu, 26 May 2022 15:35:01 +0200 Subject: [PATCH 1/4] Add missing to_json methods Fixes #2716 --- CHANGELOG.md | 2 + .../sdk/metrics/_internal/point.py | 115 +++++--- .../integration_test/test_console_exporter.py | 37 +++ opentelemetry-sdk/tests/metrics/test_point.py | 273 ++++++++++++++---- 4 files changed, 334 insertions(+), 93 deletions(-) create mode 100644 opentelemetry-sdk/tests/metrics/integration_test/test_console_exporter.py diff --git a/CHANGELOG.md b/CHANGELOG.md index e33019f119e..8209e3c182a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/open-telemetry/opentelemetry-python/compare/v1.12.0rc1-0.31b0...HEAD) +- Add missing `to_json` methods + ([#2722](https://github.com/open-telemetry/opentelemetry-python/pull/2722) - Fix type hints for textmap `Getter` and `Setter` ([#2657](https://github.com/open-telemetry/opentelemetry-python/pull/2657)) - Fix LogEmitterProvider.force_flush hanging randomly diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/point.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/point.py index 4ae68629678..a7974960aa6 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/point.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/point.py @@ -15,8 +15,8 @@ # pylint: disable=unused-import from dataclasses import asdict, dataclass -from json import dumps -from typing import Sequence, Union +from json import dumps, loads +from typing import Optional, Sequence, Union # This kind of import is needed to avoid Sphinx errors. import opentelemetry.sdk.metrics._internal @@ -36,6 +36,29 @@ class NumberDataPoint: time_unix_nano: int value: Union[int, float] + def to_json(self) -> str: + return dumps(asdict(self)) + + +@dataclass(frozen=True) +class HistogramDataPoint: + """Single data point in a timeseries that describes the time-varying scalar + value of a metric. + """ + + attributes: Attributes + start_time_unix_nano: int + time_unix_nano: int + count: int + sum: Union[int, float] + bucket_counts: Sequence[int] + explicit_bounds: Sequence[float] + min: float + max: float + + def to_json(self) -> str: + return dumps(asdict(self)) + @dataclass(frozen=True) class Sum: @@ -51,9 +74,10 @@ class Sum: def to_json(self) -> str: return dumps( { - "data_points": dumps( - [asdict(data_point) for data_point in self.data_points] - ), + "data_points": [ + loads(data_point.to_json()) + for data_point in self.data_points + ], "aggregation_temporality": self.aggregation_temporality, "is_monotonic": self.is_monotonic, } @@ -71,30 +95,14 @@ class Gauge: def to_json(self) -> str: return dumps( { - "data_points": dumps( - [asdict(data_point) for data_point in self.data_points] - ) + "data_points": [ + loads(data_point.to_json()) + for data_point in self.data_points + ], } ) -@dataclass(frozen=True) -class HistogramDataPoint: - """Single data point in a timeseries that describes the time-varying scalar - value of a metric. - """ - - attributes: Attributes - start_time_unix_nano: int - time_unix_nano: int - count: int - sum: Union[int, float] - bucket_counts: Sequence[int] - explicit_bounds: Sequence[float] - min: float - max: float - - @dataclass(frozen=True) class Histogram: """Represents the type of a metric that is calculated by aggregating as a @@ -108,9 +116,10 @@ class Histogram: def to_json(self) -> str: return dumps( { - "data_points": dumps( - [asdict(data_point) for data_point in self.data_points] - ), + "data_points": [ + loads(data_point.to_json()) + for data_point in self.data_points + ], "aggregation_temporality": self.aggregation_temporality, } ) @@ -126,17 +135,17 @@ class Metric: exported.""" name: str - description: str - unit: str + description: Optional[str] + unit: Optional[str] data: DataT def to_json(self) -> str: return dumps( { "name": self.name, - "description": self.description if self.description else "", - "unit": self.unit if self.unit else "", - "data": self.data.to_json(), + "description": self.description or "", + "unit": self.unit or "", + "data": loads(self.data.to_json()), } ) @@ -149,6 +158,21 @@ class ScopeMetrics: metrics: Sequence[Metric] schema_url: str + def to_json(self) -> str: + return dumps( + { + "scope": { + "name": self.scope.name, + "version": self.scope.version, + "schema_url": self.scope.schema_url, + }, + "metrics": [ + loads(metric.to_json()) for metric in self.metrics + ], + "schema_url": self.schema_url, + } + ) + @dataclass(frozen=True) class ResourceMetrics: @@ -158,9 +182,34 @@ class ResourceMetrics: scope_metrics: Sequence[ScopeMetrics] schema_url: str + def to_json(self) -> str: + return dumps( + { + "resource": { + "attributes": dict(self.resource.attributes.items()), + "schema_url": self.resource.schema_url, + }, + "scope_metrics": [ + loads(scope_metrics.to_json()) + for scope_metrics in self.scope_metrics + ], + "schema_url": self.schema_url, + } + ) + @dataclass(frozen=True) class MetricsData: """An array of ResourceMetrics""" resource_metrics: Sequence[ResourceMetrics] + + def to_json(self) -> str: + return dumps( + { + "resource_metrics": [ + loads(resource_metrics.to_json()) + for resource_metrics in self.resource_metrics + ] + } + ) diff --git a/opentelemetry-sdk/tests/metrics/integration_test/test_console_exporter.py b/opentelemetry-sdk/tests/metrics/integration_test/test_console_exporter.py new file mode 100644 index 00000000000..d09ade6a29e --- /dev/null +++ b/opentelemetry-sdk/tests/metrics/integration_test/test_console_exporter.py @@ -0,0 +1,37 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import TestCase + +from opentelemetry import metrics +from opentelemetry.sdk.metrics import MeterProvider +from opentelemetry.sdk.metrics.export import ( + ConsoleMetricExporter, + PeriodicExportingMetricReader, +) + + +class TestConsoleExporter(TestCase): + def test_console_exporter(self): + + try: + exporter = ConsoleMetricExporter() + reader = PeriodicExportingMetricReader(exporter) + provider = MeterProvider(metric_readers=[reader]) + metrics.set_meter_provider(provider) + meter = metrics.get_meter(__name__) + counter = meter.create_counter("test") + counter.add(1) + except Exception as error: + self.fail(f"Unexpected exception {error} raised") diff --git a/opentelemetry-sdk/tests/metrics/test_point.py b/opentelemetry-sdk/tests/metrics/test_point.py index ce3e73b7b0b..9c509bbd958 100644 --- a/opentelemetry-sdk/tests/metrics/test_point.py +++ b/opentelemetry-sdk/tests/metrics/test_point.py @@ -15,85 +15,238 @@ from unittest import TestCase from opentelemetry.sdk.metrics.export import ( + AggregationTemporality, Gauge, Histogram, HistogramDataPoint, Metric, + MetricsData, NumberDataPoint, + ResourceMetrics, + ScopeMetrics, Sum, ) +from opentelemetry.sdk.resources import Resource +from opentelemetry.sdk.util.instrumentation import InstrumentationScope -def _create_metric(data): - return Metric( - name="test-name", - description="test-description", - unit="test-unit", - data=data, - ) +class TestToJson(TestCase): + @classmethod + def setUpClass(cls): + cls.attributes_0 = { + "a": "b", + "b": True, + "c": 1, + "d": 1.1, + "e": ["a", "b"], + "f": [True, False], + "g": [1, 2], + "h": [1.1, 2.2], + } + cls.attributes_0_str = '{"a": "b", "b": true, "c": 1, "d": 1.1, "e": ["a", "b"], "f": [true, false], "g": [1, 2], "h": [1.1, 2.2]}' -class TestDatapointToJSON(TestCase): - def test_sum(self): - self.maxDiff = None - point = _create_metric( - Sum( - data_points=[ - NumberDataPoint( - attributes={"attr-key": "test-val"}, - start_time_unix_nano=10, - time_unix_nano=20, - value=9, - ) - ], - aggregation_temporality=2, - is_monotonic=True, - ) + cls.attributes_1 = { + "i": "a", + "j": False, + "k": 2, + "l": 2.2, + "m": ["b", "a"], + "n": [False, True], + "o": [2, 1], + "p": [2.2, 1.1], + } + cls.attributes_1_str = '{"i": "a", "j": false, "k": 2, "l": 2.2, "m": ["b", "a"], "n": [false, true], "o": [2, 1], "p": [2.2, 1.1]}' + + cls.number_data_point_0 = NumberDataPoint( + attributes=cls.attributes_0, + start_time_unix_nano=1, + time_unix_nano=2, + value=3.3, + ) + cls.number_data_point_0_str = f'{{"attributes": {cls.attributes_0_str}, "start_time_unix_nano": 1, "time_unix_nano": 2, "value": 3.3}}' + + cls.number_data_point_1 = NumberDataPoint( + attributes=cls.attributes_1, + start_time_unix_nano=2, + time_unix_nano=3, + value=4.4, + ) + cls.number_data_point_1_str = f'{{"attributes": {cls.attributes_1_str}, "start_time_unix_nano": 2, "time_unix_nano": 3, "value": 4.4}}' + + cls.histogram_data_point_0 = HistogramDataPoint( + attributes=cls.attributes_0, + start_time_unix_nano=1, + time_unix_nano=2, + count=3, + sum=3.3, + bucket_counts=[1, 1, 1], + explicit_bounds=[0.1, 1.2, 2.3, 3.4], + min=0.2, + max=3.3, + ) + cls.histogram_data_point_0_str = f'{{"attributes": {cls.attributes_0_str}, "start_time_unix_nano": 1, "time_unix_nano": 2, "count": 3, "sum": 3.3, "bucket_counts": [1, 1, 1], "explicit_bounds": [0.1, 1.2, 2.3, 3.4], "min": 0.2, "max": 3.3}}' + + cls.histogram_data_point_1 = HistogramDataPoint( + attributes=cls.attributes_1, + start_time_unix_nano=2, + time_unix_nano=3, + count=4, + sum=4.4, + bucket_counts=[2, 1, 1], + explicit_bounds=[1.2, 2.3, 3.4, 4.5], + min=0.3, + max=4.4, + ) + cls.histogram_data_point_1_str = f'{{"attributes": {cls.attributes_1_str}, "start_time_unix_nano": 2, "time_unix_nano": 3, "count": 4, "sum": 4.4, "bucket_counts": [2, 1, 1], "explicit_bounds": [1.2, 2.3, 3.4, 4.5], "min": 0.3, "max": 4.4}}' + + cls.sum_0 = Sum( + data_points=[cls.number_data_point_0, cls.number_data_point_1], + aggregation_temporality=AggregationTemporality.DELTA, + is_monotonic=False, + ) + cls.sum_0_str = f'{{"data_points": [{cls.number_data_point_0_str}, {cls.number_data_point_1_str}], "aggregation_temporality": 1, "is_monotonic": false}}' + + cls.gauge_0 = Gauge( + data_points=[cls.number_data_point_0, cls.number_data_point_1], + ) + cls.gauge_0_str = f'{{"data_points": [{cls.number_data_point_0_str}, {cls.number_data_point_1_str}]}}' + + cls.histogram_0 = Histogram( + data_points=[ + cls.histogram_data_point_0, + cls.histogram_data_point_1, + ], + aggregation_temporality=AggregationTemporality.DELTA, + ) + cls.histogram_0_str = f'{{"data_points": [{cls.histogram_data_point_0_str}, {cls.histogram_data_point_1_str}], "aggregation_temporality": 1}}' + + cls.metric_0 = Metric( + name="metric_0", + description="description_0", + unit="unit_0", + data=cls.sum_0, + ) + cls.metric_0_str = f'{{"name": "metric_0", "description": "description_0", "unit": "unit_0", "data": {cls.sum_0_str}}}' + + cls.metric_1 = Metric( + name="metric_1", description=None, unit="unit_1", data=cls.gauge_0 + ) + cls.metric_1_str = f'{{"name": "metric_1", "description": "", "unit": "unit_1", "data": {cls.gauge_0_str}}}' + + cls.metric_2 = Metric( + name="metric_2", + description="description_2", + unit=None, + data=cls.histogram_0, + ) + cls.metric_2_str = f'{{"name": "metric_2", "description": "description_2", "unit": "", "data": {cls.histogram_0_str}}}' + + cls.scope_metrics_0 = ScopeMetrics( + scope=InstrumentationScope( + name="name_0", + version="version_0", + schema_url="schema_url_0", + ), + metrics=[cls.metric_0, cls.metric_1, cls.metric_2], + schema_url="schema_url_0", + ) + cls.scope_metrics_0_str = f'{{"scope": {{"name": "name_0", "version": "version_0", "schema_url": "schema_url_0"}}, "metrics": [{cls.metric_0_str}, {cls.metric_1_str}, {cls.metric_2_str}], "schema_url": "schema_url_0"}}' + + cls.scope_metrics_1 = ScopeMetrics( + scope=InstrumentationScope( + name="name_1", + version="version_1", + schema_url="schema_url_1", + ), + metrics=[cls.metric_0, cls.metric_1, cls.metric_2], + schema_url="schema_url_1", + ) + cls.scope_metrics_1_str = f'{{"scope": {{"name": "name_1", "version": "version_1", "schema_url": "schema_url_1"}}, "metrics": [{cls.metric_0_str}, {cls.metric_1_str}, {cls.metric_2_str}], "schema_url": "schema_url_1"}}' + + cls.resource_metrics_0 = ResourceMetrics( + resource=Resource( + attributes=cls.attributes_0, schema_url="schema_url_0" + ), + scope_metrics=[cls.scope_metrics_0, cls.scope_metrics_1], + schema_url="schema_url_0", + ) + cls.resource_metrics_0_str = f'{{"resource": {{"attributes": {cls.attributes_0_str}, "schema_url": "schema_url_0"}}, "scope_metrics": [{cls.scope_metrics_0_str}, {cls.scope_metrics_1_str}], "schema_url": "schema_url_0"}}' + + cls.resource_metrics_1 = ResourceMetrics( + resource=Resource( + attributes=cls.attributes_1, schema_url="schema_url_1" + ), + scope_metrics=[cls.scope_metrics_0, cls.scope_metrics_1], + schema_url="schema_url_1", + ) + cls.resource_metrics_1_str = f'{{"resource": {{"attributes": {cls.attributes_1_str}, "schema_url": "schema_url_1"}}, "scope_metrics": [{cls.scope_metrics_0_str}, {cls.scope_metrics_1_str}], "schema_url": "schema_url_1"}}' + + cls.metrics_data_0 = MetricsData( + resource_metrics=[cls.resource_metrics_0, cls.resource_metrics_1] + ) + cls.metrics_data_0_str = f'{{"resource_metrics": [{cls.resource_metrics_0_str}, {cls.resource_metrics_1_str}]}}' + + def test_number_data_point(self): + + self.assertEqual( + self.number_data_point_0.to_json(), self.number_data_point_0_str ) self.assertEqual( - '{"name": "test-name", "description": "test-description", "unit": "test-unit", "data": "{\\"data_points\\": \\"[{\\\\\\"attributes\\\\\\": {\\\\\\"attr-key\\\\\\": \\\\\\"test-val\\\\\\"}, \\\\\\"start_time_unix_nano\\\\\\": 10, \\\\\\"time_unix_nano\\\\\\": 20, \\\\\\"value\\\\\\": 9}]\\", \\"aggregation_temporality\\": 2, \\"is_monotonic\\": true}"}', - point.to_json(), + self.number_data_point_1.to_json(), self.number_data_point_1_str ) - def test_gauge(self): - point = _create_metric( - Gauge( - data_points=[ - NumberDataPoint( - attributes={"attr-key": "test-val"}, - start_time_unix_nano=10, - time_unix_nano=20, - value=9, - ) - ] - ) + def test_histogram_data_point(self): + + self.assertEqual( + self.histogram_data_point_0.to_json(), + self.histogram_data_point_0_str, ) self.assertEqual( - '{"name": "test-name", "description": "test-description", "unit": "test-unit", "data": "{\\"data_points\\": \\"[{\\\\\\"attributes\\\\\\": {\\\\\\"attr-key\\\\\\": \\\\\\"test-val\\\\\\"}, \\\\\\"start_time_unix_nano\\\\\\": 10, \\\\\\"time_unix_nano\\\\\\": 20, \\\\\\"value\\\\\\": 9}]\\"}"}', - point.to_json(), + self.histogram_data_point_1.to_json(), + self.histogram_data_point_1_str, ) + def test_sum(self): + + self.assertEqual(self.sum_0.to_json(), self.sum_0_str) + + def test_gauge(self): + + self.assertEqual(self.gauge_0.to_json(), self.gauge_0_str) + def test_histogram(self): - point = _create_metric( - Histogram( - data_points=[ - HistogramDataPoint( - attributes={"attr-key": "test-val"}, - start_time_unix_nano=50, - time_unix_nano=60, - count=1, - sum=0.8, - bucket_counts=[0, 0, 1, 0], - explicit_bounds=[0.1, 0.5, 0.9, 1], - min=0.8, - max=0.8, - ) - ], - aggregation_temporality=1, - ) - ) - self.maxDiff = None + + self.assertEqual(self.histogram_0.to_json(), self.histogram_0_str) + + def test_metric(self): + + self.assertEqual(self.metric_0.to_json(), self.metric_0_str) + + self.assertEqual(self.metric_1.to_json(), self.metric_1_str) + + self.assertEqual(self.metric_2.to_json(), self.metric_2_str) + + def test_scope_metrics(self): + + self.assertEqual( + self.scope_metrics_0.to_json(), self.scope_metrics_0_str + ) + self.assertEqual( + self.scope_metrics_1.to_json(), self.scope_metrics_1_str + ) + + def test_resource_metrics(self): + + self.assertEqual( + self.resource_metrics_0.to_json(), self.resource_metrics_0_str + ) + self.assertEqual( + self.resource_metrics_1.to_json(), self.resource_metrics_1_str + ) + + def test_metrics_data(self): + self.assertEqual( - '{"name": "test-name", "description": "test-description", "unit": "test-unit", "data": "{\\"data_points\\": \\"[{\\\\\\"attributes\\\\\\": {\\\\\\"attr-key\\\\\\": \\\\\\"test-val\\\\\\"}, \\\\\\"start_time_unix_nano\\\\\\": 50, \\\\\\"time_unix_nano\\\\\\": 60, \\\\\\"count\\\\\\": 1, \\\\\\"sum\\\\\\": 0.8, \\\\\\"bucket_counts\\\\\\": [0, 0, 1, 0], \\\\\\"explicit_bounds\\\\\\": [0.1, 0.5, 0.9, 1], \\\\\\"min\\\\\\": 0.8, \\\\\\"max\\\\\\": 0.8}]\\", \\"aggregation_temporality\\": 1}"}', - point.to_json(), + self.metrics_data_0.to_json(), self.metrics_data_0_str ) From fd7522b370327b545462a71c10dfa4f45a7ff70e Mon Sep 17 00:00:00 2001 From: Diego Hurtado Date: Mon, 6 Jun 2022 11:31:13 +0100 Subject: [PATCH 2/4] Added resource to_json method --- .../sdk/metrics/_internal/point.py | 60 ++++++++++--------- .../opentelemetry/sdk/resources/__init__.py | 9 +++ opentelemetry-sdk/tests/metrics/test_point.py | 38 +++++++----- 3 files changed, 64 insertions(+), 43 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/point.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/point.py index a7974960aa6..18fe81daaac 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/point.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/point.py @@ -36,8 +36,8 @@ class NumberDataPoint: time_unix_nano: int value: Union[int, float] - def to_json(self) -> str: - return dumps(asdict(self)) + def to_json(self, indent=4) -> str: + return dumps(asdict(self), indent=indent) @dataclass(frozen=True) @@ -56,8 +56,8 @@ class HistogramDataPoint: min: float max: float - def to_json(self) -> str: - return dumps(asdict(self)) + def to_json(self, indent=4) -> str: + return dumps(asdict(self), indent=indent) @dataclass(frozen=True) @@ -71,16 +71,17 @@ class Sum: ) is_monotonic: bool - def to_json(self) -> str: + def to_json(self, indent=4) -> str: return dumps( { "data_points": [ - loads(data_point.to_json()) + loads(data_point.to_json(indent=indent)) for data_point in self.data_points ], "aggregation_temporality": self.aggregation_temporality, "is_monotonic": self.is_monotonic, - } + }, + indent=indent, ) @@ -92,14 +93,15 @@ class Gauge: data_points: Sequence[NumberDataPoint] - def to_json(self) -> str: + def to_json(self, indent=4) -> str: return dumps( { "data_points": [ - loads(data_point.to_json()) + loads(data_point.to_json(indent=indent)) for data_point in self.data_points ], - } + }, + indent=indent, ) @@ -113,15 +115,16 @@ class Histogram: "opentelemetry.sdk.metrics.export.AggregationTemporality" ) - def to_json(self) -> str: + def to_json(self, indent=4) -> str: return dumps( { "data_points": [ - loads(data_point.to_json()) + loads(data_point.to_json(indent=indent)) for data_point in self.data_points ], "aggregation_temporality": self.aggregation_temporality, - } + }, + indent=indent, ) @@ -139,14 +142,15 @@ class Metric: unit: Optional[str] data: DataT - def to_json(self) -> str: + def to_json(self, indent=4) -> str: return dumps( { "name": self.name, "description": self.description or "", "unit": self.unit or "", - "data": loads(self.data.to_json()), - } + "data": loads(self.data.to_json(indent=indent)), + }, + indent=indent, ) @@ -158,7 +162,7 @@ class ScopeMetrics: metrics: Sequence[Metric] schema_url: str - def to_json(self) -> str: + def to_json(self, indent=4) -> str: return dumps( { "scope": { @@ -167,10 +171,12 @@ def to_json(self) -> str: "schema_url": self.scope.schema_url, }, "metrics": [ - loads(metric.to_json()) for metric in self.metrics + loads(metric.to_json(indent=indent)) + for metric in self.metrics ], "schema_url": self.schema_url, - } + }, + indent=indent, ) @@ -182,19 +188,17 @@ class ResourceMetrics: scope_metrics: Sequence[ScopeMetrics] schema_url: str - def to_json(self) -> str: + def to_json(self, indent=4) -> str: return dumps( { - "resource": { - "attributes": dict(self.resource.attributes.items()), - "schema_url": self.resource.schema_url, - }, + "resource": loads(self.resource.to_json(indent=indent)), "scope_metrics": [ - loads(scope_metrics.to_json()) + loads(scope_metrics.to_json(indent=indent)) for scope_metrics in self.scope_metrics ], "schema_url": self.schema_url, - } + }, + indent=indent, ) @@ -204,11 +208,11 @@ class MetricsData: resource_metrics: Sequence[ResourceMetrics] - def to_json(self) -> str: + def to_json(self, indent=4) -> str: return dumps( { "resource_metrics": [ - loads(resource_metrics.to_json()) + loads(resource_metrics.to_json(indent=indent)) for resource_metrics in self.resource_metrics ] } diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py index d99f097b389..996a7f28002 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py @@ -242,6 +242,15 @@ def __hash__(self): f"{dumps(self._attributes.copy(), sort_keys=True)}|{self._schema_url}" ) + def to_json(self, indent=4) -> str: + return dumps( + { + "attributes": dict(self._attributes), + "schema_url": self._schema_url, + }, + indent=indent, + ) + _EMPTY_RESOURCE = Resource({}) _DEFAULT_RESOURCE = Resource( diff --git a/opentelemetry-sdk/tests/metrics/test_point.py b/opentelemetry-sdk/tests/metrics/test_point.py index 9c509bbd958..5d6640fdea6 100644 --- a/opentelemetry-sdk/tests/metrics/test_point.py +++ b/opentelemetry-sdk/tests/metrics/test_point.py @@ -190,63 +190,71 @@ def setUpClass(cls): def test_number_data_point(self): self.assertEqual( - self.number_data_point_0.to_json(), self.number_data_point_0_str + self.number_data_point_0.to_json(indent=None), + self.number_data_point_0_str, ) self.assertEqual( - self.number_data_point_1.to_json(), self.number_data_point_1_str + self.number_data_point_1.to_json(indent=None), + self.number_data_point_1_str, ) def test_histogram_data_point(self): self.assertEqual( - self.histogram_data_point_0.to_json(), + self.histogram_data_point_0.to_json(indent=None), self.histogram_data_point_0_str, ) self.assertEqual( - self.histogram_data_point_1.to_json(), + self.histogram_data_point_1.to_json(indent=None), self.histogram_data_point_1_str, ) def test_sum(self): - self.assertEqual(self.sum_0.to_json(), self.sum_0_str) + self.assertEqual(self.sum_0.to_json(indent=None), self.sum_0_str) def test_gauge(self): - self.assertEqual(self.gauge_0.to_json(), self.gauge_0_str) + self.maxDiff = None + + self.assertEqual(self.gauge_0.to_json(indent=None), self.gauge_0_str) def test_histogram(self): - self.assertEqual(self.histogram_0.to_json(), self.histogram_0_str) + self.assertEqual( + self.histogram_0.to_json(indent=None), self.histogram_0_str + ) def test_metric(self): - self.assertEqual(self.metric_0.to_json(), self.metric_0_str) + self.assertEqual(self.metric_0.to_json(indent=None), self.metric_0_str) - self.assertEqual(self.metric_1.to_json(), self.metric_1_str) + self.assertEqual(self.metric_1.to_json(indent=None), self.metric_1_str) - self.assertEqual(self.metric_2.to_json(), self.metric_2_str) + self.assertEqual(self.metric_2.to_json(indent=None), self.metric_2_str) def test_scope_metrics(self): self.assertEqual( - self.scope_metrics_0.to_json(), self.scope_metrics_0_str + self.scope_metrics_0.to_json(indent=None), self.scope_metrics_0_str ) self.assertEqual( - self.scope_metrics_1.to_json(), self.scope_metrics_1_str + self.scope_metrics_1.to_json(indent=None), self.scope_metrics_1_str ) def test_resource_metrics(self): self.assertEqual( - self.resource_metrics_0.to_json(), self.resource_metrics_0_str + self.resource_metrics_0.to_json(indent=None), + self.resource_metrics_0_str, ) self.assertEqual( - self.resource_metrics_1.to_json(), self.resource_metrics_1_str + self.resource_metrics_1.to_json(indent=None), + self.resource_metrics_1_str, ) def test_metrics_data(self): self.assertEqual( - self.metrics_data_0.to_json(), self.metrics_data_0_str + self.metrics_data_0.to_json(indent=None), self.metrics_data_0_str ) From 06974a14fef85cc60d4718bb7b23226d7f1164f2 Mon Sep 17 00:00:00 2001 From: Diego Hurtado Date: Mon, 6 Jun 2022 11:37:37 +0100 Subject: [PATCH 3/4] Rename to metrics data --- .../opentelemetry/sdk/metrics/_internal/export/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/export/__init__.py index 684cb4c5067..8ed5596c81a 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/export/__init__.py @@ -105,8 +105,8 @@ def __init__( self, out: IO = stdout, formatter: Callable[ - ["opentelemetry.sdk.metrics.export.Metric"], str - ] = lambda metric: metric.to_json() + ["opentelemetry.sdk.metrics.export.MetricsData"], str + ] = lambda metrics_data: metrics_data.to_json() + linesep, ): self.out = out From 6c1e0bd5eb8eeb2ba6158166b7d9bb7e36c7b18a Mon Sep 17 00:00:00 2001 From: Diego Hurtado Date: Wed, 8 Jun 2022 14:32:16 +0100 Subject: [PATCH 4/4] Add to_json to Instrumentation Scope as well Fixes #2716 --- .../sdk/metrics/_internal/point.py | 6 +--- .../opentelemetry/sdk/util/instrumentation.py | 29 +++++++++++++------ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/point.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/point.py index 18fe81daaac..b4d813accaf 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/point.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/point.py @@ -165,11 +165,7 @@ class ScopeMetrics: def to_json(self, indent=4) -> str: return dumps( { - "scope": { - "name": self.scope.name, - "version": self.scope.version, - "schema_url": self.scope.schema_url, - }, + "scope": loads(self.scope.to_json(indent=indent)), "metrics": [ loads(metric.to_json(indent=indent)) for metric in self.metrics diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/util/instrumentation.py b/opentelemetry-sdk/src/opentelemetry/sdk/util/instrumentation.py index a489c207a03..55d7c6277ba 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/util/instrumentation.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/util/instrumentation.py @@ -11,7 +11,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -import typing +from json import dumps +from typing import Optional from deprecated import deprecated @@ -29,8 +30,8 @@ class InstrumentationInfo: def __init__( self, name: str, - version: typing.Optional[str] = None, - schema_url: typing.Optional[str] = None, + version: Optional[str] = None, + schema_url: Optional[str] = None, ): self._name = name self._version = version @@ -59,11 +60,11 @@ def __lt__(self, value): ) @property - def schema_url(self) -> typing.Optional[str]: + def schema_url(self) -> Optional[str]: return self._schema_url @property - def version(self) -> typing.Optional[str]: + def version(self) -> Optional[str]: return self._version @property @@ -84,8 +85,8 @@ class InstrumentationScope: def __init__( self, name: str, - version: typing.Optional[str] = None, - schema_url: typing.Optional[str] = None, + version: Optional[str] = None, + schema_url: Optional[str] = None, ) -> None: self._name = name self._version = version @@ -116,13 +117,23 @@ def __lt__(self, value: object) -> bool: ) @property - def schema_url(self) -> typing.Optional[str]: + def schema_url(self) -> Optional[str]: return self._schema_url @property - def version(self) -> typing.Optional[str]: + def version(self) -> Optional[str]: return self._version @property def name(self) -> str: return self._name + + def to_json(self, indent=4) -> str: + return dumps( + { + "name": self._name, + "version": self._version, + "schema_url": self._schema_url, + }, + indent=indent, + )