Skip to content

Commit 5a032f5

Browse files
authored
Merge pull request #181 from pjanotti/tracker.type.per.module.rebase
Perf: create a tracker per module and use indexes to improve performance
2 parents 30c000b + 28f2b0e commit 5a032f5

File tree

14 files changed

+413
-125
lines changed

14 files changed

+413
-125
lines changed

Diff for: coverlet.sln

-15
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.core.tests", "test
1515
EndProject
1616
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.console", "src\coverlet.console\coverlet.console.csproj", "{F3DBE7C3-ABBB-4B8B-A6CB-A1D3D607163E}"
1717
EndProject
18-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.tracker", "src\coverlet.tracker\coverlet.tracker.csproj", "{F4273009-536D-4999-A126-B0A2E3AA3E70}"
19-
EndProject
2018
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "coverlet.testsubject", "test\coverlet.testsubject\coverlet.testsubject.csproj", "{AE117FAA-C21D-4F23-917E-0C8050614750}"
2119
EndProject
2220
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "coverlet.core.performancetest", "test\coverlet.core.performancetest\coverlet.core.performancetest.csproj", "{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}"
@@ -79,18 +77,6 @@ Global
7977
{F3DBE7C3-ABBB-4B8B-A6CB-A1D3D607163E}.Release|x64.Build.0 = Release|Any CPU
8078
{F3DBE7C3-ABBB-4B8B-A6CB-A1D3D607163E}.Release|x86.ActiveCfg = Release|Any CPU
8179
{F3DBE7C3-ABBB-4B8B-A6CB-A1D3D607163E}.Release|x86.Build.0 = Release|Any CPU
82-
{F4273009-536D-4999-A126-B0A2E3AA3E70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
83-
{F4273009-536D-4999-A126-B0A2E3AA3E70}.Debug|Any CPU.Build.0 = Debug|Any CPU
84-
{F4273009-536D-4999-A126-B0A2E3AA3E70}.Debug|x64.ActiveCfg = Debug|Any CPU
85-
{F4273009-536D-4999-A126-B0A2E3AA3E70}.Debug|x64.Build.0 = Debug|Any CPU
86-
{F4273009-536D-4999-A126-B0A2E3AA3E70}.Debug|x86.ActiveCfg = Debug|Any CPU
87-
{F4273009-536D-4999-A126-B0A2E3AA3E70}.Debug|x86.Build.0 = Debug|Any CPU
88-
{F4273009-536D-4999-A126-B0A2E3AA3E70}.Release|Any CPU.ActiveCfg = Release|Any CPU
89-
{F4273009-536D-4999-A126-B0A2E3AA3E70}.Release|Any CPU.Build.0 = Release|Any CPU
90-
{F4273009-536D-4999-A126-B0A2E3AA3E70}.Release|x64.ActiveCfg = Release|Any CPU
91-
{F4273009-536D-4999-A126-B0A2E3AA3E70}.Release|x64.Build.0 = Release|Any CPU
92-
{F4273009-536D-4999-A126-B0A2E3AA3E70}.Release|x86.ActiveCfg = Release|Any CPU
93-
{F4273009-536D-4999-A126-B0A2E3AA3E70}.Release|x86.Build.0 = Release|Any CPU
9480
{AE117FAA-C21D-4F23-917E-0C8050614750}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
9581
{AE117FAA-C21D-4F23-917E-0C8050614750}.Debug|Any CPU.Build.0 = Debug|Any CPU
9682
{AE117FAA-C21D-4F23-917E-0C8050614750}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -124,7 +110,6 @@ Global
124110
{FA73E423-9790-4F35-B018-3C4E3CA338BA} = {E877EBA4-E78B-4F7D-A2D3-1E070FED04CD}
125111
{E7637CC6-43F7-461A-A0BF-3C14562419BD} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
126112
{F3DBE7C3-ABBB-4B8B-A6CB-A1D3D607163E} = {E877EBA4-E78B-4F7D-A2D3-1E070FED04CD}
127-
{F4273009-536D-4999-A126-B0A2E3AA3E70} = {E877EBA4-E78B-4F7D-A2D3-1E070FED04CD}
128113
{AE117FAA-C21D-4F23-917E-0C8050614750} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
129114
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
130115
EndGlobalSection

Diff for: src/coverlet.core/Coverage.cs

+14-16
Original file line numberDiff line numberDiff line change
@@ -164,32 +164,30 @@ private void CalculateCoverage()
164164
List<Document> documents = result.Documents.Values.ToList();
165165

166166
using (var fs = new FileStream(result.HitsFilePath, FileMode.Open))
167-
using (var sr = new StreamReader(fs))
167+
using (var br = new BinaryReader(fs))
168168
{
169-
string row;
170-
while ((row = sr.ReadLine()) != null)
169+
int hitCandidatesCount = br.ReadInt32();
170+
171+
// TODO: hitCandidatesCount should be verified against result.HitCandidates.Count
172+
173+
var documentsList = result.Documents.Values.ToList();
174+
175+
for (int i = 0; i < hitCandidatesCount; ++i)
171176
{
172-
var info = row.Split(',');
173-
// Ignore malformed lines
174-
if (info.Length != 5)
175-
continue;
177+
var hitLocation = result.HitCandidates[i];
176178

177-
bool isBranch = info[0] == "B";
178-
var document = documents[int.Parse(info[1])];
179+
var document = documentsList[hitLocation.docIndex];
179180

180-
int start = int.Parse(info[2]);
181-
int hits = int.Parse(info[4]);
181+
int hits = br.ReadInt32();
182182

183-
if (isBranch)
183+
if (hitLocation.isBranch)
184184
{
185-
int ordinal = int.Parse(info[3]);
186-
var branch = document.Branches[(start, ordinal)];
185+
var branch = document.Branches[(hitLocation.start, hitLocation.end)];
187186
branch.Hits += hits;
188187
}
189188
else
190189
{
191-
int end = int.Parse(info[3]);
192-
for (int j = start; j <= end; j++)
190+
for (int j = hitLocation.start; j <= hitLocation.end; j++)
193191
{
194192
var line = document.Lines[j];
195193
line.Hits += hits;

Diff for: src/coverlet.core/Helpers/InstrumentationHelper.cs

-11
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,6 @@ public static bool HasPdb(string module)
4040
}
4141
}
4242

43-
public static void CopyCoverletDependency(string module)
44-
{
45-
var moduleFileName = Path.GetFileName(module);
46-
if (Path.GetFileName(typeof(Coverage).Assembly.Location) == moduleFileName)
47-
return;
48-
49-
var directory = Path.GetDirectoryName(module);
50-
var assembly = typeof(Coverlet.Tracker.CoverageTracker).Assembly;
51-
File.Copy(assembly.Location, Path.Combine(directory, Path.GetFileName(assembly.Location)), true);
52-
}
53-
5443
public static void BackupOriginalModule(string module, string identifier)
5544
{
5645
var backupPath = GetBackupPath(module, identifier);

Diff for: src/coverlet.core/Instrumentation/Instrumenter.cs

+128-27
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Diagnostics;
23
using System.Diagnostics.CodeAnalysis;
34
using System.IO;
45
using System.Linq;
@@ -7,7 +8,6 @@
78
using Coverlet.Core.Attributes;
89
using Coverlet.Core.Helpers;
910
using Coverlet.Core.Symbols;
10-
using Coverlet.Tracker;
1111

1212
using Mono.Cecil;
1313
using Mono.Cecil.Cil;
@@ -22,8 +22,12 @@ internal class Instrumenter
2222
private readonly string[] _excludeFilters;
2323
private readonly string[] _includeFilters;
2424
private readonly string[] _excludedFiles;
25-
private readonly static Lazy<MethodInfo> _markExecutedMethodLoader = new Lazy<MethodInfo>(GetMarkExecutedMethod);
2625
private InstrumenterResult _result;
26+
private FieldDefinition _customTrackerHitsArray;
27+
private FieldDefinition _customTrackerHitsFilePath;
28+
private ILProcessor _customTrackerClassConstructorIl;
29+
private TypeDefinition _customTrackerTypeDef;
30+
private MethodReference _customTrackerRecordHitMethod;
2731

2832
public Instrumenter(string module, string identifier, string[] excludeFilters, string[] includeFilters, string[] excludedFiles)
2933
{
@@ -51,7 +55,6 @@ public InstrumenterResult Instrument()
5155
};
5256

5357
InstrumentModule();
54-
InstrumentationHelper.CopyCoverletDependency(_module);
5558

5659
return _result;
5760
}
@@ -67,20 +70,119 @@ private void InstrumentModule()
6770
using (var module = ModuleDefinition.ReadModule(stream, parameters))
6871
{
6972
var types = module.GetTypes();
73+
AddCustomModuleTrackerToModule(module);
74+
7075
foreach (TypeDefinition type in types)
7176
{
7277
var actualType = type.DeclaringType ?? type;
7378
if (!actualType.CustomAttributes.Any(IsExcludeAttribute)
79+
&& actualType.Namespace != "Coverlet.Core.Instrumentation.Tracker"
7480
&& !InstrumentationHelper.IsTypeExcluded(_module, actualType.FullName, _excludeFilters)
7581
&& InstrumentationHelper.IsTypeIncluded(_module, actualType.FullName, _includeFilters))
7682
InstrumentType(type);
7783
}
7884

85+
// Fixup the custom tracker class constructor, according to all instrumented types
86+
Instruction lastInstr = _customTrackerClassConstructorIl.Body.Instructions.Last();
87+
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldc_I4, _result.HitCandidates.Count));
88+
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Newarr, module.TypeSystem.Int32));
89+
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsArray));
90+
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldstr, _result.HitsFilePath));
91+
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsFilePath));
92+
7993
module.Write(stream);
8094
}
8195
}
8296
}
8397

