prometheus-net

Форк
0
/
ChildBase.cs 
195 строк · 8.1 Кб
1
namespace Prometheus;
2

3
/// <summary>
4
/// Base class for labeled instances of metrics (with all label names and label values defined).
5
/// </summary>
6
public abstract class ChildBase : ICollectorChild, IDisposable
7
{
8
    internal ChildBase(Collector parent, LabelSequence instanceLabels, LabelSequence flattenedLabels, bool publish, ExemplarBehavior exemplarBehavior)
9
    {
10
        Parent = parent;
11
        InstanceLabels = instanceLabels;
12
        FlattenedLabels = flattenedLabels;
13
        _publish = publish;
14
        _exemplarBehavior = exemplarBehavior;
15
    }
16

17
    private readonly ExemplarBehavior _exemplarBehavior;
18

19
    /// <summary>
20
    /// Marks the metric as one to be published, even if it might otherwise be suppressed.
21
    /// 
22
    /// This is useful for publishing zero-valued metrics once you have loaded data on startup and determined
23
    /// that there is no need to increment the value of the metric.
24
    /// </summary>
25
    /// <remarks>
26
    /// Subclasses must call this when their value is first set, to mark the metric as published.
27
    /// </remarks>
28
    public void Publish()
29
    {
30
        Volatile.Write(ref _publish, true);
31
    }
32

33
    /// <summary>
34
    /// Marks the metric as one to not be published.
35
    /// 
36
    /// The metric will be published when Publish() is called or the value is updated.
37
    /// </summary>
38
    public void Unpublish()
39
    {
40
        Volatile.Write(ref _publish, false);
41
    }
42

43
    /// <summary>
44
    /// Removes this labeled instance from metrics.
45
    /// It will no longer be published and any existing measurements/buckets will be discarded.
46
    /// </summary>
47
    public void Remove()
48
    {
49
        Parent.RemoveLabelled(InstanceLabels);
50
    }
51

52
    public void Dispose() => Remove();
53

54
    /// <summary>
55
    /// Labels specific to this metric instance, without any inherited static labels.
56
    /// Internal for testing purposes only.
57
    /// </summary>
58
    internal LabelSequence InstanceLabels { get; }
59

60
    /// <summary>
61
    /// All labels that materialize on this metric instance, including inherited static labels.
62
    /// Internal for testing purposes only.
63
    /// </summary>
64
    internal LabelSequence FlattenedLabels { get; }
65

66
    internal byte[] FlattenedLabelsBytes => NonCapturingLazyInitializer.EnsureInitialized(ref _flattenedLabelsBytes, this, _assignFlattenedLabelsBytesFunc)!;
67
    private byte[]? _flattenedLabelsBytes;
68
    private static readonly Action<ChildBase> _assignFlattenedLabelsBytesFunc = AssignFlattenedLabelsBytes;
69
    private static void AssignFlattenedLabelsBytes(ChildBase instance) => instance._flattenedLabelsBytes = instance.FlattenedLabels.Serialize();
70

71
    internal readonly Collector Parent;
72

73
    private bool _publish;
74

75
    /// <summary>
76
    /// Collects all the metric data rows from this collector and serializes it using the given serializer.
77
    /// </summary>
78
    /// <remarks>
79
    /// Subclass must check _publish and suppress output if it is false.
80
    /// </remarks>
81
    internal ValueTask CollectAndSerializeAsync(IMetricsSerializer serializer, CancellationToken cancel)
82
    {
83
        if (!Volatile.Read(ref _publish))
84
            return default;
85

86
        return CollectAndSerializeImplAsync(serializer, cancel);
87
    }
88

89
    // Same as above, just only called if we really need to serialize this metric (if publish is true).
90
    private protected abstract ValueTask CollectAndSerializeImplAsync(IMetricsSerializer serializer, CancellationToken cancel);
91

92
    /// <summary>
93
    /// Borrows an exemplar temporarily, to be later returned via ReturnBorrowedExemplar.
94
    /// Borrowing ensures that no other thread is modifying it (as exemplars are not thread-safe).
95
    /// You would typically want to do this while serializing the exemplar.
96
    /// </summary>
97
    internal static ObservedExemplar BorrowExemplar(ref ObservedExemplar storage)
98
    {
99
        return Interlocked.Exchange(ref storage, ObservedExemplar.Empty);
100
    }
101

102
    /// <summary>
103
    /// Returns a borrowed exemplar to storage or the object pool, with correct handling for cases where it is Empty.
104
    /// </summary>
105
    internal static void ReturnBorrowedExemplar(ref ObservedExemplar storage, ObservedExemplar borrowed)
106
    {
107
        if (borrowed == ObservedExemplar.Empty)
108
            return;
109

110
        // Return the exemplar unless a new one has arrived, in which case we discard the old one we were holding.
111
        var foundExemplar = Interlocked.CompareExchange(ref storage, borrowed, ObservedExemplar.Empty);
112

113
        if (foundExemplar != ObservedExemplar.Empty)
114
        {
115
            // A new exemplar had already been written, so we could not return the borrowed one. That's perfectly fine - discard it.
116
            ObservedExemplar.ReturnPooledIfNotEmpty(borrowed);
117
        }
118
    }
119

120
    internal void RecordExemplar(Exemplar exemplar, ref ObservedExemplar storage, double observedValue)
121
    {
122
        exemplar.MarkAsConsumed();
123

124
        // We do the "is allowed" check only if we really have an exemplar to record, to minimize the performance impact on users who do not use exemplars.
125
        // If you are using exemplars, you are already paying for a lot of value serialization overhead, so this is insignificant.
126
        // Whereas if you are not using exemplars, the difference from this simple check can be substantial.
127
        if (!IsRecordingNewExemplarAllowed())
128
        {
129
            // We will not record the exemplar but must still release the resources to the pool.
130
            exemplar.ReturnToPoolIfNotEmpty();
131
            return;
132
        }
133

134
        // ObservedExemplar takes ownership of the Exemplar and will return its resources to the pool when the time is right.
135
        var observedExemplar = ObservedExemplar.CreatePooled(exemplar, observedValue);
136
        ObservedExemplar.ReturnPooledIfNotEmpty(Interlocked.Exchange(ref storage, observedExemplar));
137
        MarkNewExemplarHasBeenRecorded();
138

139
        // We cannot record an exemplar every time we record an exemplar!
140
        Volatile.Read(ref ExemplarsRecorded)?.Inc(Exemplar.None);
141
    }
142

143
    protected Exemplar GetDefaultExemplar(double value)
144
    {
145
        if (_exemplarBehavior.DefaultExemplarProvider == null)
146
            return Exemplar.None;
147

148
        return _exemplarBehavior.DefaultExemplarProvider(Parent, value);
149
    }
150

151
    // May be replaced in test code.
152
    internal static Func<double> ExemplarRecordingTimestampProvider = DefaultExemplarRecordingTimestampProvider;
153
    internal static double DefaultExemplarRecordingTimestampProvider() => LowGranularityTimeSource.GetSecondsFromUnixEpoch();
154

155
    // Timetamp of when we last recorded an exemplar. We do not use ObservedExemplar.Timestamp because we do not want to
156
    // read from an existing ObservedExemplar when we are writing to our metrics (to avoid the synchronization overhead).
157
    // We start at a deep enough negative value to not cause funny behavior near zero point (only likely in tests, really).
158
    private ThreadSafeDouble _exemplarLastRecordedTimestamp = new(-100_000_000);
159

160
    protected bool IsRecordingNewExemplarAllowed()
161
    {
162
        if (_exemplarBehavior.NewExemplarMinInterval <= TimeSpan.Zero)
163
            return true;
164

165
        var elapsedSeconds = ExemplarRecordingTimestampProvider() - _exemplarLastRecordedTimestamp.Value;
166

167
        return elapsedSeconds >= _exemplarBehavior.NewExemplarMinInterval.TotalSeconds;
168
    }
169

170
    protected void MarkNewExemplarHasBeenRecorded()
171
    {
172
        if (_exemplarBehavior.NewExemplarMinInterval <= TimeSpan.Zero)
173
            return; // No need to record the timestamp if we are not enforcing a minimum interval.
174

175
        _exemplarLastRecordedTimestamp.Value = ExemplarRecordingTimestampProvider();
176
    }
177

178

179
    // This is only set if and when debug metrics are enabled in the default registry.
180
    private static Counter? ExemplarsRecorded;
181

182
    static ChildBase()
183
    {
184
        Metrics.DefaultRegistry.OnStartCollectingRegistryMetrics(delegate
185
        {
186
            Volatile.Write(ref ExemplarsRecorded, Metrics.CreateCounter("prometheus_net_exemplars_recorded_total", "Number of exemplars that were accepted into in-memory storage in the prometheus-net SDK."));
187
        });
188
    }
189

190
    public override string ToString()
191
    {
192
        // Just for debugging.
193
        return $"{Parent.Name}{{{FlattenedLabels}}}";
194
    }
195
}

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

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

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

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