mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-27 14:39:40 -08:00
615 lines
16 KiB
Go
615 lines
16 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"
|
|
"github.com/prometheus/prometheus/coding"
|
|
"github.com/prometheus/prometheus/coding/indexable"
|
|
"github.com/prometheus/prometheus/model"
|
|
dto "github.com/prometheus/prometheus/model/generated"
|
|
"github.com/prometheus/prometheus/storage"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// tieredStorage both persists samples and generates materialized views for
|
|
// queries.
|
|
type tieredStorage struct {
|
|
appendToDiskQueue chan model.Sample
|
|
appendToMemoryQueue chan model.Sample
|
|
diskFrontier *diskFrontier
|
|
diskStorage *LevelDBMetricPersistence
|
|
draining chan bool
|
|
flushMemoryInterval time.Duration
|
|
memoryArena memorySeriesStorage
|
|
memoryTTL time.Duration
|
|
mutex sync.Mutex
|
|
viewQueue chan viewJob
|
|
writeMemoryInterval time.Duration
|
|
}
|
|
|
|
// viewJob encapsulates a request to extract sample values from the datastore.
|
|
type viewJob struct {
|
|
builder ViewRequestBuilder
|
|
output chan View
|
|
err chan error
|
|
}
|
|
|
|
// Provides a unified means for batch appending values into the datastore along
|
|
// with querying for values in an efficient way.
|
|
type Storage interface {
|
|
// Enqueues a Sample for storage.
|
|
AppendSample(model.Sample) error
|
|
// Enqueus a ViewRequestBuilder for materialization, subject to a timeout.
|
|
MakeView(request ViewRequestBuilder, timeout time.Duration) (View, error)
|
|
// Starts serving requests.
|
|
Serve()
|
|
// Stops the storage subsystem, flushing all pending operations.
|
|
Drain()
|
|
Flush()
|
|
Close()
|
|
}
|
|
|
|
func NewTieredStorage(appendToMemoryQueueDepth, appendToDiskQueueDepth, viewQueueDepth uint, flushMemoryInterval, writeMemoryInterval, memoryTTL time.Duration, root string) Storage {
|
|
diskStorage, err := NewLevelDBMetricPersistence(root)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return &tieredStorage{
|
|
appendToDiskQueue: make(chan model.Sample, appendToDiskQueueDepth),
|
|
appendToMemoryQueue: make(chan model.Sample, appendToMemoryQueueDepth),
|
|
diskStorage: diskStorage,
|
|
draining: make(chan bool),
|
|
flushMemoryInterval: flushMemoryInterval,
|
|
memoryArena: NewMemorySeriesStorage(),
|
|
memoryTTL: memoryTTL,
|
|
viewQueue: make(chan viewJob, viewQueueDepth),
|
|
writeMemoryInterval: writeMemoryInterval,
|
|
}
|
|
}
|
|
|
|
func (t *tieredStorage) AppendSample(s model.Sample) (err error) {
|
|
if len(t.draining) > 0 {
|
|
return fmt.Errorf("Storage is in the process of draining.")
|
|
}
|
|
|
|
t.appendToMemoryQueue <- s
|
|
|
|
return
|
|
}
|
|
|
|
func (t *tieredStorage) Drain() {
|
|
if len(t.draining) == 0 {
|
|
t.draining <- true
|
|
}
|
|
}
|
|
|
|
func (t *tieredStorage) MakeView(builder ViewRequestBuilder, deadline time.Duration) (view View, err error) {
|
|
if len(t.draining) > 0 {
|
|
err = fmt.Errorf("Storage is in the process of draining.")
|
|
return
|
|
}
|
|
|
|
result := make(chan View)
|
|
errChan := make(chan error)
|
|
t.viewQueue <- viewJob{
|
|
builder: builder,
|
|
output: result,
|
|
err: errChan,
|
|
}
|
|
|
|
select {
|
|
case value := <-result:
|
|
view = value
|
|
case err = <-errChan:
|
|
return
|
|
case <-time.After(deadline):
|
|
err = fmt.Errorf("MakeView timed out after %s.", deadline)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (t *tieredStorage) rebuildDiskFrontier() (err error) {
|
|
begin := time.Now()
|
|
defer func() {
|
|
duration := time.Since(begin)
|
|
|
|
recordOutcome(duration, err, map[string]string{operation: appendSample, result: success}, map[string]string{operation: rebuildDiskFrontier, result: failure})
|
|
}()
|
|
i, closer, err := t.diskStorage.metricSamples.GetIterator()
|
|
if closer != nil {
|
|
defer closer.Close()
|
|
}
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
t.diskFrontier, err = newDiskFrontier(i)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (t *tieredStorage) Serve() {
|
|
var (
|
|
flushMemoryTicker = time.Tick(t.flushMemoryInterval)
|
|
writeMemoryTicker = time.Tick(t.writeMemoryInterval)
|
|
)
|
|
|
|
for {
|
|
t.reportQueues()
|
|
|
|
select {
|
|
case <-writeMemoryTicker:
|
|
t.writeMemory()
|
|
case <-flushMemoryTicker:
|
|
t.flushMemory()
|
|
case viewRequest := <-t.viewQueue:
|
|
t.renderView(viewRequest)
|
|
case <-t.draining:
|
|
t.flush()
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func (t *tieredStorage) reportQueues() {
|
|
queueSizes.Set(map[string]string{"queue": "append_to_disk", "facet": "occupancy"}, float64(len(t.appendToDiskQueue)))
|
|
queueSizes.Set(map[string]string{"queue": "append_to_disk", "facet": "capacity"}, float64(cap(t.appendToDiskQueue)))
|
|
|
|
queueSizes.Set(map[string]string{"queue": "append_to_memory", "facet": "occupancy"}, float64(len(t.appendToMemoryQueue)))
|
|
queueSizes.Set(map[string]string{"queue": "append_to_memory", "facet": "capacity"}, float64(cap(t.appendToMemoryQueue)))
|
|
|
|
queueSizes.Set(map[string]string{"queue": "view_generation", "facet": "occupancy"}, float64(len(t.viewQueue)))
|
|
queueSizes.Set(map[string]string{"queue": "view_generation", "facet": "capacity"}, float64(cap(t.viewQueue)))
|
|
}
|
|
|
|
func (t *tieredStorage) writeMemory() {
|
|
begin := time.Now()
|
|
defer func() {
|
|
duration := time.Since(begin)
|
|
|
|
recordOutcome(duration, nil, map[string]string{operation: appendSample, result: success}, map[string]string{operation: writeMemory, result: failure})
|
|
}()
|
|
|
|
t.mutex.Lock()
|
|
defer t.mutex.Unlock()
|
|
|
|
pendingLength := len(t.appendToMemoryQueue)
|
|
|
|
for i := 0; i < pendingLength; i++ {
|
|
t.memoryArena.AppendSample(<-t.appendToMemoryQueue)
|
|
}
|
|
}
|
|
|
|
func (t *tieredStorage) Flush() {
|
|
t.flush()
|
|
}
|
|
|
|
func (t *tieredStorage) Close() {
|
|
t.Drain()
|
|
t.diskStorage.Close()
|
|
}
|
|
|
|
// Write all pending appends.
|
|
func (t *tieredStorage) flush() (err error) {
|
|
// Trim any old values to reduce iterative write costs.
|
|
t.flushMemory()
|
|
t.writeMemory()
|
|
t.flushMemory()
|
|
return
|
|
}
|
|
|
|
type memoryToDiskFlusher struct {
|
|
toDiskQueue chan model.Sample
|
|
disk MetricPersistence
|
|
olderThan time.Time
|
|
valuesAccepted int
|
|
valuesRejected int
|
|
}
|
|
|
|
type memoryToDiskFlusherVisitor struct {
|
|
stream stream
|
|
flusher *memoryToDiskFlusher
|
|
}
|
|
|
|
func (f memoryToDiskFlusherVisitor) DecodeKey(in interface{}) (out interface{}, err error) {
|
|
out = time.Time(in.(skipListTime))
|
|
return
|
|
}
|
|
|
|
func (f memoryToDiskFlusherVisitor) DecodeValue(in interface{}) (out interface{}, err error) {
|
|
out = in.(value).get()
|
|
return
|
|
}
|
|
|
|
func (f memoryToDiskFlusherVisitor) Filter(key, value interface{}) (filterResult storage.FilterResult) {
|
|
var (
|
|
recordTime = key.(time.Time)
|
|
)
|
|
|
|
if recordTime.Before(f.flusher.olderThan) {
|
|
f.flusher.valuesAccepted++
|
|
|
|
return storage.ACCEPT
|
|
}
|
|
f.flusher.valuesRejected++
|
|
return storage.STOP
|
|
}
|
|
|
|
func (f memoryToDiskFlusherVisitor) Operate(key, value interface{}) (err *storage.OperatorError) {
|
|
var (
|
|
recordTime = key.(time.Time)
|
|
recordValue = value.(model.SampleValue)
|
|
)
|
|
|
|
if len(f.flusher.toDiskQueue) == cap(f.flusher.toDiskQueue) {
|
|
f.flusher.Flush()
|
|
}
|
|
|
|
f.flusher.toDiskQueue <- model.Sample{
|
|
Metric: f.stream.metric,
|
|
Timestamp: recordTime,
|
|
Value: recordValue,
|
|
}
|
|
|
|
f.stream.values.Delete(skipListTime(recordTime))
|
|
|
|
return
|
|
}
|
|
|
|
func (f *memoryToDiskFlusher) ForStream(stream stream) (decoder storage.RecordDecoder, filter storage.RecordFilter, operator storage.RecordOperator) {
|
|
visitor := memoryToDiskFlusherVisitor{
|
|
stream: stream,
|
|
flusher: f,
|
|
}
|
|
|
|
// fmt.Printf("fingerprint -> %s\n", model.NewFingerprintFromMetric(stream.metric).ToRowKey())
|
|
|
|
return visitor, visitor, visitor
|
|
}
|
|
|
|
func (f *memoryToDiskFlusher) Flush() {
|
|
length := len(f.toDiskQueue)
|
|
samples := model.Samples{}
|
|
for i := 0; i < length; i++ {
|
|
samples = append(samples, <-f.toDiskQueue)
|
|
}
|
|
start := time.Now()
|
|
f.disk.AppendSamples(samples)
|
|
if false {
|
|
fmt.Printf("Took %s to append...\n", time.Since(start))
|
|
}
|
|
}
|
|
|
|
func (f memoryToDiskFlusher) Close() {
|
|
f.Flush()
|
|
}
|
|
|
|
// Persist a whole bunch of samples to the datastore.
|
|
func (t *tieredStorage) flushMemory() {
|
|
begin := time.Now()
|
|
defer func() {
|
|
duration := time.Since(begin)
|
|
|
|
recordOutcome(duration, nil, map[string]string{operation: appendSample, result: success}, map[string]string{operation: flushMemory, result: failure})
|
|
}()
|
|
|
|
t.mutex.Lock()
|
|
defer t.mutex.Unlock()
|
|
|
|
flusher := &memoryToDiskFlusher{
|
|
disk: t.diskStorage,
|
|
olderThan: time.Now().Add(-1 * t.memoryTTL),
|
|
toDiskQueue: t.appendToDiskQueue,
|
|
}
|
|
defer flusher.Close()
|
|
|
|
t.memoryArena.ForEachSample(flusher)
|
|
|
|
return
|
|
}
|
|
|
|
func (t *tieredStorage) renderView(viewJob viewJob) (err error) {
|
|
begin := time.Now()
|
|
defer func() {
|
|
duration := time.Since(begin)
|
|
|
|
recordOutcome(duration, err, map[string]string{operation: appendSample, result: success}, map[string]string{operation: renderView, result: failure})
|
|
}()
|
|
|
|
t.mutex.Lock()
|
|
defer t.mutex.Unlock()
|
|
|
|
var (
|
|
scans = viewJob.builder.ScanJobs()
|
|
// standingOperations = ops{}
|
|
// lastTime = time.Time{}
|
|
view = newView()
|
|
)
|
|
|
|
// Rebuilding of the frontier should happen on a conditional basis if a
|
|
// (fingerprint, timestamp) tuple is outside of the current frontier.
|
|
err = t.rebuildDiskFrontier()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
iterator, closer, err := t.diskStorage.metricSamples.GetIterator()
|
|
if closer != nil {
|
|
defer closer.Close()
|
|
}
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
for _, scanJob := range scans {
|
|
// XXX: Memoize the last retrieval for forward scans.
|
|
var (
|
|
// standingOperations ops
|
|
)
|
|
|
|
// fmt.Printf("Starting scan of %s...\n", scanJob)
|
|
if t.diskFrontier != nil || t.diskFrontier.ContainsFingerprint(scanJob.fingerprint) {
|
|
// fmt.Printf("Using diskFrontier %s\n", t.diskFrontier)
|
|
seriesFrontier, err := newSeriesFrontier(scanJob.fingerprint, *t.diskFrontier, iterator)
|
|
// fmt.Printf("Using seriesFrontier %s\n", seriesFrontier)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if seriesFrontier != nil {
|
|
var (
|
|
targetKey = &dto.SampleKey{}
|
|
foundKey = &dto.SampleKey{}
|
|
foundValue *dto.SampleValueSeries
|
|
)
|
|
|
|
for _, operation := range scanJob.operations {
|
|
if seriesFrontier.lastTime.Before(operation.StartsAt()) {
|
|
// fmt.Printf("operation %s occurs after %s; aborting...\n", operation, seriesFrontier.lastTime)
|
|
break
|
|
}
|
|
|
|
scanJob.operations = scanJob.operations[1:len(scanJob.operations)]
|
|
|
|
if operation.StartsAt().Before(seriesFrontier.firstSupertime) {
|
|
// fmt.Printf("operation %s occurs before %s; discarding...\n", operation, seriesFrontier.firstSupertime)
|
|
continue
|
|
}
|
|
|
|
if seriesFrontier.lastSupertime.Before(operation.StartsAt()) && !seriesFrontier.lastTime.Before(operation.StartsAt()) {
|
|
targetKey.Timestamp = indexable.EncodeTime(seriesFrontier.lastSupertime)
|
|
} else {
|
|
targetKey.Timestamp = indexable.EncodeTime(operation.StartsAt())
|
|
}
|
|
|
|
targetKey.Fingerprint = scanJob.fingerprint.ToDTO()
|
|
|
|
rawKey, _ := coding.NewProtocolBufferEncoder(targetKey).Encode()
|
|
|
|
// XXX: Use frontiers to manage out of range queries.
|
|
iterator.Seek(rawKey)
|
|
|
|
foundKey, err = extractSampleKey(iterator)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
var (
|
|
fst = indexable.DecodeTime(foundKey.Timestamp)
|
|
lst = time.Unix(*foundKey.LastTimestamp, 0)
|
|
)
|
|
|
|
if !((operation.StartsAt().Before(fst)) || lst.Before(operation.StartsAt())) {
|
|
// fmt.Printf("operation %s occurs inside of %s...\n", operation, foundKey)
|
|
foundValue, err = extractSampleValue(iterator)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
} else if operation.StartsAt().Before(fst) {
|
|
fmt.Printf("operation %s may occur in next entity; fast forwarding...\n", operation)
|
|
panic("oops")
|
|
} else {
|
|
panic("illegal state")
|
|
}
|
|
|
|
var (
|
|
elementCount = len(foundValue.Value)
|
|
searcher = func(i int) bool {
|
|
return time.Unix(*foundValue.Value[i].Timestamp, 0).After(operation.StartsAt())
|
|
}
|
|
index = sort.Search(elementCount, searcher)
|
|
)
|
|
|
|
if index != elementCount {
|
|
if index > 0 {
|
|
index--
|
|
}
|
|
|
|
foundValue.Value = foundValue.Value[index:elementCount]
|
|
}
|
|
switch operation.(type) {
|
|
case getValuesAtTimeOp:
|
|
if len(foundValue.Value) > 0 {
|
|
view.appendSample(scanJob.fingerprint, time.Unix(*foundValue.Value[0].Timestamp, 0), model.SampleValue(*foundValue.Value[0].Value))
|
|
}
|
|
if len(foundValue.Value) > 1 {
|
|
view.appendSample(scanJob.fingerprint, time.Unix(*foundValue.Value[1].Timestamp, 0), model.SampleValue(*foundValue.Value[1].Value))
|
|
}
|
|
default:
|
|
panic("unhandled")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// for {
|
|
// if len(s.operations) == 0 {
|
|
// if len(standingOperations) > 0 {
|
|
// var (
|
|
// intervals = collectIntervals(standingOperations)
|
|
// ranges = collectRanges(standingOperations)
|
|
// )
|
|
|
|
// if len(intervals) > 0 {
|
|
// }
|
|
|
|
// if len(ranges) > 0 {
|
|
// if len(ranges) > 0 {
|
|
|
|
// }
|
|
// }
|
|
// break
|
|
// }
|
|
// }
|
|
|
|
// operation := s.operations[0]
|
|
// if operation.StartsAt().Equal(lastTime) {
|
|
// standingOperations = append(standingOperations, operation)
|
|
// } else {
|
|
// standingOperations = ops{operation}
|
|
// lastTime = operation.StartsAt()
|
|
// }
|
|
|
|
// s.operations = s.operations[1:len(s.operations)]
|
|
// }
|
|
|
|
viewJob.output <- view
|
|
|
|
return
|
|
}
|
|
|
|
func (s scanJobs) Represent(d *LevelDBMetricPersistence, m memorySeriesStorage) (storage *memorySeriesStorage, err error) {
|
|
|
|
if len(s) == 0 {
|
|
return
|
|
}
|
|
|
|
iterator, closer, err := d.metricSamples.GetIterator()
|
|
if err != nil {
|
|
panic(err)
|
|
return
|
|
}
|
|
defer closer.Close()
|
|
|
|
diskFrontier, err := newDiskFrontier(iterator)
|
|
if err != nil {
|
|
panic(err)
|
|
return
|
|
}
|
|
if diskFrontier == nil {
|
|
panic("diskfrontier == nil")
|
|
}
|
|
|
|
for _, job := range s {
|
|
if len(job.operations) == 0 {
|
|
panic("len(job.operations) == 0 should never occur")
|
|
}
|
|
|
|
// Determine if the metric is in the known keyspace. This is used as a
|
|
// high-level heuristic before comparing the timestamps.
|
|
var (
|
|
fingerprint = job.fingerprint
|
|
absentDiskKeyspace = fingerprint.Less(diskFrontier.firstFingerprint) || diskFrontier.lastFingerprint.Less(fingerprint)
|
|
absentMemoryKeyspace = false
|
|
)
|
|
|
|
if _, ok := m.fingerprintToSeries[fingerprint]; !ok {
|
|
absentMemoryKeyspace = true
|
|
}
|
|
|
|
var (
|
|
firstSupertime time.Time
|
|
lastSupertime time.Time
|
|
)
|
|
|
|
var (
|
|
_ = absentMemoryKeyspace
|
|
_ = firstSupertime
|
|
_ = lastSupertime
|
|
)
|
|
|
|
// If the key is present in the disk keyspace, we should find out the maximum
|
|
// seek points ahead of time. In the LevelDB case, this will save us from
|
|
// having to dispose of and recreate the iterator.
|
|
if !absentDiskKeyspace {
|
|
seriesFrontier, err := newSeriesFrontier(fingerprint, *diskFrontier, iterator)
|
|
if err != nil {
|
|
panic(err)
|
|
return nil, err
|
|
}
|
|
|
|
if seriesFrontier == nil {
|
|
panic("ouch")
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// var (
|
|
// memoryLowWaterMark time.Time
|
|
// memoryHighWaterMark time.Time
|
|
// )
|
|
|
|
// if !absentMemoryKeyspace {
|
|
// }
|
|
// // if firstDiskFingerprint.Equal(job.fingerprint) {
|
|
// // for _, operation := range job.operations {
|
|
// // if o, ok := operation.(getMetricAtTimeOperation); ok {
|
|
// // if o.StartTime().Before(firstDiskSuperTime) {
|
|
// // }
|
|
// // }
|
|
|
|
// // if o, ok := operation.(GetMetricAtInterval); ok {
|
|
// // }
|
|
// // }
|
|
// // }
|
|
// }
|
|
// // // Compare the metrics on the basis of the keys.
|
|
// // firstSampleInRange = sort.IsSorted(model.Fingerprints{firstDiskFingerprint, s[0].fingerprint})
|
|
// // lastSampleInRange = sort.IsSorted(model.Fingerprints{s[s.Len()-1].fingerprint, lastDiskFingerprint})
|
|
|
|
// // if firstSampleInRange && firstDiskFingerprint.Equal(s[0].fingerprint) {
|
|
// // firstSampleInRange = !indexable.DecodeTime(firstKey.Timestamp).After(s.operations[0].StartTime())
|
|
// // }
|
|
// // if lastSampleInRange && lastDiskFingerprint.Equal(s[s.Len()-1].fingerprint) {
|
|
// // lastSampleInRange = !s.operations[s.Len()-1].StartTime().After(indexable.DecodeTime(lastKey.Timestamp))
|
|
// // }
|
|
|
|
// // for _, job := range s {
|
|
// // operations := job.operations
|
|
// // numberOfOperations := len(operations)
|
|
// // for j := 0; j < numberOfOperations; j++ {
|
|
// // operationTime := operations[j].StartTime()
|
|
// // group, skipAhead := collectOperationsForTime(operationTime, operations[j:numberOfOperations])
|
|
// // ranges := collectRanges(group)
|
|
// // intervals := collectIntervals(group)
|
|
|
|
// // fmt.Printf("ranges -> %s\n", ranges)
|
|
// // if len(ranges) > 0 {
|
|
// // fmt.Printf("d -> %s\n", peekForLongestRange(ranges, ranges[0].through))
|
|
// // }
|
|
|
|
// // j += skipAhead
|
|
// // }
|
|
// // }
|