diff --git a/README.md b/README.md index 79a4a89e..3020b581 100644 --- a/README.md +++ b/README.md @@ -228,6 +228,24 @@ To add Prometheus exposition to an existing HTTP server, see the `MetricsServlet which provides a `BaseHTTPRequestHandler`. It also serves as a simple example of how to write a custom endpoint. +#### Twisted + +To use prometheus with [twisted](https://twistedmatrix.com/), there is `MetricsResource` which exposes metrics as a twisted resource. + +```python +from prometheus_client.twisted import MetricsResource +from twisted.web.server import Site +from twisted.web.resource import Resource +from twisted.internet import reactor + +root = Resource() +root.putChild(b'metrics', MetricsResource()) + +factory = Site(root) +reactor.listenTCP(8000, factory) +reactor.run() +``` + ### Node exporter textfile collector The [textfile collector](https://github.com/prometheus/node_exporter#textfile-collector) diff --git a/prometheus_client/twisted/__init__.py b/prometheus_client/twisted/__init__.py new file mode 100644 index 00000000..87e0b8a6 --- /dev/null +++ b/prometheus_client/twisted/__init__.py @@ -0,0 +1,3 @@ +from ._exposition import MetricsResource + +__all__ = ['MetricsResource'] diff --git a/prometheus_client/twisted/_exposition.py b/prometheus_client/twisted/_exposition.py new file mode 100644 index 00000000..66c548c7 --- /dev/null +++ b/prometheus_client/twisted/_exposition.py @@ -0,0 +1,18 @@ +from __future__ import absolute_import, unicode_literals +from .. import REGISTRY, generate_latest, CONTENT_TYPE_LATEST + +from twisted.web.resource import Resource + + +class MetricsResource(Resource): + """ + Twisted ``Resource`` that serves prometheus metrics. + """ + isLeaf = True + + def __init__(self, registry=REGISTRY): + self.registry = registry + + def render_GET(self, request): + request.setHeader(b'Content-Type', CONTENT_TYPE_LATEST.encode('ascii')) + return generate_latest(self.registry) diff --git a/setup.py b/setup.py index c4d055b7..de535fb7 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,10 @@ license = "Apache Software License 2.0", keywords = "prometheus monitoring instrumentation client", url = "https://github.com/prometheus/client_python", - packages=['prometheus_client', 'prometheus_client.bridge'], + packages=['prometheus_client', 'prometheus_client.bridge', 'prometheus_client.twisted'], + extras_requires={ + 'twisted': ['twisted'], + }, test_suite="tests", classifiers=[ "Development Status :: 4 - Beta", diff --git a/tests/test_twisted.py b/tests/test_twisted.py new file mode 100644 index 00000000..8554cc95 --- /dev/null +++ b/tests/test_twisted.py @@ -0,0 +1,48 @@ +from __future__ import absolute_import, unicode_literals + +from unittest import skipUnless + +from prometheus_client import Counter +from prometheus_client import CollectorRegistry, generate_latest + +try: + from prometheus_client.twisted import MetricsResource + + from twisted.trial.unittest import TestCase + from twisted.web.server import Site + from twisted.web.resource import Resource + from twisted.internet import reactor + from twisted.web.client import Agent + from twisted.web.client import readBody + HAVE_TWISTED = True +except ImportError: + from unittest import TestCase + HAVE_TWISTED = False + + +class MetricsResourceTest(TestCase): + @skipUnless(HAVE_TWISTED, "Don't have twisted installed.") + def setUp(self): + self.registry = CollectorRegistry() + + def test_reports_metrics(self): + """ + ``MetricsResource`` serves the metrics from the provided registry. + """ + c = Counter('cc', 'A counter', registry=self.registry) + c.inc() + + root = Resource() + root.putChild(b'metrics', MetricsResource(registry=self.registry)) + server = reactor.listenTCP(0, Site(root)) + self.addCleanup(server.stopListening) + + agent = Agent(reactor) + port = server.getHost().port + url = u"http://localhost:{port}/metrics".format(port=port) + d = agent.request(b"GET", url.encode("ascii")) + + d.addCallback(readBody) + d.addCallback(self.assertEqual, generate_latest(self.registry)) + + return d