...

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

Documentation: github.com/prometheus/common/expfmt

     1  // Copyright 2014 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  
    22  	"google.golang.org/protobuf/proto"
    23  
    24  	dto "github.com/prometheus/client_model/go"
    25  
    26  	"github.com/prometheus/common/model"
    27  )
    28  
    29  func TestCreate(t *testing.T) {
    30  	oldDefaultScheme := model.NameEscapingScheme
    31  	model.NameEscapingScheme = model.NoEscaping
    32  	defer func() {
    33  		model.NameEscapingScheme = oldDefaultScheme
    34  	}()
    35  
    36  	scenarios := []struct {
    37  		in  *dto.MetricFamily
    38  		out string
    39  	}{
    40  		// 0: Counter, NaN as value, timestamp given.
    41  		{
    42  			in: &dto.MetricFamily{
    43  				Name: proto.String("name"),
    44  				Help: proto.String("two-line\n doc  str\\ing"),
    45  				Type: dto.MetricType_COUNTER.Enum(),
    46  				Metric: []*dto.Metric{
    47  					{
    48  						Label: []*dto.LabelPair{
    49  							{
    50  								Name:  proto.String("labelname"),
    51  								Value: proto.String("val1"),
    52  							},
    53  							{
    54  								Name:  proto.String("basename"),
    55  								Value: proto.String("basevalue"),
    56  							},
    57  						},
    58  						Counter: &dto.Counter{
    59  							Value: proto.Float64(math.NaN()),
    60  						},
    61  					},
    62  					{
    63  						Label: []*dto.LabelPair{
    64  							{
    65  								Name:  proto.String("labelname"),
    66  								Value: proto.String("val2"),
    67  							},
    68  							{
    69  								Name:  proto.String("basename"),
    70  								Value: proto.String("basevalue"),
    71  							},
    72  						},
    73  						Counter: &dto.Counter{
    74  							Value: proto.Float64(.23),
    75  						},
    76  						TimestampMs: proto.Int64(1234567890),
    77  					},
    78  				},
    79  			},
    80  			out: `# HELP name two-line\n doc  str\\ing
    81  # TYPE name counter
    82  name{labelname="val1",basename="basevalue"} NaN
    83  name{labelname="val2",basename="basevalue"} 0.23 1234567890
    84  `,
    85  		},
    86  		// 1: Gauge, some escaping required, +Inf as value, multi-byte characters in label values.
    87  		{
    88  			in: &dto.MetricFamily{
    89  				Name: proto.String("gauge_name"),
    90  				Help: proto.String("gauge\ndoc\nstr\"ing"),
    91  				Type: dto.MetricType_GAUGE.Enum(),
    92  				Metric: []*dto.Metric{
    93  					{
    94  						Label: []*dto.LabelPair{
    95  							{
    96  								Name:  proto.String("name_1"),
    97  								Value: proto.String("val with\nnew line"),
    98  							},
    99  							{
   100  								Name:  proto.String("name_2"),
   101  								Value: proto.String("val with \\backslash and \"quotes\""),
   102  							},
   103  						},
   104  						Gauge: &dto.Gauge{
   105  							Value: proto.Float64(math.Inf(+1)),
   106  						},
   107  					},
   108  					{
   109  						Label: []*dto.LabelPair{
   110  							{
   111  								Name:  proto.String("name_1"),
   112  								Value: proto.String("Björn"),
   113  							},
   114  							{
   115  								Name:  proto.String("name_2"),
   116  								Value: proto.String("佖佥"),
   117  							},
   118  						},
   119  						Gauge: &dto.Gauge{
   120  							Value: proto.Float64(3.14e42),
   121  						},
   122  					},
   123  				},
   124  			},
   125  			out: `# HELP gauge_name gauge\ndoc\nstr"ing
   126  # TYPE gauge_name gauge
   127  gauge_name{name_1="val with\nnew line",name_2="val with \\backslash and \"quotes\""} +Inf
   128  gauge_name{name_1="Björn",name_2="佖佥"} 3.14e+42
   129  `,
   130  		},
   131  		// 2: Gauge, utf-8, +Inf as value, multi-byte characters in label values.
   132  		{
   133  			in: &dto.MetricFamily{
   134  				Name: proto.String("gauge.name"),
   135  				Help: proto.String("gauge\ndoc\nstr\"ing"),
   136  				Type: dto.MetricType_GAUGE.Enum(),
   137  				Metric: []*dto.Metric{
   138  					{
   139  						Label: []*dto.LabelPair{
   140  							{
   141  								Name:  proto.String("name.1"),
   142  								Value: proto.String("val with\nnew line"),
   143  							},
   144  							{
   145  								Name:  proto.String("name*2"),
   146  								Value: proto.String("val with \\backslash and \"quotes\""),
   147  							},
   148  						},
   149  						Gauge: &dto.Gauge{
   150  							Value: proto.Float64(math.Inf(+1)),
   151  						},
   152  					},
   153  					{
   154  						Label: []*dto.LabelPair{
   155  							{
   156  								Name:  proto.String("name.1"),
   157  								Value: proto.String("Björn"),
   158  							},
   159  							{
   160  								Name:  proto.String("name*2"),
   161  								Value: proto.String("佖佥"),
   162  							},
   163  						},
   164  						Gauge: &dto.Gauge{
   165  							Value: proto.Float64(3.14e42),
   166  						},
   167  					},
   168  				},
   169  			},
   170  			out: `# HELP "gauge.name" gauge\ndoc\nstr"ing
   171  # TYPE "gauge.name" gauge
   172  {"gauge.name","name.1"="val with\nnew line","name*2"="val with \\backslash and \"quotes\""} +Inf
   173  {"gauge.name","name.1"="Björn","name*2"="佖佥"} 3.14e+42
   174  `,
   175  		},
   176  		// 3: Untyped, no help, one sample with no labels and -Inf as value, another sample with one label.
   177  		{
   178  			in: &dto.MetricFamily{
   179  				Name: proto.String("untyped_name"),
   180  				Type: dto.MetricType_UNTYPED.Enum(),
   181  				Metric: []*dto.Metric{
   182  					{
   183  						Untyped: &dto.Untyped{
   184  							Value: proto.Float64(math.Inf(-1)),
   185  						},
   186  					},
   187  					{
   188  						Label: []*dto.LabelPair{
   189  							{
   190  								Name:  proto.String("name_1"),
   191  								Value: proto.String("value 1"),
   192  							},
   193  						},
   194  						Untyped: &dto.Untyped{
   195  							Value: proto.Float64(-1.23e-45),
   196  						},
   197  					},
   198  				},
   199  			},
   200  			out: `# TYPE untyped_name untyped
   201  untyped_name -Inf
   202  untyped_name{name_1="value 1"} -1.23e-45
   203  `,
   204  		},
   205  		// 4: Summary.
   206  		{
   207  			in: &dto.MetricFamily{
   208  				Name: proto.String("summary_name"),
   209  				Help: proto.String("summary docstring"),
   210  				Type: dto.MetricType_SUMMARY.Enum(),
   211  				Metric: []*dto.Metric{
   212  					{
   213  						Summary: &dto.Summary{
   214  							SampleCount: proto.Uint64(42),
   215  							SampleSum:   proto.Float64(-3.4567),
   216  							Quantile: []*dto.Quantile{
   217  								{
   218  									Quantile: proto.Float64(0.5),
   219  									Value:    proto.Float64(-1.23),
   220  								},
   221  								{
   222  									Quantile: proto.Float64(0.9),
   223  									Value:    proto.Float64(.2342354),
   224  								},
   225  								{
   226  									Quantile: proto.Float64(0.99),
   227  									Value:    proto.Float64(0),
   228  								},
   229  							},
   230  						},
   231  					},
   232  					{
   233  						Label: []*dto.LabelPair{
   234  							{
   235  								Name:  proto.String("name_1"),
   236  								Value: proto.String("value 1"),
   237  							},
   238  							{
   239  								Name:  proto.String("name_2"),
   240  								Value: proto.String("value 2"),
   241  							},
   242  						},
   243  						Summary: &dto.Summary{
   244  							SampleCount: proto.Uint64(4711),
   245  							SampleSum:   proto.Float64(2010.1971),
   246  							Quantile: []*dto.Quantile{
   247  								{
   248  									Quantile: proto.Float64(0.5),
   249  									Value:    proto.Float64(1),
   250  								},
   251  								{
   252  									Quantile: proto.Float64(0.9),
   253  									Value:    proto.Float64(2),
   254  								},
   255  								{
   256  									Quantile: proto.Float64(0.99),
   257  									Value:    proto.Float64(3),
   258  								},
   259  							},
   260  						},
   261  					},
   262  				},
   263  			},
   264  			out: `# HELP summary_name summary docstring
   265  # TYPE summary_name summary
   266  summary_name{quantile="0.5"} -1.23
   267  summary_name{quantile="0.9"} 0.2342354
   268  summary_name{quantile="0.99"} 0
   269  summary_name_sum -3.4567
   270  summary_name_count 42
   271  summary_name{name_1="value 1",name_2="value 2",quantile="0.5"} 1
   272  summary_name{name_1="value 1",name_2="value 2",quantile="0.9"} 2
   273  summary_name{name_1="value 1",name_2="value 2",quantile="0.99"} 3
   274  summary_name_sum{name_1="value 1",name_2="value 2"} 2010.1971
   275  summary_name_count{name_1="value 1",name_2="value 2"} 4711
   276  `,
   277  		},
   278  		// 5: Histogram
   279  		{
   280  			in: &dto.MetricFamily{
   281  				Name: proto.String("request_duration_microseconds"),
   282  				Help: proto.String("The response latency."),
   283  				Type: dto.MetricType_HISTOGRAM.Enum(),
   284  				Metric: []*dto.Metric{
   285  					{
   286  						Histogram: &dto.Histogram{
   287  							SampleCount: proto.Uint64(2693),
   288  							SampleSum:   proto.Float64(1756047.3),
   289  							Bucket: []*dto.Bucket{
   290  								{
   291  									UpperBound:      proto.Float64(100),
   292  									CumulativeCount: proto.Uint64(123),
   293  								},
   294  								{
   295  									UpperBound:      proto.Float64(120),
   296  									CumulativeCount: proto.Uint64(412),
   297  								},
   298  								{
   299  									UpperBound:      proto.Float64(144),
   300  									CumulativeCount: proto.Uint64(592),
   301  								},
   302  								{
   303  									UpperBound:      proto.Float64(172.8),
   304  									CumulativeCount: proto.Uint64(1524),
   305  								},
   306  								{
   307  									UpperBound:      proto.Float64(math.Inf(+1)),
   308  									CumulativeCount: proto.Uint64(2693),
   309  								},
   310  							},
   311  						},
   312  					},
   313  				},
   314  			},
   315  			out: `# HELP request_duration_microseconds The response latency.
   316  # TYPE request_duration_microseconds histogram
   317  request_duration_microseconds_bucket{le="100"} 123
   318  request_duration_microseconds_bucket{le="120"} 412
   319  request_duration_microseconds_bucket{le="144"} 592
   320  request_duration_microseconds_bucket{le="172.8"} 1524
   321  request_duration_microseconds_bucket{le="+Inf"} 2693
   322  request_duration_microseconds_sum 1.7560473e+06
   323  request_duration_microseconds_count 2693
   324  `,
   325  		},
   326  		// 6: Histogram with missing +Inf bucket.
   327  		{
   328  			in: &dto.MetricFamily{
   329  				Name: proto.String("request_duration_microseconds"),
   330  				Help: proto.String("The response latency."),
   331  				Type: dto.MetricType_HISTOGRAM.Enum(),
   332  				Metric: []*dto.Metric{
   333  					{
   334  						Histogram: &dto.Histogram{
   335  							SampleCount: proto.Uint64(2693),
   336  							SampleSum:   proto.Float64(1756047.3),
   337  							Bucket: []*dto.Bucket{
   338  								{
   339  									UpperBound:      proto.Float64(100),
   340  									CumulativeCount: proto.Uint64(123),
   341  								},
   342  								{
   343  									UpperBound:      proto.Float64(120),
   344  									CumulativeCount: proto.Uint64(412),
   345  								},
   346  								{
   347  									UpperBound:      proto.Float64(144),
   348  									CumulativeCount: proto.Uint64(592),
   349  								},
   350  								{
   351  									UpperBound:      proto.Float64(172.8),
   352  									CumulativeCount: proto.Uint64(1524),
   353  								},
   354  							},
   355  						},
   356  					},
   357  				},
   358  			},
   359  			out: `# HELP request_duration_microseconds The response latency.
   360  # TYPE request_duration_microseconds histogram
   361  request_duration_microseconds_bucket{le="100"} 123
   362  request_duration_microseconds_bucket{le="120"} 412
   363  request_duration_microseconds_bucket{le="144"} 592
   364  request_duration_microseconds_bucket{le="172.8"} 1524
   365  request_duration_microseconds_bucket{le="+Inf"} 2693
   366  request_duration_microseconds_sum 1.7560473e+06
   367  request_duration_microseconds_count 2693
   368  `,
   369  		},
   370  		// 7: No metric type, should result in default type Counter.
   371  		{
   372  			in: &dto.MetricFamily{
   373  				Name: proto.String("name"),
   374  				Help: proto.String("doc string"),
   375  				Metric: []*dto.Metric{
   376  					{
   377  						Counter: &dto.Counter{
   378  							Value: proto.Float64(math.Inf(-1)),
   379  						},
   380  					},
   381  				},
   382  			},
   383  			out: `# HELP name doc string
   384  # TYPE name counter
   385  name -Inf
   386  `,
   387  		},
   388  	}
   389  
   390  	for i, scenario := range scenarios {
   391  		out := bytes.NewBuffer(make([]byte, 0, len(scenario.out)))
   392  		n, err := MetricFamilyToText(out, scenario.in)
   393  		if err != nil {
   394  			t.Errorf("%d. error: %s", i, err)
   395  			continue
   396  		}
   397  		if expected, got := len(scenario.out), n; expected != got {
   398  			t.Errorf(
   399  				"%d. expected %d bytes written, got %d",
   400  				i, expected, got,
   401  			)
   402  		}
   403  		if expected, got := scenario.out, out.String(); expected != got {
   404  			t.Errorf(
   405  				"%d. expected out=%q, got %q",
   406  				i, expected, got,
   407  			)
   408  		}
   409  	}
   410  }
   411  
   412  func BenchmarkCreate(b *testing.B) {
   413  	mf := &dto.MetricFamily{
   414  		Name: proto.String("request_duration_microseconds"),
   415  		Help: proto.String("The response latency."),
   416  		Type: dto.MetricType_HISTOGRAM.Enum(),
   417  		Metric: []*dto.Metric{
   418  			{
   419  				Label: []*dto.LabelPair{
   420  					{
   421  						Name:  proto.String("name_1"),
   422  						Value: proto.String("val with\nnew line"),
   423  					},
   424  					{
   425  						Name:  proto.String("name_2"),
   426  						Value: proto.String("val with \\backslash and \"quotes\""),
   427  					},
   428  					{
   429  						Name:  proto.String("name_3"),
   430  						Value: proto.String("Just a quite long label value to test performance."),
   431  					},
   432  				},
   433  				Histogram: &dto.Histogram{
   434  					SampleCount: proto.Uint64(2693),
   435  					SampleSum:   proto.Float64(1756047.3),
   436  					Bucket: []*dto.Bucket{
   437  						{
   438  							UpperBound:      proto.Float64(100),
   439  							CumulativeCount: proto.Uint64(123),
   440  						},
   441  						{
   442  							UpperBound:      proto.Float64(120),
   443  							CumulativeCount: proto.Uint64(412),
   444  						},
   445  						{
   446  							UpperBound:      proto.Float64(144),
   447  							CumulativeCount: proto.Uint64(592),
   448  						},
   449  						{
   450  							UpperBound:      proto.Float64(172.8),
   451  							CumulativeCount: proto.Uint64(1524),
   452  						},
   453  						{
   454  							UpperBound:      proto.Float64(math.Inf(+1)),
   455  							CumulativeCount: proto.Uint64(2693),
   456  						},
   457  					},
   458  				},
   459  			},
   460  			{
   461  				Label: []*dto.LabelPair{
   462  					{
   463  						Name:  proto.String("name_1"),
   464  						Value: proto.String("Björn"),
   465  					},
   466  					{
   467  						Name:  proto.String("name_2"),
   468  						Value: proto.String("佖佥"),
   469  					},
   470  					{
   471  						Name:  proto.String("name_3"),
   472  						Value: proto.String("Just a quite long label value to test performance."),
   473  					},
   474  				},
   475  				Histogram: &dto.Histogram{
   476  					SampleCount: proto.Uint64(5699),
   477  					SampleSum:   proto.Float64(49484343543.4343),
   478  					Bucket: []*dto.Bucket{
   479  						{
   480  							UpperBound:      proto.Float64(100),
   481  							CumulativeCount: proto.Uint64(120),
   482  						},
   483  						{
   484  							UpperBound:      proto.Float64(120),
   485  							CumulativeCount: proto.Uint64(412),
   486  						},
   487  						{
   488  							UpperBound:      proto.Float64(144),
   489  							CumulativeCount: proto.Uint64(596),
   490  						},
   491  						{
   492  							UpperBound:      proto.Float64(172.8),
   493  							CumulativeCount: proto.Uint64(1535),
   494  						},
   495  					},
   496  				},
   497  				TimestampMs: proto.Int64(1234567890),
   498  			},
   499  		},
   500  	}
   501  	out := bytes.NewBuffer(make([]byte, 0, 1024))
   502  
   503  	for i := 0; i < b.N; i++ {
   504  		_, err := MetricFamilyToText(out, mf)
   505  		if err != nil {
   506  			b.Fatal(err)
   507  		}
   508  		out.Reset()
   509  	}
   510  }
   511  
   512  func BenchmarkCreateBuildInfo(b *testing.B) {
   513  	mf := &dto.MetricFamily{
   514  		Name: proto.String("benchmark_build_info"),
   515  		Help: proto.String("Test the creation of constant 1-value build_info metric."),
   516  		Type: dto.MetricType_GAUGE.Enum(),
   517  		Metric: []*dto.Metric{
   518  			{
   519  				Label: []*dto.LabelPair{
   520  					{
   521  						Name:  proto.String("version"),
   522  						Value: proto.String("1.2.3"),
   523  					},
   524  					{
   525  						Name:  proto.String("revision"),
   526  						Value: proto.String("2e84f5e4eacdffb574035810305191ff390360fe"),
   527  					},
   528  					{
   529  						Name:  proto.String("go_version"),
   530  						Value: proto.String("1.11.1"),
   531  					},
   532  				},
   533  				Gauge: &dto.Gauge{
   534  					Value: proto.Float64(1),
   535  				},
   536  			},
   537  		},
   538  	}
   539  	out := bytes.NewBuffer(make([]byte, 0, 1024))
   540  
   541  	for i := 0; i < b.N; i++ {
   542  		_, err := MetricFamilyToText(out, mf)
   543  		if err != nil {
   544  			b.Fatal(err)
   545  		}
   546  		out.Reset()
   547  	}
   548  }
   549  
   550  func TestCreateError(t *testing.T) {
   551  	scenarios := []struct {
   552  		in  *dto.MetricFamily
   553  		err string
   554  	}{
   555  		// 0: No metric.
   556  		{
   557  			in: &dto.MetricFamily{
   558  				Name:   proto.String("name"),
   559  				Help:   proto.String("doc string"),
   560  				Type:   dto.MetricType_COUNTER.Enum(),
   561  				Metric: []*dto.Metric{},
   562  			},
   563  			err: "MetricFamily has no metrics",
   564  		},
   565  		// 1: No metric name.
   566  		{
   567  			in: &dto.MetricFamily{
   568  				Help: proto.String("doc string"),
   569  				Type: dto.MetricType_UNTYPED.Enum(),
   570  				Metric: []*dto.Metric{
   571  					{
   572  						Untyped: &dto.Untyped{
   573  							Value: proto.Float64(math.Inf(-1)),
   574  						},
   575  					},
   576  				},
   577  			},
   578  			err: "MetricFamily has no name",
   579  		},
   580  		// 2: Wrong type.
   581  		{
   582  			in: &dto.MetricFamily{
   583  				Name: proto.String("name"),
   584  				Help: proto.String("doc string"),
   585  				Type: dto.MetricType_COUNTER.Enum(),
   586  				Metric: []*dto.Metric{
   587  					{
   588  						Untyped: &dto.Untyped{
   589  							Value: proto.Float64(math.Inf(-1)),
   590  						},
   591  					},
   592  				},
   593  			},
   594  			err: "expected counter in metric",
   595  		},
   596  	}
   597  
   598  	for i, scenario := range scenarios {
   599  		var out bytes.Buffer
   600  		_, err := MetricFamilyToText(&out, scenario.in)
   601  		if err == nil {
   602  			t.Errorf("%d. expected error, got nil", i)
   603  			continue
   604  		}
   605  		if expected, got := scenario.err, err.Error(); strings.Index(got, expected) != 0 {
   606  			t.Errorf(
   607  				"%d. expected error starting with %q, got %q",
   608  				i, expected, got,
   609  			)
   610  		}
   611  	}
   612  }
   613  

View as plain text