mirror of
https://github.com/Kae7in/perfview
synced 2026-08-09 12:09:44 +00:00
Address merge conflicts
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user