From 0121531bf66e7a33828e0edb9778bd19b9b0b914 Mon Sep 17 00:00:00 2001 From: Karl Burtram Date: Wed, 17 Jan 2018 18:21:40 -0800 Subject: [PATCH] Bugs fixes so XEvent profiler isn't broken (#577) * Fix a few issues with profiler method handlers * Filter out profiler polling events * Add a unit test for profiler events * Add method comment headers --- .../ProfilerEventsAvailableNotification.cs | 4 +- .../Contracts/StopProfilingRequest.cs | 2 +- .../Profiler/ProfilerService.cs | 49 ++++++++++++++++--- .../Profiler/ProfilerSession.cs | 36 ++++++++++++++ .../Profiler/ProfilerSessionMonitor.cs | 4 +- .../Workspace/Workspace.cs | 3 +- .../Profiler/ProfilerSessionTests.cs | 24 +++++++++ 7 files changed, 110 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.SqlTools.ServiceLayer/Profiler/Contracts/ProfilerEventsAvailableNotification.cs b/src/Microsoft.SqlTools.ServiceLayer/Profiler/Contracts/ProfilerEventsAvailableNotification.cs index 161e5766..91ad3159 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Profiler/Contracts/ProfilerEventsAvailableNotification.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Profiler/Contracts/ProfilerEventsAvailableNotification.cs @@ -11,7 +11,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler.Contracts { public class ProfilerEventsAvailableParams { - public string SessionId { get; set; } + public string OwnerUri { get; set; } public List Events { get; set; } } @@ -23,5 +23,3 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler.Contracts EventType.Create("profiler/eventsavailable"); } } - - diff --git a/src/Microsoft.SqlTools.ServiceLayer/Profiler/Contracts/StopProfilingRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/Profiler/Contracts/StopProfilingRequest.cs index 45428259..8510083b 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Profiler/Contracts/StopProfilingRequest.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Profiler/Contracts/StopProfilingRequest.cs @@ -13,7 +13,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler.Contracts /// public class StopProfilingParams { - public string SessionId { get; set; } + public string OwnerUri { get; set; } } public class StopProfilingResult diff --git a/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerService.cs b/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerService.cs index 78dd6177..d430f6cd 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerService.cs @@ -129,7 +129,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler if (connInfo != null) { - ProfilerSession session = StartSession(connInfo); + ProfilerSession session = StartSession(parameters.OwnerUri, connInfo); result.SessionId = session.SessionId; result.Succeeded = true; } @@ -154,7 +154,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler { try { - monitor.StopMonitoringSession(parameters.SessionId); + monitor.StopMonitoringSession(parameters.OwnerUri); await requestContext.SendResult(new StopProfilingResult { Succeeded = true @@ -169,13 +169,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler /// /// Starts a new profiler session for the provided connection /// - internal ProfilerSession StartSession(ConnectionInfo connInfo) + internal ProfilerSession StartSession(string sessionId, ConnectionInfo connInfo) { // create a new XEvent session and Profiler session var xeSession = this.XEventSessionFactory.CreateXEventSession(connInfo); var profilerSession = new ProfilerSession() { - SessionId = Guid.NewGuid().ToString(), + SessionId = sessionId, XEventSession = xeSession }; @@ -208,8 +208,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler private static Session GetOrCreateSession(SqlStoreConnection connection, string sessionName) { XEStore store = new XEStore(connection); - Session session = store.Sessions["Profiler"]; + Session session = store.Sessions[sessionName]; // start the session if it isn't already running + if (session == null) + { + session = CreateSession(connection, sessionName); + } + if (session != null && !session.IsRunning) { session.Start(); @@ -217,6 +222,38 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler return session; } + private static Session CreateSession(SqlStoreConnection connection, string sessionName) + { + string createSessionSql = + @" + CREATE EVENT SESSION [Profiler] ON SERVER + ADD EVENT sqlserver.attention( + ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.nt_username,sqlserver.query_hash,sqlserver.server_principal_name,sqlserver.session_id) + WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))), + ADD EVENT sqlserver.existing_connection(SET collect_options_text=(1) + ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.nt_username,sqlserver.server_principal_name,sqlserver.session_id)), + ADD EVENT sqlserver.login(SET collect_options_text=(1) + ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.nt_username,sqlserver.server_principal_name,sqlserver.session_id)), + ADD EVENT sqlserver.logout( + ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.nt_username,sqlserver.server_principal_name,sqlserver.session_id)), + ADD EVENT sqlserver.rpc_completed( + ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.nt_username,sqlserver.query_hash,sqlserver.server_principal_name,sqlserver.session_id) + WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))), + ADD EVENT sqlserver.sql_batch_completed( + ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.nt_username,sqlserver.query_hash,sqlserver.server_principal_name,sqlserver.session_id) + WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))), + ADD EVENT sqlserver.sql_batch_starting( + ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.nt_username,sqlserver.query_hash,sqlserver.server_principal_name,sqlserver.session_id) + WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))) + ADD TARGET package0.ring_buffer(SET max_events_limit=(1000),max_memory=(51200)) + WITH (MAX_MEMORY=8192 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=5 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=PER_CPU,TRACK_CAUSALITY=ON,STARTUP_STATE=OFF)"; + + connection.ServerConnection.ExecuteNonQuery(createSessionSql); + + XEStore store = new XEStore(connection); + return store.Sessions[sessionName]; + } + /// /// Callback when profiler events are available /// @@ -227,7 +264,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler ProfilerEventsAvailableNotification.Type, new ProfilerEventsAvailableParams() { - SessionId = sessionId, + OwnerUri = sessionId, Events = events }); } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerSession.cs b/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerSession.cs index c661bf8f..f15ed0ea 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerSession.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerSession.cs @@ -89,6 +89,42 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler } } + /// + /// Determine if an event was caused by the XEvent polling queries + /// + private bool IsProfilerEvent(ProfilerEvent currentEvent) + { + if (string.IsNullOrWhiteSpace(currentEvent.Name) || currentEvent.Values == null) + { + return false; + } + + if ((currentEvent.Name.Equals("sql_batch_completed") + || currentEvent.Name.Equals("sql_batch_starting")) + && currentEvent.Values.ContainsKey("batch_text")) + { + return currentEvent.Values["batch_text"].Contains("SELECT target_data FROM sys.dm_xe_session_targets"); + } + + return false; + } + + /// + /// Removed profiler polling events from event list + /// + public List FilterProfilerEvents(List events) + { + int idx = events.Count; + while (--idx >= 0) + { + if (IsProfilerEvent(events[idx])) + { + events.RemoveAt(idx); + } + } + return events; + } + /// /// Filter the event list to not include previously seen events /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerSessionMonitor.cs b/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerSessionMonitor.cs index 44b0f0db..310cb6d6 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerSessionMonitor.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerSessionMonitor.cs @@ -160,7 +160,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler session.IsPolling = false; } - return session.FilterOldEvents(events); + return session.FilterProfilerEvents( + session.FilterOldEvents(events) + ); } /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/Workspace/Workspace.cs b/src/Microsoft.SqlTools.ServiceLayer/Workspace/Workspace.cs index 910c03a9..a4813d7b 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Workspace/Workspace.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Workspace/Workspace.cs @@ -104,7 +104,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace if (!this.workspaceFiles.TryGetValue(keyName, out scriptFile)) { if (IsUntitled(resolvedFile.FilePath) - || !resolvedFile.CanReadFromDisk) + || !resolvedFile.CanReadFromDisk + || !File.Exists(resolvedFile.FilePath)) { // It's either not a registered untitled file, or not a valid file on disk // so any attempt to read from disk will fail. diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Profiler/ProfilerSessionTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Profiler/ProfilerSessionTests.cs index b5227b8b..2e7dcfa7 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Profiler/ProfilerSessionTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Profiler/ProfilerSessionTests.cs @@ -48,6 +48,30 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Profiler Assert.Equal(newProfilerEvents.Count, 0); } + + /// + /// Test the FilterProfilerEvents method + /// + [Fact] + public void TestFilterProfilerEvents() + { + // create a profiler session and get some test events + var profilerSession = new ProfilerSession(); + var profilerEvents = ProfilerTestObjects.TestProfilerEvents; + + int expectedEventCount = profilerEvents.Count; + + // add a new "Profiler Polling" event + var newEvent = new ProfilerEvent("sql_batch_completed", "1/1/2017"); + newEvent.Values.Add("batch_text", "SELECT target_data FROM sys.dm_xe_session_targets"); + profilerEvents.Add(newEvent); + + // verify that the polling event is removed + Assert.Equal(profilerEvents.Count, expectedEventCount + 1); + var newProfilerEvents = profilerSession.FilterProfilerEvents(profilerEvents); + Assert.Equal(newProfilerEvents.Count, expectedEventCount); + } + /// /// Test the TryEnterPolling method ///