2015-11-04 21:06:06 +00:00
|
|
|
package influxql
|
|
|
|
|
|
|
|
import (
|
2016-01-08 18:02:14 +00:00
|
|
|
"bytes"
|
2016-02-02 20:23:06 +00:00
|
|
|
"container/heap"
|
2015-11-04 21:06:06 +00:00
|
|
|
"fmt"
|
|
|
|
"math"
|
|
|
|
"sort"
|
|
|
|
)
|
|
|
|
|
|
|
|
/*
|
|
|
|
This file contains iterator implementations for each function call available
|
|
|
|
in InfluxQL. Call iterators are separated into two groups:
|
|
|
|
|
|
|
|
1. Map/reduce-style iterators - these are passed to IteratorCreator so that
|
|
|
|
processing can be at the low-level storage and aggregates are returned.
|
|
|
|
|
|
|
|
2. Raw aggregate iterators - these require the full set of data for a window.
|
|
|
|
These are handled by the select() function and raw points are streamed in
|
|
|
|
from the low-level storage.
|
|
|
|
|
|
|
|
There are helpers to aid in building aggregate iterators. For simple map/reduce
|
|
|
|
iterators, you can use the reduceIterator types and pass a reduce function. This
|
|
|
|
reduce function is passed a previous and current value and the new timestamp,
|
|
|
|
value, and auxilary fields are returned from it.
|
|
|
|
|
|
|
|
For raw aggregate iterators, you can use the reduceSliceIterators which pass
|
|
|
|
in a slice of all points to the function and return a point. For more complex
|
|
|
|
iterator types, you may need to create your own iterators by hand.
|
|
|
|
|
|
|
|
Once your iterator is complete, you'll need to add it to the NewCallIterator()
|
|
|
|
function if it is to be available to IteratorCreators and add it to the select()
|
|
|
|
function to allow it to be included during planning.
|
|
|
|
*/
|
|
|
|
|
|
|
|
// NewCallIterator returns a new iterator for a Call.
|
|
|
|
func NewCallIterator(input Iterator, opt IteratorOptions) Iterator {
|
|
|
|
name := opt.Expr.(*Call).Name
|
|
|
|
switch name {
|
|
|
|
case "count":
|
|
|
|
return newCountIterator(input, opt)
|
|
|
|
case "min":
|
|
|
|
return newMinIterator(input, opt)
|
|
|
|
case "max":
|
|
|
|
return newMaxIterator(input, opt)
|
|
|
|
case "sum":
|
|
|
|
return newSumIterator(input, opt)
|
|
|
|
case "first":
|
|
|
|
return newFirstIterator(input, opt)
|
|
|
|
case "last":
|
|
|
|
return newLastIterator(input, opt)
|
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unsupported function call: %s", name))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// newCountIterator returns an iterator for operating on a count() call.
|
|
|
|
func newCountIterator(input Iterator, opt IteratorOptions) Iterator {
|
|
|
|
// FIXME: Wrap iterator in int-type iterator and always output int value.
|
|
|
|
|
|
|
|
switch input := input.(type) {
|
|
|
|
case FloatIterator:
|
|
|
|
return &floatReduceIterator{input: newBufFloatIterator(input), opt: opt, fn: floatCountReduce}
|
2016-01-18 22:48:49 +00:00
|
|
|
case IntegerIterator:
|
|
|
|
return &integerReduceIterator{input: newBufIntegerIterator(input), opt: opt, fn: integerCountReduce}
|
2015-11-04 21:06:06 +00:00
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unsupported count iterator type: %T", input))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// floatCountReduce returns the count of points.
|
|
|
|
func floatCountReduce(prev, curr *FloatPoint, opt *reduceOptions) (int64, float64, []interface{}) {
|
|
|
|
if prev == nil {
|
|
|
|
return opt.startTime, 1, nil
|
|
|
|
}
|
|
|
|
return prev.Time, prev.Value + 1, nil
|
|
|
|
}
|
|
|
|
|
2016-01-18 22:48:49 +00:00
|
|
|
// integerCountReduce returns the count of points.
|
|
|
|
func integerCountReduce(prev, curr *IntegerPoint, opt *reduceOptions) (int64, int64, []interface{}) {
|
|
|
|
if prev == nil {
|
|
|
|
return opt.startTime, 1, nil
|
|
|
|
}
|
|
|
|
return prev.Time, prev.Value + 1, nil
|
|
|
|
}
|
|
|
|
|
2015-11-04 21:06:06 +00:00
|
|
|
// newMinIterator returns an iterator for operating on a min() call.
|
|
|
|
func newMinIterator(input Iterator, opt IteratorOptions) Iterator {
|
|
|
|
switch input := input.(type) {
|
|
|
|
case FloatIterator:
|
|
|
|
return &floatReduceIterator{input: newBufFloatIterator(input), opt: opt, fn: floatMinReduce}
|
2016-01-18 22:48:49 +00:00
|
|
|
case IntegerIterator:
|
|
|
|
return &integerReduceIterator{input: newBufIntegerIterator(input), opt: opt, fn: integerMinReduce}
|
2015-11-04 21:06:06 +00:00
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unsupported min iterator type: %T", input))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// floatMinReduce returns the minimum value between prev & curr.
|
|
|
|
func floatMinReduce(prev, curr *FloatPoint, opt *reduceOptions) (int64, float64, []interface{}) {
|
|
|
|
if prev == nil || curr.Value < prev.Value || (curr.Value == prev.Value && curr.Time < prev.Time) {
|
2016-01-23 03:04:10 +00:00
|
|
|
return curr.Time, curr.Value, curr.Aux
|
2015-11-04 21:06:06 +00:00
|
|
|
}
|
|
|
|
return prev.Time, prev.Value, prev.Aux
|
|
|
|
}
|
|
|
|
|
2016-01-18 22:48:49 +00:00
|
|
|
// integerMinReduce returns the minimum value between prev & curr.
|
|
|
|
func integerMinReduce(prev, curr *IntegerPoint, opt *reduceOptions) (int64, int64, []interface{}) {
|
|
|
|
if prev == nil || curr.Value < prev.Value || (curr.Value == prev.Value && curr.Time < prev.Time) {
|
2016-01-23 03:04:10 +00:00
|
|
|
return curr.Time, curr.Value, curr.Aux
|
2016-01-18 22:48:49 +00:00
|
|
|
}
|
|
|
|
return prev.Time, prev.Value, prev.Aux
|
|
|
|
}
|
|
|
|
|
2015-11-04 21:06:06 +00:00
|
|
|
// newMaxIterator returns an iterator for operating on a max() call.
|
|
|
|
func newMaxIterator(input Iterator, opt IteratorOptions) Iterator {
|
|
|
|
switch input := input.(type) {
|
|
|
|
case FloatIterator:
|
|
|
|
return &floatReduceIterator{input: newBufFloatIterator(input), opt: opt, fn: floatMaxReduce}
|
2016-01-18 22:48:49 +00:00
|
|
|
case IntegerIterator:
|
|
|
|
return &integerReduceIterator{input: newBufIntegerIterator(input), opt: opt, fn: integerMaxReduce}
|
2015-11-04 21:06:06 +00:00
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unsupported max iterator type: %T", input))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// floatMaxReduce returns the maximum value between prev & curr.
|
|
|
|
func floatMaxReduce(prev, curr *FloatPoint, opt *reduceOptions) (int64, float64, []interface{}) {
|
|
|
|
if prev == nil || curr.Value > prev.Value || (curr.Value == prev.Value && curr.Time < prev.Time) {
|
2016-01-23 03:04:10 +00:00
|
|
|
return curr.Time, curr.Value, curr.Aux
|
2015-11-04 21:06:06 +00:00
|
|
|
}
|
|
|
|
return prev.Time, prev.Value, prev.Aux
|
|
|
|
}
|
|
|
|
|
2016-01-18 22:48:49 +00:00
|
|
|
// integerMaxReduce returns the maximum value between prev & curr.
|
|
|
|
func integerMaxReduce(prev, curr *IntegerPoint, opt *reduceOptions) (int64, int64, []interface{}) {
|
|
|
|
if prev == nil || curr.Value > prev.Value || (curr.Value == prev.Value && curr.Time < prev.Time) {
|
2016-01-23 03:04:10 +00:00
|
|
|
return curr.Time, curr.Value, curr.Aux
|
2016-01-18 22:48:49 +00:00
|
|
|
}
|
|
|
|
return prev.Time, prev.Value, prev.Aux
|
|
|
|
}
|
|
|
|
|
2015-11-04 21:06:06 +00:00
|
|
|
// newSumIterator returns an iterator for operating on a sum() call.
|
|
|
|
func newSumIterator(input Iterator, opt IteratorOptions) Iterator {
|
|
|
|
switch input := input.(type) {
|
|
|
|
case FloatIterator:
|
|
|
|
return &floatReduceIterator{input: newBufFloatIterator(input), opt: opt, fn: floatSumReduce}
|
2016-01-18 22:48:49 +00:00
|
|
|
case IntegerIterator:
|
|
|
|
return &integerReduceIterator{input: newBufIntegerIterator(input), opt: opt, fn: integerSumReduce}
|
2015-11-04 21:06:06 +00:00
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unsupported sum iterator type: %T", input))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// floatSumReduce returns the sum prev value & curr value.
|
|
|
|
func floatSumReduce(prev, curr *FloatPoint, opt *reduceOptions) (int64, float64, []interface{}) {
|
|
|
|
if prev == nil {
|
2016-01-23 03:04:10 +00:00
|
|
|
return curr.Time, curr.Value, nil
|
2015-11-04 21:06:06 +00:00
|
|
|
}
|
2016-01-23 03:04:10 +00:00
|
|
|
return prev.Time, prev.Value + curr.Value, nil
|
2015-11-04 21:06:06 +00:00
|
|
|
}
|
|
|
|
|
2016-01-18 22:48:49 +00:00
|
|
|
// integerSumReduce returns the sum prev value & curr value.
|
|
|
|
func integerSumReduce(prev, curr *IntegerPoint, opt *reduceOptions) (int64, int64, []interface{}) {
|
|
|
|
if prev == nil {
|
2016-01-23 03:04:10 +00:00
|
|
|
return curr.Time, curr.Value, nil
|
2016-01-18 22:48:49 +00:00
|
|
|
}
|
2016-01-23 03:04:10 +00:00
|
|
|
return prev.Time, prev.Value + curr.Value, nil
|
2016-01-18 22:48:49 +00:00
|
|
|
}
|
|
|
|
|
2015-11-04 21:06:06 +00:00
|
|
|
// newFirstIterator returns an iterator for operating on a first() call.
|
|
|
|
func newFirstIterator(input Iterator, opt IteratorOptions) Iterator {
|
|
|
|
switch input := input.(type) {
|
|
|
|
case FloatIterator:
|
|
|
|
return &floatReduceIterator{input: newBufFloatIterator(input), opt: opt, fn: floatFirstReduce}
|
2016-01-18 22:48:49 +00:00
|
|
|
case IntegerIterator:
|
|
|
|
return &integerReduceIterator{input: newBufIntegerIterator(input), opt: opt, fn: integerFirstReduce}
|
2015-11-04 21:06:06 +00:00
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unsupported first iterator type: %T", input))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// floatFirstReduce returns the first point sorted by time.
|
|
|
|
func floatFirstReduce(prev, curr *FloatPoint, opt *reduceOptions) (int64, float64, []interface{}) {
|
|
|
|
if prev == nil || curr.Time < prev.Time || (curr.Time == prev.Time && curr.Value > prev.Value) {
|
2016-01-23 03:04:10 +00:00
|
|
|
return curr.Time, curr.Value, curr.Aux
|
2015-11-04 21:06:06 +00:00
|
|
|
}
|
2016-01-23 03:04:10 +00:00
|
|
|
return prev.Time, prev.Value, prev.Aux
|
2016-01-18 22:48:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// integerFirstReduce returns the first point sorted by time.
|
|
|
|
func integerFirstReduce(prev, curr *IntegerPoint, opt *reduceOptions) (int64, int64, []interface{}) {
|
|
|
|
if prev == nil || curr.Time < prev.Time || (curr.Time == prev.Time && curr.Value > prev.Value) {
|
2016-01-23 03:04:10 +00:00
|
|
|
return curr.Time, curr.Value, curr.Aux
|
2016-01-18 22:48:49 +00:00
|
|
|
}
|
2016-01-23 03:04:10 +00:00
|
|
|
return prev.Time, prev.Value, prev.Aux
|
2015-11-04 21:06:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// newLastIterator returns an iterator for operating on a last() call.
|
|
|
|
func newLastIterator(input Iterator, opt IteratorOptions) Iterator {
|
|
|
|
switch input := input.(type) {
|
|
|
|
case FloatIterator:
|
|
|
|
return &floatReduceIterator{input: newBufFloatIterator(input), opt: opt, fn: floatLastReduce}
|
2016-01-18 22:48:49 +00:00
|
|
|
case IntegerIterator:
|
|
|
|
return &integerReduceIterator{input: newBufIntegerIterator(input), opt: opt, fn: integerLastReduce}
|
2015-11-04 21:06:06 +00:00
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unsupported last iterator type: %T", input))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// floatLastReduce returns the last point sorted by time.
|
|
|
|
func floatLastReduce(prev, curr *FloatPoint, opt *reduceOptions) (int64, float64, []interface{}) {
|
|
|
|
if prev == nil || curr.Time > prev.Time || (curr.Time == prev.Time && curr.Value > prev.Value) {
|
2016-01-23 03:04:10 +00:00
|
|
|
return curr.Time, curr.Value, curr.Aux
|
2015-11-04 21:06:06 +00:00
|
|
|
}
|
2016-01-23 03:04:10 +00:00
|
|
|
return prev.Time, prev.Value, prev.Aux
|
2016-01-18 22:48:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// integerLastReduce returns the last point sorted by time.
|
|
|
|
func integerLastReduce(prev, curr *IntegerPoint, opt *reduceOptions) (int64, int64, []interface{}) {
|
|
|
|
if prev == nil || curr.Time > prev.Time || (curr.Time == prev.Time && curr.Value > prev.Value) {
|
2016-01-23 03:04:10 +00:00
|
|
|
return curr.Time, curr.Value, curr.Aux
|
2016-01-18 22:48:49 +00:00
|
|
|
}
|
2016-01-23 03:04:10 +00:00
|
|
|
return prev.Time, prev.Value, prev.Aux
|
2015-11-04 21:06:06 +00:00
|
|
|
}
|
|
|
|
|
2016-02-04 15:12:52 +00:00
|
|
|
// NewDistinctIterator returns an iterator for operating on a distinct() call.
|
|
|
|
func NewDistinctIterator(input Iterator, opt IteratorOptions) Iterator {
|
2015-11-04 21:06:06 +00:00
|
|
|
switch input := input.(type) {
|
|
|
|
case FloatIterator:
|
|
|
|
return &floatReduceSliceIterator{input: newBufFloatIterator(input), opt: opt, fn: floatDistinctReduceSlice}
|
2016-01-18 22:48:49 +00:00
|
|
|
case IntegerIterator:
|
|
|
|
return &integerReduceSliceIterator{input: newBufIntegerIterator(input), opt: opt, fn: integerDistinctReduceSlice}
|
2015-11-04 21:06:06 +00:00
|
|
|
case StringIterator:
|
|
|
|
return &stringReduceSliceIterator{input: newBufStringIterator(input), opt: opt, fn: stringDistinctReduceSlice}
|
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unsupported distinct iterator type: %T", input))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// floatDistinctReduceSlice returns the distinct value within a window.
|
|
|
|
func floatDistinctReduceSlice(a []FloatPoint, opt *reduceOptions) []FloatPoint {
|
|
|
|
m := make(map[float64]FloatPoint)
|
|
|
|
for _, p := range a {
|
|
|
|
if _, ok := m[p.Value]; !ok {
|
|
|
|
m[p.Value] = p
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
points := make([]FloatPoint, 0, len(m))
|
|
|
|
for _, p := range m {
|
|
|
|
points = append(points, FloatPoint{Time: p.Time, Value: p.Value})
|
|
|
|
}
|
|
|
|
sort.Sort(floatPoints(points))
|
|
|
|
return points
|
|
|
|
}
|
|
|
|
|
2016-01-18 22:48:49 +00:00
|
|
|
// integerDistinctReduceSlice returns the distinct value within a window.
|
|
|
|
func integerDistinctReduceSlice(a []IntegerPoint, opt *reduceOptions) []IntegerPoint {
|
|
|
|
m := make(map[int64]IntegerPoint)
|
|
|
|
for _, p := range a {
|
|
|
|
if _, ok := m[p.Value]; !ok {
|
|
|
|
m[p.Value] = p
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
points := make([]IntegerPoint, 0, len(m))
|
|
|
|
for _, p := range m {
|
|
|
|
points = append(points, IntegerPoint{Time: p.Time, Value: p.Value})
|
|
|
|
}
|
|
|
|
sort.Sort(integerPoints(points))
|
|
|
|
return points
|
|
|
|
}
|
|
|
|
|
2015-11-04 21:06:06 +00:00
|
|
|
// stringDistinctReduceSlice returns the distinct value within a window.
|
|
|
|
func stringDistinctReduceSlice(a []StringPoint, opt *reduceOptions) []StringPoint {
|
|
|
|
m := make(map[string]StringPoint)
|
|
|
|
for _, p := range a {
|
|
|
|
if _, ok := m[p.Value]; !ok {
|
|
|
|
m[p.Value] = p
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
points := make([]StringPoint, 0, len(m))
|
|
|
|
for _, p := range m {
|
|
|
|
points = append(points, StringPoint{Time: p.Time, Value: p.Value})
|
|
|
|
}
|
|
|
|
sort.Sort(stringPoints(points))
|
|
|
|
return points
|
|
|
|
}
|
|
|
|
|
|
|
|
// newMeanIterator returns an iterator for operating on a mean() call.
|
|
|
|
func newMeanIterator(input Iterator, opt IteratorOptions) Iterator {
|
|
|
|
switch input := input.(type) {
|
|
|
|
case FloatIterator:
|
|
|
|
return &floatReduceSliceIterator{input: newBufFloatIterator(input), opt: opt, fn: floatMeanReduceSlice}
|
2016-01-18 22:48:49 +00:00
|
|
|
case IntegerIterator:
|
|
|
|
return &integerReduceSliceFloatIterator{input: newBufIntegerIterator(input), opt: opt, fn: integerMeanReduceSlice}
|
2015-11-04 21:06:06 +00:00
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unsupported mean iterator type: %T", input))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// floatMeanReduceSlice returns the mean value within a window.
|
|
|
|
func floatMeanReduceSlice(a []FloatPoint, opt *reduceOptions) []FloatPoint {
|
|
|
|
var mean float64
|
|
|
|
var count int
|
|
|
|
for _, p := range a {
|
|
|
|
if math.IsNaN(p.Value) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
count++
|
|
|
|
mean += (p.Value - mean) / float64(count)
|
|
|
|
}
|
|
|
|
return []FloatPoint{{Time: opt.startTime, Value: mean}}
|
|
|
|
}
|
|
|
|
|
2016-01-18 22:48:49 +00:00
|
|
|
// integerMeanReduceSlice returns the mean value within a window.
|
|
|
|
func integerMeanReduceSlice(a []IntegerPoint, opt *reduceOptions) []FloatPoint {
|
|
|
|
var mean float64
|
|
|
|
var count int
|
|
|
|
for _, p := range a {
|
|
|
|
count++
|
|
|
|
mean += (float64(p.Value) - mean) / float64(count)
|
|
|
|
}
|
|
|
|
return []FloatPoint{{Time: opt.startTime, Value: mean}}
|
|
|
|
}
|
|
|
|
|
2015-11-04 21:06:06 +00:00
|
|
|
// newMedianIterator returns an iterator for operating on a median() call.
|
|
|
|
func newMedianIterator(input Iterator, opt IteratorOptions) Iterator {
|
|
|
|
switch input := input.(type) {
|
|
|
|
case FloatIterator:
|
|
|
|
return &floatReduceSliceIterator{input: newBufFloatIterator(input), opt: opt, fn: floatMedianReduceSlice}
|
2016-01-18 22:48:49 +00:00
|
|
|
case IntegerIterator:
|
|
|
|
return &integerReduceSliceFloatIterator{input: newBufIntegerIterator(input), opt: opt, fn: integerMedianReduceSlice}
|
2015-11-04 21:06:06 +00:00
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unsupported median iterator type: %T", input))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// floatMedianReduceSlice returns the median value within a window.
|
|
|
|
func floatMedianReduceSlice(a []FloatPoint, opt *reduceOptions) []FloatPoint {
|
|
|
|
if len(a) == 1 {
|
|
|
|
return []FloatPoint{{Time: opt.startTime, Value: a[0].Value}}
|
|
|
|
}
|
|
|
|
|
|
|
|
// OPTIMIZE(benbjohnson): Use getSortedRange() from v0.9.5.1.
|
|
|
|
|
|
|
|
// Return the middle value from the points.
|
|
|
|
// If there are an even number of points then return the mean of the two middle points.
|
|
|
|
sort.Sort(floatPointsByValue(a))
|
|
|
|
if len(a)%2 == 0 {
|
|
|
|
lo, hi := a[len(a)/2-1], a[(len(a)/2)]
|
|
|
|
return []FloatPoint{{Time: opt.startTime, Value: lo.Value + (hi.Value-lo.Value)/2}}
|
|
|
|
}
|
|
|
|
return []FloatPoint{{Time: opt.startTime, Value: a[len(a)/2].Value}}
|
|
|
|
}
|
|
|
|
|
2016-01-18 22:48:49 +00:00
|
|
|
// integerMedianReduceSlice returns the median value within a window.
|
|
|
|
func integerMedianReduceSlice(a []IntegerPoint, opt *reduceOptions) []FloatPoint {
|
|
|
|
if len(a) == 1 {
|
|
|
|
return []FloatPoint{{Time: opt.startTime, Value: float64(a[0].Value)}}
|
|
|
|
}
|
|
|
|
|
|
|
|
// OPTIMIZE(benbjohnson): Use getSortedRange() from v0.9.5.1.
|
|
|
|
|
|
|
|
// Return the middle value from the points.
|
|
|
|
// If there are an even number of points then return the mean of the two middle points.
|
|
|
|
sort.Sort(integerPointsByValue(a))
|
|
|
|
if len(a)%2 == 0 {
|
|
|
|
lo, hi := a[len(a)/2-1], a[(len(a)/2)]
|
|
|
|
return []FloatPoint{{Time: opt.startTime, Value: float64(lo.Value) + float64(hi.Value-lo.Value)/2}}
|
|
|
|
}
|
|
|
|
return []FloatPoint{{Time: opt.startTime, Value: float64(a[len(a)/2].Value)}}
|
|
|
|
}
|
|
|
|
|
2015-11-04 21:06:06 +00:00
|
|
|
// newStddevIterator returns an iterator for operating on a stddev() call.
|
|
|
|
func newStddevIterator(input Iterator, opt IteratorOptions) Iterator {
|
|
|
|
switch input := input.(type) {
|
|
|
|
case FloatIterator:
|
|
|
|
return &floatReduceSliceIterator{input: newBufFloatIterator(input), opt: opt, fn: floatStddevReduceSlice}
|
2016-01-18 22:48:49 +00:00
|
|
|
case IntegerIterator:
|
|
|
|
return &integerReduceSliceFloatIterator{input: newBufIntegerIterator(input), opt: opt, fn: integerStddevReduceSlice}
|
2015-11-04 21:06:06 +00:00
|
|
|
case StringIterator:
|
|
|
|
return &stringReduceSliceIterator{input: newBufStringIterator(input), opt: opt, fn: stringStddevReduceSlice}
|
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unsupported stddev iterator type: %T", input))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// floatStddevReduceSlice returns the stddev value within a window.
|
|
|
|
func floatStddevReduceSlice(a []FloatPoint, opt *reduceOptions) []FloatPoint {
|
2016-02-02 17:19:15 +00:00
|
|
|
// If there is only one point then return 0.
|
2015-11-04 21:06:06 +00:00
|
|
|
if len(a) < 2 {
|
2016-02-02 17:19:15 +00:00
|
|
|
return []FloatPoint{{Time: opt.startTime, Nil: true}}
|
2015-11-04 21:06:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate the mean.
|
|
|
|
var mean float64
|
|
|
|
var count int
|
|
|
|
for _, p := range a {
|
|
|
|
if math.IsNaN(p.Value) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
count++
|
|
|
|
mean += (p.Value - mean) / float64(count)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate the variance.
|
|
|
|
var variance float64
|
|
|
|
for _, p := range a {
|
|
|
|
if math.IsNaN(p.Value) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
variance += math.Pow(p.Value-mean, 2)
|
|
|
|
}
|
|
|
|
return []FloatPoint{{
|
|
|
|
Time: opt.startTime,
|
|
|
|
Value: math.Sqrt(variance / float64(count-1)),
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
2016-01-18 22:48:49 +00:00
|
|
|
// integerStddevReduceSlice returns the stddev value within a window.
|
|
|
|
func integerStddevReduceSlice(a []IntegerPoint, opt *reduceOptions) []FloatPoint {
|
2016-02-02 17:19:15 +00:00
|
|
|
// If there is only one point then return 0.
|
2016-01-18 22:48:49 +00:00
|
|
|
if len(a) < 2 {
|
2016-02-02 17:19:15 +00:00
|
|
|
return []FloatPoint{{Time: opt.startTime, Nil: true}}
|
2016-01-18 22:48:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate the mean.
|
|
|
|
var mean float64
|
|
|
|
var count int
|
|
|
|
for _, p := range a {
|
|
|
|
count++
|
|
|
|
mean += (float64(p.Value) - mean) / float64(count)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate the variance.
|
|
|
|
var variance float64
|
|
|
|
for _, p := range a {
|
|
|
|
variance += math.Pow(float64(p.Value)-mean, 2)
|
|
|
|
}
|
|
|
|
return []FloatPoint{{
|
|
|
|
Time: opt.startTime,
|
|
|
|
Value: math.Sqrt(variance / float64(count-1)),
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
2015-11-04 21:06:06 +00:00
|
|
|
// stringStddevReduceSlice always returns "".
|
|
|
|
func stringStddevReduceSlice(a []StringPoint, opt *reduceOptions) []StringPoint {
|
|
|
|
return []StringPoint{{Time: opt.startTime, Value: ""}}
|
|
|
|
}
|
|
|
|
|
|
|
|
// newSpreadIterator returns an iterator for operating on a spread() call.
|
|
|
|
func newSpreadIterator(input Iterator, opt IteratorOptions) Iterator {
|
|
|
|
switch input := input.(type) {
|
|
|
|
case FloatIterator:
|
|
|
|
return &floatReduceSliceIterator{input: newBufFloatIterator(input), opt: opt, fn: floatSpreadReduceSlice}
|
2016-01-18 22:48:49 +00:00
|
|
|
case IntegerIterator:
|
|
|
|
return &integerReduceSliceIterator{input: newBufIntegerIterator(input), opt: opt, fn: integerSpreadReduceSlice}
|
2015-11-04 21:06:06 +00:00
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unsupported spread iterator type: %T", input))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// floatSpreadReduceSlice returns the spread value within a window.
|
|
|
|
func floatSpreadReduceSlice(a []FloatPoint, opt *reduceOptions) []FloatPoint {
|
|
|
|
// Find min & max values.
|
|
|
|
min, max := a[0].Value, a[0].Value
|
|
|
|
for _, p := range a[1:] {
|
|
|
|
min = math.Min(min, p.Value)
|
|
|
|
max = math.Max(max, p.Value)
|
|
|
|
}
|
|
|
|
return []FloatPoint{{Time: opt.startTime, Value: max - min}}
|
|
|
|
}
|
|
|
|
|
2016-01-18 22:48:49 +00:00
|
|
|
// integerSpreadReduceSlice returns the spread value within a window.
|
|
|
|
func integerSpreadReduceSlice(a []IntegerPoint, opt *reduceOptions) []IntegerPoint {
|
|
|
|
// Find min & max values.
|
|
|
|
min, max := a[0].Value, a[0].Value
|
|
|
|
for _, p := range a[1:] {
|
|
|
|
if p.Value < min {
|
|
|
|
min = p.Value
|
|
|
|
}
|
|
|
|
if p.Value > max {
|
|
|
|
max = p.Value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return []IntegerPoint{{Time: opt.startTime, Value: max - min}}
|
|
|
|
}
|
|
|
|
|
2016-01-08 18:02:14 +00:00
|
|
|
// newTopIterator returns an iterator for operating on a top() call.
|
|
|
|
func newTopIterator(input Iterator, opt IteratorOptions, n *NumberLiteral, tags []int) Iterator {
|
|
|
|
switch input := input.(type) {
|
|
|
|
case FloatIterator:
|
2016-02-02 20:23:06 +00:00
|
|
|
return &floatReduceSliceIterator{input: newBufFloatIterator(input), opt: opt, fn: newFloatTopReduceSliceFunc(int(n.Val), tags, opt.Interval)}
|
|
|
|
case IntegerIterator:
|
|
|
|
return &integerReduceSliceIterator{input: newBufIntegerIterator(input), opt: opt, fn: newIntegerTopReduceSliceFunc(int(n.Val), tags, opt.Interval)}
|
2016-01-08 18:02:14 +00:00
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unsupported top iterator type: %T", input))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-02 20:23:06 +00:00
|
|
|
// newFloatTopReduceSliceFunc returns the top values within a window.
|
|
|
|
func newFloatTopReduceSliceFunc(n int, tags []int, interval Interval) floatReduceSliceFunc {
|
2016-01-08 18:02:14 +00:00
|
|
|
return func(a []FloatPoint, opt *reduceOptions) []FloatPoint {
|
2016-02-02 20:23:06 +00:00
|
|
|
// Filter by tags if they exist.
|
|
|
|
if tags != nil {
|
|
|
|
a = filterFloatByUniqueTags(a, tags, func(cur, p *FloatPoint) bool {
|
|
|
|
return p.Value > cur.Value || (p.Value == cur.Value && p.Time < cur.Time)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we ask for more elements than exist, restrict n to be the length of the array.
|
|
|
|
size := n
|
|
|
|
if size > len(a) {
|
|
|
|
size = len(a)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Construct a heap preferring higher values and breaking ties
|
|
|
|
// based on the earliest time for a point.
|
|
|
|
h := floatPointsSortBy(a, func(a, b *FloatPoint) bool {
|
|
|
|
if a.Value != b.Value {
|
|
|
|
return a.Value > b.Value
|
2016-01-08 18:02:14 +00:00
|
|
|
}
|
2016-02-02 20:23:06 +00:00
|
|
|
return a.Time < b.Time
|
|
|
|
})
|
|
|
|
heap.Init(h)
|
|
|
|
|
|
|
|
// Pop the first n elements and then sort by time.
|
|
|
|
points := make([]FloatPoint, 0, size)
|
|
|
|
for i := 0; i < size; i++ {
|
|
|
|
p := heap.Pop(h).(FloatPoint)
|
|
|
|
points = append(points, p)
|
|
|
|
}
|
2016-01-08 18:02:14 +00:00
|
|
|
|
2016-02-02 20:23:06 +00:00
|
|
|
// Either zero out all values or sort the points by time
|
|
|
|
// depending on if a time interval was given or not.
|
|
|
|
if !interval.IsZero() {
|
|
|
|
for i := range points {
|
|
|
|
points[i].Time = opt.startTime
|
2016-01-08 18:02:14 +00:00
|
|
|
}
|
2016-02-02 20:23:06 +00:00
|
|
|
} else {
|
|
|
|
sort.Stable(floatPoints(points))
|
|
|
|
}
|
|
|
|
return points
|
|
|
|
}
|
|
|
|
}
|
2016-01-08 18:02:14 +00:00
|
|
|
|
2016-02-02 20:23:06 +00:00
|
|
|
// newIntegerTopReduceSliceFunc returns the top values within a window.
|
|
|
|
func newIntegerTopReduceSliceFunc(n int, tags []int, interval Interval) integerReduceSliceFunc {
|
|
|
|
return func(a []IntegerPoint, opt *reduceOptions) []IntegerPoint {
|
|
|
|
// Filter by tags if they exist.
|
|
|
|
if tags != nil {
|
|
|
|
a = filterIntegerByUniqueTags(a, tags, func(cur, p *IntegerPoint) bool {
|
|
|
|
return p.Value > cur.Value || (p.Value == cur.Value && p.Time < cur.Time)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we ask for more elements than exist, restrict n to be the length of the array.
|
|
|
|
size := n
|
|
|
|
if size > len(a) {
|
|
|
|
size = len(a)
|
|
|
|
}
|
2016-01-08 18:02:14 +00:00
|
|
|
|
2016-02-02 20:23:06 +00:00
|
|
|
// Construct a heap preferring higher values and breaking ties
|
|
|
|
// based on the earliest time for a point.
|
|
|
|
h := integerPointsSortBy(a, func(a, b *IntegerPoint) bool {
|
|
|
|
if a.Value != b.Value {
|
|
|
|
return a.Value > b.Value
|
2016-01-08 18:02:14 +00:00
|
|
|
}
|
2016-02-02 20:23:06 +00:00
|
|
|
return a.Time < b.Time
|
|
|
|
})
|
|
|
|
heap.Init(h)
|
|
|
|
|
|
|
|
// Pop the first n elements and then sort by time.
|
|
|
|
points := make([]IntegerPoint, 0, size)
|
|
|
|
for i := 0; i < size; i++ {
|
|
|
|
p := heap.Pop(h).(IntegerPoint)
|
|
|
|
points = append(points, p)
|
|
|
|
}
|
2016-01-08 18:02:14 +00:00
|
|
|
|
2016-02-02 20:23:06 +00:00
|
|
|
// Either zero out all values or sort the points by time
|
|
|
|
// depending on if a time interval was given or not.
|
|
|
|
if !interval.IsZero() {
|
2016-01-08 18:02:14 +00:00
|
|
|
for i := range points {
|
|
|
|
points[i].Time = opt.startTime
|
|
|
|
}
|
2016-02-02 20:23:06 +00:00
|
|
|
} else {
|
|
|
|
sort.Stable(integerPoints(points))
|
2016-01-08 18:02:14 +00:00
|
|
|
}
|
|
|
|
return points
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// newBottomIterator returns an iterator for operating on a bottom() call.
|
|
|
|
func newBottomIterator(input Iterator, opt IteratorOptions, n *NumberLiteral, tags []int) Iterator {
|
|
|
|
switch input := input.(type) {
|
|
|
|
case FloatIterator:
|
2016-02-02 20:23:06 +00:00
|
|
|
return &floatReduceSliceIterator{input: newBufFloatIterator(input), opt: opt, fn: newFloatBottomReduceSliceFunc(int(n.Val), tags, opt.Interval)}
|
|
|
|
case IntegerIterator:
|
|
|
|
return &integerReduceSliceIterator{input: newBufIntegerIterator(input), opt: opt, fn: newIntegerBottomReduceSliceFunc(int(n.Val), tags, opt.Interval)}
|
2016-01-08 18:02:14 +00:00
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unsupported bottom iterator type: %T", input))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// newFloatBottomReduceSliceFunc returns the bottom values within a window.
|
2016-02-02 20:23:06 +00:00
|
|
|
func newFloatBottomReduceSliceFunc(n int, tags []int, interval Interval) floatReduceSliceFunc {
|
2016-01-08 18:02:14 +00:00
|
|
|
return func(a []FloatPoint, opt *reduceOptions) []FloatPoint {
|
2016-02-02 20:23:06 +00:00
|
|
|
// Filter by tags if they exist.
|
|
|
|
if tags != nil {
|
|
|
|
a = filterFloatByUniqueTags(a, tags, func(cur, p *FloatPoint) bool {
|
|
|
|
return p.Value < cur.Value || (p.Value == cur.Value && p.Time < cur.Time)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we ask for more elements than exist, restrict n to be the length of the array.
|
|
|
|
size := n
|
|
|
|
if size > len(a) {
|
|
|
|
size = len(a)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Construct a heap preferring lower values and breaking ties
|
|
|
|
// based on the earliest time for a point.
|
|
|
|
h := floatPointsSortBy(a, func(a, b *FloatPoint) bool {
|
|
|
|
if a.Value != b.Value {
|
|
|
|
return a.Value < b.Value
|
2016-01-08 18:02:14 +00:00
|
|
|
}
|
2016-02-02 20:23:06 +00:00
|
|
|
return a.Time < b.Time
|
|
|
|
})
|
|
|
|
heap.Init(h)
|
|
|
|
|
|
|
|
// Pop the first n elements and then sort by time.
|
|
|
|
points := make([]FloatPoint, 0, size)
|
|
|
|
for i := 0; i < size; i++ {
|
|
|
|
p := heap.Pop(h).(FloatPoint)
|
|
|
|
points = append(points, p)
|
|
|
|
}
|
2016-01-08 18:02:14 +00:00
|
|
|
|
2016-02-02 20:23:06 +00:00
|
|
|
// Either zero out all values or sort the points by time
|
|
|
|
// depending on if a time interval was given or not.
|
|
|
|
if !interval.IsZero() {
|
|
|
|
for i := range points {
|
|
|
|
points[i].Time = opt.startTime
|
2016-01-08 18:02:14 +00:00
|
|
|
}
|
2016-02-02 20:23:06 +00:00
|
|
|
} else {
|
|
|
|
sort.Stable(floatPoints(points))
|
|
|
|
}
|
|
|
|
return points
|
|
|
|
}
|
|
|
|
}
|
2016-01-08 18:02:14 +00:00
|
|
|
|
2016-02-02 20:23:06 +00:00
|
|
|
// newIntegerBottomReduceSliceFunc returns the bottom values within a window.
|
|
|
|
func newIntegerBottomReduceSliceFunc(n int, tags []int, interval Interval) integerReduceSliceFunc {
|
|
|
|
return func(a []IntegerPoint, opt *reduceOptions) []IntegerPoint {
|
|
|
|
// Filter by tags if they exist.
|
|
|
|
if tags != nil {
|
|
|
|
a = filterIntegerByUniqueTags(a, tags, func(cur, p *IntegerPoint) bool {
|
|
|
|
return p.Value < cur.Value || (p.Value == cur.Value && p.Time < cur.Time)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we ask for more elements than exist, restrict n to be the length of the array.
|
|
|
|
size := n
|
|
|
|
if size > len(a) {
|
|
|
|
size = len(a)
|
|
|
|
}
|
2016-01-08 18:02:14 +00:00
|
|
|
|
2016-02-02 20:23:06 +00:00
|
|
|
// Construct a heap preferring lower values and breaking ties
|
|
|
|
// based on the earliest time for a point.
|
|
|
|
h := integerPointsSortBy(a, func(a, b *IntegerPoint) bool {
|
|
|
|
if a.Value != b.Value {
|
|
|
|
return a.Value < b.Value
|
2016-01-08 18:02:14 +00:00
|
|
|
}
|
2016-02-02 20:23:06 +00:00
|
|
|
return a.Time < b.Time
|
|
|
|
})
|
|
|
|
heap.Init(h)
|
|
|
|
|
|
|
|
// Pop the first n elements and then sort by time.
|
|
|
|
points := make([]IntegerPoint, 0, size)
|
|
|
|
for i := 0; i < size; i++ {
|
|
|
|
p := heap.Pop(h).(IntegerPoint)
|
|
|
|
points = append(points, p)
|
|
|
|
}
|
2016-01-08 18:02:14 +00:00
|
|
|
|
2016-02-02 20:23:06 +00:00
|
|
|
// Either zero out all values or sort the points by time
|
|
|
|
// depending on if a time interval was given or not.
|
|
|
|
if !interval.IsZero() {
|
2016-01-08 18:02:14 +00:00
|
|
|
for i := range points {
|
|
|
|
points[i].Time = opt.startTime
|
|
|
|
}
|
2016-02-02 20:23:06 +00:00
|
|
|
} else {
|
|
|
|
sort.Stable(integerPoints(points))
|
2016-01-08 18:02:14 +00:00
|
|
|
}
|
|
|
|
return points
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-02 20:23:06 +00:00
|
|
|
func filterFloatByUniqueTags(a []FloatPoint, tags []int, cmpFunc func(cur, p *FloatPoint) bool) []FloatPoint {
|
2016-01-08 18:02:14 +00:00
|
|
|
pointMap := make(map[string]FloatPoint)
|
|
|
|
for _, p := range a {
|
|
|
|
keyBuf := bytes.NewBuffer(nil)
|
|
|
|
for i, index := range tags {
|
|
|
|
if i > 0 {
|
|
|
|
keyBuf.WriteString(",")
|
|
|
|
}
|
|
|
|
fmt.Fprintf(keyBuf, "%s", p.Aux[index])
|
|
|
|
}
|
|
|
|
key := keyBuf.String()
|
|
|
|
|
|
|
|
cur, ok := pointMap[key]
|
|
|
|
if ok {
|
|
|
|
if cmpFunc(&cur, &p) {
|
|
|
|
pointMap[key] = p
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
pointMap[key] = p
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Recreate the original array with our new filtered list.
|
|
|
|
points := make([]FloatPoint, 0, len(pointMap))
|
|
|
|
for _, p := range pointMap {
|
|
|
|
points = append(points, p)
|
|
|
|
}
|
|
|
|
return points
|
|
|
|
}
|
|
|
|
|
2016-02-02 20:23:06 +00:00
|
|
|
func filterIntegerByUniqueTags(a []IntegerPoint, tags []int, cmpFunc func(cur, p *IntegerPoint) bool) []IntegerPoint {
|
|
|
|
pointMap := make(map[string]IntegerPoint)
|
|
|
|
for _, p := range a {
|
|
|
|
keyBuf := bytes.NewBuffer(nil)
|
|
|
|
for i, index := range tags {
|
|
|
|
if i > 0 {
|
|
|
|
keyBuf.WriteString(",")
|
|
|
|
}
|
|
|
|
fmt.Fprintf(keyBuf, "%s", p.Aux[index])
|
|
|
|
}
|
|
|
|
key := keyBuf.String()
|
|
|
|
|
|
|
|
cur, ok := pointMap[key]
|
|
|
|
if ok {
|
|
|
|
if cmpFunc(&cur, &p) {
|
|
|
|
pointMap[key] = p
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
pointMap[key] = p
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Recreate the original array with our new filtered list.
|
|
|
|
points := make([]IntegerPoint, 0, len(pointMap))
|
|
|
|
for _, p := range pointMap {
|
|
|
|
points = append(points, p)
|
|
|
|
}
|
|
|
|
return points
|
|
|
|
}
|
|
|
|
|
2015-11-04 21:06:06 +00:00
|
|
|
// newPercentileIterator returns an iterator for operating on a percentile() call.
|
|
|
|
func newPercentileIterator(input Iterator, opt IteratorOptions, percentile float64) Iterator {
|
|
|
|
switch input := input.(type) {
|
|
|
|
case FloatIterator:
|
|
|
|
return &floatReduceSliceIterator{input: newBufFloatIterator(input), opt: opt, fn: newFloatPercentileReduceSliceFunc(percentile)}
|
2016-01-18 22:48:49 +00:00
|
|
|
case IntegerIterator:
|
|
|
|
return &integerReduceSliceIterator{input: newBufIntegerIterator(input), opt: opt, fn: newIntegerPercentileReduceSliceFunc(percentile)}
|
2015-11-04 21:06:06 +00:00
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unsupported percentile iterator type: %T", input))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// newFloatPercentileReduceSliceFunc returns the percentile value within a window.
|
|
|
|
func newFloatPercentileReduceSliceFunc(percentile float64) floatReduceSliceFunc {
|
|
|
|
return func(a []FloatPoint, opt *reduceOptions) []FloatPoint {
|
2016-02-11 18:05:34 +00:00
|
|
|
length := len(a)
|
|
|
|
i := int(math.Floor(float64(length)*percentile/100.0+0.5)) - 1
|
2015-11-04 21:06:06 +00:00
|
|
|
|
2016-02-11 18:05:34 +00:00
|
|
|
if i < 0 || i >= length {
|
2016-02-02 17:19:15 +00:00
|
|
|
return []FloatPoint{{Time: opt.startTime, Nil: true}}
|
2015-11-04 21:06:06 +00:00
|
|
|
}
|
|
|
|
|
2016-02-11 18:05:34 +00:00
|
|
|
sort.Sort(floatPointsByValue(a))
|
2015-11-04 21:06:06 +00:00
|
|
|
return []FloatPoint{{Time: opt.startTime, Value: a[i].Value}}
|
|
|
|
}
|
|
|
|
}
|
2015-12-24 18:46:31 +00:00
|
|
|
|
2016-01-18 22:48:49 +00:00
|
|
|
// newIntegerPercentileReduceSliceFunc returns the percentile value within a window.
|
|
|
|
func newIntegerPercentileReduceSliceFunc(percentile float64) integerReduceSliceFunc {
|
|
|
|
return func(a []IntegerPoint, opt *reduceOptions) []IntegerPoint {
|
2016-02-11 18:05:34 +00:00
|
|
|
length := len(a)
|
|
|
|
i := int(math.Floor(float64(length)*percentile/100.0+0.5)) - 1
|
2016-01-18 22:48:49 +00:00
|
|
|
|
2016-02-11 18:05:34 +00:00
|
|
|
if i < 0 || i >= length {
|
2016-01-18 22:48:49 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-02-11 18:05:34 +00:00
|
|
|
sort.Sort(integerPointsByValue(a))
|
2016-01-18 22:48:49 +00:00
|
|
|
return []IntegerPoint{{Time: opt.startTime, Value: a[i].Value}}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-24 18:46:31 +00:00
|
|
|
// newDerivativeIterator returns an iterator for operating on a derivative() call.
|
|
|
|
func newDerivativeIterator(input Iterator, opt IteratorOptions, interval Interval, isNonNegative bool) Iterator {
|
|
|
|
switch input := input.(type) {
|
|
|
|
case FloatIterator:
|
|
|
|
return &floatReduceSliceIterator{input: newBufFloatIterator(input), opt: opt, fn: newFloatDerivativeReduceSliceFunc(interval, isNonNegative)}
|
2016-02-12 16:23:52 +00:00
|
|
|
case IntegerIterator:
|
|
|
|
return &integerReduceSliceFloatIterator{input: newBufIntegerIterator(input), opt: opt, fn: newIntegerDerivativeReduceSliceFunc(interval, isNonNegative)}
|
2015-12-24 18:46:31 +00:00
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unsupported derivative iterator type: %T", input))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// newFloatDerivativeReduceSliceFunc returns the derivative value within a window.
|
|
|
|
func newFloatDerivativeReduceSliceFunc(interval Interval, isNonNegative bool) floatReduceSliceFunc {
|
|
|
|
prev := FloatPoint{Time: -1}
|
|
|
|
|
|
|
|
return func(a []FloatPoint, opt *reduceOptions) []FloatPoint {
|
|
|
|
if len(a) == 0 {
|
|
|
|
return a
|
|
|
|
} else if len(a) == 1 {
|
2016-02-02 17:19:15 +00:00
|
|
|
return []FloatPoint{{Time: a[0].Time, Nil: true}}
|
2015-12-24 18:46:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if prev.Time == -1 {
|
|
|
|
prev = a[0]
|
|
|
|
}
|
|
|
|
|
|
|
|
output := make([]FloatPoint, 0, len(a)-1)
|
|
|
|
for i := 1; i < len(a); i++ {
|
|
|
|
p := &a[i]
|
|
|
|
|
|
|
|
// Calculate the derivative of successive points by dividing the
|
|
|
|
// difference of each value by the elapsed time normalized to the interval.
|
|
|
|
diff := p.Value - prev.Value
|
|
|
|
elapsed := p.Time - prev.Time
|
|
|
|
|
|
|
|
value := 0.0
|
|
|
|
if elapsed > 0 {
|
|
|
|
value = diff / (float64(elapsed) / float64(interval.Duration))
|
|
|
|
}
|
|
|
|
|
|
|
|
prev = *p
|
|
|
|
|
|
|
|
// Drop negative values for non-negative derivatives.
|
|
|
|
if isNonNegative && diff < 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
output = append(output, FloatPoint{Time: p.Time, Value: value})
|
|
|
|
}
|
|
|
|
return output
|
|
|
|
}
|
|
|
|
}
|
2016-01-22 18:58:15 +00:00
|
|
|
|
2016-02-12 16:23:52 +00:00
|
|
|
// newIntegerDerivativeReduceSliceFunc returns the derivative value within a window.
|
|
|
|
func newIntegerDerivativeReduceSliceFunc(interval Interval, isNonNegative bool) integerReduceSliceFloatFunc {
|
|
|
|
prev := IntegerPoint{Time: -1}
|
|
|
|
|
|
|
|
return func(a []IntegerPoint, opt *reduceOptions) []FloatPoint {
|
|
|
|
if len(a) == 0 {
|
|
|
|
return []FloatPoint{}
|
|
|
|
} else if len(a) == 1 {
|
|
|
|
return []FloatPoint{{Time: a[0].Time, Nil: true}}
|
|
|
|
}
|
|
|
|
|
|
|
|
if prev.Time == -1 {
|
|
|
|
prev = a[0]
|
|
|
|
}
|
|
|
|
|
|
|
|
output := make([]FloatPoint, 0, len(a)-1)
|
|
|
|
for i := 1; i < len(a); i++ {
|
|
|
|
p := &a[i]
|
|
|
|
|
|
|
|
// Calculate the derivative of successive points by dividing the
|
|
|
|
// difference of each value by the elapsed time normalized to the interval.
|
|
|
|
diff := float64(p.Value - prev.Value)
|
|
|
|
elapsed := p.Time - prev.Time
|
|
|
|
|
|
|
|
value := 0.0
|
|
|
|
if elapsed > 0 {
|
|
|
|
value = diff / (float64(elapsed) / float64(interval.Duration))
|
|
|
|
}
|
|
|
|
|
|
|
|
prev = *p
|
|
|
|
|
|
|
|
// Drop negative values for non-negative derivatives.
|
|
|
|
if isNonNegative && diff < 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
output = append(output, FloatPoint{Time: p.Time, Value: value})
|
|
|
|
}
|
|
|
|
return output
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-22 18:58:15 +00:00
|
|
|
// integerReduceSliceFloatIterator executes a reducer on all points in a window and buffers the result.
|
|
|
|
// This iterator receives an integer iterator but produces a float iterator.
|
|
|
|
type integerReduceSliceFloatIterator struct {
|
|
|
|
input *bufIntegerIterator
|
|
|
|
fn integerReduceSliceFloatFunc
|
|
|
|
opt IteratorOptions
|
|
|
|
points []FloatPoint
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close closes the iterator and all child iterators.
|
|
|
|
func (itr *integerReduceSliceFloatIterator) Close() error { return itr.input.Close() }
|
|
|
|
|
|
|
|
// Next returns the minimum value for the next available interval.
|
|
|
|
func (itr *integerReduceSliceFloatIterator) Next() *FloatPoint {
|
|
|
|
// Calculate next window if we have no more points.
|
|
|
|
if len(itr.points) == 0 {
|
|
|
|
itr.points = itr.reduce()
|
|
|
|
if len(itr.points) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pop next point off the stack.
|
|
|
|
p := itr.points[len(itr.points)-1]
|
|
|
|
itr.points = itr.points[:len(itr.points)-1]
|
|
|
|
return &p
|
|
|
|
}
|
|
|
|
|
|
|
|
// reduce executes fn once for every point in the next window.
|
|
|
|
// The previous value for the dimension is passed to fn.
|
|
|
|
func (itr *integerReduceSliceFloatIterator) reduce() []FloatPoint {
|
|
|
|
// Calculate next window.
|
|
|
|
startTime, endTime := itr.opt.Window(itr.input.peekTime())
|
|
|
|
|
|
|
|
var reduceOptions = reduceOptions{
|
|
|
|
startTime: startTime,
|
|
|
|
endTime: endTime,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Group points by name and tagset.
|
|
|
|
groups := make(map[string]struct {
|
|
|
|
name string
|
|
|
|
tags Tags
|
|
|
|
points []IntegerPoint
|
|
|
|
})
|
|
|
|
for {
|
|
|
|
// Read next point.
|
|
|
|
p := itr.input.NextInWindow(startTime, endTime)
|
|
|
|
if p == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
tags := p.Tags.Subset(itr.opt.Dimensions)
|
|
|
|
|
|
|
|
// Append point to dimension.
|
|
|
|
id := tags.ID()
|
|
|
|
g := groups[id]
|
|
|
|
g.name = p.Name
|
|
|
|
g.tags = tags
|
|
|
|
g.points = append(g.points, *p)
|
|
|
|
groups[id] = g
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reduce each set into a set of values.
|
|
|
|
results := make(map[string][]FloatPoint)
|
|
|
|
for key, g := range groups {
|
|
|
|
a := itr.fn(g.points, &reduceOptions)
|
|
|
|
if len(a) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update name and tags for each returned point.
|
|
|
|
for i := range a {
|
|
|
|
a[i].Name = g.name
|
|
|
|
a[i].Tags = g.tags
|
|
|
|
}
|
|
|
|
results[key] = a
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reverse sort points by name & tag.
|
|
|
|
keys := make([]string, 0, len(results))
|
|
|
|
for k := range results {
|
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
sort.Sort(sort.Reverse(sort.StringSlice(keys)))
|
|
|
|
|
|
|
|
// Reverse order points within each key.
|
|
|
|
a := make([]FloatPoint, 0, len(results))
|
|
|
|
for _, k := range keys {
|
|
|
|
for i := len(results[k]) - 1; i >= 0; i-- {
|
|
|
|
a = append(a, results[k][i])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
|
|
|
|
// integerReduceSliceFloatFunc is the function called by a IntegerPoint slice reducer that emits FloatPoint.
|
|
|
|
type integerReduceSliceFloatFunc func(a []IntegerPoint, opt *reduceOptions) []FloatPoint
|