Skip to content

Commit 9de04ab

Browse files
Move encoding of metrics to opentelemetry-exporter-otlp-proto-common
1 parent 1947f3f commit 9de04ab

File tree

11 files changed

+1136
-1175
lines changed

11 files changed

+1136
-1175
lines changed

exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/__init__.py

+39-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
import logging
1717
from collections.abc import Sequence
18-
from typing import Any, Optional, List
18+
from typing import Any, Mapping, Optional, List, Callable, TypeVar, Dict
1919

2020
from opentelemetry.sdk.util.instrumentation import InstrumentationScope
2121
from opentelemetry.proto.common.v1.common_pb2 import (
@@ -26,6 +26,9 @@
2626
)
2727
from opentelemetry.proto.common.v1.common_pb2 import AnyValue as PB2AnyValue
2828
from opentelemetry.proto.common.v1.common_pb2 import KeyValue as PB2KeyValue
29+
from opentelemetry.proto.common.v1.common_pb2 import (
30+
KeyValueList as PB2KeyValueList,
31+
)
2932
from opentelemetry.proto.common.v1.common_pb2 import (
3033
ArrayValue as PB2ArrayValue,
3134
)
@@ -35,6 +38,9 @@
3538

3639
_logger = logging.getLogger(__name__)
3740

41+
_TypingResourceT = TypeVar("_TypingResourceT")
42+
_ResourceDataT = TypeVar("_ResourceDataT")
43+
3844

