prometheus-net
81 строка · 3.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 ManagedLifetimeSummary : ManagedLifetimeMetricHandle<Summary.Child, ISummary>, ICollector<ISummary>
14{
15static ManagedLifetimeSummary()
16{
17_assignUnlabelledFunc = AssignUnlabelled;
18}
19
20public ManagedLifetimeSummary(Collector<Summary.Child> metric, TimeSpan expiresAfter) : base(metric, expiresAfter)
21{
22}
23
24public override ICollector<ISummary> WithExtendLifetimeOnUse() => this;
25
26#region ICollector<ISummary> implementation (for WithExtendLifetimeOnUse)
27public string Name => _metric.Name;
28public string Help => _metric.Help;
29public string[] LabelNames => _metric.LabelNames;
30
31public ISummary Unlabelled => NonCapturingLazyInitializer.EnsureInitialized(ref _unlabelled, this, _assignUnlabelledFunc);
32private AutoLeasingInstance? _unlabelled;
33private static readonly Action<ManagedLifetimeSummary> _assignUnlabelledFunc;
34private static void AssignUnlabelled(ManagedLifetimeSummary 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-managed
38// metrics are used is that we do not have a meaningful way to reuse metrics or identify their lifetime.
39public ISummary WithLabels(params string[] labelValues) => WithLabels(labelValues.AsMemory());
40
41public ISummary WithLabels(ReadOnlyMemory<string> labelValues)
42{
43return new AutoLeasingInstance(this, labelValues);
44}
45
46public ISummary 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#endregion
53
54private sealed class AutoLeasingInstance : ISummary
55{
56public AutoLeasingInstance(IManagedLifetimeMetricHandle<ISummary> inner, ReadOnlyMemory<string> labelValues)
57{
58_inner = inner;
59_labelValues = labelValues;
60}
61
62private readonly IManagedLifetimeMetricHandle<ISummary> _inner;
63private readonly ReadOnlyMemory<string> _labelValues;
64
65public void Observe(double val)
66{
67var args = new ObserveArgs(val);
68
69// We use the Span overload to signal that we expect the label values to be known already.
70_inner.WithLease(_observeCoreFunc, args, _labelValues.Span);
71}
72
73private readonly struct ObserveArgs(double val)
74{
75public readonly double Val = val;
76}
77
78private static void ObserveCore(ObserveArgs args, ISummary summary) => summary.Observe(args.Val);
79private static readonly Action<ObserveArgs, ISummary> _observeCoreFunc = ObserveCore;
80}
81}
82