From 453f28e704544a3ed8393b3e5cf7f94ba4c05b69 Mon Sep 17 00:00:00 2001 From: Connor Lindsey Date: Tue, 18 Aug 2020 12:43:22 -0600 Subject: [PATCH] Cortex: Add support for histogram and distribution (#237) * Cortex: Add support for histogram and distribution * Cortex: Switch to HistogramDistribution Processor * Update exporters/metric/cortex/cortex.go Co-authored-by: Tyler Yahn * Update exporters/metric/cortex/cortex.go Co-authored-by: Tyler Yahn * Update exporters/metric/cortex/cortex.go Co-authored-by: Tyler Yahn * Update exporters/metric/cortex/cortex.go Co-authored-by: Tyler Yahn * Fix comment and remove count map * Make precommit Co-authored-by: Tyler Yahn --- exporters/metric/cortex/README.md | 60 ++-- exporters/metric/cortex/config.go | 24 +- exporters/metric/cortex/cortex.go | 198 +++++++---- exporters/metric/cortex/cortex_test.go | 160 ++------- exporters/metric/cortex/testutil_test.go | 405 +++++++++++++++++++++++ 5 files changed, 618 insertions(+), 229 deletions(-) diff --git a/exporters/metric/cortex/README.md b/exporters/metric/cortex/README.md index ce72e7eb6ba..94c6fa1b1ce 100644 --- a/exporters/metric/cortex/README.md +++ b/exporters/metric/cortex/README.md @@ -5,11 +5,11 @@ Remote Write API. ## Setting up an Exporter -Users can setup the Exporter with the `InstallNewPipeline` function. It requires a -`Config` struct and returns a push Controller that will periodically collect and push -data. +Users can setup the Exporter with the `InstallNewPipeline` function. It requires a `Config` struct +and returns a push Controller that will periodically collect and push data. Example: + ```go pusher, err := cortex.InstallNewPipeline(config) if err != nil { @@ -21,15 +21,15 @@ if err != nil { ## Configuration -The Exporter needs certain information, such as the endpoint URL and push interval -duration, to function properly. This information is stored in a `Config` struct, which is -passed into the Exporter during the setup pipeline. +The Exporter needs certain information, such as the endpoint URL and push interval duration, to +function properly. This information is stored in a `Config` struct, which is passed into the +Exporter during the setup pipeline. ### Creating the Config struct -Users can either create the struct manually or use a `utils` submodule in the package to -read settings from a YAML file into a new Config struct using `Viper`. Here are the -supported YAML properties as well as the Config struct that they map to. +Users can either create the struct manually or use a `utils` submodule in the package to read +settings from a YAML file into a new Config struct using [Viper](https://github.com/spf13/viper). +Here are the supported YAML properties as well as the Config struct that they map to. ```yaml # The URL of the endpoint to send samples to. @@ -61,35 +61,49 @@ basic_auth: tls_config: # CA certificate to validate API server certificate with. [ ca_file: ] - + # Certificate and key files for client cert authentication to the server. [ cert_file: ] [ key_file: ] - + # ServerName extension to indicate the name of the server. # https://tools.ietf.org/html/rfc4366#section-3.1 [ server_name: ] - + # Disable validation of the server certificate. [ insecure_skip_verify: ] # Optional proxy URL. [ proxy_url: ] + +# Quantiles for Distribution aggregations +[ quantiles: ] + - + - + - ... + +# Histogram Buckets +[ histogram_buckets: ] + - + - + - ... ``` ```go type Config struct { - Endpoint string `mapstructure:"url"` - RemoteTimeout time.Duration `mapstructure:"remote_timeout"` - Name string `mapstructure:"name"` - BasicAuth map[string]string `mapstructure:"basic_auth"` - BearerToken string `mapstructure:"bearer_token"` - BearerTokenFile string `mapstructure:"bearer_token_file"` - TLSConfig map[string]string `mapstructure:"tls_config"` - ProxyURL string `mapstructure:"proxy_url"` - PushInterval time.Duration `mapstructure:"push_interval"` - Headers map[string]string `mapstructure:"headers"` - Client *http.Client + Endpoint string `mapstructure:"url"` + RemoteTimeout time.Duration `mapstructure:"remote_timeout"` + Name string `mapstructure:"name"` + BasicAuth map[string]string `mapstructure:"basic_auth"` + BearerToken string `mapstructure:"bearer_token"` + BearerTokenFile string `mapstructure:"bearer_token_file"` + TLSConfig map[string]string `mapstructure:"tls_config"` + ProxyURL string `mapstructure:"proxy_url"` + PushInterval time.Duration `mapstructure:"push_interval"` + Quantiles []float64 `mapstructure:"quantiles"` + HistogramBoundaries []float64 `mapstructure:"histogram_boundaries"` + Headers map[string]string `mapstructure:"headers"` + Client *http.Client } ``` diff --git a/exporters/metric/cortex/config.go b/exporters/metric/cortex/config.go index 90b283c43e4..df1aa5b2078 100644 --- a/exporters/metric/cortex/config.go +++ b/exporters/metric/cortex/config.go @@ -36,17 +36,19 @@ var ( // Config contains properties the Exporter uses to export metrics data to Cortex. type Config struct { - Endpoint string `mapstructure:"url"` - RemoteTimeout time.Duration `mapstructure:"remote_timeout"` - Name string `mapstructure:"name"` - BasicAuth map[string]string `mapstructure:"basic_auth"` - BearerToken string `mapstructure:"bearer_token"` - BearerTokenFile string `mapstructure:"bearer_token_file"` - TLSConfig map[string]string `mapstructure:"tls_config"` - ProxyURL string `mapstructure:"proxy_url"` - PushInterval time.Duration `mapstructure:"push_interval"` - Headers map[string]string `mapstructure:"headers"` - Client *http.Client + Endpoint string `mapstructure:"url"` + RemoteTimeout time.Duration `mapstructure:"remote_timeout"` + Name string `mapstructure:"name"` + BasicAuth map[string]string `mapstructure:"basic_auth"` + BearerToken string `mapstructure:"bearer_token"` + BearerTokenFile string `mapstructure:"bearer_token_file"` + TLSConfig map[string]string `mapstructure:"tls_config"` + ProxyURL string `mapstructure:"proxy_url"` + PushInterval time.Duration `mapstructure:"push_interval"` + Quantiles []float64 `mapstructure:"quantiles"` + HistogramBoundaries []float64 `mapstructure:"histogram_boundaries"` + Headers map[string]string `mapstructure:"headers"` + Client *http.Client } // Validate checks a Config struct for missing required properties and property conflicts. diff --git a/exporters/metric/cortex/cortex.go b/exporters/metric/cortex/cortex.go index 908c78f7387..092b08f6d37 100644 --- a/exporters/metric/cortex/cortex.go +++ b/exporters/metric/cortex/cortex.go @@ -20,6 +20,7 @@ import ( "fmt" "log" "net/http" + "strconv" "github.com/gogo/protobuf/proto" "github.com/golang/snappy" @@ -91,7 +92,7 @@ func NewExportPipeline(config Config, options ...push.Option) (*push.Controller, } pusher := push.New( - simple.NewWithExactDistribution(), + simple.NewWithHistogramDistribution(config.HistogramBoundaries), exporter, options..., ) @@ -110,6 +111,8 @@ func InstallNewPipeline(config Config, options ...push.Option) (*push.Controller } // ConvertToTimeSeries converts a CheckpointSet to a slice of TimeSeries pointers +// Based on the aggregation type, ConvertToTimeSeries will call helper functions like +// convertFromSum to generate the correct number of TimeSeries. func (e *Exporter) ConvertToTimeSeries(checkpointSet export.CheckpointSet) ([]*prompb.TimeSeries, error) { var aggError error var timeSeries []*prompb.TimeSeries @@ -119,24 +122,42 @@ func (e *Exporter) ConvertToTimeSeries(checkpointSet export.CheckpointSet) ([]*p // Convert based on aggregation type agg := record.Aggregation() - // Check if aggregation has Sum value - if sum, ok := agg.(aggregation.Sum); ok { + // Check if aggregation has Histogram value + if histogram, ok := agg.(aggregation.Histogram); ok { + tSeries, err := convertFromHistogram(record, histogram) + if err != nil { + return err + } + timeSeries = append(timeSeries, tSeries...) + // Check if aggregation has sum value + } else if sum, ok := agg.(aggregation.Sum); ok { tSeries, err := convertFromSum(record, sum) if err != nil { return err } timeSeries = append(timeSeries, tSeries) - } - // Check if aggregation has MinMaxSumCount value - if minMaxSumCount, ok := agg.(aggregation.MinMaxSumCount); ok { - tSeries, err := convertFromMinMaxSumCount(record, minMaxSumCount) - if err != nil { - return err - } + // Check if aggregation has MinMaxSumCount value + if minMaxSumCount, ok := agg.(aggregation.MinMaxSumCount); ok { + tSeries, err := convertFromMinMaxSumCount(record, minMaxSumCount) + if err != nil { + return err + } - timeSeries = append(timeSeries, tSeries...) + timeSeries = append(timeSeries, tSeries...) + + // Check if aggregation has a Distribution value + if distribution, ok := agg.(aggregation.Distribution); ok && len(e.config.Quantiles) != 0 { + tSeries, err := convertFromDistribution(record, distribution, e.config.Quantiles) + if err != nil { + return err + } + + timeSeries = append(timeSeries, tSeries...) + } + } + // Check if aggregation has lastValue } else if lastValue, ok := agg.(aggregation.LastValue); ok { tSeries, err := convertFromLastValue(record, lastValue) if err != nil { @@ -144,6 +165,9 @@ func (e *Exporter) ConvertToTimeSeries(checkpointSet export.CheckpointSet) ([]*p } timeSeries = append(timeSeries, tSeries) + } else { + // Report to the user when no conversion was found + fmt.Printf("No conversion found for record: %s\n", record.Descriptor().Name()) } return nil @@ -157,6 +181,21 @@ func (e *Exporter) ConvertToTimeSeries(checkpointSet export.CheckpointSet) ([]*p return timeSeries, nil } +// createTimeSeries is a helper function to create a timeseries from a value and labels +func createTimeSeries(record metric.Record, value apimetric.Number, extraLabels ...string) *prompb.TimeSeries { + sample := prompb.Sample{ + Value: value.CoerceToFloat64(record.Descriptor().NumberKind()), + Timestamp: record.EndTime().Unix(), + } + + labels := createLabelSet(record, extraLabels...) + + return &prompb.TimeSeries{ + Samples: []prompb.Sample{sample}, + Labels: labels, + } +} + // convertFromSum returns a single TimeSeries based on a Record with a Sum aggregation func convertFromSum(record metric.Record, sum aggregation.Sum) (*prompb.TimeSeries, error) { // Get Sum value @@ -164,21 +203,12 @@ func convertFromSum(record metric.Record, sum aggregation.Sum) (*prompb.TimeSeri if err != nil { return nil, err } - // Create sample from Sum value - sample := prompb.Sample{ - Value: value.CoerceToFloat64(record.Descriptor().NumberKind()), - Timestamp: record.EndTime().Unix(), // Convert time to Unix (int64) - } - // Create labels, including metric name + // Create TimeSeries name := sanitize(record.Descriptor().Name()) - labels := createLabelSet(record, "__name__", name) - - // Create TimeSeries and return - tSeries := &prompb.TimeSeries{ - Samples: []prompb.Sample{sample}, - Labels: labels, - } + // Note: Cortex requires the name label to be in the format "__name__". + // This is the case for all time series created by this exporter. + tSeries := createTimeSeries(record, value, "__name__", name) return tSeries, nil } @@ -191,21 +221,9 @@ func convertFromLastValue(record metric.Record, lastValue aggregation.LastValue) return nil, err } - // Create sample from Last value - sample := prompb.Sample{ - Value: value.CoerceToFloat64(record.Descriptor().NumberKind()), - Timestamp: record.EndTime().Unix(), // Convert time to Unix (int64) - } - - // Create labels, including metric name + // Create TimeSeries name := sanitize(record.Descriptor().Name()) - labels := createLabelSet(record, "__name__", name) - - // Create TimeSeries and return - tSeries := &prompb.TimeSeries{ - Samples: []prompb.Sample{sample}, - Labels: labels, - } + tSeries := createTimeSeries(record, value, "__name__", name) return tSeries, nil } @@ -217,42 +235,19 @@ func convertFromMinMaxSumCount(record metric.Record, minMaxSumCount aggregation. if err != nil { return nil, err } - minSample := prompb.Sample{ - Value: min.CoerceToFloat64(record.Descriptor().NumberKind()), - Timestamp: record.EndTime().Unix(), // Convert time to Unix (int64) - } - - // Create labels, including metric name name := sanitize(record.Descriptor().Name() + "_min") - labels := createLabelSet(record, "__name__", name) - - // Create TimeSeries - minTimeSeries := &prompb.TimeSeries{ - Samples: []prompb.Sample{minSample}, - Labels: labels, - } + minTimeSeries := createTimeSeries(record, min, "__name__", name) // Convert Max max, err := minMaxSumCount.Max() if err != nil { return nil, err } - maxSample := prompb.Sample{ - Value: max.CoerceToFloat64(record.Descriptor().NumberKind()), - Timestamp: record.EndTime().Unix(), // Convert time to Unix (int64) - } - - // Create labels, including metric name name = sanitize(record.Descriptor().Name() + "_max") - labels = createLabelSet(record, "__name__", name) - - // Create TimeSeries - maxTimeSeries := &prompb.TimeSeries{ - Samples: []prompb.Sample{maxSample}, - Labels: labels, - } + maxTimeSeries := createTimeSeries(record, max, "__name__", name) // Convert Count + // TODO: Refactor this to use createTimeSeries helper function count, err := minMaxSumCount.Count() if err != nil { return nil, err @@ -264,7 +259,7 @@ func convertFromMinMaxSumCount(record metric.Record, minMaxSumCount aggregation. // Create labels, including metric name name = sanitize(record.Descriptor().Name() + "_count") - labels = createLabelSet(record, "__name__", name) + labels := createLabelSet(record, "__name__", name) // Create TimeSeries countTimeSeries := &prompb.TimeSeries{ @@ -272,6 +267,7 @@ func convertFromMinMaxSumCount(record metric.Record, minMaxSumCount aggregation. Labels: labels, } + // Return all timeSeries tSeries := []*prompb.TimeSeries{ minTimeSeries, maxTimeSeries, countTimeSeries, } @@ -279,6 +275,78 @@ func convertFromMinMaxSumCount(record metric.Record, minMaxSumCount aggregation. return tSeries, nil } +// convertFromDistribution returns len(quantiles) number of TimeSeries in a distribution. +func convertFromDistribution(record metric.Record, distribution aggregation.Distribution, quantiles []float64) ([]*prompb.TimeSeries, error) { + var timeSeries []*prompb.TimeSeries + metricName := sanitize(record.Descriptor().Name()) + + // For each configured quantile, get the value and create a timeseries + for _, q := range quantiles { + value, err := distribution.Quantile(q) + if err != nil { + return nil, err + } + + // Add quantile as a label. e.g. {quantile="0.5"} + quantileStr := strconv.FormatFloat(q, 'f', -1, 64) + + // Create TimeSeries + tSeries := createTimeSeries(record, value, "__name__", metricName, "quantile", quantileStr) + timeSeries = append(timeSeries, tSeries) + } + + return timeSeries, nil +} + +// convertFromHistogram returns len(histogram.Buckets) timeseries for a histogram aggregation +func convertFromHistogram(record metric.Record, histogram aggregation.Histogram) ([]*prompb.TimeSeries, error) { + var timeSeries []*prompb.TimeSeries + metricName := sanitize(record.Descriptor().Name()) + + // Create Sum TimeSeries + sum, err := histogram.Sum() + if err != nil { + return nil, err + } + sumTimeSeries := createTimeSeries(record, sum, "__name__", metricName+"_sum") + timeSeries = append(timeSeries, sumTimeSeries) + + // Handle Histogram buckets + buckets, err := histogram.Histogram() + if err != nil { + return nil, err + } + + var totalCount float64 + // counts maps from the bucket upper-bound to the cumulative count. + // The bucket with upper-bound +inf is not included. + for i, boundary := range buckets.Boundaries { + // Add bucket count to totalCount and record in map + totalCount += buckets.Counts[i] + + // Add lowerbound as a label. e.g. {le="5"} + boundaryStr := strconv.FormatFloat(boundary, 'f', -1, 64) + + // Create timeSeries and append + tSeries := createTimeSeries(record, apimetric.NewFloat64Number(totalCount), "__name__", metricName, "le", boundaryStr) + timeSeries = append(timeSeries, tSeries) + fmt.Printf("%+v\n", tSeries) + } + + // Include the +inf boundary in the total count + totalCount += buckets.Counts[len(buckets.Counts)-1] + + // Create a timeSeries for the +inf bucket and total count + // These are the same and are both required by Prometheus-based backends + upperBoundTimeSeries := createTimeSeries(record, apimetric.NewFloat64Number(totalCount), "__name__", metricName, "le", "+inf") + timeSeries = append(timeSeries, upperBoundTimeSeries) + + countTimeSeries := createTimeSeries(record, apimetric.NewFloat64Number(totalCount), "__name__", metricName+"_count") + timeSeries = append(timeSeries, countTimeSeries) + + return timeSeries, nil +} + // createLabelSet combines labels from a Record, resource, and extra labels to // create a slice of prompb.Label func createLabelSet(record metric.Record, extras ...string) []*prompb.Label { diff --git a/exporters/metric/cortex/cortex_test.go b/exporters/metric/cortex/cortex_test.go index 223ca1fc525..139de6856ac 100644 --- a/exporters/metric/cortex/cortex_test.go +++ b/exporters/metric/cortex/cortex_test.go @@ -79,8 +79,12 @@ func TestExportKindFor(t *testing.T) { } func TestConvertToTimeSeries(t *testing.T) { - // Setup - exporter := Exporter{} + // Setup exporter with default quantiles and histogram buckets + exporter := Exporter{ + config: Config{ + Quantiles: []float64{0.5, 0.9, .99}, + }, + } // Test conversions based on aggregation type tests := []struct { @@ -90,145 +94,41 @@ func TestConvertToTimeSeries(t *testing.T) { wantLength int }{ { - name: "validCheckpointSet", - input: getValidCheckpointSet(t), - want: []*prompb.TimeSeries{ - { - Labels: []*prompb.Label{ - { - Name: "R", - Value: "V", - }, - { - Name: "__name__", - Value: "metric_name", - }, - }, - Samples: []prompb.Sample{{ - Value: 321, - Timestamp: mockTime, - }}, - }, - }, + name: "validCheckpointSet", + input: getValidCheckpointSet(t), + want: wantValidCheckpointSet, wantLength: 1, }, { - name: "convertFromSum", - input: getSumCheckpoint(t, 321), - want: []*prompb.TimeSeries{ - { - Labels: []*prompb.Label{ - { - Name: "R", - Value: "V", - }, - { - Name: "__name__", - Value: "metric_name", - }, - }, - Samples: []prompb.Sample{{ - Value: 321, - Timestamp: mockTime, - }}, - }, - }, + name: "convertFromSum", + input: getSumCheckpoint(t, 321), + want: wantSumCheckpointSet, wantLength: 1, }, { - name: "convertFromLastValue", - input: getLastValueCheckpoint(t, 123), - want: []*prompb.TimeSeries{ - { - Labels: []*prompb.Label{ - { - Name: "R", - Value: "V", - }, - { - Name: "__name__", - Value: "metric_name", - }, - }, - Samples: []prompb.Sample{{ - Value: 123, - Timestamp: mockTime, - }}, - }, - }, + name: "convertFromLastValue", + input: getLastValueCheckpoint(t, 123), + want: wantLastValueCheckpointSet, wantLength: 1, }, { - name: "convertFromMinMaxSumCount", - input: getMMSCCheckpoint(t, 123.456, 876.543), - want: []*prompb.TimeSeries{ - { - Labels: []*prompb.Label{ - { - Name: "R", - Value: "V", - }, - { - Name: "__name__", - Value: "metric_name", - }, - }, - Samples: []prompb.Sample{{ - Value: 999.999, - Timestamp: mockTime, - }}, - }, - { - Labels: []*prompb.Label{ - { - Name: "R", - Value: "V", - }, - { - Name: "__name__", - Value: "metric_name_min", - }, - }, - Samples: []prompb.Sample{{ - Value: 123.456, - Timestamp: mockTime, - }}, - }, - { - Labels: []*prompb.Label{ - { - Name: "__name__", - Value: "metric_name_max", - }, - { - Name: "R", - Value: "V", - }, - }, - Samples: []prompb.Sample{{ - Value: 876.543, - Timestamp: mockTime, - }}, - }, - { - Labels: []*prompb.Label{ - { - Name: "R", - Value: "V", - }, - { - Name: "__name__", - Value: "metric_name_count", - }, - }, - Samples: []prompb.Sample{{ - Value: 2, - Timestamp: mockTime, - }}, - }, - }, + name: "convertFromMinMaxSumCount", + input: getMMSCCheckpoint(t, 123.456, 876.543), + want: wantMMSCCheckpointSet, wantLength: 4, }, + { + name: "convertFromDistribution", + input: getDistributionCheckpoint(t), + want: wantDistributionCheckpointSet, + wantLength: 7, + }, + { + name: "convertFromHistogram", + input: getHistogramCheckpoint(t), + want: wantHistogramCheckpointSet, + wantLength: 6, + }, } for _, tt := range tests { diff --git a/exporters/metric/cortex/testutil_test.go b/exporters/metric/cortex/testutil_test.go index 005d5022795..a4258c24c71 100644 --- a/exporters/metric/cortex/testutil_test.go +++ b/exporters/metric/cortex/testutil_test.go @@ -17,12 +17,15 @@ package cortex import ( "testing" + "github.com/prometheus/prometheus/prompb" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/api/metric" export "go.opentelemetry.io/otel/sdk/export/metric" "go.opentelemetry.io/otel/sdk/export/metric/metrictest" "go.opentelemetry.io/otel/sdk/metric/aggregator/aggregatortest" + "go.opentelemetry.io/otel/sdk/metric/aggregator/array" + "go.opentelemetry.io/otel/sdk/metric/aggregator/histogram" "go.opentelemetry.io/otel/sdk/metric/aggregator/lastvalue" "go.opentelemetry.io/otel/sdk/metric/aggregator/minmaxsumcount" "go.opentelemetry.io/otel/sdk/metric/aggregator/sum" @@ -79,3 +82,405 @@ func getMMSCCheckpoint(t *testing.T, values ...float64) export.CheckpointSet { return checkpointSet } + +// getDistributionCheckpoint returns a checkpoint set with a distribution aggregation record +func getDistributionCheckpoint(t *testing.T) export.CheckpointSet { + // Create checkpoint set with resource and descriptor + checkpointSet := metrictest.NewCheckpointSet(testResource) + desc := metric.NewDescriptor("metric_name", metric.ValueRecorderKind, metric.Float64NumberKind) + + // Create aggregation, add value, and update checkpointset + agg, ckpt := metrictest.Unslice2(array.New(2)) + for i := 0; i < 1000; i++ { + aggregatortest.CheckedUpdate(t, agg, metric.NewFloat64Number(float64(i)+0.5), &desc) + } + require.NoError(t, agg.SynchronizedMove(ckpt, &desc)) + checkpointSet.Add(&desc, ckpt) + + return checkpointSet +} + +// getHistogramCheckpoint returns a checkpoint set with a histogram aggregation record +func getHistogramCheckpoint(t *testing.T) export.CheckpointSet { + // Create checkpoint set with resource and descriptor + checkpointSet := metrictest.NewCheckpointSet(testResource) + desc := metric.NewDescriptor("metric_name", metric.ValueRecorderKind, metric.Float64NumberKind) + + // Create aggregation, add value, and update checkpointset + boundaries := []float64{100, 500, 900} + agg, ckpt := metrictest.Unslice2(histogram.New(2, &desc, boundaries)) + for i := 0; i < 1000; i++ { + aggregatortest.CheckedUpdate(t, agg, metric.NewFloat64Number(float64(i)+0.5), &desc) + } + require.NoError(t, agg.SynchronizedMove(ckpt, &desc)) + checkpointSet.Add(&desc, ckpt) + + return checkpointSet +} + +// The following variables hold expected TimeSeries values to be used in ConvertToTimeSeries tests +var wantValidCheckpointSet = []*prompb.TimeSeries{ + { + Labels: []*prompb.Label{ + { + Name: "R", + Value: "V", + }, + { + Name: "__name__", + Value: "metric_name", + }, + }, + Samples: []prompb.Sample{{ + Value: 321, + Timestamp: mockTime, + }}, + }, +} + +var wantSumCheckpointSet = []*prompb.TimeSeries{ + { + Labels: []*prompb.Label{ + { + Name: "R", + Value: "V", + }, + { + Name: "__name__", + Value: "metric_name", + }, + }, + Samples: []prompb.Sample{{ + Value: 321, + Timestamp: mockTime, + }}, + }, +} + +var wantLastValueCheckpointSet = []*prompb.TimeSeries{ + { + Labels: []*prompb.Label{ + { + Name: "R", + Value: "V", + }, + { + Name: "__name__", + Value: "metric_name", + }, + }, + Samples: []prompb.Sample{{ + Value: 123, + Timestamp: mockTime, + }}, + }, +} + +var wantMMSCCheckpointSet = []*prompb.TimeSeries{ + { + Labels: []*prompb.Label{ + { + Name: "R", + Value: "V", + }, + { + Name: "__name__", + Value: "metric_name", + }, + }, + Samples: []prompb.Sample{{ + Value: 999.999, + Timestamp: mockTime, + }}, + }, + { + Labels: []*prompb.Label{ + { + Name: "R", + Value: "V", + }, + { + Name: "__name__", + Value: "metric_name_min", + }, + }, + Samples: []prompb.Sample{{ + Value: 123.456, + Timestamp: mockTime, + }}, + }, + { + Labels: []*prompb.Label{ + { + Name: "__name__", + Value: "metric_name_max", + }, + { + Name: "R", + Value: "V", + }, + }, + Samples: []prompb.Sample{{ + Value: 876.543, + Timestamp: mockTime, + }}, + }, + { + Labels: []*prompb.Label{ + { + Name: "R", + Value: "V", + }, + { + Name: "__name__", + Value: "metric_name_count", + }, + }, + Samples: []prompb.Sample{{ + Value: 2, + Timestamp: mockTime, + }}, + }, +} + +var wantDistributionCheckpointSet = []*prompb.TimeSeries{ + { + Labels: []*prompb.Label{ + { + Name: "R", + Value: "V", + }, + { + Name: "__name__", + Value: "metric_name", + }, + }, + Samples: []prompb.Sample{{ + Value: 500000, + Timestamp: mockTime, + }}, + }, + { + Labels: []*prompb.Label{ + { + Name: "R", + Value: "V", + }, + { + Name: "__name__", + Value: "metric_name_min", + }, + }, + Samples: []prompb.Sample{{ + Value: 0.5, + Timestamp: mockTime, + }}, + }, + { + Labels: []*prompb.Label{ + { + Name: "__name__", + Value: "metric_name_max", + }, + { + Name: "R", + Value: "V", + }, + }, + Samples: []prompb.Sample{{ + Value: 999.5, + Timestamp: mockTime, + }}, + }, + { + Labels: []*prompb.Label{ + { + Name: "R", + Value: "V", + }, + { + Name: "__name__", + Value: "metric_name_count", + }, + }, + Samples: []prompb.Sample{{ + Value: 1000, + Timestamp: mockTime, + }}, + }, + { + Labels: []*prompb.Label{ + { + Name: "R", + Value: "V", + }, + { + Name: "__name__", + Value: "metric_name", + }, + { + Name: "quantile", + Value: "0.5", + }, + }, + Samples: []prompb.Sample{{ + Value: 500.5, + Timestamp: mockTime, + }}, + }, + { + Labels: []*prompb.Label{ + { + Name: "R", + Value: "V", + }, + { + Name: "__name__", + Value: "metric_name_count", + }, + { + Name: "quantile", + Value: "0.9", + }, + }, + Samples: []prompb.Sample{{ + Value: 900.5, + Timestamp: mockTime, + }}, + }, + { + Labels: []*prompb.Label{ + { + Name: "R", + Value: "V", + }, + { + Name: "__name__", + Value: "metric_name_count", + }, + { + Name: "quantile", + Value: "0.99", + }, + }, + Samples: []prompb.Sample{{ + Value: 990.5, + Timestamp: mockTime, + }}, + }, +} + +var wantHistogramCheckpointSet = []*prompb.TimeSeries{ + { + Labels: []*prompb.Label{ + { + Name: "R", + Value: "V", + }, + { + Name: "__name__", + Value: "metric_name_sum", + }, + }, + Samples: []prompb.Sample{{ + Value: 500000, + Timestamp: mockTime, + }}, + }, + { + Labels: []*prompb.Label{ + { + Name: "R", + Value: "V", + }, + { + Name: "__name__", + Value: "metric_name_count", + }, + }, + Samples: []prompb.Sample{{ + Value: 1000, + Timestamp: mockTime, + }}, + }, + { + Labels: []*prompb.Label{ + { + Name: "R", + Value: "V", + }, + { + Name: "__name__", + Value: "metric_name", + }, + { + Name: "le", + Value: "100", + }, + }, + Samples: []prompb.Sample{{ + Value: 100, + Timestamp: mockTime, + }}, + }, + { + Labels: []*prompb.Label{ + { + Name: "R", + Value: "V", + }, + { + Name: "__name__", + Value: "metric_name", + }, + { + Name: "le", + Value: "500", + }, + }, + Samples: []prompb.Sample{{ + Value: 500, + Timestamp: mockTime, + }}, + }, + { + Labels: []*prompb.Label{ + { + Name: "R", + Value: "V", + }, + { + Name: "__name__", + Value: "metric_name", + }, + { + Name: "le", + Value: "900", + }, + }, + Samples: []prompb.Sample{{ + Value: 900, + Timestamp: mockTime, + }}, + }, + { + Labels: []*prompb.Label{ + { + Name: "R", + Value: "V", + }, + { + Name: "__name__", + Value: "metric_name", + }, + { + Name: "le", + Value: "+inf", + }, + }, + Samples: []prompb.Sample{{ + Value: 1000, + Timestamp: mockTime, + }}, + }, +}