Skip to content

Commit 7b06e9c

Browse files
authored
Add some more documentation (DataDog#2317)
* Move docs to _docs_ folder Fix links * Clarify return types in instrumentation docs * Mention that integration methods are optional * Add small section on constraints duck-typing.
1 parent 6b142cc commit 7b06e9c

File tree

3 files changed

+44
-12
lines changed

3 files changed

+44
-12
lines changed

tracer/src/Datadog.Trace/ClrProfiler/README.md renamed to docs/development/AutomaticInstrumentation.md

+40-8
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ Creating a new instrumentation implementation typically uses the following proce
88

99
1. Identify the operation of interest that we want to measure. Also gather the tags, resource names that we will need to set. Don't forget to check what has been implemented by other tracers.
1010
2. Find an appropriate instrumentation point in the target library. You may need to use multiple instrumentation points, and you may need to use different targets for different _versions_ of the library
11-
3. Create an instrumentation class using one of the standard "shapes" (described below), and place it in the [ClrProfiler/AutoInstrumentation folder](./AutoInstrumentation). If the methods you need to instrument have different prototypes (especially the number of parameters), you will need multiple class to instrument them.
11+
3. Create an instrumentation class using one of the standard "shapes" (described below), and place it in the [ClrProfiler/AutoInstrumentation folder](../../tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation). If the methods you need to instrument have different prototypes (especially the number of parameters), you will need multiple class to instrument them.
1212
4. Add an `[InstrumentMethod]` attribute to the instrumentation class, as described below. Alternatively, add an assembly-level `[AdoNetClientInstrumentMethods]` attribute
1313
5. (Optional) Create duck-typing unit tests in _Datadog.Trace.Tests_ to confirm any duck types are valid. This can make the feedback cycle much faster than relying on integration tests
1414
6. Create integration tests for your instrumentation
1515
1. Create (or reuse) a sample application that uses the target library, which ideally exercises all the code paths in your new instrumentation. Use an `$(ApiVersion)` MSBuild variables to allow testing against multiple package versions in CI.
16-
2. Add an entry in [tracer/build/PackageVersionsGeneratorDefinitions.json](../../../build/PackageVersionsGeneratorDefinitions.json) defining the range of all supported versions. See the existing definitions for examples
16+
2. Add an entry in [tracer/build/PackageVersionsGeneratorDefinitions.json](../../tracer/build/PackageVersionsGeneratorDefinitions.json) defining the range of all supported versions. See the existing definitions for examples
1717
3. Run `./tracer/build.ps1 GeneratePackageVersions`. This generates the xunit test data for package versions in the `TestData` that you can use as `[MemberData]` for your `[Theory]` tests.
1818
4. Use the `MockTracerAgent` to confirm your instrumentation is working as expected.
1919
7. After testing locally, push to GitHub, and do a manual run in Azure Devops for your branch
@@ -40,14 +40,16 @@ public class ClientQueryIteratorsIntegrations
4040

4141
// Include ONE of the following:
4242
43-
// 👇 Async method
43+
// 👇 Async method, with different behavior based on the return type
44+
// - Task<TReturn>: returnValue is set to the Result
45+
// - Task: TReturn is set to typeof(object) and returnValue is set to null
4446
internal static TReturn OnAsyncMethodEnd<TTarget, TReturn>(TTarget instance, TReturn returnValue, Exception exception, in CallTargetState state)
4547
{
4648
state.Scope?.DisposeWithException(exception);
4749
return returnValue;
4850
}
4951

50-
// 👇 Method with return value TReturn
52+
// 👇 Method with return type TReturn
5153
internal static CallTargetReturn<TReturn> OnMethodEnd<TTarget, TReturn>(TTarget instance, TReturn returnValue, Exception exception, in CallTargetState state)
5254
{
5355
// Run your "method end" code here
@@ -61,12 +63,42 @@ public class ClientQueryIteratorsIntegrations
6163
}
6264
```
6365

66+
> Note that both `OnMethodBegin` and `OnMethodEnd` are optional. If you only need one of the integration points, you can omit the others
67+
68+
The first parameter passed to the method is the instance on which the method is called (for `static` methods, this parameter should be omitted), and should be a _generic parameter_ type.
69+
70+
For parameters that are well-known types like `string`, `object`, or `Exception`, you can use the type directly in the `OnMethodBegin` or `OnMethodEnd` methods. For other types that can't be directly referenced, such as types in the target-library, you should use generic parameters. If you need to manipulate the generic parameters, for example to access values, use the duck-typing approach described below.
71+
72+
### Duck-typing, instrumentation classes, and constraints
73+
74+
When creating instrumentation classes you often need to work with `Type`s in the target library that you can't reference directly. Rather than using reflection directly to manipulate these types, the .NET Tracer has an optimised solution for working with them called _Duck Typing_.
75+
76+
> See [the Duck Typing document](./DuckTyping.md) for a detailed description of duck-typing, use cases, best practices, and benchmarks.
77+
78+
You can use duck-typing imperatively at runtime to "cast" any object to a type you can manipulate directly, but if you know at call time that you need to work with one of the generic parameters passed to an `OnMethodBegin` or `OnMethodEnd`, you can use a more performant approach leveraging _constraints_.
79+
80+
Add a constraint to your method that the generic type implements your duck type. The value passed to your method will then be pre-duck-typed, and will have better performance than using duck-typing manually at a later point. For example, the integration below [(A GraphQL integration)](../../tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/GraphQL/ExecuteAsyncIntegration.cs) takes a single parameter which is duck-typed using constraints to implement the type IExecutionContext
81+
82+
```csharp
83+
public class ExecuteAsyncIntegration
84+
{
85+
internal static CallTargetState OnMethodBegin<TTarget, TContext>(TTarget instance, TContext context)
86+
where TContext : IExecutionContext
87+
{
88+
// ...
89+
}
90+
```
91+
92+
> ⚠ Note that pre duck-typed parameters that use constraints will never be `null`. If you need to check the parameter for `null`, add the `IDuckType` constraint too, and check the value of `IDuckType.Instance`.
93+
94+
For more information on duck-typing, see [the documentation](./DuckTyping.md).
95+
6496
### Instrumentation attributes
6597

6698
A source generator is used to automatically "find" all custom instrumentation classes in the app and generate a list of them to pass to the native CLR profiler. We do this by using one of two attributes:
6799

68-
- [`[InstrumentMethod]`](./InstrumentMethodAttribute.cs)
69-
- [`[AdoNetClientInstrumentMethods]`](./AutoInstrumentation/AdoNet/AdoNetClientInstrumentMethodsAttribute.cs)
100+
- [`[InstrumentMethod]`](../../tracer/src/Datadog.Trace/ClrProfiler/InstrumentMethodAttribute.cs)
101+
- [`[AdoNetClientInstrumentMethods]`](../../tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AdoNet/AdoNetClientInstrumentMethodsAttribute.cs)
70102

71103
> Alternatively, you can _manually_ call `NativeMethods.InitializeProfiler()`, passing in `NativeCallTargetDefinition[]`. This is not the "normal" approach, but may be necessary when you need to dynamically generate definitions, for example in serverless scenarios
72104

@@ -88,7 +120,7 @@ public class HttpClientHandlerIntegration
88120
}
89121
```
90122

91-
We also have a special case for ADO.NET method instrumentation, as this is generally more convoluted, and requires a lot of duplication. All new ADO.NET implementations will likely reuse existing instrumentation classes, such as [`CommandExecuteReaderIntegration`](./AutoInstrumentation/AdoNet/CommandExecuteReaderIntegration.cs) for example. To save having to specify many `[InstrumentMethod]` attributes, you can instead use the `[AdoNetClientInstrumentMethods]` _assembly_ attribute, to define some standard types, as well as which of the standard ADO.NET signatures to implement. For example:
123+
We also have a special case for ADO.NET method instrumentation, as this is generally more convoluted, and requires a lot of duplication. All new ADO.NET implementations will likely reuse existing instrumentation classes, such as [`CommandExecuteReaderIntegration`](../../tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AdoNet/CommandExecuteReaderIntegration.cs) for example. To save having to specify many `[InstrumentMethod]` attributes, you can instead use the `[AdoNetClientInstrumentMethods]` _assembly_ attribute, to define some standard types, as well as which of the standard ADO.NET signatures to implement. For example:
92124

93125
```csharp
94126
[assembly: AdoNetClientInstrumentMethods(
@@ -114,6 +146,6 @@ We also have a special case for ADO.NET method instrumentation, as this is gener
114146
})]
115147
```
116148

117-
The above attribute shows how to select which signatures to implement, via the `TargetMethodAttributes` property. These attributes are nested types defined inside [`AdoNetClientInstrumentMethodsAttribute`](./AutoInstrumentation/AdoNet/AdoNetClientInstrumentMethodsAttribute.cs), each of which are associated with a given signature + instrumentation class (via the `[AdoNetClientInstrumentMethodsAttribute.AdoNetTargetSignature]` attribute)
149+
The above attribute shows how to select which signatures to implement, via the `TargetMethodAttributes` property. These attributes are nested types defined inside [`AdoNetClientInstrumentMethodsAttribute`](../../tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AdoNet/AdoNetClientInstrumentMethodsAttribute.cs), each of which are associated with a given signature + instrumentation class (via the `[AdoNetClientInstrumentMethodsAttribute.AdoNetTargetSignature]` attribute)
118150

119151
> Note that there are separate target method attributes if you are using the new abstract/interface instrumentation feature.

tracer/src/Datadog.Trace/DuckTyping/README.md renamed to docs/development/DuckTyping.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ In summary:
372372
<details>
373373
<summary>Best practices benchmarks</summary>
374374
375-
[Benchmark Code](../../../test/benchmarks/Benchmarks.Trace/DuckTyping/DuckTypeMethodCallComparisonBenchmark.cs)
375+
[Benchmark Code](../../tracer/test/benchmarks/Benchmarks.Trace/DuckTyping/DuckTypeMethodCallComparisonBenchmark.cs)
376376

377377
``` ini
378378
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.22000

tracer/README.MD

+3-3
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ You can develop the tracer on various environments.
3737
- Optional: [nuget.exe CLI](https://www.nuget.org/downloads) v5.3 or newer
3838
- Optional: [WiX Toolset 3.11.1](http://wixtoolset.org/releases/) or newer to build Windows installer (msi)
3939
- [WiX Toolset Visual Studio Extension](https://wixtoolset.org/releases/) to build installer from Visual Studio
40-
- Optional: [Docker for Windows](https://docs.docker.com/docker-for-windows/) to build Linux binaries and run integration tests on Linux containers. See [section on Docker Compose](#building-and-running-tests-with-docker-compose).
40+
- Optional: [Docker for Windows](https://docs.docker.com/docker-for-windows/) to build Linux binaries and run integration tests on Linux containers.
4141
- Requires Windows 10 (1607 Anniversary Update, Build 14393 or newer)
4242

4343
Microsoft provides [evaluation developer VMs](https://developer.microsoft.com/en-us/windows/downloads/virtual-machines) with Windows and Visual Studio pre-installed.
@@ -101,8 +101,8 @@ You can use Rider and CLion to develop on macOS. Building and testing can be don
101101

102102
## Additional Technical Documentation
103103

104-
* [Implementing an automatic instrumentation](./src/Datadog.Trace/ClrProfiler/README.md)
105-
* [Duck typing: usages, best practices, and benchmarks](./src/Datadog.Trace/DuckTyping/README.md)
104+
* [Implementing an automatic instrumentation](../docs/development/AutomaticInstrumentation.md)
105+
* [Duck typing: usages, best practices, and benchmarks](../docs/development/DuckTyping.md)
106106
* [Datadog.Trace NuGet package README](../docs/Datadog.Trace/README.md)
107107

108108
## Further Reading

0 commit comments

Comments
 (0)