Skip to content

Commit 7a90559

Browse files
committed
Add support for configurable histogram percentiles
1 parent 46b48d4 commit 7a90559

File tree

4 files changed

+88
-3
lines changed

4 files changed

+88
-3
lines changed

aggregator.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ def flush(self, timestamp, interval):
223223

224224
DEFAULT_HISTOGRAM_AGGREGATES = ['max', 'median', 'avg', 'count']
225225
DEFAULT_HISTOGRAM_PERCENTILES = [0.95]
226+
DEFAULT_HISTOGRAM_NAME_FORMAT = '%s.%spercentile'
226227

227228
class Histogram(Metric):
228229
""" A metric to track the distribution of a set of values. """
@@ -238,6 +239,9 @@ def __init__(self, formatter, name, tags, hostname, device_name, extra_config=No
238239
self.percentiles = extra_config['percentiles'] if\
239240
extra_config is not None and extra_config.get('percentiles') is not None\
240241
else DEFAULT_HISTOGRAM_PERCENTILES
242+
self.name_format = extra_config['name_format'] if\
243+
extra_config is not None and extra_config.get('name_format') is not None\
244+
else DEFAULT_HISTOGRAM_NAME_FORMAT
241245
self.tags = tags
242246
self.hostname = hostname
243247
self.device_name = device_name
@@ -287,7 +291,11 @@ def flush(self, ts, interval):
287291

288292
for p in self.percentiles:
289293
val = self.samples[int(round(p * length - 1))]
290-
name = '%s.%spercentile' % (self.name, int(p * 100))
294+
try:
295+
name = self.name_format % (self.name, int(p * 100))
296+
except TypeError:
297+
log.warn("Invalid histogram name format %s, defaulting to '%s'" % (self.name_format, DEFAULT_HISTOGRAM_NAME_FORMAT))
298+
name = DEFAULT_HISTOGRAM_NAME_FORMAT % (self.name, int(p * 100))
291299
metrics.append(self.formatter(
292300
hostname=self.hostname,
293301
tags=self.tags,
@@ -401,6 +409,7 @@ class Aggregator(object):
401409
def __init__(self, hostname, interval=1.0, expiry_seconds=300,
402410
formatter=None, recent_point_threshold=None,
403411
histogram_aggregates=None, histogram_percentiles=None,
412+
histogram_name_format=None,
404413
utf8_decoding=False):
405414
self.events = []
406415
self.service_checks = []
@@ -421,7 +430,8 @@ def __init__(self, hostname, interval=1.0, expiry_seconds=300,
421430
self.metric_config = {
422431
Histogram: {
423432
'aggregates': histogram_aggregates,
424-
'percentiles': histogram_percentiles
433+
'percentiles': histogram_percentiles,
434+
'name_format': histogram_name_format
425435
}
426436
}
427437

@@ -723,6 +733,7 @@ class MetricsBucketAggregator(Aggregator):
723733
def __init__(self, hostname, interval=1.0, expiry_seconds=300,
724734
formatter=None, recent_point_threshold=None,
725735
histogram_aggregates=None, histogram_percentiles=None,
736+
histogram_name_format=None,
726737
utf8_decoding=False):
727738
super(MetricsBucketAggregator, self).__init__(
728739
hostname,
@@ -732,6 +743,7 @@ def __init__(self, hostname, interval=1.0, expiry_seconds=300,
732743
recent_point_threshold,
733744
histogram_aggregates,
734745
histogram_percentiles,
746+
histogram_name_format,
735747
utf8_decoding
736748
)
737749
self.metric_by_bucket = {}
@@ -863,6 +875,7 @@ class MetricsAggregator(Aggregator):
863875
def __init__(self, hostname, interval=1.0, expiry_seconds=300,
864876
formatter=None, recent_point_threshold=None,
865877
histogram_aggregates=None, histogram_percentiles=None,
878+
histogram_name_format=None,
866879
utf8_decoding=False):
867880
super(MetricsAggregator, self).__init__(
868881
hostname,
@@ -872,6 +885,7 @@ def __init__(self, hostname, interval=1.0, expiry_seconds=300,
872885
recent_point_threshold,
873886
histogram_aggregates,
874887
histogram_percentiles,
888+
histogram_name_format,
875889
utf8_decoding
876890
)
877891
self.metrics = {}

config.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,6 @@ def get_histogram_percentiles(configstr=None):
317317

318318
return result
319319

320-
321320
def clean_dd_url(url):
322321
url = url.strip()
323322
if not url.startswith('http'):
@@ -477,6 +476,9 @@ def get_config(parse_args=True, cfg_path=None, options=None):
477476
if config.has_option('Main', 'histogram_percentiles'):
478477
agentConfig['histogram_percentiles'] = get_histogram_percentiles(config.get('Main', 'histogram_percentiles'))
479478

479+
if config.has_option('Main', 'histogram_name_format'):
480+
agentConfig['histogram_name_format'] = config.get('Main', 'histogram_name_format')
481+
480482
# Disable Watchdog (optionally)
481483
if config.has_option('Main', 'watchdog'):
482484
if config.get('Main', 'watchdog').lower() in ('no', 'false'):

dogstatsd.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,7 @@ def init(config_path=None, use_watchdog=False, use_forwarder=False, args=None):
503503
formatter=get_formatter(c),
504504
histogram_aggregates=c.get('histogram_aggregates'),
505505
histogram_percentiles=c.get('histogram_percentiles'),
506+
histogram_name_format=c.get('histogram_name_format'),
506507
utf8_decoding=c['utf8_decoding']
507508
)
508509

tests/core/test_histogram.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,74 @@ def test_custom_single_percentile(self):
5757

5858
self.assertEquals(value_by_type['40percentile'], 7, value_by_type)
5959

60+
def test_custom_single_percentile_withCustomName(self):
61+
configstr = '0.40'
62+
configname = '%s.p%s'
63+
64+
stats = MetricsAggregator(
65+
'myhost',
66+
histogram_percentiles=get_histogram_percentiles(configstr),
67+
histogram_name_format=configname
68+
)
69+
70+
self.assertEquals(
71+
stats.metric_config[Histogram]['percentiles'],
72+
[0.40],
73+
stats.metric_config[Histogram]
74+
)
75+
self.assertEquals(
76+
stats.metric_config[Histogram]['name_format'],
77+
'%s.p%s',
78+
stats.metric_config[Histogram]
79+
)
80+
81+
for i in xrange(20):
82+
stats.submit_packets('myhistogram:{0}|h'.format(i))
83+
84+
metrics = stats.flush()
85+
86+
self.assertEquals(len(metrics), 5, metrics)
87+
88+
value_by_type = {}
89+
for k in metrics:
90+
value_by_type[k['metric'][len('myhistogram')+1:]] = k['points'][0][1]
91+
92+
self.assertEquals(value_by_type['p40'], 7, value_by_type)
93+
94+
def test_custom_single_percentile_withInvalidCustomName(self):
95+
configstr = '0.40'
96+
configname = '%s.p%s.%s'
97+
98+
stats = MetricsAggregator(
99+
'myhost',
100+
histogram_percentiles=get_histogram_percentiles(configstr),
101+
histogram_name_format=configname
102+
)
103+
104+
self.assertEquals(
105+
stats.metric_config[Histogram]['percentiles'],
106+
[0.40],
107+
stats.metric_config[Histogram]
108+
)
109+
self.assertEquals(
110+
stats.metric_config[Histogram]['name_format'],
111+
'%s.p%s.%s',
112+
stats.metric_config[Histogram]
113+
)
114+
115+
for i in xrange(20):
116+
stats.submit_packets('myhistogram:{0}|h'.format(i))
117+
118+
metrics = stats.flush()
119+
120+
self.assertEquals(len(metrics), 5, metrics)
121+
122+
value_by_type = {}
123+
for k in metrics:
124+
value_by_type[k['metric'][len('myhistogram')+1:]] = k['points'][0][1]
125+
126+
self.assertEquals(value_by_type['40percentile'], 7, value_by_type)
127+
60128
def test_custom_multiple_percentile(self):
61129
configstr = '0.4, 0.65, 0.999'
62130
stats = MetricsAggregator(

0 commit comments

Comments
 (0)