diff --git a/src/OpenTelemetry.Exporter.InMemory/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.InMemory/.publicApi/net462/PublicAPI.Unshipped.txt index ca21920895c..17941b6fcf3 100644 --- a/src/OpenTelemetry.Exporter.InMemory/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.InMemory/.publicApi/net462/PublicAPI.Unshipped.txt @@ -1,3 +1,14 @@ OpenTelemetry.Metrics.InMemoryExporterMetricsExtensions +OpenTelemetry.Metrics.MetricSnapshot +OpenTelemetry.Metrics.MetricSnapshot.Description.get -> string +OpenTelemetry.Metrics.MetricSnapshot.MeterName.get -> string +OpenTelemetry.Metrics.MetricSnapshot.MeterVersion.get -> string +OpenTelemetry.Metrics.MetricSnapshot.MetricPoints.get -> System.Collections.Generic.IReadOnlyList +OpenTelemetry.Metrics.MetricSnapshot.MetricSnapshot(OpenTelemetry.Metrics.Metric metric) -> void +OpenTelemetry.Metrics.MetricSnapshot.MetricType.get -> OpenTelemetry.Metrics.MetricType +OpenTelemetry.Metrics.MetricSnapshot.Name.get -> string +OpenTelemetry.Metrics.MetricSnapshot.Unit.get -> string static OpenTelemetry.Metrics.InMemoryExporterMetricsExtensions.AddInMemoryExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Collections.Generic.ICollection exportedItems) -> OpenTelemetry.Metrics.MeterProviderBuilder -static OpenTelemetry.Metrics.InMemoryExporterMetricsExtensions.AddInMemoryExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Collections.Generic.ICollection exportedItems, System.Action configureMetricReader) -> OpenTelemetry.Metrics.MeterProviderBuilder \ No newline at end of file +static OpenTelemetry.Metrics.InMemoryExporterMetricsExtensions.AddInMemoryExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Collections.Generic.ICollection exportedItems, System.Action configureMetricReader) -> OpenTelemetry.Metrics.MeterProviderBuilder +static OpenTelemetry.Metrics.InMemoryExporterMetricsExtensions.AddInMemoryExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Collections.Generic.ICollection exportedItems) -> OpenTelemetry.Metrics.MeterProviderBuilder +static OpenTelemetry.Metrics.InMemoryExporterMetricsExtensions.AddInMemoryExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Collections.Generic.ICollection exportedItems, System.Action configureMetricReader) -> OpenTelemetry.Metrics.MeterProviderBuilder diff --git a/src/OpenTelemetry.Exporter.InMemory/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.InMemory/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index ca21920895c..17941b6fcf3 100644 --- a/src/OpenTelemetry.Exporter.InMemory/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.InMemory/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,3 +1,14 @@ OpenTelemetry.Metrics.InMemoryExporterMetricsExtensions +OpenTelemetry.Metrics.MetricSnapshot +OpenTelemetry.Metrics.MetricSnapshot.Description.get -> string +OpenTelemetry.Metrics.MetricSnapshot.MeterName.get -> string +OpenTelemetry.Metrics.MetricSnapshot.MeterVersion.get -> string +OpenTelemetry.Metrics.MetricSnapshot.MetricPoints.get -> System.Collections.Generic.IReadOnlyList +OpenTelemetry.Metrics.MetricSnapshot.MetricSnapshot(OpenTelemetry.Metrics.Metric metric) -> void +OpenTelemetry.Metrics.MetricSnapshot.MetricType.get -> OpenTelemetry.Metrics.MetricType +OpenTelemetry.Metrics.MetricSnapshot.Name.get -> string +OpenTelemetry.Metrics.MetricSnapshot.Unit.get -> string static OpenTelemetry.Metrics.InMemoryExporterMetricsExtensions.AddInMemoryExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Collections.Generic.ICollection exportedItems) -> OpenTelemetry.Metrics.MeterProviderBuilder -static OpenTelemetry.Metrics.InMemoryExporterMetricsExtensions.AddInMemoryExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Collections.Generic.ICollection exportedItems, System.Action configureMetricReader) -> OpenTelemetry.Metrics.MeterProviderBuilder \ No newline at end of file +static OpenTelemetry.Metrics.InMemoryExporterMetricsExtensions.AddInMemoryExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Collections.Generic.ICollection exportedItems, System.Action configureMetricReader) -> OpenTelemetry.Metrics.MeterProviderBuilder +static OpenTelemetry.Metrics.InMemoryExporterMetricsExtensions.AddInMemoryExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Collections.Generic.ICollection exportedItems) -> OpenTelemetry.Metrics.MeterProviderBuilder +static OpenTelemetry.Metrics.InMemoryExporterMetricsExtensions.AddInMemoryExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Collections.Generic.ICollection exportedItems, System.Action configureMetricReader) -> OpenTelemetry.Metrics.MeterProviderBuilder diff --git a/src/OpenTelemetry.Exporter.InMemory/CHANGELOG.md b/src/OpenTelemetry.Exporter.InMemory/CHANGELOG.md index 4b7ad94f5c3..e00489807a5 100644 --- a/src/OpenTelemetry.Exporter.InMemory/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.InMemory/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +* Adds new `AddInMemoryExporter` extension method to export `Metric` as new + type `MetricSnapshot`. + ([#2361](https://github.com/open-telemetry/opentelemetry-dotnet/issues/2361)) + ## 1.3.0-beta.2 Released 2022-May-16 diff --git a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporter.cs b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporter.cs index d76995f8fb6..8dab7b51d96 100644 --- a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporter.cs +++ b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporter.cs @@ -14,6 +14,7 @@ // limitations under the License. // +using System; using System.Collections.Generic; namespace OpenTelemetry.Exporter @@ -22,13 +23,22 @@ public class InMemoryExporter : BaseExporter where T : class { private readonly ICollection exportedItems; + private readonly Func, ExportResult> onExport; public InMemoryExporter(ICollection exportedItems) { this.exportedItems = exportedItems; + this.onExport = (Batch batch) => this.DefaultExport(batch); } - public override ExportResult Export(in Batch batch) + internal InMemoryExporter(Func, ExportResult> exportFunc) + { + this.onExport = exportFunc; + } + + public override ExportResult Export(in Batch batch) => this.onExport(batch); + + private ExportResult DefaultExport(in Batch batch) { if (this.exportedItems == null) { diff --git a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricsExtensions.cs b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricsExtensions.cs index 9809b09552b..f817f5744fa 100644 --- a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricsExtensions.cs +++ b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricsExtensions.cs @@ -33,10 +33,28 @@ public static class InMemoryExporterMetricsExtensions /// /// Adds InMemory metric exporter to the using default options. /// + /// + /// Be aware that may continue to be updated after export. + /// /// builder to use. - /// Collection which will be populated with the exported MetricItem. + /// Collection which will be populated with the exported . /// The instance of to chain the calls. public static MeterProviderBuilder AddInMemoryExporter(this MeterProviderBuilder builder, ICollection exportedItems) + { + return builder.AddInMemoryExporter(exportedItems: exportedItems, configureMetricReader: null); + } + + /// + /// Adds InMemory metric exporter to the . + /// + /// + /// Be aware that may continue to be updated after export. + /// + /// builder to use. + /// Collection which will be populated with the exported . + /// configuration options. + /// The instance of to chain the calls. + public static MeterProviderBuilder AddInMemoryExporter(this MeterProviderBuilder builder, ICollection exportedItems, Action configureMetricReader) { Guard.ThrowIfNull(builder); Guard.ThrowIfNull(exportedItems); @@ -45,21 +63,45 @@ public static MeterProviderBuilder AddInMemoryExporter(this MeterProviderBuilder { return deferredMeterProviderBuilder.Configure((sp, builder) => { - AddInMemoryExporter(builder, exportedItems, sp.GetOptions(), null); + AddInMemoryExporter(builder, exportedItems, sp.GetOptions(), configureMetricReader); }); } - return AddInMemoryExporter(builder, exportedItems, new MetricReaderOptions(), null); + return AddInMemoryExporter(builder, exportedItems, new MetricReaderOptions(), configureMetricReader); } /// /// Adds InMemory metric exporter to the using default options. + /// The exporter will be setup to export . /// + /// + /// Use this if you need a copy of that will not be updated after export. + /// /// builder to use. - /// Collection which will be populated with the exported MetricItem. + /// Collection which will be populated with the exported represented as . + /// The instance of to chain the calls. + public static MeterProviderBuilder AddInMemoryExporter( + this MeterProviderBuilder builder, + ICollection exportedItems) + { + return builder.AddInMemoryExporter(exportedItems: exportedItems, configureMetricReader: null); + } + + /// + /// Adds InMemory metric exporter to the . + /// The exporter will be setup to export . + /// + /// + /// Use this if you need a copy of that will not be updated after export. + /// + /// builder to use. + /// Collection which will be populated with the exported represented as . /// configuration options. /// The instance of to chain the calls. - public static MeterProviderBuilder AddInMemoryExporter(this MeterProviderBuilder builder, ICollection exportedItems, Action configureMetricReader) + public static MeterProviderBuilder AddInMemoryExporter( + this MeterProviderBuilder builder, + ICollection exportedItems, + Action configureMetricReader) { Guard.ThrowIfNull(builder); Guard.ThrowIfNull(exportedItems); @@ -93,5 +135,40 @@ private static MeterProviderBuilder AddInMemoryExporter( return builder.AddReader(metricReader); } + + private static MeterProviderBuilder AddInMemoryExporter( + MeterProviderBuilder builder, + ICollection exportedItems, + MetricReaderOptions metricReaderOptions, + Action configureMetricReader) + { + configureMetricReader?.Invoke(metricReaderOptions); + + var metricExporter = new InMemoryExporter( + exportFunc: metricBatch => ExportMetricSnapshot(metricBatch, exportedItems)); + + var metricReader = PeriodicExportingMetricReaderHelper.CreatePeriodicExportingMetricReader( + metricExporter, + metricReaderOptions, + DefaultExportIntervalMilliseconds, + DefaultExportTimeoutMilliseconds); + + return builder.AddReader(metricReader); + } + + private static ExportResult ExportMetricSnapshot(in Batch batch, ICollection exportedItems) + { + if (exportedItems == null) + { + return ExportResult.Failure; + } + + foreach (var metric in batch) + { + exportedItems.Add(new MetricSnapshot(metric)); + } + + return ExportResult.Success; + } } } diff --git a/src/OpenTelemetry.Exporter.InMemory/MetricSnapshot.cs b/src/OpenTelemetry.Exporter.InMemory/MetricSnapshot.cs new file mode 100644 index 00000000000..635e3549310 --- /dev/null +++ b/src/OpenTelemetry.Exporter.InMemory/MetricSnapshot.cs @@ -0,0 +1,58 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Collections.Generic; + +namespace OpenTelemetry.Metrics +{ + /// + /// This class represents a selective copy of . + /// This contains the minimum fields and properties needed for most + /// unit testing scenarios. + /// + public class MetricSnapshot + { + private readonly MetricStreamIdentity instrumentIdentity; + + public MetricSnapshot(Metric metric) + { + this.instrumentIdentity = metric.InstrumentIdentity; + this.MetricType = metric.MetricType; + + List metricPoints = new(); + foreach (ref readonly var metricPoint in metric.GetMetricPoints()) + { + metricPoints.Add(metricPoint.Copy()); + } + + this.MetricPoints = metricPoints; + } + + public string Name => this.instrumentIdentity.InstrumentName; + + public string Description => this.instrumentIdentity.Description; + + public string Unit => this.instrumentIdentity.Unit; + + public string MeterName => this.instrumentIdentity.MeterName; + + public MetricType MetricType { get; } + + public string MeterVersion => this.instrumentIdentity.MeterVersion; + + public IReadOnlyList MetricPoints { get; } + } +} diff --git a/src/OpenTelemetry.Exporter.InMemory/OpenTelemetry.Exporter.InMemory.csproj b/src/OpenTelemetry.Exporter.InMemory/OpenTelemetry.Exporter.InMemory.csproj index bdb3c634cd8..546d54e153f 100644 --- a/src/OpenTelemetry.Exporter.InMemory/OpenTelemetry.Exporter.InMemory.csproj +++ b/src/OpenTelemetry.Exporter.InMemory/OpenTelemetry.Exporter.InMemory.csproj @@ -27,8 +27,6 @@ - - diff --git a/src/OpenTelemetry/AssemblyInfo.cs b/src/OpenTelemetry/AssemblyInfo.cs index 5b17a4357a6..545b35a13e8 100644 --- a/src/OpenTelemetry/AssemblyInfo.cs +++ b/src/OpenTelemetry/AssemblyInfo.cs @@ -17,6 +17,7 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("OpenTelemetry.Tests" + AssemblyInfo.PublicKey)] +[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.InMemory" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.Tests" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Hosting.Tests" + AssemblyInfo.PublicKey)] diff --git a/test/OpenTelemetry.Tests/Metrics/InMemoryExporterTests.cs b/test/OpenTelemetry.Tests/Metrics/InMemoryExporterTests.cs index c5337ef49a6..841d4f293ad 100644 --- a/test/OpenTelemetry.Tests/Metrics/InMemoryExporterTests.cs +++ b/test/OpenTelemetry.Tests/Metrics/InMemoryExporterTests.cs @@ -16,17 +16,19 @@ using System.Collections.Generic; using System.Diagnostics.Metrics; + using OpenTelemetry.Tests; + using Xunit; namespace OpenTelemetry.Metrics.Tests { public class InMemoryExporterTests { - [Fact(Skip = "To be run after https://github.com/open-telemetry/opentelemetry-dotnet/issues/2361 is fixed")] + [Fact] public void InMemoryExporterShouldDeepCopyMetricPoints() { - var exportedItems = new List(); + var exportedItems = new List(); using var meter = new Meter(Utils.GetCurrentMethodName()); using var meterProvider = Sdk.CreateMeterProviderBuilder() @@ -39,30 +41,29 @@ public void InMemoryExporterShouldDeepCopyMetricPoints() var counter = meter.CreateCounter("meter"); - // Emit 10 for the MetricPoint with a single key-vaue pair: ("tag1", "value1") + // TEST 1: Emit 10 for the MetricPoint with a single key-vaue pair: ("tag1", "value1") counter.Add(10, new KeyValuePair("tag1", "value1")); meterProvider.ForceFlush(); - var metric = exportedItems[0]; // Only one Metric object is added to the collection at this point - var metricPointsEnumerator = metric.GetMetricPoints().GetEnumerator(); - Assert.True(metricPointsEnumerator.MoveNext()); // One MetricPoint is emitted for the Metric - ref readonly var metricPointForFirstExport = ref metricPointsEnumerator.Current; - Assert.Equal(10, metricPointForFirstExport.GetSumLong()); + Assert.Single(exportedItems); + var metric1 = exportedItems[0]; // Only one Metric object is added to the collection at this point + Assert.Single(metric1.MetricPoints); + Assert.Equal(10, metric1.MetricPoints[0].GetSumLong()); - // Emit 25 for the MetricPoint with a single key-vaue pair: ("tag1", "value1") + // TEST 2: Emit 25 for the MetricPoint with a single key-vaue pair: ("tag1", "value1") counter.Add(25, new KeyValuePair("tag1", "value1")); meterProvider.ForceFlush(); - metric = exportedItems[1]; // Second Metric object is added to the collection at this point - metricPointsEnumerator = metric.GetMetricPoints().GetEnumerator(); - Assert.True(metricPointsEnumerator.MoveNext()); // One MetricPoint is emitted for the Metric - var metricPointForSecondExport = metricPointsEnumerator.Current; - Assert.Equal(25, metricPointForSecondExport.GetSumLong()); + Assert.Equal(2, exportedItems.Count); + var metric2 = exportedItems[1]; // Second Metric object is added to the collection at this point + Assert.Single(metric2.MetricPoints); + Assert.Equal(25, metric2.MetricPoints[0].GetSumLong()); - // MetricPoint.LongValue for the first exporter metric should still be 10 - Assert.Equal(10, metricPointForFirstExport.GetSumLong()); + // TEST 3: Verify first exported metric is unchanged + // MetricPoint.LongValue for the first exported metric should still be 10 + Assert.Equal(10, metric1.MetricPoints[0].GetSumLong()); } } } diff --git a/test/OpenTelemetry.Tests/Trace/BatchExportActivityProcessorTest.cs b/test/OpenTelemetry.Tests/Trace/BatchExportActivityProcessorTest.cs index 33c6579d11c..67e24fa18fc 100644 --- a/test/OpenTelemetry.Tests/Trace/BatchExportActivityProcessorTest.cs +++ b/test/OpenTelemetry.Tests/Trace/BatchExportActivityProcessorTest.cs @@ -190,9 +190,8 @@ public void CheckExportForRecordingButNotSampledActivity() [Fact] public void CheckExportDrainsBatchOnFailure() { - using var exporter = new InMemoryExporter(null); using var processor = new BatchActivityExportProcessor( - exporter, + exporter: new FailureExporter(), maxQueueSize: 3, maxExportBatchSize: 3); @@ -208,5 +207,11 @@ public void CheckExportDrainsBatchOnFailure() Assert.Equal(3, processor.ProcessedCount); // Verify batch was drained even though nothing was exported. } + + private class FailureExporter : BaseExporter + where T : class + { + public override ExportResult Export(in Batch batch) => ExportResult.Failure; + } } }