Click here to Skip to main content
15,884,298 members
Articles / Web Development / HTML

NTime - Performance unit testing tool

Rate me:
Please Sign up or sign in to vote.
4.73/5 (27 votes)
30 Mar 20066 min read 432.3K   8.2K   163  
An article on a performance testing tool to test an application against its performance
#region Copyright (c) 2004, Adam Slosarski
/************************************************************************************
'
' Copyright  2004 Adam Slosarski
'
' This software is provided 'as-is', without any express or implied warranty. In no 
' event will the authors be held liable for any damages arising from the use of this 
' software.
' 
' Permission is granted to anyone to use this software for any purpose, including 
' commercial applications, and to alter it and redistribute it freely, subject to the 
' following restrictions:
'
' 1. The origin of this software must not be misrepresented; you must not claim that 
' you wrote the original software. If you use this software in a product, an 
' acknowledgment (see the following) in the product documentation is required.
'
' Portions Copyright  2004 Adam Slosarski
'
' 2. Altered source versions must be plainly marked as such, and must not be 
' misrepresented as being the original software.
'
' 3. This notice may not be removed or altered from any source distribution.
'
'***********************************************************************************/
#endregion
using System;
using System.Collections;
using System.Reflection;
using System.Windows.Forms;
using NTime.Framework;

namespace NTime
{
	/// <summary>
	/// This is Unit test result enumeration status.
	/// </summary>
	public enum UnitResult 
	{
		/// <summary>
		/// Unknown test state, grey indicator.
		/// </summary>
		Unknown,
		/// <summary>
		/// Accepted test state, green indicator.
		/// </summary>
		Accepted,
		/// <summary>
		/// Rejected test state, red indicator.
		/// </summary>
		Rejected,
		/// <summary>
		/// Ignored test state, yellow indicator.
		/// </summary>
		Ignored}
	/// <summary>
	/// This class isolates performance test assemblies in application domain.
	/// </summary>
	public class AssemblyDomain
	{
		/// <summary>
		/// Application domain for internal use.
		/// </summary>
		private System.AppDomain appDomain;
		/// <summary>
		/// Initializes class to isolate performance test assemblies.
		/// </summary>
		public AssemblyDomain()
		{
		}

		/// <summary>
		/// Creates application domain to manage performance test assemblies.
		/// </summary>
		/// <returns>Proxy remote class to access performance test assemblies.</returns>
		public RemoteLoader CreateDomain(string[] assemblies, string configurationFileName)
		{
			if(assemblies.Length == 0)
			{
				appDomain = null;
				return null;
			}
			else
			{
				string paths = "";
				string appPath = "";
				foreach(string s in assemblies)
				{
					paths += System.IO.Path.GetDirectoryName(s) + ";";
					if(appPath == "" || System.IO.Path.GetDirectoryName(s).Length < System.IO.Path.GetDirectoryName(appPath).Length)
						appPath = System.IO.Path.GetDirectoryName(s);
				}
				System.AppDomainSetup appds = new AppDomainSetup();
				appds.ShadowCopyFiles = "true";
				appds.ConfigurationFile = configurationFileName;
				appds.ApplicationName = "Performance Tester";
				appds.ApplicationBase = appPath; // addins
//				appds.ApplicationBase = System.IO.Directory.GetCurrentDirectory(); // addins
// applicationbase przeszukuje cale drzewo katalogow rekurencyjnie a potem uzywa privatebinpath
//				appds.ApplicationBase = System.IO.Path.GetDirectoryName(Application.ExecutablePath);
				appds.PrivateBinPath = paths + System.IO.Path.GetDirectoryName(Application.ExecutablePath); // remoteloader
//				appds.PrivateBinPathProbe = paths + System.IO.Path.GetDirectoryName(Application.ExecutablePath);
//				appds.PrivateBinPath = paths + System.IO.Directory.GetCurrentDirectory(); // remoteloader
				appds.ShadowCopyDirectories = paths;// + System.IO.Path.GetDirectoryName(Application.ExecutablePath); // addins
				appDomain = System.AppDomain.CreateDomain("Tests", null, appds);
				RemoteLoader rl = (RemoteLoader)appDomain.CreateInstanceFromAndUnwrap(Application.ExecutablePath, "NTime.RemoteLoader");
				return rl;
			}
		}

