diff --git a/src/Perfolizer/Perfolizer.Demo/QuantileEstimatorDemo.cs b/src/Perfolizer/Perfolizer.Demo/QuantileEstimatorDemo.cs index f7a51a9b..48610781 100644 --- a/src/Perfolizer/Perfolizer.Demo/QuantileEstimatorDemo.cs +++ b/src/Perfolizer/Perfolizer.Demo/QuantileEstimatorDemo.cs @@ -28,7 +28,7 @@ public void Run() // * Akinshin, Andrey. "Trimmed Harrell-Davis quantile estimator based on the highest density interval of the given width." // arXiv preprint arXiv:2111.11776 (2021). // https://arxiv.org/abs/2111.11776 - TrimmedHarrellDavisQuantileEstimator.SqrtInstance, + TrimmedHarrellDavisQuantileEstimator.Sqrt, // The Sfakianakis-Verginis quantile estimators // They use a weighted sum of all sample elements, weights are assigned according to the Binomial distribution // * Sfakianakis, Michael E., and Dimitris G. Verginis. "A new family of nonparametric quantile estimators." diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/EdPeltTests.cs b/src/Perfolizer/Perfolizer.SimulationTests/Cpd/EdPeltTests.cs similarity index 98% rename from src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/EdPeltTests.cs rename to src/Perfolizer/Perfolizer.SimulationTests/Cpd/EdPeltTests.cs index fe17bf00..5a2e22be 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/EdPeltTests.cs +++ b/src/Perfolizer/Perfolizer.SimulationTests/Cpd/EdPeltTests.cs @@ -1,8 +1,8 @@ using JetBrains.Annotations; using Perfolizer.Mathematics.Cpd; -using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; -namespace Perfolizer.Tests.Mathematics.Cpd; +namespace Perfolizer.SimulationTests.Cpd; public class EdPeltTests { diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/RqqPeltTests.cs b/src/Perfolizer/Perfolizer.SimulationTests/Cpd/RqqPeltTests.cs similarity index 85% rename from src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/RqqPeltTests.cs rename to src/Perfolizer/Perfolizer.SimulationTests/Cpd/RqqPeltTests.cs index ecf4495c..694d6161 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/RqqPeltTests.cs +++ b/src/Perfolizer/Perfolizer.SimulationTests/Cpd/RqqPeltTests.cs @@ -1,21 +1,16 @@ using JetBrains.Annotations; using Perfolizer.Mathematics.Cpd; using Perfolizer.Mathematics.Distributions.ContinuousDistributions; -using Perfolizer.Tests.Common; -using Perfolizer.Tests.Mathematics.Cpd.TestDataSets; +using Perfolizer.SimulationTests.Cpd.TestDataSets; +using Perfolizer.Tests.Infra; +using Xunit.Abstractions; -namespace Perfolizer.Tests.Mathematics.Cpd; +namespace Perfolizer.SimulationTests.Cpd; -public class RqqPeltTests +public class RqqPeltTests(ITestOutputHelper output) { - private readonly ITestOutputHelper output; private readonly PeltChangePointDetector detector = RqqPeltChangePointDetector.Instance; - public RqqPeltTests(ITestOutputHelper output) - { - this.output = output; - } - [AssertionMethod] private void Check(double[] data, int minDistance, int[] expectedChangePoints) { @@ -31,13 +26,13 @@ private void Check(double[] data, int minDistance, int[] expectedChangePoints) public void Tie01() => Check(new double[] { 0, 0, 0, 0, 0, 100, 100, 100, 100 - }, 1, new[] {4}); + }, 1, new[] { 4 }); [Fact] public void Tie02() => Check(new double[] { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2 - }, 1, new[] {5, 11}); + }, 1, new[] { 5, 11 }); [Fact] public void Check_WhenTwoMinDistanceLessThanDataLength_ReturnEmptyArray() => Check(new double[] @@ -47,7 +42,7 @@ public void Check_WhenTwoMinDistanceLessThanDataLength_ReturnEmptyArray() => Che [Fact] [Trait(TraitConstants.Category, TraitConstants.Slow)] - public void ArithmeticProgression() => Check(Enumerable.Range(1, 500).Select(it => (double) it).ToArray(), 10, new[] + public void ArithmeticProgression() => Check(Enumerable.Range(1, 500).Select(it => (double)it).ToArray(), 10, new[] { 9, 19, 29, 39, 49, 59, 69, 79, 89, 99, 109, 119, 129, 139, 149, 159, 169, 179, 189, 199, 209, 219, 229, 239, 249, 259, 269, 279, 289, 299, 309, 319, 329, 339, 349, 359, 369, 379, 389, 399, 409, 419, 429, 439, 449, @@ -91,11 +86,13 @@ public void GaussianStdDevProgression(int error, string stdDevValuesString) var indexes = detector.GetChangePointIndexes(data.ToArray(), 20); Check100(stdDevValues.Length, error, indexes); } - - private static readonly IReadOnlyList ReferenceDataSet = CpdReferenceDataSet.Generate(new Random(42), 1); + + private static readonly IReadOnlyList ReferenceDataSet = + CpdReferenceDataSet.Generate(new Random(42), 1); [UsedImplicitly] - public static TheoryData ReferenceDataSetNames = TheoryDataHelper.Create(ReferenceDataSet.Select(d => d.Name)); + public static TheoryData ReferenceDataSetNames = + TheoryDataHelper.Create(ReferenceDataSet.Select(d => d.Name)); [Theory] [MemberData(nameof(ReferenceDataSetNames))] diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/TestDataSets/CpdBinomialMeanProgressionDataSet.cs b/src/Perfolizer/Perfolizer.SimulationTests/Cpd/TestDataSets/CpdBinomialMeanProgressionDataSet.cs similarity index 97% rename from src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/TestDataSets/CpdBinomialMeanProgressionDataSet.cs rename to src/Perfolizer/Perfolizer.SimulationTests/Cpd/TestDataSets/CpdBinomialMeanProgressionDataSet.cs index bc5601e0..66c49f42 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/TestDataSets/CpdBinomialMeanProgressionDataSet.cs +++ b/src/Perfolizer/Perfolizer.SimulationTests/Cpd/TestDataSets/CpdBinomialMeanProgressionDataSet.cs @@ -1,7 +1,7 @@ using Perfolizer.Mathematics.Distributions.ContinuousDistributions; using Perfolizer.Mathematics.Randomization; -namespace Perfolizer.Tests.Mathematics.Cpd.TestDataSets; +namespace Perfolizer.SimulationTests.Cpd.TestDataSets; public static class CpdBinomialMeanProgressionDataSet { diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/TestDataSets/CpdFrechetLocationProgressionDataSet.cs b/src/Perfolizer/Perfolizer.SimulationTests/Cpd/TestDataSets/CpdFrechetLocationProgressionDataSet.cs similarity index 96% rename from src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/TestDataSets/CpdFrechetLocationProgressionDataSet.cs rename to src/Perfolizer/Perfolizer.SimulationTests/Cpd/TestDataSets/CpdFrechetLocationProgressionDataSet.cs index 28610e4d..06f7330b 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/TestDataSets/CpdFrechetLocationProgressionDataSet.cs +++ b/src/Perfolizer/Perfolizer.SimulationTests/Cpd/TestDataSets/CpdFrechetLocationProgressionDataSet.cs @@ -1,6 +1,6 @@ using Perfolizer.Mathematics.Distributions.ContinuousDistributions; -namespace Perfolizer.Tests.Mathematics.Cpd.TestDataSets; +namespace Perfolizer.SimulationTests.Cpd.TestDataSets; public static class CpdFrechetLocationProgressionDataSet { diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/TestDataSets/CpdGaussianMeanProgressionDataSet.cs b/src/Perfolizer/Perfolizer.SimulationTests/Cpd/TestDataSets/CpdGaussianMeanProgressionDataSet.cs similarity index 96% rename from src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/TestDataSets/CpdGaussianMeanProgressionDataSet.cs rename to src/Perfolizer/Perfolizer.SimulationTests/Cpd/TestDataSets/CpdGaussianMeanProgressionDataSet.cs index b15dc5d4..eda5cbb0 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/TestDataSets/CpdGaussianMeanProgressionDataSet.cs +++ b/src/Perfolizer/Perfolizer.SimulationTests/Cpd/TestDataSets/CpdGaussianMeanProgressionDataSet.cs @@ -1,6 +1,6 @@ using Perfolizer.Mathematics.Distributions.ContinuousDistributions; -namespace Perfolizer.Tests.Mathematics.Cpd.TestDataSets; +namespace Perfolizer.SimulationTests.Cpd.TestDataSets; public static class CpdGaussianMeanProgressionDataSet { diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/TestDataSets/CpdGumbelLocationProgressionDataSet.cs b/src/Perfolizer/Perfolizer.SimulationTests/Cpd/TestDataSets/CpdGumbelLocationProgressionDataSet.cs similarity index 97% rename from src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/TestDataSets/CpdGumbelLocationProgressionDataSet.cs rename to src/Perfolizer/Perfolizer.SimulationTests/Cpd/TestDataSets/CpdGumbelLocationProgressionDataSet.cs index 4c0766f1..7be659a8 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/TestDataSets/CpdGumbelLocationProgressionDataSet.cs +++ b/src/Perfolizer/Perfolizer.SimulationTests/Cpd/TestDataSets/CpdGumbelLocationProgressionDataSet.cs @@ -1,6 +1,6 @@ using Perfolizer.Mathematics.Distributions.ContinuousDistributions; -namespace Perfolizer.Tests.Mathematics.Cpd.TestDataSets; +namespace Perfolizer.SimulationTests.Cpd.TestDataSets; public static class CpdGumbelLocationProgressionDataSet { diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/TestDataSets/CpdRealDataSet.cs b/src/Perfolizer/Perfolizer.SimulationTests/Cpd/TestDataSets/CpdRealDataSet.cs similarity index 98% rename from src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/TestDataSets/CpdRealDataSet.cs rename to src/Perfolizer/Perfolizer.SimulationTests/Cpd/TestDataSets/CpdRealDataSet.cs index be0ffa37..4c1a81f7 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/TestDataSets/CpdRealDataSet.cs +++ b/src/Perfolizer/Perfolizer.SimulationTests/Cpd/TestDataSets/CpdRealDataSet.cs @@ -1,4 +1,4 @@ -namespace Perfolizer.Tests.Mathematics.Cpd.TestDataSets; +namespace Perfolizer.SimulationTests.Cpd.TestDataSets; public static class CpdRealDataSet { diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/TestDataSets/CpdReferenceDataSet.cs b/src/Perfolizer/Perfolizer.SimulationTests/Cpd/TestDataSets/CpdReferenceDataSet.cs similarity index 93% rename from src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/TestDataSets/CpdReferenceDataSet.cs rename to src/Perfolizer/Perfolizer.SimulationTests/Cpd/TestDataSets/CpdReferenceDataSet.cs index 73011323..e168aeb7 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/TestDataSets/CpdReferenceDataSet.cs +++ b/src/Perfolizer/Perfolizer.SimulationTests/Cpd/TestDataSets/CpdReferenceDataSet.cs @@ -1,4 +1,4 @@ -namespace Perfolizer.Tests.Mathematics.Cpd.TestDataSets; +namespace Perfolizer.SimulationTests.Cpd.TestDataSets; public static class CpdReferenceDataSet { diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/TestDataSets/CpdTestData.cs b/src/Perfolizer/Perfolizer.SimulationTests/Cpd/TestDataSets/CpdTestData.cs similarity index 97% rename from src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/TestDataSets/CpdTestData.cs rename to src/Perfolizer/Perfolizer.SimulationTests/Cpd/TestDataSets/CpdTestData.cs index fffa4af2..ddf38b3f 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/TestDataSets/CpdTestData.cs +++ b/src/Perfolizer/Perfolizer.SimulationTests/Cpd/TestDataSets/CpdTestData.cs @@ -1,4 +1,4 @@ -namespace Perfolizer.Tests.Mathematics.Cpd.TestDataSets; +namespace Perfolizer.SimulationTests.Cpd.TestDataSets; public class CpdTestData { diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/TestDataSets/CpdTestDataVerification.cs b/src/Perfolizer/Perfolizer.SimulationTests/Cpd/TestDataSets/CpdTestDataVerification.cs similarity index 98% rename from src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/TestDataSets/CpdTestDataVerification.cs rename to src/Perfolizer/Perfolizer.SimulationTests/Cpd/TestDataSets/CpdTestDataVerification.cs index 86e6b96d..44bc0acb 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Cpd/TestDataSets/CpdTestDataVerification.cs +++ b/src/Perfolizer/Perfolizer.SimulationTests/Cpd/TestDataSets/CpdTestDataVerification.cs @@ -1,6 +1,6 @@ using System.Text; -namespace Perfolizer.Tests.Mathematics.Cpd.TestDataSets; +namespace Perfolizer.SimulationTests.Cpd.TestDataSets; public class CpdTestDataVerification { diff --git a/src/Perfolizer/Perfolizer.SimulationTests/GlobalUsings.cs b/src/Perfolizer/Perfolizer.SimulationTests/GlobalUsings.cs new file mode 100644 index 00000000..3a1918fb --- /dev/null +++ b/src/Perfolizer/Perfolizer.SimulationTests/GlobalUsings.cs @@ -0,0 +1,3 @@ +global using System; +global using Xunit; +global using Xunit.Abstractions; \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer.SimulationTests/Perfolizer.SimulationTests.csproj b/src/Perfolizer/Perfolizer.SimulationTests/Perfolizer.SimulationTests.csproj new file mode 100644 index 00000000..1388dceb --- /dev/null +++ b/src/Perfolizer/Perfolizer.SimulationTests/Perfolizer.SimulationTests.csproj @@ -0,0 +1,31 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/ExtendedP2QuantileEstimatorTests.cs b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/ExtendedP2QuantileEstimatorTests.cs similarity index 96% rename from src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/ExtendedP2QuantileEstimatorTests.cs rename to src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/ExtendedP2QuantileEstimatorTests.cs index 6940b007..507e4f7e 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/ExtendedP2QuantileEstimatorTests.cs +++ b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/ExtendedP2QuantileEstimatorTests.cs @@ -1,12 +1,12 @@ using JetBrains.Annotations; -using Perfolizer.Common; using Perfolizer.Extensions; using Perfolizer.Mathematics.Common; using Perfolizer.Mathematics.QuantileEstimators; using Perfolizer.Mathematics.ScaleEstimators; -using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; +using Xunit.Abstractions; -namespace Perfolizer.Tests.Mathematics.QuantileEstimators; +namespace Perfolizer.SimulationTests.QuantileEstimators; public class ExtendedP2QuantileEstimatorTests { diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/GreenwaldKhannaQuantileEstimatorTests.cs b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/GreenwaldKhannaQuantileEstimatorTests.cs similarity index 97% rename from src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/GreenwaldKhannaQuantileEstimatorTests.cs rename to src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/GreenwaldKhannaQuantileEstimatorTests.cs index 23d8ae03..eb50dc03 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/GreenwaldKhannaQuantileEstimatorTests.cs +++ b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/GreenwaldKhannaQuantileEstimatorTests.cs @@ -1,8 +1,8 @@ -using Perfolizer.Common; using Perfolizer.Mathematics.Common; using Perfolizer.Mathematics.QuantileEstimators; +using Xunit.Abstractions; -namespace Perfolizer.Tests.Mathematics.QuantileEstimators; +namespace Perfolizer.SimulationTests.QuantileEstimators; public class GreenwaldKhannaQuantileEstimatorTests { diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/HarrellDavisQuantileEstimatorTests.cs b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/HarrellDavisQuantileEstimatorTests.cs similarity index 99% rename from src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/HarrellDavisQuantileEstimatorTests.cs rename to src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/HarrellDavisQuantileEstimatorTests.cs index d1d67e6a..6071fd21 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/HarrellDavisQuantileEstimatorTests.cs +++ b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/HarrellDavisQuantileEstimatorTests.cs @@ -1,14 +1,14 @@ using JetBrains.Annotations; using Perfolizer.Collections; -using Perfolizer.Common; using Perfolizer.Mathematics.Common; using Perfolizer.Mathematics.Distributions.ContinuousDistributions; using Perfolizer.Mathematics.QuantileEstimators; using Perfolizer.Mathematics.Randomization; using Perfolizer.Mathematics.Sequences; -using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; +using Xunit.Abstractions; -namespace Perfolizer.Tests.Mathematics.QuantileEstimators; +namespace Perfolizer.SimulationTests.QuantileEstimators; public class HarrellDavisQuantileEstimatorTests : QuantileEstimatorTests { diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/HyndmanFanQuantileEstimatorTests.cs b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/HyndmanFanQuantileEstimatorTests.cs similarity index 98% rename from src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/HyndmanFanQuantileEstimatorTests.cs rename to src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/HyndmanFanQuantileEstimatorTests.cs index 49eef0ba..6aa87c59 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/HyndmanFanQuantileEstimatorTests.cs +++ b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/HyndmanFanQuantileEstimatorTests.cs @@ -3,9 +3,10 @@ using Perfolizer.Mathematics.Common; using Perfolizer.Mathematics.Distributions.ContinuousDistributions; using Perfolizer.Mathematics.QuantileEstimators; -using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; +using Xunit.Abstractions; -namespace Perfolizer.Tests.Mathematics.QuantileEstimators; +namespace Perfolizer.SimulationTests.QuantileEstimators; public class HyndmanFanQuantileEstimatorTests : QuantileEstimatorTests { diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/MovingP2QuantileEstimatorTests.cs b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/MovingP2QuantileEstimatorTests.cs similarity index 95% rename from src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/MovingP2QuantileEstimatorTests.cs rename to src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/MovingP2QuantileEstimatorTests.cs index 12550351..63157b01 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/MovingP2QuantileEstimatorTests.cs +++ b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/MovingP2QuantileEstimatorTests.cs @@ -1,10 +1,10 @@ using System.Text; -using Perfolizer.Common; using Perfolizer.Extensions; using Perfolizer.Mathematics.QuantileEstimators; -using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; +using Xunit.Abstractions; -namespace Perfolizer.Tests.Mathematics.QuantileEstimators; +namespace Perfolizer.SimulationTests.QuantileEstimators; public class MovingP2QuantileEstimatorTests { diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/MovingQuantileEstimatorTestsBase.cs b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/MovingQuantileEstimatorTestsBase.cs similarity index 98% rename from src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/MovingQuantileEstimatorTestsBase.cs rename to src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/MovingQuantileEstimatorTestsBase.cs index 3ed3bae6..e4ca0ed6 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/MovingQuantileEstimatorTestsBase.cs +++ b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/MovingQuantileEstimatorTestsBase.cs @@ -1,9 +1,9 @@ using System.Text; using Perfolizer.Mathematics.Common; using Perfolizer.Mathematics.QuantileEstimators; -using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; -namespace Perfolizer.Tests.Mathematics.QuantileEstimators; +namespace Perfolizer.SimulationTests.QuantileEstimators; public abstract class MovingQuantileEstimatorTestsBase { diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/P2QuantileEstimatorTests.cs b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/P2QuantileEstimatorTests.cs similarity index 98% rename from src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/P2QuantileEstimatorTests.cs rename to src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/P2QuantileEstimatorTests.cs index 5cda3081..2bd04bfd 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/P2QuantileEstimatorTests.cs +++ b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/P2QuantileEstimatorTests.cs @@ -1,13 +1,12 @@ using JetBrains.Annotations; -using Perfolizer.Common; using Perfolizer.Mathematics.Common; using Perfolizer.Mathematics.Distributions.ContinuousDistributions; using Perfolizer.Mathematics.QuantileEstimators; using Perfolizer.Mathematics.Randomization; using Perfolizer.Mathematics.ScaleEstimators; -using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; -namespace Perfolizer.Tests.Mathematics.QuantileEstimators; +namespace Perfolizer.SimulationTests.QuantileEstimators; public class P2QuantileEstimatorTests { diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/PartitioningHeapsMovingQuantileEstimatorTests.cs b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/PartitioningHeapsMovingQuantileEstimatorTests.cs similarity index 95% rename from src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/PartitioningHeapsMovingQuantileEstimatorTests.cs rename to src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/PartitioningHeapsMovingQuantileEstimatorTests.cs index d1bf59c9..5201207d 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/PartitioningHeapsMovingQuantileEstimatorTests.cs +++ b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/PartitioningHeapsMovingQuantileEstimatorTests.cs @@ -1,12 +1,11 @@ using JetBrains.Annotations; using Perfolizer.Collections; -using Perfolizer.Common; using Perfolizer.Mathematics.Common; using Perfolizer.Mathematics.QuantileEstimators; using Perfolizer.Mathematics.Randomization; -using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; -namespace Perfolizer.Tests.Mathematics.QuantileEstimators; +namespace Perfolizer.SimulationTests.QuantileEstimators; [UsedImplicitly] public class PartitioningHeapsMovingQuantileEstimatorTests : MovingQuantileEstimatorTestsBase diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/QuantileEstimatorTests.cs b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/QuantileEstimatorTests.cs similarity index 95% rename from src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/QuantileEstimatorTests.cs rename to src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/QuantileEstimatorTests.cs index ed325f59..27af68e2 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/QuantileEstimatorTests.cs +++ b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/QuantileEstimatorTests.cs @@ -1,9 +1,8 @@ -using Perfolizer.Common; using Perfolizer.Mathematics.Common; using Perfolizer.Mathematics.QuantileEstimators; -using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; -namespace Perfolizer.Tests.Mathematics.QuantileEstimators; +namespace Perfolizer.SimulationTests.QuantileEstimators; public abstract class QuantileEstimatorTests { diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/QuartilesTests.cs b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/QuartilesTests.cs similarity index 95% rename from src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/QuartilesTests.cs rename to src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/QuartilesTests.cs index 50d787f5..fba71597 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/QuartilesTests.cs +++ b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/QuartilesTests.cs @@ -1,8 +1,7 @@ using System.Diagnostics.CodeAnalysis; -using Perfolizer.Common; using Perfolizer.Mathematics.QuantileEstimators; -namespace Perfolizer.Tests.Mathematics.QuantileEstimators; +namespace Perfolizer.SimulationTests.QuantileEstimators; public class QuartilesTests { diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/SampleQuantileEstimatorTests.cs b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/SampleQuantileEstimatorTests.cs similarity index 99% rename from src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/SampleQuantileEstimatorTests.cs rename to src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/SampleQuantileEstimatorTests.cs index c6c8f779..08b002e5 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/SampleQuantileEstimatorTests.cs +++ b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/SampleQuantileEstimatorTests.cs @@ -1,9 +1,9 @@ using JetBrains.Annotations; using Perfolizer.Mathematics.Common; using Perfolizer.Mathematics.QuantileEstimators; -using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; -namespace Perfolizer.Tests.Mathematics.QuantileEstimators; +namespace Perfolizer.SimulationTests.QuantileEstimators; public class SampleQuantileEstimatorTests : QuantileEstimatorTests { diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/SimpleMovingQuantileEstimatorTests.cs b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/SimpleMovingQuantileEstimatorTests.cs similarity index 92% rename from src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/SimpleMovingQuantileEstimatorTests.cs rename to src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/SimpleMovingQuantileEstimatorTests.cs index c6c26064..3c78cfab 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/SimpleMovingQuantileEstimatorTests.cs +++ b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/SimpleMovingQuantileEstimatorTests.cs @@ -2,7 +2,7 @@ using Perfolizer.Mathematics.Common; using Perfolizer.Mathematics.QuantileEstimators; -namespace Perfolizer.Tests.Mathematics.QuantileEstimators; +namespace Perfolizer.SimulationTests.QuantileEstimators; [UsedImplicitly] public class SimpleMovingQuantileEstimatorTests : MovingQuantileEstimatorTestsBase diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/SmokeQuantileEstimatorTests.cs b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/SmokeQuantileEstimatorTests.cs similarity index 96% rename from src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/SmokeQuantileEstimatorTests.cs rename to src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/SmokeQuantileEstimatorTests.cs index 6adef81a..7a66ce04 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/SmokeQuantileEstimatorTests.cs +++ b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/SmokeQuantileEstimatorTests.cs @@ -1,13 +1,12 @@ using JetBrains.Annotations; -using Perfolizer.Common; using Perfolizer.Extensions; using Perfolizer.Mathematics.Common; using Perfolizer.Mathematics.Distributions.ContinuousDistributions; using Perfolizer.Mathematics.QuantileEstimators; -using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; using Range = Perfolizer.Mathematics.Common.Range; -namespace Perfolizer.Tests.Mathematics.QuantileEstimators; +namespace Perfolizer.SimulationTests.QuantileEstimators; public class SmokeQuantileEstimatorTests { @@ -68,7 +67,7 @@ static SmokeQuantileEstimatorTests() ("HF8", new HyndmanFanQuantileEstimator(HyndmanFanType.Type8)), ("HF9", new HyndmanFanQuantileEstimator(HyndmanFanType.Type9)), ("HD", HarrellDavisQuantileEstimator.Instance), - ("THD", TrimmedHarrellDavisQuantileEstimator.SqrtInstance), + ("THD", TrimmedHarrellDavisQuantileEstimator.Sqrt), ("SV1", SfakianakisVerginis1QuantileEstimator.Instance), ("SV2", SfakianakisVerginis2QuantileEstimator.Instance), ("SV3", SfakianakisVerginis3QuantileEstimator.Instance), diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/TrimmedHarrellDavisQuantileEstimatorTests.cs b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/TrimmedHarrellDavisQuantileEstimatorTests.cs similarity index 90% rename from src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/TrimmedHarrellDavisQuantileEstimatorTests.cs rename to src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/TrimmedHarrellDavisQuantileEstimatorTests.cs index ced8f9a3..e60a12ea 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/QuantileEstimators/TrimmedHarrellDavisQuantileEstimatorTests.cs +++ b/src/Perfolizer/Perfolizer.SimulationTests/QuantileEstimators/TrimmedHarrellDavisQuantileEstimatorTests.cs @@ -1,10 +1,9 @@ -using Perfolizer.Common; using Perfolizer.Mathematics.Common; using Perfolizer.Mathematics.QuantileEstimators; using Perfolizer.Mathematics.Sequences; -using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; -namespace Perfolizer.Tests.Mathematics.QuantileEstimators; +namespace Perfolizer.SimulationTests.QuantileEstimators; public class TrimmedHarrellDavisQuantileEstimatorTests { @@ -29,7 +28,7 @@ public void EstimationTest01() { var sample = new Sample(new double[] { -3, -2, -1, 0, 1, 2, 3 }); var probabilities = new Probability[] { 0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 }; - double[] actualQuantiles = TrimmedHarrellDavisQuantileEstimator.SqrtInstance.Quantiles(sample, probabilities); + double[] actualQuantiles = TrimmedHarrellDavisQuantileEstimator.Sqrt.Quantiles(sample, probabilities); double[] expectedQuantiles = { -3, -2.72276083590394, -2.30045481668633, -1.66479731161074, -0.877210708467137, 2.22044604925031e-16, 0.877210708467138, @@ -43,7 +42,7 @@ public void EstimationTest02() { var sample = new Sample(new ArithmeticProgressionSequence(0, 1).GenerateArray(100)); var probabilities = new Probability[] { 0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 }; - double[] actualQuantiles = TrimmedHarrellDavisQuantileEstimator.SqrtInstance.Quantiles(sample, probabilities); + double[] actualQuantiles = TrimmedHarrellDavisQuantileEstimator.Sqrt.Quantiles(sample, probabilities); double[] expectedQuantiles = { 0, 9.23049888501009, 19.1571295075902, 29.235253395819, 39.3599642036428, 49.5, 59.6400357963572, 69.764746604181, @@ -57,7 +56,7 @@ public void EstimationTest03() { var sample = new Sample(new ArithmeticProgressionSequence(0, 1).GenerateArray(1_000_000)); var probabilities = new Probability[] { 0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 }; - double[] actualQuantiles = TrimmedHarrellDavisQuantileEstimator.SqrtInstance.Quantiles(sample, probabilities); + double[] actualQuantiles = TrimmedHarrellDavisQuantileEstimator.Sqrt.Quantiles(sample, probabilities); double[] expectedQuantiles = { 0, 99999.2066949439, 199999.152626934, 299999.235056257, 399999.360305779, 499999.5, 599999.639694222, 699999.764943743, diff --git a/src/Perfolizer/Perfolizer.Simulations/Perfolizer.Simulations.csproj b/src/Perfolizer/Perfolizer.Simulations/Perfolizer.Simulations.csproj index 86dfbf0a..7d2bdfac 100644 --- a/src/Perfolizer/Perfolizer.Simulations/Perfolizer.Simulations.csproj +++ b/src/Perfolizer/Perfolizer.Simulations/Perfolizer.Simulations.csproj @@ -6,6 +6,7 @@ + diff --git a/src/Perfolizer/Perfolizer.Simulations/RqqPeltSimulation.cs b/src/Perfolizer/Perfolizer.Simulations/RqqPeltSimulation.cs index 96bf0958..29e8bf38 100644 --- a/src/Perfolizer/Perfolizer.Simulations/RqqPeltSimulation.cs +++ b/src/Perfolizer/Perfolizer.Simulations/RqqPeltSimulation.cs @@ -3,7 +3,7 @@ using Perfolizer.Mathematics.QuantileEstimators; using Perfolizer.Mathematics.Randomization; using Perfolizer.Mathematics.Sequences; -using Perfolizer.Tests.Mathematics.Cpd.TestDataSets; +using Perfolizer.SimulationTests.Cpd.TestDataSets; namespace Perfolizer.Simulations; diff --git a/src/Perfolizer/Perfolizer.Tests/Common/PrecisionHelperTests.cs b/src/Perfolizer/Perfolizer.Tests/Common/PrecisionHelperTests.cs new file mode 100644 index 00000000..0431a500 --- /dev/null +++ b/src/Perfolizer/Perfolizer.Tests/Common/PrecisionHelperTests.cs @@ -0,0 +1,20 @@ +using Perfolizer.Mathematics.Common; + +namespace Perfolizer.Tests.Common; + +public class PrecisionHelperTests +{ + [Theory] + [InlineData(2, 1.0)] + [InlineData(1, 10.0)] + [InlineData(1, 100.0)] + [InlineData(3, 0.1)] + [InlineData(4, 0.01)] + [InlineData(5, 0.001)] + [InlineData(5, 0.001, 10.0)] + public void PrecisionHelperTest(int expected, params double[] values) + { + int actual = PrecisionHelper.GetOptimalPrecision(values); + Assert.Equal(expected, actual); + } +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer.Tests/Common/SampleTests.cs b/src/Perfolizer/Perfolizer.Tests/Common/SampleTests.cs deleted file mode 100644 index 942217ed..00000000 --- a/src/Perfolizer/Perfolizer.Tests/Common/SampleTests.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Perfolizer.Common; - -namespace Perfolizer.Tests.Common; - -public class SampleTests -{ - [Fact] - public void SampleCtorTest() - { - string expected = "1;2;3"; - string Format(Sample s) => string.Join(";", s.Values); - - Assert.Equal(expected, Format(new Sample(new[] { 1.0, 2.0, 3.0 }))); - Assert.Equal(expected, Format(new Sample(new[] { 1, 2, 3 }))); - Assert.Equal(expected, Format(new Sample(new[] { 1L, 2L, 3L }))); - } -} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer.Tests/Helpers/CpuBrandHelperTests.cs b/src/Perfolizer/Perfolizer.Tests/Helpers/CpuBrandHelperTests.cs new file mode 100644 index 00000000..08c7f625 --- /dev/null +++ b/src/Perfolizer/Perfolizer.Tests/Helpers/CpuBrandHelperTests.cs @@ -0,0 +1,35 @@ +using System.Text; +using Perfolizer.Helpers; +using Perfolizer.Phd.Dto; +using Perfolizer.Tests.Infra; + +namespace Perfolizer.Tests.Helpers +{ + [Collection("VerifyTests")] + public class CpuBrandHelperTests + { + [Fact] + public Task CpuBrandHelperTest01() + { + var captions = new StringBuilder(); + foreach (string? processorName in new[] { null, "", "Intel" }) + foreach (int? physicalProcessorCount in new int?[] { null, 0, 1, 2 }) + foreach (int? physicalCoreCount in new int?[] { null, 0, 1, 2 }) + foreach (int? logicalCoreCount in new int?[] { null, 0, 1, 2 }) + { + var cpu = new PhdCpu + { + ProcessorName = processorName, + PhysicalProcessorCount = physicalProcessorCount, + PhysicalCoreCount = physicalCoreCount, + LogicalCoreCount = logicalCoreCount, + }; + + captions.AppendLine(cpu.ToFullBrandName()); + } + + var settings = VerifyHelper.CreateSettings("Cpu"); + return Verifier.Verify(captions.ToString(), settings); + } + } +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer.Tests/Helpers/OsBrandHelperTests.cs b/src/Perfolizer/Perfolizer.Tests/Helpers/OsBrandHelperTests.cs new file mode 100644 index 00000000..94c6beba --- /dev/null +++ b/src/Perfolizer/Perfolizer.Tests/Helpers/OsBrandHelperTests.cs @@ -0,0 +1,63 @@ +using Perfolizer.Helpers; + +namespace Perfolizer.Tests.Helpers; + +public class OsBrandHelperTests(ITestOutputHelper output) +{ + private void Check(string actual, string expected) + { + output.WriteLine("LENGTH : " + actual.Length); + output.WriteLine("ACTUAL : " + actual); + output.WriteLine("EXPECTED : " + expected); + Assert.Equal(expected, actual); + + // The line with OsBrandString is one of the longest lines in the summary. + // When people past in on GitHub, it can be a reason of an ugly horizontal scrollbar. + // To avoid this, we are trying to minimize this line and use the minimum possible number of characters. + // In this test, we check that the length of the OS brand string for typical Windows versions + // is less than 61 characters. + const int maxLength = 61; + Assert.True(actual.Length <= maxLength, $"The brand string name length should be <= {maxLength}"); + } + + [Theory] + [InlineData("6.3.9600", "Windows 8.1 (6.3.9600)")] + [InlineData("10.0.14393", "Windows 10 (10.0.14393/1607/AnniversaryUpdate/Redstone1)")] + public void WindowsIsPrettified(string originalVersion, string prettifiedName) + => Check(OsBrandHelper.Prettify("Windows", originalVersion), prettifiedName); + + [Theory] + [InlineData("10.0.10240", 17797, "Windows 10 (10.0.10240.17797/1507/RTM/Threshold1)")] + [InlineData("10.0.10586", 1478, "Windows 10 (10.0.10586.1478/1511/NovemberUpdate/Threshold2)")] + [InlineData("10.0.14393", 2156, "Windows 10 (10.0.14393.2156/1607/AnniversaryUpdate/Redstone1)")] + [InlineData("10.0.15063", 997, "Windows 10 (10.0.15063.997/1703/CreatorsUpdate/Redstone2)")] + [InlineData("10.0.16299", 334, "Windows 10 (10.0.16299.334/1709/FallCreatorsUpdate/Redstone3)")] + [InlineData("10.0.17134", 48, "Windows 10 (10.0.17134.48/1803/April2018Update/Redstone4)")] + [InlineData("10.0.17763", 1, "Windows 10 (10.0.17763.1/1809/October2018Update/Redstone5)")] + [InlineData("10.0.18362", 693, "Windows 10 (10.0.18362.693/1903/May2019Update/19H1)")] + [InlineData("10.0.18363", 657, "Windows 10 (10.0.18363.657/1909/November2019Update/19H2)")] + [InlineData("10.0.19041", 1, "Windows 10 (10.0.19041.1/2004/May2020Update/20H1)")] + [InlineData("10.0.19042", 746, "Windows 10 (10.0.19042.746/20H2/October2020Update)")] + [InlineData("10.0.19043", 964, "Windows 10 (10.0.19043.964/21H1/May2021Update)")] + [InlineData("10.0.19044", 1147, "Windows 10 (10.0.19044.1147/21H2/November2021Update)")] + [InlineData("10.0.19099", 1729, "Windows 10 (10.0.19099.1729)")] + [InlineData("10.0.19045", 0, "Windows 10 (10.0.19045.0/22H2/2022Update)")] + [InlineData("10.0.22000", 348, "Windows 11 (10.0.22000.348/21H2/SunValley)")] + [InlineData("10.0.22518", 1012, "Windows 11 (10.0.22518.1012)")] + [InlineData("10.0.22621", 0, "Windows 11 (10.0.22621.0/22H2/2022Update/SunValley2)")] + [InlineData("10.0.22631", 2428, "Windows 11 (10.0.22631.2428/23H2/2023Update/SunValley3)")] + public void WindowsWithUbrIsPrettified(string originalVersion, int ubr, string prettifiedName) + => Check(OsBrandHelper.Prettify("Windows", originalVersion, ubr.ToString()), prettifiedName); + + [Theory] + [InlineData("macOS 10.12.6 (16G29)", "Darwin 16.7.0", "macOS Sierra 10.12.6 (16G29) [Darwin 16.7.0]")] + [InlineData("macOS 10.13.4 (17E199)", "Darwin 17.5.0", "macOS High Sierra 10.13.4 (17E199) [Darwin 17.5.0]")] + [InlineData("macOS 10.15.4 (19E266)", "Darwin 19.4.0", "macOS Catalina 10.15.4 (19E266) [Darwin 19.4.0]")] + [InlineData("macOS 11.1 (20C69)", "Darwin 20.2.0", "macOS Big Sur 11.1 (20C69) [Darwin 20.2.0]")] + [InlineData("macOS 11.3.1 (20E241)", "Darwin 20.4.0", "macOS Big Sur 11.3.1 (20E241) [Darwin 20.4.0]")] + [InlineData("macOS 12.1 (21C52)", "Darwin 21.2.0", "macOS Monterey 12.1 (21C52) [Darwin 21.2.0]")] + [InlineData("macOS 13.0.1 (22A400)", "Darwin 22.1.0", "macOS Ventura 13.0.1 (22A400) [Darwin 22.1.0]")] + [InlineData("macOS 14.0.0", "Darwin 23.0.0", "macOS Sonoma 14.0.0 [Darwin 23.0.0]")] + public void MacOSXIsPrettified(string systemVersion, string kernelVersion, string prettifiedName) + => Check(OsBrandHelper.PrettifyMacOSX(systemVersion, kernelVersion), prettifiedName); +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer.Tests/Helpers/ProcessorBrandStringTests.cs b/src/Perfolizer/Perfolizer.Tests/Helpers/ProcessorBrandStringTests.cs new file mode 100644 index 00000000..37e9058d --- /dev/null +++ b/src/Perfolizer/Perfolizer.Tests/Helpers/ProcessorBrandStringTests.cs @@ -0,0 +1,81 @@ +using Perfolizer.Helpers; +using Perfolizer.Horology; +using Perfolizer.Mathematics.Common; +using Perfolizer.Phd.Dto; + +namespace Perfolizer.Tests.Helpers; + +// ReSharper disable StringLiteralTypo +// ReSharper disable CommentTypo +public class ProcessorBrandStringTests +{ + [Theory] + [InlineData("Intel(R) Pentium(TM) G4560 CPU @ 3.50GHz", "Intel Pentium G4560 CPU 3.50GHz")] + [InlineData("Intel(R) Core(TM) i7 CPU 970 @ 3.20GHz", "Intel Core i7 CPU 970 3.20GHz (Nehalem)")] // Nehalem/Westmere/Gulftown + [InlineData("Intel(R) Core(TM) i7-920 CPU @ 2.67GHz", "Intel Core i7-920 CPU 2.67GHz (Nehalem)")] + [InlineData("Intel(R) Core(TM) i7-2600 CPU @ 3.40GHz", "Intel Core i7-2600 CPU 3.40GHz (Sandy Bridge)")] + [InlineData("Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz", "Intel Core i7-3770 CPU 3.40GHz (Ivy Bridge)")] + [InlineData("Intel(R) Core(TM) i7-4770K CPU @ 3.50GHz", "Intel Core i7-4770K CPU 3.50GHz (Haswell)")] + [InlineData("Intel(R) Core(TM) i7-5775R CPU @ 3.30GHz", "Intel Core i7-5775R CPU 3.30GHz (Broadwell)")] + [InlineData("Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz", "Intel Core i7-6700HQ CPU 2.60GHz (Skylake)")] + [InlineData("Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz", "Intel Core i7-7700 CPU 3.60GHz (Kaby Lake)")] + [InlineData("Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz ", "Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R)")] + [InlineData("Intel(R) Core(TM) i7-8700K CPU @ 3.70GHz", "Intel Core i7-8700K CPU 3.70GHz (Coffee Lake)")] + public void IntelCoreIsPrettified(string processorName, string cpuBrandName) => + Assert.Equal(cpuBrandName, new PhdCpu { ProcessorName = processorName }.ToShortBrandName()); + + [Theory] + [InlineData("Intel(R) Pentium(TM) G4560 CPU @ 3.50GHz", "Intel Pentium G4560 CPU 3.50GHz (Max: 3.70GHz)", 3.7)] + [InlineData("Intel(R) Core(TM) i5-2500 CPU @ 3.30GHz", "Intel Core i5-2500 CPU 3.30GHz (Max: 3.70GHz) (Sandy Bridge)", 3.7)] + [InlineData("Intel(R) Core(TM) i7-2600 CPU @ 3.40GHz", "Intel Core i7-2600 CPU 3.40GHz (Max: 3.70GHz) (Sandy Bridge)", 3.7)] + [InlineData("Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz", "Intel Core i7-3770 CPU 3.40GHz (Max: 3.50GHz) (Ivy Bridge)", 3.5)] + [InlineData("Intel(R) Core(TM) i7-4770K CPU @ 3.50GHz", "Intel Core i7-4770K CPU 3.50GHz (Max: 3.60GHz) (Haswell)", 3.6)] + [InlineData("Intel(R) Core(TM) i7-5775R CPU @ 3.30GHz", "Intel Core i7-5775R CPU 3.30GHz (Max: 3.40GHz) (Broadwell)", 3.4)] + public void CoreIsPrettifiedWithDiffFrequencies(string processorName, string brandName, double nominalFrequency) + { + var cpu = new PhdCpu + { + ProcessorName = processorName, + NominalFrequencyHz = Frequency.FromGHz(nominalFrequency).Hertz.RoundToLong() + }; + Assert.Equal(brandName, cpu.ToShortBrandName(includeMaxFrequency: true)); + } + + [Theory] + [InlineData("AMD Ryzen 7 2700X Eight-Core Processor", "AMD Ryzen 7 2700X 4.10GHz", 4.1, 8, 16)] + [InlineData("AMD Ryzen 7 2700X Eight-Core Processor", "AMD Ryzen 7 2700X Eight-Core Processor 4.10GHz", 4.1, null, null)] + public void AmdIsPrettifiedWithDiffFrequencies( + string processorName, + string brandName, + double nominalFrequency, + int? physicalCoreCount, + int? logicalCoreCount) + { + var cpu = new PhdCpu + { + ProcessorName = processorName, + NominalFrequencyHz = Frequency.FromGHz(nominalFrequency).Hertz.RoundToLong(), + PhysicalCoreCount = physicalCoreCount, + LogicalCoreCount = logicalCoreCount + }; + Assert.Equal(brandName, cpu.ToShortBrandName(includeMaxFrequency: true)); + } + + [Theory] + [InlineData("8130U", "Kaby Lake")] + [InlineData("9900K", "Coffee Lake")] + [InlineData("8809G", "Kaby Lake G")] + public void IntelCoreMicroarchitecture(string modelNumber, string microarchitecture) + { + Assert.Equal(microarchitecture, CpuBrandHelper.ParseIntelCoreMicroarchitecture(modelNumber)); + } + + [Theory] + [InlineData("", "Unknown processor")] + [InlineData(null, "Unknown processor")] + public void UnknownProcessorDoesNotThrow(string? processorName, string brandName) + { + var cpu = new PhdCpu { ProcessorName = processorName }; + Assert.Equal(brandName, cpu.ToShortBrandName(includeMaxFrequency: true)); + } +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer.Tests/Helpers/VerifiedFiles/Cpu.CpuBrandHelperTest01.verified.txt b/src/Perfolizer/Perfolizer.Tests/Helpers/VerifiedFiles/Cpu.CpuBrandHelperTest01.verified.txt new file mode 100644 index 00000000..63212b55 --- /dev/null +++ b/src/Perfolizer/Perfolizer.Tests/Helpers/VerifiedFiles/Cpu.CpuBrandHelperTest01.verified.txt @@ -0,0 +1,192 @@ +Unknown processor +Unknown processor +Unknown processor, 1 logical core +Unknown processor, 2 logical cores +Unknown processor +Unknown processor +Unknown processor, 1 logical core +Unknown processor, 2 logical cores +Unknown processor, 1 physical core +Unknown processor, 1 physical core +Unknown processor, 1 logical core and 1 physical core +Unknown processor, 2 logical cores and 1 physical core +Unknown processor, 2 physical cores +Unknown processor, 2 physical cores +Unknown processor, 1 logical core and 2 physical cores +Unknown processor, 2 logical and 2 physical cores +Unknown processor +Unknown processor +Unknown processor, 1 logical core +Unknown processor, 2 logical cores +Unknown processor +Unknown processor +Unknown processor, 1 logical core +Unknown processor, 2 logical cores +Unknown processor, 1 physical core +Unknown processor, 1 physical core +Unknown processor, 1 logical core and 1 physical core +Unknown processor, 2 logical cores and 1 physical core +Unknown processor, 2 physical cores +Unknown processor, 2 physical cores +Unknown processor, 1 logical core and 2 physical cores +Unknown processor, 2 logical and 2 physical cores +Unknown processor, 1 CPU +Unknown processor, 1 CPU +Unknown processor, 1 CPU, 1 logical core +Unknown processor, 1 CPU, 2 logical cores +Unknown processor, 1 CPU +Unknown processor, 1 CPU +Unknown processor, 1 CPU, 1 logical core +Unknown processor, 1 CPU, 2 logical cores +Unknown processor, 1 CPU, 1 physical core +Unknown processor, 1 CPU, 1 physical core +Unknown processor, 1 CPU, 1 logical core and 1 physical core +Unknown processor, 1 CPU, 2 logical cores and 1 physical core +Unknown processor, 1 CPU, 2 physical cores +Unknown processor, 1 CPU, 2 physical cores +Unknown processor, 1 CPU, 1 logical core and 2 physical cores +Unknown processor, 1 CPU, 2 logical and 2 physical cores +Unknown processor, 2 CPU +Unknown processor, 2 CPU +Unknown processor, 2 CPU, 1 logical core +Unknown processor, 2 CPU, 2 logical cores +Unknown processor, 2 CPU +Unknown processor, 2 CPU +Unknown processor, 2 CPU, 1 logical core +Unknown processor, 2 CPU, 2 logical cores +Unknown processor, 2 CPU, 1 physical core +Unknown processor, 2 CPU, 1 physical core +Unknown processor, 2 CPU, 1 logical core and 1 physical core +Unknown processor, 2 CPU, 2 logical cores and 1 physical core +Unknown processor, 2 CPU, 2 physical cores +Unknown processor, 2 CPU, 2 physical cores +Unknown processor, 2 CPU, 1 logical core and 2 physical cores +Unknown processor, 2 CPU, 2 logical and 2 physical cores +Unknown processor +Unknown processor +Unknown processor, 1 logical core +Unknown processor, 2 logical cores +Unknown processor +Unknown processor +Unknown processor, 1 logical core +Unknown processor, 2 logical cores +Unknown processor, 1 physical core +Unknown processor, 1 physical core +Unknown processor, 1 logical core and 1 physical core +Unknown processor, 2 logical cores and 1 physical core +Unknown processor, 2 physical cores +Unknown processor, 2 physical cores +Unknown processor, 1 logical core and 2 physical cores +Unknown processor, 2 logical and 2 physical cores +Unknown processor +Unknown processor +Unknown processor, 1 logical core +Unknown processor, 2 logical cores +Unknown processor +Unknown processor +Unknown processor, 1 logical core +Unknown processor, 2 logical cores +Unknown processor, 1 physical core +Unknown processor, 1 physical core +Unknown processor, 1 logical core and 1 physical core +Unknown processor, 2 logical cores and 1 physical core +Unknown processor, 2 physical cores +Unknown processor, 2 physical cores +Unknown processor, 1 logical core and 2 physical cores +Unknown processor, 2 logical and 2 physical cores +Unknown processor, 1 CPU +Unknown processor, 1 CPU +Unknown processor, 1 CPU, 1 logical core +Unknown processor, 1 CPU, 2 logical cores +Unknown processor, 1 CPU +Unknown processor, 1 CPU +Unknown processor, 1 CPU, 1 logical core +Unknown processor, 1 CPU, 2 logical cores +Unknown processor, 1 CPU, 1 physical core +Unknown processor, 1 CPU, 1 physical core +Unknown processor, 1 CPU, 1 logical core and 1 physical core +Unknown processor, 1 CPU, 2 logical cores and 1 physical core +Unknown processor, 1 CPU, 2 physical cores +Unknown processor, 1 CPU, 2 physical cores +Unknown processor, 1 CPU, 1 logical core and 2 physical cores +Unknown processor, 1 CPU, 2 logical and 2 physical cores +Unknown processor, 2 CPU +Unknown processor, 2 CPU +Unknown processor, 2 CPU, 1 logical core +Unknown processor, 2 CPU, 2 logical cores +Unknown processor, 2 CPU +Unknown processor, 2 CPU +Unknown processor, 2 CPU, 1 logical core +Unknown processor, 2 CPU, 2 logical cores +Unknown processor, 2 CPU, 1 physical core +Unknown processor, 2 CPU, 1 physical core +Unknown processor, 2 CPU, 1 logical core and 1 physical core +Unknown processor, 2 CPU, 2 logical cores and 1 physical core +Unknown processor, 2 CPU, 2 physical cores +Unknown processor, 2 CPU, 2 physical cores +Unknown processor, 2 CPU, 1 logical core and 2 physical cores +Unknown processor, 2 CPU, 2 logical and 2 physical cores +Intel +Intel +Intel, 1 logical core +Intel, 2 logical cores +Intel +Intel +Intel, 1 logical core +Intel, 2 logical cores +Intel, 1 physical core +Intel, 1 physical core +Intel, 1 logical core and 1 physical core +Intel, 2 logical cores and 1 physical core +Intel, 2 physical cores +Intel, 2 physical cores +Intel, 1 logical core and 2 physical cores +Intel, 2 logical and 2 physical cores +Intel +Intel +Intel, 1 logical core +Intel, 2 logical cores +Intel +Intel +Intel, 1 logical core +Intel, 2 logical cores +Intel, 1 physical core +Intel, 1 physical core +Intel, 1 logical core and 1 physical core +Intel, 2 logical cores and 1 physical core +Intel, 2 physical cores +Intel, 2 physical cores +Intel, 1 logical core and 2 physical cores +Intel, 2 logical and 2 physical cores +Intel, 1 CPU +Intel, 1 CPU +Intel, 1 CPU, 1 logical core +Intel, 1 CPU, 2 logical cores +Intel, 1 CPU +Intel, 1 CPU +Intel, 1 CPU, 1 logical core +Intel, 1 CPU, 2 logical cores +Intel, 1 CPU, 1 physical core +Intel, 1 CPU, 1 physical core +Intel, 1 CPU, 1 logical core and 1 physical core +Intel, 1 CPU, 2 logical cores and 1 physical core +Intel, 1 CPU, 2 physical cores +Intel, 1 CPU, 2 physical cores +Intel, 1 CPU, 1 logical core and 2 physical cores +Intel, 1 CPU, 2 logical and 2 physical cores +Intel, 2 CPU +Intel, 2 CPU +Intel, 2 CPU, 1 logical core +Intel, 2 CPU, 2 logical cores +Intel, 2 CPU +Intel, 2 CPU +Intel, 2 CPU, 1 logical core +Intel, 2 CPU, 2 logical cores +Intel, 2 CPU, 1 physical core +Intel, 2 CPU, 1 physical core +Intel, 2 CPU, 1 logical core and 1 physical core +Intel, 2 CPU, 2 logical cores and 1 physical core +Intel, 2 CPU, 2 physical cores +Intel, 2 CPU, 2 physical cores +Intel, 2 CPU, 1 logical core and 2 physical cores +Intel, 2 CPU, 2 logical and 2 physical cores diff --git a/src/Perfolizer/Perfolizer.Tests/Horology/TimeSpanExtensionsTests.cs b/src/Perfolizer/Perfolizer.Tests/Horology/TimeSpanExtensionsTests.cs index 853e2e15..6d218300 100644 --- a/src/Perfolizer/Perfolizer.Tests/Horology/TimeSpanExtensionsTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Horology/TimeSpanExtensionsTests.cs @@ -1,6 +1,7 @@ using JetBrains.Annotations; using Perfolizer.Horology; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Horology; diff --git a/src/Perfolizer/Perfolizer.Tests/Common/AbsoluteEqualityComparer.cs b/src/Perfolizer/Perfolizer.Tests/Infra/AbsoluteEqualityComparer.cs similarity index 95% rename from src/Perfolizer/Perfolizer.Tests/Common/AbsoluteEqualityComparer.cs rename to src/Perfolizer/Perfolizer.Tests/Infra/AbsoluteEqualityComparer.cs index 8109bed7..fba70315 100644 --- a/src/Perfolizer/Perfolizer.Tests/Common/AbsoluteEqualityComparer.cs +++ b/src/Perfolizer/Perfolizer.Tests/Infra/AbsoluteEqualityComparer.cs @@ -1,4 +1,4 @@ -namespace Perfolizer.Tests.Common; +namespace Perfolizer.Tests.Infra; public class AbsoluteEqualityComparer(double eps) : IEqualityComparer { diff --git a/src/Perfolizer/Perfolizer.Tests/Common/Permutator.cs b/src/Perfolizer/Perfolizer.Tests/Infra/Permutator.cs similarity index 94% rename from src/Perfolizer/Perfolizer.Tests/Common/Permutator.cs rename to src/Perfolizer/Perfolizer.Tests/Infra/Permutator.cs index ab2e6362..b6865794 100644 --- a/src/Perfolizer/Perfolizer.Tests/Common/Permutator.cs +++ b/src/Perfolizer/Perfolizer.Tests/Infra/Permutator.cs @@ -1,4 +1,4 @@ -namespace Perfolizer.Tests.Common; +namespace Perfolizer.Tests.Infra; public static class Permutator { diff --git a/src/Perfolizer/Perfolizer.Tests/Common/TestCultureInfo.cs b/src/Perfolizer/Perfolizer.Tests/Infra/TestCultureInfo.cs similarity index 51% rename from src/Perfolizer/Perfolizer.Tests/Common/TestCultureInfo.cs rename to src/Perfolizer/Perfolizer.Tests/Infra/TestCultureInfo.cs index 4fd3a59a..be3fd405 100644 --- a/src/Perfolizer/Perfolizer.Tests/Common/TestCultureInfo.cs +++ b/src/Perfolizer/Perfolizer.Tests/Infra/TestCultureInfo.cs @@ -1,14 +1,14 @@ using System.Globalization; using Perfolizer.Common; -namespace Perfolizer.Tests.Common; +namespace Perfolizer.Tests.Infra; -internal static class TestCultureInfo +public static class TestCultureInfo { public static readonly CultureInfo Instance; static TestCultureInfo() { - Instance = (CultureInfo) DefaultCultureInfo.Instance.Clone(); + Instance = (CultureInfo)DefaultCultureInfo.Instance.Clone(); } } \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer.Tests/Common/TestOutputHelperExtensions.cs b/src/Perfolizer/Perfolizer.Tests/Infra/TestOutputHelperExtensions.cs similarity index 96% rename from src/Perfolizer/Perfolizer.Tests/Common/TestOutputHelperExtensions.cs rename to src/Perfolizer/Perfolizer.Tests/Infra/TestOutputHelperExtensions.cs index acac50df..c8dc1c27 100644 --- a/src/Perfolizer/Perfolizer.Tests/Common/TestOutputHelperExtensions.cs +++ b/src/Perfolizer/Perfolizer.Tests/Infra/TestOutputHelperExtensions.cs @@ -1,6 +1,6 @@ using JetBrains.Annotations; -namespace Perfolizer.Tests.Common; +namespace Perfolizer.Tests.Infra; public static class TestOutputHelperExtensions { diff --git a/src/Perfolizer/Perfolizer.Tests/Common/TheoryDataHelper.cs b/src/Perfolizer/Perfolizer.Tests/Infra/TheoryDataHelper.cs similarity index 88% rename from src/Perfolizer/Perfolizer.Tests/Common/TheoryDataHelper.cs rename to src/Perfolizer/Perfolizer.Tests/Infra/TheoryDataHelper.cs index f2c03aa4..a6e9ab21 100644 --- a/src/Perfolizer/Perfolizer.Tests/Common/TheoryDataHelper.cs +++ b/src/Perfolizer/Perfolizer.Tests/Infra/TheoryDataHelper.cs @@ -1,4 +1,4 @@ -namespace Perfolizer.Tests.Common; +namespace Perfolizer.Tests.Infra; public static class TheoryDataHelper { diff --git a/src/Perfolizer/Perfolizer.Tests/Common/TraitConstants.cs b/src/Perfolizer/Perfolizer.Tests/Infra/TraitConstants.cs similarity index 78% rename from src/Perfolizer/Perfolizer.Tests/Common/TraitConstants.cs rename to src/Perfolizer/Perfolizer.Tests/Infra/TraitConstants.cs index 86d9814a..552a4da0 100644 --- a/src/Perfolizer/Perfolizer.Tests/Common/TraitConstants.cs +++ b/src/Perfolizer/Perfolizer.Tests/Infra/TraitConstants.cs @@ -1,4 +1,4 @@ -namespace Perfolizer.Tests.Common; +namespace Perfolizer.Tests.Infra; public static class TraitConstants { diff --git a/src/Perfolizer/Perfolizer.Tests/Infra/VerifyHelper.cs b/src/Perfolizer/Perfolizer.Tests/Infra/VerifyHelper.cs new file mode 100644 index 00000000..82774fda --- /dev/null +++ b/src/Perfolizer/Perfolizer.Tests/Infra/VerifyHelper.cs @@ -0,0 +1,15 @@ +namespace Perfolizer.Tests.Infra; + +public static class VerifyHelper +{ + public static VerifySettings CreateSettings(string? typeName = null) + { + var settings = new VerifySettings(); + settings.UseDirectory("VerifiedFiles"); + settings.DisableDiff(); + if (typeName != null) + settings.UseTypeName(typeName); + + return settings; + } +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Common/RankerTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Common/RankerTests.cs index b7218580..038342e8 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Common/RankerTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Common/RankerTests.cs @@ -4,6 +4,7 @@ using Perfolizer.Mathematics.Common; using Perfolizer.Mathematics.Functions; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Common; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/BetaDistributionTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/BetaDistributionTests.cs index 497d86f2..d5907363 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/BetaDistributionTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/BetaDistributionTests.cs @@ -1,6 +1,7 @@ using JetBrains.Annotations; using Perfolizer.Mathematics.Distributions.ContinuousDistributions; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Distributions.ContinuousDistributions; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/CauchyDistributionTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/CauchyDistributionTests.cs index 5d4c0e9c..2563203b 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/CauchyDistributionTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/CauchyDistributionTests.cs @@ -1,6 +1,7 @@ using JetBrains.Annotations; using Perfolizer.Mathematics.Distributions.ContinuousDistributions; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Distributions.ContinuousDistributions; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/DistributionTestsBase.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/DistributionTestsBase.cs index 5abbde43..cf0e8732 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/DistributionTestsBase.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/DistributionTestsBase.cs @@ -2,6 +2,7 @@ using Perfolizer.Mathematics.Common; using Perfolizer.Mathematics.Distributions.ContinuousDistributions; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Distributions.ContinuousDistributions; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/ExponentialDistributionTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/ExponentialDistributionTests.cs index e10957b4..3685432d 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/ExponentialDistributionTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/ExponentialDistributionTests.cs @@ -1,6 +1,7 @@ using JetBrains.Annotations; using Perfolizer.Mathematics.Distributions.ContinuousDistributions; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Distributions.ContinuousDistributions; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/FrechetDistributionTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/FrechetDistributionTests.cs index b84ce38d..c224f274 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/FrechetDistributionTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/FrechetDistributionTests.cs @@ -1,6 +1,7 @@ using JetBrains.Annotations; using Perfolizer.Mathematics.Distributions.ContinuousDistributions; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Distributions.ContinuousDistributions; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/GumbelDistributionTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/GumbelDistributionTests.cs index 9f5b26f0..74b359ed 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/GumbelDistributionTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/GumbelDistributionTests.cs @@ -1,6 +1,7 @@ using JetBrains.Annotations; using Perfolizer.Mathematics.Distributions.ContinuousDistributions; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Distributions.ContinuousDistributions; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/LaplaceDistributionTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/LaplaceDistributionTests.cs index 4352c0a0..c690a097 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/LaplaceDistributionTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/LaplaceDistributionTests.cs @@ -1,6 +1,7 @@ using JetBrains.Annotations; using Perfolizer.Mathematics.Distributions.ContinuousDistributions; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Distributions.ContinuousDistributions; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/LogNormalDistributionTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/LogNormalDistributionTests.cs index f87f7612..f7f488a0 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/LogNormalDistributionTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/LogNormalDistributionTests.cs @@ -1,6 +1,7 @@ using JetBrains.Annotations; using Perfolizer.Mathematics.Distributions.ContinuousDistributions; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Distributions.ContinuousDistributions; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/MixtureDistributionTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/MixtureDistributionTests.cs index 07068eaa..6c9dafa7 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/MixtureDistributionTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/MixtureDistributionTests.cs @@ -2,6 +2,7 @@ using Perfolizer.Extensions; using Perfolizer.Mathematics.Distributions.ContinuousDistributions; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Distributions.ContinuousDistributions; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/NormalDistributionTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/NormalDistributionTests.cs index c5864429..3cf5ba5e 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/NormalDistributionTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/NormalDistributionTests.cs @@ -1,6 +1,7 @@ using JetBrains.Annotations; using Perfolizer.Mathematics.Distributions.ContinuousDistributions; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Distributions.ContinuousDistributions; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/ParetoDistributionTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/ParetoDistributionTests.cs index 61d9cbfc..a0107568 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/ParetoDistributionTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/ParetoDistributionTests.cs @@ -1,6 +1,7 @@ using JetBrains.Annotations; using Perfolizer.Mathematics.Distributions.ContinuousDistributions; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Distributions.ContinuousDistributions; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/StudentDistributionTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/StudentDistributionTests.cs index a7a456f9..5c1ac754 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/StudentDistributionTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/StudentDistributionTests.cs @@ -1,6 +1,7 @@ using JetBrains.Annotations; using Perfolizer.Mathematics.Distributions.ContinuousDistributions; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Distributions.ContinuousDistributions; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/TriangularDistributionTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/TriangularDistributionTests.cs index 653114b2..8ec4d79e 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/TriangularDistributionTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/TriangularDistributionTests.cs @@ -1,6 +1,7 @@ using JetBrains.Annotations; using Perfolizer.Mathematics.Distributions.ContinuousDistributions; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; using static System.Math; namespace Perfolizer.Tests.Mathematics.Distributions.ContinuousDistributions; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/TukeyGhDistributionTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/TukeyGhDistributionTests.cs index bd0276e8..195511cf 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/TukeyGhDistributionTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/TukeyGhDistributionTests.cs @@ -1,6 +1,7 @@ using JetBrains.Annotations; using Perfolizer.Mathematics.Distributions.ContinuousDistributions; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Distributions.ContinuousDistributions; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/UniformDistributionTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/UniformDistributionTests.cs index 39b2f041..7a354465 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/UniformDistributionTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/UniformDistributionTests.cs @@ -1,6 +1,7 @@ using JetBrains.Annotations; using Perfolizer.Mathematics.Distributions.ContinuousDistributions; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Distributions.ContinuousDistributions; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/WeibullDistributionTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/WeibullDistributionTests.cs index 0fa5b574..e37c83a7 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/WeibullDistributionTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/ContinuousDistributions/WeibullDistributionTests.cs @@ -1,6 +1,7 @@ using JetBrains.Annotations; using Perfolizer.Mathematics.Distributions.ContinuousDistributions; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Distributions.ContinuousDistributions; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/DiscreteDistributions/BinomialDistributionTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/DiscreteDistributions/BinomialDistributionTests.cs index b125c0e8..e80d4cce 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/DiscreteDistributions/BinomialDistributionTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Distributions/DiscreteDistributions/BinomialDistributionTests.cs @@ -2,6 +2,7 @@ using Perfolizer.Mathematics.Common; using Perfolizer.Mathematics.Distributions.DiscreteDistributions; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Distributions.DiscreteDistributions; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/EffectSizes/GammaEffectSizeTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/EffectSizes/GammaEffectSizeTests.cs index dee3d34c..b2aa504c 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/EffectSizes/GammaEffectSizeTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/EffectSizes/GammaEffectSizeTests.cs @@ -6,6 +6,7 @@ using Perfolizer.Mathematics.EffectSizes; using Perfolizer.Mathematics.Functions; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.EffectSizes; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Functions/BetaFunctionTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Functions/BetaFunctionTests.cs index 9d077a39..50e2e2ff 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Functions/BetaFunctionTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Functions/BetaFunctionTests.cs @@ -1,5 +1,6 @@ using Perfolizer.Mathematics.Functions; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Functions; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Functions/ErrorFunctionTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Functions/ErrorFunctionTests.cs index 24fcb311..ff0f630d 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Functions/ErrorFunctionTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Functions/ErrorFunctionTests.cs @@ -1,5 +1,6 @@ using Perfolizer.Mathematics.Functions; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Functions; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Functions/GammaFunctionTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Functions/GammaFunctionTests.cs index 6533c1f0..be022ffe 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Functions/GammaFunctionTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Functions/GammaFunctionTests.cs @@ -1,5 +1,6 @@ using Perfolizer.Mathematics.Functions; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Functions; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Functions/InverseMonotonousFunctionTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Functions/InverseMonotonousFunctionTests.cs index 2d5d8df2..9660b24d 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Functions/InverseMonotonousFunctionTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Functions/InverseMonotonousFunctionTests.cs @@ -1,5 +1,6 @@ using Perfolizer.Mathematics.Functions; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Functions; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/GenericEstimators/HodgesLehmannEstimatorTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/GenericEstimators/HodgesLehmannEstimatorTests.cs index 85efccd7..3e995744 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/GenericEstimators/HodgesLehmannEstimatorTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/GenericEstimators/HodgesLehmannEstimatorTests.cs @@ -4,6 +4,7 @@ using Perfolizer.Mathematics.GenericEstimators; using Perfolizer.Mathematics.QuantileEstimators; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.GenericEstimators; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Histograms/HistogramTestHelper.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Histograms/HistogramTestHelper.cs index 25d83257..d254af70 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Histograms/HistogramTestHelper.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Histograms/HistogramTestHelper.cs @@ -4,6 +4,7 @@ using Perfolizer.Mathematics.Histograms; using Perfolizer.Mathematics.Multimodality; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Histograms; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Histograms/MultimodalTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Histograms/MultimodalTests.cs index 47fdeaea..5f544ca9 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Histograms/MultimodalTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Histograms/MultimodalTests.cs @@ -4,6 +4,7 @@ using Perfolizer.Mathematics.Multimodality; using Perfolizer.Mathematics.OutlierDetection; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Histograms; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Modality/ModalityDataFormatterTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Modality/ModalityDataFormatterTests.cs index 7a2a86dd..412c2446 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Modality/ModalityDataFormatterTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Modality/ModalityDataFormatterTests.cs @@ -3,6 +3,7 @@ using Perfolizer.Mathematics.Common; using Perfolizer.Mathematics.Multimodality; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Modality; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Modality/ModalityDetectorTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Modality/ModalityDetectorTests.cs index 0bd031bf..0b98b03f 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Modality/ModalityDetectorTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Modality/ModalityDetectorTests.cs @@ -7,6 +7,7 @@ using Perfolizer.Mathematics.Multimodality; using Perfolizer.Mathematics.Sequences; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; using Perfolizer.Tests.Mathematics.Modality.TestDataSets; namespace Perfolizer.Tests.Mathematics.Modality; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/OutlierDetection/DoubleMadOutlierDetectorTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/OutlierDetection/DoubleMadOutlierDetectorTests.cs index 9a5a927a..754c945d 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/OutlierDetection/DoubleMadOutlierDetectorTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/OutlierDetection/DoubleMadOutlierDetectorTests.cs @@ -3,6 +3,7 @@ using Perfolizer.Mathematics.OutlierDetection; using Perfolizer.Mathematics.ScaleEstimators; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.OutlierDetection; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/OutlierDetection/MadOutlierDetectorTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/OutlierDetection/MadOutlierDetectorTests.cs index c67a4ccb..ef2bfff3 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/OutlierDetection/MadOutlierDetectorTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/OutlierDetection/MadOutlierDetectorTests.cs @@ -3,6 +3,7 @@ using Perfolizer.Mathematics.OutlierDetection; using Perfolizer.Mathematics.ScaleEstimators; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.OutlierDetection; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/OutlierDetection/OutlierDetectorTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/OutlierDetection/OutlierDetectorTests.cs index 90f4c11e..a0a6db3c 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/OutlierDetection/OutlierDetectorTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/OutlierDetection/OutlierDetectorTests.cs @@ -1,5 +1,6 @@ using Perfolizer.Mathematics.OutlierDetection; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.OutlierDetection; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/OutlierDetection/TukeyOutlierDetectorTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/OutlierDetection/TukeyOutlierDetectorTests.cs index 6bf6c496..2c67305a 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/OutlierDetection/TukeyOutlierDetectorTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/OutlierDetection/TukeyOutlierDetectorTests.cs @@ -2,6 +2,7 @@ using Perfolizer.Mathematics.OutlierDetection; using Perfolizer.Mathematics.QuantileEstimators; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.OutlierDetection; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/RangeEstimator/RangeEstimatorTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/RangeEstimator/RangeEstimatorTests.cs index c7b299a0..31e682e1 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/RangeEstimator/RangeEstimatorTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/RangeEstimator/RangeEstimatorTests.cs @@ -4,6 +4,7 @@ using Perfolizer.Mathematics.Distributions.ContinuousDistributions; using Perfolizer.Mathematics.Functions; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; using Range = Perfolizer.Mathematics.Common.Range; namespace Perfolizer.Tests.Mathematics.RangeEstimator; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/RangeEstimator/RangeTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/RangeEstimator/RangeTests.cs index 32d78bed..384da834 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/RangeEstimator/RangeTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/RangeEstimator/RangeTests.cs @@ -1,5 +1,6 @@ using System.Globalization; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; using Range = Perfolizer.Mathematics.Common.Range; namespace Perfolizer.Tests.Mathematics.RangeEstimator; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/ScaleEstimators/ScaleEstimatorTestsBase.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/ScaleEstimators/ScaleEstimatorTestsBase.cs index 4126ae39..504897fe 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/ScaleEstimators/ScaleEstimatorTestsBase.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/ScaleEstimators/ScaleEstimatorTestsBase.cs @@ -3,6 +3,7 @@ using Perfolizer.Mathematics.Distributions.ContinuousDistributions; using Perfolizer.Mathematics.ScaleEstimators; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.ScaleEstimators; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Selectors/QuickSelectAdaptiveAlgorithmsTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Selectors/QuickSelectAdaptiveAlgorithmsTests.cs index 8aebb424..5549f6ce 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Selectors/QuickSelectAdaptiveAlgorithmsTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Selectors/QuickSelectAdaptiveAlgorithmsTests.cs @@ -1,6 +1,7 @@ using JetBrains.Annotations; using Perfolizer.Mathematics.Selectors; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Selectors; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/Selectors/SelectorTestBase.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/Selectors/SelectorTestBase.cs index 768da3a6..de06c6ed 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/Selectors/SelectorTestBase.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/Selectors/SelectorTestBase.cs @@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis; using JetBrains.Annotations; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.Selectors; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/SequenceGenerators/ExponentialDecaySequenceGeneratorTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/SequenceGenerators/ExponentialDecaySequenceGeneratorTests.cs index ed46d2ba..612e8c0b 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/SequenceGenerators/ExponentialDecaySequenceGeneratorTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/SequenceGenerators/ExponentialDecaySequenceGeneratorTests.cs @@ -1,5 +1,6 @@ using Perfolizer.Mathematics.Sequences; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.SequenceGenerators; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/SignificanceTesting/BrunnerMunzelTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/SignificanceTesting/BrunnerMunzelTests.cs index 03ef47b1..94dd19c6 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/SignificanceTesting/BrunnerMunzelTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/SignificanceTesting/BrunnerMunzelTests.cs @@ -4,6 +4,7 @@ using Perfolizer.Mathematics.SignificanceTesting.Base; using Perfolizer.Metrology; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.SignificanceTesting; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/SignificanceTesting/MannWhitneyTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/SignificanceTesting/MannWhitneyTests.cs index ec9971a7..77ac4559 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/SignificanceTesting/MannWhitneyTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/SignificanceTesting/MannWhitneyTests.cs @@ -8,6 +8,7 @@ using Perfolizer.Mathematics.SignificanceTesting.MannWhitney; using Perfolizer.Metrology; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.SignificanceTesting; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/SignificanceTesting/StudentTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/SignificanceTesting/StudentTests.cs index a4effebd..98a909dd 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/SignificanceTesting/StudentTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/SignificanceTesting/StudentTests.cs @@ -3,6 +3,7 @@ using Perfolizer.Mathematics.SignificanceTesting; using Perfolizer.Mathematics.SignificanceTesting.Base; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.SignificanceTesting; diff --git a/src/Perfolizer/Perfolizer.Tests/Mathematics/SignificanceTesting/WelchTests.cs b/src/Perfolizer/Perfolizer.Tests/Mathematics/SignificanceTesting/WelchTests.cs index d0edbf8a..9d5fa502 100644 --- a/src/Perfolizer/Perfolizer.Tests/Mathematics/SignificanceTesting/WelchTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Mathematics/SignificanceTesting/WelchTests.cs @@ -4,6 +4,7 @@ using Perfolizer.Mathematics.SignificanceTesting.Base; using Perfolizer.Metrology; using Perfolizer.Tests.Common; +using Perfolizer.Tests.Infra; namespace Perfolizer.Tests.Mathematics.SignificanceTesting; diff --git a/src/Perfolizer/Perfolizer.Tests/Metrology/MetrologyTests.cs b/src/Perfolizer/Perfolizer.Tests/Metrology/MetrologyTests.cs index 4bd9edd2..010c5515 100644 --- a/src/Perfolizer/Perfolizer.Tests/Metrology/MetrologyTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/Metrology/MetrologyTests.cs @@ -27,7 +27,7 @@ public class MetrologyTests [InlineData("20GHz")] public void MeasurementUnitToStringParseTest(string s) { - if (!MeasurementValue.TryParse(s, out var value)) + if (!Measurement.TryParse(s, out var value)) throw new Exception($"Failed to parse '{s}'"); Assert.Equal(s, value.ToString()); } diff --git a/src/Perfolizer/Perfolizer.Tests/Perfolizer.Tests.csproj b/src/Perfolizer/Perfolizer.Tests/Perfolizer.Tests.csproj index 8467bee4..ff12aa66 100644 --- a/src/Perfolizer/Perfolizer.Tests/Perfolizer.Tests.csproj +++ b/src/Perfolizer/Perfolizer.Tests/Perfolizer.Tests.csproj @@ -1,22 +1,28 @@ - - net8.0 + + net8.0 - false - + false + - - - - - - - - - - - + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + diff --git a/src/Perfolizer/Perfolizer.Tests/Phd/JsonHelper.cs b/src/Perfolizer/Perfolizer.Tests/Phd/JsonHelper.cs new file mode 100644 index 00000000..c599b380 --- /dev/null +++ b/src/Perfolizer/Perfolizer.Tests/Phd/JsonHelper.cs @@ -0,0 +1,34 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; +using Perfolizer.Phd; +using Perfolizer.Phd.Base; + +namespace Perfolizer.Tests.Phd; + +public class PolymorphicTypeResolver(PhdSchema schema) : DefaultJsonTypeInfoResolver +{ + public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options) + { + var jsonTypeInfo = base.GetTypeInfo(type, options); + + foreach (var implementation in schema.Implementations) + { + if (jsonTypeInfo.Type == implementation.Base) + { + jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions + { + TypeDiscriminatorPropertyName = "$type", + IgnoreUnrecognizedTypeDiscriminators = true, + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization, + DerivedTypes = + { + new JsonDerivedType(implementation.Derived, schema.Name) + } + }; + } + } + + return jsonTypeInfo; + } +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer.Tests/Phd/ModuleInit.cs b/src/Perfolizer/Perfolizer.Tests/Phd/ModuleInit.cs new file mode 100644 index 00000000..7f995eb6 --- /dev/null +++ b/src/Perfolizer/Perfolizer.Tests/Phd/ModuleInit.cs @@ -0,0 +1,21 @@ +using System.Runtime.CompilerServices; + +namespace Perfolizer.Tests.Phd; + +public static class ModuleInit +{ + [ModuleInitializer] + public static void Init() + { + VerifierSettings.UseStrictJson(); + VerifierSettings.AutoVerify(); + VerifierSettings.DontScrubGuids(); + VerifierSettings.DontScrubDateTimes(); + VerifierSettings.DontSortDictionaries(); + } + + [ModuleInitializer] + public static void InitDerivePathInfo() => + DerivePathInfo( + (_, _, type, method) => new(AttributeReader.GetProjectDirectory(), typeName: type.Name, method.Name)); +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer.Tests/Phd/PhdBdnTests.cs b/src/Perfolizer/Perfolizer.Tests/Phd/PhdBdnTests.cs new file mode 100644 index 00000000..36b6260d --- /dev/null +++ b/src/Perfolizer/Perfolizer.Tests/Phd/PhdBdnTests.cs @@ -0,0 +1,80 @@ +using JetBrains.Annotations; +using Perfolizer.Horology; +using Perfolizer.Phd; + +namespace Perfolizer.Tests.Phd; + +// public class PhdBdnTests : PhdTestsBase +// { +// [Fact] +// public Task PhdBdn() +// { +// PerfMetric Metric(double value, int iterationIndex) => new() +// { Value = value, Unit = TimeUnit.Nanosecond, IterationIndex = iterationIndex, InvocationCount = 2 }; +// +// var root = new PhdAttributes +// { +// Info = new BdnInfo { Title = "BenchmarkDotNet.Samples.IntroExportJson-20240309-013216" }, +// Host = new BdnHost +// { +// BenchmarkDotNetCaption = "BenchmarkDotNet", +// BenchmarkDotNetVersion = "0.13.13-develop (2024-03-09)", +// OsVersion = "macOS Sonoma 14.2.1 (23C71) [Darwin 23.2.0]", +// ProcessorName = "Apple M1 Max", +// PhysicalProcessorCount = "1", +// PhysicalCoreCount = "10", +// LogicalCoreCount = "10", +// RuntimeVersion = ".NET 8.0.0 (8.0.23.53103)", +// Architecture = "Arm64", +// HasAttachedDebugger = false, +// HasRyuJit = true, +// Configuration = "RELEASE", +// DotNetSdkVersion = "8.0.100", +// ChronometerFrequency = 1000000000, +// HardwareTimerKind = "Unknown" +// } +// }.ToPerfEntry().Add( +// new PhdAttributes +// { +// Descriptor = new BdnDescriptor +// { +// DisplayInfo = "BenchmarkDotNet.Samples.IntroExportJson-20240309-013216", +// Namespace = "BenchmarkDotNet.Samples", +// Type = "IntroExportJson", +// Method = "ExportJson", +// MethodTitle = "ExportJson", +// Parameters = "Foo=1" +// } +// }.ToPerfEntry() +// .Add(new PhdAttributes +// { +// Lifecycle = new BdnLifecycle +// { +// IterationMode = "Pilot", +// IterationStage = "Overhead", +// LaunchIndex = 0 +// } +// }.ToPerfEntry() +// .Add(Metric(12, 1)) +// .Add(Metric(13, 2)) +// .Add(Metric(14, 3))) +// .Add(new PhdAttributes +// { +// Lifecycle = new BdnLifecycle +// { +// IterationMode = "Result", +// IterationStage = "Workload", +// LaunchIndex = 1 +// } +// }.ToPerfEntry() +// .Add(Metric(15, 1)) +// .Add(Metric(16, 2)) +// .Add(Metric(17, 3))) +// ); +// +// +// return VerifyPhd(root, schema); +// } +// +// +// } \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer.Tests/Phd/PhdEmptyTests.cs b/src/Perfolizer/Perfolizer.Tests/Phd/PhdEmptyTests.cs new file mode 100644 index 00000000..a74b4b8f --- /dev/null +++ b/src/Perfolizer/Perfolizer.Tests/Phd/PhdEmptyTests.cs @@ -0,0 +1,9 @@ +using Perfolizer.Phd.Base; + +namespace Perfolizer.Tests.Phd; + +public class PhdEmptyTests : PhdTestsBase +{ + [Fact] + public Task PhdEmpty() => VerifyPhd(new PhdEntry(), new PhdSchema("")); +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer.Tests/Phd/PhdSimpleTests.cs b/src/Perfolizer/Perfolizer.Tests/Phd/PhdSimpleTests.cs new file mode 100644 index 00000000..64548179 --- /dev/null +++ b/src/Perfolizer/Perfolizer.Tests/Phd/PhdSimpleTests.cs @@ -0,0 +1,127 @@ +using JetBrains.Annotations; +using Perfolizer.Horology; +using Perfolizer.Mathematics.Distributions.ContinuousDistributions; +using Perfolizer.Metrology; +using Perfolizer.Phd.Base; +using Perfolizer.Phd.Dto; +using Perfolizer.Phd.Presenting; +using Perfolizer.Phd.Tables; +using Perfolizer.Presenting; + +namespace Perfolizer.Tests.Phd; + +public class PhdSimpleTests : PhdTestsBase +{ + [Fact] + public Task PhdSimpleTableTest() + { + var table = new PhdTable(CreateRoot()); + var presenter = new StringPresenter(); + new PhdMarkdownTablePresenter(presenter).Present(table, new PhdTableStyle()); + return Verify(presenter.Dump(), CreateSettings()); + } + + [Fact] + public Task PhdSimpleJsonTest() + { + var root = CreateRoot(); + var schema = new PhdSchema("Simple") + .Add() + .Add() + .Add() + .Add(); + return VerifyPhd(root, schema); + } + + private static PhdEntry CreateRoot() + { + var runId = Guid.Parse("11214D6B-4E25-44A4-8032-D4290C9F5617"); + var random = new NormalDistribution(10).Random(1729); + double NextValue() => Math.Round(random.Next(), 3); + int minute = 0; + + PhdEntry CreateEntry(string benchmarkId) => new PhdEntry + { + Meta = new PhdMeta + { + Table = new PhdTableConfig + { + ColumnDefinitions = + [ + new PhdColumnDefinition(".engine") { Cloud = "primary", IsSelfExplanatory = true }, + new PhdColumnDefinition(".host.os") { Cloud = "primary", IsSelfExplanatory = true }, + new PhdColumnDefinition(".host.cpu") { Cloud = "primary", IsSelfExplanatory = true }, + new PhdColumnDefinition(".benchmark") { Cloud = "secondary" }, + new PhdColumnDefinition(".job") { Cloud = "secondary", Compressed = true }, + new PhdColumnDefinition("=average"), + new PhdColumnDefinition("=spread") + ] + } + }, + Info = new PhdInfo + { + RunId = runId, + Timestamp = DateTimeOffset.Parse($"2021-01-01T00:0{minute++}:00Z").ToUnixTimeMilliseconds() + }, + Source = new SimpleSource + { + BuildId = 142857, + Branch = "main", + ConfigurationId = "TeamCityConfiguration01", + CommitHash = "1189402d156078473122bf787f3df6db81dc927c" + }, + Host = new SimpleHost + { + Os = new PhdOs { Name = "Linux" }, + Arch = "x64", + ImageId = "Image42", + }, + Benchmark = new SimpleBenchmark + { + BenchmarkId = benchmarkId, + BenchmarkVersion = 1, + }, + }.Add( + new PhdEntry { Metric = "stage1", Value = NextValue(), Unit = TimeUnit.Millisecond }, + new PhdEntry { Metric = new PhdMetric("stage2", 2), Value = NextValue(), Unit = TimeUnit.Millisecond }, + new PhdEntry { Metric = "totalTime", Value = NextValue(), Unit = TimeUnit.Millisecond }, + new PhdEntry { Metric = "Footprint", Value = 20, Unit = SizeUnit.MB }, + new PhdEntry { Metric = "Gc.CollectCount", Value = 3 }); + + var root = new PhdEntry + { + Info = new PhdInfo { Title = "Simple Measurements" } + }.Add( + CreateEntry("benchmark1"), + CreateEntry("benchmark1"), + CreateEntry("benchmark2"), + CreateEntry("benchmark2")); + return root; + } + + [PublicAPI] + private class SimpleHost : PhdHost + { + public string Arch { get; set; } = ""; + public string ImageId { get; set; } = ""; + } + + [PublicAPI] + private class SimpleExecution : PhdExecution { } + + [PublicAPI] + private class SimpleBenchmark : PhdBenchmark + { + public string BenchmarkId { get; set; } = ""; + public int BenchmarkVersion { get; set; } + } + + [PublicAPI] + private class SimpleSource : PhdSource + { + public int BuildId { get; set; } + public string ConfigurationId { get; set; } = ""; + public string Branch { get; set; } = ""; + public string CommitHash { get; set; } = ""; + } +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer.Tests/Phd/PhdTestsBase.cs b/src/Perfolizer/Perfolizer.Tests/Phd/PhdTestsBase.cs new file mode 100644 index 00000000..b692f15c --- /dev/null +++ b/src/Perfolizer/Perfolizer.Tests/Phd/PhdTestsBase.cs @@ -0,0 +1,24 @@ +using Perfolizer.Json; +using Perfolizer.Phd.Base; +using Perfolizer.Tests.Infra; + +namespace Perfolizer.Tests.Phd; + +public class PhdTestsBase +{ + protected static Task VerifyPhd(PhdEntry entry, PhdSchema schema) + { + // TODO + // JsonSerializerOptions jsonOptions = new() + // { + // DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + // PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + // TypeInfoResolver = new PolymorphicTypeResolver(schema) + // }; + // string json = JsonSerializer.Serialize(entry.Serialize(), jsonOptions); + string json = LightJsonSerializer.Serialize(entry); + return VerifyJson(json, CreateSettings()); + } + + protected static VerifySettings CreateSettings() => VerifyHelper.CreateSettings("Phd"); +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer.Tests/Phd/VerifiedFiles/Phd.PhdEmpty.verified.json b/src/Perfolizer/Perfolizer.Tests/Phd/VerifiedFiles/Phd.PhdEmpty.verified.json new file mode 100644 index 00000000..22fdca1b --- /dev/null +++ b/src/Perfolizer/Perfolizer.Tests/Phd/VerifiedFiles/Phd.PhdEmpty.verified.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer.Tests/Phd/VerifiedFiles/Phd.PhdSimpleJsonTest.verified.json b/src/Perfolizer/Perfolizer.Tests/Phd/VerifiedFiles/Phd.PhdSimpleJsonTest.verified.json new file mode 100644 index 00000000..bc825cda --- /dev/null +++ b/src/Perfolizer/Perfolizer.Tests/Phd/VerifiedFiles/Phd.PhdSimpleJsonTest.verified.json @@ -0,0 +1,361 @@ +{ + "info": { + "title": "Simple Measurements", + "runId": "00000000-0000-0000-0000-000000000000", + "timestamp": 0 + }, + "children": [ + { + "meta": { + "table": { + "columnDefinitions": [ + { + "selector": ".engine", + "cloud": "primary", + "isSelfExplanatory": true + }, + { + "selector": ".host.os", + "cloud": "primary", + "isSelfExplanatory": true + }, + { + "selector": ".host.cpu", + "cloud": "primary", + "isSelfExplanatory": true + }, + { + "selector": ".benchmark", + "cloud": "secondary" + }, + { + "selector": ".job", + "cloud": "secondary", + "compressed": true + }, + { + "selector": "=average" + }, + { + "selector": "=spread" + } + ] + } + }, + "info": { + "runId": "11214d6b-4e25-44a4-8032-d4290c9f5617", + "timestamp": 1609459200000 + }, + "source": { + "buildId": 142857, + "configurationId": "TeamCityConfiguration01", + "branch": "main", + "commitHash": "1189402d156078473122bf787f3df6db81dc927c" + }, + "host": { + "arch": "x64", + "imageId": "Image42", + "os": { + "name": "Linux" + } + }, + "benchmark": { + "benchmarkId": "benchmark1", + "benchmarkVersion": 1 + }, + "children": [ + { + "metric": "stage1", + "value": 10.021, + "unit": "ms" + }, + { + "metric": { + "id": "stage2", + "version": 2 + }, + "value": 9.103, + "unit": "ms" + }, + { + "metric": "totalTime", + "value": 9.891, + "unit": "ms" + }, + { + "metric": "Footprint", + "value": 20, + "unit": "MB" + }, + { + "metric": "Gc.CollectCount", + "value": 3 + } + ] + }, + { + "meta": { + "table": { + "columnDefinitions": [ + { + "selector": ".engine", + "cloud": "primary", + "isSelfExplanatory": true + }, + { + "selector": ".host.os", + "cloud": "primary", + "isSelfExplanatory": true + }, + { + "selector": ".host.cpu", + "cloud": "primary", + "isSelfExplanatory": true + }, + { + "selector": ".benchmark", + "cloud": "secondary" + }, + { + "selector": ".job", + "cloud": "secondary", + "compressed": true + }, + { + "selector": "=average" + }, + { + "selector": "=spread" + } + ] + } + }, + "info": { + "runId": "11214d6b-4e25-44a4-8032-d4290c9f5617", + "timestamp": 1609459260000 + }, + "source": { + "buildId": 142857, + "configurationId": "TeamCityConfiguration01", + "branch": "main", + "commitHash": "1189402d156078473122bf787f3df6db81dc927c" + }, + "host": { + "arch": "x64", + "imageId": "Image42", + "os": { + "name": "Linux" + } + }, + "benchmark": { + "benchmarkId": "benchmark1", + "benchmarkVersion": 1 + }, + "children": [ + { + "metric": "stage1", + "value": 9.194, + "unit": "ms" + }, + { + "metric": { + "id": "stage2", + "version": 2 + }, + "value": 9.588, + "unit": "ms" + }, + { + "metric": "totalTime", + "value": 11.931, + "unit": "ms" + }, + { + "metric": "Footprint", + "value": 20, + "unit": "MB" + }, + { + "metric": "Gc.CollectCount", + "value": 3 + } + ] + }, + { + "meta": { + "table": { + "columnDefinitions": [ + { + "selector": ".engine", + "cloud": "primary", + "isSelfExplanatory": true + }, + { + "selector": ".host.os", + "cloud": "primary", + "isSelfExplanatory": true + }, + { + "selector": ".host.cpu", + "cloud": "primary", + "isSelfExplanatory": true + }, + { + "selector": ".benchmark", + "cloud": "secondary" + }, + { + "selector": ".job", + "cloud": "secondary", + "compressed": true + }, + { + "selector": "=average" + }, + { + "selector": "=spread" + } + ] + } + }, + "info": { + "runId": "11214d6b-4e25-44a4-8032-d4290c9f5617", + "timestamp": 1609459320000 + }, + "source": { + "buildId": 142857, + "configurationId": "TeamCityConfiguration01", + "branch": "main", + "commitHash": "1189402d156078473122bf787f3df6db81dc927c" + }, + "host": { + "arch": "x64", + "imageId": "Image42", + "os": { + "name": "Linux" + } + }, + "benchmark": { + "benchmarkId": "benchmark2", + "benchmarkVersion": 1 + }, + "children": [ + { + "metric": "stage1", + "value": 9.903, + "unit": "ms" + }, + { + "metric": { + "id": "stage2", + "version": 2 + }, + "value": 8.49, + "unit": "ms" + }, + { + "metric": "totalTime", + "value": 9.854, + "unit": "ms" + }, + { + "metric": "Footprint", + "value": 20, + "unit": "MB" + }, + { + "metric": "Gc.CollectCount", + "value": 3 + } + ] + }, + { + "meta": { + "table": { + "columnDefinitions": [ + { + "selector": ".engine", + "cloud": "primary", + "isSelfExplanatory": true + }, + { + "selector": ".host.os", + "cloud": "primary", + "isSelfExplanatory": true + }, + { + "selector": ".host.cpu", + "cloud": "primary", + "isSelfExplanatory": true + }, + { + "selector": ".benchmark", + "cloud": "secondary" + }, + { + "selector": ".job", + "cloud": "secondary", + "compressed": true + }, + { + "selector": "=average" + }, + { + "selector": "=spread" + } + ] + } + }, + "info": { + "runId": "11214d6b-4e25-44a4-8032-d4290c9f5617", + "timestamp": 1609459380000 + }, + "source": { + "buildId": 142857, + "configurationId": "TeamCityConfiguration01", + "branch": "main", + "commitHash": "1189402d156078473122bf787f3df6db81dc927c" + }, + "host": { + "arch": "x64", + "imageId": "Image42", + "os": { + "name": "Linux" + } + }, + "benchmark": { + "benchmarkId": "benchmark2", + "benchmarkVersion": 1 + }, + "children": [ + { + "metric": "stage1", + "value": 8.833, + "unit": "ms" + }, + { + "metric": { + "id": "stage2", + "version": 2 + }, + "value": 10.24, + "unit": "ms" + }, + { + "metric": "totalTime", + "value": 10.571, + "unit": "ms" + }, + { + "metric": "Footprint", + "value": 20, + "unit": "MB" + }, + { + "metric": "Gc.CollectCount", + "value": 3 + } + ] + } + ] +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer.Tests/Phd/VerifiedFiles/Phd.PhdSimpleTableTest.verified.txt b/src/Perfolizer/Perfolizer.Tests/Phd/VerifiedFiles/Phd.PhdSimpleTableTest.verified.txt new file mode 100644 index 00000000..e60dcb10 --- /dev/null +++ b/src/Perfolizer/Perfolizer.Tests/Phd/VerifiedFiles/Phd.PhdSimpleTableTest.verified.txt @@ -0,0 +1,8 @@ +Linux + +BenchmarkVersion = 1 + +| BenchmarkId | Average | Spread | +|:------------|--------:|-------:| +| benchmark1 | 9.9 ms | 6.6 ms | +| benchmark2 | 9.9 ms | 6.9 ms | diff --git a/src/Perfolizer/Perfolizer.Tests/PhdSimpleTests.PhdSimpleTableTest.verified.txt b/src/Perfolizer/Perfolizer.Tests/PhdSimpleTests.PhdSimpleTableTest.verified.txt new file mode 100644 index 00000000..081cf583 --- /dev/null +++ b/src/Perfolizer/Perfolizer.Tests/PhdSimpleTests.PhdSimpleTableTest.verified.txt @@ -0,0 +1,8 @@ +Linux + +BenchmarkVersion = 1 + +| BenchmarkId | Average | Spread | +|-------------|---------|--------| +| benchmark1 | 9.9 ms | 6.6 ms | +| benchmark2 | 9.9 ms | 6.9 ms | diff --git a/src/Perfolizer/Perfolizer.Tests/SampleTests.cs b/src/Perfolizer/Perfolizer.Tests/SampleTests.cs index 5de53636..d3b6967d 100644 --- a/src/Perfolizer/Perfolizer.Tests/SampleTests.cs +++ b/src/Perfolizer/Perfolizer.Tests/SampleTests.cs @@ -11,4 +11,15 @@ public void SampleConcatTest(string a, string b, string c) string expected = Sample.Parse(c).ToString(); Assert.Equal(expected, actual); } + + [Fact] + public void SampleCtorTest() + { + string expected = "1;2;3"; + string Format(Sample s) => string.Join(";", s.Values); + + Assert.Equal(expected, Format(new Sample(new[] { 1.0, 2.0, 3.0 }))); + Assert.Equal(expected, Format(new Sample(new[] { 1, 2, 3 }))); + Assert.Equal(expected, Format(new Sample(new[] { 1L, 2L, 3L }))); + } } \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer.sln b/src/Perfolizer/Perfolizer.sln index ace403dc..9b86505f 100644 --- a/src/Perfolizer/Perfolizer.sln +++ b/src/Perfolizer/Perfolizer.sln @@ -8,6 +8,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perfolizer.Demo", "Perfoliz EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perfolizer.Simulations", "Perfolizer.Simulations\Perfolizer.Simulations.csproj", "{C9338572-948F-4164-A1B7-43DEA1B9B696}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perfolizer.SimulationTests", "Perfolizer.SimulationTests\Perfolizer.SimulationTests.csproj", "{8E2B01DC-7A08-431A-ACB8-8709CEC04228}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -30,5 +32,9 @@ Global {C9338572-948F-4164-A1B7-43DEA1B9B696}.Debug|Any CPU.Build.0 = Debug|Any CPU {C9338572-948F-4164-A1B7-43DEA1B9B696}.Release|Any CPU.ActiveCfg = Release|Any CPU {C9338572-948F-4164-A1B7-43DEA1B9B696}.Release|Any CPU.Build.0 = Release|Any CPU + {8E2B01DC-7A08-431A-ACB8-8709CEC04228}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8E2B01DC-7A08-431A-ACB8-8709CEC04228}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8E2B01DC-7A08-431A-ACB8-8709CEC04228}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8E2B01DC-7A08-431A-ACB8-8709CEC04228}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/src/Perfolizer/Perfolizer.sln.DotSettings b/src/Perfolizer/Perfolizer.sln.DotSettings index ee3f0fcd..882a2404 100644 --- a/src/Perfolizer/Perfolizer.sln.DotSettings +++ b/src/Perfolizer/Perfolizer.sln.DotSettings @@ -8,6 +8,7 @@ 0 4 True + True False 120 UseExplicitType diff --git a/src/Perfolizer/Perfolizer/Attributes/NullableAttributes.cs b/src/Perfolizer/Perfolizer/Attributes/NullableAttributes.cs index 9de89540..12db3118 100644 --- a/src/Perfolizer/Perfolizer/Attributes/NullableAttributes.cs +++ b/src/Perfolizer/Perfolizer/Attributes/NullableAttributes.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace System.Diagnostics.CodeAnalysis; +namespace Perfolizer.Attributes; #if !NETSTANDARD2_1 /// Specifies that null is allowed as an input even if the corresponding type disallows it. [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] diff --git a/src/Perfolizer/Perfolizer/Collections/CollectionExtensions.cs b/src/Perfolizer/Perfolizer/Collections/CollectionExtensions.cs index aa682379..7640d065 100644 --- a/src/Perfolizer/Perfolizer/Collections/CollectionExtensions.cs +++ b/src/Perfolizer/Perfolizer/Collections/CollectionExtensions.cs @@ -1,5 +1,7 @@ +using System.Collections; using System.Diagnostics.CodeAnalysis; using Perfolizer.Common; +using Perfolizer.Metrology; namespace Perfolizer.Collections; @@ -89,16 +91,26 @@ internal static int WhichMax(this IReadOnlyList source, int start, int l /// internal static int WhichMax(this IReadOnlyList source) => WhichMax(source, 0, source.Count); - public static Sample ToSample(this IEnumerable values) + public static Sample ToSample(this IEnumerable values, MeasurementUnit? unit = null) { Assertion.NotNull(nameof(values), values); if (values is IReadOnlyList list) - return new Sample(list); - return new Sample(values.ToList()); + return new Sample(list, unit); + return new Sample(values.ToList(), unit); } + public static bool IsEmpty(this IReadOnlyCollection value) => value.Count == 0; + public static bool IsNotEmpty(this IReadOnlyCollection value) => value.Count > 0; public static bool IsEmpty(this IEnumerable values) => !values.Any(); + public static bool IsNotEmpty(this IEnumerable values) => values.Any(); public static IEnumerable WhereNotNull(this IEnumerable values) => values.Where(value => value != null).Cast(); + + public static IReadOnlyList ToReadOnlyList(this IEnumerable values) => values.ToList(); + +#if NETSTANDARD2_0 + public static TValue? GetValueOrDefault(this IDictionary dictionary, TKey key) + => dictionary.TryGetValue(key, out var value) ? value : default; +#endif } \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Common/Assertion.cs b/src/Perfolizer/Perfolizer/Common/Assertion.cs index 7bc7cbbc..dd2378ff 100644 --- a/src/Perfolizer/Perfolizer/Common/Assertion.cs +++ b/src/Perfolizer/Perfolizer/Common/Assertion.cs @@ -1,5 +1,6 @@ using JetBrains.Annotations; using Perfolizer.Exceptions; +using Perfolizer.Metrology; namespace Perfolizer.Common; @@ -166,6 +167,14 @@ public static void Equal(string name1, int value1, string name2, int value2) } } + [AssertionMethod] + public static void Equal(MeasurementUnit unit1, MeasurementUnit unit2) + { + if (unit1 != unit2) + throw new InvalidOperationException( + $"Invalid operations on incompatible units: {unit1.FullName} and {unit2.FullName}"); + } + [AssertionMethod] public static void Equal(string name, double value, double expectedValue, double eps = 1e-9) { diff --git a/src/Perfolizer/Perfolizer/Extensions/DoubleExtensions.cs b/src/Perfolizer/Perfolizer/Extensions/DoubleExtensions.cs index a966a0ac..e9ae507a 100644 --- a/src/Perfolizer/Perfolizer/Extensions/DoubleExtensions.cs +++ b/src/Perfolizer/Perfolizer/Extensions/DoubleExtensions.cs @@ -1,5 +1,6 @@ using Perfolizer.Common; using Perfolizer.Mathematics.Common; +using Perfolizer.Metrology; namespace Perfolizer.Extensions; @@ -10,4 +11,6 @@ internal static class DoubleExtensions public static string ToStringInvariant(this Probability p) => p.ToString(DefaultCultureInfo.Instance); public static string ToStringInvariant(this Probability p, string format) => p.ToString(format, DefaultCultureInfo.Instance); + + public static Measurement WithUnit(this double value, MeasurementUnit unit) => new (value, unit); } \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Extensions/IntExtensions.cs b/src/Perfolizer/Perfolizer/Extensions/IntExtensions.cs new file mode 100644 index 00000000..9fd78d14 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Extensions/IntExtensions.cs @@ -0,0 +1,9 @@ +using Perfolizer.Metrology; + +namespace Perfolizer.Extensions; + +internal static class IntExtensions +{ + public static Measurement AsMeasurement(this int value) => new (value, NumberUnit.Instance); + public static int Abs(this int value) => Math.Abs(value); +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Extensions/StringBuilderExtensions.cs b/src/Perfolizer/Perfolizer/Extensions/StringBuilderExtensions.cs index 7c2b8cc8..39c799db 100644 --- a/src/Perfolizer/Perfolizer/Extensions/StringBuilderExtensions.cs +++ b/src/Perfolizer/Perfolizer/Extensions/StringBuilderExtensions.cs @@ -1,16 +1,11 @@ -using System.Diagnostics.CodeAnalysis; using System.Text; namespace Perfolizer.Extensions; internal static class StringBuilderExtensions { - [return: NotNullIfNotNull("builder")] - public static StringBuilder? TrimEnd(this StringBuilder? builder, params char[] trimChars) + public static StringBuilder TrimEnd(this StringBuilder builder, params char[] trimChars) { - if (builder == null) - return null; - int length = builder.Length; if (trimChars.Any()) while (length > 0 && trimChars.Contains(builder[length - 1])) diff --git a/src/Perfolizer/Perfolizer/Extensions/StringExtensions.cs b/src/Perfolizer/Perfolizer/Extensions/StringExtensions.cs index 1fce28dc..088da479 100644 --- a/src/Perfolizer/Perfolizer/Extensions/StringExtensions.cs +++ b/src/Perfolizer/Perfolizer/Extensions/StringExtensions.cs @@ -5,4 +5,9 @@ internal static class StringExtensions public static bool IsBlank(this string? value) => string.IsNullOrWhiteSpace(value); public static bool IsNotBlank(this string? value) => !value.IsBlank(); public static bool EquationsIgnoreCase(this string a, string b) => a.Equals(b, StringComparison.OrdinalIgnoreCase); + public static bool StartWithIgnoreCase(this string a, string b) => a.StartsWith(b, StringComparison.OrdinalIgnoreCase); + public static string JoinToString(this IEnumerable values, string separator) => string.Join(separator, values); + public static string CapitalizeFirst(this string s) => s.Length == 0 ? s : char.ToUpper(s[0]) + s.Substring(1); + public static string ToCamelCase(this string s) => s.Length == 0 ? s : char.ToLower(s[0]) + s.Substring(1); + public static bool StartsWith(this string s, char c) => s.Length > 0 && s[0] == c; } \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Helpers/CpuBrandHelper.cs b/src/Perfolizer/Perfolizer/Helpers/CpuBrandHelper.cs new file mode 100644 index 00000000..5a77ec59 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Helpers/CpuBrandHelper.cs @@ -0,0 +1,188 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.RegularExpressions; +using Perfolizer.Horology; +using Perfolizer.Phd.Dto; + +namespace Perfolizer.Helpers +{ + public static class CpuBrandHelper + { + public static string ToFullBrandName(this PhdCpu? cpu) + { + if (cpu == null) + { + return "Unknown processor"; + } + + var parts = new List { cpu.ToShortBrandName(includeMaxFrequency: true) }; + + if (cpu.PhysicalProcessorCount > 0) + parts.Add($", {cpu.PhysicalProcessorCount} CPU"); + + if (cpu.LogicalCoreCount == 1) + parts.Add(", 1 logical core"); + + if (cpu.LogicalCoreCount > 1) + parts.Add($", {cpu.LogicalCoreCount} logical cores"); + + if (cpu.LogicalCoreCount > 0 && cpu.PhysicalCoreCount > 0) + parts.Add(" and "); + else if (cpu.PhysicalCoreCount > 0) + parts.Add(", "); + + if (cpu.PhysicalCoreCount == 1) + parts.Add("1 physical core"); + if (cpu.PhysicalCoreCount > 1) + parts.Add($"{cpu.PhysicalCoreCount} physical cores"); + + string result = string.Join("", parts); + // The line with ProcessorBrandString is one of the longest lines in the summary. + // When people past in on GitHub, it can be a reason of an ugly horizontal scrollbar. + // To avoid this, we are trying to minimize this line and use the minimum possible number of characters. + // Here we are removing the repetitive "cores" word. + if (result.Contains("logical cores") && result.Contains("physical cores")) + result = result.Replace("logical cores", "logical"); + + return result; + } + + + /// + /// Transform a processor brand string to a nice form for summary. + /// + /// The CPU information + /// Whether to include determined max frequency information + /// Prettified version + public static string ToShortBrandName(this PhdCpu? cpu, bool includeMaxFrequency = false) + { + if (cpu == null || string.IsNullOrEmpty(cpu.ProcessorName)) + { + return "Unknown processor"; + } + + // Remove parts which don't provide any useful information for user + var processorName = cpu.ProcessorName!.Replace("@", "").Replace("(R)", "").Replace("(TM)", ""); + + // If we have found physical core(s), we can safely assume we can drop extra info from brand + if (cpu.PhysicalCoreCount.HasValue && cpu.PhysicalCoreCount.Value > 0) + processorName = Regex.Replace(processorName, @"(\w+?-Core Processor)", "").Trim(); + + string? frequencyString = GetBrandStyledActualFrequency(cpu.NominalFrequency()); + if (includeMaxFrequency && frequencyString != null && !processorName.Contains(frequencyString)) + { + // show Max only if there's already a frequency name to differentiate the two + string maxFrequency = processorName.Contains("Hz") ? $"(Max: {frequencyString})" : frequencyString; + processorName = $"{processorName} {maxFrequency}"; + } + + // Remove double spaces + processorName = Regex.Replace(processorName.Trim(), @"\s+", " "); + + // Add microarchitecture name if known + string? microarchitecture = ParseMicroarchitecture(processorName); + if (microarchitecture != null) + processorName = $"{processorName} ({microarchitecture})"; + + return processorName; + } + + /// + /// Presents actual processor's frequency into brand string format + /// + /// + private static string? GetBrandStyledActualFrequency(Frequency? frequency) => frequency == null + ? null + : $"{frequency.Value.ToString(FrequencyUnit.GHz, "N2")}"; + + /// + /// Parse a processor name and tries to return a microarchitecture name. + /// Works only for well-known microarchitectures. + /// + private static string? ParseMicroarchitecture(string processorName) + { + if (processorName.StartsWith("Intel Core")) + { + string model = processorName.Substring("Intel Core".Length).Trim(); + + // Core i3/5/7/9 + if ( + model.Length > 4 && + model[0] == 'i' && + (model[1] == '3' || model[1] == '5' || model[1] == '7' || model[1] == '9') && + (model[2] == '-' || model[2] == ' ')) + { + string modelNumber = model.Substring(3); + if (modelNumber.StartsWith("CPU")) + modelNumber = modelNumber.Substring(3).Trim(); + if (modelNumber.Contains("CPU")) + modelNumber = modelNumber.Substring(0, modelNumber.IndexOf("CPU", StringComparison.Ordinal)).Trim(); + return ParseIntelCoreMicroarchitecture(modelNumber); + } + } + + return null; + } + + private static readonly Lazy> KnownMicroarchitectures = new (() => + { + var data = ResourceHelper.LoadResource("Perfolizer.Resources.microarchitectures.txt").Split('\r', '\n'); + var dictionary = new Dictionary(); + string? currentMicroarchitecture = null; + foreach (string line in data) + { + if (line.StartsWith("//") || string.IsNullOrWhiteSpace(line)) + continue; + if (line.StartsWith("#")) + { + currentMicroarchitecture = line.Substring(1).Trim(); + continue; + } + + string modelNumber = line.Trim(); + if (dictionary.ContainsKey(modelNumber)) + throw new Exception($"{modelNumber} is defined twice in microarchitectures.txt"); + if (currentMicroarchitecture == null) + throw new Exception($"{modelNumber} doesn't have defined microarchitecture in microarchitectures.txt"); + dictionary[modelNumber] = currentMicroarchitecture; + } + + return dictionary; + }); + + // see http://www.intel.com/content/www/us/en/processors/processor-numbers.html + [SuppressMessage("ReSharper", "StringLiteralTypo")] + internal static string? ParseIntelCoreMicroarchitecture(string modelNumber) + { + if (KnownMicroarchitectures.Value.TryGetValue(modelNumber, out string? microarchitecture)) + return microarchitecture; + + if (modelNumber.Length >= 3 && modelNumber.Substring(0, 3).All(char.IsDigit) && + (modelNumber.Length == 3 || !char.IsDigit(modelNumber[3]))) + return "Nehalem"; + if (modelNumber.Length >= 4 && modelNumber.Substring(0, 4).All(char.IsDigit)) + { + char generation = modelNumber[0]; + switch (generation) + { + case '2': + return "Sandy Bridge"; + case '3': + return "Ivy Bridge"; + case '4': + return "Haswell"; + case '5': + return "Broadwell"; + case '6': + return "Skylake"; + case '7': + return "Kaby Lake"; + case '8': + return "Coffee Lake"; + default: + return null; + } + } + return null; + } + } +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Helpers/HashCodeHelper.cs b/src/Perfolizer/Perfolizer/Helpers/HashCodeHelper.cs index 035a72ec..1914d6d6 100644 --- a/src/Perfolizer/Perfolizer/Helpers/HashCodeHelper.cs +++ b/src/Perfolizer/Perfolizer/Helpers/HashCodeHelper.cs @@ -151,6 +151,6 @@ private static int Hash(int hashCode, T value, IEqualityComparer comparer) [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes.", error: true)] [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object obj) => throw new NotSupportedException(); + public override bool Equals(object? obj) => throw new NotSupportedException(); #pragma warning restore CS0809 // Obsolete member 'HashCode.GetHashCode()' overrides non-obsolete member 'object.GetHashCode()' } \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Helpers/OsBrandHelper.cs b/src/Perfolizer/Perfolizer/Helpers/OsBrandHelper.cs new file mode 100644 index 00000000..f8cc6978 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Helpers/OsBrandHelper.cs @@ -0,0 +1,300 @@ +using System.Diagnostics.CodeAnalysis; +using Perfolizer.Collections; +using Perfolizer.Extensions; +using Perfolizer.Phd.Dto; + +namespace Perfolizer.Helpers; + +[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] +public static class OsBrandHelper +{ + // See https://en.wikipedia.org/wiki/Ver_(command) + // See https://docs.microsoft.com/en-us/windows/release-health/release-information + // See https://docs.microsoft.com/en-us/windows/release-health/windows11-release-information + private static readonly Dictionary WindowsBrandVersions = new () + { + { "1.04", "1.0" }, + { "2.11", "2.0" }, + { "3", "3.0" }, + { "3.10.528", "NT 3.1" }, + { "3.11", "for Workgroups 3.11" }, + { "3.50.807", "NT 3.5" }, + { "3.51.1057", "NT 3.51" }, + { "4.00.950", "95" }, + { "4.00.1111", "95 OSR2" }, + { "4.03.1212-1214", "95 OSR2.1" }, + { "4.03.1214", "95 OSR2.5" }, + { "4.00.1381", "NT 4.0" }, + { "4.10.1998", "98" }, + { "4.10.2222", "98 SE" }, + { "4.90.2380.2", "ME Beta" }, + { "4.90.2419", "ME Beta 2" }, + { "4.90.3000", "ME" }, + { "5.00.1515", "NT 5.0 Beta" }, + { "5.00.2031", "2000 Beta 3" }, + { "5.00.2128", "2000 RC2" }, + { "5.00.2183", "2000 RC3" }, + { "5.00.2195", "2000" }, + { "5.0.2195", "2000 Professional" }, + { "5.1.2505", "XP RC1" }, + { "5.1.2600", "XP" }, + { "5.1.2600.1105-1106", "XP SP1" }, + { "5.1.2600.2180", "XP SP2" }, + { "5.2.3541", ".NET Server interim" }, + { "5.2.3590", ".NET Server Beta 3" }, + { "5.2.3660", ".NET Server RC1" }, + { "5.2.3718", ".NET Server 2003 RC2" }, + { "5.2.3763", "Server 2003 Beta" }, + { "5.2.3790", "XP Professional x64 Edition" }, + { "5.2.3790.1180", "Server 2003 SP1" }, + { "5.2.3790.1218", "Server 2003" }, + { "6.0.5048", "Longhorn" }, + { "6.0.5112", "Vista Beta 1" }, + { "6.0.5219", "Vista CTP" }, + { "6.0.5259", "Vista TAP Preview" }, + { "6.0.5270", "Vista CTP December" }, + { "6.0.5308", "Vista CTP February" }, + { "6.0.5342", "Vista CTP Refresh" }, + { "6.0.5365", "Vista April EWD" }, + { "6.0.5381", "Vista Beta 2 Preview" }, + { "6.0.5384", "Vista Beta 2" }, + { "6.0.5456", "Vista Pre-RC1 Build 5456" }, + { "6.0.5472", "Vista Pre-RC1 Build 5472" }, + { "6.0.5536", "Vista Pre-RC1 Build 5536" }, + { "6.0.5600.16384", "Vista RC1" }, + { "6.0.5700", "Vista Pre-RC2" }, + { "6.0.5728", "Vista Pre-RC2 Build 5728" }, + { "6.0.5744.16384", "Vista RC2" }, + { "6.0.5808", "Vista Pre-RTM Build 5808" }, + { "6.0.5824", "Vista Pre-RTM Build 5824" }, + { "6.0.5840", "Vista Pre-RTM Build 5840" }, + { "6.0.6000", "Vista" }, + { "6.0.6000.16386", "Vista RTM" }, + { "6.0.6001", "Vista SP1" }, + { "6.0.6002", "Vista SP2" }, + { "6.1.7600", "7" }, + { "6.1.7600.16385", "7" }, + { "6.1.7601", "7 SP1" }, + { "6.1.8400", "Home Server 2011" }, + { "6.2.8102", "8 Developer Preview" }, + { "6.2.9200", "8" }, + { "6.2.9200.16384", "8 RTM" }, + { "6.2.10211", "Phone 8" }, + { "6.3.9600", "8.1" }, + { "6.4.9841", "10 Technical Preview 1" }, + { "6.4.9860", "10 Technical Preview 2" }, + { "6.4.9879", "10 Technical Preview 3" }, + { "10.0.9926", "10 Technical Preview 4" }, + { "10.0.10041", "10 Technical Preview 5" }, + { "10.0.10049", "10 Technical Preview 6" }, + { "10.0.10240", "10 Threshold 1 [1507, RTM]" }, + { "10.0.10586", "10 Threshold 2 [1511, November Update]" }, + { "10.0.14393", "10 Redstone 1 [1607, Anniversary Update]" }, + { "10.0.15063", "10 Redstone 2 [1703, Creators Update]" }, + { "10.0.16299", "10 Redstone 3 [1709, Fall Creators Update]" }, + { "10.0.17134", "10 Redstone 4 [1803, April 2018 Update]" }, + { "10.0.17763", "10 Redstone 5 [1809, October 2018 Update]" }, + { "10.0.18362", "10 19H1 [1903, May 2019 Update]" }, + { "10.0.18363", "10 19H2 [1909, November 2019 Update]" }, + { "10.0.19041", "10 20H1 [2004, May 2020 Update]" }, + { "10.0.19042", "10 20H2 [20H2, October 2020 Update]" }, + { "10.0.19043", "10 21H1 [21H1, May 2021 Update]" }, + { "10.0.19044", "10 21H2 [21H2, November 2021 Update]" }, + { "10.0.19045", "10 22H2 [22H2, 2022 Update]" }, + { "10.0.22000", "11 21H2 [21H2, Sun Valley]" }, + { "10.0.22621", "11 22H2 [22H2, Sun Valley 2]" }, + }; + + private class Windows1XVersion + { + private string? CodeVersion { get; } + private string? CodeName { get; } + private string? MarketingName { get; } + private int BuildNumber { get; } + + private string MarketingNumber => BuildNumber >= 22000 ? "11" : "10"; + private string? CompactedCodeName => CodeName?.Replace(" ", ""); + private string? CompactedMarketingName => MarketingName?.Replace(" ", ""); + + private Windows1XVersion(string? codeVersion, string? codeName, string? marketingName, int buildNumber) + { + CodeVersion = codeVersion; + CodeName = codeName; + MarketingName = marketingName; + BuildNumber = buildNumber; + } + + private string ToFullVersion(string? ubr = null) + => ubr == null ? $"10.0.{BuildNumber}" : $"10.0.{BuildNumber}.{ubr}"; + + private static string Collapse(params string?[] values) => + string.Join("/", values.Where(v => !string.IsNullOrEmpty(v))); + + // The line with OsBrandString is one of the longest lines in the summary. + // When people past in on GitHub, it can be a reason of an ugly horizontal scrollbar. + // To avoid this, we are trying to minimize this line and use the minimum possible number of characters. + public string ToPrettifiedString(string? ubr) + => CodeVersion == CompactedCodeName + ? $"{MarketingNumber} ({Collapse(ToFullVersion(ubr), CodeVersion, CompactedMarketingName)})" + : $"{MarketingNumber} ({Collapse(ToFullVersion(ubr), CodeVersion, CompactedMarketingName, CompactedCodeName)})"; + + // See https://en.wikipedia.org/wiki/Windows_10_version_history + // See https://en.wikipedia.org/wiki/Windows_11_version_history + private static readonly List WellKnownVersions = new () + { + // Windows 10 + new Windows1XVersion("1507", "Threshold 1", "RTM", 10240), + new Windows1XVersion("1511", "Threshold 2", "November Update", 10586), + new Windows1XVersion("1607", "Redstone 1", "Anniversary Update", 14393), + new Windows1XVersion("1703", "Redstone 2", "Creators Update", 15063), + new Windows1XVersion("1709", "Redstone 3", "Fall Creators Update", 16299), + new Windows1XVersion("1803", "Redstone 4", "April 2018 Update", 17134), + new Windows1XVersion("1809", "Redstone 5", "October 2018 Update", 17763), + new Windows1XVersion("1903", "19H1", "May 2019 Update", 18362), + new Windows1XVersion("1909", "19H2", "November 2019 Update", 18363), + new Windows1XVersion("2004", "20H1", "May 2020 Update", 19041), + new Windows1XVersion("20H2", "20H2", "October 2020 Update", 19042), + new Windows1XVersion("21H1", "21H1", "May 2021 Update", 19043), + new Windows1XVersion("21H2", "21H2", "November 2021 Update", 19044), + new Windows1XVersion("22H2", "22H2", "2022 Update", 19045), + // Windows 11 + new Windows1XVersion("21H2", "Sun Valley", null, 22000), + new Windows1XVersion("22H2", "Sun Valley 2", "2022 Update", 22621), + new Windows1XVersion("23H2", "Sun Valley 3", "2023 Update", 22631), + }; + + public static Windows1XVersion? Resolve(string osVersionString) + { + var windows1XVersion = WellKnownVersions.FirstOrDefault(v => osVersionString == $"10.0.{v.BuildNumber}"); + if (windows1XVersion != null) + return windows1XVersion; + if (Version.TryParse(osVersionString, out var osVersion)) + { + if (osVersion.Major == 10 && osVersion.Minor == 0) + return new Windows1XVersion(null, null, null, osVersion.Build); + } + return null; + } + } + + /// + /// Transform an operation system name and version to a nice form for summary. + /// + /// Original operation system name + /// Original operation system version + /// UBR (Update Build Revision), the revision number of Windows version (if available) + /// Prettified operation system title + public static string Prettify(string osName, string osVersion, string? windowsUbr = null) + { + if (osName == "Windows") + return PrettifyWindows(osVersion, windowsUbr); + return $"{osName} {osVersion}"; + } + + private static string PrettifyWindows(string osVersion, string? windowsUbr) + { + var windows1XVersion = Windows1XVersion.Resolve(osVersion); + if (windows1XVersion != null) + return "Windows " + windows1XVersion.ToPrettifiedString(windowsUbr); + + string? brandVersion = WindowsBrandVersions.GetValueOrDefault(osVersion); + string completeOsVersion = windowsUbr != null && osVersion.Count(c => c == '.') == 2 + ? osVersion + "." + windowsUbr + : osVersion; + string fullVersion = brandVersion == null ? osVersion : brandVersion + " (" + completeOsVersion + ")"; + return "Windows " + fullVersion; + } + + private class MacOSXVersion + { + private int DarwinVersion { get; } + private string CodeName { get; } + + private MacOSXVersion(int darwinVersion, string codeName) + { + DarwinVersion = darwinVersion; + CodeName = codeName; + } + + private static readonly List WellKnownVersions = + [ + new MacOSXVersion(6, "Jaguar"), + new MacOSXVersion(7, "Panther"), + new MacOSXVersion(8, "Tiger"), + new MacOSXVersion(9, "Leopard"), + new MacOSXVersion(10, "Snow Leopard"), + new MacOSXVersion(11, "Lion"), + new MacOSXVersion(12, "Mountain Lion"), + new MacOSXVersion(13, "Mavericks"), + new MacOSXVersion(14, "Yosemite"), + new MacOSXVersion(15, "El Capitan"), + new MacOSXVersion(16, "Sierra"), + new MacOSXVersion(17, "High Sierra"), + new MacOSXVersion(18, "Mojave"), + new MacOSXVersion(19, "Catalina"), + new MacOSXVersion(20, "Big Sur"), + new MacOSXVersion(21, "Monterey"), + new MacOSXVersion(22, "Ventura"), + new MacOSXVersion(23, "Sonoma"), + new MacOSXVersion(24, "Sequoia"), + ]; + + public static string? ResolveCodeName(string kernelVersion) + { + if (string.IsNullOrWhiteSpace(kernelVersion)) + return null; + + kernelVersion = kernelVersion.ToLowerInvariant().Trim(); + if (kernelVersion.StartsWith("darwin")) + kernelVersion = kernelVersion.Substring(6).Trim(); + var numbers = kernelVersion.Split('.'); + if (numbers.Length == 0) + return null; + + string majorVersionStr = numbers[0]; + if (int.TryParse(majorVersionStr, out int majorVersion)) + return WellKnownVersions.FirstOrDefault(v => v.DarwinVersion == majorVersion)?.CodeName; + return null; + } + } + + public static string PrettifyMacOSX(string systemVersion, string kernelVersion) + { + string? codeName = MacOSXVersion.ResolveCodeName(kernelVersion); + if (codeName != null) + { + int firstDigitIndex = systemVersion.IndexOfAny("0123456789".ToCharArray()); + if (firstDigitIndex == -1) + return $"{systemVersion} {codeName} [{kernelVersion}]"; + + string systemVersionTitle = systemVersion.Substring(0, firstDigitIndex).Trim(); + string systemVersionNumbers = systemVersion.Substring(firstDigitIndex).Trim(); + return $"{systemVersionTitle} {codeName} {systemVersionNumbers} [{kernelVersion}]"; + } + + return $"{systemVersion} [{kernelVersion}]"; + } + + public static string ToBrandString(this PhdOs os) + { + string? display = os.Display; + if (display != null) return display; + + string? name = os.Name; + if (name == null) return ""; + + if (name.EquationsIgnoreCase("macos") && os is { Version: not null, KernelVersion: not null }) + return PrettifyMacOSX(os.Version, os.KernelVersion); + if (name.EquationsIgnoreCase("windows")) + { + string? fullVersion = os.Version; + if (fullVersion != null) + { + string baseVersion = fullVersion.Split('.').Take(3).JoinToString("."); + string? ubr = fullVersion.Split('.').Skip(3).FirstOrDefault(); + return PrettifyWindows(baseVersion, ubr); + } + } + return name + " " + os.Version; + } +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Helpers/ResourceHelper.cs b/src/Perfolizer/Perfolizer/Helpers/ResourceHelper.cs new file mode 100644 index 00000000..bc0ff6a6 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Helpers/ResourceHelper.cs @@ -0,0 +1,18 @@ +using System.Reflection; + +namespace Perfolizer.Helpers; + +internal static class ResourceHelper +{ + internal static string LoadResource(string resourceName) + { + using var stream = GetResourceStream(resourceName); + if (stream == null) throw new Exception($"Resource {resourceName} not found"); + + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } + + private static Stream? GetResourceStream(string resourceName) => + typeof(ResourceHelper).GetTypeInfo().Assembly.GetManifestResourceStream(resourceName); +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Helpers/StringHelper.cs b/src/Perfolizer/Perfolizer/Helpers/StringHelper.cs new file mode 100644 index 00000000..7650126a --- /dev/null +++ b/src/Perfolizer/Perfolizer/Helpers/StringHelper.cs @@ -0,0 +1,8 @@ +using Perfolizer.Extensions; + +namespace Perfolizer.Helpers; + +public static class StringHelper +{ + public static string FirstUpper(string s) => s.IsBlank() ? s : char.ToUpper(s[0]) + s.Substring(1); +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Helpers/UnicodeHelper.cs b/src/Perfolizer/Perfolizer/Helpers/UnicodeHelper.cs index b7c5d853..6dfab2c5 100644 --- a/src/Perfolizer/Perfolizer/Helpers/UnicodeHelper.cs +++ b/src/Perfolizer/Perfolizer/Helpers/UnicodeHelper.cs @@ -21,7 +21,7 @@ public static string ConvertToAscii(this string s) var builder = new StringBuilder(); foreach (char c in s) - builder.Append(CharMap.TryGetValue(c, out string replacement) ? replacement : c.ToString()); + builder.Append(CharMap.TryGetValue(c, out string? replacement) ? replacement : c.ToString()); return builder.ToString(); } } \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Horology/Frequency.cs b/src/Perfolizer/Perfolizer/Horology/Frequency.cs index 479b32fb..f120ee48 100644 --- a/src/Perfolizer/Perfolizer/Horology/Frequency.cs +++ b/src/Perfolizer/Perfolizer/Horology/Frequency.cs @@ -53,13 +53,13 @@ [PublicAPI] public Frequency(double value, FrequencyUnit unit) : this(value * un [PublicAPI] public static bool operator !=(Frequency a, Frequency b) => !a.Hertz.Equals(b.Hertz); [PublicAPI] - public static bool TryParse([NotNullWhen(true)] string? s, FrequencyUnit unit, out Frequency freq) + public static bool TryParse(string? s, FrequencyUnit unit, out Frequency freq) { return TryParse(s, unit, NumberStyles.Any, DefaultCultureInfo.Instance, out freq); } [PublicAPI] - public static bool TryParse([NotNullWhen(true)] string? s, FrequencyUnit unit, NumberStyles numberStyle, + public static bool TryParse(string? s, FrequencyUnit unit, NumberStyles numberStyle, IFormatProvider formatProvider, out Frequency freq) { bool success = double.TryParse(s, numberStyle, formatProvider, out double result); @@ -67,35 +67,35 @@ public static bool TryParse([NotNullWhen(true)] string? s, FrequencyUnit unit, N return success; } - [PublicAPI] public static bool TryParseHz([NotNullWhen(true)] string? s, out Frequency freq) => + [PublicAPI] public static bool TryParseHz(string? s, out Frequency freq) => TryParse(s, FrequencyUnit.Hz, out freq); [PublicAPI] - public static bool TryParseHz([NotNullWhen(true)] string? s, NumberStyles numberStyle, + public static bool TryParseHz(string? s, NumberStyles numberStyle, IFormatProvider formatProvider, out Frequency freq) => TryParse(s, FrequencyUnit.Hz, numberStyle, formatProvider, out freq); - [PublicAPI] public static bool TryParseKHz([NotNullWhen(true)] string? s, out Frequency freq) => + [PublicAPI] public static bool TryParseKHz(string? s, out Frequency freq) => TryParse(s, FrequencyUnit.KHz, out freq); [PublicAPI] - public static bool TryParseKHz([NotNullWhen(true)] string? s, NumberStyles numberStyle, + public static bool TryParseKHz(string? s, NumberStyles numberStyle, IFormatProvider formatProvider, out Frequency freq) => TryParse(s, FrequencyUnit.KHz, numberStyle, formatProvider, out freq); - [PublicAPI] public static bool TryParseMHz([NotNullWhen(true)] string? s, out Frequency freq) => + [PublicAPI] public static bool TryParseMHz(string? s, out Frequency freq) => TryParse(s, FrequencyUnit.MHz, out freq); [PublicAPI] - public static bool TryParseMHz([NotNullWhen(true)] string? s, NumberStyles numberStyle, + public static bool TryParseMHz(string? s, NumberStyles numberStyle, IFormatProvider formatProvider, out Frequency freq) => TryParse(s, FrequencyUnit.MHz, numberStyle, formatProvider, out freq); - [PublicAPI] public static bool TryParseGHz([NotNullWhen(true)] string? s, out Frequency freq) => + [PublicAPI] public static bool TryParseGHz(string? s, out Frequency freq) => TryParse(s, FrequencyUnit.GHz, out freq); [PublicAPI] - public static bool TryParseGHz([NotNullWhen(true)] string? s, NumberStyles numberStyle, + public static bool TryParseGHz(string? s, NumberStyles numberStyle, IFormatProvider formatProvider, out Frequency freq) => TryParse(s, FrequencyUnit.GHz, numberStyle, formatProvider, out freq); @@ -129,13 +129,13 @@ public string ToString( frequencyUnit ??= FrequencyUnit.GetBestFrequencyUnit(Hertz); format ??= DefaultFormat; double nominalValue = FrequencyUnit.Convert(Hertz, FrequencyUnit.Hz, frequencyUnit); - var measurementValue = new MeasurementValue(nominalValue, frequencyUnit); + var measurementValue = new Measurement(nominalValue, frequencyUnit); return measurementValue.ToString(format, formatProvider, unitPresentation); } public bool Equals(Frequency other) => Hertz.Equals(other.Hertz); public bool Equals(Frequency other, double hertzEpsilon) => Abs(Hertz - other.Hertz) < hertzEpsilon; - public override bool Equals([NotNullWhen(true)] object? obj) => obj is Frequency other && Equals(other); + public override bool Equals(object? obj) => obj is Frequency other && Equals(other); public override int GetHashCode() => Hertz.GetHashCode(); public int CompareTo(Frequency other) => Hertz.CompareTo(other.Hertz); } \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Horology/TimeInterval.cs b/src/Perfolizer/Perfolizer/Horology/TimeInterval.cs index 5d166220..bc2dfaba 100644 --- a/src/Perfolizer/Perfolizer/Horology/TimeInterval.cs +++ b/src/Perfolizer/Perfolizer/Horology/TimeInterval.cs @@ -1,5 +1,4 @@ -using System.Diagnostics.CodeAnalysis; -using JetBrains.Annotations; +using JetBrains.Annotations; using Perfolizer.Exceptions; using Perfolizer.Metrology; @@ -12,11 +11,9 @@ public readonly struct TimeInterval(double nanoseconds) public double Nanoseconds { get; } = nanoseconds; - public TimeInterval(double value, TimeUnit unit) : this(value * unit.BaseUnits) - { - } + public TimeInterval(double value, TimeUnit unit) : this(value * unit.BaseUnits) { } - [PublicAPI] public static readonly TimeInterval Zero = new(0); + [PublicAPI] public static readonly TimeInterval Zero = new (0); public static readonly TimeInterval Nanosecond = TimeUnit.Nanosecond.ToInterval(); public static readonly TimeInterval Microsecond = TimeUnit.Microsecond.ToInterval(); public static readonly TimeInterval Millisecond = TimeUnit.Millisecond.ToInterval(); @@ -25,8 +22,8 @@ public TimeInterval(double value, TimeUnit unit) : this(value * unit.BaseUnits) public static readonly TimeInterval Hour = TimeUnit.Hour.ToInterval(); public static readonly TimeInterval Day = TimeUnit.Day.ToInterval(); - public Frequency ToFrequency() => new(Second / this); - public TimeInterval Abs() => new(Math.Abs(Nanoseconds)); + public Frequency ToFrequency() => new (Second / this); + public TimeInterval Abs() => new (Math.Abs(Nanoseconds)); public double ToNanoseconds() => this / Nanosecond; public double ToMicroseconds() => this / Microsecond; @@ -45,12 +42,12 @@ public TimeInterval(double value, TimeUnit unit) : this(value * unit.BaseUnits) public static TimeInterval FromDays(double value) => Day * value; public static double operator /(TimeInterval a, TimeInterval b) => 1.0 * a.Nanoseconds / b.Nanoseconds; - public static TimeInterval operator /(TimeInterval a, double k) => new(a.Nanoseconds / k); - public static TimeInterval operator /(TimeInterval a, int k) => new(a.Nanoseconds / k); - public static TimeInterval operator *(TimeInterval a, double k) => new(a.Nanoseconds * k); - public static TimeInterval operator *(TimeInterval a, int k) => new(a.Nanoseconds * k); - public static TimeInterval operator *(double k, TimeInterval a) => new(a.Nanoseconds * k); - public static TimeInterval operator *(int k, TimeInterval a) => new(a.Nanoseconds * k); + public static TimeInterval operator /(TimeInterval a, double k) => new (a.Nanoseconds / k); + public static TimeInterval operator /(TimeInterval a, int k) => new (a.Nanoseconds / k); + public static TimeInterval operator *(TimeInterval a, double k) => new (a.Nanoseconds * k); + public static TimeInterval operator *(TimeInterval a, int k) => new (a.Nanoseconds * k); + public static TimeInterval operator *(double k, TimeInterval a) => new (a.Nanoseconds * k); + public static TimeInterval operator *(int k, TimeInterval a) => new (a.Nanoseconds * k); public static bool operator <(TimeInterval a, TimeInterval b) => a.Nanoseconds < b.Nanoseconds; public static bool operator >(TimeInterval a, TimeInterval b) => a.Nanoseconds > b.Nanoseconds; public static bool operator <=(TimeInterval a, TimeInterval b) => a.Nanoseconds <= b.Nanoseconds; @@ -77,7 +74,7 @@ public string ToString( timeUnit ??= TimeUnit.GetBestTimeUnit(Nanoseconds); format ??= DefaultFormat; double nominalValue = TimeUnit.Convert(Nanoseconds, TimeUnit.Nanosecond, timeUnit); - var measurementValue = new MeasurementValue(nominalValue, timeUnit); + var measurementValue = new Measurement(nominalValue, timeUnit); return measurementValue.ToString(format, formatProvider, unitPresentation); } @@ -87,7 +84,7 @@ public string ToString( public bool Equals(TimeInterval other, double nanosecondEpsilon) => Math.Abs(other.Nanoseconds - Nanoseconds) < nanosecondEpsilon; - public override bool Equals([NotNullWhen(true)] object? obj) => obj is TimeInterval other && Equals(other); + public override bool Equals(object? obj) => obj is TimeInterval other && Equals(other); public override int GetHashCode() => Nanoseconds.GetHashCode(); public MeasurementUnit Unit => TimeUnit.Nanosecond; diff --git a/src/Perfolizer/Perfolizer/Horology/TimeUnit.cs b/src/Perfolizer/Perfolizer/Horology/TimeUnit.cs index 49262ebd..ed656ecc 100644 --- a/src/Perfolizer/Perfolizer/Horology/TimeUnit.cs +++ b/src/Perfolizer/Perfolizer/Horology/TimeUnit.cs @@ -55,6 +55,6 @@ public static bool TryParse(string s, out TimeUnit unit) return false; } - public static TimeUnit Parse(string s) => + public new static TimeUnit Parse(string s) => TryParse(s, out var unit) ? unit : throw new FormatException($"Unknown time unit: {s}"); } \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Horology/WindowsClock.cs b/src/Perfolizer/Perfolizer/Horology/WindowsClock.cs index bdd52c73..0f8920b9 100644 --- a/src/Perfolizer/Perfolizer/Horology/WindowsClock.cs +++ b/src/Perfolizer/Perfolizer/Horology/WindowsClock.cs @@ -17,7 +17,12 @@ internal class WindowsClock : IClock [DllImport("kernel32.dll")] private static extern bool QueryPerformanceFrequency(out long value); - [HandleProcessCorruptedStateExceptions] // #276 + // 'HandleProcessCorruptedStateExceptionsAttribute' is obsolete in net6.0 because + // recovery from corrupted process state exceptions is not supported. + // https://aka.ms/dotnet-warnings/SYSLIB0032 +#if NETSTANDARD2_0 + [HandleProcessCorruptedStateExceptions] // https://github.com/dotnet/BenchmarkDotNet/issues/276 +#endif [SecurityCritical] private static bool Initialize(out long qpf) { diff --git a/src/Perfolizer/Perfolizer/Json/LightJsonSerializer.cs b/src/Perfolizer/Perfolizer/Json/LightJsonSerializer.cs new file mode 100644 index 00000000..642b89f2 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Json/LightJsonSerializer.cs @@ -0,0 +1,229 @@ +using System.Collections; +using System.Text; +using Perfolizer.Common; +using Perfolizer.Extensions; +using Perfolizer.Metrology; +using Perfolizer.Phd.Base; + +namespace Perfolizer.Json; + +public class LightJsonSerializer +{ + private const int IndentStep = 2; + + public static string Serialize(object obj, LightJsonSettings? settings = null) => + new LightJsonSerializer(settings).Append(obj).ToString(); + + private readonly LightJsonSettings settings; + private int currentIndent; + private readonly StringBuilder builder = new (); + + private LightJsonSerializer(LightJsonSettings? settings = null) + { + this.settings = settings ?? new LightJsonSettings(); + } + + public override string ToString() => builder.ToString(); + + private LightJsonSerializer Append(object obj) + { + switch (obj) + { + case IDictionary dictionary: + AppendDictionary(dictionary); + break; + case string stringValue: + AppendString(stringValue); + break; + case int intValue: + AppendInt(intValue); + break; + case long longValue: + AppendLong(longValue); + break; + case double doubleValue: + AppendDouble(doubleValue); + break; + case bool boolValue: + AppendBool(boolValue); + break; + case Guid guidValue: + AppendGuidValue(guidValue); + break; + case DateTimeOffset dateTimeOffsetValue: + AppendDateTimeOffset(dateTimeOffsetValue); + break; + case MeasurementUnit measurementUnit: + AppendMeasurementUnit(measurementUnit); + break; + case PhdMetric metric: + AppendMetric(metric); + break; + case ICollection collection: + AppendCollection(collection); + break; + case Enum enumValue: + AppendEnum(enumValue); + break; + case PhdObject phdObject: + AppendPhdObject(phdObject); + break; + default: + throw new NotSupportedException($"Unsupported type: {obj.GetType()}"); + } + + return this; + } + + private void AppendPhdObject(PhdObject phdObject) + { + builder.Append('{'); + currentIndent += IndentStep; + + bool isFirst = true; + foreach (var property in phdObject.GetType().GetProperties()) + { + object? value; + try + { + value = property.GetValue(phdObject); + } + catch (Exception) + { + continue; + } + if (value == null || IsEmptyValue(value)) + continue; + + string key = property.Name.ToCamelCase(); + AppendValue(isFirst, key, value); + isFirst = false; + } + currentIndent -= IndentStep; + AppendNextLine(); + AppendIndent(); + builder.Append("}"); + } + + private void AppendDictionary(IDictionary dictionary) + { + builder.Append('{'); + currentIndent += IndentStep; + + bool isFirst = true; + foreach (DictionaryEntry dictionaryEntry in dictionary) + { + string? key = dictionaryEntry.Key.ToString(); + if (key == null) + continue; + + object? value = dictionaryEntry.Value; + if (value == null || IsEmptyValue(value)) + continue; + + AppendValue(isFirst, key.ToLowerInvariant(), value); + isFirst = false; + } + currentIndent -= IndentStep; + AppendNextLine(); + AppendIndent(); + builder.Append("}"); + } + + private void AppendValue(bool isFirst, string key, object value) + { + if (!isFirst) + builder.Append(','); + AppendNextLine(); + AppendIndent(); + builder.Append('\"'); + builder.Append(key); + builder.Append('\"'); + builder.Append(':'); + AppendSpace(); + Append(value); + } + + private static bool IsEmptyValue(object? value) + { + if (value == null) + return true; + if (value is string stringValue && stringValue.IsBlank()) + return true; + if (value is NumberUnit) + return true; + if (value is ICollection { Count: 0 }) + return true; + if (value is PhdMetric metric && metric.IsEmpty) + return true; + return false; + } + + private void AppendCollection(ICollection collection) + { + builder.Append('['); + currentIndent += IndentStep; + bool isFirst = true; + foreach (object value in collection) + { + if (value == null) + continue; + + if (!isFirst) + builder.Append(','); + AppendNextLine(); + AppendIndent(); + Append(value); + isFirst = false; + } + currentIndent -= IndentStep; + AppendNextLine(); + AppendIndent(); + builder.Append(']'); + } + + private void AppendString(string value) + { + builder.Append('\"'); + builder.Append(value); + builder.Append('\"'); + } + + private void AppendBool(bool value) => builder.Append(value ? "true" : "false"); + private void AppendEnum(Enum enumValue) => AppendString(enumValue.ToString().ToCamelCase()); + private void AppendGuidValue(Guid value) => AppendString(value.ToString()); + private void AppendDateTimeOffset(DateTimeOffset value) => AppendLong(value.ToUnixTimeMilliseconds()); + + private void AppendMeasurementUnit(MeasurementUnit unit) + { + AppendString(unit.AbbreviationAscii); + } + + private void AppendMetric(PhdMetric metric) + { + if (metric.IsEmpty) + builder.Append("\"\""); + else if (metric.Version == null && metric.Id != null) + AppendString(metric.Id); + else + AppendPhdObject(metric); + } + + private void AppendInt(int value) => builder.Append(value.ToString(DefaultCultureInfo.Instance)); + private void AppendLong(long value) => builder.Append(value.ToString(DefaultCultureInfo.Instance)); + private void AppendDouble(double value) => builder.Append(value.ToString(DefaultCultureInfo.Instance)); + + private void AppendIndent() => builder.Append(' ', settings.Indent ? currentIndent : 0); + + private void AppendNextLine() + { + if (settings.Indent) + builder.AppendLine(); + } + + private void AppendSpace() + { + if (settings.Indent) + builder.Append(' '); + } +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Json/LightJsonSettings.cs b/src/Perfolizer/Perfolizer/Json/LightJsonSettings.cs new file mode 100644 index 00000000..c8af037c --- /dev/null +++ b/src/Perfolizer/Perfolizer/Json/LightJsonSettings.cs @@ -0,0 +1,6 @@ +namespace Perfolizer.Json; + +public class LightJsonSettings +{ + public bool Indent { get; set; } +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Mathematics/Common/ConfidenceInterval.cs b/src/Perfolizer/Perfolizer/Mathematics/Common/ConfidenceInterval.cs index 25fb74b3..716d95c1 100644 --- a/src/Perfolizer/Perfolizer/Mathematics/Common/ConfidenceInterval.cs +++ b/src/Perfolizer/Perfolizer/Mathematics/Common/ConfidenceInterval.cs @@ -72,7 +72,7 @@ public bool Equals(ConfidenceInterval other, IEqualityComparer comparer) comparer.Equals(ConfidenceLevel, other.ConfidenceLevel); } - public override bool Equals([NotNullWhen(true)] object? obj) + public override bool Equals(object? obj) { return obj is ConfidenceInterval other && Equals(other); } diff --git a/src/Perfolizer/Perfolizer/Mathematics/Common/PrecisionHelper.cs b/src/Perfolizer/Perfolizer/Mathematics/Common/PrecisionHelper.cs new file mode 100644 index 00000000..7c3e2fa5 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Mathematics/Common/PrecisionHelper.cs @@ -0,0 +1,14 @@ +namespace Perfolizer.Mathematics.Common; + +public static class PrecisionHelper +{ + public static int GetOptimalPrecision(IReadOnlyList values) + { + double minLog = values + .Where(value => value > 0) + .Select(Log10) + .DefaultIfEmpty(0) + .Min(); + return Max(1, -(minLog - 2).RoundToInt()); + } +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Mathematics/Common/Probability.cs b/src/Perfolizer/Perfolizer/Mathematics/Common/Probability.cs index 1809aaaa..21ec59bc 100644 --- a/src/Perfolizer/Perfolizer/Mathematics/Common/Probability.cs +++ b/src/Perfolizer/Perfolizer/Mathematics/Common/Probability.cs @@ -29,7 +29,7 @@ public override string ToString() return Value.ToString(DefaultCultureInfo.Instance); } - public int CompareTo(object obj) + public int CompareTo(object? obj) { return Value.CompareTo(obj); } @@ -39,7 +39,7 @@ public string ToString(IFormatProvider formatProvider) return Value.ToString(formatProvider); } - public string ToString(string format, IFormatProvider? formatProvider = null) + public string ToString(string? format, IFormatProvider? formatProvider = null) { return Value.ToString(format, formatProvider ?? DefaultCultureInfo.Instance); } @@ -49,7 +49,7 @@ public bool Equals(Probability other) return Value.Equals(other.Value); } - public override bool Equals([NotNullWhen(true)] object? obj) + public override bool Equals(object? obj) { return obj is Probability other && Equals(other); } @@ -64,11 +64,8 @@ public int CompareTo(Probability other) return Value.CompareTo(other.Value); } - [return: NotNullIfNotNull("values")] - public static Probability[]? ToProbabilities(double[]? values) + public static Probability[] ToProbabilities(double[] values) { - if (values == null) - return null; var probabilities = new Probability[values.Length]; for (int i = 0; i < values.Length; i++) probabilities[i] = values[i]; diff --git a/src/Perfolizer/Perfolizer/Mathematics/Histograms/QuantileRespectfulDensityHistogramBuilder.cs b/src/Perfolizer/Perfolizer/Mathematics/Histograms/QuantileRespectfulDensityHistogramBuilder.cs index 2d71a2df..a0314e8f 100644 --- a/src/Perfolizer/Perfolizer/Mathematics/Histograms/QuantileRespectfulDensityHistogramBuilder.cs +++ b/src/Perfolizer/Perfolizer/Mathematics/Histograms/QuantileRespectfulDensityHistogramBuilder.cs @@ -26,8 +26,8 @@ public DensityHistogram Build(Sample sample, int binCount, IQuantileEstimator? q if (sample.IsWeighted && !quantileEstimator.SupportsWeightedSamples) throw new WeightedSampleNotSupportedException(); - var probabilities = - Probability.ToProbabilities(new ArithmeticProgressionSequence(0, 1.0 / binCount).GenerateArray(binCount + 1)); + double[] probabilityValues = new ArithmeticProgressionSequence(0, 1.0 / binCount).GenerateArray(binCount + 1); + var probabilities = Probability.ToProbabilities(probabilityValues); double[] quantiles = quantileEstimator.Quantiles(sample, probabilities); var bins = new List(binCount); diff --git a/src/Perfolizer/Perfolizer/Mathematics/Multimodality/LowlandModalityDetector.cs b/src/Perfolizer/Perfolizer/Mathematics/Multimodality/LowlandModalityDetector.cs index f86c411e..896b15e2 100644 --- a/src/Perfolizer/Perfolizer/Mathematics/Multimodality/LowlandModalityDetector.cs +++ b/src/Perfolizer/Perfolizer/Mathematics/Multimodality/LowlandModalityDetector.cs @@ -65,7 +65,7 @@ RangedMode LocalMode(double location, double left, double right) modeWeights?.Add(sample.SortedWeights[i]); } } - if (modeValues.IsEmpty()) + if (modeValues.Count == 0) throw new InvalidOperationException( $"Can't find any values in [{left.ToStringInvariant()}, {right.ToStringInvariant()}]"); diff --git a/src/Perfolizer/Perfolizer/Mathematics/QuantileEstimators/TrimmedHarrellDavisQuantileEstimator.cs b/src/Perfolizer/Perfolizer/Mathematics/QuantileEstimators/TrimmedHarrellDavisQuantileEstimator.cs index f3611137..f5f6e6e6 100644 --- a/src/Perfolizer/Perfolizer/Mathematics/QuantileEstimators/TrimmedHarrellDavisQuantileEstimator.cs +++ b/src/Perfolizer/Perfolizer/Mathematics/QuantileEstimators/TrimmedHarrellDavisQuantileEstimator.cs @@ -12,7 +12,7 @@ public class TrimmedHarrellDavisQuantileEstimator : IQuantileEstimator { private readonly Func getIntervalWidth; - public static readonly IQuantileEstimator SqrtInstance = new TrimmedHarrellDavisQuantileEstimator(n => 1.0 / Sqrt(n), "SQRT"); + public static readonly IQuantileEstimator Sqrt = new TrimmedHarrellDavisQuantileEstimator(n => 1.0 / Sqrt(n), "SQRT"); public TrimmedHarrellDavisQuantileEstimator(Func getIntervalWidth, string? alias = null) { diff --git a/src/Perfolizer/Perfolizer/Mathematics/Reference/ReferenceDistribution.cs b/src/Perfolizer/Perfolizer/Mathematics/Reference/ReferenceDistribution.cs index 7746e615..ed6d1587 100644 --- a/src/Perfolizer/Perfolizer/Mathematics/Reference/ReferenceDistribution.cs +++ b/src/Perfolizer/Perfolizer/Mathematics/Reference/ReferenceDistribution.cs @@ -2,30 +2,9 @@ namespace Perfolizer.Mathematics.Reference; -public class ReferenceDistribution +public class ReferenceDistribution(string key, string description, IContinuousDistribution distribution) { - public string Key { get; } - public string Description { get; } - public IContinuousDistribution Distribution { get; } - - public ReferenceDistribution(IContinuousDistribution distribution) - { - Key = Description = distribution.ToString(); - Distribution = distribution; - } - - public ReferenceDistribution(string key, IContinuousDistribution distribution) - { - Key = key; - Description = distribution.ToString(); - Distribution = distribution; - } - - - public ReferenceDistribution(string key, string description, IContinuousDistribution distribution) - { - Key = key; - Description = description; - Distribution = distribution; - } + public string Key { get; } = key; + public string Description { get; } = description; + public IContinuousDistribution Distribution { get; } = distribution; } \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Metrology/EffectSizeValue.cs b/src/Perfolizer/Perfolizer/Metrology/EffectSizeValue.cs index 7fe35c93..2caed0d8 100644 --- a/src/Perfolizer/Perfolizer/Metrology/EffectSizeValue.cs +++ b/src/Perfolizer/Perfolizer/Metrology/EffectSizeValue.cs @@ -16,7 +16,7 @@ public string ToString( UnitPresentation? unitPresentation = null) { format ??= DefaultFormat; - var measurementValue = new MeasurementValue(Value, EffectSizeUnit.Instance); + var measurementValue = new Measurement(Value, EffectSizeUnit.Instance); return measurementValue.ToString(format, formatProvider, unitPresentation); } diff --git a/src/Perfolizer/Perfolizer/Metrology/MeasurementValue.cs b/src/Perfolizer/Perfolizer/Metrology/Measurement.cs similarity index 59% rename from src/Perfolizer/Perfolizer/Metrology/MeasurementValue.cs rename to src/Perfolizer/Perfolizer/Metrology/Measurement.cs index 05469e53..436361be 100644 --- a/src/Perfolizer/Perfolizer/Metrology/MeasurementValue.cs +++ b/src/Perfolizer/Perfolizer/Metrology/Measurement.cs @@ -7,9 +7,9 @@ namespace Perfolizer.Metrology; -public class MeasurementValue(double nominalValue, MeasurementUnit unit) : IWithUnits +public class Measurement(double nominalValue, MeasurementUnit unit) : IWithUnits, IComparable, IComparable { - public static readonly MeasurementValue Zero = new(0, NumberUnit.Instance); + public static readonly Measurement Zero = new (0, NumberUnit.Instance); protected virtual string DefaultFormat => "G"; @@ -66,7 +66,7 @@ public string ToString( return $"{nominalValue}{Unit.ToString(unitPresentation)}"; } - public static bool TryParse(string s, out MeasurementValue value) + public static bool TryParse(string s, out Measurement value) { if (s.IsNotBlank()) { @@ -77,17 +77,19 @@ public static bool TryParse(string s, out MeasurementValue value) } } - value = new MeasurementValue(0, NumberUnit.Instance); + value = new Measurement(0, NumberUnit.Instance); return false; } - public static bool TryParse(string s, MeasurementUnit unit, out MeasurementValue value) + public static Measurement Parse(string s) => TryParse(s, out var value) ? value : throw new FormatException($"Cannot parse {s} as a measurement"); + + public static bool TryParse(string s, MeasurementUnit unit, out Measurement value) { if (TryParseBySuffix(unit.Abbreviation, out double nominalValue) || TryParseBySuffix(unit.AbbreviationAscii, out nominalValue) || TryParseBySuffix(unit.FullName, out nominalValue)) { - value = new MeasurementValue(nominalValue, unit); + value = new Measurement(nominalValue, unit); return true; } @@ -107,4 +109,48 @@ bool TryParseBySuffix(string suffix, out double value) return false; } } + + public static Measurement operator +(Measurement a, Measurement b) + { + Assertion.Equal(a.Unit, b.Unit); + return new Measurement(a.NominalValue + b.NominalValue, a.Unit); + } + + public static Measurement operator -(Measurement a, Measurement b) + { + Assertion.Equal(a.Unit, b.Unit); + return new Measurement(a.NominalValue - b.NominalValue, a.Unit); + } + + public int CompareTo(Measurement? other) + { + if (ReferenceEquals(this, other)) + return 0; + if (ReferenceEquals(null, other)) + return 1; + if (Unit.GetFlavor() != other.Unit.GetFlavor()) + throw new InvalidOperationException($"Cannot compare units of different flavors: {this} and {other}"); + double a = NominalValue * Unit.BaseUnits; + double b = other.NominalValue * Unit.BaseUnits; + return a.CompareTo(b); + } + + public int CompareTo(object? obj) => obj switch + { + null => 1, + Measurement other => CompareTo(other), + _ => throw new ArgumentException($"Object must be of type {nameof(Measurement)}") + }; + + public static bool operator <(Measurement? left, Measurement? right) => + Comparer.Default.Compare(left, right) < 0; + + public static bool operator >(Measurement? left, Measurement? right) => + Comparer.Default.Compare(left, right) > 0; + + public static bool operator <=(Measurement? left, Measurement? right) => + Comparer.Default.Compare(left, right) <= 0; + + public static bool operator >=(Measurement? left, Measurement? right) => + Comparer.Default.Compare(left, right) >= 0; } \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Metrology/MeasurementUnit.cs b/src/Perfolizer/Perfolizer/Metrology/MeasurementUnit.cs index b9109510..81511da6 100644 --- a/src/Perfolizer/Perfolizer/Metrology/MeasurementUnit.cs +++ b/src/Perfolizer/Perfolizer/Metrology/MeasurementUnit.cs @@ -26,7 +26,7 @@ public static IEnumerable GetAll() public long BaseUnits { get; } = baseUnits; public string AbbreviationAscii => Abbreviation.ConvertToAscii(); - public virtual string GetFlavor() => GetType().Name.Replace("Unit", ""); + public string GetFlavor() => GetType().Name.Replace("Unit", ""); public override string ToString() => Abbreviation; @@ -66,10 +66,14 @@ public static bool TryParse(string? s, out MeasurementUnit unit) public static MeasurementUnit Parse(string s) => TryParse(s, out var unit) ? unit : throw new FormatException($"Unknown unit: {s}"); - public bool Equals(MeasurementUnit other) => - Abbreviation == other.Abbreviation && - FullName == other.FullName && - BaseUnits == other.BaseUnits; + public bool Equals(MeasurementUnit? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Abbreviation == other.Abbreviation && + FullName == other.FullName && + BaseUnits == other.BaseUnits; + } public override bool Equals(object? obj) { diff --git a/src/Perfolizer/Perfolizer/Metrology/NumberValue.cs b/src/Perfolizer/Perfolizer/Metrology/NumberValue.cs index 841690cf..6a7481ca 100644 --- a/src/Perfolizer/Perfolizer/Metrology/NumberValue.cs +++ b/src/Perfolizer/Perfolizer/Metrology/NumberValue.cs @@ -17,7 +17,7 @@ public string ToString( UnitPresentation? unitPresentation = null) { format ??= DefaultFormat; - var measurementValue = new MeasurementValue(Value, NumberUnit.Instance); + var measurementValue = new Measurement(Value, NumberUnit.Instance); return measurementValue.ToString(format, formatProvider, unitPresentation); } diff --git a/src/Perfolizer/Perfolizer/Metrology/PercentValue.cs b/src/Perfolizer/Perfolizer/Metrology/PercentValue.cs index 50aff342..6d2583d2 100644 --- a/src/Perfolizer/Perfolizer/Metrology/PercentValue.cs +++ b/src/Perfolizer/Perfolizer/Metrology/PercentValue.cs @@ -17,7 +17,7 @@ public string ToString( UnitPresentation? unitPresentation = null) { format ??= DefaultFormat; - var measurementValue = new MeasurementValue(Percentage, PercentUnit.Instance); + var measurementValue = new Measurement(Percentage, PercentUnit.Instance); return measurementValue.ToString(format, formatProvider, unitPresentation); } diff --git a/src/Perfolizer/Perfolizer/Metrology/RatioValue.cs b/src/Perfolizer/Perfolizer/Metrology/RatioValue.cs index c13c1deb..14bfab4b 100644 --- a/src/Perfolizer/Perfolizer/Metrology/RatioValue.cs +++ b/src/Perfolizer/Perfolizer/Metrology/RatioValue.cs @@ -15,7 +15,7 @@ public string ToString( UnitPresentation? unitPresentation = null) { format ??= DefaultFormat; - var measurementValue = new MeasurementValue(Value, RatioUnit.Instance); + var measurementValue = new Measurement(Value, RatioUnit.Instance); return measurementValue.ToString(format, formatProvider, unitPresentation); } diff --git a/src/Perfolizer/Perfolizer/Metrology/SizeValue.cs b/src/Perfolizer/Perfolizer/Metrology/SizeValue.cs index f82b48d2..b93f311e 100644 --- a/src/Perfolizer/Perfolizer/Metrology/SizeValue.cs +++ b/src/Perfolizer/Perfolizer/Metrology/SizeValue.cs @@ -52,7 +52,7 @@ public string ToString( sizeUnit ??= SizeUnit.GetBestSizeUnit(Bytes); format ??= DefaultFormat; double nominalValue = SizeUnit.Convert(Bytes, SizeUnit.B, sizeUnit); - var measurementValue = new MeasurementValue(nominalValue, sizeUnit); + var measurementValue = new Measurement(nominalValue, sizeUnit); return measurementValue.ToString(format, formatProvider, unitPresentation); } diff --git a/src/Perfolizer/Perfolizer/Metrology/Threshold.cs b/src/Perfolizer/Perfolizer/Metrology/Threshold.cs index 9cc840e2..21b5cc7f 100644 --- a/src/Perfolizer/Perfolizer/Metrology/Threshold.cs +++ b/src/Perfolizer/Perfolizer/Metrology/Threshold.cs @@ -29,7 +29,7 @@ public Sample ApplyMax(Sample sample) values[i] = double.MinValue; foreach (var appliedSample in appliedSamples) values[i] = Max(values[i], appliedSample.Values[i]); - if (appliedSamples.IsEmpty()) + if (appliedSamples.Length == 0) values[i] = sample.Values[i]; } return sample.IsWeighted @@ -83,7 +83,7 @@ public static bool TryParse(string s, out Threshold threshold) var thresholdValues = new ISpecificMeasurementValue[parts.Length]; for (int i = 0; i < parts.Length; i++) { - if (!MeasurementValue.TryParse(parts[i], out var measurementValue)) + if (!Measurement.TryParse(parts[i], out var measurementValue)) { threshold = Zero; return false; diff --git a/src/Perfolizer/Perfolizer/Perfolizer.csproj b/src/Perfolizer/Perfolizer/Perfolizer.csproj index 9189df74..9e088b38 100644 --- a/src/Perfolizer/Perfolizer/Perfolizer.csproj +++ b/src/Perfolizer/Perfolizer/Perfolizer.csproj @@ -1,15 +1,17 @@ - - netstandard2.0;netstandard2.1 - Performance analysis toolkit - - - - - - + + netstandard2.0;net6.0 + Performance analysis toolkit + + + + + + + + diff --git a/src/Perfolizer/Perfolizer/Phd/Base/PhdEntry.cs b/src/Perfolizer/Perfolizer/Phd/Base/PhdEntry.cs new file mode 100644 index 00000000..990b93dc --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Base/PhdEntry.cs @@ -0,0 +1,89 @@ +using Perfolizer.Collections; +using Perfolizer.Metrology; +using Perfolizer.Phd.Dto; + +namespace Perfolizer.Phd.Base; + +public class PhdEntry : PhdObject +{ + /// + /// Primary information about the current report + /// + public PhdInfo? Info { get; set; } + + /// + /// Information about the measurement framework + /// + public PhdEngine? Engine { get; set; } + + /// + /// BuildId, Commit, etc. + /// + public PhdSource? Source { get; set; } + + /// + /// Os, Cpu, etc. + /// + public PhdHost? Host { get; set; } + + /// + /// Benchmark execution, environment, etc. + /// + public PhdJob? Job { get; set; } + + /// + /// Benchmark parameters + /// + public Dictionary? Parameters { get; set; } + + /// + /// Benchmark descriptor + /// + public PhdBenchmark? Benchmark { get; set; } + + /// + /// Stage of the benchmark lifecycle (e.g. Pilot/Warmup/Actual/etc., Overhead/Workload, etc.) + /// + public PhdLifecycle? Lifecycle { get; set; } + + public PhdMetric Metric { get; set; } = PhdMetric.Empty; + public double? Value { get; set; } + public MeasurementUnit Unit { get; set; } = NumberUnit.Instance; + + // TODO: Refactor + public int? IterationIndex { get; set; } + + // TODO: Refactor + public long? InvocationCount { get; set; } + + private readonly List nested = []; + public IReadOnlyList Nested => nested; + + /// + /// Service information (like the structure of the reports) + /// + public PhdMeta? Meta { get; set; } + + + public PhdMeta? ResolveMeta() + { + if (Meta != null) + return Meta; + return Nested.Select(x => x.ResolveMeta()).WhereNotNull().FirstOrDefault(); + } + + // Tree + public PhdEntry Add(params PhdEntry[] entries) + { + foreach (var entry in entries) + nested.Add(entry); + return this; + } + + public IEnumerable Traverse() + { + yield return this; + foreach (var entry in nested.SelectMany(entry => entry.Traverse())) + yield return entry; + } +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Base/PhdHelper.cs b/src/Perfolizer/Perfolizer/Phd/Base/PhdHelper.cs new file mode 100644 index 00000000..8bc8c1c8 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Base/PhdHelper.cs @@ -0,0 +1,11 @@ +namespace Perfolizer.Phd.Base; + +internal static class PhdHelper +{ + public static bool IsPhdPrimitive(this Type type) => + type.IsPrimitive || + type.IsEnum || + type == typeof(Guid) || + type == typeof(DateTimeOffset) || + type == typeof(string); +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Base/PhdIndex.cs b/src/Perfolizer/Perfolizer/Phd/Base/PhdIndex.cs new file mode 100644 index 00000000..2d338b06 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Base/PhdIndex.cs @@ -0,0 +1,125 @@ +using System.Collections; +using System.Text; +using Perfolizer.Extensions; +using Perfolizer.Metrology; + +namespace Perfolizer.Phd.Base; + +public class PhdIndex +{ + private readonly PhdEntry rootEntry; + private readonly Dictionary entries = new (); + + public IReadOnlyList Keys { get; } + public IReadOnlyCollection Entries => entries.Keys; + public PhdIndexedEntry this[PhdEntry entry] => entries[entry]; + + public PhdIndex(PhdEntry rootEntry) + { + this.rootEntry = rootEntry; + IndexEntry(PhdKey.Empty, rootEntry, null); + Keys = entries.Values + .SelectMany(indexedEntry => indexedEntry + .AllProperties + .Select(property => property.Key) + .Concat([indexedEntry.Key])) + .Distinct() + .Where(key => key.Path.IsNotBlank()) + .ToList(); + } + + private void IndexEntry(PhdKey key, PhdEntry entry, PhdEntry? parent) + { + IndexSelf(key, entry, parent); + foreach (var nested in entry.Nested) + IndexEntry(key, nested, entry); + } + + private void IndexSelf(PhdKey key, PhdEntry entry, PhdEntry? parent) + { + var indexedEntry = entries[entry] = new PhdIndexedEntry(key, entry, new Dictionary()); + + if (parent != null) + { + var indexedParent = entries[parent]; + foreach (var property in indexedParent.AllProperties) + indexedEntry[property.Key] = property; + } + + IndexAttributes(key, entry, entry); + } + + private void IndexAttributes(PhdKey key, PhdEntry entry, object obj) + { + var indexedEntry = entries[entry]; + if (obj is IDictionary dictionary) + { + foreach (DictionaryEntry dictionaryEntry in dictionary) + { + string? subKey = dictionaryEntry.Key.ToString(); + object? value = dictionaryEntry.Value; + if (subKey == null || value == null || value.ToString().IsBlank()) + continue; + + var phdSubKey = key.Append(subKey, value.GetType()); + indexedEntry[phdSubKey] = new PhdProperty(phdSubKey, value); + } + } + else + { + foreach (var property in obj.GetType().GetProperties()) + { + if (obj is PhdEntry && property.Name == nameof(PhdEntry.Nested)) + continue; + object? value; + try + { + value = property.GetValue(obj); + } + catch (Exception) + { + continue; + } + if (value == null || value.ToString().IsBlank()) + continue; + if (value is PhdMeta) + continue; + var propertyKey = key.Append(property.Name, value.GetType()); + indexedEntry[propertyKey] = new PhdProperty(propertyKey, value); + if (!value.GetType().IsPrimitive && + value is not string && + value is not MeasurementUnit) + IndexAttributes(propertyKey, entry, value); + } + } + } + + public string Dump() + { + var builder = new StringBuilder(); + builder.AppendLine("[Keys]"); + foreach (var key in Keys.OrderBy(key => key.Path, StringComparer.OrdinalIgnoreCase)) + builder.AppendLine($" {key.Path}"); + builder.AppendLine(); + + int index = 0; + foreach (var entry in rootEntry.Traverse()) + { + if (entry.Value == null) continue; + + builder.AppendLine($"[Entry{index++}]"); + var indexedEntry = entries[entry]; + foreach (var property in indexedEntry.AllProperties.OrderBy(property => property.Key.Path)) + { + string value = property.Display; + if (property.Value is IDictionary) + value = ""; + else if (value.IsBlank() && !property.Value.GetType().IsPrimitive) + value = ""; + builder.AppendLine($" {property.Key.Path} = {value}"); + } + } + + return builder.ToString().Trim(); + } +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Base/PhdIndexedEntry.cs b/src/Perfolizer/Perfolizer/Phd/Base/PhdIndexedEntry.cs new file mode 100644 index 00000000..187fff8f --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Base/PhdIndexedEntry.cs @@ -0,0 +1,25 @@ +using Perfolizer.Collections; + +namespace Perfolizer.Phd.Base; + +public class PhdIndexedEntry(PhdKey key, PhdEntry entry, Dictionary properties) +{ + public PhdKey Key { get; } = key; + public PhdEntry Entry { get; } = entry; + public IReadOnlyCollection AllProperties => properties.Values; + + private PhdProperty? this[string key] + { + get => properties.GetValueOrDefault(key); + set + { + if (value != null) properties[key] = value; + } + } + + public PhdProperty? this[PhdKey key] + { + get => this[key.Path]; + set => this[key.Path] = value; + } +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Base/PhdKey.cs b/src/Perfolizer/Perfolizer/Phd/Base/PhdKey.cs new file mode 100644 index 00000000..66c65562 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Base/PhdKey.cs @@ -0,0 +1,48 @@ +using Perfolizer.Extensions; + +namespace Perfolizer.Phd.Base; + +public class PhdKey(string path, Type type) : IEquatable +{ + public static readonly PhdKey Empty = new ("", typeof(object)); + + public string Path { get; } = path; + public Type Type { get; } = type; + + public string Name { get; } = path.Split(PhdSymbol.Attribute).DefaultIfEmpty("").Last(); + public override string ToString() => Path; + + public bool IsComposite() => !Type.IsPhdPrimitive(); + + public bool IsMatched(string selector) => + Path.EquationsIgnoreCase(selector) || + Path.StartWithIgnoreCase(selector) || + Name.EquationsIgnoreCase(selector); + + public bool Equals(PhdKey? other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + return Path == other.Path; + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) + return false; + if (ReferenceEquals(this, obj)) + return true; + if (obj.GetType() != GetType()) + return false; + return Equals((PhdKey)obj); + } + + public override int GetHashCode() => Path.GetHashCode(); + public static bool operator ==(PhdKey? left, PhdKey? right) => Equals(left, right); + public static bool operator !=(PhdKey? left, PhdKey? right) => !Equals(left, right); + + public PhdKey Append(string subName, Type subType) => + new ($"{Path}{PhdSymbol.Attribute}{subName.ToCamelCase()}", subType); +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Base/PhdMeta.cs b/src/Perfolizer/Perfolizer/Phd/Base/PhdMeta.cs new file mode 100644 index 00000000..f33da5fb --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Base/PhdMeta.cs @@ -0,0 +1,10 @@ +using JetBrains.Annotations; +using Perfolizer.Phd.Tables; + +namespace Perfolizer.Phd.Base; + +[PublicAPI] +public class PhdMeta: PhdObject +{ + public PhdTableConfig Table { get; set; } = new (); +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Base/PhdMetric.cs b/src/Perfolizer/Perfolizer/Phd/Base/PhdMetric.cs new file mode 100644 index 00000000..c48dd69c --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Base/PhdMetric.cs @@ -0,0 +1,18 @@ +using Microsoft.VisualBasic; + +namespace Perfolizer.Phd.Base; + +public class PhdMetric(string? id, int? version) : PhdObject +{ + public static readonly PhdMetric Empty = new (null, null); + + public string? Id { get; set; } = id; + public int? Version { get; set; } = version; + + public override string ToString() => Version is null or 0 + ? Id ?? "" + : $"{Id} v{Version}".Trim(); + + public static implicit operator PhdMetric(string id) => new (id, null); + public bool IsEmpty => Id == null && Version == null; +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Base/PhdObject.cs b/src/Perfolizer/Perfolizer/Phd/Base/PhdObject.cs new file mode 100644 index 00000000..4e2dc293 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Base/PhdObject.cs @@ -0,0 +1,8 @@ +namespace Perfolizer.Phd.Base; + +public class PhdObject +{ + public string? Display { get; set; } + + public virtual string? GetDisplay() => Display; +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Base/PhdProperty.cs b/src/Perfolizer/Perfolizer/Phd/Base/PhdProperty.cs new file mode 100644 index 00000000..0a656f53 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Base/PhdProperty.cs @@ -0,0 +1,11 @@ +using System.Diagnostics; + +namespace Perfolizer.Phd.Base; + +[DebuggerDisplay("{Key}={Value}")] +public class PhdProperty(PhdKey key, object value) +{ + public PhdKey Key { get; } = key; + public object Value { get; } = value; + public string Display => (Value is PhdObject phdObject ? phdObject.GetDisplay() : Value.ToString()) ?? ""; +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Base/PhdSchema.cs b/src/Perfolizer/Perfolizer/Phd/Base/PhdSchema.cs new file mode 100644 index 00000000..019bc862 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Base/PhdSchema.cs @@ -0,0 +1,31 @@ +namespace Perfolizer.Phd.Base; + +public class PhdSchema(string name) +{ + public string Name { get; } = name; + private readonly List implementations = []; + public IReadOnlyList Implementations => implementations; + + public PhdSchema Add() where T : PhdObject + { + var baseType = GetBaseType(); + if (baseType == null) + throw new InvalidCastException($"Failed to case {typeof(T).Name} to {nameof(PhdObject)}"); + implementations.Add(new PhdImplementation(baseType, typeof(T))); + return this; + } + + private static Type? GetBaseType() + { + var t = typeof(T); + while (t != null && t.Assembly != typeof(PhdObject).Assembly) + t = t.BaseType; + return t; + } +} + +public class PhdImplementation(Type @base, Type derived) +{ + public Type Base { get; } = @base; + public Type Derived { get; } = derived; +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Base/PhdSymbol.cs b/src/Perfolizer/Perfolizer/Phd/Base/PhdSymbol.cs new file mode 100644 index 00000000..543ca6f7 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Base/PhdSymbol.cs @@ -0,0 +1,8 @@ +namespace Perfolizer.Phd.Base; + +internal static class PhdSymbol +{ + public const char Attribute = '.'; + public const char Anchor = '@'; + public const char Function = '='; +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Dto/PhdBenchmark.cs b/src/Perfolizer/Perfolizer/Phd/Dto/PhdBenchmark.cs new file mode 100644 index 00000000..330751fd --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Dto/PhdBenchmark.cs @@ -0,0 +1,5 @@ +using Perfolizer.Phd.Base; + +namespace Perfolizer.Phd.Dto; + +public abstract class PhdBenchmark : PhdObject; \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Dto/PhdCpu.cs b/src/Perfolizer/Perfolizer/Phd/Dto/PhdCpu.cs new file mode 100644 index 00000000..d806e9bc --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Dto/PhdCpu.cs @@ -0,0 +1,24 @@ +using JetBrains.Annotations; +using Perfolizer.Helpers; +using Perfolizer.Horology; +using Perfolizer.Phd.Base; + +namespace Perfolizer.Phd.Dto; + +[PublicAPI] +public class PhdCpu : PhdObject +{ + public string? ProcessorName { get; set; } + public int? PhysicalProcessorCount { get; set; } + public int? PhysicalCoreCount { get; set; } + public int? LogicalCoreCount { get; set; } + public string? Architecture { get; set; } + public long? NominalFrequencyHz { get; set; } + public long? MaxFrequencyHz { get; set; } + + public Frequency? NominalFrequency() => NominalFrequencyHz.HasValue ? Frequency.FromHz(NominalFrequencyHz.Value) : null; + public Frequency? MaxFrequency() => MaxFrequencyHz.HasValue ? Frequency.FromHz(MaxFrequencyHz.Value) : null; + + public override string GetDisplay() => Display ?? this.ToFullBrandName(); + public override string ToString() => this.ToFullBrandName(); +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Dto/PhdEngine.cs b/src/Perfolizer/Perfolizer/Phd/Dto/PhdEngine.cs new file mode 100644 index 00000000..0a0c783e --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Dto/PhdEngine.cs @@ -0,0 +1,15 @@ +using JetBrains.Annotations; +using Perfolizer.Phd.Base; + +namespace Perfolizer.Phd.Dto; + +[PublicAPI] +public class PhdEngine : PhdObject +{ + public string Name { get; set; } = ""; + public string? Version { get; set; } + + public override string GetDisplay() => Display ?? ToBrandTitle(); + public string ToBrandTitle() => Version == null ? Name : $"{Name} v{Version}"; + public override string ToString() => ToBrandTitle(); +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Dto/PhdEnvironment.cs b/src/Perfolizer/Perfolizer/Phd/Dto/PhdEnvironment.cs new file mode 100644 index 00000000..a5c026d6 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Dto/PhdEnvironment.cs @@ -0,0 +1,5 @@ +using Perfolizer.Phd.Base; + +namespace Perfolizer.Phd.Dto; + +public abstract class PhdEnvironment : PhdObject; \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Dto/PhdExecution.cs b/src/Perfolizer/Perfolizer/Phd/Dto/PhdExecution.cs new file mode 100644 index 00000000..4f5bf8ee --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Dto/PhdExecution.cs @@ -0,0 +1,5 @@ +using Perfolizer.Phd.Base; + +namespace Perfolizer.Phd.Dto; + +public abstract class PhdExecution : PhdObject; \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Dto/PhdHost.cs b/src/Perfolizer/Perfolizer/Phd/Dto/PhdHost.cs new file mode 100644 index 00000000..bc0f2166 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Dto/PhdHost.cs @@ -0,0 +1,9 @@ +using Perfolizer.Phd.Base; + +namespace Perfolizer.Phd.Dto; + +public class PhdHost : PhdObject +{ + public PhdOs? Os { get; set; } + public PhdCpu? Cpu { get; set; } +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Dto/PhdInfo.cs b/src/Perfolizer/Perfolizer/Phd/Dto/PhdInfo.cs new file mode 100644 index 00000000..0d457ee4 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Dto/PhdInfo.cs @@ -0,0 +1,12 @@ +using JetBrains.Annotations; +using Perfolizer.Phd.Base; + +namespace Perfolizer.Phd.Dto; + +[PublicAPI] +public class PhdInfo : PhdObject +{ + public string Title { get; set; } = ""; + public Guid RunId { get; set; } + public long Timestamp { get; set; } +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Dto/PhdJob.cs b/src/Perfolizer/Perfolizer/Phd/Dto/PhdJob.cs new file mode 100644 index 00000000..812ec81c --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Dto/PhdJob.cs @@ -0,0 +1,11 @@ +using JetBrains.Annotations; +using Perfolizer.Phd.Base; + +namespace Perfolizer.Phd.Dto; + +[PublicAPI] +public class PhdJob : PhdObject +{ + public PhdEnvironment? Environment { get; set; } + public PhdExecution? Execution { get; set; } +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Dto/PhdLifecycle.cs b/src/Perfolizer/Perfolizer/Phd/Dto/PhdLifecycle.cs new file mode 100644 index 00000000..f79b49c0 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Dto/PhdLifecycle.cs @@ -0,0 +1,5 @@ +using Perfolizer.Phd.Base; + +namespace Perfolizer.Phd.Dto; + +public abstract class PhdLifecycle : PhdObject; \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Dto/PhdOs.cs b/src/Perfolizer/Perfolizer/Phd/Dto/PhdOs.cs new file mode 100644 index 00000000..0bdbc6fa --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Dto/PhdOs.cs @@ -0,0 +1,29 @@ +using Perfolizer.Helpers; +using Perfolizer.Phd.Base; + +namespace Perfolizer.Phd.Dto; + +public class PhdOs : PhdObject +{ + /// + /// E.g., Windows, Linux, macOS + /// + public string? Name { get; set; } + + /// + /// E.g., Ubuntu, Fedora, CentOS, Debian, RHEL + /// + public string? Distro { get; set; } + + public string? Version { get; set; } + + public string? KernelVersion { get; set; } + + /// + /// E.g., Docker + /// + public string? Container { get; set; } + + public override string GetDisplay() => Display ?? this.ToBrandString(); + public override string ToString() => this.ToBrandString(); +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Dto/PhdRatioStyle.cs b/src/Perfolizer/Perfolizer/Phd/Dto/PhdRatioStyle.cs new file mode 100644 index 00000000..69343e88 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Dto/PhdRatioStyle.cs @@ -0,0 +1,8 @@ +namespace Perfolizer.Phd.Dto; + +public enum PhdRatioStyle +{ + Value, + Percentage, + Trend +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Dto/PhdSource.cs b/src/Perfolizer/Perfolizer/Phd/Dto/PhdSource.cs new file mode 100644 index 00000000..eb30ccf4 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Dto/PhdSource.cs @@ -0,0 +1,5 @@ +using Perfolizer.Phd.Base; + +namespace Perfolizer.Phd.Dto; + +public abstract class PhdSource : PhdObject; \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Dto/PhdTextAlignment.cs b/src/Perfolizer/Perfolizer/Phd/Dto/PhdTextAlignment.cs new file mode 100644 index 00000000..1c1086a1 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Dto/PhdTextAlignment.cs @@ -0,0 +1,8 @@ +namespace Perfolizer.Phd.Dto; + +public enum PhdTextAlignment +{ + Auto, + Left, + Right +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Functions/PhdFunction.cs b/src/Perfolizer/Perfolizer/Phd/Functions/PhdFunction.cs new file mode 100644 index 00000000..20037829 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Functions/PhdFunction.cs @@ -0,0 +1,23 @@ +using System.Diagnostics; +using Perfolizer.Helpers; + +namespace Perfolizer.Phd.Functions; + +[DebuggerDisplay("{Id}")] +public class PhdFunction(string id, string title, Func apply) +{ + public string Id { get; } = id; + public string Title { get; } = title; + public Func Apply { get; } = apply; + public string? Legend { get; set; } + + public virtual Type ReturnType { get; } = typeof(object); +} + +[DebuggerDisplay("{Id}")] +public class PhdFunction(string id, string title, Func apply) : PhdFunction(id, title, s => apply(s)) +{ + public PhdFunction(string id, Func apply) : this(id, StringHelper.FirstUpper(id), apply) { } + public new Func Apply { get; } = apply; + public override Type ReturnType { get; } = typeof(T); +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Functions/PhdFunctionResolver.cs b/src/Perfolizer/Perfolizer/Phd/Functions/PhdFunctionResolver.cs new file mode 100644 index 00000000..2a05a8f5 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Functions/PhdFunctionResolver.cs @@ -0,0 +1,42 @@ +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; +using Perfolizer.Extensions; +using Perfolizer.Mathematics.GenericEstimators; +using Perfolizer.Mathematics.QuantileEstimators; +using Perfolizer.Mathematics.ScaleEstimators; +using Perfolizer.Metrology; +using Perfolizer.Phd.Base; + +namespace Perfolizer.Phd.Functions; + +public class PhdFunctionResolver +{ + private readonly Dictionary registeredFunctions = new (StringComparer.OrdinalIgnoreCase); + + [PublicAPI] + public PhdFunctionResolver Register(params PhdFunction[] functions) + { + foreach (var function in functions) + registeredFunctions[function.Id] = function; + return this; + } + + public PhdFunctionResolver RegisterDefaults() => Register( + new PhdFunction("n", sample => sample.Size.AsMeasurement()) + { Legend = "Sample Size" }, + new PhdFunction("mean", sample => sample.Mean().WithUnit(sample.Unit)) + { Legend = "Arithmetic Average" }, + new PhdFunction("min", sample => sample.Min().WithUnit(sample.Unit)), + new PhdFunction("max", sample => sample.Max().WithUnit(sample.Unit)), + new PhdFunction("median", + sample => TrimmedHarrellDavisQuantileEstimator.Sqrt.Median(sample).WithUnit(sample.Unit)), + new PhdFunction("average", + sample => HodgesLehmannEstimator.Instance.Median(sample).WithUnit(sample.Unit)), + new PhdFunction("spread", + sample => ShamosEstimator.Instance.Scale(sample).WithUnit(sample.Unit)) + ); + + [SuppressMessage("ReSharper", "CanSimplifyDictionaryTryGetValueWithGetValueOrDefault")] + public PhdFunction? Resolve(string id) => + registeredFunctions.TryGetValue(id.TrimStart(PhdSymbol.Function), out var function) ? function : null; +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Presenting/IPhdTablePresenter.cs b/src/Perfolizer/Perfolizer/Phd/Presenting/IPhdTablePresenter.cs new file mode 100644 index 00000000..ed35703a --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Presenting/IPhdTablePresenter.cs @@ -0,0 +1,8 @@ +using Perfolizer.Phd.Tables; + +namespace Perfolizer.Phd.Presenting; + +public interface IPhdTablePresenter +{ + void Present(PhdTable table, PhdTableStyle style); +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Presenting/PhdMarkdownTablePresenter.cs b/src/Perfolizer/Perfolizer/Phd/Presenting/PhdMarkdownTablePresenter.cs new file mode 100644 index 00000000..8392a4bf --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Presenting/PhdMarkdownTablePresenter.cs @@ -0,0 +1,108 @@ +using Perfolizer.Common; +using Perfolizer.Extensions; +using Perfolizer.Metrology; +using Perfolizer.Phd.Base; +using Perfolizer.Phd.Dto; +using Perfolizer.Phd.Tables; +using Perfolizer.Presenting; + +namespace Perfolizer.Phd.Presenting; + +public class PhdTableStyle +{ + public IFormatProvider FormatProvider { get; set; } = DefaultCultureInfo.Instance; + public UnitPresentation UnitPresentation { get; set; } = UnitPresentation.WithGap; + public int PreferredWidth { get; set; } = 100; +} + +public class PhdMarkdownTablePresenter(IPresenter presenter) : IPhdTablePresenter +{ + public void Present(PhdTable table, PhdTableStyle style) + { + var view = new PhdTableView(table, style); + PresentClouds(view.Cloudscapes, style); + PresentTable(view); + presenter.Flush(); + } + + private void PresentClouds(IReadOnlyList cloudscapes, PhdTableStyle style) + { + foreach (var cloudscape in cloudscapes) + { + int maxIdLength = cloudscape.Clouds.Max(cloud => cloud.Id.Length); + foreach (var cloud in cloudscape.Clouds) + Present(cloud, style, maxIdLength); + presenter.WriteLine(); + } + } + + private void Present(PhdCloud cloud, PhdTableStyle style, int maxIdLength) + { + string idText = cloud.Id.IsNotBlank() ? $"{PhdSymbol.Anchor}{cloud.Id}: ".PadRight(maxIdLength + 3) : ""; + presenter.Write(idText); + int currentLineLength = idText.Length; + for (int i = 0; i < cloud.Cells.Count; i++) + { + var cell = cloud.Cells[i]; + string value = cell.Value?.ToString() ?? ""; // TODO: Present + string content = cell.Column.Definition.IsSelfExplanatory ?? false + ? value + : $"{cell.Column.Title} = {value}"; + + string separator = i == 0 ? "" : ", "; + bool onSameLine = currentLineLength == 0 || + currentLineLength + separator.Length + content.Length <= style.PreferredWidth; + if (onSameLine) + { + presenter.Write(separator); + presenter.Write(content); + currentLineLength += separator.Length + content.Length; + } + else + { + if (currentLineLength > 0) + presenter.WriteLine(); + presenter.Write(content); + currentLineLength = content.Length; + } + } + + if (currentLineLength > 0) + presenter.WriteLine(); + } + + private void PresentTable(PhdTableView view) + { + PresentRow(col => view.Table.Columns[col].Title); + PresentRow(col => + (view.Columns[col].Definition.ResolveAlignment() == PhdTextAlignment.Left ? ':' : '-') + + new string('-', view.ColumnWidths[col]) + + (view.Columns[col].Definition.ResolveAlignment() == PhdTextAlignment.Right ? ':' : '-') + , false); + for (int row = 0; row < view.RowCount; row++) + { + int currentRow = row; + PresentRow(col => view.Cells[currentRow, col]); + } + return; + + void PresentRow(Func presentCell, bool addGaps = true) + { + for (int col = 0; col < view.ColumnCount; col++) + { + if (col == 0) presenter.Write('|'); + if (addGaps) presenter.Write(' '); + var alignment = view.Columns[col].Definition.ResolveAlignment(); + string presentation = alignment switch + { + PhdTextAlignment.Right => presentCell(col).PadLeft(view.ColumnWidths[col]), + _ => presentCell(col).PadRight(view.ColumnWidths[col]) + }; + presenter.Write(presentation); + if (addGaps) presenter.Write(' '); + presenter.Write('|'); + } + presenter.WriteLine(); + } + } +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/README.md b/src/Perfolizer/Perfolizer/Phd/README.md new file mode 100644 index 00000000..cef0fa9c --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/README.md @@ -0,0 +1,124 @@ +# Phd + +The PHD (Performance History Data) is designed for keeping sets of software performance measurements. +Essentially, it is a convention for JSON files that allows us + to present performance data from various sources in a unified way. + +## Background + +Performance is an important feature of many software applications. +To maintain performance metrics at the desired level during development, + we should systematically measure the metric values and analyze the results. + +TODO + +## Design + +We want to design a format for presenting sets of abstract performance measurements. +In the simplest case, a measurement is presented by a single number. +In the case of a single metric, it can be enough. +If we want to support various metrics, we should introduce a name, + so that a measurement becomes a pair of a name and a value. +A set of such key-value pairs can be presented in a table: + +```md +| Metric | Value | +|----------|-------| +| Latency1 | 100 | +| Latency2 | 200 | +``` + +To avoid confusion in the interpretation of these values, + we should also specify the measurement unit: + +```md +| Metric | Value | Unit | +|-----------|-------|------| +| Latency | 100 | ms | +| Footprint | 64 | kb | +| FPS | 25 | hz | +| RPS | 500 | hz | +| GcCount | 42 | | +``` + +We assume that the absence of the measurement unit means that the value is dimensionless. + +Measurements may have attributes. +For example, we can consider a set of benchmark results: + +```md +| Benchmark | Metric | Value | Unit | +|-----------|---------|-------|------| +| WorkloadA | Latency | 200 | ms | +| WorkloadB | Latency | 400 | ms | +``` + +We call such an annotated measurement an *entry*. +An entry may contain different kinds of attributes. +For example, we can perform measurements under different operating systems: + +```md +| OS | Benchmark | Metric | Value | Unit | +|---------|-----------|---------|-------|------| +| Linux | WorkloadA | Latency | 200 | ms | +| Windows | WorkloadA | Latency | 250 | ms | +| Linux | WorkloadB | Latency | 400 | ms | +| Windows | WorkloadB | Latency | 450 | ms | +``` + +Or we may want to add metadata describing the origin of the source code: + +```md +| Branch | Commit | OS | Benchmark | Metric | Value | Unit | +|--------|----------| ---------|-----------|---------|-------|------| +| main | a5b847bd | Linux | WorkloadA | Latency | 200 | ms | +| main | a5b847bd | Windows | WorkloadA | Latency | 250 | ms | +| main | a5b847bd | Linux | WorkloadB | Latency | 400 | ms | +| main | a5b847bd | Windows | WorkloadB | Latency | 450 | ms | +``` + +When the number of data fields is small, one may consider using the CSV format. +If the number of fields is large, CSV may become inconvenient and inefficient. +In order to ensure natural compression and avoid data duplication, + we propose to use the JSON format with hierarchically structured data. +The last table can be represented as follows: + +```json +{ + "branch": "main", + "commit": "a5b847bd", + "unit": "ms", + "children": [ + { + "os": "Linux", + "children": [ + { + "benchmark": "WorkloadA", + "metric": "Latency", + "value": 200 + }, + { + "benchmark": "WorkloadB", + "metric": "Latency", + "value": 400 + } + ] + }, + { + "os": "Windows", + "children": [ + { + "benchmark": "WorkloadA", + "metric": "Latency", + "value": 250 + }, + { + "benchmark": "WorkloadB", + "metric": "Latency", + "value": 450 + } + ] + } + ] +} +``` \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/TODO.md b/src/Perfolizer/Perfolizer/Phd/TODO.md new file mode 100644 index 00000000..fd7ea82f --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/TODO.md @@ -0,0 +1,7 @@ +# TODO + +* Logical and physical groups +* Analysers +* Import old jobs +* GC +* Unit: RPS \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Tables/PhdAnchorGenerator.cs b/src/Perfolizer/Perfolizer/Phd/Tables/PhdAnchorGenerator.cs new file mode 100644 index 00000000..a2a04d51 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Tables/PhdAnchorGenerator.cs @@ -0,0 +1,96 @@ +using System.Text; +using Perfolizer.Extensions; + +namespace Perfolizer.Phd.Tables; + +/// +/// Anchors are string ids for subsets of attributes. +/// Let's say we have multiple attributes defining a job (e.g., RuntimeMoniker, Jit, Platform, etc.). +/// Instead of listing all the attributes in the table, we extract this subset and mark it with an anchor. +/// For example, we can introduce "@MyAnchor: Runtime = Net70, Jit = RyuJit" above a table +/// and reference it in the Job column as "MyAnchor". +/// +/// +public class PhdAnchorGenerator(int? maxAnchorLength = null) +{ + private const int DefaultMaxAnchorLength = 20; + private int MaxAnchorLength { get; } = maxAnchorLength ?? DefaultMaxAnchorLength; + private readonly Dictionary attributeIdToAnchor = new (); + private readonly HashSet existingAnchors = []; + + public string GetAnchor(string attributeId) + { + if (attributeIdToAnchor.TryGetValue(attributeId, out string? existingAnchor)) + return existingAnchor; + + string anchor = Compress(attributeId); + if (anchor.Length > MaxAnchorLength || existingAnchors.Contains(anchor)) + anchor = GenerateRandomAnchor(attributeId); + + attributeIdToAnchor[attributeId] = anchor; + existingAnchors.Add(anchor); + return anchor; + } + + private static string Compress(string s) + { + var builder = new StringBuilder(); + for (int i = 0; i < s.Length; i++) + { + char c = s[i]; + if (char.IsLetterOrDigit(c)) + builder.Append(c); + if (c == '_' && i != s.Length - 1 && char.IsDigit(s[i + 1])) + builder.Append(c); + } + return builder.ToString(); + } + + private string GenerateRandomAnchor(string attributeId) + { + int animalIndex = GetStableHashCode(attributeId).Abs() % animals.Count; + string anchor = animals[animalIndex]; + int suffix = 1; + while (existingAnchors.Contains(anchor)) + anchor = animals[animalIndex] + suffix++; + return anchor; + } + + // We do not use string.GetHashCode() because it is randomized on each runtime start + private static int GetStableHashCode(string s) => s.Aggregate(0, (current, c) => current * 31 + c); + + // ReSharper disable StringLiteralTypo + private readonly List animals = + [ + "Cat", "Dog", "Fox", "Bear", "Wolf", "Hawk", "Crow", "Ant", "Bee", + "Deer", "Boar", "Lynx", "Mole", "Swan", "Frog", "Toad", "Duck", "Eel", + "Carp", "Lion", "Moth", "Crab", "Emu", "Seal", "Mink", "Pike", "Rat", + "Bat", "Yak", "Cod", "Hare", "Koi", "Elk", "Ram", "Bug", "Hen", "Owl", + "Ape", "Gnu", "Pug", "Jay", "Doe", "Roe", "Kid", "Cub", "Colt", "Foal", + "Calf", "Lamb", "Tuna", "Wasp", "Mite", "Lark", "Clam", "Sole", "Roach", + "Squid", "Quail", "Vole", "Shrew", "Loon", "Rail", "Mussel", "Skink", + "Goby", "Orca", "Mako", "Coati", "Zebu", "Tahr", "Egret", "Ibex", "Kudu", + "Erne", "Dace", "Reed", "Tern", "Ruff", "Smelt", "Bison", "Brant", "Brook", + "Dunlin", "Finch", "Gecko", "Ghoul", "Goral", "Grebe", "Grouse", "Guil", + "Hoatzin", "Hyena", "Iguana", "Indri", "Jackal", "Jaguar", "Jerboa", + "Kakapo", "Kestrel", "Kinkajou", "Koala", "Kob", "Kook", "Lapwing", + "Lemur", "Leopard", "Liger", "Loris", "Lynx", "Macaw", "Magpie", "Mallard", + "Manatee", "Marten", "Meerkat", "Minke", "Mongoose", "Monkey", "Moorhen", + "Narwhal", "Ocelot", "Octopus", "Okapi", "Opossum", "Orang", "Oryx", + "Osprey", "Otter", "Ouzel", "Panda", "Panther", "Parrot", "Peafowl", + "Pelican", "Penguin", "Perch", "Petrel", "Pheasant", "Pigeon", "Pika", + "Piranha", "Platypus", "Plover", "Polaris", "Pony", "Porpoise", "Possum", + "Puffin", "Python", "Quagga", "Quokka", "Rabbit", "Raccoon", "Rail", + "Ramora", "Ratel", "Raven", "Redpoll", "Reindeer", "Rhea", "Robin", + "Rooster", "Sable", "Saiga", "Salamander", "Salmon", "Sandpiper", "Sardine", + "Scallop", "Scarab", "Seahorse", "Serval", "Shark", "Sheep", "Shelduck", + "Shiner", "Siskin", "Skate", "Skunk", "Sloth", "Snail", "Snake", "Snipe", + "Sparrow", "Spider", "Spoonbill", "Sprat", "Squab", "Squirrel", "Starling", + "Stilt", "Stingray", "Stoat", "Stork", "Sunfish", "Swallow", "Swan", + "Tamarin", "Tapir", "Tarpon", "Tarsier", "Teal", "Tenrec", "Tetra", + "Thrush", "Tiger", "Titmouse", "Toad", "Topi", "Toucan", "Turtle", "Uakari", + "Urchin", "Vicuna", "Viper", "Vulture", "Walrus", "Warbler", "Waxwing", + "Weasel", "Whale", "Whimbrel", "Wigeon", "Willet", "Wombat", "Woodcock", + "Woodpecker", "Wren", "Yak", "Zander", "Zebra" + ]; +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Tables/PhdAttributeColumn.cs b/src/Perfolizer/Perfolizer/Phd/Tables/PhdAttributeColumn.cs new file mode 100644 index 00000000..736cf7fc --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Tables/PhdAttributeColumn.cs @@ -0,0 +1,9 @@ +using Perfolizer.Phd.Base; + +namespace Perfolizer.Phd.Tables; + +public class PhdAttributeColumn(string title, string selector, PhdColumnDefinition definition, PhdKey key) + : PhdColumn(title, selector, definition) +{ + public PhdKey Key { get; } = key; +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Tables/PhdCell.cs b/src/Perfolizer/Perfolizer/Phd/Tables/PhdCell.cs new file mode 100644 index 00000000..28f92b74 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Tables/PhdCell.cs @@ -0,0 +1,10 @@ +using System.Diagnostics; + +namespace Perfolizer.Phd.Tables; + +[DebuggerDisplay("{Column.Title}={Value}")] +public class PhdCell(PhdColumn column, object? value) +{ + public PhdColumn Column { get; } = column; + public object? Value { get; } = value; +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Tables/PhdCloud.cs b/src/Perfolizer/Perfolizer/Phd/Tables/PhdCloud.cs new file mode 100644 index 00000000..4f11f00f --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Tables/PhdCloud.cs @@ -0,0 +1,10 @@ +namespace Perfolizer.Phd.Tables; + +/// +/// A named collection of shared cells +/// +public class PhdCloud(string id, IReadOnlyList cells) +{ + public string Id { get; } = id; + public IReadOnlyList Cells { get; } = cells; +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Tables/PhdCloudscape.cs b/src/Perfolizer/Perfolizer/Phd/Tables/PhdCloudscape.cs new file mode 100644 index 00000000..2715ce6a --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Tables/PhdCloudscape.cs @@ -0,0 +1,21 @@ +namespace Perfolizer.Phd.Tables; + +/// +/// A collection of shared cell values displayed above a table +/// +public class PhdCloudscape +{ + public List Clouds { get; } = []; + + public PhdCloudscape Add(PhdCloud cloud) + { + Clouds.Add(cloud); + return this; + } + + public PhdCloudscape AddRange(IEnumerable clouds) + { + Clouds.AddRange(clouds); + return this; + } +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Tables/PhdColumn.cs b/src/Perfolizer/Perfolizer/Phd/Tables/PhdColumn.cs new file mode 100644 index 00000000..57bde78e --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Tables/PhdColumn.cs @@ -0,0 +1,11 @@ +using System.Diagnostics; + +namespace Perfolizer.Phd.Tables; + +[DebuggerDisplay("{Selector}")] +public class PhdColumn(string title, string selector, PhdColumnDefinition definition) +{ + public string Title { get; } = title; + public string Selector { get; } = selector; + public PhdColumnDefinition Definition { get; } = definition; +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Tables/PhdColumnDefinition.cs b/src/Perfolizer/Perfolizer/Phd/Tables/PhdColumnDefinition.cs new file mode 100644 index 00000000..8470a9ea --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Tables/PhdColumnDefinition.cs @@ -0,0 +1,65 @@ +using System.Diagnostics; +using Perfolizer.Extensions; +using Perfolizer.Phd.Base; +using Perfolizer.Phd.Dto; + +namespace Perfolizer.Phd.Tables; + +[DebuggerDisplay("{Selector}")] +public class PhdColumnDefinition(string selector) : PhdObject +{ + public string Title { get; set; } = ""; + public string Selector { get; set; } = selector; + + /// + /// Group for shared cell clouds + /// + public string Cloud { get; set; } = ""; + + /// + /// If true, when presented in the shared section, the title is omitted. + /// + public bool? IsSelfExplanatory { get; set; } + + /// + /// Controls whether the column can be presented in the shared section + /// + public bool? CanBeShared { get; set; } + + /// + /// Controls whether the column can be compressed into an anchor cloudscape + /// + public bool? Compressed { get; set; } + + public bool? IsAtomic { get; set; } + + public PhdTextAlignment? Alignment { get; set; } + + public string ResolveTitle() => Title.IsNotBlank() + ? Title + : new PhdKey(Selector, typeof(object)).Name.CapitalizeFirst(); + + public PhdColumnFlavor ResolveFlavor() + { + if (Selector.StartsWith(PhdSymbol.Attribute)) + return PhdColumnFlavor.Attribute; + if (Selector.StartsWith(PhdSymbol.Function)) + return PhdColumnFlavor.Function; + return PhdColumnFlavor.Unknown; + } + + public PhdTextAlignment ResolveAlignment() + { + if (Alignment.HasValue && Alignment != PhdTextAlignment.Auto) + return Alignment.Value; + return ResolveFlavor() switch + { + PhdColumnFlavor.Attribute => Selector.StartsWith(PhdSymbol.Attribute + "parameters") + ? PhdTextAlignment.Right + : PhdTextAlignment.Left, + PhdColumnFlavor.Function => PhdTextAlignment.Right, + PhdColumnFlavor.Unknown => PhdTextAlignment.Left, + _ => PhdTextAlignment.Left + }; + } +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Tables/PhdColumnFlavor.cs b/src/Perfolizer/Perfolizer/Phd/Tables/PhdColumnFlavor.cs new file mode 100644 index 00000000..7caff217 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Tables/PhdColumnFlavor.cs @@ -0,0 +1,8 @@ +namespace Perfolizer.Phd.Tables; + +public enum PhdColumnFlavor +{ + Attribute, + Function, + Unknown +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Tables/PhdFilter.cs b/src/Perfolizer/Perfolizer/Phd/Tables/PhdFilter.cs new file mode 100644 index 00000000..238ab173 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Tables/PhdFilter.cs @@ -0,0 +1,11 @@ +using Perfolizer.Phd.Base; + +namespace Perfolizer.Phd.Tables; + +public class PhdFilter +{ + public bool IsMatched(PhdEntry entry) + { + return true; // TODO + } +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Tables/PhdFunctionColumn.cs b/src/Perfolizer/Perfolizer/Phd/Tables/PhdFunctionColumn.cs new file mode 100644 index 00000000..db0f449b --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Tables/PhdFunctionColumn.cs @@ -0,0 +1,15 @@ +using Perfolizer.Phd.Functions; + +namespace Perfolizer.Phd.Tables; + +public class PhdFunctionColumn(PhdColumnDefinition definition, PhdFunction function) + : PhdColumn(function.Title, definition.Selector, definition) +{ + public PhdFunction Function { get; } = function; +} + +public class PhdFunctionColumn(PhdColumnDefinition definition, PhdFunction function) + : PhdFunctionColumn(definition, function) +{ + public new PhdFunction Function { get; } = function; +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Tables/PhdRow.cs b/src/Perfolizer/Perfolizer/Phd/Tables/PhdRow.cs new file mode 100644 index 00000000..f6eb835d --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Tables/PhdRow.cs @@ -0,0 +1,44 @@ +using System.Diagnostics; +using System.Text; +using Perfolizer.Collections; +using Perfolizer.Metrology; +using Perfolizer.Phd.Base; + +namespace Perfolizer.Phd.Tables; + +[DebuggerDisplay("{Measurements.Count} measurements, {cells.Count} cells")] +public class PhdRow(PhdEntry entry) +{ + public PhdEntry Entry { get; } = entry; + private readonly Dictionary cells = new (StringComparer.OrdinalIgnoreCase); + public List Measurements { get; } = []; + + public ICollection Cells => cells.Values; + + public PhdCell this[PhdColumn column] + { + get => cells[column.Selector]; + set => cells[column.Selector] = value; + } + + public PhdCell this[string selector] + { + get => cells[selector]; + set => cells[selector] = value; + } + + public string BuildAttributeId() + { + var builder = new StringBuilder(); + foreach (var cell in cells.Values) + if (cell.Column is PhdAttributeColumn) + { + if (builder.Length > 0) + builder.Append("_"); + builder.Append(cell.Value); + } + return builder.ToString(); + } + + public Sample ToSample() => Measurements.Select(m => m.NominalValue).ToSample(Measurements.First().Unit); +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Tables/PhdSortDirection.cs b/src/Perfolizer/Perfolizer/Phd/Tables/PhdSortDirection.cs new file mode 100644 index 00000000..919146c4 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Tables/PhdSortDirection.cs @@ -0,0 +1,12 @@ +namespace Perfolizer.Phd.Tables; + +public enum PhdSortDirection +{ + Descending = -1, + Ascending = 1 +} + +public static class PhdSortDirectionExtensions +{ + public static int ToSign(this PhdSortDirection direction) => (int)direction; +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Tables/PhdSortPolicy.cs b/src/Perfolizer/Perfolizer/Phd/Tables/PhdSortPolicy.cs new file mode 100644 index 00000000..e4d354fb --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Tables/PhdSortPolicy.cs @@ -0,0 +1,11 @@ +using System.Diagnostics; +using Perfolizer.Phd.Base; + +namespace Perfolizer.Phd.Tables; + +[DebuggerDisplay("{Selector}/{Direction}")] +public class PhdSortPolicy(string selector, PhdSortDirection direction) : PhdObject +{ + public string Selector { get; } = selector; + public PhdSortDirection Direction { get; } = direction; +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Tables/PhdTable.cs b/src/Perfolizer/Perfolizer/Phd/Tables/PhdTable.cs new file mode 100644 index 00000000..4f81c89b --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Tables/PhdTable.cs @@ -0,0 +1,219 @@ +using Perfolizer.Collections; +using Perfolizer.Extensions; +using Perfolizer.Metrology; +using Perfolizer.Phd.Base; +using Perfolizer.Phd.Functions; + +namespace Perfolizer.Phd.Tables; + +public class PhdTable +{ + public PhdEntry RootEntry { get; } + public PhdTableConfig Config { get; } + public List Columns { get; } + public IReadOnlyList Rows { get; } + public IReadOnlyList Cloudscapes { get; } + + public int RowCount => Rows.Count; + public int ColumnCount => Columns.Count; + public object? this[int row, int col] => Rows[row][Columns[col]].Value; + + public PhdTable(PhdEntry rootEntry) + { + RootEntry = rootEntry; + Config = rootEntry.ResolveMeta()?.Table ?? new PhdTableConfig(); + var index = new PhdIndex(rootEntry); + Columns = BuildColumns(Config, index); + var rows = BuildRows(rootEntry, Config, Columns, index); + FillFunctionCells(rows, Columns); + SortRows(Config, rows); + Rows = rows; + Cloudscapes = ExtractCloudscapes(Config, index, Rows, Columns); + } + + private static List BuildColumns(PhdTableConfig config, PhdIndex index) + { + var columns = new List(); + var functionResolver = new PhdFunctionResolver().RegisterDefaults(); + foreach (var definition in config.ColumnDefinitions) + { + // PhdFunctionColumn + if (definition.Selector.StartsWith(PhdSymbol.Function)) + { + var function = functionResolver.Resolve(definition.Selector); + if (function == null) + continue; // TODO + + var column = function switch + { + PhdFunction measurementFunction => + new PhdFunctionColumn(definition, measurementFunction), + _ => new PhdFunctionColumn(definition, function) + }; + columns.Add(column); + } + + // PhdAttributeColumn + var columnKeys = index.Keys.Where(key => key.IsMatched(definition.Selector)).ToReadOnlyList(); + if (columnKeys.IsEmpty()) continue; + + var displayKey = columnKeys.FirstOrDefault(key => key.Path.EquationsIgnoreCase(definition.Selector)); + if (displayKey != null && definition.IsAtomic == true) + { + string title = definition.ResolveTitle(); + columns.Add(new PhdAttributeColumn(title, definition.Selector, definition, displayKey)); + } + else + { + // TODO: Support display columns / composite keys + + var primitiveColumnKeys = columnKeys.Where(key => !key.IsComposite()).ToList(); + foreach (var columnKey in primitiveColumnKeys) + { + if (columnKey.Name.EquationsIgnoreCase(nameof(PhdObject.Display))) + continue; + string title = columnKey.Name.CapitalizeFirst(); + columns.Add(new PhdAttributeColumn(title, columnKey.Path, definition, columnKey)); + } + } + } + return columns; + } + + private static List BuildRows(PhdEntry rootEntry, PhdTableConfig config, IReadOnlyList columns, PhdIndex index) + { + var rowMap = new Dictionary(); + foreach (var entry in rootEntry.Traverse().Where(config.IsMatched)) + { + var row = new PhdRow(entry); + foreach (var column in columns.OfType()) + { + object? value = index[entry][column.Key]?.Value; + row[column] = new PhdCell(column, value); + } + string definitionId = row.BuildAttributeId(); + if (!rowMap.TryGetValue(definitionId, out var existingRow)) + rowMap[definitionId] = row; + else + row = existingRow; + if (entry.Value != null) + row.Measurements.Add(new Measurement(entry.Value.Value, entry.Unit)); + } + return rowMap.Values.Where(row => row.Measurements.Any()).ToList(); + } + + private static void FillFunctionCells(IReadOnlyList rows, IReadOnlyList columns) + { + foreach (var row in rows) + foreach (var column in columns.OfType()) + { + object? value = column.Function.Apply(row.ToSample()); + row[column] = new PhdCell(column, value); + } + } + + private static void SortRows(PhdTableConfig config, List rows) + { + if (rows.IsEmpty() || config.SortPolicies.IsEmpty()) + return; + + rows.Sort(Compare); + return; + + int Compare(PhdRow a, PhdRow b) + { + foreach (var sortPolicy in config.SortPolicies) + { + object? aCell = a[sortPolicy.Selector].Value; + object? bCell = b[sortPolicy.Selector].Value; + if (aCell == null && bCell == null) + return 0; + if (aCell is IComparable aComparable && bCell is IComparable bComparable) + return aComparable.CompareTo(bComparable) * sortPolicy.Direction.ToSign(); + } + return 0; + } + } + + private static IReadOnlyList ExtractCloudscapes( + PhdTableConfig config, + PhdIndex index, + IReadOnlyList rows, + List columns) + { + List cloudscapes = []; + cloudscapes.AddRange(ExtractSharedCloudscapes(rows, columns)); + cloudscapes.AddRange(ExtractAnchorCloudscapes(config, index, rows, columns)); + return cloudscapes.ToArray(); + } + + private static List ExtractSharedCloudscapes(IReadOnlyList rows, List columns) + { + var sharedCells = new List(); + var sharedColumns = new HashSet(); + foreach (var column in columns) + { + if (column.Definition.CanBeShared == false) continue; + if (column.Definition.CanBeShared == null && column is PhdFunctionColumn) continue; + + var cells = rows.Select(row => row[column]).ToReadOnlyList(); + if (cells.Select(cell => cell.Value).Distinct().Count() == 1) + { + sharedColumns.Add(column); + sharedCells.Add(cells.First()); + } + } + columns.RemoveAll(column => sharedColumns.Contains(column)); + + return sharedCells + .GroupBy(cell => cell.Column.Definition.Cloud) + .Select(group => new PhdCloudscape().Add(new PhdCloud("", group.ToReadOnlyList()))).ToList(); + } + + private static List ExtractAnchorCloudscapes( + PhdTableConfig config, + PhdIndex index, + IReadOnlyList rows, + List columns) + { + List cloudscapes = []; + foreach (var definition in config.ColumnDefinitions.Where(definition => definition.Compressed ?? false)) + { + var matchedColumns = columns.Where(column => column.Definition == definition).ToList(); + if (matchedColumns.Count > 1) + { + var cloudscape = new PhdCloudscape(); + var newColumn = new PhdAttributeColumn( + definition.ResolveTitle(), + definition.Selector, + definition, + new PhdKey(definition.Selector, typeof(object))); + + var selectorKey = index.Keys.FirstOrDefault(key => key.IsMatched($"{definition.Selector}.Id")); + var anchorGenerator = new PhdAnchorGenerator(config.MaxAnchorLength); + var anchors = new HashSet(); + foreach (var row in rows) + { + var subRow = new PhdRow(row.Entry); + foreach (var matchedColumn in matchedColumns) + subRow[matchedColumn] = row[matchedColumn]; + string attributeId = subRow.BuildAttributeId(); + string anchor = selectorKey != null + ? index[row.Entry][selectorKey]?.Value.ToString() ?? "" + : anchorGenerator.GetAnchor(attributeId); + + if (anchors.Add(anchor)) + cloudscape.Add(new PhdCloud(anchor, subRow.Cells.ToReadOnlyList())); + row[newColumn] = new PhdCell(newColumn, anchor); + } + + int minIndex = matchedColumns.Min(columns.IndexOf); + foreach (var matchedColumn in matchedColumns) + columns.Remove(matchedColumn); + columns.Insert(minIndex, newColumn); + cloudscapes.Add(cloudscape); + } + } + return cloudscapes; + } +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Tables/PhdTableConfig.cs b/src/Perfolizer/Perfolizer/Phd/Tables/PhdTableConfig.cs new file mode 100644 index 00000000..6b5e36b5 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Tables/PhdTableConfig.cs @@ -0,0 +1,25 @@ +using Perfolizer.Phd.Base; + +namespace Perfolizer.Phd.Tables; + +public class PhdTableConfig : PhdObject +{ + public List Filters { get; set; } = []; + public List ColumnDefinitions { get; set; } = []; + public List SortPolicies { get; set; } = []; + + /// + /// See + /// + public int? MaxAnchorLength { get; set; } + + public bool IsMatched(PhdEntry entry) => Filters.All(filter => filter.IsMatched(entry)); + + // public bool PrintUnitsInHeader { get; set; } + // public bool PrintUnitsInContent { get; set; } + // public bool PrintZeroValuesInContent { get; set; } + // public int MaxParameterColumnWidth { get; set; } + // public SizeUnit? SizeUnit { get; set; } + // public TimeUnit? TimeUnit { get; set; } + // public PhdRatioStyle PhdRatioStyle { get; set; } +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Phd/Tables/PhdTableView.cs b/src/Perfolizer/Perfolizer/Phd/Tables/PhdTableView.cs new file mode 100644 index 00000000..626f0a1f --- /dev/null +++ b/src/Perfolizer/Perfolizer/Phd/Tables/PhdTableView.cs @@ -0,0 +1,108 @@ +using Perfolizer.Collections; +using Perfolizer.Extensions; +using Perfolizer.Horology; +using Perfolizer.Mathematics.Common; +using Perfolizer.Metrology; +using Perfolizer.Phd.Presenting; + +namespace Perfolizer.Phd.Tables; + +public class PhdTableView +{ + public PhdTable Table { get; } + public PhdTableStyle Style { get; } + public object?[,] CellObjects { get; } + public string?[] ColumnFormats { get; } + public string[,] Cells { get; } + public int[] ColumnWidths { get; } + + public IReadOnlyList Columns => Table.Columns; + public int RowCount => Table.RowCount; + public int ColumnCount => Table.ColumnCount; + public IReadOnlyList Cloudscapes => Table.Cloudscapes; + + public PhdTableView(PhdTable table, PhdTableStyle style) + { + Table = table; + Style = style; + CellObjects = BuildCellObjects(table); + ColumnFormats = BuildColumnFormats(table); + Cells = BuildCells(table, style, CellObjects, ColumnFormats); + ColumnWidths = BuildColumnWidths(table, Cells); + } + + private static object?[,] BuildCellObjects(PhdTable table) + { + object?[,] cellObjects = new object?[table.RowCount, table.ColumnCount]; + for (int row = 0; row < table.RowCount; row++) + for (int col = 0; col < table.ColumnCount; col++) + cellObjects[row, col] = table[row, col]; + return cellObjects; + } + + private static string?[] BuildColumnFormats(PhdTable table) + { + string?[] columnFormats = new string?[table.ColumnCount]; + for (int col = 0; col < table.ColumnCount; col++) + { + var column = table.Columns[col]; + if (column is PhdFunctionColumn functionColumn) + { + var measurements = table.Rows + .Select(row => row[functionColumn].Value as Measurement) + .WhereNotNull() + .ToArray(); + if (measurements.IsEmpty()) + continue; + var unit = measurements.First().Unit; + + if (unit is TimeUnit timeUnit) + { + double[] timeIntervals = measurements + .Select(m => m.AsTimeInterval()!.Value.Nanoseconds) + .WhereNotNull() + .ToArray(); + var bestUnit = TimeUnit.GetBestTimeUnit(timeIntervals); + for (int i = 0; i < measurements.Length; i++) + { + double nominalValue = TimeUnit.Convert(measurements[i].NominalValue, timeUnit, bestUnit); + measurements[i] = nominalValue.WithUnit(bestUnit); + timeIntervals[i] = nominalValue; + } + int precision = PrecisionHelper.GetOptimalPrecision(timeIntervals); + columnFormats[col] = $"F{precision}"; + } + } + } + return columnFormats; + } + + private static string[,] BuildCells(PhdTable table, PhdTableStyle style, object?[,] cellObjects, string?[] columnFormats) + { + var formatProvider = style.FormatProvider; + string[,] cells = new string[table.RowCount, table.ColumnCount]; + for (int row = 0; row < table.RowCount; row++) + for (int col = 0; col < table.ColumnCount; col++) + { + object? cellObject = cellObjects[row, col]; + cells[row, col] = cellObject switch + { + IWithUnits withUnit => withUnit.ToString(columnFormats[col], formatProvider, style.UnitPresentation), + _ => cellObject?.ToString() ?? "" + }; + } + return cells; + } + + private static int[] BuildColumnWidths(PhdTable table, string[,] cells) + { + int[] columnWidths = new int[table.ColumnCount]; + for (int col = 0; col < table.ColumnCount; col++) + { + columnWidths[col] = table.Columns[col].Title.Length; + for (int row = 0; row < table.RowCount; row++) + columnWidths[col] = Max(columnWidths[col], cells[row, col].Length); + } + return columnWidths; + } +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Presenting/BufferedPresenter.cs b/src/Perfolizer/Perfolizer/Presenting/BufferedPresenter.cs new file mode 100644 index 00000000..b1bc4c4d --- /dev/null +++ b/src/Perfolizer/Perfolizer/Presenting/BufferedPresenter.cs @@ -0,0 +1,20 @@ +using System.Text; + +namespace Perfolizer.Presenting; + +public abstract class BufferedPresenter : IPresenter +{ + protected readonly StringBuilder Builder = new (); + + public void Write(char c) => Builder.Append(c); + public void Write(string message) => Builder.Append(message); + public void WriteLine() => Builder.AppendLine(); + + public virtual void Flush() + { + Flush(Builder.ToString()); + Builder.Clear(); + } + + protected abstract void Flush(string text); +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Presenting/ConsolePresenter.cs b/src/Perfolizer/Perfolizer/Presenting/ConsolePresenter.cs new file mode 100644 index 00000000..f250ca2f --- /dev/null +++ b/src/Perfolizer/Perfolizer/Presenting/ConsolePresenter.cs @@ -0,0 +1,9 @@ +namespace Perfolizer.Presenting; + +public class ConsolePresenter : IPresenter +{ + public void Write(char c) => Console.Write(c); + public void Write(string message) => Console.Write(message); + public void WriteLine() => Console.WriteLine(); + public void Flush() { } +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Presenting/IPresenter.cs b/src/Perfolizer/Perfolizer/Presenting/IPresenter.cs new file mode 100644 index 00000000..8984b41c --- /dev/null +++ b/src/Perfolizer/Perfolizer/Presenting/IPresenter.cs @@ -0,0 +1,9 @@ +namespace Perfolizer.Presenting; + +public interface IPresenter +{ + void Write(char c); + void Write(string message); + void WriteLine(); + void Flush(); +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Presenting/StringPresenter.cs b/src/Perfolizer/Perfolizer/Presenting/StringPresenter.cs new file mode 100644 index 00000000..e0c91a19 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Presenting/StringPresenter.cs @@ -0,0 +1,8 @@ +namespace Perfolizer.Presenting; + +public class StringPresenter : BufferedPresenter +{ + public override void Flush() { } + protected override void Flush(string text) { } + public string Dump() => Builder.ToString(); +} \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Properties/AssemblyInfo.cs b/src/Perfolizer/Perfolizer/Properties/AssemblyInfo.cs index 3ccb339f..c9a47b88 100644 --- a/src/Perfolizer/Perfolizer/Properties/AssemblyInfo.cs +++ b/src/Perfolizer/Perfolizer/Properties/AssemblyInfo.cs @@ -4,4 +4,5 @@ [assembly: CLSCompliant(true)] [assembly: InternalsVisibleTo("Perfolizer.Tests,PublicKey=" + PerfolizerInfo.PublicKey)] +[assembly: InternalsVisibleTo("Perfolizer.SimulationTests,PublicKey=" + PerfolizerInfo.PublicKey)] [assembly: InternalsVisibleTo("Perfolizer.Simulations,PublicKey=" + PerfolizerInfo.PublicKey)] \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Resources/microarchitectures.txt b/src/Perfolizer/Perfolizer/Resources/microarchitectures.txt new file mode 100644 index 00000000..f06d2953 --- /dev/null +++ b/src/Perfolizer/Perfolizer/Resources/microarchitectures.txt @@ -0,0 +1,183 @@ +//////////////////////////////////////////////////////////////////////////////// +// Kaby Lake +// See: https://en.wikipedia.org/wiki/Kaby_Lake +//////////////////////////////////////////////////////////////////////////////// + +# Kaby Lake +7Y30 +7Y32 +7Y54 +7Y57 +7Y75 +3865U +3965U +4410Y +4415U +7100 +7100H +7100T +7100U +7101E +7101TE +7130U +7167U +7200U +7260U +7267U +7287U +7300 +7300HQ +7300T +7300U +7320 +7350K +7360U +7400 +7400T +7440HQ +7500 +7500T +7500U +7560U +7567U +7600 +7600K +7600T +7600U +7640X +7660U +7700 +7700HQ +7700K +7700T +7740X +7820HK +7820HQ +7920HQ +8130U +E3-1220 v6 +E3-1225 v6 +E3-1230 v6 +E3-1240 v6 +E3-1245 v6 +E3-1270 v6 +E3-1275 v6 +E3-1280 v6 +E3-1285 v6 +E3-1505L v6 +E3-1505M v6 +E3-1535M v6 +G3930 +G3930T +G3950 +G4560 +G4560T +G4600 +G4600T +G4620 + +# Kaby Lake R +8250U +8350U +8550U +8650U + +# Kaby Lake G +8305G +8705G +8706G +8709G +8809G + +# Amber Lake Y +8100Y +8200Y +8210Y +8500Y + +//////////////////////////////////////////////////////////////////////////////// +// Coffee Lake +// See: https://en.wikipedia.org/wiki/Coffee_Lake +//////////////////////////////////////////////////////////////////////////////// + +# Coffee Lake +610 +2104G +2124 +2124G +2126G +2134 +2136 +2144G +2146G +2174G +2176G +2176M +2186G +2186M +8086K +8100 +8100H +8100T +8109U +8259U +8269U +8300 +8300H +8300T +8350K +8400 +8400B +8400H +8400T +8500 +8500B +8500T +8559U +8600 +8600K +8600T +8700 +8700B +8700K +8700T +8750H +8850H +8950HK +9350KF +9400 +9400F +9600K +9600KF +9700K +9700KF +9900K +9900KF +G4900 +G4900T +G4920 +G5400 +G5400T +G5500 +G5500T +G5600 + +//////////////////////////////////////////////////////////////////////////////// +// Cannon Lake +// See: https://en.wikipedia.org/wiki/Cannon_Lake_(microarchitecture) +//////////////////////////////////////////////////////////////////////////////// + +# Cannon Lake +8121U + +//////////////////////////////////////////////////////////////////////////////// +// Whiskey Lake +// See: https://en.wikipedia.org/wiki/Whiskey_Lake_(microarchitecture) +//////////////////////////////////////////////////////////////////////////////// + +# Whiskey Lake +8565U +8265U +8145U +5405U +4205U \ No newline at end of file diff --git a/src/Perfolizer/Perfolizer/Sample.cs b/src/Perfolizer/Perfolizer/Sample.cs index 15a6ab51..42d80be9 100644 --- a/src/Perfolizer/Perfolizer/Sample.cs +++ b/src/Perfolizer/Perfolizer/Sample.cs @@ -36,20 +36,16 @@ public class Sample : IWithUnits /// public double WeightedSize { get; } - public Sample(params double[] values) : this(values, null) - { - } + public Sample(params double[] values) : this(values, null) { } - public Sample(params int[] values) : this(values, null) - { - } + public Sample(params int[] values) : this(values, null) { } - public Sample(IReadOnlyList values, MeasurementUnit? measurementUnit = null) + public Sample(IReadOnlyList values, MeasurementUnit? unit = null) { Assertion.NotNullOrEmpty(nameof(values), values); Values = values; - Unit = measurementUnit ?? NumberUnit.Instance; + Unit = unit ?? NumberUnit.Instance; double weight = 1.0 / values.Count; Weights = new IdenticalReadOnlyList(values.Count, weight); TotalWeight = 1.0; @@ -79,8 +75,8 @@ public Sample(IReadOnlyList values, IReadOnlyList weights, Measu { totalWeight += weight; totalWeightSquared += weight.Sqr(); - maxWeight = Max(maxWeight, weight); - minWeight = Min(minWeight, weight); + maxWeight = Math.Max(maxWeight, weight); + minWeight = Math.Min(minWeight, weight); } if (minWeight < 0) @@ -111,15 +107,11 @@ public Sample(IReadOnlyList values, IReadOnlyList weights, Measu } [PublicAPI] - public Sample(IEnumerable values, MeasurementUnit? measurementUnit = null) - : this(values.Select(x => (double)x).ToList(), measurementUnit) - { - } + public Sample(IEnumerable values, MeasurementUnit? unit = null) + : this(values.Select(x => (double)x).ToList(), unit) { } - public Sample(IEnumerable values, MeasurementUnit? measurementUnit = null) - : this(values.Select(x => (double)x).ToList(), measurementUnit) - { - } + public Sample(IEnumerable values, MeasurementUnit? unit = null) + : this(values.Select(x => (double)x).ToList(), unit) { } public Sample Concat(Sample sample) { @@ -192,6 +184,7 @@ public static bool TryParse(string s, out Sample sample) string unitString = s.Substring(closeBracketIndex + 1); if (!MeasurementUnit.TryParse(unitString, out var unit)) + return false; sample = new Sample(values, unit); @@ -250,4 +243,8 @@ private static bool IsSorted(IReadOnlyList list) return false; return true; } + + public double Mean() => Values.Average(); + public double Min() => SortedValues.First(); + public double Max() => SortedValues.Last(); } \ No newline at end of file