Skip to content

Commit

Permalink
Merge pull request coverlet-coverage#134 from petli/performance
Browse files Browse the repository at this point in the history
Performance improvements
  • Loading branch information
tonerdo committed Jul 5, 2018
2 parents dc0751f + bd1d6e0 commit a33524c
Show file tree
Hide file tree
Showing 11 changed files with 5,082 additions and 95 deletions.
35 changes: 33 additions & 2 deletions coverlet.sln
@@ -1,4 +1,5 @@
Microsoft Visual Studio Solution File, Format Version 12.00

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26124.0
MinimumVisualStudioVersion = 15.0.26124.0
Expand All @@ -14,7 +15,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.core.tests", "test
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.console", "src\coverlet.console\coverlet.console.csproj", "{F3DBE7C3-ABBB-4B8B-A6CB-A1D3D607163E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "coverlet.tracker", "src\coverlet.tracker\coverlet.tracker.csproj", "{F4273009-536D-4999-A126-B0A2E3AA3E70}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.tracker", "src\coverlet.tracker\coverlet.tracker.csproj", "{F4273009-536D-4999-A126-B0A2E3AA3E70}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "coverlet.testsubject", "test\coverlet.testsubject\coverlet.testsubject.csproj", "{AE117FAA-C21D-4F23-917E-0C8050614750}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "coverlet.core.performancetest", "test\coverlet.core.performancetest\coverlet.core.performancetest.csproj", "{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -86,6 +91,30 @@ Global
{F4273009-536D-4999-A126-B0A2E3AA3E70}.Release|x64.Build.0 = Release|Any CPU
{F4273009-536D-4999-A126-B0A2E3AA3E70}.Release|x86.ActiveCfg = Release|Any CPU
{F4273009-536D-4999-A126-B0A2E3AA3E70}.Release|x86.Build.0 = Release|Any CPU
{AE117FAA-C21D-4F23-917E-0C8050614750}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AE117FAA-C21D-4F23-917E-0C8050614750}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AE117FAA-C21D-4F23-917E-0C8050614750}.Debug|x64.ActiveCfg = Debug|Any CPU
{AE117FAA-C21D-4F23-917E-0C8050614750}.Debug|x64.Build.0 = Debug|Any CPU
{AE117FAA-C21D-4F23-917E-0C8050614750}.Debug|x86.ActiveCfg = Debug|Any CPU
{AE117FAA-C21D-4F23-917E-0C8050614750}.Debug|x86.Build.0 = Debug|Any CPU
{AE117FAA-C21D-4F23-917E-0C8050614750}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AE117FAA-C21D-4F23-917E-0C8050614750}.Release|Any CPU.Build.0 = Release|Any CPU
{AE117FAA-C21D-4F23-917E-0C8050614750}.Release|x64.ActiveCfg = Release|Any CPU
{AE117FAA-C21D-4F23-917E-0C8050614750}.Release|x64.Build.0 = Release|Any CPU
{AE117FAA-C21D-4F23-917E-0C8050614750}.Release|x86.ActiveCfg = Release|Any CPU
{AE117FAA-C21D-4F23-917E-0C8050614750}.Release|x86.Build.0 = Release|Any CPU
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Debug|x64.ActiveCfg = Debug|Any CPU
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Debug|x64.Build.0 = Debug|Any CPU
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Debug|x86.ActiveCfg = Debug|Any CPU
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Debug|x86.Build.0 = Debug|Any CPU
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Release|Any CPU.Build.0 = Release|Any CPU
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Release|x64.ActiveCfg = Release|Any CPU
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Release|x64.Build.0 = Release|Any CPU
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Release|x86.ActiveCfg = Release|Any CPU
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -96,6 +125,8 @@ Global
{E7637CC6-43F7-461A-A0BF-3C14562419BD} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
{F3DBE7C3-ABBB-4B8B-A6CB-A1D3D607163E} = {E877EBA4-E78B-4F7D-A2D3-1E070FED04CD}
{F4273009-536D-4999-A126-B0A2E3AA3E70} = {E877EBA4-E78B-4F7D-A2D3-1E070FED04CD}
{AE117FAA-C21D-4F23-917E-0C8050614750} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9CA57C02-97B0-4C38-A027-EA61E8741F10}
Expand Down
79 changes: 38 additions & 41 deletions src/coverlet.core/Coverage.cs
Expand Up @@ -55,10 +55,10 @@ public CoverageResult GetCoverageResult()
foreach (var result in _results)
{
Documents documents = new Documents();
foreach (var doc in result.Documents)
foreach (var doc in result.Documents.Values)
{
// Construct Line Results
foreach (var line in doc.Lines)
foreach (var line in doc.Lines.Values)
{
if (documents.TryGetValue(doc.Path, out Classes classes))
{
Expand Down Expand Up @@ -91,7 +91,7 @@ public CoverageResult GetCoverageResult()
}

// Construct Branch Results
foreach (var branch in doc.Branches)
foreach (var branch in doc.Branches.Values)
{
if (documents.TryGetValue(doc.Path, out Classes classes))
{
Expand Down Expand Up @@ -147,55 +147,52 @@ private void CalculateCoverage()
{
foreach (var result in _results)
{
var i = 0;
while (true)
if (!File.Exists(result.HitsFilePath))
{
var file = $"{result.HitsFilePath}_compressed_{i}";
if(!File.Exists(file)) break;

using (var fs = new FileStream(file, FileMode.Open))
using (var gz = new GZipStream(fs, CompressionMode.Decompress))
using (var sr = new StreamReader(gz))
// File not instrumented, or nothing in it called. Warn about this?
continue;
}

using (var fs = new FileStream(result.HitsFilePath, FileMode.Open))
using (var sr = new StreamReader(fs))
{
string row;
while ((row = sr.ReadLine()) != null)
{
string row;
while ((row = sr.ReadLine()) != null)
{
var info = row.Split(',');
// Ignore malformed lines
if (info.Length != 4)
continue;
var info = row.Split(',');
// Ignore malformed lines
if (info.Length != 5)
continue;

bool isBranch = info[0] == "B";
bool isBranch = info[0] == "B";

var document = result.Documents.FirstOrDefault(d => d.Path == info[1]);
if (document == null)
continue;
if (!result.Documents.TryGetValue(info[1], out var document))
{
continue;
}

int start = int.Parse(info[2]);
int start = int.Parse(info[2]);
int hits = int.Parse(info[4]);

if (isBranch)
{
uint ordinal = uint.Parse(info[3]);
var branch = document.Branches.First(b => b.Number == start && b.Ordinal == ordinal);
if (branch.Hits != int.MaxValue)
branch.Hits += branch.Hits + 1;
}
else
if (isBranch)
{
int ordinal = int.Parse(info[3]);
var branch = document.Branches[(start, ordinal)];
branch.Hits = hits;
}
else
{
int end = int.Parse(info[3]);
for (int j = start; j <= end; j++)
{
int end = int.Parse(info[3]);
for (int j = start; j <= end; j++)
{
var line = document.Lines.First(l => l.Number == j);
if (line.Hits != int.MaxValue)
line.Hits = line.Hits + 1;
}
var line = document.Lines[j];
line.Hits = hits;
}
}
}

InstrumentationHelper.DeleteHitsFile(file);
i++;
}

InstrumentationHelper.DeleteHitsFile(result.HitsFilePath);
}
}
}
Expand Down
23 changes: 11 additions & 12 deletions src/coverlet.core/Instrumentation/Instrumenter.cs
Expand Up @@ -166,17 +166,16 @@ private void InstrumentIL(MethodDefinition method)

private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor processor, Instruction instruction, SequencePoint sequencePoint)
{
var document = _result.Documents.FirstOrDefault(d => d.Path == sequencePoint.Document.Url);
if (document == null)
{
if (!_result.Documents.TryGetValue(sequencePoint.Document.Url, out var document))
{
document = new Document { Path = sequencePoint.Document.Url };
_result.Documents.Add(document);
_result.Documents.Add(document.Path, document);
}

for (int i = sequencePoint.StartLine; i <= sequencePoint.EndLine; i++)
{
if (!document.Lines.Exists(l => l.Number == i))
document.Lines.Add(new Line { Number = i, Class = method.DeclaringType.FullName, Method = method.FullName });
if (!document.Lines.ContainsKey(i))
document.Lines.Add(i, new Line { Number = i, Class = method.DeclaringType.FullName, Method = method.FullName });
}

string marker = $"L,{document.Path},{sequencePoint.StartLine},{sequencePoint.EndLine}";
Expand All @@ -194,15 +193,15 @@ private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor

private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor processor, Instruction instruction, BranchPoint branchPoint)
{
var document = _result.Documents.FirstOrDefault(d => d.Path == branchPoint.Document);
if (document == null)
{
if (!_result.Documents.TryGetValue(branchPoint.Document, out var document))
{
document = new Document { Path = branchPoint.Document };
_result.Documents.Add(document);
_result.Documents.Add(document.Path, document);
}

if (!document.Branches.Exists(l => l.Number == branchPoint.StartLine && l.Ordinal == branchPoint.Ordinal))
document.Branches.Add(
var key = (branchPoint.StartLine, (int)branchPoint.Ordinal);
if (!document.Branches.ContainsKey(key))
document.Branches.Add(key,
new Branch
{
Number = branchPoint.StartLine,
Expand Down
17 changes: 11 additions & 6 deletions src/coverlet.core/Instrumentation/InstrumenterResult.cs
Expand Up @@ -22,21 +22,26 @@ internal class Document
{
public Document()
{
Lines = new List<Line>();
Branches = new List<Branch>();
Lines = new Dictionary<int, Line>();
Branches = new Dictionary<(int Line, int Ordinal), Branch>();
}

public string Path;
public List<Line> Lines { get; private set; }
public List<Branch> Branches { get; private set; }

public Dictionary<int, Line> Lines { get; private set; }
public Dictionary<(int Line, int Ordinal), Branch> Branches { get; private set; }
}

internal class InstrumenterResult
{
public InstrumenterResult() => Documents = new List<Document>();
public InstrumenterResult()
{
Documents = new Dictionary<string, Document>();
}

public string Module;
public string HitsFilePath;
public string ModulePath;
public List<Document> Documents { get; private set; }
public Dictionary<string, Document> Documents { get; private set; }
}
}
5 changes: 5 additions & 0 deletions src/coverlet.msbuild.tasks/CoverageResultTask.cs
@@ -1,4 +1,5 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
Expand Down Expand Up @@ -50,8 +51,12 @@ public override bool Execute()
try
{
Console.WriteLine("\nCalculating coverage result...");
var duration = new Stopwatch();
duration.Start();
var coverage = InstrumentationTask.Coverage;
var result = coverage.GetCoverageResult();
duration.Stop();
Console.WriteLine($"Results calculated in {duration.Elapsed.TotalSeconds} seconds");

var directory = Path.GetDirectoryName(_filename);
if (!Directory.Exists(directory))
Expand Down
55 changes: 23 additions & 32 deletions src/coverlet.tracker/CoverageTracker.cs
Expand Up @@ -2,70 +2,61 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.IO.Compression;

using Coverlet.Tracker.Extensions;

namespace Coverlet.Tracker
{
public static class CoverageTracker
{
private static Dictionary<string, List<string>> _markers;
private static Dictionary<string, int> _markerFileCount;
private static Dictionary<string, Dictionary<string, int>> _events;

[ExcludeFromCodeCoverage]
static CoverageTracker()
{
_markers = new Dictionary<string, List<string>>();
_markerFileCount = new Dictionary<string, int>();
_events = new Dictionary<string, Dictionary<string, int>>();
AppDomain.CurrentDomain.ProcessExit += new EventHandler(CurrentDomain_ProcessExit);
AppDomain.CurrentDomain.DomainUnload += new EventHandler(CurrentDomain_ProcessExit);
}

[ExcludeFromCodeCoverage]
public static void MarkExecuted(string path, string marker)
public static void MarkExecuted(string file, string evt)
{
lock (_markers)
lock (_events)
{
_markers.TryAdd(path, new List<string>());
_markers[path].Add(marker);
_markerFileCount.TryAdd(path, 0);
if (_markers[path].Count >= 100000)
if (!_events.TryGetValue(file, out var fileEvents))
{
using (var fs = new FileStream($"{path}_compressed_{_markerFileCount[path]}", FileMode.OpenOrCreate))
using (var gz = new GZipStream(fs, CompressionMode.Compress))
using (var sw = new StreamWriter(gz))
{
foreach (var line in _markers[path])
{
sw.WriteLine(line);
}
}
_markers[path].Clear();
_markerFileCount[path] = _markerFileCount[path] + 1;
fileEvents = new Dictionary<string, int>();
_events.Add(file, fileEvents);
}

if (!fileEvents.TryGetValue(evt, out var count))
{
fileEvents.Add(evt, 1);
}
else if (count < int.MaxValue)
{
fileEvents[evt] = count + 1;
}
}
}

[ExcludeFromCodeCoverage]
public static void CurrentDomain_ProcessExit(object sender, EventArgs e)
{
lock (_markers)
lock (_events)
{
foreach (var kvp in _markers)
foreach (var files in _events)
{
using (var fs = new FileStream($"{kvp.Key}_compressed_{_markerFileCount[kvp.Key]}", FileMode.OpenOrCreate))
using (var gz = new GZipStream(fs, CompressionMode.Compress))
using (var sw = new StreamWriter(gz))
using (var fs = new FileStream(files.Key, FileMode.Create))
using (var sw = new StreamWriter(fs))
{
foreach (var line in kvp.Value)
foreach (var evt in files.Value)
{
sw.WriteLine(line);
sw.WriteLine($"{evt.Key},{evt.Value}");
}
}
}

_markers.Clear();
_events.Clear();
}
}
}
Expand Down
27 changes: 27 additions & 0 deletions test/coverlet.core.performancetest/PerformanceTest.cs
@@ -0,0 +1,27 @@
using coverlet.testsubject;
using Xunit;

namespace coverlet.core.performancetest
{
/// <summary>
/// Test the performance of coverlet by running a unit test that calls a reasonably big and complex test class.
/// Enable the test, compile, then run the test in the command line:
/// <code>
/// dotnet test -p:CollectCoverage=true -p:CoverletOutputFormat=opencover test/coverlet.core.performa ncetest/
/// </code>
/// </summary>
public class PerformanceTest
{
[Theory(Skip = "Only enabled when explicitly testing performance.")]
[InlineData(150)]
public void TestPerformance(int iterations)
{
var big = new BigClass();

for (var i = 0; i < iterations; i++)
{
big.Do(i);
}
}
}
}

0 comments on commit a33524c

Please sign in to comment.