Browse Source

New database schema for profiler. The Top20 now executes in less than a second.

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5037 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
Daniel Grunwald 16 years ago
parent
commit
3f2b077991
  1. 66
      src/AddIns/Misc/Profiler/Controller/Data/IncompatibleDatabaseException.cs
  2. 9
      src/AddIns/Misc/Profiler/Controller/Data/Linq/AllCalls.cs
  3. 10
      src/AddIns/Misc/Profiler/Controller/Data/Linq/AllFunctions.cs
  4. 50
      src/AddIns/Misc/Profiler/Controller/Data/Linq/ExpressionSqlWriter.cs
  5. 21
      src/AddIns/Misc/Profiler/Controller/Data/Linq/SQLiteQueryProvider.cs
  6. 16
      src/AddIns/Misc/Profiler/Controller/Data/Linq/SqlQueryContext.cs
  7. 123
      src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataSQLiteProvider.cs
  8. 37
      src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataSQLiteWriter.cs
  9. 5
      src/AddIns/Misc/Profiler/Controller/Data/SQLiteCallTreeNode.cs
  10. 1
      src/AddIns/Misc/Profiler/Controller/Profiler.Controller.csproj
  11. 11
      src/AddIns/Misc/Profiler/Frontend/AddIn/Src/Commands/CopySelectedData.cs
  12. 24
      src/AddIns/Misc/Profiler/Frontend/AddIn/Src/Views/ProfilerDisplayBinding.cs
  13. 14
      src/AddIns/Misc/Profiler/Frontend/AddIn/Src/Views/WpfViewer.cs
  14. 12
      src/AddIns/Misc/Profiler/Tests/Profiler.Tests/Controller/Data/LinqTests.cs

66
src/AddIns/Misc/Profiler/Controller/Data/IncompatibleDatabaseException.cs

@ -0,0 +1,66 @@ @@ -0,0 +1,66 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Siegfried Pammer" email="siegfriedpammer@gmail.com" />
// <version>$Revision$</version>
// </file>
using System;
using System.Runtime.Serialization;
namespace ICSharpCode.Profiler.Controller.Data
{
/// <summary>
/// Thrown when the database used to internally store the usage data is incompatible with the DB version
/// expected by the UsageDataCollector library.
/// </summary>
[Serializable]
public class IncompatibleDatabaseException : Exception
{
/// <summary>
/// Expected database version.
/// </summary>
public Version ExpectedVersion { get; set; }
/// <summary>
/// Actual database version.
/// </summary>
public Version ActualVersion { get; set; }
/// <summary>
/// Creates a new IncompatibleDatabaseException instance.
/// </summary>
public IncompatibleDatabaseException() {}
/// <summary>
/// Creates a new IncompatibleDatabaseException instance.
/// </summary>
public IncompatibleDatabaseException(Version expectedVersion, Version actualVersion)
: base("Expected DB version " + expectedVersion + " but found " + actualVersion)
{
this.ExpectedVersion = expectedVersion;
this.ActualVersion = actualVersion;
}
/// <summary>
/// Deserializes an IncompatibleDatabaseException instance.
/// </summary>
protected IncompatibleDatabaseException(SerializationInfo info, StreamingContext context) : base(info, context)
{
if (info != null) {
this.ExpectedVersion = (Version)info.GetValue("ExpectedVersion", typeof(Version));
this.ActualVersion = (Version)info.GetValue("ActualVersion", typeof(Version));
}
}
/// <inheritdoc/>
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
if (info != null) {
info.AddValue("ExpectedVersion", this.ExpectedVersion, typeof(Version));
info.AddValue("ActualVersion", this.ActualVersion, typeof(Version));
}
}
}
}

9
src/AddIns/Misc/Profiler/Controller/Data/Linq/AllCalls.cs

