diff --git a/src/Mocha.Core/Storage/Jaeger/IJaegerSpanReader.cs b/src/Mocha.Core/Storage/Jaeger/IJaegerSpanReader.cs index 24c8c43..0f4beaf 100644 --- a/src/Mocha.Core/Storage/Jaeger/IJaegerSpanReader.cs +++ b/src/Mocha.Core/Storage/Jaeger/IJaegerSpanReader.cs @@ -7,9 +7,9 @@ namespace Mocha.Core.Storage.Jaeger; public interface IJaegerSpanReader { - Task GetServicesAsync(); + Task> GetServicesAsync(); - Task GetOperationsAsync(string serviceName); + Task> GetOperationsAsync(string serviceName); Task FindTracesAsync(JaegerTraceQueryParameters query); diff --git a/src/Mocha.Query.Jaeger/Controllers/TraceController.cs b/src/Mocha.Query.Jaeger/Controllers/TraceController.cs index 6ad6ada..9412fdc 100644 --- a/src/Mocha.Query.Jaeger/Controllers/TraceController.cs +++ b/src/Mocha.Query.Jaeger/Controllers/TraceController.cs @@ -6,119 +6,118 @@ using Mocha.Core.Storage.Jaeger.Trace; using Mocha.Query.Jaeger.DTOs; -namespace Mocha.Query.Jaeger.Controllers +namespace Mocha.Query.Jaeger.Controllers; + +[Route("/api")] +public class TraceController(IJaegerSpanReader spanReader) : Controller { - [Route("/api")] - public class TraceController(IJaegerSpanReader spanReader) : Controller + [HttpGet("services")] + public async Task>> GetSeries() { - [HttpGet("services")] - public async Task> GetSeries() - { - return new(await spanReader.GetServicesAsync()); - } + return new(await spanReader.GetServicesAsync()); + } - [HttpGet("services/{serviceName}/operations")] - public async Task> GetOperations(string serviceName) - { - return new(await spanReader.GetOperationsAsync(serviceName)); - } + [HttpGet("services/{serviceName}/operations")] + public async Task>> GetOperations(string serviceName) + { + return new(await spanReader.GetOperationsAsync(serviceName)); + } - [HttpGet("traces")] - public async Task> FindTraces([FromQuery] FindTracesRequest request) + [HttpGet("traces")] + public async Task> FindTraces([FromQuery] FindTracesRequest request) + { + static ulong? ParseAsNanoseconds(string? input) { - static ulong? ParseAsNanoseconds(string? input) + if (string.IsNullOrWhiteSpace(input)) { - if (string.IsNullOrWhiteSpace(input)) - { - return null; - } - - var m = Regex.Match(input, - @"^((?\d+)d)?((?\d+)h)?((?\d+)m)?((?\d+)s)?((?\d+)ms)?((?\d+)μs)?$", - RegexOptions.ExplicitCapture - | RegexOptions.Compiled - | RegexOptions.CultureInvariant - | RegexOptions.RightToLeft); - - if (!m.Success) - { - return null; - } - - var days = m.Groups["days"].Success ? long.Parse(m.Groups["days"].Value) : 0; - var hours = m.Groups["hours"].Success ? long.Parse(m.Groups["hours"].Value) : 0; - var minutes = m.Groups["minutes"].Success ? long.Parse(m.Groups["minutes"].Value) : 0; - var seconds = m.Groups["seconds"].Success ? long.Parse(m.Groups["seconds"].Value) : 0; - var milliseconds = m.Groups["milliseconds"].Success ? long.Parse(m.Groups["milliseconds"].Value) : 0; - var microseconds = m.Groups["microseconds"].Success ? long.Parse(m.Groups["microseconds"].Value) : 0; - - return - (ulong)(((days * 24 * 60 * 60 + hours * 60 * 60 + minutes * 60 + seconds) * 1000 + milliseconds) - * 1000 + microseconds) * 1000; + return null; } - var startTimeMin = request.Start * 1000; + var m = Regex.Match(input, + @"^((?\d+)d)?((?\d+)h)?((?\d+)m)?((?\d+)s)?((?\d+)ms)?((?\d+)μs)?$", + RegexOptions.ExplicitCapture + | RegexOptions.Compiled + | RegexOptions.CultureInvariant + | RegexOptions.RightToLeft); + if (!m.Success) + { + return null; + } - var startTimeMax = request.End * 1000; + var days = m.Groups["days"].Success ? long.Parse(m.Groups["days"].Value) : 0; + var hours = m.Groups["hours"].Success ? long.Parse(m.Groups["hours"].Value) : 0; + var minutes = m.Groups["minutes"].Success ? long.Parse(m.Groups["minutes"].Value) : 0; + var seconds = m.Groups["seconds"].Success ? long.Parse(m.Groups["seconds"].Value) : 0; + var milliseconds = m.Groups["milliseconds"].Success ? long.Parse(m.Groups["milliseconds"].Value) : 0; + var microseconds = m.Groups["microseconds"].Success ? long.Parse(m.Groups["microseconds"].Value) : 0; - var lookBack = ParseAsNanoseconds(request.LookBack); + return + (ulong)(((days * 24 * 60 * 60 + hours * 60 * 60 + minutes * 60 + seconds) * 1000 + milliseconds) + * 1000 + microseconds) * 1000; + } - if (lookBack.HasValue) - { - var now = DateTimeOffset.Now.ToUnixTimeNanoseconds(); - startTimeMin = now - lookBack.Value; - startTimeMax = now; - } + var startTimeMin = request.Start * 1000; - JaegerTrace[] traces; - if (request.TraceID?.Any() ?? false) - { - traces = await spanReader.FindTracesAsync(request.TraceID, startTimeMin, startTimeMax); - } - else - { - traces = await spanReader.FindTracesAsync(new JaegerTraceQueryParameters - { - ServiceName = request.Service, - OperationName = request.Operation, - Tags = (request.Tags ?? "{}").FromJson>()!, - StartTimeMinUnixNano = startTimeMin, - StartTimeMaxUnixNano = startTimeMax, - DurationMinNanoseconds = - string.IsNullOrWhiteSpace(request.MinDuration) - ? null - : ParseAsNanoseconds(request.MinDuration)!, - DurationMaxNanoseconds = - string.IsNullOrWhiteSpace(request.MaxDuration) - ? null - : ParseAsNanoseconds(request.MaxDuration)!, - NumTraces = request.Limit - }); - } + var startTimeMax = request.End * 1000; - JaegerResponseError? error = null; - if (traces.Length == 0) - { - error = new JaegerResponseError { Code = (int)HttpStatusCode.NotFound, Message = "trace not found" }; - } + var lookBack = ParseAsNanoseconds(request.LookBack); - return new JaegerResponse(traces) { Error = error }; + if (lookBack.HasValue) + { + var now = DateTimeOffset.Now.ToUnixTimeNanoseconds(); + startTimeMin = now - lookBack.Value; + startTimeMax = now; } - [HttpGet("traces/{traceID}")] - public async Task> GetTrace(string traceID) - { - var traces = await spanReader.FindTracesAsync([traceID]); + JaegerTrace[] traces; - JaegerResponseError? error = null; - if (traces.Length == 0) + if (request.TraceID?.Any() ?? false) + { + traces = await spanReader.FindTracesAsync(request.TraceID, startTimeMin, startTimeMax); + } + else + { + traces = await spanReader.FindTracesAsync(new JaegerTraceQueryParameters { - error = new JaegerResponseError { Code = (int)HttpStatusCode.NotFound, Message = "trace not found" }; - } + ServiceName = request.Service, + OperationName = request.Operation, + Tags = (request.Tags ?? "{}").FromJson>()!, + StartTimeMinUnixNano = startTimeMin, + StartTimeMaxUnixNano = startTimeMax, + DurationMinNanoseconds = + string.IsNullOrWhiteSpace(request.MinDuration) + ? null + : ParseAsNanoseconds(request.MinDuration)!, + DurationMaxNanoseconds = + string.IsNullOrWhiteSpace(request.MaxDuration) + ? null + : ParseAsNanoseconds(request.MaxDuration)!, + NumTraces = request.Limit + }); + } - return new JaegerResponse(traces) { Error = error }; + JaegerResponseError? error = null; + if (traces.Length == 0) + { + error = new JaegerResponseError { Code = (int)HttpStatusCode.NotFound, Message = "trace not found" }; } + + return new JaegerResponse(traces) { Error = error }; + } + + [HttpGet("traces/{traceID}")] + public async Task> GetTrace(string traceID) + { + var traces = await spanReader.FindTracesAsync([traceID]); + + JaegerResponseError? error = null; + if (traces.Length == 0) + { + error = new JaegerResponseError { Code = (int)HttpStatusCode.NotFound, Message = "trace not found" }; + } + + return new JaegerResponse(traces) { Error = error }; } } diff --git a/src/Mocha.Storage/EntityFrameworkCore/Jaeger/EFJaegerSpanReader.cs b/src/Mocha.Storage/EntityFrameworkCore/Jaeger/EFJaegerSpanReader.cs index 1b87190..d11d8db 100644 --- a/src/Mocha.Storage/EntityFrameworkCore/Jaeger/EFJaegerSpanReader.cs +++ b/src/Mocha.Storage/EntityFrameworkCore/Jaeger/EFJaegerSpanReader.cs @@ -10,28 +10,33 @@ namespace Mocha.Storage.EntityFrameworkCore.Jaeger; internal class EFJaegerSpanReader(IDbContextFactory contextFactory) : IJaegerSpanReader { - public async Task GetServicesAsync() + public async Task> GetServicesAsync() { await using var context = await contextFactory.CreateDbContextAsync(); - var services = await context.Spans.Select(s => s.ServiceName).Distinct().ToArrayAsync(); + var services = await context.Spans + .AsNoTracking() + .Select(s => s.ServiceName) + .Distinct() + .ToListAsync(); return services; } - public async Task GetOperationsAsync(string serviceName) + public async Task> GetOperationsAsync(string serviceName) { await using var context = await contextFactory.CreateDbContextAsync(); var operations = await context.Spans + .AsNoTracking() .Where(s => s.ServiceName == serviceName) .Select(s => s.SpanName) .Distinct() - .ToArrayAsync(); + .ToListAsync(); return operations; } public async Task FindTracesAsync(JaegerTraceQueryParameters query) { await using var context = await contextFactory.CreateDbContextAsync(); - var queryableSpans = context.Spans.AsQueryable(); + var queryableSpans = context.Spans.AsNoTracking(); if (!string.IsNullOrEmpty(query.ServiceName)) { @@ -99,7 +104,7 @@ public async Task FindTracesAsync( { await using var context = await contextFactory.CreateDbContextAsync(); - var queryableSpans = context.Spans.AsQueryable(); + var queryableSpans = context.Spans.AsNoTracking(); if (traceIDs?.Any() ?? false) { @@ -123,25 +128,29 @@ private static async Task QueryJaegerTracesAsync( IQueryable queryableSpans, MochaContext context) { - var spans = await queryableSpans.ToArrayAsync(); + var spans = await queryableSpans.ToListAsync(); var spanIds = spans.Select(s => s.SpanId).ToArray(); var spanAttributes = await context.SpanAttributes + .AsNoTracking() .Where(a => spanIds.Contains(a.SpanId)) - .ToArrayAsync(); + .ToListAsync(); var resourceAttributes = await context.ResourceAttributes + .AsNoTracking() .Where(a => spanIds.Contains(a.SpanId)) - .ToArrayAsync(); + .ToListAsync(); var spanEvents = await context.SpanEvents + .AsNoTracking() .Where(e => spanIds.Contains(e.SpanId)) - .ToArrayAsync(); + .ToListAsync(); var spanEventAttributes = await context.SpanEventAttributes + .AsNoTracking() .Where(a => spanIds.Contains(a.SpanId)) - .ToArrayAsync(); + .ToListAsync(); var jaegerTraces = spans.ToJaegerTraces( spanAttributes, resourceAttributes, spanEvents, spanEventAttributes).ToArray(); diff --git a/src/Mocha.Storage/EntityFrameworkCore/Jaeger/EFToJaegerSpanConversionExtensions.cs b/src/Mocha.Storage/EntityFrameworkCore/Jaeger/EFToJaegerSpanConversionExtensions.cs index 9f363a6..328620d 100644 --- a/src/Mocha.Storage/EntityFrameworkCore/Jaeger/EFToJaegerSpanConversionExtensions.cs +++ b/src/Mocha.Storage/EntityFrameworkCore/Jaeger/EFToJaegerSpanConversionExtensions.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Core Community under one or more agreements. // The .NET Core Community licenses this file to you under the MIT license. -using Mocha.Core.Extensions; using Mocha.Core.Storage.Jaeger.Trace; using Mocha.Storage.EntityFrameworkCore.Trace;