		/// <summary>
		/// Destroys application domain to free up loaded performance test assemblies.
		/// </summary>
		public void DestroyDomain()
		{
			System.AppDomain.Unload(appDomain);
		}
	}


	/// <summary>
	/// This class is test container.
	/// </summary>
	public class TestFixture : MarshalByRefObject
	{
		/// <summary>
		/// Array of test units contained in TestFixture.
		/// </summary>
		public ArrayList Units = new ArrayList();

		/// <summary>
		/// Global reflection method for TestFixture startup.
		/// </summary>
		public MethodInfo StartUpObjectMethod;

		/// <summary>
		/// Global reflection type for TestFixture startup.
		/// </summary>
		public Type StartUpObjectType;

		/// <summary>
		/// Global reflection method for TestFixture teardown.
		/// </summary>
		public MethodInfo TearDownObjectMethod;

		/// <summary>
		/// Global reflection type for TestFixture teardown.
		/// </summary>
		public Type TearDownObjectType;

		/// <summary>
		/// Local reflection method for TestFixture startup.
		/// </summary>
		public MethodInfo StartUpLocalObjectMethod;

		/// <summary>
		/// Local reflection type for TestFixture startup.
		/// </summary>
		public Type StartUpLocalObjectType;

		/// <summary>
		/// Local reflection method for TestFixture teardown.
		/// </summary>
		public MethodInfo TearDownLocalObjectMethod;

		/// <summary>
		/// Local reflection type for TestFixture teardown.
		/// </summary>
		public Type TearDownLocalObjectType;

		public override object InitializeLifetimeService()
		{
			return null;
		}
	}

	/// <summary>
	/// Type of test. This is a constant.
	/// </summary>
	public enum TestKind {
		/// <summary>
		/// Type of test is Duration test.
		/// </summary>
		Duration,
		/// <summary>
		/// Type of test is Hitcount test.
		/// </summary>
		Hitcount,
		/// <summary>
		/// Type of test is Performance counter test.
		/// </summary>
		Counter,
		/// <summary>
		/// Type of test is internal timing test to calculate body execution time.
		/// </summary>
		InternalTiming};

	/// <summary>
	/// This class is test unit for attributed test method.
	/// </summary>
	public class TestUnit : MarshalByRefObject
	{
		/// <summary>
		/// Reflection type for test fixture. Used with dynamic method invoking.
		/// </summary>
		public Type ObjectType;

		/// <summary>
		/// Reflection method for attributed test method. Used with dynamic method invoking.
		/// </summary>
		public MethodInfo ObjectMethod;

		/// <summary>
		/// Namespace with class and method string. Used to generate nodes in TreeView.
		/// </summary>
		public string ObjectName;

		/// <summary>
		/// Unit test state result after dynamic invoking.
		/// </summary>
		public UnitResult Result = UnitResult.Unknown;

		/// <summary>
		/// Time period for duration test.
		/// </summary>
		public TimePeriod TimeUnit;

		/// <summary>
		/// Time period for hit count test.
		/// </summary>
		public TimePeriod TimeUnitHitCount;

		/// <summary>
		/// Number of concurrent threads that call method.
		/// </summary>
		public int Threads;
		
		/// <summary>
		/// Minimum time (defaults to microseconds) the method has to be executed, otherwise performance test fails.
		/// </summary>
		public int Duration;

		/// <summary>
		/// Minimum hits per second (or other time period) that must occur, otherwise performance test fails.
		/// </summary>
		public int ExpectedHits;

		/// <summary>
		/// Duration time converted from <see cref="Duration"/> or <see cref="ExpectedHits"/> to nanoseconds.
		/// </summary>
		public double DurationInNanoseconds;

		/// <summary>
		/// The name of the performance counter category for performance counter.
		/// </summary>
		public string CategoryName;

		/// <summary>
		/// The name of the performance counter that is associated with PerformanceCounter instance.
		/// </summary>
		public string CounterName;

		/// <summary>
		/// The instance name for performance counter.
		/// </summary>
		public string InstanceName;

		/// <summary>
		/// The computer name for performance counter.
		/// </summary>
		public string MachineName;

		/// <summary>
		/// Minimum value that performance counter may have to accept performance test.
		/// </summary>
		public int MinimumValue;

		/// <summary>
		/// Maximum value that performance counter may have to accept performance test.
		/// </summary>
		public int MaximumValue;

		/// <summary>
		/// Parent to test fixture container.
		/// </summary>
		public TestFixture Fixture;