@ -19,7 +19,7 @@ using System.Text; @@ -19,7 +19,7 @@ using System.Text;
namespace ICSharpCode.Profiler.Controller.Data.Linq
{
/// <summary>
/// Query AST node representing the whole 'FunctionData' table.
/// Query AST node representing the whole 'Calls' table.
/// This is the source of all queries.
/// Produces SELECT .. FROM .. in SQL.
/// </summary>
@ -48,12 +48,13 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -48,12 +48,13 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
b.AppendLine("SELECT "
+ SqlAs("nameid", newNames.NameID) + ", "
+ SqlAs("timespent", newNames.CpuCyclesSpent) + ", " // TODO : change to CpuCyclesSpent
+ SqlAs("cpucyclesspent", newNames.CpuCyclesSpent) + ", "
+ SqlAs("callcount", newNames.CallCount) + ", "
+ SqlAs("(id != endid)", newNames.HasChildren) + ", "
+ SqlAs("((datasetid = " + context.StartDataSetID + ") AND isActiveAtStart)", newNames.ActiveCallCount) + ", "
+ SqlAs("((id BETWEEN " + context.StartDataSet.RootID + " AND " + context.StartDataSet.CallEndID
+ ") AND isActiveAtStart)", newNames.ActiveCallCount) + ", "
+ SqlAs("id", newNames.ID));
b.AppendLine("FROM FunctionData");
b.AppendLine("FROM Calls");
return SqlStatementKind.Select;
}
}

10
src/AddIns/Misc/Profiler/Controller/Data/Linq/AllFunctions.cs

@ -12,7 +12,7 @@ using System.Text; @@ -12,7 +12,7 @@ using System.Text;
namespace ICSharpCode.Profiler.Controller.Data.Linq
{
/// <summary>
/// Query AST node representing the whole 'FunctionData' table.
/// Query AST node representing the whole 'Functions' table.
/// This is the source of all queries.
/// Produces SELECT .. FROM .. in SQL.
/// </summary>
@ -55,11 +55,11 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -55,11 +55,11 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
b.AppendLine("SELECT "
+ SqlAs("nameid", newNames.NameID) + ", "
+ SqlAs("SUM(timespent)", newNames.CpuCyclesSpent) + ", "
+ SqlAs("SUM(cpucyclesspent)", newNames.CpuCyclesSpent) + ", "
+ SqlAs("SUM(callcount)", newNames.CallCount) + ", "
+ SqlAs("MAX(id != endid)", newNames.HasChildren) + ", "
+ SqlAs("SUM((datasetid = " + context.StartDataSetID + ") AND isActiveAtStart)", newNames.ActiveCallCount));
b.AppendLine("FROM FunctionData");
+ SqlAs("MAX(hasChildren)", newNames.HasChildren) + ", "
+ SqlAs("SUM(CASE WHEN datasetid = " + context.StartDataSet.ID + " THEN activecallcount ELSE 0 END)", newNames.ActiveCallCount));
b.AppendLine("FROM Functions");
if (StartDataSet >= 0)
b.AppendLine("WHERE datasetid BETWEEN " + StartDataSet + " AND " + EndDataSet);
b.AppendLine("GROUP BY nameid");

50
src/AddIns/Misc/Profiler/Controller/Data/Linq/ExpressionSqlWriter.cs

