2017-08-15 19:24:22 +00:00
|
|
|
package query
|
2016-03-02 20:52:03 +00:00
|
|
|
|
2016-05-12 21:11:19 +00:00
|
|
|
import (
|
Optimize top() and bottom() using an incremental aggregator
The previous version of `top()` and `bottom()` would gather all of the
points to use in a slice, filter them (if necessary), then use a
slightly modified heap sort to retrieve the top or bottom values.
This performed horrendously from the standpoint of memory. Since it
consumed so much memory and spent so much time in allocations (along
with sorting a potentially very large slice), this affected speed too.
These calls have now been modified so they keep the top or bottom points
in a min or max heap. For `top()`, a new point will read the minimum
value from the heap. If the new point is greater than the minimum point,
it will replace the minimum point and fix the heap with the new value.
If the new point is smaller, it discards that point. For `bottom()`, the
process is the opposite.
It will then sort the final result to ensure the correct ordering of the
selected points.
When `top()` or `bottom()` contain a tag to select, they have now been
modified so this query:
SELECT top(value, host, 2) FROM cpu
Essentially becomes this query:
SELECT top(value, 2), host FROM (
SELECT max(value) FROM cpu GROUP BY host
)
This should drastically increase the performance of all `top()` and
`bottom()` queries.
2017-05-16 17:37:39 +00:00
|
|
|
"container/heap"
|
2016-05-12 21:11:19 +00:00
|
|
|
"math"
|
Optimize top() and bottom() using an incremental aggregator
The previous version of `top()` and `bottom()` would gather all of the
points to use in a slice, filter them (if necessary), then use a
slightly modified heap sort to retrieve the top or bottom values.
This performed horrendously from the standpoint of memory. Since it
consumed so much memory and spent so much time in allocations (along
with sorting a potentially very large slice), this affected speed too.
These calls have now been modified so they keep the top or bottom points
in a min or max heap. For `top()`, a new point will read the minimum
value from the heap. If the new point is greater than the minimum point,
it will replace the minimum point and fix the heap with the new value.
If the new point is smaller, it discards that point. For `bottom()`, the
process is the opposite.
It will then sort the final result to ensure the correct ordering of the
selected points.
When `top()` or `bottom()` contain a tag to select, they have now been
modified so this query:
SELECT top(value, host, 2) FROM cpu
Essentially becomes this query:
SELECT top(value, 2), host FROM (
SELECT max(value) FROM cpu GROUP BY host
)
This should drastically increase the performance of all `top()` and
`bottom()` queries.
2017-05-16 17:37:39 +00:00
|
|
|
"sort"
|
2016-05-12 21:11:19 +00:00
|
|
|
"time"
|
|
|
|
|
2017-10-30 17:24:15 +00:00
|
|
|
"github.com/influxdata/influxdb/query/neldermead"
|
2017-10-30 21:40:26 +00:00
|
|
|
"github.com/influxdata/influxql"
|
2016-05-12 21:11:19 +00:00
|
|
|
)
|
|
|
|
|
2016-03-24 14:40:55 +00:00
|
|
|
// FloatMeanReducer calculates the mean of the aggregated points.
|
2016-03-02 20:52:03 +00:00
|
|
|
type FloatMeanReducer struct {
|
|
|
|
sum float64
|
|
|
|
count uint32
|
|
|
|
}
|
|
|
|
|
2016-03-24 14:40:55 +00:00
|
|
|
// NewFloatMeanReducer creates a new FloatMeanReducer.
|
2016-03-02 20:52:03 +00:00
|
|
|
func NewFloatMeanReducer() *FloatMeanReducer {
|
|
|
|
return &FloatMeanReducer{}
|
|
|
|
}
|
|
|
|
|
2016-03-24 14:40:55 +00:00
|
|
|
// AggregateFloat aggregates a point into the reducer.
|
2016-03-07 18:25:45 +00:00
|
|
|
func (r *FloatMeanReducer) AggregateFloat(p *FloatPoint) {
|
2016-03-02 20:52:03 +00:00
|
|
|
if p.Aggregated >= 2 {
|
|
|
|
r.sum += p.Value * float64(p.Aggregated)
|
|
|
|
r.count += p.Aggregated
|
|
|
|
} else {
|
|
|
|
r.sum += p.Value
|
|
|
|
r.count++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-24 14:40:55 +00:00
|
|
|
// Emit emits the mean of the aggregated points as a single point.
|
2016-03-07 18:25:45 +00:00
|
|
|
func (r *FloatMeanReducer) Emit() []FloatPoint {
|
|
|
|
return []FloatPoint{{
|
2016-03-04 01:53:45 +00:00
|
|
|
Time: ZeroTime,
|
2016-03-02 20:52:03 +00:00
|
|
|
Value: r.sum / float64(r.count),
|
|
|
|
Aggregated: r.count,
|
2016-03-07 18:25:45 +00:00
|
|
|
}}
|
2016-03-02 20:52:03 +00:00
|
|
|
}
|
|
|
|
|
2016-03-24 14:40:55 +00:00
|
|
|
// IntegerMeanReducer calculates the mean of the aggregated points.
|
2016-03-02 20:52:03 +00:00
|
|
|
type IntegerMeanReducer struct {
|
|
|
|
sum int64
|
|
|
|
count uint32
|
|
|
|
}
|
|
|
|
|
2016-03-24 14:40:55 +00:00
|
|
|
// NewIntegerMeanReducer creates a new IntegerMeanReducer.
|
2016-03-02 20:52:03 +00:00
|
|
|
func NewIntegerMeanReducer() *IntegerMeanReducer {
|
|
|
|
return &IntegerMeanReducer{}
|
|
|
|
}
|
|
|
|
|
2016-03-24 14:40:55 +00:00
|
|
|
// AggregateInteger aggregates a point into the reducer.
|
2016-03-07 18:25:45 +00:00
|
|
|
func (r *IntegerMeanReducer) AggregateInteger(p *IntegerPoint) {
|
2016-03-02 20:52:03 +00:00
|
|
|
if p.Aggregated >= 2 {
|
|
|
|
r.sum += p.Value * int64(p.Aggregated)
|
|
|
|
r.count += p.Aggregated
|
|
|
|
} else {
|
|
|
|
r.sum += p.Value
|
|
|
|
r.count++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-24 14:40:55 +00:00
|
|
|
// Emit emits the mean of the aggregated points as a single point.
|
2016-03-07 18:25:45 +00:00
|
|
|
func (r *IntegerMeanReducer) Emit() []FloatPoint {
|
|
|
|
return []FloatPoint{{
|
2016-03-04 01:53:45 +00:00
|
|
|
Time: ZeroTime,
|
2016-03-02 20:52:03 +00:00
|
|
|
Value: float64(r.sum) / float64(r.count),
|
|
|
|
Aggregated: r.count,
|
2016-03-07 18:25:45 +00:00
|
|
|
}}
|
2016-03-02 20:52:03 +00:00
|
|
|
}
|
2016-03-24 14:40:55 +00:00
|
|
|
|
2017-09-18 16:28:37 +00:00
|
|
|
// UnsignedMeanReducer calculates the mean of the aggregated points.
|
|
|
|
type UnsignedMeanReducer struct {
|
|
|
|
sum uint64
|
|
|
|
count uint32
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewUnsignedMeanReducer creates a new UnsignedMeanReducer.
|
|
|
|
func NewUnsignedMeanReducer() *UnsignedMeanReducer {
|
|
|
|
return &UnsignedMeanReducer{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AggregateUnsigned aggregates a point into the reducer.
|
|
|
|
func (r *UnsignedMeanReducer) AggregateUnsigned(p *UnsignedPoint) {
|
|
|
|
if p.Aggregated >= 2 {
|
|
|
|
r.sum += p.Value * uint64(p.Aggregated)
|
|
|
|
r.count += p.Aggregated
|
|
|
|
} else {
|
|
|
|
r.sum += p.Value
|
|
|
|
r.count++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Emit emits the mean of the aggregated points as a single point.
|
|
|
|
func (r *UnsignedMeanReducer) Emit() []FloatPoint {
|
|
|
|
return []FloatPoint{{
|
|
|
|
Time: ZeroTime,
|
|
|
|
Value: float64(r.sum) / float64(r.count),
|
|
|
|
Aggregated: r.count,
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
2016-02-19 21:05:36 +00:00
|
|
|
// FloatDerivativeReducer calculates the derivative of the aggregated points.
|
|
|
|
type FloatDerivativeReducer struct {
|
|
|
|
interval Interval
|
|
|
|
prev FloatPoint
|
|
|
|
curr FloatPoint
|
|
|
|
isNonNegative bool
|
|
|
|
ascending bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewFloatDerivativeReducer creates a new FloatDerivativeReducer.
|
|
|
|
func NewFloatDerivativeReducer(interval Interval, isNonNegative, ascending bool) *FloatDerivativeReducer {
|
|
|
|
return &FloatDerivativeReducer{
|
|
|
|
interval: interval,
|
|
|
|
isNonNegative: isNonNegative,
|
|
|
|
ascending: ascending,
|
|
|
|
prev: FloatPoint{Nil: true},
|
|
|
|
curr: FloatPoint{Nil: true},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AggregateFloat aggregates a point into the reducer and updates the current window.
|
|
|
|
func (r *FloatDerivativeReducer) AggregateFloat(p *FloatPoint) {
|
2016-09-13 21:57:34 +00:00
|
|
|
// Skip past a point when it does not advance the stream. A joined series
|
|
|
|
// may have multiple points at the same time so we will discard anything
|
|
|
|
// except the first point we encounter.
|
|
|
|
if !r.curr.Nil && r.curr.Time == p.Time {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-02-19 21:05:36 +00:00
|
|
|
r.prev = r.curr
|
|
|
|
r.curr = *p
|
|
|
|
}
|
|
|
|
|
|
|
|
// Emit emits the derivative of the reducer at the current point.
|
|
|
|
func (r *FloatDerivativeReducer) Emit() []FloatPoint {
|
|
|
|
if !r.prev.Nil {
|
|
|
|
// Calculate the derivative of successive points by dividing the
|
|
|
|
// difference of each value by the elapsed time normalized to the interval.
|
|
|
|
diff := r.curr.Value - r.prev.Value
|
|
|
|
elapsed := r.curr.Time - r.prev.Time
|
|
|
|
if !r.ascending {
|
|
|
|
elapsed = -elapsed
|
|
|
|
}
|
2016-04-08 19:00:47 +00:00
|
|
|
value := diff / (float64(elapsed) / float64(r.interval.Duration))
|
2016-02-19 21:05:36 +00:00
|
|
|
|
2016-09-13 21:57:34 +00:00
|
|
|
// Mark this point as read by changing the previous point to nil.
|
|
|
|
r.prev.Nil = true
|
|
|
|
|
2016-02-19 21:05:36 +00:00
|
|
|
// Drop negative values for non-negative derivatives.
|
|
|
|
if r.isNonNegative && diff < 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return []FloatPoint{{Time: r.curr.Time, Value: value}}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// IntegerDerivativeReducer calculates the derivative of the aggregated points.
|
|
|
|
type IntegerDerivativeReducer struct {
|
|
|
|
interval Interval
|
|
|
|
prev IntegerPoint
|
|
|
|
curr IntegerPoint
|
|
|
|
isNonNegative bool
|
|
|
|
ascending bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewIntegerDerivativeReducer creates a new IntegerDerivativeReducer.
|
|
|
|
func NewIntegerDerivativeReducer(interval Interval, isNonNegative, ascending bool) *IntegerDerivativeReducer {
|
|
|
|
return &IntegerDerivativeReducer{
|
|
|
|
interval: interval,
|
|
|
|
isNonNegative: isNonNegative,
|
|
|
|
ascending: ascending,
|
|
|
|
prev: IntegerPoint{Nil: true},
|
|
|
|
curr: IntegerPoint{Nil: true},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AggregateInteger aggregates a point into the reducer and updates the current window.
|
|
|
|
func (r *IntegerDerivativeReducer) AggregateInteger(p *IntegerPoint) {
|
2016-09-13 21:57:34 +00:00
|
|
|
// Skip past a point when it does not advance the stream. A joined series
|
|
|
|
// may have multiple points at the same time so we will discard anything
|
|
|
|
// except the first point we encounter.
|
|
|
|
if !r.curr.Nil && r.curr.Time == p.Time {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-02-19 21:05:36 +00:00
|
|
|
r.prev = r.curr
|
|
|
|
r.curr = *p
|
|
|
|
}
|
|
|
|
|
|
|
|
// Emit emits the derivative of the reducer at the current point.
|
|
|
|
func (r *IntegerDerivativeReducer) Emit() []FloatPoint {
|
|
|
|
if !r.prev.Nil {
|
|
|
|
// Calculate the derivative of successive points by dividing the
|
|
|
|
// difference of each value by the elapsed time normalized to the interval.
|
|
|
|
diff := float64(r.curr.Value - r.prev.Value)
|
|
|
|
elapsed := r.curr.Time - r.prev.Time
|
|
|
|
if !r.ascending {
|
|
|
|
elapsed = -elapsed
|
|
|
|
}
|
2016-04-08 19:00:47 +00:00
|
|
|
value := diff / (float64(elapsed) / float64(r.interval.Duration))
|
2016-02-19 21:05:36 +00:00
|
|
|
|
2016-09-13 21:57:34 +00:00
|
|
|
// Mark this point as read by changing the previous point to nil.
|
|
|
|
r.prev.Nil = true
|
|
|
|
|
2016-02-19 21:05:36 +00:00
|
|
|
// Drop negative values for non-negative derivatives.
|
|
|
|
if r.isNonNegative && diff < 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return []FloatPoint{{Time: r.curr.Time, Value: value}}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-09-18 16:28:37 +00:00
|
|
|
// UnsignedDerivativeReducer calculates the derivative of the aggregated points.
|
|
|
|
type UnsignedDerivativeReducer struct {
|
|
|
|
interval Interval
|
|
|
|
prev UnsignedPoint
|
|
|
|
curr UnsignedPoint
|
|
|
|
isNonNegative bool
|
|
|
|
ascending bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewUnsignedDerivativeReducer creates a new UnsignedDerivativeReducer.
|
|
|
|
func NewUnsignedDerivativeReducer(interval Interval, isNonNegative, ascending bool) *UnsignedDerivativeReducer {
|
|
|
|
return &UnsignedDerivativeReducer{
|
|
|
|
interval: interval,
|
|
|
|
isNonNegative: isNonNegative,
|
|
|
|
ascending: ascending,
|
|
|
|
prev: UnsignedPoint{Nil: true},
|
|
|
|
curr: UnsignedPoint{Nil: true},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AggregateUnsigned aggregates a point into the reducer and updates the current window.
|
|
|
|
func (r *UnsignedDerivativeReducer) AggregateUnsigned(p *UnsignedPoint) {
|
|
|
|
// Skip past a point when it does not advance the stream. A joined series
|
|
|
|
// may have multiple points at the same time so we will discard anything
|
|
|
|
// except the first point we encounter.
|
|
|
|
if !r.curr.Nil && r.curr.Time == p.Time {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
r.prev = r.curr
|
|
|
|
r.curr = *p
|
|
|
|
}
|
|
|
|
|
|
|
|
// Emit emits the derivative of the reducer at the current point.
|
|
|
|
func (r *UnsignedDerivativeReducer) Emit() []FloatPoint {
|
|
|
|
if !r.prev.Nil {
|
|
|
|
// Calculate the derivative of successive points by dividing the
|
|
|
|
// difference of each value by the elapsed time normalized to the interval.
|
|
|
|
var diff float64
|
|
|
|
if r.curr.Value > r.prev.Value {
|
|
|
|
diff = float64(r.curr.Value - r.prev.Value)
|
|
|
|
} else {
|
|
|
|
diff = -float64(r.prev.Value - r.curr.Value)
|
|
|
|
}
|
|
|
|
elapsed := r.curr.Time - r.prev.Time
|
|
|
|
if !r.ascending {
|
|
|
|
elapsed = -elapsed
|
|
|
|
}
|
|
|
|
value := diff / (float64(elapsed) / float64(r.interval.Duration))
|
|
|
|
|
|
|
|
// Mark this point as read by changing the previous point to nil.
|
|
|
|
r.prev.Nil = true
|
|
|
|
|
|
|
|
// Drop negative values for non-negative derivatives.
|
|
|
|
if r.isNonNegative && diff < 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return []FloatPoint{{Time: r.curr.Time, Value: value}}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-02-19 21:05:36 +00:00
|
|
|
// FloatDifferenceReducer calculates the derivative of the aggregated points.
|
|
|
|
type FloatDifferenceReducer struct {
|
2017-03-31 02:26:14 +00:00
|
|
|
isNonNegative bool
|
|
|
|
prev FloatPoint
|
|
|
|
curr FloatPoint
|
2016-02-19 21:05:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewFloatDifferenceReducer creates a new FloatDifferenceReducer.
|
2017-03-31 02:26:14 +00:00
|
|
|
func NewFloatDifferenceReducer(isNonNegative bool) *FloatDifferenceReducer {
|
2016-02-19 21:05:36 +00:00
|
|
|
return &FloatDifferenceReducer{
|
2017-03-31 02:26:14 +00:00
|
|
|
isNonNegative: isNonNegative,
|
|
|
|
prev: FloatPoint{Nil: true},
|
|
|
|
curr: FloatPoint{Nil: true},
|
2016-02-19 21:05:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AggregateFloat aggregates a point into the reducer and updates the current window.
|
|
|
|
func (r *FloatDifferenceReducer) AggregateFloat(p *FloatPoint) {
|
2016-09-13 21:57:34 +00:00
|
|
|
// Skip past a point when it does not advance the stream. A joined series
|
|
|
|
// may have multiple points at the same time so we will discard anything
|
|
|
|
// except the first point we encounter.
|
|
|
|
if !r.curr.Nil && r.curr.Time == p.Time {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-02-19 21:05:36 +00:00
|
|
|
r.prev = r.curr
|
|
|
|
r.curr = *p
|
|
|
|
}
|
|
|
|
|
|
|
|
// Emit emits the difference of the reducer at the current point.
|
|
|
|
func (r *FloatDifferenceReducer) Emit() []FloatPoint {
|
|
|
|
if !r.prev.Nil {
|
|
|
|
// Calculate the difference of successive points.
|
|
|
|
value := r.curr.Value - r.prev.Value
|
2016-09-13 21:57:34 +00:00
|
|
|
|
2017-03-31 02:26:14 +00:00
|
|
|
// If it is non_negative_difference discard any negative value. Since
|
|
|
|
// prev is still marked as unread. The correctness can be ensured.
|
|
|
|
if r.isNonNegative && value < 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-09-13 21:57:34 +00:00
|
|
|
// Mark this point as read by changing the previous point to nil.
|
|
|
|
r.prev.Nil = true
|
2016-02-19 21:05:36 +00:00
|
|
|
return []FloatPoint{{Time: r.curr.Time, Value: value}}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// IntegerDifferenceReducer calculates the derivative of the aggregated points.
|
|
|
|
type IntegerDifferenceReducer struct {
|
2017-03-31 02:26:14 +00:00
|
|
|
isNonNegative bool
|
|
|
|
prev IntegerPoint
|
|
|
|
curr IntegerPoint
|
2016-02-19 21:05:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewIntegerDifferenceReducer creates a new IntegerDifferenceReducer.
|
2017-03-31 02:26:14 +00:00
|
|
|
func NewIntegerDifferenceReducer(isNonNegative bool) *IntegerDifferenceReducer {
|
2016-02-19 21:05:36 +00:00
|
|
|
return &IntegerDifferenceReducer{
|
2017-03-31 02:26:14 +00:00
|
|
|
isNonNegative: isNonNegative,
|
|
|
|
prev: IntegerPoint{Nil: true},
|
|
|
|
curr: IntegerPoint{Nil: true},
|
2016-02-19 21:05:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AggregateInteger aggregates a point into the reducer and updates the current window.
|
|
|
|
func (r *IntegerDifferenceReducer) AggregateInteger(p *IntegerPoint) {
|
2016-09-13 21:57:34 +00:00
|
|
|
// Skip past a point when it does not advance the stream. A joined series
|
|
|
|
// may have multiple points at the same time so we will discard anything
|
|
|
|
// except the first point we encounter.
|
|
|
|
if !r.curr.Nil && r.curr.Time == p.Time {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-02-19 21:05:36 +00:00
|
|
|
r.prev = r.curr
|
|
|
|
r.curr = *p
|
|
|
|
}
|
|
|
|
|
|
|
|
// Emit emits the difference of the reducer at the current point.
|
|
|
|
func (r *IntegerDifferenceReducer) Emit() []IntegerPoint {
|
|
|
|
if !r.prev.Nil {
|
|
|
|
// Calculate the difference of successive points.
|
|
|
|
value := r.curr.Value - r.prev.Value
|
2016-09-13 21:57:34 +00:00
|
|
|
|
2017-03-31 02:26:14 +00:00
|
|
|
// If it is non_negative_difference discard any negative value. Since
|
|
|
|
// prev is still marked as unread. The correctness can be ensured.
|
|
|
|
if r.isNonNegative && value < 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-09-13 21:57:34 +00:00
|
|
|
// Mark this point as read by changing the previous point to nil.
|
|
|
|
r.prev.Nil = true
|
2017-03-31 02:26:14 +00:00
|
|
|
|
2016-02-19 21:05:36 +00:00
|
|
|
return []IntegerPoint{{Time: r.curr.Time, Value: value}}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-09-18 16:28:37 +00:00
|
|
|
// UnsignedDifferenceReducer calculates the derivative of the aggregated points.
|
|
|
|
type UnsignedDifferenceReducer struct {
|
|
|
|
isNonNegative bool
|
|
|
|
prev UnsignedPoint
|
|
|
|
curr UnsignedPoint
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewUnsignedDifferenceReducer creates a new UnsignedDifferenceReducer.
|
|
|
|
func NewUnsignedDifferenceReducer(isNonNegative bool) *UnsignedDifferenceReducer {
|
|
|
|
return &UnsignedDifferenceReducer{
|
|
|
|
isNonNegative: isNonNegative,
|
|
|
|
prev: UnsignedPoint{Nil: true},
|
|
|
|
curr: UnsignedPoint{Nil: true},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AggregateUnsigned aggregates a point into the reducer and updates the current window.
|
|
|
|
func (r *UnsignedDifferenceReducer) AggregateUnsigned(p *UnsignedPoint) {
|
|
|
|
// Skip past a point when it does not advance the stream. A joined series
|
|
|
|
// may have multiple points at the same time so we will discard anything
|
|
|
|
// except the first point we encounter.
|
|
|
|
if !r.curr.Nil && r.curr.Time == p.Time {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
r.prev = r.curr
|
|
|
|
r.curr = *p
|
|
|
|
}
|
|
|
|
|
|
|
|
// Emit emits the difference of the reducer at the current point.
|
|
|
|
func (r *UnsignedDifferenceReducer) Emit() []UnsignedPoint {
|
|
|
|
if !r.prev.Nil {
|
|
|
|
// If it is non_negative_difference discard any negative value. Since
|
|
|
|
// prev is still marked as unread. The correctness can be ensured.
|
|
|
|
if r.isNonNegative && r.curr.Value < r.prev.Value {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate the difference of successive points.
|
|
|
|
value := r.curr.Value - r.prev.Value
|
|
|
|
|
|
|
|
// Mark this point as read by changing the previous point to nil.
|
|
|
|
r.prev.Nil = true
|
|
|
|
|
|
|
|
return []UnsignedPoint{{Time: r.curr.Time, Value: value}}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-03-24 14:40:55 +00:00
|
|
|
// FloatMovingAverageReducer calculates the moving average of the aggregated points.
|
|
|
|
type FloatMovingAverageReducer struct {
|
|
|
|
pos int
|
|
|
|
sum float64
|
|
|
|
time int64
|
|
|
|
buf []float64
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewFloatMovingAverageReducer creates a new FloatMovingAverageReducer.
|
|
|
|
func NewFloatMovingAverageReducer(n int) *FloatMovingAverageReducer {
|
|
|
|
return &FloatMovingAverageReducer{
|
|
|
|
buf: make([]float64, 0, n),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AggregateFloat aggregates a point into the reducer and updates the current window.
|
|
|
|
func (r *FloatMovingAverageReducer) AggregateFloat(p *FloatPoint) {
|
|
|
|
if len(r.buf) != cap(r.buf) {
|
|
|
|
r.buf = append(r.buf, p.Value)
|
|
|
|
} else {
|
|
|
|
r.sum -= r.buf[r.pos]
|
|
|
|
r.buf[r.pos] = p.Value
|
|
|
|
}
|
|
|
|
r.sum += p.Value
|
|
|
|
r.time = p.Time
|
|
|
|
r.pos++
|
|
|
|
if r.pos >= cap(r.buf) {
|
|
|
|
r.pos = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Emit emits the moving average of the current window. Emit should be called
|
|
|
|
// after every call to AggregateFloat and it will produce one point if there
|
|
|
|
// is enough data to fill a window, otherwise it will produce zero points.
|
|
|
|
func (r *FloatMovingAverageReducer) Emit() []FloatPoint {
|
|
|
|
if len(r.buf) != cap(r.buf) {
|
|
|
|
return []FloatPoint{}
|
|
|
|
}
|
|
|
|
return []FloatPoint{
|
|
|
|
{
|
|
|
|
Value: r.sum / float64(len(r.buf)),
|
|
|
|
Time: r.time,
|
|
|
|
Aggregated: uint32(len(r.buf)),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// IntegerMovingAverageReducer calculates the moving average of the aggregated points.
|
|
|
|
type IntegerMovingAverageReducer struct {
|
|
|
|
pos int
|
|
|
|
sum int64
|
|
|
|
time int64
|
|
|
|
buf []int64
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewIntegerMovingAverageReducer creates a new IntegerMovingAverageReducer.
|
|
|
|
func NewIntegerMovingAverageReducer(n int) *IntegerMovingAverageReducer {
|
|
|
|
return &IntegerMovingAverageReducer{
|
|
|
|
buf: make([]int64, 0, n),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AggregateInteger aggregates a point into the reducer and updates the current window.
|
|
|
|
func (r *IntegerMovingAverageReducer) AggregateInteger(p *IntegerPoint) {
|
|
|
|
if len(r.buf) != cap(r.buf) {
|
|
|
|
r.buf = append(r.buf, p.Value)
|
|
|
|
} else {
|
|
|
|
r.sum -= r.buf[r.pos]
|
|
|
|
r.buf[r.pos] = p.Value
|
|
|
|
}
|
|
|
|
r.sum += p.Value
|
|
|
|
r.time = p.Time
|
|
|
|
r.pos++
|
|
|
|
if r.pos >= cap(r.buf) {
|
|
|
|
r.pos = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Emit emits the moving average of the current window. Emit should be called
|
|
|
|
// after every call to AggregateInteger and it will produce one point if there
|
|
|
|
// is enough data to fill a window, otherwise it will produce zero points.
|
|
|
|
func (r *IntegerMovingAverageReducer) Emit() []FloatPoint {
|
|
|
|
if len(r.buf) != cap(r.buf) {
|
|
|
|
return []FloatPoint{}
|
|
|
|
}
|
|
|
|
return []FloatPoint{
|
|
|
|
{
|
|
|
|
Value: float64(r.sum) / float64(len(r.buf)),
|
|
|
|
Time: r.time,
|
|
|
|
Aggregated: uint32(len(r.buf)),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2016-05-12 21:11:19 +00:00
|
|
|
|
2017-09-18 16:28:37 +00:00
|
|
|
// UnsignedMovingAverageReducer calculates the moving average of the aggregated points.
|
|
|
|
type UnsignedMovingAverageReducer struct {
|
|
|
|
pos int
|
|
|
|
sum uint64
|
|
|
|
time int64
|
|
|
|
buf []uint64
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewUnsignedMovingAverageReducer creates a new UnsignedMovingAverageReducer.
|
|
|
|
func NewUnsignedMovingAverageReducer(n int) *UnsignedMovingAverageReducer {
|
|
|
|
return &UnsignedMovingAverageReducer{
|
|
|
|
buf: make([]uint64, 0, n),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AggregateUnsigned aggregates a point into the reducer and updates the current window.
|
|
|
|
func (r *UnsignedMovingAverageReducer) AggregateUnsigned(p *UnsignedPoint) {
|
|
|
|
if len(r.buf) != cap(r.buf) {
|
|
|
|
r.buf = append(r.buf, p.Value)
|
|
|
|
} else {
|
|
|
|
r.sum -= r.buf[r.pos]
|
|
|
|
r.buf[r.pos] = p.Value
|
|
|
|
}
|
|
|
|
r.sum += p.Value
|
|
|
|
r.time = p.Time
|
|
|
|
r.pos++
|
|
|
|
if r.pos >= cap(r.buf) {
|
|
|
|
r.pos = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Emit emits the moving average of the current window. Emit should be called
|
|
|
|
// after every call to AggregateUnsigned and it will produce one point if there
|
|
|
|
// is enough data to fill a window, otherwise it will produce zero points.
|
|
|
|
func (r *UnsignedMovingAverageReducer) Emit() []FloatPoint {
|
|
|
|
if len(r.buf) != cap(r.buf) {
|
|
|
|
return []FloatPoint{}
|
|
|
|
}
|
|
|
|
return []FloatPoint{
|
|
|
|
{
|
|
|
|
Value: float64(r.sum) / float64(len(r.buf)),
|
|
|
|
Time: r.time,
|
|
|
|
Aggregated: uint32(len(r.buf)),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-07 15:11:50 +00:00
|
|
|
// FloatCumulativeSumReducer cumulates the values from each point.
|
|
|
|
type FloatCumulativeSumReducer struct {
|
|
|
|
curr FloatPoint
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewFloatCumulativeSumReducer creates a new FloatCumulativeSumReducer.
|
|
|
|
func NewFloatCumulativeSumReducer() *FloatCumulativeSumReducer {
|
|
|
|
return &FloatCumulativeSumReducer{
|
|
|
|
curr: FloatPoint{Nil: true},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *FloatCumulativeSumReducer) AggregateFloat(p *FloatPoint) {
|
|
|
|
r.curr.Value += p.Value
|
|
|
|
r.curr.Time = p.Time
|
|
|
|
r.curr.Nil = false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *FloatCumulativeSumReducer) Emit() []FloatPoint {
|
|
|
|
var pts []FloatPoint
|
|
|
|
if !r.curr.Nil {
|
|
|
|
pts = []FloatPoint{r.curr}
|
|
|
|
}
|
|
|
|
return pts
|
|
|
|
}
|
|
|
|
|
|
|
|
// IntegerCumulativeSumReducer cumulates the values from each point.
|
|
|
|
type IntegerCumulativeSumReducer struct {
|
|
|
|
curr IntegerPoint
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewIntegerCumulativeSumReducer creates a new IntegerCumulativeSumReducer.
|
|
|
|
func NewIntegerCumulativeSumReducer() *IntegerCumulativeSumReducer {
|
|
|
|
return &IntegerCumulativeSumReducer{
|
|
|
|
curr: IntegerPoint{Nil: true},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *IntegerCumulativeSumReducer) AggregateInteger(p *IntegerPoint) {
|
|
|
|
r.curr.Value += p.Value
|
|
|
|
r.curr.Time = p.Time
|
|
|
|
r.curr.Nil = false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *IntegerCumulativeSumReducer) Emit() []IntegerPoint {
|
|
|
|
var pts []IntegerPoint
|
|
|
|
if !r.curr.Nil {
|
|
|
|
pts = []IntegerPoint{r.curr}
|
|
|
|
}
|
|
|
|
return pts
|
|
|
|
}
|
|
|
|
|
2017-09-18 16:28:37 +00:00
|
|
|
// UnsignedCumulativeSumReducer cumulates the values from each point.
|
|
|
|
type UnsignedCumulativeSumReducer struct {
|
|
|
|
curr UnsignedPoint
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewUnsignedCumulativeSumReducer creates a new UnsignedCumulativeSumReducer.
|
|
|
|
func NewUnsignedCumulativeSumReducer() *UnsignedCumulativeSumReducer {
|
|
|
|
return &UnsignedCumulativeSumReducer{
|
|
|
|
curr: UnsignedPoint{Nil: true},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *UnsignedCumulativeSumReducer) AggregateUnsigned(p *UnsignedPoint) {
|
|
|
|
r.curr.Value += p.Value
|
|
|
|
r.curr.Time = p.Time
|
|
|
|
r.curr.Nil = false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *UnsignedCumulativeSumReducer) Emit() []UnsignedPoint {
|
|
|
|
var pts []UnsignedPoint
|
|
|
|
if !r.curr.Nil {
|
|
|
|
pts = []UnsignedPoint{r.curr}
|
|
|
|
}
|
|
|
|
return pts
|
|
|
|
}
|
|
|
|
|
2016-05-12 21:11:19 +00:00
|
|
|
// FloatHoltWintersReducer forecasts a series into the future.
|
|
|
|
// This is done using the Holt-Winters damped method.
|
|
|
|
// 1. Using the series the initial values are calculated using a SSE.
|
|
|
|
// 2. The series is forecasted into the future using the iterative relations.
|
|
|
|
type FloatHoltWintersReducer struct {
|
|
|
|
// Season period
|
|
|
|
m int
|
|
|
|
seasonal bool
|
|
|
|
|
|
|
|
// Horizon
|
|
|
|
h int
|
|
|
|
|
|
|
|
// Interval between points
|
|
|
|
interval int64
|
2016-05-26 19:57:15 +00:00
|
|
|
// interval / 2 -- used to perform rounding
|
|
|
|
halfInterval int64
|
2016-05-12 21:11:19 +00:00
|
|
|
|
|
|
|
// Whether to include all data or only future values
|
|
|
|
includeFitData bool
|
|
|
|
|
|
|
|
// NelderMead optimizer
|
|
|
|
optim *neldermead.Optimizer
|
|
|
|
// Small difference bound for the optimizer
|
|
|
|
epsilon float64
|
|
|
|
|
|
|
|
y []float64
|
|
|
|
points []FloatPoint
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
2016-06-02 16:15:15 +00:00
|
|
|
// Arbitrary weight for initializing some intial guesses.
|
|
|
|
// This should be in the range [0,1]
|
|
|
|
hwWeight = 0.5
|
|
|
|
// Epsilon value for the minimization process
|
|
|
|
hwDefaultEpsilon = 1.0e-4
|
|
|
|
// Define a grid of initial guesses for the parameters: alpha, beta, gamma, and phi.
|
|
|
|
// Keep in mind that this grid is N^4 so we should keep N small
|
|
|
|
// The starting lower guess
|
|
|
|
hwGuessLower = 0.3
|
|
|
|
// The upper bound on the grid
|
|
|
|
hwGuessUpper = 1.0
|
|
|
|
// The step between guesses
|
|
|
|
hwGuessStep = 0.4
|
2016-05-12 21:11:19 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// NewFloatHoltWintersReducer creates a new FloatHoltWintersReducer.
|
|
|
|
func NewFloatHoltWintersReducer(h, m int, includeFitData bool, interval time.Duration) *FloatHoltWintersReducer {
|
|
|
|
seasonal := true
|
|
|
|
if m < 2 {
|
|
|
|
seasonal = false
|
|
|
|
}
|
|
|
|
return &FloatHoltWintersReducer{
|
|
|
|
h: h,
|
|
|
|
m: m,
|
|
|
|
seasonal: seasonal,
|
|
|
|
includeFitData: includeFitData,
|
|
|
|
interval: int64(interval),
|
2016-05-26 19:57:15 +00:00
|
|
|
halfInterval: int64(interval) / 2,
|
2016-05-12 21:11:19 +00:00
|
|
|
optim: neldermead.New(),
|
2016-06-02 16:15:15 +00:00
|
|
|
epsilon: hwDefaultEpsilon,
|
2016-05-12 21:11:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *FloatHoltWintersReducer) aggregate(time int64, value float64) {
|
|
|
|
r.points = append(r.points, FloatPoint{
|
|
|
|
Time: time,
|
|
|
|
Value: value,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// AggregateFloat aggregates a point into the reducer and updates the current window.
|
|
|
|
func (r *FloatHoltWintersReducer) AggregateFloat(p *FloatPoint) {
|
|
|
|
r.aggregate(p.Time, p.Value)
|
|
|
|
}
|
|
|
|
|
|
|
|
// AggregateInteger aggregates a point into the reducer and updates the current window.
|
|
|
|
func (r *FloatHoltWintersReducer) AggregateInteger(p *IntegerPoint) {
|
|
|
|
r.aggregate(p.Time, float64(p.Value))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *FloatHoltWintersReducer) roundTime(t int64) int64 {
|
2016-05-26 19:57:15 +00:00
|
|
|
// Overflow safe round function
|
|
|
|
remainder := t % r.interval
|
|
|
|
if remainder > r.halfInterval {
|
|
|
|
// Round up
|
|
|
|
return (t/r.interval + 1) * r.interval
|
|
|
|
}
|
2016-06-20 13:51:02 +00:00
|
|
|
// Round down
|
|
|
|
return (t / r.interval) * r.interval
|
2016-05-12 21:11:19 +00:00
|
|
|
}
|
|
|
|
|
2016-06-20 13:51:02 +00:00
|
|
|
// Emit returns the points generated by the HoltWinters algorithm.
|
2016-05-12 21:11:19 +00:00
|
|
|
func (r *FloatHoltWintersReducer) Emit() []FloatPoint {
|
|
|
|
if l := len(r.points); l < 2 || r.seasonal && l < r.m || r.h <= 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// First fill in r.y with values and NaNs for missing values
|
|
|
|
start, stop := r.roundTime(r.points[0].Time), r.roundTime(r.points[len(r.points)-1].Time)
|
|
|
|
count := (stop - start) / r.interval
|
|
|
|
if count <= 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
r.y = make([]float64, 1, count)
|
|
|
|
r.y[0] = r.points[0].Value
|
|
|
|
t := r.roundTime(r.points[0].Time)
|
|
|
|
for _, p := range r.points[1:] {
|
|
|
|
rounded := r.roundTime(p.Time)
|
|
|
|
if rounded <= t {
|
|
|
|
// Drop values that occur for the same time bucket
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
t += r.interval
|
|
|
|
// Add any missing values before the next point
|
|
|
|
for rounded != t {
|
|
|
|
// Add in a NaN so we can skip it later.
|
|
|
|
r.y = append(r.y, math.NaN())
|
|
|
|
t += r.interval
|
|
|
|
}
|
|
|
|
r.y = append(r.y, p.Value)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Seasonality
|
|
|
|
m := r.m
|
|
|
|
|
|
|
|
// Starting guesses
|
|
|
|
// NOTE: Since these values are guesses
|
|
|
|
// in the cases where we were missing data,
|
|
|
|
// we can just skip the value and call it good.
|
|
|
|
|
2016-06-20 13:51:02 +00:00
|
|
|
l0 := 0.0
|
2016-05-12 21:11:19 +00:00
|
|
|
if r.seasonal {
|
|
|
|
for i := 0; i < m; i++ {
|
|
|
|
if !math.IsNaN(r.y[i]) {
|
2016-06-20 13:51:02 +00:00
|
|
|
l0 += (1 / float64(m)) * r.y[i]
|
2016-05-12 21:11:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2016-06-20 13:51:02 +00:00
|
|
|
l0 += hwWeight * r.y[0]
|
2016-05-12 21:11:19 +00:00
|
|
|
}
|
|
|
|
|
2016-06-20 13:51:02 +00:00
|
|
|
b0 := 0.0
|
2016-05-12 21:11:19 +00:00
|
|
|
if r.seasonal {
|
|
|
|
for i := 0; i < m && m+i < len(r.y); i++ {
|
|
|
|
if !math.IsNaN(r.y[i]) && !math.IsNaN(r.y[m+i]) {
|
2016-06-20 13:51:02 +00:00
|
|
|
b0 += 1 / float64(m*m) * (r.y[m+i] - r.y[i])
|
2016-05-12 21:11:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if !math.IsNaN(r.y[1]) {
|
2016-06-20 13:51:02 +00:00
|
|
|
b0 = hwWeight * (r.y[1] - r.y[0])
|
2016-05-12 21:11:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var s []float64
|
|
|
|
if r.seasonal {
|
|
|
|
s = make([]float64, m)
|
|
|
|
for i := 0; i < m; i++ {
|
|
|
|
if !math.IsNaN(r.y[i]) {
|
2016-06-20 13:51:02 +00:00
|
|
|
s[i] = r.y[i] / l0
|
2016-05-12 21:11:19 +00:00
|
|
|
} else {
|
|
|
|
s[i] = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
parameters := make([]float64, 6+len(s))
|
2016-06-20 13:51:02 +00:00
|
|
|
parameters[4] = l0
|
|
|
|
parameters[5] = b0
|
2016-05-12 21:11:19 +00:00
|
|
|
o := len(parameters) - len(s)
|
|
|
|
for i := range s {
|
|
|
|
parameters[i+o] = s[i]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine best fit for the various parameters
|
2016-06-02 16:15:15 +00:00
|
|
|
minSSE := math.Inf(1)
|
|
|
|
var bestParams []float64
|
|
|
|
for alpha := hwGuessLower; alpha < hwGuessUpper; alpha += hwGuessStep {
|
|
|
|
for beta := hwGuessLower; beta < hwGuessUpper; beta += hwGuessStep {
|
|
|
|
for gamma := hwGuessLower; gamma < hwGuessUpper; gamma += hwGuessStep {
|
|
|
|
for phi := hwGuessLower; phi < hwGuessUpper; phi += hwGuessStep {
|
|
|
|
parameters[0] = alpha
|
|
|
|
parameters[1] = beta
|
|
|
|
parameters[2] = gamma
|
|
|
|
parameters[3] = phi
|
|
|
|
sse, params := r.optim.Optimize(r.sse, parameters, r.epsilon, 1)
|
|
|
|
if sse < minSSE || bestParams == nil {
|
|
|
|
minSSE = sse
|
|
|
|
bestParams = params
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-05-12 21:11:19 +00:00
|
|
|
|
|
|
|
// Forecast
|
2016-06-02 16:15:15 +00:00
|
|
|
forecasted := r.forecast(r.h, bestParams)
|
2016-05-12 21:11:19 +00:00
|
|
|
var points []FloatPoint
|
|
|
|
if r.includeFitData {
|
2016-06-02 16:15:15 +00:00
|
|
|
start := r.points[0].Time
|
|
|
|
points = make([]FloatPoint, 0, len(forecasted))
|
2016-05-12 21:11:19 +00:00
|
|
|
for i, v := range forecasted {
|
2016-06-02 16:15:15 +00:00
|
|
|
if !math.IsNaN(v) {
|
|
|
|
t := start + r.interval*(int64(i))
|
|
|
|
points = append(points, FloatPoint{
|
|
|
|
Value: v,
|
|
|
|
Time: t,
|
|
|
|
})
|
2016-05-12 21:11:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2016-06-02 16:15:15 +00:00
|
|
|
stop := r.points[len(r.points)-1].Time
|
|
|
|
points = make([]FloatPoint, 0, r.h)
|
2016-05-12 21:11:19 +00:00
|
|
|
for i, v := range forecasted[len(r.y):] {
|
2016-06-02 16:15:15 +00:00
|
|
|
if !math.IsNaN(v) {
|
|
|
|
t := stop + r.interval*(int64(i)+1)
|
|
|
|
points = append(points, FloatPoint{
|
|
|
|
Value: v,
|
|
|
|
Time: t,
|
|
|
|
})
|
2016-05-12 21:11:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Clear data set
|
|
|
|
r.y = r.y[0:0]
|
|
|
|
return points
|
|
|
|
}
|
|
|
|
|
|
|
|
// Using the recursive relations compute the next values
|
2016-06-20 13:51:02 +00:00
|
|
|
func (r *FloatHoltWintersReducer) next(alpha, beta, gamma, phi, phiH, yT, lTp, bTp, sTm, sTmh float64) (yTh, lT, bT, sT float64) {
|
|
|
|
lT = alpha*(yT/sTm) + (1-alpha)*(lTp+phi*bTp)
|
|
|
|
bT = beta*(lT-lTp) + (1-beta)*phi*bTp
|
|
|
|
sT = gamma*(yT/(lTp+phi*bTp)) + (1-gamma)*sTm
|
|
|
|
yTh = (lT + phiH*bT) * sTmh
|
2016-05-12 21:11:19 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Forecast the data h points into the future.
|
|
|
|
func (r *FloatHoltWintersReducer) forecast(h int, params []float64) []float64 {
|
2016-06-02 16:15:15 +00:00
|
|
|
// Constrain parameters
|
|
|
|
r.constrain(params)
|
|
|
|
|
2016-06-20 13:51:02 +00:00
|
|
|
yT := r.y[0]
|
2016-05-12 21:11:19 +00:00
|
|
|
|
|
|
|
phi := params[3]
|
2016-06-20 13:51:02 +00:00
|
|
|
phiH := phi
|
2016-05-12 21:11:19 +00:00
|
|
|
|
2016-06-20 13:51:02 +00:00
|
|
|
lT := params[4]
|
|
|
|
bT := params[5]
|
2016-05-12 21:11:19 +00:00
|
|
|
|
2016-06-20 13:51:02 +00:00
|
|
|
// seasonals is a ring buffer of past sT values
|
2016-05-12 21:11:19 +00:00
|
|
|
var seasonals []float64
|
|
|
|
var m, so int
|
|
|
|
if r.seasonal {
|
|
|
|
seasonals = params[6:]
|
|
|
|
m = len(params[6:])
|
|
|
|
if m == 1 {
|
|
|
|
seasonals[0] = 1
|
|
|
|
}
|
|
|
|
// Season index offset
|
|
|
|
so = m - 1
|
|
|
|
}
|
|
|
|
|
|
|
|
forecasted := make([]float64, len(r.y)+h)
|
2016-06-20 13:51:02 +00:00
|
|
|
forecasted[0] = yT
|
2016-05-12 21:11:19 +00:00
|
|
|
l := len(r.y)
|
|
|
|
var hm int
|
|
|
|
stm, stmh := 1.0, 1.0
|
|
|
|
for t := 1; t < l+h; t++ {
|
|
|
|
if r.seasonal {
|
2016-06-02 16:15:15 +00:00
|
|
|
hm = t % m
|
2016-05-12 21:11:19 +00:00
|
|
|
stm = seasonals[(t-m+so)%m]
|
|
|
|
stmh = seasonals[(t-m+hm+so)%m]
|
|
|
|
}
|
2016-12-29 19:13:48 +00:00
|
|
|
var sT float64
|
2016-06-20 13:51:02 +00:00
|
|
|
yT, lT, bT, sT = r.next(
|
2016-05-12 21:11:19 +00:00
|
|
|
params[0], // alpha
|
|
|
|
params[1], // beta
|
|
|
|
params[2], // gamma
|
|
|
|
phi,
|
2016-06-20 13:51:02 +00:00
|
|
|
phiH,
|
|
|
|
yT,
|
|
|
|
lT,
|
|
|
|
bT,
|
2016-05-12 21:11:19 +00:00
|
|
|
stm,
|
|
|
|
stmh,
|
|
|
|
)
|
2016-06-20 13:51:02 +00:00
|
|
|
phiH += math.Pow(phi, float64(t))
|
2016-05-12 21:11:19 +00:00
|
|
|
|
|
|
|
if r.seasonal {
|
2016-06-20 13:51:02 +00:00
|
|
|
seasonals[(t+so)%m] = sT
|
2016-06-02 16:15:15 +00:00
|
|
|
so++
|
2016-05-12 21:11:19 +00:00
|
|
|
}
|
|
|
|
|
2016-06-20 13:51:02 +00:00
|
|
|
forecasted[t] = yT
|
2016-05-12 21:11:19 +00:00
|
|
|
}
|
|
|
|
return forecasted
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compute sum squared error for the given parameters.
|
|
|
|
func (r *FloatHoltWintersReducer) sse(params []float64) float64 {
|
|
|
|
sse := 0.0
|
|
|
|
forecasted := r.forecast(0, params)
|
|
|
|
for i := range forecasted {
|
|
|
|
// Skip missing values since we cannot use them to compute an error.
|
|
|
|
if !math.IsNaN(r.y[i]) {
|
|
|
|
// Compute error
|
2016-06-02 16:15:15 +00:00
|
|
|
if math.IsNaN(forecasted[i]) {
|
|
|
|
// Penalize forecasted NaNs
|
|
|
|
return math.Inf(1)
|
|
|
|
}
|
2016-06-20 13:51:02 +00:00
|
|
|
diff := forecasted[i] - r.y[i]
|
|
|
|
sse += diff * diff
|
2016-05-12 21:11:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return sse
|
|
|
|
}
|
|
|
|
|
|
|
|
// Constrain alpha, beta, gamma, phi in the range [0, 1]
|
|
|
|
func (r *FloatHoltWintersReducer) constrain(x []float64) {
|
|
|
|
// alpha
|
|
|
|
if x[0] > 1 {
|
|
|
|
x[0] = 1
|
|
|
|
}
|
|
|
|
if x[0] < 0 {
|
|
|
|
x[0] = 0
|
|
|
|
}
|
|
|
|
// beta
|
|
|
|
if x[1] > 1 {
|
|
|
|
x[1] = 1
|
|
|
|
}
|
|
|
|
if x[1] < 0 {
|
|
|
|
x[1] = 0
|
|
|
|
}
|
|
|
|
// gamma
|
|
|
|
if x[2] > 1 {
|
|
|
|
x[2] = 1
|
|
|
|
}
|
|
|
|
if x[2] < 0 {
|
|
|
|
x[2] = 0
|
|
|
|
}
|
|
|
|
// phi
|
|
|
|
if x[3] > 1 {
|
|
|
|
x[3] = 1
|
|
|
|
}
|
|
|
|
if x[3] < 0 {
|
|
|
|
x[3] = 0
|
|
|
|
}
|
|
|
|
}
|
2016-11-06 12:54:26 +00:00
|
|
|
|
|
|
|
// FloatIntegralReducer calculates the time-integral of the aggregated points.
|
|
|
|
type FloatIntegralReducer struct {
|
|
|
|
interval Interval
|
|
|
|
sum float64
|
|
|
|
prev FloatPoint
|
2017-03-23 20:18:03 +00:00
|
|
|
window struct {
|
|
|
|
start int64
|
|
|
|
end int64
|
|
|
|
}
|
|
|
|
ch chan FloatPoint
|
|
|
|
opt IteratorOptions
|
2016-11-06 12:54:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewFloatIntegralReducer creates a new FloatIntegralReducer.
|
2017-03-23 20:18:03 +00:00
|
|
|
func NewFloatIntegralReducer(interval Interval, opt IteratorOptions) *FloatIntegralReducer {
|
2016-11-06 12:54:26 +00:00
|
|
|
return &FloatIntegralReducer{
|
|
|
|
interval: interval,
|
|
|
|
prev: FloatPoint{Nil: true},
|
2017-03-23 20:18:03 +00:00
|
|
|
ch: make(chan FloatPoint, 1),
|
|
|
|
opt: opt,
|
2016-11-06 12:54:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AggregateFloat aggregates a point into the reducer.
|
|
|
|
func (r *FloatIntegralReducer) AggregateFloat(p *FloatPoint) {
|
|
|
|
// If this is the first point, just save it
|
|
|
|
if r.prev.Nil {
|
|
|
|
r.prev = *p
|
2017-03-23 20:18:03 +00:00
|
|
|
if !r.opt.Interval.IsZero() {
|
|
|
|
// Record the end of the time interval.
|
|
|
|
// We do not care for whether the last number is inclusive or exclusive
|
|
|
|
// because we treat both the same for the involved math.
|
|
|
|
if r.opt.Ascending {
|
|
|
|
r.window.start, r.window.end = r.opt.Window(p.Time)
|
|
|
|
} else {
|
|
|
|
r.window.end, r.window.start = r.opt.Window(p.Time)
|
|
|
|
}
|
|
|
|
}
|
2016-11-06 12:54:26 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// If this point has the same timestamp as the previous one,
|
2017-03-23 20:18:03 +00:00
|
|
|
// skip the point. Points sent into this reducer are expected
|
|
|
|
// to be fed in order.
|
2016-11-06 12:54:26 +00:00
|
|
|
if r.prev.Time == p.Time {
|
|
|
|
r.prev = *p
|
|
|
|
return
|
2017-03-23 20:18:03 +00:00
|
|
|
} else if !r.opt.Interval.IsZero() && ((r.opt.Ascending && p.Time >= r.window.end) || (!r.opt.Ascending && p.Time <= r.window.end)) {
|
|
|
|
// If our previous time is not equal to the window, we need to
|
|
|
|
// interpolate the area at the end of this interval.
|
|
|
|
if r.prev.Time != r.window.end {
|
|
|
|
value := linearFloat(r.window.end, r.prev.Time, p.Time, r.prev.Value, p.Value)
|
|
|
|
elapsed := float64(r.window.end-r.prev.Time) / float64(r.interval.Duration)
|
|
|
|
r.sum += 0.5 * (value + r.prev.Value) * elapsed
|
|
|
|
|
|
|
|
r.prev.Value = value
|
|
|
|
r.prev.Time = r.window.end
|
|
|
|
}
|
|
|
|
|
|
|
|
// Emit the current point through the channel and then clear it.
|
|
|
|
r.ch <- FloatPoint{Time: r.window.start, Value: r.sum}
|
|
|
|
if r.opt.Ascending {
|
|
|
|
r.window.start, r.window.end = r.opt.Window(p.Time)
|
|
|
|
} else {
|
|
|
|
r.window.end, r.window.start = r.opt.Window(p.Time)
|
|
|
|
}
|
|
|
|
r.sum = 0.0
|
2016-11-06 12:54:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Normal operation: update the sum using the trapezium rule
|
|
|
|
elapsed := float64(p.Time-r.prev.Time) / float64(r.interval.Duration)
|
|
|
|
r.sum += 0.5 * (p.Value + r.prev.Value) * elapsed
|
|
|
|
r.prev = *p
|
|
|
|
}
|
|
|
|
|
|
|
|
// Emit emits the time-integral of the aggregated points as a single point.
|
|
|
|
// InfluxQL convention dictates that outside a group-by-time clause we return
|
|
|
|
// a timestamp of zero. Within a group-by-time, we can set the time to ZeroTime
|
|
|
|
// and a higher level will change it to the start of the time group.
|
|
|
|
func (r *FloatIntegralReducer) Emit() []FloatPoint {
|
2017-03-23 20:18:03 +00:00
|
|
|
select {
|
|
|
|
case pt, ok := <-r.ch:
|
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return []FloatPoint{pt}
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close flushes any in progress points to ensure any remaining points are
|
|
|
|
// emitted.
|
|
|
|
func (r *FloatIntegralReducer) Close() error {
|
|
|
|
// If our last point is at the start time, then discard this point since
|
|
|
|
// there is no area within this bucket. Otherwise, send off what we
|
|
|
|
// currently have as the final point.
|
|
|
|
if !r.prev.Nil && r.prev.Time != r.window.start {
|
|
|
|
r.ch <- FloatPoint{Time: r.window.start, Value: r.sum}
|
|
|
|
}
|
|
|
|
close(r.ch)
|
|
|
|
return nil
|
2016-11-06 12:54:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// IntegerIntegralReducer calculates the time-integral of the aggregated points.
|
|
|
|
type IntegerIntegralReducer struct {
|
|
|
|
interval Interval
|
|
|
|
sum float64
|
|
|
|
prev IntegerPoint
|
2017-03-23 20:18:03 +00:00
|
|
|
window struct {
|
|
|
|
start int64
|
|
|
|
end int64
|
|
|
|
}
|
|
|
|
ch chan FloatPoint
|
|
|
|
opt IteratorOptions
|
2016-11-06 12:54:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewIntegerIntegralReducer creates a new IntegerIntegralReducer.
|
2017-03-23 20:18:03 +00:00
|
|
|
func NewIntegerIntegralReducer(interval Interval, opt IteratorOptions) *IntegerIntegralReducer {
|
2016-11-06 12:54:26 +00:00
|
|
|
return &IntegerIntegralReducer{
|
|
|
|
interval: interval,
|
|
|
|
prev: IntegerPoint{Nil: true},
|
2017-03-23 20:18:03 +00:00
|
|
|
ch: make(chan FloatPoint, 1),
|
|
|
|
opt: opt,
|
2016-11-06 12:54:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AggregateInteger aggregates a point into the reducer.
|
|
|
|
func (r *IntegerIntegralReducer) AggregateInteger(p *IntegerPoint) {
|
|
|
|
// If this is the first point, just save it
|
|
|
|
if r.prev.Nil {
|
|
|
|
r.prev = *p
|
2017-03-23 20:18:03 +00:00
|
|
|
|
|
|
|
// Record the end of the time interval.
|
|
|
|
// We do not care for whether the last number is inclusive or exclusive
|
|
|
|
// because we treat both the same for the involved math.
|
|
|
|
if r.opt.Ascending {
|
|
|
|
r.window.start, r.window.end = r.opt.Window(p.Time)
|
|
|
|
} else {
|
|
|
|
r.window.end, r.window.start = r.opt.Window(p.Time)
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we see the minimum allowable time, set the time to zero so we don't
|
|
|
|
// break the default returned time for aggregate queries without times.
|
2017-08-15 19:24:22 +00:00
|
|
|
if r.window.start == influxql.MinTime {
|
2017-03-23 20:18:03 +00:00
|
|
|
r.window.start = 0
|
|
|
|
}
|
2016-11-06 12:54:26 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// If this point has the same timestamp as the previous one,
|
2017-03-23 20:18:03 +00:00
|
|
|
// skip the point. Points sent into this reducer are expected
|
|
|
|
// to be fed in order.
|
|
|
|
value := float64(p.Value)
|
2016-11-06 12:54:26 +00:00
|
|
|
if r.prev.Time == p.Time {
|
|
|
|
r.prev = *p
|
|
|
|
return
|
2017-03-23 20:18:03 +00:00
|
|
|
} else if (r.opt.Ascending && p.Time >= r.window.end) || (!r.opt.Ascending && p.Time <= r.window.end) {
|
|
|
|
// If our previous time is not equal to the window, we need to
|
|
|
|
// interpolate the area at the end of this interval.
|
|
|
|
if r.prev.Time != r.window.end {
|
|
|
|
value = linearFloat(r.window.end, r.prev.Time, p.Time, float64(r.prev.Value), value)
|
|
|
|
elapsed := float64(r.window.end-r.prev.Time) / float64(r.interval.Duration)
|
|
|
|
r.sum += 0.5 * (value + float64(r.prev.Value)) * elapsed
|
|
|
|
|
|
|
|
r.prev.Time = r.window.end
|
|
|
|
}
|
|
|
|
|
|
|
|
// Emit the current point through the channel and then clear it.
|
|
|
|
r.ch <- FloatPoint{Time: r.window.start, Value: r.sum}
|
|
|
|
if r.opt.Ascending {
|
|
|
|
r.window.start, r.window.end = r.opt.Window(p.Time)
|
|
|
|
} else {
|
|
|
|
r.window.end, r.window.start = r.opt.Window(p.Time)
|
|
|
|
}
|
|
|
|
r.sum = 0.0
|
2016-11-06 12:54:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Normal operation: update the sum using the trapezium rule
|
|
|
|
elapsed := float64(p.Time-r.prev.Time) / float64(r.interval.Duration)
|
2017-03-23 20:18:03 +00:00
|
|
|
r.sum += 0.5 * (value + float64(r.prev.Value)) * elapsed
|
2016-11-06 12:54:26 +00:00
|
|
|
r.prev = *p
|
|
|
|
}
|
|
|
|
|
|
|
|
// Emit emits the time-integral of the aggregated points as a single FLOAT point
|
|
|
|
// InfluxQL convention dictates that outside a group-by-time clause we return
|
|
|
|
// a timestamp of zero. Within a group-by-time, we can set the time to ZeroTime
|
|
|
|
// and a higher level will change it to the start of the time group.
|
|
|
|
func (r *IntegerIntegralReducer) Emit() []FloatPoint {
|
2017-03-23 20:18:03 +00:00
|
|
|
select {
|
|
|
|
case pt, ok := <-r.ch:
|
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return []FloatPoint{pt}
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close flushes any in progress points to ensure any remaining points are
|
|
|
|
// emitted.
|
|
|
|
func (r *IntegerIntegralReducer) Close() error {
|
|
|
|
// If our last point is at the start time, then discard this point since
|
|
|
|
// there is no area within this bucket. Otherwise, send off what we
|
|
|
|
// currently have as the final point.
|
|
|
|
if !r.prev.Nil && r.prev.Time != r.window.start {
|
|
|
|
r.ch <- FloatPoint{Time: r.window.start, Value: r.sum}
|
|
|
|
}
|
|
|
|
close(r.ch)
|
|
|
|
return nil
|
2016-11-06 12:54:26 +00:00
|
|
|
}
|
Optimize top() and bottom() using an incremental aggregator
The previous version of `top()` and `bottom()` would gather all of the
points to use in a slice, filter them (if necessary), then use a
slightly modified heap sort to retrieve the top or bottom values.
This performed horrendously from the standpoint of memory. Since it
consumed so much memory and spent so much time in allocations (along
with sorting a potentially very large slice), this affected speed too.
These calls have now been modified so they keep the top or bottom points
in a min or max heap. For `top()`, a new point will read the minimum
value from the heap. If the new point is greater than the minimum point,
it will replace the minimum point and fix the heap with the new value.
If the new point is smaller, it discards that point. For `bottom()`, the
process is the opposite.
It will then sort the final result to ensure the correct ordering of the
selected points.
When `top()` or `bottom()` contain a tag to select, they have now been
modified so this query:
SELECT top(value, host, 2) FROM cpu
Essentially becomes this query:
SELECT top(value, 2), host FROM (
SELECT max(value) FROM cpu GROUP BY host
)
This should drastically increase the performance of all `top()` and
`bottom()` queries.
2017-05-16 17:37:39 +00:00
|
|
|
|
2017-09-18 16:28:37 +00:00
|
|
|
// IntegerIntegralReducer calculates the time-integral of the aggregated points.
|
|
|
|
type UnsignedIntegralReducer struct {
|
|
|
|
interval Interval
|
|
|
|
sum float64
|
|
|
|
prev UnsignedPoint
|
|
|
|
window struct {
|
|
|
|
start int64
|
|
|
|
end int64
|
|
|
|
}
|
|
|
|
ch chan FloatPoint
|
|
|
|
opt IteratorOptions
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewUnsignedIntegralReducer creates a new UnsignedIntegralReducer.
|
|
|
|
func NewUnsignedIntegralReducer(interval Interval, opt IteratorOptions) *UnsignedIntegralReducer {
|
|
|
|
return &UnsignedIntegralReducer{
|
|
|
|
interval: interval,
|
|
|
|
prev: UnsignedPoint{Nil: true},
|
|
|
|
ch: make(chan FloatPoint, 1),
|
|
|
|
opt: opt,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AggregateUnsigned aggregates a point into the reducer.
|
|
|
|
func (r *UnsignedIntegralReducer) AggregateUnsigned(p *UnsignedPoint) {
|
|
|
|
// If this is the first point, just save it
|
|
|
|
if r.prev.Nil {
|
|
|
|
r.prev = *p
|
|
|
|
|
|
|
|
// Record the end of the time interval.
|
|
|
|
// We do not care for whether the last number is inclusive or exclusive
|
|
|
|
// because we treat both the same for the involved math.
|
|
|
|
if r.opt.Ascending {
|
|
|
|
r.window.start, r.window.end = r.opt.Window(p.Time)
|
|
|
|
} else {
|
|
|
|
r.window.end, r.window.start = r.opt.Window(p.Time)
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we see the minimum allowable time, set the time to zero so we don't
|
|
|
|
// break the default returned time for aggregate queries without times.
|
|
|
|
if r.window.start == influxql.MinTime {
|
|
|
|
r.window.start = 0
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// If this point has the same timestamp as the previous one,
|
|
|
|
// skip the point. Points sent into this reducer are expected
|
|
|
|
// to be fed in order.
|
|
|
|
value := float64(p.Value)
|
|
|
|
if r.prev.Time == p.Time {
|
|
|
|
r.prev = *p
|
|
|
|
return
|
|
|
|
} else if (r.opt.Ascending && p.Time >= r.window.end) || (!r.opt.Ascending && p.Time <= r.window.end) {
|
|
|
|
// If our previous time is not equal to the window, we need to
|
|
|
|
// interpolate the area at the end of this interval.
|
|
|
|
if r.prev.Time != r.window.end {
|
|
|
|
value = linearFloat(r.window.end, r.prev.Time, p.Time, float64(r.prev.Value), value)
|
|
|
|
elapsed := float64(r.window.end-r.prev.Time) / float64(r.interval.Duration)
|
|
|
|
r.sum += 0.5 * (value + float64(r.prev.Value)) * elapsed
|
|
|
|
|
|
|
|
r.prev.Time = r.window.end
|
|
|
|
}
|
|
|
|
|
|
|
|
// Emit the current point through the channel and then clear it.
|
|
|
|
r.ch <- FloatPoint{Time: r.window.start, Value: r.sum}
|
|
|
|
if r.opt.Ascending {
|
|
|
|
r.window.start, r.window.end = r.opt.Window(p.Time)
|
|
|
|
} else {
|
|
|
|
r.window.end, r.window.start = r.opt.Window(p.Time)
|
|
|
|
}
|
|
|
|
r.sum = 0.0
|
|
|
|
}
|
|
|
|
|
|
|
|
// Normal operation: update the sum using the trapezium rule
|
|
|
|
elapsed := float64(p.Time-r.prev.Time) / float64(r.interval.Duration)
|
|
|
|
r.sum += 0.5 * (value + float64(r.prev.Value)) * elapsed
|
|
|
|
r.prev = *p
|
|
|
|
}
|
|
|
|
|
|
|
|
// Emit emits the time-integral of the aggregated points as a single FLOAT point
|
|
|
|
// InfluxQL convention dictates that outside a group-by-time clause we return
|
|
|
|
// a timestamp of zero. Within a group-by-time, we can set the time to ZeroTime
|
|
|
|
// and a higher level will change it to the start of the time group.
|
|
|
|
func (r *UnsignedIntegralReducer) Emit() []FloatPoint {
|
|
|
|
select {
|
|
|
|
case pt, ok := <-r.ch:
|
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return []FloatPoint{pt}
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close flushes any in progress points to ensure any remaining points are
|
|
|
|
// emitted.
|
|
|
|
func (r *UnsignedIntegralReducer) Close() error {
|
|
|
|
// If our last point is at the start time, then discard this point since
|
|
|
|
// there is no area within this bucket. Otherwise, send off what we
|
|
|
|
// currently have as the final point.
|
|
|
|
if !r.prev.Nil && r.prev.Time != r.window.start {
|
|
|
|
r.ch <- FloatPoint{Time: r.window.start, Value: r.sum}
|
|
|
|
}
|
|
|
|
close(r.ch)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
Optimize top() and bottom() using an incremental aggregator
The previous version of `top()` and `bottom()` would gather all of the
points to use in a slice, filter them (if necessary), then use a
slightly modified heap sort to retrieve the top or bottom values.
This performed horrendously from the standpoint of memory. Since it
consumed so much memory and spent so much time in allocations (along
with sorting a potentially very large slice), this affected speed too.
These calls have now been modified so they keep the top or bottom points
in a min or max heap. For `top()`, a new point will read the minimum
value from the heap. If the new point is greater than the minimum point,
it will replace the minimum point and fix the heap with the new value.
If the new point is smaller, it discards that point. For `bottom()`, the
process is the opposite.
It will then sort the final result to ensure the correct ordering of the
selected points.
When `top()` or `bottom()` contain a tag to select, they have now been
modified so this query:
SELECT top(value, host, 2) FROM cpu
Essentially becomes this query:
SELECT top(value, 2), host FROM (
SELECT max(value) FROM cpu GROUP BY host
)
This should drastically increase the performance of all `top()` and
`bottom()` queries.
2017-05-16 17:37:39 +00:00
|
|
|
type FloatTopReducer struct {
|
|
|
|
h *floatPointsByFunc
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewFloatTopReducer(n int) *FloatTopReducer {
|
|
|
|
return &FloatTopReducer{
|
|
|
|
h: floatPointsSortBy(make([]FloatPoint, 0, n), func(a, b *FloatPoint) bool {
|
|
|
|
if a.Value != b.Value {
|
|
|
|
return a.Value < b.Value
|
|
|
|
}
|
|
|
|
return a.Time > b.Time
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *FloatTopReducer) AggregateFloat(p *FloatPoint) {
|
|
|
|
if r.h.Len() == cap(r.h.points) {
|
|
|
|
// Compare the minimum point and the aggregated point. If our value is
|
|
|
|
// larger, replace the current min value.
|
|
|
|
if !r.h.cmp(&r.h.points[0], p) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
r.h.points[0] = *p
|
|
|
|
heap.Fix(r.h, 0)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
heap.Push(r.h, *p)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *FloatTopReducer) Emit() []FloatPoint {
|
|
|
|
// Ensure the points are sorted with the maximum value last. While the
|
|
|
|
// first point may be the minimum value, the rest is not guaranteed to be
|
|
|
|
// in any particular order while it is a heap.
|
|
|
|
points := make([]FloatPoint, len(r.h.points))
|
|
|
|
for i, p := range r.h.points {
|
|
|
|
p.Aggregated = 0
|
|
|
|
points[i] = p
|
|
|
|
}
|
|
|
|
h := floatPointsByFunc{points: points, cmp: r.h.cmp}
|
|
|
|
sort.Sort(sort.Reverse(&h))
|
|
|
|
return points
|
|
|
|
}
|
|
|
|
|
|
|
|
type IntegerTopReducer struct {
|
|
|
|
h *integerPointsByFunc
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewIntegerTopReducer(n int) *IntegerTopReducer {
|
|
|
|
return &IntegerTopReducer{
|
|
|
|
h: integerPointsSortBy(make([]IntegerPoint, 0, n), func(a, b *IntegerPoint) bool {
|
|
|
|
if a.Value != b.Value {
|
|
|
|
return a.Value < b.Value
|
|
|
|
}
|
|
|
|
return a.Time > b.Time
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *IntegerTopReducer) AggregateInteger(p *IntegerPoint) {
|
|
|
|
if r.h.Len() == cap(r.h.points) {
|
|
|
|
// Compare the minimum point and the aggregated point. If our value is
|
|
|
|
// larger, replace the current min value.
|
|
|
|
if !r.h.cmp(&r.h.points[0], p) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
r.h.points[0] = *p
|
|
|
|
heap.Fix(r.h, 0)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
heap.Push(r.h, *p)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *IntegerTopReducer) Emit() []IntegerPoint {
|
|
|
|
// Ensure the points are sorted with the maximum value last. While the
|
|
|
|
// first point may be the minimum value, the rest is not guaranteed to be
|
|
|
|
// in any particular order while it is a heap.
|
|
|
|
points := make([]IntegerPoint, len(r.h.points))
|
|
|
|
for i, p := range r.h.points {
|
|
|
|
p.Aggregated = 0
|
|
|
|
points[i] = p
|
|
|
|
}
|
|
|
|
h := integerPointsByFunc{points: points, cmp: r.h.cmp}
|
|
|
|
sort.Sort(sort.Reverse(&h))
|
|
|
|
return points
|
|
|
|
}
|
|
|
|
|
2017-09-18 16:28:37 +00:00
|
|
|
type UnsignedTopReducer struct {
|
|
|
|
h *unsignedPointsByFunc
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewUnsignedTopReducer(n int) *UnsignedTopReducer {
|
|
|
|
return &UnsignedTopReducer{
|
|
|
|
h: unsignedPointsSortBy(make([]UnsignedPoint, 0, n), func(a, b *UnsignedPoint) bool {
|
|
|
|
if a.Value != b.Value {
|
|
|
|
return a.Value < b.Value
|
|
|
|
}
|
|
|
|
return a.Time > b.Time
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *UnsignedTopReducer) AggregateUnsigned(p *UnsignedPoint) {
|
|
|
|
if r.h.Len() == cap(r.h.points) {
|
|
|
|
// Compare the minimum point and the aggregated point. If our value is
|
|
|
|
// larger, replace the current min value.
|
|
|
|
if !r.h.cmp(&r.h.points[0], p) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
r.h.points[0] = *p
|
|
|
|
heap.Fix(r.h, 0)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
heap.Push(r.h, *p)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *UnsignedTopReducer) Emit() []UnsignedPoint {
|
|
|
|
// Ensure the points are sorted with the maximum value last. While the
|
|
|
|
// first point may be the minimum value, the rest is not guaranteed to be
|
|
|
|
// in any particular order while it is a heap.
|
|
|
|
points := make([]UnsignedPoint, len(r.h.points))
|
|
|
|
for i, p := range r.h.points {
|
|
|
|
p.Aggregated = 0
|
|
|
|
points[i] = p
|
|
|
|
}
|
|
|
|
h := unsignedPointsByFunc{points: points, cmp: r.h.cmp}
|
|
|
|
sort.Sort(sort.Reverse(&h))
|
|
|
|
return points
|
|
|
|
}
|
|
|
|
|
Optimize top() and bottom() using an incremental aggregator
The previous version of `top()` and `bottom()` would gather all of the
points to use in a slice, filter them (if necessary), then use a
slightly modified heap sort to retrieve the top or bottom values.
This performed horrendously from the standpoint of memory. Since it
consumed so much memory and spent so much time in allocations (along
with sorting a potentially very large slice), this affected speed too.
These calls have now been modified so they keep the top or bottom points
in a min or max heap. For `top()`, a new point will read the minimum
value from the heap. If the new point is greater than the minimum point,
it will replace the minimum point and fix the heap with the new value.
If the new point is smaller, it discards that point. For `bottom()`, the
process is the opposite.
It will then sort the final result to ensure the correct ordering of the
selected points.
When `top()` or `bottom()` contain a tag to select, they have now been
modified so this query:
SELECT top(value, host, 2) FROM cpu
Essentially becomes this query:
SELECT top(value, 2), host FROM (
SELECT max(value) FROM cpu GROUP BY host
)
This should drastically increase the performance of all `top()` and
`bottom()` queries.
2017-05-16 17:37:39 +00:00
|
|
|
type FloatBottomReducer struct {
|
|
|
|
h *floatPointsByFunc
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewFloatBottomReducer(n int) *FloatBottomReducer {
|
|
|
|
return &FloatBottomReducer{
|
|
|
|
h: floatPointsSortBy(make([]FloatPoint, 0, n), func(a, b *FloatPoint) bool {
|
|
|
|
if a.Value != b.Value {
|
|
|
|
return a.Value > b.Value
|
|
|
|
}
|
|
|
|
return a.Time > b.Time
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *FloatBottomReducer) AggregateFloat(p *FloatPoint) {
|
|
|
|
if r.h.Len() == cap(r.h.points) {
|
|
|
|
// Compare the minimum point and the aggregated point. If our value is
|
|
|
|
// larger, replace the current min value.
|
|
|
|
if !r.h.cmp(&r.h.points[0], p) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
r.h.points[0] = *p
|
|
|
|
heap.Fix(r.h, 0)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
heap.Push(r.h, *p)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *FloatBottomReducer) Emit() []FloatPoint {
|
|
|
|
// Ensure the points are sorted with the maximum value last. While the
|
|
|
|
// first point may be the minimum value, the rest is not guaranteed to be
|
|
|
|
// in any particular order while it is a heap.
|
|
|
|
points := make([]FloatPoint, len(r.h.points))
|
|
|
|
for i, p := range r.h.points {
|
|
|
|
p.Aggregated = 0
|
|
|
|
points[i] = p
|
|
|
|
}
|
|
|
|
h := floatPointsByFunc{points: points, cmp: r.h.cmp}
|
|
|
|
sort.Sort(sort.Reverse(&h))
|
|
|
|
return points
|
|
|
|
}
|
|
|
|
|
|
|
|
type IntegerBottomReducer struct {
|
|
|
|
h *integerPointsByFunc
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewIntegerBottomReducer(n int) *IntegerBottomReducer {
|
|
|
|
return &IntegerBottomReducer{
|
|
|
|
h: integerPointsSortBy(make([]IntegerPoint, 0, n), func(a, b *IntegerPoint) bool {
|
|
|
|
if a.Value != b.Value {
|
|
|
|
return a.Value > b.Value
|
|
|
|
}
|
|
|
|
return a.Time > b.Time
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *IntegerBottomReducer) AggregateInteger(p *IntegerPoint) {
|
|
|
|
if r.h.Len() == cap(r.h.points) {
|
|
|
|
// Compare the minimum point and the aggregated point. If our value is
|
|
|
|
// larger, replace the current min value.
|
|
|
|
if !r.h.cmp(&r.h.points[0], p) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
r.h.points[0] = *p
|
|
|
|
heap.Fix(r.h, 0)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
heap.Push(r.h, *p)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *IntegerBottomReducer) Emit() []IntegerPoint {
|
|
|
|
// Ensure the points are sorted with the maximum value last. While the
|
|
|
|
// first point may be the minimum value, the rest is not guaranteed to be
|
|
|
|
// in any particular order while it is a heap.
|
|
|
|
points := make([]IntegerPoint, len(r.h.points))
|
|
|
|
for i, p := range r.h.points {
|
|
|
|
p.Aggregated = 0
|
|
|
|
points[i] = p
|
|
|
|
}
|
|
|
|
h := integerPointsByFunc{points: points, cmp: r.h.cmp}
|
|
|
|
sort.Sort(sort.Reverse(&h))
|
|
|
|
return points
|
|
|
|
}
|
2017-09-18 16:28:37 +00:00
|
|
|
|
|
|
|
type UnsignedBottomReducer struct {
|
|
|
|
h *unsignedPointsByFunc
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewUnsignedBottomReducer(n int) *UnsignedBottomReducer {
|
|
|
|
return &UnsignedBottomReducer{
|
|
|
|
h: unsignedPointsSortBy(make([]UnsignedPoint, 0, n), func(a, b *UnsignedPoint) bool {
|
|
|
|
if a.Value != b.Value {
|
|
|
|
return a.Value > b.Value
|
|
|
|
}
|
|
|
|
return a.Time > b.Time
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *UnsignedBottomReducer) AggregateUnsigned(p *UnsignedPoint) {
|
|
|
|
if r.h.Len() == cap(r.h.points) {
|
|
|
|
// Compare the minimum point and the aggregated point. If our value is
|
|
|
|
// larger, replace the current min value.
|
|
|
|
if !r.h.cmp(&r.h.points[0], p) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
r.h.points[0] = *p
|
|
|
|
heap.Fix(r.h, 0)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
heap.Push(r.h, *p)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *UnsignedBottomReducer) Emit() []UnsignedPoint {
|
|
|
|
// Ensure the points are sorted with the maximum value last. While the
|
|
|
|
// first point may be the minimum value, the rest is not guaranteed to be
|
|
|
|
// in any particular order while it is a heap.
|
|
|
|
points := make([]UnsignedPoint, len(r.h.points))
|
|
|
|
for i, p := range r.h.points {
|
|
|
|
p.Aggregated = 0
|
|
|
|
points[i] = p
|
|
|
|
}
|
|
|
|
h := unsignedPointsByFunc{points: points, cmp: r.h.cmp}
|
|
|
|
sort.Sort(sort.Reverse(&h))
|
|
|
|
return points
|
|
|
|
}
|