Terrible performance for heavily concurrent
This commit is contained in:
@@ -164,32 +164,30 @@ namespace Coverlet.Core
|
||||
List<Document> documents = result.Documents.Values.ToList();
|
||||
|
||||
using (var fs = new FileStream(result.HitsFilePath, FileMode.Open))
|
||||
using (var sr = new StreamReader(fs))
|
||||
using (var br = new BinaryReader(fs))
|
||||
{
|
||||
string row;
|
||||
while ((row = sr.ReadLine()) != null)
|
||||
int hitCandidatesCount = br.ReadInt32();
|
||||
|
||||
// TODO: hitCandidatesCount should be verified against result.HitCandidates.Count
|
||||
|
||||
var documentsList = result.Documents.Values.ToList();
|
||||
|
||||
for (int i = 0; i < hitCandidatesCount; ++i)
|
||||
{
|
||||
var info = row.Split(',');
|
||||
// Ignore malformed lines
|
||||
if (info.Length != 5)
|
||||
continue;
|
||||
var hitLocation = result.HitCandidates[i];
|
||||
|
||||
bool isBranch = info[0] == "B";
|
||||
var document = documents[int.Parse(info[1])];
|
||||
var document = documentsList[hitLocation.docIndex];
|
||||
|
||||
int start = int.Parse(info[2]);
|
||||
int hits = int.Parse(info[4]);
|
||||
int hits = br.ReadInt32();
|
||||
|
||||
if (isBranch)
|
||||
if (hitLocation.isBranch)
|
||||
{
|
||||
int ordinal = int.Parse(info[3]);
|
||||
var branch = document.Branches[(start, ordinal)];
|
||||
var branch = document.Branches[(hitLocation.start, hitLocation.end)];
|
||||
branch.Hits += hits;
|
||||
}
|
||||
else
|
||||
{
|
||||
int end = int.Parse(info[3]);
|
||||
for (int j = start; j <= end; j++)
|
||||
for (int j = hitLocation.start; j <= hitLocation.end; j++)
|
||||
{
|
||||
var line = document.Lines[j];
|
||||
line.Hits += hits;
|
||||
|
||||
@@ -40,17 +40,6 @@ namespace Coverlet.Core.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
public static void CopyCoverletDependency(string module)
|
||||
{
|
||||
var moduleFileName = Path.GetFileName(module);
|
||||
if (Path.GetFileName(typeof(Coverage).Assembly.Location) == moduleFileName)
|
||||
return;
|
||||
|
||||
var directory = Path.GetDirectoryName(module);
|
||||
var assembly = typeof(Coverlet.Tracker.CoverageTracker).Assembly;
|
||||
File.Copy(assembly.Location, Path.Combine(directory, Path.GetFileName(assembly.Location)), true);
|
||||
}
|
||||
|
||||
public static void BackupOriginalModule(string module, string identifier)
|
||||
{
|
||||
var backupPath = GetBackupPath(module, identifier);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -7,7 +8,6 @@ using System.Reflection;
|
||||
using Coverlet.Core.Attributes;
|
||||
using Coverlet.Core.Helpers;
|
||||
using Coverlet.Core.Symbols;
|
||||
using Coverlet.Tracker;
|
||||
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
@@ -22,8 +22,11 @@ namespace Coverlet.Core.Instrumentation
|
||||
private readonly string[] _excludeFilters;
|
||||
private readonly string[] _includeFilters;
|
||||
private readonly string[] _excludedFiles;
|
||||
private readonly static Lazy<MethodInfo> _markExecutedMethodLoader = new Lazy<MethodInfo>(GetMarkExecutedMethod);
|
||||
private InstrumenterResult _result;
|
||||
private FieldDefinition _customModuleTrackerHitsArray;
|
||||
private FieldDefinition _customModuleTrackerHitsFilePath;
|
||||
private ILProcessor _customModuleTrackerClassConstructorIl;
|
||||
private MethodReference _cachedInterlockedIncMethod;
|
||||
|
||||
public Instrumenter(string module, string identifier, string[] excludeFilters, string[] includeFilters, string[] excludedFiles)
|
||||
{
|
||||
@@ -51,7 +54,6 @@ namespace Coverlet.Core.Instrumentation
|
||||
};
|
||||
|
||||
InstrumentModule();
|
||||
InstrumentationHelper.CopyCoverletDependency(_module);
|
||||
|
||||
return _result;
|
||||
}
|
||||
@@ -67,20 +69,109 @@ namespace Coverlet.Core.Instrumentation
|
||||
using (var module = ModuleDefinition.ReadModule(stream, parameters))
|
||||
{
|
||||
var types = module.GetTypes();
|
||||
AddCustomModuleTrackerToModule(module);
|
||||
|
||||
foreach (TypeDefinition type in types)
|
||||
{
|
||||
var actualType = type.DeclaringType ?? type;
|
||||
if (!actualType.CustomAttributes.Any(IsExcludeAttribute)
|
||||
&& actualType.Namespace != "Coverlet.Core.Instrumentation.Tracker"
|
||||
&& !InstrumentationHelper.IsTypeExcluded(_module, actualType.FullName, _excludeFilters)
|
||||
&& InstrumentationHelper.IsTypeIncluded(_module, actualType.FullName, _includeFilters))
|
||||
InstrumentType(type);
|
||||
}
|
||||
|
||||
// Fixup the custom tracker class constructor, according to all instrumented types
|
||||
Instruction lastInstr = _customModuleTrackerClassConstructorIl.Body.Instructions.Last();
|
||||
_customModuleTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldc_I4, _result.HitCandidates.Count));
|
||||
_customModuleTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Newarr, module.TypeSystem.Int32));
|
||||
_customModuleTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customModuleTrackerHitsArray));
|
||||
_customModuleTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldstr, _result.HitsFilePath));
|
||||
_customModuleTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customModuleTrackerHitsFilePath));
|
||||
|
||||
module.Write(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddCustomModuleTrackerToModule(ModuleDefinition module)
|
||||
{
|
||||
using (AssemblyDefinition xad = AssemblyDefinition.ReadAssembly(typeof(Instrumenter).Assembly.Location))
|
||||
{
|
||||
TypeDefinition moduleTrackerTemplate = xad.MainModule.GetType(
|
||||
"Coverlet.Core.Instrumentation", nameof(ModuleTrackerTemplate));
|
||||
|
||||
TypeDefinition customTrackerTypeDef = new TypeDefinition(
|
||||
"Coverlet.Core.Instrumentation.Tracker", Path.GetFileNameWithoutExtension(module.Name) + "_" + _identifier, moduleTrackerTemplate.Attributes);
|
||||
|
||||
customTrackerTypeDef.BaseType = module.TypeSystem.Object;
|
||||
foreach (FieldDefinition fieldDef in moduleTrackerTemplate.Fields)
|
||||
{
|
||||
var fieldClone = new FieldDefinition(fieldDef.Name, fieldDef.Attributes, fieldDef.FieldType);
|
||||
customTrackerTypeDef.Fields.Add(fieldClone);
|
||||
|
||||
if (fieldClone.Name == "HitsArray")
|
||||
_customModuleTrackerHitsArray = fieldClone;
|
||||
else if (fieldClone.Name == "HitsFilePath")
|
||||
_customModuleTrackerHitsFilePath = fieldClone;
|
||||
}
|
||||
|
||||
foreach (MethodDefinition methodDef in moduleTrackerTemplate.Methods)
|
||||
{
|
||||
MethodDefinition methodOnCustomType = new MethodDefinition(methodDef.Name, methodDef.Attributes, methodDef.ReturnType);
|
||||
|
||||
foreach (var variable in methodDef.Body.Variables)
|
||||
{
|
||||
methodOnCustomType.Body.Variables.Add(new VariableDefinition(module.ImportReference(variable.VariableType)));
|
||||
}
|
||||
|
||||
methodOnCustomType.Body.InitLocals = methodDef.Body.InitLocals;
|
||||
|
||||
ILProcessor ilProcessor = methodOnCustomType.Body.GetILProcessor();
|
||||
if (methodDef.Name == ".cctor")
|
||||
_customModuleTrackerClassConstructorIl = ilProcessor;
|
||||
|
||||
foreach (Instruction instr in methodDef.Body.Instructions)
|
||||
{
|
||||
if (instr.Operand is MethodReference methodReference)
|
||||
{
|
||||
if (!methodReference.FullName.Contains(moduleTrackerTemplate.Namespace))
|
||||
{
|
||||
// External method references, just import then
|
||||
instr.Operand = module.ImportReference(methodReference);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Move to the custom type
|
||||
instr.Operand = new MethodReference(
|
||||
methodReference.Name, methodReference.ReturnType, customTrackerTypeDef);
|
||||
}
|
||||
}
|
||||
else if (instr.Operand is FieldReference fieldReference)
|
||||
{
|
||||
instr.Operand = customTrackerTypeDef.Fields.Single(fd => fd.Name == fieldReference.Name);
|
||||
}
|
||||
else if (instr.Operand is TypeReference typeReference)
|
||||
{
|
||||
instr.Operand = module.ImportReference(typeReference);
|
||||
}
|
||||
|
||||
ilProcessor.Append(instr);
|
||||
}
|
||||
|
||||
foreach (var handler in methodDef.Body.ExceptionHandlers)
|
||||
methodOnCustomType.Body.ExceptionHandlers.Add(handler);
|
||||
|
||||
customTrackerTypeDef.Methods.Add(methodOnCustomType);
|
||||
}
|
||||
|
||||
module.Types.Add(customTrackerTypeDef);
|
||||
}
|
||||
|
||||
Debug.Assert(_customModuleTrackerHitsArray != null);
|
||||
Debug.Assert(_customModuleTrackerClassConstructorIl != null);
|
||||
}
|
||||
|
||||
private void InstrumentType(TypeDefinition type)
|
||||
{
|
||||
var methods = type.GetMethods();
|
||||
@@ -143,7 +234,7 @@ namespace Coverlet.Core.Instrumentation
|
||||
foreach (ExceptionHandler handler in processor.Body.ExceptionHandlers)
|
||||
ReplaceExceptionHandlerBoundary(handler, instruction, target);
|
||||
|
||||
index += 3;
|
||||
index += 5;
|
||||
}
|
||||
|
||||
foreach (var _branchTarget in targetedBranchPoints)
|
||||
@@ -164,7 +255,7 @@ namespace Coverlet.Core.Instrumentation
|
||||
foreach (ExceptionHandler handler in processor.Body.ExceptionHandlers)
|
||||
ReplaceExceptionHandlerBoundary(handler, instruction, target);
|
||||
|
||||
index += 3;
|
||||
index += 5;
|
||||
}
|
||||
|
||||
index++;
|
||||
@@ -188,17 +279,10 @@ namespace Coverlet.Core.Instrumentation
|
||||
document.Lines.Add(i, new Line { Number = i, Class = method.DeclaringType.FullName, Method = method.FullName });
|
||||
}
|
||||
|
||||
string marker = $"L,{document.Index},{sequencePoint.StartLine},{sequencePoint.EndLine}";
|
||||
var entry = (false, document.Index, sequencePoint.StartLine, sequencePoint.EndLine);
|
||||
_result.HitCandidates.Add(entry);
|
||||
|
||||
var pathInstr = Instruction.Create(OpCodes.Ldstr, _result.HitsFilePath);
|
||||
var markInstr = Instruction.Create(OpCodes.Ldstr, marker);
|
||||
var callInstr = Instruction.Create(OpCodes.Call, processor.Body.Method.Module.ImportReference(_markExecutedMethodLoader.Value));
|
||||
|
||||
processor.InsertBefore(instruction, callInstr);
|
||||
processor.InsertBefore(callInstr, markInstr);
|
||||
processor.InsertBefore(markInstr, pathInstr);
|
||||
|
||||
return pathInstr;
|
||||
return AddInstrumentationInstructions(method, processor, instruction, _result.HitCandidates.Count);
|
||||
}
|
||||
|
||||
private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor processor, Instruction instruction, BranchPoint branchPoint)
|
||||
@@ -225,17 +309,34 @@ namespace Coverlet.Core.Instrumentation
|
||||
}
|
||||
);
|
||||
|
||||
string marker = $"B,{document.Index},{branchPoint.StartLine},{branchPoint.Ordinal}";
|
||||
var entry = (true, document.Index, branchPoint.StartLine, (int)branchPoint.Ordinal);
|
||||
_result.HitCandidates.Add(entry);
|
||||
|
||||
var pathInstr = Instruction.Create(OpCodes.Ldstr, _result.HitsFilePath);
|
||||
var markInstr = Instruction.Create(OpCodes.Ldstr, marker);
|
||||
var callInstr = Instruction.Create(OpCodes.Call, processor.Body.Method.Module.ImportReference(_markExecutedMethodLoader.Value));
|
||||
return AddInstrumentationInstructions(method, processor, instruction, _result.HitCandidates.Count);
|
||||
}
|
||||
|
||||
processor.InsertBefore(instruction, callInstr);
|
||||
processor.InsertBefore(callInstr, markInstr);
|
||||
processor.InsertBefore(markInstr, pathInstr);
|
||||
private Instruction AddInstrumentationInstructions(MethodDefinition method, ILProcessor processor, Instruction instruction, int hitEntryIndex)
|
||||
{
|
||||
if (_cachedInterlockedIncMethod == null)
|
||||
{
|
||||
_cachedInterlockedIncMethod = new MethodReference(
|
||||
"Increment", method.Module.TypeSystem.Int32, method.Module.ImportReference(typeof(System.Threading.Interlocked)));
|
||||
_cachedInterlockedIncMethod.Parameters.Add(new ParameterDefinition(new ByReferenceType(method.Module.TypeSystem.Int32)));
|
||||
}
|
||||
|
||||
return pathInstr;
|
||||
var sfldInstr = Instruction.Create(OpCodes.Ldsfld, _customModuleTrackerHitsArray);
|
||||
var indxInstr = Instruction.Create(OpCodes.Ldc_I4, hitEntryIndex);
|
||||
var arefInstr = Instruction.Create(OpCodes.Ldelema, method.Module.TypeSystem.Int32);
|
||||
var callInstr = Instruction.Create(OpCodes.Call, _cachedInterlockedIncMethod);
|
||||
var popInstr = Instruction.Create(OpCodes.Pop);
|
||||
|
||||
processor.InsertBefore(instruction, popInstr);
|
||||
processor.InsertBefore(popInstr, callInstr);
|
||||
processor.InsertBefore(callInstr, arefInstr);
|
||||
processor.InsertBefore(arefInstr, indxInstr);
|
||||
processor.InsertBefore(indxInstr, sfldInstr);
|
||||
|
||||
return sfldInstr;
|
||||
}
|
||||
|
||||
private static void ReplaceInstructionTarget(Instruction instruction, Instruction oldTarget, Instruction newTarget)
|
||||
@@ -301,10 +402,5 @@ namespace Coverlet.Core.Instrumentation
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static MethodInfo GetMarkExecutedMethod()
|
||||
{
|
||||
return typeof(CoverageTracker).GetMethod(nameof(CoverageTracker.MarkExecuted));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,6 @@ namespace Coverlet.Core.Instrumentation
|
||||
|
||||
public string Path;
|
||||
public int Index;
|
||||
|
||||
public Dictionary<int, Line> Lines { get; private set; }
|
||||
public Dictionary<(int Line, int Ordinal), Branch> Branches { get; private set; }
|
||||
}
|
||||
@@ -38,11 +37,13 @@ namespace Coverlet.Core.Instrumentation
|
||||
public InstrumenterResult()
|
||||
{
|
||||
Documents = new Dictionary<string, Document>();
|
||||
}
|
||||
HitCandidates = new List<(bool isBranch, int docIndex, int start, int end)>();
|
||||
}
|
||||
|
||||
public string Module;
|
||||
public string HitsFilePath;
|
||||
public string ModulePath;
|
||||
public Dictionary<string, Document> Documents { get; private set; }
|
||||
public List<(bool isBranch, int docIndex, int start, int end)> HitCandidates { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,4 @@
|
||||
<PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" Version="2.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(MSBuildThisFileDirectory)..\coverlet.tracker\coverlet.tracker.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Coverlet.Tracker.Extensions
|
||||
{
|
||||
internal static class DictionaryExtensions
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
public static bool TryAdd<T, U>(this Dictionary<T, U> dictionary, T key, U value)
|
||||
{
|
||||
if (dictionary.ContainsKey(key))
|
||||
return false;
|
||||
|
||||
dictionary.Add(key, value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
[assembly: System.Reflection.AssemblyKeyFileAttribute("coverlet.tracker.snk")]
|
||||
@@ -1,7 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
Binary file not shown.
@@ -14,8 +14,9 @@ namespace coverlet.core.performancetest
|
||||
/// </summary>
|
||||
public class PerformanceTest
|
||||
{
|
||||
[Theory(Skip = "Only enabled when explicitly testing performance.")]
|
||||
[Theory(/*Skip = "Only enabled when explicitly testing performance."*/)]
|
||||
[InlineData(150)]
|
||||
// [InlineData(20_000)]
|
||||
public void TestPerformance(int iterations)
|
||||
{
|
||||
var big = new BigClass();
|
||||
|
||||
@@ -38,17 +38,6 @@ namespace Coverlet.Core.Helpers.Tests
|
||||
Assert.True(File.Exists(backupPath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestCopyCoverletDependency()
|
||||
{
|
||||
var tempPath = Path.GetTempPath();
|
||||
var directory = Directory.CreateDirectory(Path.Combine(tempPath, "tempdir"));
|
||||
InstrumentationHelper.CopyCoverletDependency(Path.Combine(directory.FullName, "somemodule.dll"));
|
||||
|
||||
Assert.True(File.Exists(Path.Combine(directory.FullName, "coverlet.tracker.dll")));
|
||||
Directory.Delete(directory.FullName, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestIsValidFilterExpression()
|
||||
{
|
||||
@@ -64,17 +53,6 @@ namespace Coverlet.Core.Helpers.Tests
|
||||
Assert.False(InstrumentationHelper.IsValidFilterExpression(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestDontCopyCoverletDependency()
|
||||
{
|
||||
var tempPath = Path.GetTempPath();
|
||||
var directory = Directory.CreateDirectory(Path.Combine(tempPath, "tempdir"));
|
||||
InstrumentationHelper.CopyCoverletDependency(Path.Combine(directory.FullName, "coverlet.core.dll"));
|
||||
|
||||
Assert.False(File.Exists(Path.Combine(directory.FullName, "coverlet.core.dll")));
|
||||
Directory.Delete(directory.FullName, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestDeleteHitsFile()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user