This commit is contained in:
Sean Stolberg 2018-08-14 11:32:48 -07:00
Родитель 9ca92eed59
Коммит 90a589eb31
42 изменённых файлов: 30300 добавлений и 1 удалений

5
.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,5 @@
.vs/
.idea/
*/bin/
*/obj/
*/Properties/launchSettings.json

19
.gitlab-ci.yml Normal file
Просмотреть файл

@ -0,0 +1,19 @@
stages:
- build
build:win:
stage: build
tags:
- buildfarm
- windows
- "10"
before_script:
- echo "Installing dotnetcore-sdk"
- 'powershell -Command "Unblock-File dotnet-install.ps1 && powershell -Command ".\dotnet-install.ps1 -InstallDir C:\Program Files\dotnet"'
script:
- echo "Building solution"
- '"C:\Program Files\dotnet\dotnet.exe" build'
# - echo "Running tests"
# - cd UnityPerformanceBenchmarkReporterTests
# - '"C:\Program Files\dotnet\dotnet.exe" test --logger "trx;LogFileName=UnityPerformanceBenchmarkReporterTests.trx"'
after_script:
- C:\Users\builduser\post_build_script.bat

8
CONTRIBUTING Normal file
Просмотреть файл

@ -0,0 +1,8 @@
# PR review process
- Any PR must have an entry in the corresponding changelog in a separate commit (CHANGELOG.MD file)
- Changelog follow these guidelines: https://github.com/Unity-Technologies/PostProcessing/blob/v2/CHANGELOG.md
- Each release branch (2018.1, 2018.2...) have a unique Changelog file
- when backporting, don't backport the changelog commit but update the branch changelog manually
- (optional) add reviewver from doc team
- Any more complex description of a change with future need to go in a release note file

9
CONTRIBUTIONS.md Normal file
Просмотреть файл

@ -0,0 +1,9 @@
# Contributions
## If you are interested in contributing, here are some ground rules:
* Talk to us before doing the work -- we love contributions, but we might already be working on the same thing, or we might have different opinions on how it should be implemented.
## All contributions are subject to the [Unity Contribution Agreement(UCA)](https://unity3d.com/legal/licenses/Unity_Contribution_Agreement)
By making a pull request, you are confirming agreement to the terms and conditions of the UCA, including that your Contributions are your original creation and that you have complete right and authority to make your Contributions.
## Once you have a change ready following these ground rules. Simply make a pull request in Github

5
LICENSE.md Normal file
Просмотреть файл

@ -0,0 +1,5 @@
Copyright © 2018 Unity Technologies ApS
Licensed under the Unity Companion License for Unity-dependent projects--see [Unity Companion License](http://www.unity3d.com/legal/licenses/Unity_Companion_License).
Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions.

Просмотреть файл

@ -1 +1 @@
# PerformanceBenchmarkReporter
The Unity Performance Benchmark tool enables partners and developers to establish benchmark samples and measurements using the Performance Testing package, then use these benchmark values to compare subsequent performance test results in an html output utilizing graphical visualizations.

15
Third Party Notices.md Normal file
Просмотреть файл

@ -0,0 +1,15 @@
This package contains third-party software components governed by the license(s) indicated below:
Component Name: Chart.js
License Type: MIT
The MIT License (MIT)
Copyright (c) 2018 Chart.js Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Двоичные данные
UnityPerformanceBenchmarkComponents.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 80 KiB

Просмотреть файл

@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27428.2027
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnityPerformanceBenchmarkReporter", "UnityPerformanceBenchmarkReporter\UnityPerformanceBenchmarkReporter.csproj", "{3E9C48B1-7CA9-4E55-B3EF-0EE91FF8BF37}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnityPerformanceBenchmarkReporterTests", "UnityPerformanceBenchmarkReporterTests\UnityPerformanceBenchmarkReporterTests.csproj", "{4BB0BB51-6DDE-4ED2-8F41-51943FA74844}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3E9C48B1-7CA9-4E55-B3EF-0EE91FF8BF37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3E9C48B1-7CA9-4E55-B3EF-0EE91FF8BF37}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3E9C48B1-7CA9-4E55-B3EF-0EE91FF8BF37}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3E9C48B1-7CA9-4E55-B3EF-0EE91FF8BF37}.Release|Any CPU.Build.0 = Release|Any CPU
{4BB0BB51-6DDE-4ED2-8F41-51943FA74844}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4BB0BB51-6DDE-4ED2-8F41-51943FA74844}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4BB0BB51-6DDE-4ED2-8F41-51943FA74844}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4BB0BB51-6DDE-4ED2-8F41-51943FA74844}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DB9F077A-8AD5-4C3A-84C6-1E9E4658D66F}
EndGlobalSection
EndGlobal

Просмотреть файл

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
namespace UnityPerformanceBenchmarkReporter.Entities
{
[Serializable]
public class PerformanceTestResult
{
public string TestName;
public List<string> TestCategories;
public string TestVersion;
public double StartTime;
public double EndTime;
public List<SampleGroup> SampleGroups;
}
}

Просмотреть файл

@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
namespace UnityPerformanceBenchmarkReporter.Entities
{
[Serializable]
public class PerformanceTestRun
{
public PlayerSystemInfo PlayerSystemInfo;
public EditorVersion EditorVersion;
public BuildSettings BuildSettings;
public ScreenSettings ScreenSettings;
public QualitySettings QualitySettings;
public PlayerSettings PlayerSettings;
public string TestSuite;
public double StartTime;
public double EndTime;
public List<PerformanceTestResult> Results = new List<PerformanceTestResult>();
}
[Serializable]
public class PlayerSystemInfo
{
public string OperatingSystem;
public string DeviceModel;
public string DeviceName;
public string ProcessorType;
public int ProcessorCount;
public string GraphicsDeviceName;
public int SystemMemorySize;
public string XrModel;
public string XrDevice;
}
[Serializable]
public class EditorVersion
{
public string FullVersion;
public int DateSeconds;
public string Branch;
public int RevisionValue;
}
[Serializable]
public class BuildSettings
{
public string Platform;
public string BuildTarget;
public bool DevelopmentPlayer;
public string AndroidBuildSystem;
}
[Serializable]
public class ScreenSettings
{
public int ScreenWidth;
public int ScreenHeight;
public int ScreenRefreshRate;
public bool Fullscreen;
}
[Serializable]
public class QualitySettings
{
public int Vsync;
public int AntiAliasing;
public string ColorSpace;
public string AnisotropicFiltering;
public string BlendWeights;
}
[Serializable]
public class PlayerSettings
{
public string ScriptingBackend;
public bool VrSupported;
public bool MtRendering;
public bool GraphicsJobs;
public bool GpuSkinning;
public string GraphicsApi;
//public string Batchmode; TODO
//public int StaticBatching; TODO
//public int DynamicBatching; TODO
public string StereoRenderingPath;
public string RenderThreadingMode;
public string AndroidMinimumSdkVersion;
public string AndroidTargetSdkVersion;
public List<string> EnabledXrTargets;
}
}

Просмотреть файл

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
namespace UnityPerformanceBenchmarkReporter.Entities
{
[Serializable]
public class PerformanceTestRunResult
{
public PlayerSystemInfo PlayerSystemInfo;
public EditorVersion EditorVersion;
public BuildSettings BuildSettings;
public ScreenSettings ScreenSettings;
public QualitySettings QualitySettings;
public PlayerSettings PlayerSettings;
public string TestSuite;
public DateTime StartTime;
public List<TestResult> TestResults = new List<TestResult>();
public bool IsBaseline;
public string ResultName;
}
}

Просмотреть файл

@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
namespace UnityPerformanceBenchmarkReporter.Entities
{
[Serializable]
public class SampleGroup
{
public List<double> Samples;
public double Min;
public double Max;
public double Median;
public double Average;
public double StandardDeviation;
public double PercentileValue;
public double Sum;
public int Zeroes;
public int SampleCount;
public SampleGroupDefinition Definition;
}
[Serializable]
public class SampleGroupDefinition
{
public string Name;
public SampleUnit SampleUnit;
public AggregationType AggregationType;
public double Threshold;
public bool IncreaseIsBetter;
public double Percentile;
}
public enum AggregationType
{
Average = 0,
Min = 1,
Max = 2,
Median = 3,
Percentile = 4
}
public enum SampleUnit
{
Nanosecond,
Microsecond,
Millisecond,
Second,
Byte,
Kilobyte,
Megabyte,
Gigabyte,
None
}
}

Просмотреть файл

@ -0,0 +1,25 @@

namespace UnityPerformanceBenchmarkReporter.Entities
{
public class SampleGroupResult
{
public string SampleGroupName;
public string SampleUnit;
public double AggregatedValue;
public double BaselineValue;
public double Threshold;
public bool IncreaseIsBetter;
public string AggregationType;
public double Percentile;
public bool Regressed;
public double Min;
public double Max;
public double Median;
public double Average;
public double StandardDeviation;
public double PercentileValue;
public double Sum;
public int Zeroes;
public int SampleCount;
}
}

Просмотреть файл

@ -0,0 +1,13 @@
using System.Collections.Generic;
namespace UnityPerformanceBenchmarkReporter.Entities
{
public class TestResult
{
public string TestName;
public List<string> TestCategories;
public string TestVersion;
public int State;
public List<SampleGroupResult> SampleGroupResults;
}
}

Просмотреть файл

@ -0,0 +1,15 @@

namespace UnityPerformanceBenchmarkReporter.Entities
{
public enum TestState
{
Inconclusive = 0,
NotRunnable = 1,
Skipped = 2,
Ignored = 3,
Success = 4,
Failure = 5,
Error = 6,
Cancelled = 7
}
}

Просмотреть файл

@ -0,0 +1,33 @@
using System;
namespace UnityPerformanceBenchmarkReporter
{
public static class ExtensionMethods
{
public static double TruncToSigFig(this double d, uint digits)
{
double truncated;
if(d == 0)
{
truncated = 0;
}
else
{
var s = Convert.ToString(d);
var parts = s.Split('.');
if (parts.Length <= 1 || parts[1].Length <= digits)
{
truncated = d;
}
else
{
var newSigDigits = parts[1].Substring(0, (int) (digits == 0 ? digits : parts[1].Length - digits));
truncated = Convert.ToUInt32(parts[0] + newSigDigits);
}
}
return truncated;
}
}
}

Просмотреть файл

@ -0,0 +1,138 @@
using System;
using UnityPerformanceBenchmarkReporter.Entities;
namespace UnityPerformanceBenchmarkReporter
{
internal class MetadataValidator
{
public void ValidatePlayerSystemInfo(PerformanceTestRun testRun1, PerformanceTestRun testRun2)
{
if (testRun1.PlayerSystemInfo.DeviceModel != testRun2.PlayerSystemInfo.DeviceModel)
{
WriteWarningMessage("Test results with mismatching PlayerSystemInfo.DeviceModeltestRun");
}
if (testRun1.PlayerSystemInfo.GraphicsDeviceName != testRun2.PlayerSystemInfo.GraphicsDeviceName)
{
WriteWarningMessage("Test results with mismatching PlayerSystemInfo.GraphicsDeviceName");
}
if (testRun1.PlayerSystemInfo.ProcessorCount != testRun2.PlayerSystemInfo.ProcessorCount)
{
WriteWarningMessage("Test results with mismatching PlayerSystemInfo.ProcessorCount");
}
if (testRun1.PlayerSystemInfo.ProcessorType != testRun2.PlayerSystemInfo.ProcessorType)
{
WriteWarningMessage("Test results with mismatching PlayerSystemInfo.ProcessorType");
}
if (testRun1.PlayerSystemInfo.XrDevice != testRun2.PlayerSystemInfo.XrDevice)
{
WriteWarningMessage("Test results with mismatching PlayerSystemInfo.XrDevice");
}
if (testRun1.PlayerSystemInfo.XrModel != testRun2.PlayerSystemInfo.XrModel)
{
WriteWarningMessage("Test results with mismatching PlayerSystemInfo.XrModel");
}
}
public void ValidatePlayerSettings(PerformanceTestRun testRun1, PerformanceTestRun testRun2)
{
if (testRun1.PlayerSettings.AndroidMinimumSdkVersion != testRun2.PlayerSettings.AndroidMinimumSdkVersion)
{
WriteWarningMessage("Test results with mismatching PlayerSettings.AndroidMinimumSdkVersion");
}
if (testRun1.PlayerSettings.AndroidTargetSdkVersion != testRun2.PlayerSettings.AndroidTargetSdkVersion)
{
WriteWarningMessage("Test results with mismatching PlayerSettings.GraphicsDeviceName");
}
foreach (var enabledXrTarget in testRun1.PlayerSettings.EnabledXrTargets)
{
if (!testRun2.PlayerSettings.EnabledXrTargets.Contains(enabledXrTarget))
{
WriteWarningMessage("Test results with mismatching PlayerSettings.EnabledXrTargets");
}
}
if (testRun1.PlayerSettings.GpuSkinning != testRun2.PlayerSettings.GpuSkinning)
{
WriteWarningMessage("Test results with mismatching PlayerSettings.GpuSkinning");
}
if (testRun1.PlayerSettings.GraphicsApi != testRun2.PlayerSettings.GraphicsApi)
{
WriteWarningMessage("Test results with mismatching PlayerSettings.GraphicsApi");
}
if (testRun1.PlayerSettings.GraphicsJobs != testRun2.PlayerSettings.GraphicsJobs)
{
WriteWarningMessage("Test results with mismatching PlayerSettings.GraphicsJobs");
}
if (testRun1.PlayerSettings.MtRendering != testRun2.PlayerSettings.MtRendering)
{
WriteWarningMessage("Test results with mismatching PlayerSettings.MtRendering");
}
if (testRun1.PlayerSettings.RenderThreadingMode != testRun2.PlayerSettings.RenderThreadingMode)
{
WriteWarningMessage("Test results with mismatching PlayerSettings.RenderThreadingMode");
}
if (testRun1.PlayerSettings.ScriptingBackend != testRun2.PlayerSettings.ScriptingBackend)
{
WriteWarningMessage("Test results with mismatching PlayerSettings.ScriptingBackend");
}
if (testRun1.PlayerSettings.StereoRenderingPath != testRun2.PlayerSettings.StereoRenderingPath)
{
WriteWarningMessage("Test results with mismatching PlayerSettings.StereoRenderingPath");
}
if (testRun1.PlayerSettings.VrSupported != testRun2.PlayerSettings.VrSupported)
{
WriteWarningMessage("Test results with mismatching PlayerSettings.VrSupported");
}
}
public void ValidateQualitySettings(PerformanceTestRun testRun1, PerformanceTestRun testRun2)
{
if (testRun1.QualitySettings.AnisotropicFiltering != testRun2.QualitySettings.AnisotropicFiltering)
{
WriteWarningMessage("Test results with mismatching QualitySettings.AnisotropicFiltering");
}
if (testRun1.QualitySettings.AntiAliasing != testRun2.QualitySettings.AntiAliasing)
{
WriteWarningMessage("Test results with mismatching QualitySettings.AntiAliasing");
}
if (testRun1.QualitySettings.BlendWeights != testRun2.QualitySettings.BlendWeights)
{
WriteWarningMessage("Test results with mismatching QualitySettings.BlendWeights");
}
if (testRun1.QualitySettings.ColorSpace != testRun2.QualitySettings.ColorSpace)
{
WriteWarningMessage("Test results with mismatching QualitySettings.ColorSpace");
}
if (testRun1.QualitySettings.Vsync != testRun2.QualitySettings.Vsync)
{
WriteWarningMessage("Test results with mismatching QualitySettings.Vsync");
}
}
public void ValidateScreenSettings(PerformanceTestRun testRun1, PerformanceTestRun testRun2)
{
if (testRun1.ScreenSettings.Fullscreen != testRun2.ScreenSettings.Fullscreen)
{
WriteWarningMessage("Test results with mismatching ScreenSettings.Fullscreen");
}
if (testRun1.ScreenSettings.ScreenHeight != testRun2.ScreenSettings.ScreenHeight)
{
WriteWarningMessage("Test results with mismatching ScreenSettings.ScreenHeight");
}
if (testRun1.ScreenSettings.ScreenRefreshRate != testRun2.ScreenSettings.ScreenRefreshRate)
{
WriteWarningMessage("Test results with mismatching ScreenSettings.ScreenRefreshRate");
}
if (testRun1.ScreenSettings.ScreenWidth != testRun2.ScreenSettings.ScreenWidth)
{
WriteWarningMessage("Test results with mismatching ScreenSettings.ScreenWidth");
}
}
private void WriteWarningMessage(string msg)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(msg);
Console.ResetColor();
}
}
}

