// 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 ( "github.com/prometheus/prometheus/model" "github.com/ryszard/goskiplist/skiplist" "sort" "time" ) var ( // firstSupertime is the smallest valid supertime that may be seeked to. firstSupertime = []byte{0, 0, 0, 0, 0, 0, 0, 0} // lastSupertime is the largest valid supertime that may be seeked to. lastSupertime = []byte{127, 255, 255, 255, 255, 255, 255, 255} ) // Represents the summation of all datastore queries that shall be performed to // extract values. Each operation mutates the state of the builder. type ViewRequestBuilder interface { GetMetricAtTime(fingerprint model.Fingerprint, time time.Time) GetMetricAtInterval(fingerprint model.Fingerprint, from, through time.Time, interval time.Duration) GetMetricRange(fingerprint model.Fingerprint, from, through time.Time) ScanJobs() scanJobs } // Contains the various unoptimized requests for data. type viewRequestBuilder struct { operations map[model.Fingerprint]ops } // Furnishes a ViewRequestBuilder for remarking what types of queries to perform. func NewViewRequestBuilder() viewRequestBuilder { return viewRequestBuilder{ operations: make(map[model.Fingerprint]ops), } } // Gets for the given Fingerprint either the value at that time if there is an // match or the one or two values adjacent thereto. func (v viewRequestBuilder) GetMetricAtTime(fingerprint model.Fingerprint, time time.Time) { ops := v.operations[fingerprint] ops = append(ops, &getValuesAtTimeOp{ time: time, }) v.operations[fingerprint] = ops } // Gets for the given Fingerprint either the value at that interval from From // through Through if there is an match or the one or two values adjacent // for each point. func (v viewRequestBuilder) GetMetricAtInterval(fingerprint model.Fingerprint, from, through time.Time, interval time.Duration) { ops := v.operations[fingerprint] ops = append(ops, &getValuesAtIntervalOp{ from: from, through: through, interval: interval, }) v.operations[fingerprint] = ops } // Gets for the given Fingerprint either the values that occur inclusively from // From through Through. func (v viewRequestBuilder) GetMetricRange(fingerprint model.Fingerprint, from, through time.Time) { ops := v.operations[fingerprint] ops = append(ops, &getValuesAlongRangeOp{ from: from, through: through, }) v.operations[fingerprint] = ops } // Emits the optimized scans that will occur in the data store. This // effectively resets the ViewRequestBuilder back to a pristine state. func (v viewRequestBuilder) ScanJobs() (j scanJobs) { for fingerprint, operations := range v.operations { sort.Sort(startsAtSort{operations}) j = append(j, scanJob{ fingerprint: fingerprint, operations: optimize(operations), }) delete(v.operations, fingerprint) } sort.Sort(j) return } type view struct { fingerprintToSeries map[model.Fingerprint]viewStream } func (v view) appendSample(fingerprint model.Fingerprint, timestamp time.Time, value model.SampleValue) { var ( series, ok = v.fingerprintToSeries[fingerprint] ) if !ok { series = newViewStream() v.fingerprintToSeries[fingerprint] = series } series.add(timestamp, value) } func (v view) Close() { v.fingerprintToSeries = make(map[model.Fingerprint]viewStream) } func (v view) GetValueAtTime(f model.Fingerprint, t time.Time) (samples []model.SamplePair) { series, ok := v.fingerprintToSeries[f] if !ok { return } iterator := series.values.Seek(skipListTime(t)) if iterator == nil { // If the iterator is nil, it means we seeked past the end of the series, // so we seek to the last value instead. Due to the reverse ordering // defined on skipListTime, this corresponds to the sample with the // earliest timestamp. iterator = series.values.SeekToLast() if iterator == nil { // The list is empty. return } } defer iterator.Close() if iterator.Key() == nil || iterator.Value() == nil { return } samples = append(samples, model.SamplePair{ Timestamp: time.Time(iterator.Key().(skipListTime)), Value: iterator.Value().(value).get(), }) if iterator.Previous() { samples = append(samples, model.SamplePair{ Timestamp: time.Time(iterator.Key().(skipListTime)), Value: iterator.Value().(value).get(), }) } return } func (v view) GetBoundaryValues(f model.Fingerprint, i model.Interval) (first []model.SamplePair, second []model.SamplePair) { first = v.GetValueAtTime(f, i.OldestInclusive) second = v.GetValueAtTime(f, i.NewestInclusive) return } func (v view) GetRangeValues(f model.Fingerprint, i model.Interval) (samples []model.SamplePair) { series, ok := v.fingerprintToSeries[f] if !ok { return } iterator := series.values.Seek(skipListTime(i.OldestInclusive)) if iterator == nil { // If the iterator is nil, it means we seeked past the end of the series, // so we seek to the last value instead. Due to the reverse ordering // defined on skipListTime, this corresponds to the sample with the // earliest timestamp. iterator = series.values.SeekToLast() if iterator == nil { // The list is empty. return } } for { timestamp := time.Time(iterator.Key().(skipListTime)) if timestamp.After(i.NewestInclusive) { break } if !timestamp.Before(i.OldestInclusive) { samples = append(samples, model.SamplePair{ Value: iterator.Value().(value).get(), Timestamp: timestamp, }) } if !iterator.Previous() { break } } return } func newView() view { return view{ fingerprintToSeries: make(map[model.Fingerprint]viewStream), } } type viewStream struct { values *skiplist.SkipList } func (s viewStream) add(timestamp time.Time, value model.SampleValue) { s.values.Set(skipListTime(timestamp), singletonValue(value)) } func newViewStream() viewStream { return viewStream{ values: skiplist.New(), } }