Skip to content

Commit b68f44a

Browse files
authored
Merge | Port XEvents activity ID correlation to .NET Core (#3373)
* Port XEvents activity ID correlation to .NET Core * TdsParser style cleanup * .NET 8.0 tests: remove interface from ref struct Also add comment to TdsParser referencing MS-TDS specification
1 parent 4ce9c83 commit b68f44a

File tree

6 files changed

+243
-72
lines changed

6 files changed

+243
-72
lines changed

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11366,25 +11366,39 @@ private void WriteRPCBatchHeaders(TdsParserStateObject stateObj, SqlNotification
1136611366

1136711367
int notificationHeaderSize = GetNotificationHeaderSize(notificationRequest);
1136811368

11369-
const int marsHeaderSize = 18; // 4 + 2 + 8 + 4
11369+
const int MarsHeaderSize = 18; // 4 + 2 + 8 + 4
1137011370

11371-
int totalHeaderLength = 4 + marsHeaderSize + notificationHeaderSize;
11371+
// Header Length (DWORD)
11372+
// Header Type (ushort)
11373+
// Trace Data Guid
11374+
// Trace Data Sequence Number (uint)
11375+
const int TraceHeaderSize = 26; // 4 + 2 + sizeof(Guid) + sizeof(uint);
11376+
11377+
// TotalLength - DWORD - including all headers and lengths, including itself
11378+
int totalHeaderLength = IncludeTraceHeader ? (4 + MarsHeaderSize + notificationHeaderSize + TraceHeaderSize) : (4 + MarsHeaderSize + notificationHeaderSize);
1137211379
Debug.Assert(stateObj._outBytesUsed == stateObj._outputHeaderLen, "Output bytes written before total header length");
1137311380
// Write total header length
1137411381
WriteInt(totalHeaderLength, stateObj);
1137511382

11376-
// Write Mars header length
11377-
WriteInt(marsHeaderSize, stateObj);
11378-
// Write Mars header data
11383+
// Write MARS header length and data
11384+
WriteInt(MarsHeaderSize, stateObj);
1137911385
WriteMarsHeaderData(stateObj, CurrentTransaction);
1138011386

11381-
if (0 != notificationHeaderSize)
11387+
if (notificationHeaderSize != 0)
1138211388
{
11383-
// Write Notification header length
11389+
// Write notification header length and data
11390+
// MS-TDS 2.2.5.3.1: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/e168d373-a7b7-41aa-b6ca-25985466a7e0
1138411391
WriteInt(notificationHeaderSize, stateObj);
11385-
// Write notificaiton header data
1138611392
WriteQueryNotificationHeaderData(notificationRequest, stateObj);
1138711393
}
11394+
11395+
if (IncludeTraceHeader)
11396+
{
11397+
// Write trace header length and data
11398+
// MS-TDS 2.2.5.3.3: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/6e9f106b-df6e-4cbe-a6eb-45ceb10c63be
11399+
WriteInt(TraceHeaderSize, stateObj);
11400+
WriteTraceHeaderData(stateObj);
11401+
}
1138811402
}
1138911403

1139011404

src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11554,39 +11554,37 @@ private void WriteRPCBatchHeaders(TdsParserStateObject stateObj, SqlNotification
1155411554

1155511555
int notificationHeaderSize = GetNotificationHeaderSize(notificationRequest);
1155611556

11557-
const int marsHeaderSize = 18; // 4 + 2 + 8 + 4
11557+
const int MarsHeaderSize = 18; // 4 + 2 + 8 + 4
1155811558

1155911559
// Header Length (DWORD)
1156011560
// Header Type (ushort)
1156111561
// Trace Data Guid
1156211562
// Trace Data Sequence Number (uint)
11563-
const int traceHeaderSize = 26; // 4 + 2 + GUID_SIZE + sizeof(UInt32);
11563+
const int TraceHeaderSize = 26; // 4 + 2 + sizeof(Guid) + sizeof(uint);
1156411564

1156511565
// TotalLength - DWORD - including all headers and lengths, including itself
11566-
int totalHeaderLength = this.IncludeTraceHeader ? (4 + marsHeaderSize + notificationHeaderSize + traceHeaderSize) : (4 + marsHeaderSize + notificationHeaderSize);
11566+
int totalHeaderLength = IncludeTraceHeader ? (4 + MarsHeaderSize + notificationHeaderSize + TraceHeaderSize) : (4 + MarsHeaderSize + notificationHeaderSize);
1156711567
Debug.Assert(stateObj._outBytesUsed == stateObj._outputHeaderLen, "Output bytes written before total header length");
1156811568
// Write total header length
1156911569
WriteInt(totalHeaderLength, stateObj);
1157011570

11571-
// Write Mars header length
11572-
WriteInt(marsHeaderSize, stateObj);
11573-
// Write Mars header data
11571+
// Write MARS header length and data
11572+
WriteInt(MarsHeaderSize, stateObj);
1157411573
WriteMarsHeaderData(stateObj, CurrentTransaction);
1157511574

11576-
if (0 != notificationHeaderSize)
11575+
if (notificationHeaderSize != 0)
1157711576
{
11578-
// Write Notification header length
11577+
// Write notification header length and data
11578+
// MS-TDS 2.2.5.3.1: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/e168d373-a7b7-41aa-b6ca-25985466a7e0
1157911579
WriteInt(notificationHeaderSize, stateObj);
11580-
// Write notificaiton header data
1158111580
WriteQueryNotificationHeaderData(notificationRequest, stateObj);
1158211581
}
1158311582

1158411583
if (IncludeTraceHeader)
1158511584
{
11586-
11587-
// Write trace header length
11588-
WriteInt(traceHeaderSize, stateObj);
11589-
// Write trace header data
11585+
// Write trace header length and data
11586+
// MS-TDS 2.2.5.3.3: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/6e9f106b-df6e-4cbe-a6eb-45ceb10c63be
11587+
WriteInt(TraceHeaderSize, stateObj);
1159011588
WriteTraceHeaderData(stateObj);
1159111589
}
1159211590
}

src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using System.Net;
1414
using System.Net.NetworkInformation;
1515
using System.Net.Sockets;
16+
using System.Reflection;
1617
using System.Runtime.InteropServices;
1718
using System.Security;
1819
using System.Security.Principal;
@@ -1027,7 +1028,33 @@ public class AKVEventListener : BaseEventListener
10271028

10281029
public class MDSEventListener : BaseEventListener
10291030
{
1031+
private static Type s_activityCorrelatorType = Type.GetType("Microsoft.Data.Common.ActivityCorrelator, Microsoft.Data.SqlClient");
1032+
private static PropertyInfo s_currentActivityProperty = s_activityCorrelatorType.GetProperty("Current", BindingFlags.NonPublic | BindingFlags.Static);
1033+
1034+
public readonly HashSet<string> ActivityIDs = new();
1035+
10301036
public override string Name => MDSEventSourceName;
1037+
1038+
protected override void OnMatchingEventWritten(EventWrittenEventArgs eventData)
1039+
{
1040+
object currentActivity = s_currentActivityProperty.GetValue(null);
1041+
string activityId = GetActivityId(currentActivity);
1042+
1043+
ActivityIDs.Add(activityId);
1044+
}
1045+
1046+
private static string GetActivityId(object currentActivity)
1047+
{
1048+
Type activityIdType = currentActivity.GetType();
1049+
FieldInfo idField = activityIdType.GetField("Id", BindingFlags.NonPublic | BindingFlags.Instance);
1050+
FieldInfo sequenceField = activityIdType.GetField("Sequence", BindingFlags.NonPublic | BindingFlags.Instance);
1051+
1052+
Guid id = (Guid)idField.GetValue(currentActivity);
1053+
uint sequence = (uint)sequenceField.GetValue(currentActivity);
1054+
1055+
// This activity ID format matches the one used in the XEvents trace
1056+
return string.Format("{0}-{1}", id, sequence).ToUpper();
1057+
}
10311058
}
10321059

10331060
public class BaseEventListener : EventListener
@@ -1056,6 +1083,103 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData)
10561083
{
10571084
IDs.Add(eventData.EventId);
10581085
EventData.Add(eventData);
1086+
OnMatchingEventWritten(eventData);
1087+
}
1088+
}
1089+
1090+
protected virtual void OnMatchingEventWritten(EventWrittenEventArgs eventData)
1091+
{
1092+
}
1093+
}
1094+
1095+
public readonly ref struct XEventScope // : IDisposable
1096+
{
1097+
private readonly SqlConnection _connection;
1098+
private readonly bool _useDatabaseSession;
1099+
1100+
public string SessionName { get; }
1101+
1102+
public XEventScope(SqlConnection connection, string eventSpecification, string targetSpecification)
1103+
{
1104+
SessionName = GenerateRandomCharacters("Session");
1105+
_connection = connection;
1106+
// SQL Azure only supports database-scoped XEvent sessions
1107+
_useDatabaseSession = GetSqlServerProperty(connection.ConnectionString, "EngineEdition") == "5";
1108+
1109+
SetupXEvent(eventSpecification, targetSpecification);
1110+
}
1111+
1112+
public void Dispose()
1113+
=> DropXEvent();
1114+
1115+
public System.Xml.XmlDocument GetEvents()
1116+
{
1117+
string xEventQuery = _useDatabaseSession
1118+
? $@"SELECT xet.target_data
1119+
FROM sys.dm_xe_database_session_targets AS xet
1120+
INNER JOIN sys.dm_xe_database_sessions AS xe
1121+
ON (xe.address = xet.event_session_address)
1122+
WHERE xe.name = '{SessionName}'"
1123+
: $@"SELECT xet.target_data
1124+
FROM sys.dm_xe_session_targets AS xet
1125+
INNER JOIN sys.dm_xe_sessions AS xe
1126+
ON (xe.address = xet.event_session_address)
1127+
WHERE xe.name = '{SessionName}'";
1128+
1129+
using (SqlCommand command = new SqlCommand(xEventQuery, _connection))
1130+
{
1131+
if (_connection.State == ConnectionState.Closed)
1132+
{
1133+
_connection.Open();
1134+
}
1135+
1136+
string targetData = command.ExecuteScalar() as string;
1137+
System.Xml.XmlDocument xmlDocument = new System.Xml.XmlDocument();
1138+
1139+
xmlDocument.LoadXml(targetData);
1140+
return xmlDocument;
1141+
}
1142+
}
1143+
1144+
private void SetupXEvent(string eventSpecification, string targetSpecification)
1145+
{
1146+
string sessionLocation = _useDatabaseSession ? "DATABASE" : "SERVER";
1147+
string xEventCreateAndStartCommandText = $@"CREATE EVENT SESSION [{SessionName}] ON {sessionLocation}
1148+
{eventSpecification}
1149+
{targetSpecification}
1150+
WITH (
1151+
MAX_MEMORY=4096 KB,
1152+
EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,
1153+
MAX_DISPATCH_LATENCY=30 SECONDS,
1154+
MAX_EVENT_SIZE=0 KB,
1155+
MEMORY_PARTITION_MODE=NONE,
1156+
TRACK_CAUSALITY=ON,
1157+
STARTUP_STATE=OFF)
1158+
1159+
ALTER EVENT SESSION [{SessionName}] ON {sessionLocation} STATE = START ";
1160+
1161+
using (SqlCommand createXEventSession = new SqlCommand(xEventCreateAndStartCommandText, _connection))
1162+
{
1163+
if (_connection.State == ConnectionState.Closed)
1164+
{
1165+
_connection.Open();
1166+
}
1167+
1168+
createXEventSession.ExecuteNonQuery();
1169+
}
1170+
}
1171+
1172+
private void DropXEvent()
1173+
{
1174+
string dropXEventSessionCommand = _useDatabaseSession
1175+
? $"IF EXISTS (select * from sys.dm_xe_database_sessions where name ='{SessionName}')" +
1176+
$" DROP EVENT SESSION [{SessionName}] ON DATABASE"
1177+
: $"IF EXISTS (select * from sys.dm_xe_sessions where name ='{SessionName}')" +
1178+
$" DROP EVENT SESSION [{SessionName}] ON SERVER";
1179+
1180+
using (SqlCommand command = new SqlCommand(dropXEventSessionCommand, _connection))
1181+
{
1182+
command.ExecuteNonQuery();
10591183
}
10601184
}
10611185
}

