Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Add cloud monitoring exporter #739

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Jun 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
35 changes: 35 additions & 0 deletions docs/examples/cloud_monitoring/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
Cloud Monitoring Exporter Example
=================================

These examples show how to use OpenTelemetry to send metrics data to Cloud Monitoring.


Basic Example
-------------

To use this exporter you first need to:
* `Create a Google Cloud project <https://console.cloud.google.com/projectcreate>`_.
* Enable the Cloud Monitoring API (aka Stackdriver Monitoring API) in the project `here <https://console.cloud.google.com/apis/library?q=cloud_monitoring>`_.
* Enable `Default Application Credentials <https://developers.google.com/identity/protocols/application-default-credentials>`_.

* Installation

.. code-block:: sh

pip install opentelemetry-api
pip install opentelemetry-sdk
pip install opentelemetry-exporter-cloud-monitoring

* Run example

.. code-block:: sh

python basic_metrics.py

Viewing Output
--------------------------

After running the example:
* Go to the `Cloud Monitoring Metrics Explorer page <https://console.cloud.google.com/monitoring/metrics-explorer>`_.
* In "Find resource type and metric" enter "OpenTelemetry/request_counter".
* You can filter by labels and change the graphical output here as well.
45 changes: 45 additions & 0 deletions docs/examples/cloud_monitoring/basic_metrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/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 opentelemetry.exporter.cloud_monitoring import (
CloudMonitoringMetricsExporter,
)
from opentelemetry.sdk.metrics import Counter, MeterProvider
from opentelemetry.sdk.metrics.export.controller import PushController

meter = metrics.get_meter(__name__, True)

# Gather and export metrics every 5 seconds
controller = PushController(
meter=meter, exporter=CloudMonitoringMetricsExporter(), interval=5
)

requests_counter = meter.create_metric(
name="request_counter",
description="number of requests",
unit="1",
value_type=int,
metric_type=Counter,
label_keys=("environment"),
)

staging_labels = {"environment": "staging"}

for i in range(20):
requests_counter.add(25, staging_labels)
time.sleep(10)
7 changes: 7 additions & 0 deletions docs/ext/cloud_monitoring/cloud_monitoring.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
OpenTelemetry Cloud Monitoring Exporter
=======================================

.. automodule:: opentelemetry.exporter.cloud_monitoring
:members:
:undoc-members:
:show-inheritance:
18 changes: 18 additions & 0 deletions ext/opentelemetry-exporter-cloud-monitoring/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
OpenTelemetry Cloud Monitoring Exporters
========================================

This library provides classes for exporting metrics data to Google Cloud Monitoring.

Installation
------------

::

pip install opentelemetry-exporter-cloud-monitoring

References
----------

* `OpenTelemetry Cloud Monitoring Exporter <https://opentelemetry-python.readthedocs.io/en/latest/ext/cloud_monitoring/cloud_monitoring.html>`_
* `Cloud Monitoring <https://cloud.google.com/monitoring/>`_
* `OpenTelemetry Project <https://opentelemetry.io/>`_
48 changes: 48 additions & 0 deletions ext/opentelemetry-exporter-cloud-monitoring/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@

# 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-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 = [email protected]
url = https://github.com/open-telemetry/opentelemetry-python/ext/opentelemetry-exporter-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

[options.packages.find]
where = src
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
where = src
where = src

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your commit suggestion has no change, can you clarify?

31 changes: 31 additions & 0 deletions ext/opentelemetry-exporter-cloud-monitoring/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# 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",
"exporter",
"cloud_monitoring",
"version.py",
)
PACKAGE_INFO = {}
with open(VERSION_FILENAME) as f:
exec(f.read(), PACKAGE_INFO)

setuptools.setup(version=PACKAGE_INFO["__version__"])
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import logging
from typing import Optional, 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
WRITE_INTERVAL = 10


# 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(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).

Args:
series: ProtoBuf TimeSeries
"""
# TODO: Leverage this better

def _batch_write(self, series: TimeSeries) -> None:
""" 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
) -> 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
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": None,
"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, 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
)
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
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

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] = str(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
)

# 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)
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
Original file line number Diff line number Diff line change
@@ -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.9.dev0"
Empty file.
Loading