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