Browse Source

- implemented PerformanceCounterDescriptor

- added ExtendedTimeLineControl
- updated SDPS to 1.2

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5049 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
Siegfried Pammer 16 years ago
parent
commit
3bc647a0f3
  1. 5
      src/AddIns/Misc/Profiler/Controller/Data/IProfilingDataSet.cs
  2. 2
      src/AddIns/Misc/Profiler/Controller/Data/IProfilingDataWriter.cs
  3. 82
      src/AddIns/Misc/Profiler/Controller/Data/PerformanceCounterDescriptor.cs
  4. 5
      src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataProvider.cs
  5. 85
      src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataSQLiteProvider.cs
  6. 81
      src/AddIns/Misc/Profiler/Controller/Data/ProfilingDataSQLiteWriter.cs
  7. 27
      src/AddIns/Misc/Profiler/Controller/Data/TempFileDatabase.cs
  8. 12
      src/AddIns/Misc/Profiler/Controller/Data/UnitTestWriter.cs
  9. 12
      src/AddIns/Misc/Profiler/Controller/Data/UnmanagedProfilingDataSet.cs
  10. 46
      src/AddIns/Misc/Profiler/Controller/Profiler.cs
  11. 22
      src/AddIns/Misc/Profiler/Controller/ProfilerOptions.cs
  12. 50
      src/AddIns/Misc/Profiler/Frontend/AddIn/Src/Dialogs/ProfilerControlWindow.xaml
  13. 6
      src/AddIns/Misc/Profiler/Frontend/AddIn/Src/OptionPanels/OptionWrapper.cs
  14. 28
      src/AddIns/Misc/Profiler/Frontend/AddIn/Src/ProfilerRunner.cs
  15. 20
      src/AddIns/Misc/Profiler/Frontend/AddIn/Src/Views/ProfilerView.xaml
  16. 21
      src/AddIns/Misc/Profiler/Frontend/AddIn/Src/Views/ProfilerView.xaml.cs
  17. 37
      src/AddIns/Misc/Profiler/Frontend/Controls/ExtendedTimeLineControl.xaml
  18. 70
      src/AddIns/Misc/Profiler/Frontend/Controls/ExtendedTimeLineControl.xaml.cs
  19. 14
      src/AddIns/Misc/Profiler/Frontend/Controls/QueryView.xaml
  20. 10
      src/AddIns/Misc/Profiler/Frontend/Controls/QueryView.xaml.cs
  21. 209
      src/AddIns/Misc/Profiler/Frontend/Controls/TimeLineControl.cs
  22. 1
      src/AddIns/Misc/Profiler/Tests/Profiler.Tests/Controller/Data/CallTreeNodeStub.cs
  23. 9
      src/AddIns/Misc/Profiler/Tests/Profiler.Tests/Controller/Data/LinqTests.cs

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

@ -22,11 +22,6 @@ namespace ICSharpCode.Profiler.Controller.Data
/// </remarks> /// </remarks>
public interface IProfilingDataSet public interface IProfilingDataSet
{ {
/// <summary>
/// Gets the percent of CPU power used by the profilee at a certain point.
/// </summary>
double CpuUsage { get; }
/// <summary> /// <summary>
/// Gets whether this dataset is the first dataset of a profiling run. /// Gets whether this dataset is the first dataset of a profiling run.
/// </summary> /// </summary>

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

@ -29,6 +29,8 @@ namespace ICSharpCode.Profiler.Controller.Data
/// </summary> /// </summary>
void WriteMappings(IEnumerable<NameMapping> mappings); void WriteMappings(IEnumerable<NameMapping> mappings);
void WritePerformanceCounterData(IEnumerable<PerformanceCounterDescriptor> counters);
/// <summary> /// <summary>
/// Closes and disposes the underlying data structure. /// Closes and disposes the underlying data structure.
/// </summary> /// </summary>

82
src/AddIns/Misc/Profiler/Controller/Data/PerformanceCounterDescriptor.cs

@ -6,13 +6,83 @@
// </file> // </file>
using System; using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace ICSharpCode.Profiler.Controller.Data namespace ICSharpCode.Profiler.Controller.Data
{ {
// public class PerformanceCounterDescriptor public class PerformanceCounterDescriptor
// { {
// public PerformanceCounterDescriptor() public string Category { get; private set; }
// { public string Name { get; private set; }
// } public string Instance { get; private set; }
// } public string Computer { get; private set; }
public IList<float> Values { get; private set; }
public float? MinValue { get; private set; }
public float? MaxValue { get; private set; }
public string Unit { 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)
{
Category = category;
Name = name;
Instance = instance;
Computer = computer;
Values = new List<float>();
this.defaultValue = defaultValue;
MinValue = minValue;
MaxValue = maxValue;
Unit = unit;
}
public PerformanceCounterDescriptor(string name, float? minValue, float? maxValue, string unit)
: this(null, name, null, null, 0, minValue, maxValue, unit)
{
}
public static string GetProcessInstanceName(int pid)
{
PerformanceCounterCategory cat = new PerformanceCounterCategory("Process");
string[] instances = cat.GetInstanceNames();
foreach (string instance in instances) {
using (PerformanceCounter cnt = new PerformanceCounter("Process", "ID Process", instance, true)) {
int val = (int)cnt.RawValue;
if (val == pid)
return instance;
}
}
return null;
}
public void Reset()
{
this.Values.Clear();
}
public void Collect(string instanceName)
{
if (counter == null && Instance != null)
counter = new PerformanceCounter(Category, Name, instanceName ?? Instance, Computer);
try {
this.Values.Add(counter.NextValue());
} catch (Exception e) {
Console.WriteLine(e.ToString());
this.Values.Add(defaultValue);
}
}
public override string ToString()
{
return Name;
}
}
} }

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

@ -6,6 +6,7 @@
// </file> // </file>
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
@ -75,5 +76,9 @@ namespace ICSharpCode.Profiler.Controller.Data
{ {
return GetAllCalls(startIndex, endIndex).Where(c => !c.IsThread).MergeByName(); return GetAllCalls(startIndex, endIndex).Where(c => !c.IsThread).MergeByName();
} }
public abstract PerformanceCounterDescriptor[] GetPerformanceCounters();
public abstract float[] GetPerformanceCounterValues(int index);
} }
} }

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

