From 55a9b4cd49d24ad8aff577eb776dd91a6a0ca43c Mon Sep 17 00:00:00 2001 From: Viktor Adam Date: Wed, 26 Aug 2020 21:18:19 +1000 Subject: [PATCH] Allow exporting default request latencies as summary instead of histogram --- README.md | 1 + prometheus_flask_exporter/__init__.py | 49 ++++++++++++++------- setup.py | 4 +- tests/test_defaults.py | 61 +++++++++++++++++++++++++++ tests/test_group_by.py | 23 ++++++++++ 5 files changed, 121 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 6ffded3..ede320f 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/prometheus_flask_exporter/__init__.py b/prometheus_flask_exporter/__init__.py index f61a917..f806a94 100644 --- a/prometheus_flask_exporter/__init__.py +++ b/prometheus_flask_exporter/__init__.py @@ -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): """ @@ -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 @@ -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__ @@ -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): @@ -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: @@ -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 @@ -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 ' @@ -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( @@ -884,4 +903,4 @@ def _make_response(response): return _make_response -__version__ = '0.15.4' +__version__ = '0.16.0' diff --git a/setup.py b/setup.py index 4130bf9..0fbb3b2 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ 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', @@ -14,7 +14,7 @@ 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', diff --git a/tests/test_defaults.py b/tests/test_defaults.py index ea83951..cf7c17e 100644 --- a/tests/test_defaults.py +++ b/tests/test_defaults.py @@ -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) @@ -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)) diff --git a/tests/test_group_by.py b/tests/test_group_by.py index 6c325ae..67f2912 100644 --- a/tests/test_group_by.py +++ b/tests/test_group_by.py @@ -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('/') + 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')