Skip to content

Commit

Permalink
Merge pull request #2 from claws/decorators
Browse files Browse the repository at this point in the history
add decorators
  • Loading branch information
claws committed Aug 3, 2016
2 parents 3872cba + 5527d6d commit cdf41a2
Show file tree
Hide file tree
Showing 13 changed files with 450 additions and 24 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ style.fix:


check_types:
MYPYPATH=$VIRTUAL_ENV/lib/python3.5/site-packages mypy -p aioprometheus --fast-parser -s
@MYPYPATH=$VIRTUAL_ENV/lib/python3.5/site-packages mypy -p aioprometheus --fast-parser -s

# help: docs - generate project documentation
docs: coverage
Expand Down
3 changes: 2 additions & 1 deletion aioprometheus/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@

from .collectors import (
Collector, Counter, Gauge, Summary, Histogram)
from .decorators import count_exceptions, inprogress, timer
from .formats import (BinaryFormatter, TextFormatter)
from .pusher import Pusher
from .registry import Registry, CollectorRegistry
from .service import Service

__version__ = "16.07.31"
__version__ = "16.08.03"
8 changes: 8 additions & 0 deletions aioprometheus/collectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,10 @@ def add(self, labels: LabelsType, value: NumericValueType) -> None:

e.observe(float(value)) # type: ignore

# https://prometheus.io/docs/instrumenting/writing_clientlibs/#summary
# A summary MUST have the ``observe`` methods
observe = add

def get(self,
labels: LabelsType) -> Dict[Union[float, str], NumericValueType]:
'''
Expand Down Expand Up @@ -377,6 +381,10 @@ def add(self, labels: LabelsType, value: NumericValueType) -> None:

h.observe(float(value))

# https://prometheus.io/docs/instrumenting/writing_clientlibs/#histogram
# A histogram MUST have the ``observe`` methods
observe = add

def get(self,
labels: LabelsType) -> Dict[Union[float, str], NumericValueType]:
'''
Expand Down
148 changes: 148 additions & 0 deletions aioprometheus/decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
'''
This module provides metrics decorators
'''

import asyncio
import time

from .collectors import Counter, Gauge, Summary
from functools import wraps
from typing import Any, Callable, Dict


def timer(metric: Summary,
labels: Dict[str, str] = None) -> Callable[..., Any]:
'''
This decorator provides a way for users to time code in seconds.
This decorator function wraps a function with code to calculate how long
the wrapped function takes to execute and updates the metric with the
duration.
:param metric: a metric to update with the calculated function duration.
The metric object being updated is expected to be a Summary metric
object.
:param labels: a dict of extra labels to associate with the metric.
:return: a coroutine function that wraps the decortated function
'''
if not isinstance(metric, Summary):
raise Exception(
'time decorator expects a Summary metric but got: {}'.format(
metric))

def measure(func):
'''
This function wraps a decorated function with timing and metric
updating logic.
:param func: the callable to be timed.
:returns: the return value from the decorated callable.
'''
@wraps(func)
async def func_wrapper(*args, **kwds):
start_time = time.time()
rv = func(*args, **kwds)
if isinstance(rv, asyncio.Future) or asyncio.iscoroutine(rv):
rv = await rv
metric.add(labels, time.time() - start_time)
return rv

return func_wrapper

return measure


def inprogress(metric: Gauge,
labels: Dict[str, str] = None) -> Callable[..., Any]:
'''
This decorator provides a way for users to track in-progress requests
(or other things) in some piece of code/function.
This decorator function wraps a function with code to track how many
of the measured items are in progress.
The metric is incremented before calling the wrapped function and
decremented when the wrapped function is complete.
:param metric: a metric to increment and decrement. The metric object
being updated is expected to be a Gauge metric object.
:param labels: a dict of extra labels to associate with the metric.
:return: a coroutine function that wraps the decortated function
'''
if not isinstance(metric, Gauge):
raise Exception(
'inprogess decorator expects a Gauge metric but got: {}'.format(
metric))

def track(func):
'''
This function wraps a decorated function with metric incremeting
and decrementing logic.
:param func: the callable to be tracked.
:returns: the return value from the decorated callable.
'''
@wraps(func)
async def func_wrapper(*args, **kwds):
metric.inc(labels)
rv = func(*args, **kwds)
if isinstance(rv, asyncio.Future) or asyncio.iscoroutine(rv):
rv = await rv
metric.dec(labels)
return rv

return func_wrapper

return track


def count_exceptions(metric: Counter,
labels: Dict[str, str] = None) -> Callable[..., Any]:
'''
This decorator provides a way for users to track in-progress requests
(or other things) in some piece of code/function.
This decorator function wraps a function with code to track how many
exceptions occur.
:param metric: a metric to increment when an exception is caught. The
metric object being updated is expected to be a Counter metric object.
:param labels: a dict of extra labels to associate with the metric.
:return: a coroutine function that wraps the decortated function
'''
if not isinstance(metric, Counter):
raise Exception(
'inprogess decorator expects a Counter metric but got: {}'.format(
metric))

def track(func):
'''
This function wraps a decorated function with metric incremeting
logic.
:param func: the callable to be tracked.
:returns: the return value from the decorated callable.
'''
@wraps(func)
async def func_wrapper(*args, **kwds):
try:
rv = func(*args, **kwds)
if isinstance(rv, asyncio.Future) or asyncio.iscoroutine(rv):
rv = await rv
except:
metric.inc(labels)
raise
return rv

return func_wrapper