		/// <summary>
		/// Indicates whether test unit is ignored.
		/// </summary>
		public bool Ignored;

		/// <summary>
		/// Ignore text injected from test method attribute or warning text describing failed test.
		/// </summary>
		public string Warning;

		/// <summary>
		/// Description of test unit. It contains summary text for executed test.
		/// </summary>
		public ArrayList UnitResultInfo = new ArrayList();

		/// <summary>
		/// Test unit linked to tree node of TreeView.
		/// </summary>
		public TreeNode Node;

		/// <summary>
		/// Title of unit test including type of test and other parameters injected from method attribute.
		/// </summary>
		public string TypeNameLong;

		/// <summary>
		/// Title of unit test including only type of test.
		/// </summary>
		public string TypeName;

		/// <summary>
		/// Type of test. See <see cref="TestKind"/> enumeration type.
		/// </summary>
		public TestKind TestType;

		/// <summary>
		/// Initializes TestUnit class that holds test unit node.
		/// </summary>
		/// <param name="type">Reflection type of TestFixture class.</param>
		/// <param name="methodInfo">Reflection method of performance test method.</param>
		public TestUnit(Type type, MethodInfo methodInfo)
		{
			this.ObjectType = type;
			this.ObjectMethod = methodInfo;
			this.ObjectName = type.ToString() + "." + methodInfo.Name;
			this.UnitResultInfo.Clear();
			Duration = 0;
			ExpectedHits = 0;
		}

		public override object InitializeLifetimeService()
		{
			return null;
		}
	}

	/// <summary>
	/// Remote loader existing in other application domain to support assembly loading in isolated
	/// process space to perform assembly replacing and unloading. It is main class that reloads
	/// assemblies and reflects them to test units.
	/// </summary>
	public class RemoteLoader : MarshalByRefObject
	{
		/// <summary>
		/// An array with type information for each performance test fixture.
		/// </summary>
		public ArrayList Fixtures = new ArrayList();

		/// <summary>
		/// Actually running test unit. Used to forward test unit across multiple threads.
		/// </summary>
		public ArrayList RunningTestUnits;

		/// <summary>
		/// Number of calls to average execute time.
		/// </summary>
		public int NumberOfMethodCalls = 5;

		/// <summary>
		/// An array containing finished tests. This list is used in query for ready tests to reflect in treeview and gui.
		/// </summary>
		public ArrayList CompletedTests = new ArrayList();

		/// <summary>
		/// Index in CompletedTests array to begin search at specified index. This variable is used in query to improve performance.
		/// </summary>
		public int CompletedIndex;
 
		/// <summary>
		/// Indicates whether tests are finished and need to update gui in application.
		/// </summary>
		public bool TestsFinished;

		/// <summary>
		/// Used by running threads to test whether tests should be running or stopped.
		/// </summary>
		public bool Running;

		/// <summary>
		/// An array with reflection types for activating test fixture class objects.
		/// </summary>
		private ArrayList ClassTypes = new ArrayList();

		/// <summary>
		/// An array cooperated with ClassTypes array to keep class objects activated
		/// during multiple invoking.
		/// </summary>
		private ArrayList ClassInstances = new ArrayList();

		/// <summary>
		/// Represents start date of tests.
		/// </summary>
		private DateTime StartDate;
		/// <summary>
		/// Represents tests total execution time.
		/// </summary>
		public TimeSpan TotalTime;

		/// <summary>
		/// Initializes RemoteLoader class.
		/// </summary>
		public RemoteLoader()
		{
			Fixtures.Clear();
		}

		public override object InitializeLifetimeService()
		{
			return null;
		}

