diff --git a/tools/perf-automation/Azure.Sdk.Tools.PerfAutomation.Tests/NumberFormatterTests.cs b/tools/perf-automation/Azure.Sdk.Tools.PerfAutomation.Tests/NumberFormatterTests.cs new file mode 100644 index 00000000000..cb0a217baf0 --- /dev/null +++ b/tools/perf-automation/Azure.Sdk.Tools.PerfAutomation.Tests/NumberFormatterTests.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using NUnit.Framework; +using System; + +namespace Azure.Sdk.Tools.PerfAutomation.Tests +{ + public class NumberFormatterTests + { + // Standard Tests + [TestCase(0.00012345, 1, "0.0001")] + [TestCase(0.00012345, 2, "0.00012")] + [TestCase(0.00012345, 3, "0.000123")] + [TestCase(0.00012345, 4, "0.0001235")] + [TestCase(0.00012345, 5, "0.00012345")] + [TestCase(0.00012345, 6, "0.000123450")] + [TestCase(0.00012345, 7, "0.0001234500")] + [TestCase(1.2345, 1, "1")] + [TestCase(1.2345, 2, "1.2")] + [TestCase(1.2345, 3, "1.23")] + [TestCase(1.2345, 4, "1.235")] + [TestCase(1.2345, 5, "1.2345")] + [TestCase(1.2345, 6, "1.23450")] + [TestCase(1.2345, 7, "1.234500")] + [TestCase(12_345, 1, "12,345")] + [TestCase(12_345, 2, "12,345")] + [TestCase(12_345, 3, "12,345")] + [TestCase(12_345, 4, "12,345")] + [TestCase(12_345, 5, "12,345")] + [TestCase(12_345, 6, "12,345.0")] + [TestCase(12_345, 7, "12,345.00")] + + // Group separator + [TestCase(12_345, 1, "12345", false)] + [TestCase(12_345, 2, "12345", false)] + [TestCase(12_345, 3, "12345", false)] + [TestCase(12_345, 4, "12345", false)] + [TestCase(12_345, 5, "12345", false)] + [TestCase(12_345, 6, "12345.0", false)] + [TestCase(12_345, 7, "12345.00", false)] + + // Bug where fractional part of log10 was > 0.5 + [TestCase(8.22929639076288, 4, "8.229")] + + // Zero + [TestCase(0, 1, "0")] + [TestCase(0, 2, "0.0")] + [TestCase(0, 3, "0.00")] + [TestCase(0, 4, "0.000")] + + // Negative numbers + [TestCase(-1, 1, "-1")] + [TestCase(-1, 4, "-1.000")] + [TestCase(-0.00012345, 4, "-0.0001235")] + [TestCase(-1.2345, 4, "-1.235")] + [TestCase(-12_345, 4, "-12,345")] + public void Format(double value, int minSignificantDigits, string expected, bool groupSeparator = true) + { + Assert.AreEqual(expected, NumberFormatter.Format(value, minSignificantDigits, groupSeparator)); + } + + [TestCase(1.2345, 0, typeof(ArgumentException))] + [TestCase(1.2345, -1, typeof(ArgumentException))] + public void FormatException(double value, int minSignificantDigits, Type expectedExceptionType) + { + Assert.Throws(expectedExceptionType, () => NumberFormatter.Format(value, minSignificantDigits)); + } + } +} diff --git a/tools/perf-automation/Azure.Sdk.Tools.PerfAutomation/NumberFormatter.cs b/tools/perf-automation/Azure.Sdk.Tools.PerfAutomation/NumberFormatter.cs new file mode 100644 index 00000000000..87724842280 --- /dev/null +++ b/tools/perf-automation/Azure.Sdk.Tools.PerfAutomation/NumberFormatter.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Azure.Sdk.Tools.PerfAutomation +{ + public static class NumberFormatter + { + // Formats a number with a minimum number of significant digits. + // Digits to the left of the decimal point are always significant. + // Examples: + // - Format(0, 4) -> "0.000" + // - Format(12345, 4) -> "12,345" + // - Format(1.2345, 4) -> "1.235" + // - Format(0.00012345, 4) -> "0.0001235" + public static string Format(double value, int minSignificantDigits, bool groupSeparator = true) + { + if (minSignificantDigits <= 0) + { + throw new ArgumentException("Must be greater than zero", nameof(minSignificantDigits)); + } + + // Special case since log(0) is undefined + if (value == 0) + { + return value.ToString($"F{minSignificantDigits - 1}"); + } + + double log = Math.Log10(Math.Abs(value)); + int significantDigits = (int)Math.Max(Math.Ceiling(log), minSignificantDigits); + + double divisor = Math.Pow(10, Math.Ceiling(log) - significantDigits); + double rounded = divisor * Math.Round(value / divisor, MidpointRounding.AwayFromZero); + + int decimals = (int)Math.Max(0, significantDigits - Math.Floor(log) - 1); + + return rounded.ToString(groupSeparator ? $"N{decimals}" : $"F{decimals}"); + } + } +} diff --git a/tools/perf-automation/Azure.Sdk.Tools.PerfAutomation/Program.cs b/tools/perf-automation/Azure.Sdk.Tools.PerfAutomation/Program.cs index 047ffe73e12..7a37922a8c5 100644 --- a/tools/perf-automation/Azure.Sdk.Tools.PerfAutomation/Program.cs +++ b/tools/perf-automation/Azure.Sdk.Tools.PerfAutomation/Program.cs @@ -552,7 +552,8 @@ private static async Task WriteResultsSummaryThroughput( row.Add(resultSummary.Test); row.Add(resultSummary.Arguments); - var operationsPerSecondStrings = operationsPerSecond(resultSummary).Select(o => $"{o.operationsPerSecond:F2}"); + var operationsPerSecondStrings = operationsPerSecond(resultSummary) + .Select(o => $"{NumberFormatter.Format(o.operationsPerSecond, 4, groupSeparator: outputFormat != OutputFormat.Csv)}"); var operationsPerSecondDifferencesStrings = operationsPerSecondDifferences(resultSummary).Select(o => $"{o * 100:N2}%"); var values = operationsPerSecondStrings.Take(1).Concat(operationsPerSecondStrings.Skip(1)