Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JsonExporter: make Json export more extensible. #2081

Merged
merged 1 commit into from
Aug 24, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 57 additions & 47 deletions src/BenchmarkDotNet/Exporters/Json/JsonExporterBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Reports;
using Perfolizer.Horology;
using JsonSerializer = SimpleJson.SimpleJson;

namespace BenchmarkDotNet.Exporters.Json
Expand All @@ -21,53 +22,71 @@ protected JsonExporterBase(bool indentJson = false, bool excludeMeasurements = f
}

public override void ExportToLog(Summary summary, ILogger logger)
{
JsonSerializer.CurrentJsonSerializerStrategy.Indent = IndentJson;
logger.WriteLine(JsonSerializer.SerializeObject(GetDataToSerialize(summary)));
}

protected virtual IReadOnlyDictionary<string, object> GetDataToSerialize(Summary summary)
{
// If we just ask SimpleJson to serialize the entire "summary" object it throws several errors.
// So we are more specific in what we serialize (plus some fields/properties aren't relevant)
return new Dictionary<string, object>
{
{ "Title", summary.Title },
{ "HostEnvironmentInfo", GetDataToSerialize(summary.HostEnvironmentInfo) },
{ "Benchmarks", summary.Reports.Select(GetDataToSerialize) }
};
}

protected virtual IReadOnlyDictionary<string, object> GetDataToSerialize(HostEnvironmentInfo environmentInfo)
{
// We construct HostEnvironmentInfo manually, so that we can have the HardwareTimerKind enum as text, rather than an integer
// SimpleJson serializer doesn't seem to have an enum String/Value option (to-be-fair, it is meant to be "Simple")
var environmentInfo = new
return new Dictionary<string, object>
{
HostEnvironmentInfo.BenchmarkDotNetCaption,
summary.HostEnvironmentInfo.BenchmarkDotNetVersion,
OsVersion = summary.HostEnvironmentInfo.OsVersion.Value,
ProcessorName = ProcessorBrandStringHelper.Prettify(summary.HostEnvironmentInfo.CpuInfo.Value),
summary.HostEnvironmentInfo.CpuInfo.Value?.PhysicalProcessorCount,
summary.HostEnvironmentInfo.CpuInfo.Value?.PhysicalCoreCount,
summary.HostEnvironmentInfo.CpuInfo.Value?.LogicalCoreCount,
summary.HostEnvironmentInfo.RuntimeVersion,
summary.HostEnvironmentInfo.Architecture,
summary.HostEnvironmentInfo.HasAttachedDebugger,
summary.HostEnvironmentInfo.HasRyuJit,
summary.HostEnvironmentInfo.Configuration,
DotNetCliVersion = summary.HostEnvironmentInfo.DotNetSdkVersion.Value,
summary.HostEnvironmentInfo.ChronometerFrequency,
HardwareTimerKind = summary.HostEnvironmentInfo.HardwareTimerKind.ToString()
{ nameof(HostEnvironmentInfo.BenchmarkDotNetCaption), HostEnvironmentInfo.BenchmarkDotNetCaption },
{ nameof(environmentInfo.BenchmarkDotNetVersion), environmentInfo.BenchmarkDotNetVersion },
{ "OsVersion", environmentInfo.OsVersion.Value },
{ "ProcessorName", ProcessorBrandStringHelper.Prettify(environmentInfo.CpuInfo.Value) },
{ "PhysicalProcessorCount", environmentInfo.CpuInfo.Value?.PhysicalProcessorCount },
{ "PhysicalCoreCount", environmentInfo.CpuInfo.Value?.PhysicalCoreCount },
{ "LogicalCoreCount", environmentInfo.CpuInfo.Value?.LogicalCoreCount },
{ nameof(environmentInfo.RuntimeVersion), environmentInfo.RuntimeVersion },
{ nameof(environmentInfo.Architecture), environmentInfo.Architecture },
{ nameof(environmentInfo.HasAttachedDebugger), environmentInfo.HasAttachedDebugger },
{ nameof(environmentInfo.HasRyuJit), environmentInfo.HasRyuJit },
{ nameof(environmentInfo.Configuration), environmentInfo.Configuration },
{ "DotNetCliVersion", environmentInfo.DotNetSdkVersion.Value },
{ nameof(environmentInfo.ChronometerFrequency), environmentInfo.ChronometerFrequency },
{ nameof(HardwareTimerKind), environmentInfo.HardwareTimerKind.ToString() },
};
}

// If we just ask SimpleJson to serialize the entire "summary" object it throws several errors.
// So we are more specific in what we serialize (plus some fields/properties aren't relevant)

var benchmarks = summary.Reports.Select(report =>
protected virtual IReadOnlyDictionary<string, object> GetDataToSerialize(BenchmarkReport report)
{
var benchmark = new Dictionary<string, object>
{
var data = new Dictionary<string, object>
// We don't need Benchmark.ShortInfo, that info is available via Benchmark.Parameters below
{ "DisplayInfo", report.BenchmarkCase.DisplayInfo },
{ "Namespace", report.BenchmarkCase.Descriptor.Type.Namespace },
{ "Type", FullNameProvider.GetTypeName(report.BenchmarkCase.Descriptor.Type) },
{ "Method", report.BenchmarkCase.Descriptor.WorkloadMethod.Name },
{ "MethodTitle", report.BenchmarkCase.Descriptor.WorkloadMethodDisplayInfo },
{ "Parameters", report.BenchmarkCase.Parameters.PrintInfo },
{
// We don't need Benchmark.ShortInfo, that info is available via Benchmark.Parameters below
{ "DisplayInfo", report.BenchmarkCase.DisplayInfo },
{ "Namespace", report.BenchmarkCase.Descriptor.Type.Namespace },
{ "Type", FullNameProvider.GetTypeName(report.BenchmarkCase.Descriptor.Type) },
{ "Method", report.BenchmarkCase.Descriptor.WorkloadMethod.Name },
{ "MethodTitle", report.BenchmarkCase.Descriptor.WorkloadMethodDisplayInfo },
{ "Parameters", report.BenchmarkCase.Parameters.PrintInfo },
{ "FullName", FullNameProvider.GetBenchmarkName(report.BenchmarkCase) }, // do NOT remove this property, it is used for xunit-performance migration
// Hardware Intrinsics can be disabled using env vars, that is why they might be different per benchmark and are not exported as part of HostEnvironmentInfo
{ "HardwareIntrinsics", report.GetHardwareIntrinsicsInfo() ?? "" },
// { "Properties", r.Benchmark.Job.ToSet().ToDictionary(p => p.Name, p => p.Value) }, // TODO
{ "Statistics", report.ResultStatistics }
};
"FullName", FullNameProvider.GetBenchmarkName(report.BenchmarkCase)
}, // do NOT remove this property, it is used for xunit-performance migration
// Hardware Intrinsics can be disabled using env vars, that is why they might be different per benchmark and are not exported as part of HostEnvironmentInfo
{ "HardwareIntrinsics", report.GetHardwareIntrinsicsInfo() ?? "" },
// { "Properties", r.Benchmark.Job.ToSet().ToDictionary(p => p.Name, p => p.Value) }, // TODO
{ "Statistics", report.ResultStatistics }
};

// We show MemoryDiagnoser's results only if it is being used
if (report.BenchmarkCase.Config.HasMemoryDiagnoser())
{
data.Add("Memory", new
benchmark.Add("Memory", new
{
report.GcStats.Gen0Collections,
report.GcStats.Gen1Collections,
Expand All @@ -80,7 +99,7 @@ public override void ExportToLog(Summary summary, ILogger logger)
if (ExcludeMeasurements == false)
{
// We construct Measurements manually, so that we can have the IterationMode enum as text, rather than an integer
data.Add("Measurements",
benchmark.Add("Measurements",
report.AllMeasurements.Select(m => new
{
IterationMode = m.IterationMode.ToString(),
Expand All @@ -93,20 +112,11 @@ public override void ExportToLog(Summary summary, ILogger logger)

if (report.Metrics.Any())
{
data.Add("Metrics", report.Metrics.Values);
benchmark.Add("Metrics", report.Metrics.Values);
}
}

return data;
});

JsonSerializer.CurrentJsonSerializerStrategy.Indent = IndentJson;
logger.WriteLine(JsonSerializer.SerializeObject(new Dictionary<string, object>
{
{ "Title", summary.Title },
{ "HostEnvironmentInfo", environmentInfo },
{ "Benchmarks", benchmarks }
}));
return benchmark;
}
}
}