src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@
177177
<Compile Include="ProviderAgnostic\MultipleResultsTest\MultipleResultsTest.cs" />
178178
<Compile Include="ProviderAgnostic\ReaderTest\ReaderTest.cs" />
179179
<Compile Include="TracingTests\EventSourceTest.cs" />
180+
<Compile Include="TracingTests\XEventsTracingTest.cs" />
180181
<Compile Include="SQL\AADFedAuthTokenRefreshTest\AADFedAuthTokenRefreshTest.cs" />
181182
<Compile Include="SQL\ConnectionPoolTest\ConnectionPoolTest.cs" />
182183
<Compile Include="SQL\ConnectionPoolTest\PoolBlockPeriodTest.cs" />

src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs

Lines changed: 7 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1913,12 +1913,14 @@ private static void VariantCollationsTest(string connectionString)
19131913

19141914
private static void TestXEventsStreaming(string connectionString)
19151915
{
1916-
string sessionName = DataTestUtility.GenerateRandomCharacters("Session");
1917-
1918-
try
1916+
// Create XEvent
1917+
using (SqlConnection xEventManagementConnection = new SqlConnection(connectionString))
1918+
using (DataTestUtility.XEventScope xEventScope = new DataTestUtility.XEventScope(xEventManagementConnection,
1919+
"ADD EVENT sqlserver.user_event(ACTION(package0.event_sequence))",
1920+
"ADD TARGET package0.ring_buffer"))
19191921
{
1920-
//Create XEvent
1921-
SetupXevent(connectionString, sessionName);
1922+
string sessionName = xEventScope.SessionName;
1923+
19221924
Task.Factory.StartNew(() =>
19231925
{
19241926
// Read XEvents
@@ -1957,52 +1959,6 @@ private static void TestXEventsStreaming(string connectionString)
19571959
}
19581960
}).Wait(10000);
19591961
}
1960-
finally
1961-
{
1962-
//Delete XEvent
1963-
DeleteXevent(connectionString, sessionName);
1964-
}
1965-
}
1966-
1967-
private static void SetupXevent(string connectionString, string sessionName)
1968-
{
1969-
string xEventCreateAndStartCommandText = @"CREATE EVENT SESSION [" + sessionName + @"] ON SERVER
1970-
ADD EVENT sqlserver.user_event(ACTION(package0.event_sequence))
1971-
ADD TARGET package0.ring_buffer
1972-
WITH (
1973-
MAX_MEMORY=4096 KB,
1974-
EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,
1975-
MAX_DISPATCH_LATENCY=30 SECONDS,
1976-
MAX_EVENT_SIZE=0 KB,
1977-
MEMORY_PARTITION_MODE=NONE,
1978-
TRACK_CAUSALITY=ON,
1979-
STARTUP_STATE=OFF)
1980-
1981-
ALTER EVENT SESSION [" + sessionName + "] ON SERVER STATE = START ";
1982-
1983-
using (SqlConnection connection = new SqlConnection(connectionString))
1984-
{
1985-
connection.Open();
1986-
using (SqlCommand createXeventSession = new SqlCommand(xEventCreateAndStartCommandText, connection))
1987-
{
1988-
createXeventSession.ExecuteNonQuery();
1989-
}
1990-
}
1991-
}
1992-
1993-
private static void DeleteXevent(string connectionString, string sessionName)
1994-
{
1995-
string deleteXeventSessionCommand = $"IF EXISTS (select * from sys.server_event_sessions where name ='{sessionName}')" +
1996-
$" DROP EVENT SESSION [{sessionName}] ON SERVER";
1997-
1998-
using (SqlConnection connection = new SqlConnection(connectionString))
1999-
{
2000-
connection.Open();
2001-
using (SqlCommand deleteXeventSession = new SqlCommand(deleteXeventSessionCommand, connection))
2002-
{
2003-
deleteXeventSession.ExecuteNonQuery();
2004-
}
2005-
}
20061962
}
20071963

20081964
private static void TimeoutDuringReadAsyncWithClosedReaderTest(string connectionString)

0 commit comments

Comments
 (0)