prometheus-net
93 строки · 3.0 Кб
1using Microsoft.Extensions.ObjectPool;
2using System.Diagnostics;
3
4namespace Prometheus;
5
6/// <summary>
7/// Internal representation of an Exemplar ready to be serialized.
8/// </summary>
9internal sealed class ObservedExemplar
10{
11/// <summary>
12/// OpenMetrics places a length limit of 128 runes on the exemplar (sum of all key value pairs).
13/// </summary>
14private const int MaxRunes = 128;
15
16/// <summary>
17/// We have a pool of unused instances that we can reuse, to avoid constantly allocating memory. Once the set of metrics stabilizes,
18/// all allocations should generally be coming from the pool. We expect the default pool configuratiopn to be suitable for this.
19/// </summary>
20private static readonly ObjectPool<ObservedExemplar> Pool = ObjectPool.Create<ObservedExemplar>();
21
22public static readonly ObservedExemplar Empty = new();
23
24internal static Func<double> NowProvider = DefaultNowProvider;
25internal static double DefaultNowProvider() => LowGranularityTimeSource.GetSecondsFromUnixEpoch();
26
27public Exemplar? Labels { get; private set; }
28public double Value { get; private set; }
29public double Timestamp { get; private set; }
30
31public ObservedExemplar()
32{
33Labels = null;
34Value = 0;
35Timestamp = 0;
36}
37
38public bool IsValid => Labels != null;
39
40private void Update(Exemplar labels, double value)
41{
42Debug.Assert(this != Empty, "Do not mutate the sentinel");
43
44var totalRuneCount = 0;
45
46for (var i = 0; i < labels.Length; i++)
47{
48totalRuneCount += labels[i].RuneCount;
49for (var j = 0; j < labels.Length; j++)
50{
51if (i == j) continue;
52if (ByteArraysEqual(labels[i].KeyBytes, labels[j].KeyBytes))
53throw new ArgumentException("Exemplar contains duplicate keys.");
54}
55}
56
57if (totalRuneCount > MaxRunes)
58throw new ArgumentException($"Exemplar consists of {totalRuneCount} runes, exceeding the OpenMetrics limit of {MaxRunes}.");
59
60Labels = labels;
61Value = value;
62Timestamp = NowProvider();
63}
64
65private static bool ByteArraysEqual(byte[] a, byte[] b)
66{
67if (a.Length != b.Length) return false;
68
69for (var i = 0; i < a.Length; i++)
70if (a[i] != b[i]) return false;
71
72return true;
73}
74
75/// <remarks>
76/// Takes ownership of the labels and will destroy them when the instance is returned to the pool.
77/// </remarks>
78public static ObservedExemplar CreatePooled(Exemplar labels, double value)
79{
80var instance = Pool.Get();
81instance.Update(labels, value);
82return instance;
83}
84
85public static void ReturnPooledIfNotEmpty(ObservedExemplar instance)
86{
87if (object.ReferenceEquals(instance, Empty))
88return; // We never put the "Empty" instance into the pool. Do the check here to avoid repeating it any time we return instances to the pool.
89
90instance.Labels?.ReturnToPoolIfNotEmpty();
91Pool.Return(instance);
92}
93}