Skip to content

Commit 24561c6

Browse files
committed
Add InMemoryMetricReader to metrics SDK
1 parent 09a03ae commit 24561c6

File tree

3 files changed

+107
-2
lines changed

3 files changed

+107
-2
lines changed

Diff for: CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
([#2525](https://github.com/open-telemetry/opentelemetry-python/pull/2525))
1313
- Change OTLPHandler to LoggingHandler
1414
([#2528](https://github.com/open-telemetry/opentelemetry-python/pull/2528))
15+
- Add InMemoryMetricReader to metrics SDK
16+
([#2540](https://github.com/open-telemetry/opentelemetry-python/pull/2540))
1517

1618
## [1.10.0-0.29b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.10.0-0.29b0) - 2022-03-10
1719

Diff for: opentelemetry-sdk/src/opentelemetry/sdk/_metrics/export/__init__.py

+29-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
from enum import Enum
1919
from os import environ, linesep
2020
from sys import stdout
21-
from threading import Event, Thread
22-
from typing import IO, Callable, Iterable, Optional, Sequence
21+
from threading import Event, RLock, Thread
22+
from typing import IO, Callable, Iterable, List, Optional, Sequence
2323

2424
from opentelemetry.context import (
2525
_SUPPRESS_INSTRUMENTATION_KEY,
@@ -96,6 +96,33 @@ def shutdown(self) -> None:
9696
pass
9797

9898

99+
class InMemoryMetricReader(MetricReader):
100+
"""Implementation of :class:`MetricReader` that returns its metrics from :func:`metrics`.
101+
102+
This is useful for e.g. unit tests.
103+
"""
104+
105+
def __init__(self) -> None:
106+
super().__init__()
107+
self._lock = RLock()
108+
self._metrics: List[Metric] = []
109+
110+
def get_metrics(self) -> List[Metric]:
111+
with self._lock:
112+
self.collect()
113+
metrics = self._metrics
114+
self._metrics = []
115+
return metrics
116+
117+
def _receive_metrics(self, metrics: Iterable[Metric]):
118+
"""Called by `MetricReader.collect` when it receives a batch of metrics"""
119+
with self._lock:
120+
self._metrics = list(metrics)
121+
122+
def shutdown(self) -> None:
123+
pass
124+
125+
99126
class PeriodicExportingMetricReader(MetricReader):
100127
"""`PeriodicExportingMetricReader` is an implementation of `MetricReader`
101128
that collects metrics based on a user-configurable time interval, and passes the
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from unittest import TestCase
16+
from unittest.mock import Mock
17+
18+
from opentelemetry._metrics.measurement import Measurement
19+
from opentelemetry.sdk._metrics import MeterProvider
20+
from opentelemetry.sdk._metrics.export import InMemoryMetricReader
21+
from opentelemetry.sdk._metrics.point import (
22+
AggregationTemporality,
23+
Metric,
24+
Sum,
25+
)
26+
from opentelemetry.sdk.resources import Resource
27+
from opentelemetry.sdk.util.instrumentation import InstrumentationInfo
28+
29+
30+
class TestInMemoryMetricReader(TestCase):
31+
def test_no_metrics(self):
32+
mock_collect_callback = Mock(return_value=[])
33+
reader = InMemoryMetricReader()
34+
reader._set_collect_callback(mock_collect_callback)
35+
self.assertEqual(reader.get_metrics(), [])
36+
mock_collect_callback.assert_called_once()
37+
38+
def test_converts_metrics_to_list(self):
39+
metric = Metric(
40+
attributes={"myattr": "baz"},
41+
description="",
42+
instrumentation_info=InstrumentationInfo("testmetrics"),
43+
name="foo",
44+
resource=Resource.create(),
45+
unit="",
46+
point=Sum(
47+
start_time_unix_nano=1647626444152947792,
48+
time_unix_nano=1647626444153163239,
49+
value=72.3309814450449,
50+
aggregation_temporality=AggregationTemporality.CUMULATIVE,
51+
is_monotonic=True,
52+
),
53+
)
54+
mock_collect_callback = Mock(return_value=(metric,))
55+
reader = InMemoryMetricReader()
56+
reader._set_collect_callback(mock_collect_callback)
57+
58+
returned_metrics = reader.get_metrics()
59+
mock_collect_callback.assert_called_once()
60+
self.assertIsInstance(returned_metrics, list)
61+
self.assertEqual(len(returned_metrics), 1)
62+
self.assertIs(returned_metrics[0], metric)
63+
64+
def test_integration(self):
65+
reader = InMemoryMetricReader()
66+
meter = MeterProvider(metric_readers=[reader]).get_meter("test_meter")
67+
counter1 = meter.create_counter("counter1")
68+
meter.create_observable_gauge(
69+
"observable_gauge1", lambda: [Measurement(value=12)]
70+
)
71+
counter1.add(1, {"foo": "1"})
72+
counter1.add(1, {"foo": "2"})
73+
74+
metrics = reader.get_metrics()
75+
# should be 3 metrics, one from the observable gauge and one for each labelset from the counter
76+
self.assertEqual(len(metrics), 3)

0 commit comments

Comments
 (0)