98+
private void AddCustomModuleTrackerToModule(ModuleDefinition module)
99+
{
100+
using (AssemblyDefinition coverletInstrumentationAssembly = AssemblyDefinition.ReadAssembly(typeof(Instrumenter).Assembly.Location))
101+
{
102+
TypeDefinition moduleTrackerTemplate = coverletInstrumentationAssembly.MainModule.GetType(
103+
"Coverlet.Core.Instrumentation", nameof(ModuleTrackerTemplate));
104+
105+
_customTrackerTypeDef = new TypeDefinition(
106+
"Coverlet.Core.Instrumentation.Tracker", Path.GetFileNameWithoutExtension(module.Name) + "_" + _identifier, moduleTrackerTemplate.Attributes);
107+
108+
_customTrackerTypeDef.BaseType = module.TypeSystem.Object;
109+
foreach (FieldDefinition fieldDef in moduleTrackerTemplate.Fields)
110+
{
111+
var fieldClone = new FieldDefinition(fieldDef.Name, fieldDef.Attributes, fieldDef.FieldType);
112+
fieldClone.FieldType = module.ImportReference(fieldClone.FieldType);
113+
114+
_customTrackerTypeDef.Fields.Add(fieldClone);
115+
116+
if (fieldClone.Name == "HitsArray")
117+
_customTrackerHitsArray = fieldClone;
118+
else if (fieldClone.Name == "HitsFilePath")
119+
_customTrackerHitsFilePath = fieldClone;
120+
}
121+
122+
foreach (MethodDefinition methodDef in moduleTrackerTemplate.Methods)
123+
{
124+
MethodDefinition methodOnCustomType = new MethodDefinition(methodDef.Name, methodDef.Attributes, methodDef.ReturnType);
125+
126+
if (methodDef.Name == "RecordHit")
127+
{
128+
foreach (var parameter in methodDef.Parameters)
129+
{
130+
methodOnCustomType.Parameters.Add(new ParameterDefinition(module.ImportReference(parameter.ParameterType)));
131+
}
132+
}
133+
134+
foreach (var variable in methodDef.Body.Variables)
135+
{
136+
methodOnCustomType.Body.Variables.Add(new VariableDefinition(module.ImportReference(variable.VariableType)));
137+
}
138+
139+
methodOnCustomType.Body.InitLocals = methodDef.Body.InitLocals;
140+
141+
ILProcessor ilProcessor = methodOnCustomType.Body.GetILProcessor();
142+
if (methodDef.Name == ".cctor")
143+
_customTrackerClassConstructorIl = ilProcessor;
144+
145+
foreach (Instruction instr in methodDef.Body.Instructions)
146+
{
147+
if (instr.Operand is MethodReference methodReference)
148+
{
149+
if (!methodReference.FullName.Contains(moduleTrackerTemplate.Namespace))
150+
{
151+
// External method references, just import then
152+
instr.Operand = module.ImportReference(methodReference);
153+
}
154+
else
155+
{
156+
// Move to the custom type
157+
instr.Operand = new MethodReference(
158+
methodReference.Name, methodReference.ReturnType, _customTrackerTypeDef);
159+
}
160+
}
161+
else if (instr.Operand is FieldReference fieldReference)
162+
{
163+
instr.Operand = _customTrackerTypeDef.Fields.Single(fd => fd.Name == fieldReference.Name);
164+
}
165+
else if (instr.Operand is TypeReference typeReference)
166+
{
167+
instr.Operand = module.ImportReference(typeReference);
168+
}
169+
170+
ilProcessor.Append(instr);
171+
}
172+
173+
foreach (var handler in methodDef.Body.ExceptionHandlers)
174+
methodOnCustomType.Body.ExceptionHandlers.Add(handler);
175+
176+
_customTrackerTypeDef.Methods.Add(methodOnCustomType);
177+
}
178+
179+
module.Types.Add(_customTrackerTypeDef);
180+
}
181+
182+
Debug.Assert(_customTrackerHitsArray != null);
183+
Debug.Assert(_customTrackerClassConstructorIl != null);
184+
}
185+
84186
private void InstrumentType(TypeDefinition type)
85187
{
86188
var methods = type.GetMethods();
@@ -143,7 +245,7 @@ private void InstrumentIL(MethodDefinition method)
143245
foreach (ExceptionHandler handler in processor.Body.ExceptionHandlers)
144246
ReplaceExceptionHandlerBoundary(handler, instruction, target);
145247

146-
index += 3;
248+
index += 2;
147249
}
148250

