mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-24 01:25:42 -05:00
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
This commit is contained in:
committed by
GitHub
parent
aff0f1afae
commit
f53e532225
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Pause Profiling request parameters
|
||||
/// </summary>
|
||||
public class PauseProfilingParams : GeneralRequestDetails
|
||||
{
|
||||
public string OwnerUri { get; set; }
|
||||
}
|
||||
|
||||
public class PauseProfilingResult{}
|
||||
|
||||
/// <summary>
|
||||
/// Pause Profile request type
|
||||
/// </summary>
|
||||
public class PauseProfilingRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Request definition
|
||||
/// </summary>
|
||||
public static readonly
|
||||
RequestType<PauseProfilingParams, PauseProfilingResult> Type =
|
||||
RequestType<PauseProfilingParams, PauseProfilingResult>.Create("profiler/pause");
|
||||
}
|
||||
}
|
||||
@@ -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{}
|
||||
|
||||
/// <summary>
|
||||
/// Start Profile request type
|
||||
|
||||
@@ -22,11 +22,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler
|
||||
/// <summary>
|
||||
/// Starts monitoring a profiler session
|
||||
/// </summary>
|
||||
bool StartMonitoringSession(ProfilerSession session);
|
||||
bool StartMonitoringSession(string viewerId, IXEventSession session);
|
||||
|
||||
/// <summary>
|
||||
/// Stops monitoring a profiler session
|
||||
/// </summary>
|
||||
bool StopMonitoringSession(string sessionId, out ProfilerSession session);
|
||||
bool StopMonitoringSession(string viewerId, out ProfilerSession session);
|
||||
|
||||
/// <summary>
|
||||
/// Pauses or Unpauses the stream of events to the viewer
|
||||
/// </summary>
|
||||
void PauseViewer(string viewerId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,13 +11,23 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler
|
||||
public interface IXEventSession
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads XEvent XML from the default session target
|
||||
/// Gets unique XEvent session Id
|
||||
/// </summary>
|
||||
string GetTargetXml();
|
||||
int Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Starts XEvent session
|
||||
/// </summary>
|
||||
void Start();
|
||||
|
||||
/// <summary>
|
||||
/// Stops XEvent session
|
||||
/// </summary>
|
||||
void Stop();
|
||||
|
||||
/// <summary>
|
||||
/// Reads XEvent XML from the default session target
|
||||
/// </summary>
|
||||
string GetTargetXml();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler
|
||||
public interface IXEventSessionFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new XEvent session
|
||||
/// Gets or creates an XEvent session with the given template
|
||||
/// </summary>
|
||||
IXEventSession CreateXEventSession(ConnectionInfo connInfo);
|
||||
IXEventSession GetOrCreateXEventSession(string template, ConnectionInfo connInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a new profiler session for the provided connection
|
||||
/// Handle request to pause a profiling session
|
||||
/// </summary>
|
||||
internal ProfilerSession StartSession(string sessionId, ConnectionInfo connInfo)
|
||||
internal async Task HandlePauseProfilingRequest(PauseProfilingParams parameters, RequestContext<PauseProfilingResult> requestContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
monitor.PauseViewer(parameters.OwnerUri);
|
||||
|
||||
await requestContext.SendResult(new PauseProfilingResult{});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await requestContext.SendError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a new profiler session or connects to an existing session
|
||||
/// for the provided connection and template info
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The XEvent Session Id that was started
|
||||
/// </returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new XEvent sessions per the IXEventSessionFactory contract
|
||||
/// </summary>
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
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)
|
||||
|
||||
@@ -24,10 +24,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler
|
||||
private TimeSpan pollingDelay = DefaultPollingDelay;
|
||||
private ProfilerEvent lastSeenEvent = null;
|
||||
|
||||
/// <summary>
|
||||
/// Unique ID for the session
|
||||
/// </summary>
|
||||
public string SessionId { get; set; }
|
||||
public bool pollImmediatly = false;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// <summary>
|
||||
/// The delay between session polls
|
||||
/// </summary>
|
||||
public TimeSpan PollingDelay
|
||||
public TimeSpan PollingDelay
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -108,15 +106,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Removed profiler polling events from event list
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public List<ProfilerEvent> FilterProfilerEvents(List<ProfilerEvent> events)
|
||||
{
|
||||
int idx = events.Count;
|
||||
while (--idx >= 0)
|
||||
{
|
||||
{
|
||||
if (IsProfilerEvent(events[idx]))
|
||||
{
|
||||
events.RemoveAt(idx);
|
||||
@@ -126,7 +124,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public void FilterOldEvents(List<ProfilerEvent> 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();
|
||||
|
||||
@@ -20,7 +20,7 @@ using Microsoft.SqlTools.Utility;
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Profiler
|
||||
{
|
||||
/// <summary>
|
||||
/// Classs to monitor active profiler sessions
|
||||
/// Class to monitor active profiler sessions
|
||||
/// </summary>
|
||||
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<string, ProfilerSession> monitoredSessions = new Dictionary<string, ProfilerSession>();
|
||||
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<int, List<string>> sessionViewers = new Dictionary<int, List<string>>();
|
||||
|
||||
// XEvent Session Id's matched to their Profiler Sessions
|
||||
private Dictionary<int, ProfilerSession> monitoredSessions = new Dictionary<int, ProfilerSession>();
|
||||
|
||||
// ViewerId -> Viewer objects
|
||||
private Dictionary<string, Viewer> allViewers = new Dictionary<string, Viewer>();
|
||||
|
||||
private List<IProfilerSessionListener> listeners = new List<IProfilerSessionListener>();
|
||||
|
||||
@@ -40,29 +64,58 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler
|
||||
/// Registers a session event listener to receive a callback when events arrive
|
||||
/// </summary>
|
||||
public void AddSessionListener(IProfilerSessionListener listener)
|
||||
{
|
||||
lock (this.listenersLock)
|
||||
{
|
||||
lock (this.listenersLock)
|
||||
{
|
||||
this.listeners.Add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start monitoring the provided sessions
|
||||
/// Start monitoring the provided session
|
||||
/// </summary>
|
||||
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<string> viewers;
|
||||
if (this.sessionViewers.TryGetValue(session.Id, out viewers))
|
||||
{
|
||||
viewers.Add(viewerId);
|
||||
}
|
||||
else
|
||||
{
|
||||
viewers = new List<string>{ viewerId };
|
||||
sessionViewers.Add(session.Id, viewers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,15 +123,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Profiler
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop monitoring the session specified by the sessionId
|
||||
/// Stop monitoring the session watched by viewerId
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggle the pause state for the viewer
|
||||
/// </summary>
|
||||
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<string> 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The core queue processing method
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
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<string> 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<string> 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"];
|
||||
|
||||
@@ -10,10 +10,23 @@ using Microsoft.SqlTools.ServiceLayer.Profiler.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Profiler
|
||||
{
|
||||
/// <summary>
|
||||
/// Class to access underlying XEvent session.
|
||||
/// </summary>
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user