File: Otlp\Model\MetricValues\DimensionScope.cs
Web Access
Project: src\src\Aspire.Dashboard\Aspire.Dashboard.csproj (Aspire.Dashboard)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using Google.Protobuf.Collections;
using OpenTelemetry.Proto.Metrics.V1;
 
namespace Aspire.Dashboard.Otlp.Model.MetricValues;
 
[DebuggerDisplay("Name = {Name}, Values = {Values.Count}")]
public class DimensionScope
{
    public const string NoDimensions = "no-dimensions";
    public string Name { get; init; }
    public KeyValuePair<string, string>[] Attributes { get; init; }
    public IList<MetricValueBase> Values => _values;
 
    private readonly CircularBuffer<MetricValueBase> _values;
    // Used to aid in merging values that are the same in a concurrent environment
    private MetricValueBase? _lastValue;
 
    public int Capacity => _values.Capacity;
 
    public DimensionScope(int capacity, KeyValuePair<string, string>[] attributes)
    {
        Attributes = attributes;
        var name = Attributes.ConcatProperties();
        Name = name != null && name.Length > 0 ? name : NoDimensions;
        _values = new(capacity);
    }
 
    public void AddPointValue(NumberDataPoint d, OtlpContext context)
    {
        var start = OtlpHelpers.UnixNanoSecondsToDateTime(d.StartTimeUnixNano);
        var end = OtlpHelpers.UnixNanoSecondsToDateTime(d.TimeUnixNano);
 
        if (d.ValueCase == NumberDataPoint.ValueOneofCase.AsInt)
        {
            var value = d.AsInt;
            var lastLongValue = _lastValue as MetricValue<long>;
            if (lastLongValue is not null && lastLongValue.Value == value)
            {
                lastLongValue.End = end;
                AddExemplars(lastLongValue, d.Exemplars, context);
                Interlocked.Increment(ref lastLongValue.Count);
            }
            else
            {
                if (lastLongValue is not null)
                {
                    start = lastLongValue.End;
                }
                _lastValue = new MetricValue<long>(d.AsInt, start, end);
                AddExemplars(_lastValue, d.Exemplars, context);
                _values.Add(_lastValue);
            }
        }
        else if (d.ValueCase == NumberDataPoint.ValueOneofCase.AsDouble)
        {
            var lastDoubleValue = _lastValue as MetricValue<double>;
            if (lastDoubleValue is not null && lastDoubleValue.Value == d.AsDouble)
            {
                lastDoubleValue.End = end;
                AddExemplars(lastDoubleValue, d.Exemplars, context);
                Interlocked.Increment(ref lastDoubleValue.Count);
            }
            else
            {
                if (lastDoubleValue is not null)
                {
                    start = lastDoubleValue.End;
                }
                _lastValue = new MetricValue<double>(d.AsDouble, start, end);
                AddExemplars(_lastValue, d.Exemplars, context);
                _values.Add(_lastValue);
            }
        }
    }
 
    public void AddHistogramValue(HistogramDataPoint h, OtlpContext context)
    {
        var start = OtlpHelpers.UnixNanoSecondsToDateTime(h.StartTimeUnixNano);
        var end = OtlpHelpers.UnixNanoSecondsToDateTime(h.TimeUnixNano);
 
        var lastHistogramValue = _lastValue as HistogramValue;
        if (lastHistogramValue is not null && lastHistogramValue.Count == h.Count)
        {
            lastHistogramValue.End = end;
            AddExemplars(lastHistogramValue, h.Exemplars, context);
        }
        else
        {
            // If the explicit bounds are the same as the last value, reuse them.
            double[] explicitBounds;
            if (lastHistogramValue is not null)
            {
                start = lastHistogramValue.End;
                explicitBounds = lastHistogramValue.ExplicitBounds.SequenceEqual(h.ExplicitBounds)
                    ? lastHistogramValue.ExplicitBounds
                    : h.ExplicitBounds.ToArray();
            }
            else
            {
                explicitBounds = h.ExplicitBounds.ToArray();
            }
            _lastValue = new HistogramValue(h.BucketCounts.ToArray(), h.Sum, h.Count, start, end, explicitBounds);
            AddExemplars(_lastValue, h.Exemplars, context);
            _values.Add(_lastValue);
        }
    }
 
    private static void AddExemplars(MetricValueBase value, RepeatedField<Exemplar> exemplars, OtlpContext context)
    {
        if (exemplars.Count > 0)
        {
            foreach (var exemplar in exemplars)
            {
                // Can't do anything useful with exemplars without a linked trace. Filter them out.
                if (exemplar.TraceId == null || exemplar.SpanId == null)
                {
                    continue;
                }
 
                var start = OtlpHelpers.UnixNanoSecondsToDateTime(exemplar.TimeUnixNano);
                var exemplarValue = exemplar.HasAsDouble ? exemplar.AsDouble : exemplar.AsInt;
 
                var exists = false;
                foreach (var existingExemplar in value.Exemplars)
                {
                    if (start == existingExemplar.Start && exemplarValue == existingExemplar.Value)
                    {
                        exists = true;
                        break;
                    }
                }
                if (exists)
                {
                    continue;
                }
 
                value.Exemplars.Add(new MetricsExemplar
                {
                    Start = start,
                    Value = exemplarValue,
                    Attributes = exemplar.FilteredAttributes.ToKeyValuePairs(context),
                    SpanId = exemplar.SpanId.ToHexString(),
                    TraceId = exemplar.TraceId.ToHexString()
                });
            }
        }
    }
 
    internal static DimensionScope Clone(DimensionScope value, DateTime? valuesStart, DateTime? valuesEnd)
    {
        var newDimensionScope = new DimensionScope(value.Capacity, value.Attributes);
        if (valuesStart != null && valuesEnd != null)
        {
            foreach (var item in value._values)
            {
                if ((item.Start <= valuesEnd.Value && item.End >= valuesStart.Value) ||
                    (item.Start >= valuesStart.Value && item.End <= valuesEnd.Value))
                {
                    newDimensionScope._values.Add(MetricValueBase.Clone(item));
                }
            }
        }
 
        return newDimensionScope;
    }
}