blob: 6be7a3f6b61eb89cbdf861cd8a15c1aeee2d9b7a [file] [log] [blame]
Joey Armstrong903c69d2024-02-01 19:46:39 -05001// Copyright The OpenTelemetry Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package label
16
17import (
18 "bytes"
19 "sync"
20 "sync/atomic"
21)
22
23type (
24 // Encoder is a mechanism for serializing a label set into a
25 // specific string representation that supports caching, to
26 // avoid repeated serialization. An example could be an
27 // exporter encoding the label set into a wire representation.
28 Encoder interface {
29 // Encode returns the serialized encoding of the label
30 // set using its Iterator. This result may be cached
31 // by a label.Set.
32 Encode(iterator Iterator) string
33
34 // ID returns a value that is unique for each class of
35 // label encoder. Label encoders allocate these using
36 // `NewEncoderID`.
37 ID() EncoderID
38 }
39
40 // EncoderID is used to identify distinct Encoder
41 // implementations, for caching encoded results.
42 EncoderID struct {
43 value uint64
44 }
45
46 // defaultLabelEncoder uses a sync.Pool of buffers to reduce
47 // the number of allocations used in encoding labels. This
48 // implementation encodes a comma-separated list of key=value,
49 // with '/'-escaping of '=', ',', and '\'.
50 defaultLabelEncoder struct {
51 // pool is a pool of labelset builders. The buffers in this
52 // pool grow to a size that most label encodings will not
53 // allocate new memory.
54 pool sync.Pool // *bytes.Buffer
55 }
56)
57
58// escapeChar is used to ensure uniqueness of the label encoding where
59// keys or values contain either '=' or ','. Since there is no parser
60// needed for this encoding and its only requirement is to be unique,
61// this choice is arbitrary. Users will see these in some exporters
62// (e.g., stdout), so the backslash ('\') is used as a conventional choice.
63const escapeChar = '\\'
64
65var (
66 _ Encoder = &defaultLabelEncoder{}
67
68 // encoderIDCounter is for generating IDs for other label
69 // encoders.
70 encoderIDCounter uint64
71
72 defaultEncoderOnce sync.Once
73 defaultEncoderID = NewEncoderID()
74 defaultEncoderInstance *defaultLabelEncoder
75)
76
77// NewEncoderID returns a unique label encoder ID. It should be
78// called once per each type of label encoder. Preferably in init() or
79// in var definition.
80func NewEncoderID() EncoderID {
81 return EncoderID{value: atomic.AddUint64(&encoderIDCounter, 1)}
82}
83
84// DefaultEncoder returns a label encoder that encodes labels
85// in such a way that each escaped label's key is followed by an equal
86// sign and then by an escaped label's value. All key-value pairs are
87// separated by a comma.
88//
89// Escaping is done by prepending a backslash before either a
90// backslash, equal sign or a comma.
91func DefaultEncoder() Encoder {
92 defaultEncoderOnce.Do(func() {
93 defaultEncoderInstance = &defaultLabelEncoder{
94 pool: sync.Pool{
95 New: func() interface{} {
96 return &bytes.Buffer{}
97 },
98 },
99 }
100 })
101 return defaultEncoderInstance
102}
103
104// Encode is a part of an implementation of the LabelEncoder
105// interface.
106func (d *defaultLabelEncoder) Encode(iter Iterator) string {
107 buf := d.pool.Get().(*bytes.Buffer)
108 defer d.pool.Put(buf)
109 buf.Reset()
110
111 for iter.Next() {
112 i, keyValue := iter.IndexedLabel()
113 if i > 0 {
114 _, _ = buf.WriteRune(',')
115 }
116 copyAndEscape(buf, string(keyValue.Key))
117
118 _, _ = buf.WriteRune('=')
119
120 if keyValue.Value.Type() == STRING {
121 copyAndEscape(buf, keyValue.Value.AsString())
122 } else {
123 _, _ = buf.WriteString(keyValue.Value.Emit())
124 }
125 }
126 return buf.String()
127}
128
129// ID is a part of an implementation of the LabelEncoder interface.
130func (*defaultLabelEncoder) ID() EncoderID {
131 return defaultEncoderID
132}
133
134// copyAndEscape escapes `=`, `,` and its own escape character (`\`),
135// making the default encoding unique.
136func copyAndEscape(buf *bytes.Buffer, val string) {
137 for _, ch := range val {
138 switch ch {
139 case '=', ',', escapeChar:
140 buf.WriteRune(escapeChar)
141 }
142 buf.WriteRune(ch)
143 }
144}
145
146// Valid returns true if this encoder ID was allocated by
147// `NewEncoderID`. Invalid encoder IDs will not be cached.
148func (id EncoderID) Valid() bool {
149 return id.value != 0
150}