Просмотреть файл

@ -0,0 +1,108 @@
using System;
using System.Linq;
using Mono.Options;
namespace UnityPerformanceBenchmarkReporter
{
public class OptionsParser
{
private bool help;
private readonly string about = "The Unity Performance Benchmark Reporter enables the comparison of baseline and subsequent performance test measurements (as generated using the Unity Test Runner with the Unity Performance Testing package) in an html report utilizing graphical visualizations.";
private readonly string learnMore =
"To learn more about the Unity Performance Benchmark Reporter visit the Unity PerformanceBenchmark GitHub wiki at https://github.com/Unity-Technologies/PerformanceBenchmark/wiki.";
private string sigFigString;
public enum ResultType
{
Test,
Baseline
}
public void ParseOptions(PerformanceBenchmark performanceBenchmark, string[] args)
{
var os = GetOptions(performanceBenchmark);
try
{
var remaining = os.Parse(args);
if (help)
{
ShowHelp(string.Empty, os);
}
if (!string.IsNullOrEmpty(sigFigString))
{
try
{
var sigFig = System.Convert.ToUInt32(sigFigString);
performanceBenchmark.AddSigFig(sigFig);
}
catch (Exception)
{
ShowHelp(string.Format("Error trying to convert sigfig value {0} to integer >= 0.", sigFigString), os);
}
}
if (!performanceBenchmark.ResultXmlFilePaths.Any() && !performanceBenchmark.ResultXmlDirectoryPaths.Any())
{
ShowHelp("Missing required option --testresultsxmlsource=(filePath|directoryPath)", os);
}
if (remaining.Any())
{
var errorMessage = string.Format("Unknown option: '{0}.\r\n'", remaining[0]);
ShowHelp(errorMessage, os);
}
}
catch (Exception e)
{
ShowHelp(string.Format("Error encountered while parsing option: {0}.\r\n", e.Message), os);
}
}
private OptionSet GetOptions(PerformanceBenchmark performanceBenchmark)
{
return new OptionSet()
.Add("?|help|h", "Prints out the options.", option => help = option != null)
.Add("testresultsxmlsource=", "REQUIRED - Path to a test result XML filename or directory. Directories are searched resursively. You can repeat this option with multiple result file or directory paths.",
xmlsource =>
{
performanceBenchmark.AddXmlSourcePath(xmlsource, "testresultsxmlsource", ResultType.Test);
})
.Add("baselinexmlsource:", "OPTIONAL - Path to a baseline XML filename or directory. Directories are searched resursively. You can repeat this option with multiple baseline file or directory paths.",
xmlsource =>
{
performanceBenchmark.AddXmlSourcePath(xmlsource, "baselinexmlsource", ResultType.Baseline);
})
.Add("reportdirpath:", "OPTIONAL - Path to directory where the UnityPerformanceBenchmark report will be written. Default is current working directory.",
performanceBenchmark.AddReportDirPath)
.Add("sigfig:", "OPTIONAL - Specify the number of significant figures to use when collecting and calculating thresholds and failures for non-integer based metrics (from the profiler, Camer.Render CPU time in milliseconds, for example). This value must be an integer >= 0.",
option =>
{
if (option != null)
{
sigFigString = option;
}
});
}
private void ShowHelp(string message, OptionSet optionSet)
{
if (!string.IsNullOrEmpty(message))
{
Console.ForegroundColor = ConsoleColor.Red;
Console.Error.WriteLine(message);
Console.ResetColor();
}
Console.WriteLine(about + "\r\n");
Console.WriteLine(learnMore + "\r\n");
Console.WriteLine("Usage is:");
optionSet.WriteOptionDescriptions(Console.Error);
Environment.Exit(-1);
}
}
}

Просмотреть файл

@ -0,0 +1,213 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using UnityPerformanceBenchmarkReporter.Entities;
namespace UnityPerformanceBenchmarkReporter
{
public class PerformanceBenchmark
{
public HashSet<string> ResultXmlFilePaths { get; } = new HashSet<string>();
public HashSet<string> ResultXmlDirectoryPaths { get; } = new HashSet<string>();
public HashSet<string> BaselineXmlFilePaths { get; } = new HashSet<string>();
public HashSet<string> BaselineXmlDirectoryPaths { get; } = new HashSet<string>();
public uint SigFig { get; private set; }
public string ReportDirPath { get; set; }
private bool firstResult = true;
private PerformanceTestRun firstTestRun = new PerformanceTestRun();
private readonly PerformanceTestRunProcessor performanceTestRunProcessor = new PerformanceTestRunProcessor();
private readonly string xmlFileExtension = ".xml";
public bool BaselineResultFilesExist => BaselineXmlFilePaths.Any() || BaselineXmlDirectoryPaths.Any();
public bool ResultFilesExist => ResultXmlFilePaths.Any() || ResultXmlDirectoryPaths.Any();
public PerformanceBenchmark()
{
// Default significant figures to use for non-integer metrics if user doesn't specify another value.
// Most values are in milliseconds or a count of something, so using more often creates an artificial baseline
// failure based on insignificant digits
SigFig = 0;
}
public void AddPerformanceTestRunResults(
TestResultXmlParser testResultXmlParser,
List<PerformanceTestRunResult> performanceTestRunResults,
List<TestResult> testResults,
List<TestResult> baselineTestResults)
{
AddTestResults(testResultXmlParser, performanceTestRunResults, testResults, baselineTestResults, ResultXmlDirectoryPaths, ResultXmlFilePaths);
}
public void AddBaselinePerformanceTestRunResults(
TestResultXmlParser testResultXmlParser,
List<PerformanceTestRunResult> baselinePerformanceTestRunResults,
List<TestResult> baselineTestResults)
{
AddTestResults(testResultXmlParser, baselinePerformanceTestRunResults, baselineTestResults, baselineTestResults, BaselineXmlDirectoryPaths, BaselineXmlFilePaths, true);
}
public void AddTestResults(
TestResultXmlParser testResultXmlParser,
List<PerformanceTestRunResult> runResults,
List<TestResult> testResults,
List<TestResult> baselineTestResults,
HashSet<string> xmlDirectoryPaths,
HashSet<string> xmlFileNamePaths,
bool isBaseline = false)
{
if (xmlDirectoryPaths.Any())
{
foreach (var xmlDirectory in xmlDirectoryPaths)
{
var xmlFileNames = GetAllXmlFileNames(xmlDirectory);
foreach (var xmlFileName in xmlFileNames)
{
xmlFileNamePaths.Add(xmlFileName);
}
}
}
if (xmlFileNamePaths.Any())
{
foreach (var xmlFileNamePath in xmlFileNamePaths)
{
var performanceTestRun = testResultXmlParser.GetPerformanceTestRunFromXml(xmlFileNamePath);
if (performanceTestRun != null && performanceTestRun.Results.Any())
{
var results = performanceTestRunProcessor.GetTestResults(performanceTestRun);
if (!results.Any())
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("No performance test data found to report in: {0}", xmlFileNamePath);
Console.ResetColor();
}
testResults.AddRange(results);
performanceTestRunProcessor.UpdateTestResultsBasedOnBaselineResults(baselineTestResults, testResults, SigFig);
ValidateMetadata(performanceTestRun);
runResults.Add(performanceTestRunProcessor.CreateTestRunResult
(
firstTestRun,
testResults,
Path.GetFileNameWithoutExtension(xmlFileNamePath),
isBaseline)
);
}
}
}
}
private IEnumerable<string> GetAllXmlFileNames(string xmlDirectory)
{
var dir = new DirectoryInfo(xmlDirectory);
var xmlFileNames = dir.GetFiles("*" + xmlFileExtension, SearchOption.AllDirectories).Select(f => f.FullName);
return xmlFileNames;
}
private void ValidateMetadata(PerformanceTestRun performanceTestRun)
{
if (firstResult)
{
firstTestRun = performanceTestRun;
firstResult = false;
}
else
{
var metadataValidator = new MetadataValidator();
metadataValidator.ValidatePlayerSystemInfo(firstTestRun, performanceTestRun);
metadataValidator.ValidatePlayerSettings(firstTestRun, performanceTestRun);
metadataValidator.ValidateQualitySettings(firstTestRun, performanceTestRun);
metadataValidator.ValidateScreenSettings(firstTestRun, performanceTestRun);
}
}
public void AddXmlSourcePath(string xmlSourcePath, string optionName, OptionsParser.ResultType resultType)
{
if (string.IsNullOrEmpty(xmlSourcePath))
{
throw new ArgumentNullException(xmlSourcePath);
}
if (string.IsNullOrEmpty(optionName))
{
throw new ArgumentNullException(optionName);
}
//If has .xml file extension
if (xmlSourcePath.EndsWith(xmlFileExtension))
{
ProcessAsXmlFile(xmlSourcePath, optionName, resultType);
}
else
{
ProcessAsXmlDirectory(xmlSourcePath, optionName, resultType);
}
}
private void ProcessAsXmlDirectory(string xmlSourcePath, string optionName, OptionsParser.ResultType resultType)
{
if (!Directory.Exists(xmlSourcePath))
{
throw new ArgumentException(string.Format("{0} directory `{1}` cannot be found", optionName,
xmlSourcePath));
}
var xmlFileNames = GetAllXmlFileNames(xmlSourcePath).ToArray();
if (!xmlFileNames.Any())
{
throw new ArgumentException(string.Format("{0} directory `{1}` doesn't contain any .xml files.", optionName,
xmlSourcePath));
}
switch (resultType)
{
case OptionsParser.ResultType.Test:
ResultXmlDirectoryPaths.Add(xmlSourcePath);
break;
case OptionsParser.ResultType.Baseline:
BaselineXmlDirectoryPaths.Add(xmlSourcePath);
break;
default:
throw new InvalidEnumArgumentException(resultType.ToString());
}
}
private void ProcessAsXmlFile(string xmlSourcePath, string optionName, OptionsParser.ResultType resultType)
{
if (!File.Exists(xmlSourcePath))
{
throw new ArgumentException(string.Format("{0} file `{1}` cannot be found", optionName, xmlSourcePath));
}
switch (resultType)
{
case OptionsParser.ResultType.Test:
ResultXmlFilePaths.Add(xmlSourcePath);
break;
case OptionsParser.ResultType.Baseline:
BaselineXmlFilePaths.Add(xmlSourcePath);
break;
default:
throw new InvalidEnumArgumentException(resultType.ToString());
}
}
public void AddReportDirPath(string reportDirectoryPath)
{
ReportDirPath = reportDirectoryPath;
}
public void AddSigFig(uint sigFig)
{
SigFig = sigFig;
}
}
}

Просмотреть файл

@ -0,0 +1,194 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityPerformanceBenchmarkReporter.Entities;
namespace UnityPerformanceBenchmarkReporter
{
internal enum MeasurementResult
{
Neutral = 0,
Regression = 1,
Progression = 2
}
public class PerformanceTestRunProcessor
{
public List<TestResult> GetTestResults(
PerformanceTestRun performanceTestRun)
{
var mergedTestExecutions = MergeTestExecutions(performanceTestRun);
var performanceTestResults = new List<TestResult>();
foreach (var testName in mergedTestExecutions.Keys)
{
var performanceTestResult = new TestResult
{
TestName = testName,
TestCategories = performanceTestRun.Results.First(r => r.TestName == testName).TestCategories,
TestVersion = performanceTestRun.Results.First(r => r.TestName == testName).TestVersion,
State = (int) TestState.Success,
SampleGroupResults = new List<SampleGroupResult>()
};
foreach (var sampleGroup in mergedTestExecutions[testName])
{
var sampleGroupResult = new SampleGroupResult
{
SampleGroupName = sampleGroup.Definition.Name,
SampleUnit = sampleGroup.Definition.SampleUnit.ToString(),
IncreaseIsBetter = sampleGroup.Definition.IncreaseIsBetter,
Threshold = sampleGroup.Definition.Threshold,
AggregationType = sampleGroup.Definition.AggregationType.ToString(),
Percentile = sampleGroup.Definition.Percentile,
Min = sampleGroup.Min,
Max = sampleGroup.Max,
Median = sampleGroup.Median,
Average = sampleGroup.Average,
StandardDeviation = sampleGroup.StandardDeviation,
PercentileValue = sampleGroup.PercentileValue,
Sum = sampleGroup.Sum,
Zeroes = sampleGroup.Zeroes,
SampleCount = sampleGroup.SampleCount,
BaselineValue = -1,
AggregatedValue = GetAggregatedSampleValue(sampleGroup)
};
performanceTestResult.SampleGroupResults.Add(sampleGroupResult);
}
performanceTestResults.Add(performanceTestResult);
}
return performanceTestResults;
}
public void UpdateTestResultsBasedOnBaselineResults(List<TestResult> baselineTestResults,
List<TestResult> testResults, uint sigfig)
{
foreach (var testResult in testResults)
{
if (baselineTestResults.All(r => r.TestName != testResult.TestName)) continue;
var baselineSampleGroupResults = baselineTestResults.First(r => r.TestName == testResult.TestName).SampleGroupResults;
foreach (var sampleGroupResult in testResult.SampleGroupResults)
{
if (baselineSampleGroupResults.Any(sg => sg.SampleGroupName == sampleGroupResult.SampleGroupName))
{
var baselineSampleGroupResult = baselineSampleGroupResults.First(sg =>
sg.SampleGroupName == sampleGroupResult.SampleGroupName);
sampleGroupResult.BaselineValue = baselineSampleGroupResult.AggregatedValue;
sampleGroupResult.Regressed = DeterminePerformanceResult(sampleGroupResult, sigfig) == MeasurementResult.Regression;
}
}
if (testResult.SampleGroupResults.Any(r => r.Regressed))
{
testResult.State = (int) TestState.Failure;
}
}
}
public Dictionary<string, List<SampleGroup>> MergeTestExecutions(PerformanceTestRun performanceTestRun)
{
var mergedTestExecutions = new Dictionary<string, List<SampleGroup>>();
var testNames = performanceTestRun.Results.Select(te => te.TestName).Distinct().ToList();
foreach (var testName in testNames)
{
var executions = performanceTestRun.Results.Where(te => te.TestName == testName);
var sampleGroups = new List<SampleGroup>();
foreach (var execution in executions)
{
foreach (var sampleGroup in execution.SampleGroups)
{
if (sampleGroups.Any(sg => sg.Definition.Name == sampleGroup.Definition.Name))
{
sampleGroups.First(sg => sg.Definition.Name == sampleGroup.Definition.Name).Samples
.AddRange(sampleGroup.Samples);
}
else
{
sampleGroups.Add(sampleGroup);
}
}
}
mergedTestExecutions.Add(testName, sampleGroups);
}
return mergedTestExecutions;
}
double GetAggregatedSampleValue(SampleGroup sampleGroup)
{
double aggregatedSampleValue;
switch (sampleGroup.Definition.AggregationType)
{
case AggregationType.Average:
aggregatedSampleValue = sampleGroup.Average;
break;
case AggregationType.Min:
aggregatedSampleValue = sampleGroup.Min;
break;
case AggregationType.Max:
aggregatedSampleValue = sampleGroup.Max;
break;
case AggregationType.Median:
aggregatedSampleValue = sampleGroup.Median;
break;
case AggregationType.Percentile:
aggregatedSampleValue = sampleGroup.PercentileValue;
break;
default:
throw new ArgumentOutOfRangeException(string.Format("Unhandled aggregation type {0}", sampleGroup.Definition.AggregationType));
}
return aggregatedSampleValue;
}
MeasurementResult DeterminePerformanceResult(SampleGroupResult sampleGroup, uint sigFig)
{
var measurementResult = MeasurementResult.Neutral;
var positiveThresholdValue = sampleGroup.BaselineValue + sampleGroup.BaselineValue * sampleGroup.Threshold;
var negativeThresholdValue = sampleGroup.BaselineValue - sampleGroup.BaselineValue * sampleGroup.Threshold;
if (sampleGroup.IncreaseIsBetter)
{
if (sampleGroup.AggregatedValue.TruncToSigFig(sigFig) < negativeThresholdValue.TruncToSigFig(sigFig))
{
measurementResult = MeasurementResult.Regression;
}
if (sampleGroup.AggregatedValue.TruncToSigFig(sigFig) > positiveThresholdValue.TruncToSigFig(sigFig))
{
measurementResult = MeasurementResult.Progression;
}
}
else
{
if (sampleGroup.AggregatedValue.TruncToSigFig(sigFig) > positiveThresholdValue.TruncToSigFig(sigFig))
{
measurementResult = MeasurementResult.Regression;
}
if (sampleGroup.AggregatedValue.TruncToSigFig(sigFig) < negativeThresholdValue.TruncToSigFig(sigFig))
{
measurementResult = MeasurementResult.Progression;
}
}
return measurementResult;
}
public PerformanceTestRunResult CreateTestRunResult(PerformanceTestRun runResults,
List<TestResult> testResults, string resultName, bool isBaseline = false)
{
var performanceTestRunResult = new PerformanceTestRunResult
{
ResultName = resultName,
IsBaseline = isBaseline,
TestSuite = runResults.TestSuite,
StartTime =
new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(
runResults.StartTime),
TestResults = testResults,
PlayerSystemInfo = runResults.PlayerSystemInfo,
EditorVersion = runResults.EditorVersion,
BuildSettings = runResults.BuildSettings,
ScreenSettings = runResults.ScreenSettings,
QualitySettings = runResults.QualitySettings,
PlayerSettings = runResults.PlayerSettings
};
return performanceTestRunResult;
}
}
}

Просмотреть файл

@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityPerformanceBenchmarkReporter.Entities;
using UnityPerformanceBenchmarkReporter.Report;
namespace UnityPerformanceBenchmarkReporter
{
internal class Program
{
private static void Main(string[] args)
{
var aggregateTestRunResults = new List<PerformanceTestRunResult>();
var baselinePerformanceTestRunResults = new List<PerformanceTestRunResult>();
var baselineTestResults = new List<TestResult>();
var performanceTestRunResults = new List<PerformanceTestRunResult>();
var testResults = new List<TestResult>();
var performanceBenchmark = new PerformanceBenchmark();
var optionsParser = new OptionsParser();
optionsParser.ParseOptions(performanceBenchmark, args);
var testResultXmlParser = new TestResultXmlParser();
if (performanceBenchmark.BaselineResultFilesExist)
{
performanceBenchmark.AddBaselinePerformanceTestRunResults(testResultXmlParser, baselinePerformanceTestRunResults, baselineTestResults);
if (baselinePerformanceTestRunResults.Any())
{
aggregateTestRunResults.AddRange(baselinePerformanceTestRunResults);
}
else
{
Environment.Exit(1);
}
}
if (performanceBenchmark.ResultFilesExist)
{
performanceBenchmark.AddPerformanceTestRunResults(testResultXmlParser, performanceTestRunResults, testResults, baselineTestResults);
if (performanceTestRunResults.Any())
{
aggregateTestRunResults.AddRange(performanceTestRunResults);
}
else
{
Environment.Exit(1);
}
}
var reportWriter = new ReportWriter();
reportWriter.WriteReport(aggregateTestRunResults, performanceBenchmark.SigFig, performanceBenchmark.ReportDirPath, performanceBenchmark.BaselineResultFilesExist);
}
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,840 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using UnityPerformanceBenchmarkReporter.Entities;
namespace UnityPerformanceBenchmarkReporter.Report
{
public class ReportWriter
{
private readonly string unityPerformanceBenchmarkName = "UnityPerformanceBenchmark";
private readonly List<PerformanceTestRunResult> perfTestRunResults = new List<PerformanceTestRunResult>();
private readonly HashSet<string> embeddedResourceNames = new HashSet<string>
{
"Chart.bundle.js",
"styles.css",
"UnityLogo.png"
};
private List<string> distinctTestNames;
private List<string> distinctSampleGroupNames;
private PerformanceTestRunResult baselineResults;
private readonly Regex illegalCharacterScrubberRegex = new Regex("[^0-9a-zA-Z]", RegexOptions.Compiled);
private uint thisSigFig;
private bool thisHasBenchmarkResults;
public void WriteReport(List<PerformanceTestRunResult> results, uint sigFig = 2,
string reportDirectoryPath = null, bool hasBenchmarkResults = false)
{
if (results != null && results.Count > 0)
{
thisSigFig = sigFig;
thisHasBenchmarkResults = hasBenchmarkResults;
EnsureOrderedResults(results);
SetDistinctTestNames();
SetDistinctSampleGroupNames();
var reportDirectory = EnsureBenchmarkDirectory(reportDirectoryPath);
WriteEmbeddedResourceFiles(reportDirectory);
var benchmarkReportFile = GetBenchmarkReportFile(reportDirectory);
using (var rw = new StreamWriter(benchmarkReportFile))
{
WriteHtmlReport(rw);
}
}
else
{
throw new ArgumentNullException(nameof(results), "PerformanceTestRun results list is empty. No report will be written.");
}
}
private FileStream GetBenchmarkReportFile(DirectoryInfo benchmarkDirectory)
{
var htmlFileName = Path.Combine(benchmarkDirectory.FullName,
string.Format("{0}_{1:yyyy-MM-dd_hh-mm-ss-fff}.html", unityPerformanceBenchmarkName, DateTime.Now));
var benchmarkReportFile = TryCreateHtmlFile(htmlFileName);
return benchmarkReportFile;
}
private DirectoryInfo EnsureBenchmarkDirectory(string reportDirectoryPath)
{
var reportDirPath = !string.IsNullOrEmpty(reportDirectoryPath)
? reportDirectoryPath
: Directory.GetCurrentDirectory();
var benchmarkDirectory = Directory.CreateDirectory(Path.Combine(reportDirPath, unityPerformanceBenchmarkName));
return benchmarkDirectory;
}
private static FileStream TryCreateHtmlFile(string htmlFileName)
{
try
{
return File.Create(htmlFileName);
}
catch (Exception e)
{
Console.Error.WriteLine("Exception thrown while trying to create report file at {0}:\r\n{1}", htmlFileName, e.Message);
throw;
}
}
private void WriteEmbeddedResourceFiles(DirectoryInfo benchmarkDirectory)
{
var assemblyNameParts = Assembly.GetExecutingAssembly().Location.Split('\\');
var assemblyName = assemblyNameParts[assemblyNameParts.Length - 1].Split('.')[0];
foreach (var embeddedResourceName in embeddedResourceNames)
{
var fileName = Path.Combine(benchmarkDirectory.FullName, embeddedResourceName);
try
{
WriteResourceToFile(
string.Format("{0}.Report.{1}", assemblyName, embeddedResourceName),
fileName);
}
catch (Exception e)
{
Console.Error.WriteLine("Exception thrown while trying to create file from embedded resource at {0}:\r\n{1}", fileName, e.Message);
throw;
}
}
}
private void WriteResourceToFile(string resourceName, string fileName)
{
using(var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
{
using(var file = new FileStream(fileName, FileMode.Create, FileAccess.Write))
{
resource.CopyTo(file);
}
}
}
private string ScrubStringForSafeForVariableUse(string source)
{
return illegalCharacterScrubberRegex.Replace(source, "_");
}
private void WriteHtmlReport(StreamWriter streamWriter)
{
if (streamWriter == null)
{
throw new ArgumentNullException(nameof(streamWriter));
}
streamWriter.WriteLine("<!doctype html>");
streamWriter.WriteLine("<html>");
WriteHeader(streamWriter);
WriteBody(streamWriter);
streamWriter.WriteLine("</html>");
}
private void WriteBody(StreamWriter streamWriter)
{
streamWriter.WriteLine("<body>");
WriteLogoWithTitle(streamWriter);
if (TestRunSettingsExist())
{
WriteTestConfig(streamWriter);
}
WriteStatMethodTable(streamWriter);
WriteTestTableWithVisualizations(streamWriter);
streamWriter.WriteLine("</body>");
}
private void WriteTestConfig(StreamWriter streamWriter)
{
streamWriter.Write("<table class=\"testconfigtable\">");
streamWriter.WriteLine("<tr><td>");
WriteShowTestConfigButton(streamWriter);
streamWriter.WriteLine("</td></tr>");
streamWriter.WriteLine("<tr><td>");
WriteTestConfigTable(streamWriter);
streamWriter.WriteLine("</td></tr>");
streamWriter.Write("</table>");
}
private void WriteStatMethodTable(StreamWriter streamWriter)
{
streamWriter.WriteLine("<table class=\"statMethodTable\">");
WriteShowFailedTestsCheckbox(streamWriter);
WriteStatMethodButtons(streamWriter);
streamWriter.WriteLine("</table>");
}
private static void WriteStatMethodButtons(StreamWriter streamWriter)
{
streamWriter.WriteLine("<tr><td>");
streamWriter.WriteLine("<div class=\"buttonheader\">Select statistical method</div>");
streamWriter.WriteLine(
"<div class=\"buttondiv\"><button id=\"MinButton\" class=\"button\">Min</button></div>&nbsp<div class=\"buttondiv\"><button id=\"MaxButton\" class=\"button\">Max</button></div>&nbsp<div class=\"buttondiv\"><button id=\"MedianButton\" class=\"initialbutton\">Median</button></div>&nbsp<div class=\"buttondiv\"><button id=\"AverageButton\" class=\"button\">Average</button></div>");
streamWriter.WriteLine("</td></tr>");
}
private void WriteShowFailedTestsCheckbox(StreamWriter streamWriter)
{
streamWriter.WriteLine("<tr><td>");
streamWriter.WriteLine("<div class=\"showedfailedtests\">");
if (thisHasBenchmarkResults)
{
streamWriter.WriteLine("<label id=\"hidefailed\" class=\"containerLabel\">Show failed tests only");
streamWriter.WriteLine("<input type=\"checkbox\" onclick=\"toggleCanvasWithNoFailures()\">");
}
else
{
streamWriter.WriteLine("<label id=\"hidefailed\" class=\"disabledContainerLabel\">Show failed tests only");
streamWriter.WriteLine(
"<span class=\"tooltiptext\">No failed tests to show because there are no baseline results.</span>");
streamWriter.WriteLine("<input type=\"checkbox\" disabled>");
}
streamWriter.WriteLine("<span class=\"checkmark\"></span>");
streamWriter.WriteLine("</label>");
streamWriter.WriteLine("</div");
streamWriter.WriteLine("</td></tr>");
}
private bool TestRunSettingsExist()
{
var firstResult = perfTestRunResults.First();
return firstResult.PlayerSystemInfo != null
|| firstResult.PlayerSettings != null
|| firstResult.QualitySettings != null
|| firstResult.ScreenSettings != null
|| firstResult.BuildSettings != null
|| firstResult.EditorVersion != null;
}
private void WriteShowTestConfigButton(StreamWriter rw)
{
rw.WriteLine("<button id=\"toggleconfig\" class=\"button\" onclick=\"showTestConfiguration()\">Show Test Configuration</button>");
}
private void WriteJavaScript(StreamWriter rw)
{
rw.WriteLine("<script>");
rw.WriteLine("var failColor = \"rgba(255, 99, 132,0.5)\";");
rw.WriteLine("var passColor = \"rgba(54, 162, 235,0.5)\";");
rw.WriteLine("var baselineColor = \"rgb(255, 159, 64)\";");
WriteShowTestConfigurationButton(rw);
WriteToggleCanvasWithNoFailures(rw);
WriteTestRunArray(rw);
WriteValueArrays(rw);
foreach (var distinctTestName in distinctTestNames)
{
var resultsForThisTest = GetResultsForThisTest(distinctTestName);
foreach (var distinctSampleGroupName in distinctSampleGroupNames)
{
if (!SampleGroupHasSamples(resultsForThisTest, distinctSampleGroupName)) continue;
var canvasId = GetCanvasId(distinctTestName, distinctSampleGroupName);
var format = string.Format("var {0}_data = {{", canvasId);
rw.WriteLine(format);
rw.WriteLine(" labels: testRuns,");
rw.WriteLine(" datasets: [{");
var resultColors = new StringBuilder();
resultColors.Append(" backgroundColor: [");
foreach (var testResult in resultsForThisTest)
{
if (testResult.SampleGroupResults.Any(r =>
ScrubStringForSafeForVariableUse(r.SampleGroupName).Equals(distinctSampleGroupName)))
{
var sampleGroupResult = testResult.SampleGroupResults.First(r =>
ScrubStringForSafeForVariableUse(r.SampleGroupName).Equals(distinctSampleGroupName));
resultColors.Append(sampleGroupResult.Regressed ? "failColor, " : "passColor, ");
}
else
{
resultColors.Append("passColor, ");
}
}
var sampleUnit = GetSampleUnit(resultsForThisTest, distinctSampleGroupName);
// remove trailing comma
resultColors.Length = resultColors.Length - 2;
resultColors.Append("],");
rw.WriteLine(resultColors.ToString());
rw.WriteLine(" borderWidth: 1,");
rw.WriteLine(" label: \"" + (sampleUnit.Equals("None") ? distinctSampleGroupName : sampleUnit) + "\",");
rw.WriteLine(" legend: {");
rw.WriteLine("display: false,");
rw.WriteLine(" },");
rw.WriteLine(" data: {0}", string.Format("{0}_Aggregated_Values", canvasId));
rw.WriteLine(" }");
if (baselineResults != null)
{
rw.WriteLine(" ,{");
rw.WriteLine(" borderColor: baselineColor,");
rw.WriteLine(" borderWidth: 2,");
rw.WriteLine(" fill: false,");
rw.WriteLine(" pointStyle: 'line',");
rw.WriteLine(" label: \"" + (sampleUnit.Equals("None") ? "Baseline " + distinctSampleGroupName : "Baseline " + sampleUnit) + "\",");
rw.WriteLine(" legend: {");
rw.WriteLine("display: false,");
rw.WriteLine(" },");
rw.WriteLine(" data: {0}", string.Format("{0}_Baseline_Values,", canvasId));
rw.WriteLine(" type: 'line'}");
}
rw.WriteLine(" ]");
rw.WriteLine("};");
}
}
rw.WriteLine("window.onload = function() {");
foreach (var distinctTestName in distinctTestNames)
{
var resultsForThisTest = GetResultsForThisTest(distinctTestName);
foreach (var distinctSampleGroupName in distinctSampleGroupNames)
{
if (!SampleGroupHasSamples(resultsForThisTest, distinctSampleGroupName))
{
continue;
}
WriteChartConfigForSampleGroup(rw, resultsForThisTest, distinctSampleGroupName, distinctTestName);
}
}
WriteStatMethodButtonEventListeners(rw);
rw.WriteLine("};");
rw.WriteLine("</script>");
}
private void WriteStatMethodButtonEventListeners(StreamWriter rw)
{
var statisticalMethods = new List<string> {"Min", "Max", "Median", "Average"};
foreach (var thisStatMethod in statisticalMethods)
{
rw.WriteLine(" document.getElementById('{0}Button').addEventListener('click', function()", thisStatMethod);
rw.WriteLine(" {");
foreach (var distinctTestName in distinctTestNames)
{
var resultsForThisTest = GetResultsForThisTest(distinctTestName);
foreach (var distinctSampleGroupName in distinctSampleGroupNames)
{
if (!SampleGroupHasSamples(resultsForThisTest, distinctSampleGroupName)) continue;
var canvasId = GetCanvasId(distinctTestName, distinctSampleGroupName);
var sampleUnit = GetSampleUnit(resultsForThisTest, distinctSampleGroupName);
rw.WriteLine("window.{0}.options.scales.yAxes[0].scaleLabel.labelString = \"{1} {2}\";", canvasId,
thisStatMethod, !sampleUnit.Equals("None") ? sampleUnit : distinctSampleGroupName);
rw.WriteLine("{0}_data.datasets[0].data = {0}_{1}_Values;", canvasId, thisStatMethod);
rw.WriteLine("window.{0}.update();", canvasId);
}
rw.WriteLine("var a = document.getElementById('{0}Button');", thisStatMethod);
rw.WriteLine("a.style.backgroundColor = \"#2196F3\";");
var count = 98;
foreach (var statMethod in statisticalMethods.Where(m=>!m.Equals(thisStatMethod)))
{
var varName = Convert.ToChar(count);
rw.WriteLine("var {0} = document.getElementById('{1}Button');", varName, statMethod);
rw.WriteLine("{0}.style.backgroundColor = \"#3e6892\";", varName);
count++;
}
}
rw.WriteLine(" });");
}
}
private void WriteChartConfigForSampleGroup(StreamWriter rw, List<TestResult> resultsForThisTest, string distinctSampleGroupName,
string distinctTestName)
{
var aggregationType = GetAggregationType(resultsForThisTest, distinctSampleGroupName);
var sampleUnit = GetSampleUnit(resultsForThisTest, distinctSampleGroupName);
var threshold = GetThreshold(resultsForThisTest, distinctSampleGroupName);
var canvasId = GetCanvasId(distinctTestName, distinctSampleGroupName);
rw.WriteLine(" var ctx{0} = document.getElementById('{0}').getContext('2d');", canvasId);
rw.WriteLine(" window.{0} = new Chart(ctx{0}, {{", canvasId);
rw.WriteLine(" type: 'bar',");
rw.WriteLine(" data: {0}_data,", canvasId);
rw.WriteLine(" options: {");
rw.WriteLine("tooltips:");
rw.WriteLine("{");
rw.WriteLine(" mode: 'index',");
rw.WriteLine(" callbacks:");
rw.WriteLine(" {");
rw.WriteLine(" footer: function(tooltipItems, data) {");
rw.WriteLine(" var std = {0}_Stdev_Values[tooltipItems[0].index];", canvasId);
rw.WriteLine(" return 'Threshold: " + threshold + " Standard deviation: ' + std;");
rw.WriteLine(" },");
rw.WriteLine(" },");
rw.WriteLine(" footerFontStyle: 'normal'");
rw.WriteLine("},");
rw.WriteLine("legend: { display: false},");
rw.WriteLine("maintainAspectRatio: false,");
rw.WriteLine("scales: {");
rw.WriteLine(" yAxes: [{");
rw.WriteLine(" display: true,");
rw.WriteLine(" scaleLabel:");
rw.WriteLine(" {");
rw.WriteLine(" display: true,");
rw.WriteLine(" labelString: \"{0} {1}\"", aggregationType, !sampleUnit.Equals("None") ? sampleUnit : distinctSampleGroupName);
rw.WriteLine(" },");
rw.WriteLine(" ticks: {");
rw.WriteLine("suggestedMax: .001,");
rw.WriteLine("suggestedMin: .0");
rw.WriteLine(" }");
rw.WriteLine(" }]");
rw.WriteLine("},");
rw.WriteLine("responsive: true,");
rw.WriteLine("responsiveAnimationDuration: 0,");
rw.WriteLine("title: {");
rw.WriteLine(" display: true,");
rw.WriteLine(" text: \"{0}\"", distinctSampleGroupName);
rw.WriteLine("}");
rw.WriteLine(" }");
rw.WriteLine(" });");
rw.WriteLine(" ");
}
private void WriteShowTestConfigurationButton(StreamWriter rw)
{
rw.WriteLine("function showTestConfiguration() {");
rw.WriteLine(" var x = document.getElementById(\"testconfig\");");
rw.WriteLine(" if (x.style.display === \"none\") {");
rw.WriteLine(" x.style.display = \"block\";");
rw.WriteLine(" document.getElementById(\"toggleconfig\").innerHTML=\"Hide Test Configuration\";");
rw.WriteLine(" } else {");
rw.WriteLine(" x.style.display = \"none\";");
rw.WriteLine(" document.getElementById(\"toggleconfig\").innerHTML=\"Show Test Configuration\";");
rw.WriteLine(" }");
rw.WriteLine("}");
}
private void WriteToggleCanvasWithNoFailures(StreamWriter rw)
{
rw.WriteLine("function toggleCanvasWithNoFailures() {");
rw.WriteLine(" var x = document.getElementsByClassName(\"nofailures\");");
rw.WriteLine(" for(var i = 0; i < x.length; i++)");
rw.WriteLine(" {");
rw.WriteLine(" if (x[i].style.display === \"none\") {");
rw.WriteLine(" x[i].getAttribute('style');");
rw.WriteLine(" x[i].removeAttribute('style');");
rw.WriteLine(" } else {");
rw.WriteLine(" x[i].style.display = \"none\";");
rw.WriteLine(" }");
rw.WriteLine(" }");
rw.WriteLine("}");
}
private void WriteHeader(StreamWriter rw)
{
rw.WriteLine("<head>");
rw.WriteLine("<title>Unity Performance Benchmark Report</title>");
rw.WriteLine("<script src=\"Chart.bundle.js\"></script>");
rw.WriteLine("<link rel=\"stylesheet\" href=\"styles.css\">");
rw.WriteLine("<style>");
rw.WriteLine(" canvas {");
rw.WriteLine("-moz-user-select: none;");
rw.WriteLine("-webkit-user-select: none;");
rw.WriteLine("-ms-user-select: none;");
rw.WriteLine(" }");
rw.WriteLine("</style>");
WriteJavaScript(rw);
rw.WriteLine("</head>");
}
private void WriteLogoWithTitle(StreamWriter rw)
{
rw.WriteLine("<table class=\"titletable\">");
rw.WriteLine("<tr><td class=\"logocell\"><img src=\"UnityLogo.png\" alt=\"Unity\" class=\"logo\"></td></tr>");
rw.WriteLine("<tr><td class=\"titlecell\"><div class=\"title\"><h1>Performance Benchmark Report</h1></div></td></tr>");
rw.WriteLine("</table>");
}
private void WriteTestTableWithVisualizations(StreamWriter rw)
{
rw.WriteLine("<table class=\"visualizationTable\">");
foreach (var distinctTestName in distinctTestNames)
{
WriteResultForThisTest(rw, distinctTestName);
}
rw.WriteLine("</table>");
}
private void WriteResultForThisTest(StreamWriter rw, string distinctTestName)
{
var resultsForThisTest = GetResultsForThisTest(distinctTestName);
var noTestRegressions = IsNoTestFailures(resultsForThisTest);
rw.WriteLine(noTestRegressions ? "<tr class=\"nofailures\"><td><hr></td></tr>" : "<tr><td><hr></td></tr>");
rw.WriteLine(noTestRegressions ? "<tr class=\"nofailures\">" : "<tr>");
rw.WriteLine("<td class=\"testnamecell chartcell\">");
rw.WriteLine(
noTestRegressions
? "<div class=\"testname nofailures\"><h3>{0}</h3> </div>"
: "<div class=\"testname\"><h3>{0}</h3> </div>", distinctTestName);
rw.WriteLine("</td></tr>");
rw.WriteLine(noTestRegressions ? "<tr class=\"nofailures\"><td><hr></td></tr>" : "<tr><td><hr></td></tr>");
foreach (var distinctSampleGroupName in distinctSampleGroupNames)
{
WriteResultForThisSampleGroup(rw, distinctTestName, resultsForThisTest, distinctSampleGroupName, noTestRegressions);
}
}
private void WriteResultForThisSampleGroup(StreamWriter rw, string distinctTestName,
List<TestResult> resultsForThisTest,
string distinctSampleGroupName, bool noTestRegressions)
{
if (!SampleGroupHasSamples(resultsForThisTest, distinctSampleGroupName))
{
return;
}
var canvasId = GetCanvasId(distinctTestName, distinctSampleGroupName);
var noSampleGroupRegressions = !SampleGroupHasRegressions(resultsForThisTest, distinctSampleGroupName);
rw.WriteLine(
noSampleGroupRegressions
? "<tr class=\"nofailures\"><td class=\"chartcell nofailures\"><div id=\"container\" class=\"container nofailures\">"
: "<tr><td class=\"chartcell\"><div id=\"container\" class=\"container\">");
rw.WriteLine(
noSampleGroupRegressions
? "<canvas class=\"nofailures canvas\" id=\"{0}\"></canvas>"
: "<canvas class=\"canvas\" id=\"{0}\"></canvas>", canvasId);
rw.WriteLine("</div></td></tr>");
}
private bool IsNoTestFailures(List<TestResult> resultsForThisTest)
{
bool noTestFailures = true;
foreach (var distinctSampleGroupName2 in distinctSampleGroupNames)
{
if (!SampleGroupHasSamples(resultsForThisTest, distinctSampleGroupName2)) continue;
noTestFailures = noTestFailures && !SampleGroupHasRegressions(resultsForThisTest, distinctSampleGroupName2);
}
return noTestFailures;
}
private void WriteTestRunArray(StreamWriter rw)
{
var runsString = new StringBuilder();
runsString.Append("var testRuns = [");
// Write remaining values
foreach (var performanceTestRunResult in perfTestRunResults)
{
runsString.Append(string.Format("'{0}', ", performanceTestRunResult.ResultName));
}
// Remove trailing comma and space
runsString.Length = runsString.Length - 2;
runsString.Append("];");
rw.WriteLine(runsString.ToString());
}
private void WriteValueArrays(StreamWriter rw)
{
foreach (var distinctTestName in distinctTestNames)
{
var resultsForThisTest = GetResultsForThisTest(distinctTestName);
foreach (var distinctSampleGroupName in distinctSampleGroupNames)
{
if (!SampleGroupHasSamples(resultsForThisTest, distinctSampleGroupName)) continue;
var aggregatedValuesArrayName =
string.Format("{0}_{1}_Aggregated_Values", distinctTestName, distinctSampleGroupName);
var medianValuesArrayName =
string.Format("{0}_{1}_Median_Values", distinctTestName, distinctSampleGroupName);
var minValuesArrayName = string.Format("{0}_{1}_Min_Values", distinctTestName, distinctSampleGroupName);
var maxValuesArrayName = string.Format("{0}_{1}_Max_Values", distinctTestName, distinctSampleGroupName);
var avgValuesArrayName = string.Format("{0}_{1}_Average_Values", distinctTestName, distinctSampleGroupName);
var stdevValuesArrayName = string.Format("{0}_{1}_Stdev_Values", distinctTestName, distinctSampleGroupName);
var baselineValuesArrayName =
string.Format("{0}_{1}_Baseline_Values", distinctTestName, distinctSampleGroupName);
var aggregatedValuesArrayString = new StringBuilder();
aggregatedValuesArrayString.Append(string.Format("var {0} = [", aggregatedValuesArrayName));
var medianValuesArrayString = new StringBuilder();
medianValuesArrayString.Append(string.Format("var {0} = [", medianValuesArrayName));
var minValuesArrayString = new StringBuilder();
minValuesArrayString.Append(string.Format("var {0} = [", minValuesArrayName));
var maxValuesArrayString = new StringBuilder();
maxValuesArrayString.Append(string.Format("var {0} = [", maxValuesArrayName));
var avgValuesArrayString = new StringBuilder();
avgValuesArrayString.Append(string.Format("var {0} = [", avgValuesArrayName));
var stdevValuesArrayString = new StringBuilder();
stdevValuesArrayString.Append(string.Format("var {0} = [", stdevValuesArrayName));
var baselineValuesArrayString = new StringBuilder();
baselineValuesArrayString.Append(string.Format("var {0} = [", baselineValuesArrayName));
foreach (var performanceTestRunResult in perfTestRunResults)
{
if (performanceTestRunResult.TestResults.Any(r => ScrubStringForSafeForVariableUse(r.TestName).Equals(distinctTestName)))
{
var testResult =
performanceTestRunResult.TestResults.First(r =>
ScrubStringForSafeForVariableUse(r.TestName).Equals(distinctTestName));
var sgResult =
testResult.SampleGroupResults.FirstOrDefault(r =>
ScrubStringForSafeForVariableUse(r.SampleGroupName).Equals(distinctSampleGroupName));
aggregatedValuesArrayString.Append(string.Format("'{0}', ",
sgResult != null ? sgResult.AggregatedValue.ToString("F" + thisSigFig) : ""));
medianValuesArrayString.Append(string.Format("'{0}', ",
sgResult != null ? sgResult.Median.ToString("F" + thisSigFig) : ""));
minValuesArrayString.Append(string.Format("'{0}', ",
sgResult != null ? sgResult.Min.ToString("F" + thisSigFig) : ""));
maxValuesArrayString.Append(string.Format("'{0}' ,",
sgResult != null ? sgResult.Max.ToString("F" + thisSigFig) : ""));
avgValuesArrayString.Append(string.Format("'{0}' ,",
sgResult != null ? sgResult.Average.ToString("F" + thisSigFig) : ""));
stdevValuesArrayString.Append(string.Format("'{0}' ,",
sgResult != null ? sgResult.StandardDeviation.ToString("F" + thisSigFig) : ""));
baselineValuesArrayString.Append(string.Format("'{0}' ,",
sgResult != null ? sgResult.BaselineValue.ToString("F" + thisSigFig) : ""));
}
}
// Remove trailing commas from string builder
aggregatedValuesArrayString.Length = aggregatedValuesArrayString.Length - 2;
medianValuesArrayString.Length = medianValuesArrayString.Length - 2;
minValuesArrayString.Length = minValuesArrayString.Length - 2;
maxValuesArrayString.Length = maxValuesArrayString.Length - 2;
avgValuesArrayString.Length = avgValuesArrayString.Length - 2;
stdevValuesArrayString.Length = stdevValuesArrayString.Length - 2;
baselineValuesArrayString.Length = baselineValuesArrayString.Length - 2;
aggregatedValuesArrayString.Append("];");
medianValuesArrayString.Append("];");
minValuesArrayString.Append("];");
maxValuesArrayString.Append("];");
avgValuesArrayString.Append("];");
stdevValuesArrayString.Append("];");
baselineValuesArrayString.Append("];");
rw.WriteLine(aggregatedValuesArrayString.ToString());
rw.WriteLine(medianValuesArrayString.ToString());
rw.WriteLine(minValuesArrayString.ToString());
rw.WriteLine(maxValuesArrayString.ToString());
rw.WriteLine(avgValuesArrayString.ToString());
rw.WriteLine(stdevValuesArrayString.ToString());
rw.WriteLine(baselineValuesArrayString.ToString());
}
}
}
private void WriteTestConfigTable(StreamWriter rw)
{
var firstResult = perfTestRunResults.First();
rw.WriteLine("<div ng-hide=\"isCollapsed\" select-on-click>");
rw.WriteLine("<div id=\"testconfig\" class=\"testconfig\"> ");
if (firstResult.PlayerSystemInfo != null)
{
WriteClassNameWithFields<PlayerSystemInfo>(rw, firstResult.PlayerSystemInfo);
}
if (firstResult.PlayerSettings != null)
{
WriteClassNameWithFields<PlayerSettings>(rw, firstResult.PlayerSettings);
}
if (firstResult.QualitySettings != null)
{
WriteClassNameWithFields<QualitySettings>(rw, firstResult.QualitySettings);
}
if (firstResult.ScreenSettings != null)
{
WriteClassNameWithFields<ScreenSettings>(rw, firstResult.ScreenSettings);
}
if (firstResult.BuildSettings != null)
{
WriteClassNameWithFields<BuildSettings>(rw, firstResult.BuildSettings);
}
if (firstResult.EditorVersion != null)
{
WriteClassNameWithFields<EditorVersion>(rw, firstResult.EditorVersion, new[] { "DateSeconds", "RevisionValue" });
}
rw.WriteLine("</div>");
rw.WriteLine("</div>");
}
private void WriteClassNameWithFields<T>(StreamWriter rw, object instance, string[] excludedFields = null)
{
var thisObject = (T)instance;
rw.WriteLine("<div><hr></div><div class=\"typename\">{0}</div><div><hr></div>", thisObject.GetType().Name);
rw.WriteLine("<div class=\"systeminfo\">");
var sb = new StringBuilder();
foreach (var field in thisObject.GetType().GetFields())
{
if (excludedFields != null && excludedFields.Contains(field.Name))
{
continue;
}
// if field is an IEnumberable, enumerate and append each value to the sb
if (typeof(IEnumerable).IsAssignableFrom(field.FieldType) && field.FieldType != typeof(string))
{
sb.Append(string.Format("<div><div class=\"fieldname\">{0}:</div><div class=\"fieldvalue\">", field.Name));
foreach (var enumerable in (IEnumerable) field.GetValue(thisObject))
{
sb.Append(enumerable + ",");
}
if (sb.ToString().EndsWith(','))
{
// trim trailing comma
sb.Length--;
}
sb.Append("</div></div>");
}
else
{
sb.Append(string.Format("<div><div class=\"fieldname\">{0}:</div><div class=\"fieldvalue\">{1}</div></div>", field.Name, field.GetValue(thisObject)));
}
}
rw.WriteLine(sb.ToString());
rw.WriteLine("</div>");
}
private List<TestResult> GetResultsForThisTest(string distinctTestName)
{
var resultsForThisTest = new List<TestResult>();
foreach (var performanceTestRunResult in perfTestRunResults)
{
if (performanceTestRunResult.TestResults.Any(r => ScrubStringForSafeForVariableUse(r.TestName).Equals(distinctTestName)))
{
resultsForThisTest.Add(
performanceTestRunResult.TestResults.First(r => ScrubStringForSafeForVariableUse(r.TestName).Equals(distinctTestName)));
}
}
return resultsForThisTest;
}
private string GetSampleUnit(List<TestResult> resultsForThisTest, string sampleGroupName)
{
var sampleUnit = "";
if (resultsForThisTest.First().SampleGroupResults
.Any(sg => ScrubStringForSafeForVariableUse(sg.SampleGroupName) == sampleGroupName))
{
sampleUnit = resultsForThisTest.First().SampleGroupResults
.First(sg => ScrubStringForSafeForVariableUse(sg.SampleGroupName) == sampleGroupName).SampleUnit;
}
return sampleUnit;
}
private double GetThreshold(List<TestResult> resultsForThisTest, string sampleGroupName)
{
var threshold = 0.0;
if (resultsForThisTest.First().SampleGroupResults
.Any(sg => ScrubStringForSafeForVariableUse(sg.SampleGroupName) == sampleGroupName))
{
threshold = resultsForThisTest.First().SampleGroupResults
.First(sg => ScrubStringForSafeForVariableUse(sg.SampleGroupName) == sampleGroupName).Threshold;
}
return threshold;
}
private string GetAggregationType(List<TestResult> resultsForThisTest, string sampleGroupName)
{
var aggregationType = "";
if (resultsForThisTest.First().SampleGroupResults
.Any(sg => ScrubStringForSafeForVariableUse(sg.SampleGroupName) == sampleGroupName))
{
aggregationType = resultsForThisTest.First().SampleGroupResults
.First(sg => ScrubStringForSafeForVariableUse(sg.SampleGroupName) == sampleGroupName).AggregationType;
}
return aggregationType;
}
private void EnsureOrderedResults(List<PerformanceTestRunResult> results)
{
baselineResults = results.FirstOrDefault(r => r.IsBaseline);
if (baselineResults != null)
{
perfTestRunResults.Add(baselineResults);
}
var tempResults = results.Where(r => !r.IsBaseline).ToList();
tempResults.Sort((x, y) => DateTime.Compare(x.StartTime, y.StartTime));
perfTestRunResults.AddRange(tempResults);
}
private bool SampleGroupHasSamples(IEnumerable<TestResult> resultsForThisTest, string distinctSampleGroupName)
{
return resultsForThisTest.First().SampleGroupResults.Any(sg => ScrubStringForSafeForVariableUse(sg.SampleGroupName) == distinctSampleGroupName);
}
private bool SampleGroupHasRegressions(IEnumerable<TestResult> resultsForThisTest, string distinctSampleGroupName)
{
var failureInSampleGroup = resultsForThisTest.SelectMany(r => r.SampleGroupResults).Where(sg => ScrubStringForSafeForVariableUse(sg.SampleGroupName) == distinctSampleGroupName ).Any(r => r.Regressed);
return failureInSampleGroup;
}
private string GetCanvasId(string distinctTestName, string distinctSgName)
{
return string.Format("{0}_{1}", distinctTestName, distinctSgName);
}
private void SetDistinctSampleGroupNames()
{
var sgNames = new List<string>();
foreach (var performanceTestRunResult in perfTestRunResults)
{
foreach (var testResult in performanceTestRunResult.TestResults)
{
sgNames.AddRange(testResult.SampleGroupResults.Select(r => r.SampleGroupName));
}
}
var tempDistinctSgNames = sgNames.Distinct().ToArray();
for (var i = 0; i < tempDistinctSgNames.Length; i++)
{
tempDistinctSgNames[i] = ScrubStringForSafeForVariableUse(tempDistinctSgNames[i]);
}
distinctSampleGroupNames = tempDistinctSgNames.ToList();
distinctSampleGroupNames.Sort();
}
private void SetDistinctTestNames()
{
var testNames = new List<string>();
foreach (var performanceTestRunResult in perfTestRunResults)
{
testNames.AddRange(performanceTestRunResult.TestResults.Select(tr => tr.TestName));
}
var tempDistinctTestNames = testNames.Distinct().ToArray();
for (var i = 0; i < tempDistinctTestNames.Length; i++)
{
tempDistinctTestNames[i] = ScrubStringForSafeForVariableUse(tempDistinctTestNames[i]);
}
distinctTestNames = tempDistinctTestNames.ToList();
distinctTestNames.Sort();
}
}
}

Двоичные данные
UnityPerformanceBenchmarkReporter/Report/UnityLogo.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 49 KiB

Просмотреть файл

@ -0,0 +1,281 @@
.body * {
font-family: 'Arial', 'Helvetica', 'Helvetica Neue', sans-serif !important;
}
h1 {
font-size: 3vh;
overflow-wrap: break-word;
}
h3 {
font-size: 1.5vh;
overflow-wrap: break-word;
}
.titletable {
height: 80%;
width: 100%;
padding: 5px 5px 5px 5px;
}
.logocell {
padding: 10px;
background-color: black;
}
.logo {
width: 30%;
vertical-align: bottom;
max-width: 400px;
}
.titlecell {
padding-left: 10px;
background-color: black;
}
.title {
font-family: 'Arial', 'Helvetica', 'Helvetica Neue', sans-serif;
color: white;
text-align: left;
vertical-align: bottom
}
.testconfigtable {
padding: 5px 5px 5px 5px;
width: 100%;
}
.testconfig {
display: none
}
.systeminfo > div {
display: inline-block;
*display: inline; /* For IE7 */
zoom: 1; /* Trigger hasLayout */
width: 15%;
text-align: left;
vertical-align: text-top;
font-size: small;
padding: 5px 5px 5px 5px;
font-family: 'Arial', 'Helvetica', 'Helvetica Neue', sans-serif;
}
.testnamecell {
background-color: lightgray;
}
.testname {
color: black;
}
.initialbutton {
font-size: 1.25vh;
background-color: #2196F3;
border: none;
color: white;
padding: 15px 25px;
text-align: center;
cursor: pointer;
border-radius: 4px;
font-weight: bold;
}
.button {
font-size: 1.25vh;
background-color: #3e6892;
border: none;
color: white;
padding: 15px 25px;
text-align: center;
cursor: pointer;
border-radius: 4px;
font-weight: bold;
}
.button:hover {
background-color: #2196F3 !important;
}
.buttondiv {
display: inline;
}
.initialbutton:hover {
background-color: #2196F3 !important;
}
.buttonheader {
font-size: 1.25vh;
font-weight: bold;
padding: 5px 5px 5px 0px;
}
.showedfailedtests {
padding: 5px 5px 5px 0px;
}
.typename {
text-align: left;
font-weight: bold;
font-size: 1.25vh;
background-color: black;
color: white;
font-family: 'Arial', 'Helvetica', 'Helvetica Neue', sans-serif;
padding: 5px 5px 5px 10px;
}
.fieldname {
font-weight: bold;
font-size: 1.15vh;
overflow-wrap: break-word;
}
.fieldvalue {
font-size: 1vh;
padding: 5px 5px 5px 0px;
overflow-wrap: break-word;
}
.containerLabel {
display: block;
position: relative;
padding-left: 35px;
margin-bottom: 12px;
cursor: pointer;
font-size: 1.25vh;
font-weight: bold;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
font-family: 'Arial', 'Helvetica', 'Helvetica Neue', sans-serif;
}
/* Hide the browser's default checkbox */
.containerLabel input {
position: absolute;
opacity: 0;
cursor: pointer;
}
/* Create a custom checkbox */
.checkmark {
position: absolute;
top: 0;
left: 0;
height: 4vh;
width: 4vh;
max-height: 24px;
max-width: 24px;
background-color: #eee;
border-radius: 4px;
}
/* On mouse-over, add a grey background color */
.containerLabel:hover input ~ .checkmark {
background-color: #2196F3;
}
/* When the checkbox is checked, add a blue background */
.containerLabel input:checked ~ .checkmark {
background-color: #2196F3;
}
/* Create the checkmark/indicator (hidden when not checked) */
.checkmark:after {
content: "";
position: absolute;
display: none;
}
/* Show the checkmark when checked */
.containerLabel input:checked ~ .checkmark:after {
display: block;
}
/* Style the checkmark/indicator */
.containerLabel .checkmark:after {
left: 9px;
top: 5px;
width: 5px;
height: 10px;
border: solid white;
border-width: 0 3px 3px 0;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.disabledContainerLabel {
display: block;
font-weight: bold;
position: relative;
padding-left: 35px;
margin-bottom: 12px;
cursor: pointer;
font-size: medium;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
font-family: 'Arial', 'Helvetica', 'Helvetica Neue', sans-serif;
}
/* Hide the browser's default checkbox */
.disabledContainerLabel input {
position: absolute;
opacity: 0;
cursor: pointer;
}
.disabledContainerLabel .tooltiptext {
visibility: hidden;
width: 120px;
background-color: #2196F3;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px 0;
z-index: 1;
top: -5px;
left: 105%;
padding: 5px 5px 5px 5px;
font-family: 'Arial', 'Helvetica', 'Helvetica Neue', sans-serif;
}
.disabledContainerLabel:hover .tooltiptext {
visibility: visible;
}
.visualizationTable {
width: 100%;
font-family: 'Arial', 'Helvetica', 'Helvetica Neue', sans-serif;
padding: 5px 5px 5px 5px;
}
.statMethodTable {
width: 100%;
border: none;
font-family: 'Arial', 'Helvetica', 'Helvetica Neue', sans-serif;
padding: 5px 5px 5px 5px;
}
.chartcell {
padding: 5px 5px 5px 10px;
}
.container {
position: relative;
width: 99%;
height: 18vh;
font-family: 'Arial', 'Helvetica', 'Helvetica Neue', sans-serif;
}
.divider {
width: 5px;
height: auto;
display: inline-block;
}

Просмотреть файл

@ -0,0 +1,179 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Newtonsoft.Json;
using UnityPerformanceBenchmarkReporter.Entities;
namespace UnityPerformanceBenchmarkReporter
{
public class TestResultXmlParser
{
public PerformanceTestRun GetPerformanceTestRunFromXml(string resultXmlFileName)
{
ValidateInput(resultXmlFileName);
var xmlDocument = TryLoadResultXmlFile(resultXmlFileName);
var performanceTestRun = TryParseXmlToPerformanceTestRun(xmlDocument);
return performanceTestRun;
}
public void ValidateInput(string resultXmlFileName)
{
if (string.IsNullOrEmpty(resultXmlFileName))
{
throw new ArgumentNullException(resultXmlFileName, nameof(resultXmlFileName));
}
if (!File.Exists(resultXmlFileName))
{
throw new FileNotFoundException("Result file not found; {0}", resultXmlFileName);
}
}
private XDocument TryLoadResultXmlFile(string resultXmlFileName)
{
try
{
return XDocument.Load(resultXmlFileName);
}
catch (Exception e)
{
var errMsg = string.Format("Failed to load xml result file: {0}", resultXmlFileName);
WriteExceptionConsoleErrorMessage(errMsg, e);
throw;
}
}
public PerformanceTestRun TryParseXmlToPerformanceTestRun(XDocument xmlDocument)
{
var output = xmlDocument.Descendants("output").ToArray();
if (output == null || !output.Any())
{
throw new Exception("The xmlDocument passed to the TryParseXmlToPerformanceTestRun method does not have any \'ouput\' xml tags needed for correct parsing.");
}
var run = new PerformanceTestRun();
DeserializeTestResults(output, run);
DeserializeMetadata(output, run);
return run;
}
private void DeserializeTestResults(IEnumerable<XElement> output, PerformanceTestRun run)
{
foreach (var element in output)
{
foreach (var line in element.Value.Split('\n'))
{
var json = GetJsonFromHashtag("performancetestresult", line);
if (json == null)
{
continue;
}
var result = TryDeserializePerformanceTestResultJsonObject(json);
if (result != null)
{
run.Results.Add(result);
}
}
}
}
private void DeserializeMetadata(IEnumerable<XElement> output, PerformanceTestRun run)
{
foreach (var element in output)
{
foreach (var line in element.Value.Split('\n'))
{
var json = GetJsonFromHashtag("performancetestruninfo", line);
if (json == null)
{
continue;
}
var result = TryDeserializePerformanceTestRunJsonObject(json);
if (result != null)
{
run.TestSuite = result.TestSuite;
run.EditorVersion = result.EditorVersion;
run.QualitySettings = result.QualitySettings;
run.ScreenSettings = result.ScreenSettings;
run.BuildSettings = result.BuildSettings;
run.PlayerSettings = result.PlayerSettings;
run.PlayerSystemInfo = result.PlayerSystemInfo;
run.StartTime = result.StartTime;
// @TODO fix end time, does it matter for now?
run.EndTime = run.StartTime;
}
}
}
}
private PerformanceTestResult TryDeserializePerformanceTestResultJsonObject(string json)
{
PerformanceTestResult performanceTestResult = null;
try
{
performanceTestResult = JsonConvert.DeserializeObject<PerformanceTestResult>(json);
}
catch (Exception e)
{
var errMsg = string.Format("Exception thrown while deserializing json string to PerformanceTestResult: {0}", json);
WriteExceptionConsoleErrorMessage(errMsg, e);
throw;
}
return performanceTestResult;
}
private void WriteExceptionConsoleErrorMessage(string errMsg, Exception e)
{
Console.Error.WriteLine("{0}\r\nException: {1}\r\nInnerException: {2}", errMsg, e.Message,
e.InnerException.Message);
}
private PerformanceTestRun TryDeserializePerformanceTestRunJsonObject(string json)
{
PerformanceTestRun performanceTestRun = null;
try
{
performanceTestRun = JsonConvert.DeserializeObject<PerformanceTestRun>(json);
}
catch (Exception e)
{
var errMsg = string.Format("Exception thrown while deserializing json string to PerformanceTestRun: {0}", json);
WriteExceptionConsoleErrorMessage(errMsg, e);
throw;
}
return performanceTestRun;
}
public string GetJsonFromHashtag(string tag, string line)
{
if (!line.Contains($"##{tag}:")) return null;
var jsonStart = line.IndexOf('{');
var openBrackets = 0;
var stringIndex = jsonStart;
while (openBrackets > 0 || stringIndex == jsonStart)
{
var character = line[stringIndex];
switch (character)
{
case '{':
openBrackets++;
break;
case '}':
openBrackets--;
break;
}
stringIndex++;
}
var jsonEnd = stringIndex;
return line.Substring(jsonStart, jsonEnd - jsonStart);
}
}
}

Просмотреть файл

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<startupobject>UnityPerformanceBenchmarkReporter.Program</startupobject>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Tests\**" />
<EmbeddedResource Remove="Tests\**" />
<None Remove="Tests\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Mono.Options" Version="5.3.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Report\Chart.bundle.js" />
<EmbeddedResource Include="Report\styles.css" />
<EmbeddedResource Include="Report\UnityLogo.png" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ShowAllFiles>true</ShowAllFiles>
</PropertyGroup>
</Project>

Просмотреть файл

@ -0,0 +1,250 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using UnityPerformanceBenchmarkReporter;
using UnityPerformanceBenchmarkReporter.Entities;
namespace UnityPerformanceBenchmarkReporterTests
{
public class PerformanceBenchmarkTests : PerformanceBenchmarkTestsBase
{
private OptionsParser optionsParser;
private TestResultXmlParser testResultXmlParser;
private List<PerformanceTestRunResult> performanceTestRunResults;
private List<TestResult> testResults;
private List<PerformanceTestRunResult> baselinePerformanceTestRunResults;
private List<TestResult> baselineTestResults;
[SetUp]
public void Setup()
{
optionsParser = new OptionsParser();
PerformanceBenchmark = new PerformanceBenchmark();
testResultXmlParser = new TestResultXmlParser();
performanceTestRunResults = new List<PerformanceTestRunResult>();
testResults = new List<TestResult>();
baselinePerformanceTestRunResults = new List<PerformanceTestRunResult>();
baselineTestResults = new List<TestResult>();
}
[Test]
public void Verify_AddPerformanceTestRunResults()
{
// Arrange
var resultXmlFilePath = EnsureFullPath("results.xml");
var args = new[] { string.Format("--testresultsxmlsource={0}", resultXmlFilePath) };
optionsParser.ParseOptions(PerformanceBenchmark, args);
// Act
PerformanceBenchmark.AddPerformanceTestRunResults(testResultXmlParser, performanceTestRunResults, testResults, new List<TestResult>());
// Assert
Assert.IsTrue(PerformanceBenchmark.ResultFilesExist);
AssertCorrectResultXmlFilePaths(new[] { resultXmlFilePath });
Assert.NotNull(testResults);
Assert.IsTrue(testResults.Count > 0);
Assert.NotNull(performanceTestRunResults);
Assert.IsTrue(performanceTestRunResults.Count > 0);
}
[Test]
public void Verify_AddPerformanceTestRunResults_TwoResultFiles()
{
// Arrange
var resultXmlFilePath = EnsureFullPath("results.xml");
var resultFileName2 = EnsureFullPath("results2.xml");
var args = new[]
{
string.Format("--testresultsxmlsource={0}", resultXmlFilePath),
string.Format("--testresultsxmlsource={0}", resultFileName2)
};
optionsParser.ParseOptions(PerformanceBenchmark, args);
// Act
PerformanceBenchmark.AddPerformanceTestRunResults(testResultXmlParser, performanceTestRunResults, testResults, new List<TestResult>());
// Assert
Assert.IsTrue(PerformanceBenchmark.ResultFilesExist);
AssertCorrectResultXmlFilePaths(new[] { resultXmlFilePath, resultFileName2 });
Assert.NotNull(testResults);
Assert.IsTrue(testResults.Count > 0);
Assert.NotNull(performanceTestRunResults);
Assert.IsTrue(performanceTestRunResults.Count > 0);
}
[Test]
public void Verify_AddPerformanceTestRunResults_OneResultFiles_OneResultDirectory()
{
// Arrange
var resultXmlFilePath = EnsureFullPath("results.xml");
var resultsXmlDir = EnsureFullPath("Results");
var args = new[]
{
string.Format("--testresultsxmlsource={0}", resultXmlFilePath),
string.Format("--testresultsxmlsource={0}", resultsXmlDir)
};
optionsParser.ParseOptions(PerformanceBenchmark, args);
// Act
PerformanceBenchmark.AddPerformanceTestRunResults(testResultXmlParser, performanceTestRunResults, testResults, new List<TestResult>());
// Assert
Assert.IsTrue(PerformanceBenchmark.ResultFilesExist);
AssertCorrectResultsXmlDirectoryPaths(new[] { resultsXmlDir });
AssertCorrectResultXmlFilePaths(new[] { resultXmlFilePath });
Assert.NotNull(testResults);
Assert.IsTrue(testResults.Count > 0);
Assert.NotNull(performanceTestRunResults);
Assert.IsTrue(performanceTestRunResults.Count > 0);
}
[Test]
public void Verify_AddBaselinePerformanceTestRunResults()
{
// Arrange
var resultXmlFilePath = EnsureFullPath("results.xml");
var baselineXmlFilePath = EnsureFullPath("baseline.xml");
var args = new[]
{
string.Format("--testresultsxmlsource={0}", resultXmlFilePath),
string.Format("--baselinexmlsource={0}", baselineXmlFilePath)
};
optionsParser.ParseOptions(PerformanceBenchmark, args);
// Act
PerformanceBenchmark.AddBaselinePerformanceTestRunResults(testResultXmlParser, baselinePerformanceTestRunResults, baselineTestResults);
// Assert
Assert.IsTrue(PerformanceBenchmark.BaselineResultFilesExist);
AssertCorrectBaselineXmlFilePaths(new[] { baselineXmlFilePath });
AssertCorrectResultXmlFilePaths(new[] { resultXmlFilePath });
Assert.NotNull(baselineTestResults);
Assert.IsTrue(baselineTestResults.Count > 0);
Assert.NotNull(baselinePerformanceTestRunResults);
Assert.IsTrue(baselinePerformanceTestRunResults.Count > 0);
}
[Test]
public void Verify_AddBaselinePerformanceTestRunResultsDirectory()
{
// Arrange
var resultXmlFilePath = EnsureFullPath("results.xml");
var baselineXmlDir = EnsureFullPath("Baselines");
var args = new[]
{
string.Format("--testresultsxmlsource={0}", resultXmlFilePath),
string.Format("--baselinexmlsource={0}", baselineXmlDir)
};
optionsParser.ParseOptions(PerformanceBenchmark, args);
// Act
PerformanceBenchmark.AddBaselinePerformanceTestRunResults(testResultXmlParser, baselinePerformanceTestRunResults, baselineTestResults);
// Assert
Assert.IsTrue(PerformanceBenchmark.BaselineResultFilesExist);
AssertCorrectBaselineXmlDirectoryPaths(new[] { baselineXmlDir });
Assert.NotNull(baselineTestResults);
Assert.IsTrue(baselineTestResults.Count > 0);
Assert.NotNull(baselinePerformanceTestRunResults);
Assert.IsTrue(baselinePerformanceTestRunResults.Count > 0);
}
[Test]
public void Verify_AddMultipleBaselinePerformanceTestRunResultsDirectory()
{
// Arrange
var resultXmlFilePath = EnsureFullPath("results.xml");
var baselineXmlDir = EnsureFullPath("Baselines");
var baselineXmlDir2 = EnsureFullPath("Baselines2");
var args = new[]
{
string.Format("--testresultsxmlsource={0}", resultXmlFilePath),
string.Format("--baselinexmlsource={0}", baselineXmlDir),
string.Format("--baselinexmlsource={0}", baselineXmlDir2)
};
optionsParser.ParseOptions(PerformanceBenchmark, args);
// Act
PerformanceBenchmark.AddBaselinePerformanceTestRunResults(testResultXmlParser, baselinePerformanceTestRunResults,
baselineTestResults);
// Assert
Assert.IsTrue(PerformanceBenchmark.BaselineResultFilesExist);
AssertCorrectBaselineXmlDirectoryPaths(new[] {baselineXmlDir, baselineXmlDir2});
Assert.NotNull(baselineTestResults);
Assert.IsTrue(baselineTestResults.Count > 0);
Assert.NotNull(baselinePerformanceTestRunResults);
Assert.IsTrue(baselinePerformanceTestRunResults.Count > 0);
}
[Test]
public void Verify_Verify_AddBaselineAndNonBaselinePerformanceTestRunResults()
{
// Arrange
var resultXmlFilePath = EnsureFullPath("results.xml");
var baselineXmlFilePath = EnsureFullPath("baseline.xml");
var args = new[]
{
string.Format("--testresultsxmlsource={0}", resultXmlFilePath),
string.Format("--baselinexmlsource={0}", baselineXmlFilePath)
};
optionsParser.ParseOptions(PerformanceBenchmark, args);
// Act
PerformanceBenchmark.AddBaselinePerformanceTestRunResults(testResultXmlParser, baselinePerformanceTestRunResults, baselineTestResults);
PerformanceBenchmark.AddPerformanceTestRunResults(testResultXmlParser, performanceTestRunResults, testResults, new List<TestResult>());
// Assert
Assert.IsTrue(PerformanceBenchmark.ResultFilesExist);
AssertCorrectResultXmlFilePaths(new[] { resultXmlFilePath });
Assert.NotNull(testResults);
Assert.IsTrue(testResults.Count > 0);
Assert.NotNull(performanceTestRunResults);
Assert.IsTrue(performanceTestRunResults.Count > 0);
Assert.IsTrue(PerformanceBenchmark.BaselineResultFilesExist);
AssertCorrectBaselineXmlFilePaths(new[] { baselineXmlFilePath });
Assert.NotNull(baselineTestResults);
Assert.IsTrue(baselineTestResults.Count > 0);
Assert.NotNull(baselinePerformanceTestRunResults);
Assert.IsTrue(baselinePerformanceTestRunResults.Count > 0);
}
private void AssertCorrectBaselineXmlFilePaths(string[] baselineXmlFilePaths)
{
foreach (var baselineXmlFilePath in baselineXmlFilePaths)
{
Assert.IsFalse(PerformanceBenchmark.ResultXmlFilePaths.Any(f => f.Equals(baselineXmlFilePath)));
Assert.IsTrue(PerformanceBenchmark.BaselineXmlFilePaths.Any(f => f.Equals(baselineXmlFilePath)));
}
}
private void AssertCorrectBaselineXmlDirectoryPaths(string[] baselineXmlDirPaths)
{
foreach (var baselineXmlDirPath in baselineXmlDirPaths)
{
Assert.IsFalse(PerformanceBenchmark.ResultXmlDirectoryPaths.Contains(baselineXmlDirPath));
Assert.IsTrue(PerformanceBenchmark.BaselineXmlDirectoryPaths.Contains(baselineXmlDirPath));
}
}
private void AssertCorrectResultXmlFilePaths(string[] resultFileNames)
{
foreach (var resultXmlFilePath in resultFileNames)
{
Assert.IsTrue(PerformanceBenchmark.ResultXmlFilePaths.Contains(resultXmlFilePath));
Assert.IsFalse(PerformanceBenchmark.BaselineXmlFilePaths.Contains(resultXmlFilePath));
}
}
private void AssertCorrectResultsXmlDirectoryPaths(string[] resultsXmlDirPaths)
{
foreach (var resultXmlDirPath in resultsXmlDirPaths)
{
Assert.IsTrue(PerformanceBenchmark.ResultXmlDirectoryPaths.Contains(resultXmlDirPath));
Assert.IsFalse(PerformanceBenchmark.BaselineXmlDirectoryPaths.Contains(resultXmlDirPath));
}
}
}
}

Просмотреть файл

@ -0,0 +1,19 @@
using System.IO;
using UnityPerformanceBenchmarkReporter;
namespace UnityPerformanceBenchmarkReporterTests
{
public class PerformanceBenchmarkTestsBase
{
protected PerformanceBenchmark PerformanceBenchmark;
private static readonly string TestDataDirectoryName = "TestData";
protected string EnsureFullPath(string directoryOrFileName)
{
var currentDirectory = Directory.GetCurrentDirectory();
return !directoryOrFileName.StartsWith(TestDataDirectoryName)
? Path.Combine(currentDirectory, TestDataDirectoryName, directoryOrFileName)
: Path.Combine(currentDirectory, directoryOrFileName);
}
}
}

Просмотреть файл

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
using UnityPerformanceBenchmarkReporter.Entities;
using UnityPerformanceBenchmarkReporter.Report;
namespace UnityPerformanceBenchmarkReporterTests
{
public class ReportWriterTests
{
private ReportWriter tr;
[SetUp]
public void Setup()
{
}
[Test]
public void WhenResultsAreNull_ReporterWriterThrowsArgNullException()
{
// Arrange
tr = new ReportWriter();
// Act/Assert
Assert.Throws<ArgumentNullException>(() => tr.WriteReport(null));
}
[Test]
public void WhenResultsAreEmpty_ReporterWriterThrowsArgNullException()
{
// Arrange
tr = new ReportWriter();
var results = new List<PerformanceTestRunResult>();
// Act/Assert
Assert.Throws<ArgumentNullException>(() => tr.WriteReport(results));
}
}
}

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -0,0 +1,49 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="nunit" Version="3.10.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\UnityPerformanceBenchmarkReporter\UnityPerformanceBenchmarkReporter.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="baseline - Copy.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="TestData\baseline.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="TestData\baseline2.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="TestData\Baselines2\baseline2.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="TestData\Baselines\baseline.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="TestData\results.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="TestData\results2.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="TestData\Results\results.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="TestData\Results\results2.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

589
dotnet-install.ps1 поставляемый Normal file
Просмотреть файл

@ -0,0 +1,589 @@
#
# Copyright (c) .NET Foundation and contributors. All rights reserved.
# Licensed under the MIT license. See LICENSE file in the project root for full license information.
#
<#
.SYNOPSIS
Installs dotnet cli
.DESCRIPTION
Installs dotnet cli. If dotnet installation already exists in the given directory
it will update it only if the requested version differs from the one already installed.
.PARAMETER Channel
Default: LTS
Download from the Channel specified. Possible values:
- Current - most current release
- LTS - most current supported release
- 2-part version in a format A.B - represents a specific release
examples: 2.0; 1.0
- Branch name
examples: release/2.0.0; Master
.PARAMETER Version
Default: latest
Represents a build version on specific channel. Possible values:
- latest - most latest build on specific channel
- coherent - most latest coherent build on specific channel
coherent applies only to SDK downloads
- 3-part version in a format A.B.C - represents specific version of build
examples: 2.0.0-preview2-006120; 1.1.0
.PARAMETER InstallDir
Default: %LocalAppData%\Microsoft\dotnet
Path to where to install dotnet. Note that binaries will be placed directly in a given directory.
.PARAMETER Architecture
Default: <auto> - this value represents currently running OS architecture
Architecture of dotnet binaries to be installed.
Possible values are: <auto>, x64 and x86
.PARAMETER SharedRuntime
This parameter is obsolete and may be removed in a future version of this script.
The recommended alternative is '-Runtime dotnet'.
Default: false
Installs just the shared runtime bits, not the entire SDK.
This is equivalent to specifying `-Runtime dotnet`.
.PARAMETER Runtime
Installs just a shared runtime, not the entire SDK.
Possible values:
- dotnet - the Microsoft.NETCore.App shared runtime
- aspnetcore - the Microsoft.AspNetCore.App shared runtime
.PARAMETER DryRun
If set it will not perform installation but instead display what command line to use to consistently install
currently requested version of dotnet cli. In example if you specify version 'latest' it will display a link
with specific version so that this command can be used deterministicly in a build script.
It also displays binaries location if you prefer to install or download it yourself.
.PARAMETER NoPath
By default this script will set environment variable PATH for the current process to the binaries folder inside installation folder.
If set it will display binaries location but not set any environment variable.
.PARAMETER Verbose
Displays diagnostics information.
.PARAMETER AzureFeed
Default: https://dotnetcli.azureedge.net/dotnet
This parameter typically is not changed by the user.
It allows changing the URL for the Azure feed used by this installer.
.PARAMETER UncachedFeed
This parameter typically is not changed by the user.
It allows changing the URL for the Uncached feed used by this installer.
.PARAMETER FeedCredential
Used as a query string to append to the Azure feed.
It allows changing the URL to use non-public blob storage accounts.
.PARAMETER ProxyAddress
If set, the installer will use the proxy when making web requests
.PARAMETER ProxyUseDefaultCredentials
Default: false
Use default credentials, when using proxy address.
.PARAMETER SkipNonVersionedFiles
Default: false
Skips installing non-versioned files if they already exist, such as dotnet.exe.
.PARAMETER NoCdn
Disable downloading from the Azure CDN, and use the uncached feed directly.
#>
[cmdletbinding()]
param(
[string]$Channel="LTS",
[string]$Version="Latest",
[string]$InstallDir="<auto>",
[string]$Architecture="<auto>",
[ValidateSet("dotnet", "aspnetcore", IgnoreCase = $false)]
[string]$Runtime,
[Obsolete("This parameter may be removed in a future version of this script. The recommended alternative is '-Runtime dotnet'.")]
[switch]$SharedRuntime,
[switch]$DryRun,
[switch]$NoPath,
[string]$AzureFeed="https://dotnetcli.azureedge.net/dotnet",
[string]$UncachedFeed="https://dotnetcli.blob.core.windows.net/dotnet",
[string]$FeedCredential,
[string]$ProxyAddress,
[switch]$ProxyUseDefaultCredentials,
[switch]$SkipNonVersionedFiles,
[switch]$NoCdn
)
Set-StrictMode -Version Latest
$ErrorActionPreference="Stop"
$ProgressPreference="SilentlyContinue"
if ($NoCdn) {
$AzureFeed = $UncachedFeed
}
$BinFolderRelativePath=""
if ($SharedRuntime -and (-not $Runtime)) {
$Runtime = "dotnet"
}
# example path with regex: shared/1.0.0-beta-12345/somepath
$VersionRegEx="/\d+\.\d+[^/]+/"
$OverrideNonVersionedFiles = !$SkipNonVersionedFiles
function Say($str) {
Write-Host "dotnet-install: $str"
}
function Say-Verbose($str) {
Write-Verbose "dotnet-install: $str"
}
function Say-Invocation($Invocation) {
$command = $Invocation.MyCommand;
$args = (($Invocation.BoundParameters.Keys | foreach { "-$_ `"$($Invocation.BoundParameters[$_])`"" }) -join " ")
Say-Verbose "$command $args"
}
function Invoke-With-Retry([ScriptBlock]$ScriptBlock, [int]$MaxAttempts = 3, [int]$SecondsBetweenAttempts = 1) {
$Attempts = 0
while ($true) {
try {
return $ScriptBlock.Invoke()
}
catch {
$Attempts++
if ($Attempts -lt $MaxAttempts) {
Start-Sleep $SecondsBetweenAttempts
}
else {
throw
}
}
}
}
function Get-Machine-Architecture() {
Say-Invocation $MyInvocation
# possible values: AMD64, IA64, x86
return $ENV:PROCESSOR_ARCHITECTURE
}
# TODO: Architecture and CLIArchitecture should be unified
function Get-CLIArchitecture-From-Architecture([string]$Architecture) {
Say-Invocation $MyInvocation
switch ($Architecture.ToLower()) {
{ $_ -eq "<auto>" } { return Get-CLIArchitecture-From-Architecture $(Get-Machine-Architecture) }
{ ($_ -eq "amd64") -or ($_ -eq "x64") } { return "x64" }
{ $_ -eq "x86" } { return "x86" }
default { throw "Architecture not supported. If you think this is a bug, please report it at https://github.com/dotnet/cli/issues" }
}
}
function Get-Version-Info-From-Version-Text([string]$VersionText) {
Say-Invocation $MyInvocation
$Data = @($VersionText.Split([char[]]@(), [StringSplitOptions]::RemoveEmptyEntries));
$VersionInfo = @{}
$VersionInfo.CommitHash = $Data[0].Trim()
$VersionInfo.Version = $Data[1].Trim()
return $VersionInfo
}
function Load-Assembly([string] $Assembly) {
try {
Add-Type -Assembly $Assembly | Out-Null
}
catch {
# On Nano Server, Powershell Core Edition is used. Add-Type is unable to resolve base class assemblies because they are not GAC'd.
# Loading the base class assemblies is not unnecessary as the types will automatically get resolved.
}
}
function GetHTTPResponse([Uri] $Uri)
{
Invoke-With-Retry(
{
$HttpClient = $null
try {
# HttpClient is used vs Invoke-WebRequest in order to support Nano Server which doesn't support the Invoke-WebRequest cmdlet.
Load-Assembly -Assembly System.Net.Http
if(-not $ProxyAddress) {
try {
# Despite no proxy being explicitly specified, we may still be behind a default proxy
$DefaultProxy = [System.Net.WebRequest]::DefaultWebProxy;
if($DefaultProxy -and (-not $DefaultProxy.IsBypassed($Uri))) {
$ProxyAddress = $DefaultProxy.GetProxy($Uri).OriginalString
$ProxyUseDefaultCredentials = $true
}
} catch {
# Eat the exception and move forward as the above code is an attempt
# at resolving the DefaultProxy that may not have been a problem.
$ProxyAddress = $null
Say-Verbose("Exception ignored: $_.Exception.Message - moving forward...")
}
}
if($ProxyAddress) {
$HttpClientHandler = New-Object System.Net.Http.HttpClientHandler
$HttpClientHandler.Proxy = New-Object System.Net.WebProxy -Property @{Address=$ProxyAddress;UseDefaultCredentials=$ProxyUseDefaultCredentials}
$HttpClient = New-Object System.Net.Http.HttpClient -ArgumentList $HttpClientHandler
}
else {
$HttpClient = New-Object System.Net.Http.HttpClient
}
# Default timeout for HttpClient is 100s. For a 50 MB download this assumes 500 KB/s average, any less will time out
# 10 minutes allows it to work over much slower connections.
$HttpClient.Timeout = New-TimeSpan -Minutes 10
$Response = $HttpClient.GetAsync("${Uri}${FeedCredential}").Result
if (($Response -eq $null) -or (-not ($Response.IsSuccessStatusCode))) {
# The feed credential is potentially sensitive info. Do not log FeedCredential to console output.
$ErrorMsg = "Failed to download $Uri."
if ($Response -ne $null) {
$ErrorMsg += " $Response"
}
throw $ErrorMsg
}
return $Response
}
finally {
if ($HttpClient -ne $null) {
$HttpClient.Dispose()
}
}
})
}
function Get-Latest-Version-Info([string]$AzureFeed, [string]$Channel, [bool]$Coherent) {
Say-Invocation $MyInvocation
$VersionFileUrl = $null
if ($Runtime -eq "dotnet") {
$VersionFileUrl = "$UncachedFeed/Runtime/$Channel/latest.version"
}
elseif ($Runtime -eq "aspnetcore") {
$VersionFileUrl = "$UncachedFeed/aspnetcore/Runtime/$Channel/latest.version"
}
elseif (-not $Runtime) {
if ($Coherent) {
$VersionFileUrl = "$UncachedFeed/Sdk/$Channel/latest.coherent.version"
}
else {
$VersionFileUrl = "$UncachedFeed/Sdk/$Channel/latest.version"
}
}
else {
throw "Invalid value for `$Runtime"
}
$Response = GetHTTPResponse -Uri $VersionFileUrl
$StringContent = $Response.Content.ReadAsStringAsync().Result
switch ($Response.Content.Headers.ContentType) {
{ ($_ -eq "application/octet-stream") } { $VersionText = $StringContent }
{ ($_ -eq "text/plain") } { $VersionText = $StringContent }
{ ($_ -eq "text/plain; charset=UTF-8") } { $VersionText = $StringContent }
default { throw "``$Response.Content.Headers.ContentType`` is an unknown .version file content type." }
}
$VersionInfo = Get-Version-Info-From-Version-Text $VersionText
return $VersionInfo
}
function Get-Specific-Version-From-Version([string]$AzureFeed, [string]$Channel, [string]$Version) {
Say-Invocation $MyInvocation
switch ($Version.ToLower()) {
{ $_ -eq "latest" } {
$LatestVersionInfo = Get-Latest-Version-Info -AzureFeed $AzureFeed -Channel $Channel -Coherent $False
return $LatestVersionInfo.Version
}
{ $_ -eq "coherent" } {
$LatestVersionInfo = Get-Latest-Version-Info -AzureFeed $AzureFeed -Channel $Channel -Coherent $True
return $LatestVersionInfo.Version
}
default { return $Version }
}
}
function Get-Download-Link([string]$AzureFeed, [string]$SpecificVersion, [string]$CLIArchitecture) {
Say-Invocation $MyInvocation
if ($Runtime -eq "dotnet") {
$PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-runtime-$SpecificVersion-win-$CLIArchitecture.zip"
}
elseif ($Runtime -eq "aspnetcore") {
$PayloadURL = "$AzureFeed/aspnetcore/Runtime/$SpecificVersion/aspnetcore-runtime-$SpecificVersion-win-$CLIArchitecture.zip"
}
elseif (-not $Runtime) {
$PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-sdk-$SpecificVersion-win-$CLIArchitecture.zip"
}
else {
throw "Invalid value for `$Runtime"
}
Say-Verbose "Constructed primary payload URL: $PayloadURL"
return $PayloadURL
}
function Get-LegacyDownload-Link([string]$AzureFeed, [string]$SpecificVersion, [string]$CLIArchitecture) {
Say-Invocation $MyInvocation
if (-not $Runtime) {
$PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-dev-win-$CLIArchitecture.$SpecificVersion.zip"
}
elseif ($Runtime -eq "dotnet") {
$PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-win-$CLIArchitecture.$SpecificVersion.zip"
}
else {
return $null
}
Say-Verbose "Constructed legacy payload URL: $PayloadURL"
return $PayloadURL
}
function Get-User-Share-Path() {
Say-Invocation $MyInvocation
$InstallRoot = $env:DOTNET_INSTALL_DIR
if (!$InstallRoot) {
$InstallRoot = "$env:LocalAppData\Microsoft\dotnet"
}
return $InstallRoot
}
function Resolve-Installation-Path([string]$InstallDir) {
Say-Invocation $MyInvocation
if ($InstallDir -eq "<auto>") {
return Get-User-Share-Path
}
return $InstallDir
}
function Get-Version-Info-From-Version-File([string]$InstallRoot, [string]$RelativePathToVersionFile) {
Say-Invocation $MyInvocation
$VersionFile = Join-Path -Path $InstallRoot -ChildPath $RelativePathToVersionFile
Say-Verbose "Local version file: $VersionFile"
if (Test-Path $VersionFile) {
$VersionText = cat $VersionFile
Say-Verbose "Local version file text: $VersionText"
return Get-Version-Info-From-Version-Text $VersionText
}
Say-Verbose "Local version file not found."
return $null
}
function Is-Dotnet-Package-Installed([string]$InstallRoot, [string]$RelativePathToPackage, [string]$SpecificVersion) {
Say-Invocation $MyInvocation
$DotnetPackagePath = Join-Path -Path $InstallRoot -ChildPath $RelativePathToPackage | Join-Path -ChildPath $SpecificVersion
Say-Verbose "Is-Dotnet-Package-Installed: Path to a package: $DotnetPackagePath"
return Test-Path $DotnetPackagePath -PathType Container
}
function Get-Absolute-Path([string]$RelativeOrAbsolutePath) {
# Too much spam
# Say-Invocation $MyInvocation
return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($RelativeOrAbsolutePath)
}
function Get-Path-Prefix-With-Version($path) {
$match = [regex]::match($path, $VersionRegEx)
if ($match.Success) {
return $entry.FullName.Substring(0, $match.Index + $match.Length)
}
return $null
}
function Get-List-Of-Directories-And-Versions-To-Unpack-From-Dotnet-Package([System.IO.Compression.ZipArchive]$Zip, [string]$OutPath) {
Say-Invocation $MyInvocation
$ret = @()
foreach ($entry in $Zip.Entries) {
$dir = Get-Path-Prefix-With-Version $entry.FullName
if ($dir -ne $null) {
$path = Get-Absolute-Path $(Join-Path -Path $OutPath -ChildPath $dir)
if (-Not (Test-Path $path -PathType Container)) {
$ret += $dir
}
}
}
$ret = $ret | Sort-Object | Get-Unique
$values = ($ret | foreach { "$_" }) -join ";"
Say-Verbose "Directories to unpack: $values"
return $ret
}
# Example zip content and extraction algorithm:
# Rule: files if extracted are always being extracted to the same relative path locally
# .\
# a.exe # file does not exist locally, extract
# b.dll # file exists locally, override only if $OverrideFiles set
# aaa\ # same rules as for files
# ...
# abc\1.0.0\ # directory contains version and exists locally
# ... # do not extract content under versioned part
# abc\asd\ # same rules as for files
# ...
# def\ghi\1.0.1\ # directory contains version and does not exist locally
# ... # extract content
function Extract-Dotnet-Package([string]$ZipPath, [string]$OutPath) {
Say-Invocation $MyInvocation
Load-Assembly -Assembly System.IO.Compression.FileSystem
Set-Variable -Name Zip
try {
$Zip = [System.IO.Compression.ZipFile]::OpenRead($ZipPath)
$DirectoriesToUnpack = Get-List-Of-Directories-And-Versions-To-Unpack-From-Dotnet-Package -Zip $Zip -OutPath $OutPath
foreach ($entry in $Zip.Entries) {
$PathWithVersion = Get-Path-Prefix-With-Version $entry.FullName
if (($PathWithVersion -eq $null) -Or ($DirectoriesToUnpack -contains $PathWithVersion)) {
$DestinationPath = Get-Absolute-Path $(Join-Path -Path $OutPath -ChildPath $entry.FullName)
$DestinationDir = Split-Path -Parent $DestinationPath
$OverrideFiles=$OverrideNonVersionedFiles -Or (-Not (Test-Path $DestinationPath))
if ((-Not $DestinationPath.EndsWith("\")) -And $OverrideFiles) {
New-Item -ItemType Directory -Force -Path $DestinationDir | Out-Null
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $DestinationPath, $OverrideNonVersionedFiles)
}
}
}
}
finally {
if ($Zip -ne $null) {
$Zip.Dispose()
}
}
}
function DownloadFile([Uri]$Uri, [string]$OutPath) {
if ($Uri -notlike "http*") {
Say-Verbose "Copying file from $Uri to $OutPath"
Copy-Item $Uri.AbsolutePath $OutPath
return
}
$Stream = $null
try {
$Response = GetHTTPResponse -Uri $Uri
$Stream = $Response.Content.ReadAsStreamAsync().Result
$File = [System.IO.File]::Create($OutPath)
$Stream.CopyTo($File)
$File.Close()
}
finally {
if ($Stream -ne $null) {
$Stream.Dispose()
}
}
}
function Prepend-Sdk-InstallRoot-To-Path([string]$InstallRoot, [string]$BinFolderRelativePath) {
$BinPath = Get-Absolute-Path $(Join-Path -Path $InstallRoot -ChildPath $BinFolderRelativePath)
if (-Not $NoPath) {
Say "Adding to current process PATH: `"$BinPath`". Note: This change will not be visible if PowerShell was run as a child process."
$env:path = "$BinPath;" + $env:path
}
else {
Say "Binaries of dotnet can be found in $BinPath"
}
}
$CLIArchitecture = Get-CLIArchitecture-From-Architecture $Architecture
$SpecificVersion = Get-Specific-Version-From-Version -AzureFeed $AzureFeed -Channel $Channel -Version $Version
$DownloadLink = Get-Download-Link -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture
$LegacyDownloadLink = Get-LegacyDownload-Link -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture
if ($DryRun) {
Say "Payload URLs:"
Say "Primary - $DownloadLink"
if ($LegacyDownloadLink) {
Say "Legacy - $LegacyDownloadLink"
}
Say "Repeatable invocation: .\$($MyInvocation.Line)"
exit 0
}
$InstallRoot = Resolve-Installation-Path $InstallDir
Say-Verbose "InstallRoot: $InstallRoot"
if ($Runtime -eq "dotnet") {
$assetName = ".NET Core Runtime"
$dotnetPackageRelativePath = "shared\Microsoft.NETCore.App"
}
elseif ($Runtime -eq "aspnetcore") {
$assetName = "ASP.NET Core Runtime"
$dotnetPackageRelativePath = "shared\Microsoft.AspNetCore.App"
}
elseif (-not $Runtime) {
$assetName = ".NET Core SDK"
$dotnetPackageRelativePath = "sdk"
}
else {
throw "Invalid value for `$Runtime"
}
# Check if the SDK version is already installed.
$isAssetInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $SpecificVersion
if ($isAssetInstalled) {
Say "$assetName version $SpecificVersion is already installed."
Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot -BinFolderRelativePath $BinFolderRelativePath
exit 0
}
New-Item -ItemType Directory -Force -Path $InstallRoot | Out-Null
$installDrive = $((Get-Item $InstallRoot).PSDrive.Name);
$free = Get-CimInstance -Class win32_logicaldisk | where Deviceid -eq "${installDrive}:"
if ($free.Freespace / 1MB -le 100 ) {
Say "There is not enough disk space on drive ${installDrive}:"
exit 0
}
$ZipPath = [System.IO.Path]::combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName())
Say-Verbose "Zip path: $ZipPath"
Say "Downloading link: $DownloadLink"
try {
DownloadFile -Uri $DownloadLink -OutPath $ZipPath
}
catch {
Say "Cannot download: $DownloadLink"
if ($LegacyDownloadLink) {
$DownloadLink = $LegacyDownloadLink
$ZipPath = [System.IO.Path]::combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName())
Say-Verbose "Legacy zip path: $ZipPath"
Say "Downloading legacy link: $DownloadLink"
DownloadFile -Uri $DownloadLink -OutPath $ZipPath
}
else {
throw "Could not download $assetName version $SpecificVersion"
}
}
Say "Extracting zip from $DownloadLink"
Extract-Dotnet-Package -ZipPath $ZipPath -OutPath $InstallRoot
# Check if the SDK version is now installed; if not, fail the installation.
$isAssetInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $SpecificVersion
if (!$isAssetInstalled) {
throw "$assetName version $SpecificVersion failed to install with an unknown error."
}
Remove-Item $ZipPath
Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot -BinFolderRelativePath $BinFolderRelativePath
Say "Installation finished"
exit 0