383 lines
13 KiB
Go
383 lines
13 KiB
Go
// Package index provides a number of indexes backed by persistent key-value
|
|
// stores. The only supported implementation of a key-value store is currently
|
|
// goleveldb, but other implementations can easily be added.
|
|
package index
|
|
|
|
import (
|
|
"flag"
|
|
"path"
|
|
|
|
clientmodel "github.com/prometheus/client_golang/model"
|
|
|
|
"github.com/prometheus/prometheus/storage/local/codec"
|
|
"github.com/prometheus/prometheus/storage/metric"
|
|
"github.com/prometheus/prometheus/utility"
|
|
)
|
|
|
|
const (
|
|
fingerprintToMetricDir = "archived_fingerprint_to_metric"
|
|
fingerprintTimeRangeDir = "archived_fingerprint_to_timerange"
|
|
labelNameToLabelValuesDir = "labelname_to_labelvalues"
|
|
labelPairToFingerprintsDir = "labelpair_to_fingerprints"
|
|
)
|
|
|
|
var (
|
|
fingerprintToMetricCacheSize = flag.Int("storage.fingerprintToMetricCacheSizeBytes", 25*1024*1024, "The size in bytes for the fingerprint to metric index cache.")
|
|
labelNameToLabelValuesCacheSize = flag.Int("storage.labelNameToLabelValuesCacheSizeBytes", 25*1024*1024, "The size in bytes for the label name to label values index cache.")
|
|
labelPairToFingerprintsCacheSize = flag.Int("storage.labelPairToFingerprintsCacheSizeBytes", 25*1024*1024, "The size in bytes for the label pair to fingerprints index cache.")
|
|
fingerprintTimeRangeCacheSize = flag.Int("storage.fingerprintTimeRangeCacheSizeBytes", 5*1024*1024, "The size in bytes for the metric time range index cache.")
|
|
)
|
|
|
|
// FingerprintMetricMapping is an in-memory map of fingerprints to metrics.
|
|
type FingerprintMetricMapping map[clientmodel.Fingerprint]clientmodel.Metric
|
|
|
|
// FingerprintMetricIndex models a database mapping fingerprints to metrics.
|
|
type FingerprintMetricIndex struct {
|
|
KeyValueStore
|
|
}
|
|
|
|
// IndexBatch indexes a batch of mappings from fingerprints to metrics.
|
|
//
|
|
// This method is goroutine-safe, but note that no specific order of execution
|
|
// can be guaranteed (especially critical if IndexBatch and UnindexBatch are
|
|
// called concurrently).
|
|
func (i *FingerprintMetricIndex) IndexBatch(mapping FingerprintMetricMapping) error {
|
|
b := i.NewBatch()
|
|
|
|
for fp, m := range mapping {
|
|
b.Put(codec.CodableFingerprint(fp), codec.CodableMetric(m))
|
|
}
|
|
|
|
return i.Commit(b)
|
|
}
|
|
|
|
// UnindexBatch unindexes a batch of mappings from fingerprints to metrics.
|
|
//
|
|
// This method is goroutine-safe, but note that no specific order of execution
|
|
// can be guaranteed (especially critical if IndexBatch and UnindexBatch are
|
|
// called concurrently).
|
|
func (i *FingerprintMetricIndex) UnindexBatch(mapping FingerprintMetricMapping) error {
|
|
b := i.NewBatch()
|
|
|
|
for fp := range mapping {
|
|
b.Delete(codec.CodableFingerprint(fp))
|
|
}
|
|
|
|
return i.Commit(b)
|
|
}
|
|
|
|
// Lookup looks up a metric by fingerprint. Looking up a non-existing
|
|
// fingerprint is not an error. In that case, (nil, false, nil) is returned.
|
|
//
|
|
// This method is goroutine-safe.
|
|
func (i *FingerprintMetricIndex) Lookup(fp clientmodel.Fingerprint) (metric clientmodel.Metric, ok bool, err error) {
|
|
ok, err = i.Get(codec.CodableFingerprint(fp), (*codec.CodableMetric)(&metric))
|
|
return
|
|
}
|
|
|
|
// NewFingerprintMetricIndex returns a LevelDB-backed FingerprintMetricIndex
|
|
// ready to use.
|
|
func NewFingerprintMetricIndex(basePath string) (*FingerprintMetricIndex, error) {
|
|
fingerprintToMetricDB, err := NewLevelDB(LevelDBOptions{
|
|
Path: path.Join(basePath, fingerprintToMetricDir),
|
|
CacheSizeBytes: *fingerprintToMetricCacheSize,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &FingerprintMetricIndex{
|
|
KeyValueStore: fingerprintToMetricDB,
|
|
}, nil
|
|
}
|
|
|
|
// LabelNameLabelValuesMapping is an in-memory map of label names to
|
|
// label values.
|
|
type LabelNameLabelValuesMapping map[clientmodel.LabelName]clientmodel.LabelValues
|
|
|
|
// LabelNameLabelValuesIndex is a KeyValueStore that maps existing label names
|
|
// to all label values stored for that label name.
|
|
type LabelNameLabelValuesIndex struct {
|
|
KeyValueStore
|
|
}
|
|
|
|
// IndexBatch adds a batch of label name to label values mappings to the
|
|
// index. A mapping of a label name to an empty slice of label values results in
|
|
// a deletion of that mapping from the index.
|
|
//
|
|
// While this method is fundamentally goroutine-safe, note that the order of
|
|
// execution for multiple batches executed concurrently is undefined. Also, it
|
|
// is in general not safe to mutate the index while Extend or Reduce are
|
|
// running.
|
|
func (i *LabelNameLabelValuesIndex) IndexBatch(b LabelNameLabelValuesMapping) error {
|
|
batch := i.NewBatch()
|
|
|
|
for name, values := range b {
|
|
if len(values) == 0 {
|
|
if err := batch.Delete(codec.CodableLabelName(name)); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err := batch.Put(codec.CodableLabelName(name), codec.CodableLabelValues(values)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return i.Commit(batch)
|
|
}
|
|
|
|
// Lookup looks up all label values for a given label name. Looking up a
|
|
// non-existing label name is not an error. In that case, (nil, false, nil) is
|
|
// returned.
|
|
//
|
|
// This method is goroutine-safe.
|
|
func (i *LabelNameLabelValuesIndex) Lookup(l clientmodel.LabelName) (values clientmodel.LabelValues, ok bool, err error) {
|
|
ok, err = i.Get(codec.CodableLabelName(l), (*codec.CodableLabelValues)(&values))
|
|
return
|
|
}
|
|
|
|
// Extend incorporates the given metric into the index, i.e. it creates new
|
|
// label name to label values mappings for new label names, and it extends the
|
|
// label values list mapped from already existing label names appropriately.
|
|
//
|
|
// This method is not goroutine-safe.
|
|
func (i *LabelNameLabelValuesIndex) Extend(m clientmodel.Metric) error {
|
|
b := make(LabelNameLabelValuesMapping, len(m))
|
|
for ln, lv := range m {
|
|
baseLVs, _, err := i.Lookup(ln)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
lvSet := utility.Set{}
|
|
for _, baseLV := range baseLVs {
|
|
lvSet.Add(baseLV)
|
|
}
|
|
lvSet.Add(lv)
|
|
if len(lvSet) == len(baseLVs) {
|
|
continue
|
|
}
|
|
lvs := make(clientmodel.LabelValues, 0, len(lvSet))
|
|
for v := range lvSet {
|
|
lvs = append(lvs, v.(clientmodel.LabelValue))
|
|
}
|
|
b[ln] = lvs
|
|
}
|
|
return i.IndexBatch(b)
|
|
}
|
|
|
|
// Reduce removes label values from the index based on the given label pair to
|
|
// fingerprints mapping. The mapping to be passed in here is returned by
|
|
// LabelPairFingerprintIndex.Reduce. It contains all the label pairs that have
|
|
// now fewer fingerprints mapped to it. This method checks if any label pair has
|
|
// arrived at zero mapped fingerprints. In that case, the value of that label
|
|
// pair is removed from the list of label values mapped to the name of that
|
|
// label pair. Label names that are then mapped to zero label values are removed
|
|
// entirely from the index.
|
|
//
|
|
// This method is not goroutine-safe.
|
|
func (i *LabelNameLabelValuesIndex) Reduce(m LabelPairFingerprintsMapping) error {
|
|
b := make(LabelNameLabelValuesMapping, len(m))
|
|
for lp, fps := range m {
|
|
if len(fps) != 0 {
|
|
continue
|
|
}
|
|
ln := lp.Name
|
|
lv := lp.Value
|
|
baseValues, ok := b[ln]
|
|
if !ok {
|
|
var err error
|
|
baseValues, _, err = i.Lookup(ln)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
lvSet := utility.Set{}
|
|
for _, baseValue := range baseValues {
|
|
lvSet.Add(baseValue)
|
|
}
|
|
lvSet.Remove(lv)
|
|
if len(lvSet) == len(baseValues) {
|
|
continue
|
|
}
|
|
lvs := make(clientmodel.LabelValues, 0, len(lvSet))
|
|
for v := range lvSet {
|
|
lvs = append(lvs, v.(clientmodel.LabelValue))
|
|
}
|
|
b[ln] = lvs
|
|
}
|
|
return i.IndexBatch(b)
|
|
}
|
|
|
|
// NewLabelNameLabelValuesIndex returns a LevelDB-backed
|
|
// LabelNameLabelValuesIndex ready to use.
|
|
func NewLabelNameLabelValuesIndex(basePath string) (*LabelNameLabelValuesIndex, error) {
|
|
labelNameToLabelValuesDB, err := NewLevelDB(LevelDBOptions{
|
|
Path: path.Join(basePath, labelNameToLabelValuesDir),
|
|
CacheSizeBytes: *labelNameToLabelValuesCacheSize,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &LabelNameLabelValuesIndex{
|
|
KeyValueStore: labelNameToLabelValuesDB,
|
|
}, nil
|
|
}
|
|
|
|
// LabelPairFingerprintsMapping is an in-memory map of label pairs to
|
|
// fingerprints.
|
|
type LabelPairFingerprintsMapping map[metric.LabelPair]clientmodel.Fingerprints
|
|
|
|
// LabelPairFingerprintIndex is a KeyValueStore that maps existing label pairs
|
|
// to the fingerprints of all metrics containing those label pairs.
|
|
type LabelPairFingerprintIndex struct {
|
|
KeyValueStore
|
|
}
|
|
|
|
// IndexBatch indexes a batch of mappings from label pairs to fingerprints. A
|
|
// mapping to an empty slice of fingerprints results in deletion of that mapping
|
|
// from the index.
|
|
//
|
|
// While this method is fundamentally goroutine-safe, note that the order of
|
|
// execution for multiple batches executed concurrently is undefined. Also, it
|
|
// is in general not safe to mutate the index while Extend or Reduce are
|
|
// running.
|
|
func (i *LabelPairFingerprintIndex) IndexBatch(m LabelPairFingerprintsMapping) error {
|
|
batch := i.NewBatch()
|
|
|
|
for pair, fps := range m {
|
|
if len(fps) == 0 {
|
|
batch.Delete(codec.CodableLabelPair(pair))
|
|
} else {
|
|
batch.Put(codec.CodableLabelPair(pair), codec.CodableFingerprints(fps))
|
|
}
|
|
}
|
|
|
|
return i.Commit(batch)
|
|
}
|
|
|
|
// Lookup looks up all fingerprints for a given label pair. Looking up a
|
|
// non-existing label pair is not an error. In that case, (nil, false, nil) is
|
|
// returned.
|
|
//
|
|
// This method is goroutine-safe.
|
|
func (i *LabelPairFingerprintIndex) Lookup(p metric.LabelPair) (fps clientmodel.Fingerprints, ok bool, err error) {
|
|
ok, err = i.Get((codec.CodableLabelPair)(p), (*codec.CodableFingerprints)(&fps))
|
|
return
|
|
}
|
|
|
|
// Extend incorporates the given metric into the index, i.e. it creates new
|
|
// label pair to fingerprint mappings for new label pairs, and it extends the
|
|
// fingerprint list mapped from already existing label pairs appropriately.
|
|
//
|
|
// This method is not goroutine-safe.
|
|
func (i *LabelPairFingerprintIndex) Extend(m clientmodel.Metric, fp clientmodel.Fingerprint) error {
|
|
b := make(LabelPairFingerprintsMapping, len(m))
|
|
for ln, lv := range m {
|
|
lp := metric.LabelPair{Name: ln, Value: lv}
|
|
baseFPs, _, err := i.Lookup(lp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fpSet := utility.Set{}
|
|
for _, baseFP := range baseFPs {
|
|
fpSet.Add(baseFP)
|
|
}
|
|
fpSet.Add(fp)
|
|
if len(fpSet) == len(baseFPs) {
|
|
continue
|
|
}
|
|
fps := make(clientmodel.Fingerprints, 0, len(fpSet))
|
|
for f := range fpSet {
|
|
fps = append(fps, f.(clientmodel.Fingerprint))
|
|
}
|
|
b[lp] = fps
|
|
|
|
}
|
|
return i.IndexBatch(b)
|
|
}
|
|
|
|
// Reduce removes the given fingerprint from the fingerprint lists mapped from
|
|
// the label pairs contained in the given metric. All the updated mappings are
|
|
// returned (for consumption by LabelNameLabelValuesIndex.Reduce).
|
|
//
|
|
// This method is not goroutine-safe.
|
|
func (i *LabelPairFingerprintIndex) Reduce(m clientmodel.Metric, fp clientmodel.Fingerprint) (LabelPairFingerprintsMapping, error) {
|
|
b := make(LabelPairFingerprintsMapping, len(m))
|
|
for ln, lv := range m {
|
|
lp := metric.LabelPair{Name: ln, Value: lv}
|
|
baseFPs, _, err := i.Lookup(lp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fpSet := utility.Set{}
|
|
for _, baseFP := range baseFPs {
|
|
fpSet.Add(baseFP)
|
|
}
|
|
fpSet.Remove(fp)
|
|
if len(fpSet) == len(baseFPs) {
|
|
continue
|
|
}
|
|
fps := make(clientmodel.Fingerprints, 0, len(fpSet))
|
|
for f := range fpSet {
|
|
fps = append(fps, f.(clientmodel.Fingerprint))
|
|
}
|
|
b[lp] = fps
|
|
|
|
}
|
|
return b, i.IndexBatch(b)
|
|
}
|
|
|
|
// NewLabelPairFingerprintIndex returns a LevelDB-backed
|
|
// LabelPairFingerprintIndex ready to use.
|
|
func NewLabelPairFingerprintIndex(basePath string) (*LabelPairFingerprintIndex, error) {
|
|
labelPairToFingerprintsDB, err := NewLevelDB(LevelDBOptions{
|
|
Path: path.Join(basePath, labelPairToFingerprintsDir),
|
|
CacheSizeBytes: *labelPairToFingerprintsCacheSize,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &LabelPairFingerprintIndex{
|
|
KeyValueStore: labelPairToFingerprintsDB,
|
|
}, nil
|
|
}
|
|
|
|
// FingerprintTimeRangeIndex models a database tracking the time ranges
|
|
// of metrics by their fingerprints.
|
|
type FingerprintTimeRangeIndex struct {
|
|
KeyValueStore
|
|
}
|
|
|
|
// Lookup returns the time range for the given fingerprint. Looking up a
|
|
// non-existing fingerprint is not an error. In that case, (0, 0, false, nil) is
|
|
// returned.
|
|
//
|
|
// This method is goroutine-safe.
|
|
func (i *FingerprintTimeRangeIndex) Lookup(fp clientmodel.Fingerprint) (firstTime, lastTime clientmodel.Timestamp, ok bool, err error) {
|
|
var tr codec.CodableTimeRange
|
|
ok, err = i.Get(codec.CodableFingerprint(fp), &tr)
|
|
return tr.First, tr.Last, ok, err
|
|
}
|
|
|
|
// Has returns true if the given fingerprint is present.
|
|
//
|
|
// This method is goroutine-safe.
|
|
func (i *FingerprintTimeRangeIndex) Has(fp clientmodel.Fingerprint) (ok bool, err error) {
|
|
return i.KeyValueStore.Has(codec.CodableFingerprint(fp))
|
|
}
|
|
|
|
// NewFingerprintTimeRangeIndex returns a LevelDB-backed
|
|
// FingerprintTimeRangeIndex ready to use.
|
|
func NewFingerprintTimeRangeIndex(basePath string) (*FingerprintTimeRangeIndex, error) {
|
|
fingerprintTimeRangeDB, err := NewLevelDB(LevelDBOptions{
|
|
Path: path.Join(basePath, fingerprintTimeRangeDir),
|
|
CacheSizeBytes: *fingerprintTimeRangeCacheSize,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &FingerprintTimeRangeIndex{
|
|
KeyValueStore: fingerprintTimeRangeDB,
|
|
}, nil
|
|
}
|