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 @@
-
+