From 7c3872078901071009eb5cfd1f0b468bd6c72bde Mon Sep 17 00:00:00 2001 From: Andrew Xue Date: Wed, 27 May 2020 13:14:46 -0400 Subject: [PATCH 01/21] update changelog --- opentelemetry-sdk/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/opentelemetry-sdk/CHANGELOG.md b/opentelemetry-sdk/CHANGELOG.md index 0e982b6bc14..0cd98b51786 100644 --- a/opentelemetry-sdk/CHANGELOG.md +++ b/opentelemetry-sdk/CHANGELOG.md @@ -23,6 +23,7 @@ Released 2020-05-27 ([#729](https://github.com/open-telemetry/opentelemetry-python/pull/729)) - deep copy empty attributes ([#714](https://github.com/open-telemetry/opentelemetry-python/pull/714)) +- add cloud monitoring exporter ## 0.7b1 From ceb0517bc2a6650766d991e83ede589e51f54423 Mon Sep 17 00:00:00 2001 From: Andrew Xue Date: Wed, 27 May 2020 13:16:44 -0400 Subject: [PATCH 02/21] f --- opentelemetry-sdk/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/opentelemetry-sdk/CHANGELOG.md b/opentelemetry-sdk/CHANGELOG.md index 0cd98b51786..b111befddc3 100644 --- a/opentelemetry-sdk/CHANGELOG.md +++ b/opentelemetry-sdk/CHANGELOG.md @@ -24,6 +24,8 @@ Released 2020-05-27 - deep copy empty attributes ([#714](https://github.com/open-telemetry/opentelemetry-python/pull/714)) - add cloud monitoring exporter + ([#739](https://github.com/open-telemetry/opentelemetry-python/pull/739)) + ## 0.7b1 From 6aeea7f27663ad73f4d9ce2224ad9948a9b78e52 Mon Sep 17 00:00:00 2001 From: Andrew Xue Date: Wed, 27 May 2020 16:04:52 -0400 Subject: [PATCH 03/21] initial commit --- .../examples/metrics.py | 55 +++++++++++++ .../setup.cfg | 49 +++++++++++ .../setup.py | 26 ++++++ .../ext/cloud_monitoring/__init__.py | 82 +++++++++++++++++++ .../ext/cloud_monitoring/version.py | 15 ++++ 5 files changed, 227 insertions(+) create mode 100644 ext/opentelemetry-ext-cloud-monitoring/examples/metrics.py create mode 100644 ext/opentelemetry-ext-cloud-monitoring/setup.cfg create mode 100644 ext/opentelemetry-ext-cloud-monitoring/setup.py create mode 100644 ext/opentelemetry-ext-cloud-monitoring/src/opentelemetry/ext/cloud_monitoring/__init__.py create mode 100644 ext/opentelemetry-ext-cloud-monitoring/src/opentelemetry/ext/cloud_monitoring/version.py diff --git a/ext/opentelemetry-ext-cloud-monitoring/examples/metrics.py b/ext/opentelemetry-ext-cloud-monitoring/examples/metrics.py new file mode 100644 index 00000000000..d9dfa1805d4 --- /dev/null +++ b/ext/opentelemetry-ext-cloud-monitoring/examples/metrics.py @@ -0,0 +1,55 @@ +import sys +import time + +from opentelemetry import metrics +from opentelemetry.sdk.metrics import Counter, MeterProvider +from opentelemetry.sdk.metrics.export import ConsoleMetricsExporter +from opentelemetry.ext.cloud_monitoring import CloudMonitoringMetricsExporter +from opentelemetry.sdk.metrics.export.controller import PushController +from opentelemetry.context import attach, detach, set_value + +metrics.set_meter_provider(MeterProvider()) +meter = metrics.get_meter(__name__, True) +exporter = ConsoleMetricsExporter() +cloud_exporter = CloudMonitoringMetricsExporter('aaxue-starter') + +exporters = [cloud_exporter] + +staging_labels = {"environment": "staging"} +other_labels = {"label1": "s", 'label2': 'b'} + +requests_counter = meter.create_metric( + name="num_requests", + description="number of requests", + unit="1", + value_type=int, + metric_type=Counter, + label_keys=("environment",), +) + +double_counter = meter.create_metric( + name="doublly", + description="double", + unit="unit double name", + value_type=float, + metric_type=Counter, + label_keys=("label1", 'label1'), +) + +def _ghetto_export(): + for exporter in exporters: + meter.collect() + token = attach(set_value("suppress_instrumentation", True)) + to_export = meter.batcher.checkpoint_set() + exporter.export(to_export) + detach(token) + # Perform post-exporting logic based on batcher configuration + meter.batcher.finished_collection() + +requests_counter.add(25, staging_labels) +double_counter.add(2.3, other_labels) +_ghetto_export() +time.sleep(3) +requests_counter.add(20, staging_labels) +double_counter.add(2.53, other_labels) +_ghetto_export() \ No newline at end of file diff --git a/ext/opentelemetry-ext-cloud-monitoring/setup.cfg b/ext/opentelemetry-ext-cloud-monitoring/setup.cfg new file mode 100644 index 00000000000..e784f373cd0 --- /dev/null +++ b/ext/opentelemetry-ext-cloud-monitoring/setup.cfg @@ -0,0 +1,49 @@ + +# Copyright OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +[metadata] +name = opentelemetry-ext-cloud-monitoring +description = Cloud Monitoring integration for OpenTelemetry +long_description = file: README.rst +long_description_content_type = text/x-rst +author = OpenTelemetry Authors +author_email = cncf-opentelemetry-contributors@lists.cncf.io +url = https://github.com/open-telemetry/opentelemetry-python/ext/opentelemetry-ext-cloud-monitoring +platforms = any +license = Apache-2.0 +classifiers = + Development Status :: 4 - Beta + Intended Audience :: Developers + License :: OSI Approved :: Apache Software License + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.4 + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + +[options] +python_requires = >=3.4 +package_dir= + =src +packages=find_namespace: +install_requires = + opentelemetry-api + opentelemetry-sdk + google-cloud-monitoring + google-cloud-trace + +[options.packages.find] +where = src \ No newline at end of file diff --git a/ext/opentelemetry-ext-cloud-monitoring/setup.py b/ext/opentelemetry-ext-cloud-monitoring/setup.py new file mode 100644 index 00000000000..5d802e7ca98 --- /dev/null +++ b/ext/opentelemetry-ext-cloud-monitoring/setup.py @@ -0,0 +1,26 @@ +# Copyright OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os + +import setuptools + +BASE_DIR = os.path.dirname(__file__) +VERSION_FILENAME = os.path.join( + BASE_DIR, "src", "opentelemetry", "ext", "cloud_monitoring", "version.py" +) +PACKAGE_INFO = {} +with open(VERSION_FILENAME) as f: + exec(f.read(), PACKAGE_INFO) + +setuptools.setup(version=PACKAGE_INFO["__version__"]) \ No newline at end of file diff --git a/ext/opentelemetry-ext-cloud-monitoring/src/opentelemetry/ext/cloud_monitoring/__init__.py b/ext/opentelemetry-ext-cloud-monitoring/src/opentelemetry/ext/cloud_monitoring/__init__.py new file mode 100644 index 00000000000..bdf85cd70e7 --- /dev/null +++ b/ext/opentelemetry-ext-cloud-monitoring/src/opentelemetry/ext/cloud_monitoring/__init__.py @@ -0,0 +1,82 @@ +from typing import Sequence + +from google.cloud.monitoring_v3 import MetricServiceClient +from google.cloud.monitoring_v3.proto.metric_pb2 import TimeSeries +from opentelemetry.sdk.metrics.export import ( + MetricsExporter, + MetricRecord, + MetricsExportResult, +) + +MAX_BATCH_WRITE = 200 + +class CloudMonitoringMetricsExporter(MetricsExporter): + """ + Implementation of Metrics Exporter that sends metrics to Google Cloud + Monitoring + """ + + def __init__(self, project_id, client=None): + # TODO cloud monitoring seems to only allow 1 write a minute per time series + self.client = client or MetricServiceClient() + self.project_id = project_id + self.project_name = self.client.project_path(project_id) + self._last_updated = {} + + def _add_resource_info(self, series): + """ Add Google resource specific information (e.g. instance id, region) + + :param series: ProtoBuf TimeSeries + :return: + """ + series.resource.type = "gce_instance" + series.resource.labels["instance_id"] = "1234567890123456789" + series.resource.labels["zone"] = "us-central1-f" + + def _batch_write(self, series): + """ Cloud Monitoring allows writing up to 200 time series at once + + :param series: ProtoBuf TimeSeries + :return: + """ + write_ind = 0 + while write_ind < len(series): + self.client.create_time_series(self.project_name, series[write_ind: write_ind + MAX_BATCH_WRITE]) + write_ind += MAX_BATCH_WRITE + + def export( + self, metric_records: Sequence[MetricRecord] + ) -> "MetricsExportResult": + all_series = [] + for record in metric_records: + recorded_metric = record.metric + aggregated_metric = record.aggregator + series_name = "custom.googleapis.com/OpenTelemetry/{}".format( + recorded_metric.name + ) + series = TimeSeries() + self._add_resource_info(series) + series.metric.type = series_name + for k, v in record.labels: + series.metric.labels[k] = v + point = series.points.add() + if recorded_metric.value_type == int: + point.value.int64_value = aggregated_metric.checkpoint + elif recorded_metric.value_type == float: + point.value.double_value = aggregated_metric.checkpoint + seconds, nanos = divmod( + aggregated_metric.last_update_timestamp, 1e9 + ) + + # Cloud Monitoring allows every timeseries to be updated one per + # minute at most + if seconds - self._last_updated.get(series_name, 0) < 60: + continue + self._last_updated[series.metric.type] = seconds + point.interval.end_time.seconds = int(seconds) + point.interval.end_time.nanos = int(nanos) + all_series.append(series) + print("will write {} to {}".format(point, series.metric.type)) + self._batch_write(all_series) + print("wrote to cloud monitorint") + return MetricsExportResult.SUCCESS diff --git a/ext/opentelemetry-ext-cloud-monitoring/src/opentelemetry/ext/cloud_monitoring/version.py b/ext/opentelemetry-ext-cloud-monitoring/src/opentelemetry/ext/cloud_monitoring/version.py new file mode 100644 index 00000000000..7c18d112277 --- /dev/null +++ b/ext/opentelemetry-ext-cloud-monitoring/src/opentelemetry/ext/cloud_monitoring/version.py @@ -0,0 +1,15 @@ +# Copyright OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__version__ = "0.8.dev0" \ No newline at end of file From 3b62a247684a2f7ff91c4eddc2d5e94f880bca07 Mon Sep 17 00:00:00 2001 From: Andrew Xue Date: Wed, 27 May 2020 16:22:40 -0400 Subject: [PATCH 04/21] lint --- .../examples/metrics.py | 10 ++++++---- ext/opentelemetry-ext-cloud-monitoring/setup.py | 2 +- .../src/opentelemetry/ext/cloud_monitoring/__init__.py | 9 ++++++--- .../src/opentelemetry/ext/cloud_monitoring/version.py | 2 +- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/ext/opentelemetry-ext-cloud-monitoring/examples/metrics.py b/ext/opentelemetry-ext-cloud-monitoring/examples/metrics.py index d9dfa1805d4..6ff6128fa43 100644 --- a/ext/opentelemetry-ext-cloud-monitoring/examples/metrics.py +++ b/ext/opentelemetry-ext-cloud-monitoring/examples/metrics.py @@ -11,12 +11,12 @@ metrics.set_meter_provider(MeterProvider()) meter = metrics.get_meter(__name__, True) exporter = ConsoleMetricsExporter() -cloud_exporter = CloudMonitoringMetricsExporter('aaxue-starter') +cloud_exporter = CloudMonitoringMetricsExporter("aaxue-starter") exporters = [cloud_exporter] staging_labels = {"environment": "staging"} -other_labels = {"label1": "s", 'label2': 'b'} +other_labels = {"label1": "s", "label2": "b"} requests_counter = meter.create_metric( name="num_requests", @@ -33,9 +33,10 @@ unit="unit double name", value_type=float, metric_type=Counter, - label_keys=("label1", 'label1'), + label_keys=("label1", "label1"), ) + def _ghetto_export(): for exporter in exporters: meter.collect() @@ -46,10 +47,11 @@ def _ghetto_export(): # Perform post-exporting logic based on batcher configuration meter.batcher.finished_collection() + requests_counter.add(25, staging_labels) double_counter.add(2.3, other_labels) _ghetto_export() time.sleep(3) requests_counter.add(20, staging_labels) double_counter.add(2.53, other_labels) -_ghetto_export() \ No newline at end of file +_ghetto_export() diff --git a/ext/opentelemetry-ext-cloud-monitoring/setup.py b/ext/opentelemetry-ext-cloud-monitoring/setup.py index 5d802e7ca98..b4266b82655 100644 --- a/ext/opentelemetry-ext-cloud-monitoring/setup.py +++ b/ext/opentelemetry-ext-cloud-monitoring/setup.py @@ -23,4 +23,4 @@ with open(VERSION_FILENAME) as f: exec(f.read(), PACKAGE_INFO) -setuptools.setup(version=PACKAGE_INFO["__version__"]) \ No newline at end of file +setuptools.setup(version=PACKAGE_INFO["__version__"]) diff --git a/ext/opentelemetry-ext-cloud-monitoring/src/opentelemetry/ext/cloud_monitoring/__init__.py b/ext/opentelemetry-ext-cloud-monitoring/src/opentelemetry/ext/cloud_monitoring/__init__.py index bdf85cd70e7..846fa3fbeb9 100644 --- a/ext/opentelemetry-ext-cloud-monitoring/src/opentelemetry/ext/cloud_monitoring/__init__.py +++ b/ext/opentelemetry-ext-cloud-monitoring/src/opentelemetry/ext/cloud_monitoring/__init__.py @@ -10,6 +10,7 @@ MAX_BATCH_WRITE = 200 + class CloudMonitoringMetricsExporter(MetricsExporter): """ Implementation of Metrics Exporter that sends metrics to Google Cloud @@ -17,7 +18,6 @@ class CloudMonitoringMetricsExporter(MetricsExporter): """ def __init__(self, project_id, client=None): - # TODO cloud monitoring seems to only allow 1 write a minute per time series self.client = client or MetricServiceClient() self.project_id = project_id self.project_name = self.client.project_path(project_id) @@ -41,11 +41,14 @@ def _batch_write(self, series): """ write_ind = 0 while write_ind < len(series): - self.client.create_time_series(self.project_name, series[write_ind: write_ind + MAX_BATCH_WRITE]) + self.client.create_time_series( + self.project_name, + series[write_ind : write_ind + MAX_BATCH_WRITE], + ) write_ind += MAX_BATCH_WRITE def export( - self, metric_records: Sequence[MetricRecord] + self, metric_records: Sequence[MetricRecord] ) -> "MetricsExportResult": all_series = [] for record in metric_records: diff --git a/ext/opentelemetry-ext-cloud-monitoring/src/opentelemetry/ext/cloud_monitoring/version.py b/ext/opentelemetry-ext-cloud-monitoring/src/opentelemetry/ext/cloud_monitoring/version.py index 7c18d112277..ec792e9af10 100644 --- a/ext/opentelemetry-ext-cloud-monitoring/src/opentelemetry/ext/cloud_monitoring/version.py +++ b/ext/opentelemetry-ext-cloud-monitoring/src/opentelemetry/ext/cloud_monitoring/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.8.dev0" \ No newline at end of file +__version__ = "0.8.dev0" From d024eff3ae5cebd69ed1ef8a19eada317593a4a9 Mon Sep 17 00:00:00 2001 From: Andrew Xue Date: Wed, 27 May 2020 18:27:07 -0400 Subject: [PATCH 05/21] add first draft example --- .../examples/metrics.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ext/opentelemetry-ext-cloud-monitoring/examples/metrics.py b/ext/opentelemetry-ext-cloud-monitoring/examples/metrics.py index 6ff6128fa43..6ccf829a886 100644 --- a/ext/opentelemetry-ext-cloud-monitoring/examples/metrics.py +++ b/ext/opentelemetry-ext-cloud-monitoring/examples/metrics.py @@ -47,11 +47,11 @@ def _ghetto_export(): # Perform post-exporting logic based on batcher configuration meter.batcher.finished_collection() - -requests_counter.add(25, staging_labels) -double_counter.add(2.3, other_labels) -_ghetto_export() -time.sleep(3) -requests_counter.add(20, staging_labels) -double_counter.add(2.53, other_labels) -_ghetto_export() +for i in range(20): + requests_counter.add(25, staging_labels) + double_counter.add(2.3, other_labels) + _ghetto_export() + time.sleep(10) + requests_counter.add(20, staging_labels) + double_counter.add(2.53, other_labels) + _ghetto_export() From 1e461940b0cba4987fb64e6ebf3740ac921b42e8 Mon Sep 17 00:00:00 2001 From: Andrew Xue Date: Thu, 28 May 2020 15:54:59 -0400 Subject: [PATCH 06/21] move examples --- docs/examples/cloud_monitoring/README.rst | 35 +++++++++++++++++++ .../examples/cloud_monitoring}/metrics.py | 0 2 files changed, 35 insertions(+) create mode 100644 docs/examples/cloud_monitoring/README.rst rename {ext/opentelemetry-ext-cloud-monitoring/examples => docs/examples/cloud_monitoring}/metrics.py (100%) diff --git a/docs/examples/cloud_monitoring/README.rst b/docs/examples/cloud_monitoring/README.rst new file mode 100644 index 00000000000..94daa1dae85 --- /dev/null +++ b/docs/examples/cloud_monitoring/README.rst @@ -0,0 +1,35 @@ +Cloud Trace Exporter Example +============================ + +These examples show how to use OpenTelemetry to send tracing data to Cloud Trace. + + +Basic Example +------------- + +To use this exporter you first need to: + * A Google Cloud project. You can `create one here. `_ + * Enable Cloud Monitoring API (aka StackDriver Monitoring API) in the project `here. `_ + * Enable `Default Application Credentials. `_ + +* Installation + +.. code-block:: sh + + pip install opentelemetry-api + pip install opentelemetry-sdk + pip install opentelemetry-ext-cloud-monitoring + +* Run example + +.. code-block:: sh + + python basic_metrics.py + +Checking Output +-------------------------- + +After running any of these examples, to see the results + * Go to `Cloud Monitoring overview `_ to see the results. + * In "Find resource type and metric" enter "OpenTelemetry/ Date: Tue, 2 Jun 2020 17:59:33 -0400 Subject: [PATCH 07/21] finish exporter --- docs-requirements.txt | 1 + docs/examples/cloud_monitoring/README.rst | 10 +- .../cloud_monitoring/basic_metrics.py | 30 ++++ docs/examples/cloud_monitoring/metrics.py | 57 -------- .../ext/cloud_monitoring/cloud_monitoring.rst | 7 + .../README.rst | 54 +++++++ .../setup.cfg | 5 +- .../setup.py | 7 +- .../exporter/cloud_monitoring/__init__.py | 136 ++++++++++++++++++ .../exporter}/cloud_monitoring/version.py | 0 .../tests/__init__.py | 0 .../tests/test_cloud_monitoring.py | 20 +++ .../ext/cloud_monitoring/__init__.py | 85 ----------- 13 files changed, 261 insertions(+), 151 deletions(-) create mode 100644 docs/examples/cloud_monitoring/basic_metrics.py delete mode 100644 docs/examples/cloud_monitoring/metrics.py create mode 100644 docs/ext/cloud_monitoring/cloud_monitoring.rst create mode 100644 ext/opentelemetry-exporter-cloud-monitoring/README.rst rename ext/{opentelemetry-ext-cloud-monitoring => opentelemetry-exporter-cloud-monitoring}/setup.cfg (93%) rename ext/{opentelemetry-ext-cloud-monitoring => opentelemetry-exporter-cloud-monitoring}/setup.py (88%) create mode 100644 ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py rename ext/{opentelemetry-ext-cloud-monitoring/src/opentelemetry/ext => opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter}/cloud_monitoring/version.py (100%) create mode 100644 ext/opentelemetry-exporter-cloud-monitoring/tests/__init__.py create mode 100644 ext/opentelemetry-exporter-cloud-monitoring/tests/test_cloud_monitoring.py delete mode 100644 ext/opentelemetry-ext-cloud-monitoring/src/opentelemetry/ext/cloud_monitoring/__init__.py diff --git a/docs-requirements.txt b/docs-requirements.txt index db10f6f9ee4..23a7047e8a3 100644 --- a/docs-requirements.txt +++ b/docs-requirements.txt @@ -22,3 +22,4 @@ wrapt>=1.0.0,<2.0.0 psutil~=5.7.0 boto~=2.0 google-cloud-trace >=0.23.0 +google-cloud-monitoring>=0.36.0 diff --git a/docs/examples/cloud_monitoring/README.rst b/docs/examples/cloud_monitoring/README.rst index 94daa1dae85..425d342eeba 100644 --- a/docs/examples/cloud_monitoring/README.rst +++ b/docs/examples/cloud_monitoring/README.rst @@ -1,7 +1,7 @@ -Cloud Trace Exporter Example -============================ +Cloud Monitoring Exporter Example +================================= -These examples show how to use OpenTelemetry to send tracing data to Cloud Trace. +These examples show how to use OpenTelemetry to send tracing data to Cloud Monitoring. Basic Example @@ -18,7 +18,7 @@ To use this exporter you first need to: pip install opentelemetry-api pip install opentelemetry-sdk - pip install opentelemetry-ext-cloud-monitoring + pip install opentelemetry-exporter-cloud-monitoring * Run example @@ -31,5 +31,5 @@ Checking Output After running any of these examples, to see the results * Go to `Cloud Monitoring overview `_ to see the results. - * In "Find resource type and metric" enter "OpenTelemetry/" * You can filter by labels and change the graphical output here as well \ No newline at end of file diff --git a/docs/examples/cloud_monitoring/basic_metrics.py b/docs/examples/cloud_monitoring/basic_metrics.py new file mode 100644 index 00000000000..cd263e05abe --- /dev/null +++ b/docs/examples/cloud_monitoring/basic_metrics.py @@ -0,0 +1,30 @@ +import time + +from opentelemetry import metrics +from opentelemetry.exporter.cloud_monitoring import ( + CloudMonitoringMetricsExporter, +) +from opentelemetry.sdk.metrics import Counter, MeterProvider +from opentelemetry.sdk.metrics.export import ConsoleMetricsExporter +from opentelemetry.sdk.metrics.export.controller import PushController + +metrics.set_meter_provider(MeterProvider()) +meter = metrics.get_meter(__name__, True) +exporter = ConsoleMetricsExporter() +cloud_exporter = CloudMonitoringMetricsExporter("your-gcloud-project") +controller = PushController(meter=meter, exporter=cloud_exporter, interval=5) + +staging_labels = {"environment": "staging"} + +requests_counter = meter.create_metric( + name="request_counter", + description="number of requests", + unit="1", + value_type=int, + metric_type=Counter, + label_keys=("environment"), +) + +for i in range(20): + requests_counter.add(25, staging_labels) + time.sleep(10) diff --git a/docs/examples/cloud_monitoring/metrics.py b/docs/examples/cloud_monitoring/metrics.py deleted file mode 100644 index 6ccf829a886..00000000000 --- a/docs/examples/cloud_monitoring/metrics.py +++ /dev/null @@ -1,57 +0,0 @@ -import sys -import time - -from opentelemetry import metrics -from opentelemetry.sdk.metrics import Counter, MeterProvider -from opentelemetry.sdk.metrics.export import ConsoleMetricsExporter -from opentelemetry.ext.cloud_monitoring import CloudMonitoringMetricsExporter -from opentelemetry.sdk.metrics.export.controller import PushController -from opentelemetry.context import attach, detach, set_value - -metrics.set_meter_provider(MeterProvider()) -meter = metrics.get_meter(__name__, True) -exporter = ConsoleMetricsExporter() -cloud_exporter = CloudMonitoringMetricsExporter("aaxue-starter") - -exporters = [cloud_exporter] - -staging_labels = {"environment": "staging"} -other_labels = {"label1": "s", "label2": "b"} - -requests_counter = meter.create_metric( - name="num_requests", - description="number of requests", - unit="1", - value_type=int, - metric_type=Counter, - label_keys=("environment",), -) - -double_counter = meter.create_metric( - name="doublly", - description="double", - unit="unit double name", - value_type=float, - metric_type=Counter, - label_keys=("label1", "label1"), -) - - -def _ghetto_export(): - for exporter in exporters: - meter.collect() - token = attach(set_value("suppress_instrumentation", True)) - to_export = meter.batcher.checkpoint_set() - exporter.export(to_export) - detach(token) - # Perform post-exporting logic based on batcher configuration - meter.batcher.finished_collection() - -for i in range(20): - requests_counter.add(25, staging_labels) - double_counter.add(2.3, other_labels) - _ghetto_export() - time.sleep(10) - requests_counter.add(20, staging_labels) - double_counter.add(2.53, other_labels) - _ghetto_export() diff --git a/docs/ext/cloud_monitoring/cloud_monitoring.rst b/docs/ext/cloud_monitoring/cloud_monitoring.rst new file mode 100644 index 00000000000..a3a4f5660ad --- /dev/null +++ b/docs/ext/cloud_monitoring/cloud_monitoring.rst @@ -0,0 +1,7 @@ +OpenTelemetry Cloud Monitoring Exporter +======================================= + +.. automodule:: opentelemetry.exporter.cloud_monitoring + :members: + :undoc-members: + :show-inheritance: \ No newline at end of file diff --git a/ext/opentelemetry-exporter-cloud-monitoring/README.rst b/ext/opentelemetry-exporter-cloud-monitoring/README.rst new file mode 100644 index 00000000000..8b010f94582 --- /dev/null +++ b/ext/opentelemetry-exporter-cloud-monitoring/README.rst @@ -0,0 +1,54 @@ +OpenTelemetry Cloud Trace Exporters +=================================== + +This library provides classes for exporting trace data to Google Cloud Trace. + +Installation +------------ + +:: + + pip install opentelemetry-exporter-cloud-monitoring + +Usage +----- + +.. code:: python + + import time + + from opentelemetry import metrics + from opentelemetry.exporter.cloud_monitoring import ( + CloudMonitoringMetricsExporter, + ) + from opentelemetry.sdk.metrics import Counter, MeterProvider + from opentelemetry.sdk.metrics.export import ConsoleMetricsExporter + from opentelemetry.sdk.metrics.export.controller import PushController + + metrics.set_meter_provider(MeterProvider()) + meter = metrics.get_meter(__name__, True) + exporter = ConsoleMetricsExporter() + cloud_exporter = CloudMonitoringMetricsExporter("your-gcloud-project") + controller = PushController(meter=meter, exporter=cloud_exporter, interval=5) + + staging_labels = {"environment": "staging"} + + requests_counter = meter.create_metric( + name="request_counter", + description="number of requests", + unit="1", + value_type=int, + metric_type=Counter, + label_keys=("environment"), + ) + + for i in range(20): + requests_counter.add(25, staging_labels) + time.sleep(10) + + +References +---------- + +* `Cloud Trace `_ +* `OpenTelemetry Project `_ \ No newline at end of file diff --git a/ext/opentelemetry-ext-cloud-monitoring/setup.cfg b/ext/opentelemetry-exporter-cloud-monitoring/setup.cfg similarity index 93% rename from ext/opentelemetry-ext-cloud-monitoring/setup.cfg rename to ext/opentelemetry-exporter-cloud-monitoring/setup.cfg index e784f373cd0..37665ee48bc 100644 --- a/ext/opentelemetry-ext-cloud-monitoring/setup.cfg +++ b/ext/opentelemetry-exporter-cloud-monitoring/setup.cfg @@ -14,13 +14,13 @@ # limitations under the License. # [metadata] -name = opentelemetry-ext-cloud-monitoring +name = opentelemetry-exporter-cloud-monitoring description = Cloud Monitoring integration for OpenTelemetry long_description = file: README.rst long_description_content_type = text/x-rst author = OpenTelemetry Authors author_email = cncf-opentelemetry-contributors@lists.cncf.io -url = https://github.com/open-telemetry/opentelemetry-python/ext/opentelemetry-ext-cloud-monitoring +url = https://github.com/open-telemetry/opentelemetry-python/ext/opentelemetry-exporter-cloud-monitoring platforms = any license = Apache-2.0 classifiers = @@ -43,7 +43,6 @@ install_requires = opentelemetry-api opentelemetry-sdk google-cloud-monitoring - google-cloud-trace [options.packages.find] where = src \ No newline at end of file diff --git a/ext/opentelemetry-ext-cloud-monitoring/setup.py b/ext/opentelemetry-exporter-cloud-monitoring/setup.py similarity index 88% rename from ext/opentelemetry-ext-cloud-monitoring/setup.py rename to ext/opentelemetry-exporter-cloud-monitoring/setup.py index b4266b82655..0ca88bc330f 100644 --- a/ext/opentelemetry-ext-cloud-monitoring/setup.py +++ b/ext/opentelemetry-exporter-cloud-monitoring/setup.py @@ -17,7 +17,12 @@ BASE_DIR = os.path.dirname(__file__) VERSION_FILENAME = os.path.join( - BASE_DIR, "src", "opentelemetry", "ext", "cloud_monitoring", "version.py" + BASE_DIR, + "src", + "opentelemetry", + "exporter", + "cloud_monitoring", + "version.py", ) PACKAGE_INFO = {} with open(VERSION_FILENAME) as f: diff --git a/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py b/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py new file mode 100644 index 00000000000..6e169dc406a --- /dev/null +++ b/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py @@ -0,0 +1,136 @@ +import logging +from typing import Sequence + +import google.auth +from google.api.label_pb2 import LabelDescriptor +from google.api.metric_pb2 import MetricDescriptor +from google.cloud.monitoring_v3 import MetricServiceClient +from google.cloud.monitoring_v3.proto.metric_pb2 import TimeSeries + +from opentelemetry.sdk.metrics.export import ( + MetricRecord, + MetricsExporter, + MetricsExportResult, +) +from opentelemetry.sdk.metrics.export.aggregate import CounterAggregator + +logger = logging.getLogger(__name__) +MAX_BATCH_WRITE = 200 + + +# pylint is unable to resolve members of protobuf objects +# pylint: disable=no-member +class CloudMonitoringMetricsExporter(MetricsExporter): + """ Implementation of Metrics Exporter to Google Cloud Monitoring""" + + def __init__(self, project_id=None, client=None): + self.client = client or MetricServiceClient() + if not project_id: + _, self.project_id = google.auth.default() + else: + self.project_id = project_id + self.project_name = self.client.project_path(project_id) + self._metric_descriptors = {} + + def _add_resource_info(self, series: TimeSeries): + """ Add Google resource specific information (e.g. instance id, region) + + :param series: ProtoBuf TimeSeries + :return: + """ + # TODO: Leverage this better + + def _batch_write(self, series: TimeSeries): + """ Cloud Monitoring allows writing up to 200 time series at once + + :param series: ProtoBuf TimeSeries + :return: + """ + write_ind = 0 + while write_ind < len(series): + self.client.create_time_series( + self.project_name, + series[write_ind : write_ind + MAX_BATCH_WRITE], + ) + write_ind += MAX_BATCH_WRITE + + def _get_metric_descriptor(self, record: MetricRecord) -> MetricDescriptor: + """ We can map Metric to MetricDescriptor using Metric.name or + MetricDescriptor.type. We create the MetricDescriptor if it doesn't + exist already and cache it. Note that recreating MetricDescriptors is + a no-op if it already exists. + + :param record: + :return: + """ + descriptor_type = "custom.googleapis.com/OpenTelemetry/{}".format( + record.metric.name + ) + if descriptor_type in self._metric_descriptors: + return self._metric_descriptors[descriptor_type] + descriptor = { + "name": "display_name", + "type": descriptor_type, + "display_name": record.metric.name, + "description": record.metric.description, + "labels": [], + } + for key, value in record.labels: + if isinstance(value, str): + descriptor["labels"].append( + LabelDescriptor(key=key, value_type="STRING") + ) + elif isinstance(value, int): + descriptor["labels"].append( + LabelDescriptor(key=key, value_type="INT64") + ) + elif isinstance(value, bool): + descriptor["labels"].append( + LabelDescriptor(key=key, value_type="BOOL") + ) + if isinstance(record.aggregator, CounterAggregator): + descriptor["metric_kind"] = MetricDescriptor.MetricKind.GAUGE + else: + logger.warning( + "Unsupported aggregation type %s, ignoring it", + type(record.aggregator).__name__, + ) + return None + if record.metric.value_type == int: + descriptor["value_type"] = MetricDescriptor.ValueType.INT64 + elif record.metric.value_type == float: + descriptor["value_type"] = MetricDescriptor.ValueType.DOUBLE + descriptor = self.client.create_metric_descriptor( + self.project_name, MetricDescriptor(**descriptor) + ) + self._metric_descriptors[descriptor_type] = descriptor + return descriptor + + def export( + self, metric_records: Sequence[MetricRecord] + ) -> "MetricsExportResult": + all_series = [] + for record in metric_records: + metric_descriptor = self._get_metric_descriptor(record) + if not metric_descriptor: + continue + + series = TimeSeries() + self._add_resource_info(series) + series.metric.type = metric_descriptor.type + for key, value in record.labels: + series.metric.labels[key] = value + + point = series.points.add() + if record.metric.value_type == int: + point.value.int64_value = record.aggregator.checkpoint + elif record.metric.value_type == float: + point.value.double_value = record.aggregator.checkpoint + seconds, nanos = divmod( + record.aggregator.last_update_timestamp, 1e9 + ) + point.interval.end_time.seconds = int(seconds) + point.interval.end_time.nanos = int(nanos) + all_series.append(series) + self._batch_write(all_series) + return MetricsExportResult.SUCCESS diff --git a/ext/opentelemetry-ext-cloud-monitoring/src/opentelemetry/ext/cloud_monitoring/version.py b/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/version.py similarity index 100% rename from ext/opentelemetry-ext-cloud-monitoring/src/opentelemetry/ext/cloud_monitoring/version.py rename to ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/version.py diff --git a/ext/opentelemetry-exporter-cloud-monitoring/tests/__init__.py b/ext/opentelemetry-exporter-cloud-monitoring/tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ext/opentelemetry-exporter-cloud-monitoring/tests/test_cloud_monitoring.py b/ext/opentelemetry-exporter-cloud-monitoring/tests/test_cloud_monitoring.py new file mode 100644 index 00000000000..162e45016c7 --- /dev/null +++ b/ext/opentelemetry-exporter-cloud-monitoring/tests/test_cloud_monitoring.py @@ -0,0 +1,20 @@ +# Copyright OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest + + +class TestCloudTraceSpanExporter(unittest.TestCase): + def test_case(self): + pass diff --git a/ext/opentelemetry-ext-cloud-monitoring/src/opentelemetry/ext/cloud_monitoring/__init__.py b/ext/opentelemetry-ext-cloud-monitoring/src/opentelemetry/ext/cloud_monitoring/__init__.py deleted file mode 100644 index 846fa3fbeb9..00000000000 --- a/ext/opentelemetry-ext-cloud-monitoring/src/opentelemetry/ext/cloud_monitoring/__init__.py +++ /dev/null @@ -1,85 +0,0 @@ -from typing import Sequence - -from google.cloud.monitoring_v3 import MetricServiceClient -from google.cloud.monitoring_v3.proto.metric_pb2 import TimeSeries -from opentelemetry.sdk.metrics.export import ( - MetricsExporter, - MetricRecord, - MetricsExportResult, -) - -MAX_BATCH_WRITE = 200 - - -class CloudMonitoringMetricsExporter(MetricsExporter): - """ - Implementation of Metrics Exporter that sends metrics to Google Cloud - Monitoring - """ - - def __init__(self, project_id, client=None): - self.client = client or MetricServiceClient() - self.project_id = project_id - self.project_name = self.client.project_path(project_id) - self._last_updated = {} - - def _add_resource_info(self, series): - """ Add Google resource specific information (e.g. instance id, region) - - :param series: ProtoBuf TimeSeries - :return: - """ - series.resource.type = "gce_instance" - series.resource.labels["instance_id"] = "1234567890123456789" - series.resource.labels["zone"] = "us-central1-f" - - def _batch_write(self, series): - """ Cloud Monitoring allows writing up to 200 time series at once - - :param series: ProtoBuf TimeSeries - :return: - """ - write_ind = 0 - while write_ind < len(series): - self.client.create_time_series( - self.project_name, - series[write_ind : write_ind + MAX_BATCH_WRITE], - ) - write_ind += MAX_BATCH_WRITE - - def export( - self, metric_records: Sequence[MetricRecord] - ) -> "MetricsExportResult": - all_series = [] - for record in metric_records: - recorded_metric = record.metric - aggregated_metric = record.aggregator - series_name = "custom.googleapis.com/OpenTelemetry/{}".format( - recorded_metric.name - ) - series = TimeSeries() - self._add_resource_info(series) - series.metric.type = series_name - for k, v in record.labels: - series.metric.labels[k] = v - point = series.points.add() - if recorded_metric.value_type == int: - point.value.int64_value = aggregated_metric.checkpoint - elif recorded_metric.value_type == float: - point.value.double_value = aggregated_metric.checkpoint - seconds, nanos = divmod( - aggregated_metric.last_update_timestamp, 1e9 - ) - - # Cloud Monitoring allows every timeseries to be updated one per - # minute at most - if seconds - self._last_updated.get(series_name, 0) < 60: - continue - self._last_updated[series.metric.type] = seconds - point.interval.end_time.seconds = int(seconds) - point.interval.end_time.nanos = int(nanos) - all_series.append(series) - print("will write {} to {}".format(point, series.metric.type)) - self._batch_write(all_series) - print("wrote to cloud monitorint") - return MetricsExportResult.SUCCESS From c58718f548b362758b7707739a124746282fcc1a Mon Sep 17 00:00:00 2001 From: Andrew Xue Date: Tue, 2 Jun 2020 18:05:15 -0400 Subject: [PATCH 08/21] f --- ext/opentelemetry-exporter-cloud-monitoring/README.rst | 2 +- .../tests/test_cloud_monitoring.py | 2 +- opentelemetry-sdk/CHANGELOG.md | 3 --- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/ext/opentelemetry-exporter-cloud-monitoring/README.rst b/ext/opentelemetry-exporter-cloud-monitoring/README.rst index 8b010f94582..61a638eb6a1 100644 --- a/ext/opentelemetry-exporter-cloud-monitoring/README.rst +++ b/ext/opentelemetry-exporter-cloud-monitoring/README.rst @@ -50,5 +50,5 @@ Usage References ---------- -* `Cloud Trace `_ +* `Cloud Monitoring `_ * `OpenTelemetry Project `_ \ No newline at end of file diff --git a/ext/opentelemetry-exporter-cloud-monitoring/tests/test_cloud_monitoring.py b/ext/opentelemetry-exporter-cloud-monitoring/tests/test_cloud_monitoring.py index 162e45016c7..4c3c0757abb 100644 --- a/ext/opentelemetry-exporter-cloud-monitoring/tests/test_cloud_monitoring.py +++ b/ext/opentelemetry-exporter-cloud-monitoring/tests/test_cloud_monitoring.py @@ -15,6 +15,6 @@ import unittest -class TestCloudTraceSpanExporter(unittest.TestCase): +class TestCloudMonitoringMetricsExporter(unittest.TestCase): def test_case(self): pass diff --git a/opentelemetry-sdk/CHANGELOG.md b/opentelemetry-sdk/CHANGELOG.md index b111befddc3..0e982b6bc14 100644 --- a/opentelemetry-sdk/CHANGELOG.md +++ b/opentelemetry-sdk/CHANGELOG.md @@ -23,9 +23,6 @@ Released 2020-05-27 ([#729](https://github.com/open-telemetry/opentelemetry-python/pull/729)) - deep copy empty attributes ([#714](https://github.com/open-telemetry/opentelemetry-python/pull/714)) -- add cloud monitoring exporter - ([#739](https://github.com/open-telemetry/opentelemetry-python/pull/739)) - ## 0.7b1 From 758bcf26360c136141b50b5f4f2cec54be5f0d61 Mon Sep 17 00:00:00 2001 From: Andrew Xue Date: Tue, 2 Jun 2020 18:08:26 -0400 Subject: [PATCH 09/21] f --- ext/opentelemetry-exporter-cloud-monitoring/README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/opentelemetry-exporter-cloud-monitoring/README.rst b/ext/opentelemetry-exporter-cloud-monitoring/README.rst index 61a638eb6a1..9ca0e72cc81 100644 --- a/ext/opentelemetry-exporter-cloud-monitoring/README.rst +++ b/ext/opentelemetry-exporter-cloud-monitoring/README.rst @@ -1,7 +1,7 @@ -OpenTelemetry Cloud Trace Exporters -=================================== +OpenTelemetry Cloud Monitoring Exporters +======================================== -This library provides classes for exporting trace data to Google Cloud Trace. +This library provides classes for exporting metrics data to Google Cloud Monitoring. Installation ------------ From b2d2e3ba52004dc0d4587d5dbbca68b7efb4eddf Mon Sep 17 00:00:00 2001 From: Andrew Xue Date: Thu, 4 Jun 2020 14:44:27 -0400 Subject: [PATCH 10/21] address comments --- docs/examples/cloud_monitoring/README.rst | 18 ++++----- .../cloud_monitoring/basic_metrics.py | 11 +++--- .../README.rst | 38 +------------------ .../exporter/cloud_monitoring/__init__.py | 16 +++++--- .../tests/test_boto_instrumentation.py | 2 +- 5 files changed, 28 insertions(+), 57 deletions(-) diff --git a/docs/examples/cloud_monitoring/README.rst b/docs/examples/cloud_monitoring/README.rst index 425d342eeba..645f7a98670 100644 --- a/docs/examples/cloud_monitoring/README.rst +++ b/docs/examples/cloud_monitoring/README.rst @@ -1,16 +1,16 @@ Cloud Monitoring Exporter Example ================================= -These examples show how to use OpenTelemetry to send tracing data to Cloud Monitoring. +These examples show how to use OpenTelemetry to send metrics data to Cloud Monitoring. Basic Example ------------- To use this exporter you first need to: - * A Google Cloud project. You can `create one here. `_ - * Enable Cloud Monitoring API (aka StackDriver Monitoring API) in the project `here. `_ - * Enable `Default Application Credentials. `_ + * A Google Cloud project. You can `create one here `_. + * Enable the Cloud Monitoring API (aka Stackdriver Monitoring API) in the project `here `_. + * Enable `Default Application Credentials `_. * Installation @@ -26,10 +26,10 @@ To use this exporter you first need to: python basic_metrics.py -Checking Output +Viewing Output -------------------------- -After running any of these examples, to see the results - * Go to `Cloud Monitoring overview `_ to see the results. - * In "Find resource type and metric" enter "OpenTelemetry/" - * You can filter by labels and change the graphical output here as well \ No newline at end of file +After running the example: + * Go to `Cloud Monitoring Metrics Explorer page `_. + * In "Find resource type and metric" enter "OpenTelemetry/request_counter". + * You can filter by labels and change the graphical output here as well. \ No newline at end of file diff --git a/docs/examples/cloud_monitoring/basic_metrics.py b/docs/examples/cloud_monitoring/basic_metrics.py index cd263e05abe..6a3b929f9be 100644 --- a/docs/examples/cloud_monitoring/basic_metrics.py +++ b/docs/examples/cloud_monitoring/basic_metrics.py @@ -5,16 +5,15 @@ CloudMonitoringMetricsExporter, ) from opentelemetry.sdk.metrics import Counter, MeterProvider -from opentelemetry.sdk.metrics.export import ConsoleMetricsExporter from opentelemetry.sdk.metrics.export.controller import PushController metrics.set_meter_provider(MeterProvider()) meter = metrics.get_meter(__name__, True) -exporter = ConsoleMetricsExporter() -cloud_exporter = CloudMonitoringMetricsExporter("your-gcloud-project") -controller = PushController(meter=meter, exporter=cloud_exporter, interval=5) -staging_labels = {"environment": "staging"} +# Gather and export metrics every 5 seconds +controller = PushController( + meter=meter, exporter=CloudMonitoringMetricsExporter(), interval=5 +) requests_counter = meter.create_metric( name="request_counter", @@ -25,6 +24,8 @@ label_keys=("environment"), ) +staging_labels = {"environment": "staging"} + for i in range(20): requests_counter.add(25, staging_labels) time.sleep(10) diff --git a/ext/opentelemetry-exporter-cloud-monitoring/README.rst b/ext/opentelemetry-exporter-cloud-monitoring/README.rst index 9ca0e72cc81..f1fd52528c9 100644 --- a/ext/opentelemetry-exporter-cloud-monitoring/README.rst +++ b/ext/opentelemetry-exporter-cloud-monitoring/README.rst @@ -10,45 +10,9 @@ Installation pip install opentelemetry-exporter-cloud-monitoring -Usage ------ - -.. code:: python - - import time - - from opentelemetry import metrics - from opentelemetry.exporter.cloud_monitoring import ( - CloudMonitoringMetricsExporter, - ) - from opentelemetry.sdk.metrics import Counter, MeterProvider - from opentelemetry.sdk.metrics.export import ConsoleMetricsExporter - from opentelemetry.sdk.metrics.export.controller import PushController - - metrics.set_meter_provider(MeterProvider()) - meter = metrics.get_meter(__name__, True) - exporter = ConsoleMetricsExporter() - cloud_exporter = CloudMonitoringMetricsExporter("your-gcloud-project") - controller = PushController(meter=meter, exporter=cloud_exporter, interval=5) - - staging_labels = {"environment": "staging"} - - requests_counter = meter.create_metric( - name="request_counter", - description="number of requests", - unit="1", - value_type=int, - metric_type=Counter, - label_keys=("environment"), - ) - - for i in range(20): - requests_counter.add(25, staging_labels) - time.sleep(10) - - References ---------- +* `OpenTelemetry Cloud Monitoring Exporter `_ * `Cloud Monitoring `_ * `OpenTelemetry Project `_ \ No newline at end of file diff --git a/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py b/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py index 6e169dc406a..e3865745f6f 100644 --- a/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py +++ b/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py @@ -1,5 +1,5 @@ import logging -from typing import Sequence +from typing import Optional, Sequence import google.auth from google.api.label_pb2 import LabelDescriptor @@ -29,10 +29,10 @@ def __init__(self, project_id=None, client=None): _, self.project_id = google.auth.default() else: self.project_id = project_id - self.project_name = self.client.project_path(project_id) + self.project_name = self.client.project_path(self.project_id) self._metric_descriptors = {} - def _add_resource_info(self, series: TimeSeries): + def _add_resource_info(self, series: TimeSeries) -> None: """ Add Google resource specific information (e.g. instance id, region) :param series: ProtoBuf TimeSeries @@ -40,7 +40,7 @@ def _add_resource_info(self, series: TimeSeries): """ # TODO: Leverage this better - def _batch_write(self, series: TimeSeries): + def _batch_write(self, series: TimeSeries) -> None: """ Cloud Monitoring allows writing up to 200 time series at once :param series: ProtoBuf TimeSeries @@ -54,7 +54,9 @@ def _batch_write(self, series: TimeSeries): ) write_ind += MAX_BATCH_WRITE - def _get_metric_descriptor(self, record: MetricRecord) -> MetricDescriptor: + def _get_metric_descriptor( + self, record: MetricRecord + ) -> Optional[MetricDescriptor]: """ We can map Metric to MetricDescriptor using Metric.name or MetricDescriptor.type. We create the MetricDescriptor if it doesn't exist already and cache it. Note that recreating MetricDescriptors is @@ -88,6 +90,10 @@ def _get_metric_descriptor(self, record: MetricRecord) -> MetricDescriptor: descriptor["labels"].append( LabelDescriptor(key=key, value_type="BOOL") ) + else: + logger.warning( + "Label value %s is not a string, bool or integer", value + ) if isinstance(record.aggregator, CounterAggregator): descriptor["metric_kind"] = MetricDescriptor.MetricKind.GAUGE else: diff --git a/ext/opentelemetry-ext-boto/tests/test_boto_instrumentation.py b/ext/opentelemetry-ext-boto/tests/test_boto_instrumentation.py index 492fac5a883..a629b108705 100644 --- a/ext/opentelemetry-ext-boto/tests/test_boto_instrumentation.py +++ b/ext/opentelemetry-ext-boto/tests/test_boto_instrumentation.py @@ -19,13 +19,13 @@ import boto.elasticache import boto.s3 import boto.sts - from moto import ( # pylint: disable=import-error mock_ec2_deprecated, mock_lambda_deprecated, mock_s3_deprecated, mock_sts_deprecated, ) + from opentelemetry.ext.boto import BotoInstrumentor from opentelemetry.test.test_base import TestBase From 8b5e343f6f96390f063fce6190d47e1c982bb741 Mon Sep 17 00:00:00 2001 From: Andrew Xue Date: Thu, 4 Jun 2020 21:52:19 -0400 Subject: [PATCH 11/21] add tests --- .../exporter/cloud_monitoring/__init__.py | 21 +- .../tests/test_cloud_monitoring.py | 268 +++++++++++++++++- 2 files changed, 281 insertions(+), 8 deletions(-) diff --git a/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py b/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py index e3865745f6f..14f3b66a9a8 100644 --- a/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py +++ b/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py @@ -16,6 +16,7 @@ logger = logging.getLogger(__name__) MAX_BATCH_WRITE = 200 +WRITE_INTERVAL = 10 # pylint is unable to resolve members of protobuf objects @@ -31,6 +32,7 @@ def __init__(self, project_id=None, client=None): self.project_id = project_id self.project_name = self.client.project_path(self.project_id) self._metric_descriptors = {} + self._last_updated = {} def _add_resource_info(self, series: TimeSeries) -> None: """ Add Google resource specific information (e.g. instance id, region) @@ -71,7 +73,7 @@ def _get_metric_descriptor( if descriptor_type in self._metric_descriptors: return self._metric_descriptors[descriptor_type] descriptor = { - "name": "display_name", + "name": None, "type": descriptor_type, "display_name": record.metric.name, "description": record.metric.description, @@ -82,14 +84,14 @@ def _get_metric_descriptor( descriptor["labels"].append( LabelDescriptor(key=key, value_type="STRING") ) - elif isinstance(value, int): - descriptor["labels"].append( - LabelDescriptor(key=key, value_type="INT64") - ) elif isinstance(value, bool): descriptor["labels"].append( LabelDescriptor(key=key, value_type="BOOL") ) + elif isinstance(value, int): + descriptor["labels"].append( + LabelDescriptor(key=key, value_type="INT64") + ) else: logger.warning( "Label value %s is not a string, bool or integer", value @@ -125,7 +127,7 @@ def export( self._add_resource_info(series) series.metric.type = metric_descriptor.type for key, value in record.labels: - series.metric.labels[key] = value + series.metric.labels[key] = str(value) point = series.points.add() if record.metric.value_type == int: @@ -135,6 +137,13 @@ def export( seconds, nanos = divmod( record.aggregator.last_update_timestamp, 1e9 ) + + # Cloud Monitoring API allows, for any combination of labels and metric name, one update per WRITE_INTERVAL seconds + updated_key = (metric_descriptor.type, record.labels) + last_updated_seconds = self._last_updated.get(updated_key, 0) + if seconds <= last_updated_seconds + WRITE_INTERVAL: + continue + self._last_updated[updated_key] = seconds point.interval.end_time.seconds = int(seconds) point.interval.end_time.nanos = int(nanos) all_series.append(series) diff --git a/ext/opentelemetry-exporter-cloud-monitoring/tests/test_cloud_monitoring.py b/ext/opentelemetry-exporter-cloud-monitoring/tests/test_cloud_monitoring.py index 4c3c0757abb..ebf696e08d7 100644 --- a/ext/opentelemetry-exporter-cloud-monitoring/tests/test_cloud_monitoring.py +++ b/ext/opentelemetry-exporter-cloud-monitoring/tests/test_cloud_monitoring.py @@ -13,8 +13,272 @@ # limitations under the License. import unittest +from unittest import mock + +from google.api.label_pb2 import LabelDescriptor +from google.api.metric_pb2 import MetricDescriptor +from google.cloud.monitoring_v3.proto.metric_pb2 import TimeSeries + +from opentelemetry.exporter.cloud_monitoring import ( + MAX_BATCH_WRITE, + WRITE_INTERVAL, + CloudMonitoringMetricsExporter, +) +from opentelemetry.sdk.metrics.export import MetricRecord +from opentelemetry.sdk.metrics.export.aggregate import CounterAggregator + + +class UnsupportedAggregator: + pass + + +class MockMetric: + def __init__(self, name="name", description="description", value_type=int): + self.name = name + self.description = description + self.value_type = value_type + + +# pylint: disable=protected-access +# pylint can't deal with ProtoBuf object members +# pylint: disable=no-member class TestCloudMonitoringMetricsExporter(unittest.TestCase): - def test_case(self): - pass + def setUp(self): + self.client_patcher = mock.patch( + "opentelemetry.exporter.cloud_monitoring.MetricServiceClient" + ) + self.client_patcher.start() + self.project_id = "PROJECT" + self.project_name = "PROJECT_NAME" + + def tearDown(self): + self.client_patcher.stop() + + def test_constructor_default(self): + exporter = CloudMonitoringMetricsExporter(self.project_id) + self.assertEqual(exporter.project_id, self.project_id) + + def test_constructor_explicit(self): + client = mock.Mock() + exporter = CloudMonitoringMetricsExporter( + self.project_id, client=client + ) + + self.assertIs(exporter.client, client) + self.assertEqual(exporter.project_id, self.project_id) + + def test_batch_write(self): + client = mock.Mock() + exporter = CloudMonitoringMetricsExporter(client=client) + exporter.project_name = self.project_name + exporter._batch_write(range(2 * MAX_BATCH_WRITE + 1)) + client.create_time_series.assert_has_calls( + [ + mock.call(self.project_name, range(MAX_BATCH_WRITE)), + mock.call( + self.project_name, + range(MAX_BATCH_WRITE, 2 * MAX_BATCH_WRITE), + ), + mock.call( + self.project_name, + range(2 * MAX_BATCH_WRITE, 2 * MAX_BATCH_WRITE + 1), + ), + ] + ) + + exporter._batch_write(range(MAX_BATCH_WRITE)) + client.create_time_series.assert_has_calls( + [mock.call(self.project_name, range(MAX_BATCH_WRITE))] + ) + + exporter._batch_write(range(MAX_BATCH_WRITE - 1)) + client.create_time_series.assert_has_calls( + [mock.call(self.project_name, range(MAX_BATCH_WRITE - 1))] + ) + + def test_get_metric_descriptor(self): + client = mock.Mock() + exporter = CloudMonitoringMetricsExporter(client=client) + exporter.project_name = self.project_name + + self.assertIsNone( + exporter._get_metric_descriptor( + MetricRecord(UnsupportedAggregator(), (), MockMetric()) + ) + ) + + record = MetricRecord( + CounterAggregator(), (("label1", "value1"),), MockMetric() + ) + metric_descriptor = exporter._get_metric_descriptor(record) + client.create_metric_descriptor.assert_called_with( + self.project_name, + MetricDescriptor( + **{ + "name": None, + "type": "custom.googleapis.com/OpenTelemetry/name", + "display_name": "name", + "description": "description", + "labels": [ + LabelDescriptor(key="label1", value_type="STRING") + ], + "metric_kind": "GAUGE", + "value_type": "INT64", + } + ), + ) + + # Getting a cached metric descriptor shouldn't use another call + cached_metric_descriptor = exporter._get_metric_descriptor(record) + client.create_metric_descriptor.assert_called_once() + self.assertEqual(metric_descriptor, cached_metric_descriptor) + + # Drop labels with values that aren't string, int or bool + exporter._get_metric_descriptor( + MetricRecord( + CounterAggregator(), + ( + ("label1", "value1"), + ("label2", dict()), + ("label3", 3), + ("label4", False), + ), + MockMetric(name="name2", value_type=float), + ) + ) + client.create_metric_descriptor.assert_called_with( + self.project_name, + MetricDescriptor( + **{ + "name": None, + "type": "custom.googleapis.com/OpenTelemetry/name2", + "display_name": "name2", + "description": "description", + "labels": [ + LabelDescriptor(key="label1", value_type="STRING"), + LabelDescriptor(key="label3", value_type="INT64"), + LabelDescriptor(key="label4", value_type="BOOL"), + ], + "metric_kind": "GAUGE", + "value_type": "DOUBLE", + } + ), + ) + + def test_export(self): + client = mock.Mock() + exporter = CloudMonitoringMetricsExporter(client=client) + exporter.project_name = self.project_name + + exporter.export( + [ + MetricRecord( + UnsupportedAggregator(), + (("label1", "value1"),), + MockMetric(), + ) + ] + ) + client.create_time_series.assert_not_called() + + client.create_metric_descriptor.return_value = MetricDescriptor( + **{ + "name": None, + "type": "custom.googleapis.com/OpenTelemetry/name", + "display_name": "name", + "description": "description", + "labels": [ + LabelDescriptor(key="label1", value_type="STRING"), + LabelDescriptor(key="label2", value_type="INT64"), + ], + "metric_kind": "GAUGE", + "value_type": "DOUBLE", + } + ) + + counter_one = CounterAggregator() + counter_one.checkpoint = 1 + counter_one.last_update_timestamp = (WRITE_INTERVAL + 1) * 1e9 + exporter.export( + [ + MetricRecord( + counter_one, + (("label1", "value1"), ("label2", 1),), + MockMetric(), + ), + MetricRecord( + counter_one, + (("label1", "value2"), ("label2", 2),), + MockMetric(), + ), + ] + ) + series1 = TimeSeries() + series1.metric.type = "custom.googleapis.com/OpenTelemetry/name" + series1.metric.labels["label1"] = "value1" + series1.metric.labels["label2"] = "1" + point = series1.points.add() + point.value.int64_value = 1 + point.interval.end_time.seconds = WRITE_INTERVAL + 1 + point.interval.end_time.nanos = 0 + + series2 = TimeSeries() + series2.metric.type = "custom.googleapis.com/OpenTelemetry/name" + series2.metric.labels["label1"] = "value2" + series2.metric.labels["label2"] = "2" + point = series2.points.add() + point.value.int64_value = 1 + point.interval.end_time.seconds = WRITE_INTERVAL + 1 + point.interval.end_time.nanos = 0 + client.create_time_series.assert_has_calls( + [mock.call(self.project_name, [series1, series2])] + ) + + # Attempting to export too soon after another export with the exact same labels leads to it + # being dropped + counter_two = CounterAggregator() + counter_two.checkpoint = 1 + counter_two.last_update_timestamp = (WRITE_INTERVAL + 2) * 1e9 + exporter.export( + [ + MetricRecord( + counter_two, + (("label1", "value1"), ("label2", 1),), + MockMetric(), + ), + MetricRecord( + counter_two, + (("label1", "value2"), ("label2", 2),), + MockMetric(), + ), + ] + ) + self.assertEqual(client.create_time_series.call_count, 1) + + # But exporting with different labels is fine + counter_two.checkpoint = 2 + exporter.export( + [ + MetricRecord( + counter_two, + (("label1", "changed_label"), ("label2", 2),), + MockMetric(), + ), + ] + ) + series3 = TimeSeries() + series3.metric.type = "custom.googleapis.com/OpenTelemetry/name" + series3.metric.labels["label1"] = "changed_label" + series3.metric.labels["label2"] = "2" + point = series3.points.add() + point.value.int64_value = 2 + point.interval.end_time.seconds = WRITE_INTERVAL + 2 + point.interval.end_time.nanos = 0 + client.create_time_series.assert_has_calls( + [ + mock.call(self.project_name, [series1, series2]), + mock.call(self.project_name, [series3]), + ] + ) From f98f31aa252fdb9016a56c2e94d6b64ac6401bb9 Mon Sep 17 00:00:00 2001 From: Andrew Xue Date: Thu, 4 Jun 2020 22:07:12 -0400 Subject: [PATCH 12/21] f --- .../src/opentelemetry/exporter/cloud_monitoring/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/version.py b/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/version.py index ec792e9af10..f83f20e7bac 100644 --- a/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/version.py +++ b/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.8.dev0" +__version__ = "0.9.dev0" From 2821882b485f9aac23054af5fe97f640b0c09249 Mon Sep 17 00:00:00 2001 From: Andrew Xue Date: Fri, 5 Jun 2020 15:34:36 -0400 Subject: [PATCH 13/21] revert --- ext/opentelemetry-ext-boto/tests/test_boto_instrumentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/opentelemetry-ext-boto/tests/test_boto_instrumentation.py b/ext/opentelemetry-ext-boto/tests/test_boto_instrumentation.py index a629b108705..492fac5a883 100644 --- a/ext/opentelemetry-ext-boto/tests/test_boto_instrumentation.py +++ b/ext/opentelemetry-ext-boto/tests/test_boto_instrumentation.py @@ -19,13 +19,13 @@ import boto.elasticache import boto.s3 import boto.sts + from moto import ( # pylint: disable=import-error mock_ec2_deprecated, mock_lambda_deprecated, mock_s3_deprecated, mock_sts_deprecated, ) - from opentelemetry.ext.boto import BotoInstrumentor from opentelemetry.test.test_base import TestBase From 6ebd1e5ea946aa3758fc5ed0e25b067871bf33a3 Mon Sep 17 00:00:00 2001 From: Andrew Xue Date: Fri, 5 Jun 2020 21:59:10 -0400 Subject: [PATCH 14/21] address comments --- .../exporter/cloud_monitoring/__init__.py | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py b/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py index 14f3b66a9a8..d80318d77b8 100644 --- a/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py +++ b/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py @@ -108,9 +108,19 @@ def _get_metric_descriptor( descriptor["value_type"] = MetricDescriptor.ValueType.INT64 elif record.metric.value_type == float: descriptor["value_type"] = MetricDescriptor.ValueType.DOUBLE - descriptor = self.client.create_metric_descriptor( - self.project_name, MetricDescriptor(**descriptor) - ) + proto_descriptor = MetricDescriptor(**descriptor) + try: + descriptor = self.client.create_metric_descriptor( + self.project_name, proto_descriptor + ) + # pylint: disable=broad-except + except Exception as ex: + logger.error( + "Failed to create metric descriptor %s", + proto_descriptor, + exc_info=ex, + ) + return None self._metric_descriptors[descriptor_type] = descriptor return descriptor @@ -147,5 +157,12 @@ def export( point.interval.end_time.seconds = int(seconds) point.interval.end_time.nanos = int(nanos) all_series.append(series) - self._batch_write(all_series) + try: + self._batch_write(all_series) + # pylint: disable=broad-except + except Exception as ex: + logger.error( + "Error while writing to Cloud Monitoring", exc_info=ex + ) + return MetricsExportResult.FAILURE return MetricsExportResult.SUCCESS From 00524afbf54a659883c75ecce616337682bc241c Mon Sep 17 00:00:00 2001 From: Andrew Xue Date: Fri, 5 Jun 2020 21:47:50 -0400 Subject: [PATCH 15/21] Update docs/examples/cloud_monitoring/README.rst Co-authored-by: Chris Kleinknecht --- docs/examples/cloud_monitoring/README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/examples/cloud_monitoring/README.rst b/docs/examples/cloud_monitoring/README.rst index 645f7a98670..f8332a46e17 100644 --- a/docs/examples/cloud_monitoring/README.rst +++ b/docs/examples/cloud_monitoring/README.rst @@ -30,6 +30,6 @@ Viewing Output -------------------------- After running the example: - * Go to `Cloud Monitoring Metrics Explorer page `_. + * Go to the `Cloud Monitoring Metrics Explorer page `_. * In "Find resource type and metric" enter "OpenTelemetry/request_counter". - * You can filter by labels and change the graphical output here as well. \ No newline at end of file + * You can filter by labels and change the graphical output here as well. From 9e3c596adad3de1e7addd5f1073bd6afe44be387 Mon Sep 17 00:00:00 2001 From: Andrew Xue Date: Fri, 5 Jun 2020 21:47:57 -0400 Subject: [PATCH 16/21] Update docs/examples/cloud_monitoring/README.rst Co-authored-by: Chris Kleinknecht --- docs/examples/cloud_monitoring/README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/examples/cloud_monitoring/README.rst b/docs/examples/cloud_monitoring/README.rst index f8332a46e17..5ad54217945 100644 --- a/docs/examples/cloud_monitoring/README.rst +++ b/docs/examples/cloud_monitoring/README.rst @@ -8,7 +8,7 @@ Basic Example ------------- To use this exporter you first need to: - * A Google Cloud project. You can `create one here `_. + * `Create a Google Cloud project `_. * Enable the Cloud Monitoring API (aka Stackdriver Monitoring API) in the project `here `_. * Enable `Default Application Credentials `_. From 320e6db5b2993a2c73381da0d40d567a76ca0312 Mon Sep 17 00:00:00 2001 From: Andrew Xue Date: Fri, 5 Jun 2020 21:48:05 -0400 Subject: [PATCH 17/21] Update docs/examples/cloud_monitoring/basic_metrics.py Co-authored-by: Chris Kleinknecht --- docs/examples/cloud_monitoring/basic_metrics.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/examples/cloud_monitoring/basic_metrics.py b/docs/examples/cloud_monitoring/basic_metrics.py index 6a3b929f9be..cef31928771 100644 --- a/docs/examples/cloud_monitoring/basic_metrics.py +++ b/docs/examples/cloud_monitoring/basic_metrics.py @@ -1,3 +1,18 @@ +#!/usr/bin/env python3 +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import time from opentelemetry import metrics From cd64112b6fba3d5fcf956a1f8dc7b0a3d58b94dc Mon Sep 17 00:00:00 2001 From: Andrew Xue Date: Fri, 5 Jun 2020 21:48:15 -0400 Subject: [PATCH 18/21] Update docs/examples/cloud_monitoring/basic_metrics.py Co-authored-by: Chris Kleinknecht --- docs/examples/cloud_monitoring/basic_metrics.py | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/examples/cloud_monitoring/basic_metrics.py b/docs/examples/cloud_monitoring/basic_metrics.py index cef31928771..e0ceb420b62 100644 --- a/docs/examples/cloud_monitoring/basic_metrics.py +++ b/docs/examples/cloud_monitoring/basic_metrics.py @@ -22,7 +22,6 @@ from opentelemetry.sdk.metrics import Counter, MeterProvider from opentelemetry.sdk.metrics.export.controller import PushController -metrics.set_meter_provider(MeterProvider()) meter = metrics.get_meter(__name__, True) # Gather and export metrics every 5 seconds From 27f35ee6ba0708b5475c7b59f24dcc3195b5b0b2 Mon Sep 17 00:00:00 2001 From: Andrew Xue Date: Fri, 5 Jun 2020 21:48:40 -0400 Subject: [PATCH 19/21] Update ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py Co-authored-by: Chris Kleinknecht --- .../src/opentelemetry/exporter/cloud_monitoring/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py b/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py index d80318d77b8..dc7d9639366 100644 --- a/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py +++ b/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py @@ -35,10 +35,10 @@ def __init__(self, project_id=None, client=None): self._last_updated = {} def _add_resource_info(self, series: TimeSeries) -> None: - """ Add Google resource specific information (e.g. instance id, region) + """Add Google resource specific information (e.g. instance id, region). - :param series: ProtoBuf TimeSeries - :return: + Args: + series: ProtoBuf TimeSeries """ # TODO: Leverage this better From 053790e3a3b9b74fd098f5341ae79e5863823302 Mon Sep 17 00:00:00 2001 From: Andrew Xue Date: Fri, 5 Jun 2020 21:48:48 -0400 Subject: [PATCH 20/21] Update ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py Co-authored-by: Chris Kleinknecht --- .../src/opentelemetry/exporter/cloud_monitoring/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py b/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py index dc7d9639366..6d6af26677a 100644 --- a/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py +++ b/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py @@ -148,7 +148,8 @@ def export( record.aggregator.last_update_timestamp, 1e9 ) - # Cloud Monitoring API allows, for any combination of labels and metric name, one update per WRITE_INTERVAL seconds + # Cloud Monitoring API allows, for any combination of labels and + # metric name, one update per WRITE_INTERVAL seconds updated_key = (metric_descriptor.type, record.labels) last_updated_seconds = self._last_updated.get(updated_key, 0) if seconds <= last_updated_seconds + WRITE_INTERVAL: From 86c51574ad01d499bb7ee8080966314fa17e5901 Mon Sep 17 00:00:00 2001 From: Andrew Xue Date: Fri, 5 Jun 2020 21:49:53 -0400 Subject: [PATCH 21/21] Update ext/opentelemetry-exporter-cloud-monitoring/tests/test_cloud_monitoring.py Co-authored-by: Chris Kleinknecht --- .../tests/test_cloud_monitoring.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ext/opentelemetry-exporter-cloud-monitoring/tests/test_cloud_monitoring.py b/ext/opentelemetry-exporter-cloud-monitoring/tests/test_cloud_monitoring.py index ebf696e08d7..d7f98e024a7 100644 --- a/ext/opentelemetry-exporter-cloud-monitoring/tests/test_cloud_monitoring.py +++ b/ext/opentelemetry-exporter-cloud-monitoring/tests/test_cloud_monitoring.py @@ -236,8 +236,9 @@ def test_export(self): [mock.call(self.project_name, [series1, series2])] ) - # Attempting to export too soon after another export with the exact same labels leads to it - # being dropped + # Attempting to export too soon after another export with the exact + # same labels leads to it being dropped + counter_two = CounterAggregator() counter_two.checkpoint = 1 counter_two.last_update_timestamp = (WRITE_INTERVAL + 2) * 1e9