Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,13 @@ internal static Delegate FindBuilder(MulticastDelegate mcd)

internal static long TimerCurrent() => DateTime.UtcNow.ToFileTimeUtc();

internal static long FastTimerCurrent() => Environment.TickCount;

internal static uint CalculateTickCountElapsed(long startTick, long endTick)
{
return (uint)(endTick - startTick);
}

internal static long TimerFromSeconds(int seconds)
{
long result = checked((long)seconds * TimeSpan.TicksPerSecond);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ internal static ValueSqlStatisticsScope TimedScope(SqlStatistics statistics)
// internal values that are not exposed through properties
internal long _closeTimestamp;
internal long _openTimestamp;
internal long _startExecutionTimestamp;
internal long? _startExecutionTimestamp;
internal long _startFetchTimestamp;
internal long _startNetworkServerTimestamp;

Expand Down Expand Up @@ -80,7 +80,7 @@ internal bool WaitForDoneAfterRow

internal void ContinueOnNewConnection()
{
_startExecutionTimestamp = 0;
_startExecutionTimestamp = null;
_startFetchTimestamp = 0;
_waitForDoneAfterRow = false;
_waitForReply = false;
Expand Down Expand Up @@ -108,7 +108,7 @@ internal IDictionary GetDictionary()
{ "UnpreparedExecs", _unpreparedExecs },

{ "ConnectionTime", ADP.TimerToMilliseconds(_connectionTime) },
{ "ExecutionTime", ADP.TimerToMilliseconds(_executionTime) },
{ "ExecutionTime", _executionTime },
{ "NetworkServerTime", ADP.TimerToMilliseconds(_networkServerTime) }
};
Debug.Assert(dictionary.Count == Count);
Expand All @@ -117,17 +117,17 @@ internal IDictionary GetDictionary()

internal bool RequestExecutionTimer()
{
if (_startExecutionTimestamp == 0)
if (!_startExecutionTimestamp.HasValue)
{
_startExecutionTimestamp = ADP.TimerCurrent();
_startExecutionTimestamp = ADP.FastTimerCurrent();
return true;
}
return false;
}

internal void RequestNetworkServerTimer()
{
Debug.Assert(_startExecutionTimestamp != 0, "No network time expected outside execution period");
Debug.Assert(_startExecutionTimestamp.HasValue, "No network time expected outside execution period");
if (_startNetworkServerTimestamp == 0)
{
_startNetworkServerTimestamp = ADP.TimerCurrent();
Expand All @@ -137,10 +137,11 @@ internal void RequestNetworkServerTimer()

internal void ReleaseAndUpdateExecutionTimer()
{
if (_startExecutionTimestamp > 0)
if (_startExecutionTimestamp.HasValue)
{
_executionTime += (ADP.TimerCurrent() - _startExecutionTimestamp);
_startExecutionTimestamp = 0;
uint elapsed = ADP.CalculateTickCountElapsed(_startExecutionTimestamp.Value, ADP.FastTimerCurrent());
_executionTime += elapsed;
_startExecutionTimestamp = null;
}
}

Expand Down Expand Up @@ -176,7 +177,7 @@ internal void Reset()
_unpreparedExecs = 0;
_waitForDoneAfterRow = false;
_waitForReply = false;
_startExecutionTimestamp = 0;
_startExecutionTimestamp = null;
_startNetworkServerTimestamp = 0;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@ public static void Test(string srcConstr, string dstConstr, string dstTable)

Assert.True(0 < (long)stats["BytesReceived"], "BytesReceived is non-positive.");
Assert.True(0 < (long)stats["BytesSent"], "BytesSent is non-positive.");
Assert.True((long)stats["ConnectionTime"] >= (long)stats["ExecutionTime"], "Connection Time is less than Execution Time.");
Assert.True((long)stats["ExecutionTime"] >= (long)stats["NetworkServerTime"], "Execution Time is less than Network Server Time.");
DataTestUtility.AssertEqualsWithDescription((long)0, (long)stats["UnpreparedExecs"], "Non-zero UnpreparedExecs value: " + (long)stats["UnpreparedExecs"]);
DataTestUtility.AssertEqualsWithDescription((long)0, (long)stats["PreparedExecs"], "Non-zero PreparedExecs value: " + (long)stats["PreparedExecs"]);
DataTestUtility.AssertEqualsWithDescription((long)0, (long)stats["Prepares"], "Non-zero Prepares value: " + (long)stats["Prepares"]);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Microsoft.Data.Common;
using Xunit;

namespace Microsoft.Data.SqlClient.UnitTests;

/// <summary>
/// Tests for Environment.TickCount elapsed time calculation with wraparound handling.
/// </summary>
public sealed class TickCountElapsedTest
{
/// <summary>
/// Verifies that normal elapsed time calculation works correctly.
/// </summary>
[Fact]
public void CalculateTickCountElapsed_NormalCase_ReturnsCorrectElapsed()
{
uint elapsed = ADP.CalculateTickCountElapsed(1000, 1500);
Assert.Equal(500u, elapsed);
}

/// <summary>
/// Verifies that wraparound from int.MaxValue to int.MinValue is handled correctly.
/// </summary>
[Fact]
public void CalculateTickCountElapsed_MaxWraparound_ReturnsOne()
{
uint elapsed = ADP.CalculateTickCountElapsed(int.MaxValue, int.MinValue);
Assert.Equal(1u, elapsed);
}

/// <summary>
/// Verifies that partial wraparound scenarios work correctly.
/// </summary>
[Theory]
[InlineData(2147483600, -2147483600, 96u)]
[InlineData(2147483647, -2147483647, 2u)]
public void CalculateTickCountElapsed_PartialWraparound_ReturnsCorrectElapsed(long start, long end, uint expected)
{
uint elapsed = ADP.CalculateTickCountElapsed(start, end);
Assert.Equal(expected, elapsed);
}

/// <summary>
/// Verifies that zero elapsed time returns zero.
/// </summary>
[Fact]
public void CalculateTickCountElapsed_ZeroElapsed_ReturnsZero()
{
uint elapsed = ADP.CalculateTickCountElapsed(1000, 1000);
Assert.Equal(0u, elapsed);
}
}
Loading