From f53e532225259b9eee8f00deb2c91e790d2cb54c Mon Sep 17 00:00:00 2001 From: Madeline MacDonald Date: Wed, 13 Jun 2018 17:55:01 -0700 Subject: [PATCH] Adding pausing functionality for the profiler (#634) * Dropping profiler session on stop request * Changes to IXEventSession to simplify dropping sessions * Stop sessions instead of dropping, disable flaky tests * Initial framework for profiler pause requests * Restructuring profiler session monitoring * Fixes to session monitor * Testing for pause functionality * Fixing comments from PR * Changes to testing * Commenting out flaky test * Deleting leftover testing code --- .../Contracts/PauseProfilingRequest.cs | 34 ++++ .../Contracts/StopProfilingRequest.cs | 7 +- .../Profiler/IProfilerSessionMonitor.cs | 9 +- .../Profiler/IXEventSession.cs | 14 +- .../Profiler/IXEventSessionFactory.cs | 4 +- .../Profiler/ProfilerService.cs | 78 ++++---- .../Profiler/ProfilerSession.cs | 20 +- .../Profiler/ProfilerSessionMonitor.cs | 171 +++++++++++++++--- .../Profiler/XEventSession.cs | 13 ++ .../Profiler/ProfilerServiceTests.cs | 6 +- .../Profiler/ProfilerServiceTests.cs | 141 +++++++++++++-- .../Profiler/ProfilerTestObjects.cs | 153 +++++++++++++++- 12 files changed, 549 insertions(+), 101 deletions(-) create mode 100644 src/Microsoft.SqlTools.ServiceLayer/Profiler/Contracts/PauseProfilingRequest.cs diff --git a/src/Microsoft.SqlTools.ServiceLayer/Profiler/Contracts/PauseProfilingRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/Profiler/Contracts/PauseProfilingRequest.cs new file mode 100644 index 00000000..c3f0d1f6 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Profiler/Contracts/PauseProfilingRequest.cs @@ -0,0 +1,34 @@ +// +// 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.Hosting.Protocol.Contracts; +using Microsoft.SqlTools.ServiceLayer.Utility; +using Microsoft.SqlTools.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.Profiler.Contracts +{ + /// + /// Pause Profiling request parameters + /// + public class PauseProfilingParams : GeneralRequestDetails + { + public string OwnerUri { get; set; } + } + + public class PauseProfilingResult{} + + /// + /// Pause Profile request type + /// + public class PauseProfilingRequest + { + /// + /// Request definition + /// + public static readonly + RequestType Type = + RequestType.Create("profiler/pause"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Profiler/Contracts/StopProfilingRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/Profiler/Contracts/StopProfilingRequest.cs index 8510083b..da652675 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Profiler/Contracts/StopProfilingRequest.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Profiler/Contracts/StopProfilingRequest.cs @@ -16,12 +16,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler.Contracts public string OwnerUri { get; set; } } - public class StopProfilingResult - { - public bool Succeeded { get; set; } - - public string ErrorMessage { get; set; } - } + public class StopProfilingResult{} /// /// Start Profile request type diff --git a/src/Microsoft.SqlTools.ServiceLayer/Profiler/IProfilerSessionMonitor.cs b/src/Microsoft.SqlTools.ServiceLayer/Profiler/IProfilerSessionMonitor.cs index 1579f839..b47a1a50 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Profiler/IProfilerSessionMonitor.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Profiler/IProfilerSessionMonitor.cs @@ -22,11 +22,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler /// /// Starts monitoring a profiler session /// - bool StartMonitoringSession(ProfilerSession session); + bool StartMonitoringSession(string viewerId, IXEventSession session); /// /// Stops monitoring a profiler session /// - bool StopMonitoringSession(string sessionId, out ProfilerSession session); + bool StopMonitoringSession(string viewerId, out ProfilerSession session); + + /// + /// Pauses or Unpauses the stream of events to the viewer + /// + void PauseViewer(string viewerId); } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Profiler/IXEventSession.cs b/src/Microsoft.SqlTools.ServiceLayer/Profiler/IXEventSession.cs index 5d46dd3b..4e91be3a 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Profiler/IXEventSession.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Profiler/IXEventSession.cs @@ -11,13 +11,23 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler public interface IXEventSession { /// - /// Reads XEvent XML from the default session target + /// Gets unique XEvent session Id /// - string GetTargetXml(); + int Id { get; } + + /// + /// Starts XEvent session + /// + void Start(); /// /// Stops XEvent session /// void Stop(); + + /// + /// Reads XEvent XML from the default session target + /// + string GetTargetXml(); } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Profiler/IXEventSessionFactory.cs b/src/Microsoft.SqlTools.ServiceLayer/Profiler/IXEventSessionFactory.cs index 24e46152..36bb7b82 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Profiler/IXEventSessionFactory.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Profiler/IXEventSessionFactory.cs @@ -16,8 +16,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler public interface IXEventSessionFactory { /// - /// Create a new XEvent session + /// Gets or creates an XEvent session with the given template /// - IXEventSession CreateXEventSession(ConnectionInfo connInfo); + IXEventSession GetOrCreateXEventSession(string template, ConnectionInfo connInfo); } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerService.cs b/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerService.cs index c25629d2..169030ff 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerService.cs @@ -110,6 +110,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler this.ServiceHost = serviceHost; this.ServiceHost.SetRequestHandler(StartProfilingRequest.Type, HandleStartProfilingRequest); this.ServiceHost.SetRequestHandler(StopProfilingRequest.Type, HandleStopProfilingRequest); + this.ServiceHost.SetRequestHandler(PauseProfilingRequest.Type, HandlePauseProfilingRequest); this.SessionMonitor.AddSessionListener(this); } @@ -129,8 +130,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler if (connInfo != null) { - ProfilerSession session = StartSession(parameters.OwnerUri, connInfo); - result.SessionId = session.SessionId; + int xEventSessionId = StartSession(parameters.OwnerUri, parameters.TemplateName, connInfo); + result.SessionId = xEventSessionId.ToString(); result.Succeeded = true; } else @@ -158,10 +159,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler monitor.StopMonitoringSession(parameters.OwnerUri, out session); session.XEventSession.Stop(); - await requestContext.SendResult(new StopProfilingResult - { - Succeeded = true - }); + await requestContext.SendResult(new StopProfilingResult{}); } catch (Exception e) { @@ -170,48 +168,53 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler } /// - /// Starts a new profiler session for the provided connection + /// Handle request to pause a profiling session /// - internal ProfilerSession StartSession(string sessionId, ConnectionInfo connInfo) + internal async Task HandlePauseProfilingRequest(PauseProfilingParams parameters, RequestContext requestContext) + { + try + { + monitor.PauseViewer(parameters.OwnerUri); + + await requestContext.SendResult(new PauseProfilingResult{}); + } + catch (Exception e) + { + await requestContext.SendError(e); + } + } + + /// + /// Starts a new profiler session or connects to an existing session + /// for the provided connection and template info + /// + /// + /// The XEvent Session Id that was started + /// + internal int StartSession(string ownerUri, string template, ConnectionInfo connInfo) { // create a new XEvent session and Profiler session - var xeSession = this.XEventSessionFactory.CreateXEventSession(connInfo); - var profilerSession = new ProfilerSession() - { - SessionId = sessionId, - XEventSession = xeSession - }; + var xeSession = this.XEventSessionFactory.GetOrCreateXEventSession(template, connInfo); // start monitoring the profiler session - monitor.StartMonitoringSession(profilerSession); + monitor.StartMonitoringSession(ownerUri, xeSession); - return profilerSession; + return xeSession.Id; } /// - /// Create a new XEvent sessions per the IXEventSessionFactory contract - /// - public IXEventSession CreateXEventSession(ConnectionInfo connInfo) - { - var sqlConnection = ConnectionService.OpenSqlConnection(connInfo); - SqlStoreConnection connection = new SqlStoreConnection(sqlConnection); - Session session = ProfilerService.GetOrCreateSession(connection, "Profiler"); - - // create xevent session wrapper - return new XEventSession() - { - Session = session - }; - } - - /// - /// Gets an existing XEvent session or creates one if no matching session exists. + /// Gets or creates an XEvent session with the given template per the IXEventSessionFactory contract /// Also starts the session if it isn't currently running /// - private static Session GetOrCreateSession(SqlStoreConnection connection, string sessionName) + public IXEventSession GetOrCreateXEventSession(string template, ConnectionInfo connInfo) { + string sessionName = "Profiler"; + + var sqlConnection = ConnectionService.OpenSqlConnection(connInfo); + SqlStoreConnection connection = new SqlStoreConnection(sqlConnection); XEStore store = new XEStore(connection); Session session = store.Sessions[sessionName]; + // start the session if it isn't already running if (session == null) { @@ -222,7 +225,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler { session.Start(); } - return session; + + // create xevent session wrapper + return new XEventSession() + { + Session = session + }; } private static Session CreateSession(SqlStoreConnection connection, string sessionName) diff --git a/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerSession.cs b/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerSession.cs index 9d5f59f7..66a73bf2 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerSession.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerSession.cs @@ -24,10 +24,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler private TimeSpan pollingDelay = DefaultPollingDelay; private ProfilerEvent lastSeenEvent = null; - /// - /// Unique ID for the session - /// - public string SessionId { get; set; } + public bool pollImmediatly = false; /// /// Connection to use for the session @@ -47,10 +44,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler { lock (this.pollingLock) { - if (!this.isPolling && DateTime.Now.Subtract(this.lastPollTime) >= pollingDelay) + if (pollImmediatly || (!this.isPolling && DateTime.Now.Subtract(this.lastPollTime) >= pollingDelay)) { this.isPolling = true; this.lastPollTime = DateTime.Now; + this.pollImmediatly = false; return true; } else @@ -81,7 +79,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler /// /// The delay between session polls /// - public TimeSpan PollingDelay + public TimeSpan PollingDelay { get { @@ -108,15 +106,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler 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); @@ -126,7 +124,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler } /// - /// Filter the event list to not include previously seen events, + /// Filter the event list to not include previously seen events, /// and to exclude events that happened before the profiling session began. /// public void FilterOldEvents(List events) @@ -151,7 +149,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler events.RemoveRange(0, idx + 1); } - // save the last event so we know where to clean-up the list from next time + // save the last event so we know where to clean-up the list from next time if (events.Count > 0) { lastSeenEvent = events.LastOrDefault(); diff --git a/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerSessionMonitor.cs b/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerSessionMonitor.cs index 09050b5d..a8bd72a9 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerSessionMonitor.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Profiler/ProfilerSessionMonitor.cs @@ -20,7 +20,7 @@ using Microsoft.SqlTools.Utility; namespace Microsoft.SqlTools.ServiceLayer.Profiler { /// - /// Classs to monitor active profiler sessions + /// Class to monitor active profiler sessions /// public class ProfilerSessionMonitor : IProfilerSessionMonitor { @@ -30,9 +30,33 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler private object listenersLock = new object(); + private object pollingLock = new object(); + private Task processorThread = null; - private Dictionary monitoredSessions = new Dictionary(); + private struct Viewer + { + public string Id { get; set; } + public bool active { get; set; } + + public int xeSessionId { get; set; } + + public Viewer(string Id, bool active, int xeId) + { + this.Id = Id; + this.active = active; + this.xeSessionId = xeId; + } + }; + + // XEvent Session Id's matched to the Profiler Id's watching them + private Dictionary> sessionViewers = new Dictionary>(); + + // XEvent Session Id's matched to their Profiler Sessions + private Dictionary monitoredSessions = new Dictionary(); + + // ViewerId -> Viewer objects + private Dictionary allViewers = new Dictionary(); private List listeners = new List(); @@ -40,29 +64,58 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler /// Registers a session event listener to receive a callback when events arrive /// public void AddSessionListener(IProfilerSessionListener listener) - { - lock (this.listenersLock) + { + lock (this.listenersLock) { this.listeners.Add(listener); } } /// - /// Start monitoring the provided sessions + /// Start monitoring the provided session /// - public bool StartMonitoringSession(ProfilerSession session) + public bool StartMonitoringSession(string viewerId, IXEventSession session) { lock (this.sessionsLock) { - // start the monitoring thread + // start the monitoring thread if (this.processorThread == null) { - this.processorThread = Task.Factory.StartNew(ProcessSessions);; + this.processorThread = Task.Factory.StartNew(ProcessSessions); } - if (!this.monitoredSessions.ContainsKey(session.SessionId)) + // create new profiling session if needed + if (!this.monitoredSessions.ContainsKey(session.Id)) { - this.monitoredSessions.Add(session.SessionId, session); + var profilerSession = new ProfilerSession(); + profilerSession.XEventSession = session; + + this.monitoredSessions.Add(session.Id, profilerSession); + } + + // create a new viewer, or configure existing viewer + Viewer viewer; + if (!this.allViewers.TryGetValue(viewerId, out viewer)) + { + viewer = new Viewer(viewerId, true, session.Id); + allViewers.Add(viewerId, viewer); + } + else + { + viewer.active = true; + viewer.xeSessionId = session.Id; + } + + // add viewer to XEvent session viewers + List viewers; + if (this.sessionViewers.TryGetValue(session.Id, out viewers)) + { + viewers.Add(viewerId); + } + else + { + viewers = new List{ viewerId }; + sessionViewers.Add(session.Id, viewers); } } @@ -70,15 +123,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler } /// - /// Stop monitoring the session specified by the sessionId + /// Stop monitoring the session watched by viewerId /// - public bool StopMonitoringSession(string sessionId, out ProfilerSession session) + public bool StopMonitoringSession(string viewerId, out ProfilerSession session) { lock (this.sessionsLock) { - if (this.monitoredSessions.ContainsKey(sessionId)) + Viewer v; + if (this.allViewers.TryGetValue(viewerId, out v)) { - return this.monitoredSessions.Remove(sessionId, out session); + return RemoveSession(v.xeSessionId, out session); } else { @@ -88,23 +142,84 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler } } + /// + /// Toggle the pause state for the viewer + /// + public void PauseViewer(string viewerId) + { + lock (this.sessionsLock) + { + Viewer v = this.allViewers[viewerId]; + v.active = !v.active; + this.allViewers[viewerId] = v; + } + } + + private bool RemoveSession(int sessionId, out ProfilerSession session) + { + lock (this.sessionsLock) + { + if (this.monitoredSessions.Remove(sessionId, out session)) + { + //remove all viewers for this session + List viewerIds; + if (sessionViewers.Remove(sessionId, out viewerIds)) + { + foreach (String viewerId in viewerIds) + { + this.allViewers.Remove(viewerId); + } + return true; + } + else + { + session = null; + return false; + } + } + else + { + session = null; + return false; + } + } + } + + public void PollSession(int sessionId) + { + lock (this.sessionsLock) + { + this.monitoredSessions[sessionId].pollImmediatly = true; + } + lock (this.pollingLock) + { + Monitor.Pulse(pollingLock); + } + } + /// /// The core queue processing method /// /// private void ProcessSessions() - { + { while (true) { - lock (this.sessionsLock) + lock (this.pollingLock) { - foreach (var session in this.monitoredSessions.Values) + lock (this.sessionsLock) { - ProcessSession(session); + foreach (var session in this.monitoredSessions.Values) + { + List viewers = this.sessionViewers[session.XEventSession.Id]; + if (viewers.Any(v => allViewers[v].active)) + { + ProcessSession(session); + } + } } + Monitor.Wait(this.pollingLock, PollingLoopDelay); } - - Thread.Sleep(PollingLoopDelay); } } @@ -115,12 +230,20 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler { if (session.TryEnterPolling()) { - Task.Factory.StartNew(() => + Task.Factory.StartNew(() => { var events = PollSession(session); if (events.Count > 0) { - SendEventsToListeners(session.SessionId, events); + // 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); + } + } } }); } @@ -151,7 +274,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler } } } - catch(Exception ex) + catch (Exception ex) { Logger.Write(LogLevel.Warning, "Failed to pool session. error: " + ex.Message); } @@ -187,7 +310,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler var timestamp = node.Attributes["timestamp"]; var profilerEvent = new ProfilerEvent(name.InnerText, timestamp.InnerText); - + foreach (XmlNode childNode in node.ChildNodes) { var childName = childNode.Attributes["name"]; diff --git a/src/Microsoft.SqlTools.ServiceLayer/Profiler/XEventSession.cs b/src/Microsoft.SqlTools.ServiceLayer/Profiler/XEventSession.cs index 4a65c67b..b1a3903c 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Profiler/XEventSession.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Profiler/XEventSession.cs @@ -10,10 +10,23 @@ using Microsoft.SqlTools.ServiceLayer.Profiler.Contracts; namespace Microsoft.SqlTools.ServiceLayer.Profiler { + /// + /// Class to access underlying XEvent session. + /// public class XEventSession : IXEventSession { public Session Session { get; set; } + public int Id + { + get { return Session.ID; } + } + + public void Start() + { + this.Session.Start(); + } + public void Stop() { this.Session.Stop(); diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Profiler/ProfilerServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Profiler/ProfilerServiceTests.cs index c384ef9a..f0a730d8 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Profiler/ProfilerServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Profiler/ProfilerServiceTests.cs @@ -44,7 +44,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Profiler string sessionId = null; var startContext = new Mock>(); startContext.Setup(rc => rc.SendResult(It.IsAny())) - .Returns((result) => + .Returns((result) => { // capture the session id for sending the stop message sessionId = result.SessionId; @@ -71,7 +71,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Profiler await profilerService.HandleStopProfilingRequest(stopParams, stopContext.Object); stopContext.VerifyAll(); - } + } } /// @@ -82,7 +82,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Profiler { var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo("master"); ProfilerService profilerService = new ProfilerService(); - IXEventSession xeSession = profilerService.CreateXEventSession(liveConnection.ConnectionInfo); + IXEventSession xeSession = profilerService.GetOrCreateXEventSession("Profiler", liveConnection.ConnectionInfo); Assert.NotNull(xeSession); Assert.NotNull(xeSession.GetTargetXml()); } diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Profiler/ProfilerServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Profiler/ProfilerServiceTests.cs index ac5b22d2..24740383 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Profiler/ProfilerServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Profiler/ProfilerServiceTests.cs @@ -7,6 +7,7 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.Collections.Generic; using Microsoft.SqlServer.Management.XEvent; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Connection; @@ -32,6 +33,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Profiler public async Task TestStartProfilingRequest() { string sessionId = null; + bool recievedEvents = false; string testUri = "profiler_uri"; var requestContext = new Mock>(); requestContext.Setup(rc => rc.SendResult(It.IsAny())) @@ -42,10 +44,15 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Profiler return Task.FromResult(0); }); - var sessionListener = new TestSessionListener(); + // capture listener event notifications + var mockListener = new Mock(); + mockListener.Setup(p => p.EventsAvailable(It.IsAny(), It.IsAny>())).Callback(() => + { + recievedEvents = true; + }); var profilerService = new ProfilerService(); - profilerService.SessionMonitor.AddSessionListener(sessionListener); + profilerService.SessionMonitor.AddSessionListener(mockListener.Object); profilerService.ConnectionServiceInstance = TestObjects.GetTestConnectionService(); ConnectionInfo connectionInfo = TestObjects.GetTestConnectionInfo(); profilerService.ConnectionServiceInstance.OwnerToConnectionMap.Add(testUri, connectionInfo); @@ -55,15 +62,33 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Profiler requestParams.OwnerUri = testUri; requestParams.TemplateName = "Standard"; + // start profiling session await profilerService.HandleStartProfilingRequest(requestParams, requestContext.Object); - // wait a bit for profile sessions to be polled - Thread.Sleep(500); + 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;}); + while (sessionId == null && !timeout) + { + Thread.Sleep(250); + } + pollingTimer.Stop(); requestContext.VerifyAll(); - Assert.Equal(sessionListener.PreviousSessionId, sessionId); - Assert.Equal(sessionListener.PreviousEvents.Count, 1); + // Check that the correct XEvent session was started + Assert.Equal(sessionId, "1"); + + // check that the proper owner Uri was used + Assert.True(recievedEvents); } /// @@ -82,11 +107,11 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Profiler requestContext.Setup(rc => rc.SendResult(It.IsAny())) .Returns((result) => { - success = result.Succeeded; + success = true; return Task.FromResult(0); }); - // capture if session was dropped + // capture if session was stopped var mockSession = new Mock(); mockSession.Setup(p => p.Stop()).Callback(() => { @@ -104,17 +129,13 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Profiler var requestParams = new StopProfilingParams(); requestParams.OwnerUri = testUri; - ProfilerSession session = new ProfilerSession(); - session.XEventSession = mockSession.Object; - session.SessionId = testUri; - - profilerService.SessionMonitor.StartMonitoringSession(session); + profilerService.SessionMonitor.StartMonitoringSession(testUri, mockSession.Object); await profilerService.HandleStopProfilingRequest(requestParams, requestContext.Object); requestContext.VerifyAll(); - // check that session was succesfully stopped and drop was called + // check that session was succesfully stopped and stop was called Assert.True(success); Assert.True(stopped); @@ -122,5 +143,97 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Profiler ProfilerSession ps; Assert.False(profilerService.SessionMonitor.StopMonitoringSession(testUri, out ps)); } + + /// + /// Test pausing then resuming a session + /// + /// + [Fact] + 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())) + .Returns((result) => + { + success = true; + return Task.FromResult(0); + }); + + // capture listener event notifications + var mockListener = new Mock(); + mockListener.Setup(p => p.EventsAvailable(It.IsAny(), It.IsAny>())).Callback(() => + { + recievedEvents = true; + }); + + // setup profiler service + var profilerService = new ProfilerService(); + profilerService.SessionMonitor.AddSessionListener(mockListener.Object); + profilerService.ConnectionServiceInstance = TestObjects.GetTestConnectionService(); + ConnectionInfo connectionInfo = TestObjects.GetTestConnectionInfo(); + profilerService.ConnectionServiceInstance.OwnerToConnectionMap.Add(testUri, connectionInfo); + + var requestParams = new PauseProfilingParams(); + requestParams.OwnerUri = testUri; + + // begin monitoring session + 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(); + } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Profiler/ProfilerTestObjects.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Profiler/ProfilerTestObjects.cs index a79f8336..f05adeef 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Profiler/ProfilerTestObjects.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Profiler/ProfilerTestObjects.cs @@ -188,19 +188,168 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Profiler " " + ""; + + + public int Id { get { return 51; } } + + public void Start(){} + + public void Stop(){} public string GetTargetXml() { return testXEventXml; } + } + + public class TestXEventSession1 : IXEventSession + { + private const string testXEventXml_1 = + "" + + " " + + " " + + " " + + " 1" + + " " + + " " + + ""; + + private const string testXEventXml_2 = + "" + + " " + + " " + + " " + + " 1" + + " " + + " " + + " " + + " " + + " " + + " 1" + + " " + + " " + + ""; + + private const string testXEventXml_3 = + "" + + " " + + " " + + " " + + " 1" + + " " + + " " + + " " + + " " + + " " + + " 1" + + " " + + " " + + " " + + " " + + " " + + " 1" + + " " + + " " + + ""; + + public int Id { get { return 1; } } + + public void Start(){} public void Stop(){} + + private int pollCount = 0; + private string[] poll_returns = { testXEventXml_1, testXEventXml_2, testXEventXml_3 }; + public string GetTargetXml() + { + string res = poll_returns[pollCount]; + pollCount++; + pollCount = pollCount > 2 ? 0 : pollCount; + return res; + } + } + + public class TestXEventSession2 : IXEventSession + { + private const string testXEventXml_1 = + "" + + " " + + " " + + " " + + " 2" + + " " + + " " + + ""; + + private const string testXEventXml_2 = + "" + + " " + + " " + + " " + + " 2" + + " " + + " " + + " " + + " " + + " " + + " 2" + + " " + + " " + + ""; + + private const string testXEventXml_3 = + "" + + " " + + " " + + " " + + " 2" + + " " + + " " + + " " + + " " + + " " + + " 2" + + " " + + " " + + " " + + " " + + " " + + " 2" + + " " + + " " + + ""; + + public int Id { get { return 2; } } + + public void Start(){} + + public void Stop(){} + + private int pollCount = 0; + private string[] poll_returns = { testXEventXml_1, testXEventXml_2, testXEventXml_3 }; + public string GetTargetXml() + { + string res = poll_returns[pollCount]; + pollCount++; + pollCount = pollCount > 2 ? 0 : pollCount; + return res; + } } public class TestXEventSessionFactory : IXEventSessionFactory { - public IXEventSession CreateXEventSession(ConnectionInfo connInfo) + private int sessionNum = 1; + public IXEventSession GetOrCreateXEventSession(string template, ConnectionInfo connInfo) { - return new TestXEventSession(); + if(sessionNum == 1) + { + sessionNum = 2; + return new TestXEventSession1(); + } + else + { + sessionNum = 1; + return new TestXEventSession2(); + } } } }