Improvements to R2R Test Harness (#6653)

To support multi-core testing, associate the test process's PID with the ETW filter so that only assemblies loaded by the runtime the harness started are considered.

Emit the assembly name for methods that are jitted to allow grouping of methods by their assembly.

Add a new parameter, `--include` which configures the ETW filter to include all assemblies in a given folder, instead of having to specify a large number of assemblies individually.

Bug fixes around path casing causing events to be dropped.
This commit is contained in:
Simon Nattress 2018-12-07 16:06:42 -08:00 коммит произвёл GitHub
Родитель 26bcecd15d
Коммит 2dadfbbd2e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
3 изменённых файлов: 107 добавлений и 15 удалений

Просмотреть файл

@ -0,0 +1,34 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
namespace ReadyToRun.TestHarness
{
/// <summary>
/// A set of helper to manipulate paths into a canonicalized form to ensure user-provided paths
/// match those in the ETW log.
/// </summary>
static class PathExtensions
{
internal static string ToAbsolutePath(this string argValue) => Path.GetFullPath(argValue);
internal static string ToAbsoluteDirectoryPath(this string argValue) => argValue.ToAbsolutePath().StripTrailingDirectorySeparators();
internal static string StripTrailingDirectorySeparators(this string str)
{
if (String.IsNullOrWhiteSpace(str))
{
return str;
}
while (str.Length > 0 && str[str.Length - 1] == Path.DirectorySeparatorChar)
{
str = str.Remove(str.Length - 1);
}
return str;
}
}
}

Просмотреть файл

@ -39,13 +39,14 @@ namespace ReadyToRun.TestHarness
private static string _coreRunExePath;
private static string _testExe;
private static IReadOnlyList<string> _referenceFilenames = Array.Empty<string>();
private static IReadOnlyList<string> _referenceFolders = Array.Empty<string>();
private static string _whitelistFilename;
private static IReadOnlyList<string> _testargs = Array.Empty<string>();
private static bool _noEtl;
static void ShowUsage()
{
Console.WriteLine("dotnet ReadyToRun.TestHarness --corerun <PathToCoreRun> --in <PathToTestBinary> --ref [ReferencedBinaries] --whitelist [MethodWhiteListFile] --testargs [TestArgs]");
Console.WriteLine("dotnet ReadyToRun.TestHarness --corerun <PathToCoreRun> --in <PathToTestBinary> --ref [ReferencedBinaries] --whitelist [MethodWhiteListFile] --testargs [TestArgs] --include [FoldersContainingAssemblies]");
}
private static ArgumentSyntax ParseCommandLine(string[] args)
@ -55,11 +56,13 @@ namespace ReadyToRun.TestHarness
syntax.ApplicationName = "ReadyToRun.TestHarness";
syntax.HandleHelp = false;
syntax.HandleErrors = true;
syntax.HandleResponseFiles = true;
syntax.DefineOption("h|help", ref _help, "Help message for R2RDump");
syntax.DefineOption("c|corerun", ref _coreRunExePath, "Path to CoreRun");
syntax.DefineOption("i|in", ref _testExe, "Path to test exe");
syntax.DefineOptionList("r|ref", ref _referenceFilenames, "Paths to referenced assemblies");
syntax.DefineOptionList("include", ref _referenceFolders, "Folders containing assemblies to monitor");
syntax.DefineOption("w|whitelist", ref _whitelistFilename, "Path to method whitelist file");
syntax.DefineOptionList("testargs", ref _testargs, "Args to pass into test");
syntax.DefineOption ("noetl", ref _noEtl, "Run the test without ETL enabled");
@ -100,24 +103,39 @@ namespace ReadyToRun.TestHarness
}
var testModules = new HashSet<string>();
testModules.Add(_testExe);
var testFolders = new HashSet<string>();
testModules.Add(_testExe.ToLower());
foreach (string reference in _referenceFilenames)
{
testModules.Add(reference);
// CoreCLR generates ETW events with all lower case native image files that break our string comparison.
testModules.Add(reference.ToLower());
}
foreach (string reference in _referenceFolders)
{
string absolutePath = reference.ToAbsoluteDirectoryPath();
if (!Directory.Exists(reference))
{
Console.WriteLine($"Error: {reference} does not exist.");
ShowUsage();
return exitCode;
}
testFolders.Add(reference.ToLower());
}
if (_noEtl)
{
RunTest(null, passThroughArguments, out exitCode);
RunTest(null, null, passThroughArguments, out exitCode);
}
else
{
using (var session = new TraceEventSession("ReadyToRunTestSession"))
{
var r2rMethodFilter = new ReadyToRunJittedMethods(session, testModules);
var r2rMethodFilter = new ReadyToRunJittedMethods(session, testModules, testFolders);
session.EnableProvider(ClrTraceEventParser.ProviderGuid, TraceEventLevel.Verbose, (ulong)(ClrTraceEventParser.Keywords.Jit | ClrTraceEventParser.Keywords.Loader));
Task.Run(() => RunTest(session, passThroughArguments, out exitCode));
Task.Run(() => RunTest(session, r2rMethodFilter, passThroughArguments, out exitCode));
// Block, processing callbacks for events we subscribed to
session.Source.Process();
@ -183,9 +201,10 @@ namespace ReadyToRun.TestHarness
int jittedMethodCount = 0;
foreach (var jittedMethod in jittedMethods.JittedMethods)
{
if (!whiteListedMethods.Contains(jittedMethod.MethodName))
string fullName = GetFullModuleName(jittedMethod.assemblyName, jittedMethod.MethodName);
if (!whiteListedMethods.Contains(jittedMethod.MethodName) && !whiteListedMethods.Contains(fullName))
{
Console.WriteLine(jittedMethod.MethodName);
Console.WriteLine(fullName);
++jittedMethodCount;
}
}
@ -203,7 +222,12 @@ namespace ReadyToRun.TestHarness
return StatusTestPassed;
}
private static void RunTest(TraceEventSession session, string testArguments, out int exitCode)
private static string GetFullModuleName(string moduleName, string methodName)
{
return $"[{moduleName}]{methodName}";
}
private static void RunTest(TraceEventSession session, ReadyToRunJittedMethods r2rMethodFilter, string testArguments, out int exitCode)
{
exitCode = -100;
@ -217,6 +241,11 @@ namespace ReadyToRun.TestHarness
process.Start();
if (r2rMethodFilter != null)
{
r2rMethodFilter.SetProcessId(process.Id);
}
process.OutputDataReceived += delegate(object sender, DataReceivedEventArgs args)
{
Console.WriteLine(args.Data);

Просмотреть файл

@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Collections.Generic;
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Parsers.Clr;
@ -19,33 +20,61 @@ namespace ReadyToRun.TestHarness
class ReadyToRunJittedMethods
{
private ICollection<string> _testModuleNames;
private ICollection<string> _testFolderNames;
private List<long> _testModuleIds = new List<long>();
private List<(string, bool)> _methodsJitted = new List<(string name, bool readyToRunRejected)>();
private Dictionary<long, string> _testModuleIdToName = new Dictionary<long, string>();
private List<(string, string, bool)> _methodsJitted = new List<(string name, string moduleName, bool readyToRunRejected)>();
private int _pid = -1;
public ReadyToRunJittedMethods(TraceEventSession session, ICollection<string> testModuleNames)
public ReadyToRunJittedMethods(TraceEventSession session, ICollection<string> testModuleNames, ICollection<string> testFolderNames)
{
_testModuleNames = testModuleNames;
_testFolderNames = testFolderNames;
session.Source.Clr.LoaderModuleLoad += delegate(ModuleLoadUnloadTraceData data)
{
if (_testModuleNames.Contains(data.ModuleILPath) || _testModuleNames.Contains(data.ModuleNativePath))
if (ShouldMonitorModule(data))
{
Console.WriteLine($"Tracking module {data.ModuleILFileName} with Id {data.ModuleID}");
_testModuleIds.Add(data.ModuleID);
_testModuleIdToName[data.ModuleID] = Path.GetFileNameWithoutExtension(data.ModuleILFileName);
}
};
session.Source.Clr.MethodLoadVerbose += delegate (MethodLoadUnloadVerboseTraceData data)
{
if (_testModuleIds.Contains(data.ModuleID) && data.IsJitted)
if (data.ProcessID == _pid && _testModuleIds.Contains(data.ModuleID) && data.IsJitted)
{
Console.WriteLine($"Method loaded {GetName(data)} - {data}");
_methodsJitted.Add((GetName(data), ((int)data.MethodFlags & 0x40) != 0));
_methodsJitted.Add((GetName(data), _testModuleIdToName[data.ModuleID], ((int)data.MethodFlags & 0x40) != 0));
}
};
}
public IEnumerable<(string MethodName, bool ReadyToRunRejected)> JittedMethods => _methodsJitted;
private bool ShouldMonitorModule(ModuleLoadUnloadTraceData data)
{
if (data.ProcessID != _pid)
return false;
if (File.Exists(data.ModuleILPath) && _testFolderNames.Contains(Path.GetDirectoryName(data.ModuleILPath).ToAbsoluteDirectoryPath().ToLower()))
return true;
if (_testModuleNames.Contains(data.ModuleILPath.ToLower()) || _testModuleNames.Contains(data.ModuleNativePath.ToLower()))
return true;
return false;
}
/// <summary>
/// Set the process to monitor events for given its Id. This should be set immediately after
/// calling Process.Start to ensure no module load events are missed for the runtime instance.
/// </summary>
public void SetProcessId(int pid)
{
_pid = pid;
}
public IEnumerable<(string MethodName, string assemblyName, bool ReadyToRunRejected)> JittedMethods => _methodsJitted;
/// <summary>
/// Returns the number of test assemblies that were loaded by the runtime