Browse Source

optimized processing of *Self columns to avoid GUI hangs in SharpDevelop

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/branches/3.0@5009 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
Siegfried Pammer 17 years ago
parent
commit
e4591410ee
  1. 24
      src/AddIns/Misc/Profiler/Controller/Data/CallTreeNode.cs
  2. 75
      src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataSQLiteProvider.cs
  3. 21
      src/AddIns/Misc/Profiler/Controller/Data/SQLiteCallTreeNode.cs
  4. 39
      src/AddIns/Misc/Profiler/Controller/Data/UnitTestRootCallTreeNode.cs
  5. 6
      src/AddIns/Misc/Profiler/Controller/Data/UnmanagedCallTreeNode.cs
  6. 16
      src/AddIns/Misc/Profiler/Frontend/Controls/CallTreeNodeViewModel.cs

24
src/AddIns/Misc/Profiler/Controller/Data/CallTreeNode.cs

@ -79,12 +79,17 @@ namespace ICSharpCode.Profiler.Controller.Data
/// </summary> /// </summary>
public abstract long CpuCyclesSpent { get; } public abstract long CpuCyclesSpent { get; }
long cpuCyclesSpentSelf = -1;
/// <summary> /// <summary>
/// Gets how many CPU cycles were spent inside this method, excluding sub calls. /// Gets how many CPU cycles were spent inside this method, excluding sub calls.
/// </summary> /// </summary>
public virtual long CpuCyclesSpentSelf { public virtual long CpuCyclesSpentSelf {
get { get {
return GetCpuCyclesSelf(); if (cpuCyclesSpentSelf == -1)
cpuCyclesSpentSelf = GetCpuCyclesSelf();
return cpuCyclesSpentSelf;
} }
} }
@ -116,11 +121,19 @@ namespace ICSharpCode.Profiler.Controller.Data
/// <summary> /// <summary>
/// Determines whether this node is a thread node or not. /// Determines whether this node is a thread node or not.
/// </summary> /// </summary>
public bool IsThread public bool IsThread {
{
get { return this.Name.StartsWith("Thread#", StringComparison.Ordinal); } get { return this.Name.StartsWith("Thread#", StringComparison.Ordinal); }
} }
/// <summary>
/// Determines whether this node has chil
/// </summary>
public virtual bool HasChildren {
get {
return this.Children.Any();
}
}
/// <summary> /// <summary>
/// Gets a readonly list of the string representation of the parameters. /// Gets a readonly list of the string representation of the parameters.
/// </summary> /// </summary>
@ -136,6 +149,11 @@ namespace ICSharpCode.Profiler.Controller.Data
/// </summary> /// </summary>
public abstract double TimeSpent { get; } public abstract double TimeSpent { get; }
/// <summary>
/// Gets the time spent inside the method (excluding sub calls) in milliseconds.
/// </summary>
public abstract double TimeSpentSelf { get; }
/// <summary> /// <summary>
/// Gets a reference to the parent of this CallTreeNode. /// Gets a reference to the parent of this CallTreeNode.
/// </summary> /// </summary>

75
src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataSQLiteProvider.cs

@ -60,35 +60,11 @@ namespace ICSharpCode.Profiler.Controller.Data
internal IQueryable<CallTreeNode> GetChildren(SQLiteCallTreeNode parent) internal IQueryable<CallTreeNode> GetChildren(SQLiteCallTreeNode parent)
{ {
SQLiteCommand cmd; string[] ids = parent.ids
using (LockAndCreateCommand(out cmd)) { .Select(a => a.ToString(CultureInfo.InvariantCulture))
cmd.CommandText = @"SELECT id, nameid, callcount, timespent, isactiveatstart .ToArray();
FROM FunctionData
WHERE parentid IN(" + string.Join(",", parent.ids.Select(a => a.ToString(CultureInfo.InvariantCulture.NumberFormat)).ToArray()) + @") return GetMergedFunctionData("parentid IN(" + string.Join(",", ids) + ")", parent.selectionStartIndex);
ORDER BY id;";
using (SQLiteDataReader reader = cmd.ExecuteReader()) {
List<SQLiteCallTreeNode> items = new List<SQLiteCallTreeNode>();
while (reader.Read()) {
int childNameId = reader.GetInt32(1);
SQLiteCallTreeNode newItem = items.Find(node => node.nameId == childNameId);
if (newItem == null) {
newItem = new SQLiteCallTreeNode(childNameId, (parent.nameId == 0) ? null : parent, this);
items.Add(newItem);
// works because of ORDER BY id
newItem.isActiveAtStart = reader.GetBoolean(4);
}
newItem.callCount += reader.GetInt32(2);
newItem.cpuCyclesSpent += (ulong)reader.GetInt64(3);
newItem.ids.Add(reader.GetInt32(0));
}
return items.Cast<CallTreeNode>().AsQueryable();
}
}
} }
internal IQueryable<CallTreeNode> GetCallers(SQLiteCallTreeNode item) internal IQueryable<CallTreeNode> GetCallers(SQLiteCallTreeNode item)
@ -114,6 +90,7 @@ namespace ICSharpCode.Profiler.Controller.Data
SQLiteCallTreeNode newItem = items.Find(node => node.nameId == childNameId); SQLiteCallTreeNode newItem = items.Find(node => node.nameId == childNameId);
if (newItem == null) { if (newItem == null) {
newItem = new SQLiteCallTreeNode(childNameId, null, this); newItem = new SQLiteCallTreeNode(childNameId, null, this);
newItem.selectionStartIndex = item.selectionStartIndex;
items.Add(newItem); items.Add(newItem);
// works because of ORDER BY id // works because of ORDER BY id
@ -291,6 +268,8 @@ namespace ICSharpCode.Profiler.Controller.Data
using (SQLiteDataReader reader = cmd.ExecuteReader()) { using (SQLiteDataReader reader = cmd.ExecuteReader()) {
SQLiteCallTreeNode root = new SQLiteCallTreeNode(0, null, this); SQLiteCallTreeNode root = new SQLiteCallTreeNode(0, null, this);
root.selectionStartIndex = startIndex;
while (reader.Read()) { while (reader.Read()) {
root.callCount += reader.GetInt32(2); root.callCount += reader.GetInt32(2);
@ -303,6 +282,7 @@ namespace ICSharpCode.Profiler.Controller.Data
} }
} }
#region Properties
/// <inheritdoc/> /// <inheritdoc/>
public override void SetProperty(string name, string value) public override void SetProperty(string name, string value)
{ {
@ -349,6 +329,7 @@ namespace ICSharpCode.Profiler.Controller.Data
return null; return null;
} }
#endregion
int processorFrequency = -1; int processorFrequency = -1;
@ -375,35 +356,49 @@ namespace ICSharpCode.Profiler.Controller.Data
if (endIndex < startIndex || endIndex >= this.DataSets.Count) if (endIndex < startIndex || endIndex >= this.DataSets.Count)
throw new ArgumentOutOfRangeException("endIndex", endIndex, "Value must be between " + startIndex + " and " + (this.DataSets.Count - 1)); throw new ArgumentOutOfRangeException("endIndex", endIndex, "Value must be between " + startIndex + " and " + (this.DataSets.Count - 1));
IList<CallTreeNode> functions = new List<CallTreeNode>(); var result = GetMergedFunctionData(string.Format("datasetid BETWEEN {0} AND {1}", startIndex, endIndex), startIndex);
return result
.Where(i => i.NameMapping.Id != 0 && !i.NameMapping.Name.StartsWith("Thread#", StringComparison.Ordinal))
.AsQueryable();
}
IQueryable<CallTreeNode> GetMergedFunctionData(string condition, int startIndex)
{
IList<CallTreeNode> result = new List<CallTreeNode>();
SQLiteCommand cmd; SQLiteCommand cmd;
using (LockAndCreateCommand(out cmd)) { using (LockAndCreateCommand(out cmd)) {
cmd.CommandText = @"SELECT GROUP_CONCAT(id), nameid, SUM(timespent), SUM(callcount) cmd.CommandText = @"SELECT
FROM FunctionData GROUP_CONCAT(id),
WHERE datasetid BETWEEN @start AND @end nameid,
GROUP BY nameid;"; SUM(timespent),
cmd.Parameters.Add(new SQLiteParameter("@start", startIndex)); SUM(callcount),
cmd.Parameters.Add(new SQLiteParameter("@end", endIndex)); MAX(id != endid) AS hasChildren,
SUM((datasetid = " + startIndex + @") AND isActiveAtStart) AS activeCallCount
FROM FunctionData
WHERE " + condition + @"
GROUP BY nameid;";
using (SQLiteDataReader reader = cmd.ExecuteReader()) { using (SQLiteDataReader reader = cmd.ExecuteReader()) {
while (reader.Read()) { while (reader.Read()) {
SQLiteCallTreeNode node = new SQLiteCallTreeNode(reader.GetInt32(1), null, this); SQLiteCallTreeNode node = new SQLiteCallTreeNode(reader.GetInt32(1), null, this);
node.selectionStartIndex = startIndex;
node.callCount = reader.GetInt32(3); node.callCount = reader.GetInt32(3);
node.cpuCyclesSpent = (ulong)reader.GetInt64(2); node.cpuCyclesSpent = (ulong)reader.GetInt64(2);
node.ids = reader.GetString(0).Split(',').Select(s => int.Parse(s)).ToList(); node.ids = reader.GetString(0).Split(',').Select(s => int.Parse(s)).ToList();
node.ids.Sort(); node.ids.Sort();
node.hasChildren = reader.GetBoolean(4);
node.activeCallCount = reader.GetInt32(5);
// Can not do filtering of root and thread nodes here, // Can not do filtering of root and thread nodes here,
// because retrieval of names needs access to DB // because retrieval of names needs access to DB
// which is forbidden now (we are inside the lock!) // which is forbidden now (we are inside the lock!)
functions.Add(node); result.Add(node);
} }
} }
} }
// Do filtering now via LINQ return result.AsQueryable();
return functions.SkipWhile(i => i.NameMapping.Id == 0 || i.NameMapping.Name.StartsWith("Thread#", StringComparison.Ordinal)).AsQueryable();
} }
LockObject LockAndCreateCommand(out SQLiteCommand cmd) LockObject LockAndCreateCommand(out SQLiteCommand cmd)

21
src/AddIns/Misc/Profiler/Controller/Data/SQLiteCallTreeNode.cs

@ -26,6 +26,9 @@ namespace ICSharpCode.Profiler.Controller.Data
CallTreeNode parent; CallTreeNode parent;
ProfilingDataSQLiteProvider provider; ProfilingDataSQLiteProvider provider;
internal List<int> ids = new List<int>(); internal List<int> ids = new List<int>();
internal bool hasChildren;
internal int activeCallCount;
internal int selectionStartIndex;
/// <summary> /// <summary>
/// Creates a new CallTreeNode. /// Creates a new CallTreeNode.
@ -91,6 +94,12 @@ namespace ICSharpCode.Profiler.Controller.Data
return CpuCyclesSpent / (1000.0 * this.provider.ProcessorFrequency); return CpuCyclesSpent / (1000.0 * this.provider.ProcessorFrequency);
} }
} }
public override double TimeSpentSelf {
get {
return CpuCyclesSpentSelf / (1000.0 * this.provider.ProcessorFrequency);
}
}
/// <summary> /// <summary>
/// Gets whether the function call started in a previous data set that's not selected. /// Gets whether the function call started in a previous data set that's not selected.
@ -101,8 +110,12 @@ namespace ICSharpCode.Profiler.Controller.Data
} }
} }
public override int CallCount {
get { return callCount + activeCallCount; }
}
/// <summary> /// <summary>
/// Merges a collection of CallTreeNodes into one CallTreeNode, all valuess are accumulated. /// Merges a collection of CallTreeNodes into one CallTreeNode, all values are accumulated.
/// </summary> /// </summary>
/// <param name="nodes">The collection of nodes to process.</param> /// <param name="nodes">The collection of nodes to process.</param>
/// <returns>A new CallTreeNode.</returns> /// <returns>A new CallTreeNode.</returns>
@ -110,8 +123,10 @@ namespace ICSharpCode.Profiler.Controller.Data
{ {
SQLiteCallTreeNode mergedNode = new SQLiteCallTreeNode(0, null, this.provider); SQLiteCallTreeNode mergedNode = new SQLiteCallTreeNode(0, null, this.provider);
bool initialised = false; bool initialised = false;
foreach (SQLiteCallTreeNode node in nodes) { foreach (SQLiteCallTreeNode node in nodes) {
mergedNode.ids.AddRange(node.ids); mergedNode.ids.AddRange(node.ids);
mergedNode.selectionStartIndex = Math.Min(mergedNode.selectionStartIndex, node.selectionStartIndex);
mergedNode.callCount += node.callCount; mergedNode.callCount += node.callCount;
mergedNode.cpuCyclesSpent += node.cpuCyclesSpent; mergedNode.cpuCyclesSpent += node.cpuCyclesSpent;
if (!initialised || mergedNode.nameId == node.nameId) if (!initialised || mergedNode.nameId == node.nameId)
@ -168,5 +183,9 @@ namespace ICSharpCode.Profiler.Controller.Data
return hash; return hash;
} }
public override bool HasChildren {
get { return this.hasChildren; }
}
} }
} }

39
src/AddIns/Misc/Profiler/Controller/Data/UnitTestRootCallTreeNode.cs

@ -18,7 +18,7 @@ namespace ICSharpCode.Profiler.Controller.Data
List<CallTreeNode> unitTests; List<CallTreeNode> unitTests;
/// <summary> /// <summary>
/// Creates a new UnitTestRootCallTreeNode. /// Creates a new UnitTestRootCallTreeNode.
/// </summary> /// </summary>
public UnitTestRootCallTreeNode(IEnumerable<CallTreeNode> unitTests) public UnitTestRootCallTreeNode(IEnumerable<CallTreeNode> unitTests)
{ {
@ -46,58 +46,65 @@ namespace ICSharpCode.Profiler.Controller.Data
} }
} }
/// <inheritdoc/> /// <inheritdoc/>
public override double TimeSpent { public override double TimeSpent {
get { get {
return 0; return 0;
} }
} }
/// <inheritdoc/> /// <inheritdoc/>
public override int RawCallCount { public override int RawCallCount {
get { get {
return 0; return 0;
} }
} }
/// <inheritdoc/> /// <inheritdoc/>
public override CallTreeNode Parent { public override CallTreeNode Parent {
get { get {
return null; return null;
} }
} }
/// <inheritdoc/> /// <inheritdoc/>
public override CallTreeNode Merge(System.Collections.Generic.IEnumerable<CallTreeNode> nodes) public override CallTreeNode Merge(System.Collections.Generic.IEnumerable<CallTreeNode> nodes)
{ {
// throw new ShouldNeverHappenException(); // throw new ShouldNeverHappenException();
throw new NotSupportedException("Cannot merge a UnitTestRootCallTreeNode (should never be possible)"); throw new NotSupportedException("Cannot merge a UnitTestRootCallTreeNode (should never be possible)");
} }
/// <inheritdoc/> /// <inheritdoc/>
public override int GetHashCode() public override int GetHashCode()
{ {
return this.unitTests.Aggregate(0, (sum, item) => sum ^= item.GetHashCode()); return this.unitTests.Aggregate(0, (sum, item) => sum ^= item.GetHashCode());
} }
/// <inheritdoc/> /// <inheritdoc/>
public override bool Equals(CallTreeNode other) public override bool Equals(CallTreeNode other)
{ {
return (other is UnitTestRootCallTreeNode) && (other as UnitTestRootCallTreeNode).unitTests.SequenceEqual(unitTests); return (other is UnitTestRootCallTreeNode) && (other as UnitTestRootCallTreeNode).unitTests.SequenceEqual(unitTests);
} }
/// <inheritdoc/> /// <inheritdoc/>
public override IQueryable<CallTreeNode> Callers { public override IQueryable<CallTreeNode> Callers {
get { get {
return Enumerable.Empty<CallTreeNode>().AsQueryable(); return Enumerable.Empty<CallTreeNode>().AsQueryable();
} }
} }
/// <inheritdoc/> /// <inheritdoc/>
public override IQueryable<CallTreeNode> Children { public override IQueryable<CallTreeNode> Children {
get { get {
return unitTests.AsQueryable(); return unitTests.AsQueryable();
} }
} }
/// <inheritdoc/>
public override double TimeSpentSelf {
get {
return 0;
}
}
} }
} }

6
src/AddIns/Misc/Profiler/Controller/Data/UnmanagedCallTreeNode.cs

@ -94,6 +94,12 @@ namespace ICSharpCode.Profiler.Controller.Data
return this.CpuCyclesSpent / (1000.0 * this.dataSet.ProcessorFrequency); return this.CpuCyclesSpent / (1000.0 * this.dataSet.ProcessorFrequency);
} }
} }
public override double TimeSpentSelf {
get {
return this.CpuCyclesSpentSelf / (1000.0 * this.dataSet.ProcessorFrequency);
}
}
public override CallTreeNode Merge(IEnumerable<CallTreeNode> nodes) public override CallTreeNode Merge(IEnumerable<CallTreeNode> nodes)
{ {

16
src/AddIns/Misc/Profiler/Frontend/Controls/CallTreeNodeViewModel.cs

@ -330,20 +330,18 @@ namespace ICSharpCode.Profiler.Controls
public string TimeSpentSelf { public string TimeSpentSelf {
get { get {
if (!node.IsThread) { if (!node.IsThread)
double value = node.TimeSpent - node.Children.Aggregate(0.0, (sum, item) => sum + item.TimeSpent); return node.TimeSpentSelf.ToString("f6") + "ms";
return value.ToString("f6") + "ms"; else
} else
return null; return null;
} }
} }
public string TimeSpentSelfPerCall { public string TimeSpentSelfPerCall {
get { get {
if (!node.IsThread) { if (!node.IsThread)
double value = node.TimeSpent - node.Children.Aggregate(0.0, (sum, item) => sum + item.TimeSpent); return (node.TimeSpentSelf / node.CallCount).ToString("f6") + "ms";
return (value / node.CallCount).ToString("f6") + "ms"; else
} else
return null; return null;
} }
} }
@ -369,7 +367,7 @@ namespace ICSharpCode.Profiler.Controls
public Visibility CheckBoxVisibility public Visibility CheckBoxVisibility
{ {
get { get {
if (this.Children.Count > 0) if (node.HasChildren)
return Visibility.Visible; return Visibility.Visible;
else else
return Visibility.Hidden; return Visibility.Hidden;

Loading…
Cancel
Save