Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(storage): Use ToListAsync() instead of ToArrayAsync(), because ToArrayAsync() is a wrapper around ToListAsync() #35

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Mocha.Core/Storage/Jaeger/IJaegerSpanReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ namespace Mocha.Core.Storage.Jaeger;

public interface IJaegerSpanReader
{
Task<string[]> GetServicesAsync();
Task<IEnumerable<string>> GetServicesAsync();

Task<string[]> GetOperationsAsync(string serviceName);
Task<IEnumerable<string>> GetOperationsAsync(string serviceName);

Task<JaegerTrace[]> FindTracesAsync(JaegerTraceQueryParameters query);

Expand Down
183 changes: 91 additions & 92 deletions src/Mocha.Query.Jaeger/Controllers/TraceController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<JaegerResponse<IEnumerable<string>>> GetSeries()
{
[HttpGet("services")]
public async Task<JaegerResponse<string[]>> GetSeries()
{
return new(await spanReader.GetServicesAsync());
}
return new(await spanReader.GetServicesAsync());
}

[HttpGet("services/{serviceName}/operations")]
public async Task<JaegerResponse<string[]>> GetOperations(string serviceName)
{
return new(await spanReader.GetOperationsAsync(serviceName));
}
[HttpGet("services/{serviceName}/operations")]
public async Task<JaegerResponse<IEnumerable<string>>> GetOperations(string serviceName)
{
return new(await spanReader.GetOperationsAsync(serviceName));
}

[HttpGet("traces")]
public async Task<JaegerResponse<JaegerTrace[]>> FindTraces([FromQuery] FindTracesRequest request)
[HttpGet("traces")]
public async Task<JaegerResponse<JaegerTrace[]>> 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,
@"^((?<days>\d+)d)?((?<hours>\d+)h)?((?<minutes>\d+)m)?((?<seconds>\d+)s)?((?<milliseconds>\d+)ms)?((?<microseconds>\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,
@"^((?<days>\d+)d)?((?<hours>\d+)h)?((?<minutes>\d+)m)?((?<seconds>\d+)s)?((?<milliseconds>\d+)ms)?((?<microseconds>\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<Dictionary<string, object>>()!,
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<JaegerTrace[]>(traces) { Error = error };
if (lookBack.HasValue)
{
var now = DateTimeOffset.Now.ToUnixTimeNanoseconds();
startTimeMin = now - lookBack.Value;
startTimeMax = now;
}

[HttpGet("traces/{traceID}")]
public async Task<JaegerResponse<JaegerTrace[]>> 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<Dictionary<string, object>>()!,
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<JaegerTrace[]>(traces) { Error = error };
JaegerResponseError? error = null;
if (traces.Length == 0)
{
error = new JaegerResponseError { Code = (int)HttpStatusCode.NotFound, Message = "trace not found" };
}

return new JaegerResponse<JaegerTrace[]>(traces) { Error = error };
}

[HttpGet("traces/{traceID}")]
public async Task<JaegerResponse<JaegerTrace[]>> 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<JaegerTrace[]>(traces) { Error = error };
}
}
31 changes: 20 additions & 11 deletions src/Mocha.Storage/EntityFrameworkCore/Jaeger/EFJaegerSpanReader.cs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ToArrayAsync on line 27 has not been replaced.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've fixed it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create another pr if you want to update sth. else

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,33 @@ namespace Mocha.Storage.EntityFrameworkCore.Jaeger;

internal class EFJaegerSpanReader(IDbContextFactory<MochaContext> contextFactory) : IJaegerSpanReader
{
public async Task<string[]> GetServicesAsync()
public async Task<IEnumerable<string>> 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<string[]> GetOperationsAsync(string serviceName)
public async Task<IEnumerable<string>> 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<JaegerTrace[]> 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))
{
Expand Down Expand Up @@ -99,7 +104,7 @@ public async Task<JaegerTrace[]> FindTracesAsync(
{
await using var context = await contextFactory.CreateDbContextAsync();

var queryableSpans = context.Spans.AsQueryable();
var queryableSpans = context.Spans.AsNoTracking();

if (traceIDs?.Any() ?? false)
{
Expand All @@ -123,25 +128,29 @@ private static async Task<JaegerTrace[]> QueryJaegerTracesAsync(
IQueryable<EFSpan> 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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand Down