Skip to content

Commit d7d9b15

Browse files
Resources sdk (#464)
Moving resources to the SDK, as per the specification. Modifying the values it accepts to int, float, str, and bool, to match the spec. Introducing an empty resource. To short-circuit the common empty situation.
1 parent 9af7951 commit d7d9b15

File tree

8 files changed

+119
-77
lines changed

8 files changed

+119
-77
lines changed

opentelemetry-api/src/opentelemetry/resources/__init__.py

-55
This file was deleted.

opentelemetry-api/src/opentelemetry/resources/py.typed

Whitespace-only changes.

opentelemetry-sdk/src/opentelemetry/sdk/metrics/__init__.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from opentelemetry import metrics as metrics_api
2020
from opentelemetry.sdk.metrics.export.aggregate import Aggregator
2121
from opentelemetry.sdk.metrics.export.batcher import Batcher, UngroupedBatcher
22+
from opentelemetry.sdk.resources import Resource
2223
from opentelemetry.sdk.util.instrumentation import InstrumentationInfo
2324
from opentelemetry.util import time_ns
2425

@@ -289,12 +290,16 @@ class Meter(metrics_api.Meter):
289290
"""
290291

291292
def __init__(
292-
self, instrumentation_info: "InstrumentationInfo", stateful: bool,
293+
self,
294+
instrumentation_info: "InstrumentationInfo",
295+
stateful: bool,
296+
resource: Resource = Resource.create_empty(),
293297
):
294298
self.instrumentation_info = instrumentation_info
295299
self.metrics = set()
296300
self.observers = set()
297301
self.batcher = UngroupedBatcher(stateful)
302+
self.resource = resource
298303

299304
def collect(self) -> None:
300305
"""Collects all the metrics created with this `Meter` for export.
@@ -400,6 +405,11 @@ def get_label_set(self, labels: Dict[str, str]):
400405

401406

402407
class MeterProvider(metrics_api.MeterProvider):
408+
def __init__(
409+
self, resource: Resource = Resource.create_empty(),
410+
):
411+
self.resource = resource
412+
403413
def get_meter(
404414
self,
405415
instrumenting_module_name: str,
@@ -413,4 +423,5 @@ def get_meter(
413423
instrumenting_module_name, instrumenting_library_version
414424
),
415425
stateful=stateful,
426+
resource=self.resource,
416427
)

opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py

+25-16
Original file line numberDiff line numberDiff line change
@@ -12,33 +12,42 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
import opentelemetry.resources as resources
15+
import typing
1616

17+
LabelValue = typing.Union[str, bool, int, float]
18+
Labels = typing.Dict[str, LabelValue]
1719

18-
class Resource(resources.Resource):
19-
def __init__(self, labels):
20-
self._labels = labels
20+
21+
class Resource:
22+
def __init__(self, labels: Labels):
23+
self._labels = labels.copy()
2124

2225
@staticmethod
23-
def create(labels):
26+
def create(labels: Labels) -> "Resource":
27+
if not labels:
28+
return _EMPTY_RESOURCE
2429
return Resource(labels)
2530

31+
@staticmethod
32+
def create_empty() -> "Resource":
33+
return _EMPTY_RESOURCE
34+
2635
@property
27-
def labels(self):
28-
return self._labels
29-
30-
def merge(self, other):
31-
if other is None:
32-
return self
33-
if not self._labels:
34-
return other
35-
merged_labels = self.labels.copy()
36-
for key, value in other.labels.items():
36+
def labels(self) -> Labels:
37+
return self._labels.copy()
38+
39+
def merge(self, other: "Resource") -> "Resource":
40+
merged_labels = self.labels
41+
# pylint: disable=protected-access
42+
for key, value in other._labels.items():
3743
if key not in merged_labels or merged_labels[key] == "":
3844
merged_labels[key] = value
3945
return Resource(merged_labels)
4046

4147
def __eq__(self, other: object) -> bool:
4248
if not isinstance(other, Resource):
4349
return False
44-
return self.labels == other.labels
50+
return self._labels == other._labels
51+
52+
53+
_EMPTY_RESOURCE = Resource({})

opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from opentelemetry import context as context_api
2626
from opentelemetry import trace as trace_api
2727
from opentelemetry.sdk import util
28+
from opentelemetry.sdk.resources import Resource
2829
from opentelemetry.sdk.util import BoundedDict, BoundedList
2930
from opentelemetry.sdk.util.instrumentation import InstrumentationInfo
3031
from opentelemetry.trace import SpanContext, sampling
@@ -127,7 +128,7 @@ class Span(trace_api.Span):
127128
remote, null if this is a root span
128129
sampler: The sampler used to create this span
129130
trace_config: TODO
130-
resource: TODO
131+
resource: Entity producing telemetry
131132
attributes: The span's attributes to be exported
132133
events: Timestamped events to be exported
133134
links: Links to other spans to be exported
@@ -147,7 +148,7 @@ def __init__(
147148
parent: trace_api.ParentSpan = None,
148149
sampler: Optional[sampling.Sampler] = None,
149150
trace_config: None = None, # TODO
150-
resource: None = None, # TODO
151+
resource: None = None,
151152
attributes: types.Attributes = None, # TODO
152153
events: Sequence[trace_api.Event] = None, # TODO
153154
links: Sequence[trace_api.Link] = (),
@@ -486,6 +487,7 @@ def start_span( # pylint: disable=too-many-locals
486487
context=context,
487488
parent=parent,
488489
sampler=self.source.sampler,
490+
resource=self.source.resource,
489491
attributes=span_attributes,
490492
span_processor=self.source._active_span_processor, # pylint:disable=protected-access
491493
kind=kind,
@@ -535,9 +537,11 @@ class TracerProvider(trace_api.TracerProvider):
535537
def __init__(
536538
self,
537539
sampler: sampling.Sampler = trace_api.sampling.ALWAYS_ON,
540+
resource: Resource = Resource.create_empty(),
538541
shutdown_on_exit: bool = True,
539542
):
540543
self._active_span_processor = MultiSpanProcessor()
544+
self.resource = resource
541545
self.sampler = sampler
542546
self._atexit_handler = None
543547
if shutdown_on_exit:

opentelemetry-sdk/tests/metrics/test_metrics.py

+19-2
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,24 @@
1616
from unittest import mock
1717

1818
from opentelemetry import metrics as metrics_api
19-
from opentelemetry.sdk import metrics
19+
from opentelemetry.sdk import metrics, resources
2020
from opentelemetry.sdk.metrics import export
2121

2222

23+
class TestMeterProvider(unittest.TestCase):
24+
def test_resource(self):
25+
resource = resources.Resource.create({})
26+
meter_provider = metrics.MeterProvider(resource=resource)
27+
meter = meter_provider.get_meter(__name__)
28+
self.assertIs(meter.resource, resource)
29+
30+
def test_resource_empty(self):
31+
meter_provider = metrics.MeterProvider()
32+
meter = meter_provider.get_meter(__name__)
33+
# pylint: disable=protected-access
34+
self.assertIs(meter.resource, resources._EMPTY_RESOURCE)
35+
36+
2337
class TestMeter(unittest.TestCase):
2438
def test_extends_api(self):
2539
meter = metrics.MeterProvider().get_meter(__name__)
@@ -126,13 +140,16 @@ def test_record_batch_exists(self):
126140
self.assertEqual(handle.aggregator.current, 2.0)
127141

128142
def test_create_metric(self):
129-
meter = metrics.MeterProvider().get_meter(__name__)
143+
resource = mock.Mock(spec=resources.Resource)
144+
meter_provider = metrics.MeterProvider(resource=resource)
145+
meter = meter_provider.get_meter(__name__)
130146
counter = meter.create_metric(
131147
"name", "desc", "unit", int, metrics.Counter, ()
132148
)
133149
self.assertIsInstance(counter, metrics.Counter)
134150
self.assertEqual(counter.value_type, int)
135151
self.assertEqual(counter.name, "name")
152+
self.assertIs(counter.meter.resource, resource)
136153

137154
def test_create_measure(self):
138155
meter = metrics.MeterProvider().get_meter(__name__)

opentelemetry-sdk/tests/resources/test_init.py renamed to opentelemetry-sdk/tests/resources/test_resources.py

+42
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,35 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
# pylint: disable=protected-access
16+
1517
import unittest
1618

1719
from opentelemetry.sdk import resources
1820

1921

2022
class TestResources(unittest.TestCase):
23+
def test_create(self):
24+
labels = {
25+
"service": "ui",
26+
"version": 1,
27+
"has_bugs": True,
28+
"cost": 112.12,
29+
}
30+
31+
resource = resources.Resource.create(labels)
32+
self.assertIsInstance(resource, resources.Resource)
33+
self.assertEqual(resource.labels, labels)
34+
35+
resource = resources.Resource.create_empty()
36+
self.assertIs(resource, resources._EMPTY_RESOURCE)
37+
38+
resource = resources.Resource.create(None)
39+
self.assertIs(resource, resources._EMPTY_RESOURCE)
40+
41+
resource = resources.Resource.create({})
42+
self.assertIs(resource, resources._EMPTY_RESOURCE)
43+
2144
def test_resource_merge(self):
2245
left = resources.Resource({"service": "ui"})
2346
right = resources.Resource({"host": "service-host"})
@@ -41,3 +64,22 @@ def test_resource_merge_empty_string(self):
4164
left.merge(right),
4265
resources.Resource({"service": "ui", "host": "service-host"}),
4366
)
67+
68+
def test_immutability(self):
69+
labels = {
70+
"service": "ui",
71+
"version": 1,
72+
"has_bugs": True,
73+
"cost": 112.12,
74+
}
75+
76+
labels_copy = labels.copy()
77+
78+
resource = resources.Resource.create(labels)
79+
self.assertEqual(resource.labels, labels_copy)
80+
81+
resource.labels["has_bugs"] = False
82+
self.assertEqual(resource.labels, labels_copy)
83+
84+
labels["cost"] = 999.91
85+
self.assertEqual(resource.labels, labels_copy)

opentelemetry-sdk/tests/trace/test_trace.py

+15-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from unittest import mock
2020

2121
from opentelemetry import trace as trace_api
22-
from opentelemetry.sdk import trace
22+
from opentelemetry.sdk import resources, trace
2323
from opentelemetry.sdk.util.instrumentation import InstrumentationInfo
2424
from opentelemetry.trace import sampling
2525
from opentelemetry.trace.status import StatusCanonicalCode
@@ -364,6 +364,20 @@ def test_start_as_current_span_explicit(self):
364364
self.assertIs(tracer.get_current_span(), root)
365365
self.assertIsNotNone(child.end_time)
366366

367+
def test_explicit_span_resource(self):
368+
resource = resources.Resource.create({})
369+
tracer_provider = trace.TracerProvider(resource=resource)
370+
tracer = tracer_provider.get_tracer(__name__)
371+
span = tracer.start_span("root")
372+
self.assertIs(span.resource, resource)
373+
374+
def test_default_span_resource(self):
375+
tracer_provider = trace.TracerProvider()
376+
tracer = tracer_provider.get_tracer(__name__)
377+
span = tracer.start_span("root")
378+
# pylint: disable=protected-access
379+
self.assertIs(span.resource, resources._EMPTY_RESOURCE)
380+
367381

368382
class TestSpan(unittest.TestCase):
369383
def setUp(self):

0 commit comments

Comments
 (0)