in src/coreclr/tools/dotnet-pgo/Program.cs [988:1881]
private int InnerProcessTraceFileMain()
{
string outputPath = Get(_command.OutputFilePath);
if (outputPath == null)
{
PrintError("Output filename must be specified");
return -8;
}
if ((_command.FileType.Value != PgoFileType.jittrace) && (_command.FileType != PgoFileType.mibc))
{
PrintError($"Invalid output pgo type {_command.FileType} specified.");
return -9;
}
if (_command.FileType == PgoFileType.jittrace)
{
if (!outputPath.EndsWith(".jittrace"))
{
PrintError($"jittrace output file name must end with .jittrace");
return -9;
}
}
if (_command.FileType == PgoFileType.mibc)
{
if (!outputPath.EndsWith(".mibc"))
{
PrintError($"mibc output file name must end with .mibc");
return -9;
}
}
string etlFileName = Get(_command.TraceFilePath);
foreach (string nettraceExtension in new string[] { ".netperf", ".netperf.zip", ".nettrace" })
{
if (etlFileName.EndsWith(nettraceExtension))
{
string etlxFileName = Path.ChangeExtension(etlFileName, ".etlx");
PrintMessage($"Creating ETLX file {etlxFileName} from {etlFileName}");
TraceLog.CreateFromEventPipeDataFile(etlFileName, etlxFileName);
etlFileName = etlxFileName;
}
}
string lttngExtension = ".trace.zip";
if (etlFileName.EndsWith(lttngExtension))
{
string etlxFileName = Path.ChangeExtension(etlFileName, ".etlx");
PrintMessage($"Creating ETLX file {etlxFileName} from {etlFileName}");
TraceLog.CreateFromLttngTextDataFile(etlFileName, etlxFileName);
etlFileName = etlxFileName;
}
UnZipIfNecessary(ref etlFileName, _command.BasicProgressMessages ? Console.Out : new StringWriter());
// For SPGO we need to be able to map raw IPs back to IL offsets in methods.
// Normally TraceEvent facilitates this remapping automatically and discards the IL<->IP mapping table events.
// However, we have found TraceEvent's remapping to be imprecise (see https://github.com/microsoft/perfview/issues/1410).
// Thus, when SPGO is requested, we need to keep these events.
// Note that we always request these events to be kept because if one switches back and forth between SPGO and non-SPGO,
// the cached .etlx file will not update.
using (var traceLog = TraceLog.OpenOrConvert(etlFileName, new TraceLogOptions { KeepAllEvents = true }))
{
bool hasPid = IsSet(_command.Pid);
string processName = Get(_command.ProcessName);
if (hasPid && processName == null && traceLog.Processes.Count != 1)
{
PrintError("Trace file contains multiple processes to distinguish between");
PrintOutput("Either a pid or process name from the following list must be specified");
foreach (TraceProcess proc in traceLog.Processes.OrderByDescending(proc => proc.CPUMSec))
{
PrintOutput($"Procname = {proc.Name} Pid = {proc.ProcessID} CPUMsec = {proc.CPUMSec:F0}");
}
return 1;
}
if (hasPid && processName != null)
{
PrintError("--pid and --process-name cannot be specified together");
return -1;
}
// For a particular process
TraceProcess p;
if (hasPid)
{
p = traceLog.Processes.LastProcessWithID(Get(_command.Pid));
}
else if (processName != null)
{
List<TraceProcess> matchingProcesses = new List<TraceProcess>();
foreach (TraceProcess proc in traceLog.Processes)
{
if (String.Compare(proc.Name, processName, StringComparison.OrdinalIgnoreCase) == 0)
{
matchingProcesses.Add(proc);
}
}
if (matchingProcesses.Count == 0)
{
PrintError("Unable to find matching process in trace");
return -1;
}
if (matchingProcesses.Count > 1)
{
StringBuilder errorMessage = new StringBuilder();
errorMessage.AppendLine("Found multiple matching processes in trace");
foreach (TraceProcess proc in matchingProcesses)
{
errorMessage.AppendLine($"{proc.Name}\tpid={proc.ProcessID}\tCPUMSec={proc.CPUMSec}");
}
PrintError(errorMessage.ToString());
return -2;
}
p = matchingProcesses[0];
}
else
{
p = traceLog.Processes.First();
}
if (!p.EventsInProcess.ByEventType<MethodDetailsTraceData>().Any())
{
PrintError($"No MethodDetails\nWas the trace collected with provider at least \"Microsoft-Windows-DotNETRuntime:0x4000080018:5\"?");
return -3;
}
if (!p.EventsInProcess.ByEventType<GCBulkTypeTraceData>().Any())
{
PrintError($"No BulkType data\nWas the trace collected with provider at least \"Microsoft-Windows-DotNETRuntime:0x4000080018:5\"?");
return -4;
}
if (!p.EventsInProcess.ByEventType<ModuleLoadUnloadTraceData>().Any())
{
PrintError($"No managed module load data\nWas the trace collected with provider at least \"Microsoft-Windows-DotNETRuntime:0x4000080018:5\"?");
return -5;
}
if (!p.EventsInProcess.ByEventType<MethodJittingStartedTraceData>().Any() &&
!p.EventsInProcess.ByEventType<R2RGetEntryPointTraceData>().Any() &&
!p.EventsInProcess.ByEventType< SampledProfileTraceData>().Any())
{
PrintError($"No data in trace for process\nWas the trace collected with provider at least \"Microsoft-Windows-DotNETRuntime:0x4000080018:5\"?");
return -5;
}
PgoTraceProcess pgoProcess = new PgoTraceProcess(p);
int clrInstanceId = Get(_command.ClrInstanceId);
if (!IsSet(_command.ClrInstanceId))
{
HashSet<int> clrInstanceIds = new HashSet<int>();
HashSet<int> examinedClrInstanceIds = new HashSet<int>();
foreach (var assemblyLoadTrace in p.EventsInProcess.ByEventType<AssemblyLoadUnloadTraceData>())
{
if (examinedClrInstanceIds.Add(assemblyLoadTrace.ClrInstanceID))
{
if (pgoProcess.ClrInstanceIsCoreCLRInstance(assemblyLoadTrace.ClrInstanceID))
clrInstanceIds.Add(assemblyLoadTrace.ClrInstanceID);
}
}
if (clrInstanceIds.Count != 1)
{
if (clrInstanceIds.Count == 0)
{
PrintError($"No managed CLR in target process, or per module information could not be loaded from the trace.");
}
else
{
// There are multiple clr processes... search for the one that implements
int[] clrInstanceIdsArray = clrInstanceIds.ToArray();
Array.Sort(clrInstanceIdsArray);
StringBuilder errorMessage = new StringBuilder();
errorMessage.AppendLine("Multiple CLR instances used in process. Choose one to examine with -clrInstanceID:<id> Valid ids:");
foreach (int instanceID in clrInstanceIds)
{
errorMessage.AppendLine(instanceID.ToString());
}
PrintError(errorMessage.ToString());
}
return -10;
}
else
{
clrInstanceId = clrInstanceIds.First();
}
}
var tsc = new TraceTypeSystemContext(pgoProcess, clrInstanceId, s_logger, Get(_command.AutomaticReferences));
if (_command.VerboseWarnings)
PrintWarning($"{traceLog.EventsLost} Lost events");
bool filePathError = false;
HashSet<ModuleDesc> modulesLoadedViaReference = new HashSet<ModuleDesc>();
foreach (string file in Get(_command.Reference))
{
try
{
if (!File.Exists(file))
{
PrintError($"Unable to find reference '{file}'");
filePathError = true;
}
else
{
var module = tsc.GetModuleFromPath(file, throwIfNotLoadable: false);
if (module != null)
modulesLoadedViaReference.Add(module);
}
}
catch (Internal.TypeSystem.TypeSystemException.BadImageFormatException)
{
// Ignore BadImageFormat in order to allow users to use '-r *.dll'
// in a folder with native dynamic libraries (which have the same extension on Windows).
// We don't need to log a warning here - it's already logged in GetModuleFromPath
}
}
if (filePathError)
return -6;
if (!tsc.Initialize())
return -12;
Dictionary<string, HashSet<string>> duplicateModuleAnalysis = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
foreach (var module in pgoProcess.EnumerateLoadedManagedModules())
{
var managedModule = module.ManagedModule;
if (module.ClrInstanceID != clrInstanceId)
continue;
if (managedModule.ModuleFile != null)
{
string simpleName = managedModule.ModuleFile.Name;
if (simpleName.EndsWith(".il"))
simpleName = simpleName.Substring(0, simpleName.Length - 3);
string filePathTemp = PgoTraceProcess.ComputeFilePathOnDiskForModule(managedModule);
string candidateFilePath;
// This path may be normalized
if (File.Exists(filePathTemp) || !tsc._normalizedFilePathToFilePath.TryGetValue(filePathTemp, out candidateFilePath))
candidateFilePath = filePathTemp;
if (!duplicateModuleAnalysis.TryGetValue(simpleName, out HashSet<string> candidatePaths))
{
duplicateModuleAnalysis[simpleName] = candidatePaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
}
candidatePaths.Add(candidateFilePath);
}
}
bool duplicateError = false;
foreach (var assembliesWithDuplicates in duplicateModuleAnalysis)
{
if (assembliesWithDuplicates.Value.Count == 1)
continue;
ModuleDesc loadedViaReference = null;
foreach (var module in modulesLoadedViaReference)
{
if (string.Equals(module.Assembly.GetName().Name, assembliesWithDuplicates.Key, StringComparison.OrdinalIgnoreCase))
{
loadedViaReference = module;
break;
}
}
// AutomaticReferences set to false disables this error, as no more references can actually be loaded past this point and cause a problem.
if (loadedViaReference == null && Get(_command.AutomaticReferences))
{
duplicateError = true;
PrintError($"Multiple assemblies with the same simple name loaded into the process. Specify the preferred module via the -reference parameter.");
foreach (string path in assembliesWithDuplicates.Value)
{
PrintMessage(path);
}
}
}
if (duplicateError)
return -13;
TraceRuntimeDescToTypeSystemDesc idParser = new TraceRuntimeDescToTypeSystemDesc(p, tsc, clrInstanceId);
int mismatchErrors = 0;
foreach (var e in p.EventsInProcess.ByEventType<ModuleLoadUnloadTraceData>())
{
ModuleDesc loadedModule = idParser.ResolveModuleID(e.ModuleID, false);
if (loadedModule == null)
{
if (!idParser.IsDynamicModuleID(e.ModuleID))
PrintWarning($"Unable to find loaded module {e.ModuleILFileName} to verify match");
continue;
}
EcmaModule ecmaModule = loadedModule as EcmaModule;
if (ecmaModule == null)
{
continue;
}
bool matched = false;
bool mismatch = false;
bool mismatchHandled = false;
foreach (DebugDirectoryEntry debugEntry in ecmaModule.PEReader.SafeReadDebugDirectory())
{
if (debugEntry.Type == DebugDirectoryEntryType.CodeView)
{
var codeViewData = ecmaModule.PEReader.ReadCodeViewDebugDirectoryData(debugEntry);
if (codeViewData.Path.EndsWith("ni.pdb"))
continue;
if (codeViewData.Guid != e.ManagedPdbSignature)
{
if (modulesLoadedViaReference.Contains(ecmaModule) && duplicateModuleAnalysis[ecmaModule.Assembly.GetName().Name].Count > 1)
{
// This is the case where a duplicate dll mismatch was avoided by specifying a -reference parameter
PrintMessage($"Disabling load of assembly data from assembly located at \"{e.ModuleILPath}\" during trace collection as module \"{tsc.PEReaderToFilePath(ecmaModule.PEReader)}\" is preferred, and does not match");
idParser.RemoveModuleIDFromLoader(e.ModuleID);
mismatchHandled = true;
}
else
{
PrintError($"Dll mismatch between assembly located at \"{e.ModuleILPath}\" during trace collection and module \"{tsc.PEReaderToFilePath(ecmaModule.PEReader)}\"");
mismatchErrors++;
mismatch = true;
}
continue;
}
else
{
matched = true;
}
}
}
if (!matched && !mismatch && !mismatchHandled)
{
PrintMessage($"Unable to validate match between assembly located at \"{e.ModuleILPath}\" during trace collection and module \"{tsc.PEReaderToFilePath(ecmaModule.PEReader)}\"");
}
// TODO find some way to match on MVID as only some dlls have managed pdbs, and this won't find issues with embedded pdbs
}
if (mismatchErrors != 0)
{
PrintError($"{mismatchErrors} mismatch error(s) found");
return -1;
}
// Now that the modules are validated run Init to prepare for the rest of execution
idParser.Init();
SortedDictionary<long, ProcessedMethodData> methodsToAttemptToPrepare = new SortedDictionary<long, ProcessedMethodData>();
double excludeEventsBefore = Get(_command.ExcludeEventsBefore);
double excludeEventsAfter = Get(_command.ExcludeEventsAfter);
Regex excludeEventsBeforeJittingMethod = !string.IsNullOrEmpty(Get(_command.ExcludeEventsBeforeJittingMethod)) ? new Regex(Get(_command.ExcludeEventsBeforeJittingMethod)) : null;
Regex excludeEventsAfterJittingMethod = !string.IsNullOrEmpty(Get(_command.ExcludeEventsAfterJittingMethod)) ? new Regex(Get(_command.ExcludeEventsAfterJittingMethod)) : null;
Regex includeMethods = !string.IsNullOrEmpty(Get(_command.IncludeMethods)) ? new Regex(Get(_command.IncludeMethods)) : null;
Regex excludeMethods = !string.IsNullOrEmpty(Get(_command.ExcludeMethods)) ? new Regex(Get(_command.ExcludeMethods)) : null;
// Find all the R2RLoad events.
if (_command.ProcessR2REvents)
{
foreach (var e in p.EventsInProcess.ByEventType<R2RGetEntryPointTraceData>())
{
int parenIndex = e.MethodSignature.IndexOf('(');
string retArg = e.MethodSignature.Substring(0, parenIndex);
string paramsArgs = e.MethodSignature.Substring(parenIndex);
string methodNameFromEventDirectly = retArg + e.MethodNamespace + "." + e.MethodName + paramsArgs;
if (e.ClrInstanceID != clrInstanceId)
{
if (!_command.Warnings)
continue;
PrintWarning($"Skipped R2REntryPoint {methodNameFromEventDirectly} due to ClrInstanceID of {e.ClrInstanceID}");
continue;
}
MethodDesc method = null;
string extraWarningText = null;
bool failedDueToNonloadableModule = false;
try
{
method = idParser.ResolveMethodID(e.MethodID, out failedDueToNonloadableModule, _command.VerboseWarnings);
}
catch (Exception exception)
{
extraWarningText = exception.ToString();
}
if (method == null)
{
if ((e.MethodNamespace == "dynamicClass") || failedDueToNonloadableModule || !_command.Warnings)
continue;
PrintWarning($"Unable to parse {methodNameFromEventDirectly} when looking up R2R methods");
if (extraWarningText != null)
PrintWarning(extraWarningText);
continue;
}
if (e.TimeStampRelativeMSec < excludeEventsBefore)
{
continue;
}
if (e.TimeStampRelativeMSec > excludeEventsAfter)
{
break;
}
string perfviewMethodName = e.MethodNamespace + "." + e.MethodName + paramsArgs;
if (PassesMethodFilter(includeMethods, excludeMethods, perfviewMethodName))
{
methodsToAttemptToPrepare.Add((int)e.EventIndex, new ProcessedMethodData(e.TimeStampRelativeMSec, method, "R2RLoad"));
}
}
}
// In case requesting events before/after jitting a method, discover the
// corresponding excludeEventsBefore/excludeEventsAfter in event stream based
// on filter criterias.
if (_command.ProcessJitEvents && (excludeEventsBeforeJittingMethod != null || excludeEventsAfterJittingMethod != null))
{
double firstMatchEventsBeforeJittingMethod = double.PositiveInfinity;
double lastMatchEventsAfterJittingMethod = double.NegativeInfinity;
foreach (var e in p.EventsInProcess.ByEventType<MethodJittingStartedTraceData>())
{
if (e.ClrInstanceID != clrInstanceId)
{
continue;
}
MethodDesc method = null;
bool failedDueToNonloadableModule = false;
try
{
method = idParser.ResolveMethodID(e.MethodID, out failedDueToNonloadableModule, false);
}
catch { }
if (method == null)
{
continue;
}
int parenIndex = e.MethodSignature.IndexOf('(');
string paramsArgs = e.MethodSignature.Substring(parenIndex);
string perfviewMethodName = e.MethodNamespace + "." + e.MethodName + paramsArgs;
if (e.TimeStampRelativeMSec > excludeEventsBefore && e.TimeStampRelativeMSec < firstMatchEventsBeforeJittingMethod && excludeEventsBeforeJittingMethod != null && excludeEventsBeforeJittingMethod.IsMatch(perfviewMethodName))
{
firstMatchEventsBeforeJittingMethod = e.TimeStampRelativeMSec;
}
if (e.TimeStampRelativeMSec < excludeEventsAfter && e.TimeStampRelativeMSec > lastMatchEventsAfterJittingMethod && excludeEventsAfterJittingMethod != null && excludeEventsAfterJittingMethod.IsMatch(perfviewMethodName))
{
lastMatchEventsAfterJittingMethod = e.TimeStampRelativeMSec;
}
}
if (firstMatchEventsBeforeJittingMethod < double.PositiveInfinity)
{
excludeEventsBefore = firstMatchEventsBeforeJittingMethod;
}
if (lastMatchEventsAfterJittingMethod > double.NegativeInfinity)
{
excludeEventsAfter = lastMatchEventsAfterJittingMethod;
}
if (excludeEventsBefore > excludeEventsAfter)
{
PrintError($"Exclude events before timestamp: \"{excludeEventsBefore}\" can't be later than exclude events after timestamp: \"{excludeEventsAfter}\"");
return -1;
}
}
// Find all the jitStart events.
if (_command.ProcessJitEvents)
{
foreach (var e in p.EventsInProcess.ByEventType<MethodJittingStartedTraceData>())
{
int parenIndex = e.MethodSignature.IndexOf('(');
string retArg = e.MethodSignature.Substring(0, parenIndex);
string paramsArgs = e.MethodSignature.Substring(parenIndex);
string methodNameFromEventDirectly = retArg + e.MethodNamespace + "." + e.MethodName + paramsArgs;
if (e.ClrInstanceID != clrInstanceId)
{
if (!_command.Warnings)
continue;
PrintWarning($"Skipped {methodNameFromEventDirectly} due to ClrInstanceID of {e.ClrInstanceID}");
continue;
}
MethodDesc method = null;
string extraWarningText = null;
bool failedDueToNonloadableModule = false;
try
{
method = idParser.ResolveMethodID(e.MethodID, out failedDueToNonloadableModule, _command.VerboseWarnings);
}
catch (Exception exception)
{
extraWarningText = exception.ToString();
}
if (method == null)
{
if ((e.MethodNamespace == "dynamicClass") || failedDueToNonloadableModule || !_command.Warnings)
continue;
PrintWarning($"Unable to parse {methodNameFromEventDirectly}");
if (extraWarningText != null)
PrintWarning(extraWarningText);
continue;
}
if (e.TimeStampRelativeMSec < excludeEventsBefore)
{
continue;
}
if (e.TimeStampRelativeMSec > excludeEventsAfter)
{
break;
}
string perfviewMethodName = e.MethodNamespace + "." + e.MethodName + paramsArgs;
if (PassesMethodFilter(includeMethods, excludeMethods, perfviewMethodName))
{
methodsToAttemptToPrepare.Add((int)e.EventIndex, new ProcessedMethodData(e.TimeStampRelativeMSec, method, "JitStart"));
}
}
}
MethodMemoryMap methodMemMap = null;
MethodMemoryMap GetMethodMemMap()
{
if (methodMemMap == null)
{
methodMemMap = new MethodMemoryMap(
p,
tsc,
idParser,
clrInstanceId,
Get(_command.PreciseDebugInfoFile),
s_logger);
}
return methodMemMap;
}
Dictionary<MethodDesc, Dictionary<MethodDesc, int>> callGraph = null;
Dictionary<MethodDesc, int> exclusiveSamples = null;
if (_command.GenerateCallGraph)
{
HashSet<MethodDesc> methodsListedToPrepare = new HashSet<MethodDesc>();
foreach (var entry in methodsToAttemptToPrepare)
{
methodsListedToPrepare.Add(entry.Value.Method);
}
callGraph = new Dictionary<MethodDesc, Dictionary<MethodDesc, int>>();
exclusiveSamples = new Dictionary<MethodDesc, int>();
MethodMemoryMap mmap = GetMethodMemMap();
foreach (var e in p.EventsInProcess.ByEventType<SampledProfileTraceData>())
{
if ((e.TimeStampRelativeMSec < excludeEventsBefore) && (e.TimeStampRelativeMSec > excludeEventsAfter))
continue;
var callstack = e.CallStack();
if (callstack == null)
continue;
ulong address1 = callstack.CodeAddress.Address;
MethodDesc topOfStackMethod = mmap.GetMethod(address1);
MethodDesc nextMethod = null;
if (callstack.Caller != null)
{
ulong address2 = callstack.Caller.CodeAddress.Address;
nextMethod = mmap.GetMethod(address2);
}
if (topOfStackMethod != null)
{
if (!methodsListedToPrepare.Contains(topOfStackMethod))
{
methodsListedToPrepare.Add(topOfStackMethod);
methodsToAttemptToPrepare.Add((int)e.EventIndex, new ProcessedMethodData(e.TimeStampRelativeMSec, topOfStackMethod, "SampleMethod"));
}
if (exclusiveSamples.TryGetValue(topOfStackMethod, out int count))
{
exclusiveSamples[topOfStackMethod] = count + 1;
}
else
{
exclusiveSamples[topOfStackMethod] = 1;
}
}
if (topOfStackMethod != null && nextMethod != null)
{
if (!methodsListedToPrepare.Contains(nextMethod))
{
methodsListedToPrepare.Add(nextMethod);
methodsToAttemptToPrepare.Add(0x100000000 + (int)e.EventIndex, new ProcessedMethodData(e.TimeStampRelativeMSec, nextMethod, "SampleMethodCaller"));
}
if (!callGraph.TryGetValue(nextMethod, out var innerDictionary))
{
innerDictionary = new Dictionary<MethodDesc, int>();
callGraph[nextMethod] = innerDictionary;
}
if (innerDictionary.TryGetValue(topOfStackMethod, out int count))
{
innerDictionary[topOfStackMethod] = count + 1;
}
else
{
innerDictionary[topOfStackMethod] = 1;
}
}
}
}
Dictionary<MethodDesc, MethodChunks> instrumentationDataByMethod = new Dictionary<MethodDesc, MethodChunks>();
foreach (var e in p.EventsInProcess.ByEventType<JitInstrumentationDataVerboseTraceData>())
{
AddToInstrumentationData(e.ClrInstanceID, e.MethodID, e.MethodFlags, e.Data);
}
foreach (var e in p.EventsInProcess.ByEventType<JitInstrumentationDataTraceData>())
{
AddToInstrumentationData(e.ClrInstanceID, e.MethodID, e.MethodFlags, e.Data);
}
// Local function used with the above two loops as the behavior is supposed to be identical
void AddToInstrumentationData(int eventClrInstanceId, long methodID, int methodFlags, byte[] data)
{
if (eventClrInstanceId != clrInstanceId)
{
return;
}
MethodDesc method = null;
try
{
method = idParser.ResolveMethodID(methodID, out _, _command.VerboseWarnings);
}
catch (Exception)
{
}
if (method != null)
{
if (!instrumentationDataByMethod.TryGetValue(method, out MethodChunks perMethodChunks))
{
perMethodChunks = new MethodChunks();
instrumentationDataByMethod.Add(method, perMethodChunks);
}
const int FinalChunkFlag = unchecked((int)0x80000000);
int chunkIndex = methodFlags & ~FinalChunkFlag;
if ((chunkIndex != (perMethodChunks.LastChunk + 1)) || perMethodChunks.Done)
{
instrumentationDataByMethod.Remove(method);
return;
}
perMethodChunks.LastChunk = perMethodChunks.InstrumentationData.Count;
perMethodChunks.InstrumentationData.Add(data);
if ((methodFlags & FinalChunkFlag) == FinalChunkFlag)
perMethodChunks.Done = true;
}
}
SampleCorrelator correlator = null;
if (Get(_command.Spgo))
{
correlator = new SampleCorrelator(GetMethodMemMap());
Guid lbrGuid = Guid.Parse("99134383-5248-43fc-834b-529454e75df3");
bool hasLbr = traceLog.Events.Any(e => e.TaskGuid == lbrGuid);
if (!hasLbr)
{
foreach (SampledProfileTraceData e in p.EventsInProcess.ByEventType<SampledProfileTraceData>())
{
correlator.AttributeSamplesToIP(e.InstructionPointer, 1);
}
PrintOutput($"Samples outside managed code: {correlator.SamplesOutsideManagedCode}");
PrintOutput($"Samples in managed code that does not have native<->IL mappings: {correlator.SamplesInManagedCodeWithoutAnyMappings}");
PrintOutput($"Samples in managed code with mappings that could not be correlated: {correlator.SamplesInManagedCodeOutsideMappings}");
PrintOutput($"Samples in inlinees that were not present in ETW events: {correlator.SamplesInUnknownInlinees}");
PrintOutput($"Samples in managed code for which we could not get the IL: {correlator.SamplesInManagedCodeWithoutIL}");
PrintOutput($"Samples in managed code that could not be attributed to the method's flow graph: {correlator.SamplesInManagedCodeOutsideFlowGraph}");
PrintOutput($"Samples successfully attributed: {correlator.TotalAttributedSamples}");
}
else
{
long numLbrRecords = 0;
foreach (var e in traceLog.Events)
{
if (e.TaskGuid != lbrGuid)
continue;
// Opcode is always 32 for the LBR event.
if (e.Opcode != (TraceEventOpcode)32)
continue;
numLbrRecords++;
unsafe
{
if (traceLog.PointerSize == 4)
{
// For 32-bit machines we convert the data into a 64-bit format first.
LbrTraceEventData32* data = (LbrTraceEventData32*)e.DataStart;
if (data->ProcessId != p.ProcessID)
continue;
Span<LbrEntry32> lbr32 = LbrTraceEventData32.Entries(ref *data, e.EventDataLength);
correlator.AttributeSampleToLbrRuns(lbr32);
}
else
{
Trace.Assert(traceLog.PointerSize == 8, $"Unexpected PointerSize {traceLog.PointerSize}");
LbrTraceEventData64* data = (LbrTraceEventData64*)e.DataStart;
// TODO: The process ID check is not sufficient as PIDs can be reused, so we need to use timestamps too,
// but we do not have access to PerfView functions to convert it. Hopefully TraceEvent will handle this
// for us in the future.
if (data->ProcessId != p.ProcessID)
continue;
Span<LbrEntry64> lbr64 = LbrTraceEventData64.Entries(ref *data, e.EventDataLength);
correlator.AttributeSampleToLbrRuns(lbr64);
}
}
}
PrintOutput($"Profile is based on {numLbrRecords} LBR records");
}
correlator.SmoothAllProfiles();
}
if (_command.DisplayProcessedEvents)
{
foreach (var entry in methodsToAttemptToPrepare)
{
MethodDesc method = entry.Value.Method;
string reason = entry.Value.Reason;
PrintOutput($"{entry.Value.Millisecond.ToString("F4")} {reason} {method}");
}
}
PrintMessage("Done processing input file");
// Deduplicate entries
HashSet<MethodDesc> methodsInListAlready = new HashSet<MethodDesc>();
List<ProcessedMethodData> methodsUsedInProcess = new List<ProcessedMethodData>();
PgoDataLoader pgoDataLoader = new PgoDataLoader(idParser);
foreach (var entry in methodsToAttemptToPrepare)
{
if (methodsInListAlready.Add(entry.Value.Method))
{
var methodData = entry.Value;
if (_command.GenerateCallGraph)
{
exclusiveSamples.TryGetValue(methodData.Method, out methodData.ExclusiveWeight);
callGraph.TryGetValue(methodData.Method, out methodData.WeightedCallData);
}
if (instrumentationDataByMethod.TryGetValue(methodData.Method, out MethodChunks chunks))
{
int size = 0;
foreach (byte[] arr in chunks.InstrumentationData)
{
size += arr.Length;
}
byte[] instrumentationData = new byte[size];
int offset = 0;
foreach (byte[] arr in chunks.InstrumentationData)
{
arr.CopyTo(instrumentationData, offset);
offset += arr.Length;
}
var intDecompressor = new PgoProcessor.PgoEncodedCompressedIntParser(instrumentationData, 0);
methodData.InstrumentationData = PgoProcessor.ParsePgoData<TypeSystemEntityOrUnknown, TypeSystemEntityOrUnknown>(pgoDataLoader, intDecompressor, true).ToArray();
}
else
{
SampleProfile sp = correlator?.GetProfile(methodData.Method);
if (sp != null && sp.AttributedSamples >= Get(_command.SpgoMinSamples))
{
IEnumerable<PgoSchemaElem> schema =
sp.SmoothedSamples
.Select(
kvp =>
new PgoSchemaElem
{
InstrumentationKind = kvp.Value > uint.MaxValue ? PgoInstrumentationKind.BasicBlockLongCount : PgoInstrumentationKind.BasicBlockIntCount,
ILOffset = kvp.Key.Start,
Count = 1,
DataLong = kvp.Value,
});
bool includeFullGraphs = Get(_command.IncludeFullGraphs);
if (includeFullGraphs)
{
schema = schema.Concat(
sp.SmoothedEdgeSamples
.Select(kvp =>
new PgoSchemaElem
{
InstrumentationKind = kvp.Value > uint.MaxValue ? PgoInstrumentationKind.EdgeLongCount : PgoInstrumentationKind.EdgeIntCount,
ILOffset = kvp.Key.Item1.Start,
Other = kvp.Key.Item2.Start,
Count = 1,
DataLong = kvp.Value
}));
}
methodData.InstrumentationData = schema.ToArray();
#if DEBUG
if (includeFullGraphs)
{
var writtenBlocks =
new HashSet<int>(
methodData.InstrumentationData
.Where(elem => elem.InstrumentationKind == PgoInstrumentationKind.BasicBlockIntCount || elem.InstrumentationKind == PgoInstrumentationKind.BasicBlockLongCount)
.Select(elem => elem.ILOffset));
var writtenEdges =
new HashSet<(int, int)>(
methodData.InstrumentationData
.Where(elem => elem.InstrumentationKind == PgoInstrumentationKind.EdgeIntCount || elem.InstrumentationKind == PgoInstrumentationKind.EdgeLongCount)
.Select(elem => (elem.ILOffset, elem.Other)));
Debug.Assert(writtenBlocks.SetEquals(sp.FlowGraph.BasicBlocks.Select(bb => bb.Start)));
Debug.Assert(writtenEdges.SetEquals(sp.FlowGraph.BasicBlocks.SelectMany(bb => bb.Targets.Select(bbTar => (bb.Start, bbTar.Start)))));
}
#endif
}
}
methodsUsedInProcess.Add(methodData);
}
}
FileInfo outputFileInfo = new(outputPath);
if (_command.FileType.Value == PgoFileType.jittrace)
GenerateJittraceFile(outputFileInfo, methodsUsedInProcess, _command.JitTraceOptions);
else if (_command.FileType.Value == PgoFileType.mibc)
{
var config = new MibcConfig();
// Look for OS and Arch, e.g. "Windows" and "x64"
TraceEvent processInfo = p.EventsInProcess.Filter(t => t.EventName == "ProcessInfo").FirstOrDefault();
config.Os = processInfo?.PayloadByName("OSInformation")?.ToString();
config.Arch = processInfo?.PayloadByName("ArchInformation")?.ToString();
// Look for Sku, e.g. "CoreClr"
TraceEvent runtimeStart = p.EventsInProcess.Filter(t => t.EventName == "Runtime/Start").FirstOrDefault();
config.Runtime = runtimeStart?.PayloadByName("Sku")?.ToString();
ILCompiler.MethodProfileData[] methodProfileData = new ILCompiler.MethodProfileData[methodsUsedInProcess.Count];
for (int i = 0; i < methodProfileData.Length; i++)
{
ProcessedMethodData processedData = methodsUsedInProcess[i];
methodProfileData[i] = new ILCompiler.MethodProfileData(processedData.Method, ILCompiler.MethodProfilingDataFlags.ReadMethodCode, processedData.ExclusiveWeight, processedData.WeightedCallData, 0xFFFFFFFF, processedData.InstrumentationData);
}
return MibcEmitter.GenerateMibcFile(config, tsc, outputFileInfo, methodProfileData, _command.ValidateOutputFile, !Get(_command.Compressed));
}
}
return 0;
}