prometheus-net
195 строк · 8.1 Кб
1namespace Prometheus;2
3/// <summary>
4/// Base class for labeled instances of metrics (with all label names and label values defined).
5/// </summary>
6public abstract class ChildBase : ICollectorChild, IDisposable7{
8internal ChildBase(Collector parent, LabelSequence instanceLabels, LabelSequence flattenedLabels, bool publish, ExemplarBehavior exemplarBehavior)9{10Parent = parent;11InstanceLabels = instanceLabels;12FlattenedLabels = flattenedLabels;13_publish = publish;14_exemplarBehavior = exemplarBehavior;15}16
17private 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 determined23/// 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>28public void Publish()29{30Volatile.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>38public void Unpublish()39{40Volatile.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>47public void Remove()48{49Parent.RemoveLabelled(InstanceLabels);50}51
52public 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>58internal 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>64internal LabelSequence FlattenedLabels { get; }65
66internal byte[] FlattenedLabelsBytes => NonCapturingLazyInitializer.EnsureInitialized(ref _flattenedLabelsBytes, this, _assignFlattenedLabelsBytesFunc)!;67private byte[]? _flattenedLabelsBytes;68private static readonly Action<ChildBase> _assignFlattenedLabelsBytesFunc = AssignFlattenedLabelsBytes;69private static void AssignFlattenedLabelsBytes(ChildBase instance) => instance._flattenedLabelsBytes = instance.FlattenedLabels.Serialize();70
71internal readonly Collector Parent;72
73private 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>81internal ValueTask CollectAndSerializeAsync(IMetricsSerializer serializer, CancellationToken cancel)82{83if (!Volatile.Read(ref _publish))84return default;85
86return CollectAndSerializeImplAsync(serializer, cancel);87}88
89// Same as above, just only called if we really need to serialize this metric (if publish is true).90private 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>97internal static ObservedExemplar BorrowExemplar(ref ObservedExemplar storage)98{99return 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>105internal static void ReturnBorrowedExemplar(ref ObservedExemplar storage, ObservedExemplar borrowed)106{107if (borrowed == ObservedExemplar.Empty)108return;109
110// Return the exemplar unless a new one has arrived, in which case we discard the old one we were holding.111var foundExemplar = Interlocked.CompareExchange(ref storage, borrowed, ObservedExemplar.Empty);112
113if (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.116ObservedExemplar.ReturnPooledIfNotEmpty(borrowed);117}118}119
120internal void RecordExemplar(Exemplar exemplar, ref ObservedExemplar storage, double observedValue)121{122exemplar.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.127if (!IsRecordingNewExemplarAllowed())128{129// We will not record the exemplar but must still release the resources to the pool.130exemplar.ReturnToPoolIfNotEmpty();131return;132}133
134// ObservedExemplar takes ownership of the Exemplar and will return its resources to the pool when the time is right.135var observedExemplar = ObservedExemplar.CreatePooled(exemplar, observedValue);136ObservedExemplar.ReturnPooledIfNotEmpty(Interlocked.Exchange(ref storage, observedExemplar));137MarkNewExemplarHasBeenRecorded();138
139// We cannot record an exemplar every time we record an exemplar!140Volatile.Read(ref ExemplarsRecorded)?.Inc(Exemplar.None);141}142
143protected Exemplar GetDefaultExemplar(double value)144{145if (_exemplarBehavior.DefaultExemplarProvider == null)146return Exemplar.None;147
148return _exemplarBehavior.DefaultExemplarProvider(Parent, value);149}150
151// May be replaced in test code.152internal static Func<double> ExemplarRecordingTimestampProvider = DefaultExemplarRecordingTimestampProvider;153internal 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 to156// 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).158private ThreadSafeDouble _exemplarLastRecordedTimestamp = new(-100_000_000);159
160protected bool IsRecordingNewExemplarAllowed()161{162if (_exemplarBehavior.NewExemplarMinInterval <= TimeSpan.Zero)163return true;164
165var elapsedSeconds = ExemplarRecordingTimestampProvider() - _exemplarLastRecordedTimestamp.Value;166
167return elapsedSeconds >= _exemplarBehavior.NewExemplarMinInterval.TotalSeconds;168}169
170protected void MarkNewExemplarHasBeenRecorded()171{172if (_exemplarBehavior.NewExemplarMinInterval <= TimeSpan.Zero)173return; // 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.180private static Counter? ExemplarsRecorded;181
182static ChildBase()183{184Metrics.DefaultRegistry.OnStartCollectingRegistryMetrics(delegate185{186Volatile.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
190public override string ToString()191{192// Just for debugging.193return $"{Parent.Name}{{{FlattenedLabels}}}";194}195}