├── Sample ├── appsettings.json ├── appsettings.Development.json ├── Sample.csproj ├── Program.cs ├── Controllers │ └── ValuesController.cs └── Startup.cs ├── Diagnostics.Runtime.Middleware ├── IDataTargetProvider.cs ├── MemoryDumps │ ├── IMemoryDumper.cs │ ├── MemoryDumpMiddleware.cs │ ├── LinuxMemoryDumper.cs │ └── WindowsMemoryDumper.cs ├── PlatformServices.cs ├── Diagnostics.Runtime.Middleware.csproj ├── ServiceCollectionExtensions.cs ├── MicrosoftDiagnosticsRuntimeDataTargetProvider.cs ├── TableColumn.cs ├── RuntimeDiagnosticsMiddleware.cs ├── ThreadsDiagnosticsMiddleware.cs ├── ModulesDiagnosticsMiddleware.cs ├── HeapDiagnosticsMiddleware.cs ├── ApplicationBuilderExtensions.cs ├── TableBuilder.cs └── StacksDiagnosticsMiddleware.cs ├── azure-pipelines.yml ├── README.md ├── LICENSE ├── Diagnostics.Runtime.Middleware.sln └── .gitignore /Sample/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | -------------------------------------------------------------------------------- /Sample/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Diagnostics.Runtime.Middleware/IDataTargetProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Diagnostics.Runtime; 3 | 4 | namespace Diagnostics.Runtime.Middleware 5 | { 6 | internal interface IDataTargetProvider : IDisposable 7 | { 8 | DataTarget GetDataTarget(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Diagnostics.Runtime.Middleware/MemoryDumps/IMemoryDumper.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.IO; 3 | 4 | namespace Diagnostics.Runtime.Middleware.MemoryDumps 5 | { 6 | internal interface IMemoryDumper 7 | { 8 | Stream CreateMemoryDump(Process process); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Diagnostics.Runtime.Middleware/PlatformServices.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace Diagnostics.Runtime.Middleware 4 | { 5 | public static class PlatformServices 6 | { 7 | public static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); 8 | public static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sample/Sample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Diagnostics.Runtime.Middleware/Diagnostics.Runtime.Middleware.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Sample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace Sample 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateWebHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # .NET Desktop 2 | # Build and run tests for .NET Desktop or Windows classic desktop solutions. 3 | # Add steps that publish symbols, save build artifacts, and more: 4 | # https://docs.microsoft.com/azure/devops/pipelines/apps/windows/dot-net 5 | 6 | pool: 7 | vmImage: 'VS2017-Win2016' 8 | 9 | variables: 10 | solution: '**/*.sln' 11 | buildPlatform: 'Any CPU' 12 | buildConfiguration: 'Release' 13 | 14 | steps: 15 | - task: NuGetToolInstaller@0 16 | 17 | - task: NuGetCommand@2 18 | inputs: 19 | restoreSolution: '$(solution)' 20 | 21 | - task: VSBuild@1 22 | inputs: 23 | solution: '$(solution)' 24 | platform: '$(buildPlatform)' 25 | configuration: '$(buildConfiguration)' 26 | 27 | - task: VSTest@2 28 | inputs: 29 | platform: '$(buildPlatform)' 30 | configuration: '$(buildConfiguration)' 31 | -------------------------------------------------------------------------------- /Diagnostics.Runtime.Middleware/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Diagnostics.Runtime.Middleware.MemoryDumps; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace Diagnostics.Runtime.Middleware 5 | { 6 | public static class ServiceCollectionExtensions 7 | { 8 | public static IServiceCollection AddClrMd(this IServiceCollection services) 9 | { 10 | services.AddTransient(); 11 | 12 | if (PlatformServices.IsWindows) 13 | { 14 | services.AddTransient(); 15 | } 16 | 17 | else if (PlatformServices.IsLinux) 18 | { 19 | services.AddTransient(); 20 | } 21 | 22 | 23 | return services; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clrmd-middleware 2 | 3 | ASP.NET Core Middleware for introspecting current dotnet process using [Microsoft.Diagnostics.Runtime](https://github.com/Microsoft/clrmd) 4 | 5 | Call services.AddClrMd() in your ConfigureServices Method: 6 | 7 | ```cs 8 | public void ConfigureServices(IServiceCollection services) 9 | { 10 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); 11 | services.AddClrMd(); 12 | } 13 | ``` 14 | 15 | Register the Middleware in your Configure-Method by calling app.UseClrMd() 16 | 17 | ```cs 18 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 19 | { 20 | if (env.IsDevelopment()) 21 | { 22 | app.UseDeveloperExceptionPage(); 23 | } 24 | else 25 | { 26 | app.UseHsts(); 27 | } 28 | 29 | app.UseClrMd(); 30 | 31 | app.UseHttpsRedirection(); 32 | app.UseMvc(); 33 | } 34 | ``` 35 | 36 | calling the diagnostics endpoint under **http://localhost:56868/diagnostics** 37 | 38 | ## Generating Memory Dumps 39 | 40 | http://localhost:56868/diagnostics/dump 41 | 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Stefan Geiger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Diagnostics.Runtime.Middleware/MicrosoftDiagnosticsRuntimeDataTargetProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Microsoft.Diagnostics.Runtime; 3 | 4 | namespace Diagnostics.Runtime.Middleware 5 | { 6 | internal class MicrosoftDiagnosticsRuntimeDataTargetProvider : IDataTargetProvider 7 | { 8 | private DataTarget _target; 9 | 10 | public DataTarget GetDataTarget() 11 | { 12 | if (_target == null) 13 | { 14 | int pid = Process.GetCurrentProcess().Id; 15 | _target = DataTarget.CreateSnapshotAndAttach(pid); 16 | } 17 | 18 | return _target; 19 | } 20 | 21 | private bool disposedValue = false; 22 | protected virtual void Dispose(bool disposing) 23 | { 24 | if (!disposedValue) 25 | { 26 | if (disposing) 27 | { 28 | _target?.Dispose(); 29 | } 30 | 31 | disposedValue = true; 32 | } 33 | } 34 | 35 | public void Dispose() 36 | { 37 | Dispose(true); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Diagnostics.Runtime.Middleware/MemoryDumps/MemoryDumpMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace Diagnostics.Runtime.Middleware.MemoryDumps 6 | { 7 | internal class MemoryDumpMiddleware 8 | { 9 | private readonly RequestDelegate _next; 10 | private readonly IMemoryDumper _memoryDumper; 11 | 12 | public MemoryDumpMiddleware(RequestDelegate next, IMemoryDumper memoryDumper) 13 | { 14 | _next = next; 15 | _memoryDumper = memoryDumper; 16 | } 17 | 18 | public async Task InvokeAsync(HttpContext context) 19 | { 20 | var process = Process.GetCurrentProcess(); 21 | using (var dump = _memoryDumper.CreateMemoryDump(process)) 22 | { 23 | string filename = $"{process.ProcessName}-{process.Id}.dmp"; 24 | 25 | context.Response.Headers.Add("Content-Disposition", new Microsoft.Extensions.Primitives.StringValues($"attachment; filename={filename}")); 26 | await dump.CopyToAsync(context.Response.Body); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sample/Controllers/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace Sample.Controllers 8 | { 9 | [Route("api/[controller]")] 10 | [ApiController] 11 | public class ValuesController : ControllerBase 12 | { 13 | // GET api/values 14 | [HttpGet] 15 | public ActionResult> Get() 16 | { 17 | return new string[] { "value1", "value2" }; 18 | } 19 | 20 | // GET api/values/5 21 | [HttpGet("{id}")] 22 | public ActionResult Get(int id) 23 | { 24 | return "value"; 25 | } 26 | 27 | // POST api/values 28 | [HttpPost] 29 | public void Post([FromBody] string value) 30 | { 31 | } 32 | 33 | // PUT api/values/5 34 | [HttpPut("{id}")] 35 | public void Put(int id, [FromBody] string value) 36 | { 37 | } 38 | 39 | // DELETE api/values/5 40 | [HttpDelete("{id}")] 41 | public void Delete(int id) 42 | { 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sample/Startup.cs: -------------------------------------------------------------------------------- 1 | using Diagnostics.Runtime.Middleware; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | 8 | namespace Sample 9 | { 10 | public class Startup 11 | { 12 | public Startup(IConfiguration configuration) 13 | { 14 | Configuration = configuration; 15 | } 16 | 17 | public IConfiguration Configuration { get; } 18 | 19 | // This method gets called by the runtime. Use this method to add services to the container. 20 | public void ConfigureServices(IServiceCollection services) 21 | { 22 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); 23 | services.AddClrMd(); 24 | } 25 | 26 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 27 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 28 | { 29 | app.UseDeveloperExceptionPage(); 30 | app.UseClrMd(); 31 | app.UseMvc(); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Diagnostics.Runtime.Middleware/TableColumn.cs: -------------------------------------------------------------------------------- 1 | namespace Diagnostics.Runtime.Middleware 2 | { 3 | 4 | internal class TableColumn 5 | { 6 | private readonly object _item; 7 | private string _format; 8 | 9 | private string _link; 10 | 11 | public TableColumn(object item) 12 | { 13 | _item = item; 14 | } 15 | 16 | public TableColumn Link(string link) 17 | { 18 | _link = link; 19 | return this; 20 | } 21 | 22 | public TableColumn Format(string format) 23 | { 24 | _format = format; 25 | return this; 26 | } 27 | 28 | public override string ToString() 29 | { 30 | string value = _item.ToString(); 31 | 32 | if (!string.IsNullOrWhiteSpace(_format)) 33 | { 34 | value = string.Format(_format, _item); 35 | } 36 | 37 | if (!string.IsNullOrWhiteSpace(_link)) 38 | { 39 | value = $"{value}"; 40 | } 41 | 42 | return value; 43 | } 44 | 45 | public static TableColumn Wrap(object item) 46 | { 47 | return new TableColumn(item); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Diagnostics.Runtime.Middleware/RuntimeDiagnosticsMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace Diagnostics.Runtime.Middleware 6 | { 7 | internal class RuntimeDiagnosticsMiddleware 8 | { 9 | private readonly RequestDelegate _next; 10 | private readonly IDataTargetProvider _dataTargetProvider; 11 | 12 | public RuntimeDiagnosticsMiddleware(RequestDelegate next, IDataTargetProvider dataTargetProvider) 13 | { 14 | _next = next; 15 | _dataTargetProvider = dataTargetProvider; 16 | } 17 | 18 | public async Task InvokeAsync(HttpContext context) 19 | { 20 | await _next(context); 21 | 22 | var versions = _dataTargetProvider.GetDataTarget().ClrVersions; 23 | 24 | string content = TableBuilder.CreateDataTable("Clr Info", versions.Select(f => new { 25 | ClrVersion = f.Version.ToString(), 26 | Flavor = f.Flavor, 27 | LocalMatchingDac = f.LocalMatchingDac, 28 | DacInfoFileName = f.DacInfo.FileName, 29 | DacInfoFileSize = f.DacInfo.FileSize, 30 | Architecture = f.DacInfo.TargetArchitecture, 31 | })); 32 | 33 | await context.Response.WriteAsync(content); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Diagnostics.Runtime.Middleware/ThreadsDiagnosticsMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.Diagnostics.Runtime; 5 | 6 | namespace Diagnostics.Runtime.Middleware 7 | { 8 | internal class ThreadsDiagnosticsMiddleware 9 | { 10 | private readonly RequestDelegate _next; 11 | private readonly IDataTargetProvider _dataTargetProvider; 12 | 13 | public ThreadsDiagnosticsMiddleware(RequestDelegate next, IDataTargetProvider dataTargetProvider) 14 | { 15 | _next = next; 16 | _dataTargetProvider = dataTargetProvider; 17 | } 18 | 19 | public async Task InvokeAsync(HttpContext context) 20 | { 21 | ClrInfo runtimeInfo = _dataTargetProvider.GetDataTarget().ClrVersions[0]; 22 | ClrRuntime runtime = runtimeInfo.CreateRuntime(); 23 | var content = TableBuilder.CreateDataTable("Threads", runtime.Threads.Select(f => new 24 | { 25 | ThreadId = TableColumn.Wrap(f.OSThreadId).Link($"{context.Request.PathBase.Value}/stacks?id={f.OSThreadId}"), 26 | GcMode = f.GcMode, 27 | Runtime = f.Runtime.ToString(), 28 | AppDomains = string.Join(",", f.Runtime.AppDomains.Select(a => a.Name)) 29 | })); 30 | 31 | await _next(context); 32 | await context.Response.WriteAsync(content); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Diagnostics.Runtime.Middleware/ModulesDiagnosticsMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.Diagnostics.Runtime; 5 | 6 | namespace Diagnostics.Runtime.Middleware 7 | { 8 | internal class ModulesDiagnosticsMiddleware 9 | { 10 | private readonly RequestDelegate _next; 11 | private readonly IDataTargetProvider _dataTargetProvider; 12 | 13 | public ModulesDiagnosticsMiddleware(RequestDelegate next, IDataTargetProvider dataTargetProvider) 14 | { 15 | _next = next; 16 | _dataTargetProvider = dataTargetProvider; 17 | } 18 | 19 | public async Task InvokeAsync(HttpContext context) 20 | { 21 | ClrInfo runtimeInfo = _dataTargetProvider.GetDataTarget().ClrVersions[0]; 22 | ClrRuntime runtime = runtimeInfo.CreateRuntime(); 23 | var content = TableBuilder.CreateDataTable("Modules", runtime.Modules.Select(f => new 24 | { 25 | AssemblyId = f.AssemblyId, 26 | AssemblyName = f.AssemblyName, 27 | DebuggingMode = f.DebuggingMode, 28 | FileName = f.FileName, 29 | ImageBase = f.ImageBase, 30 | MetadataAddress = f.MetadataAddress, 31 | Name = f.Name, 32 | Runtime = f.Runtime, 33 | })); 34 | 35 | await _next(context); 36 | await context.Response.WriteAsync(content); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Diagnostics.Runtime.Middleware/HeapDiagnosticsMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.Diagnostics.Runtime; 5 | 6 | namespace Diagnostics.Runtime.Middleware 7 | { 8 | internal class HeapDiagnosticsMiddleware 9 | { 10 | private readonly RequestDelegate _next; 11 | private readonly IDataTargetProvider _dataTargetProvider; 12 | 13 | public HeapDiagnosticsMiddleware(RequestDelegate next, IDataTargetProvider dataTargetProvider) 14 | { 15 | _next = next; 16 | _dataTargetProvider = dataTargetProvider; 17 | } 18 | 19 | public async Task InvokeAsync(HttpContext context) 20 | { 21 | ClrInfo runtimeInfo = _dataTargetProvider.GetDataTarget().ClrVersions[0]; 22 | ClrRuntime runtime = runtimeInfo.CreateRuntime(); 23 | var stats = from o in runtime.Heap.EnumerateObjects() 24 | let t = o.Type 25 | group o by t into g 26 | let size = g.Sum(o => (uint)o.Size) 27 | select new 28 | { 29 | Name = g.Key.Name, 30 | Size = size, 31 | Count = g.Count() 32 | }; 33 | 34 | var content = TableBuilder.CreateDataTable("Heap", stats.OrderByDescending(f => f.Size).Select(f => new 35 | { 36 | Size = f.Size, 37 | Count = f.Count.ToString(), 38 | Name = f.Name 39 | })); 40 | 41 | await _next(context); 42 | await context.Response.WriteAsync(content); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Diagnostics.Runtime.Middleware/ApplicationBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Diagnostics.Runtime.Middleware.MemoryDumps; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace Diagnostics.Runtime.Middleware 6 | { 7 | public static class ApplicationBuilderExtensions 8 | { 9 | public static IApplicationBuilder UseClrMd(this IApplicationBuilder builder) 10 | { 11 | string basePath = "/diagnostics"; 12 | 13 | builder.Map(new PathString($"{basePath}/stacks"), x => x.UseMiddleware()); 14 | builder.Map(new PathString($"{basePath}/runtime"), x => x.UseMiddleware()); 15 | builder.Map(new PathString($"{basePath}/heap"), x => x.UseMiddleware()); 16 | builder.Map(new PathString($"{basePath}/threads"), x => x.UseMiddleware()); 17 | builder.Map(new PathString($"{basePath}/modules"), x => x.UseMiddleware()); 18 | 19 | if (PlatformServices.IsLinux || PlatformServices.IsWindows) 20 | { 21 | builder.Map(new PathString($"{basePath}/dump"), x => x.UseMiddleware()); 22 | } 23 | 24 | 25 | builder.Map(new PathString(basePath), x => 26 | x.UseMiddleware() 27 | .UseMiddleware() 28 | .UseMiddleware() 29 | .UseMiddleware() 30 | .UseMiddleware() 31 | ); 32 | 33 | return builder; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /Diagnostics.Runtime.Middleware/TableBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Reflection; 3 | using System.Text; 4 | 5 | namespace Diagnostics.Runtime.Middleware 6 | { 7 | internal static class TableBuilder 8 | { 9 | public static string CreateDataTable(string title, IEnumerable data) 10 | { 11 | bool createHeader = true; 12 | PropertyInfo[] properties = null; 13 | 14 | StringBuilder builder = new StringBuilder(); 15 | builder.AppendLine($"

{title}


"); 16 | 17 | builder.AppendLine(""); 18 | foreach (var record in data) 19 | { 20 | if (createHeader) 21 | { 22 | properties = record.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); 23 | foreach (var header in properties) 24 | { 25 | builder.Append($""); 26 | } 27 | 28 | builder.AppendLine(""); 29 | createHeader = false; 30 | } 31 | 32 | 33 | builder.AppendLine(""); 34 | foreach (var property in properties) 35 | { 36 | object value = property.GetValue(record); 37 | if (value is TableColumn) 38 | { 39 | builder.Append($""); 40 | } 41 | else 42 | { 43 | builder.Append($""); 44 | } 45 | } 46 | builder.AppendLine(""); 47 | } 48 | 49 | builder.AppendLine("
{header.Name}
{value.ToString()}{value}
"); 50 | 51 | 52 | return builder.ToString(); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Diagnostics.Runtime.Middleware.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.106 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Diagnostics.Runtime.Middleware", "Diagnostics.Runtime.Middleware\Diagnostics.Runtime.Middleware.csproj", "{44FC2B9A-6251-481D-B27B-635045D244EB}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample", "Sample\Sample.csproj", "{50BC0219-FA34-4DB2-942D-046E7E1F6F69}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{1EF2AA13-7B8F-4043-BD4E-11EC7150816E}" 11 | ProjectSection(SolutionItems) = preProject 12 | README.md = README.md 13 | EndProjectSection 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {44FC2B9A-6251-481D-B27B-635045D244EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {44FC2B9A-6251-481D-B27B-635045D244EB}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {44FC2B9A-6251-481D-B27B-635045D244EB}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {44FC2B9A-6251-481D-B27B-635045D244EB}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {50BC0219-FA34-4DB2-942D-046E7E1F6F69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {50BC0219-FA34-4DB2-942D-046E7E1F6F69}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {50BC0219-FA34-4DB2-942D-046E7E1F6F69}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {50BC0219-FA34-4DB2-942D-046E7E1F6F69}.Release|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | SolutionGuid = {6FEDDF65-F90D-40A2-A82A-DE8285EE0871} 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /Diagnostics.Runtime.Middleware/StacksDiagnosticsMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.Diagnostics.Runtime; 5 | using Microsoft.Extensions.Primitives; 6 | 7 | namespace Diagnostics.Runtime.Middleware 8 | { 9 | internal class StacksDiagnosticsMiddleware 10 | { 11 | private readonly RequestDelegate _next; 12 | private readonly IDataTargetProvider _dataTargetProvider; 13 | 14 | public StacksDiagnosticsMiddleware(RequestDelegate next, IDataTargetProvider dataTargetProvider) 15 | { 16 | _next = next; 17 | _dataTargetProvider = dataTargetProvider; 18 | } 19 | 20 | public async Task InvokeAsync(HttpContext context) 21 | { 22 | await _next(context); 23 | 24 | ClrInfo runtimeInfo = _dataTargetProvider.GetDataTarget().ClrVersions[0]; 25 | ClrRuntime runtime = runtimeInfo.CreateRuntime(); 26 | 27 | if (context.Request.Query.TryGetValue("id", out StringValues value)) 28 | { 29 | var threadId = value.ToString(); 30 | ClrThread thread = runtime.Threads.FirstOrDefault(f => f.OSThreadId.ToString() == threadId); 31 | if (thread != null) 32 | { 33 | string content = TableBuilder.CreateDataTable($"Stack for Thread {threadId}", thread.StackTrace.Select(f => new 34 | { 35 | InstructionPointer = TableColumn.Wrap(f.InstructionPointer).Format("{0,12:X}"), 36 | StackPointer = TableColumn.Wrap(f.StackPointer).Format("{0,12:X}"), 37 | DisplayString = f.DisplayString, 38 | Method = f.Method, 39 | ModuleName = f.ModuleName, 40 | })); 41 | 42 | await context.Response.WriteAsync(content); 43 | } 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Diagnostics.Runtime.Middleware/MemoryDumps/LinuxMemoryDumper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Diagnostics.Runtime.Middleware.MemoryDumps 8 | { 9 | /// 10 | /// From https://github.com/aspnet/AspLabs/blob/master/src/DotNetDiagnostics/src/dotnet-dump/Dumper.Linux.cs 11 | /// 12 | internal class LinuxMemoryDumper : IMemoryDumper 13 | { 14 | public Stream CreateMemoryDump(Process process) 15 | { 16 | string tempPath = Path.GetTempFileName(); 17 | 18 | Linux.CollectDumpAsync(process, tempPath).Wait(); 19 | 20 | var fileStream = new FileStream(tempPath, FileMode.Open, FileAccess.Read, FileShare.None); 21 | fileStream.Seek(0, SeekOrigin.Begin); 22 | return fileStream; 23 | } 24 | 25 | private static class Linux 26 | { 27 | internal static async Task CollectDumpAsync(Process process, string fileName) 28 | { 29 | // We don't work on WSL :( 30 | var ostype = File.ReadAllText("/proc/sys/kernel/osrelease"); 31 | if (ostype.Contains("Microsoft")) 32 | { 33 | throw new PlatformNotSupportedException("Cannot collect memory dumps from Windows Subsystem for Linux."); 34 | } 35 | 36 | // First step is to find the .NET runtime. To do this we look for coreclr.so 37 | var coreclr = process.Modules.Cast().FirstOrDefault(m => string.Equals(m.ModuleName, "libcoreclr.so")); 38 | if (coreclr == null) 39 | { 40 | throw new NotSupportedException("Unable to locate .NET runtime associated with this process!"); 41 | } 42 | 43 | // Find createdump next to that file 44 | var runtimeDirectory = Path.GetDirectoryName(coreclr.FileName); 45 | var createDumpPath = Path.Combine(runtimeDirectory, "createdump"); 46 | if (!File.Exists(createDumpPath)) 47 | { 48 | throw new NotSupportedException($"Unable to locate 'createdump' tool in '{runtimeDirectory}'"); 49 | } 50 | 51 | // Create the dump 52 | var exitCode = await CreateDumpAsync(createDumpPath, fileName, process.Id); 53 | if (exitCode != 0) 54 | { 55 | throw new Exception($"createdump exited with non-zero exit code: {exitCode}"); 56 | } 57 | } 58 | 59 | private static Task CreateDumpAsync(string exePath, string fileName, int processId) 60 | { 61 | var tcs = new TaskCompletionSource(); 62 | var createdump = new Process() 63 | { 64 | StartInfo = new ProcessStartInfo() 65 | { 66 | FileName = exePath, 67 | Arguments = $"-f {fileName} {processId}", 68 | RedirectStandardError = true, 69 | RedirectStandardOutput = true, 70 | RedirectStandardInput = true, 71 | }, 72 | EnableRaisingEvents = true, 73 | }; 74 | createdump.Exited += (s, a) => tcs.TrySetResult(createdump.ExitCode); 75 | createdump.Start(); 76 | return tcs.Task; 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Diagnostics.Runtime.Middleware/MemoryDumps/WindowsMemoryDumper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Runtime.InteropServices; 5 | using System.Threading.Tasks; 6 | using Microsoft.Win32.SafeHandles; 7 | 8 | namespace Diagnostics.Runtime.Middleware.MemoryDumps 9 | { 10 | /// 11 | /// From https://github.com/aspnet/AspLabs/blob/master/src/DotNetDiagnostics/src/dotnet-dump/Dumper.Windows.cs 12 | /// 13 | internal class WindowsMemoryDumper : IMemoryDumper 14 | { 15 | public Stream CreateMemoryDump(Process process) 16 | { 17 | string tempPath = Path.GetTempFileName(); 18 | 19 | // Open the file for writing 20 | 21 | var fileStream = new FileStream(tempPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None); 22 | 23 | // Dump the process! 24 | var exceptionInfo = new NativeMethods.MINIDUMP_EXCEPTION_INFORMATION(); 25 | if (!NativeMethods.MiniDumpWriteDump(process.Handle, 26 | (uint) process.Id, 27 | fileStream.SafeFileHandle, 28 | NativeMethods.MINIDUMP_TYPE.MiniDumpWithFullMemory, 29 | ref exceptionInfo, 30 | IntPtr.Zero, 31 | IntPtr.Zero)) 32 | { 33 | var err = Marshal.GetHRForLastWin32Error(); 34 | Marshal.ThrowExceptionForHR(err); 35 | } 36 | 37 | fileStream.Seek(0, SeekOrigin.Begin); 38 | return fileStream; 39 | } 40 | 41 | 42 | private static class NativeMethods 43 | { 44 | [DllImport("Dbghelp.dll")] 45 | public static extern bool MiniDumpWriteDump(IntPtr hProcess, uint ProcessId, SafeFileHandle hFile, MINIDUMP_TYPE DumpType, ref MINIDUMP_EXCEPTION_INFORMATION ExceptionParam, IntPtr UserStreamParam, IntPtr CallbackParam); 46 | 47 | [StructLayout(LayoutKind.Sequential, Pack = 4)] 48 | public struct MINIDUMP_EXCEPTION_INFORMATION 49 | { 50 | public uint ThreadId; 51 | public IntPtr ExceptionPointers; 52 | public int ClientPointers; 53 | } 54 | 55 | [Flags] 56 | public enum MINIDUMP_TYPE : uint 57 | { 58 | MiniDumpNormal = 0, 59 | MiniDumpWithDataSegs = 1 << 0, 60 | MiniDumpWithFullMemory = 1 << 1, 61 | MiniDumpWithHandleData = 1 << 2, 62 | MiniDumpFilterMemory = 1 << 3, 63 | MiniDumpScanMemory = 1 << 4, 64 | MiniDumpWithUnloadedModules = 1 << 5, 65 | MiniDumpWithIndirectlyReferencedMemory = 1 << 6, 66 | MiniDumpFilterModulePaths = 1 << 7, 67 | MiniDumpWithProcessThreadData = 1 << 8, 68 | MiniDumpWithPrivateReadWriteMemory = 1 << 9, 69 | MiniDumpWithoutOptionalData = 1 << 10, 70 | MiniDumpWithFullMemoryInfo = 1 << 11, 71 | MiniDumpWithThreadInfo = 1 << 12, 72 | MiniDumpWithCodeSegs = 1 << 13, 73 | MiniDumpWithoutAuxiliaryState = 1 << 14, 74 | MiniDumpWithFullAuxiliaryState = 1 << 15, 75 | MiniDumpWithPrivateWriteCopyMemory = 1 << 16, 76 | MiniDumpIgnoreInaccessibleMemory = 1 << 17, 77 | MiniDumpWithTokenInformation = 1 << 18, 78 | MiniDumpWithModuleHeaders = 1 << 19, 79 | MiniDumpFilterTriage = 1 << 20, 80 | MiniDumpWithAvxXStateContext = 1 << 21, 81 | MiniDumpWithIptTrace = 1 << 22, 82 | MiniDumpValidTypeFlags = (-1) ^ ((~1) << 22) 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | --------------------------------------------------------------------------------