149251
foreach (var _branchTarget in targetedBranchPoints)
@@ -164,7 +266,7 @@ private void InstrumentIL(MethodDefinition method)
164266
foreach (ExceptionHandler handler in processor.Body.ExceptionHandlers)
165267
ReplaceExceptionHandlerBoundary(handler, instruction, target);
166268

167-
index += 3;
269+
index += 2;
168270
}
169271

170272
index++;
@@ -188,17 +290,10 @@ private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor
188290
document.Lines.Add(i, new Line { Number = i, Class = method.DeclaringType.FullName, Method = method.FullName });
189291
}
190292

191-
string marker = $"L,{document.Index},{sequencePoint.StartLine},{sequencePoint.EndLine}";
293+
var entry = (false, document.Index, sequencePoint.StartLine, sequencePoint.EndLine);
294+
_result.HitCandidates.Add(entry);
192295

193-
var pathInstr = Instruction.Create(OpCodes.Ldstr, _result.HitsFilePath);
194-
var markInstr = Instruction.Create(OpCodes.Ldstr, marker);
195-
var callInstr = Instruction.Create(OpCodes.Call, processor.Body.Method.Module.ImportReference(_markExecutedMethodLoader.Value));
196-
197-
processor.InsertBefore(instruction, callInstr);
198-
processor.InsertBefore(callInstr, markInstr);
199-
processor.InsertBefore(markInstr, pathInstr);
200-
201-
return pathInstr;
296+
return AddInstrumentationInstructions(method, processor, instruction, _result.HitCandidates.Count - 1);
202297
}
203298

204299
private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor processor, Instruction instruction, BranchPoint branchPoint)
@@ -225,17 +320,28 @@ private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor
225320
}
226321
);
227322

228-
string marker = $"B,{document.Index},{branchPoint.StartLine},{branchPoint.Ordinal}";
323+
var entry = (true, document.Index, branchPoint.StartLine, (int)branchPoint.Ordinal);
324+
_result.HitCandidates.Add(entry);
325+
326+
return AddInstrumentationInstructions(method, processor, instruction, _result.HitCandidates.Count - 1);
327+
}
328+
329+
private Instruction AddInstrumentationInstructions(MethodDefinition method, ILProcessor processor, Instruction instruction, int hitEntryIndex)
330+
{
331+
if (_customTrackerRecordHitMethod == null)
332+
{
333+
_customTrackerRecordHitMethod = new MethodReference(
334+
"RecordHit", method.Module.TypeSystem.Void, _customTrackerTypeDef);
335+
_customTrackerRecordHitMethod.Parameters.Add(new ParameterDefinition(method.Module.TypeSystem.Int32));
336+
}
229337

230-
var pathInstr = Instruction.Create(OpCodes.Ldstr, _result.HitsFilePath);
231-
var markInstr = Instruction.Create(OpCodes.Ldstr, marker);
232-
var callInstr = Instruction.Create(OpCodes.Call, processor.Body.Method.Module.ImportReference(_markExecutedMethodLoader.Value));
338+
var indxInstr = Instruction.Create(OpCodes.Ldc_I4, hitEntryIndex);
339+
var callInstr = Instruction.Create(OpCodes.Call, _customTrackerRecordHitMethod);
233340

234341
processor.InsertBefore(instruction, callInstr);
235-
processor.InsertBefore(callInstr, markInstr);
236-
processor.InsertBefore(markInstr, pathInstr);
342+
processor.InsertBefore(callInstr, indxInstr);
237343

238-
return pathInstr;
344+
return indxInstr;
239345
}
240346

