Skip to content

Commit

Permalink
Merge pull request #20 from claws/add_tests_and_bump_version
Browse files Browse the repository at this point in the history
Add tests, fix style, update CI, bump version
  • Loading branch information
claws committed Apr 26, 2018
2 parents a8739df + 00e7593 commit fe5a43d
Show file tree
Hide file tree
Showing 11 changed files with 81 additions and 41 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ install:
- pip install dist/aioprometheus-*.whl
script:
- make test
- pip install pycodestyle
- make style
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

STYLE_EXCLUDE_LIST:=git status --porcelain --ignored | grep "!!" | grep ".py$$" | cut -d " " -f2 | tr "\n" ","
STYLE_MAX_LINE_LENGTH:=160
STYLE_CMD:=pycodestyle --exclude=.git,docs,$(shell $(STYLE_EXCLUDE_LIST)) --ignore=E309,E402 --max-line-length=$(STYLE_MAX_LINE_LENGTH) src/aioprometheus tests examples
STYLE_CMD:=pycodestyle --exclude=.git,docs,$(shell $(STYLE_EXCLUDE_LIST)) --ignore=E309,E402,W504 --max-line-length=$(STYLE_MAX_LINE_LENGTH) src/aioprometheus tests examples
VENVS_DIR := $(HOME)/.venvs
VENV_DIR := $(VENVS_DIR)/vap

Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Example
svr.registry.register(events_collector)
loop.run_until_complete(svr.start())
print('Serving prometheus metrics on: {}'.format(svr.url))
print('Serving prometheus metrics on: {}'.format(svr.metrics_url))
loop.call_later(1.0, on_timer_expiry, loop, events_collector)
Expand Down
11 changes: 5 additions & 6 deletions examples/app-example.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
#!/usr/bin/env python
'''
This more complicated example implements an application that uses the
aioprometheus package to collect application metrics and expose them on a
service endpoint.
This more complicated example implements an application that exposes
application metrics obtained from the psutil package.
This example requires ``psutil`` which can be obtained using
``pip install psutil``.
This example requires the ``psutil`` package which can be installed
using ``pip install psutil``.
'''

