How to write your own commands in dotnet-dump (2/2)
By: Christophe Nasarre
Tags: DevOps, Software Development, Csharp, Dotnet, Dotnet Core

In the previous post, I presented the new commands that were added to dotnet-dump and how to use them. It is now time to show how to implement such a command.

But before jumping into the code, you should first ensure that you have a valid use case that the Diagnostics team is not currently working on. I recommend to create an issue in the Diagnostics repository to explain what is missing for which scenario and propose to implement the corresponding command.

What is a dotnet-dump command?

Here is the directory structure related to the dotnet-dump tool in the Diagnostics repository:

The built binaries are generated under artifacts\bin\dotnet-dump\<Release or Debug>

etcoreapp2.1 folder if you need to test them outside of Visual Studio.

The eng folder contains the versions.props file that lists the versions for nuget dependencies. In my case, I had to reference the ParallelStacks.Runtime nuget so I added the following line: <ParallelStacksRuntimeVersion>2.0.1</ParallelStacksRuntimeVersion>

And in the dotnet-dump.csproj, this nuget is referenced with the same variable:

<PackageReference Include="ParallelStacks.Runtime.Version" Version="(ParallelStacksRuntimeVersion)

A question of class

Before thinking about a command implementation details, you need to ensure that your implementation follows the MIT license and to add the corresponding text at the top of each of your source files:

Next, to ease the implementation of a command, the CommandBase abstract class provides a few properties and helpers to write to the console:

As you can see, most of the commands derive from this abstract class. The only method to override is Invoke : it will be called when the user wants to execute your method.

How the command name used by the user is bound to your class is simple: just decorate the class with a Command attribute:

The Name property maps to the command typed by the user. If you want to define short names, add a CommandAlias attribute. The Help property is used when the Help command is called to list all available commands:

The new commands were supposed to be extension commands so I preferred to bind the implementation to as less as dependencies as possible. This is why they all derive from the ExtensionCommandBase class that contains what is needed:

In addition to the Invoke method to implement, it is also required to output the command help in your command GetDetailedHelp method implementation.

Beyond the obvious

After this introduction come a few questions: How to get access to global services? How to handle parameters?

The dotnet-dump application is providing dependency injection to the commands. The Analyzer.AddServices method is the place where the shared services are registered with the ServiceProvider . In my particular case, I needed to register an instance of ClrMDHelper ; a class that uses ClrMD API to provide high level helpers such as enumerating ThreadPool or Timer work items. The ClrMDHelper class needs the ClrRuntime corresponding to the memory dump but when the AddServices method gets called, the ClrRuntime instance is not already created. This lazy creation mechanism relies on factories that expect callbacks to lazily create service instance:

So when the ClrMDHelper instance will be needed, the callback will run and the constructor will get the ClrRuntime from the ServiceProvider container and set its own properties with it:

The second part of the dependency injection mechanism is that you simply need to add a property with a type registered in the container et voila! The ExtensionCommandBase defining the following property:

public ClrMDHelper Helper {get; set; }

allows any derived class to access the ClrMDHelper global service via the Helper property like in the TimersCommans.Invoke implementation:

How to parse parameters

Some commands accept parameters but the Invoke method has no parameter in its signature! Instead of passing to Invoke the string corresponding to what the user typed in dotnet-dump after your command, you just have to define what you expect as properties; decorated with Option/OptionAlias attributes if needed. Some automatic transformations are available such as to boolean or to number like in ParallelStacksCommand :

If the parameter -a is passed, the value will be true; otherwise it will be false.

You could also explicitly check if a parameter is missing with nullable types like in TaskStateCommand :

The numeric transformation supports decimal or hexadecimal 0x-prefixed syntaxes. However, if you need your own transformation, you define a string property decorated with the Argument attribute. For example, most addresses returned by SOS commands are prefixed by zeros but without 0x. So, as a user, I would like to simply copy and paste an address as a parameter for another command without having to manually type any “0x” prefix.

To support that scenario, I have added a parsing code to ConmmandBase abstract class:

and the TaskStateCommand shows how to use it:

You check if the parameter is given by checking against null or empty and then call TryParseAddress if an hexadecimal address is expected.

What’s next?

During the implementation of these extension command, the interactions with the Microsoft teams were really helpful. The follow-up and validation of pull requests were very fast. With the 9 hours time difference between us, I always did my best to answer the questions before the end of my day so they could work while I was sleeping. Beyond “simple” pull requests corresponding to new features, issues in ClrMD were taken into account very quickly and I want to thank Lee Culver for that.

Within Criteo, we are using tools that provide other high services that are not in dotnet-dump already such as easily dumping heap statistics per generation. This is why we have plan to add more commands before the end of the year.

It is your turn now! Create an issue to describe a command you miss and implement it in a pull request once the details are validated.