Skip to content

Commit be3687c

Browse files
authored
Merge branch 'master' into metric
2 parents d24043a + c1ec444 commit be3687c

File tree

37 files changed

+636
-178
lines changed

37 files changed

+636
-178
lines changed

.github/workflows/test.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
name: Test
22

3-
on: [push, pull_request]
3+
on:
4+
push:
5+
branches-ignore:
6+
- 'release/*'
7+
pull_request:
48

59
jobs:
610
build:

docs-requirements.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ sphinx~=2.4
22
sphinx-rtd-theme~=0.4
33
sphinx-autodoc-typehints~=1.10.2
44

5+
# Need to install the api/sdk in the venv for autodoc. Modifying sys.path
6+
# doesn't work for pkg_resources.
7+
./opentelemetry-api
8+
./opentelemetry-sdk
9+
510
# Required by ext packages
611
asgiref~=3.0
712
asyncpg>=0.12.0

docs/conf.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@
2525

2626

2727
source_dirs = [
28-
os.path.abspath("../opentelemetry-api/src/"),
29-
os.path.abspath("../opentelemetry-sdk/src/"),
3028
os.path.abspath("../opentelemetry-instrumentation/src/"),
3129
]
3230

exporter/opentelemetry-exporter-prometheus/src/opentelemetry/exporter/prometheus/__init__.py

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -67,22 +67,22 @@
6767
import collections
6868
import logging
6969
import re
70-
from typing import Sequence
70+
from typing import Iterable, Optional, Sequence, Union
7171

72-
from prometheus_client import start_http_server
7372
from prometheus_client.core import (
7473
REGISTRY,
75-
CollectorRegistry,
7674
CounterMetricFamily,
75+
SummaryMetricFamily,
7776
UnknownMetricFamily,
7877
)
7978

80-
from opentelemetry.metrics import Counter, Metric, ValueRecorder
79+
from opentelemetry.metrics import Counter, ValueRecorder
8180
from opentelemetry.sdk.metrics.export import (
8281
MetricRecord,
8382
MetricsExporter,
8483
MetricsExportResult,
8584
)
85+
from opentelemetry.sdk.metrics.export.aggregate import MinMaxSumCountAggregator
8686

8787
logger = logging.getLogger(__name__)
8888

@@ -110,8 +110,8 @@ def shutdown(self) -> None:
110110

111111

112112
class CustomCollector:
113-
""" CustomCollector represents the Prometheus Collector object
114-
https://github.com/prometheus/client_python#custom-collectors
113+
"""CustomCollector represents the Prometheus Collector object
114+
https://github.com/prometheus/client_python#custom-collectors
115115
"""
116116

117117
def __init__(self, prefix: str = ""):
@@ -121,7 +121,7 @@ def __init__(self, prefix: str = ""):
121121
r"[^\w]", re.UNICODE | re.IGNORECASE
122122
)
123123

124-
def add_metrics_data(self, metric_records: Sequence[MetricRecord]):
124+
def add_metrics_data(self, metric_records: Sequence[MetricRecord]) -> None:
125125
self._metrics_to_export.append(metric_records)
126126

127127
def collect(self):
@@ -152,34 +152,44 @@ def _translate_to_prometheus(self, metric_record: MetricRecord):
152152
metric_name = self._prefix + "_"
153153
metric_name += self._sanitize(metric_record.instrument.name)
154154

155+
description = getattr(metric_record.instrument, "description", "")
155156
if isinstance(metric_record.instrument, Counter):
156157
prometheus_metric = CounterMetricFamily(
157-
name=metric_name,
158-
documentation=metric_record.instrument.description,
159-
labels=label_keys,
158+
name=metric_name, documentation=description, labels=label_keys
160159
)
161160
prometheus_metric.add_metric(
162161
labels=label_values, value=metric_record.aggregator.checkpoint
163162
)
164163
# TODO: Add support for histograms when supported in OT
165164
elif isinstance(metric_record.instrument, ValueRecorder):
166-
prometheus_metric = UnknownMetricFamily(
167-
name=metric_name,
168-
documentation=metric_record.instrument.description,
169-
labels=label_keys,
170-
)
171-
prometheus_metric.add_metric(
172-
labels=label_values, value=metric_record.aggregator.checkpoint
173-
)
165+
value = metric_record.aggregator.checkpoint
166+
if isinstance(metric_record.aggregator, MinMaxSumCountAggregator):
167+
prometheus_metric = SummaryMetricFamily(
168+
name=metric_name,
169+
documentation=description,
170+
labels=label_keys,
171+
)
172+
prometheus_metric.add_metric(
173+
labels=label_values,
174+
count_value=value.count,
175+
sum_value=value.sum,
176+
)
177+
else:
178+
prometheus_metric = UnknownMetricFamily(
179+
name=metric_name,
180+
documentation=description,
181+
labels=label_keys,
182+
)
183+
prometheus_metric.add_metric(labels=label_values, value=value)
174184

175185
else:
176186
logger.warning(
177187
"Unsupported metric type. %s", type(metric_record.instrument)
178188
)
179189
return prometheus_metric
180190

181-
def _sanitize(self, key):
182-
""" sanitize the given metric name or label according to Prometheus rule.
191+
def _sanitize(self, key: str) -> str:
192+
"""sanitize the given metric name or label according to Prometheus rule.
183193
Replace all characters other than [A-Za-z0-9_] with '_'.
184194
"""
185195
return self._non_letters_nor_digits_re.sub("_", key)

