Implement new request trace identifier format
The format: The trace identifier begins with connection ID and ends with a number that increments with each request per connection. Example: Connection ID = xyz Request 1 = "xyz:00000001" Request 2 = "xyz:00000002" ... Request 15 = "xyz:0000000F" Request 16 = "xyz:00000010"
This commit is contained in:
parent
3375cf0e28
commit
4dc7946cd8
|
|
@ -73,6 +73,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
private string _requestId;
|
private string _requestId;
|
||||||
private int _remainingRequestHeadersBytesAllowed;
|
private int _remainingRequestHeadersBytesAllowed;
|
||||||
private int _requestHeadersParsed;
|
private int _requestHeadersParsed;
|
||||||
|
private uint _requestCount;
|
||||||
|
|
||||||
protected readonly long _keepAliveTicks;
|
protected readonly long _keepAliveTicks;
|
||||||
private readonly long _requestHeadersTimeoutTicks;
|
private readonly long _requestHeadersTimeoutTicks;
|
||||||
|
|
@ -128,7 +129,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
// don't generate an ID until it is requested
|
// don't generate an ID until it is requested
|
||||||
if (_requestId == null)
|
if (_requestId == null)
|
||||||
{
|
{
|
||||||
_requestId = CorrelationIdGenerator.GetNextId();
|
_requestId = StringUtilities.ConcatAsHexSuffix(ConnectionId, ':', _requestCount);
|
||||||
}
|
}
|
||||||
return _requestId;
|
return _requestId;
|
||||||
}
|
}
|
||||||
|
|
@ -388,6 +389,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
_requestHeadersParsed = 0;
|
_requestHeadersParsed = 0;
|
||||||
|
|
||||||
_responseBytesWritten = 0;
|
_responseBytesWritten = 0;
|
||||||
|
_requestCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
string key = new string('\0', keyLength);
|
string key = new string('\0', keyLength);
|
||||||
fixed (char* keyBuffer = key)
|
fixed (char* keyBuffer = key)
|
||||||
{
|
{
|
||||||
if (!AsciiUtilities.TryGetAsciiString(pKeyBytes, keyBuffer, keyLength))
|
if (!StringUtilities.TryGetAsciiString(pKeyBytes, keyBuffer, keyLength))
|
||||||
{
|
{
|
||||||
throw BadHttpRequestException.GetException(RequestRejectionReason.InvalidCharactersInHeaderName);
|
throw BadHttpRequestException.GetException(RequestRejectionReason.InvalidCharactersInHeaderName);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
{
|
{
|
||||||
// This version if AsciiUtilities returns null if there are any null (0 byte) characters
|
// This version if AsciiUtilities returns null if there are any null (0 byte) characters
|
||||||
// in the string
|
// in the string
|
||||||
if (!AsciiUtilities.TryGetAsciiString(buffer, output, span.Length))
|
if (!StringUtilities.TryGetAsciiString(buffer, output, span.Length))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException();
|
throw new InvalidOperationException();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
{
|
{
|
||||||
internal class AsciiUtilities
|
internal class StringUtilities
|
||||||
{
|
{
|
||||||
public static unsafe bool TryGetAsciiString(byte* input, char* output, int count)
|
public static unsafe bool TryGetAsciiString(byte* input, char* output, int count)
|
||||||
{
|
{
|
||||||
|
|
@ -75,5 +75,49 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
|
|
||||||
return isValid;
|
return isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly string _encode16Chars = "0123456789ABCDEF";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A faster version of String.Concat(<paramref name="str"/>, <paramref name="separator"/>, <paramref name="number"/>.ToString("X8"))
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="str"></param>
|
||||||
|
/// <param name="separator"></param>
|
||||||
|
/// <param name="number"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static unsafe string ConcatAsHexSuffix(string str, char separator, uint number)
|
||||||
|
{
|
||||||
|
var length = 1 + 8;
|
||||||
|
if (str != null)
|
||||||
|
{
|
||||||
|
length += str.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stackalloc to allocate array on stack rather than heap
|
||||||
|
char* charBuffer = stackalloc char[length];
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
if (str != null)
|
||||||
|
{
|
||||||
|
for (i = 0; i < str.Length; i++)
|
||||||
|
{
|
||||||
|
charBuffer[i] = str[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
charBuffer[i] = separator;
|
||||||
|
|
||||||
|
charBuffer[i + 1] = _encode16Chars[(int)(number >> 28) & 0xF];
|
||||||
|
charBuffer[i + 2] = _encode16Chars[(int)(number >> 24) & 0xF];
|
||||||
|
charBuffer[i + 3] = _encode16Chars[(int)(number >> 20) & 0xF];
|
||||||
|
charBuffer[i + 4] = _encode16Chars[(int)(number >> 16) & 0xF];
|
||||||
|
charBuffer[i + 5] = _encode16Chars[(int)(number >> 12) & 0xF];
|
||||||
|
charBuffer[i + 6] = _encode16Chars[(int)(number >> 8) & 0xF];
|
||||||
|
charBuffer[i + 7] = _encode16Chars[(int)(number >> 4) & 0xF];
|
||||||
|
charBuffer[i + 8] = _encode16Chars[(int)number & 0xF];
|
||||||
|
|
||||||
|
// string ctor overload that takes char*
|
||||||
|
return new string(charBuffer, 0, length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -141,6 +141,31 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
Assert.NotEqual(nextId, secondId);
|
Assert.NotEqual(nextId, secondId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TraceIdentifierCountsRequestsPerFrame()
|
||||||
|
{
|
||||||
|
var connectionId = _frameContext.ConnectionId;
|
||||||
|
var feature = ((IFeatureCollection)_frame).Get<IHttpRequestIdentifierFeature>();
|
||||||
|
// Reset() is called once in the test ctor
|
||||||
|
var count = 1;
|
||||||
|
void Reset()
|
||||||
|
{
|
||||||
|
_frame.Reset();
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextId = feature.TraceIdentifier;
|
||||||
|
Assert.Equal($"{connectionId}:00000001", nextId);
|
||||||
|
|
||||||
|
Reset();
|
||||||
|
var secondId = feature.TraceIdentifier;
|
||||||
|
Assert.Equal($"{connectionId}:00000002", secondId);
|
||||||
|
|
||||||
|
var big = 1_000_000;
|
||||||
|
while (big-- > 0) Reset();
|
||||||
|
Assert.Equal($"{connectionId}:{count:X8}", feature.TraceIdentifier);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TraceIdentifierGeneratesWhenNull()
|
public void TraceIdentifierGeneratesWhenNull()
|
||||||
{
|
{
|
||||||
|
|
@ -149,7 +174,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
Assert.NotNull(id);
|
Assert.NotNull(id);
|
||||||
Assert.Equal(id, _frame.TraceIdentifier);
|
Assert.Equal(id, _frame.TraceIdentifier);
|
||||||
|
|
||||||
_frame.TraceIdentifier = null;
|
_frame.Reset();
|
||||||
Assert.NotEqual(id, _frame.TraceIdentifier);
|
Assert.NotEqual(id, _frame.TraceIdentifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
{
|
||||||
|
public class StringUtilitiesTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData(uint.MinValue)]
|
||||||
|
[InlineData(0xF)]
|
||||||
|
[InlineData(0xA)]
|
||||||
|
[InlineData(0xFF)]
|
||||||
|
[InlineData(0xFFC59)]
|
||||||
|
[InlineData(uint.MaxValue)]
|
||||||
|
public void ConvertsToHex(uint value)
|
||||||
|
{
|
||||||
|
var str = CorrelationIdGenerator.GetNextId();
|
||||||
|
Assert.Equal($"{str}:{value:X8}", StringUtilities.ConcatAsHexSuffix(str, ':', value));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void HandlesNull()
|
||||||
|
{
|
||||||
|
uint value = 0x23BC0234;
|
||||||
|
Assert.Equal(":23BC0234", StringUtilities.ConcatAsHexSuffix(null, ':', value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -611,13 +611,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task TraceIdentifierIsUnique()
|
public async Task TraceIdentifierIsUnique()
|
||||||
{
|
{
|
||||||
const int IdentifierLength = 13;
|
const int identifierLength = 22;
|
||||||
const int iterations = 10;
|
const int iterations = 10;
|
||||||
|
|
||||||
using (var server = new TestServer(async context =>
|
using (var server = new TestServer(async context =>
|
||||||
{
|
{
|
||||||
Assert.Equal(IdentifierLength, Encoding.ASCII.GetByteCount(context.TraceIdentifier));
|
Assert.Equal(identifierLength, Encoding.ASCII.GetByteCount(context.TraceIdentifier));
|
||||||
context.Response.ContentLength = IdentifierLength;
|
context.Response.ContentLength = identifierLength;
|
||||||
await context.Response.WriteAsync(context.TraceIdentifier);
|
await context.Response.WriteAsync(context.TraceIdentifier);
|
||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
|
|
@ -635,7 +635,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
// requests on same connection
|
// requests on same connection
|
||||||
using (var connection = server.CreateConnection())
|
using (var connection = server.CreateConnection())
|
||||||
{
|
{
|
||||||
var buffer = new char[IdentifierLength];
|
var buffer = new char[identifierLength];
|
||||||
for (var i = 0; i < iterations; i++)
|
for (var i = 0; i < iterations; i++)
|
||||||
{
|
{
|
||||||
await connection.Send("GET / HTTP/1.1",
|
await connection.Send("GET / HTTP/1.1",
|
||||||
|
|
@ -645,12 +645,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
|
|
||||||
await connection.Receive($"HTTP/1.1 200 OK",
|
await connection.Receive($"HTTP/1.1 200 OK",
|
||||||
$"Date: {server.Context.DateHeaderValue}",
|
$"Date: {server.Context.DateHeaderValue}",
|
||||||
$"Content-Length: {IdentifierLength}",
|
$"Content-Length: {identifierLength}",
|
||||||
"",
|
"",
|
||||||
"").TimeoutAfter(TimeSpan.FromSeconds(10));
|
"").TimeoutAfter(TimeSpan.FromSeconds(10));
|
||||||
|
|
||||||
var read = await connection.Reader.ReadAsync(buffer, 0, IdentifierLength);
|
var read = await connection.Reader.ReadAsync(buffer, 0, identifierLength);
|
||||||
Assert.Equal(IdentifierLength, read);
|
Assert.Equal(identifierLength, read);
|
||||||
var id = new string(buffer, 0, read);
|
var id = new string(buffer, 0, read);
|
||||||
Assert.DoesNotContain(id, usedIds.ToArray());
|
Assert.DoesNotContain(id, usedIds.ToArray());
|
||||||
usedIds.Add(id);
|
usedIds.Add(id);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||||
|
{
|
||||||
|
[Config(typeof(CoreConfig))]
|
||||||
|
public class StringUtilitiesBenchmark
|
||||||
|
{
|
||||||
|
private const int Iterations = 500_000;
|
||||||
|
|
||||||
|
[Benchmark(Baseline = true, OperationsPerInvoke = Iterations)]
|
||||||
|
public void UintToString()
|
||||||
|
{
|
||||||
|
var connectionId = CorrelationIdGenerator.GetNextId();
|
||||||
|
for (uint i = 0; i < Iterations; i++)
|
||||||
|
{
|
||||||
|
var id = connectionId + ':' + i.ToString("X8");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark(OperationsPerInvoke = Iterations)]
|
||||||
|
public void ConcatAsHexSuffix()
|
||||||
|
{
|
||||||
|
var connectionId = CorrelationIdGenerator.GetNextId();
|
||||||
|
for (uint i = 0; i < Iterations; i++)
|
||||||
|
{
|
||||||
|
var id = StringUtilities.ConcatAsHexSuffix(connectionId, ':', i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue