Terrible performance for heavily concurrent

This commit is contained in:
Paulo Janotti
2018-08-20 15:33:37 -07:00
parent 30c000b3b3
commit 69f67db366
11 changed files with 143 additions and 110 deletions
+14 -16
View File
@@ -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);
+124 -28
View File
@@ -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; }
}
}
-4
View File
@@ -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()
{