diff --git a/src/AddIns/Misc/Profiler/Controller/Data/Linq/ExpressionSqlWriter.cs b/src/AddIns/Misc/Profiler/Controller/Data/Linq/ExpressionSqlWriter.cs index dd54ea7561..5951f8cab7 100644 --- a/src/AddIns/Misc/Profiler/Controller/Data/Linq/ExpressionSqlWriter.cs +++ b/src/AddIns/Misc/Profiler/Controller/Data/Linq/ExpressionSqlWriter.cs @@ -7,13 +7,8 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; using System.IO; -using System.Linq; using System.Linq.Expressions; -using System.Reflection; -using System.Text; namespace ICSharpCode.Profiler.Controller.Data.Linq { @@ -107,6 +102,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq MethodCallExpression mc = (MethodCallExpression)expression; if (mc.Method == KnownMembers.ListOfInt_Contains) { List list = (List)((ConstantExpression)mc.Object).Value; + w.Write('('); Write(mc.Arguments[0]); w.Write(" IN ("); for (int i = 0; i < list.Count; i++) { @@ -114,7 +110,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq w.Write(','); w.Write(list[i]); } - w.Write(')'); + w.Write("))"); break; } else if (mc.Method == KnownMembers.Like) { w.Write("( "); diff --git a/src/AddIns/Misc/Profiler/Controller/Data/Linq/KnownMembers.cs b/src/AddIns/Misc/Profiler/Controller/Data/Linq/KnownMembers.cs index 642cc19c0e..5a3a41999e 100644 --- a/src/AddIns/Misc/Profiler/Controller/Data/Linq/KnownMembers.cs +++ b/src/AddIns/Misc/Profiler/Controller/Data/Linq/KnownMembers.cs @@ -6,17 +6,11 @@ // using System; -using System.Collections; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; -using IQToolkit; -using System.Text; - namespace ICSharpCode.Profiler.Controller.Data.Linq { static class KnownMembers diff --git a/src/AddIns/Misc/Profiler/Controller/Data/Linq/OptimizeQueryExpressionVisitor.cs b/src/AddIns/Misc/Profiler/Controller/Data/Linq/OptimizeQueryExpressionVisitor.cs index 48ce93215f..b82177273e 100644 --- a/src/AddIns/Misc/Profiler/Controller/Data/Linq/OptimizeQueryExpressionVisitor.cs +++ b/src/AddIns/Misc/Profiler/Controller/Data/Linq/OptimizeQueryExpressionVisitor.cs @@ -6,17 +6,11 @@ // using System; -using System.Collections; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; -using IQToolkit; -using System.Text; - namespace ICSharpCode.Profiler.Controller.Data.Linq { /// diff --git a/src/AddIns/Misc/Profiler/Controller/Data/Linq/QueryAst.cs b/src/AddIns/Misc/Profiler/Controller/Data/Linq/QueryAst.cs index eda0ff3ad3..346c3eb586 100644 --- a/src/AddIns/Misc/Profiler/Controller/Data/Linq/QueryAst.cs +++ b/src/AddIns/Misc/Profiler/Controller/Data/Linq/QueryAst.cs @@ -6,7 +6,6 @@ // using System; -using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; @@ -35,6 +34,9 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq public static readonly FieldInfo DataSetIdField = typeof(SingleCall).GetField("DataSetID"); } + /// + /// Base class for nodes in the Query AST. + /// abstract class QueryNode : Expression { public enum SqlStatementKind @@ -110,6 +112,11 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq } } + /// + /// Query AST node representing the whole 'FunctionData' table. + /// This is the source of all queries. + /// Produces SELECT .. FROM .. in SQL. + /// sealed class AllCalls : QueryNode { public static readonly AllCalls Instance = new AllCalls(); @@ -144,6 +151,9 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq } } + /// + /// Query node that represents the 'group by nameid and merge' operation. Produces SELECT ... GROUP BY .. in SQL. + /// sealed class MergeByName : QueryNode { public MergeByName(QueryNode target) @@ -187,6 +197,9 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq } } + /// + /// Query node that filters the input using conditions. Produces WHERE or HAVING in SQL. + /// sealed class Filter : QueryNode { /// diff --git a/src/AddIns/Misc/Profiler/Controller/Data/Linq/SQLiteQueryProvider.cs b/src/AddIns/Misc/Profiler/Controller/Data/Linq/SQLiteQueryProvider.cs index 3e4da3a8a3..71390384af 100644 --- a/src/AddIns/Misc/Profiler/Controller/Data/Linq/SQLiteQueryProvider.cs +++ b/src/AddIns/Misc/Profiler/Controller/Data/Linq/SQLiteQueryProvider.cs @@ -6,16 +6,12 @@ // using System; -using System.Collections; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; using IQToolkit; -using System.Text; namespace ICSharpCode.Profiler.Controller.Data.Linq { @@ -46,14 +42,14 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq Only a limited set of expressions are valid in conditions and sort descriptors. These are checked by the SafeExpressionImporter. The set of valid expressions is: - - Integer constants - - String constants - - Binary operators: < <= > >= == != && || LIKE GLOB - - Unary operator: ! - - value(List).Contains(validExpr) - - if c is the lambda parameter, then these expressions are valid: - c.NameMapping.ID - c.NameMapping.Name + - Integer constants + - String constants + - Binary operators: < <= > >= == != && || LIKE GLOB + - Unary operator: ! + - value(List).Contains(validExpr) + - if c is the lambda parameter, then these expressions are valid: + c.NameMapping.ID + c.NameMapping.Name Additionally, field references on a lambda parameter of type SingleCall are valid inside filters that operate directly on "AllCalls" (e.g. AllCalls.Filter()). @@ -94,7 +90,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq Optimization of QueryAst: The OptimizeQueryExpressionVisitor is performing these optimizations: x.Filter(y).Filter(z) -> x.Filter(y && z) - x.MergeByName().Filter(criteria) -> x.Filter(x, criteria).MergeByName() for some safe criterias + x.MergeByName().Filter(criteria) -> x.Filter(criteria).MergeByName() for some safe criterias Criterias are safe if they access no CallTreeNode properties except for NameMapping x.MergeByName().MergeByName() -> x.MergeByName() @@ -200,8 +196,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq { protected override Expression VisitExtension(Expression node) { - // The .NET ExpressionVisitor cannot recurse into our query nodes - - // but those don't need conversion anymore. + // We found a query that's already converted, let's keep it as it is. QueryNode query = node as QueryNode; if (query != null) return query; @@ -252,6 +247,10 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq this.callTreeNodeParameter = callTreeNodeParameter; } + /// + /// Imports 'expr' and adds the imported conditions to 'filters'. + /// + /// True if the import was successful. public bool AddConditionsToFilterList(Expression expr, List filters) { if (expr.NodeType == ExpressionType.AndAlso) { @@ -279,6 +278,10 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq /// static readonly char[] forbiddenGlobChars = new[] { '*', '?', '[', ']' }; + /// + /// Imports an expresion. + /// + /// The imported expression; or null if the expression was not safe for import. public Expression Import(Expression expr) { switch (expr.NodeType) { @@ -372,6 +375,9 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq .Replace("?", escape + "?"); } + /// + /// Tests if expr is 'c.NameMapping'. + /// bool IsNameMappingOnParameter(Expression expr) { if (expr.NodeType == ExpressionType.MemberAccess) { diff --git a/src/AddIns/Misc/Profiler/Controller/Data/Linq/SqlQueryContext.cs b/src/AddIns/Misc/Profiler/Controller/Data/Linq/SqlQueryContext.cs index 349a7dca36..65ce67639d 100644 --- a/src/AddIns/Misc/Profiler/Controller/Data/Linq/SqlQueryContext.cs +++ b/src/AddIns/Misc/Profiler/Controller/Data/Linq/SqlQueryContext.cs @@ -6,14 +6,6 @@ // using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Text; namespace ICSharpCode.Profiler.Controller.Data.Linq { diff --git a/src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataSQLiteWriter.cs b/src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataSQLiteWriter.cs index 0f85da436d..051f11ed92 100644 --- a/src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataSQLiteWriter.cs +++ b/src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataSQLiteWriter.cs @@ -70,8 +70,13 @@ namespace ICSharpCode.Profiler.Controller.Data public void Close() { using (SQLiteCommand cmd = this.connection.CreateCommand()) { + // create index at the end (after inserting data), this is faster cmd.CommandText = @"CREATE INDEX Parents ON FunctionData(parentid ASC);"; cmd.ExecuteNonQuery(); + + // make SQLite analyze the indices available; this will help the query planner later + cmd.CommandText = @"ANALYZE;"; + cmd.ExecuteNonQuery(); } this.Dispose(); diff --git a/src/AddIns/Misc/Profiler/Controller/Data/SQLiteCallTreeNode.cs b/src/AddIns/Misc/Profiler/Controller/Data/SQLiteCallTreeNode.cs index 251a1ad232..a8380160f1 100644 --- a/src/AddIns/Misc/Profiler/Controller/Data/SQLiteCallTreeNode.cs +++ b/src/AddIns/Misc/Profiler/Controller/Data/SQLiteCallTreeNode.cs @@ -80,12 +80,20 @@ namespace ICSharpCode.Profiler.Controller.Data return this.parent; } } - + + readonly static IQueryable EmptyQueryable = Enumerable.Empty().AsQueryable(); + /// /// Returns all children of the current CallTreeNode, sorted by order of first call. /// public override IQueryable Children { get { + // Approx. half of all nodes in a complex data set are leafs. + // Not doing an SQL Query in these cases can safe time when someone recursively walks the call-tree + // (e.g. RingDiagramControl) + if (!hasChildren) + return EmptyQueryable; + Expression> filterLambda = c => this.ids.Contains(c.ParentID); return provider.CreateQuery(new MergeByName(new Filter(AllCalls.Instance, filterLambda))); } diff --git a/src/AddIns/Misc/Profiler/Tests/Profiler.Tests/Controller/Data/LinqTests.cs b/src/AddIns/Misc/Profiler/Tests/Profiler.Tests/Controller/Data/LinqTests.cs index 09202a053c..51a8cce9bf 100644 --- a/src/AddIns/Misc/Profiler/Tests/Profiler.Tests/Controller/Data/LinqTests.cs +++ b/src/AddIns/Misc/Profiler/Tests/Profiler.Tests/Controller/Data/LinqTests.cs @@ -7,6 +7,7 @@ using System; +using System.Linq; using System.IO; using System.Collections.Generic; using ICSharpCode.Profiler.Controller.Data; @@ -26,18 +27,18 @@ namespace Profiler.Tests.Controller.Data { if (File.Exists("temp.sdps")) File.Delete("temp.sdps"); + NameMapping method0 = new NameMapping(0, "r0", "m0", new List()); NameMapping method1 = new NameMapping(1, "r1", "m1", new List()); NameMapping method2 = new NameMapping(2, "r2", "m2", new List()); - NameMapping method3 = new NameMapping(3, "r3", "m3", new List()); using (var writer = new ProfilingDataSQLiteWriter("temp.sdps", false, null)) { writer.ProcessorFrequency = 2000; // MHz - writer.WriteMappings(new[] { method1, method2, method3 } ); + writer.WriteMappings(new[] { method0, method1, method2 } ); CallTreeNodeStub dataSet; dataSet = new CallTreeNodeStub { - NameMappingValue = method1, + NameMappingValue = method0, AddChildren = { new CallTreeNodeStub { - NameMappingValue = method2, + NameMappingValue = method1, RawCallCountValue = 10, CpuCyclesSpentValue = 500 * k } @@ -45,26 +46,46 @@ namespace Profiler.Tests.Controller.Data }; writer.WriteDataSet(new DataSetStub { CpuUsage = 0.3, IsFirst = true, RootNode = dataSet }); dataSet = new CallTreeNodeStub { - NameMappingValue = method1, + NameMappingValue = method0, IsActiveAtStartValue = true, AddChildren = { new CallTreeNodeStub { - NameMappingValue = method2, + NameMappingValue = method1, RawCallCountValue = 0, IsActiveAtStartValue = true, CpuCyclesSpentValue = 200 * k }, new CallTreeNodeStub { - NameMappingValue = method3, + NameMappingValue = method2, RawCallCountValue = 1, - IsActiveAtStartValue = true, CpuCyclesSpentValue = 300 * k } } }; writer.WriteDataSet(new DataSetStub { CpuUsage = 0.4, IsFirst = false, RootNode = dataSet }); + dataSet = new CallTreeNodeStub { + NameMappingValue = method0, + IsActiveAtStartValue = true, + AddChildren = { + new CallTreeNodeStub { + NameMappingValue = method2, + RawCallCountValue = 0, + IsActiveAtStartValue = true, + CpuCyclesSpentValue = 50 * k, + AddChildren = { + new CallTreeNodeStub { + NameMappingValue = method1, + RawCallCountValue = 5, + CpuCyclesSpentValue = 1 * k + } + } + } + } + }; + writer.WriteDataSet(new DataSetStub { CpuUsage = 0.1, IsFirst = false, RootNode = dataSet }); + writer.Close(); } - provider = new ProfilingDataSQLiteProvider("test.sdps"); + provider = new ProfilingDataSQLiteProvider("temp.sdps"); } [TestFixtureTearDown] @@ -75,9 +96,51 @@ namespace Profiler.Tests.Controller.Data } [Test] - public void TestMethod() + public void TestDataSets() + { + Assert.AreEqual(3, provider.DataSets.Count); + Assert.AreEqual(0.3, provider.DataSets[0].CpuUsage); + Assert.AreEqual(0.4, provider.DataSets[1].CpuUsage); + Assert.AreEqual(0.1, provider.DataSets[2].CpuUsage); + + Assert.IsTrue(provider.DataSets[0].IsFirst); + Assert.IsFalse(provider.DataSets[1].IsFirst); + Assert.IsFalse(provider.DataSets[2].IsFirst); + } + + [Test] + public void TestMergedTree() + { + CallTreeNode root = provider.GetRoot(0, 1); + Assert.IsTrue(root.HasChildren); + CallTreeNode[] children = root.Children.ToArray(); + Assert.AreEqual(2, children.Length); + Assert.IsFalse(children[0].HasChildren); + Assert.AreEqual("m1", children[0].Name); + Assert.AreEqual(10, children[0].CallCount); + Assert.AreEqual(700 * k, children[0].CpuCyclesSpent); + + Assert.IsFalse(children[1].HasChildren); + Assert.AreEqual("m2", children[1].Name); + Assert.AreEqual(1, children[1].CallCount); + Assert.AreEqual(300 * k, children[1].CpuCyclesSpent); + } + + [Test] + public void TestFunctions() { - // TODO: Add your test. + CallTreeNode[] functions = provider.GetFunctions(1, 2).OrderBy(f => f.Name).ToArray(); + Assert.AreEqual(2, functions.Length); + + Assert.AreEqual("m1", functions[0].Name); + Assert.IsFalse(functions[0].HasChildren); + Assert.AreEqual(6, functions[0].CallCount); + Assert.AreEqual(201 * k, functions[0].CpuCyclesSpent); + + Assert.AreEqual("m2", functions[1].Name); + Assert.IsTrue(functions[1].HasChildren); + Assert.AreEqual(1, functions[1].CallCount); + Assert.AreEqual(350 * k, functions[1].CpuCyclesSpent); } } }