Skip to content

Commit

Permalink
OtlpMetricsExporter: Add test transforming metrics to OTLP metrics (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
alanwest authored Jul 29, 2021
1 parent 175154e commit 138035b
Show file tree
Hide file tree
Showing 6 changed files with 341 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,18 @@ internal static OtlpMetrics.Metric ToOtlpMetric(this IMetric metric)
var otlpMetric = new OtlpMetrics.Metric
{
Name = metric.Name,
Description = metric.Description,
Unit = metric.Unit,
};

if (metric.Description != null)
{
otlpMetric.Description = metric.Description;
}

if (metric.Unit != null)
{
otlpMetric.Unit = metric.Unit;
}

if (metric is ISumMetric sumMetric)
{
var sum = new OtlpMetrics.Sum
Expand All @@ -133,7 +141,7 @@ internal static OtlpMetrics.Metric ToOtlpMetric(this IMetric metric)
? OtlpMetrics.AggregationTemporality.Delta
: OtlpMetrics.AggregationTemporality.Cumulative,
};
var dataPoint = metric.ToNumberDataPoint(sumMetric.Sum, sumMetric.Exemplars);
var dataPoint = metric.ToNumberDataPoint(sumMetric.Sum.Value, sumMetric.Exemplars);
sum.DataPoints.Add(dataPoint);
otlpMetric.Sum = sum;
}
Expand Down Expand Up @@ -238,7 +246,7 @@ private static OtlpMetrics.NumberDataPoint ToNumberDataPoint(this IMetric metric
{
// TODO: Determine how we want to handle exceptions here.
// Do we want to just skip this metric and move on?
throw new ArgumentException();
throw new ArgumentException($"Value must be a long or a double.", nameof(value));
}

// TODO: Do TagEnumerationState thing.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// <copyright file="MetricsService.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>

using System;
using System.Threading;
using Grpc.Core;

namespace Opentelemetry.Proto.Collector.Metrics.V1
{
/// <summary>
/// MetricsService extensions.
/// </summary>
internal static partial class MetricsService
{
/// <summary>Interface for MetricService.</summary>
public interface IMetricsServiceClient
{
/// <summary>
/// For performance reasons, it is recommended to keep this RPC
/// alive for the entire life of the application.
/// </summary>
/// <param name="request">The request to send to the server.</param>
/// <param name="headers">The initial metadata to send with the call. This parameter is optional.</param>
/// <param name="deadline">An optional deadline for the call. The call will be cancelled if deadline is hit.</param>
/// <param name="cancellationToken">An optional token for canceling the call.</param>
/// <returns>The response received from the server.</returns>
ExportMetricsServiceResponse Export(ExportMetricsServiceRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default);
}

/// <summary>
/// MetricServiceClient extensions.
/// </summary>
public partial class MetricsServiceClient : IMetricsServiceClient
{
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public class OtlpMetricsExporter : BaseExporter<MetricItem>
#else
private readonly Channel channel;
#endif
private readonly OtlpCollector.MetricsService.MetricsServiceClient metricsClient;
private readonly OtlpCollector.MetricsService.IMetricsServiceClient metricsClient;
private readonly Metadata headers;

/// <summary>
Expand All @@ -59,8 +59,8 @@ public OtlpMetricsExporter(OtlpExporterOptions options)
/// Initializes a new instance of the <see cref="OtlpMetricsExporter"/> class.
/// </summary>
/// <param name="options">Configuration options for the exporter.</param>
/// <param name="metricsServiceClient"><see cref="OtlpCollector.MetricsService.MetricsServiceClient"/>.</param>
internal OtlpMetricsExporter(OtlpExporterOptions options, OtlpCollector.MetricsService.MetricsServiceClient metricsServiceClient = null)
/// <param name="metricsServiceClient"><see cref="OtlpCollector.MetricsService.IMetricsServiceClient"/>.</param>
internal OtlpMetricsExporter(OtlpExporterOptions options, OtlpCollector.MetricsService.IMetricsServiceClient metricsServiceClient = null)
{
this.options = options ?? throw new ArgumentNullException(nameof(options));
this.headers = GetMetadataFromHeaders(options.Headers);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// <copyright file="OtlpMetricsExporterTests.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>

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;

namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests
{
public class OtlpMetricsExporterTests
{
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ToOtlpResourceMetricsTest(bool addResource)
{
using var exporter = new OtlpMetricsExporter(
new OtlpExporterOptions(),
new NoopMetricsServiceClient());

if (addResource)
{
exporter.SetResource(
ResourceBuilder.CreateEmpty().AddAttributes(
new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>(ResourceSemanticConventions.AttributeServiceName, "service-name"),
new KeyValuePair<string, object>(ResourceSemanticConventions.AttributeServiceNamespace, "ns1"),
}).Build());
}
else
{
exporter.SetResource(Resource.Empty);
}

var tags = new KeyValuePair<string, object>[]
{
new KeyValuePair<string, object>("key1", "value1"),
new KeyValuePair<string, object>("key2", "value2"),
};

var processor = new PullMetricProcessor(new TestExporter<MetricItem>(RunTest), true);

using var provider = Sdk.CreateMeterProviderBuilder()
.AddSource("TestMeter")
.AddMetricProcessor(processor)
.Build();

using var meter = new Meter("TestMeter", "0.0.1");

var counter = meter.CreateCounter<int>("counter");

counter.Add(100, tags);

var testCompleted = false;

// Invokes the TestExporter which will invoke RunTest
processor.PullRequest();

Assert.True(testCompleted);

void RunTest(Batch<MetricItem> metricItem)
{
var request = new OtlpCollector.ExportMetricsServiceRequest();
request.AddBatch(exporter.ProcessResource, metricItem);

Assert.Single(request.ResourceMetrics);
var resourceMetric = request.ResourceMetrics.First();
var oltpResource = resourceMetric.Resource;

if (addResource)
{
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.Single(resourceMetric.InstrumentationLibraryMetrics);
var instrumentationLibraryMetrics = resourceMetric.InstrumentationLibraryMetrics.First();
Assert.Equal(string.Empty, instrumentationLibraryMetrics.SchemaUrl);
Assert.Equal("TestMeter", instrumentationLibraryMetrics.InstrumentationLibrary.Name);
Assert.Equal("0.0.1", instrumentationLibraryMetrics.InstrumentationLibrary.Version);

Assert.Single(instrumentationLibraryMetrics.Metrics);

foreach (var metric in instrumentationLibraryMetrics.Metrics)
{
Assert.Equal(string.Empty, metric.Description);
Assert.Equal(string.Empty, metric.Unit);
Assert.Equal("counter", metric.Name);

Assert.Equal(OtlpMetrics.Metric.DataOneofCase.Sum, metric.DataCase);
Assert.True(metric.Sum.IsMonotonic);
Assert.Equal(OtlpMetrics.AggregationTemporality.Delta, metric.Sum.AggregationTemporality);

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);

#pragma warning disable CS0612 // Type or member is obsolete
Assert.Empty(dataPoint.Labels);
#pragma warning restore CS0612 // Type or member is obsolete
OtlpTestHelpers.AssertOtlpAttributes(tags.ToList(), dataPoint.Attributes);

Assert.Empty(dataPoint.Exemplars);
}

testCompleted = true;
}
}

private class NoopMetricsServiceClient : OtlpCollector.MetricsService.IMetricsServiceClient
{
public OtlpCollector.ExportMetricsServiceResponse Export(OtlpCollector.ExportMetricsServiceRequest request, GrpcCore.Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default)
{
return null;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// <copyright file="OtlpTestHelpers.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>

using System.Collections.Generic;
using System.Linq;
using Google.Protobuf.Collections;
using Xunit;
using OtlpCommon = Opentelemetry.Proto.Common.V1;

namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests
{
internal static class OtlpTestHelpers
{
public static void AssertOtlpAttributes(
IEnumerable<KeyValuePair<string, object>> expected,
RepeatedField<OtlpCommon.KeyValue> actual)
{
var expectedAttributes = expected.ToList();
int expectedSize = 0;
for (int i = 0; i < expectedAttributes.Count; i++)
{
var current = expectedAttributes[i].Value;

if (current.GetType().IsArray)
{
if (current is bool[] boolArray)
{
int index = 0;
foreach (var item in boolArray)
{
Assert.Equal(expectedAttributes[i].Key, actual[i + index].Key);
AssertOtlpAttributeValue(item, actual[i + index]);
index++;
expectedSize++;
}
}
else if (current is int[] intArray)
{
int index = 1;
foreach (var item in intArray)
{
Assert.Equal(expectedAttributes[i].Key, actual[i + index].Key);
AssertOtlpAttributeValue(item, actual[i + index]);
index++;
expectedSize++;
}
}
else if (current is double[] doubleArray)
{
int index = 2;
foreach (var item in doubleArray)
{
Assert.Equal(expectedAttributes[i].Key, actual[i + index].Key);
AssertOtlpAttributeValue(item, actual[i + index]);
index++;
expectedSize++;
}
}
else if (current is string[] stringArray)
{
int index = 3;
foreach (var item in stringArray)
{
Assert.Equal(expectedAttributes[i].Key, actual[i + index].Key);
AssertOtlpAttributeValue(item, actual[i + index]);
index++;
expectedSize++;
}
}
}
else
{
Assert.Equal(expectedAttributes[i].Key, actual[i].Key);
AssertOtlpAttributeValue(current, actual[i]);
expectedSize++;
}
}

Assert.Equal(expectedSize, actual.Count);
}

private static void AssertOtlpAttributeValue(object expected, OtlpCommon.KeyValue actual)
{
switch (expected)
{
case string s:
Assert.Equal(s, actual.Value.StringValue);
break;
case bool b:
Assert.Equal(b, actual.Value.BoolValue);
break;
case long l:
Assert.Equal(l, actual.Value.IntValue);
break;
case double d:
Assert.Equal(d, actual.Value.DoubleValue);
break;
case int i:
Assert.Equal(i, actual.Value.IntValue);
break;
default:
Assert.Equal(expected.ToString(), actual.Value.StringValue);
break;
}
}
}
}
Loading

0 comments on commit 138035b

Please sign in to comment.