@ -56,6 +56,8 @@ namespace ICSharpCode.Profiler.Controller.Data
} }
} }
const string currentVersion = "1.2";
void CheckFileVersion(bool allowUpgrade) void CheckFileVersion(bool allowUpgrade)
{ {
string version = GetProperty("version"); string version = GetProperty("version");
@ -63,7 +65,9 @@ namespace ICSharpCode.Profiler.Controller.Data
if (version == "1.0" && allowUpgrade) { if (version == "1.0" && allowUpgrade) {
try { try {
using (SQLiteCommand cmd = connection.CreateCommand()) { using (SQLiteCommand cmd = connection.CreateCommand()) {
cmd.CommandText = "ALTER TABLE DataSets ADD COLUMN isfirst INTEGER NOT NULL DEFAULT(0);"; cmd.CommandText = "DROP TABLE CounterData;" +
"DROP TABLE PerformanceCounter;" +
"ALTER TABLE DataSets ADD COLUMN isfirst INTEGER NOT NULL DEFAULT(0);";
cmd.ExecuteNonQuery(); cmd.ExecuteNonQuery();
} }
} catch (SQLiteException) { } catch (SQLiteException) {
@ -73,7 +77,7 @@ namespace ICSharpCode.Profiler.Controller.Data
try { try {
using (SQLiteCommand cmd = connection.CreateCommand()) { using (SQLiteCommand cmd = connection.CreateCommand()) {
cmd.CommandText = ProfilingDataSQLiteWriter.CallsAndFunctionsTableDefs + @" cmd.CommandText = ProfilingDataSQLiteWriter.CallsAndFunctionsTableDefs + @"
INSERT OR REPLACE INTO Properties(name, value) VALUES('version', '1.1'); INSERT OR REPLACE INTO Properties(name, value) VALUES('version', '" + currentVersion + @"');
INSERT INTO Calls INSERT INTO Calls
SELECT f1.id, f1.endid, f1.parentid, f1.nameid, f1.timespent, SELECT f1.id, f1.endid, f1.parentid, f1.nameid, f1.timespent,
@ -88,8 +92,16 @@ namespace ICSharpCode.Profiler.Controller.Data
JOIN FunctionData f JOIN FunctionData f
ON c.id = f.id ON c.id = f.id
GROUP BY c.nameid, f.datasetid; GROUP BY c.nameid, f.datasetid;
INSERT INTO PerformanceCounters(id, name, minvalue, maxvalue, unit)
VALUES(0, '% Processor Time', 0, 100, '%');
INSERT INTO CounterData
SELECT id, 0, cpuusage
FROM DataSets;
" "
+ "DELETE FROM FunctionData;" // I would like to do DROP TABLE, but that causes locking errors + "DELETE FROM FunctionData;" +
"DROP TABLE FunctionData;" // I would like to do DROP TABLE, but that causes locking errors
+ ProfilingDataSQLiteWriter.CallsAndFunctionsIndexDefs; + ProfilingDataSQLiteWriter.CallsAndFunctionsIndexDefs;
cmd.ExecuteNonQuery(); cmd.ExecuteNonQuery();
} }
@ -99,7 +111,7 @@ namespace ICSharpCode.Profiler.Controller.Data
throw; throw;
} }
} }
version = "1.1"; // new version that was upgraded to version = currentVersion; // new version that was upgraded to
try { try {
// VACUUM must be run outside the transaction // VACUUM must be run outside the transaction
using (SQLiteCommand cmd = connection.CreateCommand()) { using (SQLiteCommand cmd = connection.CreateCommand()) {
@ -111,7 +123,7 @@ namespace ICSharpCode.Profiler.Controller.Data
} }
} }
if (version != "1.1") if (version != currentVersion)
throw new IncompatibleDatabaseException(new Version(1, 0), new Version(version)); throw new IncompatibleDatabaseException(new Version(1, 0), new Version(version));
} }
@ -181,7 +193,7 @@ namespace ICSharpCode.Profiler.Controller.Data
using (LockAndCreateCommand(out cmd)) { using (LockAndCreateCommand(out cmd)) {
SQLiteDataReader reader; SQLiteDataReader reader;
cmd.CommandText = @"SELECT d.id, d.cpuusage, d.isfirst, d.rootid, c.endid cmd.CommandText = @"SELECT d.id, d.isfirst, d.rootid, c.endid
FROM DataSets d FROM DataSets d
JOIN Calls c ON c.id = d.rootid JOIN Calls c ON c.id = d.rootid
ORDER BY d.id;"; ORDER BY d.id;";
@ -189,8 +201,8 @@ namespace ICSharpCode.Profiler.Controller.Data
reader = cmd.ExecuteReader(); reader = cmd.ExecuteReader();
while (reader.Read()) { while (reader.Read()) {
list.Add(new SQLiteDataSet(this, reader.GetInt32(0), reader.GetDouble(1), reader.GetBoolean(2), list.Add(new SQLiteDataSet(this, reader.GetInt32(0), reader.GetBoolean(1),
reader.GetInt32(3), reader.GetInt32(4))); reader.GetInt32(2), reader.GetInt32(3)));
} }
} }
@ -205,26 +217,18 @@ namespace ICSharpCode.Profiler.Controller.Data
{ {
ProfilingDataSQLiteProvider provider; ProfilingDataSQLiteProvider provider;
public readonly int ID; public readonly int ID;
double cpuUsage;
bool isFirst; bool isFirst;
public readonly int RootID, CallEndID; public readonly int RootID, CallEndID;
public SQLiteDataSet(ProfilingDataSQLiteProvider provider, int id, double cpuUsage, bool isFirst, int rootID, int callEndID) public SQLiteDataSet(ProfilingDataSQLiteProvider provider, int id, bool isFirst, int rootID, int callEndID)
{ {
this.provider = provider; this.provider = provider;
this.ID = id; this.ID = id;
this.cpuUsage = cpuUsage;
this.isFirst = isFirst; this.isFirst = isFirst;
this.RootID = rootID; this.RootID = rootID;
this.CallEndID = callEndID; this.CallEndID = callEndID;
} }
public double CpuUsage {
get {
return cpuUsage;
}
}
public CallTreeNode RootNode { public CallTreeNode RootNode {
get { get {
return this.provider.GetRoot(ID, ID); return this.provider.GetRoot(ID, ID);
@ -348,6 +352,53 @@ namespace ICSharpCode.Profiler.Controller.Data
return query.Where(c => c.NameMapping.Id != 0 && !c.IsThread).MergeByName(); return query.Where(c => c.NameMapping.Id != 0 && !c.IsThread).MergeByName();
} }
public override PerformanceCounterDescriptor[] GetPerformanceCounters()
{
SQLiteCommand cmd;
using (LockAndCreateCommand(out cmd)) {
cmd.CommandText = "SELECT name, minvalue, maxvalue, unit " +
"FROM PerformanceCounter " +
"ORDER BY id ASC;";
List<PerformanceCounterDescriptor> list = new List<PerformanceCounterDescriptor>();
var reader = cmd.ExecuteReader();
while (reader.Read()) {
list.Add(
new PerformanceCounterDescriptor(
reader.GetString(0),
reader.IsDBNull(1) ? null : new Nullable<float>(reader.GetFloat(1)),
reader.IsDBNull(2) ? null : new Nullable<float>(reader.GetFloat(2)),
reader.GetString(3)
)
);
}
return list.ToArray();
}
}
public override float[] GetPerformanceCounterValues(int index)
{
SQLiteCommand cmd;
using (LockAndCreateCommand(out cmd)) {
cmd.CommandText = "SELECT value " +
"FROM CounterData " +
"WHERE counterid = " + index + " " +
"ORDER BY datasetid ASC;";
List<float> list = new List<float>();
var reader = cmd.ExecuteReader();
while (reader.Read())
list.Add(reader.GetFloat(0));
return list.ToArray();
}
}
Expression<Func<SingleCall, bool>> DataSetFilter(int startIndex, int endIndex) Expression<Func<SingleCall, bool>> DataSetFilter(int startIndex, int endIndex)
{ {
return c => startIndex <= c.DataSetID && c.DataSetID <= endIndex; return c => startIndex <= c.DataSetID && c.DataSetID <= endIndex;

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

@ -99,12 +99,11 @@ namespace ICSharpCode.Profiler.Controller.Data
dataSetCount = 0; dataSetCount = 0;
cmd.Parameters.Add(new SQLiteParameter("id", dataSetCount)); cmd.Parameters.Add(new SQLiteParameter("id", dataSetCount));
cmd.Parameters.Add(new SQLiteParameter("cpuuage", dataSet.CpuUsage.ToString(CultureInfo.InvariantCulture)));
cmd.Parameters.Add(new SQLiteParameter("isfirst", dataSet.IsFirst)); cmd.Parameters.Add(new SQLiteParameter("isfirst", dataSet.IsFirst));
cmd.Parameters.Add(new SQLiteParameter("rootid", functionInfoCount)); cmd.Parameters.Add(new SQLiteParameter("rootid", functionInfoCount));
cmd.CommandText = "INSERT INTO DataSets(id, cpuusage, isfirst, rootid)" + cmd.CommandText = "INSERT INTO DataSets(id, isfirst, rootid)" +
"VALUES(?,?,?,?);"; "VALUES(?,?,?);";
int dataSetStartId = functionInfoCount; int dataSetStartId = functionInfoCount;
@ -164,7 +163,22 @@ namespace ICSharpCode.Profiler.Controller.Data
activecallcount INTEGER NOT NULL, activecallcount INTEGER NOT NULL,
callcount INTEGER NOT NULL, callcount INTEGER NOT NULL,
hasChildren INTEGER NOT NULL 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
);
";
internal const string CallsAndFunctionsIndexDefs = internal const string CallsAndFunctionsIndexDefs =
"CREATE INDEX CallsParent ON Calls(parentid ASC);" // required for searching the children "CREATE INDEX CallsParent ON Calls(parentid ASC);" // required for searching the children
@ -175,7 +189,7 @@ namespace ICSharpCode.Profiler.Controller.Data
// NameMapping { Id, ReturnType, Name, Parameters } // NameMapping { Id, ReturnType, Name, Parameters }
// Calls { id, endid, parentid, nameid, cpucyclesspent, cpucyclesspentself, isactiveatstart, callcount } // Calls { id, endid, parentid, nameid, cpucyclesspent, cpucyclesspentself, isactiveatstart, callcount }
// Functions { datasetid, nameid, cpucyclesspent, cpucyclesspentself, activecallcount, callcount, haschildren } // Functions { datasetid, nameid, cpucyclesspent, cpucyclesspentself, activecallcount, callcount, haschildren }
// DataSets { Id, CPUUsage, RootId } // DataSets { Id, IsFirst, RootId }
// //
// NameMapping.Id <-> FunctionData.NameId 1:N // NameMapping.Id <-> FunctionData.NameId 1:N
// FunctionData.ParentId <-> FunctionData.Id 1:N // FunctionData.ParentId <-> FunctionData.Id 1:N
@ -195,7 +209,6 @@ namespace ICSharpCode.Profiler.Controller.Data
CREATE TABLE DataSets( CREATE TABLE DataSets(
id INTEGER NOT NULL PRIMARY KEY, id INTEGER NOT NULL PRIMARY KEY,
cpuusage REAL NOT NULL,
isfirst INTEGER NOT NULL, isfirst INTEGER NOT NULL,
rootid INTEGER NOT NULL rootid INTEGER NOT NULL
); );
@ -205,18 +218,7 @@ namespace ICSharpCode.Profiler.Controller.Data
value TEXT NOT NULL value TEXT NOT NULL
); );
INSERT INTO Properties(name, value) VALUES('version', '1.1'); INSERT INTO Properties(name, value) VALUES('version', '1.2');
CREATE TABLE PerformanceCounter(
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE CounterData(
datasetid INTEGER NOT NULL,
counterid INTEGER NOT NULL,
value INTEGER NOT NULL
);
"; ";
cmd.ExecuteNonQuery(); cmd.ExecuteNonQuery();
@ -299,5 +301,48 @@ namespace ICSharpCode.Profiler.Controller.Data
isDisposed = true; isDisposed = true;
} }
public void WritePerformanceCounterData(IEnumerable<PerformanceCounterDescriptor> counters)
{
using (SQLiteTransaction trans = this.connection.BeginTransaction()) {
using (SQLiteCommand cmd = this.connection.CreateCommand()) {
SQLiteParameter idParam = new SQLiteParameter("id");
SQLiteParameter nameParam = new SQLiteParameter("name");
SQLiteParameter dataSetParam = new SQLiteParameter("dataset");
SQLiteParameter valueParam = new SQLiteParameter("value");
SQLiteParameter minParam = new SQLiteParameter("min");
SQLiteParameter maxParam = new SQLiteParameter("max");
SQLiteParameter unitParam = new SQLiteParameter("unit");
cmd.CommandText =
"INSERT OR IGNORE INTO PerformanceCounter(id, name, minvalue, maxvalue, unit)" +
"VALUES(@id,@name,@min,@max,@unit);" +
"INSERT INTO CounterData(datasetid, counterid, value)" +
"VALUES(@dataset,@id,@value);";
cmd.Parameters.AddRange(new SQLiteParameter[] { idParam, nameParam, dataSetParam, valueParam, minParam, maxParam, unitParam });
int id = 0;
foreach (PerformanceCounterDescriptor counter in counters) {
idParam.Value = id;
nameParam.Value = counter.Name;
minParam.Value = counter.MinValue;
maxParam.Value = counter.MaxValue;
unitParam.Value = counter.Unit;
for (int i = 0; i < counter.Values.Count; i++) {
dataSetParam.Value = i;
valueParam.Value = counter.Values[i];
cmd.ExecuteNonQuery();
}
id++;
}
}
trans.Commit();
}
}
} }
} }

27
src/AddIns/Misc/Profiler/Controller/Data/TempFileDatabase.cs

