diff --git a/README.md b/README.md index 70c91ca..914d279 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,8 @@ Options: --aggregationMethod=AGGREGATIONMETHOD The consolidation function to fetch from on input and aggregationMethod to set on output. One of: average, - last, max, min, avg_zero, absmax, absmin + last, max, min, avg_zero, absmax, absmin, median, + median_low, median_high --destinationPath=DESTINATIONPATH Path to place created whisper file. Defaults to the RRD file's source path. @@ -66,7 +67,8 @@ Options: --xFilesFactor=XFILESFACTOR --aggregationMethod=AGGREGATIONMETHOD Function to use when aggregating values (average, sum, - last, max, min, avg_zero, absmax, absmin) + last, max, min, avg_zero, absmax, absmin, median, + median_low, median_high) --overwrite --estimate Don't create a whisper file, estimate storage requirements based on archive definitions ``` @@ -170,7 +172,8 @@ Options: Change the xFilesFactor --aggregationMethod=AGGREGATIONMETHOD Change the aggregation function (average, sum, last, - max, min, avg_zero, absmax, absmin) + max, min, avg_zero, absmax, absmin, median, + median_low, median_high) --force Perform a destructive change --newfile=NEWFILE Create a new database file without removing the existing one @@ -185,7 +188,7 @@ whisper-set-aggregation-method.py Change the aggregation method of an existing whisper file. ``` -Usage: whisper-set-aggregation-method.py path +Usage: whisper-set-aggregation-method.py path Options: -h, --help show this help message and exit diff --git a/bin/rrd2whisper.py b/bin/rrd2whisper.py index 4abe724..2693ece 100755 --- a/bin/rrd2whisper.py +++ b/bin/rrd2whisper.py @@ -28,6 +28,12 @@ aggregationMethods.remove('absmax') # RRD doesn't have a 'absmin' type aggregationMethods.remove('absmin') +# RRD doesn't have a 'median' type +aggregationMethods.remove('median') +# RRD doesn't have a 'median_low' type +aggregationMethods.remove('median_low') +# RRD doesn't have a 'median_high' type +aggregationMethods.remove('median_high') option_parser = optparse.OptionParser(usage='''%prog rrd_path''') option_parser.add_option( diff --git a/test_whisper.py b/test_whisper.py index 33936be..7145fd8 100755 --- a/test_whisper.py +++ b/test_whisper.py @@ -228,6 +228,14 @@ def test_aggregate(self): self.assertEqual(whisper.aggregate('absmin', [-3, -2, 1, 2]), 1) # absmin with negative min self.assertEqual(whisper.aggregate('absmin', [-2, -1, 2, 3]), -1) + # median with odd number of values + self.assertEqual(whisper.aggregate('median', [10, 1, 12, 42, 7]), 10) + # median with even number of values + self.assertEqual(whisper.aggregate('median', [9, 2, 5, 4]), 4.5) + # median_low + self.assertEqual(whisper.aggregate('median_low', [9, 2, 5, 4]), 4) + # median_high + self.assertEqual(whisper.aggregate('median_high', [9, 2, 5, 4]), 5) with AssertRaisesException( whisper.InvalidAggregationMethod( diff --git a/whisper.py b/whisper.py index 3cbbdca..b8baaca 100644 --- a/whisper.py +++ b/whisper.py @@ -125,7 +125,10 @@ def _py_fallocate(fd, offset, len_): 5: 'min', 6: 'avg_zero', 7: 'absmax', - 8: 'absmin' + 8: 'absmin', + 9: 'median', + 10: 'median_low', + 11: 'median_high' }) aggregationMethodToType = dict([[v, k] for k, v in aggregationTypeToMethod.items()]) aggregationMethods = aggregationTypeToMethod.values() @@ -570,6 +573,17 @@ def aggregate(aggregationMethod, knownValues, neighborValues=None): return max(knownValues, key=abs) elif aggregationMethod == 'absmin': return min(knownValues, key=abs) + elif aggregationMethod == 'median': + values = sorted(knownValues) + mid, isodd = divmod(len(values), 2) + return values[mid] if isodd else float(values[mid-1] + values[mid]) / 2.0 + elif aggregationMethod == 'median_low': + values = sorted(knownValues) + mid, isodd = divmod(len(values), 2) + return values[mid] if isodd else values[mid-1] + elif aggregationMethod == 'median_high': + values = sorted(knownValues) + return values[len(values) // 2] else: raise InvalidAggregationMethod( "Unrecognized aggregation method %s" % aggregationMethod)