using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; using System.Data; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Windows.Forms; using TD.SandDock; using Time = System.Double; namespace ProfileEventViewer { public partial class ProfileEventTrace : UserTabbedDocument { #region Internal classes, structures and enums struct View { public Time TimeStart { get; private set;} public Time TimeEnd { get; private set; } public Time ZoomFactor { get { return 1.0 / (TimeEnd - TimeStart); } } public delegate void ViewChangedHandler(View view); public event ViewChangedHandler ViewChanged; const Time MIN_DIFF = 5; const Time MAX_DIFF = 100000000000; private void SetZoom(Time startTime, Time endTime) { SetZoom(startTime, endTime, TimeStart, 0); } // In charge of changing the zoom and not putting bad values in it! private void SetZoom(Time startTime, Time endTime, Time zoomLocation, Time startRatio) { if (endTime - startTime < MIN_DIFF) // Maximal zoom { TimeStart = zoomLocation - startRatio * MIN_DIFF; TimeEnd = zoomLocation + (1 - startRatio) * MIN_DIFF; } else if (endTime - startTime > MAX_DIFF) // Minimal zoom { TimeStart = zoomLocation - startRatio * MAX_DIFF; TimeEnd = zoomLocation + (1 - startRatio) * MAX_DIFF; } else { TimeStart = startTime; TimeEnd = endTime; } if (ViewChanged != null) ViewChanged(this); } private void ChangeZoom(Time startDelta, Time endDelta) { SetZoom(TimeStart + startDelta, TimeEnd + endDelta); } private void ChangeZoom(Time startDelta, Time endDelta, Time zoomLocation, Time startRatio) { SetZoom(TimeStart + startDelta, TimeEnd + endDelta, zoomLocation, startRatio); } public void ZoomOnTime(Time timeLocation, Time timeDx) { // where removing dx * timeStepPerPixel * 2 units from the selection // split it around the focus so that the focus location on screen remains // constant Time timeToRemove = timeDx * 2; Time ratio = (TimeEnd - timeLocation) / (TimeEnd - TimeStart); ChangeZoom(timeToRemove * (1 - ratio), -timeToRemove * ratio, timeLocation, (1 - ratio)); } public void Zoom(Time timeDx) { Time centerLocation = TimeStart + (TimeEnd - TimeStart) / 2; ZoomOnTime(centerLocation, timeDx); } public void ZoomToAbsolute(Time start, Time end) { SetZoom(start, end, start, 0); } public void CenterOnLocation(Time location) { Time dt = location - (TimeStart + TimeEnd) / 2; Pan(dt); } public void Pan(Time delta) { ChangeZoom(delta, delta); } public bool IsRangeVisible(Time rangeStart, Time rangeEnd) { System.Diagnostics.Debug.Assert(rangeStart <= rangeEnd); return !(rangeStart > TimeEnd || rangeEnd < TimeStart || rangeStart == rangeEnd); } } public class SelectionInfo { public Time m_MinTimeExtent = Time.MaxValue; public Time m_MaxTimeExtent = Time.MinValue; public ClockTime m_ClockTime = new ClockTime(); public Time m_TotalTime; public Time m_MinTime = Time.MaxValue; public Time m_MaxTime = Time.MinValue; public UInt32 m_Calls; }; struct DrawEventContext { public int m_Y; public Graphics Graphics; }; struct FindEventContext { public Time Time; public int TargetDepth; } enum MouseMode { None, Pan, ZoomOnLocation, MarkRange, SelectContexts }; #endregion #region Static stuff private const int EventHeight = 20; private const int TitleHeight = 30; private const int MaxDataEventWidth = 300; private static readonly Color DataEventColor = Color.Yellow; private static readonly Color FilteredDataEventColor = Color.FromArgb(100, Color.Yellow); private static readonly Color FilteredOutColor = Color.FromArgb(100, 100, 100, 100); private static readonly Pen engineFrameDelimiterPen = new Pen(Color.FromArgb(25, 200, 0, 0), 1); private static readonly Pen graphicFrameDelimiterPen = new Pen(Color.FromArgb(25, 0, 0, 200), 1); private static readonly Pen rectPen = new Pen(Color.Black, 1); private static readonly Pen rectFilteredOutPen = new Pen(FilteredOutColor); private static readonly Pen adjustedRectPen = new Pen(Color.Black, 1); private static readonly Pen adjustedRectFilteredOutPen = new Pen(FilteredOutColor); private static readonly Pen selectedRectPen = new Pen(Color.Black, 2); private static readonly Pen selectedRectFilteredOutPen = new Pen(FilteredOutColor, 2); private static readonly Pen adjustedSelectedRectPen = new Pen(Color.Black, 2); private static readonly Pen adjustedSelectedRectFilteredOutPen = new Pen(FilteredOutColor, 2); private static readonly Pen[,,] eventPens; private static readonly SolidBrush rectBrush = new SolidBrush(Color.DarkGray); private static readonly Brush contextBackgroundBrushLight = new SolidBrush(Color.LightGray); private static readonly Brush contextBackgroundBrushDark = new SolidBrush(Color.Gray); private static readonly Brush contextButtonBrush = new SolidBrush(Color.WhiteSmoke); private static readonly Font contextTitleFont = new Font("Arial Black", 16); private static readonly Font barFont = new Font("Arial Narrow", 10); private static readonly Font dataEventFont = new Font("Arial Narrow", 9); private static readonly Brush fontBrush = new SolidBrush(Color.Black); private static readonly Brush fontFilteredOutBrush = new SolidBrush(Color.FromArgb(80, 80, 80, 80)); private static readonly Brush rangeBrush = new SolidBrush(Color.FromArgb(64, 64, 64, 64)); private static readonly Pen tspen = new Pen(Color.Black); private static readonly Pen tspen2 = new Pen(Color.Black); private static readonly SolidBrush sleepBrush = new SolidBrush(Color.FromArgb(40, Color.DarkRed)); private static readonly Pen sleepPen = new Pen(Color.FromArgb(96, Color.DarkRed)); private static readonly SolidBrush sleepFontBrush = new SolidBrush(Color.FromArgb(190, Color.DarkRed)); private static readonly SolidBrush filteredOutSleepBrush = new SolidBrush(Color.FromArgb(20, sleepBrush.Color)); private static readonly Pen filteredOutSleepPen = new Pen(Color.FromArgb(48, sleepPen.Color)); private static readonly SolidBrush filteredOutSleepFontBrush = new SolidBrush(Color.FromArgb(95, sleepFontBrush.Color)); private static readonly SolidBrush awakeBrush = new SolidBrush(Color.Ivory); private static readonly Pen awakePen = new Pen(Color.Black); private static readonly SolidBrush filteredOutAwakeBrush = new SolidBrush(Color.FromArgb(100, awakeBrush.Color)); private static readonly Pen filteredOutAwakePen = new Pen(Color.FromArgb(100, awakePen.Color)); private static readonly Pen timeLinePrimaryPen = new Pen(Color.FromArgb(32, Color.Black)); private static readonly Pen timeLineSecondaryPen = new Pen(Color.FromArgb(16, Color.Black)); private static readonly Brush timeLineBrush = new SolidBrush(Color.FromArgb(128, Color.Black)); private static readonly Font timeLineFont = new Font("Arial Black", 8); private static readonly Pen highlightPen = new Pen(Color.Black, 2); private static readonly Brush engineFrameBrush = new SolidBrush(Color.FromArgb(100, 200, 0, 0)); private static readonly Brush graphicFrameBrush = new SolidBrush(Color.FromArgb(100, 0, 0, 200)); private static readonly Brush whiteFontBrush = new SolidBrush(Color.White); static ProfileEventTrace() { adjustedRectPen.DashStyle = DashStyle.Dash; adjustedRectFilteredOutPen.DashStyle = DashStyle.Dash; adjustedSelectedRectPen.DashStyle = DashStyle.Dash; adjustedSelectedRectFilteredOutPen.DashStyle = DashStyle.Dash; eventPens = new Pen[2 /*IsAdjusted*/, 2/*IsSelected*/, 2/*IsHidden*/]; eventPens[0, 0, 0] = rectPen; eventPens[0, 0, 1] = rectFilteredOutPen; eventPens[0, 1, 0] = selectedRectPen; eventPens[0, 1, 1] = selectedRectFilteredOutPen; eventPens[1, 0, 0] = adjustedRectPen; eventPens[1, 0, 1] = adjustedRectFilteredOutPen; eventPens[1, 1, 0] = adjustedSelectedRectPen; eventPens[1, 1, 1] = adjustedSelectedRectFilteredOutPen; } #endregion #region Events public delegate void FilterChangedHandler(ProfileEventTrace sender); public event FilterChangedHandler EventsFilterChanged; public event FilterChangedHandler ContextsFilterChanged; public delegate void RangeSelectionChangedHandler(ProfileEventTrace sender, bool markingEnded, bool contentChanged); public event RangeSelectionChangedHandler RangeSelectionChanged; public delegate void ContextSelectionChangedHandler(ProfileEventTrace sender, HashSet filteredContexts); public event ContextSelectionChangedHandler ContextSelectionChanged; public delegate void SelectionTimesChangedHandler(ProfileEventTrace sender); public event SelectionTimesChangedHandler SelectionTimesChanged; #endregion #region Private Members private ProfileEventDoc m_Doc; private ProfileEventData m_Data; private Summary m_Summary; private TaskGraph m_TaskGraph; private Filters m_Filters = new Filters(); private Regex m_ContextsVisibilityFilter = new Regex(".*", RegexOptions.Compiled); private View m_View; private int m_YOffset; private Time m_RangeStart; private Time m_RangeEnd; private Time m_FocusTime; private int m_SelectContextStartYOffset; private int m_SelectContextEndYOffset; private Time m_TimeStepPerPixel; private Hashtable m_SelectionTimes = new Hashtable(); private bool m_ShowThreadInfo = false; private bool m_ShowThreadInfoContexts = false; private bool m_ShowTimeLine = false; private bool m_ShowSecondaryTimeLineMarkers = false; private bool m_ShowParentShadow = false; private bool m_ShowTinyEvents = true; private int m_LastX; private int m_LastY; private MouseMode m_MouseMode = MouseMode.None; private ProfileEventData.ProfileContext.Event m_SelectedProfileEvent = null; private ProfileEventData.ProfileContext.Data m_SelectedDataEvent = null; private ProfileEventData.ProfileContext.Event m_ToolTipEvent = null; private ProfileEventData.ProfileContext.Data m_ToolTipDataEvent = null; private ProfileEventData.ProfileContext.SleepEvent m_SleepToolTip = null; private HashSet m_FilteredContexts = new HashSet(); // Create a compatible Graphics private Bitmap m_OffScreenBitmap = null; private Graphics m_OffScreenGraphics = null; private bool m_needRedraw = true; #endregion public ProfileEventTrace(ProfileEventDoc doc) { InitializeComponent(); m_Doc = doc; m_Data = doc.Data; Text = doc.Name + " - Trace"; doc.SelectedProfileEventChanged += new EventDelegate(OnSelectedProfileEventChanged); doc.SelectedDataEventChanged += new EventDelegate(OnSelectedDataEventChanged); ShowTimeLine = false; ShowSecondaryTimeLineMarkers = true; // set the time start to the extent TimeExtent extent = m_Data.TimeExtent; m_View.ViewChanged += new View.ViewChangedHandler(m_View_ViewChanged); m_View.ZoomToAbsolute(extent.Min, extent.Max); Redraw(); this.MouseWheel += new MouseEventHandler(ProfileEventsViewer_MouseWheel); m_Summary = new Summary(this); m_TaskGraph = null; engineFrameDelimiterPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid; graphicFrameDelimiterPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid; eventToolTip.UseFading = false; } public ProfileEventDoc Doc { get { return m_Doc; } } public ProfileEventData Data { get { return m_Data; } } public Summary Summary { get { return m_Summary; } } public TaskGraph TaskGraph { get { return m_TaskGraph; } set { m_TaskGraph = value; } } public bool ShowThreadInfo { get { return m_ShowThreadInfo; } set { m_ShowThreadInfo = value; Redraw(); } } public bool ShowThreadInfoContexts { get { return m_ShowThreadInfoContexts; } set { m_ShowThreadInfoContexts = value; Redraw(); } } public bool ShowTimeLine { get { return m_ShowTimeLine; } set { m_ShowTimeLine = value; Redraw(); } } public bool ShowSecondaryTimeLineMarkers { get { return m_ShowSecondaryTimeLineMarkers; } set { m_ShowSecondaryTimeLineMarkers = value; Redraw(); } } public bool HasSelection { get { return m_RangeEnd - m_RangeStart > 0; } } public bool HasProfileEventSelection { get { return m_SelectedProfileEvent != null; } } public bool HasDataEventSelection { get { return m_SelectedDataEvent != null; } } public Hashtable SelectionTimes { get { return m_SelectionTimes; } } private Time ConvertXToTime(int x) { Time timeStepPerPixel = TimeStepPerPixel(); return (Time)((x * timeStepPerPixel) + m_View.TimeStart); } private Int64 ConvertTimeToXForDrawing(Time time) { Time timeStepPerPixel = TimeStepPerPixel(); // Allow 10 pixels outside the bounds of the screen Time clampedTime = Math.Min(Math.Max(time, m_View.TimeStart - 10 * timeStepPerPixel), m_View.TimeEnd + 10 * timeStepPerPixel); return (Int64)((clampedTime - m_View.TimeStart) / timeStepPerPixel); } private Int64 ConvertTimeToX(Time time) { Time timeStepPerPixel = TimeStepPerPixel(); return (Int64)((time - m_View.TimeStart) / timeStepPerPixel); } private bool IsContextShown(ProfileEventData.ProfileContext context) { return !context.IsHidden && (context.ContainsProfileEvents() || (context.ContainsAnyEvents() && ShowThreadInfoContexts)); } public ProfileEventData.ProfileContext.Event FindEvent(ProfileEventData.ProfileContext.Event profileEvent, UInt32 profileID, ref int count, ref int depth) { // recursive function if (profileEvent == null) return null; if (profileEvent.Inside(m_RangeStart, m_RangeEnd)) { if (profileEvent.ProfileID == profileID) --count; if (count == 0) { depth = 1; return profileEvent; } if (profileEvent.Child != null) { ProfileEventData.ProfileContext.Event child = profileEvent.Child; while (child != null) { ProfileEventData.ProfileContext.Event candidate = FindEvent(child, profileID, ref count, ref depth); if (candidate != null) { ++depth; return candidate; } child = child.Sibling; } } } return null; } private bool TryGetProfileIDFromEventName(string itemName, out UInt32 profileID) { profileID = 0; foreach (UInt32 id in m_Data.Profiles.Keys) { ProfileEventData.ProfileDesc desc = (ProfileEventData.ProfileDesc)m_Data.Profiles[id]; if (desc.Name == itemName) { profileID = id; return true; } } return false; } private bool TryGetEventFromIdAndCount(UInt32 itemId, int clickCount, out ProfileEventData.ProfileContext.Event targetEvent, out int yOffset, out int depth) { targetEvent = null; yOffset = m_YOffset; depth = 0; //UInt32 profileID = 0; //if (!TryGetProfileIDFromEventName(itemName, out profileID)) // return false; UInt32 profileID = itemId; // from the profile ID, find occurrences of the event int count = clickCount; do { int contextmaxy = 0; foreach (ProfileEventData.ProfileContext context in m_Data.Contexts.Values) { int contextStart; bool isContextFiltered = m_FilteredContexts.Contains(context.Name); if (!IsContextShown(context) || isContextFiltered) continue; contextmaxy += TitleHeight; contextStart = contextmaxy; contextmaxy += (context.VisibleLevelCount * EventHeight); foreach (ProfileEventData.ProfileContext.Event profileEvent in context.Heads) { targetEvent = FindEvent(profileEvent, profileID, ref count, ref depth); if (targetEvent != null) { yOffset = contextStart + TitleHeight + (depth - 1) * EventHeight; return true; } } } } while (count < clickCount); return false; } private void ShowEventToolTip(ProfileEventData.ProfileContext.Event target, int yOffset) { string itemName = ((ProfileEventData.ProfileDesc)m_Data.Profiles[target.ProfileID]).Name; string toolTipString; if (target.Task != null) { eventToolTip.ToolTipTitle = String.Format("{0} - Task: {1}", itemName, target.Task.Desc.Name); toolTipString = String.Format("Event time {0}\nTotal task time: {1}", TimeToString(target.Length), TimeToString(target.Task.TotalTime)); if (target.Task.PreTasks.Count > 0) { toolTipString += "\n\nPre Tasks:\n"; foreach (ProfileEventData.TaskInstance instance in target.Task.PreTasks) { toolTipString += String.Format("{0}\n", instance.Desc.Name); } } if (target.Task.PostTasks.Count > 0) { toolTipString += "\nPost Tasks:\n"; foreach (ProfileEventData.TaskInstance instance in target.Task.PostTasks) { toolTipString += String.Format("{0}\n", instance.Desc.Name); } toolTipString += "\n"; } } else { eventToolTip.ToolTipTitle = itemName; toolTipString = TimeToString(target.Length); } m_ToolTipEvent = target; m_ToolTipDataEvent = null; m_SleepToolTip = null; eventToolTip.InitialDelay = 0; eventToolTip.AutoPopDelay = 100000; eventToolTip.ShowAlways = true; eventToolTip.Active = true; eventToolTip.BackColor = m_Data.GetProfileColor(target.ProfileID); eventToolTip.SetToolTip(this, toolTipString); eventToolTip.Show(toolTipString, this, (int)ConvertTimeToX((m_View.TimeStart + m_View.TimeEnd) / 2), m_YOffset + yOffset - EventHeight / 2); } private void CenterViewVerticallyOnOffset(int yOffset) { m_YOffset = ClientSize.Height / 2 - yOffset; } public void CenterViewOnItem(UInt32 itemId, int clickCount) { ProfileEventData.ProfileContext.Event target; int yOffset, depth; if (!TryGetEventFromIdAndCount(itemId, clickCount, out target, out yOffset, out depth)) return; Time time = target.Time + target.Length / 2; m_View.CenterOnLocation(time); if (target.Context.VisibleLevelCount < depth) target.Context.VisibleLevelCount = depth; CenterViewVerticallyOnOffset(yOffset); ShowEventToolTip(target, yOffset); Redraw(); } private void ZoomOnEvent(ProfileEventData.ProfileContext.Event target) { if (target == null) return; m_View.ZoomToAbsolute(target.Time - target.Length / 20, target.EndTime + target.Length / 20); m_View.CenterOnLocation(target.Time + (Time)(target.EndTime - target.Time) / 2); // Center on location if zoom is too small } public void ZoomViewOnItem(UInt32 itemId, int clickCount) { ProfileEventData.ProfileContext.Event target; int yOffset, depth; if (!TryGetEventFromIdAndCount(itemId, clickCount, out target, out yOffset, out depth)) return; ZoomOnEvent(target); if (target.Context.VisibleLevelCount < depth ) target.Context.VisibleLevelCount = depth ; CenterViewVerticallyOnOffset(yOffset); ShowEventToolTip(target, yOffset); Redraw(); } private void ProfileEventTrace_MouseClick(object sender, MouseEventArgs e) { if (e.Button != MouseButtons.Left) return; ProfileEventData.ProfileContext.Data clickedDataEvent = null; ProfileEventData.ProfileContext.Event clickedProfileEvent = GetEventFromMousePosition(m_LastX, m_LastY); if (clickedProfileEvent == null) { clickedDataEvent = GetDataEventFromMousePosition(m_LastX, m_LastY); if (clickedDataEvent == null) { bool ctrlIsDown = (Control.ModifierKeys & Keys.Control) == Keys.Control; // Handle context plus/minus buttons int yOffset = m_YOffset; foreach (ProfileEventData.ProfileContext context in m_Data.Contexts.Values) { if (!IsContextShown(context)) continue; Rectangle rectButtonMinus = new Rectangle(2, yOffset + 2, 11, 11); Rectangle rectButtonPlus = new Rectangle(2, yOffset + 17, 11, 11); if (rectButtonMinus.Contains(m_LastX, m_LastY)) { if (!ctrlIsDown) --context.VisibleLevelCount; else context.VisibleLevelCount = 0; Redraw(); return; } if (rectButtonPlus.Contains(m_LastX, m_LastY)) { if (!ctrlIsDown) ++context.VisibleLevelCount; else context.VisibleLevelCount = context.MaxLevel + 1; Redraw(); return; } yOffset += TitleHeight + (context.VisibleLevelCount * EventHeight); } } } m_Doc.SelectedProfileEvent = clickedProfileEvent; m_Doc.SelectedDataEvent = clickedDataEvent; } private void ProfileEventTrace_MouseDoubleClick(object sender, MouseEventArgs e) { if (e.Button != MouseButtons.Left) return; ProfileEventData.ProfileContext.Event targetEvent = GetEventFromMousePosition(m_LastX, m_LastY); if (targetEvent != null) { bool ctrlIsDown = (Control.ModifierKeys & Keys.Control) == Keys.Control; if (!ctrlIsDown) { // Double click: change profile event filter string profileName = m_Data.GetProfileName(targetEvent.ProfileID); string newFilterName = string.Format(@"""{0}""", profileName); if (m_Filters.OriginalFilter != newFilterName) SetEventsFilterString(newFilterName); else SetEventsFilterString(string.Empty); } else { // Ctrl + Double click: change selection range to targeted event's range // Ctrl + Shift + Double click: extend selection range to include targeted event's range bool shiftIsDown = (Control.ModifierKeys & Keys.Shift) == Keys.Shift; bool extendSelection = shiftIsDown; m_Doc.SelectedProfileEvent = targetEvent; AdjustRangeToSelectedEvent(extendSelection); } } } void ProfileEventsViewer_MouseWheel(object sender, MouseEventArgs e) { Time timeStepPerPixel = TimeStepPerPixel(); Time timeDx = (timeStepPerPixel); // Cap the delta, so that we don't zoom in too much const int MAX_DELTA = 240; const int MIN_DELTA = -360; // It's longer to unzoom than to zoom... int delta = Math.Max(Math.Min(e.Delta, MAX_DELTA), MIN_DELTA); Time location = ConvertXToTime(e.X); if (Control.ModifierKeys == Keys.Shift) { m_View.ZoomOnTime(location, timeDx * (delta / 10)); } else { m_View.ZoomOnTime(location, timeDx * delta); } Redraw(); } private void ProfileEventsViewer_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e) { m_LastX = e.X; m_LastY = e.Y; bool shiftIsDown = (Control.ModifierKeys & Keys.Shift) == Keys.Shift; bool ctrlIsDown = (Control.ModifierKeys & Keys.Control) == Keys.Control; if (e.Button == MouseButtons.Left) { if (shiftIsDown) { m_MouseMode = MouseMode.Pan; Cursor = Cursors.NoMove2D; Capture = true; } else if (ctrlIsDown) { m_FocusTime = ConvertXToTime(e.X); m_MouseMode = MouseMode.ZoomOnLocation; Cursor = Cursors.NoMoveHoriz; Capture = true; } } if (e.Button == MouseButtons.Middle) { m_MouseMode = MouseMode.Pan; Cursor = Cursors.NoMove2D; Capture = true; } if (e.Button == MouseButtons.Right) { Capture = true; if (!ctrlIsDown) { m_MouseMode = MouseMode.MarkRange; Cursor = Cursors.NoMoveHoriz; if (!shiftIsDown || m_RangeStart == m_RangeEnd) // Create new selection { m_RangeStart = ConvertXToTime(e.X); m_RangeEnd = m_RangeStart; m_FocusTime = m_RangeStart; } else // Extend selection { Time clickTime = ConvertXToTime(e.X); if (clickTime < m_RangeStart) { m_FocusTime = m_RangeEnd; m_RangeStart = clickTime; } else { m_FocusTime = m_RangeStart; m_RangeEnd = clickTime; } } FireSelectionChanged(false, true); } else { // Select contexts. m_MouseMode = MouseMode.SelectContexts; Cursor = Cursors.NoMoveVert; m_SelectContextStartYOffset = e.Y - m_YOffset; m_SelectContextEndYOffset = m_SelectContextStartYOffset; } Redraw(); } } private void OnSelectedProfileEventChanged(ProfileEventDoc doc) { if (m_SelectedProfileEvent != doc.SelectedProfileEvent) { m_SelectedProfileEvent = m_Doc.SelectedProfileEvent; Redraw(); } } private void OnSelectedDataEventChanged(ProfileEventDoc doc) { if (m_SelectedDataEvent != doc.SelectedDataEvent) { m_SelectedDataEvent = m_Doc.SelectedDataEvent; Redraw(); } } private void ProfileEventsViewer_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e) { MouseMode oldMouseMode = m_MouseMode; m_MouseMode = MouseMode.None; Cursor = Cursors.Default; Capture = false; if (oldMouseMode == MouseMode.MarkRange) { FireSelectionChanged(true, true); } else if (oldMouseMode == MouseMode.SelectContexts) { SelectContextsInYRange(m_SelectContextStartYOffset, m_SelectContextEndYOffset, true); m_SelectContextStartYOffset = m_SelectContextEndYOffset; Redraw(); } } private void ProfileEventsViewer_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e) { int dx = e.X - m_LastX; int dy = e.Y - m_LastY; m_LastX = e.X; m_LastY = e.Y; // return if nothing to do if (dx == 0 && dy == 0) return; // Handle tooltips if (m_MouseMode == MouseMode.None) { ProfileEventData.ProfileContext.Event profileEvent = GetEventFromMousePosition(m_LastX, m_LastY); // draw a tooltip if (profileEvent != null) { if (m_ToolTipEvent != profileEvent) { string toolTipString; if (profileEvent.Task != null) { eventToolTip.ToolTipTitle = String.Format("{0} - Task: {1}", m_Data.GetProfileName(profileEvent.ProfileID), profileEvent.Task.Desc.Name); toolTipString = String.Format("Event time {0}\nTotal task time: {1}", TimeToString(profileEvent.Length), TimeToString(profileEvent.Task.TotalTime)); if (profileEvent.Task.PreTasks.Count > 0) { toolTipString += "\n\nPre Tasks:\n"; foreach (ProfileEventData.TaskInstance instance in profileEvent.Task.PreTasks) { toolTipString += String.Format("{0}\n", instance.Desc.Name); } } if (profileEvent.Task.PostTasks.Count > 0) { toolTipString += "\nPost Tasks:\n"; foreach (ProfileEventData.TaskInstance instance in profileEvent.Task.PostTasks) { toolTipString += String.Format("{0}\n", instance.Desc.Name); } toolTipString += "\n"; } } else { eventToolTip.ToolTipTitle = m_Data.GetProfileName(profileEvent.ProfileID); toolTipString = TimeToString(profileEvent.Length); } m_ToolTipEvent = profileEvent; m_ToolTipDataEvent = null; m_SleepToolTip = null; eventToolTip.InitialDelay = 0; eventToolTip.AutoPopDelay = 100000; eventToolTip.ShowAlways = true; eventToolTip.Active = true; eventToolTip.BackColor = m_Data.GetProfileColor(profileEvent.ProfileID); eventToolTip.SetToolTip(this, toolTipString); } } else { ProfileEventData.ProfileContext.Data dataEvent = GetDataEventFromMousePosition(m_LastX, m_LastY); if (dataEvent != null) { if (m_ToolTipDataEvent != dataEvent) { eventToolTip.ToolTipTitle = ""; m_ToolTipEvent = null; m_ToolTipDataEvent = dataEvent; m_SleepToolTip = null; eventToolTip.InitialDelay = 0; eventToolTip.AutoPopDelay = 100000; eventToolTip.ShowAlways = true; eventToolTip.Active = true; eventToolTip.BackColor = Color.Yellow; eventToolTip.SetToolTip(this, dataEvent.m_String); } } else { ProfileEventData.ProfileContext.SleepEvent sleepEvent = GetSleepEventFromMousePosition(m_LastX, m_LastY); if (sleepEvent != null && ShowThreadInfo) { if(sleepEvent != m_SleepToolTip) { // find the ThreadSwitchEvents related to this sleep event.. String ContextTo = "Unknown (Lock?)"; String ContextFrom = "Unknown (Unlock?)"; for (int i = 0; i < m_Data.ThreadSwitchs.Count; i++) { if (sleepEvent.m_Start == m_Data.ThreadSwitchs[i].m_Time) { // find the Context to which this thread switched to... foreach (ProfileEventData.ProfileContext loopcontext in m_Data.Contexts.Values) if (loopcontext.OsThreadID == m_Data.ThreadSwitchs[i].m_newThreadID && loopcontext.OsThreadID != 0) ContextTo = loopcontext.Name; } if (sleepEvent.m_End == m_Data.ThreadSwitchs[i].m_Time) { // find the Context to which this thread switched from... foreach (ProfileEventData.ProfileContext loopcontext2 in m_Data.Contexts.Values) if (loopcontext2.OsThreadID == m_Data.ThreadSwitchs[i].m_oldThreadID && loopcontext2.OsThreadID != 0) ContextFrom = loopcontext2.Name; } } eventToolTip.ToolTipTitle = String.Format("This thread is IDLE: "); m_ToolTipEvent = null; m_ToolTipDataEvent = null; m_SleepToolTip = sleepEvent; eventToolTip.InitialDelay = 0; eventToolTip.AutoPopDelay = 100000; eventToolTip.ShowAlways = true; eventToolTip.Active = true; eventToolTip.BackColor = Color.Yellow; String ToolTipText = String.Format("The thread switched to : {0}\nAnd then switched back from : {1}.", ContextTo, ContextFrom); eventToolTip.SetToolTip(this, ToolTipText); } } else { m_ToolTipEvent = null; m_ToolTipDataEvent = null; m_SleepToolTip = null; eventToolTip.Active = false; } } } return; } else { m_ToolTipEvent = null; m_ToolTipDataEvent = null; m_SleepToolTip = null; eventToolTip.Active = false; } // get window width Time timeStepPerPixel = TimeStepPerPixel(); Time timeDx = (timeStepPerPixel * dx); switch (m_MouseMode) { case MouseMode.Pan: m_View.Pan(-timeDx); m_YOffset += dy; WrapAround(e.X, e.Y, dx, dy); break; case MouseMode.ZoomOnLocation: m_View.ZoomOnTime(m_FocusTime, timeDx); WrapAround(e.X, e.Y, dx, dy); break; case MouseMode.MarkRange: Time currentSelectedTime = ConvertXToTime(e.X); if (currentSelectedTime < m_FocusTime) { m_RangeEnd = m_FocusTime; m_RangeStart = currentSelectedTime; } else { m_RangeStart = m_FocusTime; m_RangeEnd = currentSelectedTime; } FireSelectionChanged(false, true); break; case MouseMode.SelectContexts: m_SelectContextEndYOffset = e.Y - m_YOffset; SelectContextsInYRange(m_SelectContextStartYOffset, m_SelectContextEndYOffset, false); break; } Redraw(); } private void SelectContextsInYRange(int startYRange, int endYRange, bool applyFilterChanges) { // If the range is empty, clear the selection if (startYRange == endYRange) { m_FilteredContexts.Clear(); } else { int start = Math.Min(startYRange, endYRange); int end = Math.Max(startYRange, endYRange); int currentYOffset = 0; // Find overlapping contexts. foreach (ProfileEventData.ProfileContext context in m_Data.Contexts.Values) { if (!IsContextShown(context)) continue; int contextStart = currentYOffset; int contextEnd = contextStart + TitleHeight + (context.VisibleLevelCount * EventHeight); bool overlaps = ((contextStart <= start && start <= contextEnd) || (contextStart <= end && end <= contextEnd) || (contextStart > start && contextEnd < end)); if (overlaps) m_FilteredContexts.Remove(context.Name); else m_FilteredContexts.Add(context.Name); currentYOffset = contextEnd; } } if (applyFilterChanges) { if (ContextSelectionChanged != null) ContextSelectionChanged(this, m_FilteredContexts); ComputeSelectionTimes(); Redraw(); } } private void WrapAround(int x, int y, int dx, int dy) { int newX = x, newY = y; if (x > ClientSize.Width) newX = x % ClientSize.Width; else if (x < 0) newX = ClientSize.Width + x % ClientSize.Width; if (y > ClientSize.Height) newY = y % ClientSize.Height; else if (y < 0) newY = ClientSize.Height + y % ClientSize.Height; m_LastX = newX; m_LastY = newY; if (newX != x || newY != y) { Point p = new Point(newX, newY); Point newMousePos = PointToScreen(p); Cursor.Position = newMousePos; } } public string TimeToString(Time length) { UInt32 frequency = m_Data.GetFrequency(); Time frequencyInUS = ((Time)frequency) / 1000000; Time lengthInUS = length / frequencyInUS; if (lengthInUS <= 0) { return String.Format("{0} cycles", length); } else if (lengthInUS < 500) { return String.Format("{0:F3} us", lengthInUS); } else if (lengthInUS < 1000000) { return String.Format("{0:F2} ms", (lengthInUS / 1000)); } else { return String.Format("{0:F2} s", (lengthInUS / 1000000)); } } struct EventScreenInfo { public Int64 m_ScreenX; public Int64 m_ScreenLength; public Int64 m_ScreenXE; public Int64 m_ScreenY; }; private bool ComputeEventScreenInfo(ProfileEventData.ProfileContext.Event profileEvent, ref DrawEventContext drawContext, ref EventScreenInfo info) { ProfileEventData.ProfileContext targetContext = profileEvent.Context; if (targetContext.IsHidden) return false; Int64 screenXE = (Int64)ConvertTimeToX(profileEvent.Time + profileEvent.Length); Int64 screenX = (Int64)ConvertTimeToX(profileEvent.Time); screenX = Math.Max(-20, screenX); screenXE = Math.Min(ClientSize.Width + 20, screenXE); if (screenXE == screenX) screenXE++; Int64 screenLength = screenXE - screenX; info.m_ScreenLength = screenLength; info.m_ScreenX = screenX; info.m_ScreenXE = screenXE; // find the Y int contextmaxy = (Int32)m_YOffset; // find which context it's in foreach (ProfileEventData.ProfileContext context in m_Data.Contexts.Values) { int contextStart; if (!IsContextShown(context)) continue; contextmaxy += TitleHeight; contextStart = contextmaxy; contextmaxy += (context.VisibleLevelCount * EventHeight); if (context == targetContext) { info.m_ScreenY = contextStart; break; } } return true; } private void DrawEvent(ProfileEventData.ProfileContext.Event profileEvent, bool isContextFiltered, ref DrawEventContext drawContext, int maxStack) { if (maxStack == 0) return; if (profileEvent.Inside(m_View.TimeStart, m_View.TimeEnd)) { // compute screen X size Int64 screenX = ConvertTimeToX(profileEvent.Time); Int64 screenXE = ConvertTimeToX(profileEvent.EndTime); string profileEventName = m_Data.GetProfileName(profileEvent.ProfileID); bool filteredOut = isContextFiltered; if (!filteredOut && m_Filters.IsValid) filteredOut = !m_Filters.IsAllowed(profileEventName); // compute correct color Color profileColor = m_Data.GetProfileColor(profileEvent.ProfileID); if (filteredOut) profileColor = Color.FromArgb(25, profileColor); // draw a rectangle rectBrush.Color = profileColor; screenX = Math.Max(-20, screenX); screenXE = Math.Min(ClientSize.Width + 20, screenXE); if (screenXE == screenX) screenXE++; Int64 screenLength = screenXE - screenX; if (screenLength > 1) drawContext.Graphics.FillRectangle(rectBrush, screenX, drawContext.m_Y, screenLength, EventHeight); else if (!m_ShowTinyEvents) return; int adjustedIndex = (profileEvent.IsAdjusted ? 1 : 0); int selectedIndex = (m_SelectedProfileEvent == profileEvent ? 1 : 0); int filteredIndex = (filteredOut ? 1 : 0); Pen drawPen = eventPens[adjustedIndex, selectedIndex, filteredIndex]; drawContext.Graphics.DrawRectangle(drawPen, screenX, drawContext.m_Y, screenLength, EventHeight); // set the clip region and display the profile name (if there's any chance it could fit) if (screenLength > 20) { Region oldClipRegion = drawContext.Graphics.Clip; drawContext.Graphics.SetClip(new Rectangle((int)screenX + 1, drawContext.m_Y, (int)screenLength - 1, EventHeight)); string profileName = String.Format("{0}{1}, {2}", (screenX < 0) ? "<< " : "", profileEventName, TimeToString(profileEvent.Length)); drawContext.Graphics.DrawString(profileName, barFont, filteredOut ? fontFilteredOutBrush : fontBrush, Math.Max(screenX, 0), drawContext.m_Y); drawContext.Graphics.Clip = oldClipRegion; } // must draw the childs (if we were not too small) if (profileEvent.Child != null && screenLength > 1 && maxStack > 1) { if (m_ShowParentShadow) drawContext.Graphics.FillRectangle(rangeBrush, screenX, drawContext.m_Y + EventHeight, screenLength, EventHeight); drawContext.m_Y += EventHeight; ProfileEventData.ProfileContext.Event child = profileEvent.Child; while (child != null) { DrawEvent(child, isContextFiltered, ref drawContext, maxStack - 1); child = child.Sibling; } drawContext.m_Y -= EventHeight; } } } private int GetDataEventScreenWidth(ProfileEventData.ProfileContext.Data dataEvent) { Time maxTimeLength = dataEvent.GetMaxTimeLength(); int maxWidth = MaxDataEventWidth; int screenLength = 1; if (maxTimeLength > 0) { Int64 maxScreenWidth = (Int64)(maxTimeLength / TimeStepPerPixel()); if (maxScreenWidth < maxWidth) maxWidth = (int)maxScreenWidth; } if (maxWidth <= 0) screenLength = 1; else if (maxWidth > 20) screenLength = (int)m_OffScreenGraphics.MeasureString(dataEvent.m_String, dataEventFont, maxWidth).Width; else screenLength = maxWidth; return screenLength; } private void DrawDataEvent(ProfileEventData.ProfileContext.Data dataEvent, bool isContextFiltered, ref DrawEventContext drawContext) { if (dataEvent.Inside(m_View.TimeStart, m_View.TimeEnd)) { int eventY = drawContext.m_Y - EventHeight; int gapHeight = 5; int eventHeight = EventHeight - gapHeight; // compute screen X size Int64 screenX = ConvertTimeToX(dataEvent.m_Time); Int64 screenLength = GetDataEventScreenWidth(dataEvent); Int64 screenXE = screenX + screenLength; // draw a data event poly // 1___________2 // | __________| // 5|/4 3 // bool filteredOut = isContextFiltered; if (!filteredOut && m_Filters.IsValid) filteredOut = !m_Filters.IsAllowed(dataEvent.m_String); rectBrush.Color = (filteredOut ? FilteredDataEventColor : DataEventColor); screenX = Math.Max(-20, screenX); screenXE = Math.Min(ClientSize.Width + 20, screenXE); screenLength = screenXE - screenX; Point[] eventPoly = { new Point((int)screenX, eventY), new Point((int)screenX+(int)screenLength, eventY), new Point((int)screenX+(int)screenLength, eventY+eventHeight), new Point((int)screenX+gapHeight, eventY+eventHeight), new Point((int)screenX, eventY+eventHeight+gapHeight), }; if (screenLength > 1) drawContext.Graphics.FillPolygon(rectBrush, eventPoly); int adjustedIndex = 0; // Data events are never adjusted int selectedIndex = (m_SelectedDataEvent == dataEvent ? 1 : 0); int filteredIndex = (filteredOut ? 1 : 0); Pen drawPen = eventPens[adjustedIndex, selectedIndex, filteredIndex]; drawContext.Graphics.DrawPolygon(drawPen, eventPoly); // set the clip region and display the profile name (if there's any chance it could fit) if (screenLength > 20) { Region oldClipRegion = drawContext.Graphics.Clip; drawContext.Graphics.SetClip(new Rectangle((int)screenX + 1, eventY, (int)screenLength - 1, EventHeight)); drawContext.Graphics.DrawString(dataEvent.m_String, dataEventFont, filteredOut ? fontFilteredOutBrush : fontBrush, Math.Max(screenX, 0), eventY); drawContext.Graphics.Clip = oldClipRegion; } } } void HightLightEvent(ProfileEventData.ProfileContext.Event forEvent, Color color, ref DrawEventContext drawContext) { if (forEvent == null) return; // Locate the event screen coordinates EventScreenInfo info = new EventScreenInfo(); if (ComputeEventScreenInfo(forEvent, ref drawContext, ref info)) { // Draw rectangle highlightPen.Color = color; drawContext.Graphics.DrawRectangle(highlightPen, info.m_ScreenX, info.m_ScreenY, info.m_ScreenLength, EventHeight); } } void HightLightTaskInstance(ProfileEventData.TaskInstance taskInstance, Color color, ref DrawEventContext drawContext) { foreach (ProfileEventData.ProfileContext.Event profileEvent in taskInstance.Events) { HightLightEvent(profileEvent, color, ref drawContext); } } void DrawLinkedTasks(ProfileEventData.TaskInstance taskInstance, ref DrawEventContext drawContext, bool preTasks) { List instanceList = taskInstance.PostTasks; Color color = Color.Green; if (preTasks) { instanceList = taskInstance.PreTasks; color = Color.Red; } foreach (ProfileEventData.TaskInstance task in instanceList) { if (task.Events.Count > 0) HightLightTaskInstance(task, color, ref drawContext); else // probably a node task follow the node to the next task DrawLinkedTasks(task, ref drawContext, preTasks); } } void DrawTaskLinks(ProfileEventData.ProfileContext.Event forEvent, ref DrawEventContext drawContext) { ProfileEventData.TaskInstance instance = forEvent.Task; if (instance != null) { // highlight the selected event HightLightTaskInstance(instance, Color.Blue, ref drawContext); DrawLinkedTasks(instance, ref drawContext, true); DrawLinkedTasks(instance, ref drawContext, false); } } private void DrawThreadSwitch(ref DrawEventContext drawContext) { const Int32 trisizeX = 5; const Int32 trisizeY = 10; const float MaxThreadSwitchInView = 500.0F; if (ShowThreadInfo) { // Draw Thread Switch Int32 X = 0; Int32 oldX = -1; ListThreadSwitchX = new List(); foreach (ThreadSwitch ts in m_Data.ThreadSwitchs) { X = (Int32)ConvertTimeToX((Time)ts.m_Time); if (X < ClientSize.Width && X > 0) ThreadSwitchX.Add(X); } if(ThreadSwitchX.Count < MaxThreadSwitchInView && ThreadSwitchX.Count > 0) { double alpha = ((1.0 - (ThreadSwitchX.Count / MaxThreadSwitchInView)) * 128.0); alpha /= (1 + ((128.0 - alpha) / 5)); tspen.Color = Color.FromArgb((int)alpha, Color.Black); // tspen.DashPattern = new float[] { 1.0F, 2.0F }; tspen2.Color = Color.FromArgb((int)alpha, Color.Black); Point p1 = new Point(0, drawContext.m_Y - 15); Point p2 = new Point(0, ClientSize.Height - 1); foreach (int x in ThreadSwitchX) { p1.X = x; // tspen.DashOffset = p1.X % 3; if (oldX != p1.X) { p2.X = p1.X; oldX = p1.X; drawContext.Graphics.DrawLine(tspen, p1, p2); drawContext.Graphics.DrawLine(tspen2, p1.X - trisizeX, p1.Y - trisizeY, p1.X, p1.Y); drawContext.Graphics.DrawLine(tspen2, p1.X + trisizeX, p1.Y - trisizeY, p1.X, p1.Y); drawContext.Graphics.DrawLine(tspen2, p1.X - trisizeX, p1.Y - trisizeY, p1.X + trisizeX, p1.Y - trisizeY); } } } // End thread switch draw... } } void DrawSleepEvent(ProfileEventData.ProfileContext.SleepEvent sleep, bool isContextFiltered, ref DrawEventContext drawContext, int numContextLevel) { if (ShowThreadInfo) { Int32 Height = numContextLevel * EventHeight; Int32 screenY = drawContext.m_Y + (EventHeight * 1 / 2); Int32 textScreenY; if (Height > EventHeight) { Height -= EventHeight; textScreenY = screenY + (Height - EventHeight) / 2; } else { screenY = drawContext.m_Y; textScreenY = screenY; } Int32 screenX = (Int32)ConvertTimeToX(sleep.m_Start); Int32 screenXE = (Int32)ConvertTimeToX(sleep.m_End); screenX = Math.Max(-20, screenX); screenXE = Math.Min(ClientSize.Width + 20, screenXE); Int32 screenLength = screenXE - screenX; if (screenX > ClientSize.Width || screenXE < 0) return; Rectangle rect = new Rectangle(screenX, screenY, screenXE - screenX, Height); // draw a rectangle if (screenLength > 1) drawContext.Graphics.FillRectangle(isContextFiltered ? filteredOutSleepBrush : sleepBrush, rect); drawContext.Graphics.DrawRectangle(isContextFiltered ? filteredOutSleepPen : sleepPen, rect); // set the clip region and display the profile name (if there's any chance it could fit) if (screenLength > 20) { Region oldClipRegion = drawContext.Graphics.Clip; drawContext.Graphics.SetClip(new Rectangle((int)screenX + 1, textScreenY, (int)screenXE - screenX - 1, Height)); string sleepInfo = String.Format("{0}{1}, {2}", (screenX < 0) ? "<< " : "", "Idle", TimeToString(sleep.m_End - sleep.m_Start)); drawContext.Graphics.DrawString(sleepInfo, barFont, isContextFiltered ? filteredOutSleepFontBrush : sleepFontBrush, Math.Max(screenX, 0), textScreenY); drawContext.Graphics.Clip = oldClipRegion; } } } void DrawAwakeEvent(ProfileEventData.ProfileContext.SleepEvent awake, bool isContextFiltered, ref DrawEventContext drawContext, int numContextLevel) { if (numContextLevel == 0) return; //if (ShowThreadInfo) { Int32 Height = EventHeight; Int32 screenY = drawContext.m_Y; Int32 screenX = (Int32)ConvertTimeToX(awake.m_Start); Int32 screenXE = (Int32)ConvertTimeToX(awake.m_End); screenX = Math.Max(-20, screenX); screenXE = Math.Min(ClientSize.Width + 20, screenXE); if (screenX > ClientSize.Width || screenXE < 0) return; Int32 screenLength = screenXE - screenX; Rectangle rect = new Rectangle(screenX, screenY, screenLength, Height); // draw a rectangle if (screenLength > 1) drawContext.Graphics.FillRectangle(isContextFiltered ? filteredOutAwakeBrush : awakeBrush, rect); drawContext.Graphics.DrawRectangle(isContextFiltered ? filteredOutAwakePen : awakePen, rect); // set the clip region and display the profile name (if there's any chance it could fit) if (screenLength > 20) { Region oldClipRegion = drawContext.Graphics.Clip; drawContext.Graphics.SetClip(new Rectangle((int)screenX + 1, screenY, (int)screenXE - screenX - 1, Height)); string awakeInfo = String.Format("{0}{1}, {2}", (screenX < 0) ? "<< " : "", "Working", TimeToString(awake.m_End - awake.m_Start)); drawContext.Graphics.DrawString(awakeInfo, barFont, isContextFiltered ? fontFilteredOutBrush : fontBrush, Math.Max(screenX, 0), screenY); drawContext.Graphics.Clip = oldClipRegion; } } } Time FindNextTimeMarker(Time startTime, Time timeStep) { return FindNextTimeMarker(startTime, timeStep, true); } Time FindNextTimeMarker(Time startTime, Time timeStep, bool ahead) { return Math.Floor((startTime / timeStep) + (ahead ? 1 : 0)) * timeStep; } IEnumerable