Address merge conflicts

This commit is contained in:
jeffschw
2016-06-22 10:24:44 -07:00
2 changed files with 488 additions and 0 deletions
+200
View File
@@ -0,0 +1,200 @@
// Copyright (c) Microsoft Corporation. All rights reserved
// This file is best viewed using outline mode (Ctrl-M Ctrl-O)
//
// This program uses code hyperlinks available as part of the HyperAddin Visual Studio plug-in.
// It is available from http://www.codeplex.com/hyperAddin
// using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.Symbols;
using Microsoft.Diagnostics.Tracing.Etlx;
using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.Tracing.Parsers.Clr;
using Microsoft.Diagnostics.Tracing.Parsers.FrameworkEventSource;
using Microsoft.Diagnostics.Tracing.Parsers.Kernel;
using Microsoft.Diagnostics.Tracing.Parsers.Symbol;
using Microsoft.Diagnostics.Tracing.Parsers.Tpl;
using Microsoft.Diagnostics.Tracing.Stacks;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using Address = System.UInt64;
namespace Microsoft.Diagnostics.Tracing
{
/// <summary>
/// An ProcessComputer is a state machine that tracks information about Processes. Large portions
/// of the code are taken from TraceLog with the goal of long term unification.
///
/// TODO This implementation is poor at idenitfying the ParentPID, 64bitness, and Start/End times
/// </summary>
public class ProcessComputer
{
/// <summary>
/// Gathers relevant details about the processes in the event source
/// </summary>
/// <param name="rawEvents"></param>
/// <param name="log"></param>
public ProcessComputer(TraceEventDispatcher rawEvents, TraceLog log = null)
{
processingDisabled = false;
sampleProfileInterval100ns = 1;
processes = new TraceProcesses(log);
//
// Code lifted from TraceLog.cs to create the TraceProcess
//
// These parsers create state and we want to collect that so we put it on our 'parsers' list that we serialize.
var kernelParser = rawEvents.Kernel;
// Process level events.
kernelParser.ProcessStartGroup += delegate (ProcessTraceData data)
{
this.processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC, data.Opcode == TraceEventOpcode.Start).ProcessStart(data);
// Don't filter them out (not that many, useful for finding command line)
};
kernelParser.ProcessEndGroup += delegate (ProcessTraceData data)
{
this.processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC).ProcessEnd(data);
// Don't filter them out (not that many, useful for finding command line)
};
// Thread level events
kernelParser.ThreadStartGroup += delegate (ThreadTraceData data)
{
this.processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC);
};
kernelParser.ThreadEndGroup += delegate (ThreadTraceData data)
{
this.processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC);
};
kernelParser.ImageGroup += delegate (ImageLoadTraceData data)
{
this.processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC);
};
rawEvents.Clr.LoaderModuleLoad += delegate (ModuleLoadUnloadTraceData data)
{
this.processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC).LoadedModules.ManagedModuleLoadOrUnload(data, true, false);
};
rawEvents.Clr.LoaderModuleUnload += delegate (ModuleLoadUnloadTraceData data)
{
this.processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC).LoadedModules.ManagedModuleLoadOrUnload(data, false, false);
};
rawEvents.Clr.LoaderModuleDCStopV2 += delegate (ModuleLoadUnloadTraceData data)
{
this.processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC).LoadedModules.ManagedModuleLoadOrUnload(data, false, true);
};
var ClrRundownParser = new ClrRundownTraceEventParser(rawEvents);
Action<ModuleLoadUnloadTraceData> onLoaderRundown = delegate (ModuleLoadUnloadTraceData data)
{
this.processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC).LoadedModules.ManagedModuleLoadOrUnload(data, false, true);
};
ClrRundownParser.LoaderModuleDCStop += onLoaderRundown;
ClrRundownParser.LoaderModuleDCStart += onLoaderRundown;
Action<MethodLoadUnloadVerboseTraceData> onMethodStart = delegate (MethodLoadUnloadVerboseTraceData data)
{
if (data.IsJitted)
{
TraceProcess process = this.processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC);
}
};
rawEvents.Clr.MethodLoadVerbose += onMethodStart;
rawEvents.Clr.MethodDCStartVerboseV2 += onMethodStart;
ClrRundownParser.MethodDCStartVerbose += onMethodStart;
Action<ClrStackWalkTraceData> clrStackWalk = delegate (ClrStackWalkTraceData data)
{
// Avoid creating data structures for events we will throw away
if (processingDisabled)
return;
var process = Processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC);
};
rawEvents.Clr.ClrStackWalk += clrStackWalk;
Action<RuntimeInformationTraceData> doAtRuntimeStart = delegate (RuntimeInformationTraceData data)
{
TraceProcess process = this.processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC);
process.clrRuntimeVersion = new Version(data.VMMajorVersion, data.VMMinorVersion, data.VMBuildNumber, data.VMQfeNumber);
process.ClrStartupFlags = data.StartupFlags;
// proxy for bitness, given we don't have a traceevent to pass through
process.loadedAModuleHigh = (data.RuntimeDllPath.ToLower().Contains("framework64"));
if (process.commandLine.Length == 0)
process.commandLine = data.CommandLine;
};
ClrRundownParser.RuntimeStart += doAtRuntimeStart;
rawEvents.Clr.RuntimeStart += doAtRuntimeStart;
var clrPrivate = new ClrPrivateTraceEventParser(rawEvents);
clrPrivate.ClrStackWalk += clrStackWalk;
kernelParser.StackWalkStack += delegate (StackWalkStackTraceData data)
{
if (processingDisabled)
return;
var timeStampQPC = data.TimeStampQPC;
TraceProcess process = this.processes.GetOrCreateProcess(data.ProcessID, timeStampQPC);
};
// Attribute CPU samples to processes.
kernelParser.PerfInfoSample += delegate (SampledProfileTraceData data)
{
if (data.ThreadID == 0) // Don't count process 0 (idle)
{
return;
}
var process = Processes.GetOrCreateProcess(data.ProcessID, data.TimeStampQPC);
process.cpuSamples++;
};
// We assume that the sampling interval is uniform over the trace. We pick the start if it
// is there, otherwise the OLD value of the LAST set interval (since we RESET the interval at the end)
// OR the OLD value at the end.
bool setSeen = false;
bool startSeen = false;
kernelParser.PerfInfoCollectionStart += delegate (SampledProfileIntervalTraceData data)
{
startSeen = true;
sampleProfileInterval100ns = data.NewInterval;
};
kernelParser.PerfInfoSetInterval += delegate (SampledProfileIntervalTraceData data)
{
setSeen = true;
if (!startSeen)
sampleProfileInterval100ns = data.OldInterval;
};
kernelParser.PerfInfoSetInterval += delegate (SampledProfileIntervalTraceData data)
{
if (!setSeen && !startSeen)
sampleProfileInterval100ns = data.OldInterval;
};
}
/// <summary>
/// All the processes in the event source.
/// </summary>
public TraceProcesses Processes { get { return processes; } }
#region private
private TraceProcesses processes;
private bool processingDisabled; // Have we turned off processing because of a MaxCount?
private int sampleProfileInterval100ns;
internal TraceLogOptions options;
#endregion
}
}
@@ -0,0 +1,288 @@
// Copyright (c) Microsoft Corporation. All rights reserved
// This file is best viewed using outline mode (Ctrl-M Ctrl-O)
//
// This program uses code hyperlinks available as part of the HyperAddin Visual Studio plug-in.
// It is available from http://www.codeplex.com/hyperAddin
// using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.Symbols;
using Microsoft.Diagnostics.Tracing.Etlx;
using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.Tracing.Parsers.Clr;
using Microsoft.Diagnostics.Tracing.Parsers.ClrPrivate;
using Microsoft.Diagnostics.Tracing.Parsers.FrameworkEventSource;
using Microsoft.Diagnostics.Tracing.Parsers.Kernel;
using Microsoft.Diagnostics.Tracing.Parsers.Symbol;
using Microsoft.Diagnostics.Tracing.Parsers.Tpl;
using Microsoft.Diagnostics.Tracing.Stacks;
using Microsoft.Diagnostics.Utilities;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using Address = System.UInt64;
namespace Microsoft.Diagnostics.Tracing.Analysis
{
/// <summary>
/// Extension methods to enable TraceManagedProcess
/// </summary>
public static class TraceManagedSamplesExtensions
{
public static void NeedManagedSamples(this TraceEventDispatcher source, Microsoft.Diagnostics.Tracing.Etlx.TraceLog tracelog)
{
// ensure there are base processes
source.NeedProcesses();
source.NeedManagedProcesses();
if (!source.UserData.ContainsKey("Computers/ManagedSamples"))
{
TraceProcesses processes = source.Processes();
SetupCallbacks(source, tracelog);
source.UserData["Computers/ManagedSamples"] = processes;
}
}
#region private
private static void SetupCallbacks(TraceEventDispatcher source, Microsoft.Diagnostics.Tracing.Etlx.TraceLog tracelog)
{
source.Kernel.PerfInfoSample += delegate (SampledProfileTraceData data)
{
var stats = data.Process();
var mang = stats.AsManagedProcess();
if (mang != null)
{
mang.JIT.Stats.TotalCpuTimeMSec += 1;
Tuple<string/*module*/,string/*method*/> name = GetSampledMethodName(tracelog, mang.JIT, data);
if (name == null) return;
MethodInformation meth = null;
var methods = mang.JIT.Methods.Where(m => m.ModuleILPath.Contains(name.Item1) && m.MethodName.Equals(name.Item2));
if (methods.Count() > 0)
{
Debug.Assert(methods.Count() == 1);
meth = methods.First();
}
else
{
meth = new MethodInformation( stats.ProcessIndex);
meth.ModuleILPath = name.Item1;
meth.MethodName = name.Item2;
meth.ILSize = meth.NativeSize = -1;
meth.CompileCpuTimeMSec = -1;
meth.StartTimeMSec = -1;
mang.JIT.Methods.Add(meth);
}
meth.RunCpuTimeMSec++;
}
};
}
private static Tuple<string/*module*/,string/*method*/> GetSampledMethodName(Microsoft.Diagnostics.Tracing.Etlx.TraceLog tracelog, JITDetails details, SampledProfileTraceData data)
{
TraceCodeAddress ca = details.Stats.GetManagedMethodOnStack(data, tracelog);
if (ca == null)
{
return null;
}
if (String.IsNullOrEmpty(ca.ModuleName))
{
return null;
}
// Lookup symbols, if not already looked up.
if (!details.Stats.SymbolsLookedUp.Contains(ca.ModuleName))
{
try
{
LookupSymbolsForModule(tracelog, data, ca.ModuleName);
}
catch (Exception)
{
return null;
}
details.Stats.SymbolsLookedUp.Add(ca.ModuleName);
}
if (ca.Method == null)
{
details.Stats.SymbolsMissing.Add(ca.ModuleName);
return null;
}
return new Tuple<string,string>(ca.ModuleName, ca.Method.FullMethodName);
}
public static void LookupSymbolsForModule(Microsoft.Diagnostics.Tracing.Etlx.TraceLog tracelog, SampledProfileTraceData data, string simpleModuleName)
{
var symbolReader = GetSymbolReader(tracelog.FilePath);
var filterProcess = tracelog.Processes.LastProcessWithID(data.ProcessID);
// Remove any extensions.
simpleModuleName = Path.GetFileNameWithoutExtension(simpleModuleName);
// If we have a process, look the DLL up just there
var moduleFiles = new Dictionary<int, TraceModuleFile>();
if (filterProcess != null)
{
foreach (var loadedModule in filterProcess.LoadedModules)
{
var baseName = Path.GetFileNameWithoutExtension(loadedModule.Name);
if (string.Compare(baseName, simpleModuleName, StringComparison.OrdinalIgnoreCase) == 0)
moduleFiles[(int)loadedModule.ModuleFile.ModuleFileIndex] = loadedModule.ModuleFile;
}
}
// We did not find it, try system-wide
if (moduleFiles.Count == 0)
{
foreach (var moduleFile in tracelog.ModuleFiles)
{
var baseName = Path.GetFileNameWithoutExtension(moduleFile.Name);
if (string.Compare(baseName, simpleModuleName, StringComparison.OrdinalIgnoreCase) == 0)
moduleFiles[(int)moduleFile.ModuleFileIndex] = moduleFile;
}
}
if (moduleFiles.Count == 0)
throw new ApplicationException("Could not find module " + simpleModuleName + " in trace.");
foreach (var moduleFile in moduleFiles.Values)
{
try
{
tracelog.CodeAddresses.LookupSymbolsForModule(symbolReader, moduleFile);
}
catch (ApplicationException)
{
}
}
}
public static SymbolReader GetSymbolReader(string etlFilePath = null, SymbolReaderOptions symbolFlags = SymbolReaderOptions.None)
{
SymbolPath symPath = new SymbolPath(SymbolPath);
if ((symbolFlags & SymbolReaderOptions.CacheOnly) != 0)
symPath = new SymbolPath("SRV*" + symPath.DefaultSymbolCache());
var sourcePath = SourcePath; // ???
bool hasLocalSymDir = false;
if (etlFilePath != null)
{
// Add the directory where the file resides and a 'symbols' subdirectory
var filePathDir = Path.GetDirectoryName(etlFilePath);
if (filePathDir.Length != 0)
{
// Then the directory where the .ETL file lives.
symPath.Insert(filePathDir);
// The symbols directory has even higher priority (less likely to have a false positive)
var localSymDir = Path.Combine(filePathDir, "symbols");
if (Directory.Exists(localSymDir))
{
hasLocalSymDir = true;
symPath.Insert(localSymDir);
symPath.Insert("SRV*" + localSymDir);
}
// WPR conventions add any .etl.ngenPDB directory to the path too. has higher priority still.
var wprSymDir = etlFilePath + ".NGENPDB";
if (Directory.Exists(wprSymDir))
symPath.Insert("SRV*" + wprSymDir);
else
{
// I have now seen both conventions .etl.ngenpdb and .ngenpdb, so look for both.
wprSymDir = Path.ChangeExtension(etlFilePath, ".NGENPDB");
if (Directory.Exists(wprSymDir))
symPath.Insert("SRV*" + wprSymDir);
}
// VS uses .NGENPDBS as a convention.
wprSymDir = etlFilePath + ".NGENPDBS";
if (Directory.Exists(wprSymDir))
symPath.Insert("SRV*" + wprSymDir);
if (!string.IsNullOrWhiteSpace(sourcePath))
sourcePath += ";";
sourcePath += filePathDir;
var srcDir = Path.Combine(filePathDir, "src");
if (Directory.Exists(srcDir))
sourcePath += ";" + srcDir;
}
}
// Can we use the cached symbol reader?
if (s_symbolReader != null)
{
s_symbolReader.SourcePath = sourcePath;
if (symbolFlags == SymbolReaderOptions.None && s_symbolReader.SymbolPath == symPath.ToString())
return s_symbolReader;
s_symbolReader.Dispose();
s_symbolReader = null;
}
SymbolReader ret = new SymbolReader(TextWriter.Null, symPath.ToString());
ret.SourcePath = sourcePath;
ret.Options = symbolFlags;
ret.SecurityCheck = (pdbFile => true);
ret.CacheUnsafeSymbols = hasLocalSymDir;
if (symbolFlags == SymbolReaderOptions.None)
s_symbolReader = ret;
return ret;
}
public static string SymbolPath
{
get
{
if (m_SymbolPath == null)
{
// Start with _NT_SYMBOL_PATH
var symPath = new SymbolPath(Microsoft.Diagnostics.Symbols.SymbolPath.SymbolPathFromEnvironment);
// If we still don't have anything, add a default one
// Since the default goes off machine, if we are outside of Microsoft, we have to ask
// the user for permission.
symPath.Add(Microsoft.Diagnostics.Symbols.SymbolPath.MicrosoftSymbolServerPath);
// Remember it.
SymbolPath = symPath.InsureHasCache(symPath.DefaultSymbolCache()).CacheFirst().ToString();
}
return m_SymbolPath;
}
set
{
m_SymbolPath = value;
}
}
public static string SourcePath
{
get
{
if (m_SourcePath == null)
{
var symPath = new SymbolPath(Environment.GetEnvironmentVariable("_NT_SOURCE_PATH"));
// Remember it.
SourcePath = symPath.ToString();
}
return m_SourcePath;
}
set
{
m_SourcePath = value;
}
}
private static string m_SymbolPath;
private static string m_SourcePath;
private static SymbolReader s_symbolReader;
#endregion
}
}