diff --git a/diagnostics.sln b/diagnostics.sln
index c53cec4f3f..23ae2ff91a 100644
--- a/diagnostics.sln
+++ b/diagnostics.sln
@@ -266,7 +266,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommonTestRunner", "src\tes
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotnetStack.UnitTests", "src\tests\dotnet-stack\DotnetStack.UnitTests.csproj", "{E8F133F8-4D20-475D-9D16-2BA236DAB65F}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Diagnostics.WebSocketServer", "src\Microsoft.Diagnostics.WebSocketServer\Microsoft.Diagnostics.WebSocketServer.csproj", "{1043FA82-37CC-4809-80DC-C1EB06A55133}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.WebSocketServer", "src\Microsoft.Diagnostics.WebSocketServer\Microsoft.Diagnostics.WebSocketServer.csproj", "{1043FA82-37CC-4809-80DC-C1EB06A55133}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestExtension", "src\tests\TestExtension\TestExtension.csproj", "{C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -1907,6 +1909,46 @@ Global
{1043FA82-37CC-4809-80DC-C1EB06A55133}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU
{1043FA82-37CC-4809-80DC-C1EB06A55133}.RelWithDebInfo|x86.ActiveCfg = Debug|Any CPU
{1043FA82-37CC-4809-80DC-C1EB06A55133}.RelWithDebInfo|x86.Build.0 = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|Any CPU.ActiveCfg = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|Any CPU.Build.0 = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|ARM.ActiveCfg = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|ARM.Build.0 = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|ARM64.ActiveCfg = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|ARM64.Build.0 = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|x64.ActiveCfg = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|x64.Build.0 = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|x86.ActiveCfg = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|x86.Build.0 = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|ARM.Build.0 = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|x64.Build.0 = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|x86.Build.0 = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|ARM.ActiveCfg = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|ARM.Build.0 = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|ARM64.Build.0 = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|x64.ActiveCfg = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|x64.Build.0 = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|x86.ActiveCfg = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|x86.Build.0 = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|ARM64.ActiveCfg = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|ARM64.Build.0 = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1966,6 +2008,7 @@ Global
{DFF48CB6-4504-41C6-A8F1-F4A3D316D49F} = {03479E19-3F18-49A6-910A-F5041E27E7C0}
{E8F133F8-4D20-475D-9D16-2BA236DAB65F} = {03479E19-3F18-49A6-910A-F5041E27E7C0}
{1043FA82-37CC-4809-80DC-C1EB06A55133} = {19FAB78C-3351-4911-8F0C-8C6056401740}
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E} = {03479E19-3F18-49A6-910A-F5041E27E7C0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {46465737-C938-44FC-BE1A-4CE139EBB5E0}
diff --git a/documentation/design-docs/ipc-protocol.md b/documentation/design-docs/ipc-protocol.md
index 60a268c760..a8c2496a90 100644
--- a/documentation/design-docs/ipc-protocol.md
+++ b/documentation/design-docs/ipc-protocol.md
@@ -383,6 +383,7 @@ enum class ProcessCommandId : uint8_t
EnablePerfMap = 0x05,
DisablePerfMap = 0x06,
ApplyStartupHook = 0x07
+ ProcessInfo3 = 0x08,
// future
}
```
@@ -804,7 +805,7 @@ In the event of an [error](#Errors), the runtime will attempt to send an error m
#### Inputs:
-Header: `{ Magic; Size; 0x0402; 0x0000 }`
+Header: `{ Magic; Size; 0x0404; 0x0000 }`
There is no payload.
@@ -848,6 +849,8 @@ struct Payload
}
```
+> Available since .NET 7.0
+
### `EnablePerfMap`
Command Code: `0x0405`
@@ -972,6 +975,66 @@ struct Payload
> Available since .NET 8.0
+### `ProcessInfo3`
+
+Command Code: `0x0408`
+
+The `ProcessInfo3` command queries the runtime for some basic information about the process. The returned payload is versioned and fields will be added over time.
+
+In the event of an [error](#Errors), the runtime will attempt to send an error message and subsequently close the connection.
+
+#### Inputs:
+
+Header: `{ Magic; Size; 0x0408; 0x0000 }`
+
+There is no payload.
+
+#### Returns (as an IPC Message Payload):
+
+Header: `{ Magic; size; 0xFF00; 0x0000; }`
+
+Payload:
+* `uint32 version`: the version of the payload returned. Future versions can add new fields after the end of the current structure, but will never remove or change any field that has already been defined.
+* `uint64 processId`: the process id in the process's PID-space
+* `GUID runtimeCookie`: a 128-bit GUID that should be unique across PID-spaces
+* `string commandLine`: the command line that invoked the process
+ * Windows: will be the same as the output of `GetCommandLineW`
+ * Non-Windows: will be the fully qualified path of the executable in `argv[0]` followed by all arguments as the appear in `argv` separated by spaces, i.e., `/full/path/to/argv[0] argv[1] argv[2] ...`
+* `string OS`: the operating system that the process is running on
+ * macOS => `"macOS"`
+ * Windows => `"Windows"`
+ * Linux => `"Linux"`
+ * other => `"Unknown"`
+* `string arch`: the architecture of the process
+ * 32-bit => `"x86"`
+ * 64-bit => `"x64"`
+ * ARM32 => `"arm32"`
+ * ARM64 => `"arm64"`
+ * Other => `"Unknown"`
+* `string managedEntrypointAssemblyName`: the assembly name from the assembly identity of the entrypoint assembly of the process. This is the same value that is returned from executing `System.Reflection.Assembly.GetEntryAssembly().GetName().Name` in the target process.
+* `string clrProductVersion`: the product version of the CLR of the process; may contain prerelease label information e.g. `6.0.0-preview.6.#####`
+* `string runtimeIdentifier`: information to identify the platform this runtime targets, e.g. `linux_musl_arm`64, `linux_x64`, or `windows_x64` are all valid identifiers. See [.NET RID Catalog](https://learn.microsoft.com/en-us/dotnet/core/rid-catalog) for more information.
+
+##### Details:
+
+Returns:
+```c++
+struct Payload
+{
+ uint32_t Version;
+ uint64_t ProcessId;
+ LPCWSTR CommandLine;
+ LPCWSTR OS;
+ LPCWSTR Arch;
+ GUID RuntimeCookie;
+ LPCWSTR ManagedEntrypointAssemblyName;
+ LPCWSTR ClrProductVersion;
+ LPCWSTR RuntimeIdentifier;
+}
+```
+
+> Available since .NET 8.0
+
## Errors
In the event an error occurs in the handling of an Ipc Message, the Diagnostic Server will attempt to send an Ipc Message encoding the error and subsequently close the connection. The connection will be closed **regardless** of the success of sending the error message. The Client is expected to be resilient in the event of a connection being abruptly closed.
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index 81280ad2f8..45ac8876e1 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -1,55 +1,55 @@
-
+
https://github.com/dotnet/symstore
- df78bdccafe0dca31c9e6a1b5c3cf21c33e8f9a1
+ a3b341f9e61c8d8e832c4acfeb5b3a2305e51bcc
-
+
https://github.com/microsoft/clrmd
- c7ec730380da83d9dcb63a3d8928da701219db8e
+ 903207ffe9dbac775a2a70d54980fc03abad4cb1
-
+
https://github.com/microsoft/clrmd
- c7ec730380da83d9dcb63a3d8928da701219db8e
+ 903207ffe9dbac775a2a70d54980fc03abad4cb1
-
+
https://github.com/dotnet/arcade
- 385129cbc980a515ddee2fa56f6b16f3183ed9bc
+ 822f095b8c815dd7b9161140a9ff8151de593f82
-
+
https://github.com/dotnet/arcade
- 385129cbc980a515ddee2fa56f6b16f3183ed9bc
+ 822f095b8c815dd7b9161140a9ff8151de593f82
https://github.com/dotnet/arcade
ccfe6da198c5f05534863bbb1bff66e830e0c6ab
-
+
https://github.com/dotnet/installer
- ec2c1ec1b16874f748cfc5d1f7da769be90e10c8
+ 0ffc9fdc93e578268a09b0dccdc4c3527f4697f3
-
+
https://github.com/dotnet/aspnetcore
- 9781991a2402d10e6a94f804907bafecf7852b67
+ 7ffeb436ad029d1e1012372b7bb345ad22770f09
-
+
https://github.com/dotnet/aspnetcore
- 9781991a2402d10e6a94f804907bafecf7852b67
+ 7ffeb436ad029d1e1012372b7bb345ad22770f09
-
+
https://github.com/dotnet/runtime
- 786b9872ad306d5b0febdc2e6c820b69e0e232dc
+ a9cc3c80fe43d19a38cacda4c1aecc51fb6eabb1
-
+
https://github.com/dotnet/runtime
- 786b9872ad306d5b0febdc2e6c820b69e0e232dc
+ a9cc3c80fe43d19a38cacda4c1aecc51fb6eabb1
-
+
https://github.com/dotnet/source-build-reference-packages
- 93c23409e630c4f267234540b0e3557b76a53ef4
+ 5d89368fe132c3f6210d661e18087db782b74f2d
@@ -60,13 +60,13 @@
https://github.com/dotnet/roslyn
6acaa7b7c0efea8ea292ca26888c0346fbf8b0c1
-
+
https://github.com/dotnet/roslyn-analyzers
- c6352bf2e1bd214fce090829de1042000d021497
+ 76d99c5f3e11f0600fae074270c0d89042c360f0
-
+
https://github.com/dotnet/roslyn-analyzers
- c6352bf2e1bd214fce090829de1042000d021497
+ 76d99c5f3e11f0600fae074270c0d89042c360f0
diff --git a/eng/Versions.props b/eng/Versions.props
index d36f1541b1..84939e6d25 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -16,26 +16,26 @@
- 1.0.442101
+ 1.0.450901
- 8.0.0-rc.1.23410.15
- 8.0.0-rc.1.23410.15
+ 8.0.0-rtm.23509.5
+ 8.0.0-rtm.23509.5
- 8.0.0-rc.2.23424.13
- 8.0.0-rc.2.23424.13
+ 8.0.0-rtm.23510.7
+ 8.0.0-rtm.23510.7
- 8.0.100-rc.2.23420.6
+ 8.0.100-rtm.23506.1
6.0.19
$(MicrosoftNETCoreApp60Version)
- 7.0.10
+ 7.0.11
$(MicrosoftNETCoreApp70Version)
$(MicrosoftNETCoreApp60Version)
$(MicrosoftNETCoreApp70Version)
- 8.0.0-rc.1.23414.4
+ 8.0.0-rtm.23504.8
@@ -43,10 +43,11 @@
true
5.0.0
+ 6.0.0
6.0.0
- 3.0.442202
- 16.9.0-beta1.21055.5
+ 3.1.451001
+ 16.11.27-beta1.23180.1
3.0.7
6.0.0
6.0.0
@@ -58,15 +59,15 @@
4.5.1
4.5.5
4.3.0
- 4.7.2
- 4.7.1
+ 6.0.0
+ 6.0.8
2.0.3
- 8.0.0-beta.23419.1
+ 9.0.0-beta.23508.1
1.2.0-beta.406
7.0.0-beta.22316.2
10.0.18362
13.0.1
- 8.0.0-alpha.1.23424.1
+ 9.0.0-alpha.1.23510.3
3.11.0
@@ -80,8 +81,8 @@
4.4.0
4.4.0
$(MicrosoftCodeAnalysisVersion)
- 3.3.5-beta1.23124.1
- 8.0.0-preview1.23124.1
+ 3.11.0-beta1.23420.2
+ 8.0.0-preview.23420.2
@@ -92,8 +93,8 @@
Any tools that contribute to the design-time experience should use the MicrosoftCodeAnalysisVersion_LatestVS property above to ensure
they do not break the local dev experience.
-->
- 4.6.0-1.23073.4
- 4.6.0-1.23073.4
- 4.6.0-1.23073.4
+ 4.8.0-2.23422.14
+ 4.8.0-2.23422.14
+ 4.8.0-2.23422.14
diff --git a/eng/common/cross/toolchain.cmake b/eng/common/cross/toolchain.cmake
index a88d643c8a..0998e875e5 100644
--- a/eng/common/cross/toolchain.cmake
+++ b/eng/common/cross/toolchain.cmake
@@ -207,6 +207,7 @@ elseif(ILLUMOS)
set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lssp")
elseif(HAIKU)
set(CMAKE_SYSROOT "${CROSS_ROOTFS}")
+ set(CMAKE_PROGRAM_PATH "${CMAKE_PROGRAM_PATH};${CROSS_ROOTFS}/cross-tools-x86_64/bin")
set(TOOLSET_PREFIX ${TOOLCHAIN}-)
function(locate_toolchain_exec exec var)
@@ -217,7 +218,6 @@ elseif(HAIKU)
endif()
find_program(EXEC_LOCATION_${exec}
- PATHS "${CROSS_ROOTFS}/cross-tools-x86_64/bin"
NAMES
"${TOOLSET_PREFIX}${exec}${CLR_CMAKE_COMPILER_FILE_NAME_VERSION}"
"${TOOLSET_PREFIX}${exec}")
diff --git a/eng/common/loc/P22DotNetHtmlLocalization.lss b/eng/common/loc/P22DotNetHtmlLocalization.lss
index 858a0b237c..5d892d6193 100644
Binary files a/eng/common/loc/P22DotNetHtmlLocalization.lss and b/eng/common/loc/P22DotNetHtmlLocalization.lss differ
diff --git a/eng/common/sdk-task.ps1 b/eng/common/sdk-task.ps1
index 6c4ac6fec1..91f8196cc8 100644
--- a/eng/common/sdk-task.ps1
+++ b/eng/common/sdk-task.ps1
@@ -64,7 +64,7 @@ try {
$GlobalJson.tools | Add-Member -Name "vs" -Value (ConvertFrom-Json "{ `"version`": `"16.5`" }") -MemberType NoteProperty
}
if( -not ($GlobalJson.tools.PSObject.Properties.Name -match "xcopy-msbuild" )) {
- $GlobalJson.tools | Add-Member -Name "xcopy-msbuild" -Value "17.6.0-2" -MemberType NoteProperty
+ $GlobalJson.tools | Add-Member -Name "xcopy-msbuild" -Value "17.7.2-1" -MemberType NoteProperty
}
if ($GlobalJson.tools."xcopy-msbuild".Trim() -ine "none") {
$xcopyMSBuildToolsFolder = InitializeXCopyMSBuild $GlobalJson.tools."xcopy-msbuild" -install $true
diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1
index aa74ab4a81..84cfe7cd9c 100644
--- a/eng/common/tools.ps1
+++ b/eng/common/tools.ps1
@@ -379,13 +379,13 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements =
}
# Minimum VS version to require.
- $vsMinVersionReqdStr = '17.6'
+ $vsMinVersionReqdStr = '17.7'
$vsMinVersionReqd = [Version]::new($vsMinVersionReqdStr)
# If the version of msbuild is going to be xcopied,
# use this version. Version matches a package here:
- # https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet-eng/NuGet/RoslynTools.MSBuild/versions/17.6.0-2
- $defaultXCopyMSBuildVersion = '17.6.0-2'
+ # https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet-eng/NuGet/RoslynTools.MSBuild/versions/17.7.2-1
+ $defaultXCopyMSBuildVersion = '17.7.2-1'
if (!$vsRequirements) {
if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') {
diff --git a/eng/native/tryrun.cmake b/eng/native/tryrun.cmake
index fa38d24599..a5a8b51ac6 100644
--- a/eng/native/tryrun.cmake
+++ b/eng/native/tryrun.cmake
@@ -180,8 +180,6 @@ else()
message(FATAL_ERROR "Unsupported platform. OS: ${CMAKE_SYSTEM_NAME}, arch: ${TARGET_ARCH_NAME}")
endif()
-if(TARGET_ARCH_NAME MATCHES "^(x86|x64|s390x|armv6|loongarch64|ppc64le)$")
+if(TARGET_ARCH_NAME MATCHES "^(x86|x64|s390x|armv6|loongarch64|riscv64|ppc64le)$")
set_cache_value(HAVE_FUNCTIONAL_PTHREAD_ROBUST_MUTEXES_EXITCODE 0)
-elseif (TARGET_ARCH_NAME STREQUAL "riscv64")
- set_cache_value(HAVE_FUNCTIONAL_PTHREAD_ROBUST_MUTEXES_EXITCODE 1)
endif()
diff --git a/global.json b/global.json
index b9885f3713..92fde626a4 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"tools": {
- "dotnet": "8.0.100-preview.7.23376.3",
+ "dotnet": "8.0.100-rc.1.23455.8",
"runtimes": {
"dotnet": [
"$(MicrosoftNETCoreApp60Version)",
@@ -16,6 +16,6 @@
},
"msbuild-sdks": {
"Microsoft.Build.NoTargets": "3.5.0",
- "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.23419.1"
+ "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.23508.1"
}
}
diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/AssemblyResolver.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/AssemblyResolver.cs
index 95eb3690dc..626b31082f 100644
--- a/src/Microsoft.Diagnostics.DebugServices.Implementation/AssemblyResolver.cs
+++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/AssemblyResolver.cs
@@ -37,6 +37,15 @@ private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEven
string probingPath;
Assembly assembly;
+ // Look next to the executing assembly
+ probingPath = Path.Combine(_defaultAssembliesPath, fileName);
+ Debug.WriteLine($"Considering {probingPath} based on ExecutingAssembly");
+ if (Probe(probingPath, referenceName.Version, out assembly))
+ {
+ Debug.WriteLine($"Matched {probingPath} based on ExecutingAssembly");
+ return assembly;
+ }
+
// Look next to requesting assembly
assemblyPath = args.RequestingAssembly?.Location;
if (!string.IsNullOrEmpty(assemblyPath))
@@ -50,15 +59,6 @@ private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEven
}
}
- // Look next to the executing assembly
- probingPath = Path.Combine(_defaultAssembliesPath, fileName);
- Debug.WriteLine($"Considering {probingPath} based on ExecutingAssembly");
- if (Probe(probingPath, referenceName.Version, out assembly))
- {
- Debug.WriteLine($"Matched {probingPath} based on ExecutingAssembly");
- return assembly;
- }
-
return null;
}
diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/CommandService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/CommandService.cs
index 01e4cfdb61..2c87c85141 100644
--- a/src/Microsoft.Diagnostics.DebugServices.Implementation/CommandService.cs
+++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/CommandService.cs
@@ -9,10 +9,9 @@
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using System.CommandLine.Parsing;
-using System.Diagnostics;
using System.Linq;
using System.Reflection;
-using System.Runtime.InteropServices;
+using System.Text;
using System.Threading.Tasks;
namespace Microsoft.Diagnostics.DebugServices.Implementation
@@ -22,9 +21,8 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
///
public class CommandService : ICommandService
{
- private Parser _parser;
- private readonly CommandLineBuilder _rootBuilder;
- private readonly Dictionary _commandHandlers = new();
+ private readonly List _commandGroups = new();
+ private readonly string _commandPrompt;
///
/// Create an instance of the command processor;
@@ -32,8 +30,10 @@ public class CommandService : ICommandService
/// command prompted used in help message
public CommandService(string commandPrompt = null)
{
- _rootBuilder = new CommandLineBuilder(new Command(commandPrompt ?? ">"));
- _rootBuilder.UseHelpBuilder((bindingContext) => new LocalHelpBuilder(this, bindingContext.Console, useHelpBuilder: false));
+ _commandPrompt = commandPrompt ?? ">";
+
+ // Create default command group (should always be last in this list)
+ _commandGroups.Add(new CommandGroup(_commandPrompt));
}
///
@@ -41,125 +41,168 @@ public CommandService(string commandPrompt = null)
///
/// command line text
/// services for the command
- /// true success, false failure
+ /// true - found command, false - command not found
+ /// empty command line
+ /// other errors
+ /// parsing error
public bool Execute(string commandLine, IServiceProvider services)
{
- // Parse the command line and invoke the command
- ParseResult parseResult = Parser.Parse(commandLine);
+ string[] commandLineArray = CommandLineStringSplitter.Instance.Split(commandLine).ToArray();
+ if (commandLineArray.Length <= 0)
+ {
+ throw new ArgumentException("Empty command line", nameof(commandLine));
+ }
+ string commandName = commandLineArray[0].Trim();
+ return Execute(commandName, commandLineArray, services);
+ }
+
+ ///
+ /// Parse and execute the command.
+ ///
+ /// command name
+ /// command arguments/options
+ /// services for the command
+ /// true - found command, false - command not found
+ /// empty command name or arguments
+ /// other errors
+ /// parsing error
+ public bool Execute(string commandName, string commandArguments, IServiceProvider services)
+ {
+ commandName = commandName.Trim();
+ string[] commandLineArray = CommandLineStringSplitter.Instance.Split(commandName + " " + (commandArguments ?? "")).ToArray();
+ if (commandLineArray.Length <= 0)
+ {
+ throw new ArgumentException("Empty command name or arguments", nameof(commandArguments));
+ }
+ return Execute(commandName, commandLineArray, services);
+ }
- InvocationContext context = new(parseResult, new LocalConsole(services));
- if (parseResult.Errors.Count > 0)
+ ///
+ /// Find, parse and execute the command.
+ ///
+ /// command name
+ /// command line
+ /// services for the command
+ /// true - found command, false - command not found
+ /// empty command name
+ /// other errors
+ /// parsing error
+ private bool Execute(string commandName, string[] commandLineArray, IServiceProvider services)
+ {
+ if (string.IsNullOrEmpty(commandName))
{
- context.InvocationResult = new ParseErrorResult();
+ throw new ArgumentException("Empty command name", nameof(commandName));
}
- else
+ List messages = new();
+ foreach (CommandGroup group in _commandGroups)
{
- if (parseResult.CommandResult.Command is Command command)
+ if (group.TryGetCommandHandler(commandName, out CommandHandler handler))
{
- if (command.Handler is CommandHandler handler)
+ try
{
- ITarget target = services.GetService();
- if (!handler.IsValidPlatform(target))
+ if (handler.IsCommandSupported(group.Parser, services))
{
- if (target != null)
- {
- context.Console.Error.WriteLine($"Command '{command.Name}' not supported on this target");
- }
- else
+ if (group.Execute(commandLineArray, services))
{
- context.Console.Error.WriteLine($"Command '{command.Name}' needs a target");
+ return true;
}
- return false;
- }
- try
- {
- handler.Invoke(context, services);
}
- catch (Exception ex)
+ if (handler.FilterInvokeMessage != null)
{
- if (ex is NullReferenceException or
- ArgumentException or
- ArgumentNullException or
- ArgumentOutOfRangeException or
- NotImplementedException)
- {
- context.Console.Error.WriteLine(ex.ToString());
- }
- else
- {
- context.Console.Error.WriteLine(ex.Message);
- }
- Trace.TraceError(ex.ToString());
- return false;
+ messages.Add(handler.FilterInvokeMessage);
}
}
+ catch (CommandNotFoundException ex)
+ {
+ messages.Add(ex.Message);
+ }
}
}
-
- context.InvocationResult?.Apply(context);
- return context.ResultCode == 0;
+ if (messages.Count > 0)
+ {
+ throw new CommandNotFoundException(string.Concat(messages.Select(s => s + Environment.NewLine)));
+ }
+ return false;
}
///
/// Displays the help for a command
///
- /// name of the command or alias
/// service provider
- /// true if success, false if command not found
- public bool DisplayHelp(string commandName, IServiceProvider services)
+ /// command invocation and help enumeration
+ public IEnumerable<(string Invocation, string Help)> GetAllCommandHelp(IServiceProvider services)
{
- Command command = null;
- if (!string.IsNullOrEmpty(commandName))
+ List<(string Invocation, string Help)> help = new();
+ foreach (CommandGroup group in _commandGroups)
{
- command = _rootBuilder.Command.Children.OfType().FirstOrDefault((cmd) => commandName == cmd.Name || cmd.Aliases.Any((alias) => commandName == alias));
- if (command == null)
- {
- return false;
- }
- if (command.Handler is CommandHandler handler)
+ foreach (CommandHandler handler in group.CommandHandlers)
{
- ITarget target = services.GetService();
- if (!handler.IsValidPlatform(target))
+ try
+ {
+ if (handler.IsCommandSupported(group.Parser, services))
+ {
+ string invocation = handler.HelpInvocation;
+ help.Add((invocation, handler.Help));
+ }
+ }
+ catch (CommandNotFoundException)
{
- return false;
}
}
}
- else
- {
- ITarget target = services.GetService();
+ return help;
+ }
- // Create temporary builder adding only the commands that are valid for the target
- CommandLineBuilder builder = new(new Command(_rootBuilder.Command.Name));
- foreach (Command cmd in _rootBuilder.Command.Children.OfType())
+ ///
+ /// Displays the detailed help for a command
+ ///
+ /// name of the command or alias
+ /// service provider
+ /// the width to format the help or int.MaxValue
+ /// help text or null if not found
+ public string GetDetailedHelp(string commandName, IServiceProvider services, int consoleWidth)
+ {
+ if (string.IsNullOrWhiteSpace(commandName))
+ {
+ throw new ArgumentNullException(nameof(commandName));
+ }
+ List messages = new();
+ foreach (CommandGroup group in _commandGroups)
+ {
+ if (group.TryGetCommand(commandName, out Command command))
{
- if (cmd.Handler is CommandHandler handler)
+ if (command.Handler is CommandHandler handler)
{
- if (handler.IsValidPlatform(target))
+ try
+ {
+ if (handler.IsCommandSupported(group.Parser, services))
+ {
+ return group.GetDetailedHelp(command, services, consoleWidth);
+ }
+ if (handler.FilterInvokeMessage != null)
+ {
+ messages.Add(handler.FilterInvokeMessage);
+ }
+ }
+ catch (CommandNotFoundException ex)
{
- builder.AddCommand(cmd);
+ messages.Add(ex.Message);
}
}
}
- command = builder.Command;
}
- Debug.Assert(command != null);
- IHelpBuilder helpBuilder = new LocalHelpBuilder(this, new LocalConsole(services), useHelpBuilder: true);
- helpBuilder.Write(command);
- return true;
+ if (messages.Count > 0)
+ {
+ return string.Concat(messages.Select(s => s + Environment.NewLine));
+ }
+ return null;
}
///
- /// Does this command or alias exists?
- ///
- /// command or alias name
- /// true if command exists
- public bool IsCommand(string commandName) => _rootBuilder.Command.Children.Contains(commandName);
-
- ///
- /// Enumerates all the command's name and help
+ /// Enumerates all the command's name, help and aliases
///
- public IEnumerable<(string name, string help, IEnumerable aliases)> Commands => _commandHandlers.Select((keypair) => (keypair.Value.Name, keypair.Value.Help, keypair.Value.Aliases));
+ public IEnumerable<(string name, string help, IEnumerable aliases)> Commands =>
+ _commandGroups.SelectMany((group) => group.CommandHandlers).Select((handler) => (handler.Name, handler.Help, handler.Aliases));
///
/// Add the commands and aliases attributes found in the type.
@@ -185,84 +228,242 @@ public void AddCommands(Type type, Func factory)
CommandAttribute[] commandAttributes = (CommandAttribute[])baseType.GetCustomAttributes(typeof(CommandAttribute), inherit: true);
foreach (CommandAttribute commandAttribute in commandAttributes)
{
- if ((commandAttribute.Flags & CommandFlags.Manual) == 0 || factory != null)
+ factory ??= (services) => Utilities.CreateInstance(type, services);
+
+ bool dup = true;
+ foreach (CommandGroup group in _commandGroups)
{
- factory ??= (services) => Utilities.CreateInstance(type, services);
- CreateCommand(baseType, commandAttribute, factory);
+ // If the group doesn't contain a duplicate command name, add it to that group
+ if (!group.Contains(commandAttribute.Name))
+ {
+ group.CreateCommand(baseType, commandAttribute, factory);
+ dup = false;
+ break;
+ }
+ }
+ // If this is a duplicate command, create a new group and add it to the beginning. The default group must be last.
+ if (dup)
+ {
+ CommandGroup group = new(_commandPrompt);
+ _commandGroups.Insert(0, group);
+ group.CreateCommand(baseType, commandAttribute, factory);
}
}
}
-
- // Build or re-build parser instance after all these commands and aliases are added
- FlushParser();
}
}
- private void CreateCommand(Type type, CommandAttribute commandAttribute, Func factory)
+ ///
+ /// This groups like commands that may have the same name as another group or the default one.
+ ///
+ private sealed class CommandGroup
{
- Command command = new(commandAttribute.Name, commandAttribute.Help);
- List<(PropertyInfo, Option)> properties = new();
- List<(PropertyInfo, Argument)> arguments = new();
+ private Parser _parser;
+ private readonly CommandLineBuilder _rootBuilder;
+ private readonly Dictionary _commandHandlers = new();
- foreach (string alias in commandAttribute.Aliases)
+ ///
+ /// Create an instance of the command processor;
+ ///
+ /// command prompted used in help message
+ public CommandGroup(string commandPrompt = null)
{
- command.AddAlias(alias);
+ _rootBuilder = new CommandLineBuilder(new Command(commandPrompt));
}
- foreach (PropertyInfo property in type.GetProperties().Where(p => p.CanWrite))
+ ///
+ /// Parse and execute the command line.
+ ///
+ /// command line text
+ /// services for the command
+ /// true if command was found and executed without error
+ /// parsing error
+ internal bool Execute(IReadOnlyList commandLine, IServiceProvider services)
{
- ArgumentAttribute argumentAttribute = (ArgumentAttribute)property.GetCustomAttributes(typeof(ArgumentAttribute), inherit: false).SingleOrDefault();
- if (argumentAttribute != null)
- {
- IArgumentArity arity = property.PropertyType.IsArray ? ArgumentArity.ZeroOrMore : ArgumentArity.ZeroOrOne;
+ // Parse the command line and invoke the command
+ ParseResult parseResult = Parser.Parse(commandLine);
- Argument argument = new()
+ if (parseResult.Errors.Count > 0)
+ {
+ StringBuilder sb = new();
+ foreach (ParseError error in parseResult.Errors)
{
- Name = argumentAttribute.Name ?? property.Name.ToLowerInvariant(),
- Description = argumentAttribute.Help,
- ArgumentType = property.PropertyType,
- Arity = arity
- };
- command.AddArgument(argument);
- arguments.Add((property, argument));
+ sb.AppendLine(error.Message);
+ }
+ string helpText = GetDetailedHelp(parseResult.CommandResult.Command, services, int.MaxValue);
+ throw new CommandParsingException(sb.ToString(), helpText);
}
else
{
- OptionAttribute optionAttribute = (OptionAttribute)property.GetCustomAttributes(typeof(OptionAttribute), inherit: false).SingleOrDefault();
- if (optionAttribute != null)
+ if (parseResult.CommandResult.Command is Command command)
{
- Option option = new(optionAttribute.Name ?? BuildOptionAlias(property.Name), optionAttribute.Help)
+ if (command.Handler is CommandHandler handler)
{
- Argument = new Argument { ArgumentType = property.PropertyType }
- };
- command.AddOption(option);
- properties.Add((property, option));
+ InvocationContext context = new(parseResult, new LocalConsole(services.GetService()));
+ handler.Invoke(context, services);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ ///
+ /// Build/return parser
+ ///
+ internal Parser Parser => _parser ??= _rootBuilder.Build();
+
+ ///
+ /// Returns all the command handler instances
+ ///
+ internal IEnumerable CommandHandlers => _commandHandlers.Values;
+
+ ///
+ /// Returns true if command or command alias is found
+ ///
+ internal bool Contains(string commandName) => _rootBuilder.Command.Children.Contains(commandName);
+
+ ///
+ /// Returns the command handler for the command or command alias
+ ///
+ /// command or alias
+ /// handler instance
+ /// true if found
+ internal bool TryGetCommandHandler(string commandName, out CommandHandler handler)
+ {
+ handler = null;
+ if (TryGetCommand(commandName, out Command command))
+ {
+ handler = command.Handler as CommandHandler;
+ }
+ return handler != null;
+ }
+
+ ///
+ /// Returns the command instance for the command or command alias
+ ///
+ /// command or alias
+ /// command instance
+ /// true if found
+ internal bool TryGetCommand(string commandName, out Command command)
+ {
+ command = _rootBuilder.Command.Children.GetByAlias(commandName) as Command;
+ return command != null;
+ }
+
+ ///
+ /// Add the commands and aliases attributes found in the type.
+ ///
+ /// Command type to search
+ /// function to create command instance
+ internal void AddCommands(Type type, Func factory)
+ {
+ for (Type baseType = type; baseType != null; baseType = baseType.BaseType)
+ {
+ if (baseType == typeof(CommandBase))
+ {
+ break;
+ }
+ CommandAttribute[] commandAttributes = (CommandAttribute[])baseType.GetCustomAttributes(typeof(CommandAttribute), inherit: false);
+ foreach (CommandAttribute commandAttribute in commandAttributes)
+ {
+ factory ??= (services) => Utilities.CreateInstance(type, services);
+ CreateCommand(baseType, commandAttribute, factory);
+ }
+ }
+
+ // Build or re-build parser instance after all these commands and aliases are added
+ FlushParser();
+ }
+
+ internal void CreateCommand(Type type, CommandAttribute commandAttribute, Func factory)
+ {
+ Command command = new(commandAttribute.Name, commandAttribute.Help);
+ List<(PropertyInfo, Argument)> arguments = new();
+ List<(PropertyInfo, Option)> options = new();
- foreach (string alias in optionAttribute.Aliases)
+ foreach (string alias in commandAttribute.Aliases)
+ {
+ command.AddAlias(alias);
+ }
+
+ foreach (PropertyInfo property in type.GetProperties().Where(p => p.CanWrite))
+ {
+ ArgumentAttribute argumentAttribute = (ArgumentAttribute)property.GetCustomAttributes(typeof(ArgumentAttribute), inherit: false).SingleOrDefault();
+ if (argumentAttribute != null)
+ {
+ IArgumentArity arity = property.PropertyType.IsArray ? ArgumentArity.ZeroOrMore : ArgumentArity.ZeroOrOne;
+
+ Argument argument = new()
+ {
+ Name = argumentAttribute.Name ?? property.Name.ToLowerInvariant(),
+ Description = argumentAttribute.Help,
+ ArgumentType = property.PropertyType,
+ Arity = arity
+ };
+ command.AddArgument(argument);
+ arguments.Add((property, argument));
+ }
+ else
+ {
+ OptionAttribute optionAttribute = (OptionAttribute)property.GetCustomAttributes(typeof(OptionAttribute), inherit: false).SingleOrDefault();
+ if (optionAttribute != null)
{
- option.AddAlias(alias);
+ Option option = new(optionAttribute.Name ?? BuildOptionAlias(property.Name), optionAttribute.Help)
+ {
+ Argument = new Argument { ArgumentType = property.PropertyType }
+ };
+ command.AddOption(option);
+ options.Add((property, option));
+
+ foreach (string alias in optionAttribute.Aliases)
+ {
+ option.AddAlias(alias);
+ }
}
}
}
+
+ CommandHandler handler = new(commandAttribute, arguments, options, type, factory);
+ _commandHandlers.Add(command.Name, handler);
+ command.Handler = handler;
+ _rootBuilder.AddCommand(command);
+
+ // Build or re-build parser instance after this command is added
+ FlushParser();
}
- CommandHandler handler = new(commandAttribute, arguments, properties, type, factory);
- _commandHandlers.Add(command.Name, handler);
- command.Handler = handler;
- _rootBuilder.AddCommand(command);
- }
+ internal string GetDetailedHelp(ICommand command, IServiceProvider services, int windowWidth)
+ {
+ CaptureConsole console = new();
- private Parser Parser => _parser ??= _rootBuilder.Build();
+ // Get the command help
+ HelpBuilder helpBuilder = new(console, maxWidth: windowWidth);
+ helpBuilder.Write(command);
- private void FlushParser() => _parser = null;
+ // Get the detailed help if any
+ if (TryGetCommandHandler(command.Name, out CommandHandler handler))
+ {
+ string helpText = handler.GetDetailedHelp(Parser, services);
+ if (helpText is not null)
+ {
+ console.Out.Write(helpText);
+ }
+ }
- private static string BuildOptionAlias(string parameterName)
- {
- if (string.IsNullOrWhiteSpace(parameterName))
+ return console.ToString();
+ }
+
+ private void FlushParser() => _parser = null;
+
+ private static string BuildOptionAlias(string parameterName)
{
- throw new ArgumentException("Value cannot be null or whitespace.", nameof(parameterName));
+ if (string.IsNullOrWhiteSpace(parameterName))
+ {
+ throw new ArgumentException("Value cannot be null or whitespace.", nameof(parameterName));
+ }
+ return parameterName.Length > 1 ? $"--{parameterName.ToKebabCase()}" : $"-{parameterName.ToLowerInvariant()}";
}
- return parameterName.Length > 1 ? $"--{parameterName.ToKebabCase()}" : $"-{parameterName.ToLowerInvariant()}";
}
///
@@ -272,28 +473,68 @@ private sealed class CommandHandler : ICommandHandler
{
private readonly CommandAttribute _commandAttribute;
private readonly IEnumerable<(PropertyInfo Property, Argument Argument)> _arguments;
- private readonly IEnumerable<(PropertyInfo Property, Option Option)> _properties;
+ private readonly IEnumerable<(PropertyInfo Property, Option Option)> _options;
private readonly Func _factory;
private readonly MethodInfo _methodInfo;
private readonly MethodInfo _methodInfoHelp;
+ private readonly MethodInfo _methodInfoFilter;
+ private readonly FilterInvokeAttribute _filterInvokeAttribute;
public CommandHandler(
CommandAttribute commandAttribute,
IEnumerable<(PropertyInfo, Argument)> arguments,
- IEnumerable<(PropertyInfo, Option)> properties,
+ IEnumerable<(PropertyInfo, Option)> options,
Type type,
Func factory)
{
_commandAttribute = commandAttribute;
_arguments = arguments;
- _properties = properties;
+ _options = options;
_factory = factory;
- _methodInfo = type.GetMethods().Where((methodInfo) => methodInfo.GetCustomAttribute() != null).SingleOrDefault() ??
+ // Now search for the command, help and filter attributes in the command type
+ foreach (MethodInfo methodInfo in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy))
+ {
+ if (methodInfo.GetCustomAttribute() != null)
+ {
+ if (_methodInfo != null)
+ {
+ throw new ArgumentException($"Multiple CommandInvokeAttribute's found in {type}");
+ }
+ _methodInfo = methodInfo;
+ }
+ if (methodInfo.GetCustomAttribute() != null)
+ {
+ if (_methodInfoHelp != null)
+ {
+ throw new ArgumentException($"Multiple HelpInvokeAttribute's found in {type}");
+ }
+ if (methodInfo.ReturnType != typeof(string))
+ {
+ throw new ArgumentException($"HelpInvokeAttribute doesn't return string in {type}");
+ }
+ _methodInfoHelp = methodInfo;
+ }
+ FilterInvokeAttribute filterInvokeAttribute = methodInfo.GetCustomAttribute();
+ if (filterInvokeAttribute != null)
+ {
+ if (_methodInfoFilter != null)
+ {
+ throw new ArgumentException($"Multiple FilterInvokeAttribute's found in {type}");
+ }
+ if (methodInfo.ReturnType != typeof(bool))
+ {
+ throw new ArgumentException($"FilterInvokeAttribute doesn't return bool in {type}");
+ }
+ _filterInvokeAttribute = filterInvokeAttribute;
+ _methodInfoFilter = methodInfo;
+ }
+ }
+ if (_methodInfo == null)
+ {
throw new ArgumentException($"No command invoke method found in {type}");
-
- _methodInfoHelp = type.GetMethods().Where((methodInfo) => methodInfo.GetCustomAttribute() != null).SingleOrDefault();
+ }
}
Task ICommandHandler.InvokeAsync(InvocationContext context)
@@ -311,37 +552,25 @@ Task ICommandHandler.InvokeAsync(InvocationContext context)
///
internal string Help => _commandAttribute.Help;
+ ///
+ /// Filter invoke message or null if no attribute or message
+ ///
+ internal string FilterInvokeMessage => _filterInvokeAttribute?.Message;
+
///
/// Returns the list of the command's aliases.
///
internal IEnumerable Aliases => _commandAttribute.Aliases;
///
- /// Returns true if the command should be added.
+ /// Returns the list of arguments
///
- internal bool IsValidPlatform(ITarget target)
- {
- if ((_commandAttribute.Flags & CommandFlags.Global) != 0)
- {
- return true;
- }
- if (target != null)
- {
- if (target.OperatingSystem == OSPlatform.Windows)
- {
- return (_commandAttribute.Flags & CommandFlags.Windows) != 0;
- }
- if (target.OperatingSystem == OSPlatform.Linux)
- {
- return (_commandAttribute.Flags & CommandFlags.Linux) != 0;
- }
- if (target.OperatingSystem == OSPlatform.OSX)
- {
- return (_commandAttribute.Flags & CommandFlags.OSX) != 0;
- }
- }
- return false;
- }
+ internal IEnumerable Arguments => _arguments.Select((a) => a.Argument);
+
+ ///
+ /// Returns true is the command is supported by the command filter. Calls the FilterInvokeAttribute marked method.
+ ///
+ internal bool IsCommandSupported(Parser parser, IServiceProvider services) => _methodInfoFilter == null || (bool)Invoke(_methodInfoFilter, context: null, parser, services);
///
/// Execute the command synchronously.
@@ -350,32 +579,56 @@ internal bool IsValidPlatform(ITarget target)
/// service provider
internal void Invoke(InvocationContext context, IServiceProvider services) => Invoke(_methodInfo, context, context.Parser, services);
+ ///
+ /// Return the various ways the command can be invoked. For building the help text.
+ ///
+ internal string HelpInvocation
+ {
+ get
+ {
+ IEnumerable rawAliases = new string[] { Name }.Concat(Aliases);
+ string invocation = string.Join(", ", rawAliases);
+ foreach (Argument argument in Arguments)
+ {
+ string argumentDescriptor = argument.Name;
+ if (!string.IsNullOrWhiteSpace(argumentDescriptor))
+ {
+ invocation = $"{invocation} <{argumentDescriptor}>";
+ }
+ }
+ return invocation;
+ }
+ }
+
///
/// Executes the command's help invoke function if exists
///
/// parser instance
/// service provider
/// true help called, false no help function
- internal bool InvokeHelp(Parser parser, IServiceProvider services)
+ internal string GetDetailedHelp(Parser parser, IServiceProvider services)
{
if (_methodInfoHelp == null)
{
- return false;
+ return null;
}
// The InvocationContext is null so the options and arguments in the
// command instance created are not set. The context for the command
// requesting help (either the help command or some other command using
// --help) won't work for the command instance that implements it's own
// help (SOS command).
- Invoke(_methodInfoHelp, context: null, parser, services);
- return true;
+ return (string)Invoke(_methodInfoHelp, context: null, parser, services);
}
- private void Invoke(MethodInfo methodInfo, InvocationContext context, Parser parser, IServiceProvider services)
+ private object Invoke(MethodInfo methodInfo, InvocationContext context, Parser parser, IServiceProvider services)
{
- object instance = _factory(services);
- SetProperties(context, parser, instance);
- Utilities.Invoke(methodInfo, instance, services);
+ object instance = null;
+ if (!methodInfo.IsStatic)
+ {
+ instance = _factory(services);
+ SetProperties(context, parser, instance);
+ }
+ return Utilities.Invoke(methodInfo, instance, services);
}
private void SetProperties(InvocationContext context, Parser parser, object instance)
@@ -390,31 +643,28 @@ private void SetProperties(InvocationContext context, Parser parser, object inst
}
// Now initialize the option and service properties from the default and command line options
- foreach ((PropertyInfo Property, Option Option) property in _properties)
+ foreach ((PropertyInfo Property, Option Option) option in _options)
{
- object value = property.Property.GetValue(instance);
+ object value = option.Property.GetValue(instance);
- if (property.Option != null)
+ if (defaultParseResult != null)
{
- if (defaultParseResult != null)
+ OptionResult defaultOptionResult = defaultParseResult.FindResultFor(option.Option);
+ if (defaultOptionResult != null)
{
- OptionResult defaultOptionResult = defaultParseResult.FindResultFor(property.Option);
- if (defaultOptionResult != null)
- {
- value = defaultOptionResult.GetValueOrDefault();
- }
+ value = defaultOptionResult.GetValueOrDefault();
}
- if (context != null)
+ }
+ if (context != null)
+ {
+ OptionResult optionResult = context.ParseResult.FindResultFor(option.Option);
+ if (optionResult != null)
{
- OptionResult optionResult = context.ParseResult.FindResultFor(property.Option);
- if (optionResult != null)
- {
- value = optionResult.GetValueOrDefault();
- }
+ value = optionResult.GetValueOrDefault();
}
}
- property.Property.SetValue(instance, value);
+ option.Property.SetValue(instance, value);
}
// Initialize any argument properties from the default and command line arguments
@@ -463,66 +713,46 @@ private void SetProperties(InvocationContext context, Parser parser, object inst
}
///
- /// Local help builder that allows commands to provide more detailed help
- /// text via the "InvokeHelp" function.
+ /// IConsole implementation that captures all the output into a string.
///
- private sealed class LocalHelpBuilder : IHelpBuilder
+ private sealed class CaptureConsole : IConsole
{
- private readonly CommandService _commandService;
- private readonly LocalConsole _console;
- private readonly bool _useHelpBuilder;
+ private readonly StringBuilder _builder = new();
- public LocalHelpBuilder(CommandService commandService, IConsole console, bool useHelpBuilder)
+ public CaptureConsole()
{
- _commandService = commandService;
- _console = (LocalConsole)console;
- _useHelpBuilder = useHelpBuilder;
+ Out = Error = new StandardStreamWriter((text) => _builder.Append(text));
}
- void IHelpBuilder.Write(ICommand command)
- {
- bool useHelpBuilder = _useHelpBuilder;
- if (_commandService._commandHandlers.TryGetValue(command.Name, out CommandHandler handler))
- {
- if (handler.InvokeHelp(_commandService.Parser, _console.Services))
- {
- return;
- }
- useHelpBuilder = true;
- }
- if (useHelpBuilder)
- {
- HelpBuilder helpBuilder = new(_console, maxWidth: _console.ConsoleService.WindowWidth);
- helpBuilder.Write(command);
- }
- }
+ public override string ToString() => _builder.ToString();
+
+ #region IConsole
+
+ public IStandardStreamWriter Out { get; }
+
+ bool IStandardOut.IsOutputRedirected { get { return false; } }
+
+ public IStandardStreamWriter Error { get; }
+
+ bool IStandardError.IsErrorRedirected { get { return false; } }
+
+ bool IStandardIn.IsInputRedirected { get { return false; } }
+
+ #endregion
}
///
- /// This class does two things: wraps the IConsoleService and provides the IConsole interface and
- /// pipes through the System.CommandLine parsing allowing per command invocation data (service
- /// provider and raw command line) to be passed through.
+ /// This class wraps the IConsoleService and provides the IConsole interface for System.CommandLine.
///
private sealed class LocalConsole : IConsole
{
- private IConsoleService _console;
+ private readonly IConsoleService _consoleService;
- public LocalConsole(IServiceProvider services)
+ public LocalConsole(IConsoleService consoleService)
{
- Services = services;
- Out = new StandardStreamWriter(ConsoleService.Write);
- Error = new StandardStreamWriter(ConsoleService.WriteError);
- }
-
- internal readonly IServiceProvider Services;
-
- internal IConsoleService ConsoleService
- {
- get
- {
- _console ??= Services.GetService();
- return _console;
- }
+ _consoleService = consoleService;
+ Out = new StandardStreamWriter(_consoleService.Write);
+ Error = new StandardStreamWriter(_consoleService.WriteError);
}
#region IConsole
@@ -537,16 +767,16 @@ internal IConsoleService ConsoleService
bool IStandardIn.IsInputRedirected { get { return false; } }
- private sealed class StandardStreamWriter : IStandardStreamWriter
- {
- private readonly Action _write;
+ #endregion
+ }
- public StandardStreamWriter(Action write) => _write = write;
+ private sealed class StandardStreamWriter : IStandardStreamWriter
+ {
+ private readonly Action _write;
- void IStandardStreamWriter.Write(string value) => _write(value);
- }
+ public StandardStreamWriter(Action write) => _write = write;
- #endregion
+ void IStandardStreamWriter.Write(string value) => _write(value);
}
}
}
diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs
new file mode 100644
index 0000000000..a10c4001a3
--- /dev/null
+++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs
@@ -0,0 +1,192 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Diagnostics.DebugServices.Implementation
+{
+ public class CrashInfoService : ICrashInfoService
+ {
+ ///
+ /// This is a "transport" exception code required by Watson to trigger the proper analyzer/provider for bucketing
+ ///
+ public const uint STATUS_STACK_BUFFER_OVERRUN = 0xC0000409;
+
+ ///
+ /// This is the Native AOT fail fast subcode used by Watson
+ ///
+ public const uint FAST_FAIL_EXCEPTION_DOTNET_AOT = 0x48;
+
+ public sealed class CrashInfoJson
+ {
+ [JsonPropertyName("version")]
+ public string Version { get; set; }
+
+ [JsonPropertyName("reason")]
+ public int Reason { get; set; }
+
+ [JsonPropertyName("runtime")]
+ public string Runtime { get; set; }
+
+ [JsonPropertyName("runtime_type")]
+ public int RuntimeType { get; set; }
+
+ [JsonPropertyName("thread")]
+ [JsonConverter(typeof(HexUInt32Converter))]
+ public uint Thread { get; set; }
+
+ [JsonPropertyName("message")]
+ public string Message { get; set; }
+
+ [JsonPropertyName("exception")]
+ public CrashInfoException Exception { get; set; }
+ }
+
+ public sealed class CrashInfoException : IManagedException
+ {
+ [JsonPropertyName("address")]
+ [JsonConverter(typeof(HexUInt64Converter))]
+ public ulong Address { get; set; }
+
+ [JsonPropertyName("hr")]
+ [JsonConverter(typeof(HexUInt32Converter))]
+ public uint HResult { get; set; }
+
+ [JsonPropertyName("message")]
+ public string Message { get; set; }
+
+ [JsonPropertyName("type")]
+ public string Type { get; set; }
+
+ [JsonPropertyName("stack")]
+ public CrashInfoStackFrame[] Stack { get; set; }
+
+ IEnumerable IManagedException.Stack => Stack;
+
+ [JsonPropertyName("inner")]
+ public CrashInfoException[] InnerExceptions { get; set; }
+
+ IEnumerable IManagedException.InnerExceptions => InnerExceptions;
+ }
+
+ public sealed class CrashInfoStackFrame : IStackFrame
+ {
+ [JsonPropertyName("ip")]
+ [JsonConverter(typeof(HexUInt64Converter))]
+ public ulong InstructionPointer { get; set; }
+
+ [JsonPropertyName("sp")]
+ [JsonConverter(typeof(HexUInt64Converter))]
+ public ulong StackPointer { get; set; }
+
+ [JsonPropertyName("module")]
+ [JsonConverter(typeof(HexUInt64Converter))]
+ public ulong ModuleBase { get; set; }
+
+ [JsonPropertyName("offset")]
+ [JsonConverter(typeof(HexUInt32Converter))]
+ public uint Offset { get; set; }
+
+ [JsonPropertyName("name")]
+ public string MethodName { get; set; }
+ }
+
+ public sealed class HexUInt64Converter : JsonConverter
+ {
+ public override ulong Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ string valueString = reader.GetString();
+ if (valueString == null ||
+ !valueString.StartsWith("0x") ||
+ !ulong.TryParse(valueString.Substring(2), System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out ulong value))
+ {
+ throw new JsonException("Invalid hex value");
+ }
+ return value;
+ }
+
+ public override void Write(Utf8JsonWriter writer, ulong value, JsonSerializerOptions options) => throw new NotImplementedException();
+ }
+
+ public sealed class HexUInt32Converter : JsonConverter
+ {
+ public override uint Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ string valueString = reader.GetString();
+ if (valueString == null ||
+ !valueString.StartsWith("0x") ||
+ !uint.TryParse(valueString.Substring(2), System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out uint value))
+ {
+ throw new JsonException("Invalid hex value");
+ }
+ return value;
+ }
+
+ public override void Write(Utf8JsonWriter writer, uint value, JsonSerializerOptions options) => throw new NotImplementedException();
+ }
+
+ public static ICrashInfoService Create(uint hresult, ReadOnlySpan triageBuffer)
+ {
+ CrashInfoService crashInfoService = null;
+ try
+ {
+ JsonSerializerOptions options = new() { AllowTrailingCommas = true, NumberHandling = JsonNumberHandling.AllowReadingFromString };
+ CrashInfoJson crashInfo = JsonSerializer.Deserialize(triageBuffer, options);
+ if (crashInfo != null)
+ {
+ if (Version.TryParse(crashInfo.Version, out Version protocolVersion) && protocolVersion.Major >= 1)
+ {
+ crashInfoService = new(crashInfo.Thread, hresult, crashInfo);
+ }
+ else
+ {
+ Trace.TraceError($"CrashInfoService: invalid or not supported protocol version {crashInfo.Version}");
+ }
+ }
+ else
+ {
+ Trace.TraceError($"CrashInfoService: JsonSerializer.Deserialize failed");
+ }
+ }
+ catch (Exception ex) when (ex is JsonException or NotSupportedException or DecoderFallbackException or ArgumentException)
+ {
+ Trace.TraceError($"CrashInfoService: {ex}");
+ }
+ return crashInfoService;
+ }
+
+ private CrashInfoService(uint threadId, uint hresult, CrashInfoJson crashInfo)
+ {
+ ThreadId = threadId;
+ HResult = hresult;
+ CrashReason = (CrashReason)crashInfo.Reason;
+ RuntimeVersion = crashInfo.Runtime;
+ RuntimeType = (RuntimeType)crashInfo.RuntimeType;
+ Message = crashInfo.Message;
+ Exception = crashInfo.Exception;
+ }
+
+ #region ICrashInfoService
+
+ public uint ThreadId { get; }
+
+ public uint HResult { get; }
+
+ public CrashReason CrashReason { get; }
+
+ public string RuntimeVersion { get; }
+
+ public RuntimeType RuntimeType { get; }
+
+ public string Message { get; }
+
+ public IManagedException Exception { get; }
+
+ #endregion
+ }
+}
diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/DataReader.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/DataReader.cs
index ba1ecc6d21..826eaecdf1 100644
--- a/src/Microsoft.Diagnostics.DebugServices.Implementation/DataReader.cs
+++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/DataReader.cs
@@ -13,7 +13,7 @@
namespace Microsoft.Diagnostics.DebugServices.Implementation
{
///
- /// ClrMD runtime service implementation
+ /// ClrMD runtime service implementation. This MUST never be disposable.
///
[ServiceExport(Type = typeof(IDataReader), Scope = ServiceScope.Target)]
public class DataReader : IDataReader
@@ -48,7 +48,7 @@ public DataReader(ITarget target)
int IDataReader.ProcessId => unchecked((int)_target.ProcessId.GetValueOrDefault());
- IEnumerable IDataReader.EnumerateModules() => _modules ??= ModuleService.EnumerateModules().Select((module) => new DataReaderModule(module)).ToList();
+ IEnumerable IDataReader.EnumerateModules() => _modules ??= ModuleService.EnumerateModules().Select((module) => new DataReaderModule(this, module)).ToList();
bool IDataReader.GetThreadContext(uint threadId, uint contextFlags, Span context)
{
@@ -114,11 +114,14 @@ ulong IMemoryReader.ReadPointer(ulong address)
private sealed class DataReaderModule : ModuleInfo
{
+ private readonly IDataReader _reader;
private readonly IModule _module;
+ private IResourceNode _resourceRoot;
- public DataReaderModule(IModule module)
+ public DataReaderModule(IDataReader reader, IModule module)
: base(module.ImageBase, module.FileName)
{
+ _reader = reader;
_module = module;
}
@@ -202,7 +205,7 @@ public override ulong GetExportSymbolAddress(string symbol)
return 0;
}
- public override IResourceNode ResourceRoot => base.ResourceRoot;
+ public override IResourceNode ResourceRoot => _resourceRoot ??= ModuleInfo.TryCreateResourceRoot(_reader, _module.ImageBase, _module.ImageSize, _module.IsFileLayout.GetValueOrDefault(false));
}
}
}
diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj b/src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj
index 06c99c30c2..63facc266b 100644
--- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj
+++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj
@@ -1,7 +1,8 @@
-
+
netstandard2.0
+ true
;1591;1701
Diagnostics debug services
true
@@ -20,6 +21,7 @@
+
diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs
index 04325022be..351bd02df7 100644
--- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs
+++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs
@@ -30,7 +30,7 @@ public class Runtime : IRuntime, IDisposable
public Runtime(IServiceProvider services, int id, ClrInfo clrInfo)
{
- Target = services.GetService() ?? throw new ArgumentNullException(nameof(Target), "Uninitialized service");
+ Target = services.GetService() ?? throw new NullReferenceException($"Uninitialized service: {nameof(Target)}");
Id = id;
_clrInfo = clrInfo ?? throw new ArgumentNullException(nameof(clrInfo));
_symbolService = services.GetService();
@@ -252,7 +252,7 @@ private string DownloadFile(DebugLibraryInfo libraryInfo)
if (key is not null)
{
// Now download the DAC module from the symbol server
- filePath = _symbolService.DownloadFile(key);
+ filePath = _symbolService.DownloadFile(key.Index, key.FullPathName);
}
}
else
diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceManager.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceManager.cs
index 090270e288..19904d2f10 100644
--- a/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceManager.cs
+++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceManager.cs
@@ -363,6 +363,7 @@ private sealed class ExtensionLoadContext : AssemblyLoadContext
public ExtensionLoadContext(string extensionPath)
{
+ Trace.TraceInformation($"ExtensionLoadContext: {extensionPath}");
_extensionPath = extensionPath;
}
@@ -387,12 +388,15 @@ protected override Assembly Load(AssemblyName assemblyName)
{
throw new InvalidOperationException($"Extension assembly reference version not supported for {assemblyName.Name} {assemblyName.Version}");
}
+ Trace.TraceInformation($"ExtensionLoadContext: loading SOS assembly {assembly.CodeBase}");
return assembly;
}
else if (_extensionPaths.TryGetValue(assemblyName.Name, out string path))
{
+ Trace.TraceInformation($"ExtensionLoadContext: loading from extension path {path}");
return LoadFromAssemblyPath(path);
}
+ Trace.TraceInformation($"ExtensionLoadContext: returning null {assemblyName}");
return null;
}
}
diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/SpecialDiagInfo.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/SpecialDiagInfo.cs
new file mode 100644
index 0000000000..32d7989a93
--- /dev/null
+++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/SpecialDiagInfo.cs
@@ -0,0 +1,137 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+// ******************************************************************************
+// WARNING!!!: This code is also used by createdump in the runtime repo.
+// See: https://github.com/dotnet/runtime/blob/main/src/coreclr/debug/createdump/specialdiaginfo.h
+// ******************************************************************************
+
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Microsoft.Diagnostics.DebugServices.Implementation
+{
+ ///
+ /// This is a special memory region added to ELF and MachO dumps that contains extra diagnostics
+ /// information like the exception record for a crash for a NativeAOT app. The exception record
+ /// contains the pointer to the JSON formatted crash info.
+ ///
+ public unsafe class SpecialDiagInfo
+ {
+ private static readonly byte[] SPECIAL_DIAGINFO_SIGNATURE = Encoding.ASCII.GetBytes("DIAGINFOHEADER");
+ private const int SPECIAL_DIAGINFO_VERSION = 1;
+
+ private const ulong SpecialDiagInfoAddressMacOS64 = 0x7fffffff10000000;
+ private const ulong SpecialDiagInfoAddress64 = 0x00007ffffff10000;
+ private const ulong SpecialDiagInfoAddress32 = 0x7fff1000;
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct SpecialDiagInfoHeader
+ {
+ public const int SignatureSize = 16;
+ public fixed byte Signature[SignatureSize];
+ public int Version;
+ public ulong ExceptionRecordAddress;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct EXCEPTION_RECORD64
+ {
+ public uint ExceptionCode;
+ public uint ExceptionFlags;
+ public ulong ExceptionRecord;
+ public ulong ExceptionAddress;
+ public uint NumberParameters;
+ public uint __unusedAlignment;
+ public fixed ulong ExceptionInformation[15]; //EXCEPTION_MAXIMUM_PARAMETERS
+ }
+
+ private readonly ITarget _target;
+ private readonly IMemoryService _memoryService;
+
+ public SpecialDiagInfo(ITarget target, IMemoryService memoryService)
+ {
+ _target = target;
+ _memoryService = memoryService;
+ }
+
+ private ulong SpecialDiagInfoAddress
+ {
+ get
+ {
+ if (_target.OperatingSystem == OSPlatform.OSX)
+ {
+ if (_memoryService.PointerSize == 8)
+ {
+ return SpecialDiagInfoAddressMacOS64;
+ }
+ }
+ else if (_target.OperatingSystem == OSPlatform.Linux)
+ {
+ if (_memoryService.PointerSize == 8)
+ {
+ return SpecialDiagInfoAddress64;
+ }
+ else
+ {
+ return SpecialDiagInfoAddress32;
+ }
+ }
+ return 0;
+ }
+ }
+
+ public static ICrashInfoService CreateCrashInfoService(IServiceProvider services)
+ {
+ EXCEPTION_RECORD64 exceptionRecord;
+
+ SpecialDiagInfo diagInfo = new(services.GetService(), services.GetService());
+ exceptionRecord = diagInfo.GetExceptionRecord();
+
+ if (exceptionRecord.ExceptionCode == CrashInfoService.STATUS_STACK_BUFFER_OVERRUN &&
+ exceptionRecord.NumberParameters >= 4 &&
+ exceptionRecord.ExceptionInformation[0] == CrashInfoService.FAST_FAIL_EXCEPTION_DOTNET_AOT)
+ {
+ uint hresult = (uint)exceptionRecord.ExceptionInformation[1];
+ ulong triageBufferAddress = exceptionRecord.ExceptionInformation[2];
+ int triageBufferSize = (int)exceptionRecord.ExceptionInformation[3];
+
+ Span buffer = new byte[triageBufferSize];
+ if (services.GetService().ReadMemory(triageBufferAddress, buffer, out int bytesRead) && bytesRead == triageBufferSize)
+ {
+ return CrashInfoService.Create(hresult, buffer);
+ }
+ else
+ {
+ Trace.TraceError($"SpecialDiagInfo: ReadMemory({triageBufferAddress}) failed");
+ }
+ }
+ return null;
+ }
+
+ internal EXCEPTION_RECORD64 GetExceptionRecord()
+ {
+ Span headerBuffer = stackalloc byte[Unsafe.SizeOf()];
+ if (_memoryService.ReadMemory(SpecialDiagInfoAddress, headerBuffer, out int bytesRead) && bytesRead == headerBuffer.Length)
+ {
+ SpecialDiagInfoHeader header = Unsafe.As(ref MemoryMarshal.GetReference(headerBuffer));
+ ReadOnlySpan signature = new(header.Signature, SPECIAL_DIAGINFO_SIGNATURE.Length);
+ if (signature.SequenceEqual(SPECIAL_DIAGINFO_SIGNATURE))
+ {
+ if (header.Version >= SPECIAL_DIAGINFO_VERSION && header.ExceptionRecordAddress != 0)
+ {
+ Span exceptionRecordBuffer = stackalloc byte[Unsafe.SizeOf()];
+ if (_memoryService.ReadMemory(header.ExceptionRecordAddress, exceptionRecordBuffer, out bytesRead) && bytesRead == exceptionRecordBuffer.Length)
+ {
+ return Unsafe.As(ref MemoryMarshal.GetReference(exceptionRecordBuffer));
+ }
+ }
+ }
+ }
+ return default;
+ }
+ }
+}
diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolService.cs
index f764a4b336..4b5ad2b9ba 100644
--- a/src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolService.cs
+++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolService.cs
@@ -400,44 +400,9 @@ public string DownloadSymbolFile(IModule module)
///
/// Download a file from the symbol stores/server.
///
- /// index of the file to download
- /// path to the downloaded file either in the cache or in the temp directory or null if error
- public string DownloadFile(SymbolStoreKey key)
- {
- string downloadFilePath = null;
-
- if (IsSymbolStoreEnabled)
- {
- using SymbolStoreFile file = GetSymbolStoreFile(key);
- if (file != null)
- {
- try
- {
- downloadFilePath = file.FileName;
-
- // Make sure the stream is at the beginning of the module
- file.Stream.Position = 0;
-
- // If the downloaded doesn't already exists on disk in the cache, then write it to a temporary location.
- if (!File.Exists(downloadFilePath))
- {
- downloadFilePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + "-" + Path.GetFileName(key.FullPathName));
- using (Stream destinationStream = File.OpenWrite(downloadFilePath))
- {
- file.Stream.CopyTo(destinationStream);
- }
- Trace.WriteLine($"Downloaded symbol file {key.FullPathName}");
- }
- }
- catch (Exception ex) when (ex is UnauthorizedAccessException or DirectoryNotFoundException)
- {
- Trace.TraceError("{0}: {1}", file.FileName, ex.Message);
- downloadFilePath = null;
- }
- }
- }
- return downloadFilePath;
- }
+ /// index to lookup on symbol server
+ /// the full path name of the file
+ public string DownloadFile(string index, string file) => DownloadFile(new SymbolStoreKey(index, file));
///
/// Returns the metadata for the assembly
@@ -842,6 +807,48 @@ private string DownloadMachO(IModule module, KeyTypeFlags flags)
return null;
}
+ ///
+ /// Download a file from the symbol stores/server.
+ ///
+ /// index of the file to download
+ /// path to the downloaded file either in the cache or in the temp directory or null if error
+ private string DownloadFile(SymbolStoreKey key)
+ {
+ string downloadFilePath = null;
+
+ if (IsSymbolStoreEnabled)
+ {
+ using SymbolStoreFile file = GetSymbolStoreFile(key);
+ if (file != null)
+ {
+ try
+ {
+ downloadFilePath = file.FileName;
+
+ // Make sure the stream is at the beginning of the module
+ file.Stream.Position = 0;
+
+ // If the downloaded doesn't already exists on disk in the cache, then write it to a temporary location.
+ if (!File.Exists(downloadFilePath))
+ {
+ downloadFilePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + "-" + Path.GetFileName(key.FullPathName));
+ using (Stream destinationStream = File.OpenWrite(downloadFilePath))
+ {
+ file.Stream.CopyTo(destinationStream);
+ }
+ Trace.WriteLine($"Downloaded symbol file {key.FullPathName}");
+ }
+ }
+ catch (Exception ex) when (ex is UnauthorizedAccessException or DirectoryNotFoundException)
+ {
+ Trace.TraceError("{0}: {1}", file.FileName, ex.Message);
+ downloadFilePath = null;
+ }
+ }
+ }
+ return downloadFilePath;
+ }
+
private static void ReadPortableDebugTableEntries(PEReader peReader, out DebugDirectoryEntry codeViewEntry, out DebugDirectoryEntry embeddedPdbEntry)
{
// See spec: https://github.com/dotnet/runtime/blob/main/docs/design/specs/PE-COFF.md
diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Target.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/Target.cs
index 9150458413..9addd99b10 100644
--- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Target.cs
+++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Target.cs
@@ -43,6 +43,8 @@ protected void Finished()
Host.OnTargetCreate.Fire(this);
}
+ protected void FlushService() => _serviceContainer?.RemoveService(typeof(T));
+
#region ITarget
///
diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs
index 604205ae69..5266ea006c 100644
--- a/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs
+++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs
@@ -66,6 +66,10 @@ public TargetFromDataReader(IDataReader dataReader, OSPlatform targetOS, IHost h
return memoryService;
});
+ // Add optional crash info service (currently only for Native AOT on Linux/MacOS).
+ _serviceContainerFactory.AddServiceFactory((services) => SpecialDiagInfo.CreateCrashInfoService(services));
+ OnFlushEvent.Register(() => FlushService());
+
Finished();
}
}
diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Utilities.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/Utilities.cs
index cc69c944aa..fd231f2270 100644
--- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Utilities.cs
+++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Utilities.cs
@@ -9,6 +9,8 @@
using System.Reflection;
using System.Reflection.PortableExecutable;
using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
using Microsoft.FileFormats;
using Microsoft.FileFormats.ELF;
using Microsoft.FileFormats.MachO;
@@ -34,7 +36,7 @@ public static class Utilities
///
/// This function is neither commutative nor associative; the hash codes must be combined in
/// a deterministic order. Do not use this when hashing collections whose contents are
- /// nondeterministically ordered!
+ /// non-deterministically ordered!
///
public static int CombineHashCodes(int hashCode0, int hashCode1)
{
@@ -412,4 +414,46 @@ private static object[] BuildArguments(MethodBase methodBase, IServiceProvider s
return arguments;
}
}
+
+ public class CaptureConsoleService : IConsoleService
+ {
+ private readonly StringBuilder _builder = new();
+
+ public CaptureConsoleService()
+ {
+ }
+
+ public void Clear() => _builder.Clear();
+
+ public override string ToString() => _builder.ToString();
+
+ #region IConsoleService
+
+ public void Write(string text)
+ {
+ _builder.Append(text);
+ }
+
+ public void WriteWarning(string text)
+ {
+ _builder.Append(text);
+ }
+
+ public void WriteError(string text)
+ {
+ _builder.Append(text);
+ }
+
+ public bool SupportsDml => false;
+
+ public void WriteDml(string text) => throw new NotSupportedException();
+
+ public void WriteDmlExec(string text, string _) => throw new NotSupportedException();
+
+ public CancellationToken CancellationToken { get; set; } = CancellationToken.None;
+
+ int IConsoleService.WindowWidth => int.MaxValue;
+
+ #endregion
+ }
}
diff --git a/src/Microsoft.Diagnostics.DebugServices/CommandAttributes.cs b/src/Microsoft.Diagnostics.DebugServices/CommandAttributes.cs
index 4b1df4b601..bd988f0c20 100644
--- a/src/Microsoft.Diagnostics.DebugServices/CommandAttributes.cs
+++ b/src/Microsoft.Diagnostics.DebugServices/CommandAttributes.cs
@@ -6,32 +6,6 @@
namespace Microsoft.Diagnostics.DebugServices
{
- ///
- /// Command flags to filter by OS Platforms, control scope and how the command is registered.
- ///
- [Flags]
- public enum CommandFlags : byte
- {
- Windows = 0x01,
- Linux = 0x02,
- OSX = 0x04,
-
- ///
- /// Command is supported when there is no target
- ///
- Global = 0x08,
-
- ///
- /// Command is not added through reflection, but manually with command service API.
- ///
- Manual = 0x10,
-
- ///
- /// Default. All operating system, but target is required
- ///
- Default = Windows | Linux | OSX
- }
-
///
/// Marks the class as a Command.
///
@@ -53,11 +27,6 @@ public class CommandAttribute : Attribute
///
public string[] Aliases = Array.Empty();
- ///
- /// Command flags to filter by OS Platforms, control scope and how the command is registered.
- ///
- public CommandFlags Flags = CommandFlags.Default;
-
///
/// A string of options that are parsed before the command line options
///
@@ -121,10 +90,24 @@ public class CommandInvokeAttribute : Attribute
}
///
- /// Marks the function to invoke to display alternate help for command.
+ /// Marks the function to invoke to return the alternate help for command. The function returns
+ /// a string. The Argument and Option properties of the command are not set.
///
[AttributeUsage(AttributeTargets.Method)]
public class HelpInvokeAttribute : Attribute
{
}
+
+ ///
+ /// Marks the function to invoke to filter a command. The function returns a bool; true if
+ /// the command is supported. The Argument and Option properties of the command are not set.
+ ///
+ [AttributeUsage(AttributeTargets.Method)]
+ public class FilterInvokeAttribute : Attribute
+ {
+ ///
+ /// Message to display if the filter fails
+ ///
+ public string Message;
+ }
}
diff --git a/src/Microsoft.Diagnostics.DebugServices/DiagnosticsException.cs b/src/Microsoft.Diagnostics.DebugServices/DiagnosticsException.cs
index c6ea751959..fa5d9c85cd 100644
--- a/src/Microsoft.Diagnostics.DebugServices/DiagnosticsException.cs
+++ b/src/Microsoft.Diagnostics.DebugServices/DiagnosticsException.cs
@@ -27,23 +27,43 @@ public DiagnosticsException(string message, Exception innerException)
}
///
- /// Thrown if a command is not supported on the configuration, platform or runtime
+ /// Thrown if a command is not found.
///
- public class CommandNotSupportedException : DiagnosticsException
+ public class CommandNotFoundException : DiagnosticsException
{
- public CommandNotSupportedException()
- : base()
+ public const string NotFoundMessage = $"Unrecognized SOS command";
+
+ public CommandNotFoundException(string message)
+ : base(message)
+ {
+ }
+
+ public CommandNotFoundException(string message, Exception innerException)
+ : base(message, innerException)
{
}
+ }
+
+ ///
+ /// Thrown if a command is not found.
+ ///
+ public class CommandParsingException : DiagnosticsException
+ {
+ ///
+ /// The detailed help of the command
+ ///
+ public string DetailedHelp { get; }
- public CommandNotSupportedException(string message)
+ public CommandParsingException(string message, string detailedHelp)
: base(message)
{
+ DetailedHelp = detailedHelp;
}
- public CommandNotSupportedException(string message, Exception innerException)
+ public CommandParsingException(string message, string detailedHelp, Exception innerException)
: base(message, innerException)
{
+ DetailedHelp = detailedHelp;
}
}
}
diff --git a/src/Microsoft.Diagnostics.DebugServices/ICommandService.cs b/src/Microsoft.Diagnostics.DebugServices/ICommandService.cs
index 2a4dbe8109..6455767449 100644
--- a/src/Microsoft.Diagnostics.DebugServices/ICommandService.cs
+++ b/src/Microsoft.Diagnostics.DebugServices/ICommandService.cs
@@ -12,7 +12,7 @@ namespace Microsoft.Diagnostics.DebugServices
public interface ICommandService
{
///
- /// Enumerates all the command's name and help
+ /// Enumerates all the command's name, help and aliases
///
IEnumerable<(string name, string help, IEnumerable aliases)> Commands { get; }
@@ -23,11 +23,19 @@ public interface ICommandService
void AddCommands(Type type);
///
- /// Displays the help for a command
+ /// Gets help for all of the commands
+ ///
+ /// service provider
+ /// command invocation and help enumeration
+ public IEnumerable<(string Invocation, string Help)> GetAllCommandHelp(IServiceProvider services);
+
+ ///
+ /// Displays the detailed help for a command
///
/// name of the command or alias
/// service provider
- /// true if success, false if command not found
- bool DisplayHelp(string commandName, IServiceProvider services);
+ /// the width to format the help or int.MaxValue
+ /// help text or null if not found
+ string GetDetailedHelp(string commandName, IServiceProvider services, int consoleWidth);
}
}
diff --git a/src/Microsoft.Diagnostics.DebugServices/ICrashInfoService.cs b/src/Microsoft.Diagnostics.DebugServices/ICrashInfoService.cs
new file mode 100644
index 0000000000..797fb75fd1
--- /dev/null
+++ b/src/Microsoft.Diagnostics.DebugServices/ICrashInfoService.cs
@@ -0,0 +1,59 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+ ///
+ /// The kind or reason of crash for the triage JSON
+ ///
+ public enum CrashReason
+ {
+ Unknown = 0,
+ UnhandledException = 1,
+ EnvironmentFailFast = 2,
+ InternalFailFast = 3,
+ }
+
+ ///
+ /// Crash information service. Details about the unhandled exception or crash.
+ ///
+ public interface ICrashInfoService
+ {
+ ///
+ /// The kind or reason for the crash
+ ///
+ CrashReason CrashReason { get; }
+
+ ///
+ /// Crashing OS thread id
+ ///
+ uint ThreadId { get; }
+
+ ///
+ /// The HRESULT passed to Watson
+ ///
+ uint HResult { get; }
+
+ ///
+ /// Runtime type or flavor
+ ///
+ RuntimeType RuntimeType { get; }
+
+ ///
+ /// Runtime version and possible commit id
+ ///
+ string RuntimeVersion { get; }
+
+ ///
+ /// Crash or FailFast message
+ ///
+ string Message { get; }
+
+ ///
+ /// The exception that caused the crash or null
+ ///
+ IManagedException Exception { get; }
+ }
+}
diff --git a/src/Microsoft.Diagnostics.DebugServices/IManagedException.cs b/src/Microsoft.Diagnostics.DebugServices/IManagedException.cs
new file mode 100644
index 0000000000..f73b172e5c
--- /dev/null
+++ b/src/Microsoft.Diagnostics.DebugServices/IManagedException.cs
@@ -0,0 +1,43 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+ ///
+ /// Describes a managed exception
+ ///
+ public interface IManagedException
+ {
+ ///
+ /// Exception object address
+ ///
+ ulong Address { get; }
+
+ ///
+ /// The exception type name
+ ///
+ string Type { get; }
+
+ ///
+ /// The exception message
+ ///
+ string Message { get; }
+
+ ///
+ /// Exception.HResult
+ ///
+ uint HResult { get; }
+
+ ///
+ /// Stack trace of exception
+ ///
+ IEnumerable Stack { get; }
+
+ ///
+ /// The inner exception or exceptions in the AggregateException case
+ ///
+ IEnumerable InnerExceptions { get; }
+ }
+}
diff --git a/src/Microsoft.Diagnostics.DebugServices/IRuntime.cs b/src/Microsoft.Diagnostics.DebugServices/IRuntime.cs
index e369639384..5dd9e4ce93 100644
--- a/src/Microsoft.Diagnostics.DebugServices/IRuntime.cs
+++ b/src/Microsoft.Diagnostics.DebugServices/IRuntime.cs
@@ -14,7 +14,8 @@ public enum RuntimeType
Desktop = 1,
NetCore = 2,
SingleFile = 3,
- Other = 4
+ NativeAOT = 4,
+ Other = 5
}
///
diff --git a/src/Microsoft.Diagnostics.DebugServices/IStackFrame.cs b/src/Microsoft.Diagnostics.DebugServices/IStackFrame.cs
new file mode 100644
index 0000000000..42f33eb7f6
--- /dev/null
+++ b/src/Microsoft.Diagnostics.DebugServices/IStackFrame.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+ ///
+ /// Describes a stack frame
+ ///
+ public interface IStackFrame
+ {
+ ///
+ /// The instruction pointer for this frame
+ ///
+ ulong InstructionPointer { get; }
+
+ ///
+ /// The stack pointer of this frame or 0
+ ///
+ ulong StackPointer { get; }
+
+ ///
+ /// The module base of the IP
+ ///
+ public ulong ModuleBase { get; }
+
+ ///
+ /// Offset from beginning of method
+ ///
+ uint Offset { get; }
+
+ ///
+ /// The exception type name
+ ///
+ string MethodName { get; }
+ }
+}
diff --git a/src/Microsoft.Diagnostics.DebugServices/ISymbolService.cs b/src/Microsoft.Diagnostics.DebugServices/ISymbolService.cs
index af5dc291c9..13e66f4a63 100644
--- a/src/Microsoft.Diagnostics.DebugServices/ISymbolService.cs
+++ b/src/Microsoft.Diagnostics.DebugServices/ISymbolService.cs
@@ -3,7 +3,6 @@
using System.Collections.Immutable;
using System.IO;
-using Microsoft.SymbolStore;
namespace Microsoft.Diagnostics.DebugServices
{
@@ -96,9 +95,9 @@ public interface ISymbolService
///
/// Download a file from the symbol stores/server.
///
- /// index of the file to download
- /// path to the downloaded file either in the cache or in the temp directory or null if error
- string DownloadFile(SymbolStoreKey key);
+ /// index to lookup on symbol server
+ /// the full path name of the file
+ string DownloadFile(string index, string file);
///
/// Returns the metadata for the assembly
diff --git a/src/Microsoft.Diagnostics.DebugServices/IType.cs b/src/Microsoft.Diagnostics.DebugServices/IType.cs
index 28a1355709..6682f059f7 100644
--- a/src/Microsoft.Diagnostics.DebugServices/IType.cs
+++ b/src/Microsoft.Diagnostics.DebugServices/IType.cs
@@ -20,11 +20,6 @@ public interface IType
///
IModule Module { get; }
- ///
- /// A list of all the fields in the type
- ///
- List Fields { get; }
-
///
/// Get a field by name
///
diff --git a/src/Microsoft.Diagnostics.DebugServices/Microsoft.Diagnostics.DebugServices.csproj b/src/Microsoft.Diagnostics.DebugServices/Microsoft.Diagnostics.DebugServices.csproj
index 33f0fe3e90..e550154f29 100644
--- a/src/Microsoft.Diagnostics.DebugServices/Microsoft.Diagnostics.DebugServices.csproj
+++ b/src/Microsoft.Diagnostics.DebugServices/Microsoft.Diagnostics.DebugServices.csproj
@@ -13,8 +13,8 @@
true
false
-
+
-
+
diff --git a/src/Microsoft.Diagnostics.DebugServices/ProviderExportAttribute.cs b/src/Microsoft.Diagnostics.DebugServices/ProviderExportAttribute.cs
index 8a70aff359..be60004fa1 100644
--- a/src/Microsoft.Diagnostics.DebugServices/ProviderExportAttribute.cs
+++ b/src/Microsoft.Diagnostics.DebugServices/ProviderExportAttribute.cs
@@ -13,7 +13,7 @@ public class ProviderExportAttribute : Attribute
{
///
/// The interface or type to register the provider. If null, the provider type registered will be
- /// he class itself or the return type of the method.
+ /// the class itself or the return type of the method.
///
public Type Type { get; set; }
diff --git a/src/Microsoft.Diagnostics.DebugServices/ServiceContainer.cs b/src/Microsoft.Diagnostics.DebugServices/ServiceContainer.cs
index 9b497c53fe..df9aa18d95 100644
--- a/src/Microsoft.Diagnostics.DebugServices/ServiceContainer.cs
+++ b/src/Microsoft.Diagnostics.DebugServices/ServiceContainer.cs
@@ -29,9 +29,8 @@ public class ServiceContainer : IServiceProvider
///
/// search this provider if service isn't found in this instance or null
/// service factories to initialize provider or null
- public ServiceContainer(IServiceProvider parent, Dictionary factories)
+ public ServiceContainer(IServiceProvider parent, Dictionary factories = null)
{
- Debug.Assert(factories != null);
_parent = parent;
_factories = factories;
_instances = new Dictionary();
@@ -88,7 +87,7 @@ public object GetService(Type type)
{
return service;
}
- if (_factories.TryGetValue(type, out ServiceFactory factory))
+ if (_factories != null && _factories.TryGetValue(type, out ServiceFactory factory))
{
service = factory(this);
_instances.Add(type, service);
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/AnalyzeOOMCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/AnalyzeOOMCommand.cs
index 35e38607a0..ad970e05bb 100644
--- a/src/Microsoft.Diagnostics.ExtensionCommands/AnalyzeOOMCommand.cs
+++ b/src/Microsoft.Diagnostics.ExtensionCommands/AnalyzeOOMCommand.cs
@@ -7,12 +7,9 @@
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "analyzeoom", Help = "Displays the info of the last OOM that occurred on an allocation request to the GC heap.")]
- public class AnalyzeOOMCommand : CommandBase
+ [Command(Name = "analyzeoom", Aliases = new[] { "AnalyzeOOM" }, Help = "Displays the info of the last OOM that occurred on an allocation request to the GC heap.")]
+ public class AnalyzeOOMCommand : ClrRuntimeCommandBase
{
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
public override void Invoke()
{
bool foundOne = false;
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ClrModulesCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/AssembliesCommand.cs
similarity index 79%
rename from src/Microsoft.Diagnostics.ExtensionCommands/ClrModulesCommand.cs
rename to src/Microsoft.Diagnostics.ExtensionCommands/AssembliesCommand.cs
index 6e19a346e4..ebeda4ed1d 100644
--- a/src/Microsoft.Diagnostics.ExtensionCommands/ClrModulesCommand.cs
+++ b/src/Microsoft.Diagnostics.ExtensionCommands/AssembliesCommand.cs
@@ -9,28 +9,21 @@
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "clrmodules", Help = "Lists the managed modules in the process.")]
- public class ClrModulesCommand : CommandBase
+ [Command(Name = "assemblies", Aliases = new[] { "clrmodules" }, Help = "Lists the managed assemblies in the process.")]
+ public class AssembliesCommand : ClrRuntimeCommandBase
{
- [ServiceImport(Optional = true)]
- public ClrRuntime Runtime { get; set; }
-
[ServiceImport]
public IModuleService ModuleService { get; set; }
- [Option(Name = "--name", Aliases = new string[] { "-n" }, Help = "RegEx filter on module name (path not included).")]
- public string ModuleName { get; set; }
+ [Option(Name = "--name", Aliases = new string[] { "-n" }, Help = "RegEx filter on assembly name (path not included).")]
+ public string AssemblyName { get; set; }
- [Option(Name = "--verbose", Aliases = new string[] { "-v" }, Help = "Displays detailed information about the modules.")]
+ [Option(Name = "--verbose", Aliases = new string[] { "-v" }, Help = "Displays detailed information about the assemblies.")]
public bool Verbose { get; set; }
public override void Invoke()
{
- if (Runtime == null)
- {
- throw new DiagnosticsException("No CLR runtime set");
- }
- Regex regex = ModuleName is not null ? new Regex(ModuleName, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant) : null;
+ Regex regex = AssemblyName is not null ? new Regex(AssemblyName, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant) : null;
foreach (ClrModule module in Runtime.EnumerateModules())
{
if (regex is null || !string.IsNullOrEmpty(module.Name) && regex.IsMatch(Path.GetFileName(module.Name)))
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ClrMDHelper.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ClrMDHelper.cs
index 76cda04435..1d87e10de3 100644
--- a/src/Microsoft.Diagnostics.ExtensionCommands/ClrMDHelper.cs
+++ b/src/Microsoft.Diagnostics.ExtensionCommands/ClrMDHelper.cs
@@ -17,7 +17,7 @@ public class ClrMDHelper
private readonly ClrHeap _heap;
[ServiceExport(Scope = ServiceScope.Runtime)]
- public static ClrMDHelper Create([ServiceImport(Optional = true)] ClrRuntime clrRuntime)
+ public static ClrMDHelper TryCreate([ServiceImport(Optional = true)] ClrRuntime clrRuntime)
{
return clrRuntime != null ? new ClrMDHelper(clrRuntime) : null;
}
@@ -995,7 +995,7 @@ private IEnumerable EnumerateConcurrentQueueCore(ulong address)
ClrType slotType = _heap.GetObjectType(slotEntry.ToUInt64());
if (slotType.IsString)
{
- yield return $"\"{new ClrObject(slotEntry.ToUInt64(), slotType).AsString()}\"";
+ yield return $"\"{_heap.GetObject(slotEntry.ToUInt64(), slotType).AsString()}\"";
}
else
{
@@ -1106,7 +1106,7 @@ private static bool IsSimpleType(string typeName)
}
}
- private static string DumpPropertyValue(ClrObject obj, string propertyName)
+ private string DumpPropertyValue(ClrObject obj, string propertyName)
{
const string defaultContent = "?";
@@ -1115,7 +1115,7 @@ private static string DumpPropertyValue(ClrObject obj, string propertyName)
{
if (fieldType.IsString)
{
- return $"\"{new ClrObject(field.Address, fieldType).AsString()}\"";
+ return $"\"{_heap.GetObject(field.Address, fieldType).AsString()}\"";
}
else if (fieldType.IsArray)
{
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ClrMDHelperCommandBase.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ClrMDHelperCommandBase.cs
new file mode 100644
index 0000000000..b91d528e68
--- /dev/null
+++ b/src/Microsoft.Diagnostics.ExtensionCommands/ClrMDHelperCommandBase.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Diagnostics.DebugServices;
+
+namespace Microsoft.Diagnostics.ExtensionCommands
+{
+ public abstract class ClrMDHelperCommandBase : CommandBase
+ {
+ ///
+ /// Helper bound to the current ClrRuntime that provides high level services on top of ClrMD.
+ ///
+ [ServiceImport(Optional = true)]
+ public ClrMDHelper Helper { get; set; }
+
+ [FilterInvoke(Message = ClrRuntimeCommandBase.RuntimeNotFoundMessage)]
+ public static bool FilterInvoke([ServiceImport(Optional = true)] ClrMDHelper helper) => helper != null;
+ }
+}
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ClrRuntimeCommandBase.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ClrRuntimeCommandBase.cs
new file mode 100644
index 0000000000..2f139129d6
--- /dev/null
+++ b/src/Microsoft.Diagnostics.ExtensionCommands/ClrRuntimeCommandBase.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Diagnostics.DebugServices;
+using Microsoft.Diagnostics.Runtime;
+
+namespace Microsoft.Diagnostics.ExtensionCommands
+{
+ public abstract class ClrRuntimeCommandBase : CommandBase
+ {
+ public const string RuntimeNotFoundMessage = "No CLR runtime found. This means that a .NET runtime module or the DAC for the runtime can not be found or downloaded.";
+
+ [ServiceImport(Optional = true)]
+ public ClrRuntime Runtime { get; set; }
+
+ [FilterInvoke(Message = RuntimeNotFoundMessage)]
+ public static bool FilterInvoke([ServiceImport(Optional = true)] ClrRuntime runtime) => runtime != null;
+ }
+}
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs
new file mode 100644
index 0000000000..994522dd2a
--- /dev/null
+++ b/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs
@@ -0,0 +1,81 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using Microsoft.Diagnostics.DebugServices;
+
+namespace Microsoft.Diagnostics.ExtensionCommands
+{
+ [Command(Name = "crashinfo", Help = "Displays the crash details that created the dump.")]
+ public class CrashInfoCommand : CommandBase
+ {
+ [ServiceImport(Optional = true)]
+ public ICrashInfoService CrashInfo { get; set; }
+
+ [ServiceImport]
+ public IModuleService ModuleService { get; set; }
+
+ public override void Invoke()
+ {
+ if (CrashInfo == null)
+ {
+ throw new DiagnosticsException("No crash info to display");
+ }
+ WriteLine();
+
+ WriteLine($"CrashReason: {CrashInfo.CrashReason}");
+ WriteLine($"ThreadId: {CrashInfo.ThreadId:X4}");
+ WriteLine($"HResult: {CrashInfo.HResult:X4}");
+ WriteLine($"RuntimeType: {CrashInfo.RuntimeType}");
+ WriteLine($"RuntimeVersion: {CrashInfo.RuntimeVersion}");
+ WriteLine($"Message: {CrashInfo.Message}");
+
+ if (CrashInfo.Exception != null)
+ {
+ WriteLine("-----------------------------------------------");
+ PrintException(CrashInfo.Exception, string.Empty);
+ }
+ }
+
+ private void PrintException(IManagedException exception, string indent)
+ {
+ WriteLine($"{indent}Exception object: {exception.Address:X16}");
+ WriteLine($"{indent}Exception type: {exception.Type}");
+ WriteLine($"{indent}HResult: {exception.HResult:X8}");
+ WriteLine($"{indent}Message: {exception.Message}");
+
+ if (exception.Stack != null && exception.Stack.Any())
+ {
+ WriteLine($"{indent}StackTrace:");
+ WriteLine($"{indent} IP Function");
+ foreach (IStackFrame frame in exception.Stack)
+ {
+ string moduleName = "";
+ if (frame.ModuleBase != 0)
+ {
+ IModule module = ModuleService.GetModuleFromBaseAddress(frame.ModuleBase);
+ if (module != null)
+ {
+ moduleName = Path.GetFileName(module.FileName);
+ }
+ }
+ string methodName = frame.MethodName ?? "";
+ WriteLine($"{indent} {frame.InstructionPointer:X16} {moduleName}!{methodName} + 0x{frame.Offset:X}");
+ }
+ }
+
+ if (exception.InnerExceptions != null)
+ {
+ WriteLine("InnerExceptions:");
+ foreach (IManagedException inner in exception.InnerExceptions)
+ {
+ WriteLine("-----------------------------------------------");
+ PrintException(inner, " ");
+ }
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/DumpAsyncCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/DumpAsyncCommand.cs
index 0acbca362e..954fd6fa61 100644
--- a/src/Microsoft.Diagnostics.ExtensionCommands/DumpAsyncCommand.cs
+++ b/src/Microsoft.Diagnostics.ExtensionCommands/DumpAsyncCommand.cs
@@ -14,45 +14,17 @@
namespace Microsoft.Diagnostics.ExtensionCommands
{
[Command(Name = CommandName, Aliases = new string[] { "DumpAsync" }, Help = "Displays information about async \"stacks\" on the garbage-collected heap.")]
- public sealed class DumpAsyncCommand : ExtensionCommandBase
+ public sealed class DumpAsyncCommand : ClrRuntimeCommandBase
{
/// The name of the command.
private const string CommandName = "dumpasync";
/// Indent width.
private const int TabWidth = 2;
+
/// The command invocation syntax when used in Debugger Markup Language (DML) commands.
private const string DmlCommandInvoke = $"!{CommandName}";
- /// The help text to render when asked for help.
- private static readonly string s_detailedHelpText =
- $"Usage: {CommandName} [--stats] [--coalesce] [--address
[Command(Name = "notreachableinrange", Help = "A helper command for !finalizerqueue")]
- public class NotReachableInRangeCommand : CommandBase
+ public class NotReachableInRangeCommand : ClrRuntimeCommandBase
{
private HashSet _nonFQLiveObjects;
@@ -30,9 +30,6 @@ public class NotReachableInRangeCommand : CommandBase
[ServiceImport]
public IMemoryService Memory { get; set; }
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
[Option(Name = "-short")]
public bool Short { get; set; }
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ObjSizeCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ObjSizeCommand.cs
index 0beb0e98f0..c65f381fa4 100644
--- a/src/Microsoft.Diagnostics.ExtensionCommands/ObjSizeCommand.cs
+++ b/src/Microsoft.Diagnostics.ExtensionCommands/ObjSizeCommand.cs
@@ -8,12 +8,9 @@
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "objsize", Help = "Lists the sizes of the all the objects found on managed threads.")]
- public class ObjSizeCommand : CommandBase
+ [Command(Name = "objsize", Aliases = new[] { "ObjSize" }, Help = "Lists the sizes of the all the objects found on managed threads.")]
+ public class ObjSizeCommand : ClrRuntimeCommandBase
{
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
[ServiceImport]
public DumpHeapService DumpHeap { get; set; }
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacksCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacksCommand.cs
index f61442a902..3320f11d34 100644
--- a/src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacksCommand.cs
+++ b/src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacksCommand.cs
@@ -9,17 +9,17 @@
namespace Microsoft.Diagnostics.ExtensionCommands
{
[Command(Name = "parallelstacks", Aliases = new string[] { "pstacks" }, Help = "Displays the merged threads stack similarly to the Visual Studio 'Parallel Stacks' panel.")]
- public class ParallelStacksCommand : ExtensionCommandBase
+ public class ParallelStacksCommand : ClrMDHelperCommandBase
{
- [ServiceImport]
+ [ServiceImport(Optional = true)]
public ClrRuntime Runtime { get; set; }
[Option(Name = "--allthreads", Aliases = new string[] { "-a" }, Help = "Displays all threads per group instead of at most 4 by default.")]
public bool AllThreads { get; set; }
- public override void ExtensionInvoke()
+ public override void Invoke()
{
- ParallelStack ps = ParallelStacks.Runtime.ParallelStack.Build(Runtime);
+ ParallelStack ps = ParallelStack.Build(Runtime);
if (ps == null)
{
return;
@@ -41,47 +41,41 @@ public override void ExtensionInvoke()
WriteLine($"==> {ps.ThreadIds.Count} threads with {ps.Stacks.Count} roots{Environment.NewLine}");
}
- protected override string GetDetailedHelp()
- {
- return DetailedHelpText;
- }
+ [HelpInvoke]
+ public static string GetDetailedHelp() =>
+@"-------------------------------------------------------------------------------
+ParallelStacks
+
+pstacks groups the callstack of all running threads and shows a merged display a la Visual Studio 'Parallel Stacks' panel
+By default, only 4 threads ID per frame group are listed. Use --allThreads/-a to list all threads ID.
+
+> pstacks
+________________________________________________
+~~~~ 8f8c
+ 1 (dynamicClass).IL_STUB_PInvoke(IntPtr, Byte*, Int32, Int32 ByRef, IntPtr)
+ ...
+ 1 System.Console.ReadLine()
+ 1 NetCoreConsoleApp.Program.Main(String[])
+
+________________________________________________
+ ~~~~ 7034
+ 1 System.Threading.Monitor.Wait(Object, Int32, Boolean)
+ ...
+ 1 System.Threading.Tasks.Task.Wait()
+ 1 NetCoreConsoleApp.Program+c.b__1_4(Object)
+ ~~~~ 9c6c,4020
+ 2 System.Threading.Monitor.Wait(Object, Int32, Boolean)
+ ...
+ 2 NetCoreConsoleApp.Program+c__DisplayClass1_0.b__7()
+ 3 System.Threading.Tasks.Task.InnerInvoke()
+ 4 System.Threading.Tasks.Task+c.cctor>b__278_1(Object)
+ ...
+ 4 System.Threading.Tasks.Task.ExecuteEntryUnsafe()
+ 4 System.Threading.Tasks.Task.ExecuteWorkItem()
+ 7 System.Threading.ThreadPoolWorkQueue.Dispatch()
+ 7 System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
- private readonly string DetailedHelpText =
- "-------------------------------------------------------------------------------" + Environment.NewLine +
- "ParallelStacks" + Environment.NewLine +
- Environment.NewLine +
- "pstacks groups the callstack of all running threads and shows a merged display a la Visual Studio 'Parallel Stacks' panel" + Environment.NewLine +
- "By default, only 4 threads ID per frame group are listed. Use --allThreads/-a to list all threads ID." + Environment.NewLine +
- Environment.NewLine +
- "> pstacks" + Environment.NewLine +
- "________________________________________________" + Environment.NewLine +
- "~~~~ 8f8c" + Environment.NewLine +
- " 1 (dynamicClass).IL_STUB_PInvoke(IntPtr, Byte*, Int32, Int32 ByRef, IntPtr)" + Environment.NewLine +
- " ..." + Environment.NewLine +
- " 1 System.Console.ReadLine()" + Environment.NewLine +
- " 1 NetCoreConsoleApp.Program.Main(String[])" + Environment.NewLine +
- Environment.NewLine +
- "________________________________________________" + Environment.NewLine +
- " ~~~~ 7034" + Environment.NewLine +
- " 1 System.Threading.Monitor.Wait(Object, Int32, Boolean)" + Environment.NewLine +
- " ..." + Environment.NewLine +
- " 1 System.Threading.Tasks.Task.Wait()" + Environment.NewLine +
- " 1 NetCoreConsoleApp.Program+c.b__1_4(Object)" + Environment.NewLine +
- " ~~~~ 9c6c,4020" + Environment.NewLine +
- " 2 System.Threading.Monitor.Wait(Object, Int32, Boolean)" + Environment.NewLine +
- " ..." + Environment.NewLine +
- " 2 NetCoreConsoleApp.Program+c__DisplayClass1_0.b__7()" + Environment.NewLine +
- " 3 System.Threading.Tasks.Task.InnerInvoke()" + Environment.NewLine +
- " 4 System.Threading.Tasks.Task+c.cctor>b__278_1(Object)" + Environment.NewLine +
- " ..." + Environment.NewLine +
- " 4 System.Threading.Tasks.Task.ExecuteEntryUnsafe()" + Environment.NewLine +
- " 4 System.Threading.Tasks.Task.ExecuteWorkItem()" + Environment.NewLine +
- " 7 System.Threading.ThreadPoolWorkQueue.Dispatch()" + Environment.NewLine +
- " 7 System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()" + Environment.NewLine +
- Environment.NewLine +
- "==> 8 threads with 2 roots" + Environment.NewLine +
- Environment.NewLine +
- ""
- ;
+==> 8 threads with 2 roots
+";
}
}
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/PathToCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/PathToCommand.cs
index e63940d3be..ac0975b859 100644
--- a/src/Microsoft.Diagnostics.ExtensionCommands/PathToCommand.cs
+++ b/src/Microsoft.Diagnostics.ExtensionCommands/PathToCommand.cs
@@ -7,12 +7,9 @@
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name ="pathto", Help = "Displays the GC path from to .")]
- public class PathToCommand : CommandBase
+ [Command(Name ="pathto", Aliases = new[] { "PathTo" }, Help = "Displays the GC path from to .")]
+ public class PathToCommand : ClrRuntimeCommandBase
{
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
[ServiceImport]
public RootCacheService RootCache { get; set; }
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/SimulateGCHeapCorruption.cs b/src/Microsoft.Diagnostics.ExtensionCommands/SimulateGCHeapCorruption.cs
index 2105875b9d..e018cf218b 100644
--- a/src/Microsoft.Diagnostics.ExtensionCommands/SimulateGCHeapCorruption.cs
+++ b/src/Microsoft.Diagnostics.ExtensionCommands/SimulateGCHeapCorruption.cs
@@ -13,16 +13,13 @@
namespace Microsoft.Diagnostics.ExtensionCommands
{
[DebugCommand(Name=nameof(SimulateGCHeapCorruption), Help = "Writes values to the GC heap in strategic places to simulate heap corruption.")]
- public class SimulateGCHeapCorruption : CommandBase
+ public class SimulateGCHeapCorruption : ClrRuntimeCommandBase
{
private static readonly List _changes = new();
[ServiceImport]
public IMemoryService MemoryService { get; set; }
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
[Argument]
public string Command { get; set; }
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/SizeStatsCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/SizeStatsCommand.cs
index b8f67a7a5f..a1b59b07a0 100644
--- a/src/Microsoft.Diagnostics.ExtensionCommands/SizeStatsCommand.cs
+++ b/src/Microsoft.Diagnostics.ExtensionCommands/SizeStatsCommand.cs
@@ -10,11 +10,8 @@
namespace Microsoft.Diagnostics.ExtensionCommands
{
[Command(Name = "sizestats", Help = "Size statistics for the GC heap.")]
- public sealed class SizeStatsCommand : CommandBase
+ public sealed class SizeStatsCommand : ClrRuntimeCommandBase
{
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
public override void Invoke()
{
SizeStats(Generation.Generation0, isFree: false);
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/TaskStateCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/TaskStateCommand.cs
index 63a7e4536c..716fec0903 100644
--- a/src/Microsoft.Diagnostics.ExtensionCommands/TaskStateCommand.cs
+++ b/src/Microsoft.Diagnostics.ExtensionCommands/TaskStateCommand.cs
@@ -7,7 +7,7 @@
namespace Microsoft.Diagnostics.ExtensionCommands
{
[Command(Name = "taskstate", Aliases = new string[] { "tks" }, Help = "Displays a Task state in a human readable format.")]
- public class TaskStateCommand : ExtensionCommandBase
+ public class TaskStateCommand : ClrMDHelperCommandBase
{
[Argument(Help = "The Task instance address.")]
public string Address { get; set; }
@@ -15,7 +15,7 @@ public class TaskStateCommand : ExtensionCommandBase
[Option(Name = "--value", Aliases = new string[] { "-v" }, Help = " is the value of a Task m_stateFlags field.")]
public ulong? Value { get; set; }
- public override void ExtensionInvoke()
+ public override void Invoke()
{
if (string.IsNullOrEmpty(Address) && !Value.HasValue)
{
@@ -57,23 +57,19 @@ public override void ExtensionInvoke()
}
- protected override string GetDetailedHelp()
- {
- return DetailedHelpText;
- }
+ [HelpInvoke]
+ public static string GetDetailedHelp() =>
+@"-------------------------------------------------------------------------------
+TaskState [hexa address] [-v ]
+
+TaskState translates a Task m_stateFlags field value into human readable format.
+It supports hexadecimal address corresponding to a task instance or -v .
+
+> tks 000001db16cf98f0
+Running
- private readonly string DetailedHelpText =
- "-------------------------------------------------------------------------------" + Environment.NewLine +
- "TaskState [hexa address] [-v ]" + Environment.NewLine +
- Environment.NewLine +
- "TaskState translates a Task m_stateFlags field value into human readable format." + Environment.NewLine +
- "It supports hexadecimal address corresponding to a task instance or -v ." + Environment.NewLine +
- Environment.NewLine +
- "> tks 000001db16cf98f0" + Environment.NewLine +
- "Running" + Environment.NewLine +
- Environment.NewLine +
- "> tks -v 73728" + Environment.NewLine +
- "WaitingToRun"
- ;
+> tks -v 73728
+WaitingToRun
+";
}
}
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ThreadPoolCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ThreadPoolCommand.cs
index 85f7c4d973..f78549ffca 100644
--- a/src/Microsoft.Diagnostics.ExtensionCommands/ThreadPoolCommand.cs
+++ b/src/Microsoft.Diagnostics.ExtensionCommands/ThreadPoolCommand.cs
@@ -11,12 +11,9 @@
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "threadpool", Help = "Displays info about the runtime thread pool.")]
- public sealed class ThreadPoolCommand : CommandBase
+ [Command(Name = "threadpool", Aliases = new[] { "ThreadPool" }, Help = "Displays info about the runtime thread pool.")]
+ public sealed class ThreadPoolCommand : ClrRuntimeCommandBase
{
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
[Option(Name = "-ti", Help = "Print the hill climbing log.", Aliases = new string[] { "-hc" })]
public bool PrintHillClimbingLog { get; set; }
@@ -33,15 +30,25 @@ public override void Invoke()
}
else
{
- Table output = new(Console, Text.WithWidth(17), Text);
- output.WriteRow("CPU utilization:", $"{threadPool.CpuUtilization}%");
- output.WriteRow("Workers Total:", threadPool.ActiveWorkerThreads + threadPool.IdleWorkerThreads + threadPool.RetiredWorkerThreads);
- output.WriteRow("Workers Running:", threadPool.ActiveWorkerThreads);
- output.WriteRow("Workers Idle:", threadPool.IdleWorkerThreads);
- output.WriteRow("Worker Min Limit:", threadPool.MinThreads);
- output.WriteRow("Worker Max Limit:", threadPool.MaxThreads);
+ string threadpoolType = threadPool.UsingWindowsThreadPool ? "Windows" : "Portable";
+ Console.WriteLine($"Using the {threadpoolType} thread pool.");
Console.WriteLine();
+ Table output = new(Console, Text.WithWidth(17), Text);
+ if (threadPool.UsingWindowsThreadPool)
+ {
+ output.WriteRow("Thread count:", threadPool.WindowsThreadPoolThreadCount);
+ }
+ else
+ {
+ output.WriteRow("CPU utilization:", $"{threadPool.CpuUtilization}%");
+ output.WriteRow("Workers Total:", threadPool.ActiveWorkerThreads + threadPool.IdleWorkerThreads + threadPool.RetiredWorkerThreads);
+ output.WriteRow("Workers Running:", threadPool.ActiveWorkerThreads);
+ output.WriteRow("Workers Idle:", threadPool.IdleWorkerThreads);
+ output.WriteRow("Worker Min Limit:", threadPool.MinThreads);
+ output.WriteRow("Worker Max Limit:", threadPool.MaxThreads);
+ }
+ Console.WriteLine();
ClrType threadPoolType = Runtime.BaseClassLibrary.GetTypeByName("System.Threading.ThreadPool");
ClrStaticField usePortableIOField = threadPoolType?.GetStaticFieldByName("UsePortableThreadPoolForIO");
@@ -68,10 +75,14 @@ public override void Invoke()
}
}
- // We will assume that if UsePortableThreadPoolForIO field is deleted from ThreadPool then we are always
- // using C# version.
- bool usingPortableCompletionPorts = threadPool.Portable && (usePortableIOField is null || usePortableIOField.Read(usePortableIOField.Type.Module.AppDomain));
- if (!usingPortableCompletionPorts)
+ /*
+ The IO completion thread pool exists in .NET 7 and earlier
+ It is the only option in .NET 6 and below. The UsePortableThreadPoolForIO field doesn't exist.
+ In .NET 7, the UsePortableThreadPoolForIO field exists and is true by default, in which case the IO completion thread pool is not used, but that can be changed through config
+ In .NET 8, the UsePortableThreadPoolForIO field doesn't exist and the IO completion thread pool doesn't exist. However, in .NET 8, GetThreadpoolData returns E_NOTIMPL.
+ */
+ bool usingIOCompletionThreadPool = threadPool.HasLegacyData && (usePortableIOField is null || !usePortableIOField.Read(usePortableIOField.Type.Module.AppDomain));
+ if (usingIOCompletionThreadPool)
{
output.Columns[0] = output.Columns[0].WithWidth(19);
output.WriteRow("Completion Total:", threadPool.TotalCompletionPorts);
@@ -87,28 +98,36 @@ public override void Invoke()
if (PrintHillClimbingLog)
{
- HillClimbingLogEntry[] hcl = threadPool.EnumerateHillClimbingLog().ToArray();
- if (hcl.Length > 0)
+ if (threadPool.UsingWindowsThreadPool)
{
- output = new(Console, Text.WithWidth(10).WithAlignment(Align.Right), Column.ForEnum(), Integer, Integer, Text.WithAlignment(Align.Right));
+ Console.WriteLine("Hill Climbing Log is not supported by the Windows thread pool.");
+ Console.WriteLine();
+ }
+ else
+ {
+ HillClimbingLogEntry[] hcl = threadPool.EnumerateHillClimbingLog().ToArray();
+ if (hcl.Length > 0)
+ {
+ output = new(Console, Text.WithWidth(10).WithAlignment(Align.Right), Column.ForEnum(), Integer, Integer, Text.WithAlignment(Align.Right));
- Console.WriteLine("Hill Climbing Log:");
- output.WriteHeader("Time", "Transition", "#New Threads", "#Samples", "Throughput");
+ Console.WriteLine("Hill Climbing Log:");
+ output.WriteHeader("Time", "Transition", "#New Threads", "#Samples", "Throughput");
- int end = hcl.Last().TickCount;
- foreach (HillClimbingLogEntry entry in hcl)
- {
- Console.CancellationToken.ThrowIfCancellationRequested();
- output.WriteRow($"{(entry.TickCount - end)/1000.0:0.00}", entry.StateOrTransition, entry.NewThreadCount, entry.SampleCount, $"{entry.Throughput:0.00}");
- }
+ int end = hcl.Last().TickCount;
+ foreach (HillClimbingLogEntry entry in hcl)
+ {
+ Console.CancellationToken.ThrowIfCancellationRequested();
+ output.WriteRow($"{(entry.TickCount - end) / 1000.0:0.00}", entry.StateOrTransition, entry.NewThreadCount, entry.SampleCount, $"{entry.Throughput:0.00}");
+ }
- Console.WriteLine();
+ Console.WriteLine();
+ }
}
}
}
// We can print managed work items even if we failed to request the ThreadPool.
- if (PrintWorkItems && (threadPool is null || threadPool.Portable))
+ if (PrintWorkItems && (threadPool is null || threadPool.UsingPortableThreadPool || threadPool.UsingWindowsThreadPool))
{
DumpWorkItems();
}
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ThreadPoolQueueCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ThreadPoolQueueCommand.cs
index 1a0bd35d5f..85b5d9b4ed 100644
--- a/src/Microsoft.Diagnostics.ExtensionCommands/ThreadPoolQueueCommand.cs
+++ b/src/Microsoft.Diagnostics.ExtensionCommands/ThreadPoolQueueCommand.cs
@@ -9,9 +9,9 @@
namespace Microsoft.Diagnostics.ExtensionCommands
{
[Command(Name = "threadpoolqueue", Aliases = new string[] { "tpq" }, Help = "Displays queued ThreadPool work items.")]
- public class ThreadPoolQueueCommand : ExtensionCommandBase
+ public class ThreadPoolQueueCommand : ClrMDHelperCommandBase
{
- public override void ExtensionInvoke()
+ public override void Invoke()
{
Dictionary workItems = new();
int workItemCount = 0;
@@ -89,8 +89,7 @@ private static void UpdateStats(Dictionary stats, string statN
{
count++;
- WorkInfo wi;
- if (!stats.ContainsKey(statName))
+ if (!stats.TryGetValue(statName, out WorkInfo wi))
{
wi = new WorkInfo()
{
@@ -99,50 +98,40 @@ private static void UpdateStats(Dictionary stats, string statN
};
stats[statName] = wi;
}
- else
- {
- wi = stats[statName];
- }
wi.Count++;
}
- protected override string GetDetailedHelp()
- {
- return DetailedHelpText;
- }
+ [HelpInvoke]
+ public static string GetDetailedHelp() =>
+@"-------------------------------------------------------------------------------
+ThreadPoolQueue
+
+ThreadPoolQueue lists the enqueued work items in the Clr Thread Pool followed by a summary of the different tasks/work items.
+The global queue is first iterated before local per-thread queues.
+The name of the method to be called (on which instance if any) is also provided when available.
+
+> tpq
+
+global work item queue________________________________
+0x000002AC3C1DDBB0 Work | (ASP.global_asax)System.Web.HttpApplication.ResumeStepsWaitCallback
+ ...
+0x000002AABEC19148 Task | System.Threading.Tasks.Dataflow.Internal.TargetCore.b__3
+
+local per thread work items_____________________________________
+0x000002AE79D80A00 System.Threading.Tasks.ContinuationTaskFromTask
+ ...
+0x000002AB7CBB84A0 Task | System.Net.Http.HttpClientHandler.StartRequest
+
+ 7 Task System.Threading.Tasks.Dataflow.Internal.TargetCore.b__3
+ ...
+ 84 Task System.Net.Http.HttpClientHandler.StartRequest
+----
+6039
- private readonly string DetailedHelpText =
- "-------------------------------------------------------------------------------" + Environment.NewLine +
- "ThreadPoolQueue" + Environment.NewLine +
- Environment.NewLine +
- "ThreadPoolQueue lists the enqueued work items in the Clr Thread Pool followed by a summary of the different tasks/work items." + Environment.NewLine +
- "The global queue is first iterated before local per-thread queues." + Environment.NewLine +
- "The name of the method to be called (on which instance if any) is also provided when available." + Environment.NewLine +
- Environment.NewLine +
- "> tpq" + Environment.NewLine +
- Environment.NewLine +
- "global work item queue________________________________" + Environment.NewLine +
- "0x000002AC3C1DDBB0 Work | (ASP.global_asax)System.Web.HttpApplication.ResumeStepsWaitCallback" + Environment.NewLine +
- " ..." + Environment.NewLine +
- "0x000002AABEC19148 Task | System.Threading.Tasks.Dataflow.Internal.TargetCore.b__3" + Environment.NewLine +
- "" + Environment.NewLine +
- "local per thread work items_____________________________________" + Environment.NewLine +
- "0x000002AE79D80A00 System.Threading.Tasks.ContinuationTaskFromTask" + Environment.NewLine +
- " ..." + Environment.NewLine +
- "0x000002AB7CBB84A0 Task | System.Net.Http.HttpClientHandler.StartRequest" + Environment.NewLine +
- "" + Environment.NewLine +
- " 7 Task System.Threading.Tasks.Dataflow.Internal.TargetCore.b__3" + Environment.NewLine +
- " ..." + Environment.NewLine +
- " 84 Task System.Net.Http.HttpClientHandler.StartRequest" + Environment.NewLine +
- "----" + Environment.NewLine +
- "6039" + Environment.NewLine +
- "" + Environment.NewLine +
- "1810 Work (ASP.global_asax) System.Web.HttpApplication.ResumeStepsWaitCallback" + Environment.NewLine +
- "----" + Environment.NewLine +
- "1810" + Environment.NewLine +
- ""
- ;
+1810 Work (ASP.global_asax) System.Web.HttpApplication.ResumeStepsWaitCallback
+----
+1810";
private sealed class WorkInfo
{
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/TimersCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/TimersCommand.cs
index 6a040242a6..2c576c7193 100644
--- a/src/Microsoft.Diagnostics.ExtensionCommands/TimersCommand.cs
+++ b/src/Microsoft.Diagnostics.ExtensionCommands/TimersCommand.cs
@@ -9,9 +9,9 @@
namespace Microsoft.Diagnostics.ExtensionCommands
{
[Command(Name = "timerinfo", Aliases = new string[] { "ti" }, Help = "Displays information about running timers.")]
- public class TimersCommand : ExtensionCommandBase
+ public class TimersCommand : ClrMDHelperCommandBase
{
- public override void ExtensionInvoke()
+ public override void Invoke()
{
try
{
@@ -104,33 +104,28 @@ private static string GetTimerString(TimerInfo timer)
}
- protected override string GetDetailedHelp()
- {
- return DetailedHelpText;
- }
+ [HelpInvoke]
+ public static string GetDetailedHelp() =>
+@"-------------------------------------------------------------------------------
+TimerInfo
- private readonly string DetailedHelpText =
- "-------------------------------------------------------------------------------" + Environment.NewLine +
- "TimerInfo" + Environment.NewLine +
- Environment.NewLine +
- "TimerInfo lists all the running timers followed by a summary of the different items." + Environment.NewLine +
- "The name of the method to be called (on which instance if any) is also provided when available." + Environment.NewLine +
- Environment.NewLine +
- "> ti" + Environment.NewLine +
- "0x000001E29BD45848 @ 964 ms every 1000 ms | 0x000001E29BD0C828 (Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.Heartbeat) ->" + Environment.NewLine +
- "0x000001E19BD0F868 @ 1 ms every ------ ms | 0x000001E19BD0F800 (System.Threading.Tasks.Task+DelayPromise) -> System.Threading.Tasks.Task+<>c.b__260_1" + Environment.NewLine +
- "0x000001E09BD09B40 @ 1 ms every ------ ms | 0x000001E09BD09AD8 (System.Threading.Tasks.Task+DelayPromise) -> System.Threading.Tasks.Task+<>c.b__260_1" + Environment.NewLine +
- "0x000001E29BD58C68 @ 1 ms every ------ ms | 0x000001E29BD58C00 (System.Threading.Tasks.Task+DelayPromise) -> System.Threading.Tasks.Task+<>c.b__260_1" + Environment.NewLine +
- "0x000001E29BCB1398 @ 5000 ms every ------ ms | 0x0000000000000000 () -> System.Diagnostics.Tracing.EventPipeController.PollForTracingCommand" + Environment.NewLine +
- Environment.NewLine +
- " 5 timers" + Environment.NewLine +
- "-----------------------------------------------" + Environment.NewLine +
- " 1 | @ 964 ms every 1000 ms | (Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.Heartbeat) ->" + Environment.NewLine +
- " 1 | @ 5000 ms every ------ ms | () -> System.Diagnostics.Tracing.EventPipeController.PollForTracingCommand" + Environment.NewLine +
- " 3 | @ 1 ms every ------ ms | (System.Threading.Tasks.Task+DelayPromise) -> System.Threading.Tasks.Task+<>c.b__260_1"
- ;
- }
+TimerInfo lists all the running timers followed by a summary of the different items.
+The name of the method to be called (on which instance if any) is also provided when available.
+> ti
+0x000001E29BD45848 @ 964 ms every 1000 ms | 0x000001E29BD0C828 (Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.Heartbeat) ->
+0x000001E19BD0F868 @ 1 ms every ------ ms | 0x000001E19BD0F800 (System.Threading.Tasks.Task+DelayPromise) -> System.Threading.Tasks.Task+<>c.b__260_1
+0x000001E09BD09B40 @ 1 ms every ------ ms | 0x000001E09BD09AD8 (System.Threading.Tasks.Task+DelayPromise) -> System.Threading.Tasks.Task+<>c.b__260_1
+0x000001E29BD58C68 @ 1 ms every ------ ms | 0x000001E29BD58C00 (System.Threading.Tasks.Task+DelayPromise) -> System.Threading.Tasks.Task+<>c.b__260_1
+0x000001E29BCB1398 @ 5000 ms every ------ ms | 0x0000000000000000 () -> System.Diagnostics.Tracing.EventPipeController.PollForTracingCommand
+
+ 5 timers
+-----------------------------------------------
+ 1 | @ 964 ms every 1000 ms | (Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.Heartbeat) ->
+ 1 | @ 5000 ms every ------ ms | () -> System.Diagnostics.Tracing.EventPipeController.PollForTracingCommand
+ 3 | @ 1 ms every ------ ms | (System.Threading.Tasks.Task+DelayPromise) -> System.Threading.Tasks.Task+<>c.b__260_1
+";
+ }
internal sealed class TimerStat
{
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/TraverseHeapCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/TraverseHeapCommand.cs
index 40d2820502..57315180bb 100644
--- a/src/Microsoft.Diagnostics.ExtensionCommands/TraverseHeapCommand.cs
+++ b/src/Microsoft.Diagnostics.ExtensionCommands/TraverseHeapCommand.cs
@@ -12,12 +12,9 @@
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "traverseheap", Help = "Writes out heap information to a file in a format understood by the CLR Profiler.")]
- public class TraverseHeapCommand : CommandBase
+ [Command(Name = "traverseheap", Aliases = new[] { "TraverseHeap" }, Help = "Writes out heap information to a file in a format understood by the CLR Profiler.")]
+ public class TraverseHeapCommand : ClrRuntimeCommandBase
{
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
[ServiceImport]
public RootCacheService RootCache { get; set; }
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/VerifyHeapCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/VerifyHeapCommand.cs
index e5229cbf32..c7b7b19578 100644
--- a/src/Microsoft.Diagnostics.ExtensionCommands/VerifyHeapCommand.cs
+++ b/src/Microsoft.Diagnostics.ExtensionCommands/VerifyHeapCommand.cs
@@ -9,16 +9,13 @@
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = CommandName, Help = "Searches the managed heap for memory corruption..")]
- public class VerifyHeapCommand : CommandBase
+ [Command(Name = CommandName, Aliases = new[] { "VerifyHeap" }, Help = "Searches the managed heap for memory corruption..")]
+ public class VerifyHeapCommand : ClrRuntimeCommandBase
{
private const string CommandName = "verifyheap";
private int _totalObjects;
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
[ServiceImport]
public IMemoryService MemoryService { get; set; }
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/VerifyObjectCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/VerifyObjectCommand.cs
index f952aca361..a9779d337b 100644
--- a/src/Microsoft.Diagnostics.ExtensionCommands/VerifyObjectCommand.cs
+++ b/src/Microsoft.Diagnostics.ExtensionCommands/VerifyObjectCommand.cs
@@ -11,16 +11,13 @@
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "verifyobj", Help = "Checks the given object for signs of corruption.")]
- public sealed class VerifyObjectCommand : CommandBase
+ [Command(Name = "verifyobj", Aliases = new[] { "VerifyObj" }, Help = "Checks the given object for signs of corruption.")]
+ public sealed class VerifyObjectCommand : ClrRuntimeCommandBase
{
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
[ServiceImport]
public IMemoryService Memory { get; set; }
- [Argument(Name = "ObjectAddress", Help = "The object to verify.")]
+ [Argument(Name = "objectaddress", Help = "The object to verify.")]
public string ObjectAddress { get; set; }
public override void Invoke()
diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterPayload.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterPayload.cs
index 99e3030081..850949b087 100644
--- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterPayload.cs
+++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterPayload.cs
@@ -7,12 +7,9 @@
namespace Microsoft.Diagnostics.Monitoring.EventPipe
{
- ///
- /// TODO This is currently a duplication of the src\Tools\dotnet-counters\CounterPayload.cs stack. The two will be unified in a separate change.
- ///
- internal class CounterPayload : ICounterPayload
+ internal abstract class CounterPayload : ICounterPayload
{
- public CounterPayload(DateTime timestamp,
+ protected CounterPayload(DateTime timestamp,
string provider,
string name,
string displayName,
@@ -20,7 +17,9 @@ public CounterPayload(DateTime timestamp,
double value,
CounterType counterType,
float interval,
- string metadata)
+ int series,
+ string metadata,
+ EventType eventType)
{
Timestamp = timestamp;
Name = name;
@@ -30,30 +29,11 @@ public CounterPayload(DateTime timestamp,
CounterType = counterType;
Provider = provider;
Interval = interval;
+ Series = series;
Metadata = metadata;
- EventType = EventType.Gauge;
- }
-
- // Copied from dotnet-counters
- public CounterPayload(string providerName,
- string name,
- string metadata,
- double value,
- DateTime timestamp,
- string type,
- EventType eventType)
- {
- Provider = providerName;
- Name = name;
- Metadata = metadata;
- Value = value;
- Timestamp = timestamp;
- CounterType = (CounterType)Enum.Parse(typeof(CounterType), type);
EventType = eventType;
}
- public string Namespace { get; }
-
public string Name { get; }
public string DisplayName { get; protected set; }
@@ -73,12 +53,50 @@ public CounterPayload(string providerName,
public string Metadata { get; }
public EventType EventType { get; set; }
+
+ public virtual bool IsMeter => false;
+
+ public int Series { get; }
+ }
+
+ internal sealed class EventCounterPayload : CounterPayload
+ {
+ public EventCounterPayload(DateTime timestamp,
+ string provider,
+ string name,
+ string displayName,
+ string unit,
+ double value,
+ CounterType counterType,
+ float interval,
+ int series,
+ string metadata) : base(timestamp, provider, name, displayName, unit, value, counterType, interval, series, metadata, EventType.Gauge)
+ {
+ }
+ }
+
+ internal abstract class MeterPayload : CounterPayload
+ {
+ protected MeterPayload(DateTime timestamp,
+ string provider,
+ string name,
+ string displayName,
+ string unit,
+ double value,
+ CounterType counterType,
+ string metadata,
+ EventType eventType)
+ : base(timestamp, provider, name, displayName, unit, value, counterType, 0.0f, 0, metadata, eventType)
+ {
+ }
+
+ public override bool IsMeter => true;
}
- internal class GaugePayload : CounterPayload
+ internal sealed class GaugePayload : MeterPayload
{
public GaugePayload(string providerName, string name, string displayName, string displayUnits, string metadata, double value, DateTime timestamp) :
- base(providerName, name, metadata, value, timestamp, "Metric", EventType.Gauge)
+ base(timestamp, providerName, name, displayName, displayUnits, value, CounterType.Metric, metadata, EventType.Gauge)
{
// In case these properties are not provided, set them to appropriate values.
string counterName = string.IsNullOrEmpty(displayName) ? name : displayName;
@@ -86,10 +104,10 @@ public GaugePayload(string providerName, string name, string displayName, string
}
}
- internal class UpDownCounterPayload : CounterPayload
+ internal class UpDownCounterPayload : MeterPayload
{
public UpDownCounterPayload(string providerName, string name, string displayName, string displayUnits, string metadata, double value, DateTime timestamp) :
- base(providerName, name, metadata, value, timestamp, "Metric", EventType.UpDownCounter)
+ base(timestamp, providerName, name, displayName, displayUnits, value, CounterType.Metric, metadata, EventType.UpDownCounter)
{
// In case these properties are not provided, set them to appropriate values.
string counterName = string.IsNullOrEmpty(displayName) ? name : displayName;
@@ -97,19 +115,26 @@ public UpDownCounterPayload(string providerName, string name, string displayName
}
}
- internal class CounterEndedPayload : CounterPayload
+ internal sealed class BeginInstrumentReportingPayload : MeterPayload
{
- public CounterEndedPayload(string providerName, string name, DateTime timestamp)
- : base(providerName, name, null, 0.0, timestamp, "Metric", EventType.CounterEnded)
+ public BeginInstrumentReportingPayload(string providerName, string name, DateTime timestamp)
+ : base(timestamp, providerName, name, string.Empty, string.Empty, 0.0, CounterType.Metric, null, EventType.BeginInstrumentReporting)
{
+ }
+ }
+ internal sealed class CounterEndedPayload : MeterPayload
+ {
+ public CounterEndedPayload(string providerName, string name, DateTime timestamp)
+ : base(timestamp, providerName, name, string.Empty, string.Empty, 0.0, CounterType.Metric, null, EventType.CounterEnded)
+ {
}
}
- internal class RatePayload : CounterPayload
+ internal sealed class RatePayload : MeterPayload
{
public RatePayload(string providerName, string name, string displayName, string displayUnits, string metadata, double value, double intervalSecs, DateTime timestamp) :
- base(providerName, name, metadata, value, timestamp, "Rate", EventType.Rate)
+ base(timestamp, providerName, name, displayName, displayUnits, value, CounterType.Rate, metadata, EventType.Rate)
{
// In case these properties are not provided, set them to appropriate values.
string counterName = string.IsNullOrEmpty(displayName) ? name : displayName;
@@ -119,35 +144,46 @@ public RatePayload(string providerName, string name, string displayName, string
}
}
- internal class PercentilePayload : CounterPayload
+ internal record struct Quantile(double Percentage, double Value);
+
+ internal sealed class PercentilePayload : MeterPayload
{
- public PercentilePayload(string providerName, string name, string displayName, string displayUnits, string metadata, IEnumerable quantiles, DateTime timestamp) :
- base(providerName, name, metadata, 0.0, timestamp, "Metric", EventType.Histogram)
+ public PercentilePayload(string providerName, string name, string displayName, string displayUnits, string metadata, double value, DateTime timestamp) :
+ base(timestamp, providerName, name, displayName, displayUnits, value, CounterType.Metric, metadata, EventType.Histogram)
{
// In case these properties are not provided, set them to appropriate values.
string counterName = string.IsNullOrEmpty(displayName) ? name : displayName;
DisplayName = !string.IsNullOrEmpty(displayUnits) ? $"{counterName} ({displayUnits})" : counterName;
- Quantiles = quantiles.ToArray();
}
-
- public Quantile[] Quantiles { get; }
}
- internal record struct Quantile(double Percentage, double Value);
-
- internal class ErrorPayload : CounterPayload
+ // Dotnet-monitor and dotnet-counters previously had incompatible PercentilePayload implementations before being unified -
+ // Dotnet-monitor created a single payload that contained all of the quantiles to keep them together, whereas
+ // dotnet-counters created a separate payload for each quantile (multiple payloads per TraceEvent).
+ // AggregatePercentilePayload allows dotnet-monitor to construct a PercentilePayload for individual quantiles
+ // like dotnet-counters, while still keeping the quantiles together as a unit.
+ internal sealed class AggregatePercentilePayload : MeterPayload
{
- public ErrorPayload(string errorMessage) : this(errorMessage, DateTime.UtcNow)
+ public AggregatePercentilePayload(string providerName, string name, string displayName, string displayUnits, string metadata, IEnumerable quantiles, DateTime timestamp) :
+ base(timestamp, providerName, name, displayName, displayUnits, 0.0, CounterType.Metric, metadata, EventType.Histogram)
{
+ //string counterName = string.IsNullOrEmpty(displayName) ? name : displayName;
+ //DisplayName = !string.IsNullOrEmpty(displayUnits) ? $"{counterName} ({displayUnits})" : counterName;
+ Quantiles = quantiles.ToArray();
}
- public ErrorPayload(string errorMessage, DateTime timestamp) :
- base(string.Empty, string.Empty, null, 0.0, timestamp, "Metric", EventType.Error)
+ public Quantile[] Quantiles { get; }
+ }
+
+ internal sealed class ErrorPayload : MeterPayload
+ {
+ public ErrorPayload(string errorMessage, DateTime timestamp, EventType eventType)
+ : base(timestamp, string.Empty, string.Empty, string.Empty, string.Empty, 0.0, CounterType.Metric, null, eventType)
{
ErrorMessage = errorMessage;
}
- public string ErrorMessage { get; private set; }
+ public string ErrorMessage { get; }
}
internal enum EventType : int
@@ -156,7 +192,52 @@ internal enum EventType : int
Gauge,
Histogram,
UpDownCounter,
- Error,
- CounterEnded
+ BeginInstrumentReporting,
+ CounterEnded,
+ HistogramLimitError,
+ TimeSeriesLimitError,
+ ErrorTargetProcess,
+ MultipleSessionsNotSupportedError,
+ MultipleSessionsConfiguredIncorrectlyError,
+ ObservableInstrumentCallbackError
+ }
+
+ internal static class EventTypeExtensions
+ {
+ public static bool IsValuePublishedEvent(this EventType eventType)
+ {
+ return eventType is EventType.Gauge
+ || eventType is EventType.Rate
+ || eventType is EventType.Histogram
+ || eventType is EventType.UpDownCounter;
+ }
+
+ public static bool IsError(this EventType eventType)
+ {
+ return eventType is EventType.HistogramLimitError
+ || eventType is EventType.TimeSeriesLimitError
+ || eventType is EventType.ErrorTargetProcess
+ || eventType is EventType.MultipleSessionsNotSupportedError
+ || eventType is EventType.MultipleSessionsConfiguredIncorrectlyError
+ || eventType is EventType.ObservableInstrumentCallbackError;
+ }
+
+ public static bool IsNonFatalError(this EventType eventType)
+ {
+ return IsError(eventType)
+ && !IsTracingError(eventType)
+ && !IsSessionStartupError(eventType);
+ }
+
+ public static bool IsTracingError(this EventType eventType)
+ {
+ return eventType is EventType.ErrorTargetProcess;
+ }
+
+ public static bool IsSessionStartupError(this EventType eventType)
+ {
+ return eventType is EventType.MultipleSessionsNotSupportedError
+ || eventType is EventType.MultipleSessionsConfiguredIncorrectlyError;
+ }
}
}
diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterPayloadExtensions.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterPayloadExtensions.cs
deleted file mode 100644
index 12d4b862e4..0000000000
--- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterPayloadExtensions.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.Diagnostics.Monitoring.EventPipe
-{
- internal static class CounterPayloadExtensions
- {
- public static string GetDisplay(this ICounterPayload counterPayload)
- {
- if (counterPayload.CounterType == CounterType.Rate)
- {
- return $"{counterPayload.DisplayName} ({counterPayload.Unit} / {counterPayload.Interval} sec)";
- }
- if (!string.IsNullOrEmpty(counterPayload.Unit))
- {
- return $"{counterPayload.DisplayName} ({counterPayload.Unit})";
- }
- return $"{counterPayload.DisplayName}";
- }
- }
-}
diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/ICounterPayload.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/ICounterPayload.cs
index 4e3ce06f74..4cc5fdb043 100644
--- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/ICounterPayload.cs
+++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/ICounterPayload.cs
@@ -41,6 +41,10 @@ internal interface ICounterPayload
///
string Metadata { get; }
- EventType EventType { get; set; }
+ EventType EventType { get; }
+
+ bool IsMeter { get; }
+
+ int Series { get; }
}
}
diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/MetricsPipeline.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/MetricsPipeline.cs
index 1c3a29ab8a..9f8f34c150 100644
--- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/MetricsPipeline.cs
+++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/MetricsPipeline.cs
@@ -17,6 +17,7 @@ internal class MetricsPipeline : EventSourcePipeline
private readonly CounterFilter _filter;
private string _clientId;
private string _sessionId;
+ private CounterConfiguration _counterConfiguration;
public MetricsPipeline(DiagnosticsClient client,
MetricsPipelineSettings settings,
@@ -51,6 +52,14 @@ protected override MonitoringSourceConfiguration CreateConfiguration()
_clientId = config.ClientId;
_sessionId = config.SessionId;
+ _counterConfiguration = new CounterConfiguration(_filter)
+ {
+ SessionId = _sessionId,
+ ClientId = _clientId,
+ MaxHistograms = Settings.MaxHistograms,
+ MaxTimeseries = Settings.MaxTimeSeries
+ };
+
return config;
}
@@ -61,7 +70,7 @@ protected override async Task OnEventSourceAvailable(EventPipeEventSource eventS
eventSource.Dynamic.All += traceEvent => {
try
{
- if (traceEvent.TryGetCounterPayload(_filter, _sessionId, _clientId, out ICounterPayload counterPayload))
+ if (traceEvent.TryGetCounterPayload(_counterConfiguration, out ICounterPayload counterPayload))
{
ExecuteCounterLoggerAction((metricLogger) => metricLogger.Log(counterPayload));
}
diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/TraceEventExtensions.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/TraceEventExtensions.cs
index 942a7ebddf..06472cf3f3 100644
--- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/TraceEventExtensions.cs
+++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/TraceEventExtensions.cs
@@ -9,11 +9,29 @@
namespace Microsoft.Diagnostics.Monitoring.EventPipe
{
+ internal class CounterConfiguration
+ {
+ public CounterConfiguration(CounterFilter filter)
+ {
+ CounterFilter = filter ?? throw new ArgumentNullException(nameof(filter));
+ }
+
+ public CounterFilter CounterFilter { get; }
+
+ public string SessionId { get; set; }
+
+ public string ClientId { get; set; }
+
+ public int MaxHistograms { get; set; }
+
+ public int MaxTimeseries { get; set; }
+ }
+
internal static class TraceEventExtensions
{
private static HashSet inactiveSharedSessions = new(StringComparer.OrdinalIgnoreCase);
- public static bool TryGetCounterPayload(this TraceEvent traceEvent, CounterFilter filter, string sessionId, string clientId, out ICounterPayload payload)
+ public static bool TryGetCounterPayload(this TraceEvent traceEvent, CounterConfiguration counterConfiguration, out ICounterPayload payload)
{
payload = null;
@@ -27,12 +45,12 @@ public static bool TryGetCounterPayload(this TraceEvent traceEvent, CounterFilte
string counterName = payloadFields["Name"].ToString();
string metadata = payloadFields["Metadata"].ToString();
-
+ int seriesValue = GetInterval(series);
//CONSIDER
//Concurrent counter sessions do not each get a separate interval. Instead the payload
//for _all_ the counters changes the Series to be the lowest specified interval, on a per provider basis.
//Currently the CounterFilter will remove any data whose Series doesn't match the requested interval.
- if (!filter.IsIncluded(traceEvent.ProviderName, counterName, GetInterval(series)))
+ if (!counterConfiguration.CounterFilter.IsIncluded(traceEvent.ProviderName, counterName, seriesValue))
{
return false;
}
@@ -61,7 +79,7 @@ public static bool TryGetCounterPayload(this TraceEvent traceEvent, CounterFilte
// Note that dimensional data such as pod and namespace are automatically added in prometheus and azure monitor scenarios.
// We no longer added it here.
- payload = new CounterPayload(
+ payload = new EventCounterPayload(
traceEvent.TimeStamp,
traceEvent.ProviderName,
counterName, displayName,
@@ -69,57 +87,57 @@ public static bool TryGetCounterPayload(this TraceEvent traceEvent, CounterFilte
value,
counterType,
intervalSec,
+ seriesValue / 1000,
metadata);
return true;
}
- if (clientId != null && !inactiveSharedSessions.Contains(clientId) && MonitoringSourceConfiguration.SystemDiagnosticsMetricsProviderName.Equals(traceEvent.ProviderName))
+ if (counterConfiguration.ClientId != null && !inactiveSharedSessions.Contains(counterConfiguration.ClientId) && MonitoringSourceConfiguration.SystemDiagnosticsMetricsProviderName.Equals(traceEvent.ProviderName))
{
if (traceEvent.EventName == "BeginInstrumentReporting")
{
- // Do we want to log something for this?
- //HandleBeginInstrumentReporting(traceEvent);
+ HandleBeginInstrumentReporting(traceEvent, counterConfiguration, out payload);
}
if (traceEvent.EventName == "HistogramValuePublished")
{
- HandleHistogram(traceEvent, filter, sessionId, out payload);
+ HandleHistogram(traceEvent, counterConfiguration, out payload);
}
else if (traceEvent.EventName == "GaugeValuePublished")
{
- HandleGauge(traceEvent, filter, sessionId, out payload);
+ HandleGauge(traceEvent, counterConfiguration, out payload);
}
else if (traceEvent.EventName == "CounterRateValuePublished")
{
- HandleCounterRate(traceEvent, filter, sessionId, out payload);
+ HandleCounterRate(traceEvent, counterConfiguration, out payload);
}
else if (traceEvent.EventName == "UpDownCounterRateValuePublished")
{
- HandleUpDownCounterValue(traceEvent, filter, sessionId, out payload);
+ HandleUpDownCounterValue(traceEvent, counterConfiguration, out payload);
}
else if (traceEvent.EventName == "TimeSeriesLimitReached")
{
- HandleTimeSeriesLimitReached(traceEvent, sessionId, out payload);
+ HandleTimeSeriesLimitReached(traceEvent, counterConfiguration, out payload);
}
else if (traceEvent.EventName == "HistogramLimitReached")
{
- HandleHistogramLimitReached(traceEvent, sessionId, out payload);
+ HandleHistogramLimitReached(traceEvent, counterConfiguration, out payload);
}
else if (traceEvent.EventName == "Error")
{
- HandleError(traceEvent, sessionId, out payload);
+ HandleError(traceEvent, counterConfiguration, out payload);
}
else if (traceEvent.EventName == "ObservableInstrumentCallbackError")
{
- HandleObservableInstrumentCallbackError(traceEvent, sessionId, out payload);
+ HandleObservableInstrumentCallbackError(traceEvent, counterConfiguration, out payload);
}
else if (traceEvent.EventName == "MultipleSessionsNotSupportedError")
{
- HandleMultipleSessionsNotSupportedError(traceEvent, sessionId, out payload);
+ HandleMultipleSessionsNotSupportedError(traceEvent, counterConfiguration, out payload);
}
else if (traceEvent.EventName == "MultipleSessionsConfiguredIncorrectlyError")
{
- HandleMultipleSessionsConfiguredIncorrectlyError(traceEvent, clientId, out payload);
+ HandleMultipleSessionsConfiguredIncorrectlyError(traceEvent, counterConfiguration.ClientId, out payload);
}
return payload != null;
@@ -128,13 +146,13 @@ public static bool TryGetCounterPayload(this TraceEvent traceEvent, CounterFilte
return false;
}
- private static void HandleGauge(TraceEvent obj, CounterFilter filter, string sessionId, out ICounterPayload payload)
+ private static void HandleGauge(TraceEvent obj, CounterConfiguration counterConfiguration, out ICounterPayload payload)
{
payload = null;
string payloadSessionId = (string)obj.PayloadValue(0);
- if (payloadSessionId != sessionId)
+ if (payloadSessionId != counterConfiguration.SessionId)
{
return;
}
@@ -146,7 +164,7 @@ private static void HandleGauge(TraceEvent obj, CounterFilter filter, string ses
string tags = (string)obj.PayloadValue(5);
string lastValueText = (string)obj.PayloadValue(6);
- if (!filter.IsIncluded(meterName, instrumentName))
+ if (!counterConfiguration.CounterFilter.IsIncluded(meterName, instrumentName))
{
return;
}
@@ -164,13 +182,36 @@ private static void HandleGauge(TraceEvent obj, CounterFilter filter, string ses
}
}
- private static void HandleCounterRate(TraceEvent traceEvent, CounterFilter filter, string sessionId, out ICounterPayload payload)
+ private static void HandleBeginInstrumentReporting(TraceEvent traceEvent, CounterConfiguration counterConfiguration, out ICounterPayload payload)
+ {
+ payload = null;
+
+ string payloadSessionId = (string)traceEvent.PayloadValue(0);
+ if (payloadSessionId != counterConfiguration.SessionId)
+ {
+ return;
+ }
+
+ string meterName = (string)traceEvent.PayloadValue(1);
+ //string meterVersion = (string)obj.PayloadValue(2);
+ string instrumentName = (string)traceEvent.PayloadValue(3);
+
+ if (!counterConfiguration.CounterFilter.IsIncluded(meterName, instrumentName))
+ {
+ return;
+ }
+
+
+ payload = new BeginInstrumentReportingPayload(meterName, instrumentName, traceEvent.TimeStamp);
+ }
+
+ private static void HandleCounterRate(TraceEvent traceEvent, CounterConfiguration counterConfiguration, out ICounterPayload payload)
{
payload = null;
string payloadSessionId = (string)traceEvent.PayloadValue(0);
- if (payloadSessionId != sessionId)
+ if (payloadSessionId != counterConfiguration.SessionId)
{
return;
}
@@ -182,14 +223,14 @@ private static void HandleCounterRate(TraceEvent traceEvent, CounterFilter filte
string tags = (string)traceEvent.PayloadValue(5);
string rateText = (string)traceEvent.PayloadValue(6);
- if (!filter.IsIncluded(meterName, instrumentName))
+ if (!counterConfiguration.CounterFilter.IsIncluded(meterName, instrumentName))
{
return;
}
- if (double.TryParse(rateText, NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double rate))
+ if (double.TryParse(rateText, NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double value))
{
- payload = new RatePayload(meterName, instrumentName, null, unit, tags, rate, filter.DefaultIntervalSeconds, traceEvent.TimeStamp);
+ payload = new RatePayload(meterName, instrumentName, null, unit, tags, value, counterConfiguration.CounterFilter.DefaultIntervalSeconds, traceEvent.TimeStamp);
}
else
{
@@ -200,13 +241,13 @@ private static void HandleCounterRate(TraceEvent traceEvent, CounterFilter filte
}
}
- private static void HandleUpDownCounterValue(TraceEvent traceEvent, CounterFilter filter, string sessionId, out ICounterPayload payload)
+ private static void HandleUpDownCounterValue(TraceEvent traceEvent, CounterConfiguration configuration, out ICounterPayload payload)
{
payload = null;
string payloadSessionId = (string)traceEvent.PayloadValue(0);
- if (payloadSessionId != sessionId || traceEvent.Version < 1) // Version 1 added the value field.
+ if (payloadSessionId != configuration.SessionId || traceEvent.Version < 1) // Version 1 added the value field.
{
return;
}
@@ -219,7 +260,7 @@ private static void HandleUpDownCounterValue(TraceEvent traceEvent, CounterFilte
//string rateText = (string)traceEvent.PayloadValue(6); // Not currently using rate for UpDownCounters.
string valueText = (string)traceEvent.PayloadValue(7);
- if (!filter.IsIncluded(meterName, instrumentName))
+ if (!configuration.CounterFilter.IsIncluded(meterName, instrumentName))
{
return;
}
@@ -239,12 +280,13 @@ private static void HandleUpDownCounterValue(TraceEvent traceEvent, CounterFilte
}
}
- private static void HandleHistogram(TraceEvent obj, CounterFilter filter, string sessionId, out ICounterPayload payload)
+ private static void HandleHistogram(TraceEvent obj, CounterConfiguration configuration, out ICounterPayload payload)
{
payload = null;
string payloadSessionId = (string)obj.PayloadValue(0);
- if (payloadSessionId != sessionId)
+
+ if (payloadSessionId != configuration.SessionId)
{
return;
}
@@ -256,72 +298,71 @@ private static void HandleHistogram(TraceEvent obj, CounterFilter filter, string
string tags = (string)obj.PayloadValue(5);
string quantilesText = (string)obj.PayloadValue(6);
- if (!filter.IsIncluded(meterName, instrumentName))
+ if (!configuration.CounterFilter.IsIncluded(meterName, instrumentName))
{
return;
}
//Note quantiles can be empty.
IList quantiles = ParseQuantiles(quantilesText);
- payload = new PercentilePayload(meterName, instrumentName, null, unit, tags, quantiles, obj.TimeStamp);
- }
-
+ payload = new AggregatePercentilePayload(meterName, instrumentName, null, unit, tags, quantiles, obj.TimeStamp);
+ }
- private static void HandleHistogramLimitReached(TraceEvent obj, string sessionId, out ICounterPayload payload)
+ private static void HandleHistogramLimitReached(TraceEvent obj, CounterConfiguration configuration, out ICounterPayload payload)
{
payload = null;
string payloadSessionId = (string)obj.PayloadValue(0);
- if (payloadSessionId != sessionId)
+ if (payloadSessionId != configuration.SessionId)
{
return;
}
- string errorMessage = $"Warning: Histogram tracking limit reached. Not all data is being shown. The limit can be changed with maxHistograms but will use more memory in the target process.";
+ string errorMessage = $"Warning: Histogram tracking limit ({configuration.MaxHistograms}) reached. Not all data is being shown. The limit can be changed but will use more memory in the target process.";
- payload = new ErrorPayload(errorMessage);
+ payload = new ErrorPayload(errorMessage, obj.TimeStamp, EventType.HistogramLimitError);
}
- private static void HandleTimeSeriesLimitReached(TraceEvent obj, string sessionId, out ICounterPayload payload)
+ private static void HandleTimeSeriesLimitReached(TraceEvent obj, CounterConfiguration configuration, out ICounterPayload payload)
{
payload = null;
string payloadSessionId = (string)obj.PayloadValue(0);
- if (payloadSessionId != sessionId)
+ if (payloadSessionId != configuration.SessionId)
{
return;
}
- string errorMessage = "Warning: Time series tracking limit reached. Not all data is being shown. The limit can be changed with maxTimeSeries but will use more memory in the target process.";
+ string errorMessage = $"Warning: Time series tracking limit ({configuration.MaxTimeseries}) reached. Not all data is being shown. The limit can be changed but will use more memory in the target process.";
- payload = new ErrorPayload(errorMessage, obj.TimeStamp);
+ payload = new ErrorPayload(errorMessage, obj.TimeStamp, EventType.TimeSeriesLimitError);
}
- private static void HandleError(TraceEvent obj, string sessionId, out ICounterPayload payload)
+ private static void HandleError(TraceEvent obj, CounterConfiguration configuration, out ICounterPayload payload)
{
payload = null;
string payloadSessionId = (string)obj.PayloadValue(0);
string error = (string)obj.PayloadValue(1);
- if (payloadSessionId != sessionId)
+ if (configuration.SessionId != payloadSessionId)
{
return;
}
string errorMessage = "Error reported from target process:" + Environment.NewLine + error;
- payload = new ErrorPayload(errorMessage, obj.TimeStamp);
+ payload = new ErrorPayload(errorMessage, obj.TimeStamp, EventType.ErrorTargetProcess);
}
- private static void HandleMultipleSessionsNotSupportedError(TraceEvent obj, string sessionId, out ICounterPayload payload)
+ private static void HandleMultipleSessionsNotSupportedError(TraceEvent obj, CounterConfiguration configuration, out ICounterPayload payload)
{
payload = null;
string payloadSessionId = (string)obj.PayloadValue(0);
- if (payloadSessionId == sessionId)
+ if (payloadSessionId == configuration.SessionId)
{
// If our session is the one that is running then the error is not for us,
// it is for some other session that came later
@@ -332,7 +373,7 @@ private static void HandleMultipleSessionsNotSupportedError(TraceEvent obj, stri
string errorMessage = "Error: Another metrics collection session is already in progress for the target process." + Environment.NewLine +
"Concurrent sessions are not supported.";
- payload = new ErrorPayload(errorMessage, obj.TimeStamp);
+ payload = new ErrorPayload(errorMessage, obj.TimeStamp, EventType.MultipleSessionsNotSupportedError);
}
}
@@ -383,20 +424,20 @@ private static void HandleMultipleSessionsConfiguredIncorrectlyError(TraceEvent
if (TryCreateSharedSessionConfiguredIncorrectlyMessage(obj, clientId, out string message))
{
- payload = new ErrorPayload(message.ToString(), obj.TimeStamp);
+ payload = new ErrorPayload(message.ToString(), obj.TimeStamp, EventType.MultipleSessionsConfiguredIncorrectlyError);
inactiveSharedSessions.Add(clientId);
}
}
- private static void HandleObservableInstrumentCallbackError(TraceEvent obj, string sessionId, out ICounterPayload payload)
+ private static void HandleObservableInstrumentCallbackError(TraceEvent obj, CounterConfiguration configuration, out ICounterPayload payload)
{
payload = null;
string payloadSessionId = (string)obj.PayloadValue(0);
string error = (string)obj.PayloadValue(1);
- if (payloadSessionId != sessionId)
+ if (payloadSessionId != configuration.SessionId)
{
return;
}
@@ -404,10 +445,10 @@ private static void HandleObservableInstrumentCallbackError(TraceEvent obj, stri
string errorMessage = "Exception thrown from an observable instrument callback in the target process:" + Environment.NewLine +
error;
- payload = new ErrorPayload(errorMessage, obj.TimeStamp);
+ payload = new ErrorPayload(errorMessage, obj.TimeStamp, EventType.ObservableInstrumentCallbackError);
}
- private static IList ParseQuantiles(string quantileList)
+ private static List ParseQuantiles(string quantileList)
{
string[] quantileParts = quantileList.Split(';', StringSplitOptions.RemoveEmptyEntries);
List quantiles = new();
@@ -418,11 +459,11 @@ private static IList ParseQuantiles(string quantileList)
{
continue;
}
- if (!double.TryParse(keyValParts[0], out double key))
+ if (!double.TryParse(keyValParts[0], NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double key))
{
continue;
}
- if (!double.TryParse(keyValParts[1], out double val))
+ if (!double.TryParse(keyValParts[1], NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double val))
{
continue;
}
diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/DiagnosticsEventPipeProcessor.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/DiagnosticsEventPipeProcessor.cs
index 7ebc6409b3..cff749c126 100644
--- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/DiagnosticsEventPipeProcessor.cs
+++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/DiagnosticsEventPipeProcessor.cs
@@ -39,7 +39,7 @@ Func, CancellationToken, Task> onEventSourceAva
_sessionStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
}
- public async Task Process(DiagnosticsClient client, TimeSpan duration, CancellationToken token)
+ public async Task Process(DiagnosticsClient client, TimeSpan duration, bool resumeRuntime, CancellationToken token)
{
//No need to guard against reentrancy here, since the calling pipeline does this already.
IDisposable registration = token.Register(() => TryCancelCompletionSources(token));
@@ -53,7 +53,7 @@ public async Task Process(DiagnosticsClient client, TimeSpan duration, Cancellat
// Allows the event handling routines to stop processing before the duration expires.
Func stopFunc = () => Task.Run(() => { streamProvider.StopProcessing(); });
- Stream sessionStream = await streamProvider.ProcessEvents(client, duration, token).ConfigureAwait(false);
+ Stream sessionStream = await streamProvider.ProcessEvents(client, duration, resumeRuntime, token).ConfigureAwait(false);
if (!_sessionStarted.TrySetResult(true))
{
diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/EventPipeStreamProvider.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/EventPipeStreamProvider.cs
index 45cf97be43..23bb51b28f 100644
--- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/EventPipeStreamProvider.cs
+++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/EventPipeStreamProvider.cs
@@ -21,7 +21,7 @@ public EventPipeStreamProvider(MonitoringSourceConfiguration sourceConfig)
_stopProcessingSource = new TaskCompletionSource