@ -37,13 +37,18 @@ namespace ICSharpCode.Profiler.Controller.Data
get { return nameMappings.Select(i => i.Value).ToList().AsReadOnly(); } get { return nameMappings.Select(i => i.Value).ToList().AsReadOnly(); }
} }
List<PerformanceCounterDescriptor> counters = new List<PerformanceCounterDescriptor>();
public ReadOnlyCollection<PerformanceCounterDescriptor> Counters {
get { return counters.AsReadOnly(); }
}
struct StreamInfo struct StreamInfo
{ {
public TargetProcessPointer NativeStartPosition { get; set; } public TargetProcessPointer NativeStartPosition { get; set; }
public TargetProcessPointer NativeRootFuncInfoStartPosition { get; set; } public TargetProcessPointer NativeRootFuncInfoStartPosition { get; set; }
public long StreamStartPosition { get; set; } public long StreamStartPosition { get; set; }
public long StreamLength { get; set; } public long StreamLength { get; set; }
public double CpuUsage { get; set; }
public bool IsFirst { get; set; } public bool IsFirst { get; set; }
} }
@ -55,8 +60,8 @@ namespace ICSharpCode.Profiler.Controller.Data
public DataSet(TempFileDatabase database, UnmanagedMemory view, public DataSet(TempFileDatabase database, UnmanagedMemory view,
TargetProcessPointer nativeStartPosition, TargetProcessPointer nativeRootFuncInfoStartPosition, TargetProcessPointer nativeStartPosition, TargetProcessPointer nativeRootFuncInfoStartPosition,
double cpuUsage, bool isFirst) bool isFirst)
: base(nativeStartPosition, nativeRootFuncInfoStartPosition, view.Pointer, view.Length, cpuUsage, isFirst, database.is64Bit) : base(nativeStartPosition, nativeRootFuncInfoStartPosition, view.Pointer, view.Length, isFirst, database.is64Bit)
{ {
this.database = database; this.database = database;
this.view = view; this.view = view;
@ -117,7 +122,7 @@ namespace ICSharpCode.Profiler.Controller.Data
if (dataSet == null) if (dataSet == null)
throw new InvalidOperationException("TempFileDatabase cannot write DataSets other than UnmanagedProfilingDataSet!"); throw new InvalidOperationException("TempFileDatabase cannot write DataSets other than UnmanagedProfilingDataSet!");
database.AddDataset((byte *)uDataSet.StartPtr.ToPointer(), uDataSet.Length, uDataSet.NativeStartPosition, uDataSet.NativeRootFuncInfoPosition, uDataSet.CpuUsage, uDataSet.IsFirst); database.AddDataset((byte *)uDataSet.StartPtr.ToPointer(), uDataSet.Length, uDataSet.NativeStartPosition, uDataSet.NativeRootFuncInfoPosition, uDataSet.IsFirst);
this.database.is64Bit = uDataSet.Is64Bit; this.database.is64Bit = uDataSet.Is64Bit;
} }
@ -133,6 +138,11 @@ namespace ICSharpCode.Profiler.Controller.Data
foreach (NameMapping nm in mappings) foreach (NameMapping nm in mappings)
this.database.nameMappings.Add(nm.Id, nm); this.database.nameMappings.Add(nm.Id, nm);
} }
public void WritePerformanceCounterData(IEnumerable<PerformanceCounterDescriptor> counters)
{
this.database.counters.AddRange(counters);
}
} }
#endregion #endregion
@ -150,14 +160,14 @@ namespace ICSharpCode.Profiler.Controller.Data
this.file = new FileStream(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite, 4096, FileOptions.Asynchronous | FileOptions.DeleteOnClose); this.file = new FileStream(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite, 4096, FileOptions.Asynchronous | FileOptions.DeleteOnClose);
} }
unsafe void AddDataset(byte *ptr, long length, TargetProcessPointer nativeStartPosition, TargetProcessPointer nativeRootFuncInfoStartPosition, double cpuUsage, bool isFirst) unsafe void AddDataset(byte *ptr, long length, TargetProcessPointer nativeStartPosition, TargetProcessPointer nativeRootFuncInfoStartPosition, bool isFirst)
{ {
byte[] data = new byte[length]; byte[] data = new byte[length];
Marshal.Copy(new IntPtr(ptr), data, 0, (int)length); Marshal.Copy(new IntPtr(ptr), data, 0, (int)length);
if (this.currentWrite != null) if (this.currentWrite != null)
this.file.EndWrite(this.currentWrite); this.file.EndWrite(this.currentWrite);
this.streamInfos.Add(new StreamInfo { NativeStartPosition = nativeStartPosition, NativeRootFuncInfoStartPosition = nativeRootFuncInfoStartPosition, this.streamInfos.Add(new StreamInfo { NativeStartPosition = nativeStartPosition, NativeRootFuncInfoStartPosition = nativeRootFuncInfoStartPosition,
StreamStartPosition = this.file.Length, StreamLength = length, CpuUsage = cpuUsage, IsFirst = isFirst }); StreamStartPosition = this.file.Length, StreamLength = length, IsFirst = isFirst });
this.currentWrite = this.file.BeginWrite(data, 0, (int)length, null, null); this.currentWrite = this.file.BeginWrite(data, 0, (int)length, null, null);
} }
@ -206,8 +216,7 @@ namespace ICSharpCode.Profiler.Controller.Data
throw new InvalidOperationException("All writers have to be closed before reading the data from the database!"); throw new InvalidOperationException("All writers have to be closed before reading the data from the database!");
return new DataSet(this, mmf.MapView(streamInfos[index].StreamStartPosition, streamInfos[index].StreamLength), streamInfos[index].NativeStartPosition, return new DataSet(this, mmf.MapView(streamInfos[index].StreamStartPosition, streamInfos[index].StreamLength), streamInfos[index].NativeStartPosition,
streamInfos[index].NativeRootFuncInfoStartPosition, streamInfos[index].NativeRootFuncInfoStartPosition, streamInfos[index].IsFirst);
streamInfos[index].CpuUsage, streamInfos[index].IsFirst);
} }
/// <summary> /// <summary>
@ -219,6 +228,8 @@ namespace ICSharpCode.Profiler.Controller.Data
{ {
writer.ProcessorFrequency = this.processorFrequency; writer.ProcessorFrequency = this.processorFrequency;
writer.WriteMappings(this.nameMappings.Values); writer.WriteMappings(this.nameMappings.Values);
writer.WritePerformanceCounterData(this.counters);
for (int i = 0; i < this.DataSetCount; i++) { for (int i = 0; i < this.DataSetCount; i++) {
using (UnmanagedProfilingDataSet dataSet = this.LoadDataSet(i)) using (UnmanagedProfilingDataSet dataSet = this.LoadDataSet(i))
writer.WriteDataSet(dataSet); writer.WriteDataSet(dataSet);

12
src/AddIns/Misc/Profiler/Controller/Data/UnitTestWriter.cs

@ -38,15 +38,12 @@ namespace ICSharpCode.Profiler.Controller.Data
sealed class UnitTestDataSet : IProfilingDataSet sealed class UnitTestDataSet : IProfilingDataSet
{ {
public UnitTestDataSet(CallTreeNode root, bool isFirst, double cpuUsage) public UnitTestDataSet(CallTreeNode root, bool isFirst)
{ {
this.RootNode = root; this.RootNode = root;
this.IsFirst = isFirst; this.IsFirst = isFirst;
this.CpuUsage = cpuUsage;
} }
public double CpuUsage { get; private set; }
public bool IsFirst { get; private set; } public bool IsFirst { get; private set; }
public CallTreeNode RootNode { get; private set; } public CallTreeNode RootNode { get; private set; }
@ -70,7 +67,7 @@ namespace ICSharpCode.Profiler.Controller.Data
if (list.Count > 0) { if (list.Count > 0) {
this.targetWriter.WriteDataSet( this.targetWriter.WriteDataSet(
new UnitTestDataSet(new UnitTestRootCallTreeNode(list), dataSet.IsFirst, dataSet.CpuUsage) new UnitTestDataSet(new UnitTestRootCallTreeNode(list), dataSet.IsFirst)
); );
} }
} }
@ -103,5 +100,10 @@ namespace ICSharpCode.Profiler.Controller.Data
{ {
this.targetWriter.Close(); this.targetWriter.Close();
} }
public void WritePerformanceCounterData(IEnumerable<PerformanceCounterDescriptor> counters)
{
this.targetWriter.WritePerformanceCounterData(counters);
}
} }
} }

12
src/AddIns/Misc/Profiler/Controller/Data/UnmanagedProfilingDataSet.cs

