Reduce coverage analysis run time to almost nothing.

This is done by aggregating hits in the CoverateTracker directly, rather than just recording events in files, and by avoiding the former quadratic complexity when translating these event files into hits on lines and branches.
This commit is contained in:
Peter Liljenberg
2018-06-26 15:25:59 +02:00
parent b3c080ca36
commit 1f4da61d7a
5 changed files with 86 additions and 94 deletions
+39 -42
View File
@@ -55,10 +55,10 @@ namespace Coverlet.Core
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))
{
@@ -91,7 +91,7 @@ namespace Coverlet.Core
}
// Construct Branch Results
foreach (var branch in doc.Branches)
foreach (var branch in doc.Branches.Values)
{
if (documents.TryGetValue(doc.Path, out Classes classes))
{
@@ -147,55 +147,52 @@ namespace Coverlet.Core
{
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 != 5)
continue;
bool isBranch = info[0] == "B";
if (!result.Documents.TryGetValue(info[1], out var document))
{
var info = row.Split(',');
// Ignore malformed lines
if (info.Length != 4)
continue;
continue;
}
bool isBranch = info[0] == "B";
int start = int.Parse(info[2]);
int hits = int.Parse(info[4]);
var document = result.Documents.FirstOrDefault(d => d.Path == info[1]);
if (document == null)
continue;
int start = int.Parse(info[2]);
if (isBranch)
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++)
{
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
{
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);
}
}
}
@@ -169,17 +169,16 @@ namespace Coverlet.Core.Instrumentation
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}";
@@ -197,15 +196,15 @@ namespace Coverlet.Core.Instrumentation
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,
@@ -22,21 +22,26 @@ namespace Coverlet.Core.Instrumentation
{
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; }
}
}
+23 -32
View File
@@ -2,47 +2,39 @@ using System;
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;
}
}
}
@@ -50,22 +42,21 @@ namespace Coverlet.Tracker
[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();
}
}
}
@@ -30,10 +30,10 @@ namespace Coverlet.Core.Instrumentation.Tests
var instrumenterTest = CreateInstrumentor();
var result = instrumenterTest.Instrumenter.Instrument();
var doc = result.Documents.FirstOrDefault(d => Path.GetFileName(d.Path) == "Samples.cs");
var doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Samples.cs");
Assert.NotNull(doc);
var found = doc.Lines.Any(l => l.Class == excludedType.FullName);
var found = doc.Lines.Values.Any(l => l.Class == excludedType.FullName);
Assert.False(found, "Class decorated with with exclude attribute should be excluded");
instrumenterTest.Directory.Delete(true);