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 17 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 @@
// <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;
namespace ICSharpCode.Profiler.Controller.Data.Linq namespace ICSharpCode.Profiler.Controller.Data.Linq
{ {
/// <summary> /// <summary>
/// Query AST node representing the whole 'FunctionData' table. /// Query AST node representing the whole 'Calls' table.
/// This is the source of all queries. /// This is the source of all queries.
/// Produces SELECT .. FROM .. in SQL. /// Produces SELECT .. FROM .. in SQL.
/// </summary> /// </summary>
@ -48,12 +48,13 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
b.AppendLine("SELECT " b.AppendLine("SELECT "
+ SqlAs("nameid", newNames.NameID) + ", " + SqlAs("nameid", newNames.NameID) + ", "
+ SqlAs("timespent", newNames.CpuCyclesSpent) + ", " // TODO : change to CpuCyclesSpent + SqlAs("cpucyclesspent", newNames.CpuCyclesSpent) + ", "
+ SqlAs("callcount", newNames.CallCount) + ", " + SqlAs("callcount", newNames.CallCount) + ", "
+ SqlAs("(id != endid)", newNames.HasChildren) + ", " + 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)); + SqlAs("id", newNames.ID));
b.AppendLine("FROM FunctionData"); b.AppendLine("FROM Calls");
return SqlStatementKind.Select; return SqlStatementKind.Select;
} }
} }

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

@ -12,7 +12,7 @@ using System.Text;
namespace ICSharpCode.Profiler.Controller.Data.Linq namespace ICSharpCode.Profiler.Controller.Data.Linq
{ {
/// <summary> /// <summary>
/// Query AST node representing the whole 'FunctionData' table. /// Query AST node representing the whole 'Functions' table.
/// This is the source of all queries. /// This is the source of all queries.
/// Produces SELECT .. FROM .. in SQL. /// Produces SELECT .. FROM .. in SQL.
/// </summary> /// </summary>
@ -55,11 +55,11 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
b.AppendLine("SELECT " b.AppendLine("SELECT "
+ SqlAs("nameid", newNames.NameID) + ", " + SqlAs("nameid", newNames.NameID) + ", "
+ SqlAs("SUM(timespent)", newNames.CpuCyclesSpent) + ", " + SqlAs("SUM(cpucyclesspent)", newNames.CpuCyclesSpent) + ", "
+ SqlAs("SUM(callcount)", newNames.CallCount) + ", " + SqlAs("SUM(callcount)", newNames.CallCount) + ", "
+ SqlAs("MAX(id != endid)", newNames.HasChildren) + ", " + SqlAs("MAX(hasChildren)", newNames.HasChildren) + ", "
+ SqlAs("SUM((datasetid = " + context.StartDataSetID + ") AND isActiveAtStart)", newNames.ActiveCallCount)); + SqlAs("SUM(CASE WHEN datasetid = " + context.StartDataSet.ID + " THEN activecallcount ELSE 0 END)", newNames.ActiveCallCount));
b.AppendLine("FROM FunctionData"); b.AppendLine("FROM Functions");
if (StartDataSet >= 0) if (StartDataSet >= 0)
b.AppendLine("WHERE datasetid BETWEEN " + StartDataSet + " AND " + EndDataSet); b.AppendLine("WHERE datasetid BETWEEN " + StartDataSet + " AND " + EndDataSet);
b.AppendLine("GROUP BY nameid"); 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
case ExpressionType.Call: case ExpressionType.Call:
WriteMethodCall((MethodCallExpression)expression); WriteMethodCall((MethodCallExpression)expression);
break; 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.AndAlso:
case ExpressionType.OrElse: case ExpressionType.OrElse:
case ExpressionType.LessThan: case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
case ExpressionType.GreaterThan: case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual: case ExpressionType.GreaterThanOrEqual:
case ExpressionType.Equal: case ExpressionType.Equal:
case ExpressionType.NotEqual: case ExpressionType.NotEqual:
BinaryExpression binary = (BinaryExpression)expression; {
w.Write('('); BinaryExpression binary = (BinaryExpression)expression;
Write(binary.Left); w.Write('(');
w.Write(' '); Write(binary.Left);
w.Write(GetOperatorSymbol(expression.NodeType)); w.Write(' ');
w.Write(' '); w.Write(GetOperatorSymbol(expression.NodeType));
Write(binary.Right); w.Write(' ');
w.Write(')'); Write(binary.Right);
w.Write(')');
}
break; break;
case ExpressionType.Constant: case ExpressionType.Constant:
var ce = (ConstantExpression)expression; var ce = (ConstantExpression)expression;
@ -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) void WriteMemberAccess(MemberExpression me)
{ {
if (me.Expression == callTreeNodeParameter) { if (me.Expression == callTreeNodeParameter) {
@ -114,7 +144,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
if (me.Member == SingleCall.IDField) { if (me.Member == SingleCall.IDField) {
w.Write("id"); w.Write("id");
} else if (me.Member == SingleCall.DataSetIdField) { } 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) { } else if (me.Member == SingleCall.ParentIDField) {
w.Write("parentid"); w.Write("parentid");
} else if (me.Member == KnownMembers.CallTreeNode_CallCount) { } 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
For QueryNodes that have input, that input must be another QueryNode. For QueryNodes that have input, that input must be another QueryNode.
QueryAst nodes: 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.Filter(x => condition1(x) && y => condition2(y)): WHERE clause with multiple conditions
input.MergeByName(): GROUP BY nameid input.MergeByName(): GROUP BY nameid
@ -137,15 +138,23 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
*/ */
readonly ProfilingDataSQLiteProvider sqliteProvider; 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) public SQLiteQueryProvider(ProfilingDataSQLiteProvider sqliteProvider, int startDataSetID, int endDataSetID)
{ {
if (sqliteProvider == null) if (sqliteProvider == null)
throw new ArgumentNullException("sqliteProvider"); throw new ArgumentNullException("sqliteProvider");
this.sqliteProvider = 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 // Implement GetMapping and ProcessorFrequency so that SQLiteQueryProvider can be used in place of
@ -176,8 +185,8 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
{ {
string command = string.Format( string command = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"SELECT id FROM FunctionData WHERE (nameid = {0}) AND (datasetid BETWEEN {1} AND {2});", "SELECT id FROM Calls WHERE (nameid = {0}) AND (id BETWEEN {1} AND {2});",
nameid, startDataSetID, endDataSetID); nameid, StartDataSet.RootID, EndDataSet.CallEndID);
return sqliteProvider.RunSQLIDList(command).ToArray(); 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
{ {
sealed class SqlQueryContext sealed class SqlQueryContext
{ {
public readonly int StartDataSetID; public readonly ProfilingDataSQLiteProvider.SQLiteDataSet StartDataSet;
public readonly ProfilingDataSQLiteProvider.SQLiteDataSet EndDataSet;
public CallTreeNodeSqlNameSet CurrentNameSet { get; private set; } public CallTreeNodeSqlNameSet CurrentNameSet { get; private set; }
@ -39,9 +40,18 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
this.HasIDList = hasIDList; this.HasIDList = hasIDList;
} }
SQLiteQueryProvider provider;
public SqlQueryContext(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; int uniqueVariableIndex;
@ -59,7 +69,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
/// </summary> /// </summary>
None, None,
/// <summary> /// <summary>
/// The FunctionData table /// The Calls table
/// </summary> /// </summary>
Calls Calls
} }

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

@ -34,7 +34,7 @@ namespace ICSharpCode.Profiler.Controller.Data
/// <summary> /// <summary>
/// Creates a new SQLite profiling data provider and opens a database stored in a file. /// Creates a new SQLite profiling data provider and opens a database stored in a file.
/// </summary> /// </summary>
public ProfilingDataSQLiteProvider(string fileName) private ProfilingDataSQLiteProvider(string fileName, bool allowUpgrade)
{ {
this.nameMappingCache = new Dictionary<int, NameMapping>(); this.nameMappingCache = new Dictionary<int, NameMapping>();
@ -42,7 +42,78 @@ namespace ICSharpCode.Profiler.Controller.Data
conn.Add("Data Source", fileName); conn.Add("Data Source", fileName);
this.connection = new SQLiteConnection(conn.ConnectionString); 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> /// <summary>
@ -50,7 +121,15 @@ namespace ICSharpCode.Profiler.Controller.Data
/// </summary> /// </summary>
public static ProfilingDataSQLiteProvider FromFile(string fileName) 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/> /// <inheritdoc/>
@ -103,25 +182,16 @@ namespace ICSharpCode.Profiler.Controller.Data
using (LockAndCreateCommand(out cmd)) { using (LockAndCreateCommand(out cmd)) {
SQLiteDataReader reader; SQLiteDataReader reader;
bool isFirstAllowed = true; cmd.CommandText = @"SELECT d.id, d.cpuusage, d.isfirst, d.rootid, c.endid
try { FROM DataSets d
cmd.CommandText = @"SELECT id, cpuusage, isfirst JOIN Calls c ON c.id = d.rootid
FROM DataSets ORDER BY d.id;";
ORDER BY id;";
reader = cmd.ExecuteReader();
reader = cmd.ExecuteReader();
} catch (SQLiteException) {
cmd.CommandText = @"SELECT id, cpuusage
FROM DataSets
ORDER BY id;";
reader = cmd.ExecuteReader();
isFirstAllowed = false;
}
while (reader.Read()) { 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
} }
} }
class SQLiteDataSet : IProfilingDataSet internal sealed class SQLiteDataSet : IProfilingDataSet
{ {
ProfilingDataSQLiteProvider provider; ProfilingDataSQLiteProvider provider;
int index; public readonly int ID;
double cpuUsage; double cpuUsage;
bool isFirst; 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.provider = provider;
this.index = index; this.ID = id;
this.cpuUsage = cpuUsage; this.cpuUsage = cpuUsage;
this.isFirst = isFirst; this.isFirst = isFirst;
this.RootID = rootID;
this.CallEndID = callEndID;
} }
public double CpuUsage { public double CpuUsage {
@ -155,7 +228,7 @@ namespace ICSharpCode.Profiler.Controller.Data
public CallTreeNode RootNode { public CallTreeNode RootNode {
get { 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
} }
} }
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() void InitializeTables()
{ {
//NameMapping { Id, ReturnType, Name, Parameters } //NameMapping { Id, ReturnType, Name, Parameters }
@ -197,9 +222,9 @@ namespace ICSharpCode.Profiler.Controller.Data
cmd.CommandText = @" cmd.CommandText = @"
CREATE TABLE NameMapping( CREATE TABLE NameMapping(
id INTEGER NOT NULL PRIMARY KEY, id INTEGER NOT NULL PRIMARY KEY,
returntype VARCHAR2(100) NOT NULL, returntype TEXT NOT NULL,
name VARCHAR2(255) NOT NULL, name TEXT NOT NULL,
parameters VARCHAR2(1000) NOT NULL parameters TEXT NOT NULL
); );
CREATE TABLE FunctionData( CREATE TABLE FunctionData(
@ -221,15 +246,15 @@ namespace ICSharpCode.Profiler.Controller.Data
); );
CREATE TABLE Properties( CREATE TABLE Properties(
name VARCHAR2(100) NOT NULL PRIMARY KEY, name TEXT NOT NULL PRIMARY KEY,
value VARCHAR2(100) NOT NULL value TEXT NOT NULL
); );
INSERT INTO Properties(name, value) VALUES('version', '1.0'); INSERT INTO Properties(name, value) VALUES('version', '1.0');
CREATE TABLE PerformanceCounter( CREATE TABLE PerformanceCounter(
id INTEGER NOT NULL PRIMARY KEY, id INTEGER NOT NULL PRIMARY KEY,
name VARCHAR2(100) NOT NULL name TEXT NOT NULL
); );
CREATE TABLE CounterData( CREATE TABLE CounterData(

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

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

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

@ -95,6 +95,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Analysis\IProfilingDataComparer.cs" /> <Compile Include="Analysis\IProfilingDataComparer.cs" />
<Compile Include="Data\IncompatibleDatabaseException.cs" />
<Compile Include="Data\Linq\AllCalls.cs" /> <Compile Include="Data\Linq\AllCalls.cs" />
<Compile Include="Data\Linq\AllFunctions.cs" /> <Compile Include="Data\Linq\AllFunctions.cs" />
<Compile Include="Data\Linq\ExpressionSqlWriter.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
foreach (CallTreeNodeViewModel node in list) { foreach (CallTreeNodeViewModel node in list) {
if (node != null) 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()); Clipboard.SetText(builder.ToString());

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

@ -5,11 +5,12 @@
// <version>$Revision$</version> // <version>$Revision$</version>
// </file> // </file>
using ICSharpCode.Profiler.Controller.Data;
using System; using System;
using System.IO; using System.IO;
using ICSharpCode.Profiler.Controller; using ICSharpCode.Core;
using ICSharpCode.Profiler.Controller.Data;
using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop;
using ICSharpCode.SharpDevelop.Gui;
namespace ICSharpCode.Profiler.AddIn.Views namespace ICSharpCode.Profiler.AddIn.Views
{ {
@ -29,7 +30,24 @@ namespace ICSharpCode.Profiler.AddIn.Views
public ICSharpCode.SharpDevelop.Gui.IViewContent CreateContentForFile(OpenedFile file) 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
/// <summary> /// <summary>
/// Creates a new WpfViewer object /// Creates a new WpfViewer object
/// </summary> /// </summary>
public WpfViewer(OpenedFile file) public WpfViewer(OpenedFile file, ProfilingDataSQLiteProvider provider)
{ {
// HACK : OpenedFile architecture does not allow to keep files open // HACK : OpenedFile architecture does not allow to keep files open
// but it is necessary for the ProfilerView to keep the session file 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. // We don't want to load all session data into memory.
// this.Files.Add(file); // 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.file = file;
this.provider = ProfilingDataSQLiteProvider.FromFile(file.FileName); this.provider = provider;
this.TabPageText = Path.GetFileName(file.FileName); this.TabPageText = Path.GetFileName(file.FileName);
this.TitleName = this.TabPageText; this.TitleName = this.TabPageText;
dataView = new ProfilerView(this.provider); 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) 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)) FileUtility.IsBaseDirectory(e.FileName, file.FileName))
this.WorkbenchWindow.CloseWindow(true); 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
const int k = 1000; const int k = 1000;
ProfilingDataSQLiteProvider provider; ProfilingDataSQLiteProvider provider;
const string databaseFileName = "test.sdps";
[TestFixtureSetUp] [TestFixtureSetUp]
public void FixtureSetUp() public void FixtureSetUp()
{ {
if (File.Exists("temp.sdps")) if (File.Exists(databaseFileName))
File.Delete("temp.sdps"); File.Delete(databaseFileName);
NameMapping method0 = new NameMapping(0, "r0", "m0", new List<string>()); NameMapping method0 = new NameMapping(0, "r0", "m0", new List<string>());
NameMapping method1 = new NameMapping(1, "r1", "m1", new List<string>()); NameMapping method1 = new NameMapping(1, "r1", "m1", new List<string>());
NameMapping method2 = new NameMapping(2, "r2", "m2", 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.ProcessorFrequency = 2000; // MHz
writer.WriteMappings(new[] { method0, method1, method2 } ); writer.WriteMappings(new[] { method0, method1, method2 } );
CallTreeNodeStub dataSet; CallTreeNodeStub dataSet;
@ -86,14 +88,14 @@ namespace Profiler.Tests.Controller.Data
writer.WriteDataSet(new DataSetStub { CpuUsage = 0.1, IsFirst = false, RootNode = dataSet }); writer.WriteDataSet(new DataSetStub { CpuUsage = 0.1, IsFirst = false, RootNode = dataSet });
writer.Close(); writer.Close();
} }
provider = new ProfilingDataSQLiteProvider("temp.sdps"); provider = ProfilingDataSQLiteProvider.UpgradeFromOldVersion(databaseFileName);
} }
[TestFixtureTearDown] [TestFixtureTearDown]
public void FixtureCleanUp() public void FixtureCleanUp()
{ {
provider.Dispose(); provider.Dispose();
File.Delete("temp.sdps"); File.Delete(databaseFileName);
} }
[Test] [Test]

Loading…
Cancel
Save