Elukey has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/396051 )

Change subject: List of fixes:
......................................................................


List of fixes:

* Allow a metric to be emitted by multiple daemons,
  even with different metrics. The use case of 'segment/count'
  is unique since the metric is emitted by both coordinator and
  historical, with different labels.
* Correct the historical's metric labels for segment/count
  and segment/used.
* Change name of druid_(broker|historical)_query_cache_sizebytes_count
  to a more appropriate _query_cache_size_bytes
* No-op variable name changes to be more consistent in the code.
* Prefer absolute import for the collector.py module
* Add a more precise and robust set of tests.

Change-Id: I792dfc34f5fd0185c5ec379e861aa50d28e88869
---
M druid_exporter/collector.py
M druid_exporter/exporter.py
M setup.py
M test/test_collector.py
4 files changed, 352 insertions(+), 64 deletions(-)

Approvals:
  Elukey: Verified; Looks good to me, approved



diff --git a/druid_exporter/collector.py b/druid_exporter/collector.py
index fc4affd..1cf4518 100644
--- a/druid_exporter/collector.py
+++ b/druid_exporter/collector.py
@@ -34,44 +34,62 @@
         # List of supported metrics and their fields of the JSON dictionary
         # sent by a Druid daemon. These fields will be added as labels
         # when returning the available metrics in @collect.
+        # Due to the fact that metric names are not unique (like 
segment/count),
+        # it is necessary to split the data structure by daemon.
         self.supported_metric_names = {
-            # Broker, Historical
-            'query/time': ['dataSource'],
-            'query/bytes': ['dataSource'],
-            'query/cache/total/numEntries': None,
-            'query/cache/total/sizeBytes': None,
-            'query/cache/total/hits': None,
-            'query/cache/total/misses': None,
-            'query/cache/total/evictions': None,
-            'query/cache/total/timeouts': None,
-            'query/cache/total/errors': None,
-            # Historical + Coordinator
-            'segment/count': ['dataSource'],
-            # Historical
-            'segment/max': None,
-            'segment/used': ['tier', 'dataSource'],
-            'segment/scan/pending': None,
-            # Coordinator
-            'segment/assigned/count': ['tier'],
-            'segment/moved/count': ['tier'],
-            'segment/dropped/count': ['tier'],
-            'segment/deleted/count': ['tier'],
-            'segment/unneeded/count': ['tier'],
-            'segment/overShadowed/count': None,
-            'segment/loadQueue/failed': ['server'],
-            'segment/loadQueue/count': ['server'],
-            'segment/dropQueue/count': ['server'],
-            'segment/size': ['dataSource'],
-            'segment/unavailable/count': ['dataSource'],
-            'segment/underReplicated/count': ['tier', 'dataSource'],
-            'ingest/events/thrownAway': ['dataSource'],
-            'ingest/events/unparseable': ['dataSource'],
-            'ingest/events/processed': ['dataSource'],
-            'ingest/rows/output': ['dataSource'],
-            'ingest/persists/count': ['dataSource'],
-            'ingest/persists/failed': ['dataSource'],
-            'ingest/handoff/failed': ['dataSource'],
-            'ingest/handoff/count': ['dataSource'],
+            'broker': {
+                'query/time': ['dataSource'],
+                'query/bytes': ['dataSource'],
+                'query/cache/total/numEntries': None,
+                'query/cache/total/sizeBytes': None,
+                'query/cache/total/hits': None,
+                'query/cache/total/misses': None,
+                'query/cache/total/evictions': None,
+                'query/cache/total/timeouts': None,
+                'query/cache/total/errors': None,
+            },
+            'historical': {
+                'query/time': ['dataSource'],
+                'query/bytes': ['dataSource'],
+                'query/cache/total/numEntries': None,
+                'query/cache/total/sizeBytes': None,
+                'query/cache/total/hits': None,
+                'query/cache/total/misses': None,
+                'query/cache/total/evictions': None,
+                'query/cache/total/timeouts': None,
+                'query/cache/total/errors': None,
+                'segment/count': ['tier', 'dataSource'],
+                'segment/max': None,
+                'segment/used': ['tier', 'dataSource'],
+                'segment/scan/pending': None,
+            },
+            'coordinator': {
+                'segment/count': ['dataSource'],
+                'segment/assigned/count': ['tier'],
+                'segment/moved/count': ['tier'],
+                'segment/dropped/count': ['tier'],
+                'segment/deleted/count': ['tier'],
+                'segment/unneeded/count': ['tier'],
+                'segment/overShadowed/count': None,
+                'segment/loadQueue/failed': ['server'],
+                'segment/loadQueue/count': ['server'],
+                'segment/dropQueue/count': ['server'],
+                'segment/size': ['dataSource'],
+                'segment/unavailable/count': ['dataSource'],
+                'segment/underReplicated/count': ['tier', 'dataSource'],
+            },
+            'peon': {
+                'query/time': ['dataSource'],
+                'query/bytes': ['dataSource'],
+                'ingest/events/thrownAway': ['dataSource'],
+                'ingest/events/unparseable': ['dataSource'],
+                'ingest/events/processed': ['dataSource'],
+                'ingest/rows/output': ['dataSource'],
+                'ingest/persists/count': ['dataSource'],
+                'ingest/persists/failed': ['dataSource'],
+                'ingest/handoff/failed': ['dataSource'],
+                'ingest/handoff/count': ['dataSource'],
+            },
         }
 
         # Buckets used when storing histogram metrics.