exporter/opentelemetry-exporter-prometheus/tests/test_prometheus_exporter.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import unittest
1616
from unittest import mock
1717

18+
from prometheus_client import generate_latest
1819
from prometheus_client.core import CounterMetricFamily
1920

2021
from opentelemetry.exporter.prometheus import (
@@ -24,7 +25,11 @@
2425
from opentelemetry.metrics import get_meter_provider, set_meter_provider
2526
from opentelemetry.sdk import metrics
2627
from opentelemetry.sdk.metrics.export import MetricRecord, MetricsExportResult
27-
from opentelemetry.sdk.metrics.export.aggregate import SumAggregator
28+
from opentelemetry.sdk.metrics.export.aggregate import (
29+
MinMaxSumCountAggregator,
30+
SumAggregator,
31+
)
32+
from opentelemetry.sdk.util import get_dict_as_key
2833

2934

3035
class TestPrometheusMetricExporter(unittest.TestCase):
@@ -35,7 +40,7 @@ def setUp(self):
3540
"testname", "testdesc", "unit", int, metrics.Counter,
3641
)
3742
labels = {"environment": "staging"}
38-
self._labels_key = metrics.get_dict_as_key(labels)
43+
self._labels_key = get_dict_as_key(labels)
3944

4045
self._mock_registry_register = mock.Mock()
4146
self._registry_register_patch = mock.patch(
@@ -70,13 +75,32 @@ def test_export(self):
7075
self.assertEqual(len(exporter._collector._metrics_to_export), 1)
7176
self.assertIs(result, MetricsExportResult.SUCCESS)
7277

78+
def test_min_max_sum_aggregator_to_prometheus(self):
79+
meter = get_meter_provider().get_meter(__name__)
80+
metric = meter.create_metric(
81+
"test@name", "testdesc", "unit", int, metrics.ValueRecorder, []
82+
)
83+
labels = {}
84+
key_labels = get_dict_as_key(labels)
85+
aggregator = MinMaxSumCountAggregator()
86+
aggregator.update(123)
87+
aggregator.update(456)
88+
aggregator.take_checkpoint()
89+
record = MetricRecord(metric, key_labels, aggregator)
90+
collector = CustomCollector("testprefix")
91+
collector.add_metrics_data([record])
92+
result_bytes = generate_latest(collector)
93+
result = result_bytes.decode("utf-8")
94+
self.assertIn("testprefix_test_name_count 2.0", result)
95+
self.assertIn("testprefix_test_name_sum 579.0", result)
96+
7397
def test_counter_to_prometheus(self):
7498
meter = get_meter_provider().get_meter(__name__)
7599
metric = meter.create_metric(
76100
"test@name", "testdesc", "unit", int, metrics.Counter,
77101
)
78102
labels = {"environment@": "staging", "os": "Windows"}
79-
key_labels = metrics.get_dict_as_key(labels)
103+
key_labels = get_dict_as_key(labels)
80104
aggregator = SumAggregator()
81105
aggregator.update(123)
82106
aggregator.take_checkpoint()
@@ -107,7 +131,7 @@ def test_invalid_metric(self):
107131
"tesname", "testdesc", "unit", int, StubMetric
108132
)
109133
labels = {"environment": "staging"}
110-
key_labels = metrics.get_dict_as_key(labels)
134+
key_labels = get_dict_as_key(labels)
111135
record = MetricRecord(metric, key_labels, None)
112136
collector = CustomCollector("testprefix")
113137
collector.add_metrics_data([record])

exporter/opentelemetry-exporter-zipkin/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## Unreleased
44