@ -23,7 +23,6 @@ namespace ICSharpCode.Profiler.Controller.Data
TargetProcessPointer nativeStartPosition; TargetProcessPointer nativeStartPosition;
TargetProcessPointer nativeRootFuncInfoPosition; TargetProcessPointer nativeRootFuncInfoPosition;
bool isDisposed; bool isDisposed;
double cpuUsage;
bool isFirst; bool isFirst;
bool is64Bit; bool is64Bit;
@ -60,14 +59,6 @@ namespace ICSharpCode.Profiler.Controller.Data
get { return startPtr; } get { return startPtr; }
} }
/// <summary>
/// Gets the percentage of the CPU usage by the profilee.
/// </summary>
public double CpuUsage
{
get { return cpuUsage; }
}
internal abstract unsafe void *TranslatePointer(TargetProcessPointer ptr); internal abstract unsafe void *TranslatePointer(TargetProcessPointer ptr);
/// <summary> /// <summary>
@ -83,10 +74,9 @@ namespace ICSharpCode.Profiler.Controller.Data
public abstract NameMapping GetMapping(int nameId); public abstract NameMapping GetMapping(int nameId);
internal UnmanagedProfilingDataSet(TargetProcessPointer nativeStartPosition, TargetProcessPointer nativeRootFuncInfoPosition, internal UnmanagedProfilingDataSet(TargetProcessPointer nativeStartPosition, TargetProcessPointer nativeRootFuncInfoPosition,
byte *startPtr, long length, double cpuUsage, bool isFirst, bool is64Bit) byte *startPtr, long length, bool isFirst, bool is64Bit)
{ {
this.nativeStartPosition = nativeStartPosition; this.nativeStartPosition = nativeStartPosition;
this.cpuUsage = cpuUsage;
this.nativeRootFuncInfoPosition = nativeRootFuncInfoPosition; this.nativeRootFuncInfoPosition = nativeRootFuncInfoPosition;
this.is64Bit = is64Bit; this.is64Bit = is64Bit;
this.isFirst = isFirst; this.isFirst = isFirst;

46
src/AddIns/Misc/Profiler/Controller/Profiler.cs

@ -84,6 +84,8 @@ namespace ICSharpCode.Profiler.Controller
readonly string MutexId = "Local\\Profiler.Mutex.{" + Guid.NewGuid().ToString().ToUpperInvariant() + "}"; readonly string MutexId = "Local\\Profiler.Mutex.{" + Guid.NewGuid().ToString().ToUpperInvariant() + "}";
readonly string AccessEventId = "Local\\Profiler.Events.{" + Guid.NewGuid().ToString().ToUpperInvariant() + "}"; readonly string AccessEventId = "Local\\Profiler.Events.{" + Guid.NewGuid().ToString().ToUpperInvariant() + "}";
string performanceCounterInstanceName;
/// <summary> /// <summary>
/// The Guid of the CProfiler class in the Hook. /// The Guid of the CProfiler class in the Hook.
/// </summary> /// </summary>
@ -101,8 +103,7 @@ namespace ICSharpCode.Profiler.Controller
UnmanagedCircularBuffer nativeToManagedBuffer; UnmanagedCircularBuffer nativeToManagedBuffer;
IProfilingDataWriter dataWriter; IProfilingDataWriter dataWriter;
Dictionary<string, PerformanceCounter> performanceCounters; PerformanceCounterDescriptor[] performanceCounters;
PerformanceCounter cpuUsageCounter;
/// <summary> /// <summary>
/// The currently used data provider. /// The currently used data provider.
@ -118,7 +119,7 @@ namespace ICSharpCode.Profiler.Controller
// before the profilee can open it. // before the profilee can open it.
MemoryMappedFile file; MemoryMappedFile file;
ProfilerOptions profilerOptions = new ProfilerOptions(); ProfilerOptions profilerOptions;
/// <summary> /// <summary>
/// Gets all settings used by this profiler instance. /// Gets all settings used by this profiler instance.
@ -248,7 +249,11 @@ namespace ICSharpCode.Profiler.Controller
this.is64Bit = DetectBinaryType.RunsAs64Bit(info.FileName); this.is64Bit = DetectBinaryType.RunsAs64Bit(info.FileName);
this.profilerOutput = new StringBuilder(); this.profilerOutput = new StringBuilder();
this.performanceCounters = new Dictionary<string, PerformanceCounter>(); this.performanceCounters = options.Counters;
foreach (var counter in performanceCounters)
counter.Reset();
this.dataWriter = dataWriter; this.dataWriter = dataWriter;
this.threadListMutex = new Mutex(false, MutexId); this.threadListMutex = new Mutex(false, MutexId);
@ -360,14 +365,13 @@ namespace ICSharpCode.Profiler.Controller
item = (ThreadLocalData32*)TranslatePointer(item->Predecessor); item = (ThreadLocalData32*)TranslatePointer(item->Predecessor);
} }
if (this.enableDC) { if (this.enableDC) {
this.AddDataset(fullView.Pointer, this.AddDataset(fullView.Pointer,
memHeader32->NativeAddress + memHeader32->HeapOffset, memHeader32->NativeAddress + memHeader32->HeapOffset,
memHeader32->Allocator.startPos - memHeader32->NativeAddress, memHeader32->Allocator.startPos - memHeader32->NativeAddress,
memHeader32->Allocator.pos - memHeader32->Allocator.startPos, memHeader32->Allocator.pos - memHeader32->Allocator.startPos,
(cpuUsageCounter == null) ? 0 : cpuUsageCounter.NextValue(), isFirstDC, memHeader32->RootFuncInfoAddress);
isFirstDC,
memHeader32->RootFuncInfoAddress);
isFirstDC = false; isFirstDC = false;
} }
@ -409,11 +413,20 @@ namespace ICSharpCode.Profiler.Controller
} }
} }
unsafe void AddDataset(byte *ptr, TargetProcessPointer nativeStartPosition, long offset, long length, double cpuUsage, bool isFirst, TargetProcessPointer nativeRootFuncInfoPosition) unsafe void AddDataset(byte *ptr, TargetProcessPointer nativeStartPosition, long offset, long length, bool isFirst, TargetProcessPointer nativeRootFuncInfoPosition)
{ {
using (DataSet dataSet = new DataSet(this, ptr + offset, length, nativeStartPosition, nativeRootFuncInfoPosition, cpuUsage, isFirst, is64Bit)) { using (DataSet dataSet = new DataSet(this, ptr + offset, length, nativeStartPosition, nativeRootFuncInfoPosition, isFirst, is64Bit)) {
lock (this.dataWriter) { lock (this.dataWriter) {
this.dataWriter.WriteDataSet(dataSet); this.dataWriter.WriteDataSet(dataSet);
if (performanceCounterInstanceName == null)
performanceCounterInstanceName = PerformanceCounterDescriptor.GetProcessInstanceName(profilee.Id);
if (performanceCounterInstanceName == null)
LogString("instance not found!");
foreach (var counter in performanceCounters)
counter.Collect(performanceCounterInstanceName);
} }
} }
} }
@ -519,8 +532,6 @@ namespace ICSharpCode.Profiler.Controller
Debug.WriteLine("Launching profiler for " + this.psi.FileName + "..."); Debug.WriteLine("Launching profiler for " + this.psi.FileName + "...");
this.profilee.Start(); this.profilee.Start();
this.cpuUsageCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total", ".");
this.logger.Start(nativeToManagedBuffer.CreateReadingStream()); this.logger.Start(nativeToManagedBuffer.CreateReadingStream());
// GC references currentSession // GC references currentSession
@ -599,11 +610,7 @@ namespace ICSharpCode.Profiler.Controller
if (this.profilerOptions.EnableDC) if (this.profilerOptions.EnableDC)
this.dataCollector.Join(); this.dataCollector.Join();
// unload all counters to prevent exception during last collection! // Do last data collection
this.cpuUsageCounter = null;
this.performanceCounters = null;
// Take last shot
if (this.is64Bit) if (this.is64Bit)
CollectData64(); CollectData64();
else else
@ -611,6 +618,8 @@ namespace ICSharpCode.Profiler.Controller
isRunning = false; isRunning = false;
this.dataWriter.WritePerformanceCounterData(performanceCounters);
this.dataWriter.Close(); this.dataWriter.Close();
OnSessionEnded(EventArgs.Empty); OnSessionEnded(EventArgs.Empty);
@ -820,9 +829,8 @@ namespace ICSharpCode.Profiler.Controller
Profiler profiler; Profiler profiler;
public DataSet(Profiler profiler, byte *startPtr, long length, TargetProcessPointer nativeStartPosition, public DataSet(Profiler profiler, byte *startPtr, long length, TargetProcessPointer nativeStartPosition,
TargetProcessPointer nativeRootFuncInfoPosition, TargetProcessPointer nativeRootFuncInfoPosition, bool isFirst, bool is64Bit)
double cpuUsage, bool isFirst, bool is64Bit) : base(nativeStartPosition, nativeRootFuncInfoPosition, startPtr, length, isFirst, is64Bit)
: base(nativeStartPosition, nativeRootFuncInfoPosition, startPtr, length, cpuUsage, isFirst, is64Bit)
{ {
this.profiler = profiler; this.profiler = profiler;
} }

22
src/AddIns/Misc/Profiler/Controller/ProfilerOptions.cs

@ -6,6 +6,11 @@
// </file> // </file>
using System; using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using ICSharpCode.Profiler.Controller.Data;
namespace ICSharpCode.Profiler.Controller namespace ICSharpCode.Profiler.Controller
{ {
@ -19,12 +24,23 @@ namespace ICSharpCode.Profiler.Controller
/// </summary> /// </summary>
public const int DefaultSharedMemorySize = 64 * 1024 * 1024; // 64 mb 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")
};
bool enableDC; bool enableDC;
bool enableDCAtStart; bool enableDCAtStart;
bool dotNotProfileDotNetInternals; bool dotNotProfileDotNetInternals;
bool combineRecursiveFunction; bool combineRecursiveFunction;
int sharedMemorySize; int sharedMemorySize;
PerformanceCounterDescriptor[] counters;
public PerformanceCounterDescriptor[] Counters {
get { return this.counters; }
}
/// <summary> /// <summary>
/// Gets whether .NET internal calls are profiled or not. /// Gets whether .NET internal calls are profiled or not.
/// </summary> /// </summary>
@ -63,20 +79,22 @@ namespace ICSharpCode.Profiler.Controller
/// <summary> /// <summary>
/// Creates new ProfilerOptions using the selected settings. /// Creates new ProfilerOptions using the selected settings.
/// </summary> /// </summary>
public ProfilerOptions(bool enableDC, int sharedMemorySize, bool profileDotNetInternals, bool combineRecursiveFunction, bool enableDCAtStart) public ProfilerOptions(bool enableDC, int sharedMemorySize, bool profileDotNetInternals,
bool combineRecursiveFunction, bool enableDCAtStart, IEnumerable<PerformanceCounterDescriptor> counters)
{ {
this.enableDC = enableDC; this.enableDC = enableDC;
this.sharedMemorySize = sharedMemorySize; this.sharedMemorySize = sharedMemorySize;
this.dotNotProfileDotNetInternals = profileDotNetInternals; this.dotNotProfileDotNetInternals = profileDotNetInternals;
this.combineRecursiveFunction = combineRecursiveFunction; this.combineRecursiveFunction = combineRecursiveFunction;
this.enableDCAtStart = enableDCAtStart; this.enableDCAtStart = enableDCAtStart;
this.counters = counters.ToArray();
} }
/// <summary> /// <summary>
/// Creates default ProfilerOptions. /// Creates default ProfilerOptions.
/// </summary> /// </summary>
public ProfilerOptions() public ProfilerOptions()
: this(true, DefaultSharedMemorySize, false, false, true) : this(true, DefaultSharedMemorySize, false, false, true, DefaultCounters)
{ {
} }
} }

