In my last blog post, I showed you how I go about baselining wait statistics. Typically my next step once I have found my top wait types is to use extended events to figure out the SQL statements causing the majority of the waits for those wait types. This is actually my favorite example of using extended events as it clearly shows you something that can be done that wasn’t possible with a SQL Server server side trace.
** Download Scripts Here **
So, looking at the results of benchmarking my wait stats I noticed that my workload had several waits for SOS_SCHEDULER_YIELD and PAGEIOLATCH_EX. I could then run the following code below to figure out which statement(s) caused the majority of these waits as shown below and tune them to reduce my waits.
NOTE: Waits schema is required. It is created in the Baseline Wait Statistics sample code.
Capture Data with Extended Events
Regardless if you are using SQL 2008 or SQL Server 2012 the statement used create your extended event session to capture your SQL statements causing your top wait types is the same. In this extended event we will capture data into memory using 20 MB. This and the max_dispatch_latency can be configured with specifying the parameters into the stored procedure.
IF NOT EXISTS (SELECT 1 FROM sys.table_types where name like 'WaitType') BEGIN CREATE TYPE WaitType AS TABLE ( Name VARCHAR(200)) END /****** Object: StoredProcedure [Waits].[CaptureStatementsCausingWaits] Script Date: 10/7/2013 10:28:24 PM ******/SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- ============================================= -- Author:@JohnSterrett -- Create date: 9/23/2013 -- Description:Gets SQL Statements causing top wait types. -- -- ============================================= CREATE PROCEDURE [Waits].[CaptureStatementsCausingWaits] -- Add the parameters for the stored procedure here @TVP WaitType READONLY, -- Table Value Parameter with Wait Types @Duration int = 10, @StartXEvent bit = 1, @CreateXEvent bit = 1, @StopXevent bit = 0, @max_memory bigint = 20480, @max_dispatch_latency int =5 -- Sample Executions : --**** What wait types are causing PAGEIOLATCH_EX, SOS_SCHEDULER_YIELD, 'PAGEIOLATCH_SH waits **** /* DECLARE @WaitTypeTVP AS WaitType; INSERT INTO @WaitTypeTVP (Name) VALUES ('PAGEIOLATCH_EX'), ('SOS_SCHEDULER_YIELD'),('PAGEIOLATCH_SH') EXEC Waits.CaptureStatementsCausingWaits @TVP = @WaitTypeTVP; GO */--*** Get wait details. XEvent is in memory so must still be running **** --exec [Waits].[GetStatementsCausingWaits] --*** Stop Xevent from capturing (NOTE: Once this is done you will loose collected data) *** --exec Waits.CaptureStatementsCausingWaits @StopXevent = 1 --/********* TODO: *********************** --Add checks for valid parameters --Verify that WaitTypes exist */AS BEGIN -- SET NOCOUNT ON added to prevent extra result sets from -- interfering with SELECT statements. SET NOCOUNT ON; IF @StopXevent = 1 BEGIN IF EXISTS ( SELECT * FROM sys.server_event_sessions WHERE name = 'TrackResourceWaits' ) ALTER EVENT SESSION TrackResourceWaits ON SERVER STATE = STOP; END ELSE BEGIN -- Insert statements for procedure here DECLARE @SQLStmt nvarchar(max) IF OBJECT_ID('tempdb..##TmpWaitTypes') IS NOT NULL DROP TABLE ##TmpWaitTypes SELECT map_key, map_value INTO ##TmpWaitTypes FROM sys.dm_xe_map_values xmv JOIN @TVP tvp ON (tvp.name = xmv.map_value) WHERE xmv.name = 'wait_types' SELECT * FROM ##TmpWaitTypes /* Step 3: Create XEvent to capture queries causing waits. Must update from step 2 Use script to find top waits and then use this script to get statements causing those waits */IF @CreateXEvent = 1 BEGIN DECLARE curWaitTypes CURSOR LOCAL FAST_FORWARD FOR SELECT map_key FROM ##TmpWaitTypes OPEN curWaitTypes DECLARE @map_key bigint, @SmallSQL nvarchar(max) SET @SmallSQL = '' FETCH NEXT FROM curWaitTypes INTO @map_key WHILE @@FETCH_STATUS = 0 BEGIN SET @SmallSQL += 'wait_type = '+CAST(@map_key AS VARCHAR(50)) +' OR ' FETCH NEXT FROM curWaitTypes INTO @map_key END CLOSE curWaitTypes; DEALLOCATE curWaitTypes; /* Remove the last comma */SET @SmallSQL = LEFT(@SmallSQL, LEN(@SmallSQL) - 3) PRINT @SmallSQL --AND (wait_type IN (50, 51, 124) IF EXISTS ( SELECT * FROM sys.server_event_sessions WHERE name = 'TrackResourceWaits' ) DROP EVENT SESSION TrackResourceWaits ON SERVER SET @SQLStmt = 'CREATE EVENT SESSION [TrackResourceWaits] ON SERVER ADD EVENT sqlos.wait_info ( -- Capture the database_id, session_id, plan_handle, and sql_text ACTION(sqlserver.database_id,sqlserver.username, sqlserver.session_id,sqlserver.sql_text,sqlserver.plan_handle, sqlserver.tsql_stack) WHERE (opcode = 1 --End Events Only AND duration > ' +CAST(@Duration AS VARCHAR(10)) +' AND (' +@SmallSQL+ ') ) ) ADD TARGET package0.ring_buffer(SET max_memory= '+CAST(@max_memory AS varchar(200))+') WITH (EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS, MAX_DISPATCH_LATENCY= '+CAST(@max_dispatch_latency as varchar(10))+' SECONDS)' PRINT @SQLStmt EXEC(@SQLStmt) /* Cleanup tasks */IF OBJECT_ID('tempdb..##TmpWaitTypes') IS NOT NULL DROP TABLE ##TmpWaitTypes END IF @StartXEvent = 1 ALTER EVENT SESSION TrackResourceWaits ON SERVER STATE = START; /* Step 4a: Start workload and wait */END END
Reading Captured Data with SQL Server 2008
Now that we have our extended event running into memory we will need complete our analysis before we stop the extended event. The following stored procedure can be used to pull the statements causing your top waits.
SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- ============================================= -- Author:<Author,,Name> -- Create date: <Create Date,,> -- Description:<Description,,> -- ============================================= CREATE PROCEDURE [Waits].[GetStatementsCausingWaits_2008] -- Add the parameters for the stored procedure here AS BEGIN -- SET NOCOUNT ON added to prevent extra result sets from -- interfering with SELECT statements. SET NOCOUNT ON; -- Insert statements for procedure here /* Step 4b: Query the waits */IF OBJECT_ID('tempdb..#XWaits') IS NOT NULL DROP TABLE #XWaits SELECT event_data.value('(event/@name)[1]', 'varchar(50)') AS event_name, DATEADD(hh, DATEDIFF(hh, GETUTCDATE(), CURRENT_TIMESTAMP), event_data.value('(event/@timestamp)[1]', 'datetime2')) AS [timestamp], COALESCE(event_data.value('(event/data[@name="database_id"]/value)[1]', 'int'), event_data.value('(event/action[@name="database_id"]/value)[1]', 'int')) AS database_id, event_data.value('(event/action[@name="session_id"]/value)[1]', 'int') AS [session_id], event_data.value('(event/data[@name="wait_type"]/text)[1]', 'nvarchar(4000)') AS [wait_type], event_data.value('(event/data[@name="opcode"]/text)[1]', 'nvarchar(4000)') AS [opcode], event_data.value('(event/data[@name="duration"]/value)[1]', 'bigint') AS [duration], event_data.value('(event/data[@name="max_duration"]/value)[1]', 'bigint') AS [max_duration], event_data.value('(event/data[@name="total_duration"]/value)[1]', 'bigint') AS [total_duration], event_data.value('(event/data[@name="signal_duration"]/value)[1]', 'bigint') AS [signal_duration], event_data.value('(event/data[@name="completed_count"]/value)[1]', 'bigint') AS [completed_count], event_data.value('(event/action[@name="plan_handle"]/value)[1]', 'nvarchar(4000)') AS [plan_handle], event_data.value('(event/action[@name="sql_text"]/value)[1]', 'nvarchar(4000)') AS [sql_text], event_data.value('(event/action[@name="tsql_stack"]/value)[1]', 'nvarchar(4000)') AS [tsql_stack] INTO #XWaits FROM ( SELECT XEvent.query('.') AS event_data FROM ( -- Cast the target_data to XML SELECT CAST(target_data AS XML) AS TargetData FROM sys.dm_xe_session_targets st JOIN sys.dm_xe_sessions s ON s.address = st.event_session_address WHERE name = 'TrackResourceWaits' AND target_name = 'ring_buffer' ) AS Data -- Split out the Event Nodes CROSS APPLY TargetData.nodes ('RingBufferTarget/event') AS XEventData (XEvent) ) AS tab (event_data) -- Need to get tsql_stack as XML to manipulate -- ALTER TABLE #XWaits ADD tsql_stack2 XML UPDATE #XWaits SET tsql_stack2 = '<Root>' + tsql_stack + '</Root>' -- Duration by Wait SELECT wait_Type, COUNT(*) AS NumOfWaitsGT10ms, SUM(duration) AS TotalDurationMS FROM #XWaits GROUP BY wait_type ORDER BY TotalDurationMS DESC -- Get wait's for block of t-sql code IF OBJECT_ID('tempdb..#TempData') IS NOT NULL DROP TABLE #TempData; WITH XWaitsCTE (database_id, session_id, wait_type, duration, plan_handle, tsql_stack, handle, offsetStart, offsetEnd) AS ( SELECT database_id, session_id, wait_type, duration, plan_handle, tsql_stack --,tsql_stack2 ,tsql_stack2.value('(Root/frame/@handle)[1]', 'varchar(2000)') as handle --,CONVERT(tsql_stack2.value('(Root/frame/@handle)[1]', 'nvarchar(2000)') as handle2 ,tsql_stack2.value('(Root/frame/@offsetStart)[1]', 'varchar(4000)') as offsetStart ,tsql_stack2.value('(Root/frame/@offsetEnd)[1]', 'varchar(4000)') as offsetEnd FROM#XWaits ) SELECT wait_type, COUNT(*) AS NumOfWaitsGT10ms, SUM(duration) AS TotalDurationMS, handle, offsetStart, offsetEnd, database_id INTO #TempData FROM XWaitsCTE GROUP BY wait_type, handle, offsetStart, offsetEnd, database_id; --SELECT *, DB_NAME(database_id) --FROM #TempData --ORDER BY TotalDurationMS DESC; /* Top statements causing wait types SELECT TOP 20 SUM(TotalDurationMS) TotalDurationMS, handle, offsetStart, offsetEnd, DB_NAME(database_id) FROM #TempData GROUP BY handle, offsetEnd, offsetStart, database_id ORDER BY 1 desc; *//* Look at statement causing wait times Substitue handle, offsetStart and offsetEnd from query above *//*** Get statement causing waits ****/IF OBJECT_ID('tempdb..#SQLStatement') IS NOT NULL DROP TABLE #SQLStatement; CREATE TABLE #SQLStatement (handleText varchar(4000), offsetStart varchar(1000), offsetEnd varchar(1000), sql_statement xml, ObjectName varchar(2000), objectid bigint, databaseid int, encrypted bit) DECLARE WaitStatement CURSOR LOCAL FAST_FORWARD FOR SELECT TOP 20 SUM(TotalDurationMS) AS TotalDurationMS, handle, offsetStart, offsetEnd, DB_NAME(database_id) FROM #TempData GROUP BY handle, offsetEnd, offsetStart, database_id ORDER BY 1 desc; OPEN WaitStatement DECLARE @TotalDurationMS bigint, @handle varchar(2000), @offsetStart varchar(100), @offsetEnd varchar(100), @databaseName varchar(4000), @SQLStmt nvarchar(max) FETCH NEXT FROM WaitStatement INTO @TotalDurationMS, @handle, @offsetStart, @offsetEnd, @databaseName WHILE @@FETCH_STATUS = 0 BEGIN SET @SQLStmt = 'USE [' + @databaseName+'] '+CHAR(10) SET @SQLStmt = @SQLStmt + ' declare @offsetStart bigint, @offsetEnd bigint, @handle varbinary(64), @handleText varchar(4000) ' SET @SQLStmt = @SQLStmt + ' select @offsetStart = '+@offsetStart +', @offsetEnd = '+case when @offsetEnd like '-1' then '2147483647' else @offsetEnd end+', @handle = '+@handle+', @handleText = '''+@handle+'''' SET @SQLStmt = @SQLStmt + CHAR(10)+ ' INSERT INTO #SQLStatement (sql_statement, ObjectName, objectid, databaseid, encrypted, offsetStart, offsetEnd, handleText) ' SET @SQLStmt = @SQLStmt + CHAR(10)+ ' select CAST(''<?query --''+CHAR(13)+SUBSTRING(qt.text, (@offsetStart/ 2)+1, (( @offsetEnd - @offsetStart)/2) + 1)+CHAR(13)+''--?>'' AS xml) as sql_statement ' SET @SQLStmt = @SQLStmt + CHAR(10)+ ' , OBJECT_NAME(qt.objectid) OBJNAME ,qt.objectid,qt.dbid, qt.encrypted, @offsetStart as offsetStart, @offsetEnd as offsetEnd, @handleText as handleText ' SET @SQLStmt = @SQLStmt + CHAR(10)+ ' from sys.dm_exec_sql_text(@handle) qt ' --PRINT @SQLStmt EXEC (@SQLStmt) FETCH NEXT FROM WaitStatement INTO @TotalDurationMS, @handle, @offsetStart, @offsetEnd, @databaseName END CLOSE WaitStatement; DEALLOCATE WaitStatement; /*** GET THE SQL GOOD STUFF *******************************************/WITH cte_SQLStatement (TotalDurationMS, handle, offsetStart, offsetEnd, databaseName) AS ( SELECT TOP 20 SUM(TotalDurationMS) TotalDurationMS, handle, offsetStart, offsetEnd, DB_NAME(database_id) FROM #TempData GROUP BY handle, offsetEnd, offsetStart, database_id ORDER BY 1 desc ) SELECT TotalDurationMS, ts.sql_statement, ts.ObjectName, td.handle, td.offsetStart, td.offsetEnd, td.databaseName FROM cte_SQLStatement td LEFT JOIN #SQLStatement ts ON (td.handle = ts.handleText AND td.offsetStart = ts.offsetStart) ORDER BY TotalDurationMS desc; END GO
Reading Captured Data with SQL Server 2012
The following is a similar stored procedure customized to work with SQL Server 2012.
SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- ============================================= -- Author:<Author,,Name> -- Create date: <Create Date,,> -- Description:<Description,,> -- ============================================= CREATE PROCEDURE [Waits].[GetStatementsCausingWaits_2012] -- Add the parameters for the stored procedure here AS BEGIN -- SET NOCOUNT ON added to prevent extra result sets from -- interfering with SELECT statements. SET NOCOUNT ON; -- Insert statements for procedure here /* Step 4b: Query the waits */IF OBJECT_ID('tempdb..#XWaits') IS NOT NULL DROP TABLE #XWaits SELECT event_data.value('(event/@name)[1]', 'varchar(50)') AS event_name, DATEADD(hh, DATEDIFF(hh, GETUTCDATE(), CURRENT_TIMESTAMP), event_data.value('(event/@timestamp)[1]', 'datetime2')) AS [timestamp], COALESCE(event_data.value('(event/data[@name="database_id"]/value)[1]', 'int'), event_data.value('(event/action[@name="database_id"]/value)[1]', 'int')) AS database_id, event_data.value('(event/action[@name="session_id"]/value)[1]', 'int') AS [session_id], event_data.value('(event/data[@name="wait_type"]/text)[1]', 'nvarchar(4000)') AS [wait_type], event_data.value('(event/data[@name="opcode"]/text)[1]', 'nvarchar(4000)') AS [opcode], event_data.value('(event/data[@name="duration"]/value)[1]', 'bigint') AS [duration], event_data.value('(event/data[@name="max_duration"]/value)[1]', 'bigint') AS [max_duration], event_data.value('(event/data[@name="total_duration"]/value)[1]', 'bigint') AS [total_duration], event_data.value('(event/data[@name="signal_duration"]/value)[1]', 'bigint') AS [signal_duration], event_data.value('(event/data[@name="completed_count"]/value)[1]', 'bigint') AS [completed_count], event_data.value('(event/action[@name="plan_handle"]/value)[1]', 'nvarchar(4000)') AS [plan_handle], event_data.query('(event/action[@name="tsql_stack"]/value)[1]') AS tsql_stack, event_data.query('(event/action[@name="tsql_frame"]/value)[1]') AS tsql_frame INTO #XWaits FROM ( SELECT XEvent.query('.') AS event_data FROM ( -- Cast the target_data to XML SELECT CAST(target_data AS XML) AS TargetData FROM sys.dm_xe_session_targets st JOIN sys.dm_xe_sessions s ON s.address = st.event_session_address WHERE name = 'TrackResourceWaits' AND target_name = 'ring_buffer' ) AS Data -- Split out the Event Nodes CROSS APPLY TargetData.nodes ('RingBufferTarget/event') AS XEventData (XEvent) ) AS tab (event_data) -- Need to get tsql_stack as XML to manipulate -- /**** Required for SQL 2008 **** ALTER TABLE #XWaits ADD tsql_stack2 XML UPDATE #XWaits SET tsql_stack2 = '<Root>' + tsql_stack + '</Root>' */-- Duration by Wait SELECT wait_Type, COUNT(*) AS NumOfWaitsGT10ms, SUM(duration) AS TotalDurationMS FROM #XWaits GROUP BY wait_type ORDER BY TotalDurationMS DESC -- Get wait's for block of t-sql code IF OBJECT_ID('tempdb..#TempData') IS NOT NULL DROP TABLE #TempData; WITH XWaitsCTE (database_id, session_id, wait_type, duration, plan_handle, tsql_stack, handle, offsetStart, offsetEnd) AS ( SELECT database_id, session_id, wait_type, duration, plan_handle, tsql_stack --,tsql_stack2 ,pref.value('(/value/frames/frame/@handle)[1]', 'varchar(2000)') as handle ,pref.value('(/value/frames/frame/@offsetStart)[1]', 'varchar(4000)') as offsetStart ,pref.value('(/value/frames/frame/@offsetEnd)[1]', 'varchar(200)') as offsetEnd --,SQLTEXT.text FROM#XWaits CROSS APPLY tsql_stack.nodes('/value/frames') AS People(pref) ) SELECT wait_type, COUNT(*) AS NumOfWaitsGT10ms, SUM(duration) AS TotalDurationMS, handle, offsetStart, offsetEnd, database_id INTO #TempData FROM XWaitsCTE GROUP BY wait_type, handle, offsetStart, offsetEnd, database_id; --SELECT *, DB_NAME(database_id) --FROM #TempData --ORDER BY TotalDurationMS DESC; /* Top statements causing wait types SELECT TOP 20 SUM(TotalDurationMS) TotalDurationMS, handle, offsetStart, offsetEnd, DB_NAME(database_id) FROM #TempData GROUP BY handle, offsetEnd, offsetStart, database_id ORDER BY 1 desc; *//* Look at statement causing wait times Substitue handle, offsetStart and offsetEnd from query above *//*** Get statement causing waits ****/IF OBJECT_ID('tempdb..#SQLStatement') IS NOT NULL DROP TABLE #SQLStatement; CREATE TABLE #SQLStatement (handleText varchar(4000), offsetStart varchar(1000), offsetEnd varchar(1000), sql_statement xml, ObjectName varchar(2000), objectid bigint, databaseid int, encrypted bit) DECLARE WaitStatement CURSOR LOCAL FAST_FORWARD FOR SELECT TOP 20 SUM(TotalDurationMS) AS TotalDurationMS, handle, offsetStart, offsetEnd, DB_NAME(database_id) FROM #TempData GROUP BY handle, offsetEnd, offsetStart, database_id ORDER BY 1 desc; OPEN WaitStatement DECLARE @TotalDurationMS bigint, @handle varchar(2000), @offsetStart varchar(100), @offsetEnd varchar(100), @databaseName varchar(4000), @SQLStmt nvarchar(max) FETCH NEXT FROM WaitStatement INTO @TotalDurationMS, @handle, @offsetStart, @offsetEnd, @databaseName WHILE @@FETCH_STATUS = 0 BEGIN SET @SQLStmt = 'USE [' + @databaseName+'] '+CHAR(10) SET @SQLStmt = @SQLStmt + ' declare @offsetStart bigint, @offsetEnd bigint, @handle varbinary(64), @handleText varchar(4000) ' SET @SQLStmt = @SQLStmt + ' select @offsetStart = '+@offsetStart +', @offsetEnd = '+case when @offsetEnd like '-1' then '2147483647' else @offsetEnd end+', @handle = '+@handle+', @handleText = '''+@handle+'''' SET @SQLStmt = @SQLStmt + CHAR(10)+ ' INSERT INTO #SQLStatement (sql_statement, ObjectName, objectid, databaseid, encrypted, offsetStart, offsetEnd, handleText) ' SET @SQLStmt = @SQLStmt + CHAR(10)+ ' select CAST(''<?query --''+CHAR(13)+SUBSTRING(qt.text, (@offsetStart/ 2)+1, (( @offsetEnd - @offsetStart)/2) + 1)+CHAR(13)+''--?>'' AS xml) as sql_statement ' SET @SQLStmt = @SQLStmt + CHAR(10)+ ' , OBJECT_NAME(qt.objectid) OBJNAME ,qt.objectid,qt.dbid, qt.encrypted, @offsetStart as offsetStart, @offsetEnd as offsetEnd, @handleText as handleText ' SET @SQLStmt = @SQLStmt + CHAR(10)+ ' from sys.dm_exec_sql_text(@handle) qt ' --PRINT @SQLStmt EXEC (@SQLStmt) FETCH NEXT FROM WaitStatement INTO @TotalDurationMS, @handle, @offsetStart, @offsetEnd, @databaseName END CLOSE WaitStatement; DEALLOCATE WaitStatement; /*** GET THE SQL GOOD STUFF *******************************************/WITH cte_SQLStatement (TotalDurationMS, handle, offsetStart, offsetEnd, databaseName) AS ( SELECT TOP 20 SUM(TotalDurationMS) TotalDurationMS, handle, offsetStart, offsetEnd, DB_NAME(database_id) FROM #TempData GROUP BY handle, offsetEnd, offsetStart, database_id ORDER BY 1 desc ) SELECT TotalDurationMS, ts.sql_statement, ts.ObjectName, td.handle, td.offsetStart, td.offsetEnd, td.databaseName FROM cte_SQLStatement td LEFT JOIN #SQLStatement ts ON (td.handle = ts.handleText AND td.offsetStart = ts.offsetStart) ORDER BY TotalDurationMS desc; END
Making Get Statements Causing Waits Easy
The following stored procedure makes getting our statements easy. It basically does a quick check to see if your using SQL 2012 or SQL 2008 and executes the correct stored procedure.
SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- ============================================= -- Author:<Author,,Name> -- Create date: <Create Date,,> -- Description:<Description,,> -- ============================================= CREATE PROCEDURE [Waits].[GetStatementsCausingWaits] -- Add the parameters for the stored procedure here AS BEGIN -- SET NOCOUNT ON added to prevent extra result sets from -- interfering with SELECT statements. SET NOCOUNT ON; DECLARE @VersionNumber VARCHAR(100), @VersionInt INT SELECT @VersionNumber = CAST(SERVERPROPERTY(N'productversion') AS VARCHAR(100)) SELECT @VersionInt = CAST(SUBSTRING(@VersionNumber, 1, CHARINDEX('.', @VersionNumber)-1) AS INT) IF @VersionInt = 11 BEGIN EXEC Waits.GetStatementsCausingWaits_2012 END ELSE IF @VersionInt = 10 BEGIN EXEC Waits.GetStatementsCausingWaits_2008 END --end ifs END -- end proc GO
Execution Example
- Benchmark Wait Types to find the top wait types for your workload
- Start Extended Event Capture for your workload top wait types
DECLARE @WaitTypeTVP AS WaitType; INSERT INTO @WaitTypeTVP (Name) VALUES ('PAGEIOLATCH_EX'), ('SOS_SCHEDULER_YIELD'),('PAGEIOLATCH_SH') EXEC Waits.CaptureStatementsCausingWaits @TVP = @WaitTypeTVP;
- Get Statements Causing Waits
exec [Waits].[GetStatementsCausingWaits]
- Stop Extended Event Capture
exec Waits.CaptureStatementsCausingWaits @StopXevent = 1