Skip to content

Commit ca8c097

Browse files
authored
Resources prototype (open-telemetry#853)
1 parent 112bade commit ca8c097

File tree

17 files changed

+617
-16
lines changed

17 files changed

+617
-16
lines changed

docs/examples/basic_tracer/README.rst

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
Basic Trace
2+
===========
3+
4+
These examples show how to use OpenTelemetry to create and export Spans.
5+
6+
There are two different examples:
7+
8+
* basic_trace: Shows how to configure a SpanProcessor and Exporter, and how to create a tracer and span.
9+
10+
* resources: Shows how to add resource information to a Provider. Note that this must be run on a Google Compute Engine instance.
11+
12+
The source files of these examples are available :scm_web:`here <docs/examples/basic_tracer/>`.
13+
14+
Installation
15+
------------
16+
17+
.. code-block:: sh
18+
19+
pip install opentelemetry-api
20+
pip install opentelemetry-sdk
21+
22+
Run the Example
23+
---------------
24+
25+
.. code-block:: sh
26+
27+
python <example_name>.py
28+
29+
The output will be shown in the console.
30+
31+
Useful links
32+
------------
33+
34+
- OpenTelemetry_
35+
- :doc:`../../api/trace`
36+
37+
.. _OpenTelemetry: https://github.com/open-telemetry/opentelemetry-python/
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from opentelemetry import trace
2+
from opentelemetry.sdk.trace import TracerProvider
3+
from opentelemetry.sdk.trace.export import (
4+
ConsoleSpanExporter,
5+
SimpleExportSpanProcessor,
6+
)
7+
8+
trace.set_tracer_provider(TracerProvider())
9+
trace.get_tracer_provider().add_span_processor(
10+
SimpleExportSpanProcessor(ConsoleSpanExporter())
11+
)
12+
tracer = trace.get_tracer(__name__)
13+
with tracer.start_as_current_span("foo"):
14+
print("Hello world!")
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from opentelemetry import trace
2+
from opentelemetry.sdk.resources import get_aggregated_resources
3+
from opentelemetry.sdk.trace import TracerProvider
4+
from opentelemetry.sdk.trace.export import (
5+
ConsoleSpanExporter,
6+
SimpleExportSpanProcessor,
7+
)
8+
from opentelemetry.tools.resource_detector import GoogleCloudResourceDetector
9+
10+
resources = get_aggregated_resources([GoogleCloudResourceDetector()])
11+
12+
trace.set_tracer_provider(TracerProvider(resource=resources))
13+
14+
trace.get_tracer_provider().add_span_processor(
15+
SimpleExportSpanProcessor(ConsoleSpanExporter())
16+
)
17+
tracer = trace.get_tracer(__name__)
18+
with tracer.start_as_current_span("foo"):
19+
print("Hello world!")

ext/opentelemetry-exporter-cloud-monitoring/CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## Unreleased
44

5+
- Add support for resources
6+
([#853](https://github.com/open-telemetry/opentelemetry-python/pull/853))
7+
58
## Version 0.10b0
69

710
Released 2020-06-23

ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py

+37-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import google.auth
66
from google.api.label_pb2 import LabelDescriptor
77
from google.api.metric_pb2 import MetricDescriptor
8+
from google.api.monitored_resource_pb2 import MonitoredResource
89
from google.cloud.monitoring_v3 import MetricServiceClient
910
from google.cloud.monitoring_v3.proto.metric_pb2 import TimeSeries
1011

@@ -14,12 +15,21 @@
1415
MetricsExportResult,
1516
)
1617
from opentelemetry.sdk.metrics.export.aggregate import SumAggregator
18+
from opentelemetry.sdk.resources import Resource
1719

1820
logger = logging.getLogger(__name__)
1921
MAX_BATCH_WRITE = 200
2022
WRITE_INTERVAL = 10
2123
UNIQUE_IDENTIFIER_KEY = "opentelemetry_id"
2224

25+
OT_RESOURCE_LABEL_TO_GCP = {
26+
"gce_instance": {
27+
"cloud.account.id": "project_id",
28+
"host.id": "instance_id",
29+
"cloud.zone": "zone",
30+
}
31+
}
32+
2333

2434
# pylint is unable to resolve members of protobuf objects
2535
# pylint: disable=no-member
@@ -56,13 +66,33 @@ def __init__(
5666
random.randint(0, 16 ** 8)
5767
)
5868

59-
def _add_resource_info(self, series: TimeSeries) -> None:
69+
@staticmethod
70+
def _get_monitored_resource(
71+
resource: Resource,
72+
) -> Optional[MonitoredResource]:
6073
"""Add Google resource specific information (e.g. instance id, region).
6174
75+
See
76+
https://cloud.google.com/monitoring/custom-metrics/creating-metrics#custom-metric-resources
77+
for supported types
6278
Args:
6379
series: ProtoBuf TimeSeries
6480
"""
65-
# TODO: Leverage this better
81+
82+
if resource.labels.get("cloud.provider") != "gcp":
83+
return None
84+
resource_type = resource.labels["gcp.resource_type"]
85+
if resource_type not in OT_RESOURCE_LABEL_TO_GCP:
86+
return None
87+
return MonitoredResource(
88+
type=resource_type,
89+
labels={
90+
gcp_label: str(resource.labels[ot_label])
91+
for ot_label, gcp_label in OT_RESOURCE_LABEL_TO_GCP[
92+
resource_type
93+
].items()
94+
},
95+
)
6696

6797
def _batch_write(self, series: TimeSeries) -> None:
6898
""" Cloud Monitoring allows writing up to 200 time series at once
@@ -162,9 +192,11 @@ def export(
162192
metric_descriptor = self._get_metric_descriptor(record)
163193
if not metric_descriptor:
164194
continue
165-
166-
series = TimeSeries()
167-
self._add_resource_info(series)
195+
series = TimeSeries(
196+
resource=self._get_monitored_resource(
197+
record.instrument.meter.resource
198+
)
199+
)
168200
series.metric.type = metric_descriptor.type
169201
for key, value in record.labels:
170202
series.metric.labels[key] = str(value)

ext/opentelemetry-exporter-cloud-monitoring/tests/test_cloud_monitoring.py

+97-5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
from google.api.label_pb2 import LabelDescriptor
1919
from google.api.metric_pb2 import MetricDescriptor
20+
from google.api.monitored_resource_pb2 import MonitoredResource
2021
from google.cloud.monitoring_v3.proto.metric_pb2 import TimeSeries
2122

2223
from opentelemetry.exporter.cloud_monitoring import (
@@ -27,17 +28,30 @@
2728
)
2829
from opentelemetry.sdk.metrics.export import MetricRecord
2930
from opentelemetry.sdk.metrics.export.aggregate import SumAggregator
31+
from opentelemetry.sdk.resources import Resource
3032

3133

3234
class UnsupportedAggregator:
3335
pass
3436

3537

38+
class MockMeter:
39+
def __init__(self, resource=Resource.create_empty()):
40+
self.resource = resource
41+
42+
3643
class MockMetric:
37-
def __init__(self, name="name", description="description", value_type=int):
44+
def __init__(
45+
self,
46+
name="name",
47+
description="description",
48+
value_type=int,
49+
meter=None,
50+
):
3851
self.name = name
3952
self.description = description
4053
self.value_type = value_type
54+
self.meter = meter or MockMeter()
4155

4256

4357
# pylint: disable=protected-access
@@ -205,24 +219,41 @@ def test_export(self):
205219
}
206220
)
207221

222+
resource = Resource(
223+
labels={
224+
"cloud.account.id": 123,
225+
"host.id": "host",
226+
"cloud.zone": "US",
227+
"cloud.provider": "gcp",
228+
"extra_info": "extra",
229+
"gcp.resource_type": "gce_instance",
230+
"not_gcp_resource": "value",
231+
}
232+
)
233+
208234
sum_agg_one = SumAggregator()
209235
sum_agg_one.checkpoint = 1
210236
sum_agg_one.last_update_timestamp = (WRITE_INTERVAL + 1) * 1e9
211237
exporter.export(
212238
[
213239
MetricRecord(
214-
MockMetric(),
240+
MockMetric(meter=MockMeter(resource=resource)),
215241
(("label1", "value1"), ("label2", 1),),
216242
sum_agg_one,
217243
),
218244
MetricRecord(
219-
MockMetric(),
245+
MockMetric(meter=MockMeter(resource=resource)),
220246
(("label1", "value2"), ("label2", 2),),
221247
sum_agg_one,
222248
),
223249
]
224250
)
225-
series1 = TimeSeries()
251+
expected_resource = MonitoredResource(
252+
type="gce_instance",
253+
labels={"project_id": "123", "instance_id": "host", "zone": "US"},
254+
)
255+
256+
series1 = TimeSeries(resource=expected_resource)
226257
series1.metric.type = "custom.googleapis.com/OpenTelemetry/name"
227258
series1.metric.labels["label1"] = "value1"
228259
series1.metric.labels["label2"] = "1"
@@ -231,7 +262,7 @@ def test_export(self):
231262
point.interval.end_time.seconds = WRITE_INTERVAL + 1
232263
point.interval.end_time.nanos = 0
233264

234-
series2 = TimeSeries()
265+
series2 = TimeSeries(resource=expected_resource)
235266
series2.metric.type = "custom.googleapis.com/OpenTelemetry/name"
236267
series2.metric.labels["label1"] = "value2"
237268
series2.metric.labels["label2"] = "2"
@@ -342,3 +373,64 @@ def test_unique_identifier(self):
342373
first_call[0][1][0].metric.labels[UNIQUE_IDENTIFIER_KEY],
343374
second_call[0][1][0].metric.labels[UNIQUE_IDENTIFIER_KEY],
344375
)
376+
377+
def test_extract_resources(self):
378+
exporter = CloudMonitoringMetricsExporter(project_id=self.project_id)
379+
380+
self.assertIsNone(
381+
exporter._get_monitored_resource(Resource.create_empty())
382+
)
383+
resource = Resource(
384+
labels={
385+
"cloud.account.id": 123,
386+
"host.id": "host",
387+
"cloud.zone": "US",
388+
"cloud.provider": "gcp",
389+
"extra_info": "extra",
390+
"gcp.resource_type": "gce_instance",
391+
"not_gcp_resource": "value",
392+
}
393+
)
394+
expected_extract = MonitoredResource(
395+
type="gce_instance",
396+
labels={"project_id": "123", "instance_id": "host", "zone": "US"},
397+
)
398+
self.assertEqual(
399+
exporter._get_monitored_resource(resource), expected_extract
400+
)
401+
402+
resource = Resource(
403+
labels={
404+
"cloud.account.id": "123",
405+
"host.id": "host",
406+
"extra_info": "extra",
407+
"not_gcp_resource": "value",
408+
"gcp.resource_type": "gce_instance",
409+
"cloud.provider": "gcp",
410+
}
411+
)
412+
# Should throw when passed a malformed GCP resource dict
413+
self.assertRaises(KeyError, exporter._get_monitored_resource, resource)
414+
415+
resource = Resource(
416+
labels={
417+
"cloud.account.id": "123",
418+
"host.id": "host",
419+
"extra_info": "extra",
420+
"not_gcp_resource": "value",
421+
"gcp.resource_type": "unsupported_gcp_resource",
422+
"cloud.provider": "gcp",
423+
}
424+
)
425+
self.assertIsNone(exporter._get_monitored_resource(resource))
426+
427+
resource = Resource(
428+
labels={
429+
"cloud.account.id": "123",
430+
"host.id": "host",
431+
"extra_info": "extra",
432+
"not_gcp_resource": "value",
433+
"cloud.provider": "aws",
434+
}
435+
)
436+
self.assertIsNone(exporter._get_monitored_resource(resource))

ext/opentelemetry-exporter-cloud-trace/CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## Unreleased
44

5+
- Add support for resources and resource detector
6+
([#853](https://github.com/open-telemetry/opentelemetry-python/pull/853))
7+
58
## Version 0.10b0
69

710
Released 2020-06-23

ext/opentelemetry-exporter-cloud-trace/setup.cfg

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ package_dir=
3939
=src
4040
packages=find_namespace:
4141
install_requires =
42+
requests
4243
opentelemetry-api
4344
opentelemetry-sdk
4445
google-cloud-trace

0 commit comments

Comments
 (0)