summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.Test/Performance/PerformanceMeasurement.cs
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2011-05-02 21:37:34 -0700
committerAndrew Arnott <andrewarnott@gmail.com>2011-05-02 21:37:34 -0700
commitbcdd8eb12670332b0de69752d583cba710490e38 (patch)
tree74b83b186088e742db82958ace7e4cc38c81689c /src/DotNetOpenAuth.Test/Performance/PerformanceMeasurement.cs
parent6c751fc1364d94733e099c3a21623033c85ad86d (diff)
downloadDotNetOpenAuth-bcdd8eb12670332b0de69752d583cba710490e38.zip
DotNetOpenAuth-bcdd8eb12670332b0de69752d583cba710490e38.tar.gz
DotNetOpenAuth-bcdd8eb12670332b0de69752d583cba710490e38.tar.bz2
Perf tests now compare results against a baseline produced on the test machine.
This uses portions of MeasureIt, which normalizes perf measurements in terms of the machine's speed. We also do other things to reduce noise: * set process and thread priority * wait for the CPU to quiet down before beginning. * set power management to High Performance * wake the CPU up if it's in a low power mode.
Diffstat (limited to 'src/DotNetOpenAuth.Test/Performance/PerformanceMeasurement.cs')
-rw-r--r--src/DotNetOpenAuth.Test/Performance/PerformanceMeasurement.cs268
1 files changed, 268 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.Test/Performance/PerformanceMeasurement.cs b/src/DotNetOpenAuth.Test/Performance/PerformanceMeasurement.cs
new file mode 100644
index 0000000..6199005
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Performance/PerformanceMeasurement.cs
@@ -0,0 +1,268 @@
+//-----------------------------------------------------------------------
+// <copyright file="CodeTimers.cs" company="Microsoft Corporation">
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// </copyright>
+// <author>Vance Morrison</author>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Performance {
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+ using System.IO;
+ using System.Web; // for HttpUtility.HtmlEncode
+ using System.Reflection; // for Assembly
+ using System.Diagnostics; // for Process, DebuggableAttribute
+
+ /// <summary>
+ /// A code:StatsLogger is something that remembers a set of common
+ /// atrributes (verison of the code, Machine used, NGENed or JITed ...) as
+ /// well as a set of performance results from mulitple benchmarks.
+ /// (represented by a code:StatsCollection)
+ ///
+ /// The primary value of a StatsLogger is the
+ /// code:StatsLogger.DisplayHtmlReport which displayes the data in a
+ /// user-friendly way.
+ /// </summary>
+ internal class StatsLogger {
+ public StatsLogger(StatsCollection dataSet) {
+ this.dataSet = dataSet;
+ attributes = new Dictionary<string, object>();
+ }
+ StatsCollection DataSet { get { return dataSet; } }
+ public object this[string key] {
+ get { return attributes[key]; }
+ set { attributes[key] = value; }
+ }
+ public void Add(string name, Stats sample) { dataSet.Add(name, sample); }
+ public string Category {
+ get { return category; }
+ set { category = value; }
+ }
+ public void AddWithCount(string name, int iterationCount, float scale, Stats sample) {
+ if (!string.IsNullOrEmpty(category))
+ name = category + ": " + name;
+ if (iterationCount != 1 || scale != 1) {
+ name += " [";
+ if (iterationCount != 1) {
+ name = name + "count=" + iterationCount.ToString();
+ if (scale != 1)
+ name += " ";
+ }
+ if (scale != 1)
+ name = name + " scale=" + scale.ToString("f1");
+ name += "]";
+ }
+
+ Add(name, sample);
+ }
+ public void DisplayHtmlReport(string reportFileName) {
+ TextWriter writer = File.CreateText(reportFileName);
+
+ writer.WriteLine("<html>");
+ writer.WriteLine("<h1> MeasureIt Performance Results </h1>");
+ object optimizationValue;
+ if (attributes.TryGetValue("CodeOptimization", out optimizationValue) && ((string)optimizationValue) == "Unoptimized") {
+ writer.WriteLine("<font color=red><p>");
+ writer.WriteLine("Warning: the MeasureIt code was not optimized. The results are likely invalid.");
+ writer.WriteLine("</p></font>");
+ }
+ if (Environment.OSVersion.Version.Major < 6) {
+ writer.WriteLine("<b><p>");
+ writer.WriteLine("Data was collected on a Pre-Vista machine. MeasureIt does NOT automatically");
+ writer.WriteLine("set the CPU to a high performance power policy. This means the CPU might");
+ writer.WriteLine("be throttled to save power and can lead to");
+ writer.WriteLine("incorrect and inconsistant benchmark measurements.");
+ writer.WriteLine("</p></b>");
+ } else if (PowerManagment.CurrentPolicy != PowerManagment.PowerProfiles.HighPerformance) {
+ writer.WriteLine("<font color=red><p>");
+ writer.WriteLine("Warning: The power policy settings were not set at 'High Performance' during this run.");
+ writer.WriteLine("This means that the CPU could be throttled to lower frequency resulting in");
+ writer.WriteLine("incorrect and inconsistant benchmark measurements.");
+ writer.WriteLine("To correct go to Start Menu -> Contol Panel -> System and Maintance -> Power Options");
+ writer.WriteLine("and set the power policy to 'High Performance' for the duration of the tests.");
+ writer.WriteLine("</p></font>");
+ }
+ writer.WriteLine("<p>");
+ writer.WriteLine("Below are the results of running a series of benchmarks. Use the");
+ writer.WriteLine("<b>MeasureIt /usersGuide</b> for more details on exactly what the benchmarks do.");
+ writer.WriteLine("</p><p>");
+ writer.WriteLine("It is very easy for benchmark results to be wrong or misleading. You should read the guidance");
+ writer.WriteLine("in the <b>MeasureIt /usersGuide</b> before making important decisions based on this data.");
+ writer.WriteLine("</p><p>");
+ writer.WriteLine("To improve the stability of the measurements, a may be cloned several times");
+ writer.WriteLine("and this cloned code is then run in a loop.");
+ writer.WriteLine("If the benchmark was cloned the 'scale' attribute represents the number of times");
+ writer.WriteLine("it was cloned, and the count represents the number of times the cloned code was run in a loop");
+ writer.WriteLine("before the measurement was made. The reported number divides by both");
+ writer.WriteLine("of these values, so it represents a single instance of the operation being measured.");
+ writer.WriteLine("</p>");
+ writer.WriteLine("<p>");
+ writer.WriteLine("The benchmarks data can vary from run to run, so the benchmark is run several times and");
+ writer.WriteLine("the statistics are displayed. If we assume a normal distribution, you can expect 68% of all measureuments");
+ writer.WriteLine("to fall within 1 StdDev of the Mean. You can expect over 95% of all measurements");
+ writer.WriteLine("to fall witin 2 StdDev of the Mean. Thus 2 StdDev is a good error bound.");
+ writer.WriteLine("Keep in mind, however that it is not uncommon for the statistics to be quite stable");
+ writer.WriteLine("during a run and yet very widely across different runs. See the users guide for more.");
+ writer.WriteLine("</p>");
+ writer.WriteLine("<p>");
+ writer.WriteLine("Generally the mean is a better measurment if you use the number to compute an");
+ writer.WriteLine("aggregate throughput for a large number of items. The median is a better");
+ writer.WriteLine("guess if you want to best guess of a typical sample. The median is also");
+ writer.WriteLine("more stable if the sample is noisy (eg has outliers).");
+ writer.WriteLine("</p>");
+ writer.WriteLine("<h3>Data collected</h3>");
+ {
+ writer.WriteLine("<p>");
+ writer.WriteLine(UnitsDescription);
+ writer.WriteLine("</p>");
+
+ dataSet.WriteReportTable(writer, Scale);
+ }
+
+ writer.WriteLine("<p>");
+ {
+ writer.WriteLine("<h2>Attributes of the machine used to collect the data</h2>");
+ writer.WriteLine("<table border>");
+ writer.WriteLine("<tr><th>Attribute</th><th>Value</th></tr>");
+ foreach (string key in attributes.Keys) {
+ object valueObj = this[key];
+ writer.Write("<tr>");
+ writer.Write("<td>" + HttpUtility.HtmlEncode(key) + "</td>");
+
+ string valueStr = HttpUtility.HtmlEncode(valueObj.ToString());
+ writer.Write("<td>" + valueStr + "</td>");
+ writer.WriteLine("<tr>");
+ }
+ writer.WriteLine("</table>");
+ }
+ writer.WriteLine("</p>");
+
+ writer.WriteLine("</html>");
+ writer.Close();
+ }
+ static public void LaunchIE(string fileName) {
+ Process process = new Process();
+ process.StartInfo = new ProcessStartInfo(fileName);
+ process.Start();
+ }
+
+ public float Scale = 1.0F;
+ public string UnitsDescription = "Scale in usec";
+
+ // TODO: probabably does not belong in this class.
+ public void CaptureCurrentMachineInfo(Assembly assemblyWithCode, bool skipMachineStats) {
+ this["Computer Name"] = Environment.MachineName;
+ if (!skipMachineStats) {
+ ////ComputerSpecs specs = new ComputerSpecs();
+ ////this["Number of Processors"] = specs.NumberOfProcessors;
+ ////this["Processor Name "] = specs.ProcessorName;
+ ////this["Processor Mhz"] = specs.ProcessorClockSpeedMhz;
+ ////this["Memory MBytes"] = specs.MemoryMBytes;
+ ////this["L1 Cache KBytes"] = specs.L1KBytes;
+ ////this["L2 Cache KBytes"] = specs.L2KBytes;
+ ////this["Operating System"] = specs.OperatingSystem;
+ ////this["Operating System Version"] = specs.OperatingSystemVersion;
+ ////this["Stopwatch resolution (nsec)"] = (CodeTimer.ResolutionUsec * 1000.0).ToString("f3");
+ }
+
+ this["Machine Word Size (Bits)"] = (System.Runtime.InteropServices.Marshal.SizeOf(typeof(IntPtr)) * 8).ToString();
+
+ this["CLR Version"] = Environment.Version.ToString();
+ this["CLR Directory"] = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory();
+
+ // Are we NGENed or JITTed?
+ if (IsNGenedCodeLoaded(assemblyWithCode))
+ this["CompileType"] = "NGEN";
+ else
+ this["CompileType"] = "JIT";
+
+ // Are we Appdomain Shared, or not?
+ MethodInfo currentMethod = Assembly.GetEntryAssembly().EntryPoint;
+ LoaderOptimizationAttribute loaderAttribute = (LoaderOptimizationAttribute)System.Attribute.GetCustomAttribute(currentMethod, typeof(LoaderOptimizationAttribute));
+ if (loaderAttribute != null && loaderAttribute.Value == LoaderOptimization.MultiDomain)
+ this["CodeSharing"] = "AppDomainShared";
+ else
+ this["CodeSharing"] = "AppDomainSpecific";
+
+ this["CodeOptimization"] = IsOptimized(assemblyWithCode) ? "Optimized" : "Unoptimized";
+ }
+
+ internal static bool IsOptimized(Assembly assembly) {
+ DebuggableAttribute debugAttribute = (DebuggableAttribute)System.Attribute.GetCustomAttribute(assembly, typeof(System.Diagnostics.DebuggableAttribute));
+ if (debugAttribute != null && debugAttribute.IsJITOptimizerDisabled)
+ return false;
+ else
+ return true;
+ }
+
+ static bool IsNGenedCodeLoaded(Assembly assembly) {
+ // This is a bit of a hack, basically I find the assemblies file name, then
+ // look for a module loaded called '<filename>.ni.<ext>'. It is not foolproof,
+ // but it more than good enough for most purposes.
+ string assemblyFileName = Path.GetFileName(assembly.ManifestModule.FullyQualifiedName);
+ string nativeImageExt = "ni" + Path.GetExtension(assemblyFileName);
+ string nativeImageSuffix = @"\" + Path.ChangeExtension(assemblyFileName, nativeImageExt);
+
+ System.Diagnostics.Process myProcess = System.Diagnostics.Process.GetCurrentProcess();
+ foreach (System.Diagnostics.ProcessModule module in myProcess.Modules) {
+ if (module.FileName.EndsWith(nativeImageSuffix, StringComparison.OrdinalIgnoreCase))
+ return true;
+ }
+ return false;
+ }
+
+ #region privates
+ Dictionary<string, object> attributes;
+ StatsCollection dataSet;
+ string category;
+
+ #endregion
+ }
+
+ /// <summary>
+ /// StatsCollection represents a collecton of named of samples (class
+ /// Stats) that have have been given string names. The data can be
+ /// looked up by name, but is also collection also remembers the order in
+ /// which the samples were added, and the names can be enumerated in that
+ /// order.
+ /// </summary>
+ internal class StatsCollection {
+ public StatsCollection() {
+ dict = new Dictionary<string, Stats>();
+ order = new List<string>();
+ }
+
+ public Stats this[string key] { get { return dict[key]; } }
+ public bool ContainsKey(string key) { return dict.ContainsKey(key); }
+ public IEnumerable<string> Keys { get { return order; } }
+ public void Add(string key, Stats value) {
+ dict.Add(key, value);
+ order.Add(key);
+ }
+ public void WriteReportTable(TextWriter writer, float scale) {
+ writer.WriteLine("<table border>");
+ writer.WriteLine("<tr><th>Name</th><th>Median</th><th>Mean</th><th>StdDev</th><th>Min</th><th>Max</th><th>Samples</th></tr>");
+
+ foreach (string key in this.Keys) {
+ Stats value = this[key];
+ writer.Write("<tr>");
+ writer.Write("<td>" + HttpUtility.HtmlEncode(key) + "</td>");
+ writer.Write("<td>" + (value.Median / scale).ToString("f3") + "</td>");
+ writer.Write("<td>" + (value.Mean / scale).ToString("f3") + "</td>");
+ writer.Write("<td>" + (value.StandardDeviation / scale).ToString("f3") + "</td>");
+ writer.Write("<td>" + (value.Minimum / scale).ToString("f3") + "</td>");
+ writer.Write("<td>" + (value.Maximum / scale).ToString("f3") + "</td>");
+ writer.Write("<td>" + value.Count + "</td>");
+ writer.WriteLine("</tr>");
+ }
+ writer.WriteLine("</table>");
+ }
+
+ #region privates
+ Dictionary<string, Stats> dict;
+ List<string> order;
+ #endregion
+ }
+}
+