|
|
|
@ -15,10 +15,11 @@ package metric
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"github.com/prometheus/prometheus/model"
|
|
|
|
|
"math"
|
|
|
|
|
"sort"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/prometheus/prometheus/model"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Encapsulates a primitive query operation.
|
|
|
|
@ -65,11 +66,11 @@ type getValuesAtTimeOp struct {
|
|
|
|
|
consumed bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (o getValuesAtTimeOp) String() string {
|
|
|
|
|
return fmt.Sprintf("getValuesAtTimeOp at %s", o.time)
|
|
|
|
|
func (g *getValuesAtTimeOp) String() string {
|
|
|
|
|
return fmt.Sprintf("getValuesAtTimeOp at %s", g.time)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (g getValuesAtTimeOp) StartsAt() time.Time {
|
|
|
|
|
func (g *getValuesAtTimeOp) StartsAt() time.Time {
|
|
|
|
|
return g.time
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -82,7 +83,7 @@ func (g *getValuesAtTimeOp) ExtractSamples(in model.Values) (out model.Values) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (g getValuesAtTimeOp) GreedierThan(op op) (superior bool) {
|
|
|
|
|
func (g *getValuesAtTimeOp) GreedierThan(op op) (superior bool) {
|
|
|
|
|
switch op.(type) {
|
|
|
|
|
case *getValuesAtTimeOp:
|
|
|
|
|
superior = true
|
|
|
|
@ -139,15 +140,15 @@ type getValuesAtIntervalOp struct {
|
|
|
|
|
interval time.Duration
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (o getValuesAtIntervalOp) String() string {
|
|
|
|
|
func (o *getValuesAtIntervalOp) String() string {
|
|
|
|
|
return fmt.Sprintf("getValuesAtIntervalOp from %s each %s through %s", o.from, o.interval, o.through)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (g getValuesAtIntervalOp) StartsAt() time.Time {
|
|
|
|
|
func (g *getValuesAtIntervalOp) StartsAt() time.Time {
|
|
|
|
|
return g.from
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (g getValuesAtIntervalOp) Through() time.Time {
|
|
|
|
|
func (g *getValuesAtIntervalOp) Through() time.Time {
|
|
|
|
|
return g.through
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -174,14 +175,14 @@ func (g *getValuesAtIntervalOp) ExtractSamples(in model.Values) (out model.Value
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (g getValuesAtIntervalOp) CurrentTime() (currentTime *time.Time) {
|
|
|
|
|
func (g *getValuesAtIntervalOp) CurrentTime() (currentTime *time.Time) {
|
|
|
|
|
if g.from.After(g.through) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
return &g.from
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (g getValuesAtIntervalOp) GreedierThan(op op) (superior bool) {
|
|
|
|
|
func (g *getValuesAtIntervalOp) GreedierThan(op op) (superior bool) {
|
|
|
|
|
switch o := op.(type) {
|
|
|
|
|
case *getValuesAtTimeOp:
|
|
|
|
|
superior = true
|
|
|
|
@ -199,15 +200,15 @@ type getValuesAlongRangeOp struct {
|
|
|
|
|
through time.Time
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (o getValuesAlongRangeOp) String() string {
|
|
|
|
|
func (o *getValuesAlongRangeOp) String() string {
|
|
|
|
|
return fmt.Sprintf("getValuesAlongRangeOp from %s through %s", o.from, o.through)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (g getValuesAlongRangeOp) StartsAt() time.Time {
|
|
|
|
|
func (g *getValuesAlongRangeOp) StartsAt() time.Time {
|
|
|
|
|
return g.from
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (g getValuesAlongRangeOp) Through() time.Time {
|
|
|
|
|
func (g *getValuesAlongRangeOp) Through() time.Time {
|
|
|
|
|
return g.through
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -244,14 +245,14 @@ func (g *getValuesAlongRangeOp) ExtractSamples(in model.Values) (out model.Value
|
|
|
|
|
return in[firstIdx:lastIdx]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (g getValuesAlongRangeOp) CurrentTime() (currentTime *time.Time) {
|
|
|
|
|
func (g *getValuesAlongRangeOp) CurrentTime() (currentTime *time.Time) {
|
|
|
|
|
if g.from.After(g.through) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
return &g.from
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (g getValuesAlongRangeOp) GreedierThan(op op) (superior bool) {
|
|
|
|
|
func (g *getValuesAlongRangeOp) GreedierThan(op op) (superior bool) {
|
|
|
|
|
switch o := op.(type) {
|
|
|
|
|
case *getValuesAtTimeOp:
|
|
|
|
|
superior = true
|
|
|
|
@ -376,109 +377,106 @@ func collectRanges(ops ops) (ranges ops) {
|
|
|
|
|
// simplification. For instance, if a range query happens to overlap a get-a-
|
|
|
|
|
// value-at-a-certain-point-request, the range query should flatten and subsume
|
|
|
|
|
// the other.
|
|
|
|
|
func optimizeForward(pending ops) (out ops) {
|
|
|
|
|
if len(pending) == 0 {
|
|
|
|
|
return
|
|
|
|
|
func optimizeForward(unoptimized ops) ops {
|
|
|
|
|
if len(unoptimized) <= 1 {
|
|
|
|
|
return unoptimized
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
head op = pending[0]
|
|
|
|
|
tail ops
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
pending = pending[1:]
|
|
|
|
|
head := unoptimized[0]
|
|
|
|
|
unoptimized = unoptimized[1:]
|
|
|
|
|
optimized := ops{}
|
|
|
|
|
|
|
|
|
|
switch t := head.(type) {
|
|
|
|
|
switch headOp := head.(type) {
|
|
|
|
|
case *getValuesAtTimeOp:
|
|
|
|
|
out = ops{head}
|
|
|
|
|
optimized = ops{head}
|
|
|
|
|
case *getValuesAtIntervalOp:
|
|
|
|
|
// If the last value was a scan at a given frequency along an interval,
|
|
|
|
|
// several optimizations may exist.
|
|
|
|
|
for _, peekOperation := range pending {
|
|
|
|
|
if peekOperation.StartsAt().After(t.Through()) {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
optimized, unoptimized = optimizeForwardGetValuesAtInterval(headOp, unoptimized)
|
|
|
|
|
case *getValuesAlongRangeOp:
|
|
|
|
|
optimized, unoptimized = optimizeForwardGetValuesAlongRange(headOp, unoptimized)
|
|
|
|
|
default:
|
|
|
|
|
panic("unknown operation type")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the type is not a range request, we can't do anything.
|
|
|
|
|
switch next := peekOperation.(type) {
|
|
|
|
|
case *getValuesAlongRangeOp:
|
|
|
|
|
if !next.GreedierThan(t) {
|
|
|
|
|
var (
|
|
|
|
|
before = getValuesAtIntervalOp(*t)
|
|
|
|
|
after = getValuesAtIntervalOp(*t)
|
|
|
|
|
)
|
|
|
|
|
tail := optimizeForward(unoptimized)
|
|
|
|
|
|
|
|
|
|
before.through = next.from
|
|
|
|
|
return append(optimized, tail...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Truncate the get value at interval request if a range request cuts
|
|
|
|
|
// it off somewhere.
|
|
|
|
|
var (
|
|
|
|
|
from = next.from
|
|
|
|
|
)
|
|
|
|
|
func optimizeForwardGetValuesAtInterval(headOp *getValuesAtIntervalOp, unoptimized ops) (ops, ops) {
|
|
|
|
|
// If the last value was a scan at a given frequency along an interval,
|
|
|
|
|
// several optimizations may exist.
|
|
|
|
|
for _, peekOperation := range unoptimized {
|
|
|
|
|
if peekOperation.StartsAt().After(headOp.Through()) {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for !from.After(next.through) {
|
|
|
|
|
from = from.Add(t.interval)
|
|
|
|
|
}
|
|
|
|
|
// If the type is not a range request, we can't do anything.
|
|
|
|
|
switch next := peekOperation.(type) {
|
|
|
|
|
case *getValuesAlongRangeOp:
|
|
|
|
|
if !next.GreedierThan(headOp) {
|
|
|
|
|
after := getValuesAtIntervalOp(*headOp)
|
|
|
|
|
|
|
|
|
|
after.from = from
|
|
|
|
|
headOp.through = next.from
|
|
|
|
|
|
|
|
|
|
pending = append(ops{&before, &after}, pending...)
|
|
|
|
|
sort.Sort(startsAtSort{pending})
|
|
|
|
|
// Truncate the get value at interval request if a range request cuts
|
|
|
|
|
// it off somewhere.
|
|
|
|
|
from := next.from
|
|
|
|
|
|
|
|
|
|
return optimizeForward(pending)
|
|
|
|
|
for !from.After(next.through) {
|
|
|
|
|
from = from.Add(headOp.interval)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case *getValuesAlongRangeOp:
|
|
|
|
|
for _, peekOperation := range pending {
|
|
|
|
|
if peekOperation.StartsAt().After(t.Through()) {
|
|
|
|
|
break
|
|
|
|
|
after.from = from
|
|
|
|
|
|
|
|
|
|
unoptimized = append(ops{&after}, unoptimized...)
|
|
|
|
|
sort.Sort(startsAtSort{unoptimized})
|
|
|
|
|
return ops{headOp}, unoptimized
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ops{headOp}, unoptimized
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch next := peekOperation.(type) {
|
|
|
|
|
// All values at a specific time may be elided into the range query.
|
|
|
|
|
case *getValuesAtTimeOp:
|
|
|
|
|
pending = pending[1:]
|
|
|
|
|
continue
|
|
|
|
|
case *getValuesAlongRangeOp:
|
|
|
|
|
// Range queries should be concatenated if they overlap.
|
|
|
|
|
if next.GreedierThan(t) {
|
|
|
|
|
next.from = t.from
|
|
|
|
|
|
|
|
|
|
return optimizeForward(pending)
|
|
|
|
|
} else {
|
|
|
|
|
pending = pending[1:]
|
|
|
|
|
}
|
|
|
|
|
case *getValuesAtIntervalOp:
|
|
|
|
|
pending = pending[1:]
|
|
|
|
|
func optimizeForwardGetValuesAlongRange(headOp *getValuesAlongRangeOp, unoptimized ops) (ops, ops) {
|
|
|
|
|
optimized := ops{}
|
|
|
|
|
for _, peekOperation := range unoptimized {
|
|
|
|
|
if peekOperation.StartsAt().After(headOp.Through()) {
|
|
|
|
|
optimized = ops{headOp}
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if next.GreedierThan(t) {
|
|
|
|
|
var (
|
|
|
|
|
nextStart = next.from
|
|
|
|
|
)
|
|
|
|
|
switch next := peekOperation.(type) {
|
|
|
|
|
// All values at a specific time may be elided into the range query.
|
|
|
|
|
case *getValuesAtTimeOp:
|
|
|
|
|
optimized = ops{headOp}
|
|
|
|
|
unoptimized = unoptimized[1:]
|
|
|
|
|
case *getValuesAlongRangeOp:
|
|
|
|
|
// Range queries should be concatenated if they overlap.
|
|
|
|
|
if next.GreedierThan(headOp) {
|
|
|
|
|
next.from = headOp.from
|
|
|
|
|
return optimized, unoptimized
|
|
|
|
|
}
|
|
|
|
|
optimized = ops{headOp}
|
|
|
|
|
unoptimized = unoptimized[1:]
|
|
|
|
|
case *getValuesAtIntervalOp:
|
|
|
|
|
optimized = ops{headOp}
|
|
|
|
|
unoptimized = unoptimized[1:]
|
|
|
|
|
|
|
|
|
|
for !nextStart.After(next.through) {
|
|
|
|
|
nextStart = nextStart.Add(next.interval)
|
|
|
|
|
}
|
|
|
|
|
if next.GreedierThan(headOp) {
|
|
|
|
|
nextStart := next.from
|
|
|
|
|
|
|
|
|
|
next.from = nextStart
|
|
|
|
|
tail = append(ops{next}, pending...)
|
|
|
|
|
for !nextStart.After(headOp.through) {
|
|
|
|
|
nextStart = nextStart.Add(next.interval)
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
panic("unknown operation type")
|
|
|
|
|
|
|
|
|
|
next.from = nextStart
|
|
|
|
|
unoptimized = append(ops{next}, unoptimized...)
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
panic("unknown operation type")
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
panic("unknown operation type")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Strictly needed?
|
|
|
|
|
sort.Sort(startsAtSort{pending})
|
|
|
|
|
|
|
|
|
|
tail = optimizeForward(pending)
|
|
|
|
|
|
|
|
|
|
return append(ops{head}, tail...)
|
|
|
|
|
return optimized, unoptimized
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// selectQueriesForTime chooses all subsequent operations from the slice that
|
|
|
|
|