5+
- Zipkin exporter now accepts a ``max_tag_value_length`` attribute to customize the
6+
maximum allowed size a tag value can have. ([#1151](https://github.com/open-telemetry/opentelemetry-python/pull/1151))
7+
58
## Version 0.13b0
69

710
Released 2020-09-17

exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py

Lines changed: 49 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474

7575
DEFAULT_RETRY = False
7676
DEFAULT_URL = "http://localhost:9411/api/v2/spans"
77+
DEFAULT_MAX_TAG_VALUE_LENGTH = 128
7778
ZIPKIN_HEADERS = {"Content-Type": "application/json"}
7879

7980
SPAN_KIND_MAP = {
@@ -108,6 +109,7 @@ def __init__(
108109
ipv4: Optional[str] = None,
109110
ipv6: Optional[str] = None,
110111
retry: Optional[str] = DEFAULT_RETRY,
112+
max_tag_value_length: Optional[int] = DEFAULT_MAX_TAG_VALUE_LENGTH,
111113
):
112114
self.service_name = service_name
113115
if url is None:
@@ -122,6 +124,7 @@ def __init__(
122124
self.ipv4 = ipv4
123125
self.ipv6 = ipv6
124126
self.retry = retry
127+
self.max_tag_value_length = max_tag_value_length
125128

126129
def export(self, spans: Sequence[Span]) -> SpanExportResult:
127130
zipkin_spans = self._translate_to_zipkin(spans)
@@ -141,6 +144,9 @@ def export(self, spans: Sequence[Span]) -> SpanExportResult:
141144
return SpanExportResult.FAILURE
142145
return SpanExportResult.SUCCESS
143146

147+
def shutdown(self) -> None:
148+
pass
149+
144150
def _translate_to_zipkin(self, spans: Sequence[Span]):
145151

146152
local_endpoint = {"serviceName": self.service_name, "port": self.port}
@@ -171,8 +177,10 @@ def _translate_to_zipkin(self, spans: Sequence[Span]):
171177
"duration": duration_mus,
172178
"localEndpoint": local_endpoint,
173179
"kind": SPAN_KIND_MAP[span.kind],
174-
"tags": _extract_tags_from_span(span),
175-
"annotations": _extract_annotations_from_events(span.events),
180+
"tags": self._extract_tags_from_span(span),
181+
"annotations": self._extract_annotations_from_events(
182+
span.events
183+
),
176184
}
177185

178186
if span.instrumentation_info is not None:
@@ -184,9 +192,9 @@ def _translate_to_zipkin(self, spans: Sequence[Span]):
184192
] = span.instrumentation_info.version
185193

186194
if span.status is not None:
187-
zipkin_span["tags"][
188-
"otel.status_code"
189-
] = span.status.canonical_code.value
195+
zipkin_span["tags"]["otel.status_code"] = str(
196+
span.status.canonical_code.value
197+
)
190198
if span.status.description is not None:
191199
zipkin_span["tags"][
192200
"otel.status_description"
@@ -205,42 +213,44 @@ def _translate_to_zipkin(self, spans: Sequence[Span]):
205213
zipkin_spans.append(zipkin_span)
206214
return zipkin_spans
207215

208-
def shutdown(self) -> None:
209-
pass
210-
216+
def _extract_tags_from_dict(self, tags_dict):
217+
tags = {}
218+
if not tags_dict:
219+
return tags
220+
for attribute_key, attribute_value in tags_dict.items():
221+
if isinstance(attribute_value, (int, bool, float)):
222+
value = str(attribute_value)
223+
elif isinstance(attribute_value, str):
224+
value = attribute_value
225+
else:
226+
logger.warning("Could not serialize tag %s", attribute_key)
227+
continue
228+
229+
if self.max_tag_value_length > 0:
230+
value = value[: self.max_tag_value_length]
231+
tags[attribute_key] = value
232+
return tags
211233

212-
def _extract_tags_from_dict(tags_dict):
213-
tags = {}
214-
if not tags_dict:
234+
def _extract_tags_from_span(self, span: Span):
235+
tags = self._extract_tags_from_dict(getattr(span, "attributes", None))
236+
if span.resource:
237+
tags.update(self._extract_tags_from_dict(span.resource.attributes))
215238
return tags
216-
for attribute_key, attribute_value in tags_dict.items():
217-
if isinstance(attribute_value, (int, bool, float)):
218-
value = str(attribute_value)
219-
elif isinstance(attribute_value, str):
220-
value = attribute_value[:128]
221-
else:
222-
logger.warning("Could not serialize tag %s", attribute_key)
223-
continue
224-
tags[attribute_key] = value
225-
return tags
226-
227-
228-
def _extract_tags_from_span(span: Span):
229-
tags = _extract_tags_from_dict(getattr(span, "attributes", None))
230-
if span.resource:
231-
tags.update(_extract_tags_from_dict(span.resource.attributes))
232-
return tags
233-
234-
235-
def _extract_annotations_from_events(events):
236-
return (
237-
[
238-
{"timestamp": _nsec_to_usec_round(e.timestamp), "value": e.name}
239-
for e in events
240-
]
241-
if events
242-
else None
243-
)
239+
240+
def _extract_annotations_from_events(
241+
self, events
242+
): # pylint: disable=R0201
243+
return (
244+
[
245+
{
246+
"timestamp": _nsec_to_usec_round(e.timestamp),
247+
"value": e.name,
248+
}
249+
for e in events
250+
]
251+
if events
252+
else None
253+
)
244254

245255

246256
def _nsec_to_usec_round(nsec):

0 commit comments

Comments
 (0)