Skip to content

Commit

Permalink
feat: add text justification style (#2410)
Browse files Browse the repository at this point in the history
* feat: add text justification style

* fix: apply review changes

* fix: remove extra space and set default justification to right for numeric columns

* fix: use uniform justification for header columns

* fix: use HeaderSeparator
  • Loading branch information
Vahdanian committed Sep 1, 2023
1 parent 2a8bab5 commit b035d90
Show file tree
Hide file tree
Showing 35 changed files with 408 additions and 318 deletions.
44 changes: 19 additions & 25 deletions src/BenchmarkDotNet/Exporters/MarkdownExporter.cs
Expand Up @@ -79,8 +79,8 @@ public enum MarkdownHighlightStrategy
[PublicAPI] protected string CodeBlockStart = "```";
[PublicAPI] protected string CodeBlockEnd = "```";
[PublicAPI] protected MarkdownHighlightStrategy StartOfGroupHighlightStrategy = MarkdownHighlightStrategy.None;
[PublicAPI] protected string TableHeaderSeparator = " |";
[PublicAPI] protected string TableColumnSeparator = " |";
[PublicAPI] protected string TableHeaderSeparator = " | ";
[PublicAPI] protected string TableColumnSeparator = " | ";
[PublicAPI] protected bool UseHeaderSeparatingRow = true;
[PublicAPI] protected bool ColumnsStartWithSeparator;
[PublicAPI] protected string BoldMarkupFormat = "**{0}**";
Expand Down Expand Up @@ -156,21 +156,17 @@ private void PrintTable(SummaryTable table, ILogger logger)
logger.WriteLine();
}

if (ColumnsStartWithSeparator)
{
logger.WriteStatistic(TableHeaderSeparator.TrimStart());
}
logger.WriteStatistic(ColumnsStartWithSeparator ? TableHeaderSeparator.TrimStart() : " ");

table.PrintLine(table.FullHeader, logger, string.Empty, TableHeaderSeparator);
if (UseHeaderSeparatingRow)
{
if (ColumnsStartWithSeparator)
{
logger.WriteStatistic(TableHeaderSeparator.TrimStart());
}
logger.WriteStatistic(ColumnsStartWithSeparator ? TableHeaderSeparator.TrimStart().TrimEnd() + "-" : "-");

logger.WriteLineStatistic(string.Join("",
table.Columns.Where(c => c.NeedToShow).Select(column => new string('-', column.Width) + GetJustificationIndicator(column.Justify) + "|")));
table.Columns.Where(c => c.NeedToShow).Select(column =>
new string('-', column.Width - 1) + GetHeaderSeparatorIndicator(column.OriginalColumn.IsNumeric) +
GetHeaderSeparatorColumnDivider(column.Index, table.ColumnCount))));
}

int rowCounter = 0;
Expand All @@ -181,8 +177,8 @@ private void PrintTable(SummaryTable table, ILogger logger)
if (rowCounter > 0 && table.FullContentStartOfLogicalGroup[rowCounter] && table.SeparateLogicalGroups)
{
// Print logical separator
if (ColumnsStartWithSeparator)
logger.WriteStatistic(TableColumnSeparator.TrimStart());
logger.WriteStatistic(ColumnsStartWithSeparator ? TableColumnSeparator.TrimStart() : " ");

table.PrintLine(separatorLine, logger, string.Empty, TableColumnSeparator, highlightRow, false, StartOfGroupHighlightStrategy,
BoldMarkupFormat, false);
}
Expand All @@ -193,26 +189,24 @@ private void PrintTable(SummaryTable table, ILogger logger)
highlightRow = !highlightRow;
}

if (ColumnsStartWithSeparator)
logger.WriteStatistic(TableColumnSeparator.TrimStart());

logger.WriteStatistic(ColumnsStartWithSeparator ? TableColumnSeparator.TrimStart() : " ");

table.PrintLine(line, logger, string.Empty, TableColumnSeparator, highlightRow, table.FullContentStartOfHighlightGroup[rowCounter],
StartOfGroupHighlightStrategy, BoldMarkupFormat, EscapeHtml);
rowCounter++;
}
}