241347
private static void ReplaceInstructionTarget(Instruction instruction, Instruction oldTarget, Instruction newTarget)
@@ -301,10 +407,5 @@ private static Mono.Cecil.Cil.MethodBody GetMethodBody(MethodDefinition method)
301407
return null;
302408
}
303409
}
304-
305-
private static MethodInfo GetMarkExecutedMethod()
306-
{
307-
return typeof(CoverageTracker).GetMethod(nameof(CoverageTracker.MarkExecuted));
308-
}
309410
}
310411
}

Diff for: src/coverlet.core/Instrumentation/InstrumenterResult.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ public Document()
2828

2929
public string Path;
3030
public int Index;
31-
3231
public Dictionary<int, Line> Lines { get; private set; }
3332
public Dictionary<(int Line, int Ordinal), Branch> Branches { get; private set; }
3433
}
@@ -38,11 +37,13 @@ internal class InstrumenterResult
3837
public InstrumenterResult()
3938
{
4039
Documents = new Dictionary<string, Document>();
41-
}
40+
HitCandidates = new List<(bool isBranch, int docIndex, int start, int end)>();
41+
}
4242

4343
public string Module;
4444
public string HitsFilePath;
4545
public string ModulePath;
4646
public Dictionary<string, Document> Documents { get; private set; }
47+
public List<(bool isBranch, int docIndex, int start, int end)> HitCandidates { get; private set; }
4748
}
4849
}

0 commit comments

Comments
 (0)