Skip to content

[Debugger] Add support for probes in async methods#3079

Merged
dudikeleti merged 10 commits into
masterfrom
dudik/async
Sep 2, 2022
Merged

[Debugger] Add support for probes in async methods#3079
dudikeleti merged 10 commits into
masterfrom
dudik/async

Conversation

@dudikeleti

@dudikeleti dudikeleti commented Aug 15, 2022

Copy link
Copy Markdown
Contributor

Summary of changes

Adding the ability to add probes on async methods for all supported .NET runtimes (on both Windows and Linux).

Reason for change

The .NET Live Debugger has so-far only supported adding probes on non-async method. This is a big gap :)

Implementation details

There are a few differences between the regular method probes implementation to the async one.

  1. Async methods are translated to a state machine that may be called multiple times, and in the non async probes implementation, we assume method entry and exit only happen once.
  2. In async methods, the body of the method is executed in the state machine method, so we need a way to translate between the real method (the so-called “kick-off method”) and the state machine method.
  3. We should know for each state machine execution what is the state object that holds the correct method execution info.
  4. In C# compiler there are implementation details that we need to be aware of in order to correctly extract the local variables:
    4.1. Some of the original locals (from the kick off method) are hoisted onto a heap-allocated closure, whereas others which do not persist across invocations, are defined as ‘regular’ locals on the stack.
    4.2. Some of the reference-type hoisted locals are nullified and value-type are set to default right before method leave.
    4.3. Several "async machinery bookkeeping" locals that aren’t related to the original kick off method, are added as state machine fields or as locals.

When the user requests to put a probe on an asynchronous method, several steps occur:

  1. We extract the MoveNext method from the appropriate state machine object and ask to put the probe on it instead of the original method - the "kick-off" method.
  2. We add a new boolean field to the state machine object that we will use to know whether we have already entered the MoveNext method (if we have, it means we are re-entering the method as a continuation in a subsequent await operation, and should not capture the method parameter values as we do the first time around).

When the instrumentation request occurs, we obtain the return type from the task object of the original async method. If it is not Task<T> then the return type is System.Void. Obviously referring to the return type of the original method, the MoveNext method always returns System.Void.

The instrumentation is divided into two parts:

  1. The first part is the method entry. We call to AsyncMethodDebuggerInvoker.BeginMethod and pass in the state machine type, the this object, several more parameters and the field we added to the state machine object in step 1. We pass this field by ref.
    Inside BeginMethod, we check that boolean field value, whether this is the first entry to the method or not. If this is not the first entry, we return from the method immediately and return an AsyncMethodDebuggerState object that we save in an AsyncLocal field.
    If this is the first entry to the method, we create the AsyncMethodDebuggerState object and save in it, among other things, the kick-off method (the original asynchronous method) and its parent type.
    In addition, we extract the arguments of the kick-off method from the state machine object as well as the local variables that live in the state machine object. We then capture the this object of the original kick-off method (as a reminder, we are now running inside the state machine object, not in the original method, so we need to take care of getting the original this), and the arguments of the original method, and then return the AsyncMethodDebuggerState object.

  2. The second part is the method exit. To know when is the final exit from the MoveNext method we take advantage of the fact that an async method can only finish its work in one of two cases: In case it ran all the way to the end, there will be a call to SetResult at the end of the method, and in case an unhandled exception was thrown, there will be a call to SetException.
    So we add the call to AsyncMethodDebuggerInvoker.EndMethod in both these places.
    The difference between them is that in the case of an exception, we call an overload that does not return a value (returns a DebuggerReturn and not a DebuggerReturn<T>) and in the case of a SetResult we call an overload that returns a value (unless of course the original method was an async void method).

Within EndMethod we necessarily know that we have finished running the state machine so we collect everything needed (arguments hoisted in the state machine object, local variables - hoisted or not hoisted, the this object of the original method) and we then finally upload a debugger snapshot.

Without going into too much detail and actual lines of code, this is a pretty accurate description of what's going on.

The code after the instrumentation will look like this:

