opentelemetry-dotnet/src/OpenTelemetry.Exporter.Open.../Implementation/MetricItemExtensions.cs

445 lines
18 KiB
C#

// <copyright file="MetricItemExtensions.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.Concurrent;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using Google.Protobuf.Collections;
using OpenTelemetry.Metrics;
using OtlpCollector = OpenTelemetry.Proto.Collector.Metrics.V1;
using OtlpCommon = OpenTelemetry.Proto.Common.V1;
using OtlpMetrics = OpenTelemetry.Proto.Metrics.V1;
using OtlpResource = OpenTelemetry.Proto.Resource.V1;
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation
{
internal static class MetricItemExtensions
{
private static readonly ConcurrentBag<OtlpMetrics.ScopeMetrics> MetricListPool = new();
private static readonly Action<RepeatedField<OtlpMetrics.Metric>, int> RepeatedFieldOfMetricSetCountAction = CreateRepeatedFieldOfMetricSetCountAction();
internal static void AddMetrics(
this OtlpCollector.ExportMetricsServiceRequest request,
OtlpResource.Resource processResource,
in Batch<Metric> metrics)
{
var metricsByLibrary = new Dictionary<string, OtlpMetrics.ScopeMetrics>();
var resourceMetrics = new OtlpMetrics.ResourceMetrics
{
Resource = processResource,
};
request.ResourceMetrics.Add(resourceMetrics);
foreach (var metric in metrics)
{
var otlpMetric = metric.ToOtlpMetric();
// TODO: Replace null check with exception handling.
if (otlpMetric == null)
{
OpenTelemetryProtocolExporterEventSource.Log.CouldNotTranslateMetric(
nameof(MetricItemExtensions),
nameof(AddMetrics));
continue;
}
var meterName = metric.MeterName;
if (!metricsByLibrary.TryGetValue(meterName, out var scopeMetrics))
{
scopeMetrics = GetMetricListFromPool(meterName, metric.MeterVersion);
metricsByLibrary.Add(meterName, scopeMetrics);
resourceMetrics.ScopeMetrics.Add(scopeMetrics);
}
scopeMetrics.Metrics.Add(otlpMetric);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void Return(this OtlpCollector.ExportMetricsServiceRequest request)
{
var resourceMetrics = request.ResourceMetrics.FirstOrDefault();
if (resourceMetrics == null)
{
return;
}
foreach (var scope in resourceMetrics.ScopeMetrics)
{
RepeatedFieldOfMetricSetCountAction(scope.Metrics, 0);
MetricListPool.Add(scope);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static OtlpMetrics.ScopeMetrics GetMetricListFromPool(string name, string version)
{
if (!MetricListPool.TryTake(out var metrics))
{
metrics = new OtlpMetrics.ScopeMetrics
{
Scope = new OtlpCommon.InstrumentationScope
{
Name = name, // Name is enforced to not be null, but it can be empty.
Version = version ?? string.Empty, // NRE throw by proto
},
};
}
else
{
metrics.Scope.Name = name;
metrics.Scope.Version = version ?? string.Empty;
}
return metrics;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static OtlpMetrics.Metric ToOtlpMetric(this Metric metric)
{
var otlpMetric = new OtlpMetrics.Metric
{
Name = metric.Name,
};
if (metric.Description != null)
{
otlpMetric.Description = metric.Description;
}
if (metric.Unit != null)
{
otlpMetric.Unit = metric.Unit;
}
OtlpMetrics.AggregationTemporality temporality;
if (metric.Temporality == AggregationTemporality.Delta)
{
temporality = OtlpMetrics.AggregationTemporality.Delta;
}
else
{
temporality = OtlpMetrics.AggregationTemporality.Cumulative;
}
switch (metric.MetricType)
{
case MetricType.LongSum:
case MetricType.LongSumNonMonotonic:
{
var sum = new OtlpMetrics.Sum
{
IsMonotonic = metric.MetricType == MetricType.LongSum,
AggregationTemporality = temporality,
};
foreach (ref readonly var metricPoint in metric.GetMetricPoints())
{
var dataPoint = new OtlpMetrics.NumberDataPoint
{
StartTimeUnixNano = (ulong)metricPoint.StartTime.ToUnixTimeNanoseconds(),
TimeUnixNano = (ulong)metricPoint.EndTime.ToUnixTimeNanoseconds(),
};
AddAttributes(metricPoint.Tags, dataPoint.Attributes);
dataPoint.AsInt = metricPoint.GetSumLong();
sum.DataPoints.Add(dataPoint);
}
otlpMetric.Sum = sum;
break;
}
case MetricType.DoubleSum:
case MetricType.DoubleSumNonMonotonic:
{
var sum = new OtlpMetrics.Sum
{
IsMonotonic = metric.MetricType == MetricType.DoubleSum,
AggregationTemporality = temporality,
};
foreach (ref readonly var metricPoint in metric.GetMetricPoints())
{
var dataPoint = new OtlpMetrics.NumberDataPoint
{
StartTimeUnixNano = (ulong)metricPoint.StartTime.ToUnixTimeNanoseconds(),
TimeUnixNano = (ulong)metricPoint.EndTime.ToUnixTimeNanoseconds(),
};
AddAttributes(metricPoint.Tags, dataPoint.Attributes);
dataPoint.AsDouble = metricPoint.GetSumDouble();
sum.DataPoints.Add(dataPoint);
}
otlpMetric.Sum = sum;
break;
}
case MetricType.LongGauge:
{
var gauge = new OtlpMetrics.Gauge();
foreach (ref readonly var metricPoint in metric.GetMetricPoints())
{
var dataPoint = new OtlpMetrics.NumberDataPoint
{
StartTimeUnixNano = (ulong)metricPoint.StartTime.ToUnixTimeNanoseconds(),
TimeUnixNano = (ulong)metricPoint.EndTime.ToUnixTimeNanoseconds(),
};
AddAttributes(metricPoint.Tags, dataPoint.Attributes);
dataPoint.AsInt = metricPoint.GetGaugeLastValueLong();
gauge.DataPoints.Add(dataPoint);
}
otlpMetric.Gauge = gauge;
break;
}
case MetricType.DoubleGauge:
{
var gauge = new OtlpMetrics.Gauge();
foreach (ref readonly var metricPoint in metric.GetMetricPoints())
{
var dataPoint = new OtlpMetrics.NumberDataPoint
{
StartTimeUnixNano = (ulong)metricPoint.StartTime.ToUnixTimeNanoseconds(),
TimeUnixNano = (ulong)metricPoint.EndTime.ToUnixTimeNanoseconds(),
};
AddAttributes(metricPoint.Tags, dataPoint.Attributes);
dataPoint.AsDouble = metricPoint.GetGaugeLastValueDouble();
gauge.DataPoints.Add(dataPoint);
}
otlpMetric.Gauge = gauge;
break;
}
case MetricType.Histogram:
{
var histogram = new OtlpMetrics.Histogram
{
AggregationTemporality = temporality,
};
foreach (ref readonly var metricPoint in metric.GetMetricPoints())
{
var dataPoint = new OtlpMetrics.HistogramDataPoint
{
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;
}
foreach (var histogramMeasurement in metricPoint.GetHistogramBuckets())
{
dataPoint.BucketCounts.Add((ulong)histogramMeasurement.BucketCount);
if (histogramMeasurement.ExplicitBound != double.PositiveInfinity)
{
dataPoint.ExplicitBounds.Add(histogramMeasurement.ExplicitBound);
}
}
/* Commenting out as Exemplars is marked internal
var exemplars = metricPoint.GetExemplars();
foreach (var examplar in exemplars)
{
if (examplar.Timestamp != default)
{
byte[] traceIdBytes = new byte[16];
examplar.TraceId?.CopyTo(traceIdBytes);
byte[] spanIdBytes = new byte[8];
examplar.SpanId?.CopyTo(spanIdBytes);
var otlpExemplar = new OtlpMetrics.Exemplar
{
TimeUnixNano = (ulong)examplar.Timestamp.ToUnixTimeNanoseconds(),
TraceId = UnsafeByteOperations.UnsafeWrap(traceIdBytes),
SpanId = UnsafeByteOperations.UnsafeWrap(spanIdBytes),
AsDouble = examplar.DoubleValue,
};
if (examplar.FilteredTags != null)
{
foreach (var tag in examplar.FilteredTags)
{
if (OtlpKeyValueTransformer.Instance.TryTransformTag(tag, out var result))
{
otlpExemplar.FilteredAttributes.Add(result);
}
}
}
dataPoint.Exemplars.Add(otlpExemplar);
}
}
*/
histogram.DataPoints.Add(dataPoint);
}
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);
}
// TODO: exemplars.
histogram.DataPoints.Add(dataPoint);
}
otlpMetric.ExponentialHistogram = histogram;
break;
}
}
return otlpMetric;
}
private static void AddAttributes(ReadOnlyTagCollection tags, RepeatedField<OtlpCommon.KeyValue> attributes)
{
foreach (var tag in tags)
{
if (OtlpKeyValueTransformer.Instance.TryTransformTag(tag, out var result))
{
attributes.Add(result);
}
}
}
/*
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static OtlpMetrics.Exemplar ToOtlpExemplar(this IExemplar exemplar)
{
var otlpExemplar = new OtlpMetrics.Exemplar();
if (exemplar.Value is double doubleValue)
{
otlpExemplar.AsDouble = doubleValue;
}
else if (exemplar.Value is long longValue)
{
otlpExemplar.AsInt = longValue;
}
else
{
// TODO: Determine how we want to handle exceptions here.
// Do we want to just skip this exemplar and move on?
// Should we skip recording the whole metric?
throw new ArgumentException();
}
otlpExemplar.TimeUnixNano = (ulong)exemplar.Timestamp.ToUnixTimeNanoseconds();
// TODO: Do the TagEnumerationState thing.
foreach (var tag in exemplar.FilteredTags)
{
otlpExemplar.FilteredAttributes.Add(tag.ToOtlpAttribute());
}
if (exemplar.TraceId != default)
{
byte[] traceIdBytes = new byte[16];
exemplar.TraceId.CopyTo(traceIdBytes);
otlpExemplar.TraceId = UnsafeByteOperations.UnsafeWrap(traceIdBytes);
}
if (exemplar.SpanId != default)
{
byte[] spanIdBytes = new byte[8];
exemplar.SpanId.CopyTo(spanIdBytes);
otlpExemplar.SpanId = UnsafeByteOperations.UnsafeWrap(spanIdBytes);
}
return otlpExemplar;
}
*/
private static Action<RepeatedField<OtlpMetrics.Metric>, int> CreateRepeatedFieldOfMetricSetCountAction()
{
FieldInfo repeatedFieldOfMetricCountField = typeof(RepeatedField<OtlpMetrics.Metric>).GetField("count", BindingFlags.NonPublic | BindingFlags.Instance);
DynamicMethod dynamicMethod = new DynamicMethod(
"CreateSetCountAction",
null,
new[] { typeof(RepeatedField<OtlpMetrics.Metric>), typeof(int) },
typeof(MetricItemExtensions).Module,
skipVisibility: true);
var generator = dynamicMethod.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Stfld, repeatedFieldOfMetricCountField);
generator.Emit(OpCodes.Ret);
return (Action<RepeatedField<OtlpMetrics.Metric>, int>)dynamicMethod.CreateDelegate(typeof(Action<RepeatedField<OtlpMetrics.Metric>, int>));
}
}
}