@@ -190,7 +208,7 @@
                'druid_' + daemon + '_query_cache_numentries_count',
                'Number of cache entries.'),
             'query/cache/total/sizeBytes': GaugeMetricFamily(
-               'druid_' + daemon + '_query_cache_sizebytes_count',
+               'druid_' + daemon + '_query_cache_size_bytes',
                'Size in bytes of cache entries.'),
             'query/cache/total/hits': GaugeMetricFamily(
                'druid_' + daemon + '_query_cache_hits_count',
@@ -217,11 +235,11 @@
             'segment/count': GaugeMetricFamily(
                'druid_historical_segment_count',
                'Number of served segments.',
-               labels=['datasource']),
+               labels=['tier', 'datasource']),
             'segment/used': GaugeMetricFamily(
                'druid_historical_segment_used_bytes',
                'Bytes used for served segments.',
-               labels=['datasource']),
+               labels=['tier', 'datasource']),
             'segment/scan/pending': GaugeMetricFamily(
                'druid_historical_segment_scan_pending',
                'Number of segments in queue waiting to be scanned.'),
@@ -299,17 +317,17 @@
             The algorithm is generic enough to support all metrics handled by
             self.counters without caring about the number of labels needed.
         """
-        daemon_name = DruidCollector.sanitize_field(str(datapoint['service']))
+        daemon = DruidCollector.sanitize_field(str(datapoint['service']))
         metric_name = str(datapoint['metric'])
         metric_value = float(datapoint['value'])
 
         metrics_storage = self.counters[metric_name]
-        metric_labels = self.supported_metric_names[metric_name]
+        metric_labels = self.supported_metric_names[daemon][metric_name]
 
-        metrics_storage.setdefault(daemon_name, {})
+        metrics_storage.setdefault(daemon, {})
 
         if metric_labels:
-            metrics_storage_cursor = metrics_storage[daemon_name]
+            metrics_storage_cursor = metrics_storage[daemon]
             for label in metric_labels:
                 label_value = str(datapoint[label])
                 if metric_labels[-1] != label:
@@ -318,7 +336,7 @@
                 else:
                     metrics_storage_cursor[label_value] = metric_value
         else:
-            metrics_storage[daemon_name] = metric_value
+            metrics_storage[daemon] = metric_value
 
         log.debug("The datapoint {} modified the counters dictionary to: \n{}"
                   .format(datapoint, self.counters))
@@ -336,17 +354,17 @@
             self.counters = {'query/time': {'broker':
                 {'test': {'10': 1, '100': 1, etc.., 'sum': 10}}}}}
         """
-        daemon_name = DruidCollector.sanitize_field(str(datapoint['service']))
+        daemon = DruidCollector.sanitize_field(str(datapoint['service']))
         metric_name = str(datapoint['metric'])
         metric_value = float(datapoint['value'])
         datasource = str(datapoint['dataSource'])
 
-        self.histograms.setdefault(metric_name, {daemon_name: {datasource: 
{}}})
-        self.histograms[metric_name].setdefault(daemon_name, {datasource: {}})
-        self.histograms[metric_name][daemon_name].setdefault(datasource, {})
+        self.histograms.setdefault(metric_name, {daemon: {datasource: {}}})
+        self.histograms[metric_name].setdefault(daemon, {datasource: {}})
+        self.histograms[metric_name][daemon].setdefault(datasource, {})
 
         for bucket in self.metric_buckets[metric_name]:
