prometheus-net
103 строки · 4.7 Кб
1namespace Prometheus;2
3/// <summary>
4/// This class implements two sets of functionality:
5/// 1. A lifetime-managed metric handle that can be used to take leases on the metric.
6/// 2. An automatically-lifetime-extending-on-use metric that creates leases automatically.
7///
8/// While conceptually separate, we merge the two sets into one class to avoid allocating a bunch of small objects
9/// every time you want to obtain a lifetime-extending-on-use metric (which tends to be on a relatively hot path).
10///
11/// The lifetime-extending feature only supports write operations because we cannot guarantee that the metric is still alive when reading.
12/// </summary>
13internal sealed class ManagedLifetimeCounter : ManagedLifetimeMetricHandle<Counter.Child, ICounter>, ICollector<ICounter>14{
15static ManagedLifetimeCounter()16{17_assignUnlabelledFunc = AssignUnlabelled;18}19
20public ManagedLifetimeCounter(Collector<Counter.Child> metric, TimeSpan expiresAfter) : base(metric, expiresAfter)21{22}23
24public override ICollector<ICounter> WithExtendLifetimeOnUse() => this;25
26#region ICollector<ICounter> implementation (for WithExtendLifetimeOnUse)27public string Name => _metric.Name;28public string Help => _metric.Help;29public string[] LabelNames => _metric.LabelNames;30
31public ICounter Unlabelled => NonCapturingLazyInitializer.EnsureInitialized(ref _unlabelled, this, _assignUnlabelledFunc);32private AutoLeasingInstance? _unlabelled;33private static readonly Action<ManagedLifetimeCounter> _assignUnlabelledFunc;34private static void AssignUnlabelled(ManagedLifetimeCounter instance) => instance._unlabelled = new AutoLeasingInstance(instance, Array.Empty<string>());35
36// These do not get cached, so are potentially expensive - user code should try avoiding re-allocating these when possible,37// though admittedly this may not be so easy as often these are on the hot path and the very reason that lifetime-managed38// metrics are used is that we do not have a meaningful way to reuse metrics or identify their lifetime.39public ICounter WithLabels(params string[] labelValues) => WithLabels(labelValues.AsMemory());40
41public ICounter WithLabels(ReadOnlyMemory<string> labelValues)42{43return new AutoLeasingInstance(this, labelValues);44}45
46public ICounter WithLabels(ReadOnlySpan<string> labelValues)47{48// We are allocating a long-lived auto-leasing wrapper here, so there is no way we can just use the span directly.49// We must copy it to a long-lived array. Another reason to avoid re-allocating these as much as possible.50return new AutoLeasingInstance(this, labelValues.ToArray());51}52#endregion53
54private sealed class AutoLeasingInstance : ICounter55{56public AutoLeasingInstance(IManagedLifetimeMetricHandle<ICounter> inner, ReadOnlyMemory<string> labelValues)57{58_inner = inner;59_labelValues = labelValues;60}61
62private readonly IManagedLifetimeMetricHandle<ICounter> _inner;63private readonly ReadOnlyMemory<string> _labelValues;64
65public double Value => throw new NotSupportedException("Read operations on a lifetime-extending-on-use expiring metric are not supported.");66
67public void Inc(double increment) => Inc(increment, null);68public void Inc(Exemplar? exemplar) => Inc(increment: 1, exemplar: exemplar);69
70public void Inc(double increment, Exemplar? exemplar)71{72var args = new IncArgs(increment, exemplar);73
74// We use the Span overload to signal that we expect the label values to be known already.75_inner.WithLease(_incCoreFunc, args, _labelValues.Span);76}77
78private readonly struct IncArgs(double increment, Exemplar? exemplar)79{80public readonly double Increment = increment;81public readonly Exemplar? Exemplar = exemplar;82}83
84private static void IncCore(IncArgs args, ICounter counter) => counter.Inc(args.Increment, args.Exemplar);85private static readonly Action<IncArgs, ICounter> _incCoreFunc = IncCore;86
87public void IncTo(double targetValue)88{89var args = new IncToArgs(targetValue);90
91// We use the Span overload to signal that we expect the label values to be known already.92_inner.WithLease(_incToCoreFunc, args, _labelValues.Span);93}94
95private readonly struct IncToArgs(double targetValue)96{97public readonly double TargetValue = targetValue;98}99
100private static void IncToCore(IncToArgs args, ICounter counter) => counter.IncTo(args.TargetValue);101private static readonly Action<IncToArgs, ICounter> _incToCoreFunc = IncToCore;102}103}
104