Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[docs-metrics] Customer ExemplarReservoir and View API configuration #5624

Merged
8 changes: 4 additions & 4 deletions docs/diagnostics/experimental-apis/OTEL1004.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ We are exposing these APIs experimentally for the following reasons:
**TL;DR** We want to gather feedback on the usability of the API and for the
need(s) in general for custom reservoirs before exposing a stable API.

<!--
## Provide feedback

Please provide feedback on [this issue](TODO) if you need stable support for
custom `ExemplarReservoir`s.
-->
Please provide feedback on [this
issue](https://github.com/open-telemetry/opentelemetry-dotnet/issues/5629) if
you need stable support for custom `ExemplarReservoir`s. The feedback will help
inform decisions about what to expose stable and when.
40 changes: 33 additions & 7 deletions docs/metrics/customizing-the-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ within the maximum number of buckets defined by `MaxSize`. The default
`MaxSize` is 160 buckets and the default `MaxScale` is 20.

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

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

#### Change the ExemplarReservoir

> [!NOTE]
> `MetricStreamConfiguration.ExemplarReservoirFactory` is an experimental API only
available in pre-release builds. For details see:
[OTEL1004](../../diagnostics/experimental-apis/OTEL1004.md).

To set the [ExemplarReservoir](#exemplarreservoir) for an instrument, use the
`MetricStreamConfiguration.ExemplarReservoirFactory` property on the View API:

> [!IMPORTANT]
> Setting `MetricStreamConfiguration.ExemplarReservoirFactory` alone will NOT
vishweshbankwar marked this conversation as resolved.
Show resolved Hide resolved
enable `Exemplar`s for an instrument. An [ExemplarFilter](#exemplarfilter)
MUST also be used.

```csharp
// Use MyCustomExemplarReservoir for "MyFruitCounter"
.AddView(
instrumentName: "MyFruitCounter",
new MetricStreamConfiguration { ExemplarReservoirFactory = () => new MyCustomExemplarReservoir() })
```

### Changing maximum Metric Streams

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

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

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

> [!NOTE]
> Currently there is no ability to change or configure `ExemplarReservoir`.
See [Change the ExemplarReservoir](#change-the-exemplarreservoir) for details on
how to use the View API to change `ExemplarReservoir`s for an instrument.

See [Building your own
ExemplarReservoir](../extending-the-sdk/README.md#exemplarreservoir) for details
on how to implement custom `ExemplarReservoir`s.

### Instrumentation

Expand Down
100 changes: 95 additions & 5 deletions docs/metrics/extending-the-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

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

Not supported.

## ExemplarFilter
## ExemplarReservoir

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

## ExemplarReservoir
Custom [ExemplarReservoir](../customizing-the-sdk/README.md#exemplarreservoir)s
can be implemented to control how `Exemplar`s are recorded for a metric:
cijothomas marked this conversation as resolved.
Show resolved Hide resolved

Not supported.
* `ExemplarReservoir`s should derive from `FixedSizeExemplarReservoir` (which
belongs to the [OpenTelemetry](../../../src/OpenTelemetry/README.md) package)
and implement the `Offer` methods.
* The `FixedSizeExemplarReservoir` constructor accepts a `capacity` parameter to
control the number of `Exemplar`s which may be recorded by the
`ExemplarReservoir`.
* The `virtual` `OnCollected` method is called after the `ExemplarReservoir`
collection operation has completed and may be used to implement cleanup or
reset logic.
* The `bool` `ResetOnCollect` property on `ExemplarReservoir` is set to `true`
when delta aggregation temporality is used for the metric using the
`ExemplarReservoir`.
* The `Offer` and `Collect` `ExemplarReservoir` methods are called concurrently
by the OpenTelemetry SDK. As such any state required by custom
`ExemplarReservoir` implementations needs to be managed using appropriate
thread-safety/concurrency mechanisms (`lock`, `Interlocked`, etc.).
* Custom `ExemplarReservoir` implementations MUST NOT throw exceptions.
Exceptions thrown in custom implementations MAY lead to unreleased locks and
undefined behaviors.

The following example demonstrates a custom `ExemplarReservoir` implementation
which records `Exemplar`s for measurements which have the highest value. When
delta aggregation temporality is used the recorded `Exemplar` will be the
highest value for a given collection cycle. When cumulative aggregation
temporality is used the recorded `Exemplar` will be the highest value for the
lifetime of the process.

```csharp
cijothomas marked this conversation as resolved.
Show resolved Hide resolved
class HighestValueExemplarReservoir : FixedSizeExemplarReservoir
{
private readonly object lockObject = new();
private long? previousValueLong;
private double? previousValueDouble;

public HighestValueExemplarReservoir()
: base(capacity: 1)
{
}

public override void Offer(in ExemplarMeasurement<long> measurement)
{
if (!this.previousValueLong.HasValue || measurement.Value > this.previousValueLong.Value)
{
lock (this.lockObject)
cijothomas marked this conversation as resolved.
Show resolved Hide resolved
{
if (!this.previousValueLong.HasValue || measurement.Value > this.previousValueLong.Value)
{
this.UpdateExemplar(0, in measurement);
this.previousValueLong = measurement.Value;
}
}
}
}

public override void Offer(in ExemplarMeasurement<double> measurement)
{
if (!this.previousValueDouble.HasValue || measurement.Value > this.previousValueDouble.Value)
{
lock (this.lockObject)
{
if (!this.previousValueDouble.HasValue || measurement.Value > this.previousValueDouble.Value)
{
this.UpdateExemplar(0, in measurement);
this.previousValueDouble = measurement.Value;
}
}
}
}

protected override void OnCollected()
{
if (this.ResetOnCollect)
{
lock (this.lockObject)
{
this.previousValueLong = null;
this.previousValueDouble = null;
}
}
}
}
```

Custom [ExemplarReservoir](../customizing-the-sdk/README.md#exemplarreservoir)s
can be configured using the View API. For details see: [Change the
ExemplarReservoir](../customizing-the-sdk/README.md#change-the-exemplarreservoir).

## References