void MoveNext()
{
    try
    {
        AsyncMethodDebuggerState asyncState = AsyncMethodDebuggerInvoker.BegunMethod<StateMachineType>(probeId, instance, methodHandle, typeHandle, methodMetadataIndex, ref isReEntryToMoveNext)
    }
    catch (Exception e)
    {
         AsyncMethodDebuggerInvoker.LogException(e, ref asyncState);
    }
    try
    {
        // Original method execution
    }
    catch (Exception ex)
    {
       try
       {
           DebuggerReturn return = AsyncMethodDebuggerInvoker.EndMethod_StartMarker<StateMachineType>(instance, ex, ref asyncState);
           AsyncMethodDebuggerInvoker.LogLocal() * N
           AsyncMethodDebuggerInvoker.EndMethod_EndMarker(ref asyncState);
       }
       catch(Exception e)
       {
             AsyncMethodDebuggerInvoker.LogException(e, ref asyncState);
       }
       // Original method execution
       taskBuilder.SetException(ex);
    }
    try
    {
          // depends on the actual task return type this can be:
          DebuggerReturn<ReturnType> return = AsyncMethodDebuggerInvoker.EndMethod_StartMarker<StateMachineType, ReturnType>(instance, result, null, ref asyncState);
          // or:
          DebuggerReturn return = AsyncMethodDebuggerInvoker.EndMethod_StartMarker<StateMachineType>(instance, ex, ref asyncState);
          AsyncMethodDebuggerInvoker.LogLocal() * N
          AsyncMethodDebuggerInvoker.EndMethod_EndMarker(ref asyncState);
    }
    catch (Exception e)
    {
         AsyncMethodDebuggerInvoker.LogException(e, ref asyncState);
    }
    // Original method execution 
    taskBuilder.SetResult(result);
}

Test Coverage

Tests have been added that ensure we properly capture debugger snapshots in the following scenarios:

  • Async method that return Task<T>
  • Async method that return Task
  • Recursive call to async method
  • Async method from Task.Run
  • Chain of async calls
  • Async methods with and without arguments

@OmerRaviv OmerRaviv changed the title Adding async method probes support to Live Debugger [Debugger] Add support for probes in async methods Aug 15, 2022
Comment thread Datadog.Trace.Debugger.slnf
Comment thread tracer/src/Datadog.InstrumentedAssemblyGenerator/MetadataNameParser.cs Outdated
Comment thread tracer/src/Datadog.InstrumentedAssemblyGenerator/ProfilerMetadataImporter.cs Outdated
Comment thread tracer/src/Datadog.InstrumentedAssemblyGenerator/ProfilerMetadataImporter.cs Outdated
Comment thread tracer/src/Datadog.Trace/Debugger/Helpers/AsyncHelper.cs Outdated
Comment thread tracer/src/Datadog.Trace/Debugger/Helpers/AsyncHelper.cs Outdated
Comment thread tracer/src/Datadog.Tracer.Native/debugger_rejit_preprocessor.cpp Outdated
Comment thread tracer/src/Datadog.Tracer.Native/debugger_rejit_preprocessor.cpp Outdated
Comment thread tracer/src/Datadog.Tracer.Native/debugger_rejit_preprocessor.cpp Outdated
Comment thread tracer/src/Datadog.Tracer.Native/debugger_rejit_preprocessor.cpp Outdated
Comment thread tracer/src/Datadog.Tracer.Native/debugger_rejit_preprocessor.cpp Outdated
Comment thread tracer/src/Datadog.Tracer.Native/debugger_rejit_preprocessor.cpp Outdated
Comment thread tracer/src/Datadog.Trace/Debugger/Instrumentation/AsyncMethodDebuggerInvoker.cs Outdated
Comment thread tracer/src/Datadog.Trace/Debugger/Instrumentation/MethodMetadataInfo.cs Outdated
Comment thread tracer/src/Datadog.Trace/Debugger/Instrumentation/MethodMetadataInfo.cs Outdated
Comment thread tracer/src/Datadog.Tracer.Native/debugger_method_rewriter.cpp Outdated
Comment thread tracer/src/Datadog.Tracer.Native/debugger_method_rewriter.cpp

@GreenMatan GreenMatan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm posting my first batch of comments, concentrated around the managed side.
I'll review the native side separately.

Comment thread tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Debugger/ProbesTests.cs Outdated
Comment thread tracer/src/Datadog.Trace/Debugger/Helpers/AsyncHelper.cs Outdated
Comment thread tracer/src/Datadog.Trace/Debugger/Helpers/AsyncHelper.cs Outdated
Comment thread tracer/src/Datadog.Trace/Debugger/Helpers/AsyncHelper.cs Outdated
Comment thread tracer/src/Datadog.Trace/Debugger/Instrumentation/AsyncMethodDebuggerInvoker.cs Outdated
Comment thread tracer/src/Datadog.Trace/Debugger/Helpers/AsyncHelper.cs
Comment thread tracer/src/Datadog.Trace/Debugger/Helpers/AsyncHelper.cs Outdated
Comment thread tracer/src/Datadog.Trace/Debugger/Helpers/AsyncHelper.cs
Comment thread tracer/src/Datadog.Trace/Debugger/Instrumentation/AsyncMethodDebuggerInvoker.cs Outdated
@andrewlock

This comment has been minimized.

@andrewlock

This comment has been minimized.

@dudikeleti
dudikeleti force-pushed the dudik/async branch 2 times, most recently from 5f08851 to 6ece0c7 Compare August 24, 2022 10:14
@dudikeleti
dudikeleti requested review from a team as code owners August 29, 2022 15:46

@zacharycmontoya zacharycmontoya left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the Tracer point-of-view, everything looks good to me 👍🏼

Comment thread tracer/src/Datadog.Tracer.Native/debugger_rejit_preprocessor.cpp Outdated
Comment thread tracer/src/Datadog.Tracer.Native/debugger_rejit_preprocessor.cpp
@dudikeleti
dudikeleti force-pushed the dudik/async branch 2 times, most recently from 401bed9 to fe1d802 Compare August 30, 2022 16:19
@andrewlock

This comment has been minimized.

@andrewlock

This comment has been minimized.

@andrewlock

This comment has been minimized.

@andrewlock

This comment has been minimized.

@andrewlock

Copy link
Copy Markdown
Member

Benchmarks Report 🐌

Benchmarks for #3079 compared to master:

  • 1 benchmarks are slower, with geometric mean 1.267
  • All benchmarks have the same allocations

The following thresholds were used for comparing the benchmark speeds:

  • Mann–Whitney U test with statistical test for significance of 5%
  • Only results indicating a difference greater than 10% and 0.3 ns are considered.

Allocation changes below 0.5% are ignored.

Benchmark details

Benchmarks.Trace.AgentWriterBenchmark - Same speed ✔️ Same allocations ✔️

Raw results

Branch Method Toolchain Mean StdError StdDev Gen 0 Gen 1 Gen 2 Allocated
master WriteAndFlushEnrichedTraces net472 714μs 744ns 2.88μs 0.357 0 0 3.18 KB
master WriteAndFlushEnrichedTraces netcoreapp3.1 470μs 161ns 601ns 0 0 0 2.58 KB
#3079 WriteAndFlushEnrichedTraces net472 728μs 658ns 2.55μs 0.359 0 0 3.18 KB
#3079 WriteAndFlushEnrichedTraces netcoreapp3.1 461μs 150ns 560ns 0 0 0 2.58 KB
Benchmarks.Trace.AppSecBodyBenchmark - Slower ⚠️ Same allocations ✔️

Slower ⚠️ in #3079

Benchmark diff/base Base Median (ns) Diff Median (ns) Modality
Benchmarks.Trace.AppSecBodyBenchmark.AllCycleMoreComplexBody‑net472 1.267 182.54 231.24

Raw results

