github.com/m3db/m3@v1.5.0/src/query/storage/converter_test.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  	"fmt"
    25  	"math"
    26  	"testing"
    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  	xtest "github.com/m3db/m3/src/x/test"
    34  	xtime "github.com/m3db/m3/src/x/time"
    35  
    36  	"github.com/stretchr/testify/assert"
    37  	"github.com/stretchr/testify/require"
    38  )
    39  
    40  func TestLabelConversion(t *testing.T) {
    41  	// NB: sorted order (__name__, foo, le)
    42  	labels := []prompb.Label{
    43  		{Name: promDefaultName, Value: []byte("name-val")},
    44  		{Name: []byte("foo"), Value: []byte("bar")},
    45  		{Name: promDefaultBucketName, Value: []byte("bucket-val")},
    46  	}
    47  
    48  	opts := models.NewTagOptions().
    49  		SetMetricName([]byte("name")).
    50  		SetBucketName([]byte("bucket"))
    51  
    52  	tags := PromLabelsToM3Tags(labels, opts)
    53  	name, found := tags.Name()
    54  	assert.True(t, found)
    55  	assert.Equal(t, []byte("name-val"), name)
    56  	name, found = tags.Get([]byte("name"))
    57  	assert.True(t, found)
    58  	assert.Equal(t, []byte("name-val"), name)
    59  
    60  	bucket, found := tags.Bucket()
    61  	assert.True(t, found)
    62  	assert.Equal(t, []byte("bucket-val"), bucket)
    63  	bucket, found = tags.Get([]byte("bucket"))
    64  	assert.True(t, found)
    65  	assert.Equal(t, []byte("bucket-val"), bucket)
    66  
    67  	reverted := TagsToPromLabels(tags)
    68  	assert.Equal(t, labels, reverted)
    69  }
    70  
    71  var (
    72  	name  = []byte("foo")
    73  	value = []byte("bar")
    74  )
    75  
    76  func TestPromReadQueryToM3BadStartEnd(t *testing.T) {
    77  	q, err := PromReadQueryToM3(&prompb.Query{
    78  		StartTimestampMs: 100,
    79  		EndTimestampMs:   -100,
    80  	})
    81  
    82  	require.NoError(t, err)
    83  	assert.Equal(t, time.Time{}, q.Start)
    84  	// NB: check end is approximately correct.
    85  	diff := math.Abs(float64(time.Since(q.End)))
    86  	assert.True(t, diff < float64(time.Minute))
    87  }
    88  
    89  func TestPromReadQueryToM3(t *testing.T) {
    90  	tests := []struct {
    91  		name        string
    92  		matchers    []*prompb.LabelMatcher
    93  		expected    []*models.Matcher
    94  		expectError bool
    95  	}{
    96  		{
    97  			name: "single exact match",
    98  			matchers: []*prompb.LabelMatcher{
    99  				{Type: prompb.LabelMatcher_EQ, Name: name, Value: value},
   100  			},
   101  			expected: []*models.Matcher{
   102  				{Type: models.MatchEqual, Name: name, Value: value},
   103  			},
   104  		},
   105  		{
   106  			name: "single exact match negated",
   107  			matchers: []*prompb.LabelMatcher{
   108  				{Type: prompb.LabelMatcher_NEQ, Name: name, Value: value},
   109  			},
   110  			expected: []*models.Matcher{
   111  				{Type: models.MatchNotEqual, Name: name, Value: value},
   112  			},
   113  		},
   114  		{
   115  			name: "single regexp match",
   116  			matchers: []*prompb.LabelMatcher{
   117  				{Type: prompb.LabelMatcher_RE, Name: name, Value: value},
   118  			},
   119  			expected: []*models.Matcher{
   120  				{Type: models.MatchRegexp, Name: name, Value: value},
   121  			},
   122  		},
   123  		{
   124  			name: "single regexp match negated",
   125  			matchers: []*prompb.LabelMatcher{
   126  				{Type: prompb.LabelMatcher_NRE, Name: name, Value: value},
   127  			},
   128  			expected: []*models.Matcher{
   129  				{Type: models.MatchNotRegexp, Name: name, Value: value},
   130  			},
   131  		},
   132  		{
   133  			name: "mixed exact match and regexp match",
   134  			matchers: []*prompb.LabelMatcher{
   135  				{Type: prompb.LabelMatcher_EQ, Name: name, Value: value},
   136  				{Type: prompb.LabelMatcher_RE, Name: []byte("baz"), Value: []byte("qux")},
   137  			},
   138  			expected: []*models.Matcher{
   139  				{Type: models.MatchEqual, Name: name, Value: value},
   140  				{Type: models.MatchRegexp, Name: []byte("baz"), Value: []byte("qux")},
   141  			},
   142  		},
   143  		{
   144  			name: "unrecognized matcher type",
   145  			matchers: []*prompb.LabelMatcher{
   146  				{Type: prompb.LabelMatcher_Type(math.MaxInt32), Name: name, Value: value},
   147  			},
   148  			expectError: true,
   149  		},
   150  	}
   151  
   152  	for _, test := range tests {
   153  		t.Run(test.name, func(t *testing.T) {
   154  			input := &prompb.Query{
   155  				StartTimestampMs: 123000,
   156  				EndTimestampMs:   456000,
   157  				Matchers:         test.matchers,
   158  			}
   159  			result, err := PromReadQueryToM3(input)
   160  			if test.expectError {
   161  				assert.Error(t, err)
   162  			} else {
   163  				assert.True(t, result.Start.Equal(time.Unix(123, 0)), "start does not match")
   164  				assert.True(t, result.End.Equal(time.Unix(456, 0)), "end does not match")
   165  				require.Equal(t, len(test.expected), len(result.TagMatchers),
   166  					"tag matchers length not match")
   167  				for i, expected := range test.expected {
   168  					expectedStr := expected.String()
   169  					actualStr := result.TagMatchers[i].String()
   170  					assert.Equal(t, expectedStr, actualStr,
   171  						fmt.Sprintf("matcher does not match: idx=%d, expected=%s, actual=%s",
   172  							i, expectedStr, actualStr))
   173  				}
   174  			}
   175  		})
   176  	}
   177  }
   178  
   179  var (
   180  	benchResult *prompb.QueryResult
   181  )
   182  
   183  func TestFetchResultToPromResult(t *testing.T) {
   184  	ctrl := xtest.NewController(t)
   185  	defer ctrl.Finish()
   186  
   187  	now := xtime.Now()
   188  	promNow := TimeToPromTimestamp(now)
   189  
   190  	vals := ts.NewMockValues(ctrl)
   191  	vals.EXPECT().Len().Return(0).Times(2)
   192  	vals.EXPECT().Datapoints().Return(ts.Datapoints{})
   193  
   194  	tags := models.NewTags(1, models.NewTagOptions()).
   195  		AddTag(models.Tag{Name: []byte("a"), Value: []byte("b")})
   196  
   197  	valsNonEmpty := ts.NewMockValues(ctrl)
   198  	valsNonEmpty.EXPECT().Len().Return(1).Times(3)
   199  	dp := ts.Datapoints{{Timestamp: now, Value: 1}}
   200  	valsNonEmpty.EXPECT().Datapoints().Return(dp).Times(2)
   201  	tagsNonEmpty := models.NewTags(1, models.NewTagOptions()).
   202  		AddTag(models.Tag{Name: []byte("c"), Value: []byte("d")})
   203  
   204  	r := &FetchResult{
   205  		SeriesList: ts.SeriesList{
   206  			ts.NewSeries([]byte("a"), vals, tags),
   207  			ts.NewSeries([]byte("c"), valsNonEmpty, tagsNonEmpty),
   208  		},
   209  	}
   210  
   211  	// NB: not keeping empty series.
   212  	result := FetchResultToPromResult(r, false)
   213  	expected := &prompb.QueryResult{
   214  		Timeseries: []*prompb.TimeSeries{
   215  			{
   216  				Labels:  []prompb.Label{{Name: []byte("c"), Value: []byte("d")}},
   217  				Samples: []prompb.Sample{{Timestamp: promNow, Value: 1}},
   218  			},
   219  		},
   220  	}
   221  
   222  	assert.Equal(t, expected, result)
   223  
   224  	// NB: keeping empty series.
   225  	result = FetchResultToPromResult(r, true)
   226  	expected = &prompb.QueryResult{
   227  		Timeseries: []*prompb.TimeSeries{
   228  			{
   229  				Labels:  []prompb.Label{{Name: []byte("a"), Value: []byte("b")}},
   230  				Samples: []prompb.Sample{},
   231  			},
   232  			{
   233  				Labels:  []prompb.Label{{Name: []byte("c"), Value: []byte("d")}},
   234  				Samples: []prompb.Sample{{Timestamp: promNow, Value: 1}},
   235  			},
   236  		},
   237  	}
   238  
   239  	assert.Equal(t, expected, result)
   240  }
   241  
   242  // BenchmarkFetchResultToPromResult-8   	     100	  10563444 ns/op	25368543 B/op	    4443 allocs/op
   243  func BenchmarkFetchResultToPromResult(b *testing.B) {
   244  	var (
   245  		numSeries              = 1000
   246  		numDatapointsPerSeries = 1000
   247  		numTagsPerSeries       = 10
   248  		fr                     = &FetchResult{
   249  			SeriesList: make(ts.SeriesList, 0, numSeries),
   250  		}
   251  	)
   252  
   253  	for i := 0; i < numSeries; i++ {
   254  		values := make(ts.Datapoints, 0, numDatapointsPerSeries)
   255  		for i := 0; i < numDatapointsPerSeries; i++ {
   256  			values = append(values, ts.Datapoint{
   257  				Timestamp: 0,
   258  				Value:     float64(i),
   259  			})
   260  		}
   261  
   262  		tags := models.NewTags(numTagsPerSeries, nil)
   263  		for i := 0; i < numTagsPerSeries; i++ {
   264  			tags = tags.AddTag(models.Tag{
   265  				Name:  []byte(fmt.Sprintf("name-%d", i)),
   266  				Value: []byte(fmt.Sprintf("value-%d", i)),
   267  			})
   268  		}
   269  
   270  		series := ts.NewSeries(
   271  			[]byte(fmt.Sprintf("series-%d", i)), values, tags)
   272  
   273  		fr.SeriesList = append(fr.SeriesList, series)
   274  	}
   275  
   276  	for i := 0; i < b.N; i++ {
   277  		benchResult = FetchResultToPromResult(fr, false)
   278  	}
   279  }
   280  
   281  func TestPromTimeSeriesToSeriesAttributesSource(t *testing.T) {
   282  	attrs, err := PromTimeSeriesToSeriesAttributes(prompb.TimeSeries{})
   283  	require.NoError(t, err)
   284  	assert.Equal(t, ts.SourceTypePrometheus, attrs.Source)
   285  
   286  	attrs, err = PromTimeSeriesToSeriesAttributes(prompb.TimeSeries{Source: prompb.Source_PROMETHEUS})
   287  	require.NoError(t, err)
   288  	assert.Equal(t, ts.SourceTypePrometheus, attrs.Source)
   289  
   290  	attrs, err = PromTimeSeriesToSeriesAttributes(prompb.TimeSeries{Source: prompb.Source_OPEN_METRICS})
   291  	require.NoError(t, err)
   292  	assert.Equal(t, ts.SourceTypeOpenMetrics, attrs.Source)
   293  
   294  	attrs, err = PromTimeSeriesToSeriesAttributes(prompb.TimeSeries{Source: prompb.Source_GRAPHITE})
   295  	require.NoError(t, err)
   296  	assert.Equal(t, ts.SourceTypeGraphite, attrs.Source)
   297  
   298  	attrs, err = PromTimeSeriesToSeriesAttributes(prompb.TimeSeries{Source: -1})
   299  	require.Error(t, err)
   300  }
   301  
   302  func TestPromTimeSeriesToSeriesAttributesPromMetricsTypeFromPrometheus(t *testing.T) {
   303  	mapping := map[prompbMetricTypeWithNameSuffix]promMetricTypeWithBool{
   304  		{metricType: prompb.MetricType_UNKNOWN}:  {metricType: ts.PromMetricTypeUnknown},
   305  		{metricType: prompb.MetricType_COUNTER}:  {metricType: ts.PromMetricTypeCounter, handleValueResets: true},
   306  		{metricType: prompb.MetricType_GAUGE}:    {metricType: ts.PromMetricTypeGauge},
   307  		{metricType: prompb.MetricType_INFO}:     {metricType: ts.PromMetricTypeInfo},
   308  		{metricType: prompb.MetricType_STATESET}: {metricType: ts.PromMetricTypeStateSet},
   309  
   310  		{prompb.MetricType_HISTOGRAM, "bucket"}: {metricType: ts.PromMetricTypeHistogram, handleValueResets: true},
   311  		{prompb.MetricType_HISTOGRAM, "count"}:  {metricType: ts.PromMetricTypeHistogram, handleValueResets: true},
   312  		{prompb.MetricType_HISTOGRAM, "sum"}:    {metricType: ts.PromMetricTypeHistogram, handleValueResets: true},
   313  
   314  		{prompb.MetricType_GAUGE_HISTOGRAM, "bucket"}: {metricType: ts.PromMetricTypeGaugeHistogram},
   315  		{prompb.MetricType_GAUGE_HISTOGRAM, "count"}:  {metricType: ts.PromMetricTypeGaugeHistogram, handleValueResets: true},
   316  		{prompb.MetricType_GAUGE_HISTOGRAM, "gcount"}: {metricType: ts.PromMetricTypeGaugeHistogram, handleValueResets: true},
   317  		{prompb.MetricType_GAUGE_HISTOGRAM, "sum"}:    {metricType: ts.PromMetricTypeGaugeHistogram},
   318  
   319  		{metricType: prompb.MetricType_SUMMARY}: {metricType: ts.PromMetricTypeSummary},
   320  		{prompb.MetricType_SUMMARY, "count"}:    {metricType: ts.PromMetricTypeSummary, handleValueResets: true},
   321  		{prompb.MetricType_SUMMARY, "sum"}:      {metricType: ts.PromMetricTypeSummary, handleValueResets: true},
   322  	}
   323  
   324  	for proto, expected := range mapping { // nolint: dupl
   325  		var (
   326  			name     = fmt.Sprintf("Prometheus type: %s, name suffix: '%s'", proto.metricType, proto.nameSuffix)
   327  			proto    = proto
   328  			expected = expected
   329  		)
   330  
   331  		t.Run(name, func(t *testing.T) {
   332  			var labels []prompb.Label
   333  			if proto.nameSuffix != "" {
   334  				labels = append(labels, prompb.Label{Name: promDefaultName, Value: []byte("foo_" + proto.nameSuffix)})
   335  			}
   336  
   337  			attrs, err := PromTimeSeriesToSeriesAttributes(prompb.TimeSeries{
   338  				Source: prompb.Source_PROMETHEUS,
   339  				Type:   proto.metricType,
   340  				Labels: labels,
   341  			})
   342  
   343  			require.NoError(t, err)
   344  			assert.Equal(t, expected.metricType, attrs.PromType)
   345  			assert.Equal(t, expected.handleValueResets, attrs.HandleValueResets)
   346  			assert.Equal(t, ts.SourceTypePrometheus, attrs.Source)
   347  		})
   348  	}
   349  
   350  	_, err := PromTimeSeriesToSeriesAttributes(prompb.TimeSeries{
   351  		Source: prompb.Source_PROMETHEUS,
   352  		Type:   -1,
   353  	})
   354  	require.Error(t, err)
   355  }
   356  
   357  func TestPromTimeSeriesToSeriesAttributesM3TypeFromPrometheus(t *testing.T) {
   358  	testPromTimeSeriesToSeriesAttributesM3Type(t, prompb.Source_PROMETHEUS)
   359  }
   360  
   361  func TestPromTimeSeriesToSeriesAttributesM3TypeFromOpenMetrics(t *testing.T) {
   362  	testPromTimeSeriesToSeriesAttributesM3Type(t, prompb.Source_OPEN_METRICS)
   363  }
   364  
   365  func testPromTimeSeriesToSeriesAttributesM3Type(t *testing.T, source prompb.Source) {
   366  	mapping := map[prompb.M3Type]ts.M3MetricType{
   367  		prompb.M3Type_M3_GAUGE:   ts.M3MetricTypeGauge,
   368  		prompb.M3Type_M3_COUNTER: ts.M3MetricTypeCounter,
   369  		prompb.M3Type_M3_TIMER:   ts.M3MetricTypeTimer,
   370  	}
   371  
   372  	for proto, expected := range mapping {
   373  		attrs, err := PromTimeSeriesToSeriesAttributes(prompb.TimeSeries{
   374  			Source: source,
   375  			M3Type: proto,
   376  		})
   377  		require.NoError(t, err)
   378  		assert.Equal(t, expected, attrs.M3Type)
   379  	}
   380  
   381  	_, err := PromTimeSeriesToSeriesAttributes(prompb.TimeSeries{
   382  		Source: source,
   383  		M3Type: -1,
   384  	})
   385  	require.Error(t, err)
   386  }
   387  
   388  func TestPromTimeSeriesToSeriesAttributesMetricsTypeFromOpenMetrics(t *testing.T) {
   389  	mapping := map[prompbMetricTypeWithNameSuffix]promMetricTypeWithBool{
   390  		{metricType: prompb.MetricType_UNKNOWN}: {metricType: ts.PromMetricTypeUnknown},
   391  
   392  		{prompb.MetricType_COUNTER, "total"}:   {metricType: ts.PromMetricTypeCounter, handleValueResets: true},
   393  		{prompb.MetricType_COUNTER, "created"}: {metricType: ts.PromMetricTypeCounter},
   394  
   395  		{metricType: prompb.MetricType_GAUGE}:    {metricType: ts.PromMetricTypeGauge},
   396  		{metricType: prompb.MetricType_INFO}:     {metricType: ts.PromMetricTypeInfo},
   397  		{metricType: prompb.MetricType_STATESET}: {metricType: ts.PromMetricTypeStateSet},
   398  
   399  		{prompb.MetricType_HISTOGRAM, "bucket"}:  {metricType: ts.PromMetricTypeHistogram, handleValueResets: true},
   400  		{prompb.MetricType_HISTOGRAM, "count"}:   {metricType: ts.PromMetricTypeHistogram, handleValueResets: true},
   401  		{prompb.MetricType_HISTOGRAM, "sum"}:     {metricType: ts.PromMetricTypeHistogram, handleValueResets: true},
   402  		{prompb.MetricType_HISTOGRAM, "created"}: {metricType: ts.PromMetricTypeHistogram},
   403  
   404  		{prompb.MetricType_GAUGE_HISTOGRAM, "bucket"}: {metricType: ts.PromMetricTypeGaugeHistogram},
   405  		{prompb.MetricType_GAUGE_HISTOGRAM, "gcount"}: {metricType: ts.PromMetricTypeGaugeHistogram, handleValueResets: true},
   406  		{prompb.MetricType_GAUGE_HISTOGRAM, "gsum"}:   {metricType: ts.PromMetricTypeGaugeHistogram},
   407  
   408  		{metricType: prompb.MetricType_SUMMARY}: {metricType: ts.PromMetricTypeSummary},
   409  		{prompb.MetricType_SUMMARY, "count"}:    {metricType: ts.PromMetricTypeSummary, handleValueResets: true},
   410  		{prompb.MetricType_SUMMARY, "sum"}:      {metricType: ts.PromMetricTypeSummary, handleValueResets: true},
   411  		{prompb.MetricType_SUMMARY, "created"}:  {metricType: ts.PromMetricTypeSummary},
   412  	}
   413  
   414  	for proto, expected := range mapping { // nolint: dupl
   415  		var (
   416  			name     = fmt.Sprintf("Open Metrics type: %s, name suffix: '%s'", proto.metricType, proto.nameSuffix)
   417  			proto    = proto
   418  			expected = expected
   419  		)
   420  
   421  		t.Run(name, func(t *testing.T) {
   422  			var labels []prompb.Label
   423  			if proto.nameSuffix != "" {
   424  				labels = append(labels, prompb.Label{Name: promDefaultName, Value: []byte("foo_" + proto.nameSuffix)})
   425  			}
   426  
   427  			attrs, err := PromTimeSeriesToSeriesAttributes(prompb.TimeSeries{
   428  				Source: prompb.Source_OPEN_METRICS,
   429  				Type:   proto.metricType,
   430  				Labels: labels,
   431  			})
   432  
   433  			require.NoError(t, err)
   434  			assert.Equal(t, expected.metricType, attrs.PromType)
   435  			assert.Equal(t, expected.handleValueResets, attrs.HandleValueResets)
   436  			assert.Equal(t, ts.SourceTypeOpenMetrics, attrs.Source)
   437  		})
   438  	}
   439  
   440  	_, err := PromTimeSeriesToSeriesAttributes(prompb.TimeSeries{
   441  		Source: prompb.Source_OPEN_METRICS,
   442  		Type:   -1,
   443  	})
   444  	require.Error(t, err)
   445  }
   446  
   447  func TestPromTimeSeriesToSeriesAttributesMetricsTypeFromGraphite(t *testing.T) {
   448  	mapping := map[prompb.M3Type]struct {
   449  		m3Type   ts.M3MetricType
   450  		promType ts.PromMetricType
   451  	}{
   452  		prompb.M3Type_M3_GAUGE:   {ts.M3MetricTypeGauge, ts.PromMetricTypeGauge},
   453  		prompb.M3Type_M3_COUNTER: {ts.M3MetricTypeCounter, ts.PromMetricTypeCounter},
   454  		prompb.M3Type_M3_TIMER:   {ts.M3MetricTypeTimer, ts.PromMetricTypeUnknown},
   455  	}
   456  
   457  	for proto, expected := range mapping {
   458  		attrs, err := PromTimeSeriesToSeriesAttributes(prompb.TimeSeries{
   459  			Source: prompb.Source_GRAPHITE,
   460  			M3Type: proto,
   461  		})
   462  		require.NoError(t, err)
   463  		assert.Equal(t, expected.promType, attrs.PromType, "PromType")
   464  		assert.Equal(t, expected.m3Type, attrs.M3Type, "M3Type")
   465  		assert.False(t, attrs.HandleValueResets)
   466  	}
   467  
   468  	_, err := PromTimeSeriesToSeriesAttributes(prompb.TimeSeries{
   469  		Source: prompb.Source_GRAPHITE,
   470  		M3Type: -1,
   471  	})
   472  	require.Error(t, err)
   473  }
   474  
   475  func TestPrometheusSeriesAttributesToAnnotationPayload(t *testing.T) {
   476  	testSeriesAttributesToAnnotationPayload(t, ts.SourceTypePrometheus)
   477  }
   478  
   479  func TestOpenMetricsSeriesAttributesToAnnotationPayload(t *testing.T) {
   480  	testSeriesAttributesToAnnotationPayload(t, ts.SourceTypeOpenMetrics)
   481  }
   482  
   483  func testSeriesAttributesToAnnotationPayload(t *testing.T, source ts.SourceType) {
   484  	mapping := map[ts.PromMetricType]annotation.OpenMetricsFamilyType{
   485  		ts.PromMetricTypeUnknown:        annotation.OpenMetricsFamilyType_UNKNOWN,
   486  		ts.PromMetricTypeCounter:        annotation.OpenMetricsFamilyType_COUNTER,
   487  		ts.PromMetricTypeGauge:          annotation.OpenMetricsFamilyType_GAUGE,
   488  		ts.PromMetricTypeHistogram:      annotation.OpenMetricsFamilyType_HISTOGRAM,
   489  		ts.PromMetricTypeGaugeHistogram: annotation.OpenMetricsFamilyType_GAUGE_HISTOGRAM,
   490  		ts.PromMetricTypeSummary:        annotation.OpenMetricsFamilyType_SUMMARY,
   491  		ts.PromMetricTypeInfo:           annotation.OpenMetricsFamilyType_INFO,
   492  		ts.PromMetricTypeStateSet:       annotation.OpenMetricsFamilyType_STATESET,
   493  	}
   494  
   495  	for promType, expectedType := range mapping {
   496  		payload, err := SeriesAttributesToAnnotationPayload(ts.SeriesAttributes{
   497  			Source:   source,
   498  			PromType: promType,
   499  		})
   500  		require.NoError(t, err)
   501  
   502  		expectedPayload := annotation.Payload{
   503  			SourceFormat:          annotation.SourceFormat_OPEN_METRICS,
   504  			OpenMetricsFamilyType: expectedType,
   505  		}
   506  		assert.Equal(t, expectedPayload, payload)
   507  	}
   508  
   509  	_, err := SeriesAttributesToAnnotationPayload(ts.SeriesAttributes{PromType: math.MaxUint8})
   510  	require.Error(t, err)
   511  
   512  	payload, err := SeriesAttributesToAnnotationPayload(ts.SeriesAttributes{
   513  		Source:            source,
   514  		HandleValueResets: true,
   515  	})
   516  	require.NoError(t, err)
   517  	assert.True(t, payload.OpenMetricsHandleValueResets)
   518  
   519  	payload, err = SeriesAttributesToAnnotationPayload(ts.SeriesAttributes{
   520  		Source:            source,
   521  		HandleValueResets: false,
   522  	})
   523  	require.NoError(t, err)
   524  	assert.False(t, payload.OpenMetricsHandleValueResets)
   525  }
   526  
   527  func TestGraphiteSeriesAttributesToAnnotationPayload(t *testing.T) {
   528  	mapping := map[ts.M3MetricType]annotation.GraphiteType{
   529  		ts.M3MetricTypeGauge:   annotation.GraphiteType_GRAPHITE_GAUGE,
   530  		ts.M3MetricTypeCounter: annotation.GraphiteType_GRAPHITE_COUNTER,
   531  		ts.M3MetricTypeTimer:   annotation.GraphiteType_GRAPHITE_TIMER,
   532  	}
   533  
   534  	for graphiteType, expectedType := range mapping {
   535  		payload, err := SeriesAttributesToAnnotationPayload(ts.SeriesAttributes{
   536  			Source: ts.SourceTypeGraphite,
   537  			M3Type: graphiteType,
   538  		})
   539  		require.NoError(t, err)
   540  
   541  		expectedPayload := annotation.Payload{
   542  			SourceFormat: annotation.SourceFormat_GRAPHITE,
   543  			GraphiteType: expectedType,
   544  		}
   545  		assert.Equal(t, expectedPayload, payload)
   546  	}
   547  
   548  	_, err := SeriesAttributesToAnnotationPayload(ts.SeriesAttributes{
   549  		Source: ts.SourceTypeGraphite,
   550  		M3Type: math.MaxUint8,
   551  	})
   552  	require.EqualError(t, err, "invalid Graphite metric type 255")
   553  }
   554  
   555  func TestPromTimestampToTime(t *testing.T) {
   556  	var (
   557  		now  = time.Now()
   558  		xNow = xtime.ToUnixNano(now)
   559  
   560  		xNowMillis = xNow.Truncate(time.Millisecond)
   561  		nowMillis  = now.Truncate(time.Millisecond)
   562  
   563  		timestampMs = int64(xNow) / int64(time.Millisecond)
   564  	)
   565  
   566  	require.Equal(t, xNowMillis, promTimestampToUnixNanos(timestampMs))
   567  	require.Equal(t, nowMillis, PromTimestampToTime(timestampMs))
   568  }
   569  
   570  type prompbMetricTypeWithNameSuffix struct {
   571  	metricType prompb.MetricType
   572  	nameSuffix string
   573  }
   574  
   575  type promMetricTypeWithBool struct {
   576  	metricType        ts.PromMetricType
   577  	handleValueResets bool
   578  }