diff --git a/src/AddIns/Misc/Profiler/Controller/Data/IProfilingDataWriter.cs b/src/AddIns/Misc/Profiler/Controller/Data/IProfilingDataWriter.cs index a8c3446eb3..14f214327c 100644 --- a/src/AddIns/Misc/Profiler/Controller/Data/IProfilingDataWriter.cs +++ b/src/AddIns/Misc/Profiler/Controller/Data/IProfilingDataWriter.cs @@ -31,6 +31,8 @@ namespace ICSharpCode.Profiler.Controller.Data void WritePerformanceCounterData(IEnumerable counters); + void WriteEventData(IEnumerable events); + /// /// Closes and disposes the underlying data structure. /// @@ -41,5 +43,10 @@ namespace ICSharpCode.Profiler.Controller.Data /// The processor frequency is measured in MHz. /// int ProcessorFrequency { get; set; } + + /// + /// Gets the number of datasets that have been written by this IProfilingDataWriter. + /// + int DataSetCount { get; } } } diff --git a/src/AddIns/Misc/Profiler/Controller/Data/PerformanceCounterDescriptor.cs b/src/AddIns/Misc/Profiler/Controller/Data/PerformanceCounterDescriptor.cs index 473ad3ffe7..2c06e9e267 100644 --- a/src/AddIns/Misc/Profiler/Controller/Data/PerformanceCounterDescriptor.cs +++ b/src/AddIns/Misc/Profiler/Controller/Data/PerformanceCounterDescriptor.cs @@ -23,12 +23,13 @@ namespace ICSharpCode.Profiler.Controller.Data public float? MinValue { get; private set; } public float? MaxValue { get; private set; } public string Unit { get; private set; } + public string Format { get; private set; } float defaultValue; PerformanceCounter counter; public PerformanceCounterDescriptor(string category, string name, string instance, string computer, - float defaultValue, float? minValue, float? maxValue, string unit) + float defaultValue, float? minValue, float? maxValue, string unit, string format) { Category = category; Name = name; @@ -39,10 +40,11 @@ namespace ICSharpCode.Profiler.Controller.Data MinValue = minValue; MaxValue = maxValue; Unit = unit; + Format = format; } - public PerformanceCounterDescriptor(string name, float? minValue, float? maxValue, string unit) - : this(null, name, null, null, 0, minValue, maxValue, unit) + public PerformanceCounterDescriptor(string name, float? minValue, float? maxValue, string unit, string format) + : this(null, name, null, null, 0, minValue, maxValue, unit, format) { } @@ -52,8 +54,8 @@ namespace ICSharpCode.Profiler.Controller.Data string[] instances = cat.GetInstanceNames(); foreach (string instance in instances) { - using (PerformanceCounter cnt = new PerformanceCounter("Process", "ID Process", instance, true)) { - int val = (int)cnt.RawValue; + using (PerformanceCounter procIdCounter = new PerformanceCounter("Process", "ID Process", instance, true)) { + int val = (int)procIdCounter.RawValue; if (val == pid) return instance; } @@ -75,7 +77,9 @@ namespace ICSharpCode.Profiler.Controller.Data try { this.Values.Add(counter.NextValue()); } catch (Exception e) { + #if DEBUG Console.WriteLine(e.ToString()); + #endif this.Values.Add(defaultValue); } } @@ -85,4 +89,20 @@ namespace ICSharpCode.Profiler.Controller.Data return Name; } } + + public enum EventType : int + { + Exception = 0, + Console = 1, + WindowsForms = 2, + WindowsPresentationFoundation = 3 + } + + public class EventDataEntry + { + public int DataSetId { get; set; } + public EventType Type { get; set; } + public int NameId { get; set; } + public string Data { get; set; } + } } diff --git a/src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataProvider.cs b/src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataProvider.cs index 3d6d016c28..5e5e9b093b 100644 --- a/src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataProvider.cs +++ b/src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataProvider.cs @@ -80,5 +80,7 @@ namespace ICSharpCode.Profiler.Controller.Data public abstract PerformanceCounterDescriptor[] GetPerformanceCounters(); public abstract float[] GetPerformanceCounterValues(int index); + + public abstract EventDataEntry[] GetEventDataEntries(int index); } } diff --git a/src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataSQLiteProvider.cs b/src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataSQLiteProvider.cs index dd4e0425b1..52a552c196 100644 --- a/src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataSQLiteProvider.cs +++ b/src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataSQLiteProvider.cs @@ -356,7 +356,7 @@ namespace ICSharpCode.Profiler.Controller.Data { SQLiteCommand cmd; using (LockAndCreateCommand(out cmd)) { - cmd.CommandText = "SELECT name, minvalue, maxvalue, unit " + + cmd.CommandText = "SELECT name, minvalue, maxvalue, unit, format " + "FROM PerformanceCounter " + "ORDER BY id ASC;"; @@ -370,7 +370,8 @@ namespace ICSharpCode.Profiler.Controller.Data reader.GetString(0), reader.IsDBNull(1) ? null : new Nullable(reader.GetFloat(1)), reader.IsDBNull(2) ? null : new Nullable(reader.GetFloat(2)), - reader.GetString(3) + reader.GetString(3), + reader.GetString(4) ) ); } @@ -399,6 +400,25 @@ namespace ICSharpCode.Profiler.Controller.Data } } + public override EventDataEntry[] GetEventDataEntries(int index) + { + SQLiteCommand cmd; + using (LockAndCreateCommand(out cmd)) { + cmd.CommandText = "SELECT eventtype, nameid, data " + + "FROM EventData " + + "WHERE datasetid = " + index; + + List list = new List(); + + var reader = cmd.ExecuteReader(); + + while (reader.Read()) + list.Add(new EventDataEntry() { Data = reader.GetString(2), DataSetId = index, NameId = reader.GetInt32(1), Type = (EventType)reader.GetInt32(0) }); + + return list.ToArray(); + } + } + Expression> DataSetFilter(int startIndex, int endIndex) { return c => startIndex <= c.DataSetID && c.DataSetID <= endIndex; diff --git a/src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataSQLiteWriter.cs b/src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataSQLiteWriter.cs index 8d6e3a9803..ef5aed0259 100644 --- a/src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataSQLiteWriter.cs +++ b/src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataSQLiteWriter.cs @@ -61,6 +61,9 @@ namespace ICSharpCode.Profiler.Controller.Data /// public void Close() { + if (isDisposed) + return; + using (SQLiteCommand cmd = this.connection.CreateCommand()) { // create index at the end (after inserting data), this is faster cmd.CommandText = CallsAndFunctionsIndexDefs; @@ -165,19 +168,27 @@ namespace ICSharpCode.Profiler.Controller.Data hasChildren INTEGER NOT NULL ); - CREATE TABLE PerformanceCounter( - id INTEGER NOT NULL PRIMARY KEY, - name TEXT NOT NULL, - minvalue REAL NULL, - maxvalue REAL NULL, - unit TEXT NOT NULL - ); - - CREATE TABLE CounterData( - datasetid INTEGER NOT NULL, - counterid INTEGER NOT NULL, - value REAL NOT NULL - ); + CREATE TABLE PerformanceCounter( + id INTEGER NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + minvalue REAL NULL, + maxvalue REAL NULL, + format TEXT NOT NULL, + unit TEXT NOT NULL + ); + + CREATE TABLE CounterData( + datasetid INTEGER NOT NULL, + counterid INTEGER NOT NULL, + value REAL NOT NULL + ); + + CREATE TABLE EventData( + datasetid INTEGER NOT NULL, + eventtype INTEGER NOT NULL, + nameid INTEGER NOT NULL, + data TEXT NULL + ); "; internal const string CallsAndFunctionsIndexDefs = @@ -239,19 +250,22 @@ namespace ICSharpCode.Profiler.Controller.Data InsertCalls(cmd, child, thisID, dataParams); } + long cpuCycles = node.CpuCyclesSpent; + long cpuCyclesSelf = node.CpuCyclesSpentSelf; + // we sometimes saw invalid data with the 0x0080000000000000L bit set - if (node.CpuCyclesSpent > 0x0007ffffffffffffL || node.CpuCyclesSpent < 0) { + if (cpuCycles > 0x0007ffffffffffffL || cpuCycles < 0) { throw new InvalidOperationException("Too large CpuCyclesSpent - there's something wrong in the data"); } - if (node.NameMapping.Id != 0 && (node.CpuCyclesSpentSelf > node.CpuCyclesSpent || node.CpuCyclesSpentSelf < 0)) { - throw new InvalidOperationException("Too large/small CpuCyclesSpentSelf (" + node.CpuCyclesSpentSelf + ") - there's something wrong in the data"); + if (node.NameMapping.Id != 0 && (cpuCyclesSelf > cpuCycles || cpuCyclesSelf < 0)) { + throw new InvalidOperationException("Too large/small CpuCyclesSpentSelf (" + cpuCyclesSelf + ") - there's something wrong in the data"); } dataParams.callCount.Value = node.RawCallCount; dataParams.isActiveAtStart.Value = node.IsActiveAtStart; - dataParams.cpuCyclesSpent.Value = node.CpuCyclesSpent; - dataParams.cpuCyclesSpentSelf.Value = node.CpuCyclesSpentSelf; + dataParams.cpuCyclesSpent.Value = cpuCycles; + dataParams.cpuCyclesSpentSelf.Value = cpuCyclesSelf; dataParams.functionInfoId.Value = thisID; dataParams.nameId.Value = node.NameMapping.Id; @@ -302,6 +316,7 @@ namespace ICSharpCode.Profiler.Controller.Data isDisposed = true; } + /// public void WritePerformanceCounterData(IEnumerable counters) { using (SQLiteTransaction trans = this.connection.BeginTransaction()) { @@ -316,8 +331,8 @@ namespace ICSharpCode.Profiler.Controller.Data SQLiteParameter unitParam = new SQLiteParameter("unit"); cmd.CommandText = - "INSERT OR IGNORE INTO PerformanceCounter(id, name, minvalue, maxvalue, unit)" + - "VALUES(@id,@name,@min,@max,@unit);" + + "INSERT OR IGNORE INTO PerformanceCounter(id, name, minvalue, maxvalue, unit)" + // should I split these queries + "VALUES(@id,@name,@min,@max,@unit);" + // for better performance? "INSERT INTO CounterData(datasetid, counterid, value)" + "VALUES(@dataset,@id,@value);"; @@ -344,5 +359,38 @@ namespace ICSharpCode.Profiler.Controller.Data trans.Commit(); } } + + /// + public void WriteEventData(IEnumerable events) + { + using (SQLiteTransaction trans = this.connection.BeginTransaction()) { + using (SQLiteCommand cmd = this.connection.CreateCommand()) { + SQLiteParameter dataSetParam = new SQLiteParameter("datasetid"); + SQLiteParameter eventTypeParam = new SQLiteParameter("eventtype"); + SQLiteParameter nameIdParam = new SQLiteParameter("nameid"); + SQLiteParameter dataParam = new SQLiteParameter("data"); + + cmd.CommandText = + "INSERT INTO EventData(datasetid,eventtype,nameid,data) " + + "VALUES(@datasetid,@eventtype,@nameid,@data);"; + + cmd.Parameters.AddRange(new SQLiteParameter[] { dataSetParam, eventTypeParam, nameIdParam, dataParam }); + + foreach (EventDataEntry entry in events) { + dataSetParam.Value = entry.DataSetId; + eventTypeParam.Value = (int)entry.Type; + nameIdParam.Value = entry.NameId; + dataParam.Value = entry.Data; + cmd.ExecuteNonQuery(); + } + } + trans.Commit(); + } + } + + /// + public int DataSetCount { + get { return this.dataSetCount; } + } } } \ No newline at end of file diff --git a/src/AddIns/Misc/Profiler/Controller/Data/TempFileDatabase.cs b/src/AddIns/Misc/Profiler/Controller/Data/TempFileDatabase.cs index e4a60fda78..a8bfbea6cc 100644 --- a/src/AddIns/Misc/Profiler/Controller/Data/TempFileDatabase.cs +++ b/src/AddIns/Misc/Profiler/Controller/Data/TempFileDatabase.cs @@ -43,6 +43,12 @@ namespace ICSharpCode.Profiler.Controller.Data get { return counters.AsReadOnly(); } } + List events = new List(); + + public ReadOnlyCollection Events { + get { return events.AsReadOnly(); } + } + struct StreamInfo { public TargetProcessPointer NativeStartPosition { get; set; } @@ -143,6 +149,15 @@ namespace ICSharpCode.Profiler.Controller.Data { this.database.counters.AddRange(counters); } + + public void WriteEventData(IEnumerable events) + { + this.database.events.AddRange(events); + } + + public int DataSetCount { + get { return this.database.DataSetCount; } + } } #endregion @@ -229,6 +244,7 @@ namespace ICSharpCode.Profiler.Controller.Data writer.ProcessorFrequency = this.processorFrequency; writer.WriteMappings(this.nameMappings.Values); writer.WritePerformanceCounterData(this.counters); + writer.WriteEventData(this.events); for (int i = 0; i < this.DataSetCount; i++) { using (UnmanagedProfilingDataSet dataSet = this.LoadDataSet(i)) diff --git a/src/AddIns/Misc/Profiler/Controller/Data/UnitTestRootCallTreeNode.cs b/src/AddIns/Misc/Profiler/Controller/Data/UnitTestRootCallTreeNode.cs index 15dd603b40..d2d227bdae 100644 --- a/src/AddIns/Misc/Profiler/Controller/Data/UnitTestRootCallTreeNode.cs +++ b/src/AddIns/Misc/Profiler/Controller/Data/UnitTestRootCallTreeNode.cs @@ -15,14 +15,15 @@ namespace ICSharpCode.Profiler.Controller.Data /// public class UnitTestRootCallTreeNode : CallTreeNode { - List unitTests; + List unitTests = null; /// /// Creates a new UnitTestRootCallTreeNode. /// public UnitTestRootCallTreeNode(IEnumerable unitTests) { - this.unitTests = new List(unitTests); + if (unitTests != null) + this.unitTests = new List(unitTests); } /// @@ -42,7 +43,7 @@ namespace ICSharpCode.Profiler.Controller.Data /// public override bool IsActiveAtStart { get { - return this.unitTests.Any(test => test.IsActiveAtStart); + return (this.unitTests == null) ? false : this.unitTests.Any(test => test.IsActiveAtStart); } } @@ -77,14 +78,19 @@ namespace ICSharpCode.Profiler.Controller.Data /// public override int GetHashCode() { - return this.unitTests.Aggregate(0, (sum, item) => sum ^= item.GetHashCode()); + return (this.unitTests == null) ? 0 : this.unitTests.Aggregate(0, (sum, item) => sum ^= item.GetHashCode()); } /// public override bool Equals(CallTreeNode other) { UnitTestRootCallTreeNode node = other as UnitTestRootCallTreeNode; - return node != null && node.unitTests.SequenceEqual(unitTests); + + if (node != null && unitTests != null) { + return node.unitTests.SequenceEqual(unitTests); + } + + return false; } /// @@ -97,7 +103,7 @@ namespace ICSharpCode.Profiler.Controller.Data /// public override IQueryable Children { get { - return unitTests.AsQueryable(); + return ((unitTests == null) ? Enumerable.Empty() : unitTests).AsQueryable(); } } diff --git a/src/AddIns/Misc/Profiler/Controller/Data/UnitTestWriter.cs b/src/AddIns/Misc/Profiler/Controller/Data/UnitTestWriter.cs index 665f803f1e..79e2da7363 100644 --- a/src/AddIns/Misc/Profiler/Controller/Data/UnitTestWriter.cs +++ b/src/AddIns/Misc/Profiler/Controller/Data/UnitTestWriter.cs @@ -69,6 +69,10 @@ namespace ICSharpCode.Profiler.Controller.Data this.targetWriter.WriteDataSet( new UnitTestDataSet(new UnitTestRootCallTreeNode(list), dataSet.IsFirst) ); + } else { + this.targetWriter.WriteDataSet( + new UnitTestDataSet(new UnitTestRootCallTreeNode(null), dataSet.IsFirst) + ); } } @@ -105,5 +109,14 @@ namespace ICSharpCode.Profiler.Controller.Data { this.targetWriter.WritePerformanceCounterData(counters); } + + public void WriteEventData(IEnumerable events) + { + this.targetWriter.WriteEventData(events); + } + + public int DataSetCount { + get { return this.targetWriter.DataSetCount; } + } } } diff --git a/src/AddIns/Misc/Profiler/Controller/Data/UnmanagedCallTreeNode.cs b/src/AddIns/Misc/Profiler/Controller/Data/UnmanagedCallTreeNode.cs index 72333c801e..aae7acb523 100644 --- a/src/AddIns/Misc/Profiler/Controller/Data/UnmanagedCallTreeNode.cs +++ b/src/AddIns/Misc/Profiler/Controller/Data/UnmanagedCallTreeNode.cs @@ -82,6 +82,24 @@ namespace ICSharpCode.Profiler.Controller.Data return (long)(this.data->TimeSpent & CpuCycleMask); } } + + public override long CpuCyclesSpentSelf { + get { + dataSet.VerifyAccess(); + + long result = (long)(this.data->TimeSpent & CpuCycleMask); + + TargetProcessPointer32* childrenPtr = FunctionInfo.GetChildren32(data); + for (int i = 0; i <= data->LastChildIndex; i++) + { + FunctionInfo* child = dataSet.GetFunctionInfo(childrenPtr[i]); + if (child != null) + result -= (long)(child->TimeSpent & CpuCycleMask); + } + + return result; + } + } public override CallTreeNode Parent { get { diff --git a/src/AddIns/Misc/Profiler/Controller/Profiler.Controller.csproj b/src/AddIns/Misc/Profiler/Controller/Profiler.Controller.csproj index d17c49f2e5..58537988a6 100644 --- a/src/AddIns/Misc/Profiler/Controller/Profiler.Controller.csproj +++ b/src/AddIns/Misc/Profiler/Controller/Profiler.Controller.csproj @@ -89,7 +89,6 @@ ..\..\..\..\Libraries\SQLite\System.Data.SQLite.dll - True diff --git a/src/AddIns/Misc/Profiler/Controller/Profiler.cs b/src/AddIns/Misc/Profiler/Controller/Profiler.cs index 34d7357546..0eb800ac34 100644 --- a/src/AddIns/Misc/Profiler/Controller/Profiler.cs +++ b/src/AddIns/Misc/Profiler/Controller/Profiler.cs @@ -619,7 +619,6 @@ namespace ICSharpCode.Profiler.Controller isRunning = false; this.dataWriter.WritePerformanceCounterData(performanceCounters); - this.dataWriter.Close(); OnSessionEnded(EventArgs.Empty); @@ -703,10 +702,22 @@ namespace ICSharpCode.Profiler.Controller string name = parts[3] + ((string.IsNullOrEmpty(parts[4])) ? "" : " - " + parts[4]); lock (this.dataWriter) { - this.dataWriter.WriteMappings(new NameMapping[] {new NameMapping(id, null, name, null)}); + this.dataWriter.WriteMappings(new[] {new NameMapping(id, null, name, null)}); } return true; + } else if (readString.StartsWith("event ", StringComparison.Ordinal)) { + string[] parts = readString.Split(' '); + // event + if (parts.Length != 4) + return false; + int type = int.Parse(parts[1], CultureInfo.InvariantCulture); + int name = int.Parse(parts[2], CultureInfo.InvariantCulture); + string data = parts[3]; + + lock (this.dataWriter) { + this.dataWriter.WriteEventData(new[] { new EventDataEntry() { DataSetId = this.dataWriter.DataSetCount, NameId = name, Type = (EventType)type, Data = data } }); + } } else { if (readString.StartsWith("error-", StringComparison.Ordinal)) { string[] parts = readString.Split('-'); diff --git a/src/AddIns/Misc/Profiler/Controller/ProfilerOptions.cs b/src/AddIns/Misc/Profiler/Controller/ProfilerOptions.cs index 649adfb67d..73c3389878 100644 --- a/src/AddIns/Misc/Profiler/Controller/ProfilerOptions.cs +++ b/src/AddIns/Misc/Profiler/Controller/ProfilerOptions.cs @@ -25,8 +25,8 @@ namespace ICSharpCode.Profiler.Controller public const int DefaultSharedMemorySize = 64 * 1024 * 1024; // 64 mb public static readonly PerformanceCounterDescriptor[] DefaultCounters = new[] { - new PerformanceCounterDescriptor("Process", "% Processor Time", "_Total", ".", 0, 0, 100, "%"), - new PerformanceCounterDescriptor("Process", "IO Data Bytes/sec", "_Total", ".", 0, null, null, "bytes/sec") + new PerformanceCounterDescriptor("Process", "% Processor Time", "_Total", ".", 0, 0, 100, "%", "0.00"), + new PerformanceCounterDescriptor("Process", "IO Data Bytes/sec", "_Total", ".", 0, null, null, "bytes/sec", "#,##0") }; bool enableDC;