Branch Method Toolchain Mean StdError StdDev Gen 0 Gen 1 Gen 2 Allocated
master AllCycleSimpleBody net472 185ns 0.145ns 0.521ns 0.0676 0 0 425 B
master AllCycleSimpleBody netcoreapp3.1 236ns 0.344ns 1.33ns 0.00583 0 0 424 B
master AllCycleMoreComplexBody net472 183ns 0.0798ns 0.288ns 0.0638 0 0 401 B
master AllCycleMoreComplexBody netcoreapp3.1 233ns 0.304ns 1.18ns 0.00543 0 0 400 B
master BodyExtractorSimpleBody net472 257ns 0.269ns 1.04ns 0.0573 0 0 361 B
master BodyExtractorSimpleBody netcoreapp3.1 230ns 0.369ns 1.43ns 0.00372 0 0 272 B
master BodyExtractorMoreComplexBody net472 14.2μs 12.9ns 50.1ns 1.21 0.0214 0 7.62 KB
master BodyExtractorMoreComplexBody netcoreapp3.1 12.3μs 11.5ns 44.4ns 0.0927 0 0 6.75 KB
#3079 AllCycleSimpleBody net472 186ns 0.193ns 0.746ns 0.0676 9.2E-05 0 425 B
#3079 AllCycleSimpleBody netcoreapp3.1 238ns 0.505ns 1.96ns 0.00586 0 0 424 B
#3079 AllCycleMoreComplexBody net472 231ns 0.133ns 0.461ns 0.0637 0 0 401 B
#3079 AllCycleMoreComplexBody netcoreapp3.1 234ns 0.219ns 0.819ns 0.00551 0 0 400 B
#3079 BodyExtractorSimpleBody net472 261ns 0.27ns 1.04ns 0.0573 0 0 361 B
#3079 BodyExtractorSimpleBody netcoreapp3.1 225ns 0.307ns 1.15ns 0.00374 0 0 272 B
#3079 BodyExtractorMoreComplexBody net472 14.2μs 15.6ns 58.5ns 1.21 0.0212 0 7.62 KB
#3079 BodyExtractorMoreComplexBody netcoreapp3.1 12.2μs 13ns 50.2ns 0.0921 0 0 6.75 KB
Benchmarks.Trace.AspNetCoreBenchmark - Unknown 🤷 Same allocations ✔️

Raw results

Branch Method Toolchain Mean StdError StdDev Gen 0 Gen 1 Gen 2 Allocated
master SendRequest net472 0ns 0ns 0ns 0 0 0 0 b
master SendRequest netcoreapp3.1 184μs 302ns 1.17μs 0.184 0 0 20.57 KB
#3079 SendRequest net472 0ns 0ns 0ns 0 0 0 0 b
#3079 SendRequest netcoreapp3.1 186μs 429ns 1.66μs 0.184 0 0 20.57 KB
Benchmarks.Trace.DbCommandBenchmark - Same speed ✔️ Same allocations ✔️

Raw results

Branch Method Toolchain Mean StdError StdDev Gen 0 Gen 1 Gen 2 Allocated
master ExecuteNonQuery net472 1.94μs 0.461ns 1.73ns 0.15 0.000975 0 947 B
master ExecuteNonQuery netcoreapp3.1 1.44μs 0.432ns 1.67ns 0.0124 0 0 936 B
#3079 ExecuteNonQuery net472 1.91μs 0.606ns 2.27ns 0.15 0.000952 0 947 B
#3079 ExecuteNonQuery netcoreapp3.1 1.46μs 0.593ns 2.05ns 0.0123 0 0 936 B
Benchmarks.Trace.ElasticsearchBenchmark - Same speed ✔️ Same allocations ✔️

Raw results

Branch Method Toolchain Mean StdError StdDev Gen 0 Gen 1 Gen 2 Allocated
master CallElasticsearch net472 2.38μs 1.21ns 4.67ns 0.184 0 0 1.16 KB
master CallElasticsearch netcoreapp3.1 1.6μs 0.874ns 3.27ns 0.0146 0 0 1.1 KB
master CallElasticsearchAsync net472 2.76μs 1.4ns 5.43ns 0.205 0 0 1.29 KB
master CallElasticsearchAsync netcoreapp3.1 1.58μs 0.709ns 2.74ns 0.0166 0 0 1.22 KB
#3079 CallElasticsearch net472 2.53μs 0.656ns 2.54ns 0.183 0 0 1.16 KB
#3079 CallElasticsearch netcoreapp3.1 1.6μs 0.515ns 2ns 0.0151 0 0 1.1 KB
#3079 CallElasticsearchAsync net472 2.65μs 1.36ns 5.07ns 0.204 0 0 1.29 KB
#3079 CallElasticsearchAsync netcoreapp3.1 1.58μs 0.59ns 2.13ns 0.0166 0 0 1.22 KB
Benchmarks.Trace.GraphQLBenchmark - Same speed ✔️ Same allocations ✔️

Raw results

