Skip to content

Commit 2abf2a9

Browse files
authored
[docs-metrics] Customer ExemplarReservoir and View API configuration (#5624)
1 parent 16f2bf0 commit 2abf2a9

File tree

3 files changed

+132
-16
lines changed

3 files changed

+132
-16
lines changed

docs/diagnostics/experimental-apis/OTEL1004.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ We are exposing these APIs experimentally for the following reasons:
5454
**TL;DR** We want to gather feedback on the usability of the API and for the
5555
need(s) in general for custom reservoirs before exposing a stable API.
5656

57-
<!--
5857
## Provide feedback
5958

60-
Please provide feedback on [this issue](TODO) if you need stable support for
61-
custom `ExemplarReservoir`s.
62-
-->
59+
Please provide feedback on [this
60+
issue](https://github.com/open-telemetry/opentelemetry-dotnet/issues/5629) if
61+
you need stable support for custom `ExemplarReservoir`s. The feedback will help
62+
inform decisions about what to expose stable and when.

docs/metrics/customizing-the-sdk/README.md

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ within the maximum number of buckets defined by `MaxSize`. The default
239239
`MaxSize` is 160 buckets and the default `MaxScale` is 20.
240240

241241
```csharp
242-
// Change the maximum number of buckets
242+
// Change the maximum number of buckets for "MyHistogram"
243243
.AddView(
244244
instrumentName: "MyHistogram",
245245
new Base2ExponentialBucketHistogramConfiguration { MaxSize = 40 })
@@ -251,6 +251,28 @@ by using Views.
251251

252252
See [Program.cs](./Program.cs) for a complete example.
253253

254+
#### Change the ExemplarReservoir
255+
256+
> [!NOTE]
257+
> `MetricStreamConfiguration.ExemplarReservoirFactory` is an experimental API only
258+
available in pre-release builds. For details see:
259+
[OTEL1004](../../diagnostics/experimental-apis/OTEL1004.md).
260+
261+
To set the [ExemplarReservoir](#exemplarreservoir) for an instrument, use the
262+
`MetricStreamConfiguration.ExemplarReservoirFactory` property on the View API:
263+
264+
> [!IMPORTANT]
265+
> Setting `MetricStreamConfiguration.ExemplarReservoirFactory` alone will NOT
266+
enable `Exemplar`s for an instrument. An [ExemplarFilter](#exemplarfilter)
267+
MUST also be used.
268+
269+
```csharp
270+
// Use MyCustomExemplarReservoir for "MyFruitCounter"
271+
.AddView(
272+
instrumentName: "MyFruitCounter",
273+
new MetricStreamConfiguration { ExemplarReservoirFactory = () => new MyCustomExemplarReservoir() })
274+
```
275+
254276
### Changing maximum Metric Streams
255277

256278
Every instrument results in the creation of a single Metric stream. With Views,
@@ -421,20 +443,24 @@ and is responsible for recording `Exemplar`s. The following are the default
421443
reservoirs:
422444

423445
* `AlignedHistogramBucketExemplarReservoir` is the default reservoir used for
424-
Histograms with buckets, and it stores at most one exemplar per histogram
425-
bucket. The exemplar stored is the last measurement recorded - i.e. any new
446+
Histograms with buckets, and it stores at most one `Exemplar` per histogram
447+
bucket. The `Exemplar` stored is the last measurement recorded - i.e. any new
426448
measurement overwrites the previous one in that bucket.
427449

428450
* `SimpleFixedSizeExemplarReservoir` is the default reservoir used for all
429-
metrics except Histograms with buckets. It has a fixed reservoir pool, and
451+
metrics except histograms with buckets. It has a fixed reservoir pool, and
430452
implements the equivalent of [naive
431453
reservoir](https://en.wikipedia.org/wiki/Reservoir_sampling). The reservoir pool
432-
size (currently defaulting to 1) determines the maximum number of exemplars
454+
size (currently defaulting to 1) determines the maximum number of `Exemplar`s
433455
stored. Exponential histograms use a `SimpleFixedSizeExemplarReservoir` with a
434456
pool size equal to the number of buckets up to a max of `20`.
435457

436-
> [!NOTE]
437-
> Currently there is no ability to change or configure `ExemplarReservoir`.
458+
See [Change the ExemplarReservoir](#change-the-exemplarreservoir) for details on
459+
how to use the View API to change `ExemplarReservoir`s for an instrument.
460+
461+
See [Building your own
462+
ExemplarReservoir](../extending-the-sdk/README.md#exemplarreservoir) for details
463+
on how to implement custom `ExemplarReservoir`s.
438464

439465
### Instrumentation
440466

docs/metrics/extending-the-sdk/README.md

Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
* [Building your own exporter](#exporter)
44
* [Building your own reader](#reader)
5-
* [Building your own exemplar filter](#exemplarfilter)
65
* [Building your own exemplar reservoir](#exemplarreservoir)
76
* [Building your own resource detector](../../resources/README.md#resource-detector)
87
* [References](#references)
@@ -72,12 +71,103 @@ to the `MeterProvider` as shown in the example [here](./Program.cs).
7271

7372
Not supported.
7473

75-
## ExemplarFilter
74+
## ExemplarReservoir
7675

77-
Not supported.
76+
> [!NOTE]
77+
> `ExemplarReservoir` is an experimental API only available in pre-release
78+
builds. For details see:
79+
[OTEL1004](../../diagnostics/experimental-apis/OTEL1004.md). Please [provide
80+
feedback](https://github.com/open-telemetry/opentelemetry-dotnet/issues/5629)
81+
to help inform decisions about what should be exposed stable and when.
7882

79-
## ExemplarReservoir
83+
Custom [ExemplarReservoir](../customizing-the-sdk/README.md#exemplarreservoir)s
84+
can be implemented to control how `Exemplar`s are recorded for a metric:
8085

81-
Not supported.
86+
* `ExemplarReservoir`s should derive from `FixedSizeExemplarReservoir` (which
87+
belongs to the [OpenTelemetry](../../../src/OpenTelemetry/README.md) package)
88+
and implement the `Offer` methods.
89+
* The `FixedSizeExemplarReservoir` constructor accepts a `capacity` parameter to
90+
control the number of `Exemplar`s which may be recorded by the
91+
`ExemplarReservoir`.
92+
* The `virtual` `OnCollected` method is called after the `ExemplarReservoir`
93+
collection operation has completed and may be used to implement cleanup or
94+
reset logic.
95+
* The `bool` `ResetOnCollect` property on `ExemplarReservoir` is set to `true`
96+
when delta aggregation temporality is used for the metric using the
97+
`ExemplarReservoir`.
98+
* The `Offer` and `Collect` `ExemplarReservoir` methods are called concurrently
99+
by the OpenTelemetry SDK. As such any state required by custom
100+
`ExemplarReservoir` implementations needs to be managed using appropriate
101+
thread-safety/concurrency mechanisms (`lock`, `Interlocked`, etc.).
102+
* Custom `ExemplarReservoir` implementations MUST NOT throw exceptions.
103+
Exceptions thrown in custom implementations MAY lead to unreleased locks and
104+
undefined behaviors.
105+
106+
The following example demonstrates a custom `ExemplarReservoir` implementation
107+
which records `Exemplar`s for measurements which have the highest value. When
108+
delta aggregation temporality is used the recorded `Exemplar` will be the
109+
highest value for a given collection cycle. When cumulative aggregation
110+
temporality is used the recorded `Exemplar` will be the highest value for the
111+
lifetime of the process.
112+
113+
```csharp
114+
class HighestValueExemplarReservoir : FixedSizeExemplarReservoir
115+
{
116+
private readonly object lockObject = new();
117+
private long? previousValueLong;
118+
private double? previousValueDouble;
119+
120+
public HighestValueExemplarReservoir()
121+
: base(capacity: 1)
122+
{
123+
}
124+
125+
public override void Offer(in ExemplarMeasurement<long> measurement)
126+
{
127+
if (!this.previousValueLong.HasValue || measurement.Value > this.previousValueLong.Value)
128+
{
129+
lock (this.lockObject)
130+
{
131+
if (!this.previousValueLong.HasValue || measurement.Value > this.previousValueLong.Value)
132+
{
133+
this.UpdateExemplar(0, in measurement);
134+
this.previousValueLong = measurement.Value;
135+
}
136+
}
137+
}
138+
}
139+
140+
public override void Offer(in ExemplarMeasurement<double> measurement)
141+
{
142+
if (!this.previousValueDouble.HasValue || measurement.Value > this.previousValueDouble.Value)
143+
{
144+
lock (this.lockObject)
145+
{
146+
if (!this.previousValueDouble.HasValue || measurement.Value > this.previousValueDouble.Value)
147+
{
148+
this.UpdateExemplar(0, in measurement);
149+
this.previousValueDouble = measurement.Value;
150+
}
151+
}
152+
}
153+
}
154+
155+
protected override void OnCollected()
156+
{
157+
if (this.ResetOnCollect)
158+
{
159+
lock (this.lockObject)
160+
{
161+
this.previousValueLong = null;
162+
this.previousValueDouble = null;
163+
}
164+
}
165+
}
166+
}
167+
```
168+
169+
Custom [ExemplarReservoir](../customizing-the-sdk/README.md#exemplarreservoir)s
170+
can be configured using the View API. For details see: [Change the
171+
ExemplarReservoir](../customizing-the-sdk/README.md#change-the-exemplarreservoir).
82172

83173
## References

0 commit comments

Comments
 (0)