From e7f4912c3e705829fb6c214812fb71ced44ec948 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Thu, 14 Mar 2024 14:36:17 -0400 Subject: [PATCH] expfmt: Add a way to generate different OpenMetrics Formats (#596) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * expfmt: Add a way to generate different OpenMetrics Formats Also complete test coverage of expfmt.go --------- Signed-off-by: Owen Williams Signed-off-by: Björn Rabenstein Co-authored-by: Björn Rabenstein Co-authored-by: Ben Kochie --- expfmt/expfmt.go | 22 +++++-- expfmt/expfmt_test.go | 129 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 6 deletions(-) create mode 100644 expfmt/expfmt_test.go diff --git a/expfmt/expfmt.go b/expfmt/expfmt.go index 6fc9555e..051b38cd 100644 --- a/expfmt/expfmt.go +++ b/expfmt/expfmt.go @@ -15,6 +15,7 @@ package expfmt import ( + "fmt" "strings" "github.com/prometheus/common/model" @@ -63,7 +64,7 @@ const ( type FormatType int const ( - TypeUnknown = iota + TypeUnknown FormatType = iota TypeProtoCompact TypeProtoDelim TypeProtoText @@ -73,7 +74,8 @@ const ( // NewFormat generates a new Format from the type provided. Mostly used for // tests, most Formats should be generated as part of content negotiation in -// encode.go. +// encode.go. If a type has more than one version, the latest version will be +// returned. func NewFormat(t FormatType) Format { switch t { case TypeProtoCompact: @@ -91,13 +93,21 @@ func NewFormat(t FormatType) Format { } } +// NewOpenMetricsFormat generates a new OpenMetrics format matching the +// specified version number. +func NewOpenMetricsFormat(version string) (Format, error) { + if version == OpenMetricsVersion_0_0_1 { + return fmtOpenMetrics_0_0_1, nil + } + if version == OpenMetricsVersion_1_0_0 { + return fmtOpenMetrics_1_0_0, nil + } + return fmtUnknown, fmt.Errorf("unknown open metrics version string") +} + // FormatType deduces an overall FormatType for the given format. func (f Format) FormatType() FormatType { toks := strings.Split(string(f), ";") - if len(toks) < 2 { - return TypeUnknown - } - params := make(map[string]string) for i, t := range toks { if i == 0 { diff --git a/expfmt/expfmt_test.go b/expfmt/expfmt_test.go new file mode 100644 index 00000000..8ec16524 --- /dev/null +++ b/expfmt/expfmt_test.go @@ -0,0 +1,129 @@ +// Copyright 2024 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package expfmt + +import ( + "testing" + + "github.com/prometheus/common/model" +) + +// Test Format to Escapting Scheme conversion +// Path: expfmt/expfmt_test.go +// Compare this snippet from expfmt/expfmt.go: +func TestToFormatType(t *testing.T) { + tests := []struct { + format Format + expected FormatType + }{ + { + format: fmtProtoCompact, + expected: TypeProtoCompact, + }, + { + format: fmtProtoDelim, + expected: TypeProtoDelim, + }, + { + format: fmtProtoText, + expected: TypeProtoText, + }, + { + format: fmtOpenMetrics_1_0_0, + expected: TypeOpenMetrics, + }, + { + format: fmtText, + expected: TypeTextPlain, + }, + { + format: fmtOpenMetrics_0_0_1, + expected: TypeOpenMetrics, + }, + { + format: "application/vnd.google.protobuf; proto=BadProtocol; encoding=text", + expected: TypeUnknown, + }, + { + format: "application/vnd.google.protobuf", + expected: TypeUnknown, + }, + { + format: "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily=bad", + expected: TypeUnknown, + }, + // encoding missing + { + format: "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily", + expected: TypeUnknown, + }, + // invalid encoding + { + format: "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=textual", + expected: TypeUnknown, + }, + // bad charset, must be utf-8 + { + format: "application/openmetrics-text; version=1.0.0; charset=ascii", + expected: TypeUnknown, + }, + { + format: "text/plain", + expected: TypeTextPlain, + }, + { + format: "text/plain; version=invalid", + expected: TypeUnknown, + }, + { + format: "gobbledygook", + expected: TypeUnknown, + }, + } + for _, test := range tests { + if test.format.FormatType() != test.expected { + t.Errorf("expected %v got %v", test.expected, test.format.FormatType()) + } + } +} + +func TestToEscapingScheme(t *testing.T) { + tests := []struct { + format Format + expected model.EscapingScheme + }{ + { + format: fmtProtoCompact, + expected: model.ValueEncodingEscaping, + }, + { + format: "application/openmetrics-text; version=1.0.0; charset=utf-8; escaping=underscores", + expected: model.UnderscoreEscaping, + }, + { + format: "application/openmetrics-text; version=1.0.0; charset=utf-8; escaping=allow-utf-8", + expected: model.NoEscaping, + }, + // error returns default + { + format: "application/openmetrics-text; version=1.0.0; charset=utf-8; escaping=invalid", + expected: model.NameEscapingScheme, + }, + } + for _, test := range tests { + if test.format.ToEscapingScheme() != test.expected { + t.Errorf("expected %v got %v", test.expected, test.format.ToEscapingScheme()) + } + } +}