Skip to content

Commit

Permalink
api: Implement "named" meters + Remove "Batcher" from Meter construct…
Browse files Browse the repository at this point in the history
…or (#431)

Implements #221.
Also fixes #394.

Stateful.py and stateless.py in metrics example folder are not changed to use the new loader in anticipation of #422 being merged first and removing them.

Lastly, moves InstrumentationInfo from trace.py in the sdk to utils.
  • Loading branch information
lzchen authored Mar 1, 2020
1 parent c2ba065 commit 11467c4
Show file tree
Hide file tree
Showing 22 changed files with 321 additions and 248 deletions.
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,14 @@ with tracer.start_as_current_span('foo'):

```python
from opentelemetry import metrics
from opentelemetry.sdk.metrics import Counter, Meter
from opentelemetry.sdk.metrics import Counter, MeterProvider
from opentelemetry.sdk.metrics.export import ConsoleMetricsExporter
from opentelemetry.sdk.metrics.export.controller import PushController

metrics.set_preferred_meter_implementation(lambda T: Meter())
meter = metrics.meter()
metrics.set_preferred_meter_provider_implementation(lambda _: MeterProvider())
meter = metrics.get_meter(__name__)
exporter = ConsoleMetricsExporter()
controller = PushController(meter, exporter, 5)

counter = meter.create_metric(
"available memory",
Expand All @@ -89,9 +91,6 @@ counter = meter.create_metric(
label_values = ("staging",)
counter_handle = counter.get_handle(label_values)
counter_handle.add(100)

exporter.export([(counter, label_values)])
exporter.shutdown()
```

See the [API documentation](https://open-telemetry.github.io/opentelemetry-python/) for more detail, and the [examples folder](./examples) for a more sample code.
Expand Down
1 change: 1 addition & 0 deletions docs/opentelemetry.sdk.metrics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Submodules

opentelemetry.sdk.metrics.export.aggregate
opentelemetry.sdk.metrics.export.batcher
opentelemetry.sdk.util.instrumentation

.. automodule:: opentelemetry.sdk.metrics
:members:
Expand Down
1 change: 1 addition & 0 deletions docs/opentelemetry.sdk.trace.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Submodules
.. toctree::

opentelemetry.sdk.trace.export
opentelemetry.sdk.util.instrumentation

.. automodule:: opentelemetry.sdk.trace
:members:
Expand Down
4 changes: 4 additions & 0 deletions docs/opentelemetry.sdk.util.instrumentation.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
opentelemetry.sdk.util.instrumentation
==========================================

.. automodule:: opentelemetry.sdk.util.instrumentation
6 changes: 3 additions & 3 deletions examples/metrics/prometheus.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@

from opentelemetry import metrics
from opentelemetry.ext.prometheus import PrometheusMetricsExporter
from opentelemetry.sdk.metrics import Counter, Meter
from opentelemetry.sdk.metrics import Counter, MeterProvider
from opentelemetry.sdk.metrics.export.controller import PushController

# Start Prometheus client
start_http_server(port=8000, addr="localhost")

# Meter is responsible for creating and recording metrics
metrics.set_preferred_meter_implementation(lambda _: Meter())
meter = metrics.meter()
metrics.set_preferred_meter_provider_implementation(lambda _: MeterProvider())
meter = metrics.get_meter(__name__)
# exporter to export metrics to Prometheus
prefix = "MyAppPrefix"
exporter = PrometheusMetricsExporter(prefix)
Expand Down
10 changes: 6 additions & 4 deletions examples/metrics/record.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2019, OpenTelemetry Authors
# Copyright 2020, OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -19,13 +19,15 @@
import time

from opentelemetry import metrics
from opentelemetry.sdk.metrics import Counter, Meter
from opentelemetry.sdk.metrics import Counter, MeterProvider
from opentelemetry.sdk.metrics.export import ConsoleMetricsExporter
from opentelemetry.sdk.metrics.export.controller import PushController

# The preferred tracer implementation must be set, as the opentelemetry-api
# defines the interface with a no-op implementation.
metrics.set_preferred_meter_provider_implementation(lambda _: MeterProvider())
# Meter is responsible for creating and recording metrics
metrics.set_preferred_meter_implementation(lambda _: Meter())
meter = metrics.meter()
meter = metrics.get_meter(__name__)
# exporter to export metrics to the console
exporter = ConsoleMetricsExporter()
# controller collects metrics created from meter and exports it via the
Expand Down
24 changes: 10 additions & 14 deletions examples/metrics/simple_example.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2019, OpenTelemetry Authors
# Copyright 2020, OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -23,9 +23,8 @@
import time

from opentelemetry import metrics
from opentelemetry.sdk.metrics import Counter, Measure, Meter
from opentelemetry.sdk.metrics import Counter, Measure, MeterProvider
from opentelemetry.sdk.metrics.export import ConsoleMetricsExporter
from opentelemetry.sdk.metrics.export.batcher import UngroupedBatcher
from opentelemetry.sdk.metrics.export.controller import PushController

batcher_mode = "stateful"
Expand All @@ -44,18 +43,15 @@ def usage(argv):
usage(sys.argv)
sys.exit(1)

# Batcher used to collect all created metrics from meter ready for exporting
# Pass in True/False to indicate whether the batcher is stateful.
# True indicates the batcher computes checkpoints from over the process
# lifetime.
# False indicates the batcher computes checkpoints which describe the updates
# of a single collection period (deltas)
batcher = UngroupedBatcher(batcher_mode == "stateful")

# If a batcher is not provided, a default batcher is used
# Meter is responsible for creating and recording metrics
metrics.set_preferred_meter_implementation(lambda _: Meter(batcher))
meter = metrics.meter()
metrics.set_preferred_meter_provider_implementation(lambda _: MeterProvider())

# Meter's namespace corresponds to the string passed as the first argument Pass
# in True/False to indicate whether the batcher is stateful. True indicates the
# batcher computes checkpoints from over the process lifetime. False indicates
# the batcher computes checkpoints which describe the updates of a single
# collection period (deltas)
meter = metrics.get_meter(__name__, batcher_mode == "stateful")

# Exporter to export metrics to the console
exporter = ConsoleMetricsExporter()
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

class TestPrometheusMetricExporter(unittest.TestCase):
def setUp(self):
self._meter = metrics.Meter()
self._meter = metrics.MeterProvider().get_meter(__name__)
self._test_metric = self._meter.create_metric(
"testname",
"testdesc",
Expand Down Expand Up @@ -74,7 +74,7 @@ def test_export(self):
self.assertIs(result, MetricsExportResult.SUCCESS)

def test_counter_to_prometheus(self):
meter = metrics.Meter()
meter = metrics.MeterProvider().get_meter(__name__)
metric = meter.create_metric(
"test@name",
"testdesc",
Expand Down Expand Up @@ -111,7 +111,7 @@ def test_counter_to_prometheus(self):

def test_invalid_metric(self):

meter = metrics.Meter()
meter = metrics.MeterProvider().get_meter(__name__)
metric = meter.create_metric(
"tesname", "testdesc", "unit", int, TestMetric
)
Expand Down
119 changes: 98 additions & 21 deletions opentelemetry-api/src/opentelemetry/metrics/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2019, OpenTelemetry Authors
# Copyright 2020, OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -27,10 +27,13 @@
"""
import abc
import logging
from typing import Callable, Dict, Optional, Sequence, Tuple, Type, TypeVar

from opentelemetry.util import loader

logger = logging.getLogger(__name__)

ValueT = TypeVar("ValueT", int, float)


Expand Down Expand Up @@ -224,6 +227,56 @@ def record(self, value: ValueT, label_set: LabelSet) -> None:
"""


class MeterProvider(abc.ABC):
@abc.abstractmethod
def get_meter(
self,
instrumenting_module_name: str,
stateful: bool = True,
instrumenting_library_version: str = "",
) -> "Meter":
"""Returns a `Meter` for use by the given instrumentation library.
This function may return different `Meter` types (e.g. a no-op meter
vs. a functional meter).
Args:
instrumenting_module_name: The name of the instrumenting module
(usually just ``__name__``).
This should *not* be the name of the module that is
instrumented but the name of the module doing the instrumentation.
E.g., instead of ``"requests"``, use
``"opentelemetry.ext.http_requests"``.
stateful: True/False to indicate whether the meter will be
stateful. True indicates the meter computes checkpoints
from over the process lifetime. False indicates the meter
computes checkpoints which describe the updates of a single
collection period (deltas).
instrumenting_library_version: Optional. The version string of the
instrumenting library. Usually this should be the same as
``pkg_resources.get_distribution(instrumenting_library_name).version``.
"""


class DefaultMeterProvider(MeterProvider):
"""The default MeterProvider, used when no implementation is available.
All operations are no-op.
"""

def get_meter(
self,
instrumenting_module_name: str,
stateful: bool = True,
instrumenting_library_version: str = "",
) -> "Meter":
# pylint:disable=no-self-use,unused-argument
return DefaultMeter()


MetricT = TypeVar("MetricT", Counter, Gauge, Measure)


Expand Down Expand Up @@ -322,45 +375,69 @@ def get_label_set(self, labels: Dict[str, str]) -> "LabelSet":
# Once https://github.com/python/mypy/issues/7092 is resolved,
# the following type definition should be replaced with
# from opentelemetry.util.loader import ImplementationFactory
ImplementationFactory = Callable[[Type[Meter]], Optional[Meter]]

_METER = None
_METER_FACTORY = None
ImplementationFactory = Callable[
[Type[MeterProvider]], Optional[MeterProvider]
]

_METER_PROVIDER = None
_METER_PROVIDER_FACTORY = None


def get_meter(
instrumenting_module_name: str,
stateful: bool = True,
instrumenting_library_version: str = "",
) -> "Meter":
"""Returns a `Meter` for use by the given instrumentation library.
This function is a convenience wrapper for
opentelemetry.metrics.meter_provider().get_meter
"""
return meter_provider().get_meter(
instrumenting_module_name, stateful, instrumenting_library_version
)


def meter() -> Meter:
"""Gets the current global :class:`~.Meter` object.
def meter_provider() -> MeterProvider:
"""Gets the current global :class:`~.MeterProvider` object.
If there isn't one set yet, a default will be loaded.
"""
global _METER, _METER_FACTORY # pylint:disable=global-statement
global _METER_PROVIDER, _METER_PROVIDER_FACTORY # pylint:disable=global-statement

if _METER is None:
if _METER_PROVIDER is None:
# pylint:disable=protected-access
try:
_METER = loader._load_impl(Meter, _METER_FACTORY) # type: ignore
_METER_PROVIDER = loader._load_impl(
MeterProvider, _METER_PROVIDER_FACTORY # type: ignore
)
except TypeError:
# if we raised an exception trying to instantiate an
# abstract class, default to no-op tracer impl
_METER = DefaultMeter()
del _METER_FACTORY
# abstract class, default to no-op meter impl
logger.warning(
"Unable to instantiate MeterProvider from meter provider factory.",
exc_info=True,
)
_METER_PROVIDER = DefaultMeterProvider()
_METER_PROVIDER_FACTORY = None

return _METER
return _METER_PROVIDER


def set_preferred_meter_implementation(factory: ImplementationFactory) -> None:
"""Set the factory to be used to create the meter.
def set_preferred_meter_provider_implementation(
factory: ImplementationFactory,
) -> None:
"""Set the factory to be used to create the meter provider.
See :mod:`opentelemetry.util.loader` for details.
This function may not be called after a meter is already loaded.
Args:
factory: Callback that should create a new :class:`Meter` instance.
factory: Callback that should create a new :class:`MeterProvider` instance.
"""
global _METER, _METER_FACTORY # pylint:disable=global-statement
global _METER_PROVIDER_FACTORY # pylint:disable=global-statement

if _METER:
raise RuntimeError("Meter already loaded.")
if _METER_PROVIDER:
raise RuntimeError("MeterProvider already loaded.")

_METER_FACTORY = factory
_METER_PROVIDER_FACTORY = factory
13 changes: 13 additions & 0 deletions opentelemetry-api/src/opentelemetry/util/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
# Copyright 2020, 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

# Since we want API users to be able to provide timestamps,
Expand Down
Loading

0 comments on commit 11467c4

Please sign in to comment.