...

Source file src/github.com/prometheus/common/expfmt/openmetrics_create_test.go

Documentation: github.com/prometheus/common/expfmt

     1  // Copyright 2020 The Prometheus Authors
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package expfmt
    15  
    16  import (
    17  	"bytes"
    18  	"math"
    19  	"strings"
    20  	"testing"
    21  	"time"
    22  
    23  	"google.golang.org/protobuf/proto"
    24  	"google.golang.org/protobuf/types/known/timestamppb"
    25  
    26  	dto "github.com/prometheus/client_model/go"
    27  
    28  	"github.com/prometheus/common/model"
    29  )
    30  
    31  func TestCreateOpenMetrics(t *testing.T) {
    32  	openMetricsTimestamp := timestamppb.New(time.Unix(12345, 600000000))
    33  	if err := openMetricsTimestamp.CheckValid(); err != nil {
    34  		t.Error(err)
    35  	}
    36  
    37  	oldDefaultScheme := model.NameEscapingScheme
    38  	model.NameEscapingScheme = model.NoEscaping
    39  	defer func() {
    40  		model.NameEscapingScheme = oldDefaultScheme
    41  	}()
    42  
    43  	scenarios := []struct {
    44  		in      *dto.MetricFamily
    45  		options []EncoderOption
    46  		out     string
    47  	}{
    48  		// 0: Counter, timestamp given, no _total suffix.
    49  		{
    50  			in: &dto.MetricFamily{
    51  				Name: proto.String("name"),
    52  				Help: proto.String("two-line\n doc  str\\ing"),
    53  				Type: dto.MetricType_COUNTER.Enum(),
    54  				Metric: []*dto.Metric{
    55  					{
    56  						Label: []*dto.LabelPair{
    57  							{
    58  								Name:  proto.String("labelname"),
    59  								Value: proto.String("val1"),
    60  							},
    61  							{
    62  								Name:  proto.String("basename"),
    63  								Value: proto.String("basevalue"),
    64  							},
    65  						},
    66  						Counter: &dto.Counter{
    67  							Value: proto.Float64(42),
    68  						},
    69  					},
    70  					{
    71  						Label: []*dto.LabelPair{
    72  							{
    73  								Name:  proto.String("labelname"),
    74  								Value: proto.String("val2"),
    75  							},
    76  							{
    77  								Name:  proto.String("basename"),
    78  								Value: proto.String("basevalue"),
    79  							},
    80  						},
    81  						Counter: &dto.Counter{
    82  							Value: proto.Float64(.23),
    83  						},
    84  						TimestampMs: proto.Int64(1234567890),
    85  					},
    86  				},
    87  			},
    88  			out: `# HELP name two-line\n doc  str\\ing
    89  # TYPE name unknown
    90  name{labelname="val1",basename="basevalue"} 42.0
    91  name{labelname="val2",basename="basevalue"} 0.23 1.23456789e+06
    92  `,
    93  		},
    94  		// 1: Dots in name
    95  		{
    96  			in: &dto.MetricFamily{
    97  				Name: proto.String("name.with.dots"),
    98  				Help: proto.String("boring help"),
    99  				Type: dto.MetricType_COUNTER.Enum(),
   100  				Metric: []*dto.Metric{
   101  					{
   102  						Label: []*dto.LabelPair{
   103  							{
   104  								Name:  proto.String("labelname"),
   105  								Value: proto.String("val1"),
   106  							},
   107  							{
   108  								Name:  proto.String("basename"),
   109  								Value: proto.String("basevalue"),
   110  							},
   111  						},
   112  						Counter: &dto.Counter{
   113  							Value: proto.Float64(42),
   114  						},
   115  					},
   116  					{
   117  						Label: []*dto.LabelPair{
   118  							{
   119  								Name:  proto.String("labelname"),
   120  								Value: proto.String("val2"),
   121  							},
   122  							{
   123  								Name:  proto.String("basename"),
   124  								Value: proto.String("basevalue"),
   125  							},
   126  						},
   127  						Counter: &dto.Counter{
   128  							Value: proto.Float64(.23),
   129  						},
   130  						TimestampMs: proto.Int64(1234567890),
   131  					},
   132  				},
   133  			},
   134  			out: `# HELP "name.with.dots" boring help
   135  # TYPE "name.with.dots" unknown
   136  {"name.with.dots",labelname="val1",basename="basevalue"} 42.0
   137  {"name.with.dots",labelname="val2",basename="basevalue"} 0.23 1.23456789e+06
   138  `,
   139  		},
   140  		// 2: Dots in name, no labels
   141  		{
   142  			in: &dto.MetricFamily{
   143  				Name: proto.String("name.with.dots"),
   144  				Help: proto.String("boring help"),
   145  				Type: dto.MetricType_COUNTER.Enum(),
   146  				Metric: []*dto.Metric{
   147  					{
   148  						Counter: &dto.Counter{
   149  							Value: proto.Float64(42),
   150  						},
   151  					},
   152  					{
   153  						Counter: &dto.Counter{
   154  							Value: proto.Float64(.23),
   155  						},
   156  						TimestampMs: proto.Int64(1234567890),
   157  					},
   158  				},
   159  			},
   160  			out: `# HELP "name.with.dots" boring help
   161  # TYPE "name.with.dots" unknown
   162  {"name.with.dots"} 42.0
   163  {"name.with.dots"} 0.23 1.23456789e+06
   164  `,
   165  		},
   166  		// 3: Gauge, some escaping required, +Inf as value, multi-byte characters in label values.
   167  		{
   168  			in: &dto.MetricFamily{
   169  				Name: proto.String("gauge_name"),
   170  				Help: proto.String("gauge\ndoc\nstr\"ing"),
   171  				Type: dto.MetricType_GAUGE.Enum(),
   172  				Metric: []*dto.Metric{
   173  					{
   174  						Label: []*dto.LabelPair{
   175  							{
   176  								Name:  proto.String("name_1"),
   177  								Value: proto.String("val with\nnew line"),
   178  							},
   179  							{
   180  								Name:  proto.String("name_2"),
   181  								Value: proto.String("val with \\backslash and \"quotes\""),
   182  							},
   183  						},
   184  						Gauge: &dto.Gauge{
   185  							Value: proto.Float64(math.Inf(+1)),
   186  						},
   187  					},
   188  					{
   189  						Label: []*dto.LabelPair{
   190  							{
   191  								Name:  proto.String("name_1"),
   192  								Value: proto.String("Björn"),
   193  							},
   194  							{
   195  								Name:  proto.String("name_2"),
   196  								Value: proto.String("佖佥"),
   197  							},
   198  						},
   199  						Gauge: &dto.Gauge{
   200  							Value: proto.Float64(3.14e42),
   201  						},
   202  					},
   203  				},
   204  			},
   205  			out: `# HELP gauge_name gauge\ndoc\nstr\"ing
   206  # TYPE gauge_name gauge
   207  gauge_name{name_1="val with\nnew line",name_2="val with \\backslash and \"quotes\""} +Inf
   208  gauge_name{name_1="Björn",name_2="佖佥"} 3.14e+42
   209  `,
   210  		},
   211  		// 4: Gauge, utf-8, some escaping required, +Inf as value, multi-byte characters in label values.
   212  		{
   213  			in: &dto.MetricFamily{
   214  				Name: proto.String("gauge.name\""),
   215  				Help: proto.String("gauge\ndoc\nstr\"ing"),
   216  				Type: dto.MetricType_GAUGE.Enum(),
   217  				Metric: []*dto.Metric{
   218  					{
   219  						Label: []*dto.LabelPair{
   220  							{
   221  								Name:  proto.String("name.1"),
   222  								Value: proto.String("val with\nnew line"),
   223  							},
   224  							{
   225  								Name:  proto.String("name*2"),
   226  								Value: proto.String("val with \\backslash and \"quotes\""),
   227  							},
   228  						},
   229  						Gauge: &dto.Gauge{
   230  							Value: proto.Float64(math.Inf(+1)),
   231  						},
   232  					},
   233  					{
   234  						Label: []*dto.LabelPair{
   235  							{
   236  								Name:  proto.String("name.1"),
   237  								Value: proto.String("Björn"),
   238  							},
   239  							{
   240  								Name:  proto.String("name*2"),
   241  								Value: proto.String("佖佥"),
   242  							},
   243  						},
   244  						Gauge: &dto.Gauge{
   245  							Value: proto.Float64(3.14e42),
   246  						},
   247  					},
   248  				},
   249  			},
   250  			out: `# HELP "gauge.name\"" gauge\ndoc\nstr\"ing
   251  # TYPE "gauge.name\"" gauge
   252  {"gauge.name\"","name.1"="val with\nnew line","name*2"="val with \\backslash and \"quotes\""} +Inf
   253  {"gauge.name\"","name.1"="Björn","name*2"="佖佥"} 3.14e+42
   254  `,
   255  		},
   256  		// 5: Unknown, no help, one sample with no labels and -Inf as value, another sample with one label.
   257  		{
   258  			in: &dto.MetricFamily{
   259  				Name: proto.String("unknown_name"),
   260  				Type: dto.MetricType_UNTYPED.Enum(),
   261  				Metric: []*dto.Metric{
   262  					{
   263  						Untyped: &dto.Untyped{
   264  							Value: proto.Float64(math.Inf(-1)),
   265  						},
   266  					},
   267  					{
   268  						Label: []*dto.LabelPair{
   269  							{
   270  								Name:  proto.String("name_1"),
   271  								Value: proto.String("value 1"),
   272  							},
   273  						},
   274  						Untyped: &dto.Untyped{
   275  							Value: proto.Float64(-1.23e-45),
   276  						},
   277  					},
   278  				},
   279  			},
   280  			out: `# TYPE unknown_name unknown
   281  unknown_name -Inf
   282  unknown_name{name_1="value 1"} -1.23e-45
   283  `,
   284  		},
   285  		// 6: Summary.
   286  		{
   287  			in: &dto.MetricFamily{
   288  				Name: proto.String("summary_name"),
   289  				Help: proto.String("summary docstring"),
   290  				Type: dto.MetricType_SUMMARY.Enum(),
   291  				Metric: []*dto.Metric{
   292  					{
   293  						Summary: &dto.Summary{
   294  							SampleCount: proto.Uint64(42),
   295  							SampleSum:   proto.Float64(-3.4567),
   296  							Quantile: []*dto.Quantile{
   297  								{
   298  									Quantile: proto.Float64(0.5),
   299  									Value:    proto.Float64(-1.23),
   300  								},
   301  								{
   302  									Quantile: proto.Float64(0.9),
   303  									Value:    proto.Float64(.2342354),
   304  								},
   305  								{
   306  									Quantile: proto.Float64(0.99),
   307  									Value:    proto.Float64(0),
   308  								},
   309  							},
   310  							CreatedTimestamp: openMetricsTimestamp,
   311  						},
   312  					},
   313  					{
   314  						Label: []*dto.LabelPair{
   315  							{
   316  								Name:  proto.String("name_1"),
   317  								Value: proto.String("value 1"),
   318  							},
   319  							{
   320  								Name:  proto.String("name_2"),
   321  								Value: proto.String("value 2"),
   322  							},
   323  						},
   324  						Summary: &dto.Summary{
   325  							SampleCount: proto.Uint64(4711),
   326  							SampleSum:   proto.Float64(2010.1971),
   327  							Quantile: []*dto.Quantile{
   328  								{
   329  									Quantile: proto.Float64(0.5),
   330  									Value:    proto.Float64(1),
   331  								},
   332  								{
   333  									Quantile: proto.Float64(0.9),
   334  									Value:    proto.Float64(2),
   335  								},
   336  								{
   337  									Quantile: proto.Float64(0.99),
   338  									Value:    proto.Float64(3),
   339  								},
   340  							},
   341  							CreatedTimestamp: openMetricsTimestamp,
   342  						},
   343  					},
   344  				},
   345  			},
   346  			options: []EncoderOption{WithCreatedLines()},
   347  			out: `# HELP summary_name summary docstring
   348  # TYPE summary_name summary
   349  summary_name{quantile="0.5"} -1.23
   350  summary_name{quantile="0.9"} 0.2342354
   351  summary_name{quantile="0.99"} 0.0
   352  summary_name_sum -3.4567
   353  summary_name_count 42
   354  summary_name_created 12345.6
   355  summary_name{name_1="value 1",name_2="value 2",quantile="0.5"} 1.0
   356  summary_name{name_1="value 1",name_2="value 2",quantile="0.9"} 2.0
   357  summary_name{name_1="value 1",name_2="value 2",quantile="0.99"} 3.0
   358  summary_name_sum{name_1="value 1",name_2="value 2"} 2010.1971
   359  summary_name_count{name_1="value 1",name_2="value 2"} 4711
   360  summary_name_created{name_1="value 1",name_2="value 2"} 12345.6
   361  `,
   362  		},
   363  		// 7: Histogram
   364  		{
   365  			in: &dto.MetricFamily{
   366  				Name: proto.String("request_duration_microseconds"),
   367  				Help: proto.String("The response latency."),
   368  				Type: dto.MetricType_HISTOGRAM.Enum(),
   369  				Unit: proto.String("microseconds"),
   370  				Metric: []*dto.Metric{
   371  					{
   372  						Histogram: &dto.Histogram{
   373  							SampleCount: proto.Uint64(2693),
   374  							SampleSum:   proto.Float64(1756047.3),
   375  							Bucket: []*dto.Bucket{
   376  								{
   377  									UpperBound:      proto.Float64(100),
   378  									CumulativeCount: proto.Uint64(123),
   379  								},
   380  								{
   381  									UpperBound:      proto.Float64(120),
   382  									CumulativeCount: proto.Uint64(412),
   383  								},
   384  								{
   385  									UpperBound:      proto.Float64(144),
   386  									CumulativeCount: proto.Uint64(592),
   387  								},
   388  								{
   389  									UpperBound:      proto.Float64(172.8),
   390  									CumulativeCount: proto.Uint64(1524),
   391  								},
   392  								{
   393  									UpperBound:      proto.Float64(math.Inf(+1)),
   394  									CumulativeCount: proto.Uint64(2693),
   395  								},
   396  							},
   397  							CreatedTimestamp: openMetricsTimestamp,
   398  						},
   399  					},
   400  				},
   401  			},
   402  			options: []EncoderOption{WithCreatedLines(), WithUnit()},
   403  			out: `# HELP request_duration_microseconds The response latency.
   404  # TYPE request_duration_microseconds histogram
   405  # UNIT request_duration_microseconds microseconds
   406  request_duration_microseconds_bucket{le="100.0"} 123
   407  request_duration_microseconds_bucket{le="120.0"} 412
   408  request_duration_microseconds_bucket{le="144.0"} 592
   409  request_duration_microseconds_bucket{le="172.8"} 1524
   410  request_duration_microseconds_bucket{le="+Inf"} 2693
   411  request_duration_microseconds_sum 1.7560473e+06
   412  request_duration_microseconds_count 2693
   413  request_duration_microseconds_created 12345.6
   414  `,
   415  		},
   416  		// 8: Histogram with missing +Inf bucket.
   417  		{
   418  			in: &dto.MetricFamily{
   419  				Name: proto.String("request_duration_microseconds"),
   420  				Help: proto.String("The response latency."),
   421  				Type: dto.MetricType_HISTOGRAM.Enum(),
   422  				Unit: proto.String("microseconds"),
   423  				Metric: []*dto.Metric{
   424  					{
   425  						Histogram: &dto.Histogram{
   426  							SampleCount: proto.Uint64(2693),
   427  							SampleSum:   proto.Float64(1756047.3),
   428  							Bucket: []*dto.Bucket{
   429  								{
   430  									UpperBound:      proto.Float64(100),
   431  									CumulativeCount: proto.Uint64(123),
   432  								},
   433  								{
   434  									UpperBound:      proto.Float64(120),
   435  									CumulativeCount: proto.Uint64(412),
   436  								},
   437  								{
   438  									UpperBound:      proto.Float64(144),
   439  									CumulativeCount: proto.Uint64(592),
   440  								},
   441  								{
   442  									UpperBound:      proto.Float64(172.8),
   443  									CumulativeCount: proto.Uint64(1524),
   444  								},
   445  							},
   446  						},
   447  					},
   448  				},
   449  			},
   450  			out: `# HELP request_duration_microseconds The response latency.
   451  # TYPE request_duration_microseconds histogram
   452  request_duration_microseconds_bucket{le="100.0"} 123
   453  request_duration_microseconds_bucket{le="120.0"} 412
   454  request_duration_microseconds_bucket{le="144.0"} 592
   455  request_duration_microseconds_bucket{le="172.8"} 1524
   456  request_duration_microseconds_bucket{le="+Inf"} 2693
   457  request_duration_microseconds_sum 1.7560473e+06
   458  request_duration_microseconds_count 2693
   459  `,
   460  		},
   461  		// 9: Histogram with missing +Inf bucket but with different exemplars.
   462  		{
   463  			in: &dto.MetricFamily{
   464  				Name: proto.String("request_duration_microseconds"),
   465  				Help: proto.String("The response latency."),
   466  				Type: dto.MetricType_HISTOGRAM.Enum(),
   467  				Metric: []*dto.Metric{
   468  					{
   469  						Histogram: &dto.Histogram{
   470  							SampleCount: proto.Uint64(2693),
   471  							SampleSum:   proto.Float64(1756047.3),
   472  							Bucket: []*dto.Bucket{
   473  								{
   474  									UpperBound:      proto.Float64(100),
   475  									CumulativeCount: proto.Uint64(123),
   476  								},
   477  								{
   478  									UpperBound:      proto.Float64(120),
   479  									CumulativeCount: proto.Uint64(412),
   480  									Exemplar: &dto.Exemplar{
   481  										Label: []*dto.LabelPair{
   482  											{
   483  												Name:  proto.String("foo"),
   484  												Value: proto.String("bar"),
   485  											},
   486  										},
   487  										Value:     proto.Float64(119.9),
   488  										Timestamp: openMetricsTimestamp,
   489  									},
   490  								},
   491  								{
   492  									UpperBound:      proto.Float64(144),
   493  									CumulativeCount: proto.Uint64(592),
   494  									Exemplar: &dto.Exemplar{
   495  										Label: []*dto.LabelPair{
   496  											{
   497  												Name:  proto.String("foo"),
   498  												Value: proto.String("baz"),
   499  											},
   500  											{
   501  												Name:  proto.String("dings"),
   502  												Value: proto.String("bums"),
   503  											},
   504  										},
   505  										Value: proto.Float64(140.14),
   506  									},
   507  								},
   508  								{
   509  									UpperBound:      proto.Float64(172.8),
   510  									CumulativeCount: proto.Uint64(1524),
   511  								},
   512  							},
   513  						},
   514  					},
   515  				},
   516  			},
   517  			out: `# HELP request_duration_microseconds The response latency.
   518  # TYPE request_duration_microseconds histogram
   519  request_duration_microseconds_bucket{le="100.0"} 123
   520  request_duration_microseconds_bucket{le="120.0"} 412 # {foo="bar"} 119.9 12345.6
   521  request_duration_microseconds_bucket{le="144.0"} 592 # {foo="baz",dings="bums"} 140.14
   522  request_duration_microseconds_bucket{le="172.8"} 1524
   523  request_duration_microseconds_bucket{le="+Inf"} 2693
   524  request_duration_microseconds_sum 1.7560473e+06
   525  request_duration_microseconds_count 2693
   526  `,
   527  		},
   528  		// 10: Simple Counter.
   529  		{
   530  			in: &dto.MetricFamily{
   531  				Name: proto.String("foos_total"),
   532  				Help: proto.String("Number of foos."),
   533  				Type: dto.MetricType_COUNTER.Enum(),
   534  				Metric: []*dto.Metric{
   535  					{
   536  						Counter: &dto.Counter{
   537  							Value:            proto.Float64(42),
   538  							CreatedTimestamp: openMetricsTimestamp,
   539  						},
   540  					},
   541  				},
   542  			},
   543  			options: []EncoderOption{WithCreatedLines()},
   544  			out: `# HELP foos Number of foos.
   545  # TYPE foos counter
   546  foos_total 42.0
   547  foos_created 12345.6
   548  `,
   549  		},
   550  		// 11: Simple Counter without created line.
   551  		{
   552  			in: &dto.MetricFamily{
   553  				Name: proto.String("foos_total"),
   554  				Help: proto.String("Number of foos."),
   555  				Type: dto.MetricType_COUNTER.Enum(),
   556  				Metric: []*dto.Metric{
   557  					{
   558  						Counter: &dto.Counter{
   559  							Value:            proto.Float64(42),
   560  							CreatedTimestamp: openMetricsTimestamp,
   561  						},
   562  					},
   563  				},
   564  			},
   565  			out: `# HELP foos Number of foos.
   566  # TYPE foos counter
   567  foos_total 42.0
   568  `,
   569  		},
   570  		// 12: No metric.
   571  		{
   572  			in: &dto.MetricFamily{
   573  				Name:   proto.String("name_total"),
   574  				Help:   proto.String("doc string"),
   575  				Type:   dto.MetricType_COUNTER.Enum(),
   576  				Metric: []*dto.Metric{},
   577  			},
   578  			out: `# HELP name doc string
   579  # TYPE name counter
   580  `,
   581  		},
   582  		// 13: Simple Counter with exemplar that has empty label set:
   583  		// ignore the exemplar, since OpenMetrics spec requires labels.
   584  		{
   585  			in: &dto.MetricFamily{
   586  				Name: proto.String("foos_total"),
   587  				Help: proto.String("Number of foos."),
   588  				Type: dto.MetricType_COUNTER.Enum(),
   589  				Metric: []*dto.Metric{
   590  					{
   591  						Counter: &dto.Counter{
   592  							Value: proto.Float64(42),
   593  							Exemplar: &dto.Exemplar{
   594  								Label:     []*dto.LabelPair{},
   595  								Value:     proto.Float64(1),
   596  								Timestamp: openMetricsTimestamp,
   597  							},
   598  						},
   599  					},
   600  				},
   601  			},
   602  			out: `# HELP foos Number of foos.
   603  # TYPE foos counter
   604  foos_total 42.0
   605  `,
   606  		},
   607  		// 14: No metric plus unit.
   608  		{
   609  			in: &dto.MetricFamily{
   610  				Name:   proto.String("name_seconds_total"),
   611  				Help:   proto.String("doc string"),
   612  				Type:   dto.MetricType_COUNTER.Enum(),
   613  				Unit:   proto.String("seconds"),
   614  				Metric: []*dto.Metric{},
   615  			},
   616  			options: []EncoderOption{WithUnit()},
   617  			out: `# HELP name_seconds doc string
   618  # TYPE name_seconds counter
   619  # UNIT name_seconds seconds
   620  `,
   621  		},
   622  		// 15: Histogram plus unit, but unit not opted in.
   623  		{
   624  			in: &dto.MetricFamily{
   625  				Name: proto.String("request_duration_microseconds"),
   626  				Help: proto.String("The response latency."),
   627  				Type: dto.MetricType_HISTOGRAM.Enum(),
   628  				Unit: proto.String("microseconds"),
   629  				Metric: []*dto.Metric{
   630  					{
   631  						Histogram: &dto.Histogram{
   632  							SampleCount: proto.Uint64(2693),
   633  							SampleSum:   proto.Float64(1756047.3),
   634  							Bucket: []*dto.Bucket{
   635  								{
   636  									UpperBound:      proto.Float64(100),
   637  									CumulativeCount: proto.Uint64(123),
   638  								},
   639  								{
   640  									UpperBound:      proto.Float64(120),
   641  									CumulativeCount: proto.Uint64(412),
   642  								},
   643  								{
   644  									UpperBound:      proto.Float64(144),
   645  									CumulativeCount: proto.Uint64(592),
   646  								},
   647  								{
   648  									UpperBound:      proto.Float64(172.8),
   649  									CumulativeCount: proto.Uint64(1524),
   650  								},
   651  								{
   652  									UpperBound:      proto.Float64(math.Inf(+1)),
   653  									CumulativeCount: proto.Uint64(2693),
   654  								},
   655  							},
   656  						},
   657  					},
   658  				},
   659  			},
   660  			out: `# HELP request_duration_microseconds The response latency.
   661  # TYPE request_duration_microseconds histogram
   662  request_duration_microseconds_bucket{le="100.0"} 123
   663  request_duration_microseconds_bucket{le="120.0"} 412
   664  request_duration_microseconds_bucket{le="144.0"} 592
   665  request_duration_microseconds_bucket{le="172.8"} 1524
   666  request_duration_microseconds_bucket{le="+Inf"} 2693
   667  request_duration_microseconds_sum 1.7560473e+06
   668  request_duration_microseconds_count 2693
   669  `,
   670  		},
   671  		// 16: No metric, unit opted in, no unit in name.
   672  		{
   673  			in: &dto.MetricFamily{
   674  				Name:   proto.String("name_total"),
   675  				Help:   proto.String("doc string"),
   676  				Type:   dto.MetricType_COUNTER.Enum(),
   677  				Unit:   proto.String("seconds"),
   678  				Metric: []*dto.Metric{},
   679  			},
   680  			options: []EncoderOption{WithUnit()},
   681  			out: `# HELP name_seconds doc string
   682  # TYPE name_seconds counter
   683  # UNIT name_seconds seconds
   684  `,
   685  		},
   686  		// 17: No metric, unit opted in, BUT unit == nil.
   687  		{
   688  			in: &dto.MetricFamily{
   689  				Name:   proto.String("name_total"),
   690  				Help:   proto.String("doc string"),
   691  				Type:   dto.MetricType_COUNTER.Enum(),
   692  				Metric: []*dto.Metric{},
   693  			},
   694  			options: []EncoderOption{WithUnit()},
   695  			out: `# HELP name doc string
   696  # TYPE name counter
   697  `,
   698  		},
   699  		// 18: Counter, timestamp given, unit opted in, _total suffix.
   700  		{
   701  			in: &dto.MetricFamily{
   702  				Name: proto.String("some_measure_total"),
   703  				Help: proto.String("some testing measurement"),
   704  				Type: dto.MetricType_COUNTER.Enum(),
   705  				Unit: proto.String("seconds"),
   706  				Metric: []*dto.Metric{
   707  					{
   708  						Label: []*dto.LabelPair{
   709  							{
   710  								Name:  proto.String("labelname"),
   711  								Value: proto.String("val1"),
   712  							},
   713  							{
   714  								Name:  proto.String("basename"),
   715  								Value: proto.String("basevalue"),
   716  							},
   717  						},
   718  						Counter: &dto.Counter{
   719  							Value: proto.Float64(42),
   720  						},
   721  					},
   722  					{
   723  						Label: []*dto.LabelPair{
   724  							{
   725  								Name:  proto.String("labelname"),
   726  								Value: proto.String("val2"),
   727  							},
   728  							{
   729  								Name:  proto.String("basename"),
   730  								Value: proto.String("basevalue"),
   731  							},
   732  						},
   733  						Counter: &dto.Counter{
   734  							Value: proto.Float64(.23),
   735  						},
   736  						TimestampMs: proto.Int64(1234567890),
   737  					},
   738  				},
   739  			},
   740  			options: []EncoderOption{WithUnit()},
   741  			out: `# HELP some_measure_seconds some testing measurement
   742  # TYPE some_measure_seconds counter
   743  # UNIT some_measure_seconds seconds
   744  some_measure_seconds_total{labelname="val1",basename="basevalue"} 42.0
   745  some_measure_seconds_total{labelname="val2",basename="basevalue"} 0.23 1.23456789e+06
   746  `,
   747  		},
   748  	}
   749  
   750  	for i, scenario := range scenarios {
   751  		out := bytes.NewBuffer(make([]byte, 0, len(scenario.out)))
   752  		n, err := MetricFamilyToOpenMetrics(out, scenario.in, scenario.options...)
   753  		if err != nil {
   754  			t.Errorf("%d. error: %s", i, err)
   755  			continue
   756  		}
   757  		if expected, got := len(scenario.out), n; expected != got {
   758  			t.Errorf(
   759  				"%d. expected %d bytes written, got %d",
   760  				i, expected, got,
   761  			)
   762  		}
   763  		if expected, got := scenario.out, out.String(); expected != got {
   764  			t.Errorf(
   765  				"%d. expected out=%q, got %q",
   766  				i, expected, got,
   767  			)
   768  		}
   769  	}
   770  }
   771  
   772  func BenchmarkOpenMetricsCreate(b *testing.B) {
   773  	mf := &dto.MetricFamily{
   774  		Name: proto.String("request_duration_microseconds"),
   775  		Help: proto.String("The response latency."),
   776  		Type: dto.MetricType_HISTOGRAM.Enum(),
   777  		Metric: []*dto.Metric{
   778  			{
   779  				Label: []*dto.LabelPair{
   780  					{
   781  						Name:  proto.String("name_1"),
   782  						Value: proto.String("val with\nnew line"),
   783  					},
   784  					{
   785  						Name:  proto.String("name_2"),
   786  						Value: proto.String("val with \\backslash and \"quotes\""),
   787  					},
   788  					{
   789  						Name:  proto.String("name_3"),
   790  						Value: proto.String("Just a quite long label value to test performance."),
   791  					},
   792  				},
   793  				Histogram: &dto.Histogram{
   794  					SampleCount: proto.Uint64(2693),
   795  					SampleSum:   proto.Float64(1756047.3),
   796  					Bucket: []*dto.Bucket{
   797  						{
   798  							UpperBound:      proto.Float64(100),
   799  							CumulativeCount: proto.Uint64(123),
   800  						},
   801  						{
   802  							UpperBound:      proto.Float64(120),
   803  							CumulativeCount: proto.Uint64(412),
   804  						},
   805  						{
   806  							UpperBound:      proto.Float64(144),
   807  							CumulativeCount: proto.Uint64(592),
   808  						},
   809  						{
   810  							UpperBound:      proto.Float64(172.8),
   811  							CumulativeCount: proto.Uint64(1524),
   812  						},
   813  						{
   814  							UpperBound:      proto.Float64(math.Inf(+1)),
   815  							CumulativeCount: proto.Uint64(2693),
   816  						},
   817  					},
   818  				},
   819  			},
   820  			{
   821  				Label: []*dto.LabelPair{
   822  					{
   823  						Name:  proto.String("name_1"),
   824  						Value: proto.String("Björn"),
   825  					},
   826  					{
   827  						Name:  proto.String("name_2"),
   828  						Value: proto.String("佖佥"),
   829  					},
   830  					{
   831  						Name:  proto.String("name_3"),
   832  						Value: proto.String("Just a quite long label value to test performance."),
   833  					},
   834  				},
   835  				Histogram: &dto.Histogram{
   836  					SampleCount: proto.Uint64(5699),
   837  					SampleSum:   proto.Float64(49484343543.4343),
   838  					Bucket: []*dto.Bucket{
   839  						{
   840  							UpperBound:      proto.Float64(100),
   841  							CumulativeCount: proto.Uint64(120),
   842  						},
   843  						{
   844  							UpperBound:      proto.Float64(120),
   845  							CumulativeCount: proto.Uint64(412),
   846  						},
   847  						{
   848  							UpperBound:      proto.Float64(144),
   849  							CumulativeCount: proto.Uint64(596),
   850  						},
   851  						{
   852  							UpperBound:      proto.Float64(172.8),
   853  							CumulativeCount: proto.Uint64(1535),
   854  						},
   855  					},
   856  				},
   857  				TimestampMs: proto.Int64(1234567890),
   858  			},
   859  		},
   860  	}
   861  	out := bytes.NewBuffer(make([]byte, 0, 1024))
   862  
   863  	for i := 0; i < b.N; i++ {
   864  		_, err := MetricFamilyToOpenMetrics(out, mf)
   865  		if err != nil {
   866  			b.Fatal(err)
   867  		}
   868  		out.Reset()
   869  	}
   870  }
   871  
   872  func TestOpenMetricsCreateError(t *testing.T) {
   873  	scenarios := []struct {
   874  		in  *dto.MetricFamily
   875  		err string
   876  	}{
   877  		// 0: No metric name.
   878  		{
   879  			in: &dto.MetricFamily{
   880  				Help: proto.String("doc string"),
   881  				Type: dto.MetricType_UNTYPED.Enum(),
   882  				Metric: []*dto.Metric{
   883  					{
   884  						Untyped: &dto.Untyped{
   885  							Value: proto.Float64(math.Inf(-1)),
   886  						},
   887  					},
   888  				},
   889  			},
   890  			err: "MetricFamily has no name",
   891  		},
   892  		// 1: Wrong type.
   893  		{
   894  			in: &dto.MetricFamily{
   895  				Name: proto.String("name"),
   896  				Help: proto.String("doc string"),
   897  				Type: dto.MetricType_COUNTER.Enum(),
   898  				Metric: []*dto.Metric{
   899  					{
   900  						Untyped: &dto.Untyped{
   901  							Value: proto.Float64(math.Inf(-1)),
   902  						},
   903  					},
   904  				},
   905  			},
   906  			err: "expected counter in metric",
   907  		},
   908  	}
   909  
   910  	for i, scenario := range scenarios {
   911  		var out bytes.Buffer
   912  		_, err := MetricFamilyToOpenMetrics(&out, scenario.in)
   913  		if err == nil {
   914  			t.Errorf("%d. expected error, got nil", i)
   915  			continue
   916  		}
   917  		if expected, got := scenario.err, err.Error(); strings.Index(got, expected) != 0 {
   918  			t.Errorf(
   919  				"%d. expected error starting with %q, got %q",
   920  				i, expected, got,
   921  			)
   922  		}
   923  	}
   924  }
   925  

View as plain text