From dd6c6585b866301aff1e21c80502a20de52af039 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Tue, 10 Jul 2012 16:31:19 -0700 Subject: [PATCH] Adding MiniProfiler module --HG-- branch : NH3 --- .../Four2n.MiniProfiler/ContainerModule.cs | 56 ++++++ .../Data/PoorMansTSqlFormatter.cs | 31 +++ .../ProfiledSqlCeDataServicesProvider.cs | 36 ++++ .../Data/Providers/ProfiledSqlClientDriver.cs | 48 +++++ .../Providers/ProfiledSqlServerCeDriver.cs | 48 +++++ .../ProfiledSqlServerDataServicesProvider.cs | 38 ++++ .../Modules/Four2n.MiniProfiler/Eventer.cs | 56 ++++++ .../Filters/ProfilerFilter.cs | 132 +++++++++++++ .../Four2n.MiniProfiler.csproj | 167 ++++++++++++++++ .../Four2n.MiniProfiler/Models/DummyRecord.cs | 20 ++ .../Modules/Four2n.MiniProfiler/Module.txt | 13 ++ .../OrchardHostProxyLogger.cs | 52 +++++ .../Overrides/ContentPartDriverCoordinator.cs | 109 +++++++++++ .../Overrides/DefaultContentDisplay.cs | 178 ++++++++++++++++++ .../Overrides/ProfilingOrchardEventBus.cs | 135 +++++++++++++ .../Four2n.MiniProfiler/ProfilerStorage.cs | 157 +++++++++++++++ .../Properties/AssemblyInfo.cs | 34 ++++ .../Four2n.MiniProfiler/Scripts/Web.config | 21 +++ .../Services/IProfilerService.cs | 20 ++ .../Services/ProfilerService.cs | 83 ++++++++ .../Four2n.MiniProfiler/ShapeProfiling.cs | 93 +++++++++ .../Modules/Four2n.MiniProfiler/StepKeys.cs | 20 ++ .../Four2n.MiniProfiler/Styles/Web.config | 21 +++ .../Views/MiniProfilerTemplate.cshtml | 4 + .../Four2n.MiniProfiler/Views/Web.config | 41 ++++ .../Modules/Four2n.MiniProfiler/Web.config | 40 ++++ src/Orchard.sln | 13 ++ 27 files changed, 1666 insertions(+) create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/ContainerModule.cs create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/Data/PoorMansTSqlFormatter.cs create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/Data/Providers/ProfiledSqlCeDataServicesProvider.cs create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/Data/Providers/ProfiledSqlClientDriver.cs create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/Data/Providers/ProfiledSqlServerCeDriver.cs create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/Data/Providers/ProfiledSqlServerDataServicesProvider.cs create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/Eventer.cs create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/Filters/ProfilerFilter.cs create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/Four2n.MiniProfiler.csproj create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/Models/DummyRecord.cs create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/Module.txt create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/OrchardHostProxyLogger.cs create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/Overrides/ContentPartDriverCoordinator.cs create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/Overrides/DefaultContentDisplay.cs create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/Overrides/ProfilingOrchardEventBus.cs create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/ProfilerStorage.cs create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/Properties/AssemblyInfo.cs create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/Scripts/Web.config create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/Services/IProfilerService.cs create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/Services/ProfilerService.cs create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/ShapeProfiling.cs create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/StepKeys.cs create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/Styles/Web.config create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/Views/MiniProfilerTemplate.cshtml create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/Views/Web.config create mode 100644 src/Orchard.Web/Modules/Four2n.MiniProfiler/Web.config diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/ContainerModule.cs b/src/Orchard.Web/Modules/Four2n.MiniProfiler/ContainerModule.cs new file mode 100644 index 000000000..bb93e0752 --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/ContainerModule.cs @@ -0,0 +1,56 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2008 Daniel Dabrowski - 42n. All rights reserved. +// +// +// Defines the ContainerModule type. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace Four2n.Orchard.MiniProfiler +{ + using System; + + using Autofac; + + using Four2n.Orchard.MiniProfiler.Data; + + using global::Orchard.Environment; + + using StackExchange.Profiling; + using StackExchange.Profiling.Storage; + + using Module = Autofac.Module; + + public class ContainerModule : Module + { + private readonly IOrchardHost orchardHost; + + public ContainerModule(IOrchardHost orchardHost) + { + this.orchardHost = orchardHost; + } + + protected override void Load(ContainerBuilder moduleBuilder) + { + InitProfilerSettings(); + var currentLogger = ((DefaultOrchardHost)this.orchardHost).Logger; + if (currentLogger is OrchardHostProxyLogger) + { + return; + } + + ((DefaultOrchardHost)this.orchardHost).Logger = new OrchardHostProxyLogger(currentLogger); + } + + private static void InitProfilerSettings() + { + MiniProfiler.Settings.SqlFormatter = new PoorMansTSqlFormatter(); + MiniProfiler.Settings.Storage = new ProfilerStorage(TimeSpan.FromSeconds(30)); + MiniProfiler.Settings.StackMaxLength = 500; + MiniProfiler.Settings.ExcludeAssembly("MiniProfiler"); + MiniProfiler.Settings.ExcludeAssembly("NHibernate"); + WebRequestProfilerProvider.Settings.UserProvider = new IpAddressIdentity(); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/Data/PoorMansTSqlFormatter.cs b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Data/PoorMansTSqlFormatter.cs new file mode 100644 index 000000000..c86d64ea8 --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Data/PoorMansTSqlFormatter.cs @@ -0,0 +1,31 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2008 Daniel Dabrowski - 42n. All rights reserved. +// +// +// Defines the PoorMansTSqlFormatter type. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace Four2n.Orchard.MiniProfiler.Data +{ + using PoorMansTSqlFormatterLib; + using PoorMansTSqlFormatterLib.Formatters; + + using StackExchange.Profiling; + using StackExchange.Profiling.SqlFormatters; + + public class PoorMansTSqlFormatter : ISqlFormatter + { + + public string FormatSql(SqlTiming timing) + { + var sqlFormatter = new SqlServerFormatter(); + var sqlFormat = sqlFormatter.FormatSql(timing); + + var poorMansFormatter = new TSqlStandardFormatter(); + var fullFormatter = new SqlFormattingManager(poorMansFormatter); + return fullFormatter.Format(sqlFormat); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/Data/Providers/ProfiledSqlCeDataServicesProvider.cs b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Data/Providers/ProfiledSqlCeDataServicesProvider.cs new file mode 100644 index 000000000..c80be138c --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Data/Providers/ProfiledSqlCeDataServicesProvider.cs @@ -0,0 +1,36 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2008 Daniel Dabrowski - 42n. All rights reserved. +// +// +// Defines the ProfiledSqlCeDataServicesProvider type. +// +// -------------------------------------------------------------------------------------------------------------------- + +using FluentNHibernate.Cfg.Db; +using MsSqlCeConfiguration = Orchard.Data.Providers.MsSqlCeConfiguration; + +namespace Four2n.Orchard.MiniProfiler.Data.Providers +{ + using global::Orchard.Environment.Extensions; + + [OrchardSuppressDependency("Orchard.Data.Providers.SqlCeDataServicesProvider")] + public class ProfiledSqlCeDataServicesProvider : global::Orchard.Data.Providers.SqlCeDataServicesProvider + { + public ProfiledSqlCeDataServicesProvider(string dataFolder, string connectionString) + : base(dataFolder, connectionString) + { + } + + public static string ProviderName + { + get { return global::Orchard.Data.Providers.SqlCeDataServicesProvider.ProviderName; } + } + + public override IPersistenceConfigurer GetPersistenceConfigurer(bool createDatabase) + { + var persistence = (MsSqlCeConfiguration)base.GetPersistenceConfigurer(createDatabase); + return persistence.Driver(typeof(ProfiledSqlServerCeDriver).AssemblyQualifiedName); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/Data/Providers/ProfiledSqlClientDriver.cs b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Data/Providers/ProfiledSqlClientDriver.cs new file mode 100644 index 000000000..f0b193305 --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Data/Providers/ProfiledSqlClientDriver.cs @@ -0,0 +1,48 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2008 Daniel Dabrowski - 42n. All rights reserved. +// +// +// Defines the ProfiledSqlClientDriver type. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace Four2n.Orchard.MiniProfiler.Data.Providers +{ + using System.Data; + using System.Data.Common; + using System.Diagnostics; + + using NHibernate.Driver; + + using StackExchange.Profiling.Data; + + public class ProfiledSqlClientDriver : SqlClientDriver + { + public override IDbCommand CreateCommand() + { + var command = base.CreateCommand(); + if (StackExchange.Profiling.MiniProfiler.Current != null) + { + command = new ProfiledDbCommand( + (DbCommand)command, + (ProfiledDbConnection)command.Connection, + StackExchange.Profiling.MiniProfiler.Current); + } + + return command; + } + + public override IDbConnection CreateConnection() + { + if (StackExchange.Profiling.MiniProfiler.Current == null) + { + return base.CreateConnection(); + } + + return new ProfiledDbConnection( + base.CreateConnection() as DbConnection, + StackExchange.Profiling.MiniProfiler.Current); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/Data/Providers/ProfiledSqlServerCeDriver.cs b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Data/Providers/ProfiledSqlServerCeDriver.cs new file mode 100644 index 000000000..b01a9f74d --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Data/Providers/ProfiledSqlServerCeDriver.cs @@ -0,0 +1,48 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2008 Daniel Dabrowski - 42n. All rights reserved. +// +// +// Defines the ProfiledSqlServerCeDriver type. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace Four2n.Orchard.MiniProfiler.Data.Providers +{ + using System.Data; + using System.Data.Common; + using System.Diagnostics; + + using global::Orchard.Data.Providers; + + using StackExchange.Profiling.Data; + + public class ProfiledSqlServerCeDriver : SqlCeDataServicesProvider.OrchardSqlServerCeDriver + { + public override IDbCommand CreateCommand() + { + var command = base.CreateCommand(); + if (StackExchange.Profiling.MiniProfiler.Current != null) + { + command = new ProfiledDbCommand( + (DbCommand)command, + (ProfiledDbConnection)command.Connection, + StackExchange.Profiling.MiniProfiler.Current); + } + + return command; + } + + public override IDbConnection CreateConnection() + { + if (StackExchange.Profiling.MiniProfiler.Current == null) + { + return base.CreateConnection(); + } + + return new ProfiledDbConnection( + base.CreateConnection() as DbConnection, + StackExchange.Profiling.MiniProfiler.Current); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/Data/Providers/ProfiledSqlServerDataServicesProvider.cs b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Data/Providers/ProfiledSqlServerDataServicesProvider.cs new file mode 100644 index 000000000..bb8fd8d47 --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Data/Providers/ProfiledSqlServerDataServicesProvider.cs @@ -0,0 +1,38 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2008 Daniel Dabrowski - 42n. All rights reserved. +// +// +// Defines the ProfiledSqlServerDataServicesProvider type. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace Four2n.Orchard.MiniProfiler.Data.Providers +{ + using System.Diagnostics; + + using FluentNHibernate.Cfg.Db; + + using global::Orchard.Environment.Extensions; + + [OrchardSuppressDependency("Orchard.Data.Providers.SqlServerDataServicesProvider")] + public class ProfiledSqlServerDataServicesProvider : global::Orchard.Data.Providers.SqlServerDataServicesProvider + { + public ProfiledSqlServerDataServicesProvider(string dataFolder, string connectionString) + : base(dataFolder, connectionString) + { + } + + public static string ProviderName + { + get { return global::Orchard.Data.Providers.SqlServerDataServicesProvider.ProviderName; } + } + + public override IPersistenceConfigurer GetPersistenceConfigurer(bool createDatabase) + { + var persistence = (MsSqlConfiguration)base.GetPersistenceConfigurer(createDatabase); + Debug.WriteLine("[Four2n.MiniProfiler] - ProfiledSqlServerDataServicesProvider - GetPersistenceConfigurer "); + return persistence.Driver(typeof(ProfiledSqlClientDriver).AssemblyQualifiedName); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/Eventer.cs b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Eventer.cs new file mode 100644 index 000000000..834baa48f --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Eventer.cs @@ -0,0 +1,56 @@ +namespace Four2n.Orchard.MiniProfiler +{ + using System.Diagnostics; + + using global::Orchard.Environment; + using global::Orchard.Environment.Extensions.Models; + using global::Orchard.Environment.State; + + public class Eventer : IFeatureEventHandler, IShellStateManagerEventHandler + { + public void Installing(Feature feature) + { + Debug.WriteLine("Installing"); + } + + public void Installed(Feature feature) + { + Debug.WriteLine("Installed"); + } + + public void Enabling(Feature feature) + { + Debug.WriteLine("Enabling"); + } + + public void Enabled(Feature feature) + { + Debug.WriteLine("Enabled"); + } + + public void Disabling(Feature feature) + { + Debug.WriteLine("Disabling"); + } + + public void Disabled(Feature feature) + { + Debug.WriteLine("Disabled"); + } + + public void Uninstalling(Feature feature) + { + Debug.WriteLine("Uninstalling"); + } + + public void Uninstalled(Feature feature) + { + Debug.WriteLine("uninstalled"); + } + + public void ApplyChanges() + { + Debug.WriteLine("ApplyChanges"); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/Filters/ProfilerFilter.cs b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Filters/ProfilerFilter.cs new file mode 100644 index 000000000..f8de2cab6 --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Filters/ProfilerFilter.cs @@ -0,0 +1,132 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2008 Daniel Dabrowski - 42n. All rights reserved. +// +// +// Filter for injecting profiler view code. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace Four2n.Orchard.MiniProfiler.Filters +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Web; + using System.Web.Mvc; + using System.Web.Routing; + + using global::Orchard; + using global::Orchard.DisplayManagement; + using global::Orchard.Mvc.Filters; + using global::Orchard.Security; + using global::Orchard.UI.Admin; + using Four2n.Orchard.MiniProfiler.Services; + + using StackExchange.Profiling; + + /// + /// Filter for injecting profiler view code. + /// + public class ProfilerFilter : FilterProvider, IResultFilter, IActionFilter + { + #region Constants and Fields + + private readonly IAuthorizer authorizer; + private readonly dynamic shapeFactory; + + private readonly WorkContext workContext; + + private readonly IProfilerService profiler; + + #endregion + + #region Constructors and Destructors + + public ProfilerFilter(WorkContext workContext, IAuthorizer authorizer, IShapeFactory shapeFactory, IProfilerService profiler) + { + this.workContext = workContext; + this.shapeFactory = shapeFactory; + this.authorizer = authorizer; + this.profiler = profiler; + } + + #endregion + + #region Public Methods + + public void OnActionExecuted(ActionExecutedContext filterContext) + { + this.profiler.StepStop(StepKeys.ActionFilter); + } + + public void OnActionExecuting(ActionExecutingContext filterContext) + { + var tokens = filterContext.RouteData.DataTokens; + string area = tokens.ContainsKey("area") && !string.IsNullOrEmpty(tokens["area"].ToString()) ? + string.Concat(tokens["area"], ".") : + string.Empty; + string controller = string.Concat(filterContext.Controller.ToString().Split('.').Last(), "."); + string action = filterContext.ActionDescriptor.ActionName; + this.profiler.StepStart(StepKeys.ActionFilter, "Controller: " + area + controller + action); + } + + public void OnResultExecuted(ResultExecutedContext filterContext) + { + // should only run on a full view rendering result + if (!(filterContext.Result is ViewResult)) + { + return; + } + + if (!this.IsActivable()) + { + return; + } + + this.profiler.StepStop(StepKeys.ResultFilter); + } + + public void OnResultExecuting(ResultExecutingContext filterContext) + { + // should only run on a full view rendering result + if (!(filterContext.Result is ViewResult)) + { + return; + } + + if (!this.IsActivable()) + { + return; + } + + var place = this.workContext.Layout.Footer ?? this.workContext.Layout.Head; + place.Add(this.shapeFactory.MiniProfilerTemplate()); + + this.profiler.StepStart(StepKeys.ResultFilter, string.Format("Result: {0}", filterContext.Result)); + } + + #endregion + + #region Methods + + private bool IsActivable() + { + // activate on front-end only + if (AdminFilter.IsApplied(new RequestContext(this.workContext.HttpContext, new RouteData()))) + { + return false; + } + + // if not logged as a site owner, still activate if it's a local request (development machine) + if (!this.authorizer.Authorize(StandardPermissions.SiteOwner)) + { + return this.workContext.HttpContext.Request.IsLocal; + } + + return true; + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/Four2n.MiniProfiler.csproj b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Four2n.MiniProfiler.csproj new file mode 100644 index 000000000..cdb676e4f --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Four2n.MiniProfiler.csproj @@ -0,0 +1,167 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {CAE8555E-F636-4C97-97A7-A041D3490D28} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + Four2n.Orchard.MiniProfiler + Four2n.MiniProfiler + v4.0 + false + + + 3.5 + + + false + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + AllRules.ruleset + + + pdbonly + true + bin\ + TRACE + prompt + 4 + AllRules.ruleset + + + + False + ..\..\..\..\lib\autofac\Autofac.dll + + + False + ..\..\..\..\lib\claysharp\ClaySharp.dll + + + False + ..\..\..\..\lib\nhibernate\FluentNHibernate.dll + + + + ..\..\lib\MiniProfiler\MiniProfiler.dll + + + ..\..\..\..\lib\nhibernate\NHibernate.dll + + + False + ..\..\lib\PoorMansTSqlFormatter\PoorMansTSqlFormatterLib.dll + + + + + 3.5 + + + + False + ..\..\..\..\lib\aspnetmvc\System.Web.Mvc.dll + + + + + + + + + + + Designer + + + Designer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6} + Orchard.Framework + + + {9916839C-39FC-4CEB-A5AF-89CA7E87119F} + Orchard.Core + + + + + + + + $(ProjectDir)\..\Manifests + + + + + + + + + + + + False + True + 45979 + / + + + False + True + http://orchard.codeplex.com + False + + + + + \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/Models/DummyRecord.cs b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Models/DummyRecord.cs new file mode 100644 index 000000000..eba422006 --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Models/DummyRecord.cs @@ -0,0 +1,20 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2008 Daniel Dabrowski - 42n. All rights reserved. +// +// +// Defines the DummyRecord type. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace Four2n.Orchard.MiniProfiler.Models +{ + using global::Orchard.ContentManagement.Records; + + /// + /// Dummy record for including this module in data configuring. + /// + //public class DummyRecord : ContentPartRecord + //{ + //} +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/Module.txt b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Module.txt new file mode 100644 index 000000000..565d81021 --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Module.txt @@ -0,0 +1,13 @@ +Name: Mini Profiler +AntiForgery: enabled +Author: 42n, Daniel Dabrowski +Website: http://orchardprofiler.codeplex.com/ +Version: 0.4 +OrchardVersion: 1.4 +Description: Module which delivers profiler functionality. + Mvc Mini profiler (http://code.google.com/p/mvc-mini-profiler/) is used for collecting profile data. + Poor Man's T-SQL Formatter (http://www.architectshack.com/PoorMansTSqlFormatter.ashx) is used for nicer SQL timing format. Source code is hosted on GitHub (https://github.com/TaoK/PoorMansTSqlFormatter) + +FeatureDescription: Profiling web site using mvc mini profiler. +Category: Developer +Tags: Profiler, Profiling, mvc-mini-profiler diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/OrchardHostProxyLogger.cs b/src/Orchard.Web/Modules/Four2n.MiniProfiler/OrchardHostProxyLogger.cs new file mode 100644 index 000000000..b02ea0674 --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/OrchardHostProxyLogger.cs @@ -0,0 +1,52 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2008 Daniel Dabrowski - 42n. All rights reserved. +// +// +// Defines the OrchardHostProxyLogger type. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace Four2n.Orchard.MiniProfiler +{ + using System; + + using global::Orchard.Logging; + + using StackExchange.Profiling; + + public class OrchardHostProxyLogger : ILogger + { + private readonly ILogger logger; + + public OrchardHostProxyLogger(ILogger logger) + { + this.logger = logger; + } + + public bool IsEnabled(LogLevel level) + { + return true; + } + + public void Log(LogLevel level, Exception exception, string format, params object[] args) + { + if (level == LogLevel.Debug) + { + if ("BeginRequest".Equals(format)) + { + MiniProfiler.Start(ProfileLevel.Verbose); + } + else if ("EndRequest".Equals(format)) + { + MiniProfiler.Stop(); + } + } + + if (this.logger.IsEnabled(level)) + { + this.logger.Log(level, exception, format, args); + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/Overrides/ContentPartDriverCoordinator.cs b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Overrides/ContentPartDriverCoordinator.cs new file mode 100644 index 000000000..6074d5e66 --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Overrides/ContentPartDriverCoordinator.cs @@ -0,0 +1,109 @@ +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; +using Orchard.ContentManagement.Handlers; +using Orchard.ContentManagement.MetaData; +using Orchard.Logging; +using Orchard.Environment.Extensions; +using Four2n.Orchard.MiniProfiler.Services; +using Orchard.ContentManagement.Drivers; +using Orchard.ContentManagement; +using Orchard; +using System; + +namespace Four2n.MiniProfilter.Overrides { + [OrchardSuppressDependency("Orchard.ContentManagement.Drivers.Coordinators.ContentPartDriverCoordinator")] + public class ProfilingContentPartDriverCoordinator : ContentHandlerBase { + private readonly IEnumerable _drivers; + private readonly IContentDefinitionManager _contentDefinitionManager; + private readonly IProfilerService _profiler; + + public ProfilingContentPartDriverCoordinator(IEnumerable drivers, IContentDefinitionManager contentDefinitionManager, IProfilerService profiler) { + _drivers = drivers; + _contentDefinitionManager = contentDefinitionManager; + Logger = NullLogger.Instance; + _profiler = profiler; + } + + public ILogger Logger { get; set; } + + public override void Activating(ActivatingContentContext context) { + var contentTypeDefinition = _contentDefinitionManager.GetTypeDefinition(context.ContentType); + if (contentTypeDefinition == null) + return; + + var partInfos = _drivers.SelectMany(cpp => cpp.GetPartInfo()); + + foreach (var typePartDefinition in contentTypeDefinition.Parts) { + var partName = typePartDefinition.PartDefinition.Name; + var partInfo = partInfos.FirstOrDefault(pi => pi.PartName == partName); + var part = partInfo != null + ? partInfo.Factory(typePartDefinition) + : new ContentPart { TypePartDefinition = typePartDefinition }; + context.Builder.Weld(part); + } + } + + public override void GetContentItemMetadata(GetContentItemMetadataContext context) { + _drivers.Invoke(driver => driver.GetContentItemMetadata(context), Logger); + } + + public override void BuildDisplay(BuildDisplayContext context) { + _drivers.Invoke(driver => { + var key = "Driver:"+driver.GetType().FullName; + _profiler.StepStart(key, String.Format("ContentPartDriver: {0}", driver.GetType().FullName)); + var result = driver.BuildDisplay(context); + + if (result != null) { + var key2 = "DriverApply:" + driver.GetType().FullName; + _profiler.StepStart(key2, String.Format("ApplyDriver", driver.GetType().FullName)); + result.Apply(context); + _profiler.StepStop(key2); + } + + _profiler.StepStop(key); + + }, Logger); + } + + public override void BuildEditor(BuildEditorContext context) { + _drivers.Invoke(driver => { + var result = driver.BuildEditor(context); + if (result != null) + result.Apply(context); + }, Logger); + } + + public override void UpdateEditor(UpdateEditorContext context) { + _drivers.Invoke(driver => { + var result = driver.UpdateEditor(context); + if (result != null) + result.Apply(context); + }, Logger); + } + + public override void Importing(ImportContentContext context) { + foreach (var contentPartDriver in _drivers) { + contentPartDriver.Importing(context); + } + } + + public override void Imported(ImportContentContext context) { + foreach (var contentPartDriver in _drivers) { + contentPartDriver.Imported(context); + } + } + + public override void Exporting(ExportContentContext context) { + foreach (var contentPartDriver in _drivers) { + contentPartDriver.Exporting(context); + } + } + + public override void Exported(ExportContentContext context) { + foreach (var contentPartDriver in _drivers) { + contentPartDriver.Exported(context); + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/Overrides/DefaultContentDisplay.cs b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Overrides/DefaultContentDisplay.cs new file mode 100644 index 000000000..c88155173 --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Overrides/DefaultContentDisplay.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.Web; +using System.Web.Routing; +using ClaySharp.Implementation; +using Orchard.ContentManagement.Handlers; +using Orchard.DisplayManagement; +using Orchard.DisplayManagement.Descriptors; +using Orchard.FileSystems.VirtualPath; +using Orchard.Logging; +using Orchard.UI.Zones; +using Orchard.Environment.Extensions; +using Four2n.Orchard.MiniProfiler.Services; + +namespace Orchard.ContentManagement { + [OrchardSuppressDependency("Orchard.ContentManagement.DefaultContentDisplay")] + public class ProfilingContentDisplay : IContentDisplay { + private readonly Lazy> _handlers; + private readonly IShapeFactory _shapeFactory; + private readonly Lazy _shapeTableLocator; + + private readonly RequestContext _requestContext; + private readonly IVirtualPathProvider _virtualPathProvider; + private readonly IWorkContextAccessor _workContextAccessor; + private readonly IProfilerService _profiler; + public ProfilingContentDisplay( + Lazy> handlers, + IShapeFactory shapeFactory, + Lazy shapeTableLocator, + RequestContext requestContext, + IVirtualPathProvider virtualPathProvider, + IWorkContextAccessor workContextAccessor, + IProfilerService profiler) { + + _handlers = handlers; + _shapeFactory = shapeFactory; + _shapeTableLocator = shapeTableLocator; + _requestContext = requestContext; + _virtualPathProvider = virtualPathProvider; + _workContextAccessor = workContextAccessor; + _profiler = profiler; + + Logger = NullLogger.Instance; + } + + public ILogger Logger { get; set; } + + public dynamic BuildDisplay(IContent content, string displayType, string groupId) { + var contentKey = "ContentDisplay:" + content.Id.ToString(); + _profiler.StepStart(contentKey, String.Format("Content Display: {0} ({1})", content.Id, displayType)); + var contentTypeDefinition = content.ContentItem.TypeDefinition; + string stereotype; + if (!contentTypeDefinition.Settings.TryGetValue("Stereotype", out stereotype)) + stereotype = "Content"; + + var actualShapeType = stereotype; + var actualDisplayType = string.IsNullOrWhiteSpace(displayType) ? "Detail" : displayType; + + dynamic itemShape = CreateItemShape(actualShapeType); + itemShape.ContentItem = content.ContentItem; + itemShape.Metadata.DisplayType = actualDisplayType; + + var context = new BuildDisplayContext(itemShape, content, actualDisplayType, groupId, _shapeFactory); + BindPlacement(context, actualDisplayType, stereotype); + + _handlers.Value.Invoke(handler => { + var key = String.Format("ContentDisplay:{0}:{1}", content.Id, handler.GetType().FullName); + _profiler.StepStart(key, String.Format("Content Display: {0}", handler.GetType().FullName)); + handler.BuildDisplay(context); + _profiler.StepStop(key); + }, Logger); + _profiler.StepStop(contentKey); + return context.Shape; + } + + public dynamic BuildEditor(IContent content, string groupId) { + var contentKey = "ContentEditor:" + content.Id.ToString(); + _profiler.StepStart(contentKey, String.Format("Content Editor: {0}", content.Id)); + var contentTypeDefinition = content.ContentItem.TypeDefinition; + string stereotype; + if (!contentTypeDefinition.Settings.TryGetValue("Stereotype", out stereotype)) + stereotype = "Content"; + + var actualShapeType = stereotype + "_Edit"; + + dynamic itemShape = CreateItemShape(actualShapeType); + itemShape.ContentItem = content.ContentItem; + + var context = new BuildEditorContext(itemShape, content, groupId, _shapeFactory); + BindPlacement(context, null, stereotype); + + _handlers.Value.Invoke(handler => { + var key = String.Format("ContentEditor:{0}:{1}", content.Id, handler.GetType().FullName); + _profiler.StepStart(key, String.Format("Content Editor: {0}", content.Id)); + handler.BuildEditor(context); + _profiler.StepStop(key); + } + , Logger); + + _profiler.StepStop(contentKey); + return context.Shape; + } + + public dynamic UpdateEditor(IContent content, IUpdateModel updater, string groupInfoId) { + var contentKey = "ContentUpdate:" + content.Id.ToString(); + _profiler.StepStart(contentKey, String.Format("Content Update: {0}", content.Id)); + var contentTypeDefinition = content.ContentItem.TypeDefinition; + string stereotype; + if (!contentTypeDefinition.Settings.TryGetValue("Stereotype", out stereotype)) + stereotype = "Content"; + + var actualShapeType = stereotype + "_Edit"; + + dynamic itemShape = CreateItemShape(actualShapeType); + itemShape.ContentItem = content.ContentItem; + + var theme = _workContextAccessor.GetContext().CurrentTheme; + var shapeTable = _shapeTableLocator.Value.Lookup(theme.Id); + + var context = new UpdateEditorContext(itemShape, content, updater, groupInfoId, _shapeFactory, shapeTable); + BindPlacement(context, null, stereotype); + + _handlers.Value.Invoke(handler => { + var key = String.Format("ContentUpdate:{0}:{1}", content.Id, handler.GetType().FullName); + _profiler.StepStart(key, String.Format("Content Update: {0}", handler.GetType().FullName)); + handler.UpdateEditor(context); + _profiler.StepStop(key); + + }, Logger); + + _profiler.StepStop(contentKey); + return context.Shape; + } + + private dynamic CreateItemShape(string actualShapeType) { + Func call = () => _shapeFactory.Create("ContentZone", Arguments.Empty()); + var zoneHoldingBehavior = new ZoneHoldingBehavior(call, _workContextAccessor.GetContext().Layout); + return _shapeFactory.Create(actualShapeType, Arguments.Empty(), new[] { zoneHoldingBehavior }); + } + + private void BindPlacement(BuildShapeContext context, string displayType, string stereotype) { + context.FindPlacement = (partShapeType, differentiator, defaultLocation) => { + + var workContext = _workContextAccessor.GetContext(_requestContext.HttpContext); + + var theme = workContext.CurrentTheme; + var shapeTable = _shapeTableLocator.Value.Lookup(theme.Id); + + var request = _requestContext.HttpContext.Request; + + ShapeDescriptor descriptor; + if (shapeTable.Descriptors.TryGetValue(partShapeType, out descriptor)) { + var placementContext = new ShapePlacementContext { + ContentType = context.ContentItem.ContentType, + Stereotype = stereotype, + DisplayType = displayType, + Differentiator = differentiator, + Path = VirtualPathUtility.AppendTrailingSlash(_virtualPathProvider.ToAppRelative(request.Path)) // get the current app-relative path, i.e. ~/my-blog/foo + }; + + // define which location should be used if none placement is hit + descriptor.DefaultPlacement = defaultLocation; + + var placement = descriptor.Placement(placementContext); + if (placement != null) { + placement.Source = placementContext.Source; + return placement; + } + } + + return new PlacementInfo { + Location = defaultLocation, + Source = String.Empty + }; + }; + } + } +} diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/Overrides/ProfilingOrchardEventBus.cs b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Overrides/ProfilingOrchardEventBus.cs new file mode 100644 index 000000000..c92c36c1e --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Overrides/ProfilingOrchardEventBus.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Orchard.Exceptions; +using Orchard.Localization; + +using Orchard.Events; +using Orchard.Environment.Extensions; +using Four2n.Orchard.MiniProfiler.Services; + +namespace Four2n.Orchard.MiniProfiler.Overrides { + [OrchardSuppressDependency("Orchard.Events.DefaultOrchardEventBus")] + public class DefaultOrchardEventBus : IEventBus { + private readonly Func> _eventHandlers; + private readonly IExceptionPolicy _exceptionPolicy; + private static readonly ConcurrentDictionary _interfaceMethodsCache = new ConcurrentDictionary(); + private readonly IProfilerService _profiler; + public DefaultOrchardEventBus(Func> eventHandlers, IExceptionPolicy exceptionPolicy, IProfilerService profiler) { + _eventHandlers = eventHandlers; + _exceptionPolicy = exceptionPolicy; + _profiler = profiler; + T = NullLocalizer.Instance; + } + + + public Localizer T { get; set; } + + + public IEnumerable Notify(string messageName, IDictionary eventData) { + // NOTE: We can't profile everything because EventsInterceptor performs some work that's a bit harder to profile without forking or getting our + // own interceptor working... + _profiler.StepStart("EventBusNotify","EventBus: "+messageName); + // call ToArray to ensure evaluation has taken place + var result = NotifyHandlers(messageName, eventData).ToArray(); + _profiler.StepStop("EventBusNotify"); + return result; + } + + private IEnumerable NotifyHandlers(string messageName, IDictionary eventData) { + string[] parameters = messageName.Split('.'); + if (parameters.Length != 2) { + throw new ArgumentException(T("{0} is not formatted correctly", messageName).Text); + } + string interfaceName = parameters[0]; + string methodName = parameters[1]; + + var eventHandlers = _eventHandlers(); + foreach (var eventHandler in eventHandlers) { + IEnumerable returnValue; + if (TryNotifyHandler(eventHandler, messageName, interfaceName, methodName, eventData, out returnValue)) { + if (returnValue != null) { + foreach (var value in returnValue) { + yield return value; + } + } + } + } + } + + private bool TryNotifyHandler(IEventHandler eventHandler, string messageName, string interfaceName, string methodName, IDictionary eventData, out IEnumerable returnValue) { + try { + return TryInvoke(eventHandler, interfaceName, methodName, eventData, out returnValue, _profiler); + } + catch (Exception exception) { + if (!_exceptionPolicy.HandleException(this, exception)) { + throw; + } + + returnValue = null; + return false; + } + } + + private static bool TryInvoke(IEventHandler eventHandler, string interfaceName, string methodName, IDictionary arguments, out IEnumerable returnValue, IProfilerService profiler) { + Type type = eventHandler.GetType(); + foreach (var interfaceType in type.GetInterfaces()) { + if (String.Equals(interfaceType.Name, interfaceName, StringComparison.OrdinalIgnoreCase)) { + return TryInvokeMethod(eventHandler, interfaceType, methodName, arguments, out returnValue, profiler); + } + } + returnValue = null; + return false; + } + + private static bool TryInvokeMethod(IEventHandler eventHandler, Type interfaceType, string methodName, IDictionary arguments, out IEnumerable returnValue, IProfilerService profiler) { + MethodInfo method = _interfaceMethodsCache.GetOrAdd(String.Concat(eventHandler.GetType().Name + "_" + interfaceType.Name, "_", methodName, "_", String.Join("_", arguments.Keys)), GetMatchingMethod(eventHandler, interfaceType, methodName, arguments)); + + if (method != null) { + var parameters = new List(); + foreach (var methodParameter in method.GetParameters()) { + parameters.Add(arguments[methodParameter.Name]); + } + var key= "EventBus:"+eventHandler.GetType().FullName +"."+ methodName; + profiler.StepStart(key,String.Format("EventBus: {0}",eventHandler.GetType().FullName +"."+ methodName),true); + var result = method.Invoke(eventHandler, parameters.ToArray()); + profiler.StepStop(key); + returnValue = result as IEnumerable; + if (returnValue == null && result != null) + returnValue = new[] { result }; + return true; + } + returnValue = null; + return false; + } + + private static MethodInfo GetMatchingMethod(IEventHandler eventHandler, Type interfaceType, string methodName, IDictionary arguments) { + var allMethods = new List(interfaceType.GetMethods()); + var candidates = new List(allMethods); + + foreach (var method in allMethods) { + if (String.Equals(method.Name, methodName, StringComparison.OrdinalIgnoreCase)) { + ParameterInfo[] parameterInfos = method.GetParameters(); + foreach (var parameter in parameterInfos) { + if (!arguments.ContainsKey(parameter.Name)) { + candidates.Remove(method); + break; + } + } + } + else { + candidates.Remove(method); + } + } + + if (candidates.Count != 0) { + return candidates.OrderBy(x => x.GetParameters().Length).Last(); + } + + return null; + } + } + } diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/ProfilerStorage.cs b/src/Orchard.Web/Modules/Four2n.MiniProfiler/ProfilerStorage.cs new file mode 100644 index 000000000..f87cfbd18 --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/ProfilerStorage.cs @@ -0,0 +1,157 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2008 Daniel Dabrowski - 42n. All rights reserved. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace Four2n.Orchard.MiniProfiler +{ + using System; + using System.Collections.Generic; + using System.Web; + + using StackExchange.Profiling; + using StackExchange.Profiling.Storage; + + public class ProfilerStorage : IStorage + { + /// + /// The string that prefixes all keys that MiniProfilers are saved under, e.g. + /// "mini-profiler-ecfb0050-7ce8-4bf1-bf82-2cb38e90e31e". + /// + public const string CacheKeyPrefix = "mini-profiler-"; + + /// + /// How long to cache each for (i.e. the absolute expiration parameter of + /// ) + /// + public TimeSpan CacheDuration { get; set; } + + /// + /// Returns a new HttpRuntimeCacheStorage class that will cache MiniProfilers for the specified duration. + /// + public ProfilerStorage(TimeSpan cacheDuration) + { + CacheDuration = cacheDuration; + } + + /// + /// Saves to the HttpRuntime.Cache under a key concated with + /// and the parameter's . + /// + public void Save(MiniProfiler profiler) + { + InsertIntoCache(GetCacheKey(profiler.Id), profiler); + } + + /// + /// remembers we did not view the profile + /// + public void SetUnviewed(string user, Guid id) + { + var ids = GetPerUserUnviewedIds(user); + lock (ids) + { + if (!ids.Contains(id)) + { + ids.Add(id); + if (ids.Count > 20) + { + ids.RemoveAt(0); + } + } + } + } + + /// + /// Set the profile to viewed for this user + /// + public void SetViewed(string user, Guid id) + { + var ids = GetPerUserUnviewedIds(user); + + lock (ids) + { + ids.Remove(id); + } + } + + /// + /// Returns the saved identified by . Also marks the resulting + /// profiler to true. + /// + public MiniProfiler Load(Guid id) + { + var result = HttpRuntime.Cache[GetCacheKey(id)] as MiniProfiler; + return result; + } + + /// + /// Returns a list of s that haven't been seen by . + /// + /// User identified by the current . + public List GetUnviewedIds(string user) + { + var ids = GetPerUserUnviewedIds(user); + lock (ids) + { + return new List(ids); + } + } + + private void InsertIntoCache(string key, object value) + { + // use insert instead of add; add fails if the item already exists + HttpRuntime.Cache.Insert( + key: key, + value: value, + dependencies: null, + absoluteExpiration: DateTime.Now.Add(CacheDuration), // servers will cache based on local now + slidingExpiration: System.Web.Caching.Cache.NoSlidingExpiration, + priority: System.Web.Caching.CacheItemPriority.Low, + onRemoveCallback: null); + } + + private string GetCacheKey(Guid id) + { + return CacheKeyPrefix + id; + } + + private string GetPerUserUnviewedCacheKey(string user) + { + return CacheKeyPrefix + "unviewed-for-user-" + user; + } + + private List GetPerUserUnviewedIds(MiniProfiler profiler) + { + return GetPerUserUnviewedIds(profiler.User); + } + + private List GetPerUserUnviewedIds(string user) + { + var key = GetPerUserUnviewedCacheKey(user); + var result = HttpRuntime.Cache[key] as List; + + if (result == null) + { + lock (AddPerUserUnviewedIdsLock) + { + // check again, as we could have been waiting + result = HttpRuntime.Cache[key] as List; + if (result == null) + { + result = new List(); + InsertIntoCache(key, result); + } + } + } + + return result; + } + + /// + /// Syncs access to runtime cache when adding a new list of ids for a user. + /// + private static readonly object AddPerUserUnviewedIdsLock = new object(); + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/Properties/AssemblyInfo.cs b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..4da8c8c8a --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Properties/AssemblyInfo.cs @@ -0,0 +1,34 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Four2n.MiniProfiler")] +[assembly: AssemblyDescription("MiniProfiler Orchard Module")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyProduct("MiniProfiler")] +[assembly: AssemblyCopyright("Copyright (c) 2008 Daniel Dabrowski - 42n. All rights reserved.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f15f52be-3cad-4c2e-9a91-8ec5ee8b4df2")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("0.4.0.*")] +[assembly: AssemblyFileVersion("0.4.0.*")] diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/Scripts/Web.config b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Scripts/Web.config new file mode 100644 index 000000000..178ff35ba --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Scripts/Web.config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/Services/IProfilerService.cs b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Services/IProfilerService.cs new file mode 100644 index 000000000..3f9315f2d --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Services/IProfilerService.cs @@ -0,0 +1,20 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2008 Daniel Dabrowski - 42n. All rights reserved. +// +// +// Defines the IProfilerService type. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace Four2n.Orchard.MiniProfiler.Services +{ + using global::Orchard; + + public interface IProfilerService : IDependency + { + void StepStart(string key, string message, bool isVerbose = false); + + void StepStop(string key); + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/Services/ProfilerService.cs b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Services/ProfilerService.cs new file mode 100644 index 000000000..acd5b1f87 --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Services/ProfilerService.cs @@ -0,0 +1,83 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2008 Daniel Dabrowski - 42n. All rights reserved. +// +// +// Defines the ProfilerService type. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace Four2n.Orchard.MiniProfiler.Services +{ + using System; + using System.Collections.Concurrent; + using System.Diagnostics; + + using StackExchange.Profiling; + + public class ProfilerService : IProfilerService, IDisposable + { + private readonly ConcurrentDictionary> steps = new ConcurrentDictionary>(); + + private MiniProfiler profiler; + + public ProfilerService() + { + this.profiler = MiniProfiler.Current; + } + + protected MiniProfiler Profiler + { + get + { + // The event bus starts in a different scope where there's no MiniProfiler.Current, set it now + return this.profiler ?? (this.profiler = MiniProfiler.Current); + } + } + + public void StepStart(string key, string message, bool isVerbose = false) + { + if (this.Profiler == null) + { + return; + } + + var stack = this.steps.GetOrAdd(key, k => new ConcurrentStack()); + var step = this.Profiler.Step(message, isVerbose ? ProfileLevel.Verbose : ProfileLevel.Info); + stack.Push(step); + } + + public void StepStop(string key) + { + if (this.Profiler == null) + { + return; + } + + IDisposable step; + if (this.steps[key].TryPop(out step)) + { + step.Dispose(); + } + } + + public void StopAll() + { + // Dispose any orphaned steps + foreach (var stack in this.steps.Values) + { + IDisposable step; + while (stack.TryPop(out step)) + { + step.Dispose(); + Debug.WriteLine("[Four2n.MiniProfiler] - ProfilerService - StopAll There is some left"); + } + } + } + + public void Dispose() + { + this.StopAll(); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/ShapeProfiling.cs b/src/Orchard.Web/Modules/Four2n.MiniProfiler/ShapeProfiling.cs new file mode 100644 index 000000000..c5522af49 --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/ShapeProfiling.cs @@ -0,0 +1,93 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2008 Daniel Dabrowski - 42n. All rights reserved. +// +// +// Defines the ShapeProfiling type. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace Four2n.Orchard.MiniProfiler +{ + using Four2n.Orchard.MiniProfiler.Services; + + using global::Orchard.ContentManagement; + + using global::Orchard.DisplayManagement.Implementation; + + using global::Orchard.DisplayManagement.Shapes; + + public class ShapeProfiling : IShapeFactoryEvents + { + private readonly IProfilerService profiler; + + public ShapeProfiling(IProfilerService profiler) + { + this.profiler = profiler; + } + + public void Creating(ShapeCreatingContext context) + { + } + + public void Created(ShapeCreatedContext context) + { + var shapeMetadata = (ShapeMetadata)context.Shape.Metadata; +/* + if (shapeMetadata.Type.Equals("Zone") || context.Shape.ContentItem == null) + { + return; + } +*/ + + shapeMetadata.OnDisplaying(this.OnDisplaying); + shapeMetadata.OnDisplayed(this.OnDisplayed); + } + + public void Displaying(ShapeDisplayingContext context) + { + if (context.ShapeMetadata.Type.Equals("Zone")) + { + return; + } + + this.profiler.StepStart(StepKeys.ShapeProfiling, context.ShapeMetadata.Type + " - Display"); + } + + public void Displayed(ShapeDisplayedContext context) + { + if (context.ShapeMetadata.Type.Equals("Zone")) + { + return; + } + + this.profiler.StepStop(StepKeys.ShapeProfiling); + } + + public void OnDisplaying(ShapeDisplayingContext context) + { + IContent content = null; + if (context.Shape.ContentItem != null) + { + content = context.Shape.ContentItem; + } + else if (context.Shape.ContentPart != null) + { + content = context.Shape.ContentPart; + } + + var message = string.Format( + "Shape Display: {0} ({1}) ({2})", + context.ShapeMetadata.Type, + context.ShapeMetadata.DisplayType, + (string)(content != null ? content.ContentItem.ContentType : "non-content")); + + this.profiler.StepStart(StepKeys.ShapeProfiling, message, true); + } + + public void OnDisplayed(ShapeDisplayedContext context) + { + this.profiler.StepStop(StepKeys.ShapeProfiling); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/StepKeys.cs b/src/Orchard.Web/Modules/Four2n.MiniProfiler/StepKeys.cs new file mode 100644 index 000000000..0900be622 --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/StepKeys.cs @@ -0,0 +1,20 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2008 Daniel Dabrowski - 42n. All rights reserved. +// +// +// Defines the StepKeys type. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace Four2n.Orchard.MiniProfiler +{ + public class StepKeys + { + public const string ActionFilter = "ActionFilter"; + + public const string ResultFilter = "ResultFilter"; + + public const string ShapeProfiling = "ShapeProfiling"; + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/Styles/Web.config b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Styles/Web.config new file mode 100644 index 000000000..178ff35ba --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Styles/Web.config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/Views/MiniProfilerTemplate.cshtml b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Views/MiniProfilerTemplate.cshtml new file mode 100644 index 000000000..66f3d5788 --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Views/MiniProfilerTemplate.cshtml @@ -0,0 +1,4 @@ +@{ + Script.Require("jQuery"); +} +@StackExchange.Profiling.MiniProfiler.RenderIncludes(xhtml: true, showControls: true) \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/Views/Web.config b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Views/Web.config new file mode 100644 index 000000000..b7d215131 --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Views/Web.config @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Orchard.Web/Modules/Four2n.MiniProfiler/Web.config b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Web.config new file mode 100644 index 000000000..639c32ba6 --- /dev/null +++ b/src/Orchard.Web/Modules/Four2n.MiniProfiler/Web.config @@ -0,0 +1,40 @@ + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Orchard.sln b/src/Orchard.sln index bbbc4ab7a..c9499362e 100644 --- a/src/Orchard.sln +++ b/src/Orchard.sln @@ -148,6 +148,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.ContentPicker", "Or EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.ContentPermissions", "Orchard.Web\Modules\Orchard.ContentPermissions\Orchard.ContentPermissions.csproj", "{E826F796-8CE3-4B5B-8423-5AA5F81D2FC3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Four2n.MiniProfiler", "Orchard.Web\Modules\Four2n.MiniProfiler\Four2n.MiniProfiler.csproj", "{CAE8555E-F636-4C97-97A7-A041D3490D28}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution CodeCoverage|Any CPU = CodeCoverage|Any CPU @@ -805,6 +807,16 @@ Global {E826F796-8CE3-4B5B-8423-5AA5F81D2FC3}.FxCop|Any CPU.Build.0 = Release|Any CPU {E826F796-8CE3-4B5B-8423-5AA5F81D2FC3}.Release|Any CPU.ActiveCfg = Release|Any CPU {E826F796-8CE3-4B5B-8423-5AA5F81D2FC3}.Release|Any CPU.Build.0 = Release|Any CPU + {CAE8555E-F636-4C97-97A7-A041D3490D28}.CodeCoverage|Any CPU.ActiveCfg = Release|Any CPU + {CAE8555E-F636-4C97-97A7-A041D3490D28}.CodeCoverage|Any CPU.Build.0 = Release|Any CPU + {CAE8555E-F636-4C97-97A7-A041D3490D28}.Coverage|Any CPU.ActiveCfg = Release|Any CPU + {CAE8555E-F636-4C97-97A7-A041D3490D28}.Coverage|Any CPU.Build.0 = Release|Any CPU + {CAE8555E-F636-4C97-97A7-A041D3490D28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CAE8555E-F636-4C97-97A7-A041D3490D28}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CAE8555E-F636-4C97-97A7-A041D3490D28}.FxCop|Any CPU.ActiveCfg = Release|Any CPU + {CAE8555E-F636-4C97-97A7-A041D3490D28}.FxCop|Any CPU.Build.0 = Release|Any CPU + {CAE8555E-F636-4C97-97A7-A041D3490D28}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CAE8555E-F636-4C97-97A7-A041D3490D28}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -858,6 +870,7 @@ Global {2CF067CA-064B-43C6-8B88-5E3B99A65F1D} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} {F301EF7D-F19C-4D83-AA94-CB64F29C037D} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} {E826F796-8CE3-4B5B-8423-5AA5F81D2FC3} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} + {CAE8555E-F636-4C97-97A7-A041D3490D28} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} {ABC826D4-2FA1-4F2F-87DE-E6095F653810} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA} {F112851D-B023-4746-B6B1-8D2E5AD8F7AA} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA} {6CB3EB30-F725-45C0-9742-42599BA8E8D2} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA}