diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs index e85cdcda231..45b11f5d543 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs @@ -246,8 +246,8 @@ internal static OtlpMetrics.Metric ToOtlpMetric(this Metric metric) }; AddAttributes(metricPoint.Tags, dataPoint.Attributes); - dataPoint.Count = (ulong)metricPoint.LongValue; - dataPoint.Sum = metricPoint.DoubleValue; + dataPoint.Count = (ulong)metricPoint.GetHistogramCount(); + dataPoint.Sum = metricPoint.GetHistogramSum(); foreach (var histogramMeasurement in metricPoint.GetHistogramBuckets()) { diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs index f19925b3a5b..34f55b7a3b6 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs @@ -14,18 +14,15 @@ // limitations under the License. // -using System; using System.Collections.Generic; using System.Diagnostics.Metrics; using System.Linq; -using System.Threading; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; using OpenTelemetry.Metrics; using OpenTelemetry.Resources; using OpenTelemetry.Tests; using OpenTelemetry.Trace; using Xunit; -using GrpcCore = Grpc.Core; using OtlpCollector = Opentelemetry.Proto.Collector.Metrics.V1; using OtlpMetrics = Opentelemetry.Proto.Metrics.V1; @@ -49,99 +46,332 @@ public void ToOtlpResourceMetricsTest(bool includeServiceNameInResource) }); } - var tags = new KeyValuePair[] - { - new KeyValuePair("key1", "value1"), - new KeyValuePair("key2", "value2"), - }; + var metrics = new List(); using var meter = new Meter($"{Utils.GetCurrentMethodName()}.{includeServiceNameInResource}", "0.0.1"); - - var exportedItems = new List(); - using var provider = Sdk.CreateMeterProviderBuilder() .SetResourceBuilder(resourceBuilder) .AddMeter(meter.Name) - .AddReader(new BaseExportingMetricReader(new InMemoryExporter(exportedItems)) - { - Temporality = AggregationTemporality.Delta, - }) + .AddInMemoryExporter(metrics) .Build(); var counter = meter.CreateCounter("counter"); + counter.Add(100); + + provider.ForceFlush(); + + var batch = new Batch(metrics.ToArray(), metrics.Count); + + var request = new OtlpCollector.ExportMetricsServiceRequest(); + request.AddMetrics(resourceBuilder.Build().ToOtlpResource(), batch); + + Assert.Single(request.ResourceMetrics); + var resourceMetric = request.ResourceMetrics.First(); + var oltpResource = resourceMetric.Resource; + + if (includeServiceNameInResource) + { + Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceName && kvp.Value.StringValue == "service-name"); + Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceNamespace && kvp.Value.StringValue == "ns1"); + } + else + { + Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceName && kvp.Value.ToString().Contains("unknown_service:")); + } - counter.Add(100, tags); + Assert.Single(resourceMetric.InstrumentationLibraryMetrics); + var instrumentationLibraryMetrics = resourceMetric.InstrumentationLibraryMetrics.First(); + Assert.Equal(string.Empty, instrumentationLibraryMetrics.SchemaUrl); + Assert.Equal(meter.Name, instrumentationLibraryMetrics.InstrumentationLibrary.Name); + Assert.Equal("0.0.1", instrumentationLibraryMetrics.InstrumentationLibrary.Version); + } + + [Theory] + [InlineData("test_gauge", null, null, 123, null)] + [InlineData("test_gauge", null, null, null, 123.45)] + [InlineData("test_gauge", "description", "unit", 123, null)] + public void TestGaugeToOtlpMetric(string name, string description, string unit, long? longValue, double? doubleValue) + { + var metrics = new List(); - var testCompleted = false; + using var meter = new Meter(Utils.GetCurrentMethodName()); + using var provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddInMemoryExporter(metrics) + .Build(); + + if (longValue.HasValue) + { + meter.CreateObservableGauge(name, () => longValue.Value, unit, description); + } + else + { + meter.CreateObservableGauge(name, () => doubleValue.Value, unit, description); + } provider.ForceFlush(); - var batch = new Batch(exportedItems.ToArray(), exportedItems.Count); - RunTest(batch); + 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 instrumentationLibraryMetrics = resourceMetric.InstrumentationLibraryMetrics.Single(); + var actual = instrumentationLibraryMetrics.Metrics.Single(); + + Assert.Equal(name, actual.Name); + Assert.Equal(description ?? string.Empty, actual.Description); + Assert.Equal(unit ?? string.Empty, actual.Unit); - Assert.True(testCompleted); + Assert.Equal(OtlpMetrics.Metric.DataOneofCase.Gauge, actual.DataCase); - void RunTest(Batch metrics) + Assert.NotNull(actual.Gauge); + Assert.Null(actual.Sum); + Assert.Null(actual.Histogram); + Assert.Null(actual.ExponentialHistogram); + Assert.Null(actual.Summary); + + Assert.Single(actual.Gauge.DataPoints); + var dataPoint = actual.Gauge.DataPoints.First(); + Assert.True(dataPoint.StartTimeUnixNano > 0); + Assert.True(dataPoint.TimeUnixNano > 0); + + if (longValue.HasValue) + { + Assert.Equal(OtlpMetrics.NumberDataPoint.ValueOneofCase.AsInt, dataPoint.ValueCase); + Assert.Equal(longValue, dataPoint.AsInt); + } + else { - var request = new OtlpCollector.ExportMetricsServiceRequest(); - request.AddMetrics(resourceBuilder.Build().ToOtlpResource(), metrics); + Assert.Equal(OtlpMetrics.NumberDataPoint.ValueOneofCase.AsDouble, dataPoint.ValueCase); + Assert.Equal(doubleValue, dataPoint.AsDouble); + } - Assert.Single(request.ResourceMetrics); - var resourceMetric = request.ResourceMetrics.First(); - var oltpResource = resourceMetric.Resource; + Assert.Empty(dataPoint.Attributes); - if (includeServiceNameInResource) - { - Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceName && kvp.Value.StringValue == "service-name"); - Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceNamespace && kvp.Value.StringValue == "ns1"); - } - else - { - Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceName && kvp.Value.ToString().Contains("unknown_service:")); - } + Assert.Empty(dataPoint.Exemplars); - Assert.Single(resourceMetric.InstrumentationLibraryMetrics); - var instrumentationLibraryMetrics = resourceMetric.InstrumentationLibraryMetrics.First(); - Assert.Equal(string.Empty, instrumentationLibraryMetrics.SchemaUrl); - Assert.Equal(meter.Name, instrumentationLibraryMetrics.InstrumentationLibrary.Name); - Assert.Equal("0.0.1", instrumentationLibraryMetrics.InstrumentationLibrary.Version); +#pragma warning disable CS0612 // Type or member is obsolete + Assert.Null(actual.IntGauge); + Assert.Null(actual.IntSum); + Assert.Null(actual.IntHistogram); + Assert.Empty(dataPoint.Labels); +#pragma warning restore CS0612 // Type or member is obsolete + } - Assert.Single(instrumentationLibraryMetrics.Metrics); + [Theory] + [InlineData("test_counter", null, null, 123, null, AggregationTemporality.Cumulative, true)] + [InlineData("test_counter", null, null, null, 123.45, AggregationTemporality.Cumulative, true)] + [InlineData("test_counter", null, null, 123, null, AggregationTemporality.Delta, true)] + [InlineData("test_counter", "description", "unit", 123, null, AggregationTemporality.Cumulative, true)] + [InlineData("test_counter", null, null, 123, null, AggregationTemporality.Delta, true, "key1", "value1", "key2", 123)] + public void TestCounterToOltpMetric(string name, string description, string unit, long? longValue, double? doubleValue, AggregationTemporality aggregationTemporality, bool isMonotonic, params object[] keysValues) + { + var metrics = new List(); - foreach (var metric in instrumentationLibraryMetrics.Metrics) - { - Assert.Equal(string.Empty, metric.Description); - Assert.Equal(string.Empty, metric.Unit); - Assert.Equal("counter", metric.Name); + using var meter = new Meter(Utils.GetCurrentMethodName()); + using var provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddReader( + new BaseExportingMetricReader(new InMemoryExporter(metrics)) + { + Temporality = aggregationTemporality, + }) + .Build(); + + var attributes = ToAttributes(keysValues).ToArray(); + if (longValue.HasValue) + { + var counter = meter.CreateCounter(name, unit, description); + counter.Add(longValue.Value, attributes); + } + else + { + var counter = meter.CreateCounter(name, unit, description); + counter.Add(doubleValue.Value, 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 instrumentationLibraryMetrics = resourceMetric.InstrumentationLibraryMetrics.Single(); + var actual = instrumentationLibraryMetrics.Metrics.Single(); - Assert.Equal(OtlpMetrics.Metric.DataOneofCase.Sum, metric.DataCase); - Assert.True(metric.Sum.IsMonotonic); - Assert.Equal(OtlpMetrics.AggregationTemporality.Delta, metric.Sum.AggregationTemporality); + Assert.Equal(name, actual.Name); + Assert.Equal(description ?? string.Empty, actual.Description); + Assert.Equal(unit ?? string.Empty, actual.Unit); - Assert.Single(metric.Sum.DataPoints); - var dataPoint = metric.Sum.DataPoints.First(); - Assert.True(dataPoint.StartTimeUnixNano > 0); - Assert.True(dataPoint.TimeUnixNano > 0); - Assert.Equal(OtlpMetrics.NumberDataPoint.ValueOneofCase.AsInt, dataPoint.ValueCase); - Assert.Equal(100, dataPoint.AsInt); + Assert.Equal(OtlpMetrics.Metric.DataOneofCase.Sum, actual.DataCase); + + Assert.Null(actual.Gauge); + Assert.NotNull(actual.Sum); + Assert.Null(actual.Histogram); + Assert.Null(actual.ExponentialHistogram); + Assert.Null(actual.Summary); + + Assert.Equal(isMonotonic, actual.Sum.IsMonotonic); + + var otlpAggregationTemporality = aggregationTemporality == AggregationTemporality.Cumulative + ? OtlpMetrics.AggregationTemporality.Cumulative + : OtlpMetrics.AggregationTemporality.Delta; + Assert.Equal(otlpAggregationTemporality, actual.Sum.AggregationTemporality); + + Assert.Single(actual.Sum.DataPoints); + var dataPoint = actual.Sum.DataPoints.First(); + Assert.True(dataPoint.StartTimeUnixNano > 0); + Assert.True(dataPoint.TimeUnixNano > 0); + + if (longValue.HasValue) + { + Assert.Equal(OtlpMetrics.NumberDataPoint.ValueOneofCase.AsInt, dataPoint.ValueCase); + Assert.Equal(longValue, dataPoint.AsInt); + } + else + { + Assert.Equal(OtlpMetrics.NumberDataPoint.ValueOneofCase.AsDouble, dataPoint.ValueCase); + Assert.Equal(doubleValue, dataPoint.AsDouble); + } + + if (attributes.Length > 0) + { + OtlpTestHelpers.AssertOtlpAttributes(attributes, dataPoint.Attributes); + } + else + { + Assert.Empty(dataPoint.Attributes); + } + + Assert.Empty(dataPoint.Exemplars); #pragma warning disable CS0612 // Type or member is obsolete - Assert.Empty(dataPoint.Labels); + Assert.Null(actual.IntGauge); + Assert.Null(actual.IntSum); + Assert.Null(actual.IntHistogram); + Assert.Empty(dataPoint.Labels); #pragma warning restore CS0612 // Type or member is obsolete - OtlpTestHelpers.AssertOtlpAttributes(tags.ToList(), dataPoint.Attributes); + } + + [Theory] + [InlineData("test_histogram", null, null, 123, null, AggregationTemporality.Cumulative)] + [InlineData("test_histogram", null, null, null, 123.45, AggregationTemporality.Cumulative)] + [InlineData("test_histogram", null, null, 123, null, AggregationTemporality.Delta)] + [InlineData("test_histogram", "description", "unit", 123, null, AggregationTemporality.Cumulative)] + [InlineData("test_histogram", null, null, 123, null, AggregationTemporality.Delta, "key1", "value1", "key2", 123)] + public void TestHistogramToOltpMetric(string name, string description, string unit, long? longValue, double? doubleValue, AggregationTemporality aggregationTemporality, params object[] keysValues) + { + var metrics = new List(); + + var metricReader = new BaseExportingMetricReader(new InMemoryExporter(metrics)); + metricReader.Temporality = aggregationTemporality; + + using var meter = new Meter(Utils.GetCurrentMethodName()); + using var provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddReader(metricReader) + .Build(); + + var attributes = ToAttributes(keysValues).ToArray(); + if (longValue.HasValue) + { + var histogram = meter.CreateHistogram(name, unit, description); + histogram.Record(longValue.Value, attributes); + } + else + { + var histogram = meter.CreateHistogram(name, unit, description); + histogram.Record(doubleValue.Value, attributes); + } - Assert.Empty(dataPoint.Exemplars); + 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 instrumentationLibraryMetrics = resourceMetric.InstrumentationLibraryMetrics.Single(); + var actual = instrumentationLibraryMetrics.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.Histogram, actual.DataCase); + + Assert.Null(actual.Gauge); + Assert.Null(actual.Sum); + Assert.NotNull(actual.Histogram); + Assert.Null(actual.ExponentialHistogram); + Assert.Null(actual.Summary); + + var otlpAggregationTemporality = aggregationTemporality == AggregationTemporality.Cumulative + ? OtlpMetrics.AggregationTemporality.Cumulative + : OtlpMetrics.AggregationTemporality.Delta; + Assert.Equal(otlpAggregationTemporality, actual.Histogram.AggregationTemporality); + + Assert.Single(actual.Histogram.DataPoints); + var dataPoint = actual.Histogram.DataPoints.First(); + Assert.True(dataPoint.StartTimeUnixNano > 0); + Assert.True(dataPoint.TimeUnixNano > 0); + + Assert.Equal(1UL, dataPoint.Count); + + if (longValue.HasValue) + { + Assert.Equal((double)longValue, dataPoint.Sum); + } + else + { + Assert.Equal(doubleValue, dataPoint.Sum); + } + + int bucketIndex; + for (bucketIndex = 0; bucketIndex < dataPoint.ExplicitBounds.Count; ++bucketIndex) + { + if (dataPoint.Sum <= dataPoint.ExplicitBounds[bucketIndex]) + { + break; } - testCompleted = true; + Assert.Equal(0UL, dataPoint.BucketCounts[bucketIndex]); + } + + Assert.Equal(1UL, dataPoint.BucketCounts[bucketIndex]); + + if (attributes.Length > 0) + { + OtlpTestHelpers.AssertOtlpAttributes(attributes, dataPoint.Attributes); } + else + { + Assert.Empty(dataPoint.Attributes); + } + + Assert.Empty(dataPoint.Exemplars); + +#pragma warning disable CS0612 // Type or member is obsolete + Assert.Null(actual.IntGauge); + Assert.Null(actual.IntSum); + Assert.Null(actual.IntHistogram); + Assert.Empty(dataPoint.Labels); +#pragma warning restore CS0612 // Type or member is obsolete } - private class NoopMetricsServiceClient : OtlpCollector.MetricsService.IMetricsServiceClient + private static IEnumerable> ToAttributes(object[] keysValues) { - public OtlpCollector.ExportMetricsServiceResponse Export(OtlpCollector.ExportMetricsServiceRequest request, GrpcCore.Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default) + var keys = keysValues?.Where((_, index) => index % 2 == 0).ToArray(); + var values = keysValues?.Where((_, index) => index % 2 != 0).ToArray(); + + for (var i = 0; keys != null && i < keys.Length; ++i) { - return null; + yield return new KeyValuePair(keys[i].ToString(), values[i]); } } }