You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
823 lines
23 KiB
823 lines
23 KiB
// <file> |
|
// <copyright see="prj:///doc/copyright.txt"/> |
|
// <license see="prj:///doc/license.txt"/> |
|
// <owner name="Siegfried Pammer" email="sie_pam@gmx.at"/> |
|
// <version>$Revision$</version> |
|
// </file> |
|
|
|
using System; |
|
using System.Linq; |
|
using System.Collections.Generic; |
|
using System.Diagnostics; |
|
using System.Globalization; |
|
using System.IO; |
|
using System.Runtime.InteropServices; |
|
using System.Text; |
|
using System.Threading; |
|
|
|
using ICSharpCode.Profiler.Controller.Data; |
|
using ICSharpCode.Profiler.Interprocess; |
|
using Microsoft.Win32; |
|
|
|
namespace ICSharpCode.Profiler.Controller |
|
{ |
|
/// <summary> |
|
/// The core class of the profiler. |
|
/// </summary> |
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces")] |
|
public unsafe sealed partial class Profiler : IDisposable |
|
{ |
|
[DllImport("kernel32", EntryPoint = "RtlZeroMemory"), System.Security.SuppressUnmanagedCodeSecurityAttribute] |
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] |
|
static extern void ZeroMemory(IntPtr dest, IntPtr size); |
|
|
|
[DllImport("Hook32.dll", EntryPoint = "rdtsc"), System.Security.SuppressUnmanagedCodeSecurityAttribute] |
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] |
|
static extern void Rdtsc32(out ulong value); |
|
|
|
static ulong GetRdtsc() |
|
{ |
|
ulong value; |
|
if (UIntPtr.Size == 8) |
|
Rdtsc64(out value); |
|
else |
|
Rdtsc32(out value); |
|
return value; |
|
} |
|
|
|
bool is64Bit; |
|
bool isRunning; |
|
volatile bool stopDC; |
|
|
|
/// <summary> |
|
/// Gets whether the profiler is running inside a 64-bit profilee process or not. |
|
/// </summary> |
|
public bool Is64Bit |
|
{ |
|
get { return is64Bit; } |
|
} |
|
|
|
/// <summary> |
|
/// Gets whether the profiler is running not. |
|
/// </summary> |
|
public bool IsRunning |
|
{ |
|
get { return isRunning; } |
|
} |
|
|
|
/// <summary> |
|
/// Gets the processor frequency read from the registry of the computer the profiler is running on. |
|
/// </summary> |
|
public int ProcessorFrequency |
|
{ |
|
get { |
|
if (this.is64Bit) |
|
return this.memHeader64->ProcessorFrequency; |
|
else |
|
return this.memHeader32->ProcessorFrequency; |
|
} |
|
} |
|
|
|
readonly string SharedMemoryId = "Local\\Profiler.SharedMemory.{" + Guid.NewGuid().ToString().ToUpperInvariant() + "}"; |
|
readonly string MutexId = "Local\\Profiler.Mutex.{" + Guid.NewGuid().ToString().ToUpperInvariant() + "}"; |
|
readonly string AccessEventId = "Local\\Profiler.Events.{" + Guid.NewGuid().ToString().ToUpperInvariant() + "}"; |
|
|
|
/// <summary> |
|
/// The Guid of the CProfiler class in the Hook. |
|
/// </summary> |
|
public const string ProfilerGuid = "{E7E2C111-3471-4AC7-B278-11F4C26EDBCF}"; |
|
|
|
const int bufferSize = 4 * 1024; // 4 kb |
|
const int threadDataSize = 16 * 1024 * 1024; // 16 mb |
|
|
|
Process profilee; |
|
ProcessStartInfo psi; |
|
Mutex threadListMutex; |
|
EventWaitHandle accessEventHandle; |
|
Thread logger; |
|
Thread dataCollector; |
|
UnmanagedCircularBuffer nativeToManagedBuffer; |
|
IProfilingDataWriter dataWriter; |
|
|
|
Dictionary<string, PerformanceCounter> performanceCounters; |
|
PerformanceCounter cpuUsageCounter; |
|
|
|
/// <summary> |
|
/// The currently used data provider. |
|
/// </summary> |
|
public IProfilingDataWriter DataWriter |
|
{ |
|
get { return dataWriter; } |
|
} |
|
|
|
UnmanagedMemory fullView; |
|
|
|
// needs to be stored in member variable to prevent GC from cleaning up the memory mapped file |
|
// before the profilee can open it. |
|
MemoryMappedFile file; |
|
|
|
ProfilerOptions profilerOptions = new ProfilerOptions(); |
|
|
|
/// <summary> |
|
/// Gets all settings used by this profiler instance. |
|
/// </summary> |
|
public ProfilerOptions ProfilerOptions { |
|
get { return profilerOptions; } |
|
} |
|
|
|
SharedMemoryHeader32* memHeader32; |
|
SharedMemoryHeader64* memHeader64; |
|
|
|
StringBuilder profilerOutput; |
|
|
|
/// <summary> |
|
/// Invoked when the Hook could not be registered as a COM component. |
|
/// </summary> |
|
public event EventHandler RegisterFailed; |
|
|
|
void OnRegisterFailed(EventArgs e) |
|
{ |
|
if (RegisterFailed != null) { |
|
RegisterFailed(this, e); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Invoked when the Hook could not be deregistered from COM. |
|
/// </summary> |
|
public event EventHandler DeregisterFailed; |
|
|
|
void OnDeregisterFailed(EventArgs e) |
|
{ |
|
if (DeregisterFailed != null) { |
|
DeregisterFailed(this, e); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Invoked when any new output has been sent to the Controller. |
|
/// </summary> |
|
public event EventHandler OutputUpdated; |
|
|
|
/// <summary> |
|
/// Invoked when the session has started. |
|
/// </summary> |
|
public event EventHandler SessionStarted; |
|
|
|
void OnSessionStarted(EventArgs e) |
|
{ |
|
if (SessionStarted != null) { |
|
SessionStarted(this, e); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Invoked when the session has ended. |
|
/// </summary> |
|
public event EventHandler SessionEnded; |
|
|
|
void OnSessionEnded(EventArgs e) |
|
{ |
|
if (SessionEnded != null) { |
|
SessionEnded(this, e); |
|
} |
|
} |
|
|
|
void OnOutputUpdated(EventArgs e) |
|
{ |
|
if (OutputUpdated != null) { |
|
OutputUpdated(this, e); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Contains the whole Profiler output during the last session. |
|
/// </summary> |
|
public string ProfilerOutput |
|
{ |
|
get { return profilerOutput.ToString(); } |
|
} |
|
|
|
/// <summary> |
|
/// Creates a new profiler using the path to an executable to profile and a data writer. |
|
/// </summary> |
|
public Profiler(string pathToExecutable, IProfilingDataWriter dataWriter, ProfilerOptions options) |
|
: this(new ProcessStartInfo(pathToExecutable), dataWriter, options) |
|
{ |
|
if (!File.Exists(pathToExecutable)) |
|
throw new FileNotFoundException("File not found!", pathToExecutable); |
|
|
|
this.psi.WorkingDirectory = Path.GetDirectoryName(pathToExecutable); |
|
} |
|
|
|
/// <summary> |
|
/// Creates a new profiler using a process start info of an executable and a data writer. |
|
/// </summary> |
|
public Profiler(ProcessStartInfo info, IProfilingDataWriter dataWriter, ProfilerOptions options) |
|
{ |
|
if (dataWriter == null) |
|
throw new ArgumentNullException("dataWriter"); |
|
|
|
if (info == null) |
|
throw new ArgumentNullException("info"); |
|
|
|
if (!DetectBinaryType.IsDotNetExecutable(info.FileName)) |
|
throw new ProfilerException("File is not a valid .NET executable file!"); |
|
|
|
this.profilerOptions = options; |
|
|
|
this.is64Bit = DetectBinaryType.RunsAs64Bit(info.FileName); |
|
|
|
this.profilerOutput = new StringBuilder(); |
|
this.performanceCounters = new Dictionary<string, PerformanceCounter>(); |
|
this.dataWriter = dataWriter; |
|
|
|
this.threadListMutex = new Mutex(false, MutexId); |
|
this.accessEventHandle = new EventWaitHandle(true, EventResetMode.ManualReset, this.AccessEventId); |
|
|
|
this.psi = info; |
|
this.psi.UseShellExecute = false; // needed to get env vars working! |
|
this.psi.EnvironmentVariables["SharedMemoryName"] = SharedMemoryId; |
|
this.psi.EnvironmentVariables["MutexName"] = MutexId; // mutex for process pause/continue sychronization |
|
this.psi.EnvironmentVariables["AccessEventName"] = AccessEventId; // name for access event of controller |
|
this.psi.EnvironmentVariables["COR_ENABLE_PROFILING"] = "1"; // enable profiling; 0 = disable |
|
this.psi.EnvironmentVariables["COR_PROFILER"] = ProfilerGuid; // GUID for the profiler |
|
|
|
file = MemoryMappedFile.CreateSharedMemory(SharedMemoryId, profilerOptions.SharedMemorySize); |
|
|
|
fullView = file.MapView(0, profilerOptions.SharedMemorySize); |
|
|
|
this.dataWriter.ProcessorFrequency = GetProcessorFrequency(); |
|
|
|
this.logger = new Thread(new ParameterizedThreadStart(Logging)); |
|
this.logger.Name = "Logger"; |
|
this.logger.IsBackground = true; // don't let the logger thread prevent our process from exiting |
|
|
|
this.dataCollector = new Thread(new ThreadStart(DataCollection)); |
|
this.dataCollector.Name = "DataCollector"; |
|
this.dataCollector.IsBackground = true; |
|
} |
|
|
|
void InitializeHeader32() |
|
{ |
|
memHeader32 = (SharedMemoryHeader32*)fullView.Pointer; |
|
#if DEBUG |
|
// '~DBG' |
|
memHeader32->Magic = 0x7e444247; |
|
#else |
|
// '~SM1' |
|
memHeader32->Magic = 0x7e534d31; |
|
#endif |
|
memHeader32->TotalLength = profilerOptions.SharedMemorySize; |
|
memHeader32->NativeToManagedBufferOffset = Align(sizeof(SharedMemoryHeader32)); |
|
memHeader32->ThreadDataOffset = Align(memHeader32->NativeToManagedBufferOffset + bufferSize); |
|
memHeader32->ThreadDataLength = threadDataSize; |
|
memHeader32->HeapOffset = Align(memHeader32->ThreadDataOffset + threadDataSize); |
|
memHeader32->HeapLength = profilerOptions.SharedMemorySize - memHeader32->HeapOffset; |
|
memHeader32->ProcessorFrequency = GetProcessorFrequency(); |
|
memHeader32->DoNotProfileDotnetInternals = profilerOptions.DoNotProfileDotNetInternals; |
|
memHeader32->CombineRecursiveFunction = profilerOptions.CombineRecursiveFunction; |
|
|
|
if ((Int32)(fullView.Pointer + memHeader32->HeapOffset) % 8 != 0) { |
|
throw new DataMisalignedException("Heap is not aligned properly: " + ((Int32)(fullView.Pointer + memHeader32->HeapOffset)).ToString(CultureInfo.InvariantCulture) + "!"); |
|
} |
|
} |
|
|
|
static int Align(int address) |
|
{ |
|
return (address + 7) & ~(Int32)7; |
|
} |
|
|
|
static int GetProcessorFrequency() |
|
{ |
|
int freq = (int)Registry.GetValue(@"HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor\0", "~MHz", -1); |
|
if (freq == -1) |
|
throw new ArgumentException("The Processor Frequency could not be read!"); |
|
return freq; |
|
} |
|
|
|
void DataCollection() |
|
{ |
|
while (!stopDC) { |
|
this.Pause(); |
|
this.threadListMutex.WaitOne(); |
|
|
|
Debug.Print("running DC " + (is64Bit ? "x64" : "x86")); |
|
|
|
if (this.is64Bit) |
|
CollectData64(); |
|
else |
|
CollectData32(); |
|
|
|
this.threadListMutex.ReleaseMutex(); |
|
this.Continue(); |
|
Thread.Sleep(500); |
|
} |
|
} |
|
|
|
void CollectData32() |
|
{ |
|
if (TranslatePointer(memHeader32->RootFuncInfoAddress) == null) |
|
return; |
|
|
|
ulong now = GetRdtsc(); |
|
|
|
ThreadLocalData32* item = (ThreadLocalData32*)TranslatePointer(this.memHeader32->LastThreadListItem); |
|
|
|
List<Stack<int>> stackList = new List<Stack<int>>(); |
|
|
|
while (item != null) { |
|
StackEntry32* entry = (StackEntry32*)TranslatePointer(item->Stack.Array); |
|
Stack<int> itemIDs = new Stack<int>(); |
|
while (entry != null && entry <= (StackEntry32*)TranslatePointer(item->Stack.TopPointer)) { |
|
FunctionInfo* function = (FunctionInfo*)TranslatePointer(entry->Function); |
|
itemIDs.Push(function->Id); |
|
|
|
function->TimeSpent += now - entry->StartTime; |
|
|
|
entry++; |
|
} |
|
|
|
stackList.Add(itemIDs); |
|
|
|
item = (ThreadLocalData32*)TranslatePointer(item->Predecessor); |
|
} |
|
|
|
this.AddDataset(fullView.Pointer, |
|
memHeader32->NativeAddress + memHeader32->HeapOffset, |
|
memHeader32->Allocator.startPos - memHeader32->NativeAddress, |
|
memHeader32->Allocator.pos - memHeader32->Allocator.startPos, |
|
(cpuUsageCounter == null) ? 0 : cpuUsageCounter.NextValue(), |
|
memHeader32->RootFuncInfoAddress); |
|
|
|
ZeroMemory(new IntPtr(TranslatePointer(memHeader32->Allocator.startPos)), new IntPtr(memHeader32->Allocator.pos - memHeader32->Allocator.startPos)); |
|
|
|
memHeader32->Allocator.pos = memHeader32->Allocator.startPos; |
|
Allocator32.ClearFreeList(&memHeader32->Allocator); |
|
|
|
FunctionInfo* root = CreateFunctionInfo(0, 0, stackList.Count); |
|
|
|
memHeader32->RootFuncInfoAddress = TranslatePointerBack32(root); |
|
|
|
item = (ThreadLocalData32*)TranslatePointer(this.memHeader32->LastThreadListItem); |
|
|
|
now = GetRdtsc(); |
|
|
|
foreach (Stack<int> thread in stackList) { |
|
FunctionInfo* child = null; |
|
|
|
StackEntry32* entry = (StackEntry32*)TranslatePointer(item->Stack.TopPointer); |
|
|
|
while (thread.Count > 0) { |
|
FunctionInfo* stackItem = CreateFunctionInfo(thread.Pop(), 0, child != null ? 1 : 0); |
|
|
|
if (child != null) |
|
FunctionInfo.AddOrUpdateChild32(stackItem, child, this); |
|
|
|
entry->Function = TranslatePointerBack32(stackItem); |
|
entry->StartTime = now; |
|
entry--; |
|
|
|
child = stackItem; |
|
} |
|
|
|
if (child != null) |
|
FunctionInfo.AddOrUpdateChild32(root, child, this); |
|
|
|
item = (ThreadLocalData32*)TranslatePointer(item->Predecessor); |
|
} |
|
} |
|
|
|
unsafe void AddDataset(byte *ptr, TargetProcessPointer nativeStartPosition, long offset, long length, double cpuUsage, TargetProcessPointer nativeRootFuncInfoPosition) |
|
{ |
|
using (DataSet dataSet = new DataSet(this, ptr + offset, length, nativeStartPosition, nativeRootFuncInfoPosition, cpuUsage, is64Bit)) { |
|
lock (this.dataWriter) { |
|
this.dataWriter.WriteDataSet(dataSet); |
|
} |
|
} |
|
} |
|
|
|
FunctionInfo* CreateFunctionInfo(int id, int indexInParent, int elementCount) |
|
{ |
|
int tableSize = 4; |
|
while (elementCount * 4 >= tableSize * 3) |
|
tableSize *= 2; |
|
|
|
// Allocate the child in memory |
|
FunctionInfo* newFunction = (FunctionInfo*)Malloc(sizeof(FunctionInfo) + tableSize * (is64Bit ? 8 : 4)); |
|
|
|
// the allocater takes care of zeroing the memory |
|
|
|
// Set field values |
|
newFunction->Id = id; |
|
newFunction->TimeSpent = (ulong)indexInParent << 56; |
|
newFunction->TimeSpent |= (ulong)1 << 55; |
|
newFunction->FillCount = elementCount; |
|
// Initialize the table |
|
newFunction->LastChildIndex = tableSize - 1; |
|
// Return pointer to the created child |
|
|
|
return newFunction; |
|
} |
|
|
|
unsafe void* Malloc(int bytes) |
|
{ |
|
return is64Bit ? Malloc64(bytes) : Malloc32(bytes); |
|
} |
|
|
|
unsafe void* Malloc32(int bytes) |
|
{ |
|
#if DEBUG |
|
const int debuggingInfoSize = 8; |
|
bytes += debuggingInfoSize; |
|
#endif |
|
void* t = TranslatePointer(memHeader32->Allocator.pos); |
|
memHeader32->Allocator.pos += bytes; |
|
#if DEBUG |
|
t = (byte*)t + debuggingInfoSize; |
|
((Int32*)t)[-1] = bytes - debuggingInfoSize; |
|
#endif |
|
return t; |
|
} |
|
|
|
void Logging(object reader) |
|
{ |
|
Stream stream = reader as Stream; |
|
string readString = ReadString(stream); |
|
while (readString != null) { |
|
readString = ReadString(stream); |
|
if (readString != null && !ProcessCommand(readString)) |
|
this.LogString(readString); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Starts a new profiling session. Prepares IPC and starts the process and logging. |
|
/// </summary> |
|
/// <returns>The process information of the profilee.</returns> |
|
public Process Start() |
|
{ |
|
VerifyAccess(); |
|
|
|
if (is64Bit) |
|
InitializeHeader64(); |
|
else |
|
InitializeHeader32(); |
|
|
|
isRunning = true; |
|
|
|
RegisterProfiler(); |
|
|
|
if (is64Bit) { |
|
nativeToManagedBuffer = UnmanagedCircularBuffer.Create( |
|
new IntPtr(fullView.Pointer + memHeader64->NativeToManagedBufferOffset), bufferSize); |
|
if ((Int64)(fullView.Pointer + memHeader64->NativeToManagedBufferOffset) % 8 != 0) { |
|
throw new DataMisalignedException("nativeToManagedBuffer is not properly aligned!"); |
|
} |
|
} else { |
|
nativeToManagedBuffer = UnmanagedCircularBuffer.Create( |
|
new IntPtr(fullView.Pointer + memHeader32->NativeToManagedBufferOffset), bufferSize); |
|
if ((Int32)(fullView.Pointer + memHeader32->NativeToManagedBufferOffset) % 8 != 0) { |
|
throw new DataMisalignedException("nativeToManagedBuffer is not properly aligned!"); |
|
} |
|
} |
|
|
|
if (is64Bit) |
|
LogString("Using 64-bit hook."); |
|
LogString("Starting process, waiting for profiler hook..."); |
|
|
|
this.profilee = new Process(); |
|
|
|
this.profilee.EnableRaisingEvents = true; |
|
this.profilee.StartInfo = this.psi; |
|
this.profilee.Exited += new EventHandler(ProfileeExited); |
|
|
|
Debug.WriteLine("Launching profiler for " + this.psi.FileName + "..."); |
|
this.profilee.Start(); |
|
|
|
this.cpuUsageCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total", "."); |
|
|
|
this.logger.Start(nativeToManagedBuffer.CreateReadingStream()); |
|
|
|
// GC references currentSession |
|
if (this.profilerOptions.EnableDC) { |
|
this.dataCollector.Start(); |
|
} |
|
|
|
OnSessionStarted(EventArgs.Empty); |
|
return profilee; |
|
} |
|
|
|
/// <summary> |
|
/// Halts the profilee process. |
|
/// </summary> |
|
void Pause() |
|
{ |
|
this.accessEventHandle.Reset(); |
|
if (is64Bit) |
|
this.memHeader64->ExclusiveAccess = 1; |
|
else |
|
this.memHeader32->ExclusiveAccess = 1; |
|
Thread.MemoryBarrier(); |
|
if (is64Bit) |
|
while (!AllThreadsWait64()) ; |
|
else |
|
while (!AllThreadsWait32()) ; |
|
} |
|
|
|
bool AllThreadsWait32() |
|
{ |
|
this.threadListMutex.WaitOne(); |
|
|
|
bool isWaiting = true; |
|
|
|
ThreadLocalData32* item = (ThreadLocalData32*)TranslatePointer(this.memHeader32->LastThreadListItem); |
|
|
|
while (item != null) { |
|
if (item->InLock == 1) |
|
isWaiting = false; |
|
|
|
item = (ThreadLocalData32*)TranslatePointer(item->Predecessor); |
|
} |
|
|
|
this.threadListMutex.ReleaseMutex(); |
|
|
|
return isWaiting; |
|
} |
|
|
|
/// <summary> |
|
/// Continues execution of the profilee. |
|
/// </summary> |
|
void Continue() |
|
{ |
|
if (is64Bit) |
|
this.memHeader64->ExclusiveAccess = 0; |
|
else |
|
this.memHeader32->ExclusiveAccess = 0; |
|
this.accessEventHandle.Set(); |
|
} |
|
|
|
unsafe void ProfileeExited(object sender, EventArgs e) |
|
{ |
|
if (isDisposed) |
|
return; |
|
|
|
DeregisterProfiler(); |
|
|
|
this.stopDC = true; |
|
|
|
Debug.WriteLine("Closing native to managed buffer"); |
|
nativeToManagedBuffer.Close(); |
|
|
|
Debug.WriteLine("Joining logger thread..."); |
|
this.logger.Join(); |
|
Debug.WriteLine("Logger thread joined!"); |
|
if (this.profilerOptions.EnableDC) |
|
this.dataCollector.Join(); |
|
|
|
// unload all counters to prevent exception during last collection! |
|
this.cpuUsageCounter = null; |
|
this.performanceCounters = null; |
|
// Take last shot |
|
if (this.is64Bit) |
|
CollectData64(); |
|
else |
|
CollectData32(); |
|
|
|
isRunning = false; |
|
|
|
this.dataWriter.Close(); |
|
|
|
OnSessionEnded(EventArgs.Empty); |
|
} |
|
|
|
internal void LogString(string text) |
|
{ |
|
this.profilerOutput.AppendLine(text); |
|
OnOutputUpdated(EventArgs.Empty); |
|
} |
|
|
|
internal unsafe void* TranslatePointer32(TargetProcessPointer32 ptr) |
|
{ |
|
if (ptr.Pointer == 0) |
|
return null; |
|
// Use Int32 instead of int because of preprocessor! |
|
unchecked { |
|
Int32 spaceDiff = (Int32)(new IntPtr(fullView.Pointer)) - (Int32)memHeader32->NativeAddress.Pointer; |
|
return new IntPtr((Int32)ptr.Pointer + spaceDiff).ToPointer(); |
|
} |
|
} |
|
|
|
internal unsafe void* TranslatePointer(TargetProcessPointer ptr) |
|
{ |
|
if (this.is64Bit) |
|
return TranslatePointer64(ptr.To64()); |
|
else |
|
return TranslatePointer32(ptr.To32()); |
|
} |
|
|
|
internal unsafe TargetProcessPointer32 TranslatePointerBack32(void* ptr) |
|
{ |
|
// Use Int32 instead of int because of preprocessor! |
|
if (ptr == null) |
|
return new TargetProcessPointer32(); |
|
unchecked { |
|
Int32 spaceDiff = (Int32)(new IntPtr(fullView.Pointer)) - (Int32)memHeader32->NativeAddress.Pointer; |
|
TargetProcessPointer32 pointer = new TargetProcessPointer32(); |
|
pointer.Pointer = (UInt32)((Int32)ptr - spaceDiff); |
|
return pointer; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Verifies that the profiler is not disposed (and the shared memory is still available) |
|
/// </summary> |
|
void VerifyAccess() |
|
{ |
|
if (isDisposed) |
|
throw new ObjectDisposedException("Profiler"); |
|
} |
|
|
|
bool ProcessCommand(string readString) |
|
{ |
|
if (readString == null) |
|
return false; |
|
|
|
if (readString.StartsWith("map ", StringComparison.Ordinal)) { |
|
IList<string> parts = readString.SplitSeparatedString(' '); |
|
|
|
if (parts.Count < 3) |
|
return false; |
|
|
|
int id = int.Parse(parts[1], CultureInfo.InvariantCulture); |
|
string name = parts[4]; |
|
string returnType = parts[3]; |
|
IList<string> parameters = parts.Skip(5).ToList(); |
|
|
|
lock (this.dataWriter) { |
|
this.dataWriter.WriteMappings(new NameMapping[] {new NameMapping(id, returnType, name, parameters)}); |
|
} |
|
|
|
return true; |
|
} else if (readString.StartsWith("mapthread ", StringComparison.Ordinal)) { |
|
IList<string> parts = readString.SplitSeparatedString(' '); |
|
|
|
if (parts.Count < 5) |
|
return false; |
|
|
|
int id = int.Parse(parts[1], CultureInfo.InvariantCulture); |
|
string name = parts[3] + ((string.IsNullOrEmpty(parts[4])) ? "" : " - " + parts[4]); |
|
|
|
lock (this.dataWriter) { |
|
this.dataWriter.WriteMappings(new NameMapping[] {new NameMapping(id, null, name, null)}); |
|
} |
|
|
|
return true; |
|
} else { |
|
if (readString.StartsWith("error-", StringComparison.Ordinal)) { |
|
string[] parts = readString.Split('-'); |
|
if (parts.Length > 1) |
|
throw new ProfilerException(parts[1]); |
|
|
|
throw new ProfilerException("unknown error"); |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
/// <summary> |
|
/// Stops execution of the profilee. |
|
/// </summary> |
|
public void Stop() |
|
{ |
|
try { |
|
if (profilee != null) |
|
profilee.Kill(); |
|
} catch (InvalidOperationException) { } |
|
} |
|
|
|
void RegisterProfiler() |
|
{ |
|
string hookDll = is64Bit ? "Hook64.dll" : "Hook32.dll"; |
|
string path = Path.Combine(Path.GetDirectoryName(typeof(Profiler).Assembly.Location), hookDll); |
|
if (!Registrar.Register(ProfilerGuid, "ProfilerLib", "CProfiler", path, is64Bit)) |
|
OnRegisterFailed(EventArgs.Empty); |
|
} |
|
|
|
void DeregisterProfiler() |
|
{ |
|
if (!Registrar.Deregister(ProfilerGuid, is64Bit)) |
|
OnDeregisterFailed(EventArgs.Empty); |
|
} |
|
|
|
static int ReadInt(Stream s) |
|
{ |
|
int value = 0; |
|
int tmp = s.ReadByte(); |
|
if (tmp < 0) |
|
return -1; |
|
value = tmp; |
|
tmp = s.ReadByte(); |
|
if (tmp < 0) |
|
return -1; |
|
value += (tmp << 8); |
|
tmp = s.ReadByte(); |
|
if (tmp < 0) |
|
return -1; |
|
value += (tmp << 16); |
|
tmp = s.ReadByte(); |
|
if (tmp < 0) |
|
return -1; |
|
value += (tmp << 24); |
|
|
|
return value; |
|
} |
|
|
|
static string ReadString(Stream s) |
|
{ |
|
int len = ReadInt(s); |
|
if (len == -1) |
|
return null; |
|
byte[] bytes = new byte[len]; |
|
int offset = 0; |
|
while (offset < len) { |
|
int r = s.Read(bytes, offset, len - offset); |
|
offset += r; |
|
if (r == 0) |
|
break; |
|
} |
|
return Encoding.Unicode.GetString(bytes, 0, offset); |
|
} |
|
|
|
#region IDisposable Member |
|
bool isDisposed; |
|
|
|
/// <summary> |
|
/// Shuts down the profilee and stops logging and closes and IPC. |
|
/// </summary> |
|
public void Dispose() |
|
{ |
|
if (!isDisposed) { |
|
isDisposed = true; |
|
stopDC = true; |
|
nativeToManagedBuffer.Close(); |
|
try { |
|
this.profilee.Kill(); |
|
} catch (InvalidOperationException) { |
|
// can happen if profilee has already exited |
|
} |
|
if (logger != null && logger.IsAlive) { |
|
this.logger.Join(); |
|
} |
|
|
|
if (dataCollector != null && dataCollector.IsAlive) { |
|
this.dataCollector.Join(); |
|
} |
|
|
|
this.fullView.Dispose(); |
|
this.file.Close(); |
|
|
|
this.threadListMutex.Close(); |
|
this.accessEventHandle.Close(); |
|
|
|
this.dataWriter.Close(); |
|
|
|
this.profilee.Dispose(); |
|
} |
|
} |
|
|
|
#endregion |
|
|
|
#region UnmanagedProfilingDataSet implementation |
|
sealed class DataSet : UnmanagedProfilingDataSet |
|
{ |
|
Profiler profiler; |
|
|
|
public DataSet(Profiler profiler, byte *startPtr, long length, TargetProcessPointer nativeStartPosition, |
|
TargetProcessPointer nativeRootFuncInfoPosition, |
|
double cpuUsage, bool is64Bit) |
|
: base(nativeStartPosition, nativeRootFuncInfoPosition, startPtr, length, cpuUsage, is64Bit) |
|
{ |
|
this.profiler = profiler; |
|
} |
|
|
|
public override NameMapping GetMapping(int nameId) |
|
{ |
|
return new NameMapping(nameId); |
|
} |
|
|
|
public override int ProcessorFrequency { |
|
get { |
|
return this.profiler.ProcessorFrequency; |
|
} |
|
} |
|
|
|
internal unsafe override void* TranslatePointer(TargetProcessPointer ptr) |
|
{ |
|
return this.profiler.TranslatePointer(ptr); |
|
} |
|
} |
|
#endregion |
|
} |
|
} |