Skip to content
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

Implement "named" meters + Remove "Batcher" from Meter constructor #431

Merged
merged 35 commits into from
Mar 1, 2020
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
bba1fea
Named meters
lzchen Feb 19, 2020
712ccd3
Remove test
lzchen Feb 19, 2020
b50c269
fix comment
lzchen Feb 19, 2020
fd3d175
Merge branch 'master' of https://github.com/open-telemetry/openteleme…
lzchen Feb 19, 2020
08b1117
Fix tests
lzchen Feb 19, 2020
c92f837
address comments + fix lint
lzchen Feb 19, 2020
7b8b67d
Rename MeterSource to MeterProvider
lzchen Feb 19, 2020
63824f3
black
lzchen Feb 19, 2020
16e728b
black
lzchen Feb 19, 2020
7733a87
remove black
lzchen Feb 20, 2020
b436c62
fix lint
lzchen Feb 20, 2020
d6c97f0
fix lint
lzchen Feb 20, 2020
a2f2e0f
fix lint
lzchen Feb 20, 2020
32cf2c1
Merge branch 'master' of https://github.com/open-telemetry/openteleme…
lzchen Feb 25, 2020
ec9c673
Merge
lzchen Feb 25, 2020
7ebd438
Rename to MeterProvider, remove Batcher from constructor
lzchen Feb 25, 2020
27d75ba
fix mypy
lzchen Feb 25, 2020
08095b6
black
lzchen Feb 25, 2020
6a743c7
Black
lzchen Feb 25, 2020
e226eda
address comments
lzchen Feb 27, 2020
c7432ef
update tests
lzchen Feb 27, 2020
60b2f38
merge
lzchen Feb 27, 2020
bd53f84
fix tests
lzchen Feb 27, 2020
6f6a37d
Fix tests
lzchen Feb 27, 2020
4770ec4
black
lzchen Feb 27, 2020
0f36b31
remove black
lzchen Feb 27, 2020
4f4632b
ADDRESS COMMENTS
lzchen Feb 27, 2020
e591a39
black
lzchen Feb 27, 2020
0878f5d
fix lint
lzchen Feb 27, 2020
584d996
fix example
lzchen Feb 27, 2020
f854923
Fix lint
lzchen Feb 27, 2020
4df2553
merge
lzchen Feb 28, 2020
40ee67b
fix labelset
lzchen Feb 28, 2020
0b93285
fix lint
lzchen Feb 28, 2020
3735d88
set to none
lzchen Feb 29, 2020
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
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_source_implementation(lambda _: MeterProvider())
lzchen marked this conversation as resolved.
Show resolved Hide resolved
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
8 changes: 5 additions & 3 deletions examples/metrics/record.py
Original file line number Diff line number Diff line change
Expand Up @@ -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: 11 additions & 13 deletions examples/metrics/simple_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,17 @@ 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.

5 changes: 1 addition & 4 deletions ext/opentelemetry-ext-flask/tests/test_flask_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,7 @@ def assert_environ():

def test_simple(self):
expected_attrs = expected_attributes(
{
"http.target": "/hello/123",
"http.route": "/hello/<int:helloid>",
}
{"http.target": "/hello/123", "http.route": "/hello/<int:helloid>"}
lzchen marked this conversation as resolved.
Show resolved Hide resolved
)
resp = self.client.get("/hello/123")
self.assertEqual(200, resp.status_code)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@
CustomCollector,
PrometheusMetricsExporter,
)
from opentelemetry import metrics as metrics_api
from opentelemetry.sdk import metrics
from opentelemetry.sdk.metrics.export import MetricRecord, MetricsExportResult
from opentelemetry.sdk.metrics.export.aggregate import CounterAggregator


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

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

def test_invalid_metric(self):

meter = metrics.Meter()
meter = metrics_api.get_meter(__name__)
metric = meter.create_metric(
"tesname", "testdesc", "unit", int, TestMetric
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ def attach(self, context: Context) -> object:
def get_current(self) -> Context:
"""See `opentelemetry.context.RuntimeContext.get_current`."""
if not hasattr(self._current_context, self._CONTEXT_KEY):
setattr(
self._current_context, self._CONTEXT_KEY, Context(),
)
setattr(self._current_context, self._CONTEXT_KEY, Context())
lzchen marked this conversation as resolved.
Show resolved Hide resolved
context = getattr(
self._current_context, self._CONTEXT_KEY
) # type: Context
Expand Down
117 changes: 97 additions & 20 deletions opentelemetry-api/src/opentelemetry/metrics/__init__.py
Original file line number Diff line number Diff line change
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_SOURCE = None
_METER_SOURCE_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_SOURCE, _METER_SOURCE_FACTORY # pylint:disable=global-statement

if _METER is None:
if _METER_SOURCE is None:
# pylint:disable=protected-access
try:
_METER = loader._load_impl(Meter, _METER_FACTORY) # type: ignore
_METER_SOURCE = loader._load_impl(
MeterProvider, _METER_SOURCE_FACTORY # type: ignore
)
except TypeError:
lzchen marked this conversation as resolved.
Show resolved Hide resolved
# 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_SOURCE = DefaultMeterProvider()
del _METER_SOURCE_FACTORY
Copy link
Member

Choose a reason for hiding this comment

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

Should this variable be deleted, or just set to None? It's a lot easier to see if a variable is None that go to locals() and check if the _METER_SOURCE_FACTORY key is in there as a guard condition to check on the _METER_SOURCE_FACTORY.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't have a strong opinion on this. I'm taking what is being done in the tracing API to be consistent.

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 on setting it to None, stays consistent with the _TRACER_PROVIDER_FACTORY

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

my bad, i missed that.


return _METER
return _METER_SOURCE


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_SOURCE_FACTORY # pylint:disable=global-statement

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

_METER_FACTORY = factory
_METER_SOURCE_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 2019, OpenTelemetry Authors
lzchen marked this conversation as resolved.
Show resolved Hide resolved
#
# 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