Skip to content

Commit 138c8b0

Browse files
committed
Merge branch 'main-master' into memory-mapped-hit-counts
# Conflicts: # src/coverlet.template/ModuleTrackerTemplate.cs
2 parents 2e7d751 + a50c10a commit 138c8b0

File tree

7 files changed

+60
-67
lines changed

7 files changed

+60
-67
lines changed

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* text=auto

README.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,12 +434,39 @@ These steps must be followed before you attempt to open the solution in an IDE (
434434
435435
### Performance testing
436436
437-
There is a performance test for the hit counting instrumentation in the test project `coverlet.core.performancetest`. Build the project with the msbuild step above and then run:
437+
There is a simple performance test for the hit counting instrumentation in the test project `coverlet.core.performancetest`. Build the project with the msbuild step above and then run:
438438
439439
dotnet test /p:CollectCoverage=true test/coverlet.core.performancetest/
440440
441441
The duration of the test can be tweaked by changing the number of iterations in the `[InlineData]` in the `PerformanceTest` class.
442442
443+
For more realistic testing it is recommended to try out any changes to the hit counting code paths on large, realistic projects. If you don't have any handy https://github.com/dotnet/corefx is an excellent candidate. [This page](https://github.com/dotnet/corefx/blob/master/Documentation/building/code-coverage.md) describes how to run code coverage tests for both the full solution and for individual projects with coverlet from nuget. Suitable projects (listed in order of escalating test durations):
444+
445+
* System.Collections.Concurrent.Tests
446+
* System.Collections.Tests
447+
* System.Reflection.Metadata.Tests
448+
* System.Xml.Linq.Events.Tests
449+
* System.Runtime.Serialization.Formatters.Tests
450+
451+
Change to the directory of the library and run the msbuild code coverage command:
452+
453+
dotnet msbuild /t:BuildAndTest /p:Coverage=true
454+
455+
Look for a line like this:
456+
457+
----- start 18:13:36,59 =============== To repro directly: =====================================================
458+
459+
It is followed by a `pushd` command into the artefact directory, and a command to run coverlet. Run these to get to see the coverlet output and especially the test duration. E.g.:
460+
461+
C:\...\corefx\artifacts\tools\coverlet "System.Collections.Concurrent.Tests.dll" --target ...
462+
463+
To run with a development version of coverlet call `dotnet run` instead of the installed coverlet version, e.g.:
464+
465+
dotnet run -p C:\...\coverlet\src\coverlet.console\coverlet.console.csproj -c Release -- "System.Collections.Concurrent.Tests.dll" --target ...
466+
467+
468+
469+
443470
444471
## Code of Conduct
445472

src/coverlet.core/Instrumentation/Instrumenter.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ internal class Instrumenter
2525
private readonly string[] _excludedAttributes;
2626
private InstrumenterResult _result;
2727
private FieldDefinition _customTrackerHitsFilePath;
28-
private FieldDefinition _customTrackerHitsArraySize;
28+
private FieldDefinition _customTrackerHitsArray;
2929
private FieldDefinition _customTrackerHitsMemoryMapName;
3030
private ILProcessor _customTrackerClassConstructorIl;
3131
private TypeDefinition _customTrackerTypeDef;
@@ -101,7 +101,8 @@ private void InstrumentModule()
101101
_customTrackerClassConstructorIl.InsertBefore(firstInstr, Instruction.Create(OpCodes.Ldstr, _result.HitsFilePath));
102102
_customTrackerClassConstructorIl.InsertBefore(firstInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsFilePath));
103103
_customTrackerClassConstructorIl.InsertBefore(firstInstr, Instruction.Create(OpCodes.Ldc_I4, _result.HitCandidates.Count));
104-
_customTrackerClassConstructorIl.InsertBefore(firstInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsArraySize));
104+
_customTrackerClassConstructorIl.InsertBefore(firstInstr, Instruction.Create(OpCodes.Newarr, module.TypeSystem.Int32));
105+
_customTrackerClassConstructorIl.InsertBefore(firstInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsArray));
105106
_customTrackerClassConstructorIl.InsertBefore(firstInstr, Instruction.Create(OpCodes.Ldstr, _result.HitsResultGuid));
106107
_customTrackerClassConstructorIl.InsertBefore(firstInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsMemoryMapName));
107108

@@ -132,8 +133,8 @@ private void AddCustomModuleTrackerToModule(ModuleDefinition module)
132133
_customTrackerHitsFilePath = fieldClone;
133134
else if (fieldClone.Name == nameof(ModuleTrackerTemplate.HitsMemoryMapName))
134135
_customTrackerHitsMemoryMapName = fieldClone;
135-
else if (fieldClone.Name == nameof(ModuleTrackerTemplate.HitsArraySize))
136-
_customTrackerHitsArraySize = fieldClone;
136+
else if (fieldClone.Name == nameof(ModuleTrackerTemplate.HitsArray))
137+
_customTrackerHitsArray = fieldClone;
137138
}
138139

