// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Dynamic; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using Debugger; using Debugger.AddIn.Pads.ParallelPad; using Debugger.AddIn.TreeModel; using ICSharpCode.AvalonEdit.Rendering; using ICSharpCode.Core; using ICSharpCode.Core.Presentation; using ICSharpCode.NRefactory; using ICSharpCode.SharpDevelop.Bookmarks; using ICSharpCode.SharpDevelop.Debugging; using ICSharpCode.SharpDevelop.Editor; using ICSharpCode.SharpDevelop.Gui.Pads; namespace ICSharpCode.SharpDevelop.Gui.Pads { public enum ParallelStacksView { Threads, Tasks } public class ParallelStackPad : DebuggerPad { private DrawSurface surface; private Process debuggedProcess; private ParallelStacksGraph graph; private List currentThreadStacks = new List(); private ParallelStacksView parallelStacksView; private StackFrame selectedFrame; private bool isMethodView; #region Overrides protected override void InitializeComponents() { surface = new DrawSurface(); panel.Children.Add(surface); } protected override void SelectProcess(Process process) { if (debuggedProcess != null) { debuggedProcess.Paused -= debuggedProcess_Paused; } debuggedProcess = process; if (debuggedProcess != null) { debuggedProcess.Paused += debuggedProcess_Paused; } DebuggerService.DebugStarted += OnReset; DebuggerService.DebugStopped += OnReset; RefreshPad(); } public override void RefreshPad() { if (debuggedProcess == null || debuggedProcess.IsRunning) { return; } currentThreadStacks.Clear(); using(new PrintTimes("Parallel stack - method view + threads refresh")) { try { // create all simple ThreadStacks foreach (Thread thread in debuggedProcess.Threads) { if (debuggedProcess.IsPaused) { Utils.DoEvents(debuggedProcess); } CreateThreadStack(thread); } } catch(AbortedBecauseDebuggeeResumedException) { } catch(System.Exception) { if (debuggedProcess == null || debuggedProcess.HasExited) { // Process unexpectedly exited } else { throw; } } } using(new PrintTimes("Parallel stack - run algorithm")) { if (isMethodView) { // build method view for threads CreateMethodViewStacks(); } else { // normal view CreateCommonStacks(); } } using(new PrintTimes("Graph refresh")) { // paint the ThreadStaks graph = new ParallelStacksGraph(); foreach (var stack in this.currentThreadStacks.FindAll(ts => ts.ThreadStackParents == null )) { graph.AddVertex(stack); // add the children AddChildren(stack); } surface.SetGraph(graph); } } protected override ToolBar BuildToolBar() { return ToolBarService.CreateToolBar(this, "/SharpDevelop/Pads/ParallelStacksPad/ToolBar"); } #endregion #region Public Properties public ParallelStacksView ParallelStacksView { get { return parallelStacksView; } set { parallelStacksView = value; RefreshPad(); } } public bool IsMethodView { get { return isMethodView; } set { isMethodView = value; RefreshPad(); } } public bool IsZoomControlVisible { get { return surface.IsZoomControlVisible; } set { surface.IsZoomControlVisible = value; } } #endregion #region Private Methods private void OnReset(object sender, EventArgs e) { currentThreadStacks.Clear(); selectedFrame = null; // remove all BookmarkManager.RemoveAll(b => b is SelectedFrameBookmark); } private void debuggedProcess_Paused(object sender, ProcessEventArgs e) { RefreshPad(); } private void AddChildren(ThreadStack parent) { if(parent.ThreadStackChildren == null || parent.ThreadStackChildren.Count == 0) return; foreach (var ts in parent.ThreadStackChildren) { if (ts == null) continue; graph.AddVertex(ts); graph.AddEdge(new ParallelStacksEdge(parent, ts)); if (ts.ThreadStackChildren == null || ts.ThreadStackChildren.Count == 0) continue; AddChildren(ts); } } private void CreateCommonStacks() { // stack.ItemCollection order // 0 -> top of stack = S.C // 1 -> ............ = S.B // ....................... // n -> bottom of stack = [External Methods] // ThreadStacks with common frame var commonFrameThreads = new Dictionary>(); bool isOver = false; while (true) { for (int i = currentThreadStacks.Count - 1; i >=0; --i) { var stack = currentThreadStacks[i]; if (stack.ItemCollection.Count == 0) { currentThreadStacks.Remove(stack); continue; } } //get all thread stacks with common start frame foreach (var stack in currentThreadStacks) { int count = stack.ItemCollection.Count; dynamic frame = stack.ItemCollection[count - 1]; string fullname = frame.MethodName + stack.Level.ToString(); if (!commonFrameThreads.ContainsKey(fullname)) commonFrameThreads.Add(fullname, new List()); if (!commonFrameThreads[fullname].Contains(stack)) commonFrameThreads[fullname].Add(stack); } // for every list of common stacks, find split place and add them to currentThreadStacks foreach (var frameName in commonFrameThreads.Keys) { var listOfCurrentStacks = commonFrameThreads[frameName]; if (listOfCurrentStacks.Count == 1) // just skip the parents { isOver = true; // we finish when all are pseodo-parents: no more spliting continue; } isOver = false; // find the frameIndex where we can split int frameIndex = 0; string fn = string.Empty; bool canContinue = true; while(canContinue) { for (int i = 0; i < listOfCurrentStacks.Count; ++i) { var stack = listOfCurrentStacks[i]; if (stack.ItemCollection.Count == frameIndex) { canContinue = false; break; } dynamic item = stack.ItemCollection[stack.ItemCollection.Count - frameIndex - 1]; string currentName = item.MethodName; if (i == 0) { fn = currentName; continue; } if (fn == currentName) continue; else { canContinue = false; break; } } if (canContinue) frameIndex++; } // remove last [frameIndex] and create a new ThreadStack as the parent of what remained in the children var threadIds = new List(); var parentItems = new Stack(); while (frameIndex > 0) { for (int i = 0 ; i < listOfCurrentStacks.Count; ++i) { var stack = listOfCurrentStacks[i]; int indexToRemove = stack.ItemCollection.Count - 1; #if DEBUG dynamic d_item = stack.ItemCollection[indexToRemove]; string name = d_item.MethodName; #endif if (i == 0) parentItems.Push(stack.ItemCollection[indexToRemove]); stack.ItemCollection.RemoveAt(indexToRemove); } frameIndex--; } // update thread ids for (int i = 0 ; i < listOfCurrentStacks.Count; ++i) threadIds.AddRange(listOfCurrentStacks[i].ThreadIds); // remove stacks with no items for (int i = listOfCurrentStacks.Count - 1; i >= 0; --i) { var stack = listOfCurrentStacks[i]; if (stack.ItemCollection.Count == 0) listOfCurrentStacks.Remove(stack); } // increase the Level for (int i = 0 ; i < listOfCurrentStacks.Count; ++i) listOfCurrentStacks[i].Level++; // create new parent stack ThreadStack commonParent = new ThreadStack(); commonParent.UpdateThreadIds(threadIds.ToArray()); commonParent.ItemCollection = parentItems.ToObservable(); commonParent.Process = debuggedProcess; commonParent.StackSelected += OnThreadStackSelected; commonParent.FrameSelected += OnFrameSelected; commonParent.IsSelected = commonParent.ThreadIds.Contains(debuggedProcess.SelectedThread.ID); // add new children foreach (var stack in listOfCurrentStacks) { if (stack.ItemCollection.Count == 0) { currentThreadStacks.Remove(stack); continue; } dynamic item = stack.ItemCollection[stack.ItemCollection.Count - 1]; // add the parent to the parent if (stack.ThreadStackParents != null) { // remove stack from it's parent because it will have the commonParent as parent stack.ThreadStackParents[0].ThreadStackChildren.Remove(stack); commonParent.ThreadStackParents = stack.ThreadStackParents.Clone(); commonParent.ThreadStackParents[0].ThreadStackChildren.Add(commonParent); // set level commonParent.Level = stack.Level - 1; } else stack.ThreadStackParents = new List(); // update thread ids if (commonParent.ThreadStackParents != null) { commonParent.ThreadStackParents[0].UpdateThreadIds(commonParent.ThreadIds.ToArray()); } stack.ThreadStackParents.Clear(); stack.ThreadStackParents.Add(commonParent); string currentName = item.MethodName + stack.Level.ToString();; // add name or add to list if (!commonFrameThreads.ContainsKey(currentName)) { var newList = new List(); newList.Add(stack); commonFrameThreads.Add(currentName, newList); } else { var list = commonFrameThreads[currentName]; list.Add(stack); } } commonParent.ThreadStackChildren = listOfCurrentStacks.Clone(); commonFrameThreads[frameName].Clear(); commonFrameThreads[frameName].Add(commonParent); currentThreadStacks.Add(commonParent); // exit and retry break; } if (isOver) break; } } private void CreateMethodViewStacks() { var list = new List, ObservableCollection, List>>(); // find all threadstacks that contains the selected frame for (int i = currentThreadStacks.Count - 1; i >= 0; --i) { var tuple = currentThreadStacks[i].ItemCollection.SplitStack(selectedFrame, currentThreadStacks[i].ThreadIds); if (tuple != null) list.Add(tuple); } currentThreadStacks.Clear(); // common ThreadStack common = new ThreadStack(); var observ = new ObservableCollection(); bool dummy = false; dynamic obj; if (parallelStacksView == ParallelStacksView.Threads) obj = CreateItemForThread(selectedFrame, selectedFrame.Thread, ref dummy); else obj = CreateItemForTask(selectedFrame, selectedFrame.Thread, ref dummy); obj.Image = PresentationResourceService.GetImage("Icons.48x48.CurrentFrame").Source; observ.Add(obj); common.ItemCollection = observ; common.StackSelected += OnThreadStackSelected; common.FrameSelected += OnFrameSelected; common.UpdateThreadIds(selectedFrame.Thread.ID); common.Process = debuggedProcess; common.ThreadStackChildren = new List(); common.ThreadStackParents = new List(); // for all thread stacks, split them in 2 : // one that invokes the method frame, second that get's invoked by current method frame foreach (var tuple in list) { // add top if (tuple.Item1.Count > 0) { ThreadStack topStack = new ThreadStack(); topStack.ItemCollection = tuple.Item1; topStack.StackSelected += OnThreadStackSelected; topStack.FrameSelected += OnFrameSelected; topStack.UpdateThreadIds(tuple.Item3.ToArray()); topStack.Process = debuggedProcess; topStack.ThreadStackParents = new List(); topStack.ThreadStackParents.Add(common); currentThreadStacks.Add(topStack); common.ThreadStackChildren.Add(topStack); } // add bottom if(tuple.Item2.Count > 0) { ThreadStack bottomStack = new ThreadStack(); bottomStack.ItemCollection = tuple.Item2; bottomStack.StackSelected += OnThreadStackSelected; bottomStack.FrameSelected += OnFrameSelected; bottomStack.UpdateThreadIds(tuple.Item3.ToArray()); bottomStack.Process = debuggedProcess; bottomStack.ThreadStackChildren = new List(); bottomStack.ThreadStackChildren.Add(common); common.UpdateThreadIds(tuple.Item3.ToArray()); common.ThreadStackParents.Add(bottomStack); currentThreadStacks.Add(bottomStack); } } currentThreadStacks.Add(common); common.IsSelected = true; } private void CreateThreadStack(Thread thread) { var items = CreateItems(thread); if (items == null || items.Count == 0) return; ThreadStack threadStack = new ThreadStack(); threadStack.StackSelected += OnThreadStackSelected; threadStack.FrameSelected += OnFrameSelected; threadStack.UpdateThreadIds(thread.ID); threadStack.Process = debuggedProcess; threadStack.ItemCollection = items; if (debuggedProcess.SelectedThread != null) { threadStack.IsSelected = threadStack.ThreadIds.Contains(debuggedProcess.SelectedThread.ID); if (selectedFrame == null) selectedFrame = debuggedProcess.SelectedStackFrame; } currentThreadStacks.Add(threadStack); } private ObservableCollection CreateItems(Thread thread) { bool lastItemIsExternalMethod = false; var result = new ObservableCollection(); foreach (StackFrame frame in thread.GetCallstack(100)) { dynamic obj; if (parallelStacksView == ParallelStacksView.Threads) obj = CreateItemForThread(frame, thread, ref lastItemIsExternalMethod); else obj = CreateItemForTask(frame, thread, ref lastItemIsExternalMethod); if (obj != null) result.Add(obj); } Utils.DoEvents(debuggedProcess); return result; } private ExpandoObject CreateItemForThread(StackFrame frame, Thread thread, ref bool lastItemIsExternalMethod) { dynamic obj = new ExpandoObject(); string fullName; if (frame.HasSymbols) { // Show the method in the list fullName = frame.GetMethodName(); lastItemIsExternalMethod = false; obj.FontWeight = FontWeights.Normal; obj.Foreground = Brushes.Black; } else { // Show [External methods] in the list if (lastItemIsExternalMethod) return null; fullName = ResourceService.GetString("MainWindow.Windows.Debug.CallStack.ExternalMethods").Trim(); obj.FontWeight = FontWeights.Normal; obj.Foreground = Brushes.Gray; lastItemIsExternalMethod = true; } if (thread.SelectedStackFrame != null && thread.ID == debuggedProcess.SelectedThread.ID && thread.SelectedStackFrame.IP == frame.IP && thread.SelectedStackFrame.GetMethodName() == frame.GetMethodName()) { obj.Image = PresentationResourceService.GetImage("Bookmarks.CurrentLine").Source; obj.IsRunningStackFrame = true; } else { if (selectedFrame != null && frame.Thread.ID == selectedFrame.Thread.ID && frame.GetMethodName() == selectedFrame.GetMethodName()) obj.Image = PresentationResourceService.GetImage("Icons.48x48.CurrentFrame").Source; else obj.Image = null; obj.IsRunningStackFrame = false; } obj.MethodName = fullName; return obj; } private ExpandoObject CreateItemForTask(StackFrame frame, Thread thread, ref bool lastItemIsExternalMethod) { dynamic obj = new ExpandoObject(); string fullName; if (frame.HasSymbols) { // Show the method in the list fullName = frame.GetMethodName(); lastItemIsExternalMethod = false; obj.FontWeight = FontWeights.Normal; obj.Foreground = Brushes.Black; } else { // Show [External methods] in the list if (lastItemIsExternalMethod) return null; fullName = ResourceService.GetString("MainWindow.Windows.Debug.CallStack.ExternalMethods").Trim(); obj.FontWeight = FontWeights.Normal; obj.Foreground = Brushes.Gray; lastItemIsExternalMethod = true; } if (thread.SelectedStackFrame != null && thread.ID == debuggedProcess.SelectedThread.ID && thread.SelectedStackFrame.IP == frame.IP && thread.SelectedStackFrame.GetMethodName() == frame.GetMethodName()) { obj.Image = PresentationResourceService.GetImage("Bookmarks.CurrentLine").Source; obj.IsRunningStackFrame = true; } else { if (selectedFrame != null && frame.Thread.ID == selectedFrame.Thread.ID && frame.GetMethodName() == selectedFrame.GetMethodName()) obj.Image = PresentationResourceService.GetImage("Icons.48x48.CurrentFrame").Source; else obj.Image = null; obj.IsRunningStackFrame = false; } obj.MethodName = fullName; return obj; } private void ToggleSelectedFrameBookmark(Location location) { // remove all BookmarkManager.RemoveAll(b => b is SelectedFrameBookmark); ITextEditorProvider provider = WorkbenchSingleton.Workbench.ActiveContent as ITextEditorProvider; if (provider != null) { ITextEditor editor = provider.TextEditor; BookmarkManager.AddMark(new SelectedFrameBookmark(editor.FileName, location)); } } private void OnThreadStackSelected(object sender, EventArgs e) { foreach (var ts in this.currentThreadStacks.FindAll(ts => ts.ThreadStackChildren == null)) { if (ts.IsSelected) ts.IsSelected = false; ts.ClearImages(); } } private void OnFrameSelected(object sender, FrameSelectedEventArgs e) { selectedFrame = e.Item; ToggleSelectedFrameBookmark(e.Location); if (isMethodView) RefreshPad(); } #endregion } internal static class StackFrameExtensions { internal static string GetMethodName(this StackFrame frame) { if (frame == null) return null; StringBuilder name = new StringBuilder(); name.Append(frame.MethodInfo.DeclaringType.FullName); name.Append('.'); name.Append(frame.MethodInfo.Name); return name.ToString(); } } internal static class ParallelStackExtensions { internal static List Clone(this List listToClone) { if (listToClone == null) return null; List result = new List(); foreach (var item in listToClone) result.Add(item); return result; } internal static ObservableCollection ToObservable(this Stack stack) { if (stack == null) throw new NullReferenceException("Stack is null!"); var result = new ObservableCollection(); while (stack.Count > 0) result.Add(stack.Pop()); return result; } internal static Tuple, ObservableCollection, List> SplitStack(this ObservableCollection source, StackFrame frame, List threadIds) { var bottom = new ObservableCollection(); var top = new ObservableCollection(); int found = 0; foreach (dynamic item in source) { if (item.MethodName == frame.GetMethodName()) found = 1; if (found >= 1) { if(found > 1) bottom.Add(item); found++; } else top.Add(item); } var result = new Tuple, ObservableCollection, List>(top, bottom, threadIds); return found > 1 ? result : null; } } }