347 lines
9.8 KiB
Go
347 lines
9.8 KiB
Go
// Copyright 2013 Prometheus Team
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package metric
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"time"
|
|
|
|
clientmodel "github.com/prometheus/client_golang/model"
|
|
)
|
|
|
|
// op encapsulates a primitive query operation.
|
|
type op interface {
|
|
// Fingerprint returns the fingerprint of the metric this operation
|
|
// operates on.
|
|
Fingerprint() *clientmodel.Fingerprint
|
|
// ExtractSamples extracts samples from a stream of values and advances
|
|
// the operation time.
|
|
ExtractSamples(Values) Values
|
|
// Consumed returns whether the operator has consumed all data it needs.
|
|
Consumed() bool
|
|
// CurrentTime gets the current operation time. In a newly created op,
|
|
// this is the starting time of the operation. During ongoing execution
|
|
// of the op, the current time is advanced accordingly. Once no
|
|
// subsequent work associated with the operation remains, nil is
|
|
// returned.
|
|
CurrentTime() clientmodel.Timestamp
|
|
}
|
|
|
|
// durationOperator encapsulates a general operation that occurs over a
|
|
// duration.
|
|
type durationOperator interface {
|
|
op
|
|
Through() clientmodel.Timestamp
|
|
}
|
|
|
|
// ops is a heap of operations, primary sorting key is the fingerprint.
|
|
type ops []op
|
|
|
|
// Len implements sort.Interface and heap.Interface.
|
|
func (o ops) Len() int {
|
|
return len(o)
|
|
}
|
|
|
|
// Less implements sort.Interface and heap.Interface. It compares the
|
|
// fingerprints. If they are equal, the comparison is delegated to
|
|
// currentTimeSort.
|
|
func (o ops) Less(i, j int) bool {
|
|
fpi := o[i].Fingerprint()
|
|
fpj := o[j].Fingerprint()
|
|
if fpi.Equal(fpj) {
|
|
return currentTimeSort{o}.Less(i, j)
|
|
}
|
|
return fpi.Less(fpj)
|
|
}
|
|
|
|
// Swap implements sort.Interface and heap.Interface.
|
|
func (o ops) Swap(i, j int) {
|
|
o[i], o[j] = o[j], o[i]
|
|
}
|
|
|
|
// Push implements heap.Interface.
|
|
func (o *ops) Push(x interface{}) {
|
|
// Push and Pop use pointer receivers because they modify the slice's
|
|
// length, not just its contents.
|
|
*o = append(*o, x.(op))
|
|
}
|
|
|
|
// Push implements heap.Interface.
|
|
func (o *ops) Pop() interface{} {
|
|
old := *o
|
|
n := len(old)
|
|
x := old[n-1]
|
|
*o = old[0 : n-1]
|
|
return x
|
|
}
|
|
|
|
// currentTimeSort is a wrapper for ops with customized sorting order.
|
|
type currentTimeSort struct {
|
|
ops
|
|
}
|
|
|
|
// currentTimeSort implements sort.Interface and sorts the operations in
|
|
// chronological order by their current time.
|
|
func (s currentTimeSort) Less(i, j int) bool {
|
|
return s.ops[i].CurrentTime().Before(s.ops[j].CurrentTime())
|
|
}
|
|
|
|
// baseOp contains the implementations and fields shared between different op
|
|
// types.
|
|
type baseOp struct {
|
|
fp clientmodel.Fingerprint
|
|
current clientmodel.Timestamp
|
|
}
|
|
|
|
func (g *baseOp) Fingerprint() *clientmodel.Fingerprint {
|
|
return &g.fp
|
|
}
|
|
|
|
func (g *baseOp) CurrentTime() clientmodel.Timestamp {
|
|
return g.current
|
|
}
|
|
|
|
// getValuesAtTimeOp encapsulates getting values at or adjacent to a specific
|
|
// time.
|
|
type getValuesAtTimeOp struct {
|
|
baseOp
|
|
consumed bool
|
|
}
|
|
|
|
func (g *getValuesAtTimeOp) String() string {
|
|
return fmt.Sprintf("getValuesAtTimeOp at %s", g.current)
|
|
}
|
|
|
|
func (g *getValuesAtTimeOp) ExtractSamples(in Values) (out Values) {
|
|
if len(in) == 0 {
|
|
return
|
|
}
|
|
out = extractValuesAroundTime(g.current, in)
|
|
g.consumed = true
|
|
return
|
|
}
|
|
|
|
func (g getValuesAtTimeOp) Consumed() bool {
|
|
return g.consumed
|
|
}
|
|
|
|
// getValuesAtIntervalOp encapsulates getting values at a given interval over a
|
|
// duration.
|
|
type getValuesAtIntervalOp struct {
|
|
baseOp
|
|
through clientmodel.Timestamp
|
|
interval time.Duration
|
|
}
|
|
|
|
func (g *getValuesAtIntervalOp) String() string {
|
|
return fmt.Sprintf("getValuesAtIntervalOp from %s each %s through %s", g.current, g.interval, g.through)
|
|
}
|
|
|
|
func (g *getValuesAtIntervalOp) Through() clientmodel.Timestamp {
|
|
return g.through
|
|
}
|
|
|
|
func (g *getValuesAtIntervalOp) ExtractSamples(in Values) (out Values) {
|
|
if len(in) == 0 {
|
|
return
|
|
}
|
|
lastChunkTime := in[len(in)-1].Timestamp
|
|
for len(in) > 0 {
|
|
out = append(out, extractValuesAroundTime(g.current, in)...)
|
|
lastExtractedTime := out[len(out)-1].Timestamp
|
|
in = in.TruncateBefore(lastExtractedTime.Add(
|
|
clientmodel.MinimumTick))
|
|
g.current = g.current.Add(g.interval)
|
|
for !g.current.After(lastExtractedTime) {
|
|
g.current = g.current.Add(g.interval)
|
|
}
|
|
if lastExtractedTime.Equal(lastChunkTime) {
|
|
break
|
|
}
|
|
if g.current.After(g.through) {
|
|
break
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (g *getValuesAtIntervalOp) Consumed() bool {
|
|
return g.current.After(g.through)
|
|
}
|
|
|
|
// getValuesAlongRangeOp encapsulates getting all values in a given range.
|
|
type getValuesAlongRangeOp struct {
|
|
baseOp
|
|
through clientmodel.Timestamp
|
|
}
|
|
|
|
func (g *getValuesAlongRangeOp) String() string {
|
|
return fmt.Sprintf("getValuesAlongRangeOp from %s through %s", g.current, g.through)
|
|
}
|
|
|
|
func (g *getValuesAlongRangeOp) Through() clientmodel.Timestamp {
|
|
return g.through
|
|
}
|
|
|
|
func (g *getValuesAlongRangeOp) ExtractSamples(in Values) (out Values) {
|
|
if len(in) == 0 {
|
|
return
|
|
}
|
|
// Find the first sample where time >= g.current.
|
|
firstIdx := sort.Search(len(in), func(i int) bool {
|
|
return !in[i].Timestamp.Before(g.current)
|
|
})
|
|
if firstIdx == len(in) {
|
|
// No samples at or after operator start time. This can only
|
|
// happen if we try applying the operator to a time after the
|
|
// last recorded sample. In this case, we're finished.
|
|
g.current = g.through.Add(clientmodel.MinimumTick)
|
|
return
|
|
}
|
|
|
|
// Find the first sample where time > g.through.
|
|
lastIdx := sort.Search(len(in), func(i int) bool {
|
|
return in[i].Timestamp.After(g.through)
|
|
})
|
|
if lastIdx == firstIdx {
|
|
g.current = g.through.Add(clientmodel.MinimumTick)
|
|
return
|
|
}
|
|
|
|
lastSampleTime := in[lastIdx-1].Timestamp
|
|
// Sample times are stored with a maximum time resolution of one second,
|
|
// so we have to add exactly that to target the next chunk on the next
|
|
// op iteration.
|
|
g.current = lastSampleTime.Add(time.Second)
|
|
return in[firstIdx:lastIdx]
|
|
}
|
|
|
|
func (g *getValuesAlongRangeOp) Consumed() bool {
|
|
return g.current.After(g.through)
|
|
}
|
|
|
|
// getValueRangeAtIntervalOp encapsulates getting all values from ranges along
|
|
// intervals.
|
|
//
|
|
// Works just like getValuesAlongRangeOp, but when from > through, through is
|
|
// incremented by interval and from is reset to through-rangeDuration. Returns
|
|
// current time nil when from > totalThrough.
|
|
type getValueRangeAtIntervalOp struct {
|
|
baseOp
|
|
rangeThrough clientmodel.Timestamp
|
|
rangeDuration time.Duration
|
|
interval time.Duration
|
|
through clientmodel.Timestamp
|
|
}
|
|
|
|
func (g *getValueRangeAtIntervalOp) String() string {
|
|
return fmt.Sprintf("getValueRangeAtIntervalOp range %s from %s each %s through %s", g.rangeDuration, g.current, g.interval, g.through)
|
|
}
|
|
|
|
func (g *getValueRangeAtIntervalOp) Through() clientmodel.Timestamp {
|
|
panic("not implemented")
|
|
}
|
|
|
|
func (g *getValueRangeAtIntervalOp) advanceToNextInterval() {
|
|
g.rangeThrough = g.rangeThrough.Add(g.interval)
|
|
g.current = g.rangeThrough.Add(-g.rangeDuration)
|
|
}
|
|
|
|
func (g *getValueRangeAtIntervalOp) ExtractSamples(in Values) (out Values) {
|
|
if len(in) == 0 {
|
|
return
|
|
}
|
|
// Find the first sample where time >= g.current.
|
|
firstIdx := sort.Search(len(in), func(i int) bool {
|
|
return !in[i].Timestamp.Before(g.current)
|
|
})
|
|
if firstIdx == len(in) {
|
|
// No samples at or after operator start time. This can only
|
|
// happen if we try applying the operator to a time after the
|
|
// last recorded sample. In this case, we're finished.
|
|
g.current = g.through.Add(clientmodel.MinimumTick)
|
|
return
|
|
}
|
|
|
|
// Find the first sample where time > g.rangeThrough.
|
|
lastIdx := sort.Search(len(in), func(i int) bool {
|
|
return in[i].Timestamp.After(g.rangeThrough)
|
|
})
|
|
// This only happens when there is only one sample and it is both after
|
|
// g.current and after g.rangeThrough. In this case, both indexes are 0.
|
|
if lastIdx == firstIdx {
|
|
g.advanceToNextInterval()
|
|
return
|
|
}
|
|
|
|
lastSampleTime := in[lastIdx-1].Timestamp
|
|
// Sample times are stored with a maximum time resolution of one second,
|
|
// so we have to add exactly that to target the next chunk on the next
|
|
// op iteration.
|
|
g.current = lastSampleTime.Add(time.Second)
|
|
if g.current.After(g.rangeThrough) {
|
|
g.advanceToNextInterval()
|
|
}
|
|
return in[firstIdx:lastIdx]
|
|
}
|
|
|
|
func (g *getValueRangeAtIntervalOp) Consumed() bool {
|
|
return g.current.After(g.through)
|
|
}
|
|
|
|
// getValuesAtIntervalOps contains getValuesAtIntervalOp operations. It
|
|
// implements sort.Interface and sorts the operations in ascending order by
|
|
// their frequency.
|
|
type getValuesAtIntervalOps []*getValuesAtIntervalOp
|
|
|
|
func (s getValuesAtIntervalOps) Len() int {
|
|
return len(s)
|
|
}
|
|
|
|
func (s getValuesAtIntervalOps) Swap(i, j int) {
|
|
s[i], s[j] = s[j], s[i]
|
|
}
|
|
|
|
func (s getValuesAtIntervalOps) Less(i, j int) bool {
|
|
return s[i].interval < s[j].interval
|
|
}
|
|
|
|
// extractValuesAroundTime searches for the provided time in the list of
|
|
// available samples and emits a slice containing the data points that
|
|
// are adjacent to it.
|
|
//
|
|
// An assumption of this is that the provided samples are already sorted!
|
|
func extractValuesAroundTime(t clientmodel.Timestamp, in Values) Values {
|
|
i := sort.Search(len(in), func(i int) bool {
|
|
return !in[i].Timestamp.Before(t)
|
|
})
|
|
if i == len(in) {
|
|
// Target time is past the end, return only the last sample.
|
|
return in[len(in)-1:]
|
|
}
|
|
if in[i].Timestamp.Equal(t) && len(in) > i+1 {
|
|
// We hit exactly the current sample time. Very unlikely in
|
|
// practice. Return only the current sample.
|
|
return in[i : i+1]
|
|
}
|
|
if i == 0 {
|
|
// We hit before the first sample time. Return only the first
|
|
// sample.
|
|
return in[0:1]
|
|
}
|
|
// We hit between two samples. Return both surrounding samples.
|
|
return in[i-1 : i+1]
|
|
}
|