139140
foreach (MethodDefinition methodDef in moduleTrackerTemplate.Methods)

src/coverlet.core/Symbols/CecilSymbolHelper.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ public static class CecilSymbolHelper
2020
private const int StepOverLineCode = 0xFEEFEE;
2121
private static readonly Regex IsMovenext = new Regex(@"\<[^\s>]+\>\w__\w(\w)?::MoveNext\(\)$", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
2222

23-
public static bool IsMoveNext(string fullName)
24-
{
25-
return IsMovenext.IsMatch(fullName);
23+
public static bool IsMoveNext(string fullName)
24+
{
25+
return IsMovenext.IsMatch(fullName);
2626
}
2727

2828
public static List<BranchPoint> GetBranchPoints(MethodDefinition methodDefinition)

src/coverlet.template/ModuleTrackerTemplate.cs

Lines changed: 16 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -24,67 +24,35 @@ public static class ModuleTrackerTemplate
2424

2525
public static string HitsFilePath;
2626
public static string HitsMemoryMapName;
27-
public static int HitsArraySize;
27+
public static int[] HitsArray;
2828

29-
// Special case when instrumenting CoreLib, the thread static below prevents infinite loop in CoreLib
29+
// Special case when instrumenting CoreLib, the static below prevents infinite loop in CoreLib
3030
// while allowing the tracker template to call any of its types and functions.
31-
[ThreadStatic]
32-
private static bool t_isTracking;
33-
34-
[ThreadStatic]
35-
private static int[] t_threadHits;
36-
37-
private static readonly List<int[]> _threads;
31+
private static bool s_isTracking;
3832

3933
static ModuleTrackerTemplate()
4034
{
41-
t_isTracking = true;
42-
_threads = new List<int[]>(2 * Environment.ProcessorCount);
43-
35+
s_isTracking = true;
4436
AppDomain.CurrentDomain.ProcessExit += new EventHandler(UnloadModule);
4537
AppDomain.CurrentDomain.DomainUnload += new EventHandler(UnloadModule);
46-
t_isTracking = false;
38+
s_isTracking = false;
39+
4740
// At the end of the instrumentation of a module, the instrumenter needs to add code here
4841
// to initialize the static fields according to the values derived from the instrumentation of
4942
// the module.
5043
}
5144

5245
public static void RecordHit(int hitLocationIndex)
5346
{
54-
if (t_isTracking)
47+
if (s_isTracking)
5548
return;
5649

57-
if (t_threadHits == null)
58-
{
59-
t_isTracking = true;
60-
61-
lock (_threads)
62-
{
63-
if (t_threadHits == null)
64-
{
65-
t_threadHits = new int[HitsArraySize];
66-
_threads.Add(t_threadHits);
67-
}
68-
}
69-
70-
t_isTracking = false;
71-
}
72-
73-
++t_threadHits[hitLocationIndex];
50+
Interlocked.Increment(ref HitsArray[hitLocationIndex]);
7451
}
7552

7653
public static void UnloadModule(object sender, EventArgs e)
7754
{
78-
t_isTracking = true;
79-
80-
int[][] threads;
81-
lock (_threads)
82-
{
83-
threads = _threads.ToArray();
84-
85-
// Don't double-count if UnloadModule is called more than once
86-
_threads.Clear();
87-
}
55+
s_isTracking = true;
8856

8957
// The same module can be unloaded multiple times in the same process via different app domains.
9058
// Use a global mutex to ensure no concurrent access.
@@ -103,7 +71,7 @@ public static void UnloadModule(object sender, EventArgs e)
10371
}
10472
catch (PlatformNotSupportedException)
10573
{
106-
memoryMap = MemoryMappedFile.CreateFromFile(HitsFilePath, FileMode.Open, null, (HitsArraySize + HitsResultHeaderSize) * sizeof(int));
74+
memoryMap = MemoryMappedFile.CreateFromFile(HitsFilePath, FileMode.Open, null, (HitsArray.Length + HitsResultHeaderSize) * sizeof(int));
10775
}
10876

10977
// Tally hit counts from all threads in memory mapped area
@@ -123,18 +91,12 @@ public static void UnloadModule(object sender, EventArgs e)
12391
// the shared data.
12492
Interlocked.Increment(ref *(intPointer + HitsResultUnloadStarted));
12593

126-
for (var i = 0; i < HitsArraySize; i++)
94+
for (var i = 0; i < HitsArray.Length; i++)
12795
{
128-
var count = 0;
129-
130-
foreach (var threadHits in threads)
131-
{
132-
count += threadHits[i];
133-
}
96+
var count = HitsArray[i];
13497

13598
if (count > 0)
13699
{
137-
// There's a header of one int before the hit counts
138100
var hitLocationArrayOffset = intPointer + i + HitsResultHeaderSize;
139101

140102
// No need to use Interlocked here since the mutex ensures only one thread updates
@@ -155,12 +117,15 @@ public static void UnloadModule(object sender, EventArgs e)
155117
}
156118
finally
157119
{
120+
// Avoid double counting if unloaded multiple times (if that can happen?)
121+
HitsArray = new int[HitsArray.Length];
122+
158123
mutex.ReleaseMutex();
159124
memoryMap?.Dispose();
160125
}
161126
}
162127

163-
t_isTracking = false;
128+
s_isTracking = false;
164129
}
165130
}
166131
}

