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:
Shannon Deminick
2012-11-12 08:10:12 +05:00
parent 083bab0528
commit 156f145c69
11 changed files with 183 additions and 36 deletions

View File

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

View File

@@ -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[]

View File

@@ -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[]

View File

@@ -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[]

View File

@@ -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[]

View File

@@ -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));
}

View File

@@ -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[]

View File

@@ -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[]

View File

@@ -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[]

View File

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

View File

@@ -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);
}
};
}