// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // using System; using System.Collections.Generic; using System.Linq; using Microsoft.SqlServer.Management.XEvent; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Profiler.Contracts; namespace Microsoft.SqlTools.ServiceLayer.Profiler { /// /// Profiler session class /// public class ProfilerSession { 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 /// public ConnectionInfo ConnectionInfo { get; set; } /// /// Underlying XEvent session wrapper /// 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 /// public bool EventsLost { get { return this.eventsLost; } } /// /// 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") || currentEvent.Values["batch_text"].Contains("SELECT target_data FROM sys.dm_xe_database_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, /// and to exclude events that happened before the profiling session began. /// public void FilterOldEvents(List events) { this.eventsLost = false; if (lastSeenId != -1) { // find the last event we've previously seen bool foundLastEvent = false; int idx = events.Count; int earliestSeenEventId = int.Parse(events.LastOrDefault().Values["event_sequence"]); while (--idx >= 0) { // update the furthest back event we've found so far earliestSeenEventId = Math.Min(earliestSeenEventId, int.Parse(events[idx].Values["event_sequence"])); if (events[idx].Equals(lastSeenEvent)) { foundLastEvent = true; break; } } // remove all the events we've seen before if (foundLastEvent) { events.RemoveRange(0, idx + 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 this.eventsLost = true; } // save the last event so we know where to clean-up the list from next time if (events.Count > 0) { lastSeenEvent = events.LastOrDefault(); lastSeenId = int.Parse(lastSeenEvent.Values["event_sequence"]); } } else // first poll at start of session, all data is old { // save the last event as the beginning of the profiling session if (events.Count > 0) { lastSeenEvent = events.LastOrDefault(); lastSeenId = int.Parse(lastSeenEvent.Values["event_sequence"]); } // ignore all events before the session began events.Clear(); } } } }