Merge pull request #306 from prometheus/refactor/optimize-ops

Speedup and clean up operation optimization.
This commit is contained in:
Matt T. Proud 2013-06-19 21:55:52 -07:00
commit de8757f925
3 changed files with 134 additions and 109 deletions

View File

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

View File

@ -14,11 +14,12 @@
package metric package metric
import ( import (
"github.com/prometheus/prometheus/model"
"github.com/prometheus/prometheus/utility/test"
"sort" "sort"
"testing" "testing"
"time" "time"
"github.com/prometheus/prometheus/model"
"github.com/prometheus/prometheus/utility/test"
) )
func testOptimizeTimeGroups(t test.Tester) { func testOptimizeTimeGroups(t test.Tester) {
@ -586,6 +587,31 @@ func testOptimizeForward(t test.Tester) {
}, },
}, },
}, },
// Range with subsequent overlapping interval.
{
in: ops{
&getValuesAlongRangeOp{
from: testInstant,
through: testInstant.Add(3 * time.Minute),
},
&getValuesAtIntervalOp{
from: testInstant.Add(1 * time.Minute),
through: testInstant.Add(4 * time.Minute),
interval: time.Second * 10,
},
},
out: ops{
&getValuesAlongRangeOp{
from: testInstant,
through: testInstant.Add(3 * time.Minute),
},
&getValuesAtIntervalOp{
from: testInstant.Add(3*time.Minute + 10*time.Second),
through: testInstant.Add(4 * time.Minute),
interval: time.Second * 10,
},
},
},
} }
) )

View File

@ -14,9 +14,10 @@
package metric package metric
import ( import (
"github.com/prometheus/prometheus/model"
"sort" "sort"
"time" "time"
"github.com/prometheus/prometheus/model"
) )
var ( var (