diff --git a/src/Umbraco.Web/Profiling/StartupWebProfilerProvider.cs b/src/Umbraco.Web/Profiling/StartupWebProfilerProvider.cs deleted file mode 100644 index 72a398f17f..0000000000 --- a/src/Umbraco.Web/Profiling/StartupWebProfilerProvider.cs +++ /dev/null @@ -1,159 +0,0 @@ -using System.Threading; -using System.Web; -using StackExchange.Profiling; -using Umbraco.Core; - -namespace Umbraco.Web.Profiling -{ - /// - /// This is a custom MiniProfiler WebRequestProfilerProvider (which is generally the default) that allows - /// us to profile items during app startup - before an HttpRequest is created - /// - /// - /// Once the boot phase is changed to StartupPhase.Request then the base class (default) provider will handle all - /// profiling data and this sub class no longer performs any logic. - /// - internal class StartupWebProfilerProvider : WebRequestProfilerProvider - { - public StartupWebProfilerProvider() - { - _startupPhase = StartupPhase.Boot; - //create the startup profiler - _startupProfiler = new MiniProfiler("http://localhost/umbraco-startup", ProfileLevel.Verbose) - { - Name = "StartupProfiler" - }; - } - - private MiniProfiler _startupProfiler; - private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); - - /// - /// Used to determine which phase the boot process is in - /// - private enum StartupPhase - { - None = 0, - Boot = 1, - Request = 2 - } - - private volatile StartupPhase _startupPhase; - - /// - /// Executed once the application boot process is complete and changes the phase to Request - /// - public void BootComplete() - { - using (new ReadLock(_locker)) - { - if (_startupPhase != StartupPhase.Boot) return; - } - - using (var l = new UpgradeableReadLock(_locker)) - { - if (_startupPhase == StartupPhase.Boot) - { - l.UpgradeToWriteLock(); - _startupPhase = StartupPhase.Request; - } - } - } - - /// - /// Executed when a profiling operation is completed - /// - /// - /// - /// This checks if the bootup phase is None, if so, it just calls the base class, otherwise it checks - /// if a profiler is active (i.e. in startup), then sets the phase to None so that the base class will be used - /// for all subsequent calls. - /// - public override void Stop(bool discardResults) - { - using (new ReadLock(_locker)) - { - if (_startupPhase == StartupPhase.None) - { - base.Stop(discardResults); - return; - } - } - - using (var l = new UpgradeableReadLock(_locker)) - { - if (_startupPhase > 0 && base.GetCurrentProfiler() == null) - { - l.UpgradeToWriteLock(); - - _startupPhase = StartupPhase.None; - - //This is required to pass the mini profiling context from before a request - // to the current startup request. - if (HttpContext.Current != null) - { - HttpContext.Current.Items[":mini-profiler:"] = _startupProfiler; - base.Stop(discardResults); - _startupProfiler = null; - } - } - else - { - base.Stop(discardResults); - } - } - } - - /// - /// Executed when a profiling operation is started - /// - /// - /// - /// - /// This checks if the startup phase is not None, if this is the case and the current profiler is NULL - /// then this sets the startup profiler to be active. Otherwise it just calls the base class Start method. - /// - public override MiniProfiler Start(ProfileLevel level) - { - using (new ReadLock(_locker)) - { - if (_startupPhase > 0 && base.GetCurrentProfiler() == null) - { - SetProfilerActive(_startupProfiler); - return _startupProfiler; - } - - return base.Start(level); - } - } - - /// - /// This returns the current profiler - /// - /// - /// - /// If the boot phase is not None, then this will return the startup profiler (this), otherwise - /// returns the base class - /// - public override MiniProfiler GetCurrentProfiler() - { - using (new ReadLock(_locker)) - { - if (_startupPhase > 0) - { - try - { - var current = base.GetCurrentProfiler(); - if (current == null) return _startupProfiler; - } - catch - { - return _startupProfiler; - } - } - - return base.GetCurrentProfiler(); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Profiling/WebProfiler.cs b/src/Umbraco.Web/Profiling/WebProfiler.cs index a1998c8761..62d69019d6 100644 --- a/src/Umbraco.Web/Profiling/WebProfiler.cs +++ b/src/Umbraco.Web/Profiling/WebProfiler.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Web; using StackExchange.Profiling; using StackExchange.Profiling.SqlFormatters; @@ -14,22 +15,23 @@ namespace Umbraco.Web.Profiling /// internal class WebProfiler : IProfiler { - private StartupWebProfilerProvider _startupWebProfilerProvider; + private const string BootRequestItemKey = "Umbraco.Web.Profiling.WebProfiler__isBootRequest"; + private WebProfilerProvider _provider; + private int _first; /// /// Constructor /// internal WebProfiler() { - //setup some defaults + // create our own provider, which can provide a profiler even during boot + // MiniProfiler's default cannot because there's no HttpRequest in HttpContext + _provider = new WebProfilerProvider(); + + // settings MiniProfiler.Settings.SqlFormatter = new SqlServerFormatter(); MiniProfiler.Settings.StackMaxLength = 5000; - - //At this point we know that we've been constructed during app startup, there won't be an HttpRequest in the HttpContext - // since it hasn't started yet. So we need to do some hacking to enable profiling during startup. - _startupWebProfilerProvider = new StartupWebProfilerProvider(); - //this should always be the case during startup, we'll need to set a custom profiler provider - MiniProfiler.Settings.ProfilerProvider = _startupWebProfilerProvider; + MiniProfiler.Settings.ProfilerProvider = _provider; //Binds to application events to enable the MiniProfiler with a real HttpRequest UmbracoApplicationBase.ApplicationInit += UmbracoApplicationApplicationInit; @@ -57,43 +59,33 @@ namespace Umbraco.Web.Profiling } } - /// - /// Handle the begin request event - /// - /// - /// - void UmbracoApplicationEndRequest(object sender, EventArgs e) - { - if (_startupWebProfilerProvider != null) - { - Stop(); - _startupWebProfilerProvider = null; - } - else if (CanPerformProfilingAction(sender)) - { - Stop(); - } - } - - /// - /// Handle the end request event - /// - /// - /// void UmbracoApplicationBeginRequest(object sender, EventArgs e) { - if (_startupWebProfilerProvider != null) + // 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) { - _startupWebProfilerProvider.BootComplete(); + _provider.BeginBootRequest(); + ((HttpApplication)sender).Context.Items[BootRequestItemKey] = true; + // and no need to start anything, profiler is already there } - - if (CanPerformProfilingAction(sender)) - { + // else start a profiler, the normal way + else if (ShouldProfile(sender)) Start(); - } } - private bool CanPerformProfilingAction(object sender) + void UmbracoApplicationEndRequest(object sender, EventArgs e) + { + // if this is the boot request, or if we should profile this request, stop + // (the boot request is always profiled, no matter what) + var isBootRequest = ((HttpApplication)sender).Context.Items[BootRequestItemKey] != null; // fixme perfs + if (isBootRequest) + _provider.EndBootRequest(); + if (isBootRequest || ShouldProfile(sender)) + Stop(); + } + + private bool ShouldProfile(object sender) { if (GlobalSettings.DebugMode == false) return false; @@ -108,10 +100,10 @@ namespace Umbraco.Web.Profiling return false; if (string.IsNullOrEmpty(request.Result.QueryString["umbDebug"])) - return true; + return false; if (request.Result.Url.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath)) - return true; + return false; return true; } diff --git a/src/Umbraco.Web/Profiling/WebProfilerProvider.cs b/src/Umbraco.Web/Profiling/WebProfilerProvider.cs new file mode 100644 index 0000000000..ffd1871ecc --- /dev/null +++ b/src/Umbraco.Web/Profiling/WebProfilerProvider.cs @@ -0,0 +1,121 @@ +using System; +using System.Threading; +using System.Web; +using StackExchange.Profiling; + +namespace Umbraco.Web.Profiling +{ + /// + /// This is a custom MiniProfiler WebRequestProfilerProvider (which is generally the default) that allows + /// us to profile items during app startup - before an HttpRequest is created + /// + /// + /// Once the boot phase is changed to StartupPhase.Request then the base class (default) provider will handle all + /// profiling data and this sub class no longer performs any logic. + /// + internal class WebProfilerProvider : WebRequestProfilerProvider + { + private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); + private MiniProfiler _startupProfiler; + private int _first; + private volatile BootPhase _bootPhase; + + public WebProfilerProvider() + { + // booting... + _bootPhase = BootPhase.Boot; + } + + /// + /// Indicates the boot phase. + /// + 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 + } + + 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 + HttpContext.Current.Items[":mini-profiler:"] = _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(); + } + } + + /// + /// Executed when a profiling operation is started + /// + /// + /// + /// + /// This checks if the startup phase is not None, if this is the case and the current profiler is NULL + /// then this sets the startup profiler to be active. Otherwise it just calls the base class Start method. + /// + public override MiniProfiler Start(ProfileLevel level) + { + var first = Interlocked.Exchange(ref _first, 1) == 0; + if (first == false) return base.Start(level); + + _startupProfiler = new MiniProfiler("http://localhost/umbraco-startup") { Name = "StartupProfiler" }; + SetProfilerActive(_startupProfiler); + return _startupProfiler; + } + + /// + /// This returns the current profiler + /// + /// + /// + /// If the boot phase is not None, then this will return the startup profiler (this), otherwise + /// returns the base class + /// + public override MiniProfiler GetCurrentProfiler() + { + // if not booting then just use base (fast) + // no lock, _bootPhase is volatile + if (_bootPhase == BootPhase.Booted) + return base.GetCurrentProfiler(); + + // else + try + { + var current = base.GetCurrentProfiler(); + return current ?? _startupProfiler; + } + catch + { + return _startupProfiler; + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 7fb022e848..527d96dcea 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -382,7 +382,7 @@ - +