Skip to content

Commit

Permalink
Allow exporting default request latencies as summary instead of histo…
Browse files Browse the repository at this point in the history
…gram
  • Loading branch information
rycus86 committed Aug 26, 2020
1 parent fdaf3f5 commit 55a9b4c
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 17 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -73,6 +73,7 @@ The following metrics are exported by default

The prefix for the default metrics can be controlled by the `defaults_prefix` parameter.
If you don't want to use any prefix, pass the `prometheus_flask_exporter.NO_PREFIX` value in.
The buckets on the default request latency histogram can be changed by the `buckets` parameter, and if using a summary for them is more appropriate for your use case, then use the `default_latency_as_histogram=False` parameter.

To register your own *default* metrics that will track all registered
Flask view functions, use the `register_default` function.
Expand Down
49 changes: 34 additions & 15 deletions prometheus_flask_exporter/__init__.py
Expand Up @@ -105,6 +105,7 @@ def echo_status(status):
def __init__(self, app, path='/metrics',
export_defaults=True, defaults_prefix='flask',
group_by='path', buckets=None,
default_latency_as_histogram=True,
default_labels=None, response_converter=None,
excluded_paths=None, registry=None, **kwargs):
"""
Expand All @@ -123,6 +124,8 @@ def __init__(self, app, path='/metrics',
(defaults to `path`)
:param buckets: the time buckets for request latencies
(will use the default when `None`)
:param default_latency_as_histogram: export request latencies
as a Histogram (defaults), otherwise use a Summary
:param default_labels: default labels to attach to each of the
metrics exposed by this `PrometheusMetrics` instance
:param response_converter: a function that converts the captured
Expand All @@ -137,6 +140,7 @@ def __init__(self, app, path='/metrics',
self._export_defaults = export_defaults
self._defaults_prefix = defaults_prefix or 'flask'
self._default_labels = default_labels or {}
self._default_latency_as_histogram = default_latency_as_histogram
self._response_converter = response_converter or make_response
self.buckets = buckets
self.version = __version__
Expand Down Expand Up @@ -224,8 +228,9 @@ def init_app(self, app):

if self._export_defaults:
self.export_defaults(
self.buckets, self.group_by,
self._defaults_prefix, app
buckets=self.buckets, group_by=self.group_by,
latency_as_histogram=self._default_latency_as_histogram,
prefix=self._defaults_prefix, app=app
)

def register_endpoint(self, path, app=None):
Expand Down Expand Up @@ -289,6 +294,7 @@ def run_app():
thread.start()

def export_defaults(self, buckets=None, group_by='path',
latency_as_histogram=True,
prefix='flask', app=None, **kwargs):
"""
Export the default metrics:
Expand All @@ -301,6 +307,9 @@ def export_defaults(self, buckets=None, group_by='path',
:param group_by: group default HTTP metrics by
this request property, like `path`, `endpoint`, `rule`, etc.
(defaults to `path`)
:param latency_as_histogram: export request latencies
as a Histogram, otherwise use a Summary instead
(defaults to `True` to export as a Histogram)
:param prefix: prefix to start the default metrics names with
or `NO_PREFIX` (to skip prefix)
:param app: the Flask application
Expand All @@ -312,11 +321,6 @@ def export_defaults(self, buckets=None, group_by='path',
if not prefix:
prefix = self._defaults_prefix or 'flask'

# use the default buckets from prometheus_client if not given here
buckets_as_kwargs = {}
if buckets is not None:
buckets_as_kwargs['buckets'] = buckets

if kwargs.get('group_by_endpoint') is True:
warnings.warn(
'The `group_by_endpoint` argument of '
Expand Down Expand Up @@ -354,13 +358,28 @@ def export_defaults(self, buckets=None, group_by='path',

labels = self._get_combined_labels(None)

request_duration_metric = Histogram(
'%shttp_request_duration_seconds' % prefix,
'Flask HTTP request duration in seconds',
('method', duration_group_name, 'status') + labels.keys(),
registry=self.registry,
**buckets_as_kwargs
)
if latency_as_histogram:
# use the default buckets from prometheus_client if not given here
buckets_as_kwargs = {}
if buckets is not None:
buckets_as_kwargs['buckets'] = buckets

request_duration_metric = Histogram(
'%shttp_request_duration_seconds' % prefix,
'Flask HTTP request duration in seconds',
('method', duration_group_name, 'status') + labels.keys(),
registry=self.registry,
**buckets_as_kwargs
)

else:
# export as Summary instead
request_duration_metric = Summary(
'%shttp_request_duration_seconds' % prefix,
'Flask HTTP request duration in seconds',
('method', duration_group_name, 'status') + labels.keys(),
registry=self.registry
)

counter_labels = ('method', 'status') + labels.keys()
request_total_metric = Counter(
Expand Down Expand Up @@ -884,4 +903,4 @@ def _make_response(response):
return _make_response


__version__ = '0.15.4'
__version__ = '0.16.0'
4 changes: 2 additions & 2 deletions setup.py
Expand Up @@ -6,15 +6,15 @@
setup(
name='prometheus_flask_exporter',
packages=['prometheus_flask_exporter'],
version='0.15.4',
version='0.16.0',
description='Prometheus metrics exporter for Flask',
long_description=long_description,
long_description_content_type='text/markdown',
license='MIT',
author='Viktor Adam',
author_email='rycus86@gmail.com',
url='https://github.com/rycus86/prometheus_flask_exporter',
download_url='https://github.com/rycus86/prometheus_flask_exporter/archive/0.15.4.tar.gz',
download_url='https://github.com/rycus86/prometheus_flask_exporter/archive/0.16.0.tar.gz',
keywords=['prometheus', 'flask', 'monitoring', 'exporter'],
classifiers=[
'Development Status :: 4 - Beta',
Expand Down
61 changes: 61 additions & 0 deletions tests/test_defaults.py
Expand Up @@ -385,6 +385,48 @@ def test():
('method', 'GET'), ('path', '/test'),
)

def test_export_latencies_as_summary(self):
metrics = self.metrics(export_defaults=False)

@self.app.route('/test')
def test():
return 'OK'

self.client.get('/test')
self.client.get('/test')

self.assertAbsent(
'flask_http_request_duration_seconds_sum',
('method', 'GET'), ('path', '/test'), ('status', 200)
)
self.assertAbsent(
'flask_http_request_duration_seconds_count',
('method', 'GET'), ('path', '/test'), ('status', 200)
)
self.assertAbsent(
'flask_http_request_duration_seconds_bucket',
('le', '+Inf'), ('method', 'GET'), ('path', '/test'), ('status', 200)
)

metrics.export_defaults(latency_as_histogram=False)

self.client.get('/test')
self.client.get('/test')
self.client.get('/test')

self.assertMetric(
'flask_http_request_duration_seconds_sum', '[0-9.e-]+',
('method', 'GET'), ('path', '/test'), ('status', 200)
)
self.assertMetric(
'flask_http_request_duration_seconds_count', '3.0',
('method', 'GET'), ('path', '/test'), ('status', 200)
)
self.assertAbsent(
'flask_http_request_duration_seconds_bucket',
('le', '+Inf'), ('method', 'GET'), ('path', '/test'), ('status', 200)
)

def test_non_automatic_endpoint_registration(self):
metrics = self.metrics(path=None)

Expand All @@ -407,6 +449,25 @@ def test():
endpoint='/manual/metrics'
)

def test_latencies_as_summary(self):
self.metrics(default_latency_as_histogram=False)

@self.app.route('/test')
def test():
return 'OK'

self.client.get('/test')
self.client.get('/test')

self.assertMetric(
'flask_http_request_duration_seconds_sum', '[0-9.e-]+',
('method', 'GET'), ('path', '/test'), ('status', 200)
)
self.assertMetric(
'flask_http_request_duration_seconds_count', '2.0',
('method', 'GET'), ('path', '/test'), ('status', 200)
)

def test_custom_buckets(self):
self.metrics(buckets=(0.2, 2, 4))

Expand Down
23 changes: 23 additions & 0 deletions tests/test_group_by.py
Expand Up @@ -29,6 +29,29 @@ def a_test_endpoint(url):
endpoint='/metrics'
)

def test_group_by_path_default_with_summaries(self):
self.metrics(default_latency_as_histogram=False)

@self.app.route('/<url>')
def a_test_endpoint(url):
return url + ' is OK'

self.client.get('/default1')
self.client.get('/default2')
self.client.get('/default3')

for path in ('/default1', '/default2', '/default3'):
self.assertMetric(
'flask_http_request_duration_seconds_sum', '[0-9.e-]+',
('path', path), ('status', 200), ('method', 'GET'),
endpoint='/metrics'
)
self.assertMetric(
'flask_http_request_duration_seconds_count', '1.0',
('path', path), ('status', 200), ('method', 'GET'),
endpoint='/metrics'
)

def test_group_by_path(self):
self.metrics(group_by='path')

Expand Down

0 comments on commit 55a9b4c

Please sign in to comment.