		/// <summary>
		/// Loads performance test assembly into isolated application domain.
		/// </summary>
		/// <param name="assembly">Assembly filename to load.</param>
		public void LoadAssembly(string assembly)
		{
			Assembly a = AppDomain.CurrentDomain.Load(System.IO.Path.GetFileNameWithoutExtension(assembly));
//			string file = System.IO.Path.GetDirectoryName(assembly) + "\\" + System.IO.Path.GetFileNameWithoutExtension(assembly);
//			Assembly a = AppDomain.CurrentDomain.Load(file);

			foreach(Type t in a.GetTypes())
			{
				if(t.GetCustomAttributes(typeof(TimerFixtureAttribute), false).Length > 0)
				{
					TestFixture tf = new TestFixture();
					Fixtures.Add(tf);
					foreach(MethodInfo mi in t.GetMethods())
					{
						if(mi.IsConstructor || mi.IsAbstract || mi.GetParameters().Length > 0)
							continue;
						if(mi.GetCustomAttributes(typeof(TimerDurationTestAttribute), false).Length > 0
							|| mi.GetCustomAttributes(typeof(TimerHitCountTestAttribute), false).Length > 0
							|| mi.GetCustomAttributes(typeof(TimerCounterTestAttribute), false).Length > 0
							|| mi.GetCustomAttributes(typeof(TimerFixtureSetUpAttribute), false).Length > 0
							|| mi.GetCustomAttributes(typeof(TimerFixtureTearDownAttribute), false).Length > 0
							|| mi.GetCustomAttributes(typeof(TimerSetUpAttribute), false).Length > 0
							|| mi.GetCustomAttributes(typeof(TimerTearDownAttribute), false).Length > 0
							|| mi.GetCustomAttributes(typeof(TimerIgnoreAttribute), false).Length > 0)
						{
							foreach(object o in mi.GetCustomAttributes(false))
							{
								if(o is TimerFixtureSetUpAttribute)
								{
									tf.StartUpObjectMethod = mi;
									tf.StartUpObjectType = t;
								}
								if(o is TimerFixtureTearDownAttribute)
								{
									tf.TearDownObjectMethod = mi;
									tf.TearDownObjectType = t;
								}
								if(o is TimerSetUpAttribute)
								{
									tf.StartUpLocalObjectMethod = mi;
									tf.StartUpLocalObjectType = t;
								}
								if(o is TimerTearDownAttribute)
								{
									tf.TearDownLocalObjectMethod = mi;
									tf.TearDownLocalObjectType = t;
								}

								if(o is TimerDurationTestAttribute)
								{
									TestUnit tu = new TestUnit(t, mi);
									tu.Ignored = false;
									tu.Fixture = tf;
									tf.Units.Add(tu);
									if(mi.GetCustomAttributes(typeof(TimerIgnoreAttribute), false).Length > 0)
									{
										tu.Warning = (mi.GetCustomAttributes(typeof(TimerIgnoreAttribute), false)[0] as TimerIgnoreAttribute).Info;
										tu.Ignored = true;
									}
									TimerDurationTestAttribute att = o as TimerDurationTestAttribute;
									tu.TimeUnit = att.Unit;
									tu.Duration = att.Duration;
									tu.DurationInNanoseconds = TimeToNanoseconds((long)tu.Duration, tu.TimeUnit);
									tu.Threads = att.Threads;
									tu.TypeName = "Duration test";
									tu.TypeNameLong = "Duration test (" + tu.Duration + " " + tu.TimeUnit + ", Threads=" + tu.Threads + ")";
									tu.TestType = TestKind.Duration;
								}
								if(o is TimerHitCountTestAttribute)
								{
									TestUnit tu = new TestUnit(t, mi);
									tu.Ignored = false;
									tu.Fixture = tf;
									tf.Units.Add(tu);
									if(mi.GetCustomAttributes(typeof(TimerIgnoreAttribute), false).Length > 0)
									{
										tu.Warning = (mi.GetCustomAttributes(typeof(TimerIgnoreAttribute), false)[0] as TimerIgnoreAttribute).Info;
										tu.Ignored = true;
									}
									TimerHitCountTestAttribute att = o as TimerHitCountTestAttribute;
									tu.ExpectedHits = att.ExpectedHits;
									tu.TimeUnit = att.Unit;
									tu.DurationInNanoseconds = TimeToNanoseconds(1, tu.TimeUnit) / tu.ExpectedHits;
									tu.Threads = att.Threads;
									tu.TypeName = "Hitcount test";
									tu.TypeNameLong = "Hitcount test (" + tu.ExpectedHits + " / " + tu.TimeUnit + ", Threads=" + tu.Threads + ")";
									tu.TestType = TestKind.Hitcount;
								}
								if(o is TimerCounterTestAttribute)
								{
									TestUnit tu = new TestUnit(t, mi);
									tu.Ignored = false;
									tu.Fixture = tf;
									tf.Units.Add(tu);
									if(mi.GetCustomAttributes(typeof(TimerIgnoreAttribute), false).Length > 0)
									{
										tu.Warning = (mi.GetCustomAttributes(typeof(TimerIgnoreAttribute), false)[0] as TimerIgnoreAttribute).Info;
										tu.Ignored = true;
									}
									TimerCounterTestAttribute att = o as TimerCounterTestAttribute;
									tu.CategoryName = att.CategoryName;
									tu.CounterName = att.CounterName;
									tu.InstanceName = att.InstanceName;
									if(tu.InstanceName == "*")
										tu.InstanceName = System.IO.Path.GetFileNameWithoutExtension(Application.ExecutablePath);
									tu.MachineName = att.MachineName;
									tu.MinimumValue = att.MinimumValue;
									tu.MaximumValue = att.MaximumValue;
									tu.Threads = att.Threads;
									tu.TypeName = "Counter test";
									int min = tu.MinimumValue;
									int max = tu.MaximumValue;
									tu.TypeNameLong = "Counter test (Category=" + tu.CategoryName + ", Counter=" + tu.CounterName + ", Min=" + (min > 0 ? min.ToString() : "ignored") + ", Max=" + (max > 0 ? max.ToString() : "ignored") + ", Threads=" + tu.Threads + ")";
									tu.TestType = TestKind.Counter;
								}
							}
						}
					}
				}
			}
		}

		/// <summary>
		/// Starts unit tests.
		/// </summary>
		/// <param name="units">An array of selected test units to run</param>
		public void RunUnit(ArrayList units)
		{
			TotalTime = TimeSpan.Zero;
			StartDate = DateTime.Now;

			CompletedTests.Clear();
			CompletedIndex = 0;
			TestsFinished = false;
			Running = true;

			RunningTestUnits = units;
			foreach(TestFixture tf in Fixtures)
			{
				foreach(TestUnit tu in tf.Units)
				{
					tu.Result = UnitResult.Unknown;
					tu.UnitResultInfo.Clear();
				}
			}
			//			foreach(TestUnit tu in units)
			//			{
			//			}
			ClassInstances.Clear();
			ClassTypes.Clear();

			System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(ThreadTestsRun));
			t.Name = "NTimeTestThread";
			t.Start();
		}

		/// <summary>
		/// Used from gui form to query which tests are available to reflect in treeview.
		/// </summary>
		/// <param name="requestStop">Specify true to stop running test threads.</param>
		/// <returns>TestUnit instance with completed test or null when none of tests were finished till now.</returns>
		public TestUnit QueryUnit(bool requestStop)
		{
			if(requestStop)
				Running = false;
			else
			{
				lock(this)
				{
					if(CompletedIndex < CompletedTests.Count)
					{
						if(((TestUnit)CompletedTests[CompletedIndex]).Result == UnitResult.Accepted
							|| ((TestUnit)CompletedTests[CompletedIndex]).Result == UnitResult.Rejected
							|| ((TestUnit)CompletedTests[CompletedIndex]).Result == UnitResult.Ignored)
						{
							TestUnit tu = (TestUnit)CompletedTests[CompletedIndex];
							CompletedIndex++;
							return tu;
						}
					}
				}
			}
			return null;
		}

		/// <summary>
		/// Creates object instance, runs its method and places it in array cache for further invoking.
		/// </summary>
		/// <param name="type">Reflection type of test fixture class to create.</param>
		/// <param name="methodInfo">Reflection method of test to invoke.</param>
		public void RunMethod(Type type, MethodInfo methodInfo)
		{
			if(type != null)
			{
				int index = ClassTypes.IndexOf(type);
				if(index == -1)
				{
					ClassTypes.Add(type);
					object o = System.Activator.CreateInstance(type);
					ClassInstances.Add(o);
					methodInfo.Invoke(o, null);
				}
				else
					methodInfo.Invoke(ClassInstances[index], null);
			}
		}

		/// <summary>
		/// Worker thread invoking performance tests.
		/// </summary>
		public void ThreadTestsRun()
		{
			TestFixture tf = ((TestUnit)RunningTestUnits[0]).Fixture;
			try
			{
				RunMethod(tf.StartUpObjectType, tf.StartUpObjectMethod);
			}
			catch(Exception)
			{
			}
			foreach(TestUnit tu in RunningTestUnits)
			{
				if(tf != tu.Fixture)
				{
					try
					{
						RunMethod(tf.TearDownObjectType, tf.TearDownObjectMethod);
						RunMethod(tu.Fixture.StartUpObjectType, tu.Fixture.StartUpObjectMethod);
					}
					catch(Exception)
					{
					}
				}
				tf = tu.Fixture;
				string exception = "";
				if(tu.Ignored == false)
				{
					try
					{
//						for(int i = 0; i < 2; i++) // cache first
						{
							RunMethod(tf.StartUpLocalObjectType, tf.StartUpLocalObjectMethod);
							RunMethod(tu.ObjectType, tu.ObjectMethod);
							RunMethod(tf.TearDownLocalObjectType, tf.TearDownLocalObjectMethod);
						}

						ThreadRun[] tr = new ThreadRun[tu.Threads];
						for(int i = 0; i < tu.Threads; i++)
						{
							tr[i] = new ThreadRun(this, tu);
							tr[i].Thread = new System.Threading.Thread(new System.Threading.ThreadStart(tr[i].ThreadTest));
							tr[i].Thread.Name = "NTimeThread_" + i;
						}
						for(int i = 0; i < tu.Threads; i++)
						{
							tr[i].Thread.Start();
						}
						for(int i = 0; i < tu.Threads; i++)
						{
							tr[i].Thread.Join();
						}
						for(int i = 0; i < tu.Threads; i++)
						{
							if(tr[i].ExceptionFound != null)
							{
								throw tr[i].ExceptionFound;
							}
						}
						if(tu.TestType == TestKind.Duration || tu.TestType == TestKind.Hitcount)
						{
							double totalDuration = 0;
							for(int i = 0; i < tu.Threads; i++)
							{
								for(int j = 0; j < tr[i].Duration.Length; j++)
								{
									totalDuration += tr[i].Duration[j];
								}
							}
							long totalDurationZ = (long)(totalDuration / (tu.Threads * NumberOfMethodCalls) * 1000000000.0);
							if(totalDurationZ > tu.DurationInNanoseconds)
								tu.Result = UnitResult.Rejected;
							else
								tu.Result = UnitResult.Accepted;
							if(tu.TestType == TestKind.Duration)
							{
								double td = totalDurationZ;
								TimePeriod timePeriod;
								NormalizeTime(ref td, out timePeriod);
								tu.Warning = "Execution time was " + td.ToString() + " " + timePeriod.ToString();
							}
							else
							{
								double td = totalDurationZ;
								NormalizeHitcount(ref td, tu.TimeUnit);
								tu.Warning = "Hitcount was " + ((int)td).ToString() + " / " + tu.TimeUnit;
							}
						}
						else // performance counter test
						{
							double avg = 0;
							bool Shorted = false;
							for(int i = 0; i < tu.Threads; i++)
							{
								avg += tr[i].CounterSample;
								if(tr[i].ShortTime == true)
									Shorted = true;
							}
							if(Shorted == false)
							{
								long avgZ = (long)(avg / tu.Threads);
								if(avgZ > tu.MaximumValue && tu.MaximumValue != 0)
								{
									tu.Result = UnitResult.Rejected;
								}
								else if(avgZ < tu.MinimumValue && tu.MinimumValue != 0)
								{
									tu.Result = UnitResult.Rejected;
								}
								else
								{
									tu.Result = UnitResult.Accepted;
								}
								tu.Warning = "Average performance counter was " + avgZ.ToString();
							}
							else
							{
								tu.Result = UnitResult.Ignored;
								tu.Warning = "Time execution of method was to short to collect data or performance counter was invalid";
							}
						}
					}
					catch(Exception e)
					{
						exception = e.ToString().Substring(e.ToString().IndexOf("--->") + 4, e.ToString().LastIndexOf("--- End of inner exception stack trace ---") - e.ToString().IndexOf("--->") - 4).Trim();
						tu.Warning = "Unhandled exception";
					}
				}
				lock(this)
				{
					if(tu.Ignored)
						tu.Result = UnitResult.Ignored;
					tu.UnitResultInfo.Clear();
					tu.UnitResultInfo.Add("Performance test result");
					tu.UnitResultInfo.Add("=======================");
					tu.UnitResultInfo.Add("Method: " + tu.ObjectMethod.ToString());
					tu.UnitResultInfo.Add("Test type: " + tu.TypeNameLong.ToString());
					if(exception != "")
						tu.Result = UnitResult.Rejected;
					if(tu.Result == UnitResult.Accepted)
						tu.UnitResultInfo.Add("Status: Accepted");
					if(tu.Result == UnitResult.Rejected)
						tu.UnitResultInfo.Add("Status: Rejected");
					if(tu.Result == UnitResult.Ignored)
						tu.UnitResultInfo.Add("Status: Ignored");
					tu.UnitResultInfo.Add("Result: " + tu.Warning);
					if(exception != "")
						tu.UnitResultInfo.Add("Exception: " + exception);
					tu.UnitResultInfo.Add("");
					CompletedTests.Add(tu);
				}
				if(Running == false)
					break;
			}
			try
			{
				RunMethod(tf.TearDownObjectType, tf.TearDownObjectMethod);
			}
			catch(System.Exception)
			{
			}
			Running = false;
			TotalTime = DateTime.Now - StartDate;
			TestsFinished = true;
		}

