prometheus-net

Форк
0
304 строки · 10.8 Кб
1
using System.Diagnostics;
2
using System.Runtime.CompilerServices;
3
using System.Text;
4
using Microsoft.Extensions.ObjectPool;
5

6
namespace Prometheus;
7

8
/// <summary>
9
/// A fully-formed exemplar, describing a set of label name-value pairs.
10
/// 
11
/// One-time use only - when you pass an instance to a prometheus-net method, it will take ownership of it.
12
/// 
13
/// You should preallocate and cache:
14
/// 1. The exemplar keys created via Exemplar.Key().
15
/// 2. Exemplar key-value pairs created vvia key.WithValue() or Exemplar.Pair().
16
/// 
17
/// From the key-value pairs you can create one-use Exemplar values using Exemplar.From().
18
/// You can clone Exemplar instances using Exemplar.Clone() - each clone can only be used once!
19
/// </summary>
20
public sealed class Exemplar
21
{
22
    /// <summary>
23
    /// Indicates that no exemplar is to be recorded for a given observation.
24
    /// </summary>
25
    public static readonly Exemplar None = new(0);
26

27
    /// <summary>
28
    /// An exemplar label key. For optimal performance, create it once and reuse it forever.
29
    /// </summary>
30
    public readonly struct LabelKey
31
    {
32
        internal LabelKey(byte[] key)
33
        {
34
            Bytes = key;
35
        }
36

37
        // We only support ASCII here, so rune count always matches byte count.
38
        internal int RuneCount => Bytes.Length;
39

40
        internal byte[] Bytes { get; }
41

42
        /// <summary>
43
        /// Create a LabelPair once a value is available
44
        ///
45
        /// The string is expected to only contain runes in the ASCII range, runes outside the ASCII range will get replaced
46
        /// with placeholders. This constraint may be relaxed with future versions.
47
        /// </summary>
48
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
49
        public LabelPair WithValue(string value)
50
        {
51
            static bool IsAscii(ReadOnlySpan<char> chars)
52
            {
53
                for (var i = 0; i < chars.Length; i++)
54
                    if (chars[i] > 127)
55
                        return false;
56

57
                return true;
58
            }
59

60
            if (!IsAscii(value.AsSpan()))
61
            {
62
                // We believe that approximately 100% of use cases only consist of ASCII characters.
63
                // That being said, we do not want to throw an exception here as the value may be coming from external sources
64
                // that calling code has little control over. Therefore, we just replace such characters with placeholders.
65
                // This matches the default behavior of Encoding.ASCII.GetBytes() - it replaces non-ASCII characters with '?'.
66
                // As this is a highly theoretical case, we do an inefficient conversion here using the built-in encoder.
67
                value = Encoding.ASCII.GetString(Encoding.ASCII.GetBytes(value));
68
            }
69

70
            return new LabelPair(Bytes, value);
71
        }
72
    }
73

74
    /// <summary>
75
    /// A single exemplar label pair in a form suitable for efficient serialization.
76
    /// If you wish to reuse the same key-value pair, you should reuse this object as much as possible.
77
    /// </summary>
78
    public readonly struct LabelPair
79
    {
80
        internal LabelPair(byte[] keyBytes, string value)
81
        {
82
            KeyBytes = keyBytes;
83
            Value = value;
84
        }
85

86
        internal int RuneCount => KeyBytes.Length + Value.Length;
87
        internal byte[] KeyBytes { get; }
88

89
        // We keep the value as a string because it typically starts out its life as a string
90
        // and we want to avoid paying the cost of converting it to a byte array until we serialize it.
91
        // If we record many exemplars then we may, in fact, never serialize most of them because they get replaced.
92
        internal string Value { get; }
93
    }
94

95
    /// <summary>
96
    /// Return an exemplar label key, this may be curried with a value to produce a LabelPair.
97
    /// Reuse this for optimal performance.
98
    /// </summary>
99
    public static LabelKey Key(string key)
100
    {
101
        if (string.IsNullOrWhiteSpace(key))
102
            throw new ArgumentException("empty key", nameof(key));
103

104
        Collector.ValidateLabelName(key);
105

106
        var asciiBytes = Encoding.ASCII.GetBytes(key);
107
        return new LabelKey(asciiBytes);
108
    }
109

110
    /// <summary>
111
    /// Pair constructs a LabelPair, it is advisable to memoize a "Key" (eg: "traceID") and then to derive "LabelPair"s
112
    /// from these. You may (should) reuse a LabelPair for recording multiple observations that use the same exemplar.
113
    /// </summary>
114
    public static LabelPair Pair(string key, string value)
115
    {
116
        return Key(key).WithValue(value);
117
    }
118

119
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
120
    public static Exemplar From(in LabelPair labelPair1, in LabelPair labelPair2, in LabelPair labelPair3, in LabelPair labelPair4, in LabelPair labelPair5, in LabelPair labelPair6)
121
    {
122
        var exemplar = Exemplar.AllocateFromPool(length: 6);
123
        exemplar.LabelPair1 = labelPair1;
124
        exemplar.LabelPair2 = labelPair2;
125
        exemplar.LabelPair3 = labelPair3;
126
        exemplar.LabelPair4 = labelPair4;
127
        exemplar.LabelPair5 = labelPair5;
128
        exemplar.LabelPair6 = labelPair6;
129

130
        return exemplar;
131
    }
132

133
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
134
    public static Exemplar From(in LabelPair labelPair1, in LabelPair labelPair2, in LabelPair labelPair3, in LabelPair labelPair4, in LabelPair labelPair5)
135
    {
136
        var exemplar = Exemplar.AllocateFromPool(length: 5);
137
        exemplar.LabelPair1 = labelPair1;
138
        exemplar.LabelPair2 = labelPair2;
139
        exemplar.LabelPair3 = labelPair3;
140
        exemplar.LabelPair4 = labelPair4;
141
        exemplar.LabelPair5 = labelPair5;
142

143
        return exemplar;
144
    }
145

146
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
147
    public static Exemplar From(in LabelPair labelPair1, in LabelPair labelPair2, in LabelPair labelPair3, in LabelPair labelPair4)
148
    {
149
        var exemplar = Exemplar.AllocateFromPool(length: 4);
150
        exemplar.LabelPair1 = labelPair1;
151
        exemplar.LabelPair2 = labelPair2;
152
        exemplar.LabelPair3 = labelPair3;
153
        exemplar.LabelPair4 = labelPair4;
154

155
        return exemplar;
156
    }
157

158
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
159
    public static Exemplar From(in LabelPair labelPair1, in LabelPair labelPair2, in LabelPair labelPair3)
160
    {
161
        var exemplar = Exemplar.AllocateFromPool(length: 3);
162
        exemplar.LabelPair1 = labelPair1;
163
        exemplar.LabelPair2 = labelPair2;
164
        exemplar.LabelPair3 = labelPair3;
165

166
        return exemplar;
167
    }
168

169
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
170
    public static Exemplar From(in LabelPair labelPair1, in LabelPair labelPair2)
171
    {
172
        var exemplar = Exemplar.AllocateFromPool(length: 2);
173
        exemplar.LabelPair1 = labelPair1;
174
        exemplar.LabelPair2 = labelPair2;
175

176
        return exemplar;
177
    }
178

179
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
180
    public static Exemplar From(in LabelPair labelPair1)
181
    {
182
        var exemplar = Exemplar.AllocateFromPool(length: 1);
183
        exemplar.LabelPair1 = labelPair1;
184

185
        return exemplar;
186
    }
187

188
    internal ref LabelPair this[int index]
189
    {
190
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
191
        get
192
        {
193
            if (index == 0) return ref LabelPair1;
194
            if (index == 1) return ref LabelPair2;
195
            if (index == 2) return ref LabelPair3;
196
            if (index == 3) return ref LabelPair4;
197
            if (index == 4) return ref LabelPair5;
198
            if (index == 5) return ref LabelPair6;
199
            throw new ArgumentOutOfRangeException(nameof(index));
200
        }
201
    }
202

203
    // Based on https://opentelemetry.io/docs/reference/specification/compatibility/prometheus_and_openmetrics/
204
    private static readonly LabelKey DefaultTraceIdKey = Key("trace_id");
205
    private static readonly LabelKey DefaultSpanIdKey = Key("span_id");
206

207
    public static Exemplar FromTraceContext() => FromTraceContext(DefaultTraceIdKey, DefaultSpanIdKey);
208

209
    public static Exemplar FromTraceContext(in LabelKey traceIdKey, in LabelKey spanIdKey)
210
    {
211
#if NET6_0_OR_GREATER
212
        var activity = Activity.Current;
213
        if (activity != null)
214
        {
215
            // These values already exist as strings inside the Activity logic, so there is no string allocation happening here.
216
            var traceIdLabel = traceIdKey.WithValue(activity.TraceId.ToString());
217
            var spanIdLabel = spanIdKey.WithValue(activity.SpanId.ToString());
218

219
            return From(traceIdLabel, spanIdLabel);
220
        }
221
#endif
222

223
        // Trace context based exemplars are only supported in .NET Core, not .NET Framework.
224
        return None;
225
    }
226

227
    public Exemplar()
228
    {
229
    }
230

231
    private Exemplar(int length)
232
    {
233
        Length = length;
234
    }
235

236
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
237
    internal void Update(int length)
238
    {
239
        Length = length;
240
        Interlocked.Exchange(ref _consumed, IsNotConsumed);
241
    }
242

243
    /// <summary>
244
    /// Number of label pairs in use.
245
    /// </summary>
246
    internal int Length { get; private set; }
247

248
    internal LabelPair LabelPair1;
249
    internal LabelPair LabelPair2;
250
    internal LabelPair LabelPair3;
251
    internal LabelPair LabelPair4;
252
    internal LabelPair LabelPair5;
253
    internal LabelPair LabelPair6;
254

255
    private static readonly ObjectPool<Exemplar> ExemplarPool = ObjectPool.Create<Exemplar>();
256

257
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
258
    internal static Exemplar AllocateFromPool(int length)
259
    {
260
        var instance = ExemplarPool.Get();
261
        instance.Update(length);
262
        return instance;
263
    }
264

265
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
266
    internal void ReturnToPoolIfNotEmpty()
267
    {
268
        if (Length == 0)
269
            return; // Only the None instance can have a length of 0.
270

271
        Length = 0;
272

273
        ExemplarPool.Return(this);
274
    }
275

276
    private long _consumed;
277

278
    private const long IsConsumed = 1;
279
    private const long IsNotConsumed = 0;
280

281
    internal void MarkAsConsumed()
282
    {
283
        if (Interlocked.Exchange(ref _consumed, IsConsumed) == IsConsumed)
284
            throw new InvalidOperationException($"An instance of {nameof(Exemplar)} was reused. You must obtain a new instance via Exemplar.From() or Exemplar.Clone() for each metric value observation.");
285
    }
286

287
    /// <summary>
288
    /// Clones the exemplar so it can be reused - each copy can only be used once!
289
    /// </summary>
290
    public Exemplar Clone()
291
    {
292
        if (Interlocked.Read(ref _consumed) == IsConsumed)
293
            throw new InvalidOperationException($"An instance of {nameof(Exemplar)} cannot be cloned after it has already been used.");
294

295
        var clone = AllocateFromPool(Length);
296
        clone.LabelPair1 = LabelPair1;
297
        clone.LabelPair2 = LabelPair2;
298
        clone.LabelPair3 = LabelPair3;
299
        clone.LabelPair4 = LabelPair4;
300
        clone.LabelPair5 = LabelPair5;
301
        clone.LabelPair6 = LabelPair6;
302
        return clone;
303
    }
304
}

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.