Skip to content

Commit

Permalink
Use higher-resolution durations for metrics. Fixes #1395
Browse files Browse the repository at this point in the history
  • Loading branch information
bgrainger committed Nov 15, 2023
1 parent 25106a5 commit 8d56d85
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 34 deletions.
26 changes: 13 additions & 13 deletions src/MySqlConnector/Core/ConnectionPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ internal sealed class ConnectionPool : IDisposable

public SslProtocols SslProtocols { get; set; }

public async ValueTask<ServerSession> GetSessionAsync(MySqlConnection connection, int startTickCount, int timeoutMilliseconds, Activity? activity, IOBehavior ioBehavior, CancellationToken cancellationToken)
public async ValueTask<ServerSession> GetSessionAsync(MySqlConnection connection, long startingTimestamp, int timeoutMilliseconds, Activity? activity, IOBehavior ioBehavior, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();

Expand Down Expand Up @@ -72,7 +72,7 @@ public async ValueTask<ServerSession> GetSessionAsync(MySqlConnection connection
if (ConnectionSettings.ConnectionReset || session.DatabaseOverride is not null)
{
if (timeoutMilliseconds != 0)
session.SetTimeout(Math.Max(1, timeoutMilliseconds - (Environment.TickCount - startTickCount)));
session.SetTimeout(Math.Max(1, timeoutMilliseconds - (int) Utility.GetElapsedMilliseconds(startingTimestamp)));
reuseSession = await session.TryResetConnectionAsync(ConnectionSettings, connection, ioBehavior, cancellationToken).ConfigureAwait(false);
session.SetTimeout(Constants.InfiniteTimeout);
}
Expand Down Expand Up @@ -103,14 +103,14 @@ public async ValueTask<ServerSession> GetSessionAsync(MySqlConnection connection
ActivitySourceHelper.CopyTags(session.ActivityTags, activity);
Log.ReturningPooledSession(m_logger, Id, session.Id, leasedSessionsCountPooled);

session.LastLeasedTicks = unchecked((uint) Environment.TickCount);
MetricsReporter.RecordWaitTime(this, unchecked(session.LastLeasedTicks - (uint) startTickCount));
session.LastLeasedTimestamp = Stopwatch.GetTimestamp();
MetricsReporter.RecordWaitTime(this, Utility.GetElapsedMilliseconds(startingTimestamp, session.LastLeasedTimestamp));
return session;
}
}

// create a new session
session = await ConnectSessionAsync(connection, s_createdNewSession, startTickCount, activity, ioBehavior, cancellationToken).ConfigureAwait(false);
session = await ConnectSessionAsync(connection, s_createdNewSession, startingTimestamp, activity, ioBehavior, cancellationToken).ConfigureAwait(false);
AdjustHostConnectionCount(session, 1);
session.OwningConnection = new(connection);
int leasedSessionsCountNew;
Expand All @@ -122,8 +122,8 @@ public async ValueTask<ServerSession> GetSessionAsync(MySqlConnection connection
MetricsReporter.AddUsed(this);
Log.ReturningNewSession(m_logger, Id, session.Id, leasedSessionsCountNew);

session.LastLeasedTicks = unchecked((uint) Environment.TickCount);
MetricsReporter.RecordCreateTime(this, unchecked(session.LastLeasedTicks - (uint) startTickCount));
session.LastLeasedTimestamp = Stopwatch.GetTimestamp();
MetricsReporter.RecordCreateTime(this, Utility.GetElapsedMilliseconds(startingTimestamp, session.LastLeasedTimestamp));
return session;
}
catch (Exception ex)
Expand Down Expand Up @@ -161,7 +161,7 @@ private int GetSessionHealth(ServerSession session)
if (session.PoolGeneration != m_generation)
return 2;
if (ConnectionSettings.ConnectionLifeTime > 0
&& unchecked((uint) Environment.TickCount) - session.CreatedTicks >= ConnectionSettings.ConnectionLifeTime)
&& Utility.GetElapsedMilliseconds(session.CreatedTimestamp) >= ConnectionSettings.ConnectionLifeTime)
return 3;

