Updated the build manager to find instances based on persisted cache of previous lookups but uses BuildManager to do so, after
some benchmark unit tests against loading a type from an assembly this is about 100% faster. Updated the UmbracoModule to write more diagnostics for benchmark debugging. Have basically reduced the startup time to approx 50% of what it used to be based on the current benchmarks run. Previously app startup on my machine was about 5 seconds, now after the first startup it is about 2.5 seconds.
This commit is contained in:
@@ -7,6 +7,8 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Web.Caching;
|
||||
using System.Web.Compilation;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Umbraco.Core.Configuration;
|
||||
@@ -33,8 +35,29 @@ namespace Umbraco.Core
|
||||
/// </remarks>
|
||||
internal class PluginManager
|
||||
{
|
||||
private readonly ApplicationContext _appContext;
|
||||
|
||||
internal PluginManager()
|
||||
/// <summary>
|
||||
/// Creates a new PluginManager with an ApplicationContext instance which ensures that the plugin xml
|
||||
/// file is cached temporarily until app startup completes.
|
||||
/// </summary>
|
||||
/// <param name="appContext"></param>
|
||||
/// <param name="detectBinChanges"></param>
|
||||
internal PluginManager(ApplicationContext appContext, bool detectBinChanges = true)
|
||||
: this(detectBinChanges)
|
||||
{
|
||||
if (appContext == null) throw new ArgumentNullException("appContext");
|
||||
_appContext = appContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new PluginManager
|
||||
/// </summary>
|
||||
/// <param name="detectBinChanges">
|
||||
/// If true will detect changes in the /bin folder and therefor load plugins from the
|
||||
/// cached plugins file if one is found. If false will never use the cache file for plugins
|
||||
/// </param>
|
||||
internal PluginManager(bool detectBinChanges = true)
|
||||
{
|
||||
_tempFolder = IOHelper.MapPath("~/App_Data/TEMP/PluginCache");
|
||||
//create the folder if it doesn't exist
|
||||
@@ -42,13 +65,24 @@ namespace Umbraco.Core
|
||||
{
|
||||
Directory.CreateDirectory(_tempFolder);
|
||||
}
|
||||
//do the check if they've changed
|
||||
HaveAssembliesChanged = CachedAssembliesHash != CurrentAssembliesHash;
|
||||
//if they have changed, we need to write the new file
|
||||
if (HaveAssembliesChanged)
|
||||
|
||||
if (detectBinChanges)
|
||||
{
|
||||
WriteCachePluginsHash();
|
||||
//first check if the cached hash is 0, if it is then we ne
|
||||
//do the check if they've changed
|
||||
HaveAssembliesChanged = (CachedAssembliesHash != CurrentAssembliesHash) || CachedAssembliesHash == 0;
|
||||
//if they have changed, we need to write the new file
|
||||
if (HaveAssembliesChanged)
|
||||
{
|
||||
WriteCachePluginsHash();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//always set to true if we're not detecting (generally only for testing)
|
||||
HaveAssembliesChanged = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static PluginManager _resolver;
|
||||
@@ -72,7 +106,9 @@ namespace Umbraco.Core
|
||||
if (_resolver == null)
|
||||
{
|
||||
l.UpgradeToWriteLock();
|
||||
_resolver = new PluginManager();
|
||||
_resolver = ApplicationContext.Current == null
|
||||
? new PluginManager()
|
||||
: new PluginManager(ApplicationContext.Current);
|
||||
}
|
||||
return _resolver;
|
||||
}
|
||||
@@ -180,37 +216,49 @@ namespace Umbraco.Core
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
internal Attempt<IEnumerable<Tuple<string, string>>> TryGetCachedPluginsFromFile<T>()
|
||||
internal Attempt<IEnumerable<string>> TryGetCachedPluginsFromFile<T>()
|
||||
{
|
||||
var filePath = Path.Combine(_tempFolder, "umbraco-plugins.list");
|
||||
if (!File.Exists(filePath))
|
||||
return Attempt<IEnumerable<Tuple<string, string>>>.False;
|
||||
return Attempt<IEnumerable<string>>.False;
|
||||
|
||||
try
|
||||
{
|
||||
var xml = XDocument.Load(filePath);
|
||||
//we will load the xml document, if the app context exist, we will load it from the cache (which is only around for 5 minutes)
|
||||
//while the app boots up, this should save some IO time on app startup when the app context is there (which is always unless in unit tests)
|
||||
XDocument xml;
|
||||
if (_appContext != null)
|
||||
{
|
||||
xml = _appContext.ApplicationCache.GetCacheItem("umbraco-plugins.list",
|
||||
new TimeSpan(0, 0, 5, 0),
|
||||
() => XDocument.Load(filePath));
|
||||
}
|
||||
else
|
||||
{
|
||||
xml = XDocument.Load(filePath);
|
||||
}
|
||||
|
||||
|
||||
if (xml.Root == null)
|
||||
return Attempt<IEnumerable<Tuple<string, string>>>.False;
|
||||
return Attempt<IEnumerable<string>>.False;
|
||||
|
||||
var typeElement = xml.Root.Elements()
|
||||
.SingleOrDefault(x =>
|
||||
x.Name.LocalName == "baseType"
|
||||
&& ((string) x.Attribute("type")) == typeof (T).FullName);
|
||||
if (typeElement == null)
|
||||
return Attempt<IEnumerable<Tuple<string, string>>>.False;
|
||||
return Attempt<IEnumerable<string>>.False;
|
||||
|
||||
//return success
|
||||
return new Attempt<IEnumerable<Tuple<string, string>>>(
|
||||
return new Attempt<IEnumerable<string>>(
|
||||
true,
|
||||
typeElement.Elements("add")
|
||||
.Select(x => new Tuple<string, string>(
|
||||
(string) x.Attribute("type"),
|
||||
(string) x.Attribute("assembly"))));
|
||||
.Select(x => (string) x.Attribute("type")));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//if the file is corrupted, etc... return false
|
||||
return Attempt<IEnumerable<Tuple<string, string>>>.False;
|
||||
return Attempt<IEnumerable<string>>.False;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,10 +316,9 @@ namespace Umbraco.Core
|
||||
|
||||
//now we have the type element, we need to clear any previous types as children and add/update it with new ones
|
||||
typeElement.ReplaceNodes(typesFound
|
||||
.Select(x =>
|
||||
new XElement("add",
|
||||
new XAttribute("type", x.FullName),
|
||||
new XAttribute("assembly", x.Assembly.FullName))));
|
||||
.Select(x =>
|
||||
new XElement("add",
|
||||
new XAttribute("type", x.AssemblyQualifiedName))));
|
||||
//save the xml file
|
||||
xml.Save(filePath);
|
||||
}
|
||||
@@ -444,15 +491,17 @@ namespace Umbraco.Core
|
||||
{
|
||||
try
|
||||
{
|
||||
var type = Assembly.Load(t.Item2).GetType(t.Item1);
|
||||
typeList.AddType(type);
|
||||
//we use the build manager to ensure we get all types loaded, this is slightly slower than
|
||||
//Type.GetType but if the types in the assembly aren't loaded yet then we have problems with that.
|
||||
var type = BuildManager.GetType(t, true);
|
||||
typeList.AddType(type);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//if there are any exceptions loading types, we have to exist, this should never happen so
|
||||
//we will need to revert to scanning for types.
|
||||
successfullyLoadedFromCache = false;
|
||||
LogHelper.Error<PluginManager>("Could not load a cached plugin type: " + t.Item1 + " in assembly: " + t.Item2 + " now reverting to re-scanning assemblies for the base type: " + typeof (T).FullName, ex);
|
||||
LogHelper.Error<PluginManager>("Could not load a cached plugin type: " + t + " now reverting to re-scanning assemblies for the base type: " + typeof (T).FullName, ex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -461,6 +510,10 @@ namespace Umbraco.Core
|
||||
//we need to manually load by scanning if loading from the file was not successful.
|
||||
LoadViaScanningAndUpdateCacheFile<T>(typeList, finder);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.Debug<PluginManager>("Loaded plugin types " + typeof(T).FullName + " from persisted cache");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Umbraco.Tests
|
||||
TestHelper.SetupLog4NetForTests();
|
||||
|
||||
//this ensures its reset
|
||||
PluginManager.Current = new PluginManager();
|
||||
PluginManager.Current = new PluginManager(false);
|
||||
|
||||
//for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
|
||||
PluginManager.Current.AssembliesToScan = new[]
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Umbraco.Tests
|
||||
TestHelper.SetupLog4NetForTests();
|
||||
|
||||
//this ensures its reset
|
||||
PluginManager.Current = new PluginManager();
|
||||
PluginManager.Current = new PluginManager(false);
|
||||
|
||||
//for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
|
||||
PluginManager.Current.AssembliesToScan = new[]
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Umbraco.Tests
|
||||
TestHelper.SetupLog4NetForTests();
|
||||
|
||||
//this ensures its reset
|
||||
PluginManager.Current = new PluginManager();
|
||||
PluginManager.Current = new PluginManager(false);
|
||||
|
||||
//for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
|
||||
PluginManager.Current.AssembliesToScan = new[]
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Umbraco.Tests
|
||||
TestHelper.SetupLog4NetForTests();
|
||||
|
||||
//this ensures its reset
|
||||
PluginManager.Current = new PluginManager();
|
||||
PluginManager.Current = new PluginManager(false);
|
||||
|
||||
//for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
|
||||
PluginManager.Current.AssembliesToScan = new[]
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Web.Compilation;
|
||||
using NUnit.Framework;
|
||||
using SqlCE4Umbraco;
|
||||
using Umbraco.Core;
|
||||
@@ -15,6 +17,7 @@ using umbraco.MacroEngines.Iron;
|
||||
using umbraco.businesslogic;
|
||||
using umbraco.cms.businesslogic;
|
||||
using umbraco.editorControls;
|
||||
using umbraco.interfaces;
|
||||
using umbraco.uicontrols;
|
||||
using umbraco.cms;
|
||||
|
||||
@@ -31,7 +34,7 @@ namespace Umbraco.Tests
|
||||
TestHelper.SetupLog4NetForTests();
|
||||
|
||||
//this ensures its reset
|
||||
PluginManager.Current = new PluginManager();
|
||||
PluginManager.Current = new PluginManager(false);
|
||||
|
||||
//for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
|
||||
//TODO: Should probably update this so it only searches this assembly and add custom types to be found
|
||||
@@ -69,19 +72,97 @@ namespace Umbraco.Tests
|
||||
return dir;
|
||||
}
|
||||
|
||||
//[Test]
|
||||
//public void Scan_Vs_Load_Benchmark()
|
||||
//{
|
||||
// var pluginManager = new PluginManager(false);
|
||||
// var watch = new Stopwatch();
|
||||
// watch.Start();
|
||||
// for (var i = 0; i < 1000; i++)
|
||||
// {
|
||||
// var type2 = Type.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
|
||||
// var type3 = Type.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
|
||||
// var type4 = Type.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
|
||||
// var type5 = Type.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
|
||||
// }
|
||||
// watch.Stop();
|
||||
// Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds);
|
||||
// watch.Start();
|
||||
// for (var i = 0; i < 1000; i++)
|
||||
// {
|
||||
// var type2 = BuildManager.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
|
||||
// var type3 = BuildManager.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
|
||||
// var type4 = BuildManager.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
|
||||
// var type5 = BuildManager.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
|
||||
// }
|
||||
// watch.Stop();
|
||||
// Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds);
|
||||
// watch.Reset();
|
||||
// watch.Start();
|
||||
// for (var i = 0; i < 1000; i++)
|
||||
// {
|
||||
// var refreshers = pluginManager.ResolveTypes<ICacheRefresher>(false);
|
||||
// }
|
||||
// watch.Stop();
|
||||
// Debug.WriteLine("TOTAL TIME (2nd round): " + watch.ElapsedMilliseconds);
|
||||
//}
|
||||
|
||||
////NOTE: This test shows that Type.GetType is 100% faster than Assembly.Load(..).GetType(...) so we'll use that :)
|
||||
//[Test]
|
||||
//public void Load_Type_Benchmark()
|
||||
//{
|
||||
// var watch = new Stopwatch();
|
||||
// watch.Start();
|
||||
// for (var i = 0; i < 1000; i++)
|
||||
// {
|
||||
// var type2 = Type.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
|
||||
// var type3 = Type.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
|
||||
// var type4 = Type.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
|
||||
// var type5 = Type.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
|
||||
// }
|
||||
// watch.Stop();
|
||||
// Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds);
|
||||
// watch.Reset();
|
||||
// watch.Start();
|
||||
// for (var i = 0; i < 1000; i++)
|
||||
// {
|
||||
// var type2 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null")
|
||||
// .GetType("umbraco.macroCacheRefresh");
|
||||
// var type3 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null")
|
||||
// .GetType("umbraco.templateCacheRefresh");
|
||||
// var type4 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null")
|
||||
// .GetType("umbraco.presentation.cache.MediaLibraryRefreshers");
|
||||
// var type5 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null")
|
||||
// .GetType("umbraco.presentation.cache.pageRefresher");
|
||||
// }
|
||||
// watch.Stop();
|
||||
// Debug.WriteLine("TOTAL TIME (2nd round): " + watch.ElapsedMilliseconds);
|
||||
// watch.Reset();
|
||||
// watch.Start();
|
||||
// for (var i = 0; i < 1000; i++)
|
||||
// {
|
||||
// var type2 = BuildManager.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
|
||||
// var type3 = BuildManager.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
|
||||
// var type4 = BuildManager.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
|
||||
// var type5 = BuildManager.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
|
||||
// }
|
||||
// watch.Stop();
|
||||
// Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds);
|
||||
//}
|
||||
|
||||
[Test]
|
||||
public void Create_Cached_Plugin_File()
|
||||
{
|
||||
var types = new[] {typeof (PluginManager), typeof (PluginManagerTests), typeof (UmbracoContext)};
|
||||
|
||||
var manager = new PluginManager();
|
||||
var manager = new PluginManager(false);
|
||||
//yes this is silly, none of these types inherit from string, but this is just to test the xml file format
|
||||
manager.UpdateCachedPluginsFile<string>(types);
|
||||
|
||||
var plugins = manager.TryGetCachedPluginsFromFile<string>();
|
||||
Assert.IsTrue(plugins.Success);
|
||||
Assert.AreEqual(3, plugins.Result.Count());
|
||||
var shouldContain = types.Select(x => new System.Tuple<string, string>(x.FullName, x.Assembly.FullName));
|
||||
var shouldContain = types.Select(x => x.AssemblyQualifiedName);
|
||||
//ensure they are all found
|
||||
Assert.IsTrue(plugins.Result.ContainsAll(shouldContain));
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Umbraco.Tests.Resolvers
|
||||
TestHelper.SetupLog4NetForTests();
|
||||
|
||||
//this ensures its reset
|
||||
PluginManager.Current = new PluginManager();
|
||||
PluginManager.Current = new PluginManager(false);
|
||||
|
||||
//for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
|
||||
PluginManager.Current.AssembliesToScan = new[]
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Umbraco.Tests.Resolvers
|
||||
TestHelper.SetupLog4NetForTests();
|
||||
|
||||
//this ensures its reset
|
||||
PluginManager.Current = new PluginManager();
|
||||
PluginManager.Current = new PluginManager(false);
|
||||
|
||||
//for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
|
||||
PluginManager.Current.AssembliesToScan = new[]
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Umbraco.Tests.Resolvers
|
||||
TestHelper.SetupLog4NetForTests();
|
||||
|
||||
//this ensures its reset
|
||||
PluginManager.Current = new PluginManager();
|
||||
PluginManager.Current = new PluginManager(false);
|
||||
|
||||
//for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
|
||||
PluginManager.Current.AssembliesToScan = new[]
|
||||
|
||||
@@ -47,6 +47,8 @@ namespace Umbraco.Web
|
||||
if (httpContext == null) throw new ArgumentNullException("httpContext");
|
||||
if (applicationContext == null) throw new ArgumentNullException("applicationContext");
|
||||
|
||||
ObjectCreated = DateTime.Now;
|
||||
|
||||
HttpContext = httpContext;
|
||||
Application = applicationContext;
|
||||
RoutesCache = routesCache;
|
||||
@@ -99,6 +101,13 @@ namespace Umbraco.Web
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used internally for performance calculations, the ObjectCreated DateTime is set as soon as this
|
||||
/// object is instantiated which in the web site is created during the BeginRequest phase.
|
||||
/// We can then determine complete rendering time from that.
|
||||
/// </summary>
|
||||
internal DateTime ObjectCreated { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current ApplicationContext
|
||||
/// </summary>
|
||||
|
||||
@@ -425,8 +425,12 @@ namespace Umbraco.Web
|
||||
app.EndRequest += (sender, args) =>
|
||||
{
|
||||
var httpContext = ((HttpApplication)sender).Context;
|
||||
//write the trace output for diagnostics at the end of the request
|
||||
httpContext.Trace.Write("UmbracoModule", "Umbraco request completed");
|
||||
if (UmbracoContext.Current != null && UmbracoContext.Current.IsFrontEndUmbracoRequest)
|
||||
{
|
||||
//write the trace output for diagnostics at the end of the request
|
||||
httpContext.Trace.Write("UmbracoModule", "Umbraco request completed");
|
||||
LogHelper.Debug<UmbracoModule>("Total milliseconds for umbraco request to process: " + DateTime.Now.Subtract(UmbracoContext.Current.ObjectCreated).TotalMilliseconds);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user