@ -45,22 +45,46 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -45,22 +45,46 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
case ExpressionType.Call:
WriteMethodCall((MethodCallExpression)expression);
break;
case ExpressionType.LessThanOrEqual:
{
BinaryExpression binary = (BinaryExpression)expression;
if (IsSingleCallDataSetId(binary.Left) && binary.Right.NodeType == ExpressionType.Constant) {
if (context.CurrentTable != SqlTableType.Calls)
throw new InvalidOperationException();
// datasetid <= c --> id <= dataset.endid
w.Write("(id <= ");
w.Write(context.FindDataSetById((int)((ConstantExpression)binary.Right).Value).CallEndID);
w.Write(')');
break;
} else if (IsSingleCallDataSetId(binary.Right) && binary.Left.NodeType == ExpressionType.Constant) {
if (context.CurrentTable != SqlTableType.Calls)
throw new InvalidOperationException();
// c <= datasetid --> dataset.rootid <= id
w.Write('(');
w.Write(context.FindDataSetById((int)((ConstantExpression)binary.Left).Value).RootID);
w.Write(" <= id)");
break;
} else {
goto case ExpressionType.LessThan;
}
}
case ExpressionType.AndAlso:
case ExpressionType.OrElse:
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
case ExpressionType.Equal:
case ExpressionType.NotEqual:
BinaryExpression binary = (BinaryExpression)expression;
w.Write('(');
Write(binary.Left);
w.Write(' ');
w.Write(GetOperatorSymbol(expression.NodeType));
w.Write(' ');
Write(binary.Right);
w.Write(')');
{
BinaryExpression binary = (BinaryExpression)expression;
w.Write('(');
Write(binary.Left);
w.Write(' ');
w.Write(GetOperatorSymbol(expression.NodeType));
w.Write(' ');
Write(binary.Right);
w.Write(')');
}
break;
case ExpressionType.Constant:
var ce = (ConstantExpression)expression;
@ -106,6 +130,12 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -106,6 +130,12 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
}
}
bool IsSingleCallDataSetId(Expression expr)
{
MemberExpression me = expr as MemberExpression;
return me != null && me.Expression == callTreeNodeParameter && me.Member == SingleCall.DataSetIdField;
}
void WriteMemberAccess(MemberExpression me)
{
if (me.Expression == callTreeNodeParameter) {
@ -114,7 +144,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -114,7 +144,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
if (me.Member == SingleCall.IDField) {
w.Write("id");
} else if (me.Member == SingleCall.DataSetIdField) {
w.Write("datasetid");
throw new NotSupportedException("datasetid is only support in LessThan expressions!");
} else if (me.Member == SingleCall.ParentIDField) {
w.Write("parentid");
} else if (me.Member == KnownMembers.CallTreeNode_CallCount) {

21
src/AddIns/Misc/Profiler/Controller/Data/Linq/SQLiteQueryProvider.cs

@ -37,7 +37,8 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -37,7 +37,8 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
For QueryNodes that have input, that input must be another QueryNode.
QueryAst nodes:
AllCalls: represents the whole FunctionData table
AllCalls: represents the whole Calls table
AllFunctions: represents the whole Functions table
input.Filter(x => condition1(x) && y => condition2(y)): WHERE clause with multiple conditions
input.MergeByName(): GROUP BY nameid
@ -137,15 +138,23 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -137,15 +138,23 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
*/
readonly ProfilingDataSQLiteProvider sqliteProvider;
internal readonly int startDataSetID, endDataSetID;
public readonly ProfilingDataSQLiteProvider.SQLiteDataSet StartDataSet;
public readonly ProfilingDataSQLiteProvider.SQLiteDataSet EndDataSet;
public SQLiteQueryProvider(ProfilingDataSQLiteProvider sqliteProvider, int startDataSetID, int endDataSetID)
{
if (sqliteProvider == null)
throw new ArgumentNullException("sqliteProvider");
this.sqliteProvider = sqliteProvider;
this.startDataSetID = startDataSetID;
this.endDataSetID = endDataSetID;
this.StartDataSet = FindDataSetById(startDataSetID);
this.EndDataSet = FindDataSetById(endDataSetID);
}
public ProfilingDataSQLiteProvider.SQLiteDataSet FindDataSetById(int id)
{
var dataSets = sqliteProvider.DataSets.Cast<ProfilingDataSQLiteProvider.SQLiteDataSet>();
return dataSets.Single(d => d.ID == id);
}
// Implement GetMapping and ProcessorFrequency so that SQLiteQueryProvider can be used in place of
@ -176,8 +185,8 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -176,8 +185,8 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
{
string command = string.Format(
CultureInfo.InvariantCulture,
"SELECT id FROM FunctionData WHERE (nameid = {0}) AND (datasetid BETWEEN {1} AND {2});",
nameid, startDataSetID, endDataSetID);
"SELECT id FROM Calls WHERE (nameid = {0}) AND (id BETWEEN {1} AND {2});",
nameid, StartDataSet.RootID, EndDataSet.CallEndID);
return sqliteProvider.RunSQLIDList(command).ToArray();
}

16
src/AddIns/Misc/Profiler/Controller/Data/Linq/SqlQueryContext.cs

@ -12,7 +12,8 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -12,7 +12,8 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
{
sealed class SqlQueryContext
{
public readonly int StartDataSetID;
public readonly ProfilingDataSQLiteProvider.SQLiteDataSet StartDataSet;
public readonly ProfilingDataSQLiteProvider.SQLiteDataSet EndDataSet;
public CallTreeNodeSqlNameSet CurrentNameSet { get; private set; }
@ -39,9 +40,18 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -39,9 +40,18 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
this.HasIDList = hasIDList;
}
SQLiteQueryProvider provider;
public SqlQueryContext(SQLiteQueryProvider provider)
{
this.StartDataSetID = provider.startDataSetID;
this.provider = provider;
this.StartDataSet = provider.StartDataSet;
this.EndDataSet = provider.EndDataSet;
}
public ProfilingDataSQLiteProvider.SQLiteDataSet FindDataSetById(int id)
{
return provider.FindDataSetById(id);
}
int uniqueVariableIndex;
@ -59,7 +69,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -59,7 +69,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
/// </summary>
None,
/// <summary>
/// The FunctionData table
/// The Calls table
/// </summary>
Calls
}

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

@ -34,7 +34,7 @@ namespace ICSharpCode.Profiler.Controller.Data @@ -34,7 +34,7 @@ namespace ICSharpCode.Profiler.Controller.Data
/// <summary>
/// Creates a new SQLite profiling data provider and opens a database stored in a file.
/// </summary>
public ProfilingDataSQLiteProvider(string fileName)
private ProfilingDataSQLiteProvider(string fileName, bool allowUpgrade)
{
this.nameMappingCache = new Dictionary<int, NameMapping>();
@ -42,7 +42,78 @@ namespace ICSharpCode.Profiler.Controller.Data @@ -42,7 +42,78 @@ namespace ICSharpCode.Profiler.Controller.Data
conn.Add("Data Source", fileName);
this.connection = new SQLiteConnection(conn.ConnectionString);
this.connection.Open();
try {
this.connection.Open();
CheckFileVersion(allowUpgrade);
} catch {
try {
connection.Dispose();
} catch {
// ignore errors during cleanup, rethrow the first error instead
}
throw;
}
}
void CheckFileVersion(bool allowUpgrade)
{
string version = GetProperty("version");
if (version == "1.0" && allowUpgrade) {
try {
using (SQLiteCommand cmd = connection.CreateCommand()) {
cmd.CommandText = "ALTER TABLE DataSets ADD COLUMN isfirst INTEGER NOT NULL DEFAULT(0);";
cmd.ExecuteNonQuery();
}
} catch (SQLiteException) {
// some 1.0 DBs already have that column, ignore 'duplicate column' error
}
using (SQLiteTransaction transaction = connection.BeginTransaction()) {
try {
using (SQLiteCommand cmd = connection.CreateCommand()) {
cmd.CommandText = ProfilingDataSQLiteWriter.CallsAndFunctionsTableDefs + @"
INSERT OR REPLACE INTO Properties(name, value) VALUES('version', '1.1');
INSERT INTO Calls
SELECT f1.id, f1.endid, f1.parentid, f1.nameid, f1.timespent,
(f1.timespent - (SELECT TOTAL(f2.timespent) FROM FunctionData AS f2 WHERE f2.parentid = f1.id)),
f1.isactiveatstart, f1.callcount
FROM FunctionData f1;
INSERT INTO Functions
SELECT f.datasetid, c.nameid, SUM(c.cpucyclesspent), SUM(c.cpucyclesspentself),
SUM(c.isactiveatstart), SUM(c.callcount), MAX(c.id != c.endid)
FROM Calls c
JOIN FunctionData f
ON c.id = f.id
GROUP BY c.nameid, f.datasetid;
"
+ "DELETE FROM FunctionData;" // I would like to do DROP TABLE, but that causes locking errors
+ ProfilingDataSQLiteWriter.CallsAndFunctionsIndexDefs;
cmd.ExecuteNonQuery();
}
transaction.Commit();
} catch (Exception ex) {
Console.WriteLine(ex.ToString());
throw;
}
}
version = "1.1"; // new version that was upgraded to
try {
// VACUUM must be run outside the transaction
using (SQLiteCommand cmd = connection.CreateCommand()) {
cmd.CommandText = "VACUUM;"; // free the space used by the old FunctionData table
cmd.ExecuteNonQuery();
}
} catch (SQLiteException ex) {
Console.WriteLine(ex.ToString());
}
}
if (version != "1.1")
throw new IncompatibleDatabaseException(new Version(1, 0), new Version(version));
}
/// <summary>
@ -50,7 +121,15 @@ namespace ICSharpCode.Profiler.Controller.Data @@ -50,7 +121,15 @@ namespace ICSharpCode.Profiler.Controller.Data
/// </summary>
public static ProfilingDataSQLiteProvider FromFile(string fileName)
{
return new ProfilingDataSQLiteProvider(fileName);
return new ProfilingDataSQLiteProvider(fileName, false);
}
/// <summary>
/// Creates a new SQLite profiling data provider from a file.
/// </summary>
public static ProfilingDataSQLiteProvider UpgradeFromOldVersion(string fileName)
{
return new ProfilingDataSQLiteProvider(fileName, true);
}
/// <inheritdoc/>
@ -103,25 +182,16 @@ namespace ICSharpCode.Profiler.Controller.Data @@ -103,25 +182,16 @@ namespace ICSharpCode.Profiler.Controller.Data
using (LockAndCreateCommand(out cmd)) {
SQLiteDataReader reader;
bool isFirstAllowed = true;
try {
cmd.CommandText = @"SELECT id, cpuusage, isfirst
FROM DataSets
ORDER BY id;";
reader = cmd.ExecuteReader();
} catch (SQLiteException) {
cmd.CommandText = @"SELECT id, cpuusage
FROM DataSets
ORDER BY id;";
reader = cmd.ExecuteReader();
isFirstAllowed = false;
}
cmd.CommandText = @"SELECT d.id, d.cpuusage, d.isfirst, d.rootid, c.endid
FROM DataSets d
JOIN Calls c ON c.id = d.rootid
ORDER BY d.id;";
reader = cmd.ExecuteReader();
while (reader.Read()) {
list.Add(new SQLiteDataSet(this, reader.GetInt32(0), reader.GetDouble(1), isFirstAllowed ? reader.GetBoolean(2) : false));
list.Add(new SQLiteDataSet(this, reader.GetInt32(0), reader.GetDouble(1), reader.GetBoolean(2),
reader.GetInt32(3), reader.GetInt32(4)));
}
}
@ -132,19 +202,22 @@ namespace ICSharpCode.Profiler.Controller.Data @@ -132,19 +202,22 @@ namespace ICSharpCode.Profiler.Controller.Data
}
}
class SQLiteDataSet : IProfilingDataSet
internal sealed class SQLiteDataSet : IProfilingDataSet
{
ProfilingDataSQLiteProvider provider;
int index;
public readonly int ID;
double cpuUsage;
bool isFirst;
public readonly int RootID, CallEndID;
public SQLiteDataSet(ProfilingDataSQLiteProvider provider, int index, double cpuUsage, bool isFirst)
public SQLiteDataSet(ProfilingDataSQLiteProvider provider, int id, double cpuUsage, bool isFirst, int rootID, int callEndID)
{
this.provider = provider;
this.index = index;
this.ID = id;
this.cpuUsage = cpuUsage;
this.isFirst = isFirst;
this.RootID = rootID;
this.CallEndID = callEndID;
}
public double CpuUsage {
@ -155,7 +228,7 @@ namespace ICSharpCode.Profiler.Controller.Data @@ -155,7 +228,7 @@ namespace ICSharpCode.Profiler.Controller.Data
public CallTreeNode RootNode {
get {
return this.provider.GetRoot(index, index);
return this.provider.GetRoot(ID, ID);
}
}

37
src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataSQLiteWriter.cs

@ -182,6 +182,31 @@ namespace ICSharpCode.Profiler.Controller.Data @@ -182,6 +182,31 @@ namespace ICSharpCode.Profiler.Controller.Data
}
}
internal const string CallsAndFunctionsTableDefs = @"
CREATE TABLE Calls (
id INTEGER NOT NULL PRIMARY KEY,
endid INTEGER NOT NULL,
parentid INTEGER NOT NULL,
nameid INTEGER NOT NULL,
cpucyclesspent INTEGER NOT NULL,
cpucyclesspentself INTEGER NOT NULL,
isactiveatstart INTEGER NOT NULL,
callcount INTEGER NOT NULL
);
CREATE TABLE Functions (
datasetid INTEGER NOT NULL,
nameid INTEGER NOT NULL,
cpucyclesspent INTEGER NOT NULL,
cpucyclesspentself INTEGER NOT NULL,
activecallcount INTEGER NOT NULL,
callcount INTEGER NOT NULL,
hasChildren INTEGER NOT NULL
);";
internal const string CallsAndFunctionsIndexDefs =
"CREATE INDEX CallsParent ON Calls(parentid ASC);" // required for searching the children
+ " ANALYZE;"; // make SQLite analyze the indices available; this will help the query planner later
void InitializeTables()
{
//NameMapping { Id, ReturnType, Name, Parameters }
@ -197,9 +222,9 @@ namespace ICSharpCode.Profiler.Controller.Data @@ -197,9 +222,9 @@ namespace ICSharpCode.Profiler.Controller.Data
cmd.CommandText = @"
CREATE TABLE NameMapping(
id INTEGER NOT NULL PRIMARY KEY,
returntype VARCHAR2(100) NOT NULL,
name VARCHAR2(255) NOT NULL,
parameters VARCHAR2(1000) NOT NULL
returntype TEXT NOT NULL,
name TEXT NOT NULL,
parameters TEXT NOT NULL
);
CREATE TABLE FunctionData(
@ -221,15 +246,15 @@ namespace ICSharpCode.Profiler.Controller.Data @@ -221,15 +246,15 @@ namespace ICSharpCode.Profiler.Controller.Data
);
CREATE TABLE Properties(
name VARCHAR2(100) NOT NULL PRIMARY KEY,
value VARCHAR2(100) NOT NULL
name TEXT NOT NULL PRIMARY KEY,
value TEXT NOT NULL
);
INSERT INTO Properties(name, value) VALUES('version', '1.0');
CREATE TABLE PerformanceCounter(
id INTEGER NOT NULL PRIMARY KEY,
name VARCHAR2(100) NOT NULL
name TEXT NOT NULL
);
CREATE TABLE CounterData(

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

@ -144,7 +144,7 @@ namespace ICSharpCode.Profiler.Controller.Data @@ -144,7 +144,7 @@ namespace ICSharpCode.Profiler.Controller.Data
}
public override int CallCount {
get { return callCount + activeCallCount; }
get { return Math.Max(1, callCount + activeCallCount); }
}
/// <summary>
@ -161,6 +161,7 @@ namespace ICSharpCode.Profiler.Controller.Data @@ -161,6 +161,7 @@ namespace ICSharpCode.Profiler.Controller.Data
foreach (SQLiteCallTreeNode node in nodes) {
mergedIds.AddRange(node.IdList);
mergedNode.callCount += node.callCount;
mergedNode.cpuCyclesSpent += node.cpuCyclesSpent;
mergedNode.activeCallCount += node.activeCallCount;
mergedNode.hasChildren |= node.hasChildren;
@ -184,7 +185,7 @@ namespace ICSharpCode.Profiler.Controller.Data @@ -184,7 +185,7 @@ namespace ICSharpCode.Profiler.Controller.Data
return (new CallTreeNode[] { this.parent }).AsQueryable();
List<int> parentIDList = provider.RunSQLIDList(
"SELECT parentid FROM FunctionData "
"SELECT parentid FROM Calls "
+ "WHERE id IN(" + string.Join(",", this.IdList.Select(s => s.ToString()).ToArray()) + @")");
Expression<Func<SingleCall, bool>> filterLambda = c => parentIDList.Contains(c.ID);

1
src/AddIns/Misc/Profiler/Controller/Profiler.Controller.csproj

@ -95,6 +95,7 @@ @@ -95,6 +95,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Analysis\IProfilingDataComparer.cs" />
<Compile Include="Data\IncompatibleDatabaseException.cs" />
<Compile Include="Data\Linq\AllCalls.cs" />
<Compile Include="Data\Linq\AllFunctions.cs" />
<Compile Include="Data\Linq\ExpressionSqlWriter.cs" />

11
src/AddIns/Misc/Profiler/Frontend/AddIn/Src/Commands/CopySelectedData.cs

@ -35,7 +35,16 @@ namespace ICSharpCode.Profiler.AddIn.Commands @@ -35,7 +35,16 @@ namespace ICSharpCode.Profiler.AddIn.Commands
foreach (CallTreeNodeViewModel node in list) {
if (node != null)
builder.AppendLine(new string('\t', node.Level) + node.Name + "\t" + node.CallCount + "\t" + node.TimeSpent + "\t" + node.TimePercentageOfParentAsText);
builder.AppendLine(
new string('\t', node.Level) +
node.Name + "\t" +
node.CallCount + "\t" +
node.TimeSpent + "\t" +
node.TimeSpentSelf + "\t" +
node.TimeSpentPerCall + "\t" +
node.TimeSpentSelfPerCall + "\t" +
node.TimePercentageOfParentAsText
);
}
Clipboard.SetText(builder.ToString());

24
src/AddIns/Misc/Profiler/Frontend/AddIn/Src/Views/ProfilerDisplayBinding.cs

@ -5,11 +5,12 @@ @@ -5,11 +5,12 @@
// <version>$Revision$</version>
// </file>
using ICSharpCode.Profiler.Controller.Data;
using System;
using System.IO;
using ICSharpCode.Profiler.Controller;
using ICSharpCode.Core;
using ICSharpCode.Profiler.Controller.Data;
using ICSharpCode.SharpDevelop;
using ICSharpCode.SharpDevelop.Gui;
namespace ICSharpCode.Profiler.AddIn.Views
{
@ -29,7 +30,24 @@ namespace ICSharpCode.Profiler.AddIn.Views @@ -29,7 +30,24 @@ namespace ICSharpCode.Profiler.AddIn.Views
public ICSharpCode.SharpDevelop.Gui.IViewContent CreateContentForFile(OpenedFile file)
{
return new WpfViewer(file);
ProfilingDataSQLiteProvider provider;
try {
provider = ProfilingDataSQLiteProvider.FromFile(file.FileName);
} catch (IncompatibleDatabaseException e) {
if (e.ActualVersion == new Version(1, 0)) {
if (MessageService.AskQuestion("Upgrade DB?")) {
using (AsynchronousWaitDialog.ShowWaitDialog("Upgrading database...")) {
provider = ProfilingDataSQLiteProvider.UpgradeFromOldVersion(file.FileName);
}
} else {
return null;
}
} else {
MessageService.ShowErrorFormatted("${res:AddIns.Profiler.DatabaseTooNewError}", e.ActualVersion.ToString(), e.ExpectedVersion.ToString());
return null;
}
}
return new WpfViewer(file, provider);
}
}
}

14
src/AddIns/Misc/Profiler/Frontend/AddIn/Src/Views/WpfViewer.cs

@ -48,27 +48,27 @@ namespace ICSharpCode.Profiler.AddIn.Views @@ -48,27 +48,27 @@ namespace ICSharpCode.Profiler.AddIn.Views
/// <summary>
/// Creates a new WpfViewer object
/// </summary>
public WpfViewer(OpenedFile file)
public WpfViewer(OpenedFile file, ProfilingDataSQLiteProvider provider)
{
// HACK : OpenedFile architecture does not allow to keep files open
// but it is necessary for the ProfilerView to keep the session file open.
// We don't want to load all session data into memory.
// this.Files.Add(file);
// HACK : The file is not recognised by the FileService for closing if it is deleted while open.
// (reason see above comment)
FileService.FileRemoving += FileServiceFileRemoving;
this.file = file;
this.provider = ProfilingDataSQLiteProvider.FromFile(file.FileName);
this.provider = provider;
this.TabPageText = Path.GetFileName(file.FileName);
this.TitleName = this.TabPageText;
dataView = new ProfilerView(this.provider);
// HACK : The file is not recognised by the FileService for closing if it is deleted while open.
// (reason see above comment)
FileService.FileRemoving += FileServiceFileRemoving;
}
void FileServiceFileRemoving(object sender, FileCancelEventArgs e)
{
if (FileUtility.IsEqualFileName(e.FileName, file.FileName) ||
if (FileUtility.IsEqualFileName(e.FileName, file.FileName) ||
FileUtility.IsBaseDirectory(e.FileName, file.FileName))
this.WorkbenchWindow.CloseWindow(true);
}

12
src/AddIns/Misc/Profiler/Tests/Profiler.Tests/Controller/Data/LinqTests.cs

@ -23,15 +23,17 @@ namespace Profiler.Tests.Controller.Data @@ -23,15 +23,17 @@ namespace Profiler.Tests.Controller.Data
const int k = 1000;
ProfilingDataSQLiteProvider provider;
const string databaseFileName = "test.sdps";
[TestFixtureSetUp]
public void FixtureSetUp()
{
if (File.Exists("temp.sdps"))
File.Delete("temp.sdps");
if (File.Exists(databaseFileName))
File.Delete(databaseFileName);
NameMapping method0 = new NameMapping(0, "r0", "m0", new List<string>());
NameMapping method1 = new NameMapping(1, "r1", "m1", new List<string>());
NameMapping method2 = new NameMapping(2, "r2", "m2", new List<string>());
using (var writer = new ProfilingDataSQLiteWriter("temp.sdps", false, null)) {
using (var writer = new ProfilingDataSQLiteWriter(databaseFileName, false, null)) {
writer.ProcessorFrequency = 2000; // MHz
writer.WriteMappings(new[] { method0, method1, method2 } );
CallTreeNodeStub dataSet;
@ -86,14 +88,14 @@ namespace Profiler.Tests.Controller.Data @@ -86,14 +88,14 @@ namespace Profiler.Tests.Controller.Data
writer.WriteDataSet(new DataSetStub { CpuUsage = 0.1, IsFirst = false, RootNode = dataSet });
writer.Close();
}
provider = new ProfilingDataSQLiteProvider("temp.sdps");
provider = ProfilingDataSQLiteProvider.UpgradeFromOldVersion(databaseFileName);
}
[TestFixtureTearDown]
public void FixtureCleanUp()
{
provider.Dispose();
File.Delete("temp.sdps");
File.Delete(databaseFileName);
}
[Test]

Loading…
Cancel
Save