From 4c348bb62ebd3e17a5fc1c216256726e57652ac1 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sat, 18 Apr 2020 23:25:59 -0400 Subject: [PATCH 1/5] ASP.NET Core 3.x: use Diagnostic bits This adds timings based on the DiagnosticListener bits overhauled in https://github.com/dotnet/aspnetcore/pull/11730 with some further cleanup in https://github.com/dotnet/aspnetcore/commit/b23ea5b6683b08e8d168ccf49e0fc8515077ee2e This would also resolve #162 and #322. --- .../MvcDiagnosticListener.cs | 166 ++++++++++++++++++ .../MvcExtensions.cs | 27 +-- 2 files changed, 183 insertions(+), 10 deletions(-) create mode 100644 src/MiniProfiler.AspNetCore.Mvc/MvcDiagnosticListener.cs diff --git a/src/MiniProfiler.AspNetCore.Mvc/MvcDiagnosticListener.cs b/src/MiniProfiler.AspNetCore.Mvc/MvcDiagnosticListener.cs new file mode 100644 index 000000000..f9ab85ef8 --- /dev/null +++ b/src/MiniProfiler.AspNetCore.Mvc/MvcDiagnosticListener.cs @@ -0,0 +1,166 @@ +#if NETCOREAPP3_0 +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Diagnostics; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Razor; +using StackExchange.Profiling.Internal; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; + +namespace StackExchange.Profiling.Data +{ + /// + /// Diagnostic listener for Microsoft.AspNetCore.Mvc.* events + /// + /// + /// See: + /// - https://github.com/dotnet/aspnetcore/blob/v3.0.0/src/Mvc/Mvc.Core/src/MvcCoreDiagnosticListenerExtensions.cs + /// - https://github.com/dotnet/aspnetcore/blob/v3.0.0/src/Mvc/Mvc.Core/src/Diagnostics/MvcDiagnostics.cs + /// - https://github.com/dotnet/aspnetcore/blob/v3.0.0/src/Mvc/Mvc.Razor/src/Diagnostics/MvcDiagnostics.cs + /// + public class MvcDiagnosticListener : IMiniProfilerDiagnosticListener + { + /// + /// Diagnostic Listener name to handle. + /// + public string ListenerName => "Microsoft.AspNetCore"; + + /// + /// Notifies the observer that the provider has finished sending push-based notifications. + /// + public void OnCompleted() { } + + /// + /// Notifies the observer that the provider has experienced an error condition. + /// + /// An object that provides additional information about the error. + public void OnError(Exception error) => Trace.WriteLine(error); + + private static string GetName(string label, ActionDescriptor descriptor) + { + var controller = descriptor.RouteValues.TryGetValue("controller", out var c) ? c : "UnknownController"; + var action = descriptor.RouteValues.TryGetValue("action", out var a) ? a : "UnknownAction"; + + // TODO: Don't allocate this string more than once + return label + ": " + controller + "/" + action; + } + + private static string GetName(IActionResult result) => result switch + { + ViewResult vr => vr.ViewName.HasValue() ? "View: " + vr.ViewName : "ViewResult", + ContentResult cr => cr.ContentType.HasValue() ? "Content: " + cr.ContentType : "ContentResult", + // TODO: Other main ones? + _ => "Result: " + result.GetType().Name + }; + + private static string GetName(IFilterMetadata filter) => filter.GetType().Name; + + /// + /// Stores the current timing in the tree, on each request. + /// + private readonly AsyncLocal<(object State, Timing Timing)> CurrentTiming = new AsyncLocal<(object, Timing)>(); + + private object Start(T state, string stepName) where T : class + { + CurrentTiming.Value = (state, MiniProfiler.Current.Step(stepName)); + return null; + } + + private object Complete(T state) where T : class + { + if (CurrentTiming.Value.State is T currentState && currentState == state) + { + using (CurrentTiming.Value.Timing) { } + } + return null; + } + + /// + /// Provides the observer with new data. + /// + /// The current notification information. + public void OnNext(KeyValuePair kv) => _ = kv.Value switch + { + // MVC Bits: https://github.com/dotnet/aspnetcore/blob/v3.0.0/src/Mvc/Mvc.Core/src/Diagnostics/MvcDiagnostics.cs + // ActionEvent + BeforeActionEventData data => Start(data.ActionDescriptor, GetName("Overall Action", data.ActionDescriptor)), + AfterActionEventData data => Complete(data.ActionDescriptor), + // ControllerActionMethod + BeforeControllerActionMethodEventData data => Start(data.ActionContext.ActionDescriptor, GetName("Controller Action", data.ActionContext.ActionDescriptor)), + AfterControllerActionMethodEventData data => Complete(data.ActionContext.ActionDescriptor), + // ActionResultEvent + BeforeActionResultEventData data => Start(data.Result, GetName(data.Result)), + AfterActionResultEventData data => Complete(data.Result), + + // AuthorizationFilterOnAuthorization + BeforeAuthorizationFilterOnAuthorizationEventData data => Start(data.Filter, "Auth Filter: " + GetName(data.Filter)), + AfterAuthorizationFilterOnAuthorizationEventData data => Complete(data.Filter), + + // ResourceFilterOnResourceExecution + BeforeResourceFilterOnResourceExecutionEventData data => Start(data.Filter, "Resource Filter (Exec): " + GetName(data.Filter)), + AfterResourceFilterOnResourceExecutionEventData data => Complete(data.Filter), + // ResourceFilterOnResourceExecuting + BeforeResourceFilterOnResourceExecutingEventData data => Start(data.Filter, "Resource Filter (Execing): " + GetName(data.Filter)), + AfterResourceFilterOnResourceExecutingEventData data => Complete(data.Filter), + // ResourceFilterOnResourceExecuted + BeforeResourceFilterOnResourceExecutedEventData data => Start(data.Filter, "Resource Filter (Execed): " + GetName(data.Filter)), + AfterResourceFilterOnResourceExecutedEventData data => Complete(data.Filter), + + // ExceptionFilterOnException + BeforeExceptionFilterOnException data => Start(data.Filter, "Exception Filter: " + GetName(data.Filter)), + AfterExceptionFilterOnExceptionEventData data => Complete(data.Filter), + + // ActionFilterOnActionExecution + BeforeActionFilterOnActionExecutionEventData data => Start(data.Filter, "Action Filter (Exec): " + GetName(data.Filter)), + AfterActionFilterOnActionExecutionEventData data => Complete(data.Filter), + // ActionFilterOnActionExecuting + BeforeActionFilterOnActionExecutingEventData data => Start(data.Filter, "Action Filter (Execing): " + GetName(data.Filter)), + AfterActionFilterOnActionExecutingEventData data => Complete(data.Filter), + // ActionFilterOnActionExecuted + BeforeActionFilterOnActionExecutedEventData data => Start(data.Filter, "Action Filter (Execed): " + GetName(data.Filter)), + AfterActionFilterOnActionExecutedEventData data => Complete(data.Filter), + + // ResultFilterOnResultExecution + BeforeResultFilterOnResultExecutionEventData data => Start(data.Filter, "Result Filter (Exec): " + GetName(data.Filter)), + AfterResultFilterOnResultExecutionEventData data => Complete(data.Filter), + // ResultFilterOnResultExecuting + BeforeResultFilterOnResultExecutingEventData data => Start(data.Filter, "Result Filter (Execing): " + GetName(data.Filter)), + AfterResultFilterOnResultExecutingEventData data => Complete(data.Filter), + // ResultFilterOnResultExecuted + BeforeResultFilterOnResultExecutedEventData data => Start(data.Filter, "Result Filter (Execed): " + GetName(data.Filter)), + AfterResultFilterOnResultExecutedEventData data => Complete(data.Filter), + + // Razor Bits: https://github.com/dotnet/aspnetcore/blob/v3.0.0/src/Mvc/Mvc.Razor/src/Diagnostics/MvcDiagnostics.cs + // ViewPage + BeforeViewPageEventData data => Start(data.Page, "View: " + data.Page.Path), + AfterViewPageEventData data => Complete(data.Page), + + // RazorPage Bits: https://github.com/dotnet/aspnetcore/blob/v3.0.0/src/Mvc/Mvc.RazorPages/src/Diagnostics/MvcDiagnostics.cs + // HandlerMethod + BeforeHandlerMethodEventData data => Start(data.Instance, "Handler: " + data.HandlerMethodDescriptor.Name), + AfterHandlerMethodEventData data => Complete(data.Instance), + + // PageFilterOnPageHandlerExecution + BeforePageFilterOnPageHandlerExecutionEventData data => Start(data.Filter, "Filter (Exec): " + GetName(data.Filter)), + AfterPageFilterOnPageHandlerExecutionEventData data => Complete(data.Filter), + // PageFilterOnPageHandlerExecuting + BeforePageFilterOnPageHandlerExecutingEventData data => Start(data.Filter, "Filter (Execing): " + GetName(data.Filter)), + AfterPageFilterOnPageHandlerExecutingEventData data => Complete(data.Filter), + // PageFilterOnPageHandlerExecuted + BeforePageFilterOnPageHandlerExecutedEventData data => Start(data.Filter, "Filter (Execed): " + GetName(data.Filter)), + AfterPageFilterOnPageHandlerExecutedEventData data => Complete(data.Filter), + + // PageFilterOnPageHandlerSelection + BeforePageFilterOnPageHandlerSelectionEventData data => Start(data.Filter, "Filter (Selection): " + GetName(data.Filter)), + AfterPageFilterOnPageHandlerSelectionEventData data => Complete(data.Filter), + // PageFilterOnPageHandlerSelected + BeforePageFilterOnPageHandlerSelectedEventData data => Start(data.Filter, "Filter (Selected): " + GetName(data.Filter)), + AfterPageFilterOnPageHandlerSelectedEventData data => Complete(data.Filter), + _ => null + }; + } +} +#endif diff --git a/src/MiniProfiler.AspNetCore.Mvc/MvcExtensions.cs b/src/MiniProfiler.AspNetCore.Mvc/MvcExtensions.cs index 1f2e888a9..c71f98472 100644 --- a/src/MiniProfiler.AspNetCore.Mvc/MvcExtensions.cs +++ b/src/MiniProfiler.AspNetCore.Mvc/MvcExtensions.cs @@ -1,12 +1,17 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ViewComponents; -using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using StackExchange.Profiling; using StackExchange.Profiling.Internal; using StackExchange.Profiling.Storage; using System; +#if NETCOREAPP3_0 +using StackExchange.Profiling.Data; +#else +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ViewComponents; +#endif + namespace Microsoft.Extensions.DependencyInjection { /// @@ -23,22 +28,22 @@ public static IMiniProfilerBuilder AddMiniProfiler(this IServiceCollection servi { services.AddMemoryCache(); // Unconditionally register an IMemoryCache since it's the most common and default case services.AddSingleton, MiniProfilerOptionsDefaults>(); - services.AddSingleton(); // For any IMiniProfilerDiagnosticListener registration if (configureOptions != null) { services.Configure(configureOptions); } // Set background statics services.Configure(o => MiniProfiler.Configure(o)); + services.AddSingleton(); // For any IMiniProfilerDiagnosticListener registration - // See https://github.com/MiniProfiler/dotnet/issues/162 for plans - // Blocked on https://github.com/aspnet/Mvc/issues/6222 - //services.AddSingleton(); +#if NETCOREAPP3_0 + services.AddSingleton(); // For view and action profiling +#else services.AddTransient, MiniProfilerSetup>() - .AddTransient, MiniProfilerSetup>(); -#if !NETCOREAPP3_0 - services.AddTransient(); + .AddTransient, MiniProfilerSetup>() + .AddTransient(); #endif + return new MiniProfilerBuilder(services); } } @@ -60,6 +65,7 @@ public void Configure(MiniProfilerOptions options) } } +#if !NETCOREAPP3_0 internal class MiniProfilerSetup : IConfigureOptions, IConfigureOptions { public void Configure(MvcViewOptions options) @@ -75,4 +81,5 @@ public void Configure(MvcOptions options) options.Filters.Add(new ProfilingActionFilter()); } } +#endif } From 1232f621df721bda96b69b459cb1c4a90b884713 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sat, 18 Apr 2020 23:37:28 -0400 Subject: [PATCH 2/5] Add to release notes --- docs/Releases.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Releases.md b/docs/Releases.md index 1201a8fef..ee1189603 100644 --- a/docs/Releases.md +++ b/docs/Releases.md @@ -12,12 +12,12 @@ This page tracks major changes included in any update starting with version 4.0. - Generally moves to CSS 3 variables, for easier custom themes as well ([#451](https://github.com/MiniProfiler/dotnet/pull/451)) - Added `SqlServerFormatter.IncludeParameterValues` for excluding actual values in output if desired ([#463](https://github.com/MiniProfiler/dotnet/pull/463)) - (**.NET Core only**) Added `MiniProfilerOptions.ResultsAuthorizeAsync` and `MiniProfiler.ResultsAuthorizeListAsync` ([#472](https://github.com/MiniProfiler/dotnet/pull/472)) + - (**.NET Core only**) Added profiling to all diagnostic events (views, filters, etc. - [#475](https://github.com/MiniProfiler/dotnet/pull/475)) - **Fixes/Changes**: - Fix for ['i.Started.toUTCString is not a function'](https://github.com/MiniProfiler/dotnet/pull/462) when global serializer options are changed. - Removed jQuery (built-in) dependency ([#442](https://github.com/MiniProfiler/dotnet/pull/442)) - (**Major**) Drops IE 11 support - Fix for missing `IMemoryCache` depending on config ([#440](https://github.com/MiniProfiler/dotnet/pull/440)) - - MySQL Storage: - Updates `MySqlConnector` to 0.60.1 for misc fixes ([#432](https://github.com/MiniProfiler/dotnet/pull/432)) (thanks [@bgrainger](https://github.com/bgrainger)!) - Redis Storage From c3488df928b05fb476f3e8f27dbe49dda6dc7495 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sun, 19 Apr 2020 18:22:03 -0400 Subject: [PATCH 3/5] More common status codes --- src/MiniProfiler.AspNetCore.Mvc/MvcDiagnosticListener.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/MiniProfiler.AspNetCore.Mvc/MvcDiagnosticListener.cs b/src/MiniProfiler.AspNetCore.Mvc/MvcDiagnosticListener.cs index f9ab85ef8..38f279c8b 100644 --- a/src/MiniProfiler.AspNetCore.Mvc/MvcDiagnosticListener.cs +++ b/src/MiniProfiler.AspNetCore.Mvc/MvcDiagnosticListener.cs @@ -50,9 +50,11 @@ private static string GetName(string label, ActionDescriptor descriptor) private static string GetName(IActionResult result) => result switch { - ViewResult vr => vr.ViewName.HasValue() ? "View: " + vr.ViewName : "ViewResult", - ContentResult cr => cr.ContentType.HasValue() ? "Content: " + cr.ContentType : "ContentResult", - // TODO: Other main ones? + ViewResult vr => vr.ViewName.HasValue() ? "View: " + vr.ViewName : nameof(ViewResult), + ContentResult cr => cr.ContentType.HasValue() ? "Content: " + cr.ContentType : nameof(ContentResult), + ObjectResult or => or.DeclaredType != null ? "Object: " + or.DeclaredType.Name : nameof(ObjectResult), + StatusCodeResult scr => scr.StatusCode > 0 ? "Status Code: " + scr.StatusCode.ToString() : nameof(StatusCodeResult), + JsonResult jr => jr.ContentType.HasValue() ? "JSON: " + jr.ContentType : nameof(JsonResult), _ => "Result: " + result.GetType().Name }; From 04e62fa793492b6daa5331230faeff8fe5cbbb1d Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Mon, 20 Apr 2020 23:11:14 -0400 Subject: [PATCH 4/5] Allow a bit wider profiler in general Also puts the title for all cases on profiler steps. Not strictly related to this change (can PR the diff separately), but needed for the longer action names...so combining it. Also moves to CSS truncation instead of a magical number. Yay for modern browsers! --- src/MiniProfiler.Shared/ui/includes.css | 5 ++++- src/MiniProfiler.Shared/ui/includes.less | 5 ++++- src/MiniProfiler.Shared/ui/includes.min.css | 2 +- src/MiniProfiler.Shared/ui/lib/MiniProfiler.ts | 4 ++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/MiniProfiler.Shared/ui/includes.css b/src/MiniProfiler.Shared/ui/includes.css index a349710d6..94ccc034e 100644 --- a/src/MiniProfiler.Shared/ui/includes.css +++ b/src/MiniProfiler.Shared/ui/includes.css @@ -650,7 +650,10 @@ padding-bottom: 3px; } .mp-results .mp-popup .mp-timings .mp-label { - max-width: 275px; + max-width: 350px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; } .mp-results .mp-queries { display: none; diff --git a/src/MiniProfiler.Shared/ui/includes.less b/src/MiniProfiler.Shared/ui/includes.less index ad25cb60e..dc08ef70b 100644 --- a/src/MiniProfiler.Shared/ui/includes.less +++ b/src/MiniProfiler.Shared/ui/includes.less @@ -724,7 +724,10 @@ } .mp-label { - max-width: 275px; + max-width: 350px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; } } } diff --git a/src/MiniProfiler.Shared/ui/includes.min.css b/src/MiniProfiler.Shared/ui/includes.min.css index 0edfc8279..4853ccd58 100644 --- a/src/MiniProfiler.Shared/ui/includes.min.css +++ b/src/MiniProfiler.Shared/ui/includes.min.css @@ -1 +1 @@ -:root{--mp-main-bg-color:#fff;--mp-button-active-bg-color:#207ab7;--mp-button-active-text-color:#fff;--mp-button-warning-bg-color:#c91d2e;--mp-button-warning-text-color:#fff;--mp-duration-color:#111;--mp-warning-color:#c91d2e;--mp-critical-color:#f00;--mp-alt-row-color:#f5f5f5;--mp-muted-color:#aaa;--mp-link-color:#07c;--mp-label-color:#555;--mp-gap-font-color:#444;--mp-gap-bg-color:#e1e5ed;--mp-overlay-bg-color:#000;--mp-shadow-color:#555;--mp-info-border-color:#ddd;--mp-query-border-color:#efefef;--mp-timing-unknown-color:#80ccda;--mp-timing-dns-color:#8baad1;--mp-timing-connect-color:#8cbb4e;--mp-timing-ssl-color:#795892;--mp-timing-request-color:#0099b2;--mp-timing-response-color:#d99d35;--mp-timing-dom-color:#bd4d32;--mp-timing-domcontent-color:#dd4678;--mp-timing-load-color:#1d72aa;--mp-timing-paint-color:#bc41e3;--mp-highlight-fade-color:#ffb;--mp-highlight-default-color:#000;--mp-highlight-string-color:#756bb1;--mp-highlight-comment-color:#636363;--mp-highlight-literal-color:#31a354;--mp-highlight-variable-color:#88f;--mp-highlight-keyword-color:#3182bd;--mp-highlight-attribute-color:#e6550d;--mp-result-border:solid .5px #ababab;--mp-result-border-radius:10px;}@media(prefers-color-scheme:dark){.mp-scheme-auto{--mp-main-bg-color:#222;--mp-duration-color:#eee;--mp-alt-row-color:#333;--mp-muted-color:#888;--mp-label-color:#ccc;--mp-gap-font-color:#d6d6d6;--mp-gap-bg-color:#424448;--mp-link-color:#4db5ff;--mp-query-border-color:#575757;--mp-highlight-fade-color:#884;--mp-highlight-default-color:#eee;--mp-highlight-literal-color:#3fca6a;--mp-highlight-keyword-color:#36a1ef;--mp-result-border:solid .5px #575757;}.mp-scheme-auto body{background-color:var(--mp-main-bg-color);}}.mp-scheme-dark{--mp-main-bg-color:#222;--mp-duration-color:#eee;--mp-alt-row-color:#333;--mp-muted-color:#888;--mp-label-color:#ccc;--mp-gap-font-color:#d6d6d6;--mp-gap-bg-color:#424448;--mp-link-color:#4db5ff;--mp-query-border-color:#575757;--mp-highlight-fade-color:#884;--mp-highlight-default-color:#eee;--mp-highlight-literal-color:#3fca6a;--mp-highlight-keyword-color:#36a1ef;--mp-result-border:solid .5px #575757;}.mp-scheme-dark body{background-color:var(--mp-main-bg-color);}.mp-result,.mp-queries{color:var(--mp-label-color);line-height:1;font-size:12px;}.mp-result pre,.mp-queries pre,.mp-result code,.mp-queries code,.mp-result label,.mp-queries label,.mp-result table,.mp-queries table,.mp-result tbody,.mp-queries tbody,.mp-result thead,.mp-queries thead,.mp-result tfoot,.mp-queries tfoot,.mp-result tr,.mp-queries tr,.mp-result th,.mp-queries th,.mp-result td,.mp-queries td{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline;background-color:transparent;overflow:visible;max-height:none;}.mp-result pre,.mp-queries pre,.mp-result code,.mp-queries code{font-family:Fira Code,Consolas,monospace,serif;font-variant-ligatures:none;}.mp-result table,.mp-queries table{color:var(--mp-label-color);border-collapse:collapse;border-spacing:0;width:100%;}.mp-result a,.mp-queries a{cursor:pointer;color:var(--mp-link-color);text-decoration:none;}.mp-result a:hover,.mp-queries a:hover{text-decoration:underline;}.mp-result{font-family:sans-serif;}.mp-result.show-columns th.mp-more-columns,.mp-result.show-columns td.mp-more-columns{display:table-cell !important;}.mp-result.show-columns a.mp-more-columns{display:inline-block;}.mp-result.show-columns .mp-links span a:nth-child(1){display:none;}.mp-result.show-trivial tr.mp-trivial{display:table-row !important;}.mp-result.show-trivial a.mp-trivial{display:inline-block;}.mp-result.show-trivial .mp-links span a:nth-child(3){display:none;}.mp-result table.mp-client-timings{margin-top:10px;}.mp-result table.mp-client-timings td:nth-child(2){width:100%;padding:0;}.mp-result table.mp-client-timings td div{height:13px;min-width:1px;}.mp-result table.mp-client-timings .t-point div{height:4px;border-radius:8px;margin-bottom:4px;box-shadow:0 0 2px;}.mp-result table.mp-client-timings .t-unknown div{background:var(--mp-timing-unknown-color);}.mp-result table.mp-client-timings .t-dns div{background:var(--mp-timing-dns-color);}.mp-result table.mp-client-timings .t-connect div{background:var(--mp-timing-connect-color);}.mp-result table.mp-client-timings .t-ssl div{background:var(--mp-timing-ssl-color);}.mp-result table.mp-client-timings .t-request div{background:var(--mp-timing-request-color);}.mp-result table.mp-client-timings .t-response div{background:var(--mp-timing-response-color);}.mp-result table.mp-client-timings .t-dom div{background:var(--mp-timing-dom-color);}.mp-result table.mp-client-timings .t-domcontent div{background:var(--mp-timing-domcontent-color);}.mp-result table.mp-client-timings .t-load div{background:var(--mp-timing-load-color);}.mp-result table.mp-client-timings .t-paint div{background:var(--mp-timing-paint-color);}.mp-result .mp-label{color:var(--mp-label-color);overflow:hidden;text-overflow:ellipsis;}.mp-result .mp-unit{color:var(--mp-muted-color);}.mp-result .mp-more-columns{display:none;}.mp-result .mp-trivial{display:none;}.mp-result .mp-trivial td,.mp-result .mp-trivial td *{color:var(--mp-muted-color) !important;}.mp-result .mp-number{color:var(--mp-duration-color);}.mp-result .mp-info>div{white-space:nowrap;overflow:hidden;}.mp-result .mp-info>div>div{display:inline-block;}.mp-result .mp-info .mp-name{display:inline-block;font-weight:bold;float:left;}.mp-result .mp-info .mp-machine-name,.mp-result .mp-info .mp-started{text-align:right;float:right;}.mp-result .mp-info .mp-server-time{white-space:nowrap;}.mp-result .mp-timings th{background-color:var(--mp-main-bg-color);color:var(--mp-muted-color);text-align:right;}.mp-result .mp-timings th,.mp-result .mp-timings td{white-space:nowrap;}.mp-result .mp-timings .mp-show-more{display:none;}.mp-result .mp-timings .mp-duration{font-family:Consolas,monospace,serif;color:var(--mp-duration-color);text-align:right;}.mp-result .mp-timings .mp-indent{letter-spacing:4px;}.mp-result .mp-timings .mp-queries-show .mp-number,.mp-result .mp-timings .mp-queries-show .mp-unit{color:var(--mp-link-color);}.mp-result .mp-timings .mp-queries-show.mp-queries-warning{color:var(--mp-warning-color);font-weight:bold;}.mp-result .mp-timings .mp-queries-duration{padding-left:6px;}.mp-result .mp-timings:not(.mp-client-timings) .mp-trivial:first-child{display:table-row;}.mp-result .mp-custom-timing-overview{float:right;margin:10px 0;width:auto;}.mp-result .mp-custom-timing-overview td{white-space:nowrap;text-align:right;}.mp-result .mp-custom-timing-overview td:last-child{padding-left:8px;}.mp-result .mp-links{margin-top:10px;clear:both;}.mp-result .mp-links span{float:right;}.mp-result .mp-links a{font-size:95%;margin-left:12px;}.mp-result .mp-links a:first-child{margin-left:0;}.mp-warning{color:var(--mp-critical-color);}@keyframes mp-fadeIn{from{background-color:var(--mp-highlight-fade-color);}to{background-color:none;}}.mp-overlay{z-index:2147483642;}.mp-overlay .mp-overlay-bg{z-index:2147483643;background:var(--mp-overlay-bg-color);opacity:.5;top:0;left:0;min-width:100%;position:fixed;height:calc(100%);}.mp-overlay .mp-queries{display:block;z-index:2147483644;top:25px;left:25px;right:25px;position:fixed;max-height:calc(100vh - 50px);background-color:var(--mp-main-bg-color);border-radius:2px;overflow-y:auto;overflow-x:auto;}.mp-queries{font-family:sans-serif;}.mp-queries pre{white-space:pre-wrap;}.mp-queries div{text-align:left;}.mp-queries table{table-layout:fixed;}.mp-queries td,.mp-queries th{padding:8px;border-bottom:solid 1px var(--mp-query-border-color);}.mp-queries td:first-child,.mp-queries th:first-child{width:250px;text-align:right;}.mp-queries td:first-child div,.mp-queries th:first-child div{text-align:right;margin-bottom:5px;word-break:break-word;}.mp-queries td:last-child,.mp-queries th:last-child{text-align:left;padding-right:15px;}.mp-queries .highlight{animation:mp-fadeIn 2s 1;}.mp-queries .mp-odd{background-color:var(--mp-alt-row-color);}.mp-queries .mp-stack-trace{padding-bottom:1px;color:var(--mp-muted-color);margin-bottom:5px;}.mp-queries .mp-since-start,.mp-queries .mp-duration{text-align:right;}.mp-queries.show-trivial .mp-gap-info.mp-trivial-gap{display:table-row;}.mp-queries .mp-gap-info{background-color:var(--mp-gap-bg-color);opacity:.8;color:var(--mp-gap-font-color);}.mp-queries .mp-gap-info .query{word-break:break-all;}.mp-queries .mp-gap-info .mp-unit{color:var(--mp-muted-color);}.mp-queries .mp-gap-info.mp-trivial-gap{display:none;}.mp-queries .mp-trivial-gap-container{text-align:center;margin:8px 0;}.mp-queries .mp-call-type{font-weight:bold;}.mp-queries .hljs{display:block;overflow-x:auto;padding:.5em;}.mp-queries .hljs,.mp-queries .hljs-subst{color:var(--mp-highlight-default-color);}.mp-queries .hljs-string,.mp-queries .hljs-meta,.mp-queries .hljs-symbol,.mp-queries .hljs-template-tag,.mp-queries .hljs-template-variable,.mp-queries .hljs-addition{color:var(--mp-highlight-string-color);}.mp-queries .hljs-comment,.mp-queries .hljs-quote{color:var(--mp-highlight-comment-color);}.mp-queries .hljs-number,.mp-queries .hljs-regexp,.mp-queries .hljs-literal,.mp-queries .hljs-bullet,.mp-queries .hljs-link{color:var(--mp-highlight-literal-color);}.mp-queries .hljs-deletion,.mp-queries .hljs-variable{color:var(--mp-highlight-variable-color);}.mp-queries .hljs-keyword,.mp-queries .hljs-selector-tag,.mp-queries .hljs-title,.mp-queries .hljs-section,.mp-queries .hljs-built_in,.mp-queries .hljs-doctag,.mp-queries .hljs-type,.mp-queries .hljs-tag,.mp-queries .hljs-name,.mp-queries .hljs-selector-id,.mp-queries .hljs-selector-class,.mp-queries .hljs-strong{color:var(--mp-highlight-keyword-color);}.mp-queries .hljs-emphasis{font-style:italic;}.mp-queries .hljs-attribute{color:var(--mp-highlight-attribute-color);}.mp-results{z-index:2147483641;position:fixed;top:0;}.mp-results.mp-left,.mp-results.mp-bottomleft{left:0;}.mp-results.mp-left .mp-controls,.mp-results.mp-left.mp-no-controls .mp-result:last-child .mp-button{border-bottom-right-radius:var(--mp-result-border-radius);}.mp-results.mp-left .mp-button,.mp-results.mp-left .mp-controls{border-right:var(--mp-result-border);}.mp-results.mp-right{right:0;}.mp-results.mp-right .mp-controls,.mp-results.mp-right.mp-no-controls .mp-result:last-child .mp-button{border-bottom-left-radius:var(--mp-result-border-radius);}.mp-results.mp-right .mp-button,.mp-results.mp-right .mp-controls{border-left:var(--mp-result-border);}.mp-results.mp-bottomleft{top:inherit;bottom:0;}.mp-results.mp-bottomleft .mp-result:first-child .mp-button{border-top-right-radius:var(--mp-result-border-radius);}.mp-results.mp-bottomleft .mp-button,.mp-results.mp-bottomleft .mp-controls{border-right:var(--mp-result-border);}.mp-results.mp-bottomleft .mp-result .mp-button,.mp-results.mp-bottomleft .mp-controls{border-bottom:0;border-top:var(--mp-result-border);}.mp-results.mp-bottomright{top:inherit;bottom:0;right:0;}.mp-results.mp-bottomright .mp-result:first-child .mp-button{border-top-left-radius:var(--mp-result-border-radius);}.mp-results.mp-bottomright .mp-button,.mp-results.mp-bottomright .mp-controls{border-left:var(--mp-result-border);}.mp-results.mp-bottomright .mp-result .mp-button,.mp-results.mp-bottomright .mp-controls{border-bottom:0;border-top:var(--mp-result-border);}.mp-results .mp-button{user-select:none;}.mp-results .mp-button.mp-button-warning{font-weight:bold;background-color:var(--mp-button-warning-bg-color);color:var(--mp-button-warning-text-color);}.mp-results .mp-button.mp-button-warning .mp-number,.mp-results .mp-button.mp-button-warning .mp-unit,.mp-results .mp-button.mp-button-warning .mp-warning{color:var(--mp-button-warning-text-color);}.mp-results .mp-button .mp-warning{font-weight:bold;}.mp-results .mp-button>.mp-number{font-family:Consolas,monospace,serif;}.mp-results .mp-controls{display:none;}.mp-results .mp-button,.mp-results .mp-controls{z-index:2147483640;border-bottom:var(--mp-result-border);background-color:var(--mp-main-bg-color);padding:4px 8px;text-align:right;cursor:pointer;}.mp-results .mp-result{position:relative;}.mp-results .mp-result.active .mp-button{background-color:var(--mp-button-active-bg-color);animation:none;border-radius:0 !important;}.mp-results .mp-result.active .mp-button.mp-button-warning{background-color:var(--mp-button-warning-bg-color);}.mp-results .mp-result.active .mp-button .mp-number,.mp-results .mp-result.active .mp-button .mp-warning{color:var(--mp-button-active-text-color);font-weight:bold;}.mp-results .mp-result.active .mp-button .mp-unit{color:var(--mp-button-active-text-color);font-weight:normal;}.mp-results .mp-result.active .mp-popup{display:block;}.mp-results.new .mp-button{animation:mp-fadeIn 2s 1;}.mp-results .mp-controls{display:block;font-size:12px;font-family:Consolas,monospace,serif;cursor:default;text-align:center;}.mp-results .mp-controls span{border-right:1px solid var(--mp-muted-color);padding-right:5px;margin-right:5px;cursor:pointer;}.mp-results .mp-controls span:last-child{border-right:none;}.mp-results .mp-popup{display:none;z-index:2147483641;position:absolute;background-color:var(--mp-main-bg-color);padding:5px 10px;text-align:left;line-height:18px;overflow:auto;box-shadow:0 1px 10px var(--mp-shadow-color);border-radius:2px;}.mp-results .mp-popup .mp-info{margin-bottom:3px;padding-bottom:2px;border-bottom:1px solid var(--mp-info-border-color);}.mp-results .mp-popup .mp-info .mp-name{font-size:1.1em;}.mp-results .mp-popup .mp-info .mp-overall-duration{color:var(--mp-muted-color);}.mp-results .mp-popup .mp-timings th,.mp-results .mp-popup .mp-timings td{padding:0 6px;}.mp-results .mp-popup .mp-timings th{font-size:95%;padding-bottom:3px;}.mp-results .mp-popup .mp-timings .mp-label{max-width:275px;}.mp-results .mp-queries{display:none;}.mp-results.mp-min .mp-result{display:none;}.mp-results.mp-min .mp-controls span{display:none;}.mp-results.mp-min .mp-controls .mp-min-max{border-right:none;padding:0;margin:0;}.mp-result-full .mp-result{width:950px;margin:25px auto;}.mp-result-full .mp-result .mp-button{display:none;}.mp-result-full .mp-result .mp-popup .mp-info{font-size:18px;border-bottom:1px solid var(--mp-muted-color);padding-bottom:3px;margin-bottom:10px;}.mp-result-full .mp-result .mp-popup .mp-info .mp-name{margin-bottom:5px;}.mp-result-full .mp-result .mp-popup .mp-info .mp-overall-duration,.mp-result-full .mp-result .mp-popup .mp-info .mp-started{font-size:80%;color:var(--mp-muted-color);}.mp-result-full .mp-result .mp-popup .mp-info .mp-overall-duration{padding-right:20px;}.mp-result-full .mp-result .mp-popup .mp-timings td,.mp-result-full .mp-result .mp-popup .mp-timings th{font-size:13px;padding-left:8px;padding-right:8px;}.mp-result-full .mp-result .mp-popup .mp-timings th{padding-bottom:7px;}.mp-result-full .mp-result .mp-popup .mp-timings td{padding-bottom:4px;}.mp-result-full .mp-result .mp-popup .mp-timings td:first-child{padding-left:10px;}.mp-result-full .mp-result .mp-popup .mp-timings .mp-label{max-width:550px;}.mp-result-full .mp-result .mp-queries{margin:20px 0;}.mp-result-full .mp-result .mp-queries th{font-size:16px;}.mp-result-full .mp-share-mp-results{display:none;}table.mp-results-index{border:0;border-spacing:0;font-size:12px;font-family:Arial;}table.mp-results-index a{color:var(--mp-link-color);text-decoration:none;}table.mp-results-index tbody{color:var(--mp-label-color);}table.mp-results-index tbody tr:nth-child(odd){background-color:var(--mp-alt-row-color);}table.mp-results-index tbody tr:nth-child(even){background-color:var(--mp-main-bg-color);}table.mp-results-index tbody td{text-align:center;}table.mp-results-index tbody td:first-child{text-align:left;}table.mp-results-index tbody td[colspan="3"]{color:var(--mp-muted-color);}table.mp-results-index thead tr{color:var(--mp-label-color);}table.mp-results-index thead tr th{padding:5px 15px;}table.mp-results-index td{padding:8px;} \ No newline at end of file +:root{--mp-main-bg-color:#fff;--mp-button-active-bg-color:#207ab7;--mp-button-active-text-color:#fff;--mp-button-warning-bg-color:#c91d2e;--mp-button-warning-text-color:#fff;--mp-duration-color:#111;--mp-warning-color:#c91d2e;--mp-critical-color:#f00;--mp-alt-row-color:#f5f5f5;--mp-muted-color:#aaa;--mp-link-color:#07c;--mp-label-color:#555;--mp-gap-font-color:#444;--mp-gap-bg-color:#e1e5ed;--mp-overlay-bg-color:#000;--mp-shadow-color:#555;--mp-info-border-color:#ddd;--mp-query-border-color:#efefef;--mp-timing-unknown-color:#80ccda;--mp-timing-dns-color:#8baad1;--mp-timing-connect-color:#8cbb4e;--mp-timing-ssl-color:#795892;--mp-timing-request-color:#0099b2;--mp-timing-response-color:#d99d35;--mp-timing-dom-color:#bd4d32;--mp-timing-domcontent-color:#dd4678;--mp-timing-load-color:#1d72aa;--mp-timing-paint-color:#bc41e3;--mp-highlight-fade-color:#ffb;--mp-highlight-default-color:#000;--mp-highlight-string-color:#756bb1;--mp-highlight-comment-color:#636363;--mp-highlight-literal-color:#31a354;--mp-highlight-variable-color:#88f;--mp-highlight-keyword-color:#3182bd;--mp-highlight-attribute-color:#e6550d;--mp-result-border:solid .5px #ababab;--mp-result-border-radius:10px;}@media(prefers-color-scheme:dark){.mp-scheme-auto{--mp-main-bg-color:#222;--mp-duration-color:#eee;--mp-alt-row-color:#333;--mp-muted-color:#888;--mp-label-color:#ccc;--mp-gap-font-color:#d6d6d6;--mp-gap-bg-color:#424448;--mp-link-color:#4db5ff;--mp-query-border-color:#575757;--mp-highlight-fade-color:#884;--mp-highlight-default-color:#eee;--mp-highlight-literal-color:#3fca6a;--mp-highlight-keyword-color:#36a1ef;--mp-result-border:solid .5px #575757;}.mp-scheme-auto body{background-color:var(--mp-main-bg-color);}}.mp-scheme-dark{--mp-main-bg-color:#222;--mp-duration-color:#eee;--mp-alt-row-color:#333;--mp-muted-color:#888;--mp-label-color:#ccc;--mp-gap-font-color:#d6d6d6;--mp-gap-bg-color:#424448;--mp-link-color:#4db5ff;--mp-query-border-color:#575757;--mp-highlight-fade-color:#884;--mp-highlight-default-color:#eee;--mp-highlight-literal-color:#3fca6a;--mp-highlight-keyword-color:#36a1ef;--mp-result-border:solid .5px #575757;}.mp-scheme-dark body{background-color:var(--mp-main-bg-color);}.mp-result,.mp-queries{color:var(--mp-label-color);line-height:1;font-size:12px;}.mp-result pre,.mp-queries pre,.mp-result code,.mp-queries code,.mp-result label,.mp-queries label,.mp-result table,.mp-queries table,.mp-result tbody,.mp-queries tbody,.mp-result thead,.mp-queries thead,.mp-result tfoot,.mp-queries tfoot,.mp-result tr,.mp-queries tr,.mp-result th,.mp-queries th,.mp-result td,.mp-queries td{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline;background-color:transparent;overflow:visible;max-height:none;}.mp-result pre,.mp-queries pre,.mp-result code,.mp-queries code{font-family:Fira Code,Consolas,monospace,serif;font-variant-ligatures:none;}.mp-result table,.mp-queries table{color:var(--mp-label-color);border-collapse:collapse;border-spacing:0;width:100%;}.mp-result a,.mp-queries a{cursor:pointer;color:var(--mp-link-color);text-decoration:none;}.mp-result a:hover,.mp-queries a:hover{text-decoration:underline;}.mp-result{font-family:sans-serif;}.mp-result.show-columns th.mp-more-columns,.mp-result.show-columns td.mp-more-columns{display:table-cell !important;}.mp-result.show-columns a.mp-more-columns{display:inline-block;}.mp-result.show-columns .mp-links span a:nth-child(1){display:none;}.mp-result.show-trivial tr.mp-trivial{display:table-row !important;}.mp-result.show-trivial a.mp-trivial{display:inline-block;}.mp-result.show-trivial .mp-links span a:nth-child(3){display:none;}.mp-result table.mp-client-timings{margin-top:10px;}.mp-result table.mp-client-timings td:nth-child(2){width:100%;padding:0;}.mp-result table.mp-client-timings td div{height:13px;min-width:1px;}.mp-result table.mp-client-timings .t-point div{height:4px;border-radius:8px;margin-bottom:4px;box-shadow:0 0 2px;}.mp-result table.mp-client-timings .t-unknown div{background:var(--mp-timing-unknown-color);}.mp-result table.mp-client-timings .t-dns div{background:var(--mp-timing-dns-color);}.mp-result table.mp-client-timings .t-connect div{background:var(--mp-timing-connect-color);}.mp-result table.mp-client-timings .t-ssl div{background:var(--mp-timing-ssl-color);}.mp-result table.mp-client-timings .t-request div{background:var(--mp-timing-request-color);}.mp-result table.mp-client-timings .t-response div{background:var(--mp-timing-response-color);}.mp-result table.mp-client-timings .t-dom div{background:var(--mp-timing-dom-color);}.mp-result table.mp-client-timings .t-domcontent div{background:var(--mp-timing-domcontent-color);}.mp-result table.mp-client-timings .t-load div{background:var(--mp-timing-load-color);}.mp-result table.mp-client-timings .t-paint div{background:var(--mp-timing-paint-color);}.mp-result .mp-label{color:var(--mp-label-color);overflow:hidden;text-overflow:ellipsis;}.mp-result .mp-unit{color:var(--mp-muted-color);}.mp-result .mp-more-columns{display:none;}.mp-result .mp-trivial{display:none;}.mp-result .mp-trivial td,.mp-result .mp-trivial td *{color:var(--mp-muted-color) !important;}.mp-result .mp-number{color:var(--mp-duration-color);}.mp-result .mp-info>div{white-space:nowrap;overflow:hidden;}.mp-result .mp-info>div>div{display:inline-block;}.mp-result .mp-info .mp-name{display:inline-block;font-weight:bold;float:left;}.mp-result .mp-info .mp-machine-name,.mp-result .mp-info .mp-started{text-align:right;float:right;}.mp-result .mp-info .mp-server-time{white-space:nowrap;}.mp-result .mp-timings th{background-color:var(--mp-main-bg-color);color:var(--mp-muted-color);text-align:right;}.mp-result .mp-timings th,.mp-result .mp-timings td{white-space:nowrap;}.mp-result .mp-timings .mp-show-more{display:none;}.mp-result .mp-timings .mp-duration{font-family:Consolas,monospace,serif;color:var(--mp-duration-color);text-align:right;}.mp-result .mp-timings .mp-indent{letter-spacing:4px;}.mp-result .mp-timings .mp-queries-show .mp-number,.mp-result .mp-timings .mp-queries-show .mp-unit{color:var(--mp-link-color);}.mp-result .mp-timings .mp-queries-show.mp-queries-warning{color:var(--mp-warning-color);font-weight:bold;}.mp-result .mp-timings .mp-queries-duration{padding-left:6px;}.mp-result .mp-timings:not(.mp-client-timings) .mp-trivial:first-child{display:table-row;}.mp-result .mp-custom-timing-overview{float:right;margin:10px 0;width:auto;}.mp-result .mp-custom-timing-overview td{white-space:nowrap;text-align:right;}.mp-result .mp-custom-timing-overview td:last-child{padding-left:8px;}.mp-result .mp-links{margin-top:10px;clear:both;}.mp-result .mp-links span{float:right;}.mp-result .mp-links a{font-size:95%;margin-left:12px;}.mp-result .mp-links a:first-child{margin-left:0;}.mp-warning{color:var(--mp-critical-color);}@keyframes mp-fadeIn{from{background-color:var(--mp-highlight-fade-color);}to{background-color:none;}}.mp-overlay{z-index:2147483642;}.mp-overlay .mp-overlay-bg{z-index:2147483643;background:var(--mp-overlay-bg-color);opacity:.5;top:0;left:0;min-width:100%;position:fixed;height:calc(100%);}.mp-overlay .mp-queries{display:block;z-index:2147483644;top:25px;left:25px;right:25px;position:fixed;max-height:calc(100vh - 50px);background-color:var(--mp-main-bg-color);border-radius:2px;overflow-y:auto;overflow-x:auto;}.mp-queries{font-family:sans-serif;}.mp-queries pre{white-space:pre-wrap;}.mp-queries div{text-align:left;}.mp-queries table{table-layout:fixed;}.mp-queries td,.mp-queries th{padding:8px;border-bottom:solid 1px var(--mp-query-border-color);}.mp-queries td:first-child,.mp-queries th:first-child{width:250px;text-align:right;}.mp-queries td:first-child div,.mp-queries th:first-child div{text-align:right;margin-bottom:5px;word-break:break-word;}.mp-queries td:last-child,.mp-queries th:last-child{text-align:left;padding-right:15px;}.mp-queries .highlight{animation:mp-fadeIn 2s 1;}.mp-queries .mp-odd{background-color:var(--mp-alt-row-color);}.mp-queries .mp-stack-trace{padding-bottom:1px;color:var(--mp-muted-color);margin-bottom:5px;}.mp-queries .mp-since-start,.mp-queries .mp-duration{text-align:right;}.mp-queries.show-trivial .mp-gap-info.mp-trivial-gap{display:table-row;}.mp-queries .mp-gap-info{background-color:var(--mp-gap-bg-color);opacity:.8;color:var(--mp-gap-font-color);}.mp-queries .mp-gap-info .query{word-break:break-all;}.mp-queries .mp-gap-info .mp-unit{color:var(--mp-muted-color);}.mp-queries .mp-gap-info.mp-trivial-gap{display:none;}.mp-queries .mp-trivial-gap-container{text-align:center;margin:8px 0;}.mp-queries .mp-call-type{font-weight:bold;}.mp-queries .hljs{display:block;overflow-x:auto;padding:.5em;}.mp-queries .hljs,.mp-queries .hljs-subst{color:var(--mp-highlight-default-color);}.mp-queries .hljs-string,.mp-queries .hljs-meta,.mp-queries .hljs-symbol,.mp-queries .hljs-template-tag,.mp-queries .hljs-template-variable,.mp-queries .hljs-addition{color:var(--mp-highlight-string-color);}.mp-queries .hljs-comment,.mp-queries .hljs-quote{color:var(--mp-highlight-comment-color);}.mp-queries .hljs-number,.mp-queries .hljs-regexp,.mp-queries .hljs-literal,.mp-queries .hljs-bullet,.mp-queries .hljs-link{color:var(--mp-highlight-literal-color);}.mp-queries .hljs-deletion,.mp-queries .hljs-variable{color:var(--mp-highlight-variable-color);}.mp-queries .hljs-keyword,.mp-queries .hljs-selector-tag,.mp-queries .hljs-title,.mp-queries .hljs-section,.mp-queries .hljs-built_in,.mp-queries .hljs-doctag,.mp-queries .hljs-type,.mp-queries .hljs-tag,.mp-queries .hljs-name,.mp-queries .hljs-selector-id,.mp-queries .hljs-selector-class,.mp-queries .hljs-strong{color:var(--mp-highlight-keyword-color);}.mp-queries .hljs-emphasis{font-style:italic;}.mp-queries .hljs-attribute{color:var(--mp-highlight-attribute-color);}.mp-results{z-index:2147483641;position:fixed;top:0;}.mp-results.mp-left,.mp-results.mp-bottomleft{left:0;}.mp-results.mp-left .mp-controls,.mp-results.mp-left.mp-no-controls .mp-result:last-child .mp-button{border-bottom-right-radius:var(--mp-result-border-radius);}.mp-results.mp-left .mp-button,.mp-results.mp-left .mp-controls{border-right:var(--mp-result-border);}.mp-results.mp-right{right:0;}.mp-results.mp-right .mp-controls,.mp-results.mp-right.mp-no-controls .mp-result:last-child .mp-button{border-bottom-left-radius:var(--mp-result-border-radius);}.mp-results.mp-right .mp-button,.mp-results.mp-right .mp-controls{border-left:var(--mp-result-border);}.mp-results.mp-bottomleft{top:inherit;bottom:0;}.mp-results.mp-bottomleft .mp-result:first-child .mp-button{border-top-right-radius:var(--mp-result-border-radius);}.mp-results.mp-bottomleft .mp-button,.mp-results.mp-bottomleft .mp-controls{border-right:var(--mp-result-border);}.mp-results.mp-bottomleft .mp-result .mp-button,.mp-results.mp-bottomleft .mp-controls{border-bottom:0;border-top:var(--mp-result-border);}.mp-results.mp-bottomright{top:inherit;bottom:0;right:0;}.mp-results.mp-bottomright .mp-result:first-child .mp-button{border-top-left-radius:var(--mp-result-border-radius);}.mp-results.mp-bottomright .mp-button,.mp-results.mp-bottomright .mp-controls{border-left:var(--mp-result-border);}.mp-results.mp-bottomright .mp-result .mp-button,.mp-results.mp-bottomright .mp-controls{border-bottom:0;border-top:var(--mp-result-border);}.mp-results .mp-button{user-select:none;}.mp-results .mp-button.mp-button-warning{font-weight:bold;background-color:var(--mp-button-warning-bg-color);color:var(--mp-button-warning-text-color);}.mp-results .mp-button.mp-button-warning .mp-number,.mp-results .mp-button.mp-button-warning .mp-unit,.mp-results .mp-button.mp-button-warning .mp-warning{color:var(--mp-button-warning-text-color);}.mp-results .mp-button .mp-warning{font-weight:bold;}.mp-results .mp-button>.mp-number{font-family:Consolas,monospace,serif;}.mp-results .mp-controls{display:none;}.mp-results .mp-button,.mp-results .mp-controls{z-index:2147483640;border-bottom:var(--mp-result-border);background-color:var(--mp-main-bg-color);padding:4px 8px;text-align:right;cursor:pointer;}.mp-results .mp-result{position:relative;}.mp-results .mp-result.active .mp-button{background-color:var(--mp-button-active-bg-color);animation:none;border-radius:0 !important;}.mp-results .mp-result.active .mp-button.mp-button-warning{background-color:var(--mp-button-warning-bg-color);}.mp-results .mp-result.active .mp-button .mp-number,.mp-results .mp-result.active .mp-button .mp-warning{color:var(--mp-button-active-text-color);font-weight:bold;}.mp-results .mp-result.active .mp-button .mp-unit{color:var(--mp-button-active-text-color);font-weight:normal;}.mp-results .mp-result.active .mp-popup{display:block;}.mp-results.new .mp-button{animation:mp-fadeIn 2s 1;}.mp-results .mp-controls{display:block;font-size:12px;font-family:Consolas,monospace,serif;cursor:default;text-align:center;}.mp-results .mp-controls span{border-right:1px solid var(--mp-muted-color);padding-right:5px;margin-right:5px;cursor:pointer;}.mp-results .mp-controls span:last-child{border-right:none;}.mp-results .mp-popup{display:none;z-index:2147483641;position:absolute;background-color:var(--mp-main-bg-color);padding:5px 10px;text-align:left;line-height:18px;overflow:auto;box-shadow:0 1px 10px var(--mp-shadow-color);border-radius:2px;}.mp-results .mp-popup .mp-info{margin-bottom:3px;padding-bottom:2px;border-bottom:1px solid var(--mp-info-border-color);}.mp-results .mp-popup .mp-info .mp-name{font-size:1.1em;}.mp-results .mp-popup .mp-info .mp-overall-duration{color:var(--mp-muted-color);}.mp-results .mp-popup .mp-timings th,.mp-results .mp-popup .mp-timings td{padding:0 6px;}.mp-results .mp-popup .mp-timings th{font-size:95%;padding-bottom:3px;}.mp-results .mp-popup .mp-timings .mp-label{max-width:350px;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;}.mp-results .mp-queries{display:none;}.mp-results.mp-min .mp-result{display:none;}.mp-results.mp-min .mp-controls span{display:none;}.mp-results.mp-min .mp-controls .mp-min-max{border-right:none;padding:0;margin:0;}.mp-result-full .mp-result{width:950px;margin:25px auto;}.mp-result-full .mp-result .mp-button{display:none;}.mp-result-full .mp-result .mp-popup .mp-info{font-size:18px;border-bottom:1px solid var(--mp-muted-color);padding-bottom:3px;margin-bottom:10px;}.mp-result-full .mp-result .mp-popup .mp-info .mp-name{margin-bottom:5px;}.mp-result-full .mp-result .mp-popup .mp-info .mp-overall-duration,.mp-result-full .mp-result .mp-popup .mp-info .mp-started{font-size:80%;color:var(--mp-muted-color);}.mp-result-full .mp-result .mp-popup .mp-info .mp-overall-duration{padding-right:20px;}.mp-result-full .mp-result .mp-popup .mp-timings td,.mp-result-full .mp-result .mp-popup .mp-timings th{font-size:13px;padding-left:8px;padding-right:8px;}.mp-result-full .mp-result .mp-popup .mp-timings th{padding-bottom:7px;}.mp-result-full .mp-result .mp-popup .mp-timings td{padding-bottom:4px;}.mp-result-full .mp-result .mp-popup .mp-timings td:first-child{padding-left:10px;}.mp-result-full .mp-result .mp-popup .mp-timings .mp-label{max-width:550px;}.mp-result-full .mp-result .mp-queries{margin:20px 0;}.mp-result-full .mp-result .mp-queries th{font-size:16px;}.mp-result-full .mp-share-mp-results{display:none;}table.mp-results-index{border:0;border-spacing:0;font-size:12px;font-family:Arial;}table.mp-results-index a{color:var(--mp-link-color);text-decoration:none;}table.mp-results-index tbody{color:var(--mp-label-color);}table.mp-results-index tbody tr:nth-child(odd){background-color:var(--mp-alt-row-color);}table.mp-results-index tbody tr:nth-child(even){background-color:var(--mp-main-bg-color);}table.mp-results-index tbody td{text-align:center;}table.mp-results-index tbody td:first-child{text-align:left;}table.mp-results-index tbody td[colspan="3"]{color:var(--mp-muted-color);}table.mp-results-index thead tr{color:var(--mp-label-color);}table.mp-results-index thead tr th{padding:5px 15px;}table.mp-results-index td{padding:8px;} \ No newline at end of file diff --git a/src/MiniProfiler.Shared/ui/lib/MiniProfiler.ts b/src/MiniProfiler.Shared/ui/lib/MiniProfiler.ts index 8d987a758..2e8f00ac0 100644 --- a/src/MiniProfiler.Shared/ui/lib/MiniProfiler.ts +++ b/src/MiniProfiler.Shared/ui/lib/MiniProfiler.ts @@ -678,8 +678,8 @@ namespace StackExchange.Profiling { const customTimingTypes = p.CustomTimingStats ? Object.keys(p.CustomTimingStats) : []; let str = ` - 0 ? ` style="padding-left:${timing.Depth * 11}px;"` : ''}> - ${encode(timing.Name.slice(0, 45))}${encode(timing.Name && timing.Name.length > 45 ? '...' : '')} + 0 ? ` style="padding-left:${timing.Depth * 11}px;"` : ''}> + ${encode(timing.Name)} ${duration(timing.DurationWithoutChildrenMilliseconds)} From a33c1d254629f884fbbb9b4415afdfcb7c235c5e Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Mon, 20 Apr 2020 23:12:50 -0400 Subject: [PATCH 5/5] Pretty up and don't allocate descriptor names Makes action descriptor names a bit cleaner by removing the assembly name. Also caches so we only discard a cheaper tuple for lookup only. Could change to per-type methods and eliminate the tuple as well, or a dictionary based on key, or something else. Just depends how crazy we want to go here. --- .../MvcDiagnosticListener.cs | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/MiniProfiler.AspNetCore.Mvc/MvcDiagnosticListener.cs b/src/MiniProfiler.AspNetCore.Mvc/MvcDiagnosticListener.cs index 38f279c8b..5c65400a5 100644 --- a/src/MiniProfiler.AspNetCore.Mvc/MvcDiagnosticListener.cs +++ b/src/MiniProfiler.AspNetCore.Mvc/MvcDiagnosticListener.cs @@ -3,9 +3,9 @@ using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Diagnostics; using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.AspNetCore.Mvc.Razor; using StackExchange.Profiling.Internal; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Threading; @@ -39,13 +39,28 @@ public void OnCompleted() { } /// An object that provides additional information about the error. public void OnError(Exception error) => Trace.WriteLine(error); - private static string GetName(string label, ActionDescriptor descriptor) - { - var controller = descriptor.RouteValues.TryGetValue("controller", out var c) ? c : "UnknownController"; - var action = descriptor.RouteValues.TryGetValue("action", out var a) ? a : "UnknownAction"; + // So we don't keep allocating strings for the same actions over and over + private readonly ConcurrentDictionary<(string, string), string> _descriptorNameCache = new ConcurrentDictionary<(string, string), string>(); - // TODO: Don't allocate this string more than once - return label + ": " + controller + "/" + action; + private string GetName(string label, ActionDescriptor descriptor) + { + var key = (label, descriptor.DisplayName); + if (!_descriptorNameCache.TryGetValue(key, out var result)) + { + // For the "Samples.AspNetCore.Controllers.HomeController.Index (Samples.AspNetCore3)" format, + // ...trim off the assembly on the end. + var assemblyNamePos = descriptor.DisplayName.IndexOf(" ("); + if (assemblyNamePos > 0) + { + result = string.Concat(label, ": ", descriptor.DisplayName.AsSpan().Slice(0, assemblyNamePos)); + } + else + { + result = label + ": " + descriptor.DisplayName; + } + _descriptorNameCache[key] = result; + } + return result; } private static string GetName(IActionResult result) => result switch @@ -88,7 +103,7 @@ private object Complete(T state) where T : class { // MVC Bits: https://github.com/dotnet/aspnetcore/blob/v3.0.0/src/Mvc/Mvc.Core/src/Diagnostics/MvcDiagnostics.cs // ActionEvent - BeforeActionEventData data => Start(data.ActionDescriptor, GetName("Overall Action", data.ActionDescriptor)), + BeforeActionEventData data => Start(data.ActionDescriptor, GetName("Action", data.ActionDescriptor)), AfterActionEventData data => Complete(data.ActionDescriptor), // ControllerActionMethod BeforeControllerActionMethodEventData data => Start(data.ActionContext.ActionDescriptor, GetName("Controller Action", data.ActionContext.ActionDescriptor)),