diff --git a/src/tools/Reporting/Directory.Packages.props b/src/tools/Reporting/Directory.Packages.props
index 0fde0cf995d..234b485b92a 100644
--- a/src/tools/Reporting/Directory.Packages.props
+++ b/src/tools/Reporting/Directory.Packages.props
@@ -4,7 +4,7 @@
true
-
+
diff --git a/src/tools/Reporting/Reporting.Tests/ReporterTests.cs b/src/tools/Reporting/Reporting.Tests/ReporterTests.cs
index ee649e5df84..bee589682e4 100644
--- a/src/tools/Reporting/Reporting.Tests/ReporterTests.cs
+++ b/src/tools/Reporting/Reporting.Tests/ReporterTests.cs
@@ -2,9 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using Newtonsoft.Json;
using System;
using System.Runtime.InteropServices;
+using System.Text.Json;
using Xunit;
namespace Reporting.Tests;
@@ -30,7 +30,6 @@ public class ReporterTests
---------------|-------------------------|-------------------------|-------------------------
CounterName |10000000000000000.000 ns |10000000000000000.000 ns |10000000000000000.000 ns
";
-
private const string NoResultsTable =
@"TestName
No results in file.
@@ -57,6 +56,7 @@ public void WriteReportTableWithEmptyResults()
new Counter
{
DefaultCounter = true,
+ TopCounter = true,
MetricName = "ns",
Name = "CounterName",
Results = []
@@ -79,6 +79,7 @@ public void WriteReportTableWithNullResults()
new Counter
{
DefaultCounter = true,
+ TopCounter = true,
MetricName = "ns",
Name = "CounterName",
Results = null
@@ -119,7 +120,7 @@ public void WriteReportTableWithoutEnvironment()
public void WriteReportWithLongNameTableWithoutEnvironment()
{
PerfLabEnvironmentProviderMock environment = new NonPerfLabEnvironmentProviderMock();
- var reporter = GetReporterWithSpecifiedEnvironment(environment, counterName:"ThisIsALongerCounterName");
+ var reporter = GetReporterWithSpecifiedEnvironment(environment, counterName: "ThisIsALongerCounterName");
var table = reporter.WriteResultTable();
Assert.Equal(LongCounterNameTable, table);
}
@@ -139,8 +140,9 @@ public void JsonCanBeGenerated()
var environment = new PerfLabEnvironmentProviderMock();
var reporter = GetReporterWithSpecifiedEnvironment(environment);
var jsonString = reporter.GetJson();
+ Assert.NotNull(jsonString);
- var jsonObj = JsonConvert.DeserializeObject(jsonString);
+ var jsonObj = DeserializeReporter(jsonString);
Assert.Equal(environment.GetEnvironmentVariable("HELIX_CORRELATION_ID"), jsonObj.Run.CorrelationId);
Assert.Equal(environment.GetEnvironmentVariable("HELIX_WORKITEM_FRIENDLYNAME"), jsonObj.Run.WorkItemName);
@@ -168,6 +170,55 @@ public void JsonCanBeGenerated()
Assert.Equal(1.1, retCounter.Results[0]);
}
+ [Fact]
+ public void LegacyCounterJsonRemainsCompatible()
+ {
+ var reporter = GetReporterWithSpecifiedEnvironment(new PerfLabEnvironmentProviderMock());
+ var jsonString = reporter.GetJson();
+ Assert.NotNull(jsonString);
+
+ using var document = JsonDocument.Parse(jsonString);
+ var jsonCounter = document.RootElement.GetProperty("tests")[0].GetProperty("counters")[0];
+
+ Assert.Equal(JsonValueKind.False, jsonCounter.GetProperty("higherIsBetter").ValueKind);
+ Assert.False(jsonCounter.TryGetProperty("regressionThreshold", out _));
+ Assert.False(jsonCounter.TryGetProperty("direction", out _));
+
+ var deserialized = DeserializeReporter(jsonString);
+ Assert.False(deserialized.Tests[0].Counters[0].HigherIsBetter);
+ Assert.Null(deserialized.Tests[0].Counters[0].RegressionThreshold);
+ }
+
+ [Fact]
+ public void RegressionThresholdIsSerialized()
+ {
+ var reporter = GetReporterWithSpecifiedEnvironment(new PerfLabEnvironmentProviderMock());
+ reporter.Tests[0].Counters[0].RegressionThreshold = 0.02;
+
+ var jsonString = reporter.GetJson();
+ Assert.NotNull(jsonString);
+
+ using var document = JsonDocument.Parse(jsonString);
+ var jsonCounter = document.RootElement.GetProperty("tests")[0].GetProperty("counters")[0];
+ Assert.Equal(0.02, jsonCounter.GetProperty("regressionThreshold").GetDouble());
+
+ var deserialized = DeserializeReporter(jsonString);
+ Assert.Equal(0.02, deserialized.Tests[0].Counters[0].RegressionThreshold);
+ }
+
+ [Fact]
+ public void NonFiniteResultsRoundTrip()
+ {
+ var reporter = GetReporterWithSpecifiedEnvironment(new PerfLabEnvironmentProviderMock(), result: double.NaN);
+
+ var jsonString = reporter.GetJson();
+ Assert.NotNull(jsonString);
+ Assert.Contains("\"NaN\"", jsonString);
+
+ var deserialized = DeserializeReporter(jsonString);
+ Assert.True(double.IsNaN(deserialized.Tests[0].Counters[0].Results[0]));
+ }
+
[Fact]
public void EnforceDefaultCounterConstraint()
{
@@ -205,6 +256,9 @@ public void AddCountersEnumerable()
Assert.Equal(2, t.Counters.Count);
}
+ private static Reporter DeserializeReporter(string json)
+ => Reporter.FromJson(json);
+
private static Reporter GetReporterWithSpecifiedEnvironment(PerfLabEnvironmentProviderMock enviroment, string counterName = null, double result = 1.1)
{
var reporter = new Reporter(enviroment);
diff --git a/src/tools/Reporting/Reporting/Counter.cs b/src/tools/Reporting/Reporting/Counter.cs
index 2a7b8b686f3..0abc689e925 100644
--- a/src/tools/Reporting/Reporting/Counter.cs
+++ b/src/tools/Reporting/Reporting/Counter.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
+using System.Text.Json.Serialization;
namespace Reporting;
@@ -18,6 +19,9 @@ public class Counter
public string MetricName { get; set; } = "Count";
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public double? RegressionThreshold { get; set; }
+
public IList? Results { get; set; }
public override string ToString() => $"{nameof(Name)}: {Name}, {nameof(TopCounter)}: {TopCounter}, {nameof(DefaultCounter)}: {DefaultCounter}, {nameof(MetricName)}: {MetricName}";
diff --git a/src/tools/Reporting/Reporting/Reporter.cs b/src/tools/Reporting/Reporting/Reporter.cs
index a405e6e5096..f0ef0b3941c 100644
--- a/src/tools/Reporting/Reporting/Reporter.cs
+++ b/src/tools/Reporting/Reporting/Reporter.cs
@@ -2,8 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using Newtonsoft.Json;
-using Newtonsoft.Json.Serialization;
using System;
using System.Collections;
using System.Collections.Generic;
@@ -12,6 +10,8 @@
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
using RuntimeEnvironment = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment;
namespace Reporting;
@@ -19,13 +19,11 @@ namespace Reporting;
public class Reporter
{
private readonly static CultureInfo _culture = CultureInfo.InvariantCulture;
- private readonly static JsonSerializerSettings _jsonSerializerSettings = new()
+ private readonly static JsonSerializerOptions _jsonSerializerOptions = new()
{
- ContractResolver = new DefaultContractResolver
- {
- NamingStrategy = new CamelCaseNamingStrategy() { ProcessDictionaryKeys = false }
- },
- Culture = _culture
+ NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ WriteIndented = true
};
public List Tests { get; private set; } = [];
@@ -65,7 +63,18 @@ public void AddTest(Test test)
}
public string? GetJson()
- => InLab ? JsonConvert.SerializeObject(this, Formatting.Indented, _jsonSerializerSettings) : null;
+ {
+ if (!InLab)
+ {
+ return null;
+ }
+
+ return JsonSerializer.Serialize(this, _jsonSerializerOptions);
+ }
+
+ public static Reporter FromJson(string json)
+ => JsonSerializer.Deserialize(json, _jsonSerializerOptions)
+ ?? throw new JsonException("The JSON payload did not contain a reporter.");
public string WriteResultTable()
{
@@ -81,7 +90,7 @@ public string WriteResultTable()
var countersWithResults = test.Counters.Where(c => c.Results != null && c.Results.Count > 0);
var counterWidth = Math.Max(test.Counters.Max(c => c.Name.Length) + 1, 15);
- var resultWidth = Math.Max(countersWithResults.Max(c => c.Results.Max().ToString("F3", _culture).Length + c.MetricName.Length) + 2, 15);
+ var resultWidth = Math.Max(countersWithResults.Max(c => c.Results.Max().ToString("F3", _culture).Length + (c.MetricName?.Length ?? 0)) + 2, 15);
ret.AppendLine(test.Name);
ret.AppendLine($"{LeftJustify("Metric", counterWidth)}|{LeftJustify("Average", resultWidth)}|{LeftJustify("Min", resultWidth)}|{LeftJustify("Max", resultWidth)}");
ret.AppendLine($"{new string('-', counterWidth)}|{new string('-', resultWidth)}|{new string('-', resultWidth)}|{new string('-', resultWidth)}");
diff --git a/src/tools/Reporting/Reporting/Reporting.csproj b/src/tools/Reporting/Reporting/Reporting.csproj
index 7f9c470cb41..af9642877bf 100644
--- a/src/tools/Reporting/Reporting/Reporting.csproj
+++ b/src/tools/Reporting/Reporting/Reporting.csproj
@@ -8,7 +8,7 @@
-
+