To close out this month’s series on Extended Events we’ll look at the DDL Events for the Event Session DDL operations, and how those can be used to track changes to Event Sessions and determine all of the possible outputs that could exist from an Extended Event Session. One of my least favorite quirks about Extended Events is that there is no way to determine the Events and Actions that may exist inside a Target, except to parse all of the the captured data. Information about the Event Session does exist in the Session Definition Catalog Views and Active Session DMV’s, but as you change an Event Sessions Events and Actions while it is running, the information in these change as well, so it is possible that a Target has Events and Actions that are not returned by the current information available about the Event Session. This is where the DDL Events for the Event Session DDL operations can be useful, if the appropriate framework is deployed.
The DDL Events for Extended Events are not currently documented in the Books Online. I only recently learned about them from Mike Wachal during a discussion about what I thought was missing from Extended Events. This is simply an oversight in the documentation, and something that Mike has stated will be fixed, it doesn’t mean that the DDL Events are undocumented and subject to change without notice like other undocumented features of SQL Server. We can find the DDL Events for Extended Events in the sys.event_notification_event_types.
SELECT type, type_name, parent_type FROM sys.event_notification_event_types WHERE type_name LIKE '%SESSION%'
These can be used just like any other DDL Event to create a DDL Trigger or Event Notification that takes action when one of the DDL operations occurs. We can use this to log the DDL to track our changes over time, and we can also use it to create a tracking table of the possible outputs from our Event Session, ensuring that we know what information it may have collected when we parse the Event data from the Targets. We can also use this information to simplify the generation of our XQuery XPATH statements to parse the data from the Targets with a little extra work.
In all of my servers, I have a standard database named sqladmin that I keep DBA related information and objects. For the examples, I will create this database and use it in all the code. If you have a different database, the scripts can easily be changed to create the objects in that database. The first thing we’ll do is create our database, and two tables, one for tracking the DDL operations and the other for tracking all of the possible outputs for our Event Session.
CREATE DATABASE sqladmin GO USE sqladmin GO CREATE TABLE dbo.XEvents_DDLOperations ( DDLEventData XML, ChangeDate DATETIME DEFAULT(CURRENT_TIMESTAMP), LoginName NVARCHAR(256) DEFAULT(SUSER_SNAME()), ProgramName NVARCHAR(256) DEFAULT(program_name()) ); GO CREATE TABLE XEvents_SessionOutputs ( EventSessionName NVARCHAR(256), EventName NVARCHAR(256), EventID INT, ColumnID INT, ColumnName NVARCHAR(256), NodeType NVARCHAR(10), DataType NVARCHAR(50), XMLLocation NVARCHAR(10), TypePrecidence INT ) GO
The XEvents_SessionOutputs table will have multiple rows for each Event Session defined on the server that track the EventName, the output ColumnName, the NodeType for the data element in the Event XML, the SQL DataType returned by the output, the XMLLocation for where the data of interest exists, and a TypePrecidence value that can be used when multiple Events return the same Data Element with different DataTypes, ensuring that we can pick the most compatible DataType for the output column. The table also tracks the Event ID in the Event Session for the Event, the Column ID for the output column so that grouping and ordering can be performed during code generation from this table.
To get the SQL DataType that an output returns, we have to look at the output type_name in the Extended Events metadata for the output column or Action. To make this easier to do and allow for code reuse, I create a view that maps the type_name in Extended Events to corresponding SQL DataType. Since Maps can be a type_name in Extended Events, the view queries the sys.dm_xe_map_values DMV and calculates the maximum length of the map_value column for each Map, and then uses the nvarchar datatype and rounds the length up to the nearest power of 10 (ok, it doesn’t actually round but that is the effect of the math operations). For the actual Event data columns in the base payload, the type_name is transposed to the equivalent SQL DataType that is compatible with XQuery.
CREATE VIEW dbo.XETypeToSQLType AS SELECT XETypeName = mv.name, SQLTypeName = 'nvarchar('+CAST(MAX(LEN(mv.map_value))-(MAX(LEN(mv.map_value))%10) + 10 AS VARCHAR(4))+')', XMLLocation = 'text', TypePrecidence = 5 FROM sys.dm_xe_object_columns oc LEFT JOIN sys.dm_xe_map_values mv ON oc.type_package_guid = mv.object_package_guid AND oc.type_name = mv.name WHERE oc.column_type = 'data' AND mv.name IS NOT NULL GROUP BY mv.name UNION ALL SELECT XETypeName = o.name, SQLTypeName = CASE WHEN TYPE_NAME IN ('int8', 'int16', 'int32', 'uint8', 'uint16', 'uint32', 'float32') THEN 'int' WHEN TYPE_NAME IN ('int64', 'uint64', 'float64') THEN 'bigint' WHEN TYPE_NAME = 'boolean' THEN 'nvarchar(10)' --true/false returned WHEN TYPE_NAME = 'guid' THEN 'uniqueidentifier' ELSE 'nvarchar(4000)' END, XMLLocation = 'value', TypePrecidence = CASE WHEN TYPE_NAME IN ('int8', 'int16', 'int32', 'uint8', 'uint16', 'uint32', 'float32') THEN 1 WHEN TYPE_NAME IN ('int64', 'uint64', 'float64') THEN 2 WHEN TYPE_NAME = 'boolean' THEN 3 --true/false returned WHEN TYPE_NAME = 'guid' THEN 3 ELSE 5 END FROM sys.dm_xe_objects o WHERE object_type = 'type' AND TYPE_NAME != 'null' GO
Using this view, we can create another view that queries the Session Definition Catalog Views, to retrieve the output columns for an Event Session in a format that matches our XEvents_SessionOutputs table.
CREATE VIEW dbo.XESession_OutputsFromDMVs AS -- Find a list of all the possible output columns SELECT ses.name AS EventSessionName, sese.name AS EventName, sese.event_id AS EventID, oc.column_id AS ColumnID, oc.name AS ColumnName, 'data' AS NodeType, xetst.SQLTypeName AS DataType, xetst.XMLLocation, xetst.TypePrecidence FROM sys.server_event_sessions AS ses JOIN sys.server_event_session_events AS sese ON ses.event_session_id = sese.event_session_id JOIN sys.dm_xe_packages AS p ON sese.package = p.name JOIN sys.dm_xe_object_columns AS oc ON oc.object_name = sese.name AND oc.object_package_guid = p.guid JOIN XETypeToSQLType AS xetst ON oc.type_name = xetst.XETypeName WHERE oc.column_type = 'data' UNION SELECT ses.name, sese.name, sesa.event_id, 999 AS column_id, sesa.name, 'action', xetst.SQLTypeName, xetst.XMLLocation, xetst.TypePrecidence FROM sys.server_event_sessions AS ses JOIN sys.server_event_session_events AS sese ON ses.event_session_id = sese.event_session_id JOIN sys.server_event_session_actions AS sesa ON ses.event_session_id = sesa.event_session_id AND sesa.event_id = sese.event_id JOIN sys.dm_xe_packages AS p ON sesa.package = p.name JOIN sys.dm_xe_objects AS o ON p.guid = o.package_guid AND sesa.name = o.name JOIN XETypeToSQLType AS xetst ON o.type_name = xetst.XETypeName WHERE o.object_type = 'action' GO
We can then create a Server Level DDL Trigger for the DDL_EVENT_SESSION_EVENTS group that will log the DDL operation to the XEvents_DDLOperations table, and at the same time populate the output information in the XEvents_SessionOutputs table when an Event Session is created, add any new outputs when an Event Session is altered, and delete the Event Session information when an Event Session is dropped. By adding new outputs when a Event Session is altered, we maintain a record of the original outputs, even if the Event was dropped from the Event Session.
CREATE TRIGGER XEvents_DDLTrigger ON ALL SERVER FOR DDL_EVENT_SESSION_EVENTS AS BEGIN SET NOCOUNT ON; DECLARE @EventData XML = EVENTDATA(); INSERT INTO sqladmin.dbo.XEvents_DDLOperations (DDLEventData) VALUES (@EventData); DECLARE @EventType NVARCHAR(256) = @EventData.value('(EVENT_INSTANCE/EventType)[1]', 'nvarchar(256)') DECLARE @SessionName NVARCHAR(256) = @EventData.value('(EVENT_INSTANCE/ObjectName)[1]', 'nvarchar(256)') IF @EventType = 'CREATE_EVENT_SESSION' BEGIN INSERT INTO sqladmin.dbo.XEvents_SessionOutputs (EventSessionName, EventName, EventID, ColumnID, ColumnName, NodeType, DataType, XMLLocation, TypePrecidence) SELECT EventSessionName, EventName, EventID, ColumnID, ColumnName, NodeType, DataType, XMLLocation, TypePrecidence FROM sqladmin.dbo.XESession_OutputsFromDMVs WHERE EventSessionName = @SessionName END IF @EventType = 'ALTER_EVENT_SESSION' BEGIN -- Add any new outputs to the Table INSERT INTO sqladmin.dbo.XEvents_SessionOutputs (EventSessionName, EventName, EventID, ColumnID, ColumnName, NodeType, DataType, XMLLocation, TypePrecidence) SELECT vdmv.EventSessionName, vdmv.EventName, vdmv.EventID, vdmv.ColumnID, vdmv.ColumnName, vdmv.NodeType, vdmv.DataType, vdmv.XMLLocation, vdmv.TypePrecidence FROM sqladmin.dbo.XESession_OutputsFromDMVs vdmv LEFT JOIN sqladmin.dbo.XEvents_SessionOutputs xeso ON vdmv.EventSessionName = xeso.EventSessionName AND vdmv.EventName = xeso.EventName AND vdmv.ColumnName = vdmv.ColumnName WHERE vdmv.EventSessionName = @SessionName AND xeso.EventSessionName IS NULL END IF @EventType = 'DROP_EVENT_SESSION' BEGIN -- Delete the Output data for the Event Session DELETE sqladmin.dbo.XEvents_SessionOutputs WHERE EventSessionName = @SessionName END END GO
If we recreate the TrackResourceWaits Event Session from yesterday’s post and then query the XEvents_SessionOutputs table, we can see the outputs that we can expect from that Event Session:
SELECT * FROM sqladmin.dbo.XEvents_SessionOutputs WHERE EventSessionName = 'TrackResourceWaits'
Using this information, we can also write a query to generate our XQuery statements for each of the outputs, as well as a column definition stub if we wanted to create a table to hold this information for analysis.
SELECT 'event_data.value(''(event/'+NodeType+'[@name="'+ColumnName+'"]/'+XMLLocation+')[1]'', '''+DataType+''') AS '+QUOTENAME(ColumnName)+',' AS XQuery, QUOTENAME(ColumnName)+' '+DataType+', ' AS ColumnDefinition FROM ( SELECT ROW_NUMBER() OVER (PARTITION BY ColumnName ORDER BY TypePrecidence DESC) AS partitionid, EventSessionName, EventID, ColumnID, ColumnName, NodeType, DataType, XMLLocation FROM sqladmin.dbo.XEvents_SessionOutputs ) AS tab WHERE EventSessionName = 'TrackResourceWaits' AND partitionid = 1 ORDER BY EventID, ColumnID
The information in the XQuery column can be copied and pasted into our TSQL Script for parsing the Event Data from the ring_buffer, pair_matching, or asynchronous_file_target Targets. You could also use this as the basis for writing your own Extended Events Target Data code generator, similar to the one that Adam Machanic created a year ago.
That’s it for this months series on Extended Events. You can find links to all of the posts on the round up post from December 1, An XEvent A Day: 31 days of Extended Events. Hopefully its been informative, and you now have a better understanding of how Extended Events can be used inside of SQL Server 2008, 2008R2, and in Denali CTP1.
One thought on “An XEvent a Day (31 of 31) – Event Session DDL Events”
In the last SQL statement the condition “WHERE EventSessionName = ‘TrackResourceWaits'” must be put inside the inner select so ROW_NUMBER() only considers that session.