private static string GetJustificationIndicator(SummaryTable.SummaryTableColumn.TextJustification textJustification)
private static string GetHeaderSeparatorIndicator(bool isNumeric)
{
switch (textJustification)
{
case SummaryTable.SummaryTableColumn.TextJustification.Left:
return " ";
case SummaryTable.SummaryTableColumn.TextJustification.Right:
return ":";
default:
return " ";
}
return isNumeric ? ":" : " ";
}

private static string GetHeaderSeparatorColumnDivider(int columnIndex, int columnCount)
{
var isLastColumn = columnIndex != columnCount - 1;
return isLastColumn ? "|-" : "|";
}
}
}
20 changes: 19 additions & 1 deletion src/BenchmarkDotNet/Helpers/HashCode.cs
Expand Up @@ -109,6 +109,23 @@ public static int Combine<T1>(T1 value1)
return hashCode;
}

public static int Combine<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7,
T8 value8, T9 value9, T10 value10)
{
int hashCode = 0;
hashCode = Hash(hashCode, value1);
hashCode = Hash(hashCode, value2);
hashCode = Hash(hashCode, value3);
hashCode = Hash(hashCode, value4);
hashCode = Hash(hashCode, value5);
hashCode = Hash(hashCode, value6);
hashCode = Hash(hashCode, value7);
hashCode = Hash(hashCode, value8);
hashCode = Hash(hashCode, value9);
hashCode = Hash(hashCode, value10);
return hashCode;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Hash<T>(int hashCode, T value)
{
Expand All @@ -128,7 +145,8 @@ private static int Hash<T>(int hashCode, T value, IEqualityComparer<T> comparer)
}

#pragma warning disable CS0809 // Obsolete member 'HashCode.GetHashCode()' overrides non-obsolete member 'object.GetHashCode()'
[Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes. Use ToHashCode to retrieve the computed hash code.", error: true)]
[Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes. Use ToHashCode to retrieve the computed hash code.",
error: true)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => throw new NotSupportedException();

Expand Down
5 changes: 3 additions & 2 deletions src/BenchmarkDotNet/Reports/Summary.cs
Expand Up @@ -47,7 +47,8 @@ public class Summary
TimeSpan totalTime,
CultureInfo cultureInfo,
ImmutableArray<ValidationError> validationErrors,
ImmutableArray<IColumnHidingRule> columnHidingRules)
ImmutableArray<IColumnHidingRule> columnHidingRules,
SummaryStyle summaryStyle = null)
{
Title = title;
ResultsDirectoryPath = resultsDirectoryPath;
Expand All @@ -64,7 +65,7 @@ public class Summary
BenchmarksCases = Orderer.GetSummaryOrder(reports.Select(report => report.BenchmarkCase).ToImmutableArray(), this).ToImmutableArray(); // we sort it first
Reports = BenchmarksCases.Select(b => ReportMap[b]).ToImmutableArray(); // we use sorted collection to re-create reports list
BaseliningStrategy = BaseliningStrategy.Create(BenchmarksCases);
Style = GetConfiguredSummaryStyleOrDefaultOne(BenchmarksCases).WithCultureInfo(cultureInfo);
Style = (summaryStyle ?? GetConfiguredSummaryStyleOrDefaultOne(BenchmarksCases)).WithCultureInfo(cultureInfo);
Table = GetTable(Style);
AllRuntimes = BuildAllRuntimes(HostEnvironmentInfo, Reports);
}
Expand Down
41 changes: 30 additions & 11 deletions src/BenchmarkDotNet/Reports/SummaryStyle.cs
Expand Up @@ -3,14 +3,17 @@
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Helpers;
using Perfolizer.Horology;
using static BenchmarkDotNet.Reports.SummaryTable.SummaryTableColumn;

// ReSharper disable MemberCanBePrivate.Global