-            stored_buckets = 
self.histograms[metric_name][daemon_name][datasource]
+            stored_buckets = self.histograms[metric_name][daemon][datasource]
             if bucket not in stored_buckets:
                 stored_buckets[bucket] = 0
             if bucket != 'sum' and metric_value <= float(bucket):
@@ -381,7 +399,7 @@
 
             for metric in cache_metrics:
                 if not self.counters[metric] or daemon not in 
self.counters[metric]:
-                    if not self.supported_metric_names[metric]:
+                    if not self.supported_metric_names[daemon][metric]:
                         cache_metrics[metric].add_metric([], float('nan'))
                     else:
                         continue
@@ -397,12 +415,12 @@
                                 ('peon', realtime_metrics)]:
             for metric in metrics:
                 if not self.counters[metric] or daemon not in 
self.counters[metric]:
-                    if not self.supported_metric_names[metric]:
+                    if not self.supported_metric_names[daemon][metric]:
                         metrics[metric].add_metric([], float('nan'))
                     else:
                         continue
                 else:
-                    labels = self.supported_metric_names[metric]
+                    labels = self.supported_metric_names[daemon][metric]
                     if not labels:
                         metrics[metric].add_metric(
                             [], self.counters[metric][daemon])
@@ -425,8 +443,16 @@
         yield registered
 
     def register_datapoint(self, datapoint):