Branch Method Toolchain Mean StdError StdDev Gen 0 Gen 1 Gen 2 Allocated
master ExecuteAsync net472 2.7μs 4.34ns 15.7ns 0.223 0 0 1.41 KB
master ExecuteAsync netcoreapp3.1 1.7μs 2.66ns 9.59ns 0.0177 0 0 1.34 KB
#3079 ExecuteAsync net472 2.58μs 8.78ns 34ns 0.224 0 0 1.41 KB
#3079 ExecuteAsync netcoreapp3.1 1.77μs 2.6ns 10.1ns 0.0175 0 0 1.34 KB
Benchmarks.Trace.HttpClientBenchmark - Same speed ✔️ Same allocations ✔️

Raw results

Branch Method Toolchain Mean StdError StdDev Gen 0 Gen 1 Gen 2 Allocated
master SendAsync net472 5.8μs 14.7ns 57.1ns 0.439 0 0 2.77 KB
master SendAsync netcoreapp3.1 3.76μs 8.64ns 33.4ns 0.0343 0 0 2.6 KB
#3079 SendAsync net472 5.91μs 15.3ns 59.2ns 0.439 0 0 2.77 KB
#3079 SendAsync netcoreapp3.1 3.64μs 9.07ns 35.1ns 0.0345 0 0 2.6 KB
Benchmarks.Trace.ILoggerBenchmark - Same speed ✔️ Same allocations ✔️

Raw results

Branch Method Toolchain Mean StdError StdDev Gen 0 Gen 1 Gen 2 Allocated
master EnrichedLog net472 3.27μs 3.79ns 14.7ns 0.288 0 0 1.81 KB
master EnrichedLog netcoreapp3.1 2.54μs 1.68ns 6.3ns 0.0254 0 0 1.85 KB
#3079 EnrichedLog net472 3.3μs 3.49ns 13.1ns 0.287 0 0 1.81 KB
#3079 EnrichedLog netcoreapp3.1 2.58μs 0.674ns 2.43ns 0.0255 0 0 1.85 KB
Benchmarks.Trace.Log4netBenchmark - Same speed ✔️ Same allocations ✔️

Raw results

Branch Method Toolchain Mean StdError StdDev Gen 0 Gen 1 Gen 2 Allocated
master EnrichedLog net472 152μs 162ns 629ns 0.674 0.225 0 4.65 KB
master EnrichedLog netcoreapp3.1 117μs 173ns 669ns 0 0 0 4.49 KB
#3079 EnrichedLog net472 149μs 107ns 401ns 0.677 0.226 0 4.65 KB
#3079 EnrichedLog netcoreapp3.1 116μs 248ns 962ns 0.0577 0 0 4.49 KB
Benchmarks.Trace.NLogBenchmark - Same speed ✔️ Same allocations ✔️

Raw results

Branch Method Toolchain Mean StdError StdDev Gen 0 Gen 1 Gen 2 Allocated
master EnrichedLog net472 5.75μs 15.4ns 59.6ns 0.569 0.00292 0 3.59 KB
master EnrichedLog netcoreapp3.1 4.49μs 7.87ns 29.4ns 0.0541 0 0 3.91 KB
#3079 EnrichedLog net472 5.69μs 20.9ns 80.8ns 0.57 0.00281 0 3.59 KB
#3079 EnrichedLog netcoreapp3.1 4.34μs 8.98ns 33.6ns 0.0533 0 0 3.91 KB
Benchmarks.Trace.RedisBenchmark - Same speed ✔️ Same allocations ✔️

Raw results

Branch Method Toolchain Mean StdError StdDev Gen 0 Gen 1 Gen 2 Allocated
master SendReceive net472 2.37μs 2.21ns 8.57ns 0.217 0 0 1.37 KB
master SendReceive netcoreapp3.1 1.92μs 0.935ns 3.62ns 0.0182 0 0 1.32 KB
#3079 SendReceive net472 2.24μs 3.61ns 13.5ns 0.218 0 0 1.37 KB
#3079 SendReceive netcoreapp3.1 1.9μs 0.677ns 2.53ns 0.0181 0 0 1.32 KB
Benchmarks.Trace.SerilogBenchmark - Same speed ✔️ Same allocations ✔️

Raw results

