Browse Source

Add unit test for profiler.

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5024 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
Daniel Grunwald 17 years ago
parent
commit
7b0a2b6b3d
  1. 8
      src/AddIns/Misc/Profiler/Controller/Data/Linq/ExpressionSqlWriter.cs
  2. 6
      src/AddIns/Misc/Profiler/Controller/Data/Linq/KnownMembers.cs
  3. 6
      src/AddIns/Misc/Profiler/Controller/Data/Linq/OptimizeQueryExpressionVisitor.cs
  4. 15
      src/AddIns/Misc/Profiler/Controller/Data/Linq/QueryAst.cs
  5. 36
      src/AddIns/Misc/Profiler/Controller/Data/Linq/SQLiteQueryProvider.cs
  6. 8
      src/AddIns/Misc/Profiler/Controller/Data/Linq/SqlQueryContext.cs
  7. 5
      src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataSQLiteWriter.cs
  8. 10
      src/AddIns/Misc/Profiler/Controller/Data/SQLiteCallTreeNode.cs
  9. 85
      src/AddIns/Misc/Profiler/Tests/Profiler.Tests/Controller/Data/LinqTests.cs

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

@ -7,13 +7,8 @@ @@ -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 @@ -107,6 +102,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
MethodCallExpression mc = (MethodCallExpression)expression;
if (mc.Method == KnownMembers.ListOfInt_Contains) {
List<int> list = (List<int>)((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 @@ -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("( ");

6
src/AddIns/Misc/Profiler/Controller/Data/Linq/KnownMembers.cs

@ -6,17 +6,11 @@ @@ -6,17 +6,11 @@
// </file>
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

6
src/AddIns/Misc/Profiler/Controller/Data/Linq/OptimizeQueryExpressionVisitor.cs

@ -6,17 +6,11 @@ @@ -6,17 +6,11 @@
// </file>
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
{
/// <summary>

15
src/AddIns/Misc/Profiler/Controller/Data/Linq/QueryAst.cs

@ -6,7 +6,6 @@ @@ -6,7 +6,6 @@
// </file>
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 @@ -35,6 +34,9 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
public static readonly FieldInfo DataSetIdField = typeof(SingleCall).GetField("DataSetID");
}
/// <summary>
/// Base class for nodes in the Query AST.
/// </summary>
abstract class QueryNode : Expression
{
public enum SqlStatementKind
@ -110,6 +112,11 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -110,6 +112,11 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
}
}
/// <summary>
/// Query AST node representing the whole 'FunctionData' table.
/// This is the source of all queries.
/// Produces SELECT .. FROM .. in SQL.
/// </summary>
sealed class AllCalls : QueryNode
{
public static readonly AllCalls Instance = new AllCalls();
@ -144,6 +151,9 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -144,6 +151,9 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
}
}
/// <summary>
/// Query node that represents the 'group by nameid and merge' operation. Produces SELECT ... GROUP BY .. in SQL.
/// </summary>
sealed class MergeByName : QueryNode
{
public MergeByName(QueryNode target)
@ -187,6 +197,9 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -187,6 +197,9 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
}
}
/// <summary>
/// Query node that filters the input using conditions. Produces WHERE or HAVING in SQL.
/// </summary>
sealed class Filter : QueryNode
{
/// <summary>

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

@ -6,16 +6,12 @@ @@ -6,16 +6,12 @@
// </file>
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 @@ -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<int>).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<int>).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 @@ -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 @@ -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 @@ -252,6 +247,10 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
this.callTreeNodeParameter = callTreeNodeParameter;
}
/// <summary>
/// Imports 'expr' and adds the imported conditions to 'filters'.
/// </summary>
/// <returns>True if the import was successful.</returns>
public bool AddConditionsToFilterList(Expression expr, List<LambdaExpression> filters)
{
if (expr.NodeType == ExpressionType.AndAlso) {
@ -279,6 +278,10 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -279,6 +278,10 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
/// </summary>
static readonly char[] forbiddenGlobChars = new[] { '*', '?', '[', ']' };
/// <summary>
/// Imports an expresion.
/// </summary>
/// <returns>The imported expression; or null if the expression was not safe for import.</returns>
public Expression Import(Expression expr)
{
switch (expr.NodeType) {
@ -372,6 +375,9 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -372,6 +375,9 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
.Replace("?", escape + "?");
}
/// <summary>
/// Tests if expr is 'c.NameMapping'.
/// </summary>
bool IsNameMappingOnParameter(Expression expr)
{
if (expr.NodeType == ExpressionType.MemberAccess) {

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

@ -6,14 +6,6 @@ @@ -6,14 +6,6 @@
// </file>
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
{

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

@ -70,8 +70,13 @@ namespace ICSharpCode.Profiler.Controller.Data @@ -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();

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

@ -80,12 +80,20 @@ namespace ICSharpCode.Profiler.Controller.Data @@ -80,12 +80,20 @@ namespace ICSharpCode.Profiler.Controller.Data
return this.parent;
}
}
readonly static IQueryable<CallTreeNode> EmptyQueryable = Enumerable.Empty<CallTreeNode>().AsQueryable();
/// <summary>
/// Returns all children of the current CallTreeNode, sorted by order of first call.
/// </summary>
public override IQueryable<CallTreeNode> 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<Func<SingleCall, bool>> filterLambda = c => this.ids.Contains(c.ParentID);
return provider.CreateQuery(new MergeByName(new Filter(AllCalls.Instance, filterLambda)));
}

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

@ -7,6 +7,7 @@ @@ -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 @@ -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<string>());
NameMapping method1 = new NameMapping(1, "r1", "m1", new List<string>());
NameMapping method2 = new NameMapping(2, "r2", "m2", new List<string>());
NameMapping method3 = new NameMapping(3, "r3", "m3", new List<string>());
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 @@ -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 @@ -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);
}
}
}

Loading…
Cancel
Save