< Summary

Information
Class: Elsa.Diagnostics.OpenTelemetry.Providers.InMemory.InMemoryOpenTelemetryStore
Assembly: Elsa.Diagnostics.OpenTelemetry
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Diagnostics.OpenTelemetry/Providers/InMemory/InMemoryOpenTelemetryStore.cs
Line coverage
96%
Covered lines: 142
Uncovered lines: 5
Coverable lines: 147
Total lines: 227
Line coverage: 96.5%
Branch coverage
52%
Covered branches: 47
Total branches: 90
Branch coverage: 52.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
WriteAsync(...)100%1212100%
QueryResourcesAsync(...)50%88100%
QueryTracesAsync(...)41.66%2424100%
GetTraceAsync(...)50%2275%
QueryMetricsAsync(...)37.5%1616100%
QueryLogsAsync(...)40%2020100%
GetDiagnosticsAsync(...)100%11100%
ClampTake(...)100%22100%
ClampCapacity(...)100%11100%
ResolveResourceIds(...)100%22100%
Matches(...)50%44100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Diagnostics.OpenTelemetry/Providers/InMemory/InMemoryOpenTelemetryStore.cs

#LineLine coverage
 1using Elsa.Diagnostics.OpenTelemetry.Contracts;
 2using Elsa.Diagnostics.OpenTelemetry.Models;
 3using Elsa.Diagnostics.OpenTelemetry.Options;
 4using Microsoft.Extensions.Options;
 5
 6namespace Elsa.Diagnostics.OpenTelemetry.Providers.InMemory;
 7
 8public class InMemoryOpenTelemetryStore : IOpenTelemetryStore
 9{
 10    private readonly IOpenTelemetrySourceRegistry _sourceRegistry;
 11    private readonly OpenTelemetryDiagnosticsOptions _options;
 12    private readonly int _traceCapacity;
 13    private readonly int _spanCapacity;
 14    private readonly int _metricPointCapacity;
 15    private readonly int _logRecordCapacity;
 16    private readonly RingBuffer<TelemetryTrace> _traces;
 17    private readonly RingBuffer<TelemetrySpan> _spans;
 18    private readonly RingBuffer<MetricPoint> _metricPoints;
 19    private readonly RingBuffer<OtlpLogRecord> _logs;
 1620    private readonly object _instrumentLock = new();
 1621    private readonly Dictionary<string, MetricInstrument> _instruments = new(StringComparer.OrdinalIgnoreCase);
 22
 1623    public InMemoryOpenTelemetryStore(IOptions<OpenTelemetryDiagnosticsOptions> options, IOpenTelemetrySourceRegistry so
 24    {
 1625        _options = options.Value;
 1626        _sourceRegistry = sourceRegistry;
 1627        _traceCapacity = ClampCapacity(_options.TraceCapacity);
 1628        _spanCapacity = ClampCapacity(_options.SpanCapacity);
 1629        _metricPointCapacity = ClampCapacity(_options.MetricPointCapacity);
 1630        _logRecordCapacity = ClampCapacity(_options.LogRecordCapacity);
 1631        _traces = new(_traceCapacity);
 1632        _spans = new(_spanCapacity);
 1633        _metricPoints = new(_metricPointCapacity);
 1634        _logs = new(_logRecordCapacity);
 1635    }
 36
 37    public ValueTask WriteAsync(OpenTelemetryBatch batch, CancellationToken cancellationToken = default)
 38    {
 6239        foreach (var resource in batch.Resources)
 1540            _sourceRegistry.MarkSeen(resource);
 41
 5242        foreach (var trace in batch.Traces)
 1043            _traces.Add(trace);
 44
 4245        foreach (var span in batch.Spans)
 546            _spans.Add(span);
 47
 1648        lock (_instrumentLock)
 49        {
 4250            foreach (var instrument in batch.Instruments)
 551                _instruments[instrument.Id] = instrument;
 52        }
 53
 4254        foreach (var point in batch.MetricPoints)
 555            _metricPoints.Add(point);
 56
 4257        foreach (var log in batch.Logs)
 558            _logs.Add(log);
 59
 1660        return ValueTask.CompletedTask;
 61    }
 62
 63    public ValueTask<OpenTelemetryResourceResult> QueryResourcesAsync(OpenTelemetryResourceFilter filter, CancellationTo
 64    {
 265        var take = ClampTake(filter.Take);
 266        var items = _sourceRegistry.List()
 467            .Where(x => string.IsNullOrWhiteSpace(filter.ServiceName) || string.Equals(x.ServiceName, filter.ServiceName
 468            .Where(x => filter.Status == null || x.Status == filter.Status)
 469            .Where(x => string.IsNullOrWhiteSpace(filter.Search) || Matches(x.ServiceName, filter.Search) || Matches(x.I
 470            .OrderByDescending(x => x.LastSeen)
 471            .ThenBy(x => x.ServiceName, StringComparer.OrdinalIgnoreCase)
 272            .Take(take)
 273            .ToList();
 74
 275        return ValueTask.FromResult(new OpenTelemetryResourceResult(items, _sourceRegistry.DroppedCount));
 76    }
 77
 78    public ValueTask<OpenTelemetryTraceResult> QueryTracesAsync(OpenTelemetryTraceFilter filter, CancellationToken cance
 79    {
 480        var take = ClampTake(filter.Take);
 481        var serviceResourceIds = ResolveResourceIds(filter.ServiceName);
 482        var items = _traces.Snapshot()
 783            .Where(x => string.IsNullOrWhiteSpace(filter.TraceId) || Matches(x.TraceId, filter.TraceId))
 784            .Where(x => filter.Status == null || x.Status == filter.Status)
 785            .Where(x => filter.From == null || x.StartTime >= filter.From)
 786            .Where(x => filter.To == null || x.StartTime <= filter.To)
 787            .Where(x => string.IsNullOrWhiteSpace(filter.WorkflowInstanceId) || x.WorkflowInstanceIds.Any(id => Matches(
 788            .Where(x => string.IsNullOrWhiteSpace(filter.ResourceId) || x.ResourceIds.Contains(filter.ResourceId, String
 789            .Where(x => serviceResourceIds == null || x.ResourceIds.Any(serviceResourceIds.Contains))
 690            .Where(x => string.IsNullOrWhiteSpace(filter.Search) || Matches(x.TraceId, filter.Search) || Matches(x.Name,
 691            .OrderBy(x => x.StartTime)
 692            .GroupBy(x => x.TraceId, StringComparer.OrdinalIgnoreCase)
 593            .Select(x => x.Last())
 494            .TakeLast(take)
 495            .ToList();
 96
 497        return ValueTask.FromResult(new OpenTelemetryTraceResult(items, _traces.DroppedCount));
 98    }
 99
 100    public ValueTask<OpenTelemetryTraceDetail?> GetTraceAsync(string traceId, CancellationToken cancellationToken = defa
 101    {
 4102        var trace = _traces.Snapshot().LastOrDefault(x => string.Equals(x.TraceId, traceId, StringComparison.OrdinalIgno
 2103        if (trace == null)
 0104            return ValueTask.FromResult<OpenTelemetryTraceDetail?>(null);
 105
 2106        var spans = _spans.Snapshot()
 4107            .Where(x => string.Equals(x.TraceId, traceId, StringComparison.OrdinalIgnoreCase))
 4108            .OrderBy(x => x.StartTime)
 4109            .ThenBy(x => x.SpanId, StringComparer.OrdinalIgnoreCase)
 2110            .ToList();
 2111        var resourceIds = trace.ResourceIds.ToHashSet(StringComparer.OrdinalIgnoreCase);
 2112        var resources = _sourceRegistry.List()
 2113            .Where(x => resourceIds.Contains(x.Id))
 0114            .OrderBy(x => x.ServiceName, StringComparer.OrdinalIgnoreCase)
 0115            .ThenBy(x => x.Id, StringComparer.OrdinalIgnoreCase)
 2116            .ToList();
 2117        var logs = _logs.Snapshot()
 1118            .Where(x => string.Equals(x.TraceId, traceId, StringComparison.OrdinalIgnoreCase))
 0119            .OrderBy(x => x.Timestamp)
 0120            .ThenBy(x => x.Id, StringComparer.OrdinalIgnoreCase)
 2121            .ToList();
 122
 2123        return ValueTask.FromResult<OpenTelemetryTraceDetail?>(new OpenTelemetryTraceDetail(trace, spans, resources, log
 124    }
 125
 126    public ValueTask<OpenTelemetryMetricResult> QueryMetricsAsync(OpenTelemetryMetricFilter filter, CancellationToken ca
 127    {
 2128        var take = ClampTake(filter.Take);
 2129        var serviceResourceIds = ResolveResourceIds(filter.ServiceName);
 130
 131        Dictionary<string, MetricInstrument> instruments;
 2132        lock (_instrumentLock)
 2133            instruments = new(_instruments, StringComparer.OrdinalIgnoreCase);
 134
 2135        var instrumentFilterIds = string.IsNullOrWhiteSpace(filter.InstrumentName)
 2136            ? null
 2137            : instruments.Values
 4138                .Where(x => Matches(x.Name, filter.InstrumentName))
 2139                .Select(x => x.Id)
 2140                .ToHashSet(StringComparer.OrdinalIgnoreCase);
 141
 2142        var points = _metricPoints.Snapshot()
 4143            .Where(x => string.IsNullOrWhiteSpace(filter.ResourceId) || string.Equals(x.ResourceId, filter.ResourceId, S
 4144            .Where(x => serviceResourceIds == null || serviceResourceIds.Contains(x.ResourceId))
 4145            .Where(x => filter.From == null || x.Timestamp >= filter.From)
 4146            .Where(x => filter.To == null || x.Timestamp <= filter.To)
 4147            .Where(x => instrumentFilterIds == null || instrumentFilterIds.Contains(x.InstrumentId))
 2148            .OrderBy(x => x.Timestamp)
 2149            .TakeLast(take)
 2150            .ToList();
 151
 2152        var selectedInstruments = points
 2153            .Select(x => x.InstrumentId)
 2154            .Distinct(StringComparer.OrdinalIgnoreCase)
 2155            .Where(instruments.ContainsKey)
 2156            .Select(x => instruments[x])
 2157            .ToList();
 158
 2159        return ValueTask.FromResult(new OpenTelemetryMetricResult(selectedInstruments, points, _metricPoints.DroppedCoun
 160    }
 161
 162    public ValueTask<OpenTelemetryLogResult> QueryLogsAsync(OpenTelemetryLogFilter filter, CancellationToken cancellatio
 163    {
 1164        var take = ClampTake(filter.Take);
 1165        var serviceResourceIds = ResolveResourceIds(filter.ServiceName);
 1166        var logs = _logs.Snapshot()
 3167            .Where(x => string.IsNullOrWhiteSpace(filter.ResourceId) || string.Equals(x.ResourceId, filter.ResourceId, S
 3168            .Where(x => serviceResourceIds == null || serviceResourceIds.Contains(x.ResourceId))
 3169            .Where(x => string.IsNullOrWhiteSpace(filter.TraceId) || Matches(x.TraceId, filter.TraceId))
 2170            .Where(x => string.IsNullOrWhiteSpace(filter.SpanId) || Matches(x.SpanId, filter.SpanId))
 2171            .Where(x => string.IsNullOrWhiteSpace(filter.Severity) || Matches(x.SeverityText, filter.Severity))
 1172            .Where(x => filter.From == null || x.Timestamp >= filter.From)
 1173            .Where(x => filter.To == null || x.Timestamp <= filter.To)
 1174            .Where(x => string.IsNullOrWhiteSpace(filter.Search) || Matches(x.Body, filter.Search))
 1175            .OrderBy(x => x.Timestamp)
 1176            .TakeLast(take)
 1177            .ToList();
 178
 1179        return ValueTask.FromResult(new OpenTelemetryLogResult(logs, _logs.DroppedCount));
 180    }
 181
 182    public ValueTask<OpenTelemetryStorageDiagnostics> GetDiagnosticsAsync(CancellationToken cancellationToken = default)
 183    {
 184        Dictionary<string, MetricInstrument> instruments;
 2185        lock (_instrumentLock)
 2186            instruments = new(_instruments, StringComparer.OrdinalIgnoreCase);
 187
 2188        var traceSnapshot = _traces.Snapshot();
 2189        var spanSnapshot = _spans.Snapshot();
 2190        var metricPointSnapshot = _metricPoints.Snapshot();
 2191        var logSnapshot = _logs.Snapshot();
 2192        var diagnostics = new OpenTelemetryStorageDiagnostics(
 2193            _traceCapacity,
 2194            _spanCapacity,
 2195            _metricPointCapacity,
 2196            _logRecordCapacity,
 2197            _sourceRegistry.List().Count,
 2198            traceSnapshot.Count,
 2199            spanSnapshot.Count,
 2200            instruments.Count,
 2201            metricPointSnapshot.Count,
 2202            logSnapshot.Count,
 2203            _traces.DroppedCount,
 2204            _spans.DroppedCount,
 2205            _metricPoints.DroppedCount,
 2206            _logs.DroppedCount);
 207
 2208        return ValueTask.FromResult(diagnostics);
 209    }
 210
 9211    private int ClampTake(int? take) => Math.Clamp(take ?? _options.MaxQuerySize, 0, _options.MaxQuerySize);
 212
 64213    private static int ClampCapacity(int capacity) => Math.Max(1, capacity);
 214
 215    private HashSet<string>? ResolveResourceIds(string? serviceName)
 216    {
 7217        if (string.IsNullOrWhiteSpace(serviceName))
 6218            return null;
 219
 1220        return _sourceRegistry.List()
 2221            .Where(x => string.Equals(x.ServiceName, serviceName, StringComparison.OrdinalIgnoreCase))
 1222            .Select(x => x.Id)
 1223            .ToHashSet(StringComparer.OrdinalIgnoreCase);
 224    }
 225
 10226    private static bool Matches(string? candidate, string? search) => !string.IsNullOrEmpty(candidate) && !string.IsNull
 227}