50
src/AddIns/Misc/Profiler/Frontend/AddIn/Src/Dialogs/ProfilerControlWindow.xaml

@ -1,30 +1,26 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Window <Window
x:Class="ICSharpCode.Profiler.AddIn.Dialogs.ProfilerControlWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sd="http://icsharpcode.net/sharpdevelop/core" x:Class="ICSharpCode.Profiler.AddIn.Dialogs.ProfilerControlWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sd="http://icsharpcode.net/sharpdevelop/core"
Title="{sd:Localize AddIns.Profiler.ProfilerControlWindow.Title}" Title="{sd:Localize AddIns.Profiler.ProfilerControlWindow.Title}" SizeToContent="WidthAndHeight"
WindowStyle="ToolWindow" Closing="WindowClosing" WindowStyle="ToolWindow" Closing="WindowClosing"
Topmost="True" Topmost="True">
Height="60" <Grid>
Width="170"> <Grid.ColumnDefinitions>
<Grid> <ColumnDefinition Width="Auto" />
<Grid.RowDefinitions> </Grid.ColumnDefinitions>
<RowDefinition <Grid.RowDefinitions>
Height="*" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<ToolBar <Menu>
Grid.Column="0" <ToggleButton
Grid.Row="0" Content="{sd:Localize AddIns.Profiler.ProfilerControlWindow.CollectData}"
HorizontalAlignment="Left" x:Name="collectData"
VerticalAlignment="Stretch"> Checked="CollectDataChecked"
<ToggleButton Unchecked="CollectDataUnchecked" />
Content="{sd:Localize AddIns.Profiler.ProfilerControlWindow.CollectData}" <Button
x:Name="collectData" Content="{sd:Localize AddIns.Profiler.ProfilerControlWindow.Shutdown}"
Checked="CollectDataChecked" x:Name="shutdown"
Unchecked="CollectDataUnchecked" /> Click="ShutdownClick" />
<Button </Menu>
Content="{sd:Localize AddIns.Profiler.ProfilerControlWindow.Shutdown}" </Grid>
x:Name="shutdown"
Click="ShutdownClick" />
</ToolBar>
</Grid>
</Window> </Window>

6
src/AddIns/Misc/Profiler/Frontend/AddIn/Src/OptionPanels/OptionWrapper.cs

@ -4,9 +4,10 @@
// <owner name="Siegfried Pammer" email="siegfriedpammer@gmail.com"/> // <owner name="Siegfried Pammer" email="siegfriedpammer@gmail.com"/>
// <version>$Revision$</version> // <version>$Revision$</version>
// </file> // </file>
using ICSharpCode.Profiler.Controller;
using System; using System;
using ICSharpCode.Core; using ICSharpCode.Core;
using ICSharpCode.Profiler.Controller;
using ICSharpCode.Profiler.Controller.Data;
namespace ICSharpCode.Profiler.AddIn.OptionPanels namespace ICSharpCode.Profiler.AddIn.OptionPanels
{ {
@ -46,7 +47,8 @@ namespace ICSharpCode.Profiler.AddIn.OptionPanels
properties.Get("SharedMemorySize", ProfilerOptions.DefaultSharedMemorySize), properties.Get("SharedMemorySize", ProfilerOptions.DefaultSharedMemorySize),
properties.Get("DoNotProfileNetInternals", false), properties.Get("DoNotProfileNetInternals", false),
properties.Get("CombineRecursiveFunction", false), properties.Get("CombineRecursiveFunction", false),
properties.Get("EnableDCAtStart", true) properties.Get("EnableDCAtStart", true),
properties.Get("PerformanceCounters", ProfilerOptions.DefaultCounters)
); );
} }
} }

28
src/AddIns/Misc/Profiler/Frontend/AddIn/Src/ProfilerRunner.cs

@ -81,19 +81,23 @@ namespace ICSharpCode.Profiler.AddIn
void FinishSession() void FinishSession()
{ {
using (AsynchronousWaitDialog dlg = AsynchronousWaitDialog.ShowWaitDialog(StringParser.Parse("${res:AddIns.Profiler.Messages.PreparingForAnalysis}"), true)) { using (AsynchronousWaitDialog dlg = AsynchronousWaitDialog.ShowWaitDialog(StringParser.Parse("${res:AddIns.Profiler.Messages.PreparingForAnalysis}"), true)) {
profiler.Dispose(); try {
profiler.Dispose();
WorkbenchSingleton.SafeThreadAsyncCall(() => { controlWindow.AllowClose = true; this.controlWindow.Close(); });
if (database != null) { WorkbenchSingleton.SafeThreadAsyncCall(() => { controlWindow.AllowClose = true; this.controlWindow.Close(); });
database.WriteTo(writer, progress => !dlg.IsCancelled); if (database != null) {
writer.Close(); database.WriteTo(writer, progress => !dlg.IsCancelled);
database.Close(); writer.Close();
} else { database.Close();
writer.Close(); } else {
} writer.Close();
}
if (!dlg.IsCancelled) if (!dlg.IsCancelled)
OnRunFinished(EventArgs.Empty); OnRunFinished(EventArgs.Empty);
} catch (Exception ex) {
Debug.Print(ex.ToString());
}
} }
} }

20
src/AddIns/Misc/Profiler/Frontend/AddIn/Src/Views/ProfilerView.xaml

@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sd="http://icsharpcode.net/sharpdevelop/core" xmlns:sd="http://icsharpcode.net/sharpdevelop/core"
xmlns:y="clr-namespace:ICSharpCode.Profiler.Controls;assembly=ICSharpCode.Profiler.Controls" xmlns:controls="clr-namespace:ICSharpCode.Profiler.Controls;assembly=ICSharpCode.Profiler.Controls"
xmlns:local="clr-namespace:ICSharpCode.Profiler.AddIn.Views;assembly=ICSharpCode.Profiler.AddIn"> xmlns:local="clr-namespace:ICSharpCode.Profiler.AddIn.Views;assembly=ICSharpCode.Profiler.AddIn">
<UserControl.Resources> <UserControl.Resources>
<Style x:Key="CloseButton" TargetType="{x:Type Button}"> <Style x:Key="CloseButton" TargetType="{x:Type Button}">
@ -24,10 +24,14 @@
</Style> </Style>
</UserControl.Resources> </UserControl.Resources>
<Grid> <Grid>
<GridSplitter Width="0" Margin="0,202.5,0,204.5" /> <Grid.RowDefinitions>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled" Margin="0,30,0,0" Height="90.5" HorizontalAlignment="Stretch" VerticalAlignment="Top"> <RowDefinition Height="Auto" />
<y:TimeLineControl x:Name="timeLine" RangeChanged="TimeLineRangeChanged" /> <RowDefinition Height="Auto" />
</ScrollViewer> <RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Vertical" Grid.Row="1">
<controls:ExtendedTimeLineControl x:Name="timeLine2" RangeChanged="TimeLineRangeChanged" />
</StackPanel>
<ToolBar Height="27" x:Name="toolBar1" VerticalAlignment="Top"> <ToolBar Height="27" x:Name="toolBar1" VerticalAlignment="Top">
<Menu> <Menu>
<MenuItem x:Name="mnuQueryHistory" Header="{sd:Localize AddIns.Profiler.ProfilingView.QueryHistoryText}"> <MenuItem x:Name="mnuQueryHistory" Header="{sd:Localize AddIns.Profiler.ProfilingView.QueryHistoryText}">
@ -36,12 +40,12 @@
</MenuItem> </MenuItem>
</Menu> </Menu>
</ToolBar> </ToolBar>
<TabControl x:Name="tabView" HorizontalAlignment="Stretch" Margin="0,120,0,0" SelectionChanged="TabViewSelectionChanged"> <TabControl x:Name="tabView" HorizontalAlignment="Stretch" Grid.Row="2" SelectionChanged="TabViewSelectionChanged">
<TabItem Header="{sd:Localize AddIns.Profiler.ProfilingView.OverviewTabText}"> <TabItem Header="{sd:Localize AddIns.Profiler.ProfilingView.OverviewTabText}">
<y:QueryView x:Name="treeView" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ShowQueryItems="False" CurrentQuery="from t in Threads select t" IsQueryModifiable="False" /> <controls:QueryView x:Name="treeView" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ShowQueryItems="False" CurrentQuery="from t in Threads select t" IsQueryModifiable="False" />
</TabItem> </TabItem>
<TabItem Header="{sd:Localize AddIns.Profiler.ProfilingView.Top20TabText}"> <TabItem Header="{sd:Localize AddIns.Profiler.ProfilingView.Top20TabText}">
<y:QueryView x:Name="top20View" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ShowQueryItems="False" CurrentQuery="(from f in Functions where f.IsUserCode orderby f.CpuCyclesSpent descending select f).Take(20)" IsQueryModifiable="False" /> <controls:QueryView x:Name="top20View" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ShowQueryItems="False" CurrentQuery="(from f in Functions where f.IsUserCode orderby f.CpuCyclesSpent descending select f).Take(20)" IsQueryModifiable="False" />
</TabItem> </TabItem>
<TabItem x:Name="dummyTab" /> <TabItem x:Name="dummyTab" />
</TabControl> </TabControl>

21
src/AddIns/Misc/Profiler/Frontend/AddIn/Src/Views/ProfilerView.xaml.cs