		/// <summary>
		/// Used internally to normalize elapsed time to specified precision with appropriate time unit.
		/// </summary>
		/// <param name="duration">Elapsed time in nanoseconds. This is a ref parameter.</param>
		/// <param name="timePeriod">Normalized Time unit. This is an out parameter.</param>
		private void NormalizeTime(ref double duration, out TimePeriod timePeriod)
		{
			if(duration < 1000.0)
			{
				timePeriod = TimePeriod.Nanosecond;
			}
			else if(duration < 1000000.0)
			{
				duration = duration / 1000.0;
				timePeriod = TimePeriod.Microsecond;
			}
			else if(duration < 1000000000.0)
			{
				duration = duration / 1000000.0;
				timePeriod = TimePeriod.Millisecond;
			}
			else if(duration < 60000000000.0)
			{
				duration = duration / 1000000000.0;
				timePeriod = TimePeriod.Second;
			}
			else if(duration < 3600000000000.0)
			{
				duration = duration / 60000000000.0;
				timePeriod = TimePeriod.Minute;
			}
			else
			{
				duration = duration / 3600000000000.0;
				timePeriod = TimePeriod.Hour;
			}
		}

		/// <summary>
		/// Used internally to normalize hitcount to specified precision with appropriate time unit.
		/// </summary>
		/// <param name="hitcount">Hitcount per specified time unit.</param>
		/// <param name="timePeriod">Base time period.</param>
		private void NormalizeHitcount(ref double hitcount, TimePeriod timePeriod)
		{
			if(timePeriod == TimePeriod.Nanosecond)
				hitcount = 1.0 / hitcount;
			else if(timePeriod == TimePeriod.Microsecond)
				hitcount = 1000.0 / hitcount;
			else if(timePeriod == TimePeriod.Millisecond)
				hitcount = 1000000.0 / hitcount;
			else if(timePeriod == TimePeriod.Second)
				hitcount = 1000000000.0 / hitcount;
			else if(timePeriod == TimePeriod.Minute)
				hitcount = 60000000000.0 / hitcount;
			else if(timePeriod == TimePeriod.Hour)
				hitcount = 3600000000000.0 / hitcount;
		}

		/// <summary>
		/// Used internally to convert duration time to nanoseconds.
		/// </summary>
		/// <param name="duration">Duration in specified time unit.</param>
		/// <param name="timePeriod">Time unit of duration.</param>
		/// <returns>Duration in nanoseconds.</returns>
		private long TimeToNanoseconds(long duration, TimePeriod timePeriod)
		{
			switch(timePeriod)
			{
				case TimePeriod.Microsecond:
				{
					duration *= 1000;
					break;
				}
				case TimePeriod.Millisecond:
				{
					duration *= 1000000;
					break;
				}
				case TimePeriod.Second:
				{
					duration *= 1000000000;
					break;
				}
				case TimePeriod.Minute:
				{
					duration *= 60000000000;
					break;
				}
				case TimePeriod.Hour:
				{
					duration *= 3600000000000;
					break;
				}
			}
			return duration;
		}

	}

	/// <summary>
	/// Class used by Thread to run method. Each thread in test has its own class.
	/// </summary>
	class ThreadRun
	{
		/// <summary>
		/// Thread instance owner.
		/// </summary>
		public System.Threading.Thread Thread;
		/// <summary>
		/// Reference to <see cref="TestUnit"/> instance to inspect test properties.
		/// </summary>
		public TestUnit tu;
		/// <summary>
		/// Reference to <see cref="RemoteLoader"/> instance to access <see cref="RemoteLoader.RunMethod"/>.
		/// </summary>
		public RemoteLoader rl;

		/// <summary>
		/// Average duration time in test thread.
		/// </summary>
		public double[] Duration;

		/// <summary>
		/// Average performance counter in test thread.
		/// </summary>
		public double CounterSample;

		/// <summary>
		/// Indicates whether method executes minimum one second to collect performance counters correct.
		/// </summary>
		public bool ShortTime;

		/// <summary>
		/// Forwards exception message from executive thread to main unit testing loop thread.
		/// </summary>
		public Exception ExceptionFound;

		/// <summary>
		/// Initializes class for one thread test.
		/// </summary>
		/// <param name="remoteLoader">This instance is needed to perform <see cref="RemoteLoader.RunMethod"/>.</param>
		/// <param name="testUnit">This instance is used to inspect test properties.</param>
		public ThreadRun(RemoteLoader remoteLoader, TestUnit testUnit)
		{
			ExceptionFound = null;
			tu = testUnit;
			rl = remoteLoader;
			Duration = new double[rl.NumberOfMethodCalls];
		}

		/// <summary>
		/// Core thread function to execute test method.
		/// </summary>
		public void ThreadTest()
		{
			if(tu.TestType == TestKind.Duration || tu.TestType == TestKind.Hitcount)
			{
				for(int i = 0; i < rl.NumberOfMethodCalls; i++)
				{
					try
					{
						rl.RunMethod(tu.Fixture.StartUpLocalObjectType, tu.Fixture.StartUpLocalObjectMethod);

						Duration[i] = Counter.Value;
						rl.RunMethod(tu.ObjectType, tu.ObjectMethod);
						Duration[i] = (Counter.Value - Duration[i]) / Counter.Frequency;

						rl.RunMethod(tu.Fixture.TearDownLocalObjectType, tu.Fixture.TearDownLocalObjectMethod);
					}
					catch(Exception e)
					{
						ExceptionFound = e;
						break;
					}
				}
			}
			else if(tu.TestType == TestKind.Counter)
			{
				ShortTime = true;
//				for(int i = 0; i < rl.NumberOfMethodCalls; i++)
				{
					System.Threading.Thread t = null;
					try
					{
						rl.RunMethod(tu.Fixture.StartUpLocalObjectType, tu.Fixture.StartUpLocalObjectMethod);

						t = new System.Threading.Thread(new System.Threading.ThreadStart(PerformanceThread));
						t.Start();
						rl.RunMethod(tu.ObjectType, tu.ObjectMethod);
						t.Abort();

						rl.RunMethod(tu.Fixture.TearDownLocalObjectType, tu.Fixture.TearDownLocalObjectMethod);
					}
					catch(Exception e)
					{
						ExceptionFound = e;
					}
					if(t.ThreadState == System.Threading.ThreadState.Running)
						t.Abort();
				}
			}
		}

		/// <summary>
		/// This method working in separate thread collects performance counter.
		/// </summary>
		public void PerformanceThread()
		{
			System.Diagnostics.PerformanceCounter pc = new System.Diagnostics.PerformanceCounter();
			pc.CategoryName = tu.CategoryName;
			pc.CounterName = tu.CounterName;
			pc.InstanceName = tu.InstanceName;
			try
			{
				CounterSample = pc.NextValue();
				while(true)
				{
					System.Threading.Thread.Sleep(1000);
					CounterSample = (CounterSample + pc.NextValue()) / 2;
					ShortTime = false;
				}
			}
			catch(System.Exception)
			{
			}
		}
	}

	/// <summary>
	/// This class imports high frequency counter functions from within Kernel32 library.
	/// </summary>
	class Counter
	{
		/// <summary>
		/// Gets frequency of high frequency timer.
		/// </summary>
		public static long Frequency
		{
			get
			{
				long freq = 0;
				QueryPerformanceFrequency(ref freq);
				return freq;
			}
		}

		/// <summary>
		/// Gets counter of high frequency timer.
		/// </summary>
		public static long Value
		{
			get
			{
				long count = 0;
				QueryPerformanceCounter(ref count);
				return count;
			}
		}

		[System.Runtime.InteropServices.DllImport("KERNEL32")]
		private static extern bool QueryPerformanceCounter(ref long lpPerformanceCount);

		[System.Runtime.InteropServices.DllImport("KERNEL32")]
		private static extern bool QueryPerformanceFrequency(ref long lpFrequency);
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.


Written By
Web Developer
Poland Poland
Born in Poland, living there as employeed developer, in free time writing much .net stuff and designing applications.

Comments and Discussions