Skip to content

Commit 1b1ddec

Browse files
authored
Optimization: Use Environment.TickCount for SqlStatistics execution timing (#3609)
1 parent fd16c6d commit 1b1ddec

File tree

4 files changed

+74
-12
lines changed

4 files changed

+74
-12
lines changed

src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,13 @@ internal static Delegate FindBuilder(MulticastDelegate mcd)
629629

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

632+
internal static long FastTimerCurrent() => Environment.TickCount;
633+
634+
internal static uint CalculateTickCountElapsed(long startTick, long endTick)
635+
{
636+
return (uint)(endTick - startTick);
637+
}
638+
632639
internal static long TimerFromSeconds(int seconds)
633640
{
634641
long result = checked((long)seconds * TimeSpan.TicksPerSecond);

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlStatistics.cs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ internal static ValueSqlStatisticsScope TimedScope(SqlStatistics statistics)
3838
// internal values that are not exposed through properties
3939
internal long _closeTimestamp;
4040
internal long _openTimestamp;
41-
internal long _startExecutionTimestamp;
41+
internal long? _startExecutionTimestamp;
4242
internal long _startFetchTimestamp;
4343
internal long _startNetworkServerTimestamp;
4444

@@ -80,7 +80,7 @@ internal bool WaitForDoneAfterRow
8080

8181
internal void ContinueOnNewConnection()
8282
{
83-
_startExecutionTimestamp = 0;
83+
_startExecutionTimestamp = null;
8484
_startFetchTimestamp = 0;
8585
_waitForDoneAfterRow = false;
8686
_waitForReply = false;
@@ -108,7 +108,7 @@ internal IDictionary GetDictionary()
108108
{ "UnpreparedExecs", _unpreparedExecs },
109109

110110
{ "ConnectionTime", ADP.TimerToMilliseconds(_connectionTime) },
111-
{ "ExecutionTime", ADP.TimerToMilliseconds(_executionTime) },
111+
{ "ExecutionTime", _executionTime },
112112
{ "NetworkServerTime", ADP.TimerToMilliseconds(_networkServerTime) }
113113
};
114114
Debug.Assert(dictionary.Count == Count);
@@ -117,17 +117,17 @@ internal IDictionary GetDictionary()
117117

118118
internal bool RequestExecutionTimer()
119119
{
120-
if (_startExecutionTimestamp == 0)
120+
if (!_startExecutionTimestamp.HasValue)
121121
{
122-
_startExecutionTimestamp = ADP.TimerCurrent();
122+
_startExecutionTimestamp = ADP.FastTimerCurrent();
123123
return true;
124124
}
125125
return false;
126126
}
127127

128128
internal void RequestNetworkServerTimer()
129129
{
130-
Debug.Assert(_startExecutionTimestamp != 0, "No network time expected outside execution period");
130+
Debug.Assert(_startExecutionTimestamp.HasValue, "No network time expected outside execution period");
131131
if (_startNetworkServerTimestamp == 0)
132132
{
133133
_startNetworkServerTimestamp = ADP.TimerCurrent();
@@ -137,10 +137,11 @@ internal void RequestNetworkServerTimer()
137137

138138
internal void ReleaseAndUpdateExecutionTimer()
139139
{
140-
if (_startExecutionTimestamp > 0)
140+
if (_startExecutionTimestamp.HasValue)
141141
{
142-
_executionTime += (ADP.TimerCurrent() - _startExecutionTimestamp);
143-
_startExecutionTimestamp = 0;
142+
uint elapsed = ADP.CalculateTickCountElapsed(_startExecutionTimestamp.Value, ADP.FastTimerCurrent());
143+
_executionTime += elapsed;
144+
_startExecutionTimestamp = null;
144145
}
145146
}
146147

@@ -176,7 +177,7 @@ internal void Reset()
176177
_unpreparedExecs = 0;
177178
_waitForDoneAfterRow = false;
178179
_waitForReply = false;
179-
_startExecutionTimestamp = 0;
180+
_startExecutionTimestamp = null;
180181
_startNetworkServerTimestamp = 0;
181182
}
182183

src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,6 @@ public static void Test(string srcConstr, string dstConstr, string dstTable)
5252

5353
Assert.True(0 < (long)stats["BytesReceived"], "BytesReceived is non-positive.");
5454
Assert.True(0 < (long)stats["BytesSent"], "BytesSent is non-positive.");
55-
Assert.True((long)stats["ConnectionTime"] >= (long)stats["ExecutionTime"], "Connection Time is less than Execution Time.");
56-
Assert.True((long)stats["ExecutionTime"] >= (long)stats["NetworkServerTime"], "Execution Time is less than Network Server Time.");
5755
DataTestUtility.AssertEqualsWithDescription((long)0, (long)stats["UnpreparedExecs"], "Non-zero UnpreparedExecs value: " + (long)stats["UnpreparedExecs"]);
5856
DataTestUtility.AssertEqualsWithDescription((long)0, (long)stats["PreparedExecs"], "Non-zero PreparedExecs value: " + (long)stats["PreparedExecs"]);
5957
DataTestUtility.AssertEqualsWithDescription((long)0, (long)stats["Prepares"], "Non-zero Prepares value: " + (long)stats["Prepares"]);
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using Microsoft.Data.Common;
6+
using Xunit;
7+
8+
namespace Microsoft.Data.SqlClient.UnitTests;
9+
10+
/// <summary>
11+
/// Tests for Environment.TickCount elapsed time calculation with wraparound handling.
12+
/// </summary>
13+
public sealed class TickCountElapsedTest
14+
{
15+
/// <summary>
16+
/// Verifies that normal elapsed time calculation works correctly.
17+
/// </summary>
18+
[Fact]
19+
public void CalculateTickCountElapsed_NormalCase_ReturnsCorrectElapsed()
20+
{
21+
uint elapsed = ADP.CalculateTickCountElapsed(1000, 1500);
22+
Assert.Equal(500u, elapsed);
23+
}
24+
25+
/// <summary>
26+
/// Verifies that wraparound from int.MaxValue to int.MinValue is handled correctly.
27+
/// </summary>
28+
[Fact]
29+
public void CalculateTickCountElapsed_MaxWraparound_ReturnsOne()
30+
{
31+
uint elapsed = ADP.CalculateTickCountElapsed(int.MaxValue, int.MinValue);
32+
Assert.Equal(1u, elapsed);
33+
}
34+
35+
/// <summary>
36+
/// Verifies that partial wraparound scenarios work correctly.
37+
/// </summary>
38+
[Theory]
39+
[InlineData(2147483600, -2147483600, 96u)]
40+
[InlineData(2147483647, -2147483647, 2u)]
41+
public void CalculateTickCountElapsed_PartialWraparound_ReturnsCorrectElapsed(long start, long end, uint expected)
42+
{
43+
uint elapsed = ADP.CalculateTickCountElapsed(start, end);
44+
Assert.Equal(expected, elapsed);
45+
}
46+
47+
/// <summary>
48+
/// Verifies that zero elapsed time returns zero.
49+
/// </summary>
50+
[Fact]
51+
public void CalculateTickCountElapsed_ZeroElapsed_ReturnsZero()
52+
{
53+
uint elapsed = ADP.CalculateTickCountElapsed(1000, 1000);
54+
Assert.Equal(0u, elapsed);
55+
}
56+
}

0 commit comments

Comments
 (0)