github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/storage/prometheus/prometheus_storage.go (about)

     1  // Copyright (c) 2020 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package prometheus
    22  
    23  import (
    24  	"context"
    25  	"sort"
    26  	"time"
    27  
    28  	"github.com/pkg/errors"
    29  	"github.com/prometheus/common/model"
    30  	"github.com/prometheus/prometheus/model/labels"
    31  	promstorage "github.com/prometheus/prometheus/storage"
    32  	"github.com/prometheus/prometheus/tsdb/chunkenc"
    33  	"github.com/uber-go/tally"
    34  	"go.uber.org/zap"
    35  
    36  	"github.com/m3db/m3/src/query/block"
    37  	"github.com/m3db/m3/src/query/generated/proto/prompb"
    38  	"github.com/m3db/m3/src/query/models"
    39  	"github.com/m3db/m3/src/query/parser/promql"
    40  	"github.com/m3db/m3/src/query/storage"
    41  	"github.com/m3db/m3/src/x/instrument"
    42  )
    43  
    44  type prometheusQueryable struct {
    45  	storage storage.Storage
    46  	scope   tally.Scope
    47  	logger  *zap.Logger
    48  }
    49  
    50  // PrometheusOptions are options to create a prometheus queryable backed by
    51  // a m3 storage.
    52  type PrometheusOptions struct {
    53  	Storage           storage.Storage
    54  	InstrumentOptions instrument.Options
    55  }
    56  
    57  // StorageErr wraps all errors returned by the storage layer.
    58  // This allows the http handlers that call the Prometheus library directly to distinguish prometheus library errors
    59  // and remote storage errors.
    60  type StorageErr struct {
    61  	inner error
    62  }
    63  
    64  // NewStorageErr wraps the provided error as a StorageErr.
    65  func NewStorageErr(err error) *StorageErr {
    66  	return &StorageErr{inner: err}
    67  }
    68  
    69  // Unwrap returns the underlying error.
    70  func (e *StorageErr) Unwrap() error {
    71  	return e.inner
    72  }
    73  
    74  // Error returns the error string for the underlying error.
    75  func (e *StorageErr) Error() string {
    76  	return e.inner.Error()
    77  }
    78  
    79  // NewPrometheusQueryable returns a new prometheus queryable backed by a m3
    80  // storage.
    81  func NewPrometheusQueryable(opts PrometheusOptions) promstorage.Queryable {
    82  	scope := opts.InstrumentOptions.MetricsScope().Tagged(map[string]string{"storage": "prometheus_storage"})
    83  	return &prometheusQueryable{
    84  		storage: opts.Storage,
    85  		scope:   scope,
    86  		logger:  opts.InstrumentOptions.Logger(),
    87  	}
    88  }
    89  
    90  // Querier returns a prometheus storage Querier.
    91  func (q *prometheusQueryable) Querier(
    92  	ctx context.Context, _, _ int64,
    93  ) (promstorage.Querier, error) {
    94  	return newQuerier(ctx, q.storage, q.logger), nil
    95  }
    96  
    97  type querier struct {
    98  	ctx     context.Context
    99  	storage storage.Storage
   100  	logger  *zap.Logger
   101  }
   102  
   103  func newQuerier(
   104  	ctx context.Context,
   105  	storage storage.Storage,
   106  	logger *zap.Logger,
   107  ) promstorage.Querier {
   108  	return &querier{
   109  		ctx:     ctx,
   110  		storage: storage,
   111  		logger:  logger,
   112  	}
   113  }
   114  
   115  func (q *querier) Select(
   116  	sortSeries bool,
   117  	hints *promstorage.SelectHints,
   118  	labelMatchers ...*labels.Matcher,
   119  ) promstorage.SeriesSet {
   120  	matchers, err := promql.LabelMatchersToModelMatcher(labelMatchers, models.NewTagOptions())
   121  	if err != nil {
   122  		return promstorage.ErrSeriesSet(err)
   123  	}
   124  
   125  	query := &storage.FetchQuery{
   126  		TagMatchers: matchers,
   127  		Start:       time.Unix(0, hints.Start*int64(time.Millisecond)),
   128  		End:         time.Unix(0, hints.End*int64(time.Millisecond)),
   129  		Interval:    time.Duration(hints.Step) * time.Millisecond,
   130  	}
   131  
   132  	// NB (@shreyas): The fetch options builder sets it up from the request
   133  	// which we do not have access to here.
   134  	fetchOptions, err := fetchOptions(q.ctx)
   135  	if err != nil {
   136  		q.logger.Error("fetch options not provided in context", zap.Error(err))
   137  		return promstorage.ErrSeriesSet(err)
   138  	}
   139  
   140  	result, err := q.storage.FetchProm(q.ctx, query, fetchOptions)
   141  	if err != nil {
   142  		return promstorage.ErrSeriesSet(NewStorageErr(err))
   143  	}
   144  	seriesSet := fromQueryResult(sortSeries, result.PromResult, result.Metadata)
   145  
   146  	receiveResultMetadataFn, err := resultMetadataReceiveFn(q.ctx)
   147  	if err != nil {
   148  		q.logger.Error("result metadata not set in context", zap.Error(err))
   149  		return promstorage.ErrSeriesSet(err)
   150  	}
   151  	if receiveResultMetadataFn == nil {
   152  		err := errors.New("result metadata receive function nil for context")
   153  		q.logger.Error(err.Error())
   154  		return promstorage.ErrSeriesSet(err)
   155  	}
   156  
   157  	// Pass the result.Metadata back using the receive function.
   158  	// This handles concurrent updates to a single result metadata.
   159  	receiveResultMetadataFn(result.Metadata)
   160  
   161  	return seriesSet
   162  }
   163  
   164  func (q *querier) LabelValues(string, ...*labels.Matcher) ([]string, promstorage.Warnings, error) {
   165  	// TODO (@shreyas): Implement this.
   166  	q.logger.Warn("calling unsupported LabelValues method")
   167  	return nil, nil, errors.New("not implemented")
   168  }
   169  
   170  func (q *querier) LabelNames(...*labels.Matcher) ([]string, promstorage.Warnings, error) {
   171  	// TODO (@shreyas): Implement this.
   172  	q.logger.Warn("calling unsupported LabelNames method")
   173  	return nil, nil, errors.New("not implemented")
   174  }
   175  
   176  func (q *querier) Close() error {
   177  	return nil
   178  }
   179  
   180  func fromWarningStrings(warnings []string) []error {
   181  	errs := make([]error, 0, len(warnings))
   182  	for _, warning := range warnings {
   183  		errs = append(errs, errors.New(warning))
   184  	}
   185  	return errs
   186  }
   187  
   188  // This is a copy of the prometheus remote.FromQueryResult method. Need to
   189  // copy so that this can understand m3 prompb struct.
   190  func fromQueryResult(sortSeries bool, res *prompb.QueryResult, metadata block.ResultMetadata) promstorage.SeriesSet {
   191  	series := make([]promstorage.Series, 0, len(res.Timeseries))
   192  	for _, ts := range res.Timeseries {
   193  		labels := labelProtosToLabels(ts.Labels)
   194  		if err := validateLabelsAndMetricName(labels); err != nil {
   195  			return promstorage.ErrSeriesSet(err)
   196  		}
   197  
   198  		series = append(series, &concreteSeries{
   199  			labels:  labels,
   200  			samples: ts.Samples,
   201  		})
   202  	}
   203  
   204  	if sortSeries {
   205  		sort.Sort(byLabel(series))
   206  	}
   207  
   208  	warnings := fromWarningStrings(metadata.WarningStrings())
   209  
   210  	return &concreteSeriesSet{
   211  		series:   series,
   212  		warnings: warnings,
   213  	}
   214  }
   215  
   216  type byLabel []promstorage.Series
   217  
   218  func (a byLabel) Len() int           { return len(a) }
   219  func (a byLabel) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
   220  func (a byLabel) Less(i, j int) bool { return labels.Compare(a[i].Labels(), a[j].Labels()) < 0 }
   221  
   222  func labelProtosToLabels(labelPairs []prompb.Label) labels.Labels {
   223  	result := make(labels.Labels, 0, len(labelPairs))
   224  	for _, l := range labelPairs {
   225  		result = append(result, labels.Label{
   226  			Name:  string(l.Name),
   227  			Value: string(l.Value),
   228  		})
   229  	}
   230  	sort.Sort(result)
   231  	return result
   232  }
   233  
   234  // errSeriesSet implements storage.SeriesSet, just returning an error.
   235  type errSeriesSet struct {
   236  	err error
   237  }
   238  
   239  func (errSeriesSet) Next() bool {
   240  	return false
   241  }
   242  
   243  func (errSeriesSet) At() promstorage.Series {
   244  	return nil
   245  }
   246  
   247  func (e errSeriesSet) Err() error {
   248  	return e.err
   249  }
   250  
   251  // concreteSeriesSet implements storage.SeriesSet.
   252  type concreteSeriesSet struct {
   253  	cur      int
   254  	series   []promstorage.Series
   255  	warnings promstorage.Warnings
   256  }
   257  
   258  func (c *concreteSeriesSet) Next() bool {
   259  	c.cur++
   260  	return c.cur-1 < len(c.series)
   261  }
   262  
   263  func (c *concreteSeriesSet) At() promstorage.Series {
   264  	return c.series[c.cur-1]
   265  }
   266  
   267  func (c *concreteSeriesSet) Err() error {
   268  	return nil
   269  }
   270  
   271  func (c *concreteSeriesSet) Warnings() promstorage.Warnings {
   272  	return c.warnings
   273  }
   274  
   275  // concreteSeries implements storage.Series.
   276  type concreteSeries struct {
   277  	labels  labels.Labels
   278  	samples []prompb.Sample
   279  }
   280  
   281  func (c *concreteSeries) Labels() labels.Labels {
   282  	return labels.New(c.labels...)
   283  }
   284  
   285  func (c *concreteSeries) Iterator() chunkenc.Iterator {
   286  	return newConcreteSeriersIterator(c)
   287  }
   288  
   289  // concreteSeriesIterator implements storage.SeriesIterator.
   290  type concreteSeriesIterator struct {
   291  	cur    int
   292  	series *concreteSeries
   293  }
   294  
   295  func newConcreteSeriersIterator(series *concreteSeries) chunkenc.Iterator {
   296  	return &concreteSeriesIterator{
   297  		cur:    -1,
   298  		series: series,
   299  	}
   300  }
   301  
   302  // Seek implements storage.SeriesIterator.
   303  func (c *concreteSeriesIterator) Seek(t int64) bool {
   304  	c.cur = sort.Search(len(c.series.samples), func(n int) bool {
   305  		return c.series.samples[n].Timestamp >= t
   306  	})
   307  	return c.cur < len(c.series.samples)
   308  }
   309  
   310  // At implements storage.SeriesIterator.
   311  func (c *concreteSeriesIterator) At() (t int64, v float64) {
   312  	s := c.series.samples[c.cur]
   313  	return s.Timestamp, s.Value
   314  }
   315  
   316  // Next implements storage.SeriesIterator.
   317  func (c *concreteSeriesIterator) Next() bool {
   318  	c.cur++
   319  	return c.cur < len(c.series.samples)
   320  }
   321  
   322  // Err implements storage.SeriesIterator.
   323  func (c *concreteSeriesIterator) Err() error {
   324  	return nil
   325  }
   326  
   327  // validateLabelsAndMetricName validates the label names/values and metric names returned from remote read,
   328  // also making sure that there are no labels with duplicate names
   329  func validateLabelsAndMetricName(ls labels.Labels) error {
   330  	for i, l := range ls {
   331  		if l.Name == labels.MetricName && !model.IsValidMetricName(model.LabelValue(l.Value)) {
   332  			return errors.Errorf("invalid metric name: %v", l.Value)
   333  		}
   334  		if !model.LabelName(l.Name).IsValid() {
   335  			return errors.Errorf("invalid label name: %v", l.Name)
   336  		}
   337  		if !model.LabelValue(l.Value).IsValid() {
   338  			return errors.Errorf("invalid label value: %v", l.Value)
   339  		}
   340  		if i > 0 && l.Name == ls[i-1].Name {
   341  			return errors.Errorf("duplicate label with name: %v", l.Name)
   342  		}
   343  	}
   344  	return nil
   345  }