Skip to content

Commit 0940b71

Browse files
committed
UTF-8 support in metric and label names
Signed-off-by: Owen Williams <owen.williams@grafana.com>
1 parent 686da34 commit 0940b71

14 files changed

+719
-317
lines changed

config/http_config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import (
3030
"sync"
3131
"time"
3232

33-
"github.com/mwitkow/go-conntrack"
33+
conntrack "github.com/mwitkow/go-conntrack"
3434
"golang.org/x/net/http/httpproxy"
3535
"golang.org/x/net/http2"
3636
"golang.org/x/oauth2"

expfmt/decode_test.go

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"bufio"
1818
"errors"
1919
"io"
20+
"math"
2021
"net/http"
2122
"reflect"
2223
"sort"
@@ -105,9 +106,10 @@ func TestProtoDecoder(t *testing.T) {
105106
var testTime = model.Now()
106107

107108
scenarios := []struct {
108-
in string
109-
expected model.Vector
110-
fail bool
109+
in string
110+
expected model.Vector
111+
legacyNameFail bool
112+
fail bool
111113
}{
112114
{
113115
in: "",
@@ -333,6 +335,30 @@ func TestProtoDecoder(t *testing.T) {
333335
},
334336
},
335337
},
338+
{
339+
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",
340+
legacyNameFail: true,
341+
expected: model.Vector{
342+
&model.Sample{
343+
Metric: model.Metric{
344+
model.MetricNameLabel: "gauge.name",
345+
"name.1": "val with\nnew line",
346+
"name*2": "val with \\backslash and \"quotes\"",
347+
},
348+
Value: model.SampleValue(math.Inf(+1)),
349+
Timestamp: testTime,
350+
},
351+
&model.Sample{
352+
Metric: model.Metric{
353+
model.MetricNameLabel: "gauge.name",
354+
"name.1": "Björn",
355+
"name*2": "佖佥",
356+
},
357+
Value: 3.14e42,
358+
Timestamp: testTime,
359+
},
360+
},
361+
},
336362
}
337363

338364
for i, scenario := range scenarios {
@@ -345,11 +371,31 @@ func TestProtoDecoder(t *testing.T) {
345371

346372
var all model.Vector
347373
for {
374+
model.NameValidationScheme = model.LegacyValidation
348375
var smpls model.Vector
349376
err := dec.Decode(&smpls)
350377
if err != nil && errors.Is(err, io.EOF) {
351378
break
352379
}
380+
if scenario.legacyNameFail {
381+
if err == nil {
382+
t.Fatal("Expected error when decoding without UTF-8 support enabled but got none")
383+
}
384+
model.NameValidationScheme = model.UTF8Validation
385+
dec = &SampleDecoder{
386+
Dec: &protoDecoder{r: strings.NewReader(scenario.in)},
387+
Opts: &DecodeOptions{
388+
Timestamp: testTime,
389+
},
390+
}
391+
err = dec.Decode(&smpls)
392+
if errors.Is(err, io.EOF) {
393+
break
394+
}
395+
if err != nil {
396+
t.Fatalf("Unexpected error when decoding with UTF-8 support: %v", err)
397+
}
398+
}
353399
if scenario.fail {
354400
if err == nil {
355401
t.Fatal("Expected error but got none")
@@ -436,7 +482,7 @@ func TestExtractSamples(t *testing.T) {
436482
Help: proto.String("Help for foo."),
437483
Type: dto.MetricType_COUNTER.Enum(),
438484
Metric: []*dto.Metric{
439-
&dto.Metric{
485+
{
440486
Counter: &dto.Counter{
441487
Value: proto.Float64(4711),
442488
},
@@ -448,7 +494,7 @@ func TestExtractSamples(t *testing.T) {
448494
Help: proto.String("Help for bar."),
449495
Type: dto.MetricType_GAUGE.Enum(),
450496
Metric: []*dto.Metric{
451-
&dto.Metric{
497+
{
452498
Gauge: &dto.Gauge{
453499
Value: proto.Float64(3.14),
454500
},
@@ -460,7 +506,7 @@ func TestExtractSamples(t *testing.T) {
460506
Help: proto.String("Help for bad."),
461507
Type: dto.MetricType(42).Enum(),
462508
Metric: []*dto.Metric{
463-
&dto.Metric{
509+
{
464510
Gauge: &dto.Gauge{
465511
Value: proto.Float64(2.7),
466512
},

expfmt/openmetrics_create.go

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ import (
3535
// sanity checks. If the input contains duplicate metrics or invalid metric or
3636
// label names, the conversion will result in invalid text format output.
3737
//
38+
// If metric names conform to the legacy validation pattern, they will be placed
39+
// outside the brackets in the traditional way, like `foo{}`. If the metric name
40+
// fails the legacy validation check, it will be placed quoted inside the
41+
// brackets: `{"foo"}`. As stated above, the input is assumed to be santized and
42+
// no error will be thrown in this case.
43+
//
3844
// This function fulfills the type 'expfmt.encoder'.
3945
//
4046
// Note that OpenMetrics requires a final `# EOF` line. Since this function acts
@@ -98,7 +104,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int
98104
if err != nil {
99105
return
100106
}
101-
n, err = w.WriteString(shortName)
107+
n, err = writeName(w, shortName)
102108
written += n
103109
if err != nil {
104110
return
@@ -124,7 +130,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int
124130
if err != nil {
125131
return
126132
}
127-
n, err = w.WriteString(shortName)
133+
n, err = writeName(w, shortName)
128134
written += n
129135
if err != nil {
130136
return
@@ -303,21 +309,9 @@ func writeOpenMetricsSample(
303309
floatValue float64, intValue uint64, useIntValue bool,
304310
exemplar *dto.Exemplar,
305311
) (int, error) {
306-
var written int
307-
n, err := w.WriteString(name)
308-
written += n
309-
if err != nil {
310-
return written, err
311-
}
312-
if suffix != "" {
313-
n, err = w.WriteString(suffix)
314-
written += n
315-
if err != nil {
316-
return written, err
317-
}
318-
}
319-
n, err = writeOpenMetricsLabelPairs(
320-
w, metric.Label, additionalLabelName, additionalLabelValue,
312+
written := 0
313+
n, err := writeOpenMetricsNameAndLabelPairs(
314+
w, name+suffix, metric.Label, additionalLabelName, additionalLabelValue,
321315
)
322316
written += n
323317
if err != nil {
@@ -365,27 +359,58 @@ func writeOpenMetricsSample(
365359
return written, nil
366360
}
367361

368-
// writeOpenMetricsLabelPairs works like writeOpenMetrics but formats the float
369-
// in OpenMetrics style.
370-
func writeOpenMetricsLabelPairs(
362+
// writeOpenMetricsNameAndLabelPairs works like writeOpenMetricsSample but
363+
// formats the float in OpenMetrics style.
364+
func writeOpenMetricsNameAndLabelPairs(
371365
w enhancedWriter,
366+
name string,
372367
in []*dto.LabelPair,
373368
additionalLabelName string, additionalLabelValue float64,
374369
) (int, error) {
375-
if len(in) == 0 && additionalLabelName == "" {
376-
return 0, nil
377-
}
378370
var (
379-
written int
380-
separator byte = '{'
371+
written int
372+
separator byte = '{'
373+
metricInsideBraces = false
381374
)
375+
376+
if name != "" {
377+
// If the name does not pass the legacy validity check, we must put the
378+
// metric name inside the braces, quoted.
379+
if !model.IsValidLegacyMetricName(model.LabelValue(name)) {
380+
metricInsideBraces = true
381+
err := w.WriteByte(separator)
382+
written++
383+
if err != nil {
384+
return written, err
385+
}
386+
separator = ','
387+
}
388+
389+
n, err := writeName(w, name)
390+
written += n
391+
if err != nil {
392+
return written, err
393+
}
394+
}
395+
396+
if len(in) == 0 && additionalLabelName == "" {
397+
if metricInsideBraces {
398+
err := w.WriteByte('}')
399+
written++
400+
if err != nil {
401+
return written, err
402+
}
403+
}
404+
return written, nil
405+
}
406+
382407
for _, lp := range in {
383408
err := w.WriteByte(separator)
384409
written++
385410
if err != nil {
386411
return written, err
387412
}
388-
n, err := w.WriteString(lp.GetName())
413+
n, err := writeName(w, lp.GetName())
389414
written += n
390415
if err != nil {
391416
return written, err
@@ -451,7 +476,7 @@ func writeExemplar(w enhancedWriter, e *dto.Exemplar) (int, error) {
451476
if err != nil {
452477
return written, err
453478
}
454-
n, err = writeOpenMetricsLabelPairs(w, e.Label, "", 0)
479+
n, err = writeOpenMetricsNameAndLabelPairs(w, "", e.Label, "", 0)
455480
written += n
456481
if err != nil {
457482
return written, err

0 commit comments

Comments
 (0)