namespace BenchmarkDotNet.Reports
{
public class SummaryStyle : IEquatable<SummaryStyle>
{
public static readonly SummaryStyle Default = new SummaryStyle(DefaultCultureInfo.Instance, printUnitsInHeader: false, printUnitsInContent: true, printZeroValuesInContent: false, sizeUnit: null, timeUnit: null);
public static readonly SummaryStyle Default = new SummaryStyle(DefaultCultureInfo.Instance, printUnitsInHeader: false, printUnitsInContent: true,
printZeroValuesInContent: false, sizeUnit: null, timeUnit: null);

internal const int DefaultMaxParameterColumnWidth = 15 + 5; // 5 is for postfix " [15]"

public bool PrintUnitsInHeader { get; }
Expand All @@ -24,8 +27,12 @@ public class SummaryStyle : IEquatable<SummaryStyle>

public RatioStyle RatioStyle { get; }

public TextJustification TextColumnJustification { get; }
public TextJustification NumericColumnJustification { get; }

public SummaryStyle(CultureInfo? cultureInfo, bool printUnitsInHeader, SizeUnit sizeUnit, TimeUnit timeUnit, bool printUnitsInContent = true,
bool printZeroValuesInContent = false, int maxParameterColumnWidth = DefaultMaxParameterColumnWidth, RatioStyle ratioStyle = RatioStyle.Value)
bool printZeroValuesInContent = false, int maxParameterColumnWidth = DefaultMaxParameterColumnWidth, RatioStyle ratioStyle = RatioStyle.Value,
TextJustification textColumnJustification = TextJustification.Left, TextJustification numericColumnJustification = TextJustification.Right)
{
if (maxParameterColumnWidth < DefaultMaxParameterColumnWidth)
throw new ArgumentOutOfRangeException(nameof(maxParameterColumnWidth), $"{DefaultMaxParameterColumnWidth} is the minimum.");
Expand All @@ -35,29 +42,37 @@ public class SummaryStyle : IEquatable<SummaryStyle>
PrintUnitsInContent = printUnitsInContent;
SizeUnit = sizeUnit;
TimeUnit = timeUnit;
PrintZeroValuesInContent = printZeroValuesInContent;
MaxParameterColumnWidth = maxParameterColumnWidth;
RatioStyle = ratioStyle;
CodeSizeUnit = SizeUnit.B;
PrintZeroValuesInContent = printZeroValuesInContent;
TextColumnJustification = textColumnJustification;
NumericColumnJustification = numericColumnJustification;
}

public SummaryStyle WithTimeUnit(TimeUnit timeUnit)
=> new SummaryStyle(CultureInfo, PrintUnitsInHeader, SizeUnit, timeUnit, PrintUnitsInContent, PrintZeroValuesInContent, MaxParameterColumnWidth, RatioStyle);
=> new SummaryStyle(CultureInfo, PrintUnitsInHeader, SizeUnit, timeUnit, PrintUnitsInContent, PrintZeroValuesInContent, MaxParameterColumnWidth,
RatioStyle, TextColumnJustification, NumericColumnJustification);

public SummaryStyle WithSizeUnit(SizeUnit sizeUnit)
=> new SummaryStyle(CultureInfo, PrintUnitsInHeader, sizeUnit, TimeUnit, PrintUnitsInContent, PrintZeroValuesInContent, MaxParameterColumnWidth, RatioStyle);
=> new SummaryStyle(CultureInfo, PrintUnitsInHeader, sizeUnit, TimeUnit, PrintUnitsInContent, PrintZeroValuesInContent, MaxParameterColumnWidth,
RatioStyle, TextColumnJustification, NumericColumnJustification);

public SummaryStyle WithZeroMetricValuesInContent()
=> new SummaryStyle(CultureInfo, PrintUnitsInHeader, SizeUnit, TimeUnit, PrintUnitsInContent, printZeroValuesInContent: true, MaxParameterColumnWidth, RatioStyle);
=> new SummaryStyle(CultureInfo, PrintUnitsInHeader, SizeUnit, TimeUnit, PrintUnitsInContent, printZeroValuesInContent: true,
MaxParameterColumnWidth, RatioStyle, TextColumnJustification, NumericColumnJustification);

public SummaryStyle WithMaxParameterColumnWidth(int maxParameterColumnWidth)
=> new SummaryStyle(CultureInfo, PrintUnitsInHeader, SizeUnit, TimeUnit, PrintUnitsInContent, PrintZeroValuesInContent, maxParameterColumnWidth, RatioStyle);
=> new SummaryStyle(CultureInfo, PrintUnitsInHeader, SizeUnit, TimeUnit, PrintUnitsInContent, PrintZeroValuesInContent, maxParameterColumnWidth,
RatioStyle, TextColumnJustification, NumericColumnJustification);

public SummaryStyle WithCultureInfo(CultureInfo cultureInfo)
=> new SummaryStyle(cultureInfo, PrintUnitsInHeader, SizeUnit, TimeUnit, PrintUnitsInContent, PrintZeroValuesInContent, MaxParameterColumnWidth, RatioStyle);
=> new SummaryStyle(cultureInfo, PrintUnitsInHeader, SizeUnit, TimeUnit, PrintUnitsInContent, PrintZeroValuesInContent, MaxParameterColumnWidth,
RatioStyle, TextColumnJustification, NumericColumnJustification);

public SummaryStyle WithRatioStyle(RatioStyle ratioStyle)
=> new SummaryStyle(CultureInfo, PrintUnitsInHeader, SizeUnit, TimeUnit, PrintUnitsInContent, PrintZeroValuesInContent, MaxParameterColumnWidth, ratioStyle);
=> new SummaryStyle(CultureInfo, PrintUnitsInHeader, SizeUnit, TimeUnit, PrintUnitsInContent, PrintZeroValuesInContent, MaxParameterColumnWidth,
ratioStyle, TextColumnJustification, NumericColumnJustification);

public bool Equals(SummaryStyle other)
{
Expand All @@ -73,7 +88,9 @@ public bool Equals(SummaryStyle other)
&& Equals(CodeSizeUnit, other.CodeSizeUnit)
&& Equals(TimeUnit, other.TimeUnit)
&& MaxParameterColumnWidth == other.MaxParameterColumnWidth
&& RatioStyle == other.RatioStyle;
&& RatioStyle == other.RatioStyle
&& TextColumnJustification == other.TextColumnJustification
&& NumericColumnJustification == other.NumericColumnJustification;
}

public override bool Equals(object obj) => obj is SummaryStyle summary && Equals(summary);
Expand All @@ -87,7 +104,9 @@ public bool Equals(SummaryStyle other)
CodeSizeUnit,
TimeUnit,
MaxParameterColumnWidth,
RatioStyle);
RatioStyle,
TextColumnJustification,
NumericColumnJustification);