Branch Method Toolchain Mean StdError StdDev Gen 0 Gen 1 Gen 2 Allocated
master EnrichedLog net472 4.89μs 1.79ns 6.45ns 0.353 0 0 2.23 KB
master EnrichedLog netcoreapp3.1 4.36μs 2.29ns 8.26ns 0.024 0 0 1.8 KB
#3079 EnrichedLog net472 5.09μs 1.55ns 5.99ns 0.354 0 0 2.23 KB
#3079 EnrichedLog netcoreapp3.1 4.27μs 2.8ns 10.8ns 0.0234 0 0 1.8 KB
Benchmarks.Trace.SpanBenchmark - Same speed ✔️ Same allocations ✔️

Raw results

Branch Method Toolchain Mean StdError StdDev Gen 0 Gen 1 Gen 2 Allocated
master StartFinishSpan net472 1.17μs 1.73ns 6.69ns 0.129 0 0 810 B
master StartFinishSpan netcoreapp3.1 958ns 0.193ns 0.695ns 0.0105 0 0 760 B
master StartFinishScope net472 1.4μs 1.45ns 5.61ns 0.142 0 0 891 B
master StartFinishScope netcoreapp3.1 1.12μs 0.425ns 1.59ns 0.0118 0 0 880 B
#3079 StartFinishSpan net472 1.14μs 0.576ns 2.23ns 0.129 0 0 810 B
#3079 StartFinishSpan netcoreapp3.1 922ns 0.204ns 0.735ns 0.0106 0 0 760 B
#3079 StartFinishScope net472 1.4μs 0.663ns 2.57ns 0.141 0 0 891 B
#3079 StartFinishScope netcoreapp3.1 1.12μs 0.41ns 1.53ns 0.0118 0 0 880 B
Benchmarks.Trace.TraceAnnotationsBenchmark - Same speed ✔️ Same allocations ✔️

Raw results

Branch Method Toolchain Mean StdError StdDev Gen 0 Gen 1 Gen 2 Allocated
master RunOnMethodBegin net472 1.53μs 0.695ns 2.69ns 0.141 0 0 891 B
master RunOnMethodBegin netcoreapp3.1 1.22μs 0.45ns 1.68ns 0.0121 0 0 880 B
#3079 RunOnMethodBegin net472 1.49μs 0.604ns 2.34ns 0.141 0 0 891 B
#3079 RunOnMethodBegin netcoreapp3.1 1.19μs 0.544ns 2.04ns 0.012 0 0 880 B

@andrewlock

Copy link
Copy Markdown
Member

Code Coverage Report 📊

⚠️ Merging #3079 into master will will decrease line coverage by 1%
⚠️ Merging #3079 into master will will decrease branch coverage by 1%
⛔ Merging #3079 into master will will increase complexity by 181

master #3079 Change
Lines 17482 / 23788 17473 / 24055
Lines % 73% 73% -1% ⚠️
Branches 10347 / 14633 10330 / 14797
Branches % 71% 70% -1% ⚠️
Complexity 15796 15977 181

View the full report for further details:

Datadog.Trace Breakdown ⚠️

master #3079 Change
Lines % 73% 73% -1% ⚠️
Branches % 71% 70% -1% ⚠️
Complexity 15796 15977 181

The following classes have significant coverage changes.

File Line coverage change Branch coverage change Complexity change
Datadog.Trace.Debugger.Instrumentation.MethodMetadataInfoFactory -20% 0% ✔️ 1
Datadog.Trace.Ci.Coverage.DefaultCoverageEventHandler 0% ✔️ -30% 33
Datadog.Trace.Ci.CIVisibility 5% ✔️ 6% ✔️ 0 ✔️
Datadog.Trace.Ci.Coverage.CoverageContextContainer 17% ✔️ -75% 3

The following classes were added in #3079:

File Line coverage Branch coverage Complexity
Datadog.Trace.Ci.Coverage.CoverageReporter`1 0% 0% 13
Datadog.Trace.Ci.Coverage.Metadata.ModuleCoverageMetadata 0% 100% 4
Datadog.Trace.Ci.Coverage.MethodValues 0% 0% 2
Datadog.Trace.Ci.Coverage.ModuleValue 0% 0% 2
Datadog.Trace.Ci.Coverage.TypeValues 0% 0% 2
...And 3 more

2 classes were removed from Datadog.Trace in #3079

View the full reports for further details:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants