Skip to content
Open
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
70 changes: 70 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# wavefront_dispatch

This package contains Wavefront python wrapper for Dispatch to send metrics directly to wavefront.

## Requirements
Python 2.7 or 3.6

## Install
Install from PyPi
```
pip install wavefront_dispatch
```
More package [details](https://pypi.org/project/wavefront-dispatch/) on PyPi.

## Required Secrets

* wavefront_sever_url = https://<INSTANCE>.wavefront.com
* wavefront_auth_token = Wavefront API token with Direct Data Ingestion permission

These secrets must be present in Dispatch functions' secrets of context.

## Usage

Decorate Dispatch handler function with @wavefront_dispatch.wrapper.

```Python
import wavefront_dispatch

@wavefront_dispatch.wrapper
def handle(ctx, payload):
# codes

```
And add `wavefront_dispatch` package as runtime dependency druing Dispatch python image creation.

## Standard Metrics reported by Wavefront Dispatch wrapper

Following metrics will be reported by wrapper:
Copy link
Member

Choose a reason for hiding this comment

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

The table looks screwed up, like this:

Following metrics will be reported by wrapper:

Metric Name Type Description
dispatch.function.wf.invocations.count Delta Counter Count of Dispatch function invocations
dispatch.function.wf.errors.count Delta Counter Count of Dispatch function executions with error
dispatch.function.wf.duration.value Gauge Execution time of Dispatch function in ms.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I did insert extract spaces and dashes for alignments with table header and contents. I will delete those extra spaces since the final rendering markdown are the same.

| Metric Name | Type | Description |
|---|---|---|
| dispatch.function.wf.invocations.count | Delta Counter | Count of Dispatch function invocations. |
| dispatch.function.wf.errors.count | Delta Counter | Count of Dispatch function executions with error. |
| dispatch.function.wf.duration.value | Gauge | Execution time of Dispatch function in ms. |


## Custom Dispatch Function Metrics
Custom metrics powered by [pyformance plugin](https://github.com/wavefrontHQ/python-client/tree/master/wavefront_pyformance).

Please refer to following [example](https://github.com/dispatchframework/wavefront-dispatch-python/blob/master/example.py):

```Python
import wavefront_dispatch
import random

@wavefront_dispatch.wrapper
def handle(ctx, payload):

# Customized metrics
registry = wavefront_dispatch.get_registry()
Copy link
Member

Choose a reason for hiding this comment

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

We're getting an instance of registry every time a function gets invoked. Is that intentional (recommended / required)?

If not, it better be initialized once and reused.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The registry is MetricsRegistry which only contains various maps of metrics types. My understanding is that the registry is for keeping track of current metrics during execution and should not be expensive. It has to be created every time for new function invocation.


# Report Gauge
gauge_val = registry.gauge("dispatch.function.wf.testgauge")
gauge_val.set_value(200)

# Report Counter
counter = registry.counter("dispatch.function.wf.testcounter")
counter.inc()

...
```
33 changes: 33 additions & 0 deletions example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import wavefront_dispatch
import random

@wavefront_dispatch.wrapper
def handle(ctx, payload):

# Fibonacci
f_2, f_1 = 0, 1
for n in range(random.randint(800, 900)):
f = f_1 + f_2
f_2, f_1 = f_1, f

# Customized metrics
registry = wavefront_dispatch.get_registry()

# Report Gauge
gauge_val = registry.gauge("dispatch.function.wf.testgauge")
gauge_val.set_value(200)

# Report Counter
counter = registry.counter("dispatch.function.wf.testcounter")
counter.inc()



if __name__ == "__main__":
ctx = {
"secrets": {
"wavefront_server_url":"https://<INSTANCE>.wavefront.com",
"wavefront_auth_token":"<AUTH_TOKEN>"}
}
payload = {}
handle(ctx, payload)
36 changes: 36 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# coding: utf-8

"""
Wavefront Dispatch Wrapper

<p>This is a Wavefront python wrapper for dispatch python function handler to send metrics directly to wavefront.</p> # noqa: E501
"""


from setuptools import setup, find_packages # noqa: H301

NAME = "wavefront_dispatch"
VERSION = "0.0.2"
# To install the library, run the following
#
# python setup.py install
#
# prerequisite: setuptools
# http://pypi.python.org/pypi/setuptools

REQUIRES = ["wavefront-pyformance >= 0.9.2"]

setup(
name=NAME,
version=VERSION,
description="Wavefront Python Wrapper for Dispatch",
author_email="",
url="https://github.com/dispatchframework/wavefront-dispatch-python/tree/master",
keywords=["Wavefront Dispatch", "Wavefront"],
install_requires=REQUIRES,
packages=find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]),
include_package_data=True,
long_description="""\
This is a Wavefront python wrapper for Dispatch function handler to send metrics directly to wavefront.
"""
)
65 changes: 65 additions & 0 deletions wavefront_dispatch/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from pyformance import MetricsRegistry
from wavefront_pyformance.wavefront_reporter import WavefrontDirectReporter
import os
from datetime import datetime
from wavefront_pyformance import delta

reg = None

def wrapper(func):
"""
Returns the Wavefront Dispatch wrapper. The wrapper collects dispatch functions
standard metrics and reports it directly to the specified wavefront url. It
requires the following Environment variables to be set:
1.WAVEFRONT_URL : https://<INSTANCE>.wavefront.com
2.WAVEFRONT_API_TOKEN : Wavefront API token with Direct Data Ingestion permission
"""
def call_dispatch_function(wf_reporter, *args, **kwargs):

METRICS_PREFIX = "dispatch.function.wf."
# Register duration metrics
dispatch_function_duration_gauge = reg.gauge(METRICS_PREFIX + "duration")
# Register invocations metrics
dispatch_function_invocations_counter = delta.delta_counter(reg, METRICS_PREFIX + "invocations")
dispatch_function_invocations_counter.inc()
# Registry errors metrics
dispatch_erros_count = delta.delta_counter(reg, METRICS_PREFIX + "errors")
time_start = datetime.now()
try:
response = func(*args, **kwargs)
return response
except:
dispatch_erros_count.inc()
raise
finally:
time_taken = datetime.now() - time_start
dispatch_function_duration_gauge.set_value(time_taken.total_seconds() * 1000)
wf_reporter.report_now(registry=reg)

def wavefront_wrapper(*args, **kwargs):
print("Func has been decorated.")

# Initialize registry
global reg
reg = MetricsRegistry()

# Get wavefront secrets
context, payload = args[0], args[1]
server = context["secrets"].get("wavefront_server_url", "")
auth_token = context["secrets"].get("wavefront_auth_token", "")

# Initialize the wavefront direct reporter
wf_direct_reporter = WavefrontDirectReporter(server=server,
token=auth_token,
registry=reg,
prefix="")

call_dispatch_function(wf_direct_reporter,
*args,
**kwargs)

return wavefront_wrapper


def get_registry():
return reg
Empty file.
19 changes: 19 additions & 0 deletions wavefront_dispatch/tests/test_wavefront_dispatch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from unittest import TestCase
from wavefront_pyformance.wavefront_reporter import WavefrontDirectReporter
import unittest
from pyformance import MetricsRegistry
from wavefront_dispatch import wrapper

@wrapper
def handler(ctx, payload):
return True

class TestWrapper(TestCase):
def test_wavefront_wrapper(self):
#reg = MetricsRegistry()
function_wrapper = wrapper(handler)
assert(function_wrapper.__name__ == "wavefront_wrapper")

if __name__ == '__main__':
# run 'python -m unittest discover' from toplevel to run tests
unittest.main()