From 6b101c405ec30d8f841d74d8e416bd4ac2b19f7e Mon Sep 17 00:00:00 2001 From: Sainath Reddy G N V <144475740+sainath-reddy-gnv@users.noreply.github.com> Date: Mon, 4 Mar 2024 15:55:19 -0500 Subject: [PATCH] added new command to dump all active incoming http requests (#4536) Hello everyone, I created a simple extension to dump out all active incoming http requests. I'm also planning to add time spent column but need help in getting the dump time stamp. Any idea which api to use? ![image](https://github.com/dotnet/diagnostics/assets/144475740/df9cac92-20c4-4afd-a782-2a824914a5f2) --------- Co-authored-by: Sainath Reddy G N V --- .../DumpHttpRequestsCommand.cs | 77 +++++++++++++++++++ src/SOS/Strike/managedcommands.cpp | 6 ++ src/SOS/Strike/sos.def | 2 + src/SOS/lldbplugin/soscommand.cpp | 1 + 4 files changed, 86 insertions(+) create mode 100644 src/Microsoft.Diagnostics.ExtensionCommands/DumpHttpRequestsCommand.cs diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/DumpHttpRequestsCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/DumpHttpRequestsCommand.cs new file mode 100644 index 000000000..9eb932c47 --- /dev/null +++ b/src/Microsoft.Diagnostics.ExtensionCommands/DumpHttpRequestsCommand.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using Microsoft.Diagnostics.DebugServices; +using Microsoft.Diagnostics.ExtensionCommands.Output; +using Microsoft.Diagnostics.Runtime; +using static Microsoft.Diagnostics.ExtensionCommands.Output.ColumnKind; + +namespace Microsoft.Diagnostics.ExtensionCommands +{ + [Command(Name = "dumphttprequests", Aliases = new string[] { "DumpHttpRequests" }, Help = "Shows all currently active incoming HTTP requests.")] + public class DumpHttpRequestsCommand : ClrRuntimeCommandBase + { + public override void Invoke() + { + List<(ulong Address, string Method, string Protocol, string Url)> requests = new(); + if (Runtime.Heap.CanWalkHeap) + { + foreach (ClrObject obj in Runtime.Heap.EnumerateObjects()) + { + Console.CancellationToken.ThrowIfCancellationRequested(); + + if (!obj.IsValid || obj.IsNull) + { + continue; + } + + if (obj.Type?.Name?.Equals("Microsoft.AspNetCore.Http.DefaultHttpContext") ?? false) + { + ClrObject collection = obj.ReadValueTypeField("_features").ReadObjectField("k__BackingField"); + if (!collection.IsNull) + { + string method = collection.ReadStringField("k__BackingField") ?? ""; + string scheme = collection.ReadStringField("k__BackingField") ?? ""; + string path = collection.ReadStringField("k__BackingField") ?? ""; + string query = collection.ReadStringField("k__BackingField") ?? ""; + requests.Add((obj.Address, method, $"{scheme}", $"{path}{query}")); + } + } + } + } + else + { + Console.WriteLine("The GC heap is not in a valid state for traversal."); + } + + if (requests.Count > 0) + { + PrintRequests(requests); + Console.WriteLine($"Found {requests.Count} active requests"); + } + else + { + Console.WriteLine("No requests found"); + } + } + + public void PrintRequests(List<(ulong Address, string Method, string scheme, string Url)> requests) + { + Column methodColumn = Text.GetAppropriateWidth(requests.Select(r => r.Method)).WithAlignment(Align.Left); + Column schemeColumn = Text.GetAppropriateWidth(requests.Select(r => r.scheme)).WithAlignment(Align.Left); + Column urlColumn = Text.GetAppropriateWidth(requests.Select(r => r.Url)).WithAlignment(Align.Left); + Table output = new(Console, DumpObj.WithAlignment(Align.Left), methodColumn, schemeColumn, urlColumn); ; + output.WriteHeader("Address", "Method", "Scheme", "Url"); + + foreach ((ulong address, string method, string scheme, string url) in requests) + { + Console.CancellationToken.ThrowIfCancellationRequested(); + output.WriteRow(address, method, scheme, url); + } + } + } +} diff --git a/src/SOS/Strike/managedcommands.cpp b/src/SOS/Strike/managedcommands.cpp index 2a7b33615..a255795cd 100644 --- a/src/SOS/Strike/managedcommands.cpp +++ b/src/SOS/Strike/managedcommands.cpp @@ -180,6 +180,12 @@ DECLARE_API(sizestats) return ExecuteManagedOnlyCommand("sizestats", args); } +DECLARE_API(DumpHttpRequests) +{ + INIT_API_EXT(); + return ExecuteManagedOnlyCommand("dumphttprequests", args); +} + typedef HRESULT (*PFN_COMMAND)(PDEBUG_CLIENT client, PCSTR args); // diff --git a/src/SOS/Strike/sos.def b/src/SOS/Strike/sos.def index 7f594e187..05c873d3e 100644 --- a/src/SOS/Strike/sos.def +++ b/src/SOS/Strike/sos.def @@ -29,6 +29,8 @@ EXPORTS DumpDomain dumpdomain=DumpDomain dumpexceptions + DumpHttpRequests + dumphttprequests=DumpHttpRequests #ifdef TRACE_GC DumpGCLog dumpgclog=DumpGCLog diff --git a/src/SOS/lldbplugin/soscommand.cpp b/src/SOS/lldbplugin/soscommand.cpp index 95847b9bc..7edf9fca7 100644 --- a/src/SOS/lldbplugin/soscommand.cpp +++ b/src/SOS/lldbplugin/soscommand.cpp @@ -173,6 +173,7 @@ sosCommandInitialize(lldb::SBDebugger debugger) g_services->AddCommand("dumpdomain", new sosCommand("DumpDomain"), "Displays information about the all assemblies within all the AppDomains or the specified one."); g_services->AddCommand("dumpgcdata", new sosCommand("DumpGCData"), "Displays information about the GC data."); g_services->AddManagedCommand("dumpheap", "Displays info about the garbage-collected heap and collection statistics about objects."); + g_services->AddManagedCommand("dumphttprequests", "Shows all currently active incoming HTTP requests."); g_services->AddCommand("dumpil", new sosCommand("DumpIL"), "Displays the Microsoft intermediate language (MSIL) that's associated with a managed method."); g_services->AddCommand("dumplog", new sosCommand("DumpLog"), "Writes the contents of an in-memory stress log to the specified file."); g_services->AddCommand("dumpmd", new sosCommand("DumpMD"), "Displays information about a MethodDesc structure at the specified address."); -- 2.34.1