[CIVisibility] - Counters based Code Coverage#3134
Conversation
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
andrewlock
left a comment
There was a problem hiding this comment.
LGTM, though I confess, my eyes glazed over several times 🙈
| _ciVisibilitySettings = CIVisibilitySettings.FromDefaultSources(); | ||
|
|
||
| // Read the DD_DOTNET_TRACER_HOME environment variable | ||
| _tracerHome = Util.EnvironmentHelpers.GetEnvironmentVariable("DD_DOTNET_TRACER_HOME"); |
There was a problem hiding this comment.
Not related to this PR, but we've been discussing about being able to get rid of this variable down the line, because the native loader sets its own env vars that we know are right. I wonder if it would make sense to use those env vars here etc
There was a problem hiding this comment.
Well, I need this because I have to inject (copy to the bin folder) the Datadog.Trace assembly. And I need to choose the right version for the target application (.NET462, netstandard, .NET 6). So yeah, if there's another way to get the root folder where all the tracer assemblies are I'm open to change it.
| private void BackupFolder(string folder) | ||
| { | ||
| var destinationFolder = Path.Combine(folder, _dateTime.ToString("yyyyMMddHHmmss")); | ||
| _logger?.Debug($"Backup folder: {destinationFolder}"); | ||
| Copy(folder, destinationFolder); | ||
| } |
There was a problem hiding this comment.
Just wondering why you decided this wasn't required any more?
There was a problem hiding this comment.
We never use this backup, and we don't have plans to do a restore in CI. This was done more for debugging purposes.
| namespace Datadog.Trace.Ci.Coverage.Metadata | ||
| { | ||
| public abstract class ModuleCoverageMetadata | ||
| { | ||
| protected readonly int[][] Metadata; | ||
| protected ModuleCoverageMetadata() { } | ||
| } | ||
| } |
There was a problem hiding this comment.
This probably doesn't need to be public right?
There was a problem hiding this comment.
Sadly nope, this abstract class is inherited by the new Metadata type we emit in the target assembly. So, to bypass all visibility checks this needs to be public.
| scope.Report(2814874321813540uL); | ||
| counters[7]++; | ||
| if (i >= 100) | ||
| { | ||
| break; | ||
| } | ||
| scope.Report(3096280579112974uL, 3377772735758374uL); | ||
| counters[3]++; | ||
| counters[4]++; | ||
| Console.WriteLine(i); | ||
| scope.Report(3659230532665358uL, 2814912976519209uL); | ||
| counters[5]++; | ||
| counters[6]++; |
There was a problem hiding this comment.
counters[7] is at the top, is that right? 🤔
There was a problem hiding this comment.
Yup, awesome right!? this is the IL -> C# converter fault, the original is a for loop and the comparison is done after each loop that's why the debugging (sequencePoint) offset has this value. And when we try to get the C# code from that rewritten IL, the tool is unable to detect the for loop anymore.
| public ModuleCoverage() | ||
| { | ||
| Metadata = new int[2][]; | ||
| Metadata[0] = new int[0]; |
There was a problem hiding this comment.
I guess this could use Array.Empty<int>()?
There was a problem hiding this comment.
Sure, now I have to emit it in IL :D
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
OmerRaviv
left a comment
There was a problem hiding this comment.
Marking as approved without actually reviewing just to unblock you (since CODEOWNERs definitions are tricky ,;) )
Benchmarks Report 🐌Benchmarks for #3134 compared to master:
The following thresholds were used for comparing the benchmark speeds:
Allocation changes below 0.5% are ignored. Benchmark detailsBenchmarks.Trace.AgentWriterBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.AppSecBodyBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.AspNetCoreBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.DbCommandBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.ElasticsearchBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.GraphQLBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.HttpClientBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.ILoggerBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.Log4netBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.NLogBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.RedisBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.SerilogBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.SpanBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.TraceAnnotationsBenchmark - Same speed ✔️ Same allocations ✔️Raw results
|
zacharycmontoya
left a comment
There was a problem hiding this comment.
LGTM, nice improvement!
Code Coverage Report 📊✔️ Merging #3134 into master will not change line coverage
View the full report for further details: Datadog.Trace Breakdown ✔️
The following classes have significant coverage changes.
The following classes were added in #3134:
2 classes were removed from Datadog.Trace in #3134 View the full reports for further details: |
Summary of changes
This PR changes the algorithm of the Code Coverage feature from an individual instructions list to arrays of counters.
Reason for change
The current list based implementation works well for code with not many instructions or loops, those individual instructions are later grouped using linq to aggregate the number of times an instruction was executed. The new algorithm removes the individual instructions and keeps an
int[]array with the counters for a given method, so the data gets aggregated in place.For performance comparison, using the following test:
We have these results:
The code inside both
forloops are executed 1.000.000 times on 10 parallel tasks. In the current code coverage algorithm this makes the instructions list to grow several times and after that alinqquery is applied to the whole list.In the new algorithm we are only incrementing counters in an array.
Implementation details
At the end is a complete rewrite of the code coverage implementation, by using counters we also removes all the strings we were defining with the filepath and the ranges, and we rely in the .pdb information already available (using the vendored version of dnlib).
Test coverage
Snapshot has been updated with the new coverage format.