return track
17 changes: 15 additions & 2 deletions aioprometheus/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

from .negotiator import negotiate
from .registry import Registry
from typing import Optional, Set
from typing import Set

# imports only used for type annotations
if False:
Expand Down Expand Up @@ -47,8 +47,14 @@ def __init__(self,
:param loop: The event loop instance to use. If no loop is specified
then the default event loop will be retrieved.
:raises: Exception if the registry object is not an instance of the
Registry type.
'''
self.loop = loop or asyncio.get_event_loop()
if registry is not None and not isinstance(registry, Registry):
raise Exception(
'registry must be a Registry, got: {}'.format(registry))
self.registry = registry or Registry()
self._svr = None # type: Server
self._svc = None # type: aiohttp.web.Application
Expand All @@ -57,7 +63,12 @@ def __init__(self,

@property
def url(self) -> str:
''' Return the Prometheus metrics url '''
''' Return the Prometheus metrics url
:raises: Exception if the server has not been started.
:return: the service URL as a string
'''
if self._svr is None:
raise Exception(
"No URL available, Prometheus metrics server is not running")
Expand Down Expand Up @@ -95,6 +106,8 @@ async def start(self,
:param discovery_agent: an agent that can register the metrics
service with a service discovery mechanism.
:raises: Exception if the server could not be started.
'''
logger.debug(
'Prometheus metrics server starting on %s:%s%s',
Expand Down
18 changes: 17 additions & 1 deletion docs/dev/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,22 @@ package too.
$ python -m unittest test_negotiate
Type Annotations
----------------

The code base has been updated with type annotations. These provide helpful
gradual typing information that can improve how easily the code is understood
and which helps with any future enhancements.

The type annotations checker ``mypy`` currently runs cleanly with no warnings.

Use the Makefile convenience rule to check no issues are reported.

.. code-block:: console
$ make check_types
Coverage
--------

Expand Down Expand Up @@ -184,7 +200,7 @@ The following steps are used to make a new software release:

.. code-block:: console
python setup.py upload
python setup.py sdist upload
- Create and push a repo tag to Github.

Expand Down
33 changes: 21 additions & 12 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,6 @@ It supports multiple data formats and pushing metrics to a gateway.

The project source code can be found `here <https://github.com/claws/aioprometheus>`_.

.. warning::

While this project is mostly in a usable state it is still very early in
development. There are no backwards compatibility guarantees yet.

`aioprometheus` originates from the (now deprecated)
`prometheus python <https://github.com/slok/prometheus-python>`_ package.
Many thanks to `slok <https://github.com/slok>`_ for developing
prometheus-python. I have taken the original work and modified it to meet
the needs of my asyncio-based applications, added the histogram metric,
integrated the use of Pyrobuf, updated and extended tests, added docs, etc.


Contents
--------
Expand All @@ -39,6 +27,15 @@ Contents
api/index


Example
-------

The example below demonstrates how the ``@timer`` decorator can be used to
simplify measuring how long it takes to run a function.

.. literalinclude:: ../examples/decorator_timer.py


License
-------

Expand All @@ -50,3 +47,15 @@ was released under the MIT license. `aioprometheus` continues to use the MIT
license and contains a copy of the orignal MIT license from the
`prometheus-python` project as instructed by the original license.


Origins
-------

`aioprometheus` originates from the (now deprecated)
`prometheus python <https://github.com/slok/prometheus-python>`_ package.
Many thanks to `slok <https://github.com/slok>`_ for developing
prometheus-python. I have taken the original work and modified it to meet
the needs of my asyncio-based applications, added the histogram metric,
integrated the use of Pyrobuf, updated and extended tests, added docs,
decorators, etc.

35 changes: 30 additions & 5 deletions docs/user/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,31 @@ is the same as this one with const labels:
ram_metric.set({'type': "swap"}, 100)
Decorators
++++++++++

A number of different decorators are provided to help simplfy the process of
instrumenting your code. As the ``aioprometheus`` library is targeting use in
long running ``asyncio`` based applications, the decorators return a
coroutine object. However, the wrapped function does not have to be a
coroutine.

The example below demonstrates how the ``@timer`` decorator can be used to
time how long it takes to run a function.

.. literalinclude:: ../../examples/decorator_timer.py

The following example demonstrates how the ``@inprogress`` decorator can be
used to track how many requests are in progress.

.. literalinclude:: ../../examples/decorator_inprogress.py

The next example demonstrates how the ``@count_exceptions`` decorator can be
used to track the number of exceptions that occur in a function block.

.. literalinclude:: ../../examples/decorator_count_exceptions.py


Exporting Metrics
-----------------

Expand Down Expand Up @@ -255,9 +280,9 @@ and can observe the metrics from the example.


Push Gateway
++++++++++++
------------

Another method of exposing metrics is to push them to a gateway that will
Another method of exposing metrics is to push them to a gateway that will
get scraped by Prometheus.

Prometheus provides a push gateway intermediary that can be used to help
Expand Down Expand Up @@ -289,8 +314,8 @@ Discovery
This area is still under development.

Applications need an automated approach to declaring their presence so that
Prometheus will begin scraping their metrics. `aioprometheus` provides a
service discovery mechanism interface.
Prometheus will begin scraping their metrics. `aioprometheus` provides an
extendable service discovery mechanism interface.

An implementation based on ``etcd`` is provided as an example mechanism.
An example implementation based on ``etcd`` is included in the code.

0 comments on commit cdf41a2

Please sign in to comment.