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

     1  // Copyright (c) 2018 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 storage
    22  
    23  import (
    24  	"bytes"
    25  	"fmt"
    26  	"sort"
    27  	"time"
    28  
    29  	"github.com/m3db/m3/src/dbnode/generated/proto/annotation"
    30  	"github.com/m3db/m3/src/query/generated/proto/prompb"
    31  	"github.com/m3db/m3/src/query/models"
    32  	"github.com/m3db/m3/src/query/ts"
    33  	xtime "github.com/m3db/m3/src/x/time"
    34  
    35  	"github.com/prometheus/common/model"
    36  )
    37  
    38  var (
    39  	// The default name for the name and bucket tags in Prometheus metrics.
    40  	// This can be overwritten by setting tagOptions in the config.
    41  	promDefaultName       = []byte(model.MetricNameLabel) // __name__
    42  	promDefaultBucketName = []byte(model.BucketLabel)     // le
    43  
    44  	// The suffix of count metric name in Prometheus histogram/summary metric families.
    45  	promCountSuffix = []byte("_count")
    46  	// The suffix of sum metric name in Prometheus histogram/summary metric families.
    47  	promSumSuffix = []byte("_sum")
    48  
    49  	// The suffix of gauge count metric name in Open Metrics GaugeHistogram metric families.
    50  	openMetricsGaugeCountSuffix = []byte("_gcount")
    51  	// The suffix of count metric name in Open Metrics Summary metric families.
    52  	openMetricsCountSuffix = []byte("_count")
    53  	// The suffix of count metric name in Open Metrics Summary metric families.
    54  	openMetricsSumSuffix = []byte("_sum")
    55  	// The suffix of created metric name in Open Metrics Counter/Histogram/Summary metric families.
    56  	openMetricsCreatedSuffix = []byte("_created")
    57  )
    58  
    59  // PromLabelsToM3Tags converts Prometheus labels to M3 tags
    60  func PromLabelsToM3Tags(
    61  	labels []prompb.Label,
    62  	tagOptions models.TagOptions,
    63  ) models.Tags {
    64  	tags := models.NewTags(len(labels), tagOptions)
    65  	tagList := make([]models.Tag, 0, len(labels))
    66  	for _, label := range labels {
    67  		name := label.Name
    68  		// If this label corresponds to the Prometheus name or bucket name,
    69  		// instead set it as the given name tag from the config file.
    70  		if bytes.Equal(promDefaultName, name) {
    71  			tags = tags.SetName(label.Value)
    72  		} else if bytes.Equal(promDefaultBucketName, name) {
    73  			tags = tags.SetBucket(label.Value)
    74  		} else {
    75  			tagList = append(tagList, models.Tag{
    76  				Name:  name,
    77  				Value: label.Value,
    78  			})
    79  		}
    80  	}
    81  
    82  	return tags.AddTags(tagList)
    83  }
    84  
    85  // PromTimeSeriesToSeriesAttributes extracts the series info from a prometheus
    86  // timeseries.
    87  func PromTimeSeriesToSeriesAttributes(series prompb.TimeSeries) (ts.SeriesAttributes, error) {
    88  	switch series.Source {
    89  	case prompb.Source_PROMETHEUS:
    90  		return seriesAttributesForPrometheusSource(series)
    91  
    92  	case prompb.Source_OPEN_METRICS:
    93  		return seriesAttributesForOpenMetricsSource(series)
    94  
    95  	case prompb.Source_GRAPHITE:
    96  		return seriesAttributesForGraphiteSource(series)
    97  
    98  	default:
    99  		return ts.SeriesAttributes{}, fmt.Errorf("invalid source type %s", series.Source)
   100  	}
   101  }
   102  
   103  func seriesAttributesForPrometheusSource(series prompb.TimeSeries) (ts.SeriesAttributes, error) {
   104  	var (
   105  		promMetricType    ts.PromMetricType
   106  		handleValueResets bool
   107  	)
   108  
   109  	switch series.Type {
   110  	case prompb.MetricType_UNKNOWN:
   111  		promMetricType = ts.PromMetricTypeUnknown
   112  
   113  	case prompb.MetricType_COUNTER:
   114  		promMetricType = ts.PromMetricTypeCounter
   115  		handleValueResets = true
   116  
   117  	case prompb.MetricType_GAUGE:
   118  		promMetricType = ts.PromMetricTypeGauge
   119  
   120  	case prompb.MetricType_HISTOGRAM:
   121  		promMetricType = ts.PromMetricTypeHistogram
   122  		handleValueResets = true
   123  
   124  	case prompb.MetricType_GAUGE_HISTOGRAM:
   125  		promMetricType = ts.PromMetricTypeGaugeHistogram
   126  		name := metricNameFromLabels(series.Labels)
   127  		handleValueResets = bytes.HasSuffix(name, promCountSuffix) ||
   128  			bytes.HasSuffix(name, openMetricsGaugeCountSuffix)
   129  
   130  	case prompb.MetricType_SUMMARY:
   131  		promMetricType = ts.PromMetricTypeSummary
   132  		name := metricNameFromLabels(series.Labels)
   133  		handleValueResets = bytes.HasSuffix(name, promCountSuffix) ||
   134  			bytes.HasSuffix(name, promSumSuffix)
   135  
   136  	case prompb.MetricType_INFO:
   137  		promMetricType = ts.PromMetricTypeInfo
   138  
   139  	case prompb.MetricType_STATESET:
   140  		promMetricType = ts.PromMetricTypeStateSet
   141  
   142  	default:
   143  		return ts.SeriesAttributes{}, fmt.Errorf("invalid metric type for Prometheus: %s", series.Type)
   144  	}
   145  
   146  	m3MetricType, err := convertM3Type(series.M3Type)
   147  	if err != nil {
   148  		return ts.SeriesAttributes{}, err
   149  	}
   150  
   151  	return ts.SeriesAttributes{
   152  		Source:            ts.SourceTypePrometheus,
   153  		M3Type:            m3MetricType,
   154  		PromType:          promMetricType,
   155  		HandleValueResets: handleValueResets,
   156  	}, nil
   157  }
   158  
   159  func seriesAttributesForOpenMetricsSource(series prompb.TimeSeries) (ts.SeriesAttributes, error) {
   160  	var (
   161  		promMetricType    ts.PromMetricType
   162  		handleValueResets bool
   163  	)
   164  
   165  	// https://github.com/OpenObservability/OpenMetrics/blob/2bd6413e040/specification/OpenMetrics.md
   166  	switch series.Type {
   167  	case prompb.MetricType_UNKNOWN:
   168  		promMetricType = ts.PromMetricTypeUnknown
   169  
   170  	case prompb.MetricType_COUNTER:
   171  		promMetricType = ts.PromMetricTypeCounter
   172  		name := metricNameFromLabels(series.Labels)
   173  		handleValueResets = !bytes.HasSuffix(name, openMetricsCreatedSuffix)
   174  
   175  	case prompb.MetricType_GAUGE:
   176  		promMetricType = ts.PromMetricTypeGauge
   177  
   178  	case prompb.MetricType_HISTOGRAM:
   179  		promMetricType = ts.PromMetricTypeHistogram
   180  		name := metricNameFromLabels(series.Labels)
   181  		handleValueResets = !bytes.HasSuffix(name, openMetricsCreatedSuffix)
   182  
   183  	case prompb.MetricType_GAUGE_HISTOGRAM:
   184  		promMetricType = ts.PromMetricTypeGaugeHistogram
   185  		name := metricNameFromLabels(series.Labels)
   186  		handleValueResets = bytes.HasSuffix(name, openMetricsGaugeCountSuffix)
   187  
   188  	case prompb.MetricType_SUMMARY:
   189  		promMetricType = ts.PromMetricTypeSummary
   190  		name := metricNameFromLabels(series.Labels)
   191  		handleValueResets = bytes.HasSuffix(name, openMetricsCountSuffix) ||
   192  			bytes.HasSuffix(name, openMetricsSumSuffix)
   193  
   194  	case prompb.MetricType_INFO:
   195  		promMetricType = ts.PromMetricTypeInfo
   196  
   197  	case prompb.MetricType_STATESET:
   198  		promMetricType = ts.PromMetricTypeStateSet
   199  
   200  	default:
   201  		return ts.SeriesAttributes{}, fmt.Errorf("invalid metric type for Open Metrics: %s", series.Type)
   202  	}
   203  
   204  	m3MetricType, err := convertM3Type(series.M3Type)
   205  	if err != nil {
   206  		return ts.SeriesAttributes{}, err
   207  	}
   208  
   209  	return ts.SeriesAttributes{
   210  		Source:            ts.SourceTypeOpenMetrics,
   211  		PromType:          promMetricType,
   212  		M3Type:            m3MetricType,
   213  		HandleValueResets: handleValueResets,
   214  	}, nil
   215  }
   216  
   217  func seriesAttributesForGraphiteSource(series prompb.TimeSeries) (ts.SeriesAttributes, error) {
   218  	m3MetricType, err := convertM3Type(series.M3Type)
   219  	if err != nil {
   220  		return ts.SeriesAttributes{}, err
   221  	}
   222  
   223  	var promMetricType ts.PromMetricType
   224  	switch series.M3Type {
   225  	case prompb.M3Type_M3_COUNTER:
   226  		promMetricType = ts.PromMetricTypeCounter
   227  	case prompb.M3Type_M3_GAUGE:
   228  		promMetricType = ts.PromMetricTypeGauge
   229  	case prompb.M3Type_M3_TIMER:
   230  		promMetricType = ts.PromMetricTypeUnknown
   231  	}
   232  
   233  	return ts.SeriesAttributes{
   234  		Source:            ts.SourceTypeGraphite,
   235  		M3Type:            m3MetricType,
   236  		PromType:          promMetricType,
   237  		HandleValueResets: false,
   238  	}, nil
   239  }
   240  
   241  func convertM3Type(m3Type prompb.M3Type) (ts.M3MetricType, error) {
   242  	switch m3Type {
   243  	case prompb.M3Type_M3_GAUGE:
   244  		return ts.M3MetricTypeGauge, nil
   245  
   246  	case prompb.M3Type_M3_COUNTER:
   247  		return ts.M3MetricTypeCounter, nil
   248  
   249  	case prompb.M3Type_M3_TIMER:
   250  		return ts.M3MetricTypeTimer, nil
   251  
   252  	default:
   253  		return 0, fmt.Errorf("invalid M3 metric type: %s", m3Type)
   254  	}
   255  }
   256  
   257  var (
   258  	promMetricTypeToProto = map[ts.PromMetricType]annotation.OpenMetricsFamilyType{
   259  		ts.PromMetricTypeUnknown:        annotation.OpenMetricsFamilyType_UNKNOWN,
   260  		ts.PromMetricTypeCounter:        annotation.OpenMetricsFamilyType_COUNTER,
   261  		ts.PromMetricTypeGauge:          annotation.OpenMetricsFamilyType_GAUGE,
   262  		ts.PromMetricTypeHistogram:      annotation.OpenMetricsFamilyType_HISTOGRAM,
   263  		ts.PromMetricTypeGaugeHistogram: annotation.OpenMetricsFamilyType_GAUGE_HISTOGRAM,
   264  		ts.PromMetricTypeSummary:        annotation.OpenMetricsFamilyType_SUMMARY,
   265  		ts.PromMetricTypeInfo:           annotation.OpenMetricsFamilyType_INFO,
   266  		ts.PromMetricTypeStateSet:       annotation.OpenMetricsFamilyType_STATESET,
   267  	}
   268  
   269  	graphiteMetricTypeToProto = map[ts.M3MetricType]annotation.GraphiteType{
   270  		ts.M3MetricTypeGauge:   annotation.GraphiteType_GRAPHITE_GAUGE,
   271  		ts.M3MetricTypeCounter: annotation.GraphiteType_GRAPHITE_COUNTER,
   272  		ts.M3MetricTypeTimer:   annotation.GraphiteType_GRAPHITE_TIMER,
   273  	}
   274  )
   275  
   276  // SeriesAttributesToAnnotationPayload converts ts.SeriesAttributes into an annotation.Payload.
   277  func SeriesAttributesToAnnotationPayload(seriesAttributes ts.SeriesAttributes) (annotation.Payload, error) {
   278  	if seriesAttributes.Source == ts.SourceTypeGraphite {
   279  		metricType, ok := graphiteMetricTypeToProto[seriesAttributes.M3Type]
   280  		if !ok {
   281  			return annotation.Payload{}, fmt.Errorf(
   282  				"invalid Graphite metric type %d", seriesAttributes.M3Type)
   283  		}
   284  
   285  		return annotation.Payload{
   286  			SourceFormat: annotation.SourceFormat_GRAPHITE,
   287  			GraphiteType: metricType,
   288  		}, nil
   289  	}
   290  
   291  	metricType, ok := promMetricTypeToProto[seriesAttributes.PromType]
   292  	if !ok {
   293  		return annotation.Payload{}, fmt.Errorf(
   294  			"invalid Prometheus metric type %d", seriesAttributes.PromType)
   295  	}
   296  
   297  	return annotation.Payload{
   298  		SourceFormat:                 annotation.SourceFormat_OPEN_METRICS,
   299  		OpenMetricsFamilyType:        metricType,
   300  		OpenMetricsHandleValueResets: seriesAttributes.HandleValueResets,
   301  	}, nil
   302  }
   303  
   304  // PromSamplesToM3Datapoints converts Prometheus samples to M3 datapoints
   305  func PromSamplesToM3Datapoints(samples []prompb.Sample) ts.Datapoints {
   306  	datapoints := make(ts.Datapoints, 0, len(samples))
   307  	for _, sample := range samples {
   308  		timestamp := promTimestampToUnixNanos(sample.Timestamp)
   309  		datapoints = append(datapoints, ts.Datapoint{Timestamp: timestamp, Value: sample.Value})
   310  	}
   311  
   312  	return datapoints
   313  }
   314  
   315  // PromReadQueryToM3 converts a prometheus read query to m3 read query
   316  func PromReadQueryToM3(query *prompb.Query) (*FetchQuery, error) {
   317  	tagMatchers, err := PromMatchersToM3(query.Matchers)
   318  	if err != nil {
   319  		return nil, err
   320  	}
   321  
   322  	start := PromTimestampToTime(query.StartTimestampMs)
   323  	end := PromTimestampToTime(query.EndTimestampMs)
   324  	if start.After(end) {
   325  		start = time.Time{}
   326  		end = time.Now()
   327  	}
   328  
   329  	return &FetchQuery{
   330  		TagMatchers: tagMatchers,
   331  		Start:       start,
   332  		End:         end,
   333  	}, nil
   334  }
   335  
   336  // PromMatchersToM3 converts prometheus label matchers to m3 matchers
   337  func PromMatchersToM3(matchers []*prompb.LabelMatcher) (models.Matchers, error) {
   338  	tagMatchers := make(models.Matchers, len(matchers))
   339  	var err error
   340  	for idx, matcher := range matchers {
   341  		tagMatchers[idx], err = PromMatcherToM3(matcher)
   342  		if err != nil {
   343  			return nil, err
   344  		}
   345  	}
   346  
   347  	return tagMatchers, nil
   348  }
   349  
   350  // PromMatcherToM3 converts a prometheus label matcher to m3 matcher
   351  func PromMatcherToM3(matcher *prompb.LabelMatcher) (models.Matcher, error) {
   352  	matchType, err := PromTypeToM3(matcher.Type)
   353  	if err != nil {
   354  		return models.Matcher{}, err
   355  	}
   356  
   357  	return models.NewMatcher(matchType, matcher.Name, matcher.Value)
   358  }
   359  
   360  // PromTypeToM3 converts a prometheus label type to m3 matcher type
   361  func PromTypeToM3(labelType prompb.LabelMatcher_Type) (models.MatchType, error) {
   362  	switch labelType {
   363  	case prompb.LabelMatcher_EQ:
   364  		return models.MatchEqual, nil
   365  	case prompb.LabelMatcher_NEQ:
   366  		return models.MatchNotEqual, nil
   367  	case prompb.LabelMatcher_RE:
   368  		return models.MatchRegexp, nil
   369  	case prompb.LabelMatcher_NRE:
   370  		return models.MatchNotRegexp, nil
   371  
   372  	default:
   373  		return 0, fmt.Errorf("unknown match type: %v", labelType)
   374  	}
   375  }
   376  
   377  // PromTimestampToTime converts a prometheus timestamp to time.Time.
   378  func PromTimestampToTime(timestampMS int64) time.Time {
   379  	return promTimestampToUnixNanos(timestampMS).ToTime()
   380  }
   381  
   382  func promTimestampToUnixNanos(timestampMS int64) xtime.UnixNano {
   383  	// NB: prometheus format is in milliseconds; convert to unix nanos.
   384  	return xtime.UnixNano(timestampMS * int64(time.Millisecond))
   385  }
   386  
   387  // TimeToPromTimestamp converts a xtime.UnixNano to prometheus timestamp.
   388  func TimeToPromTimestamp(timestamp xtime.UnixNano) int64 {
   389  	// Significantly faster than time.Truncate()
   390  	return int64(timestamp) / int64(time.Millisecond)
   391  }
   392  
   393  // FetchResultToPromResult converts fetch results from M3 to Prometheus result.
   394  func FetchResultToPromResult(
   395  	result *FetchResult,
   396  	keepEmpty bool,
   397  ) *prompb.QueryResult {
   398  	// Perform bulk allocation upfront then convert to pointers afterwards
   399  	// to reduce total number of allocations. See BenchmarkFetchResultToPromResult
   400  	// if modifying.
   401  	timeseries := make([]prompb.TimeSeries, 0, len(result.SeriesList))
   402  	for _, series := range result.SeriesList {
   403  		if !keepEmpty && series.Len() == 0 {
   404  			continue
   405  		}
   406  
   407  		promTs := SeriesToPromTS(series)
   408  		timeseries = append(timeseries, promTs)
   409  	}
   410  
   411  	timeSeriesPointers := make([]*prompb.TimeSeries, 0, len(result.SeriesList))
   412  	for i := range timeseries {
   413  		timeSeriesPointers = append(timeSeriesPointers, &timeseries[i])
   414  	}
   415  
   416  	return &prompb.QueryResult{
   417  		Timeseries: timeSeriesPointers,
   418  	}
   419  }
   420  
   421  // SeriesToPromTS converts a series to prometheus timeseries.
   422  func SeriesToPromTS(series *ts.Series) prompb.TimeSeries {
   423  	labels := TagsToPromLabels(series.Tags)
   424  	samples := SeriesToPromSamples(series)
   425  	return prompb.TimeSeries{Labels: labels, Samples: samples}
   426  }
   427  
   428  type sortableLabels []prompb.Label
   429  
   430  func (t sortableLabels) Len() int      { return len(t) }
   431  func (t sortableLabels) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
   432  func (t sortableLabels) Less(i, j int) bool {
   433  	return bytes.Compare(t[i].Name, t[j].Name) == -1
   434  }
   435  
   436  // TagsToPromLabels converts tags to prometheus labels.
   437  func TagsToPromLabels(tags models.Tags) []prompb.Label {
   438  	l := tags.Len()
   439  	labels := make([]prompb.Label, 0, l)
   440  
   441  	metricName := tags.Opts.MetricName()
   442  	bucketName := tags.Opts.BucketName()
   443  	for _, t := range tags.Tags {
   444  		if bytes.Equal(t.Name, metricName) {
   445  			labels = append(labels,
   446  				prompb.Label{Name: promDefaultName, Value: t.Value})
   447  		} else if bytes.Equal(t.Name, bucketName) {
   448  			labels = append(labels,
   449  				prompb.Label{Name: promDefaultBucketName, Value: t.Value})
   450  		} else {
   451  			labels = append(labels, prompb.Label{Name: t.Name, Value: t.Value})
   452  		}
   453  	}
   454  
   455  	// Sort here since name and label may be added in a different order in tags
   456  	// if default metric name or bucket names are overridden.
   457  	sort.Sort(sortableLabels(labels))
   458  
   459  	return labels
   460  }
   461  
   462  // SeriesToPromSamples series datapoints to prometheus samples.SeriesToPromSamples.
   463  func SeriesToPromSamples(series *ts.Series) []prompb.Sample {
   464  	var (
   465  		seriesLen  = series.Len()
   466  		values     = series.Values()
   467  		datapoints = values.Datapoints()
   468  		samples    = make([]prompb.Sample, 0, seriesLen)
   469  	)
   470  	for _, dp := range datapoints {
   471  		samples = append(samples, prompb.Sample{
   472  			Timestamp: TimeToPromTimestamp(dp.Timestamp),
   473  			Value:     dp.Value,
   474  		})
   475  	}
   476  
   477  	return samples
   478  }
   479  
   480  func metricNameFromLabels(labels []prompb.Label) []byte {
   481  	for _, label := range labels {
   482  		if bytes.Equal(promDefaultName, label.GetName()) {
   483  			return label.GetValue()
   484  		}
   485  	}
   486  	return nil
   487  }