3945
def _encode_instrumentation_scope(
4046
instrumentation_scope: InstrumentationScope,
@@ -64,9 +70,12 @@ def _encode_value(value: Any) -> PB2AnyValue:
6470
return PB2AnyValue(
6571
array_value=PB2ArrayValue(values=[_encode_value(v) for v in value])
6672
)
67-
# tracing specs currently does not support Mapping type attributes.
68-
# elif isinstance(value, abc.Mapping):
69-
# pass
73+
elif isinstance(value, Mapping):
74+
return PB2AnyValue(
75+
kvlist_value=PB2KeyValueList(
76+
values=[_encode_key_value(str(k), v) for k, v in value.items()]
77+
)
78+
)
7079
raise Exception(f"Invalid type {type(value)} of value {value}")
7180

7281

@@ -95,3 +104,29 @@ def _encode_attributes(
95104
else:
96105
pb2_attributes = None
97106
return pb2_attributes
107+
108+
109+
def _get_resource_data(
110+
sdk_resource_scope_data: Dict[Resource, _ResourceDataT],
111+
resource_class: Callable[..., _TypingResourceT],
112+
name: str,
113+
) -> List[_TypingResourceT]:
114+
115+
resource_data = []
116+
117+
for (
118+
sdk_resource,
119+
scope_data,
120+
) in sdk_resource_scope_data.items():
121+
collector_resource = PB2Resource(
122+
attributes=_encode_attributes(sdk_resource.attributes)
123+
)
124+
resource_data.append(
125+
resource_class(
126+
**{
127+
"resource": collector_resource,
128+
"scope_{}".format(name): scope_data.values(),
129+
}
130+
)
131+
)
132+
return resource_data
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
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+
import logging
15+
16+
from opentelemetry.exporter.otlp.proto.common._internal import (
17+
_encode_attributes,
18+
)
19+
from opentelemetry.proto.collector.metrics.v1.metrics_service_pb2 import (
20+
ExportMetricsServiceRequest,
21+
)
22+
from opentelemetry.proto.common.v1.common_pb2 import InstrumentationScope
23+
from opentelemetry.proto.metrics.v1 import metrics_pb2 as pb2
24+
from opentelemetry.sdk.metrics.export import (
25+
MetricsData,
26+
Gauge,
27+
Histogram,
28+
Sum,
29+
ExponentialHistogram as ExponentialHistogramType,
30+
)
31+
from opentelemetry.proto.resource.v1.resource_pb2 import (
32+
Resource as PB2Resource,
33+
)
34+
35+
_logger = logging.getLogger(__name__)
36+
37+
38+
def encode(data: MetricsData) -> ExportMetricsServiceRequest:
39+
resource_metrics_dict = {}
40+
41+
for resource_metrics in data.resource_metrics:
42+
43+
resource = resource_metrics.resource
44+
45+
# It is safe to assume that each entry in data.resource_metrics is
46+
# associated with an unique resource.
47+
scope_metrics_dict = {}
48+
49+
resource_metrics_dict[resource] = scope_metrics_dict
50+
51+
for scope_metrics in resource_metrics.scope_metrics:
52+
53+
instrumentation_scope = scope_metrics.scope
54+
55+
# The SDK groups metrics in instrumentation scopes already so
56+
# there is no need to check for existing instrumentation scopes
57+
# here.
58+
pb2_scope_metrics = pb2.ScopeMetrics(
59+
scope=InstrumentationScope(
60+
name=instrumentation_scope.name,
61+
version=instrumentation_scope.version,
62+
)
63+
)
64+
65+
scope_metrics_dict[instrumentation_scope] = pb2_scope_metrics
66+
67+
for metric in scope_metrics.metrics:
68+
pb2_metric = pb2.Metric(
69+
name=metric.name,
70+
description=metric.description,
71+
unit=metric.unit,
72+
)
73+
74+
if isinstance(metric.data, Gauge):
75+
for data_point in metric.data.data_points:
76+
pt = pb2.NumberDataPoint(
77+
attributes=_encode_attributes(
78+
data_point.attributes
79+
),
80+
time_unix_nano=data_point.time_unix_nano,
81+
)
82+
if isinstance(data_point.value, int):
83+
pt.as_int = data_point.value
84+
else:
85+
pt.as_double = data_point.value
86+
pb2_metric.gauge.data_points.append(pt)
87+
88+
elif isinstance(metric.data, Histogram):
89+
for data_point in metric.data.data_points:
90+
pt = pb2.HistogramDataPoint(
91+
attributes=_encode_attributes(
92+
data_point.attributes
93+
),
94+
time_unix_nano=data_point.time_unix_nano,
95+
start_time_unix_nano=(
96+
data_point.start_time_unix_nano
97+
),
98+
count=data_point.count,
99+
sum=data_point.sum,
100+
bucket_counts=data_point.bucket_counts,
101+
explicit_bounds=data_point.explicit_bounds,
102+
max=data_point.max,
103+
min=data_point.min,
104+
)
105+
pb2_metric.histogram.aggregation_temporality = (
106+
metric.data.aggregation_temporality
107+
)
108+
pb2_metric.histogram.data_points.append(pt)
109+
110+
elif isinstance(metric.data, Sum):
111+
for data_point in metric.data.data_points:
112+
pt = pb2.NumberDataPoint(
113+
attributes=_encode_attributes(
114+
data_point.attributes
115+
),
116+
start_time_unix_nano=(
117+
data_point.start_time_unix_nano
118+
),
119+
time_unix_nano=data_point.time_unix_nano,
120+
)
121+
if isinstance(data_point.value, int):
122+
pt.as_int = data_point.value
123+
else:
124+
pt.as_double = data_point.value
125+
# note that because sum is a message type, the
126+
# fields must be set individually rather than
127+
# instantiating a pb2.Sum and setting it once
128+
pb2_metric.sum.aggregation_temporality = (
129+
metric.data.aggregation_temporality
130+
)
131+
pb2_metric.sum.is_monotonic = metric.data.is_monotonic
132+
pb2_metric.sum.data_points.append(pt)
133+
134+
elif isinstance(metric.data, ExponentialHistogramType):
135+
for data_point in metric.data.data_points:
136+
137+
if data_point.positive.bucket_counts:
138+
positive = pb2.ExponentialHistogramDataPoint.Buckets(
139+
offset=data_point.positive.offset,
140+
bucket_counts=data_point.positive.bucket_counts,
141+
)
142+
else:
143+
positive = None
144+
145+
if data_point.negative.bucket_counts:
146+
negative = pb2.ExponentialHistogramDataPoint.Buckets(
147+
offset=data_point.negative.offset,
148+
bucket_counts=data_point.negative.bucket_counts,
149+
)
150+
else:
151+
negative = None
152+
153+
pt = pb2.ExponentialHistogramDataPoint(
154+
attributes=_encode_attributes(
155+
data_point.attributes
156+
),
157+
time_unix_nano=data_point.time_unix_nano,
158+
start_time_unix_nano=(
159+
data_point.start_time_unix_nano
160+
),
161+
count=data_point.count,
162+
sum=data_point.sum,
163+
scale=data_point.scale,
164+
zero_count=data_point.zero_count,
165+
positive=positive,
166+
negative=negative,
167+
flags=data_point.flags,
168+
max=data_point.max,
169+
min=data_point.min,
170+
)
171+
pb2_metric.exponential_histogram.aggregation_temporality = (
172+
metric.data.aggregation_temporality
173+
)
174+
pb2_metric.exponential_histogram.data_points.append(pt)
175+
176+
else:
177+
_logger.warning(
178+
"unsupported data type %s",
179+
metric.data.__class__.__name__,
180+
)
181+
continue
182+
183+
pb2_scope_metrics.metrics.append(pb2_metric)
184+
185+
resource_data = []
186+
for (
187+
sdk_resource,
188+
scope_data,
189+
) in resource_metrics_dict.items():
190+
resource_data.append(
191+
pb2.ResourceMetrics(
192+
resource=PB2Resource(
193+
attributes=_encode_attributes(sdk_resource.attributes)
194+
),
195+
scope_metrics=scope_data.values(),
196+
)
197+
)
198+
resource_metrics = resource_data
199+
return ExportMetricsServiceRequest(resource_metrics=resource_metrics)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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+
16+
from opentelemetry.exporter.otlp.proto.common._internal.metrics_encoder import (
17+
encode,
18+
)
19+
20+
__all__ = ["encode"]

0 commit comments

Comments
 (0)