test/coverlet.core.tests/CoverageTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public void TestCoverage()
3636

3737
// The module hit tracker must signal to Coverage that it has done its job, so call it manually
3838
var instrumenterResult = coverage.Results.Single();
39-
ModuleTrackerTemplate.HitsArraySize = instrumenterResult.HitCandidates.Count;
39+
ModuleTrackerTemplate.HitsArray = new int[instrumenterResult.HitCandidates.Count + ModuleTrackerTemplate.HitsResultHeaderSize];
4040
ModuleTrackerTemplate.HitsFilePath = instrumenterResult.HitsFilePath;
4141
ModuleTrackerTemplate.HitsMemoryMapName = instrumenterResult.HitsResultGuid;
4242
ModuleTrackerTemplate.UnloadModule(null, null);

test/coverlet.core.tests/Instrumentation/ModuleTrackerTemplateTests.cs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ public class ModuleTrackerTemplateTests : IDisposable
3232

3333
public ModuleTrackerTemplateTests()
3434
{
35-
ModuleTrackerTemplate.HitsArraySize = 4;
35+
ModuleTrackerTemplate.HitsArray = new int[4 + ModuleTrackerTemplate.HitsResultHeaderSize];
3636
ModuleTrackerTemplate.HitsMemoryMapName = Guid.NewGuid().ToString();
3737
ModuleTrackerTemplate.HitsFilePath = Path.Combine(Path.GetTempPath(), $"coverlet.test_{ModuleTrackerTemplate.HitsMemoryMapName}");
3838

39-
var size = (ModuleTrackerTemplate.HitsArraySize + ModuleTrackerTemplate.HitsResultHeaderSize) * sizeof(int);
39+
var size = (ModuleTrackerTemplate.HitsArray.Length + ModuleTrackerTemplate.HitsResultHeaderSize) * sizeof(int);
4040

4141
try
4242
{
@@ -68,8 +68,7 @@ public void HitsFileCorrectlyWritten()
6868
[Fact]
6969
public void HitsOnMultipleThreadsCorrectlyCounted()
7070
{
71-
ModuleTrackerTemplate.HitsArraySize = 4;
72-
for (int i = 0; i < ModuleTrackerTemplate.HitsArraySize; ++i)
71+
for (int i = 0; i < 4; ++i)
7372
{
7473
var t = new Thread(HitIndex);
7574
t.Start(i);
@@ -133,7 +132,7 @@ private void RecordHits(params int[] hitCounts)
133132
// then dropped by UnloadModule the hit counting must be done
134133
// in a new thread for each test
135134

136-
Assert.Equal(ModuleTrackerTemplate.HitsArraySize, hitCounts.Length);
135+
Assert.Equal(ModuleTrackerTemplate.HitsArray.Length, hitCounts.Length + ModuleTrackerTemplate.HitsResultHeaderSize);
137136

138137
var thread = new Thread(() =>
139138
{
@@ -162,9 +161,9 @@ private int[] ReadHits(int expectedUnloads = 1)
162161

163162
var hits = new List<int>();
164163

165-
for (int i = 0; i < ModuleTrackerTemplate.HitsArraySize; ++i)
164+
for (int i = ModuleTrackerTemplate.HitsResultHeaderSize; i < ModuleTrackerTemplate.HitsArray.Length; ++i)
166165
{
167-
hits.Add(mmapAccessor.ReadInt32((i + ModuleTrackerTemplate.HitsResultHeaderSize) * sizeof(int)));
166+
hits.Add(mmapAccessor.ReadInt32(i * sizeof(int)));
168167
}
169168

170169
return hits.ToArray();

0 commit comments

Comments
 (0)