AB#5820 - Added simple controller and view to test the miniprofiler

This commit is contained in:
Bjarke Berg
2020-03-29 22:35:52 +02:00
parent 681a25b861
commit b2799ef901
11 changed files with 176 additions and 156 deletions
@@ -0,0 +1,14 @@
using Microsoft.AspNetCore.Mvc;
using Umbraco.Composing;
namespace Umbraco.Web.BackOffice.Controllers
{
public class BackOfficeController : Controller
{
// GET
public IActionResult Index()
{
return View();
}
}
}
@@ -10,7 +10,7 @@ namespace Umbraco.Web.Common.AspNetCore
GetAbsoluteUrl = globalSettings.UmbracoPath;
}
public string GetAbsoluteUrl { get; }
public string GetAbsoluteUrl { get; } // TODO make absolute
}
}
@@ -169,7 +169,7 @@ namespace Umbraco.Web.Common.AspNetCore
}
var webProfiler = new WebProfiler(httpContextAccessor);
webProfiler.Start();
webProfiler.StartBoot();
return webProfiler;
}
@@ -1,5 +1,6 @@
using System;
using Microsoft.AspNetCore.Builder;
using StackExchange.Profiling;
using Umbraco.Web.Common.Middleware;
namespace Umbraco.Web.Common.Extensions
@@ -13,9 +14,11 @@ namespace Umbraco.Web.Common.Extensions
throw new ArgumentNullException(nameof(app));
}
app.UseMiddleware<UmbracoRequestMiddleware>();
app.UseMiddleware<UmbracoRequestMiddleware>();
app.UseMiddleware<MiniProfilerMiddleware>();
return app;
}
}
}
@@ -1,76 +1,118 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using StackExchange.Profiling;
using StackExchange.Profiling.Internal;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Web.Common.Middleware;
namespace Umbraco.Web.Common.Runtime.Profiler
{
public class WebProfiler : IProfiler
{
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim();
private volatile BootPhase _bootPhase;
private MiniProfiler _startupProfiler;
private const string BootRequestItemKey = "Umbraco.Core.Logging.WebProfiler__isBootRequest";
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly WebProfilerProvider _provider;
private int _first;
public WebProfiler(IHttpContextAccessor httpContextAccessor)
{
// create our own provider, which can provide a profiler even during boot
_provider = new WebProfilerProvider();
_httpContextAccessor = httpContextAccessor;
MiniProfiler.DefaultOptions.ProfilerProvider = _provider;
_bootPhase = BootPhase.Boot;
}
/// <summary>
///
/// </summary>
/// <remarks>
/// Normally we would call MiniProfiler.Current.RenderIncludes(...), but because the requeststate is not set, this method does not work.
/// We fake the requestIds from the RequestState here.
/// </remarks>
public string Render()
{
return MiniProfiler.Current
.RenderIncludes(_httpContextAccessor.HttpContext, RenderPosition.Right).ToString();
var profiler = MiniProfiler.Current;
if (profiler == null) return string.Empty;
var context = _httpContextAccessor.HttpContext;
var path = (profiler.Options as MiniProfilerOptions)?.RouteBasePath.Value.EnsureTrailingSlash();
var result = StackExchange.Profiling.Internal.Render.Includes(
profiler,
path: context.Request.PathBase + path,
isAuthorized: true,
requestIDs: new List<Guid>{ profiler.Id },
position: RenderPosition.Right,
showTrivial: profiler.Options.PopupShowTrivial,
showTimeWithChildren: profiler.Options.PopupShowTimeWithChildren,
maxTracesToShow: profiler.Options.PopupMaxTracesToShow,
showControls:profiler.Options.ShowControls,
startHidden: profiler.Options.PopupStartHidden);
return result;
}
public IDisposable Step(string name) => MiniProfiler.Current?.Step(name);
public IDisposable Step(string name)
{
return MiniProfiler.Current?.Step(name);
}
public void Start()
{
MiniProfiler.StartNew();
}
public void StartBoot()
{
_startupProfiler = MiniProfiler.StartNew("Startup Profiler");
}
public void StopBoot()
{
_startupProfiler.Stop();
}
public void Stop(bool discardResults = false)
{
MiniProfiler.Current?.Stop(discardResults);
}
public void UmbracoApplicationBeginRequest(HttpContext context)
{
// if this is the first request, notify our own provider that this request is the boot request
var first = Interlocked.Exchange(ref _first, 1) == 0;
if (first)
if (ShouldProfile(context.Request))
{
_provider.BeginBootRequest();
context.Items[BootRequestItemKey] = true;
// and no need to start anything, profiler is already there
}
// else start a profiler, the normal way
else if (ShouldProfile(context.Request))
Start();
}
}
public void UmbracoApplicationEndRequest(HttpContext context)
{
// if this is the boot request, or if we should profile this request, stop
// (the boot request is always profiled, no matter what)
if (context.Items.TryGetValue(BootRequestItemKey, out var isBoot) && isBoot.Equals(true))
{
_provider.EndBootRequest();
Stop();
}
else if (ShouldProfile(context.Request))
if (ShouldProfile(context.Request))
{
Stop();
// if this is the first request, append the startup profiler
var first = Interlocked.Exchange(ref _first, 1) == 0;
if (first)
{
var startupDuration = _startupProfiler.Root.DurationMilliseconds.GetValueOrDefault();
MiniProfiler.Current.DurationMilliseconds += startupDuration;
MiniProfiler.Current.GetTimingHierarchy().First().DurationMilliseconds += startupDuration;
MiniProfiler.Current.Root.AddChild(_startupProfiler.Root);
_startupProfiler = null;
}
}
}
@@ -82,5 +124,15 @@ namespace Umbraco.Web.Common.Runtime.Profiler
if (bool.TryParse(request.Cookies["UMB-DEBUG"], out var cUmbDebug)) return cUmbDebug;
return false;
}
/// <summary>
/// Indicates the boot phase.
/// </summary>
private enum BootPhase
{
Boot = 0, // boot phase (before the 1st request even begins)
BootRequest = 1, // request boot phase (during the 1st request)
Booted = 2 // done booting
}
}
}
@@ -51,6 +51,9 @@ namespace Umbraco.Web.Common.Runtime.Profiler
_umbracoRequestLifetime.RequestStart +=
(sender, context) => _profiler.UmbracoApplicationBeginRequest(context);
_umbracoRequestLifetime.RequestEnd += (sender, context) => _profiler.UmbracoApplicationEndRequest(context);
// Stop the profiling of the booting process
_profiler.StopBoot();
}
}
}
@@ -1,119 +0,0 @@
using System;
using System.Threading;
using StackExchange.Profiling;
using StackExchange.Profiling.Internal;
using Umbraco.Core.Cache;
namespace Umbraco.Web.Common.Runtime.Profiler
{
public class WebProfilerProvider : DefaultProfilerProvider
{
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim();
private volatile BootPhase _bootPhase;
private int _first;
private MiniProfiler _startupProfiler;
public WebProfilerProvider()
{
// booting...
_bootPhase = BootPhase.Boot;
}
/// <summary>
/// Gets the current profiler.
/// </summary>
/// <remarks>
/// If the boot phase is not Booted, then this will return the startup profiler (this), otherwise
/// returns the base class
/// </remarks>
public override MiniProfiler CurrentProfiler
{
get
{
// if not booting then just use base (fast)
// no lock, _bootPhase is volatile
if (_bootPhase == BootPhase.Booted)
return base.CurrentProfiler;
// else
try
{
var current = base.CurrentProfiler;
return current ?? _startupProfiler;
}
catch
{
return _startupProfiler;
}
}
}
public void BeginBootRequest()
{
_locker.EnterWriteLock();
try
{
if (_bootPhase != BootPhase.Boot)
throw new InvalidOperationException("Invalid boot phase.");
_bootPhase = BootPhase.BootRequest;
// assign the profiler to be the current MiniProfiler for the request
// is's already active, starting and all
CurrentProfiler = _startupProfiler;
}
finally
{
_locker.ExitWriteLock();
}
}
public void EndBootRequest()
{
_locker.EnterWriteLock();
try
{
if (_bootPhase != BootPhase.BootRequest)
throw new InvalidOperationException("Invalid boot phase.");
_bootPhase = BootPhase.Booted;
_startupProfiler = null;
}
finally
{
_locker.ExitWriteLock();
}
}
/// <summary>
/// Starts a new MiniProfiler.
/// </summary>
/// <remarks>
/// <para>
/// This is called when WebProfiler calls MiniProfiler.Start() so,
/// - as a result of WebRuntime starting the WebProfiler, and
/// - assuming profiling is enabled, on every BeginRequest that should be profiled,
/// - except for the very first one which is the boot request.
/// </para>
/// </remarks>
public override MiniProfiler Start(string profilerName, MiniProfilerBaseOptions options)
{
var first = Interlocked.Exchange(ref _first, 1) == 0;
if (first == false) return base.Start(profilerName, options);
_startupProfiler = new MiniProfiler("StartupProfiler", options);
CurrentProfiler = _startupProfiler;
return _startupProfiler;
}
/// <summary>
/// Indicates the boot phase.
/// </summary>
private enum BootPhase
{
Boot = 0, // boot phase (before the 1st request even begins)
BootRequest = 1, // request boot phase (during the 1st request)
Booted = 2 // done booting
}
}
}
@@ -1,7 +1,7 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:36804",
"sslPort": 44354
@@ -15,10 +15,11 @@
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Umbraco.Web.UI.BackOffice": {
"Umbraco.Web.UI.NetCore": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"launchBrowser": false,
"applicationUrl": "https://localhost:44354;http://localhost:9000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
+44 -1
View File
@@ -5,13 +5,19 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using StackExchange.Profiling;
using Umbraco.Composing;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Web.BackOffice.AspNetCore;
using Umbraco.Web.Common.AspNetCore;
using Umbraco.Web.Common.Extensions;
using Umbraco.Web.Common.Runtime.Profiler;
using Umbraco.Web.Website.AspNetCore;
@@ -43,11 +49,19 @@ namespace Umbraco.Web.UI.BackOffice
services.AddUmbracoConfiguration(_config);
services.AddUmbracoCore(_webHostEnvironment);
services.AddUmbracoWebsite();
services.AddMvc();
services.AddMiniProfiler(options =>
{
options.ShouldProfile = request => false; // WebProfiler determine and start profiling. We should not use the MiniProfilerMiddleware to also profile
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// app.UseMiniProfiler();
app.UseUmbracoRequest();
if (env.IsDevelopment())
{
@@ -59,9 +73,17 @@ namespace Umbraco.Web.UI.BackOffice
app.UseUmbracoBackOffice();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
"backoffice-spa",
"/umbraco",
new
{
Controller = "BackOffice",
Action = "Index"
}
);
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync($"<html><body>Hello World!{Current.Profiler.Render()}</body></html>");
@@ -69,4 +91,25 @@ namespace Umbraco.Web.UI.BackOffice
});
}
}
public class UmbracoMiniProfilerOptions : MiniProfilerOptions
{
public UmbracoMiniProfilerOptions()
{
}
public new MiniProfiler StartProfiler(string profilerName = null)
{
return base.StartProfiler();
}
public new Func<HttpRequest, bool> ShouldProfile => (request) =>
{
if (new Uri(request.GetEncodedUrl(), UriKind.RelativeOrAbsolute).IsClientSideRequest()) return false;
if (bool.TryParse(request.Query["umbDebug"], out var umbDebug)) return umbDebug;
if (bool.TryParse(request.Headers["X-UMB-DEBUG"], out var xUmbDebug)) return xUmbDebug;
if (bool.TryParse(request.Cookies["UMB-DEBUG"], out var cUmbDebug)) return cUmbDebug;
return false;
};
}
}
@@ -0,0 +1,20 @@
@using Umbraco.Composing
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>title</title>
</head>
<body>
<div>
Hello World
@Html.Raw(Current.Profiler.Render())
</div>
</body>
</html>
@@ -104,8 +104,11 @@ namespace Umbraco.Web.Editors
public async Task<ActionResult> Default()
{
return await RenderDefaultOrProcessExternalLoginAsync(
() => View(_ioHelper.BackOfficePath.EnsureEndsWith('/') + "Views/Default.cshtml", new BackOfficeModel(_features, GlobalSettings, _umbracoVersion, _contentSettings,_ioHelper, _treeCollection, _httpContextAccessor, _hostingEnvironment, _runtimeSettings, _securitySettings)),
() => View(_ioHelper.BackOfficePath.EnsureEndsWith('/') + "Views/Default.cshtml", new BackOfficeModel(_features, GlobalSettings, _umbracoVersion, _contentSettings, _ioHelper, _treeCollection, _httpContextAccessor, _hostingEnvironment, _runtimeSettings, _securitySettings)));
() =>
View(_ioHelper.BackOfficePath.EnsureEndsWith('/') + "Views/Default.cshtml", new BackOfficeModel(_features, GlobalSettings, _umbracoVersion, _contentSettings,_ioHelper, _treeCollection, _httpContextAccessor, _hostingEnvironment, _runtimeSettings, _securitySettings)),
() =>
View(_ioHelper.BackOfficePath.EnsureEndsWith('/') + "Views/Default.cshtml", new BackOfficeModel(_features, GlobalSettings, _umbracoVersion, _contentSettings, _ioHelper, _treeCollection, _httpContextAccessor, _hostingEnvironment, _runtimeSettings, _securitySettings))
);
}
[HttpGet]