def asPercent(seriesList1, seriesList2orNumber): assert len( seriesList1 ) == 1, "asPercent series arguments must reference *exactly* 1 series" series1 = seriesList1[0] if type(seriesList2orNumber) is list: assert len( seriesList2orNumber ) == 1, "asPercent series arguments must reference *exactly* 1 series" series2 = seriesList2orNumber[0] name = "asPercent(%s,%s)" % (series1.name, series2.name) series = (series1, series2) step = reduce(lcm, [s.step for s in series]) for s in series: s.consolidate(step / s.step) start = min([s.start for s in series]) end = max([s.end for s in series]) end -= (end - start) % step values = (safeMul(safeDiv(v1, v2), 100.0) for v1, v2 in izip(*series)) else: number = float(seriesList2orNumber) name = "asPercent(%s,%.1f)" % (series1.name, number) step = series1.step start = series1.start end = series1.end values = (safeMul(safeDiv(v, number), 100.0) for v in series1) series = TimeSeries(name, start, end, step, values) series.pathExpression = name return [series]
def nonNegativeDerivative( seriesList, maxValue=None ): # useful for watching counter metrics that occasionally wrap results = [] for series in seriesList: newValues = [] prev = None for val in series: if None in (prev, val): newValues.append(None) prev = val continue diff = val - prev if diff >= 0: newValues.append(diff) elif maxValue is not None and maxValue >= val: newValues.append(prev + (maxValue - val)) else: newValues.append(None) prev = val newName = "nonNegativeDerivative(%s)" % series.name newSeries = TimeSeries(newName, series.start, series.end, series.step, newValues) newSeries.pathExpression = newName results.append(newSeries) return results
def asPercent(seriesList1,seriesList2orNumber): assert len(seriesList1) == 1, "asPercent series arguments must reference *exactly* 1 series" series1 = seriesList1[0] if type(seriesList2orNumber) is list: assert len(seriesList2orNumber) == 1, "asPercent series arguments must reference *exactly* 1 series" series2 = seriesList2orNumber[0] name = "asPercent(%s,%s)" % (series1.name,series2.name) series = (series1,series2) step = reduce(lcm,[s.step for s in series]) for s in series: s.consolidate( step / s.step ) start = min([s.start for s in series]) end = max([s.end for s in series]) end -= (end - start) % step values = ( safeMul( safeDiv(v1,v2), 100.0 ) for v1,v2 in izip(*series) ) else: number = float(seriesList2orNumber) name = "asPercent(%s,%.1f)" % (series1.name,number) step = series1.step start = series1.start end = series1.end values = ( safeMul( safeDiv(v,number), 100.0 ) for v in series1 ) series = TimeSeries(name,start,end,step,values) series.pathExpression = name return [series]
def diffSeries(*seriesLists): (seriesList,start,end,step) = normalize(seriesLists) name = "diffSeries(%s)" % ','.join(set([s.pathExpression for s in seriesList])) values = ( safeDiff(row) for row in izip(*seriesList) ) series = TimeSeries(name,start,end,step,values) series.pathExpression = name return [series]
def diffSeries(*seriesLists): (seriesList, start, end, step) = normalize(seriesLists) name = "diffSeries(%s)" % ','.join( set([s.pathExpression for s in seriesList])) values = (safeDiff(row) for row in izip(*seriesList)) series = TimeSeries(name, start, end, step, values) series.pathExpression = name return [series]
def averageSeries(*seriesLists): (seriesList,start,end,step) = normalize(seriesLists) #name = "averageSeries(%s)" % ','.join((s.name for s in seriesList)) name = "averageSeries(%s)" % ','.join(set([s.pathExpression for s in seriesList])) values = ( safeDiv(safeSum(row),safeLen(row)) for row in izip(*seriesList) ) series = TimeSeries(name,start,end,step,values) series.pathExpression = name return [series]
def boundedAverageSeries(min, max, *seriesLists): (seriesList,start,end,step) = normalize(seriesLists) #name = "averageSeries(%s)" % ','.join((s.name for s in seriesList)) name = "averageSeries(%s)" % ','.join(set([s.pathExpression for s in seriesList])) bounded_rows = (safeBound(row, min, max) for row in izip(*seriesList)) values = ( safeDiv(safeSum(row),safeLen(row)) for row in bounded_rows ) #XXX izip series = TimeSeries(name,start,end,step,values) series.pathExpression = name return [series]
def averageSeries(*seriesLists): (seriesList, start, end, step) = normalize(seriesLists) #name = "averageSeries(%s)" % ','.join((s.name for s in seriesList)) name = "averageSeries(%s)" % ','.join( set([s.pathExpression for s in seriesList])) values = (safeDiv(safeSum(row), safeLen(row)) for row in izip(*seriesList)) series = TimeSeries(name, start, end, step, values) series.pathExpression = name return [series]
def stdev(seriesList, time): count = 0 for series in seriesList: stddevs = TimeSeries("stddev(%s,%.1f)" % (series.name, float(time)), series.start, series.end, series.step, []) stddevs.pathExpression = "stddev(%s,%.1f)" % (series.name, float(time)) avg = safeDiv(safeSum(series[:time]), time) sumOfSquares = sum(map(lambda (x): x * x, series[:time])) (sd, sumOfSquares) = doStdDev(sumOfSquares, 0, 0, time, avg) stddevs.append(sd) for (index, el) in enumerate(series[time:]): if el is None: continue toDrop = series[index] if toDrop is None: toDrop = 0 s = safeSum([safeMul(time, avg), el, -toDrop]) avg = safeDiv(s, time) (sd, sumOfSquares) = doStdDev(sumOfSquares, toDrop, series[index + time], time, avg) stddevs.append(sd) for i in range(0, time - 1): stddevs.insert(0, None) seriesList[count] = stddevs count = count + 1 return seriesList
def sumSeries(*seriesLists): try: (seriesList,start,end,step) = normalize(seriesLists) except: return [] #name = "sumSeries(%s)" % ','.join((s.name for s in seriesList)) name = "sumSeries(%s)" % ','.join(set([s.pathExpression for s in seriesList])) values = ( safeSum(row) for row in izip(*seriesList) ) series = TimeSeries(name,start,end,step,values) series.pathExpression = name return [series]
def sumSeries(*seriesLists): try: (seriesList, start, end, step) = normalize(seriesLists) except: return [] #name = "sumSeries(%s)" % ','.join((s.name for s in seriesList)) name = "sumSeries(%s)" % ','.join( set([s.pathExpression for s in seriesList])) values = (safeSum(row) for row in izip(*seriesList)) series = TimeSeries(name, start, end, step, values) series.pathExpression = name return [series]
def evaluateTokens(tokens, timeInterval, originalTime = None): if tokens.expression: if tokens[0][0][0] == "timeShift": delta = timeInterval[0] - parseATTime(tokens[0][0][1][1]['string'].strip('"')) delta += timeInterval[1] - timeInterval[0] originalTime = timeInterval timeInterval = (timeInterval[0] - delta, timeInterval[1] - delta) return evaluateTokens(tokens.expression, timeInterval, originalTime) elif tokens.pathExpression: pathExpr = tokens.pathExpression if pathExpr.lower().startswith('graphite.'): pathExpr = pathExpr[9:] seriesList = [] (startTime,endTime) = originalTime or timeInterval for dbFile in settings.STORE.find(pathExpr): log.metric_access(dbFile.metric_path) getCacheResults = CarbonLink.sendRequest(dbFile.real_metric) dbResults = dbFile.fetch( timestamp(startTime), timestamp(endTime) ) results = mergeResults(dbResults, getCacheResults()) if not results: continue (timeInfo,values) = results (start,end,step) = timeInfo series = TimeSeries(dbFile.metric_path, start, end, step, values) series.pathExpression = pathExpr #hack to pass expressions through to render functions series.start = time.mktime(startTime.timetuple()) series.end = time.mktime(endTime.timetuple()) seriesList.append(series) return seriesList elif tokens.call: func = SeriesFunctions[tokens.call.func] args = [evaluateTokens(arg, timeInterval, originalTime) for arg in tokens.call.args] return func(*args) elif tokens.number: if tokens.number.integer: return int(tokens.number.integer) elif tokens.number.float: return float(tokens.number.float) elif tokens.string: return str(tokens.string)[1:-1]
def log(seriesList, base=10): results = [] for series in seriesList: newValues = [] for val in series: if val is None: newValues.append(None) else: newValues.append(math.log(val, base)) newName = "log(%s, %s)" % (series.name, base) newSeries = TimeSeries(newName, series.start, series.end, series.step, newValues) newSeries.pathExpression = newName results.append(newSeries) return results
def stdev(seriesList,time): count = 0 for series in seriesList: stddevs = TimeSeries("stddev(%s,%.1f)" % (series.name,float(time)),series.start,series.end,series.step,[]) stddevs.pathExpression = "stddev(%s,%.1f)" % (series.name,float(time)) avg = safeDiv(safeSum(series[:time]), time) sumOfSquares = sum(map(lambda(x): x * x, series[:time])) (sd, sumOfSquares) = doStdDev(sumOfSquares, 0, 0, time, avg) stddevs.append(sd) for (index, el) in enumerate(series[time:]): if el is None: continue toDrop = series[index] if toDrop is None: toDrop = 0 s = safeSum([safeMul(time, avg), el, -toDrop]) avg = safeDiv(s, time) (sd, sumOfSquares) = doStdDev(sumOfSquares, toDrop, series[index+time], time, avg) stddevs.append(sd) for i in range(0, time-1): stddevs.insert(0, None) seriesList[count] = stddevs count = count + 1 return seriesList
def integral(seriesList): results = [] for series in seriesList: newValues = [] current = 0.0 for val in series: if val is None: newValues.append(None) else: current += val newValues.append(current) newName = "integral(%s)" % series.name newSeries = TimeSeries(newName, series.start, series.end, series.step, newValues) newSeries.pathExpression = newName results.append(newSeries) return results
def evaluateTokens(tokens, timeInterval): if tokens.expression: return evaluateTokens(tokens.expression, timeInterval) elif tokens.pathExpression: pathExpr = tokens.pathExpression if pathExpr.lower().startswith('graphite.'): pathExpr = pathExpr[9:] seriesList = [] (startTime,endTime) = timeInterval for dbFile in settings.STORE.find(pathExpr): log.metric_access(dbFile.metric_path) getCacheResults = CarbonLink.sendRequest(dbFile.real_metric) dbResults = dbFile.fetch( timestamp(startTime), timestamp(endTime) ) results = mergeResults(dbResults, getCacheResults()) if not results: continue (timeInfo,values) = results (start,end,step) = timeInfo series = TimeSeries(dbFile.metric_path, start, end, step, values) series.pathExpression = pathExpr #hack to pass expressions through to render functions seriesList.append(series) return seriesList elif tokens.call: func = SeriesFunctions[tokens.call.func] args = [evaluateTokens(arg, timeInterval) for arg in tokens.call.args] try: return func(*args) except: return [] elif tokens.number: if tokens.number.integer: return int(tokens.number.integer) elif tokens.number.float: return float(tokens.number.float) elif tokens.string: return str(tokens.string)[1:-1]
def derivative(seriesList): results = [] for series in seriesList: newValues = [] prev = None for val in series: if None in (prev,val): newValues.append(None) prev = val continue newValues.append(val - prev) prev = val newName = "derivative(%s)" % series.name newSeries = TimeSeries(newName, series.start, series.end, series.step, newValues) newSeries.pathExpression = newName results.append(newSeries) return results
def nonNegativeDerivative(seriesList): # useful for watching counter metrics that occasionally wrap results = [] for series in seriesList: newValues = [] prev = None for val in series: if None in (prev,val): newValues.append(None) prev = val continue newValues.append( max(val - prev, 0) ) prev = val newName = "nonNegativeDerivative(%s)" % series.name newSeries = TimeSeries(newName, series.start, series.end, series.step, newValues) newSeries.pathExpression = newName results.append(newSeries) return results
def derivative(seriesList): results = [] for series in seriesList: newValues = [] prev = None for val in series: if None in (prev, val): newValues.append(None) prev = val continue newValues.append(val - prev) prev = val newName = "derivative(%s)" % series.name newSeries = TimeSeries(newName, series.start, series.end, series.step, newValues) newSeries.pathExpression = newName results.append(newSeries) return results
def nonNegativeDerivative(seriesList, maxValue=None): # useful for watching counter metrics that occasionally wrap results = [] for series in seriesList: newValues = [] prev = None for val in series: if None in (prev,val): newValues.append(None) prev = val continue diff = val - prev if diff >= 0: newValues.append(diff) elif maxValue is not None and maxValue >= val: newValues.append( prev + (maxValue - val) ) else: newValues.append(None) prev = val newName = "nonNegativeDerivative(%s)" % series.name newSeries = TimeSeries(newName, series.start, series.end, series.step, newValues) newSeries.pathExpression = newName results.append(newSeries) return results
def movingAverage(seriesList, time): count = 0 for series in seriesList: movAvg = TimeSeries( "movingAverage(%s,%.1f)" % (series.name, float(time)), series.start, series.end, series.step, []) movAvg.pathExpression = "movingAverage(%s,%.1f)" % (series.name, float(time)) avg = safeDiv(safeSum(series[:time]), time) movAvg.append(avg) for (index, el) in enumerate(series[time:]): if el is None: continue toDrop = series[index] if toDrop is None: toDrop = 0 s = safeSum([safeMul(time, avg), el, -toDrop]) avg = safeDiv(s, time) movAvg.append(avg) for i in range(0, time - 1): movAvg.insert(0, None) seriesList[count] = movAvg count = count + 1 return seriesList
def movingAverage(seriesList,time): count = 0 for series in seriesList: movAvg = TimeSeries("movingAverage(%s,%.1f)" % (series.name,float(time)),series.start,series.end,series.step,[]) movAvg.pathExpression = "movingAverage(%s,%.1f)" % (series.name,float(time)) avg = safeDiv(safeSum(series[:time]), time) movAvg.append(avg) for (index, el) in enumerate(series[time:]): if el is None: continue toDrop = series[index] if toDrop is None: toDrop = 0 s = safeSum([safeMul(time, avg), el, -toDrop]) avg = safeDiv(s, time) movAvg.append(avg) for i in range(0, time-1): movAvg.insert(0, None) seriesList[count] = movAvg count = count + 1 return seriesList
def drawLines(self, width=None, dash=None, linecap='butt', linejoin='miter'): if not width: width = self.lineWidth self.ctx.set_line_width(width) originalWidth = width width = float(int(width) % 2) / 2 if dash: self.ctx.set_dash(dash,1) else: self.ctx.set_dash([],0) self.ctx.set_line_cap({ 'butt' : cairo.LINE_CAP_BUTT, 'round' : cairo.LINE_CAP_ROUND, 'square' : cairo.LINE_CAP_SQUARE, }[linecap]) self.ctx.set_line_join({ 'miter' : cairo.LINE_JOIN_MITER, 'round' : cairo.LINE_JOIN_ROUND, 'bevel' : cairo.LINE_JOIN_BEVEL, }[linejoin]) # stack the values if self.areaMode == 'stacked': total = [] for series in self.data: for i in range(len(series)): if len(total) <= i: total.append(0) if series[i] is not None: original = series[i] series[i] += total[i] total[i] += original self.data = reverse_sort(self.data) # setup the clip region self.ctx.set_line_width(1.0) self.ctx.rectangle(self.area['xmin'], self.area['ymin'], self.area['xmax'] - self.area['xmin'], self.area['ymax'] - self.area['ymin']) self.ctx.clip() self.ctx.set_line_width(originalWidth) if self.params.get('areaAlpha') and self.areaMode == 'first': alphaSeries = TimeSeries(None, self.data[0].start, self.data[0].end, self.data[0].step, [x for x in self.data[0]]) alphaSeries.xStep = self.data[0].xStep alphaSeries.color = self.data[0].color try: alphaSeries.options['alpha'] = float(self.params['areaAlpha']) except ValueError: pass self.data.insert(0, alphaSeries) for series in self.data: if series.options.has_key('noDraw'): continue if series.options.has_key('lineWidth'): # adjusts the lineWidth of this line if option is set on the series self.ctx.set_line_width(series.options['lineWidth']) if series.options.has_key('dashed'): # turn on dashing if dashed option set self.ctx.set_dash([ series.options['dashed'] ],1) else: self.ctx.set_dash([], 0) x = float(self.area['xmin']) + (self.lineWidth / 2.0) y = float(self.area['ymin']) self.setColor( series.color, series.options.get('alpha') or 1.0) fromNone = True idx = -1 return_path = [] for value in series: idx = idx+1 if value is None and self.params.get('drawNullAsZero'): value = 0.0 if series.options.has_key('areaFill'): value_btm = series.options['lowBoundData'][idx] if value_btm is None: value = None if value is None: if not fromNone: if series.options.has_key('areaFill'): # close up the shape by drawing to the previous bottom points in reverse for pair in reverse_sort(return_path): self.ctx.line_to(pair[0], pair[1]) return_path = [] self.ctx.close_path() self.ctx.fill() elif self.areaMode != 'none': #Close off and fill area before unknown interval self.ctx.line_to(x, self.area['ymax']) self.ctx.close_path() self.ctx.fill() x += series.xStep fromNone = True else: y = self.area['ymax'] - ((float(value) - self.yBottom) * self.yScaleFactor) if y < 0: y = 0 if series.options.has_key('areaFill'): value_btm = series.options['lowBoundData'][idx] y_btm = self.area['ymax'] - ((float(value_btm) - self.yBottom) * self.yScaleFactor) if y_btm < 0: y_btm = 0 return_path.append((x + series.xStep, y_btm)) if series.options.has_key('drawAsInfinite') and value > 0: self.ctx.move_to(x, self.area['ymax']) self.ctx.line_to(x, self.area['ymin']) self.ctx.stroke() x += series.xStep continue if self.lineMode == 'staircase': if fromNone: if series.options.has_key('areaFill'): self.ctx.move_to(x,y_btm) self.ctx.line_to(x,y) elif self.areaMode != 'none': self.ctx.move_to(x,self.area['ymax']) self.ctx.line_to(x,y) else: self.ctx.move_to(x,y) else: self.ctx.line_to(x,y) x += series.xStep self.ctx.line_to(x,y) elif self.lineMode == 'slope': if fromNone: if series.options.has_key('areaFill'): self.ctx.move_to(x,y_btm) self.ctx.line_to(x,y) elif self.areaMode != 'none': self.ctx.move_to(x,self.area['ymax']) self.ctx.line_to(x,y) else: self.ctx.move_to(x,y) x += series.xStep self.ctx.line_to(x,y) fromNone = False if series.options.has_key('areaFill'): for pair in reverse_sort(return_path): self.ctx.line_to(pair[0], pair[1]) self.ctx.close_path() self.ctx.fill() elif self.areaMode != 'none': self.ctx.line_to(x, self.area['ymax']) self.ctx.close_path() self.ctx.fill() if self.areaMode == 'first': self.areaMode = 'none' #This ensures only the first line is drawn as area else: self.ctx.stroke() self.ctx.set_line_width(originalWidth) # return to the original line width if series.options.has_key('dash'): # if we changed the dash setting before, change it back now if dash: self.ctx.set_dash(dash,1) else: self.ctx.set_dash([],0)
def drawLines(self, width=None, dash=None, linecap='butt', linejoin='miter'): if not width: width = self.lineWidth self.ctx.set_line_width(width) originalWidth = width width = float(int(width) % 2) / 2 if dash: self.ctx.set_dash(dash, 1) else: self.ctx.set_dash([], 0) self.ctx.set_line_cap({ 'butt': cairo.LINE_CAP_BUTT, 'round': cairo.LINE_CAP_ROUND, 'square': cairo.LINE_CAP_SQUARE, }[linecap]) self.ctx.set_line_join({ 'miter': cairo.LINE_JOIN_MITER, 'round': cairo.LINE_JOIN_ROUND, 'bevel': cairo.LINE_JOIN_BEVEL, }[linejoin]) # stack the values if self.areaMode == 'stacked': total = [] for series in self.data: for i in range(len(series)): if len(total) <= i: total.append(0) if series[i] is not None: original = series[i] series[i] += total[i] total[i] += original self.data = reverse_sort(self.data) # setup the clip region self.ctx.set_line_width(1.0) self.ctx.rectangle(self.area['xmin'], self.area['ymin'], self.area['xmax'] - self.area['xmin'], self.area['ymax'] - self.area['ymin']) self.ctx.clip() self.ctx.set_line_width(originalWidth) if self.params.get('areaAlpha') and self.areaMode == 'first': alphaSeries = TimeSeries(None, self.data[0].start, self.data[0].end, self.data[0].step, [x for x in self.data[0]]) alphaSeries.xStep = self.data[0].xStep alphaSeries.color = self.data[0].color try: alphaSeries.options['alpha'] = float(self.params['areaAlpha']) except ValueError: pass self.data.insert(0, alphaSeries) for series in self.data: if series.options.has_key('noDraw'): continue if series.options.has_key( 'lineWidth' ): # adjusts the lineWidth of this line if option is set on the series self.ctx.set_line_width(series.options['lineWidth']) if series.options.has_key( 'dashed'): # turn on dashing if dashed option set self.ctx.set_dash([series.options['dashed']], 1) else: self.ctx.set_dash([], 0) x = float(self.area['xmin']) + (self.lineWidth / 2.0) y = float(self.area['ymin']) self.setColor(series.color, series.options.get('alpha') or 1.0) fromNone = True idx = -1 return_path = [] for value in series: idx = idx + 1 if value is None and self.params.get('drawNullAsZero'): value = 0.0 if series.options.has_key('areaFill'): value_btm = series.options['lowBoundData'][idx] if value_btm is None: value = None if value is None: if not fromNone: if series.options.has_key('areaFill'): # close up the shape by drawing to the previous bottom points in reverse for pair in reverse_sort(return_path): self.ctx.line_to(pair[0], pair[1]) return_path = [] self.ctx.close_path() self.ctx.fill() elif self.areaMode != 'none': #Close off and fill area before unknown interval self.ctx.line_to(x, self.area['ymax']) self.ctx.close_path() self.ctx.fill() x += series.xStep fromNone = True else: y = self.area['ymax'] - ( (float(value) - self.yBottom) * self.yScaleFactor) if y < 0: y = 0 if series.options.has_key('areaFill'): value_btm = series.options['lowBoundData'][idx] y_btm = self.area['ymax'] - ( (float(value_btm) - self.yBottom) * self.yScaleFactor) if y_btm < 0: y_btm = 0 return_path.append((x + series.xStep, y_btm)) if series.options.has_key('drawAsInfinite') and value > 0: self.ctx.move_to(x, self.area['ymax']) self.ctx.line_to(x, self.area['ymin']) self.ctx.stroke() x += series.xStep continue if self.lineMode == 'staircase': if fromNone: if series.options.has_key('areaFill'): self.ctx.move_to(x, y_btm) self.ctx.line_to(x, y) elif self.areaMode != 'none': self.ctx.move_to(x, self.area['ymax']) self.ctx.line_to(x, y) else: self.ctx.move_to(x, y) else: self.ctx.line_to(x, y) x += series.xStep self.ctx.line_to(x, y) elif self.lineMode == 'slope': if fromNone: if series.options.has_key('areaFill'): self.ctx.move_to(x, y_btm) self.ctx.line_to(x, y) elif self.areaMode != 'none': self.ctx.move_to(x, self.area['ymax']) self.ctx.line_to(x, y) else: self.ctx.move_to(x, y) x += series.xStep self.ctx.line_to(x, y) fromNone = False if series.options.has_key('areaFill'): for pair in reverse_sort(return_path): self.ctx.line_to(pair[0], pair[1]) self.ctx.close_path() self.ctx.fill() elif self.areaMode != 'none': self.ctx.line_to(x, self.area['ymax']) self.ctx.close_path() self.ctx.fill() if self.areaMode == 'first': self.areaMode = 'none' #This ensures only the first line is drawn as area else: self.ctx.stroke() self.ctx.set_line_width( originalWidth) # return to the original line width if series.options.has_key( 'dash' ): # if we changed the dash setting before, change it back now if dash: self.ctx.set_dash(dash, 1) else: self.ctx.set_dash([], 0)