@ -31,11 +31,10 @@ namespace ICSharpCode.Profiler.AddIn.Views
this.provider = provider; this.provider = provider;
this.timeLine.IsEnabled = true; this.timeLine2.IsEnabled = true;
this.timeLine.ValuesList.Clear(); this.timeLine2.Provider = provider;
this.timeLine.ValuesList.AddRange(this.provider.DataSets.Select(i => new TimeLineInfo() { value = i.CpuUsage / 100, displayMarker = i.IsFirst })); this.timeLine2.SelectedStartIndex = 0;
this.timeLine.SelectedStartIndex = 0; this.timeLine2.SelectedEndIndex = provider.DataSets.Count;
this.timeLine.SelectedEndIndex = this.timeLine.ValuesList.Count;
var translation = new SharpDevelopTranslation(); var translation = new SharpDevelopTranslation();
@ -45,7 +44,7 @@ namespace ICSharpCode.Profiler.AddIn.Views
view.Reporter = new ErrorReporter(UpdateErrorList); view.Reporter = new ErrorReporter(UpdateErrorList);
view.Provider = provider; view.Provider = provider;
view.Translation = translation; view.Translation = translation;
view.SetRange(this.timeLine.SelectedStartIndex, this.timeLine.SelectedEndIndex); view.SetRange(this.timeLine2.SelectedStartIndex, this.timeLine2.SelectedEndIndex);
view.ContextMenuOpening += delegate(object sender, ContextMenuEventArgs e) { view.ContextMenuOpening += delegate(object sender, ContextMenuEventArgs e) {
object source = (e.OriginalSource is Shape) ? e.OriginalSource : view; object source = (e.OriginalSource is Shape) ? e.OriginalSource : view;
MenuService.CreateContextMenu(source, "/AddIns/Profiler/QueryView/ContextMenu").IsOpen = true; MenuService.CreateContextMenu(source, "/AddIns/Profiler/QueryView/ContextMenu").IsOpen = true;
@ -85,9 +84,9 @@ namespace ICSharpCode.Profiler.AddIn.Views
void DoSelectAll() void DoSelectAll()
{ {
if (this.timeLine.IsEnabled) { if (this.timeLine2.IsEnabled) {
this.timeLine.SelectedStartIndex = 0; this.timeLine2.SelectedStartIndex = 0;
this.timeLine.SelectedEndIndex = this.timeLine.ValuesList.Count; this.timeLine2.SelectedEndIndex = this.timeLine2.Provider.DataSets.Count;
} }
} }
@ -99,7 +98,7 @@ namespace ICSharpCode.Profiler.AddIn.Views
void CanDoSelectAll(CanExecuteRoutedEventArgs e) void CanDoSelectAll(CanExecuteRoutedEventArgs e)
{ {
e.CanExecute = this.timeLine.IsEnabled && this.timeLine.ValuesList.Count > 0; e.CanExecute = this.timeLine2.IsEnabled && this.timeLine2.Provider.DataSets.Count > 0;
} }
void CloseButtonClick(object sender, RoutedEventArgs e) void CloseButtonClick(object sender, RoutedEventArgs e)
@ -192,7 +191,7 @@ namespace ICSharpCode.Profiler.AddIn.Views
view.Provider = this.provider; view.Provider = this.provider;
view.Reporter = new ErrorReporter(UpdateErrorList); view.Reporter = new ErrorReporter(UpdateErrorList);
view.SetRange(this.timeLine.SelectedStartIndex, this.timeLine.SelectedEndIndex); view.SetRange(this.timeLine2.SelectedStartIndex, this.timeLine2.SelectedEndIndex);
view.CurrentQuery = query; view.CurrentQuery = query;
view.ShowQueryItems = true; view.ShowQueryItems = true;

37
src/AddIns/Misc/Profiler/Frontend/Controls/ExtendedTimeLineControl.xaml

@ -1,42 +1,31 @@
<UserControl x:Class="ICSharpCode.Profiler.Controls.ExtendedTimeLineControl" <UserControl x:Class="ICSharpCode.Profiler.Controls.ExtendedTimeLineControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:y="clr-namespace:ICSharpCode.Profiler.Controls"> xmlns:local="clr-namespace:ICSharpCode.Profiler.Controls">
<DockPanel> <DockPanel>
<DockPanel Margin="3,0"> <DockPanel Margin="3,0">
<TextBlock Text="Performance Counters" DockPanel.Dock="Top" /> <TextBlock Text="Performance Counters" DockPanel.Dock="Top" />
<ListBox x:Name="perfCounterList" ItemsSource="{Binding}" /> <ListBox x:Name="perfCounterList" SelectionChanged="PerfCounterListSelectionChanged" />
</DockPanel> </DockPanel>
<Grid Margin="3,0"> <Grid Margin="3,0">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="20" /> <RowDefinition Height="20" />
<RowDefinition Height="20" /> <RowDefinition Height="20" />
<RowDefinition Height="Auto" /> <RowDefinition Height="100" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Time" /> <TextBlock Text="Time" />
<TextBlock Grid.Row="1" Text="Events" /> <TextBlock Grid.Row="1" Text="Events" />
<TextBlock Grid.Row="2" Text="Graph" /> <DockPanel Grid.Row="2">
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<TextBlock Text="Graph " />
<TextBlock x:Name="maxLabel" HorizontalAlignment="Right" />
</StackPanel>
<TextBlock x:Name="minLabel" DockPanel.Dock="Bottom" VerticalAlignment="Bottom" HorizontalAlignment="Right" />
<TextBlock x:Name="unitLabel" VerticalAlignment="Center" HorizontalAlignment="Right" />
</DockPanel>
</Grid> </Grid>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled" Margin="3,0"> <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled" Margin="3,0">
<Grid> <local:TimeLineControl x:Name="timeLine" />
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="20" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<TextBlock Text="Test" />
<TextBlock Text="a aa aaa aaaa aaaaa aaaaaa aaaaaaa aaaaaaaa aaaaaaaaa" />
<TextBlock Text="a aa aaa aaaa aaaaa aaaaaa aaaaaaa aaaaaaaa aaaaaaaaa" />
<TextBlock Text="a aa aaa aaaa aaaaa aaaaaa aaaaaaa aaaaaaaa aaaaaaaaa" />
<TextBlock Text="a aa aaa aaaa aaaaa aaaaaa aaaaaaa aaaaaaaa aaaaaaaaa" />
<TextBlock Text="a aa aaa aaaa aaaaa aaaaaa aaaaaaa aaaaaaaa aaaaaaaaa" />
<TextBlock Text="a aa aaa aaaa aaaaa aaaaaa aaaaaaa aaaaaaaa aaaaaaaaa" />
<TextBlock Text="a aa aaa aaaa aaaaa aaaaaa aaaaaaa aaaaaaaa aaaaaaaaa" />
</StackPanel>
<y:TimeLineControl x:Name="t1" Grid.Row="1" />
<TextBlock Grid.Row="2" Text="Test" />
</Grid>
</ScrollViewer> </ScrollViewer>
</DockPanel> </DockPanel>
</UserControl> </UserControl>

70
src/AddIns/Misc/Profiler/Frontend/Controls/ExtendedTimeLineControl.xaml.cs

@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text; using System.Text;
using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Data; using System.Windows.Data;
@ -11,6 +13,8 @@ using System.Windows.Media.Imaging;
using System.Windows.Navigation; using System.Windows.Navigation;
using System.Windows.Shapes; using System.Windows.Shapes;
using ICSharpCode.Profiler.Controller.Data;
namespace ICSharpCode.Profiler.Controls namespace ICSharpCode.Profiler.Controls
{ {
/// <summary> /// <summary>
@ -18,11 +22,77 @@ namespace ICSharpCode.Profiler.Controls
/// </summary> /// </summary>
public partial class ExtendedTimeLineControl : UserControl public partial class ExtendedTimeLineControl : UserControl
{ {
ProfilingDataProvider provider;
public ExtendedTimeLineControl() public ExtendedTimeLineControl()
{ {
InitializeComponent(); InitializeComponent();
this.timeLine.RangeChanged += (sender, e) => { OnRangeChanged(e); };
}
public ProfilingDataProvider Provider
{
get { return this.provider; }
set {
this.provider = value;
this.perfCounterList.ItemsSource = this.provider.GetPerformanceCounters();
this.perfCounterList.SelectedIndex = 0;
Update();
}
}
public int SelectedStartIndex {
get { return this.timeLine.SelectedStartIndex; }
set { this.timeLine.SelectedStartIndex = value; }
}
public int SelectedEndIndex {
get { return this.timeLine.SelectedEndIndex; }
set { this.timeLine.SelectedEndIndex = value; }
}
public event EventHandler<RangeEventArgs> RangeChanged;
protected virtual void OnRangeChanged(RangeEventArgs e)
{
if (RangeChanged != null)
{
RangeChanged(this, e);
}
} }
void Update()
{
this.timeLine.ValuesList.Clear();
var selectedPerformanceCounter = this.perfCounterList.SelectedItem as PerformanceCounterDescriptor;
if (selectedPerformanceCounter == null)
return;
List<TimeLineSegment> segments = new List<TimeLineSegment>();
var values = this.provider.GetPerformanceCounterValues(this.perfCounterList.SelectedIndex);
var markers = this.provider.DataSets.Select(item => item.IsFirst).ToArray();
this.timeLine.MaxValue = selectedPerformanceCounter.MaxValue ?? (values.Any() ? values.Max() : 0);
this.maxLabel.Text = (selectedPerformanceCounter.MaxValue ?? (values.Any() ? values.Max() : 0)).ToString("0");
this.minLabel.Text = (selectedPerformanceCounter.MinValue ?? (values.Any() ? values.Min() : 0)).ToString("0");
this.unitLabel.Text = this.timeLine.Unit = selectedPerformanceCounter.Unit;
for (int i = 0; i < values.Length; i++)
segments.Add(new TimeLineSegment() { Value = values[i], TimeOffset = 0, DisplayMarker = markers[i] });
this.timeLine.ValuesList.AddRange(segments);
}
void PerfCounterListSelectionChanged(object sender, SelectionChangedEventArgs e)
{
Update();
}
} }
} }

14
src/AddIns/Misc/Profiler/Frontend/Controls/QueryView.xaml

@ -331,19 +331,19 @@
</Style> </Style>
</ListView.ItemContainerStyle> </ListView.ItemContainerStyle>
</local:TreeListView> </local:TreeListView>
<ToolBar Height="26" Grid.Row="0" Grid.ColumnSpan="2"> <StackPanel Grid.Row="0" Grid.ColumnSpan="2" Orientation="Horizontal">
<Label Content="{Binding Translation.SearchLabelText, ElementName=queryView}" /> <Label Content="{Binding Translation.SearchLabelText, ElementName=queryView}" Margin="3" VerticalAlignment="Center" />
<TextBox Name="txtSearch" Width="150" KeyDown="txtSearchKeyDown" /> <TextBox Name="txtSearch" Width="150" KeyDown="txtSearchKeyDown" VerticalAlignment="Center" />
<CheckBox Content="{Binding Translation.ShowQueryBarText, ElementName=queryView}" IsChecked="{Binding ShowQueryItems}" /> <ToggleButton Content="{Binding Translation.ShowQueryBarText, ElementName=queryView}" IsChecked="{Binding ShowQueryItems}" Margin="3" VerticalAlignment="Center" />
<Button Content="{Binding Translation.ExpandHotPathSubtreeText, ElementName=queryView}" Name="btnExpandHotPathSubtree" Click="BtnExpandHotPathSubtreeClick" /> <Button Content="{Binding Translation.ExpandHotPathSubtreeText, ElementName=queryView}" Name="btnExpandHotPathSubtree" Click="BtnExpandHotPathSubtreeClick" Margin="3" VerticalAlignment="Center" />
<ComboBox Name="visibleColumnsSelection" Style="{StaticResource styleColumnsComboBox}"> <ComboBox Name="visibleColumnsSelection" Style="{StaticResource styleColumnsComboBox}" Margin="3" VerticalAlignment="Center">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
<DataTemplate> <DataTemplate>
<CheckBox IsChecked="{Binding IsVisible, Mode=TwoWay}" Content="{Binding Column.Header}" /> <CheckBox IsChecked="{Binding IsVisible, Mode=TwoWay}" Content="{Binding Column.Header}" />
</DataTemplate> </DataTemplate>
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>
</ToolBar> </StackPanel>
<DockPanel Name="queryPanel" Visibility="{Binding ShowQueryItems, Converter={StaticResource VisibilityConverter}}" Grid.Row="1" Grid.ColumnSpan="2"> <DockPanel Name="queryPanel" Visibility="{Binding ShowQueryItems, Converter={StaticResource VisibilityConverter}}" Grid.Row="1" Grid.ColumnSpan="2">
<Button Name="btnExecuteQuery" DockPanel.Dock="Right" Click="btnExecuteQueryClick" Content="{Binding Translation.ExecuteQueryText, ElementName=queryView}" /> <Button Name="btnExecuteQuery" DockPanel.Dock="Right" Click="btnExecuteQueryClick" Content="{Binding Translation.ExecuteQueryText, ElementName=queryView}" />
<TextBox Name="txtQuery" TextChanged="txtQueryTextChanged" KeyDown="txtQueryKeyDown" /> <TextBox Name="txtQuery" TextChanged="txtQueryTextChanged" KeyDown="txtQueryKeyDown" />

10
src/AddIns/Misc/Profiler/Frontend/Controls/QueryView.xaml.cs

@ -240,16 +240,6 @@ namespace ICSharpCode.Profiler.Controls
if (!this.isDirty || this.Provider == null) if (!this.isDirty || this.Provider == null)
return; return;
if (RangeStart > RangeEnd) {
int help = RangeStart;
RangeStart = RangeEnd;
RangeEnd = help;
}
if ((RangeEnd < 0 && RangeEnd >= Provider.DataSets.Count) &&
(RangeStart < 0 && RangeStart >= Provider.DataSets.Count))
return;
if (Provider.DataSets.Count == 0) if (Provider.DataSets.Count == 0)
return; return;

209
src/AddIns/Misc/Profiler/Frontend/Controls/TimeLineControl.cs

@ -12,18 +12,21 @@ using System.Diagnostics;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Documents; using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Threading;
namespace ICSharpCode.Profiler.Controls namespace ICSharpCode.Profiler.Controls
{ {
public struct TimeLineInfo { public class TimeLineSegment {
public double value; public float Value { get; set; }
public bool displayMarker; public bool DisplayMarker { get; set; }
public long TimeOffset { get; set; }
} }
public class TimeLineControl : FrameworkElement public class TimeLineControl : FrameworkElement
{ {
ObservableCollection<TimeLineInfo> valuesList; ObservableCollection<TimeLineSegment> valuesList;
int selectedStartIndex, selectedEndIndex; int selectedStartIndex, selectedEndIndex;
double pieceWidth; double pieceWidth;
@ -37,6 +40,12 @@ namespace ICSharpCode.Profiler.Controls
} }
} }
public int SelectedPerformanceCounter { get; set; }
public float MaxValue { get; set; }
public string Unit { get; set; }
public int SelectedEndIndex public int SelectedEndIndex
{ {
get { return selectedEndIndex; } get { return selectedEndIndex; }
@ -59,23 +68,32 @@ namespace ICSharpCode.Profiler.Controls
} }
} }
public ObservableCollection<TimeLineInfo> ValuesList public ObservableCollection<TimeLineSegment> ValuesList
{ {
get { return valuesList; } get { return valuesList; }
} }
public TimeLineControl() public TimeLineControl()
{ {
this.valuesList = new ObservableCollection<TimeLineInfo>(); this.valuesList = new ObservableCollection<TimeLineSegment>();
this.valuesList.CollectionChanged += delegate { this.InvalidateMeasure(); this.InvalidateVisual(); }; this.valuesList.CollectionChanged += delegate { this.InvalidateMeasure(); this.InvalidateVisual(); };
} }
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
return new Size(1, base.MeasureOverride(availableSize).Height + 10); this.pieceWidth = 25;
Size calculatedSize = base.MeasureOverride(availableSize);
return new Size(Math.Max(this.pieceWidth * this.valuesList.Count + 1, calculatedSize.Width), calculatedSize.Height + 10);
}
protected override void OnToolTipOpening(ToolTipEventArgs e)
{
Console.WriteLine("tooltip");
base.OnToolTipOpening(e);
} }
const int offset = 15; const int offset = 0;
const int offsetFromTop = 40;
protected override void OnRender(DrawingContext drawingContext) protected override void OnRender(DrawingContext drawingContext)
{ {
@ -84,22 +102,22 @@ namespace ICSharpCode.Profiler.Controls
if (this.valuesList.Count == 0) if (this.valuesList.Count == 0)
return; return;
this.pieceWidth = (this.RenderSize.Width - offset) / (double)this.valuesList.Count; double oldX = offsetFromTop, oldY = this.RenderSize.Height;
double oldX = 0, oldY = this.RenderSize.Height - offset;
StreamGeometry geometry = new StreamGeometry(); StreamGeometry geometry = new StreamGeometry();
using (StreamGeometryContext ctx = geometry.Open()) using (StreamGeometryContext ctx = geometry.Open())
{ {
ctx.BeginFigure(new Point(offset, this.RenderSize.Height - offset), true, true); ctx.BeginFigure(new Point(0, this.RenderSize.Height), true, true);
List<Point> points = new List<Point>(); List<Point> points = new List<Point>();
double maxHeight = this.RenderSize.Height - offsetFromTop;
for (int i = 0; i < this.valuesList.Count; i++) for (int i = 0; i < this.valuesList.Count; i++)
{ {
double x = this.pieceWidth / 2.0 + this.pieceWidth * i + offset; double x = this.pieceWidth / 2.0 + this.pieceWidth * i;
double y = this.RenderSize.Height - this.valuesList[i].value * (this.RenderSize.Height - offset) - offset; double y = offsetFromTop + (maxHeight - maxHeight * (this.valuesList[i].Value / this.MaxValue));
points.Add(new Point(x, y)); points.Add(new Point(x, y));
@ -107,7 +125,7 @@ namespace ICSharpCode.Profiler.Controls
oldY = y; oldY = y;
} }
points.Add(new Point(oldX, this.RenderSize.Height - offset)); points.Add(new Point(oldX, offsetFromTop + this.RenderSize.Height));
ctx.PolyLineTo(points, true, true); ctx.PolyLineTo(points, true, true);
} }
@ -116,50 +134,40 @@ namespace ICSharpCode.Profiler.Controls
Brush b = new LinearGradientBrush(Colors.Red, Colors.Orange, 90); Brush b = new LinearGradientBrush(Colors.Red, Colors.Orange, 90);
drawingContext.DrawRectangle(Brushes.White, new Pen(Brushes.White, 1), new Rect(new Point(0, 0), this.RenderSize)); drawingContext.DrawRectangle(Brushes.White, new Pen(Brushes.White, 1), new Rect(new Point(0, offsetFromTop), this.RenderSize));
drawingContext.DrawLine(new Pen(Brushes.Black, 1), new Point(offset / 2, this.RenderSize.Height - offset / 2), new Point(this.RenderSize.Width - offset, this.RenderSize.Height - offset / 2));
drawingContext.DrawLine(new Pen(Brushes.Black, 1), new Point(offset / 2, 0), new Point(offset / 2, this.RenderSize.Height - offset / 2));
var p = new Pen(Brushes.DarkRed, 2); var p = new Pen(Brushes.DarkRed, 2);
for (int i = 0; i < this.valuesList.Count; i++) {
drawingContext.DrawLine(new Pen(Brushes.Black, 1),
new Point(offset + pieceWidth / 2 + pieceWidth * i, this.RenderSize.Height - offset / 4),
new Point(offset + pieceWidth / 2 + pieceWidth * i, this.RenderSize.Height - (offset / 4 * 3 + 3)));
if (this.valuesList[i].displayMarker) {
drawingContext.DrawLine(p, new Point(offset + pieceWidth * i, 0),
new Point(offset + pieceWidth * i, this.RenderSize.Height - offset));
}
}
drawingContext.DrawGeometry(b, new Pen(b, 3), geometry); drawingContext.DrawGeometry(b, new Pen(b, 3), geometry);
for (int i = 0; i < this.valuesList.Count; i++) { for (int i = 0; i < this.valuesList.Count; i++) {
if (this.valuesList[i].displayMarker) if (this.valuesList[i].DisplayMarker)
drawingContext.DrawLine(p, new Point(offset + pieceWidth * i, 0), drawingContext.DrawLine(p, new Point(pieceWidth * i, offsetFromTop),
new Point(offset + pieceWidth * i, this.RenderSize.Height - offset)); new Point(pieceWidth * i, this.RenderSize.Height));
} }
drawingContext.DrawRectangle( drawingContext.DrawRectangle(
new SolidColorBrush(Color.FromArgb(64, Colors.CornflowerBlue.R, new SolidColorBrush(Color.FromArgb(64, Colors.Blue.R, Colors.Blue.G, Colors.Blue.B)),
Colors.CornflowerBlue.G, Colors.CornflowerBlue.B)),
new Pen(Brushes.Blue, 1), new Pen(Brushes.Blue, 1),
new Rect( new Rect(
new Point(this.selectedStartIndex * this.pieceWidth + offset, 0), new Point(Math.Min(this.selectedStartIndex, this.selectedEndIndex) * pieceWidth + offset, 0),
new Point(this.selectedEndIndex * this.pieceWidth + offset, this.RenderSize.Height - offset) new Point(Math.Max(this.selectedStartIndex, this.selectedEndIndex) * pieceWidth + offset + pieceWidth, this.RenderSize.Height - offset)
) )
); );
} }
protected override void OnMouseUp(System.Windows.Input.MouseButtonEventArgs e) protected override void OnMouseUp(System.Windows.Input.MouseButtonEventArgs e)
{ {
int index = TransformToIndex(e.GetPosition(this)); bool valid;
index = (index < 0) ? 0 : index; int index = TransformToIndex(e.GetPosition(this), out valid);
index = (index > this.valuesList.Count) ? this.valuesList.Count : index;
this.selectedEndIndex = index; if (index < this.selectedStartIndex) {
this.selectedEndIndex = this.selectedStartIndex;
this.selectedStartIndex = index;
} else
this.selectedEndIndex = index;
Console.WriteLine("start: {0} end: {1} count: {2}", SelectedStartIndex, SelectedEndIndex, valuesList.Count);
this.InvalidateMeasure(); this.InvalidateMeasure();
this.InvalidateVisual(); this.InvalidateVisual();
@ -169,43 +177,128 @@ namespace ICSharpCode.Profiler.Controls
protected override void OnMouseDown(System.Windows.Input.MouseButtonEventArgs e) protected override void OnMouseDown(System.Windows.Input.MouseButtonEventArgs e)
{ {
this.CaptureMouse(); this.CaptureMouse();
bool valid;
int index = TransformToIndex(e.GetPosition(this)); int index = TransformToIndex(e.GetPosition(this), out valid);
index = (index < 0) ? 0 : index;
index = (index > this.valuesList.Count) ? this.valuesList.Count : index;
this.selectedStartIndex = this.selectedEndIndex = index; this.selectedStartIndex = this.selectedEndIndex = index;
this.InvalidateMeasure(); this.InvalidateMeasure();
this.InvalidateVisual(); this.InvalidateVisual();
} }
protected override void OnMouseMove(System.Windows.Input.MouseEventArgs e) ToolTip tooltip;
{
int index = TransformToIndex(e.GetPosition(this));
index = (index < 0) ? 0 : index; void HandleMovement(MouseEventArgs e)
index = (index > this.valuesList.Count) ? this.valuesList.Count : index; {
bool valid;
int index = TransformToIndex(e.GetPosition(this), out valid);
if (e.LeftButton == System.Windows.Input.MouseButtonState.Pressed) if (e.LeftButton == MouseButtonState.Pressed) {
{
this.selectedEndIndex = index; this.selectedEndIndex = index;
this.InvalidateMeasure(); this.InvalidateMeasure();
this.InvalidateVisual(); this.InvalidateVisual();
} else if (tooltip == null && valid) {
tooltip = new ToolTip();
tooltip.Content = "Value: " + this.valuesList[index].Value.ToString("0.00") + " " + this.Unit;
tooltip .IsOpen = true;
} }
} }
protected override void OnLostMouseCapture(System.Windows.Input.MouseEventArgs e) protected override void OnLostMouseCapture(MouseEventArgs e)
{ {
base.OnLostMouseCapture(e); base.OnLostMouseCapture(e);
if (this.selectedEndIndex == this.selectedStartIndex)
this.selectedEndIndex++;
OnRangeChanged(new RangeEventArgs(this.selectedStartIndex, this.selectedEndIndex)); OnRangeChanged(new RangeEventArgs(this.selectedStartIndex, this.selectedEndIndex));
Debug.Print("lost capture");
} }
private int TransformToIndex(Point point) int TransformToIndex(Point point, out bool valid)
{
int value = (int)Math.Floor(point.X / this.pieceWidth);
valid = (0 <= value && value <= this.valuesList.Count - 1);
return Math.Min(Math.Max(0, value), this.valuesList.Count - 1);
}
#region MouseHover
/// <summary>
/// The MouseHover event.
/// </summary>
public static readonly RoutedEvent MouseHoverEvent =
EventManager.RegisterRoutedEvent("MouseHover", RoutingStrategy.Bubble,
typeof(MouseEventHandler), typeof(TimeLineControl));
/// <summary>
/// The MouseHoverStopped event.
/// </summary>
public static readonly RoutedEvent MouseHoverStoppedEvent =
EventManager.RegisterRoutedEvent("MouseHoverStopped", RoutingStrategy.Bubble,
typeof(MouseEventHandler), typeof(TimeLineControl));
/// <summary>
/// Occurs when the mouse has hovered over a fixed location for some time.
/// </summary>
public event MouseEventHandler MouseHover {
add { AddHandler(MouseHoverEvent, value); }
remove { RemoveHandler(MouseHoverEvent, value); }
}
/// <summary>
/// Occurs when the mouse had previously hovered but now started moving again.
/// </summary>
public event MouseEventHandler MouseHoverStopped {
add { AddHandler(MouseHoverStoppedEvent, value); }
remove { RemoveHandler(MouseHoverStoppedEvent, value); }
}
DispatcherTimer mouseHoverTimer;
Point mouseHoverStartPoint;
MouseEventArgs mouseHoverLastEventArgs;
/// <inheritdoc/>
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
Point newPosition = e.GetPosition(this);
Vector mouseMovement = mouseHoverStartPoint - newPosition;
if (Math.Abs(mouseMovement.X) > SystemParameters.MouseHoverWidth
|| Math.Abs(mouseMovement.Y) > SystemParameters.MouseHoverHeight)
{
StopHovering();
mouseHoverStartPoint = newPosition;
mouseHoverLastEventArgs = e;
mouseHoverTimer = new DispatcherTimer(SystemParameters.MouseHoverTime, DispatcherPriority.Background,
OnMouseHoverTimerElapsed, this.Dispatcher);
mouseHoverTimer.Start();
}
HandleMovement(e);
// do not set e.Handled - allow others to also handle MouseMove
}
/// <inheritdoc/>
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
StopHovering();
// do not set e.Handled - allow others to also handle MouseLeave
}
void StopHovering()
{
if (mouseHoverTimer != null) {
mouseHoverTimer.Stop();
mouseHoverTimer = null;
}
if (this.tooltip != null) {
this.tooltip.IsOpen = false;
this.tooltip = null;
}
}
void OnMouseHoverTimerElapsed(object sender, EventArgs e)
{ {
return (int)Math.Round(point.X / this.pieceWidth); mouseHoverTimer.Stop();
mouseHoverTimer = null;
} }
#endregion
} }
} }

