| File: Otlp\Model\OtlpResource.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.Collections.Concurrent; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Aspire.Dashboard.Otlp.Storage; using Google.Protobuf.Collections; using OpenTelemetry.Proto.Common.V1; namespace Aspire.Dashboard.Otlp.Model; [DebuggerDisplay("ResourceName = {ResourceName}, InstanceId = {InstanceId}")] public class OtlpResource : IOtlpResource { public const string SERVICE_NAME = "service.name"; public const string SERVICE_INSTANCE_ID = "service.instance.id"; public const string PROCESS_EXECUTABLE_NAME = "process.executable.name"; public string ResourceName { get; } public string? InstanceId { get; } public OtlpContext Context { get; } // This flag indicates whether the app was created for an uninstrumented peer. // It's used to hide the app on pages that don't use uninstrumented peers. // Traces uses uninstrumented peers, structured logs and metrics don't. public bool UninstrumentedPeer { get; private set; } /// <summary> /// Indicates whether this resource has structured logs. /// </summary> public bool HasLogs { get; internal set; } /// <summary> /// Indicates whether this resource has traces. /// </summary> public bool HasTraces { get; internal set; } /// <summary> /// Indicates whether this resource has metrics. /// </summary> public bool HasMetrics { get; internal set; } public ResourceKey ResourceKey => new ResourceKey(ResourceName, InstanceId); private readonly ConcurrentDictionary<KeyValuePair<string, string>[], OtlpResourceView> _resourceViews = new(ResourceViewKeyComparer.Instance); public OtlpResource(string name, string? instanceId, bool uninstrumentedPeer, OtlpContext context) { ResourceName = name; InstanceId = instanceId; UninstrumentedPeer = uninstrumentedPeer; Context = context; } public static Dictionary<string, List<OtlpResource>> GetReplicasByResourceName(IEnumerable<OtlpResource> allResources) { return allResources .GroupBy(resource => resource.ResourceName, StringComparers.ResourceName) .ToDictionary(grouping => grouping.Key, grouping => grouping.ToList()); } public static string GetResourceName(OtlpResourceView resource, IReadOnlyList<IOtlpResource> allResources) => OtlpHelpers.GetResourceName(resource.Resource, allResources); internal List<OtlpResourceView> GetViews() => _resourceViews.Values.ToList(); internal OtlpResourceView GetView(RepeatedField<KeyValue> attributes) { return GetView(new OtlpResourceView(this, attributes)); } internal OtlpResourceView GetViewFromProperties(KeyValuePair<string, string>[] properties) { return GetView(new OtlpResourceView(this, properties)); } private OtlpResourceView GetView(OtlpResourceView view) { // Inefficient to create this to possibly throw it away. if (_resourceViews.TryGetValue(view.Properties, out var resourceView)) { return resourceView; } if (_resourceViews.Count >= TelemetryRepositoryLimits.MaxResourceViewCount) { throw new InvalidOperationException($"Resource view limit of {TelemetryRepositoryLimits.MaxResourceViewCount} reached."); } return _resourceViews.GetOrAdd(view.Properties, view); } internal void SetUninstrumentedPeer(bool uninstrumentedPeer) { // An app could initially be created for an uninstrumented peer and then telemetry is received from it. // This method "upgrades" the resource to not be for an uninstrumented peer when appropriate. if (UninstrumentedPeer && !uninstrumentedPeer) { UninstrumentedPeer = uninstrumentedPeer; } } /// <summary> /// Resource views are equal when all properties are equal. /// </summary> private sealed class ResourceViewKeyComparer : IEqualityComparer<KeyValuePair<string, string>[]> { public static readonly ResourceViewKeyComparer Instance = new(); public bool Equals(KeyValuePair<string, string>[]? x, KeyValuePair<string, string>[]? y) { if (x == y) { return true; } if (x == null || y == null) { return false; } if (x.Length != y.Length) { return false; } for (var i = 0; i < x.Length; i++) { if (!string.Equals(x[i].Key, y[i].Key, StringComparisons.OtlpAttribute)) { return false; } if (!string.Equals(x[i].Value, y[i].Value, StringComparisons.OtlpAttribute)) { return false; } } return true; } public int GetHashCode([DisallowNull] KeyValuePair<string, string>[] obj) { var hashCode = new HashCode(); for (var i = 0; i < obj.Length; i++) { hashCode.Add(StringComparers.OtlpAttribute.GetHashCode(obj[i].Key)); hashCode.Add(StringComparers.OtlpAttribute.GetHashCode(obj[i].Value)); } return hashCode.ToHashCode(); } } } /// <summary> /// Compares resources by their resource key. /// </summary> internal sealed class OtlpResourceEqualityComparer : IEqualityComparer<OtlpResource> { public static readonly OtlpResourceEqualityComparer Instance = new(); private OtlpResourceEqualityComparer() { } public bool Equals(OtlpResource? x, OtlpResource? y) => ReferenceEquals(x, y) || x is not null && y is not null && x.ResourceKey == y.ResourceKey; public int GetHashCode(OtlpResource obj) => obj.ResourceKey.GetHashCode(); }