return 0;
Expand Down Expand Up @@ -214,7 +214,7 @@ public async Task ReapAsync(IOBehavior ioBehavior, CancellationToken cancellatio
{
Log.ReapingConnectionPool(m_logger, Id);
await RecoverLeakedSessionsAsync(ioBehavior).ConfigureAwait(false);
await CleanPoolAsync(ioBehavior, session => (unchecked((uint) Environment.TickCount) - session.LastReturnedTicks) / 1000 >= ConnectionSettings.ConnectionIdleTimeout, true, cancellationToken).ConfigureAwait(false);
await CleanPoolAsync(ioBehavior, session => Utility.GetElapsedMilliseconds(session.LastReturnedTimestamp) / 1000 >= ConnectionSettings.ConnectionIdleTimeout, true, cancellationToken).ConfigureAwait(false);
}

/// <summary>
Expand Down Expand Up @@ -403,7 +403,7 @@ private async Task CreateMinimumPooledSessions(MySqlConnection connection, IOBeh

try
{
var session = await ConnectSessionAsync(connection, s_createdToReachMinimumPoolSize, Environment.TickCount, null, ioBehavior, cancellationToken).ConfigureAwait(false);
var session = await ConnectSessionAsync(connection, s_createdToReachMinimumPoolSize, Stopwatch.GetTimestamp(), null, ioBehavior, cancellationToken).ConfigureAwait(false);
AdjustHostConnectionCount(session, 1);
lock (m_sessions)
m_sessions.AddFirst(session);
Expand All @@ -417,15 +417,15 @@ private async Task CreateMinimumPooledSessions(MySqlConnection connection, IOBeh
}
}

private async ValueTask<ServerSession> ConnectSessionAsync(MySqlConnection connection, Action<ILogger, int, string, Exception?> logMessage, int startTickCount, Activity? activity, IOBehavior ioBehavior, CancellationToken cancellationToken)
private async ValueTask<ServerSession> ConnectSessionAsync(MySqlConnection connection, Action<ILogger, int, string, Exception?> logMessage, long startingTimestamp, Activity? activity, IOBehavior ioBehavior, CancellationToken cancellationToken)
{
var session = new ServerSession(m_connectionLogger, this, m_generation, Interlocked.Increment(ref m_lastSessionId));
if (m_logger.IsEnabled(LogLevel.Debug))
logMessage(m_logger, Id, session.Id, null);
string? statusInfo;
try
{
statusInfo = await session.ConnectAsync(ConnectionSettings, connection, startTickCount, m_loadBalancer, activity, ioBehavior, cancellationToken).ConfigureAwait(false);
statusInfo = await session.ConnectAsync(ConnectionSettings, connection, startingTimestamp, m_loadBalancer, activity, ioBehavior, cancellationToken).ConfigureAwait(false);
}
catch (Exception)
{
Expand All @@ -452,7 +452,7 @@ private async ValueTask<ServerSession> ConnectSessionAsync(MySqlConnection conne
var redirectedSession = new ServerSession(m_connectionLogger, this, m_generation, Interlocked.Increment(ref m_lastSessionId));
try
{
await redirectedSession.ConnectAsync(redirectedSettings, connection, startTickCount, m_loadBalancer, activity, ioBehavior, cancellationToken).ConfigureAwait(false);
await redirectedSession.ConnectAsync(redirectedSettings, connection, startingTimestamp, m_loadBalancer, activity, ioBehavior, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
Expand Down
6 changes: 3 additions & 3 deletions src/MySqlConnector/Core/MetricsReporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ internal static class MetricsReporter
public static void RemoveIdle(ConnectionPool pool) => s_connectionsUsageCounter.Add(-1, pool.IdleStateTagList);
public static void AddUsed(ConnectionPool pool) => s_connectionsUsageCounter.Add(1, pool.UsedStateTagList);
public static void RemoveUsed(ConnectionPool pool) => s_connectionsUsageCounter.Add(-1, pool.UsedStateTagList);
public static void RecordCreateTime(ConnectionPool pool, uint ticks) => s_createTimeHistory.Record(ticks, pool.PoolNameTagList);
public static void RecordUseTime(ConnectionPool pool, uint ticks) => s_useTimeHistory.Record(ticks, pool.PoolNameTagList);
public static void RecordWaitTime(ConnectionPool pool, uint ticks) => s_waitTimeHistory.Record(ticks, pool.PoolNameTagList);
public static void RecordCreateTime(ConnectionPool pool, float milliseconds) => s_createTimeHistory.Record(milliseconds, pool.PoolNameTagList);
public static void RecordUseTime(ConnectionPool pool, float milliseconds) => s_useTimeHistory.Record(milliseconds, pool.PoolNameTagList);
public static void RecordWaitTime(ConnectionPool pool, float milliseconds) => s_waitTimeHistory.Record(milliseconds, pool.PoolNameTagList);

public static void AddPendingRequest(ConnectionPool? pool)
{
Expand Down
24 changes: 12 additions & 12 deletions src/MySqlConnector/Core/ServerSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public ServerSession(ILogger logger, ConnectionPool? pool, int poolGeneration, i
m_payloadCache = new();
Id = (pool?.Id ?? 0) + "." + id;
ServerVersion = ServerVersion.Empty;
CreatedTicks = unchecked((uint) Environment.TickCount);
CreatedTimestamp = Stopwatch.GetTimestamp();
Pool = pool;
PoolGeneration = poolGeneration;
HostName = "";
Expand All @@ -54,11 +54,11 @@ public ServerSession(ILogger logger, ConnectionPool? pool, int poolGeneration, i
public int CancellationTimeout { get; private set; }
public int ConnectionId { get; set; }
public byte[]? AuthPluginData { get; set; }
public uint CreatedTicks { get; }
public long CreatedTimestamp { get; }
public ConnectionPool? Pool { get; }
public int PoolGeneration { get; }
public uint LastLeasedTicks { get; set; }
public uint LastReturnedTicks { get; private set; }
public long LastLeasedTimestamp { get; set; }
public long LastReturnedTimestamp { get; private set; }
public string? DatabaseOverride { get; set; }
public string HostName { get; private set; }
public IPEndPoint? IPEndPoint => m_tcpClient?.Client.RemoteEndPoint as IPEndPoint;
Expand All @@ -75,11 +75,11 @@ public ServerSession(ILogger logger, ConnectionPool? pool, int poolGeneration, i
public ValueTask ReturnToPoolAsync(IOBehavior ioBehavior, MySqlConnection? owningConnection)
{
Log.ReturningToPool(m_logger, Id, Pool?.Id ?? 0);
LastReturnedTicks = unchecked((uint) Environment.TickCount);
LastReturnedTimestamp = Stopwatch.GetTimestamp();
if (Pool is null)
return default;
MetricsReporter.RecordUseTime(Pool, unchecked(LastReturnedTicks - LastLeasedTicks));
LastLeasedTicks = 0;
MetricsReporter.RecordUseTime(Pool, Utility.GetElapsedMilliseconds(LastLeasedTimestamp, LastReturnedTimestamp));
LastLeasedTimestamp = 0;
return Pool.ReturnAsync(ioBehavior, this);
}

Expand Down Expand Up @@ -392,7 +392,7 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella
m_state = State.Closed;
}

public async Task<string?> ConnectAsync(ConnectionSettings cs, MySqlConnection connection, int startTickCount, ILoadBalancer? loadBalancer, Activity? activity, IOBehavior ioBehavior, CancellationToken cancellationToken)
public async Task<string?> ConnectAsync(ConnectionSettings cs, MySqlConnection connection, long startingTimestamp, ILoadBalancer? loadBalancer, Activity? activity, IOBehavior ioBehavior, CancellationToken cancellationToken)
{
string? statusInfo = null;

Expand Down Expand Up @@ -445,7 +445,7 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella
else if (cs.ConnectionProtocol == MySqlConnectionProtocol.UnixSocket)
connected = await OpenUnixSocketAsync(cs, activity, ioBehavior, cancellationToken).ConfigureAwait(false);
else if (cs.ConnectionProtocol == MySqlConnectionProtocol.NamedPipe)
connected = await OpenNamedPipeAsync(cs, startTickCount, activity, ioBehavior, cancellationToken).ConfigureAwait(false);
connected = await OpenNamedPipeAsync(cs, startingTimestamp, activity, ioBehavior, cancellationToken).ConfigureAwait(false);
if (!connected)
{
lock (m_lock)
Expand All @@ -456,7 +456,7 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella

var byteHandler = m_socket is null ? new StreamByteHandler(m_stream!) : (IByteHandler) new SocketByteHandler(m_socket);
if (cs.ConnectionTimeout != 0)
byteHandler.RemainingTimeout = Math.Max(1, cs.ConnectionTimeoutMilliseconds - unchecked(Environment.TickCount - startTickCount));
byteHandler.RemainingTimeout = Math.Max(1, cs.ConnectionTimeoutMilliseconds - (int) Utility.GetElapsedMilliseconds(startingTimestamp));
m_payloadHandler = new StandardPayloadHandler(byteHandler);

payload = await ReceiveAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
Expand Down Expand Up @@ -1200,7 +1200,7 @@ private async Task<bool> OpenUnixSocketAsync(ConnectionSettings cs, Activity? ac
return false;
}

private async Task<bool> OpenNamedPipeAsync(ConnectionSettings cs, int startTickCount, Activity? activity, IOBehavior ioBehavior, CancellationToken cancellationToken)
private async Task<bool> OpenNamedPipeAsync(ConnectionSettings cs, long startingTimestamp, Activity? activity, IOBehavior ioBehavior, CancellationToken cancellationToken)
{
Log.ConnectingToNamedPipe(m_logger, Id, cs.PipeName, cs.HostNames![0]);

Expand All @@ -1218,7 +1218,7 @@ private async Task<bool> OpenNamedPipeAsync(ConnectionSettings cs, int startTick
}

var namedPipeStream = new NamedPipeClientStream(cs.HostNames![0], cs.PipeName, PipeDirection.InOut, PipeOptions.Asynchronous);
var timeout = Math.Max(1, cs.ConnectionTimeoutMilliseconds - unchecked(Environment.TickCount - startTickCount));
var timeout = Math.Max(1, cs.ConnectionTimeoutMilliseconds - (int) Utility.GetElapsedMilliseconds(startingTimestamp));
try
{
using (cancellationToken.Register(namedPipeStream.Dispose))
Expand Down
12 changes: 6 additions & 6 deletions src/MySqlConnector/MySqlConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ private async ValueTask<bool> PingAsync(IOBehavior ioBehavior, CancellationToken

internal async Task OpenAsync(IOBehavior? ioBehavior, CancellationToken cancellationToken)
{
var openStartTickCount = Environment.TickCount;
var openStartingTimestamp = Stopwatch.GetTimestamp();

VerifyNotDisposed();
cancellationToken.ThrowIfCancellationRequested();
Expand Down Expand Up @@ -416,7 +416,7 @@ internal async Task OpenAsync(IOBehavior? ioBehavior, CancellationToken cancella

try
{
m_session = await CreateSessionAsync(pool, openStartTickCount, activity, ioBehavior, cancellationToken).ConfigureAwait(false);
m_session = await CreateSessionAsync(pool, openStartingTimestamp, activity, ioBehavior, cancellationToken).ConfigureAwait(false);
m_hasBeenOpened = true;
SetState(ConnectionState.Open);
}
Expand Down Expand Up @@ -894,7 +894,7 @@ internal void FinishQuerying(bool hasWarnings)
}
}

private async ValueTask<ServerSession> CreateSessionAsync(ConnectionPool? pool, int startTickCount, Activity? activity, IOBehavior? ioBehavior, CancellationToken cancellationToken)
private async ValueTask<ServerSession> CreateSessionAsync(ConnectionPool? pool, long startingTimestamp, Activity? activity, IOBehavior? ioBehavior, CancellationToken cancellationToken)
{
MetricsReporter.AddPendingRequest(pool);
var connectionSettings = GetInitializedConnectionSettings();
Expand All @@ -907,7 +907,7 @@ private async ValueTask<ServerSession> CreateSessionAsync(ConnectionPool? pool,
// the cancellation token for connection is controlled by 'cancellationToken' (if it can be cancelled), ConnectionTimeout
// (from the connection string, if non-zero), or a combination of both
if (connectionSettings.ConnectionTimeout != 0)
timeoutSource = new CancellationTokenSource(TimeSpan.FromMilliseconds(Math.Max(1, connectionSettings.ConnectionTimeoutMilliseconds - unchecked(Environment.TickCount - startTickCount))));
timeoutSource = new CancellationTokenSource(TimeSpan.FromMilliseconds(Math.Max(1, connectionSettings.ConnectionTimeoutMilliseconds - (int) Utility.GetElapsedMilliseconds(startingTimestamp))));
if (cancellationToken.CanBeCanceled && timeoutSource is not null)
linkedSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutSource.Token);
var connectToken = linkedSource?.Token ?? timeoutSource?.Token ?? cancellationToken;
Expand All @@ -916,7 +916,7 @@ private async ValueTask<ServerSession> CreateSessionAsync(ConnectionPool? pool,
if (pool is not null)
{
// this returns an open session
return await pool.GetSessionAsync(this, startTickCount, connectionSettings.ConnectionTimeoutMilliseconds, activity, actualIOBehavior, connectToken).ConfigureAwait(false);
return await pool.GetSessionAsync(this, startingTimestamp, connectionSettings.ConnectionTimeoutMilliseconds, activity, actualIOBehavior, connectToken).ConfigureAwait(false);
}
else
{
Expand All @@ -929,7 +929,7 @@ private async ValueTask<ServerSession> CreateSessionAsync(ConnectionPool? pool,
Log.CreatedNonPooledSession(m_logger, session.Id);
try
{
await session.ConnectAsync(connectionSettings, this, startTickCount, loadBalancer, activity, actualIOBehavior, connectToken).ConfigureAwait(false);
await session.ConnectAsync(connectionSettings, this, startingTimestamp, loadBalancer, activity, actualIOBehavior, connectToken).ConfigureAwait(false);
return session;
}
catch (Exception)
Expand Down
21 changes: 21 additions & 0 deletions src/MySqlConnector/Utilities/Utility.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Buffers;
using System.Buffers.Text;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Reflection;
Expand Down Expand Up @@ -564,6 +565,26 @@ public static void GetOSDetails(out string? os, out string osDescription, out st
}
#endif

/// <summary>
/// Gets the elapsed time (in milliseconds) since the specified <paramref name="startingTimestamp"/> (which must be a value returned from <see cref="Stopwatch.GetTimestamp"/>.
/// </summary>
public static float GetElapsedMilliseconds(long startingTimestamp) =>
#if NET7_0_OR_GREATER
(float) Stopwatch.GetElapsedTime(startingTimestamp).TotalMilliseconds;
#else
GetElapsedMilliseconds(startingTimestamp, Stopwatch.GetTimestamp());
#endif

/// <summary>
/// Gets the elapsed time (in milliseconds) between the specified <paramref name="startingTimestamp"/> and <paramref name="endingTimestamp"/>. (These must be values returned from <see cref="Stopwatch.GetTimestamp"/>.)
/// </summary>
public static float GetElapsedMilliseconds(long startingTimestamp, long endingTimestamp) =>
#if NET7_0_OR_GREATER
(float) Stopwatch.GetElapsedTime(startingTimestamp, endingTimestamp).TotalMilliseconds;
#else
(endingTimestamp - startingTimestamp) * 1000.0f / Stopwatch.Frequency;
#endif

#if NET462
public static SslProtocols GetDefaultSslProtocols()
{
Expand Down

0 comments on commit 8d56d85

Please sign in to comment.