1
src/AddIns/Misc/Profiler/Tests/Profiler.Tests/Controller/Data/CallTreeNodeStub.cs

@ -135,7 +135,6 @@ namespace Profiler.Tests.Controller.Data
public class DataSetStub : IProfilingDataSet public class DataSetStub : IProfilingDataSet
{ {
public double CpuUsage { get; set; }
public bool IsFirst { get; set; } public bool IsFirst { get; set; }
public CallTreeNode RootNode { get; set; } public CallTreeNode RootNode { get; set; }
} }

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

@ -47,7 +47,7 @@ namespace Profiler.Tests.Controller.Data
} }
} }
}; };
writer.WriteDataSet(new DataSetStub { CpuUsage = 0.3, IsFirst = true, RootNode = dataSet }); writer.WriteDataSet(new DataSetStub { IsFirst = true, RootNode = dataSet });
dataSet = new CallTreeNodeStub { dataSet = new CallTreeNodeStub {
NameMappingValue = method0, NameMappingValue = method0,
IsActiveAtStartValue = true, IsActiveAtStartValue = true,
@ -65,7 +65,7 @@ namespace Profiler.Tests.Controller.Data
} }
} }
}; };
writer.WriteDataSet(new DataSetStub { CpuUsage = 0.4, IsFirst = false, RootNode = dataSet }); writer.WriteDataSet(new DataSetStub { IsFirst = false, RootNode = dataSet });
dataSet = new CallTreeNodeStub { dataSet = new CallTreeNodeStub {
NameMappingValue = method0, NameMappingValue = method0,
IsActiveAtStartValue = true, IsActiveAtStartValue = true,
@ -85,7 +85,7 @@ namespace Profiler.Tests.Controller.Data
} }
} }
}; };
writer.WriteDataSet(new DataSetStub { CpuUsage = 0.1, IsFirst = false, RootNode = dataSet }); writer.WriteDataSet(new DataSetStub { IsFirst = false, RootNode = dataSet });
writer.Close(); writer.Close();
} }
provider = ProfilingDataSQLiteProvider.UpgradeFromOldVersion(databaseFileName); provider = ProfilingDataSQLiteProvider.UpgradeFromOldVersion(databaseFileName);
@ -102,9 +102,6 @@ namespace Profiler.Tests.Controller.Data
public void TestDataSets() public void TestDataSets()
{ {
Assert.AreEqual(3, provider.DataSets.Count); 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.IsTrue(provider.DataSets[0].IsFirst);
Assert.IsFalse(provider.DataSets[1].IsFirst); Assert.IsFalse(provider.DataSets[1].IsFirst);

Loading…
Cancel
Save