files: #Copyright 2008 Orbitz WorldWide
  #
  #Licensed under the Apache License, Version 2.0 (the "License");
  #you may not use this file except in compliance with the License.
  #You may obtain a copy of the License at
  #
  #   http://www.apache.org/licenses/LICENSE-2.0
  #
  #Unless required by applicable law or agreed to in writing, software
  #distributed under the License is distributed on an "AS IS" BASIS,
  #WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #See the License for the specific language governing permissions and
  #limitations under the License.
  
  # make / work consistently between python 2.x and 3.x
  # https://www.python.org/dev/peps/pep-0238/
  from __future__ import division
  
  import math
  import random
  import re
  import time
  
  from datetime import datetime, timedelta
  from functools import reduce
  from six.moves import range, zip
  import six
  try:
    from itertools import izip, izip_longest
  except ImportError:
    # Python 3
    from itertools import zip_longest as izip_longest
    izip = zip
  from os import environ
  
  from django.conf import settings
  
  from graphite.errors import NormalizeEmptyResultError
  from graphite.events import models
  from graphite.functions import SeriesFunction, ParamTypes, Param
  from graphite.logger import log
  from graphite.render.attime import getUnitString, parseTimeOffset, parseATTime, SECONDS_STRING, MINUTES_STRING, HOURS_STRING, DAYS_STRING, WEEKS_STRING, MONTHS_STRING, YEARS_STRING
  from graphite.render.evaluator import evaluateTarget
  from graphite.render.grammar import grammar
  from graphite.storage import STORE
  from graphite.util import epoch, epoch_to_dt, timestamp, deltaseconds
  
  # XXX format_units() should go somewhere else
  if environ.get('READTHEDOCS'):
    format_units = lambda *args, **kwargs: (0,'')
  else:
    from graphite.render.glyph import format_units
    from graphite.render.datalib import TimeSeries
  
  NAN = float('NaN')
  INF = float('inf')
  DAY = 86400
  HOUR = 3600
  MINUTE = 60
  
  #Utility functions
  
  
  def safeSum(values):
    safeValues = [v for v in values if v is not None]
    if safeValues:
      return sum(safeValues)
  
  
  def safeDiff(values):
    safeValues = [v for v in values if v is not None]
    if safeValues:
      values = list(map(lambda x: x*-1, safeValues[1:]))
      values.insert(0, safeValues[0])
      return sum(values)
  
  
  def safeLen(values):
    return len([v for v in values if v is not None])
  
  
  def safeDiv(a, b):
    if a is None: return None
    if b in (0,None): return None
    return a / b
  
  
  def safePow(a, b):
    if a is None: return None
    if b is None: return None
    try:
      result = math.pow(a, b)
    except (ValueError, OverflowError):
      return None
    return result
  
  
  def safeMul(*factors):
    if None in factors:
      return None
  
    factors = [float(x) for x in factors]
    product = reduce(lambda x,y: x*y, factors)
    return product
  
  
  def safeSubtract(a,b):
      if a is None or b is None: return None
      return float(a) - float(b)
  
  
  def safeAvg(values):
    safeValues = [v for v in values if v is not None]
    if safeValues:
      return sum(safeValues) / len(safeValues)
  
  
  def safeMedian(values):
    safeValues = [v for v in values if v is not None]
    if safeValues:
      sortedVals = sorted(safeValues)
      mid = len(sortedVals) // 2
      if len(sortedVals) % 2 == 0:
        return float(sortedVals[mid-1] + sortedVals[mid]) / 2
      else:
        return sortedVals[mid]
  
  
  def safeStdDev(a):
    sm = safeSum(a)
    ln = safeLen(a)
    avg = safeDiv(sm,ln)
    if avg is None: return None
    sum = 0
    safeValues = [v for v in a if v is not None]
    for val in safeValues:
       sum = sum + (val - avg) * (val - avg)
    return math.sqrt(sum/ln)
  
  
  def safeLast(values):
    for v in reversed(values):
      if v is not None: return v
  
  
  def safeMin(values, default=None):
    safeValues = [v for v in values if v is not None]
    if safeValues:
      return min(safeValues)
    else:
      return default
  
  
  def safeMax(values, default=None):
    safeValues = [v for v in values if v is not None]
    if safeValues:
      return max(safeValues)
    else:
      return default
  
  
  def safeMap(function, values):
    safeValues = [v for v in values if v is not None]
    if safeValues:
      return [function(x) for x in safeValues]
  
  
  def safeAbs(value):
    if value is None: return None
    return abs(value)
  
  
  # In Py2, None < (any integer) . Use -inf for same sorting on Python 3
  def keyFunc(func):
    def safeFunc(values):
      val = func(values)
      return val if val is not None else -INF
    return safeFunc
  
  
  # Greatest common divisor
  def gcd(a, b):
    if b == 0:
      return a
    return gcd(b, a%b)
  
  
  # Least common multiple
  def lcm(a, b):
    if a == b: return a
    if a < b: (a, b) = (b, a) #ensure a > b
    return a // gcd(a,b) * b
  
  
  # check list of values against xFilesFactor
  def xffValues(values, xFilesFactor):
    if not values:
      return False
    return xff(len([v for v in values if v is not None]), len(values), xFilesFactor)
  
  
  def xff(nonNull, total, xFilesFactor=None):
    if not nonNull or not total:
      return False
    return nonNull / total >= (xFilesFactor if xFilesFactor is not None else settings.DEFAULT_XFILES_FACTOR)
  
  
  def getNodeOrTag(series, n, pathExpression=None):
    try:
      # if n is an integer use it as a node, otherwise catch ValueError and treat it as a tag
      if n == int(n):
        # first split off any tags, then list of nodes is name split on .
        return (pathExpression or series.name).split(';', 2)[0].split('.')[n]
    except (ValueError, IndexError):
      # if node isn't an integer or isn't found then try it as a tag name
      pass
    # return tag value, default to '' if not found
    return series.tags.get(str(n), '')
  
  
  def aggKey(series, nodes, pathExpression=None):
    # if series.name looks like it includes a function, use the first path expression
    if pathExpression is None and series.name[-1] == ')':
      pathExpression = _getFirstPathExpression(series.name)
    return '.'.join([getNodeOrTag(series, n, pathExpression) for n in nodes])
  
  
  def normalize(seriesLists):
    if seriesLists:
      seriesList = reduce(lambda L1,L2: L1+L2,seriesLists)
      if seriesList:
        step = reduce(lcm,[s.step for s in seriesList])
        for s in seriesList:
          s.consolidate( step // s.step )
        start = min([s.start for s in seriesList])
        end = max([s.end for s in seriesList])
        end -= (end - start) % step
        return (seriesList,start,end,step)
    raise NormalizeEmptyResultError()
  
  
  def matchSeries(seriesList1, seriesList2):
    assert len(seriesList2) == len(seriesList1), "The number of series in each argument must be the same"
    return izip(sorted(seriesList1, key=lambda a: a.name), sorted(seriesList2, key=lambda a: a.name))
  
  
  def formatPathExpressions(seriesList):
    # remove duplicates
    pathExpressions = []
    [pathExpressions.append(s.pathExpression) for s in seriesList if not pathExpressions.count(s.pathExpression)]
    return ','.join(pathExpressions)
  
  
  # Series Functions
  
  aggFuncs = {
    'average': safeAvg,
    'median': safeMedian,
    'sum': safeSum,
    'min': safeMin,
    'max': safeMax,
    'diff': safeDiff,
    'stddev': safeStdDev,
    'count': safeLen,
    'range': lambda row: safeSubtract(safeMax(row), safeMin(row)),
    'multiply': lambda row: safeMul(*row),
    'last': safeLast,
  }
  
  aggFuncAliases = {
    'rangeOf': aggFuncs['range'],
    'avg': aggFuncs['average'],
    'total': aggFuncs['sum'],
    'current': aggFuncs['last'],
  }
  
  aggFuncNames = sorted(aggFuncs.keys())
  
  
  def getAggFunc(func, rawFunc=None):
    if func in aggFuncs:
      return aggFuncs[func]
    if func in aggFuncAliases:
      return aggFuncAliases[func]
    raise ValueError('Unsupported aggregation function: %s' % (rawFunc or func))
  
  
  def aggregate(requestContext, seriesList, func, xFilesFactor=None):
    """
    Aggregate series using the specified function.
  
    Example:
  
    .. code-block:: none
  
      &target=aggregate(host.cpu-[0-7].cpu-{user,system}.value, "sum")
  
    This would be the equivalent of
  
    .. code-block:: none
  
      &target=sumSeries(host.cpu-[0-7].cpu-{user,system}.value)
  
    This function can be used with aggregation functions ``average``, ``median``, ``sum``, ``min``,
    ``max``, ``diff``, ``stddev``, ``count``, ``range``, ``multiply`` & ``last``.
    """
    # strip Series from func if func was passed like sumSeries
    rawFunc = func
    if func[-6:] == 'Series':
      func = func[:-6]
  
    consolidationFunc = getAggFunc(func, rawFunc)
  
    # if seriesList is empty then just short-circuit
    if not seriesList:
      return []
  
    # if seriesList is a single series then wrap it for normalize
    if isinstance(seriesList[0], TimeSeries):
      seriesList = [seriesList]
  
    try:
      (seriesList, start, end, step) = normalize(seriesList)
    except NormalizeEmptyResultError:
      return []
    xFilesFactor = xFilesFactor if xFilesFactor is not None else requestContext.get('xFilesFactor')
    name = "%sSeries(%s)" % (func, formatPathExpressions(seriesList))
    values = ( consolidationFunc(row) if xffValues(row, xFilesFactor) else None for row in izip_longest(*seriesList) )
    tags = seriesList[0].tags
    for series in seriesList:
      tags = {tag: tags[tag] for tag in tags if tag in series.tags and tags[tag] == series.tags[tag]}
    if 'name' not in tags:
      tags['name'] = name
    tags['aggregatedBy'] = func
    series = TimeSeries(name, start, end, step, values, xFilesFactor=xFilesFactor, tags=tags)
  
    return [series]
  
  
  aggregate.group = 'Combine'
  aggregate.params = [
    Param('seriesList', ParamTypes.seriesList, required=True),
    Param('func', ParamTypes.aggFunc, required=True, options=aggFuncNames),
    Param('xFilesFactor', ParamTypes.float),
  ]
  
  
  def sumSeries(requestContext, *seriesLists):
    """
    Short form: sum()
  
    This will add metrics together and return the sum at each datapoint. (See
    integral for a sum over time)
  
    Example:
  
    .. code-block:: none
  
      &target=sum(company.server.application*.requestsHandled)
  
    This would show the sum of all requests handled per minute (provided
    requestsHandled are collected once a minute).   If metrics with different
    retention rates are combined, the coarsest metric is graphed, and the sum
    of the other metrics is averaged for the metrics with finer retention rates.
  
    This is an alias for :py:func:`aggregate <aggregate>` with aggregation ``sum``.
    """
    return aggregate(requestContext, seriesLists, 'sum')
  
  
  sumSeries.group = 'Combine'
  sumSeries.params = [
    Param('seriesLists', ParamTypes.seriesList, required=True, multiple=True),
  ]
  
  
  def sumSeriesWithWildcards(requestContext, seriesList, *position): #XXX
    """
    Call sumSeries after inserting wildcards at the given position(s).
  
    Example:
  
    .. code-block:: none
  
      &target=sumSeriesWithWildcards(host.cpu-[0-7].cpu-{user,system}.value, 1)
  
    This would be the equivalent of
  
    .. code-block:: none
  
      &target=sumSeries(host.cpu-[0-7].cpu-user.value)&target=sumSeries(host.cpu-[0-7].cpu-system.value)
  
    This is an alias for :py:func:`aggregateWithWildcards <aggregateWithWildcards>` with aggregation ``sum``.
    """
    return aggregateWithWildcards(requestContext, seriesList, 'sum', *position)
  
  
  sumSeriesWithWildcards.group = 'Combine'
  sumSeriesWithWildcards.params = [
    Param('seriesList', ParamTypes.seriesList, required=True),
    Param('position', ParamTypes.node, multiple=True),
  ]
  
  
  def averageSeriesWithWildcards(requestContext, seriesList, *position): #XXX
    """
    Call averageSeries after inserting wildcards at the given position(s).
  
    Example:
  
    .. code-block:: none
  
      &target=averageSeriesWithWildcards(host.cpu-[0-7].cpu-{user,system}.value, 1)
  
    This would be the equivalent of
  
    .. code-block:: none
  
      &target=averageSeries(host.*.cpu-user.value)&target=averageSeries(host.*.cpu-system.value)
  
    This is an alias for :py:func:`aggregateWithWildcards <aggregateWithWildcards>` with aggregation ``average``.
    """
    return aggregateWithWildcards(requestContext, seriesList, 'average', *position)
  
  
  averageSeriesWithWildcards.group = 'Combine'
  averageSeriesWithWildcards.params = [
    Param('seriesList', ParamTypes.seriesList, required=True),
    Param('position', ParamTypes.node, multiple=True),
  ]
  
  
  def multiplySeriesWithWildcards(requestContext, seriesList, *position): #XXX
    """
    Call multiplySeries after inserting wildcards at the given position(s).
  
    Example:
  
    .. code-block:: none
  
      &target=multiplySeriesWithWildcards(web.host-[0-7].{avg-response,total-request}.value, 2)
  
    This would be the equivalent of
  
    .. code-block:: none
  
      &target=multiplySeries(web.host-0.{avg-response,total-request}.value)&target=multiplySeries(web.host-1.{avg-response,total-request}.value)...
  
    This is an alias for :py:func:`aggregateWithWildcards <aggregateWithWildcards>` with aggregation ``multiply``.
    """
    return aggregateWithWildcards(requestContext, seriesList, 'multiply', *position)
  
  
  multiplySeriesWithWildcards.group = 'Combine'
  multiplySeriesWithWildcards.params = [
    Param('seriesList', ParamTypes.seriesList, required=True),
    Param('position', ParamTypes.node, multiple=True),
  ]
  
  
  def aggregateWithWildcards(requestContext, seriesList, func, *positions):
    """
    Call aggregator after inserting wildcards at the given position(s).
  
    Example:
  
    .. code-block:: none
  
      &target=aggregateWithWildcards(host.cpu-[0-7].cpu-{user,system}.value, "sum", 1)
  
    This would be the equivalent of
  
    .. code-block:: none
  
      &target=sumSeries(host.cpu-[0-7].cpu-user.value)&target=sumSeries(host.cpu-[0-7].cpu-system.value)
      # or
      &target=aggregate(host.cpu-[0-7].cpu-user.value,"sum")&target=aggregate(host.cpu-[0-7].cpu-system.value,"sum")
  
    This function can be used with all aggregation functions supported by
    :py:func:`aggregate <aggregate>`: ``average``, ``median``, ``sum``, ``min``, ``max``, ``diff``,
    ``stddev``, ``range`` & ``multiply``.
  
    This complements :py:func:`groupByNodes <groupByNodes>` which takes a list of nodes that must match in each group.
    """
    metaSeries = {}
    keys = []
    for series in seriesList:
      key = '.'.join(map(lambda x: x[1], filter(lambda i: i[0] not in positions, enumerate(series.name.split('.')))))
      if key not in metaSeries:
        metaSeries[key] = [series]
        keys.append(key)
      else:
        metaSeries[key].append(series)
    for key in metaSeries.keys():
      metaSeries[key] = aggregate(requestContext, metaSeries[key], func)[0]
      metaSeries[key].name = key
    return [ metaSeries[key] for key in keys ]
  
  
  aggregateWithWildcards.group = 'Combine'
  aggregateWithWildcards.params = [
    Param('seriesList', ParamTypes.seriesList, required=True),
    Param('func', ParamTypes.aggFunc, required=True, options=aggFuncNames),
    Param('position', ParamTypes.node, multiple=True),
  ]
  
  
  def diff
  
  [... the rest is omitted ...]
files: webapp/graphite/render/functions.py
files: #Copyright 2008 Orbitz WorldWide
  #
  #Licensed under the Apache License, Version 2.0 (the "License");
  #you may not use this file except in compliance with the License.
  #You may obtain a copy of the License at
  #
  #   http://www.apache.org/licenses/LICENSE-2.0
  #
  #Unless required by applicable law or agreed to in writing, software
  #distributed under the License is distributed on an "AS IS" BASIS,
  #WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #See the License for the specific language governing permissions and
  #limitations under the License.
  
  # make / work consistently between python 2.x and 3.x
  # https://www.python.org/dev/peps/pep-0238/
  from __future__ import division
  
  import math
  import random
  import re
  import time
  
  from datetime import datetime, timedelta
  from functools import reduce
  from six.moves import range, zip
  import six
  try:
    from itertools import izip, izip_longest
  except ImportError:
    # Python 3
    from itertools import zip_longest as izip_longest
    izip = zip
  from os import environ
  
  from django.conf import settings
  
  from graphite.errors import NormalizeEmptyResultError
  from graphite.events import models
  from graphite.functions import SeriesFunction, ParamTypes, Param
  from graphite.logger import log
  from graphite.render.attime import getUnitString, parseTimeOffset, parseATTime, SECONDS_STRING, MINUTES_STRING, HOURS_STRING, DAYS_STRING, WEEKS_STRING, MONTHS_STRING, YEARS_STRING
  from graphite.render.evaluator import evaluateTarget
  from graphite.render.grammar import grammar
  from graphite.storage import STORE
  from graphite.util import epoch, epoch_to_dt, timestamp, deltaseconds
  
  # XXX format_units() should go somewhere else
  if environ.get('READTHEDOCS'):
    format_units = lambda *args, **kwargs: (0,'')
  else:
    from graphite.render.glyph import format_units
    from graphite.render.datalib import TimeSeries
  
  NAN = float('NaN')
  INF = float('inf')
  DAY = 86400
  HOUR = 3600
  MINUTE = 60
  
  #Utility functions
  
  
  def safeSum(values):
    safeValues = [v for v in values if v is not None]
    if safeValues:
      return sum(safeValues)
  
  
  def safeDiff(values):
    safeValues = [v for v in values if v is not None]
    if safeValues:
      values = list(map(lambda x: x*-1, safeValues[1:]))
      values.insert(0, safeValues[0])
      return sum(values)
  
  
  def safeLen(values):
    return len([v for v in values if v is not None])
  
  
  def safeDiv(a, b):
    if a is None: return None
    if b in (0,None): return None
    return a / b
  
  
  def safePow(a, b):
    if a is None: return None
    if b is None: return None
    try:
      result = math.pow(a, b)
    except (ValueError, OverflowError):
      return None
    return result
  
  
  def safeMul(*factors):
    if None in factors:
      return None
  
    factors = [float(x) for x in factors]
    product = reduce(lambda x,y: x*y, factors)
    return product
  
  
  def safeSubtract(a,b):
      if a is None or b is None: return None
      return float(a) - float(b)
  
  
  def safeAvg(values):
    safeValues = [v for v in values if v is not None]
    if safeValues:
      return sum(safeValues) / len(safeValues)
  
  
  def safeMedian(values):
    safeValues = [v for v in values if v is not None]
    if safeValues:
      sortedVals = sorted(safeValues)
      mid = len(sortedVals) // 2
      if len(sortedVals) % 2 == 0:
        return float(sortedVals[mid-1] + sortedVals[mid]) / 2
      else:
        return sortedVals[mid]
  
  
  def safeStdDev(a):
    sm = safeSum(a)
    ln = safeLen(a)
    avg = safeDiv(sm,ln)
    if avg is None: return None
    sum = 0
    safeValues = [v for v in a if v is not None]
    for val in safeValues:
       sum = sum + (val - avg) * (val - avg)
    return math.sqrt(sum/ln)
  
  
  def safeLast(values):
    for v in reversed(values):
      if v is not None: return v
  
  
  def safeMin(values, default=None):
    safeValues = [v for v in values if v is not None]
    if safeValues:
      return min(safeValues)
    else:
      return default
  
  
  def safeMax(values, default=None):
    safeValues = [v for v in values if v is not None]
    if safeValues:
      return max(safeValues)
    else:
      return default
  
  
  def safeMap(function, values):
    safeValues = [v for v in values if v is not None]
    if safeValues:
      return [function(x) for x in safeValues]
  
  
  def safeAbs(value):
    if value is None: return None
    return abs(value)
  
  
  # In Py2, None < (any integer) . Use -inf for same sorting on Python 3
  def keyFunc(func):
    def safeFunc(values):
      val = func(values)
      return val if val is not None else -INF
    return safeFunc
  
  
  # Greatest common divisor
  def gcd(a, b):
    if b == 0:
      return a
    return gcd(b, a%b)
  
  
  # Least common multiple
  def lcm(a, b):
    if a == b: return a
    if a < b: (a, b) = (b, a) #ensure a > b
    return a // gcd(a,b) * b
  
  
  # check list of values against xFilesFactor
  def xffValues(values, xFilesFactor):
    if not values:
      return False
    return xff(len([v for v in values if v is not None]), len(values), xFilesFactor)
  
  
  def xff(nonNull, total, xFilesFactor=None):
    if not nonNull or not total:
      return False
    return nonNull / total >= (xFilesFactor if xFilesFactor is not None else settings.DEFAULT_XFILES_FACTOR)
  
  
  def getNodeOrTag(series, n, pathExpression=None):
    try:
      # if n is an integer use it as a node, otherwise catch ValueError and treat it as a tag
      if n == int(n):
        # first split off any tags, then list of nodes is name split on .
        return (pathExpression or series.name).split(';', 2)[0].split('.')[n]
    except (ValueError, IndexError):
      # if node isn't an integer or isn't found then try it as a tag name
      pass
    # return tag value, default to '' if not found
    return series.tags.get(str(n), '')
  
  
  def aggKey(series, nodes, pathExpression=None):
    # if series.name looks like it includes a function, use the first path expression
    if pathExpression is None and series.name[-1] == ')':
      pathExpression = _getFirstPathExpression(series.name)
    return '.'.join([getNodeOrTag(series, n, pathExpression) for n in nodes])
  
  
  def normalize(seriesLists):
    if seriesLists:
      seriesList = reduce(lambda L1,L2: L1+L2,seriesLists)
      if seriesList:
        step = reduce(lcm,[s.step for s in seriesList])
        for s in seriesList:
          s.consolidate( step // s.step )
        start = min([s.start for s in seriesList])
        end = max([s.end for s in seriesList])
        end -= (end - start) % step
        return (seriesList,start,end,step)
    raise NormalizeEmptyResultError()
  
  
  def matchSeries(seriesList1, seriesList2):
    assert len(seriesList2) == len(seriesList1), "The number of series in each argument must be the same"
    return izip(sorted(seriesList1, key=lambda a: a.name), sorted(seriesList2, key=lambda a: a.name))
  
  
  def formatPathExpressions(seriesList):
    # remove duplicates
    pathExpressions = []
    [pathExpressions.append(s.pathExpression) for s in seriesList if not pathExpressions.count(s.pathExpression)]
    return ','.join(pathExpressions)
  
  
  # Series Functions
  
  aggFuncs = {
    'average': safeAvg,
    'median': safeMedian,
    'sum': safeSum,
    'min': safeMin,
    'max': safeMax,
    'diff': safeDiff,
    'stddev': safeStdDev,
    'count': safeLen,
    'range': lambda row: safeSubtract(safeMax(row), safeMin(row)),
    'multiply': lambda row: safeMul(*row),
    'last': safeLast,
  }
  
  aggFuncAliases = {
    'rangeOf': aggFuncs['range'],
    'avg': aggFuncs['average'],
    'total': aggFuncs['sum'],
    'current': aggFuncs['last'],
  }
  
  aggFuncNames = sorted(aggFuncs.keys())
  
  
  def getAggFunc(func, rawFunc=None):
    if func in aggFuncs:
      return aggFuncs[func]
    if func in aggFuncAliases:
      return aggFuncAliases[func]
    raise ValueError('Unsupported aggregation function: %s' % (rawFunc or func))
  
  
  def aggregate(requestContext, seriesList, func, xFilesFactor=None):
    """
    Aggregate series using the specified function.
  
    Example:
  
    .. code-block:: none
  
      &target=aggregate(host.cpu-[0-7].cpu-{user,system}.value, "sum")
  
    This would be the equivalent of
  
    .. code-block:: none
  
      &target=sumSeries(host.cpu-[0-7].cpu-{user,system}.value)
  
    This function can be used with aggregation functions ``average``, ``median``, ``sum``, ``min``,
    ``max``, ``diff``, ``stddev``, ``count``, ``range``, ``multiply`` & ``last``.
    """
    # strip Series from func if func was passed like sumSeries
    rawFunc = func
    if func[-6:] == 'Series':
      func = func[:-6]
  
    consolidationFunc = getAggFunc(func, rawFunc)
  
    # if seriesList is empty then just short-circuit
    if not seriesList:
      return []
  
    # if seriesList is a single series then wrap it for normalize
    if isinstance(seriesList[0], TimeSeries):
      seriesList = [seriesList]
  
    try:
      (seriesList, start, end, step) = normalize(seriesList)
    except NormalizeEmptyResultError:
      return []
    xFilesFactor = xFilesFactor if xFilesFactor is not None else requestContext.get('xFilesFactor')
    name = "%sSeries(%s)" % (func, formatPathExpressions(seriesList))
    values = ( consolidationFunc(row) if xffValues(row, xFilesFactor) else None for row in izip_longest(*seriesList) )
    tags = seriesList[0].tags
    for series in seriesList:
      tags = {tag: tags[tag] for tag in tags if tag in series.tags and tags[tag] == series.tags[tag]}
    if 'name' not in tags:
      tags['name'] = name
    tags['aggregatedBy'] = func
    series = TimeSeries(name, start, end, step, values, xFilesFactor=xFilesFactor, tags=tags)
  
    return [series]
  
  
  aggregate.group = 'Combine'
  aggregate.params = [
    Param('seriesList', ParamTypes.seriesList, required=True),
    Param('func', ParamTypes.aggFunc, required=True, options=aggFuncNames),
    Param('xFilesFactor', ParamTypes.float),
  ]
  
  
  def sumSeries(requestContext, *seriesLists):
    """
    Short form: sum()
  
    This will add metrics together and return the sum at each datapoint. (See
    integral for a sum over time)
  
    Example:
  
    .. code-block:: none
  
      &target=sum(company.server.application*.requestsHandled)
  
    This would show the sum of all requests handled per minute (provided
    requestsHandled are collected once a minute).   If metrics with different
    retention rates are combined, the coarsest metric is graphed, and the sum
    of the other metrics is averaged for the metrics with finer retention rates.
  
    This is an alias for :py:func:`aggregate <aggregate>` with aggregation ``sum``.
    """
    return aggregate(requestContext, seriesLists, 'sum')
  
  
  sumSeries.group = 'Combine'
  sumSeries.params = [
    Param('seriesLists', ParamTypes.seriesList, required=True, multiple=True),
  ]
  
  
  def sumSeriesWithWildcards(requestContext, seriesList, *position): #XXX
    """
    Call sumSeries after inserting wildcards at the given position(s).
  
    Example:
  
    .. code-block:: none
  
      &target=sumSeriesWithWildcards(host.cpu-[0-7].cpu-{user,system}.value, 1)
  
    This would be the equivalent of
  
    .. code-block:: none
  
      &target=sumSeries(host.cpu-[0-7].cpu-user.value)&target=sumSeries(host.cpu-[0-7].cpu-system.value)
  
    This is an alias for :py:func:`aggregateWithWildcards <aggregateWithWildcards>` with aggregation ``sum``.
    """
    return aggregateWithWildcards(requestContext, seriesList, 'sum', *position)
  
  
  sumSeriesWithWildcards.group = 'Combine'
  sumSeriesWithWildcards.params = [
    Param('seriesList', ParamTypes.seriesList, required=True),
    Param('position', ParamTypes.node, multiple=True),
  ]
  
  
  def averageSeriesWithWildcards(requestContext, seriesList, *position): #XXX
    """
    Call averageSeries after inserting wildcards at the given position(s).
  
    Example:
  
    .. code-block:: none
  
      &target=averageSeriesWithWildcards(host.cpu-[0-7].cpu-{user,system}.value, 1)
  
    This would be the equivalent of
  
    .. code-block:: none
  
      &target=averageSeries(host.*.cpu-user.value)&target=averageSeries(host.*.cpu-system.value)
  
    This is an alias for :py:func:`aggregateWithWildcards <aggregateWithWildcards>` with aggregation ``average``.
    """
    return aggregateWithWildcards(requestContext, seriesList, 'average', *position)
  
  
  averageSeriesWithWildcards.group = 'Combine'
  averageSeriesWithWildcards.params = [
    Param('seriesList', ParamTypes.seriesList, required=True),
    Param('position', ParamTypes.node, multiple=True),
  ]
  
  
  def multiplySeriesWithWildcards(requestContext, seriesList, *position): #XXX
    """
    Call multiplySeries after inserting wildcards at the given position(s).
  
    Example:
  
    .. code-block:: none
  
      &target=multiplySeriesWithWildcards(web.host-[0-7].{avg-response,total-request}.value, 2)
  
    This would be the equivalent of
  
    .. code-block:: none
  
      &target=multiplySeries(web.host-0.{avg-response,total-request}.value)&target=multiplySeries(web.host-1.{avg-response,total-request}.value)...
  
    This is an alias for :py:func:`aggregateWithWildcards <aggregateWithWildcards>` with aggregation ``multiply``.
    """
    return aggregateWithWildcards(requestContext, seriesList, 'multiply', *position)
  
  
  multiplySeriesWithWildcards.group = 'Combine'
  multiplySeriesWithWildcards.params = [
    Param('seriesList', ParamTypes.seriesList, required=True),
    Param('position', ParamTypes.node, multiple=True),
  ]
  
  
  def aggregateWithWildcards(requestContext, seriesList, func, *positions):
    """
    Call aggregator after inserting wildcards at the given position(s).
  
    Example:
  
    .. code-block:: none
  
      &target=aggregateWithWildcards(host.cpu-[0-7].cpu-{user,system}.value, "sum", 1)
  
    This would be the equivalent of
  
    .. code-block:: none
  
      &target=sumSeries(host.cpu-[0-7].cpu-user.value)&target=sumSeries(host.cpu-[0-7].cpu-system.value)
      # or
      &target=aggregate(host.cpu-[0-7].cpu-user.value,"sum")&target=aggregate(host.cpu-[0-7].cpu-system.value,"sum")
  
    This function can be used with all aggregation functions supported by
    :py:func:`aggregate <aggregate>`: ``average``, ``median``, ``sum``, ``min``, ``max``, ``diff``,
    ``stddev``, ``range`` & ``multiply``.
  
    This complements :py:func:`groupByNodes <groupByNodes>` which takes a list of nodes that must match in each group.
    """
    metaSeries = {}
    keys = []
    for series in seriesList:
      key = '.'.join(map(lambda x: x[1], filter(lambda i: i[0] not in positions, enumerate(series.name.split('.')))))
      if key not in metaSeries:
        metaSeries[key] = [series]
        keys.append(key)
      else:
        metaSeries[key].append(series)
    for key in metaSeries.keys():
      metaSeries[key] = aggregate(requestContext, metaSeries[key], func)[0]
      metaSeries[key].name = key
    return [ metaSeries[key] for key in keys ]
  
  
  aggregateWithWildcards.group = 'Combine'
  aggregateWithWildcards.params = [
    Param('seriesList', ParamTypes.seriesList, required=True),
    Param('func', ParamTypes.aggFunc, required=True, options=aggFuncNames),
    Param('position', ParamTypes.node, multiple=True),
  ]
  
  
  def diff
  
  [... the rest is omitted ...]
files: webapp/graphite/render/functions.py
files: import copy
  import inspect
  import math
  import pytz
  import six
  
  from datetime import datetime
  from fnmatch import fnmatch
  from mock import patch, call, MagicMock
  from os.path import dirname, join
  from six.moves import range
  
  from .base import TestCase
  from django.conf import settings
  
  try:
    from django.urls import reverse
  except ImportError:  # Django < 1.10
    from django.core.urlresolvers import reverse
  
  from graphite.errors import NormalizeEmptyResultError
  from graphite.functions import _SeriesFunctions, loadFunctions
  from graphite.render.datalib import TimeSeries
  from graphite.render import functions
  from graphite.render.evaluator import evaluateTarget
  from graphite.render.grammar import grammar
  from graphite.tags.utils import TaggedSeries
  from graphite.util import json
  
  
  def return_greater(series, value):
      return [i for i in series if i is not None and i > value]
  
  
  def return_less(series, value):
      return [i for i in series if i is not None and i < value]
  
  
  class FunctionsTest(TestCase):
  
      def setUp(self):
        super(FunctionsTest, self).setUp()
        # Display more diff.
        self.maxDiff = 1024
  
      #
      # Test safeSum()
      #
  
      def test_safeSum_None(self):
          with self.assertRaises(TypeError):
              functions.safeSum(None)
  
      def test_safeSum_empty_list(self):
          self.assertEqual(functions.safeSum([]), None)
  
      def test_safeSum_all_numbers(self):
          self.assertEqual(functions.safeSum([1,2,3,4]), 10)
  
      def test_safeSum_all_None(self):
          self.assertEqual(functions.safeSum([None,None,None,None]), None)
  
      def test_safeSum_mixed(self):
          self.assertEqual(functions.safeSum([10,None,5,None]), 15)
  
      #
      # Test safeDiff()
      #
  
      def test_safeDiff_None(self):
          with self.assertRaises(TypeError):
              functions.safeDiff(None)
  
      def test_safeDiff_empty_list(self):
          self.assertEqual(functions.safeDiff([]), None)
  
      def test_safeDiff_all_numbers(self):
          self.assertEqual(functions.safeDiff([1,2,3,4]), -8)
  
      def test_safeDiff_all_None(self):
          self.assertEqual(functions.safeDiff([None,None,None,None]), None)
  
      def test_safeDiff_mixed(self):
          self.assertEqual(functions.safeDiff([10,None,5,None]), 5)
  
      #
      # Test safeLen()
      #
  
      def test_safeLen_None(self):
          with self.assertRaises(TypeError):
              functions.safeLen(None)
  
      def test_safeLen_empty_list(self):
          self.assertEqual(functions.safeLen([]), 0)
  
      def test_safeLen_all_numbers(self):
          self.assertEqual(functions.safeLen([1,2,3,4]), 4)
  
      def test_safeLen_all_None(self):
          self.assertEqual(functions.safeLen([None,None,None,None]), 0)
  
      def test_safeLen_mixed(self):
          self.assertEqual(functions.safeLen([10,None,5,None]), 2)
  
      #
      # Test safeDiv()
      #
  
      def test_safeDiv_None_None(self):
          self.assertEqual(functions.safeDiv(None, None), None)
  
      def test_safeDiv_5_None(self):
          self.assertEqual(functions.safeDiv(5, None), None)
  
      def test_safeDiv_5_0(self):
          self.assertEqual(functions.safeDiv(5, 0), None)
  
      def test_safeDiv_0_10(self):
          self.assertEqual(functions.safeDiv(0,10), 0)
  
      def test_safeDiv_10_5(self):
          self.assertEqual(functions.safeDiv(10,5), 2)
  
      #
      # Test safePow()
      #
  
      def test_safePow_None_None(self):
          self.assertEqual(functions.safePow(None, None), None)
  
      def test_safePow_5_None(self):
          self.assertEqual(functions.safePow(5, None), None)
  
      def test_safePow_5_0(self):
          self.assertEqual(functions.safePow(5, 0), 1.0)
  
      def test_safePow_0_10(self):
          self.assertEqual(functions.safePow(0,10), 0)
  
      def test_safePow_10_5(self):
          self.assertEqual(functions.safePow(10,5), 100000.0)
  
      #
      # Test safeMul()
      #
  
      def test_safeMul_None_None(self):
          self.assertEqual(functions.safeMul(None, None), None)
  
      def test_safeMul_5_None(self):
          self.assertEqual(functions.safeMul(5, None), None)
  
      def test_safeMul_5_0(self):
          self.assertEqual(functions.safeMul(5, 0), 0.0)
  
      def test_safeMul_0_10(self):
          self.assertEqual(functions.safeMul(0,10), 0)
  
      def test_safeMul_10_5(self):
          self.assertEqual(functions.safeMul(10,5), 50.0)
  
      #
      # Test safeSubtract()
      #
  
      def test_safeSubtract_None_None(self):
          self.assertEqual(functions.safeSubtract(None, None), None)
  
      def test_safeSubtract_5_None(self):
          self.assertEqual(functions.safeSubtract(5, None), None)
  
      def test_safeSubtract_5_0(self):
          self.assertEqual(functions.safeSubtract(5, 0), 5.0)
  
      def test_safeSubtract_0_10(self):
          self.assertEqual(functions.safeSubtract(0,10), -10)
  
      def test_safeSubtract_10_5(self):
          self.assertEqual(functions.safeSubtract(10,5), 5)
  
      #
      # Test safeAvg()
      #
  
      def test_safeAvg_None(self):
          with self.assertRaises(TypeError):
              functions.safeAvg(None)
  
      def test_safeAvg_empty_list(self):
          self.assertEqual(functions.safeAvg([]), None)
  
      def test_safeAvg_all_numbers(self):
          self.assertEqual(functions.safeAvg([1,2,3,4]), 2.5)
  
      def test_safeAvg_all_None(self):
          self.assertEqual(functions.safeAvg([None,None,None,None]), None)
  
      def test_safeAvg_mixed(self):
          self.assertEqual(functions.safeAvg([10,None,5,None]), 7.5)
  
      #
      # Test safeMedian()
      #
  
      def test_safeMedian_None(self):
          with self.assertRaises(TypeError):
              functions.safeMedian(None)
  
      def test_safeMedian_empty_list(self):
          self.assertEqual(functions.safeMedian([]), None)
  
      def test_safeMedian_all_numbers_odd_len(self):
          self.assertEqual(functions.safeMedian([1,2,3,4,5]), 3)
  
      def test_safeMedian_all_numbers_even_len(self):
          self.assertAlmostEqual(functions.safeMedian([1,2,3,4]), 2.5)
  
      def test_safeMedian_all_None(self):
          self.assertEqual(functions.safeMedian([None,None,None,None]), None)
  
      def test_safeMedian_mixed(self):
          self.assertAlmostEqual(functions.safeMedian([10,None,5,None]), 7.5)
  
      #
      # Test safeStdDev()
      #
  
      def test_safeStdDev_None(self):
          with self.assertRaises(TypeError):
              functions.safeStdDev(None)
  
      def test_safeStdDev_empty_list(self):
          self.assertEqual(functions.safeStdDev([]), None)
  
      def test_safeStdDev_all_numbers(self):
          self.assertEqual(functions.safeStdDev([1,2,3,4]), 1.118033988749895)
  
      def test_safeStdDev_all_None(self):
          self.assertEqual(functions.safeStdDev([None,None,None,None]), None)
  
      def test_safeStdDev_mixed(self):
          self.assertEqual(functions.safeStdDev([10,None,5,None]), 2.5)
  
      #
      # Test safeLast()
      #
  
      def test_safeLast_None(self):
          with self.assertRaises(TypeError):
              functions.safeLast(None)
  
      def test_safeLast_empty_list(self):
          self.assertEqual(functions.safeLast([]), None)
  
      def test_safeLast_all_numbers(self):
          self.assertEqual(functions.safeLast([1,2,3,4]), 4)
  
      def test_safeLast_all_None(self):
          self.assertEqual(functions.safeLast([None,None,None,None]), None)
  
      def test_safeLast_mixed(self):
          self.assertEqual(functions.safeLast([10,None,5,None]), 5)
  
      #
      # Test safeMin()
      #
  
      def test_safeMin_None(self):
          with self.assertRaises(TypeError):
              functions.safeMin(None)
  
      def test_safeMin_empty_list(self):
          self.assertEqual(functions.safeMin([]), None)
  
      def test_safeMin_all_numbers(self):
          self.assertEqual(functions.safeMin([1,2,3,4]), 1)
  
      def test_safeMin_all_None(self):
          self.assertEqual(functions.safeMin([None,None,None,None]), None)
  
      def test_safeMin_mixed(self):
          self.assertEqual(functions.safeMin([10,None,5,None]), 5)
  
      #
      # Test safeMax()
      #
  
      def test_safeMax_None(self):
          with self.assertRaises(TypeError):
              functions.safeMax(None)
  
      def test_safeMax_empty_list(self):
          self.assertEqual(functions.safeMax([]), None)
  
      def test_safeMax_all_numbers(self):
          self.assertEqual(functions.safeMax([1,2,3,4]), 4)
  
      def test_safeMax_all_None(self):
          self.assertEqual(functions.safeMax([None,None,None,None]), None)
  
      def test_safeMax_mixed(self):
          self.assertEqual(functions.safeMax([10,None,5,None]), 10)
  
      #
      # Test safeAbs()
      #
  
      def test_safeAbs_None(self):
          self.assertEqual(functions.safeAbs(None), None)
  
      def test_safeAbs_empty_list(self):
          with self.assertRaises(TypeError):
            functions.safeAbs([])
  
      def test_safeAbs_pos_number(self):
          self.assertEqual(functions.safeAbs(1), 1)
  
      def test_safeAbs_neg_numbers(self):
          self.assertEqual(functions.safeAbs(-1), 1)
  
      def test_safeAbs_zero(self):
          self.assertEqual(functions.safeAbs(0), 0)
  
      #
      # Test safeMap()
      #
  
      def test_safeMap_None(self):
          with self.assertRaises(TypeError):
              functions.safeMap(abs, None)
  
      def test_safeMap_empty_list(self):
          self.assertEqual(functions.safeMap(abs, []), None)
  
      def test_safeMap_all_numbers(self):
          self.assertEqual(functions.safeMap(abs, [1,2,3,4]), [1,2,3,4])
  
      def test_safeMap_all_None(self):
          self.assertEqual(functions.safeMap(abs, [None,None,None,None]), None)
  
      def test_safeMap_mixed(self):
          self.assertEqual(functions.safeMap(abs, [10,None,5,None]), [10,5])
  
      #
      # Test gcd()
      #
  
      def test_gcd_None_None(self):
          with self.assertRaises(TypeError):
              functions.gcd(None, None)
  
      def test_gcd_5_None(self):
          with self.assertRaises(TypeError):
              functions.gcd(5, None)
  
      def test_gcd_5_0(self):
          self.assertEqual(functions.gcd(5, 0), 5)
  
      def test_gcd_0_10(self):
          self.assertEqual(functions.gcd(0,10), 10)
  
      def test_gcd_10_5(self):
          self.assertEqual(functions.gcd(10,5), 5)
  
      #
      # Test lcm()
      #
  
      def test_lcm_None_None(self):
          self.assertEqual(functions.lcm(None, None), None)
  
      def test_lcm_5_None(self):
          with self.assertRaises(TypeError):
              functions.lcm(5, None)
  
      def test_lcm_5_0(self):
          self.assertEqual(functions.lcm(5, 0), 0)
  
      def test_lcm_0_10(self):
          self.assertEqual(functions.lcm(0,10), 0)
  
      def test_lcm_10_5(self):
          self.assertEqual(functions.lcm(10,5), 10)
  
      #
      # Test xffValues()
      #
  
      def test_xffValues_None(self):
          self.assertEqual(functions.xffValues(None, 0.5), False)
  
      def test_xffValues_empty_list(self):
          self.assertEqual(functions.xffValues([], 0.5), False)
  
      def test_xffValues_all_numbers(self):
          self.assertEqual(functions.xffValues([1,2,3,4], 0.5), True)
  
      def test_xffValues_all_None(self):
          self.assertEqual(functions.xffValues([None,None,None,None], 0.5), False)
  
      def test_xffValues_mixed(self):
          self.assertEqual(functions.xffValues([10,None,5,None], 0.5), True)
  
      #
      # Test xff()
      #
  
      def test_xff_None(self):
          self.assertEqual(functions.xff(None, None, 0.5), False)
  
      def test_xff(self):
          self.assertEqual(functions.xff(1, 2, 0.5), True)
  
      #
      # Test normalize()
      #
  
      def test_normalize_empty(self):
          with self.assertRaises(NormalizeEmptyResultError):
              functions.normalize([])
  
      def test_normalize_None_values(self):
          seriesList = []
          seriesList.append(TimeSeries("collectd.test-db{0}.load.value", 0, 5, 1, [None, None, None, None, None]))
          self.assertEqual(functions.normalize([seriesList]), (seriesList, 0, 5, 1))
  
      def test_normalize_generate_series_list_input(self):
          seriesList = self._generate_series_list()
          self.assertEqual(functions.normalize([seriesList]), (seriesList, 0, 101, 1))
  
      def test_normalize_different_steps(self):
          seriesList = [
            TimeSeries("test.1", 0, 120 * 31, 120, values=list(range(1, 32))),
            TimeSeries("test.2", 0, 30 * 120, 30, values=list(range(1, 121))),
          ]
  
          self.assertEqual(seriesList[0].start, 0)
          self.assertEqual(seriesList[0].end, 120 * 31)
          self.assertEqual(seriesList[0].step, 120)
          self.assertEqual(seriesList[0].valuesPerPoint, 1)
          self.assertEqual(len(list(seriesList[0])), 31)
  
          self.assertEqual(seriesList[1].start, 0)
          self.assertEqual(seriesList[1].end, 30 * 120)
          self.assertEqual(seriesList[1].step, 30)
          self.assertEqual(seriesList[1].valuesPerPoint, 1)
          self.assertEqual(len(list(seriesList[1])), 120)
  
          # normalize seriesList
          self.assertEqual(functions.normalize([seriesList]), (seriesList, 0, 3720, 120))
  
          self.assertEqual(seriesList[0].start, 0)
          self.assertEqual(seriesList[0].end, 120 * 31)
          self.assertEqual(seriesList[0].step, 120)
          self.assertEqual(seriesList[0].valuesPerPoint, 1)
          self.assertEqual(len(list(seriesList[0])), 31)
  
          self.assertEqual(seriesList[1].start, 0)
          self.assertEqual(seriesList[1].end, 30 * 120)
          self.assertEqual(seriesList[1].step, 30)
          self.assertEqual(seriesList[1].valuesPerPoint, 4)
          self.assertEqual(len(list(seriesList[1])), 30)
  
      #
      # Test matchSeries()
      #
      def test_matchSeries_assert(self):
          seriesList = self._generate_series_list()
          with self.assertRaisesRegexp(AssertionError, 'The number of series in each argument must be the same'):
              functions.matchSeries(seriesList[0], [])
  
      def test_matchSeries_empty(self):
          results=functions.matchSeries([],[])
          for i, (series1, series2) in enumerate(results):
              self.assertEqual(series1, [])
              self.assertEqual(series2, [])
  
      def test_matchSeries(self):
          seriesList1 = self._gen_series_list_with_data(
              key=[
                  'collectd.test-db3.load.value',
                  'collectd.test-db1.load.value',
                  'collectd.test-db2.load.value',
                  'collectd.test-db4.load.value'
              ],
              end=1,
              data=[
                  [3,30,31],
                  [1,10,11],
                  [2,20,21],
                  [4,40,41]
              ]
          )
  
          seriesList2 = self._gen_series_list_with_data(
              key=[
                  'collectd.test-db4.load.value',
                  'collectd.test-db3.load.value',
                  'collectd.test-db1.load.value',
                  'collectd.test-db2.load.value'
              ],
              end=1,
              data=[
                  [4
  
  [... the rest is omitted ...]
files: webapp/tests/test_functions.py
files: import copy
  import inspect
  import math
  import pytz
  import six
  
  from datetime import datetime
  from fnmatch import fnmatch
  from mock import patch, call, MagicMock
  from os.path import dirname, join
  from six.moves import range
  
  from .base import TestCase
  from django.conf import settings
  
  try:
    from django.urls import reverse
  except ImportError:  # Django < 1.10
    from django.core.urlresolvers import reverse
  
  from graphite.errors import NormalizeEmptyResultError
  from graphite.functions import _SeriesFunctions, loadFunctions
  from graphite.render.datalib import TimeSeries
  from graphite.render import functions
  from graphite.render.evaluator import evaluateTarget
  from graphite.render.grammar import grammar
  from graphite.tags.utils import TaggedSeries
  from graphite.util import json
  
  
  def return_greater(series, value):
      return [i for i in series if i is not None and i > value]
  
  
  def return_less(series, value):
      return [i for i in series if i is not None and i < value]
  
  
  class FunctionsTest(TestCase):
  
      def setUp(self):
        super(FunctionsTest, self).setUp()
        # Display more diff.
        self.maxDiff = 1024
  
      #
      # Test safeSum()
      #
  
      def test_safeSum_None(self):
          with self.assertRaises(TypeError):
              functions.safeSum(None)
  
      def test_safeSum_empty_list(self):
          self.assertEqual(functions.safeSum([]), None)
  
      def test_safeSum_all_numbers(self):
          self.assertEqual(functions.safeSum([1,2,3,4]), 10)
  
      def test_safeSum_all_None(self):
          self.assertEqual(functions.safeSum([None,None,None,None]), None)
  
      def test_safeSum_mixed(self):
          self.assertEqual(functions.safeSum([10,None,5,None]), 15)
  
      #
      # Test safeDiff()
      #
  
      def test_safeDiff_None(self):
          with self.assertRaises(TypeError):
              functions.safeDiff(None)
  
      def test_safeDiff_empty_list(self):
          self.assertEqual(functions.safeDiff([]), None)
  
      def test_safeDiff_all_numbers(self):
          self.assertEqual(functions.safeDiff([1,2,3,4]), -8)
  
      def test_safeDiff_all_None(self):
          self.assertEqual(functions.safeDiff([None,None,None,None]), None)
  
      def test_safeDiff_mixed(self):
          self.assertEqual(functions.safeDiff([10,None,5,None]), 5)
  
      #
      # Test safeLen()
      #
  
      def test_safeLen_None(self):
          with self.assertRaises(TypeError):
              functions.safeLen(None)
  
      def test_safeLen_empty_list(self):
          self.assertEqual(functions.safeLen([]), 0)
  
      def test_safeLen_all_numbers(self):
          self.assertEqual(functions.safeLen([1,2,3,4]), 4)
  
      def test_safeLen_all_None(self):
          self.assertEqual(functions.safeLen([None,None,None,None]), 0)
  
      def test_safeLen_mixed(self):
          self.assertEqual(functions.safeLen([10,None,5,None]), 2)
  
      #
      # Test safeDiv()
      #
  
      def test_safeDiv_None_None(self):
          self.assertEqual(functions.safeDiv(None, None), None)
  
      def test_safeDiv_5_None(self):
          self.assertEqual(functions.safeDiv(5, None), None)
  
      def test_safeDiv_5_0(self):
          self.assertEqual(functions.safeDiv(5, 0), None)
  
      def test_safeDiv_0_10(self):
          self.assertEqual(functions.safeDiv(0,10), 0)
  
      def test_safeDiv_10_5(self):
          self.assertEqual(functions.safeDiv(10,5), 2)
  
      #
      # Test safePow()
      #
  
      def test_safePow_None_None(self):
          self.assertEqual(functions.safePow(None, None), None)
  
      def test_safePow_5_None(self):
          self.assertEqual(functions.safePow(5, None), None)
  
      def test_safePow_5_0(self):
          self.assertEqual(functions.safePow(5, 0), 1.0)
  
      def test_safePow_0_10(self):
          self.assertEqual(functions.safePow(0,10), 0)
  
      def test_safePow_10_5(self):
          self.assertEqual(functions.safePow(10,5), 100000.0)
  
      #
      # Test safeMul()
      #
  
      def test_safeMul_None_None(self):
          self.assertEqual(functions.safeMul(None, None), None)
  
      def test_safeMul_5_None(self):
          self.assertEqual(functions.safeMul(5, None), None)
  
      def test_safeMul_5_0(self):
          self.assertEqual(functions.safeMul(5, 0), 0.0)
  
      def test_safeMul_0_10(self):
          self.assertEqual(functions.safeMul(0,10), 0)
  
      def test_safeMul_10_5(self):
          self.assertEqual(functions.safeMul(10,5), 50.0)
  
      #
      # Test safeSubtract()
      #
  
      def test_safeSubtract_None_None(self):
          self.assertEqual(functions.safeSubtract(None, None), None)
  
      def test_safeSubtract_5_None(self):
          self.assertEqual(functions.safeSubtract(5, None), None)
  
      def test_safeSubtract_5_0(self):
          self.assertEqual(functions.safeSubtract(5, 0), 5.0)
  
      def test_safeSubtract_0_10(self):
          self.assertEqual(functions.safeSubtract(0,10), -10)
  
      def test_safeSubtract_10_5(self):
          self.assertEqual(functions.safeSubtract(10,5), 5)
  
      #
      # Test safeAvg()
      #
  
      def test_safeAvg_None(self):
          with self.assertRaises(TypeError):
              functions.safeAvg(None)
  
      def test_safeAvg_empty_list(self):
          self.assertEqual(functions.safeAvg([]), None)
  
      def test_safeAvg_all_numbers(self):
          self.assertEqual(functions.safeAvg([1,2,3,4]), 2.5)
  
      def test_safeAvg_all_None(self):
          self.assertEqual(functions.safeAvg([None,None,None,None]), None)
  
      def test_safeAvg_mixed(self):
          self.assertEqual(functions.safeAvg([10,None,5,None]), 7.5)
  
      #
      # Test safeMedian()
      #
  
      def test_safeMedian_None(self):
          with self.assertRaises(TypeError):
              functions.safeMedian(None)
  
      def test_safeMedian_empty_list(self):
          self.assertEqual(functions.safeMedian([]), None)
  
      def test_safeMedian_all_numbers_odd_len(self):
          self.assertEqual(functions.safeMedian([1,2,3,4,5]), 3)
  
      def test_safeMedian_all_numbers_even_len(self):
          self.assertAlmostEqual(functions.safeMedian([1,2,3,4]), 2.5)
  
      def test_safeMedian_all_None(self):
          self.assertEqual(functions.safeMedian([None,None,None,None]), None)
  
      def test_safeMedian_mixed(self):
          self.assertAlmostEqual(functions.safeMedian([10,None,5,None]), 7.5)
  
      #
      # Test safeStdDev()
      #
  
      def test_safeStdDev_None(self):
          with self.assertRaises(TypeError):
              functions.safeStdDev(None)
  
      def test_safeStdDev_empty_list(self):
          self.assertEqual(functions.safeStdDev([]), None)
  
      def test_safeStdDev_all_numbers(self):
          self.assertEqual(functions.safeStdDev([1,2,3,4]), 1.118033988749895)
  
      def test_safeStdDev_all_None(self):
          self.assertEqual(functions.safeStdDev([None,None,None,None]), None)
  
      def test_safeStdDev_mixed(self):
          self.assertEqual(functions.safeStdDev([10,None,5,None]), 2.5)
  
      #
      # Test safeLast()
      #
  
      def test_safeLast_None(self):
          with self.assertRaises(TypeError):
              functions.safeLast(None)
  
      def test_safeLast_empty_list(self):
          self.assertEqual(functions.safeLast([]), None)
  
      def test_safeLast_all_numbers(self):
          self.assertEqual(functions.safeLast([1,2,3,4]), 4)
  
      def test_safeLast_all_None(self):
          self.assertEqual(functions.safeLast([None,None,None,None]), None)
  
      def test_safeLast_mixed(self):
          self.assertEqual(functions.safeLast([10,None,5,None]), 5)
  
      #
      # Test safeMin()
      #
  
      def test_safeMin_None(self):
          with self.assertRaises(TypeError):
              functions.safeMin(None)
  
      def test_safeMin_empty_list(self):
          self.assertEqual(functions.safeMin([]), None)
  
      def test_safeMin_all_numbers(self):
          self.assertEqual(functions.safeMin([1,2,3,4]), 1)
  
      def test_safeMin_all_None(self):
          self.assertEqual(functions.safeMin([None,None,None,None]), None)
  
      def test_safeMin_mixed(self):
          self.assertEqual(functions.safeMin([10,None,5,None]), 5)
  
      #
      # Test safeMax()
      #
  
      def test_safeMax_None(self):
          with self.assertRaises(TypeError):
              functions.safeMax(None)
  
      def test_safeMax_empty_list(self):
          self.assertEqual(functions.safeMax([]), None)
  
      def test_safeMax_all_numbers(self):
          self.assertEqual(functions.safeMax([1,2,3,4]), 4)
  
      def test_safeMax_all_None(self):
          self.assertEqual(functions.safeMax([None,None,None,None]), None)
  
      def test_safeMax_mixed(self):
          self.assertEqual(functions.safeMax([10,None,5,None]), 10)
  
      #
      # Test safeAbs()
      #
  
      def test_safeAbs_None(self):
          self.assertEqual(functions.safeAbs(None), None)
  
      def test_safeAbs_empty_list(self):
          with self.assertRaises(TypeError):
            functions.safeAbs([])
  
      def test_safeAbs_pos_number(self):
          self.assertEqual(functions.safeAbs(1), 1)
  
      def test_safeAbs_neg_numbers(self):
          self.assertEqual(functions.safeAbs(-1), 1)
  
      def test_safeAbs_zero(self):
          self.assertEqual(functions.safeAbs(0), 0)
  
      #
      # Test safeMap()
      #
  
      def test_safeMap_None(self):
          with self.assertRaises(TypeError):
              functions.safeMap(abs, None)
  
      def test_safeMap_empty_list(self):
          self.assertEqual(functions.safeMap(abs, []), None)
  
      def test_safeMap_all_numbers(self):
          self.assertEqual(functions.safeMap(abs, [1,2,3,4]), [1,2,3,4])
  
      def test_safeMap_all_None(self):
          self.assertEqual(functions.safeMap(abs, [None,None,None,None]), None)
  
      def test_safeMap_mixed(self):
          self.assertEqual(functions.safeMap(abs, [10,None,5,None]), [10,5])
  
      #
      # Test gcd()
      #
  
      def test_gcd_None_None(self):
          with self.assertRaises(TypeError):
              functions.gcd(None, None)
  
      def test_gcd_5_None(self):
          with self.assertRaises(TypeError):
              functions.gcd(5, None)
  
      def test_gcd_5_0(self):
          self.assertEqual(functions.gcd(5, 0), 5)
  
      def test_gcd_0_10(self):
          self.assertEqual(functions.gcd(0,10), 10)
  
      def test_gcd_10_5(self):
          self.assertEqual(functions.gcd(10,5), 5)
  
      #
      # Test lcm()
      #
  
      def test_lcm_None_None(self):
          self.assertEqual(functions.lcm(None, None), None)
  
      def test_lcm_5_None(self):
          with self.assertRaises(TypeError):
              functions.lcm(5, None)
  
      def test_lcm_5_0(self):
          self.assertEqual(functions.lcm(5, 0), 0)
  
      def test_lcm_0_10(self):
          self.assertEqual(functions.lcm(0,10), 0)
  
      def test_lcm_10_5(self):
          self.assertEqual(functions.lcm(10,5), 10)
  
      #
      # Test xffValues()
      #
  
      def test_xffValues_None(self):
          self.assertEqual(functions.xffValues(None, 0.5), False)
  
      def test_xffValues_empty_list(self):
          self.assertEqual(functions.xffValues([], 0.5), False)
  
      def test_xffValues_all_numbers(self):
          self.assertEqual(functions.xffValues([1,2,3,4], 0.5), True)
  
      def test_xffValues_all_None(self):
          self.assertEqual(functions.xffValues([None,None,None,None], 0.5), False)
  
      def test_xffValues_mixed(self):
          self.assertEqual(functions.xffValues([10,None,5,None], 0.5), True)
  
      #
      # Test xff()
      #
  
      def test_xff_None(self):
          self.assertEqual(functions.xff(None, None, 0.5), False)
  
      def test_xff(self):
          self.assertEqual(functions.xff(1, 2, 0.5), True)
  
      #
      # Test normalize()
      #
  
      def test_normalize_empty(self):
          with self.assertRaises(NormalizeEmptyResultError):
              functions.normalize([])
  
      def test_normalize_None_values(self):
          seriesList = []
          seriesList.append(TimeSeries("collectd.test-db{0}.load.value", 0, 5, 1, [None, None, None, None, None]))
          self.assertEqual(functions.normalize([seriesList]), (seriesList, 0, 5, 1))
  
      def test_normalize_generate_series_list_input(self):
          seriesList = self._generate_series_list()
          self.assertEqual(functions.normalize([seriesList]), (seriesList, 0, 101, 1))
  
      def test_normalize_different_steps(self):
          seriesList = [
            TimeSeries("test.1", 0, 120 * 31, 120, values=list(range(1, 32))),
            TimeSeries("test.2", 0, 30 * 120, 30, values=list(range(1, 121))),
          ]
  
          self.assertEqual(seriesList[0].start, 0)
          self.assertEqual(seriesList[0].end, 120 * 31)
          self.assertEqual(seriesList[0].step, 120)
          self.assertEqual(seriesList[0].valuesPerPoint, 1)
          self.assertEqual(len(list(seriesList[0])), 31)
  
          self.assertEqual(seriesList[1].start, 0)
          self.assertEqual(seriesList[1].end, 30 * 120)
          self.assertEqual(seriesList[1].step, 30)
          self.assertEqual(seriesList[1].valuesPerPoint, 1)
          self.assertEqual(len(list(seriesList[1])), 120)
  
          # normalize seriesList
          self.assertEqual(functions.normalize([seriesList]), (seriesList, 0, 3720, 120))
  
          self.assertEqual(seriesList[0].start, 0)
          self.assertEqual(seriesList[0].end, 120 * 31)
          self.assertEqual(seriesList[0].step, 120)
          self.assertEqual(seriesList[0].valuesPerPoint, 1)
          self.assertEqual(len(list(seriesList[0])), 31)
  
          self.assertEqual(seriesList[1].start, 0)
          self.assertEqual(seriesList[1].end, 30 * 120)
          self.assertEqual(seriesList[1].step, 30)
          self.assertEqual(seriesList[1].valuesPerPoint, 4)
          self.assertEqual(len(list(seriesList[1])), 30)
  
      #
      # Test matchSeries()
      #
      def test_matchSeries_assert(self):
          seriesList = self._generate_series_list()
          with self.assertRaisesRegexp(AssertionError, 'The number of series in each argument must be the same'):
              functions.matchSeries(seriesList[0], [])
  
      def test_matchSeries_empty(self):
          results=functions.matchSeries([],[])
          for i, (series1, series2) in enumerate(results):
              self.assertEqual(series1, [])
              self.assertEqual(series2, [])
  
      def test_matchSeries(self):
          seriesList1 = self._gen_series_list_with_data(
              key=[
                  'collectd.test-db3.load.value',
                  'collectd.test-db1.load.value',
                  'collectd.test-db2.load.value',
                  'collectd.test-db4.load.value'
              ],
              end=1,
              data=[
                  [3,30,31],
                  [1,10,11],
                  [2,20,21],
                  [4,40,41]
              ]
          )
  
          seriesList2 = self._gen_series_list_with_data(
              key=[
                  'collectd.test-db4.load.value',
                  'collectd.test-db3.load.value',
                  'collectd.test-db1.load.value',
                  'collectd.test-db2.load.value'
              ],
              end=1,
              data=[
                  [4
  
  [... the rest is omitted ...]
files: webapp/tests/test_functions.py