diff --git a/src/AddIns/Misc/Profiler/Controller/Data/Linq/ExpressionSqlWriter.cs b/src/AddIns/Misc/Profiler/Controller/Data/Linq/ExpressionSqlWriter.cs index 31185fdef8..d699a746e3 100644 --- a/src/AddIns/Misc/Profiler/Controller/Data/Linq/ExpressionSqlWriter.cs +++ b/src/AddIns/Misc/Profiler/Controller/Data/Linq/ExpressionSqlWriter.cs @@ -107,7 +107,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq void WriteMemberAccess(MemberExpression me) { if (me.Expression == callTreeNodeParameter) { - if (!nameSet.IsCalls) + if (me.Member.DeclaringType == typeof(SingleCall) && !nameSet.IsCalls) throw new InvalidOperationException("SingleCall references are invalid here"); if (me.Member == SingleCall.IDField) { w.Write("id"); @@ -115,6 +115,10 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq w.Write("datasetid"); } else if (me.Member == SingleCall.ParentIDField) { w.Write("parentid"); + } else if (me.Member == KnownMembers.CallTreeNode_CallCount) { + w.Write(nameSet.CallCount); + } else if (me.Member == KnownMembers.CallTreeNode_CpuCyclesSpent) { + w.Write(nameSet.CpuCyclesSpent); } else { throw new NotSupportedException(me.Member.ToString()); } diff --git a/src/AddIns/Misc/Profiler/Controller/Data/Linq/KnownMembers.cs b/src/AddIns/Misc/Profiler/Controller/Data/Linq/KnownMembers.cs index d2c3a1ea4e..1a951606dd 100644 --- a/src/AddIns/Misc/Profiler/Controller/Data/Linq/KnownMembers.cs +++ b/src/AddIns/Misc/Profiler/Controller/Data/Linq/KnownMembers.cs @@ -15,14 +15,20 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq { static class KnownMembers { - public static readonly MethodInfo ListOfInt_Contains = MethodOf((List list) => list.Contains(0)); public static readonly PropertyInfo CallTreeNode_IsUserCode = PropertyOf((CallTreeNode c) => c.IsUserCode); + public static readonly PropertyInfo CallTreeNode_CpuCyclesSpent = PropertyOf((CallTreeNode c) => c.CpuCyclesSpent); + public static readonly PropertyInfo CallTreeNode_CallCount = PropertyOf((CallTreeNode c) => c.CallCount); public static readonly PropertyInfo CallTreeNode_NameMapping = PropertyOf((CallTreeNode c) => c.NameMapping); public static readonly PropertyInfo CallTreeNode_Children = PropertyOf((CallTreeNode c) => c.Children); public static readonly PropertyInfo NameMapping_ID = PropertyOf((NameMapping n) => n.Id); public static readonly PropertyInfo NameMapping_Name = PropertyOf((NameMapping n) => n.Name); - public static readonly MethodInfo QuerableOfCallTreeNode_Select = MethodOf((IQueryable q) => q.Select(x => x)); - public static readonly MethodInfo QuerableOfCallTreeNode_Where = MethodOf((IQueryable q) => q.Where(x => true)); + + public static readonly MethodInfo ListOfInt_Contains = MethodOf((List list) => list.Contains(0)); + public static readonly MethodInfo QueryableOfCallTreeNode_Select = MethodOf((IQueryable q) => q.Select(x => x)); + public static readonly MethodInfo QueryableOfCallTreeNode_Where = MethodOf((IQueryable q) => q.Where(x => true)); + public static readonly MethodInfo QueryableOfCallTreeNode_Take = MethodOf((IQueryable q) => q.Take(1)); + public static readonly MethodInfo Queryable_OrderBy = MethodOf((IQueryable q) => q.OrderBy(x => x)).GetGenericMethodDefinition(); + public static readonly MethodInfo QueryableOfCallTreeNode_OrderByDesc = MethodOf((IOrderedQueryable q) => q.OrderByDescending(x => default(int))); public static readonly MethodInfo Merge = MethodOf((IQueryable q) => q.Merge()); public static readonly MethodInfo String_StartsWith = MethodOf((string s) => s.StartsWith(s, default(StringComparison))); public static readonly MethodInfo Like = MethodOf(() => LikeImpl("", "")); diff --git a/src/AddIns/Misc/Profiler/Controller/Data/Linq/QueryAst.cs b/src/AddIns/Misc/Profiler/Controller/Data/Linq/QueryAst.cs index 530b7a87f7..d250e08a84 100644 --- a/src/AddIns/Misc/Profiler/Controller/Data/Linq/QueryAst.cs +++ b/src/AddIns/Misc/Profiler/Controller/Data/Linq/QueryAst.cs @@ -6,6 +6,7 @@ // using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; @@ -84,7 +85,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq CallTreeNodeSqlNameSet newNames = new CallTreeNodeSqlNameSet(context, false); b.Insert(0, "SELECT " + SqlAs(oldNames.ID, newNames.ID) + ", " + SqlAs(oldNames.NameID, newNames.NameID) + ", " - + SqlAs(oldNames.TimeSpent, newNames.TimeSpent) + ", " + + SqlAs(oldNames.CpuCyclesSpent, newNames.CpuCyclesSpent) + ", " + SqlAs(oldNames.CallCount, newNames.CallCount) + ", " + SqlAs(oldNames.HasChildren, newNames.HasChildren) + ", " + SqlAs(oldNames.ActiveCallCount, newNames.ActiveCallCount) @@ -143,7 +144,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq b.AppendLine("SELECT " + SqlAs("id", newNames.ID) + ", " + SqlAs("nameid", newNames.NameID) + ", " - + SqlAs("timespent", newNames.TimeSpent) + ", " + + SqlAs("timespent", newNames.CpuCyclesSpent) + ", " // TODO : change to CpuCyclesSpent + SqlAs("callcount", newNames.CallCount) + ", " + SqlAs("(id != endid)", newNames.HasChildren) + ", " + SqlAs("((datasetid = " + context.StartDataSetID + ") AND isActiveAtStart)", newNames.ActiveCallCount)); @@ -186,7 +187,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq b.Insert(0, "SELECT " + SqlAs("GROUP_CONCAT(" + oldNames.ID + ")", newNames.ID) + ", " + SqlAs(oldNames.NameID, newNames.NameID) + ", " - + SqlAs("SUM(" + oldNames.TimeSpent + ")", newNames.TimeSpent) + ", " + + SqlAs("SUM(" + oldNames.CpuCyclesSpent + ")", newNames.CpuCyclesSpent) + ", " + SqlAs("SUM(" + oldNames.CallCount + ")", newNames.CallCount) + ", " + SqlAs("MAX(" + oldNames.HasChildren + ")", newNames.HasChildren) + ", " + SqlAs("SUM(" + oldNames.ActiveCallCount + ")", newNames.ActiveCallCount) @@ -284,4 +285,126 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq b.Append(w.ToString()); } } + + /// + /// Query node that limits the amount of data selected. Produces LIMIT in SQL. + /// + sealed class Limit : QueryNode + { + public int Start { get; private set; } + public int Length { get; private set; } + + public Limit(QueryNode target, int start, int length) + : base(target) + { + this.Start = start; + this.Length = length; + } + + protected override Expression VisitChildren(Func visitor) + { + QueryNode newTarget = (QueryNode)visitor(Target); + if (newTarget == Target) + return this; + else + return new Limit(newTarget, Start, Length); + } + + public override string ToString() + { + return Target + ".Limit(" + Start + "," + Length + ")"; + } + + public override SqlStatementKind BuildSql(StringBuilder b, SqlQueryContext context) + { + SqlStatementKind kind = Target.BuildSql(b, context); + if (kind == SqlStatementKind.SelectLimit) + WrapSqlIntoNestedStatement(b, context); + + b.Append(" LIMIT " + Length); + b.AppendLine(" OFFSET " + Start); + + return SqlStatementKind.SelectLimit; + } + } + + struct SortArgument { + LambdaExpression arg; + bool desc; + + public SortArgument(LambdaExpression arg, bool desc) + { + this.arg = arg; + this.desc = desc; + } + + public bool Descending { + get { return desc; } + } + public LambdaExpression Argument { + get { return arg; } + } + } + + sealed class Sort : QueryNode + { + ReadOnlyCollection arguments; + + public Sort(QueryNode target, LambdaExpression argument, bool desc) + : this(target, new[] { new SortArgument(argument, desc) }) + { + } + + Sort(QueryNode target, IList args) + : base(target) + { + this.arguments = new ReadOnlyCollection(args); + } + + public override SqlStatementKind BuildSql(StringBuilder b, SqlQueryContext context) + { + SqlStatementKind kind = Target.BuildSql(b, context); + if (kind == SqlStatementKind.SelectOrderBy) + WrapSqlIntoNestedStatement(b, context); + + b.Append(" ORDER BY "); + + for (int i = 0; i < arguments.Count; i++) { + StringWriter w = new StringWriter(); + ExpressionSqlWriter writer = new ExpressionSqlWriter(w, context.CurrentNameSet, arguments[i].Argument.Parameters[0]); + writer.Write(arguments[i].Argument.Body); + + if (i == 0) + b.Append(w.ToString()); + else + b.Append(", " + w.ToString()); + + if (arguments[i].Descending) + b.Append(" DESC"); + else + b.Append(" ASC"); + } + + return SqlStatementKind.SelectOrderBy; + } + + protected override Expression VisitChildren(Func visitor) + { + QueryNode newTarget = (QueryNode)visitor(Target); + if (newTarget == Target) + return this; + else + return new Sort(newTarget, arguments); + } + + public override string ToString() + { + StringBuilder builder = new StringBuilder(); + + foreach (var item in arguments) + builder.Append(item.Argument + " " + (item.Descending ? "DESC" : "ASC") + ","); + + return Target + ".Sort( {" + builder.ToString() + "} )"; + } + } } diff --git a/src/AddIns/Misc/Profiler/Controller/Data/Linq/SQLiteQueryProvider.cs b/src/AddIns/Misc/Profiler/Controller/Data/Linq/SQLiteQueryProvider.cs index 22e9b2a03c..27247a7279 100644 --- a/src/AddIns/Misc/Profiler/Controller/Data/Linq/SQLiteQueryProvider.cs +++ b/src/AddIns/Misc/Profiler/Controller/Data/Linq/SQLiteQueryProvider.cs @@ -206,7 +206,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq protected override Expression VisitMethodCall(MethodCallExpression node) { - if (node.Method == KnownMembers.QuerableOfCallTreeNode_Select && node.Arguments[1].NodeType == ExpressionType.Quote) { + if (node.Method == KnownMembers.QueryableOfCallTreeNode_Select && node.Arguments[1].NodeType == ExpressionType.Quote) { // CallTreeNode[].Select: // selects are not supported by query evaluator, but we will detect and remove // degenerate selects (.Select(x => x)). @@ -216,7 +216,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq if (lambda.Parameters.Count == 1 && lambda.Body == lambda.Parameters[0]) return Visit(node.Arguments[0]); } - } else if (node.Method == KnownMembers.QuerableOfCallTreeNode_Where && node.Arguments[1].NodeType == ExpressionType.Quote) { + } else if (node.Method == KnownMembers.QueryableOfCallTreeNode_Where && node.Arguments[1].NodeType == ExpressionType.Quote) { // CallTreeNode[].Where: // if the target is a QueryNode and the condition can be safely imported, convert the Where call to Filter UnaryExpression quote = (UnaryExpression)node.Arguments[1]; @@ -233,6 +233,38 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq } } } + } else if (node.Method == KnownMembers.QueryableOfCallTreeNode_Take && node.Arguments[1].NodeType == ExpressionType.Constant) { + ConstantExpression ce = (ConstantExpression)node.Arguments[1]; + if (ce.Type == typeof(int)) { + QueryNode target = Visit(node.Arguments[0]) as QueryNode; + if (target != null) { + return new Limit(target, 0, (int)ce.Value); + } + } + } else if (node.Method.IsGenericMethod && node.Method.GetGenericMethodDefinition() == KnownMembers.Queryable_OrderBy && + node.Method.GetGenericArguments()[0] == typeof(CallTreeNode) && + node.Arguments[1].NodeType == ExpressionType.Quote) { + UnaryExpression quote = (UnaryExpression)node.Arguments[1]; + if (quote.Operand.NodeType == ExpressionType.Lambda) { + LambdaExpression lambda = (LambdaExpression)quote.Operand; + if (lambda.Parameters.Count == 1) { + QueryNode target = Visit(node.Arguments[0]) as QueryNode; + if (target != null) { + SafeExpressionImporter importer = new SafeExpressionImporter(lambda.Parameters[0]); + var imported = importer.Import(lambda.Body); + if (imported != null) + return new Sort(target, Expression.Lambda(imported, lambda.Parameters[0]), false); + } + } + } + } else if (node.Method == KnownMembers.QueryableOfCallTreeNode_OrderByDesc && node.Arguments[1].NodeType == ExpressionType.Quote) { + ConstantExpression ce = (ConstantExpression)node.Arguments[1]; + if (ce.Type == typeof(int)) { + QueryNode target = Visit(node.Arguments[0]) as QueryNode; + if (target != null) { + return new Limit(target, 0, (int)ce.Value); + } + } } return base.VisitMethodCall(node); } @@ -304,6 +336,12 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq KnownMembers.NameMapping_ID), Expression.Constant(0)); } + + if (me.Member == KnownMembers.CallTreeNode_CallCount) + return me; + + if (me.Member == KnownMembers.CallTreeNode_CpuCyclesSpent) + return me; } else if (IsNameMappingOnParameter(me.Expression)) { if (me.Member == KnownMembers.NameMapping_ID) return me; diff --git a/src/AddIns/Misc/Profiler/Controller/Data/Linq/SqlQueryContext.cs b/src/AddIns/Misc/Profiler/Controller/Data/Linq/SqlQueryContext.cs index dd8b9e0266..9d3b6f430a 100644 --- a/src/AddIns/Misc/Profiler/Controller/Data/Linq/SqlQueryContext.cs +++ b/src/AddIns/Misc/Profiler/Controller/Data/Linq/SqlQueryContext.cs @@ -33,7 +33,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq { public readonly string ID; public readonly string NameID = "nameid"; - public readonly string TimeSpent; + public readonly string CpuCyclesSpent; public readonly string CallCount; public readonly string HasChildren; public readonly string ActiveCallCount; @@ -50,11 +50,11 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq string prefix = c.GenerateUniqueVariableName(); if (isCalls) { ID = "id"; - TimeSpent = "timespent"; + CpuCyclesSpent = "cpucyclesspent"; CallCount = "callcount"; } else { ID = prefix + "ID"; - TimeSpent = prefix + "TimeSpent"; + CpuCyclesSpent = prefix + "CpuCyclesSpent"; CallCount = prefix + "CallCount"; } HasChildren = prefix + "HasChildren"; diff --git a/src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataSQLiteWriter.cs b/src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataSQLiteWriter.cs index 051f11ed92..64446fb04c 100644 --- a/src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataSQLiteWriter.cs +++ b/src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataSQLiteWriter.cs @@ -185,6 +185,7 @@ namespace ICSharpCode.Profiler.Controller.Data void InitializeTables() { //NameMapping { Id, ReturnType, Name, Parameters } + // TODO : update db schema: change FunctionData.TimeSpent to cpucyclesspent //FunctionData { DataSetId, Id, ParentId, NameId, TimeSpent, CallCount } //DataSets { Id, CPUUsage, RootId } // diff --git a/src/AddIns/Misc/Profiler/Frontend/AddIn/Src/Views/ProfilerView.xaml b/src/AddIns/Misc/Profiler/Frontend/AddIn/Src/Views/ProfilerView.xaml index 854c5fe53c..cbf33d2f84 100644 --- a/src/AddIns/Misc/Profiler/Frontend/AddIn/Src/Views/ProfilerView.xaml +++ b/src/AddIns/Misc/Profiler/Frontend/AddIn/Src/Views/ProfilerView.xaml @@ -41,7 +41,7 @@ - +