Browse Source

LINQ-to-Profiler: implemented query execution.

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5016 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
Daniel Grunwald 16 years ago
parent
commit
b20a72473f
  1. 2
      src/AddIns/Misc/Profiler/Controller/Data/CallTreeNode.cs
  2. 1
      src/AddIns/Misc/Profiler/Controller/Data/Linq/KnownMembers.cs
  3. 8
      src/AddIns/Misc/Profiler/Controller/Data/Linq/QueryAst.cs
  4. 188
      src/AddIns/Misc/Profiler/Controller/Data/Linq/SQLiteQueryProvider.cs
  5. 7
      src/AddIns/Misc/Profiler/Controller/Data/Linq/SqlQueryContext.cs
  6. 118
      src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataSQLiteProvider.cs
  7. 14
      src/AddIns/Misc/Profiler/Controller/Data/SQLiteCallTreeNode.cs
  8. 18
      src/AddIns/Misc/Profiler/Controller/ExtensionMethods.cs

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

@ -79,8 +79,6 @@ namespace ICSharpCode.Profiler.Controller.Data @@ -79,8 +79,6 @@ namespace ICSharpCode.Profiler.Controller.Data
/// </summary>
public abstract long CpuCyclesSpent { get; }
long cpuCyclesSpentSelf = -1;
/// <summary>
/// Gets how many CPU cycles were spent inside this method, excluding sub calls.
/// </summary>

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

@ -28,6 +28,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -28,6 +28,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
public static readonly PropertyInfo NameMapping_ID = PropertyOf((NameMapping n) => n.Id);
public static readonly MethodInfo QuerableOfCallTreeNode_Select = MethodOf((IQueryable<CallTreeNode> q) => q.Select(x => x));
public static readonly MethodInfo QuerableOfCallTreeNode_Where = MethodOf((IQueryable<CallTreeNode> q) => q.Where(x => true));
public static readonly MethodInfo Merge = MethodOf((IQueryable<CallTreeNode> q) => q.Merge());
#region InfoOf Helper Methods
static MethodInfo MethodOf<T, R>(Expression<Func<T, R>> ex)

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

@ -101,12 +101,12 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -101,12 +101,12 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
return expression + " AS " + newName;
}
public virtual IEnumerable<CallTreeNode> Execute(ProfilingDataSQLiteProvider provider)
public virtual IQueryable<CallTreeNode> Execute(SQLiteQueryProvider provider)
{
StringBuilder b = new StringBuilder();
BuildSql(b, new SqlQueryContext());
BuildSql(b, new SqlQueryContext(provider));
Console.WriteLine(b.ToString());
return Enumerable.Empty<CallTreeNode>().AsQueryable();
return provider.RunSQL(b.ToString()).AsQueryable();
}
}
@ -138,7 +138,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -138,7 +138,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
+ SqlAs("timespent", newNames.TimeSpent) + ", "
+ SqlAs("callcount", newNames.CallCount) + ", "
+ SqlAs("(id != endid)", newNames.HasChildren) + ", "
+ SqlAs("((datasetid = @start) AND isActiveAtStart)", newNames.ActiveCallCount));
+ SqlAs("((datasetid = " + context.StartDataSetID + ") AND isActiveAtStart)", newNames.ActiveCallCount));
b.AppendLine("FROM FunctionData");
return SqlStatementKind.Select;
}

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

