diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 668b6aac2ce..95287991b18 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -10,7 +10,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v7 + - uses: actions/stale@v8 with: stale-pr-message: 'This PR was marked stale due to lack of activity and will be closed in 7 days. Commenting or Pushing will instruct the bot to automatically remove the label. This bot runs once per day.' close-pr-message: 'Closed as inactive. Feel free to reopen if this PR is still being worked on.' diff --git a/examples/AspNetCore/Program.cs b/examples/AspNetCore/Program.cs index 8835f260fba..bc4e0602cda 100644 --- a/examples/AspNetCore/Program.cs +++ b/examples/AspNetCore/Program.cs @@ -14,6 +14,7 @@ // limitations under the License. // +using System.Diagnostics.Metrics; using Examples.AspNetCore; using OpenTelemetry.Exporter; using OpenTelemetry.Instrumentation.AspNetCore; @@ -33,6 +34,9 @@ // Note: Switch between Console/OTLP by setting UseLogExporter in appsettings.json. var logExporter = appBuilder.Configuration.GetValue("UseLogExporter").ToLowerInvariant(); +// Note: Switch between Explicit/Exponential by setting HistogramAggregation in appsettings.json +var histogramAggregation = appBuilder.Configuration.GetValue("HistogramAggregation").ToLowerInvariant(); + // Build a resource configuration action to set service information. Action configureResource = r => r.AddService( serviceName: appBuilder.Configuration.GetValue("ServiceName"), @@ -111,6 +115,22 @@ .AddHttpClientInstrumentation() .AddAspNetCoreInstrumentation(); + switch (histogramAggregation) + { + case "exponential": + builder.AddView(instrument => + { + return instrument.GetType().GetGenericTypeDefinition() == typeof(Histogram<>) + ? new Base2ExponentialBucketHistogramConfiguration() + : null; + }); + break; + default: + // Explicit bounds histogram is the default. + // No additional configuration necessary. + break; + } + switch (metricsExporter) { case "prometheus": diff --git a/examples/AspNetCore/appsettings.json b/examples/AspNetCore/appsettings.json index 19f0513276b..1f2d3368dc9 100644 --- a/examples/AspNetCore/appsettings.json +++ b/examples/AspNetCore/appsettings.json @@ -14,6 +14,7 @@ "UseTracingExporter": "console", "UseMetricsExporter": "console", "UseLogExporter": "console", + "HistogramAggregation": "explicit", "Jaeger": { "AgentHost": "localhost", "AgentPort": 6831, diff --git a/src/OpenTelemetry.Api/Internal/ExceptionExtensions.cs b/src/OpenTelemetry.Api/Internal/ExceptionExtensions.cs index fda39cb828c..f27767c9e16 100644 --- a/src/OpenTelemetry.Api/Internal/ExceptionExtensions.cs +++ b/src/OpenTelemetry.Api/Internal/ExceptionExtensions.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#nullable enable + using System.Globalization; namespace OpenTelemetry.Internal diff --git a/src/OpenTelemetry.Api/Internal/SemanticConventions.cs b/src/OpenTelemetry.Api/Internal/SemanticConventions.cs index 84d2a362a9e..a9c760378c3 100644 --- a/src/OpenTelemetry.Api/Internal/SemanticConventions.cs +++ b/src/OpenTelemetry.Api/Internal/SemanticConventions.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#nullable enable + namespace OpenTelemetry.Trace { /// diff --git a/src/OpenTelemetry.Api/Internal/SpanAttributeConstants.cs b/src/OpenTelemetry.Api/Internal/SpanAttributeConstants.cs index 5640fd3decb..3bb7809cd2f 100644 --- a/src/OpenTelemetry.Api/Internal/SpanAttributeConstants.cs +++ b/src/OpenTelemetry.Api/Internal/SpanAttributeConstants.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#nullable enable + namespace OpenTelemetry.Trace { /// diff --git a/src/OpenTelemetry.Api/Internal/StatusHelper.cs b/src/OpenTelemetry.Api/Internal/StatusHelper.cs index 78d8fa0cc24..1b3b7358f59 100644 --- a/src/OpenTelemetry.Api/Internal/StatusHelper.cs +++ b/src/OpenTelemetry.Api/Internal/StatusHelper.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#nullable enable + using System.Runtime.CompilerServices; using OpenTelemetry.Trace; @@ -26,7 +28,7 @@ internal static class StatusHelper public const string ErrorStatusCodeTagValue = "ERROR"; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string GetTagValueForStatusCode(StatusCode statusCode) + public static string? GetTagValueForStatusCode(StatusCode statusCode) { return statusCode switch { @@ -43,7 +45,7 @@ public static string GetTagValueForStatusCode(StatusCode statusCode) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static StatusCode? GetStatusCodeForTagValue(string statusCodeTagValue) + public static StatusCode? GetStatusCodeForTagValue(string? statusCodeTagValue) { return statusCodeTagValue switch { @@ -52,15 +54,15 @@ public static string GetTagValueForStatusCode(StatusCode statusCode) * first because assumption is most spans will be * Unset, then Error. Ok is not set by the SDK. */ - string _ when UnsetStatusCodeTagValue.Equals(statusCodeTagValue, StringComparison.OrdinalIgnoreCase) => StatusCode.Unset, - string _ when ErrorStatusCodeTagValue.Equals(statusCodeTagValue, StringComparison.OrdinalIgnoreCase) => StatusCode.Error, - string _ when OkStatusCodeTagValue.Equals(statusCodeTagValue, StringComparison.OrdinalIgnoreCase) => StatusCode.Ok, + not null when UnsetStatusCodeTagValue.Equals(statusCodeTagValue, StringComparison.OrdinalIgnoreCase) => StatusCode.Unset, + not null when ErrorStatusCodeTagValue.Equals(statusCodeTagValue, StringComparison.OrdinalIgnoreCase) => StatusCode.Error, + not null when OkStatusCodeTagValue.Equals(statusCodeTagValue, StringComparison.OrdinalIgnoreCase) => StatusCode.Ok, _ => null, }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryGetStatusCodeForTagValue(string statusCodeTagValue, out StatusCode statusCode) + public static bool TryGetStatusCodeForTagValue(string? statusCodeTagValue, out StatusCode statusCode) { StatusCode? tempStatusCode = GetStatusCodeForTagValue(statusCodeTagValue); diff --git a/src/OpenTelemetry.Api/README.md b/src/OpenTelemetry.Api/README.md index 6ad1efec28c..9a412ee3c6b 100644 --- a/src/OpenTelemetry.Api/README.md +++ b/src/OpenTelemetry.Api/README.md @@ -452,6 +452,21 @@ and [extract](../../examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs) context. +**Note on instrumentation libraries**: If you are using the instrumentation +libraries shipped from this repo [e.g. [ASP.NET +Core](https://github.com/open-telemetry/opentelemetry-dotnet/tree/main/src/OpenTelemetry.Instrumentation.AspNetCore) +or +[HttpClient](https://github.com/open-telemetry/opentelemetry-dotnet/tree/main/src/OpenTelemetry.Instrumentation.Http)], +context propagation is done by using the +[default](https://github.com/open-telemetry/opentelemetry-dotnet/blob/d924663dc3d6bfdf737bc49ceaa1addcec90a2d6/src/OpenTelemetry/Sdk.cs#L34-L38) +propagator. The default can be updated by calling +`Sdk.SetDefaultTextMapPropagator` and passing the propagator of your choice. + +Propagator Api used by the instrumentation libraries is different than +[DistributedContextPropagator](https://learn.microsoft.com/dotnet/api/system.diagnostics.distributedcontextpropagator) +available in `System.Diagnostics`. Implementing this will have no impact on the +propagation, if used alongside instrumentation libraries. + ## Introduction to OpenTelemetry .NET Metrics API Metrics in OpenTelemetry .NET are a somewhat unique implementation of the diff --git a/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs b/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs index f66a34ba756..fc9d13de06b 100644 --- a/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs +++ b/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs @@ -92,7 +92,7 @@ public override ExportResult Export(in Batch batch) var metricType = metric.MetricType; - if (metricType.IsHistogram()) + if (metricType == MetricType.Histogram || metricType == MetricType.ExponentialHistogram) { var bucketsBuilder = new StringBuilder(); var sum = metricPoint.GetHistogramSum(); @@ -105,41 +105,49 @@ public override ExportResult Export(in Batch batch) bucketsBuilder.AppendLine(); - bool isFirstIteration = true; - double previousExplicitBound = default; - foreach (var histogramMeasurement in metricPoint.GetHistogramBuckets()) + if (metricType == MetricType.Histogram) { - if (isFirstIteration) + bool isFirstIteration = true; + double previousExplicitBound = default; + foreach (var histogramMeasurement in metricPoint.GetHistogramBuckets()) { - bucketsBuilder.Append("(-Infinity,"); - bucketsBuilder.Append(histogramMeasurement.ExplicitBound); - bucketsBuilder.Append(']'); - bucketsBuilder.Append(':'); - bucketsBuilder.Append(histogramMeasurement.BucketCount); - previousExplicitBound = histogramMeasurement.ExplicitBound; - isFirstIteration = false; - } - else - { - bucketsBuilder.Append('('); - bucketsBuilder.Append(previousExplicitBound); - bucketsBuilder.Append(','); - if (histogramMeasurement.ExplicitBound != double.PositiveInfinity) + if (isFirstIteration) { + bucketsBuilder.Append("(-Infinity,"); bucketsBuilder.Append(histogramMeasurement.ExplicitBound); + bucketsBuilder.Append(']'); + bucketsBuilder.Append(':'); + bucketsBuilder.Append(histogramMeasurement.BucketCount); previousExplicitBound = histogramMeasurement.ExplicitBound; + isFirstIteration = false; } else { - bucketsBuilder.Append("+Infinity"); + bucketsBuilder.Append('('); + bucketsBuilder.Append(previousExplicitBound); + bucketsBuilder.Append(','); + if (histogramMeasurement.ExplicitBound != double.PositiveInfinity) + { + bucketsBuilder.Append(histogramMeasurement.ExplicitBound); + previousExplicitBound = histogramMeasurement.ExplicitBound; + } + else + { + bucketsBuilder.Append("+Infinity"); + } + + bucketsBuilder.Append(']'); + bucketsBuilder.Append(':'); + bucketsBuilder.Append(histogramMeasurement.BucketCount); } - bucketsBuilder.Append(']'); - bucketsBuilder.Append(':'); - bucketsBuilder.Append(histogramMeasurement.BucketCount); + bucketsBuilder.AppendLine(); } - - bucketsBuilder.AppendLine(); + } + else + { + // TODO: Consider how/if to display buckets for exponential histograms. + bucketsBuilder.AppendLine("Buckets are not displayed for exponential histograms."); } valueDisplay = bucketsBuilder.ToString(); diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md index 7fad8554f47..f043de9bc64 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md @@ -2,6 +2,15 @@ ## Unreleased +* Add support for exporting histograms aggregated using the + [Base2 Exponential Bucket Histogram Aggregation](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#base2-exponential-bucket-histogram-aggregation). + ([#4337](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4337)) + +* Added support to set `TraceState` when converting the + System.Diagnostics.Activity object to its corresponding + OpenTelemetry.Proto.Trace.V1.Span object. + ([#4331](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4331)) + ## 1.5.0-alpha.1 Released 2023-Mar-07 diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs index b76ac227cd6..23bf82cb24a 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs @@ -143,6 +143,7 @@ internal static Span ToOtlpSpan(this Activity activity, SdkLimitOptions sdkLimit TraceId = UnsafeByteOperations.UnsafeWrap(traceIdBytes), SpanId = UnsafeByteOperations.UnsafeWrap(spanIdBytes), ParentSpanId = parentSpanIdString, + TraceState = activity.TraceStateString ?? string.Empty, StartTimeUnixNano = (ulong)startTimeUnixNano, EndTimeUnixNano = (ulong)(startTimeUnixNano + activity.Duration.ToNanoseconds()), diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs index 9208eaa3e99..c839a771f1e 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs @@ -307,6 +307,58 @@ internal static OtlpMetrics.Metric ToOtlpMetric(this Metric metric) otlpMetric.Histogram = histogram; break; } + + case MetricType.ExponentialHistogram: + { + var histogram = new OtlpMetrics.ExponentialHistogram + { + AggregationTemporality = temporality, + }; + + foreach (ref readonly var metricPoint in metric.GetMetricPoints()) + { + var dataPoint = new OtlpMetrics.ExponentialHistogramDataPoint + { + StartTimeUnixNano = (ulong)metricPoint.StartTime.ToUnixTimeNanoseconds(), + TimeUnixNano = (ulong)metricPoint.EndTime.ToUnixTimeNanoseconds(), + }; + + AddAttributes(metricPoint.Tags, dataPoint.Attributes); + dataPoint.Count = (ulong)metricPoint.GetHistogramCount(); + dataPoint.Sum = metricPoint.GetHistogramSum(); + + if (metricPoint.TryGetHistogramMinMaxValues(out double min, out double max)) + { + dataPoint.Min = min; + dataPoint.Max = max; + } + + var exponentialHistogramData = metricPoint.GetExponentialHistogramData(); + dataPoint.Scale = exponentialHistogramData.Scale; + dataPoint.ZeroCount = (ulong)exponentialHistogramData.ZeroCount; + + dataPoint.Positive = new OtlpMetrics.ExponentialHistogramDataPoint.Types.Buckets(); + dataPoint.Positive.Offset = exponentialHistogramData.PositiveBuckets.Offset; + foreach (var bucketCount in exponentialHistogramData.PositiveBuckets) + { + dataPoint.Positive.BucketCounts.Add((ulong)bucketCount); + } + + dataPoint.Negative = new OtlpMetrics.ExponentialHistogramDataPoint.Types.Buckets(); + dataPoint.Negative.Offset = exponentialHistogramData.NegativeBuckets.Offset; + foreach (var bucketCount in exponentialHistogramData.NegativeBuckets) + { + dataPoint.Negative.BucketCounts.Add((ulong)bucketCount); + } + + // TODO: exemplars. + + histogram.DataPoints.Add(dataPoint); + } + + otlpMetric.ExponentialHistogram = histogram; + break; + } } return otlpMetric; diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs index 445e0410c81..3094129cc7e 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs @@ -36,6 +36,13 @@ UpDownCounter becomes gauge public static int WriteMetric(byte[] buffer, int cursor, Metric metric) { + if (metric.MetricType == MetricType.ExponentialHistogram) + { + // Exponential histograms are not yet support by Prometheus. + // They are ignored for now. + return cursor; + } + int metricType = (int)metric.MetricType >> 4; cursor = WriteTypeMetadata(buffer, cursor, metric.Name, metric.Unit, MetricTypes[metricType]); cursor = WriteUnitMetadata(buffer, cursor, metric.Name, metric.Unit); diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/README.md b/src/OpenTelemetry.Instrumentation.AspNetCore/README.md index fd535b1682c..131f98a4b37 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/README.md +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/README.md @@ -37,17 +37,21 @@ dotnet add package --prerelease OpenTelemetry.Instrumentation.AspNetCore ### Step 2: Enable ASP.NET Core Instrumentation at application startup ASP.NET Core instrumentation must be enabled at application startup. This is -typically done in the `ConfigureServices` of your `Startup` class. The example -below enables this instrumentation by using an extension method on -`IServiceCollection`. This extension method requires adding the package +typically done in the `ConfigureServices` of your `Startup` class. Both examples +below enables OpenTelemetry by calling `AddOpenTelemetry()` on `IServiceCollection`. + This extension method requires adding the package [`OpenTelemetry.Extensions.Hosting`](../OpenTelemetry.Extensions.Hosting/README.md) -to the application. This ensures the instrumentation is disposed when the host +to the application. This ensures instrumentations are disposed when the host is shutdown. -Additionally, this examples sets up the OpenTelemetry Jaeger exporter, which -requires adding the package -[`OpenTelemetry.Exporter.Jaeger`](../OpenTelemetry.Exporter.Jaeger/README.md) to -the application. +#### Traces + +The following example demonstrates adding ASP.NET Core instrumentation with the +extension method `WithTracing()` on `OpenTelemetryBuilder`. +then extension method `AddAspNetCoreInstrumentation()` on `TracerProviderBuilder` +to the application. This example also sets up the Console Exporter, +which requires adding the package [`OpenTelemetry.Exporter.Console`](../OpenTelemetry.Exporter.Console/README.md) +to the application. ```csharp using Microsoft.Extensions.DependencyInjection; @@ -58,10 +62,42 @@ public void ConfigureServices(IServiceCollection services) services.AddOpenTelemetry() .WithTracing(builder => builder .AddAspNetCoreInstrumentation() - .AddJaegerExporter()); + .AddConsoleExporter()); +} +``` + +#### Metrics + +The following example demonstrates adding ASP.NET Core instrumentation with the +extension method `WithMetrics()` on `OpenTelemetryBuilder` +then extension method `AddAspNetCoreInstrumentation()` on `MeterProviderBuilder` +to the application. This example also sets up the Console Exporter, +which requires adding the package [`OpenTelemetry.Exporter.Console`](../OpenTelemetry.Exporter.Console/README.md) +to the application. + +```csharp +using Microsoft.Extensions.DependencyInjection; +using OpenTelemetry.Metrics; + +public void ConfigureServices(IServiceCollection services) +{ + services.AddOpenTelemetry() + .WithMetrics(builder => builder + .AddAspNetCoreInstrumentation() + .AddConsoleExporter()); } ``` +#### List of metrics produced + +The instrumentation is implemented based on [metrics semantic +conventions](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/http-metrics.md#metric-httpserverduration). +Currently, the instrumentation supports the following metric. + +| Name | Instrument Type | Unit | Description | +|-------|-----------------|------|-------------| +| `http.server.duration` | Histogram | `ms` | Measures the duration of inbound HTTP requests. | + ## Advanced configuration This instrumentation can be configured to change the default behavior by using @@ -87,7 +123,7 @@ services.Configure(options => services.AddOpenTelemetry() .WithTracing(builder => builder .AddAspNetCoreInstrumentation() - .AddJaegerExporter()); + .AddConsoleExporter()); ``` ### Filter @@ -110,7 +146,7 @@ services.AddOpenTelemetry() // only collect telemetry about HTTP GET requests return httpContext.Request.Method.Equals("GET"); }) - .AddJaegerExporter()); + .AddConsoleExporter()); ``` It is important to note that this `Filter` option is specific to this diff --git a/src/OpenTelemetry.Instrumentation.Http/README.md b/src/OpenTelemetry.Instrumentation.Http/README.md index 13ff358a21d..7ca9400d4b2 100644 --- a/src/OpenTelemetry.Instrumentation.Http/README.md +++ b/src/OpenTelemetry.Instrumentation.Http/README.md @@ -102,7 +102,7 @@ to see how to enable this instrumentation in an ASP.NET application. #### List of metrics produced -The instrumentation was implemented based on [metrics semantic +The instrumentation is implemented based on [metrics semantic conventions](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/http-metrics.md#metric-httpclientduration). Currently, the instrumentation supports the following metric. diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt index ac2084882d9..d0ed4d4dec8 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt @@ -2,6 +2,12 @@ OpenTelemetry.Metrics.AlwaysOffExemplarFilter OpenTelemetry.Metrics.AlwaysOffExemplarFilter.AlwaysOffExemplarFilter() -> void OpenTelemetry.Metrics.AlwaysOnExemplarFilter OpenTelemetry.Metrics.AlwaysOnExemplarFilter.AlwaysOnExemplarFilter() -> void +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.Base2ExponentialBucketHistogramConfiguration() -> void +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxScale.get -> int +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxScale.set -> void +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.get -> int +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.set -> void OpenTelemetry.Metrics.Exemplar OpenTelemetry.Metrics.Exemplar.DoubleValue.get -> double OpenTelemetry.Metrics.Exemplar.Exemplar() -> void diff --git a/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt index c79ebddeb5e..a06e307d7c4 100644 --- a/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt @@ -2,6 +2,12 @@ OpenTelemetry.Metrics.AlwaysOffExemplarFilter OpenTelemetry.Metrics.AlwaysOffExemplarFilter.AlwaysOffExemplarFilter() -> void OpenTelemetry.Metrics.AlwaysOnExemplarFilter OpenTelemetry.Metrics.AlwaysOnExemplarFilter.AlwaysOnExemplarFilter() -> void +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.Base2ExponentialBucketHistogramConfiguration() -> void +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxScale.get -> int +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxScale.set -> void +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.get -> int +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.set -> void OpenTelemetry.Metrics.Exemplar OpenTelemetry.Metrics.Exemplar.DoubleValue.get -> double OpenTelemetry.Metrics.Exemplar.Exemplar() -> void diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index c79ebddeb5e..a06e307d7c4 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -2,6 +2,12 @@ OpenTelemetry.Metrics.AlwaysOffExemplarFilter OpenTelemetry.Metrics.AlwaysOffExemplarFilter.AlwaysOffExemplarFilter() -> void OpenTelemetry.Metrics.AlwaysOnExemplarFilter OpenTelemetry.Metrics.AlwaysOnExemplarFilter.AlwaysOnExemplarFilter() -> void +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.Base2ExponentialBucketHistogramConfiguration() -> void +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxScale.get -> int +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxScale.set -> void +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.get -> int +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.set -> void OpenTelemetry.Metrics.Exemplar OpenTelemetry.Metrics.Exemplar.DoubleValue.get -> double OpenTelemetry.Metrics.Exemplar.Exemplar() -> void diff --git a/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt index c79ebddeb5e..a06e307d7c4 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt @@ -2,6 +2,12 @@ OpenTelemetry.Metrics.AlwaysOffExemplarFilter OpenTelemetry.Metrics.AlwaysOffExemplarFilter.AlwaysOffExemplarFilter() -> void OpenTelemetry.Metrics.AlwaysOnExemplarFilter OpenTelemetry.Metrics.AlwaysOnExemplarFilter.AlwaysOnExemplarFilter() -> void +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.Base2ExponentialBucketHistogramConfiguration() -> void +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxScale.get -> int +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxScale.set -> void +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.get -> int +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.set -> void OpenTelemetry.Metrics.Exemplar OpenTelemetry.Metrics.Exemplar.DoubleValue.get -> double OpenTelemetry.Metrics.Exemplar.Exemplar() -> void diff --git a/src/OpenTelemetry/CHANGELOG.md b/src/OpenTelemetry/CHANGELOG.md index a7b9a3e133a..33ac5c0f5f0 100644 --- a/src/OpenTelemetry/CHANGELOG.md +++ b/src/OpenTelemetry/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +* Add support for configuring the + [Base2 Exponential Bucket Histogram Aggregation](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#base2-exponential-bucket-histogram-aggregation) + using the `AddView` API. This aggregation is supported by OTLP but not yet by + Prometheus. + ([#4337](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4337)) + * Implementation of `SuppressInstrumentationScope` changed to improve performance. ([#4304](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4304)) diff --git a/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs b/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs index 9f4c02c77f7..7b12df3054a 100644 --- a/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs +++ b/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#nullable enable + using System.Diagnostics; using System.Diagnostics.Tracing; @@ -39,15 +41,6 @@ public void SpanProcessorException(string evnt, Exception ex) } } - [NonEvent] - public void TracestateExtractException(Exception ex) - { - if (this.IsEnabled(EventLevel.Warning, EventKeywords.All)) - { - this.TracestateExtractError(ex.ToInvariantString()); - } - } - [NonEvent] public void MetricObserverCallbackException(Exception exception) { @@ -147,12 +140,6 @@ public void DroppedExportProcessorItems(string exportProcessorName, string expor } } - [Event(3, Message = "Exporter returned error '{0}'.", Level = EventLevel.Warning)] - public void ExporterErrorResult(ExportResult exportResult) - { - this.WriteEvent(3, exportResult); - } - [Event(4, Message = "Unknown error in SpanProcessor event '{0}': '{1}'.", Level = EventLevel.Error)] public void SpanProcessorException(string evnt, string ex) { @@ -165,12 +152,6 @@ public void InvalidArgument(string methodName, string argumentName, string issue this.WriteEvent(8, methodName, argumentName, issue); } - [Event(14, Message = "Tracestate parse error: '{0}'", Level = EventLevel.Warning)] - public void TracestateExtractError(string error) - { - this.WriteEvent(14, error); - } - [Event(16, Message = "Exception occurred while invoking Observable instrument callback. Exception: '{0}'", Level = EventLevel.Warning)] public void ObservableInstrumentCallbackException(string exception) { @@ -184,7 +165,7 @@ public void ActivityStarted(string name, string id) } [Event(25, Message = "Activity stopped. Name = '{0}', Id = '{1}'.", Level = EventLevel.Verbose)] - public void ActivityStopped(string name, string id) + public void ActivityStopped(string name, string? id) { this.WriteEvent(25, name, id); } @@ -298,7 +279,7 @@ public void TracerProviderSdkEvent(string message) } [Event(47, Message = "{0} environment variable has an invalid value: '{1}'", Level = EventLevel.Warning)] - public void InvalidEnvironmentVariable(string key, string value) + public void InvalidEnvironmentVariable(string key, string? value) { this.WriteEvent(47, key, value); } @@ -321,7 +302,7 @@ public override void Dispose() protected override void OnEventSourceCreated(EventSource eventSource) { - if (eventSource?.Name.StartsWith("OpenTelemetry", StringComparison.OrdinalIgnoreCase) == true) + if (eventSource.Name.StartsWith("OpenTelemetry", StringComparison.OrdinalIgnoreCase)) { this.eventSources.Add(eventSource); this.EnableEvents(eventSource, EventLevel.Verbose, EventKeywords.All); @@ -332,8 +313,8 @@ protected override void OnEventSourceCreated(EventSource eventSource) protected override void OnEventWritten(EventWrittenEventArgs e) { - string message; - if (e.Message != null && (e.Payload?.Count ?? 0) > 0) + string? message; + if (e.Message != null && e.Payload != null && e.Payload.Count > 0) { message = string.Format(e.Message, e.Payload.ToArray()); } diff --git a/src/OpenTelemetry/Internal/PeerServiceResolver.cs b/src/OpenTelemetry/Internal/PeerServiceResolver.cs index bfbb84dec04..3227bb1e700 100644 --- a/src/OpenTelemetry/Internal/PeerServiceResolver.cs +++ b/src/OpenTelemetry/Internal/PeerServiceResolver.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#nullable enable + using System.Runtime.CompilerServices; using OpenTelemetry.Trace; @@ -32,19 +34,19 @@ internal static class PeerServiceResolver public interface IPeerServiceState { - string PeerService { get; set; } + string? PeerService { get; set; } int? PeerServicePriority { get; set; } - string HostName { get; set; } + string? HostName { get; set; } - string IpAddress { get; set; } + string? IpAddress { get; set; } long Port { get; set; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InspectTag(ref T state, string key, string value) + public static void InspectTag(ref T state, string key, string? value) where T : struct, IPeerServiceState { if (PeerServiceKeyResolutionDictionary.TryGetValue(key, out int priority) @@ -78,7 +80,7 @@ public static void InspectTag(ref T state, string key, long value) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Resolve(ref T state, out string peerServiceName, out bool addAsTag) + public static void Resolve(ref T state, out string? peerServiceName, out bool addAsTag) where T : struct, IPeerServiceState { peerServiceName = state.PeerService; diff --git a/src/OpenTelemetry/Internal/PeriodicExportingMetricReaderHelper.cs b/src/OpenTelemetry/Internal/PeriodicExportingMetricReaderHelper.cs index 5514871993c..6fcc823c748 100644 --- a/src/OpenTelemetry/Internal/PeriodicExportingMetricReaderHelper.cs +++ b/src/OpenTelemetry/Internal/PeriodicExportingMetricReaderHelper.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#nullable enable + namespace OpenTelemetry.Metrics; internal static class PeriodicExportingMetricReaderHelper @@ -28,10 +30,10 @@ internal static PeriodicExportingMetricReader CreatePeriodicExportingMetricReade int defaultExportTimeoutMilliseconds = DefaultExportTimeoutMilliseconds) { var exportInterval = - options.PeriodicExportingMetricReaderOptions?.ExportIntervalMilliseconds ?? defaultExportIntervalMilliseconds; + options.PeriodicExportingMetricReaderOptions.ExportIntervalMilliseconds ?? defaultExportIntervalMilliseconds; var exportTimeout = - options.PeriodicExportingMetricReaderOptions?.ExportTimeoutMilliseconds ?? defaultExportTimeoutMilliseconds; + options.PeriodicExportingMetricReaderOptions.ExportTimeoutMilliseconds ?? defaultExportTimeoutMilliseconds; var metricReader = new PeriodicExportingMetricReader(exporter, exportInterval, exportTimeout) { diff --git a/src/OpenTelemetry/Internal/ResourceSemanticConventions.cs b/src/OpenTelemetry/Internal/ResourceSemanticConventions.cs index d07d6683103..e3e50430439 100644 --- a/src/OpenTelemetry/Internal/ResourceSemanticConventions.cs +++ b/src/OpenTelemetry/Internal/ResourceSemanticConventions.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#nullable enable + namespace OpenTelemetry.Resources { internal static class ResourceSemanticConventions diff --git a/src/OpenTelemetry/Metrics/AggregatorStore.cs b/src/OpenTelemetry/Metrics/AggregatorStore.cs index 94c7c5cada0..60792d14758 100644 --- a/src/OpenTelemetry/Metrics/AggregatorStore.cs +++ b/src/OpenTelemetry/Metrics/AggregatorStore.cs @@ -39,6 +39,7 @@ internal sealed class AggregatorStore private readonly AggregationType aggType; private readonly double[] histogramBounds; private readonly int exponentialHistogramMaxSize; + private readonly int exponentialHistogramMaxScale; private readonly UpdateLongDelegate updateLongCallback; private readonly UpdateDoubleDelegate updateDoubleCallback; private readonly int maxMetricPoints; @@ -64,6 +65,7 @@ internal AggregatorStore( this.outputDelta = temporality == AggregationTemporality.Delta; this.histogramBounds = metricStreamIdentity.HistogramBucketBounds ?? Metric.DefaultHistogramBounds; this.exponentialHistogramMaxSize = metricStreamIdentity.ExponentialHistogramMaxSize; + this.exponentialHistogramMaxScale = metricStreamIdentity.ExponentialHistogramMaxScale; this.StartTimeExclusive = DateTimeOffset.UtcNow; this.exemplarFilter = exemplarFilter ?? new AlwaysOffExemplarFilter(); if (metricStreamIdentity.TagKeys == null) @@ -188,7 +190,7 @@ private void InitializeZeroTagPointIfNotInitialized() { if (!this.zeroTagMetricPointInitialized) { - this.metricPoints[0] = new MetricPoint(this, this.aggType, null, this.histogramBounds, this.exponentialHistogramMaxSize); + this.metricPoints[0] = new MetricPoint(this, this.aggType, null, this.histogramBounds, this.exponentialHistogramMaxSize, this.exponentialHistogramMaxScale); this.zeroTagMetricPointInitialized = true; } } @@ -255,7 +257,7 @@ private int LookupAggregatorStore(KeyValuePair[] tagKeysAndValue } ref var metricPoint = ref this.metricPoints[aggregatorIndex]; - metricPoint = new MetricPoint(this, this.aggType, sortedTags.KeyValuePairs, this.histogramBounds, this.exponentialHistogramMaxSize); + metricPoint = new MetricPoint(this, this.aggType, sortedTags.KeyValuePairs, this.histogramBounds, this.exponentialHistogramMaxSize, this.exponentialHistogramMaxScale); // Add to dictionary *after* initializing MetricPoint // as other threads can start writing to the @@ -304,7 +306,7 @@ private int LookupAggregatorStore(KeyValuePair[] tagKeysAndValue } ref var metricPoint = ref this.metricPoints[aggregatorIndex]; - metricPoint = new MetricPoint(this, this.aggType, givenTags.KeyValuePairs, this.histogramBounds, this.exponentialHistogramMaxSize); + metricPoint = new MetricPoint(this, this.aggType, givenTags.KeyValuePairs, this.histogramBounds, this.exponentialHistogramMaxSize, this.exponentialHistogramMaxScale); // Add to dictionary *after* initializing MetricPoint // as other threads can start writing to the diff --git a/src/OpenTelemetry/Metrics/Base2ExponentialBucketHistogram.cs b/src/OpenTelemetry/Metrics/Base2ExponentialBucketHistogram.cs index 4df0d91fc6d..55d396fadea 100644 --- a/src/OpenTelemetry/Metrics/Base2ExponentialBucketHistogram.cs +++ b/src/OpenTelemetry/Metrics/Base2ExponentialBucketHistogram.cs @@ -49,12 +49,10 @@ internal sealed class Base2ExponentialBucketHistogram /// /// The maximum number of buckets in each of the positive and negative ranges, not counting the special zero bucket. The default value is 160. /// - public Base2ExponentialBucketHistogram(int maxBuckets = 160) - : this(maxBuckets, 20) - { - } - - internal Base2ExponentialBucketHistogram(int maxBuckets, int scale) + /// + /// Maximum scale factor. The default value is 20. + /// + public Base2ExponentialBucketHistogram(int maxBuckets = 160, int scale = 20) { /* The following table is calculated based on [ MapToIndex(double.Epsilon), MapToIndex(double.MaxValue) ]: diff --git a/src/OpenTelemetry/Metrics/Base2ExponentialBucketHistogramConfiguration.cs b/src/OpenTelemetry/Metrics/Base2ExponentialBucketHistogramConfiguration.cs index f6f7db9df19..1394d3a8715 100644 --- a/src/OpenTelemetry/Metrics/Base2ExponentialBucketHistogramConfiguration.cs +++ b/src/OpenTelemetry/Metrics/Base2ExponentialBucketHistogramConfiguration.cs @@ -19,9 +19,10 @@ namespace OpenTelemetry.Metrics; /// /// Stores configuration for a histogram metric stream with base-2 exponential bucket boundaries. /// -internal sealed class Base2ExponentialBucketHistogramConfiguration : HistogramConfiguration +public sealed class Base2ExponentialBucketHistogramConfiguration : HistogramConfiguration { - private int maxSize = 160; + private int maxSize = Metric.DefaultExponentialHistogramMaxBuckets; + private int maxScale = Metric.DefaultExponentialHistogramMaxScale; /// /// Gets or sets the maximum number of buckets in each of the positive and negative ranges, not counting the special zero bucket. @@ -46,4 +47,29 @@ public int MaxSize this.maxSize = value; } } + + /// + /// Gets or sets the maximum scale factor used to determine the resolution of bucket boundaries. + /// The higher the scale the higher the resolution. + /// + /// + /// The default value is 20. The minimum size is -11. The maximum size is 20. + /// + public int MaxScale + { + get + { + return this.maxScale; + } + + set + { + if (value < -11 || value > 20) + { + throw new ArgumentException($"Histogram max scale is invalid. Max scale must be in the range [-11, 20].", nameof(value)); + } + + this.maxScale = value; + } + } } diff --git a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs index b7511e8a7d7..d5abef60510 100644 --- a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs +++ b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs @@ -284,7 +284,10 @@ internal void Copy(long[] dst) if (this.trait != null) { - Array.Copy(this.trait, dst, this.Capacity); + for (var i = 0; i < this.Size; ++i) + { + dst[i] = this[this.Offset + i]; + } } } diff --git a/src/OpenTelemetry/Metrics/ExponentialHistogramBuckets.cs b/src/OpenTelemetry/Metrics/ExponentialHistogramBuckets.cs index 44f628fe1c7..84db5a4a0bf 100644 --- a/src/OpenTelemetry/Metrics/ExponentialHistogramBuckets.cs +++ b/src/OpenTelemetry/Metrics/ExponentialHistogramBuckets.cs @@ -21,6 +21,7 @@ namespace OpenTelemetry.Metrics; public sealed class ExponentialHistogramBuckets { private long[] buckets = Array.Empty(); + private int size; internal ExponentialHistogramBuckets() { @@ -28,7 +29,7 @@ internal ExponentialHistogramBuckets() public int Offset { get; private set; } - public Enumerator GetEnumerator() => new(this.buckets); + public Enumerator GetEnumerator() => new(this.buckets, this.size); internal void SnapshotBuckets(CircularBufferBuckets buckets) { @@ -37,6 +38,7 @@ internal void SnapshotBuckets(CircularBufferBuckets buckets) this.buckets = new long[buckets.Capacity]; } + this.size = buckets.Size; this.Offset = buckets.Offset; buckets.Copy(this.buckets); } @@ -44,6 +46,7 @@ internal void SnapshotBuckets(CircularBufferBuckets buckets) internal ExponentialHistogramBuckets Copy() { var copy = new ExponentialHistogramBuckets(); + copy.size = this.size; copy.Offset = this.Offset; copy.buckets = new long[this.buckets.Length]; Array.Copy(this.buckets, copy.buckets, this.buckets.Length); @@ -57,11 +60,13 @@ internal ExponentialHistogramBuckets Copy() public struct Enumerator { private readonly long[] buckets; + private readonly int size; private int index; - internal Enumerator(long[] buckets) + internal Enumerator(long[] buckets, int size) { this.index = 0; + this.size = size; this.buckets = buckets; this.Current = default; } @@ -81,7 +86,7 @@ internal Enumerator(long[] buckets) /// collection. public bool MoveNext() { - if (this.index < this.buckets.Length) + if (this.index < this.size) { this.Current = this.buckets[this.index++]; return true; diff --git a/src/OpenTelemetry/Metrics/Metric.cs b/src/OpenTelemetry/Metrics/Metric.cs index cf766cf6273..1b13793ff4b 100644 --- a/src/OpenTelemetry/Metrics/Metric.cs +++ b/src/OpenTelemetry/Metrics/Metric.cs @@ -25,6 +25,8 @@ public sealed class Metric { internal const int DefaultExponentialHistogramMaxBuckets = 160; + internal const int DefaultExponentialHistogramMaxScale = 20; + internal static readonly double[] DefaultHistogramBounds = new double[] { 0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000 }; private readonly AggregatorStore aggStore; diff --git a/src/OpenTelemetry/Metrics/MetricPoint.cs b/src/OpenTelemetry/Metrics/MetricPoint.cs index b411e937c98..69e1e9b2b58 100644 --- a/src/OpenTelemetry/Metrics/MetricPoint.cs +++ b/src/OpenTelemetry/Metrics/MetricPoint.cs @@ -48,7 +48,8 @@ internal MetricPoint( AggregationType aggType, KeyValuePair[] tagKeysAndValues, double[] histogramExplicitBounds, - int exponentialHistogramMaxSize) + int exponentialHistogramMaxSize, + int exponentialHistogramMaxScale) { Debug.Assert(aggregatorStore != null, "AggregatorStore was null."); Debug.Assert(histogramExplicitBounds != null, "Histogram explicit Bounds was null."); @@ -81,7 +82,7 @@ internal MetricPoint( this.aggType == AggregationType.Base2ExponentialHistogramWithMinMax) { this.mpComponents = new MetricPointOptionalComponents(); - this.mpComponents.Base2ExponentialBucketHistogram = new Base2ExponentialBucketHistogram(exponentialHistogramMaxSize); + this.mpComponents.Base2ExponentialBucketHistogram = new Base2ExponentialBucketHistogram(exponentialHistogramMaxSize, exponentialHistogramMaxScale); } else { @@ -1374,6 +1375,76 @@ internal void TakeSnapshotWithExemplar(bool outputDelta) sw.SpinOnce(); } + break; + } + + case AggregationType.Base2ExponentialHistogram: + { + var histogram = this.mpComponents!.Base2ExponentialBucketHistogram; + var sw = default(SpinWait); + while (true) + { + if (Interlocked.Exchange(ref histogram.IsCriticalSectionOccupied, 1) == 0) + { + // Lock acquired + this.snapshotValue.AsLong = this.runningValue.AsLong; + histogram.SnapshotSum = histogram.RunningSum; + histogram.Snapshot(); + + if (outputDelta) + { + this.runningValue.AsLong = 0; + histogram.RunningSum = 0; + histogram.Reset(); + } + + this.MetricPointStatus = MetricPointStatus.NoCollectPending; + + // Release lock + Interlocked.Exchange(ref histogram.IsCriticalSectionOccupied, 0); + break; + } + + sw.SpinOnce(); + } + + break; + } + + case AggregationType.Base2ExponentialHistogramWithMinMax: + { + var histogram = this.mpComponents!.Base2ExponentialBucketHistogram; + var sw = default(SpinWait); + while (true) + { + if (Interlocked.Exchange(ref histogram.IsCriticalSectionOccupied, 1) == 0) + { + // Lock acquired + this.snapshotValue.AsLong = this.runningValue.AsLong; + histogram.SnapshotSum = histogram.RunningSum; + histogram.Snapshot(); + histogram.SnapshotMin = histogram.RunningMin; + histogram.SnapshotMax = histogram.RunningMax; + + if (outputDelta) + { + this.runningValue.AsLong = 0; + histogram.RunningSum = 0; + histogram.Reset(); + histogram.RunningMin = double.PositiveInfinity; + histogram.RunningMax = double.NegativeInfinity; + } + + this.MetricPointStatus = MetricPointStatus.NoCollectPending; + + // Release lock + Interlocked.Exchange(ref histogram.IsCriticalSectionOccupied, 0); + break; + } + + sw.SpinOnce(); + } + break; } } diff --git a/src/OpenTelemetry/Metrics/MetricStreamIdentity.cs b/src/OpenTelemetry/Metrics/MetricStreamIdentity.cs index 9d38fda4154..cd879962f07 100644 --- a/src/OpenTelemetry/Metrics/MetricStreamIdentity.cs +++ b/src/OpenTelemetry/Metrics/MetricStreamIdentity.cs @@ -36,6 +36,7 @@ public MetricStreamIdentity(Instrument instrument, MetricStreamConfiguration met this.TagKeys = metricStreamConfiguration?.CopiedTagKeys; this.HistogramBucketBounds = (metricStreamConfiguration as ExplicitBucketHistogramConfiguration)?.CopiedBoundaries; this.ExponentialHistogramMaxSize = (metricStreamConfiguration as Base2ExponentialBucketHistogramConfiguration)?.MaxSize ?? 0; + this.ExponentialHistogramMaxScale = (metricStreamConfiguration as Base2ExponentialBucketHistogramConfiguration)?.MaxScale ?? 0; this.HistogramRecordMinMax = (metricStreamConfiguration as HistogramConfiguration)?.RecordMinMax ?? true; #if NETSTANDARD2_1 || NET6_0_OR_GREATER @@ -50,6 +51,7 @@ public MetricStreamIdentity(Instrument instrument, MetricStreamConfiguration met hashCode.Add(this.ViewId); hashCode.Add(this.TagKeys, StringArrayComparer); hashCode.Add(this.ExponentialHistogramMaxSize); + hashCode.Add(this.ExponentialHistogramMaxScale); if (this.HistogramBucketBounds != null) { for (var i = 0; i < this.HistogramBucketBounds.Length; ++i) @@ -69,6 +71,7 @@ public MetricStreamIdentity(Instrument instrument, MetricStreamConfiguration met hash = (hash * 31) + this.InstrumentName.GetHashCode(); hash = (hash * 31) + this.HistogramRecordMinMax.GetHashCode(); hash = (hash * 31) + this.ExponentialHistogramMaxSize.GetHashCode(); + hash = (hash * 31) + this.ExponentialHistogramMaxScale.GetHashCode(); hash = (hash * 31) + (this.Unit?.GetHashCode() ?? 0); hash = (hash * 31) + (this.Description?.GetHashCode() ?? 0); hash = (hash * 31) + (this.ViewId ?? 0); @@ -109,6 +112,8 @@ public MetricStreamIdentity(Instrument instrument, MetricStreamConfiguration met public int ExponentialHistogramMaxSize { get; } + public int ExponentialHistogramMaxScale { get; } + public bool HistogramRecordMinMax { get; } public static bool operator ==(MetricStreamIdentity metricIdentity1, MetricStreamIdentity metricIdentity2) => metricIdentity1.Equals(metricIdentity2); @@ -131,6 +136,7 @@ public bool Equals(MetricStreamIdentity other) && this.ViewId == other.ViewId && this.HistogramRecordMinMax == other.HistogramRecordMinMax && this.ExponentialHistogramMaxSize == other.ExponentialHistogramMaxSize + && this.ExponentialHistogramMaxScale == other.ExponentialHistogramMaxScale && StringArrayComparer.Equals(this.TagKeys, other.TagKeys) && HistogramBoundsEqual(this.HistogramBucketBounds, other.HistogramBucketBounds); } diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs index 48e41e3fb86..62e3c10d6b7 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs @@ -383,6 +383,8 @@ public void TestCounterToOtlpMetric(string name, string description, string unit [Theory] [InlineData("test_counter", null, null, 123L, null, MetricReaderTemporalityPreference.Cumulative)] [InlineData("test_counter", null, null, null, 123.45, MetricReaderTemporalityPreference.Cumulative)] + [InlineData("test_counter", null, null, -123L, null, MetricReaderTemporalityPreference.Cumulative)] + [InlineData("test_counter", null, null, null, -123.45, MetricReaderTemporalityPreference.Cumulative)] [InlineData("test_counter", null, null, 123L, null, MetricReaderTemporalityPreference.Delta)] [InlineData("test_counter", "description", "unit", 123L, null, MetricReaderTemporalityPreference.Cumulative)] [InlineData("test_counter", null, null, 123L, null, MetricReaderTemporalityPreference.Delta, "key1", "value1", "key2", 123)] @@ -472,6 +474,135 @@ public void TestUpDownCounterToOtlpMetric(string name, string description, strin [Theory] [InlineData("test_histogram", null, null, 123L, null, MetricReaderTemporalityPreference.Cumulative)] [InlineData("test_histogram", null, null, null, 123.45, MetricReaderTemporalityPreference.Cumulative)] + [InlineData("test_histogram", null, null, -123L, null, MetricReaderTemporalityPreference.Cumulative)] + [InlineData("test_histogram", null, null, null, -123.45, MetricReaderTemporalityPreference.Cumulative)] + [InlineData("test_histogram", null, null, 123L, null, MetricReaderTemporalityPreference.Delta)] + [InlineData("test_histogram", "description", "unit", 123L, null, MetricReaderTemporalityPreference.Cumulative)] + [InlineData("test_histogram", null, null, 123L, null, MetricReaderTemporalityPreference.Delta, "key1", "value1", "key2", 123)] + public void TestExponentialHistogramToOtlpMetric(string name, string description, string unit, long? longValue, double? doubleValue, MetricReaderTemporalityPreference aggregationTemporality, params object[] keysValues) + { + var metrics = new List(); + + using var meter = new Meter(Utils.GetCurrentMethodName()); + using var provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddInMemoryExporter(metrics, metricReaderOptions => + { + metricReaderOptions.TemporalityPreference = aggregationTemporality; + }) + .AddView(instrument => + { + return new Base2ExponentialBucketHistogramConfiguration(); + }) + .Build(); + + var attributes = ToAttributes(keysValues).ToArray(); + if (longValue.HasValue) + { + var histogram = meter.CreateHistogram(name, unit, description); + histogram.Record(longValue.Value, attributes); + histogram.Record(0, attributes); + } + else + { + var histogram = meter.CreateHistogram(name, unit, description); + histogram.Record(doubleValue.Value, attributes); + histogram.Record(0, attributes); + } + + provider.ForceFlush(); + + var batch = new Batch(metrics.ToArray(), metrics.Count); + + var request = new OtlpCollector.ExportMetricsServiceRequest(); + request.AddMetrics(ResourceBuilder.CreateEmpty().Build().ToOtlpResource(), batch); + + var resourceMetric = request.ResourceMetrics.Single(); + var scopeMetrics = resourceMetric.ScopeMetrics.Single(); + var actual = scopeMetrics.Metrics.Single(); + + Assert.Equal(name, actual.Name); + Assert.Equal(description ?? string.Empty, actual.Description); + Assert.Equal(unit ?? string.Empty, actual.Unit); + + Assert.Equal(OtlpMetrics.Metric.DataOneofCase.ExponentialHistogram, actual.DataCase); + + Assert.Null(actual.Gauge); + Assert.Null(actual.Sum); + Assert.Null(actual.Histogram); + Assert.NotNull(actual.ExponentialHistogram); + Assert.Null(actual.Summary); + + var otlpAggregationTemporality = aggregationTemporality == MetricReaderTemporalityPreference.Cumulative + ? OtlpMetrics.AggregationTemporality.Cumulative + : OtlpMetrics.AggregationTemporality.Delta; + Assert.Equal(otlpAggregationTemporality, actual.ExponentialHistogram.AggregationTemporality); + + Assert.Single(actual.ExponentialHistogram.DataPoints); + var dataPoint = actual.ExponentialHistogram.DataPoints.First(); + Assert.True(dataPoint.StartTimeUnixNano > 0); + Assert.True(dataPoint.TimeUnixNano > 0); + + Assert.Equal(20, dataPoint.Scale); + Assert.Equal(2UL, dataPoint.Count); + Assert.Equal(1UL, dataPoint.ZeroCount); + + if (longValue.HasValue) + { + // Known issue: Negative measurements affect the Sum. Per the spec, they should not. + Assert.Equal((double)longValue, dataPoint.Sum); + if (longValue > 0) + { + Assert.True(dataPoint.Positive.Offset > 0); + Assert.Equal(1UL, dataPoint.Positive.BucketCounts[0]); + Assert.True(dataPoint.Negative.Offset == 0); + Assert.Empty(dataPoint.Negative.BucketCounts); + } + else + { + Assert.True(dataPoint.Negative.Offset > 0); + Assert.Equal(1UL, dataPoint.Negative.BucketCounts[0]); + Assert.True(dataPoint.Positive.Offset == 0); + Assert.Empty(dataPoint.Positive.BucketCounts); + } + } + else + { + // Known issue: Negative measurements affect the Sum. Per the spec, they should not. + Assert.Equal(doubleValue, dataPoint.Sum); + if (doubleValue > 0) + { + Assert.True(dataPoint.Positive.Offset > 0); + Assert.Equal(1UL, dataPoint.Positive.BucketCounts[0]); + Assert.True(dataPoint.Negative.Offset == 0); + Assert.Empty(dataPoint.Negative.BucketCounts); + } + else + { + Assert.True(dataPoint.Negative.Offset > 0); + Assert.Equal(1UL, dataPoint.Negative.BucketCounts[0]); + Assert.True(dataPoint.Positive.Offset == 0); + Assert.Empty(dataPoint.Positive.BucketCounts); + } + } + + if (attributes.Length > 0) + { + OtlpTestHelpers.AssertOtlpAttributes(attributes, dataPoint.Attributes); + } + else + { + Assert.Empty(dataPoint.Attributes); + } + + Assert.Empty(dataPoint.Exemplars); + } + + [Theory] + [InlineData("test_histogram", null, null, 123L, null, MetricReaderTemporalityPreference.Cumulative)] + [InlineData("test_histogram", null, null, null, 123.45, MetricReaderTemporalityPreference.Cumulative)] + [InlineData("test_histogram", null, null, -123L, null, MetricReaderTemporalityPreference.Cumulative)] + [InlineData("test_histogram", null, null, null, -123.45, MetricReaderTemporalityPreference.Cumulative)] [InlineData("test_histogram", null, null, 123L, null, MetricReaderTemporalityPreference.Delta)] [InlineData("test_histogram", "description", "unit", 123L, null, MetricReaderTemporalityPreference.Cumulative)] [InlineData("test_histogram", null, null, 123L, null, MetricReaderTemporalityPreference.Delta, "key1", "value1", "key2", 123)] @@ -535,6 +666,7 @@ public void TestHistogramToOtlpMetric(string name, string description, string un Assert.Equal(1UL, dataPoint.Count); + // Known issue: Negative measurements affect the Sum. Per the spec, they should not. if (longValue.HasValue) { Assert.Equal((double)longValue, dataPoint.Sum); diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs index a3003baf8a7..db156c005b7 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs @@ -551,6 +551,32 @@ public void ToOtlpSpanActivityStatusTakesPrecedenceOverStatusTagsWhenActivitySta Assert.Equal(StatusDescriptionOnError, otlpSpan.Status.Message); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ToOtlpSpanTraceStateTest(bool traceStateWasSet) + { + using var activitySource = new ActivitySource(nameof(this.ToOtlpSpanTest)); + using var activity = activitySource.StartActivity("Name"); + string tracestate = "a=b;c=d"; + if (traceStateWasSet) + { + activity.TraceStateString = tracestate; + } + + var otlpSpan = activity.ToOtlpSpan(DefaultSdkLimitOptions); + + if (traceStateWasSet) + { + Assert.NotNull(otlpSpan.TraceState); + Assert.Equal(tracestate, otlpSpan.TraceState); + } + else + { + Assert.Equal(string.Empty, otlpSpan.TraceState); + } + } + [Fact] public void ToOtlpSpanPeerServiceTest() { diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs index 9970be5c83d..4819867c527 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs @@ -462,5 +462,30 @@ public void HistogramNaN() + "$").Replace('\'', '"'), Encoding.UTF8.GetString(buffer, 0, cursor)); } + + [Fact] + public void ExponentialHistogramIsIgnoredForNow() + { + var buffer = new byte[85000]; + var metrics = new List(); + + using var meter = new Meter(Utils.GetCurrentMethodName()); + using var provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddView(instrument => new Base2ExponentialBucketHistogramConfiguration()) + .AddInMemoryExporter(metrics) + .Build(); + + var histogram = meter.CreateHistogram("test_histogram"); + histogram.Record(18); + histogram.Record(100); + + provider.ForceFlush(); + + var cursor = PrometheusSerializer.WriteMetric(buffer, 0, metrics[0]); + Assert.Matches( + "^$", + Encoding.UTF8.GetString(buffer, 0, cursor)); + } } } diff --git a/test/OpenTelemetry.Tests/Internal/PeriodicExportingMetricReaderHelperTests.cs b/test/OpenTelemetry.Tests/Internal/PeriodicExportingMetricReaderHelperTests.cs index 37b2a3955fa..e206433e40d 100644 --- a/test/OpenTelemetry.Tests/Internal/PeriodicExportingMetricReaderHelperTests.cs +++ b/test/OpenTelemetry.Tests/Internal/PeriodicExportingMetricReaderHelperTests.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#nullable enable + using Microsoft.Extensions.Configuration; using OpenTelemetry.Exporter; using OpenTelemetry.Metrics; @@ -21,7 +23,7 @@ namespace OpenTelemetry.Internal.Tests { - public class PeriodicExportingMetricReaderHelperTests : IDisposable + public sealed class PeriodicExportingMetricReaderHelperTests : IDisposable { public PeriodicExportingMetricReaderHelperTests() { @@ -140,14 +142,11 @@ private static void ClearEnvVars() } private static PeriodicExportingMetricReader CreatePeriodicExportingMetricReader( - MetricReaderOptions options = null) + MetricReaderOptions? options = null) { - if (options == null) - { - options = new(); - } + options ??= new(); - var dummyMetricExporter = new InMemoryExporter(new Metric[0]); + var dummyMetricExporter = new InMemoryExporter(Array.Empty()); return PeriodicExportingMetricReaderHelper.CreatePeriodicExportingMetricReader(dummyMetricExporter, options); } } diff --git a/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsConfigRefresherTest.cs b/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsConfigRefresherTest.cs index a28e68c3a2f..7368d70d3d6 100644 --- a/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsConfigRefresherTest.cs +++ b/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsConfigRefresherTest.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#nullable enable + using System.Diagnostics; using System.Text; using Xunit; @@ -43,7 +45,7 @@ public void SelfDiagnosticsConfigRefresher_OmitAsConfigured() using var configRefresher = new SelfDiagnosticsConfigRefresher(); // Emitting event of EventLevel.Warning - OpenTelemetrySdkEventSource.Log.ExporterErrorResult(ExportResult.Success); + OpenTelemetrySdkEventSource.Log.ObservableInstrumentCallbackException("exception"); int bufferSize = 512; byte[] actualBytes = ReadFile(bufferSize); @@ -97,12 +99,12 @@ private static string ParseLogMessage(string logLine) private static byte[] ReadFile(int byteCount) { - var outputFileName = Path.GetFileName(Process.GetCurrentProcess().MainModule.FileName) + "." + var outputFileName = Path.GetFileName(Process.GetCurrentProcess().MainModule?.FileName) + "." + Process.GetCurrentProcess().Id + ".log"; var outputFilePath = Path.Combine(".", outputFileName); using var file = File.Open(outputFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); byte[] actualBytes = new byte[byteCount]; - file.Read(actualBytes, 0, byteCount); + _ = file.Read(actualBytes, 0, byteCount); return actualBytes; } @@ -126,6 +128,7 @@ private static void CleanupConfigFile() } catch { + // ignore any exceptions while removing files } } } diff --git a/test/OpenTelemetry.Tests/Metrics/AggregatorTest.cs b/test/OpenTelemetry.Tests/Metrics/AggregatorTest.cs index 3fb3d1ee62e..ef76b879a7c 100644 --- a/test/OpenTelemetry.Tests/Metrics/AggregatorTest.cs +++ b/test/OpenTelemetry.Tests/Metrics/AggregatorTest.cs @@ -30,7 +30,7 @@ public class AggregatorTest [Fact] public void HistogramDistributeToAllBucketsDefault() { - var histogramPoint = new MetricPoint(this.aggregatorStore, AggregationType.HistogramWithBuckets, null, Metric.DefaultHistogramBounds, Metric.DefaultExponentialHistogramMaxBuckets); + var histogramPoint = new MetricPoint(this.aggregatorStore, AggregationType.HistogramWithBuckets, null, Metric.DefaultHistogramBounds, Metric.DefaultExponentialHistogramMaxBuckets, Metric.DefaultExponentialHistogramMaxScale); histogramPoint.Update(-1); histogramPoint.Update(0); histogramPoint.Update(2); @@ -81,7 +81,7 @@ public void HistogramDistributeToAllBucketsDefault() public void HistogramDistributeToAllBucketsCustom() { var boundaries = new double[] { 10, 20 }; - var histogramPoint = new MetricPoint(this.aggregatorStore, AggregationType.HistogramWithBuckets, null, boundaries, Metric.DefaultExponentialHistogramMaxBuckets); + var histogramPoint = new MetricPoint(this.aggregatorStore, AggregationType.HistogramWithBuckets, null, boundaries, Metric.DefaultExponentialHistogramMaxBuckets, Metric.DefaultExponentialHistogramMaxScale); // 5 recordings <=10 histogramPoint.Update(-10); @@ -129,7 +129,7 @@ public void HistogramBinaryBucketTest() boundaries[i] = i; } - var histogramPoint = new MetricPoint(this.aggregatorStore, AggregationType.HistogramWithBuckets, null, boundaries, Metric.DefaultExponentialHistogramMaxBuckets); + var histogramPoint = new MetricPoint(this.aggregatorStore, AggregationType.HistogramWithBuckets, null, boundaries, Metric.DefaultExponentialHistogramMaxBuckets, Metric.DefaultExponentialHistogramMaxScale); // Act histogramPoint.Update(-1); @@ -162,7 +162,7 @@ public void HistogramBinaryBucketTest() public void HistogramWithOnlySumCount() { var boundaries = Array.Empty(); - var histogramPoint = new MetricPoint(this.aggregatorStore, AggregationType.Histogram, null, boundaries, Metric.DefaultExponentialHistogramMaxBuckets); + var histogramPoint = new MetricPoint(this.aggregatorStore, AggregationType.Histogram, null, boundaries, Metric.DefaultExponentialHistogramMaxBuckets, Metric.DefaultExponentialHistogramMaxScale); histogramPoint.Update(-10); histogramPoint.Update(0); @@ -228,11 +228,15 @@ internal static void AssertExponentialBucketsAreCorrect(Base2ExponentialBucketHi } [Theory] - [InlineData(AggregationType.Base2ExponentialHistogram, AggregationTemporality.Cumulative)] - [InlineData(AggregationType.Base2ExponentialHistogram, AggregationTemporality.Delta)] - [InlineData(AggregationType.Base2ExponentialHistogramWithMinMax, AggregationTemporality.Cumulative)] - [InlineData(AggregationType.Base2ExponentialHistogramWithMinMax, AggregationTemporality.Delta)] - internal void ExponentialHistogramTests(AggregationType aggregationType, AggregationTemporality aggregationTemporality) + [InlineData(AggregationType.Base2ExponentialHistogram, AggregationTemporality.Cumulative, true)] + [InlineData(AggregationType.Base2ExponentialHistogram, AggregationTemporality.Delta, true)] + [InlineData(AggregationType.Base2ExponentialHistogramWithMinMax, AggregationTemporality.Cumulative, true)] + [InlineData(AggregationType.Base2ExponentialHistogramWithMinMax, AggregationTemporality.Delta, true)] + [InlineData(AggregationType.Base2ExponentialHistogram, AggregationTemporality.Cumulative, false)] + [InlineData(AggregationType.Base2ExponentialHistogram, AggregationTemporality.Delta, false)] + [InlineData(AggregationType.Base2ExponentialHistogramWithMinMax, AggregationTemporality.Cumulative, false)] + [InlineData(AggregationType.Base2ExponentialHistogramWithMinMax, AggregationTemporality.Delta, false)] + internal void ExponentialHistogramTests(AggregationType aggregationType, AggregationTemporality aggregationTemporality, bool exemplarsEnabled) { var valuesToRecord = new[] { -10, 0, 1, 9, 10, 11, 19 }; @@ -243,24 +247,28 @@ internal void ExponentialHistogramTests(AggregationType aggregationType, Aggrega metricStreamIdentity, aggregationType, aggregationTemporality, - maxMetricPoints: 1024); - - var metricPoint = new MetricPoint( - aggregatorStore, - aggregationType, // TODO: Why is this here? AggregationType is already declared when AggregatorStore was instantiated. - tagKeysAndValues: null, - Metric.DefaultHistogramBounds, - Metric.DefaultExponentialHistogramMaxBuckets); + maxMetricPoints: 1024, + exemplarsEnabled ? new AlwaysOnExemplarFilter() : null); var expectedHistogram = new Base2ExponentialBucketHistogram(); foreach (var value in valuesToRecord) { - metricPoint.Update(value); + aggregatorStore.Update(value, Array.Empty>()); expectedHistogram.Record(value); } - metricPoint.TakeSnapshot(aggregationTemporality == AggregationTemporality.Delta); // TODO: Why outputDelta param? The aggregation temporality was declared when instantiateing the AggregatorStore. + aggregatorStore.Snapshot(); + + var metricPoints = new List(); + + foreach (ref readonly var mp in aggregatorStore.GetMetricPoints()) + { + metricPoints.Add(mp); + } + + Assert.Single(metricPoints); + var metricPoint = metricPoints[0]; var count = metricPoint.GetHistogramCount(); var sum = metricPoint.GetHistogramSum(); @@ -323,5 +331,46 @@ internal void ExponentialHistogramTests(AggregationType aggregationType, Aggrega } } } + + [Theory] + [InlineData(-5)] + [InlineData(0)] + [InlineData(5)] + [InlineData(null)] + internal void ExponentialMaxScaleConfigWorks(int? maxScale) + { + var streamConfiguration = new Base2ExponentialBucketHistogramConfiguration(); + if (maxScale.HasValue) + { + streamConfiguration.MaxScale = maxScale.Value; + } + + var metricStreamIdentity = new MetricStreamIdentity(Instrument, streamConfiguration); + + var aggregatorStore = new AggregatorStore( + metricStreamIdentity, + AggregationType.Base2ExponentialHistogram, + AggregationTemporality.Cumulative, + maxMetricPoints: 1024); + + aggregatorStore.Update(10, Array.Empty>()); + + aggregatorStore.Snapshot(); + + var metricPoints = new List(); + + foreach (ref readonly var mp in aggregatorStore.GetMetricPoints()) + { + metricPoints.Add(mp); + } + + Assert.Single(metricPoints); + var metricPoint = metricPoints[0]; + + // After a single measurement there will not have been a scale down. + // Scale will equal MaxScale. + var expectedScale = maxScale.HasValue ? maxScale : Metric.DefaultExponentialHistogramMaxScale; + Assert.Equal(expectedScale, metricPoint.GetExponentialHistogramData().Scale); + } } } diff --git a/test/OpenTelemetry.Tests/Metrics/MetricViewTests.cs b/test/OpenTelemetry.Tests/Metrics/MetricViewTests.cs index 82ef72b1246..579922f5965 100644 --- a/test/OpenTelemetry.Tests/Metrics/MetricViewTests.cs +++ b/test/OpenTelemetry.Tests/Metrics/MetricViewTests.cs @@ -208,7 +208,7 @@ public void AddViewWithInvalidHistogramBoundsThrowsArgumentException(double[] bo [InlineData(-1)] [InlineData(0)] [InlineData(1)] - public void AddViewWithInvalidExponentialHistogramConfigThrowsArgumentException(int maxSize) + public void AddViewWithInvalidExponentialHistogramMaxSizeConfigThrowsArgumentException(int maxSize) { var ex = Assert.Throws(() => Sdk.CreateMeterProviderBuilder() .AddView("name1", new Base2ExponentialBucketHistogramConfiguration { MaxSize = maxSize })); @@ -216,6 +216,17 @@ public void AddViewWithInvalidExponentialHistogramConfigThrowsArgumentException( Assert.Contains("Histogram max size is invalid", ex.Message); } + [Theory] + [InlineData(-12)] + [InlineData(21)] + public void AddViewWithInvalidExponentialHistogramMaxScaleConfigThrowsArgumentException(int maxScale) + { + var ex = Assert.Throws(() => Sdk.CreateMeterProviderBuilder() + .AddView("name1", new Base2ExponentialBucketHistogramConfiguration { MaxScale = maxScale })); + + Assert.Contains("Histogram max scale is invalid", ex.Message); + } + [Theory] [MemberData(nameof(MetricTestData.InvalidHistogramBoundaries), MemberType = typeof(MetricTestData))] public void AddViewWithInvalidHistogramBoundsIgnored(double[] boundaries)