using System; using System.Threading; using System.Web; using StackExchange.Profiling; using StackExchange.Profiling.Internal; namespace Umbraco.Web.Logging { /// /// 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 BootPhase.BootRequest then the base class (default) provider will handle all /// profiling data and this sub class no longer performs any logic. /// internal class WebProfilerProvider : AspNetRequestProvider { 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(); } } /// /// Starts a new MiniProfiler. /// /// /// 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. /// 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; } /// /// Gets the current profiler. /// /// /// If the boot phase is not Booted, then this will return the startup profiler (this), otherwise /// returns the base class /// 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; } } } } }