public static bool operator ==(SummaryStyle left, SummaryStyle right) => Equals(left, right);

Expand Down
5 changes: 2 additions & 3 deletions src/BenchmarkDotNet/Reports/SummaryTable.cs
Expand Up @@ -55,6 +55,7 @@ internal SummaryTable(Summary summary, SummaryStyle style = null)
.Select(r => r.GcStats.GetBytesAllocatedPerOperation(r.BenchmarkCase).Value)
.ToArray()));
}
EffectiveSummaryStyle = style;

var columns = summary.GetColumns();
ColumnCount = columns.Length;
Expand Down Expand Up @@ -96,8 +97,6 @@ internal SummaryTable(Summary summary, SummaryStyle style = null)
bool hide = summary.ColumnHidingRules.Any(rule => rule.NeedToHide(column));
Columns[i] = new SummaryTableColumn(this, i, column, hide);
}

EffectiveSummaryStyle = style;
}

public class SummaryTableColumn
Expand All @@ -123,7 +122,7 @@ public SummaryTableColumn(SummaryTable table, int index, IColumn column, bool hi
IsDefault = table.IsDefault[index];
OriginalColumn = column;

Justify = column.IsNumeric ? TextJustification.Right : TextJustification.Left;
Justify = column.IsNumeric ? table.EffectiveSummaryStyle.NumericColumnJustification : table.EffectiveSummaryStyle.TextColumnJustification;

bool needToShow = column.AlwaysShow || Content.Distinct().Count() > 1;
NeedToShow = !hide && needToShow;
Expand Down
37 changes: 31 additions & 6 deletions src/BenchmarkDotNet/Reports/SummaryTableExtensions.cs
Expand Up @@ -48,7 +48,8 @@ public static void PrintLine(this SummaryTable table, string[] line, ILogger log
}

public static void PrintLine(this SummaryTable table, string[] line, ILogger logger, string leftDel, string rightDel,
bool highlightRow, bool startOfGroup, MarkdownExporter.MarkdownHighlightStrategy startOfGroupHighlightStrategy, string boldMarkupFormat, bool escapeHtml)
bool highlightRow, bool startOfGroup, MarkdownExporter.MarkdownHighlightStrategy startOfGroupHighlightStrategy, string boldMarkupFormat,
bool escapeHtml)
{
for (int columnIndex = 0; columnIndex < table.ColumnCount; columnIndex++)
{
Expand Down Expand Up @@ -82,23 +83,47 @@ public static void PrintLine(this SummaryTable table, string[] line, ILogger log
private static string BuildStandardText(SummaryTable table, string[] line, string leftDel, string rightDel, int columnIndex)
{
var buffer = GetClearBuffer();
var isBuildingHeader = table.FullHeader[columnIndex] == line[columnIndex];
var columnJustification = isBuildingHeader ? SummaryTable.SummaryTableColumn.TextJustification.Left : table.Columns[columnIndex].Justify;

buffer.Append(leftDel);
PadLeft(table, line, leftDel, rightDel, columnIndex, buffer);
if (columnJustification == SummaryTable.SummaryTableColumn.TextJustification.Right)
{
AddPadding(table, line, leftDel, rightDel, columnIndex, buffer);
}

buffer.Append(line[columnIndex]);
buffer.Append(rightDel);

if (columnJustification == SummaryTable.SummaryTableColumn.TextJustification.Left)
{
AddPadding(table, line, leftDel, rightDel, columnIndex, buffer);
}
var isLastColumn = columnIndex == table.ColumnCount - 1;
buffer.Append(isLastColumn ? rightDel.TrimEnd() : rightDel);

return buffer.ToString();
}

private static string BuildBoldText(SummaryTable table, string[] line, string leftDel, string rightDel, int columnIndex, string boldMarkupFormat)
{
var buffer = GetClearBuffer();
var isBuildingHeader = table.FullHeader[columnIndex] == line[columnIndex];
var columnJustification = isBuildingHeader ? SummaryTable.SummaryTableColumn.TextJustification.Left : table.Columns[columnIndex].Justify;

buffer.Append(leftDel);
PadLeft(table, line, leftDel, rightDel, columnIndex, buffer);
if (columnJustification == SummaryTable.SummaryTableColumn.TextJustification.Right)
{
AddPadding(table, line, leftDel, rightDel, columnIndex, buffer);
}

buffer.AppendFormat(boldMarkupFormat, line[columnIndex]);
buffer.Append(rightDel);

if (columnJustification == SummaryTable.SummaryTableColumn.TextJustification.Left)
{
AddPadding(table, line, leftDel, rightDel, columnIndex, buffer);
}
var isLastColumn = columnIndex == table.ColumnCount - 1;
buffer.Append(isLastColumn ? rightDel.TrimEnd() : rightDel);

return buffer.ToString();
}
Expand All @@ -116,7 +141,7 @@ private static StringBuilder GetClearBuffer()
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void PadLeft(SummaryTable table, string[] line, string leftDel, string rightDel, int columnIndex, StringBuilder buffer)
private static void AddPadding(SummaryTable table, string[] line, string leftDel, string rightDel, int columnIndex, StringBuilder buffer)
{
const char space = ' ';
const int extraWidth = 2; // " |".Length is not included in the column's Width
Expand Down
Expand Up @@ -7,9 +7,9 @@ Frequency: 2531248 Hz, Resolution: 395.0620 ns, Timer: TSC
DefaultJob : extra output line


Method | ParamProperty | Mean | Error | StdDev |
Method | ParamProperty | Mean | Error | StdDev |
---------- |-------------- |---------:|--------:|--------:|
Benchmark | False | 102.0 ns | 6.09 ns | 1.58 ns | ^
Benchmark | True | 202.0 ns | 6.09 ns | 1.58 ns | ^
Benchmark | False | 102.0 ns | 6.09 ns | 1.58 ns | ^
Benchmark | True | 202.0 ns | 6.09 ns | 1.58 ns | ^

Errors: 0

0 comments on commit b035d90

Please sign in to comment.