← Back to team overview

graphite-dev team mailing list archive

[Merge] lp:~kenshi/graphite/moving_median into lp:graphite

 

Ken has proposed merging lp:~kenshi/graphite/moving_median into lp:graphite.

Requested reviews:
  graphite-dev (graphite-dev)

For more details, see:
https://code.launchpad.net/~kenshi/graphite/moving_median/+merge/93164

Added a moving median function to the list of available render functions.  This is useful for data that are susceptible to outliers.
-- 
https://code.launchpad.net/~kenshi/graphite/moving_median/+merge/93164
Your team graphite-dev is requested to review the proposed merge of lp:~kenshi/graphite/moving_median into lp:graphite.
=== modified file 'webapp/content/js/composer_widgets.js'
--- webapp/content/js/composer_widgets.js	2012-02-13 06:55:34 +0000
+++ webapp/content/js/composer_widgets.js	2012-02-15 09:47:18 +0000
@@ -908,6 +908,7 @@
         {text: 'Holt-Winters Forecast', handler: this.applyFuncToEach('holtWintersForecast')},
         {text: 'Holt-Winters Confidence Bands', handler: this.applyFuncToEach('holtWintersConfidenceBands')},
         {text: 'Holt-Winters Aberration', handler: this.applyFuncToEach('holtWintersAberration')},
+        {text: 'Moving Median', handler: applyFuncToEachWithInput('movingMedian', 'Moving median for the last ___ data points')},
         {text: 'As Percent', handler: applyFuncToEachWithInput('asPercent', 'Please enter the value that corresponds to 100%')},
         {text: 'Difference (of 2 series)', handler: applyFuncToAll('diffSeries')},
         {text: 'Ratio (of 2 series)', handler: applyFuncToAll('divideSeries')}

=== modified file 'webapp/graphite/render/functions.py'
--- webapp/graphite/render/functions.py	2012-02-08 23:00:15 +0000
+++ webapp/graphite/render/functions.py	2012-02-15 09:47:18 +0000
@@ -281,7 +281,7 @@
     Example:
 
     .. code-block:: none
-    
+
         &target=rangeOfSeries(Server*.connections.total)
 
     """
@@ -378,6 +378,41 @@
     step = reduce(lcm,[s.step for s in bothSeries])
 
     for s in bothSeries:
+def movingMedian(requestContext, seriesList, windowSize):
+  """
+  Takes one metric or a wildcard seriesList followed by a number N of datapoints and graphs
+  the median of N previous datapoints.  N-1 datapoints are set to None at the
+  beginning of the graph.
+
+  .. code-block:: none
+
+    &target=movingMedian(Server.instance01.threads.busy,10)
+
+  """
+  for seriesIndex, series in enumerate(seriesList):
+    newName = "movingMedian(%s,%.1f)" % (series.name, float(windowSize))
+    newSeries = TimeSeries(newName, series.start, series.end, series.step, [])
+    newSeries.pathExpression = newName
+
+    windowIndex = windowSize - 1
+
+    for i in range( len(series) ):
+      if i < windowIndex: # Pad the beginning with None's since we don't have enough data
+        newSeries.append( None )
+
+      else:
+        window = series[i - windowIndex : i + 1]
+        nonNull = [ v for v in window if v is not None ]
+        if nonNull:
+          m_index = len(nonNull) / 2
+          newSeries.append(sorted(nonNull)[m_index])
+        else:
+          newSeries.append(None)
+
+    seriesList[ seriesIndex ] = newSeries
+
+  return seriesList
+
       s.consolidate( step / s.step )
 
     start = min([s.start for s in bothSeries])
@@ -2099,6 +2134,7 @@
 
   # Calculate functions
   'movingAverage' : movingAverage,
+  'movingMedian' : movingMedian,
   'stdev' : stdev,
   'holtWintersForecast': holtWintersForecast,
   'holtWintersConfidenceBands': holtWintersConfidenceBands,