diff --git a/.github/workflows/native-aot.yml b/.github/workflows/native-aot.yml
new file mode 100644
index 0000000..71daf43
--- /dev/null
+++ b/.github/workflows/native-aot.yml
@@ -0,0 +1,42 @@
+name: Publish native AOT
+
+on:
+ push:
+ branches: [ native-aot ]
+ # pull_request:
+ # # Sequence of patterns matched against refs/heads
+ # branches:
+ # - master
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ env:
+ # temp fix frontend build
+ # Treating warnings as errors because process.env.CI = true.
+ # Most CI servers set it automatically.
+ CI: 'false'
+ steps:
+ - uses: actions/checkout@v3
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ global-json-file: global.json
+ - uses: actions/setup-node@v4
+ with:
+ # Version Spec of the version to use in SemVer notation.
+ # It also emits such aliases as lts, latest, nightly and canary builds
+ # Examples: 12.x, 10.15.1, >=10.15.0, lts/Hydrogen, 16-nightly, latest, node
+ node-version: 20
+ - name: Publish AOT version
+ shell: pwsh
+ run: |
+ ./scripts/publish-native-aot.ps1
+ - name: 'Upload Artifact'
+ uses: actions/upload-artifact@v4
+ with:
+ name: Heartbeat
+ path: artifacts/linux-x64/Heartbeat
+ retention-days: 1
+
+# \artifacts\win-x64\Heartbeat.exe
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
index 2ac07e6..f221cac 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -15,6 +15,10 @@
ClrMd diagnostics
Diagnostics utility to analyze memory dumps of a .NET application
+
+
+ Debug;Release;DebugOpenAPI;ReleaseAOT
+
diff --git a/Heartbeat.sln b/Heartbeat.sln
index 71d269e..9551b77 100644
--- a/Heartbeat.sln
+++ b/Heartbeat.sln
@@ -47,6 +47,8 @@ Global
Release|arm64 = Release|arm64
Release|x64 = Release|x64
Release|x86 = Release|x86
+ DebugOpenAPI|Any CPU = DebugOpenAPI|Any CPU
+ ReleaseAOT|Any CPU = ReleaseAOT|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{9E63F5A0-7695-474C-A946-64D75F8D9617}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -57,14 +59,6 @@ Global
{9E63F5A0-7695-474C-A946-64D75F8D9617}.Debug|x64.Build.0 = Debug|Any CPU
{9E63F5A0-7695-474C-A946-64D75F8D9617}.Debug|x86.ActiveCfg = Debug|Any CPU
{9E63F5A0-7695-474C-A946-64D75F8D9617}.Debug|x86.Build.0 = Debug|Any CPU
- {9E63F5A0-7695-474C-A946-64D75F8D9617}.DebugLocal|Any CPU.ActiveCfg = DebugLocal|Any CPU
- {9E63F5A0-7695-474C-A946-64D75F8D9617}.DebugLocal|Any CPU.Build.0 = DebugLocal|Any CPU
- {9E63F5A0-7695-474C-A946-64D75F8D9617}.DebugLocal|arm64.ActiveCfg = DebugLocal|Any CPU
- {9E63F5A0-7695-474C-A946-64D75F8D9617}.DebugLocal|arm64.Build.0 = DebugLocal|Any CPU
- {9E63F5A0-7695-474C-A946-64D75F8D9617}.DebugLocal|x64.ActiveCfg = DebugLocal|Any CPU
- {9E63F5A0-7695-474C-A946-64D75F8D9617}.DebugLocal|x64.Build.0 = DebugLocal|Any CPU
- {9E63F5A0-7695-474C-A946-64D75F8D9617}.DebugLocal|x86.ActiveCfg = DebugLocal|Any CPU
- {9E63F5A0-7695-474C-A946-64D75F8D9617}.DebugLocal|x86.Build.0 = DebugLocal|Any CPU
{9E63F5A0-7695-474C-A946-64D75F8D9617}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9E63F5A0-7695-474C-A946-64D75F8D9617}.Release|Any CPU.Build.0 = Release|Any CPU
{9E63F5A0-7695-474C-A946-64D75F8D9617}.Release|arm64.ActiveCfg = Release|Any CPU
@@ -73,6 +67,10 @@ Global
{9E63F5A0-7695-474C-A946-64D75F8D9617}.Release|x64.Build.0 = Release|Any CPU
{9E63F5A0-7695-474C-A946-64D75F8D9617}.Release|x86.ActiveCfg = Release|Any CPU
{9E63F5A0-7695-474C-A946-64D75F8D9617}.Release|x86.Build.0 = Release|Any CPU
+ {9E63F5A0-7695-474C-A946-64D75F8D9617}.DebugOpenAPI|Any CPU.ActiveCfg = DebugOpenAPI|Any CPU
+ {9E63F5A0-7695-474C-A946-64D75F8D9617}.DebugOpenAPI|Any CPU.Build.0 = DebugOpenAPI|Any CPU
+ {9E63F5A0-7695-474C-A946-64D75F8D9617}.ReleaseAOT|Any CPU.ActiveCfg = ReleaseAOT|Any CPU
+ {9E63F5A0-7695-474C-A946-64D75F8D9617}.ReleaseAOT|Any CPU.Build.0 = ReleaseAOT|Any CPU
{D4060CFE-8141-49CE-99A5-559599D0E6B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D4060CFE-8141-49CE-99A5-559599D0E6B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4060CFE-8141-49CE-99A5-559599D0E6B4}.Debug|arm64.ActiveCfg = Debug|Any CPU
@@ -81,14 +79,6 @@ Global
{D4060CFE-8141-49CE-99A5-559599D0E6B4}.Debug|x64.Build.0 = Debug|Any CPU
{D4060CFE-8141-49CE-99A5-559599D0E6B4}.Debug|x86.ActiveCfg = Debug|Any CPU
{D4060CFE-8141-49CE-99A5-559599D0E6B4}.Debug|x86.Build.0 = Debug|Any CPU
- {D4060CFE-8141-49CE-99A5-559599D0E6B4}.DebugLocal|Any CPU.ActiveCfg = DebugLocal|Any CPU
- {D4060CFE-8141-49CE-99A5-559599D0E6B4}.DebugLocal|Any CPU.Build.0 = DebugLocal|Any CPU
- {D4060CFE-8141-49CE-99A5-559599D0E6B4}.DebugLocal|arm64.ActiveCfg = DebugLocal|Any CPU
- {D4060CFE-8141-49CE-99A5-559599D0E6B4}.DebugLocal|arm64.Build.0 = DebugLocal|Any CPU
- {D4060CFE-8141-49CE-99A5-559599D0E6B4}.DebugLocal|x64.ActiveCfg = DebugLocal|Any CPU
- {D4060CFE-8141-49CE-99A5-559599D0E6B4}.DebugLocal|x64.Build.0 = DebugLocal|Any CPU
- {D4060CFE-8141-49CE-99A5-559599D0E6B4}.DebugLocal|x86.ActiveCfg = DebugLocal|Any CPU
- {D4060CFE-8141-49CE-99A5-559599D0E6B4}.DebugLocal|x86.Build.0 = DebugLocal|Any CPU
{D4060CFE-8141-49CE-99A5-559599D0E6B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4060CFE-8141-49CE-99A5-559599D0E6B4}.Release|Any CPU.Build.0 = Release|Any CPU
{D4060CFE-8141-49CE-99A5-559599D0E6B4}.Release|arm64.ActiveCfg = Release|Any CPU
@@ -97,6 +87,10 @@ Global
{D4060CFE-8141-49CE-99A5-559599D0E6B4}.Release|x64.Build.0 = Release|Any CPU
{D4060CFE-8141-49CE-99A5-559599D0E6B4}.Release|x86.ActiveCfg = Release|Any CPU
{D4060CFE-8141-49CE-99A5-559599D0E6B4}.Release|x86.Build.0 = Release|Any CPU
+ {D4060CFE-8141-49CE-99A5-559599D0E6B4}.DebugOpenAPI|Any CPU.ActiveCfg = DebugOpenAPI|Any CPU
+ {D4060CFE-8141-49CE-99A5-559599D0E6B4}.DebugOpenAPI|Any CPU.Build.0 = DebugOpenAPI|Any CPU
+ {D4060CFE-8141-49CE-99A5-559599D0E6B4}.ReleaseAOT|Any CPU.ActiveCfg = ReleaseAOT|Any CPU
+ {D4060CFE-8141-49CE-99A5-559599D0E6B4}.ReleaseAOT|Any CPU.Build.0 = ReleaseAOT|Any CPU
{AC8E6790-14D5-42C5-AF51-98E8EB80644F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AC8E6790-14D5-42C5-AF51-98E8EB80644F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AC8E6790-14D5-42C5-AF51-98E8EB80644F}.Debug|arm64.ActiveCfg = Debug|Any CPU
@@ -121,6 +115,8 @@ Global
{AC8E6790-14D5-42C5-AF51-98E8EB80644F}.Release|x64.Build.0 = Release|Any CPU
{AC8E6790-14D5-42C5-AF51-98E8EB80644F}.Release|x86.ActiveCfg = Release|Any CPU
{AC8E6790-14D5-42C5-AF51-98E8EB80644F}.Release|x86.Build.0 = Release|Any CPU
+ {AC8E6790-14D5-42C5-AF51-98E8EB80644F}.DebugOpenAPI|Any CPU.ActiveCfg = DebugOpenAPI|Any CPU
+ {AC8E6790-14D5-42C5-AF51-98E8EB80644F}.ReleaseAOT|Any CPU.ActiveCfg = ReleaseAOT|Any CPU
{789E65CA-B8F7-47B9-9013-B159D1E93F36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{789E65CA-B8F7-47B9-9013-B159D1E93F36}.Debug|Any CPU.Build.0 = Debug|Any CPU
{789E65CA-B8F7-47B9-9013-B159D1E93F36}.Debug|arm64.ActiveCfg = Debug|Any CPU
@@ -145,6 +141,8 @@ Global
{789E65CA-B8F7-47B9-9013-B159D1E93F36}.Release|x64.Build.0 = Release|Any CPU
{789E65CA-B8F7-47B9-9013-B159D1E93F36}.Release|x86.ActiveCfg = Release|Any CPU
{789E65CA-B8F7-47B9-9013-B159D1E93F36}.Release|x86.Build.0 = Release|Any CPU
+ {789E65CA-B8F7-47B9-9013-B159D1E93F36}.DebugOpenAPI|Any CPU.ActiveCfg = DebugOpenAPI|Any CPU
+ {789E65CA-B8F7-47B9-9013-B159D1E93F36}.ReleaseAOT|Any CPU.ActiveCfg = ReleaseAOT|Any CPU
{F1FF76E5-3DEE-4C64-8A62-8A645B981D1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F1FF76E5-3DEE-4C64-8A62-8A645B981D1D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F1FF76E5-3DEE-4C64-8A62-8A645B981D1D}.Debug|arm64.ActiveCfg = Debug|Any CPU
@@ -169,6 +167,8 @@ Global
{F1FF76E5-3DEE-4C64-8A62-8A645B981D1D}.Release|x64.Build.0 = Release|Any CPU
{F1FF76E5-3DEE-4C64-8A62-8A645B981D1D}.Release|x86.ActiveCfg = Release|Any CPU
{F1FF76E5-3DEE-4C64-8A62-8A645B981D1D}.Release|x86.Build.0 = Release|Any CPU
+ {F1FF76E5-3DEE-4C64-8A62-8A645B981D1D}.DebugOpenAPI|Any CPU.ActiveCfg = DebugOpenAPI|Any CPU
+ {F1FF76E5-3DEE-4C64-8A62-8A645B981D1D}.ReleaseAOT|Any CPU.ActiveCfg = ReleaseAOT|Any CPU
{3D11554D-E09C-4710-B071-D90BB2447F46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3D11554D-E09C-4710-B071-D90BB2447F46}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3D11554D-E09C-4710-B071-D90BB2447F46}.Debug|arm64.ActiveCfg = Debug|Any CPU
@@ -193,6 +193,8 @@ Global
{3D11554D-E09C-4710-B071-D90BB2447F46}.Release|x64.Build.0 = Release|Any CPU
{3D11554D-E09C-4710-B071-D90BB2447F46}.Release|x86.ActiveCfg = Release|Any CPU
{3D11554D-E09C-4710-B071-D90BB2447F46}.Release|x86.Build.0 = Release|Any CPU
+ {3D11554D-E09C-4710-B071-D90BB2447F46}.DebugOpenAPI|Any CPU.ActiveCfg = DebugOpenAPI|Any CPU
+ {3D11554D-E09C-4710-B071-D90BB2447F46}.ReleaseAOT|Any CPU.ActiveCfg = ReleaseAOT|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/README.md b/README.md
index 662973c..d2d4918 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# Heartbeat
[![NuGet Badge](https://buildstats.info/nuget/heartbeat?includePreReleases=true&dWidth=0)](https://www.nuget.org/packages/Heartbeat/)
-Diagnostics utility with web UI to analyze memory dumps of a .NET application
+Diagnostics utility with web UI to analyze memory dumps of .NET application
## Getting started
diff --git a/scripts/publish-native-aot.ps1 b/scripts/publish-native-aot.ps1
new file mode 100644
index 0000000..8d6e549
--- /dev/null
+++ b/scripts/publish-native-aot.ps1
@@ -0,0 +1,69 @@
+$ErrorActionPreference = "Stop"
+
+function Get-Runtime {
+ if ($IsWindows) {
+ return $env:PROCESSOR_ARCHITECTURE -eq 'AMD64' ? 'win-x64' : 'win-arm64'
+ }
+
+ if ($IsLinux) {
+ return (uname -m) -eq 'aarch64' ? 'linux-arm64' : 'linux-x64'
+ }
+
+ if ($IsMacOS) {
+ return (uname -m) -eq 'arm64' ? 'osx-arm64' : 'osx-x64'
+ }
+}
+
+function Assert-ExitCode {
+ if (-not $?) {
+ throw 'Latest command failed'
+ }
+}
+
+$Configuration = 'ReleaseAOT'
+$RepositoryRoot = Split-Path $PSScriptRoot
+$ArtifactsRoot = Join-Path $RepositoryRoot 'artifacts'
+
+Push-Location
+try {
+ Set-Location $RepositoryRoot
+
+ # [xml]$XmlConfig = Get-Content 'Directory.Build.props'
+
+ # $XmlElement = Select-Xml '/Project/PropertyGroup/VersionPrefix' $XmlConfig |
+ # Select-Object -ExpandProperty Node
+
+ # $VersionPrefix = $XmlElement.InnerText
+ # $VersionSuffix = "rc.$(Get-Date -Format 'yyyy-MM-dd-HHmm')"
+ # $PackageVersion = "$VersionPrefix-$VersionSuffix"
+
+ dotnet clean --configuration $Configuration
+
+ # https://learn.microsoft.com/en-us/dotnet/core/rid-catalog
+ # $Runtimes = @('win-x64', 'win-arm64', 'linux-x64', 'linux-arm64', 'osx-x64', 'osx-arm64')
+ $Runtimes = @(Get-Runtime)
+ # TODO check `uname -m` for linux and macos
+ # $env:PROCESSOR_ARCHITECTURE
+ foreach ($Runtime in $Runtimes) {
+ $OutDir = Join-Path $ArtifactsRoot $Runtime
+ Write-Host "Publish AOT version for $Runtime to $OutDir"
+ dotnet publish --configuration $Configuration --runtime $Runtime --output $OutDir
+ Assert-ExitCode
+
+ Write-Host "Files in $OutDir"
+ Get-ChildItem $OutDir
+ }
+
+ # TODO zip?
+}
+catch {
+ Write-Host 'Publish AOT - FAILED!' -ForegroundColor Red
+ throw
+}
+finally {
+ Pop-Location
+}
+
+# TODO Cross-OS native compilation
+# C:\Users\Ne4to\.nuget\packages\microsoft.dotnet.ilcompiler\8.0.1\build\Microsoft.NETCore.Native.Windows.targets(123,5): error : Platform linker not found. Ensure you have all the required prerequisites documented at https://aka.ms/nativeaot-prerequisites, in particular the Desktop Development for C++ workload in Visual Studio. For ARM64 development also install C++ ARM64 build tools. [C:\Users\Ne4to\projects\github.com\Ne4to\Heartbeat\src\Heartbeat\Heartbeat.csproj]
+# C:\Users\Ne4to\.nuget\packages\microsoft.dotnet.ilcompiler\8.0.1\build\Microsoft.NETCore.Native.Publish.targets(60,5): error : Cross-OS native compilation is not supported. [C:\Users\Ne4to\projects\github.com\Ne4to\Heartbeat\src\Heartbeat\Heartbeat.csproj]
\ No newline at end of file
diff --git a/scripts/reinstall-dev-tool.ps1 b/scripts/reinstall-dev-tool.ps1
index 07aa9e4..4f97073 100644
--- a/scripts/reinstall-dev-tool.ps1
+++ b/scripts/reinstall-dev-tool.ps1
@@ -1,5 +1,11 @@
$ErrorActionPreference = "Stop"
+function Assert-ExitCode {
+ if (-not $?) {
+ throw 'Latest command failed'
+ }
+}
+
$RepositoryRoot = Split-Path $PSScriptRoot
Push-Location
@@ -16,12 +22,14 @@ try
dotnet tool uninstall -g Heartbeat
dotnet clean --configuration Release
- Get-Date -Format ''
$VersionSuffix = "rc.$(Get-Date -Format 'yyyy-MM-dd-HHmm')"
- dotnet publish --runtime win-x64
- dotnet pack --runtime win-x64 --version-suffix $VersionSuffix
-# $PackageVersion = "$VersionPrefix-$VersionSuffix"
-# dotnet tool install --global --add-source ./src/Heartbeat/nupkg Heartbeat --version $PackageVersion
+ dotnet publish
+ Assert-ExitCode
+ dotnet pack --version-suffix $VersionSuffix
+ Assert-ExitCode
+ $PackageVersion = "$VersionPrefix-$VersionSuffix"
+ dotnet tool install --global --add-source ./src/Heartbeat/nupkg Heartbeat --version $PackageVersion
+ Assert-ExitCode
}
catch {
Write-Host 'Install global tool - FAILED!' -ForegroundColor Red
@@ -29,4 +37,7 @@ catch {
}
finally {
Pop-Location
-}
\ No newline at end of file
+}
+
+
+
diff --git a/scripts/update-ts-client.ps1 b/scripts/update-ts-client.ps1
index 718eaab..e2d4e9c 100644
--- a/scripts/update-ts-client.ps1
+++ b/scripts/update-ts-client.ps1
@@ -1,9 +1,10 @@
$ErrorActionPreference = "Stop"
+$Configuration = 'DebugOpenAPI'
$RepositoryRoot = Split-Path $PSScriptRoot
$FrontendRoot = Join-Path $RepositoryRoot 'src/Heartbeat/ClientApp'
$ContractPath = Join-Path $FrontendRoot 'api.yml'
-$DllPath = Join-Path $RepositoryRoot 'src/Heartbeat/bin/Debug/net8.0/Heartbeat.dll'
+$DllPath = Join-Path $RepositoryRoot "src/Heartbeat/bin/$Configuration/net8.0/Heartbeat.dll"
Push-Location
try
@@ -11,10 +12,9 @@ try
Set-Location $RepositoryRoot
dotnet tool restore
- dotnet build --configuration Debug
-
+ dotnet build --configuration $Configuration
+
Set-Location $FrontendRoot
- $env:HEARTBEAT_GENERATE_CONTRACTS = 'true'
dotnet swagger tofile --yaml --output $ContractPath $DllPath Heartbeat
dotnet kiota generate -l typescript --openapi $ContractPath -c HeartbeatClient -o ./src/client --clean-output
@@ -26,5 +26,4 @@ catch {
}
finally {
Pop-Location
- $env:HEARTBEAT_GENERATE_CONTRACTS = $null
}
\ No newline at end of file
diff --git a/src/Heartbeat.Runtime/Heartbeat.Runtime.csproj b/src/Heartbeat.Runtime/Heartbeat.Runtime.csproj
index 5c1d383..f29ff4c 100644
--- a/src/Heartbeat.Runtime/Heartbeat.Runtime.csproj
+++ b/src/Heartbeat.Runtime/Heartbeat.Runtime.csproj
@@ -4,8 +4,6 @@
- true
- true
true
@@ -17,8 +15,6 @@
-
-
all
diff --git a/src/Heartbeat/ClientApp/api.yml b/src/Heartbeat/ClientApp/api.yml
index 1a3c09c..97c4ce8 100644
--- a/src/Heartbeat/ClientApp/api.yml
+++ b/src/Heartbeat/ClientApp/api.yml
@@ -12,68 +12,64 @@ paths:
get:
tags:
- Dump
- summary: Get dump info
operationId: GetInfo
responses:
- '500':
- description: Server Error
+ '200':
+ description: OK
content:
application/json:
schema:
- $ref: '#/components/schemas/ProblemDetails'
- '200':
- description: Success
+ $ref: '#/components/schemas/DumpInfo'
+ '500':
+ description: Internal Server Error
content:
application/json:
schema:
- $ref: '#/components/schemas/DumpInfo'
+ $ref: '#/components/schemas/ProblemDetails'
/api/dump/modules:
get:
tags:
- Dump
- summary: Get modules
operationId: GetModules
responses:
- '500':
- description: Server Error
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ProblemDetails'
'200':
- description: Success
+ description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Module'
+ '500':
+ description: Internal Server Error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetails'
/api/dump/segments:
get:
tags:
- Dump
- summary: Get segments
operationId: GetSegments
responses:
- '500':
- description: Server Error
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ProblemDetails'
'200':
- description: Success
+ description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/HeapSegment'
+ '500':
+ description: Internal Server Error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetails'
/api/dump/roots:
get:
tags:
- Dump
- summary: Get heap roots
operationId: GetRoots
parameters:
- name: kind
@@ -82,25 +78,24 @@ paths:
schema:
$ref: '#/components/schemas/ClrRootKind'
responses:
- '500':
- description: Server Error
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ProblemDetails'
'200':
- description: Success
+ description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/RootInfo'
+ '500':
+ description: Internal Server Error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetails'
/api/dump/heap-dump-statistics:
get:
tags:
- Dump
- summary: Get heap dump statistics
operationId: GetHeapDumpStat
parameters:
- name: gcStatus
@@ -114,25 +109,24 @@ paths:
schema:
$ref: '#/components/schemas/Generation'
responses:
- '500':
- description: Server Error
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ProblemDetails'
'200':
- description: Success
+ description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/ObjectTypeStatistics'
+ '500':
+ description: Internal Server Error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetails'
/api/dump/strings:
get:
tags:
- Dump
- summary: Get heap dump statistics
operationId: GetStrings
parameters:
- name: gcStatus
@@ -146,25 +140,24 @@ paths:
schema:
$ref: '#/components/schemas/Generation'
responses:
- '500':
- description: Server Error
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ProblemDetails'
'200':
- description: Success
+ description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/StringInfo'
+ '500':
+ description: Internal Server Error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetails'
/api/dump/string-duplicates:
get:
tags:
- Dump
- summary: Get string duplicates
operationId: GetStringDuplicates
parameters:
- name: gcStatus
@@ -178,25 +171,24 @@ paths:
schema:
$ref: '#/components/schemas/Generation'
responses:
- '500':
- description: Server Error
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ProblemDetails'
'200':
- description: Success
+ description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/StringDuplicate'
+ '500':
+ description: Internal Server Error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetails'
'/api/dump/object-instances/{mt}':
get:
tags:
- Dump
- summary: Get object instances
operationId: GetObjectInstances
parameters:
- name: mt
@@ -217,23 +209,22 @@ paths:
schema:
$ref: '#/components/schemas/Generation'
responses:
- '500':
- description: Server Error
+ '200':
+ description: OK
content:
application/json:
schema:
- $ref: '#/components/schemas/ProblemDetails'
- '200':
- description: Success
+ $ref: '#/components/schemas/GetObjectInstancesResult'
+ '500':
+ description: Internal Server Error
content:
application/json:
schema:
- $ref: '#/components/schemas/GetObjectInstancesResult'
+ $ref: '#/components/schemas/ProblemDetails'
/api/dump/arrays/sparse:
get:
tags:
- Dump
- summary: Get sparse arrays
operationId: GetSparseArrays
parameters:
- name: gcStatus
@@ -247,25 +238,24 @@ paths:
schema:
$ref: '#/components/schemas/Generation'
responses:
- '500':
- description: Server Error
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ProblemDetails'
'200':
- description: Success
+ description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/ArrayInfo'
+ '500':
+ description: Internal Server Error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetails'
/api/dump/arrays/sparse/stat:
get:
tags:
- Dump
- summary: Get arrays
operationId: GetSparseArraysStat
parameters:
- name: gcStatus
@@ -279,25 +269,24 @@ paths:
schema:
$ref: '#/components/schemas/Generation'
responses:
- '500':
- description: Server Error
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ProblemDetails'
'200':
- description: Success
+ description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/SparseArrayStatistics'
+ '500':
+ description: Internal Server Error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetails'
'/api/dump/object/{address}':
get:
tags:
- Dump
- summary: Get object
operationId: GetClrObject
parameters:
- name: address
@@ -308,14 +297,8 @@ paths:
type: integer
format: int64
responses:
- '500':
- description: Server Error
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ProblemDetails'
'200':
- description: Success
+ description: OK
content:
application/json:
schema:
@@ -326,11 +309,16 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/ProblemDetails'
+ '500':
+ description: Internal Server Error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetails'
'/api/dump/object/{address}/fields':
get:
tags:
- Dump
- summary: Get object fields
operationId: GetClrObjectFields
parameters:
- name: address
@@ -341,14 +329,8 @@ paths:
type: integer
format: int64
responses:
- '500':
- description: Server Error
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ProblemDetails'
'200':
- description: Success
+ description: OK
content:
application/json:
schema:
@@ -361,11 +343,16 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/ProblemDetails'
+ '500':
+ description: Internal Server Error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetails'
'/api/dump/object/{address}/roots':
get:
tags:
- Dump
- summary: Get object roots
operationId: GetClrObjectRoots
parameters:
- name: address
@@ -376,14 +363,8 @@ paths:
type: integer
format: int64
responses:
- '500':
- description: Server Error
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ProblemDetails'
'200':
- description: Success
+ description: OK
content:
application/json:
schema:
@@ -396,6 +377,12 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/ProblemDetails'
+ '500':
+ description: Internal Server Error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetails'
components:
schemas:
Architecture:
diff --git a/src/Heartbeat/ClientApp/src/client/api/dump/arrays/sparse/index.ts b/src/Heartbeat/ClientApp/src/client/api/dump/arrays/sparse/index.ts
index 66a3615..de12874 100644
--- a/src/Heartbeat/ClientApp/src/client/api/dump/arrays/sparse/index.ts
+++ b/src/Heartbeat/ClientApp/src/client/api/dump/arrays/sparse/index.ts
@@ -28,7 +28,6 @@ export class SparseRequestBuilder extends BaseRequestBuilder new SparseRequestBuilder(x, y));
}
/**
- * Get sparse arrays
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a Promise of ArrayInfo
*/
@@ -42,7 +41,6 @@ export class SparseRequestBuilder extends BaseRequestBuilder(requestInfo, createArrayInfoFromDiscriminatorValue, errorMapping);
}
/**
- * Get sparse arrays
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a RequestInformation
*/
diff --git a/src/Heartbeat/ClientApp/src/client/api/dump/arrays/sparse/stat/index.ts b/src/Heartbeat/ClientApp/src/client/api/dump/arrays/sparse/stat/index.ts
index 904f308..d653a82 100644
--- a/src/Heartbeat/ClientApp/src/client/api/dump/arrays/sparse/stat/index.ts
+++ b/src/Heartbeat/ClientApp/src/client/api/dump/arrays/sparse/stat/index.ts
@@ -21,7 +21,6 @@ export class StatRequestBuilder extends BaseRequestBuilder {
super(pathParameters, requestAdapter, "{+baseurl}/api/dump/arrays/sparse/stat{?gcStatus*,generation*}", (x, y) => new StatRequestBuilder(x, y));
}
/**
- * Get arrays
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a Promise of SparseArrayStatistics
*/
@@ -35,7 +34,6 @@ export class StatRequestBuilder extends BaseRequestBuilder {
return this.requestAdapter.sendCollectionAsync(requestInfo, createSparseArrayStatisticsFromDiscriminatorValue, errorMapping);
}
/**
- * Get arrays
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a RequestInformation
*/
diff --git a/src/Heartbeat/ClientApp/src/client/api/dump/heapDumpStatistics/index.ts b/src/Heartbeat/ClientApp/src/client/api/dump/heapDumpStatistics/index.ts
index a840707..e4f268b 100644
--- a/src/Heartbeat/ClientApp/src/client/api/dump/heapDumpStatistics/index.ts
+++ b/src/Heartbeat/ClientApp/src/client/api/dump/heapDumpStatistics/index.ts
@@ -21,7 +21,6 @@ export class HeapDumpStatisticsRequestBuilder extends BaseRequestBuilder new HeapDumpStatisticsRequestBuilder(x, y));
}
/**
- * Get heap dump statistics
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a Promise of ObjectTypeStatistics
*/
@@ -35,7 +34,6 @@ export class HeapDumpStatisticsRequestBuilder extends BaseRequestBuilder(requestInfo, createObjectTypeStatisticsFromDiscriminatorValue, errorMapping);
}
/**
- * Get heap dump statistics
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a RequestInformation
*/
diff --git a/src/Heartbeat/ClientApp/src/client/api/dump/info/index.ts b/src/Heartbeat/ClientApp/src/client/api/dump/info/index.ts
index 145d642..39b2050 100644
--- a/src/Heartbeat/ClientApp/src/client/api/dump/info/index.ts
+++ b/src/Heartbeat/ClientApp/src/client/api/dump/info/index.ts
@@ -17,7 +17,6 @@ export class InfoRequestBuilder extends BaseRequestBuilder {
super(pathParameters, requestAdapter, "{+baseurl}/api/dump/info", (x, y) => new InfoRequestBuilder(x, y));
}
/**
- * Get dump info
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a Promise of DumpInfo
*/
@@ -31,7 +30,6 @@ export class InfoRequestBuilder extends BaseRequestBuilder {
return this.requestAdapter.sendAsync(requestInfo, createDumpInfoFromDiscriminatorValue, errorMapping);
}
/**
- * Get dump info
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a RequestInformation
*/
diff --git a/src/Heartbeat/ClientApp/src/client/api/dump/modules/index.ts b/src/Heartbeat/ClientApp/src/client/api/dump/modules/index.ts
index b42906c..e0bb107 100644
--- a/src/Heartbeat/ClientApp/src/client/api/dump/modules/index.ts
+++ b/src/Heartbeat/ClientApp/src/client/api/dump/modules/index.ts
@@ -17,7 +17,6 @@ export class ModulesRequestBuilder extends BaseRequestBuilder new ModulesRequestBuilder(x, y));
}
/**
- * Get modules
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a Promise of Module
*/
@@ -31,7 +30,6 @@ export class ModulesRequestBuilder extends BaseRequestBuilder(requestInfo, createModuleFromDiscriminatorValue, errorMapping);
}
/**
- * Get modules
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a RequestInformation
*/
diff --git a/src/Heartbeat/ClientApp/src/client/api/dump/object/item/fields/index.ts b/src/Heartbeat/ClientApp/src/client/api/dump/object/item/fields/index.ts
index 1d7c701..41ca43c 100644
--- a/src/Heartbeat/ClientApp/src/client/api/dump/object/item/fields/index.ts
+++ b/src/Heartbeat/ClientApp/src/client/api/dump/object/item/fields/index.ts
@@ -17,7 +17,6 @@ export class FieldsRequestBuilder extends BaseRequestBuilder new FieldsRequestBuilder(x, y));
}
/**
- * Get object fields
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a Promise of ClrObjectField
*/
@@ -32,7 +31,6 @@ export class FieldsRequestBuilder extends BaseRequestBuilder(requestInfo, createClrObjectFieldFromDiscriminatorValue, errorMapping);
}
/**
- * Get object fields
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a RequestInformation
*/
diff --git a/src/Heartbeat/ClientApp/src/client/api/dump/object/item/index.ts b/src/Heartbeat/ClientApp/src/client/api/dump/object/item/index.ts
index b274c22..547c321 100644
--- a/src/Heartbeat/ClientApp/src/client/api/dump/object/item/index.ts
+++ b/src/Heartbeat/ClientApp/src/client/api/dump/object/item/index.ts
@@ -31,7 +31,6 @@ export class WithAddressItemRequestBuilder extends BaseRequestBuilder new WithAddressItemRequestBuilder(x, y));
}
/**
- * Get object
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a Promise of GetClrObjectResult
*/
@@ -46,7 +45,6 @@ export class WithAddressItemRequestBuilder extends BaseRequestBuilder(requestInfo, createGetClrObjectResultFromDiscriminatorValue, errorMapping);
}
/**
- * Get object
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a RequestInformation
*/
diff --git a/src/Heartbeat/ClientApp/src/client/api/dump/object/item/roots/index.ts b/src/Heartbeat/ClientApp/src/client/api/dump/object/item/roots/index.ts
index c224129..d6f83af 100644
--- a/src/Heartbeat/ClientApp/src/client/api/dump/object/item/roots/index.ts
+++ b/src/Heartbeat/ClientApp/src/client/api/dump/object/item/roots/index.ts
@@ -17,7 +17,6 @@ export class RootsRequestBuilder extends BaseRequestBuilder
super(pathParameters, requestAdapter, "{+baseurl}/api/dump/object/{address}/roots", (x, y) => new RootsRequestBuilder(x, y));
}
/**
- * Get object roots
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a Promise of ClrObjectRootPath
*/
@@ -32,7 +31,6 @@ export class RootsRequestBuilder extends BaseRequestBuilder
return this.requestAdapter.sendCollectionAsync(requestInfo, createClrObjectRootPathFromDiscriminatorValue, errorMapping);
}
/**
- * Get object roots
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a RequestInformation
*/
diff --git a/src/Heartbeat/ClientApp/src/client/api/dump/objectInstances/item/index.ts b/src/Heartbeat/ClientApp/src/client/api/dump/objectInstances/item/index.ts
index 42321c2..45b0bb8 100644
--- a/src/Heartbeat/ClientApp/src/client/api/dump/objectInstances/item/index.ts
+++ b/src/Heartbeat/ClientApp/src/client/api/dump/objectInstances/item/index.ts
@@ -21,7 +21,6 @@ export class WithMtItemRequestBuilder extends BaseRequestBuilder new WithMtItemRequestBuilder(x, y));
}
/**
- * Get object instances
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a Promise of GetObjectInstancesResult
*/
@@ -35,7 +34,6 @@ export class WithMtItemRequestBuilder extends BaseRequestBuilder(requestInfo, createGetObjectInstancesResultFromDiscriminatorValue, errorMapping);
}
/**
- * Get object instances
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a RequestInformation
*/
diff --git a/src/Heartbeat/ClientApp/src/client/api/dump/roots/index.ts b/src/Heartbeat/ClientApp/src/client/api/dump/roots/index.ts
index e78f8d2..53001bc 100644
--- a/src/Heartbeat/ClientApp/src/client/api/dump/roots/index.ts
+++ b/src/Heartbeat/ClientApp/src/client/api/dump/roots/index.ts
@@ -20,7 +20,6 @@ export class RootsRequestBuilder extends BaseRequestBuilder
super(pathParameters, requestAdapter, "{+baseurl}/api/dump/roots{?kind*}", (x, y) => new RootsRequestBuilder(x, y));
}
/**
- * Get heap roots
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a Promise of RootInfo
*/
@@ -34,7 +33,6 @@ export class RootsRequestBuilder extends BaseRequestBuilder
return this.requestAdapter.sendCollectionAsync(requestInfo, createRootInfoFromDiscriminatorValue, errorMapping);
}
/**
- * Get heap roots
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a RequestInformation
*/
diff --git a/src/Heartbeat/ClientApp/src/client/api/dump/segments/index.ts b/src/Heartbeat/ClientApp/src/client/api/dump/segments/index.ts
index 3fcacc2..24adb7a 100644
--- a/src/Heartbeat/ClientApp/src/client/api/dump/segments/index.ts
+++ b/src/Heartbeat/ClientApp/src/client/api/dump/segments/index.ts
@@ -17,7 +17,6 @@ export class SegmentsRequestBuilder extends BaseRequestBuilder new SegmentsRequestBuilder(x, y));
}
/**
- * Get segments
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a Promise of HeapSegment
*/
@@ -31,7 +30,6 @@ export class SegmentsRequestBuilder extends BaseRequestBuilder(requestInfo, createHeapSegmentFromDiscriminatorValue, errorMapping);
}
/**
- * Get segments
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a RequestInformation
*/
diff --git a/src/Heartbeat/ClientApp/src/client/api/dump/stringDuplicates/index.ts b/src/Heartbeat/ClientApp/src/client/api/dump/stringDuplicates/index.ts
index 9297487..433614f 100644
--- a/src/Heartbeat/ClientApp/src/client/api/dump/stringDuplicates/index.ts
+++ b/src/Heartbeat/ClientApp/src/client/api/dump/stringDuplicates/index.ts
@@ -21,7 +21,6 @@ export class StringDuplicatesRequestBuilder extends BaseRequestBuilder new StringDuplicatesRequestBuilder(x, y));
}
/**
- * Get string duplicates
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a Promise of StringDuplicate
*/
@@ -35,7 +34,6 @@ export class StringDuplicatesRequestBuilder extends BaseRequestBuilder(requestInfo, createStringDuplicateFromDiscriminatorValue, errorMapping);
}
/**
- * Get string duplicates
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a RequestInformation
*/
diff --git a/src/Heartbeat/ClientApp/src/client/api/dump/strings/index.ts b/src/Heartbeat/ClientApp/src/client/api/dump/strings/index.ts
index 01bb66e..f259d74 100644
--- a/src/Heartbeat/ClientApp/src/client/api/dump/strings/index.ts
+++ b/src/Heartbeat/ClientApp/src/client/api/dump/strings/index.ts
@@ -21,7 +21,6 @@ export class StringsRequestBuilder extends BaseRequestBuilder new StringsRequestBuilder(x, y));
}
/**
- * Get heap dump statistics
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a Promise of StringInfo
*/
@@ -35,7 +34,6 @@ export class StringsRequestBuilder extends BaseRequestBuilder(requestInfo, createStringInfoFromDiscriminatorValue, errorMapping);
}
/**
- * Get heap dump statistics
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a RequestInformation
*/
diff --git a/src/Heartbeat/ClientApp/src/client/kiota-lock.json b/src/Heartbeat/ClientApp/src/client/kiota-lock.json
index 1671570..4104768 100644
--- a/src/Heartbeat/ClientApp/src/client/kiota-lock.json
+++ b/src/Heartbeat/ClientApp/src/client/kiota-lock.json
@@ -1,5 +1,5 @@
{
- "descriptionHash": "E058DCE3BA746E408EADE32EA9E442AFC9D72743F2398CD19A5CCA613B92AB688674BFB60B624AA3AD06461E16CA4C2EBC6F9C5C62315B4F01C073DEF44C1C6C",
+ "descriptionHash": "2680CD432C0BAF2EAD8335B87589C6613B8A32E7D90AAF6EC468A74F00CDC479E9320B872E8B6E0C6AE5CDD8A0F2251B0991E07026BB56A11771AE55760D7F36",
"descriptionLocation": "..\\..\\api.yml",
"lockFileVersion": "1.0.0",
"kiotaVersion": "1.10.1",
diff --git a/src/Heartbeat/Controllers/DumpController.cs b/src/Heartbeat/Controllers/DumpController.cs
index fc96a50..1240e35 100644
--- a/src/Heartbeat/Controllers/DumpController.cs
+++ b/src/Heartbeat/Controllers/DumpController.cs
@@ -1,432 +1,432 @@
-using Heartbeat.Domain;
-using Heartbeat.Runtime;
-using Heartbeat.Runtime.Analyzers;
-using Heartbeat.Runtime.Domain;
-using Heartbeat.Runtime.Extensions;
-using Heartbeat.Runtime.Proxies;
-
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.OutputCaching;
-using Microsoft.Diagnostics.Runtime;
-
-using Swashbuckle.AspNetCore.Annotations;
-
-using System.Globalization;
-using System.Net.Mime;
-
-namespace Heartbeat.Host.Controllers;
-
-[ApiController]
-[OutputCache]
-[Route("api/dump")]
-[ApiExplorerSettings(GroupName = "Heartbeat")]
-[Consumes(MediaTypeNames.Application.Json)]
-[Produces(MediaTypeNames.Application.Json)]
-[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
-public class DumpController : ControllerBase
-{
- private readonly RuntimeContext _context;
-
- public DumpController(RuntimeContext context)
- {
- _context = context;
- }
-
- [HttpGet]
- [Route("info")]
- [ProducesResponseType(typeof(DumpInfo), StatusCodes.Status200OK)]
- [SwaggerOperation(summary: "Get dump info")]
- public DumpInfo GetInfo()
- {
- var clrHeap = _context.Heap;
- var clrInfo = _context.Runtime.ClrInfo;
- var dataReader = clrInfo.DataTarget.DataReader;
-
- return new DumpInfo(
- _context.DumpPath,
- clrHeap.CanWalkHeap,
- clrHeap.IsServer,
- clrInfo.ModuleInfo.FileName,
- dataReader.Architecture,
- dataReader.ProcessId,
- dataReader.TargetPlatform.ToString(),
- clrInfo.Version.ToString(2)
- );
- }
-
- [HttpGet]
- [Route("modules")]
- [ProducesResponseType(typeof(Module[]), StatusCodes.Status200OK)]
- [SwaggerOperation(summary: "Get modules")]
- public IEnumerable GetModules()
- {
- var modules = _context.Runtime
- .EnumerateModules()
- .Select(m => new Module(m.Address, m.Size, m.Name))
- .ToArray();
-
- return modules;
- }
-
- [HttpGet]
- [Route("segments")]
- [ProducesResponseType(typeof(HeapSegment[]), StatusCodes.Status200OK)]
- [SwaggerOperation(summary: "Get segments")]
- public IEnumerable GetSegments()
- {
- var segments =
- from s in _context.Heap.Segments
- select new HeapSegment(
- s.Start,
- s.End,
- s.Kind);
-
- return segments;
- }
-
- [HttpGet]
- [Route("roots")]
- [ProducesResponseType(typeof(RootInfo[]), StatusCodes.Status200OK)]
- [SwaggerOperation(summary: "Get heap roots")]
- public IEnumerable GetRoots([FromQuery] ClrRootKind? kind = null)
- {
- return
- from root in _context.Heap.EnumerateRoots()
- where kind == null || root.RootKind == kind
- let objectType = root.Object.Type
- select new RootInfo(
- root.Object.Address,
- root.RootKind,
- root.IsPinned,
- root.Object.Size,
- objectType.MethodTable,
- objectType.Name
- );
- }
-
- [HttpGet]
- [Route("heap-dump-statistics")]
- [ProducesResponseType(typeof(ObjectTypeStatistics[]), StatusCodes.Status200OK)]
- [SwaggerOperation(summary: "Get heap dump statistics")]
- public IEnumerable GetHeapDumpStat(
- [FromQuery] ObjectGCStatus? gcStatus = null,
- [FromQuery] Generation? generation = null)
- // TODO filter by just my code - how to filter Action?
- // TODO filter by type name
- {
- var analyzer = new HeapDumpStatisticsAnalyzer(_context) { ObjectGcStatus = gcStatus, Generation = generation };
-
- var statistics = analyzer.GetObjectTypeStatistics()
- .OrderByDescending(s => s.TotalSize)
- .Select(s => new ObjectTypeStatistics(s.MethodTable, s.TypeName, s.TotalSize, s.InstanceCount))
- .ToArray();
-
- return statistics;
- }
-
- [HttpGet]
- [Route("strings")]
- [ProducesResponseType(typeof(StringInfo[]), StatusCodes.Status200OK)]
- [SwaggerOperation(summary: "Get heap dump statistics")]
- public IEnumerable GetStrings(
- [FromQuery] ObjectGCStatus? gcStatus = null,
- [FromQuery] Generation? generation = null)
- // TODO filter by min length
- // TODO filter by max length
- {
- var query = from obj in _context.EnumerateStrings(gcStatus, generation)
- let str = obj.AsString()
- let length = obj.ReadField("_stringLength")
- select new StringInfo(obj.Address, length, obj.Size, str);
-
- // TODO limit output qty
- return query;
- }
-
- [HttpGet]
- [Route("string-duplicates")]
- [ProducesResponseType(typeof(StringDuplicate[]), StatusCodes.Status200OK)]
- [SwaggerOperation(summary: "Get string duplicates")]
- public IEnumerable GetStringDuplicates(
- [FromQuery] ObjectGCStatus? gcStatus = null,
- [FromQuery] Generation? generation = null)
- // TODO filter by min length
- {
- var analyzer = new StringDuplicateAnalyzer(_context) { ObjectGcStatus = gcStatus, Generation = generation };
-
- return analyzer.GetStringDuplicates()
- .Select(sd => new StringDuplicate(sd.Value, sd.Count, sd.FullLength, sd.WastedMemory));
- }
-
- [HttpGet]
- [Route("object-instances/{mt}")]
- [ProducesResponseType(typeof(GetObjectInstancesResult), StatusCodes.Status200OK)]
- [SwaggerOperation(summary: "Get object instances")]
- public GetObjectInstancesResult GetObjectInstances(
- ulong mt,
- [FromQuery] ObjectGCStatus? gcStatus = null,
- [FromQuery] Generation? generation = null)
- // TODO limit maxCount
- {
- var methodTable = new MethodTable(mt);
-
- var clrType = _context.Heap.FindTypeByMethodTable(methodTable);
-
- var instances = (
- from obj in _context.EnumerateObjects(gcStatus, generation)
- where obj.Type != null
- && obj.Type.MethodTable == methodTable
- orderby obj.Size descending
- select new ObjectInstance
- (
- new Address(obj.Address),
- new Size(obj.Size)
- )
- ).ToArray();
-
- return new GetObjectInstancesResult(methodTable, clrType?.Name, instances);
- }
-
- [HttpGet]
- [Route("arrays/sparse")]
- [ProducesResponseType(typeof(ArrayInfo[]), StatusCodes.Status200OK)]
- [SwaggerOperation(summary: "Get sparse arrays")]
- // TODO add arrays
- // TODO add arrays/sparse
- // TODO add arrays/sparse/stat
- public IEnumerable GetSparseArrays(
- [FromQuery] ObjectGCStatus? gcStatus = null,
- [FromQuery] Generation? generation = null)
- {
- var query = from obj in _context.EnumerateObjects(gcStatus, generation)
- where obj.IsArray
- let proxy = new ArrayProxy(_context, obj)
- where proxy.UnusedItemsPercent >= 0.2
- orderby proxy.Wasted descending
- select new ArrayInfo(obj.Address, obj.Type.MethodTable, obj.Type.Name, proxy.Length, proxy.UnusedItemsCount,
- proxy.UnusedItemsPercent, proxy.Wasted);
-
- return query.Take(100);
- }
-
- [HttpGet]
- [Route("arrays/sparse/stat")]
- [ProducesResponseType(typeof(SparseArrayStatistics[]), StatusCodes.Status200OK)]
- [SwaggerOperation(summary: "Get arrays")]
- public IEnumerable GetSparseArraysStat(
- [FromQuery] ObjectGCStatus? gcStatus = null,
- [FromQuery] Generation? generation = null)
- {
- var query = from obj in _context.EnumerateObjects(gcStatus, generation)
- where obj.IsArray
- let proxy = new ArrayProxy(_context, obj)
- where proxy.UnusedItemsCount != 0
- group proxy by obj.Type.MethodTable
- into grp
- select new SparseArrayStatistics
- (
- grp.Key,
- grp.First().TargetObject.Type.Name,
- grp.Count(),
- Size.Sum(grp.Select(t => t.Wasted))
- );
-
- return query;
- }
-
- [HttpGet]
- [Route("object/{address}")]
- [ProducesResponseType(typeof(GetClrObjectResult), StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status404NotFound)]
- [SwaggerOperation(summary: "Get object")]
- public IActionResult GetClrObject(ulong address)
- {
- var clrObject = _context.Heap.GetObject(address);
- if (clrObject.Type == null)
- {
- return NotFound();
- }
-
- var result = new GetClrObjectResult(
- clrObject.Address,
- clrObject.Type.Module.Name,
- clrObject.Type.Name,
- clrObject.Type.MethodTable,
- clrObject.Size,
- _context.Heap.GetGeneration(clrObject.Address),
- clrObject.Type.IsString ? clrObject.AsString() : null);
-
- return Ok(result);
- }
-
- [HttpGet]
- [Route("object/{address}/fields")]
- [ProducesResponseType(typeof(ClrObjectField[]), StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status404NotFound)]
- [SwaggerOperation(summary: "Get object fields")]
- public IActionResult GetClrObjectFields(ulong address)
- {
- var clrObject = _context.Heap.GetObject(address);
- if (clrObject.Type == null)
- {
- return NotFound();
- }
-
- var fields = (
- from field in clrObject.Type.Fields
- let mt = field.Type?.MethodTable ?? 0UL
- let objectAddress = GetFieldObjectAddress(field, clrObject.Address)
- let value = GetFieldValue(field, clrObject.Address)
- select new ClrObjectField(
- mt,
- field.Type?.Name,
- field.Offset,
- field.IsValueType,
- objectAddress,
- value,
- field.Name)
- ).ToArray();
-
- return Ok(fields);
- }
-
- [HttpGet]
- [Route("object/{address}/roots")]
- [ProducesResponseType(typeof(ClrObjectRootPath[]), StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status404NotFound)]
- [SwaggerOperation(summary: "Get object roots")]
- public IActionResult GetClrObjectRoots(ulong address, CancellationToken ct)
- {
- var clrObject = _context.Heap.GetObject(address);
- if (clrObject.Type == null)
- {
- return NotFound();
- }
-
- var result = new List();
- GCRoot gcRoot = new(_context.Heap, new[] { address });
- foreach ((ClrRoot root, GCRoot.ChainLink path) in gcRoot.EnumerateRootPaths(ct))
- {
- var rootType = root.Object.Type!;
-
- var rootInfo = new RootInfo(
- root.Object.Address,
- root.RootKind,
- root.IsPinned,
- root.Object.Size,
- rootType.MethodTable,
- rootType.Name!
- );
-
- List pathItems = new();
-
- GCRoot.ChainLink? current = path;
- while (current != null)
- {
- var obj = _context.Heap.GetObject(current.Object);
-
- var item = new RootPathItem(
- obj.Address,
- obj.Type!.MethodTable,
- obj.Type.Name,
- obj.Size,
- _context.Heap.GetGeneration(obj.Address));
-
- pathItems.Add(item);
-
- current = current.Next;
- }
-
- result.Add(new ClrObjectRootPath(rootInfo, pathItems));
- // TODO get only one root path
- break;
- }
-
- return Ok(result);
- }
-
- private static Address? GetFieldObjectAddress(ClrInstanceField field, ulong address)
- {
- if (field.Type?.IsObjectReference ?? false)
- {
- return new Address(field.ReadObject(address, false).Address);
- }
-
- return null;
- }
-
- private static string GetFieldValue(ClrInstanceField field, ulong address)
- {
- if (field.IsPrimitive)
- {
- return field.ElementType switch
- {
- ClrElementType.Boolean => field.Read(address, false).ToString(),
- ClrElementType.Char => field.Read(address, false).ToString(),
- ClrElementType.Int8 => field.Read(address, false).ToString(),
- ClrElementType.UInt8 => field.Read(address, false).ToString(),
- ClrElementType.Int16 => field.Read(address, false).ToString(),
- ClrElementType.UInt16 => field.Read(address, false).ToString(),
- ClrElementType.Int32 => field.Read(address, false).ToString(),
- ClrElementType.UInt32 => field.Read(address, false).ToString(),
- ClrElementType.Int64 => field.Read(address, false).ToString(),
- ClrElementType.UInt64 => field.Read(address, false).ToString(),
- ClrElementType.Float => field.Read(address, false).ToString(CultureInfo.InvariantCulture),
- ClrElementType.Double => field.Read(address, false).ToString(CultureInfo.InvariantCulture),
- ClrElementType.NativeInt => field.Read(address, false).ToString(),
- ClrElementType.NativeUInt => field.Read(address, false).ToString(),
- _ => throw new ArgumentOutOfRangeException($"Unable to get primitive value for {field.ElementType} field")
- };
- }
-
- return GetAddress();
-
- string GetAddress()
- {
- if (field.Type?.IsEnum ?? false)
- {
- ClrEnum enumField = field.Type.AsEnum();
- // TODO handle other types
- if (enumField.ElementType == ClrElementType.Int32)
- {
- var fieldValue = field.Read(address, false);
- var name = enumField.EnumerateValues()
- .FirstOrDefault(v => (int)v.Value == fieldValue)
- .Name;
-
- return !string.IsNullOrEmpty(name)
- ? name
- : fieldValue.ToString();
- }
- }
-
- if (field.Type?.IsObjectReference ?? false)
- {
- ClrObject clrObject = field.ReadObject(address, false);
- if (clrObject.IsNull)
- {
- return "";
- }
-
- if (clrObject.Type?.IsString ?? false)
- {
- return clrObject.AsString(100);
- }
-
- if (clrObject is { IsNull: false, Type.Name: "System.Version" })
- {
- var major = clrObject.ReadField("_Major");
- var minor = clrObject.ReadField("_Minor");
- var build = clrObject.ReadField("_Build");
- var revision = clrObject.ReadField("_Revision");
- var version = new Version(major, minor, build, revision);
- return version.ToString();
- }
-
- return clrObject.Address.ToString("x16");
- }
-
- return string.Empty;
- }
- }
-}
\ No newline at end of file
+// using Heartbeat.Domain;
+// using Heartbeat.Runtime;
+// using Heartbeat.Runtime.Analyzers;
+// using Heartbeat.Runtime.Domain;
+// using Heartbeat.Runtime.Extensions;
+// using Heartbeat.Runtime.Proxies;
+//
+// using Microsoft.AspNetCore.Mvc;
+// using Microsoft.AspNetCore.OutputCaching;
+// using Microsoft.Diagnostics.Runtime;
+//
+// using Swashbuckle.AspNetCore.Annotations;
+//
+// using System.Globalization;
+// using System.Net.Mime;
+//
+// namespace Heartbeat.Host.Controllers;
+//
+// [ApiController]
+// [OutputCache]
+// [Route("api/dump")]
+// [ApiExplorerSettings(GroupName = "Heartbeat")]
+// [Consumes(MediaTypeNames.Application.Json)]
+// [Produces(MediaTypeNames.Application.Json)]
+// [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
+// public class DumpController : ControllerBase
+// {
+// private readonly RuntimeContext _context;
+//
+// public DumpController(RuntimeContext context)
+// {
+// _context = context;
+// }
+//
+// [HttpGet]
+// [Route("info")]
+// [ProducesResponseType(typeof(DumpInfo), StatusCodes.Status200OK)]
+// [SwaggerOperation(summary: "Get dump info")]
+// public DumpInfo GetInfo()
+// {
+// var clrHeap = _context.Heap;
+// var clrInfo = _context.Runtime.ClrInfo;
+// var dataReader = clrInfo.DataTarget.DataReader;
+//
+// return new DumpInfo(
+// _context.DumpPath,
+// clrHeap.CanWalkHeap,
+// clrHeap.IsServer,
+// clrInfo.ModuleInfo.FileName,
+// dataReader.Architecture,
+// dataReader.ProcessId,
+// dataReader.TargetPlatform.ToString(),
+// clrInfo.Version.ToString(2)
+// );
+// }
+//
+// [HttpGet]
+// [Route("modules")]
+// [ProducesResponseType(typeof(Module[]), StatusCodes.Status200OK)]
+// [SwaggerOperation(summary: "Get modules")]
+// public IEnumerable GetModules()
+// {
+// var modules = _context.Runtime
+// .EnumerateModules()
+// .Select(m => new Module(m.Address, m.Size, m.Name))
+// .ToArray();
+//
+// return modules;
+// }
+//
+// [HttpGet]
+// [Route("segments")]
+// [ProducesResponseType(typeof(HeapSegment[]), StatusCodes.Status200OK)]
+// [SwaggerOperation(summary: "Get segments")]
+// public IEnumerable GetSegments()
+// {
+// var segments =
+// from s in _context.Heap.Segments
+// select new HeapSegment(
+// s.Start,
+// s.End,
+// s.Kind);
+//
+// return segments;
+// }
+//
+// [HttpGet]
+// [Route("roots")]
+// [ProducesResponseType(typeof(RootInfo[]), StatusCodes.Status200OK)]
+// [SwaggerOperation(summary: "Get heap roots")]
+// public IEnumerable GetRoots([FromQuery] ClrRootKind? kind = null)
+// {
+// return
+// from root in _context.Heap.EnumerateRoots()
+// where kind == null || root.RootKind == kind
+// let objectType = root.Object.Type
+// select new RootInfo(
+// root.Object.Address,
+// root.RootKind,
+// root.IsPinned,
+// root.Object.Size,
+// objectType.MethodTable,
+// objectType.Name
+// );
+// }
+//
+// [HttpGet]
+// [Route("heap-dump-statistics")]
+// [ProducesResponseType(typeof(ObjectTypeStatistics[]), StatusCodes.Status200OK)]
+// [SwaggerOperation(summary: "Get heap dump statistics")]
+// public IEnumerable GetHeapDumpStat(
+// [FromQuery] ObjectGCStatus? gcStatus = null,
+// [FromQuery] Generation? generation = null)
+// // TODO filter by just my code - how to filter Action?
+// // TODO filter by type name
+// {
+// var analyzer = new HeapDumpStatisticsAnalyzer(_context) { ObjectGcStatus = gcStatus, Generation = generation };
+//
+// var statistics = analyzer.GetObjectTypeStatistics()
+// .OrderByDescending(s => s.TotalSize)
+// .Select(s => new ObjectTypeStatistics(s.MethodTable, s.TypeName, s.TotalSize, s.InstanceCount))
+// .ToArray();
+//
+// return statistics;
+// }
+//
+// [HttpGet]
+// [Route("strings")]
+// [ProducesResponseType(typeof(StringInfo[]), StatusCodes.Status200OK)]
+// [SwaggerOperation(summary: "Get heap dump statistics")]
+// public IEnumerable GetStrings(
+// [FromQuery] ObjectGCStatus? gcStatus = null,
+// [FromQuery] Generation? generation = null)
+// // TODO filter by min length
+// // TODO filter by max length
+// {
+// var query = from obj in _context.EnumerateStrings(gcStatus, generation)
+// let str = obj.AsString()
+// let length = obj.ReadField("_stringLength")
+// select new StringInfo(obj.Address, length, obj.Size, str);
+//
+// // TODO limit output qty
+// return query;
+// }
+//
+// [HttpGet]
+// [Route("string-duplicates")]
+// [ProducesResponseType(typeof(StringDuplicate[]), StatusCodes.Status200OK)]
+// [SwaggerOperation(summary: "Get string duplicates")]
+// public IEnumerable GetStringDuplicates(
+// [FromQuery] ObjectGCStatus? gcStatus = null,
+// [FromQuery] Generation? generation = null)
+// // TODO filter by min length
+// {
+// var analyzer = new StringDuplicateAnalyzer(_context) { ObjectGcStatus = gcStatus, Generation = generation };
+//
+// return analyzer.GetStringDuplicates()
+// .Select(sd => new StringDuplicate(sd.Value, sd.Count, sd.FullLength, sd.WastedMemory));
+// }
+//
+// [HttpGet]
+// [Route("object-instances/{mt}")]
+// [ProducesResponseType(typeof(GetObjectInstancesResult), StatusCodes.Status200OK)]
+// [SwaggerOperation(summary: "Get object instances")]
+// public GetObjectInstancesResult GetObjectInstances(
+// ulong mt,
+// [FromQuery] ObjectGCStatus? gcStatus = null,
+// [FromQuery] Generation? generation = null)
+// // TODO limit maxCount
+// {
+// var methodTable = new MethodTable(mt);
+//
+// var clrType = _context.Heap.FindTypeByMethodTable(methodTable);
+//
+// var instances = (
+// from obj in _context.EnumerateObjects(gcStatus, generation)
+// where obj.Type != null
+// && obj.Type.MethodTable == methodTable
+// orderby obj.Size descending
+// select new ObjectInstance
+// (
+// new Address(obj.Address),
+// new Size(obj.Size)
+// )
+// ).ToArray();
+//
+// return new GetObjectInstancesResult(methodTable, clrType?.Name, instances);
+// }
+//
+// [HttpGet]
+// [Route("arrays/sparse")]
+// [ProducesResponseType(typeof(ArrayInfo[]), StatusCodes.Status200OK)]
+// [SwaggerOperation(summary: "Get sparse arrays")]
+// // TODO add arrays
+// // TODO add arrays/sparse
+// // TODO add arrays/sparse/stat
+// public IEnumerable GetSparseArrays(
+// [FromQuery] ObjectGCStatus? gcStatus = null,
+// [FromQuery] Generation? generation = null)
+// {
+// var query = from obj in _context.EnumerateObjects(gcStatus, generation)
+// where obj.IsArray
+// let proxy = new ArrayProxy(_context, obj)
+// where proxy.UnusedItemsPercent >= 0.2
+// orderby proxy.Wasted descending
+// select new ArrayInfo(obj.Address, obj.Type.MethodTable, obj.Type.Name, proxy.Length, proxy.UnusedItemsCount,
+// proxy.UnusedItemsPercent, proxy.Wasted);
+//
+// return query.Take(100);
+// }
+//
+// [HttpGet]
+// [Route("arrays/sparse/stat")]
+// [ProducesResponseType(typeof(SparseArrayStatistics[]), StatusCodes.Status200OK)]
+// [SwaggerOperation(summary: "Get arrays")]
+// public IEnumerable GetSparseArraysStat(
+// [FromQuery] ObjectGCStatus? gcStatus = null,
+// [FromQuery] Generation? generation = null)
+// {
+// var query = from obj in _context.EnumerateObjects(gcStatus, generation)
+// where obj.IsArray
+// let proxy = new ArrayProxy(_context, obj)
+// where proxy.UnusedItemsCount != 0
+// group proxy by obj.Type.MethodTable
+// into grp
+// select new SparseArrayStatistics
+// (
+// grp.Key,
+// grp.First().TargetObject.Type.Name,
+// grp.Count(),
+// Size.Sum(grp.Select(t => t.Wasted))
+// );
+//
+// return query;
+// }
+//
+// [HttpGet]
+// [Route("object/{address}")]
+// [ProducesResponseType(typeof(GetClrObjectResult), StatusCodes.Status200OK)]
+// [ProducesResponseType(StatusCodes.Status404NotFound)]
+// [SwaggerOperation(summary: "Get object")]
+// public IActionResult GetClrObject(ulong address)
+// {
+// var clrObject = _context.Heap.GetObject(address);
+// if (clrObject.Type == null)
+// {
+// return NotFound();
+// }
+//
+// var result = new GetClrObjectResult(
+// clrObject.Address,
+// clrObject.Type.Module.Name,
+// clrObject.Type.Name,
+// clrObject.Type.MethodTable,
+// clrObject.Size,
+// _context.Heap.GetGeneration(clrObject.Address),
+// clrObject.Type.IsString ? clrObject.AsString() : null);
+//
+// return Ok(result);
+// }
+//
+// [HttpGet]
+// [Route("object/{address}/fields")]
+// [ProducesResponseType(typeof(ClrObjectField[]), StatusCodes.Status200OK)]
+// [ProducesResponseType(StatusCodes.Status404NotFound)]
+// [SwaggerOperation(summary: "Get object fields")]
+// public IActionResult GetClrObjectFields(ulong address)
+// {
+// var clrObject = _context.Heap.GetObject(address);
+// if (clrObject.Type == null)
+// {
+// return NotFound();
+// }
+//
+// var fields = (
+// from field in clrObject.Type.Fields
+// let mt = field.Type?.MethodTable ?? 0UL
+// let objectAddress = GetFieldObjectAddress(field, clrObject.Address)
+// let value = GetFieldValue(field, clrObject.Address)
+// select new ClrObjectField(
+// mt,
+// field.Type?.Name,
+// field.Offset,
+// field.IsValueType,
+// objectAddress,
+// value,
+// field.Name)
+// ).ToArray();
+//
+// return Ok(fields);
+// }
+//
+// [HttpGet]
+// [Route("object/{address}/roots")]
+// [ProducesResponseType(typeof(ClrObjectRootPath[]), StatusCodes.Status200OK)]
+// [ProducesResponseType(StatusCodes.Status404NotFound)]
+// [SwaggerOperation(summary: "Get object roots")]
+// public IActionResult GetClrObjectRoots(ulong address, CancellationToken ct)
+// {
+// var clrObject = _context.Heap.GetObject(address);
+// if (clrObject.Type == null)
+// {
+// return NotFound();
+// }
+//
+// var result = new List();
+// GCRoot gcRoot = new(_context.Heap, new[] { address });
+// foreach ((ClrRoot root, GCRoot.ChainLink path) in gcRoot.EnumerateRootPaths(ct))
+// {
+// var rootType = root.Object.Type!;
+//
+// var rootInfo = new RootInfo(
+// root.Object.Address,
+// root.RootKind,
+// root.IsPinned,
+// root.Object.Size,
+// rootType.MethodTable,
+// rootType.Name!
+// );
+//
+// List pathItems = new();
+//
+// GCRoot.ChainLink? current = path;
+// while (current != null)
+// {
+// var obj = _context.Heap.GetObject(current.Object);
+//
+// var item = new RootPathItem(
+// obj.Address,
+// obj.Type!.MethodTable,
+// obj.Type.Name,
+// obj.Size,
+// _context.Heap.GetGeneration(obj.Address));
+//
+// pathItems.Add(item);
+//
+// current = current.Next;
+// }
+//
+// result.Add(new ClrObjectRootPath(rootInfo, pathItems));
+// // TODO get only one root path
+// break;
+// }
+//
+// return Ok(result);
+// }
+//
+// private static Address? GetFieldObjectAddress(ClrInstanceField field, ulong address)
+// {
+// if (field.Type?.IsObjectReference ?? false)
+// {
+// return new Address(field.ReadObject(address, false).Address);
+// }
+//
+// return null;
+// }
+//
+// private static string GetFieldValue(ClrInstanceField field, ulong address)
+// {
+// if (field.IsPrimitive)
+// {
+// return field.ElementType switch
+// {
+// ClrElementType.Boolean => field.Read(address, false).ToString(),
+// ClrElementType.Char => field.Read(address, false).ToString(),
+// ClrElementType.Int8 => field.Read(address, false).ToString(),
+// ClrElementType.UInt8 => field.Read(address, false).ToString(),
+// ClrElementType.Int16 => field.Read(address, false).ToString(),
+// ClrElementType.UInt16 => field.Read(address, false).ToString(),
+// ClrElementType.Int32 => field.Read(address, false).ToString(),
+// ClrElementType.UInt32 => field.Read(address, false).ToString(),
+// ClrElementType.Int64 => field.Read(address, false).ToString(),
+// ClrElementType.UInt64 => field.Read(address, false).ToString(),
+// ClrElementType.Float => field.Read(address, false).ToString(CultureInfo.InvariantCulture),
+// ClrElementType.Double => field.Read(address, false).ToString(CultureInfo.InvariantCulture),
+// ClrElementType.NativeInt => field.Read(address, false).ToString(),
+// ClrElementType.NativeUInt => field.Read(address, false).ToString(),
+// _ => throw new ArgumentOutOfRangeException($"Unable to get primitive value for {field.ElementType} field")
+// };
+// }
+//
+// return GetAddress();
+//
+// string GetAddress()
+// {
+// if (field.Type?.IsEnum ?? false)
+// {
+// ClrEnum enumField = field.Type.AsEnum();
+// // TODO handle other types
+// if (enumField.ElementType == ClrElementType.Int32)
+// {
+// var fieldValue = field.Read(address, false);
+// var name = enumField.EnumerateValues()
+// .FirstOrDefault(v => (int)v.Value == fieldValue)
+// .Name;
+//
+// return !string.IsNullOrEmpty(name)
+// ? name
+// : fieldValue.ToString();
+// }
+// }
+//
+// if (field.Type?.IsObjectReference ?? false)
+// {
+// ClrObject clrObject = field.ReadObject(address, false);
+// if (clrObject.IsNull)
+// {
+// return "";
+// }
+//
+// if (clrObject.Type?.IsString ?? false)
+// {
+// return clrObject.AsString(100);
+// }
+//
+// if (clrObject is { IsNull: false, Type.Name: "System.Version" })
+// {
+// var major = clrObject.ReadField("_Major");
+// var minor = clrObject.ReadField("_Minor");
+// var build = clrObject.ReadField("_Build");
+// var revision = clrObject.ReadField("_Revision");
+// var version = new Version(major, minor, build, revision);
+// return version.ToString();
+// }
+//
+// return clrObject.Address.ToString("x16");
+// }
+//
+// return string.Empty;
+// }
+// }
+// }
\ No newline at end of file
diff --git a/src/Heartbeat/Controllers/Models.cs b/src/Heartbeat/Controllers/Models.cs
index 06ccb75..7843591 100644
--- a/src/Heartbeat/Controllers/Models.cs
+++ b/src/Heartbeat/Controllers/Models.cs
@@ -1,6 +1,7 @@
using Microsoft.Diagnostics.Runtime;
using System.Runtime.InteropServices;
+using System.Text.Json.Serialization;
namespace Heartbeat.Host.Controllers;
@@ -56,8 +57,27 @@ public record StringDuplicate(string Value, int Count, int FullLength, ulong Was
public record RootInfo(ulong Address, ClrRootKind Kind, bool IsPinned, ulong Size, ulong MethodTable, string TypeName);
public record ClrObjectRootPath(RootInfo Root, IReadOnlyList PathItems);
+
public record RootPathItem(ulong Address, ulong MethodTable, string? TypeName, ulong Size, Generation Generation);
public record ArrayInfo(ulong Address, ulong MethodTable, string? TypeName, int Length, int UnusedItemsCount, double UnusedPercent, ulong Wasted);
-public record SparseArrayStatistics(ulong MethodTable, string? TypeName, int Count, ulong TotalWasted);
\ No newline at end of file
+public record SparseArrayStatistics(ulong MethodTable, string? TypeName, int Count, ulong TotalWasted);
+
+[JsonSourceGenerationOptions(UseStringEnumConverter = true)]
+[JsonSerializable(typeof(DumpInfo))]
+[JsonSerializable(typeof(GetObjectInstancesResult))]
+[JsonSerializable(typeof(GetClrObjectResult))]
+[JsonSerializable(typeof(Module[]))]
+[JsonSerializable(typeof(ClrObjectField[]))]
+[JsonSerializable(typeof(List))]
+[JsonSerializable(typeof(IEnumerable))]
+[JsonSerializable(typeof(IEnumerable>))]
+[JsonSerializable(typeof(IEnumerable))]
+[JsonSerializable(typeof(IEnumerable))]
+[JsonSerializable(typeof(IEnumerable))]
+[JsonSerializable(typeof(IEnumerable))]
+[JsonSerializable(typeof(IEnumerable))]
+internal partial class AppJsonSerializerContext : JsonSerializerContext
+{
+}
\ No newline at end of file
diff --git a/src/Heartbeat/Extensions/SwaggerExtensions.cs b/src/Heartbeat/Extensions/SwaggerExtensions.cs
index 779a7c2..95b4f4b 100644
--- a/src/Heartbeat/Extensions/SwaggerExtensions.cs
+++ b/src/Heartbeat/Extensions/SwaggerExtensions.cs
@@ -1,3 +1,4 @@
+#if OPENAPI
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.OpenApi.Models;
@@ -77,9 +78,7 @@ public class RequireNonNullablePropertiesSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema model, SchemaFilterContext context)
{
-#if DEBUG
FixNullableProperties(model, context);
-#endif
var additionalRequiredProps = model.Properties
.Where(x => !x.Value.Nullable && !model.Required.Contains(x.Key))
@@ -119,4 +118,5 @@ private static void FixNullableProperties(OpenApiSchema schema, SchemaFilterCont
}
}
}
-}
\ No newline at end of file
+}
+#endif
\ No newline at end of file
diff --git a/src/Heartbeat/Heartbeat.csproj b/src/Heartbeat/Heartbeat.csproj
index 2403617..2699432 100644
--- a/src/Heartbeat/Heartbeat.csproj
+++ b/src/Heartbeat/Heartbeat.csproj
@@ -13,51 +13,65 @@
https://localhost:44443
npm start
-
-
- true
+
+
+ DEBUG;OPENAPI
+
+ true
+
+
+
true
- win-x64
+ true
+ RELEASE;AOT
+ true
+ false
+ true
+
+
+
+
true
true
-
-
-
- heartbeat.exe
+ heartbeat
./nupkg
README.md
-
+
-
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
-
-
-
-
+
@@ -91,8 +105,9 @@
wwwroot\%(RecursiveDir)%(FileName)%(Extension)
- PreserveNewest
- true
+ Never
+ PreserveNewest
+ false
diff --git a/src/Heartbeat/Program.cs b/src/Heartbeat/Program.cs
index 9ea2b11..3a8d672 100644
--- a/src/Heartbeat/Program.cs
+++ b/src/Heartbeat/Program.cs
@@ -1,79 +1,82 @@
+using Heartbeat.Domain;
using Heartbeat.Host.CommandLine;
-using Heartbeat.Host.Extensions;
+using Heartbeat.Host.Controllers;
using Heartbeat.Runtime;
+using Heartbeat.Runtime.Analyzers;
using Heartbeat.Runtime.Domain;
+using Heartbeat.Runtime.Extensions;
+using Heartbeat.Runtime.Proxies;
using Microsoft.AspNetCore.Diagnostics;
+using Microsoft.AspNetCore.Http.HttpResults;
+using Microsoft.AspNetCore.Mvc;
using Microsoft.Diagnostics.Runtime;
+using Microsoft.Extensions.FileProviders;
using System.CommandLine;
+using System.Diagnostics;
+using System.Globalization;
using System.Net.Mime;
-using System.Runtime.InteropServices;
+
+#if OPENAPI
+using Heartbeat.Host.Extensions;
using System.Text.Json.Serialization;
+#endif
+
+using DumpInfo = Heartbeat.Host.Controllers.DumpInfo;
+using HeapSegment = Heartbeat.Host.Controllers.HeapSegment;
+using Module = Heartbeat.Host.Controllers.Module;
+using ObjectTypeStatistics = Heartbeat.Host.Controllers.ObjectTypeStatistics;
+using StringDuplicate = Heartbeat.Host.Controllers.StringDuplicate;
+using StringInfo = Heartbeat.Host.Controllers.StringInfo;
-#if DEBUG
-if (Environment.GetEnvironmentVariable("HEARTBEAT_GENERATE_CONTRACTS") == "true")
+PrintFiles();
+
+#if OPENAPI
+Console.WriteLine("Generating OpenAPI contract");
+var builder = WebApplication.CreateSlimBuilder(args);
+
+builder.Services.AddControllers().AddJsonOptions(options =>
{
- var builder = WebApplication.CreateBuilder(args);
+ options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
+});
- builder.Services
- .AddControllers()
- .AddJsonOptions(
- options =>
- {
- // var enumConverter = new JsonStringEnumConverter();
- // options.JsonSerializerOptions.Converters.Add(enumConverter);
- options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
- options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
- options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
- options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
- options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
- });
-
- builder.Services.AddSwagger();
- var app = builder.Build();
- app.UseSwagger();
- app.UseSwaggerUI(options =>
- {
- options.SwaggerEndpoint("Heartbeat/swagger.yaml", "Heartbeat");
- });
- app.MapControllers();
- app.Run();
- return;
-}
+// workaround for https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/2550
+builder.Services.ConfigureHttpJsonOptions(options =>
+{
+ // options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
+ // options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
+ options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
+});
+
+builder.Services.AddSwagger();
+
+var app = builder.Build();
+app.UseSwagger();
+MapEndpoints(app);
+app.Run();
+return;
#endif
var (rootCommand, binder) = WebCommandOptions.RootCommand();
rootCommand.SetHandler((WebCommandOptions options) => MainWeb(options, args), binder);
-//rootCommand.Add(AnalyzeCommandOptions.Command("analyze"));
rootCommand.Invoke(args);
-// TODO try native AOT - https://learn.microsoft.com/en-us/aspnet/core/fundamentals/native-aot?view=aspnetcore-8.0
static void MainWeb(WebCommandOptions options, string[] args)
{
-#if !DEBUG
+#if !DEBUG && !AOT
// fix for static files when running as dotnet tool
string rootDir = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)!;
Directory.SetCurrentDirectory(rootDir);
#endif
- var builder = WebApplication.CreateBuilder(args);
-
- builder.Services
- .AddControllers()
- .AddJsonOptions(
- options =>
- {
- // var enumConverter = new JsonStringEnumConverter();
- // options.JsonSerializerOptions.Converters.Add(enumConverter);
- options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
- options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
- options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
- options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
- options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
- });
+ var builder = WebApplication.CreateSlimBuilder(args);
+
+ builder.Services.ConfigureHttpJsonOptions(options =>
+ {
+ options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
+ });
builder.Services.AddProblemDetails();
- builder.Services.AddSwagger();
builder.Services.AddOutputCache();
// TODO support auth
@@ -82,40 +85,484 @@ static void MainWeb(WebCommandOptions options, string[] args)
builder.Services.AddSingleton(runtimeContext);
var app = builder.Build();
+
+#if AOT
+ var fileProvider = new ManifestEmbeddedFileProvider(typeof(Program).Assembly, "ClientApp/build");
+ app.UseDefaultFiles(new DefaultFilesOptions
+ {
+ FileProvider = fileProvider
+ });
+ app.UseStaticFiles(new StaticFileOptions { FileProvider = fileProvider });
+#else
app.UseDefaultFiles();
app.UseStaticFiles();
- app.UseSwagger();
- app.UseSwaggerUI(options =>
+#endif
+ app.UseStatusCodePages(async statusCodeContext
+ => await Results.Problem(statusCode: statusCodeContext.HttpContext.Response.StatusCode)
+ .ExecuteAsync(statusCodeContext.HttpContext));
+ // app.UseExceptionHandler(exceptionHandlerApp =>
+ // {
+ // exceptionHandlerApp.Run(async context =>
+ // {
+ // context.Response.StatusCode = StatusCodes.Status500InternalServerError;
+ // context.Response.ContentType = MediaTypeNames.Application.Json;
+ //
+ // if (context.RequestServices.GetService() is { } problemDetailsService)
+ // {
+ // var exceptionHandlerFeature = context.Features.Get();
+ // var exceptionType = exceptionHandlerFeature?.Error;
+ // await problemDetailsService.WriteAsync(new ProblemDetailsContext
+ // {
+ // HttpContext = context,
+ // ProblemDetails =
+ // {
+ // Title = "An error occurred while processing your request.",
+ // Detail = exceptionType?.Message,
+ // Type = "https://tools.ietf.org/html/rfc7231#section-6.6.1",
+ // Status = 500
+ // }
+ // });
+ // }
+ // });
+ // });
+ app.UseOutputCache();
+ MapEndpoints(app);
+ app.Run();
+}
+
+static void MapEndpoints(WebApplication app)
+{
+ var dumpGroup = app.MapGroup("api/dump")
+ .CacheOutput()
+ .WithTags("Dump")
+ .WithOpenApi();
+
+ dumpGroup.MapGet("info", DumpHandler.GetInfo)
+ .Produces(StatusCodes.Status500InternalServerError)
+ .WithName("GetInfo");
+
+ dumpGroup.MapGet("modules", DumpHandler.GetModules)
+ .Produces(StatusCodes.Status500InternalServerError)
+ .WithName("GetModules");
+
+ dumpGroup.MapGet("segments", DumpHandler.GetSegments)
+ .Produces(StatusCodes.Status500InternalServerError)
+ .WithName("GetSegments");
+
+ dumpGroup.MapGet("roots", DumpHandler.GetRoots)
+ .Produces(StatusCodes.Status500InternalServerError)
+ .WithName("GetRoots");
+
+ dumpGroup.MapGet("heap-dump-statistics", DumpHandler.GetHeapDumpStat)
+ .Produces(StatusCodes.Status500InternalServerError)
+ .WithName("GetHeapDumpStat");
+
+ dumpGroup.MapGet("strings", DumpHandler.GetStrings)
+ .Produces(StatusCodes.Status500InternalServerError)
+ .WithName("GetStrings");
+
+ dumpGroup.MapGet("string-duplicates", DumpHandler.GetStringDuplicates)
+ .Produces(StatusCodes.Status500InternalServerError)
+ .WithName("GetStringDuplicates");
+
+ dumpGroup.MapGet("object-instances/{mt}", DumpHandler.GetObjectInstances)
+ .Produces(StatusCodes.Status500InternalServerError)
+ .WithName("GetObjectInstances");
+
+ dumpGroup.MapGet("arrays/sparse", DumpHandler.GetSparseArrays)
+ .Produces(StatusCodes.Status500InternalServerError)
+ .WithName("GetSparseArrays");
+
+ dumpGroup.MapGet("arrays/sparse/stat", DumpHandler.GetSparseArraysStat)
+ .Produces(StatusCodes.Status500InternalServerError)
+ .WithName("GetSparseArraysStat");
+
+ dumpGroup.MapGet("object/{address}", DumpHandler.GetClrObject)
+ .Produces(StatusCodes.Status404NotFound)
+ .Produces(StatusCodes.Status500InternalServerError)
+ .WithName("GetClrObject");
+
+ dumpGroup.MapGet("object/{address}/fields", DumpHandler.GetClrObjectFields)
+ .Produces(StatusCodes.Status404NotFound)
+ .Produces(StatusCodes.Status500InternalServerError)
+ .WithName("GetClrObjectFields");
+
+ dumpGroup.MapGet("object/{address}/roots", DumpHandler.GetClrObjectRoots)
+ .Produces(StatusCodes.Status404NotFound)
+ .Produces(StatusCodes.Status500InternalServerError)
+ .WithName("GetClrObjectRoots");
+}
+
+[Conditional("AOT")]
+static void PrintFiles()
+{
+ IFileProvider fileProvider = new ManifestEmbeddedFileProvider(typeof(Program).Assembly);
+ var contents = fileProvider.GetDirectoryContents("/");
+ using IEnumerator enumerator = contents.GetEnumerator();
+ while (enumerator.MoveNext())
{
- options.EnableTryItOutByDefault();
- options.SwaggerEndpoint("Heartbeat/swagger.yaml", "Heartbeat");
- });
- app.UseExceptionHandler(exceptionHandlerApp =>
+ Console.WriteLine(enumerator.Current.Name);
+ if (enumerator.Current.Name == "Microsoft.Extensions.FileProviders.Embedded.Manifest.xml")
+ {
+ using var stream = enumerator.Current.CreateReadStream();
+ using var reader = new StreamReader(stream);
+ Console.WriteLine(reader.ReadToEnd());
+ }
+ }
+}
+
+static class DumpHandler
+{
+ public static DumpInfo GetInfo([FromServices] RuntimeContext context)
+ {
+ var clrHeap = context.Heap;
+ var clrInfo = context.Runtime.ClrInfo;
+ var dataReader = clrInfo.DataTarget.DataReader;
+
+
+ var dumpInfo = new DumpInfo(
+ context.DumpPath,
+ clrHeap.CanWalkHeap,
+ clrHeap.IsServer,
+ clrInfo.ModuleInfo.FileName,
+ dataReader.Architecture,
+ dataReader.ProcessId,
+ dataReader.TargetPlatform.ToString(),
+ clrInfo.Version.ToString()
+ );
+
+ return dumpInfo;
+ }
+
+ public static Module[] GetModules([FromServices] RuntimeContext context)
+ {
+ var modules = context.Runtime
+ .EnumerateModules()
+ .Select(m => new Module(m.Address, m.Size, m.Name))
+ .ToArray();
+
+ return modules;
+ }
+
+ public static IEnumerable GetSegments([FromServices] RuntimeContext context)
{
- exceptionHandlerApp.Run(async context =>
+ var segments =
+ from s in context.Heap.Segments
+ select new HeapSegment(
+ s.Start,
+ s.End,
+ s.Kind
+ );
+
+ return segments;
+ }
+
+ public static IEnumerable GetRoots([FromServices] RuntimeContext context, [FromQuery] ClrRootKind? kind = null)
+ {
+ return
+ from root in context.Heap.EnumerateRoots()
+ where kind == null || root.RootKind == kind
+ let objectType = root.Object.Type
+ select new RootInfo(
+ root.Object.Address,
+ root.RootKind,
+ root.IsPinned,
+ root.Object.Size,
+ objectType.MethodTable,
+ objectType.Name
+ );
+ }
+
+ public static IEnumerable GetHeapDumpStat(
+ [FromServices] RuntimeContext context,
+ [FromQuery] ObjectGCStatus? gcStatus = null,
+ [FromQuery] Generation? generation = null)
+ // TODO filter by just my code - how to filter Action?
+ // TODO filter by type name
+ {
+ var analyzer = new HeapDumpStatisticsAnalyzer(context) { ObjectGcStatus = gcStatus, Generation = generation };
+
+ var statistics = analyzer.GetObjectTypeStatistics()
+ .OrderByDescending(s => s.TotalSize)
+ .Select(s => new ObjectTypeStatistics(s.MethodTable, s.TypeName, s.TotalSize, s.InstanceCount))
+ .ToArray();
+
+ return statistics;
+ }
+
+ public static IEnumerable GetStrings(
+ [FromServices] RuntimeContext context,
+ [FromQuery] ObjectGCStatus? gcStatus = null,
+ [FromQuery] Generation? generation = null)
+ // TODO filter by min length
+ // TODO filter by max length
+ {
+ var query = from obj in context.EnumerateStrings(gcStatus, generation)
+ let str = obj.AsString()
+ let length = obj.ReadField("_stringLength")
+ select new StringInfo(obj.Address, length, obj.Size, str);
+
+ // TODO limit output qty
+ return query;
+ }
+
+ public static IEnumerable GetStringDuplicates(
+ [FromServices] RuntimeContext context,
+ [FromQuery] ObjectGCStatus? gcStatus = null,
+ [FromQuery] Generation? generation = null)
+ // TODO filter by min length
+ {
+ var analyzer = new StringDuplicateAnalyzer(context) { ObjectGcStatus = gcStatus, Generation = generation };
+
+ return analyzer.GetStringDuplicates()
+ .Select(sd => new StringDuplicate(sd.Value, sd.Count, sd.FullLength, sd.WastedMemory));
+ }
+
+ public static GetObjectInstancesResult GetObjectInstances(
+ [FromServices] RuntimeContext context,
+ ulong mt,
+ [FromQuery] ObjectGCStatus? gcStatus = null,
+ [FromQuery] Generation? generation = null)
+ // TODO limit maxCount
+ {
+ var methodTable = new MethodTable(mt);
+
+ var clrType = context.Heap.FindTypeByMethodTable(methodTable);
+
+ var instances = (
+ from obj in context.EnumerateObjects(gcStatus, generation)
+ where obj.Type != null
+ && obj.Type.MethodTable == methodTable
+ orderby obj.Size descending
+ select new ObjectInstance
+ (
+ new Address(obj.Address),
+ new Size(obj.Size)
+ )
+ ).ToArray();
+
+ return new GetObjectInstancesResult(methodTable, clrType?.Name, instances);
+ }
+
+ // TODO add arrays
+ // TODO add arrays/sparse
+ // TODO add arrays/sparse/stat
+ public static IEnumerable GetSparseArrays(
+ [FromServices] RuntimeContext context,
+ [FromQuery] ObjectGCStatus? gcStatus = null,
+ [FromQuery] Generation? generation = null)
+ {
+ var query = from obj in context.EnumerateObjects(gcStatus, generation)
+ where obj.IsArray
+ let proxy = new ArrayProxy(context, obj)
+ where proxy.UnusedItemsPercent >= 0.2
+ orderby proxy.Wasted descending
+ select new ArrayInfo(obj.Address, obj.Type.MethodTable, obj.Type.Name, proxy.Length, proxy.UnusedItemsCount,
+ proxy.UnusedItemsPercent, proxy.Wasted);
+
+ return query.Take(100);
+ }
+
+ public static IEnumerable GetSparseArraysStat(
+ [FromServices] RuntimeContext context,
+ [FromQuery] ObjectGCStatus? gcStatus = null,
+ [FromQuery] Generation? generation = null)
+ {
+ var query = from obj in context.EnumerateObjects(gcStatus, generation)
+ where obj.IsArray
+ let proxy = new ArrayProxy(context, obj)
+ where proxy.UnusedItemsCount != 0
+ group proxy by obj.Type.MethodTable
+ into grp
+ select new SparseArrayStatistics
+ (
+ grp.Key,
+ grp.First().TargetObject.Type.Name,
+ grp.Count(),
+ Size.Sum(grp.Select(t => t.Wasted))
+ );
+
+ return query;
+ }
+
+ public static Results, NotFound> GetClrObject([FromServices] RuntimeContext context, ulong address)
+ {
+ var clrObject = context.Heap.GetObject(address);
+ if (clrObject.Type == null)
+ {
+ return TypedResults.NotFound();
+ }
+
+ var result = new GetClrObjectResult(
+ clrObject.Address,
+ clrObject.Type.Module.Name,
+ clrObject.Type.Name,
+ clrObject.Type.MethodTable,
+ clrObject.Size,
+ context.Heap.GetGeneration(clrObject.Address),
+ clrObject.Type.IsString ? clrObject.AsString() : null);
+
+ return TypedResults.Ok(result);
+ }
+
+ public static Results, NotFound> GetClrObjectFields([FromServices] RuntimeContext context, ulong address)
+ {
+ var clrObject = context.Heap.GetObject(address);
+ if (clrObject.Type == null)
+ {
+ return TypedResults.NotFound();
+ }
+
+ var fields = (
+ from field in clrObject.Type.Fields
+ let mt = field.Type?.MethodTable ?? 0UL
+ let objectAddress = GetFieldObjectAddress(field, clrObject.Address)
+ let value = GetFieldValue(field, clrObject.Address)
+ select new ClrObjectField(
+ mt,
+ field.Type?.Name,
+ field.Offset,
+ field.IsValueType,
+ objectAddress,
+ value,
+ field.Name)
+ ).ToArray();
+
+ return TypedResults.Ok(fields);
+ }
+
+ public static Results>, NotFound> GetClrObjectRoots([FromServices] RuntimeContext context, ulong address, CancellationToken ct)
+ {
+ var clrObject = context.Heap.GetObject(address);
+ if (clrObject.Type == null)
+ {
+ return TypedResults.NotFound();
+ }
+
+ var result = new List();
+ GCRoot gcRoot = new(context.Heap, new[] { address });
+ foreach ((ClrRoot root, GCRoot.ChainLink path) in gcRoot.EnumerateRootPaths(ct))
+ {
+ var rootType = root.Object.Type!;
+
+ var rootInfo = new RootInfo(
+ root.Object.Address,
+ root.RootKind,
+ root.IsPinned,
+ root.Object.Size,
+ rootType.MethodTable,
+ rootType.Name!
+ );
+
+ List pathItems = new();
+
+ GCRoot.ChainLink? current = path;
+ while (current != null)
+ {
+ var obj = context.Heap.GetObject(current.Object);
+
+ var item = new RootPathItem(
+ obj.Address,
+ obj.Type!.MethodTable,
+ obj.Type.Name,
+ obj.Size,
+ context.Heap.GetGeneration(obj.Address));
+
+ pathItems.Add(item);
+
+ current = current.Next;
+ }
+
+ result.Add(new ClrObjectRootPath(rootInfo, pathItems));
+ // TODO get only one root path
+ break;
+ }
+
+ return TypedResults.Ok(result);
+ }
+
+ private static Address? GetFieldObjectAddress(ClrInstanceField field, ulong address)
+ {
+ if (field.Type?.IsObjectReference ?? false)
+ {
+ return new Address(field.ReadObject(address, false).Address);
+ }
+
+ return null;
+ }
+
+ private static string GetFieldValue(ClrInstanceField field, ulong address)
+ {
+ if (field.IsPrimitive)
+ {
+ return field.ElementType switch
+ {
+ ClrElementType.Boolean => field.Read(address, false).ToString(),
+ ClrElementType.Char => field.Read(address, false).ToString(),
+ ClrElementType.Int8 => field.Read(address, false).ToString(),
+ ClrElementType.UInt8 => field.Read(address, false).ToString(),
+ ClrElementType.Int16 => field.Read(address, false).ToString(),
+ ClrElementType.UInt16 => field.Read(address, false).ToString(),
+ ClrElementType.Int32 => field.Read(address, false).ToString(),
+ ClrElementType.UInt32 => field.Read(address, false).ToString(),
+ ClrElementType.Int64 => field.Read(address, false).ToString(),
+ ClrElementType.UInt64 => field.Read(address, false).ToString(),
+ ClrElementType.Float => field.Read(address, false).ToString(CultureInfo.InvariantCulture),
+ ClrElementType.Double => field.Read(address, false).ToString(CultureInfo.InvariantCulture),
+ ClrElementType.NativeInt => field.Read(address, false).ToString(),
+ ClrElementType.NativeUInt => field.Read(address, false).ToString(),
+ _ => throw new ArgumentOutOfRangeException($"Unable to get primitive value for {field.ElementType} field")
+ };
+ }
+
+ return GetAddress();
+
+ string GetAddress()
{
- context.Response.StatusCode = StatusCodes.Status500InternalServerError;
- context.Response.ContentType = MediaTypeNames.Application.Json;
+ if (field.Type?.IsEnum ?? false)
+ {
+ ClrEnum enumField = field.Type.AsEnum();
+ // TODO handle other types
+ if (enumField.ElementType == ClrElementType.Int32)
+ {
+ var fieldValue = field.Read(address, false);
+ var name = enumField.EnumerateValues()
+ .FirstOrDefault(v => (int)v.Value == fieldValue)
+ .Name;
+
+ return !string.IsNullOrEmpty(name)
+ ? name
+ : fieldValue.ToString();
+ }
+ }
- if (context.RequestServices.GetService() is { } problemDetailsService)
+ if (field.Type?.IsObjectReference ?? false)
{
- var exceptionHandlerFeature = context.Features.Get();
- var exceptionType = exceptionHandlerFeature?.Error;
- await problemDetailsService.WriteAsync(new ProblemDetailsContext
+ ClrObject clrObject = field.ReadObject(address, false);
+ if (clrObject.IsNull)
+ {
+ return "";
+ }
+
+ if (clrObject.Type?.IsString ?? false)
+ {
+ return clrObject.AsString(100);
+ }
+
+ if (clrObject is { IsNull: false, Type.Name: "System.Version" })
{
- HttpContext = context,
- ProblemDetails =
- {
- Title = "An error occurred while processing your request.",
- Detail = exceptionType?.Message,
- Type = "https://tools.ietf.org/html/rfc7231#section-6.6.1",
- Status = 500
- }
- });
+ var major = clrObject.ReadField("_Major");
+ var minor = clrObject.ReadField("_Minor");
+ var build = clrObject.ReadField("_Build");
+ var revision = clrObject.ReadField("_Revision");
+ var version = new Version(major, minor, build, revision);
+ return version.ToString();
+ }
+
+ return clrObject.Address.ToString("x16");
}
- });
- });
- app.UseOutputCache();
- app.MapControllers();
- app.Run();
+
+ return string.Empty;
+ }
+ }
}
\ No newline at end of file