...

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

Documentation: github.com/prometheus/common/expfmt

     1  // Copyright 2015 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  	"bufio"
    18  	"bytes"
    19  	"errors"
    20  	"io"
    21  	"math"
    22  	"net/http"
    23  	"os"
    24  	"reflect"
    25  	"sort"
    26  	"strings"
    27  	"testing"
    28  
    29  	dto "github.com/prometheus/client_model/go"
    30  	"google.golang.org/protobuf/proto"
    31  
    32  	"github.com/prometheus/common/model"
    33  )
    34  
    35  func TestTextDecoder(t *testing.T) {
    36  	var (
    37  		ts = model.Now()
    38  		in = `
    39  # Only a quite simple scenario with two metric families.
    40  # More complicated tests of the parser itself can be found in the text package.
    41  # TYPE mf2 counter
    42  mf2 3
    43  mf1{label="value1"} -3.14 123456
    44  mf1{label="value2"} 42
    45  mf2 4
    46  `
    47  		out = model.Vector{
    48  			&model.Sample{
    49  				Metric: model.Metric{
    50  					model.MetricNameLabel: "mf1",
    51  					"label":               "value1",
    52  				},
    53  				Value:     -3.14,
    54  				Timestamp: 123456,
    55  			},
    56  			&model.Sample{
    57  				Metric: model.Metric{
    58  					model.MetricNameLabel: "mf1",
    59  					"label":               "value2",
    60  				},
    61  				Value:     42,
    62  				Timestamp: ts,
    63  			},
    64  			&model.Sample{
    65  				Metric: model.Metric{
    66  					model.MetricNameLabel: "mf2",
    67  				},
    68  				Value:     3,
    69  				Timestamp: ts,
    70  			},
    71  			&model.Sample{
    72  				Metric: model.Metric{
    73  					model.MetricNameLabel: "mf2",
    74  				},
    75  				Value:     4,
    76  				Timestamp: ts,
    77  			},
    78  		}
    79  	)
    80  
    81  	dec := &SampleDecoder{
    82  		Dec: &textDecoder{r: strings.NewReader(in)},
    83  		Opts: &DecodeOptions{
    84  			Timestamp: ts,
    85  		},
    86  	}
    87  	var all model.Vector
    88  	for {
    89  		var smpls model.Vector
    90  		err := dec.Decode(&smpls)
    91  		if err != nil && errors.Is(err, io.EOF) {
    92  			break
    93  		}
    94  		if err != nil {
    95  			t.Fatal(err)
    96  		}
    97  		all = append(all, smpls...)
    98  	}
    99  	sort.Sort(all)
   100  	sort.Sort(out)
   101  	if !reflect.DeepEqual(all, out) {
   102  		t.Fatalf("output does not match")
   103  	}
   104  }
   105  
   106  func TestProtoDecoder(t *testing.T) {
   107  	testTime := model.Now()
   108  
   109  	scenarios := []struct {
   110  		in             string
   111  		expected       model.Vector
   112  		legacyNameFail bool
   113  		fail           bool
   114  	}{
   115  		{
   116  			in: "",
   117  		},
   118  		{
   119  			in:   "\x8f\x01\n\rrequest_count\x12\x12Number of requests\x18\x00\"0\n#\n\x0fsome_!abel_name\x12\x10some_label_value\x1a\t\t\x00\x00\x00\x00\x00\x00E\xc0\"6\n)\n\x12another_label_name\x12\x13another_label_value\x1a\t\t\x00\x00\x00\x00\x00\x00U@",
   120  			fail: true,
   121  		},
   122  		{
   123  			in: "\x8f\x01\n\rrequest_count\x12\x12Number of requests\x18\x00\"0\n#\n\x0fsome_label_name\x12\x10some_label_value\x1a\t\t\x00\x00\x00\x00\x00\x00E\xc0\"6\n)\n\x12another_label_name\x12\x13another_label_value\x1a\t\t\x00\x00\x00\x00\x00\x00U@",
   124  			expected: model.Vector{
   125  				&model.Sample{
   126  					Metric: model.Metric{
   127  						model.MetricNameLabel: "request_count",
   128  						"some_label_name":     "some_label_value",
   129  					},
   130  					Value:     -42,
   131  					Timestamp: testTime,
   132  				},
   133  				&model.Sample{
   134  					Metric: model.Metric{
   135  						model.MetricNameLabel: "request_count",
   136  						"another_label_name":  "another_label_value",
   137  					},
   138  					Value:     84,
   139  					Timestamp: testTime,
   140  				},
   141  			},
   142  		},
   143  		{
   144  			in: "\xb9\x01\n\rrequest_count\x12\x12Number of requests\x18\x02\"O\n#\n\x0fsome_label_name\x12\x10some_label_value\"(\x1a\x12\t\xaeG\xe1z\x14\xae\xef?\x11\x00\x00\x00\x00\x00\x00E\xc0\x1a\x12\t+\x87\x16\xd9\xce\xf7\xef?\x11\x00\x00\x00\x00\x00\x00U\xc0\"A\n)\n\x12another_label_name\x12\x13another_label_value\"\x14\x1a\x12\t\x00\x00\x00\x00\x00\x00\xe0?\x11\x00\x00\x00\x00\x00\x00$@",
   145  			expected: model.Vector{
   146  				&model.Sample{
   147  					Metric: model.Metric{
   148  						model.MetricNameLabel: "request_count_count",
   149  						"some_label_name":     "some_label_value",
   150  					},
   151  					Value:     0,
   152  					Timestamp: testTime,
   153  				},
   154  				&model.Sample{
   155  					Metric: model.Metric{
   156  						model.MetricNameLabel: "request_count_sum",
   157  						"some_label_name":     "some_label_value",
   158  					},
   159  					Value:     0,
   160  					Timestamp: testTime,
   161  				},
   162  				&model.Sample{
   163  					Metric: model.Metric{
   164  						model.MetricNameLabel: "request_count",
   165  						"some_label_name":     "some_label_value",
   166  						"quantile":            "0.99",
   167  					},
   168  					Value:     -42,
   169  					Timestamp: testTime,
   170  				},
   171  				&model.Sample{
   172  					Metric: model.Metric{
   173  						model.MetricNameLabel: "request_count",
   174  						"some_label_name":     "some_label_value",
   175  						"quantile":            "0.999",
   176  					},
   177  					Value:     -84,
   178  					Timestamp: testTime,
   179  				},
   180  				&model.Sample{
   181  					Metric: model.Metric{
   182  						model.MetricNameLabel: "request_count_count",
   183  						"another_label_name":  "another_label_value",
   184  					},
   185  					Value:     0,
   186  					Timestamp: testTime,
   187  				},
   188  				&model.Sample{
   189  					Metric: model.Metric{
   190  						model.MetricNameLabel: "request_count_sum",
   191  						"another_label_name":  "another_label_value",
   192  					},
   193  					Value:     0,
   194  					Timestamp: testTime,
   195  				},
   196  				&model.Sample{
   197  					Metric: model.Metric{
   198  						model.MetricNameLabel: "request_count",
   199  						"another_label_name":  "another_label_value",
   200  						"quantile":            "0.5",
   201  					},
   202  					Value:     10,
   203  					Timestamp: testTime,
   204  				},
   205  			},
   206  		},
   207  		{
   208  			in: "\x8d\x01\n\x1drequest_duration_microseconds\x12\x15The response latency.\x18\x04\"S:Q\b\x85\x15\x11\xcd\xcc\xccL\x8f\xcb:A\x1a\v\b{\x11\x00\x00\x00\x00\x00\x00Y@\x1a\f\b\x9c\x03\x11\x00\x00\x00\x00\x00\x00^@\x1a\f\b\xd0\x04\x11\x00\x00\x00\x00\x00\x00b@\x1a\f\b\xf4\v\x11\x9a\x99\x99\x99\x99\x99e@\x1a\f\b\x85\x15\x11\x00\x00\x00\x00\x00\x00\xf0\u007f",
   209  			expected: model.Vector{
   210  				&model.Sample{
   211  					Metric: model.Metric{
   212  						model.MetricNameLabel: "request_duration_microseconds_bucket",
   213  						"le":                  "100",
   214  					},
   215  					Value:     123,
   216  					Timestamp: testTime,
   217  				},
   218  				&model.Sample{
   219  					Metric: model.Metric{
   220  						model.MetricNameLabel: "request_duration_microseconds_bucket",
   221  						"le":                  "120",
   222  					},
   223  					Value:     412,
   224  					Timestamp: testTime,
   225  				},
   226  				&model.Sample{
   227  					Metric: model.Metric{
   228  						model.MetricNameLabel: "request_duration_microseconds_bucket",
   229  						"le":                  "144",
   230  					},
   231  					Value:     592,
   232  					Timestamp: testTime,
   233  				},
   234  				&model.Sample{
   235  					Metric: model.Metric{
   236  						model.MetricNameLabel: "request_duration_microseconds_bucket",
   237  						"le":                  "172.8",
   238  					},
   239  					Value:     1524,
   240  					Timestamp: testTime,
   241  				},
   242  				&model.Sample{
   243  					Metric: model.Metric{
   244  						model.MetricNameLabel: "request_duration_microseconds_bucket",
   245  						"le":                  "+Inf",
   246  					},
   247  					Value:     2693,
   248  					Timestamp: testTime,
   249  				},
   250  				&model.Sample{
   251  					Metric: model.Metric{
   252  						model.MetricNameLabel: "request_duration_microseconds_sum",
   253  					},
   254  					Value:     1756047.3,
   255  					Timestamp: testTime,
   256  				},
   257  				&model.Sample{
   258  					Metric: model.Metric{
   259  						model.MetricNameLabel: "request_duration_microseconds_count",
   260  					},
   261  					Value:     2693,
   262  					Timestamp: testTime,
   263  				},
   264  			},
   265  		},
   266  		{
   267  			in: "\u007f\n\x1drequest_duration_microseconds\x12\x15The response latency.\x18\x04\"E:C\b\x85\x15\x11\xcd\xcc\xccL\x8f\xcb:A\x1a\v\b{\x11\x00\x00\x00\x00\x00\x00Y@\x1a\f\b\x9c\x03\x11\x00\x00\x00\x00\x00\x00^@\x1a\f\b\xd0\x04\x11\x00\x00\x00\x00\x00\x00b@\x1a\f\b\xf4\v\x11\x9a\x99\x99\x99\x99\x99e@",
   268  			expected: model.Vector{
   269  				&model.Sample{
   270  					Metric: model.Metric{
   271  						model.MetricNameLabel: "request_duration_microseconds_count",
   272  					},
   273  					Value:     2693,
   274  					Timestamp: testTime,
   275  				},
   276  				&model.Sample{
   277  					Metric: model.Metric{
   278  						"le":                  "+Inf",
   279  						model.MetricNameLabel: "request_duration_microseconds_bucket",
   280  					},
   281  					Value:     2693,
   282  					Timestamp: testTime,
   283  				},
   284  				&model.Sample{
   285  					Metric: model.Metric{
   286  						model.MetricNameLabel: "request_duration_microseconds_sum",
   287  					},
   288  					Value:     1756047.3,
   289  					Timestamp: testTime,
   290  				},
   291  				&model.Sample{
   292  					Metric: model.Metric{
   293  						"le":                  "172.8",
   294  						model.MetricNameLabel: "request_duration_microseconds_bucket",
   295  					},
   296  					Value:     1524,
   297  					Timestamp: testTime,
   298  				},
   299  				&model.Sample{
   300  					Metric: model.Metric{
   301  						"le":                  "144",
   302  						model.MetricNameLabel: "request_duration_microseconds_bucket",
   303  					},
   304  					Value:     592,
   305  					Timestamp: testTime,
   306  				},
   307  				&model.Sample{
   308  					Metric: model.Metric{
   309  						"le":                  "120",
   310  						model.MetricNameLabel: "request_duration_microseconds_bucket",
   311  					},
   312  					Value:     412,
   313  					Timestamp: testTime,
   314  				},
   315  				&model.Sample{
   316  					Metric: model.Metric{
   317  						"le":                  "100",
   318  						model.MetricNameLabel: "request_duration_microseconds_bucket",
   319  					},
   320  					Value:     123,
   321  					Timestamp: testTime,
   322  				},
   323  			},
   324  		},
   325  		{
   326  			// The metric type is unset in this protobuf, which needs to be handled
   327  			// correctly by the decoder.
   328  			in: "\x1c\n\rrequest_count\"\v\x1a\t\t\x00\x00\x00\x00\x00\x00\xf0?",
   329  			expected: model.Vector{
   330  				&model.Sample{
   331  					Metric: model.Metric{
   332  						model.MetricNameLabel: "request_count",
   333  					},
   334  					Value:     1,
   335  					Timestamp: testTime,
   336  				},
   337  			},
   338  		},
   339  		{
   340  			in:             "\xa8\x01\n\ngauge.name\x12\x11gauge\ndoc\nstr\"ing\x18\x01\"T\n\x1b\n\x06name.1\x12\x11val with\nnew line\n*\n\x06name*2\x12 val with \\backslash and \"quotes\"\x12\t\t\x00\x00\x00\x00\x00\x00\xf0\x7f\"/\n\x10\n\x06name.1\x12\x06Björn\n\x10\n\x06name*2\x12\x06佖佥\x12\t\t\xd1\xcfD\xb9\xd0\x05\xc2H",
   341  			legacyNameFail: true,
   342  			expected: model.Vector{
   343  				&model.Sample{
   344  					Metric: model.Metric{
   345  						model.MetricNameLabel: "gauge.name",
   346  						"name.1":              "val with\nnew line",
   347  						"name*2":              "val with \\backslash and \"quotes\"",
   348  					},
   349  					Value:     model.SampleValue(math.Inf(+1)),
   350  					Timestamp: testTime,
   351  				},
   352  				&model.Sample{
   353  					Metric: model.Metric{
   354  						model.MetricNameLabel: "gauge.name",
   355  						"name.1":              "Björn",
   356  						"name*2":              "佖佥",
   357  					},
   358  					Value:     3.14e42,
   359  					Timestamp: testTime,
   360  				},
   361  			},
   362  		},
   363  	}
   364  
   365  	for i, scenario := range scenarios {
   366  		dec := &SampleDecoder{
   367  			Dec: &protoDecoder{r: strings.NewReader(scenario.in)},
   368  			Opts: &DecodeOptions{
   369  				Timestamp: testTime,
   370  			},
   371  		}
   372  
   373  		var all model.Vector
   374  		for {
   375  			model.NameValidationScheme = model.LegacyValidation
   376  			var smpls model.Vector
   377  			err := dec.Decode(&smpls)
   378  			if err != nil && errors.Is(err, io.EOF) {
   379  				break
   380  			}
   381  			if scenario.legacyNameFail {
   382  				if err == nil {
   383  					t.Fatal("Expected error when decoding without UTF-8 support enabled but got none")
   384  				}
   385  				model.NameValidationScheme = model.UTF8Validation
   386  				dec = &SampleDecoder{
   387  					Dec: &protoDecoder{r: strings.NewReader(scenario.in)},
   388  					Opts: &DecodeOptions{
   389  						Timestamp: testTime,
   390  					},
   391  				}
   392  				err = dec.Decode(&smpls)
   393  				if errors.Is(err, io.EOF) {
   394  					break
   395  				}
   396  				if err != nil {
   397  					t.Fatalf("Unexpected error when decoding with UTF-8 support: %v", err)
   398  				}
   399  			}
   400  			if scenario.fail {
   401  				if err == nil {
   402  					t.Fatal("Expected error but got none")
   403  				}
   404  				break
   405  			}
   406  			if err != nil {
   407  				t.Fatal(err)
   408  			}
   409  			all = append(all, smpls...)
   410  		}
   411  		sort.Sort(all)
   412  		sort.Sort(scenario.expected)
   413  		if !reflect.DeepEqual(all, scenario.expected) {
   414  			t.Fatalf("%d. output does not match, want: %#v, got %#v", i, scenario.expected, all)
   415  		}
   416  	}
   417  }
   418  
   419  func TestProtoMultiMessageDecoder(t *testing.T) {
   420  	data, err := os.ReadFile("testdata/protobuf-multimessage")
   421  	if err != nil {
   422  		t.Fatalf("Reading file failed: %v", err)
   423  	}
   424  
   425  	buf := bytes.NewReader(data)
   426  	decoder := NewDecoder(buf, fmtProtoDelim)
   427  	var metrics []*dto.MetricFamily
   428  	for {
   429  		var mf dto.MetricFamily
   430  		if err := decoder.Decode(&mf); err != nil {
   431  			if errors.Is(err, io.EOF) {
   432  				break
   433  			}
   434  			t.Fatalf("Unmarshalling failed: %v", err)
   435  		}
   436  		metrics = append(metrics, &mf)
   437  	}
   438  
   439  	if len(metrics) != 6 {
   440  		t.Fatalf("Expected %d metrics but got %d!", 6, len(metrics))
   441  	}
   442  }
   443  
   444  func testDiscriminatorHTTPHeader(t testing.TB) {
   445  	scenarios := []struct {
   446  		input  map[string]string
   447  		output Format
   448  	}{
   449  		{
   450  			input:  map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="delimited"`},
   451  			output: fmtProtoDelim,
   452  		},
   453  		{
   454  			input:  map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="illegal"; encoding="delimited"`},
   455  			output: fmtUnknown,
   456  		},
   457  		{
   458  			input:  map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="illegal"`},
   459  			output: fmtUnknown,
   460  		},
   461  		{
   462  			input:  map[string]string{"Content-Type": `text/plain; version=0.0.4`},
   463  			output: fmtText,
   464  		},
   465  		{
   466  			input:  map[string]string{"Content-Type": `text/plain`},
   467  			output: fmtText,
   468  		},
   469  		{
   470  			input:  map[string]string{"Content-Type": `text/plain; version=0.0.3`},
   471  			output: fmtUnknown,
   472  		},
   473  	}
   474  
   475  	for i, scenario := range scenarios {
   476  		var header http.Header
   477  
   478  		if len(scenario.input) > 0 {
   479  			header = http.Header{}
   480  		}
   481  
   482  		for key, value := range scenario.input {
   483  			header.Add(key, value)
   484  		}
   485  
   486  		actual := ResponseFormat(header)
   487  
   488  		if scenario.output != actual {
   489  			t.Errorf("%d. expected %s, got %s", i, scenario.output, actual)
   490  		}
   491  	}
   492  }
   493  
   494  func TestDiscriminatorHTTPHeader(t *testing.T) {
   495  	testDiscriminatorHTTPHeader(t)
   496  }
   497  
   498  func BenchmarkDiscriminatorHTTPHeader(b *testing.B) {
   499  	for i := 0; i < b.N; i++ {
   500  		testDiscriminatorHTTPHeader(b)
   501  	}
   502  }
   503  
   504  func TestExtractSamples(t *testing.T) {
   505  	var (
   506  		goodMetricFamily1 = &dto.MetricFamily{
   507  			Name: proto.String("foo"),
   508  			Help: proto.String("Help for foo."),
   509  			Type: dto.MetricType_COUNTER.Enum(),
   510  			Metric: []*dto.Metric{
   511  				{
   512  					Counter: &dto.Counter{
   513  						Value: proto.Float64(4711),
   514  					},
   515  				},
   516  			},
   517  		}
   518  		goodMetricFamily2 = &dto.MetricFamily{
   519  			Name: proto.String("bar"),
   520  			Help: proto.String("Help for bar."),
   521  			Type: dto.MetricType_GAUGE.Enum(),
   522  			Metric: []*dto.Metric{
   523  				{
   524  					Gauge: &dto.Gauge{
   525  						Value: proto.Float64(3.14),
   526  					},
   527  				},
   528  			},
   529  		}
   530  		badMetricFamily = &dto.MetricFamily{
   531  			Name: proto.String("bad"),
   532  			Help: proto.String("Help for bad."),
   533  			Type: dto.MetricType(42).Enum(),
   534  			Metric: []*dto.Metric{
   535  				{
   536  					Gauge: &dto.Gauge{
   537  						Value: proto.Float64(2.7),
   538  					},
   539  				},
   540  			},
   541  		}
   542  
   543  		opts = &DecodeOptions{
   544  			Timestamp: 42,
   545  		}
   546  	)
   547  
   548  	got, err := ExtractSamples(opts, goodMetricFamily1, goodMetricFamily2)
   549  	if err != nil {
   550  		t.Error("Unexpected error from ExtractSamples:", err)
   551  	}
   552  	want := model.Vector{
   553  		&model.Sample{Metric: model.Metric{model.MetricNameLabel: "foo"}, Value: 4711, Timestamp: 42},
   554  		&model.Sample{Metric: model.Metric{model.MetricNameLabel: "bar"}, Value: 3.14, Timestamp: 42},
   555  	}
   556  	if !reflect.DeepEqual(got, want) {
   557  		t.Errorf("unexpected samples extracted, got: %v, want: %v", got, want)
   558  	}
   559  
   560  	got, err = ExtractSamples(opts, goodMetricFamily1, badMetricFamily, goodMetricFamily2)
   561  	if err == nil {
   562  		t.Error("Expected error from ExtractSamples")
   563  	}
   564  	if !reflect.DeepEqual(got, want) {
   565  		t.Errorf("unexpected samples extracted, got: %v, want: %v", got, want)
   566  	}
   567  }
   568  
   569  func TestTextDecoderWithBufioReader(t *testing.T) {
   570  	example := `
   571  	# TYPE foo gauge
   572  	foo 0
   573  	`
   574  
   575  	var decoded bool
   576  	r := bufio.NewReader(strings.NewReader(example))
   577  	dec := NewDecoder(r, fmtText)
   578  	for {
   579  		var mf dto.MetricFamily
   580  		if err := dec.Decode(&mf); err != nil {
   581  			if errors.Is(err, io.EOF) {
   582  				break
   583  			}
   584  			t.Fatalf("Unexpected error: %v", err)
   585  		}
   586  		if mf.GetName() != "foo" {
   587  			t.Errorf("Unexpected metric name: got %v, expected %v", mf.GetName(), "foo")
   588  		}
   589  		if len(mf.Metric) != 1 {
   590  			t.Errorf("Unexpected number of metrics: got %v, expected %v", len(mf.Metric), 1)
   591  		}
   592  		decoded = true
   593  	}
   594  	if !decoded {
   595  		t.Fatal("Metric foo not decoded")
   596  	}
   597  }
   598  

View as plain text