@ -21,9 +21,9 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -21,9 +21,9 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
{
/// <summary>
/// "LINQ-To-Profiler" QueryProvider implementation for SQLiteCallTreeNode.
///
///
/// Input to a LINQ QueryProvider is a System.Linq.Expressions tree describing the query that should be executed.
///
///
/// Query execution is done as:
/// 1. Partial evaluation
/// 2. Translation of expression tree to QueryAst
@ -31,85 +31,114 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -31,85 +31,114 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
/// 4. Execution of Queries (by converting them to SQL and running it on the DB)
/// 5. Execution of remaining query using LINQ-to-Objects
/// </summary>
/// <remarks>
/// The base class of all QueryAst nodes is QueryNode. A QueryNode represents a query of type IQueryable{CallTreeNode}.
/// For QueryNodes that have input, that input must be another QueryNode.
///
/// QueryAst nodes:
/// AllCalls: represents the whole FunctionData table
/// input.Filter(x => condition1(x) &amp;&amp; y => condition2(y)): WHERE clause with multiple conditions
/// input.MergeByName(): GROUP BY nameid
///
/// Valid expressions in QueryAst nodes:
/// Only a limited set of expressions are valid in conditions and sort descriptors.
/// These are checked by the SafeExpressionImporter.
/// - Integer constants
/// - Binary operators: &lt; &lt;= &gt; &gt;= == !=
/// - value(List{int}).Contains(validExpr)
/// - if c is the lambda parameter, then these expressions are valid:
/// c.NameMapping.ID
///
/// Additionally, field references on a lambda parameter of type SingleCall are valid inside
/// filters that operate directly on "AllCalls" (e.g. AllCalls.Filter()).
/// In other cases (other filters, sort descriptors), SingleCall usage is invalid.
/// SingleCall usage cannot be imported using SafeExpressionImporter; but is created directly for
/// some expressions on SQLiteCallTreeNode (see translation rules below).
///
/// Translation rules from CallTreeNode object model to QueryAst:
/// Properties serving as query roots:
/// sqliteCallTreeNode.Children = AllCalls.Filter((SingleCall c) -> sqliteCallTreeNode.ids.Contains(c.ParentID))
/// profilingDataSQLiteProvider.GetFunctions = AllCalls.Filter((SingleCall c) -> @start &lt;= c.DataSetId &amp;&amp; c.DataSetId &lt;= @end).MergeByName()
///
/// Translation rules for query nodes:
/// input.Where(x => f(x)) -> input.Filter(x => f'(x)), if f(x) is a safe expression
/// Note: if the root expression of a filter condition is the '&amp;&amp;' operator, a filter with multiple conditions is created.
/// input.Where(c => c.CallCount > 10 &amp;&amp; c.TimeSpent > 1)
/// -> input.Filter(c => c.CallCount > 10 &amp;&amp; c => c.TimeSpent > 1)
///
/// input.Select(x => x) -> input
/// This rule is necessary to remove degenerate selects so that the parts of the query continuing after the select
/// can also be represented as QueryNodes.
///
/// Translation rules for expression importer:
/// Any valid expressions (as defined in 'valid expressions in QueryAst nodes') are copied over directly.
/// Moreover, these expressions are be converted into valid expressions:
/// c.IsUserCode -> c.NameMapping.ID > 0
///
/// Optimization of QueryAst:
/// The OptimizeQueryExpressionVisitor is performing these optimizations:
/// x.Filter(y).Filter(z) -> x.Filter(y &amp;&amp; z)
/// x.MergeByName().Filter(criteria) -> x.Filter(x, criteria).MergeByName() for some safe criterias
/// Criterias are safe if they access no CallTreeNode properties except for NameMapping
///
/// SQL string building and execution:
/// It must be possible to create SQL for every combination of QueryNodes, even if they do strange things like merging multiple times.
/// To solve this, we define that every SQL query must have the same set of result fields, which are dynamically named to
/// ensure unique names even with nested queries. The current set of names is held in the SqlQueryContext.
/// Indeed, conceptually we build a nested query for every QueryNode.
///
/// The query is built inside-out: the innermost nested query is appended first to the StringBuilder, the outer queries will
/// then insert "SELECT ... FROM (" into the beginning of the StringBuilder and append ") outer query" at the end.
///
/// The return value of the QueryNode.BuildSql method contains the kind of SQL statement that is currently in the StringBuilder.
/// This allows us to simply append clauses in the majority of cases, only rarely the QueryNode.WrapSqlIntoNestedStatement
/// method will be used to create an outer query.
/// For example, a Filter will simply append a WHERE to a "SELECT .. FROM .." query. To a "SELECT .. FROM .. GROUP BY .." query,
/// a Filter will append HAVING. Only in rare cases like filtering after sorting or after limiting the number of elements,
/// a Filter query node will create a nested query.
///
/// Because all constructed SELECT queries always select fields with the same meaning in the same order, executing the query is
/// a matter of simply filling the SQLiteCallTreeNodes with the query results.
///
/// </remarks>
sealed class SQLiteQueryProvider : QueryProvider
{
/*
The base class of all QueryAst nodes is QueryNode. A QueryNode represents a query of type IQueryable<CallTreeNode>.
For QueryNodes that have input, that input must be another QueryNode.
QueryAst nodes:
AllCalls: represents the whole FunctionData table
input.Filter(x => condition1(x) && y => condition2(y)): WHERE clause with multiple conditions
input.MergeByName(): GROUP BY nameid
Valid expressions in QueryAst nodes:
Only a limited set of expressions are valid in conditions and sort descriptors.
These are checked by the SafeExpressionImporter.
- Integer constants
- Binary operators: < <= > >= == != && ||
- value(List<int>).Contains(validExpr)
- if c is the lambda parameter, then these expressions are valid:
c.NameMapping.ID
Additionally, field references on a lambda parameter of type SingleCall are valid inside
filters that operate directly on "AllCalls" (e.g. AllCalls.Filter()).
In other cases (other filters, sort descriptors), SingleCall usage is invalid.
SingleCall usage cannot be imported using SafeExpressionImporter; but is created directly for
some expressions on SQLiteCallTreeNode (see translation rules below).
Translation rules from CallTreeNode object model to QueryAst:
Properties serving as query roots:
sqliteCallTreeNode.Children
-> AllCalls.Filter((SingleCall c) => sqliteCallTreeNode.ids.Contains(c.ParentID))
profilingDataSQLiteProvider.GetFunctions
-> AllCalls.Filter((SingleCall c) => @start <= c.DataSetId && c.DataSetId <= @end).MergeByName()
profilingDataSQLiteProvider.GetRoot
-> AllCalls.Filter((SingleCall c) => @start <= c.DataSetId && c.DataSetId <= @end
&& c => c.ParentID == -1).Merge()
Translation rules for query nodes:
input.Where(x => f(x)) -> input.Filter(x => f'(x)), if f(x) is a safe expression
Note: If the root expression of a filter condition is the '&&' operator, a filter with multiple conditions is created.
This allows the optimizer to move around the filter conditions independently.
input.Where(c => c.CallCount > 10 && c.TimeSpent > 1)
-> input.Filter(c => c.CallCount > 10 && c => c.TimeSpent > 1)
input.Select(x => x) -> input
This rule is necessary to remove degenerate selects so that the parts of the query continuing after the select
can also be represented as QueryNodes.
Translation rules for expression importer:
Any valid expressions (as defined in 'valid expressions in QueryAst nodes') are copied over directly.
Moreover, these expressions are be converted into valid expressions:
c.IsUserCode -> c.NameMapping.ID > 0
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
Criterias are safe if they access no CallTreeNode properties except for NameMapping
SQL string building and execution:
It must be possible to create SQL for every combination of QueryNodes, even if they do strange things like merging multiple times.
To solve this, we define that every SQL query must have the same set of result fields, which are dynamically named to
ensure unique names even with nested queries. The current set of names is held in the SqlQueryContext.
Indeed, conceptually we build a nested query for every QueryNode.
The query is built inside-out: the innermost nested query is appended first to the StringBuilder, the outer queries will
then insert "SELECT ... FROM (" into the beginning of the StringBuilder and append ") outer query" at the end.
The return value of the QueryNode.BuildSql method contains the kind of SQL statement that is currently in the StringBuilder.
This allows us to simply append clauses in the majority of cases, only rarely the QueryNode.WrapSqlIntoNestedStatement
method will be used to create an outer query.
For example, a Filter will simply append a WHERE to a "SELECT .. FROM .." query. To a "SELECT .. FROM .. GROUP BY .." query,
a Filter will append HAVING. Only in rare cases like filtering after sorting or after limiting the number of elements,
a Filter query node will create a nested query.
Because all constructed SELECT queries always select fields with the same meaning in the same order, executing the query is
a matter of simply filling the SQLiteCallTreeNodes with the query results.
*/
readonly ProfilingDataSQLiteProvider sqliteProvider;
internal readonly int startDataSetID;
public SQLiteQueryProvider(ProfilingDataSQLiteProvider sqliteProvider)
public SQLiteQueryProvider(ProfilingDataSQLiteProvider sqliteProvider, int startDataSetID)
{
if (sqliteProvider == null)
throw new ArgumentNullException("sqliteProvider");
this.sqliteProvider = sqliteProvider;
this.startDataSetID = startDataSetID;
}
// Implement GetMapping and ProcessorFrequency so that SQLiteQueryProvider can be used in place of
// ProfilingDataSQLiteProvider.
public NameMapping GetMapping(int nameID)
{
return sqliteProvider.GetMapping(nameID);
}
public int ProcessorFrequency {
get { return sqliteProvider.ProcessorFrequency; }
}
internal IList<CallTreeNode> RunSQL(string command)
{
return sqliteProvider.RunSQL(this, command);
}
public IQueryable<CallTreeNode> CreateQuery(QueryNode query)
{
return new Query<CallTreeNode>(this, query);
}
public override string GetQueryText(Expression expression)
@ -133,10 +162,13 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -133,10 +162,13 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
// If the whole query was converted, execute it:
QueryNode query = expression as QueryNode;
if (query != null)
return query.Execute(sqliteProvider);
return query.Execute(this);
// Query not converted completely: we have to use a LINQ-To-Objects / LINQ-To-Profiler mix
expression = new ExecuteAllQueriesVisitor(sqliteProvider).Visit(expression);
expression = new ExecuteAllQueriesVisitor(this).Visit(expression);
if (expression.Type.IsValueType) {
expression = Expression.Convert(expression, typeof(object));
}
var lambdaExpression = Expression.Lambda<Func<object>>(expression);
return lambdaExpression.Compile()();
}
@ -246,6 +278,8 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -246,6 +278,8 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
}
return null;
}
case ExpressionType.AndAlso:
case ExpressionType.OrElse:
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
case ExpressionType.GreaterThan:
@ -281,9 +315,9 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -281,9 +315,9 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
#region ExecuteAllQueriesVisitor
sealed class ExecuteAllQueriesVisitor : System.Linq.Expressions.ExpressionVisitor
{
readonly ProfilingDataSQLiteProvider sqliteProvider;
readonly SQLiteQueryProvider sqliteProvider;
public ExecuteAllQueriesVisitor(ProfilingDataSQLiteProvider sqliteProvider)
public ExecuteAllQueriesVisitor(SQLiteQueryProvider sqliteProvider)
{
this.sqliteProvider = sqliteProvider;
}

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

@ -19,6 +19,13 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq @@ -19,6 +19,13 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
{
sealed class SqlQueryContext
{
public readonly int StartDataSetID;
public SqlQueryContext(SQLiteQueryProvider provider)
{
this.StartDataSetID = provider.startDataSetID;
}
int uniqueVariableIndex;
public string GenerateUniqueVariableName()

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

@ -59,46 +59,6 @@ namespace ICSharpCode.Profiler.Controller.Data @@ -59,46 +59,6 @@ namespace ICSharpCode.Profiler.Controller.Data
this.Dispose();
}
internal IQueryable<CallTreeNode> GetCallers(SQLiteCallTreeNode item)
{
SQLiteCommand cmd;
using (LockAndCreateCommand(out cmd)) {
cmd.CommandText = @"SELECT id, nameid, callcount, timespent, isactiveatstart
FROM FunctionData
WHERE id IN(
SELECT parentid
FROM FunctionData
WHERE id IN(" + string.Join(",", item.ids.Select(s => s.ToString()).ToArray()) + @")
)
ORDER BY id;";
Debug.Print("GetCallers cmd: " + cmd.CommandText);
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, null, this);
newItem.selectionStartIndex = item.selectionStartIndex;
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(); // TODO : remove Cast<> in .NET 4.0
}
}
}
/// <inheritdoc/>
public override NameMapping GetMapping(int nameId)
{
@ -246,32 +206,10 @@ namespace ICSharpCode.Profiler.Controller.Data @@ -246,32 +206,10 @@ namespace ICSharpCode.Profiler.Controller.Data
startIndex = endIndex;
endIndex = help;
}
SQLiteCommand cmd;
using (LockAndCreateCommand(out cmd)) {
cmd.CommandText = @"SELECT id, nameid, callcount, timespent
FROM FunctionData
WHERE id IN(
SELECT rootid
FROM DataSets
WHERE id BETWEEN " + startIndex + " AND " + endIndex + @"
)
ORDER BY id;";
using (SQLiteDataReader reader = cmd.ExecuteReader()) {
SQLiteCallTreeNode root = new SQLiteCallTreeNode(0, null, this);
root.selectionStartIndex = startIndex;
while (reader.Read()) {
root.callCount += reader.GetInt32(2);
root.cpuCyclesSpent += (ulong)reader.GetInt64(3);
root.ids.Add(reader.GetInt32(0));
}
return root;
}
}
SQLiteQueryProvider queryProvider = new SQLiteQueryProvider(this, startIndex);
Expression<Func<SingleCall, bool>> filterLambda = c => c.ParentID == -1;
return queryProvider.CreateQuery(new Filter(AllCalls.Instance, DataSetFilter(startIndex, endIndex), filterLambda)).Merge();
}
#region Properties
@ -340,11 +278,6 @@ namespace ICSharpCode.Profiler.Controller.Data @@ -340,11 +278,6 @@ namespace ICSharpCode.Profiler.Controller.Data
}
}
internal IQueryable<CallTreeNode> CreateQuery(QueryNode query)
{
return new IQToolkit.Query<CallTreeNode>(new SQLiteQueryProvider(this), query);
}
/// <inheritdoc/>
public override IQueryable<CallTreeNode> GetFunctions(int startIndex, int endIndex)
{
@ -353,51 +286,48 @@ namespace ICSharpCode.Profiler.Controller.Data @@ -353,51 +286,48 @@ namespace ICSharpCode.Profiler.Controller.Data
if (endIndex < startIndex || endIndex >= this.DataSets.Count)
throw new ArgumentOutOfRangeException("endIndex", endIndex, "Value must be between " + startIndex + " and " + (this.DataSets.Count - 1));
Expression<Func<SingleCall, bool>> filterLambda = c => startIndex <= c.DataSetID && c.DataSetID <= endIndex;
return from c in CreateQuery(new MergeByName(new Filter(AllCalls.Instance, filterLambda)))
SQLiteQueryProvider queryProvider = new SQLiteQueryProvider(this, startIndex);
var query = queryProvider.CreateQuery(new MergeByName(new Filter(AllCalls.Instance, DataSetFilter(startIndex, endIndex))));
return from c in query
where c.NameMapping.Id != 0 && !c.NameMapping.Name.StartsWith("Thread#", StringComparison.Ordinal)
select c;
}
/*
IQueryable<CallTreeNode> GetMergedFunctionData(string condition, int startIndex)
Expression<Func<SingleCall, bool>> DataSetFilter(int startIndex, int endIndex)
{
return c => startIndex <= c.DataSetID && c.DataSetID <= endIndex;
}
internal IList<CallTreeNode> RunSQL(SQLiteQueryProvider queryProvider, string command)
{
IList<CallTreeNode> result = new List<CallTreeNode>();
SQLiteCommand cmd;
using (LockAndCreateCommand(out cmd)) {
cmd.CommandText = @"SELECT
GROUP_CONCAT(id),
nameid,
SUM(timespent),
SUM(callcount),
MAX(id != endid) AS hasChildren,
SUM((datasetid = " + startIndex + @") AND isActiveAtStart) AS activeCallCount
FROM FunctionData
WHERE " + condition + @"
GROUP BY nameid;";
cmd.CommandText = command;
using (SQLiteDataReader reader = cmd.ExecuteReader()) {
while (reader.Read()) {
SQLiteCallTreeNode node = new SQLiteCallTreeNode(reader.GetInt32(1), null, this);
node.selectionStartIndex = startIndex;
SQLiteCallTreeNode node = new SQLiteCallTreeNode(reader.GetInt32(1), null, queryProvider);
node.callCount = reader.GetInt32(3);
node.cpuCyclesSpent = (ulong)reader.GetInt64(2);
node.ids = reader.GetString(0).Split(',').Select(s => int.Parse(s)).ToList();
node.ids.Sort();
object ids = reader.GetValue(0);
if (ids is long) {
node.ids = new List<int> { (int)(long)ids };
} else {
node.ids = reader.GetString(0).Split(',').Select(s => int.Parse(s)).ToList();
node.ids.Sort();
}
node.hasChildren = reader.GetBoolean(4);
node.activeCallCount = reader.GetInt32(5);
// Can not do filtering of root and thread nodes here,
// because retrieval of names needs access to DB
// which is forbidden now (we are inside the lock!)
result.Add(node);
}
}
}
return result.AsQueryable();
return result;
}
*/
LockObject LockAndCreateCommand(out SQLiteCommand cmd)
{

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

@ -13,30 +13,27 @@ using System.Data.SQLite; @@ -13,30 +13,27 @@ using System.Data.SQLite;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using IQToolkit;
namespace ICSharpCode.Profiler.Controller.Data
{
/// <summary>
/// A CallTreeNode based on a ProfilingDataSQLiteProvider.
/// </summary>
class SQLiteCallTreeNode : CallTreeNode
sealed class SQLiteCallTreeNode : CallTreeNode
{
internal int nameId;
internal bool isActiveAtStart;
internal int callCount;
internal ulong cpuCyclesSpent;
CallTreeNode parent;
ProfilingDataSQLiteProvider provider;
SQLiteQueryProvider provider;
internal List<int> ids = new List<int>();
internal bool hasChildren;
internal int activeCallCount;
internal int selectionStartIndex;
/// <summary>
/// Creates a new CallTreeNode.
/// </summary>
public SQLiteCallTreeNode(int nameId, CallTreeNode parent, ProfilingDataSQLiteProvider provider)
public SQLiteCallTreeNode(int nameId, CallTreeNode parent, SQLiteQueryProvider provider)
{
this.nameId = nameId;
this.parent = parent;
@ -110,7 +107,7 @@ namespace ICSharpCode.Profiler.Controller.Data @@ -110,7 +107,7 @@ namespace ICSharpCode.Profiler.Controller.Data
/// </summary>
public override bool IsActiveAtStart {
get {
return isActiveAtStart;
return activeCallCount > 0;
}
}
@ -130,7 +127,6 @@ namespace ICSharpCode.Profiler.Controller.Data @@ -130,7 +127,6 @@ namespace ICSharpCode.Profiler.Controller.Data
foreach (SQLiteCallTreeNode node in nodes) {
mergedNode.ids.AddRange(node.ids);
mergedNode.selectionStartIndex = Math.Min(mergedNode.selectionStartIndex, node.selectionStartIndex);
mergedNode.callCount += node.callCount;
mergedNode.cpuCyclesSpent += node.cpuCyclesSpent;
if (!initialised || mergedNode.nameId == node.nameId)
@ -150,7 +146,7 @@ namespace ICSharpCode.Profiler.Controller.Data @@ -150,7 +146,7 @@ namespace ICSharpCode.Profiler.Controller.Data
if (this.parent != null)
return (new CallTreeNode[] { this.parent }).AsQueryable();
return provider.GetCallers(this);
throw new NotImplementedException();
}
}

18
src/AddIns/Misc/Profiler/Controller/ExtensionMethods.cs

@ -3,6 +3,8 @@ using System.CodeDom.Compiler; @@ -3,6 +3,8 @@ using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
@ -32,11 +34,23 @@ namespace ICSharpCode.Profiler.Controller @@ -32,11 +34,23 @@ namespace ICSharpCode.Profiler.Controller
}
/// <summary>
/// Returns a CallTreeNode with all merged items.
/// Returns a CallTreeNode with all merged items.
/// </summary>
public static CallTreeNode Merge(this IEnumerable<CallTreeNode> items)
{
return items.First().Merge(items);
if (items == null)
throw new ArgumentNullException("items");
Debug.Assert(MethodBase.GetCurrentMethod() == Data.Linq.KnownMembers.Merge);
IQueryable<CallTreeNode> query = items as IQueryable<CallTreeNode>;
if (query != null) {
return query.Provider.Execute<CallTreeNode>(
Expression.Call(Data.Linq.KnownMembers.Merge, query.Expression));
} else {
var itemList = items.ToList();
return itemList.First().Merge(items);
}
}
/// <summary>

Loading…
Cancel
Save