From 0d7a3b4168659e0e747be498cb8d6c0b45d2a3fa Mon Sep 17 00:00:00 2001 From: Alan Ren Date: Wed, 8 Dec 2021 10:49:23 -0800 Subject: [PATCH] Revert "Update to XElite (#1287)" (#1334) This reverts commit 822a6459ce60aec3d10ef85079357f4346a15842. --- Packages.props | 1 - .../Localization/sr.cs | 16 -- .../Localization/sr.resx | 10 - .../Localization/sr.strings | 3 +- .../Localization/sr.xlf | 12 -- .../Microsoft.SqlTools.ServiceLayer.csproj | 1 - .../Profiler/IProfilerSessionMonitor.cs | 2 +- .../Profiler/IXEventSession.cs | 12 -- .../Profiler/ProfilerService.cs | 107 ++++------ .../Profiler/ProfilerSession.cs | 66 +++++- .../Profiler/ProfilerSessionMonitor.cs | 202 +++++++++++------- .../Profiler/XEventSession.cs | 5 +- .../Profiler/ProfilerServiceTests.cs | 83 +++++-- .../Profiler/ProfilerSessionTests.cs | 36 ++++ .../Profiler/ProfilerTestObjects.cs | 13 -- 15 files changed, 326 insertions(+), 243 deletions(-) diff --git a/Packages.props b/Packages.props index a5d44818..9a9bf056 100644 --- a/Packages.props +++ b/Packages.props @@ -30,7 +30,6 @@ - diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs index f523424f..684f4cea 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs @@ -8761,16 +8761,6 @@ namespace Microsoft.SqlTools.ServiceLayer return Keys.GetString(Keys.SessionAlreadyExists, sessionName); } - public static string SessionMissingDetails(int id) - { - return Keys.GetString(Keys.SessionMissingDetails, id); - } - - public static string StartProfilingFailed(String error) - { - return Keys.GetString(Keys.StartProfilingFailed, error); - } - public static string UnknownSizeUnit(string unit) { return Keys.GetString(Keys.UnknownSizeUnit, unit); @@ -9962,12 +9952,6 @@ namespace Microsoft.SqlTools.ServiceLayer public const string SessionAlreadyExists = "SessionAlreadyExists"; - public const string SessionMissingDetails = "SessionMissingDetails"; - - - public const string StartProfilingFailed = "StartProfilingFailed"; - - public const string CategoryLocal = "CategoryLocal"; diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx index 128499d6..74435378 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx @@ -1576,16 +1576,6 @@ An XEvent session named {0} already exists . Parameters: 0 - sessionName (String) - - - Unable to start streaming session {0} due to missing session details. - . - Parameters: 0 - id (int) - - - Failed to start profiler: {0} - . - Parameters: 0 - error (String) [Uncategorized (Local)] diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings index a40d9dce..60d6183d 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings @@ -761,8 +761,7 @@ PauseSessionFailed(String error) = Failed to pause session: {0} StopSessionFailed(String error) = Failed to stop session: {0} SessionNotFound = Cannot find requested XEvent session SessionAlreadyExists(String sessionName) = An XEvent session named {0} already exists -SessionMissingDetails(int id) = Unable to start streaming session {0} due to missing session details. -StartProfilingFailed(String error) = Failed to start profiler: {0} + ;job categories CategoryLocal = [Uncategorized (Local)] diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf index 6dbd66a1..e20d0667 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf @@ -5696,18 +5696,6 @@ Specifies whether the check constraint is Enabled - - Unable to start streaming session {0} due to missing session details. - Unable to start streaming session {0} due to missing session details. - . - Parameters: 0 - id (int) - - - Failed to start profiler: {0} - Failed to start profiler: {0} - . - Parameters: 0 - error (String) - The underlying type "{0}" for sql variant column "{1}" could not be resolved. The underlying type "{0}" for sql variant column "{1}" could not be resolved. diff --git a/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj b/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj index 50ddb5a6..95d1f2a5 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj +++ b/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj @@ -26,7 +26,6 @@ - diff --git a/src/Microsoft.SqlTools.ServiceLayer/Profiler/IProfilerSessionMonitor.cs b/src/Microsoft.SqlTools.ServiceLayer/Profiler/IProfilerSessionMonitor.cs index 2b6aa292..4fd5a6d3 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Profiler/IProfilerSessionMonitor.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Profiler/IProfilerSessionMonitor.cs @@ -22,7 +22,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler /// /// Starts monitoring a profiler session /// - void StartMonitoringSession(string viewerId, IXEventSession session); + bool StartMonitoringSession(string viewerId, IXEventSession session); /// /// Stops monitoring a profiler session diff --git a/src/Microsoft.SqlTools.ServiceLayer/Profiler/IXEventSession.cs b/src/Microsoft.SqlTools.ServiceLayer/Profiler/IXEventSession.cs index 61a262fa..4e91be3a 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Profiler/IXEventSession.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Profiler/IXEventSession.cs @@ -2,8 +2,6 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; -using Microsoft.SqlServer.Management.XEvent; namespace Microsoft.SqlTools.ServiceLayer.Profiler { @@ -17,16 +15,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler /// int Id { get; } - /// - /// Connection details associated with the session id. - /// - ConnectionDetails ConnectionDetails { get; set; } - - /// - /// Session associated with the session id. - /// - Session Session { get; set; } - /// /// Starts XEvent session /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerService.cs b/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerService.cs index 59e7f89a..dc659fbc 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerService.cs @@ -18,7 +18,6 @@ using Microsoft.SqlServer.Management.Sdk.Sfc; using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlServer.Management.XEvent; using Microsoft.SqlServer.Management.XEventDbScoped; -using Microsoft.SqlServer.XEvent.XELite; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.Hosting.Protocol.Contracts; using Microsoft.SqlTools.ServiceLayer.Connection; @@ -121,42 +120,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler this.SessionMonitor.AddSessionListener(this); } - /// - /// Handle request to start a profiling session - /// - internal async Task HandleStartProfilingRequest(StartProfilingParams parameters, RequestContext requestContext) - { - await Task.Run(async () => - { - try - { - Logger.Write(TraceEventType.Verbose, "HandleStartProfilingRequest started"); - ConnectionInfo connInfo; - ConnectionServiceInstance.TryFindConnection( - parameters.OwnerUri, - out connInfo); - if (connInfo != null) - { - // create a new XEvent session and Profiler session - var xeSession = this.XEventSessionFactory.GetXEventSession(parameters.SessionName, connInfo); - monitor.StartMonitoringSession(parameters.OwnerUri, xeSession); - var result = new StartProfilingResult(); - await requestContext.SendResult(result); - } - else - { - Logger.Write(TraceEventType.Error, "Connection Info could not be found for " + parameters.OwnerUri); - throw new Exception(SR.ProfilerConnectionNotFound); - } - } - catch (Exception e) - { - Logger.Write(TraceEventType.Error, "HandleStartProfilingRequest failed for uri " + parameters.OwnerUri); - await requestContext.SendError(new Exception (SR.StartProfilingFailed(e.Message), e)); - } - }); - } - /// /// Handle request to start a profiling session /// @@ -166,24 +129,20 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler { try { - Logger.Write(TraceEventType.Verbose, "HandleCreateXEventSessionRequest started"); ConnectionInfo connInfo; ConnectionServiceInstance.TryFindConnection( parameters.OwnerUri, out connInfo); if (connInfo == null) { - Logger.Write(TraceEventType.Error, "Connection Info could not be found for " + parameters.OwnerUri); throw new Exception(SR.ProfilerConnectionNotFound); } else if (parameters.SessionName == null) { - Logger.Write(TraceEventType.Error, "Session Name could not be found for " + parameters.OwnerUri); throw new ArgumentNullException("SessionName"); } else if (parameters.Template == null) { - Logger.Write(TraceEventType.Error, "Template could not be found for " + parameters.OwnerUri); throw new ArgumentNullException("Template"); } else @@ -197,13 +156,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler { xeSession = this.XEventSessionFactory.GetXEventSession(parameters.SessionName, connInfo); } - catch { - Logger.Write(TraceEventType.Verbose, "Session with name '" + parameters.SessionName + "' was not found"); - } + catch { } if (xeSession == null) { - Logger.Write(TraceEventType.Verbose, "Creating new XEventSession with SessionName " + parameters.SessionName); // create a new XEvent session and Profiler session xeSession = this.XEventSessionFactory.CreateXEventSession(parameters.Template.CreateStatement, parameters.SessionName, connInfo); } @@ -219,8 +175,42 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler } catch (Exception e) { - Logger.Write(TraceEventType.Error, "HandleCreateXEventSessionRequest failed for uri " + parameters.OwnerUri); - await requestContext.SendError(new Exception (SR.CreateSessionFailed(e.Message), e)); + await requestContext.SendError(new Exception(SR.CreateSessionFailed(e.Message))); + } + }); + } + + /// + /// Handle request to start a profiling session + /// + internal async Task HandleStartProfilingRequest(StartProfilingParams parameters, RequestContext requestContext) + { + await Task.Run(async () => + { + try + { + ConnectionInfo connInfo; + ConnectionServiceInstance.TryFindConnection( + parameters.OwnerUri, + out connInfo); + if (connInfo != null) + { + // create a new XEvent session and Profiler session + var xeSession = this.XEventSessionFactory.GetXEventSession(parameters.SessionName, connInfo); + // start monitoring the profiler session + monitor.StartMonitoringSession(parameters.OwnerUri, xeSession); + + var result = new StartProfilingResult(); + await requestContext.SendResult(result); + } + else + { + throw new Exception(SR.ProfilerConnectionNotFound); + } + } + catch (Exception e) + { + await requestContext.SendError(new Exception(SR.StartSessionFailed(e.Message))); } }); } @@ -234,7 +224,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler { try { - Logger.Write(TraceEventType.Verbose, "HandleStopProfilingRequest started"); ProfilerSession session; monitor.StopMonitoringSession(parameters.OwnerUri, out session); @@ -251,12 +240,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler await requestContext.SendResult(new StopProfilingResult { }); break; } - catch (InvalidOperationException e) + catch (InvalidOperationException) { remainingAttempts--; if (remainingAttempts == 0) { - Logger.Write(TraceEventType.Error, "Stop profiler session '" + session.XEventSession.Session.Name + "' failed after three retries, last exception was: " + e.Message); throw; } Thread.Sleep(500); @@ -270,7 +258,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler } catch (Exception e) { - Logger.Write(TraceEventType.Error, "HandleStopProfilingRequest failed for uri " + parameters.OwnerUri); await requestContext.SendError(new Exception(SR.StopSessionFailed(e.Message))); } }); @@ -285,14 +272,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler { try { - Logger.Write(TraceEventType.Verbose, "HandlePauseProfilingRequest started"); monitor.PauseViewer(parameters.OwnerUri); await requestContext.SendResult(new PauseProfilingResult { }); } catch (Exception e) { - Logger.Write(TraceEventType.Error, "HandlePauseProfilingRequest failed for uri " + parameters.OwnerUri); await requestContext.SendError(new Exception(SR.PauseSessionFailed(e.Message))); } }); @@ -307,7 +292,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler { try { - Logger.Write(TraceEventType.Verbose, "HandleGetXEventSessionsRequest started"); var result = new GetXEventSessionsResult(); ConnectionInfo connInfo; ConnectionServiceInstance.TryFindConnection( @@ -315,7 +299,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler out connInfo); if (connInfo == null) { - Logger.Write(TraceEventType.Error, "Connection Info could not be found for " + parameters.OwnerUri); await requestContext.SendError(new Exception(SR.ProfilerConnectionNotFound)); } else @@ -327,7 +310,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler } catch (Exception e) { - Logger.Write(TraceEventType.Error, "HandleGetXEventSessionsRequest failed for uri " + parameters.OwnerUri); await requestContext.SendError(e); } }); @@ -342,13 +324,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler { try { - Logger.Write(TraceEventType.Verbose, "HandleDisconnectSessionRequest started"); ProfilerSession session; monitor.StopMonitoringSession(parameters.OwnerUri, out session); } catch (Exception e) { - Logger.Write(TraceEventType.Error, "HandleDisconnectSessionRequest failed for uri " + parameters.OwnerUri); await requestContext.SendError(e); } }); @@ -394,15 +374,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler return store; } - /// - /// Nulls out properties in ConnectionDetails that aren't compatible with XElite. - /// - private static void RemoveIncompatibleConnectionProperties(ConnectionDetails connDetails){ - connDetails.ConnectRetryCount = null; - connDetails.ConnectRetryInterval = null; - connDetails.MultiSubnetFailover = null; - } - /// /// Gets an XEvent session with the given name per the IXEventSessionFactory contract /// Also starts the session if it isn't currently running @@ -412,7 +383,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler var sqlConnection = ConnectionService.OpenSqlConnection(connInfo); SqlStoreConnection connection = new SqlStoreConnection(sqlConnection); BaseXEStore store = CreateXEventStore(connInfo, connection); - RemoveIncompatibleConnectionProperties(connInfo.ConnectionDetails); Session session = store.Sessions[sessionName]; // start the session if it isn't already running @@ -429,7 +399,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler // create xevent session wrapper return new XEventSession() { - ConnectionDetails = connInfo.ConnectionDetails, Session = session }; } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerSession.cs b/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerSession.cs index bce45249..5835cd3b 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerSession.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerSession.cs @@ -17,13 +17,18 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler /// public class ProfilerSession { - public bool IsStreaming { get; set; } - + private static readonly TimeSpan DefaultPollingDelay = TimeSpan.FromSeconds(1); + private object pollingLock = new object(); + private bool isPolling = false; + private DateTime lastPollTime = DateTime.Now.Subtract(DefaultPollingDelay); + private TimeSpan pollingDelay = DefaultPollingDelay; private ProfilerEvent lastSeenEvent = null; private bool eventsLost = false; int lastSeenId = -1; + public bool pollImmediatly = false; + /// /// Connection to use for the session /// @@ -34,6 +39,57 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler /// public IXEventSession XEventSession { get; set; } + /// + /// Try to set the session into polling mode if criteria is meet + /// + /// True if session set to polling mode, False otherwise + public bool TryEnterPolling() + { + lock (this.pollingLock) + { + if (pollImmediatly || (!this.isPolling && DateTime.Now.Subtract(this.lastPollTime) >= pollingDelay)) + { + this.isPolling = true; + this.lastPollTime = DateTime.Now; + this.pollImmediatly = false; + return true; + } + else + { + return false; + } + } + } + + /// + /// Is the session currently being polled + /// + public bool IsPolling + { + get + { + return this.isPolling; + } + set + { + lock (this.pollingLock) + { + this.isPolling = value; + } + } + } + + /// + /// The delay between session polls + /// + public TimeSpan PollingDelay + { + get + { + return pollingDelay; + } + } + /// /// Could events have been lost in the last poll /// @@ -50,7 +106,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler /// private bool IsProfilerEvent(ProfilerEvent currentEvent) { - if (string.IsNullOrWhiteSpace(currentEvent.Name) || currentEvent.Values == null) + if (string.IsNullOrWhiteSpace(currentEvent.Name) || currentEvent.Values == null) { return false; } @@ -89,7 +145,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler public void FilterOldEvents(List events) { this.eventsLost = false; - + if (lastSeenId != -1) { // find the last event we've previously seen @@ -113,7 +169,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler { events.RemoveRange(0, idx + 1); } - else if (earliestSeenEventId > (lastSeenId + 1)) + else if(earliestSeenEventId > (lastSeenId + 1)) { // if there's a gap between the expected next event sequence // and the furthest back event seen, we know we've lost events diff --git a/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerSessionMonitor.cs b/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerSessionMonitor.cs index 3a3f7315..49a411b0 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerSessionMonitor.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerSessionMonitor.cs @@ -4,13 +4,19 @@ // using System; -using System.Linq; +using System.Collections.Concurrent; using System.Collections.Generic; +using Microsoft.Data.SqlClient; +using System.Diagnostics; +using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.SqlServer.XEvent.XELite; -using Microsoft.SqlTools.ServiceLayer.Connection; +using System.Xml; +using Microsoft.SqlServer.Management.Sdk.Sfc; +using Microsoft.SqlServer.Management.XEvent; +using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; using Microsoft.SqlTools.ServiceLayer.Profiler.Contracts; +using Microsoft.SqlTools.Utility; namespace Microsoft.SqlTools.ServiceLayer.Profiler { @@ -19,10 +25,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler /// public class ProfilerSessionMonitor : IProfilerSessionMonitor { + private const int PollingLoopDelay = 1000; + private object sessionsLock = new object(); private object listenersLock = new object(); + private object pollingLock = new object(); + private Task processorThread = null; private struct Viewer @@ -46,9 +56,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler // XEvent Session Id's matched to their Profiler Sessions private Dictionary monitoredSessions = new Dictionary(); - // XEvent Session Id's matched to their stream cancellation tokens - private Dictionary monitoredCancellationTokenSources = new Dictionary(); - // ViewerId -> Viewer objects private Dictionary allViewers = new Dictionary(); @@ -65,7 +72,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler } } - public void StartMonitoringSession(string viewerId, IXEventSession session) + /// + /// Start monitoring the provided session + /// + public bool StartMonitoringSession(string viewerId, IXEventSession session) { lock (this.sessionsLock) { @@ -105,10 +115,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler } else { - viewers = new List { viewerId }; + viewers = new List{ viewerId }; sessionViewers.Add(session.Id, viewers); } } + + return true; } /// @@ -148,16 +160,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler { lock (this.sessionsLock) { - //cancel running XEventStream for session. - CancellationTokenSource targetToken; - if (monitoredCancellationTokenSources.Remove(sessionId, out targetToken)) - { - targetToken.Cancel(); - } if (this.monitoredSessions.Remove(sessionId, out session)) { - session.IsStreaming = false; - //remove all viewers for this session List viewerIds; if (sessionViewers.Remove(sessionId, out viewerIds)) @@ -182,105 +186,113 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler } } + public void PollSession(int sessionId) + { + lock (this.sessionsLock) + { + this.monitoredSessions[sessionId].pollImmediatly = true; + } + lock (this.pollingLock) + { + Monitor.Pulse(pollingLock); + } + } + /// - /// The core queue processing method, cycles through monitored sessions and creates a stream for them if not already. + /// The core queue processing method /// + /// private void ProcessSessions() { while (true) { - lock (this.sessionsLock) + lock (this.pollingLock) { - foreach (var id in this.monitoredSessions.Keys) + lock (this.sessionsLock) { - ProfilerSession session; - this.monitoredSessions.TryGetValue(id, out session); - if (!session.IsStreaming) + foreach (var session in this.monitoredSessions.Values) { List viewers = this.sessionViewers[session.XEventSession.Id]; - if (viewers.Any(v => allViewers[v].active)){ - StartStream(id, session); + if (viewers.Any(v => allViewers[v].active)) + { + ProcessSession(session); } } } + Monitor.Wait(this.pollingLock, PollingLoopDelay); } } } /// - /// Helper function used to process the XEvent feed from a session's stream. + /// Process a session for new XEvents if it meets the polling criteria /// - private async Task HandleXEvent(IXEvent xEvent, ProfilerSession session) + private void ProcessSession(ProfilerSession session) { - ProfilerEvent profileEvent = new ProfilerEvent(xEvent.Name, xEvent.Timestamp.ToString()); - foreach (var kvp in xEvent.Fields) + if (session.TryEnterPolling()) { - profileEvent.Values.Add(kvp.Key, kvp.Value.ToString()); - } - foreach (var kvp in xEvent.Actions) - { - profileEvent.Values.Add(kvp.Key, kvp.Value.ToString()); - } - var eventList = new List(); - eventList.Add(profileEvent); - var eventsLost = session.EventsLost; - - if (eventList.Count > 0 || eventsLost) - { - session.FilterOldEvents(eventList); - eventList = session.FilterProfilerEvents(eventList); - // notify all viewers of the event. - List viewerIds = this.sessionViewers[session.XEventSession.Id]; - - foreach (string viewerId in viewerIds) + Task.Factory.StartNew(() => { - if (allViewers[viewerId].active) + var events = PollSession(session); + bool eventsLost = session.EventsLost; + if (events.Count > 0 || eventsLost) { - SendEventsToListeners(viewerId, eventList, eventsLost); + // notify all viewers for the polled session + List viewerIds = this.sessionViewers[session.XEventSession.Id]; + foreach (string viewerId in viewerIds) + { + if (allViewers[viewerId].active) + { + SendEventsToListeners(viewerId, events, eventsLost); + } + } } - } + }); } } - /// - /// Function that creates a brand new stream from a session, this is called from ProcessSessions when a session doesn't have a stream running currently. - /// - private void StartStream(int id, ProfilerSession session) + private List PollSession(ProfilerSession session) { - if(session.XEventSession != null && session.XEventSession.Session != null && session.XEventSession.ConnectionDetails != null){ - CancellationTokenSource threadCancellationToken = new CancellationTokenSource(); - var connectionString = ConnectionService.BuildConnectionString(session.XEventSession.ConnectionDetails); - var eventStreamer = new XELiveEventStreamer(connectionString, session.XEventSession.Session.Name); - // Start streaming task here, will run until cancellation or error with the feed. - var task = eventStreamer.ReadEventStream(xEvent => HandleXEvent(xEvent, session), threadCancellationToken.Token); - - task.ContinueWith(t => + var events = new List(); + try + { + if (session == null || session.XEventSession == null) { - //If cancellation token is missing, that means stream was stopped by the client, do not notify in this case. - CancellationTokenSource targetToken; - if (monitoredCancellationTokenSources.TryGetValue(id, out targetToken)) - { - StopSession(session.XEventSession.Id); - } - }, TaskContinuationOptions.OnlyOnFaulted); + return events; + } - this.monitoredCancellationTokenSources.Add(id, threadCancellationToken); - session.IsStreaming = true; + var targetXml = session.XEventSession.GetTargetXml(); + + XmlDocument xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(targetXml); + + var nodes = xmlDoc.DocumentElement.GetElementsByTagName("event"); + foreach (XmlNode node in nodes) + { + var profilerEvent = ParseProfilerEvent(node); + if (profilerEvent != null) + { + events.Add(profilerEvent); + } + } } - else { + catch (XEventException) + { + SendStoppedSessionInfoToListeners(session.XEventSession.Id); ProfilerSession tempSession; - RemoveSession(id, out tempSession); - throw new Exception(SR.SessionMissingDetails(id)); + RemoveSession(session.XEventSession.Id, out tempSession); + } + catch (Exception ex) + { + Logger.Write(TraceEventType.Warning, "Failed to poll session. error: " + ex.Message); + } + finally + { + session.IsPolling = false; } - } - /// - /// Helper function for notifying listeners and stopping session in case the session is stopped on the server. This is public for tests. - /// - public void StopSession(int Id){ - SendStoppedSessionInfoToListeners(Id); - ProfilerSession tempSession; - RemoveSession(Id, out tempSession); + session.FilterOldEvents(events); + return session.FilterProfilerEvents(events); } /// @@ -292,7 +304,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler { foreach (var listener in this.listeners) { - foreach (string viewerId in sessionViewers[sessionId]) + foreach(string viewerId in sessionViewers[sessionId]) { listener.SessionStopped(viewerId, sessionId); } @@ -313,5 +325,31 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler } } } + + /// + /// Parse a single event node from XEvent XML + /// + private ProfilerEvent ParseProfilerEvent(XmlNode node) + { + var name = node.Attributes["name"]; + var timestamp = node.Attributes["timestamp"]; + + var profilerEvent = new ProfilerEvent(name.InnerText, timestamp.InnerText); + + foreach (XmlNode childNode in node.ChildNodes) + { + var childName = childNode.Attributes["name"]; + XmlNode typeNode = childNode.SelectSingleNode("type"); + var typeName = typeNode.Attributes["name"]; + XmlNode valueNode = childNode.SelectSingleNode("value"); + + if (!profilerEvent.Values.ContainsKey(childName.InnerText)) + { + profilerEvent.Values.Add(childName.InnerText, valueNode.InnerText); + } + } + + return profilerEvent; + } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Profiler/XEventSession.cs b/src/Microsoft.SqlTools.ServiceLayer/Profiler/XEventSession.cs index 8c2ba38c..b1a3903c 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Profiler/XEventSession.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Profiler/XEventSession.cs @@ -5,7 +5,8 @@ using System.Linq; using Microsoft.SqlServer.Management.XEvent; -using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; +using Microsoft.SqlTools.ServiceLayer.Connection; +using Microsoft.SqlTools.ServiceLayer.Profiler.Contracts; namespace Microsoft.SqlTools.ServiceLayer.Profiler { @@ -16,8 +17,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler { public Session Session { get; set; } - public ConnectionDetails ConnectionDetails { get; set; } - public int Id { get { return Session.ID; } diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Profiler/ProfilerServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Profiler/ProfilerServiceTests.cs index fffb1f9e..7efe8216 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Profiler/ProfilerServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Profiler/ProfilerServiceTests.cs @@ -65,12 +65,17 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Profiler // start profiling session await profilerService.HandleStartProfilingRequest(requestParams, requestContext.Object); + profilerService.SessionMonitor.PollSession(1); + // simulate a short polling delay + Thread.Sleep(200); + profilerService.SessionMonitor.PollSession(1); + // wait for polling to finish, or for timeout System.Timers.Timer pollingTimer = new System.Timers.Timer(); pollingTimer.Interval = 10000; pollingTimer.Start(); bool timeout = false; - pollingTimer.Elapsed += new System.Timers.ElapsedEventHandler((s_, e_) => { timeout = true; }); + pollingTimer.Elapsed += new System.Timers.ElapsedEventHandler((s_, e_) => {timeout = true;}); while (sessionId == null && !timeout) { Thread.Sleep(250); @@ -120,8 +125,6 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Profiler ConnectionInfo connectionInfo = TestObjects.GetTestConnectionInfo(); profilerService.ConnectionServiceInstance.OwnerToConnectionMap.Add(testUri, connectionInfo); profilerService.XEventSessionFactory = new TestXEventSessionFactory(); - mockSession.SetupProperty(p => p.ConnectionDetails, connectionInfo.ConnectionDetails); - mockSession.SetupProperty(p => p.Session, new Session(null, testUri)); var requestParams = new StopProfilingParams(); requestParams.OwnerUri = testUri; @@ -146,12 +149,12 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Profiler /// /// [Test] - // TODO: Add more in-depth testing for pause effects on events received. public async Task TestPauseProfilingRequest() { bool success = false; string testUri = "test_session"; - + bool recievedEvents = false; + // capture pausing results var requestContext = new Mock>(); requestContext.Setup(rc => rc.SendResult(It.IsAny())) @@ -161,34 +164,75 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Profiler return Task.FromResult(0); }); + // capture Listener event notifications + var mockListener = new Mock(); + mockListener.Setup(p => p.EventsAvailable(It.IsAny(), It.IsAny>(), It.IsAny())).Callback(() => + { + recievedEvents = true; + }); + // setup profiler service - var mockListener = new TestSessionListener(); var profilerService = new ProfilerService(); - profilerService.SessionMonitor.AddSessionListener(mockListener); + profilerService.SessionMonitor.AddSessionListener(mockListener.Object); profilerService.ConnectionServiceInstance = TestObjects.GetTestConnectionService(); ConnectionInfo connectionInfo = TestObjects.GetTestConnectionInfo(); profilerService.ConnectionServiceInstance.OwnerToConnectionMap.Add(testUri, connectionInfo); - var testSession = new TestXEventSession1(); - testSession.ConnectionDetails = connectionInfo.ConnectionDetails; - testSession.Session = new Session(null, testUri); - var requestParams = new PauseProfilingParams(); requestParams.OwnerUri = testUri; // begin monitoring session - profilerService.SessionMonitor.StartMonitoringSession(testUri, testSession); + profilerService.SessionMonitor.StartMonitoringSession(testUri, new TestXEventSession1()); + + // poll the session + profilerService.SessionMonitor.PollSession(1); + Thread.Sleep(500); + profilerService.SessionMonitor.PollSession(1); + + // wait for polling to finish, or for timeout + System.Timers.Timer pollingTimer = new System.Timers.Timer(); + pollingTimer.Interval = 10000; + pollingTimer.Start(); + bool timeout = false; + pollingTimer.Elapsed += new System.Timers.ElapsedEventHandler((s_, e_) => {timeout = true;}); + while (!recievedEvents && !timeout) + { + Thread.Sleep(250); + } + pollingTimer.Stop(); + + // confirm that polling works + Assert.True(recievedEvents); // pause viewer await profilerService.HandlePauseProfilingRequest(requestParams, requestContext.Object); Assert.True(success); + recievedEvents = false; success = false; + profilerService.SessionMonitor.PollSession(1); + + // confirm that no events were sent to paused Listener + Assert.False(recievedEvents); + // unpause viewer await profilerService.HandlePauseProfilingRequest(requestParams, requestContext.Object); Assert.True(success); + profilerService.SessionMonitor.PollSession(1); + + // wait for polling to finish, or for timeout + timeout = false; + pollingTimer.Start(); + while (!recievedEvents && !timeout) + { + Thread.Sleep(250); + } + + // check that events got sent to Listener + Assert.True(recievedEvents); + requestContext.VerifyAll(); } @@ -219,14 +263,21 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Profiler profilerService.ConnectionServiceInstance = TestObjects.GetTestConnectionService(); ConnectionInfo connectionInfo = TestObjects.GetTestConnectionInfo(); profilerService.ConnectionServiceInstance.OwnerToConnectionMap.Add(testUri, connectionInfo); - mockSession.SetupProperty(p => p.ConnectionDetails, connectionInfo.ConnectionDetails); - mockSession.SetupProperty(p => p.Session, new Session(null, testUri)); // start monitoring test session profilerService.SessionMonitor.StartMonitoringSession(testUri, mockSession.Object); - // Call stop session to simulate when a server has stopped a session on its side. - profilerService.SessionMonitor.StopSession(0); + // wait for polling to finish, or for timeout + System.Timers.Timer pollingTimer = new System.Timers.Timer(); + pollingTimer.Interval = 10000; + pollingTimer.Start(); + bool timeout = false; + pollingTimer.Elapsed += new System.Timers.ElapsedEventHandler((s_, e_) => {timeout = true;}); + while (sessionStopped == false && !timeout) + { + Thread.Sleep(250); + } + pollingTimer.Stop(); // check that a stopped session notification was sent Assert.True(sessionStopped); diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Profiler/ProfilerSessionTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Profiler/ProfilerSessionTests.cs index 63d96268..85a00e83 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Profiler/ProfilerSessionTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Profiler/ProfilerSessionTests.cs @@ -123,5 +123,41 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Profiler profilerSession.FilterOldEvents(profilerEvents); Assert.False(profilerSession.EventsLost); } + + /// + /// Test the TryEnterPolling method + /// + [Test] + public void TestTryEnterPolling() + { + DateTime startTime = DateTime.Now; + + // create new profiler session + var profilerSession = new ProfilerSession(); + + // enter the polling block + Assert.True(profilerSession.TryEnterPolling()); + Assert.True(profilerSession.IsPolling); + + // verify we can't enter again + Assert.False(profilerSession.TryEnterPolling()); + + // set polling to false to exit polling block + profilerSession.IsPolling = false; + + bool outsideDelay = DateTime.Now.Subtract(startTime) >= profilerSession.PollingDelay; + + // verify we can only enter again if we're outside polling delay interval + Assert.AreEqual(profilerSession.TryEnterPolling(), outsideDelay); + + // reset IsPolling in case the delay has elasped on slow machine or while debugging + profilerSession.IsPolling = false; + + // wait for the polling delay to elapse + Thread.Sleep(profilerSession.PollingDelay); + + // verify we can enter the polling block again + Assert.True(profilerSession.TryEnterPolling()); + } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Profiler/ProfilerTestObjects.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Profiler/ProfilerTestObjects.cs index 198f6f77..94dcd171 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Profiler/ProfilerTestObjects.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Profiler/ProfilerTestObjects.cs @@ -11,7 +11,6 @@ using System.Threading.Tasks; using Microsoft.SqlServer.Management.XEvent; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Connection; -using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; using Microsoft.SqlTools.ServiceLayer.Profiler; using Microsoft.SqlTools.ServiceLayer.Profiler.Contracts; using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility; @@ -207,10 +206,6 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Profiler public int Id { get { return 51; } } - public ConnectionDetails ConnectionDetails { get; set; } - - public Session Session { get; set; } - public void Start(){} public void Stop(){} @@ -296,10 +291,6 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Profiler public int Id { get { return 1; } } - public ConnectionDetails ConnectionDetails { get; set; } - - public Session Session { get; set; } - public void Start(){} public void Stop(){} @@ -391,10 +382,6 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Profiler public int Id { get { return 2; } } - public ConnectionDetails ConnectionDetails { get; set; } - - public Session Session { get; set; } - public void Start(){} public void Stop(){}