import asyncio
Expand Down Expand Up @@ -97,7 +96,7 @@ def __init__(self,
async def start(self):
''' Start the application '''
await self.msvr.start(addr=self.metrics_host, port=self.metrics_port)
logger.debug('Serving prometheus metrics on: %s', self.msvr.url)
logger.debug('Serving prometheus metrics on: %s', self.msvr.metrics_url)

# Schedule a timer to update internal metrics. In a realistic
# application metrics would be updated as needed. In this example
Expand Down
6 changes: 3 additions & 3 deletions examples/metrics-fetcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
TEXT = 'text'
BINARY = 'binary'
header_kinds = {
TEXT: 'text/plain',
TEXT: aioprometheus.formats.TEXT_CONTENT_TYPE, # 'text/plain',
BINARY: aioprometheus.formats.BINARY_CONTENT_TYPE,
}

Expand All @@ -42,13 +42,13 @@ async def fetch_metrics(url: str,
'''
if fmt is None:
# Randomly choose a format to request metrics in.
choice = random.choice(('text', 'binary'))
choice = random.choice((TEXT, BINARY))
else:
assert fmt in header_kinds
choice = fmt

print(f'fetching metrics in {choice} format')
with aiohttp.ClientSession() as session:
async with aiohttp.ClientSession() as session:
async with session.get(url, headers={ACCEPT: header_kinds[choice]}) as resp:
assert resp.status == 200
content = await resp.read()
Expand Down
2 changes: 1 addition & 1 deletion examples/simple-example.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def on_timer_expiry(loop, events_collector):
svr.registry.register(events_collector)

loop.run_until_complete(svr.start(addr="127.0.0.1"))
print('Serving prometheus metrics on: {}'.format(svr.url))
print('Serving prometheus metrics on: {}'.format(svr.metrics_url))

loop.call_later(1.0, on_timer_expiry, loop, events_collector)

Expand Down
2 changes: 1 addition & 1 deletion src/aioprometheus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
from .service import Service


__version__ = "18.01.02"
__version__ = "18.01.03"
2 changes: 1 addition & 1 deletion src/aioprometheus/metricdict.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

# Sometimes python will access by string for example iterating objects, and
# it has this notation
regex = re.compile("\{.*:.*,?\}")
regex = re.compile(r"\{.*:.*,?\}")


# http://stackoverflow.com/questions/3387691/python-how-to-perfectly-override-a-dict
Expand Down
58 changes: 38 additions & 20 deletions src/aioprometheus/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,16 @@ def __init__(self,
self._svc = None # type: aiohttp.web.Application
self._handler = None # type: aiohttp.web.RequestHandlerFactory
self._https = None # type: bool
self._root_url = '/'
self._metrics_url = None # type: str

@property
def url(self) -> str:
''' Return the Prometheus metrics url
def base_url(self) -> str:
''' Return the base service url
:raises: Exception if the server has not been started.
:return: the service URL as a string
:return: the base service URL as a string
'''
if self._svr is None:
raise Exception(
Expand All @@ -74,13 +76,32 @@ def url(self) -> str:
# IPv4 returns 2-tuple, IPv6 returns 4-tuple
host, port, *_ = self._svr.sockets[0].getsockname()
scheme = "http{}".format('s' if self._https else '')
url = "{scheme}://{host}:{port}{metrics_url}".format(
url = "{scheme}://{host}:{port}".format(
scheme=scheme,
host=host if ":" not in host else "[{}]".format(host),
port=port,
metrics_url=self._metrics_url)
port=port)
return url

@property
def root_url(self) -> str:
''' Return the root service url
:raises: Exception if the server has not been started.
:return: the root URL as a string
'''
return f'{self.base_url}{self._root_url}'

@property
def metrics_url(self) -> str:
''' Return the Prometheus metrics url
:raises: Exception if the server has not been started.
:return: the metrics URL as a string
'''
return f'{self.base_url}{self._metrics_url}'

async def start(self,
addr: str = '',
port: int = 0,
Expand Down Expand Up @@ -127,7 +148,7 @@ async def start(self,
self._svc.router.add_route(
GET, metrics_url, self.handle_metrics)
self._svc.router.add_route(
GET, '/', self.handle_root)
GET, self._root_url, self.handle_root)
self._svc.router.add_route(
GET, '/robots.txt', self.handle_robots)
self._handler = self._svc.make_handler()
Expand All @@ -139,7 +160,7 @@ async def start(self,
logger.exception('error creating metrics server')
raise

logger.debug('Prometheus metrics server started on %s', self.url)
logger.debug('Prometheus metrics server started on %s', self.metrics_url)

# register service with service discovery
if discovery_agent:
Expand Down Expand Up @@ -227,26 +248,23 @@ def accepts(self,
return accepts

async def handle_root(self,
request: aiohttp.web.Request) -> aiohttp.web.Response:
request: aiohttp.web.Request) -> aiohttp.web.Response:
''' Handle a request to the / route.
Serves a trivial page with a link to the metrics. Use this if ever
you need to point a health check at your the service.
Serves a trivial page with a link to the metrics. Use this if ever
you need to point a health check at your the service.
'''
return aiohttp.web.Response(
content_type="text/html",
text="<html><body><a href='{}'>metrics</a></body></html>".format(
request.app['metrics_url']
)
)
request.app['metrics_url']))

async def handle_robots(self,
request: aiohttp.web.Request) -> aiohttp.web.Response:
request: aiohttp.web.Request) -> aiohttp.web.Response:
''' Handle a request to /robots.txt
If a robot ever stumbles on this server, discourage it from indexing.
If a robot ever stumbles on this server, discourage it from indexing.
'''
return aiohttp.web.Response(
content_type="text/plain",
text="User-agent: *\nDisallow: /\n"
)
text="User-agent: *\nDisallow: /\n")
27 changes: 24 additions & 3 deletions tests/test_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ async def setUp(self):
self.registry = Registry()
self.server = Service(registry=self.registry)
await self.server.start(addr="127.0.0.1")
self.metrics_url = self.server.url
self.metrics_url = self.server.metrics_url
self.root_url = self.server.root_url

async def tearDown(self):
await self.server.stop()
Expand All @@ -43,10 +44,16 @@ async def test_invalid_registry(self):
Service(registry=Registry())

def test_fetch_url_before_starting_server(self):
''' check accessing URL property raises expection if not available '''
''' check accessing a URL property raises expection if not available '''
s = Service()

with self.assertRaises(Exception) as cm:
_ = s.root_url
self.assertIn(
'No URL available, Prometheus metrics server is not running', str(cm.exception))

with self.assertRaises(Exception) as cm:
_ = s.url
_ = s.metrics_url
self.assertIn(
'No URL available, Prometheus metrics server is not running', str(cm.exception))

Expand Down Expand Up @@ -436,3 +443,17 @@ async def test_no_accept_header(self):
# but with no value set. I have not worked out how to do this
# yet as aiohttp expects headers to be a dict and a value of None
# is not permitted.

async def test_root_route(self):
''' check root route returns content '''
async with aiohttp.ClientSession() as session:
async with session.get(self.root_url) as resp:
self.assertEqual(resp.status, 200)
self.assertIn("text/html", resp.headers.get(CONTENT_TYPE))

async def test_robots_route(self):
''' check robots route returns content '''
async with aiohttp.ClientSession() as session:
async with session.get(f'{self.root_url}robots.txt') as resp:
self.assertEqual(resp.status, 200)
self.assertIn("text/plain", resp.headers.get(CONTENT_TYPE))
8 changes: 4 additions & 4 deletions tests/test_formats_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ def test_counter_format_with_timestamp(self):

c.set_value(counter_data[0], counter_data[1])

result_regex = """# HELP logged_users_total Logged users in the application
result_regex = r"""# HELP logged_users_total Logged users in the application
# TYPE logged_users_total counter
logged_users_total{country="ch",device="mobile"} 654 \d*(?:.\d*)?$"""

Expand Down Expand Up @@ -380,7 +380,7 @@ def test_gauge_format_with_timestamp(self):

g.set_value(counter_data[0], counter_data[1])

result_regex = """# HELP logged_users_total Logged users in the application
result_regex = r"""# HELP logged_users_total Logged users in the application
# TYPE logged_users_total gauge
logged_users_total{country="ch",device="mobile"} 654 \d*(?:.\d*)?$"""

Expand Down Expand Up @@ -563,7 +563,7 @@ def test_summary_format_timestamp(self):
labels = {'interval': '5s'}
values = [3, 5.2, 13, 4]

result_regex = """# HELP prometheus_target_interval_length_seconds Actual intervals between scrapes.
result_regex = r"""# HELP prometheus_target_interval_length_seconds Actual intervals between scrapes.
# TYPE prometheus_target_interval_length_seconds summary
prometheus_target_interval_length_seconds_count{interval="5s"} 4 \d*(?:.\d*)?
prometheus_target_interval_length_seconds_sum{interval="5s"} 25.2 \d*(?:.\d*)?
Expand Down Expand Up @@ -620,7 +620,7 @@ def test_registry_marshall(self):
registry.register(gauge)
registry.register(summary)

valid_regex = """# HELP counter_test A counter.
valid_regex = r"""# HELP counter_test A counter.
# TYPE counter_test counter
counter_test{c_sample="1",c_subsample="b",type="counter"} 400
counter_test{c_sample="1",type="counter"} 100
Expand Down

0 comments on commit fe5a43d

Please sign in to comment.