+        if (datapoint['feed'] != 'metrics'):
+            log.debug("The following feed does not contain a datapoint, "
+                      "dropping it: {}"
+                      .format(datapoint))
+            return
+
+        daemon = DruidCollector.sanitize_field(str(datapoint['service']))
         if (datapoint['feed'] != 'metrics' or
-                datapoint['metric'] not in self.supported_metric_names):
+                daemon not in self.supported_metric_names or
+                datapoint['metric'] not in 
self.supported_metric_names[daemon]):
             log.debug("The following datapoint is not supported, either "
                       "because the 'feed' field is not 'metrics' or "
                       "the metric itself is not supported: {}"
diff --git a/druid_exporter/exporter.py b/druid_exporter/exporter.py
index a269190..400005b 100644
--- a/druid_exporter/exporter.py
+++ b/druid_exporter/exporter.py
@@ -19,9 +19,9 @@
 import logging
 import sys
 
+from druid_exporter import collector
 from prometheus_client import generate_latest, make_wsgi_app, REGISTRY
 from wsgiref.simple_server import make_server
-from . import collector
 
 log = logging.getLogger(__name__)
 
diff --git a/setup.py b/setup.py
index 9340668..30434ea 100644
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,7 @@
 from setuptools import setup
 
 setup(name='druid_exporter',
-      version='0.5',
+      version='0.6',
       description='Prometheus exporter for Druid',
       url='https://github.com/wikimedia/operations-software-druid_exporter',
       author='Luca Toscano',
diff --git a/test/test_collector.py b/test/test_collector.py
index 8a60d66..f504ab5 100644
--- a/test/test_collector.py
+++ b/test/test_collector.py
@@ -23,19 +23,212 @@
 
     def setUp(self):
         self.collector = DruidCollector()
+
+        # List of metric names as emitted by Druid, coupled with their
+        # Prometheus metric name and labels.
+        self.supported_metric_names = {
+            'broker': {
+                'query/time': {
+                    'metric_name': 'druid_broker_query_time_ms',
+                    'labels': ['dataSource']
+                },
+                'query/bytes': {
+                    'metric_name': 'druid_broker_query_bytes',
+                    'labels': ['dataSource']
+                },
+                'query/cache/total/numEntries': {
+                    'metric_name': 'druid_broker_query_cache_numentries_count',
+                    'labels': None
+                },
+                'query/cache/total/sizeBytes': {
+                    'metric_name': 'druid_broker_query_cache_size_bytes',
+                    'labels': None
+                },
+                'query/cache/total/hits': {
+                    'metric_name': 'druid_broker_query_cache_hits_count',
+                    'labels': None
+                },
+                'query/cache/total/misses': {
+                    'metric_name':'druid_broker_query_cache_hits_count',
+                    'labels': None
+                },
+                'query/cache/total/evictions': {
+                    'metric_name':'druid_broker_query_cache_evictions_count',
+                    'labels': None
+                },
+                'query/cache/total/timeouts': {
+                    'metric_name':'druid_broker_query_cache_timeouts_count',
+                    'labels': None
+                },
+                'query/cache/total/errors': {
+                    'metric_name':'druid_broker_query_cache_errors_count',
+                    'labels': None
+                }
+            },
+            'historical': {
+                'query/time': {
+                    'metric_name': 'druid_historical_query_time_ms',
+                    'labels': ['dataSource']
+                },
+                'query/bytes': {
+                    'metric_name': 'druid_historical_query_bytes',
+                    'labels': ['dataSource']
+                },
+                'query/cache/total/numEntries': {
+                    'metric_name': 
'druid_historical_query_cache_numentries_count',
+                    'labels': None
+                },
+                'query/cache/total/sizeBytes': {
+                    'metric_name': 'druid_historical_query_cache_size_bytes',
+                    'labels': None
+                },
+                'query/cache/total/hits': {
+                    'metric_name': 'druid_historical_query_cache_hits_count',
+                    'labels': None
+                },
+                'query/cache/total/misses': {
+                    'metric_name':'druid_historical_query_cache_hits_count',
+                    'labels': None
+                },
+                'query/cache/total/evictions': {
+                    
'metric_name':'druid_historical_query_cache_evictions_count',
+                    'labels': None
+                },
+                'query/cache/total/timeouts': {
+                    
'metric_name':'druid_historical_query_cache_timeouts_count',
+                    'labels': None
+                },
+                'query/cache/total/errors': {
+                    'metric_name':'druid_historical_query_cache_errors_count',
+                    'labels': None
+                },
+                'segment/count': {
+                    'metric_name': 'druid_historical_segment_count',
+                    'labels': ['tier', 'dataSource']
+                },
+                'segment/max': {
+                    'metric_name': 'druid_historical_max_segment_bytes',
+                    'labels': None
+                },
+                'segment/used': {
+                    'metric_name': 'druid_historical_segment_used_bytes',
+                    'labels': ['tier', 'dataSource']
+                },
+                'segment/scan/pending': {
+                    'metric_name': 'druid_historical_segment_scan_pending',
+                    'labels': None
+                }
+            },
+            'coordinator': {
+                'segment/assigned/count': {
+                    'metric_name': 'druid_coordinator_segment_assigned_count',
+                    'labels': ['tier'],
+                },
+                'segment/moved/count': {
+                    'metric_name': 'druid_coordinator_segment_moved_count',
+                    'labels': ['tier']
+                },
+                'segment/dropped/count': {
+                    'metric_name': 'druid_coordinator_segment_dropped_count',
+                    'labels': ['tier']
+                },
+                'segment/deleted/count': {
+                    'metric_name': 'druid_coordinator_segment_deleted_count',
+                    'labels': ['tier']
+                },
+                'segment/unneeded/count': {
+                    'metric_name': 'druid_coordinator_segment_unneeded_count',
+                    'labels': ['tier']
+                },
+                'segment/overShadowed/count': {
+                    'metric_name': 
'druid_coordinator_segment_overshadowed_count',
+                    'labels': None
+                },
+                'segment/loadQueue/failed': {
+                    'metric_name': 
'druid_coordinator_segment_loadqueue_failed_count',
+                    'labels': ['server']
+                },
+                'segment/loadQueue/count': {
+                    'metric_name': 'druid_coordinator_segment_loadqueue_count',
+                    'labels': ['server']
+                },
+                'segment/dropQueue/count': {
+                    'metric_name': 'druid_coordinator_segment_dropqueue_count',
+                    'labels': ['server']
+                },
+                'segment/size': {
+                    'metric_name': 'druid_coordinator_segment_size_bytes',
+                    'labels': ['dataSource']
+                },
+                'segment/count': {
+                    'metric_name': 'druid_coordinator_segment_count',
+                    'labels': ['dataSource']
+                },
+                'segment/unavailable/count': {
+                    'metric_name': 
'druid_coordinator_segment_unavailable_count',
+                    'labels': ['dataSource']
+                },
+                'segment/underReplicated/count': {
+                    'metric_name': 
'druid_coordinator_segment_under_replicated_count',
+                    'labels': ['tier', 'dataSource']
+                }
+            },
+            'peon': {
+                'query/time': {
+                    'metric_name': 'druid_peon_query_time_ms',
+                    'labels': ['dataSource']
+                },
+                'query/bytes': {
+                    'metric_name': 'druid_peon_query_bytes',
+                    'labels': ['dataSource']
+                },
+                'ingest/events/thrownAway': {
+                    'metric_name': 
'druid_realtime_ingest_events_thrown_away_count',
+                    'labels': ['dataSource'],
+                },
+                'ingest/events/unparseable': {
+                    'metric_name': 
'druid_realtime_ingest_events_unparseable_count',
+                    'labels': ['dataSource'],
+                },
+                'ingest/events/processed': {
+                    'metric_name': 
'druid_realtime_ingest_events_processed_count',
+                    'labels': ['dataSource'],
+                },
+                'ingest/rows/output': {
+                    'metric_name': 'druid_realtime_ingest_rows_output_count',
+                    'labels': ['dataSource'],
+                },
+                'ingest/persists/count': {
+                    'metric_name': 'druid_realtime_ingest_persists_count',
+                    'labels': ['dataSource'],
+                },
+                'ingest/persists/failed': {
+                    'metric_name': 
'druid_realtime_ingest_persists_failed_count',
+                    'labels': ['dataSource'],
+                },
+                'ingest/handoff/failed': {
+                    'metric_name': 
'druid_realtime_ingest_handoff_failed_count',
+                    'labels': ['dataSource'],
+                },
+                'ingest/handoff/count': {
+                    'metric_name': 'druid_realtime_ingest_handoff_count',
+                    'labels': ['dataSource'],
+                }
+            }
+        }
         self.metrics_without_labels = [
             'druid_historical_segment_scan_pending',
             'druid_historical_max_segment_bytes',
             'druid_coordinator_segment_overshadowed_count',
             'druid_broker_query_cache_numentries_count',
-            'druid_broker_query_cache_sizebytes_count',
+            'druid_broker_query_cache_size_bytes',
             'druid_broker_query_cache_hits_count',
             'druid_broker_query_cache_misses_count',
             'druid_broker_query_cache_evictions_count',
             'druid_broker_query_cache_timeouts_count',
             'druid_broker_query_cache_errors_count',
             'druid_historical_query_cache_numentries_count',
-            'druid_historical_query_cache_sizebytes_count',
+            'druid_historical_query_cache_size_bytes',
             'druid_historical_query_cache_hits_count',
             'druid_historical_query_cache_misses_count',
             'druid_historical_query_cache_evictions_count',
@@ -122,16 +315,16 @@
         # Third datapoint for the same metric as used in the first test, should
         # add a key to the already existent dictionary.
         datapoint = {'feed': 'metrics', 'service': 'druid/historical', 
'dataSource': 'test2',
-                     'metric': 'segment/used', 'tier': '_default_tier', 
'value': 543}
+                     'metric': 'segment/count', 'tier': '_default_tier', 
'value': 543}
         self.collector.register_datapoint(datapoint)
-        
expected_result['segment/used']['historical']['_default_tier']['test2'] = 543.0
+        expected_result['segment/count'] = {'historical': {'_default_tier': 
{'test2': 543.0}}}
         self.assertEqual(self.collector.counters, expected_result)
 
-        # Fourth datapoint for an already seen metric but differen broker
-        datapoint = {'feed': 'metrics', 'service': 'druid/broker', 
'dataSource': 'test',
-                     'metric': 'segment/used', 'tier': '_default_tier', 
'value': 111}
+        # Fourth datapoint for an already seen metric but different daemon
+        datapoint = {'feed': 'metrics', 'service': 'druid/coordinator', 
'dataSource': 'test',
+                     'metric': 'segment/count', 'value': 111}
         self.collector.register_datapoint(datapoint)
-        expected_result['segment/used']['broker'] = {'_default_tier': {'test': 
111.0}}
+        expected_result['segment/count']['coordinator'] = {'test': 111.0}
         self.assertEqual(self.collector.counters, expected_result)
 
         # Fifth datapoint should override a pre-existent value
@@ -303,6 +496,12 @@
              "service": "druid/coordinator", "host": "druid1001.eqiad.wmnet: 
8081",
              "metric": "segment/count", "value": 56, "dataSource": "netflow"},
 
+            {"feed": "metrics", "timestamp": "2017-12-07T09:55:04.937Z",
+             "service": "druid/historical", "host": 
"druid1001.eqiad.wmnet:8083",
+             "metric": "segment/used", "value": 3252671142,
+             "dataSource": "banner_activity_minutely",
+             "priority": "0", "tier": "_default_tier"},
+
             {"feed": "metrics", "timestamp": "2017-11-14T13:08:20.820Z",
              "service": "druid/historical", "host": 
"druid1001.eqiad.wmnet:8083",
              "metric": "segment/max", "value": 2748779069440},
@@ -440,13 +639,19 @@
         for datapoint in datapoints:
             self.collector.register_datapoint(datapoint)
 
+        # Running it twice should not produce more metrics
+        for datapoint in datapoints:
+            self.collector.register_datapoint(datapoint)
+
         collected_metrics = 0
+        prometheus_metric_samples = []
         for metric in self.collector.collect():
             # Metrics should not be returned if no sample is associated
             # (not even a 'nan')
             self.assertNotEqual(metric.samples, [])
             if metric.samples and metric.samples[0][0].startswith('druid_'):
                 collected_metrics += 1
+                prometheus_metric_samples.append(metric.samples)
 
         # Number of metrics pushed using register_datapoint plus the ones
         # generated by the exporter for bookeeping,
@@ -454,6 +659,63 @@
         expected_druid_metrics_len = len(datapoints) + 1
         self.assertEqual(collected_metrics, expected_druid_metrics_len)
 
+        for datapoint in datapoints:
+            metric = datapoint['metric']
+            daemon = datapoint['service'].split('/')[1]
+            prometheus_metric_name = 
self.supported_metric_names[daemon][metric]['metric_name']
+            prometheus_metric_labels = 
self.supported_metric_names[daemon][metric]['labels']
+
+            # The prometheus metric samples are in two forms:
+            # 1) histograms:
+            # [('druid_broker_query_time_ms_bucket', {'datasource': 
'NavigationTiming', 'le': '10'}, 2),
+            #  [...]
+            #  ('druid_broker_query_time_ms_bucket', {'datasource': 
'NavigationTiming', 'le': 'inf'}, 2),
+            #  ('druid_broker_query_time_ms_count', {'datasource': 
'NavigationTiming'}, 2),
+            #  ('druid_broker_query_time_ms_sum', {'datasource': 
'NavigationTiming'}, 20.0)]
+            #
+            # 2) counter/gauge
+            #    [('druid_coordinator_segment_unneeded_count', {'tier': 
'_default_tier'}, 0.0)]
+            #
+            # The idea of the following test is to make sure that after sending
+            # one data point for each metric, the sample contains the 
information
+            # needed.
+            #
+            if 'query' not in metric:
+                for sample in prometheus_metric_samples:
+                    if prometheus_metric_name == sample[0][0]:
+                        if prometheus_metric_labels:
+                            for label in prometheus_metric_labels:
+                                self.assertTrue(label.lower() in sample[0][1])
+                        else:
+                            self.assertTrue(sample[0][1] == {})
+                        break
+            else:
+                for sample in [s for s in prometheus_metric_samples if len(s) 
== 8]:
+                    if metric in sample[0][0]:
+                        bucket_counter = 0
+                        sum_counter = 0
+                        count_counter = 0
+                        for s in sample:
+                            if s[0] == metric + "_sum":
+                                sum_counter += 1
+                            elif s[0] == metric + "_count":
+                                count_counter += 1
+                            elif s[0] == metric + "_bucket":
+                                bucket_counter += 1
+                            else:
+                                raise RuntimeError(
+                                    'Histogram sample not supported: {}'
+                                    .format(s))
+                        assertEqual(sum_counter, 1)
+                        assertEqual(count_counter, 1)
+                        assertEqual(bucket_counter, 6)
+                        break
+                else:
+                    RuntimeError(
+                        'The metric {} does not have a valid sample!'
+                        .format(metric))
+
+
     def test_register_datapoints_count(self):
         datapoints = [
 

-- 
To view, visit https://gerrit.wikimedia.org/r/396051
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I792dfc34f5fd0185c5ec379e861aa50d28e88869
Gerrit-PatchSet: 12
Gerrit-Project: operations/software/druid_exporter
Gerrit-Branch: master
Gerrit-Owner: Elukey <[email protected]>
Gerrit-Reviewer: Elukey <[email protected]>
Gerrit-Reviewer: Joal <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to