pkg/export/storage.go (77 lines of code) (raw):
// Copyright 2020 Google LLC
//
// 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
//
// https://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 export
import (
"context"
"errors"
"sync"
"github.com/prometheus/prometheus/config"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/storage"
"github.com/prometheus/prometheus/tsdb/chunks"
"github.com/prometheus/prometheus/tsdb/record"
)
// Storage provides a stateful wrapper around an Exporter that implements
// Prometheus's storage interface (Appendable).
//
// For performance reasons Exporter is optimized to be tightly integrate with
// Prometheus's storage. This makes it rely on external state (series ID to label
// mapping).
// For use cases where a full Prometheus storage engine is not present (e.g. rule
// evaluation service), Storage acts as a simple drop-in replacement that directly
// manages the state required by Exporter.
type Storage struct {
exporter *Exporter
mtx sync.Mutex
labels map[storage.SeriesRef]labels.Labels
}
// NewStorage returns a new Prometheus storage that's exporting data via the exporter.
func NewStorage(exporter *Exporter) *Storage {
s := &Storage{
exporter: exporter,
labels: map[storage.SeriesRef]labels.Labels{},
}
exporter.SetLabelsByIDFunc(s.labelsByID)
return s
}
// ApplyConfig applies the new configuration to the storage. The given `ExporterOpts`, if
// non-nil, is applied to the exporter, potentially recreating the metric client. It must be
// defaulted and validated.
func (s *Storage) ApplyConfig(cfg *config.Config, opts *ExporterOpts) error {
return s.exporter.ApplyConfig(cfg, opts)
}
// Run background processing of the storage.
func (s *Storage) Run() error {
return s.exporter.Run()
}
func (s *Storage) labelsByID(id storage.SeriesRef) labels.Labels {
s.mtx.Lock()
lset := s.labels[id]
s.mtx.Unlock()
return lset
}
func (s *Storage) setLabels(lset labels.Labels) storage.SeriesRef {
h := storage.SeriesRef(lset.Hash())
s.mtx.Lock()
s.labels[h] = lset
s.mtx.Unlock()
return h
}
func (s *Storage) clearLabels(samples []record.RefSample) {
s.mtx.Lock()
defer s.mtx.Unlock()
for _, sample := range samples {
delete(s.labels, storage.SeriesRef(sample.Ref))
}
}
// Appender returns a new Appender.
func (s *Storage) Appender(context.Context) storage.Appender {
return &storageAppender{
storage: s,
samples: make([]record.RefSample, 0, 64),
}
}
type storageAppender struct {
// Make sure all Appender methods are implemented at compile time. Panics
// are expected and intended if a method is used unexpectedly.
storage.Appender
storage *Storage
samples []record.RefSample
}
func (a *storageAppender) Append(_ storage.SeriesRef, lset labels.Labels, t int64, v float64) (storage.SeriesRef, error) {
if lset.IsEmpty() {
return 0, errors.New("label set is nil")
}
a.samples = append(a.samples, record.RefSample{
Ref: chunks.HeadSeriesRef(a.storage.setLabels(lset)),
T: t,
V: v,
})
// Return 0 ID to indicate that we don't support fast path appending.
return 0, nil
}
func (a *storageAppender) Commit() error {
// This method is used to export rule results. It's generally safe to assume that
// they are of type gauge. Thus we pass in a metadata func that always returns the
// gauge type.
// In the future we may want to populate the help text with information on the rule
// that produced the metric.
// Exemplars can be nil since rules do not query for exemplars.
// This support is raised in https://github.com/prometheus/prometheus/issues/8798.
a.storage.exporter.Export(gaugeMetadata, a.samples, nil)
// After export is complete, we can clear the labels again.
a.storage.clearLabels(a.samples)
return nil
}